diff --git a/CHANGELOG.md b/CHANGELOG.md index 0ad50252a..7e5bf6d18 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,64 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [Unreleased] + +### Fixed +- **`provision.py` esptool v5 compat** (#391) — Stale `write_flash` (underscore) syntax in the dry-run manual-flash hint now uses `write-flash` (hyphenated) for esptool >= 5.x. The primary flash command was already correct. +- **`provision.py` silent NVS wipe** (#391) — The script replaces the entire `csi_cfg` NVS namespace on every run, so partial invocations were silently erasing WiFi credentials and causing `Retrying WiFi connection (10/10)` in the field. Now refuses to run without `--ssid`, `--password`, and `--target-ip` unless `--force-partial` is passed. `--force-partial` prints a warning listing which keys will be wiped. + +### Docs +- **CHANGELOG catch-up** (#367) — Added missing entries for v0.5.5, v0.6.0, and v0.7.0 releases. + +## [v0.7.0] — 2026-04-06 + +Model release (no new firmware binary). Firmware remains at v0.6.0-esp32. + +### Added +- **Camera ground-truth training pipeline (ADR-079)** — End-to-end supervised WiFlow pose training using MediaPipe + real ESP32 CSI. + - `scripts/collect-ground-truth.py` — MediaPipe PoseLandmarker webcam capture (17 COCO keypoints, 30fps), synchronized with CSI recording over nanosecond timestamps. + - `scripts/align-ground-truth.js` — Time-aligns camera keypoints with 20-frame CSI windows by binary search, confidence-weighted averaging. + - `scripts/train-wiflow-supervised.js` — 3-phase curriculum training (contrastive → supervised SmoothL1 → bone/temporal refinement) with 4 scale presets (lite/small/medium/full). + - `scripts/eval-wiflow.js` — PCK@10/20/50, MPJPE, per-joint breakdown, baseline proxy mode. + - `scripts/record-csi-udp.py` — Lightweight ESP32 CSI UDP recorder (no Rust build required). +- **ruvector optimizations (O6-O10)** — Subcarrier selection (70→35, 50% reduction), attention-weighted subcarriers, Stoer-Wagner min-cut person separation, multi-SPSA gradient estimation, Mac M4 Pro training via Tailscale. +- **Scalable WiFlow presets** — `lite` (189K params, ~19 min) through `full` (7.7M params, ~8 hrs) to match dataset size. +- **Pre-trained WiFlow v1 model** — 92.9% PCK@20, 974 KB, 186,946 params. Published to [HuggingFace](https://huggingface.co/ruv/ruview) under `wiflow-v1/`. + +### Validated +- **92.9% PCK@20** pose accuracy from a 5-minute data collection session with one $9 ESP32-S3 and one laptop webcam. +- Training pipeline validated on real paired data: 345 samples, 19 min training, eval loss 0.082, bone constraint 0.008. + +## [v0.6.0-esp32] — 2026-04-03 + +### Added +- **Pre-trained CSI sensing weights published** — First official pre-trained models on [HuggingFace](https://huggingface.co/ruv/ruview). `model.safetensors` (48 KB), `model-q4.bin` (8 KB 4-bit), `model-q2.bin` (4 KB), `presence-head.json`, per-node LoRA adapters. +- **17 sensing applications** — Sleep monitor, apnea detector, stress monitor, gait analyzer, RF tomography, passive radar, material classifier, through-wall detector, device fingerprint, and more. Each as a standalone `scripts/*.js`. +- **ADRs 069-078** — 10 new architecture decisions covering Cognitum Seed integration, self-supervised pretraining, ruvllm pipeline, WiFlow architecture, channel hopping, SNN, MinCut person separation, CNN spectrograms, novel RF applications, multi-frequency mesh. +- **Kalman tracker** (PR #341 by @taylorjdawson) — temporal smoothing of pose keypoints. + +### Fixed +- Security fix merged via PR #310. + +### Performance +- Presence detection: 100% accuracy on 60,630 overnight samples. +- Inference: 0.008 ms per sample, 164K embeddings/sec. +- Contrastive self-supervised training: 51.6% improvement over baseline. + +## [v0.5.5-esp32] — 2026-04-03 + +### Added +- **WiFlow SOTA architecture (ADR-072)** — TCN + axial attention pose decoder, 1.8M params, 881 KB at 4-bit. 17 COCO keypoints from CSI amplitude only (no phase). +- **Multi-frequency mesh scanning (ADR-073)** — ESP32 nodes hop across channels 1/3/5/6/9/11 at 200ms dwell. Neighbor WiFi networks used as passive radar illuminators. Null subcarriers reduced from 19% to 16%. +- **Spiking neural network (ADR-074)** — STDP online learning, adapts to new rooms in <30s with no labels, 16-160x less compute than batch training. +- **MinCut person counting (ADR-075)** — Stoer-Wagner min-cut on subcarrier correlation graph. Fixes #348 (was always reporting 4 people). +- **CNN spectrogram embeddings (ADR-076)** — Treat 64×20 CSI as an image, produce 128-dim environment fingerprints (0.95+ same-room similarity). +- **Graph transformer fusion** — Multi-node CSI fusion via GATv2 attention (replaces naive averaging). +- **Camera-free pose training pipeline** — Trains 17-keypoint model from 10 sensor signals with no camera required. + +### Fixed +- **#348 person counting** — MinCut correctly counts 1-4 people (24/24 validation windows). + ## [v0.5.4-esp32] — 2026-04-02 ### Added diff --git a/firmware/esp32-csi-node/provision.py b/firmware/esp32-csi-node/provision.py index 5ed82a9af..e574fba48 100644 --- a/firmware/esp32-csi-node/provision.py +++ b/firmware/esp32-csi-node/provision.py @@ -9,8 +9,13 @@ python provision.py --port COM7 --ssid "MyWiFi" --password "secret" --target-ip 192.168.1.20 Requirements: - pip install esptool nvs-partition-gen + pip install 'esptool>=5.0' nvs-partition-gen (or use the nvs_partition_gen.py bundled with ESP-IDF) + +WARNING -- FULL-REPLACE SEMANTICS (issue #391): + Every invocation REPLACES the entire `csi_cfg` NVS namespace on the device. + Any key you don't pass on the CLI is erased. Always include WiFi credentials + (--ssid, --password, --target-ip) unless you pass --force-partial. """ import argparse @@ -150,7 +155,7 @@ def flash_nvs(port, baud, nvs_bin): "--chip", "esp32s3", "--port", port, "--baud", str(baud), - "write_flash", + "write-flash", hex(NVS_PARTITION_OFFSET), bin_path, ] print(f"Flashing NVS partition ({len(nvs_bin)} bytes) to {port}...") @@ -199,6 +204,10 @@ def main(): parser.add_argument("--swarm-hb", type=int, help="Swarm heartbeat interval in seconds (default 30)") parser.add_argument("--swarm-ingest", type=int, help="Swarm vector ingest interval in seconds (default 5)") parser.add_argument("--dry-run", action="store_true", help="Generate NVS binary but don't flash") + parser.add_argument("--force-partial", action="store_true", + help="Allow partial config without WiFi credentials. " + "WARNING: flashing REPLACES the entire csi_cfg NVS namespace - " + "any key not passed on the CLI will be erased (issue #391).") args = parser.parse_args() @@ -215,6 +224,34 @@ def main(): if not has_value: parser.error("At least one config value must be specified") + # Bug 2 (#391): Prevent silent wipe of WiFi credentials on partial invocations. + # Flashing the generated NVS binary to offset 0x9000 REPLACES the entire + # csi_cfg namespace — there is no merge with existing NVS. Require the full + # WiFi trio unless the user explicitly opts in with --force-partial. + wifi_trio_missing = [ + name for name, val in [ + ("--ssid", args.ssid), + ("--password", args.password), + ("--target-ip", args.target_ip), + ] if val is None or val == "" + ] + if wifi_trio_missing and not args.force_partial: + parser.error( + f"Missing required WiFi credentials: {', '.join(wifi_trio_missing)}.\n" + f"\n" + f" provision.py REPLACES the entire csi_cfg NVS namespace on each run.\n" + f" Any key not passed on the CLI will be erased -- including WiFi creds.\n" + f"\n" + f" Either pass all of --ssid, --password, --target-ip,\n" + f" or add --force-partial to acknowledge that other NVS keys will be wiped." + ) + if args.force_partial and wifi_trio_missing: + print("WARNING: --force-partial is set. The following NVS keys will be WIPED " + "(not present in this invocation):", file=sys.stderr) + for k in wifi_trio_missing: + print(f" - {k.lstrip('-')}", file=sys.stderr) + print(" Plus any other csi_cfg keys not passed on the CLI.\n", file=sys.stderr) + # Validate TDM: if one is given, both should be if (args.tdm_slot is not None) != (args.tdm_total is not None): parser.error("--tdm-slot and --tdm-total must be specified together") @@ -298,7 +335,7 @@ def main(): f.write(nvs_bin) print(f"NVS binary saved to {out} ({len(nvs_bin)} bytes)") print(f"Flash manually: python -m esptool --chip esp32s3 --port {args.port} " - f"write_flash 0x9000 {out}") + f"write-flash 0x9000 {out}") return flash_nvs(args.port, args.baud, nvs_bin)