# wifi-densepose

[![PyPI version](https://img.shields.io/pypi/v/wifi-densepose.svg)](https://pypi.org/project/wifi-densepose/)
[![Python](https://img.shields.io/pypi/pyversions/wifi-densepose.svg)](https://pypi.org/project/wifi-densepose/)
[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/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](https://github.com/ruvnet/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

```bash
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

```python
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

```python
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

```python
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

```python
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](https://github.com/ruvnet/RuView):

| 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](https://github.com/ruvnet/RuView/blob/main/docs/adr/ADR-117-pip-wifi-densepose-modernization.md)
- **Home Assistant integration** — [ADR-115](https://github.com/ruvnet/RuView/blob/main/docs/adr/ADR-115-home-assistant-integration.md)
- **Issues** — https://github.com/ruvnet/RuView/issues

## License

MIT.
