ROS2入门:节点、包、工作空间
一、前言
在 ROS2(Robot Operating System 2)中,节点(Node) 是最基本的计算单元。一个机器人系统往往由多个节点协作完成任务,比如一个节点负责激光雷达数据采集,另一个节点负责建图,还有节点负责导航。
本文将介绍如何在 ROS2 中 编写和运行一个最小的节点,并逐步扩展到 话题(Topic)通信。
二、节点的概念
- 节点(Node):ROS2 中最小的运行单元,通常对应一个进程。
- 话题(Topic):节点之间的数据通道,采用发布(Publisher)/订阅(Subscriber)模式。
- 服务(Service):同步通信机制,类似函数调用。
- 动作(Action):适合执行需要反馈和取消的长时间任务。
在实际开发中,我们经常会编写节点来处理传感器输入、执行算法或控制执行器。
三、编写最小节点(单文件)
3.1 Python 实现
我们以 Python 为例,使用 rclpy
库。
ros2_python_node.py
import rclpy
from rclpy.node import Node
def main():
rclpy.init()
node = Node("python_node")
node.get_logger().info("Hello from Python ROS2 Node!")
rclpy.spin(node)
rclpy.shutdown()
if __name__ == "__main__":
main()
这段代码初始化 ROS 2 Python 节点 "python_node" 并保持其运行,直到程序被终止。并且在运行时会打印一个日志:Hello from Python ROS2 Node!
,如下图:
运行这段代码后,在另一个终端使用ros2 node list
命令可以看到该节点:
3.2 C++实现
我们以 C++ 为例,使用 rclcpp.hpp
库。
ros2_cpp_node.cpp
:
#include "rclcpp/rclcpp.hpp"
int main(int argc, char * argv[])
{
rclcpp::init(argc, argv);
auto node = std::make_shared<rclcpp::Node>("cpp_node");
RCLCPP_INFO(node->get_logger(), "Hello, ROS 2!");
rclcpp::spin(node);
rclcpp::shutdown();
return 0;
}
编写对应的CMakeLists.txt
如下:
cmake_minimum_required(VERSION 3.8)
project(cpp_node)
add_executable(cpp_node ros2_cpp_node.cpp)
find_package(rclcpp REQUIRED)
target_include_directories(cpp_node PUBLIC ${rclcpp_INCLUDE_DIRS})
target_link_libraries(cpp_node ${rclcpp_LIBRARIES})
使用cmake .
生成Makefile
,使用make
编译,使用./cpp_node
运行节点如下:
注:如果这里C++插件导致头文件报错,在插件设置里添加包含路径即可:
四、使用功能包组织节点
功能包是 ROS 2 中组织代码的基本单元,可包含多个节点及相关配置、资源等,通过 package.xml 声明依赖,配合编译脚本实现自动化构建,方便管理、复用和跨包协作,支持 C++、Python 等多种语言,是 ROS 2 项目模块化开发的核心载体。
4.1 Python 实现
创建功能包:
ros2 pkg create --build-type ament_python --license MIT demo_py_pkg
生成的目录结构,具体功能见注释:
demo_py_pkg/ # ROS 2 Python功能包根目录
├── package.xml # 功能包元信息文件,包含包名、版本、依赖等
├── setup.cfg # 配置文件,指定安装选项和脚本入口
├── setup.py # 安装脚本,定义包的结构、依赖和入口点
├── LICENSE # 功能包的许可证文件
├── resource/ # 资源文件目录,存放图标、配置等静态资源
├── test/ # 测试文件目录,包含单元测试和集成测试代码
└── demo_py_pkg/ # Python模块目录,与包名一致
└── __init__.py # 标识该目录为Python模块,可为空或包含初始化代码
编写节点代码:demo_py_pkg/demo_py_pkg/ros2_python_node.py
和刚才类似:
import rclpy
from rclpy.node import Node
def main():
rclpy.init()
node = Node("python_node")
node.get_logger().info("Hello from Python ROS2 Node!")
rclpy.spin(node)
rclpy.shutdown()
在 setup.py
里添加入口:
entry_points={
'console_scripts': [
'python_node = demo_py_pkg.ros2_python_node:main',
],
},
这段代码在 setup.py 中配置了控制台脚本入口,将命令 "python_node" 映射到 demo_py_pkg 模块中 ros2_python_node 文件的 main 函数。
在package.xml
里面声明依赖:
然后就可以构建了,使用命令:
colcon build
colcon build 是 ROS 2 中用于构建工作空间(workspace)内所有功能包的命令,会自动检测并编译工作空间中的包,生成可执行文件、库等,并处理包之间的依赖关系。如图:
编译后同级目录会多出三个文件夹build
、install
、 log
,如图:
build
存编译临时文件,install
放最终可运行文件,log
记录编译日志。
使用命令source install/setup.bash
加载包,使用命令ros2 run 包名 可执行程序
运行这个节点:
我这里在setup.py
里定义了入口叫python_node
,所以对应的命令是:
ros2 run demo_py_pkg python_node
运行如下:
4.2 C++ 实现
创建功能包:
ros2 pkg create --build-type ament_cmake --license MIT demo_cpp_pkg
生成的 C++ 功能包目录结构:
demo_cpp_pkg/ # ROS 2 C++功能包根目录
├── package.xml # 功能包元信息,声明依赖和包信息
├── CMakeLists.txt # CMake编译配置文件,定义编译规则和依赖
├── LICENSE # 许可证文件
├── include/ # 头文件目录
│ └── demo_cpp_pkg/ # 与包名一致的头文件子目录
├── src/ # 源代码目录
└── test/ # 测试代码目录
编写节点代码:demo_cpp_pkg/src/cpp_node.cpp
#include "rclcpp/rclcpp.hpp"
int main(int argc, char * argv[])
{
rclcpp::init(argc, argv); // 初始化ROS 2
auto node = rclcpp::Node::make_shared("cpp_node"); // 创建名为"cpp_node"的节点
node->get_logger().info("Hello from C++ ROS2 Node!"); // 输出日志信息
rclcpp::spin(node); // 保持节点运行
rclcpp::shutdown(); // 关闭ROS 2
return 0;
}
修改CMakeLists.txt配置编译规则,添加下面的内容:
add_executable(cpp_node src/cpp_node.cpp)
find_package(rclcpp REQUIRED)
target_include_directories(cpp_node PUBLIC ${rclcpp_INCLUDE_DIRS})
target_link_libraries(cpp_node ${rclcpp_LIBRARIES})
install(
cpp_node
lib/${PROJECT_NAME}
)
或者直接写:
add_executable(cpp_node src/cpp_node.cpp)
find_package(rclcpp REQUIRED)
ament_target_dependencies(cpp_node rclcpp)
install(
cpp_node
lib/${PROJECT_NAME}
)
这里的 ament_target_dependencies
是一种简化的写法。这段 CMake 脚本的作用是:把 src/cpp_node.cpp 编译成一个名为 cpp_node 的可执行节点,链接上 ROS2 的 rclcpp 库,并在编译安装时把它放到 install/lib/<包名>/ 目录下,这样用户就可以通过 ros2 run <包名> cpp_node 来运行这个节点。
在package.xml
里面声明依赖:
同样,在包的同级目录下使用colcon build
进行构建:
可以看到,也成功构建了包demo_cpp_pkg
同样使用命令source install/setup.bash
加载包,使用命令ros2 run 包名 可执行程序
运行这个节点:
这里在CMakeLists.txt
里定义了可执行程序叫cpp_node
,所以对应的命令是:
ros2 run demo_cpp_pkg cpp_node
如下图:
五、使用工作空间组织功能包
ROS 2 工作空间是管理多个功能包的顶层目录,核心结构包含存放所有功能包的 src 目录(手动创建),以及编译后自动生成的 build(临时编译文件)、install(最终可执行文件与环境脚本)、log(编译日志)目录;它能集中管理功能包、统一按依赖顺序编译,并通过 source install/setup.bash 加载所有包的环境变量,方便调用各包节点,是 ROS 2 项目规模化开发的基础。
它尤其适用于中大型 ROS 2 项目,例如开发移动机器人时,可在同一个工作空间中存放 “激光雷达驱动包”“SLAM 建图包”“导航控制包”,既方便代码统一管理,也便于后续调试和迭代。
5.1 创建
工作空间需手动创建 src 目录(这是 colcon build 识别功能包的唯一目录),命令如下:
mkdir -p ~/ros2_ws/src # "ros2_ws" 为工作空间名,可自定义
cd ~/ros2_ws # 后续编译、环境加载需在工作空间根目录执行
若 src 目录缺失,执行 colcon build 会提示 “无包可编译”,需重点检查
5.2 功能包的添加与管理
所有功能包必须放在 src 目录下,支持两种添加方式:
- 新建包:在
src
目录下执行ros2 pkg create
命令(如cd src && ros2 pkg create --build-type ament_cmake demo_cpp_pkg
); - 导入现有包:将下载的功能包文件夹(如从 GitHub 克隆的包)直接放入
src
目录,确保包内包含package.xml
和编译配置文件(CMakeLists.txt
或setup.py
)。
5.3 编译
- 选择性编译:若仅修改某个包,可指定包名编译以节省时间,命令为
colcon build --packages-select 包名
(如colcon build --packages-select demo_py_pkg
); - 清理编译文件:若编译报错需重新编译,可删除 build 和 install 目录(
rm -rf build/ install/
),再重新执行colcon build
; - 查看编译日志:若编译失败,可在
log/latest
目录下查看详细日志(如cat log/latest/stderr.log
),定位依赖缺失、代码语法错误等问题。
5.4 环境加载的注意事项:
每次打开新终端,需在工作空间根目录执行 source install/setup.bash(Linux),否则 ROS 2 无法识别工作空间中的包;