How to write C++ ROS2 Node made easy for begginer

Introduction

Robot Operating System 2 (ROS2) represents a significant shift in the design and approach to robotic middleware. It’s tailored for modern robotics applications, offering

  • improved security
  • Reliability
  • Scalability

But this enhancement brought complexity to its structure for starters and introduced a steep learning curve. The key things you need are C++ basic syntax understanding.

Starting Point

No understanding of

  • ROS2 C++ Node Structure
  • how to select a message type
  • Customise ROS2 Node template

Learning outcomes

Easily able to

  • Publish data on different messages
  • Control Publishing messages speed
  • Single-Node Publishing and Subscribing

ROS2 C++ Node High Level Strucutre

1. Importing Libraries

#include <libraries>
#include <ros2msg_hpp>

2. Minimal Publisher Class

class{ public :
publisher = (msgtype ,topic)
timer =
   run callback function 
   every X ms (frequency)
   to publish data
     }

3. Call-back function for Publishing

private:
void callback(){
   msg object create
   msg_data fill
    publish(msg)

4. Main Function Call

int main(){
rclcpp spin (class)
}
c++ programming

Compiling and Running a ROS2 node

I would recommend you get the publisher code and build it according to the wiki. It works in following order:

  1. Writing a C++ Node
#include <rclcpp>
int main(){
node=create.publisher()
publisher.publish(data)
}

2. To compile, we add it into our package, CmakeLists.txt

find_pkg(rclcpp)
add_exe(node src/node.cpp)

3. We link required libraries with the compiled executable and build

target_dependencies(node rclcpp)
colcon build

4. We run using ros2 run command

ros2 run your_package_name node


ROS2 C++ Publisher Node Understanding

Code Snippet requires an understanding of C++ concepts, but for utilising a basic template for your purpose, you need to just understand four points.

Defining a Message type for Communication

ROS2 has a lot of buld in message that you can utilize , although you can create your own as well , below we will be using a sensor essage type for IMU data.

Finding ROS2 Message types

ros2 interface show <name> ( example : ros2 interface show sensor_msgs/msg/Imu )

For adding message to C++ source file

find /opt/ros/<ros2-distro>/include -name "imu.hpp" (example distro = humble )

Including message headers in Nodes

For a string data Message

#include "std_msgs/msg/string.hpp"

For Integer data message

#include "std_msgs/msg/int32.hpp"

For IMU sensor data message

#include "sensor_msgs/msg/imu.hpp"

Defining Publisher Topic

## For a string message
      publisher_ = this->create_publisher<std_msgs::msg::String>("string_topic", 10);
      rclcpp::Publisher<std_msgs::msg::String>::SharedPtr publisher_; 

## For a int32 message
      publisher_ = this->create_publisher<std_msgs::msg::Int32>("int_topic", 10);
      rclcpp::Publisher<std_msgs::msg::Int32>::SharedPtr publisher_; 

## For a imu sensor message
      publisher_ = this->create_publisher<sensor_msgs::msg::Imu>("imu_topic", 10);
      rclcpp::Publisher<sensor_msgs::msg::Imu>::SharedPtr publisher_;

Sending Data

Int32 Data

auto message = std_msgs::msg::Int32();
message.data = count_++;        
publisher_->publish(message);

String Data

auto message = std_msgs::msg::String();
message.data = "Hi Robotisim"; 
publisher_->publish(message);

Imu Sensor data ( this is not general data like above; it is designed for real imu sensor data carriage )

auto message = sensor_msgs::msg::Imu();
message.header.stamp = this->get_clock()->now();
message.angular_velocity.x = 0.0;
message.angular_velocity.y = 0.0;
message.angular_velocity.z = 1.0;
message.linear_acceleration.x = 0.2;
message.linear_acceleration.y = 0.3;
message.linear_acceleration.z = 0.4;
publisher_->publish(message);

ROS2 C++ Subscriber Node Understanding

Defining a Message type for Communication

This is important step for communication , message type should exactly be same as on publishing side.

For a string data type

subscription_ = this->create_subscription<std_msgs::msg::Int32>("int_topic", 10, std::bind(&MinimalSubscriber::topic_callback, this,_1));    }
  1. We have here data type as <std_msgs::msg::Int32>
  2. topic name is string_topic
  3. callback function on each message received is topic_callback
  1. We have here data type as <sensor_msgs::msg::Imu>
  2. topic name is imu_topic
  3. callback function on each message received is topic_callback

For Imu data

subscription_ = this->create_subscription<sensor_msgs::msg::Imu>( "imu_topic", 10, std::bind(&IMUSubscriber::topic_callback, this, std::placeholders::_1)); }

Processing Received Data

This message type only contains a single entry

For Int32

void topic_callback(const std_msgs::msg::Int32::SharedPtr msg) const
    {
      RCLCPP_INFO(this->get_logger(), "I heard: '%s'", msg->data);
    }

For Imu data

void topic_callback(const sensor_msgs::msg::Imu::SharedPtr msg)
    {
      RCLCPP_INFO(this->get_logger(), "Received IMU data: Angular velocity Z:                           
                  '%f'", msg->angular_velocity.z);
    }

There are other fields that you can find through ros2 interface command


Single-Node Publishing and Subscribing

There are many cases where we have to subscribe a data as well as publish data within a single node. For that scenario, we apply our programming concepts to ROS2 C++ node.Below is the code that shows the implementation of the concept.you will observe that the additions are in public initialization and private functions

We recommend you apply the four-step ROS2 C++ node structure explained initially to the below-listed node.

#include "rclcpp/rclcpp.hpp"
#include "std_msgs/msg/int32.hpp"
#include "sensor_msgs/msg/imu.hpp"

using namespace std::chrono_literals;

class IntAndIMUPublisherSubscriber : public rclcpp::Node
{
public:
    IntAndIMUPublisherSubscriber()
    : Node("int_and_imu_publisher_subscriber"), count_(0)
    {
        
        int_publisher_ = this->create_publisher<std_msgs::msg::Int32>("int_topic", 10);

        imu_subscription_ = this->create_subscription<sensor_msgs::msg::Imu>(
            "imu_topic", 10, std::bind(&IntAndIMUPublisherSubscriber::imu_callback, this, std::placeholders::_1));

        // Timer to periodically publish Int32 data
        timer_ = this->create_wall_timer(
            1000ms, std::bind(&IntAndIMUPublisherSubscriber::timer_callback, this));
    }

private:
    void timer_callback()
    {
        auto message = std_msgs::msg::Int32();
        message.data = count_++;
        RCLCPP_INFO(this->get_logger(), "Publishing: %d", message.data);
        int_publisher_->publish(message);
    }

    void imu_callback(const sensor_msgs::msg::Imu::SharedPtr msg)
    {
        RCLCPP_INFO(this->get_logger(), "Received IMU data: Angular velocity Z: '%f'", msg->angular_velocity.z);
    }

    rclcpp::TimerBase::SharedPtr timer_;
    rclcpp::Publisher<std_msgs::msg::Int32>::SharedPtr int_publisher_;
    rclcpp::Subscription<sensor_msgs::msg::Imu>::SharedPtr imu_subscription_;
    int count_;
};

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

Video Demonstration on ROS2 C++ Nodes


FAQs for ROS2 C++ Nodes

  • If we have Python-based code as well, then why should we go with C++?
    • Choosing C++ over Python for ROS2 development depends on several factors, including performance requirements, real-time capabilities, and system complexity. C++ offers more control over system resources and execution speed, which is critical in many robotics applications that require real-time performance. While Python might be easier to use and faster for development, C++ provides better efficiency and is more suitable for lower-level system interactions.
  • How do I determine when to use a publisher or a subscriber in ROS2?
    • The decision to use a publisher or a subscriber in ROS2 depends on the role your node needs to play in the communication ecosystem. Use a publisher when your node needs to send information, such as sensor data or status updates. Use a subscriber when your node needs to receive and react to information from other nodes, such as commands or sensor data from different parts of the system.
  • Can ROS2 nodes written in C++ interact with nodes written in Python?
    • Yes, ROS2 nodes written in C++ can interact seamlessly with nodes written in Python. By utilising a middleware to manage node-to-node communication, ROS2 offers an abstraction layer on top of the programming language. This implies that no extra configuration is needed for a Python node to subscribe to messages from a C++ node and vice versa.

ROS2 C++ Quiz

Leave a Comment

Your email address will not be published. Required fields are marked *

Review Your Cart
0
Add Coupon Code
Subtotal
Total Installment Payments
Bundle Discount

 
Scroll to Top