Fading Coder

One Final Commit for the Last Sprint

Home > Tech > Content

Topic Communication in ROS2

Tech May 9 4

Topic Communication in ROS2

Topics serve as a fundamental communication mechanism in ROS2, implementing a publish-subscribe model where one node publishes data to a topic and other nodes can subscribe to receive that data.

Publish-Subscribe Model

The publish-subscribe architecture supports:

  1. One-to-one communication
  2. One-to-many communication
  3. Many-to-one communication
  4. Many-to-many communication
  5. Nodes can subscribe to topics they themselves publish

ROS2 Topic Utilities

To visualize topic communication paths, ROS2 provides the rqt_graph tool when running the built-in demo nodes:

ros2 run demo_nodes_cpp listener
ros2 run demo_nodes_cpp talker
rqt_graph

Beyond rqt_graph, ROS2 offers CLI (Command Line Interface) tools for topic operations:

ros2 topic -h

This displays all available topic-related CLI commands.

Implementing Topics with rclcpp

Creating a Publisher Node

Step 1: Set up a new workspace and package

Create a new workspace ros2_demo_ws and a package named demo_topic_cpp, then add a source file:

mkdir -p ./ros2_demo_ws/src
cd ./ros2_demo_ws/src
ros2 pkg create demo_topic_cpp --build-type ament_cmake --license Apache-2.0 --dependencies rclcpp
touch ./demo_topic_cpp/src/publisher_node.cpp

Verify the directory structure:

.
└── demo_topic_cpp
    ├── CMakeLists.txt
    ├── include
    │   └── demo_topic_cpp
    ├── LICENSE
    ├── package.xml
    └── src
        └── publisher_node.cpp

4 directories, 4 files

Create a basic node using an object-oriented approach:

#include "rclcpp/rclcpp.hpp"
#include <string>

class PublisherNode : public rclcpp::Node {
public:
    explicit PublisherNode(const std::string& node_name) : Node(node_name) {
        RCLCPP_INFO(this->get_logger(), "%s node initialized", node_name.c_str());
    }

private:
};

int main(int argc, char** argv) {
    rclcpp::init(argc, argv);
    auto node = std::make_shared<PublisherNode>("publisher_node");
    rclcpp::spin(node);
    rclcpp::shutdown();
    return 0;
}

Update CMakeLists.txt to add the executable:

add_executable(publisher_node src/publisher_node.cpp)
ament_target_dependencies(publisher_node rclcpp)

install(TARGETS
  publisher_node
  DESTINATION lib/${PROJECT_NAME}
)

Step 2: Include message interfaces

Message interfaces are essential for ROS2 communication, enabling message serialization and deserialization. ROS2 provides standard message interfaces with auto-generated C++ and Python headers.

For ament_cmake packages, integrating message interfaces requires modifications in three places:

  1. In CMakeLists.txt: use find_package and ament_target_dependencies
  2. In package.xml: add <depend> tags for the message package
  3. In source code: include the message header with #include "message_package/xxx/xxx.hpp"

Modified CMakeLists.txt:

find_package(ament_cmake REQUIRED)
find_package(rclcpp REQUIRED)
find_package(std_msgs REQUIRED)

add_executable(publisher_node src/publisher_node.cpp)
ament_target_dependencies(publisher_node rclcpp std_msgs)
install(TARGETS
  publisher_node
  DESTINATION lib/${PROJECT_NAME}
)

Modified package.xml:

<buildtool_depend>ament_cmake</buildtool_depend>

<depend>rclcpp</depend>
<depend>std_msgs</depend>

<test_depend>ament_lint_auto</test_depend>
<test_depend>ament_lint_common</test_depend>

Alternatively, specify dependencies during package creation:

ros2 pkg create demo_topic_cpp01 --build-type ament_cmake --license Apache-2.0 --dependencies rclcpp std_msgs

Add the message header to the source file:

#include "rclcpp/rclcpp.hpp"
#include "std_msgs/msg/string.hpp"

class PublisherNode : public rclcpp::Node

Step 3: Create the publisher

Key parameters for publisher creation:

  • topic_name: the topic identifier, set to "robot_cmd"
  • Qos: queue depth using KeepLast, specify as an integer
  • MessageT: template parameter for message type, set to std_msgs::msg::String

Implementation:

#include "rclcpp/rclcpp.hpp"
#include "std_msgs/msg/string.hpp"

class PublisherNode : public rclcpp::Node {
public:
    explicit PublisherNode(const std::string& node_name) : Node(node_name) {
        RCLCPP_INFO(this->get_logger(), "%s node initialized", node_name.c_str());
        cmd_publisher_ = this->create_publisher<std_msgs::msg::String>("robot_cmd", 10);
    }

private:
    rclcpp::Publisher<std_msgs::msg::String>::SharedPtr cmd_publisher_;
};

int main(int argc, char** argv) {
    rclcpp::init(argc, argv);
    auto node = std::make_shared<PublisherNode>("publisher_node");
    rclcpp::spin(node);
    rclcpp::shutdown();
    return 0;
}

Step 4: Add a timer for periodic publishing

Timer parameters:

  • period: interval between callback invocations
  • callback: function to execute
  • group: callback group, defaults to nullptr

Complete publisher with timer:

#include "rclcpp/rclcpp.hpp"
#include "std_msgs/msg/string.hpp"
#include <chrono>

class PublisherNode : public rclcpp::Node {
public:
    explicit PublisherNode(const std::string& node_name) : Node(node_name) {
        RCLCPP_INFO(this->get_logger(), "%s node initialized", node_name.c_str());
        cmd_publisher_ = this->create_publisher<std_msgs::msg::String>("robot_cmd", 10);
        timer_ = this->create_wall_timer(std::chrono::milliseconds(500), 
            [this]() { this->publishCallback(); });
    }

private:
    rclcpp::Publisher<std_msgs::msg::String>::SharedPtr cmd_publisher_;
    rclcpp::TimerBase::SharedPtr timer_;

    void publishCallback() {
        std_msgs::msg::String msg;
        msg.data = "move_forward";
        RCLCPP_INFO(this->get_logger(), "Publishing: '%s'", msg.data.c_str());
        cmd_publisher_->publish(msg);
    }
};

int main(int argc, char** argv) {
    rclcpp::init(argc, argv);
    auto node = std::make_shared<PublisherNode>("publisher_node");
    rclcpp::spin(node);
    rclcpp::shutdown();
    return 0;
}

Build and test:

colcon build --packages-select demo_topic_cpp
source ./install/setup.bash
ros2 run demo_topic_cpp publisher_node

Verify with CLI tools:

ros2 topic list
ros2 topic echo /robot_cmd

Creating a Subscriber Node

Step 1: Create the subscriber source file

cd ./ros2_demo_ws/src
touch ./demo_topic_cpp/src/subscriber_node.cpp

Basic subscriber structure:

#include "rclcpp/rclcpp.hpp"

class SubscriberNode : public rclcpp::Node {
public:
    explicit SubscriberNode(const std::string& node_name) : Node(node_name) {
        RCLCPP_INFO(this->get_logger(), "%s node started", node_name.c_str());
    }

private:
};

int main(int argc, char** argv) {
    rclcpp::init(argc, argv);
    auto node = std::make_shared<SubscriberNode>("subscriber_node");
    rclcpp::spin(node);
    rclcpp::shutdown();
    return 0;
}

Update CMakeLists.txt:

add_executable(subscriber_node src/subscriber_node.cpp)
ament_target_dependencies(subscriber_node rclcpp std_msgs)

install(TARGETS
  publisher_node
  subscriber_node
  DESTINATION lib/${PROJECT_NAME}
)

Test the basic subscriber:

colcon build --packages-select demo_topic_cpp
source install/setup.bash
ros2 run demo_topic_cpp subscriber_node

Step 2: Implement the subscription callback

Key parameters for create_subscription:

  • topic_name: topic to subscribe to
  • qos: message queue depth
  • callback: function to handle receievd messages

Full subscriber implementation:

#include "rclcpp/rclcpp.hpp"
#include "std_msgs/msg/string.hpp"

class SubscriberNode : public rclcpp::Node {
public:
    explicit SubscriberNode(const std::string& node_name) : Node(node_name) {
        RCLCPP_INFO(this->get_logger(), "%s node started", node_name.c_str());
        cmd_subscription_ = this->create_subscription<std_msgs::msg::String>(
            "robot_cmd", 10,
            [this](const std_msgs::msg::String::SharedPtr msg) {
                this->handleMessage(msg);
            });
    }

private:
    rclcpp::Subscription<std_msgs::msg::String>::SharedPtr cmd_subscription_;

    void handleMessage(const std_msgs::msg::String::SharedPtr msg) {
        double velocity = 0.0;
        if (msg->data == "move_forward") {
            velocity = 0.3;
        }
        RCLCPP_INFO(this->get_logger(), "Received command [%s], velocity [%f]", 
            msg->data.c_str(), velocity);
    }
};

int main(int argc, char** argv) {
    rclcpp::init(argc, argv);
    auto node = std::make_shared<SubscriberNode>("subscriber_node");
    rclcpp::spin(node);
    rclcpp::shutdown();
    return 0;
}

Build and run both nodes:

colcon build --packages-select demo_topic_cpp
source install/setup.bash
ros2 run demo_topic_cpp publisher_node
ros2 run demo_topic_cpp subscriber_node

Sample output:

[INFO] [1712900021.099703358] [publisher_node]: Publishing: 'move_forward'
[INFO] [1712900021.599713885] [publisher_node]: Publishing: 'move_forward'
[INFO] [1712900022.099719858] [publisher_node]: Publishing: 'move_forward'
[INFO] [1712900022.599664745] [publisher_node]: Publishing: 'move_forward'

[INFO] [1712900027.600149013] [subscriber_node]: Received command [move_forward], velocity [0.300000]
[INFO] [1712900028.100186298] [subscriber_node]: Received command [move_forward], velocity [0.300000]
[INFO] [1712900028.600202891] [subscriber_node]: Received command [move_forward], velocity [0.300000]

Reference

d2lros2 ROS2 Tutorial

Related Articles

Understanding Strong and Weak References in Java

Strong References Strong reference are the most prevalent type of object referencing in Java. When an object has a strong reference pointing to it, the garbage collector will not reclaim its memory. F...

Comprehensive Guide to SSTI Explained with Payload Bypass Techniques

Introduction Server-Side Template Injection (SSTI) is a vulnerability in web applications where user input is improper handled within the template engine and executed on the server. This exploit can r...

Implement Image Upload Functionality for Django Integrated TinyMCE Editor

Django’s Admin panel is highly user-friendly, and pairing it with TinyMCE, an effective rich text editor, simplifies content management significantly. Combining the two is particular useful for bloggi...

Leave a Comment

Anonymous

◎Feel free to join the discussion and share your thoughts.