rclcpp
: CPP Client Library Overview¶
The ROS 2 core software stack breaks down into a few discrete but related parts:
Warning
This document is under construction. Some things that are implemented are not documented here and other things documented here are the “desired” state but are not implemented in the actual code yet.
Initialization and Shutdown¶
Before using rclcpp it must be initialized exactly once per process.
Initializing rclcpp is done using the rclcpp::init()
function:
#include <rclcpp/rclcpp.hpp>
int main(int argc, char ** argv)
{
rclcpp::init(argc, argv);
}
This function initializes any global resources needed by the middleware and the client library, as well as doing client library related command line argument parsing. The command line arguments can be mutated by this function, as it will remove any client library specific arguments so that later argument parsing does not have to deal with client library specific arguments. Therefore, it is generally a good idea to call this function before doing application specific command line argument parsing.
Shutdown and Reinitialization¶
Initialization can be done again, after a call to rclcpp::shutdown()
has been completed successfully:
int main(int argc, char ** argv)
{
while (/* condition */) {
rclcpp::init(argc, argv, rclcpp::init::do_not_prune_arguments);
// ...
rclcpp::shutdown();
}
}
The shutdown function causes all nodes and their constituent parts to become invalid and shutdown. It also destroys any global resources created when the initialization function was originally called.
Note that if you intend to call rclcpp::init()
multiple times, be sure to use the do_not_prune_arguments
initialization option, as is done above, in order to preserve the original arguments for future invocations.
Testing for Shutdown and Reinitialization¶
In order to test whether or not rclcpp::shutdown()
has been called, the rclcpp::ok()
function can be used:
while (rclcpp::ok()) {
// Do work...
}
In order to test if the system has been reinitialized, an rclcpp::init::InitInstance
can be retrieved using the rclcpp::init::get_instance()
function and tested against the current instance using rclcpp::ok()
:
rclcpp::init::InitInstance init_instance = rclcpp::init::get_instance();
while (rclcpp::ok(init_instance)) {
// Do work...
}
// Either shutting down or restarting...
The instance can be compared to check that the reinitialization was not missed:
rclcpp::init::InitInstance init_instance = rclcpp::init::get_instance();
while (rclcpp::ok(init_instance)) {
// Do work...
if (init_instance != rclcpp::init::get_instance()) {
// Reinitialization happened since the last rclcpp::ok check...
}
}
The initialization instance is just a handle used for comparison and can be copied, moved, or destroyed without consequence.
Nodes¶
The main entry point to the rclcpp API is the rclcpp::Node
class.
The rclcpp::Node
class represents a single element in the ROS graph.
Node’s can have publishers and subscribers on topics, provide or call services, have parameters, and many other things.
Creating a node is done by calling the constructor of the rclcpp::Node
class and providing a name for the node (after calling rclcpp::init()
):
#include <rclcpp/rclcpp.hpp>
// ...
{
rclcpp::init(/* ... */);
rclcpp::Node::SharedPtr node = rclcpp::Node::make_shared("my_node");
}
It is recommended that nodes be created within a smart pointer for automatic object lifetime management, e.g. a shared pointer or a unique pointer, as demonstrated above for the former with the make_shared
alias::
However, it can be created on the stack as well:
// ...
{
rclcpp::Node node("my_node");
}
Since the rclcpp::Node
class operates on an RAII-style pattern, the node is initialized and exposed to the ROS graph on construction and is shutdown and removed from the graph on destruction.
Therefore nodes are scoped and must be kept around to keep the node valid.
If the node object goes out of scope or is explicitly shutdown then any objects created using the node are also invalid.
The name of the node must be unique across all nodes in the ROS graph. If a node with a colliding name is created, then the conflicting node already running will be shutdown.
Todo
Add section about namespaces within nodes, e.g. http://wiki.ros.org/roscpp/Overview/NodeHandles#Namespaces
Publish and Subscribe with Topics¶
One of the middleware communication primitives provided by rclcpp is the publish-subscribe pattern using topics. In this pattern Messages, that are defined by the user in an interface description file, are passed between Publishers and Subscribers which are on the same Topic. A Topic is a name with an associated Message type, which determines whether or not Publishers and Subscribers should exchange messages. Publishers publish new Messages onto the Topic and any Subscribers that have subscribed to the same Topic (and with the same Message type) will receive those messages asynchronously.
Working with Messages¶
Before publishing, a message must be created and filled with information.
Messages are defined using the ROS IDL within .msg
files.
These files are used to generate C++ code and data structures which are used for publishing and when receiving from a subscription.
Messages are namespaced by the package in which they are defined and are converted into C++ code in a conventional way.
For example, a C++ header file is generated for each message:
package_name/msg/Foo.msg
->package_name/msg/foo.hpp
And that header would contain a C++ data structure with a similar namespace:
package_name/msg/Foo.msg
->package_name::msg::Foo
In addition to defining custom Messages, there are many predefined Messages that are defined in the common Message packages that come with ROS, for example:
std_msgs/msg/String.msg
geometry_msgs/msg/Point.msg
builtin_msgs/msg/Time.msg
There are many others, but throughout this document some of the standard messages will be used.
Generated Messages provide attribute access to the Fields so they can be accessed directly for setting and getting:
#include <geometry_msgs/msg/point.hpp>
// ...
{
geometry_msgs::msg::Point p;
p.x = 1;
p.y = 2;
p.z = 3;
printf("Point at (%d, %d, %d)\n", p.x, p.y, p.z);
}
The fields can also be accessed using methods and the named parameter idiom, a.k.a. method chaining:
#include <geometry_msgs/msg/point.hpp>
// ...
{
geometry_msgs::msg::Point p;
p.set__x(1).set__y(2).set__z(3);
printf("Point at (%d, %d, %d)\n", p.x, p.y, p.z);
}
Advanced: Messages and Smart Pointers¶
Generated Messages also have some common smart pointer definitions built in, for example:
geometry_msgs::msg::Point::SharedPtr
is equivalent tostd::shared_ptr<geometry_msgs::msg::Point>
geometry_msgs::msg::Point::ConstSharedPtr
is equivalent tostd::shared_ptr<const geometry_msgs::msg::Point>
geometry_msgs::msg::Point::UniquePtr
is equivalent tostd::unique_ptr<geometry_msgs::msg::Point>
geometry_msgs::msg::Point::WeakPtr
is equivalent tostd::weak_ptr<geometry_msgs::msg::Point>
Todo
link to exhaustive list of aliases provided.
Advanced: Messages and Allocators¶
Generated Messages are also template-able based on an Allocator.
Since the fixed and dynamic sized arrays within a generated C++ Message use STL containers like std::vector
, the generated Messages also expose an Allocator.
For example, the nav_msgs/msg/Path.msg
Message has a list of time stamped poses which are stored in a std::vector
.
You could use the Message with a custom allocator by using the template version of the Message structure that ends with a _
:
#include <nav_msgs/msg/path.hpp>
// ...
{
MyAllocator a;
nav_msgs::msg::Path_<MyAllocator> path(a);
}
Publishing with a Publisher
¶
In rclcpp publishing is achieved by creating an rclcpp::Publisher
object and calling rclcpp::Publisher::publish
with a Message as the first parameter.
Todo
link to complete API docs for Publishers.
Creating an rclcpp::Publisher
is done using the node and by providing a topic name, topic type, and, at a minimum, the publishing queue depth.
The topic type is conveyed as a template argument to the rclcpp::Node::advertise
method, for example:
#include <rclcpp/rclcpp.hpp>
#include <geometry_msgs/msg/point.hpp>
// ...
{
// Previously created a node of type rclcpp::Node::SharedPtr...
rclcpp::Publisher::SharedPtr publisher = node->advertise<geometry_msgs::msg::Point>("my_topic", 10);
geometry_msgs::msg::Point point;
point.set__x(1).set__y(2).set__z(3);
publisher->publish(point);
}
Advanced: Alternative Ways to Create Publishers¶
An rclcpp::Publisher
can also be created by passing one of the built-in QoS policies:
// ...
{
auto publisher = node->advertise<geometry_msgs::msg::Point>("my_topic", rclcpp::qos::profile_sensor_data);
}
Or with a programmatically created QoS profile based on an existing one:
// ...
{
rclcpp::qos::QoS qos = rclcpp::qos::profile_default;
qos.depth = 10;
auto publisher = node->advertise<geometry_msgs::msg::Point>("my_topic", qos);
}
Or passed directly to the method call:
// ...
{
auto publisher = node->advertise<geometry_msgs::msg::Point>(
"my_topic",
rclcpp::qos::profile_default | rclcpp::qos::best_effort | rclcpp::qos::depth(10));
}
Todo
Consider moving alternative signatures to a separate section and link to it.
The RAII-style pattern is also used with the rclcpp::Publisher
, so once created it has been advertised to the ROS graph and other nodes are aware of it.
Conversely, when the rclcpp::Publisher
is allowed to go out of scope, or is explicitly deleted, it is unadvertised from the ROS graph.