Commit Graph

10 Commits

Author SHA1 Message Date
arsen fc905c5c77 deploy(esp32s3): fix DSP, OTA, discovery, mobile WS for room01/room02
End-to-end deployment fixes that took the two ESP32-S3 sensor boards
(room01, room02) from "boots but DSP frozen, OTA always rolls back" to
"motion/presence/breathing all live, two consecutive OTA round-trips
succeed". Full forensic write-up in docs/adr/ADR-098.

Firmware (firmware/esp32-csi-node/main/):
* csi_collector.c — remove esp_wifi_set_promiscuous(true): this call
  silenced the CSI RX callback entirely on this silicon revision
  (yield=0pps). Without it, callbacks resume at ~5-10 pps.
* edge_processing.c — root cause: incoming CSI frames carry 192
  subcarriers but EDGE_MAX_SUBCARRIERS=128, so the size check
  early-returned every frame and Step 8 (motion) never ran. Truncate
  to 128 + warn once instead of returning.
* edge_processing.c — replace per-bin unwrapped-phase variance with
  temporal variance of per-frame broadband mean amplitude. Empirical
  separation on deployed hardware: empty 0.07-0.10, walking 3.5-14
  (~44x). Scaled by /3.0 and clamped to [0,1].
* edge_processing.c — biquad fs 20.0 -> 10.0, matching the actual
  callback rate (was halving the breathing passband).
* ota_update.c — OTA_WITH_SEQUENTIAL_WRITES -> OTA_SIZE_UNKNOWN to
  erase the full target partition (stale tail of the previous larger
  image was crashing the new image on boot, looking like rollback).
* ota_update.c — httpd_config_t.stack_size = 8192 (default 4 KB
  overflowed in OTA verify path).
* main.c — log esp_reset_reason() and running_partition->label once
  at app_main start, so OTA outcomes are visible without guesswork.
* sdkconfig.defaults — local deployment defaults: tier=2, display
  disabled (no expander on these boards), 8192 timer stack.

Sensing server (v2/crates/wifi-densepose-sensing-server/):
* src/main.rs — parse_rv_feature_state() for the 0xC5110006
  feature_state packet that RuView FW emits by default; this format
  was previously unhandled. Wire ahead of parse_esp32_vitals.
* src/main.rs — BaselineTracker with hysteretic motion gating on top
  of FW-reported scores, so UI sees clean boolean presence transitions.
* src/main.rs — refuse --source simulate; remove auto-fallback to
  synthetic data. Production builds never run on fake signals.
* src/main.rs/csi.rs — parse_csi_lean() for legacy FW 5.47 CSV
  packets; defence-in-depth for mistakenly flashed legacy sensors.

Desktop UI (v2/crates/wifi-densepose-desktop/):
* src/commands/discovery.rs — third discovery path: HTTP /status sweep
  across the local /24 in parallel with mDNS/UDP. mDNS+UDP-beacon are
  not advertised by current RuView FW. Replace sequential
  for-task-in-tasks select-with-deadline (which blocked on slow
  unrelated IPs) with futures::join_all + overall timeout.
* src/commands/server.rs — pass --bind-addr (was --bind); pass
  RUST_LOG env instead of unsupported --log-level; auto-load bundled
  wifi-densepose-v1.rvf next to the binary; reasonable defaults
  (esp32 source, 0.0.0.0 bind).
* ui/* — keep last good node list when a poll returns 0 (discovery
  is jittery on busy LANs); 8 s timeout (was 3 s); remove "simulate"
  from DataSource enum and Sensing dropdown; default Sensing source
  esp32.

Mobile UI (ui/mobile/):
* constants/websocket.ts — WS_PATH '/ws/sensing' + WS_PORT 8765 to
  match the RuView sensing-server's WS endpoint (was the legacy
  FastAPI /api/v1/stream/pose).
* services/ws.service.ts — derive WS host from serverUrl but use
  WS_PORT; remove simulation fallback paths entirely (no
  generateSimulatedData, no startSimulation on reconnect failure).
* stores/settingsStore.ts — serverUrl defaults to
  http://100.123.189.10:8080 (deployed Mac's Tailscale IP), so the
  phone connects from any network without LAN dependency.
* stores/matStore.ts — default dataSource='real',
  simulationAcknowledged=true; no synthetic triage data.
* screens/MATScreen, VitalsScreen — hide simulation overlay/badge.

Docker:
* docker/docker-compose.yml — sensing-server host port 5005 -> 5006
  to match the RuView FW's compiled CSI_TARGET_PORT default.

Documentation:
* docs/adr/ADR-098-esp32s3-csi-deployment-fixes.md — full forensic
  ADR covering each decision, the empirical numbers that drove it,
  the false hypotheses we ruled out along the way, and open items.

Verified on hardware (both nodes):
* motion empty < 0.05 (room01 0.018, room02 0.070)
* motion walking > 0.3 within 1-3 s, saturates at 1.0
* motion decay < 0.1 within 5 s after leaving
* breathing 21-22 BPM detected after ~30 s stationary
* two consecutive OTA round-trips succeed without USB intervention
* discovery finds both sensors via HTTP sweep in <2 s

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-14 18:56:04 +07:00
ruv c641fc44ae feat(docker+sensing-server): refresh Docker publish + opt-in bearer-token API auth
Closes #520, #514, #443.

## #520 / #514 — stale Docker image, missing UI assets

`ruvnet/wifi-densepose:latest` was published before `ui/observatory*` and
`ui/pose-fusion*` were added; users see /app/ui missing those files and the
v0.6+ packet format doesn't reach the server. Two fixes:

1. `docker/Dockerfile.rust` now `RUN`s a build-time guard after `COPY ui/`
   that fails the build if `index.html` / `observatory.html` / `pose-fusion.html`
   / `viz.html` (or the `observatory/` / `pose-fusion/` / `components/` /
   `services/` directories) are missing, plus an exec-bit check on
   `/app/sensing-server`. A stale image can never be silently produced again.

2. New `.github/workflows/sensing-server-docker.yml` rebuilds + pushes on
   every change to the Dockerfile, the server crate, the signal/vitals/
   wifiscan crates, the workspace manifests, the `ui/` tree, or itself —
   plus `v*` tags and manual dispatch. Pushes to both `docker.io/ruvnet/
   wifi-densepose` AND `ghcr.io/ruvnet/wifi-densepose` with `latest` +
   `vX.Y.Z` + `sha-<short>` tags, then post-push smoke-tests the artifact:
   /health, /api/v1/info, the observatory + pose-fusion HTML, AND the
   bearer-auth path (no token → 401, wrong → 401, correct → 200). Uses the
   `DOCKERHUB_USERNAME`/`DOCKERHUB_TOKEN` repo secrets; ghcr.io rides on
   the workflow's GITHUB_TOKEN.

## #443 — sensing-server REST API auth model

QE security audit raised that 40+ /api/v1/* routes have no auth layer with
a default `0.0.0.0` bind. New `wifi_densepose_sensing_server::bearer_auth`
module + middleware:

  - Env-var-gated: `RUVIEW_API_TOKEN` unset/empty ⇒ middleware is a no-op
    (current LAN-mode behaviour preserved — **no default change**); set ⇒
    every `/api/v1/*` request must carry `Authorization: Bearer <token>`
    or the server returns 401.
  - Constant-time byte compare via local `ct_eq` (no new dep).
  - `/health*`, `/ws/sensing`, and `/ui/*` are intentionally never gated
    (orchestrator probes + local browsers).
  - Startup logs which mode is active and warns when auth is ON with a
    `0.0.0.0` bind.
  - 8 unit tests on the middleware via `tower::ServiceExt::oneshot`
    (sensing-server lib tests 191 → 199, 0 failures).

Verified locally: `cargo build --workspace --no-default-features` ✓,
`cargo test -p wifi-densepose-sensing-server --no-default-features` ✓.

Co-Authored-By: claude-flow <ruv@ruv.net>
2026-05-13 08:52:25 -04:00
rUv 81cc241b9e
chore(repo): move v1/ → archive/v1/ + add archive/README.md (#430)
The Rust port at v2/ has been the primary codebase since the rename
in #427. The Python implementation at v1/ is no longer the active
target; the only load-bearing path is the deterministic proof bundle
at v1/data/proof/ (per ADR-011 / ADR-028 witness verification).

Move the whole Python tree into archive/v1/ and document the policy
in archive/README.md: no new features, bug fixes only when they affect
a still-load-bearing path (currently just the proof), CI continues to
verify the proof on every push and PR.

Path references updated in 26 files via path-pattern sed (only
matches v1/<known-child> patterns, never bare v1 or API URLs like
/api/v1/). Two double-prefix typos (archive/archive/v1/) caught and
hand-fixed in verify-pipeline.yml and ADR-011.

Validated:
- Python proof verify.py imports cleanly at archive/v1/data/proof/
  (numpy/scipy still required; CI installs requirements-lock.txt
  from archive/v1/ now)
- cargo test --workspace --no-default-features → 1,539 passed,
  0 failed, 8 ignored (unaffected by Python tree relocation)
- ESP32-S3 on COM7 untouched (no firmware paths changed)

After-merge: contributors should re-run any local `python v1/...`
commands as `python archive/v1/...` (CLAUDE.md and CHANGELOG already
updated).
2026-04-25 23:07:52 -04:00
rUv f49c722764
chore(repo): rename rust-port/wifi-densepose-rs → v2/ (flatten to one level) (#427)
The Rust port lived two directories deep (rust-port/wifi-densepose-rs/)
without any sibling under rust-port/ that warranted the extra level.
Move the whole workspace up to v2/ to match v1/ (Python) at the same
depth and shorten every cd / build command across the repo.

git mv preserves history for all tracked files. 60 files updated for
path references (CI workflows, ADRs, docs, scripts, READMEs, internal
.claude-flow state). Two manual fixes for relative-cd paths in
CLAUDE.md and ADR-043 that became wrong after the depth change
(cd ../.. → cd ..).

Validated:
- cargo check --workspace --no-default-features → clean (after target/
  nuke; the gitignored target/ was carried by the OS rename and had
  hard-coded old paths in build scripts)
- cargo test --workspace --no-default-features → 1,539 passed, 0 failed,
  8 ignored (same totals as pre-rename)
- ESP32-S3 on COM7 → still streaming live CSI (cb #40300, RSSI -64 dBm)

After-merge follow-up: contributors should `rm -rf v2/target` once and
let cargo regenerate from the new path.
2026-04-25 21:28:13 -04:00
voidborne-d e38c0f4dcc fix: Docker entrypoint arg handling + configurable model directory
Fixes #384: docker run with --source/--tick-ms flags now works correctly.
Fixes #399: model files in mounted volumes are now discoverable via MODELS_DIR env var.

Root cause (issue #384):
The Dockerfile used ENTRYPOINT ["/bin/sh", "-c"] with a shell-form CMD.
When users passed flags like `--source wifi --tick-ms 500` as docker run
arguments, Docker replaced CMD entirely, resulting in
`/bin/sh -c "--source wifi --tick-ms 500"` which executes `--source` as
a shell command → `--source: not found`.

Root cause (issue #399):
Model directory was hardcoded to the relative path `data/models`. When Docker
users mounted models to `/app/models/`, the scan looked in the wrong place.

Changes:

1. docker/docker-entrypoint.sh (new):
   - Proper entrypoint script that handles both env-var-based defaults and
     user-passed CLI flags
   - No arguments → starts server with CSI_SOURCE env var as --source
   - Flag arguments (start with -) → prepends /app/sensing-server + defaults,
     appends user flags (clap last-wins allows overrides)
   - Non-flag first arg → exec passthrough (e.g., /bin/sh for debugging)
   - Sets --bind-addr 0.0.0.0 (was 127.0.0.1 which blocks container access)

2. docker/Dockerfile.rust:
   - Switch from ENTRYPOINT ["/bin/sh", "-c"] to exec-form entrypoint
   - Add MODELS_DIR env var (default: data/models)
   - COPY the entrypoint script into the image

3. docker/docker-compose.yml:
   - Remove shell-form command (entrypoint handles defaults)
   - Add MODELS_DIR env var

4. model_manager.rs + main.rs:
   - Replace hardcoded `data/models` path with `effective_models_dir()`
     / `models_dir()` that reads MODELS_DIR env var at runtime
   - Docker users can now: docker run -v /host/models:/app/models -e MODELS_DIR=/app/models

5. tests/test_docker_entrypoint.sh (new, 17 tests):
   - Default CSI_SOURCE substitution (6 assertions)
   - Custom CSI_SOURCE propagation
   - User-passed flag arguments (--source, --tick-ms, --model)
   - Unset CSI_SOURCE defaults to auto
   - Explicit command passthrough
   - MODELS_DIR env var propagation
2026-04-18 21:55:01 +00:00
rUv c193cd4299
Merge pull request #88 from Harshit10j2004/harshit_1001
Update the dockerfile.python 1 by disabling Python bytecode generation
2026-03-02 11:27:17 -05:00
ruv 8166d8d822 fix: live demo static pose & inaccurate sensing data (issue #86)
- Docker default changed from --source simulated to --source auto
  (auto-detects ESP32 on UDP 5005, falls back to simulation)
- Pose derivation now driven by real sensing features: motion_band_power,
  breathing_band_power, variance, dominant_freq_hz, change_points
- Temporal feature extraction: 100-frame circular buffer, Goertzel
  breathing rate estimation (0.1-0.5 Hz), frame-to-frame L2 motion
  detection, SNR-based signal quality metric
- Signal field driven by subcarrier variance spatial mapping instead
  of fixed animation circle
- UI data source indicators: LIVE/RECONNECTING/SIMULATED banner on
  sensing tab, estimation mode badge on live demo tab
- Setup guide panel explaining ESP32 count requirements for each
  capability level (1x: presence, 3x: localization, 4x+: full pose)
- Tick rate improved from 500ms to 100ms (2fps to 10fps)
- Fixed Option<f64> division bug from PR #83
- ADR-035 documents all decisions

Closes #86

Co-Authored-By: claude-flow <ruv@ruv.net>
2026-03-02 10:54:07 -05:00
harshit 8a46fff6b0 Update the dockerfile.python 1 by disabling Python bytecode generation with PYTHONDONTWRITEBYTECODE=1 to make runtime faster 2026-03-02 20:53:15 +05:30
ruv 44b9c30dbc fix: Docker port mismatch — server now binds 3000/3001 as documented
The sensing server defaults to HTTP :8080 and WS :8765, but Docker
exposes :3000/:3001. Added --http-port 3000 --ws-port 3001 to CMD
in both Dockerfile.rust and docker-compose.yml.

Verified both images build and run:
- Rust: 133 MB, all endpoints responding (health, sensing/latest,
  vital-signs, pose/current, info, model/info, UI)
- Python: 569 MB, all packages importable (websockets, fastapi)
- RVF file: 13 KB, valid RVFS magic bytes

Also fixed README Quick Start endpoints to match actual routes:
- /api/v1/health → /health
- /api/v1/sensing → /api/v1/sensing/latest
- Added /api/v1/pose/current and /api/v1/info examples
- Added port mapping note for Docker vs local dev

Co-Authored-By: claude-flow <ruv@ruv.net>
2026-03-01 00:56:41 -05:00
ruv add9f192aa feat: Docker images, RVF export, and README update
- Add docker/ folder with Dockerfile.rust (132MB), Dockerfile.python (569MB),
  and docker-compose.yml
- Remove stale root-level Dockerfile and docker-compose files
- Implement --export-rvf CLI flag for standalone RVF package generation
- Generate wifi-densepose-v1.rvf (13KB) with model weights, vital config,
  SONA profile, and training provenance
- Update README with Docker pull/run commands and RVF export instructions
- Update test count to 542+ and fix Docker port mappings
- Reply to issues #43, #44, #45 with Docker/RVF availability

Co-Authored-By: claude-flow <ruv@ruv.net>
2026-02-28 23:44:30 -05:00