Wiki
ROS2Other communication mechanisms
Existing English translations were reused. 7 current source blocks remain in Simplified Chinese, without triggering paid translation.
1.Overview



Communication between different devices is achieved through distribution.



Mirror Turtle



2.Distributed deployment
Scene
Many robot-related application scenarios involve collaboration among multiple ROS2 devices, such as: unmanned vehicle formations, drone formations, remote control, and so on. So how do different ROS2 devices achieve communication with each other?
Concept
Distributed communication refers to a communication strategy that enables data exchange between different hosts over a network.
ROS2 is itself a distributed communication framework that can conveniently enable communication between different devices. ROS2 is based on the middleware DDS. When in the same network, distributed communication can be achieved through the DDS domain ID mechanism (ROS_DOMAIN_ID). The general process is: before starting a node, the value of the domain ID can be set. If different nodes have the same domain ID, they can freely discover each other and communicate; conversely, if the domain ID values are different, this cannot be achieved. By default, all nodes use domain ID 0 when started. In other words, as long as they are ensured to be in the same network, you do not need to perform any configuration, and different nodes on different ROS2 devices can achieve distributed communication.
Function
The application scenarios of distributed communication are relatively broad, as mentioned above: during robot formation, robots may need to obtain information related to the speed, position, and movement trajectories of surrounding robots; during remote control, the control end may need to obtain environmental information collected by the robot and issue control commands... The exchange of these data all depends on distributed communication.
Implementation
In multi-machine communication, nodes can be grouped by domain ID. Nodes within a group can communicate freely with each other, while nodes in different groups cannot communicate. If all nodes belong to the same group, the default domain ID can be used directly. If different nodes need to be divided into multiple groups, the domain ID of a node (for example, set to 6) can be set before starting the node in the terminal. The specific command is:
export ROS_DOMAIN_ID=6
After the above instruction is executed, the node will be assigned to the domain with ID 6.
If you want to set a unified domain ID for all nodes under the current device, then you can execute the following command:
echo "export ROS_DOMAIN_ID=6" >> ~/.bashrc
After execution is complete, restart the terminal, and all running nodes will be automatically assigned to the domain with ID 6.

The default domain ID is 0. If the domain IDs are different, they cannot communicate with each other.


If the IDs are different, normal communication cannot be carried out, just as in ROS1 the ROS Master must be specified.
Note
When setting the value of ROS_DOMAIN_ID, it is not arbitrary, and there are certain constraints:
-
It is recommended that the value of ROS_DOMAIN_ID be between , inclusive of 0 and 101;
-
The total number of nodes within each domain ID is limited and must be less than or equal to 120;
-
If the domain ID is 101, then the total number of nodes in that domain must be less than or equal to 54.
Calculation rules for DDS domain ID values
The relevant calculation rules for the domain ID value are as follows:
-
DDS是基于TCP/IP或UDP/IP网络通信协议的,网络通信时需要指定端口号,端口号由2个字节的无符号整数表示,其取值范围在之间;
-
端口号的分配也是有其规则的,并非可以任意使用的,根据DDS协议规定以7400作为起始端口,也即可用端口为,又已知按照DDS协议默认情况下,每个域ID占用250个端口,那么域ID的个数为:(65535-7400)/250 = 232(个),对应的其取值范围为;
-
操作系统还会设置一些预留端口,在DDS中使用端口时,还需要避开这些预留端口,以免使用中产生冲突,不同的操作系统预留端口又有所差异,其最终结果是,在Linux下,可用的域ID为与,在Windows和Mac中可用的域ID为,综上,为了兼容多平台,建议域ID在 范围内取值。
-
每个域ID默认占用250个端口,且每个ROS2节点需要占用两个端口,另外,按照DDS协议每个域ID的端口段内,第1、2个端口是Discovery Multicast端口与User Multicast端口,从第11、12个端口开始是域内第一个节点的Discovery Unicast端口与User Unicast,后续节点所占用端口依次顺延,那么一个域ID中的最大节点个数为:(250-10)/2 = 120(个);
-
特殊情况:域ID值为101时,其后半段端口属于操作系统的预留端口,其节点最大个数为54个。
It is sufficient to understand the above calculation rules.
域 ID 与节点所占用端口示意
Domain ID: 0
Participant ID: 0
Discovery Multicast Port: 7400
User Multicast Port: 7401
Discovery Unicast Port: 7410
User Unicast Port: 7411
---
Domain ID: 1
Participant ID: 2
Discovery Multicast Port: 7650
User Multicast Port: 7651
Discovery Unicast Port: 7664
User Unicast Port: 7665
Domain ID refers to the domain ID
Participant ID is the participating group ID, which refers to the number of the node within the domain
Discovery Multicast Port primary discovery port; as specified by DDS, the port should start at 7400
User Multicast Port用户广播端口,
Discovery Unicast Port. 7410 is the Discovery Unicast port used by the first node because, as specified by DDS, the 11th port is the Discovery Unicast port of the first node.
User Unicast Port unicast user port. 7411 is the User Unicast port used starting from the first node, because as specified by DDS, the 12th port is the User Unicast port of the first node.
Discovery Unicast Port and User Unicast Port are the ports occupied by the first node, so a total of two ports are occupied.
If the Domain ID remains unchanged at 0 and the Participant ID becomes 1, then the Discovery Unicast Port of the next node is 7412, and the User Unicast Port is 7413.

And so on.
A Domain ID occupies 250 ports, so when the Domain ID is 1, the Discovery Unicast Port should be 7650.
This is the third node, so it is 7664 and 7665.

Practice: Run a ROS2 Jazzy on Raspberry Pi 5 to start the keyboard control node, then run a ROS2 Humble on a physical Linux machine (or in Docker, but make sure the network is configured properly) and open the turtle display node.

it can be found that normal communication is possible.
3.Workspace Overlay


It is of little use; it is recommended not to use it.



This is related to the order in which bash files are loaded in your bashrc file. Whichever is loaded last will run, that is, it has the highest priority. Aside from the bash that comes with ROS2 itself, this bash, no matter where it is loaded, always has the lowest priority.



4.meta-function package


Distro just means distribution.


--build-type默认C++,
--dependent default none,
--node-name is itself a virtual package, so there is no need to set it either.

CMakeLists requires no modification

First, delete lines 12 and 13
<exec_depend>xxxxxx</exec_depend>
The name of the feature package to depend on



Metapackages that may be needed later:

This function package is navigation-related.

This is just a meta-package

Only configuration files, no other substantive implementation



The purpose of a metapackage is to facilitate installation; by packaging your own components, you can share them with the ROS2 community. It also makes it easier to install other people's components.
5.Duplicate node name


Moreover, the node names are all consistent. There is a <2> in the figure, which is a label assigned by the operating system; other operating systems do not have this label.

rqt_graph

It only shows one turtlesim, but in fact we used two turtlesims.

Although they are all displayed this way, the names are exactly the same, which easily causes confusion, and a duplicate name warning was also given above.



Or use aliases: Wang Dabao, Wang Xiaobao
Either add a namespace: Wang Bao of the Donkey family, Wang Bao of Li Ergou's family



ros2 run turtlesim turtlesim_node --ros-args --remap __ns:=/t1
ros2 run 功能包名 节点名 --ros-args --remap __ns:=/命名空间


ros2 run turtlesim turtlesim_node --ros-args --remap __name:=turtlesim2
ros2 run 功能包名 节点名 --ros-args --remap __name:=别名
or
ros2 run 功能包名 节点名 --ros-args --remap __node:=别名



ros2 pkg create cpp05_names --build-type ament_cmake --node-name demo01_names
The node name can be left unset; setting it here is mainly to lay the groundwork for future learning.



install(DIRECTORY launch DESTINATION share/${PROJECT_NAME})


two leading packets

Inside this LaunchDescription object is a list, and this list stores the several nodes to be launched.


from launch import LaunchDescription
from launch_ros.actions import Node
def generate_launch_description():
return LaunchDescription([
Node(package="turtlesim",executable="turtlesim_node",name="t1")
])
package: package name
executable node name
name alias

ros2 launch cpp05_names demo01_names_launch.py




The root tag is Launch
The subset label is node.

<launch>
<node pkg="turtlesim" exec="turtlesim_node" name="t1" />
</launch>

ros2 launch cpp05_names demo02_names_launch.xml

The root tag of yaml is also launch

launch:
node:
pkg: "turtlesim"
exec: "turtlesim_node"
name: "t1"

ros2 launch cpp05_names demo03_names_launch.yaml



name alias
namespace Namespace

from launch import LaunchDescription
from launch_ros.actions import Node
def generate_launch_description():
return LaunchDescription([
Node(package="turtlesim",executable="turtlesim_node",name="turtle1"),
Node(package="turtlesim",executable="turtlesim_node",namespace="t1"),
Node(package="turtlesim",executable="turtlesim_node",namespace="t1",name="turtle1")
])

<launch>
<node pkg ="turtlesim" exec ="turtlesim_node" name ="turtle1" />
<node pkg ="turtlesim" exec ="turtlesim_node" namespace ="t1" />
<node pkg ="turtlesim" exec ="turtlesim_node" namespace ="t1" name= "turtle1" />
</launch>

launch:
node:
pkg: "turtlesim"
exec: "turtlesim_node"
name: "turtle"
node:
pkg: "turtlesim"
exec: "turtlesim_node"
namespace: "t1"
node:
pkg: "turtlesim"
exec: "turtlesim_node"
namespace: "t1"
name: "turtle"





Namespace can be specified

#include "rclcpp/rclcpp.hpp"
class MyNode: public rclcpp::Node
{
public:
MyNode():Node("mynode_node_cpp","t1_ns")
{
RCLCPP_INFO(this->get_logger(),"Hello World!");
}
};
int main(int argc, char ** argv)
{
rclcpp::init(argc,argv);
auto node = std::make_shared<MyNode>();
rclcpp::spin(node);
rclcpp::shutdown();
return 0;
}




6.Topic name duplication





ros2 run turtlesim turtlesim_node --ros-args --remap __ns:=/t1
ros2 run 功能包名 节点名 --ros-args --remap __ns:=/命名空间
This way of adding a namespace is effective not only for duplicate node names, but of course still effective for topic names as well.





ros2 run teleop_twist_keyboard teleop_twist_keyboard
This is the node that opens and controls the robot's movement
ros2 run teleop_twist_keyboard teleop_twist_keyboard


The corresponding topic name is this.

However, controlling the turtle's movement has no effect at this point because the topic namespaces are different.

The topic that the turtle subscribes to is /turtle1/cmd_vel, and the namespace is /turtle1
What controls the turtle's movement is /cmd_vel, the namespace is different,
So we need to make the namespaces of the two the same.
You can change it to anything, as long as they are changed to the same value, normal communication will work.



remappings can implement topic remapping. This parameter is a list containing tuples, and each tuple can remap one topic. The first parameter in the tuple is the original topic name, and the second parameter is the remapped topic name.
A namespace can implement a namespace.








remp can also set remapping; from is the original topic name, and to is the remapped name.
<launch>
<node pkg ="turtlesim" exec ="turtlesim_node" namespace ="t1" />
<node pkg ="turtlesim" exec ="turtlesim_node" >
<remap from= "/turtle1/cmd_vel" to="/cmd_vel" />
</node>
</launch>


launch:
- node:
pkg: "turtlesim"
exec: "turtlesim_node"
name: "turtle"
- node:
pkg: "turtlesim"
exec: "turtlesim_node"
namespace: "t1"
- node:
pkg: "turtlesim"
exec: "turtlesim_node"
namespace: "t1"
name: "turtle"
node:
pkg: "turtlesim"
exec: "turtlesim_node"
namespace: "t1"
node:
pkg: "turtlesim"
exec: "turtlesim_node"
remap:
-
from: "/turtle1/cmd_vel"
to: "/cmd_vel"




Namespaces can have several levels.

A global topic is at the same level as a node namespace, that is, it is mounted under the root.
The relative topic is mounted under the namespace.
A private topic is a child of the node name.




#include "rclcpp/rclcpp.hpp"
#include "std_msgs/msg/string.hpp"
using std_msgs::msg::String;
class MyNode: public rclcpp::Node
{
public:
MyNode():Node("mynode_node_name","t1_namespace")
{
RCLCPP_INFO(this->get_logger(),"Hello World!");
publisher_ = this->create_publisher<String>("/global_topics",10);
}
private:
rclcpp::Publisher<String>::SharedPtr publisher_;
};
int main(int argc, char ** argv)
{
rclcpp::init(argc,argv);
auto node = std::make_shared<MyNode>();
rclcpp::spin(node);
rclcpp::shutdown();
return 0;
}



Global topics


Relative topic


Private Topic
7.Time-related API

A message may have a message header, which contains a timestamp. The receiver parses the message header and compares the message time with the current time to see whether the delay is too high.
Rate is frequency
Time is an instant
Duration is the duration





The constructor of this class has two overloads: the first is period, and the second is frequency.



#include "rclcpp/rclcpp.hpp"
using namespace std::chrono_literals;
class MyNode: public rclcpp::Node
{
public:
MyNode():Node("time_node_cpp")
{
RCLCPP_INFO(this->get_logger(),"Hello World!");
demo_rate();
}
private:
void demo_rate()
{
rclcpp::Rate rate1(500ms);
rclcpp::Rate rate2(1.0);
// while(rclcpp::ok())
// {
// RCLCPP_INFO(this->get_logger(),"休眠500ms");
// rate1.sleep();
// }
while(rclcpp::ok())
{
RCLCPP_INFO(this->get_logger(),"休眠1000ms");
rate2.sleep();
}
}
};
int main(int argc, char ** argv)
{
rclcpp::init(argc,argv);
auto node = std::make_shared<MyNode>();
rclcpp::spin(node);
rclcpp::shutdown();
return 0;
}




A nanosecond can be passed in

It is also possible to pass in a second and a nanosecond.

Because it is of type int64_t, we add an L after it. This is 500 million nanoseconds, which is 0.5 seconds.

Thus time2 represents 2.5 seconds.
There are two ways to obtain the current time,
One is this->get_clock()->now(),
The other is this->now();




#include "rclcpp/rclcpp.hpp"
using namespace std::chrono_literals;
class MyNode: public rclcpp::Node
{
public:
MyNode():Node("time_node_cpp")
{
RCLCPP_INFO(this->get_logger(),"Hello World!");
// demo_rate();
demo_time();
}
private:
void demo_rate()
{
rclcpp::Rate rate1(500ms);
rclcpp::Rate rate2(1.0);
// while(rclcpp::ok())
// {
// RCLCPP_INFO(this->get_logger(),"休眠500ms");
// rate1.sleep();
// }
while(rclcpp::ok())
{
RCLCPP_INFO(this->get_logger(),"休眠1000ms");
rate2.sleep();
}
}
void demo_time()
{
rclcpp::Time time1(500000000L);
rclcpp::Time time2(2,500000000L);
rclcpp::Time right_now_1 = this->get_clock()->now();
rclcpp::Time right_now_2 = this->now();
RCLCPP_INFO(this->get_logger(),"s = %.2f , ns = %ld",time1.seconds(),time1.nanoseconds());
RCLCPP_INFO(this->get_logger(),"s = %.2f , ns = %ld",time2.seconds(),time2.nanoseconds());
RCLCPP_INFO(this->get_logger(),"s = %.2f , ns = %ld",right_now_1.seconds(),right_now_1.nanoseconds());
RCLCPP_INFO(this->get_logger(),"s = %.2f , ns = %ld",right_now_2.seconds(),right_now_2.nanoseconds());
}
};
int main(int argc, char ** argv)
{
rclcpp::init(argc,argv);
auto node = std::make_shared<MyNode>();
rclcpp::spin(node);
rclcpp::shutdown();
return 0;
}



Similar to Time
But they are not exactly the same; this duration uses chrono.






t2 and t1 can be subtracted, resulting in a duration type, but cannot be added.
time can also be added to and subtracted from duration, and the result is a time.






8.Communication Mechanism Tool
8.1.Command line



ros2 doctor is used to detect system network status, version compatibility, and other states.


It passed, but there are a few warnings: the version is too low; it does not affect normal use.





The essence of a parameter server is still a server, so it will also be listed under Service Servers.


If you look at the list and find that some interface files are not displayed, that means the environment variables in your terminal have not been refreshed.


proto is somewhat more concise than show.


The pose is for sending the pose, and it will keep sending data continuously.



To output a delay, the message must have a message header.

Output real-time pose


find and type work in opposite ways.

The message publishing frequency is constantly changing. The message frequency can be controlled through a timer, but timers have errors and are not particularly precise. Of course, the network is also a major influencing factor.

ros2 topic pub -r <publishing frequency> <topic name> <message> <specific command (must be in JSON format)>






clear clears the turtle trail
kill means killing a turtle
reset resets the turtle's position.
spawn means to lay eggs, generating new turtles

Can be auto-completed by pressing Tab


type and find are opposites

empty means empty, so we don't need to write anything in the following content.






You can add -f or -feedback to enable continuous feedback; this continuous feedback is the heading angle in radians







Deletion cannot be performed for all parameters; here it indicates that static type parameters cannot be deleted.

Maximum value 255, minimum value 0
Step size 1

can be displayed on the terminal,


It can also be written to disk


Of course, it can also be modified





It can also be modified with ROS2 RUN, --ros-args -p followed by key:=value



ROS2 RUN can also read disk files directly.

8.2.Rqt Toolbox









This shows the relationship between two nodes.


This is used for publishing messages.

Click the add button

You can set the linear velocity, angular velocity, frequency, etc.

After setting the parameters, check




call /clear is for clearing the trajectory.

It can also spawn, after setting the parameters.



This parameter can be modified directly.

We cannot replace the command line with rqt. Although rqt is more convenient, because in our work we remotely control the robot through a terminal, the command line is very important, so rqt cannot be used.