Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(autoware_topic_relay_controller): add topic relay controller node #9964

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions system/autoware_topic_relay_controller/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
cmake_minimum_required(VERSION 3.14)
project(autoware_topic_relay_controller)

find_package(autoware_cmake REQUIRED)
autoware_package()

ament_auto_add_library(${PROJECT_NAME} SHARED
src/topic_relay_controller_node.cpp
)

rclcpp_components_register_node(${PROJECT_NAME}
PLUGIN "autoware::topic_relay_controller::TopicRelayController"
EXECUTABLE ${PROJECT_NAME}_node
EXECUTOR MultiThreadedExecutor
)

ament_auto_package(INSTALL_TO_SHARE
config
launch
)
45 changes: 45 additions & 0 deletions system/autoware_topic_relay_controller/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# topic_relay_controller

## Purpose

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.

## Inputs / Outputs

### Input

| Name | Type | Description |
| ------------ | -------------------------- | -------------------------------------------------------------------- |
| `/<topic>` | `<specified message type>` | Topic to be subscribed, as defined by the `topic` parameter. |
| `/tf` | `tf2_msgs::msg::TFMessage` | (Optional) If the topic is `/tf`, used for transform message relay. |
| `/tf_static` | `tf2_msgs::msg::TFMessage` | (Optional) If the topic is `/tf_static`, used for static transforms. |

### Output

| Name | Type | Description |
| ---------------- | -------------------------- | ----------------------------------------------------------------------------- |
| `/<remap_topic>` | `<specified message type>` | Republished topic after remapping, as defined by the `remap_topic` parameter. |

## Parameters

| Variable | Type | Description |
| ---------------------- | ------- | ---------------------------------------------------------------------------------------------------- |
| topic | string | The name of the input topic to subscribe to |
| remap_topic | string | The name of the output topic to publish to |
| topic_type | string | The type of messages being relayed |
| qos | integer | QoS profile to use for subscriptions and publications (default: `1`) |
| transient_local | boolean | Enables transient local QoS for subscribers (default: `false`) |
| best_effort | boolean | Enables best-effort QoS for subscribers (default: `false`) |
| enable_relay_control | boolean | Allows dynamic relay control via a service (default: `true`) |
| srv_name | string | The service name for relay control when `enable_relay_control` is `true` |
| enable_keep_publishing | boolean | Keeps publishing the last received topic value when not subscribed (default: `false`) |
| update_rate | integer | The rate (Hz) for publishing the last topic value when `enable_keep_publishing` is `true` (optional) |
| frame_id | string | Frame ID for transform messages when subscribing to `/tf` or `/tf_static` (optional) |
| child_frame_id | string | Child frame ID for transform messages when subscribing to `/tf` or `/tf_static` (optional) |

## Assumptions / Known limits

- The node assumes that the specified `topic` and `remap_topic` are valid and accessible within the ROS 2 environment.
- If `enable_keep_publishing` is `true`, the node continuously republishes the last received value even if no new messages are being received.
- For `/tf` and `/tf_static`, additional parameters like `frame_id` and `child_frame_id` are required for selective transformation relays.
- QoS settings must be carefully chosen to match the requirements of the subscribed and published topics.
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/**:
ros__parameters:
topic: "/default"
topic_type: "std_msgs/msg/String" # unneccessary for /tf or /tf_static
remap_topic: "/remap_default"
#frame_id: neccessary for /tf or /tf_static
#child_frame_id: neccessary for /tf or /tf_static
qos_depth: 1
transient_local: false # Change qos to transient_local. Default is volatile.
best_effort: false # Change qos to best_effort. Default is reliable.
enable_relay_control: true
srv_name: "/system/topic_relay_controller_default/operate" # neccessary for enable_relay_control is true
enable_keep_publishing: false
#update_rate: #neccessary for enable_keep_publishing is true
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<launch>
<arg name="node_name_suffix" default="default" description="node name suffix"/>
<arg name="config_file" default="$(find-pkg-share autoware_topic_relay_controller)/config/topic_relay_controller.param.yaml" description="config file path"/>

<node pkg="autoware_topic_relay_controller" exec="autoware_topic_relay_controller_node" name="topic_relay_controller_$(var node_name_suffix)" output="screen">
<param from="$(var config_file)"/>
</node>
</launch>
25 changes: 25 additions & 0 deletions system/autoware_topic_relay_controller/package.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<?xml version="1.0"?>
<?xml-model href="http://download.ros.org/schema/package_format3.xsd" schematypens="http://www.w3.org/2001/XMLSchema"?>
<package format="3">
<name>autoware_topic_relay_controller</name>
<version>0.1.0</version>
<description>The topic_relay_controller ROS 2 package</description>
<maintainer email="[email protected]">Makoto Kurihara</maintainer>
<maintainer email="[email protected]">Tetsuhiro Kawaguchi</maintainer>
<license>Apache License 2.0</license>

<buildtool_depend>ament_cmake_auto</buildtool_depend>
<buildtool_depend>autoware_cmake</buildtool_depend>

<depend>rclcpp</depend>
<depend>rclcpp_components</depend>
<depend>tf2_msgs</depend>
<depend>tier4_system_msgs</depend>

<test_depend>ament_lint_auto</test_depend>
<test_depend>autoware_lint_common</test_depend>

<export>
<build_type>ament_cmake</build_type>
</export>
</package>
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
// Copyright 2025 TIER IV, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
// CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language
// governing permissions and limitations under the License.

#include "topic_relay_controller_node.hpp"

#include <memory>
#include <string>

namespace autoware::topic_relay_controller
{
TopicRelayController::TopicRelayController(const rclcpp::NodeOptions & options)
: Node("topic_relay_controller", options), is_relaying_(true)

Check warning on line 22 in system/autoware_topic_relay_controller/src/topic_relay_controller_node.cpp

View check run for this annotation

Codecov / codecov/patch

system/autoware_topic_relay_controller/src/topic_relay_controller_node.cpp#L21-L22

Added lines #L21 - L22 were not covered by tests
{
// Parameter
node_param_.topic = declare_parameter<std::string>("topic");
node_param_.remap_topic = declare_parameter<std::string>("remap_topic");
node_param_.qos_depth = declare_parameter<int>("qos_depth", 1);
node_param_.transient_local = declare_parameter<bool>("transient_local", false);
node_param_.best_effort = declare_parameter<bool>("best_effort", false);
node_param_.is_transform = (node_param_.topic == "/tf" || node_param_.topic == "/tf_static");
node_param_.enable_relay_control = declare_parameter<bool>("enable_relay_control");

Check warning on line 31 in system/autoware_topic_relay_controller/src/topic_relay_controller_node.cpp

View check run for this annotation

Codecov / codecov/patch

system/autoware_topic_relay_controller/src/topic_relay_controller_node.cpp#L25-L31

Added lines #L25 - L31 were not covered by tests
if (node_param_.enable_relay_control)
node_param_.srv_name = declare_parameter<std::string>("srv_name");
node_param_.enable_keep_publishing = declare_parameter<bool>("enable_keep_publishing");

Check warning on line 34 in system/autoware_topic_relay_controller/src/topic_relay_controller_node.cpp

View check run for this annotation

Codecov / codecov/patch

system/autoware_topic_relay_controller/src/topic_relay_controller_node.cpp#L33-L34

Added lines #L33 - L34 were not covered by tests
if (node_param_.enable_keep_publishing)
node_param_.update_rate = declare_parameter<int>("update_rate");

Check warning on line 36 in system/autoware_topic_relay_controller/src/topic_relay_controller_node.cpp

View check run for this annotation

Codecov / codecov/patch

system/autoware_topic_relay_controller/src/topic_relay_controller_node.cpp#L36

Added line #L36 was not covered by tests

if (node_param_.is_transform) {
node_param_.frame_id = declare_parameter<std::string>("frame_id");
node_param_.child_frame_id = declare_parameter<std::string>("child_frame_id");

Check warning on line 40 in system/autoware_topic_relay_controller/src/topic_relay_controller_node.cpp

View check run for this annotation

Codecov / codecov/patch

system/autoware_topic_relay_controller/src/topic_relay_controller_node.cpp#L39-L40

Added lines #L39 - L40 were not covered by tests
} else {
node_param_.topic_type = declare_parameter<std::string>("topic_type");

Check warning on line 42 in system/autoware_topic_relay_controller/src/topic_relay_controller_node.cpp

View check run for this annotation

Codecov / codecov/patch

system/autoware_topic_relay_controller/src/topic_relay_controller_node.cpp#L42

Added line #L42 was not covered by tests
}

// Service
if (node_param_.enable_relay_control) {
srv_change_relay_control_ = create_service<tier4_system_msgs::srv::ChangeTopicRelayControl>(
node_param_.srv_name,
[this](

Check warning on line 49 in system/autoware_topic_relay_controller/src/topic_relay_controller_node.cpp

View check run for this annotation

Codecov / codecov/patch

system/autoware_topic_relay_controller/src/topic_relay_controller_node.cpp#L47-L49

Added lines #L47 - L49 were not covered by tests
const tier4_system_msgs::srv::ChangeTopicRelayControl::Request::SharedPtr request,
tier4_system_msgs::srv::ChangeTopicRelayControl::Response::SharedPtr response) {
is_relaying_ = request->relay_on;
RCLCPP_INFO(get_logger(), "relay control: %s", is_relaying_ ? "ON" : "OFF");
response->status.success = true;
});

Check warning on line 55 in system/autoware_topic_relay_controller/src/topic_relay_controller_node.cpp

View check run for this annotation

Codecov / codecov/patch

system/autoware_topic_relay_controller/src/topic_relay_controller_node.cpp#L51-L55

Added lines #L51 - L55 were not covered by tests
}

// Subscriber
rclcpp::QoS qos = rclcpp::SystemDefaultsQoS();

Check warning on line 59 in system/autoware_topic_relay_controller/src/topic_relay_controller_node.cpp

View check run for this annotation

Codecov / codecov/patch

system/autoware_topic_relay_controller/src/topic_relay_controller_node.cpp#L59

Added line #L59 was not covered by tests
if (node_param_.qos_depth > 0) {
size_t qos_depth = static_cast<size_t>(node_param_.qos_depth);
qos.keep_last(qos_depth);

Check warning on line 62 in system/autoware_topic_relay_controller/src/topic_relay_controller_node.cpp

View check run for this annotation

Codecov / codecov/patch

system/autoware_topic_relay_controller/src/topic_relay_controller_node.cpp#L61-L62

Added lines #L61 - L62 were not covered by tests
} else {
RCLCPP_ERROR(get_logger(), "qos_depth must be greater than 0");
return;

Check warning on line 65 in system/autoware_topic_relay_controller/src/topic_relay_controller_node.cpp

View check run for this annotation

Codecov / codecov/patch

system/autoware_topic_relay_controller/src/topic_relay_controller_node.cpp#L64-L65

Added lines #L64 - L65 were not covered by tests
}

if (node_param_.transient_local) {
mkuri marked this conversation as resolved.
Show resolved Hide resolved
qos.transient_local();

Check warning on line 69 in system/autoware_topic_relay_controller/src/topic_relay_controller_node.cpp

View check run for this annotation

Codecov / codecov/patch

system/autoware_topic_relay_controller/src/topic_relay_controller_node.cpp#L69

Added line #L69 was not covered by tests
}
if (node_param_.best_effort) {
qos.best_effort();

Check warning on line 72 in system/autoware_topic_relay_controller/src/topic_relay_controller_node.cpp

View check run for this annotation

Codecov / codecov/patch

system/autoware_topic_relay_controller/src/topic_relay_controller_node.cpp#L72

Added line #L72 was not covered by tests
}

if (node_param_.is_transform) {
// Publisher
pub_transform_ = this->create_publisher<tf2_msgs::msg::TFMessage>(node_param_.remap_topic, qos);

Check warning on line 77 in system/autoware_topic_relay_controller/src/topic_relay_controller_node.cpp

View check run for this annotation

Codecov / codecov/patch

system/autoware_topic_relay_controller/src/topic_relay_controller_node.cpp#L77

Added line #L77 was not covered by tests

sub_transform_ = this->create_subscription<tf2_msgs::msg::TFMessage>(
node_param_.topic, qos, [this](tf2_msgs::msg::TFMessage::SharedPtr msg) {

Check warning on line 80 in system/autoware_topic_relay_controller/src/topic_relay_controller_node.cpp

View check run for this annotation

Codecov / codecov/patch

system/autoware_topic_relay_controller/src/topic_relay_controller_node.cpp#L79-L80

Added lines #L79 - L80 were not covered by tests
for (const auto & transform : msg->transforms) {
if (
transform.header.frame_id != node_param_.frame_id ||
transform.child_frame_id != node_param_.child_frame_id || !is_relaying_)

Check warning on line 84 in system/autoware_topic_relay_controller/src/topic_relay_controller_node.cpp

View check run for this annotation

CodeScene Delta Analysis / CodeScene Cloud Delta Analysis (main)

❌ New issue: Complex Conditional

TopicRelayController::TopicRelayController has 1 complex conditionals with 2 branches, threshold = 2. A complex conditional is an expression inside a branch (e.g. if, for, while) which consists of multiple, logical operators such as AND/OR. The more logical operators in an expression, the more severe the code smell.

Check warning on line 84 in system/autoware_topic_relay_controller/src/topic_relay_controller_node.cpp

View check run for this annotation

Codecov / codecov/patch

system/autoware_topic_relay_controller/src/topic_relay_controller_node.cpp#L83-L84

Added lines #L83 - L84 were not covered by tests
return;

if (node_param_.enable_keep_publishing) {
last_tf_topic_ = msg;
} else {
pub_transform_->publish(*msg);

Check warning on line 90 in system/autoware_topic_relay_controller/src/topic_relay_controller_node.cpp

View check run for this annotation

Codecov / codecov/patch

system/autoware_topic_relay_controller/src/topic_relay_controller_node.cpp#L90

Added line #L90 was not covered by tests
}
}
});
} else {
// Publisher
pub_topic_ =
this->create_generic_publisher(node_param_.remap_topic, node_param_.topic_type, qos);

Check warning on line 97 in system/autoware_topic_relay_controller/src/topic_relay_controller_node.cpp

View check run for this annotation

Codecov / codecov/patch

system/autoware_topic_relay_controller/src/topic_relay_controller_node.cpp#L97

Added line #L97 was not covered by tests

sub_topic_ = this->create_generic_subscription(

Check warning on line 99 in system/autoware_topic_relay_controller/src/topic_relay_controller_node.cpp

View check run for this annotation

Codecov / codecov/patch

system/autoware_topic_relay_controller/src/topic_relay_controller_node.cpp#L99

Added line #L99 was not covered by tests
node_param_.topic, node_param_.topic_type, qos,
[this]([[maybe_unused]] std::shared_ptr<rclcpp::SerializedMessage> msg) {

Check warning on line 101 in system/autoware_topic_relay_controller/src/topic_relay_controller_node.cpp

View check run for this annotation

Codecov / codecov/patch

system/autoware_topic_relay_controller/src/topic_relay_controller_node.cpp#L101

Added line #L101 was not covered by tests
if (!is_relaying_) return;

if (node_param_.enable_keep_publishing) {
last_topic_ = msg;
} else {
pub_topic_->publish(*msg);

Check warning on line 107 in system/autoware_topic_relay_controller/src/topic_relay_controller_node.cpp

View check run for this annotation

Codecov / codecov/patch

system/autoware_topic_relay_controller/src/topic_relay_controller_node.cpp#L107

Added line #L107 was not covered by tests
}
});
}

// Timer
if (node_param_.enable_keep_publishing) {
const auto update_period_ns = rclcpp::Rate(node_param_.update_rate).period();
timer_ = rclcpp::create_timer(this, get_clock(), update_period_ns, [this]() {

Check warning on line 115 in system/autoware_topic_relay_controller/src/topic_relay_controller_node.cpp

View check run for this annotation

Codecov / codecov/patch

system/autoware_topic_relay_controller/src/topic_relay_controller_node.cpp#L114-L115

Added lines #L114 - L115 were not covered by tests
if (!is_relaying_) return;

if (node_param_.is_transform) {
mkuri marked this conversation as resolved.
Show resolved Hide resolved
if (last_tf_topic_) pub_transform_->publish(*last_tf_topic_);
} else {
if (last_topic_) pub_topic_->publish(*last_topic_);
}
});
}
}

Check warning on line 125 in system/autoware_topic_relay_controller/src/topic_relay_controller_node.cpp

View check run for this annotation

CodeScene Delta Analysis / CodeScene Cloud Delta Analysis (main)

❌ New issue: Complex Method

TopicRelayController::TopicRelayController has a cyclomatic complexity of 23, threshold = 9. This function has many conditional statements (e.g. if, for, while), leading to lower code health. Avoid adding more conditionals and code to it without refactoring.

Check warning on line 125 in system/autoware_topic_relay_controller/src/topic_relay_controller_node.cpp

View check run for this annotation

CodeScene Delta Analysis / CodeScene Cloud Delta Analysis (main)

❌ New issue: Bumpy Road Ahead

TopicRelayController::TopicRelayController has 5 blocks with nested conditional logic. Any nesting of 2 or deeper is considered. Threshold is one single, nested block per function. The Bumpy Road code smell is a function that contains multiple chunks of nested conditional logic. The deeper the nesting and the more bumps, the lower the code health.

Check warning on line 125 in system/autoware_topic_relay_controller/src/topic_relay_controller_node.cpp

View check run for this annotation

Codecov / codecov/patch

system/autoware_topic_relay_controller/src/topic_relay_controller_node.cpp#L125

Added line #L125 was not covered by tests
} // namespace autoware::topic_relay_controller

#include <rclcpp_components/register_node_macro.hpp>
RCLCPP_COMPONENTS_REGISTER_NODE(autoware::topic_relay_controller::TopicRelayController)

Check warning on line 129 in system/autoware_topic_relay_controller/src/topic_relay_controller_node.cpp

View check run for this annotation

Codecov / codecov/patch

system/autoware_topic_relay_controller/src/topic_relay_controller_node.cpp#L129

Added line #L129 was not covered by tests
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
// Copyright 2025 TIER IV, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#ifndef TOPIC_RELAY_CONTROLLER_NODE_HPP_
#define TOPIC_RELAY_CONTROLLER_NODE_HPP_

// ROS 2 core
#include <rclcpp/rclcpp.hpp>

#include <tf2_msgs/msg/tf_message.hpp>
#include <tier4_system_msgs/srv/change_topic_relay_control.hpp>

#include <memory>
#include <string>

namespace autoware::topic_relay_controller
{
struct NodeParam
{
std::string topic;
std::string remap_topic;
std::string topic_type;
int qos_depth;
std::string frame_id;
std::string child_frame_id;
bool transient_local;
bool best_effort;
bool is_transform;
bool enable_relay_control;
std::string srv_name;
bool enable_keep_publishing;
int update_rate;
};

class TopicRelayController : public rclcpp::Node
{
public:
explicit TopicRelayController(const rclcpp::NodeOptions & options);

private:
// Parameter
NodeParam node_param_;

// Subscriber
rclcpp::GenericSubscription::SharedPtr sub_topic_;
rclcpp::Subscription<tf2_msgs::msg::TFMessage>::SharedPtr sub_transform_;

// Publisher
rclcpp::GenericPublisher::SharedPtr pub_topic_;
rclcpp::Publisher<tf2_msgs::msg::TFMessage>::SharedPtr pub_transform_;

// Service
rclcpp::Service<tier4_system_msgs::srv::ChangeTopicRelayControl>::SharedPtr
srv_change_relay_control_;

// Timer
rclcpp::TimerBase::SharedPtr timer_;

// State
bool is_relaying_;
tf2_msgs::msg::TFMessage::SharedPtr last_tf_topic_;
std::shared_ptr<rclcpp::SerializedMessage> last_topic_;
};
} // namespace autoware::topic_relay_controller

#endif // TOPIC_RELAY_CONTROLLER_NODE_HPP_
Loading