# ROS2基础教程 - 3 HelloWorld
下面从 HelloWorld 开始,讲解 ROS2 的开发。
ROS 开发主要使用 C++ 或 Python 实现,如果要实现的功能,对性能有要求,可以使用 C++ 实现,如果对性能没有特别高的要求,可以使用 Python 实现,Python 的开发效率要高一些。
ROS1核心是C++03,而ROS2广泛使用C++11;ROS1 的Python使用Python2,ROS2 使用的Python版本至少是3.5及以上。
在开始之前,先介绍几个概念:工作空间、功能包、节点。
- 功能包
功能包类似于模块或子项目,它们可以被独立开发和复用,每个功能包通常有一个明确的功能目标,比如实现某种算法、驱动硬件等。
我们在开发机器人的时候,机器人的摄像头、雷达等硬件,厂家会提供 ROS 开发的功能包,我们只需要在我们的项目中集成厂家提供的 ROS 功能包,就能获取到硬件传感器的数据。同样,在开发机器人 SLAM 建图导航功能的时候,可以将建图封装在一个功能包中,导航封装在一个功能包中,这样也便于管理、复用和维护。
- 节点
节点是 ROS 中的执行单元,是一个正在运行的 ROS 程序。每个节点都独立运行,可以与其他节点通过 ROS 通信机制进行数据交换。
节点的职责是完成特定的功能,比如控制机器人、处理传感器数据、发布指令等,节点是在功能包中的,一个功能包中可以存在多个节点。
- 工作空间
工作空间是 ROS 项目开发的顶层目录,可以理解为一个项目,用来存放和管理多个功能包。它包含了所有与项目相关的代码、配置和依赖项。
三者关系:
- 工作空间是一个容器,它包含多个功能包。
- 功能包是功能代码的逻辑单元,里面可能包含多个节点。
- 节点是实际执行功能的程序,通过 ROS 的通信机制与其他节点协作完成系统功能。
所以在 ROS 中实现功能是通过节点来实现的,节点是在功能包中,一个功能包可以提供多个功能,功能包是在工作空间中进行管理的。
类似于 项目 -> 子模块 -> 功能
,所以在 ROS 中实现功能,从0开始创建,需要 创建工作空间 -> 创建功能包 -> 创建节点
。
# 3.1 创建工作空间
ROS2 中,工作空间的目录结构:
ros2_ws/ # 工作空间根目录,自定义名称
├── src/ # 源代码目录,存放所有的功能包
├── build/ # 编译目录,存放构建文件(colcon build 时生成)
├── install/ # 安装目录,存放可执行文件和已编译的依赖项(colcon build 时生成)
└── log/ # 日志文件目录,存放构建和运行时的日志信息
2
3
4
5
在进行 ROS 开发,首先需要创建工作空间,然后在工作空间的 src/
目录下创建功能包。开发完成通过 colcon build
命令将功能包编译到 install/
目录中,然后运行。
下面来创建工作空间。
可以使用界面,在你放置的位置创建工作空间的文件夹,或者使用终端命令来创建也是可以的,工作空间的名称是自定义的。
例如我在主目录(~表示主目录)创建一个 ros2_ws 的工作空间:
mkdir ~/ros2_ws # 创建文件夹
cd ros2_ws # 进入到 ros2_ws 目录中
mkdir src # 在 ros2_ws 目录下创建 src 目录
2
3
这样工作空间就创建好了,其实就是创建了两个目录。
创建完成,可以进入到工作空间下进行构建:
cd mkdir ~/ros2_ws
colcon build
2
构建完成,工作空间下就会存在 src、install、build、log 四个目录了。
# 3.2 创建功能包
功能包是在工作空间的 src 目录下创建的。ROS2 中功能包需要指定使用的语言,创建功能包使用如下命令:
# 首先进入到工作空间的 src 目录下
cd ~/dev_ws/src
# 创建C++功能包
ros2 pkg create --build-type ament_cmake hello_pkg_cpp
# 或者,创建Python功能包
ros2 pkg create --build-type ament_python hello_pkg_python
2
3
4
5
6
7
8
ros2 pkg create
:ROS2 中创建功能包的命令--build-type
:表示新创建的功能包是C++还是Python的,如果使用C++或者C,参数值为ament_cmake,如果使用Python,值为ament_python;- 命令后面的
hello_pkg_cpp
和hello_pkg_python
表示的是功能包的名称。 - 功能包的名字不要包含
-
,可以使用_
。
这样就会在工作空间下的 src
目录下创建功能包,每个功能包下都会有一个 package.xml 文件,里面有功能包的描述和功能包的依赖信息等。
# 3.3 Python实现节点
下面就来实现一个简单的节点,功能很简单,就是循环打印 Hello World!
,主要是看如何实现一个节点。
在上面已经创建了两个功能包,hello_pkg_cpp
和 hello_pkg_python
。
下面在 hello_pkg_python
功能包下,使用 Python 实现一个节点。
# 1 创建节点文件
在 hello_pkg_python/hello_pkg_python
目录下创建 python 文件,名称自定义。
例如我这里叫 hello_world.py
,内容如下:
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import rclpy # 导入 rclpy 库,这是 ROS2 的 Python 接口
from rclpy.node import Node # 导入 Node 类,这是 ROS2 的节点类
import time # 导入 time 模块,用于实现线程的休眠
def main(args=None): # main 函数是 ROS2 节点的入口函数,参数 args 通常保留为 None,表示不需要额外的启动参数
# 1.创建节点
rclpy.init(args=args) # 初始化 ROS2 Python 客户端库,必须在创建节点之前调用
node = Node("hello_node") # 创建一个名为 "hello_node" 的节点
# 2.实现功能
while rclpy.ok(): # 当 ROS2 系统处于正常运行状态时,循环会持续执行
node.get_logger().info("Hello World!") # ROS2中打印日志
time.sleep(1)
# 线程休眠 1 秒,防止打印过于频繁
# 3.销毁节点
node.destroy_node() # 销毁节点,释放资源
rclpy.shutdown() # 关闭 ROS2 客户端库
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
实现节点主要分为三个步骤:
- 创建节点
- 实现功能,循环打印
Hello World!
。 - 销毁节点
# 2 配置文件可执行权限
需要授权 python
文件可执行权限:
# 进入到python文件目录
cd src/hello_pkg_python/hello_pkg_python
chmod +x hello_world.py
2
3
# 3 配置节点
在功能包下的 setup.py 中配置节点,在 entry_points
中配置:
entry_points={
'console_scripts': [
'hello_node = hello_pkg_python.hello_world:main',
],
},
2
3
4
5
上面就是配置节点运行的是哪个 Python 文件的哪个方法。前面的 hello_node
是自定义的,建议和 Node("hello_node")
对应。
一个功能包下是可以创建多个节点的,创建多个 Python 文件即可,进行同样的配置。
# 4 构建项目
现在代码已经编写完成了,需要构建项目。
在工作空间下执行如下命令:
colcon build
构建完成,会在 install
目录下生成文件。
也可以使用如下命令,单独编译一个功能包:
colcon build --packages-select hello_pkg_python
# 5 运行节点
首先执行 source
命令,在工作空间下执行:
source install/local_setup.sh
执行的作用主要是为了让 ros2 命令可以找到我们的功能包。
每次都执行上面的命令有些麻烦,我们可以将命令添加到环境变量中,这样每次打开终端就能 source
一下,就不用每次都运行 source
了。
source /opt/ros/humble/setup.bash # 安装ros2的时候已经添加了,指定ROS2的路径
source ~/ros2_ws/install/local_setup.sh # 指定工作空间的路径,让ROS可以找到我们的功能包
2
然后使用如下命令启动节点:
ros2 run hello_pkg_python hello_node
ros2 run
:这个是 ros2 中运行节点的命令;hello_pkg_python
:是功能包的名字;hello_node
:是节点的名称。
执行效果如下,每秒打印 Hello World!
:
在 ROS 1 中,roscore
是一个非常关键的组件。它主要用于提供名称服务(ROS Master)、参数服务器等功能。节点在启动时需要向roscore
中的 ROS Master 进行注册,以便让其他节点能够发现它。所以在 ROS1 中启动节点前,需要先运行 roscore
。
但是ROS2 采用了一种分布式的发现机制,不再依赖于像roscore
这样的中央节点管理器。它使用了 DDS(Data - Distribution Service)中间件。DDS 是一种以数据为中心的发布 - 订阅通信标准,能够自动发现网络中的节点和话题。所以在 ROS2 中启动节点不需要roscore
,
# 3.4 C++实现节点
使用 C++ 实现节点也是类似的。
# 1 创建节点文件
在 hello_pkg_cpp/src
目录下创建 C++ 文件,名称自定义。
例如我这里叫 hello_world.cpp
,内容如下:
#include <rclcpp/rclcpp.hpp> // 导入 ROS2 C++ 客户端库
#include <chrono> // 导入时间处理库
#include <thread> // 导入线程库
int main(int argc, char **argv) {
// 1.创建和初始化操作
rclcpp::init(argc, argv); // 初始化 ROS2 客户端库
auto node = rclcpp::Node::make_shared("hello_node"); // 创建一个名为 "hello_node" 的节点
// 2.实现功能
while (rclcpp::ok()) { // 当 ROS2 系统正常运行时
RCLCPP_INFO(node->get_logger(), "Hello World!"); // 打印日志信息 "Hello World!"
std::this_thread::sleep_for(std::chrono::seconds(1)); // 线程休眠 1 秒,防止打印过于频繁
}
// 3.关闭节点
rclcpp::shutdown(); // 关闭 ROS2 客户端库
return 0; // 返回 0,表示程序正常结束
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 2 配置CMakeLists.txt
在功能包下的 CMakeLists.txt 中,添加 C++ 编译配置,可以添加在 find_package(ament_cmake REQUIRED)
下面:
# 找到此包需要的其他包
find_package(rclcpp REQUIRED)
# 添加可执行文件
add_executable(hello_node src/hello_world.cpp)
# 目标链接库
ament_target_dependencies(hello_node rclcpp)
# 安装可执行文件
install(TARGETS
hello_node
DESTINATION lib/${PROJECT_NAME}
)
2
3
4
5
6
7
8
9
10
11
12
13
14
节点的名称是 hello_node
,和 C++ 代码中对应。
一个功能包下是可以创建多个节点的,创建多个 C++ 文件即可,如果创建多个节点,那么在 CMakeLists.txt
中配置如下:
# 找到此包需要的其他包
find_package(rclcpp REQUIRED)
# 添加可执行文件,指向 hello_world.cpp
add_executable(hello_node src/hello_world.cpp)
ament_target_dependencies(hello_node rclcpp)
# 添加可执行文件,指向 print_word.cpp
add_executable(print_node src/print_word.cpp)
ament_target_dependencies(print_node rclcpp)
# 安装可执行文件
install(TARGETS
hello_node
print_node
DESTINATION lib/${PROJECT_NAME}
)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
上面配置了 hello_node
和 print_node
两个节点。
# 3 构建项目
现在代码已经编写完成了,需要构建项目。
在工作空间下执行如下命令:
colcon build
构建完成,会在 install
目录下生成文件。
也可以使用如下命令,单独编译一个功能包:
colcon build --packages-select hello_pkg_cpp
# 4 运行节点
首先执行 source
命令,在工作空间下执行:
source install/local_setup.sh
如果 ROS 还找不到你的功能包和节点,你就 source 一下,如果找得到就不用了。
然后使用如下命令启动节点:
ros2 run hello_pkg_cpp hello_node
ros2 run
:这个是 ros2 中运行节点的命令;hello_pkg_cpp
:是功能包的名字;hello_node
:是节点的名称。
执行效果也是每秒打印 Hello World!
。
# 3.5 节点代码优化
上面在使用 Python 实现节点的时候,使用的是面向过程的编码方式,在实际的开发中,现在更推荐使用面向对象的编码方式。
# 1 优化Python代码
我们可以创建一个类继承 Node 类,然后类中实现我们的功能。
修改上面的 Python 代码如下:
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import rclpy # ROS2 Python接口库
from rclpy.node import Node # ROS2节点类
import time # 线程用来休眠
# 创建节点类,实现节点,具体的功能在类中实现
class HelloWorldNode(Node):
def __init__(self):
super().__init__('hello_node')
def print_hello_world(self):
while rclpy.ok():
self.get_logger().info("Hello World!") # ROS2打印日志
time.sleep(1) # 休眠1秒
def main(args=None):
# 初始化和创建节点
rclpy.init(args=args)
node = HelloWorldNode()
# 调用打印
node.print_hello_world()
# 销毁
node.destroy_node()
rclpy.shutdown()
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
上面的代码使用面向对象的方式,将节点实现的具体功能放到类中。
# 2 优化C++代码
同样,也是用面向对象的编码方式优化 C++ 代码:
#include <rclcpp/rclcpp.hpp> // 引入 ROS2 C++ 接口库
#include <chrono> // 引入 chrono 库,用于时间处理
#include <thread> // 引入 thread 库,用于休眠线程
// 定义一个 HelloWorldNode 类,继承自 rclcpp::Node 类
class HelloWorldNode : public rclcpp::Node
{
public:
// 构造函数,初始化节点名称为 "hello_node"
HelloWorldNode() : Node("hello_node") {}
// 定义一个函数,用于每秒打印一次 "Hello World!"
void print_hello_world()
{
// 使用 while 循环,检查节点是否正常运行
while (rclcpp::ok())
{
// 打印 "Hello World!" 消息到终端,带有日志信息
RCLCPP_INFO(this->get_logger(), "Hello World!");
// 休眠 1 秒钟,类似于 Python 的 time.sleep(1)
std::this_thread::sleep_for(std::chrono::seconds(1));
}
}
};
// main 函数是程序的入口点
int main(int argc, char * argv[])
{
// 初始化 ROS2,必须在创建节点之前调用
rclcpp::init(argc, argv);
// 创建一个共享指针,指向 HelloWorldNode 节点对象
auto node = std::make_shared<HelloWorldNode>();
// 调用节点的 print_hello_world 函数,开始打印 "Hello World!"
node->print_hello_world();
// 当循环结束时,关闭 ROS2 系统,释放资源
rclcpp::shutdown();
// 返回 0,表示程序成功退出
return 0;
}
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
37
38
39
40
41
42
43
44