ROS2 原理

ROS(Robot Operating System)分为两个版本:ROS1 和 ROS2。它们之间有很多不同,特别是在架构、通信机制和操作系统支持等方面。

  • ROS1的架构较为简单,但缺乏实时性支持和分布式系统的灵活性。
  • ROS2在设计上更为现代,采用DDS协议来实现去中心化通信,增强了实时性、跨平台性、分布式能力和安全性。
  • 示例场景:假设用ROS1控制扫地机器人,若主节点崩溃,机器人会“僵住”;用ROS2则各模块(传感器、电机)仍能自主通信,继续工作。
特性ROS1ROS2
通信机制基于 ROS Master使用 DDS(去中心化,支持分布式)
实时性支持不支持实时性支持实时性,适合实时系统
平台支持主要支持 Linux,有限的 Windows/macOS 支持跨平台支持(Linux, Windows, macOS, RTOS)
节点生命周期无生命周期管理引入了节点生命周期管理
构建系统catkinament/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单播(可靠性)
      • 同进程:共享内存(零拷贝)。
    • 环形缓冲区:解决生产-消费速率失衡,避免数据丢失。
  1. 多通信模式支持
    • 发布/订阅:异步单向数据流(如传感器数据)。
    • 服务(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 bashfilebash bashfile的区别
    • 使用 source(或 .)执行脚本时,脚本会在 当前 shell 中执行,而不是在子 shell 中。
    • 使用 bash 执行脚本时,脚本会在 子 shell 中运行。这意味着,所有在脚本中设置的环境变量或其他设置(如 export 的变量、函数等)仅会影响子 shell,并不会影响父 shell(即当前终端会话)。执行完成后,子 shell 会退出,脚本中的更改不会继续影响当前的终端环境。
多仓库管理

ROS 2 中是否可以使用 wstool?用于多仓库管理的工具?

  1. 官方态度
    ROS 2 推荐使用 vcstool(wstool 的继任者)和 colcon(替代 catkin),但 wstool 仍可兼容使用,只是部分功能可能受限。

  2. 替代工具 vcstool

    • 语法与 wstool 类似,但支持更多版本控制系统(Git、Mercurial 等)
    • 性能更好,支持并行操作
    • 常用命令:
      # 安装
      sudo apt install python3-vcstool
      
      # 初始化(替代 wstool init)
      vcs import < deps.rosinstall
      
      # 更新(替代 wstool update)
      vcs pull
      
  3. 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级别
节点清除
  1. ROS1 清理节点核心命令:rosnode kill 节点名(单节点)、rosnode kill -a(所有节点);
  2. ROS2 清理节点核心命令:ros2 node kill 节点名(单节点),ros2 node list | xargs -I {} ros2 node kill {}
  3. 节点卡死时,需用系统 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])指定数组中的某个元素,或直接写数组名显示整个数组。
  1. 字段名大小写敏感:ROS2 消息字段名通常遵循小写+下划线(如 linear_x),必须严格匹配;
  2. 先确认消息结构:如果不知道字段名,可先用 ros2 interface show [消息类型] 查看结构,例如:
    ros2 interface show geometry_msgs/msg/Twist
    
  3. 多个字段过滤: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 为例)
    如果消息包含数组,比如 PointCloud2data 数组,指定索引查看单个元素:
# 查看 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 listros2 param list /your_node_name 列出特定节点的参数。
获取参数当前值ros2 param getros2 param get /your_node_name param_name 获取指定参数的值和类型。
详细描述参数ros2 param describeros2 param describe /your_node_name param_name 获取参数的描述信息。
转储所有参数到文件ros2 param dumpros2 param dump /your_node_name > params.yaml 将参数保存为YAML文件,便于检查和备份。
加载参数文件ros2 param loadros2 param load /your_node_name params.yaml 将YAML文件中的参数加载到正在运行的节点。

常用命令选项

  • 列出参数类型:使用 ros2 param list --param-type,输出会同时显示参数名和类型(如 IntegerDouble)。
  • 过滤参数:使用 ros2 param list --filter "expression",可以通过正则表达式只列出部分参数。
启动多个节点(Launch.py文件)

ROS2 Launch 是批量启动节点、配置参数、解决命名冲突的核心工具,Python 格式支持逻辑判断、动态配置,比 XML/YAML 更灵活,是官方推荐的首选方案。
Python 格式的 ROS2 Launch 核心是“动作+替换+条件”,记住固定模板,再根据场景组合 NodeDeclareLaunchArgumentIncludeLaunchDescription 这三个最常用动作,就能满足绝大多数需求。

  • 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

核心功能与用法(带场景解释)
    1. 核心动作(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")
    
    1. 动态替换(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 会自动找路径,换电脑、换工作空间也不用改代码。

    1. 条件判断(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)
)
  1. 参数设置(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++)配置:

  1. 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/*")),
]
  1. 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 变换等数据失效)。建议使用 chronyntp 同步。
快速配置步骤
配置项值 / 操作备注
网络连接同一子网必须能 Ping 通
ROS_DOMAIN_ID0 (或相同整数)必须一致
ROS_LOCALHOST_ONLY0必须关闭
防火墙允许 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(通常对网络配置容忍度更好)
  1. 两台机器都安装:
sudo apt install ros-<distro>-rmw-cyclonedds-cpp
# 将 <distro> 替换为你的版本,如 humble 或 foxy
  1. 设置环境变量(两端都要):
export RMW_IMPLEMENTATION=rmw_cyclonedds_cpp
  1. 指定网卡(假设你的局域网网卡是 wlan0eth0):
    创建 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>
  1. 应用配置:
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()
        )
    ])

注意事项:

  1. 文件权限:Launch 文件要加执行权限,否则启动报错:
    chmod +x launch/my_launch.py  # 进入功能包根目录执行
    
  2. 包依赖package.xml 中要声明依赖的包(比如用了 demo_nodes_cpprviz2),否则编译报错:
    <exec_depend>demo_nodes_cpp</exec_depend>
    <exec_depend>rviz2</exec_depend>
    
  3. 参数类型:命令行传参默认是字符串,如果节点需要 int/bool 类型,要么显式指定类型,要么传参时不加引号:
    # 显式指定int类型(推荐)
    from launch_ros.descriptions import ParameterValue
    DeclareLaunchArgument("rate", default_value="10", type=ParameterValue(int))
    
  4. 命名冲突:多个节点共用话题时,优先用 namespace(加前缀)或 remappings(重命名),避免话题被覆盖。
  5. 调试技巧:启动时加 --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/14Python 3.5+、Rust 等。

  • 异步回调调度:通过 SingleThreadedExecutorMultiThreadedExecutor 管理回调队列,支持 非阻塞 I/O实时任务优先级

  • 组件化节点(Component Nodes):节点可动态加载到同一进程,减少通信开销(如 rclcpp_components 包)。

ROS2 引入了明确的 Interface 概念(如 sensor_msgs/msg/Image),并基于 IDL(Interface Definition Language)标准化接口描述。

特性ROS 1ROS 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 核心命令概览
  1. ros2 interface list

    • 功能:列出所有可用的 ROS 接口(消息、服务、动作)类型。
    • 示例
      ros2 interface list              # 显示全部接口
      ros2 interface list --only-msgs  # 仅显示消息类型 
      
  2. ros2 interface show

    • 功能:查看特定接口的详细定义(字段结构及数据类型)。
    • 示例
      ros2 interface show std_msgs/msg/String  # 显示字符串消息结构
      
      输出示例
      string data  # 仅包含一个字符串字段
      
  3. 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 扩展应用场景
  1. 调试接口数据流

    • 命令组合ros2 interface show + ros2 topic echo
      ros2 topic echo /chatter --type std_msgs/msg/String  # 实时查看话题数据 
      
  2. 自定义接口开发

    • 步骤
      1. 在功能包的 msg/srv/ 目录下定义接口文件(如 MyInterface.msg)。
      2. 编译后通过 ros2 interface show my_package/msg/MyInterface 验证。
  3. 检查接口兼容性

    • 使用 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 --verbose
    
    Reliability: 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 命令,需组合使用以下命令:

  1. 查看服务列表与类型
    ros2 service list -t  # 显示服务名称及类型
    
  2. 查看服务数据结构
    ros2 interface show <service_type>   # 例如:ros2 interface show turtlesim/srv/Spawn
    
    float32 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
  1. 通用参数扩展
    • 使用 -t 可显示类型(如 ros2 topic list -t);
    • 部分命令(如 topic info)支持叠加 --verbose 深度输出。
  2. 动态系统调试
    • 若节点未显示,需检查守护进程状态:
      ros2 daemon stop && ros2 daemon start  # 重启守护进程
      
  3. 多机器人系统
    • 在分布式系统中使用命名空间隔离(如 -r __ns:=/robot1)避免冲突。
  4. 自定义接口分析
    • 消息/服务结构查询统一通过 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() #阻塞设计

编译运行
  1. 创建工作空间与功能包
mkdir -p ~/catkin_ws/src
cd ~/catkin_ws/src
catkin_create_pkg my_pkg roscpp rospy std_msgs  # 创建包并声明依赖
  1. 编写代码
  • 将节点代码(如talker.cpp/listener.cpp)放入src目录
  1. 配置编译规则
    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})
  1. 编译工作空间
cd ~/catkin_ws
catkin_make  # 生成可执行文件到devel/lib目录
source devel/setup.bash  # 激活环境变量
  1. 运行节点
roscore  # 启动ROS Master
rosrun my_pkg talker  # 启动发布者
rosrun my_pkg listener  # 启动订阅者

关键差异:需手动启动roscore;节点通过rosrunlaunch文件启动。

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)  # 非阻塞式循环

编译运行
  1. 创建工作空间与功能包
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  # 
  1. 编写代码
  • C++节点放入src,Python节点放入my_py_pkg/同名目录
  1. 配置编译规则
  • 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
  1. 编译工作空间
cd ~/ros2_ws
colcon build  # 生成可执行文件到install目录
source install/setup.bash  # 加载环境
  1. 运行节点
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 查看节点文件路径
  1. 确定节点所属包名

    rosnode info <node_name> | grep "Pid"
    

    获取节点 PID,然后通过进程信息定位文件:

    ls -l /proc/<PID>/exe
    
  2. 通过包路径定位源码

    rospack find <package_name>  # 获取包路径
    cd $(rospack find <package_name>)/src  # 进入源码目录
    

    查找与节点名匹配的源代码文件(如 .cpp.py

  3. 启动文件排查
    节点常通过 .launch 文件启动:在launch文件中查找node相关的内容

grep -r "node_name" $(rospack find <package_name>)/launch  # ROS1
ROS2 查看节点文件路径
  1. 查找节点所属包

    ros2 node info <node_name> | grep "package"  # 获取包名
    
  2. 定位包安装路径

    ros2 pkg prefix <package_name>  # 输出包安装路径
    
  3. 启动文件排查
    节点常通过 .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 findros2 pkg prefix
launch文件grep -r "node_name" $(rospack find <package_name>)/launchgrep -r "node_name" $(ros2 pkg prefix <package_name>)/share/<package_name>/launch

查看消息类型

  • 列出所有活跃Topic
    • ROS 1:rostopic list
    • ROS 2:ros2 topic list
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安装重复。

  1. 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
      
  2. 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-autolabor
      
      验证:source /opt/ros/noetic/setup.bash && roscore
  3. 终端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 桥接配置

  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. 运行桥接
    • 启动核心服务:

      # 终端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强制转发所有消息,否则需要指定指定消息类型来接收。即满足以下:

  1. 活跃的node接收消息
  2. 接收方指定消息类型
ros2 topic echo /chatter std_msgs/msg/String
  1. 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里的物体,并查看其对应的数据信息。比如,选中点云,就可以查看其对应的位置信息。

步骤如下:

  1. 打开 RViz2,add希望的 PointCloud2 topic。
  2. 在 RViz 顶部工具栏,找到 “Select” 按钮(通常是一个鼠标箭头的图标)。
  3. 在 3D 视图中,用鼠标框选或点击点。
  4. 在 RViz 右侧或顶部的 “Selection” 面板中(如果没有显示,去 Panels -> Selection 勾选打开),你会看到被选中点的详细信息。
  5. 此时在打开的selection面板中就可以查看点对应 Position 下的数值。
  6. 拖动selection,也可以实现display和selection各占一个tab(tab选择在下方)
Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐