ROS2命令行操作
运行节点程序
想要运行ROS 2中某个节点,可以使用ros2 run命令进行操作,之后第一个参数表示功能包的名称,第二个参数表示功能包内节点的名称,对于运行海龟仿真节点和键盘控制节点的命令:
python
$ ros2 run turtlesim turtlesim_node
$ ros2 run turtlesim turtle_teleop_key
输出结果:

查看运行节点
python
ros2 node list
输出结果:

查看话题信息
python
ros2 topic list
输出结果:

发布话题信息
python
$ 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}}"
输出结果:

发布服务请求
python
ros2 service call /spawn turtlesim/srv/Spawn "{x: 2, y: 2, theta: 0.2, name: ''}"
输出结果:相对于新开一个模拟海龟

发送动作目标
python
ros2 action send_goal /turtle1/rotate_absolute turtlesim/action/RotateAbsolute "theta: 3"
输出结果:

数据录制与播放
python
#数据录制
$ ros2 bag record /turtle1/cmd_vel
#数据播放 (注意路径和文件名)
$ ros2 bag play rosbag2_2024_07_06-21_21_23/ rosbag2_2024_07_06-21_21_23_0.mcap
输出结果:

工作空间与功能包:开发过程的大本营
什么是工作空间
**工作空间:**是一个存放项目开发相关文件的文件夹;是开发过程的大本营。

src:代码空间未来编写的代码、脚本,都需要人为的放置到这里
install:安装空间保存编译过程中产生的中间文件;
build:编译空间放置编译得到的可执行文件和脚本
log:日志空间编译和运行过程中,保存各种警告、错误、信息等日志
总体来讲,这四个空间的文件夹,我们绝大部分操作都是在src中进行的,编译成功后,就会执行install里边的结果,build和log两个文件夹用的很少。
这里也要强调一点,工作空间的名称我们可以自己定义,数量也并不是唯一的,比如:
工作空间1:dev_w_a,用于A机器人的开发;
工作空间2:dev_ws_b,用于B机器人的一部分功能;
工作空间3:dev_ws_b2,用于开发B机器人另外一些功能。
以上情况是完全允许的,就像是我们在集成开发环境中创建了多个新工程一样,都是并列存在的关系。
创建工作空间
了解了工作空间的概念和结果,接下来我们可以使用如下命令创建一个工作空间,并且下载教程的代码
python
$ mkdir -p ~/dev_ws/src
$ cd ~/dev_ws/src
$ git clone https://gitee.com/guyuehome/ros2_21_tutorials.git
自动安装依赖
在/dev_ws/src目录下,使用rosdep工具自动安装依赖:
python
sudo apt install -y python3-pip
$ sudo pip3 install rosdepc
$ sudo rosdepc init
$ rosdepc update
$ cd ..
$ rosdepc install -i --from-path src --rosdistro humble -y
编译工作空间
依赖安装完成后,就可以使用如下命令编译工作空间啦,如果有缺少的依赖,或者代码有错误,编译过程中会有报错,否则编译过程应该不会出现任何错误:
python
sudo apt install python3-colcon-ros
$ cd ~/dev_ws/
$ colcon build
编译成功后,就可以在工作空间中看到自动生产的build、log、install文件夹了。


设置环境变量
编译成功后,为了让系统能够找到我们的功能包和可执行文件,还需要设置环境变量:
python
source install/local_setup.sh # 仅在当前终端生效
$ echo " source ~/dev_ws/install/local_setup.sh" >> ~/.bashrc # 所有终端均生效
功能包:开发过程的大本营
在下载的教程代码中,大家可以看到有很多不同名称的文件夹,这些在ROS2并不是普通的文件夹,而是叫做功能包。
每个机器人可能有很多功能,比如移动控制、视觉感知、自主导航等,如果我们把这些功能的源码都放到一起当然也是可以的,但是当我们想把其中某些功能分享给别人时,就会发现代码都混合到了一起,很难拆分出来。

比如我们手头有红豆、绿豆和黄豆,如果把它们混装在一个袋子里,想要单独取出黄豆时,就得在五颜六色的豆子中一颗颗翻找------数量越多,操作就越麻烦。但如果把不同颜色的豆子分装到三个袋子里,需要某种豆子时就能直接取出对应的袋子。
功能包的设计正是基于这个原理:我们将不同功能的代码模块划分到独立的功能包中,尽可能减少它们之间的相互依赖。当需要在ROS社区分享时,只需说明功能包的使用方法,其他人就能快速上手使用。
因此,功能包机制是提升ROS软件复用率的重要方式之一。
创建功能包
在ROS2中创建一个功能包
python
ros2 pkg create --build-type <build-type> <package_name>
ros2命令中:
- pkg:表示功能包相关的功能;
- create:表示创建功能包;
- build-type:表示新创建的功能包是C++还是Python的,如果使用C++或者C,那这里就跟ament_cmake,如果使用Python,就跟ament_python;
- package_name:新建功能包的名字。
比如在终端中分别创建C++和Python版本的功能包:
python
$ cd ~/dev_ws/src
$ ros2 pkg create --build-type ament_cmake learning_pkg_c # C++
$ ros2 pkg create --build-type ament_python learning_pkg_python # Python
建立一个c++:

建立一个python:

编译功能包
在创建好的功能包中,我们可以继续完成代码的编写,之后需要编译和配置环境变量,才能正常运行:
python
$ cd ~/dev_ws
$ colcon build # 编译工作空间所有功能包
$ source install/local_setup.bash
输出结果:

功能包的结构
功能包并不是普通的文件夹,那如何判断一个文件夹是否是功能包呢?我们来分析下刚才新创建两个功能包的结构。
C++功能包
首先看下C++类型的功能包,其中必然存在两个文件:package.xml 和CMakerLists.txt。

package.xml文件的主要内容如下,包含功能包的版权描述,和各种依赖的声明。

CMakeLists.txt文件是编译规则,C++代码需要编译才能运行,所以必须要在该文件中设置如何编译,使用CMake语法。

Python功能包
C++功能包需要将源码编译成可执行文件,但是Python语言是解析型的,不需要编译,所以会有一些不同,但也会有这两个文件:package.xml 和setup.py。

package.xml文件的主要内容和C++版本功能包一样,包含功能包的版权描述,和各种依赖的声明。

setup.py文件里边也包含一些版权信息,除此之外,还有"entry_points"配置的程序入口,在后续编程讲解中,我们会给大家介绍如何使用。

节点
机器人是多种功能组成的综合体,每个功能单元就像机器人的一个工作细胞,这些细胞通过特定机制相互连接,共同构成完整的机器人系统。
在ROS框架中,我们将这些功能单元称为"节点"。
通信模式
完整的机器人系统可能并不是一个物理上的整体,比如这样一个的机器人:

机器人内部搭载了计算机A,它通过摄像头(机器人的"眼睛")获取环境信息,并通过轮子(机器人的"腿")实现移动功能。与此同时,放置在桌面上的计算机B可以远程监控机器人视野、调节运动参数,甚至通过连接摇杆实现人工操控。
这些分布在多台计算机中的功能模块,我们称之为"节点",它们共同构成了完整的机器人系统。从技术角度看:
- 节点是执行特定任务的独立单元,在操作系统中表现为进程
- 每个节点都是可独立运行的可执行文件,可以是Python脚本、C++编译程序等
- 节点支持多语言开发,包括C++、Python、Java、Ruby等
- 节点采用分布式架构,可部署在不同硬件上(如计算机A、计算机B或云端)
- 每个节点都有唯一标识,便于查询和状态监控
我们可以将节点比作分工明确的工人:有的在一线执行任务,有的提供后台支持,虽然互不相识,却能协同完成复杂的机器人作业。
接下来,我们将具体探讨如何实现这些"工作细胞"------节点。
案例一:Hello World节点(面向过程)
ROS2中节点的实现当然是需要编写程序了,我们从Hello World例程开始,先来实现一个最为简单的节点,功能并不复杂,就是循环打印一个"Hello World"字符串到终端中。
运行效果
通过ros2 run命令,运行编译好的课程代码,看下这个节点执行的效果如何,然后再来分析代码的实现过程,做到知其然也知其所以然。
python
ros2 run learning_node node_helloworld
运行成功后,可以在终端中看到循环打印"Hello World"字符串的效果。

代码解析
这个节点是如何实现的呢?我们来看下代码。
learning_node/node_helloworld.py
python
import rclpy # ROS2 Python接口库
from rclpy.node import Node # ROS2 节点类
import time
def main(args=None): # ROS2节点主入口main函数
rclpy.init(args=args) # ROS2 Python接口初始化
node = Node("node_helloworld") # 创建ROS2节点对象并进行初始化
while rclpy.ok(): # ROS2系统是否正常运行
node.get_logger().info("Hello World") # ROS2日志输出
time.sleep(0.5) # 休眠控制循环时间
node.destroy_node() # 销毁节点对象
rclpy.shutdown() # 关闭ROS2 Python接口
完成代码的编写后需要设置功能包的编译选项,让系统知道Python程序的入口,打开功能包的setup.py文件,加入如下入口点的配置:
python
entry_points={
'console_scripts': [
'node_helloworld = learning_node.node_helloworld:main',
],
创建节点流程
在学习机器人编程时,掌握节点的编码流程是非常关键的。这里提到的函数虽然目前不需要深入理解其具体用法,但它们是ROS(Robot Operating System)开发中常用的基础函数,后续在构建机器人系统时会频繁使用。
让我们更详细地梳理一下实现一个节点的标准编码流程:
-
编程接口初始化
- 这是节点的准备工作阶段
- 包括包含必要的头文件
- 初始化ROS客户端库
- 设置节点名称和命名空间
-
创建节点并初始化
- 实例化节点句柄(NodeHandle)
- 配置节点参数
- 初始化发布者(Publisher)和订阅者(Subscriber)
- 设置服务(Service)和动作(Action)
-
实现节点功能
- 这是核心逻辑部分
- 包括消息回调函数的实现
- 数据处理算法
- 控制逻辑实现
-
销毁节点并关闭接口
- 释放资源
- 关闭所有连接
- 优雅地终止节点
举个例子,假设我们要实现一个简单的温度传感器节点:
- 初始化时会设置采样频率等参数
- 创建节点时会初始化温度数据发布者
- 功能实现部分会定期读取传感器数据并发布
- 关闭时会确保最后一个数据包完整发送
对于有C++或Python基础的开发者,可能会注意到这种面向过程的编程方式确实比较直接。它适合小型系统或快速原型开发,比如:
- 简单的传感器数据采集节点
- 基础的运动控制节点
- 单个算法的实现节点
然而,当系统复杂度增加时(比如需要实现SLAM、导航等复杂功能),这种方式的局限性就会显现:
- 代码复用性差
- 难以维护和扩展
- 模块间耦合度高
- 测试困难
因此在实际的机器人系统开发中,我们通常会在掌握这些基础后,逐步过渡到面向对象的设计模式。
案例二:Hello World节点(面向对象)
推荐大家使用面向对象的编程方式,比如刚才的代码就可以改成这样,虽然看上去复杂了一些,但是代码会具备更好的可读性和可移植性,调试起来也会更加方便。
接下来运行一下调整后的节点:
python
ros2 run learning_node node_helloworld_class
运行成功后,可以还是可以在终端中看到循环打印"Hello World"字符串的效果。

代码解析
功能虽然一样,但是程序的结构发生了变化,我们具体看一下这份代码。
learning_node/node_helloworld_class.py
python
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节点对象并进行初始化
rclpy.spin(node) # 循环等待ROS2退出
node.destroy_node() # 销毁节点对象
rclpy.shutdown() # 关闭ROS2 Python接口
完成代码的编写后需要设置功能包的编译选项,让系统知道Python程序的入口,打开功能包的setup.py文件,加入如下入口点的配置:
python
entry_points={
'console_scripts': [
'node_helloworld = learning_node.node_helloworld:main',
'node_helloworld_class = learning_node.node_helloworld_class:main',
],
节点创建流程 总体而言,节点实现仍然遵循以下四个核心步骤,只是具体编码方式有所调整:
- 初始化编程接口
- 创建并初始化节点
- 实现节点功能
- 销毁节点并关闭接口
说到这里,大家可能还会疑惑:机器人系统中的节点功能不能仅限于打印"Hello World"这样的简单操作,而是需要完成实际的特定任务
案例三:物体识别节点
没错,接下来我们就以机器视觉的任务为例,模拟实际机器人中节点的实现过程。

我们先从网上找到一张苹果的图片,通过编写一个节点来识别图片中的苹果。
运行效果
在这个例程中,我们将用到一个图像处理的库------OpenCV,运行前请使用如下指令安装:
python
sudo apt install python3-opencv
然后就可以运行例程啦:
$ ros2 run learning_node node_object #注意修改图片路径后重新编译

代码解析
在这个例程中,我们加入了图像识别的处理过程,模拟一个节点的功能,关于图像处理的具体实现,并不是此处的重点,大家更多要关注我们是如何通过节点的概念来实现一个具体的机器人功能。
python
import rclpy # ROS2 Python接口库
from rclpy.node import Node # ROS2 节点类
import cv2 # OpenCV图像处理库
import numpy as np # Python数值计算库
lower_red = np.array([0, 90, 128]) # 红色的HSV阈值下限
upper_red = np.array([180, 255, 255]) # 红色的HSV阈值上限
def object_detect(image):
hsv_img = cv2.cvtColor(image, cv2.COLOR_BGR2HSV) # 图像从BGR颜色模型转换为HSV模型
mask_red = cv2.inRange(hsv_img, lower_red, upper_red) # 图像二值化
contours, hierarchy = cv2.findContours(mask_red, cv2.RETR_LIST, cv2.CHAIN_APPROX_NONE) # 图像中轮廓检测
for cnt in contours: # 去除一些轮廓面积太小的噪声
if cnt.shape[0] < 150:
continue
(x, y, w, h) = cv2.boundingRect(cnt) # 得到苹果所在轮廓的左上角xy像素坐标及轮廓范围的宽和高
cv2.drawContours(image, [cnt], -1, (0, 255, 0), 2) # 将苹果的轮廓勾勒出来
cv2.circle(image, (int(x+w/2), int(y+h/2)), 5, (0, 255, 0), -1) # 将苹果的图像中心点画出来
cv2.imshow("object", image) # 使用OpenCV显示处理后的图像效果
cv2.waitKey(0)
cv2.destroyAllWindows()
def main(args=None): # ROS2节点主入口main函数
rclpy.init(args=args) # ROS2 Python接口初始化
node = Node("node_object") # 创建ROS2节点对象并进行初始化
node.get_logger().info("ROS2节点示例:检测图片中的苹果")
image = cv2.imread('/home/ubuntu/dev_ws/src/ros2_21_tutorials-master/learning_node/learning_node/image-20220527101249842.png') # 读取图像
object_detect(image) # 苹果检测
rclpy.spin(node) # 循环等待ROS2退出
node.destroy_node() # 销毁节点对象
rclpy.shutdown()
完成代码的编写后需要设置功能包的编译选项,让系统知道Python程序的入口,打开功能包的setup.py文件,加入如下入口点的配置:
python
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',
节点命令行操作

节点命令的常用操作如下:
python
$ ros2 node list # 查看节点列表
$ ros2 node info <node_name> # 查看节点信息

思考题
现在,大家应该熟悉节点这个工作细胞的概念和实现方法了,回到这个机器人系统的框架图,我们还会发现另外一个问题。

电脑B中的摇杆需要控制机器人运动,这两个节点之间应该有连接机制。例如,摇杆节点可以向运动节点发送速度指令,触发机器人的运动响应。
同理,要调整机器人速度时,参数配置节点需要向运动节点发送调整指令。如果电脑B需要显示机器人视觉画面,电脑A的摄像头节点则要将图像数据实时传输过来。
这正是ROS系统的核心特性------各个节点通过多种通信机制紧密协作,共同完成机器人控制任务。
A