4、ROS2基础编程:C++与Python节点的创建、编译与运行

好,咱们进入正题。这一章是ROS2实战的基石——创建节点。说白了,节点就是ROS2世界里干活的最小单元。你想想看,自动驾驶系统里,激光雷达要发数据、规划模块要算路径、控制模块要发指令,每个模块都是一个节点。

我个人习惯,学一个新框架,先跑通一个最简单的节点。这就像学编程先写"Hello World"一样。今天我们就用C++和Python各写一个节点,然后编译、运行,把整个流程走一遍。

4.1 工作空间与功能包:先搭好架子

在写代码之前,得先把工程目录搭好。ROS2里,所有代码都放在工作空间里。我刚开始用ROS2时,总搞不清工作空间和功能包的关系。其实很简单:

  • 工作空间:一个顶层目录,里面放多个功能包
  • 功能包:一个独立的模块,包含节点代码、配置文件等

创建工作空间的命令如下:

mkdir -p ~/ros2_ws/src
cd ~/ros2_ws
colcon build

嗯,这里要注意。第一次建工作空间,src目录下是空的,colcon build会报个警告。别慌,这是正常的。等我们放进去功能包就好了。

接下来创建功能包。ROS2提供了两种模板:

语言 创建命令 生成的文件
C++ ros2 pkg create --build-type ament_cmake my_cpp_pkg CMakeLists.txt, src/, include/
Python ros2 pkg create --build-type ament_python my_py_pkg setup.py, my_py_pkg/, resource/

我在项目中遇到过一个问题:有人把C++和Python混在一个功能包里。虽然技术上可行,但维护起来很痛苦。我建议一个功能包只用一种语言,这样编译和调试都清爽。

4.2 C++节点:从零开始写一个发布者

好,我们写一个最简单的C++节点。它的任务很简单:每隔1秒发布一条"Hello ROS2"消息。

先进入功能包的src目录,创建一个cpp文件:

// src/my_first_node.cpp
#include "rclcpp/rclcpp.hpp"
#include "std_msgs/msg/string.hpp"

int main(int argc, char **argv)
{
  rclcpp::init(argc, argv);
  auto node = std::make_shared<rclcpp::Node>("my_cpp_node");
  auto publisher = node->create_publisher<std_msgs::msg::String>("topic_hello", 10);
  rclcpp::WallRate rate(1); // 1 Hz

  while (rclcpp::ok())
  {
    auto msg = std_msgs::msg::String();
    msg.data = "Hello ROS2 from C++";
    RCLCPP_INFO(node->get_logger(), "Publishing: '%s'", msg.data.c_str());
    publisher->publish(msg);
    rclcpp::spin_some(node);
    rate.sleep();
  }

  rclcpp::shutdown();
  return 0;
}

代码写完了,但别急着编译。你得告诉CMake这个文件的存在。打开CMakeLists.txt,找到find_package部分,确保依赖都加上了:

find_package(rclcpp REQUIRED)
find_package(std_msgs REQUIRED)

add_executable(my_first_node src/my_first_node.cpp)
ament_target_dependencies(my_first_node rclcpp std_msgs)

install(TARGETS
  my_first_node
  DESTINATION lib/${PROJECT_NAME}
)

我曾经犯过一个低级错误:忘了加install指令。结果编译成功了,但运行时报"找不到可执行文件"。折腾了半小时才发现是安装路径没配。嗯,这种坑踩过一次就记住了。

4.3 Python节点:同样的逻辑,更简洁的写法

Python版本的节点就直观多了。在功能包的my_py_pkg目录下创建:

# my_first_node.py
import rclpy
from rclpy.node import Node
from std_msgs.msg import String

class MyFirstNode(Node):
    def __init__(self):
        super().__init__('my_py_node')
        self.publisher_ = self.create_publisher(String, 'topic_hello', 10)
        self.timer = self.create_timer(1.0, self.timer_callback)

    def timer_callback(self):
        msg = String()
        msg.data = 'Hello ROS2 from Python'
        self.get_logger().info(f'Publishing: "{msg.data}"')
        self.publisher_.publish(msg)

def main(args=None):
    rclpy.init(args=args)
    node = MyFirstNode()
    rclpy.spin(node)
    node.destroy_node()
    rclpy.shutdown()

if __name__ == '__main__':
    main()

Python节点有个好处:不用显式写while循环。ROS2的create_timer会自动帮你调度。你想想看,如果每个节点都要自己管理循环,代码得多乱。

别忘了在setup.py里注册入口点:

entry_points={
    'console_scripts': [
        'my_first_node = my_py_pkg.my_first_node:main',
    ],
},

4.4 编译与运行:见证奇迹的时刻

代码写好了,接下来编译。回到工作空间根目录:

cd ~/ros2_ws
colcon build --packages-select my_cpp_pkg my_py_pkg

这里我建议用--packages-select指定包名。如果你直接colcon build,它会编译整个工作空间。项目大了之后,每次编译都要等很久。只编译改动的包,能省不少时间。

编译成功后,记得source环境:

source install/setup.bash

然后就可以运行了:

# 终端1:运行C++节点
ros2 run my_cpp_pkg my_first_node

# 终端2:运行Python节点
ros2 run my_py_pkg my_first_node

你会看到两个节点都在发布消息。用ros2 topic echo /topic_hello可以查看发布的内容。

核心要点:C++和Python节点在ROS2里是等价的。它们可以互相通信,订阅同一个话题。选择哪种语言,取决于你的场景:

  • C++:性能敏感,比如激光雷达驱动、控制算法
  • Python:快速原型、数据处理、调试工具

4.5 避坑指南:我踩过的那些坑

坑1:编译时找不到头文件

我曾经在CMakeLists.txt里忘了加ament_target_dependencies。结果编译报错说找不到rclcpp/rclcpp.hpp。记住,ROS2的依赖必须通过这个宏来传递,光写find_package是不够的。

坑2:Python节点运行时报模块找不到

这通常是因为setup.py里的entry_points写错了路径。检查一下:路径是从功能包名开始的,不是从src开始的。比如my_py_pkg.my_first_node:main,而不是src.my_py_pkg.my_first_node:main。

小技巧:调试时可以用ros2 node list查看当前有哪些节点在运行。如果看不到你的节点,说明节点启动失败了。再用ros2 node info /节点名查看详细信息。

4.6 本章知识体系

下面这张图总结了C++和Python节点创建的核心流程。你可以看到,两种语言的步骤是对应的:

ROS2节点创建核心流程 C++ 节点 1. 创建功能包 (ament_cmake) 2. 编写C++节点代码 3. 配置CMakeLists.txt 4. colcon build编译 5. ros2 run运行 Python 节点 1. 创建功能包 (ament_python) 2. 编写Python节点代码 3. 配置setup.py入口点 4. colcon build编译 5. ros2 run运行 两种语言最终效果相同 核心:节点 = 可执行程序 + ROS2通信能力

说白了,C++和Python的节点创建流程是对称的。区别只在于:C++需要手动管理CMakeLists.txt,Python则通过setup.py自动处理。我个人更推荐初学者从Python入手,等理解了ROS2的通信机制,再切换到C++做性能优化。

好,这一章的内容就到这里。记住:节点是ROS2的基石。把节点的创建、编译、运行练熟了,后面的话题、服务、动作才能玩得转。

可可精品资料库