55//!
66//! Based on: https://github.com/eclipse-zenoh/zenoh/blob/main/examples/examples/z_sub_shm.rs
77
8+ use argh:: FromArgs ;
89use bubbaloop_schemas:: RawImage ;
910use prost:: Message ;
11+ use serde:: Deserialize ;
12+ use std:: path:: { Path , PathBuf } ;
1013use zenoh:: Wait ;
1114
15+ /// Inference node configuration
16+ #[ derive( Debug , Clone , Deserialize ) ]
17+ struct Config {
18+ /// Topic pattern to subscribe to
19+ subscribe_topic : String ,
20+ }
21+
22+ impl Default for Config {
23+ fn default ( ) -> Self {
24+ Self {
25+ subscribe_topic : "camera/*/raw_shm" . to_string ( ) ,
26+ }
27+ }
28+ }
29+
30+ /// Inference node for camera stream processing (SHM subscriber)
31+ #[ derive( FromArgs ) ]
32+ struct Args {
33+ /// path to configuration file
34+ #[ argh( option, short = 'c' , default = "default_config_path()" ) ]
35+ config : PathBuf ,
36+
37+ /// zenoh endpoint to connect to
38+ #[ argh( option, short = 'e' , default = "default_endpoint()" ) ]
39+ endpoint : String ,
40+ }
41+
42+ fn default_config_path ( ) -> PathBuf {
43+ PathBuf :: from ( "config.yaml" )
44+ }
45+
46+ fn default_endpoint ( ) -> String {
47+ String :: from ( "tcp/127.0.0.1:7447" )
48+ }
49+
50+ fn load_config ( path : & Path ) -> Config {
51+ if path. exists ( ) {
52+ match std:: fs:: read_to_string ( path) {
53+ Ok ( content) => match serde_yaml:: from_str ( & content) {
54+ Ok ( config) => return config,
55+ Err ( e) => log:: warn!( "Failed to parse config file: {}, using defaults" , e) ,
56+ } ,
57+ Err ( e) => log:: warn!( "Failed to read config file: {}, using defaults" , e) ,
58+ }
59+ } else {
60+ log:: warn!( "Config file not found: {:?}, using defaults" , path) ;
61+ }
62+ Config :: default ( )
63+ }
64+
1265/// Compute mean and standard deviation of pixel values
1366fn compute_image_stats ( data : & [ u8 ] ) -> ( f64 , f64 ) {
1467 if data. is_empty ( ) {
@@ -30,8 +83,22 @@ async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
3083 zenoh:: init_log_from_env_or ( "error" ) ;
3184 env_logger:: init ( ) ;
3285
86+ let args: Args = argh:: from_env ( ) ;
87+
3388 log:: info!( "Starting inference node (SHM subscriber)..." ) ;
3489
90+ // Load and validate config
91+ let config = load_config ( & args. config ) ;
92+
93+ let topic_re = regex_lite:: Regex :: new ( r"^[a-zA-Z0-9/_\-\.\*]+$" ) . unwrap ( ) ;
94+ if !topic_re. is_match ( & config. subscribe_topic ) {
95+ log:: error!(
96+ "subscribe_topic '{}' contains invalid characters" ,
97+ config. subscribe_topic
98+ ) ;
99+ std:: process:: exit ( 1 ) ;
100+ }
101+
35102 // Read scope/machine env vars for health heartbeat
36103 let scope = std:: env:: var ( "BUBBALOOP_SCOPE" ) . unwrap_or_else ( |_| "local" . to_string ( ) ) ;
37104 let machine_id = std:: env:: var ( "BUBBALOOP_MACHINE_ID" ) . unwrap_or_else ( |_| {
@@ -54,26 +121,30 @@ async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
54121 }
55122
56123 // Create Zenoh session with SHM enabled
57- let mut config = zenoh:: Config :: default ( ) ;
58- config. insert_json5 ( "transport/shared_memory/enabled" , "true" ) ?;
59- if let Ok ( endpoint) = std:: env:: var ( "ZENOH_ENDPOINT" ) {
60- config. insert_json5 ( "connect/endpoints" , & format ! ( r#"["{}"]"# , endpoint) ) ?;
61- }
124+ let endpoint = std:: env:: var ( "ZENOH_ENDPOINT" ) . unwrap_or ( args. endpoint ) ;
125+ log:: info!( "Connecting to Zenoh at: {}" , endpoint) ;
126+
127+ let mut zenoh_config = zenoh:: Config :: default ( ) ;
128+ zenoh_config. insert_json5 ( "transport/shared_memory/enabled" , "true" ) ?;
129+ zenoh_config. insert_json5 ( "connect/endpoints" , & format ! ( r#"["{}"]"# , endpoint) ) ?;
62130
63- let session = zenoh:: open ( config ) . wait ( ) ?;
131+ let session = zenoh:: open ( zenoh_config ) . wait ( ) ?;
64132
65133 // Create health heartbeat publisher
66134 let health_topic = format ! ( "bubbaloop/{}/{}/health/inference" , scope, machine_id) ;
67135 let health_publisher = session. declare_publisher ( health_topic. clone ( ) ) . await ?;
68136 log:: info!( "Health heartbeat topic: {}" , health_topic) ;
69137
70- // Subscribe to all camera raw_shm topics using wildcard
71- let topic = "camera/*/raw_shm" ;
72- let subscriber = session. declare_subscriber ( topic) . wait ( ) ?;
138+ // Subscribe using scoped topic
139+ let full_topic = format ! (
140+ "bubbaloop/{}/{}/{}" ,
141+ scope, machine_id, config. subscribe_topic
142+ ) ;
143+ let subscriber = session. declare_subscriber ( & full_topic) . wait ( ) ?;
73144
74145 log:: info!(
75146 "Inference node subscribed to '{}', waiting for SHM images..." ,
76- topic
147+ full_topic
77148 ) ;
78149
79150 let mut frame_count = 0u64 ;
0 commit comments