mirror of
https://github.com/ruvnet/RuView
synced 2026-07-09 15:13:18 +00:00
81cc241b9e
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).
123 lines
6.3 KiB
Markdown
123 lines
6.3 KiB
Markdown
# ADR-019: Sensing-Only UI Mode with Gaussian Splat Visualization
|
||
|
||
| Field | Value |
|
||
|-------|-------|
|
||
| **Status** | Accepted |
|
||
| **Date** | 2026-02-28 |
|
||
| **Deciders** | ruv |
|
||
| **Relates to** | ADR-013 (Feature-Level Sensing), ADR-018 (ESP32 Dev Implementation) |
|
||
|
||
## Context
|
||
|
||
The WiFi-DensePose UI was originally built to require the full FastAPI DensePose backend (`localhost:8000`) for all functionality. This backend depends on heavy Python packages (PyTorch ~2GB, torchvision, OpenCV, SQLAlchemy, Redis) making it impractical for lightweight sensing-only deployments where the user simply wants to visualize live WiFi signal data from ESP32 CSI or Windows RSSI collectors.
|
||
|
||
A Rust port exists (`v2`) using Axum with lighter runtime footprint (~10MB binary, ~5MB RAM), but it still requires libtorch C++ bindings and OpenBLAS for compilation—a non-trivial build.
|
||
|
||
Users need a way to run the UI with **only the sensing pipeline** active, without installing the full DensePose backend stack.
|
||
|
||
## Decision
|
||
|
||
Implement a **sensing-only UI mode** that:
|
||
|
||
1. **Decouples the sensing pipeline** from the DensePose API backend. The sensing WebSocket server (`ws_server.py` on port 8765) operates independently of the FastAPI backend (port 8000).
|
||
|
||
2. **Auto-detects sensing-only mode** at startup. When the DensePose backend is unreachable, the UI sets `backendDetector.sensingOnlyMode = true` and:
|
||
- Suppresses all API requests to `localhost:8000` at the `ApiService.request()` level
|
||
- Skips initialization of DensePose-dependent tabs (Dashboard, Hardware, Live Demo)
|
||
- Shows a green "Sensing mode" status toast instead of error banners
|
||
- Silences health monitoring polls
|
||
|
||
3. **Adds a new "Sensing" tab** with Three.js Gaussian splat visualization:
|
||
- Custom GLSL `ShaderMaterial` rendering point-cloud splats on a 20×20 floor grid
|
||
- Signal field splats colored by intensity (blue → green → red)
|
||
- Body disruption blob at estimated motion position
|
||
- Breathing ring modulation when breathing-band power detected
|
||
- Side panel with RSSI sparkline, feature meters, and classification badge
|
||
|
||
4. **Python WebSocket bridge** (`archive/v1/src/sensing/ws_server.py`) that:
|
||
- Auto-detects ESP32 UDP CSI stream on port 5005 (ADR-018 binary frames)
|
||
- Falls back to `WindowsWifiCollector` → `SimulatedCollector`
|
||
- Runs `RssiFeatureExtractor` → `PresenceClassifier` pipeline
|
||
- Broadcasts JSON sensing updates every 500ms on `ws://localhost:8765`
|
||
|
||
5. **Client-side fallback**: `sensing.service.js` generates simulated data when the WebSocket server is unreachable, so the visualization always works.
|
||
|
||
## Architecture
|
||
|
||
```
|
||
ESP32 (UDP :5005) ──┐
|
||
├──▶ ws_server.py (:8765) ──▶ sensing.service.js ──▶ SensingTab.js
|
||
Windows WiFi RSSI ───┘ │ │ │
|
||
Feature extraction WebSocket client gaussian-splats.js
|
||
+ Classification + Reconnect (Three.js ShaderMaterial)
|
||
+ Sim fallback
|
||
```
|
||
|
||
### Data flow
|
||
|
||
| Source | Collector | Feature Extraction | Output |
|
||
|--------|-----------|-------------------|--------|
|
||
| ESP32 CSI (ADR-018) | `Esp32UdpCollector` (UDP :5005) | Amplitude mean → pseudo-RSSI → `RssiFeatureExtractor` | `sensing_update` JSON |
|
||
| Windows WiFi | `WindowsWifiCollector` (netsh) | RSSI + signal% → `RssiFeatureExtractor` | `sensing_update` JSON |
|
||
| Simulated | `SimulatedCollector` | Synthetic RSSI patterns | `sensing_update` JSON |
|
||
|
||
### Sensing update JSON schema
|
||
|
||
```json
|
||
{
|
||
"type": "sensing_update",
|
||
"timestamp": 1234567890.123,
|
||
"source": "esp32",
|
||
"nodes": [{ "node_id": 1, "rssi_dbm": -39, "position": [2,0,1.5], "amplitude": [...], "subcarrier_count": 56 }],
|
||
"features": { "mean_rssi": -39.0, "variance": 2.34, "motion_band_power": 0.45, ... },
|
||
"classification": { "motion_level": "active", "presence": true, "confidence": 0.87 },
|
||
"signal_field": { "grid_size": [20,1,20], "values": [...] }
|
||
}
|
||
```
|
||
|
||
## Files
|
||
|
||
### Created
|
||
| File | Purpose |
|
||
|------|---------|
|
||
| `archive/v1/src/sensing/ws_server.py` | Python asyncio WebSocket server with auto-detect collectors |
|
||
| `ui/components/SensingTab.js` | Sensing tab UI with Three.js integration |
|
||
| `ui/components/gaussian-splats.js` | Custom GLSL Gaussian splat renderer |
|
||
| `ui/services/sensing.service.js` | WebSocket client with reconnect + simulation fallback |
|
||
|
||
### Modified
|
||
| File | Change |
|
||
|------|--------|
|
||
| `ui/index.html` | Added Sensing nav tab button and content section |
|
||
| `ui/app.js` | Sensing-only mode detection, conditional tab init |
|
||
| `ui/style.css` | Sensing tab layout and component styles |
|
||
| `ui/config/api.config.js` | `AUTO_DETECT: false` (sensing uses own WS) |
|
||
| `ui/services/api.service.js` | Short-circuit requests in sensing-only mode |
|
||
| `ui/services/health.service.js` | Skip polling when backend unreachable |
|
||
| `ui/components/DashboardTab.js` | Graceful failure in sensing-only mode |
|
||
|
||
## Consequences
|
||
|
||
### Positive
|
||
- UI works with zero heavy dependencies—only `pip install websockets` (+ numpy/scipy already installed)
|
||
- ESP32 CSI data flows end-to-end without PyTorch, OpenCV, or database
|
||
- Existing DensePose tabs still work when the full backend is running
|
||
- Clean console output—no `ERR_CONNECTION_REFUSED` spam in sensing-only mode
|
||
|
||
### Negative
|
||
- Two separate WebSocket endpoints: `:8765` (sensing) and `:8000/api/v1/stream/pose` (DensePose)
|
||
- Pose estimation, zone occupancy, and historical data features unavailable in sensing-only mode
|
||
- Client-side simulation fallback may mislead users if they don't notice the "Simulated" badge
|
||
|
||
### Neutral
|
||
- Rust Axum backend remains a future option for a unified lightweight server
|
||
- The sensing pipeline reuses the existing `RssiFeatureExtractor` and `PresenceClassifier` classes unchanged
|
||
|
||
## Alternatives Considered
|
||
|
||
1. **Install minimal FastAPI** (`pip install fastapi uvicorn pydantic`): Starts the server but pose endpoints return errors without PyTorch.
|
||
2. **Build Rust backend**: Single binary, but requires libtorch + OpenBLAS build toolchain.
|
||
3. **Merge sensing into FastAPI**: Would require FastAPI installed even for sensing-only use.
|
||
|
||
Option 1 was rejected because it still shows broken tabs. The chosen approach cleanly separates concerns.
|