Initial commit — 2026-07-15
This commit is contained in:
@@ -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**
|
||||||
@@ -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
|
||||||
@@ -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?
|
||||||
@@ -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
|
||||||
|
```
|
||||||
|
┌──────────────────────────────────────┐
|
||||||
|
│ │
|
||||||
|
│ <normal content playing> │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ ┌────────────────────────────┐ │
|
||||||
|
│ │ 🔍 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 <device-id>
|
||||||
|
# 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
|
||||||
|
<div id="deviceIdOverlay"
|
||||||
|
style="display:none;position:fixed;bottom:16px;right:16px;
|
||||||
|
background:rgba(0,0,0,0.88);color:#fff;padding:14px 18px;
|
||||||
|
border-radius:10px;font-family:monospace;font-size:13px;
|
||||||
|
line-height:1.5;z-index:99999;max-width:320px;
|
||||||
|
backdrop-filter:blur(4px);">
|
||||||
|
<div style="display:flex;align-items:center;gap:8px;margin-bottom:6px;
|
||||||
|
font-size:15px;color:#f59e0b;">
|
||||||
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none"
|
||||||
|
stroke="currentColor" stroke-width="2">
|
||||||
|
<circle cx="12" cy="12" r="10"/><path d="M12 16v-4"/><circle cx="12" cy="8" r="1"/>
|
||||||
|
</svg>
|
||||||
|
Device Identity
|
||||||
|
</div>
|
||||||
|
<div id="devName" style="font-weight:700;margin-bottom:4px;"></div>
|
||||||
|
<div id="devSerial" style="color:#94a3b8;font-size:12px;"></div>
|
||||||
|
<div id="devIp" style="color:#94a3b8;font-size:12px;"></div>
|
||||||
|
<div id="devPlaylist" style="color:#94a3b8;font-size:12px;"></div>
|
||||||
|
<div id="devModel" style="color:#94a3b8;font-size:12px;"></div>
|
||||||
|
<div id="devLastSync" style="color:#64748b;font-size:11px;margin-top:4px;"></div>
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Implementation Status
|
||||||
|
📝 **Not yet built** — queued for player code phase
|
||||||
@@ -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)
|
||||||
|
```
|
||||||
|
┌──────────────────────────────────────┐
|
||||||
|
│ │
|
||||||
|
│ <content plays fullscreen> │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
└──────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
## 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)
|
||||||
Reference in New Issue
Block a user