Skip to content

fix(sensing): live-CSI presence/motion tuning for display-less S3 nodes - #1717

Open
davidwajch wants to merge 1 commit into
ruvnet:mainfrom
davidwajch:fix/csi-presence-sensing-tuning
Open

fix(sensing): live-CSI presence/motion tuning for display-less S3 nodes#1717
davidwajch wants to merge 1 commit into
ruvnet:mainfrom
davidwajch:fix/csi-presence-sensing-tuning

Conversation

@davidwajch

Copy link
Copy Markdown

Field-tuning the WiFi-CSI sensing pipeline against two ESP32-S3-DevKitC-1 nodes so a single occupant reads cleanly instead of as phantom "teleporting" detections. Changes span firmware and the sensing server, developed and validated live against real hardware in this session.

Firmware (esp32-csi-node)

  • sdkconfig.defaults: CONFIG_DISPLAY_ENABLE=n. On display-less DevKitC-1 boards the boot panel probe false-positives (no TCA9554 → "assume direct wiring"), leaving has_display=true so csi_collector_enable_data_capture() (the MGMT→MGMT+DATA promiscuous upgrade) is never applied and CSI yield collapses to 0 pps. Disabling the display fixes yield (0 → ~40 pps) and drops LVGL from the build.
  • csi_collector.c: self-ping lowered 50 Hz → 10 Hz. The 50 Hz flood dominated the channel and raised the empty-room CSI noise floor to the magnitude of a moving occupant, destroying presence SNR. 10 Hz still exceeds the Nyquist rate for human motion/breathing while cutting self-induced RF churn ~5x (empty variance floor ~175 → ~90).

Sensing server (wifi-densepose-sensing-server)

  • csi.rs: fix spatial-vs-temporal feature bug. variance returned intra_variance.max(temporal_variance) — the spatial spread across subcarriers (~constant whether empty or occupied), masking motion. Now publishes the temporal variance, and redefines motion_band_power as the frame-to-frame amplitude delta (a real temporal motion metric).
  • main.rs (attach_field_positions): adaptive empty-room presence gate. Per-node quiet baselines with asymmetric tracking (fast-down to find the floor / recover from a bad seed, slow-up so a moving occupant is not absorbed); occupancy = best ratio across nodes so a well-placed crossing node is not diluted by a noisy near-router one; hysteresis holds "occupied" through gaps between movements. Unoccupied → skeleton dropped (no phantom); occupied → estimated_persons pinned to 1. Also EMA-smooths the primary occupant's field-peak position to stop frame-to-frame "teleport", and clamps rendered skeleton count to the calibrated person count at the single publish choke point.

Known limitation

At boards-next-to-router geometry the occupant signal is comparable to ambient RF noise; reliable detection needs the boards placed so the router link crosses the room with the person in the middle.

🤖 Generated with claude-flow

Field-tuning the WiFi-CSI sensing pipeline against two ESP32-S3-DevKitC-1
nodes so a single occupant reads cleanly instead of as phantom "teleporting"
detections. Changes span firmware and the sensing server.

Firmware (esp32-csi-node):
- sdkconfig.defaults: set CONFIG_DISPLAY_ENABLE=n. On display-less DevKitC-1
  boards the boot panel probe false-positives (no TCA9554 -> "assume direct
  wiring"), leaving has_display=true so csi_collector_enable_data_capture()
  (the MGMT->MGMT+DATA promiscuous upgrade) is never applied and CSI yield
  collapses to 0 pps. Disabling the display fixes yield (0 -> ~40 pps) and
  drops LVGL from the build.
- csi_collector.c: lower the self-ping CSI traffic source from 50 Hz to 10 Hz.
  The 50 Hz flood dominated the channel and raised the empty-room CSI noise
  floor to the magnitude of a moving occupant, destroying presence SNR; 10 Hz
  still exceeds the Nyquist rate for human motion/breathing while cutting the
  self-induced RF churn ~5x (empty-room variance floor ~175 -> ~90).

Sensing server (wifi-densepose-sensing-server):
- csi.rs: the `variance` feature returned intra_variance.max(temporal_variance),
  i.e. the spatial spread across subcarriers (~constant whether empty or
  occupied), masking the motion signal. Publish temporal variance instead, and
  redefine motion_band_power as the frame-to-frame amplitude delta (a real
  temporal motion metric) rather than a spatial spread of the upper subcarriers.
- main.rs (attach_field_positions): add an adaptive empty-room presence gate.
  Per-node quiet baselines with asymmetric tracking (fast-down to find the floor
  and recover from a bad seed, slow-up so a moving occupant is not absorbed);
  occupancy is the BEST ratio across nodes so a well-placed crossing node is not
  diluted by a noisy near-router one; hysteresis holds "occupied" through the
  gaps between movements. When unoccupied the skeleton is dropped (no phantom)
  and estimated_persons is pinned to 1 when occupied. Also EMA-smooth the
  primary occupant's field-peak position to stop the frame-to-frame "teleport",
  and clamp the rendered skeleton count to the calibrated person count at the
  single publish choke point.

Known limitation: at boards-next-to-router geometry the occupant signal is
comparable to ambient RF noise; reliable detection needs the boards placed so
the router link crosses the room with the person in the middle.

Co-Authored-By: claude-flow <ruv@ruv.net>
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