【ROS2实操五】通信机制补充

简介

本章主要介绍关于通信机制的补充内容,比如:分布式框架搭建、重名问题处理、常用API、通信机制工具等等,这些补充内容的知识点比较零散但是每个知识点都不复杂。

|----------------------------------|----------------------------|
| 学习内容 | 学习收获 |
| 1.分布式通信的概念、应用场景以及通信规则。 | 能够独立搭建分布式通信框架。 |
| 2.不同工作空间功能包重名时可能会出现的问题。 | 以后要尽力避免功能包重名的情况。 |
| 3.元功能包的概念、应用场景以及创建规则。 | 能够自定义元功能包。 |
| 4.节点重名时遇到的问题以及处理方式。 | 可以以合适的方式解决节点重名问题。 |
| 5.话题重名时遇到的问题以及处理方式。 | 可以以合适的方式解决话题重名问题。 |
| 6.时间相关API,比如:Rate、Time、Duration。 | 能够掌握时间类API的使用。 |
| 7.通信中常用的命令工具以及rqt工具箱。 | 能够掌握通信工具的使用,从而提高程序的开发调试效率。 |
| 8.不同通信机制的相关练习。 | 强化对ROS2通信机制的认识。 |

一、分布式

1.场景

在许多机器人相关的应用场景中都涉及到多台ROS2设备协作,比如:无人车编队、无人机编队、远程控制等等,那么不同的ROS2设备之间是如何实现通信的呢?

2.概念

分布式通信是指可以通过网络在不同主机之间实现数据交互的一种通信策略。

ROS2本身是一个分布式通信框架,可以很方便的实现不同设备之间的通信,ROS2所基于的中间件是DDS,当处于同一网络中时,通过DDS的域ID机制(ROS_DOMAIN_ID)可以实现分布式通信,大致流程是:在启动节点之前,可以设置域ID的值,不同节点如果域ID相同,那么可以自由发现并通信,反之,如果域ID值不同,则不能实现。默认情况下,所有节点启动时所使用的域ID为0,换言之,只要保证在同一网络,你不需要做任何配置,不同ROS2设备上的不同节点即可实现分布式通信。

3.作用

分布式通信的应用场景是较为广泛的,如上所述:机器人编队时,机器人可能需要获取周边机器人的速度、位置、运行轨迹的相关信息,远程控制时,则可能需要控制端获取机器人采集的环境信息并下发控制指令...... 这些数据的交互都依赖于分布式通信。

4.实现

多机通信时,可以通过域ID对节点进行分组,组内的节点之间可以自由通信,不同组之间的节点则不可通信。如果所有节点都属于同一组,那么直接使用默认域ID即可,如果要将不同节点划分为多个组,那么可以在终端中启动节点前设置该节点的域ID(比如设置为6),具体执行命令为:

export ROS_DOMAIN_ID=6

上述指令执行后,该节点将被划分到ID为6的域内。如果要为当前设备下的所有节点设置统一的域ID,那么可以执行如下指令:

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

执行完毕后再重新启动终端,运行的所有节点将自动被划分到ID为6的域内。

5.注意

在设置ROS_DOMAIN_ID的值时并不是随意的,也是有一定约束的:

  1. 建议ROS_DOMAIN_ID的取值在[0,101] 之间,包含0和101;
  2. 每个域ID内的节点总数是有限制的,需要小于等于120个;
  3. 如果域ID为101,那么该域的节点总数需要小于等于54个。

6.DDS 域 ID 值的计算规则

域ID值的相关计算规则如下:

  1. DDS是基于TCP/IP或UDP/IP网络通信协议的,网络通信时需要指定端口号,端口号由2个字节的无符号整数表示,其取值范围在[0,65535]之间;
  2. 端口号的分配也是有其规则的,并非可以任意使用的,根据DDS协议规定以7400作为起始端口,也即可用端口为[7400,65535],又已知按照DDS协议默认情况下,每个域ID占用250个端口,那么域ID的个数为:(65535-7400)/250 = 232(个),对应的其取值范围为[0,231];
  3. 操作系统还会设置一些预留端口,在DDS中使用端口时,还需要避开这些预留端口,以免使用中产生冲突,不同的操作系统预留端口又有所差异,其最终结果是,在Linux下,可用的域ID为[0,101]与[215-231],在Windows和Mac中可用的域ID为[0,166],综上,为了兼容多平台,建议域ID在[0,101] 范围内取值。
  4. 每个域ID默认占用250个端口,且每个ROS2节点需要占用两个端口,另外,按照DDS协议每个域ID的端口段内,第1、2个端口是Discovery Multicast端口与User Multicast端口,从第11、12个端口开始是域内第一个节点的Discovery Unicast端口与User Unicast,后续节点所占用端口依次顺延,那么一个域ID中的最大节点个数为:(250-10)/2 = 120(个);
  5. 特殊情况:域ID值为101时,其后半段端口属于操作系统的预留端口,其节点最大个数为54个。

上述计算规则了解即可。

附:

cpp 复制代码
域 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

二、工作空间覆盖

1.场景

同一工作空间下不允许出现功能包重名的情况,但是当存在多个工作空间时,不同工作空间下的功能包是可以重名的,那么当功能包重名时,会调用哪一个呢?

比如:自定义工作空间A存在功能包turtlesim,自定义工作空间B也存在功能包turtlesim,当然系统自带工作空间也存在turtlesim,如果调用turtlesim包,会调用哪个工作空间中的呢?

2.概念

所谓工作空间覆盖,是指不同工作空间存在重名功能包时,重名功能包的调用会产生覆盖的情况。

3.作用

没什么用,这种情况是需要极力避免出现的。

4.演示

1.分别在不同的工作空间下创建turtlesim功能包。

终端下进入ws00_helloworld的src目录,新建功能包:

ros2 pkg create turtlesim --node-name turtlesim_node

为了方便查看演示结果,将默认生成的 turtlesim_node.cpp 中的打印内容修改为:ws00_helloworld turtlesim\n

终端下进入ws01_plumbing的src目录,新建功能包:

ros2 pkg create turtlesim --node-name turtlesim_node

为了方便查看演示结果,将默认生成的 turtlesim_node.cpp 中的打印内容修改为:ws01_plumbing turtlesim\n

2.在 ~/.bashrc 文件下追加如下内容:

source /home/ros2/ws00_helloworld/install/setup.bash
source /home/ros2/ws01_plumbing/install/setup.bash

修改完毕后,保存并关闭文件。

3.新建终端,输入如下指令:

ros2 run turtlesim turtlesim_node

输出结果为:ws01_plumbing turtlesim,也即执行的是 ws01_plumbing 功能包下的 turtlesim,而 ws00_helloworld 下的 turtlesim 与内置的 turtlesim 被覆盖了。

5.原因

这与~/.bashrc中不同工作空间的setup.bash文件的加载顺序有关:

1.ROS2 会解析 ~/.bashrc 文件,并生成全局环境变量 AMENT_PREFIX_PATH 与 PYTHONPATH,两个环境变量取值分别对应了 ROS2 中 C++ 和 Python 功能包,环境变量的值由功能包名称组成;

2.两个变量的值的设置与 ~/.bashrc 中的 setup.bash 的配置顺序有关,对于自定义的工作空间而言,后配置的优先级更高,主要表现在后配置的工作空间的功能包在环境变量值组成的前部,而前配置工作空间的功能包在环境变量值组成的后部分,如果更改两个自定义工作空间在 ~/.bashrc 中的配置顺序,那么变量值也将相应更改,但是 ROS2 系统工作空间的配置始终处于最后。

3.调用功能包时,会按照 AMENT_PREFIX_PATH 或 PYTHONPATH 中包配置顺序从前往后依次查找相关功能包,查找到功能包时会停止搜索,也即配置在前的会优先执行。

6.隐患

前面提到,工作空间覆盖的情况是需要极力避免出现的,因为导致一些安全隐患:

  1. 可能会出现功能包调用混乱,出现实际调用与预期调用结果不符的情况;
  2. 即便可以通过 ~/.bashrc 来配置不同工作空间的优先级,但是经过测试,修改 ~/.bashrc 文件之后不一定马上生效,还需要删除工作空间下build与install目录重新编译,才能生效,这个过程繁琐且有不确定性。

综上,在实际工作中,需要制定明确的包命名规范,避免包重名情况。

三、元功能包

3.1场景

完成一个系统性的功能,可能涉及到多个功能包,比如实现了机器人导航模块,该模块下有地图、定位、路径规划...等不同的子级功能包。那么调用者安装该模块时,需要逐一的安装每一个功能包吗?

显而易见的,逐一安装功能包的效率低下,在ROS2中,提供了一种方式可以将不同的功能包打包成一个功能包,当安装某个功能模块时,直接调用打包后的功能包即可,该包又称之为元功能包(metapackage)。

3.2概念

MetaPackage是Linux的一个文件管理系统的概念。是 ROS2 中的一个虚包,里面没有实质性的内容,但是它依赖了其他的软件包,通过这种方法可以把其他包组合起来,我们可以认为它是一本书的目录索引,告诉我们这个包集合中有哪些子包,并且该去哪里下载。

例如:

cpp 复制代码
sudo apt install ros-<ros2-distro>-desktop 
//命令安装 ros2 时就使用了元功能包,该元功能包依赖于 ROS2 中的其他一些功能包,安装该包时会一并安装依赖。
3.3作用

方便用户的安装,我们只需要这一个包就可以把其他相关的软件包组织到一起安装了。

3.4实现

1.新建一个功能包

ros2 pkg create tutorails_plumbing

2.修改 package.xml 文件,添加执行时所依赖的包:

XML 复制代码
<?xml version="1.0"?>
<?xml-model href="http://download.ros.org/schema/package_format3.xsd" schematypens="http://www.w3.org/2001/XMLSchema"?>
<package format="3">
  <name>tutorails_plumbing</name>
  <version>0.0.0</version>
  <description>TODO: Package description</description>
  <maintainer email="ros2@todo.todo">ros2</maintainer>
  <license>TODO: License declaration</license>

  <buildtool_depend>ament_cmake</buildtool_depend>

  <exec_depend>base_interfaces_demo</exec_depend>
  <exec_depend>cpp01_topic</exec_depend>
  <exec_depend>cpp02_service</exec_depend>
  <exec_depend>cpp03_action</exec_depend>
  <exec_depend>cpp04_param</exec_depend>
  <exec_depend>py01_topic</exec_depend>
  <exec_depend>py02_service</exec_depend>
  <exec_depend>py03_action</exec_depend>
  <exec_depend>py04_param</exec_depend>


  <export>
    <build_type>ament_cmake</build_type>
  </export>
</package>

3.文件CMakeLists.txt内容如下:

bash 复制代码
cmake_minimum_required(VERSION 3.8)
project(tutorails_plumbing)

if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")
  add_compile_options(-Wall -Wextra -Wpedantic)
endif()

find_package(ament_cmake REQUIRED)

ament_package()

四、节点重名

4.1问题描述

在 ROS2 中不同的节点可以有相同的节点名称,比如可以启动多个 turtlesim_node 节点,这些节点的名称都是 turtlesim。节点重名虽然是被允许的,但是开发者应该主动避免这种情况,因为节点重名时可能会导致操作上的混淆,仍以启动了多个 turtlesim_node 节点为例,当使用计算图(rqt_graph)查看节点运行状态时,由于他们的节点名称一致,那么虽然实际有多个节点,但是在计算图上显示一个。并且节点名称也会和话题名称、服务名称、动作名称、参数等产生关联,届时也可能会导致通信逻辑上的混乱。

那么在 ROS2 中如何避免节点重名呢?

4.2解决思路

避免重名问题,一般有两种策略:

  1. 名称重映射,也即为节点起别名;
  2. 命名空间,是为节点名称添加前缀,可以有多级,格式:/xxx/yyy/zzz。

这也是在 ROS2 中解决重名问题的常用策略。

4.3解决方案

上述两种策略的实现途径主要有如下三种:

  1. ros2 run 命令实现;
  2. launch 文件实现;
  3. 编码实现。

4.4ros2 run设置节点名称

4.4.1.ros2 run设置命名空间
1.1设置命名空间演示

**语法:**ros2 run 包名 节点名 --ros-args --remap __ns:=命名空间

示例:

ros2 run turtlesim turtlesim_node --ros-args --remap __ns:=/t1
1.2运行结果

使用ros2 node list查看节点信息,显示结果:

/t1/turtlesim
4.4.2.ros2 run名称重映射
2.1为节点起别名

语法:

bash 复制代码
ros2 run 包名 节点名 --ros-args --remap __name:=新名称
或
ros2 run 包名 节点名 --ros-args --remap __node:=新名称

示例:

ros2 run turtlesim turtlesim_node --ros-args --remap __name:=turtle1
2.2运行结果

使用ros2 node list查看节点信息,显示结果:

/turtle1
4.4.3.ros2 run命名空间与名称重映射叠加
3.1设置命名空间同时名称重映射

语法: ros2 run 包名 节点名 --ros-args --remap __ns:=新名称 --remap __name:=新名称

ros2 run turtlesim turtlesim_node --ros-args --remap __ns:=/t1 --remap __name:=turtle1
3.2运行结果

使用ros2 node list查看节点信息,显示结果:

/t1/turtle1

4.4launch设置节点名称

在ROS2中launch文件可以由Python、XML或YAML三种语言编写,每种实现方式都可以设置节点的命名空间或为节点起别名。

1.Python方式实现的launch文件设置命名空间与名称重映射

在 Python 方式实现的 launch 文件中,可以通过类 launch_ros.actions.Node来创建被启动的节点对象,在对象的构造函数中提供了 name 和 namespace 参数来设置节点的名称与命名空间,使用示例如下:

python 复制代码
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")
    ])

2.XML方式实现的launch文件设置命名空间与名称重映射

在 XML 方式实现的 launch 文件中,可以通过 node 标签中 name 和 namespace 属性来设置节点的名称与命名空间,使用示例如下:

XML 复制代码
<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>

3.YAML方式实现的launch文件设置命名空间与名称重映射

在 YAML 方式实现的 launch 文件中,可以通过 node 属性中 name 和 namespace 属性来设置节点的名称与命名空间,使用示例如下:

bash 复制代码
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

4.测试

上述三种方式在设置命名空间与名称重映射时虽然语法不同,但是实现功能类似,都是启动了三个 turtlesim_node 节点,第一个节点设置了节点名称,第二个节点设置了命名空间,第三个节点既设置了命名空间又设置了节点名称,分别执行三个launch文件,然后使用ros2 node list查看节点信息,显示结果都如下所示:

/t1/turtl1
/t1/turtlesim
/turtle1

4.5编码设置节点名称

在 rclcpp 和 rclpy 中,节点类的构造函数中,都分别提供了设置节点名称与命名空间的参数。

1.rclcpp中的相关API

rclcpp中节点类的构造函数如下:

Node (const std::string &node_name, const NodeOptions &options=NodeOptions())
Node (const std::string &node_name, const std::string &namespace_, const NodeOptions &options=NodeOptions())

构造函数1中可以直接通过node_name设置节点名称,构造函数2既可以通过node_name设置节点名称也可以通过namespace_设置命名空间。

2.rclpy中的相关API

rclpy中节点类的构造函数如下:

cpp 复制代码
Node(node_name, *,
   context=None,
   cli_args=None,
   namespace=None, 
   use_global_arguments=True, 
   enable_rosout=True, 
   start_parameter_services=True, 
   parameter_overrides=None, 
   allow_undeclared_parameters=False, 
   automatically_declare_parameters_from_overrides=False)

构造函数中可以使用node_name设置节点名称,namespace设置命名空间。

五、话题重名

六、时间相关API

6.1 Rate

6.1.1 rclcpp 中的 Rate

**示例:**周期性输出一段文本。

cpp 复制代码
#include "rclcpp/rclcpp.hpp"

using namespace std::chrono_literals;

int main(int argc, char ** argv)
{
  rclcpp::init(argc,argv);
  auto node = rclcpp::Node::make_shared("rate_demo");
  // rclcpp::Rate rate(1000ms); // 创建 Rate 对象方式1
  rclcpp::Rate rate(1.0); // 创建 Rate 对象方式2
  while (rclcpp::ok())
  {
    RCLCPP_INFO(node->get_logger(),"hello rate");
    // 休眠
    rate.sleep();
  }

  rclcpp::shutdown();
  return 0;
}
6.1.2 rclpy 中的 Rate

rclpy 中的 Rate 对象可以通过节点创建,Rate 对象的 sleep() 函数需要在子线程中执行,否咋会阻塞程序。

**示例:**周期性输出一段文本。

python 复制代码
import rclpy
import threading
from rclpy.timer import Rate

rate = None
node = None

def do_some():
    global rate
    global node
    while rclpy.ok():
        node.get_logger().info("hello ---------")
        # 休眠
        rate.sleep()

def main():
    global rate
    global node
    rclpy.init()    
    node = rclpy.create_node("rate_demo")
    # 创建 Rate 对象
    rate = node.create_rate(1.0)

    # 创建子线程
    thread = threading.Thread(target=do_some)
    thread.start()

    rclpy.shutdown()

if __name__ == "__main__":
    main()

6.2 Time

6.2.1.rclcpp 中的 Time

**示例:**创建 Time 对象,并调用其函数。

cpp 复制代码
#include "rclcpp/rclcpp.hpp"

int main(int argc, char const *argv[])
{
    rclcpp::init(argc,argv);
    auto node = rclcpp::Node::make_shared("time_demo");

    // 创建 Time 对象
    rclcpp::Time t1(10500000000L);
    rclcpp::Time t2(2,1000000000L);
    // 通过节点获取当前时刻。
    // rclcpp::Time roght_now = node->get_clock()->now();
    rclcpp::Time roght_now = node->now();
    RCLCPP_INFO(node->get_logger(),"s = %.2f, ns = %ld",t1.seconds(),t1.nanoseconds());
    RCLCPP_INFO(node->get_logger(),"s = %.2f, ns = %ld",t2.seconds(),t2.nanoseconds());
    RCLCPP_INFO(node->get_logger(),"s = %.2f, ns = %ld",roght_now.seconds(),roght_now.nanoseconds());

    rclcpp::shutdown();

    return 0;
}
6.2.2.rclpy 中的 Time

**示例:**创建 Time 对象,并调用其函数。

python 复制代码
import rclpy
from rclpy.time import Time
def main():
    rclpy.init()
    node = rclpy.create_node("time_demo")
    # 创建 Time 对象
    right_now = node.get_clock().now()
    t1 = Time(seconds=10,nanoseconds=500000000)

    node.get_logger().info("s = %.2f, ns = %d" % (right_now.seconds_nanoseconds()[0], right_now.seconds_nanoseconds()[1]))
    node.get_logger().info("s = %.2f, ns = %d" % (t1.seconds_nanoseconds()[0], t1.seconds_nanoseconds()[1]))
    node.get_logger().info("ns = %d" % right_now.nanoseconds)
    node.get_logger().info("ns = %d" % t1.nanoseconds)
    rclpy.shutdown()

if __name__ == "__main__":
    main()

6.3 Duration

6.3.1 rclcpp 中的 Duration

**示例:**创建 Duration 对象,并调用其函数。

cpp 复制代码
#include "rclcpp/rclcpp.hpp"

using namespace std::chrono_literals;

int main(int argc, char const *argv[])
{
    rclcpp::init(argc,argv);
    auto node = rclcpp::Node::make_shared("duration_node");

    // 创建 Duration 对象
    rclcpp::Duration du1(1s);
    rclcpp::Duration du2(2,500000000);

    RCLCPP_INFO(node->get_logger(),"s = %.2f, ns = %ld", du2.seconds(),du2.nanoseconds());

    rclcpp::shutdown();
    return 0;
}
6.3.2 rclpy 中的 Duration

**示例:**创建 Duration 对象,并调用其函数。

python 复制代码
import rclpy
from rclpy.duration import Duration

def main():
    rclpy.init()    

    node = rclpy.create_node("duration_demo")
    du1 = Duration(seconds = 2,nanoseconds = 500000000)
    node.get_logger().info("ns = %d" % du1.nanoseconds)

    rclpy.shutdown()

if __name__ == "__main__":

    main()

6.4 Time 与 Duration 运算

6.4.1 rclcpp 中的运算

**示例:**Time 以及 Duration 的相关运算。

cpp 复制代码
#include "rclcpp/rclcpp.hpp"

int main(int argc, char const *argv[])
{
    rclcpp::init(argc,argv);
    auto node = rclcpp::Node::make_shared("time_opt_demo");

    rclcpp::Time t1(1,500000000);
    rclcpp::Time t2(10,0);

    rclcpp::Duration du1(3,0);
    rclcpp::Duration du2(5,0);

    // 比较
    RCLCPP_INFO(node->get_logger(),"t1 >= t2 ? %d",t1 >= t2);
    RCLCPP_INFO(node->get_logger(),"t1 < t2 ? %d",t1 < t2);
    // 数学运算
    rclcpp::Time t3 = t2 + du1;
    rclcpp::Time t4 = t1 - du1;
    rclcpp::Duration du3 = t2 - t1;

    RCLCPP_INFO(node->get_logger(), "t3 = %.2f",t3.seconds());  
    RCLCPP_INFO(node->get_logger(), "t4 = %.2f",t4.seconds()); 
    RCLCPP_INFO(node->get_logger(), "du3 = %.2f",du3.seconds()); 

    RCLCPP_INFO(node->get_logger(),"--------------------------------------");
    // 比较
    RCLCPP_INFO(node->get_logger(),"du1 >= du2 ? %d", du1 >= du2);
    RCLCPP_INFO(node->get_logger(),"du1 < du2 ? %d", du1 < du2);
    // 数学运算
    rclcpp::Duration du4 = du1 * 3.0;
    rclcpp::Duration du5 = du1 + du2;
    rclcpp::Duration du6 = du1 - du2;

    RCLCPP_INFO(node->get_logger(), "du4 = %.2f",du4.seconds()); 
    RCLCPP_INFO(node->get_logger(), "du5 = %.2f",du5.seconds()); 
    RCLCPP_INFO(node->get_logger(), "du6 = %.2f",du6.seconds()); 

    rclcpp::shutdown();
    return 0;
}
6.4.2 rclpy 中的运算

**示例:**Time 以及 Duration 的相关运算。

python 复制代码
import rclpy
from rclpy.time import Time
from rclpy.duration import Duration

def main():
    rclpy.init()
    node = rclpy.create_node("time_opt_node")
    t1 = Time(seconds=10)
    t2 = Time(seconds=4)

    du1 = Duration(seconds=3)
    du2 = Duration(seconds=5)

    # 比较
    node.get_logger().info("t1 >= t2 ? %d" % (t1 >= t2))
    node.get_logger().info("t1 < t2 ? %d" % (t1 < t2))
    # 数学运算
    t3 = t1 + du1
    t4 = t1 - t2    
    t5 = t1 - du1

    node.get_logger().info("t3 = %d" % t3.nanoseconds)
    node.get_logger().info("t4 = %d" % t4.nanoseconds)
    node.get_logger().info("t5 = %d" % t5.nanoseconds)

    # 比较
    node.get_logger().info("-" * 80)
    node.get_logger().info("du1 >= du2 ? %d" % (du1 >= du2))
    node.get_logger().info("du1 < du2 ? %d" % (du1 < du2))

    rclpy.shutdown()

if __name__ == "__main__":
    main()

七、通信机制工具

7.1 命令工具

ROS2中常用的命令如下:

  • ros2 node:节点相关命令行工具
  • ros2 interface:接口(msg、srv、action)消息相关的命令行工具
  • ros2 topic:话题通信相关的命令行工具
  • ros2 service:服务通信相关的命令行工具
  • ros2 action:动作通信相关的命令行工具
  • ros2 param:参数服务相关的命令行工具

关于命令的使用一般都会提供帮助文档,帮助文档的获取方式如下:

python 复制代码
可以通过命令 -h 或 命令 --help的方式查看命令帮助文档,
比如:ros2 node -h或 ros2 node --help。

命令下参数的使用也可以通过命令 
参数 -h 或 命令 参数 --help的方式查看帮助文档,
比如:ros2 node list -h或 ros2 node list --help。
7.1.1.ros2 node

ros2 node的基本使用语法如下:

info  输出节点信息
list  输出运行中的节点的列表
7.1.2.ros2 interace

os2 interace的基本使用语法如下:

list      输出所有可用的接口消息
package   输出指定功能包下的
packages  输出包含接口消息的功能包
proto     输出接口消息原型
show      输出接口消息定义格式
7.1.3.ros2 topic

ros2 topic的基本使用语法如下:

bw       输出话题消息传输占用的带宽
delay    输出带有 header 的话题延迟
echo     输出某个话题下的消息
find     根据类型查找话题
hz       输出消息发布频率
info     输出话题相关信息
list     输出运行中的话题列表
pub      向指定话题发布消息
type     输出话题使用的接口类型
7.1.4.ros2 service

ros2 service的基本使用语法如下:

call  向某个服务发送请求
find  根据类型查找服务
list  输出运行中的服务列表
type  输出服务使用的接口类型
7.1.5.ros2 action

ros2 action的基本使用语法如下:

info       输出指定动作的相关信息
list       输出运行中的动作的列表
send_goal  向指定动作发送请求
7.1.6.ros2 param

ros2 param的基本使用语法如下:

delete    删除参数
describe  输出参数的描述信息
dump      将节点参数写入到磁盘文件
get       获取某个参数
list      输出可用的参数的列表
load      从磁盘文件加载参数到节点
set       设置参数

7.2 rqt工具箱

介绍ROS2中rqt工具箱的使用,比如:rqt的安装、启动与插件使用等。

7.2.1.安装
python 复制代码
$ sudo apt install ros-humble-rqt*
7.2.2.启动
python 复制代码
常用的rqt启动命令有:
方式1:rqt
方式2:ros2 run rqt_gui rqt_gui
7.2.3.插件使用

启动rqt之后,可以通过plugins添加所需的插件:

在plugins中包含了话题、服务、动作、参数、日志等等相关的插件,我们可以按需选用,方便的实现ROS2程序调试。使用示例如下。

1.topic 插件

添加topic插件并发送速度指令控制乌龟运动。

2.service插件

添加 service 插件并发送请求,在制定位置生成一只乌龟。

3.参数插件

通过参数插件动态修改乌龟窗体背景颜色。


下期见!

相关推荐
cloud studio AI应用2 分钟前
腾讯云 AI 代码助手:产品研发过程的思考和方法论
人工智能·云计算·腾讯云
cherub.6 分钟前
深入解析信号量:定义与环形队列生产消费模型剖析
linux·c++
Suyuoa8 分钟前
附录2-pytorch yolov5目标检测
python·深度学习·yolo
禁默13 分钟前
第六届机器人、智能控制与人工智能国际学术会议(RICAI 2024)
人工智能·机器人·智能控制
暮色_年华20 分钟前
Modern Effective C++item 9:优先考虑别名声明而非typedef
c++
Robot25121 分钟前
浅谈,华为切入具身智能赛道
人工智能
只怕自己不够好26 分钟前
OpenCV 图像运算全解析:加法、位运算(与、异或)在图像处理中的奇妙应用
图像处理·人工智能·opencv
重生之我是数学王子28 分钟前
QT基础 编码问题 定时器 事件 绘图事件 keyPressEvent QT5.12.3环境 C++实现
开发语言·c++·qt
梅见十柒29 分钟前
wsl2中kali linux下的docker使用教程(教程总结)
linux·经验分享·docker·云原生
Koi慢热32 分钟前
路由基础(全)
linux·网络·网络协议·安全