Repository navigation
Description
Description
I am testing RuView with ESP32 nodes sending live CSI data to the sensing server over UDP.
The CSI data is being received successfully by the server, and the sensing pipeline is running in live mode. However, the Active Person Count displayed by the server UI is frequently incorrect or inconsistent with the actual number of people present in the room.
For example, when the room contains a known number of people, the UI may report a different count, and the count can change dynamically even when the number of people in the room remains unchanged.
Setup
RuView:
v2754/ current test buildCSI source: ESP32-S3
Transport: UDP
UDP port:
5005Multiple ESP32 CSI nodes
Sensing server running with:
--source esp32--udp-port 5005--udp-bind 0.0.0.0--http-port 8080--ws-port 8765
Adaptive model/calibration enabled
ESP32 nodes are continuously sending CSI frames to the server
Observed Behavior
The server successfully receives CSI packets and switches to the live ESP32 source, but the Active Person Count does not reliably correspond to the actual occupancy.
For example:
Actual people | RuView Active Person Count -- | -- 0 | Variable 1 | Variable 2 | Sometimes 1 / other values 3 | VariableThe exact incorrect value is not always the same; the main issue is that the reported count changes even when the physical number of people remains constant.
Expected Behavior
If the ESP32 nodes are providing valid CSI data and the room occupancy remains unchanged, the Active Person Count should remain reasonably stable and correspond to the actual number of people present.
For example:
Actual occupancy: 2 people
Expected:
1 → 2
2 → 2
2 → 2
2 → 2
rather than continuously fluctuating between different values.
Additional Observation
I have also noticed that the server appears to have multiple stages involved in the occupancy/person-count calculation, including the CSI processing, adaptive/field model processing, per-node person-count state, and final person-count aggregation.
I would like to understand which component is considered authoritative for the final Active Person Count.
In particular, could you clarify whether the following path is expected to contribute to the final count?
ESP32 CSI
↓
CSI processing
↓
DynamicMinCut / correlation-based estimation
↓
corr_persons
↓
NodeState::prev_person_count
↓
aggregate_person_count()
↓
Active Person Count
Question
Could someone confirm whether this behavior is expected with the current ESP32 CSI pipeline, or whether there is a known issue affecting the Active Person Count?
I can provide:
Server logs
ESP32 CSI capture logs
Calibration data
Adaptive model information
Exact number of ESP32 nodes
Reproduction recordings
Screenshots/video of the changing person count
if required.
Thank you.
Activity
ruvnet commentedon Sep 30, 2026
Merged #2060 as e1fbbed to address confirmed state-consistency defects in this path:
Yes, the
corr_persons → NodeState::prev_person_count → aggregate_person_count()path contributes to the final estimate. The server's room-wide output issensing_update.estimated_persons, interpreted alongside classification and source/freshness status. The room presence gate decides whether a positive count is published; fresh per-node counts can raise the activity/model-derived estimate. Edge classification confidence is kept separate from the CSI count score.Validation: 15 focused count tests passed; the full sensing-server suite passed 929 tests with zero failures and two ignored. CI merge gates also passed, including Rust workspace tests and benchmark compilation. These are software regressions using synthetic fixtures. We still need your logs to determine whether these defects explain your particular fluctuations. Calibrated-zero precedence and buffered occupancy after a complete stream outage remain separate follow-ups.
Please retest a build containing #2060 and share the exact commit/configuration, node count and packet types/rates, calibration status, and timestamped
sensing_updateplus/api/v1/nodessamples covering a period of known steady occupancy. Redact credentials and unrelated personal/network data. Keeping this issue open for field confirmation.docs(claims): label pose and encoder figures honestly, caveat person …
docs(getting-started): add the symptom-first troubleshooting guide
ruvnet commentedon Oct 15, 2026
The underlying root cause is likely #1940: the eigenvalue-based occupancy counting is compiled out of the shipped server binary (
default-features = falseon the signal crate), leaving a fallback scalar threshold that can only distinguish 0 vs. ≥1 person. This explains why person count is stuck and does not track real occupancy changes.See also #1758 for a longer-running field report from a different user with the same symptoms.
Generated by Claude Code
danalec commentedon Oct 22, 2026
Reproduced on a live ESP32-S3 node against the current server. The count is not stable, and the reason is not the one the code suggests.
Setup
90:70:69:18:9b:d0, firmware 0.8.15 from this branch, 16 MB display-off build.sensing-serverbuilt from this branch,--source esp32 --udp-port 5005 --udp-bind 0.0.0.0 --udp-insecure-lan --bind-addr 127.0.0.1, one node./api/v1/sensing/latestwere sampled in the same run from the same origin: opening the serial port resets the board, so the node's uptime and the sampler's elapsed time share an origin.What was measured
Active Person Count(estimated_persons), 69 samples over 150 s:The count moves between 1, 2, 3 and 4 in an empty room.
The two verdicts also disagree with each other, which is the sharper symptom. In the first ten seconds the node reported
motion=0.00 presence=0.00(state 5) and the server's own room vote wasabsent, yet the count was still 1:Why
The count is derived from an activity score that is not a stable quantity on this scene:
motionflipping from 0.00 to 1.00 and staying there — in an empty room;features.varianceranged 6.32 … 281.2 over the same window — a 44x swing — and the dominant frequency hopped between 0.000 Hz and 3.935 Hz, i.e. between "no dominant component" and the motion band.score_to_person_countalready has asymmetric hysteresis (0.70/0.85 up, 0.55/0.92 down), but the input crosses the whole band every couple of seconds, so the count crosses it on every swing. The room count is thenactivity_count.max(node_max)— the maximum of two counts that each carry their own hysteresis, so neither holds the other still.So the behaviour is a known consequence of the current scoring rather than a misconfiguration, and on an empty scene the count is not a reliable occupancy number. The observation that the count changes while the number of people stays constant is reproducible, and the "not empty" verdict it produces is a false positive.
Where a fix would go
The count has to hold still for a while before it changes, not merely have a dead band: the hysteresis is on the output while the input crosses the whole range. A dwell on the count (the new value has to hold for N consecutive samples) would address that, and the
absent/present_movingroom vote flipping in the same window suggests the same input is worth stabilising at the classification level too.I have the live reproduction and can supply the trace and a candidate patch with tests.
danalec commentedon Oct 23, 2026
The part of this request that a pull request can own is done: the published person count no longer moves while the room is unchanged.
Concretely, on
mainatfefb0f91with one ESP32-S3 node (firmware 0.8.15) against an empty room, 200 s windows polled at 5 Hz, the same procedure before and after the change:The room classification stayed
present_movingfor the whole of the "before" window, so the movement was entirely in the count path: the count's hysteresis holds the output steady while the activity score sits inside a band, but it cannot hold it when the score crosses the whole band on its own.The published count now changes only after the new value has been observed continuously for 1.5 s — the same window the room classification uses, because both describe the same debounced room state. A count of zero is not held back, so an empty room is still reported as empty as soon as the room vote says so. Six new regression tests cover it, and a
RuView#2058fix-marker keeps the dwell from being silently dropped.What this does not change is what the count means: the activity score it derives from is not an occupancy estimator, so in an empty scene the count can still settle on a non-zero value. The dwell removes the movement, not the bias. The eigenvalue occupancy path remains a separate matter (#1940), and which component is authoritative for the final count is unchanged by this.