Skip to content

Commit 646861d

Browse files
authored
Merge pull request #17: AmbiSense v6.0.0 → main
AmbiSense v6.0.0 — promote v6-idf-rewrite to main
2 parents 81169ea + a4376c4 commit 646861d

106 files changed

Lines changed: 14325 additions & 0 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/firmware.yml

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
name: firmware
2+
3+
on:
4+
push:
5+
branches: [main, v6-idf-rewrite]
6+
tags: ['v6.*']
7+
paths:
8+
- 'firmware/**'
9+
- '.github/workflows/firmware.yml'
10+
pull_request:
11+
paths:
12+
- 'firmware/**'
13+
- '.github/workflows/firmware.yml'
14+
15+
jobs:
16+
build:
17+
name: idf-build (${{ matrix.target }})
18+
runs-on: ubuntu-latest
19+
strategy:
20+
fail-fast: false
21+
matrix:
22+
# esp32c3 is the validated target. Others build to keep them green
23+
# at the compile level until hardware arrives.
24+
target: [esp32c3, esp32, esp32s3, esp32c6]
25+
steps:
26+
- uses: actions/checkout@v4
27+
28+
- name: ESP-IDF build
29+
uses: espressif/esp-idf-ci-action@v1
30+
with:
31+
esp_idf_version: v5.3
32+
target: ${{ matrix.target }}
33+
path: firmware
34+
35+
- name: Upload firmware artifact
36+
if: github.ref_type == 'tag' || github.ref == 'refs/heads/main'
37+
uses: actions/upload-artifact@v4
38+
with:
39+
name: ambisense-${{ matrix.target }}-${{ github.sha }}
40+
path: |
41+
firmware/build/ambisense.bin
42+
firmware/build/bootloader/bootloader.bin
43+
firmware/build/partition_table/partition-table.bin
44+
if-no-files-found: error
45+
retention-days: 30

.gitignore

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,3 +30,15 @@
3030
*.exe
3131
*.out
3232
*.app
33+
34+
# IDE / agent state
35+
.claude/
36+
.vscode/
37+
.cache/
38+
.DS_Store
39+
40+
# ESP-IDF build output (firmware also has its own .gitignore)
41+
build/
42+
sdkconfig.old
43+
managed_components/
44+
dependencies.lock

README.md

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,115 @@
33
<img src="https://raw.githubusercontent.com/Techposts/AmbiSense/refs/heads/main/Assets/AmbiSense.webp" width="300" alt="AmbiSense Logo">
44
</p>
55

6+
> ### 🚀 v6.0.0 shipped — full ESP-IDF + FreeRTOS rewrite
7+
>
8+
> v6 is a ground-up rewrite from Arduino onto **ESP-IDF + FreeRTOS**: independent
9+
> FreeRTOS tasks for radar read, motion smoothing, LED rendering, web serving,
10+
> and ESP-NOW peer-mesh — no more cooperative `loop()` starving the LED
11+
> render under HTTP load. v6 ships with **LD2450 multi-target tracking**, a
12+
> **modular radar driver layer** (LD2410 / LD2412 / LD2420 / LD2450 / sim —
13+
> swap via web UI without reflashing), a **board picker with editable pin map**
14+
> (ESP32-C3 / ESP32 / S3 / C6), a **peer mesh** for U/L/asymmetric stairs
15+
> (every device runs identical code; lowest-MAC wins coordinator),
16+
> **asymmetric pairing** (click Pair on either device → both join), a
17+
> **Kalman-based motion filter** (3 user knobs instead of v5's 5 cryptic
18+
> gains), **OTA with rollback**, **captive portal**, **PBKDF2-SHA256 auth**,
19+
> and a **fully responsive Preact web UI** (sidebar on desktop, bottom-tab
20+
> nav on mobile).
21+
>
22+
> **Recommended hardware: ESP32-S3 (DevKitC-1 or S3-Zero).** ESP32-C3 SuperMini
23+
> is supported for single-strip installs. See
24+
> [docs/HARDWARE.md](docs/HARDWARE.md) for the full board recommendation
25+
> table and known issues.
26+
>
27+
> | Branch / tag | Purpose |
28+
> |---|---|
29+
> | `main` | v6.0.0 — current canonical firmware (this branch) |
30+
> | `v6-idf-rewrite` | active dev branch for v6.x work (encrypted ESP-NOW, S3 dual-core, auto-topology) |
31+
> | `legacy/v5-arduino` | frozen archive of the v5.x Arduino line — reference only |
32+
> | tag `v6.0.0` | shipped release with prebuilt C3 binaries on the release page |
33+
> | tag `v5.1.1` | last Arduino-era release |
34+
35+
If you cloned this repo before v6.0.0 landed and your local `main` still
36+
reflects the old Arduino tree, run `git fetch && git reset --hard origin/main`
37+
once after the merge to come up to speed. The Arduino code is preserved on
38+
`legacy/v5-arduino` and under `legacy/AmbiSense/` if you want to keep
39+
running v5.
40+
41+
## v6 quickstart (ESP-IDF v5.5.2)
42+
43+
**Easiest path** — flash the pre-built C3 binary from the
44+
[v6.0.0 release page](https://github.com/Techposts/AmbiSense/releases/tag/v6.0.0):
45+
46+
```sh
47+
pip install esptool
48+
python -m esptool --chip esp32c3 -p /dev/ttyUSB0 -b 460800 \
49+
--before default_reset --after hard_reset write_flash \
50+
--flash_mode dio --flash_size 4MB --flash_freq 80m \
51+
0x0 bootloader-c3-v6.0.0.bin \
52+
0x8000 partition-table-c3-v6.0.0.bin \
53+
0x10000 ambisense-c3-v6.0.0.bin
54+
```
55+
56+
**Build from source** — you need ESP-IDF v5.5.2 installed (`~/esp/esp-idf-v5.5.2/`):
57+
58+
```sh
59+
git clone https://github.com/Techposts/AmbiSense.git
60+
cd AmbiSense/firmware
61+
. ~/esp/esp-idf-v5.5.2/export.sh
62+
63+
idf.py set-target esp32c3 # or: esp32, esp32s3, esp32c6
64+
idf.py build flash monitor
65+
```
66+
67+
After flash the device starts a Wi-Fi AP `AmbiSense-XXXX`. Connect from
68+
your phone — the captive portal pops at `http://192.168.4.1/`. Enter
69+
home Wi-Fi creds; the device joins and is reachable as
70+
`http://ambisense-XXXX.local/`.
71+
72+
**Recommended hardware**: ESP32-S3 DevKitC-1 or S3-Zero (dual-core,
73+
native USB). **Supported**: ESP32-C3 SuperMini for single-strip installs.
74+
See [docs/HARDWARE.md](docs/HARDWARE.md) for the full table and known
75+
issues.
76+
77+
VSCode users: install Espressif's ESP-IDF extension to get IntelliSense
78+
for IDF headers — without it, clangd will report
79+
`'esp_err.h' file not found` and similar; the code still builds
80+
correctly via `idf.py`.
81+
82+
## v6 documentation
83+
84+
| Document | Read when |
85+
|---|---|
86+
| [docs/V6-ARCHITECTURE.md](docs/V6-ARCHITECTURE.md) | Picking up the v6 rewrite cold — this captures the locked architectural decisions (peer mesh, modular radar drivers, NVS schema, board profiles). Read first before proposing any structural change. |
87+
| [docs/V6-ROADMAP.md](docs/V6-ROADMAP.md) | Planning what to build next — the 5-PR plan with status per PR and the tag/release cadence. |
88+
| [docs/HARDWARE.md](docs/HARDWARE.md) | Building or debugging hardware — reference wiring for C3 SuperMini, board profiles, sensor pinouts, and a flash-fails-to-connect troubleshooting ladder. |
89+
90+
## Repo layout
91+
92+
```
93+
firmware/ — ESP-IDF v6 source (this is where new work goes)
94+
frontend/design-source/ — Claude-Design handoff bundle for the v6 UI (read frontend/design-source/README.md)
95+
legacy/AmbiSense/ — v5.x Arduino source, preserved for reference
96+
Assets/, STL Files/ — design assets, enclosures (unchanged)
97+
```
98+
99+
---
100+
101+
## v5 (Arduino) docs — reference only
102+
103+
The text below is the original v5.1.1 Arduino README, kept here for
104+
people running v5 hardware. v5 is **frozen** — no further bug fixes or
105+
features will be backported. Running v5? Track the
106+
[`legacy/v5-arduino`](https://github.com/Techposts/AmbiSense/tree/legacy/v5-arduino)
107+
branch instead of `main`, and the source is at `legacy/AmbiSense/`
108+
in this tree.
109+
110+
For new installs we strongly recommend going to v6.0.0 — see the
111+
quickstart above.
112+
113+
---
114+
6115
AmbiSense is an innovative smart lighting solution that uses radar sensing technology to create responsive ambient lighting experiences. The system detects movement and distance using an LD2410 radar sensor and dynamically controls NeoPixel LED strips in real-time, creating an interactive lighting environment.
7116

8117
The core of AmbiSense is built around an ESP32 microcontroller that interfaces with an LD2410 radar module and NeoPixel LED strips. The system creates a moving light pattern that responds to a person's proximity, with the illuminated section of the LED strip changing based on detected distance.

docs/HARDWARE.md

Lines changed: 215 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,215 @@
1+
# AmbiSense v6 — Hardware Setup & Troubleshooting
2+
3+
For locked architectural decisions about which boards/sensors are
4+
supported, see [`V6-ARCHITECTURE.md`](V6-ARCHITECTURE.md). This file
5+
covers the practical "I have hardware in front of me" stuff.
6+
7+
## Board recommendation (read this first)
8+
9+
| Tier | Board | Why |
10+
| ---- | ----- | --- |
11+
| **Recommended** | **ESP32-S3 DevKitC-1** *or* **ESP32-S3-Zero** | Dual-core LX7 @ 240 MHz, native USB-OTG (no CH340 driver fights), 512 KB SRAM. Wi-Fi/HTTP can be pinned to core 0 and radar+LED+motion to core 1 — slider floods can't starve the render loop. Headroom for v6.1 features (auto-topology, encrypted ESP-NOW, larger strips). Best choice for a stairwell with mesh peers. |
12+
| **Supported** | **ESP32-C3 SuperMini** | Single-core RISC-V @ 160 MHz. The validated v6.0 reference build. Fine for **single-strip, single-device** installs. The 300 ms client-side debounced-save shipped with v6.0 prevents HTTP saturation under slider drag. Stops being the right choice the moment you want >2 mesh peers or >300 LEDs. |
13+
| **Deprecated** | ESP32 classic (WROOM-32) | Older silicon, no native USB, no advantage over S3. Profile still builds; not recommended for new installs. |
14+
| **Avoid** | ESP32-C6 | Single-core, *less* SRAM than C3, and Wi-Fi 6 / Thread don't help AmbiSense. |
15+
16+
If you're starting from scratch and asked us "which board do I buy" → **S3-Zero**. If you already have a C3 SuperMini on the bench → it works.
17+
18+
## Reference wiring (ESP32-C3 SuperMini)
19+
20+
This is the validated reference build for v6.0.
21+
22+
| Function | C3 GPIO | Note |
23+
| -------------- | ------- | ----------------------------------- |
24+
| LED data (DIN) | GPIO 10 | WS2812 / NeoPixel |
25+
| Radar RX (MCU) | GPIO 20 | from radar's TX |
26+
| Radar TX (MCU) | GPIO 21 | to radar's RX |
27+
| Reset / mode | GPIO 4 | momentary button to GND |
28+
| Status LED | GPIO 8 | onboard (active-low, on most clones)|
29+
| 5 V power | 5V pin | for radar VCC |
30+
| Ground | GND | radar, LED logic ground |
31+
32+
**Pin defaults match the firmware out of the box** — flash the C3
33+
SuperMini build, wire to spec, no NVS pin override needed. If you
34+
remap pins from the web UI later, the unsafe-pin guard refuses
35+
strapping/USB-JTAG/flash pins (GPIOs 9, 11–19 on C3).
36+
37+
### Power supply
38+
39+
- **Logic**: USB power is fine for radar + MCU + onboard status LED.
40+
- **LED strip**: drive from a separate 5 V PSU sized to the strip
41+
(60 mA per WS2812 LED at full white). **Inject power on both ends**
42+
for runs above ~50 LEDs to prevent voltage droop and rainbow
43+
desaturation. The C3's 3.3 V LDO cannot power more than ~30 LEDs at
44+
full brightness — don't try.
45+
- **Common ground**: tie the LED PSU's GND to the C3's GND so the
46+
data signal references correctly.
47+
48+
## Sensor reference wiring
49+
50+
Both LD2410 and LD2450 use 256000 baud UART, 5 V VCC, identical pinout:
51+
52+
```
53+
Sensor MCU (C3 SuperMini)
54+
------ ------------------
55+
TX → GPIO 20 (radar_rx)
56+
RX ← GPIO 21 (radar_tx)
57+
VCC ← 5V
58+
GND ↔ GND
59+
```
60+
61+
LD2450 adds OUT pin (digital presence indicator) — leave unconnected
62+
for v6, the firmware reads everything via UART.
63+
64+
## Supported boards (v6.0)
65+
66+
| Profile | Status | LED pin | Radar RX | Radar TX | Button | Status LED |
67+
| --------------------- | ------------- | ------- | -------- | -------- | ------ | ------------- |
68+
| `esp32s3-zero` | recommended | 21 | 4 | 5 | 9 | 21 |
69+
| `esp32c3-supermini` | ✅ validated | 10 | 20 | 21 | 4 | 8 (active-low)|
70+
| `esp32-devkit` | deprecated | 5 | 16 | 17 | 4 | 2 |
71+
| `esp32c6-devkit` | builds only | 8 | 4 | 5 | 9 | 15 |
72+
73+
The `esp32s3-zero` profile is structurally validated (the firmware
74+
builds and the pinmap is correct for the AliExpress S3-Zero / S3-Mini
75+
clones) but Ravi's bench currently only has C3s — full hardware
76+
validation pending arrival of S3 units.
77+
78+
Profiles defined in
79+
[`firmware/components/board/board.c`](../firmware/components/board/board.c).
80+
Adding a new board = +1 entry there + a build target in
81+
`.github/workflows/firmware.yml`.
82+
83+
## Supported sensors (v6.0)
84+
85+
| Driver id | Sensor | Targets | x/y? |
86+
| ---------- | --------------- | ------- | ---- |
87+
| `ld2410` | HiLink LD2410(B/C) | 1 | no |
88+
| `ld2412` | HiLink LD2412 | 1 | no |
89+
| `ld2420` | HiLink LD2420 | 1 (presence only) | no |
90+
| `ld2450` | HiLink LD2450 | up to 3 | yes (LD2450 only) |
91+
| `sim` | Synthetic | scripted | optional |
92+
93+
Switch sensors at runtime via the web UI without reflashing — the
94+
driver registry compiles every driver in and selects one from NVS at
95+
boot.
96+
97+
## Troubleshooting
98+
99+
### Flash fails with "Failed to connect to ESP32-C3: No serial data received"
100+
101+
**Symptom**: `idf.py flash` fails repeatedly. The chip enumerates as
102+
`/dev/cu.usbmodem*` (so USB-CDC is up) but esptool's SYNC packets
103+
go unanswered. Sometimes the chip feels warm.
104+
105+
**Diagnosis ladder** — try in order:
106+
107+
1. **Check the port isn't being held by another process.**
108+
```sh
109+
lsof /dev/cu.usbmodem*
110+
```
111+
Common culprits on macOS: `LG Calibration` (LG monitor calibration
112+
daemon), Arduino IDE serial monitor, VSCode serial monitor. Kill
113+
any holders before retrying.
114+
115+
2. **Force the C3 into ROM download mode.** On most C3 SuperMini
116+
clones with two surface-mount buttons (RST + BOOT):
117+
- Hold `BOOT`.
118+
- While holding `BOOT`, tap `RST`.
119+
- Wait 1 s. Release `BOOT`.
120+
- Retry `idf.py flash`.
121+
122+
3. **Hold BOOT throughout the flash.** Some clones need `BOOT` held
123+
continuously, not just sampled at reset. Run `idf.py flash` with
124+
`BOOT` still pressed; release only after flash completes.
125+
126+
4. **For single-button boards**: `BOOT` is often a tiny solder pad on
127+
the back, or `BOOT = GPIO 9`. Bridge GPIO 9 to GND with tweezers
128+
while pressing `RST`.
129+
130+
5. **`--before usb_reset`** (the C3's USB-Serial-JTAG-specific reset).
131+
```sh
132+
python -m esptool --chip esp32c3 -p /dev/cu.usbmodem... \
133+
-b 460800 --before usb_reset --after hard_reset \
134+
write_flash @flash_args
135+
```
136+
137+
6. **macOS USB-CDC stuck state — restart the Mac.** This is the known
138+
final-resort fix when the chip enumerates but esptool can't sync
139+
despite all of the above. macOS sometimes caches a stale USB-CDC
140+
endpoint state for the C3's USB-Serial-JTAG and won't release it
141+
until reboot. Confirmed by Ravi as the working recovery on
142+
2026-05-05.
143+
144+
### Onboard LED stays solid after boot
145+
146+
Expected during PR #1`app_main` finishes setup and switches to
147+
`STATUS_LED_AP_MODE` (slow 1 Hz blink). If it stays solid, you're on
148+
PR #1's `BOOT` pattern still, which means `app_main` crashed before
149+
reaching the `set_pattern(AP_MODE)` call. Check the serial log over
150+
USB-Serial-JTAG (the C3's onboard USB IS the serial console).
151+
152+
### Where do I see logs?
153+
154+
The C3 SuperMini routes ESP-IDF console over its built-in USB-Serial-
155+
JTAG peripheral. Connect via:
156+
157+
```sh
158+
. $IDF_PATH/export.sh
159+
idf.py -p /dev/cu.usbmodem... monitor
160+
```
161+
162+
or any serial terminal at 115200 baud. Logs are also mirrored to a
163+
16 KB in-RAM ring buffer accessible at `GET /api/logs` once PR #2's
164+
web server is up.
165+
166+
### Device boots but second one isn't visible (no AP, no STA)
167+
168+
**Symptom**: After flashing, the device serial output shows boot logs
169+
but no AP `AmbiSense-XXXX` shows up on a phone scan and the device
170+
never joins Wi-Fi. First device works fine.
171+
172+
**Most common cause**: stale NVS holding a board profile for a
173+
different MCU (e.g., `esp32-devkit` saved on a C3 from a prior test
174+
flash). v6.0 ships an MCU-mismatch boot guard that catches this on
175+
new boots, but if you're upgrading from an alpha build the safest
176+
recovery is:
177+
178+
```sh
179+
idf.py -p /dev/cu.usbmodemXXXXXXX erase-flash
180+
idf.py -p /dev/cu.usbmodemXXXXXXX flash
181+
```
182+
183+
The erase-flash wipes the NVS partition so the device boots with
184+
factory defaults. Confirm with `idf.py monitor` — you should see
185+
`Board profile (default): esp32c3-supermini (ESP32-C3 SuperMini)`
186+
followed by netmgr starting AP.
187+
188+
### Slider in web UI throws ERR_CONNECTION_RESET
189+
190+
**Symptom**: Dragging a slider in the LED or Motion screen flood-fires
191+
`POST /api/settings` and the browser console fills with
192+
`ERR_CONNECTION_RESET` / `ERR_EMPTY_RESPONSE`.
193+
194+
**Cause**: The C3 is single-core. The HTTP server, ESP-NOW broadcast,
195+
radar UART, motion smoother, and LED render task all run on one
196+
RISC-V core. Sliders fire `onChange` ~30 times/second; each one was
197+
posting + reloading at full rate, saturating the httpd's
198+
`max_open_sockets = 7`.
199+
200+
**Fix**: v6.0 adds a 300 ms debounced-save on the client side
201+
(`useDebouncedSave` in `frontend/src/screens.tsx`). The slider only
202+
POSTs once you stop dragging, and multiple slider changes within
203+
300 ms get coalesced into a single batched request body. If you
204+
build the UI yourself, make sure you're on commit `0b63b6e` or later.
205+
206+
### Brownout reset loop
207+
208+
The C3's brownout detector is configured at threshold level 7 (~2.7 V).
209+
Brownouts on a powered-only-by-USB C3 driving a long LED strip from
210+
the same rail are common. v6 expects the **LED strip on its own PSU**;
211+
running them off the C3's 5 V/3.3 V rails will trigger the brownout
212+
detector and reboot you in a loop.
213+
214+
If you must run a few LEDs from USB power for testing, keep `count ≤ 30`
215+
and `brightness ≤ 100/255`.

0 commit comments

Comments
 (0)