From d1f134cc2ae9f7fbfbff3d14e0380b5ade6de142 Mon Sep 17 00:00:00 2001 From: Taiga Arai <39255022+araitaiga@users.noreply.github.com> Date: Fri, 29 May 2026 16:49:12 +0900 Subject: [PATCH 1/2] Backport of #673 ("Adding static transform listener") to jazzy with ABI compatibility preserved (#927) Signed-off-by: Taiga Arai (cherry picked from commit b950c7ea204db2895b9adebbd1c99b74b7fba5d0) # Conflicts: # tf2_ros/test/test_transform_listener.cpp --- .../include/tf2_ros/transform_listener.hpp | 131 ++++++++++++++++++ tf2_ros/src/transform_listener.cpp | 28 ++++ tf2_ros/test/test_transform_listener.cpp | 19 ++- 3 files changed, 177 insertions(+), 1 deletion(-) diff --git a/tf2_ros/include/tf2_ros/transform_listener.hpp b/tf2_ros/include/tf2_ros/transform_listener.hpp index 1e15f13cc..c35f7094f 100644 --- a/tf2_ros/include/tf2_ros/transform_listener.hpp +++ b/tf2_ros/include/tf2_ros/transform_listener.hpp @@ -96,6 +96,18 @@ class TransformListener bool spin_thread = true, bool static_only = false); + /** \brief Simplified constructor for transform listener with static_only option. + * + * This constructor will create a new ROS 2 node under the hood. + * If you already have access to a ROS 2 node and you want to associate the TransformListener + * to it, then it's recommended to use one of the other constructors. + */ + TF2_ROS_PUBLIC + explicit TransformListener( + tf2::BufferCore & buffer, + bool spin_thread, + bool static_only); + /** \brief Node constructor */ template> TransformListener( @@ -123,6 +135,31 @@ class TransformListener static_only) {} + /** \brief Node constructor with static_only option */ + template> + TransformListener( + tf2::BufferCore & buffer, + NodeT && node, + bool spin_thread, + const rclcpp::QoS & qos, + const rclcpp::QoS & static_qos, + const rclcpp::SubscriptionOptionsWithAllocator & options, + const rclcpp::SubscriptionOptionsWithAllocator & static_options, + bool static_only) + : TransformListener( + buffer, + node->get_node_base_interface(), + node->get_node_logging_interface(), + node->get_node_parameters_interface(), + node->get_node_topics_interface(), + spin_thread, + qos, + static_qos, + options, + static_options, + static_only) + {} + /** \brief Node interface constructor */ template> TransformListener( @@ -154,6 +191,35 @@ class TransformListener static_only); } + /** \brief Node interface constructor with static_only option */ + template> + TransformListener( + tf2::BufferCore & buffer, + rclcpp::node_interfaces::NodeBaseInterface::SharedPtr node_base, + rclcpp::node_interfaces::NodeLoggingInterface::SharedPtr node_logging, + rclcpp::node_interfaces::NodeParametersInterface::SharedPtr node_parameters, + rclcpp::node_interfaces::NodeTopicsInterface::SharedPtr node_topics, + bool spin_thread, + const rclcpp::QoS & qos, + const rclcpp::QoS & static_qos, + const rclcpp::SubscriptionOptionsWithAllocator & options, + const rclcpp::SubscriptionOptionsWithAllocator & static_options, + bool static_only) + : buffer_(buffer) + { + init( + node_base, + node_logging, + node_parameters, + node_topics, + spin_thread, + qos, + static_qos, + options, + static_options, + static_only); + } + TF2_ROS_PUBLIC virtual ~TransformListener(); @@ -231,6 +297,71 @@ class TransformListener } } + // Overload of init() with the static_only flag + template> + void init( + rclcpp::node_interfaces::NodeBaseInterface::SharedPtr node_base, + rclcpp::node_interfaces::NodeLoggingInterface::SharedPtr node_logging, + rclcpp::node_interfaces::NodeParametersInterface::SharedPtr node_parameters, + rclcpp::node_interfaces::NodeTopicsInterface::SharedPtr node_topics, + bool spin_thread, + const rclcpp::QoS & qos, + const rclcpp::QoS & static_qos, + const rclcpp::SubscriptionOptionsWithAllocator & options, + const rclcpp::SubscriptionOptionsWithAllocator & static_options, + bool static_only) + { + if (!static_only) { + init( + node_base, + node_logging, + node_parameters, + node_topics, + spin_thread, + qos, + static_qos, + options, + static_options); + return; + } + + spin_thread_ = spin_thread; + node_base_interface_ = node_base; + node_logging_interface_ = node_logging; + + using callback_t = std::function; + callback_t static_cb = std::bind( + &TransformListener::subscription_callback, this, std::placeholders::_1, true); + + if (spin_thread_) { + callback_group_ = node_base_interface_->create_callback_group( + rclcpp::CallbackGroupType::MutuallyExclusive, false); + rclcpp::SubscriptionOptionsWithAllocator tf_static_options = static_options; + tf_static_options.callback_group = callback_group_; + + message_subscription_tf_static_ = rclcpp::create_subscription( + node_parameters, + node_topics, + "/tf_static", + static_qos, + std::move(static_cb), + tf_static_options); + + executor_ = std::make_shared(); + executor_->add_callback_group(callback_group_, node_base_interface_); + dedicated_listener_thread_ = std::make_unique([&]() {executor_->spin();}); + buffer_.setUsingDedicatedThread(true); + } else { + message_subscription_tf_static_ = rclcpp::create_subscription( + node_parameters, + node_topics, + "/tf_static", + static_qos, + std::move(static_cb), + static_options); + } + } + bool spin_thread_{false}; std::unique_ptr dedicated_listener_thread_ {nullptr}; rclcpp::Executor::SharedPtr executor_ {nullptr}; diff --git a/tf2_ros/src/transform_listener.cpp b/tf2_ros/src/transform_listener.cpp index 1193781c6..408b9a73d 100644 --- a/tf2_ros/src/transform_listener.cpp +++ b/tf2_ros/src/transform_listener.cpp @@ -67,6 +67,34 @@ TransformListener::TransformListener(tf2::BufferCore & buffer, bool spin_thread, static_only); } +TransformListener::TransformListener(tf2::BufferCore & buffer, bool spin_thread, bool static_only) +: buffer_(buffer) +{ + rclcpp::NodeOptions options; + // create a unique name for the node + // but specify its name in .arguments to override any __node passed on the command line. + // avoiding sstream because it's behavior can be overridden by external libraries. + // See this issue: https://github.com/ros2/geometry2/issues/540 + char node_name[42]; + snprintf( + node_name, sizeof(node_name), "transform_listener_impl_%zx", + reinterpret_cast(this) + ); + options.arguments({"--ros-args", "-r", "__node:=" + std::string(node_name)}); + options.start_parameter_event_publisher(false); + options.start_parameter_services(false); + optional_default_node_ = rclcpp::Node::make_shared("_", options); + init( + optional_default_node_->get_node_base_interface(), + optional_default_node_->get_node_logging_interface(), + optional_default_node_->get_node_parameters_interface(), + optional_default_node_->get_node_topics_interface(), + spin_thread, DynamicListenerQoS(), StaticListenerQoS(), + detail::get_default_transform_listener_sub_options(), + detail::get_default_transform_listener_static_sub_options(), + static_only); +} + TransformListener::~TransformListener() { if (spin_thread_) { diff --git a/tf2_ros/test/test_transform_listener.cpp b/tf2_ros/test/test_transform_listener.cpp index df38d481c..83c7cb91f 100644 --- a/tf2_ros/test/test_transform_listener.cpp +++ b/tf2_ros/test/test_transform_listener.cpp @@ -37,7 +37,6 @@ #include #include - #include "node_wrapper.hpp" class CustomNode : public rclcpp::Node @@ -131,6 +130,10 @@ TEST(tf2_test_static_transform_listener, static_transform_listener_rclcpp_node) rclcpp::Clock::SharedPtr clock = std::make_shared(RCL_SYSTEM_TIME); tf2_ros::Buffer buffer(clock); +<<<<<<< HEAD +======= + tf2_ros::StaticTransformListener stfl(buffer, node, false); +>>>>>>> b950c7e (Backport of #673 ("Adding static transform listener") to jazzy with ABI compatibility preserved (#927)) } TEST(tf2_test_static_transform_listener, static_transform_listener_custom_rclcpp_node) @@ -139,7 +142,11 @@ TEST(tf2_test_static_transform_listener, static_transform_listener_custom_rclcpp rclcpp::Clock::SharedPtr clock = std::make_shared(RCL_SYSTEM_TIME); tf2_ros::Buffer buffer(clock); +<<<<<<< HEAD tf2_ros::StaticTransformListener tfl(buffer, node, false); +======= + tf2_ros::StaticTransformListener stfl(buffer, node, false); +>>>>>>> b950c7e (Backport of #673 ("Adding static transform listener") to jazzy with ABI compatibility preserved (#927)) } TEST(tf2_test_static_transform_listener, static_transform_listener_as_member) @@ -192,13 +199,23 @@ TEST(tf2_test_listeners, static_vs_dynamic) // Dynamic buffer should have both dynamic and static transforms available EXPECT_NO_THROW( dynamic_buffer.lookupTransform("parent_dynamic", "child_dynamic", tf2::TimePointZero)); +<<<<<<< HEAD EXPECT_NO_THROW(dynamic_buffer.lookupTransform("parent_static", "child_static", clock->now())); +======= + EXPECT_NO_THROW( + dynamic_buffer.lookupTransform("parent_static", "child_static", tf2::TimePointZero)); +>>>>>>> b950c7e (Backport of #673 ("Adding static transform listener") to jazzy with ABI compatibility preserved (#927)) // Static buffer should have only static transforms available EXPECT_THROW( static_buffer.lookupTransform("parent_dynamic", "child_dynamic", tf2::TimePointZero), tf2::LookupException); +<<<<<<< HEAD EXPECT_NO_THROW(static_buffer.lookupTransform("parent_static", "child_static", clock->now())); +======= + EXPECT_NO_THROW( + static_buffer.lookupTransform("parent_static", "child_static", tf2::TimePointZero)); +>>>>>>> b950c7e (Backport of #673 ("Adding static transform listener") to jazzy with ABI compatibility preserved (#927)) } int main(int argc, char ** argv) From bf259848a99e51d9d757790c0d5ef46cde2a8e36 Mon Sep 17 00:00:00 2001 From: Alejandro Hernandez Cordero Date: Mon, 15 Jun 2026 15:51:12 +0200 Subject: [PATCH 2/2] Fixed conflicts Signed-off-by: Alejandro Hernandez Cordero --- tf2_ros/test/test_transform_listener.cpp | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/tf2_ros/test/test_transform_listener.cpp b/tf2_ros/test/test_transform_listener.cpp index 83c7cb91f..c82287638 100644 --- a/tf2_ros/test/test_transform_listener.cpp +++ b/tf2_ros/test/test_transform_listener.cpp @@ -130,10 +130,7 @@ TEST(tf2_test_static_transform_listener, static_transform_listener_rclcpp_node) rclcpp::Clock::SharedPtr clock = std::make_shared(RCL_SYSTEM_TIME); tf2_ros::Buffer buffer(clock); -<<<<<<< HEAD -======= tf2_ros::StaticTransformListener stfl(buffer, node, false); ->>>>>>> b950c7e (Backport of #673 ("Adding static transform listener") to jazzy with ABI compatibility preserved (#927)) } TEST(tf2_test_static_transform_listener, static_transform_listener_custom_rclcpp_node) @@ -142,11 +139,7 @@ TEST(tf2_test_static_transform_listener, static_transform_listener_custom_rclcpp rclcpp::Clock::SharedPtr clock = std::make_shared(RCL_SYSTEM_TIME); tf2_ros::Buffer buffer(clock); -<<<<<<< HEAD - tf2_ros::StaticTransformListener tfl(buffer, node, false); -======= tf2_ros::StaticTransformListener stfl(buffer, node, false); ->>>>>>> b950c7e (Backport of #673 ("Adding static transform listener") to jazzy with ABI compatibility preserved (#927)) } TEST(tf2_test_static_transform_listener, static_transform_listener_as_member) @@ -199,23 +192,15 @@ TEST(tf2_test_listeners, static_vs_dynamic) // Dynamic buffer should have both dynamic and static transforms available EXPECT_NO_THROW( dynamic_buffer.lookupTransform("parent_dynamic", "child_dynamic", tf2::TimePointZero)); -<<<<<<< HEAD - EXPECT_NO_THROW(dynamic_buffer.lookupTransform("parent_static", "child_static", clock->now())); -======= EXPECT_NO_THROW( dynamic_buffer.lookupTransform("parent_static", "child_static", tf2::TimePointZero)); ->>>>>>> b950c7e (Backport of #673 ("Adding static transform listener") to jazzy with ABI compatibility preserved (#927)) // Static buffer should have only static transforms available EXPECT_THROW( static_buffer.lookupTransform("parent_dynamic", "child_dynamic", tf2::TimePointZero), tf2::LookupException); -<<<<<<< HEAD - EXPECT_NO_THROW(static_buffer.lookupTransform("parent_static", "child_static", clock->now())); -======= EXPECT_NO_THROW( static_buffer.lookupTransform("parent_static", "child_static", tf2::TimePointZero)); ->>>>>>> b950c7e (Backport of #673 ("Adding static transform listener") to jazzy with ABI compatibility preserved (#927)) } int main(int argc, char ** argv)