第 5 節

ROS2 Other Communication Mechanisms

0瀏覽次數0訪問次數--跳出率--平均停留

Overview


Communication between different devices is achieved through distribution.


Mirror Turtle

Distributed setup

Scene

In many robot-related application scenarios, multiple ROS2 devices need to collaborate, such as in unmanned vehicle formations, drone swarms, remote control, and more. So how do different ROS2 devices communicate with each other?

Concept

Distributed communication refers to a communication strategy that enables data exchange between different hosts over a network.

ROS2 itself is a distributed communication framework that makes it easy to enable communication between different devices. The middleware underlying ROS2 is DDS. When devices are on the same network, distributed communication can be achieved through DDS's domain ID mechanism (ROS_DOMAIN_ID). The general process is: before starting a node, you can set the domain ID value. If different nodes share the same domain ID, they can freely discover and communicate with each other. Conversely, if the domain ID values differ, communication is not possible. By default, all nodes use domain ID 0 when starting. In other words, as long as you are on the same network, you don't need to make any configuration — different nodes on different ROS2 devices can achieve distributed communication.

Purpose

Distributed communication has a wide range of application scenarios, as mentioned above: during robot formation, a robot may need to obtain information about the speed, position, and trajectory of nearby robots; in remote control, the control terminal may need to acquire environmental data collected by the robot and send control commands... The exchange of this data all relies on distributed communication.


Implementation

During multi-machine communication, nodes can be grouped by domain ID. Nodes within the same group can communicate freely, while nodes in different groups cannot communicate with each other. If all nodes belong to the same group, the default domain ID can be used directly. To divide different nodes into multiple groups, you can set the domain ID of a node (for example, set it to 6) in the terminal before starting the node. The specific command to execute is:

export ROS_DOMAIN_ID=6

After executing the above command, the node will be assigned to domain ID 6.

If you want to set a unified domain ID for all nodes under the current device, you can execute the following command:

echo "export ROS_DOMAIN_ID=6" >> ~/.bashrc

After execution, restart the terminal. All running nodes will be automatically assigned to the domain with ID 6.

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

If the IDs are different, normal communication cannot occur, similar to how a ROS Master must be specified in ROS1.

Note

When setting the value of ROS_DOMAIN_ID, it is not arbitrary; there are certain constraints to follow:

  1. It is recommended that the value of ROS_DOMAIN_ID be in the range 0, 101, inclusive of 0 and 101.
  2. The total number of nodes within each domain ID is limited to 120 or fewer.
  3. If the domain ID is 101, the total number of nodes in that domain must be less than or equal to 54.

DDS Domain ID Value Calculation Rules

The calculation rules for the domain ID value are as follows:

  1. DDS is based on the TCP/IP or UDP/IP network communication protocols. When communicating over a network, a port number must be specified. The port number is represented by a 2-byte unsigned integer, with a value range between 0, 65535.
  2. Port number allocation follows specific rules and cannot be arbitrary. According to the DDS protocol, 7400 is designated as the starting port, meaning the available port range is 7400, 65535. It is also known that, by default under the DDS protocol, each domain ID occupies 250 ports. Therefore, the number of domain IDs is: (65535 - 7400) / 250 = 232, with a corresponding value range of 0, 231.
  3. The operating system also reserves some ports. When using ports in DDS, these reserved ports must be avoided to prevent conflicts during use. Different operating systems have different reserved ports. As a result, on Linux, the available domain IDs are 0, 101 and 215, 231, while on Windows and Mac, the available domain IDs are 0, 166. In summary, for cross-platform compatibility, it is recommended to use domain IDs within the range 0, 101.
  4. Each domain ID occupies 250 ports by default, and each ROS 2 node requires two ports. Additionally, according to the DDS protocol, within the port range of each domain ID, the first and second ports are the Discovery Multicast port and the User Multicast port. Starting from the 11th and 12th ports, these are the Discovery Unicast port and User Unicast port for the first node in the domain, with subsequent nodes occupying ports in sequence. Therefore, the maximum number of nodes in a single domain ID is: (250 - 10) / 2 = 120.
  5. Special case: When the domain ID value is 101, the latter half of the ports are reserved by the operating system, and the maximum number of nodes is 54.

Just 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.

The Participant ID is the participant group ID, referring to which node within that domain.

Discovery Multicast Port: The main discovery port. Per DDS specification, the port should start at 7400.

Multicast Port

Discovery Unicast Port: 7410 is the Discovery Unicast port used by the first node. This is because, as specified by DDS, the 11th port is the Discovery Unicast port for the first node.

User Unicast Port: 7411 is the User Unicast port used by the first node, because according to the DDS specification, the 12th port is the User Unicast port for the first node.

The Discovery Unicast Port and User Unicast Port are the ports occupied by the first node, so a total of two ports are used.

If the Domain ID remains 0 and the Participant ID changes to 1, then the next node's Discovery Unicast Port becomes 7412, and the User Unicast Port becomes 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's 7664 and 7665.

Practice: Run a ROS2 Jazzy on a Raspberry Pi 5 to start a keyboard control node, then run a ROS2 Humble on a physical Linux machine (or in Docker with proper network setup) and open the turtlesim display node.

It can be seen that communication is working normally.

Workspace coverage

Not very useful, it's recommended not to use it.

This is related to the order in which bash files are loaded in your .bashrc file. The one loaded last will be executed, meaning it has the highest priority. Except for the bash that comes with ROS2 itself, this bash has the lowest priority no matter where it is loaded.


meta function package

distro means distribution.


The default build type is C++.

--dependent defaults to none.

The --node-name itself is a virtual package, so there is no need to configure it.

No changes needed for CMakeLists.

First delete lines 12 and 13.

<exec_depend>xxxxxx</exec_depend>

The name of the required feature package


Meta-function packages that may be needed in the future:

This feature package is related to navigation.

This is just a meta-function package.

Only configuration files, no other substantive implementation.

The purpose of a metapackage is to simplify installation by bundling your own components together, making them shareable with the ROS2 community. It also makes it easy to install packages from others.

Duplicate node names

Moreover, the node names are all consistent. In the diagram, there is a <2>, 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 reality we are using two turtlesims.

Although they are all displayed, they have identical names, which can be confusing. Additionally, a duplicate name warning has been shown above.

Either use an alias: Wang Dabao, Wang Xiaobao

Either add a namespace: Wang Bao from Mao Lvzi's family, Wang Bao from Li Ergou's family


ros2 run turtlesim turtlesim_node --ros-args --remap __ns:=/t1

ros2 run package_name node_name --ros-args --remap __ns:=/namespace

ros2 run turtlesim turtlesim_node --ros-args --remap __name:=turtlesim2

ros2 run package_name node_name --ros-args --remap __name:=alias

or

ros2 run package_name node_name --ros-args --remap __node:=alias


ros2 pkg create cpp05_names --build-type ament_cmake --node-name demo01_names

The node name is optional; setting it here is mainly to lay the groundwork for future learning.

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

First, import two packages.

Inside this LaunchDescription object is a list, which stores the 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

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"


You can specify a namespace.

#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;
}

Duplicate topic name


ros2 run turtlesim turtlesim_node --ros-args --remap __ns:=/t1

ros2 run package_name node_name --ros-args --remap __ns:=/namespace

This method of adding namespaces not only works for duplicate node names, but also remains effective for topic names.

ros2 run teleop_twist_keyboard teleop_twist_keyboard

This is the node that 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 is ineffective at this point because the topic namespaces are different.

The topic received by the turtle is /turtle1/cmd_vel, and the namespace is /turtle1.

The one controlling the turtle's movement is /cmd_vel, but the namespace is different.

So we need to make the namespaces of the two the same.

Just change it however you like; as long as they match, communication will work normally.


The remappings parameter enables topic remapping. It is a list containing tuples, where each tuple can remap a topic. The first element in the tuple is the original topic name, and the second element is the remapped topic name.

Namespaces can implement namespaces.

remp also supports remapping, where 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 multiple levels.

The global topic is at the same level as the node namespace, meaning it is mounted under the root.

Relative topics are mounted under a namespace.

Private topics are children 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

Messages can include a header containing a timestamp. The receiver parses the message header and compares the message time with the current time to check for excessive delay.

Rate is frequency.

Time is a moment.

Duration is the length of time.


This class's constructor has two overloads: the first is for period, and the second is for 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;
}

You can pass in a nanosecond.

You can also pass in a second and a nanosecond.

Because it is of type int64_t, we append an L at the end. This is 500 million nanoseconds, which equals 0.5 seconds.

This means time2 represents 2.5 seconds.

There are two ways to get the current time:

One is this->get_clock()->now(),

The other one 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 not exactly the same; this duration uses chrono.


t2 and t1 can be subtracted, and the result is of type duration, but they cannot be added.

time can also be added to or subtracted from duration, resulting in a time.

Communication Mechanism Tools

command line


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

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

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


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

Proto is a bit more concise than show.


The pose is used to send position and orientation data, and it will continuously transmit data.

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

Output real-time pose

find and type work in opposite ways.

The message publishing frequency is constantly changing. The message rate can be controlled through a timer, but the timer has inherent inaccuracies and is not extremely precise. Of course, the network is also a major influencing factor.

ros2 topic pub -r publish message frequency topic name message specific command (in JSON format)


clear clears the turtle's trail.

kill is to kill the turtle

reset resets the turtle's position.

spawn means to lay eggs or generate a new turtle.

You can use Tab to complete.

type and find are opposites.

Empty means nothing, so we don't need to write anything after this.


You can add -f or -feedback to enable continuous feedback, which provides the heading angle in radians.


Not all parameters can be deleted; this indicates that static type parameters cannot be deleted.

Maximum value 255, minimum value 0.

Step size 1

It can be displayed on the terminal.

It can also be written to disk.

Of course, you can also modify it.

You can also modify it using ROS2 RUN, with --ros-args -p followed by key:=value.

ROS2 RUN can also read disk files directly.

Rqt toolbox


This is used to display the relationship between two nodes.


This is used to publish messages.

Click the Add button.

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

After setting the parameters, check the box.


call /clear clears the trajectory.

It can also lay eggs, with the parameters set accordingly.


You can modify the parameters directly.


We cannot use rqt to replace the command line. Although rqt is more convenient, since we remotely control the robot during work, we do so through a terminal. Therefore, the command line is very important, and rqt cannot be used in this case.

音乐页