ros2学习记录

一键脚本安装 ROS2

在终端中执行以下命令即可一键安装 ROS2(适用于 Ubuntu):

1
wget http://fishros.com/install -O fishros && . fishros

该脚本由 Fishros 社区维护,支持多种 ROS2 版本的自动安装,适合新手快速部署环境。

检查 ROS2 是否安装成功

安装完成后,可以通过运行 ROS2 自带的小乌龟仿真程序来检测环境是否正常。

  1. 启动 turtlesim 仿真窗口:

    1
    ros2 run turtlesim turtlesim_node

    执行该命令后,会弹出一个小乌龟的图形窗口,说明 ROS2 及其 GUI 组件安装成功。

  2. 启动键盘控制节点:

    1
    ros2 run turtlesim turtle_teleop_key

    在弹出的终端窗口中,使用键盘的方向键即可控制小乌龟移动。

如果上述命令均能正常运行,说明 ROS2 安装和基本环境配置已经完成。

更多 ROS2 控制海龟的常用指令

1. 控制小乌龟移动

可以通过发布速度话题控制小乌龟运动。例如,让 turtle1 以一定速度前进并旋转:

1
ros2 topic pub --rate 1 /turtle1/cmd_vel geometry_msgs/msg/Twist "{linear: {x: 2.0, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: 1.8}}"

此命令每秒向 /turtle1/cmd_vel 话题发布一次速度消息,让小乌龟前进并旋转。

2. 生成新小乌龟

可以通过 service 调用生成新的小乌龟:

1
ros2 service call /spawn turtlesim/srv/Spawn "{x: 5.0, y: 5.0, theta: 0.0, name: 'new_turtle'}"

执行后会在指定位置生成名为 new_turtle 的新小乌龟。

3. 查看所有话题

1
ros2 topic list

你会看到类似如下输出:

1
2
3
4
5
6
7
8
/new_turtle/cmd_vel
/new_turtle/color_sensor
/new_turtle/pose
/parameter_events
/rosout
/turtle1/cmd_vel
/turtle1/color_sensor
/turtle1/pose

4. 控制新生成的小乌龟

1
ros2 topic pub --rate 1 /new_turtle/cmd_vel geometry_msgs/msg/Twist "{linear: {x: 2.0, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: 1.8}}"

此命令与控制 turtle1 类似,只不过目标变成了 new_turtle。

5. 录制话题数据

1
ros2 bag record -a -o my_bag

该命令会录制所有话题的数据,便于后续分析和回放。-o my_bag 可自定义录制包名称。

6. 播放录制的数据包

录制完成后,可以通过如下命令回放数据包:

1
ros2 bag play <bag文件夹名>

例如,如果你录制的数据包保存在名为 my_bag 的文件夹下,可以这样播放:

1
ros2 bag play my_bag

回放时,所有被录制的话题数据会按原始时间顺序重新发布,非常适合调试和仿真。


开发工具安装与配置

1. 一键脚本安装 VSCode

Fishros 社区提供了一键安装 VSCode 的脚本,适用于 Ubuntu 系统。执行以下命令即可自动下载安装:

1
wget http://fishros.com/install -O fishros && . fishros

在脚本交互界面中选择 VSCode 相关选项即可完成安装。

2. 推荐安装的 VSCode 插件

为了更好地进行 ROS2 及机器人开发,建议在 VSCode 扩展商店安装以下插件:

  • C/C++(微软官方,支持 C/C++ 语法高亮、智能提示、调试等)
  • Python(微软官方,支持 Python 语法、调试、虚拟环境等)
  • CMake Tools(CMake 项目管理与编译支持)
  • Intellicode(AI 智能代码补全)
  • Markdown All in One(Markdown 编辑增强)
  • msg support(ROS 消息文件语法高亮)
  • ROS(官方 ROS 扩展,支持 ROS/ROS2 工作区、调试、launch 文件等)
  • URDF(URDF 机器人模型文件支持)
  • vscode-icons(美化文件图标)
  • Chinese (Simplified) Language Pack for Visual Studio Code(中文汉化包)

插件安装方法

  1. 打开 VSCode,点击左侧扩展(Extensions)图标,或按 Ctrl+Shift+X
  2. 在搜索框中输入插件名称,点击“安装”即可。

建议优先安装 ROS、C++、Python、CMake、msg support、URDF 等与机器人开发密切相关的插件,提升开发效率和体验。


ROS2 工作空间与功能包创建

1. 新建工作空间及源码文件夹

1
2
mkdir -p ~/DevWs/src
cd ~/DevWs/src

2. 克隆示例教程包

1
2
git clone https://gitee.com/guyuehome/ros2_21_tutorials.git
cd ..

3. 安装依赖与构建工具

1
2
3
4
sudo apt install python3-pip
wget http://fishros.com/install -O fishros && . fishros # 小雨脚本安装rosdepc
rosdepc install -i --from-path src --rosdistro humble -y
sudo apt install python3-colcon-ros

4. 编译工作空间

注意:编译时需在 src 的上级目录(即 ~/DevWs)下执行 colcon build,否则无法正确识别和编译所有包。

1
colcon build

5. 添加环境变量

建议将以下命令添加到 ~/.bashrc 文件末尾,实现每次终端自动加载:

1
2
echo "source ~/DevWs/install/local_setup.sh" >> ~/.bashrc
source ~/.bashrc

6. 创建功能包

进入 src 目录后,使用如下命令创建功能包:

  • 创建 C++ 功能包:

    1
    ros2 pkg create --build-type ament_cmake learning_pkg_c
  • 创建 Python 功能包:

    1
    ros2 pkg create --build-type ament_python learning_pkg_py

以上为 ROS2 工作空间的标准搭建流程及功能包创建方法,适合初学者快速上手。

7. 运行功能包中的节点

编译并配置好环境变量后,可以直接运行功能包中的节点。例如:

1
ros2 run learning_node node_helloworld

注意:每次新开终端或编译后,务必先执行 source ~/DevWs/install/local_setup.sh,否则找不到新建的包和节点。

这样即可在 ~/DevWs/src 目录下运行你自己编写的 ROS2 节点程序。


节点

ROS2 节点是系统中最基本的执行单元。一个节点通常包括:

  • 编程接口初始化
  • 创建节点并初始化
  • 实现节点功能
  • 销毁节点并关闭接口

下面是一个最简单的 Python 节点示例(面向过程实现):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
@作者: 古月居(www.guyuehome.com)
@说明: ROS2节点示例-发布“Hello World”日志信息, 使用面向对象的实现方式
"""
import rclpy # ROS2 Python接口库
from rclpy.node import Node # ROS2 节点类
import time
"""
创建一个HelloWorld节点, 初始化时输出“hello world”日志
"""
class HelloWorldNode(Node):
def __init__(self, name):
super().__init__(name) # ROS2节点父类初始化
while rclpy.ok(): # ROS2系统是否正常运行
self.get_logger().info("Hello World") # ROS2日志输出
time.sleep(0.5) # 休眠控制循环时间

def main(args=None): # ROS2节点主入口main函数
rclpy.init(args=args) # ROS2 Python接口初始化
node = HelloWorldNode("node_helloworld_class") # 创建ROS2节点对象并进行初始化
node.destroy_node() # 销毁节点对象
rclpy.shutdown() # 关闭ROS2 Python接口

setup.py 配置

在 Python 功能包的 setup.py 文件中,需要通过 entry_points 指定可执行节点,否则无法正常编译和运行。例如:

1
2
3
4
5
6
7
8
entry_points={
'console_scripts': [
'node_helloworld = learning_node.node_helloworld:main',
'node_helloworld_class = learning_node.node_helloworld_class:main',
'node_object = learning_node.node_object:main',
'node_object_webcam = learning_node.node_object_webcam:main',
],
},

注意:每新增一个可执行节点,都要在 entry_points 中进行注册,否则无法通过 ros2 run 命令运行。


ROS2话题

在机器人操作系统(ROS2)中,话题(Topic) 是节点间通信的核心机制。它如同一座桥梁,允许不同的节点通过发布(Publish)和订阅(Subscribe)消息来进行数据交换。这种松耦合的通信方式是构建复杂机器人系统的基石。

  • 发布者(Publisher):负责向特定话题发送消息的节点。
  • 订阅者(Subscriber):负责从特定话题接收消息的节点。

本文将详细介绍如何使用 Python 编写 ROS2 节点,创建话题的发布者和订阅者,并实现完整的消息通信流程。

核心编程步骤

无论是创建发布者还是订阅者,其基本流程都遵循相似的步骤:初始化、创建节点、执行核心逻辑、保持运行以及最后的资源清理。

1. 初始化 ROS2 客户端库

任何 ROS2 程序的第一步都是初始化 rclpy(ROS Client Library for Python)。这一步将启动 ROS2 的底层通信系统。

1
2
3
4
import rclpy

# 初始化 rclpy
rclpy.init()

2. 创建节点(Node)

节点是 ROS2 网络中的基本执行单元。每个节点都必须有一个在整个 ROS2 系统中唯一的名字。我们通常通过继承 rclpy.node.Node 来创建自定义的节点类。

1
2
3
4
5
6
from rclpy.node import Node

class MyNode(Node):
def __init__(self, node_name):
# 调用父类构造函数并传入节点名
super().__init__(node_name)

在主程序中,我们可以实例化这个节点:

1
2
# 创建一个名为 "my_first_node" 的节点
node = MyNode("my_first_node")

3. 创建发布者(Publisher)

要在节点中发布消息,需要创建一个发布者对象。使用 create_publisher() 方法,并指定三个关键参数:

  1. 消息类型:定义了话题上传输的数据结构。例如 std_msgs.msg.String
  2. 话题名称:消息发布的目标地址。例如 "chatter"
  3. 队列大小(QoS):服务质量(Quality of Service)设置的一部分,用于处理网络拥堵时消息的缓冲。
1
2
3
4
5
from std_msgs.msg import String

# 在节点类 __init__ 方法中创建发布者
# 消息类型为 String,话题名为 "chatter",队列大小为 10
self.publisher_ = self.create_publisher(String, 'chatter', 10)
创建并发布消息

创建发布者后,您可以构造相应类型的消息对象,填充数据,并通过 publish() 方法将其发送出去。

1
2
3
4
5
6
7
8
# 1. 创建消息对象
msg = String()
# 2. 填充消息内容
msg.data = 'Hello, ROS2 World!'
# 3. 发布消息
self.publisher_.publish(msg)
# 4. 记录日志
self.get_logger().info(f'Publishing: "{msg.data}"')

4. 创建订阅者(Subscriber)

与发布者类似,订阅者通过 create_subscription() 方法创建,但需要额外指定一个参数:

  1. 消息类型:必须与发布者的消息类型一致。
  2. 话题名称:必须与发布者发布的话题名称一致。
  3. 回调函数:当收到消息时,系统会自动调用的函数。
  4. 队列大小(QoS):与发布者类似。
1
2
3
4
5
6
# 在节点类 __init__ 方法中创建订阅者
self.subscription_ = self.create_subscription(
String,
'chatter',
self.listener_callback, # 指定回调函数
10)
创建回调函数

回调函数是订阅者的核心。每当有新消息到达时,该函数就会被执行,并将接收到的消息作为参数传入。

1
2
3
4
5
def listener_callback(self, msg):
"""
当接收到话题 "chatter" 上的消息时,此函数被调用。
"""
self.get_logger().info(f'I heard: "{msg.data}"')

5. 保持节点运行(Spin)

创建完发布者或订阅者后,节点需要进入一个循环等待状态,以便处理回调、发送消息等事件。rclpy.spin() 会阻塞程序,使节点保持活动状态,直到程序被外部终止(例如按下 Ctrl+C)。

1
2
# 使节点保持运行,等待消息和事件
rclpy.spin(node)

注意:对于纯发布者,有时会使用定时器(create_timer)来周期性地发布消息,而不是简单地自旋。但对于订阅者和需要响应外部事件的节点,spin 是必不可少的。

6. 销毁节点并关闭

在程序结束时,为了优雅地释放资源,应当显式地销毁节点并关闭 rclpy

1
2
3
4
# 销毁节点
node.destroy_node()
# 关闭 rclpy
rclpy.shutdown()

完整示例

下面是一个完整的发布者和订阅者节点的代码示例,您可以将它们分别保存为两个 Python 文件来运行。

发布者节点 (topic_publisher.py)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
import rclpy
from rclpy.node import Node
from std_msgs.msg import String

class HelloWorldPublisher(Node):
"""
一个周期性发布 "Hello World" 消息的节点。
"""
def __init__(self):
super().__init__('hello_world_publisher')
self.publisher_ = self.create_publisher(String, 'chatter', 10)
self.timer_period = 0.5 # 秒
self.timer = self.create_timer(self.timer_period, self.timer_callback)
self.counter = 0

def timer_callback(self):
msg = String()
msg.data = f'Hello World: {self.counter}'
self.publisher_.publish(msg)
self.get_logger().info(f'Publishing: "{msg.data}"')
self.counter += 1

def main(args=None):
rclpy.init(args=args)
publisher_node = HelloWorldPublisher()
try:
rclpy.spin(publisher_node)
except KeyboardInterrupt:
pass
finally:
# 销毁节点并关闭 rclpy
publisher_node.destroy_node()
rclpy.shutdown()

if __name__ == '__main__':
main()

订阅者节点 (topic_subscriber.py)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
import rclpy
from rclpy.node import Node
from std_msgs.msg import String

class HelloWorldSubscriber(Node):
"""
一个订阅 "chatter" 话题并打印消息的节点。
"""
def __init__(self):
super().__init__('hello_world_subscriber')
self.subscription = self.create_subscription(
String,
'chatter',
self.listener_callback,
10)
self.get_logger().info('Subscriber is ready and listening.')

def listener_callback(self, msg):
self.get_logger().info(f'I heard: "{msg.data}"')

def main(args=None):
rclpy.init(args=args)
subscriber_node = HelloWorldSubscriber()
try:
rclpy.spin(subscriber_node)
except KeyboardInterrupt:
pass
finally:
# 销毁节点并关闭 rclpy
subscriber_node.destroy_node()
rclpy.shutdown()

if __name__ == '__main__':
main()

总结

通过本节,我们学习了 ROS2 话题通信的完整编程流程。无论是发布者还是订阅者,核心步骤都围绕着初始化 rclpy -> 创建节点 -> 创建通信组件(发布者/订阅者) -> 运行节点 (spin) -> 清理资源。掌握了这一模式,您就可以轻松地在 ROS2 中构建基于话题的分布式机器人应用程序。
```