docs: add Troubleshooting section + serial_monitor.py diagnostic tool
- README: note NVS may be cleared by firmware uploads (requires re-running flash_device.py); new Troubleshooting table covering the fast-blink fatal state, captive-portal fallback, and no-counts cases. - tools/serial_monitor.py: ESP32 RTS/DTR reset + serial capture with per-line elapsed-time prefix. Used to distinguish "unprovisioned" vs "WiFi failed" boot states (fast-blink LED alone is ambiguous). - README project-tree updated to include lib/cv, docs/server-prompt-…, and the new tool. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
32
README.md
32
README.md
@@ -66,6 +66,12 @@ python tools/flash_device.py \
|
|||||||
|
|
||||||
WiFi credentials are optional — if omitted, device starts captive portal on boot.
|
WiFi credentials are optional — if omitted, device starts captive portal on boot.
|
||||||
|
|
||||||
|
> **Re-provision after firmware uploads.** Flashing firmware via
|
||||||
|
> `pio run -t upload` may clear the NVS partition on this board. If the device
|
||||||
|
> boots into a ~1 Hz LED blink (the "not provisioned" fatal state) after a
|
||||||
|
> firmware update, re-run `flash_device.py` with the same credentials. See
|
||||||
|
> [Troubleshooting](#troubleshooting).
|
||||||
|
|
||||||
### 3. OTA updates
|
### 3. OTA updates
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
@@ -101,7 +107,9 @@ All requests are HMAC-SHA256 signed. See [design spec](docs/superpowers/specs/20
|
|||||||
DoorCounter/
|
DoorCounter/
|
||||||
├── firmware/
|
├── firmware/
|
||||||
│ ├── platformio.ini
|
│ ├── platformio.ini
|
||||||
│ ├── lib/hmac/ — HMAC-SHA256 signing library
|
│ ├── lib/
|
||||||
|
│ │ ├── cv/ — CV pipeline (blob tracking, line cross, cooldown)
|
||||||
|
│ │ └── hmac/ — HMAC-SHA256 signing library
|
||||||
│ └── src/
|
│ └── src/
|
||||||
│ ├── main.cpp — FreeRTOS tasks, boot sequence
|
│ ├── main.cpp — FreeRTOS tasks, boot sequence
|
||||||
│ ├── config.* — NVS read/write
|
│ ├── config.* — NVS read/write
|
||||||
@@ -111,8 +119,24 @@ DoorCounter/
|
|||||||
│ └── reporter.* — hourly batch POST + local buffer
|
│ └── reporter.* — hourly batch POST + local buffer
|
||||||
├── tools/
|
├── tools/
|
||||||
│ ├── flash_device.py — NVS provisioning script
|
│ ├── flash_device.py — NVS provisioning script
|
||||||
│ └── ota_push.py — OTA push script
|
│ ├── ota_push.py — OTA push script
|
||||||
├── docs/superpowers/specs/
|
│ └── serial_monitor.py — reset + read serial with timestamps (diagnostic)
|
||||||
│ └── 2026-04-13-door-counter-design.md
|
├── docs/
|
||||||
|
│ ├── server-prompt-crossing-cooldown.md — server-side coordination notes
|
||||||
|
│ └── superpowers/specs/2026-04-13-door-counter-design.md
|
||||||
└── server/ — API server (separate deployment)
|
└── server/ — API server (separate deployment)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
| Symptom | Likely cause | Remedy |
|
||||||
|
|---------|--------------|--------|
|
||||||
|
| ~1 Hz LED blink after boot, no serial beyond `esp_core_dump_flash: No core dump partition found!` | NVS missing `device_id` / `location_id` / `hmac_secret`. Commonly triggered by a firmware upload wiping NVS. | Re-run `flash_device.py` with the device's known credentials. |
|
||||||
|
| Device stays on `DoorCounter-Setup` AP instead of joining customer WiFi | SSID/password in NVS wrong, or network out of range. | Connect phone to `DoorCounter-Setup` → captive portal → re-enter WiFi. Or reflash NVS with correct `--wifi-ssid` / `--wifi-password`. |
|
||||||
|
| No entries/exits counted for a known-walking doorway | WiFi captive portal still up (camera task starts only after connect); or camera blocked/unfocused. | Check LED: solid on = booting/uploading, off = counting. Run `serial_monitor.py` to see `[CV] entry/exit` log lines. |
|
||||||
|
|
||||||
|
Capture a boot log with timestamps:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python tools/serial_monitor.py --port /dev/ttyUSB0 --reset --timestamp --seconds 30
|
||||||
|
```
|
||||||
|
|||||||
56
tools/serial_monitor.py
Executable file
56
tools/serial_monitor.py
Executable file
@@ -0,0 +1,56 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# Serial monitor for ESP32. Optionally pulses RTS/DTR to reset the device
|
||||||
|
# so we capture boot output. Prefixes each line with elapsed seconds.
|
||||||
|
import serial, sys, time, argparse
|
||||||
|
|
||||||
|
def main():
|
||||||
|
ap = argparse.ArgumentParser()
|
||||||
|
ap.add_argument("--port", default="/dev/ttyUSB0")
|
||||||
|
ap.add_argument("--baud", type=int, default=115200)
|
||||||
|
ap.add_argument("--seconds", type=int, default=20)
|
||||||
|
ap.add_argument("--reset", action="store_true",
|
||||||
|
help="Pulse RTS/DTR to reset the ESP32 before reading")
|
||||||
|
ap.add_argument("--timestamp", action="store_true",
|
||||||
|
help="Prefix each line with elapsed seconds since boot")
|
||||||
|
args = ap.parse_args()
|
||||||
|
|
||||||
|
try:
|
||||||
|
s = serial.Serial(args.port, args.baud, timeout=0.2,
|
||||||
|
rtscts=False, dsrdtr=False)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"[open-fail] {e}", flush=True)
|
||||||
|
sys.exit(2)
|
||||||
|
|
||||||
|
if args.reset:
|
||||||
|
s.setDTR(False)
|
||||||
|
s.setRTS(True)
|
||||||
|
time.sleep(0.1)
|
||||||
|
s.setRTS(False)
|
||||||
|
s.reset_input_buffer()
|
||||||
|
|
||||||
|
t0 = time.time()
|
||||||
|
end = t0 + args.seconds
|
||||||
|
buf = b""
|
||||||
|
while time.time() < end:
|
||||||
|
chunk = s.read(512)
|
||||||
|
if chunk:
|
||||||
|
buf += chunk
|
||||||
|
while b"\n" in buf:
|
||||||
|
line, buf = buf.split(b"\n", 1)
|
||||||
|
text = line.decode("utf-8", errors="replace").rstrip("\r")
|
||||||
|
if args.timestamp:
|
||||||
|
sys.stdout.write(f"[{time.time()-t0:5.1f}s] {text}\n")
|
||||||
|
else:
|
||||||
|
sys.stdout.write(text + "\n")
|
||||||
|
sys.stdout.flush()
|
||||||
|
if buf:
|
||||||
|
text = buf.decode("utf-8", errors="replace")
|
||||||
|
if args.timestamp:
|
||||||
|
sys.stdout.write(f"[{time.time()-t0:5.1f}s] {text}\n")
|
||||||
|
else:
|
||||||
|
sys.stdout.write(text)
|
||||||
|
sys.stdout.flush()
|
||||||
|
s.close()
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
Reference in New Issue
Block a user