From ae792aad0d1d4020c9f4cea891ad79e8f09fe836 Mon Sep 17 00:00:00 2001 From: ruv Date: Sun, 19 Apr 2026 20:38:17 -0400 Subject: [PATCH] =?UTF-8?q?Add=20brain=20bridge=20=E2=80=94=20sparse=20spa?= =?UTF-8?q?tial=20observation=20sync=20every=2060s?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Stores room scan summaries, motion events, and vital signs in the ruOS brain as memories. Only syncs every 120 frames (~60 seconds) to keep the brain sparse and optimized. Categories: spatial-observation, spatial-motion, spatial-vitals. Co-Authored-By: claude-flow --- .../src/brain_bridge.rs | 90 +++++++++++++++++++ .../wifi-densepose-pointcloud/src/main.rs | 1 + .../wifi-densepose-pointcloud/src/stream.rs | 16 +++- 3 files changed, 106 insertions(+), 1 deletion(-) create mode 100644 rust-port/wifi-densepose-rs/crates/wifi-densepose-pointcloud/src/brain_bridge.rs diff --git a/rust-port/wifi-densepose-rs/crates/wifi-densepose-pointcloud/src/brain_bridge.rs b/rust-port/wifi-densepose-rs/crates/wifi-densepose-pointcloud/src/brain_bridge.rs new file mode 100644 index 00000000..2ca2ba9e --- /dev/null +++ b/rust-port/wifi-densepose-rs/crates/wifi-densepose-pointcloud/src/brain_bridge.rs @@ -0,0 +1,90 @@ +//! Brain bridge — sends spatial observations to the ruOS brain. +//! +//! Periodically summarizes the sensor pipeline state and stores it +//! as brain memories for the agent to reason about. + +use crate::csi_pipeline::PipelineOutput; +use anyhow::Result; + +const BRAIN_URL: &str = "http://127.0.0.1:9876"; + +/// Store a spatial observation in the brain. +async fn store_memory(category: &str, content: &str) -> Result<()> { + let client = reqwest::Client::builder() + .timeout(std::time::Duration::from_secs(5)) + .build()?; + + let body = serde_json::json!({ + "category": category, + "content": content, + }); + + client.post(format!("{BRAIN_URL}/memories")) + .json(&body) + .send() + .await?; + Ok(()) +} + +/// Summarize pipeline state and store in brain (called every 60 seconds). +pub async fn sync_to_brain(pipeline: &PipelineOutput, camera_frames: u64) { + // Only store if there's meaningful data + if pipeline.total_frames < 10 && camera_frames < 5 { return; } + + // Store spatial summary + let motion_str = if pipeline.motion_detected { "detected" } else { "absent" }; + let skeleton_str = if let Some(ref sk) = pipeline.skeleton { + format!("{} keypoints ({:.0}% conf)", sk.keypoints.len(), sk.confidence * 100.0) + } else { + "inactive".to_string() + }; + + let summary = format!( + "Room scan: {} camera frames, {} CSI frames from {} nodes. \ + Motion {} ({:.0}%). Breathing {:.0} BPM. Skeleton: {}. \ + Occupancy grid {}x{}x{} with {} occupied voxels.", + camera_frames, + pipeline.total_frames, + pipeline.num_nodes, + motion_str, + pipeline.vitals.motion_score * 100.0, + pipeline.vitals.breathing_rate, + skeleton_str, + pipeline.occupancy_dims.0, + pipeline.occupancy_dims.1, + pipeline.occupancy_dims.2, + pipeline.occupancy.iter().filter(|&&d| d > 0.3).count(), + ); + + let _ = store_memory("spatial-observation", &summary).await; + + // Store motion events + if pipeline.motion_detected && pipeline.vitals.motion_score > 0.3 { + let _ = store_memory("spatial-motion", + &format!("Strong motion detected: {:.0}% score, {} CSI frames", + pipeline.vitals.motion_score * 100.0, pipeline.total_frames) + ).await; + } + + // Store vital signs if available + if pipeline.vitals.breathing_rate > 5.0 && pipeline.vitals.breathing_rate < 35.0 { + let _ = store_memory("spatial-vitals", + &format!("Vital signs: breathing {:.0} BPM, motion {:.0}%", + pipeline.vitals.breathing_rate, pipeline.vitals.motion_score * 100.0) + ).await; + } +} + +/// Check if brain is reachable. +pub async fn brain_available() -> bool { + reqwest::Client::builder() + .timeout(std::time::Duration::from_secs(2)) + .build() + .ok() + .and_then(|c| { + tokio::runtime::Handle::current().block_on(async { + c.get(format!("{BRAIN_URL}/health")).send().await.ok() + }) + }) + .is_some() +} diff --git a/rust-port/wifi-densepose-rs/crates/wifi-densepose-pointcloud/src/main.rs b/rust-port/wifi-densepose-rs/crates/wifi-densepose-pointcloud/src/main.rs index 10b4029c..62534d6a 100644 --- a/rust-port/wifi-densepose-rs/crates/wifi-densepose-pointcloud/src/main.rs +++ b/rust-port/wifi-densepose-rs/crates/wifi-densepose-pointcloud/src/main.rs @@ -10,6 +10,7 @@ //! ruview-pointcloud train # calibration training //! ruview-pointcloud csi-test # send test CSI frames +mod brain_bridge; mod camera; mod csi; mod csi_pipeline; diff --git a/rust-port/wifi-densepose-rs/crates/wifi-densepose-pointcloud/src/stream.rs b/rust-port/wifi-densepose-rs/crates/wifi-densepose-pointcloud/src/stream.rs index 68e0f4b6..c11f13d9 100644 --- a/rust-port/wifi-densepose-rs/crates/wifi-densepose-pointcloud/src/stream.rs +++ b/rust-port/wifi-densepose-rs/crates/wifi-densepose-pointcloud/src/stream.rs @@ -1,5 +1,6 @@ //! HTTP server — live camera + ESP32 CSI + fusion → real-time point cloud. +use crate::brain_bridge; use crate::camera; use crate::csi_pipeline; use crate::depth; @@ -59,7 +60,9 @@ pub async fn serve(host: &str, port: u16, _wifi_source: Option<&str>) -> anyhow: let frame_num = *bg.frame_count.lock().unwrap(); skip_depth = !out.motion_detected && frame_num % 5 != 0; } + let pipeline_clone = pipeline_out.clone(); *bg.latest_pipeline.lock().unwrap() = pipeline_out; + let pipeline_out = pipeline_clone; let interval = if skip_depth { 1000 } else { 500 }; // slower when no motion tokio::time::sleep(std::time::Duration::from_millis(interval)).await; @@ -74,7 +77,18 @@ pub async fn serve(host: &str, port: u16, _wifi_source: Option<&str>) -> anyhow: let splats = pointcloud::to_gaussian_splats(&cloud); *bg.latest_cloud.lock().unwrap() = cloud; *bg.latest_splats.lock().unwrap() = splats; - *bg.frame_count.lock().unwrap() += 1; + let frame_num = { + let mut fc = bg.frame_count.lock().unwrap(); + *fc += 1; + *fc + }; + + // Brain sync — sparse, every 120 frames (~60 seconds) + if frame_num % 120 == 0 { + if let Some(ref out) = pipeline_out { + brain_bridge::sync_to_brain(out, frame_num).await; + } + } } });