Skip to content

Commit ce0e29c

Browse files
author
Davide Iafrate
committed
feat(ros_gz_bridge): dynamically bridge Gazebo topics
Signed-off-by: Davide Iafrate <dvde.iafrate98@gmail.com>
1 parent 0ea9efc commit ce0e29c

4 files changed

Lines changed: 151 additions & 15 deletions

File tree

ros_gz_bridge/README.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -410,6 +410,19 @@ ros2 topic pub /demo/chatter std_msgs/msg/String "data: 'Hi from inside of a nam
410410
411411
By changing `chatter` to `/chatter` or `~/chatter` you can obtain different results.
412412
413+
## Example 9: Dynamically bridge Gazebo topics
414+
415+
The bridge can discover advertised Gazebo Transport topics and create bridges for
416+
topics whose Gazebo message types have known ROS mappings:
417+
418+
```bash
419+
ros2 run ros_gz_bridge parameter_bridge --ros-args -p create_dynamic_bridges:=true
420+
```
421+
422+
By default, dynamically discovered bridges are created from Gazebo to ROS. Set
423+
`dynamic_bridge_direction` to `BIDIRECTIONAL` or `ROS_TO_GZ` to change the
424+
direction.
425+
413426
## API
414427
415428
ROS 2 Parameters:
@@ -423,6 +436,16 @@ ROS 2 Parameters:
423436
* type: string
424437
* default: ""
425438
* description: YAML file to be loaded as the bridge configuration
439+
* `create_dynamic_bridges`
440+
* type: bool
441+
* default: false
442+
* description: Discover Gazebo topics and automatically create bridges for
443+
topics with known Gazebo to ROS message mappings.
444+
* `dynamic_bridge_direction`
445+
* type: string
446+
* default: "GZ_TO_ROS"
447+
* description: Direction used for dynamically discovered bridges. Supported
448+
values are "GZ_TO_ROS", "ROS_TO_GZ", and "BIDIRECTIONAL".
426449
* `expand_gz_topic_names`
427450
* type: bool
428451
* default: false

ros_gz_bridge/include/ros_gz_bridge/ros_gz_bridge.hpp

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
#define ROS_GZ_BRIDGE__ROS_GZ_BRIDGE_HPP_
1717

1818
#include <memory>
19+
#include <set>
1920
#include <string>
2021
#include <vector>
2122

@@ -56,6 +57,9 @@ class RosGzBridge : public rclcpp::Node
5657
/// \brief Periodic callback to check connectivity and liveliness
5758
void spin();
5859

60+
/// \brief Add bridges for discovered Gazebo topics with known ROS mappings.
61+
void add_dynamic_bridges();
62+
5963
protected:
6064
/// \brief Pointer to Gazebo node used to create publishers/subscribers
6165
std::shared_ptr<gz::transport::Node> gz_node_;
@@ -68,6 +72,12 @@ class RosGzBridge : public rclcpp::Node
6872

6973
/// \brief Timer to control periodic callback
7074
rclcpp::TimerBase::SharedPtr heartbeat_timer_;
75+
76+
/// \brief True after bridge configuration parameters have been processed.
77+
bool configured_bridges_created_{false};
78+
79+
/// \brief Topic/type pairs that have already had bridge creation attempted.
80+
std::set<std::string> bridge_topics_;
7181
};
7282
} // namespace ros_gz_bridge
7383

ros_gz_bridge/src/parameter_bridge.cpp

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -68,17 +68,20 @@ using BridgeConfig = ros_gz_bridge::BridgeConfig;
6868
//////////////////////////////////////////////////
6969
int main(int argc, char * argv[])
7070
{
71-
if (argc < 2) {
72-
usage();
73-
return -1;
74-
}
7571
// skip the process name in argument processing
7672
++argv;
7773
--argc;
7874
auto filteredArgs = rclcpp::init_and_remove_ros_arguments(argc, argv);
7975

8076
auto bridge_node = std::make_shared<RosGzBridge>(rclcpp::NodeOptions());
8177

78+
bool create_dynamic_bridges = false;
79+
bridge_node->get_parameter("create_dynamic_bridges", create_dynamic_bridges);
80+
if (filteredArgs.empty() && !create_dynamic_bridges) {
81+
usage();
82+
return -1;
83+
}
84+
8285
// Set lazy subscriber on a global basis
8386
bool lazy_subscription = false;
8487
bridge_node->get_parameter("lazy", lazy_subscription);

ros_gz_bridge/src/ros_gz_bridge.cpp

Lines changed: 111 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -16,17 +16,48 @@
1616

1717
#include <cstddef>
1818
#include <memory>
19+
#include <set>
20+
#include <sstream>
1921
#include <string>
2022
#include <vector>
2123

2224
#include "bridge_handle_ros_to_gz.hpp"
2325
#include "bridge_handle_gz_to_ros.hpp"
26+
#include "get_mappings.hpp"
2427

2528
#include <rclcpp/expand_topic_or_service_name.hpp>
2629

2730
namespace ros_gz_bridge
2831
{
2932

33+
namespace
34+
{
35+
std::string bridge_key(const BridgeConfig & config)
36+
{
37+
std::stringstream stream;
38+
stream << config.ros_topic_name << '|';
39+
stream << config.ros_type_name << '|';
40+
stream << config.gz_topic_name << '|';
41+
stream << config.gz_type_name << '|';
42+
stream << static_cast<int>(config.direction);
43+
return stream.str();
44+
}
45+
46+
BridgeDirection parse_bridge_direction(const std::string & direction)
47+
{
48+
if (direction == "BIDIRECTIONAL") {
49+
return BridgeDirection::BIDIRECTIONAL;
50+
}
51+
if (direction == "GZ_TO_ROS") {
52+
return BridgeDirection::GZ_TO_ROS;
53+
}
54+
if (direction == "ROS_TO_GZ") {
55+
return BridgeDirection::ROS_TO_GZ;
56+
}
57+
return BridgeDirection::NONE;
58+
}
59+
} // namespace
60+
3061
RosGzBridge::RosGzBridge(const rclcpp::NodeOptions & options)
3162
: rclcpp::Node("ros_gz_bridge", options)
3263
{
@@ -38,6 +69,8 @@ RosGzBridge::RosGzBridge(const rclcpp::NodeOptions & options)
3869
this->declare_parameter<bool>("expand_gz_topic_names", false);
3970
this->declare_parameter<bool>("override_timestamps_with_wall_time", false);
4071
this->declare_parameter<std::string>("override_frame_id", "");
72+
this->declare_parameter<bool>("create_dynamic_bridges", false);
73+
this->declare_parameter<std::string>("dynamic_bridge_direction", "GZ_TO_ROS");
4174
this->declare_parameter("bridge_names", std::vector<std::string>());
4275
const auto names = this->get_parameter("bridge_names").as_string_array();
4376

@@ -120,7 +153,7 @@ RosGzBridge::RosGzBridge(const rclcpp::NodeOptions & options)
120153

121154
void RosGzBridge::spin()
122155
{
123-
if (handles_.empty()) {
156+
if (!configured_bridges_created_) {
124157
std::string config_file;
125158
this->get_parameter("config_file", config_file);
126159
bool expand_names;
@@ -158,16 +191,8 @@ void RosGzBridge::spin()
158191
const auto prefix = "bridges." + name + ".";
159192
if (!this->get_parameter(prefix + "ros_topic_name").as_string().empty()) {
160193
const auto directionStr = this->get_parameter(prefix + "direction").as_string();
161-
BridgeDirection direction {BridgeDirection::NONE};
162-
if (directionStr == "NONE") {
163-
direction = BridgeDirection::NONE;
164-
} else if (directionStr == "BIDIRECTIONAL") {
165-
direction = BridgeDirection::BIDIRECTIONAL;
166-
} else if (directionStr == "GZ_TO_ROS") {
167-
direction = BridgeDirection::GZ_TO_ROS;
168-
} else if (directionStr == "ROS_TO_GZ") {
169-
direction = BridgeDirection::ROS_TO_GZ;
170-
} else {
194+
const auto direction = parse_bridge_direction(directionStr);
195+
if (direction == BridgeDirection::NONE && directionStr != "NONE") {
171196
RCLCPP_ERROR(
172197
this->get_logger(),
173198
"Bridge %s defines unknown direction %s.",
@@ -234,12 +259,79 @@ void RosGzBridge::spin()
234259
this->get_parameter(prefix + "service_name").as_string());
235260
}
236261
}
262+
configured_bridges_created_ = true;
237263
}
264+
265+
bool create_dynamic_bridges = false;
266+
this->get_parameter("create_dynamic_bridges", create_dynamic_bridges);
267+
if (create_dynamic_bridges) {
268+
this->add_dynamic_bridges();
269+
}
270+
238271
for (auto & bridge : handles_) {
239272
bridge->Spin();
240273
}
241274
}
242275

276+
void RosGzBridge::add_dynamic_bridges()
277+
{
278+
std::vector<std::string> gz_topics;
279+
gz_node_->TopicList(gz_topics);
280+
281+
bool lazy;
282+
this->get_parameter("lazy", lazy);
283+
284+
const auto direction_str = this->get_parameter("dynamic_bridge_direction").as_string();
285+
const auto direction = parse_bridge_direction(direction_str);
286+
if (direction == BridgeDirection::NONE) {
287+
RCLCPP_WARN(
288+
this->get_logger(),
289+
"Ignoring dynamic bridge discovery with invalid direction [%s].",
290+
direction_str.c_str());
291+
return;
292+
}
293+
294+
for (const auto & gz_topic : gz_topics) {
295+
std::vector<gz::transport::MessagePublisher> publishers;
296+
std::vector<gz::transport::MessagePublisher> subscribers;
297+
if (!gz_node_->TopicInfo(gz_topic, publishers, subscribers)) {
298+
continue;
299+
}
300+
301+
std::set<std::string> gz_type_names;
302+
for (const auto & publisher : publishers) {
303+
const auto gz_type_name = publisher.MsgTypeName();
304+
if (!gz_type_name.empty()) {
305+
gz_type_names.insert(gz_type_name);
306+
}
307+
}
308+
309+
if (gz_type_names.size() > 1) {
310+
RCLCPP_WARN(
311+
this->get_logger(),
312+
"Skipping dynamic bridge for topic [%s] with multiple Gazebo types.",
313+
gz_topic.c_str());
314+
continue;
315+
}
316+
317+
for (const auto & gz_type_name : gz_type_names) {
318+
std::string ros_type_name;
319+
if (!get_gz_to_ros_mapping(gz_type_name, ros_type_name)) {
320+
continue;
321+
}
322+
323+
BridgeConfig config;
324+
config.ros_type_name = ros_type_name;
325+
config.ros_topic_name = gz_topic;
326+
config.gz_type_name = gz_type_name;
327+
config.gz_topic_name = gz_topic;
328+
config.direction = direction;
329+
config.is_lazy = lazy;
330+
this->add_bridge(config);
331+
}
332+
}
333+
}
334+
243335
void RosGzBridge::add_bridge(const BridgeConfig & input_config)
244336
{
245337
// Resolve the laziness: if the caller left is_lazy as nullopt, inherit the
@@ -266,6 +358,14 @@ void RosGzBridge::add_bridge(const BridgeConfig & input_config)
266358
gz_to_ros = true;
267359
}
268360

361+
if (!gz_to_ros && !ros_to_gz) {
362+
return;
363+
}
364+
365+
if (!bridge_topics_.insert(bridge_key(config)).second) {
366+
return;
367+
}
368+
269369
try {
270370
if (gz_to_ros) {
271371
RCLCPP_INFO(

0 commit comments

Comments
 (0)