dobrovolskiy.com
RU
← Notes2026-08-30 · by Dmitriy Dobrovolskiy

How I built FOURLEDS Cloud Controllers: ESP32-C3 + LTE lighting controllers, fleet server and dashboard

Case study: a cellular lighting-control system for heritage facades — six KiCad boards, ESP-IDF firmware with a PPP-over-CMUX modem recovery ladder, ESP-NOW mesh, FastAPI/TimescaleDB fleet server. ~18 sites in production.

FOURLEDS Cloud Controllers is a complete lighting-control product I designed and built alone: KiCad boards (an ESP32-C3 + SimCom A7672E 4G core, relay, sensor and DALI modules), ESP-IDF C++ firmware with a five-step cellular recovery ladder, an ESP-NOW mesh between boards, and a FastAPI + TimescaleDB + EMQX fleet server with a SvelteKit dashboard. It runs about 18 sites of architectural lighting on heritage buildings.

The problem

Architectural lighting on heritage buildings is switched from an outdoor cabinet, often on a wall in a courtyard, sometimes on a bridge. There is no wired network there and usually no Wi-Fi. The client (a lighting company I also work for as technical director) needed every cabinet to follow a sunrise/sunset schedule, to be switchable from a phone, and to report when something is wrong — a tripped breaker, a dead contactor, a dark facade — before a resident calls the city.

Off-the-shelf GSM relays do the switching but not the rest: no confirmation that the light is actually on, no fleet view, no firmware updates in the field. So I built the whole stack.

Hardware: one 87 mm form factor, several boards

All boards share an 87 mm width so they stack inside a standard DIN cabinet. Designed in KiCad, fabricated in several revisions (the silkscreen on the current core says FOURLEDS Online controller v. 2.11.7):

An earlier monolithic All-in-One (ESP32-S3 + SIM800C + BL0937 energy metering, 229 footprints) and a 2025 Simple LED Controller show where the modular design came from.

Firmware: assume the network will fail

The firmware is ESP-IDF 5.4, C++17, built with PlatformIO. The cellular link is PPP over 3GPP 27.010 CMUX, which leaves a parallel AT channel open for SMS and signal-quality polling while the data session is up. Everything above it is MQTT to an EMQX broker.

The part that took the most field time is the recovery ladder. Carrier networks behind CGNAT drop idle sessions silently; a modem can look connected and be black-holed. The controller escalates in order: MQTT reconnect → PDP context re-activation → re-initialising the modem DCE/DTE → a PWRKEY power cycle → a hardware RESET. Each step is timed and counted.

To see what actually happens in the field, every board keeps a stability log in RTC_NOINIT memory, so it survives resets, and ships it to the server. Multi-week analyses of those logs traced outages to two causes — carrier CGNAT blackholes and heap starvation during reconnect storms — and produced targeted fixes: storm-aware backoff, reconnect-burst suppression and a downlink keepalive.

Between boards inside one cabinet there is no Wi-Fi network at all; the core talks to coil and sensor boards over ESP-NOW with ack/dedupe rings and idempotent command replay, and can push firmware to them (dependency OTA). Coil boards keep a local schedule so the lights keep switching if the core loses the network.

SMS is the transport of last resort: commands and status are sealed with AES-GCM so a spoofed text cannot switch a facade.

Server and dashboard

The fleet server is FastAPI with SQLAlchemy async and Alembic (58 migrations, 40 test modules), PostgreSQL 17 with TimescaleDB for telemetry, EMQX Enterprise for MQTT. It runs an MQTT consumer, a command service with a watchdog, a sunrise/sunset scheduler (astral), an OTA engine whose artifact library is published automatically from the PlatformIO build, and power/relay/compliance analytics that compare what was scheduled with what the sensor board actually saw. A Telegram bot reports and takes commands.

The dashboard is SvelteKit 2 / Svelte 5 with a MapLibre fleet map, per-device pages (ping, stability log, relay history, compliance), a schedule editor, OTA, audit and raw MQTT/ESP-NOW admin views, in two languages.

Numbers

About 109 000 lines across firmware, server and dashboard; nine KiCad projects with gerbers, interactive BOMs and 3D exports; roughly 18 sites in production, including facades of listed buildings in several cities.

What I would do differently

Start with the stability log. I added it after the first winter, and every real fix since came from its data, not from bench tests. And I would put the sensor board into the first revision instead of trusting relay state — knowing the contactor closed is not the same as knowing the facade is lit.

FAQ

Why PPP over CMUX instead of plain AT commands for the modem?

CMUX multiplexes the single UART into virtual channels, so the PPP data session and an AT channel for SMS and signal quality run at the same time. Without it you have to tear down the data session to send a text or read CSQ.

Why ESP-NOW between boards instead of RS-485?

The boards sit centimetres apart inside a cabinet, but wiring a bus to every module adds connectors and assembly steps. ESP-NOW needs no network and no cabling; the reliability layer (acks, deduplication, replay) makes it deterministic enough for relay commands. RS-485 is still on the coil board for external devices.

Can I hire you for a similar controller?

Yes — that is what the site is for. I work live on a Zoom / Telemost call, billed by the hour, from schematic review to firmware and server. See the rate on the home page.

Want something like this?

I build it live on a Zoom call, you watch the screen, the timer stops when you say stop. First 15 minutes are free.

$50 / hourBook an hour

More projects

FOURLEDS Cloud ControllersCellular lighting controllers for heritage facades: six KiCad boards, ESP-IDF firmware, fleet server, dashboard