分布式通信
概念
分布式通信是指可以通过网络在不同主机之间实现数据交互的一种通信策略。
ROS2 本身是一个分布式通信框架,可以很方便的实现不同设备之间的通信,ROS2 所基于的中间件是 DDS,当处于同一网络中时,通过 DDS 的域 ID 机制(ROS_DOMAIN_ID)可以实现分布式通信,大致流程是:在启动节点之前,可以设置域 ID 的值,不同节点如果域 ID 相同,那么可以自由发现并通信,反之,如果域 ID 值不同,则不能实现。默认情况下,所有节点启动时所使用的域 ID 为 0,换言之,只要保证在同一网络,你不需要做任何配置,不同 ROS2设备上的不同节点即可实现分布式通信。
作用
分布式通信的应用场景是较为广泛的,如上所述:机器人编队时,机器人可能需要获取周边机器人的速度、位置、运行轨迹的相关信息,远程控制时,则可能需要控制端获取机器人采集的环境信息并下发控制指令… 这些数据的交互都依赖于分布式通信。
实现
多机通信时,可以通过域 ID 对节点进行分组,组内的节点之间可以自由通信,不同组之间的节点则不可通信。如果所有节点都属于同一组,那么直接使用默认域 ID 即可,如果要将不同节点划分为多个组,那么可以在终端中启动节点前设置该节点的域 ID(比如设置为 6),具体执行命令为:
export ROS_DOMAIN_ID=6
上述指令执行后,该节点将被划分到 ID 为 6 的域内。
如果要为当前设备下的所有节点设置统一的域 ID,那么可以执行如下指令:
echo "export ROS_DOMAIN_ID=6" >> ~/.bashrc
执行完毕后再重新启动终端,运行的所有节点将自动被划分到 ID 为 6 的域内。
WARRING
在设置 ROS_DOMAIN_ID 的值时并不是随意的,也是有一定约束的:
1.建议 ROS_DOMAIN_ID 的取值在[0,101] 之间,包含 0 和 101;
2.每个域 ID 内的节点总数是有限制的,需要小于等于 120 个;
3.如果域 ID 为 101,那么该域的节点总数需要小于等于 54 个。
DDS 域 ID 值的计算规则
域 ID 值的相关计算规则如下:
DDS 是基于 TCP/IP 或 UDP/IP 网络通信协议的,网络通信时需要指定端口号,端口号由 2 个字节的无符号整数表示,其取值范围在[0,65535]之间;
端口号的分配也是有其规则的,并非可以任意使用的,根据 DDS 协议规定以 7400 作为起始端口,也即可用端口为[7400,65535],又已知按照 DDS 协议默认情况 下,每个域 ID 占用 250 个端口,那么域 ID 的个数为:(65535-7400)/250 = 232(个),对应的其取值范围为[0,231];
操作系统还会设置一些预留端口,在 DDS 中使用端口时,还需要避开这些预留端口,以免使用中产生冲突,不同的操作系统预留端口又有所差异,其最终结果是,在 Linux 下,可用的域 ID 为[0,101]与[215-231],在 Windows和 Mac 中可用的域 ID 为[0,166],综上,为了兼容多平台,建议域 ID 在[0,101] 范围内取值。
每个域 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 个。
上述计算规则了解即可。
域 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
时间相关API
Rate
C++
void demo_rate(){
//用间隔时间
rclcpp::Rate rate1(500ms);
//用频率
rclcpp::Rate rate2(1.0);
while (rclcpp::ok())
{
RCLCPP_INFO(this->get_logger(),"---");
rate2.sleep();
}
Python
在 C++ 中实现很简单,但是在 python 中,在主线程里这样操作会导致主程序挂起······
def demo_rate(self):
self.rate = self.create_rate(1.0)
while rclpy.ok():
self.get_logger().info("+++")
self.rate.sleep()
解决方案:
** time 包**
import time
# 导入 time 包
·······
time.sleep(1.0)
# 以秒为单位
** 线程包 **
import threading
# 导入线程包
···
# 创建子线程实现运行频率控制
thread = threading.Thread(target=self.do_some)
thread.start()
def do_some(self):
while rclpy.ok():
self.get_logger().info("++++++")
self.rate.sleep()
Time
C++
time 类型获取秒和纳秒
demo_time();
······
void demo_time(){
rclcpp::Time t1(500000000L);
rclcpp::Time t2(2,500000000L);
// rclcpp::Time right_now = this->get_clock()->now();
rclcpp::Time right_now = this->now();
RCLCPP_INFO(this->get_logger(),"s = %.2f , ns = %ld",t1.seconds(),t1.nanoseconds());
RCLCPP_INFO(this->get_logger(),"s = %.2f , ns = %ld",t2.seconds(),t2.nanoseconds());
RCLCPP_INFO(this->get_logger(),"s = %.2f , ns = %ld",right_now.seconds(),right_now.nanoseconds());
}
输出:
herrscher@herrscher-virtual-machine:~/ws01_plumbing$ ros2 run cpp06_time demo01_time
[INFO] [1745655034.738105787] [time_node_cpp]: s = 0.50 , ns = 500000000
[INFO] [1745655034.738174782] [time_node_cpp]: s = 2.50 , ns = 2500000000
[INFO] [1745655034.738185576] [time_node_cpp]: s = 1745655034.74 , ns = 1745655034738104927
python
self.demo_time()
def demo_time(self):
t1 = Time(seconds=5,nanoseconds=500000000)
right_now = self.get_clock().now()
self.get_logger().info(" s = %.2f , ns = %d" % (t1.seconds_nanoseconds()[0],t1.seconds_nanoseconds()[1]))
self.get_logger().info(" s = %.2f , ns = %d" % (right_now.seconds_nanoseconds()[0],right_now.seconds_nanoseconds()[1]))
# python 中没有直接返回秒的函数,只能返回 seconds_nanoseconds 元组的第一个元素
self.get_logger().info(" ns = %d" % t1.nanoseconds)
self.get_logger().info(" ns = %d" % right_now.nanoseconds)
Duration
C++
demo_duration();
···
void demo_duration(){
rclcpp::Duration du1(1s);
rclcpp::Duration du2(2,500000000);
RCLCPP_INFO(this->get_logger(),"s = %.2f , ns = %ld",du1.seconds(),du1.nanoseconds());
RCLCPP_INFO(this->get_logger(),"s = %.2f , ns = %ld",du2.seconds(),du2.nanoseconds());
}
python
self.demo_duration()
def demo_duration(self):
du1 = Duration(seconds=10,nanoseconds=800000000)
self.get_logger().info("ns = %d" % du1.nanoseconds)
Time 和 Duration 的区别
Time 代指的是一个具体的时刻(从美国时间1970年1月1日 0:00:00开始)
Duration 代指的是一个时间段
API 运算
C++
void demo_opt(){
rclcpp::Time t1(10,0);
rclcpp::Time t2(30,0);
rclcpp::Duration du1(8,0);
rclcpp::Duration du2(17,0);
// 运算
// 比较运算
RCLCPP_INFO(this->get_logger(),"t1 >= t2 %d",t1 >= t2);
RCLCPP_INFO(this->get_logger(),"t1 < t2 %d",t1 < t2);
// 数学运算
rclcpp::Duration du3 = t2 - t1;
rclcpp::Time t3 = t1 + du1;
rclcpp::Time t4 = t1 - du1;
}
python
def demo_opt(self):
t1 = Time(seconds=20)
t2 = Time(seconds=15)
du1 = Duration(seconds=7)
du1 = Duration(seconds=13)
t3 = t1 - t2
t4 = t1 + du1
t5 = t1 - du1
命令行工具
和 ros 基本一样,可以查看 help 文档
ros2 -h
rqt工具箱
安装命令
sudo apt install ros-humble-rqt*
rqt