pub mod actuator;
pub mod navigation;
pub mod perception;
pub mod sensor;
pub mod system;
pub use actuator::{GripperCommand, JointState, Twist};
pub use navigation::{NavigationGoal, NavigationStatus, Path, Waypoint};
pub use perception::{BoundingBox, Detection, Pose};
pub use sensor::{GpsData, Image, Imu, LaserScan, Odometry};
pub use system::{HealthLevel, HealthStatus, NodeStatus, SystemCommand};
pub trait Message: Send + Sync + 'static {}
pub trait Timestamped {
fn timestamp(&self) -> u64;
fn age_micros(&self) -> u64 {
crate::prelude::now_micros().saturating_sub(self.timestamp())
}
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct RedisMessage<T> {
pub payload: T,
#[serde(default)]
pub publisher: String,
pub timestamp: u64,
}
impl<T> RedisMessage<T> {
pub fn new(payload: T, publisher: impl Into<String>, timestamp: u64) -> Self {
Self {
payload,
publisher: publisher.into(),
timestamp,
}
}
}
impl Message for Image {}
impl Message for LaserScan {}
impl Message for Odometry {}
impl Message for Imu {}
impl Message for GpsData {}
impl Message for Twist {}
impl Message for JointState {}
impl Message for GripperCommand {}
impl Message for Detection {}
impl Message for Pose {}
impl Message for NavigationGoal {}
impl Message for NavigationStatus {}
impl Message for Path {}
impl Message for Waypoint {}
impl Message for HealthStatus {}
impl Message for NodeStatus {}
impl Message for SystemCommand {}
impl Message for bool {}
impl Message for f32 {}
impl Message for f64 {}
impl Message for i32 {}
impl Message for i64 {}
impl Message for u32 {}
impl Message for u64 {}
impl Message for String {}
impl Message for [f32; 2] {}
impl Message for [f32; 3] {}
impl Message for [f32; 6] {}
impl Message for crate::types::Image {}
impl Message for crate::types::LaserScan {}
impl Message for crate::types::IMU {}
impl Message for serde_json::Value {}
impl<T: Send + Sync + 'static> Message for Vec<T> {}
impl<T1: Send + Sync + 'static, T2: Send + Sync + 'static> Message for (T1, T2) {}
#[cfg(feature = "messaging")]
impl<T: Send + Sync + 'static> Message for crate::tracing_otel::TracedMessage<T> {}
impl<T: Send + Sync + 'static> Message for RedisMessage<T> {}
impl Timestamped for Detection {
fn timestamp(&self) -> u64 {
self.timestamp
}
}
impl Timestamped for Image {
fn timestamp(&self) -> u64 {
self.timestamp
}
}
impl Timestamped for LaserScan {
fn timestamp(&self) -> u64 {
self.timestamp
}
}
impl Timestamped for Imu {
fn timestamp(&self) -> u64 {
self.timestamp
}
}
impl Timestamped for GpsData {
fn timestamp(&self) -> u64 {
self.timestamp
}
}
#[cfg(test)]
mod timestamped_tests {
use super::*;
use crate::messages::perception::BoundingBox;
use crate::prelude::now_micros;
#[test]
fn timestamp_returns_the_underlying_field() {
let detection = Detection::new(1, "cup".to_string(), 0.9, BoundingBox::new(0.0, 0.0, 1.0, 1.0));
assert_eq!(detection.timestamp(), detection.timestamp);
}
#[test]
fn age_micros_reflects_elapsed_time_since_timestamp() {
let past = now_micros().saturating_sub(1_000);
let detection = Detection {
timestamp: past,
class_id: 0,
class_name: "test".to_string(),
confidence: 1.0,
bbox: BoundingBox::new(0.0, 0.0, 1.0, 1.0),
track_id: None,
position_3d: None,
};
assert!(detection.age_micros() >= 1_000);
}
#[test]
fn age_micros_saturates_instead_of_underflowing_for_future_timestamps() {
let future = now_micros() + 1_000_000_000;
let detection = Detection {
timestamp: future,
class_id: 0,
class_name: "test".to_string(),
confidence: 1.0,
bbox: BoundingBox::new(0.0, 0.0, 1.0, 1.0),
track_id: None,
position_3d: None,
};
assert_eq!(detection.age_micros(), 0);
}
#[test]
fn all_five_message_types_implement_timestamped() {
fn assert_timestamped<T: Timestamped>(value: &T) -> u64 {
value.age_micros()
}
let image = Image {
timestamp: now_micros(),
width: 1,
height: 1,
encoding: "rgb8".to_string(),
data: vec![0, 0, 0],
frame_id: None,
};
let laser_scan = LaserScan {
timestamp: now_micros(),
angle_min: 0.0,
angle_max: 0.0,
angle_increment: 0.0,
time_increment: 0.0,
scan_time: 0.0,
range_min: 0.0,
range_max: 0.0,
ranges: vec![],
intensities: None,
frame_id: None,
};
let imu = Imu {
timestamp: now_micros(),
orientation: [0.0, 0.0, 0.0, 1.0],
orientation_covariance: None,
angular_velocity: [0.0, 0.0, 0.0],
angular_velocity_covariance: None,
linear_acceleration: [0.0, 0.0, 0.0],
linear_acceleration_covariance: None,
frame_id: None,
};
let gps = GpsData {
timestamp: now_micros(),
latitude: 0.0,
longitude: 0.0,
altitude: 0.0,
horizontal_accuracy: None,
vertical_accuracy: None,
satellites: None,
fix_type: None,
};
assert_timestamped(&image);
assert_timestamped(&laser_scan);
assert_timestamped(&imu);
assert_timestamped(&gps);
}
}