Sign mode
One-tap status text (BUSY, FOCUS, ON AIR) overrides the ambient rotation, with an optional auto-clear timer.
● ON AIR · build your own
It rotates live stocks, weather, and the time across a bright dot-matrix display. Tap once and it flips to a status sign that stays up until you clear it. It runs on an ESP32-S3 microcontroller and an LED matrix, set up over Bluetooth from an iOS app.
/ what it does
An ambient rotation you set once and forget. An active sign that takes over the second you need it.
One-tap status text (BUSY, FOCUS, ON AIR) overrides the ambient rotation, with an optional auto-clear timer.
A live MM:SS countdown from 1–99 min, ending in a random animation (fireworks, sonar, sparkle), then back to ambient.
Stock quotes from Finnhub and multi-location weather from Open-Meteo, geocoded on-device. No phone tether required.
Steady H:MM when shown alone, scrolls H:MM AM/PM when mixed in. NTP-synced, timezone-aware.
A native app: multi-device switcher, preset chip grid, and a Display tab with per-category toggles plus a master on/off.
Every write is gated on a 6-digit PIN. iOS uses the native pairing dialog; the PIN rotates on factory reset.
/ under the hood
Single-file firmware, deliberately. Here's what makes it tick.
Core 0 runs a background task that pulls quotes and weather. Core 1 drives the display in a cooperative loop() with no delay() anywhere. They hand off through volatile flags, so a slow network never stutters what's on screen.
Wi-Fi, API keys, tickers, locations, mode, the active sign. All of it is configured over one documented BLE service. The iOS app and the Python CLI are just two clients of that same contract. Writes get queued out of the radio callback and applied by the main loop.
Every frame draws by priority. A countdown or status sign wins first, then the idle animation, then the steady clock, then the scrolling rotation. One small state machine decides what lands on the matrix.
No Wi-Fi yet, or no API key? It drops into a setup mode that scrolls its own name and PIN. Categories that are still loading just say Loading…. Signs and timers run off millis(), so they keep working with no network at all.
Your settings live in flash and survive a power cycle. The fetched quotes, weather, and active sign stay in RAM. Reboot, and the transient stuff clears while your ambient rotation picks right back up.
At power-on, the BOOT button is the bootloader strap. At runtime it's just a button. Hold it ten seconds and the matrix counts down, then wipes every setting and forgets every BLE bond.
/ make your own
Two off-the-shelf modules, a custom PCB, and a 3D-printed bracket. Everything you need is in the repo.
| Part | What | |
|---|---|---|
| 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 ↗ |
| Case | 3D-printed case (STLs included) | STLs ↗ |
| Cable | USB-C for power + flashing | any |
// then flash it
git clone https://github.com/ssayala/esp32-led-simple
cd esp32-led-simple/firmware
pio run -t upload // build & flash over USB-C
First boot scrolls the BLE name and a 6-digit PIN. Pair from the iOS app or the Python CLI, set your Wi-Fi, and you're live. Full walkthrough in the Getting started guide.
No toolchain? Flash it from your browser with Chrome, over USB.
/ not a tinkerer
Building isn't for everyone. If you'd rather have a finished unit that arrives assembled, flashed, and ready to plug in, leave your email. It's just a waitlist to gauge interest, with no commitment and no charge. I'll only reach out if pre-built devices become available.
// no spam · US only for now