#1703·RuView

ESP32 recordings contain sensing_update events with empty CSI; dashboard training parses 0 frames and falls back to live history

Author: Brains1337Created Aug 24, 2026Updated Sep 16, 2026
Labelsbug

Reproduced on image digest sha256:48a71081b6df942530dda4e63bfb9c0aa6c5aa9f5022e9815e176ac8bcac9658, created 2026-08-22.

Summary

I am trying to collect room-specific ESP32 CSI recordings through the RuView dashboard and train from the selected recordings.

The recording files are created successfully and are large/non-empty, but they contain sensing_update JSON events where every node has:

"amplitude": [],
"subcarrier_count": 0

When I select those recordings and click Start Training, the backend can read the files but fails to parse every line as a training frame. It then falls back to 100 in-memory live frames, trains for 20 epochs, early-stops, and produces 0 PCK / 0 OKS.

Is this expected for the current latest image? What supported ESP32 firmware/image/configuration combination produces .csi.jsonl records compatible with training_api?

Environment

  • Docker image reference: ruvnet/wifi-densepose:latest
  • Resolved image digest:
    ruvnet/wifi-densepose@sha256:48a71081b6df942530dda4e63bfb9c0aa6c5aa9f5022e9815e176ac8bcac9658
    
  • Image created: 2026-08-22T22:14:50.126177885Z
  • Container name: ruview
  • Data source: CSI_SOURCE=esp32
  • HTTP API/UI: port 3000
  • WebSocket: port 3001
  • ESP32 CSI UDP ingress: port 5005
  • UDP bind: 0.0.0.0:5005
  • UDP source allowlist: enabled
  • Persistent data mount:
    /opt/stacks/ruvnet-wifi-densepose/data -> /app/data
    
  • Training recordings: 8 JSONL recordings, approximately 973 MB total
  • Nodes observed in live API: IDs 1, 3, 4, 5, and 6

Expected behavior

Based on the documented recording/training workflow, I expected:

  1. POST /api/v1/recording/start / dashboard Start Recording to persist usable CSI frames.
  2. Recordings to contain non-empty CSI/amplitude or raw-subcarrier data.
  3. Selected recordings to be parsed by the dashboard training workflow.
  4. Training to use the selected recording dataset rather than a 100-frame live-history fallback.
  5. A clear UI/API error if no selected recording can be parsed.

Actual behavior

1. Recording files are created but use an unexpected shape

Example first line from a recording, with the long payload truncated:

{
  "type": "sensing_update",
  "timestamp": 1787540226.629,
  "source": "esp32",
  "tick": 191299,
  "nodes": [
    {
      "node_id": 6,
      "rssi_dbm": -66.0,
      "position": [2.0, 0.0, 1.5],
      "amplitude": [],
      "subcarrier_count": 0,
      "sync": {
        "is_leader": false,
        "is_valid": false,
        "sequence": 68560,
        "csi_fps_ema": 35.54
      },
      "node_inference": {
        "classification": "present_moving",
        "confidence": 0.4
      }
    }
  ]
}

The original recordings are saved as:

data/recordings/rec_<timestamp>.jsonl

The dashboard training loader looked for:

data/recordings/rec_<timestamp>.csi.jsonl

I created temporary symlinks from the expected .csi.jsonl filenames to the existing .jsonl files only to diagnose whether this was a filename issue. This allowed the loader to open the files, but parsing still failed for every line.

2. Trainer parses zero frames from all selected recordings

Relevant training logs:

INFO sensing_server::training_api: Real supervised training started: 100 epochs, lr=0.0003, lambda=0.0001

INFO sensing_server::training_api: Loaded recording rec_1787540226: 9709 lines, 0 frames, 9709 parse errors
INFO sensing_server::training_api: Loaded recording rec_1787541090: 3730 lines, 0 frames, 3730 parse errors
INFO sensing_server::training_api: Loaded recording rec_1787541233: 3826 lines, 0 frames, 3826 parse errors
INFO sensing_server::training_api: Loaded recording rec_1787541676: 5702 lines, 0 frames, 5702 parse errors
INFO sensing_server::training_api: Loaded recording rec_1787540550: 9820 lines, 0 frames, 9820 parse errors
INFO sensing_server::training_api: Loaded recording rec_1787537286: 3847 lines, 0 frames, 3847 parse errors
INFO sensing_server::training_api: Loaded recording rec_1787541366: 9371 lines, 0 frames, 9371 parse errors

INFO sensing_server::training_api: No recordings found for dataset_ids; falling back to live frame_history
INFO sensing_server::training_api: Loaded 100 frames for training
INFO sensing_server::training_api: Features extracted: 100 samples, 396 features/sample, 51 targets/sample
INFO sensing_server::training_api: Train/val split: 80 train, 20 val

That is 46,005 JSONL lines across the selected recordings, with:

0 parsed training frames
46,005 parse errors

3. Training finishes, but only from fallback frames

INFO sensing_server::training_api: Epoch 20/100: loss=50678.782466, [email protected]=0.0000, val_mse=61536.2854, best_pck_torso_h=0.0000@0, patience=0
INFO sensing_server::training_api: Early stopping at epoch 20 (best=0, [email protected]=0.0000)
INFO sensing_server::training_api: Trained model saved: data/models/trained-supervised-...rvf (20247 params, [email protected]=0.0000)
INFO sensing_server::training_api: Real supervised training finished: phase=completed

/api/v1/train/status then reports:

{
  "active": false,
  "best_epoch": 0,
  "best_pck": 0.0,
  "enabled": true,
  "epoch": 20,
  "eta_secs": 0,
  "patience_remaining": 0,
  "phase": "completed",
  "total_epochs": 100,
  "train_loss": 50678.782466,
  "val_oks": 0.0,
  "val_pck": 0.0
}

Live sensing state

The server appears to receive ESP32-related node traffic:

INFO sensing_server: UDP: 0.0.0.0:5005 (ESP32 CSI)
INFO sensing_server: Source: esp32
INFO sensing_server: Data source: esp32 (udp_receiver=true, simulator=false, wifi=false)
INFO sensing_server: UDP data plane security: bind 0.0.0.0:5005 — ROUTABLE, source allowlist enforced
INFO sensing_server: UDP listening on 0.0.0.0:5005 for ESP32, MediaTek, Qualcomm CSI, and RTL8720F radar frames

However, /api/v1/sensing/latest shows every listed node with empty CSI fields. Example:

{
  "node_id": 5,
  "rssi_dbm": -53.0,
  "amplitude": [],
  "subcarrier_count": 0,
  "sync": {
    "csi_fps_ema": 29.48,
    "csi_fps_samples": 5190,
    "is_valid": false,
    "staleness_ms": 752
  }
}

Other relevant live API observations:

frame_rate_hz: 0.0
sync.is_valid: false

The API still reports presence/motion and estimated persons, but all returned pose keypoints have confidence: 0.0, so I do not consider that output valid.

Fusion warnings

The server repeatedly logs multistatic-fusion failures:

WARN sensing_server::engine_bridge:
fusion error: Timestamp spread 71158 us exceeds guard interval 60000 us

WARN sensing_server::engine_bridge:
fusion error: Timestamp spread 102982 us exceeds guard interval 60000 us

WARN sensing_server::engine_bridge:
fusion error: Timestamp spread 123242 us exceeds guard interval 60000 us

All observed nodes have sync.is_valid: false.

I understand this timing problem may affect fused inference quality, but it does not appear to explain why each recording line has empty amplitudes and cannot deserialize as a training frame.

Questions

  1. Is the dashboard recording endpoint currently expected to write sensing_update JSONL records, or should it write raw CSI/training-frame .csi.jsonl records?
  2. Is the .jsonl versus .csi.jsonl filename mismatch known/fixed in a newer image or commit?
  3. Why does the training UI silently fall back to frame_history when all selected recordings parse as zero frames, instead of reporting a failure?
  4. Which ESP32 firmware version/configuration and ruvnet/wifi-densepose image tag are known to produce non-empty:
    nodes[].amplitude
    nodes[].subcarrier_count
    
    and recordings compatible with training_api?
  5. Is there a documented raw-CSI capture endpoint or mode distinct from the dashboard Start Recording action?
  6. Is --calibrate the recommended workflow for single-room presence/motion calibration while the supervised pose-training path requires a separate MM-Fi/Wi-Pose or paired/labelled dataset workflow?
  7. Are the repeated timestamp-spread fusion errors and sync.is_valid: false expected with the current ESP32 firmware, and what is the supported procedure to establish valid multi-node synchronization?

Additional notes

  • I have preserved the original recordings and model files and can provide a redacted first-line sample, file statistics, or additional non-secret logs if needed.
  • I will not attach API tokens, .env contents, or raw network credentials.
  • The original recordings are approximately 973 MB total, so I have not attached them directly.