scripts/c6-presence-watcher.py and friends carry a Python port of
`wifi_densepose_bfld::PrivacyClass`. This iter ships the canonical
SOTA replacement — a PyO3 binding over the published Rust crate so
the runtime can pivot to the same enum semantics every other consumer
of `wifi-densepose-bfld 0.3.0` already uses.
New file: `python/src/bindings/privacy_gate.rs` (~155 LOC)
- `#[pyclass] PrivacyClass {Raw, Derived, Anonymous, Restricted}`
- `.allows_network`, `.allows_matter`, `.allows_hap`, `.as_u8` getters
- `PrivacyClass.from_u8(v)` / `PrivacyClass.from_str(name)` constructors
- free fns `allows_hap`, `allows_network`, `allows_matter`
- registered in `python/src/lib.rs` via `bindings::privacy_gate::register`
Cargo.toml gains `wifi-densepose-bfld = { version = "0.3.0", path = ... }`
as a hard dep; numpy + pyo3 + the existing core/vitals deps unchanged.
ADR-125 §2.1.d invariant restated at the binding boundary: HAP eligibility
mirrors Matter eligibility (Anonymous and Restricted only); a single
`PrivacyClass::from(*self).allows_matter()` call is the gate truth-source.
Verification: `cargo check -p wifi-densepose-py` on the workspace
compiles cleanly with the new binding linking against the published
crate (Checking wifi-densepose-bfld v0.3.0 ✓, Checking
wifi-densepose-py v2.0.0-alpha.1 ✓).
Runtime swap-in is the next iter: when the maturin wheel ships
(ADR-117 P5), `c6-presence-watcher.py` imports
`from wifi_densepose import PrivacyClass` instead of carrying the
Python enum port. Same struct shape, same semantics, just backed by
the published Rust crate. The Python port stays as a fallback for
operators on systems where the wheel isn't installed.
Refs ADR-118 §2.1, ADR-125 §2.1.d, ADR-117 §5.7 (binding strategy).
Co-Authored-By: claude-flow <ruv@ruv.net>
|
||
|---|---|---|
| .. | ||
| bench | ||
| ruview-meta | ||
| src | ||
| tests | ||
| tombstone | ||
| wifi_densepose | ||
| .gitignore | ||
| Cargo.lock | ||
| Cargo.toml | ||
| README.md | ||
| pyproject.toml | ||
README.md
wifi-densepose
Detect human presence, count people, read breathing and heart rate, and estimate skeletal pose — using only the WiFi signal already in your home.
No cameras. No wearables. Works through walls and in the dark.
wifi-densepose is the Python binding for the RuView
sensing stack: a Rust core that turns the Channel State Information (CSI)
emitted by ordinary WiFi chips into ambient-intelligence signals. The wheel
ships compiled DSP for fast offline analysis, plus an opt-in Python client
for talking to a live RuView sensing-server over WebSocket or MQTT.
Features
- 17-keypoint pose — full-body skeletal estimate from WiFi CSI, no camera
- Vital signs — respiratory rate (6–30 BPM) and heart rate (40–120 BPM) with a confidence score and clinical-grade / degraded / unreliable status
- Presence, person count, fall detection, motion — fused outputs from the same CSI stream
- 10 semantic primitives (HA-MIND) — someone-sleeping, possible-distress, room-active, bathroom-occupied, fall-risk-elevated, bed-exit, … — ready to wire into Home Assistant or Apple Home automations
- Beamforming Feedback (BFLD) support — 802.11ac/ax/be compressed feedback matrices on top of the receiver-side CSI path
- GIL-releasing DSP — extract loops run with the GIL released, so a tokio-backed web server can call into the pipeline without stalling its event loop
- Tiny wheel — ~240 KB compiled (one binary per OS/arch covers Python 3.10+ via the stable ABI)
Install
pip install wifi-densepose # core DSP only
pip install "wifi-densepose[client]" # + WebSocket/MQTT clients
Wheels are published for Linux (x86_64, aarch64), macOS (x86_64, arm64), and Windows (amd64).
Usage
Extract breathing rate from a CSI stream
from wifi_densepose import BreathingExtractor
br = BreathingExtractor.esp32_default() # 56 subcarriers @ 100 Hz, 30s window
for residuals, weights in your_csi_source: # one frame at a time
est = br.extract(residuals=residuals, weights=weights)
if est is not None:
print(f"{est.value_bpm:.1f} BPM (confidence={est.confidence:.2f})")
Heart rate is the same shape — HeartRateExtractor.esp32_default() with a
0.8–2.0 Hz band-pass and a 15-second window.
Subscribe to a live sensing-server
import asyncio
from wifi_densepose.client import SensingClient, EdgeVitalsMessage
async def main():
async with SensingClient("ws://your-ruview-node:8765/ws/sensing") as c:
async for msg in c.stream():
if isinstance(msg, EdgeVitalsMessage):
print(msg.presence, msg.breathing_rate_bpm, msg.heartrate_bpm)
asyncio.run(main())
React to Home Assistant semantic primitives
from wifi_densepose.client import (
RuViewMqttClient, SemanticPrimitive, SemanticPrimitiveListener,
)
listener = SemanticPrimitiveListener()
listener.on(SemanticPrimitive.BedExit, lambda e: print("bed exit:", e.node_id))
listener.on(SemanticPrimitive.PossibleDistress, lambda e: alert(e))
client = RuViewMqttClient(broker_host="homeassistant.local")
client.on_message(
"homeassistant/+/wifi_densepose_+/+/state",
listener.handle_mqtt_message,
)
client.start()
client.wait_connected()
Decode 802.11ax beamforming feedback
import numpy as np
from wifi_densepose import BfldFrame, BfldKind
# Parse compressed BFR from a Wireshark capture into a Complex64 ndarray ...
fb = np.zeros((2, 1, 996), dtype=np.complex64) # Nr=2 Nc=1 Nsc=996 for HE80
frame = BfldFrame.from_compressed_feedback(
timestamp_ms=ts,
sounding_index=seq,
sta_mac="aa:bb:cc:dd:ee:ff",
kind=BfldKind.CompressedHE80,
feedback_matrix=fb,
)
print(frame.n_subcarriers, frame.mean_amplitude)
Hardware
Works with any WiFi chip that exposes CSI. Reference setups (ESP-IDF firmware, build scripts, witness-verified test bundles) are in the RuView repo:
| Device | Cost | Role |
|---|---|---|
| ESP32-S3 (8MB flash) | ~$9 | WiFi CSI sensing node |
| ESP32-S3 SuperMini (4MB) | ~$6 | WiFi CSI (compact) |
| ESP32-C6 + Seeed MR60BHA2 | ~$15 | mmWave HR/BR/presence add-on |
The legacy v1 line (Wi-Pose-style FastAPI server) is end-of-life;
wifi-densepose==1.99.0 is a tombstone that raises ImportError pointing
to v2 with a migration URL.
Links
- Repository — https://github.com/ruvnet/RuView
- Modernization plan — ADR-117
- Home Assistant integration — ADR-115
- Issues — https://github.com/ruvnet/RuView/issues
License
MIT.