You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Per-node presence classifier reports present on 99.6% of an empty room (ESP32-C6) #2522
smooth_and_classify_node in wifi-densepose-sensing-server reported presence: true on 6,093 of 6,104 observations (99.8%) recorded while the room was empty, and on 99.6% of the held-out half. It is effectively always on for this link. Details and method are in docs/adr/ADR-426-ruv-fann-proof-programme.md, results 6 to 9 (PR #2519).
Evidence (MEASURED, 2026-11-10)
One ESP32-C6 node (firmware 0.8.17, channel 10), one room, server started with --source esp32.
Empty: the operator left for 32 minutes; 31.0 minutes kept after trimming 30 s at each end. No keyboard or mouse input occurred in that span.
Occupied reference: keyboard or mouse input within the last 3 s (scripts/input_activity_logger.py).
Condition
Observations
Classifier said present
Empty room
6,104
6,093 (99.8%)
Input-confirmed occupied
429
429 (100%)
During the empty stretch the label was present_moving for 6,048 observations.
Likely cause
The level is raw_classify(smoothed_motion) with adjusted = raw_motion - 0.7 * baseline. In a steady room the baseline converges to the raw score, so adjusted settles at about 30% of the raw motion score. On this link the raw score is high enough at rest that 30% of it stays above the 0.12 present_moving threshold indefinitely. The thresholds assume a motion-score scale this hardware does not have, and the per-feature direction is inverted here: variance, motion-band and breathing-band power were higher with the room empty than with a person seated.
What did work on the same recordings
Variability over a 30 s window, not level: the standard deviation of RSSI separated occupied from empty with AUC 1.00 in three sessions, and a head fitted on those windowed features (ruview.node-features/2) gave 0 false positives in 3,069 held-out empty observations at 91% sensitivity.
Limits
One node, one room, one afternoon. "Empty" is the operator's declaration. Occupied means seated and typing. ESP32-S3 nodes were not tested and may behave differently.
Suggested direction
Either calibrate the thresholds per link from a known-empty period, or base the per-node decision on windowed variability. Both need the motionless-person case tested before they replace the current rule.
The node was capturing CSI from every transmitter, not from one. Looking at per-observation RSSI in the same recordings:
Empty room: 55% of observations at -46 dBm and 30% at -44 dBm. One steady source.
Operator present: that cluster is absent. About 40% of observations are at -38/-39 dBm and the rest spread over -49 to -61 dBm.
With capture restricted to the access point (provision.py --filter-mac <bssid>), the access point's frames arrive at -30 to -39 dBm. So the source that appeared only when the operator was present is the access point sending data, which happens when someone is using the network.
Consequences:
The empty and occupied recordings differ in which transmitters were heard, not only in whether a person was there. The 99.8% figure above was measured, but under a condition that also changed the traffic mix, so it does not isolate the classifier's response to an empty room.
The windowed-feature result (0 false positives, 91% sensitivity) is confounded the same way and may largely reflect network activity.
Both are being re-recorded with the MAC filter on. First observation under the filter, operator present and typing: the classifier reports absent on 80 of 116 observations, which is a different failure from the one reported above. I will post the filtered empty-room comparison here.
Activity
ruvnet commentedon Nov 11, 2026
Correction: these recordings mixed transmitters
The node was capturing CSI from every transmitter, not from one. Looking at per-observation RSSI in the same recordings:
With capture restricted to the access point (
provision.py --filter-mac <bssid>), the access point's frames arrive at -30 to -39 dBm. So the source that appeared only when the operator was present is the access point sending data, which happens when someone is using the network.Consequences:
Both are being re-recorded with the MAC filter on. First observation under the filter, operator present and typing: the classifier reports
absenton 80 of 116 observations, which is a different failure from the one reported above. I will post the filtered empty-room comparison here.