---
name: digital-signage
description: |-
  Build and maintain a multi-tenant digital signage CMS platform — Raspberry Pi kiosk players, customer upload portal, playlist management, device registration. Replaces OptiSigns ($15-30/mo).
author: Sho'Nuff
version: 1.0.0
tags: [raspberry-pi, signage, kiosk, multi-tenant, content-management]
platforms: [linux]
---

# Digital Signage CMS

Multi-tenant digital signage platform. Customers upload images/video via a web portal, create playlists, and Raspberry Pi players display content in fullscreen kiosk mode.

## Project Directory

All docs live at `/root/projects/digital-signage/`. Key files:

| File | Contents |
|------|----------|
| `README.md` | Full architecture, multi-tenant hierarchy, Pi registration flow, MVP scope |
| `optisigns-comparison.md` | Feature-by-feature comparison vs OptiSigns (what we build vs what we skip) |
| `cms/customer-setup-guide.md` | Customer-facing 9-step quick-start guide (printable) |
| `player/provisioning.md` | Zero-touch Pi provisioning — first-boot script, SD card imaging, recovery |
| `player/device-identification.md` | Hidden Ctrl+I overlay for support — device name, serial, IP, playlist |
| `mockups/` | Frontend HTML mockups (customer portal) |

## Architecture

```
Customer Web Portal                  Raspberry Pi Player
┌────────────────────────┐           ┌──────────────────────┐
│ Login (per tenant)     │           │ Registration script  │
│ Upload images/video    │  API      │ POST /api/register   │
│ Create playlists       │ ←───────→ │ Poll /api/playlist   │
│ Manage devices         │           │ Chromium kiosk mode  │
│ Registration codes     │           │ Fullscreen playback  │
└────────────────────────┘           └──────────────────────┘
              │
              ▼
     FastAPI + SQLite + S3
```

### Multi-tenant hierarchy
```
Platform
├── Tenant: Joe's Diner
│   ├── Users: admin, staff@...
│   ├── Device: Main Lobby Pi
│   ├── Device: Menu Board Pi
│   └── Content: 8 items, 2 playlists
├── Tenant: Main Street Gym
│   └── Device: Front Desk Pi
└── Tenant: Third Coast Dental
    ├── Device: Waiting Room Pi
    ├── Device: Exam Room Pi
    └── Device: Hallway Pi
```

Key rule: **one customer = one tenant = unlimited devices.** Content libraries are per-tenant. Playlists assignable to one or multiple devices.

## OptiSigns Replication

| Tier | What's Included | Timeline |
|------|----------------|----------|
| **Phase 1 — MVP** | Upload images/video, playlists (ordered + shuffle), multi-tenant, Pi registration, fullscreen kiosk, device identification overlay | ~2 weeks |
| **Phase 2 — Parity** | Content scheduling, multi-user per tenant, thumbnail previews, auto-update on content change | ~1 week |
| **Phase 3 — Surpass** | Sho'Nuff text-to-broadcast integration, ops portal dark theme, no OptiSigns logo, API for remote control | ~1 week |

**What we skip:** Power BI dashboards, touch interactivity, video walls, background music licensing, wireless presentation.

## Pi Provisioning — Zero Touch

1. **Image SD card** with Raspberry Pi OS lite + first-boot script (auto-login, Chromium, player service)
2. **Generate registration code** from CMS
3. **Ship Pi** to customer (HDMI + power cable included)
4. **Customer plugs in** — two cables: HDMI + power
5. **Pi boots**, auto-registers, fetches playlist, plays content

**On power loss/reboot:** Auto-restarts and resumes. Already registered → skips to content.
**On SD card failure:** Flash new card → 5 min fix → re-register from CMS.
**On TV change:** Pi auto-detects resolution via HDMI EDID. No config needed.

## Device Registration Flow

```
1. Customer portal → Devices → Register New Device → code: SIGN-4K7P-M9X2
2. Pi boots → runs registration script → POST /api/register with code + serial
3. Server: creates device under tenant → returns device_id + token
4. Pi stores token locally → starts polling GET /api/playlist/:device_id every 60s
5. CMS playlist change → Pi picks it up on next poll
```

## Device Identification Overlay

Hidden overlay triggered by Ctrl+I pressed 3 times in 2 seconds (or remote API call). Shows device name, serial, IP, playlist, model, last sync. Auto-hides after 30s. No customer sees it in normal operation.

### 3 trigger methods:
1. **Keyboard:** `Ctrl+I` × 3 within 2 seconds
2. **Remote API:** `POST /api/devices/:id/identify` (device polls `/status` every 10s)
3. **Boot flag:** `./kiosk.sh --identify`

## Resolution Handling

**Pi auto-detects via HDMI EDID.** Upload content at highest quality (4K if you have it). Pi scales to whatever display is connected. CMS does NOT need to know or care about output resolution — that's the Pi's job.

| Pi Model | Max Output | Best For |
|----------|-----------|----------|
| Pi 3 | 1080p @ 60fps | Stills, basic slideshows |
| Pi 4 | 4K @ 60fps | Mixed content, smooth video |
| Pi 5 | 4K @ 60fps HDR | Heavy video, multiple layers |

## Frontend Portal Mockup

The customer portal mockup lives at `ops.itpropartner.com/signage-mockup.html` — dark theme, 6 views (login, dashboard, content, playlists, devices, register Pi modal). Demo tenant: Joe's Diner with 3 devices.

## Pitfalls

- **Pi registration code expiry** — codes should auto-expire after 24h to prevent reuse/snooping
- **Offline playback** — player should cache the current playlist locally so it keeps playing if network drops
- **Content moderation** — uploaded files need type/size validation before storage. Allow common image formats (jpg, png, webp) and video (mp4, mov, webm). Reject executables, archives, scripts
- **Multi-user per tenant** — Phase 2 feature but plan the DB schema for it now (users table with tenant_id FK)
- **Scheduling complexity** — time-based playlists need a cron-like scheduler on the backend, not just the player polling interval
- **Player self-healing** — if Chromium crashes (OOM on Pi 3), the service should auto-restart. Player script needs `Restart=always` in the systemd unit
- **Bandwidth** — multiple Pis polling every 60s is negligible. Multiple Pis fetching 140MB video files simultaneously is not. Consider S3 signed URLs or CDN for content delivery at scale
