pub mod messages;
pub use transforms::errors::TransformError;
pub use transforms::geometry::{Quaternion, Vector3};
pub use transforms::time::{Stamp, TimeError, TimePoint, Timestamp};
pub use transforms::{Registry, Transform};
use std::marker::PhantomData;
use std::sync::Arc;
use std::time::Duration;
use roslibrust_common::{Publish, RosMessageType, Subscribe, TopicProvider};
use tokio::sync::{watch, RwLock};
use tokio_util::sync::CancellationToken;
#[derive(thiserror::Error, Debug)]
pub enum TransformManagerError {
#[error("Transform lookup failed: {0}")]
LookupError(String),
#[error("Transform rejected by the registry: {0}")]
RejectedTransform(String),
#[error("ROS communication error: {0}")]
RosError(#[from] roslibrust_common::Error),
#[error("Timeout waiting for transform from '{0}' to '{1}' at time '{2}'")]
Timeout(String, String, String),
}
pub trait RosTimestamp: TimePoint {
fn from_ros_time(sec: i32, nsec: u32) -> Self;
fn as_ros_time(self) -> (i32, u32);
}
impl RosTimestamp for Timestamp {
fn from_ros_time(sec: i32, nsec: u32) -> Self {
match u64::try_from(sec) {
Ok(sec) => Timestamp::from_nanos(sec * 1_000_000_000 + (nsec as u64)),
Err(_) => Timestamp::zero(),
}
}
fn as_ros_time(self) -> (i32, u32) {
let nanos = self.as_nanos();
let secs = nanos / 1_000_000_000;
let nsecs = nanos % 1_000_000_000;
(secs as i32, nsecs as u32)
}
}
impl RosTimestamp for std::time::SystemTime {
fn from_ros_time(sec: i32, nsec: u32) -> Self {
use std::time::UNIX_EPOCH;
if sec >= 0 {
UNIX_EPOCH + Duration::new(sec as u64, nsec)
} else {
UNIX_EPOCH
.checked_sub(Duration::new((-sec) as u64, nsec))
.unwrap_or(UNIX_EPOCH)
}
}
fn as_ros_time(self) -> (i32, u32) {
use std::time::UNIX_EPOCH;
let duration = self.duration_since(UNIX_EPOCH).unwrap_or_default();
(duration.as_secs() as i32, duration.subsec_nanos())
}
}
pub trait IntoTransform<T = Timestamp>
where
T: TimePoint,
{
fn into_transform(self, is_static: bool) -> Result<transforms::Transform<T>, TransformError>;
}
pub trait FromTransform<T = Timestamp>: Sized
where
T: TimePoint,
{
fn from_transform(transform: &transforms::Transform<T>) -> Self;
}
pub trait TFMessageType<T = Timestamp>: RosMessageType + Send + Clone + 'static
where
T: TimePoint,
{
type TransformStamped: IntoTransform<T> + FromTransform<T> + Clone;
fn transforms(self) -> Vec<Self::TransformStamped>;
fn from_transforms(transforms: Vec<Self::TransformStamped>) -> Self;
}
pub struct TransformManager<M, P, T = Timestamp>
where
M: TFMessageType<T>,
P: Publish<M> + Send + Sync,
T: TimePoint,
{
registry: Arc<RwLock<Registry<T>>>,
buffer_duration: Duration,
transform_notify: watch::Sender<()>,
cancel_token: CancellationToken,
tf_publisher: P,
tf_static_publisher: P,
_phantom: PhantomData<M>,
}
impl<M, P, T> TransformManager<M, P, T>
where
M: TFMessageType<T>,
P: Publish<M> + Send + Sync,
T: TimePoint + Send + Sync + 'static,
{
pub async fn new<R>(
ros: &R,
buffer_duration: Duration,
) -> Result<TransformManager<M, R::Publisher<M>, T>, TransformManagerError>
where
R: TopicProvider<Publisher<M> = P> + Clone + Send + Sync + 'static,
R::Subscriber<M>: Send + 'static,
R::Publisher<M>: Send + Sync,
{
let registry = Arc::new(RwLock::new(Registry::<T>::with_max_age(buffer_duration)));
let (transform_notify, _) = watch::channel(());
let cancel_token = CancellationToken::new();
let tf_subscriber = ros.subscribe::<M>("/tf").await?;
let tf_static_subscriber = ros.subscribe::<M>("/tf_static").await?;
let tf_publisher = ros.advertise::<M>("/tf").await?;
let tf_static_publisher = ros.advertise::<M>("/tf_static").await?;
let registry_clone = registry.clone();
let notify_clone = transform_notify.clone();
let cancel_clone = cancel_token.clone();
tokio::spawn(async move {
Self::process_tf_messages(
tf_subscriber,
registry_clone,
notify_clone,
cancel_clone,
false,
)
.await;
});
let registry_clone = registry.clone();
let notify_clone = transform_notify.clone();
let cancel_clone = cancel_token.clone();
tokio::spawn(async move {
Self::process_tf_messages(
tf_static_subscriber,
registry_clone,
notify_clone,
cancel_clone,
true,
)
.await;
});
Ok(TransformManager {
registry,
buffer_duration,
transform_notify,
cancel_token,
tf_publisher,
tf_static_publisher,
_phantom: PhantomData,
})
}
async fn process_tf_messages<S: Subscribe<M>>(
mut subscriber: S,
registry: Arc<RwLock<Registry<T>>>,
notify: watch::Sender<()>,
cancel_token: CancellationToken,
is_static: bool,
) {
let topic = if is_static { "/tf_static" } else { "/tf" };
loop {
tokio::select! {
_ = cancel_token.cancelled() => {
log::debug!("Shutting down {topic} listener task");
break;
}
result = subscriber.next() => {
match result {
Ok(msg) => {
let mut reg = registry.write().await;
for tf in <M as TFMessageType<T>>::transforms(msg) {
let transform =
match <M::TransformStamped as IntoTransform<T>>::into_transform(
tf, is_static,
) {
Ok(transform) => transform,
Err(e) => {
log::warn!(
"Dropping invalid transform received on {topic}: {e}"
);
continue;
}
};
let parent = transform.parent().to_owned();
let child = transform.child().to_owned();
if let Err(e) = reg.add_transform(transform) {
log::warn!(
"Dropping transform from '{parent}' to '{child}' rejected by the registry on {topic}: {e}"
);
}
}
let _ = notify.send(());
}
Err(e) => {
log::warn!("Error receiving {topic} message: {e}");
}
}
}
}
}
}
pub async fn get_transform(
&self,
target_frame: &str,
source_frame: &str,
time: T,
) -> Result<transforms::Transform<T>, TransformManagerError> {
let registry = self.registry.read().await;
registry
.get_transform(target_frame, source_frame, time)
.map_err(|e| TransformManagerError::LookupError(e.to_string()))
}
fn pretty_print_timestamp(time: T) -> String {
format!("{:.3}s", time.as_seconds_lossy())
}
pub async fn wait_for_transform(
&self,
target_frame: &str,
source_frame: &str,
time: T,
timeout: Option<Duration>,
) -> Result<transforms::Transform<T>, TransformManagerError> {
let timeout_duration = timeout.unwrap_or(self.buffer_duration);
let deadline = tokio::time::Instant::now() + timeout_duration;
let mut receiver = self.transform_notify.subscribe();
loop {
{
let registry = self.registry.read().await;
if let Ok(transform) = registry.get_transform(target_frame, source_frame, time) {
return Ok(transform);
}
}
let remaining = deadline.saturating_duration_since(tokio::time::Instant::now());
if remaining.is_zero() {
return Err(TransformManagerError::Timeout(
target_frame.to_string(),
source_frame.to_string(),
Self::pretty_print_timestamp(time),
));
}
tokio::select! {
_ = tokio::time::sleep(remaining) => {
let registry = self.registry.read().await;
if let Ok(transform) = registry.get_transform(target_frame, source_frame, time) {
return Ok(transform);
}
return Err(TransformManagerError::Timeout(
target_frame.to_string(),
source_frame.to_string(),
Self::pretty_print_timestamp(time),
));
}
result = receiver.changed() => {
if result.is_err() {
return Err(TransformManagerError::Timeout(
target_frame.to_string(),
source_frame.to_string(),
Self::pretty_print_timestamp(time),
));
}
}
}
}
}
pub async fn add_transform(
&self,
transform: transforms::Transform<T>,
) -> Result<(), TransformManagerError> {
let transform_stamped =
<M::TransformStamped as FromTransform<T>>::from_transform(&transform);
let msg = <M as TFMessageType<T>>::from_transforms(vec![transform_stamped]);
let is_static = transform.timestamp().is_static();
{
let mut registry = self.registry.write().await;
registry
.add_transform(transform)
.map_err(|e| TransformManagerError::RejectedTransform(e.to_string()))?;
}
let _ = self.transform_notify.send(());
if is_static {
self.tf_static_publisher.publish(&msg).await?;
} else {
self.tf_publisher.publish(&msg).await?;
}
Ok(())
}
pub async fn get_transform_at(
&self,
target_frame: &str,
target_time: T,
source_frame: &str,
source_time: T,
fixed_frame: &str,
) -> Result<transforms::Transform<T>, TransformManagerError> {
let registry = self.registry.read().await;
registry
.get_transform_at(
target_frame,
target_time,
source_frame,
source_time,
fixed_frame,
)
.map_err(|e| TransformManagerError::LookupError(e.to_string()))
}
pub async fn latest_common_time(
&self,
target_frame: &str,
source_frame: &str,
) -> Result<Stamp<T>, TransformManagerError> {
let registry = self.registry.read().await;
registry
.latest_common_time(target_frame, source_frame)
.map_err(|e| TransformManagerError::LookupError(e.to_string()))
}
pub async fn remove_frame(&self, frame: &str) -> bool {
let mut registry = self.registry.write().await;
registry.remove_frame(frame)
}
}
impl<M, P, T> Drop for TransformManager<M, P, T>
where
M: TFMessageType<T>,
P: Publish<M> + Send + Sync,
T: TimePoint,
{
fn drop(&mut self) {
self.cancel_token.cancel();
}
}
pub type Ros1TFMessage = crate::messages::ros1::TFMessage;
pub type Ros1TransformStamped = crate::messages::ros1::geometry_msgs::TransformStamped;
impl<T> TFMessageType<T> for Ros1TFMessage
where
T: RosTimestamp,
{
type TransformStamped = Ros1TransformStamped;
fn transforms(self) -> Vec<Self::TransformStamped> {
self.transforms
}
fn from_transforms(transforms: Vec<Self::TransformStamped>) -> Self {
Ros1TFMessage { transforms }
}
}
impl<T> IntoTransform<T> for Ros1TransformStamped
where
T: RosTimestamp,
{
fn into_transform(self, is_static: bool) -> Result<transforms::Transform<T>, TransformError> {
let timestamp = if is_static {
Stamp::Static
} else {
Stamp::At(T::from_ros_time(
self.header.stamp.secs,
self.header.stamp.nsecs as u32,
))
};
transforms::Transform::new(
&self.header.frame_id,
&self.child_frame_id,
Vector3::new(
self.transform.translation.x,
self.transform.translation.y,
self.transform.translation.z,
),
Quaternion {
w: self.transform.rotation.w,
x: self.transform.rotation.x,
y: self.transform.rotation.y,
z: self.transform.rotation.z,
},
timestamp,
)
}
}
impl<T> FromTransform<T> for Ros1TransformStamped
where
T: RosTimestamp,
{
fn from_transform(transform: &transforms::Transform<T>) -> Self {
use crate::messages::ros1::{geometry_msgs, std_msgs};
let (secs, nsecs) = match transform.timestamp() {
Stamp::At(time) => time.as_ros_time(),
Stamp::Static => (0, 0),
};
if nsecs > i32::MAX as u32 {
panic!("Timestamp overflow when converting to Ros1TransformStamped");
}
let nsecs = nsecs as i32;
let translation = transform.translation();
let rotation = transform.rotation();
Ros1TransformStamped {
header: std_msgs::Header {
seq: 0,
stamp: roslibrust::codegen::integral_types::Time { secs, nsecs },
frame_id: transform.parent().to_string(),
},
child_frame_id: transform.child().to_string(),
transform: geometry_msgs::Transform {
translation: geometry_msgs::Vector3 {
x: translation.x,
y: translation.y,
z: translation.z,
},
rotation: geometry_msgs::Quaternion {
x: rotation.x,
y: rotation.y,
z: rotation.z,
w: rotation.w,
},
},
}
}
}
pub type Ros2TFMessage = crate::messages::ros2::TFMessage;
pub type Ros2TransformStamped = crate::messages::ros2::geometry_msgs::TransformStamped;
impl<T> TFMessageType<T> for Ros2TFMessage
where
T: RosTimestamp,
{
type TransformStamped = Ros2TransformStamped;
fn transforms(self) -> Vec<Self::TransformStamped> {
self.transforms
}
fn from_transforms(transforms: Vec<Self::TransformStamped>) -> Self {
Ros2TFMessage { transforms }
}
}
impl<T> IntoTransform<T> for Ros2TransformStamped
where
T: RosTimestamp,
{
fn into_transform(self, is_static: bool) -> Result<transforms::Transform<T>, TransformError> {
let timestamp = if is_static {
Stamp::Static
} else {
Stamp::At(T::from_ros_time(
self.header.stamp.sec,
self.header.stamp.nanosec,
))
};
transforms::Transform::new(
&self.header.frame_id,
&self.child_frame_id,
Vector3::new(
self.transform.translation.x,
self.transform.translation.y,
self.transform.translation.z,
),
Quaternion {
w: self.transform.rotation.w,
x: self.transform.rotation.x,
y: self.transform.rotation.y,
z: self.transform.rotation.z,
},
timestamp,
)
}
}
impl<T> FromTransform<T> for Ros2TransformStamped
where
T: RosTimestamp,
{
fn from_transform(transform: &transforms::Transform<T>) -> Self {
use crate::messages::ros2::{builtin_interfaces, geometry_msgs, std_msgs};
let (sec, nanosec) = match transform.timestamp() {
Stamp::At(time) => time.as_ros_time(),
Stamp::Static => (0, 0),
};
let translation = transform.translation();
let rotation = transform.rotation();
Ros2TransformStamped {
header: std_msgs::Header {
stamp: builtin_interfaces::Time { sec, nanosec },
frame_id: transform.parent().to_string(),
},
child_frame_id: transform.child().to_string(),
transform: geometry_msgs::Transform {
translation: geometry_msgs::Vector3 {
x: translation.x,
y: translation.y,
z: translation.z,
},
rotation: geometry_msgs::Quaternion {
x: rotation.x,
y: rotation.y,
z: rotation.z,
w: rotation.w,
},
},
}
}
}