feat(sensing-server): per-node CSI separation + dynamic classifier classes#289
Open
taylorjdawson wants to merge 1 commit intoruvnet:mainfrom
Open
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
7 tasks
This was referenced Mar 25, 2026
…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>
0e74711 to
11a413d
Compare
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: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 (variance, motion_band_power) so single-node strong signals aren't dilutedbuild_per_node_features()— sorted per-node feature list for WebSocket broadcastnodes_endpoint()— newGET /api/v1/nodesendpoint returns per-node health, frame rate, features, classificationsaturating_neg()for correct negative dBm valuesSensingUpdate.node_features— optional field, backward compatible viaskip_serializing_ifDefaultimpls 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— XSS safe)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 historiesHow to test
🤖 Generated with Claude Code