Hardware: Building your own
led-ticker ships two reference builds: the smallsign (Pi 4 + 5× 32×16 panels) and the bigsign (Pi 5 + 8× P3 32×64 panels). They’re starting points, not requirements — anything in between (or beyond) is a viable build, and the engine is the same.
Choosing a sign size
Section titled “Choosing a sign size”The two reference builds sit at very different points on the cost / complexity / use-case curve. Use them as starting points and adjust panel count, panel pitch, or layout to fit your space.
| Reference build | Cost & complexity | Use case |
|---|---|---|
| Smallsign — 5x 32x16, 160x16 | Lowest. One Pi 4, five panels, a bonnet, a 10A 5V supply. One evening of soldering and a weekend of config work. | A scrolling marquee for a desk, a shop window, or a single-line live feed (RSS, weather, countdowns). 16 rows is one BDF text line tall. |
| Bigsign — 8x P3 32x64, 256x64 | High. Pi 5, eight P3 panels, a HAT, a 30A+ supply, a rigid frame, a 2x4 serpentine wiring run, hi-res font tuning. | Storefront-scale display. Fits hi-res emoji, two-row handles + promos, animated GIFs, and Inter / Beloved Sans rendered at native pixels. |
Anything between those two — three panels in a row, six panels in a
2x3 grid, a custom non-rectangular layout — is fine; the rgbmatrix
library and the led-ticker engine don’t assume the reference shapes.
You’ll edit the [display] block (rows, cols, chain_length,
pixel_mapper_config) to match your wiring. For pixel_mapper_config
specifically, don’t hand-write the Remap string — use
panel-map to derive it from your physical layout.
You don’t need any of this hardware to start. The
render-demo tool runs the real ticker engine
against a test stub canvas and writes the output to a gif, so you can
develop your config end-to-end with nothing but a laptop.
Power budgets
Section titled “Power budgets”RGB matrix panels draw current proportional to the number of LEDs lit at any moment. The rule-of-thumb upper bound is:
amps_max = rows × cols × number_of_panels × 0.06A (per LED, full white)That’s the theoretical maximum at 5V with every pixel cranked to white at the same instant. In practice that never happens — text, emoji, and animations only light a fraction of the panel — so the practical draw is roughly 30-40% of the theoretical max.
| Reference build | Theoretical max | Practical pick |
|---|---|---|
| Smallsign | 5V × ~150A | 5V × 10A |
| Bigsign | 5V × ~980A | 5V × 60A |
A 5V / 10A brick covers the smallsign with comfortable headroom; the bigsign wants a 5V / 30-60A supply with thick busbars (split across multiple injection points if your panels have multiple power inputs). Adafruit’s product pages publish per-panel current draw curves — cross-reference those for whichever panels you actually buy, since brightness, panel revision, and pixel pitch all shift the number.
The Pi itself runs off its own USB-C supply; do not try to power panels off the Pi’s 5V rail. The math doesn’t work for one panel either, let alone five or eight.
Software-first development
Section titled “Software-first development”You can develop a complete led-ticker config without owning a single panel. Two paths — pick whichever fits your workflow:
Browser preview (Docker, no Python needed) — the fastest way to see the sign rendering:
make try # build + start; open http://localhost:8080 → live preview tabmake try starts a headless engine and the web UI in Docker. Stop with
Ctrl-C then make try-down.
GIF renderer (Python dev path) — render any config to a GIF at panel resolution, useful for CI, scripted demos, and configs with live-data widgets:
git clone https://github.com/JamesAwesome/led-ticker.gitcd led-tickermake dev # install Python dev deps (requires uv)Run the test suite — no hardware, no Docker:
make testmake test runs the full suite — pyproject’s [tool.pytest.ini_options] pythonpath = ["tests/stubs"] puts the stub canvas on the path automatically, so no env var is needed. ~1450 tests, finishes in ~2 minutes.
Render any config to a gif at panel resolution:
make render-demo CONFIG=mysign.toml OUT=mysign.gifA minimal config you can render right now:
[display]rows = 16cols = 32chain_length = 5default_scale = 1brightness = 60
[[playlist.section]]mode = "slideshow"loop_count = 1hold_time = 4.0
[[playlist.section.widget]]type = "message"text = "Hello, panel"font_color = "rainbow"The output gif is upscaled 4x by default so individual LED pixels read
clearly. See render-demo for the full set of
flags and the long-running pipeline used for data-fetch widgets.
Deploying to the Pi
Section titled “Deploying to the Pi”Once your config renders the way you want, push it to a Pi. Deployment is Docker Compose — one command and the sign is running:
make setup # Docker preflight + config/.env seed + bring-up (first time)make update # rebuild the image (real version) + recreate services (subsequent deploys)make setup (or make setup MODE=deploy) checks that Docker is installed,
seeds config/config.toml and .env if they don’t exist yet, and runs
docker compose up -d. After that, make update rebuilds the image (with a
real version baked in) and recreates the running services for every subsequent
deploy.
Key behaviors built into compose.yaml:
restart: unless-stopped— the ticker comes back up on power loss or crash.- Config mounted read-only (
./config:/code/config:ro) — a typo in a hot-edit can’t crash the ticker into a half-applied state. - Hot-reload is on by default: edit
config/config.tomlon the host and most changes (sections, widgets, schedule) take effect within one playlist cycle — no restart needed.
Panel bring-up — after wiring up the hardware and the first boot:
- Run
make panel-testto cycle the panel through solid R/G/B/White/Black. If colors are missing or wrong, check your wiring and theled_rgb_sequenceconfig knob before going further. - Use the
panel-mapreveal → derive → verify workflow to build thepixel_mapper_configRemap string for your exact panel layout.
To take the sign down for a diagnostic, docker compose stop; bring it back
with docker compose start.
Repo hygiene — your running config/config.toml is gitignored (it’s
yours to customize per sign). Private media — images, GIFs, custom fonts —
goes in config/local/ (also gitignored) and is referenced in TOML as
path = "local/<file>".
Sharp edges
Section titled “Sharp edges”The rgbmatrix library is hardcoded to a fork in the Dockerfile
Section titled “The rgbmatrix library is hardcoded to a fork in the Dockerfile”If you deploy via Docker (the recommended path), you don’t need to do anything — the image already builds the correct fork.
For source builds: the Dockerfile pins jamesawesome/rpi-rgb-led-matrix — our fork of hzeller/rpi-rgb-led-matrix. It tracks upstream’s Pi 5 RP1 support (hzeller#1886) and adds three patches the engine depends on: a GCC-10 build fix, a Pillow shim, and a SubFill Python binding that upstream doesn’t ship. Because of that last one, stock upstream is not a drop-in replacement. If you apt install or pip install a different rgbmatrix on the Pi outside the Docker image — or check out upstream hzeller/master and make install it — your Pi will diverge from what the engine was tested against, and you’ll spend an afternoon chasing flicker that isn’t a config bug. Rebuild from source against the pinned fork.
USB-C “5V 3A” power supplies aren’t dedicated panel supplies
Section titled “USB-C “5V 3A” power supplies aren’t dedicated panel supplies”A phone charger or a generic USB-C brick marketed as 5V 3A does not sustain rated current under inductive matrix load — the voltage sags, panels glitch, and you blame the config. Use a dedicated 5V supply sized per the power-budget table above (10A for the smallsign, 30-60A for the bigsign), with thick power leads to each panel’s 2-pin input.
Panels run hot at full brightness
Section titled “Panels run hot at full brightness”P3 panels at brightness 60+ get warm to the touch within minutes; the smallsign is more forgiving but still climbs over time. For sustained or always-on installs add a fan behind the panels, or design a shroud with airflow gaps. Heat shortens LED lifespan and can warp panel substrates if it accumulates with no ventilation.
Running from a source checkout (dev / evaluation)
Section titled “Running from a source checkout (dev / evaluation)”If you want to run led-ticker directly from a Python virtualenv rather than
Docker — for development, custom patches, or experimentation — install the
package and its deps with make dev (which runs uv sync --extra dev), then
invoke led-ticker --config config/config.toml directly.
Inspecting the running async loop (Python 3.14+)
Section titled “Inspecting the running async loop (Python 3.14+)”led-ticker is one long-lived asyncio loop plus a background task per data
widget (run_monitor_loop) and the optional busy-light HTTP server. On Python
3.14 you can print the live task tree of a running process with no code
changes or restart:
# pid of the led-ticker process (inside the container or in a dev checkout)python -m asyncio pstree <pid># flat list with awaited-by edgespython -m asyncio ps <pid>If a data widget’s update() task has died (the tell-tale: its periodic
”… updated: N stories” INFO log stopped), it will be absent from the tree —
turning “is the poller still alive?” into a direct answer.