feat(sensing-server): per-node CSI separation + dynamic classifier classes#289
Draft
taylorjdawson wants to merge 1 commit intoruvnet:mainfrom
Draft
feat(sensing-server): per-node CSI separation + dynamic classifier classes#289taylorjdawson wants to merge 1 commit intoruvnet:mainfrom
taylorjdawson wants to merge 1 commit intoruvnet:mainfrom
Conversation
…asses Track each ESP32 node independently instead of merging all CSI frames into a single buffer. This enables per-node feature computation, spatial awareness, and proper multi-node visualization. Per-node CSI separation: - Add NodeState struct with per-node frame_history, RSSI history, features, classification, and smoothing state - Compute features per-node using each node's own temporal history - Add compute_fused_features() for backward-compatible aggregate - Add smooth_and_classify_node() for per-node motion classification - Add GET /api/v1/nodes endpoint for per-node health/status - Add PerNodeFeatureInfo to WebSocket SensingUpdate messages - Fix RSSI sign (use saturating_neg for correct negative dBm values) - Node timeout: stale after 5s, removed after 30s Dynamic classifier classes: - Remove hardcoded CLASSES array and N_CLASSES constant - Discover classes automatically from training data filenames - Convention: train_<class>_<description>.jsonl - Users can add any class by recording with appropriate filename - Backward compatible with existing 4-class models via serde default - AdaptiveModel now stores class_names as Vec<String> UI changes: - Dynamic node count display (was hardcoded "1 ESP32") - Per-node status cards showing RSSI, variance, classification - Color-coded node markers in 3D gaussian splat view - Per-node RSSI history tracking in sensing service - XSS-safe DOM element creation (no innerHTML with server data) Addresses ruvnet#237, ruvnet#276, ruvnet#51 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
7 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Motivation
The sensing server merges CSI frames from all ESP32 nodes into one
frame_historybuffer, discardingnode_idafter parsing. This means:Additionally, the adaptive classifier has hardcoded classes (
absent,present_still,present_moving,active), making it impossible for users to add custom activity classes without modifying source code.Addresses #237 (multi-node display identical for all states), #276 (only one detected), #51 (amplitude detection fragile).
Implements server-side per-node tracking from the ADR-029 (RuvSense multistatic sensing) architecture.
Changes
Per-node CSI separation (
sensing-server/src/main.rs)NodeStatestruct — per-nodeframe_history, RSSI history, features, classification, smoothing statesmooth_and_classify_node()— per-node motion classification with EMA/debouncecompute_fused_features()— weighted aggregation across active nodes (max-boosted for presence-sensitive features like variance and motion_band_power)build_per_node_features()— sorted per-node feature list for WebSocketnodes_endpoint()—GET /api/v1/nodesreturns per-node healthsaturating_neg()for correct negative dBm valuesSensingUpdate.node_features— optional field (backward compatible viaskip_serializing_if)Defaultimpls forFeatureInfoandClassificationInfoDynamic classifier classes (
adaptive_classifier.rs)CLASSESarray andN_CLASSESconstantclassify_recording_namereturnsOption<String>— discovers classes from filenamestrain_<class>_<description>.jsonl(e.g.,train_cooking_kitchen.jsonl)*absent*,*still*,*walking*,*active*AdaptiveModel.class_names: Vec<String>— dynamic, serialized in model JSONAdaptiveModel.weights: Vec<Vec<f64>>— dynamic class count instead of fixed array#[serde(default)]UI changes
createElement, noinnerHTML)Backward Compatibility
SensingUpdate.featuresstill populated with fused aggregate — existing consumers unchangedSensingUpdate.nodesnow contains ALL active nodes (was single node per message) — existing code readingnodes[0]still worksnode_featuresfield isOptionwithskip_serializing_if— old clients don't receive itframe_historystill maintained alongside per-node historiesTest plan
cargo build -p wifi-densepose-sensing-servercompiles cleancurl /api/v1/nodesreturns per-node data for each connected ESP32featuresfield still populated (backward compat)train_cooking_kitchen.jsonl→ model learns "cooking" class🤖 Generated with Claude Code