Skip to content

Releases: Techposts/AmbiSense

v6.2.0-alpha.5 — UI fixes (icons, light-theme logo, mobile layout)

08 May 11:38

Choose a tag to compare

Pure UI fix release on top of alpha.4. No firmware contract changes — schema 1.1 + smartghar v0.7.1 integration unchanged.

What's fixed

Missing icons (root cause for multiple reported bugs)

The Icon component was returning null for three unregistered names — download, eye, eyeOff — rendering them as empty SVGs that looked like small grey boxes:

  • The "small grey box beside Disable Auth" → the password show/hide toggle button rendering with no icon content
  • The Export button on System → no icon visible
  • Same bug, three places.

Three SVG paths added to atoms.tsx's Icon registry. All three buttons now render correctly.

Light-theme logo no longer shows a grey square

LogoMark hardcoded fill="#0a0c0f" on the rounded-rect tile + the inner triangle cutout. On dark theme that was invisible (matched the page); on light theme it leaked a black square behind the orange logo. Both swapped to var(--bg-0) so the cutouts follow whichever theme is active.

Mobile layout

Was Now
Hardware → Board profile + Radar side-by-side, squeezed Stacks vertically below 760 px
System → Firmware + Diagnostics side-by-side, squeezed Stacks vertically below 760 px
System → JSON config + Factory reset side-by-side, factory-reset input unusable Stacks vertically; full-width input
System → Firmware header row (cpu icon + version + Reboot) overflowing Reboot button wraps to its own line on phones
System → Auth password row (input + eye + Set) cramped Input gets full width; buttons sit below
System → Diagnostics values truncating on narrow phones Tighter gaps at <760 px; single-column at <480 px

How to test

  1. Web OTA from PWA: visit your device's UI → System → drop the ambisense.bin from this release into the firmware-update box.
  2. Or idf.py -p <port> flash if building from source.
  3. After reboot, verify on a real phone:
    • Light-theme logo (top-left) shows clean orange triangle, no grey square
    • System tab: Export button shows download arrow icon; Auth password row has eye icon
    • Hardware tab on portrait phone: Board profile + Radar stack vertically, both readable
    • Factory-reset input on phone is full-width, hostname easy to type

UI bundle stats

  • 99.43 kB raw / 29.12 kB gzipped (was ~28.9 kB — +0.5 KB for the three SVG paths and CSS rules)
  • Firmware: 1.184 MB / 18% partition free (unchanged)

Pair with

  • smartghar-homeassistant v0.7.1 — already paired with alpha.4. No integration changes needed for this UI-only release.

v6.2.0-alpha.4 — schema 1.1: topology + real-time WS push

07 May 15:09

Choose a tag to compare

What's new

This alpha lifts AmbiSense onto smartghar protocol schema 1.1, which the smartghar HA integration v0.7.1 needs to render AmbiSense as a single device with real-time updates.

Two backwards-compatible contract additions

1. info.topology declaration/api/v1/info now returns topology: "standalone". The HA integration uses this to collapse AmbiSense's presence entities onto the hub's HA device card. Fixes the "shows as two devices in HA" bug ("AmbiSense Hub" + synthetic "Presence Sensor").

2. /api/v1/stream WebSocket — new push channel emitting smartghar protocol frames at 3 s. The HA integration subscribes for real-time presence updates instead of falling back to 30 s polling. Existing /api/live WS (the PWA's 20 Hz dashboard feed) is unchanged.

{ "kind": "hello", "schema_version": "1.1", "hub_id": "ambisense_..." }
{ "kind": "snapshot", "hub": { "uptime_s": ..., "wifi_rssi": -45 },
  "devices": [{ "kind": "presence", ... }] }

Reserved frame kind event (per-device state delta) is documented in the spec for future use (locks, gas) but not emitted by AmbiSense yet.

Schema bumped 1.0 → 1.1

Old smartghar integration versions (≤ v0.7.0) ignore the new fields and stay on 30 s polling — backwards compatibility preserved.

Pair with

  • smartghar-homeassistant v0.7.1+ — required to consume the new contract correctly. v0.7.0 will work but won't get real-time updates and will still split AmbiSense into two HA devices.

Testing

  • idf.py build clean — 18% partition free (1.13 MB ELF / 1.37 MB partition)
  • ✅ Flash verified on ESP32-C3 SuperMini
curl -s http://ambisense-XXXX.local/api/v1/info | jq '{schema_version, topology, stream}'
# → { "schema_version": "1.1", "topology": "standalone",
#     "stream": { "ws_path": "/api/v1/stream", "frame_period_ms": 3000 } }

websocat ws://ambisense-XXXX.local/api/v1/stream
# → {"kind":"hello",...} immediately, then {"kind":"snapshot",...} every 3s

Documentation

  • Full spec: docs/SMARTGHAR-PROTOCOL.md (schema 1.1 section)
  • Decisions: docs/DECISIONS.md D-009 (topology), D-010 (split WS endpoints)

Flashing

Standard web OTA from the device's PWA, or:

esptool.py --chip esp32c3 -b 460800 write_flash --flash_mode dio --flash_size 4MB \
  0x0 bootloader.bin \
  0x8000 partition-table.bin \
  0xd000 ota_data_initial.bin \
  0x10000 ambisense.bin

AmbiSense v6.2.0-alpha.3 — fix: mDNS instance + hub_name

07 May 14:05

Choose a tag to compare

AmbiSense v6.2.0-alpha.3 — fix: mDNS instance name + hub_name

Hotfix on top of v6.2.0-alpha.2. Two bench-discovered bugs that made AmbiSense show up in Home Assistant with broken labels — discovery card said _smartghar instead of the device's name, and the device-registry showed "SmartGhar Hub (XXXXXX)" / "TankSync Hub" instead of "AmbiSense Hub".

What was broken

In v6.2.0-alpha.2, after installing the SmartGhar HA integration v0.7.0 and discovering an AmbiSense unit, HA showed:

Hubs:
  _smartghar                              ← wrong: raw service label as instance name
    Presence Sensor / AmbiSense Sensor    ← OK: 5 entities
  SmartGhar Hub (ambise)                  ← wrong: missing hub_name
    TankSync Hub • 9 entities             ← wrong: model fallback

Root causes

Bug 1: mDNS instance name leak

netmgr.c registered the smartghar service via:

mdns_service_add("_smartghar", "_smartghar", "_tcp", 80, NULL, 0);

The first argument is the friendly instance name shown in zeroconf browsers — passing "_smartghar" (matching the service-type label) leaked into HA's discovery card as _smartghar [random] instead of the device's friendly name.

Fix: pass NULL so the service inherits the global mdns_instance_name_set("AmbiSense") — discovery cards now read "AmbiSense".

Bug 2: missing hub_name field

The smartghar-homeassistant integration's SmartGharHubSensor.device_info reads info.get("hub_name") to populate HA's device-registry name. Without it, the integration falls back to "SmartGhar Hub (XXXXXX)". TankSync's firmware emits hub_name: "tanksync-f6dc" (matching its hostname); AmbiSense was missing this field entirely.

Fix: /api/v1/info now returns hub_name: "ambisense-f6dc" matching the hostname — same convention TankSync uses.

Combined effect

After flashing alpha.3 + reloading the SmartGhar integration in HA:

Hubs:
  AmbiSense                               ← discovery card shows the friendly name
    Presence Sensor • 5 entities
  ambisense-f6dc                          ← device-registry name = hostname
    AmbiSense Hub • 9 entities            ← model resolves correctly via product field

Note: the model label ("AmbiSense Hub") was always going to resolve correctly because info["product"]: "ambisense" was already in alpha.2 — the integration's hub_model_for_product() dispatcher (in v0.7.0) maps "ambisense""AmbiSense Hub". The user-visible "TankSync Hub" label in alpha.2 was the fallback from missing hub_name cascading into bad mDNS instance handling — once both are fixed, model dispatch works as designed.

Build

  • ambisense.bin 0x1207c0 (~1.16 MB), 18% partition free
  • UI bundle unchanged from alpha.2 (firmware-only fix)

Flash

pip install esptool
python -m esptool --chip esp32c3 -p /dev/cu.usbmodem... -b 460800 \
    --before default_reset --after hard_reset write_flash \
    --flash_mode dio --flash_size 4MB --flash_freq 80m \
    0x0     bootloader-c3-v6.2.0-alpha.3.bin \
    0x8000  partition-table-c3-v6.2.0-alpha.3.bin \
    0xd000  ota-data-initial-c3-v6.2.0-alpha.3.bin \
    0x10000 ambisense-c3-v6.2.0-alpha.3.bin

Or OTA from any v6.x install: drop just ambisense-c3-v6.2.0-alpha.3.bin in System → Firmware update.

After flashing

  1. Wait ~20 seconds for the device to reconnect to home Wi-Fi
  2. In HA: Settings → Devices & Services → SmartGhar → ⋮ → Reload (or wait ~30 s for the next coordinator poll)
  3. Discovery card + device-registry labels should now read "AmbiSense" / "ambisense-XXXX" / "AmbiSense Hub"

Documented

docs/DECISIONS.md records the decision log; both bugs are bench-test-only (build was always green) and reinforce the "always test before commit and release" discipline (D-007).

AmbiSense v6.2.0-alpha.2 — SmartGhar HA integration contract

07 May 13:34

Choose a tag to compare

AmbiSense v6.2.0-alpha.2 — smartghar HA integration contract

Pre-release on v6-idf-rewrite. AmbiSense now speaks the SmartGhar device protocol. Auto-discovered by the SmartGhar HA integration on the same LAN — no broker, no YAML, no per-device config. Multiple AmbiSense units coexist as separate HA devices automatically.

What's new

🏠 Home Assistant integration via SmartGhar protocol

AmbiSense advertises itself on _smartghar._tcp.local. zeroconf with a per-device hub_id derived from the MAC. The SmartGhar HA integration scans the LAN once, finds every Techposts device (AmbiSense + TankSync + future products), creates HA devices and entities for each.

Concretely, after installing the integration + adding your AmbiSense to HA, you get:

  • binary_sensor.ambisense_06f0_occupancy — occupied / vacant (with stationary, target_count, nearest_cm, seconds_since_seen as attributes)
  • sensor.ambisense_06f0_distance — nearest target distance in cm
  • sensor.ambisense_06f0_target_count — number of detected targets
  • sensor.ambisense_06f0_seconds_since_seen — vacancy timer (diagnostic)
  • sensor.ambisense_06f0_uptime, sensor.ambisense_06f0_wifi_signal, sensor.ambisense_06f0_firmware_version — hub diagnostics
  • binary_sensor.ambisense_06f0_firmware_update_available — OTA notifier

🔌 SmartGhar protocol contract — /api/v1/*

Five new REST endpoints + the existing WS /api/live:

Endpoint Purpose
GET /api/v1/info Hub identity + diagnostics — manufacturer, product, model, fw_version, hub_id, uptime, RSSI, capabilities
GET /api/v1/devices Sub-devices array — one entry of kind: "presence" for AmbiSense's standalone-hub topology
PUT /api/v1/devices/0 Config writes (vacancy_secs, radar_kind) — auth-gated
POST /api/v1/hub/identify Flash the status LED for ~3 s so users can physically locate the device from HA
POST /api/v1/hub/reboot Soft reboot — auth-gated

Existing /api/* web-UI endpoints are unchanged. The web UI still uses them; the integration uses /api/v1/* exclusively.

📡 Multiple AmbiSense units? Zero conflicts.

Each device's hub_id = ambisense_<MAC> is the integration's primary key. 5 AmbiSense units = 5 separate HA devices, all entities namespaced under their own hub_id. No setup ceremony beyond installing the integration once.

📚 Cross-product protocol spec

New: docs/SMARTGHAR-PROTOCOL.md — the wire contract every Techposts IoT device honors, designed so future products (RidgeSync fingerprint lock, etc.) integrate with one small additive PR to the integration. Documents both topology models:

  • Standalone hub (AmbiSense, mains-powered RidgeSync) — single ESP32 advertising itself, one virtual sub-device per capability
  • Hub + TX (TankSync, battery-powered multi-sensor kits) — always-on hub gateways for short-range RF TX nodes

Same wire contract for both; the difference is whether /api/v1/devices returns 1 entry or N.

🗑️ Reverted: MQTT publisher (was in v6.2.0-alpha.1 dev branch only)

After investigation (see commit log + protocol doc), MQTT was the wrong choice for this product line. The SmartGhar integration uses local push via WS + mDNS — faster (~5 ms LAN hop vs ~10–15 ms via broker), simpler (no broker setup), and fits HA's local_push IoT class. Recovered ~150 KB partition headroom by dropping the MQTT component + cJSON dependency. MQTT may return as an opt-in power-user feature later if real demand surfaces (Node-RED + Grafana users), but it's not on the v6.2 path.

Bug fixes carried from v6.1.x hotfixes

  • netmgr vTaskDelay in event handler (alpha.1) — replaced with esp_timer-based STA retry so reconnect storms don't block the Wi-Fi event loop
  • First-attempt Wi-Fi connect failed (alpha.2) — switched to WIFI_ALL_CHANNEL_SCAN
  • AP teardown after onboarding (alpha.3) — s_net.sta_configured is now updated when credentials are written at runtime, not just at boot
  • Direction chip flicker — bumped Kalman threshold to 10 cm/s + 200 ms hysteresis
  • Live row layout wobble — fixed-width chips so sparkline doesn't twitch as text changes
  • Reset Wi-Fi no feedback — optimistic toast before fire-and-forget request
  • Copy buttons on http origindocument.execCommand('copy') fallback for non-secure contexts
  • Presence "nearest 0 cm" bug — was reading targets[i].y_cm (Y axis component) instead of f.distance_cm (precomputed euclidean). Fixed.

Build

  • ambisense.bin 0x1207a0 (~1.16 MB), 18% partition free
  • UI bundle 98.5 KB raw, 28.8 KB gzipped

Companion HA integration update

Tracked in Techposts/smartghar-homeassistant#1 — adds DEVICE_KIND_PRESENCE, presence entity classes, and dispatches the AmbiSense hub model. Until that PR merges, AmbiSense will discover but only show hub-level diagnostics in HA; merge to get full presence entities.

Flash

pip install esptool
python -m esptool --chip esp32c3 -p /dev/cu.usbmodem... -b 460800 \
    --before default_reset --after hard_reset write_flash \
    --flash_mode dio --flash_size 4MB --flash_freq 80m \
    0x0     bootloader-c3-v6.2.0-alpha.2.bin \
    0x8000  partition-table-c3-v6.2.0-alpha.2.bin \
    0xd000  ota-data-initial-c3-v6.2.0-alpha.2.bin \
    0x10000 ambisense-c3-v6.2.0-alpha.2.bin

OTA from any v6.x install: drop just ambisense-c3-v6.2.0-alpha.2.bin in System → Firmware update.

What's next

v6.2.0-alpha.3 lands the WS /api/v1/stream push channel (currently the integration falls back to 30 s polling — fine for telemetry, less great for occupancy). After that, v6.2.0 stable.

For the broader Techposts product fleet, docs/SMARTGHAR-PROTOCOL.md is the spec a new product (e.g. RidgeSync) designs against — should land entirely as a small additive PR to the integration when it ships.

Bench-tested

Clean-flashed ESP32-C3 (MAC d8:3b:da:35:06:f0):

  • mDNS _smartghar._tcp advertises with all 5 TXT records ✓
  • /api/v1/info, /api/v1/devices return valid JSON ✓
  • /api/v1/devices/0 PUT round-trips vacancy_secs
  • /api/v1/hub/identify flashes the onboard status LED visibly ✓
  • All v6.1.x bug fixes carried forward verified working ✓

Documentation

AmbiSense v6.2.0-alpha.1 — presence detection + LD2410(C) restored

07 May 10:52

Choose a tag to compare

AmbiSense v6.2.0-alpha.1 — presence detection + LD2410(C) restored

Pre-release on v6-idf-rewrite. AmbiSense expands from "stairwell follow-me lighting" into a dual-purpose device: same C3 + LD2450 hardware now also acts as a room-presence sensor for Home Assistant (and any other system that speaks HTTP). LD2410 / LD2410C drivers are restored for users who want a presence-only build with first-class static-detection.

What's new

🟢 Presence detection

A new presence firmware component reads the radar stream via a non-consuming radar_peek() snapshot at 10 Hz and derives:

  • occupied — boolean, debounced by a configurable vacancy timeout (5–3600 s, default 60 s)
  • target_count — 0..3 (LD2450) or 0..1 (LD2410-family)
  • stationary — at least one target with low velocity (or always-true on LD2410-family which biases toward static detection)
  • nearest_cm — distance to the closest target (the radar driver's precomputed euclidean distance, NOT a single axis component — bench-fixed)
  • seconds_since_seen — counts up while vacant; useful for HA automations like "vacant ≥ 5 min → turn off lights"

Exposed via:

  • GET /api/presence — REST snapshot (perfect for HA's RESTful sensor)
  • /api/live WebSocket — extended payload includes presence fields inline so subscribers get distance + presence coherently

👤 New "Presence" tab

Sits between Motion and Hardware in the web UI. Includes:

  • Big Occupied / Vacant status card with stationary-vs-moving chip
  • Three stat tiles: Targets, Nearest cm, Last seen
  • Vacancy timeout slider (5–600 s, debounced save)
  • "Choosing the right radar" card explaining LD2450 vs LD2410C use cases
  • Home Assistant integration card with copy-pasteable RESTful sensor YAML

🛰️ LD2410 / LD2410C drivers restored

v6.x had stripped the LD2410-family drivers because LED-following needs (x, y) coordinates that LD2410 doesn't expose. With presence detection added, single-target distance + native static-detection becomes the right choice for some installs:

  • LD2450 (kit default) — best when you want LED follow-me AND moving-target presence. 3 targets, x/y/speed.
  • LD2410C — best for static-presence installs (couch, desk, bed) where someone sits still for long periods. Native micro-motion detection picks up breathing-level movement that LD2450 might drop after ~30–60 s of stillness.
  • LD2410 — legacy compat for v5/v6.0 hardware in the field.
  • sim — synthetic distance trace for desk testing.

Switch via Hardware → Radar kind → Save & reboot. No reflash needed.

🪟 Live tab UI stability

The Live row's chips ("still"/"closer →"/"away →", "in window"/"outside", "min N", "max N") now have fixed min-widths sized for their widest possible inner text. Without this, every direction or window-state change reflowed the whole flex row and tugged the sparkline column horizontally. No more graph wobble on every distance update.

Bug fixes

  • Presence "Nearest 0 cm" bugpresence.c was reading f.targets[i].y_cm (the Y axis component, NOT the radial distance) when the LD2450 driver actually fills f.distance_cm with the precomputed euclidean distance. Symptom: a target standing on the sensor's X axis showed "Nearest 0 cm" while the Live tab correctly showed e.g. 80 cm. Fixed.
  • Live row layout wobble — see "Live tab UI stability" above.

Architecture notes

The radar component now provides two independent access paths:

  • radar_read() — consumes from a 1-slot queue. Used by motion_task which wants a per-frame trigger.
  • radar_peek() — non-consuming snapshot under a mutex. Used by presence_task (and any future consumer) at its own polling cadence without competing with motion.

This means the LED engine's frame budget is unaffected by presence existing — they're not even on the same thread.

vacancy_secs is handled out-of-band in handle_settings_post (not in the SETTINGS map) so the in-memory presence state updates immediately on POST instead of waiting for an NVS-reload pass.

Build

  • ambisense.bin 0x11f130 (~1.16 MB), 18% partition free
  • UI bundle 96.6 KB raw, 28.4 KB gzipped (+1.6 KB vs alpha.3)

Flash

pip install esptool
python -m esptool --chip esp32c3 -p /dev/cu.usbmodem... -b 460800 \
    --before default_reset --after hard_reset write_flash \
    --flash_mode dio --flash_size 4MB --flash_freq 80m \
    0x0     bootloader-c3-v6.2.0-alpha.1.bin \
    0x8000  partition-table-c3-v6.2.0-alpha.1.bin \
    0xd000  ota-data-initial-c3-v6.2.0-alpha.1.bin \
    0x10000 ambisense-c3-v6.2.0-alpha.1.bin

Or OTA from the System tab of an existing v6.1.x install — drop just ambisense-c3-v6.2.0-alpha.1.bin. Settings persist across the upgrade (NVS layout is unchanged; the new presence namespace just appears with default vacancy_secs = 60).

Home Assistant integration (today)

# configuration.yaml
binary_sensor:
  - platform: rest
    resource: http://ambisense-XXXX.local/api/presence
    name: AmbiSense Presence
    value_template: "{{ value_json.occupied }}"
    payload_on: "true"
    payload_off: "false"
    scan_interval: 5
sensor:
  - platform: rest
    resource: http://ambisense-XXXX.local/api/presence
    name: AmbiSense Distance
    value_template: "{{ value_json.nearest_cm }}"
    unit_of_measurement: cm
    scan_interval: 5

The Presence tab in the web UI shows this exact YAML pre-filled with your hostname, so you can copy-paste straight into HA.

What's next

v6.2.0-alpha.2 brings MQTT publisher with HA auto-discovery: the device will publish itself to homeassistant/binary_sensor/<host>_occupancy/config (and friends) on first connect, so HA auto-creates the sensors without any YAML editing. RESTful sensor support stays in place for users who prefer it.

After that, v6.2.0 stable with the full presence + HA story documented.

See the v6.x roadmap.

Documentation


Bench-tested end-to-end on ESP32-C3 + LD2450 (MAC d8:3b:da:35:06:f0):

  • Presence flips Occupied within ~100 ms of target entering cone ✓
  • Vacancy timer counts correctly; flips Vacant after timeout ✓
  • Nearest distance matches Live tab distance ✓
  • Live row chips are layout-stable; sparkline doesn't wobble ✓
  • LD2410 / LD2410C drivers compile-clean; runtime selectable ✓

AmbiSense v6.1.0-alpha.3 — fix: AP teardown after onboarding

07 May 10:23

Choose a tag to compare

AmbiSense v6.1.0-alpha.3 — fix: AP teardown after first-time onboarding

Hotfix on top of v6.1.0-alpha.2. A single-line netmgr bug was leaving the captive-portal AP broadcasting forever after a fresh-NVS onboarding. Reproduced and fixed on real hardware (ESP32-C3 SuperMini, MAC d8:3b:da:35:06:f0).

What was broken

After a fresh-flash + first-time Wi-Fi onboarding via the captive portal, AmbiSense-XXXX stayed visible in the phone's Wi-Fi list indefinitely — even though STA had successfully joined the home network and the device was reachable on its home-network IP.

Reboots after that first onboarding worked correctly. The bug only manifested on the very first runtime credential-save.

Root cause

netmgr.c keeps a flag s_net.sta_configured that mirrors "are STA credentials saved in NVS." It got computed once at boot from NVS, then never updated when netmgr_set_credentials() persisted new creds. So on a fresh-NVS device:

  1. Boot → NVS empty → sta_configured = false
  2. User saves Wi-Fi creds via the web UI → NVS gets the creds, but sta_configured stays false
  3. STA connects, IP_EVENT_STA_GOT_IP fires
  4. ap_should_be_on() checks sta_configured first and returns true unconditionally when it's false — bypassing the state check
  5. The AP teardown timer never gets armed, AP stays up forever

Reboots fixed it because netmgr_init() re-reads NVS at boot and computes sta_configured = true correctly.

Fix

netmgr_set_credentials() now mirrors the in-memory flag alongside every NVS write — true on the success path, false on the clear path. One-liner. The hard part was finding it.

Build

  • ambisense.bin 0x11c6c0 (~1.13 MB), 19% partition free
  • UI bundle unchanged from alpha.2 (26.8 KB gzipped)

Flash

pip install esptool
python -m esptool --chip esp32c3 -p /dev/cu.usbmodem... -b 460800 \
    --before default_reset --after hard_reset write_flash \
    --flash_mode dio --flash_size 4MB --flash_freq 80m \
    0x0     bootloader-c3-v6.1.0-alpha.3.bin \
    0x8000  partition-table-c3-v6.1.0-alpha.3.bin \
    0xd000  ota-data-initial-c3-v6.1.0-alpha.3.bin \
    0x10000 ambisense-c3-v6.1.0-alpha.3.bin

Or OTA from the System tab of an existing v6.1.0-alpha.2 install — drop just ambisense-c3-v6.1.0-alpha.3.bin.

Verification

After flashing, walk through this on a freshly-erased C3:

  1. Connect phone to AmbiSense-XXXX
  2. Captive portal pops the setup page
  3. Save your home Wi-Fi creds
  4. Wait for the success modal
  5. Within 8–10 seconds, AmbiSense-XXXX should disappear from your phone's available networks (this is the part that was previously broken)
  6. Phone reconnects to home Wi-Fi; device reachable at the IP/hostname shown in the modal

Testing discipline note

This bug shipped in alpha.2 because the bench test confirmed end-to-end functionality (modal showed IP, device was reachable on home network) without explicitly verifying the AP-disappearance step. Going forward, "AP visibility before/after" is a discrete verification step on its own, not a sub-bullet at the end of a multi-step list.

What's next

v6.2.0-alpha.1 brings room-presence detection: restored LD2410 / LD2410C drivers (presence-only sensor option), a new presence firmware component, a new Presence tab in the web UI, and /api/presence + extended /api/live payload. See the v6.x roadmap.

Documentation

AmbiSense v6.1.0-alpha.2 — Wi-Fi onboarding UX overhaul

07 May 10:13

Choose a tag to compare

AmbiSense v6.1.0-alpha.2 — Wi-Fi onboarding UX overhaul

Pre-release on v6-idf-rewrite. Bench-tested end-to-end on a fresh-NVS ESP32-C3: connect phone to AP → save home Wi-Fi creds → success modal shows IP + mDNS hostname → AP drops within 8 s → device reachable on home network. All three reported v6.1.0-alpha.1 issues fixed.

What's fixed

1. First-attempt Wi-Fi connect now succeeds

v6.1.0-alpha.1 used WIFI_FAST_SCAN, which relies on a cached BSSID/channel from a previous successful connect — but that cache is empty on first connect, so first-attempt joins silently failed and only worked on the second try. v6.1.0-alpha.2 uses WIFI_ALL_CHANNEL_SCAN + WIFI_CONNECT_AP_BY_SIGNAL. Adds ~3 s of scan time but works reliably on the very first attempt — which is the only attempt that matters during onboarding.

2. Connecting / success / failed modal

After tapping Connect, a modal opens with a spinner and "Connecting to «SSID»…". The frontend polls /api/wifi every 1.5 s for up to 25 s. As soon as sta_connected is true and an IP is assigned, the modal flips to a success state showing two copyable URLs (http://hostname.local/ and http://192.168.x.y/) plus instructions to reconnect the phone to home Wi-Fi. On timeout, a failed modal lets you retry without re-pairing the phone.

3. AP teardown grace

After STA gets an IP, the AP stays up for 8 seconds before the netmgr's AUTO policy tears it down. Long enough for the captive-portal browser to receive the success poll and surface the URLs; short enough that the user doesn't wait forever for the AP to disappear from their phone's Wi-Fi list. Implemented as an esp_timer (not a vTaskDelay in the event loop, which was the root cause of an unrelated retry-storm bug we also fixed).

4. Copy buttons in the success modal now work

navigator.clipboard.writeText() silently no-ops on non-secure HTTP origins in modern browsers. Since we serve over HTTP from a LAN IP, the new build falls back to a hidden-textarea document.execCommand('copy') path on insecure contexts. Works in Chrome, Safari, Firefox on phone and desktop.

5. Reset Wi-Fi shows immediate feedback

The "Reset Wi-Fi" action drops STA, which kills the browser's connection to the device — the POST always errors out from the browser's perspective even though the device acted on it. The new build shows the success toast before firing the request and swallows the inevitable network error. No more "did it work?" moments.

6. Mobile layout cleanup

  • The 2-column hardware grid that overlapped on phones is gone — the Hostname card is full-width now.
  • Tap Join → page auto-scrolls to the password field, password input auto-focuses, Enter key submits.
  • Modal max-height 90 vh with overflow scroll for short viewports.
  • scroll-margin-top: 80px on the join card prevents the sticky header from clipping the Connect button after auto-scroll.

7. AP-mode picker removed from the UI

v6.1.0-alpha.1 exposed an Auto / Always-on / STA-only picker. Per the v6.x single-sensor architecture, AP behaviour should just work — no toggle needed. The firmware's netmgr_ap_mode_t enum is retained for future use, but the UI is automatic-only.

Other improvements

  • netmgr concurrency fix: replaced vTaskDelay(STA_RETRY_BACKOFF_MS) inside the Wi-Fi event handler with an esp_timer-based retry. Calling vTaskDelay inside the IDF event loop blocks every subsequent Wi-Fi event for the duration; under reconnect storms (microwave on, neighbour's camera streaming) the stack falls behind. Now the retry runs on the esp_timer task and the event loop stays responsive.
  • Stale comments cleaned: peer-mesh devices need a stable channel → no longer accurate post-v6.x; replaced with the actual rationale (captive-portal SSID stability across reboots).
  • AP max_connection dropped from 6 to 4: 6 was sized for the v6.0 mesh that's now gone; single-device only needs a phone or two.

Build

  • ambisense.bin 0x11c6a0 (~1.13 MB), 19% partition free
  • UI bundle 90.3 KB raw, 26.8 KB gzipped

Flash from binaries

pip install esptool
python -m esptool --chip esp32c3 -p /dev/cu.usbmodem... -b 460800 \
    --before default_reset --after hard_reset write_flash \
    --flash_mode dio --flash_size 4MB --flash_freq 80m \
    0x0     bootloader-c3-v6.1.0-alpha.2.bin \
    0x8000  partition-table-c3-v6.1.0-alpha.2.bin \
    0xd000  ota-data-initial-c3-v6.1.0-alpha.2.bin \
    0x10000 ambisense-c3-v6.1.0-alpha.2.bin

Or OTA from the System tab of an existing v6.1.0-alpha.1 install — drop just ambisense-c3-v6.1.0-alpha.2.bin.

What's next

v6.2.0-alpha.1 brings room-presence detection: restored LD2410 / LD2410C drivers (LD2450 stays the kit default; LD2410C is the recommended choice for static-presence-only installs like couches), a new presence firmware component derived from the radar stream, a new Presence tab in the web UI, and /api/presence + extended /api/live payload. Home Assistant MQTT auto-discovery follows in v6.2.0-alpha.2.

See the v6.x roadmap for the full sequence.

Documentation

AmbiSense v6.1.0-alpha.1 — single-sensor architecture (pre-release)

07 May 07:50

Choose a tag to compare

AmbiSense v6.1.0-alpha.1 — Single-sensor architecture (PCB-rev pre-release)

This is an alpha pre-release on the v6-idf-rewrite development branch. It is the firmware that will power the upcoming v6.x kit PCB. v6.0.0 (current main) remains the stable dual-device release until this branch is merged.

What changed and why

v6.0.0 shipped a dual-device ESP-NOW master/slave architecture: every device broadcast its smoothed radar reading at 5 Hz, ran fusion locally on the merged peer stream, elected a coordinator (lowest-MAC), and rendered its own LED segment. It worked, but bench testing on real U-shape and L-shape stair installs surfaced two structural problems we couldn't tune away:

  1. ESP-NOW jitter. Round-trip latency ran 5–15 ms in best case and spiked to 50–100 ms under retry. With the LED render budget of ~50 ms (sensor → MCU → LED to feel "instant"), the wireless hop was eating 30–60% of the budget on every frame the second sensor's reading was authoritative. The motion Kalman filter could mask short bursts but not sustained jitter — installers saw it as "lag".
  2. The second sensor is redundant for the geometries users actually install. The HiLink LD2450 reports up to 3 targets as (x_mm, y_mm, v_mm/s) within a 60° horizontal cone, ~6 m range. Mounted at the inside corner of an L or the centre-back of a U, both arms of the LED run sit inside that cone. One sensor with x/y data covers what two single-target LD2410 sensors needed mesh fusion to do.

So v6.x drops the dual-device architecture entirely and locks the firmware to one ESP32-C3 + one LD2450 per install, in preparation for a custom PCB.

Removed

  • firmware/components/mesh/ (ESP-NOW peer broadcast, coordinator election, fusion modes)
  • firmware/components/topology/ (multi-device segment graph)
  • radar_ld2410.c and the ld2410 / ld2412 / ld2420 registry entries (single-target distance-only drivers were only useful for dual-device fusion)
  • HTTP endpoints: /api/mesh, /api/mesh/identify, /api/topology (GET + POST)
  • Web UI: the Mesh tab + Topology editor + pairing window + identify button + peer health card on Live screen
  • NVS namespaces: mesh (peer blob, channel, fusion mode), topo (kind, segments)
  • Status LED patterns: STATUS_LED_PAIRING, STATUS_LED_IDENTIFY
  • Long-press → mesh_open_pairing button wiring (long-press is now reserved for v6.1 factory reset)

Net code change: −1569 lines deleted, +340 lines added across 28 files.

Kept

  • ESP-IDF + FreeRTOS task model — radar/motion/LED on independent tasks
  • LD2450 driver with 3-target tracking
  • Kalman motion filter (3-knob simplified UI plus advanced PI gains)
  • OTA with rollback, captive portal, PBKDF2-SHA256 admin auth
  • Runtime board picker (C3 SuperMini / S3-Zero / classic ESP32 / C6)
  • Preact web UI — now 6 screens: Live, LEDs, Motion, Hardware, Network, System
  • 20 Hz /api/live WebSocket telemetry
  • Debounced settings save (slider drag won't saturate httpd)
  • MCU-mismatch boot guard (catches NVS profile from a different SoC)

Trade-off accepted

Single-sensor mode requires that the LD2450 has line-of-sight to both arms of the LED run. Mount at the inside corner (L-shape) or centre-back (U-shape). For arms longer than 5 m, range-test at the far end before locking your kit configuration. This is a documented installation constraint, not a regression: it's the price of removing the wireless hop and the pairing flow.

Hardware target

ESP32-C3 SuperMini + HiLink LD2450 is the target board for the kit PCB. With no peer broadcast and no second sensor to fuse, the C3's single core has plenty of headroom for radar UART + Kalman + LED render + Wi-Fi/HTTP.

PCB-rev power-rail recommendation:

  • Discrete AP2112K-3.3 (600 mA) or MIC5219-3.3 (500 mA) feeding both C3's 3V3 and LD2450 directly — avoids loading the SuperMini onboard LDO under sustained radar transmit
  • Decoupling: 10 µF + 100 nF on LDO output, close to LD2450 VCC
  • Star-grounded LED-strip return to the PSU (not through the PCB ground plane) — strip current would otherwise inject noise into the radar reference

Other boards (S3-Zero / classic ESP32 / C6) still build clean — only C3 is the PCB target.

Build sizes (C3)

  • bootloader.bin: 0x5330 bytes (35% headroom)
  • ambisense.bin: 0x11aa70 bytes (~1.1 MB) — 20% partition free
  • UI bundle: 85.4 KB raw, 25.2 KB gzipped (down from 27.2 KB in v6.0)

Flash from binaries

Pre-built C3 binaries are attached to this release.

pip install esptool
python -m esptool --chip esp32c3 -p /dev/cu.usbmodem... -b 460800 \
    --before default_reset --after hard_reset write_flash \
    --flash_mode dio --flash_size 4MB --flash_freq 80m \
    0x0     bootloader-c3-v6.1.0-alpha.1.bin \
    0x8000  partition-table-c3-v6.1.0-alpha.1.bin \
    0xd000  ota-data-initial-c3-v6.1.0-alpha.1.bin \
    0x10000 ambisense-c3-v6.1.0-alpha.1.bin

If esptool reports Failed to connect to ESP32-C3: No serial data received, the chip needs the BOOT-mode dance: hold BOOT, tap RST while still holding, release BOOT after ~1 s, retry. See docs/HARDWARE.md for the full ladder.

Migrating from v6.0

If you were running v6.0.0 on a paired pair of devices:

  1. Decide which device will be the surviving single sensor — typically the one with the better mounting position (inside corner / centre-back).
  2. Flash this firmware onto that device. Old mesh and topo NVS keys are simply ignored, not erased — no migration required.
  3. Mount at the geometric vantage point with line-of-sight to both arms.
  4. The other device is no longer needed; you can repurpose or shelve it.

The Mesh tab is gone. The new Live tab + Hardware tab cover everything you need.

Status

  • ✅ Firmware build green on C3
  • ✅ UI build green
  • ✅ Pushed as v6.1.0-alpha.1 tag from v6-idf-rewrite
  • ⏳ Bench validation on real L-shape and U-shape benches (in progress)
  • ⏳ PCB schematic + gerbers (next)
  • ⏳ Merge to main blocked on bench validation

What's next

  1. Real-geometry range test of single LD2450 at inside-corner mounting position
  2. 2-D zone mapping in led_engine so target (x, y) projects to LED index based on strip shape config (straight / L / U)
  3. PCB design + prototype build
  4. Promote to v6.1.0 stable and merge to main

See docs/V6-ROADMAP.md for the full v6.x roadmap.

Documentation

AmbiSense v6.0.0 — full ESP-IDF + FreeRTOS rewrite

05 May 17:40

Choose a tag to compare

AmbiSense v6.0.0

The full ESP-IDF + FreeRTOS rewrite. Every subsystem now runs as an independent FreeRTOS task — radar UART parsing, motion smoothing, LED rendering, ESP-NOW peer mesh, web server, and OTA all run side-by-side instead of fighting for time inside one Arduino loop(). v5.1.1's "web request stalls the LED render" class of bug is gone.

TL;DR

  • Recommended board: ESP32-S3 (DevKitC-1 or S3-Zero) — dual-core, native USB.
  • Supported board: ESP32-C3 SuperMini — single-core; works for single-strip installs with the slider-debounce fix shipped in this release.
  • Deprecated: ESP32 classic (no advantage over S3, no native USB).
  • Avoid: ESP32-C6 (single-core, less SRAM than C3).

See docs/HARDWARE.md for full reasoning, wiring, and recovery steps.

What's new vs. v5.1.1

Architecture

  • ESP-IDF v5.5.2 + FreeRTOS — independent tasks per subsystem with per-task watchdogs.
  • Custom partition table — 16 KB NVS + 8 KB OTA data + 1408 KB × 2 OTA app slots + 960 KB LittleFS + 64 KB coredump.
  • Bootloader rollback — failed boots after OTA auto-revert to the previous slot.
  • Coredump-to-flash — crash backtraces persist across reboots; readable via idf.py coredump-info.

Radar / motion

  • Modular radar drivers — LD2410 / LD2410B/C / LD2412 / LD2420 / LD2450 / sim. All linked in; switch at runtime via the web UI without reflashing.
  • LD2450 multi-target — up to 3 targets, x/y position + speed.
  • 1-D Kalman filter (default) — energy-aware observation noise, ±200 cm/s velocity clamp, 3-sample direction hysteresis. UI exposes 3 user knobs (Response, Look-ahead, Outlier) instead of v5's 5 cryptic gains.
  • Legacy PI smoother still available as Algorithm = Legacy PI for users who tuned the v5 build.
  • 20 Hz live telemetry over WebSocket (was 5 Hz in alpha builds — visibly stair-step jitter on the distance graph).

Mesh

  • Peer mesh, not master/slave — every device runs identical code, broadcasts radar reads via ESP-NOW, fuses locally, renders only its own segment. Lowest-MAC node serves the canonical web UI.
  • Coordinator hysteresis — 5 s of stable agreement before a role flip, prevents flap from a single dropped heartbeat.
  • Asymmetric pairing — receiving a MSG_PAIR beacon auto-opens your own pairing window. Click Pair on either device → both join. No need to walk to both physical devices.
  • Topology gossip — straight / L-shape / U-shape / custom segments persist in NVS and propagate via ESP-NOW.
  • Identify — POST /api/mesh/identify {mac} blinks the recipient's onboard LED at 10 Hz for 5 s. Useful for physically locating which device is which during stairwell wiring.
  • 4 fusion modes — most-recent / slave-first / master-first / zone-based (per-segment dist windows).

LED engine

  • All 11 v5 modes — Standard, Rainbow, Color Wave, Breathing, Solid, Comet, Pulse, Fire, Theater, Dual Scan, Motion Particles.
  • led_strip via RMT — 60 Hz render task, no flicker under HTTP load.

Web UI

  • Preact + Vite — single-file embedded gzipped UI (~27 KB).
  • 7 screens — Live, LEDs, Motion, Mesh & Topology, Hardware, Network, System.
  • Responsive — sidebar nav on desktop, bottom-tab nav on phones.
  • Live preview — LED strip preview with mode-accurate animation, distance sparkline (last 16 s), raw-vs-smoothed motion chart.
  • Captive portal — iOS / Android / Win 11 setup popup auto-detects on AP join.
  • Debounced saves — sliders POST once on release, not 30×/s during drag.

Auth & OTA

  • PBKDF2-SHA256 (250k rounds) password hash; cookie sessions.
  • OTA upload with rollback armed; signed-OTA on the v6.x roadmap.
  • Factory reset with typed-confirmation in the System screen.

Boards in this release

Profile Status Notes
esp32s3-zero recommended Build-clean; pinmap correct for AliExpress S3-Zero / S3-Mini. Hardware validation pending arrival of S3 units on dev bench.
esp32c3-supermini ✅ validated The full v6.0 dev/test platform. Two C3s on Ravi's bench; pairing, identify, motion, OTA, web UI all confirmed.
esp32-devkit deprecated Builds clean; not recommended for new installs.
esp32c6-devkit builds only Single-core, less SRAM than C3 — not recommended.

Sensors in this release

Driver Sensor Targets x/y
ld2410 HiLink LD2410 (also B / C variants) 1 no
ld2412 HiLink LD2412 1 no
ld2420 HiLink LD2420 (presence only) 1 no
ld2450 HiLink LD2450 up to 3 yes
sim synthetic scripted optional

Switch via the web UI's Hardware screen. No reflash required.

Known issues / things shipping in v6.1

These are tracked but not fixed in 6.0.0:

  1. ESP-NOW packets are unencrypted. PMK/LMK setup and a Mesh-Password field in the UI are designed but not enabled in 6.0. Anyone within Wi-Fi range running an ESP-NOW sniffer can see the broadcast distance values. Defense in depth via your home Wi-Fi WPA2/3 is enough for typical installs but if you want true end-to-end encryption, wait for 6.1.
  2. Single-core C3 saturates under bulk slider input. Mitigated in 6.0 by the 300 ms client-side debounced save (verified with 50× rapid POST stress test — all 200 OK), but if you build the UI yourself make sure you're on commit 0b63b6e or later. The proper fix (dual-core pinning) requires moving to S3.
  3. Auto-topology learning is manual. You set kind = straight / L / U / custom in the Mesh tab and define segments yourself. v6.1 adds a "walk through your stairs" calibration routine that learns segment ranges from observed distance distributions.
  4. Sessions are RAM-resident. A reboot logs everyone out. NVS-backed sessions are on the v6.x roadmap.
  5. LED count tested only to 300. Theoretical maximum is 1500 per device — but no real-world install above 300 has been measured for refresh-rate / brownout.
  6. sim radar driver doesn't yet generate realistic walking traces. Currently emits a slow sine; v6.1 will replay LD2410 capture files.
  7. First-boot before STA join can take ~15 s — the captive portal AP comes up immediately, but the full web UI handler set takes a moment to finish registering. If /api/version 404s briefly after first power-on, give it a few seconds.
  8. Mesh peers must share a Wi-Fi channel. Two devices on different APs (e.g., one on a 2.4 GHz extender, one on the main 5 GHz router) won't see each other over ESP-NOW. Same SSID isn't enough — channel must match.
  9. OTA does not preserve paired peers across factory reset. Intentional, but worth noting: a factory reset wipes NVS including the topology blob. Re-pair after.

How to flash (C3 SuperMini)

You can flash from this release via either:

Option A — esptool (no IDF setup required)

pip install esptool
python -m esptool --chip esp32c3 -p /dev/ttyUSB0 -b 460800 \
    --before default_reset --after hard_reset write_flash \
    --flash_mode dio --flash_size 4MB --flash_freq 80m \
    0x0     bootloader-c3-v6.0.0.bin \
    0x8000  partition-table-c3-v6.0.0.bin \
    0x10000 ambisense-c3-v6.0.0.bin

(For other boards, build from source — see firmware/README.md.)

Option B — Web OTA (if you already have a v6.x device on the network)

System screen → Firmware update → upload ambisense-c3-v6.0.0.bin. The bootloader marks the new image as pending, reboots, and rolls back automatically if the new image fails to boot 3 times.

First-boot setup

  1. After flash, the device starts an AP named AmbiSense-XXXX (XXXX = last 4 hex of MAC). Connect from your phone — captive portal auto-pops to http://192.168.4.1/.
  2. Network screen → enter your home Wi-Fi credentials. Device will reboot, join, and (on most home networks) be discoverable as ambisense-XXXX.local/.
  3. Hardware screen → confirm board profile and radar kind. Adjust pin overrides only if you've remapped.
  4. Mesh screen → click Pair new device on each device. The pairing window is symmetric — they auto-join.
  5. System screen → set a password (optional but recommended).

What changed file-by-file

The v6-idf-rewrite branch's diff against legacy/v5-arduino is the canonical source of truth. Notable commits in this release:

  • e3b5eef — PR #1: IDF skeleton, board profiles, NVS settings, status_led
  • (PR #2-#5 in subsequent commits — Wi-Fi/web/OTA/auth/captive portal, radar+motion+LED, peer mesh + topology, full Preact UI)
  • 5d6419a — full design port of all 7 screens, every control wired
  • 0b63b6e — robust pairing flow + Motion v3 (Kalman) + debounced UI saves
  • aab9e3e — mobile layout fix + board MCU-mismatch boot guard
  • 563ee29 — release docs

Thanks

To everyone who tested the alpha builds, found the C3 USB-Serial-JTAG enumeration bug on macOS, broke the slider with rage-dragging, and patiently rewired their LD2450s when we discovered TX/RX was swapped on one of the dev boards. The v6 firmware is what it is because of you.

— Ravi Singh / TechPosts

AmbiSense v5.1.1

07 Jun 21:14
c0740c8

Choose a tag to compare

📦 AmbISense - Minor Release v5.1.1

🛠️ Fixes

  • Resolved Wi-Fi related issues to improve connectivity stability and performance.

Thank you for your continued feedback and support!