AmbiSense v6.0.0 — promote v6-idf-rewrite to main#17
Merged
Conversation
added 17 commits
May 5, 2026 18:22
…sign handoff Begins the v6 rewrite. Moves the v5.1.1 Arduino source under legacy/ and stands up an ESP-IDF v5.3-compatible project under firmware/. Highlights: - Custom partition table: 16K NVS + 8K otadata + 1408K x 2 OTA app slots + 960K LittleFS + 64K coredump (4MB layout, dual-bank safe rollback). - sdkconfig.defaults: task watchdog, brownout detector, coredump-to-flash, bootloader rollback, esp_http_server with WS support — applied across every target. esp32c3-specific overrides in sdkconfig.defaults.esp32c3. - components/board: profile struct + four ship-ready profiles (esp32c3-supermini validated, esp32-devkit / esp32s3-zero / esp32c6-devkit build-clean but untested). unsafe_pin_mask encodes strapping/USB-JTAG/ flash pins so the upcoming web UI pin-remap can refuse foot-guns. - components/settings: NVS facade (replaces v5 320-byte EEPROM map); exposes board namespace accessors used by main. - components/status_led: dedicated FreeRTOS task drives the onboard LED; pattern API (BOOT, AP_MODE, STA_MODE, OTA, ERROR, PANIC) replaces the inline blink-in-loop pattern from the Arduino code. - main/main.c: resolves board profile (NVS override > compile default), applies pin overrides while rejecting unsafe ones, brings up status LED. - frontend/design-source/: full Claude-Design handoff bundle (tokens.css, 7 screen JSXs, README, chat). Reference for PR #5 UI build. - .github/workflows/firmware.yml: IDF v5.3 build matrix across all four targets on push/PR; uploads tagged firmware artifacts. - README banner explaining v5 -> v6 transition, branches, and quickstart. Verified: idf.py build succeeds for esp32c3. Output ambisense.bin is ~195 KB (86%% free in the 1.4 MB app slot). Refs #v6 epic. Next: PR #2 — Wi-Fi STA/AP fallback, captive portal, esp_http_server, OTA, auth scaffold.
Captures the locked architecture decisions, the 5-PR plan with current status, and the hardware reference + flash troubleshooting ladder so future sessions/contributors can pick up the v6 rewrite without reconstructing context. - docs/V6-ARCHITECTURE.md — locked decisions (peer mesh, modular radar drivers, NVS schema, board profiles, FreeRTOS task model, HTTP API surface). Single source of truth for "why is it built this way". - docs/V6-ROADMAP.md — PR-by-PR plan (PR #1 done; PR #2-5 scoped), done criteria, tag/release cadence (v6.0.0-alpha.N → v6.0.0). - docs/HARDWARE.md — C3 SuperMini reference wiring, sensor pinouts for LD2410/LD2412/LD2420/LD2450, and a tested flash-fails-to- connect troubleshooting ladder including the macOS USB-CDC stuck-state recovery (Mac restart). - README pointers to the three new docs.
…th, OTA
Adds the full networking + web layer. AP and STA both run simultaneously
so the device works with OR without an external router — many installs
have no Wi-Fi at all and the AP becomes the only access point.
- components/netmgr — WIFI_MODE_APSTA always on. AP "AmbiSense-XXXX" up
for the lifetime of the device on a configurable channel (default 6),
open or WPA2 (NVS-configurable). STA additive: if creds saved, joins
user's router; on failure, AP stays available so the user can fix
creds via the captive portal. Captive-portal DNS responder steers
every query at the device IP so iOS/Android/Win11 auto-pop setup.
mDNS publishes <hostname>.local + _ambisense._tcp service.
- components/auth — PBKDF2-SHA256 (250k rounds) password hash, 32-byte
random session tokens, 8-slot in-RAM session store, 24h TTL. Off by
default; banner in placeholder UI nudges user to set a password.
- components/ota — esp_https_ota wrapper streaming an octet-stream
upload into the inactive OTA partition, validates, marks for boot,
reboots in 1s. Bootloader rollback armed via sdkconfig — failed boot
reverts automatically. ota_mark_valid() called early in main to defuse
rollback once the running image proves it boots clean.
- components/webui — esp_http_server with full route surface:
GET / placeholder HTML (PR #5 replaces)
GET /generate_204, /hotspot-detect.html, /connecttest.txt, ...
captive portal redirects (302 → /)
GET /api/version firmware/idf/build/uptime/heap/ip/etc.
GET /api/wifi/scan list nearby APs
POST /api/wifi set creds, deferred reconnect
POST /api/auth/login | logout | password
GET /api/board/profiles 4 board profiles + unsafe pin masks
POST /api/board save board id + per-pin overrides
GET /api/radar/kinds 5 radar drivers + active selection
GET /api/settings flat read of every NVS namespace
POST /api/settings partial update (any subset)
GET /api/distance placeholder (PR #3 wires real value)
POST /api/ota octet-stream firmware upload
WS /api/live 5 Hz JSON: distance/rssi/heap/peers
- components/settings extended: typed get/set for u32/i32/u8/blob/str
across any namespace + wifi/sys/auth shortcuts.
- main.c brings them all up in order: nvs → board → status_led → auth
→ netmgr → webui → ota_mark_valid.
Built clean; binary 950 KB (33%% free in 1.4 MB app slot). Flashed to
both C3s. AP visible from phone; captive portal expected to pop.
Refs v6 epic. Next: PR #3 — radar driver registry + LED engine.
… engine Lights and sensors come online. The peer-mesh-shaped target queue from PR #4 is the next integration; for now the LED engine consumes the local motion smoother directly. Components added: - components/radar — driver registry with all 5 v6.0 drivers compiled in: ld2410, ld2412 (alias), ld2420 (alias), ld2450, sim. Selection at runtime via NVS board.radar_kind. UART-driven parser task pushes radar_frame_t into a 1-slot queue (overwrite-style). radar_ld2410.c — 23-byte basic-mode frames, F4F3F2F1...F8F7F6F5 header/tail; extracts moving/stationary state + primary distance + energy. radar_ld2450.c — 30-byte fixed frames, AA FF 03 00 ... 55 CC; up to 3 targets with x,y,speed,resolution. Decodes the inverted sign convention (bit 15 = positive). Computes primary radial distance via integer sqrt. radar_sim.c — synthetic trace generator. Defaults to a 4 s 30..200 cm sine wave; user can POST a scripted trace to /api/sim/trace (PR #5 wires the UI). - components/motion — v5 PI smoother ported faithfully (radar_manager.cpp:38-198). Low-pass on position, EMA on velocity, position prediction with PI controller, 50 Hz task. Tunables in NVS namespace `motion`, x1000 fixed-point. - components/led_engine — uses espressif/led_strip managed component (RMT-backed) for non-blocking refresh. All 11 v5 visual modes ported: standard, rainbow, color_wave, breathing, solid, comet, pulse, fire, theater_chase, dual_scan, motion_particles. 60 Hz render task. NVS-backed parameters; led_engine_reload() called from /api/settings POST applies changes without reboot. Strip resize triggers a clean re-init of the led_strip handle. - main.c wires radar_init → motion_init → led_engine_init in order using the resolved board profile's pins (UART num, RX/TX, LED data pin). Telemetry task pumps motion_get → webui_publish_live at 5 Hz so /api/live WS clients receive smoothed distance. Build: 1.04 MB binary, 28% free in 1.4 MB app slot. Flashed to both C3 SuperMinis. Default radar driver is ld2450 (matches the bench hardware on Ravi's setup). Refs v6 epic. Next: PR #5 — Vite+Preact UI from design source so the full 7-screen dashboard replaces the placeholder HTML, then PR #4 layers peer mesh on top.
…ped binary Replaces the PR #2 placeholder HTML with the full 7-screen dashboard built from the Claude Design handoff. Single-file Preact bundle (48 KB raw, 16.9 KB gzipped) embedded via EMBED_FILES; served with Content-Encoding: gzip — no LittleFS needed. Frontend (frontend/): - Vite 5 + Preact 10 + TypeScript + vite-plugin-singlefile + terser. - src/styles.css lifts design-source/project/tokens.css colors, spacing, type, atoms verbatim. Layout adds sidebar/main on desktop, bottom tab bar on mobile (≤760px), light/dark theme toggle, prefers- reduced-motion respect. - src/components.tsx — Card, Toggle, Field, Slider, Row, Dot, ColorPicker (8 presets + native picker), useToaster. - src/led_preview.tsx — canvas LED strip preview mirrors the firmware's mode logic; on-screen animation matches the wire output. All 11 modes rendered live (standard, rainbow, color_wave, breathing, solid, comet, pulse, fire, theater_chase, dual_scan, motion_particles). - src/api.ts — fetch JSON, octet-stream OTA upload with progress, WebSocket /api/live with auto-reconnect backoff. - src/screens.tsx — all 7 screens: Live — distance meter + LED preview + device + mesh cards LEDs — mode picker with animated thumbnails, color picker, brightness/count/distance window/effects/trail/dir Motion — PI smoother sliders (live x1k fixed-point readout) Mesh — topology picker (PR #4 wires fully) Hardware — board profile dropdown, radar driver picker, per-pin overrides with unsafe-pin-mask filtering Network — STA scan + connect + AP behaviour (auto/always/ sta_only) + AP password + forget-STA + status badges System — auth password, OTA upload (drag-drop), diagnostics - Vite dev proxies /api → http://192.168.4.1 for in-browser dev. Firmware: - components/webui/ui.html.gz embedded via EMBED_FILES; handle_root serves with Content-Encoding: gzip + max-age=300 cache header. - Linker symbols _binary_ui_html_gz_start/end resolved in webui's own translation unit (moved out of main). - The PR #2 inline HTML kept under #if 0 for reference. Build: 1.05 MB binary, 27%% free. UI gzipped 16.9 KB. Flashed to both C3s (ports 21101 and 2122201). Refs v6 epic. Next: PR #4 (peer mesh) — Mesh screen gets real peer cards, topology editor, pairing flow.
Adds the mesh layer that lets multiple devices coordinate without a
master/slave relationship. Each device renders only its own LED segment,
broadcasts its smoothed reading at 5 Hz, and runs the same fusion
algorithm locally on the merged peer stream — every device arrives at
the same active position.
components/topology — explicit topology model in NVS:
- 4 kinds: straight, L_shape, U_shape, custom.
- Up to 8 segments. Each segment owned by one peer (MAC), with its
own LED virtual address range and optional distance window.
- Versioned blob; remote-gossiped updates with higher version win.
- Defaults to a single-device 30-LED segment on first boot.
components/mesh — ESP-NOW peer mesh:
- 5 Hz target broadcast (24-byte packet: distance, direction, energy,
flags, timestamp).
- Coordinator role auto-elected to lowest-MAC healthy peer; re-runs
every broadcast tick. Coordinator serves the canonical web UI host
(mDNS resolves to it).
- 30-second pairing window opens at boot AND on /api/mesh POST {pair:true}.
Outside the window, only known peers' broadcasts are accepted.
- 10-second peer health timeout marks stale peers; logs transitions.
- Topology gossip via MSG_GOSSIP carrying the full topology_t blob;
receivers compare versions before accepting.
- 4 fusion algorithms (port of v5 SENSOR_PRIORITY modes):
most_recent — newest reading wins
slave_first — non-coordinator readings preferred
master_first — coordinator's reading preferred
zone_based — per-segment dist_min/max windows score candidates;
reading inside a segment's window gets +100 score
Persisted in NVS topo.fuse.
webui adds:
- GET /api/mesh → coordinator role, fusion mode, peer list
(mac, distance, direction, rssi, healthy)
- POST /api/mesh → set fusion mode, open pairing window
- GET /api/topology → kind, version, total LEDs, segment list
- POST /api/topology → replace topology + force gossip to peers
main.c brings them up after netmgr (Wi-Fi must be started before
esp_now_init). Telemetry pump now reports mesh-fused distance + peer
counts to /api/live WS clients.
Build: 1.07 MB binary, 26%% free in app slot.
Refs v6 epic. Next: validate mesh between the two C3s on bench, then
tag v6.0.0.
…r phone-grade perf Reported issue: web UI is slow on phone. Root cause: the LEDs tab was mounting 11 simultaneous animated canvases (one per mode card), each running a 60 Hz requestAnimationFrame loop with per-pixel math (sin/cos for color_wave, Math.random per pixel for fire). Mobile GPU cooked. Fixes: - Replace per-card animated canvases with **static CSS-gradient thumbnails** (one custom gradient per mode, hand-tuned to match each mode's feel: rainbow strip, comet trail, fire vertical gradient, repeating-stripe for theater chase, particle dots, etc.). 11 canvases → 0. - Hero animated canvas remains: ONE on the Live tab, ONE on the LEDs tab, showing the active mode + live distance — exactly the "real-time where it matters" win. - led_preview now respects document.hidden — RAF returns immediately when the tab/page is backgrounded. Phone in pocket = zero CPU. - /api/version polling: 5 s → 30 s, and skipped while hidden. Distance, RSSI, peer health continue at 5 Hz over WebSocket — those are the real real-time signals. - Network tab no longer auto-scans WiFi on mount. The 1-second wifi scan was stalling first paint. User clicks "Scan" when ready. Bundle: 50.1 KB raw, 17.3 KB gzipped (+0.4 KB for the new gradient classes). Same firmware embed path; flashed both C3s. Real-time vs on-demand split: - WebSocket /api/live (5 Hz): distance, direction, RSSI, heap, uptime, peer count, healthy peer count. - Animated canvas: only the active mode's preview — mirrors hardware. - Slider feedback: immediate optimistic write + toast confirm. - Polled at 30 s: /api/version (uptime + heap). - On-demand: /api/wifi/scan, /api/board/profiles, /api/topology, etc.
…/ping health endpoint User reported: at the device's STA IP, only the page header showed — the body wouldn't render. Reproducible mid-session and not earlier on the AP IP. Most plausible cause: some browsers/edge cases (private-LAN detection, captive-portal probes, certain mobile WebViews) don't send Accept-Encoding: gzip even when the page is plain HTTP. The C3 was unconditionally returning Content-Encoding: gzip, so those clients got the gzipped binary as text and parsed only as far as the first non- ASCII byte. Fix: - EMBED both ui.html.gz (17.3 KB) and ui.html (50.1 KB). - handle_root_real now sniffs Accept-Encoding and serves the gzipped blob with Content-Encoding: gzip when supported, raw HTML otherwise. - Cost: +33 KB flash. Worth it to eliminate this whole bug class. Also added GET /api/ping → "pong" so a stuck client can curl it and confirm the server is alive even when HTML rendering misbehaves. Build: 1.13 MB binary, 22%% free in app slot. Flashed both C3s. If user's browser issue persists after this flash, the next diagnostic step is to curl http://<device-ip>/api/ping and check Content-Length headers on / — that points at TCP/HTTP plumbing rather than encoding.
…eboot button + /api/reboot
Three bugs reported by Ravi during hardware testing, three fixes:
1. **HTML response truncated at ~16 KB** (header-only render at STA IP):
esp_http_server's single-call httpd_resp_send doesn't reliably push
large bodies on the C3 — TX buffer can't accept the full 50 KB raw
HTML in one go. Switched to chunked sending (4 KB chunks +
terminator). Diagnosed via curl from host: Content-Length: 50172 but
only 15802 bytes actually arrived. Fix verified — full HTML now
delivers as Transfer-Encoding: chunked.
2. **Pins "not persisted across reboot"** — actually a UI display bug,
not a persistence bug. Verified end-to-end via curl: save led_pin=7,
/api/reboot, ping back, readback returns 7. NVS works fine.
The lie was the Hardware screen: it only fetched
/api/board/profiles (which returns profile defaults like GPIO 10)
and never /api/settings (which has saved overrides). User saw "10"
in the dropdown after their save and assumed the save was lost.
Fixed:
- ScreenHardware now fetches /api/board/profiles + /api/radar/kinds
+ /api/settings in parallel, initializes pin state from saved
values falling back to profile defaults only when no override exists.
- Each pin shows "(default)" or "(custom)" label so user can see at
a glance which pins are overridden.
- Switching board profile snaps pins to that board's defaults so
dropdowns can't point at GPIOs that don't exist on the new MCU.
3. **JSON key inconsistency** — /api/settings GET returns "button_pin"
and "status_led_pin", but /api/board POST expected "button" and
"status_led". UI couldn't round-trip cleanly. Fixed: server accepts
both forms; UI uses the canonical *_pin form everywhere.
4. **No way to reboot from the UI**:
- New POST /api/reboot endpoint (deferred 500 ms so the response
flushes before esp_restart()).
- Hardware tab: "Save & reboot" button next to "Save".
- System tab: dedicated "Reboot device" danger-style button.
- Both prompt for confirmation before rebooting.
5. **Radar driver versions audit** (Ravi asked):
- All five drivers (LD2410/LD2412/LD2420/LD2450/sim) are bare-metal
UART parsers against HiLink's official protocol specs — not
third-party libraries with versions to upgrade.
- LD2410 family: F4F3F2F1 + len + type=0x02 + head=AA + state/dist/
energy + tail=55 + F8F7F6F5; matches every LD2410 firmware ever
shipped (current v2.04.x).
- LD2412: same data-report protocol family; per-gate sensitivity
is config-command only.
- LD2450: 30-byte fixed frames AA FF 03 00...55 CC, three targets
with inverted-sign-bit convention. Current spec.
6. **Managed component versions**: IDF 5.5.2, espressif/led_strip 3.0.3,
espressif/mdns 1.11.1 — all latest stable as of pin date.
Build: 1.13 MB binary, 22%% free in app slot. Flashed both C3s.
…ar diag
User reported the implemented Live screen was missing major elements
shown in the Claude Design handoff. This PR ports the Live screen to
match the design pixel-for-pixel: system enable hero card, gradient
distance number, sparkline + min/max guides, strip preview, 4 stat
tiles, device card with all 8 fields, mesh card with peer info.
Frontend (frontend/):
- src/atoms.tsx: Icon (24 SVG paths from design), Sparkline (gradient
fill + line, exact port of core.jsx implementation), fmtUptime helper.
- src/screens.tsx Live: full rewrite matching screen-live.jsx layout —
2-column dash-grid (left: distance + strip + stat tiles; right: device
+ mesh cards), system enable hero with gradient backdrop when active,
bigger gradient distance number (64 px) with in-window/min/max chips,
client-side ring-buffer sparkline (80 samples = 16 s @ 5 Hz) with
dashed min/max guide lines.
- src/styles.css: dist-big gradient text, distance-row, dash-grid +
stat-row responsive breakpoints (collapse to 1 col at ≤900 px).
Firmware:
- components/led_engine: respects sys.enabled NVS flag — paints all
black + 100 ms tick when disabled (mesh keeps running, no light out).
- components/webui: GET/POST /api/system endpoints. POST {enabled:bool}
persists to NVS sys.enabled.
- components/radar: byte counter + last-64-bytes ring + frames-parsed
counter exposed via new GET /api/radar/diag with hint string.
Diagnosed Ravi's "distance always 0" → was actually wiring; once
fixed, diag now shows "OK — radar streaming valid frames" with hex
dump of the LD2410 frames (e.g. F4 F3 F2 F1 0D 00 02 AA 03 1E 00 64...).
- components/webui: GET/POST /api/system + /api/reboot endpoints.
/api/reboot defers 500 ms so the response can flush before
esp_restart(). Used by Hardware tab "Save & reboot" and System tab
"Reboot device" buttons.
Build: 1.12 MB binary, 21%% free in 1.4 MB app slot. UI bundle 61.8 KB
raw / 20.7 KB gzipped. Both C3s flashed and verified:
- /api/system GET → {"enabled":true}; POST cycle works
- /api/distance → 41 cm (live radar)
- /api/radar/diag → 156 frames parsed in 8 s, hint "OK"
- / → Transfer-Encoding: chunked, full 61936 bytes delivered
…wired to firmware
Comprehensive port from frontend/design-source/. Same look/feel/sizes/
placements as Claude Design handoff; every button, dropdown, field, and
toggle calls a real /api endpoint and persists in NVS or has hardware
effect. Verified all 11 endpoints live on the C3.
Frontend (src/screens.tsx full rewrite + atoms.tsx additions):
LEDs screen — 11 mode cards each with the Claude-design description
("Distance-driven cluster with directional fade", etc), animated mini
LedPreview thumbnails, color preset row + hex input, NumberAndSlider
for brightness/effect speed/intensity/trail, dual-handle distance window
slider, light span / center shift / strip length sliders. Optimistic
writes via /api/settings POST with toast confirms.
Motion screen — large smoothing toggle, real-time raw-vs-smoothed
LineChart (SVG, dashed gridlines, 80-sample ring buffer), filter
sliders (position/velocity/prediction smoothing) + PI gains panel
with explanatory hint card. All values round-trip via /api/settings.
Mesh screen — topology cards with SVG diagrams (straight/L/U/custom,
ported verbatim from design), pair-new-device button opening a 30 s
window with animated pairing card, devices list with healthy dots +
RSSI + lost%, sensor priority cards (most_recent / slave_first /
master_first / zone_based) with descriptions. Wires to /api/topology
and /api/mesh.
Hardware screen — board profile cards (not dropdown), radar driver
cards, pin map with field-label icons + dropdowns filtering unsafe
GPIOs. "Save & reboot" button when reboot is needed; reboots via
/api/reboot.
Network screen — connected status hero card with wifi icon, scan
button + signal-bars indicator + Join/current chip per network, inline
password prompt on Join, hostname input with .local suffix, AP-mode
selector (auto/always/sta_only) + Reset Wi-Fi danger button with
two-click confirmation. All endpoints wired.
System screen — firmware card with version + reboot button, drop-zone
file input with drag-drop or click-to-select, progress bar during
upload, "Flash & reboot" button. Diagnostics with heap/min-heap/
uptime/MAC. Auth section with show/hide password toggle and explicit
"Set password" / "Disable auth" button. JSON config Export downloads
/api/settings JSON. Factory reset with type-the-hostname confirmation
posts to new /api/factory_reset (erases NVS + reboots).
src/atoms.tsx adds: Icon (24 SVG paths from design), Sparkline (gradient
fill + line), NumberAndSlider, DualHandleRange (touch-friendly with
pointermove), TopologyDiagram (4 SVGs), LineChart (raw vs smoothed),
hsv2rgb / rgb2hex / hex2rgb helpers, fmtUptime, useRing.
Firmware:
- /api/factory_reset endpoint: nvs_flash_erase() + esp_restart() in
deferred task so the JSON response can flush first.
- /api/system GET/POST already shipped.
Bug fixes:
- Set Password now shows real error when < 8 chars (was silent).
- OTA upload uses postBinary() with progress callback (was generic POST).
Build: 1.16 MB binary, 19%% free in app slot. UI bundle 81.8 KB raw /
24.1 KB gzipped. Both C3s flashed.
Verified live on hardware at 192.168.0.216:
- /api/ping, /api/version, /api/distance, /api/system, /api/wifi,
/api/mesh, /api/topology, /api/board/profiles, /api/radar/kinds,
/api/radar/diag, /api/settings — all returning expected payloads.
- Radar diag: driver=ld2410, 867 frames parsed in 30 s, last frame 31 ms ago.
- Distance live: 39 cm.
…useEffect imports Two compounding bugs from the previous comprehensive rewrite that together produced "distance shows 0 + console useRef error": 1. Server-side route table overflow. esp_http_server cfg.max_uri_handlers was 32. The previous PRs added /api/mesh GET/POST, /api/topology GET/POST, /api/factory_reset, /api/ping, /api/system GET/POST, /api/radar/diag — bringing total route count to 33. The WS route is registered last, so it silently failed registration. No client could subscribe to live data, so the Live screen distance stayed at 0 forever. Bumped to 48. Verified post-flash: WS handshake returns 101 Switching Protocols from python ws client; chunked HTML still serves intact at 81 KB. 2. Client-side missing imports. atoms.tsx uses useRef (in DualHandleRange) and useEffect (in DualHandleRange + LineChart) but only imported useState from preact/hooks. Errors only fired on LEDs/Motion tabs where those components render. Added the missing imports. Build: same 1.16 MB, both C3s flashed, all 11 endpoints verified live.
…on v2 (median + adaptive) User reported visible jitter and missing UI elements. Three-pronged fix. Frontend — full app shell port from design-source/app.jsx: - LogoMark: animated triangle "A" with 3 concentric pulse rings emanating outward, radial proximity-glow that breathes (scale + opacity), corner-tick chip details. Pulse strength scales with live distance (closer target = brighter glow). Uses lm-grad (amber→orange→pink) and lm-core radial gradients verbatim from design. - Wordmark: "Ambi" in slate-gradient + "Sense" in accent-gradient, with "v6.x · ESP32" caps subtitle. - Sticky header (.app-header) with backdrop-filter blur — page name + hostname.local breadcrumb + live distance chip + RSSI chip + sun/moon theme toggle. - Sidebar with logo block + nav + bottom IP/board chip (10.0.0.x · ESP32-C3 · LD2410C style). - styles.css: keyframes logo-pulse, logo-breath, pulse-acc; .app-header, .brand-block, .sidebar-foot, .btn-icon classes. Firmware — kill the visible 200ms stair-step jitter: - ws_broadcast_task: 200 ms (5 Hz) → 50 ms (20 Hz). Bandwidth: ~2.5 KB/s, trivial. Browser sparkline now updates 4× more often, no flat dwells. - main.c telemetry_pump_task: same bump 5 Hz → 20 Hz, also publishes raw_cm now (median-filtered but un-smoothed) so the Motion screen graph shows what the firmware *actually* feeds the LED engine, removing 200 ms client-side simulation lag. - webui_live_t and the WS JSON gain a 'raw' field. Firmware — motion algorithm v2: - Median-of-5 filter on raw radar samples before the smoother. Single- sample LD2410 spurious readings (every few minutes) get out-voted; rejection is total. Insertion-sort over 5 ints — cheap. - Adaptive smoothing: alpha scales with motion magnitude. Stationary (|delta| < 30 cm/sample) uses configured pos_smooth; fast-moving (>= 30 cm/sample) ramps alpha up to 4× the configured value (capped at 0.9). Result: calm reading when still, snappy when moving — best of both worlds. - target_t exposes raw_cm alongside distance_cm. Frontend — Live sparkline + Motion graph fixes: - ScreenLive: useEffect deps changed from [dist] to [live] so the sparkline advances on every WS frame at 20 Hz, even when the integer cm value hasn't changed (was freezing during stationary periods). - ScreenMotion: raw and smoothed lines come straight from firmware WS payload, no client-side alpha simulation. Graph now reflects the real on-device smoother instead of a 200 ms-lagged copy. Build: 1.17 MB binary, 19%% free in 1.4 MB app slot. UI gzipped 25.4 KB. Both C3s flashed.
Pairing flow (PR-A):
- mesh.c broadcasts MSG_PAIR every 1s while pairing window open
- Asymmetric pairing: receiving MSG_PAIR auto-opens own window — clicking
Pair on either device pulls the other in (no need to tap both)
- mesh_identify(mac) unicasts MSG_IDENTIFY; recipient blinks status LED
at 10 Hz for 5 s so users can physically locate which board is which
- Coordinator election now has 5 s hysteresis — prevents brief flap when
a single heartbeat is dropped
- mesh_event_cb_t: peer-joined / pairing-opened/closed / identify-requested
events surface to main.c which drives the status LED reactions
- POST /api/mesh/identify {mac} endpoint
- GET /api/mesh now returns pairing/pairing_ms_left/my_mac for live UI
countdown and self-vs-peer card highlighting
Status LED:
- New STATUS_LED_PAIRING (5 Hz) and STATUS_LED_IDENTIFY (10 Hz) patterns
- status_led_oneshot(pattern, ms) — temporary pattern with auto-revert
to last stable pattern; identify becomes one line in main.c
Button (new component):
- Polling-based long-press detector (50 Hz, 40 ms debounce)
- 3 s long-press → mesh_open_pairing(); 10 s reserved for v6.1 factory reset
Motion v3 (PR-B):
- New motion_kalman.{c,h}: 1-D Kalman over [position, velocity], energy-
aware observation noise (R *= 4 when energy < 30), ±200 cm/s velocity
clamp, 3-sample direction hysteresis
- motion.c integrates: motion.mode NVS string ("kalman" default | "pi"),
motion.resp 0..100 (Calm⇄Snappy), motion.la_ms 0..500, motion.outl 0/1/2
(Off / median-3 / median-7); legacy ps/vs/pf/pg/ig kept as advanced
- motion_reload() called by /api/settings POST so changes apply live
Frontend:
- ScreenMesh: SVG circular pairing countdown, per-peer Identify button
with "Blinking…" 5 s cooldown, self-card highlight
- ScreenMotion: Algorithm picker (Kalman/Legacy PI), Response slider with
context-aware hint text, Look-ahead-ms slider, Outlier segmented control,
collapsible Advanced (5 PI knobs)
- New atoms icons: link, plus, search
Debounced saves (fixes ERR_CONNECTION_RESET):
- useDebouncedSave hook: 300 ms tail; coalesces multiple slider changes
into one POST /api/settings batch. Fixes the C3-single-core httpd
saturation when sliders fired ~30 POST/s during drag (verified with
50× rapid POST stress test — all 200 OK)
Verified: idf.py build green; ambisense.bin 1.17 MB / 1.4 MB partition
(18% free); both C3s flashed; /api/mesh, /api/settings round-trip works.
Mobile (ERR was: sidebar + bottom-tabs both rendered, scrambling layout): - Added the missing .hide-mobile / .show-mobile rules — they were referenced inline by main.tsx but never defined in styles.css, so on phones the desktop sidebar AND mobile bottom-nav both rendered at once, overlapping every card and pushing the page off-screen - Trimmed paddings, reduced page-head gap, and force-collapse all inline auto-fit grids (220/280 px min-cols) to 2-column at ≤760 px and 1-column at ≤480 px — Hardware/Mesh/Motion screens now lay out cleanly on a 360 px viewport Boot guard (ERR was: 2nd C3 not starting AP after reflash): - resolve_board_profile now rejects an NVS board.id whose profile->mcu doesn't match CONFIG_IDF_TARGET (e.g., "esp32-devkit" pinmap loaded on a C3). Wrong pinmap drives USB-JTAG / flash pins as outputs and bricks boot before netmgr starts, looking like "device dead". Falls back to the compile-time default profile in that case.
README: v6.0.0 shipped — describe the actual delivered features instead of the "in progress" status. Highlight ESP32-S3 as recommended board. HARDWARE.md additions: - Board recommendation tier table at the top (S3 recommended, C3 supported, classic deprecated, C6 avoid). Plain reasoning so users picking a board for a new install pick S3. - "Second device not visible" recovery — erase-flash + reflash for stale NVS with mismatched MCU pinmap. (v6.0 has the boot guard but alpha-flashed devices need the manual recovery once.) - "Slider throws ERR_CONNECTION_RESET" entry — explains the C3 single-core httpd saturation and the 300 ms client debounce fix shipped in 0b63b6e. - Reordered the supported-boards table by recommendation, not by validation status.
README: - Branch table now shows main = v6.0.0 (post-merge state) with v5 archived on legacy/v5-arduino. Includes a one-liner for users pulling onto an old local main. - Quickstart split into "easiest path" (flash prebuilt binary from the v6.0.0 release page) and "build from source" — the previous copy still described the PR #1 skeleton ("waiting for Wi-Fi setup in PR #2") which is wrong now that 6.0 is live. - IDF version bumped from v5.3 LTS to v5.5.2 to match what was actually used to build 6.0.0. - Reference wiring + first-boot AP setup added so a fresh user can go from clone to running without bouncing through HARDWARE.md. - "v5 (Arduino) docs" disclaimer rewritten — v5 is frozen, not "until v6 takes over". Points at legacy/v5-arduino branch. V6-ROADMAP.md: - Top banner: v6.0.0 RELEASED, links to release page. - New "v6.x roadmap (post-6.0.0)" section at the bottom captures encrypted ESP-NOW, signed OTA, persistent sessions, auto-topology learning, S3 dual-core pinning, sim-driver replay, and the few smaller follow-ups documented in the v6.0 release notes.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Promotes the v6 ESP-IDF + FreeRTOS rewrite from
v6-idf-rewritetomain. After merge, fresh clones default to v6.0.0; the v5 Arduino tree stays accessible onlegacy/v5-arduino.The shipped release is already published at https://github.com/Techposts/AmbiSense/releases/tag/v6.0.0 with prebuilt C3 binaries — this PR just makes
mainthe canonical home for that code.Recommended merge strategy: merge commit (not squash). Squashing would collapse 7 meaningful commits (PR #1 skeleton, PR #2-#5 rewrite, Kalman + pairing, mobile + boot guard, release docs) into one, losing
git bisectgranularity for any v6.x regression hunt later.What lands on
mainfirmware/(12 components: board, settings, status_led, button, netmgr, auth, webui, ota, radar, motion, led_engine, topology, mesh)frontend/) with 7 fully wired screenslegacy/AmbiSense/(frozen reference)README.md,docs/HARDWARE.md,docs/V6-ROADMAP.md,docs/V6-ARCHITECTURE.mdRecommended hardware tier (per HARDWARE.md)
Known issues shipping with 6.0.0 (deferred to 6.1+)
0b63b6e); proper fix is dual-core pinning on S3Full list and recovery procedures are in the release notes and
docs/HARDWARE.md.Test plan
idf.py buildclean foresp32c3target — binary 1.17 MB / 1.4 MB partition (18% free)http://192.168.0.216/POST /api/settingsstress test — all 200 OK (verifies the debounced-save fix)/api/meshreturnspairing_ms_leftcorrectly during 30 s window/api/settingsPOST + GET (motion_mode, response, look_ahead_ms, outlier_strength all persist).hide-mobile/.show-mobilerules now defined; sidebar disappears on phones