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
3× ESP32-S3 presence-detection build — a control method to separate real presence from channel drift, my honest results so far, and where I'd love guidance
#1416
First: thanks for open-sourcing this. I'm building toward exactly the use case this is perfect for — whole-home presence without any indoor cameras (I run a local Home Assistant setup and deliberately keep cameras outside only). WiFi CSI is the one modality that could give me per-room "someone's here" without a lens, so I really want to get this working, and I've got the hardware set up and staying up.
Posting this as its own thread to (a) share a control experiment I think is useful for anyone measuring presence, (b) give honest numbers from my room, and (c) ask the folks who've gotten further what I should try next.
One distinction I wanted to nail down for myself, since a lot of the discussion elsewhere turns on it: "the nodes stream real CSI" and "presence detection works" are two separate claims. I confirmed the first solidly; the second I haven't cracked yet, and I'd like help figuring out whether that's placement, phase, calibration, or something else.
Server ruvnet/wifi-densepose:v0.8.4-esp32, --net=host, --source esp32, /health → "source":"esp32", all three nodes in one bedroom on the same AP.
Confirmed it's real hardware, not sim mode: I tcpdump'd UDP frames from the three ESP32 IPs and hand-decoded them against the ADR-018 spec in #34 — magic 0xC5110001, correct header, int8 I/Q, 64-subcarrier HT20 (some 128), RSSI matching each board's placement, subcarrier profiles with the expected DC-null shape and real temporal variation. So the pipeline genuinely works end to end. 🎉 That part was really satisfying to see.
A control experiment I'd suggest for everyone
The thing that tripped me up (and might be tripping up some positive reports): a naive occupied-vs-empty test is confounded by time-of-day channel drift. So I ran three windows:
5.9 h occupied overnight (asleep in the room)
a fresh empty baseline after leaving
an occupied-again window 37 min after the empty one — same time of day, so drift is controlled
The confounded column looks great (node3 0.96!). But the controlled column is the honest one, and it tells a cautionary tale:
node3's 0.96 was mostly overnight drift — controlled, it drops to ~chance.
node1's perfect 1.0 was a link artifact — during the empty window its RSSI was −59 dBm vs −87 dBm in both occupied windows (a 28 dB swing that reverses when re-occupied = a re-association, not a person).
Controlled, stable-link, combined: AUC ≈ 0.58, so with amplitude-only features I'm not yet separating presence from noise.
This lines up with the server's own adaptive classifier for me: after room calibration (recorded train_empty_room/train_still_presence/train_walking/train_active_movement, correctly split into absent/present_still/present_moving/active), training accuracy was 0.45 on 4 classes, and a known-empty room still read "present" 31/31 frames. So I don't think I'm doing something obviously wrong — I think I'm missing an ingredient.
(Two useful robustness notes: the per-node AUCs don't depend on cross-node TDM sync, so imperfect TDM isn't the cause; and the same-time-of-day control neutralizes the documented "fan/AC false-positive" caveat, since a stationary interferer is present in both windows.)
Where I think the signal might be — and where I'd love input
I'm fairly convinced the info is there and I'm just not extracting it. My leading suspects, and I'd love to know which to prioritize:
Phase. I used amplitude only and threw away phase — but breathing/presence is often in the phase and the 0.1–0.5 Hz band. Is phase the main lever here? Any recommended CSI phase-sanitization (linear detrend / conjugate-multiply between antennas) for these boards?
Placement. Mine were ~4 m apart, one bedroom, not rigorously to the placement guide (height/diagonal). How much does getting this right actually move presence AUC in your experience?
The pretrained encoder. I dug into ruvnet/wifi-densepose-pretrained — it's an 8→64→128 MLP on the 8 summary features (not raw CSI), and its 82.3% is heldout_triplet_acc (random encoder ~69.6% on the same metric), so I set expectations that it's a representation model rather than a presence classifier. Is there a presence-specific model/head I should be loading instead, and what's the current jsonl→rvf path (saw Training does not start – /ws/train/progress returns 404 and no model is generated #1233/Training is badly documented #1322 — is there a working converter yet)?
Feature window / cadence. Would longer windows, or the breathing-band spectral features specifically, be the unlock over my crude window stats?
Offer
I've got the 3-node rig up and staying up, a clean overnight raw-CSI capture (749k frames, ADR-018 parsed), and Python parse/analysis scripts I'm happy to share so results are comparable. I'll gladly re-run with whatever placement/firmware/feature changes people suggest and post updated controlled numbers. If a phase-aware pipeline is the key, I've even got idle GPUs to throw at training a room-specific model.
Genuinely want to get this to a reliable boolean "room occupied" — that alone would be hugely useful for camera-free home automation. What would you try next?
reacted with thumbs up emoji reacted with thumbs down emoji reacted with laugh emoji reacted with hooray emoji reacted with confused emoji reacted with heart emoji reacted with rocket emoji reacted with eyes emoji
Uh oh!
There was an error while loading. Please reload this page.
First: thanks for open-sourcing this. I'm building toward exactly the use case this is perfect for — whole-home presence without any indoor cameras (I run a local Home Assistant setup and deliberately keep cameras outside only). WiFi CSI is the one modality that could give me per-room "someone's here" without a lens, so I really want to get this working, and I've got the hardware set up and staying up.
Posting this as its own thread to (a) share a control experiment I think is useful for anyone measuring presence, (b) give honest numbers from my room, and (c) ask the folks who've gotten further what I should try next.
One distinction I wanted to nail down for myself, since a lot of the discussion elsewhere turns on it: "the nodes stream real CSI" and "presence detection works" are two separate claims. I confirmed the first solidly; the second I haven't cracked yet, and I'd like help figuring out whether that's placement, phase, calibration, or something else.
Setup
--edge-tier 0(raw passthrough), unique--node-id,--tdm-slot 0/1/2 --tdm-total 3.ruvnet/wifi-densepose:v0.8.4-esp32,--net=host,--source esp32,/health→"source":"esp32", all three nodes in one bedroom on the same AP.Confirmed it's real hardware, not sim mode: I
tcpdump'd UDP frames from the three ESP32 IPs and hand-decoded them against the ADR-018 spec in #34 — magic0xC5110001, correct header, int8 I/Q, 64-subcarrier HT20 (some 128), RSSI matching each board's placement, subcarrier profiles with the expected DC-null shape and real temporal variation. So the pipeline genuinely works end to end. 🎉 That part was really satisfying to see.A control experiment I'd suggest for everyone
The thing that tripped me up (and might be tripping up some positive reports): a naive occupied-vs-empty test is confounded by time-of-day channel drift. So I ran three windows:
Amplitude window features, balanced classes, AUC (imbalance-robust), 20–30 draws:
The confounded column looks great (node3 0.96!). But the controlled column is the honest one, and it tells a cautionary tale:
This lines up with the server's own adaptive classifier for me: after room calibration (recorded
train_empty_room/train_still_presence/train_walking/train_active_movement, correctly split into absent/present_still/present_moving/active), training accuracy was 0.45 on 4 classes, and a known-empty room still read "present" 31/31 frames. So I don't think I'm doing something obviously wrong — I think I'm missing an ingredient.(Two useful robustness notes: the per-node AUCs don't depend on cross-node TDM sync, so imperfect TDM isn't the cause; and the same-time-of-day control neutralizes the documented "fan/AC false-positive" caveat, since a stationary interferer is present in both windows.)
Where I think the signal might be — and where I'd love input
I'm fairly convinced the info is there and I'm just not extracting it. My leading suspects, and I'd love to know which to prioritize:
ruvnet/wifi-densepose-pretrained— it's an 8→64→128 MLP on the 8 summary features (not raw CSI), and its 82.3% isheldout_triplet_acc(random encoder ~69.6% on the same metric), so I set expectations that it's a representation model rather than a presence classifier. Is there a presence-specific model/head I should be loading instead, and what's the current jsonl→rvf path (saw Training does not start – /ws/train/progress returns 404 and no model is generated #1233/Training is badly documented #1322 — is there a working converter yet)?Offer
I've got the 3-node rig up and staying up, a clean overnight raw-CSI capture (749k frames, ADR-018 parsed), and Python parse/analysis scripts I'm happy to share so results are comparable. I'll gladly re-run with whatever placement/firmware/feature changes people suggest and post updated controlled numbers. If a phase-aware pipeline is the key, I've even got idle GPUs to throw at training a room-specific model.
Genuinely want to get this to a reliable boolean "room occupied" — that alone would be hugely useful for camera-free home automation. What would you try next?
All reactions