This issue collects the most common setup problems and their solutions. Check here before opening a new issue.
Quick Checks
| Symptom |
Likely Cause |
Fix |
| UI shows same skeleton regardless of people |
You're in simulation mode |
Check curl http://localhost:3000/health — if "source":"simulated", see Simulation vs Live below |
| Server says "No hardware detected" |
ESP32 not sending UDP to your computer |
Check --target-ip, firewall, WiFi connection (see No Data Arriving) |
| Only 1 node detected despite multiple ESP32s |
Node IDs not persisted |
Flash v0.5.0-esp32, re-provision each node separately |
| "Always 2 persons" when only 1 present |
Multipath ghosting |
Use 3+ nodes, update to latest server (PR #295 hysteresis fix) |
| Skeletons shaking / jittery |
No trained model + count instability |
PR #295 hysteresis fix + record calibration data |
Watchdog crash CPU 1: edge_dsp |
Firmware DSP task not yielding |
Flash v0.4.3-esp32 or later |
| Training endpoint returns instantly |
No recorded data to train on |
Record labeled data first (see Training below) |
Fall detected! flooding logs |
Threshold too low in old firmware |
Flash v0.4.3+ (threshold raised from 2.0 to 15.0) |
Simulation vs Live Mode
How to tell which mode you're in:
curl http://localhost:3000/health
"source":"simulated" → You're seeing synthetic data. The UI will not respond to real room conditions.
"source":"esp32" → You're receiving real CSI data from hardware.
To switch to live mode:
- Flash ESP32-S3 with v0.5.0-esp32 firmware
- Provision with your computer's real LAN IP (not
0.0.0.0):
# Find your IP: ifconfig (macOS) / ipconfig (Windows) / hostname -I (Linux)
python firmware/esp32-csi-node/provision.py --port YOUR_PORT \
--ssid "YourWiFi" --password "YourPassword" --target-ip YOUR_COMPUTER_IP
- Start server with
--source esp32 or -e CSI_SOURCE=esp32 (Docker)
- Verify:
curl http://localhost:3000/health should show "source":"esp32"
No Data Arriving (ESP32 → Server)
Diagnostic steps:
# 1. Check ESP32 serial output
python -m serial.tools.miniterm YOUR_PORT 115200
# Look for: "Got IP: 192.168.x.x" and "CSI cb #N" messages
# 2. Check if UDP packets reach your computer
# macOS/Linux:
sudo tcpdump -i any udp port 5005 -c 5
# Windows (PowerShell as Admin):
netstat -an | findstr "5005"
# 3. If no packets → re-provision with correct target IP
# 4. If packets arrive but server says "no hardware" → check firewall
Firewall rules (UDP 5005 must be open inbound):
- macOS: System Preferences → Security → Firewall → allow sensing-server
- Windows: Windows Defender Firewall → allow UDP 5005 inbound
- Linux:
sudo ufw allow 5005/udp
Multi-Node Setup
Each node must be provisioned one at a time with unique IDs:
# Node 0
python provision.py --port COM7 --ssid "WiFi" --password "pass" \
--target-ip 192.168.1.20 --node-id 0 --tdm-slot 0 --tdm-total 3
# Unplug, plug next ESP32
# Node 1
python provision.py --port COM7 --ssid "WiFi" --password "pass" \
--target-ip 192.168.1.20 --node-id 1 --tdm-slot 1 --tdm-total 3
# Node 2
python provision.py --port COM7 --ssid "WiFi" --password "pass" \
--target-ip 192.168.1.20 --node-id 2 --tdm-slot 2 --tdm-total 3
Important: --node-id 0,1,2 (comma-separated) does NOT work. Each node must be provisioned individually.
Training the Adaptive Classifier
The training endpoint returns instantly if no data has been recorded.
The adaptive classifier derives the class label from the recording filename, so the label must be encoded into the id you pass to /recording/start (e.g. train_empty_1, train_walking_1). Recognised tokens include empty/absent, still/sitting/standing, walking/moving, active/exercise; anything else falls back to train_<class>_*.jsonl.
# Record ~60s of empty room
curl -X POST http://localhost:3000/api/v1/recording/start \
-H 'Content-Type: application/json' \
-d '{"id": "train_empty_1"}'
sleep 60
curl -X POST http://localhost:3000/api/v1/recording/stop
# Record ~60s with 1 person present and moving
curl -X POST http://localhost:3000/api/v1/recording/start \
-H 'Content-Type: application/json' \
-d '{"id": "train_walking_1"}'
sleep 60
curl -X POST http://localhost:3000/api/v1/recording/stop
# Now train
curl -X POST http://localhost:3000/api/v1/adaptive/train
Note: the correct path is /api/v1/recording/... (not /record/...), and the label is passed via the JSON body id field — there is no ?label= query parameter.
What WiFi CSI Can and Cannot Do
Works today (no ML model needed): Presence detection, motion classification, person count (1-3), breathing rate, basic heart rate, fall detection.
Requires a trained model: Full skeleton/pose estimation, multi-person pose tracking, cross-room transfer.
The 3D skeleton shown in the UI without a model is driven by signal heuristics — it demonstrates the pipeline is working but is not a true pose reconstruction.
Useful Links
Related Issues
This issue collects the most common setup problems and their solutions. Check here before opening a new issue.
Quick Checks
curl http://localhost:3000/health— if"source":"simulated", see Simulation vs Live below--target-ip, firewall, WiFi connection (see No Data Arriving)CPU 1: edge_dspFall detected!flooding logsSimulation vs Live Mode
How to tell which mode you're in:
"source":"simulated"→ You're seeing synthetic data. The UI will not respond to real room conditions."source":"esp32"→ You're receiving real CSI data from hardware.To switch to live mode:
0.0.0.0):--source esp32or-e CSI_SOURCE=esp32(Docker)curl http://localhost:3000/healthshould show"source":"esp32"No Data Arriving (ESP32 → Server)
Diagnostic steps:
Firewall rules (UDP 5005 must be open inbound):
sudo ufw allow 5005/udpMulti-Node Setup
Each node must be provisioned one at a time with unique IDs:
Important:
--node-id 0,1,2(comma-separated) does NOT work. Each node must be provisioned individually.Training the Adaptive Classifier
The training endpoint returns instantly if no data has been recorded.
The adaptive classifier derives the class label from the recording filename, so the label must be encoded into the
idyou pass to/recording/start(e.g.train_empty_1,train_walking_1). Recognised tokens includeempty/absent,still/sitting/standing,walking/moving,active/exercise; anything else falls back totrain_<class>_*.jsonl.What WiFi CSI Can and Cannot Do
Works today (no ML model needed): Presence detection, motion classification, person count (1-3), breathing rate, basic heart rate, fall detection.
Requires a trained model: Full skeleton/pose estimation, multi-person pose tracking, cross-room transfer.
The 3D skeleton shown in the UI without a model is driven by signal heuristics — it demonstrates the pipeline is working but is not a true pose reconstruction.
Useful Links
Related Issues