ROS2编程基础
博客仅记录个人学习过程
节点和软件包是ROS2程序的组织形式,节点相当于一个ROS2程序文件,软件包是一个包含了一系列配置文件的目录。
一、节点和软件包
1、创建软件包

如上图,已经把 ROS 2 的环境初始化命令写进了终端启动脚本,每次打开终端即可使用ROS2指令。

ros2 pkg create 包名
这个是创建软件包的ros指令(c++默认)。
VSCode打开,有以下文件/文件夹:

include:用于存放头文件的子目录
src:用于存放节点源码文件的子目录
CMakeLists.txt:该软件包的编译规则描述文件
package.xml:软件包的配置信息文件
2、编写节点

3、设置编译规则
打卡CMakeLists.txt文件
先寻找节点源码中用到的rclcpp依赖项

使用如下代码添加节点源码的编译规则

使用如下代码添加节点编译完成后的安装规则

4、修改软件包信息
打卡package.xml文件,修改软件包信息
xml:一种用尖括号标签描述数据的纯文本格式,可以自定义标签

5、编译软件包

6、运行节点
source install/setup.bash
先加载当前工作空间中的环境设置参数

如果不中断,会一直循环。
(slam_toolbox 就是一个 ament_cmake 包,跟 my_pkg 是同一个模子出来的,只是功能多得多。)
二、话题和消息
节点之间的通信最常用方式就是话题和消息,在话题聊天室发言的节点,可以称为发布者,看到发言内容的节点,成为订阅者。
发布者和订阅者所使用的消息格式必须一致。
针对我当前的项目,情况如下:

1、编写话题发布者
(1)创建软件包

(2)编写节点代码

在src下新建文件publisher_node.cpp
编写如下代码:

其中,可以看到一个节点只创建了一个发布者,实际上,可创建多个。
同时,还提到了缓冲队列的概念,针对我目前项目,情况如下:

(3)设置编译规则
在 CMakeLists.txt添加publisher_node节点的编译规则
A.寻找节点源码中用到的依赖项

B.使用如下代码添加节点源码的编译规则

C.最后使用如下代码添加节点编译完成后的安装规则

(4)修改软件包信息
打卡package.xml,添加依赖信息:

(5)编译软件包
cd ~/ros2_ws
colcon build
(6)运行节点


如果关闭了节点程序,那么自然而然,话题也就消失了。

可以看到话题的发布者在不断发送字符串消息,并且间隔1Hz。
2、编写话题订阅者
(1)编写节点代码

(2)设置编译规则

(3)编译软件包
cd ~/ros2_ws
colcon build
(4)运行节点

我从来没设置过 ROS_DOMAIN_ID,为什么发布节点和订阅节点能互相发现?答案是——不设置并不等于”没有 domain”,而是默认取 0。ROS 2 里取值的地方写得很直白:int(os.environ.get(‘ROS_DOMAIN_ID’, 0)),第二个参数就是默认值。两个节点都没设,于是都落在 domain 0,自然就在同一个通信域里。domain 的本质是一组固定的 UDP 端口号,Fast DDS 的换算是 7400 + 250 × domain_id,用 ss -ulnp 去看 domain 0 里某个节点绑定的端口,会发现 7400 赫然在列;而一旦换到 domain 1,整个端口段平移开,两套系统即便在同一台机器、同一个网段上也会互相”看不见”。这正是 domain 存在的意义——当同一个局域网里跑着多套 ROS 2 系统时,大家默认都在 domain 0,话题会串到一起,你可能会收到本来不属于自己的消息,这时给每组分配不同的 ROS_DOMAIN_ID 就能彼此隔离。
一个域分到250个端口,但实际用不到250个。具体多少,需要看有多少进程。同一个话题在两个机器(比如我的Pi和本机)对应的单播数据端口不一定相同,但多播发现端口是相同的。
3、面向对象的节点实现
把节点封装成类是 ROS 2 里的标准写法,核心思想是让节点类直接继承 rclcpp::Node,把原先散落在 main 函数和全局作用域里的东西统统收进类内部:发布者、订阅者成为成员变量,在构造函数里创建;回调函数从自由函数变成成员函数。这样写最直接的收益,是解决了回调函数”够不着”节点的问题——在订阅节点的例子里,之所以需要定义一个全局的 std::shared_ptr,正是因为自由函数的回调拿不到节点对象,只能靠全局变量去调用 get_logger();而改成成员函数之后,this 天然可达,日志器和所有成员变量都能直接访问,那个全局变量可以彻底删掉。其次,main 函数会瘦身成固定的四步——rclcpp::init、构造节点对象、rclcpp::spin、rclcpp::shutdown——程序启动流程与节点自身逻辑彻底分离,节点的构造即初始化、析构即释放,生命周期一目了然。再进一步,类让节点变得可复用、可继承、可测试:话题名和队列长度这类目前硬编码的字面量可以提升为构造参数或成员变量,同一个节点类换个参数就能起第二个实例;发布与订阅也能自然地放进同一个类里做双向通信。封装时有一个必须注意的坑——订阅者、发布者对象一定要保存为成员变量,因为 rclcpp 内部对它们全是弱引用(节点持有的 callback_groups_ 是 CallbackGroup::WeakPtr 的 vector,而回调组里的 subscription_ptrs_ 又是 SubscriptionBase::WeakPtr 的 vector),一旦你只在构造函数里创建成局部变量、把 create_subscription 的返回值丢掉,它们在构造函数结束时就会被析构,订阅随之静默失效:不报错、不告警,只是永远收不到消息。此外,耗时或阻塞的逻辑不要写进构造函数,因为那时 spin 还没开始。
三、Launch文件
Python

XML

YAML

pkg为要启动节点所在软件包的名字。exec为要启动节点的名字。name表示节点启动后,在当前ROS2里的别名,且具有唯一性。
配置下编译:

然后编译。
运行Launch文件,节点同时运行。

….