Skip to content

Conversation

RaulTrombin
Copy link
Member

@RaulTrombin RaulTrombin commented Jul 8, 2025

Add Vehicle module to handle relevant data and share across other services.
The technical approach uses a RWLock that updates accordingly with the latest received values.

The Zenoh connection changes according to the feature flag "blueos-extension", which defines local/remote settings.

A channel containing the structure VehicleData (with JSON schema autogenerated already) will store vehicle data alongside Sonars. This closes a requirement also desired by integrators such as ThinkSmart, that have their own GPS data source.

VehicleData {
    roll: att.roll,
    pitch: att.pitch,
    yaw: att.yaw,
    alt: pos.alt as f64 / 1000.0,
    lat: pos.lat as f64 / 1e7,
    lon: pos.lon as f64 / 1e7,
};

The channel log was changed to use log_with_time, ensuring both channels will have the same timestamp.

image

trace!("Handling RecordingManager request: Success");
Ok(ans)
pub async fn zenoh_pose_bridge(latest_pose: Arc<RwLock<Option<VehicleData>>>) {
let session = match zenoh::open(zenoh::Config::default()).await {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's missing the service configuration as declared in mavlink-camera-manager and mavlink-server, for more information about it check the zBlueberry document that I sent to you.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

added new config following our requirements,
please check the load/save/generate steps and let me know you have some suggestions.

Comment on lines 405 to 406
};
let sub = match session.declare_subscriber("mavlink/1/1/AHRS2").await {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It should subscribe to mavlink/**/1/AHRS2 to work with any system id

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

changed to use :
mavlink//1/ATTITUDE and mavlink//1/GLOBAL_POSITION_INT
where both are used to generate:

        let pose = VehicleData {
            roll: att.roll,
            pitch: att.pitch,
            yaw: att.yaw,
            alt: pos.alt as f64 / 1000.0,
            lat: pos.lat as f64 / 1e7,
            lon: pos.lon as f64 / 1e7,
        };

@RaulTrombin RaulTrombin force-pushed the add_zenoh_client branch 4 times, most recently from 76c1d86 to 5e76d00 Compare July 15, 2025 21:18
@RaulTrombin RaulTrombin changed the title Add zenoh client Add vehicle module Jul 15, 2025
@RaulTrombin RaulTrombin marked this pull request as ready for review July 16, 2025 16:08
.insert_json5("mode", r#""client""#)
.expect("Failed to insert client mode");
config
.insert_json5("metadata", &format!(r#"{{"name": "{}"}}"#, node_name))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
.insert_json5("metadata", &format!(r#"{{"name": "{}"}}"#, node_name))
.insert_json5("metadata", &format!(r#"{{"name": "{node_name}"}}"#))

Comment on lines 49 to 64
config
.insert_json5("scouting/timeout", r#"3000"#)
.expect("Failed to insert scouting/timeout");
config
.insert_json5("scouting/multicast/enabled", r#"true"#)
.expect("Failed to insert scouting/multicast/enabled");
config
.insert_json5("scouting/multicast/ttl", r#"1"#)
.expect("Failed to insert scouting/multicast/ttl");
config
.insert_json5("scouting/gossip/enabled", r#"true"#)
.expect("Failed to insert scouting/gossip/enabled");
config
.insert_json5("scouting/gossip/multihop", r#"false"#)
.expect("Failed to insert scouting/gossip/multihop");
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this configuration is not necessary for an extension, the ttl and and multihop configuration is only necessary for peripherals to not leak data inside the vehicle. The only thing necessary is to change the connect/endpoints

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we still need to deal with it for standalone app.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

there is no need to configure it as well for a standalone app, the connection ip is the only thing necessary

Comment on lines 69 to 83
fn save_config(config: &zenoh::Config, config_path: &str) {
match serde_json::to_string_pretty(config) {
Ok(json_str) => {
if let Err(e) = std::fs::write(config_path, json_str) {
error!("Failed to write zenoh_config.json: {e}");
} else {
info!("zenoh_config.json saved successfully");
}
}
Err(e) => {
error!("Failed to serialize config with serde_json: {e}");
debug!("Config debug: {:#?}", config);
}
}
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Saving the configuration on loading the configuration from a file is problematic, this will save the zenoh id that should be unique per application while being unnecessary at all.
If the goal is to load custom zenoh configuration you should do as the following pseudo-python script

zenoh_config = zenoh.Config()
zenoh_config.insert_json5("mode", json.dumps("client"))
zenoh_config.insert_json5("connect/endpoints", json.dumps(["tcp/127.0.0.1:7447"]))
zenoh_config.insert_json5("adminspace", json.dumps({"enabled": True}))
zenoh_config.insert_json5("metadata", json.dumps({"name": node_name}))

if input_file:
    zenoh_config.from_json5(json5.dumps(input_file))

session = zenoh.open(zenoh_config)

loop {
tokio::select! {
Ok(sample) = attitude_sub.recv_async() => {
if let Ok(env) = serde_json::from_slice::<Envelope<ATTITUDE_DATA>>(&sample.payload().to_bytes()) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

need to use serde_json5 over serde_json

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

added

roll: att.roll,
pitch: att.pitch,
yaw: att.yaw,
alt: pos.alt as f64 / 1000.0,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

would be nice to have the units here in docstring

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
pub struct VehicleData {
#[schemars(description = "Roll angle in radians")]
pub roll: f32,
#[schemars(description = "Pitch angle in radians")]
pub pitch: f32,
#[schemars(description = "Yaw angle in radians")]
pub yaw: f32,
#[schemars(description = "Altitude in meters above sea level")]
pub alt: f64,
#[schemars(description = "Latitude in decimal degrees")]
pub lat: f64,
#[schemars(description = "Longitude in decimal degrees")]
pub lon: f64,
}

let vehicle_data = Arc::new(RwLock::new(None));

// Start the Zenoh-client with shared data
tokio::spawn(zenoh_client_bridge(vehicle_data.clone()));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this should be reconnected if it fails to connect

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please check
012ee15

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

// Define topic strings
let ping1d_topic = format!("/device_{}/Ping1D", device_id);
let ping360_topic = format!("/device_{}/Ping360", device_id);
let vehicle_topic = format!("/device_{}/VehicleData", device_id);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just to inform you, topics don't have trailing slashes in zenoh, it would be better to remove them.

Ok(s) => s,
Err(e) => {
error!("Zenoh session error: {e}, retrying in {reconnect_delay_secs}s");
sleep(reconnect_delay).await;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add the delay in the beginning of the loop to avoid calling it in every continue call

Comment on lines 60 to 57
let reconnect_delay_secs = 30;
let reconnect_delay = Duration::from_secs(reconnect_delay_secs);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

30s is a lot. We should make it 5 or less.

lon: pos.lon as f64 / 1e7,
};
let mut pose_guard = latest_pose.write().await;
*pose_guard = Some(pose.clone());
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this clone is unnecessary

config
.insert_json5("connect/endpoints", r#"["tcp/127.0.0.1:7447"]"#)
.expect("Failed to insert endpoints");
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should have it by default, if there is a configuration in the loaded settings it'll replace it.

@RaulTrombin RaulTrombin merged commit 7b9886c into bluerobotics:master Jul 17, 2025
14 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants