commit 55609f6e91f7c6b17d06b3c1a0b0852e183b9c73 Author: root Date: Wed Jul 15 17:32:04 2026 -0400 Initial commit — 2026-07-15 diff --git a/README.md b/README.md new file mode 100644 index 0000000..41655d7 --- /dev/null +++ b/README.md @@ -0,0 +1,124 @@ +# Digital Signage CMS — Project Overview + +## Concept +Multi-tenant digital signage platform. Customers upload content (images, video) via a web portal, create playlists, and Raspberry Pi players pull and display content in fullscreen kiosk mode. + +## Architecture + +``` +┌─────────────────────────────┐ ┌──────────────────────────────┐ +│ Customer Portal (Web) │ │ Raspberry Pi Player │ +│ │ │ │ +│ ┌───────────────────────┐ │ │ ┌────────────────────────┐ │ +│ │ Login (auth per │ │ │ │ Registration script │ │ +│ │ customer/tenant) │ │ │ │ P: POST /api/register │ │ +│ ├───────────────────────┤ │ │ │ → gets device token │ │ +│ │ Dashboard │ │ │ ├────────────────────────┤ │ +│ │ - My Screens │ │ │ │ Polling script │ │ +│ │ - Content Library │ │ │ │ GET /api/playlist/:id │ │ +│ │ - Playlists │ │ │ │ → returns content URLs │ │ +│ │ - Upload │ │ │ ├────────────────────────┤ │ +│ ├───────────────────────┤ │ │ │ Chromium kiosk │ │ +│ │ Admin (ITPP) │ │ │ │ --kiosk --no-sandbox │ │ +│ │ - All tenants │ │ │ └────────────────────────┘ │ +│ │ - Device management │ │ └──────────────────────────────┘ +│ └───────────────────────┘ │ +└─────────────────────────────┘ + │ + ▼ + ┌─────────────────┐ + │ API + Storage │ + │ │ + │ FastAPI backend │ + │ SQLite/S3 │ + │ per tenant │ + └─────────────────┘ +``` + +## Multi-Tenant Architecture + +### Tenant Hierarchy +``` +Signage CMS (Platform) +├── Tenant: Joe's Diner +│ ├── User: alice@joesdiner.com (admin) +│ ├── Device: Main Lobby Pi +│ │ ├── Serial: 10000000a1b2c3d4 +│ │ ├── Status: Online +│ │ └── Playlist: Main Menu Loop +│ ├── Device: Menu Board Pi +│ │ ├── Serial: 10000000e5f6g7h8 +│ │ ├── Status: Online +│ │ └── Playlist: Daily Specials +│ ├── Device: Waiting Room Pi +│ │ ├── Serial: 10000000i9j0k1l2 +│ │ ├── Status: Offline +│ │ └── Playlist: Promo Rotation +│ └── Content: 8 items (4 images, 4 videos) +│ +├── Tenant: Main Street Gym +│ ├── User: owner@mainstreetgym.com +│ └── Device: Front Desk Pi +│ +└── Tenant: Third Coast Dental + ├── User: frontdesk@thirdcoast.com + ├── Device: Waiting Room Pi + ├── Device: Exam Room Pi + └── Device: Hallway Pi +``` + +### Key Design Points +- **One customer = one tenant = unlimited devices** +- Each device registers independently with a unique code + serial +- Devices belong to a tenant, not directly to a user +- Multiple users can manage the same tenant (owner + staff accounts) +- Content libraries are per-tenant (shared across all their devices) +- Playlists can be assigned to one or multiple devices +- A device can only play ONE playlist at a time + +### Pi Registration Flow +``` +1. Customer gets Pi with a unique registration code (printed or emailed) +2. Pi boots → runs registration script +3. Script calls POST /api/register with code + Pi serial number +4. Server registers device under that tenant +5. Pi receives device_id + auth token +6. Pi starts polling GET /api/playlist/:device_id every 60s +7. CMS pushes updates → Pi picks up new playlist on next poll +``` + +### Registration Script (on Pi) +```bash +#!/bin/bash +# register-pi.sh +REG_CODE="$1" +PI_SERIAL=$(cat /proc/cpuinfo | grep Serial | awk '{print $3}') +API="https://signage.itpropartner.com/api" + +curl -X POST "$API/register" \ + -H "Content-Type: application/json" \ + -d "{\"code\":\"$REG_CODE\",\"serial\":\"$PI_SERIAL\"}" +``` + +## MVP Scope (Phase 1) +| Feature | Status | +|---------|--------| +| Customer login | To build | +| Content upload (images + video) | To build | +| Simple playlist (ordered) | To build | +| Pi kiosk player script | To build | +| Multi-tenant (tenant isolation) | To build | +| Cloudflare proxy | Future | +| Scheduling | Future | +| Shuffled/random play | Future | +| 4K detection via EDID | To research | + +## Stack +- **Backend:** FastAPI + SQLite (same pattern as ops portal) +- **Frontend:** Dark theme, mobile-responsive (PWA-ready) +- **Player:** Raspberry Pi 3/4/5, Chromium kiosk, auto-updating +- **Storage:** Local filesystem → S3 in future +- **Auth:** JWT per tenant, admin override + +## Status +📝 **Project created — MVP design underway** diff --git a/cms/customer-setup-guide.md b/cms/customer-setup-guide.md new file mode 100644 index 0000000..a8645ad --- /dev/null +++ b/cms/customer-setup-guide.md @@ -0,0 +1,123 @@ +# Customer Setup Guide — Digital Signage Player + +## What's in the Box +- 1x Raspberry Pi (the small computer) +- 1x Power adapter (USB-C) +- 1x HDMI cable +- 1x SD card (already installed) + +## What You Need +- A TV or monitor with HDMI input +- Internet connection (WiFi or Ethernet) +- A web browser (Chrome, Safari, Edge) + +--- + +## Step 1: Plug It In + +``` +1. Connect HDMI cable → Pi → TV +2. Connect power cable → Pi → wall outlet +3. TV screen will show a registration screen +``` + +**Pi Location:** Place behind or below the TV where it's out of sight. The Pi's green light should be solid (flashing means it's starting up). + +--- + +## Step 2: Get Your Registration Code + +**From your computer or phone:** + +1. Go to **https://signage.itpropartner.com** +2. Log in to your account +3. Click **Devices** → **Register New Device** +4. A registration code will appear: e.g. **`SIGN-4K7P-M9X2`** +5. Keep this screen open + +--- + +## Step 3: Activate Your Player + +**Look at the TV screen.** You should see a registration screen with a code entry field. + +| If You See | Action | +|---|---| +| A code entry screen | Type your registration code from Step 2 and press **Activate** | +| Content already playing | Your player is already registered — you're done! | +| A black screen | Check HDMI cable is fully inserted. Try a different HDMI port. | + +**After activation:** The screen will refresh and begin playing content within 30 seconds. + +--- + +## Step 4: Assign Content + +**Back on your computer:** + +1. Go to **https://signage.itpropartner.com** → **Content** +2. Click **Upload** → select images or videos from your computer +3. Go to **Playlists** → **Create Playlist** +4. Name your playlist (e.g. "Lunch Menu") +5. Drag your content into the playlist in the order you want +6. Click **Assign to Device** → select your player +7. Content appears on your TV within 60 seconds + +--- + +## Troubleshooting + +### Screen says "No Signal" +- Make sure the TV is on the correct HDMI input +- Try a different HDMI cable +- Ensure the Pi's green light is on (solid or flashing) + +### Screen shows registration screen but won't activate +- Double-check the code was typed exactly (codes are case-sensitive) +- Code refreshes every 15 minutes — click "Get New Code" on the CMS +- Make sure the Pi has internet (Ethernet cable plugged in, or WiFi configured) + +### Content isn't updating +- Players check for new content every 60 seconds +- Click **Sync Now** on the Devices page to force an immediate update +- If still not updating, reboot the Pi by unplugging power and plugging it back in + +### Pi won't turn on +- Green light should be on. No light = try a different power outlet +- Red light only = SD card issue. Contact support for a replacement card + +### I need to move the player to a different TV +- Just unplug HDMI from current TV and plug into new TV +- The player will automatically adjust to the new screen resolution +- No configuration needed + +--- + +## Quick Reference Card (Print This) + +``` +┌─────────────────────────────────────┐ +│ DIGITAL SIGNAGE QUICK START │ +│ │ +│ 1. HDMI → Pi → TV │ +│ 2. Power → Pi → Wall │ +│ 3. Wait for registration screen │ +│ 4. Login at signage.itpropartner.com│ +│ 5. Devices → Register New Device │ +│ 6. Enter code on screen │ +│ 7. Content → Upload → Playlists │ +│ 8. Assign playlist to device │ +│ 9. Done. Content plays. │ +│ │ +│ Need help? support@itpropartner.com │ +└─────────────────────────────────────┘ +``` + +## What comes next? +Once your player is running, you can: +- **Upload more content** anytime from the Content page +- **Schedule content** to change at specific times (coming soon) +- **Add more players** to show different content on different TVs +- **Invite team members** to manage your content (multi-user coming soon) + +**Support:** Email support@itpropartner.com diff --git a/optisigns-comparison.md b/optisigns-comparison.md new file mode 100644 index 0000000..99cf053 --- /dev/null +++ b/optisigns-comparison.md @@ -0,0 +1,106 @@ +# OptiSigns Feature Replication — Build Plan + +## What OptiSigns Costs You +| Plan | Price | Per | +|---|---|---| +| **Standard** | $10/mo | Unlimited screens | +| **Pro** | $15/mo | Unlimited screens | +| **Pro Plus** | $30/mo | Unlimited screens | +| Add-on: Video Wall | $25/mo extra | | +| Add-on: Background Music | $15/mo extra | | + +## Feature Comparison — OptiSigns vs Our Build + +### Tier 1 — MVP (Phase 1, ~2 weeks) +| Feature | OptiSigns | Our Build | +|---|---|---| +| Upload images + videos | ✅ | ✅ | +| Playlist (ordered sequence) | ✅ | ✅ | +| Playlist (shuffle mode) | ✅ | ✅ | +| Multi-tenant customers | ✅ | ✅ | +| Unlimited devices per customer | ✅ | ✅ | +| Pi registration via code | ❌ (Fire TV/Android) | ✅ (custom) | +| Fullscreen kiosk playback | ✅ | ✅ | +| Device identification overlay | ❌ | ✅ (planned) | +| **Monthly cost** | **$10-$30** | **$0 (on your infra)** | + +### Tier 2 — Parity (Phase 2, ~1 week) +| Feature | OptiSigns | Our Build | +|---|---|---| +| Content scheduling (time-based) | ✅ | ⬜ | +| Multi-user per tenant | ✅ | ⬜ | +| Thumbnail preview in CMS | ✅ | ⬜ | +| Drag-drop reorder playlist | ✅ | ⬜ | +| Device auto-update on content change | ✅ | ⬜ | +| **Monthly cost** | **$15** | **$0** | + +### Tier 3 — Surpass (Phase 3, ~1 week) +| Feature | OptiSigns | Our Build | +|---|---|---| +| API for remote control | ✅ | ✅ (built-in) | +| Custom branding (no OptiSigns logo) | ❌ ($30 plan) | ✅ (ours) | +| Device identification (Ctrl+I) | ❌ | ✅ | +| Sho'Nuff integration (text-to-broadcast) | ❌ | ✅ | +| Dark ops-portal theme | ❌ | ✅ | +| **Monthly cost** | **$30** | **$0** | + +## Phase 1 — Feature Descriptions (MVP) + +### Content Management +- Upload images (.jpg, .png, .webp) and videos (.mp4, .mov, .webm) +- View in grid with thumbnails +- Delete content +- per-tenant content library (customers only see their own) + +### Playlist Builder +- Create playlist → name it → drag items into order +- Toggle shuffle mode (ordered vs random loop) +- Assign playlist to one or multiple devices +- Auto-play from first item when assigned + +### Device Management +- See all devices per customer with status (Online/Offline) +- View device details: name, serial, IP, model, last seen, current playlist +- Assign playlist to device +- Remote identify (triggers overlay on screen) +- Registration code generator + +### Pi Player +- Chromium fullscreen kiosk mode +- Polls API every 60s for playlist +- Preloads next media while current plays +- Loops playlist (ordered or shuffled) +- Hidden identify overlay (Ctrl+I × 3) +- Auto-updates when playlist changes (no reboot needed) + +### Registration Flow +1. Customer portal generates registration code → displays as QR + text +2. Pi boots → runs registration script +3. Script calls API with code + serial +4. Server registers device under customer tenant +5. Pi receives token + device ID +6. Pi starts polling immediately + +## What OptiSigns Does That We WON'T Build (save $30/mo) + +| Feature | Why Skip | +|---|---| +| Microsoft 365/Google Workspace integration | Not relevant for signage | +| Power BI / Salesforce dashboard display | Overkill for restaurant menus | +| Interactive experiences (touch) | Not needed for basic signage | +| Video wall (multi-screen sync) | Rare use case, future | +| Background music licensing | Legal/complex, separate project | +| Wireless presentation (AeriCast) | Different product entirely | +| Enterprise SSO/SAML | Not needed yet | + +## Savings +| Service | Cost | After Cancel | +|---|---|---| +| **OptiSigns** | ~$15-30/mo | **$0** 🎉 | +| **Your infra** | $0 (already running) | $0 | +| **Net savings** | **~$180-360/yr** | | + +## What You're Really Paying For +OptiSigns is $10-30/mo for something you can run on a $5/mo netcup VPS with Pis you already have. The only value they add is the player app and the CMS UI — both of which we build once and own forever. + +Cancel OptiSigns as soon as Phase 1 is live. Want me to start building Phase 1 after Cartagena? diff --git a/player/device-identification.md b/player/device-identification.md new file mode 100644 index 0000000..76600d2 --- /dev/null +++ b/player/device-identification.md @@ -0,0 +1,102 @@ +# Device Identification — Support Overlay + +## Problem +When a customer calls support saying "my screen isn't working," we need to know WHICH of their devices we're talking to without relying on them to read a serial number off the back of a Pi. + +## Solution +Hidden overlay on the player that only appears when triggered. No customer sees it in normal operation. + +## How it looks +``` +┌──────────────────────────────────────┐ +│ │ +│ │ +│ │ +│ │ +│ ┌────────────────────────────┐ │ +│ │ 🔍 DEVICE │ │ +│ │ Main Lobby Pi │ │ +│ │ Serial: 10000000a1b2c3d4 │ │ +│ │ IP: 192.168.1.50 │ │ +│ │ Playlist: Main Menu Loop │ │ +│ │ Last sync: 30s ago │ │ +│ │ Model: RPi 4 Model B 4GB │ │ +│ └────────────────────────────┘ │ +└──────────────────────────────────────┘ +``` + +## Trigger Methods (build all 3) + +### 1. Keyboard shortcut — Ctrl+I pressed 3 times in 2s +On-site tech can trigger it without any tools. + +```javascript +var keyCount = 0, keyTimer; +document.addEventListener('keydown', function(e) { + if (e.ctrlKey && e.key === 'i') { + keyCount++; + clearTimeout(keyTimer); + keyTimer = setTimeout(function() { keyCount = 0; }, 2000); + if (keyCount >= 3) { showOverlay(); keyCount = 0; } + } +}); +``` + +### 2. Remote API trigger +I call this from the ops portal — device polls for the flag. + +```javascript +// Player polls every 10s +setInterval(function() { + fetch('/api/device/' + deviceId + '/status') + .then(function(r) { return r.json(); }) + .then(function(d) { if (d.identify) showOverlay(); }); +}, 10000); +``` + +API endpoint: `POST /api/devices/:id/identify` (returns `{identify: true}`, auto-clears after 30s) + +### 3. Boot-time flag +For when support asks customer to reboot with a specific trigger: + +```bash +# On the Pi +curl -s https://signage.itpropartner.com/identify.sh | bash -s +# Rebooting with this arg causes the player to start in visibility mode +./kiosk.sh --identify +``` + +## Overlay Behavior +- **Auto-hides** after 30 seconds +- Can be dismissed early with another Ctrl+I +- **No click-away** — intentionally no close button (onsite tech might fat-finger) +- Refresh/DST resets it back to hidden +- **No data exfiltration** — shows only device metadata, no passwords or tokens + +## Overlay HTML (skeleton) +```html + +``` + +## Implementation Status +📝 **Not yet built** — queued for player code phase diff --git a/player/provisioning.md b/player/provisioning.md new file mode 100644 index 0000000..8f631a5 --- /dev/null +++ b/player/provisioning.md @@ -0,0 +1,192 @@ +# Raspberry Pi Provisioning — Zero-Touch Player Setup + +## Goal +Customer unboxes Pi → plugs in power → content plays. That's it. No keyboard, no monitor, no SSH. + +## Provisioning Flow + +### Step 1: Image the SD Card (We Do This) +Using Raspberry Pi Imager (CLI version for batch flashing): + +```bash +# One-time image prep +cat << 'EOF' > /tmp/signage-firstboot.sh +#!/bin/bash +# Runs on first boot only — sets up the player + +# 1. Set hostname +hostnamectl set-hostname signage-player + +# 2. Enable auto-login to console (required for kiosk mode) +raspi-config nonint do_boot_behaviour B2 + +# 3. Install dependencies +apt-get update && apt-get install -y chromium-browser unclutter x11-xserver-utils + +# 4. Create player service +cat > /etc/systemd/system/signage-player.service << 'SRVEOF' +[Unit] +Description=Digital Signage Player +After=network-online.target +Wants=network-online.target + +[Service] +User=pi +Environment=DISPLAY=:0 +ExecStartPre=/usr/bin/sleep 5 +ExecStart=/usr/bin/bash /home/pi/player.sh +Restart=always +RestartSec=10 + +[Install] +WantedBy=multi-user.target +SRVEOF + +# 5. Create player script +cat > /home/pi/player.sh << 'PLAYEOF' +#!/bin/bash +# Kiosk player — registers if new, plays if registered + +DEVICE_ID_FILE="/home/pi/.device-id" +TOKEN_FILE="/home/pi/.device-token" +API="https://signage.itpropartner.com/api" +REG_CODE="${1:-}" + +# Hide mouse cursor +unclutter -idle 0.1 & + +# Turn off screen blanking +xset s off +xset s noblank +xset -dpms + +# Register if not yet registered +if [ ! -f "$DEVICE_ID_FILE" ] && [ -n "$REG_CODE" ]; then + SERIAL=$(cat /proc/cpuinfo | grep Serial | head -1 | awk '{print $3}') + RESPONSE=$(curl -s -X POST "$API/devices/register" \ + -H "Content-Type: application/json" \ + -d "{\"code\":\"$REG_CODE\",\"serial\":\"$SERIAL\"}") + echo "$RESPONSE" | python3 -c "import sys,json;d=json.load(sys.stdin);open('$DEVICE_ID_FILE','w').write(str(d['id']));open('$TOKEN_FILE','w').write(d['token'])" 2>/dev/null +fi + +# Read device ID +DEVICE_ID=$(cat "$DEVICE_ID_FILE" 2>/dev/null || echo "") + +# Launch kiosk browser +if [ -n "$DEVICE_ID" ]; then + chromium-browser --kiosk \ + --no-sandbox \ + --disable-infobars \ + --disable-session-crashed-bubble \ + --disable-features=Translate \ + --noerrdialogs \ + "$API/player/$DEVICE_ID" +else + # Show registration screen with QR code + chromium-browser --kiosk \ + --no-sandbox \ + "$API/player/register?code=$REG_CODE" +fi +PLAYEOF + +chmod +x /home/pi/player.sh +chown pi:pi /home/pi/player.sh + +# 6. Enable service +systemctl enable signage-player.service + +# 7. Clean up (remove self) +rm /boot/signage-firstboot.sh +EOF +``` + +### Step 2: Flash + Inject Registration (We Do) +```bash +# Using Raspberry Pi Imager CLI on your machine +rpi-imager-cli --write raspios-lite.img --device /dev/sdX + +# Mount boot partition and inject first-boot script +mount /dev/sdX1 /mnt +cp /tmp/signage-firstboot.sh /mnt/ +umount /mnt +``` + +### Step 3: Ship to Customer +Pi arrives at customer location. They plug in: +1. Power (USB-C) +2. HDMI to TV +3. Ethernet (or WiFi pre-configured) + +**That's it.** No keyboard needed. No monitor for setup. + +## What the Customer Sees + +### First Boot (Unregistered) +``` +┌──────────────────────────────────────┐ +│ │ +│ DIGITAL SIGNAGE │ +│ │ +│ ┌────────────────────────────┐ │ +│ │ This screen needs to be │ │ +│ │ activated. │ │ +│ │ │ │ +│ │ Registration Code: │ │ +│ │ SIGN-4K7P-M9X2 │ │ +│ │ │ │ +│ │ [████████████░░░░░░] │ │ +│ │ Waiting for activation...│ │ +│ └────────────────────────────┘ │ +│ │ +└──────────────────────────────────────┘ +``` + +### After Registration (Normal Operation) +``` +┌──────────────────────────────────────┐ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +│ │ +└──────────────────────────────────────┘ +``` + +## Recovery / Re-provisioning + +### If Pi needs to be re-registered (different customer): +```bash +# On Pi via SSH or USB keyboard: +rm /home/pi/.device-id /home/pi/.device-token +sudo reboot +``` +Then register via the CMS with a new code. + +### If customer wants to move Pi to a different TV: +Just plug HDMI into the new TV. Pi auto-detects resolution via EDID. No config needed. + +### If SD card dies: +Flash a new one (same image) → insert → Pi boots → waiting for registration → activate from CMS. 5 minutes total. + +## Startup Behavior Summary +| Event | What Happens | +|---|---| +| Power on | Boot → auto-login → start player service | +| Network connected | Register or poll for playlist | +| Content fetched | Fullscreen Chromium kiosk | +| Signal loss | Content cached locally, keeps playing | +| Power loss | Auto-restart on power return | +| Reboot | Same as power on | +| SD card failure | Replace card, re-register | +| TV resolution change | Auto-detected via HDMI EDID | + +## Provisioning Checklist (For Us) +- [ ] Prepare base Raspberry Pi OS lite image +- [ ] Add first-boot script (auto-login, dependencies, service) +- [ ] Create player kiosk launch script +- [ ] Configure Chromium kiosk flags +- [ ] Test: flash → boot → play → power loss → reboot → play again +- [ ] Package for batch flashing (10+ Pis at once)