|
| 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