Siddhant Kumar
Project 084 · Smart City

Digital Signage Controller.

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.

Intermediate 12–18 hours 27 min read DisplayNetworkCity
Jump to source Bill of materials
Digital Signage Controller — reference build illustration MCU VCC · GND · SIG · NC
Difficulty
Intermediate
Build time
12–18 hours
Indicative cost
₹5,000 – ₹8,000 per screen (+ CMS)
Platform
Raspberry Pi 4 Model B (4 GB)
Category
Smart City
Last updated
28 July 2026
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.

A city skyline at night
A fleet of public displays managed from one place — always showing the right, current content. Photograph sourced from Wikimedia Commons — Smart city.jpg. Reused under the licence stated on that page; please check it before republishing.

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

SettingHow it is used
Public information displaysTransit, campus and civic screens with remotely-scheduled, always-current content.
Retail / advertising signageDayparted, per-location content and reliable uptime across many screens.
Corporate / lobby displaysCentral management and health monitoring of building screens.
Menu boards / wayfindingRemotely-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

AttributeValue
Difficulty levelIntermediate
Estimated completion time12–18 hours
Indicative build cost₹5,000 – ₹8,000 per screen (+ CMS)
Primary disciplineSmart City
Reference platformRaspberry 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.

ComponentKey specificationQtyApprox. 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 GPIO1₹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 DAC1₹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 controller1₹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 recommended1₹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 protection1₹350
Display + media player
Pi plays media; ESP32 can drive simple/text displays
Public display + a media player (Raspberry Pi common) per screen1₹4,500
CMS + content pipeline
Software/platform
Central system for playlists/schedules/health1
Network + mountWi-Fi/Ethernet and secure screen mounting1₹500
Watchdog/health hardwareHardware 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

PartSpecificationSupplyInterfaceReference
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 GPIO5 V / 3 A USB-CGPIO, SPI, I²C, UART, CSI, DSIDatasheet
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 DAC3.3 V logic / 5 V USBUART, SPI, I²C, I²S, CAN, PWMDatasheet
2.4″ ILI9341 SPI TFT (240 × 320)262 K colour, 40 MHz SPI, optional resistive touch controller3.3 VSPIDatasheet
microSD card 32 GB A1 classA1 rated, 10 MB/s random write, UHS-I, endurance-grade recommended3.3 VSDIO / SPIDatasheet
5 V 3 A regulated SMPS adapter100–240 VAC in, 5 V ±5 % out, 3 A, short-circuit and over-voltage protection5 VDC barrel / USBDatasheet

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.

LoadSupply railTypical current (mA)Notes
Raspberry Pi 4 Model B (4 GB)5 V / 3 A USB-C1200Use 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 USB160Wi-Fi transmit bursts peak near 500 mA — size the regulator accordingly.
2.4″ ILI9341 SPI TFT (240 × 320)3.3 V90Backlight is most of the current — PWM it for battery builds.
microSD card 32 GB A1 class3.3 V100For 24/7 loggers buy a high-endurance card — normal cards die in months.
5 V 3 A regulated SMPS adapter5 V3000Measure 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-wide pip install by 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

LibraryWhy it is neededInstall
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.

Digital Signage Controller — system block diagramFunctional block diagram of the Digital Signage Controller system. ManageCMSplaylists/schedulesTargetscreen/groupDistributePush + syncto playersLocal cacheoffline playPlay + healPlayerscheduleWatchdogsafe fallbackMonitorHealthonline/showingFaultscentralrightrightnone
Digital Signage Controller — system block diagram

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.

Digital Signage Controller — wiring schematicConnection schematic showing which controller pin drives each peripheral. Sensors / InputsControllerActuators / OutputsRaspberry Pi 4 ModelB (4 GB)5 V / 3 A USB-CDisplayContent outputStorageLocal content cacheNetworkCMS sync + healthWatchdogGPIOSelf-healing restartStatusGPIOHealthSupplyPower
Digital Signage Controller — wiring schematic
PeripheralPeripheral pinController pinSignal
DisplayHDMI/TFTContent output
StorageSD/SSDLocal content cache
NetworkWi-Fi/EthCMS sync + health
WatchdogresetGPIOSelf-healing restart
StatusLEDGPIOHealth
Supply5VPower

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.
An ESP32 development board with the ESP-WROOM-32 module and USB connector
Each player caches content locally and self-heals so a network drop or crash never blanks the screen. Photograph sourced from Wikimedia Commons — ESP32 Espressif ESP-WROOM-32 Dev Board.jpg. Reused under the licence stated on that page; please check it before republishing.

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.

Digital Signage Controller — architecture stackLayered architecture from hardware to user interface. Hardware layerRaspberry Pi 4 Model B (4 GB) · sensors and actuatorsDriver layerpython · fastapi · sqlite · dockerApplication logicsampling loop · filtering · thresholds · state machineTransport layerWi-Fi/Ethernet → signage CMS · TLS · retry and backoffPresentation layerdashboard · mobile notifications · historical charts
Digital Signage Controller — architecture stack

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

plainSchedule / 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

plainOffline 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

plainSelf-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.

Digital Signage Controller — firmware flowchartControl flow through the main program loop. CMS: define playlist/schedulePush to targeted playersPlayer: cache + play byschedulePlayer healthy/displaying?Report healthWatchdog restart / safe fallbackWatchdog restart / safefallbackReport healthOperator: monitor + update
Digital Signage Controller — firmware flowchart

Assembly Instructions

Build on a breadboard first and only commit to solder once the whole system has run for an hour without a fault.

  1. 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.

  2. Set up remote content management

    A CMS defines playlists/schedules (dayparting, per-screen/group) and pushes them to players, which sync opportunistically.

  3. 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.

  1. 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.py
    import 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.
  2. 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.

pythonsignage_controller.py
#!/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()
class Watchdog:A watchdog thread restarts the player if it stops petting — self-healing that keeps an unattended public screen running without an operator.
except requests.RequestException: pass # offline: keep playing cachedWhen the CMS is unreachable the player keeps playing its cached content, so a network drop never blanks the screen.
def show_safe_default(self):The fallback renders known-good content rather than ever exposing a blank, desktop or error in public — the safe worst-case.
def report(self, displaying, showing):The player reports whether it is actually displaying and what it is showing, so faults are visible centrally, not to a passer-by.
pl = self.local_playlist(time.time()) # dayparting/targetingThe active playlist is resolved locally for the current time and screen, so content stays current everywhere without visiting screens.

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.

  1. Offline continuity

    Verify the player keeps playing correct content when the CMS/network is unavailable, and syncs on reconnect.

  2. Self-healing

    Confirm the watchdog restarts a hung player and that it falls back to safe default content — never a blank/error.

  3. Health accuracy

    Confirm health reflects actual display state (displaying/showing), not just power/network.

Network Architecture & Connectivity

Digital Signage Controller — network topologyPath taken by telemetry from field node to end user. Edge nodesGatewayCloudClientsPlayerPi/ESP32Other screensestateWi-Fi/EthernetNetworkto CMSHTTPSSignage CMSschedules + healthCMSmanageOpshealth/faults
Digital Signage Controller — network topology

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 / endpointDirectionPayload
signage/<screen>/scheduleCMS → playerplaylists/schedules (dayparting/targeting)
signage/<screen>/healthplayer → CMSonline, displaying, showing
signage/<screen>/alertplayer → opsfault (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.

TestWhat you should see
Push a new schedule/playlistTargeted screens update remotely
Drop the networkScreen keeps playing cached content; syncs on reconnect
Hang the playerWatchdog restarts it; safe fallback meanwhile
Remove contentSafe default shown — never blank/error
Freeze the player (powered but stuck)Health shows not-displaying; fault caught centrally
Daypart changeCorrect 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.

jsonscreen-health.json
{
  "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.

Stocked shelves in a supermarket aisle
The CMS verifies what every screen is showing and catches faults before a passer-by does. Photograph sourced from Wikimedia Commons — Supermarket shelves.jpg. Reused under the licence stated on that page; please check it before republishing.

Troubleshooting: Common Errors & Fixes

Blank/error screen in public

Likely cause. No safe fallback / not caching

Fix. Fall back to safe default content; cache locally; never expose desktop/error

Screens out of date

Likely cause. Manual content updates

Fix. Manage centrally via CMS; push schedules; sync players

Frozen player looks "online"

Likely cause. Only checking power/network

Fix. Report and check "displaying" (actually showing content)

Network drop blanks screen

Likely cause. No local cache

Fix. Cache content locally; play offline; sync on reconnect

Hung player stays hung

Likely cause. No watchdog

Fix. Add a watchdog that restarts a hung player

The Python script crashes with "externally-managed-environment" on pip install

Likely cause. Raspberry Pi OS Bookworm marks the system Python as managed by apt, and refuses global pip installs.

Fix. Create and activate a virtual environment — python3 -m venv ~/venv && source ~/venv/bin/activate — and install there. Use --system-site-packages if you also need apt-installed modules such as picamera2.

The Pi reboots or shows a lightning-bolt icon under load

Likely cause. Under-voltage. The supply sags below 4.63 V when the CPU and peripherals ramp up.

Fix. Use the official supply for your model (5 V 3 A for Pi 4, 5 V 5 A for Pi 5) and a short, thick USB-C cable. Check with vcgencmd get_throttled — anything other than 0x0 means power problems.

Wi-Fi connects but MQTT never does (state -2)

Likely cause. Wrong broker address or port, a firewall in the way, or the broker requiring credentials the sketch is not sending.

Fix. Test from a laptop on the same network first: mosquitto_sub -h <broker> -t "#" -v. If that works, the problem is on the device — check the IP literal, port 1883 (or 8883 for TLS), and that client.setServer() runs before connect(). PubSubClient state codes are documented in its header.

Readings arrive for a while and then stop

Likely cause. The Wi-Fi or MQTT session dropped and the sketch never reconnects, or the broker dropped the client on keep-alive timeout.

Fix. Never assume the link stays up. Check WiFi.status() and client.connected() at the top of every loop and reconnect with exponential backoff. Add a watchdog so a wedged network stack reboots the device instead of going silent.

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 taskset and 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 systemd with Restart=always so 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

What two things matter most in signage?

Remote content management (getting the right, current content onto every screen without visiting it) and reliability (never a blank, frozen or error screen in public). This controller provides both.

What happens when the network drops?

The screen keeps playing its locally-cached content correctly and syncs new content when the connection returns. A public screen must never go blank because the Wi-Fi hiccupped.

How does it avoid embarrassing failures?

A watchdog restarts a hung player, and the worst failure mode is designed to be safe default content (a known-good loop or card) — never a blank, a desktop, or an error dialog in public.

How does the operator know a screen has failed?

Each player reports whether it is actually displaying content (not just powered/online) and what it is showing, so a frozen or offline screen surfaces as a fault in the CMS before any passer-by notices.

What is dayparting?

Scheduling different content by time of day (and per screen/group), so a screen shows the right thing at the right time — all defined centrally and pushed to the players.

References & Learning Resources

These are the primary sources worth reading in full. Manufacturer datasheets always outrank forum posts when the two disagree.

  1. Digital signageReference
  2. Content management systems for signageReference
  3. Watchdog timers / self-healingReference
  4. DaypartingReference
  5. Raspberry Pi media playbackRaspberry Pi