Compare commits
15 Commits
6f77b37f5e
...
00304f9dc7
| Author | SHA1 | Date |
|---|---|---|
|
|
00304f9dc7 | |
|
|
d0b64bdeb6 | |
|
|
a2686d47a2 | |
|
|
f2525d7a0d | |
|
|
601b3406fd | |
|
|
deb561bf9c | |
|
|
d40411e6d7 | |
|
|
b116a99481 | |
|
|
684a064816 | |
|
|
7393cc2b73 | |
|
|
6432dfbd2d | |
|
|
46f701bca8 | |
|
|
94745242a8 | |
|
|
1e684cb208 | |
|
|
d98b7e3f65 |
|
|
@ -252,3 +252,9 @@ firmware/esp32-csi-node/build_firmware.batdata/
|
|||
models/
|
||||
demo_pointcloud.ply
|
||||
demo_splats.json
|
||||
|
||||
# rvCSI napi-rs addon — generated by `napi build` (do not commit)
|
||||
v2/crates/rvcsi-node/*.node
|
||||
v2/crates/rvcsi-node/binding.js
|
||||
v2/crates/rvcsi-node/binding.d.ts
|
||||
v2/crates/rvcsi-node/npm/
|
||||
|
|
|
|||
|
|
@ -10,3 +10,7 @@
|
|||
path = vendor/sublinear-time-solver
|
||||
url = https://github.com/ruvnet/sublinear-time-solver
|
||||
branch = main
|
||||
[submodule "vendor/rvcsi"]
|
||||
path = vendor/rvcsi
|
||||
url = https://github.com/ruvnet/rvcsi
|
||||
branch = main
|
||||
|
|
|
|||
31
CHANGELOG.md
31
CHANGELOG.md
|
|
@ -7,6 +7,19 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
|
||||
## [Unreleased]
|
||||
|
||||
### Changed
|
||||
- **rvCSI moved to its own repo and is now vendored as a submodule.** The 9 `rvcsi-*`
|
||||
crates (`rvcsi-core`/`-dsp`/`-events`/`-adapter-file`/`-adapter-nexmon`/`-ruvector`/
|
||||
`-runtime`/`-node`/`-cli` — added inline in #542) now live in
|
||||
[`github.com/ruvnet/rvcsi`](https://github.com/ruvnet/rvcsi): published to crates.io
|
||||
as `rvcsi-* 0.3.x`, to npm as `@ruv/rvcsi`, with a Claude Code plugin marketplace and
|
||||
a RuView-style README. RuView vendors it under `vendor/rvcsi` (alongside
|
||||
`vendor/ruvector` / `vendor/midstream` / `vendor/sublinear-time-solver`) and no longer
|
||||
carries inline copies in `v2/crates/`; consumers depend on the published crates (or the
|
||||
submodule's `crates/rvcsi-*` paths). `v2/Cargo.toml`, `CLAUDE.md`, and the README docs
|
||||
table updated accordingly. The ADRs (ADR-095, ADR-096), PRD, and DDD model stay in
|
||||
`docs/` here as the design record of the incubation.
|
||||
|
||||
### Fixed
|
||||
- **README: corrected the camera-supervised pose-accuracy claim.** The README stated
|
||||
"92.9% PCK@20" for camera-supervised training; that figure does not appear in
|
||||
|
|
@ -17,8 +30,26 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
ADR-079 target (35%+), and notes the eval phases are pending. Surfaced by the
|
||||
PowerPlatePulse training-pipeline audit (2026-05-11); 6 remaining audit findings
|
||||
tracked in the PR.
|
||||
- **rvCSI `BaselineDriftDetector`: drift thresholds are now scale-relative, not absolute.**
|
||||
The detector compared `mean_amplitude` against its EWMA baseline with absolute
|
||||
thresholds (`anomaly_threshold = 1.0`, `drift_threshold = 0.15`) — fine for the
|
||||
synthetic unit tests (amplitudes ≈ 1.0), but raw ESP32 CSI is `int8` I/Q with
|
||||
amplitudes up to ~128, so the window-to-window RMS distance is routinely 5–50 ≫ 1.0
|
||||
and `AnomalyDetected` fired on ~96 % of windows (319/331 on a real node-1 capture).
|
||||
Drift is now `‖current − baseline‖₂ / ‖baseline‖₂` (a fraction, with an `eps` floor
|
||||
for a degenerate near-zero baseline), so one tuning works across raw-`int8` ESP32,
|
||||
`int16`-scaled Nexmon, and baseline-subtracted streams alike — `AnomalyDetected`
|
||||
drops to 40/331 on the same data, the existing detector tests still pass, and a
|
||||
`baseline_drift_is_scale_invariant_no_anomaly_storm` regression test was added.
|
||||
ADR-095 D13 / ADR-096 §2.1, §5 updated. Surfaced by an end-to-end test against
|
||||
real ESP32 CSI (a 7,000-frame node-1 capture; transcoder at
|
||||
`scripts/esp32_jsonl_to_rvcsi.py`).
|
||||
|
||||
### Added
|
||||
- **rvCSI — edge RF sensing runtime (design + first implementation).** New subsystem **rvCSI**: a Rust-first / TypeScript-accessible / hardware-abstracted edge RF sensing runtime that normalizes WiFi CSI from Nexmon, ESP32, Intel, Atheros, file and replay sources into one validated `CsiFrame` schema, runs reusable DSP, emits typed confidence-scored events, and bridges to RuVector RF memory, an MCP tool server and a TS SDK.
|
||||
- **Design docs:** `docs/prd/rvcsi-platform-prd.md` (purpose, users, success criteria, FR1–FR10, NFRs, system architecture, data model); `docs/adr/ADR-095-rvcsi-edge-rf-sensing-platform.md` (the 15 architectural decisions: Rust core, C-at-the-boundary, TS SDK via napi-rs, normalized schema, validate-before-FFI, CSI-as-temporal-delta, RuVector as RF memory, replayability, detection≠decision, local-first, read-first/write-gated MCP, mandatory quality scoring, versioned calibration, plugin adapters); `docs/adr/ADR-096-rvcsi-ffi-crate-layout.md` (crate topology, the napi-c shim record format & contract, the napi-rs Node surface, build/test invariants); `docs/ddd/rvcsi-domain-model.md` (7 bounded contexts: Capture, Validation, Signal, Calibration, Event, Memory, Agent — with aggregates, invariants, context map and domain services). Indexed in `docs/adr/README.md` and `docs/ddd/README.md`.
|
||||
- **Crates** (9 new `v2/crates/rvcsi-*` workspace members): `rvcsi-core` (normalized `CsiFrame`/`CsiWindow`/`CsiEvent` schema, `AdapterProfile`, `CsiSource` plugin trait, id newtypes + `IdGenerator`, `RvcsiError`, the `validate_frame` pipeline + quality scoring; `forbid(unsafe_code)`); `rvcsi-adapter-nexmon` — the **napi-c** seam: `native/rvcsi_nexmon_shim.{c,h}` (the only C in the runtime — allocation-free, bounds-checked, ABI `1.1`), compiled via `build.rs`+`cc`, handling **two byte formats** — the compact self-describing "rvCSI Nexmon record", and the **real nexmon_csi UDP payload** (the 18-byte `magic 0x1111 · rssi · fctl · src_mac · seq · core/stream · chanspec · chip_ver` header + `nsub` int16 I/Q samples, the modern BCM43455c0/4358/4366c0 export read by CSIKit/`csireader.py`), with a Broadcom d11ac **chanspec decoder** (channel/bandwidth/band) — plus a pure-Rust **libpcap reader** (classic `.pcap`, all byte-order/timestamp-resolution magics, Ethernet/raw-IPv4/Linux-SLL link types) and a **Nexmon-chip / Raspberry-Pi-model registry** (`NexmonChip` / `RaspberryPiModel` — including the **Raspberry Pi 5** (CYW43455/BCM43455c0, same wireless as the Pi 4 — 20/40/80 MHz, 2.4+5 GHz, 64/128/256 subcarriers), the Pi 3B+/4/400, and the Pi Zero 2 W (BCM43436b0); `nexmon_adapter_profile` / `raspberry_pi_profile` build the per-chip `AdapterProfile`; `chip_ver` words auto-resolve to a chip). Wrapped by a documented `ffi` module and two `CsiSource`s: `NexmonAdapter` (record buffers) and `NexmonPcapAdapter` (real nexmon_csi UDP inside a `tcpdump -i wlan0 dst port 5500 -w csi.pcap` capture — the pcap timestamp stamps each frame; the chip is auto-detected from `chip_ver`, overridable via `.with_pi_model(Pi5)` / `.with_chip(...)`). `rvcsi-dsp` (DC removal, phase unwrap, smoothing, Hampel/MAD filter, sliding variance, baseline subtraction, motion-energy/presence/confidence features, heuristic breathing-band estimate, non-destructive `SignalPipeline`); `rvcsi-events` (`WindowBuffer`, the `EventDetector` trait + presence/motion/quality/baseline-drift state machines, `EventPipeline`; the baseline-drift detector uses **scale-relative** thresholds — drift as a fraction of the baseline's RMS magnitude — so one tuning works across raw-`int8` ESP32, `int16`-scaled Nexmon, and baseline-subtracted streams alike); `rvcsi-adapter-file` (the `.rvcsi` JSONL capture format, `FileRecorder`, `FileReplayAdapter` deterministic replay); `rvcsi-ruvector` (deterministic window/event embeddings, `cosine_similarity`, the `RfMemoryStore` trait, `InMemoryRfMemory` + `JsonlRfMemory` — a standin until the production RuVector binding); `rvcsi-runtime` (the no-FFI composition layer: `CaptureRuntime` = `CsiSource` + `validate_frame` + `SignalPipeline` + `EventPipeline`, plus one-shot helpers `summarize_capture`/`decode_nexmon_records`/`decode_nexmon_pcap`/`summarize_nexmon_pcap`/`events_from_capture`/`export_capture_to_rf_memory`); `rvcsi-node` — the **napi-rs** seam (a `["cdylib","rlib"]` Node addon, `build.rs` runs `napi_build::setup()`; thin `#[napi]` wrappers over `rvcsi-runtime` — `nexmonDecodeRecords`/`nexmonDecodePcap` (with optional `chip`)/`inspectNexmonPcap`/`decodeChanspec`/`nexmonChipName`/`nexmonProfile`/`nexmonChips`/`inspectCaptureFile`/`eventsFromCaptureFile`/`exportCaptureToRfMemory` + an `RvcsiRuntime` streaming class; everything that crosses to JS is a validated/normalized struct serialized to JSON); `rvcsi-cli` (the `rvcsi` binary: `record` (Nexmon-dump *or* `--source nexmon-pcap [--chip pi5]` → `.rvcsi`), `inspect`, `inspect-nexmon`, `nexmon-chips`, `decode-chanspec`, `replay`, `stream`, `events`, `health`, `calibrate` v0-baseline, `export ruvector`). Plus the `@ruv/rvcsi` npm package (`package.json`/`index.js`/`index.d.ts`/`README`/`__test__`) alongside `rvcsi-node` — a curated JS surface that parses the addon's JSON into plain `CsiFrame`/`CsiWindow`/`CsiEvent`/`SourceHealth`/`CaptureSummary`/`NexmonPcapSummary`/`DecodedChanspec` objects, with a lazy native-addon load.
|
||||
- **Tests:** 169 across the rvcsi crates (core 29, dsp 28, events 19 — incl. a baseline-drift scale-invariance regression, adapter-file 20 + 1 doctest, adapter-nexmon 28 — round-tripping through the C shim and synthetic libpcap files, incl. Pi 5 / chip-detection, ruvector 20 + 1 doctest, runtime 13, cli 10), 0 failures; all rvcsi crates build together and are clippy-clean (`rvcsi-node` under `deny(clippy::all)`); `forbid(unsafe_code)` everywhere except `rvcsi-adapter-nexmon` (FFI, every `unsafe` block documented). Also exercised end-to-end against a real 7,000-frame ESP32 node-1 capture (transcoded with `scripts/esp32_jsonl_to_rvcsi.py` — the stand-in for the not-yet-shipped `record --source esp32-jsonl`): `rvcsi inspect`/`replay`/`calibrate`/`events` all run on real hardware data. Not yet wired in: live radio capture, `rvcsi-adapter-esp32` (live serial/UDP ESP32 source), the WebSocket daemon (`rvcsi-daemon`), the MCP tool server (`rvcsi-mcp`), and the legacy nexmon *packed-float* CSI export — follow-ups on top of these crates.
|
||||
- **`wifi-densepose-train`: `signal_features` module — wires `wifi-densepose-signal` into the training pipeline.** `wifi-densepose-signal` was previously a phantom dependency of `wifi-densepose-train` (listed in `Cargo.toml`, never imported). New `wifi_densepose_train::signal_features::extract_signal_features` (and `CsiSample::signal_features()`) run a windowed CSI observation's centre frame through `wifi_densepose_signal::features::FeatureExtractor`, producing a fixed-length (`FEATURE_LEN = 12`) amplitude/phase/PSD feature vector — the hook for a future vitals / multi-task supervision head (breathing- and heart-rate-band power are read off the PSD summary). The vector is produced on demand and not yet fed back into the loss. Surfaced by the 2026-05-11 training-pipeline audit (findings #1 "vitals features absent from training" and #2 "`wifi-densepose-signal` ghost dep").
|
||||
- **`wifi-densepose-train`: `TrainingConfig` subcarrier-layout presets + a real-loader integration test.** New `TrainingConfig::for_subcarriers(native, target)` plus named presets `ht40_192()` (≈192-sc ESP32 HT40 → 56) and `multiband_168()` (168-sc ADR-078 multi-band mesh → 56), so non-MM-Fi CSI shapes are first-class instead of requiring manual `native_subcarriers`/`num_subcarriers` overrides; field docs now list the supported source counts and the multi-NIC mapping. New `tests/test_real_loader.rs` round-trips synthetic CSI through `.npy` files → `MmFiDataset::discover`/`get` (including the subcarrier-interpolation branch and the empty-root case) — exercising the on-disk loader path the deterministic `verify-training` proof intentionally bypasses. Addresses training-pipeline audit findings #6 (56-sc/1-NIC config default) and #7 (multi-band mesh not in config); the #4 concern ("proof uses synthetic data") is reframed — the proof *should* use a reproducible source, and this test covers the real loader it skips.
|
||||
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ Dual codebase: Python v1 (`v1/`) and Rust port (`v2/`).
|
|||
| `wifi-densepose-wifiscan` | Multi-BSSID WiFi scanning (ADR-022) |
|
||||
| `wifi-densepose-vitals` | ESP32 CSI-grade vital sign extraction (ADR-021) |
|
||||
| `nvsim` | Deterministic NV-diamond magnetometer pipeline simulator (ADR-089) — standalone leaf, WASM-ready |
|
||||
| `vendor/rvcsi` (submodule) | **rvCSI** — edge RF sensing runtime (ADR-095/096): 9 crates (`rvcsi-core`/`-dsp`/`-events`/`-adapter-file`/`-adapter-nexmon`/`-ruvector`/`-runtime`/`-node`/`-cli`). Lives in its own repo ([github.com/ruvnet/rvcsi](https://github.com/ruvnet/rvcsi)), vendored here under `vendor/rvcsi`, published to crates.io as `rvcsi-* 0.3.x` and to npm as `@ruv/rvcsi`. Not a `v2/` workspace member — depend on the published crates (or the submodule's `crates/rvcsi-*` paths). Normalized `CsiFrame`/`CsiWindow`/`CsiEvent` schema, validate-before-FFI, reusable DSP, typed confidence-scored events, the napi-c Nexmon shim (real nexmon_csi `.pcap` from a Raspberry Pi 5 / 4 / 3B+ — BCM43455c0), the napi-rs SDK, the `rvcsi` CLI, a Claude Code plugin. |
|
||||
|
||||
### RuvSense Modules (`signal/src/ruvsense/`)
|
||||
| Module | Purpose |
|
||||
|
|
|
|||
|
|
@ -520,8 +520,9 @@ Verify the plugin structure: `bash plugins/ruview/scripts/smoke.sh`. Full detail
|
|||
| [User Guide](docs/user-guide.md) | Step-by-step guide: installation, first run, API usage, hardware setup, training |
|
||||
| [Build Guide](docs/build-guide.md) | Building from source (Rust and Python) |
|
||||
| [Claude Code / Codex Plugin](plugins/ruview/README.md) | The `ruview` plugin + marketplace — skills, `/ruview-*` commands, agents, and the Codex prompt mirror |
|
||||
| [Architecture Decisions](docs/adr/README.md) | 79 ADRs — why each technical choice was made, organized by domain (hardware, signal processing, ML, platform, infrastructure) |
|
||||
| [Domain Models](docs/ddd/README.md) | 7 DDD models (RuvSense, Signal Processing, Training Pipeline, Hardware Platform, Sensing Server, WiFi-Mat, CHCI) — bounded contexts, aggregates, domain events, and ubiquitous language |
|
||||
| [Architecture Decisions](docs/adr/README.md) | 96 ADRs — why each technical choice was made, organized by domain (hardware, signal processing, ML, platform, infrastructure) |
|
||||
| [Domain Models](docs/ddd/README.md) | 8 DDD models (RuvSense, Signal Processing, Training Pipeline, Hardware Platform, Sensing Server, WiFi-Mat, CHCI, rvCSI) — bounded contexts, aggregates, domain events, and ubiquitous language |
|
||||
| [rvCSI — edge RF sensing runtime](https://github.com/ruvnet/rvcsi) | Rust-first / TypeScript-accessible / hardware-abstracted CSI runtime: multi-source ingestion (incl. real nexmon_csi `.pcap` from a **Raspberry Pi 5** / Pi 4 / Pi 3B+ — CYW43455 / BCM43455c0) → validation → DSP → typed events → RuVector RF memory ([ADR-095](docs/adr/ADR-095-rvcsi-edge-rf-sensing-platform.md), [ADR-096](docs/adr/ADR-096-rvcsi-ffi-crate-layout.md), [domain model](docs/ddd/rvcsi-domain-model.md)). Now its own repo — [`ruvnet/rvcsi`](https://github.com/ruvnet/rvcsi) — vendored here under `vendor/rvcsi`; 9 `rvcsi-*` crates on crates.io, `@ruv/rvcsi` on npm, plus a Claude Code plugin. |
|
||||
| [Desktop App](v2/crates/wifi-densepose-desktop/README.md) | **WIP** — Tauri v2 desktop app for node management, OTA updates, WASM deployment, and mesh visualization |
|
||||
| [Medical Examples](examples/medical/README.md) | Contactless blood pressure, heart rate, breathing rate via 60 GHz mmWave radar — $15 hardware, no wearable |
|
||||
| [Extended Documentation](docs/readme-details.md) | Latest additions, key features, installation, quick start, signal processing, training, CLI, testing, deployment, and changelog |
|
||||
|
|
|
|||
|
|
@ -0,0 +1,210 @@
|
|||
# ADR-095: rvCSI — Edge RF Sensing Runtime Platform
|
||||
|
||||
| Field | Value |
|
||||
|-------|-------|
|
||||
| **Status** | Proposed |
|
||||
| **Date** | 2026-05-12 |
|
||||
| **Deciders** | ruv |
|
||||
| **Codename** | **rvCSI** — RuVector Channel State Information runtime |
|
||||
| **Relates to** | ADR-012 (ESP32 CSI mesh), ADR-013 (feature-level sensing on commodity gear), ADR-014 (SOTA signal processing), ADR-016 (RuVector integration), ADR-024 (AETHER contrastive embeddings), ADR-031 (RuView sensing-first RF mode), ADR-040 (WASM programmable sensing), ADR-049 (cross-platform WiFi interface detection) |
|
||||
| **PRD** | [rvCSI Platform PRD](../prd/rvcsi-platform-prd.md) |
|
||||
| **Domain model** | [rvCSI Domain Model](../ddd/rvcsi-domain-model.md) |
|
||||
|
||||
---
|
||||
|
||||
## 1. Context
|
||||
|
||||
WiFi Channel State Information (CSI) is a powerful camera-free sensing primitive — but in practice it is hard to operationalize. Most CSI pipelines today are Linux shell scripts, patched firmware, kernel modules, Python notebooks, PCAP dumps, and ad-hoc signal processing. Packet formats are inconsistent across chips; drivers are unstable; malformed packets are common; and device-specific assumptions leak everywhere. CSI works in the lab and falls over in the field.
|
||||
|
||||
RuView already contains substantial CSI infrastructure (`wifi-densepose-signal`, `wifi-densepose-ruvector`, the ESP32 mesh of ADR-012, the RuView multistatic work of ADR-031). What is missing is a **stable, hardware-abstracted runtime layer** that:
|
||||
|
||||
- ingests CSI from many sources behind one interface,
|
||||
- validates every packet before it can touch application code,
|
||||
- normalizes everything into one schema,
|
||||
- runs reusable signal processing,
|
||||
- emits typed, confidence-scored events,
|
||||
- exposes a safe TypeScript SDK, a CLI, MCP tools, and a RuVector bridge,
|
||||
- and runs unattended on Raspberry Pi-class hardware.
|
||||
|
||||
This ADR establishes that runtime — **rvCSI** — and the architectural decisions that constrain it. Detailed requirements are in the [PRD](../prd/rvcsi-platform-prd.md); the bounded contexts, aggregates, and ubiquitous language are in the [domain model](../ddd/rvcsi-domain-model.md).
|
||||
|
||||
### 1.1 What rvCSI is not (day one)
|
||||
|
||||
rvCSI is *not* a pure-Rust replacement for vendor firmware patches, *not* a universal driver for all WiFi chips, and *not* an identity/pose/medical/legal-grade claim. It is a **structural sensing** runtime: excellent at detecting change, presence, motion, drift, and learned patterns; deliberately silent on exact identity, exact pose, and certainty guarantees. The product surface stays inside that boundary (see Decision D7).
|
||||
|
||||
### 1.2 Existing assets rvCSI builds on
|
||||
|
||||
| Asset | Source | Reuse in rvCSI |
|
||||
|-------|--------|----------------|
|
||||
| SOTA DSP (Hampel, phase unwrap, Fresnel, BVP, spectrograms) | `wifi-densepose-signal` (ADR-014) | `rvcsi-dsp` wraps/extends rather than re-implements |
|
||||
| RuVector integration (5 crates) | `wifi-densepose-ruvector` (ADR-016) | `rvcsi-ruvector` exporter rides on the existing integration |
|
||||
| ESP32 CSI firmware + aggregator | `wifi-densepose-hardware` / firmware (ADR-012) | `rvcsi-adapter-esp32` consumes the existing serial/UDP stream |
|
||||
| AETHER contrastive embeddings | ADR-024 | optional embedding backend for window/event vectors |
|
||||
| Cross-platform interface detection | ADR-049 | adapter discovery / health checks |
|
||||
|
||||
---
|
||||
|
||||
## 2. Decision
|
||||
|
||||
**Adopt rvCSI as a layered edge RF sensing runtime** with the boundary discipline `C → Rust → TypeScript`, a single normalized `CsiFrame` schema, mandatory validation before any language boundary crossing, and RuVector as RF memory. The fifteen decisions below are the architectural contract.
|
||||
|
||||
### D1 — Rust is the core runtime
|
||||
|
||||
CSI parsing and DSP require memory safety, predictable latency, and high throughput; C/Python research stacks are fragile for unattended edge deployment. **rvCSI uses Rust** for parsing, validation, signal processing, event extraction, and daemon execution.
|
||||
*Consequences:* safer packet handling; better long-running stability; stronger portability to edge devices; more complex build system than pure TypeScript.
|
||||
|
||||
### D2 — C only at the hardware-compatibility boundary
|
||||
|
||||
Nexmon and similar CSI sources often require C shims, legacy drivers, or firmware-patch hooks. **C is isolated to thin shims** for existing capture and firmware compatibility — never in the data path beyond decode.
|
||||
*Consequences:* existing Nexmon capability reused; unsafe surface stays small; full firmware rewrite avoided; some device support stays dependent on upstream tools.
|
||||
|
||||
### D3 — TypeScript for SDK, CLI, and developer orchestration
|
||||
|
||||
Developers need an approachable SDK, agent integrations, dashboards, and scripts. **rvCSI exposes a first-class TypeScript SDK** (`@ruv/rvcsi`) and CLI; native performance stays in Rust.
|
||||
*Consequences:* easy adoption by app/agent developers; native perf preserved; requires a native build + prebuild release pipeline.
|
||||
|
||||
### D4 — napi-rs for Node bindings
|
||||
|
||||
Native Node modules need a stable ABI and ergonomic Rust integration. **rvCSI uses napi-rs** for the `rvcsi-node` bindings.
|
||||
*Consequences:* Rust exposes typed APIs to TypeScript; prebuilt binaries distributable; careful memory-ownership rules required.
|
||||
|
||||
### D5 — Normalize all sources into one `CsiFrame` / `CsiWindow` schema
|
||||
|
||||
Different CSI sources expose incompatible formats; application code must not know device-specific details. **Every source is normalized into `CsiFrame` and `CsiWindow`** (schema in the domain model).
|
||||
*Consequences:* hardware-agnostic application code; easier RuVector integration; some source-specific metadata needs extension fields.
|
||||
|
||||
### D6 — Validate before crossing language boundaries
|
||||
|
||||
Malformed packets and unsafe pointers are the dominant stability risk. **All raw data is validated in Rust before it crosses into TypeScript or RuVector**; rejected frames are quarantined (when enabled); parser failures return structured errors; TypeScript never receives raw unchecked pointers.
|
||||
*Consequences:* safer SDK; cleaner error model; small validation overhead.
|
||||
|
||||
### D7 — Treat CSI as a temporal delta, not absolute truth
|
||||
|
||||
CSI is noisy and environment-specific. **rvCSI frames CSI as a temporal delta stream against learned baselines**, not as exact vision.
|
||||
*Consequences:* honest product claims; good fit for presence/motion/drift/anomaly; identity and exact pose excluded from core claims.
|
||||
|
||||
### D8 — RuVector is RF memory
|
||||
|
||||
CSI becomes far more valuable stored as temporal embeddings and room signatures. **rvCSI integrates with RuVector** for vector storage, similarity search, drift detection, and sensor-graph relationships.
|
||||
*Consequences:* rvCSI joins the broader ruvnet cognitive stack; RF field history becomes queryable; requires embedding design and retention policy.
|
||||
|
||||
### D9 — Design for replayability
|
||||
|
||||
Signal algorithms need repeatable benchmarks and debugging. **rvCSI supports deterministic replay** of captured sessions (timestamps, ordering, validation decisions, event output, calibration version, runtime config all preserved).
|
||||
*Consequences:* easier testing; better audit trail; enables benchmark datasets.
|
||||
|
||||
### D10 — Separate detection from decision
|
||||
|
||||
rvCSI detects RF events; agents/applications decide what to do. **rvCSI emits events with confidence and evidence and performs no high-consequence actions by default.**
|
||||
*Consequences:* cleaner safety model; clean integration with Cognitum proof-gated execution; applications implement policy.
|
||||
|
||||
### D11 — Local-first operation
|
||||
|
||||
RF sensing is privacy-sensitive and often valuable offline. **rvCSI runs locally by default and requires no cloud service**; remote observability is opt-in.
|
||||
*Consequences:* better privacy posture; usable in industrial/care/sovereign deployments; remote observability must be explicitly enabled.
|
||||
|
||||
### D12 — MCP tools are read-first, write-gated
|
||||
|
||||
Agents should observe RF state safely; device mutation and calibration change system behavior. **MCP tools default to read actions**; capture start/stop, calibration, and export are gated.
|
||||
*Consequences:* safer agent integration; lower accidental device disruption; more explicit operational control.
|
||||
|
||||
### D13 — Quality scoring is mandatory
|
||||
|
||||
CSI quality varies widely by chip, antenna, environment, channel, and interference. **Every frame, window, and event carries quality or confidence scoring.**
|
||||
*Consequences:* downstream systems can suppress weak evidence; easier debugging; requires calibration and thresholds. Where a detector compares against a learned baseline (e.g. baseline-drift / anomaly), thresholds are expressed **relative to the baseline's magnitude**, not as absolute amplitude units, so a single tuning is valid across sources whose raw CSI scales differ by orders of magnitude (raw `int8` ESP32 vs. `int16`-scaled Nexmon vs. baseline-subtracted streams).
|
||||
|
||||
### D14 — Versioned calibration profiles
|
||||
|
||||
Room baselines change over time. **Calibration profiles are versioned**, and event outputs reference the calibration version used.
|
||||
*Consequences:* more auditable detection; replay can reproduce prior outputs; slight storage overhead.
|
||||
|
||||
### D15 — Hardware adapters are plugins
|
||||
|
||||
Device support will evolve and vary by platform. **Source adapters are plugins behind a common Rust trait** (`CsiSource`).
|
||||
*Consequences:* easier support for Nexmon/ESP32/Intel/Atheros/SDR/future sources; cleaner testability; adapter certification becomes important.
|
||||
|
||||
---
|
||||
|
||||
## 3. Architecture
|
||||
|
||||
```
|
||||
CSI Source
|
||||
↓ ┌─ Capture context ──────────────┐
|
||||
Adapter Layer (C shims here) │ Source · CaptureSession · │
|
||||
↓ │ AdapterProfile │
|
||||
Rust Validation Pipeline ─────┤ Validation context │
|
||||
↓ │ ValidationPolicy · Quarantine │
|
||||
Normalized CsiFrame ──────────┘ ← FFI-safe boundary object
|
||||
↓ ┌─ Signal context ───────────────┐
|
||||
Signal Processing │ SignalPipeline · WindowBuffer │
|
||||
↓ ├─ Calibration context ──────────┤
|
||||
Window Aggregator ───────────┤ CalibrationProfile · │
|
||||
↓ │ RoomSignature · BaselineModel │
|
||||
Event Extractor ─────────────┤ Event context │
|
||||
↓ │ EventDetector · StateMachine │
|
||||
TS SDK · CLI · MCP · RuVector └─ Memory + Agent contexts ──────┘
|
||||
```
|
||||
|
||||
**Crates (within RuView's `v2/crates/`, or a standalone `rvcsi/crates/`):**
|
||||
`rvcsi-core` · `rvcsi-adapter-file` · `rvcsi-adapter-nexmon` · `rvcsi-adapter-esp32` · `rvcsi-dsp` · `rvcsi-events` · `rvcsi-ruvector` · `rvcsi-daemon` · `rvcsi-node` · `rvcsi-mcp` — plus TypeScript packages `sdk`, `cli`, `dashboard`, and `native/nexmon-shim-c`.
|
||||
|
||||
See the [PRD §9](../prd/rvcsi-platform-prd.md#9-system-architecture) for the full component table and reference layout, and the [domain model](../ddd/rvcsi-domain-model.md) for bounded contexts, aggregates, invariants, and domain services.
|
||||
|
||||
---
|
||||
|
||||
## 4. Consequences
|
||||
|
||||
**Positive**
|
||||
|
||||
- CSI becomes reusable infrastructure: npm-installable, reproducible, typed, safe-parsed, embeddable, WebSocket-streamable, WASM-portable, MCP-exposed, agent-integrable.
|
||||
- One application codebase works across Nexmon, ESP32, Intel, and Atheros sources.
|
||||
- Bad packets cannot crash the daemon; unattended operation becomes realistic.
|
||||
- RuView/RuVector/Cognitum/agents gain a validated live source of RF observations.
|
||||
- Honest product framing ("structural sensing") avoids over-claiming.
|
||||
|
||||
**Negative / costs**
|
||||
|
||||
- Larger build surface: Rust core + napi-rs native module + C shims + TypeScript packages + prebuild pipeline.
|
||||
- Adapter certification and a supported-hardware matrix become ongoing maintenance.
|
||||
- Embedding design, calibration thresholds, and retention policy are non-trivial open questions (tracked in the PRD).
|
||||
- Risk of duplicating `wifi-densepose-signal` / `wifi-densepose-ruvector`; mitigated by wrapping, not re-implementing.
|
||||
|
||||
**Risks**
|
||||
|
||||
- Nexmon coupling: some device support remains dependent on upstream firmware/driver projects.
|
||||
- CSI quality variance: weak-signal environments may yield low-confidence events; mitigated by mandatory quality scoring (D13) and versioned calibration (D14).
|
||||
|
||||
---
|
||||
|
||||
## 5. Alternatives considered
|
||||
|
||||
| Alternative | Why not |
|
||||
|-------------|---------|
|
||||
| Pure-Python runtime (extend the v1 stack) | Fragile under malformed packets; GC pauses break the < 50 ms latency target; poor unattended stability. |
|
||||
| Pure-Rust including firmware (replace Nexmon) | Enormous scope; vendor-specific; would block v0 indefinitely. D2 keeps C at the boundary instead. |
|
||||
| Per-source SDKs (no normalized schema) | Pushes device specifics into application code; defeats the "same app code across adapters" success criterion. |
|
||||
| WASM-only core | No raw socket / serial / monitor-mode access for live capture; fine for offline parsing (a later target) but not v0 live capture. |
|
||||
| Cloud-first ingestion | Violates the privacy posture and the local-first requirement; unacceptable for care/industrial/sovereign deployments. |
|
||||
|
||||
---
|
||||
|
||||
## 6. Implementation phases (proposed)
|
||||
|
||||
1. **v0** — `rvcsi-core` + file/replay/ESP32 adapters + validation + `rvcsi-dsp` (presence/motion) + `rvcsi-node` SDK + `rvcsi-cli` + WebSocket output + `rvcsi-ruvector` export + basic calibration + health checks. Targets all eight PRD success criteria.
|
||||
2. **v1** — multi-node sync, RF room signatures, breathing-rate where signal permits, temporal embeddings, drift detection, room-topology graph, `rvcsi-mcp` tool server, replayable benchmark datasets, RuView sensor fusion, Cognitum deployment profile.
|
||||
3. **v2** — hardware-agnostic RF sensor fabric, multi-room RF memory, streaming anomaly detection, RF-SLAM research mode, on-device embedding model, federated room-signature learning, signed sensor-evidence records, proof-gated event publication, dynamic cut-based coherence over RF graphs, agent-driven calibration and self-repair.
|
||||
|
||||
---
|
||||
|
||||
## 7. References
|
||||
|
||||
- [rvCSI Platform PRD](../prd/rvcsi-platform-prd.md)
|
||||
- [rvCSI Domain Model](../ddd/rvcsi-domain-model.md)
|
||||
- ADR-012 — ESP32 CSI Sensor Mesh
|
||||
- ADR-013 — Feature-Level Sensing on Commodity Gear
|
||||
- ADR-014 — SOTA Signal Processing
|
||||
- ADR-016 — RuVector Integration
|
||||
- ADR-024 — Project AETHER: Contrastive CSI Embeddings
|
||||
- ADR-031 — RuView Sensing-First RF Mode
|
||||
- ADR-040 — WASM Programmable Sensing
|
||||
- ADR-049 — Cross-Platform WiFi Interface Detection
|
||||
|
|
@ -0,0 +1,144 @@
|
|||
# ADR-096: rvCSI — Crate Topology, the napi-c Shim, and the napi-rs Node Surface
|
||||
|
||||
| Field | Value |
|
||||
|-------|-------|
|
||||
| **Status** | Proposed |
|
||||
| **Date** | 2026-05-12 |
|
||||
| **Deciders** | ruv |
|
||||
| **Codename** | **rvCSI** — RuVector Channel State Information runtime |
|
||||
| **Relates to** | ADR-095 (rvCSI platform — D1 Rust core, D2 C-at-the-boundary, D3 TS SDK, D4 napi-rs, D5 normalized schema, D6 validate-before-FFI, D15 plugin adapters), ADR-009/ADR-040 (WASM runtimes), ADR-049 (cross-platform WiFi interface detection) |
|
||||
| **PRD** | [rvCSI Platform PRD](../prd/rvcsi-platform-prd.md) |
|
||||
| **Domain model** | [rvCSI Domain Model](../ddd/rvcsi-domain-model.md) |
|
||||
| **Implements** | `v2/crates/rvcsi-core`, `rvcsi-dsp`, `rvcsi-events`, `rvcsi-adapter-file`, `rvcsi-adapter-nexmon`, `rvcsi-ruvector`, `rvcsi-node`, `rvcsi-cli` |
|
||||
|
||||
---
|
||||
|
||||
## 1. Context
|
||||
|
||||
ADR-095 set the platform-level invariant `C → Rust → TypeScript` and the fifteen decisions that constrain rvCSI. This ADR makes the *implementation* concrete: which crates exist, what each owns, where the two FFI seams are (the **napi-c** C shim below Rust, and the **napi-rs** Node addon above it), and the rules that keep `unsafe` confined and the boundary objects validated.
|
||||
|
||||
The two seams:
|
||||
|
||||
- **napi-c** — the *downward* seam to fragile vendor/firmware/driver code. Per ADR-095 D2, C is the only language allowed here, and only as a thin, allocation-free, bounds-checked shim. The Nexmon family is the first consumer.
|
||||
- **napi-rs** — the *upward* seam to Node.js/TypeScript. Per ADR-095 D3/D4, the Rust runtime is exposed to JS via [napi-rs](https://napi.rs/); nothing crosses this seam that hasn't been validated (D6) and normalized (D5).
|
||||
|
||||
Both seams are *narrow on purpose*: everything in between — parsing, validation, DSP, windowing, event extraction, RuVector export — is safe Rust (`#![forbid(unsafe_code)]` in every crate except `rvcsi-adapter-nexmon`, which needs `extern "C"`).
|
||||
|
||||
---
|
||||
|
||||
## 2. Decision
|
||||
|
||||
### 2.1 Crate topology
|
||||
|
||||
Eight new workspace members under `v2/crates/`:
|
||||
|
||||
| Crate | `unsafe`? | Depends on | Owns |
|
||||
|-------|-----------|------------|------|
|
||||
| `rvcsi-core` | no (`forbid`) | — (serde, thiserror) | The normalized schema (`CsiFrame`/`CsiWindow`/`CsiEvent`), `AdapterProfile`, the `CsiSource` plugin trait, id newtypes + `IdGenerator`, `RvcsiError`, and the `validate_frame` pipeline + quality scoring. The shared kernel. |
|
||||
| `rvcsi-dsp` | no (`forbid`) | `rvcsi-core` | Reusable DSP stages (DC removal, phase unwrap, smoothing, Hampel/MAD outlier filter, sliding variance, baseline subtraction) and scalar features (motion energy, presence score, confidence, heuristic breathing-band estimate), plus a non-destructive `SignalPipeline::process_frame`. |
|
||||
| `rvcsi-events` | no (`forbid`) | `rvcsi-core` | `WindowBuffer` (frames → `CsiWindow`), the `EventDetector` trait + presence/motion/quality/baseline-drift state machines, and `EventPipeline` (windows → `CsiEvent`s). The baseline-drift detector measures drift **relative to the running baseline's RMS magnitude** (a fraction, not absolute amplitude units), so the same thresholds work for raw `int8` ESP32 CSI, `int16`-scaled Nexmon CSI, and baseline-subtracted streams alike — see ADR-095 D13. |
|
||||
| `rvcsi-adapter-file` | no (`forbid`) | `rvcsi-core` | The `.rvcsi` capture format (JSONL: a header line + one `CsiFrame` per line), `FileRecorder`, and `FileReplayAdapter` (a `CsiSource`) — deterministic replay (D9). |
|
||||
| `rvcsi-adapter-nexmon` | **yes** (FFI only) | `rvcsi-core` + the C shim | The **napi-c** seam: `native/rvcsi_nexmon_shim.{c,h}` compiled via `build.rs`+`cc`, a documented `ffi` module wrapping it, a pure-Rust libpcap reader (`pcap.rs`), the Nexmon-chip / Raspberry-Pi-model registry (`chips.rs` — `NexmonChip`, `RaspberryPiModel` incl. **Pi 5**, profile builders), and two `CsiSource`s — `NexmonAdapter` (rvCSI-record buffers) and `NexmonPcapAdapter` (real nexmon_csi UDP payloads inside a `.pcap`, with chip auto-detection). |
|
||||
| `rvcsi-ruvector` | no (`forbid`) | `rvcsi-core` | The RuVector RF-memory bridge: deterministic `window_embedding`/`event_embedding`, `cosine_similarity`, the `RfMemoryStore` trait, and `InMemoryRfMemory` + `JsonlRfMemory` (a standin until the production RuVector binding lands). |
|
||||
| `rvcsi-runtime` | no (`forbid`) | core, dsp, events, adapter-file, adapter-nexmon, ruvector | The composition layer (no FFI): `CaptureRuntime` (a `CsiSource` + `validate_frame` + `SignalPipeline` + `EventPipeline`) plus one-shot helpers (`summarize_capture`, `decode_nexmon_records`, `decode_nexmon_pcap`, `summarize_nexmon_pcap`, `events_from_capture`, `export_capture_to_rf_memory`). The shared layer under `rvcsi-node` and `rvcsi-cli`. |
|
||||
| `rvcsi-node` | no (`deny(clippy::all)`) | `rvcsi-core`, `rvcsi-runtime`, `rvcsi-adapter-nexmon` | The **napi-rs** seam: the `.node` addon (cdylib + rlib) exposing a safe TS-facing surface (thin `#[napi]` wrappers over `rvcsi-runtime`); `build.rs` runs `napi_build::setup()`. |
|
||||
| `rvcsi-cli` | no | core, adapter-file, adapter-nexmon, runtime | The `rvcsi` binary: `record` (Nexmon-dump or nexmon-pcap → `.rvcsi`), `inspect`, `inspect-nexmon`, `decode-chanspec`, `replay`, `stream`, `events`, `health`, `calibrate`, `export ruvector` (ADR-095 FR7). |
|
||||
|
||||
`rvcsi-events` does **not** call into `rvcsi-dsp`: window statistics are simple enough to compute in `WindowBuffer` itself, and keeping the two leaves independent removes a coordination point. `rvcsi-cli` does **not** depend on `rvcsi-node` (a binary can't link a napi cdylib's undefined Node symbols) — the shared logic lives in `rvcsi-runtime`, which both build on. Higher layers wire `SignalPipeline::process_frame` → `WindowBuffer::push` when they want cleaned frames.
|
||||
|
||||
The MCP tool server (`rvcsi-mcp`) and the long-running daemon (`rvcsi-daemon`) — and live radio capture — are *not* in this ADR's scope; they sit on top of `rvcsi-runtime` / the crates above and are tracked as follow-ups. The `@ruv/rvcsi` npm package ships alongside `rvcsi-node`.
|
||||
|
||||
### 2.2 The napi-c shim — record formats and contract
|
||||
|
||||
`native/rvcsi_nexmon_shim.{c,h}` is the only C in the runtime. It handles **two byte formats** (ABI `1.1`):
|
||||
|
||||
**(1) The "rvCSI Nexmon record"** — a compact, self-describing record (`'RVNX'` magic, version, flags, RSSI/noise, channel, bandwidth, timestamp, then interleaved `int16` I/Q in Q8.8 fixed point; total `24 + 4*N`). Used by the `rvcsi capture`/`record` recorder, the file replay path, and tests. Functions: `rvcsi_nx_record_len`, `rvcsi_nx_parse_record`, `rvcsi_nx_write_record`.
|
||||
|
||||
**(2) The *real* nexmon_csi UDP payload** — what the patched Broadcom firmware actually sends to the host (port 5500 by default): the 18-byte header `magic=0x1111 (2) · rssi int8 (1) · fctl (1) · src_mac (6) · seq_cnt (2) · core/stream (2) · chanspec (2) · chip_ver (2)`, followed by `nsub` complex CSI samples. The shim implements the **modern int16 I/Q export** (`nsub` pairs of little-endian `int16` `(real, imag)`, raw counts — what CSIKit / `csireader.py` read for the BCM43455c0 / 4358 / 4366c0); `nsub` is derived from the payload length, `(len − 18) / 4`. Functions: `rvcsi_nx_csi_udp_header` (just the 18-byte header), `rvcsi_nx_csi_udp_decode` (header + CSI body, `csi_format` selector), `rvcsi_nx_csi_udp_write` (synthesize a payload — tests/examples), and `rvcsi_nx_decode_chanspec` (decode a Broadcom d11ac chanspec word → `channel` = `chanspec & 0xff`, bandwidth from bits `[13:11]` cross-checked against the FFT size, band from bits `[15:14]` cross-checked against the channel number). The legacy nexmon *packed-float* export used by some 4339/4358 firmwares is a documented follow-up (it sits behind the same `csi_format` selector).
|
||||
|
||||
The `timestamp_ns` of a frame from format (2) comes from the **pcap packet timestamp**, not the wire (nexmon_csi doesn't carry one). The pcap file itself is parsed in **pure Rust** (`rvcsi-adapter-nexmon::pcap` — classic libpcap, all four byte-order/timestamp-resolution magics, Ethernet / raw-IPv4 / Linux-SLL link types; pcapng is a follow-up): peeling the Ethernet/IPv4/UDP headers down to the payload is not a vendor-fragility concern, so it doesn't belong in C.
|
||||
|
||||
Contract (both formats):
|
||||
|
||||
- **Allocation-free, global-free.** Every read is bounds-checked against the caller-supplied length; nothing can scribble outside caller buffers; no `malloc`, no statics.
|
||||
- **Structured errors, never panics.** Functions return one of a small set of `RvcsiNxError` codes (`TOO_SHORT`, `BAD_MAGIC`, `BAD_VERSION`, `CAPACITY`, `TRUNCATED`, `ZERO_SUBCARRIERS`, `TOO_MANY_SUBCARRIERS`, `NULL_ARG`, `BAD_NEXMON_MAGIC`, `BAD_CSI_LEN`, `UNKNOWN_FORMAT`); `rvcsi_nx_strerror` maps each to a static string.
|
||||
- **ABI versioned.** `rvcsi_nx_abi_version()` returns `major << 16 | minor` (`0x0001_0001`); the Rust side `debug_assert`s the major matches the header it was compiled against. The minor was bumped from `1.0` → `1.1` when the format-(2) entry points landed (additive — format (1) is unchanged).
|
||||
- The Rust `ffi` module wraps these in safe functions (`record_len`, `decode_record`, `encode_record`, `decode_chanspec`, `parse_nexmon_udp_header`, `decode_nexmon_udp`, `encode_nexmon_udp`, `shim_abi_version`); every `unsafe` block is limited to the FFI call (and reading back C-initialised structs) and carries a `// SAFETY:` comment, per the project rule.
|
||||
|
||||
**Chip registry (`rvcsi-adapter-nexmon::chips`).** nexmon_csi runs on a handful of patched Broadcom/Cypress chips; `NexmonChip` names them, `RaspberryPiModel` maps Pi boards to their chip, and `nexmon_adapter_profile` / `raspberry_pi_profile` build the [`AdapterProfile`] (supported channels / bandwidths / expected subcarrier counts — 20→64, 40→128, 80→256, 160→512) `validate_frame` bounds CSI frames against. The **Raspberry Pi 5** carries the same **CYW43455 / BCM43455c0** 802.11ac wireless as the Pi 3B+ / Pi 4 / Pi 400 (20/40/80 MHz, 2.4 + 5 GHz) — the chip with the most mature nexmon_csi support — so `RaspberryPiModel::Pi5 → NexmonChip::Bcm43455c0`; the Pi Zero 2 W is `Bcm43436b0` (2.4 GHz, ≤40 MHz). `NexmonPcapAdapter` **auto-detects** the chip from each packet's `chip_ver` word (`0x4345` → `Bcm43455c0`, etc.) and uses the matching profile; `.with_chip(...)` / `.with_pi_model(...)` override it. `NexmonChip::from_chip_ver` and the `chip_ver` field are best-effort/preserved respectively — the c0/b0 revision suffix isn't carried by that word, and the int16-vs-packed-float export distinction is handled by the `csi_format` selector, not by chip-ver parsing.
|
||||
|
||||
A real deployment captures with `tcpdump -i wlan0 dst port 5500 -w csi.pcap` on the Pi and feeds the `.pcap` to `NexmonPcapAdapter::open` (or `rvcsi record --source nexmon-pcap --in csi.pcap --out cap.rvcsi --chip pi5`, then the rest of the toolchain works on the `.rvcsi`; `rvcsi inspect-nexmon` reports the resolved chip, `rvcsi nexmon-chips` lists the matrix). Production *live* capture (binding the UDP socket, monitor mode, firmware patch hooks) is a later increment that reuses the same shim parse path — the shim's job is the *parse*, not the *socket*.
|
||||
|
||||
### 2.3 The napi-rs surface — what crosses the seam
|
||||
|
||||
`rvcsi-node` is a `["cdylib", "rlib"]` crate (cdylib = the `.node` addon; rlib so `cargo test --workspace` can link and test the Rust side without Node). Rules:
|
||||
|
||||
- **Only normalized/validated data crosses.** The boundary types are JS-friendly mirrors of `CsiFrame`/`CsiWindow`/`CsiEvent`/`AdapterProfile`/`SourceHealth`, or plain JSON strings — never raw pointers, never `Pending` frames. A frame is run through `rvcsi_core::validate_frame` before it is handed to JS.
|
||||
- **Errors map to JS exceptions** via napi-rs's `Result` integration; `RvcsiError`'s `Display` is the message.
|
||||
- **The build emits link args + `binding.js`/`binding.d.ts`** via `napi_build::setup()` in `build.rs`; the `@ruv/rvcsi` npm package's hand-written `index.js`/`index.d.ts` wrap that loader and `JSON.parse` the addon's returns into plain `CsiFrame`/`CsiWindow`/`CsiEvent`/`SourceHealth`/`CaptureSummary`/`NexmonPcapSummary`/`DecodedChanspec` objects.
|
||||
- The free functions exposed are: `rvcsiVersion`, `nexmonShimAbiVersion` (the linked shim's ABI), `nexmonDecodeRecords`, `nexmonDecodePcap`, `inspectNexmonPcap`, `decodeChanspec`, `inspectCaptureFile`, `eventsFromCaptureFile`, `exportCaptureToRfMemory`; plus the `RvcsiRuntime` streaming class (`openCaptureFile` / `openNexmonFile` / `openNexmonPcap` factories + `nextFrameJson` / `nextCleanFrameJson` / `drainEventsJson` / `healthJson`).
|
||||
|
||||
### 2.4 Build & test invariants
|
||||
|
||||
- `cargo build --workspace` and `cargo test --workspace --no-default-features` (the repo's pre-merge gate) must stay green; the new crates add tests and don't regress the existing 1,031+.
|
||||
- `rvcsi-node` stays a workspace *member* (not `exclude`d like `wifi-densepose-wasm-edge`): on Linux/macOS a napi cdylib links fine with Node symbols left undefined (resolved at addon-load time), so `cargo build`/`cargo test` work without a Node toolchain. Only `napi build` (npm packaging) needs Node.
|
||||
- No new heavy dependencies in the rvCSI crates: `serde`, `serde_json`, `thiserror`, `cc` (build only), `napi`/`napi-derive`/`napi-build`, `clap` (CLI only), `tempfile` (dev only). DSP math is hand-rolled — no `ndarray`/`rustfft`.
|
||||
|
||||
---
|
||||
|
||||
## 3. Consequences
|
||||
|
||||
**Positive**
|
||||
|
||||
- The two FFI seams are small, audited, and independently testable: the C shim round-trips through Rust tests; the napi surface tests run under `cargo test` without Node.
|
||||
- `unsafe` is confined to one crate (`rvcsi-adapter-nexmon`) and within it to one module (`ffi`), every block documented.
|
||||
- Each leaf crate (`rvcsi-dsp`, `rvcsi-events`, `rvcsi-adapter-file`, `rvcsi-ruvector`) depends only on `rvcsi-core`, so they can evolve (and be reviewed, and be swarm-implemented) independently.
|
||||
- The `.rvcsi` JSONL capture format and the `JsonlRfMemory` standin make the whole pipeline runnable and testable end-to-end before any hardware or the real RuVector binding exists.
|
||||
|
||||
**Negative / costs**
|
||||
|
||||
- A `cc`-built C library means a C toolchain is required to build `rvcsi-adapter-nexmon` (already true for many workspace crates via transitive `cc` deps; acceptable).
|
||||
- The "rvCSI Nexmon record" is a *normalized* format, not byte-identical to any upstream nexmon_csi build — a thin demux/transcode step is needed when wiring real Nexmon output. This is intentional (we control the contract the shim parses) and documented.
|
||||
- JSONL captures are larger than a packed binary format; fine for v0 (and the PRD already standardizes on JSON/WebSocket on the wire), revisit if capture size becomes a problem.
|
||||
- `rvcsi-node` as a workspace member adds the `napi` dependency tree to `cargo build --workspace`; mitigated by it being a small, well-maintained crate.
|
||||
|
||||
**Risks**
|
||||
|
||||
- napi-rs major-version churn could change the macro/`build.rs` surface; pinned to `napi = "2.16"` in workspace deps, bumped deliberately.
|
||||
- If a future platform can't link a napi cdylib under plain `cargo build`, `rvcsi-node` moves to the workspace `exclude` list (like `wifi-densepose-wasm-edge`) with a separate build command — same pattern, already established.
|
||||
|
||||
---
|
||||
|
||||
## 4. Alternatives considered
|
||||
|
||||
| Alternative | Why not |
|
||||
|-------------|---------|
|
||||
| One mega-crate `rvcsi` instead of eight | Couples DSP/events/adapters/FFI; can't review or implement them independently; bloats compile units for downstream users who only want `rvcsi-core`. |
|
||||
| `bindgen` for the C shim | Pulls in `libclang`; the shim's C API is six functions — hand-written `extern "C"` decls are clearer and dependency-free. |
|
||||
| Binary `.rvcsi` capture format (bincode/custom) | Smaller, but not human-inspectable; JSONL is debuggable, append-friendly, and matches the PRD's on-the-wire JSON. Revisit if size matters. |
|
||||
| Expose raw `CsiFrame` pointers / typed arrays across napi for zero-copy | Violates ADR-095 D6 (validate-before-FFI) and the "no raw pointers to TS" safety NFR; the per-frame copy cost is negligible at the target rates. |
|
||||
| `wasm-bindgen` instead of napi-rs for the JS surface | WASM can't do live capture (no raw sockets/serial); great for offline parsing (a later target) but not the primary Node runtime. |
|
||||
| `rvcsi-events` depending on `rvcsi-dsp` for window stats | Adds a coordination point for two leaf crates; the stats are a few lines — keep the leaves independent and let higher layers compose them. |
|
||||
|
||||
---
|
||||
|
||||
## 5. Status of the implementation
|
||||
|
||||
- `rvcsi-core` — implemented, `forbid(unsafe_code)`, 29 unit tests.
|
||||
- `rvcsi-adapter-nexmon` + the napi-c shim — implemented; C (ABI `1.1`) compiled via `build.rs`+`cc`; the `ffi` module wraps both record formats (rvCSI record **and** the real nexmon_csi UDP payload + chanspec decode); a pure-Rust `pcap` reader; the Nexmon-chip / Raspberry-Pi-model registry (`chips.rs` — incl. **Pi 5 → BCM43455c0** + chip auto-detection from `chip_ver`); `NexmonAdapter` + `NexmonPcapAdapter` `CsiSource`s; 28 tests, several round-tripping through the C shim and through synthetic libpcap files.
|
||||
- `rvcsi-dsp` (28 tests), `rvcsi-events` (19 tests — incl. a scale-invariance regression for the baseline-drift detector), `rvcsi-adapter-file` (20 + 1 doctest), `rvcsi-ruvector` (20 + 1 doctest) — implemented.
|
||||
- `rvcsi-runtime` (13 tests) — composition layer + the one-shot helpers, including `decode_nexmon_pcap` / `decode_nexmon_pcap_for` (per-chip) / `summarize_nexmon_pcap` / `nexmon_profile_for`.
|
||||
- `rvcsi-node` (napi-rs surface — incl. `nexmonDecodePcap` (with `chip`) / `inspectNexmonPcap` / `decodeChanspec` / `nexmonChipName` / `nexmonProfile` / `nexmonChips` / `RvcsiRuntime.openNexmonPcap`) and `rvcsi-cli` (10 tests — incl. `record --source nexmon-pcap [--chip pi5]`, `inspect-nexmon`, `nexmon-chips`, `decode-chanspec`) — implemented; the `@ruv/rvcsi` npm package + a Node smoke test ship alongside.
|
||||
- Totals: 169 rvcsi unit/integration tests + 2 doctests, 0 failures; all rvcsi crates build together and are clippy-clean.
|
||||
- **Validated against real ESP32 CSI** (a 7,000-frame node-1 capture, transcoded to `.rvcsi` via `scripts/esp32_jsonl_to_rvcsi.py` — the stand-in for the not-yet-shipped `record --source esp32-jsonl`): `rvcsi inspect` / `replay` / `calibrate` / `events` all run end-to-end. This surfaced and fixed the baseline-drift over-trigger (absolute → relative thresholds, above).
|
||||
- `rvcsi-adapter-esp32` (live serial/UDP ESP32 source — ADR-095 §1.2 / D15), `rvcsi-mcp` (MCP tool server), `rvcsi-daemon` (live capture + WebSocket), and the legacy nexmon *packed-float* CSI export — not in this PR; tracked as follow-ups.
|
||||
|
||||
---
|
||||
|
||||
## 6. References
|
||||
|
||||
- [ADR-095 — rvCSI Edge RF Sensing Platform](ADR-095-rvcsi-edge-rf-sensing-platform.md)
|
||||
- [rvCSI Platform PRD](../prd/rvcsi-platform-prd.md)
|
||||
- [rvCSI Domain Model](../ddd/rvcsi-domain-model.md)
|
||||
- napi-rs — https://napi.rs/
|
||||
- nexmon_csi — the upstream Broadcom CSI extractor the record format normalizes
|
||||
|
|
@ -105,6 +105,8 @@ Statuses: **Proposed** (under discussion), **Accepted** (approved and/or impleme
|
|||
| [ADR-011](ADR-011-python-proof-of-reality-mock-elimination.md) | Proof-of-Reality and Mock Elimination | Proposed |
|
||||
| [ADR-026](ADR-026-survivor-track-lifecycle.md) | Survivor Track Lifecycle (MAT crate) | Accepted |
|
||||
| [ADR-038](ADR-038-sublinear-goal-oriented-action-planning.md) | Sublinear GOAP for Roadmap Optimization | Proposed |
|
||||
| [ADR-095](ADR-095-rvcsi-edge-rf-sensing-platform.md) | rvCSI — Edge RF Sensing Runtime Platform | Proposed |
|
||||
| [ADR-096](ADR-096-rvcsi-ffi-crate-layout.md) | rvCSI — Crate Topology, the napi-c Shim, and the napi-rs Node Surface | Proposed |
|
||||
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ DDD organizes the codebase around the problem being solved — not around techni
|
|||
| [Sensing Server](sensing-server-domain-model.md) | Single-binary Axum server: CSI ingestion, model management, recording, training, visualization | 5 contexts: CSI Ingestion, Model Management, CSI Recording, Training Pipeline, Visualization |
|
||||
| [WiFi-Mat](wifi-mat-domain-model.md) | Disaster response: survivor detection, START triage, mass casualty assessment | 3 contexts: Detection, Localization, Alerting |
|
||||
| [CHCI](chci-domain-model.md) | Coherent Human Channel Imaging: sub-millimeter body surface reconstruction | 3 contexts: Sounding, Channel Estimation, Imaging |
|
||||
| [rvCSI](rvcsi-domain-model.md) | Edge RF sensing runtime: multi-source CSI ingestion, validation, normalization, event extraction, RuVector RF memory, agent/MCP integration | 7 contexts: Capture, Validation, Signal, Calibration, Event, Memory, Agent |
|
||||
|
||||
## How to read these
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,395 @@
|
|||
# rvCSI — Edge RF Sensing Runtime Domain Model
|
||||
|
||||
## Domain-Driven Design Specification
|
||||
|
||||
> Companion documents: [rvCSI Platform PRD](../prd/rvcsi-platform-prd.md) · [ADR-095 — rvCSI Edge RF Sensing Platform](../adr/ADR-095-rvcsi-edge-rf-sensing-platform.md)
|
||||
|
||||
### Domain
|
||||
|
||||
Camera-free RF spatial sensing from WiFi Channel State Information (CSI).
|
||||
|
||||
### Core domain
|
||||
|
||||
**RF field interpretation.** rvCSI converts noisy radio channel measurements into validated events and temporal embeddings that represent changes in physical space. CSI is treated as a *temporal delta stream* against learned baselines — not as exact vision.
|
||||
|
||||
### Supporting subdomains
|
||||
|
||||
Hardware adapter management · packet parsing · signal processing · calibration · event extraction · temporal memory · agent integration · replay and audit.
|
||||
|
||||
### Generic subdomains
|
||||
|
||||
Logging · configuration · CLI parsing · WebSocket streaming · package publishing · dashboard visualization.
|
||||
|
||||
---
|
||||
|
||||
## Ubiquitous Language
|
||||
|
||||
| Term | Definition |
|
||||
|------|------------|
|
||||
| **CSI** | Channel State Information — per-subcarrier complex channel response measured by a WiFi receiver |
|
||||
| **Source** | A physical or replayed producer of CSI frames (a NIC, an ESP32 node, a PCAP file, a recorded capture) |
|
||||
| **Adapter** | A software module that knows how to receive and decode source-specific CSI and normalize it into a `CsiFrame` |
|
||||
| **Frame** | One CSI observation at a timestamp — the unit of ingestion |
|
||||
| **Window** | A bounded sequence of frames from one source/session, used for analysis |
|
||||
| **Baseline** | The learned normal RF-field state for a space |
|
||||
| **Delta** | The measured difference of the current field from baseline |
|
||||
| **Event** | A semantic interpretation of one or more windows (presence started, motion detected, anomaly, …) |
|
||||
| **Quality score** | Confidence, in [0, 1], that a signal/frame/window is usable |
|
||||
| **Calibration** | The process of learning a stable baseline for a space |
|
||||
| **Room signature** | A vector representation of a space under normal conditions |
|
||||
| **Drift** | Slow movement of the field away from baseline |
|
||||
| **Anomaly** | A significant, unexplained deviation from baseline |
|
||||
| **RF memory** | Persisted temporal vectors and events for a physical space (stored in RuVector) |
|
||||
| **Coherence** | Consistency among sources, windows, and learned baselines |
|
||||
| **Quarantine** | A holding store for rejected/corrupt frames, kept for audit rather than discarded |
|
||||
| **Adapter profile** | A capability descriptor for a source (chip, firmware/driver versions, supported channels/bandwidths, expected subcarrier counts, capture/injection/monitor-mode support) |
|
||||
| **Calibration version** | An immutable identifier for a particular learned baseline; every event references the calibration version it was detected against |
|
||||
| **Evidence window set** | The set of `WindowId`s an event references as its justification — an event with no evidence is invalid |
|
||||
|
||||
---
|
||||
|
||||
## Bounded Contexts
|
||||
|
||||
```
|
||||
┌─────────────┐ ┌──────────────┐ ┌────────────┐ ┌──────────────┐
|
||||
│ Capture │──▶│ Validation │──▶│ Signal │──▶│ Calibration │
|
||||
│ context │ │ context │ │ context │ │ context │
|
||||
└─────────────┘ └──────────────┘ └─────┬──────┘ └──────┬───────┘
|
||||
│ │
|
||||
▼ │
|
||||
┌────────────┐ │
|
||||
│ Event │◀──────────┘
|
||||
│ context │
|
||||
└─────┬──────┘
|
||||
│
|
||||
┌─────────────┴─────────────┐
|
||||
▼ ▼
|
||||
┌────────────┐ ┌────────────┐
|
||||
│ Memory │ │ Agent │
|
||||
│ context │ │ context │
|
||||
└────────────┘ └────────────┘
|
||||
```
|
||||
|
||||
- **Capture** upstreams raw input from sources.
|
||||
- **Validation** protects every downstream context — nothing crosses into SDK/DSP/memory/agents unvalidated.
|
||||
- **Signal** turns frames into windows.
|
||||
- **Calibration** gives windows a room-specific baseline.
|
||||
- **Event** converts deltas into meaning.
|
||||
- **Memory** stores time, similarity, drift, and coherence (RuVector).
|
||||
- **Agent** exposes safe actions and queries (MCP / TypeScript).
|
||||
|
||||
---
|
||||
|
||||
### 1. Capture context
|
||||
|
||||
**Responsibility:** connect to CSI sources and produce raw frames.
|
||||
|
||||
```
|
||||
┌──────────────────────────────────────────────────────────────┐
|
||||
│ Capture Context │
|
||||
├──────────────────────────────────────────────────────────────┤
|
||||
│ ┌────────────┐ ┌─────────────────┐ ┌─────────────────┐ │
|
||||
│ │ Source │ │ CaptureSession │ │ AdapterProfile │ │
|
||||
│ │ (adapter │ │ (aggregate root)│ │ (capability │ │
|
||||
│ │ plugin) │ │ │ │ descriptor) │ │
|
||||
│ └────────────┘ └─────────────────┘ └─────────────────┘ │
|
||||
│ │
|
||||
│ CsiSource trait: open · start · next_frame · stop · health │
|
||||
└──────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
| Element | Kind | Notes |
|
||||
|---------|------|-------|
|
||||
| `Source` | Entity | A configured adapter instance bound to a device or file |
|
||||
| `CaptureSession` | Entity / **aggregate root** | Owns exactly one `AdapterProfile` and one runtime configuration |
|
||||
| `AdapterProfile` | Entity | Chip, firmware/driver versions, supported channels/bandwidths, expected subcarrier counts, capability flags |
|
||||
| `Channel`, `Bandwidth`, `FirmwareVersion`, `DriverVersion` | Value objects | Immutable |
|
||||
|
||||
**Commands:** `StartCapture` · `StopCapture` · `RestartCapture` · `InspectSource`
|
||||
**Domain events:** `CaptureStarted` · `CaptureStopped` · `SourceDisconnected` · `AdapterUnsupported`
|
||||
|
||||
---
|
||||
|
||||
### 2. Validation context
|
||||
|
||||
**Responsibility:** make frames safe and trustworthy before any language-boundary crossing.
|
||||
|
||||
| Element | Kind | Notes |
|
||||
|---------|------|-------|
|
||||
| `ValidationPolicy` | Entity | Bounds, monotonicity rules, finiteness checks, quarantine on/off |
|
||||
| `QuarantineStore` | Entity | Holds rejected/corrupt frames for audit |
|
||||
| `ValidatedFrame` | **Aggregate root** | The frame once it has passed (or been degraded by) validation |
|
||||
| `ValidationError`, `QualityScore`, `FrameBounds` | Value objects | `QualityScore` ∈ [0, 1] |
|
||||
|
||||
**Commands:** `ValidateFrame` · `QuarantineFrame`
|
||||
**Domain events:** `FrameAccepted` · `FrameRejected` · `QualityDropped`
|
||||
|
||||
---
|
||||
|
||||
### 3. Signal context
|
||||
|
||||
**Responsibility:** DSP and window features.
|
||||
|
||||
```
|
||||
Frame stream ─▶ SignalPipeline ─▶ WindowBuffer ─▶ CsiWindow
|
||||
(DC removal, phase unwrap, (mean amplitude,
|
||||
smoothing, Hampel filter, phase variance,
|
||||
variance, baseline subtraction, motion energy,
|
||||
motion energy, presence score) presence/quality scores)
|
||||
```
|
||||
|
||||
| Element | Kind | Notes |
|
||||
|---------|------|-------|
|
||||
| `SignalPipeline` | Entity | Ordered DSP stages; reuses `wifi-densepose-signal` primitives |
|
||||
| `WindowBuffer` | Entity | Accumulates frames into bounded windows |
|
||||
| `CsiWindow` | **Aggregate root** | Frames from exactly one source/session |
|
||||
| `AmplitudeVector`, `PhaseVector`, `MotionEnergy`, `PresenceScore` | Value objects | |
|
||||
|
||||
**Commands:** `ProcessFrame` · `BuildWindow` · `EstimateBaselineDelta`
|
||||
**Domain events:** `WindowReady` · `BaselineDeltaMeasured`
|
||||
|
||||
---
|
||||
|
||||
### 4. Calibration context
|
||||
|
||||
**Responsibility:** learn and version the normal RF state and room signatures.
|
||||
|
||||
| Element | Kind | Notes |
|
||||
|---------|------|-------|
|
||||
| `CalibrationProfile` | **Aggregate root** | Linked to source, room, adapter profile, configuration |
|
||||
| `RoomSignature` | Entity | Vector representation of a space under normal conditions |
|
||||
| `BaselineModel` | Entity | Statistical model of the baseline field; carries version history |
|
||||
| `CalibrationVersion`, `StabilityScore`, `RoomId` | Value objects | Calibration cannot complete if `StabilityScore` < threshold |
|
||||
|
||||
**Commands:** `StartCalibration` · `CompleteCalibration` · `UpdateBaseline` · `RejectUnstableCalibration`
|
||||
**Domain events:** `CalibrationStarted` · `CalibrationCompleted` · `CalibrationFailed` · `BaselineUpdated`
|
||||
|
||||
---
|
||||
|
||||
### 5. Event context
|
||||
|
||||
**Responsibility:** semantic event extraction with confidence and evidence.
|
||||
|
||||
| Element | Kind | Notes |
|
||||
|---------|------|-------|
|
||||
| `EventDetector` | Entity | One per event family (presence, motion, breathing, anomaly, …) |
|
||||
| `EventStateMachine` | Entity | Holds the per-source detection state; emits transitions |
|
||||
| `CsiEvent` | **Aggregate root** | Must reference ≥ 1 evidence window; confidence ∈ [0, 1]; references calibration version |
|
||||
| `Confidence`, `EvidenceWindowSet`, `EventKind` | Value objects | |
|
||||
|
||||
**Commands:** `DetectEvents` · `PublishEvent` · `SuppressEvent`
|
||||
**Domain events (the `CsiEventKind` enum):** `PresenceStarted` · `PresenceEnded` · `MotionDetected` · `MotionSettled` · `BaselineChanged` · `SignalQualityDropped` · `DeviceDisconnected` · `BreathingCandidate` · `AnomalyDetected` · `CalibrationRequired`
|
||||
|
||||
---
|
||||
|
||||
### 6. Memory context
|
||||
|
||||
**Responsibility:** RuVector storage and retrieval — RF memory.
|
||||
|
||||
| Element | Kind | Notes |
|
||||
|---------|------|-------|
|
||||
| `RfMemoryCollection` | Entity | A RuVector collection scoped to a deployment |
|
||||
| `TemporalEmbedding` | Entity | Frame / window / event embedding with timestamp |
|
||||
| `SensorGraph` | Entity | Graph of sources and their topological relationships |
|
||||
| `RoomMemory` | **Aggregate root** | Stored embeddings must be traceable to frame windows or event windows |
|
||||
| `EmbeddingVector`, `DriftScore`, `CoherenceScore` | Value objects | `DriftScore` must include the baseline version |
|
||||
|
||||
**Commands:** `StoreWindowEmbedding` · `StoreEventEmbedding` · `QuerySimilarWindows` · `ComputeDrift`
|
||||
**Domain events:** `EmbeddingStored` · `DriftDetected` · `SimilarPatternFound`
|
||||
|
||||
Data stored: frame embeddings · window embeddings · room baseline vectors · event vectors · drift snapshots · sensor-topology graph edges · source health records. Retention policy applies at collection level. No orphan embeddings.
|
||||
|
||||
---
|
||||
|
||||
### 7. Agent context
|
||||
|
||||
**Responsibility:** MCP and TypeScript agent interaction — safe actions and queries.
|
||||
|
||||
| Element | Kind | Notes |
|
||||
|---------|------|-------|
|
||||
| `AgentSubscription` | Entity | An agent's filtered stream of events |
|
||||
| `McpToolSession` | Entity | A tool invocation context with permissions |
|
||||
| `AgentSession` | **Aggregate root** | |
|
||||
| `ToolPermission`, `EventFilter`, `AgentIntent` | Value objects | `ToolPermission` distinguishes read vs. write-gated |
|
||||
|
||||
**Commands:** `SubscribeToEvents` · `RequestStatus` · `RequestCalibration` · `QueryMemory`
|
||||
**Domain events:** `AgentSubscribed` · `ToolExecuted` · `PermissionDenied`
|
||||
|
||||
**MCP tools** (read by default; write-gated marked `*`): `rvcsi_status` · `rvcsi_list_sources` · `rvcsi_start_capture *` · `rvcsi_stop_capture *` · `rvcsi_get_presence` · `rvcsi_get_recent_events` · `rvcsi_calibrate_room *` · `rvcsi_export_window *` · `rvcsi_query_ruvector` · `rvcsi_health_report`.
|
||||
|
||||
---
|
||||
|
||||
## Context Map
|
||||
|
||||
| Upstream → Downstream | Relationship | ACL / contract |
|
||||
|-----------------------|--------------|----------------|
|
||||
| Capture → Validation | Customer/Supplier | Raw frames pass through `ValidationPolicy`; only `Accepted`/`Degraded` continue |
|
||||
| Validation → Signal | Conformist (Signal accepts `ValidatedFrame` as-is) | `CsiFrame` schema is the published language |
|
||||
| Signal → Calibration | Customer/Supplier | Windows + baseline-delta measurements feed baseline modeling |
|
||||
| Calibration → Event | Customer/Supplier | Detectors declare which `CalibrationVersion` they used |
|
||||
| Signal/Event → Memory | Published Language (`EmbeddingVector`, event metadata) | `rvcsi-ruvector` ACL translates to RuVector's API |
|
||||
| Event → Agent | Open Host Service (event stream + MCP tools) | `EventFilter` + `ToolPermission` enforced at the boundary |
|
||||
| Capture → Agent | Conformist (health/status only, via MCP read tools) | No raw frames cross to agents |
|
||||
|
||||
The **`CsiFrame` schema is the shared kernel** between Capture, Validation, Signal, and the language-boundary (napi-rs) layer. It is the FFI-safe object; nothing device-specific leaks past it.
|
||||
|
||||
---
|
||||
|
||||
## Aggregates and Invariants
|
||||
|
||||
### `CaptureSession` aggregate
|
||||
|
||||
**Invariant:** a capture session has exactly one source profile and one runtime configuration.
|
||||
|
||||
1. A session cannot emit frames before it is started.
|
||||
2. A session cannot change channel without restart unless the adapter supports dynamic retune.
|
||||
3. A session must emit `SourceDisconnected` before stopping due to device loss.
|
||||
|
||||
### `ValidatedFrame` aggregate
|
||||
|
||||
**Invariant:** no frame crosses into SDK, DSP, memory, or agents unless its validation status is `Accepted` or `Degraded`.
|
||||
|
||||
1. Rejected frames go to quarantine when quarantine is enabled.
|
||||
2. Degraded frames must carry quality-reason metadata.
|
||||
3. Missing *optional* hardware metadata must not invalidate a frame.
|
||||
|
||||
### `CsiWindow` aggregate
|
||||
|
||||
**Invariant:** a window contains frames from exactly one source and one session.
|
||||
|
||||
1. Mixed-source windows are not allowed.
|
||||
2. Window start time must be strictly less than end time.
|
||||
3. Window quality is bounded in [0, 1].
|
||||
|
||||
### `CalibrationProfile` aggregate
|
||||
|
||||
**Invariant:** a calibration profile is linked to source, room, adapter profile, and configuration.
|
||||
|
||||
1. Calibration cannot complete if `StabilityScore` is below threshold.
|
||||
2. Baseline updates must preserve version history.
|
||||
3. Event detectors must declare which calibration version they used.
|
||||
|
||||
### `CsiEvent` aggregate
|
||||
|
||||
**Invariant:** an event must have evidence.
|
||||
|
||||
1. Every event references at least one evidence window.
|
||||
2. Confidence is bounded in [0, 1].
|
||||
3. Event suppression must be explainable by policy.
|
||||
|
||||
### `RoomMemory` aggregate
|
||||
|
||||
**Invariant:** stored embeddings are traceable to frame windows or event windows.
|
||||
|
||||
1. No orphan embeddings.
|
||||
2. Retention policy applies at collection level.
|
||||
3. Drift scores must include the baseline version.
|
||||
|
||||
---
|
||||
|
||||
## Data Model
|
||||
|
||||
```rust
|
||||
pub struct CsiFrame {
|
||||
pub frame_id: FrameId,
|
||||
pub session_id: SessionId,
|
||||
pub source_id: SourceId,
|
||||
pub adapter_kind: AdapterKind,
|
||||
pub timestamp_ns: u64,
|
||||
pub channel: u16,
|
||||
pub bandwidth_mhz: u16,
|
||||
pub rssi_dbm: Option<i16>,
|
||||
pub noise_floor_dbm: Option<i16>,
|
||||
pub antenna_index: Option<u8>,
|
||||
pub tx_chain: Option<u8>,
|
||||
pub rx_chain: Option<u8>,
|
||||
pub subcarrier_count: u16,
|
||||
pub i_values: Vec<f32>,
|
||||
pub q_values: Vec<f32>,
|
||||
pub amplitude: Vec<f32>,
|
||||
pub phase: Vec<f32>,
|
||||
pub validation: ValidationStatus,
|
||||
pub quality_score: f32,
|
||||
pub calibration_version: Option<String>,
|
||||
}
|
||||
|
||||
pub struct CsiWindow {
|
||||
pub window_id: WindowId,
|
||||
pub session_id: SessionId,
|
||||
pub source_id: SourceId,
|
||||
pub start_ns: u64,
|
||||
pub end_ns: u64,
|
||||
pub frame_count: u32,
|
||||
pub mean_amplitude: Vec<f32>,
|
||||
pub phase_variance: Vec<f32>,
|
||||
pub motion_energy: f32,
|
||||
pub presence_score: f32,
|
||||
pub quality_score: f32,
|
||||
}
|
||||
|
||||
pub enum CsiEventKind {
|
||||
PresenceStarted,
|
||||
PresenceEnded,
|
||||
MotionDetected,
|
||||
MotionSettled,
|
||||
BaselineChanged,
|
||||
SignalQualityDropped,
|
||||
DeviceDisconnected,
|
||||
BreathingCandidate,
|
||||
AnomalyDetected,
|
||||
CalibrationRequired,
|
||||
}
|
||||
|
||||
pub struct CsiEvent {
|
||||
pub event_id: EventId,
|
||||
pub kind: CsiEventKind,
|
||||
pub session_id: SessionId,
|
||||
pub source_id: SourceId,
|
||||
pub timestamp_ns: u64,
|
||||
pub confidence: f32,
|
||||
pub evidence_window_ids: Vec<WindowId>,
|
||||
pub metadata_json: String,
|
||||
}
|
||||
|
||||
pub struct AdapterProfile {
|
||||
pub adapter_kind: AdapterKind,
|
||||
pub chip: Option<String>,
|
||||
pub firmware_version: Option<String>,
|
||||
pub driver_version: Option<String>,
|
||||
pub supported_channels: Vec<u16>,
|
||||
pub supported_bandwidths_mhz: Vec<u16>,
|
||||
pub expected_subcarrier_counts: Vec<u16>,
|
||||
pub supports_live_capture: bool,
|
||||
pub supports_injection: bool,
|
||||
pub supports_monitor_mode: bool,
|
||||
}
|
||||
|
||||
pub enum ValidationStatus { Accepted, Degraded, Rejected, Recovered }
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Domain Services
|
||||
|
||||
| Service | Input | Output | Responsibility |
|
||||
|---------|-------|--------|----------------|
|
||||
| `FrameValidationService` | `RawFrame`, `AdapterProfile`, `ValidationPolicy` | `ValidatedFrame` or `RejectedFrame` | Enforce bounds, finiteness, monotonicity; assign initial `QualityScore`; route rejects to quarantine; emit structured errors |
|
||||
| `SignalProcessingService` | `ValidatedFrame` stream | `CsiWindow` stream | Run the DSP pipeline; build bounded windows; compute motion energy, presence score, window quality |
|
||||
| `BaselineDeltaService` | `CsiWindow`, `BaselineModel` | `BaselineDelta` | Subtract the calibrated baseline; measure deviation magnitude |
|
||||
| `CalibrationService` | `CsiWindow` stream over a calibration window | `CalibrationProfile` (new version) or `CalibrationFailed` | Learn a stable baseline; compute `StabilityScore`; reject unstable calibrations; preserve version history |
|
||||
| `EventDetectionService` | `CsiWindow` + `BaselineDelta` + `CalibrationVersion` | `CsiEvent` stream | Drive per-source state machines; attach confidence + evidence windows + calibration version; apply suppression policy |
|
||||
| `EmbeddingService` | `CsiWindow` / `CsiEvent` | `TemporalEmbedding` | Produce frame/window/event vectors (v0: deterministic DSP feature vector; later: AETHER / on-device model) |
|
||||
| `RfMemoryService` | `TemporalEmbedding`, query | `EmbeddingStored` / similar windows / `DriftScore` | Store to RuVector; similarity search; drift computation against a baseline version |
|
||||
| `ReplayService` | A captured session bundle | A deterministic frame/window/event stream | Replay preserving timestamps, ordering, validation decisions, event output, calibration version, runtime config |
|
||||
| `AdapterRegistryService` | — | List of available adapters + `AdapterProfile`s | Discover sources (reuses ADR-049 interface detection); report health; flag unsupported firmware/driver state |
|
||||
| `AgentGatewayService` | MCP tool call / SDK subscription | Tool result / filtered event stream | Enforce `ToolPermission` (read vs. write-gated), apply `EventFilter`, audit `ToolExecuted` / `PermissionDenied` |
|
||||
|
||||
---
|
||||
|
||||
## Related
|
||||
|
||||
- [rvCSI Platform PRD](../prd/rvcsi-platform-prd.md) — requirements, success criteria, scope
|
||||
- [ADR-095 — rvCSI Edge RF Sensing Platform](../adr/ADR-095-rvcsi-edge-rf-sensing-platform.md) — the fifteen architectural decisions
|
||||
- [RuvSense Domain Model](ruvsense-domain-model.md) — adjacent multistatic sensing context
|
||||
- [Signal Processing Domain Model](signal-processing-domain-model.md) — the DSP primitives `rvcsi-dsp` reuses
|
||||
- [ADR Index](../adr/README.md)
|
||||
|
|
@ -0,0 +1,376 @@
|
|||
# rvCSI — Edge RF Sensing Runtime
|
||||
|
||||
## Product Design Requirements (PRD)
|
||||
|
||||
| Field | Value |
|
||||
|-------|-------|
|
||||
| **Product name** | rvCSI |
|
||||
| **Category** | Edge RF sensing runtime and developer platform |
|
||||
| **Status** | Proposed (v0 design) |
|
||||
| **Date** | 2026-05-12 |
|
||||
| **Owner** | ruv |
|
||||
| **Relates to** | [ADR-095](../adr/ADR-095-rvcsi-edge-rf-sensing-platform.md) (rvCSI platform), [ADR-012](../adr/ADR-012-esp32-csi-sensor-mesh.md) (ESP32 mesh), [ADR-013](../adr/ADR-013-feature-level-sensing-commodity-gear.md) (feature-level sensing), [ADR-014](../adr/ADR-014-sota-signal-processing.md) (SOTA signal processing), [ADR-016](../adr/ADR-016-ruvector-integration.md) (RuVector integration), [ADR-024](../adr/ADR-024-contrastive-csi-embedding-model.md) (AETHER embeddings), [ADR-031](../adr/ADR-031-ruview-sensing-first-rf-mode.md) (RuView sensing-first RF mode), [ADR-040](../adr/ADR-040-wasm-programmable-sensing.md) (WASM programmable sensing) |
|
||||
| **Domain model** | [rvCSI Domain Model](../ddd/rvcsi-domain-model.md) |
|
||||
|
||||
---
|
||||
|
||||
## 1. Purpose
|
||||
|
||||
rvCSI is a **Rust-first, TypeScript-accessible, hardware-abstracted Channel State Information (CSI) platform** for WiFi-based spatial sensing.
|
||||
|
||||
The goal is to convert CSI from fragile research data into a durable edge sensing runtime that can feed RuView, RuVector, Cognitum, and agentic systems with validated live radio-field observations.
|
||||
|
||||
rvCSI does **not** try to replace Nexmon on day one. It wraps, validates, normalizes, streams, embeds, and learns from CSI produced by Nexmon, ESP32 CSI, Intel CSI, Atheros CSI, SDR pipelines, and future RF sensor sources.
|
||||
|
||||
### 1.1 System framing
|
||||
|
||||
CSI is treated as a **physical-world delta stream**.
|
||||
|
||||
A room, hallway, vehicle, warehouse, machine bay, or care facility has a radio-field baseline. Human motion, breathing, door movement, equipment vibration, device movement, and environmental change perturb that baseline. rvCSI captures those perturbations, normalizes them into tensors, converts them into events, stores them as temporal memory, and exposes them to agents.
|
||||
|
||||
The core invariant:
|
||||
|
||||
| Layer | Owns |
|
||||
|-------|------|
|
||||
| **C** | Fragile vendor and firmware compatibility |
|
||||
| **Rust** | Safety, validation, signal processing, memory discipline, deterministic runtime behavior |
|
||||
| **TypeScript** | Developer experience, orchestration, dashboards, SDKs, agent integration |
|
||||
| **RuVector** | Memory, similarity, drift, graph relationships, coherence over time |
|
||||
| **Cognitum** | Low-power event-driven deployment, local decision loops |
|
||||
|
||||
### 1.2 Strategic framing
|
||||
|
||||
Most CSI projects today are Linux shell scripts, kernel patching, Python notebooks, PCAP dumps, and ad-hoc signal processing. A Rust + TypeScript + napi-rs architecture turns CSI into **real-time sensor infrastructure**: npm-installable, reproducible, typed, safe-parsed, embeddable, WebSocket-streamable, WASM-portable, MCP-exposed, agent-integrable, and edge/cloud-federated.
|
||||
|
||||
The right framing is **structural sensing**, not "magic X-ray vision". CSI is excellent for detecting change, presence, and learned patterns; it is weak for exact identity, exact pose, legal/security certainty, and highly dynamic RF spaces. rvCSI's product claims stay inside that boundary (see Non-goals, §6).
|
||||
|
||||
---
|
||||
|
||||
## 2. Users
|
||||
|
||||
| User | Need |
|
||||
|------|------|
|
||||
| AI engineers building physical-world agents | A stable sensing primitive that emits typed events agents can react to |
|
||||
| Researchers working with WiFi CSI and RF sensing | Reproducible ingestion, replay, and benchmark datasets |
|
||||
| Smart-building and elder-care solution builders | Privacy-preserving presence/motion/breathing without cameras |
|
||||
| Industrial monitoring teams | Camera-free movement/anomaly detection that runs unattended |
|
||||
| Developers using RuView / RuVector / Cognitum | A drop-in source of RF observations for the broader ruvnet stack |
|
||||
|
||||
---
|
||||
|
||||
## 3. Problem & Hypothesis
|
||||
|
||||
**Problem.** WiFi CSI is useful but hard to operationalize. Most CSI pipelines are built from fragile scripts, patched firmware, lab notebooks, inconsistent packet formats, unstable drivers, and device-specific assumptions. This makes CSI difficult to deploy outside research settings. The system needs a production-grade runtime that can ingest CSI from multiple sources, validate packets, normalize formats, stream typed events, support signal processing, and feed vector-based learning systems.
|
||||
|
||||
**Hypothesis.** If rvCSI provides a stable Rust core with TypeScript APIs and hardware adapters, then CSI can become a reusable sensing primitive for camera-free spatial intelligence.
|
||||
|
||||
---
|
||||
|
||||
## 4. Success criteria
|
||||
|
||||
1. A developer can install rvCSI and parse recorded CSI files in **under five minutes**.
|
||||
2. A supported live device can stream **validated** CSI frames into TypeScript.
|
||||
3. Bad packets **cannot crash** the process.
|
||||
4. The same application code consumes CSI from Nexmon, ESP32, Intel, or Atheros adapters.
|
||||
5. Presence and motion detection work from **normalized tensors**, not device-specific raw packets.
|
||||
6. rvCSI can publish embeddings and event summaries into **RuVector**.
|
||||
7. rvCSI can run as a **local daemon on Raspberry Pi-class hardware**.
|
||||
8. rvCSI can expose events to **MCP tools and local agents**.
|
||||
|
||||
---
|
||||
|
||||
## 5. Scope
|
||||
|
||||
### 5.1 Version zero — safe ingestion, normalized data, live streaming, SDK usability, RuVector integration
|
||||
|
||||
1. Recorded CSI file parser
|
||||
2. Live capture adapter for existing Nexmon CSI output where supported
|
||||
3. ESP32 CSI adapter
|
||||
4. Unified CSI frame schema
|
||||
5. Rust validation pipeline
|
||||
6. TypeScript SDK through napi-rs
|
||||
7. CLI for capture, inspect, replay, stream
|
||||
8. WebSocket output
|
||||
9. Presence and motion baseline detectors
|
||||
10. RuVector export interface
|
||||
11. Basic calibration model
|
||||
12. Hardware and driver health checks
|
||||
|
||||
### 5.2 Version one
|
||||
|
||||
1. Multi-node synchronization
|
||||
2. RF room signatures
|
||||
3. Breathing-rate estimation where signal quality permits
|
||||
4. Temporal embeddings
|
||||
5. Drift detection
|
||||
6. Graph-based room topology
|
||||
7. Local MCP tool server
|
||||
8. Replayable benchmark datasets
|
||||
9. Sensor fusion with RuView
|
||||
10. Deployment profile for Cognitum Seed and Appliance
|
||||
|
||||
### 5.3 Version two
|
||||
|
||||
1. Hardware-agnostic RF sensor fabric
|
||||
2. Multi-room RF memory
|
||||
3. Streaming anomaly detection
|
||||
4. RF SLAM research mode
|
||||
5. On-device embedding model
|
||||
6. Federated learning of room signatures
|
||||
7. Secure signed sensor-evidence records
|
||||
8. Proof-gated event publication
|
||||
9. Dynamic cut-based coherence over RF graphs
|
||||
10. Agent-driven calibration and self-repair
|
||||
|
||||
---
|
||||
|
||||
## 6. Non-goals (version zero)
|
||||
|
||||
1. Pure-Rust replacement for Broadcom firmware patches
|
||||
2. Universal support for all WiFi chips
|
||||
3. Identity recognition from RF signals
|
||||
4. Medical-grade vital-sign diagnosis
|
||||
5. Legal-grade occupancy proof
|
||||
6. Guaranteed through-wall pose detection
|
||||
7. Cloud dependency
|
||||
8. Camera-replacement claims
|
||||
|
||||
---
|
||||
|
||||
## 7. Functional requirements
|
||||
|
||||
### FR1 — CSI ingestion
|
||||
|
||||
rvCSI shall ingest CSI from multiple sources. Initial source types: recorded binary dump, PCAP file, Nexmon CSI live stream, ESP32 CSI serial/UDP stream, Intel CSI logs (where supported), Atheros CSI logs (where supported). **Output:** a normalized `CsiFrame` object.
|
||||
|
||||
### FR2 — Packet validation
|
||||
|
||||
rvCSI shall validate every frame before exposing it to TypeScript or RuVector:
|
||||
|
||||
1. Frame length must match declared schema.
|
||||
2. Subcarrier count must be inside adapter-profile limits.
|
||||
3. Timestamp must be monotonic within a capture session unless marked as recovered.
|
||||
4. RSSI must be within plausible device bounds.
|
||||
5. Complex values must be finite.
|
||||
6. Corrupt frames must be rejected or quarantined.
|
||||
7. Parser failures must return structured errors.
|
||||
|
||||
### FR3 — Normalized frame schema
|
||||
|
||||
rvCSI shall normalize all hardware output into a common schema. Required fields: `frame_id`, `session_id`, `source_id`, `adapter_kind`, `timestamp_ns`, `channel`, `bandwidth_mhz`, `rssi_dbm`, `noise_floor_dbm` (when available), `antenna_index` (when available), `tx_chain` (when available), `rx_chain` (when available), `subcarrier_count`, `i_values`, `q_values`, `amplitude`, `phase`, `validation_status`, `quality_score`, `calibration_version`.
|
||||
|
||||
### FR4 — Signal processing
|
||||
|
||||
rvCSI shall provide reusable Rust signal-processing stages: DC offset removal, phase unwrap, amplitude smoothing, Hampel/median outlier filter, short-window variance, baseline subtraction, motion energy, presence score, breathing-band estimator (where supported), confidence scoring.
|
||||
|
||||
### FR5 — Event extraction
|
||||
|
||||
rvCSI shall convert frame streams into typed events: `PresenceStarted`, `PresenceEnded`, `MotionDetected`, `MotionSettled`, `BaselineChanged`, `SignalQualityDropped`, `DeviceDisconnected`, `BreathingCandidate`, `AnomalyDetected`, `CalibrationRequired`.
|
||||
|
||||
### FR6 — TypeScript SDK
|
||||
|
||||
rvCSI shall expose a TypeScript SDK:
|
||||
|
||||
```ts
|
||||
import { RvCsi } from "@ruv/rvcsi";
|
||||
|
||||
const sensor = await RvCsi.open({
|
||||
source: "nexmon",
|
||||
iface: "wlan0",
|
||||
channel: 6,
|
||||
bandwidthMHz: 20,
|
||||
});
|
||||
|
||||
sensor.on("frame", (frame) => {
|
||||
console.log(frame.qualityScore);
|
||||
});
|
||||
|
||||
sensor.on("presence", (event) => {
|
||||
console.log(event.confidence);
|
||||
});
|
||||
|
||||
await sensor.start();
|
||||
```
|
||||
|
||||
### FR7 — CLI
|
||||
|
||||
```bash
|
||||
rvcsi inspect file sample.csi
|
||||
rvcsi capture start --source nexmon --iface wlan0 --channel 6
|
||||
rvcsi replay sample.csi --speed 1x
|
||||
rvcsi stream --format json --port 8787
|
||||
rvcsi calibrate --room livingroom --duration 60
|
||||
rvcsi health --source nexmon
|
||||
rvcsi export ruvector --collection room_rf
|
||||
```
|
||||
|
||||
### FR8 — RuVector integration
|
||||
|
||||
rvCSI shall export temporal RF embeddings and event metadata to RuVector. Data stored: frame embeddings, window embeddings, room baseline vectors, event vectors, drift snapshots, sensor-topology graph edges, source health records.
|
||||
|
||||
### FR9 — MCP integration
|
||||
|
||||
rvCSI shall expose MCP tools for local agents: `rvcsi_status`, `rvcsi_list_sources`, `rvcsi_start_capture`, `rvcsi_stop_capture`, `rvcsi_get_presence`, `rvcsi_get_recent_events`, `rvcsi_calibrate_room`, `rvcsi_export_window`, `rvcsi_query_ruvector`, `rvcsi_health_report`. Tools default to read actions; capture start/stop, calibration, and export are write-gated.
|
||||
|
||||
### FR10 — Replay and audit
|
||||
|
||||
rvCSI shall support deterministic replay of captured sessions, preserving: original timestamps, frame ordering, validation decisions, event-extraction output, calibration version, runtime configuration.
|
||||
|
||||
---
|
||||
|
||||
## 8. Non-functional requirements
|
||||
|
||||
### 8.1 Safety
|
||||
|
||||
1. TypeScript shall never receive raw unchecked pointers.
|
||||
2. Rust shall validate all frames before the FFI boundary export.
|
||||
3. C shims shall be minimal and isolated.
|
||||
4. All `unsafe` blocks shall be documented.
|
||||
5. Fuzz tests shall cover parsers.
|
||||
|
||||
### 8.2 Performance (v0 targets)
|
||||
|
||||
1. Parse one CSI frame in **< 1 ms** on Raspberry Pi 5.
|
||||
2. Sustain **≥ 1000 frames/s** on Pi 5 for normalized parsing.
|
||||
3. Keep memory **< 256 MB** for one active source.
|
||||
4. Keep event latency **< 50 ms** for presence and motion.
|
||||
5. Avoid heap growth during steady capture.
|
||||
|
||||
### 8.3 Reliability
|
||||
|
||||
1. Bad packets shall not crash the daemon.
|
||||
2. Device disconnect shall produce a typed event.
|
||||
3. Capture sessions shall be restartable.
|
||||
4. Logs shall include source, adapter, session, and validation details.
|
||||
5. Health checks shall identify unsupported firmware or driver state.
|
||||
|
||||
### 8.4 Privacy
|
||||
|
||||
1. rvCSI shall operate locally by default.
|
||||
2. No cloud endpoint shall be required.
|
||||
3. Raw CSI export shall be disableable by policy.
|
||||
4. Event-level export shall be supported for privacy-preserving deployments.
|
||||
5. Retention policies shall be configurable.
|
||||
|
||||
### 8.5 Security
|
||||
|
||||
1. Device-control operations shall require explicit permission.
|
||||
2. Firmware-installation operations shall be separated from capture operations.
|
||||
3. Signed capture profiles shall be supported in later versions.
|
||||
4. MCP tools shall mark write actions as gated.
|
||||
5. File parsing shall be fuzzed and sandbox-friendly.
|
||||
|
||||
### 8.6 Portability
|
||||
|
||||
1. Linux first.
|
||||
2. Raspberry Pi first among edge devices.
|
||||
3. macOS and Windows support for file replay and SDK development.
|
||||
4. Live-capture support depends on adapter and driver capability.
|
||||
5. WASM support for offline parsing and visualization is a later target.
|
||||
|
||||
---
|
||||
|
||||
## 9. System architecture
|
||||
|
||||
### 9.1 High-level pipeline
|
||||
|
||||
```
|
||||
CSI Source
|
||||
↓
|
||||
Adapter Layer (vendor-specific decode, C shims isolated here)
|
||||
↓
|
||||
Rust Validation Pipeline (bounds, finiteness, monotonicity, quarantine)
|
||||
↓
|
||||
Normalized CSI Frame (CsiFrame schema — the FFI-safe boundary object)
|
||||
↓
|
||||
Signal Processing (DC removal, phase unwrap, smoothing, motion energy …)
|
||||
↓
|
||||
Window Aggregator (bounded frame sequences → CsiWindow)
|
||||
↓
|
||||
Event Extractor (state machines → CsiEvent with confidence + evidence)
|
||||
↓
|
||||
TypeScript SDK · CLI · MCP · RuVector
|
||||
```
|
||||
|
||||
### 9.2 Runtime components
|
||||
|
||||
| # | Component | Role |
|
||||
|---|-----------|------|
|
||||
| 1 | `rvcsi-core` | Frame types, parser traits, validation, quality scoring, shared abstractions |
|
||||
| 2 | `rvcsi-adapter-*` | Rust/C-backed adapters: Nexmon, ESP32, Intel, Atheros, files, replay |
|
||||
| 3 | `rvcsi-dsp` | Rust signal-processing primitives |
|
||||
| 4 | `rvcsi-events` | Windowing, baseline modeling, event extraction, state machines |
|
||||
| 5 | `rvcsi-node` | napi-rs bindings exposing safe APIs to Node.js |
|
||||
| 6 | `rvcsi-sdk` | TypeScript SDK |
|
||||
| 7 | `rvcsi-cli` | Command-line interface |
|
||||
| 8 | `rvcsi-daemon` | Long-running capture and event service |
|
||||
| 9 | `rvcsi-mcp` | MCP tool server |
|
||||
| 10 | `rvcsi-ruvector` | Exporter and query bridge |
|
||||
|
||||
### 9.3 Reference repository layout
|
||||
|
||||
```
|
||||
rvcsi/
|
||||
crates/
|
||||
rvcsi-core/
|
||||
rvcsi-adapter-file/
|
||||
rvcsi-adapter-nexmon/
|
||||
rvcsi-adapter-esp32/
|
||||
rvcsi-dsp/
|
||||
rvcsi-events/
|
||||
rvcsi-ruvector/
|
||||
rvcsi-daemon/
|
||||
rvcsi-node/
|
||||
rvcsi-mcp/
|
||||
packages/
|
||||
sdk/
|
||||
cli/
|
||||
dashboard/
|
||||
native/
|
||||
nexmon-shim-c/
|
||||
docs/
|
||||
adr/
|
||||
ddd/
|
||||
prd/
|
||||
benchmarks/
|
||||
testdata/
|
||||
captures/
|
||||
malformed/
|
||||
replay/
|
||||
```
|
||||
|
||||
> Within the RuView monorepo, rvCSI would be introduced as a new bounded context (see the [domain model](../ddd/rvcsi-domain-model.md)) and a small set of `v2/crates/rvcsi-*` crates, reusing existing `wifi-densepose-signal` DSP and `wifi-densepose-ruvector` integration where they overlap rather than duplicating them.
|
||||
|
||||
---
|
||||
|
||||
## 10. Data model (summary)
|
||||
|
||||
The authoritative definitions live in the [rvCSI domain model](../ddd/rvcsi-domain-model.md). Summary:
|
||||
|
||||
- **`CsiFrame`** — one validated CSI observation at a timestamp (the FFI-safe object). Carries I/Q, amplitude, phase, RSSI, channel/bandwidth, optional antenna/chain metadata, validation status, quality score, calibration version.
|
||||
- **`CsiWindow`** — a bounded sequence of frames from one source/session, with mean amplitude, phase variance, motion energy, presence score, quality score.
|
||||
- **`CsiEvent`** — a semantic interpretation of one or more windows, with `kind`, confidence, evidence window IDs, and metadata.
|
||||
- **`AdapterProfile`** — capability descriptor for a source: chip, firmware/driver versions, supported channels/bandwidths, expected subcarrier counts, capture/injection/monitor-mode support.
|
||||
|
||||
---
|
||||
|
||||
## 11. Open questions
|
||||
|
||||
1. **Embedding model.** What produces frame/window embeddings in v0 — a fixed DSP feature vector, the existing AETHER contrastive model (ADR-024), or a lightweight on-device model? v0 leans on a deterministic DSP feature vector; v2 targets an on-device model.
|
||||
2. **Calibration UX.** How long must a calibration window be before `StabilityScore` is trustworthy, and how is that surfaced in the SDK/CLI?
|
||||
3. **Nexmon coupling.** Which Nexmon-supported chips/firmwares are in the v0 "supported" matrix vs. "best effort"?
|
||||
4. **Monorepo vs. standalone.** Does rvCSI ship as `v2/crates/rvcsi-*` inside RuView or as a separate `rvcsi/` repo? This PRD assumes monorepo crates that reuse `wifi-densepose-signal` and `wifi-densepose-ruvector`.
|
||||
5. **MCP transport.** stdio-only for v1, or also a local socket for multi-agent fan-out?
|
||||
|
||||
---
|
||||
|
||||
## 12. References
|
||||
|
||||
- [ADR-095 — rvCSI Edge RF Sensing Platform](../adr/ADR-095-rvcsi-edge-rf-sensing-platform.md)
|
||||
- [rvCSI Domain Model](../ddd/rvcsi-domain-model.md)
|
||||
- [ADR-013 — Feature-Level Sensing on Commodity Gear](../adr/ADR-013-feature-level-sensing-commodity-gear.md)
|
||||
- [ADR-014 — SOTA Signal Processing](../adr/ADR-014-sota-signal-processing.md)
|
||||
- [ADR-016 — RuVector Integration](../adr/ADR-016-ruvector-integration.md)
|
||||
- [ADR-024 — Project AETHER: Contrastive CSI Embeddings](../adr/ADR-024-contrastive-csi-embedding-model.md)
|
||||
- [ADR-031 — RuView Sensing-First RF Mode](../adr/ADR-031-ruview-sensing-first-rf-mode.md)
|
||||
- [ADR-040 — WASM Programmable Sensing](../adr/ADR-040-wasm-programmable-sensing.md)
|
||||
|
|
@ -0,0 +1,188 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Transcode an ESP32 .csi.jsonl recording into a .rvcsi capture (JSONL).
|
||||
|
||||
This is the moral equivalent of `rvcsi record --source esp32-jsonl` (which the
|
||||
PR does not ship yet): parse each ESP32 frame, derive amplitude/phase from the
|
||||
raw int8 I/Q pairs, run the same validation/quality logic rvcsi_core does, and
|
||||
write a .rvcsi file whose first line is a CaptureHeader and every later line a
|
||||
CsiFrame. Rejected frames are dropped (quarantine), like the real pipeline.
|
||||
|
||||
Usage: esp32_jsonl_to_rvcsi.py <in.csi.jsonl> <out.rvcsi> [--limit N]
|
||||
"""
|
||||
import json
|
||||
import math
|
||||
import sys
|
||||
|
||||
# --- rvcsi_core::ValidationPolicy::default() -------------------------------
|
||||
MIN_SUBCARRIERS = 1
|
||||
MAX_SUBCARRIERS = 4096
|
||||
RSSI_LO, RSSI_HI = -110, 0
|
||||
MIN_QUALITY = 0.25
|
||||
RSSI_HARD_MARGIN = 30
|
||||
|
||||
|
||||
def quality_and_status(amplitude, rssi_dbm):
|
||||
"""Faithful port of rvcsi_core::validation::validate_frame soft scoring."""
|
||||
reasons = []
|
||||
q = 1.0
|
||||
sc = len(amplitude)
|
||||
# out-of-range (non-fatal) RSSI
|
||||
if rssi_dbm is not None and (rssi_dbm < RSSI_LO or rssi_dbm > RSSI_HI):
|
||||
q *= 0.6
|
||||
reasons.append(f"rssi {rssi_dbm} dBm outside [{RSSI_LO},{RSSI_HI}]")
|
||||
# dead subcarriers
|
||||
dead = sum(1 for a in amplitude if a < 1e-6)
|
||||
if dead > 0:
|
||||
frac = dead / max(sc, 1)
|
||||
q *= max(1.0 - frac, 0.05)
|
||||
reasons.append(f"{dead}/{sc} dead subcarriers")
|
||||
# amplitude spike vs median
|
||||
if sc >= 3:
|
||||
s = sorted(amplitude)
|
||||
median = max(s[sc // 2], 1e-9)
|
||||
mx = s[-1]
|
||||
if mx > median * 50.0:
|
||||
q *= 0.7
|
||||
reasons.append(f"amplitude spike: max {mx:.3f} vs median {median:.3f}")
|
||||
if rssi_dbm is None:
|
||||
q *= 0.95
|
||||
reasons.append("missing rssi")
|
||||
q = min(max(q, 0.0), 1.0)
|
||||
if q < MIN_QUALITY:
|
||||
status = "Degraded" # degrade_instead_of_reject = true
|
||||
else:
|
||||
status = "Accepted"
|
||||
return q, status, reasons
|
||||
|
||||
|
||||
def main():
|
||||
if len(sys.argv) < 3:
|
||||
print(__doc__)
|
||||
sys.exit(2)
|
||||
in_path, out_path = sys.argv[1], sys.argv[2]
|
||||
limit = None
|
||||
if "--limit" in sys.argv:
|
||||
limit = int(sys.argv[sys.argv.index("--limit") + 1])
|
||||
|
||||
source_id = "esp32-com7-rec"
|
||||
header = {
|
||||
"rvcsi_capture_version": 1,
|
||||
"session_id": 0,
|
||||
"source_id": source_id,
|
||||
"adapter_profile": {
|
||||
"adapter_kind": "Esp32",
|
||||
"chip": "ESP32-S3",
|
||||
"firmware_version": None,
|
||||
"driver_version": None,
|
||||
"supported_channels": [],
|
||||
"supported_bandwidths_mhz": [],
|
||||
"expected_subcarrier_counts": [],
|
||||
"supports_live_capture": True,
|
||||
"supports_injection": False,
|
||||
"supports_monitor_mode": False,
|
||||
},
|
||||
"validation_policy": {
|
||||
"min_subcarriers": MIN_SUBCARRIERS,
|
||||
"max_subcarriers": MAX_SUBCARRIERS,
|
||||
"rssi_dbm_bounds": [RSSI_LO, RSSI_HI],
|
||||
"strict_monotonic_time": False,
|
||||
"degrade_instead_of_reject": True,
|
||||
"min_quality": MIN_QUALITY,
|
||||
},
|
||||
"calibration_version": None,
|
||||
"runtime_config_json": "{}",
|
||||
"created_unix_ns": 0,
|
||||
}
|
||||
|
||||
stats = {
|
||||
"read": 0, "written": 0,
|
||||
"rej_len": 0, "rej_sc": 0, "rej_nonfinite": 0, "rej_rssi": 0,
|
||||
"accepted": 0, "degraded": 0,
|
||||
}
|
||||
sc_hist = {}
|
||||
out = open(out_path, "w", newline="\n")
|
||||
out.write(json.dumps(header, separators=(",", ":")) + "\n")
|
||||
fid = 0
|
||||
with open(in_path) as f:
|
||||
for line in f:
|
||||
line = line.strip()
|
||||
if not line:
|
||||
continue
|
||||
d = json.loads(line)
|
||||
if d.get("type") != "raw_csi":
|
||||
continue
|
||||
stats["read"] += 1
|
||||
if limit is not None and stats["read"] > limit:
|
||||
stats["read"] -= 1
|
||||
break
|
||||
iq_hex = d.get("iq_hex", "")
|
||||
raw = bytes.fromhex(iq_hex)
|
||||
n_pairs = len(raw) // 2
|
||||
# ESP-IDF CSI buffer layout: [imag0, real0, imag1, real1, ...] as int8
|
||||
i_vals, q_vals, amp, ph = [], [], [], []
|
||||
for k in range(n_pairs):
|
||||
imag = raw[2 * k]
|
||||
real = raw[2 * k + 1]
|
||||
if imag >= 128:
|
||||
imag -= 256
|
||||
if real >= 128:
|
||||
real -= 256
|
||||
fi, fq = float(real), float(imag)
|
||||
i_vals.append(fi)
|
||||
q_vals.append(fq)
|
||||
amp.append(math.sqrt(fi * fi + fq * fq))
|
||||
ph.append(math.atan2(fq, fi))
|
||||
sc = n_pairs
|
||||
sc_hist[sc] = sc_hist.get(sc, 0) + 1
|
||||
# hard checks (mirror validate_frame)
|
||||
if sc < MIN_SUBCARRIERS or sc > MAX_SUBCARRIERS:
|
||||
stats["rej_sc"] += 1
|
||||
continue
|
||||
# int8 -> always finite, lengths consistent by construction
|
||||
# RSSI: the v1 collector's rssi byte is unreliable (sentinels 64/-128
|
||||
# etc.); only carry it through when it lands in a plausible band,
|
||||
# otherwise leave it None (a small quality penalty, not a reject).
|
||||
r = d.get("rssi")
|
||||
rssi_dbm = r if (isinstance(r, int) and -140 <= r <= 30) else None
|
||||
if rssi_dbm is not None and (rssi_dbm < RSSI_LO - RSSI_HARD_MARGIN or rssi_dbm > RSSI_HI + RSSI_HARD_MARGIN):
|
||||
stats["rej_rssi"] += 1
|
||||
continue
|
||||
if rssi_dbm is not None and not (-110 <= rssi_dbm <= 0):
|
||||
rssi_dbm = None # implausible but not insane -> drop the field
|
||||
q, status, reasons = quality_and_status(amp, rssi_dbm)
|
||||
ch = d.get("channel", 0) or 0
|
||||
frame = {
|
||||
"frame_id": fid,
|
||||
"session_id": 0,
|
||||
"source_id": source_id,
|
||||
"adapter_kind": "Esp32",
|
||||
"timestamp_ns": int(d.get("ts_ns", 0)),
|
||||
"channel": int(ch),
|
||||
"bandwidth_mhz": 20,
|
||||
"rssi_dbm": rssi_dbm,
|
||||
"noise_floor_dbm": None,
|
||||
"antenna_index": 0,
|
||||
"tx_chain": None,
|
||||
"rx_chain": None,
|
||||
"subcarrier_count": sc,
|
||||
"i_values": i_vals,
|
||||
"q_values": q_vals,
|
||||
"amplitude": amp,
|
||||
"phase": ph,
|
||||
"validation": status,
|
||||
"quality_score": q,
|
||||
}
|
||||
if reasons:
|
||||
frame["quality_reasons"] = reasons
|
||||
frame["calibration_version"] = None
|
||||
out.write(json.dumps(frame, separators=(",", ":")) + "\n")
|
||||
fid += 1
|
||||
stats["written"] += 1
|
||||
stats[status.lower()] = stats.get(status.lower(), 0) + 1
|
||||
out.close()
|
||||
print("transcode stats:", json.dumps(stats))
|
||||
print("subcarrier-count histogram:", json.dumps(dict(sorted(sc_hist.items(), key=lambda x: -x[1]))))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
|
@ -231,6 +231,18 @@ dependencies = [
|
|||
"wait-timeout",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "async-compression"
|
||||
version = "0.4.42"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e79b3f8a79cccc2898f31920fc69f304859b3bd567490f75ebf51ae1c792a9ac"
|
||||
dependencies = [
|
||||
"compression-codecs",
|
||||
"compression-core",
|
||||
"pin-project-lite",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "async-trait"
|
||||
version = "0.1.89"
|
||||
|
|
@ -318,7 +330,7 @@ dependencies = [
|
|||
"sync_wrapper 1.0.2",
|
||||
"tokio",
|
||||
"tokio-tungstenite",
|
||||
"tower",
|
||||
"tower 0.5.3",
|
||||
"tower-layer",
|
||||
"tower-service",
|
||||
"tracing",
|
||||
|
|
@ -871,6 +883,23 @@ dependencies = [
|
|||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "compression-codecs"
|
||||
version = "0.4.38"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ce2548391e9c1929c21bf6aa2680af86fe4c1b33e6cea9ac1cfeec0bd11218cf"
|
||||
dependencies = [
|
||||
"compression-core",
|
||||
"flate2",
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "compression-core"
|
||||
version = "0.4.32"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cc14f565cf027a105f7a44ccf9e5b424348421a1d8952a8fc9d499d313107789"
|
||||
|
||||
[[package]]
|
||||
name = "concurrent-queue"
|
||||
version = "2.5.0"
|
||||
|
|
@ -915,6 +944,15 @@ version = "0.4.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e"
|
||||
|
||||
[[package]]
|
||||
name = "convert_case"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca"
|
||||
dependencies = [
|
||||
"unicode-segmentation",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cookie"
|
||||
version = "0.18.1"
|
||||
|
|
@ -1256,7 +1294,7 @@ version = "0.99.20"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6edb4b64a43d977b8e99788fe3a04d483834fba1215a7e02caa415b626497f7f"
|
||||
dependencies = [
|
||||
"convert_case",
|
||||
"convert_case 0.4.0",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"rustc_version",
|
||||
|
|
@ -2371,6 +2409,16 @@ version = "0.16.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100"
|
||||
|
||||
[[package]]
|
||||
name = "hdrhistogram"
|
||||
version = "7.5.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "765c9198f173dd59ce26ff9f95ef0aafd0a0fe01fb9d72841bc5066a4c06511d"
|
||||
dependencies = [
|
||||
"byteorder",
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "heapless"
|
||||
version = "0.6.1"
|
||||
|
|
@ -3152,7 +3200,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "6e9ec52138abedcc58dc17a7c6c0c00a2bdb4f3427c7f63fa97fd0d859155caf"
|
||||
dependencies = [
|
||||
"gtk-sys",
|
||||
"libloading",
|
||||
"libloading 0.7.4",
|
||||
"once_cell",
|
||||
]
|
||||
|
||||
|
|
@ -3172,6 +3220,16 @@ dependencies = [
|
|||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libloading"
|
||||
version = "0.8.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"windows-link 0.2.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libm"
|
||||
version = "0.2.16"
|
||||
|
|
@ -3585,6 +3643,63 @@ dependencies = [
|
|||
"getrandom 0.2.17",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "napi"
|
||||
version = "2.16.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "55740c4ae1d8696773c78fdafd5d0e5fe9bc9f1b071c7ba493ba5c413a9184f3"
|
||||
dependencies = [
|
||||
"bitflags 2.11.0",
|
||||
"ctor",
|
||||
"napi-derive",
|
||||
"napi-sys",
|
||||
"once_cell",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "napi-build"
|
||||
version = "2.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d376940fd5b723c6893cd1ee3f33abbfd86acb1cd1ec079f3ab04a2a3bc4d3b1"
|
||||
|
||||
[[package]]
|
||||
name = "napi-derive"
|
||||
version = "2.16.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7cbe2585d8ac223f7d34f13701434b9d5f4eb9c332cccce8dee57ea18ab8ab0c"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"convert_case 0.6.0",
|
||||
"napi-derive-backend",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.117",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "napi-derive-backend"
|
||||
version = "1.0.75"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1639aaa9eeb76e91c6ae66da8ce3e89e921cd3885e99ec85f4abacae72fc91bf"
|
||||
dependencies = [
|
||||
"convert_case 0.6.0",
|
||||
"once_cell",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"regex",
|
||||
"semver",
|
||||
"syn 2.0.117",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "napi-sys"
|
||||
version = "2.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "427802e8ec3a734331fec1035594a210ce1ff4dc5bc1950530920ab717964ea3"
|
||||
dependencies = [
|
||||
"libloading 0.8.9",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "native-tls"
|
||||
version = "0.2.18"
|
||||
|
|
@ -3892,13 +4007,35 @@ name = "nvsim"
|
|||
version = "0.3.0"
|
||||
dependencies = [
|
||||
"approx 0.5.1",
|
||||
"criterion",
|
||||
"js-sys",
|
||||
"rand 0.8.5",
|
||||
"rand_chacha 0.3.1",
|
||||
"serde",
|
||||
"serde-wasm-bindgen",
|
||||
"serde_json",
|
||||
"sha2",
|
||||
"thiserror 1.0.69",
|
||||
"tracing",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nvsim-server"
|
||||
version = "0.3.0"
|
||||
dependencies = [
|
||||
"axum",
|
||||
"clap",
|
||||
"futures-util",
|
||||
"nvsim",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"thiserror 1.0.69",
|
||||
"tokio",
|
||||
"tower 0.4.13",
|
||||
"tower-http 0.5.2",
|
||||
"tracing",
|
||||
"tracing-subscriber",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -4487,6 +4624,26 @@ dependencies = [
|
|||
"siphasher 1.0.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pin-project"
|
||||
version = "1.1.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cbf0d9e68100b3a7989b4901972f265cd542e560a3a8a724e1e20322f4d06ce9"
|
||||
dependencies = [
|
||||
"pin-project-internal",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pin-project-internal"
|
||||
version = "1.1.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a990e22f43e84855daf260dded30524ef4a9021cc7541c26540500a50b624389"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.117",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pin-project-lite"
|
||||
version = "0.2.17"
|
||||
|
|
@ -5278,7 +5435,7 @@ dependencies = [
|
|||
"sync_wrapper 1.0.2",
|
||||
"tokio",
|
||||
"tokio-native-tls",
|
||||
"tower",
|
||||
"tower 0.5.3",
|
||||
"tower-http 0.6.8",
|
||||
"tower-service",
|
||||
"url",
|
||||
|
|
@ -5311,7 +5468,7 @@ dependencies = [
|
|||
"sync_wrapper 1.0.2",
|
||||
"tokio",
|
||||
"tokio-util",
|
||||
"tower",
|
||||
"tower 0.5.3",
|
||||
"tower-http 0.6.8",
|
||||
"tower-service",
|
||||
"url",
|
||||
|
|
@ -5798,6 +5955,111 @@ version = "2.0.4"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "178f93f84a4a72c582026a45d9b8710acf188df4a22a25434c5dbba1df6c4cac"
|
||||
|
||||
[[package]]
|
||||
name = "rvcsi-adapter-file"
|
||||
version = "0.3.0"
|
||||
dependencies = [
|
||||
"rvcsi-core",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tempfile",
|
||||
"thiserror 1.0.69",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rvcsi-adapter-nexmon"
|
||||
version = "0.3.0"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"rvcsi-core",
|
||||
"thiserror 1.0.69",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rvcsi-cli"
|
||||
version = "0.3.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"clap",
|
||||
"rvcsi-adapter-file",
|
||||
"rvcsi-adapter-nexmon",
|
||||
"rvcsi-core",
|
||||
"rvcsi-runtime",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tempfile",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rvcsi-core"
|
||||
version = "0.3.0"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"serde_json",
|
||||
"thiserror 1.0.69",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rvcsi-dsp"
|
||||
version = "0.3.0"
|
||||
dependencies = [
|
||||
"rvcsi-core",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"thiserror 1.0.69",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rvcsi-events"
|
||||
version = "0.3.0"
|
||||
dependencies = [
|
||||
"rvcsi-core",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"thiserror 1.0.69",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rvcsi-node"
|
||||
version = "0.3.0"
|
||||
dependencies = [
|
||||
"napi",
|
||||
"napi-build",
|
||||
"napi-derive",
|
||||
"rvcsi-adapter-nexmon",
|
||||
"rvcsi-core",
|
||||
"rvcsi-runtime",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tempfile",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rvcsi-runtime"
|
||||
version = "0.3.0"
|
||||
dependencies = [
|
||||
"rvcsi-adapter-file",
|
||||
"rvcsi-adapter-nexmon",
|
||||
"rvcsi-core",
|
||||
"rvcsi-dsp",
|
||||
"rvcsi-events",
|
||||
"rvcsi-ruvector",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tempfile",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rvcsi-ruvector"
|
||||
version = "0.3.0"
|
||||
dependencies = [
|
||||
"rvcsi-core",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tempfile",
|
||||
"thiserror 1.0.69",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ryu"
|
||||
version = "1.0.23"
|
||||
|
|
@ -7379,6 +7641,27 @@ dependencies = [
|
|||
"zip 0.6.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tower"
|
||||
version = "0.4.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"futures-util",
|
||||
"hdrhistogram",
|
||||
"indexmap 1.9.3",
|
||||
"pin-project",
|
||||
"pin-project-lite",
|
||||
"rand 0.8.5",
|
||||
"slab",
|
||||
"tokio",
|
||||
"tokio-util",
|
||||
"tower-layer",
|
||||
"tower-service",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tower"
|
||||
version = "0.5.3"
|
||||
|
|
@ -7401,8 +7684,10 @@ version = "0.5.2"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e9cd434a998747dd2c4276bc96ee2e0c7a2eadf3cae88e52be55a05fa9053f5"
|
||||
dependencies = [
|
||||
"async-compression",
|
||||
"bitflags 2.11.0",
|
||||
"bytes",
|
||||
"futures-core",
|
||||
"futures-util",
|
||||
"http 1.4.0",
|
||||
"http-body 1.0.1",
|
||||
|
|
@ -7433,7 +7718,7 @@ dependencies = [
|
|||
"http-body 1.0.1",
|
||||
"iri-string",
|
||||
"pin-project-lite",
|
||||
"tower",
|
||||
"tower 0.5.3",
|
||||
"tower-layer",
|
||||
"tower-service",
|
||||
]
|
||||
|
|
@ -8385,6 +8670,7 @@ dependencies = [
|
|||
"serde",
|
||||
"serde_json",
|
||||
"tokio",
|
||||
"tower-http 0.5.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -8454,7 +8740,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "wifi-densepose-train"
|
||||
version = "0.3.0"
|
||||
version = "0.3.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"approx 0.5.1",
|
||||
|
|
|
|||
|
|
@ -21,6 +21,11 @@ members = [
|
|||
"crates/wifi-densepose-geo",
|
||||
"crates/nvsim",
|
||||
"crates/nvsim-server",
|
||||
# rvCSI — edge RF sensing runtime (ADR-095 platform, ADR-096 FFI/crate layout):
|
||||
# lives in its own repo (https://github.com/ruvnet/rvcsi), vendored here as
|
||||
# `vendor/rvcsi` and published to crates.io as `rvcsi-*` 0.3.x. Depend on the
|
||||
# published crates (or the submodule's `crates/rvcsi-*` paths) — not as v2
|
||||
# workspace members, since `vendor/rvcsi/Cargo.toml` is its own workspace.
|
||||
]
|
||||
# ADR-040: WASM edge crate targets wasm32-unknown-unknown (no_std),
|
||||
# excluded from workspace to avoid breaking `cargo test --workspace`.
|
||||
|
|
@ -108,6 +113,13 @@ indicatif = "0.17"
|
|||
# CLI
|
||||
clap = { version = "4.4", features = ["derive", "env"] }
|
||||
|
||||
# rvCSI: napi-rs (Rust -> Node bindings) + napi-c (C-shim build glue)
|
||||
napi = { version = "2.16", default-features = false, features = ["napi8"] }
|
||||
napi-derive = "2.16"
|
||||
napi-build = "2.1"
|
||||
cc = "1.0"
|
||||
libc = "0.2"
|
||||
|
||||
# Testing
|
||||
criterion = { version = "0.5", features = ["html_reports"] }
|
||||
proptest = "1.4"
|
||||
|
|
|
|||
|
|
@ -0,0 +1 @@
|
|||
Subproject commit acd5689d9f995dc9952bc924268d42e52aae547f
|
||||
Loading…
Reference in New Issue