wifi-densepose/python
ruv b71d243b42 feat(adr-117): publish wifi-densepose 2.0.0a1 + ruview 2.0.0a1 to PyPI
Three PyPI artifacts now live (published from .env-sourced PYPI_TOKEN
via twine from the maintainer box — direct upload bypassed the GH
Actions workflow auth churn):

1. wifi-densepose==1.99.0 — tombstone (raises ImportError with migration URL)
   https://pypi.org/project/wifi-densepose/1.99.0/

2. wifi-densepose==2.0.0a1 — PyO3 wheel (win_amd64 cp310-abi3) + sdist
   https://pypi.org/project/wifi-densepose/2.0.0a1/

3. ruview==2.0.0a1 — meta-package re-exporting wifi_densepose
   https://pypi.org/project/ruview/2.0.0a1/

New `python/ruview-meta/` subdirectory:
- pyproject.toml — name="ruview", version="2.0.0a1", setuptools backend,
  dependencies = ["wifi-densepose==2.0.0a1"]
- src/ruview/__init__.py — re-exports every name from
  `wifi_densepose.__all__` so `from ruview import BreathingExtractor`
  is equivalent to `from wifi_densepose import BreathingExtractor`.
  Also re-exports `__version__`, `__rust_version__`,
  `__rust_build_tag__`, `__build_features__`. Aliases the `client`
  sub-package transparently when wifi-densepose[client] extras are
  installed.
- README.md — explains why two PyPI names ship the same code (brand
  vs technical name) and shows install commands for both.

End-to-end verified: fresh venv, `pip install ruview`,
`import ruview` + `import wifi_densepose` both succeed,
`ruview.BreathingExtractor is wifi_densepose.BreathingExtractor` → True.

Multi-platform wheels (manylinux x86_64+aarch64, macos x86_64+arm64)
still pending — the cibuildwheel workflow path remains for that.
Linux/macOS users today install via the sdist (requires rustup +
maturin locally).

Refs: docs/adr/ADR-117-pip-wifi-densepose-modernization.md
Refs: #785

Co-Authored-By: claude-flow <ruv@ruv.net>
2026-05-24 12:46:22 -04:00
..
bench hardening(adr-117): benchmarks + security/robustness test suite 2026-05-24 11:44:54 -04:00
ruview-meta feat(adr-117): publish wifi-densepose 2.0.0a1 + ruview 2.0.0a1 to PyPI 2026-05-24 12:46:22 -04:00
src feat(adr-117/p3+p3.5): vitals + BFLD bindings 2026-05-24 11:21:58 -04:00
tests hardening(adr-117): benchmarks + security/robustness test suite 2026-05-24 11:44:54 -04:00
tombstone feat(adr-117/p5+p-tomb): pip-release workflow + v1.99.0 tombstone wheel 2026-05-24 11:39:04 -04:00
wifi_densepose feat(adr-117/p4): pure-Python WS/MQTT client layer 2026-05-24 11:31:29 -04:00
.gitignore feat(adr-117/p2): Keypoint + KeypointType bindings — 23 new tests (29/29 GREEN) 2026-05-24 10:54:34 -04:00
Cargo.lock feat(adr-117/p3+p3.5): vitals + BFLD bindings 2026-05-24 11:21:58 -04:00
Cargo.toml feat(adr-117/p3+p3.5): vitals + BFLD bindings 2026-05-24 11:21:58 -04:00
README.md fix(adr-117/p5): switch publish workflow to PYPI_API_TOKEN + user-facing README 2026-05-24 12:14:56 -04:00
pyproject.toml fix(adr-117/p1): standalone Cargo.toml + python-source=. + #[pyo3(name=_native)] (P1 GREEN) 2026-05-24 10:48:28 -04:00

README.md

wifi-densepose

PyPI version Python License: MIT

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 (630 BPM) and heart rate (40120 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.82.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.

License

MIT.