Skip to content

Commit ae792aa

Browse files
committed
Add brain bridge — sparse spatial observation sync every 60s
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 <ruv@ruv.net>
1 parent 898d90f commit ae792aa

File tree

3 files changed

+106
-1
lines changed

3 files changed

+106
-1
lines changed
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
//! Brain bridge — sends spatial observations to the ruOS brain.
2+
//!
3+
//! Periodically summarizes the sensor pipeline state and stores it
4+
//! as brain memories for the agent to reason about.
5+
6+
use crate::csi_pipeline::PipelineOutput;
7+
use anyhow::Result;
8+
9+
const BRAIN_URL: &str = "http://127.0.0.1:9876";
10+
11+
/// Store a spatial observation in the brain.
12+
async fn store_memory(category: &str, content: &str) -> Result<()> {
13+
let client = reqwest::Client::builder()
14+
.timeout(std::time::Duration::from_secs(5))
15+
.build()?;
16+
17+
let body = serde_json::json!({
18+
"category": category,
19+
"content": content,
20+
});
21+
22+
client.post(format!("{BRAIN_URL}/memories"))
23+
.json(&body)
24+
.send()
25+
.await?;
26+
Ok(())
27+
}
28+
29+
/// Summarize pipeline state and store in brain (called every 60 seconds).
30+
pub async fn sync_to_brain(pipeline: &PipelineOutput, camera_frames: u64) {
31+
// Only store if there's meaningful data
32+
if pipeline.total_frames < 10 && camera_frames < 5 { return; }
33+
34+
// Store spatial summary
35+
let motion_str = if pipeline.motion_detected { "detected" } else { "absent" };
36+
let skeleton_str = if let Some(ref sk) = pipeline.skeleton {
37+
format!("{} keypoints ({:.0}% conf)", sk.keypoints.len(), sk.confidence * 100.0)
38+
} else {
39+
"inactive".to_string()
40+
};
41+
42+
let summary = format!(
43+
"Room scan: {} camera frames, {} CSI frames from {} nodes. \
44+
Motion {} ({:.0}%). Breathing {:.0} BPM. Skeleton: {}. \
45+
Occupancy grid {}x{}x{} with {} occupied voxels.",
46+
camera_frames,
47+
pipeline.total_frames,
48+
pipeline.num_nodes,
49+
motion_str,
50+
pipeline.vitals.motion_score * 100.0,
51+
pipeline.vitals.breathing_rate,
52+
skeleton_str,
53+
pipeline.occupancy_dims.0,
54+
pipeline.occupancy_dims.1,
55+
pipeline.occupancy_dims.2,
56+
pipeline.occupancy.iter().filter(|&&d| d > 0.3).count(),
57+
);
58+
59+
let _ = store_memory("spatial-observation", &summary).await;
60+
61+
// Store motion events
62+
if pipeline.motion_detected && pipeline.vitals.motion_score > 0.3 {
63+
let _ = store_memory("spatial-motion",
64+
&format!("Strong motion detected: {:.0}% score, {} CSI frames",
65+
pipeline.vitals.motion_score * 100.0, pipeline.total_frames)
66+
).await;
67+
}
68+
69+
// Store vital signs if available
70+
if pipeline.vitals.breathing_rate > 5.0 && pipeline.vitals.breathing_rate < 35.0 {
71+
let _ = store_memory("spatial-vitals",
72+
&format!("Vital signs: breathing {:.0} BPM, motion {:.0}%",
73+
pipeline.vitals.breathing_rate, pipeline.vitals.motion_score * 100.0)
74+
).await;
75+
}
76+
}
77+
78+
/// Check if brain is reachable.
79+
pub async fn brain_available() -> bool {
80+
reqwest::Client::builder()
81+
.timeout(std::time::Duration::from_secs(2))
82+
.build()
83+
.ok()
84+
.and_then(|c| {
85+
tokio::runtime::Handle::current().block_on(async {
86+
c.get(format!("{BRAIN_URL}/health")).send().await.ok()
87+
})
88+
})
89+
.is_some()
90+
}

rust-port/wifi-densepose-rs/crates/wifi-densepose-pointcloud/src/main.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
//! ruview-pointcloud train # calibration training
1111
//! ruview-pointcloud csi-test # send test CSI frames
1212
13+
mod brain_bridge;
1314
mod camera;
1415
mod csi;
1516
mod csi_pipeline;

rust-port/wifi-densepose-rs/crates/wifi-densepose-pointcloud/src/stream.rs

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
//! HTTP server — live camera + ESP32 CSI + fusion → real-time point cloud.
22
3+
use crate::brain_bridge;
34
use crate::camera;
45
use crate::csi_pipeline;
56
use crate::depth;
@@ -59,7 +60,9 @@ pub async fn serve(host: &str, port: u16, _wifi_source: Option<&str>) -> anyhow:
5960
let frame_num = *bg.frame_count.lock().unwrap();
6061
skip_depth = !out.motion_detected && frame_num % 5 != 0;
6162
}
63+
let pipeline_clone = pipeline_out.clone();
6264
*bg.latest_pipeline.lock().unwrap() = pipeline_out;
65+
let pipeline_out = pipeline_clone;
6366

6467
let interval = if skip_depth { 1000 } else { 500 }; // slower when no motion
6568
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:
7477
let splats = pointcloud::to_gaussian_splats(&cloud);
7578
*bg.latest_cloud.lock().unwrap() = cloud;
7679
*bg.latest_splats.lock().unwrap() = splats;
77-
*bg.frame_count.lock().unwrap() += 1;
80+
let frame_num = {
81+
let mut fc = bg.frame_count.lock().unwrap();
82+
*fc += 1;
83+
*fc
84+
};
85+
86+
// Brain sync — sparse, every 120 frames (~60 seconds)
87+
if frame_num % 120 == 0 {
88+
if let Some(ref out) = pipeline_out {
89+
brain_bridge::sync_to_brain(out, frame_num).await;
90+
}
91+
}
7892
}
7993
});
8094

0 commit comments

Comments
 (0)