Skip to content

Commit 0c118ba

Browse files
feat(autoware_topic_relay_controller): add topic relay controller node (#9964)
* feat: add node base Signed-off-by: TetsuKawa <kawaguchitnon@icloud.com> * modify: include guard Signed-off-by: TetsuKawa <kawaguchitnon@icloud.com> * feat: delete schema Signed-off-by: TetsuKawa <kawaguchitnon@icloud.com> * feat: delete config Signed-off-by: TetsuKawa <kawaguchitnon@icloud.com> * feat: add subscriber Signed-off-by: TetsuKawa <kawaguchitnon@icloud.com> * modify: add include Signed-off-by: TetsuKawa <kawaguchitnon@icloud.com> * feat: add publisher Signed-off-by: TetsuKawa <kawaguchitnon@icloud.com> * feat: add service Signed-off-by: TetsuKawa <kawaguchitnon@icloud.com> * modify: typo Signed-off-by: TetsuKawa <kawaguchitnon@icloud.com> * style(pre-commit): autofix Signed-off-by: TetsuKawa <kawaguchitnon@icloud.com> * modify: add include memory Signed-off-by: TetsuKawa <kawaguchitnon@icloud.com> * modify: add qos setting Signed-off-by: TetsuKawa <kawaguchitnon@icloud.com> * style(pre-commit): autofix Signed-off-by: TetsuKawa <kawaguchitnon@icloud.com> * feat: add enable_keep_publishing Signed-off-by: TetsuKawa <kawaguchitnon@icloud.com> * style(pre-commit): autofix Signed-off-by: TetsuKawa <kawaguchitnon@icloud.com> * feat: add readme Signed-off-by: TetsuKawa <kawaguchitnon@icloud.com> * style(pre-commit): autofix Signed-off-by: TetsuKawa <kawaguchitnon@icloud.com> * feat: change qos name and add parameter type Signed-off-by: TetsuKawa <kawaguchitnon@icloud.com> * feat: add config and delete arg from launch Signed-off-by: TetsuKawa <kawaguchitnon@icloud.com> * modify: typo Signed-off-by: TetsuKawa <kawaguchitnon@icloud.com> * style(pre-commit): autofix * modify: add comment Signed-off-by: TetsuKawa <kawaguchitnon@icloud.com> * modify: modify comment Signed-off-by: TetsuKawa <kawaguchitnon@icloud.com> * feat: add maintainer Signed-off-by: TetsuKawa <kawaguchitnon@icloud.com> * modift: change readme param format Signed-off-by: TetsuKawa <kawaguchitnon@icloud.com> * style(pre-commit): autofix --------- Signed-off-by: TetsuKawa <kawaguchitnon@icloud.com> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
1 parent 30ab90e commit 0c118ba

File tree

7 files changed

+318
-0
lines changed

7 files changed

+318
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
cmake_minimum_required(VERSION 3.14)
2+
project(autoware_topic_relay_controller)
3+
4+
find_package(autoware_cmake REQUIRED)
5+
autoware_package()
6+
7+
ament_auto_add_library(${PROJECT_NAME} SHARED
8+
src/topic_relay_controller_node.cpp
9+
)
10+
11+
rclcpp_components_register_node(${PROJECT_NAME}
12+
PLUGIN "autoware::topic_relay_controller::TopicRelayController"
13+
EXECUTABLE ${PROJECT_NAME}_node
14+
EXECUTOR MultiThreadedExecutor
15+
)
16+
17+
ament_auto_package(INSTALL_TO_SHARE
18+
config
19+
launch
20+
)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
# topic_relay_controller
2+
3+
## Purpose
4+
5+
The node subscribes to a specified topic, remaps it, and republishes it. Additionally, it has the capability to continue publishing the last received value if the subscription stops.
6+
7+
## Inputs / Outputs
8+
9+
### Input
10+
11+
| Name | Type | Description |
12+
| ------------ | -------------------------- | -------------------------------------------------------------------- |
13+
| `/<topic>` | `<specified message type>` | Topic to be subscribed, as defined by the `topic` parameter. |
14+
| `/tf` | `tf2_msgs::msg::TFMessage` | (Optional) If the topic is `/tf`, used for transform message relay. |
15+
| `/tf_static` | `tf2_msgs::msg::TFMessage` | (Optional) If the topic is `/tf_static`, used for static transforms. |
16+
17+
### Output
18+
19+
| Name | Type | Description |
20+
| ---------------- | -------------------------- | ----------------------------------------------------------------------------- |
21+
| `/<remap_topic>` | `<specified message type>` | Republished topic after remapping, as defined by the `remap_topic` parameter. |
22+
23+
## Parameters
24+
25+
| Variable | Type | Description |
26+
| ---------------------- | ------- | ---------------------------------------------------------------------------------------------------- |
27+
| topic | string | The name of the input topic to subscribe to |
28+
| remap_topic | string | The name of the output topic to publish to |
29+
| topic_type | string | The type of messages being relayed |
30+
| qos | integer | QoS profile to use for subscriptions and publications (default: `1`) |
31+
| transient_local | boolean | Enables transient local QoS for subscribers (default: `false`) |
32+
| best_effort | boolean | Enables best-effort QoS for subscribers (default: `false`) |
33+
| enable_relay_control | boolean | Allows dynamic relay control via a service (default: `true`) |
34+
| srv_name | string | The service name for relay control when `enable_relay_control` is `true` |
35+
| enable_keep_publishing | boolean | Keeps publishing the last received topic value when not subscribed (default: `false`) |
36+
| update_rate | integer | The rate (Hz) for publishing the last topic value when `enable_keep_publishing` is `true` (optional) |
37+
| frame_id | string | Frame ID for transform messages when subscribing to `/tf` or `/tf_static` (optional) |
38+
| child_frame_id | string | Child frame ID for transform messages when subscribing to `/tf` or `/tf_static` (optional) |
39+
40+
## Assumptions / Known limits
41+
42+
- The node assumes that the specified `topic` and `remap_topic` are valid and accessible within the ROS 2 environment.
43+
- If `enable_keep_publishing` is `true`, the node continuously republishes the last received value even if no new messages are being received.
44+
- For `/tf` and `/tf_static`, additional parameters like `frame_id` and `child_frame_id` are required for selective transformation relays.
45+
- QoS settings must be carefully chosen to match the requirements of the subscribed and published topics.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
/**:
2+
ros__parameters:
3+
topic: "/default"
4+
topic_type: "std_msgs/msg/String" # unneccessary for /tf or /tf_static
5+
remap_topic: "/remap_default"
6+
#frame_id: neccessary for /tf or /tf_static
7+
#child_frame_id: neccessary for /tf or /tf_static
8+
qos_depth: 1
9+
transient_local: false # Change qos to transient_local. Default is volatile.
10+
best_effort: false # Change qos to best_effort. Default is reliable.
11+
enable_relay_control: true
12+
srv_name: "/system/topic_relay_controller_default/operate" # neccessary for enable_relay_control is true
13+
enable_keep_publishing: false
14+
#update_rate: #neccessary for enable_keep_publishing is true
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<launch>
2+
<arg name="node_name_suffix" default="default" description="node name suffix"/>
3+
<arg name="config_file" default="$(find-pkg-share autoware_topic_relay_controller)/config/topic_relay_controller.param.yaml" description="config file path"/>
4+
5+
<node pkg="autoware_topic_relay_controller" exec="autoware_topic_relay_controller_node" name="topic_relay_controller_$(var node_name_suffix)" output="screen">
6+
<param from="$(var config_file)"/>
7+
</node>
8+
</launch>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
<?xml version="1.0"?>
2+
<?xml-model href="http://download.ros.org/schema/package_format3.xsd" schematypens="http://www.w3.org/2001/XMLSchema"?>
3+
<package format="3">
4+
<name>autoware_topic_relay_controller</name>
5+
<version>0.1.0</version>
6+
<description>The topic_relay_controller ROS 2 package</description>
7+
<maintainer email="makoto.kurihara@tier4.jp">Makoto Kurihara</maintainer>
8+
<maintainer email="tetsuhiro.kawaguchi@tier4.jp">Tetsuhiro Kawaguchi</maintainer>
9+
<license>Apache License 2.0</license>
10+
11+
<buildtool_depend>ament_cmake_auto</buildtool_depend>
12+
<buildtool_depend>autoware_cmake</buildtool_depend>
13+
14+
<depend>rclcpp</depend>
15+
<depend>rclcpp_components</depend>
16+
<depend>tf2_msgs</depend>
17+
<depend>tier4_system_msgs</depend>
18+
19+
<test_depend>ament_lint_auto</test_depend>
20+
<test_depend>autoware_lint_common</test_depend>
21+
22+
<export>
23+
<build_type>ament_cmake</build_type>
24+
</export>
25+
</package>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
// Copyright 2025 TIER IV, Inc.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
11+
// CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language
12+
// governing permissions and limitations under the License.
13+
14+
#include "topic_relay_controller_node.hpp"
15+
16+
#include <memory>
17+
#include <string>
18+
19+
namespace autoware::topic_relay_controller
20+
{
21+
TopicRelayController::TopicRelayController(const rclcpp::NodeOptions & options)
22+
: Node("topic_relay_controller", options), is_relaying_(true)
23+
{
24+
// Parameter
25+
node_param_.topic = declare_parameter<std::string>("topic");
26+
node_param_.remap_topic = declare_parameter<std::string>("remap_topic");
27+
node_param_.qos_depth = declare_parameter<int>("qos_depth", 1);
28+
node_param_.transient_local = declare_parameter<bool>("transient_local", false);
29+
node_param_.best_effort = declare_parameter<bool>("best_effort", false);
30+
node_param_.is_transform = (node_param_.topic == "/tf" || node_param_.topic == "/tf_static");
31+
node_param_.enable_relay_control = declare_parameter<bool>("enable_relay_control");
32+
if (node_param_.enable_relay_control)
33+
node_param_.srv_name = declare_parameter<std::string>("srv_name");
34+
node_param_.enable_keep_publishing = declare_parameter<bool>("enable_keep_publishing");
35+
if (node_param_.enable_keep_publishing)
36+
node_param_.update_rate = declare_parameter<int>("update_rate");
37+
38+
if (node_param_.is_transform) {
39+
node_param_.frame_id = declare_parameter<std::string>("frame_id");
40+
node_param_.child_frame_id = declare_parameter<std::string>("child_frame_id");
41+
} else {
42+
node_param_.topic_type = declare_parameter<std::string>("topic_type");
43+
}
44+
45+
// Service
46+
if (node_param_.enable_relay_control) {
47+
srv_change_relay_control_ = create_service<tier4_system_msgs::srv::ChangeTopicRelayControl>(
48+
node_param_.srv_name,
49+
[this](
50+
const tier4_system_msgs::srv::ChangeTopicRelayControl::Request::SharedPtr request,
51+
tier4_system_msgs::srv::ChangeTopicRelayControl::Response::SharedPtr response) {
52+
is_relaying_ = request->relay_on;
53+
RCLCPP_INFO(get_logger(), "relay control: %s", is_relaying_ ? "ON" : "OFF");
54+
response->status.success = true;
55+
});
56+
}
57+
58+
// Subscriber
59+
rclcpp::QoS qos = rclcpp::SystemDefaultsQoS();
60+
if (node_param_.qos_depth > 0) {
61+
size_t qos_depth = static_cast<size_t>(node_param_.qos_depth);
62+
qos.keep_last(qos_depth);
63+
} else {
64+
RCLCPP_ERROR(get_logger(), "qos_depth must be greater than 0");
65+
return;
66+
}
67+
68+
if (node_param_.transient_local) {
69+
qos.transient_local();
70+
}
71+
if (node_param_.best_effort) {
72+
qos.best_effort();
73+
}
74+
75+
if (node_param_.is_transform) {
76+
// Publisher
77+
pub_transform_ = this->create_publisher<tf2_msgs::msg::TFMessage>(node_param_.remap_topic, qos);
78+
79+
sub_transform_ = this->create_subscription<tf2_msgs::msg::TFMessage>(
80+
node_param_.topic, qos, [this](tf2_msgs::msg::TFMessage::SharedPtr msg) {
81+
for (const auto & transform : msg->transforms) {
82+
if (
83+
transform.header.frame_id != node_param_.frame_id ||
84+
transform.child_frame_id != node_param_.child_frame_id || !is_relaying_)
85+
return;
86+
87+
if (node_param_.enable_keep_publishing) {
88+
last_tf_topic_ = msg;
89+
} else {
90+
pub_transform_->publish(*msg);
91+
}
92+
}
93+
});
94+
} else {
95+
// Publisher
96+
pub_topic_ =
97+
this->create_generic_publisher(node_param_.remap_topic, node_param_.topic_type, qos);
98+
99+
sub_topic_ = this->create_generic_subscription(
100+
node_param_.topic, node_param_.topic_type, qos,
101+
[this]([[maybe_unused]] std::shared_ptr<rclcpp::SerializedMessage> msg) {
102+
if (!is_relaying_) return;
103+
104+
if (node_param_.enable_keep_publishing) {
105+
last_topic_ = msg;
106+
} else {
107+
pub_topic_->publish(*msg);
108+
}
109+
});
110+
}
111+
112+
// Timer
113+
if (node_param_.enable_keep_publishing) {
114+
const auto update_period_ns = rclcpp::Rate(node_param_.update_rate).period();
115+
timer_ = rclcpp::create_timer(this, get_clock(), update_period_ns, [this]() {
116+
if (!is_relaying_) return;
117+
118+
if (node_param_.is_transform) {
119+
if (last_tf_topic_) pub_transform_->publish(*last_tf_topic_);
120+
} else {
121+
if (last_topic_) pub_topic_->publish(*last_topic_);
122+
}
123+
});
124+
}
125+
}
126+
} // namespace autoware::topic_relay_controller
127+
128+
#include <rclcpp_components/register_node_macro.hpp>
129+
RCLCPP_COMPONENTS_REGISTER_NODE(autoware::topic_relay_controller::TopicRelayController)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
// Copyright 2025 TIER IV, Inc.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
#ifndef TOPIC_RELAY_CONTROLLER_NODE_HPP_
16+
#define TOPIC_RELAY_CONTROLLER_NODE_HPP_
17+
18+
// ROS 2 core
19+
#include <rclcpp/rclcpp.hpp>
20+
21+
#include <tf2_msgs/msg/tf_message.hpp>
22+
#include <tier4_system_msgs/srv/change_topic_relay_control.hpp>
23+
24+
#include <memory>
25+
#include <string>
26+
27+
namespace autoware::topic_relay_controller
28+
{
29+
struct NodeParam
30+
{
31+
std::string topic;
32+
std::string remap_topic;
33+
std::string topic_type;
34+
int qos_depth;
35+
std::string frame_id;
36+
std::string child_frame_id;
37+
bool transient_local;
38+
bool best_effort;
39+
bool is_transform;
40+
bool enable_relay_control;
41+
std::string srv_name;
42+
bool enable_keep_publishing;
43+
int update_rate;
44+
};
45+
46+
class TopicRelayController : public rclcpp::Node
47+
{
48+
public:
49+
explicit TopicRelayController(const rclcpp::NodeOptions & options);
50+
51+
private:
52+
// Parameter
53+
NodeParam node_param_;
54+
55+
// Subscriber
56+
rclcpp::GenericSubscription::SharedPtr sub_topic_;
57+
rclcpp::Subscription<tf2_msgs::msg::TFMessage>::SharedPtr sub_transform_;
58+
59+
// Publisher
60+
rclcpp::GenericPublisher::SharedPtr pub_topic_;
61+
rclcpp::Publisher<tf2_msgs::msg::TFMessage>::SharedPtr pub_transform_;
62+
63+
// Service
64+
rclcpp::Service<tier4_system_msgs::srv::ChangeTopicRelayControl>::SharedPtr
65+
srv_change_relay_control_;
66+
67+
// Timer
68+
rclcpp::TimerBase::SharedPtr timer_;
69+
70+
// State
71+
bool is_relaying_;
72+
tf2_msgs::msg::TFMessage::SharedPtr last_tf_topic_;
73+
std::shared_ptr<rclcpp::SerializedMessage> last_topic_;
74+
};
75+
} // namespace autoware::topic_relay_controller
76+
77+
#endif // TOPIC_RELAY_CONTROLLER_NODE_HPP_

0 commit comments

Comments
 (0)