-
-
Notifications
You must be signed in to change notification settings - Fork 14
Architecture Overview
For tinkerers who want to understand what's happening inside the firmware. This is the user-friendly version; for the locked architectural decisions and PR-by-PR build history, see docs/V6-ARCHITECTURE.md in the repo.
βββββββββββββββ UART ββββββββββββββββ queue ββββββββββββββββ RMT ββββββββββββ
β HiLink β 256 kbaud β radar driver β 1-frame β motion β 60 Hz β WS2812 β
β LD2450 β ββββββββββ β (ld2450.c) β βββββββββββ β Kalman / PI β ββββββββββ β strip β
β (24 GHz) β β β β smoother β β β
βββββββββββββββ ββββββββββββββββ ββββββββββββββββ ββββββββββββ
β
β 20 Hz
βΌ
ββββββββββββββββ WS ββββββββββββ
β webui β βββββββ β browser β
β (esp_http_ β β β
β server) β βββββββ β Preact β
ββββββββββββββββ POST β UI β
ββββββββββββ
One ESP32-C3 reads one LD2450 over UART, smooths the target stream through a Kalman filter, and drives one WS2812 strip via the RMT peripheral. A web server on port 80 serves the UI bundle and exposes JSON endpoints for every config knob plus a 20 Hz WebSocket telemetry stream.
That's the whole pipeline. There is no peer broadcast, no coordinator, no pairing, no fusion β those were v6.0 features dropped in the v6.x rewrite. See FAQ β Why was the mesh dropped for the reasoning.
| Task | Priority | Stack | Period | Job |
|---|---|---|---|---|
radar_task |
6 | 4 KB | UART event | Read radar bytes β parse radar_frame_t
|
motion_task |
5 | 4 KB | 50 Hz | Kalman / PI smoother β publishes target_t
|
led_render_task |
4 | 6 KB | 60 Hz | Read smoothed target β framebuffer β WS2812 |
web_task |
3 | 8 KB | event | HTTPD handler |
tele_pump |
3 | 3 KB | 20 Hz | Publish smoothed target to live WS clients |
ws_bcast |
3 | 4 KB | 20 Hz | Coalesce + emit JSON to all WS clients |
status_led_task |
2 | 2 KB | pattern | Drive onboard LED blink pattern |
Higher priority preempts lower. The radar parse is highest because if it falls behind, the FreeRTOS UART driver's ring buffer overruns and we drop frames. LED render is next-highest because dropped frames there look like the strip is stuttering. Wi-Fi / HTTP / WebSocket are lower-priority β slow web requests can never starve the LED render loop, which was a recurring v5-era bug.
The firmware is organized into IDF components under firmware/components/:
| Component | What it does |
|---|---|
| board | Board profile struct + 4 profiles (C3 SuperMini validated; S3-Zero / classic ESP32 / C6 build-clean). Each profile declares an unsafe_pin_mask covering strapping pins, USB-Serial-JTAG D-/D+, and internal SPI flash. |
| settings | NVS facade β typed accessors for every config namespace (sys, board, led, dist, motion, wifi, auth). |
| status_led | Pattern-driven onboard LED in its own task. Patterns: BOOT, AP_MODE, STA_MODE, OTA, ERROR, PANIC. |
| button | Single-button polling for the BOOT button. Long-press (3 s) reserved for v6.1 factory reset. |
| netmgr | Wi-Fi STA/AP + mDNS + captive DNS portal. AP always-on; STA optional once configured. |
| auth | PBKDF2-SHA256 admin password (250k rounds). Off until configured; UI banner nags until set. |
| webui |
esp_http_server with all /api/* routes plus root + captive-portal redirects. UI bundle embedded as ui.html.gz (gzipped to ~25 KB). |
| ota |
esp_https_ota-style wrapper with rollback. Bootloader marks new image as pending; firmware calls ota_mark_valid() on successful boot. |
| radar | Driver registry (LD2450 + sim). Drivers compiled in unconditionally; one selected at runtime via NVS board.radar_kind. |
| motion | Kalman smoother + legacy PI smoother. Single output: target_t (smoothed distance + direction + raw value). |
| led_engine | 11 light modes via led_strip RMT. Reads smoothed target each frame; renders into a framebuffer; emits to the strip at 60 Hz. |
Removed in v6.x: mesh/ (ESP-NOW peer broadcast) and topology/ (multi-device segment graph). See Migration from v6.0 and FAQ.
| Namespace | Keys |
|---|---|
sys |
device_name |
board |
id, led_pin, radar_rx, radar_tx, button, status_led, radar_kind
|
led |
count, brightness, r/g/b, mode, span, center_shift, trail, dir_light, bg_mode, effect_speed, effect_intensity
|
dist |
min_cm, max_cm
|
motion |
mode, enabled, response, look_ahead_ms, outlier_strength, pos_smooth, vel_smooth, predict, p_gain, i_gain
|
wifi |
ssid, pass, host (hostname), static_ip (optional) |
auth |
pw_hash (PBKDF2-SHA256), pw_salt
|
NVS is journaled (atomic per-key writes), wear-levelled, and typed. Replaces v5's manual XOR-CRC EEPROM byte layout.
Removed in v6.x: mesh (peer blob, channel, fusion mode) and topo (kind, segments). Devices upgraded from v6.0 may still have orphan keys in those namespaces; the firmware never reads them, so they're harmless.
POST /api/auth/login β cookie session
POST /api/auth/logout
POST /api/auth/password β set/change PBKDF2 admin hash
WS /api/live β distance + raw + RSSI + heap + uptime @ 20 Hz
GET /api/distance β text/plain (legacy compatibility)
GET /api/version β app version + git sha + idf version + target
GET /api/system β enabled toggle + system metrics
POST /api/system β mute/unmute LED output
GET /api/wifi β current Wi-Fi state
GET /api/wifi/scan β list nearby APs
POST /api/wifi β save creds + reconnect
GET /api/board/profiles β board dropdown
POST /api/board β save board id + pin overrides; reboot
GET /api/radar/kinds β ld2450 | sim
GET /api/radar/diag β driver id + byte/frame counters + last raw bytes
GET /api/settings β flat read of every NVS namespace
POST /api/settings β batched write
POST /api/ota β application/octet-stream firmware upload
POST /api/factory_reset
POST /api/reboot
GET /api/ping β health check
Removed in v6.x: /api/mesh, /api/mesh/identify, /api/topology. See Migration from v6.0 for what they did and why they're gone.
| Offset | Size | Purpose |
|---|---|---|
| 0x0000 | 32 KB | Bootloader |
| 0x8000 | 4 KB | Partition table |
| 0x9000 | 16 KB | NVS |
| 0xD000 | 8 KB | OTA data (current slot pointer) |
| 0x10000 | 1408 KB | OTA app slot 0 |
| 0x170000 | 1408 KB | OTA app slot 1 |
| 0x2D0000 | 960 KB | LittleFS |
| 0x3C0000 | 64 KB | Coredump |
App slot 1408 KB; current build is ~1158 KB β 20% headroom. Coredump is read out via the C3's USB-Serial-JTAG console after a panic.
1. ROM bootloader β 1st-stage bootloader from 0x0000
2. 2nd-stage bootloader picks current OTA slot
β if OTA-pending and previous boot failed, fall back to other slot
3. app_main()
3a. settings_init() β bring up NVS
3b. resolve board profile β NVS-saved board.id wins; MCU-mismatch guard
3c. apply pin overrides β unsafe-pin guard rejects strapping/JTAG pins
3d. status_led_init() β boot pattern
3e. auth_init()
3f. netmgr_init() β start AP always; STA if creds saved
3g. webui_init() β esp_http_server + register all routes
3h. radar_init() β UART setup + radar_task
3i. motion_init() β motion_task starts smoothing
3j. led_engine_init() β RMT + led_render_task
3k. button_init() β BOOT-button polling
3l. xTaskCreate(tele_pump) β start 20 Hz telemetry
3m. ota_mark_valid() β disarm bootloader rollback
3n. status_led switches to AP_MODE or STA_MODE depending on Wi-Fi state
4. app_main returns; FreeRTOS owns the device.
| Artifact | Size |
|---|---|
bootloader.bin |
0x5330 (~21 KB) |
partition-table.bin |
3 KB |
ambisense.bin |
0x11aa70 (~1158 KB) |
| UI bundle (gzipped, embedded) | ~25 KB |
| App slot free | 20% |
The repo is laid out as:
firmware/
βββ main/main.c # app_main entry, boot order
βββ components/
β βββ board/ # board profiles
β βββ settings/ # NVS facade
β βββ status_led/
β βββ button/
β βββ auth/
β βββ netmgr/ # Wi-Fi + mDNS + captive DNS
β βββ webui/ # HTTP server + UI bundle
β βββ ota/
β βββ radar/ # LD2450 + sim drivers
β βββ motion/ # Kalman + PI smoothers
β βββ led_engine/ # 11 modes via RMT
βββ partitions.csv # custom 4 MB layout
βββ CMakeLists.txt # IDF project root
frontend/
βββ src/
β βββ main.tsx # App shell β sidebar + header
β βββ screens.tsx # All 6 screens (Live, LEDs, Motion, Hardware, Network, System)
β βββ atoms.tsx # Icon, Sparkline, sliders, charts
β βββ components.tsx # Card, Toggle, Field, etc
β βββ led_preview.tsx # Live LED preview rendering
β βββ api.ts # fetch helpers + WS client
β βββ styles.css # Design tokens
βββ vite.config.ts # Single-file bundle (vite-plugin-singlefile)
docs/
βββ V6-ARCHITECTURE.md # Locked architectural decisions
βββ V6-ROADMAP.md # PR record + v6.x roadmap
βββ HARDWARE.md # Wiring, mounting, troubleshooting
legacy/
βββ AmbiSense/ # v5.x Arduino source preserved
-
Add a new radar driver β drop a
radar_<kind>.cintofirmware/components/radar/implementingsize_t radar_<kind>_parse(const uint8_t*, size_t, radar_frame_t*), register inradar.c'sk_drivers[], expose via/api/radar/kindsJSON. -
Add an LED mode β extend the
mode_*switch infirmware/components/led_engine/. Modes are render-time only; persistence is just auint8_tin NVS. -
Tweak motion β
firmware/components/motion/motion_kalman.chas the v3 Kalman filter; the legacy PI smoother is inmotion.c. UI knobs map to NVS keys; seehandle_settings_postinwebui.cfor the JSON-key β NVS mapping. -
Add an HTTP endpoint β append to
k_routes[]infirmware/components/webui/webui.c. Don't forget to bumpcfg.max_uri_handlersif you cross the soft limit.
PRs welcome. Open against the v6-idf-rewrite branch.
Get started
Use it
Help
Upgrading
Reference