Contents — 26 sections
Project Overview
Manages a fleet of public displays from one place — scheduling content remotely, keeping every screen showing the right thing, and never a blank or frozen screen in public.
A network of public displays — in transit stations, malls, campuses, lobbies, on the street — is only useful if the right content reaches the right screen at the right time, and stays showing. Manage them by walking a USB stick to each screen and you have a system that is always out of date, and where a crashed player leaves a blank or frozen screen in a public place for days. This project builds the controller that fixes that: each display becomes a networked node that plays scheduled content pushed from a central system, and that keeps itself showing correctly — the two things digital signage lives or dies by, remote content management and reliability.
On the content side, a central CMS defines playlists and schedules — what plays, in what order, and when (dayparting: different content by time of day; different content per screen or group) — and pushes them to the players over the network, so updating every screen in a city is a few clicks, not a fleet of trips. Each player caches its content locally so it keeps playing correctly even if the network drops (a signage screen must not go blank because the Wi-Fi hiccupped), and syncs new content and schedules when connected.
On the reliability side, each player watches itself and reports health — is it online, is it actually displaying (not frozen or crashed), what is it showing — so the operator knows the true state of every screen and a fault is caught centrally instead of by an embarrassed passer-by. A watchdog restarts a hung player and falls back to safe default content rather than showing a blank screen or a desktop/error in public. Networked over Wi-Fi/Ethernet to the CMS, this scales to a whole estate of screens, remotely scheduled and health-monitored. It is honest that real signage integrates with a proper CMS and content pipeline and that content licensing/appropriateness is the operator's responsibility. But as a remote-scheduling, self-healing, health-reporting signage controller, it turns a set of unmanaged screens into a fleet that always shows the right, current content — and never an embarrassing blank in public.
What this project does
- Plays scheduled content (playlists, dayparting) pushed from a central CMS
- Targets content per screen or group remotely
- Caches content locally so it keeps playing through network drops
- Watches itself — restarts a hung player, falls back to safe default content
- Reports health and what it is showing to the operator
- Catches faults centrally instead of via a passer-by
- Scales to a whole estate of remotely-managed screens
Real-World Applications
| Setting | How it is used |
|---|---|
| Public information displays | Transit, campus and civic screens with remotely-scheduled, always-current content. |
| Retail / advertising signage | Dayparted, per-location content and reliable uptime across many screens. |
| Corporate / lobby displays | Central management and health monitoring of building screens. |
| Menu boards / wayfinding | Remotely-updated, self-healing displays that never show a blank. |
Deployment contexts where a build of this kind earns its keep.
Features & Capabilities
- Remote playlist/schedule management (dayparting, per-screen/group)
- Local content caching (plays through outages)
- Self-healing watchdog + safe fallback (never blank/error in public)
- Health/status reporting (online, displaying, showing what)
- Central fault visibility
- Estate-scale management
- Honest about CMS integration and content responsibility
Difficulty, Time & Required Skills
| Attribute | Value |
|---|---|
| Difficulty level | Intermediate |
| Estimated completion time | 12–18 hours |
| Indicative build cost | ₹5,000 – ₹8,000 per screen (+ CMS) |
| Primary discipline | Smart City |
| Reference platform | Raspberry Pi 4 Model B (4 GB) |
Skills you should have (or will pick up)
- Networked media playback and scheduling
- Playlist/dayparting and per-screen targeting
- Local caching and offline playback
- Watchdog/self-healing and safe fallback
- Health reporting and central management
Bill of Materials
Every part below is commonly available from Indian and international hobby-electronics suppliers. Prices are indicative 2026 retail figures in Indian rupees and will drift — treat them as a budgeting guide, not a quotation.
| Component | Key specification | Qty | Approx. cost |
|---|---|---|---|
| Raspberry Pi 4 Model B (4 GB) Use an official 5 V 3 A supply — brown-outs from phone chargers corrupt SD cards. | Quad-core Cortex-A72 @ 1.8 GHz, 4 GB LPDDR4, Gigabit Ethernet, Wi-Fi 5, BT 5.0, 2× USB 3.0, 40-pin GPIO | 1 | ₹5,800 |
| ESP32 DevKit V1 (ESP-WROOM-32) Wi-Fi transmit bursts peak near 500 mA — size the regulator accordingly. | Dual-core Xtensa LX6 @ 240 MHz, 520 KB SRAM, 4 MB flash, Wi-Fi 802.11 b/g/n + BLE 4.2, 34 GPIO, 18× 12-bit ADC, 2× 8-bit DAC | 1 | ₹450 |
| 2.4″ ILI9341 SPI TFT (240 × 320) Backlight is most of the current — PWM it for battery builds. | 262 K colour, 40 MHz SPI, optional resistive touch controller | 1 | ₹750 |
| microSD card 32 GB A1 class For 24/7 loggers buy a high-endurance card — normal cards die in months. | A1 rated, 10 MB/s random write, UHS-I, endurance-grade recommended | 1 | ₹450 |
| 5 V 3 A regulated SMPS adapter Measure the real output — many "3 A" adapters sag below 4.7 V at 2 A. | 100–240 VAC in, 5 V ±5 % out, 3 A, short-circuit and over-voltage protection | 1 | ₹350 |
| Display + media player Pi plays media; ESP32 can drive simple/text displays | Public display + a media player (Raspberry Pi common) per screen | 1 | ₹4,500 |
| CMS + content pipeline Software/platform | Central system for playlists/schedules/health | 1 | — |
| Network + mount | Wi-Fi/Ethernet and secure screen mounting | 1 | ₹500 |
| Watchdog/health hardware | Hardware watchdog for self-healing (or software) | 1 | ₹200 |
Estimated total: ₹13,000, excluding tools, shipping and consumables.
Tools and consumables
- Soldering iron (temperature controlled, 350 °C) with 0.8 mm 60/40 or lead-free solder
- Digital multimeter — continuity, DC volts and current ranges
- Wire strippers, flush cutters and a small set of precision screwdrivers
- Heat-shrink tubing and a heat gun (or a lighter, carefully)
- A laptop with a USB port and the toolchain listed above
Hardware Specifications
| Part | Specification | Supply | Interface | Reference |
|---|---|---|---|---|
| Raspberry Pi 4 Model B (4 GB) | Quad-core Cortex-A72 @ 1.8 GHz, 4 GB LPDDR4, Gigabit Ethernet, Wi-Fi 5, BT 5.0, 2× USB 3.0, 40-pin GPIO | 5 V / 3 A USB-C | GPIO, SPI, I²C, UART, CSI, DSI | Datasheet |
| ESP32 DevKit V1 (ESP-WROOM-32) | Dual-core Xtensa LX6 @ 240 MHz, 520 KB SRAM, 4 MB flash, Wi-Fi 802.11 b/g/n + BLE 4.2, 34 GPIO, 18× 12-bit ADC, 2× 8-bit DAC | 3.3 V logic / 5 V USB | UART, SPI, I²C, I²S, CAN, PWM | Datasheet |
| 2.4″ ILI9341 SPI TFT (240 × 320) | 262 K colour, 40 MHz SPI, optional resistive touch controller | 3.3 V | SPI | Datasheet |
| microSD card 32 GB A1 class | A1 rated, 10 MB/s random write, UHS-I, endurance-grade recommended | 3.3 V | SDIO / SPI | Datasheet |
| 5 V 3 A regulated SMPS adapter | 100–240 VAC in, 5 V ±5 % out, 3 A, short-circuit and over-voltage protection | 5 V | DC barrel / USB | Datasheet |
Consolidated electrical and interface specifications for every active part in the build.
Power Budget & Supply Sizing
Add up the typical active current of every part, then size the supply with at least 50 % headroom so transmit bursts and motor inrush never brown out the controller.
| Load | Supply rail | Typical current (mA) | Notes |
|---|---|---|---|
| Raspberry Pi 4 Model B (4 GB) | 5 V / 3 A USB-C | 1200 | Use an official 5 V 3 A supply — brown-outs from phone chargers corrupt SD cards. |
| ESP32 DevKit V1 (ESP-WROOM-32) | 3.3 V logic / 5 V USB | 160 | Wi-Fi transmit bursts peak near 500 mA — size the regulator accordingly. |
| 2.4″ ILI9341 SPI TFT (240 × 320) | 3.3 V | 90 | Backlight is most of the current — PWM it for battery builds. |
| microSD card 32 GB A1 class | 3.3 V | 100 | For 24/7 loggers buy a high-endurance card — normal cards die in months. |
| 5 V 3 A regulated SMPS adapter | 5 V | 3000 | Measure the real output — many "3 A" adapters sag below 4.7 V at 2 A. |
Summed typical draw is 4550 mA. With a 1.5× design margin the supply should deliver at least 6900 mA continuously at the stated rail voltage.
Software Requirements & Development Environment
Reference toolchain: Raspberry Pi OS Bookworm (64-bit) + Python 3.11 + VS Code Remote-SSH. Anything newer normally works; anything older may lack the board definitions used here.
- Flash Raspberry Pi OS (64-bit) with Raspberry Pi Imager; pre-configure Wi-Fi, hostname and SSH in the Imager settings so the board comes up headless.
- Update first:
sudo apt update && sudo apt full-upgrade -y, then reboot. - Work inside a virtual environment —
python3 -m venv ~/venv && source ~/venv/bin/activate. Bookworm blocks system-widepip installby design. - Enable the buses you need with
sudo raspi-config→ Interface Options (I²C, SPI, Serial, Camera). - Develop over VS Code Remote-SSH so you edit on your laptop but run on the Pi.
Required libraries
| Library | Why it is needed | Install |
|---|---|---|
| Python 3.11+ | Runtime for the analysis, training and service code. | sudo apt install python3 python3-venv python3-pip |
| FastAPI + Uvicorn 0.115+ | Typed async REST API with automatic OpenAPI docs. | pip install fastapi uvicorn[standard] |
| SQLite 3.45+ | Zero-configuration embedded database for local logs. | Bundled with Python (`import sqlite3`) |
| Docker Engine 27+ | Reproducible deployment of the broker, database and dashboard. | curl -fsSL https://get.docker.com | sh |
| ffmpeg | — | — |
Block Diagram
The block diagram shows the functional decomposition of the system — what senses, what decides, what acts, and where the data ends up.
Circuit Diagram & Wiring
Every signal line in the build is shown below, followed by a pin-by-pin connection table you can work through with a multimeter in hand.
| Peripheral | Peripheral pin | Controller pin | Signal |
|---|---|---|---|
| Display | HDMI/TFT | — | Content output |
| Storage | SD/SSD | — | Local content cache |
| Network | Wi-Fi/Eth | — | CMS sync + health |
| Watchdog | reset | GPIO | Self-healing restart |
| Status | LED | GPIO | Health |
| Supply | 5V | — | Power |
Wire one row at a time and tick it off — most "it does not work" reports trace back to a single swapped pair.
Wiring explanation
- Each player drives its display and caches content locally so it plays correctly offline.
- Connect to the CMS over Wi-Fi/Ethernet for schedule/content sync and health reporting.
- Use a watchdog (hardware or software) to restart a hung player and fall back to safe default content.
- Never show a desktop, error or blank in public — always fall back to known-good content.
- Integrate with a proper CMS/content pipeline for a real deployment.
System Architecture
Read the stack from the bottom up: physical hardware, the firmware that drives it, the transport that moves data off the device, and the software a human actually looks at.
Working Principle
Digital signage is deceptively simple to demo and hard to run at scale, and the two properties that separate a real system from a laptop showing a slideshow are exactly what this controller provides: remote content management and reliability. Everything else — nice transitions, fancy layouts — is secondary to getting the right content onto every screen without visiting it, and keeping every screen showing correctly without an operator watching it.
Remote content management means content is defined centrally and pushed to players over the network. A CMS holds playlists (what plays and in what order) and schedules (when) — including dayparting (different content by time of day) and targeting (different content per screen or group) — and distributes them to the players. Updating a campaign across a city becomes a few clicks in the CMS rather than a trip to each screen with a USB stick, which is the entire operational value: content stays current everywhere, effortlessly. Crucially, each player caches its content locally, so it plays from local storage and keeps showing correctly even when the network drops — a public screen must never go blank because the Wi-Fi hiccupped — syncing new content and schedules opportunistically when connected.
Reliability is the other half, and it is about a player looking after itself in an unattended public place. A media player can crash, hang, or exit to a desktop or an error dialog — and a frozen or blank or error-showing screen in public is worse than embarrassing, it undermines the whole point. So each player runs a watchdog that detects a hung or failed player and restarts it, and — critically — falls back to safe default content (a known-good loop, a logo, an "information coming soon" card) rather than ever exposing a blank, a desktop, or an error. The screen is designed so that its worst failure mode is still presentable.
Tying it together is health reporting and central visibility. Each player reports its status — is it online, is it actually displaying (not merely powered), what is it currently showing — so the operator has a live, accurate picture of the whole estate and a fault is caught centrally (an alert in the CMS) rather than by an embarrassed member of the public phoning in. This closes the loop: the operator knows every screen's true state, faults surface to them first, and content is verifiably where it should be. The design is honest that a real deployment integrates with a proper CMS and content pipeline, and that content licensing and appropriateness are the operator's responsibility. But the core contribution is exactly what running public screens requires: manage content remotely so every screen is always current, and make each screen self-healing and self-reporting so it never shows an embarrassing blank and its faults reach the operator, not the public.
The maths behind it
Schedule / dayparting resolution
For a player P at time t:
active_playlist = schedule(P, t) (dayparting + targeting)
play items in order from the LOCAL cache
Content is defined centrally, resolved locally → current
content everywhere without visiting screens.
Offline continuity
Player plays from a local cache:
if online: sync new content/schedules opportunistically
if offline: keep playing cached content correctly
A network drop must NOT blank the screen.
Self-healing + safe fallback
watchdog: if player hung/exited → restart it
if no valid content/schedule → show SAFE DEFAULT content
(never a blank/desktop/error in public)
report health: {online, displaying, current_item} → CMS
Program Flowchart
The firmware is a single cooperative loop. Nothing blocks for long, so networking, sensing and the user interface all stay responsive.
Assembly Instructions
Build on a breadboard first and only commit to solder once the whole system has run for an hour without a fault.
Build the self-healing player
Each player: drive the display, cache content locally, play by schedule, and run a watchdog that restarts a hung player and falls back to safe default content.
Set up remote content management
A CMS defines playlists/schedules (dayparting, per-screen/group) and pushes them to players, which sync opportunistically.
Set up health monitoring
Each player reports online/displaying/current-item health to the CMS so faults are caught centrally.
Step-by-Step Implementation Guide
Work through these in order. Each step ends in something you can observe, so a failure is always localised to the step you just finished.
Play by schedule with local cache and fallback
Resolve the active playlist for the current time (dayparting/targeting) from the local cache, play it, and fall back to safe default content if none is valid.
pythonplayer.pyimport time class Player: def __init__(self, cache, watchdog): self.cache, self.wd = cache, watchdog def active_playlist(self, now): # dayparting + targeting resolved from the synced schedule return self.cache.schedule_for(now, this_screen()) def run(self): while True: self.wd.pet() # tell the watchdog we're alive pl = self.active_playlist(time.time()) if not pl or not self.cache.valid(pl): self.show_safe_default() # never blank/error in public continue for item in pl.items: if not self.cache.has(item): # missing media -> skip safely continue self.display(item) # play from LOCAL cache self.report_health(showing=item.id) self.wd.pet()self.wd.pet() # tell the watchdog we're aliveThe player regularly pets the watchdog; if it hangs and stops petting, the watchdog restarts it — the self-healing that keeps an unattended screen running.if not pl or not self.cache.valid(pl): self.show_safe_default()With no valid playlist or content, the player shows safe default content instead of ever exposing a blank, desktop or error in public.self.display(item) # play from LOCAL cacheContent plays from the local cache, so a network drop never blanks the screen — offline continuity by design.self.report_health(showing=item.id)The player reports what it is actually showing, so the operator knows the true state of every screen centrally.Manage centrally and monitor health
Define/push playlists and schedules from the CMS, sync to players, and monitor each player's health (online/displaying/showing) so faults surface centrally.
Complete Source Code
The listing below is complete and compiles as written — there are no elided sections. Read the annotations under each block before you upload it.
#!/usr/bin/env python3
"""
Digital Signage Controller — player node (Raspberry Pi)
Plays centrally-scheduled content from a local cache (offline-continuous),
self-heals with a watchdog + safe fallback (never blank/error in public),
and reports health (online/displaying/showing) to the CMS.
"""
import time, threading, requests
class Watchdog:
def __init__(self, timeout=30): self.timeout=timeout; self.last=time.time()
def pet(self): self.last=time.time()
def watch(self, on_hang):
while True:
if time.time()-self.last > self.timeout: on_hang() # restart player
time.sleep(5)
class SignagePlayer:
def __init__(self, cms, screen_id):
self.cms, self.screen = cms, screen_id
self.wd = Watchdog()
threading.Thread(target=self.wd.watch, args=(self.restart,), daemon=True).start()
threading.Thread(target=self.sync_loop, daemon=True).start()
def sync_loop(self): # pull schedule/content when online
while True:
try:
sched = requests.get(f"{self.cms}/schedule/{self.screen}", timeout=5).json()
self.cache_schedule(sched) # store locally for offline play
except requests.RequestException:
pass # offline: keep playing cached
time.sleep(60)
def report(self, displaying, showing):
try: requests.post(f"{self.cms}/health/{self.screen}",
json={"online": True, "displaying": displaying,
"showing": showing}, timeout=5)
except requests.RequestException: pass
def show_safe_default(self):
self.render("safe_default.mp4") # known-good; never blank/error
self.report(displaying=True, showing="safe_default")
def restart(self): self.render_restart() # watchdog fired -> restart player
def run(self):
while True:
self.wd.pet()
pl = self.local_playlist(time.time()) # dayparting/targeting
if not pl:
self.show_safe_default(); time.sleep(5); continue
for item in pl:
if not self.cached(item): continue # skip missing safely
self.render(item) # from LOCAL cache
self.report(displaying=True, showing=item)
self.wd.pet()
if __name__ == "__main__":
SignagePlayer(CMS_URL, SCREEN_ID).run()
Configuration & Calibration
Configuration steps
- Configure the CMS URL, screen ID/group, and the local content cache.
- Define playlists/schedules (dayparting, targeting) in the CMS.
- Configure the watchdog timeout and safe default content.
- Configure health reporting (online/displaying/showing).
Calibration procedure
An uncalibrated sensor produces confident, precise, wrong numbers. Do this once per physical unit and record the constants.
Offline continuity
Verify the player keeps playing correct content when the CMS/network is unavailable, and syncs on reconnect.
Self-healing
Confirm the watchdog restarts a hung player and that it falls back to safe default content — never a blank/error.
Health accuracy
Confirm health reflects actual display state (displaying/showing), not just power/network.
Network Architecture & Connectivity
Communication protocol
Players pull schedules/content and push health (online/displaying/showing); content plays from a local cache so outages do not blank screens.
| Topic / endpoint | Direction | Payload |
|---|---|---|
signage/<screen>/schedule | CMS → player | playlists/schedules (dayparting/targeting) |
signage/<screen>/health | player → CMS | online, displaying, showing |
signage/<screen>/alert | player → ops | fault (offline/frozen) |
Message contract between the device and the broker.
Cloud platform configuration
A CMS manages content/schedules across the estate, verifies what each screen shows, and alerts on faults — content current everywhere, faults caught centrally.
Dashboard setup
An estate view of screen status/content, remote scheduling/targeting, and fault alerts.
Mobile app integration
Fault alerts (offline/frozen screens) and content-update confirmations.
Security considerations
- Authenticate content pushes and health; secure the CMS/content pipeline.
- Cache locally and fail safe so outages never blank screens.
- Operator is responsible for content licensing/appropriateness.
Testing Procedure & Expected Output
Test from the bottom up. Confirm power, then each sensor in isolation, then the integrated loop — the first failing step tells you exactly where to look.
| Test | What you should see |
|---|---|
| Push a new schedule/playlist | Targeted screens update remotely |
| Drop the network | Screen keeps playing cached content; syncs on reconnect |
| Hang the player | Watchdog restarts it; safe fallback meanwhile |
| Remove content | Safe default shown — never blank/error |
| Freeze the player (powered but stuck) | Health shows not-displaying; fault caught centrally |
| Daypart change | Correct content plays for the time of day |
Bench-test checklist. If a row fails, stop and fix it before moving on.
Expected output
The CMS shows each screen's online/displaying status and current content, supports remote scheduling/targeting, and alerts on faults.
{
"screen": "S-118",
"online": true,
"displaying": true,
"showing": "campaign_autumn_02",
"last_sync": "2026-07-27T14:00:00"
}
Screen S-118 online, actually displaying the scheduled campaign — verifiable centrally; a frozen or offline screen would surface as a fault in the CMS before any passer-by noticed.
Troubleshooting: Common Errors & Fixes
Performance Optimisation
- Play from a local cache; sync opportunistically for offline continuity.
- Watchdog + safe fallback so the worst failure is presentable.
- Report "displaying" health, not just online.
- Scale via the CMS across the estate.
- Pin the hot loop to one core with
tasksetand leave the others free for the OS. - Prefer MJPEG over raw YUY2 when capturing from USB cameras — the decode cost is far lower than the USB bandwidth cost.
- Log to a tmpfs RAM disk and flush to the SD card once a minute; per-sample SD writes are what kills cards.
- Run the service under
systemdwithRestart=alwaysso a crash never means a dead deployment. - Batch several samples into one MQTT publish. Radio time, not CPU time, dominates the energy budget.
- Set the MQTT keep-alive to a value that matches your reporting interval so the broker does not churn reconnections.
- For battery builds use deep sleep between samples: an ESP32 drops from ~160 mA awake to about 10 µA asleep, which is the difference between days and months of runtime.
Safety Precautions
- Never show a blank, desktop or error in public — fail safe to known-good content.
- Content licensing and appropriateness are the operator's responsibility.
- Mount screens securely; follow electrical/installation safety.
- Secure the CMS/content pipeline against unauthorised content.
- Wear eye protection when soldering or cutting, and solder in a ventilated space — rosin flux fumes are a respiratory irritant.
- Power the circuit through a bench supply with a current limit while you are testing. A 300 mA limit turns a wiring mistake into a beep instead of a dead board.
- Disconnect power before changing any wiring. Hot-plugging a sensor onto a live bus is the fastest way to lose a controller.
Maintenance
- Monitor screen health; act on faults caught centrally.
- Verify safe-fallback and watchdog behaviour.
- Keep content/schedules current via the CMS.
- Check caching/sync and player health across the estate.
- Re-check every screw terminal and header after the first week — thermal cycling loosens connections that felt tight on day one.
- Rotate the microSD card annually and keep an image of the working system. Cards used as loggers wear out silently.
- Keep the broker and dashboard containers patched, and rotate device credentials at least once a year.
- Recalibrate at the interval given in the calibration section, and keep the constants in a text file next to the firmware — not only in flash.
- Keep a short logbook of firmware versions and what changed. Six months later you will not remember why that constant is 1.083.
Future Improvements & Upgrades
A working v1 is a platform, not a finish line. These are the upgrades that add the most capability for the least rework.
- Add interactive/触ouch and sensor-triggered content.
- Add proof-of-play logging for advertising.
- Add content approval workflows and templating.
- Add richer analytics (audience/engagement) where appropriate.
- Design a proper PCB. Once the breadboard version has run for a month, moving to a two-layer board removes the intermittent-contact failures that dominate prototype faults.
- Add over-the-air firmware updates so you never have to physically reach a deployed node again.
- Add persistent local storage (microSD or the on-chip flash) so a network outage does not create a hole in your data.
- Move configuration out of the source: a captive-portal setup page or a JSON config file makes the build reusable without a recompile.
- Add a battery and solar option so the unit survives a power cut and can be sited away from a socket.
- Write a small test harness that feeds synthetic sensor values through the decision logic, so you can validate thresholds without physically triggering the event.
Frequently Asked Questions
References & Learning Resources
These are the primary sources worth reading in full. Manufacturer datasheets always outrank forum posts when the two disagree.
- Digital signageReference
- Content management systems for signageReference
- Watchdog timers / self-healingReference
- DaypartingReference
- Raspberry Pi media playbackRaspberry Pi