Skip to content

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

python
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命令可以看到该节点:

589

3.2 C++实现

我们以 C++ 为例,使用 rclcpp.hpp 库。

ros2_cpp_node.cpp:

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 实现

创建功能包:

bash
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 和刚才类似:

python
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 里添加入口:

python
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里面声明依赖:

655

然后就可以构建了,使用命令:

bash
colcon build

colcon build 是 ROS 2 中用于构建工作空间(workspace)内所有功能包的命令,会自动检测并编译工作空间中的包,生成可执行文件、库等,并处理包之间的依赖关系。如图:

749

编译后同级目录会多出三个文件夹buildinstalllog,如图:

590

build 存编译临时文件,install 放最终可运行文件,log 记录编译日志。

使用命令source install/setup.bash 加载包,使用命令ros2 run 包名 可执行程序 运行这个节点:

我这里在setup.py 里定义了入口叫python_node,所以对应的命令是:

bash
ros2 run demo_py_pkg python_node

运行如下:

4.2 C++ 实现

创建功能包:

bash
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

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,所以对应的命令是:

bash
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 识别功能包的唯一目录),命令如下:

bash
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 无法识别工作空间中的包;

参考

  1. https://www.bilibili.com/video/BV1Pb42177BJ