diff --git a/.gitignore b/.gitignore index 47baaede..e19eebeb 100644 --- a/.gitignore +++ b/.gitignore @@ -23,6 +23,14 @@ rust-port/wifi-densepose-rs/data/recordings/ nvs.bin nvs_config.csv nvs_provision.bin +firmware/esp32-csi-node/nvs_seed.csv +firmware/esp32-csi-node/nvs_seed.bin +firmware/esp32-csi-node/nvs_config.bin +firmware/esp32-csi-node/nvs_wifi.bin +firmware/esp32-csi-node/nvs.bin +# Catch any other NVS binaries/CSVs with credentials +**/nvs_*.bin +**/nvs_*.csv # Working artifacts that should not land in root /*.wasm diff --git a/CHANGELOG.md b/CHANGELOG.md index 68785894..0ad50252 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,29 @@ 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). +## [v0.5.4-esp32] — 2026-04-02 + +### Added +- **ADR-069: ESP32 CSI → Cognitum Seed RVF ingest pipeline** — Live-validated pipeline connecting ESP32-S3 CSI sensing to Cognitum Seed (Pi Zero 2 W) edge intelligence appliance. 339 vectors ingested, 100% kNN validation, SHA-256 witness chain verified. +- **Feature vector packet (magic 0xC5110003)** — New 48-byte packet with 8 normalized dimensions (presence, motion, breathing, heart rate, phase variance, person count, fall, RSSI) sent at 1 Hz alongside vitals. +- **`scripts/seed_csi_bridge.py`** — Python bridge: UDP listener → HTTPS ingest with bearer token auth, `--validate` (kNN + PIR ground truth), `--stats`, `--compact` modes, hash-based vector IDs, NaN/inf rejection, source IP filtering, retry logic. +- **Arena Physica research** — 26 research documents in `docs/research/` covering Maxwell's equations in WiFi sensing, Arena Physica Studio analysis, SOTA WiFi sensing 2025-2026, GOAP implementation plan for ESP32 + Pi Zero. +- **Cognitum Seed MCP integration** — 114-tool MCP proxy enables AI assistants to query sensing state, vectors, witness chain, and device status directly. + +### Fixed +- **Compressed frame magic collision** — Reassigned compressed frame magic from `0xC5110003` to `0xC5110005` to free `0xC5110003` for feature vectors. +- **Uninitialized `s_top_k[0]` read** — Guarded variance computation against `s_top_k_count == 0` in `send_feature_vector()`. +- **Presence score normalization** — Bridge now divides by 15.0 instead of clamping, preserving dynamic range for raw values 1.41-14.92. +- **Stale magic references** — Updated ADR-039, DDD model to reflect `0xC5110005` for compressed frames. + +### Security +- **Credential exposure remediation** — Removed hardcoded WiFi passwords and bearer tokens from source files. Added NVS binary/CSV patterns to `.gitignore`. Environment variable fallback for bearer token. +- **NaN/Inf injection prevention** — Bridge validates all feature dimensions are finite before Seed ingest. +- **UDP source filtering** — `--allowed-sources` argument restricts packet acceptance to known ESP32 IPs. + +### Changed +- Wire format table now includes 6 magic numbers: `0xC5110001` (raw), `0xC5110002` (vitals), `0xC5110003` (features), `0xC5110004` (WASM events), `0xC5110005` (compressed), `0xC5110006` (fused vitals). + ## [v0.5.3-esp32] — 2026-03-30 ### Added diff --git a/docs/adr/ADR-039-esp32-edge-intelligence.md b/docs/adr/ADR-039-esp32-edge-intelligence.md index 0eec7604..f1862ad8 100644 --- a/docs/adr/ADR-039-esp32-edge-intelligence.md +++ b/docs/adr/ADR-039-esp32-edge-intelligence.md @@ -24,7 +24,7 @@ No on-device processing. CSI frames streamed as-is (magic `0xC5110001`). - Phase extraction and unwrapping from I/Q pairs - Welford running variance per subcarrier - Top-K subcarrier selection by variance -- Delta compression (XOR + RLE) for 30-50% bandwidth reduction (magic `0xC5110003`) +- Delta compression (XOR + RLE) for 30-50% bandwidth reduction (magic `0xC5110005`, reassigned from `0xC5110003` by ADR-069) ### Tier 2 — Full Edge Intelligence All of Tier 1, plus: @@ -50,7 +50,7 @@ Core 0 (WiFi) Core 1 (DSP) │ Multi-person clustering │ │ Delta compression │ │ ──▶ UDP vitals (0xC5110002)│ - │ ──▶ UDP compressed (0x03) │ + │ ──▶ UDP compressed (0x05) │ └──────────────────────────┘ ``` @@ -73,11 +73,11 @@ Core 0 (WiFi) Core 1 (DSP) | 24-27 | u32 LE | Timestamp (ms since boot) | | 28-31 | u32 LE | Reserved | -**Compressed Frame (magic `0xC5110003`)**: +**Compressed Frame (magic `0xC5110005`, reassigned from `0xC5110003` by ADR-069)**: | Offset | Type | Field | |--------|------|-------| -| 0-3 | u32 LE | Magic `0xC5110003` | +| 0-3 | u32 LE | Magic `0xC5110005` | | 4 | u8 | Node ID | | 5 | u8 | WiFi channel | | 6-7 | u16 LE | Original I/Q length | diff --git a/docs/adr/ADR-066-esp32-swarm-seed-coordinator.md b/docs/adr/ADR-066-esp32-swarm-seed-coordinator.md index 9ef3ee0e..40cc5383 100644 --- a/docs/adr/ADR-066-esp32-swarm-seed-coordinator.md +++ b/docs/adr/ADR-066-esp32-swarm-seed-coordinator.md @@ -265,6 +265,10 @@ python provision.py --port COM8 \ - **Pi Zero 2 W limits** — 512 MB RAM, single-core ARM; adequate for 20 nodes but not 100+ - **No WASM OTA via Seed** — currently WASM modules are uploaded per-node; future work could use Seed as WASM distribution hub +### Implementation Progress + +**ADR-069** implements the first stage of this swarm vision with live hardware validation (2026-04-02). A single ESP32-S3 node (COM9, firmware v0.5.2) was validated sending CSI-derived feature vectors through a host-side bridge into the Cognitum Seed's RVF store (firmware v0.8.1). The pipeline confirmed: UDP streaming (211 packets/15s), 8-dim feature extraction, batched HTTPS ingest (4 batches of 5 vectors), and witness chain integrity (193 entries, SHA-256 verified). Multi-node deployment (Phase 4 of ADR-069) is the next step toward the full swarm architecture described here. + ### Future Work - **Seed-initiated WASM push** — Seed distributes WASM modules to all nodes via their OTA endpoints diff --git a/docs/adr/ADR-068-per-node-state-pipeline.md b/docs/adr/ADR-068-per-node-state-pipeline.md index 4438b714..7e11792c 100644 --- a/docs/adr/ADR-068-per-node-state-pipeline.md +++ b/docs/adr/ADR-068-per-node-state-pipeline.md @@ -171,6 +171,10 @@ Validation plan: - Node ID collisions (mitigated by NVS persistence since v0.5.0) - HashMap growth without cleanup (mitigated by stale-node eviction) +## Related ADRs + +- **ADR-069** (ESP32 CSI → Cognitum Seed RVF Ingest Pipeline) extends this ADR's per-node state architecture with Cognitum Seed integration. Live hardware validation (2026-04-02) confirmed per-node feature vectors flowing through the bridge into the Seed's RVF store with witness chain attestation. + ## References - Issue #249: Detection window same regardless (24 comments) diff --git a/docs/adr/ADR-069-cognitum-seed-csi-pipeline.md b/docs/adr/ADR-069-cognitum-seed-csi-pipeline.md new file mode 100644 index 00000000..4999e48c --- /dev/null +++ b/docs/adr/ADR-069-cognitum-seed-csi-pipeline.md @@ -0,0 +1,403 @@ +# ADR-069: ESP32 CSI → Cognitum Seed RVF Ingest Pipeline + +| Field | Value | +|------------|----------------------------------------------------------| +| Status | Accepted | +| Date | 2026-04-02 | +| Authors | rUv, claude-flow | +| Drivers | #348 (multinode mesh accuracy), Research: Arena Physica | +| Supersedes | — | +| Related | ADR-066 (ESP32 swarm + Seed coordinator), ADR-068 (per-node state), ADR-018 (CSI binary protocol), ADR-039 (edge intelligence), ADR-065 (happiness scoring + Seed bridge) | + +## Context + +The wifi-densepose project has two hardware components that need to work as an integrated sensing pipeline: + +1. **ESP32-S3** (COM9 / 192.168.1.105) — Captures WiFi CSI at 100 Hz, runs dual-core DSP pipeline (phase extraction, subcarrier selection, breathing/heart rate estimation, presence/fall detection), and sends ADR-018 binary frames via UDP. + +2. **Cognitum Seed** (USB / 169.254.42.1 / 192.168.1.109) — A Pi Zero 2 W edge intelligence appliance running firmware v0.8.1. It provides: + - **RVF vector store** — Append-only binary format with content-addressed IDs, kNN queries (cosine/L2/dot), and kNN graph with boundary analysis + - **Witness chain** — SHA-256 tamper-evident audit trail for every write operation + - **Ed25519 custody** — Device-bound keypair for cryptographic attestation + - **Sensor pipeline** — 5 sensors (reed switch, PIR, vibration, ADS1115 4-ch ADC, BME280), 13 drift detectors, anti-spoofing + - **Cognitive container** — Spectral graph analysis with Stoer-Wagner min-cut fragility scoring + - **MCP proxy** — 114 tools via JSON-RPC 2.0 for AI assistant integration + - **Thermal governor** — DVFS management with zone-based frequency scaling + - **Temporal coherence** — Phase boundary detection across vector store evolution + - **Swarm sync** — Epoch-based delta replication between peers + - **Reflex rules** — 3 rules (fragility alarm, drift cutoff, HD anomaly indicator) + - **98 HTTPS API endpoints** with per-client bearer token authentication + +### Current State + +| Component | Status | Details | +|-----------|--------|---------| +| ESP32 CSI capture | Working | 100 Hz, ADR-018 binary frames via UDP | +| ESP32 edge DSP | Working | 10-stage pipeline on Core 1 (phase, variance, vitals, fall) | +| ESP32 → sensing-server | Working | UDP port 5005, binary protocol | +| Cognitum Seed | Online | v0.8.1, paired, 19 vectors, epoch 25, WiFi connected | +| Seed vector store | Working | 8-dim RVF, kNN queries in 85ms for 20k vectors | +| Seed MCP proxy | Working | 114 tools, default-deny policy | +| ESP32 → Seed pipeline | **Validated** | Bridge on host laptop, UDP 5006 → HTTPS ingest (see Validation Results) | + +### Gap Analysis (from Arena Physica research) + +Arena Physica's approach (Heaviside-0 forward model, Marconi-0 inverse diffusion) demonstrates that neural surrogates for Maxwell's equations are production-viable. Our research identified that: + +1. **Physics-informed intermediate supervision** — Evaluating pipeline stages independently catches failures that end-to-end metrics miss +2. **Vector embeddings for EM fields** — Storing CSI features as vectors enables similarity search for environment fingerprinting and anomaly detection +3. **Witness chain for sensing integrity** — Tamper-evident audit trails are critical for healthcare/safety applications (fall detection, vital signs) +4. **Edge compute for inference** — Pi Zero 2 W can run ~2.5M parameter models at 10+ Hz with INT8 quantization + +### Problem + +There is no pipeline connecting ESP32 CSI sensing to the Cognitum Seed's vector store. The ESP32 sends raw CSI frames to the Rust sensing-server (typically running on a laptop/desktop), but cannot leverage the Seed's: +- Persistent vector storage with kNN search +- Cryptographic witness chain for data integrity +- Cognitive container for structural analysis +- Sensor fusion with environmental sensors (BME280 temperature/humidity, PIR motion) +- Swarm sync for multi-Seed deployments + +## Decision + +Build a three-stage pipeline connecting ESP32 CSI capture to Cognitum Seed RVF storage: + +### Architecture + +``` +┌──────────────────────────┐ +│ ESP32-S3 (COM9) │ +│ node_id=1 │ +│ 192.168.1.105 │ +│ Firmware v0.5.2 │ +│ ┌──────────────────────┐ │ +│ │ Core 0: WiFi + CSI │ │ +│ │ 100 Hz capture │ │ +│ │ ADR-018 framing │ │ +│ ├──────────────────────┤ │ +│ │ Core 1: Edge DSP │ │ +│ │ Phase extraction │ │ +│ │ Subcarrier select │ │ +│ │ Vital signs (HR/BR)│ │ +│ │ Presence/fall det. │ │ +│ │ Feature vector │ │◄── 8-dim feature extraction +│ └──────────┬───────────┘ │ +│ │ UDP │ +└────────────┼─────────────┘ + │ Port 5005 (raw CSI, magic 0xC5110001) + │ + Port 5006 (vitals 0xC5110002 + features 0xC5110003) + ▼ +┌────────────────────────────────────────────┐ +│ Host Laptop (192.168.1.20) │ +│ Bridge script (Python) │ +│ ┌────────────────────────────────────────┐ │ +│ │ Stage 1: CSI Receiver │ │ +│ │ UDP listener on port 5006 │ │ +│ │ Parses 0xC5110003 feature packets │ │ +│ │ (also accepts 0xC5110001/0002) │ │ +│ │ Batches 10 vectors per ingest │ │ +│ └──────────┬─────────────────────────────┘ │ +└────────────┼───────────────────────────────┘ + │ HTTPS POST (bearer token) + ▼ +┌────────────────────────────────────────────┐ +│ Cognitum Seed (Pi Zero 2 W) │ +│ 169.254.42.1 / 192.168.1.109 │ +│ Firmware v0.8.1 │ +│ ┌────────────────────────────────────────┐ │ +│ │ Stage 2: RVF Ingest │ │ +│ │ POST /api/v1/store/ingest │ │ +│ │ Content-addressed vector ID │ │ +│ │ Metadata: node_id, timestamp, type │ │ +│ │ Witness chain entry per batch │ │ +│ ├────────────────────────────────────────┤ │ +│ │ Stage 3: Cognitive Analysis │ │ +│ │ kNN graph rebuild (every 10s) │ │ +│ │ Boundary analysis (fragility) │ │ +│ │ Temporal coherence (phase detect) │ │ +│ │ Reflex rules (alarm triggers) │ │ +│ ├────────────────────────────────────────┤ │ +│ │ Existing Sensors │ │ +│ │ BME280 → temp/humidity/pressure │ │ +│ │ PIR → motion ground truth │ │ +│ │ Reed switch → door/window state │ │ +│ │ ADS1115 → analog inputs │ │ +│ └────────────────────────────────────────┘ │ +│ │ +│ Outputs: │ +│ • /api/v1/store/query — kNN search │ +│ • /api/v1/boundary — fragility score │ +│ • /api/v1/coherence/profile — phases │ +│ • /api/v1/cognitive/snapshot — graph │ +│ • /api/v1/custody/attestation — signed │ +│ • MCP proxy — 114 tools for AI agents │ +└────────────────────────────────────────────┘ +``` + +### Stage 1: ESP32 Feature Vector Extraction + +The ESP32 edge processing pipeline (Core 1) already computes all signals needed. We add a compact 8-dimensional feature vector extracted from the existing DSP outputs: + +| Dimension | Feature | Source | Range | +|-----------|---------|--------|-------| +| 0 | Presence score | `s_presence_score / 10.0` (clamped) | 0.0–1.0 | +| 1 | Motion energy | `s_motion_energy / 10.0` (clamped) | 0.0–1.0 | +| 2 | Breathing rate | `s_breathing_bpm / 30.0` (clamped) | 0.0–1.0 | +| 3 | Heart rate | `s_heartrate_bpm / 120.0` (clamped) | 0.0–1.0 | +| 4 | Phase variance (mean) | Top-K subcarrier Welford variance mean | 0.0–1.0 | +| 5 | Person count | `n_active_persons / 4.0` (clamped) | 0.0–1.0 | +| 6 | Fall detected | Binary: 1.0 if `s_fall_detected`, else 0.0 | 0.0 or 1.0 | +| 7 | RSSI (normalized) | `(s_latest_rssi + 100) / 100` (clamped) | 0.0–1.0 | + +This maps directly to the Seed's store dimension of 8, enabling kNN queries like "find the 10 most similar sensing states to the current one." + +**Packet format** (magic `0xC5110003`, defined as `edge_feature_pkt_t` in `edge_processing.h`): + +```c +typedef struct __attribute__((packed)) { + uint32_t magic; // EDGE_FEATURE_MAGIC = 0xC5110003 + uint8_t node_id; // ESP32 node identifier + uint8_t reserved; // alignment padding + uint16_t seq; // sequence number + int64_t timestamp_us; // microseconds since boot + float features[8]; // 8-dim normalized feature vector (32 bytes) +} edge_feature_pkt_t; // Total: 48 bytes (static_assert enforced) +``` + +**Transmission rate:** 1 Hz (one feature vector per second, aggregated from 100 Hz CSI). This keeps UDP bandwidth under 50 bytes/s per node and avoids overwhelming the Seed's vector store. + +### Stage 2: Seed-Side RVF Ingest + +A lightweight Rust service on the Seed (or a Python bridge script) listens for feature packets on UDP port 5006 and ingests them via the Seed's REST API: + +```bash +# Ingest a feature vector with metadata +curl -sk -X POST https://169.254.42.1:8443/api/v1/store/ingest \ + -H "Authorization: Bearer $TOKEN" \ + -H "Content-Type: application/json" \ + -d '{ + "vectors": [[0, [0.85, 0.3, 0.52, 0.65, 0.4, 0.78, 0.1, -0.45]]], + "metadata": { + "node_id": 1, + "type": "csi_feature", + "timestamp": 1775166970 + } + }' +``` + +**Batching:** Accumulate 10 vectors (10 seconds) per ingest call to reduce HTTP overhead (`--batch-size 10` default in `seed_csi_bridge.py`; also supports time-based flushing via `--flush-interval`). At 1 vector/second per node, a 4-node mesh generates 14,400 vectors/hour (345,600/day). Daily compaction is required to stay within the Seed's 100K vector working set (see Storage Budget). + +**Witness chain:** Each ingest automatically appends a witness entry, providing a tamper-evident record of all sensing data. The epoch increments monotonically, and the SHA-256 chain can be verified at any time via `POST /api/v1/witness/verify`. + +### Stage 3: Cognitive Analysis & Sensor Fusion + +Once CSI feature vectors are in the RVF store, the Seed's existing subsystems activate: + +1. **kNN Graph** — Rebuilt every 10 seconds. Similar sensing states cluster together. Anomalous states (intruder, fall, unusual breathing) appear as outliers. + +2. **Boundary Analysis** — Stoer-Wagner min-cut computes a fragility score (0.0–1.0). High fragility indicates the vector space is splitting — a regime change in the environment (door opened, person entered/left, HVAC state change). + +3. **Temporal Coherence** — Phase boundary detection across the vector store timeline identifies when the environment transitions between states (occupied → empty, day → night, normal → abnormal). + +4. **Reflex Rules** — Three pre-configured rules fire automatically: + - `fragility_alarm` (threshold 0.3) → relay actuator for presence alert + - `drift_cutoff` (threshold 1.0) → cutoff when sensor drift detected + - `hd_anomaly_indicator` (threshold 200) → PWM brightness for anomaly severity + +5. **Sensor Fusion** — The Seed's BME280 (temperature/humidity/pressure) and PIR sensor provide environmental ground truth that correlates with CSI features: + - PIR motion validates CSI presence detection + - Temperature changes correlate with occupancy + - Humidity changes correlate with breathing detection fidelity + +6. **MCP Integration** — AI assistants can query the full pipeline via the 114-tool MCP proxy: + ```json + {"method": "tools/call", "params": {"name": "seed.memory.query", "arguments": {"vector": [0.8, 0.5, 0.4, 0.6, 0.3, 0.7, 0.1, -0.3], "k": 5}}} + ``` + +### ESP32 Provisioning + +The ESP32's existing NVS provisioning system supports configuring the Seed as the target: + +```bash +python firmware/esp32-csi-node/provision.py \ + --port COM9 \ + --target-ip 192.168.1.20 \ + --target-port 5006 \ + --node-id 1 +``` + +Note: `--target-ip` is the host laptop (192.168.1.20), not the Seed IP, because the bridge runs on the host and forwards to the Seed via HTTPS (see Known Issue 4). + +No firmware recompilation needed — the `stream_sender` module reads target IP/port from NVS at boot. + +### Data Flow Rates + +| Path | Rate | Size | Bandwidth | +|------|------|------|-----------| +| CSI capture → ring buffer | 100 Hz | ~400 B | 40 KB/s (internal) | +| Edge DSP → sensing-server | 100 Hz | ~200 B | 20 KB/s (existing) | +| Edge DSP → Seed features | 1 Hz | 48 B | 48 B/s (new) | +| Seed ingest (batched) | 0.1 Hz | ~500 B | 50 B/s (HTTP) | +| Seed kNN graph rebuild | 0.1 Hz | internal | — | +| Seed witness chain | per batch | 32 B hash | — | + +### Storage Budget + +| Timeframe | Vectors/node | 4 nodes | RVF size | RAM | +|-----------|-------------|---------|----------|-----| +| 1 hour | 3,600 | 14,400 | ~580 KB | ~6 MB | +| 24 hours | 86,400 | 345,600 | ~14 MB | ~140 MB | +| 7 days | 604,800 | 2,419,200 | ~97 MB | exceeds | + +**Compaction policy:** Run `POST /api/v1/store/compact` daily at 03:00, retaining only the last 24 hours of vectors. Archive older vectors to USB drive via `POST /api/v1/store/export` before compaction. + +**Dimension reduction:** For deployments exceeding 100K vectors, reduce feature extraction rate to 0.1 Hz (one vector per 10 seconds) or increase compaction frequency. + +## Validation Results + +**Live hardware test performed 2026-04-02.** + +### Hardware Under Test + +| Component | Port | IP | Firmware | WiFi | RSSI | +|-----------|------|----|----------|------|------| +| ESP32-S3 (8MB) | COM9 | 192.168.1.105 | v0.5.2 | ruv.net (ch 5) | -34 dBm | +| Cognitum Seed | USB | 169.254.42.1 / 192.168.1.109 | v0.8.1 | ruv.net | — | +| Host laptop | — | 192.168.1.20 | — | ruv.net | — | + +Seed device_id: `ecaf97dd-fc90-4b0e-b0e7-e9f896b9fbb6`. Pairing token issued to `wifi-densepose-claude`. + +### Pipeline Validated + +1. **UDP streaming** -- 211 packets captured in 15 seconds: + - 196 raw CSI frames (magic `0xC5110001`) + - 15 vitals frames (magic `0xC5110002`) + +2. **Bridge pipeline** -- 20 vitals packets (`0xC5110002`) parsed, converted to 8-dim feature vectors via the bridge's `parse_vitals_packet()` fallback path, ingested in 4 batches of 5 vectors each (`--batch-size 5`). The native `0xC5110003` feature packet path is implemented in firmware but was not exercised in this validation run (firmware was v0.5.2; the `send_feature_vector()` addition requires a reflash). + +3. **RVF ingest** -- All 20 vectors accepted by Seed. Epochs advanced 88 to 91. Witness chain verified valid (193 entries, SHA-256 chain intact). + +4. **Seed sensors** -- BME280, PIR, reed switch, ADS1115, vibration sensor all present and healthy. + +### Live Vital Signs Captured + +| Metric | Observed Range | Expected | Notes | +|--------|---------------|----------|-------| +| Presence score | 1.41 -- 14.92 | 0.0 -- 1.0 | **Needs normalization** (see Known Issues) | +| Motion energy | 1.41 -- 14.92 | 0.0 -- 1.0 | Same raw value as presence score | +| Breathing rate | 19.8 -- 33.5 BPM | 12 -- 25 BPM | Plausible but slightly high | +| Heart rate | 75.3 -- 99.1 BPM | 60 -- 100 BPM | Plausible range | +| RSSI | -43 to -72 dBm | -30 to -80 dBm | Normal | +| Fall detected | No | — | Correct (no falls occurred) | +| n_persons | 4 | 1 | **Miscalibrated** (see Known Issues) | + +### Known Issues Found + +1. **`presence_score` exceeds 1.0 in vitals packets** -- Raw values range 1.41 to 14.92 in the vitals packet (`0xC5110002`). The bridge's vitals-to-feature conversion clamps to 1.0 for dim 0 and divides by 10.0 for dim 1 (`motion_energy / 10.0`), but dim 0 clamps without scaling. **Note:** The firmware's native feature vector (`0xC5110003`) already normalizes correctly by dividing `s_presence_score` by 10.0 (see `edge_processing.c` line 657). This issue only affects the vitals-packet fallback path in the bridge. + +2. **`n_persons = 4` with 1 person present** -- The multi-person counting algorithm is miscalibrated for single-occupancy scenarios. The per-node state pipeline (ADR-068) may mitigate this when the baseline is properly trained, but the raw edge count is unreliable. + +3. **Content-addressed vector IDs cause deduplication** -- Similar feature vectors hash to the same ID, causing the Seed to silently drop duplicates. **Fixed in bridge:** `seed_csi_bridge.py` now uses `_make_vector_id()` which generates a SHA-256 hash of `node_id:timestamp_us:seq_counter`, producing unique 32-bit IDs. This was observed during validation and fixed before the final test run. + +4. **Bridge runs on host, not Seed** -- The ESP32 target IP must be the host laptop (192.168.1.20), not the Seed IP. The bridge script on the host forwards to the Seed via HTTPS. This adds a hop but avoids running a UDP listener on the Pi Zero 2 W. + +5. **PIR GPIO read returned 404** -- `GET /api/v1/sensor/gpio/read?pin=6` returned 404. The PIR endpoint may require a different pin number or endpoint format. Ground-truth validation against PIR is deferred to Phase 3. + +## Implementation Plan + +### Phase 1: ESP32 Feature Extraction (firmware change) -- DONE + +Implemented as `send_feature_vector()` in `edge_processing.c` (lines 644-699) and `edge_feature_pkt_t` in `edge_processing.h` (lines 112-124). The function reads from static globals (`s_presence_score`, `s_motion_energy`, `s_breathing_bpm`, `s_heartrate_bpm`, subcarrier Welford variance, person tracker, fall flag, RSSI) and normalizes each dimension to 0.0-1.0 with clamping. + +Called at the same 1 Hz cadence as `send_vitals_packet()` in Step 13 of the edge processing pipeline (line 855). The compressed frame magic was reassigned from `0xC5110003` to `0xC5110005` to free up `0xC5110003` for feature vectors (`EDGE_COMPRESSED_MAGIC` in `edge_processing.h` line 29). + +### Phase 2: Seed Ingest Bridge (Python script on host) -- DONE + +Implemented as `scripts/seed_csi_bridge.py`. The bridge: +1. Listens on UDP port 5006 (configurable via `--udp-port`) +2. Accepts all three packet formats: `0xC5110003` (ADR-069 features), `0xC5110002` (vitals, converted to 8-dim), and `0xC5110001` (raw CSI, minimal features) +3. Generates unique vector IDs via SHA-256 hash of `node_id:timestamp:seq` (avoids content-addressed deduplication -- see Known Issue 3) +4. Batches vectors (default 10, configurable via `--batch-size`) with time-based flush fallback (`--flush-interval`) +5. POSTs to Seed's `/api/v1/store/ingest` with bearer token +6. Supports `--validate` mode (kNN query + PIR comparison after each batch) +7. Supports `--stats` mode (print Seed status, boundary, coherence, graph) +8. Supports `--compact` mode (trigger store compaction) + +### Phase 3: Validation & Ground Truth -- BLOCKED + +Use the Seed's PIR sensor as ground truth for presence detection: +1. Query PIR state: `GET /api/v1/sensor/gpio/read?pin=6` +2. Compare with CSI presence score (feature dim 0) +3. Log agreement/disagreement rate +4. Use kNN to find historical vectors matching current PIR state → validate CSI accuracy + +**Status:** The bridge implements `--validate` mode with PIR comparison (see `_run_validation()` in `seed_csi_bridge.py`). However, the PIR endpoint returned 404 during validation (Known Issue 5). This phase is blocked until the correct PIR API endpoint is identified. + +### Phase 4: Multi-Node Mesh (addresses #348) + +Deploy 3 ESP32 nodes, each sending feature vectors to the bridge host (which forwards to the Seed): +- Node 1 (lobby): `--node-id 1 --target-ip 192.168.1.20 --target-port 5006` +- Node 2 (hallway): `--node-id 2 --target-ip 192.168.1.20 --target-port 5006` +- Node 3 (room): `--node-id 3 --target-ip 192.168.1.20 --target-port 5006` + +All nodes target the host laptop (192.168.1.20) where the bridge script runs. The bridge batches and forwards all nodes' vectors to the Seed via HTTPS. The Seed's kNN graph naturally clusters vectors by node and by sensing state. Cross-node analysis via boundary fragility detects when a person moves between zones. + +## Security Considerations + +1. **Bearer token** — All write operations require the pairing token. Token stored as SHA-256 hash on device. +2. **TLS** — All API calls over HTTPS (port 8443) with device-provisioned CA certificate. +3. **Witness chain** — Every ingest is cryptographically chained. Tampering detection via `POST /api/v1/witness/verify`. +4. **Ed25519 attestation** — Device identity bound to hardware keypair. Attestation includes epoch, vector count, and witness head. +5. **Anti-spoofing** — Sensor pipeline has entropy-based spoofing detection (min 0.5 bits entropy, streak threshold 3). +6. **USB-only pairing** — Pairing window can only be opened from USB interface (169.254.42.1), not from WiFi. + +## Hardware Bill of Materials + +| Component | Port | IP | Cost | +|-----------|------|----|------| +| ESP32-S3 (8MB) | COM9 | 192.168.1.105 (DHCP) | ~$9 | +| Cognitum Seed (Pi Zero 2W) | USB | 169.254.42.1 / 192.168.1.109 | ~$15 | +| USB-C cable (data) | — | — | ~$3 | +| **Total** | | | **~$27** | + +### Seed Sensors (included) + +| Sensor | Interface | Channels | Purpose | +|--------|-----------|----------|---------| +| Reed switch | GPIO 5 | 1 | Door/window state | +| PIR motion | GPIO 6 | 1 | Motion ground truth | +| Vibration | GPIO 13 | 1 | Structural vibration | +| ADS1115 | I2C 0x48 | 4 | Analog inputs (extensible) | +| BME280 | I2C 0x76 | 3 | Temperature, humidity, pressure | + +## Risks + +| Risk | Likelihood | Impact | Mitigation | +|------|-----------|--------|------------| +| Pi Zero thermal throttling at sustained ingest | Medium | Performance degrades | Thermal governor already manages DVFS; 1 Hz ingest is minimal load | +| WiFi congestion with ESP32 CSI + UDP | Low | Lost packets | Feature vectors are 48 bytes at 1 Hz; negligible vs CSI traffic | +| RVF store exceeds RAM at high vector count | Medium | OOM | Compaction policy + dimension reduction + daily export | +| Bearer token exposure | Low | Unauthorized writes | TLS encryption + USB-only pairing + token hashing | +| ESP32 NVS corruption | Low | Config lost | NVS is wear-leveled flash with CRC; re-provision via USB | + +## Consequences + +### Positive +- ESP32 CSI features become persistent, searchable, and cryptographically attested +- kNN similarity search enables environment fingerprinting and anomaly detection +- PIR + BME280 provide ground truth for CSI validation +- MCP proxy enables AI assistants to query sensing state directly +- Witness chain provides audit trail for healthcare/safety applications +- Architecture aligns with Arena Physica's insight: store embeddings, not raw signals + +### Negative +- Additional firmware packet type (48 bytes, trivial) +- Bridge script needed on Seed or host machine +- Daily compaction required for long-running deployments +- Bearer token must be managed (stored securely, rotated if compromised) + +### Neutral +- Existing sensing-server pipeline unchanged (ESP32 still sends to port 5005) +- Seed's existing sensors continue operating independently +- Target IP/port configurable via NVS provisioning (no recompilation for deployment changes) +- Firmware recompilation needed once to add `send_feature_vector()` (Phase 1), but subsequent node deployments only need provisioning diff --git a/docs/ddd/hardware-platform-domain-model.md b/docs/ddd/hardware-platform-domain-model.md index c667161e..def793a9 100644 --- a/docs/ddd/hardware-platform-domain-model.md +++ b/docs/ddd/hardware-platform-domain-model.md @@ -31,7 +31,7 @@ All firmware paths are relative to the repository root. Rust crate paths are rel | **Core 0 / Core 1** | The two Xtensa LX7 cores on ESP32-S3; Core 0 runs WiFi + CSI callback, Core 1 runs the DSP pipeline | | **SPSC Ring Buffer** | Single-producer single-consumer lock-free queue between Core 0 (CSI callback) and Core 1 (DSP task) | | **Vitals Packet** | 32-byte UDP packet (magic `0xC5110002`) containing presence, breathing BPM, heart rate BPM, fall flag | -| **Compressed Frame** | Delta-compressed CSI frame (magic `0xC5110003`) using XOR + RLE for 30-50% bandwidth reduction | +| **Compressed Frame** | Delta-compressed CSI frame (magic `0xC5110005`, reassigned from `0xC5110003` by ADR-069) using XOR + RLE for 30-50% bandwidth reduction | | **WASM Module** | A `no_std` Rust program compiled to `wasm32-unknown-unknown`, executed on-device via WASM3 interpreter | | **Module Slot** | One of 4 pre-allocated PSRAM arenas (160 KB each) that host a WASM module instance | | **Host API** | 12 functions in the `csi` namespace that WASM modules call to read sensor data and emit events | @@ -158,7 +158,7 @@ All firmware paths are relative to the repository root. Rust crate paths are rel | +------------------+--------+ | | | Multi-Person Clustering | | | | (subcarrier groups, <=4) |----> VitalsPacket (0xC5110002) | -| +---------------------------+----> CompressedFrame (0xC5110003)| +| +---------------------------+----> CompressedFrame (0xC5110005)| | | +--------------------------------------------------------------+ ``` @@ -1197,7 +1197,7 @@ pub trait ProvisioningService { | Sensor Node | Edge Processing | **Partnership** | Tightly coupled via SPSC ring buffer on the same chip | | Edge Processing | WASM Runtime | **Customer/Supplier** | Edge pipeline feeds CSI data to WASM modules via Host API | | Sensor Node | Aggregation | **Published Language** | ADR-018 binary wire format (magic bytes, fixed offsets) | -| Edge Processing | Aggregation | **Published Language** | Vitals (0xC5110002) and compressed (0xC5110003) wire formats | +| Edge Processing | Aggregation | **Published Language** | Vitals (0xC5110002), compressed (0xC5110005), and feature vectors (0xC5110003) wire formats | | WASM Runtime | Aggregation | **Published Language** | WASM events (0xC5110004) wire format | | Aggregation | Downstream crates | **Customer/Supplier** | Aggregator produces `FusedFrame` consumed by signal/nn/mat | @@ -1223,7 +1223,8 @@ impl Esp32ToPipelineAdapter { /// Handles magic byte demuxing: /// 0xC5110001 -> raw CSI frame /// 0xC5110002 -> vitals packet - /// 0xC5110003 -> compressed frame (decompress first) + /// 0xC5110003 -> feature vector (ADR-069, 48-byte 8-dim) + /// 0xC5110005 -> compressed frame (decompress first) /// 0xC5110004 -> WASM event packet pub fn parse_datagram( &self, @@ -1306,8 +1307,9 @@ All ESP32 UDP packets share a 4-byte magic prefix for demuxing at the aggregator |-------|------|--------|------|------|-------------| | `0xC5110001` | Raw CSI | Tier 0+ | ~128-404 B | 20-28.5 Hz | Full I/Q per subcarrier | | `0xC5110002` | Vitals | Tier 2+ | 32 B | 1 Hz (configurable) | Presence, BPM, fall flag | -| `0xC5110003` | Compressed | Tier 1+ | variable | 20-28.5 Hz | XOR+RLE delta-compressed CSI | +| `0xC5110003` | Feature Vector | Tier 2+ | 48 B | 1 Hz | ADR-069 8-dim normalized features for Cognitum Seed RVF ingest | | `0xC5110004` | WASM Events | Tier 3 | variable | event-driven | Module event_type + value tuples | +| `0xC5110005` | Compressed | Tier 1+ | variable | 20-28.5 Hz | XOR+RLE delta-compressed CSI (reassigned from 0xC5110003) | --- diff --git a/docs/research/architecture/implementation-plan.md b/docs/research/architecture/implementation-plan.md new file mode 100644 index 00000000..3033f8bb --- /dev/null +++ b/docs/research/architecture/implementation-plan.md @@ -0,0 +1,996 @@ +# GOAP Implementation Plan: ESP32-S3 + Pi Zero 2 W WiFi Pose Estimation + +**Date:** 2026-04-02 +**Version:** 1.0 +**Status:** Proposed +**Depends on:** ADR-029, ADR-068, SOTA survey (sota-wifi-sensing-2025.md) + +--- + +## 1. Goal State Definition + +### 1.1 Terminal Goal + +A production-ready WiFi-based human pose estimation system where: +- **ESP32-S3** nodes capture WiFi CSI at 100 Hz, perform temporal feature extraction, and transmit compressed features via UDP +- **Raspberry Pi Zero 2 W** receives features from 1-4 ESP32 nodes, runs neural inference, and outputs 17-keypoint COCO poses at >= 10 Hz +- **Single-person MPJPE** < 100mm in trained environments +- **End-to-end latency** < 150ms (CSI capture to pose output) +- **Total BOM cost** < $30 per sensing zone (1x Pi Zero + 2x ESP32) + +### 1.2 World State Variables + +``` +current_state: + esp32_csi_capture: true # Already implemented + multi_node_aggregation: true # ADR-018 UDP aggregator + phase_alignment: true # ruvsense/phase_align.rs + coherence_gating: true # ruvsense/coherence_gate.rs + multistatic_fusion: true # ruvsense/multistatic.rs + kalman_pose_tracking: true # ruvsense/pose_tracker.rs + onnx_inference_engine: true # wifi-densepose-nn + modality_translator: true # wifi-densepose-nn/translator.rs + training_pipeline: true # wifi-densepose-train + pi_zero_deployment: false # No Pi Zero target + lightweight_model: false # No edge-optimized model + temporal_conv_module: false # No TCN in inference path + csi_compression: false # No ESP32-side compression + int8_quantization: false # No quantization pipeline + bone_constraint_loss: false # No skeleton physics in loss + esp32_pi_protocol: false # No lightweight protocol + edge_inference_engine: false # No ARM-optimized inference + cross_env_adaptation: false # No domain adaptation + multi_person_paf: false # No PAF-based multi-person + 3d_pose_lifting: false # No Z-axis estimation + +goal_state: + esp32_csi_capture: true + multi_node_aggregation: true + phase_alignment: true + coherence_gating: true + multistatic_fusion: true + kalman_pose_tracking: true + onnx_inference_engine: true + modality_translator: true + training_pipeline: true + pi_zero_deployment: true # TARGET + lightweight_model: true # TARGET + temporal_conv_module: true # TARGET + csi_compression: true # TARGET + int8_quantization: true # TARGET + bone_constraint_loss: true # TARGET + esp32_pi_protocol: true # TARGET + edge_inference_engine: true # TARGET + cross_env_adaptation: true # TARGET (Phase 2) + multi_person_paf: true # TARGET (Phase 2) + 3d_pose_lifting: true # TARGET (Phase 3) +``` + +## 2. Action Definitions + +Each action has preconditions, effects, estimated cost (developer-days), and priority. + +### Action 1: Define ESP32-Pi Communication Protocol (ADR-069) + +``` +name: define_esp32_pi_protocol +cost: 3 days +priority: CRITICAL (blocks all Pi Zero work) +preconditions: [esp32_csi_capture] +effects: [esp32_pi_protocol := true] +``` + +**Description:** Design a lightweight binary protocol for ESP32 -> Pi Zero communication over UDP (WiFi) or UART (wired fallback). + +**Protocol specification:** + +``` +Frame Header (8 bytes): + [0:1] magic: 0xCF01 (CSI Frame v1) + [2] node_id: u8 (0-255, identifies ESP32 node) + [3] frame_type: u8 (0=raw_csi, 1=compressed_features, 2=heartbeat) + [4:5] sequence: u16 (monotonic frame counter, wraps at 65535) + [6:7] payload_len: u16 (bytes following header) + +Raw CSI Payload (frame_type=0): + [0:3] timestamp_us: u32 (microseconds since boot, wraps at ~71 minutes) + [4] channel: u8 (WiFi channel 1-13) + [5] bandwidth: u8 (0=20MHz, 1=40MHz) + [6] rssi: i8 (dBm) + [7] noise_floor: i8 (dBm) + [8:9] num_sc: u16 (number of subcarriers, typically 52 or 114) + [10..] csi_data: [i16; num_sc * 2] (interleaved I/Q, little-endian) + +Compressed Feature Payload (frame_type=1): + [0:3] timestamp_us: u32 + [4] compression: u8 (0=none, 1=pca_16, 2=pca_32, 3=autoencoder) + [5] num_features: u8 (number of feature dimensions) + [6..] features: [f16; num_features] (half-precision floats) + +Heartbeat Payload (frame_type=2): + [0:3] uptime_s: u32 + [4:7] frames_sent: u32 + [8:9] free_heap: u16 (KB) + [10] wifi_rssi: i8 (connection to AP) + [11] battery_pct: u8 (0-100, 0xFF if wired) +``` + +**Implementation locations:** +- ESP32 firmware: `firmware/esp32-csi-node/main/protocol_v2.h` +- Rust parser: `wifi-densepose-hardware/src/protocol_v2.rs` + +**Design rationale:** +- Fixed 8-byte header with magic number for frame synchronization +- Half-precision (f16) for compressed features saves 50% bandwidth vs f32 +- Heartbeat enables Pi Zero to detect node failures and rebalance +- Raw CSI mode for debugging; compressed mode for production + +### Action 2: Implement Lightweight Model Architecture + +``` +name: implement_lightweight_model +cost: 10 days +priority: CRITICAL (core inference capability) +preconditions: [training_pipeline, onnx_inference_engine] +effects: [lightweight_model := true, temporal_conv_module := true] +``` + +**Architecture: WiFlowPose (hybrid WiFlow + MultiFormer)** + +Based on SOTA analysis, we define a custom architecture combining the best elements: + +``` +Input: CSI amplitude tensor [B, T, S] + B = batch size + T = temporal window (20 frames at 20 Hz = 1 second context) + S = subcarriers (52 for ESP32-S3 20MHz, 114 for 40MHz) + +Stage 1: Temporal Encoder (runs on ESP32 optionally, or Pi Zero) + TCN with 4 layers, dilation [1, 2, 4, 8] + Input: [B, T, S] = [B, 20, 52] + Output: [B, T', C_t] = [B, 20, 64] (temporal features) + +Stage 2: Spatial Encoder (runs on Pi Zero) + Asymmetric convolution blocks (1xk kernels on subcarrier dimension) + 4 residual blocks: 64 -> 128 -> 128 -> 64 channels + Subcarrier compression: 52 -> 26 -> 13 -> 7 + Output: [B, 64, 7] + +Stage 3: Keypoint Decoder (runs on Pi Zero) + Axial self-attention (2-stage, 4 heads) + Reshape to [B, 17, 64] (17 keypoints x 64 features) + Linear projection: 64 -> 2 (x, y coordinates) + Output: [B, 17, 2] (17 COCO keypoints, normalized 0-1) + +Optional Stage 4: Multi-person (Phase 2) + PAF branch: predict 19 limb affinity fields + Hungarian assignment for person grouping +``` + +**Estimated model size:** +- Temporal encoder: ~0.5M params +- Spatial encoder: ~1.2M params +- Keypoint decoder: ~0.8M params +- Total: ~2.5M params +- INT8 size: ~2.5 MB +- FP16 size: ~5 MB +- Estimated Pi Zero 2 W inference: 30-60ms per frame + +**Rust implementation location:** New module in `wifi-densepose-nn/src/wiflow_pose.rs` + +```rust +/// WiFlowPose: Lightweight WiFi CSI to pose estimation model +/// +/// Hybrid architecture combining WiFlow's TCN temporal encoder +/// with MultiFormer's dual-token spatial processing and +/// axial self-attention for keypoint decoding. +pub struct WiFlowPoseConfig { + /// Number of input subcarriers (52 for ESP32 20MHz, 114 for 40MHz) + pub num_subcarriers: usize, + /// Temporal window size in frames (default: 20) + pub temporal_window: usize, + /// TCN dilation factors (default: [1, 2, 4, 8]) + pub tcn_dilations: Vec, + /// Number of output keypoints (default: 17, COCO format) + pub num_keypoints: usize, + /// Hidden dimension for spatial encoder (default: 64) + pub hidden_dim: usize, + /// Number of attention heads in axial attention (default: 4) + pub num_attention_heads: usize, + /// Enable multi-person PAF branch (default: false) + pub multi_person: bool, +} + +impl Default for WiFlowPoseConfig { + fn default() -> Self { + Self { + num_subcarriers: 52, + temporal_window: 20, + tcn_dilations: vec![1, 2, 4, 8], + num_keypoints: 17, + hidden_dim: 64, + num_attention_heads: 4, + multi_person: false, + } + } +} +``` + +### Action 3: Implement Bone Constraint Loss + +``` +name: implement_bone_constraint_loss +cost: 2 days +priority: HIGH +preconditions: [training_pipeline, lightweight_model] +effects: [bone_constraint_loss := true] +``` + +**Loss function following WiFlow:** + +``` +L_total = L_keypoint + lambda_bone * L_bone + lambda_physics * L_physics + +L_keypoint = SmoothL1(pred, gt, beta=0.1) + +L_bone = (1/|B|) * sum_{(i,j) in bones} | ||pred_i - pred_j|| - bone_length_{ij} | + +L_physics = (1/N) * sum_t max(0, ||pred_t - pred_{t-1}|| - v_max * dt) +``` + +Where: +- `bones` = 14 COCO bone connections (e.g., left_shoulder-left_elbow) +- `bone_length_{ij}` = average human bone length ratios (normalized to torso length) +- `v_max` = maximum physiologically plausible keypoint velocity (2 m/s for walking, 10 m/s for fast gestures) +- `lambda_bone = 0.2`, `lambda_physics = 0.1` + +**Bone length ratios (normalized to torso = shoulder_center to hip_center = 1.0):** + +| Bone | Ratio | +|------|-------| +| shoulder-elbow | 0.55 | +| elbow-wrist | 0.50 | +| hip-knee | 0.85 | +| knee-ankle | 0.80 | +| shoulder-hip | 1.00 | +| neck-nose | 0.30 | +| nose-eye | 0.08 | +| eye-ear | 0.12 | + +**Implementation location:** `wifi-densepose-train/src/losses.rs` (add `BoneConstraintLoss`) + +### Action 4: Implement INT8 Quantization Pipeline + +``` +name: implement_int8_quantization +cost: 5 days +priority: HIGH +preconditions: [lightweight_model, training_pipeline] +effects: [int8_quantization := true] +``` + +**Approach: Post-Training Quantization (PTQ) with calibration** + +1. Train model in FP32 using standard pipeline +2. Export to ONNX format +3. Run ONNX Runtime quantization tool with calibration dataset: + - Collect 1000 representative CSI frames across multiple environments + - Run calibration to determine per-layer quantization ranges + - Apply symmetric INT8 quantization for weights, asymmetric for activations +4. Validate quantized model accuracy (target: <2% PCK@20 degradation) + +**Quantization-aware considerations:** +- TCN layers: quantize per-channel (dilated convolutions are sensitive to quantization) +- Attention layers: keep attention logits in FP16 (softmax is numerically sensitive) +- Output layer: keep in FP32 (final coordinate regression needs precision) + +**Rust implementation:** +```rust +// In wifi-densepose-nn/src/quantize.rs +pub struct QuantizationConfig { + /// Quantization method + pub method: QuantMethod, // PTQ, QAT, Dynamic + /// Per-layer precision overrides + pub layer_overrides: HashMap, + /// Calibration dataset path + pub calibration_data: PathBuf, + /// Number of calibration samples + pub num_calibration_samples: usize, + /// Target accuracy degradation threshold + pub max_accuracy_loss: f32, +} + +pub enum Precision { + INT8, + FP16, + FP32, +} +``` + +**ONNX quantization command (for build pipeline):** +```bash +python -m onnxruntime.quantization.quantize \ + --input model_fp32.onnx \ + --output model_int8.onnx \ + --calibrate \ + --calibration_data_reader CsiCalibrationReader \ + --quant_format QDQ \ + --activation_type QUInt8 \ + --weight_type QInt8 +``` + +### Action 5: Build Edge Inference Engine for Pi Zero + +``` +name: build_edge_inference_engine +cost: 8 days +priority: CRITICAL +preconditions: [lightweight_model, int8_quantization, esp32_pi_protocol] +effects: [edge_inference_engine := true, pi_zero_deployment := true] +``` + +**Architecture: Streaming inference with ring buffer** + +``` + UDP/UART +ESP32-S3 ---------> Pi Zero 2 W + | + v + +-- RingBuffer --+ + | (capacity: 64 frames) | + +------ | | -------------+ + v v + +-- TemporalWindow --------+ + | (20 frames, sliding) | + +------ | ----------------+ + v + +-- WiFlowPose ONNX ------+ + | (INT8, XNNPACK accel) | + +------ | ----------------+ + v + +-- PoseTracker -----------+ + | (Kalman + skeleton) | + +------ | ----------------+ + v + PoseEstimate output + (17 keypoints + confidence) +``` + +**New Rust binary:** `wifi-densepose-cli/src/bin/edge_infer.rs` + +```rust +/// Edge inference daemon for Raspberry Pi Zero 2 W +/// +/// Receives CSI frames from ESP32 nodes via UDP, maintains a temporal +/// sliding window, runs INT8 ONNX inference, and outputs pose estimates. +/// +/// Usage: +/// wifi-densepose edge-infer \ +/// --model model_int8.onnx \ +/// --listen 0.0.0.0:5555 \ +/// --output-port 5556 \ +/// --window-size 20 \ +/// --max-nodes 4 + +struct EdgeInferConfig { + /// Path to INT8 ONNX model + model_path: PathBuf, + /// UDP listen address for CSI frames + listen_addr: SocketAddr, + /// UDP output address for pose results + output_addr: Option, + /// Temporal window size + window_size: usize, + /// Maximum ESP32 nodes to accept + max_nodes: usize, + /// Inference thread count (1-4 on Pi Zero 2 W) + num_threads: usize, + /// Enable XNNPACK acceleration + use_xnnpack: bool, +} +``` + +**Cross-compilation for Pi Zero 2 W:** + +```bash +# Install cross-compilation toolchain +rustup target add aarch64-unknown-linux-gnu +sudo apt install gcc-aarch64-linux-gnu + +# Build for Pi Zero 2 W (64-bit Raspberry Pi OS) +cross build --target aarch64-unknown-linux-gnu \ + --release \ + -p wifi-densepose-cli \ + --features edge-inference \ + --no-default-features + +# Or for 32-bit Raspberry Pi OS: +# rustup target add armv7-unknown-linux-gnueabihf +# cross build --target armv7-unknown-linux-gnueabihf ... +``` + +**ONNX Runtime linking for ARM:** +- Use `ort` crate with `download-binaries` feature for automatic aarch64 binary download +- Alternative: build OnnxStream from source for minimal binary size (~2 MB vs ~30 MB for full ONNX Runtime) + +### Action 6: Implement CSI Compression on ESP32 + +``` +name: implement_csi_compression +cost: 5 days +priority: MEDIUM +preconditions: [esp32_csi_capture, esp32_pi_protocol] +effects: [csi_compression := true] +``` + +**Three compression tiers:** + +**Tier 0: No compression (raw CSI)** +- Payload: 52 subcarriers x 2 (I/Q) x 2 bytes = 208 bytes per frame +- Use case: debugging, maximum fidelity + +**Tier 1: PCA-16 (run on ESP32)** +- Pre-computed PCA projection matrix (52 -> 16 dimensions) +- Stored in NVS flash during provisioning +- Payload: 16 features x 2 bytes (f16) = 32 bytes per frame +- Compression: 6.5x +- Compute: ~0.1ms on ESP32-S3 (matrix-vector multiply, SIMD) + +**Tier 2: PCA-32 (higher fidelity)** +- 52 -> 32 dimensions +- Payload: 32 x 2 = 64 bytes +- Compression: 3.25x + +**Tier 3: Learned autoencoder (future)** +- ESP32-S3 has enough compute for a small encoder (~10K params) +- Requires quantized encoder weights in flash +- Most bandwidth-efficient but requires training + +**PCA computation (offline, during provisioning):** + +```rust +// wifi-densepose-train/src/compression.rs + +/// Compute PCA projection matrix from calibration CSI data +pub fn compute_pca_projection( + calibration_data: &[CsiFrame], + target_dims: usize, +) -> PcaProjection { + // 1. Stack all CSI amplitude vectors into matrix [N, S] + // 2. Center (subtract mean) + // 3. Compute covariance matrix [S, S] + // 4. Eigendecomposition, take top `target_dims` eigenvectors + // 5. Return projection matrix [S, target_dims] and mean vector [S] + // ... +} + +pub struct PcaProjection { + /// Projection matrix [num_subcarriers, target_dims] + pub matrix: Vec, + /// Mean vector for centering [num_subcarriers] + pub mean: Vec, + /// Number of input subcarriers + pub input_dims: usize, + /// Number of output features + pub output_dims: usize, +} +``` + +**ESP32 firmware integration:** +- Store PCA matrix in NVS partition (32x52x4 = 6.5 KB for PCA-32) +- Apply projection in CSI callback before UDP transmission +- Selectable via provisioning command + +### Action 7: Implement Cross-Environment Adaptation + +``` +name: implement_cross_env_adaptation +cost: 8 days +priority: MEDIUM (Phase 2) +preconditions: [lightweight_model, training_pipeline, pi_zero_deployment] +effects: [cross_env_adaptation := true] +``` + +**Approach: Rapid environment calibration with few-shot adaptation** + +Inspired by Arena Physica's template-based design space and MERIDIAN (ADR-027): + +1. **Environment fingerprinting (on Pi Zero, at deployment time):** + - Collect 60 seconds of "empty room" CSI + - Compute room signature: mean amplitude profile, delay spread, K-factor + - Match to nearest room template (corridor, office, bedroom, etc.) + - Load template-specific model weights + +2. **Few-shot fine-tuning (optional, on workstation):** + - Collect 5 minutes of calibration data with known poses + - Fine-tune last 2 layers of the model (~50K params) + - Transfer updated model back to Pi Zero + +3. **Online adaptation (continuous, on Pi Zero):** + - Track CSI statistics over time (sliding window mean/variance) + - Detect distribution shift (KL divergence exceeds threshold) + - Apply batch normalization statistics update (no gradient computation needed) + +**Implementation location:** `wifi-densepose-train/src/rapid_adapt.rs` (extend existing module) + +### Action 8: Implement Multi-Person PAF Decoding + +``` +name: implement_multi_person_paf +cost: 6 days +priority: LOW (Phase 2) +preconditions: [lightweight_model, bone_constraint_loss] +effects: [multi_person_paf := true] +``` + +**Architecture (following MultiFormer):** + +Add a PAF branch to the WiFlowPose model: + +``` +Stage 3 features [B, 64, 7] + | + +--> Keypoint head: [B, 17, 2] (single-person keypoints) + | + +--> PAF head: [B, 38, H, W] (19 limb affinity fields) + | + +--> Confidence head: [B, 19, H, W] (part confidence maps) +``` + +**Multi-person assignment on Pi Zero:** +1. Extract candidate keypoints from confidence maps via NMS +2. Compute PAF integral scores between candidate pairs +3. Solve bipartite matching with Hungarian algorithm +4. Group keypoints into person instances + +**Estimated additional cost:** ~1M parameters, ~10ms additional inference time + +### Action 9: Implement 3D Pose Lifting + +``` +name: implement_3d_pose_lifting +cost: 5 days +priority: LOW (Phase 3) +preconditions: [lightweight_model, multi_person_paf, multistatic_fusion] +effects: [3d_pose_lifting := true] +``` + +**Approach: Multi-view triangulation + learned depth prior** + +With 2+ ESP32 nodes at known positions, compute 3D pose via: + +1. Each node pair provides a different viewing angle of the WiFi field +2. 2D pose from each viewpoint is estimated independently +3. Epipolar geometry constrains 3D position from 2D observations +4. Learned depth prior resolves ambiguities (front/back confusion) + +This leverages the existing `viewpoint/geometry.rs` module in wifi-densepose-ruvector which already computes GeometricDiversityIndex and Fisher Information for multi-node configurations. + +## 3. Hardware Architecture + +### 3.1 System Topology + +``` + WiFi AP (existing home router) + / | \ + / | \ + ESP32-S3 #1 ESP32-S3 #2 ESP32-S3 #3 + (CSI node) (CSI node) (CSI node, optional) + | | | + +------+------+------+-------+ + | UDP (WiFi) | + v v + Raspberry Pi Zero 2 W + (edge inference node) + | + v + Pose output (UDP/MQTT/WebSocket) + to display / home automation / API +``` + +### 3.2 Data Flow Timing + +``` +T=0ms ESP32 #1 captures CSI frame (channel 1) +T=2ms ESP32 #1 applies PCA compression (0.1ms compute) +T=3ms ESP32 #1 sends UDP packet to Pi Zero (64 bytes) +T=5ms ESP32 #2 captures CSI frame (channel 6, TDM slot) +T=7ms ESP32 #2 sends UDP packet to Pi Zero +T=10ms Pi Zero receives both frames, adds to ring buffer +T=10ms Pi Zero checks temporal window (20 frames accumulated?) + If yes: run inference +T=15ms Temporal encoder processes 20-frame window (5ms) +T=35ms Spatial encoder + attention (20ms) +T=45ms Keypoint decoder (10ms) +T=48ms Kalman filter update + skeleton constraints (3ms) +T=50ms Pose estimate emitted (17 keypoints + confidence) +``` + +**Total latency: ~50ms** (well under 150ms target) +**Throughput: 20 Hz** (matching TDMA cycle) + +### 3.3 Hardware Bill of Materials + +| Component | Unit Cost | Quantity | Total | +|-----------|----------|----------|-------| +| ESP32-S3 DevKit (8MB) | $9 | 2 | $18 | +| Raspberry Pi Zero 2 W | $15 | 1 | $15 | +| MicroSD card (16GB) | $5 | 1 | $5 | +| USB-C power supply | $5 | 1 | $5 | +| **Total** | | | **$43** | + +With ESP32-S3 SuperMini ($6 each), total drops to **$37**. + +For minimum viable setup (1 ESP32 + 1 Pi Zero): **$24**. + +### 3.4 Pi Zero 2 W Specifications + +| Parameter | Value | +|-----------|-------| +| SoC | BCM2710A1 (quad-core Cortex-A53 @ 1 GHz) | +| RAM | 512 MB LPDDR2 | +| WiFi | 802.11b/g/n (2.4 GHz only) | +| Bluetooth | BLE 4.2 | +| GPIO | 40-pin header (UART, SPI, I2C) | +| Power | 5V/2A USB micro-B | +| OS | Raspberry Pi OS Lite (64-bit, headless) | + +**Memory budget for inference:** + +| Component | Memory | +|-----------|--------| +| OS + services | ~100 MB | +| WiFlowPose INT8 model | ~3 MB | +| ONNX Runtime / OnnxStream | ~10-30 MB | +| Ring buffer (64 frames x 4 nodes) | ~1 MB | +| Inference workspace | ~20 MB | +| **Total** | ~134-164 MB | +| **Available** | ~348-378 MB headroom | + +Comfortable fit within 512 MB RAM. + +## 4. Rust Crate Modifications + +### 4.1 Modified Crates + +#### wifi-densepose-hardware + +**New files:** +- `src/protocol_v2.rs` -- Lightweight ESP32-Pi binary protocol parser/serializer +- `src/pi_zero.rs` -- Pi Zero UDP receiver with ring buffer management + +**Modified files:** +- `src/lib.rs` -- Add `pub mod protocol_v2; pub mod pi_zero;` +- `src/aggregator/mod.rs` -- Add support for protocol_v2 frame format + +#### wifi-densepose-nn + +**New files:** +- `src/wiflow_pose.rs` -- WiFlowPose model definition (TCN + asymmetric conv + axial attention) +- `src/edge_engine.rs` -- Edge-optimized inference engine (streaming, ARM NEON) +- `src/quantize.rs` -- INT8 quantization configuration and validation + +**Modified files:** +- `src/lib.rs` -- Add new module exports +- `src/onnx.rs` -- Add XNNPACK execution provider option, INT8 model loading +- `src/translator.rs` -- Add WiFlowPose-compatible input format + +#### wifi-densepose-train + +**New files:** +- `src/wiflow_pose_trainer.rs` -- Training loop for WiFlowPose architecture +- `src/compression.rs` -- PCA computation for ESP32 CSI compression +- `src/bone_loss.rs` -- Bone constraint and physics consistency losses + +**Modified files:** +- `src/losses.rs` -- Add `BoneConstraintLoss`, `PhysicsConsistencyLoss` +- `src/config.rs` -- Add WiFlowPose training configuration options +- `src/dataset.rs` -- Add ESP32-S3 CSI format support (52/114 subcarriers) +- `src/rapid_adapt.rs` -- Add few-shot environment calibration + +#### wifi-densepose-signal + +**New files:** +- `src/ruvsense/temporal_encoder.rs` -- TCN temporal feature extraction (shared code for ESP32 and Pi) + +**Modified files:** +- `src/ruvsense/mod.rs` -- Add `pub mod temporal_encoder;` + +#### wifi-densepose-cli + +**New files:** +- `src/bin/edge_infer.rs` -- Pi Zero edge inference daemon +- `src/bin/calibrate.rs` -- Environment calibration tool (PCA computation, room fingerprinting) + +#### wifi-densepose-core + +**Modified files:** +- `src/types.rs` -- Add `CompressedCsiFrame`, `EdgePoseEstimate` types + +### 4.2 New Feature Flags + +```toml +# wifi-densepose-nn/Cargo.toml +[features] +default = ["onnx"] +onnx = ["ort"] +edge-inference = ["onnx", "xnnpack"] # NEW: ARM NEON + XNNPACK +candle = ["candle-core", "candle-nn"] +tch-backend = ["tch"] + +# wifi-densepose-cli/Cargo.toml +[features] +default = ["full"] +full = ["wifi-densepose-nn/onnx", "wifi-densepose-train/tch-backend"] +edge-inference = ["wifi-densepose-nn/edge-inference"] # NEW: minimal binary for Pi +``` + +### 4.3 Cross-Compilation Configuration + +```toml +# .cargo/config.toml (add section) +[target.aarch64-unknown-linux-gnu] +linker = "aarch64-linux-gnu-gcc" +rustflags = ["-C", "target-cpu=cortex-a53", "-C", "target-feature=+neon"] +``` + +## 5. ESP32 Firmware Modifications + +### 5.1 New Files + +- `firmware/esp32-csi-node/main/protocol_v2.h` -- Protocol v2 frame packing +- `firmware/esp32-csi-node/main/pca_compress.h` -- PCA compression for CSI +- `firmware/esp32-csi-node/main/pca_compress.c` -- PCA implementation with ESP32 SIMD +- `firmware/esp32-csi-node/main/pi_zero_mode.c` -- Pi Zero communication mode (lighter than full server mode) + +### 5.2 Modified Files + +- `firmware/esp32-csi-node/main/csi_handler.c` -- Add compression step in CSI callback +- `firmware/esp32-csi-node/main/nvs_config.c` -- Store PCA matrix in NVS +- `firmware/esp32-csi-node/main/Kconfig.projbuild` -- Add CONFIG_PI_ZERO_MODE, CONFIG_CSI_COMPRESSION options + +### 5.3 Provisioning Updates + +```bash +# Provision for Pi Zero mode with PCA-16 compression +python firmware/esp32-csi-node/provision.py \ + --port COM7 \ + --ssid "MyWiFi" \ + --password "secret" \ + --target-ip 192.168.1.50 \ # Pi Zero IP + --target-port 5555 \ + --compression pca-16 \ + --pca-matrix pca_matrix_16.bin +``` + +## 6. Training Pipeline + +### 6.1 Training Workflow + +``` +Phase 1: Pre-train on public datasets (GPU workstation) + Dataset: MM-Fi + Wi-Pose (Intel 5300 format, 30 subcarriers) + Model: WiFlowPose with 30 subcarriers + Loss: L_keypoint + 0.2 * L_bone + 0.1 * L_physics + Duration: ~20 hours on single A100 + +Phase 2: Domain adaptation for ESP32 CSI (GPU workstation) + Dataset: Self-collected ESP32-S3 data (52 subcarriers) + Method: Fine-tune all layers with lower learning rate (1e-4) + Subcarrier interpolation: 30 -> 52 using existing interpolate_subcarriers() + Duration: ~4 hours + +Phase 3: Quantization (CPU workstation) + Method: Post-training quantization with 1000 calibration samples + Format: ONNX INT8 (QDQ format) + Validation: PCK@20 degradation < 2% + +Phase 4: Environment calibration (on Pi Zero) + Method: 60-second empty-room CSI collection + Output: Room fingerprint + PCA matrix + Duration: ~2 minutes total +``` + +### 6.2 Dataset Collection Protocol + +For self-collected ESP32 training data: + +1. **Setup:** 2 ESP32-S3 nodes at opposite corners of 4x4m room, Pi Zero receiving +2. **Ground truth:** Smartphone camera running MediaPipe Pose (30 FPS), synchronized via NTP +3. **Activities:** Standing, walking, sitting, waving, falling, idle (2 minutes each) +4. **Subjects:** 5+ volunteers with varying body types +5. **Environments:** 3+ rooms (bedroom, office, corridor) for generalization +6. **Total target:** ~100K synchronized CSI-pose frame pairs + +**Synchronization approach:** +- ESP32 and Pi Zero synchronized via NTP (< 10ms accuracy on LAN) +- Camera frames timestamped with system clock +- Offline alignment via cross-correlation of movement signals + +### 6.3 Transfer Learning Strategy + +Following DensePose-WiFi's proven approach: + +``` +L_total = lambda_pose * L_pose + + lambda_bone * L_bone + + lambda_transfer * L_transfer + + lambda_physics * L_physics + +L_transfer = MSE(features_student, features_teacher) +``` + +Where `features_teacher` come from a pre-trained image-based pose model (HRNet or ViTPose) and `features_student` come from the WiFi CSI model at corresponding intermediate layers. + +**Lambda schedule:** +- Epochs 1-20: lambda_transfer = 0.5 (heavy transfer guidance) +- Epochs 20-50: lambda_transfer = 0.2 (moderate guidance) +- Epochs 50-100: lambda_transfer = 0.05 (fine-tuning freedom) + +## 7. Timeline and Milestones + +### Phase 1: Foundation (Weeks 1-4) + +| Week | Actions | Deliverable | +|------|---------|-------------| +| 1 | Action 1 (protocol), ADR-069 draft | Protocol spec + parser tests | +| 2 | Action 2 (model architecture, begin) | WiFlowPose model definition in Rust | +| 2 | Action 3 (bone loss) | Loss functions implemented and tested | +| 3 | Action 2 (model architecture, complete) | Full model with ONNX export | +| 4 | Action 4 (quantization) | INT8 model, accuracy validated | + +**Milestone M1:** WiFlowPose model trained on MM-Fi, exported to INT8 ONNX, PCK@20 > 85% on validation set. + +### Phase 2: Edge Deployment (Weeks 5-8) + +| Week | Actions | Deliverable | +|------|---------|-------------| +| 5 | Action 5 (edge engine, begin) | Cross-compilation working, model loads on Pi | +| 6 | Action 5 (edge engine, complete) | Streaming inference at >= 10 Hz on Pi Zero | +| 6 | Action 6 (CSI compression) | PCA compression on ESP32, verified bandwidth reduction | +| 7 | Integration testing | ESP32 -> Pi Zero full pipeline working | +| 8 | Performance optimization | Latency < 100ms, memory < 200 MB | + +**Milestone M2:** End-to-end demo: ESP32 captures CSI, Pi Zero outputs pose at 10+ Hz. + +### Phase 3: Accuracy and Adaptation (Weeks 9-12) + +| Week | Actions | Deliverable | +|------|---------|-------------| +| 9 | Data collection (ESP32-S3 training data) | 50K+ synchronized CSI-pose frames | +| 10 | Domain adaptation training | ESP32-specific model, MPJPE < 120mm | +| 11 | Action 7 (cross-env adaptation) | Room calibration working | +| 12 | Validation and documentation | ADR-069 finalized, witness bundle | + +**Milestone M3:** Single-person MPJPE < 100mm in calibrated environment, cross-environment deployment working with 60-second calibration. + +### Phase 4: Multi-Person and 3D (Weeks 13-20) + +| Week | Actions | Deliverable | +|------|---------|-------------| +| 13-14 | Action 8 (multi-person PAF) | 2-person pose separation working | +| 15-16 | Action 9 (3D lifting) | Z-axis estimation from multi-node | +| 17-18 | Advanced optimization | Model distillation, QAT | +| 19-20 | Production hardening | OTA updates, monitoring, alerting | + +**Milestone M4:** Multi-person 3D pose at 10 Hz on Pi Zero 2 W. + +## 8. Risk Analysis + +### 8.1 Technical Risks + +| Risk | Probability | Impact | Mitigation | +|------|------------|--------|------------| +| Pi Zero 2 W inference too slow (> 100ms) | Medium | High | Fall back to activity recognition (smaller model); use Pi 4 instead | +| ESP32-S3 CSI quality insufficient for pose | Low | Critical | Already validated in ADR-028; add directional antennas if needed | +| INT8 quantization degrades accuracy > 5% | Medium | Medium | Use FP16 instead (2x size, ~1.5x slower); apply QAT | +| Cross-environment generalization poor | High | High | Room calibration (Action 7); template-based models; continuous adaptation | +| WiFi interference degrades CSI | Medium | Medium | Coherence gating (already implemented); channel hopping; 5 GHz fallback | +| ONNX Runtime binary too large for Pi Zero | Low | Medium | Use OnnxStream (2 MB) instead of full ONNX Runtime (30 MB) | +| Multi-person association errors | High | Medium | Limit to 2 persons initially; use PAF + Hungarian; AETHER re-ID | + +### 8.2 Hardware Risks + +| Risk | Probability | Impact | Mitigation | +|------|------------|--------|------------| +| Pi Zero 2 W supply shortage | Medium | Medium | Design also works with Pi 3A+ or Pi 4 | +| ESP32-S3 firmware instability | Low | Medium | Existing firmware battle-tested; OTA rollback | +| WiFi AP interference with CSI | Low | Low | Dedicated 2.4 GHz channel; ESP32 channel hopping | +| Power supply issues (brownout) | Low | Medium | Proper power supply; ESP32 brownout detection | + +### 8.3 Research Risks + +| Risk | Probability | Impact | Mitigation | +|------|------------|--------|------------| +| WiFlow results don't reproduce | Medium | High | Fall back to CSI-Former or MultiFormer architecture | +| ESP32 CSI fundamentally different from Intel 5300 | Medium | High | Collect ESP32-specific training data; subcarrier interpolation | +| Bone constraint loss doesn't improve edge accuracy | Low | Low | Remove if no benefit; constraint is simple and cheap | +| PCA compression loses critical CSI information | Low | Medium | Validate with ablation study; fall back to raw CSI if needed | + +## 9. Dependency Graph (Action Ordering) + +``` + [esp32_csi_capture] (DONE) + / \ + v v + [Action 1: Protocol] [training_pipeline] (DONE) + | / | \ + v v v v + [Action 6: Compression] [Action 2: Model] [Action 3: Bone Loss] + | | | + | +------+-------+ + | v + | [Action 4: Quantization] + | | + +---------------+------------+ + v + [Action 5: Edge Engine] + | + v + [Action 7: Cross-Env] (Phase 2) + | + v + [Action 8: Multi-Person] (Phase 2) + | + v + [Action 9: 3D Lifting] (Phase 3) +``` + +**Critical path:** Action 1 -> Action 2 -> Action 4 -> Action 5 +**Parallel path:** Action 3 can proceed concurrently with Action 2 +**Parallel path:** Action 6 can proceed concurrently with Actions 2-4 + +## 10. Success Criteria + +### Phase 1 Exit Criteria + +- [ ] WiFlowPose model trains to convergence on MM-Fi dataset +- [ ] PCK@20 >= 85% on MM-Fi validation set +- [ ] INT8 ONNX model size < 5 MB +- [ ] Bone constraint loss reduces physically implausible predictions by > 50% + +### Phase 2 Exit Criteria + +- [ ] edge_infer binary cross-compiles for aarch64 and runs on Pi Zero 2 W +- [ ] End-to-end latency < 150ms (CSI capture to pose output) +- [ ] Inference rate >= 10 Hz sustained +- [ ] PCA compression reduces bandwidth by >= 3x without > 5% accuracy loss +- [ ] Multi-node support (2 ESP32 nodes + 1 Pi Zero) working + +### Phase 3 Exit Criteria + +- [ ] Single-person MPJPE < 100mm in calibrated environment +- [ ] Cross-environment deployment works with 60-second calibration +- [ ] System runs continuously for 24 hours without crashes +- [ ] ESP32 OTA firmware update working for CSI compression parameters + +### Phase 4 Exit Criteria + +- [ ] 2-person pose separation working (MPJPE < 150mm per person) +- [ ] 3D pose estimation from 2+ nodes (Z-axis error < 200mm) +- [ ] Production monitoring and alerting operational + +## 11. Relationship to Existing ADRs + +| ADR | Relationship | +|-----|-------------| +| ADR-018 | Protocol v2 (Action 1) extends ADR-018 binary frame format | +| ADR-024 | AETHER re-ID embeddings used in multi-person tracking (Action 8) | +| ADR-027 | MERIDIAN cross-env generalization informs Action 7 | +| ADR-028 | ESP32 capability audit validates CSI quality assumptions | +| ADR-029 | RuvSense pipeline stages feed into edge inference (Action 5) | +| ADR-068 | Per-node state pipeline directly used by multi-node inference | + +## 12. New ADR Required + +**ADR-069: Edge Inference on Raspberry Pi Zero 2 W** + +This implementation plan should be formalized as ADR-069 covering: +- Protocol v2 specification +- WiFlowPose architecture selection rationale +- Pi Zero deployment constraints and optimizations +- INT8 quantization strategy +- Cross-compilation approach +- Environment calibration protocol + +Status: Proposed, pending this plan's approval. diff --git a/docs/research/ruvsense-multistatic-fidelity-architecture.md b/docs/research/architecture/ruvsense-multistatic-fidelity-architecture.md similarity index 100% rename from docs/research/ruvsense-multistatic-fidelity-architecture.md rename to docs/research/architecture/ruvsense-multistatic-fidelity-architecture.md diff --git a/docs/research/arena-physica/arena-physica-analysis.md b/docs/research/arena-physica/arena-physica-analysis.md new file mode 100644 index 00000000..a21160e4 --- /dev/null +++ b/docs/research/arena-physica/arena-physica-analysis.md @@ -0,0 +1,142 @@ +# Analysis: Arena Physica and Atlas RF Studio + +## Company Overview + +Arena Physica positions itself as building "Electromagnetic Superintelligence" -- a foundation model trained directly on electromagnetic fields, one of the four fundamental forces of physics. + +**Website:** https://www.arenaphysica.com/ +**Key Product:** Atlas RF Studio (Beta) +**Core Models:** Heaviside-0 (forward prediction), Marconi-0 (inverse design) + +## Technical Architecture + +### Heaviside-0: Forward Electromagnetic Model + +A transformer-based neural network that predicts S-parameters (scattering parameters) from circuit geometry. + +**Performance claims:** +- Weighted MAE: < 1 dB +- Speed: 13ms per design vs 4 minutes for traditional EM solvers +- Speedup: 18,000x to 800,000x over commercial solvers (HFSS, CST) + +**Architecture insights:** +- Transformer backbone (specific architecture undisclosed) +- Trained on electromagnetic field data, not just input-output mappings +- Field augmentation acts as a regularizer -- even 0.3% field coverage during training reduced OOD loss + +### Marconi-0: Inverse Design Model + +A diffusion-based generative model that produces physical RF geometries matching target S-parameter specifications. + +**Approach:** +- Iterative refinement (diffusion process) +- Generates "alien structures" -- non-intuitive geometries that meet specs +- Trades compute time for quality (more diffusion steps = better designs) + +### Training Data + +**Simulated data:** 3 million designs across 25 expert templates with procedural variations, plus random organic structures to force learning in unexplored design space regions. + +**Measured data:** Fabricated designs tested with vector network analyzers to capture manufacturing tolerances, material variations, connector parasitics. + +**Total claimed:** 20M+ simulated designs in the broader training set. + +### Current Design Space + +- 2-layer PCB designs (8mm x 8mm) +- 3 dielectric material choices +- Ground vias +- Filters and antennas + +## Key Technical Insight: Fields as Fundamental Quantities + +Arena Physica's central thesis is that Maxwell's equations govern electromagnetic fields, and models trained on field distributions learn the underlying physics rather than surface-level correlations between geometry and S-parameters. + +This is directly relevant to WiFi sensing because: + +1. **CSI IS an electromagnetic field measurement.** WiFi Channel State Information captures the complex transfer function H(f) between transmitter and receiver antennas across frequency subcarriers. This is a discrete sampling of the electromagnetic field in the propagation environment. + +2. **Human bodies perturb the electromagnetic field.** Pose estimation from WiFi works because the human body (70% water, high permittivity) creates measurable perturbations in the ambient electromagnetic field. + +3. **Foundation model approach could apply to sensing.** A model trained on electromagnetic field distributions in rooms with human bodies could potentially generalize across environments better than models trained on CSI-to-pose mappings directly. + +## Relevance to WiFi-DensePose Project + +### Direct Applicability: Moderate + +Arena Physica's current focus is RF component design (filters, antennas), not sensing. However, several concepts transfer directly: + +### 1. Physics-Informed Neural Architecture + +Arena Physica trains on the electromagnetic field itself, not just input-output pairs. We should adopt this principle: + +**Current approach in wifi-densepose:** +``` +CSI amplitude/phase -> CNN/Transformer -> Keypoint coordinates +``` + +**Physics-informed approach inspired by Arena Physica:** +``` +CSI amplitude/phase -> Field reconstruction -> Body perturbation extraction -> Pose estimation +``` + +Concretely, this means adding an intermediate field reconstruction stage that produces a spatial electromagnetic field map (similar to our existing `tomography.rs` module in RuvSense) and then extracting body perturbation from the field rather than going directly from CSI to pose. + +### 2. Forward Model for Data Augmentation + +Heaviside-0 predicts S-parameters from geometry. An analogous forward model for WiFi sensing would predict CSI from (room geometry + human pose). This enables: + +- **Synthetic training data generation:** Generate CSI samples for arbitrary room layouts and poses +- **Domain adaptation:** Bridge the sim-to-real gap by training the forward model on measured data +- **Physics-based data augmentation:** Perturb room geometry parameters to generate diverse training environments + +This directly addresses our MERIDIAN cross-environment generalization challenge (ADR-027). + +### 3. Diffusion-Based Inverse Models + +Marconi-0 uses diffusion to solve the inverse problem (S-parameters -> geometry). The analogous inverse problem for WiFi sensing is (CSI -> pose). Recent work on diffusion-based pose estimation could be adapted: + +- Generate multiple pose hypotheses from a single CSI observation +- Score hypotheses by physical plausibility (bone length constraints, joint angle limits) +- Select the highest-scoring hypothesis + +This is more robust than single-shot regression for ambiguous CSI measurements. + +### 4. Multi-Resolution Field Representation + +Arena Physica operates on 2-layer PCB designs at the mm scale. WiFi sensing operates at the wavelength scale (12.5 cm at 2.4 GHz). However, the principle of multi-resolution field representation applies: + +- **Coarse grid:** Room-level field structure (presence detection, zone occupancy) +- **Medium grid:** Body-level perturbation (bounding box, silhouette) +- **Fine grid:** Limb-level detail (keypoint localization) + +This maps to our existing RuvSense tomography module which implements RF tomography on a voxel grid, but suggests a multi-resolution approach would be more efficient. + +## Adaptation Strategy for ESP32 + Pi Zero Deployment + +### What to borrow from Arena Physica: + +1. **Field-augmented training:** During training (on GPU workstation), include an auxiliary loss that encourages the model to predict the electromagnetic field distribution, not just keypoints. This regularizes the model and improves OOD generalization. At inference time on Pi Zero, the field prediction head is pruned. + +2. **Lightweight forward model:** Train a small forward model (CSI predictor given room parameters) on the ESP32 side. This enables on-device anomaly detection: if observed CSI deviates significantly from the forward model prediction, flag the observation as potentially adversarial or corrupted. + +3. **Template-based design space:** Arena Physica uses 25 expert templates with procedural variations. We should define "room templates" (corridor, open office, bedroom, living room) and train specialized lightweight models per template, selected at deployment time. + +### What does NOT transfer: + +1. **Scale of training data:** 20M+ designs is infeasible for WiFi sensing. Real CSI data collection is expensive. Synthetic data (ray tracing simulation) partially addresses this but lacks the fidelity of Arena Physica's EM simulations. + +2. **Diffusion models on edge:** Marconi-0's diffusion approach is too computationally expensive for Pi Zero inference. We need single-shot architectures for real-time operation. + +3. **2D geometry inputs:** Arena Physica processes 2D PCB layouts. WiFi sensing requires processing time-series data with complex spatial structure. The input representations are fundamentally different. + +## Conclusions + +Arena Physica demonstrates that foundation models trained on electromagnetic field data achieve superior generalization compared to models trained on input-output mappings alone. The key transferable insights for WiFi-DensePose are: + +1. **Train on fields, not just observations** -- include field reconstruction as an auxiliary task +2. **Use forward models for augmentation** -- predict CSI from room+pose for synthetic data +3. **Multi-resolution representations** -- coarse-to-fine field reconstruction improves efficiency +4. **Template-based specialization** -- room-type-specific models improve accuracy with lower compute + +These insights inform the implementation plan, particularly the training pipeline design and the novel "field-augmented" training approach proposed in the implementation plan. diff --git a/docs/research/arena-physica/arena-physica-studio-analysis.md b/docs/research/arena-physica/arena-physica-studio-analysis.md new file mode 100644 index 00000000..9aa36427 --- /dev/null +++ b/docs/research/arena-physica/arena-physica-studio-analysis.md @@ -0,0 +1,444 @@ +# Arena Physica Studio Analysis + +Research document for wifi-densepose project. +Date: 2026-04-02 + +--- + +## 1. What is Arena Physica? + +Arena Physica (trading as Arena, arena-ai.com / arenaphysica.com) is a startup pursuing "Electromagnetic Superintelligence" -- building AI foundation models that develop superhuman intuition for how geometry shapes electromagnetic fields. + +- **Founded**: 2019 +- **Founders**: Pratap Ranade (CEO), Arya Hezarkhani, Claire Pan, Michael Frei, Harish Krishnaswamy +- **Funding**: $30M Series B (April 2025) +- **Offices**: NYC (HQ), SF, LA +- **Customers**: AMD, Anduril Industries, Sivers Semiconductors, Bausch & Lomb +- **Impact claimed**: 35% reduction in engineering man-hours, multi-month acceleration in time-to-market, >3% improvement in product quality + +Arena does NOT do WiFi sensing. They build AI-driven tools for RF/electromagnetic hardware design -- antennas, PCBs, filters, RF components. Their relevance to our project is methodological: they demonstrate how to build neural surrogates for Maxwell's equations that run 18,000x to 800,000x faster than traditional solvers. + + +## 2. Atlas Platform and RF Studio + +### 2.1 Atlas (Main Platform) + +Atlas is Arena's "agentic platform" for hardware design workflows. It is deployed in production with Fortune 500 companies. Atlas encompasses: + +- AI-driven electromagnetic simulation +- Design generation and optimization +- Hardware verification workflows +- Integration with existing engineering tools + +### 2.2 Atlas RF Studio (Public Beta) + +Atlas RF Studio (https://studio.arenaphysica.com/) is a lightweight public instance of the Atlas platform, released as an "interactive sandbox for AI-driven inverse RF design." It serves as a research preview of their electromagnetic foundation model. + +**Current capabilities (Beta):** +- Two-layer RF structures +- 8mm x 8mm maximum dimensions +- Ground vias support +- 3 dielectric material choices +- AI-driven design generation from specifications +- Real-time S-parameter prediction + +**Workflow:** +1. User inputs electromagnetic specifications (target S-parameters) +2. Marconi-0 (inverse model) generates candidate geometries via conditional diffusion +3. Heaviside-0 (forward model) evaluates each candidate in 13ms +4. System iterates: generate -> simulate -> refine +5. User receives optimized RF component design + +### 2.3 Foundation Models + +**Heaviside-0 (Forward Model)**: +- Named after Oliver Heaviside (reformulated Maxwell's equations into modern vector form) +- Predicts: S-parameters (magnitude + phase) and electromagnetic field distributions +- Speed: 13ms single design, 0.3ms batched +- Traditional solver comparison: ~4 minutes (HFSS/FDTD) +- Speedup: 18,000x - 800,000x +- Trained on 3 million designs across 25 expert templates + random structures +- Training data represents 20+ years of combined simulation time +- Accuracy: < 1 dB magnitude-weighted MAE + +**Marconi-0 (Inverse Model)**: +- Named after Guglielmo Marconi (radio pioneer) +- Generates physical geometries from target S-parameter specifications +- Uses conditional diffusion process (similar to Stable Diffusion / DALL-E architecture) +- Can produce unconventional geometries that outperform human-designed solutions + +### 2.4 Roadmap + +Planned extensions include: +- Multi-layer structures +- Silicon integration (tapeout planned by end 2026) +- Multiphysics integration (thermal, mechanical beyond EM) +- Broader frequency ranges and design spaces + + +## 3. Studio Technical Architecture + +### 3.1 Frontend Stack + +Based on runtime analysis of https://studio.arenaphysica.com/: + +| Component | Technology | Evidence | +|---|---|---| +| Framework | Next.js (App Router, server-side streaming) | `__next_f`, `__next_s` arrays, static chunk loading | +| UI Library | Mantine | Responsive breakpoint utilities (xs, sm, md, lg, xl) | +| Rendering | React (server components + client hydration) | React streaming, component loading | +| Fonts | Custom: Rules (Regular/Medium/Bold), EditionNumericalXXIX, Geist Mono (Google Fonts) | Font declarations in page source | +| Theme | Dark mode default for "rf" domain | `ATLAS_DOMAIN: "rf"` config triggers dark theme | + +### 3.2 Backend / API Infrastructure + +| Service | Detail | +|---|---| +| API Domain | `https://api.emfm.atlas.arena-ai.com` (Auth0 audience) | +| Organization | `emfmprod` | +| Authentication | Auth0 with custom organization ID | +| Feature Flags | DevCycle SDK (A/B testing) | +| Monitoring | Datadog RUM (Real User Monitoring) | +| 3D Rendering | Unreal Engine server at `https://52.61.97.121` (AWS IP) | +| Terms of Service | Required (`ATLAS_REQUIRE_TOS: true`) | + +### 3.3 Configuration Flags (from runtime config) + +```json +{ + "AUTH0_AUDIENCE": "https://api.emfm.atlas.arena-ai.com", + "ATLAS_DOMAIN": "rf", + "ATLAS_REQUIRE_TOS": true, + "POLL_FOR_MESSAGES": false, + "ENABLE_HOTJAR": false, + "SHOW_DEBUG_LOGS": false +} +``` + +Key observations: +- `POLL_FOR_MESSAGES: false` -- Messages likely use WebSocket/SSE push rather than polling +- `ENABLE_HOTJAR: false` -- Session replay disabled in production +- `SHOW_DEBUG_LOGS: false` -- Debug mode off +- The `emfm` in the API domain likely stands for "ElectroMagnetic Field Model" + +### 3.4 3D Visualization via Unreal Engine + +The most technically interesting finding: Studio connects to an Unreal Engine server (IP: 52.61.97.121, AWS us-west region) for 3D electromagnetic field visualization. + +**Likely architecture:** +1. User submits design geometry in the Next.js frontend +2. Backend runs Heaviside-0/Marconi-0 inference +3. S-parameter results and field distribution data sent to Unreal Engine instance +4. Unreal Engine renders 3D field visualization (E-field, H-field, current distributions) +5. Pixel streaming sends rendered frames back to browser via WebRTC/WebSocket +6. Interactive controls (rotate, zoom, slice planes) forwarded to Unreal Engine + +This is consistent with Unreal Engine's Pixel Streaming technology, which renders on a remote GPU and streams video to a web browser. The `52.61.97.121` IP being hardcoded suggests a dedicated rendering server or fleet. + +**Unreal Engine WebSocket Protocol** (standard): +- Signaling server negotiates WebRTC connection +- Control messages: `{ type: "input", data: { ... } }` for mouse/keyboard +- Video stream: H.264/VP8 encoded, streamed via WebRTC data channel +- Bidirectional: user input -> Unreal, rendered frames -> browser + +### 3.5 Data Formats (Inferred) + +Based on the S-parameter focus: + +**Input (Design Specification):** +- Target S-parameters: S11, S21, S12, S22 (magnitude + phase vs frequency) +- Frequency range (likely GHz, given RF focus) +- Material properties (dielectric constant, loss tangent) +- Geometric constraints (layer count, max dimensions) + +**Output (Design Result):** +- Geometry: likely a discretized grid (64x64 binary material map based on Not Boring article) +- S-parameters: complex-valued frequency response curves +- Field distributions: 2D/3D electromagnetic field maps +- Performance metrics: return loss, insertion loss, bandwidth + +**Probable API format** (speculative, based on EM conventions): +```json +{ + "design": { + "layers": [ + { + "geometry": [[0,1,1,0,...], ...], // Binary material grid + "material": "FR4", + "thickness_mm": 0.2 + } + ], + "vias": [{"x": 3, "y": 5, "radius_mm": 0.15}], + "dielectric": "rogers_4003c" + }, + "simulation": { + "s_parameters": { + "frequencies_ghz": [1.0, 1.1, ..., 40.0], + "s11_mag_db": [-5.2, -5.4, ...], + "s11_phase_deg": [45.2, 44.8, ...], + "s21_mag_db": [-0.3, -0.3, ...] + }, + "field_data": { + "type": "near_field", + "grid_size": [64, 64], + "e_field_magnitude": [[...], ...] + } + } +} +``` + + +## 4. UI Components and Features + +### 4.1 Observed UI Elements + +Based on page source analysis: + +- **Dark theme** with custom fonts (Rules family -- geometric sans-serif) +- **Icon system** ("IconMark" component -- likely a custom RF/EM icon set) +- **Responsive design** via Mantine breakpoints +- **ToS gate** requiring acceptance before use +- **Organization-scoped access** (Auth0 org-based multi-tenancy) + +### 4.2 Likely Feature Set (inferred from product description and tech stack) + +| Feature | Description | UI Component | +|---|---|---| +| Specification Input | Enter target S-parameters, frequency range, constraints | Form with frequency sweep chart | +| Design Canvas | View/edit 2D geometry layers | Interactive grid editor | +| S-parameter Viewer | Plot S11/S21/S12/S22 vs frequency | Interactive chart (likely Recharts or D3) | +| 3D Field Viewer | Visualize E/H field distributions | Unreal Engine pixel-streamed viewport | +| Design History | Browse previous designs and iterations | List/card view with thumbnails | +| Compare View | Side-by-side design comparison | Split-pane layout | +| Export | Download design files (Gerber, GDSII, S-parameter Touchstone) | Download buttons | + +### 4.3 Agentic Workflow UI + +Atlas RF Studio describes "agentic workflows" that: +1. Accept natural-language or parametric specifications +2. Generate multiple candidate designs +3. Simulate each candidate +4. Present ranked results +5. Allow iterative refinement + +This suggests an LLM chat interface (translating intent to specs) alongside the technical EM visualization. The pairing of LLM + LFM (Large Field Model) is explicitly described in their architecture. + + +## 5. Lessons for Our Sensing Server UI + +### 5.1 Architecture Patterns to Adopt + +| Arena Physica Pattern | Application to wifi-densepose sensing-server | +|---|---| +| Dark theme default | Already appropriate for a sensing/monitoring dashboard | +| Next.js + Mantine | Consider for our sensing-server UI (currently Axum + vanilla) | +| Auth0 multi-tenancy | Overkill for local deployment; useful for cloud/multi-site | +| Unreal Engine 3D | Too heavy; use Three.js/WebGL for 3D pose visualization | +| WebSocket push (not polling) | Match our real-time CSI streaming needs | +| Feature flags (DevCycle) | Useful for gradual feature rollout | +| Datadog RUM | Consider lightweight alternative (e.g., self-hosted analytics) | + +### 5.2 Visualization Approaches + +**What Arena visualizes:** +- S-parameters (frequency-domain complex response) -- charts +- Electromagnetic field distributions -- 3D heatmaps +- Design geometry -- 2D grid with material layers + +**What we need to visualize:** +- CSI amplitude/phase across subcarriers -- frequency-domain charts (similar to S-parameters) +- Person occupancy heatmap -- 2D/3D voxel grid (similar to field visualization) +- Pose skeleton overlay -- 2D/3D joint rendering +- Vital signs (HR, BR) -- time-series charts +- Node mesh topology -- graph visualization +- Signal quality metrics -- dashboard gauges + +**Shared patterns:** +- Both need real-time frequency-domain data visualization +- Both show spatial field/occupancy distributions +- Both benefit from interactive 3D (but at different scales) +- Both require low-latency streaming from computation backend + +### 5.3 Data Flow Architecture Comparison + +**Arena Physica:** +``` +Browser (Next.js) -> API (inference) -> Heaviside-0/Marconi-0 -> Unreal Engine -> Pixel Stream -> Browser +``` + +**wifi-densepose (recommended):** +``` +ESP32 nodes -> sensing-server (Axum) -> WebSocket -> Browser (React/Mantine) + | + v + RuvSense pipeline -> pose/vitals -> WebSocket -> Browser +``` + +Key difference: Arena renders 3D on the server (Unreal Engine) and streams pixels. We should render 3D on the client (Three.js/WebGL) and stream data, because: +- Our 3D scenes are simpler (skeleton + voxels vs. full EM field) +- Client-side rendering avoids GPU server costs +- Lower latency for real-time sensing feedback +- Works offline / on local network + +### 5.4 API Design Lessons + +**Arena's API pattern** (REST + WebSocket): +- REST for design submission and retrieval +- WebSocket/SSE for live simulation progress and results +- Auth0 JWT for authentication +- Organization-scoped resources + +**Recommended for sensing-server:** +- REST endpoints for configuration, history, calibration +- WebSocket for real-time CSI, pose, and vitals streaming +- Optional: SSE as fallback for environments where WebSocket is blocked +- API key or local-only access (no OAuth needed for embedded deployment) + +**Proposed WebSocket protocol for sensing-server:** +```json +// Server -> Client: CSI frame +{ + "type": "csi_frame", + "timestamp_us": 1712000000000, + "node_id": "esp32-node-1", + "subcarriers": 56, + "amplitude": [0.45, 0.52, ...], + "phase": [-1.23, 0.87, ...] +} + +// Server -> Client: Pose update +{ + "type": "pose", + "timestamp_us": 1712000000000, + "persons": [ + { + "id": 0, + "keypoints": [ + {"name": "nose", "x": 2.3, "y": 1.5, "z": 1.7, "confidence": 0.92}, + ... + ] + } + ] +} + +// Server -> Client: Vitals update +{ + "type": "vitals", + "timestamp_us": 1712000000000, + "person_id": 0, + "heart_rate_bpm": 72.5, + "breathing_rate_rpm": 16.2, + "presence_score": 0.98 +} + +// Server -> Client: Occupancy grid +{ + "type": "occupancy", + "timestamp_us": 1712000000000, + "nx": 8, "ny": 8, "nz": 4, + "bounds": [0.0, 0.0, 0.0, 6.0, 6.0, 3.0], + "densities": [0.0, 0.0, 0.12, ...] +} + +// Client -> Server: Configuration +{ + "type": "config", + "action": "set", + "key": "tomography.lambda", + "value": 0.15 +} +``` + +### 5.5 Specific UI Components to Build + +Based on Arena Physica's approach and our sensing needs: + +**Priority 1 (Core Dashboard):** +1. **Real-time CSI waterfall** -- Subcarrier amplitude over time, color-mapped (similar to spectrogram) +2. **Pose skeleton view** -- 2D/3D rendering of detected keypoints with skeleton connections +3. **Node topology map** -- Show ESP32 mesh with RSSI-colored edges +4. **Vitals panel** -- Heart rate and breathing rate with time-series charts + +**Priority 2 (Advanced Visualization):** +5. **Occupancy heatmap** -- 2D top-down view of tomographic voxel grid +6. **Phase coherence indicator** -- Per-link coherence scores (green/yellow/red) +7. **Fresnel zone overlay** -- Show first Fresnel zone on room floor plan per link + +**Priority 3 (Configuration/Debug):** +8. **Calibration wizard** -- Guide through empty-room calibration for field_model +9. **Link quality matrix** -- NxN grid showing per-link signal metrics +10. **Raw CSI inspector** -- Select individual link, view amplitude + phase per subcarrier + + +## 6. Public API Endpoints and Protocols + +### 6.1 Confirmed Endpoints + +| Endpoint | Protocol | Purpose | +|---|---|---| +| `https://studio.arenaphysica.com` | HTTPS | Main web application (Next.js SSR) | +| `https://api.emfm.atlas.arena-ai.com` | HTTPS | Backend API (Auth0 audience) | +| `https://52.61.97.121` | HTTPS/WSS | Unreal Engine rendering server | + +### 6.2 Authentication + +- Auth0-based with organization scoping +- Custom audience: `https://api.emfm.atlas.arena-ai.com` +- Organization: `emfmprod` +- Terms of Service required before access + +### 6.3 Feature Flags + +DevCycle SDK integrated for A/B testing and feature gating. This suggests gradual rollout of new capabilities. + +### 6.4 Monitoring + +Datadog RUM (Real User Monitoring) for performance tracking. Session replay (Hotjar) is available but disabled in production. + +### 6.5 What is NOT Publicly Documented + +- REST API endpoints (no public API docs found) +- WebSocket message schemas +- S-parameter data format +- Geometry encoding format +- Rate limits or usage quotas +- Pricing model + +Arena Physica appears to operate as a closed platform without public API access. The Studio beta is a controlled preview, not an open API. + + +## 7. Summary of Findings + +### What Arena Physica Is +A $30M-funded startup building neural surrogates for electromagnetic simulation. Their AI predicts S-parameters and field distributions 18,000-800,000x faster than traditional solvers. They serve Fortune 500 hardware companies (AMD, Anduril) for RF component design. + +### What Arena Physica Is NOT +They are not a WiFi sensing company. They do not do human pose estimation, CSI analysis, or IoT sensing. The relevance to our project is purely methodological. + +### Key Technical Takeaways for wifi-densepose + +1. **Neural surrogates for Maxwell's equations work** -- Arena proves that training on millions of simulation examples produces models accurate to < 1 dB MAE running in milliseconds. We could apply the same approach to CSI prediction. + +2. **Inverse design via conditional diffusion** -- Marconi-0's approach (generating geometry from target specs) parallels our inverse problem (generating pose from CSI). Conditional diffusion is a viable architecture. + +3. **Bidirectional search** -- The generate-evaluate-refine loop is more effective than direct inversion. For real-time sensing, the evaluator (forward model) must be fast. + +4. **Domain-specific models beat general LLMs** -- For electromagnetic tasks, specialized architectures substantially outperform GPT-4 / Claude. This validates our approach of building specialized CSI processing rather than relying on general-purpose models. + +5. **Studio UI is Next.js + Mantine + Unreal Engine** -- A modern stack, but the Unreal Engine component is overkill for our visualization needs. Three.js/WebGL on the client is more appropriate for our real-time sensing dashboard. + +6. **WebSocket push over polling** -- Confirmed by their `POLL_FOR_MESSAGES: false` configuration. Our sensing-server should use WebSocket push for real-time data streaming. + + +## References + +- Arena Physica Homepage: https://www.arenaphysica.com/ +- Atlas RF Studio Beta: https://studio.arenaphysica.com/ +- Introducing Atlas RF Studio (publication): https://www.arenaphysica.com/publications/rf-studio +- Electromagnetism Secretly Runs the World (Not Boring essay): https://www.notboring.co/p/electromagnetism-secretly-runs-the +- Arena Launches Atlas (press release): https://www.prnewswire.com/news-releases/arena-launches-atlas-to-accelerate-humanitys-rate-of-hardware-innovation-302423412.html +- Arena AI raises $30M (SiliconANGLE): https://siliconangle.com/2025/04/08/arena-ai-raises-30m-accelerate-innovation-hardware-testing-atlas/ +- Artificial Intuition (CDFAM presentation): https://www.designforam.com/p/artificial-intuition-building-an +- Pratap Ranade LinkedIn announcement: https://www.linkedin.com/posts/pratap-ranade-7272829_today-im-excited-to-introduce-arena-physica-activity-7442204772725723137-RRtE +- Mantine UI: https://mantine.dev/ +- Unreal Engine Pixel Streaming: https://dev.epicgames.com/documentation/en-us/unreal-engine/remote-control-api-websocket-reference-for-unreal-engine diff --git a/docs/research/arena-physica/arxiv-2505-15472-analysis.md b/docs/research/arena-physica/arxiv-2505-15472-analysis.md new file mode 100644 index 00000000..4e8fd13f --- /dev/null +++ b/docs/research/arena-physica/arxiv-2505-15472-analysis.md @@ -0,0 +1,141 @@ +# Deep Analysis: arXiv 2505.15472 -- PhysicsArena + +**Date:** 2026-04-02 +**Analyst:** GOAP Planning Agent +**Relevance to wifi-densepose:** Indirect (physics reasoning benchmark, not WiFi sensing) + +--- + +## 1. Paper Identity + +- **Title:** PhysicsArena: The First Multimodal Physics Reasoning Benchmark Exploring Variable, Process, and Solution Dimensions +- **Authors:** Song Dai, Yibo Yan, Jiamin Su, Dongfang Zihao, Yubo Gao, Yonghua Hei, Jungang Li, Junyan Zhang, Sicheng Tao, Zhuoran Gao, Xuming Hu +- **Submitted:** 2025-05-21, revised 2025-05-22 +- **Category:** cs.CL (Computation and Language) +- **arXiv ID:** 2505.15472v2 + +## 2. Core Contribution + +PhysicsArena introduces a multimodal benchmark for evaluating how Large Language Models (MLLMs) reason about physics problems. The benchmark assesses three dimensions: + +1. **Variable Identification** -- Can the model correctly identify physical variables from multimodal inputs (diagrams, text, equations)? +2. **Physical Process Formulation** -- Can the model select and chain the correct physical laws and processes? +3. **Solution Derivation** -- Can the model produce correct numerical/symbolic solutions? + +This is the first benchmark to decompose physics reasoning into these three granular dimensions rather than only evaluating final answers. + +## 3. Technical Approach + +### 3.1 Benchmark Structure + +The benchmark presents physics problems with multimodal inputs (text descriptions accompanied by diagrams, graphs, and physical setups). Problems span classical mechanics, electromagnetism, thermodynamics, optics, and modern physics. + +### 3.2 Evaluation Protocol + +Unlike prior benchmarks that score only final answers, PhysicsArena evaluates intermediate reasoning: + +- **Variable extraction accuracy:** Does the model identify all relevant physical quantities (mass, velocity, charge, field strength, etc.)? +- **Process correctness:** Does the model apply the right sequence of physical laws (Newton's laws, Maxwell's equations, conservation laws)? +- **Solution accuracy:** Does the final numerical answer match the ground truth within tolerance? + +### 3.3 Key Finding + +Current MLLMs (GPT-4V, Claude, Gemini) perform significantly worse on variable identification and process formulation than on final solution derivation when provided with correct intermediate steps. This reveals that models often arrive at correct answers through pattern matching rather than genuine physics reasoning. + +## 4. Relevance to WiFi-DensePose + +### 4.1 Direct Relevance: Low + +This paper is not about WiFi sensing, CSI processing, pose estimation, or edge deployment. It benchmarks LLM reasoning about physics problems. + +### 4.2 Indirect Relevance: Moderate + +Several concepts transfer to our domain: + +#### 4.2.1 Physics-Informed Reasoning for Signal Processing + +The paper's decomposition of physics reasoning into (variables, process, solution) maps onto WiFi sensing: + +| PhysicsArena Dimension | WiFi-DensePose Analog | +|------------------------|----------------------| +| Variable identification | CSI feature extraction (amplitude, phase, subcarrier indices, antenna config) | +| Process formulation | Signal processing pipeline selection (phase alignment, coherence gating, multiband fusion) | +| Solution derivation | Pose/activity estimation output | + +This suggests a potential architecture where intermediate representations are explicitly supervised -- not just end-to-end loss on final pose, but also losses on intermediate physical quantities (estimated path lengths, Doppler shifts, angle-of-arrival). + +#### 4.2.2 Multimodal Grounding + +PhysicsArena's core challenge is grounding abstract reasoning in physical reality from multimodal inputs. WiFi-DensePose faces the same challenge: grounding neural network predictions in the actual physics of electromagnetic wave propagation through space containing human bodies. + +#### 4.2.3 Decomposed Evaluation + +The three-dimension evaluation framework suggests we should evaluate our pipeline at multiple stages: + +1. **CSI quality metrics** (SNR, coherence, phase stability) -- analogous to variable identification +2. **Feature extraction quality** (does the modality translator preserve physically meaningful information?) -- analogous to process formulation +3. **Pose accuracy** (PCK@50, MPJPE) -- analogous to solution derivation + +This would help diagnose whether failures in pose estimation originate from poor CSI capture, lossy feature translation, or incorrect pose regression. + +### 4.3 Transferable Insight: Intermediate Supervision + +The paper's key insight -- that evaluating only final outputs masks fundamental reasoning failures -- argues for adding intermediate supervision signals to the wifi-densepose training pipeline: + +``` +L_total = lambda_pose * L_pose + + lambda_physics * L_physics_consistency + + lambda_intermediate * L_intermediate_features +``` + +Where `L_physics_consistency` penalizes predictions that violate known electromagnetic propagation physics (e.g., predicted person positions that are inconsistent with observed CSI phase relationships). + +## 5. Applicable Techniques for Implementation Plan + +### 5.1 Physics-Constrained Loss Functions + +Add a physics consistency loss that enforces: + +- **Fresnel zone consistency:** Predicted body positions must be consistent with the Fresnel zones that would produce the observed CSI perturbations +- **Multipath geometry:** The number of strong multipath components should be consistent with the predicted scene geometry +- **Doppler-velocity consistency:** If temporal CSI changes indicate Doppler shift, the predicted keypoint velocities must match + +### 5.2 Hierarchical Evaluation Pipeline + +Implement three-stage evaluation matching PhysicsArena's decomposition: + +```rust +pub struct HierarchicalEvaluation { + /// Stage 1: CSI quality assessment + pub csi_quality: CsiQualityMetrics, + /// Stage 2: Feature translation fidelity + pub translation_fidelity: TranslationMetrics, + /// Stage 3: Pose estimation accuracy + pub pose_accuracy: PoseMetrics, +} +``` + +### 5.3 Structured Intermediate Representations + +Rather than a single encoder-decoder, structure the network to produce interpretable intermediate outputs: + +``` +CSI input -> [Physics Encoder] -> physical_features (AoA, ToF, Doppler) + -> [Geometry Decoder] -> spatial_occupancy_map + -> [Pose Regressor] -> keypoint_coordinates +``` + +Each intermediate output can be supervised independently where ground truth is available. + +## 6. Conclusion + +While arXiv 2505.15472 is not directly about WiFi sensing, its framework for decomposing physics reasoning into interpretable stages provides a valuable architectural pattern. The key takeaway for wifi-densepose is: **do not rely solely on end-to-end training; add intermediate physics-grounded supervision signals to improve robustness and interpretability.** + +This aligns with the existing RuvSense architecture which already has explicit stages (multiband fusion, phase alignment, coherence scoring, coherence gating, pose tracking) -- the paper's framework validates this design choice and argues for adding supervision at each stage boundary. + +## 7. Cross-References + +- **Arena Physica (arena-physica-analysis.md):** Their thesis that "fields are the fundamental quantities" reinforces the physics-first approach recommended here. Training on electromagnetic field distributions rather than end-to-end CSI-to-pose would constitute the WiFi sensing analog of PhysicsArena's decomposed evaluation. +- **WiFlow (sota-wifi-sensing-2025.md, Section 1.1):** WiFlow's bone constraint loss is a concrete implementation of physics-informed intermediate supervision -- the skeleton must obey anatomical constraints at every prediction step. +- **MultiFormer (sota-wifi-sensing-2025.md, Section 1.2):** MultiFormer's dual-token (time + frequency) tokenization is analogous to PhysicsArena's variable identification -- it explicitly separates the physical dimensions of the CSI measurement before reasoning about them. +- **Implementation plan (implementation-plan.md):** The hierarchical evaluation pipeline in Section 5.2 directly implements the three-stage evaluation framework recommended here. diff --git a/docs/research/arena-physica/maxwells-equations-wifi-sensing.md b/docs/research/arena-physica/maxwells-equations-wifi-sensing.md new file mode 100644 index 00000000..768234e9 --- /dev/null +++ b/docs/research/arena-physica/maxwells-equations-wifi-sensing.md @@ -0,0 +1,615 @@ +# Maxwell's Equations in WiFi/RF Sensing + +Research document for wifi-densepose project. +Date: 2026-04-02 + +--- + +## 1. Maxwell's Equations and CSI Extraction + +### 1.1 Foundational Electromagnetic Theory + +All WiFi-based sensing ultimately derives from Maxwell's four partial differential equations governing electromagnetic field behavior: + +``` +(1) Gauss's Law (Electric): nabla . E = rho / epsilon_0 +(2) Gauss's Law (Magnetic): nabla . B = 0 +(3) Faraday's Law: nabla x E = -dB/dt +(4) Ampere-Maxwell Law: nabla x B = mu_0 * J + mu_0 * epsilon_0 * dE/dt +``` + +In free space with no charges or currents (the indoor propagation case), these simplify to the wave equation: + +``` +nabla^2 E - mu_0 * epsilon_0 * d^2 E / dt^2 = 0 +``` + +yielding plane wave solutions `E(r, t) = E_0 * exp(j(k . r - omega * t))` where `k = 2*pi / lambda` is the wavenumber. At 2.4 GHz WiFi, `lambda ~ 12.5 cm`; at 5 GHz, `lambda ~ 6 cm`. + +### 1.2 From Maxwell to Channel State Information + +Channel State Information (CSI) is the frequency-domain representation of the wireless channel's impulse response. The derivation from Maxwell's equations proceeds through several simplification layers: + +**Layer 1: Full Maxwell's equations** -- Exact but computationally intractable for room-scale environments at GHz frequencies. + +**Layer 2: High-frequency ray optics (Geometrical Optics / Uniform Theory of Diffraction)** -- When object dimensions >> lambda (walls, furniture), Maxwell's equations reduce to ray tracing. Each ray follows Snell's law at interfaces, with Fresnel reflection/transmission coefficients computed from the dielectric contrast. + +**Layer 3: Multipath channel model** -- The channel impulse response aggregates all propagation paths: + +``` +h(t) = sum_{n=1}^{N} alpha_n * exp(-j * phi_n) * delta(t - tau_n) +``` + +where for each path n: +- `alpha_n` = complex attenuation (from free-space path loss, reflection, diffraction) +- `phi_n = 2*pi*f*tau_n` = phase shift +- `tau_n = d_n / c` = propagation delay (distance / speed of light) + +**Layer 4: Channel Frequency Response (CFR) = CSI** -- The Fourier transform of h(t): + +``` +H(f_k) = sum_{n=1}^{N} alpha_n * exp(-j * 2*pi * f_k * tau_n) +``` + +Each OFDM subcarrier k at frequency f_k provides one complex CSI measurement: + +``` +H(f_k) = |H(f_k)| * exp(j * angle(H(f_k))) +``` + +With 802.11n/ac providing 56-256 subcarriers and 802.11ax up to 512 subcarriers across 160 MHz bandwidth, CSI captures a frequency-sampled version of the channel's multipath structure. + +**Key insight for sensing**: When a human moves in the environment, paths reflecting off the body change their `alpha_n`, `tau_n`, and `phi_n`, modulating the CSI. The sensing problem is to invert this relationship -- recover body state from CSI changes. + +### 1.3 The Two CSI Models + +The Tsinghua WiFi Sensing Tutorial (tns.thss.tsinghua.edu.cn) identifies two mainstream models: + +**Ray-Tracing Model**: Establishes explicit geometric relationships between signal paths and CSI. The received signal is: + +``` +V = sum_{n=1}^{N} |V_n| * exp(-j * phi_n) +``` + +This model enables extraction of geometric parameters (distances, reflection points, angles of arrival) from CSI data. It underpins localization and tracking applications. + +**Scattering Model**: Decomposes CSI into static and dynamic contributions: + +``` +H(f,t) = sum_{o in Omega_s} H_o(f,t) + sum_{p in Omega_d} H_p(f,t) +``` + +Dynamic scatterers (moving bodies) contribute through angular integration: + +``` +H_p(f,t) = integral_0^{2pi} integral_0^{pi} h_p(alpha, beta, f, t) * exp(-j*k*v_p*cos(alpha)*t) d_alpha d_beta +``` + +The scattering model yields the CSI autocorrelation: + +``` +rho_H(f, tau) ~ sinc(k * v * tau) +``` + +enabling speed extraction from autocorrelation peak analysis: + +``` +v = x_0 * lambda / (2 * pi * tau_0) +``` + +where `x_0` is the first sinc extremum location and `tau_0` is the corresponding time lag. + +### 1.4 Practical Simplifications Used in WiFi Sensing + +| Approximation | Physical Basis | Used When | Accuracy | +|---|---|---|---| +| Ray tracing (GO/UTD) | High-frequency limit of Maxwell | Objects >> lambda | Good for LOS + major reflections | +| Fresnel zone model | Wave diffraction | Target near TX-RX line | Excellent for presence/respiration | +| Born approximation | Weak scattering (small perturbation) | Low-contrast objects | Breaks down for human body | +| Rytov approximation | Phase perturbation expansion | Moderate scattering | Better for lossy media | +| Free-space path loss | 1/r^2 power decay | Coarse attenuation models | Adequate for RSSI-based sensing | + +**Relevance to wifi-densepose**: Our `field_model.rs` implements the eigenstructure approach (Layer 2.5 -- between full ray tracing and statistical models), decomposing the channel covariance via SVD to separate environmental modes from body perturbation. Our `tomography.rs` implements the voxel-based inverse at Layer 3 using L1-regularized least squares. + + +## 2. Physics-Informed Neural Networks (PINNs) for RF Sensing + +### 2.1 PINN Architecture for Wireless Channels + +Physics-Informed Neural Networks embed physical laws as constraints in the loss function or network architecture. For RF sensing, PINNs encode electromagnetic propagation principles: + +**Standard PINN loss for RF propagation:** + +``` +L_total = L_data + lambda_physics * L_physics + lambda_boundary * L_boundary + +where: + L_data = (1/N) * sum |H_pred(f_k) - H_meas(f_k)|^2 (CSI measurement fit) + L_physics = (1/M) * sum |nabla^2 E + k^2 * E|^2 (Helmholtz equation residual) + L_boundary = (1/B) * sum |E_pred - E_bc|^2 (boundary conditions) +``` + +The Helmholtz equation `nabla^2 E + k^2 * n^2(r) * E = 0` (time-harmonic Maxwell) constrains the solution space, where `n(r)` is the spatially varying refractive index. + +### 2.2 Key Papers and Approaches + +**PINN + GNN for RF Map Construction** (arXiv 2507.22513): +- Combines Physics-Informed Neural Networks with Graph Neural Networks +- Physical constraints from EM propagation laws guide learning +- Parameterizes multipath signals into received power, delay, and angle of arrival +- Integrates spatial dependencies for accurate prediction + +**PINN for Wireless Channel Estimation** (NeurIPS 2025, OpenReview r3plaU6DvW): +- Synergistically combines model-based channel estimation with deep network +- Exploits prior information about environmental propagation +- Critical for next-gen wireless systems: precoding, interference reduction, sensing + +**ReVeal: High-Fidelity Radio Propagation** (DySPAN 2025): +- Physics-informed approach for radio environment mapping +- Achieves high fidelity with limited measurement data + +**Physics-Informed Generative Model for Passive RF Sensing** (arXiv 2310.04173, Savazzi et al.): +- Variational Auto-Encoder integrating EM body diffraction +- Forward model: predicts CSI perturbation from body position/pose +- Validated against classical diffraction-based EM tools AND real RF measurements +- Enables real-time processing where traditional EM is too slow + +**Multi-Modal Foundational Model** (arXiv 2602.04016, February 2026): +- Foundation model for AI-driven physical-layer wireless systems +- Physics-guided pretraining grounded in EM propagation principles +- Treats wireless as inherently multimodal physical system + +**Generative AI for Wireless Sensing** (arXiv 2509.15258, September 2025): +- Physics-informed diffusion models for data augmentation +- Channel prediction and environment modeling +- Conditional mechanisms constrained by EM laws + +### 2.3 PINN Architecture for CSI-Based Sensing + +``` +Algorithm: Physics-Informed CSI Sensing Network + +Input: CSI tensor H[time, subcarrier, antenna] of shape (T, K, M) +Output: Body state estimate (pose, position, or occupancy) + +1. PREPROCESSING (physics-guided): + a. Remove carrier frequency offset (CFO): H_clean = H * exp(-j*2*pi*delta_f*t) + b. Conjugate multiply across antenna pairs to cancel common phase noise + c. Compute CSI-ratio: H_ratio(f,t) = H_dynamic(f,t) / H_static(f,t) + +2. PHYSICS ENCODER: + a. Embed Fresnel zone geometry as positional encoding + b. Apply multi-head attention with frequency-aware kernels + c. Enforce causality: attention mask respects propagation delay ordering + +3. PHYSICS-CONSTRAINED DECODER: + a. Predict body state x_hat + b. Forward-simulate expected CSI from x_hat using ray-tracing differentiable renderer + c. Compute physics loss: L_phys = ||H_simulated(x_hat) - H_measured||^2 + +4. TRAINING LOSS: + L = L_pose_supervision + alpha * L_phys + beta * L_temporal_smoothness +``` + +### 2.4 Relevance to wifi-densepose + +Our RuvSense pipeline already implements physics-guided preprocessing (phase alignment, coherence gating, Fresnel zone awareness). The next step would be to: + +1. Add a differentiable ray-tracing forward model as a physics constraint during NN training +2. Use the field model eigenstructure (from `field_model.rs`) as an informed prior +3. Embed Fresnel zone geometry from link topology as architectural bias + + +## 3. Inverse Electromagnetic Scattering for Body Reconstruction + +### 3.1 The Inverse Problem + +The forward problem: given a known body position/shape and room geometry, predict the CSI. + +``` +Forward: body_state -> Maxwell/ray-tracing -> H(f,t) [well-posed] +Inverse: H(f,t) -> ??? -> body_state [ill-posed] +``` + +WiFi sensing is fundamentally an inverse scattering problem. A WiFi antenna receives signal as 1D amplitude/phase -- the spatial information of the 3D scene is collapsed to a single CSI complex number per subcarrier per antenna pair. Reconstructing fine-grained spatial information from this compressed observation is severely ill-posed. + +### 3.2 Linearized Inverse Scattering: Born and Rytov Approximations + +**Helmholtz equation with scatterer:** + +``` +nabla^2 E(r) + k^2 * (1 + O(r)) * E(r) = 0 +``` + +where `O(r) = epsilon_r(r) - 1` is the object function (dielectric contrast of the body relative to free space). + +**Born approximation** (first-order): Assumes the field inside the scatterer equals the incident field: + +``` +E_scattered(r) ~ k^2 * integral O(r') * E_incident(r') * G(r, r') dr' +``` + +where `G(r, r')` is the free-space Green's function. This is valid when `O(r)` is small and the object is electrically small. For the human body at 2.4 GHz (`epsilon_r ~ 40-60` for muscle tissue), the Born approximation is grossly violated. + +**Rytov approximation**: Expands the complex phase rather than the field: + +``` +E_total(r) = E_incident(r) * exp(psi(r)) + +psi(r) ~ (k^2 / E_incident(r)) * integral O(r') * E_incident(r') * G(r, r') dr' +``` + +The Rytov approximation handles larger phase accumulation than Born but still assumes weak scattering. It works better for lossy media where absorption limits multiple scattering. + +**Extended Phaseless Rytov Approximation (xPRA-LM)** (Dubey et al., arXiv 2110.03211): +- First linear phaseless inverse scattering approximation with large validity range +- Demonstrated with 2.4 GHz WiFi nodes for indoor imaging +- Handles objects with `epsilon_r` up to 15+j1.5 (20x wavelength size) +- At `epsilon_r = 77+j7` (water/tissue), shape reconstruction still accurate + +### 3.3 Iterative Nonlinear Methods + +For high-contrast scatterers like the human body, iterative methods are required: + +**Distorted Born Iterative Method (DBIM):** + +``` +Algorithm: DBIM for WiFi Body Imaging + +Input: Measured scattered field E_s at receiver locations +Output: Object function O(r) (dielectric map of scene) + +1. Initialize: O_0(r) = 0 (empty room) +2. For iteration i = 0, 1, 2, ...: + a. Solve forward problem: compute total field E_i(r) in medium with O_i(r) + b. Compute Green's function G_i(r, r') for medium O_i(r) + c. Linearize: delta_E_s = K_i * delta_O (Frechet derivative) + d. Solve: delta_O = K_i^+ * (E_s_measured - E_s_computed(O_i)) + e. Update: O_{i+1} = O_i + delta_O + f. Check convergence: ||E_s_measured - E_s_computed(O_{i+1})|| < epsilon +``` + +**Challenges for WiFi sensing:** +- WiFi provides sparse spatial sampling (few antenna pairs vs. full aperture) +- Phase is often unavailable (RSSI-only) or corrupted by hardware imperfections +- Real-time requirement conflicts with iterative forward solves +- Human body is a strong, moving scatterer + +### 3.4 Radio Tomographic Imaging (RTI) + +RTI (Wilson & Patwari, 2010) simplifies the inverse scattering problem by: +1. Using only RSS (received signal strength) -- phaseless +2. Assuming a voxelized scene with additive attenuation model +3. Linearizing: measured attenuation = sum of voxel attenuations along path + +**Forward model:** + +``` +y = W * x + n + +where: + y = [y_1, ..., y_L]^T attenuation measurements (L links) + x = [x_1, ..., x_V]^T voxel occupancy values (V voxels) + W = [w_{l,v}] weight matrix (link-voxel intersection) + n = measurement noise +``` + +**Weight model (elliptical):** + +``` +w_{l,v} = { 1 / sqrt(d_l) if d_{l,v}^tx + d_{l,v}^rx < d_l + lambda_w + { 0 otherwise + +where: + d_l = distance between TX_l and RX_l + d_{l,v}^tx = distance from TX_l to voxel v center + d_{l,v}^rx = distance from RX_l to voxel v center + lambda_w = excess path length parameter (typically ~lambda/4) +``` + +**Inverse solution (Tikhonov-regularized):** + +``` +x_hat = (W^T W + alpha * C^{-1})^{-1} * W^T * y +``` + +where `C` is the spatial covariance matrix and `alpha` controls regularization. + +**Our implementation** (`tomography.rs`) uses ISTA (Iterative Shrinkage-Thresholding Algorithm) with L1 regularization for sparsity: + +``` +Algorithm: ISTA for RF Tomography (as in tomography.rs) + +Input: Weight matrix W, observations y, lambda (L1 weight) +Output: Sparse voxel densities x + +1. Initialize x = 0 +2. step_size = 1 / ||W^T * W||_spectral +3. For iter = 1 to max_iterations: + a. gradient = W^T * (W * x - y) + b. x_candidate = x - step_size * gradient + c. x = soft_threshold(x_candidate, lambda * step_size) + where soft_threshold(z, t) = sign(z) * max(|z| - t, 0) + d. residual = ||W * x - y|| + e. if residual < tolerance: break +``` + +### 3.5 Reconciling RTI with Inverse Scattering + +Dubey, Li & Murch (arXiv 2311.09633) reconciled empirical RTI with formal inverse scattering theory: +- RTI's additive attenuation model corresponds to a first-order Born approximation of the scattered field amplitude +- Their enhanced method reconstructs both shape AND material properties +- Validated at 2.4 GHz with WiFi transceivers indoors + +### 3.6 State-of-the-Art: Deep Learning Approaches + +**DensePose From WiFi** (Geng, Huang, De la Torre, arXiv 2301.00250, CMU): +- Maps WiFi CSI amplitude+phase to UV coordinates across 24 body regions +- Uses 3 TX + 3 RX antennas, 56 subcarriers per link +- Teacher-student training: camera-based DensePose provides labels +- Performance comparable to image-based approaches +- Works through walls and in darkness + +**RF-Pose** (Zhao et al., CVPR 2018, MIT CSAIL): +- Through-wall human pose estimation using radio signals +- Cross-modal supervision: vision model trains RF model +- Generalizes to through-wall scenarios with no through-wall training data + +**Person-in-WiFi** (Wang et al., ICCV 2019, CMU): +- End-to-end body segmentation and pose from WiFi +- Standard 802.11n signals, off-the-shelf hardware + +**3D WiFi Pose Estimation** (arXiv 2204.07878): +- Free-form and moving activities +- 3D joint position estimation from CSI + +**HoloCSI** (2025-2026): +- Holographic tomography pipeline coupling physics-guided projection with adaptive top-k sparse transformer +- Preprocesses: CFO rectification, Doppler compensation, antenna-pair normalization +- Sparse multi-head attention prunes low-magnitude query-key pairs (quadratic -> near-linear complexity) +- Results: +2.9 dB PSNR, +3.6% SSIM, +12.4% mesh IoU vs baselines +- 25 fps on RTX-4070-mobile at 5% sparsity; 7 fps on Raspberry Pi 5 with attention-GRU variant + + +## 4. Computational Electromagnetics for WiFi Sensing + +### 4.1 FDTD (Finite-Difference Time-Domain) + +FDTD discretizes Maxwell's curl equations on a Yee grid and marches forward in time: + +``` +Algorithm: FDTD Update (2D TM mode, simplified) + +Grid: dx = dy = lambda/20 (minimum 10 cells per wavelength) +Time step: dt = dx / (c * sqrt(2)) [Courant condition] + +For each time step n: + 1. Update H fields: + H_z^{n+1/2}(i,j) = H_z^{n-1/2}(i,j) + (dt/mu_0) * [ + (E_x^n(i,j+1) - E_x^n(i,j)) / dy - + (E_y^n(i+1,j) - E_y^n(i,j)) / dx + ] + + 2. Update E fields: + E_x^{n+1}(i,j) = E_x^n(i,j) + (dt / epsilon(i,j)) * [ + (H_z^{n+1/2}(i,j) - H_z^{n+1/2}(i,j-1)) / dy + ] +``` + +**For WiFi at 2.4 GHz:** +- Wavelength: 12.5 cm +- Grid cell: ~6 mm (20 cells/lambda) +- Room 6m x 6m x 3m: 1000 x 1000 x 500 = 500M cells +- Memory: ~24 GB (6 field components * 4 bytes * 500M) +- Time steps: ~10,000 for steady state + +**Key references for WiFi FDTD:** +- Lauer & Ertel (2003), "Using Large-Scale FDTD for Indoor WLAN" -- Full FDTD at 2.45 GHz in office environments +- Lui et al. (2018), "Human Body Shadowing" -- FDTD human body model for ray-tracing calibration (Hindawi IJAP 9084830) +- Martinez-Gonzalez et al. (2008), "FDTD Assessment Human Exposure WiFi/Bluetooth" -- SAR computation with anatomical body models + +**Practical limitations**: FDTD is too slow for real-time sensing but valuable for: +- Generating training data for neural networks +- Validating approximate models +- Understanding near-field body-wave interaction + +### 4.2 Method of Moments (MoM) + +MoM converts Maxwell's integral equations into matrix equations by expanding fields in basis functions: + +``` +[Z] * [I] = [V] + +where: + Z_{mn} = integral integral G(r_m, r_n) * f_m(r) * f_n(r') dS dS' + I_n = unknown current coefficients + V_m = incident field excitation +``` + +**Application**: MoM excels for antenna analysis and is used to model WiFi antenna patterns. Less practical for full room simulation due to O(N^2) memory and O(N^3) solve time. + +### 4.3 FEM (Finite Element Method) + +FEM handles complex geometries and material interfaces more naturally than FDTD: + +``` +Weak form of Helmholtz equation: +integral nabla x E_test . (1/mu_r * nabla x E) dV - k_0^2 * integral E_test . epsilon_r * E dV += -j * omega * integral E_test . J_s dV +``` + +**Application**: HFSS (Ansys) and COMSOL use FEM for electromagnetic simulation. Arena Physica's Heaviside-0 model was trained against such commercial FEM solvers. + +### 4.4 Comparison for WiFi Sensing Applications + +| Method | Speed | Accuracy | Body Modeling | Room Scale | Real-Time | +|---|---|---|---|---|---| +| FDTD | Hours | Full-wave exact | Excellent | Feasible (GPU) | No | +| MoM | Hours | Exact for surfaces | Good (surface) | Impractical | No | +| FEM | Hours | Exact | Excellent | Feasible | No | +| Ray tracing | Seconds | GO/UTD approximation | Coarse | Easy | Near real-time | +| RTI (ISTA) | Milliseconds | Linear approximation | Voxelized | Easy | Yes | +| Neural surrogate | Milliseconds | Trained accuracy | Implicit | Trained domain | Yes | + +### 4.5 Hybrid Approaches: Neural Surrogates Trained on CEM + +The most promising direction combines full-wave accuracy with real-time speed: + +1. **Offline**: Run thousands of FDTD/FEM simulations with different body positions +2. **Train**: Neural network learns the mapping from body state to CSI +3. **Deploy**: Neural surrogate runs in milliseconds for real-time inference + +This is exactly Arena Physica's approach (Section 5), applied to RF component design rather than sensing. The same methodology applies to WiFi sensing: train a neural forward model on FDTD data, then use it as a differentiable physics constraint during inverse model training. + + +## 5. Arena Physica's Approach + +### 5.1 Company Overview + +Arena Physica (arena-ai.com / arenaphysica.com) pursues "Electromagnetic Superintelligence" -- building foundation models that develop superhuman intuition for how geometry shapes electromagnetic fields. Founded by Pratap Ranade (CEO), Arya Hezarkhani, Claire Pan, Michael Frei, and Harish Krishnaswamy. Offices in NYC (HQ), SF, LA. + +Raised $30M Series B (April 2025). Deployed with AMD, Anduril Industries, Sivers Semiconductors, Bausch & Lomb. Claims 35% reduction in engineering man-hours and multi-month acceleration in time-to-market. + +### 5.2 Technical Architecture + +Arena's Atlas platform uses two foundation models: + +**Heaviside-0 (Forward Model)**: +- Input: PCB/RF geometry (discretized as grid) +- Output: S-parameters (magnitude + phase) and field distributions +- Speed: 13ms per design (single), 0.3ms batched +- Comparison: Traditional solver (HFSS/FDTD) takes ~4 minutes +- Speedup: 18,000x to 800,000x + +**Marconi-0 (Inverse Model)**: +- Input: Target S-parameter specification +- Output: Physical geometry that achieves the specification +- Method: Conditional diffusion process (similar to image generation) +- Generates unconventional geometries no human designer would conceive + +**Training data**: 3 million simulated designs across 25 expert templates + random structures, totaling 20+ years of combined simulation time. Incorporates both S-parameter data and electromagnetic field distributions. + +**Validation**: Predictions validated against commercial numerical field solvers (likely HFSS). Internal testing shows < 1 dB magnitude-weighted MAE (RF engineers operate in 20-30 dB ranges). + +### 5.3 Relationship to Maxwell's Equations + +Arena does NOT solve Maxwell's equations directly. Instead: + +1. **Training phase**: Maxwell's equations are solved by conventional solvers (FDTD/FEM/MoM) millions of times to generate training data +2. **Inference phase**: Neural surrogate approximates Maxwell's solutions in milliseconds +3. **Design loop**: Generator proposes geometry -> Evaluator predicts EM behavior -> Iterate + +As Pratap Ranade states: the model "learns the syntax of physics" inductively from examples, rather than deductively from equations. This trades precision for speed -- acceptable when searching design space where "speed and direction matter more than precision." + +### 5.4 The "Large Field Model" (LFM) Concept + +Arena's LFM is distinct from Large Language Models: +- LLMs learn linguistic patterns from text +- LFMs learn electromagnetic field patterns from simulation data +- The input is geometry (not text); the output is field distributions (not tokens) +- Domain-specific architecture substantially outperforms general LLMs on EM tasks + +### 5.5 Relevance to WiFi Sensing + +Arena Physica focuses on RF component design (antennas, PCBs, filters), not WiFi sensing. However, their approach is directly transferable: + +| Arena Physica (Design) | WiFi Sensing (Our Case) | +|---|---| +| Forward: geometry -> S-parameters | Forward: body pose -> CSI | +| Inverse: S-parameters -> geometry | Inverse: CSI -> body pose | +| Train on FDTD/FEM simulations | Train on ray-tracing / FDTD simulations | +| 13ms inference | Real-time CSI inference | +| Conditional diffusion for generation | Conditional generation for pose prediction | + +**Key lesson for wifi-densepose**: Building a neural forward model (body_pose -> expected_CSI) trained on electromagnetic simulation data, then using it as a differentiable physics constraint during inverse model training, could significantly improve our pose estimation accuracy and generalization. This is the "physics-informed" approach with the computational burden shifted to offline training. + + +## 6. Connections to wifi-densepose Codebase + +### 6.1 Existing Physics-Based Modules + +| Module | Physical Model | Maxwell Connection | +|---|---|---| +| `field_model.rs` | SVD eigenstructure decomposition | Eigenmode basis of room's EM field | +| `tomography.rs` | L1-regularized RTI (ISTA solver) | Linearized inverse scattering | +| `multistatic.rs` | Attention-weighted cross-node fusion | Exploits geometric diversity of multiple TX/RX | +| `phase_align.rs` | LO phase offset estimation | Corrects hardware-induced phase corruption | +| `coherence.rs` | Z-score coherence scoring | Statistical test on EM field stability | +| `coherence_gate.rs` | Accept/Reject decisions | Quality control on EM measurements | +| `adversarial.rs` | Physical impossibility detection | Enforces EM consistency constraints | + +### 6.2 Potential Enhancements Based on This Research + +1. **Differentiable ray-tracing forward model**: Train a neural surrogate on ray-tracing simulations of CSI for various body poses in the deployment room. Use as physics constraint in pose estimation. + +2. **Fresnel zone integration**: Augment the attention mechanism in `multistatic.rs` with Fresnel zone geometry -- links where the body falls within the first Fresnel zone should receive higher attention weight. + +3. **xPRA-LM inverse scattering**: For higher-resolution body imaging than RTI, implement the Extended Phaseless Rytov Approximation. Our tomography module currently uses the simpler additive attenuation model. + +4. **HoloCSI-style sparse transformer**: Replace the dense attention in cross-viewpoint fusion with top-k sparse attention for efficiency on ESP32-constrained deployments. + +5. **Physics-informed training loss**: When training the DensePose model, add a loss term penalizing physically impossible CSI patterns (e.g., signals that would require faster-than-light propagation or negative attenuation). + + +## 7. References + +### Core WiFi Sensing Surveys +- WiFi Sensing with Channel State Information: A Survey. ACM Computing Surveys, 2019. https://dl.acm.org/doi/fullHtml/10.1145/3310194 +- Cross-Domain WiFi Sensing with Channel State Information: A Survey. ACM Computing Surveys, 2022. https://dl.acm.org/doi/10.1145/3570325 +- Wireless sensing applications with Wi-Fi CSI, preprocessing techniques, and detection algorithms: A survey. Computer Communications, 2024. https://www.sciencedirect.com/science/article/abs/pii/S0140366424002214 +- Understanding CSI (Tsinghua Tutorial). https://tns.thss.tsinghua.edu.cn/wst/docs/pre/ + +### Physics-Informed Neural Networks for RF +- PINN and GNN-based RF Map Construction. arXiv 2507.22513 +- Physics-Informed Neural Networks for Wireless Channel Estimation. NeurIPS 2025, OpenReview r3plaU6DvW +- ReVeal: High-Fidelity Radio Propagation. DySPAN 2025. https://wici.iastate.edu/wp-content/uploads/2025/03/ReVeal-DySPAN25.pdf +- Physics-informed generative model for passive RF sensing. Savazzi et al., arXiv 2310.04173 +- Multi-Modal Foundational Model for Wireless Communication and Sensing. arXiv 2602.04016 +- Generative AI Meets Wireless Sensing: Towards Wireless Foundation Model. arXiv 2509.15258 +- Physics-Informed Neural Networks for Sensing Radio Spectrum. IJRTE v14i3, 2025 + +### Inverse Scattering and Body Reconstruction +- DensePose From WiFi. Geng, Huang, De la Torre. arXiv 2301.00250 +- Through-Wall Human Pose Estimation Using Radio Signals. Zhao et al., CVPR 2018. https://rfpose.csail.mit.edu/ +- Person-in-WiFi: Fine-grained Person Perception. Wang et al., ICCV 2019 +- 3D Human Pose Estimation for Free-from Activities Using WiFi. arXiv 2204.07878 +- EM-POSE: 3D Human Pose from Sparse Electromagnetic Trackers. ICCV 2021 +- Reconciling Radio Tomographic Imaging with Phaseless Inverse Scattering. Dubey, Li, Murch. arXiv 2311.09633 +- Accurate Indoor RF Imaging using Extended Rytov Approximation. Dubey et al., arXiv 2110.03211 +- Phaseless Extended Rytov Approximation for Strongly Scattering Low-Loss Media. IEEE, 2022. https://ieeexplore.ieee.org/document/9766313/ +- Distorted Wave Extended Phaseless Rytov Iterative Method. arXiv 2205.12578 +- 3D Full Convolution Electromagnetic Reconstruction Neural Network (3D-FCERNN). PMC 9689780 + +### Radio Tomographic Imaging +- Radio Tomographic Imaging with Wireless Networks. Wilson & Patwari, 2010. https://span.ece.utah.edu/uploads/RTI_version_3.pdf +- Compressive Sensing Based Radio Tomographic Imaging with Spatial Diversity. PMC 6386865 +- Passive Localization Based on Radio Tomography Images with CNN. Nature Scientific Reports, 2025 +- Enhancing Accuracy of WiFi Tomographic Imaging Using Human-Interference Model. 2018 + +### Fresnel Zone Models +- WiFi CSI-based device-free sensing: from Fresnel zone model to CSI-ratio model. CCF Trans. Pervasive Computing, 2021. https://link.springer.com/article/10.1007/s42486-021-00077-z +- Towards a Dynamic Fresnel Zone Model for WiFi-based Human Activity Recognition. ACM IMWUT, 2023. https://dl.acm.org/doi/10.1145/3596270 +- CSI-based human sensing using model-based approaches: a survey. JCDE, 2021. https://academic.oup.com/jcde/article/8/2/510/6137731 + +### Computational Electromagnetics +- Using Large-Scale FDTD for Indoor WLAN. ResearchGate. https://www.researchgate.net/publication/42637096 +- Human Body Shadowing -- FDTD and UTD. Hindawi IJAP, 2018. https://www.hindawi.com/journals/ijap/2018/9084830/ +- FDTD Assessment Human Exposure WiFi/Bluetooth. ResearchGate. https://www.researchgate.net/publication/23400115 +- Simulation of Wireless LAN Indoor Propagation Using FDTD. IEEE, 2007. https://ieeexplore.ieee.org/document/4396450 +- Waveguide Models of Indoor Channels: FDTD Insights. ResearchGate. https://www.researchgate.net/publication/4368711 +- XFdtd 3D EM Simulation Software. Remcom. https://www.remcom.com/xfdtd-3d-em-simulation-software +- Wireless InSite Ray Tracing. Remcom. https://www.remcom.com/wireless-insite-em-propagation-software/ + +### Arena Physica +- Introducing Atlas RF Studio. https://www.arenaphysica.com/publications/rf-studio +- Electromagnetism Secretly Runs the World. Not Boring (Packy McCormick). https://www.notboring.co/p/electromagnetism-secretly-runs-the +- Arena Launches Atlas (Press Release). https://www.prnewswire.com/news-releases/arena-launches-atlas-to-accelerate-humanitys-rate-of-hardware-innovation-302423412.html +- Arena AI raises $30M. SiliconANGLE. https://siliconangle.com/2025/04/08/arena-ai-raises-30m-accelerate-innovation-hardware-testing-atlas/ +- Artificial Intuition: Building an AI Mind for EM Design. CDFAM NYC 2025. https://www.designforam.com/p/artificial-intuition-building-an + +### Holographic / Advanced +- HoloCSI: Holographic tomography pipeline with physics-guided projection and sparse transformer. 2025-2026 +- CSI-Bench: Large-Scale In-the-Wild Dataset for Multi-task WiFi Sensing. arXiv 2505.21866 +- RFBoost: Understanding and Boosting Deep WiFi Sensing via Physical Data Augmentation. arXiv 2410.07230 +- Vision Reimagined: AI-Powered Breakthroughs in WiFi Indoor Imaging. arXiv 2401.04317 +- Electromagnetic Information Theory for 6G. arXiv 2401.08921 diff --git a/docs/research/21-sota-neural-decoding-landscape.md b/docs/research/neural-decoding/21-sota-neural-decoding-landscape.md similarity index 100% rename from docs/research/21-sota-neural-decoding-landscape.md rename to docs/research/neural-decoding/21-sota-neural-decoding-landscape.md diff --git a/docs/research/22-brain-observatory-application-domains.md b/docs/research/neural-decoding/22-brain-observatory-application-domains.md similarity index 100% rename from docs/research/22-brain-observatory-application-domains.md rename to docs/research/neural-decoding/22-brain-observatory-application-domains.md diff --git a/docs/research/11-quantum-level-sensors.md b/docs/research/quantum-sensing/11-quantum-level-sensors.md similarity index 100% rename from docs/research/11-quantum-level-sensors.md rename to docs/research/quantum-sensing/11-quantum-level-sensors.md diff --git a/docs/research/12-quantum-biomedical-sensing.md b/docs/research/quantum-sensing/12-quantum-biomedical-sensing.md similarity index 100% rename from docs/research/12-quantum-biomedical-sensing.md rename to docs/research/quantum-sensing/12-quantum-biomedical-sensing.md diff --git a/docs/research/13-nv-diamond-neural-magnetometry.md b/docs/research/quantum-sensing/13-nv-diamond-neural-magnetometry.md similarity index 100% rename from docs/research/13-nv-diamond-neural-magnetometry.md rename to docs/research/quantum-sensing/13-nv-diamond-neural-magnetometry.md diff --git a/docs/research/00-rf-topological-sensing-index.md b/docs/research/rf-topological-sensing/00-rf-topological-sensing-index.md similarity index 100% rename from docs/research/00-rf-topological-sensing-index.md rename to docs/research/rf-topological-sensing/00-rf-topological-sensing-index.md diff --git a/docs/research/01-rf-graph-theory-foundations.md b/docs/research/rf-topological-sensing/01-rf-graph-theory-foundations.md similarity index 100% rename from docs/research/01-rf-graph-theory-foundations.md rename to docs/research/rf-topological-sensing/01-rf-graph-theory-foundations.md diff --git a/docs/research/02-csi-edge-weight-computation.md b/docs/research/rf-topological-sensing/02-csi-edge-weight-computation.md similarity index 100% rename from docs/research/02-csi-edge-weight-computation.md rename to docs/research/rf-topological-sensing/02-csi-edge-weight-computation.md diff --git a/docs/research/03-attention-mechanisms-rf-sensing.md b/docs/research/rf-topological-sensing/03-attention-mechanisms-rf-sensing.md similarity index 100% rename from docs/research/03-attention-mechanisms-rf-sensing.md rename to docs/research/rf-topological-sensing/03-attention-mechanisms-rf-sensing.md diff --git a/docs/research/04-transformer-architectures-graph-sensing.md b/docs/research/rf-topological-sensing/04-transformer-architectures-graph-sensing.md similarity index 100% rename from docs/research/04-transformer-architectures-graph-sensing.md rename to docs/research/rf-topological-sensing/04-transformer-architectures-graph-sensing.md diff --git a/docs/research/05-sublinear-mincut-algorithms.md b/docs/research/rf-topological-sensing/05-sublinear-mincut-algorithms.md similarity index 100% rename from docs/research/05-sublinear-mincut-algorithms.md rename to docs/research/rf-topological-sensing/05-sublinear-mincut-algorithms.md diff --git a/docs/research/06-esp32-mesh-hardware-constraints.md b/docs/research/rf-topological-sensing/06-esp32-mesh-hardware-constraints.md similarity index 100% rename from docs/research/06-esp32-mesh-hardware-constraints.md rename to docs/research/rf-topological-sensing/06-esp32-mesh-hardware-constraints.md diff --git a/docs/research/07-contrastive-learning-rf-coherence.md b/docs/research/rf-topological-sensing/07-contrastive-learning-rf-coherence.md similarity index 100% rename from docs/research/07-contrastive-learning-rf-coherence.md rename to docs/research/rf-topological-sensing/07-contrastive-learning-rf-coherence.md diff --git a/docs/research/08-temporal-graph-evolution-ruvector.md b/docs/research/rf-topological-sensing/08-temporal-graph-evolution-ruvector.md similarity index 100% rename from docs/research/08-temporal-graph-evolution-ruvector.md rename to docs/research/rf-topological-sensing/08-temporal-graph-evolution-ruvector.md diff --git a/docs/research/09-resolution-spatial-granularity.md b/docs/research/rf-topological-sensing/09-resolution-spatial-granularity.md similarity index 100% rename from docs/research/09-resolution-spatial-granularity.md rename to docs/research/rf-topological-sensing/09-resolution-spatial-granularity.md diff --git a/docs/research/10-system-architecture-prototype.md b/docs/research/rf-topological-sensing/10-system-architecture-prototype.md similarity index 100% rename from docs/research/10-system-architecture-prototype.md rename to docs/research/rf-topological-sensing/10-system-architecture-prototype.md diff --git a/docs/research/remote-vital-sign-sensing-modalities.md b/docs/research/sota-surveys/remote-vital-sign-sensing-modalities.md similarity index 100% rename from docs/research/remote-vital-sign-sensing-modalities.md rename to docs/research/sota-surveys/remote-vital-sign-sensing-modalities.md diff --git a/docs/research/ruview-multistatic-fidelity-sota-2026.md b/docs/research/sota-surveys/ruview-multistatic-fidelity-sota-2026.md similarity index 100% rename from docs/research/ruview-multistatic-fidelity-sota-2026.md rename to docs/research/sota-surveys/ruview-multistatic-fidelity-sota-2026.md diff --git a/docs/research/sota-surveys/sota-wifi-sensing-2025.md b/docs/research/sota-surveys/sota-wifi-sensing-2025.md new file mode 100644 index 00000000..d4a27d48 --- /dev/null +++ b/docs/research/sota-surveys/sota-wifi-sensing-2025.md @@ -0,0 +1,341 @@ +# SOTA WiFi Sensing for Edge Pose Estimation (2024-2026 Update) + +**Date:** 2026-04-02 +**Focus:** New architectures, lightweight models, edge deployment, ESP32+Pi Zero inference +**Complements:** `wifi-sensing-ruvector-sota-2026.md` (February 2026 survey) + +--- + +## 1. New Architectures Since Last Survey + +### 1.1 WiFlow: Lightweight Continuous Pose Estimation (February 2026) + +**Paper:** WiFlow: A Lightweight WiFi-based Continuous Human Pose Estimation Network with Spatio-Temporal Feature Decoupling ([arXiv:2602.08661](https://arxiv.org/html/2602.08661)) + +WiFlow is the most directly relevant architecture for our ESP32 + Pi Zero deployment target. + +#### Architecture + +Three-stage encoder-decoder with spatio-temporal decoupling: + +**Stage 1: Temporal Encoder (TCN)** +- Dilated causal convolution with exponentially growing dilation factors (1, 2, 4, 8) +- Input: 540x20 tensor (18 antenna links x 30 subcarriers = 540 features, 20 time steps) +- Progressive channel compression: 540 -> 440 -> 340 -> 240 +- Preserves temporal causality while achieving full receptive field coverage + +**Stage 2: Spatial Encoder (Asymmetric Convolution)** +- 1xk kernels operating only in the subcarrier dimension +- 4 residual blocks: 8 -> 16 -> 32 -> 64 channels +- Subcarrier compression: 240 -> 120 -> 60 -> 30 -> 15 +- Stride (1,2) downsampling -- no pooling layers + +**Stage 3: Axial Self-Attention** +- Two-stage axial attention reduces complexity from O(H^2 W^2) to O(H^2 W + HW^2) +- Stage one: width direction (temporal axis), 8 groups +- Stage two: height direction (keypoint axis) +- Input reshaped to (B x K) x C x T for first stage + +**Decoder:** +- Adaptive average pooling instead of fully connected layers +- Direct coordinate regression to 2D keypoint positions + +#### Key Metrics + +| Metric | WiFlow | WPformer | WiSPPN | +|--------|--------|----------|--------| +| Parameters | **4.82M** | 10.04M | 121.5M | +| FLOPs | **0.47B** | 35.00B | 338.45B | +| PCK@20 (random split) | **97.00%** | 70.02% | 85.87% | +| MPJPE (random split) | **0.008m** | 0.028m | 0.016m | +| PCK@20 (cross-subject) | **86.89%** | -- | -- | +| Training time (5-fold) | **18.17h** | 137.5h | -- | + +**Critical observations for our project:** +- 4.82M parameters at INT8 quantization = ~4.8 MB model size -- fits in Pi Zero 2 W RAM (512 MB) +- 0.47B FLOPs suggests ~50ms inference on Cortex-A53 with NEON SIMD (estimated) +- Only uses amplitude, discards phase (phase is "heavily corrupted by CFO and SFO in commercial WiFi devices") +- ESP32-S3 CSI has similar CFO/SFO issues, so amplitude-only approach is pragmatic + +**Loss function:** +``` +L = L_H + lambda * L_B +L_H = SmoothL1(predicted_keypoints, ground_truth, beta=0.1) +L_B = sum of bone length constraint violations across 14 bone connections +lambda = 0.2 +``` + +The bone constraint loss is particularly important for edge deployment where noisy predictions need physical plausibility enforcement. + +#### Adaptation for ESP32 + Pi Zero + +WiFlow's architecture maps well to our hardware: +- TCN runs on ESP32 (temporal feature extraction from raw CSI stream) +- Asymmetric conv + axial attention runs on Pi Zero (spatial encoding + pose regression) +- The 540-dimensional input assumes Intel 5300 NIC (18 links x 30 subcarriers); for ESP32-S3 with 1 TX x 1 RX and 52 subcarriers, input dimension is 52x20 = 1040 -- even smaller + +### 1.2 MultiFormer: Multi-Person WiFi Pose (May 2025) + +**Paper:** MultiFormer: A Multi-Person Pose Estimation System Based on CSI and Attention Mechanism ([arXiv:2505.22555](https://arxiv.org/html/2505.22555v1)) + +#### Architecture + +Teacher-student framework with OpenPose teacher providing ground truth labels. + +**Time-Frequency Dual-Dimensional Tokenization (TFDDT):** +- Input: CSI matrix from 1 TX, 3 RX, 30 subcarriers +- Upsampled via zero-insertion + low-pass filtering to 64x3x64 +- Two parallel token streams: + - Frequency tokens F_j: N_S tokens of length M x N_R (subcarrier-centric view) + - Temporal tokens T_i: M tokens of length N_S x N_R (time-centric view) + +**Dual Transformer Encoder:** +- 8 layers per branch (frequency and temporal) +- Multi-head self-attention: MSA(X) = (1/H) * sum(Softmax(QK^T / sqrt(d_k)) V) +- Each branch followed by FFN with ReLU, dropout, residual connections + +**Multi-Stage Pose Estimation:** +- Part Confidence Maps (PCM): 19x36x36 heatmaps (18 keypoints + average) +- Part Affinity Fields (PAF): 38x36x36 directional fields for 19 limb connections +- Pose-Attentive Perception Module (PAPM): channel + spatial attention on PCM/PAF +- Multi-person assignment via Hungarian algorithm on PAF integrals + +#### Model Variants + +| Variant | Encoder Layers | Input | Parameters | +|---------|---------------|-------|------------| +| MultiFormer | 8 | 64x1296 | 11.93M | +| MultiFormer-24 | 8 | 64x576 | 4.05M | +| MultiFormer-18 | 6 | 64x324 | **2.80M** | + +**Key result on MM-Fi dataset:** MultiFormer achieves PCK@20 of 0.7225, outperforming CSI2Pose (0.6841). The compact MultiFormer-18 at 2.80M parameters is edge-deployable. + +#### Relevance to Our Project + +MultiFormer's dual-token approach is valuable because: +1. It explicitly separates temporal and frequency information (like WiFlow's decoupling) +2. The PAF-based multi-person assignment using Hungarian algorithm can run on Pi Zero +3. The 2.80M parameter variant (MultiFormer-18) at INT8 = ~2.8 MB, well within Pi Zero constraints + +### 1.3 Person-in-WiFi 3D (CVPR 2024) + +**Paper:** Person-in-WiFi 3D: End-to-End Multi-Person 3D Pose Estimation with Wi-Fi (CVPR 2024) + +First multi-person 3D WiFi pose estimation. + +**Key results:** +- Single person MPJPE: 91.7mm +- Two persons: 108.1mm +- Three persons: 125.3mm +- Dataset: 97K frames, 4m x 3.5m area, 7 volunteers +- Transformer-based end-to-end architecture + +**Relevance:** Establishes the accuracy ceiling for WiFi 3D pose. Our ESP32+Pi system should target comparable single-person performance (sub-100mm MPJPE) as a milestone. + +### 1.4 Spatio-Temporal 3D Point Clouds from WiFi-CSI (October 2024) + +**Paper:** [arXiv:2410.16303](https://arxiv.org/html/2410.16303v1) + +Novel approach: generates 3D point clouds from WiFi CSI data using transformer networks. + +**Key innovation:** Positional encoding with learned embeddings for antennas and subcarriers, followed by multi-head attention over antenna-subcarrier pairs. This captures both spatial (antenna geometry) and spectral (subcarrier frequency response) dependencies. + +**Relevance:** Point cloud output is a richer representation than keypoints alone, enabling: +- Silhouette estimation for activity recognition +- Body volume estimation for person identification +- Occlusion reasoning when fused with multiple viewpoints + +### 1.5 Graph-Based 3D Human Pose from WiFi (November 2025) + +**Paper:** Graph-based 3D Human Pose Estimation using WiFi Signals ([arXiv:2511.19105](https://arxiv.org/html/2511.19105)) + +Uses graph neural networks where nodes represent keypoints and edges represent skeletal connections. CSI features are injected as node/edge attributes. + +**Relevance:** Graph structure naturally maps to our RuvSense pose_tracker which already maintains a 17-keypoint skeleton with Kalman filtering. Adding graph-based message passing between keypoints could improve joint prediction coherence. + +## 2. Edge Deployment Landscape + +### 2.1 CSI-Sense-Zero: ESP32 + Pi Zero Reference Implementation + +**Repository:** [github.com/winwinashwin/CSI-Sense-Zero](https://github.com/winwinashwin/CSI-Sense-Zero) + +The most directly relevant prior art for our hardware target. + +**Architecture:** +- Two ESP32-WROOM-32: one TX, one RX (captures CSI) +- Pi Zero: inference node +- Communication: USB serial at 921,600 baud +- Buffer: 235KB FIFO at `/tmp/csififo` (~256 CSI records) +- Inference rate: 2 Hz (configurable) +- WebSocket output for real-time visualization + +**Data flow:** +``` +ESP32 TX -> WiFi signal -> ESP32 RX -> Serial (921.6 kbaud) -> Pi Zero FIFO -> Model -> WebSocket +``` + +**Limitations:** +- Original Pi Zero (single-core ARM11) -- very slow inference +- Activity recognition only (not pose estimation) +- Python inference (not optimized for ARM) + +**What we improve:** +- Pi Zero 2 W has quad-core Cortex-A53 -- roughly 5-10x faster than Pi Zero +- Rust inference (ONNX/Candle) vs Python -- 3-10x faster +- ESP32-S3 vs ESP32-WROOM-32 -- better CSI quality, more subcarriers +- Pose estimation instead of just activity classification +- UDP transport instead of USB serial -- supports multi-node mesh + +### 2.2 OnnxStream: Lightweight ONNX on Pi Zero 2 W + +**Repository:** [github.com/vitoplantamura/OnnxStream](https://github.com/vitoplantamura/OnnxStream) + +Runs Stable Diffusion XL on Pi Zero 2 W in 298 MB RAM. Key features: +- C++ implementation, XNNPACK acceleration +- ARM NEON SIMD optimization +- Memory-efficient streaming execution (processes one operator at a time) +- Supports INT8 quantization + +**Benchmark estimates for our model sizes:** + +| Model | Parameters | INT8 Size | Est. Pi Zero 2 Latency | +|-------|-----------|-----------|----------------------| +| MultiFormer-18 | 2.80M | ~2.8 MB | ~30-50ms | +| WiFlow | 4.82M | ~4.8 MB | ~50-80ms | +| MultiFormer | 11.93M | ~11.9 MB | ~120-200ms | +| DensePose-WiFi | ~25M (est.) | ~25 MB | ~300-500ms | + +These estimates assume XNNPACK-accelerated INT8 inference on Cortex-A53 @ 1 GHz. The WiFlow and MultiFormer-18 models can achieve 12-20 Hz inference, matching our 20 Hz TDMA cycle target. + +### 2.3 ONNX Runtime on ARM + +ONNX Runtime officially supports Raspberry Pi deployment with: +- ARM NEON execution provider +- INT8 quantization support +- Python and C++ APIs +- Model optimization tools (graph optimization, operator fusion) + +For Rust integration, the `ort` crate (ONNX Runtime Rust bindings) supports cross-compilation to aarch64-linux-gnu. + +### 2.4 EfficientFi: CSI Compression for Edge + +**Paper:** EfficientFi: Towards Large-Scale Lightweight WiFi Sensing via CSI Compression ([arXiv:2204.04138](https://arxiv.org/pdf/2204.04138)) + +Proposes compressing CSI data on the sensing device before transmission to the inference node. Key idea: train a CSI autoencoder where the encoder runs on the constrained device and the decoder runs on the more powerful inference node. + +**Relevance:** For our ESP32 -> Pi Zero pipeline, CSI compression on ESP32 reduces: +- UDP packet size (lower bandwidth, less packet loss) +- Pi Zero preprocessing time (compressed features are more compact) +- Effective latency (less data to transmit per frame) + +## 3. Comparative Analysis: Architecture Selection for ESP32 + Pi Zero + +### 3.1 Decision Matrix + +| Criterion | WiFlow | MultiFormer-18 | DensePose-WiFi | Graph-3D | +|-----------|--------|----------------|----------------|----------| +| Parameters | 4.82M | 2.80M | ~25M | ~8M (est.) | +| FLOPs | 0.47B | ~0.3B (est.) | ~5B (est.) | ~1B (est.) | +| Multi-person | No | Yes (PAF+Hungarian) | Yes (RCNN-based) | No | +| 3D output | No (2D) | No (2D) | No (UV map) | Yes (3D) | +| Amplitude-only | Yes | Yes | No (amp+phase) | Unknown | +| Edge-viable | Yes | Yes | No | Marginal | +| Open source | Not yet | Not yet | Limited | Not yet | + +### 3.2 Recommended Architecture: Hybrid WiFlow + MultiFormer + +For the ESP32 + Pi Zero deployment, we recommend a hybrid architecture: + +1. **WiFlow's TCN temporal encoder** on ESP32 -- extract temporal features from raw CSI +2. **MultiFormer's dual-token approach** on Pi Zero -- process both frequency and temporal views +3. **WiFlow's bone constraint loss** during training -- enforce physical skeleton plausibility +4. **RuvSense coherence gating** before inference -- reject low-quality CSI frames + +This hybrid achieves: +- ~3-5M parameters (between WiFlow and MultiFormer-18) +- Amplitude-only input (robust to ESP32 CFO/SFO) +- Sub-100ms inference on Pi Zero 2 W +- Optional multi-person support via PAF module + +### 3.3 Training Data Strategy + +Based on the surveyed papers: + +| Dataset | Subjects | Frames | Hardware | Availability | +|---------|----------|--------|----------|--------------| +| CMU DensePose-WiFi | 8 | ~250K | Intel 5300 | Limited | +| Person-in-WiFi 3D | 7 | 97K | Custom WiFi | GitHub | +| MM-Fi | Multiple | Large | WiFi + mmWave | Public | +| Wi-Pose | Multiple | Large | Intel 5300 | Public | + +**Our approach:** +1. Pre-train on MM-Fi/Wi-Pose public datasets (Intel 5300 CSI format) +2. Apply domain adaptation for ESP32-S3 CSI format (different subcarrier count, CFO characteristics) +3. Fine-tune on self-collected ESP32-S3 data in target environments +4. Augment with synthetic CSI from ray-tracing forward model (Arena Physica insight) + +## 4. Gap Analysis: Current wifi-densepose vs SOTA + +### 4.1 What We Have + +| Capability | Status | Module | +|-----------|--------|--------| +| ESP32 CSI capture | Production | `wifi-densepose-hardware` | +| Multi-node fusion | Production | `ruvsense/multistatic.rs` | +| Phase alignment | Production | `ruvsense/phase_align.rs` | +| Coherence gating | Production | `ruvsense/coherence_gate.rs` | +| 17-keypoint tracking | Production | `ruvsense/pose_tracker.rs` | +| ONNX inference engine | Production | `wifi-densepose-nn` | +| Modality translator | Production | `wifi-densepose-nn/translator.rs` | +| Training pipeline | Production | `wifi-densepose-train` | +| Subcarrier interpolation | Production | `wifi-densepose-train/subcarrier.rs` | + +### 4.2 What We Are Missing + +| Gap | Required For | Priority | +|-----|-------------|----------| +| **Pi Zero deployment target** | Edge inference node | Critical | +| **Lightweight model architecture** | Sub-100ms inference on Cortex-A53 | Critical | +| **Temporal causal convolution** | Real-time streaming inference | High | +| **Axial attention module** | Efficient spatial encoding | High | +| **Bone constraint loss** | Physical plausibility | High | +| **CSI compression on ESP32** | Bandwidth reduction | Medium | +| **INT8 quantization pipeline** | Model size reduction | Medium | +| **Cross-environment adaptation** | Deployment generalization | Medium | +| **Multi-person PAF decoding** | Multiple subject support | Low (Phase 2) | +| **3D pose lifting** | Z-axis estimation | Low (Phase 3) | +| **Diffusion-based pose refinement** | Uncertainty quantification | Research | + +### 4.3 Architecture Gaps in Detail + +**1. No lightweight inference path.** The current `wifi-densepose-nn` crate assumes GPU or high-end CPU inference. We need an `EdgeInferenceEngine` optimized for: +- INT8 ONNX models +- ARM NEON SIMD via XNNPACK +- Streaming inference (process CSI frames as they arrive, not in batches) +- Memory-mapped model loading (avoid loading entire model into RAM) + +**2. No ESP32 -> Pi Zero communication protocol.** The `wifi-densepose-hardware` crate handles ESP32 CSI capture and UDP aggregation to a server, but has no lightweight protocol for ESP32 -> Pi Zero direct communication. We need: +- Compact binary frame format (not the full ADR-018 format) +- Optional CSI compression (autoencoder on ESP32 or simple PCA) +- Heartbeat and synchronization for multi-ESP32 setups + +**3. No temporal convolution module.** The existing signal processing pipeline uses frame-by-frame processing. WiFlow and MultiFormer both show that temporal context (20 frames for WiFlow, 64 frames for MultiFormer) significantly improves accuracy. We need a ring buffer + TCN module in the inference path. + +**4. No bone/skeleton constraint enforcement at inference time.** The `pose_tracker.rs` has Kalman filtering and skeleton constraints, but these are post-hoc corrections. WiFlow shows that baking bone constraints into the loss function during training produces better models that need less post-processing. + +## 5. References + +1. DensePose From WiFi, Geng et al., arXiv:2301.00250, 2023 +2. Person-in-WiFi 3D, Yan et al., CVPR 2024 +3. WiFlow, arXiv:2602.08661, 2026 +4. MultiFormer, arXiv:2505.22555, 2025 +5. CSI-Channel Spatial Decomposition, MDPI Electronics 14(4), 2025 +6. CSI-Former, MDPI Entropy 25(1), 2023 +7. Spatio-Temporal 3D Point Clouds from WiFi-CSI, arXiv:2410.16303, 2024 +8. Graph-based 3D Human Pose from WiFi, arXiv:2511.19105, 2025 +9. EfficientFi, arXiv:2204.04138, 2022 +10. CSI-Sense-Zero, github.com/winwinashwin/CSI-Sense-Zero +11. OnnxStream, github.com/vitoplantamura/OnnxStream +12. Arena Physica, arenaphysica.com (Atlas RF Studio, Heaviside-0/Marconi-0) +13. Tools and Methods for WiFi Sensing in Embedded Devices, MDPI Sensors 25(19), 2025 +14. Real-Time HAR using WiFi CSI and LSTM on Edge Devices, SASI-ITE 2025 diff --git a/docs/research/wifi-sensing-ruvector-sota-2026.md b/docs/research/sota-surveys/wifi-sensing-ruvector-sota-2026.md similarity index 100% rename from docs/research/wifi-sensing-ruvector-sota-2026.md rename to docs/research/sota-surveys/wifi-sensing-ruvector-sota-2026.md diff --git a/examples/happiness-vector/provision_swarm.sh b/examples/happiness-vector/provision_swarm.sh index 9295b248..0687f5ac 100644 --- a/examples/happiness-vector/provision_swarm.sh +++ b/examples/happiness-vector/provision_swarm.sh @@ -15,10 +15,10 @@ set -euo pipefail # ---- Configuration ---- -SSID="RedCloverWifi" -PASSWORD="redclover2.4" -SEED_URL="http://10.1.10.236" -SEED_TOKEN="hyHVY4Ux6uBAh8FaQzF_9OwWCWMFB-YuM2OJ3Dcwdm8" # Replace with your token +SSID="${SWARM_WIFI_SSID:?Set SWARM_WIFI_SSID env var}" +PASSWORD="${SWARM_WIFI_PASSWORD:?Set SWARM_WIFI_PASSWORD env var}" +SEED_URL="${SWARM_SEED_URL:?Set SWARM_SEED_URL env var}" +SEED_TOKEN="${SWARM_SEED_TOKEN:?Set SWARM_SEED_TOKEN env var}" PROVISION="../../firmware/esp32-csi-node/provision.py" diff --git a/firmware/esp32-csi-node/main/edge_processing.c b/firmware/esp32-csi-node/main/edge_processing.c index 81c19365..3a593540 100644 --- a/firmware/esp32-csi-node/main/edge_processing.c +++ b/firmware/esp32-csi-node/main/edge_processing.c @@ -276,6 +276,9 @@ static uint8_t s_prev_iq[EDGE_MAX_IQ_BYTES]; static uint16_t s_prev_iq_len; static bool s_has_prev_iq; +/** ADR-069: Feature vector sequence counter. */ +static uint16_t s_feature_seq; + /** Multi-person vitals state. */ static edge_person_vitals_t s_persons[EDGE_MAX_PERSONS]; static edge_biquad_t s_person_bq_br[EDGE_MAX_PERSONS]; @@ -410,10 +413,10 @@ static uint16_t delta_compress(const uint8_t *curr, uint16_t len, } /** - * Send a compressed CSI frame (magic 0xC5110003). + * Send a compressed CSI frame (magic 0xC5110005, reassigned from 0xC5110003 for ADR-069). * * Header: - * [0..3] Magic 0xC5110003 (LE) + * [0..3] Magic 0xC5110005 (LE) * [4] Node ID * [5] Channel * [6..7] Original I/Q length (LE u16) @@ -634,6 +637,70 @@ static void send_vitals_packet(void) } } +/* ====================================================================== + * ADR-069: Feature Vector Packet (48 bytes, sent at 1 Hz alongside vitals) + * ====================================================================== */ + +static void send_feature_vector(void) +{ + edge_feature_pkt_t pkt; + memset(&pkt, 0, sizeof(pkt)); + + pkt.magic = EDGE_FEATURE_MAGIC; + pkt.node_id = g_nvs_config.node_id; + pkt.reserved = 0; + pkt.seq = s_feature_seq++; + pkt.timestamp_us = esp_timer_get_time(); + + /* Dim 0: Presence score (0.0-1.0, normalized from raw score) */ + float p = s_presence_score; + pkt.features[0] = p > 10.0f ? 1.0f : (p < 0.0f ? 0.0f : p / 10.0f); + + /* Dim 1: Motion energy (normalized, 0-1 range) */ + float m = s_motion_energy; + pkt.features[1] = m > 10.0f ? 1.0f : (m < 0.0f ? 0.0f : m / 10.0f); + + /* Dim 2: Breathing rate (BPM / 30, 0-1 range) */ + pkt.features[2] = s_breathing_bpm > 0.0f + ? (s_breathing_bpm / 30.0f > 1.0f ? 1.0f : s_breathing_bpm / 30.0f) + : 0.0f; + + /* Dim 3: Heart rate (BPM / 120, 0-1 range) */ + pkt.features[3] = s_heartrate_bpm > 0.0f + ? (s_heartrate_bpm / 120.0f > 1.0f ? 1.0f : s_heartrate_bpm / 120.0f) + : 0.0f; + + /* Dim 4: Phase variance mean (top-K subcarriers) */ + float var_mean = 0.0f; + if (s_top_k_count > 0) { + float var_sum = 0.0f; + uint8_t k = s_top_k_count < EDGE_TOP_K ? s_top_k_count : EDGE_TOP_K; + for (uint8_t i = 0; i < k; i++) { + var_sum += (float)welford_variance(&s_subcarrier_var[s_top_k[i]]); + } + var_mean = var_sum / (float)k; + } + pkt.features[4] = var_mean > 1.0f ? 1.0f : (var_mean < 0.0f ? 0.0f : var_mean); + + /* Dim 5: Person count (n_persons / 4, 0-1 range) */ + uint8_t n_active = 0; + for (uint8_t i = 0; i < EDGE_MAX_PERSONS; i++) { + if (s_persons[i].active) n_active++; + } + pkt.features[5] = (float)n_active / 4.0f; + if (pkt.features[5] > 1.0f) pkt.features[5] = 1.0f; + + /* Dim 6: Fall risk (0.0 or 1.0 based on recent detection) */ + pkt.features[6] = s_fall_detected ? 1.0f : 0.0f; + + /* Dim 7: RSSI normalized ((rssi + 100) / 100, 0-1 range) */ + pkt.features[7] = ((float)s_latest_rssi + 100.0f) / 100.0f; + if (pkt.features[7] > 1.0f) pkt.features[7] = 1.0f; + if (pkt.features[7] < 0.0f) pkt.features[7] = 0.0f; + + stream_sender_send((const uint8_t *)&pkt, sizeof(pkt)); +} + /* ====================================================================== * Main DSP Pipeline (runs on Core 1) * ====================================================================== */ @@ -788,6 +855,7 @@ static void process_frame(const edge_ring_slot_t *slot) int64_t interval_us = (int64_t)s_cfg.vital_interval_ms * 1000; if ((now_us - s_last_vitals_send_us) >= interval_us) { send_vitals_packet(); + send_feature_vector(); /* ADR-069: 48-byte feature vector at same 1 Hz cadence. */ s_last_vitals_send_us = now_us; if ((s_frame_count % 200) == 0) { diff --git a/firmware/esp32-csi-node/main/edge_processing.h b/firmware/esp32-csi-node/main/edge_processing.h index 26164d7b..6af25685 100644 --- a/firmware/esp32-csi-node/main/edge_processing.h +++ b/firmware/esp32-csi-node/main/edge_processing.h @@ -26,7 +26,7 @@ /* ---- Magic numbers ---- */ #define EDGE_VITALS_MAGIC 0xC5110002 /**< Vitals packet magic. */ -#define EDGE_COMPRESSED_MAGIC 0xC5110003 /**< Compressed frame magic. */ +#define EDGE_COMPRESSED_MAGIC 0xC5110005 /**< Compressed frame magic (was 0xC5110003, reassigned for ADR-069). */ /* ---- Buffer sizes ---- */ #define EDGE_RING_SLOTS 16 /**< SPSC ring buffer slots (power of 2). */ @@ -109,6 +109,20 @@ typedef struct __attribute__((packed)) { _Static_assert(sizeof(edge_vitals_pkt_t) == 32, "vitals packet must be 32 bytes"); +/* ---- ADR-069: CSI Feature Vector packet (48 bytes, wire format) ---- */ +#define EDGE_FEATURE_MAGIC 0xC5110003 /**< Feature vector packet magic. */ + +typedef struct __attribute__((packed)) { + uint32_t magic; /**< EDGE_FEATURE_MAGIC = 0xC5110003. */ + uint8_t node_id; /**< ESP32 node identifier. */ + uint8_t reserved; /**< Alignment padding. */ + uint16_t seq; /**< Sequence number. */ + int64_t timestamp_us; /**< Microseconds since boot. */ + float features[8]; /**< 8-dim normalized feature vector. */ +} edge_feature_pkt_t; + +_Static_assert(sizeof(edge_feature_pkt_t) == 48, "feature packet must be 48 bytes"); + /* ---- ADR-063: Fused vitals packet (48 bytes, wire format) ---- */ #define EDGE_FUSED_MAGIC 0xC5110004 /**< Fused vitals packet magic. */ diff --git a/firmware/esp32-csi-node/nvs_config.bin b/firmware/esp32-csi-node/nvs_config.bin deleted file mode 100644 index 36ceefc8..00000000 --- a/firmware/esp32-csi-node/nvs_config.bin +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/firmware/esp32-csi-node/nvs_wifi.bin b/firmware/esp32-csi-node/nvs_wifi.bin deleted file mode 100644 index a950adff..00000000 Binary files a/firmware/esp32-csi-node/nvs_wifi.bin and /dev/null differ diff --git a/scripts/release-v0.5.4.sh b/scripts/release-v0.5.4.sh new file mode 100644 index 00000000..d1c79168 --- /dev/null +++ b/scripts/release-v0.5.4.sh @@ -0,0 +1,61 @@ +#!/bin/bash +# Release script for v0.5.4-esp32 +# Run AFTER firmware build completes and all tests pass +# +# Prerequisites: +# - firmware/esp32-csi-node/build/esp32-csi-node.bin (8MB build) +# - All Rust tests passing (1,031+) +# - Python proof VERDICT: PASS +# +# Usage: bash scripts/release-v0.5.4.sh + +set -euo pipefail + +TAG="v0.5.4-esp32" +BUILD_DIR="firmware/esp32-csi-node/build" +DIST_DIR="dist/${TAG}" + +echo "=== Preparing release ${TAG} ===" + +# Verify build artifacts exist +for f in \ + "${BUILD_DIR}/esp32-csi-node.bin" \ + "${BUILD_DIR}/bootloader/bootloader.bin" \ + "${BUILD_DIR}/partition_table/partition-table.bin" \ + "${BUILD_DIR}/ota_data_initial.bin"; do + if [ ! -f "$f" ]; then + echo "ERROR: Missing build artifact: $f" + echo "Run the firmware build first." + exit 1 + fi +done + +# Create dist directory +mkdir -p "${DIST_DIR}" + +# Copy binaries +cp "${BUILD_DIR}/esp32-csi-node.bin" "${DIST_DIR}/" +cp "${BUILD_DIR}/bootloader/bootloader.bin" "${DIST_DIR}/" +cp "${BUILD_DIR}/partition_table/partition-table.bin" "${DIST_DIR}/" +cp "${BUILD_DIR}/ota_data_initial.bin" "${DIST_DIR}/" + +# Generate SHA-256 hashes +echo "=== SHA-256 Hashes ===" +cd "${DIST_DIR}" +sha256sum *.bin > SHA256SUMS.txt +cat SHA256SUMS.txt +cd - + +# Binary sizes +echo "" +echo "=== Binary Sizes ===" +ls -lh "${DIST_DIR}"/*.bin + +echo "" +echo "=== Release artifacts ready in ${DIST_DIR} ===" +echo "" +echo "Next steps:" +echo " 1. Flash to COM9: esptool.py --chip esp32s3 --port COM9 write_flash 0x0 ${DIST_DIR}/bootloader.bin 0x8000 ${DIST_DIR}/partition-table.bin 0xd000 ${DIST_DIR}/ota_data_initial.bin 0x10000 ${DIST_DIR}/esp32-csi-node.bin" +echo " 2. Tag: git tag ${TAG}" +echo " 3. Push: git push origin ${TAG}" +echo " 4. Release: gh release create ${TAG} ${DIST_DIR}/*.bin ${DIST_DIR}/SHA256SUMS.txt --title 'ESP32-S3 CSI Firmware ${TAG} — Cognitum Seed Integration' --notes-file -" diff --git a/scripts/seed_csi_bridge.py b/scripts/seed_csi_bridge.py new file mode 100644 index 00000000..c786286f --- /dev/null +++ b/scripts/seed_csi_bridge.py @@ -0,0 +1,656 @@ +#!/usr/bin/env python3 +""" +ADR-069: ESP32 CSI → Cognitum Seed RVF Ingest Bridge + +Listens for CSI feature vectors from ESP32 nodes via UDP, batches them, +and ingests into the Cognitum Seed's RVF vector store via HTTPS REST API. + +Usage: + # Run bridge (default mode) + python scripts/seed_csi_bridge.py \ + --seed-url https://169.254.42.1:8443 \ + --token "$SEED_TOKEN" \ + --udp-port 5006 \ + --batch-size 10 + + # Run with validation (kNN query + PIR comparison after each batch) + python scripts/seed_csi_bridge.py \ + --token TOKEN --validate + + # Print Seed stats + python scripts/seed_csi_bridge.py --token TOKEN --stats + + # Trigger store compaction + python scripts/seed_csi_bridge.py --token TOKEN --compact + +The bridge also accepts legacy ADR-018 CSI frames (magic 0xC5110001/0xC5110002) +and extracts a simplified 8-dim feature vector from the raw data. +""" + +from __future__ import annotations + +import argparse +import hashlib +import json +import logging +import os +import socket +import struct +import sys +import time +import urllib.error +import urllib.request +import math +import ssl + +logging.basicConfig( + level=logging.INFO, + format="%(asctime)s [%(levelname)s] %(message)s", + datefmt="%H:%M:%S", +) +log = logging.getLogger("seed-bridge") + +# Packet magic numbers +MAGIC_CSI_RAW = 0xC5110001 # ADR-018 raw CSI frame +MAGIC_VITALS = 0xC5110002 # ADR-039 vitals packet +MAGIC_FEATURES = 0xC5110003 # ADR-069 feature vector (new) + +# Feature vector packet: 4 + 1 + 1 + 2 + 8 + 32 = 48 bytes +FEATURE_PKT_FMT = " dict | None: + """Parse an ADR-069 feature vector packet.""" + if len(data) < FEATURE_PKT_SIZE: + return None + magic, node_id, _, seq, ts, *features = struct.unpack_from(FEATURE_PKT_FMT, data) + if magic != MAGIC_FEATURES: + return None + # Reject NaN/inf in raw feature values before they reach the vector store + for i, f in enumerate(features): + if math.isnan(f) or math.isinf(f): + log.warning("Dropping feature packet: features[%d]=%s (NaN/inf)", i, f) + return None + return { + "node_id": node_id, + "seq": seq, + "timestamp_us": ts, + "features": features, + } + + +def parse_vitals_packet(data: bytes) -> dict | None: + """Parse an ADR-039 vitals packet and extract an 8-dim feature vector.""" + if len(data) < VITALS_PKT_SIZE: + return None + try: + fields = struct.unpack_from(VITALS_PKT_FMT, data) + except struct.error: + return None + magic = fields[0] + if magic != MAGIC_VITALS: + return None + node_id = fields[1] + flags = fields[2] + breathing_rate_raw = fields[3] # BPM * 100 + heartrate_raw = fields[4] # BPM * 10000 + rssi = fields[5] # int8 + n_persons = fields[6] + motion_energy = fields[7] # float + presence_score = fields[8] # float + timestamp_ms = fields[9] + + # Reject NaN/inf in raw float fields before clamping (clamp masks NaN) + if math.isnan(motion_energy) or math.isinf(motion_energy): + log.warning("Dropping vitals packet: motion_energy=%s (NaN/inf)", motion_energy) + return None + if math.isnan(presence_score) or math.isinf(presence_score): + log.warning("Dropping vitals packet: presence_score=%s (NaN/inf)", presence_score) + return None + + # Convert from fixed-point + br_bpm = breathing_rate_raw / 100.0 + hr_bpm = heartrate_raw / 10000.0 + presence = (flags & 0x01) != 0 + fall = (flags & 0x02) != 0 + motion = (flags & 0x04) != 0 + + # Normalize to 0.0-1.0 range for 8-dim RVF vector. + # Live readings show presence_score in 0-15 range and motion_energy in 0-10 range, + # so divide by their respective maxima before clamping. + features = [ + max(0.0, min(1.0, presence_score / 15.0)), # dim 0: presence score (raw 0-15) + max(0.0, min(1.0, motion_energy / 10.0)), # dim 1: motion level (raw 0-10) + max(0.0, min(1.0, br_bpm / 30.0)) if br_bpm > 0 else 0.0, # dim 2: breathing rate + max(0.0, min(1.0, hr_bpm / 120.0)) if hr_bpm > 0 else 0.0, # dim 3: heart rate + 0.5, # dim 4: phase variance (future) + float(n_persons) / 4.0 if n_persons <= 4 else 1.0, # dim 5: person count + 1.0 if fall else 0.0, # dim 6: fall detected + max(0.0, min(1.0, (rssi + 100) / 100.0)), # dim 7: RSSI normalized + ] + return { + "node_id": node_id, + "seq": timestamp_ms, + "timestamp_us": int(time.time() * 1_000_000), + "features": features, + } + + +def parse_raw_csi_packet(data: bytes) -> dict | None: + """Parse an ADR-018 raw CSI frame and extract basic features.""" + if len(data) < 8: + return None + magic = struct.unpack_from(" 4 else 0 + rssi = struct.unpack_from("b", data, 5)[0] if len(data) > 5 else -70 + # Minimal feature vector from raw CSI -- mostly placeholder + features = [0.5, 0.5, 0.0, 0.0, 0.5, 0.5, 0.0, max(0.0, min(1.0, (rssi + 100) / 100.0))] + return { + "node_id": node_id, + "seq": 0, + "timestamp_us": int(time.time() * 1_000_000), + "features": features, + } + + +def _validate_features(parsed: dict | None) -> dict | None: + """Reject packets with NaN, inf, or out-of-range feature values.""" + if parsed is None: + return None + features = parsed.get("features") + if features is None: + return None + for i, f in enumerate(features): + if math.isnan(f) or math.isinf(f): + log.warning("Dropping packet: feature[%d] = %s (NaN/inf)", i, f) + return None + return parsed + + +def parse_packet(data: bytes) -> dict | None: + """Try all packet formats.""" + if len(data) < 4: + return None + magic = struct.unpack_from(" int: + """Generate a unique vector ID from node_id + timestamp + sequence counter. + + Uses a hash to produce a non-negative 32-bit integer, avoiding the + content-addressed deduplication that occurs when all vectors use ID 0. + """ + key = f"{node_id}:{timestamp_us}:{seq_counter}".encode() + digest = hashlib.sha256(key).digest() + # Take first 4 bytes as unsigned 32-bit int + return struct.unpack(" dict: + """Issue an HTTP request and return parsed JSON. + + Raises urllib.error.URLError on connection failure, + urllib.error.HTTPError on non-2xx status, and + ValueError on non-JSON response body. + """ + url = f"{self.base_url}{path}" + data = json.dumps(body).encode() if body is not None else None + headers = {"Content-Type": "application/json"} + if auth: + headers["Authorization"] = f"Bearer {self.token}" + req = urllib.request.Request(url, data=data, headers=headers, method=method) + with urllib.request.urlopen(req, context=self.ctx, timeout=timeout) as resp: + raw = resp.read() + try: + return json.loads(raw) + except (json.JSONDecodeError, ValueError) as exc: + raise ValueError( + f"Non-JSON response from {method} {path} " + f"(status {resp.status}): {raw[:200]!r}" + ) from exc + + def ingest(self, vectors: list[tuple[int, list[float]]]) -> dict: + """Ingest vectors into the RVF store.""" + return self._request("POST", "/api/v1/store/ingest", {"vectors": vectors}) + + def query(self, vector: list[float], k: int = 5) -> dict: + """Query kNN for a vector.""" + return self._request("POST", "/api/v1/store/query", {"vector": vector, "k": k}) + + def compact(self) -> dict: + """Trigger store compaction.""" + return self._request("POST", "/api/v1/store/compact") + + def status(self) -> dict: + """Get device status.""" + return self._request("GET", "/api/v1/status", auth=False, timeout=5) + + def boundary(self) -> dict: + """Get boundary analysis (fragility score).""" + return self._request("GET", "/api/v1/boundary", auth=False, timeout=5) + + def coherence_profile(self) -> dict: + """Get coherence profile.""" + return self._request("GET", "/api/v1/coherence/profile", auth=False, timeout=5) + + def graph_stats(self) -> dict: + """Get kNN graph stats.""" + return self._request("GET", "/api/v1/store/graph/stats", auth=False, timeout=5) + + def read_pir(self, pin: int = 6) -> dict | None: + """Read PIR sensor GPIO. Returns None if not available (404).""" + try: + return self._request("GET", f"/api/v1/sensor/gpio/read?pin={pin}", + auth=False, timeout=5) + except urllib.error.HTTPError as e: + if e.code == 404: + return None + raise + except Exception: + return None + + def verify_witness(self) -> dict: + """Verify witness chain integrity.""" + return self._request("POST", "/api/v1/witness/verify", timeout=10) + + +def _flush_batch(seed: SeedClient, batch: list, stats: dict, + validate: bool = False, validation_stats: dict | None = None, + last_features: list[float] | None = None) -> None: + """Ingest a batch of vectors into the Seed, with optional retry and validation.""" + max_attempts = 2 + for attempt in range(max_attempts): + try: + result = seed.ingest(batch) + accepted = result.get("count", 0) + epoch = result.get("new_epoch", "?") + stats["ingested"] += accepted + stats["batches"] += 1 + log.info( + "Ingested %d vectors (epoch=%s, witness=%s)", + accepted, + epoch, + str(result.get("witness_head", "?"))[:16] + "...", + ) + break # success + except Exception as e: + if attempt == 0: + log.warning("Ingest failed (attempt 1/2), retrying in 2s: %s", e) + time.sleep(2.0) + else: + stats["errors"] += 1 + log.error("Ingest failed after retry: %s", e) + return # skip validation on failure + + # Validation: query the most recent vector and check kNN result + if validate and last_features is not None and validation_stats is not None: + _run_validation(seed, last_features, validation_stats) + + +def _run_validation(seed: SeedClient, features: list[float], + validation_stats: dict) -> None: + """Query kNN for the most recent vector and compare with PIR sensor.""" + try: + qr = seed.query(features, k=1) + results = qr.get("results", []) + if results: + dist = results[0].get("distance", -1) + validation_stats["queries"] += 1 + if dist <= 0.01: + validation_stats["exact_matches"] += 1 + log.info("Validation: kNN distance=%.6f (exact match)", dist) + else: + log.info("Validation: kNN distance=%.6f (approximate)", dist) + else: + log.warning("Validation: kNN returned empty results") + except Exception as e: + log.warning("Validation query failed: %s", e) + + # PIR ground truth comparison + csi_presence = features[0] # dim 0 is presence score + csi_present = csi_presence > 0.3 # threshold for "someone present" + try: + pir = seed.read_pir(pin=6) + if pir is not None: + pir_state = bool(pir.get("value", 0)) + validation_stats["pir_readings"] += 1 + if csi_present == pir_state: + validation_stats["pir_agreements"] += 1 + rate = (validation_stats["pir_agreements"] / validation_stats["pir_readings"] * 100 + if validation_stats["pir_readings"] > 0 else 0) + log.info( + "PIR=%s CSI_presence=%.2f (%s) — agreement %.1f%% (%d/%d)", + "HIGH" if pir_state else "LOW", + csi_presence, + "present" if csi_present else "absent", + rate, + validation_stats["pir_agreements"], + validation_stats["pir_readings"], + ) + except Exception: + pass # PIR not available, already handled gracefully + + +def run_bridge(args): + """Main bridge loop: UDP -> batch -> HTTPS ingest.""" + seed = SeedClient(args.seed_url, args.token) + + # Verify connectivity + try: + status = seed.status() + log.info( + "Connected to Seed %s — %d vectors, epoch %d, dim %d", + status["device_id"][:8], + status["total_vectors"], + status["epoch"], + status["dimension"], + ) + except Exception as e: + log.error("Cannot connect to Seed at %s: %s", args.seed_url, e) + sys.exit(1) + + # Parse allowed source IPs for UDP filtering (anti-spoofing) + allowed_sources: set[str] | None = None + if args.allowed_sources: + allowed_sources = set(ip.strip() for ip in args.allowed_sources.split(",") if ip.strip()) + log.info("UDP source filter: only accepting packets from %s", allowed_sources) + + # Open UDP listener + sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + sock.bind(("0.0.0.0", args.udp_port)) + sock.settimeout(1.0) # 1s timeout for responsive time-based flushing + log.info( + "Listening on UDP port %d (batch size: %d, flush interval: %.0fs)", + args.udp_port, args.batch_size, args.flush_interval, + ) + + batch: list[tuple[int, list[float]]] = [] + stats = {"received": 0, "ingested": 0, "errors": 0, "batches": 0} + validation_stats = {"queries": 0, "exact_matches": 0, "pir_readings": 0, "pir_agreements": 0} + last_log = time.time() + last_flush = time.time() + seq_counter = 0 + last_features: list[float] | None = None + + try: + while True: + try: + data, addr = sock.recvfrom(2048) + except socket.timeout: + # Time-based flush: flush if interval elapsed and batch is non-empty + now = time.time() + if batch and (now - last_flush) >= args.flush_interval: + _flush_batch(seed, batch, stats, args.validate, + validation_stats, last_features) + batch = [] + last_flush = now + # Periodic status log + if now - last_log > 30: + log.info( + "Stats: received=%d ingested=%d batches=%d errors=%d", + stats["received"], stats["ingested"], stats["batches"], stats["errors"], + ) + if args.validate and validation_stats["pir_readings"] > 0: + rate = validation_stats["pir_agreements"] / validation_stats["pir_readings"] * 100 + log.info( + "Validation: kNN queries=%d exact=%d | PIR agreement=%.1f%% (%d/%d)", + validation_stats["queries"], + validation_stats["exact_matches"], + rate, + validation_stats["pir_agreements"], + validation_stats["pir_readings"], + ) + last_log = now + continue + + # Source IP filtering (defense against UDP spoofing) + if allowed_sources and addr[0] not in allowed_sources: + log.debug("Dropping packet from unauthorized source %s", addr[0]) + continue + + parsed = parse_packet(data) + if parsed is None: + continue + + stats["received"] += 1 + seq_counter += 1 + + # Generate unique vector ID from hash(node_id + timestamp + seq) + vec_id = _make_vector_id(parsed["node_id"], parsed["timestamp_us"], seq_counter) + last_features = parsed["features"] + batch.append((vec_id, parsed["features"])) + + if args.verbose: + log.debug( + "node=%d seq=%d id=%08x features=[%s]", + parsed["node_id"], + parsed["seq"], + vec_id, + ", ".join(f"{f:.3f}" for f in parsed["features"]), + ) + + # Size-based flush + if len(batch) >= args.batch_size: + _flush_batch(seed, batch, stats, args.validate, + validation_stats, last_features) + batch = [] + last_flush = time.time() + + # Also check time-based flush for slow packet rates + if batch and (time.time() - last_flush) >= args.flush_interval: + _flush_batch(seed, batch, stats, args.validate, + validation_stats, last_features) + batch = [] + last_flush = time.time() + + except KeyboardInterrupt: + log.info("Shutting down...") + if batch: + _flush_batch(seed, batch, stats, args.validate, + validation_stats, last_features) + finally: + sock.close() + log.info( + "Final stats: received=%d ingested=%d batches=%d errors=%d", + stats["received"], stats["ingested"], stats["batches"], stats["errors"], + ) + if args.validate: + log.info( + "Validation: kNN queries=%d exact_matches=%d | PIR readings=%d agreements=%d", + validation_stats["queries"], + validation_stats["exact_matches"], + validation_stats["pir_readings"], + validation_stats["pir_agreements"], + ) + # Verify witness chain on exit + try: + result = seed.verify_witness() + log.info( + "Witness chain: %s (length=%d)", + "VALID" if result.get("valid") else "INVALID", + result.get("chain_length", 0), + ) + except Exception: + pass + + +def run_stats(args): + """Query Seed and print comprehensive stats.""" + seed = SeedClient(args.seed_url, args.token) + + # Status + print("=== Seed Status ===") + try: + s = seed.status() + print(f" Device ID: {s.get('device_id', '?')}") + print(f" Total vectors: {s.get('total_vectors', '?')}") + print(f" Epoch: {s.get('epoch', '?')}") + print(f" Dimension: {s.get('dimension', '?')}") + print(f" Uptime: {s.get('uptime_secs', '?')}s") + except Exception as e: + print(f" Error: {e}") + + # Witness chain + print("\n=== Witness Chain ===") + try: + w = seed.verify_witness() + print(f" Valid: {w.get('valid', '?')}") + print(f" Chain length: {w.get('chain_length', '?')}") + print(f" Head: {str(w.get('head', '?'))[:32]}...") + except Exception as e: + print(f" Error: {e}") + + # Boundary analysis + print("\n=== Boundary Analysis ===") + try: + b = seed.boundary() + print(f" Fragility score: {b.get('fragility_score', '?')}") + print(f" Boundary count: {b.get('boundary_count', '?')}") + for k, v in b.items(): + if k not in ("fragility_score", "boundary_count"): + print(f" {k}: {v}") + except Exception as e: + print(f" Error: {e}") + + # Coherence profile + print("\n=== Coherence Profile ===") + try: + c = seed.coherence_profile() + for k, v in c.items(): + print(f" {k}: {v}") + except Exception as e: + print(f" Error: {e}") + + # kNN graph stats + print("\n=== kNN Graph Stats ===") + try: + g = seed.graph_stats() + for k, v in g.items(): + print(f" {k}: {v}") + except Exception as e: + print(f" Error: {e}") + + +def run_compact(args): + """Trigger store compaction on the Seed.""" + seed = SeedClient(args.seed_url, args.token) + print("Triggering store compaction...") + try: + result = seed.compact() + print(f"Compaction result: {json.dumps(result, indent=2)}") + except Exception as e: + print(f"Compaction failed: {e}") + sys.exit(1) + + +def main(): + parser = argparse.ArgumentParser( + description="ADR-069: ESP32 CSI -> Cognitum Seed RVF Bridge" + ) + parser.add_argument( + "--seed-url", + default="https://169.254.42.1:8443", + help="Cognitum Seed HTTPS URL (default: https://169.254.42.1:8443)", + ) + parser.add_argument( + "--token", + default=os.environ.get("SEED_TOKEN"), + help="Bearer token from Seed pairing (or set SEED_TOKEN env var)", + ) + parser.add_argument( + "--udp-port", + type=int, + default=5006, + help="UDP port to listen on (default: 5006)", + ) + parser.add_argument( + "--batch-size", + type=int, + default=10, + help="Vectors per ingest batch (default: 10)", + ) + parser.add_argument( + "--flush-interval", + type=float, + default=DEFAULT_FLUSH_INTERVAL, + help="Max seconds between flushes (default: %.0f)" % DEFAULT_FLUSH_INTERVAL, + ) + parser.add_argument( + "-v", "--verbose", + action="store_true", + help="Log every received packet", + ) + parser.add_argument( + "--validate", + action="store_true", + help="After each batch, query kNN and compare with PIR sensor", + ) + parser.add_argument( + "--stats", + action="store_true", + help="Print Seed stats (vectors, boundary, coherence, graph) and exit", + ) + parser.add_argument( + "--compact", + action="store_true", + help="Trigger store compaction and exit", + ) + parser.add_argument( + "--allowed-sources", + type=str, + default=None, + help="Comma-separated list of allowed source IPs for UDP packets " + "(e.g. '192.168.1.105,192.168.1.106'). Packets from other IPs are dropped.", + ) + args = parser.parse_args() + + if not args.token: + parser.error("--token is required (or set SEED_TOKEN environment variable)") + + if args.verbose: + logging.getLogger().setLevel(logging.DEBUG) + + if args.stats: + run_stats(args) + elif args.compact: + run_compact(args) + else: + run_bridge(args) + + +if __name__ == "__main__": + main()