3 · 编写第一个发布者节点

好,到了真正动手的时刻。这一章我们直接写一个能跑的发布者节点——用 C++ 和 Python 各写一遍。你想想看,ROS2 里最核心的动作就是“发消息”。不管你是做导航、机械臂还是感知,本质上都是把数据扔到话题上。我个人习惯先跑通一个最小例子,再慢慢往里填逻辑。

发布者节点 rclcpp / rclpy 初始化 rclcpp::init() create_publisher 话题 + 消息类型 + QoS 循环发布 publish() spin / 定时器 话题 /chatter

3.1 发布者节点的骨架

说白了,一个发布者节点就干三件事:初始化ROS2、创建发布者、循环发消息。不管用 C++ 还是 Python,流程一模一样。我刚开始学的时候,总以为要写很多配置代码,其实核心就十几行。

核心流程:
rclcpp/rclpy 初始化 → 创建节点 → 创建发布者 → while 循环里 publish() → spin 保持运行

3.2 C++ 发布者(rclcpp)

先看 C++ 版本。我个人习惯用 std::make_shared 创建节点,这样生命周期好控制。注意看 rclcpp::init 和 spin 的配合——嗯,这里要注意,spin 会阻塞,所以发布循环要放在 spin 之前或者用定时器。

// talker_cpp.cpp
#include <rclcpp/rclcpp.hpp>
#include <std_msgs/msg/string.hpp>

int main(int argc, char * argv[])
{
    // 1. 初始化ROS2
    rclcpp::init(argc, argv);

    // 2. 创建节点
    auto node = std::make_shared<rclcpp::Node>("talker");

    // 3. 创建发布者(话题名 /chatter,队列大小 10)
    auto publisher = node->create_publisher<std_msgs::msg::String>("chatter", 10);

    // 4. 循环发布消息
    rclcpp::WallRate loop_rate(2);  // 2Hz
    int count = 0;
    while (rclcpp::ok()) {
        auto msg = std_msgs::msg::String();
        msg.data = "Hello ROS2: " + std::to_string(count++);
        RCLCPP_INFO(node->get_logger(), "发布: '%s'", msg.data.c_str());
        publisher->publish(msg);
        rclcpp::spin_some(node);   // 处理回调(如果有)
        loop_rate.sleep();
    }

    rclcpp::shutdown();
    return 0;
}
💡 我的小经验: 早期我老忘记加 rclcpp::spin_some,结果订阅者收不到消息。其实 spin_some 不是必须的,但如果你节点里还有服务或定时器,最好加上。纯发布场景下,用 rclcpp::ok() 判断就够了。

3.3 Python 发布者(rclpy)

Python 版本更简洁,但容易掉进一个坑——rclpy.spin 也是阻塞的。我建议用 Rate 对象配合 while rclpy.ok(),这样逻辑清晰。

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

class Talker(Node):
    def __init__(self):
        super().__init__('talker')
        self.publisher_ = self.create_publisher(String, 'chatter', 10)
        self.timer = self.create_timer(0.5, self.timer_callback)  # 2Hz
        self.count = 0

    def timer_callback(self):
        msg = String()
        msg.data = f'Hello ROS2: {self.count}'
        self.count += 1
        self.get_logger().info(f'发布: "{msg.data}"')
        self.publisher_.publish(msg)

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

if __name__ == '__main__':
    main()
⚠️ 曾经踩过的坑: 我在一个项目里用 Python 发布者,忘记调用 rclpy.spin,结果节点启动后直接退出了。记住:spin 保持节点存活,定时器才能触发。如果你用 while 循环自己控制,记得加 rclpy.spin_once。

3.4 代码结构对比

我把两个版本的关键差异整理成表格,方便你对照。其实核心思想一模一样,只是语法糖不同。

步骤C++ (rclcpp)Python (rclpy)
初始化rclcpp::init(argc, argv)rclpy.init()
创建节点make_shared<Node>("name")Node('name')
创建发布者create_publisher<T>("topic", qos)create_publisher(T, 'topic', qos)
循环发布while + WallRate + publish定时器回调 + publish
保持运行spin_some / spinspin(node)
关闭rclcpp::shutdown()rclpy.shutdown()

3.5 编译与运行

C++ 节点需要编译。你记得在 CMakeLists.txt 里添加 add_executable 和 ament_target_dependencies。Python 节点直接 ros2 run 就行。我个人习惯先开一个终端跑发布者,再开另一个终端用 ros2 topic echo /chatter 验证。

快速验证命令:
ros2 run your_package talker_cpp 或 ros2 run your_package talker_py
然后 ros2 topic echo /chatter 看消息。

嗯,到这里你已经写完了第一个发布者节点。是不是比想象中简单?其实 ROS2 的发布者就是一个“循环发消息”的工人,你给它话题名和消息类型,它就一直干。