From be44299d3ec989b545d20ab15f1dd5655d33ce0d Mon Sep 17 00:00:00 2001 From: Peter Woolery Date: Mon, 27 Apr 2026 14:26:45 -0700 Subject: [PATCH] docs(readme): add quick-start, hardware sources, power draw + latency notes Adds a sourced parts table (M5 TimerCamera-F, USB cable, 5V adapter), the ~750 mW measured power draw, the 3-5s detection latency caveat, and a six-step Quick Start aimed at semi-technical operators deploying their own device. Co-Authored-By: Claude Opus 4.7 (1M context) --- README.md | 123 ++++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 115 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 1dfcb0e..3391e6f 100644 --- a/README.md +++ b/README.md @@ -2,13 +2,95 @@ Retail door traffic counter using M5Stack TimerCamera-F (ESP32 + OV3660). Counts walker traversals via overhead camera CV, passively scans BLE foot traffic, and reports hourly to `logs.research.bike`. -> **Known limitation — directional accuracy.** This firmware reports counts as `{entries, exits}` for API compatibility, but **per-walk direction labelling is not reliable at the current mount (7' overhead, straight down).** In bench testing, event detection was 100% (8/8 walks detected) while per-walk direction matched the physical walk only ~50% of the time — the centroid trajectories produced by entries and exits were nearly indistinguishable. **The number to trust is gross traffic: `entries + exits` ≈ total walkers through the doorway.** The directional split is an unreliable best-effort heuristic. See [Directional counting](#directional-counting) for why. +> **Known limitations.** +> - **Directional accuracy.** Counts are reported as `{entries, exits}` for API compatibility, but **per-walk direction labelling is not reliable at the current mount (7' overhead, straight down).** Bench testing: event detection 100% (8/8), per-walk direction ~50% (coin flip). **Trust gross traffic: `entries + exits` ≈ total walkers.** See [Directional counting](#directional-counting). +> - **Detection latency.** A walker takes **3–5 seconds** from entering the FOV to being registered as a count — the state machine waits for the walker to clear the frame (or a 5s timeout) before finalizing. Counts are not instantaneous; hourly aggregation is the intended consumption mode. ## Hardware -- **Device**: M5Stack TimerCamera-F (ESP32-S, OV3660, PSRAM, WiFi/BLE) -- **Mount**: Overhead, camera pointing straight down, centered above doorway -- **Power**: USB (any phone charger) +| Component | Source | Notes | +|-----------|--------|-------| +| **Camera** | [M5Stack TimerCamera-F (OV3660 fisheye, PSRAM)](https://shop.m5stack.com/products/esp32-psram-timer-camera-fisheye-ov3660) | ESP32 + WiFi/BLE on board | +| **USB cable** | [USB-A → USB-C, right-angle](https://www.amazon.com/dp/B0DWMPVP4F) | Right-angle plug helps with overhead mounts | +| **Power supply** | [5V USB wall adapter](https://www.amazon.com/dp/B0B2WLSY9D) | Any 5V/1A+ USB charger works | + +- **Mount**: Overhead, camera pointing straight down, centered above doorway (~7' / 2.1m height) +- **Power draw**: **~750 mW measured at the wall** (camera + WiFi + BLE all active). Runs cool — fanless, can be sealed in a small enclosure. Annual energy cost at US residential rates is well under $1. + +## Quick Start (semi-technical) + +The fastest path from "box arrived" to "counts in the dashboard." Comfortable with a terminal but not necessarily an embedded developer? Start here. + +**You will need**: the camera + cable + power supply listed above, a Linux/macOS computer with USB, and ~20 minutes. + +### 1. Install the toolchain (one-time) + +```bash +# Python 3.10+ and pip +pip install --user platformio esptool esp-idf-nvs-partition-gen +``` + +PlatformIO installs the ESP32 compiler on first build — expect a few minutes the first time. + +### 2. Clone this repo + +```bash +git clone https://github.com//DoorCounter.git +cd DoorCounter +``` + +### 3. Plug the camera in + +Connect the USB-C cable to the TimerCamera and the other end to your computer. On Linux it appears as `/dev/ttyUSB0`; on macOS as `/dev/tty.usbserial-*`. If you don't see it, install [CP210x USB drivers](https://www.silabs.com/developer-tools/usb-to-uart-bridge-vcp-drivers). + +### 4. Flash the firmware + +```bash +cd firmware +pio run -t upload --upload-port /dev/ttyUSB0 +``` + +### 5. Provision the device with its credentials + +Pick a unique device ID (e.g. `dc-0001`), a location ID, and generate a 32-byte HMAC secret. The server admin must record this same secret — counts won't be accepted without it. + +```bash +# Generate a fresh secret +openssl rand -hex 32 > my-device-secret.txt + +# Provision +python tools/flash_device.py \ + --port /dev/ttyUSB0 \ + --device-id dc-0001 \ + --location-id my-store \ + --hmac-secret "$(cat my-device-secret.txt)" \ + --wifi-ssid "MyStoreWiFi" \ + --wifi-password "wifi-password-here" +``` + +> If you skip `--wifi-ssid`/`--wifi-password`, the device opens a `DoorCounter-Setup` WiFi access point on boot. Connect a phone to it and enter the credentials in the captive portal. + +### 6. Mount the device + +1. Position above the doorway, camera lens pointing straight down (~7' / 2.1m up). +2. Plug into the wall adapter — that's it. The LED turns red while joining WiFi, then off once it's counting. +3. First heartbeat lands at the server within ~60 seconds; first hourly count batch arrives at the top of the next hour. + +### What "working" looks like + +- LED behavior: **off** = counting normally · **red** = no WiFi · **yellow** = uploading · **brief flash** when a walker is registered (1 flash = entry, 2 flashes = exit). +- A walker takes 3–5 seconds from entering the FOV to triggering the LED flash — this is normal. +- Hourly uploads to `logs.research.bike` (or your configured server) include the entry/exit counts since the last report. + +### If something is off + +| Symptom | Try | +|---------|-----| +| Red LED stays on | Wrong WiFi password — re-run step 5, or use the `DoorCounter-Setup` captive portal. | +| LED blinks ~1 Hz forever (or device reboots in a loop) | NVS got wiped — re-run step 5 with the same credentials. | +| No counts appearing on server | Run `python tools/serial_monitor.py --port /dev/ttyUSB0 --reset --timestamp --seconds 30` and watch for `[CV] entry/exit` lines as you walk under it. | + +For deeper troubleshooting see [Troubleshooting](#troubleshooting) and [Operator Setup](#operator-setup). ## Firmware @@ -123,10 +205,29 @@ python tools/flash_device.py \ WiFi credentials are optional — if omitted, device starts captive portal on boot. +**Known-good command for dc-0002** (dev device at research.bike): + +```bash +python tools/flash_device.py \ + --port /dev/ttyUSB0 \ + --device-id dc-0002 \ + --location-id retailer-123 \ + --hmac-secret "$(cat .agent/dc-0002-secret)" \ + --wifi-ssid Elly-Fi \ + --wifi-password \ + --line-offset 50 +``` + +Secret is stored in `.agent/dc-0002-secret` (gitignored). Server must already +know this secret — do not rotate without updating the server side. + > **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 +> `pio run -t upload` may clear the NVS partition on this board. +> - **FW 1.0**: device boots into a ~1 Hz LED blink (hang in "not provisioned" fatal). +> - **FW 1.1+**: device reboot-loops with `FATAL: device_id/location_id/hmac_secret not provisioned` +> followed by `rst:0xc (SW_CPU_RESET)` (FATAL paths now reboot instead of hang). +> +> Either way, re-run `flash_device.py` with the same credentials. See > [Troubleshooting](#troubleshooting). ### 3. OTA updates @@ -188,7 +289,7 @@ DoorCounter/ | 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. | +| ~1 Hz LED blink after boot (FW 1.0), OR reboot loop with `FATAL: device_id/location_id/hmac_secret not provisioned` → `rst:0xc (SW_CPU_RESET)` (FW 1.1+) | NVS missing `device_id` / `location_id` / `hmac_secret`. Commonly triggered by a firmware upload wiping NVS. FW 1.1+ reboots on FATAL instead of hanging. | Re-run `flash_device.py` with the device's known credentials (see section 2 for dc-0002). | | 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. | @@ -228,6 +329,12 @@ flash any device. cd firmware && pio run -e timercam -t upload ``` +> **If the device reboot-loops after flashing** with `FATAL: +> device_id/location_id/hmac_secret not provisioned`, NVS was wiped. Re-run +> `flash_device.py` (see [section 2](#2-provision-device-identity)). FW 1.1 +> turned the old FW 1.0 LED-blink hang into an explicit reboot loop; same +> root cause, same fix. + ### Expected first boot On the serial log (115200 baud), the device prints the boot banner, then