Table of Contents
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
- The below example is taken from the official Wiki of ROS2 : Writing a simple publisher and subscriber (C++)
- No Need to fully understand the code; just absorb the workflow
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)
}
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:
- 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)); }
- We have here data type as <std_msgs::msg::Int32>
- topic name is string_topic
- callback function on each message received is topic_callback
- We have here data type as <sensor_msgs::msg::Imu>
- topic name is imu_topic
- 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.