Skip to content

Per-node presence classifier reports present on 99.6% of an empty room (ESP32-C6) #2522

Open

Description

@ruvnet

Summary

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.

Activity

  1. ruvnet commented on Nov 11, 2026

    @ruvnet
    OwnerAuthor

    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:

    • 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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions