ROS2介绍、基本使用及与ROS1的区别
文章目录
ROS2 原理
ROS(Robot Operating System)分为两个版本:ROS1 和 ROS2。它们之间有很多不同,特别是在架构、通信机制和操作系统支持等方面。
- ROS1的架构较为简单,但缺乏实时性支持和分布式系统的灵活性。
- ROS2在设计上更为现代,采用DDS协议来实现去中心化通信,增强了实时性、跨平台性、分布式能力和安全性。
- 示例场景:假设用ROS1控制扫地机器人,若主节点崩溃,机器人会“僵住”;用ROS2则各模块(传感器、电机)仍能自主通信,继续工作。
| 特性 | ROS1 | ROS2 |
|---|---|---|
| 通信机制 | 基于 ROS Master | 使用 DDS(去中心化,支持分布式) |
| 实时性支持 | 不支持实时性 | 支持实时性,适合实时系统 |
| 平台支持 | 主要支持 Linux,有限的 Windows/macOS 支持 | 跨平台支持(Linux, Windows, macOS, RTOS) |
| 节点生命周期 | 无生命周期管理 | 引入了节点生命周期管理 |
| 构建系统 | catkin | ament/colcon |
| 多机支持 | 支持有限 | 原生支持分布式、跨机器通信 |
| 消息序列化 | ROS1 自定义序列化 | 使用 DDS 高效序列化 |
ROS2 相比 ROS1 更加注重实时性、跨平台支持、分布式系统以及工业级别的可靠性和性能。如果你正在开发一个新的系统,特别是对于需要实时性和更高性能的系统,ROS2 是更好的选择。对于许多现有的机器人应用,尤其是在传统工业应用中,ROS1 仍然是非常成熟的解决方案,但随着 ROS2 生态系统的逐渐成熟,ROS2 会成为未来的主要发展方向。
推荐论文《Exploring the Performance of ROS》
ROS1 melodic 使用介绍-https://blog.csdn.net/weixin_41469272/article/details/105289174
ROS2 工作流程
-
节点初始化与发现
- 节点启动时创建
DomainParticipant,加入指定域,注册发布者/订阅者等端点。 - 动态发现通过多播/单播完成,无需中心节点(如ROS1的roscore),支持跨主机和广域网通信。
- 节点启动时创建
-
数据传输与优化
- 序列化:使用CDR(Common Data Representation)格式跨语言兼容(C++/Python)。
- 传输协议动态选择:
- 局域网:UDP多播(低延迟)
- 不可靠网络:TCP单播(可靠性)
- 同进程:共享内存(零拷贝)。
- 环形缓冲区:解决生产-消费速率失衡,避免数据丢失。
- 多通信模式支持
• 发布/订阅:异步单向数据流(如传感器数据)。
• 服务(Service):同步请求-响应(如设备控制)。
• 动作(Action):可中断的长任务(如导航任务)。
ROS1 vs ROS2 对比
-
实时性
- ROS1:实时性差(如机械臂控制难精准同步)。尽管可以通过第三方的实时操作系统来改进,但ROS1本身并不适合进行严格的实时控制。
- ROS2:ROS2为实时系统设计,支持更高效的实时性要求。它能够通过一些实时操作系统(RTOS)和特定的DDS配置来提供更高精度的时序控制。
-
跨平台支持
- ROS1:ROS1主要支持Linux操作系统,虽然有一些对Windows和Mac的支持,但并不完美。
- ROS2:ROS2增加了对Windows、macOS和Linux的原生支持。它的跨平台能力比ROS1更强,特别是在不同操作系统间的兼容性和部署上有很大改进。
-
开发与维护
- ROS1:ROS1在设计时使用了一些当前已经过时的工具和库,使得它的开发和维护变得复杂。
- ROS2:ROS2采用了现代的开发工具和更简洁的设计,使得开发者能够更轻松地进行扩展和维护。ROS2的架构相较于ROS1来说更为模块化和灵活。
-
多机器人系统与依赖管理
- ROS1:在面对多机器人系统和分布式系统时,配置复杂,依赖管理(如第三方库)较麻烦。
- ROS2:ROS2在设计上更适合分布式应用,支持多机器人协同工作。并且它提供了更强的安全性(如加密和认证)和更好的网络可靠性。原生支持多机器人通信,依赖管理更规范(类似Python的
requirements.txt),构建工具从catkin升级为ament/colcon。
其他:ROS 1 通常使用 Python 2(根据发行版不同),而 ROS 2 使用 Python 3。
ROS2基础使用命令
创建工作区与包
mkdir -p ros2_ws/src
cd ros2_ws/src
ros2 pkg create my_pkg --dependencies rclcpp std_msgs # 创建包,依赖ROS2库
ros2 pkg create --build-type ament_python <package_name> #可以生成setup.py package.xml
#注意python版本改动也需要编译才能生效,`setup.py `用来设置node运行入口`entry_points`,以及package name
colcon build --symlink-install
#使用 --symlink-install 参数可以确保 Python 脚本在开发过程中可以直接更新而无需重新安装。
安装colcon
#安装colcon用于管理ros2工作空间
sudo apt install python3-colcon-common-extensions
编译
cd ros2_ws
colcon list # 查看当前所有可被识别的包列表
colcon build # 编译整个工作区
source install/setup.bash # 激活环境
colcon build --packages-select <package_name> #编译指定node
#编译指定文件夹下的package
colcon build --packages-under-path src/[目标文件夹名]
colcon build --packages-under-path src/group1 #eg
#按正则匹配包名
colcon build --packages-select-regex "^[包名前缀]"
# eg: 编译包名均以 group1_ 开头的包
colcon build --packages-select-regex "^group1_"
colcon build --packages-select <package_name> --cmake-args -Wno-dev #去掉warning编译
# 加快编译
# 通过--parallel-workers参数设置编译线程数(建议为CPU核心数的1-2倍):
colcon build --parallel-workers 8
# 若内存不足(如嵌入式设备)少用,使用如下方法关闭并行编译(极端情况)
# 内存严重不足时强制顺序执行:
colcon build --executor sequential
source bashfile和bash bashfile的区别- 使用 source(或 .)执行脚本时,脚本会在 当前 shell 中执行,而不是在子 shell 中。
- 使用 bash 执行脚本时,脚本会在 子 shell 中运行。这意味着,所有在脚本中设置的环境变量或其他设置(如 export 的变量、函数等)仅会影响子 shell,并不会影响父 shell(即当前终端会话)。执行完成后,子 shell 会退出,脚本中的更改不会继续影响当前的终端环境。
多仓库管理
ROS 2 中是否可以使用 wstool?用于多仓库管理的工具?
-
官方态度:
ROS 2 推荐使用vcstool(wstool 的继任者)和colcon(替代 catkin),但 wstool 仍可兼容使用,只是部分功能可能受限。 -
替代工具 vcstool:
- 语法与 wstool 类似,但支持更多版本控制系统(Git、Mercurial 等)
- 性能更好,支持并行操作
- 常用命令:
# 安装 sudo apt install python3-vcstool # 初始化(替代 wstool init) vcs import < deps.rosinstall # 更新(替代 wstool update) vcs pull
-
ROS 2 中的正确工具链:
- 仓库管理:
vcstool(替代 wstool) - 构建系统:
colcon(替代 catkin) - 依赖管理:
rosdep(与 ROS 1 相同)
示例工作流:
mkdir -p ~/ros2_ws/src && cd ~/ros2_ws/src # 导入仓库配置(兼容 .rosinstall 文件) vcs import < https://example.com/ros2_deps.rosinstall cd .. # 安装依赖 rosdep install --from-paths src --ignore-src -r -y # 编译 colcon build - 仓库管理:
安装包依赖
#自动安装工作空间下的pkg需要的包:
rosdep install --from-paths src --ignore-src -r -y
#通过apt安装对应包
sudo apt-get install ros-$ROS_DISTRO-<pac_name>
#eg:
sudo apt-get install ros-$ROS_DISTRO-rqt-tf-tree
sudo apt-get install ros-$ROS_DISTRO-rqt-tf-tool
#注意有些包 ros-$ROS_DISTRO-<pac_name>只能安装cpp版本,python包需要额外安装
sudo apt-get install python3-<pac_name>
#可用apt list查看包名
#eg:
apt list |grep sensors-msg
sudo apt-get install python3-sensor-msgs
cd 到package
#1 ros2 没有对应的roscd,推荐使用:
cd $(ros2 pkg prefix your_package_name)
# 可以使用colcon 来cd
sudo apt install python3-colcon-common-extensions
colcon_cd your_package_name
运行节点
ros2 run my_pkg my_node # 运行名为my_node的节点
ros2 run your_package your_node --ros-args --log-level DEBUG #设置log级别
节点清除
- ROS1 清理节点核心命令:
rosnode kill 节点名(单节点)、rosnode kill -a(所有节点); - ROS2 清理节点核心命令:
ros2 node kill 节点名(单节点),ros2 node list | xargs -I {} ros2 node kill {}; - 节点卡死时,需用系统
ps+kill -9强制清理。
rosnode kill(ROS1)和ros2 node kill(ROS2)本质是向节点发送终止信号,仅能关闭正常响应信号的节点;如果节点卡死,需用系统命令(ps+kill)强制杀死:
# 查找ROS节点对应的进程ID(适用于ROS1/ROS2)
ps -aux | grep ros # 找到节点进程的PID(第二列数字)
kill -9 PID # 强制杀死进程(替换为实际PID)
ROS2 低版本(如 Dashing)可能没有
ros2 node kill命令,需升级版本或用系统命令清理。
ROS2 采用 ros2 node 工具集,清理节点的核心命令是 ros2 node kill(ROS2 Foxy 及以上版本支持)。
# 1. 杀死单个节点:ros2 node kill 节点名称
ros2 node kill /talker # 示例:杀死名为/talker的节点
# 2. 杀死所有运行的节点
ROS2 没有直接的 `-a` 参数,需结合命令行工具批量杀死:
# 方式1:通过xargs批量执行(Linux/macOS)
ros2 node list | xargs -I {} ros2 node kill {}
# 方式2:如果上述命令报错,可手动逐个杀死(兼容更多环境)
for node in $(ros2 node list); do ros2 node kill $node; done
# 3. 辅助命令(确认节点状态)
# 查看当前运行的所有节点
ros2 node list
# 检查节点信息(确认是否清理成功)
ros2 node info /talker
编译清除
清除编译内容,以及cmake文件
sudo apt install python3-colcon-clean #需要安装colcon clean 工具
colcon clean {packages,workspace}
#在构建前执行 make clean(或等效命令),删除所有由编译生成的目标文件和可执行文件。
colcon build --packages-select <package_name> --cmake-clean-first
#删除 CMake 配置阶段生成的缓存文件(主要是 CMakeCache.txt),迫使 CMake 在下一次配置时重新运行环境检测。
colcon build --packages-select <package_name> --cmake-args --no-warn-unused-cli -DBUILD_TESTING=0 --cmake-clean-cache
话题管理
ros2 topic info /chatter #查看topic 信息
ros2 topic info /chatter --versbose #打印更多信息
ros2 topic list # 查看所有话题
ros2 topic echo /chatter # 订阅话题并打印消息
ros2 topic pub /chatter std_msgs/String "data: 'Hello'" # 发布消息
ros2 topic echo 与 QoS: 是不是必须知道 QoS 才能打印?
原理: ros2 topic echo 其实就是一个通用的订阅者节点。
实际行为: 为了方便用户,ros2 topic echo 默认会尝试使用一种**“万能兼容”**的策略(SensorData QoS,即 Best Effort + Volatile)。但如果发布者的 QoS 配置非常刁钻(比如某种特殊的 Durability),有时候确实会出现 echo 没反应的情况。这时候你需要用 --qos-reliability 等参数显式告诉它怎么连。
ros2 topic 过滤显示字段:
ros2 topic echo --field 的作用是过滤并只显示话题消息中指定的字段,语法格式如下:
ros2 topic echo [话题名称] --field [字段路径]
使用说明:
- 如果是消息的一级字段(直接属于消息的顶层字段),直接写字段名即可;
- 如果是嵌套字段(比如消息里包含子消息,子消息又有自己的字段),用
.分隔层级; - 如果是数组/列表字段,可以用索引(如
[0])指定数组中的某个元素,或直接写数组名显示整个数组。
- 字段名大小写敏感:ROS2 消息字段名通常遵循小写+下划线(如
linear_x),必须严格匹配; - 先确认消息结构:如果不知道字段名,可先用
ros2 interface show [消息类型]查看结构,例如:ros2 interface show geometry_msgs/msg/Twist - 多个字段过滤:ROS2 不支持一次指定多个
--field,如果需要看多个字段,只能分开执行命令,或不使用--field查看完整消息。
实操示例:(以常见的 geometry_msgs/msg/Twist 为例)
Twist 消息的结构如下:
geometry_msgs/msg/Twist
├── linear: geometry_msgs/msg/Vector3
│ ├── x: float64
│ ├── y: float64
│ └── z: float64
└── angular: geometry_msgs/msg/Vector3
├── x: float64
├── y: float64
└── z: float64
- 示例1:打印一级嵌套字段(整个 linear 部分)
只看 Twist 消息中 linear 字段的全部内容:
ros2 topic echo /cmd_vel --field linear
输出示例:
x: 0.5
y: 0.0
z: 0.0
---
x: 0.6
y: 0.0
z: 0.0
---
- 示例2:打印深层嵌套字段(linear 的 x 分量)
只看 linear.x 这个具体数值(最常用的场景):
ros2 topic echo /cmd_vel --field linear.x
输出示例:
0.5
---
0.6
---
- 示例3:打印数组字段(以
sensor_msgs/msg/PointCloud2为例)
如果消息包含数组,比如PointCloud2的data数组,指定索引查看单个元素:
# 查看 data 数组的第0个元素
ros2 topic echo /point_cloud --field data[0]
# 查看整个 data 数组(直接写数组名)打印整个雷达数据
ros2 topic echo /point_cloud --field data
# 打印点云fields字段
ros2 topic echo /point_cloud --field fields
# 或
ros2 topic echo /point_cloud |grep -A 30 "fields:"
数据录制及播放
#数据包录制:会生成对应的bag_name的文件夹
ros2 bag record <topic_name1> <topic_name2> -o <bag_name>
#eg:
ros2 bag record /rslidar_points /imu/data -o bag_name
#数据集播放:
ros2 bag play <bag_name>
#使用录制时间戳
ros2 bag play <bag_name> --clock
服务与参数
ros2 service list # 列出所有服务
ros2 param get <node_name> <param_name> # 获取某个参数的值
ros2 param set /my_node param_name value # 修改节点参数
# 将节点的所有参数(包括默认值、用户配置值)保存为 YAML 文件,方便参数备份、复用或批量配置。
ros2 param dump <node_name> [--output-file <文件路径>] [--print]
<node_name>是要导出参数的节点名称(必须是当前运行的节点);--output-file/-o:指定导出的 YAML 文件路径(不指定则默认输出到终端);--print:在导出文件的同时,将参数内容打印到终端(仅查看时常用)。
| 操作 | 对应的命令 | 命令示例与说明 |
|---|---|---|
| 列出节点所有参数 | ros2 param list | ros2 param list /your_node_name 列出特定节点的参数。 |
| 获取参数当前值 | ros2 param get | ros2 param get /your_node_name param_name 获取指定参数的值和类型。 |
| 详细描述参数 | ros2 param describe | ros2 param describe /your_node_name param_name 获取参数的描述信息。 |
| 转储所有参数到文件 | ros2 param dump | ros2 param dump /your_node_name > params.yaml 将参数保存为YAML文件,便于检查和备份。 |
| 加载参数文件 | ros2 param load | ros2 param load /your_node_name params.yaml 将YAML文件中的参数加载到正在运行的节点。 |
常用命令选项:
- 列出参数类型:使用
ros2 param list --param-type,输出会同时显示参数名和类型(如Integer、Double)。 - 过滤参数:使用
ros2 param list --filter "expression",可以通过正则表达式只列出部分参数。
启动多个节点(Launch.py文件)
ROS2 Launch 是批量启动节点、配置参数、解决命名冲突的核心工具,Python 格式支持逻辑判断、动态配置,比 XML/YAML 更灵活,是官方推荐的首选方案。
Python 格式的 ROS2 Launch 核心是“动作+替换+条件”,记住固定模板,再根据场景组合 Node、DeclareLaunchArgument、IncludeLaunchDescription 这三个最常用动作,就能满足绝大多数需求。
LaunchDescription:启动配置的“容器”,所有要执行的操作(启动节点、传参数等)都要放进这个容器里。- 动作(Actions):启动时要做的具体事(比如“启动 talker 节点”“声明一个可配置参数”“打印日志”)。
- 替换(Substitutions):动态获取值的“工具”,不用写死固定字符串(比如“获取命令行传入的参数”“自动找包的安装路径”)。
- 条件(Conditions):启动的“开关”(比如“只有传了 use_gui:=true 才启动GUI节点”)。
基本结构(固定模板,直接套用)
所有 Python Launch 文件都遵循这个最小结构,核心是 generate_launch_description() 函数(必须叫这个名字,ROS2 会自动识别):
# 1. 导入需要的工具(像搭积木一样,需要什么拿什么)
from launch import LaunchDescription # 启动配置容器
from launch_ros.actions import Node # 启动ROS节点的工具
# 2. 定义启动配置生成函数(ROS2会自动调用这个函数)
def generate_launch_description():
# 3. 定义要做的**动作actions**(这里是启动2个节点)
talker_node = Node(
package="my_pkg", # 节点所在的功能包(必须是已经安装好的包)
executable="talker", # 节点的可执行文件名(比如CMakeLists.txt里编译的目标)
name="my_talker", # 自定义节点名(可选,会覆盖代码里写的节点名)
output="screen" # 日志打印到终端(默认是存到日志文件,新手推荐screen)
)
listener_node = Node(
package="my_pkg",
executable="listener",
name="my_listener",
output="screen"
)
# 4. 把所有动作装进容器,返回给ROS2
return LaunchDescription([
talker_node,
listener_node
])
generate_launch_description() 函数用来声明launch文件要做的动作actions(但实际没有做),而后通过LaunchDescription启动 generate_launch_description()声明的actions
核心功能与用法(带场景解释)
-
- 核心动作(Actions)—— 启动时要做的事
(1)启动节点(Node)—— 最常用动作
启动 ROS2 节点的核心工具,关键参数通俗解释:
| 参数名 | 作用(通俗版) | 示例 | 适用场景 |
|---|---|---|---|
package | 节点在哪个包里面 | package="demo_nodes_cpp" | 所有节点启动都必须填 |
executable | 节点的“可执行文件”名(编译后生成的程序) | executable="talker" | 同上,和 package 成对出现 |
name | 给节点起个别名(覆盖代码里的名字) | name="custom_talker" | 避免节点名冲突 |
namespace | 给节点/话题加个“前缀” | namespace="demo" | 多个机器人/节点共用话题时 |
output | 日志放哪(screen=终端,log=文件) | output="screen" | 调试时看日志用 screen |
parameters | 给节点传参数(键值对/配置文件) | parameters=[{"count": 100}] | 节点需要动态配置时 |
remappings | 话题重命名(“把A话题改成B话题”) | [("chatter", "demo/chatter")] | 解决话题名称冲突 |
condition | 启动开关(满足条件才启动) | IfCondition(use_gui) | 可选启动GUI/调试节点时 |
示例:带命名空间和参数的节点
Node(
package="demo_nodes_cpp",
executable="talker",
name="my_talker",
namespace="robot1", # 最终节点名:/robot1/my_talker
output="screen",
parameters=[{"max_count": 200}], # 给节点传参数 max_count=200
remappings=[("chatter", "robot1_chatter")] # 话题 chatter 改成 robot1_chatter
)
(2)声明启动参数(DeclareLaunchArgument)—— 动态传参
允许启动时通过命令行修改配置,不用改代码,比如“是否启动GUI”“设置节点频率”。
示例:声明并使用参数
from launch.actions import DeclareLaunchArgument
from launch.substitutions import LaunchConfiguration
# 1. 声明参数(相当于“定义一个变量”,可传可不传,有默认值)
declare_use_gui = DeclareLaunchArgument(
name="use_gui", # 参数名(命令行传参格式:use_gui:=false)
default_value="true", # 默认值(没传参就用这个)
description="是否启动GUI节点(true/false)" # 说明(--show-args时能看到)
)
# 2. 获取参数值(相当于“使用这个变量”)
use_gui = LaunchConfiguration("use_gui")
# 3. 用参数控制节点启动(比如:use_gui为true才启动GUI节点)
gui_node = Node(
package="my_gui_pkg",
executable="gui_node",
condition=IfCondition(use_gui) # 条件:use_gui是true才启动
)
(3)包含其他 Launch 文件(IncludeLaunchDescription)—— 复用配置
不用重复写代码,直接复用别人写好的 Launch 文件(比如启动 RViz、Gazebo,或自己之前写的配置)。
示例:启动 RViz 并加载自定义配置
from launch.actions import IncludeLaunchDescription
from launch.launch_description_sources import PythonLaunchDescriptionSource
from launch.substitutions import PathJoinSubstitution, FindPackageShare
# 包含 RViz 的 Launch 文件
rviz_launch = IncludeLaunchDescription(
# 指定要复用的 Launch 文件路径
PythonLaunchDescriptionSource([
PathJoinSubstitution([
FindPackageShare("rviz2"), # 找到 rviz2 包的安装路径
"launch", "rviz2.launch.py" # rviz2 的 Launch 文件相对路径
])
]),
# 给被包含的 Launch 文件传参数(比如指定自定义的 RViz 配置文件)
launch_arguments={
"rviz_config": PathJoinSubstitution([
FindPackageShare("my_pkg"), # 自己的包
"config", "my_rviz.rviz" # 自己的 RViz 配置文件
])
}.items()
)
(4)其他常用动作
-
LogInfo:打印日志到终端(调试用,比如“确认参数是否生效”):from launch.actions import LogInfo from launch.substitutions import PythonExpression # 静态日志 LogInfo(msg="启动完成!") # 动态日志(结合参数,比如打印GUI状态) LogInfo(msg=PythonExpression(['"当前GUI状态:" + "', use_gui, '"'])) -
ExecuteProcess:执行系统命令(比如启动 Gazebo 世界、创建文件夹):from launch.actions import ExecuteProcess # 执行 ls -l 命令(终端输出文件列表) ExecuteProcess(cmd=["ls", "-l"], output="screen") # 启动 Gazebo 空世界(示例) ExecuteProcess(cmd=["gazebo", "--verbose", "-s", "libgazebo_ros_init.so"], output="screen") -
- 动态替换(Substitutions)—— 不用写死的“工具”
相当于“动态字符串工具”,解决“路径变了怎么办”“参数要动态传”的问题,常用类型:
| 替换类 | 作用(通俗版) | 示例 | 适用场景 |
|---|---|---|---|
LaunchConfiguration | 获取启动参数的值(比如命令行传的参数) | LaunchConfiguration("use_gui") | 用动态参数配置节点时 |
PathJoinSubstitution | 拼接路径(跨平台兼容,Windows/Linux通用) | PathJoinSubstitution([FindPackageShare("my_pkg"), "config"]) | 找配置文件/YAML时 |
FindPackageShare | 自动找包的“共享目录”(比如 share/my_pkg) | FindPackageShare("my_pkg") | 引用包内文件时(不用写绝对路径) |
EnvironmentVariable | 获取系统环境变量(比如 ROS_LOG_LEVEL) | EnvironmentVariable("ROS_LOG_LEVEL") | 配置日志级别、环境参数时 |
PythonExpression | 执行简单Python代码(拼接字符串/判断) | PythonExpression(['"a" + "b"']) | 动态生成字符串时 |
通俗理解:比如你要引用包内的 config/params.yaml 文件,不用写死 /home/user/ros2_ws/install/my_pkg/share/my_pkg/config/params.yaml,用 PathJoinSubstitution + FindPackageShare 会自动找路径,换电脑、换工作空间也不用改代码。
-
- 条件判断(Conditions)—— 启动开关
控制动作是否执行,常用两个“开关”:
- 条件判断(Conditions)—— 启动开关
IfCondition(变量):变量为true时执行(比如“启动GUI”)。UnlessCondition(变量):变量为false时执行(比如“不启动GUI时启动日志节点”)。
示例:条件启动节点
from launch.conditions import IfCondition, UnlessCondition
# 1. 声明参数(命令行可传 use_gui:=false)
declare_use_gui = DeclareLaunchArgument("use_gui", default_value="true")
use_gui = LaunchConfiguration("use_gui")
# 2. use_gui=true 时启动GUI节点
gui_node = Node(
package="my_gui_pkg",
executable="gui_node",
condition=IfCondition(use_gui)
)
# 3. use_gui=false 时启动日志节点
log_node = Node(
package="demo_nodes_cpp",
executable="logger",
condition=UnlessCondition(use_gui)
)
- 参数设置(
parameters)—— 给节点传配置
节点的参数可以通过3种方式设置,可混合使用:
(1)硬编码字典(简单参数,直接写)
Node(
package="demo_nodes_cpp",
executable="talker",
parameters=[{"max_count": 100, "enable_print": True}] # 键值对直接写
)
(2)导入YAML文件(多个参数,批量管理)
适合参数多的场景,先在包内创建 config/params.yaml 文件:
# params.yaml 内容(和节点的 namespace、name 对应)
robot1: # 命名空间(如果节点没有namespace,这行去掉)
my_talker: # 节点名(如果要给所有节点共享,这行去掉)
max_count: 200
publish_rate: 10 # 发布频率10Hz
然后在 Launch 文件中导入:
from launch.substitutions import PathJoinSubstitution, FindPackageShare
Node(
package="demo_nodes_cpp",
executable="talker",
name="my_talker",
namespace="robot1",
parameters=[
PathJoinSubstitution([
FindPackageShare("my_pkg"), # 自己的包
"config", "params.yaml" # YAML文件路径
])
]
)
(3)关联启动参数(动态传参,灵活配置)
结合 LaunchConfiguration,允许命令行修改节点参数:
# 1. 声明参数(命令行可传 max_count:=300)
declare_max_count = DeclareLaunchArgument("max_count", default_value="100")
max_count = LaunchConfiguration("max_count")
# 2. 节点参数关联启动参数
Node(
package="demo_nodes_cpp",
executable="talker",
parameters=[{"max_count": max_count}] # 动态获取命令行参数
)
文件安装配置(必须做!否则启动不了)
写好的 Launch 文件要告诉 ROS2 安装路径,否则 ros2 launch 找不到。根据功能包类型(Python/C++)配置:
- Python 功能包(
setup.py)
在data_files中添加以下内容(告诉安装脚本把 launch、config 文件夹复制到安装目录):
import os
from glob import glob
package_name = "my_pkg" # 替换成你的包名
data_files = [
# 安装launch文件
(os.path.join("share", package_name, "launch"), glob("launch/*.launch.py")),
# 安装config文件夹(含YAML、RViz配置等)
(os.path.join("share", package_name, "config"), glob("config/*")),
]
- C++ 功能包(
CMakeLists.txt)
添加以下代码(安装 launch 和 config 文件夹):
# 替换成你的包名
project(my_pkg)
# 安装launch文件和config文件夹
install(DIRECTORY
launch
config
DESTINATION share/${PROJECT_NAME}/
)
为什么要配置?
不配置的话,编译后 launch 文件还在你的工作空间源码目录,ROS2 会在安装目录(install/my_pkg/share/my_pkg/)找,导致报错“Could not find the launch file”。
多机协同
ROS2 与 ROS1 不同,它取消了 Master 节点(roscore),而是基于 DDS (Data Distribution Service) 协议。这意味着只要两台机器在同一个局域网内,并且配置了相同的Domain ID,它们通常就能自动发现彼此并通信。
核心前提条件
确保满足以下三个条件:
- 同一网络:本地和远程主机必须连接在同一个局域网(LAN)下(例如连接同一个 WiFi 或交换机),且能互相 Ping 通。
- 多播支持 (Multicast):DDS 默认使用多播(Multicast)进行节点发现,网络设备(路由器)必须支持多播。
- 时间同步:两台机器的系统时间尽量保持同步(虽然 DDS 有一定的容忍度,但时间差过大会导致 TF 变换等数据失效)。建议使用
chrony或ntp同步。
快速配置步骤
| 配置项 | 值 / 操作 | 备注 |
|---|---|---|
| 网络连接 | 同一子网 | 必须能 Ping 通 |
| ROS_DOMAIN_ID | 0 (或相同整数) | 必须一致 |
| ROS_LOCALHOST_ONLY | 0 | 必须关闭 |
| 防火墙 | 允许 UDP | 关闭或放行端口 |
| RMW 实现 | 建议一致 | 推荐 CycloneDDS 用于局域网 |
第一步:设置 Domain ID (两端都要做)
ROS2 使用 ROS_DOMAIN_ID 来区分不同的机器人网络。两台机器必须设置为相同的整数(建议 0-100 之间,默认是 0)。
在本地和远程主机的终端中执行:
export ROS_DOMAIN_ID=0
如果需要永久生效,请将其写入 ~/.bashrc 文件:
echo "export ROS_DOMAIN_ID=0" >> ~/.bashrc
source ~/.bashrc
第二步:检查 Localhost Only 模式 (关键)
许多 ROS2 发行版(如 Humble)默认可能开启了“仅限本地”模式。必须将其关闭才能跨机通信。
检查环境变量:
printenv | grep ROS_LOCALHOST_ONLY
如果是 1,则需要在两台机器上都将其设为 0:
export ROS_LOCALHOST_ONLY=0
(可以写入 ~/.bashrc从而使永久生效)
第三步:测试通信
使用 ROS2 自带的 demo 节点进行测试。
- 远程主机 (发布者/Talker):
ros2 run demo_nodes_cpp talker
- 本地主机 (订阅者/Listener):
ros2 run demo_nodes_cpp listener
如果本地主机看到 "I heard: [Hello World: ID]",则通信成功。
排错指南
如果 ros2 topic list 看不到远程话题,请按以下顺序排查:
A. 检查防火墙 (Firewall)
防火墙是最大的阻碍。DDS 使用 UDP 协议。
- 简单粗暴法 (测试用):暂时关闭防火墙。
sudo ufw disable
- 安全法 (推荐):允许 UDP 通信。
sudo ufw allow from <远程IP> to any port 7400:7500 proto udp
# 或者直接允许所有 UDP
sudo ufw allow proto udp
B. 强制指定网卡 (ROS_AUTOMATIC_DISCOVERY_RANGE)
如果机器有多个网卡(如同时有 WiFi、有线、Docker 的虚拟网桥),DDS 可能会选错网卡。目前主流的 DDS 实现(如 CycloneDDS 或 FastDDS)有不同的配置文件,但最简单的方法是使用 XML 配置或环境变量限制网卡,或者直接切换 DDS 实现。
- 推荐方案:切换到 CycloneDDS(通常对网络配置容忍度更好)
- 两台机器都安装:
sudo apt install ros-<distro>-rmw-cyclonedds-cpp
# 将 <distro> 替换为你的版本,如 humble 或 foxy
- 设置环境变量(两端都要):
export RMW_IMPLEMENTATION=rmw_cyclonedds_cpp
- 指定网卡(假设你的局域网网卡是
wlan0或eth0):
创建cyclonedds.xml文件:
<?xml version="1.0" encoding="UTF-8" ?>
<CycloneDDS xmlns="https://cdds.io/config" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="https://cdds.io/config https://raw.githubusercontent.com/eclipse-cyclonedds/cyclonedds/master/etc/cyclonedds.xsd">
<Domain id="any">
<General>
<NetworkInterfaceAddress>wlan0</NetworkInterfaceAddress> </General>
</Domain>
</CycloneDDS>
- 应用配置:
export CYCLONEDDS_URI=file:///path/to/your/cyclonedds.xml
C. 检查多播 (Multicast)
在两台机器上分别运行多播测试工具:
- 机器 A:
ros2 multicast receive - 机器 B:
ros2 multicast send
如果接收端收不到消息,说明路由器不支持多播,或者网络环境禁止了多播(常见于公司/校园网)。
解决方案:如果多播被禁,则需要使用 Discovery Server(发现服务器)模式。
常用命令及示例
# 1. 启动Launch文件(可传参)
ros2 launch my_pkg my_launch.py use_gui:=false max_count:=300
# 2. 查看Launch文件的可用参数(知道能传哪些参数)
ros2 launch my_pkg my_launch.py --show-args # 简写 -s
# 3. 调试模式(输出详细日志,找错用)
ros2 launch my_pkg my_launch.py --verbose # 简写 -v
示例1:动态传参+条件启动GUI
from launch import LaunchDescription
from launch.actions import DeclareLaunchArgument
from launch.substitutions import LaunchConfiguration
from launch.conditions import IfCondition
from launch_ros.actions import Node
def generate_launch_description():
# 声明参数
declare_use_gui = DeclareLaunchArgument("use_gui", default_value="true")
declare_rate = DeclareLaunchArgument("rate", default_value="10") # 发布频率
# 获取参数
use_gui = LaunchConfiguration("use_gui")
rate = LaunchConfiguration("rate")
return LaunchDescription([
declare_use_gui,
declare_rate,
# 核心节点(动态参数)
Node(
package="my_pkg",
executable="talker",
parameters=[{"publish_rate": rate}],
output="screen"
),
# 条件启动GUI
Node(
package="my_pkg",
executable="gui_node",
condition=IfCondition(use_gui),
output="screen"
)
])
示例2:导入YAML参数+包含RViz
from launch import LaunchDescription
from launch.actions import IncludeLaunchDescription
from launch.launch_description_sources import PythonLaunchDescriptionSource
from launch.substitutions import PathJoinSubstitution, FindPackageShare
from launch_ros.actions import Node
def generate_launch_description():
return LaunchDescription([
# 启动核心节点(导入YAML参数)
Node(
package="my_pkg",
executable="talker",
parameters=[
PathJoinSubstitution([
FindPackageShare("my_pkg"),
"config", "params.yaml"
])
],
output="screen"
),
# 包含RViz
IncludeLaunchDescription(
PythonLaunchDescriptionSource([
PathJoinSubstitution([
FindPackageShare("rviz2"),
"launch", "rviz2.launch.py"
])
]),
launch_arguments={
"rviz_config": PathJoinSubstitution([
FindPackageShare("my_pkg"),
"config", "my_rviz.rviz"
])
}.items()
)
])
注意事项:
- 文件权限:Launch 文件要加执行权限,否则启动报错:
chmod +x launch/my_launch.py # 进入功能包根目录执行 - 包依赖:
package.xml中要声明依赖的包(比如用了demo_nodes_cpp、rviz2),否则编译报错:<exec_depend>demo_nodes_cpp</exec_depend> <exec_depend>rviz2</exec_depend> - 参数类型:命令行传参默认是字符串,如果节点需要
int/bool类型,要么显式指定类型,要么传参时不加引号:# 显式指定int类型(推荐) from launch_ros.descriptions import ParameterValue DeclareLaunchArgument("rate", default_value="10", type=ParameterValue(int)) - 命名冲突:多个节点共用话题时,优先用
namespace(加前缀)或remappings(重命名),避免话题被覆盖。 - 调试技巧:启动时加
--verbose能看到详细日志,比如“节点是否找到”“参数是否加载成功”,方便定位问题。
查找包及安装地址
- 查找包的安装位置:
ros2 pkg prefix <包>
#eg:
ros2 pkg prefix tf2_sensor_msgs
- 查找包是否安装
ros2 pkg list | grep <包>
#eg:
ros2 pkg list | grep tf2_sensor_msgs
- 查找包的python版本安装或绑定
find /opt/ros/humble/lib/python3.10/site-packages -name "<包名>*"
- 查找包的安装位置
dpkg -L <包名>
#eg:
dpkg-query -L python3-sensor-msgs
- python包的安装地址
/opt/ros//lib/python3.x/site-packages:标准的 ROS 2 Python 包安装路径。ros-${ROS_DISTRO}-pkgxxx 安装路径
/opt/ros//local/lib/python3.x/dist-packages:某些 ROS 2 包或额外的 Python 依赖被安装到的地方。尤其是在使用系统级的 apt 安装时,Python 包可能安装到 dist-packages,而不是默认的 site-packages 路径。
/usr/lib/python3/dist-packages:系统级的 Python 包,通常与 ROS 1 一起使用。pythonx-pkgxxx安装路径,有些ros-${ROS_DISTRO}-pkgxxx不提供python版本(仅与cpp相关包)
tf信息输出
使用 tf2_tools 生成 TF Tree 图形:
#安装工具包:
sudo apt install ros-${ROS_DISTRO}-tf2-tools
#生成tf tree pdf:
ros2 run tf2_tools view_frames
可视化查看tf tree:
#安装工具包:
sudo apt install ros-${ROS_DISTRO}-rqt-tf-tree
#使用`rqt`打开可视化界面,Plugins -> Visualization -> TF Tree 打开tf_tree
通过topic输出tf信息:
ros2 topic echo /tf # 查看动态 TF 变换,固定的坐标系关系,仅需要广播一次
ros2 topic echo /tf_static # 查看静态 TF 变换,动态坐标关系,
ros2 run tf2_ros tf2_echo [父坐标系] [子坐标系] # 输出两坐标系间的变换关系
ROS2 interface
ROS 2 通过 抽象中间件接口(rmw, rcl, rosidl)实现与底层通信中间件的解耦,默认采用 DDS(Data Distribution Service) 作为通信基础。
-
序列化:使用 IDL(Interface Definition Language)定义消息结构,生成语言无关的类型支持代码(如 C++/Python 的
.hpp/.py文件)。 -
通信模型:发布-订阅(Topics)、服务(Services)、动作(Actions)均通过 DDS 的 QoS 策略(如可靠性、持久性、截止时间)动态配置。
-
多供应商支持:可适配不同 DDS 实现(如 Fast DDS、RTI Connext),通过
rmw接口层屏蔽差异。 -
统一接口规范:ROS 2 将 Topic、Service、Action 统一视为 通信接口类型,所有接口通过
.msg、.srv、.action文件定义。 -
静态类型生成:利用
rosidl_generator生成目标语言的结构体,支持 C++11/14、Python 3.5+、Rust 等。 -
异步回调调度:通过
SingleThreadedExecutor或MultiThreadedExecutor管理回调队列,支持 非阻塞 I/O 和 实时任务优先级。 -
组件化节点(Component Nodes):节点可动态加载到同一进程,减少通信开销(如
rclcpp_components包)。
ROS2 引入了明确的 Interface 概念(如 sensor_msgs/msg/Image),并基于 IDL(Interface Definition Language)标准化接口描述。
| 特性 | ROS 1 | ROS 2 |
|---|---|---|
| 通信协议 | 自定义协议(TCPROS/UDPROS) | DDS 标准协议(内置 QoS 策略) |
| 节点发现机制 | 依赖 ROS Master(单点故障) | 去中心化动态发现(无 Master) |
| 服务模型 | 同步阻塞(单客户端) | 异步非阻塞(支持多客户端并发) |
| 参数管理 | 全局参数服务器 | 节点私有参数(生命周期绑定) |
| 接口定义 | 分散式(.msg/.srv 文件) | 统一 IDL 描述(.idl 文件) |
| 多语言支持 | 有限(C++/Python 为主) | 完善(C++/Python/Java 等) |
| 实时性 | 弱(依赖 TCP/UDP) | 强(基于 DDS 的 QoS 策略) |
| 硬件抽象 | ros_control 框架 | ros2_control(模块化扩展更强) |
注:ROS1 的接口机制更依赖开发者约定,而 ROS2 通过标准化接口提升跨平台兼容性。
1 核心命令概览
-
ros2 interface list- 功能:列出所有可用的 ROS 接口(消息、服务、动作)类型。
- 示例:
ros2 interface list # 显示全部接口 ros2 interface list --only-msgs # 仅显示消息类型
-
ros2 interface show- 功能:查看特定接口的详细定义(字段结构及数据类型)。
- 示例:
输出示例:ros2 interface show std_msgs/msg/String # 显示字符串消息结构string data # 仅包含一个字符串字段
-
ros2 interface package- 功能:列出某个功能包定义的所有接口。
- 示例:
ros2 interface package std_msgs # 查看 std_msgs 包的接口
| 参数 | 作用 | 示例 |
|---|---|---|
--only-msgs | 仅过滤消息类型 | ros2 interface list --only-msgs |
--only-srvs | 仅过滤服务类型 | ros2 interface list --only-srvs |
--include-hidd | 显示隐藏接口(如内部调试接口) | ros2 interface list --include-hidden-nodes |
2 扩展应用场景
-
调试接口数据流
- 命令组合:
ros2 interface show+ros2 topic echoros2 topic echo /chatter --type std_msgs/msg/String # 实时查看话题数据
- 命令组合:
-
自定义接口开发
- 步骤:
- 在功能包的
msg/或srv/目录下定义接口文件(如MyInterface.msg)。 - 编译后通过
ros2 interface show my_package/msg/MyInterface验证。
- 在功能包的
- 步骤:
-
检查接口兼容性
- 使用
ros2 interface对比不同节点间的接口版本,避免通信失败 。
- 使用
ROS1 bag 转 ROS2
# 安装
pip install rosbags
# ubuntu 2204
pip install rosbags==0.9.16
# 基本转换
rosbags-convert your_ros1.bag --dst your_ros2_bag/
# 指定输出目录
rosbags-convert input.bag --dst ./output_ros2/
也可以用ros1_bridge 通过record 来记录下其转换的topic实现转换。参见本文最后一章。
各种info
ROS 2的 info 系列命令是系统状态诊断的核心工具,通过组合使用可快速定位通信异常(如话题未连接、QoS不匹配)或录制数据完整性。
节点信息查询:ros2 node info
功能:查看节点的详细连接信息,包括其发布/订阅的话题、服务、动作服务器/客户端等。
命令格式:
ros2 node info <node_name> # 例如:ros2 node info /turtlesim
输出内容示例:
Subscribers:
/turtlesim/parameter_events: rcl_interfaces/msg/ParameterEvent
Publishers:
/turtle1/color_sensor: turtlesim/msg/Color
Services:
/turtlesim/describe_parameters...
Action Servers:
/turtle1/rotate_absolute: turtlesim/action/RotateAbsolute
关键参数:
- 可通过重映射修改节点命名空间(如
-r __ns:=/demo)。
话题信息查询:ros2 topic info
功能:显示话题的基础信息,包括消息类型、发布者与订阅者数量。
命令格式:
ros2 topic info <topic_name> # 例如:ros2 topic info /odom
输出内容:
Type: nav_msgs/msg/Odometry
Publisher count: 1
Subscriber count: 0
高级参数(--verbose):
- 增加
--verbose可显示QoS策略(如可靠性、持久性)和消息字段结构:ros2 topic info /odom --verboseReliability: RELIABLE Durability: VOLATILE History: KEEP_LAST (depth=10) Message fields: std_msgs/Header header uint32 seq time stamp string frame_id ...
Bag文件信息查询:ros2 bag info
功能:分析录制的ROS 2 bag文件,显示录制时间、话题列表、消息数量及存储格式。
命令格式:
ros2 bag info <bag_dir> # 例如:ros2 bag info subset
输出内容示例:
Files: subset.db3
Duration: 10.2s
Topic: /image | Type: sensor_msgs/msg/Image | Count: 120
Topic: /action | Type: geometry_msgs/msg/Twist | Count: 85
服务信息查询(替代命令)
说明:ROS 2无直接 service info 命令,需组合使用以下命令:
- 查看服务列表与类型:
ros2 service list -t # 显示服务名称及类型 - 查看服务数据结构:
ros2 interface show <service_type> # 例如:ros2 interface show turtlesim/srv/Spawnfloat32 x float32 y string name --- string response
动作信息查询:ros2 action info
功能:显示动作的客户端/服务器状态及目标数量。
命令格式:
ros2 action info <action_name> # 例如:ros2 action info /turtle1/rotate_absolute
输出内容:
Action: /turtle1/rotate_absolute
Action servers: 1 (active)
Action clients: 0
- 通用参数扩展:
- 使用
-t可显示类型(如ros2 topic list -t); - 部分命令(如
topic info)支持叠加--verbose深度输出。
- 使用
- 动态系统调试:
- 若节点未显示,需检查守护进程状态:
ros2 daemon stop && ros2 daemon start # 重启守护进程
- 若节点未显示,需检查守护进程状态:
- 多机器人系统:
- 在分布式系统中使用命名空间隔离(如
-r __ns:=/robot1)避免冲突。
- 在分布式系统中使用命名空间隔离(如
- 自定义接口分析:
- 消息/服务结构查询统一通过
ros2 interface show实现。
- 消息/服务结构查询统一通过
代码及编译
ROS1
C++
///talker.cpp
#include "ros/ros.h"
#include "std_msgs/String.h"
int main(int argc, char **argv) {
ros::init(argc, argv, "ros1_template_node");
ros::NodeHandle nh;
ros::Publisher pub = nh.advertise<std_msgs::String>("chatter", 1000);
ros::Rate loop_rate(10);
while (ros::ok()) {
std_msgs::String msg;
msg.data = "Hello ROS1";
pub.publish(msg);
ros::spinOnce();
loop_rate.sleep();
}
return 0;
}
/// listener.cpp
#include "ros/ros.h"
#include "std_msgs/String.h"
void chatterCallback(const std_msgs::String::ConstPtr& msg) {
ROS_INFO("I heard: [%s]", msg->data.c_str());
}
int main(int argc, char **argv) {
ros::init(argc, argv, "ros1_subscriber");
ros::NodeHandle nh;
ros::Subscriber sub = nh.subscribe("chatter", 1000, chatterCallback);
ros::spin();
return 0;
}
Python
# ROS1 Publisher
import rospy
from std_msgs.msg import String
rospy.init_node('talker') # 全局初始化
pub = rospy.Publisher('chatter', String, queue_size=10) # 无QoS配置
rate = rospy.Rate(1)
while not rospy.is_shutdown():
pub.publish("Hello ROS1")
rate.sleep()
# ROS1 Subscriber
def callback(msg):
print(msg.data)
rospy.init_node('listener') #全局rospy.init_node()初始化
sub = rospy.Subscriber('chatter', String, callback)
rospy.spin() #阻塞设计
编译运行
- 创建工作空间与功能包
mkdir -p ~/catkin_ws/src
cd ~/catkin_ws/src
catkin_create_pkg my_pkg roscpp rospy std_msgs # 创建包并声明依赖
- 编写代码
- 将节点代码(如
talker.cpp/listener.cpp)放入src目录
- 配置编译规则
在CMakeLists.txt中新增:
catkin_package(CATKIN_DEPENDS roscpp) # 保证生成的可执行文件放在devel/lib下,否则会错误地存放在build目录
add_executable(talker src/talker.cpp)
target_link_libraries(talker ${catkin_LIBRARIES})
add_executable(listener src/listener.cpp)
target_link_libraries(listener ${catkin_LIBRARIES})
- 编译工作空间
cd ~/catkin_ws
catkin_make # 生成可执行文件到devel/lib目录
source devel/setup.bash # 激活环境变量
- 运行节点
roscore # 启动ROS Master
rosrun my_pkg talker # 启动发布者
rosrun my_pkg listener # 启动订阅者
关键差异:需手动启动
roscore;节点通过rosrun或launch文件启动。
ROS2
C++
///talker.cpp
#include "rclcpp/rclcpp.hpp"
#include "std_msgs/msg/string.hpp"
class Ros2TemplateNode : public rclcpp::Node {
public:
Ros2TemplateNode() : Node("ros2_talker_node") {
publisher_ = this->create_publisher<std_msgs::msg::String>("chatter", 10);
timer_ = this->create_wall_timer(
std::chrono::milliseconds(100),
[this]() {
auto message = std_msgs::msg::String();
message.data = "Hello ROS2";
publisher_->publish(message);
});
}
private:
rclcpp::Publisher<std_msgs::msg::String>::SharedPtr publisher_;
rclcpp::TimerBase::SharedPtr timer_;
};
int main(int argc, char * argv[]) {
rclcpp::init(argc, argv);
rclcpp::spin(std::make_shared<Ros2TemplateNode>());
rclcpp::shutdown();
return 0;
}
///listener.cpp
#include "rclcpp/rclcpp.hpp"
#include "std_msgs/msg/string.hpp"
class Ros2Subscriber : public rclcpp::Node {
public:
Ros2Subscriber() : Node("ros2_subscriber") {
subscription_ = this->create_subscription<std_msgs::msg::String>(
"chatter", 10,
[this](const std_msgs::msg::String::SharedPtr msg) {
RCLCPP_INFO(this->get_logger(), "I heard: '%s'", msg->data.c_str());
});
}
private:
rclcpp::Subscription<std_msgs::msg::String>::SharedPtr subscription_;
};
int main(int argc, char * argv[]) {
rclcpp::init(argc, argv);
rclcpp::spin(std::make_shared<Ros2Subscriber>());
rclcpp::shutdown();
return 0;
}
Python
# ROS2 Publisher
import rclpy
from rclpy.node import Node
from std_msgs.msg import String
class Talker(Node):
def __init__(self):
super().__init__('talker')
self.pub = self.create_publisher(String, 'chatter', qos_profile=10) # 显式QoS
self.timer = self.create_timer(1.0, self.timer_callback)
def timer_callback(self):
self.pub.publish(String(data="Hello ROS2"))
# ROS2 Subscriber
class Listener(Node):
def __init__(self):
super().__init__('listener')
self.sub = self.create_subscription(String, 'chatter', self.callback, qos_profile=10)
def callback(self, msg):
self.get_logger().info(f'Received: {msg.data}')
rclpy.init()
talker = Talker()
listener = Listener()
rclpy.spin(talker) # 非阻塞式循环
编译运行
- 创建工作空间与功能包
mkdir -p ~/ros2_ws/src
cd ~/ros2_ws
ros2 pkg create my_pkg --build-type ament_cmake \
--dependencies rclcpp std_msgs # 创建C++包
# 或Python包:
ros2 pkg create my_py_pkg --build-type ament_python \
--dependencies rclpy example_interface #
- 编写代码
- C++节点放入
src,Python节点放入my_py_pkg/同名目录
- 配置编译规则
- C++:在
CMakeLists.txt中添加:add_executable(talker src/talker.cpp) ament_target_dependencies(talker rclcpp std_msgs) # 声明依赖 install(TARGETS talker DESTINATION lib/${PROJECT_NAME}) # 需安装到lib目录 add_executable(listener src/listener.cpp) ament_target_dependencies(listener rclcpp std_msgs) install(TARGETS listener DESTINATION lib/${PROJECT_NAME}) - Python:在
setup.py(位于包的根目录下)中配置入口点- setup.py配置
检查entry_points是否正确定义可执行入口:
修改后需完全重新编译(删除entry_points={ 'console_scripts': [ 'talker = my_pkg.talker:main', # 格式: 可执行名=包名.模块名:函数 ], }build/install目录再colcon build) - setup.py配置
- 编译工作空间
cd ~/ros2_ws
colcon build # 生成可执行文件到install目录
source install/setup.bash # 加载环境
- 运行节点
ros2 run my_pkg talker # 启动单个节点
ros2 run my_py_pkg listener # Python节点同理
# 或通过launch文件启动多节点:
ros2 launch my_pkg demo.launch.py #
关键差异:无需单独启动Master;通信基于DDS;支持
launch文件统一管理节点。
依赖缺失
- ROS1:
sudo apt install ros-<distro>-<pkg_name> - ROS2:
rosdep install -i --from-path src --rosdistro <distro> -y
查看运行节点文件路径
ROS1 查看节点文件路径
-
确定节点所属包名
rosnode info <node_name> | grep "Pid"获取节点 PID,然后通过进程信息定位文件:
ls -l /proc/<PID>/exe -
通过包路径定位源码
rospack find <package_name> # 获取包路径 cd $(rospack find <package_name>)/src # 进入源码目录查找与节点名匹配的源代码文件(如
.cpp、.py) -
启动文件排查
节点常通过.launch文件启动:在launch文件中查找node相关的内容
grep -r "node_name" $(rospack find <package_name>)/launch # ROS1
ROS2 查看节点文件路径
-
查找节点所属包
ros2 node info <node_name> | grep "package" # 获取包名 -
定位包安装路径
ros2 pkg prefix <package_name> # 输出包安装路径 -
启动文件排查
节点常通过.launch文件启动:在launch文件中查找node相关的内容
grep -r "node_name" $(ros2 pkg prefix <package_name>)/share/<package_name>/launch # ROS2
对比表
| 操作步骤 | ROS1 命令 | ROS2 命令 |
|---|---|---|
| 获取节点包名 | rosnode info + PID 解析 | ros2 node info |
| 查找包路径 | rospack find | ros2 pkg prefix |
| launch文件 | grep -r "node_name" $(rospack find <package_name>)/launch | grep -r "node_name" $(ros2 pkg prefix <package_name>)/share/<package_name>/launch |
查看消息类型
- 列出所有活跃Topic:
- ROS 1:
rostopic list - ROS 2:
ros2 topic list
- ROS 1:
ROS 1
-
查看Topic的消息类型
使用rostopic type命令:rostopic type <topic_name> rostopic type /chatter # 输出示例:std_msgs/String -
查看消息类型的详细定义
通过rosmsg show解析消息结构:rosmsg show <message_type> #or: rosmsg info <message_type> rosmsg show std_msgs/String # 输出:string data
ROS 2
-
查看Topic的消息类型
使用ros2 topic type命令:ros2 topic type <topic_name> ros2 topic type /chatter # 输出示例:std_msgs/msg/String -
查看消息类型的详细定义
通过ros2 interface show解析消息结构和字段:ros2 interface show <message_type> ros2 interface show std_msgs/msg/String # 输出:string data -
过滤输出内容(ROS 2特有):
使用--filter筛选特定字段(如时间戳):ros2 topic echo <topic_name> --filter "m.field_name" # 或结合grep ros2 topic echo /lidar_topic | grep -A 2 "stamp" # 提取时间戳字段
python 相关
python 包环境变量:PYTHONPATH
python代码中打印:
import sys
print(sys.path)
查看指定包的位置:
pip show 包名
import site
print(site.getsitepackages())
import pkg_resources
print(pkg_resources.get_distribution("包名").location)
__name__变量的特性:分离脚本与模块功能,允许同一文件兼具脚本及模块两种角色。
Python 每个模块均包含内置变量 __name__,其值取决于模块运行方式:
- 直接运行脚本时,
__name__被自动赋值为 ‘main’; - 作为模块被导入时,
__name__值为模块文件名(不含.py后缀)
ROS2 humble & ROS1 noetic
以下是针对 Ubuntu 22.04 Jammy Jellyfish 系统实现 ROS 2 Humble 与 ROS 1 通信部署:
ROS 1 & 2 安装
ROS1和ROS2 全部安装推荐系统ubuntu20.04,支持ros1 noetic和ros2 foxy。
推荐参考链接:https://blog.csdn.net/zardforever123/article/details/130510145
与前文humble安装重复。
-
ROS 2 Humble 安装
- 配置 UTF-8 区域设置:
sudo apt update && sudo apt install locales && sudo locale-gen en_US en_US.UTF-8 - 添加 ROS 2 仓库与 GPG 密钥(推荐国内镜像加速):
sudo curl -sSL https://raw.githubusercontent.com/ros/rosdistro/master/ros.key -o /usr/share/keyrings/ros-archive-keyring.gpg echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/ros-archive-keyring.gpg] https://mirrors.tuna.tsinghua.edu.cn/ros2/ubuntu jammy main" | sudo tee /etc/apt/sources.list.d/ros2.list > /dev/null sudo apt update sudo apt install ros-humble-desktop # 或 ros-humble-ros-base
- 配置 UTF-8 区域设置:
-
ROS 1 Noetic 安装
- 通过第三方源安装(非官方维护但兼容 Ubuntu 22.04):
验证:echo "deb [trusted=yes arch=amd64] http://deb.repo.autolabor.com.cn jammy main" | sudo tee /etc/apt/sources.list.d/autolabor.list sudo apt update && sudo apt install ros-noetic-autolaborsource /opt/ros/noetic/setup.bash && roscore
- 通过第三方源安装(非官方维护但兼容 Ubuntu 22.04):
-
终端ROS环境配置
可以使用如下配置,打开终端,输入1则配置ROS1环境,输入2则配置ROS2环境。其他数字,则先source ROS1 后source ROS2。
vim/gedit添加到~/.bashrc最后。echo "ros noetic(1) or ros2 humble(2) or ros12(Other number)?" read edition if [ "$edition" -eq "1" ]; then source /opt/ros/noetic/setup.bash else if [ "$edition" -eq "2" ]; then source /opt/ros/humble/setup.bash else source /opt/ros/noetic/setup.bash source /opt/ros/humble/setup.bash fi
也可按以下配置使用RE环境变量来
# for colcon build tab complement
_complete_colcon_build() {
local cur=${COMP_WORDS[COMP_CWORD]}
COMPREPLY=( $(compgen -W "--packages-select --symlink-install --cmake-args --event-handlers" -- "$cur") )
}
_ros2init() {
complete -F _complete_colcon_build colcon build
source /opt/ros/humble/setup.bash
}
#ros env set
if [[ -n "$RE" ]]; then
if [ "$RE" -eq 0 ]; then
echo "ros noetic(1) or ros2 humble(2) or ros12(Other number)?"
read edition
if [ "$edition" -eq "1" ]; then
source /opt/ros/noetic/setup.bash
elif [ "$edition" -eq "2" ]; then
#source /opt/ros/humble/setup.bash
_ros2init
else
source /opt/ros/noetic/setup.bash
#source /opt/ros/humble/setup.bash
_ros2init
fi
elif [ "$RE" -eq 1 ]; then
source /opt/ros/noetic/setup.bash
elif [ "$RE" -eq 2 ]; then
source /opt/ros/humble/setup.bash
#source /opt/ros/humble/setup.bash
_ros2init
else
source /opt/ros/noetic/setup.bash
source /opt/ros/humble/setup.bash
#source /opt/ros/humble/setup.bash
_ros2init
fi
fi
alias ros1init='RE=1 && source ~/.bashrc'
alias ros2init='RE=2 && source ~/.bashrc'
alias ros3init='RE=3 && source ~/.bashrc'
使用ros1init ros2init分别设置ros1 和ros2环境变量。ros2 的 colcon build 的tab补全。
ROS 2 ↔ ROS 1 桥接配置
- 编译 ros1_bridge
- 创建工作空间并克隆扩展仓库(支持更多消息类型):
mkdir -p ros_bridge_ws/src && cd ros_bridge_ws/src git clone https://github.com/smith-doug/ros1_bridge.git # 社区维护版本 - 关键编译步骤:
注意:必须先单独编译其他包,再加载双环境编译桥接包cd .. && colcon build --symlink-install --packages-skip ros1_bridge source /opt/ros/noetic/setup.sh # 加载 ROS1 环境 source /opt/ros/humble/setup.sh # 加载 ROS2 环境 #colcon build --symlink-install --packages-select ros1_bridge --cmake-force-configure colcon build --event-handlers console_direct+ --cmake-args -DCMAKE_BUILD_TYPE=Releas
- 创建工作空间并克隆扩展仓库(支持更多消息类型):
报错:
Finished <<< ros1_bridge [3min 4s]
Summary: 1 package finished [3min 4s]
1 package had stderr output: ros1_bridge
解决:
vi ros1_bridge/src/bridge.cpp +81:
改成:
RCLCPP_INFO(ros2_node->get_logger(), ("create bidirectional bridge for topic " + topic_name).c_str());
或者使用:``
- 运行桥接
-
启动核心服务:
# 终端1: 启动 ROS1 roscore source /opt/ros/noetic/setup.sh && roscore # 终端2: 启动 ROS2 守护进程 source /opt/ros/humble/setup.sh && ros2 daemon start # 终端3: 运行桥接 source ros_bridge_ws/install/setup.sh ros2 run ros1_bridge dynamic_bridge -
验证通信:
- ROS1终端1 发布话题:
rosrun rospy_tutorials talker- ROS2终端2 接收:
ros2 run demo_nodes_cpp listener或:
- 在ROS1节点中发布消息:
rostopic pub /chatter std_msgs/String "data: 'Hello from ROS1'"- 在ROS2节点中订阅ROS1消息:
ros2 topic echo /chatter std_msgs/msg/String
-
ros1_bridge的转发策略
- 动态桥接特性:
ros1_bridge仅在检测到 ROS2 侧存在活跃且类型明确的订阅者时才会建立转发通道
- 临时命令限制:
ros2 topic echo作为临时命令无法提供稳定的订阅节点,导致桥接无法初始化
- 类型推导失败:在没有预先桥接的情况下,ROS2 无法自动推导 ROS1 侧话题的消息类型
ros1_bridge需要活跃的接收方节点,才会转发消息,可以通过--bridge-all-topic强制转发所有消息,否则需要指定指定消息类型来接收。即满足以下:
- 活跃的node接收消息
- 接收方指定消息类型
ros2 topic echo /chatter std_msgs/msg/String
- ros1 bridge 使用
--bridge-all-topic强制转发所有消息(资源消耗大)
ros2 run ros1_bridge dynamic_bridge -- --bridge-all-topics
- 中间的
--用于 严格分隔 ros2 run 命令的通用选项和具体节点的专属参数
--bridge-all-topic 的作用
强制桥接器初始化时扫描并创建所有ROS1话题的转发通道,而非等待ROS2侧出现订阅者后才建立连接。这解决了以下关键问题:
- 临时命令失效:
ros2 topic echo无法触发桥接初始化的问题被彻底解决 - 消息类型探测:无需手动指定消息类型即可识别ROS1话题
- 冷启动延迟:避免首次消息因桥接未建立而被丢弃
与默认行为的对比
| 行为特征 | 默认动态桥接 | –bridge-all-topics模式 |
|---|---|---|
| 触发条件 | 需ROS2订阅节点存在 | 立即初始化所有通道 |
| 首消息丢失风险 | 高(桥接建立前消息丢弃) | 零丢失 |
| 临时命令兼容性 | 不兼容 ros2 topic echo | 完美兼容 |
| 资源占用 | 按需分配 | 预分配所有通道资源 |
Rviz selection 用法
使用 RViz 的 “Select” 工具(最直观)可以选中rviz里的物体,并查看其对应的数据信息。比如,选中点云,就可以查看其对应的位置信息。
步骤如下:
- 打开 RViz2,add希望的 PointCloud2 topic。
- 在 RViz 顶部工具栏,找到 “Select” 按钮(通常是一个鼠标箭头的图标)。
- 在 3D 视图中,用鼠标框选或点击点。
- 在 RViz 右侧或顶部的 “Selection” 面板中(如果没有显示,去 Panels -> Selection 勾选打开),你会看到被选中点的详细信息。
- 此时在打开的selection面板中就可以查看点对应 Position 下的数值。
- 拖动selection,也可以实现display和selection各占一个tab(tab选择在下方)
更多推荐



所有评论(0)