Skip to content

Commit d9eec46

Browse files
committed
Add ABI-compatible static_only support for TransformListener
Signed-off-by: Taiga Arai <araitaiga2020.service@gmail.com>
1 parent dc13549 commit d9eec46

3 files changed

Lines changed: 279 additions & 11 deletions

File tree

tf2_ros/include/tf2_ros/transform_listener.hpp

Lines changed: 154 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,18 @@ class TransformListener
9393
TF2_ROS_PUBLIC
9494
explicit TransformListener(tf2::BufferCore & buffer, bool spin_thread = true);
9595

96+
/** \brief Simplified constructor for transform listener with static_only option.
97+
*
98+
* This constructor will create a new ROS 2 node under the hood.
99+
* If you already have access to a ROS 2 node and you want to associate the TransformListener
100+
* to it, then it's recommended to use one of the other constructors.
101+
*/
102+
TF2_ROS_PUBLIC
103+
explicit TransformListener(
104+
tf2::BufferCore & buffer,
105+
bool spin_thread,
106+
bool static_only);
107+
96108
/** \brief Node constructor */
97109
template<class NodeT, class AllocatorT = std::allocator<void>>
98110
TransformListener(
@@ -118,6 +130,31 @@ class TransformListener
118130
static_options)
119131
{}
120132

133+
/** \brief Node constructor with static_only option */
134+
template<class NodeT, class AllocatorT = std::allocator<void>>
135+
TransformListener(
136+
tf2::BufferCore & buffer,
137+
NodeT && node,
138+
bool spin_thread,
139+
const rclcpp::QoS & qos,
140+
const rclcpp::QoS & static_qos,
141+
const rclcpp::SubscriptionOptionsWithAllocator<AllocatorT> & options,
142+
const rclcpp::SubscriptionOptionsWithAllocator<AllocatorT> & static_options,
143+
bool static_only)
144+
: TransformListener(
145+
buffer,
146+
node->get_node_base_interface(),
147+
node->get_node_logging_interface(),
148+
node->get_node_parameters_interface(),
149+
node->get_node_topics_interface(),
150+
spin_thread,
151+
qos,
152+
static_qos,
153+
options,
154+
static_options,
155+
static_only)
156+
{}
157+
121158
/** \brief Node interface constructor */
122159
template<class AllocatorT = std::allocator<void>>
123160
TransformListener(
@@ -147,6 +184,35 @@ class TransformListener
147184
static_options);
148185
}
149186

187+
/** \brief Node interface constructor with static_only option */
188+
template<class AllocatorT = std::allocator<void>>
189+
TransformListener(
190+
tf2::BufferCore & buffer,
191+
rclcpp::node_interfaces::NodeBaseInterface::SharedPtr node_base,
192+
rclcpp::node_interfaces::NodeLoggingInterface::SharedPtr node_logging,
193+
rclcpp::node_interfaces::NodeParametersInterface::SharedPtr node_parameters,
194+
rclcpp::node_interfaces::NodeTopicsInterface::SharedPtr node_topics,
195+
bool spin_thread,
196+
const rclcpp::QoS & qos,
197+
const rclcpp::QoS & static_qos,
198+
const rclcpp::SubscriptionOptionsWithAllocator<AllocatorT> & options,
199+
const rclcpp::SubscriptionOptionsWithAllocator<AllocatorT> & static_options,
200+
bool static_only)
201+
: buffer_(buffer)
202+
{
203+
init(
204+
node_base,
205+
node_logging,
206+
node_parameters,
207+
node_topics,
208+
spin_thread,
209+
qos,
210+
static_qos,
211+
options,
212+
static_options,
213+
static_only);
214+
}
215+
150216
TF2_ROS_PUBLIC
151217
virtual ~TransformListener();
152218

@@ -165,7 +231,8 @@ class TransformListener
165231
const rclcpp::QoS & qos,
166232
const rclcpp::QoS & static_qos,
167233
const rclcpp::SubscriptionOptionsWithAllocator<AllocatorT> & options,
168-
const rclcpp::SubscriptionOptionsWithAllocator<AllocatorT> & static_options)
234+
const rclcpp::SubscriptionOptionsWithAllocator<AllocatorT> & static_options,
235+
bool static_only = false)
169236
{
170237
spin_thread_ = spin_thread;
171238
node_base_interface_ = node_base;
@@ -181,14 +248,19 @@ class TransformListener
181248
// Create new callback group for message_subscription of tf and tf_static
182249
callback_group_ = node_base_interface_->create_callback_group(
183250
rclcpp::CallbackGroupType::MutuallyExclusive, false);
184-
// Duplicate to modify option of subscription
185-
rclcpp::SubscriptionOptionsWithAllocator<AllocatorT> tf_options = options;
251+
252+
if (!static_only) {
253+
// Duplicate to modify subscription options
254+
rclcpp::SubscriptionOptionsWithAllocator<AllocatorT> tf_options = options;
255+
tf_options.callback_group = callback_group_;
256+
257+
message_subscription_tf_ = rclcpp::create_subscription<tf2_msgs::msg::TFMessage>(
258+
node_parameters, node_topics, "/tf", qos, std::move(cb), tf_options);
259+
}
260+
186261
rclcpp::SubscriptionOptionsWithAllocator<AllocatorT> tf_static_options = static_options;
187-
tf_options.callback_group = callback_group_;
188262
tf_static_options.callback_group = callback_group_;
189263

190-
message_subscription_tf_ = rclcpp::create_subscription<tf2_msgs::msg::TFMessage>(
191-
node_parameters, node_topics, "/tf", qos, std::move(cb), tf_options);
192264
message_subscription_tf_static_ = rclcpp::create_subscription<tf2_msgs::msg::TFMessage>(
193265
node_parameters,
194266
node_topics,
@@ -204,8 +276,10 @@ class TransformListener
204276
// Tell the buffer we have a dedicated thread to enable timeouts
205277
buffer_.setUsingDedicatedThread(true);
206278
} else {
207-
message_subscription_tf_ = rclcpp::create_subscription<tf2_msgs::msg::TFMessage>(
208-
node_parameters, node_topics, "/tf", qos, std::move(cb), options);
279+
if (!static_only) {
280+
message_subscription_tf_ = rclcpp::create_subscription<tf2_msgs::msg::TFMessage>(
281+
node_parameters, node_topics, "/tf", qos, std::move(cb), options);
282+
}
209283
message_subscription_tf_static_ = rclcpp::create_subscription<tf2_msgs::msg::TFMessage>(
210284
node_parameters,
211285
node_topics,
@@ -231,6 +305,78 @@ class TransformListener
231305
rclcpp::node_interfaces::NodeBaseInterface::SharedPtr node_base_interface_ {nullptr};
232306
rclcpp::CallbackGroup::SharedPtr callback_group_{nullptr};
233307
};
308+
309+
/** \brief Convenience class for subscribing to static-only transforms
310+
*
311+
* While users can achieve the same thing with a normal TransformListener, the number of parameters that must be
312+
* provided is unwieldy.
313+
*/
314+
class StaticTransformListener : public TransformListener
315+
{
316+
public:
317+
/** \brief Simplified constructor for a static transform listener
318+
* \see the simplified TransformListener documentation
319+
*/
320+
TF2_ROS_PUBLIC
321+
explicit StaticTransformListener(tf2::BufferCore & buffer, bool spin_thread = true)
322+
: TransformListener(buffer, spin_thread, true)
323+
{
324+
}
325+
326+
/** \brief Node constructor */
327+
template<class NodeT, class AllocatorT = std::allocator<void>>
328+
StaticTransformListener(
329+
tf2::BufferCore & buffer,
330+
NodeT && node,
331+
bool spin_thread = true,
332+
const rclcpp::QoS & static_qos = StaticListenerQoS(),
333+
const rclcpp::SubscriptionOptionsWithAllocator<AllocatorT> & static_options =
334+
detail::get_default_transform_listener_static_sub_options<AllocatorT>())
335+
: TransformListener(
336+
buffer,
337+
node,
338+
spin_thread,
339+
rclcpp::QoS(1),
340+
static_qos,
341+
rclcpp::SubscriptionOptionsWithAllocator<AllocatorT>(),
342+
static_options,
343+
true)
344+
{
345+
}
346+
347+
/** \brief Node interface constructor */
348+
template<class AllocatorT = std::allocator<void>>
349+
StaticTransformListener(
350+
tf2::BufferCore & buffer,
351+
rclcpp::node_interfaces::NodeBaseInterface::SharedPtr node_base,
352+
rclcpp::node_interfaces::NodeLoggingInterface::SharedPtr node_logging,
353+
rclcpp::node_interfaces::NodeParametersInterface::SharedPtr node_parameters,
354+
rclcpp::node_interfaces::NodeTopicsInterface::SharedPtr node_topics,
355+
bool spin_thread = true,
356+
const rclcpp::QoS & static_qos = StaticListenerQoS(),
357+
const rclcpp::SubscriptionOptionsWithAllocator<AllocatorT> & static_options =
358+
detail::get_default_transform_listener_static_sub_options<AllocatorT>())
359+
: TransformListener(
360+
buffer,
361+
node_base,
362+
node_logging,
363+
node_parameters,
364+
node_topics,
365+
spin_thread,
366+
rclcpp::QoS(1),
367+
static_qos,
368+
rclcpp::SubscriptionOptionsWithAllocator<AllocatorT>(),
369+
static_options,
370+
true)
371+
{
372+
}
373+
374+
TF2_ROS_PUBLIC
375+
virtual ~StaticTransformListener()
376+
{
377+
}
378+
};
379+
234380
} // namespace tf2_ros
235381

236382
#endif // TF2_ROS__TRANSFORM_LISTENER_HPP_

tf2_ros/src/transform_listener.cpp

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,34 @@ TransformListener::TransformListener(tf2::BufferCore & buffer, bool spin_thread)
6767
detail::get_default_transform_listener_static_sub_options());
6868
}
6969

70+
TransformListener::TransformListener(tf2::BufferCore & buffer, bool spin_thread, bool static_only)
71+
: buffer_(buffer)
72+
{
73+
rclcpp::NodeOptions options;
74+
// create a unique name for the node
75+
// but specify its name in .arguments to override any __node passed on the command line.
76+
// avoiding sstream because it's behavior can be overridden by external libraries.
77+
// See this issue: https://github.com/ros2/geometry2/issues/540
78+
char node_name[42];
79+
snprintf(
80+
node_name, sizeof(node_name), "transform_listener_impl_%zx",
81+
reinterpret_cast<size_t>(this)
82+
);
83+
options.arguments({"--ros-args", "-r", "__node:=" + std::string(node_name)});
84+
options.start_parameter_event_publisher(false);
85+
options.start_parameter_services(false);
86+
optional_default_node_ = rclcpp::Node::make_shared("_", options);
87+
init(
88+
optional_default_node_->get_node_base_interface(),
89+
optional_default_node_->get_node_logging_interface(),
90+
optional_default_node_->get_node_parameters_interface(),
91+
optional_default_node_->get_node_topics_interface(),
92+
spin_thread, DynamicListenerQoS(), StaticListenerQoS(),
93+
detail::get_default_transform_listener_sub_options(),
94+
detail::get_default_transform_listener_static_sub_options(),
95+
static_only);
96+
}
97+
7098
TransformListener::~TransformListener()
7199
{
72100
if (spin_thread_) {

tf2_ros/test/test_transform_listener.cpp

Lines changed: 97 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,14 +28,13 @@
2828
*/
2929

3030
#include <gtest/gtest.h>
31-
32-
#include <memory>
33-
3431
#include <tf2_ros/buffer.hpp>
3532
#include <tf2_ros/transform_listener.hpp>
3633
#include <tf2_ros/transform_broadcaster.hpp>
3734
#include <tf2_ros/static_transform_broadcaster.hpp>
3835

36+
#include <chrono>
37+
#include <memory>
3938

4039
#include "node_wrapper.hpp"
4140

@@ -53,6 +52,14 @@ class CustomNode : public rclcpp::Node
5352
tf_listener_ = std::make_shared<tf2_ros::TransformListener>(buffer, shared_from_this(), false);
5453
}
5554

55+
void init_static_tf_listener()
56+
{
57+
rclcpp::Clock::SharedPtr clock = std::make_shared<rclcpp::Clock>(RCL_SYSTEM_TIME);
58+
tf2_ros::Buffer buffer(clock);
59+
tf_listener_ =
60+
std::make_shared<tf2_ros::StaticTransformListener>(buffer, shared_from_this(), false);
61+
}
62+
5663
private:
5764
std::shared_ptr<tf2_ros::TransformListener> tf_listener_;
5865
};
@@ -71,6 +78,14 @@ class CustomComposableNode : public rclcpp::Node
7178
tf_listener_ = std::make_shared<tf2_ros::TransformListener>(buffer, shared_from_this(), false);
7279
}
7380

81+
void init_static_tf_listener()
82+
{
83+
rclcpp::Clock::SharedPtr clock = std::make_shared<rclcpp::Clock>(RCL_SYSTEM_TIME);
84+
tf2_ros::Buffer buffer(clock);
85+
tf_listener_ =
86+
std::make_shared<tf2_ros::StaticTransformListener>(buffer, shared_from_this(), false);
87+
}
88+
7489
private:
7590
std::shared_ptr<tf2_ros::TransformListener> tf_listener_;
7691
};
@@ -108,6 +123,85 @@ TEST(tf2_test_transform_listener, transform_listener_with_intraprocess)
108123
custom_node->init_tf_listener();
109124
}
110125

126+
TEST(tf2_test_static_transform_listener, static_transform_listener_rclcpp_node)
127+
{
128+
auto node = rclcpp::Node::make_shared("tf2_ros_static_transform_listener");
129+
130+
rclcpp::Clock::SharedPtr clock = std::make_shared<rclcpp::Clock>(RCL_SYSTEM_TIME);
131+
tf2_ros::Buffer buffer(clock);
132+
tf2_ros::StaticTransformListener stfl(buffer, node, false);
133+
}
134+
135+
TEST(tf2_test_static_transform_listener, static_transform_listener_custom_rclcpp_node)
136+
{
137+
auto node = std::make_shared<NodeWrapper>("tf2_ros_static_transform_listener");
138+
139+
rclcpp::Clock::SharedPtr clock = std::make_shared<rclcpp::Clock>(RCL_SYSTEM_TIME);
140+
tf2_ros::Buffer buffer(clock);
141+
tf2_ros::StaticTransformListener stfl(buffer, node, false);
142+
}
143+
144+
TEST(tf2_test_static_transform_listener, static_transform_listener_as_member)
145+
{
146+
auto custom_node = std::make_shared<CustomNode>();
147+
custom_node->init_static_tf_listener();
148+
}
149+
150+
TEST(tf2_test_static_transform_listener, static_transform_listener_with_intraprocess)
151+
{
152+
rclcpp::executors::SingleThreadedExecutor exec;
153+
rclcpp::NodeOptions options;
154+
options = options.use_intra_process_comms(true);
155+
auto custom_node = std::make_shared<CustomComposableNode>(options);
156+
custom_node->init_static_tf_listener();
157+
}
158+
159+
TEST(tf2_test_listeners, static_vs_dynamic)
160+
{
161+
auto node = rclcpp::Node::make_shared("tf2_ros_static_transform_listener");
162+
163+
rclcpp::Clock::SharedPtr clock = std::make_shared<rclcpp::Clock>(RCL_SYSTEM_TIME);
164+
tf2_ros::Buffer dynamic_buffer(clock);
165+
tf2_ros::Buffer static_buffer(clock);
166+
tf2_ros::TransformListener tfl(dynamic_buffer, node, true);
167+
tf2_ros::StaticTransformListener stfl(static_buffer, node, true);
168+
tf2_ros::TransformBroadcaster broadcaster(node);
169+
tf2_ros::StaticTransformBroadcaster static_broadcaster(node);
170+
171+
geometry_msgs::msg::TransformStamped static_trans;
172+
static_trans.header.stamp = clock->now();
173+
static_trans.header.frame_id = "parent_static";
174+
static_trans.child_frame_id = "child_static";
175+
static_trans.transform.rotation.w = 1.0;
176+
static_broadcaster.sendTransform(static_trans);
177+
178+
geometry_msgs::msg::TransformStamped dynamic_trans;
179+
dynamic_trans.header.frame_id = "parent_dynamic";
180+
dynamic_trans.child_frame_id = "child_dynamic";
181+
dynamic_trans.transform.rotation.w = 1.0;
182+
183+
for (int i = 0; i < 10; ++i) {
184+
dynamic_trans.header.stamp = clock->now();
185+
broadcaster.sendTransform(dynamic_trans);
186+
187+
rclcpp::spin_some(node);
188+
rclcpp::sleep_for(std::chrono::milliseconds(10));
189+
}
190+
191+
// Dynamic buffer should have both dynamic and static transforms available
192+
EXPECT_NO_THROW(
193+
dynamic_buffer.lookupTransform("parent_dynamic", "child_dynamic", tf2::TimePointZero));
194+
EXPECT_NO_THROW(
195+
dynamic_buffer.lookupTransform("parent_static", "child_static", tf2::TimePointZero));
196+
197+
// Static buffer should have only static transforms available
198+
EXPECT_THROW(
199+
static_buffer.lookupTransform("parent_dynamic", "child_dynamic", tf2::TimePointZero),
200+
tf2::LookupException);
201+
EXPECT_NO_THROW(
202+
static_buffer.lookupTransform("parent_static", "child_static", tf2::TimePointZero));
203+
}
204+
111205
int main(int argc, char ** argv)
112206
{
113207
testing::InitGoogleTest(&argc, argv);

0 commit comments

Comments
 (0)