Skip to content

fix(mqtt): per-node HA devices use each node's own presence/motion (#872)#918

Merged
ruvnet merged 1 commit into
mainfrom
fix/mqtt-per-node-presence
Jun 2, 2026
Merged

fix(mqtt): per-node HA devices use each node's own presence/motion (#872)#918
ruvnet merged 1 commit into
mainfrom
fix/mqtt-per-node-presence

Conversation

@ruvnet
Copy link
Copy Markdown
Owner

@ruvnet ruvnet commented Jun 2, 2026

The bug

#898 made the MQTT bridge emit one Home-Assistant device per node, but it applied the room-level aggregate classification to every node:

let mk = |nid, rssi| VitalsSnapshot { presence /*aggregate*/, motion /*aggregate*/,};
for node in arr { vtx.send(mk(format!("{node_id}-node{n}"), node["rssi_dbm"])); }

So on a multi-node deployment:

  • a node watching an empty corner reported "present" whenever any other node saw a person;
  • motion_level: "absent" was mis-mapped to full motion (the aggregate match had no "absent" arm, so it hit Some(_) => 1.0).

The per-node data was already available — each entry in the broadcast nodes array is a PerNodeFeatureInfo carrying its own classification (motion_level/presence/confidence) and rssi_dbm.

The fix

Extracted the JSON→VitalsSnapshot mapping into a pure, testable function vitals_snapshots_from_sensing_json(v, base_id) that:

  • reads each node's own classification, deferring to the room aggregate only for fields a node omits;
  • keeps vitals (breathing/heart rate) and person count room-level (they aren't per-node);
  • maps "absent"/"none"/"still"/"idle" → zero motion;
  • falls back to a single aggregate snapshot when there's no nodes array (wifi/simulate).

Tests (new)

4 unit tests under mqtt_bridge_tests:

  • per_node_presence_uses_each_nodes_own_classification — node 1 present+moving, node 2 absent → node 2 does not inherit the aggregate
  • per_node_missing_fields_fall_back_to_aggregate
  • falls_back_to_single_aggregate_when_no_nodes
  • absent_motion_level_is_zero_motion

Verification

  • cargo build -p wifi-densepose-sensing-server --no-default-features --features mqtt
  • new tests pass; full crate unit suite 432 + 114 passed, 0 failed.

Supersedes #899

#899 targeted the same bug but read non-existent fields (node["motion_level"], node["status"] == "active") instead of the nested node["classification"] + stale, so it would have forced every node to "absent". Recommend closing #899 in favor of this.

🤖 Generated with claude-flow

)

The MQTT bridge fanned out one Home-Assistant device per node (#898) but
applied the *room-level aggregate* classification to every node — so in a
multi-node setup a node in an empty corner inherited another node's
"present", and `motion_level: "absent"` was mis-mapped to full motion
(the aggregate match fell through `Some(_) => 1.0`).

Each node in the sensing broadcast's `nodes` array already carries its own
`classification` (`motion_level`/`presence`/`confidence`, see
PerNodeFeatureInfo) and RSSI. Now each per-node snapshot reads that node's
own classification, deferring to the room aggregate only for fields a node
omits. Vitals (breathing/heart rate) and person count stay room-level.

Extracted the JSON→VitalsSnapshot mapping into a pure, testable function
(`vitals_snapshots_from_sensing_json`) and added 4 unit tests covering
per-node divergence, partial-field fallback, the no-nodes aggregate path,
and the absent→zero-motion fix.

Supersedes #899, which targeted the right bug but read non-existent fields
(`node["motion_level"]` / `node["status"]` instead of the nested
`node["classification"]` + `stale`).

Verified: builds with `--features mqtt`; new tests pass; full crate unit
suite 432 + 114 passed, 0 failed.

Co-Authored-By: claude-flow <ruv@ruv.net>
@ruvnet ruvnet merged commit b12662a into main Jun 2, 2026
28 of 29 checks passed
@ruvnet ruvnet deleted the fix/mqtt-per-node-presence branch June 2, 2026 17:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant