fix(toolkit): integrate capture and device corrections for 0.9.2

Integrate reviewed source PRs #2201-#2223, #2225-#2231, #2235-#2240, #2242 and #2243 (firmware README hunks remain in #2247). Regenerate one package manifest after integration, validate native Windows test execution, bound MTK listener startup, support official C6 release bundles, and retain stronger release metadata gates.
This commit is contained in:
ruv committed 2026-10-12 11:49:52 -04:00
1 parent a71634ae8a
commit 3620abff12
109 files changed
+2784 -337

No files matched your search

+4 -3
View File
@@ -126,7 +126,8 @@ listener.on(SemanticPrimitive.PossibleDistress, lambda e: alert(e))
client = RuViewMqttClient(broker_host="homeassistant.local")
client.on_message(
"homeassistant/+/wifi_densepose_+/+/state",
# MQTT `+` must fill a whole topic level; the listener skips non-RuView topics.
"homeassistant/+/+/+/state",
listener.handle_mqtt_message,
)
client.start()
@@ -139,8 +140,8 @@ client.wait_connected()
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
# Parse compressed BFR from a Wireshark capture into a complex128 ndarray ...
fb = np.zeros((2, 1, 996), dtype=np.complex128) # Nr=2 Nc=1 Nsc=996 for HE80
frame = BfldFrame.from_compressed_feedback(
timestamp_ms=ts,
+5 -5
View File
@@ -15,7 +15,7 @@
//!
//! 1. Capture BFR frames with `tcpdump` / Wireshark + the BFR dissector
//! (or via `mac80211` debugfs on Linux 6.10+)
//! 2. Parse the compressed feedback into a numpy Complex64 ndarray
//! 2. Parse the compressed feedback into a numpy complex128 ndarray
//! `[Nr × Nc × Nsc]` using your favourite Python BFR parser
//! 3. Construct `BfldFrame.from_compressed_feedback(...)` to hand the
//! matrix to RuView
@@ -103,7 +103,7 @@ impl PyBfldKind {
/// One BFR snapshot: a compressed beamforming feedback matrix tagged
/// with metadata (timestamp, sounding sequence, source MAC, kind).
///
/// Backing storage: a numpy Complex64 ndarray `[Nr × Nc × Nsc]`. The
/// Backing storage: a numpy complex128 ndarray `[Nr × Nc × Nsc]`. The
/// Python constructor accepts the ndarray directly; under the hood we
/// hold a `Vec<Complex64>` in row-major order.
///
@@ -112,7 +112,7 @@ impl PyBfldKind {
/// import numpy as np
/// from wifi_densepose import BfldFrame, BfldKind
///
/// fb = np.zeros((2, 1, 996), dtype=np.complex64) # Nr=2, Nc=1, Nsc=996
/// fb = np.zeros((2, 1, 996), dtype=np.complex128) # Nr=2, Nc=1, Nsc=996
/// frame = BfldFrame.from_compressed_feedback(
/// timestamp_ms=1234,
/// sounding_index=42,
@@ -138,7 +138,7 @@ pub struct PyBfldFrame {
#[pymethods]
impl PyBfldFrame {
/// Construct from a pre-parsed Complex64 ndarray of shape
/// Construct from a pre-parsed numpy complex128 ndarray of shape
/// `[n_rows, n_cols, n_subcarriers]`. The last dimension MUST
/// match `kind.n_subcarriers`.
#[staticmethod]
@@ -212,7 +212,7 @@ impl PyBfldFrame {
sum / self.matrix.len() as f64
}
/// Return the feedback matrix as a numpy Complex64 ndarray of
/// Return the feedback matrix as a numpy complex128 ndarray of
/// shape `[n_rows, n_cols, n_subcarriers]`. Allocates a fresh
/// Python-owned array; the BfldFrame keeps its own copy.
fn feedback_matrix<'py>(&self, py: Python<'py>) -> Bound<'py, PyArray3<Complex64>> {
+9
View File
@@ -65,6 +65,15 @@ def test_parse_discovery_topic_event() -> None:
assert out == ("event", "aabbccddeeff", "fall")
def test_parse_discovery_topic_accepts_default_server_node_id() -> None:
"""The sensing-server's node id defaults to its MQTT client id,
`wifi-densepose-<8 hex>`, with `-node<N>` appended per node."""
out = parse_discovery_topic(
"homeassistant/binary_sensor/wifi_densepose_wifi-densepose-1a2b3c4d-node1/presence/config"
)
assert out == ("binary_sensor", "wifi-densepose-1a2b3c4d-node1", "presence")
def test_parse_discovery_topic_returns_none_for_non_discovery() -> None:
assert parse_discovery_topic("homeassistant/binary_sensor/foo/state") is None
assert parse_discovery_topic("ruview/aabbccddeeff/raw/edge_vitals") is None
+29 -1
View File
@@ -21,7 +21,11 @@ from typing import Any
import pytest
from wifi_densepose.client import RuViewMqttClient
from wifi_densepose.client import (
RuViewMqttClient,
SemanticPrimitive,
SemanticPrimitiveListener,
)
from wifi_densepose.client.mqtt import _topic_matches
@@ -185,6 +189,30 @@ def test_multiple_handlers_for_overlapping_patterns_all_fire() -> None:
assert len(b_received) == 1
def test_documented_ha_state_pattern_subscribes_and_reaches_listener() -> None:
"""The subscription shown in python/README.md and the
SemanticPrimitiveListener docstring. paho rejects mid-level `+`
(e.g. `wifi_densepose_+`) with ValueError on subscribe."""
listener = SemanticPrimitiveListener()
received: list[Any] = []
listener.on(SemanticPrimitive.SomeoneSleeping, received.append)
c = RuViewMqttClient()
c.on_message("homeassistant/+/+/+/state", listener.handle_mqtt_message)
# The real paho client validates the filter before it checks for a
# connection, so this exercises the same subscribe() the broker path runs.
c._on_connect(c._client, None, None, 0)
c._on_message(None, None, _fake_message(
"homeassistant/binary_sensor/wifi_densepose_aabb/someone_sleeping/state",
{"state": "ON", "confidence": 0.9},
))
assert [(e.kind, e.node_id, e.state) for e in received] == [
(SemanticPrimitive.SomeoneSleeping, "aabb", "ON"),
]
# ─── on_connect path ─────────────────────────────────────────────────
+30 -2
View File
@@ -22,12 +22,12 @@ def test_enum_covers_all_10_v1_primitives() -> None:
"someone_sleeping",
"possible_distress",
"room_active",
"elderly_inactivity",
"elderly_inactivity_anomaly",
"meeting_in_progress",
"bathroom_occupied",
"fall_risk_elevated",
"bed_exit",
"no_movement_safety",
"no_movement",
"multi_room_transition",
}
actual = {p.value for p in SemanticPrimitive}
@@ -121,6 +121,34 @@ def test_listener_decodes_numeric_sensor_state() -> None:
assert evt.state == "73"
def test_listener_decodes_server_score_payload() -> None:
"""The sensing-server publishes fall_risk_elevated as a ScorePayload
(`{"score": ..., "ts": ...}`, mqtt/state.rs) with no `state` key."""
listener = SemanticPrimitiveListener()
evt = listener.handle_mqtt_message(
"homeassistant/sensor/wifi_densepose_aabb/fall_risk_elevated/state",
json.dumps({"score": 73.0, "ts": "2026-10-11T12:00:00Z"}),
)
assert evt is not None
assert evt.state == "73.0"
def test_listener_routes_server_topic_slugs() -> None:
"""Slugs must match EntityKind::topic_slug() in the sensing-server's
mqtt/discovery.rs, or the server's state messages are dropped."""
listener = SemanticPrimitiveListener()
received: list[SemanticPrimitive] = []
listener.on_any(lambda e: received.append(e.kind))
for slug in ("elderly_inactivity_anomaly", "no_movement"):
listener.handle_mqtt_message(
f"homeassistant/binary_sensor/wifi_densepose_aabb/{slug}/state", "ON"
)
assert received == [
SemanticPrimitive.ElderlyInactivityAnomaly,
SemanticPrimitive.NoMovementSafety,
]
def test_listener_decodes_bytes_payload() -> None:
listener = SemanticPrimitiveListener()
evt = listener.handle_mqtt_message(
+7 -4
View File
@@ -40,7 +40,9 @@ from typing import Any, Iterable
_DISCOVERY_TOPIC_RE = re.compile(
r"^homeassistant/"
r"(?P<entity_kind>[A-Za-z_]+)/"
r"wifi_densepose_(?P<node_id>[A-Za-z0-9]+)/"
# Node ids default to the server's MQTT client id (`wifi-densepose-<hex>`,
# plus `-node<N>` per node), so accept `-`, `_` and `.` as well.
r"wifi_densepose_(?P<node_id>[A-Za-z0-9_.\-]+)/"
r"(?P<object_id>[A-Za-z0-9_\-]+)/"
r"config$"
)
@@ -143,9 +145,10 @@ class HABlueprintHelper:
structured queries against them.
Intended use: subscribe a RuViewMqttClient to
`homeassistant/+/wifi_densepose_+/+/config`, feed every message
into `add_payload()`, then ask the helper "what entities does
node X expose?" or "what binary_sensors are presence-class?".
`homeassistant/+/+/+/config`, feed every message into
`add_payload()` (it ignores non-RuView topics), then ask the helper
"what entities does node X expose?" or "what binary_sensors are
presence-class?".
"""
def __init__(self) -> None:
+8 -6
View File
@@ -13,12 +13,12 @@ The 10 v1 primitives (ADR-115 §3.12.1):
| `SomeoneSleeping` | `someone_sleeping` | binary_sensor |
| `PossibleDistress` | `possible_distress` | binary_sensor + event |
| `RoomActive` | `room_active` | binary_sensor |
| `ElderlyInactivityAnomaly` | `elderly_inactivity` | binary_sensor + event |
| `ElderlyInactivityAnomaly` | `elderly_inactivity_anomaly` | binary_sensor + event |
| `MeetingInProgress` | `meeting_in_progress` | binary_sensor |
| `BathroomOccupied` | `bathroom_occupied` | binary_sensor |
| `FallRiskElevated` | `fall_risk_elevated` | sensor (0–100) + event |
| `BedExit` | `bed_exit` | event |
| `NoMovementSafety` | `no_movement_safety` | binary_sensor + event |
| `NoMovementSafety` | `no_movement` | binary_sensor + event |
| `MultiRoomTransition` | `multi_room_transition` | event |
"""
@@ -38,12 +38,12 @@ class SemanticPrimitive(enum.Enum):
SomeoneSleeping = "someone_sleeping"
PossibleDistress = "possible_distress"
RoomActive = "room_active"
ElderlyInactivityAnomaly = "elderly_inactivity"
ElderlyInactivityAnomaly = "elderly_inactivity_anomaly"
MeetingInProgress = "meeting_in_progress"
BathroomOccupied = "bathroom_occupied"
FallRiskElevated = "fall_risk_elevated"
BedExit = "bed_exit"
NoMovementSafety = "no_movement_safety"
NoMovementSafety = "no_movement"
MultiRoomTransition = "multi_room_transition"
@classmethod
@@ -96,7 +96,8 @@ class SemanticPrimitiveListener:
client = RuViewMqttClient()
client.on_message(
"homeassistant/+/wifi_densepose_+/+/state",
# MQTT `+` must fill a whole topic level; the listener skips non-RuView topics.
"homeassistant/+/+/+/state",
listener.handle_mqtt_message,
)
client.start()
@@ -214,7 +215,8 @@ def _decode_event(
return SemanticPrimitiveEvent(
kind=primitive,
node_id=node_id,
state=str(body.get("state", "")),
# Score sensors (fall_risk_elevated) publish `{"score": ..., "ts": ...}`.
state=str(body.get("state", body.get("score", ""))),
confidence=float(body.get("confidence", 0.0)),
explanation=expl_tuple,
timestamp=float(body.get("timestamp", 0.0)),