前言

很多刚学ROS2的同学都会遇到一个问题:跟着步骤能跑通程序,但完全不懂原理

比如:为什么创建包要加 --dependencies?为什么写了代码必须编译?为什么新开终端找不到功能包?文件夹这么多分别是干嘛的?ROS1和ROS2发布者到底有什么区别?

本文基于Ubuntu + ROS2 + Python,结合亲手实操的小乌龟发布者案例,从零、无死角、逐行拆解ROS2发布者开发全流程。

包含:目录结构详解、每个文件的作用、代码逐行解析、编译运行原理、报错原因、固定开发套路,看完彻底告别“稀里糊涂跑通代码”。

本文适配纯小白,无任何ROS基础也能看懂,所有命令、配置、代码均为实操验证过的可落地版本。


一、核心认知:ROS2话题通信与发布者核心概念

1.1 什么是节点、话题、发布者?

  • 节点(Node):ROS2中最小的执行单元,就是我们写的Python程序(publisher节点),负责实现具体功能。

  • 话题(Topic):节点之间数据传输的“通道”,是单向通信管道。比如小乌龟的速度控制话题 /turtle1/cmd_vel

  • 发布者(Publisher):负责主动往话题通道发送数据的节点。简单说:发布者=发数据的一方。

  • 订阅者(Subscriber):对应接收话题数据的一方(本文暂不展开,后续适配)。

本次实操核心逻辑:我们自己写的Python发布者节点 → 持续向 /turtle1/cmd_vel 话题发布速度数据 → 小乌龟仿真节点接收数据 → 小乌龟转圈运动。

1.2 ROS2 与 ROS1 最大区别(新手必看)

ROS1与ROS2,语法、命令、机制完全不通用,核心差异:

  1. ROS1 需要 roscore 启动核心,ROS2 无需核心节点,分布式运行

  2. ROS1 创建包直接后缀写依赖,ROS2 必须加 --dependencies 声明依赖;

  3. ROS1 无需注册节点,ROS2 必须在 setup.py 注册可执行节点,否则 ros2 run 找不到;

  4. ROS1 编译 catkin_make,ROS2 编译 colcon build

  5. ROS1 环境 devel,ROS2 环境 install


二、ROS2工作空间与功能包完整结构详解

2.1 工作空间路径

你本次实操的完整工作目录:~/Test/colcon_ws

ROS2 标准工作空间固定结构:

  • src:唯一的源码存放目录,所有自定义功能包必须放在这里,绝对不能乱放;

  • build:编译缓存目录,自动生成,无需手动修改;

  • install:编译输出目录,ROS2 真正运行的代码在这里(核心重点);

  • log:日志目录,存放运行报错日志。

2.2 功能包完整目录逐文件解析(重点)

执行创建包命令后,src 下生成的 learning_topic 功能包,每一个文件、文件夹作用全覆盖解析

ros2 pkg create --build-type ament_python learning_topic --dependencies rclpy std_msgs geometry_msgs turtlesim
learning_topic
├── learning_topic        # 【核心代码目录】写 Python节点的地方
│   └── __init__.py       # Python包标识文件,空文件即可,无需修改
├── package.xml           # 【ROS身份配置】声明包名、依赖、版本
├── resource
│   └── learning_topic    # ROS内部资源索引文件,无需修改
├── setup.cfg             # Python打包辅助配置,无需修改
├── setup.py              # 【必改核心】编译配置、注册可执行节点
└── test                  # 自动化测试目录,新手完全不用管
    ├── test_copyright.py
    ├── test_flake8.py
    └── test_pep257.py

2.3 关键文件核心作用

1. 内层 learning_topic 文件夹

唯一的代码编写目录,所有的publisher.py、subscriber.py 放在这里,和__init__.py 同级。

__init__.py:标记当前文件夹为Python模块,让ROS2可以识别导入内部的py文件,默认空文件,无需编辑。

2. package.xml(依赖配置文件)

相当于功能包的“身份证”,创建包时填写的依赖 rclpy std_msgs geometry_msgs turtlesim 会自动写入此文件。

作用:告诉ROS2,当前这个包运行需要依赖哪些官方库,缺少依赖会编译报错、运行报错。

新手场景:不需要手动修改,只有新增额外依赖时才需要编辑。

3. setup.py(最最重要!90%新手报错都在这里)

ROS2 Python包的编译+运行核心配置文件,必须手动修改。

核心作用:注册可执行节点。如果不在这里注册,哪怕代码写的再对,ros2 run 也找不到节点,报 Package not found 或节点不存在错误。

核心配置字段 entry_points 格式固定:

entry_points={
    'console_scripts': [
        '终端运行的节点名 = 包名.文件名:主函数名',
    ],
},

对应实操配置:

entry_points={
    'console_scripts': [
        'velocity_publisher = learning_topic.publisher:main',
    ],
},
  • velocity_publisher:ros2 run 后面的节点名称(自定义)
  • learning_topic.publisher:包名.你的py文件名(无需加后缀)
  • main:代码入口函数名,固定对应代码里的main函数

三、从零创建ROS2发布者功能包(标准正确命令)

3.1 ROS2高频报错点

ROS2 不支持 ROS1 直接后缀写依赖的语法,所有依赖必须跟在 --dependencies 后面

3.2 正确创建包完整命令

# 进入工作空间源码目录
cd ~/Test/colcon_ws/src
# ROS2 Python功能包创建命令(带完整依赖)
ros2 pkg create --build-type ament_python learning_topic --dependencies rclpy std_msgs geometry_msgs turtlesim

3.3 命令逐段解析

  • ros2 pkg create:ROS2 创建功能包主命令;
  • --build-type ament_python:指定为Python类型功能包(C++用ament_cmake);
  • learning_topic:自定义功能包名;
  • --dependencies:固定关键字,声明后续为依赖库;
  • rclpy:ROS2 Python核心库(必须,所有Python节点都需要);
  • std_msgs:标准消息库;
  • geometry_msgs:几何消息库(本次小乌龟Twist消息依赖);
  • turtlesim:小乌龟仿真依赖库。

四、ROS2 Publisher发布者代码逐行超详细解析

路径:~/Test/colcon_ws/src/learning_topic/learning_topic/publisher.py
完整可运行代码(适配小乌龟转圈案例):

# 1.导入ROS2核心库
import rclpy
from rclpy.node import Node
# 导入小乌龟速度消息类型
from geometry_msgs.msg import Twist

# 2.自定义发布者节点类,继承ROS2基础Node类
class VelocityPublisher(Node):
    # 节点初始化函数
    def __init__(self):
        # 初始化父类,注册节点名称:velocity_publisher
        super().__init__("velocity_publisher")
        
        # 3.创建发布者对象(核心)
        # 参数:消息类型、话题名称、队列长度
        self.pub = self.create_publisher(Twist, "/turtle1/cmd_vel", 10)
        
        # 4.设置定时器,控制发布频率(0.1秒发布一次=10Hz)
        self.timer_period = 0.1
        self.timer = self.create_timer(self.timer_period, self.timer_callback)

    # 定时器回调函数:定时执行,持续发布消息
    def timer_callback(self):
        # 实例化速度消息对象
        vel_msg = Twist()
        # 设置线速度:x方向前进
        vel_msg.linear.x = 0.5
        # 设置角速度:z方向旋转(实现转圈)
        vel_msg.angular.z = 0.2

        # 5.发布消息(核心动作)
        self.pub.publish(vel_msg)
        
        # 终端打印发布日志
        self.get_logger().info(f"发布速度:线速度={vel_msg.linear.x}, 角速度={vel_msg.angular.z}")

# 程序入口函数
def main():
    # 初始化ROS2环境
    rclpy.init()
    # 创建自定义发布者节点对象
    node = VelocityPublisher()
    # 循环阻塞,保持节点运行,持续执行定时器回调
    rclpy.spin(node)
    # 释放节点资源
    node.destroy_node()
    # 关闭ROS2环境
    rclpy.shutdown()

# 程序入口触发
if __name__ == "__main__":
    main()

4.1 代码四大核心步骤

  • 初始化ROS节点:rclpy.init() + super().__init__()
  • 注册发布者:self.create_publisher() 绑定话题和消息类型
  • 封装消息数据:实例化 Twist 对象,赋值线速度、角速度
  • 循环发布消息:定时器定时调用 publish() 发送数据

4.2 关键函数深度解释

  • create_publisher(消息类型, 话题名, 队列长度):创建发布者,告诉ROS2当前节点要往哪个话题发什么数据,队列长度10为通用默认值,缓存未处理的消息;
  • create_timer(周期, 回调函数):实现循环发布,替代ROS1的 ros::Rate,精准控制发布频率;
  • rclpy.spin(node):节点“保活”核心,阻塞程序,持续监听、执行定时器回调,没有这行代码,节点运行后会立刻退出;
  • get_logger().info():ROS2标准打印日志,替代print,可分级输出日志信息。

五、ROS2编译核心原理

这是90%新手最疑惑的点:我只是改了py代码,为什么必须重新编译?

5.1 ROS2编译的本质(ament_python机制)

你的源码在:colcon_ws/src/功能包/xxx.py

ROS2 绝对不直接运行src里的源码!

执行 colcon build 的核心作用:把src中的Python代码、配置文件,完整复制到 install 目录下。

ROS2 运行节点时,读取的是install/ 目录下的副本,不是 src 源码!

5.2 关键结论

  • 修改 src 中的代码 → install 里还是旧代码

  • 不编译直接运行:程序无变化,依旧跑旧代码;

  • 执行编译:新代码覆盖install旧副本,修改生效;

  • 增量编译:只编译修改的包,不会覆盖其他包的文件,速度极快。

5.3 标准编译命令(推荐,最快)

# 回到工作空间根目录
cd ~/Test/colcon_ws
# 只编译当前自定义包(避免全空间编译浪费时间)
colcon build --packages-select learning_topic
# 刷新环境变量
source install/setup.bash

六、环境变量核心原理

wzl@ubuntu-wzl:~/Test$ ros2 run learning_topic velocity_publisher
Package 'learning_topic' not found

6.1 报错根本原因

新开终端、切换目录后,终端没有加载ROS2工作空间环境变量

终端不知道你的 learning_topic 功能包存在。

source install/setup.bash 命令的作用:

告诉当前终端,注册这个工作空间的所有功能包、节点、话题,让终端可以识别 ros2 run 命令。


6.2 核心规则

  • 每次新开终端,必须执行一次 source;

  • 每次重新编译后,必须重新 source;

  • source 只对当前终端生效,其他终端不受影响。


6.3 永久解决不用每次source(可选)

echo "source ~/Test/colcon_ws/install/setup.bash" >> ~/.bashrc
source ~/.bashrc

七、完整可落地运行流程

全程无错、已验证成功的最终流程,以后所有ROS2 Python节点都按这个来:

第一步:进入工作空间 src 源码目录 + 创建包

cd ~/Test/colcon_ws/src
ros2 pkg create --build-type ament_python learning_topic --dependencies rclpy std_msgs geometry_msgs turtlesim

第二步:在init同级文件下创建并写入 publisher.py 完整发布者代码


cd ~/Test/colcon_ws/src/learning_topic/learning_topic
touch publisher.py
# 导入ROS2核心依赖
import rclpy
from rclpy.node import Node
# 导入小乌龟速度消息类型
from geometry_msgs.msg import Twist

# 自定义发布者节点类
class VelocityPublisher(Node):
    def __init__(self):
        # 初始化节点,节点名:velocity_publisher
        super().__init__("velocity_publisher")

        # 创建发布者:消息类型、话题名、队列长度
        self.pub = self.create_publisher(Twist, "/turtle1/cmd_vel", 10)

        # 定时器:0.1s执行一次,10Hz频率发布
        self.timer_period = 0.1
        self.timer = self.create_timer(self.timer_period, self.timer_callback)

    # 定时发布回调函数
    def timer_callback(self):
        # 实例化消息对象
        vel_msg = Twist()
        vel_msg.linear.x = 0.5
        vel_msg.angular.z = 0.2

        # 发布话题数据
        self.pub.publish(vel_msg)
        # 终端日志输出
        self.get_logger().info(f"发布速度:线速度={vel_msg.linear.x}, 角速度={vel_msg.angular.z}")

# 程序入口
def main():
    rclpy.init()
    node = VelocityPublisher()
    rclpy.spin(node)
    node.destroy_node()
    rclpy.shutdown()

if __name__ == "__main__":
    main()

第三步:修改 setup.py 完成节点注册(ROS2 最关键一步)

只有注册节点后,ros2 run 才能识别你的程序,否则报 Package not found / 无节点 错误。

cd ~/Test/colcon_ws/src/learning_topic

打开 setup.py 文件,找到 entry_points 字段,替换为下面内容:

entry_points={
    'console_scripts': [
        'velocity_publisher = learning_topic.publisher:main',
    ],
},

第三步:回到工作空间根目录编译 + 刷新环境

Python 代码必须编译复制到 install 目录,新代码才会生效

cd ~/Test/colcon_ws
colcon build --packages-select learning_topic
source install/setup.bash

第四步:双终端运行测试(最终验证)

终端1:启动小乌龟仿真

ros2 run turtlesim turtlesim_node

终端2:加载环境并运行自定义发布者

source ~/Test/colcon_ws/install/setup.bash
ros2 run learning_topic velocity_publisher

八、配套调试命令(新手必备)

运行成功后,可通过以下命令验证话题通信:

1. 查看所有活跃话题

ros2 topic list

可以看到你的核心话题:/turtle1/cmd_vel

2. 实时打印话题发布的数据

ros2 topic echo /turtle1/cmd_vel

实时查看你代码发布的线速度、角速度,验证发布功能正常。

3. 检查功能包是否被终端识别

ros2 pkg list | grep learning_topic

有输出说明包加载成功,无输出说明环境未加载或编译失败。


九、新手高频报错汇总+终极解决方案

报错1:创建包 unrecognized arguments

原因:ROS2 依赖未加 --dependencies,沿用ROS1语法

解决:使用本文标准创建包命令

报错2:Package not found

原因:未source环境、编译失败、setup.py未注册节点

解决:回到工作空间source环境,重新编译,核对setup.py配置

报错3:运行无效果,代码修改不生效

原因:修改代码后未重新编译,运行的是install旧代码

解决:改完代码必执行编译+source

更多推荐