ON AIR · build your own

A desk sign
that never sleeps.

An ambient ticker that rotates live stocks, weather, and the time — then flips to a one-tap status sign on demand. Built on an ESP32-S3 and a 4-in-1 MAX7219 LED matrix. Configured entirely over Bluetooth.

  • ESP32-S3
  • BLE control
  • iOS app
  • Custom PCB
The LED Ticker on a desk, its red dot-matrix display reading ON AIR, wired to an ESP32-S3 board over a braided USB-C cable.
LED-Ticker-AB12 · live on a desk

/ what it does

Two display layers, one little box.

An ambient rotation you set and forget, and an active sign that takes over the moment you need it.

01

Sign mode

One-tap status text — BUSY, FOCUS, ON AIR — overrides the ambient rotation, with an optional auto-clear timer.

02

Countdown timer

A live MM:SS countdown from 1–99 min, capped with a random animation — fireworks, sonar, sparkle — then back to ambient.

03

Live data

Stock quotes from Finnhub and multi-location weather from Open-Meteo, geocoded on-device. No phone tether required.

04

12-hour clock

Steady H:MM when shown alone, scrolls H:MM AM/PM when mixed in. NTP-synced, timezone-aware.

05

iOS companion

A native app: multi-device switcher, preset chip grid, and a Display tab with per-category toggles plus a master on/off.

06

PIN-gated BLE

Every write is gated on a 6-digit PIN. iOS uses the native pairing dialog; the PIN rotates on factory reset.

/ under the hood

How it works.

Single-file firmware, deliberately. Here's what's holding it together.

Dual-core, no blocking

A fetch task pinned to Core 0 pulls quotes and weather while Core 1 runs a cooperative loop() — no delay(), no jank on the display.

BLE is the contract

WiFi, API keys, tickers, locations, mode, and the active sign are all set over a documented GATT service. The CLI and the iOS app speak the same protocol.

Pairing & PIN

Bonded links via passkey entry, with a fallback PIN characteristic for non-pairing clients. Five wrong tries buys a lockout.

Persisted in NVS

Config survives power cycles in flash; fetched data and the active sign stay in RAM, so a reboot cleanly resumes the ambient rotation.

On-device geocoding

Type a city or ZIP and the ESP32 resolves it to coordinates itself via Open-Meteo — no companion server, the device does its own lookups and fetches.

One button, two jobs

GPIO 0 is the bootloader strap at power-on but a plain button at runtime: hold it 10 s and the matrix counts down before wiping all config and forgetting every BLE bond.

Go deeper: Architecture Protocol iOS app

/ make your own

Build one.

Two off-the-shelf modules, a custom PCB, and a 3D-printed bracket. Everything you need is in the repo.

PartWhat
MCU board Freenove ESP32-S3-WROOM (FNK0099) — onboard NeoPixel, native USB-C Buy ↗
Display DIYables 4-in-1 MAX7219 8×8 LED matrix Buy ↗
PCB Custom board carrying both modules — EasyEDA sources, order via JLCPCB Files ↗
Bracket 3D-printed bracket (STL included) STL ↗
Cable USB-C for power + flashing any

// then flash it

git clone https://github.com/ssayala/esp32-led-simple
cd esp32-led-simple
pio run -t upload   // build & flash over USB-C

First boot scrolls the BLE name and a 6-digit PIN on the matrix. Pair from the iOS app or the Python CLI, set your WiFi, and you're live. Full walkthrough in the Getting started guide.

No toolchain? Flash it from your browser → (Chrome/Edge, over USB).

3D render of the custom PCB that carries the Freenove module and the MAX7219 matrix header on a single board.
The custom PCB — designed in EasyEDA, fabbed at JLCPCB.