DDS:机器人的神经网络

常用通信模型

  • 第一种,点对点模型,许多客户端连接到一个服务端,每次通信时,通信双方必须建立一条连接。当通信节点增多时,连接数也会增多。而且每个客户端都需要知道服务器的具体地址和所提供的服务,一旦服务器地址发生变化,所有客户端都会受到影响。

  • 第二种,Broker模型,针对点对点模型进行了优化,由Broker集中处理所有人的请求,并进一步找到真正能响应该服务的角色。这样客户端就不用关心服务器的具体地址了。不过问题也很明显,Broker作为核心,它的处理速度会影响所有节点的效率,当系统规模增长到一定程度,Broker就会成为整个系统的性能瓶颈。更麻烦是,如果Broker发生异常,可能导致整个系统都无法正常运转。之前的ROS1系统,使用的就是类似这样的架构。

  • 第三种,广播模型,所有节点都可以在通道上广播消息,并且节点都可以收到消息。这个模型解决了服务器地址的问题,而且通信双方也不用单独建立连接,但是广播通道上的消息太多了,所有节点都必须关心每条消息,其实很多是和自己没有关系的。

  • 第四种,就是以数据为中心的DDS模型了,这种模型与广播模型有些类似,所有节点都可以在DataBus上发布和订阅消息。但它的先进之处在于,通信中包含了很多并行的通路,每个节点可以只关心自己感兴趣的消息,忽略不感兴趣的消息,有点像是一个旋转火锅,各种好吃的都在这个DataBus传送,我们只需要拿自己想吃的就行,其他的和我们没有关系。

DDS在自动驾驶中的应用

DDS(Data Distribution Service)是一种用于实时系统数据分发的中间件标准,由对象管理组织(OMG)制定并维护。它采用发布-订阅通信模型,专门针对需要高性能、低延迟数据传输的分布式系统而设计。以下是DDS的关键特性与技术细节:

  1. 核心架构

    • 基于DCPS(Data-Centric Publish-Subscribe)模型
    • 采用去中心化的对等网络架构,无需中央服务器
    • 内置发现机制实现动态节点自动发现(如SPDP和SEDP协议)
  2. 服务质量(QoS)策略

    • 提供22种可配置QoS策略,包括:
      • 可靠性(RELIABILITY):可选择"尽力而为"或"可靠传输"
      • 持久性(DURABILITY):支持历史数据缓存
      • 截止时间(DEADLINE):确保数据时效性
      • 资源限制(RESOURCE_LIMITS):防止内存溢出
  3. 典型应用场景

    • 工业物联网(IIoT):如西门子PLC设备间实时数据交换
    • 自动驾驶系统:特斯拉Autopilot中传感器数据分发
    • 金融交易系统:高频交易订单传输
    • 军事系统:北约采用的DIS标准就基于DDS
  4. 实现版本比较

    实现方案开发者特点
    OpenDDSOCI开源实现,支持C++/Java
    RTI ConnextRTI商业版,提供微秒级延迟
    Fast DDSeProsimaROS2默认中间件,Apache许可
  5. 性能指标

    • 典型延迟:<100μs(千兆网络环境下)
    • 吞吐量:可达Gbps级
    • 支持节点数:理论上无上限(实际受网络限制)

最新DDS规范(v1.4)新增了:

  • 类型安全增强(TypeObject)
  • 网络绑定规范(DDSI-RTPS)
  • 安全扩展(DDS-Security)

DDS在ROS2中的地位

刚才我们也提到,DDS是一种通信的标准,就像4G、5G一样,既然是标准,那大家都可以按照这个标准来实现对应的功能,所以华为、高通都有很多5G的技术专利,DDS也是一样,能够按照DDS标准实现的通信系统很多,这里每一个红色模块,就是某一企业或组织实现的一种DDS系统。

既然可选用的DDS这么多,那我们该用哪一个呢?具体而言,他们肯定都符合基本标准,但还是会有性能上的差别,ROS2的原则就是尽量兼容,让用户根据使用场景选择,比如个人开发,我们选择一个开源版本的DDS就行,如果是工业应用,那可能得选择一个商业授权的版本了。

为了实现对多个DDS的兼容,ROS设计了一个Middleware中间件,也就是一个统一的标准,不管我们用那个DDS,保证上层编程使用的函数接口都是一样的。此时兼容性的问题就转移给了DDS厂商,如果他们想让自己的DDS系统进入ROS生态,就得按照ROS的接口标准,开发一个驱动,也就是这个部分。

ROS2中DDS的核心特性

这张图讲的就是 ROS2 底层 DDS 通信的 QoS(服务质量)规则,控制话题消息怎么发、怎么存、丢包怎么办,对应机械臂、机器人通信非常关键。

左边框图:整体模型

  1. Data Domain(全局数据空间) 可以理解成一个大的 “消息共享广场”,所有节点都在这个广场交换话题数据。

  2. 参与者(Participant) 每一个 ROS2 程序节点,就是一个参与者。一个节点里面可以同时有:

  • 数据写入器 DataWriter(发布方):往话题发消息
  • 数据读取器 DataReader(订阅方):接收话题消息

注意:同一个节点,既能发布,也能订阅。

  1. 历史数据缓冲区(黄色高亮) 发布方本地会存一堆历史消息:data1 data2 data3 data4,不是发完就扔掉。 网络不好的时候,订阅者晚来了、断连重连,可以补发缓存里的旧消息。 消息沿着线路:data4 → data3 → data2 → data1传给订阅者。

  2. DEADLINE 周期 图下面双向箭头代表Deadline(截止时间)。约定:发布方必须在这个周期内至少发一次数据;超时不发,订阅方就判定对方异常。

例子:机械臂关节状态,要求 10ms 必须来一次消息,如果超过 20ms 没来,就判定传感器 / 发布节点挂掉。

每个读写器都带自己一套 QoS 配置,发布方、订阅方 QoS 必须兼容,否则连不上话题!

右边表格:四大核心 QoS 策略()

1. DEADLINE 截止时间

A DataWriter and a DataReader must update data at least once every deadline period. :约定好最大间隔多久必须来一条消息。 比如设置deadline=0.1s:发布者必须每 0.1 秒至少输出一次数据。超过这个时间还没有新消息,订阅端就收到 “发布方失联” 事件。 用途:监控传感器、关节状态、控制指令心跳。

2. HISTORY 历史策略 + depth(深度)

控制缓存里面存多少条历史消息。

  • KEEP_LAST:只存最新 N 条(depth=N),旧消息直接扔掉。机器人最常用。depth=1 就是只留最新 1 条。
  • KEEP_ALL:全部历史消息都缓存下来。

depth 就是缓冲区最多存多少条。比如 depth=10,就存最近 10 条消息。 举例:网络卡顿,一下子收到一堆滞后的传感器数据,缓冲区决定到底给订阅者补发多少旧消息。

###3. RELIABILITY 可靠性策略

  1. BEST_EFFORT(尽力而为)

能发就发,网络差消息直接丢,不会重传。 适合高频传感器,比如摄像头图像,旧帧没用,丢就丢,要最新的。

  1. RELIABLE(可靠传输)

消息丢了就自动重传,保证订阅者一定收到全部消息。 适合控制指令、关节轨迹,不能丢,丢包会导致机械臂失控;代价是网络差的时候会有延迟。

###4. DURABILITY 持久性

新的订阅者 “迟到”,晚才启动,能不能收到订阅之前就已经发布过的旧消息

  • VOLATILE:晚来的订阅拿不到老消息,只能接收连接之后的新消息。
  • TRANSIENT_LOCAL:发布方本地缓存消息,后面新启动的订阅者,上来就能拿到之前发过的数据。

典型例子:机器人参数、地图,新节点启动不需要等下一轮发布,立刻拿到地图数据。

在命令行中配置DDS参数

先来试一试在命令行中配置DDS的参数。

启动第一个终端,我们使用best_effort创建一个发布者节点,循环发布任意数据,在另外一个终端中,如果我们使用reliable模型订阅同一话题,无法实现数据通信,如果修改为同样的best_effort,才能实现数据传输。

$ ros2 topic pub /chatter std_msgs/msg/Int32 "data: 42" --qos-reliability best_effort 
$ ros2 topic echo /chatter --qos-reliability reliable
$ ros2 topic echo /chatter --qos-reliability best_effort

如何去查看ROS2系统中每一个发布者或者订阅者的QoS策略呢,在topic命令后边跟一个"--verbose"参数就行了。

 ros2 topic info /chatter --verbose

DDS编程示例

接下来我们尝试在代码中配置DDS,以之前Hello World话题通信为例

启动两个终端,分别运行发布者和订阅者节点:

$ ros2 run learning_qos qos_helloworld_pub
$ ros2 run learning_qos qos_helloworld_sub

可以看到两个终端中的通信效果如下,和之前貌似并没有太大区别。

看效果确实差不多,不过底层通信机理上可是有所不同的

发布者代码解析

在代码中,如果加入QoS的配置

import rclpy                     # ROS2 Python接口库
from rclpy.node import Node      # ROS2 节点类
from std_msgs.msg import String  # 字符串消息类型
from rclpy.qos import QoSProfile, QoSReliabilityPolicy, QoSHistoryPolicy # ROS2 QoS类

"""
创建一个发布者节点
"""
class PublisherNode(Node):

    def __init__(self, name):
        super().__init__(name)        # ROS2节点父类初始化

        qos_profile = QoSProfile(     # 创建一个QoS原则
            # reliability=QoSReliabilityPolicy.BEST_EFFORT,
            reliability=QoSReliabilityPolicy.RELIABLE,
            history=QoSHistoryPolicy.KEEP_LAST,
            depth=1
        )
        self.pub = self.create_publisher(String, "chatter", qos_profile) # 创建发布者对象(消息类型、话题名、QoS原则)
        self.timer = self.create_timer(0.5, self.timer_callback)         # 创建一个定时器(单位为秒的周期,定时执行的回调函数)

    def timer_callback(self):                                # 创建定时器周期执行的回调函数
        msg = String()                                       # 创建一个String类型的消息对象
        msg.data = 'Hello World'                             # 填充消息对象中的消息数据
        self.pub.publish(msg)                                # 发布话题消息
        self.get_logger().info('Publishing: "%s"' % msg.data)# 输出日志信息,提示已经完成话题发布

def main(args=None):                           # ROS2节点主入口main函数
    rclpy.init(args=args)                      # ROS2 Python接口初始化
    node = PublisherNode("qos_helloworld_pub") # 创建ROS2节点对象并进行初始化
    rclpy.spin(node)                           # 循环等待ROS2退出
    node.destroy_node()                        # 销毁节点对象
    rclpy.shutdown()                           # 关闭ROS2 Python接口

完成代码的编写后需要设置功能包的编译选项,让系统知道Python程序的入口,打开功能包的setup.py文件,加入如下入口点的配置:

entry_points={
    'console_scripts': [
     'qos_helloworld_pub  = learning_qos.qos_helloworld_pub:main',
},

订阅者代码解析

订阅者中的QoS配置和发布者类似。

import rclpy                                     # ROS2 Python接口库
from rclpy.node   import Node                    # ROS2 节点类
from std_msgs.msg import String                  # ROS2标准定义的String消息
from rclpy.qos import QoSProfile, QoSReliabilityPolicy, QoSHistoryPolicy  # ROS2 QoS类

"""
创建一个订阅者节点
"""
class SubscriberNode(Node):

    def __init__(self, name):
        super().__init__(name)         # ROS2节点父类初始化

        qos_profile = QoSProfile(      # 创建一个QoS原则
            # reliability=QoSReliabilityPolicy.BEST_EFFORT,
            reliability=QoSReliabilityPolicy.RELIABLE,
            history=QoSHistoryPolicy.KEEP_LAST,
            depth=1
        )

        self.sub = self.create_subscription(\
            String, "chatter", self.listener_callback, qos_profile) # 创建订阅者对象(消息类型、话题名、订阅者回调函数、QoS原则)

    def listener_callback(self, msg):                      # 创建回调函数,执行收到话题消息后对数据的处理
        self.get_logger().info('I heard: "%s"' % msg.data) # 输出日志信息,提示订阅收到的话题消息

def main(args=None):                               # ROS2节点主入口main函数
    rclpy.init(args=args)                          # ROS2 Python接口初始化
    node = SubscriberNode("qos_helloworld_sub")    # 创建ROS2节点对象并进行初始化
    rclpy.spin(node)                               # 循环等待ROS2退出
    node.destroy_node()                            # 销毁节点对象
    rclpy.shutdown()                               # 关闭ROS2 Python接口

完成代码的编写后需要设置功能包的编译选项,让系统知道Python程序的入口,打开功能包的setup.py文件,加入如下入口点的配置:

entry_points={
    'console_scripts': [
     'qos_helloworld_pub  = learning_qos.qos_helloworld_pub:main',
     'qos_helloworld_sub  = learning_qos.qos_helloworld_sub:main',
    ],
},

Launch:多节点启动与配置脚本

运行ROS节点时,每次都要打开新终端输入命令确实很繁琐,尤其是当机器人系统包含多个节点时。那么有没有办法能一次性启动所有节点呢?

其实ROS提供了Launch启动文件功能,它可以轻松实现多节点的统一启动和配置。

Launch文件

这是一个完整的Launch文件,乍看上去,好像Python代码呀,没错,ROS2中的Launch文件就是基于Python描述的

Launch的核心目的是启动节点,我们在命令行中输入的各种参数,在Launch文件中,通过类似这样的很多代码模版,也可以进行配置,甚至还可以使用Python原有的编程功能,大大丰富了启动过程中的多样化配置。

Launch文件在ROS系统中出现的频次相当之高,它就像粘合剂一样,可以自由组装和配置各个节点,那如何编写或者阅读一个Launch文件呢,我们通过一系列例程带领大家来了解。

源码分析

ROS2 launch.py 代码通俗解析 + Java 类比

ROS2 的launch.py本质:用 Python 写配置,组装要启动的节点 / 子 launch 文件,最后一次性交给 ROS2 启动器运行,不是普通 Python 业务程序

整体流程梳理
def generate_launch_description():
    # 1. 定义路径、参数变量
    # 2. 引入另一个launch文件(mbot.launch.py) → 类比Java调用别的方法
    # 3. 引入Gazebo官方的launch文件,启动仿真环境
    # 4. 定义一个Node:spawn_entity.py,把机器人模型生成到Gazebo世界里
    # 5. 把上面全部组件打包返回,ROS2框架拿到返回值,执行启动
    return LaunchDescription([mbot, gazebo, spawn_entity])

📌关键知识点:generate_launch_description() 不是我们自己调用,是 ROS2 launch 框架自动调用。 类比 Java:你写一个类,实现框架接口,框架反射调用你的方法拿到配置对象。


逐段拆解 + Java 类比
① 路径拼接部分

package_name='learning_gazebo'
world_file_path = 'worlds/neighborhood.world'
pkg_path = os.path.join(get_package_share_directory(package_name))
world_path = os.path.join(pkg_path, world_file_path)
  • get_package_share_directory("包名"):拿到 ROS 包的安装路径,相当于 Java 里读取资源文件,自动找share下资源,不用写死绝对路径。
  • os.path.join():安全拼接文件路径,兼容 Linux 斜杠。

Java 类比

// 相当于从classpath找资源文件
String pkgPath = ResourceLoader.getPackagePath("learning_gazebo");
String worldPath = Paths.get(pkgPath, "worlds", "neighborhood.world").toString();
② 设置机器人生成位姿

spawn_x_val = '0.0'
spawn_y_val = '0.0'
spawn_z_val = '0.0'
spawn_yaw_val = '0.0'

定义机器人在 Gazebo 仿真世界的初始坐标、偏航角,后面传给 spawn 工具。

③ IncludeLaunchDescription —— 导入并启动另一个 launch 文件

mbot = IncludeLaunchDescription(
    PythonLaunchDescriptionSource([os.path.join(
        get_package_share_directory(package_name),'launch','mbot.launch.py'
    )]), launch_arguments={'use_sim_time': 'true', 'world':world_path}.items()
)
  • IncludeLaunchDescription包含并运行另一个 launch 脚本 mbot.launch.py
  • PythonLaunchDescriptionSource:指定要调用的 launch 文件路径
  • launch_arguments:给被调用的 launch 传参数,use_sim_time:=true开启仿真时间,把 world 世界路径传过去。 .items()把字典转成(key,value)元组列表,这里就是你之前遇到报错dictionary update sequence element #0 has length 1;2 is required的地方!传错格式直接崩溃

Java 类比

// 调用另一个配置,传入参数,类似调用别的Service/方法
LaunchComponent mbot = launch.include("mbot.launch.py")
        .addArg("use_sim_time","true")
        .addArg("world",worldPath);

相当于:Java 中,主方法不去写全部逻辑,调用另外一个模块,同时给它传参数。

⚠️注意:IncludeLaunchDescription只是描述对象,此时不会执行,只是登记,等最后返回给框架才会真正启动。

④ 导入 Gazebo 官方 launch
gazebo = IncludeLaunchDescription(
    PythonLaunchDescriptionSource([os.path.join(
        get_package_share_directory('gazebo_ros'), 'launch', 'gazebo.launch.py')]),
)

调用系统包gazebo_ros自带 launch,启动 Gazebo 仿真器 GUI、物理引擎。 类比 Java:调用第三方库提供的初始化方法,启动仿真环境。

⑤ Node 定义要运行的可执行程序 spawn_entity.py
spawn_entity = Node(package='gazebo_ros', executable='spawn_entity.py',
    arguments=['-topic', 'robot_description',
               '-entity', 'mbot',
               '-x', spawn_x_val,
               '-y', spawn_y_val,
               '-z', spawn_z_val,
               '-Y', spawn_yaw_val],
    output='screen')
  • Node:声明一个 ROS 节点。
    • package:属于哪个功能包
    • executable:要运行的程序脚本 spawn_entity.py
  • arguments:命令行参数。 逻辑:订阅robot_description话题拿到机器人 URDF 模型,在 Gazebo 世界生成实体,名字叫mbot,放在 x/y/z/yaw 坐标。
  • output='screen':把程序打印日志输出到终端。

Java 类比

// 登记要启动的进程,类似Runtime.exec(),但只是声明,还没跑
LaunchComponent spawnEntity = launch.newNode()
        .packageName("gazebo_ros")
        .executable("spawn_entity.py")
        .args("-topic","robot_description","-entity","mbot","-x","0.0")
        .outputScreen();

⑥ 返回全部待启动组件

return LaunchDescription([
    mbot,
    gazebo,
    spawn_entity,
])

把上面 3 个组件打包,返回LaunchDescription对象交给 ROS2 框架。

⚠️这里非常关键:整个脚本从头到尾只是组装 “启动计划”,没有任何东西真正运行! 只有框架拿到这个返回对象之后,才会依次:启动 mbot 子 launch、启动 Gazebo、运行 spawn_entity 节点。

Java 类比

// 返回全部待启动组件列表,框架拿到再调度执行
return new LaunchDescription(List.of(mbot,gazebo,spawnEntity));

就像 SpringBoot:你装配 Bean,不会在装配的时候执行业务逻辑,容器拿到配置后才实例化、启动

多节点启动

启动终端,使用ros2中的launch命令来启动第一个launch文件示例:

 ros2 launch learning_launch simple.launch.py

运行成功后,就可以在终端中看到发布者和订阅者两个节点的日志信息啦

文件解析

from launch import LaunchDescription           # launch文件的描述类
from launch_ros.actions import Node            # 节点启动的描述类

def generate_launch_description():             # 自动生成launch文件的函数
    return LaunchDescription([                 # 返回launch文件的描述信息
        Node(                                  # 配置一个节点的启动
            package='learning_topic',          # 节点所在的功能包
            executable='topic_helloworld_pub', # 节点的可执行文件
        ),
        Node(                                  # 配置一个节点的启动
            package='learning_topic',          # 节点所在的功能包
            executable='topic_helloworld_sub', # 节点的可执行文件名
        ),
    ])

命令行参数配置

我们使用ros2命令在终端中启动节点时,还可以在命令后配置一些传入程序的参数,使用launch文件一样可以做到

运行效果

我们使用ros2命令在终端中启动节点时,还可以在命令后配置一些传入程序的参数,使用launch文件一样可以做到

ros2 run rviz2 rviz2 -d <PACKAGE-PATH>/rviz/turtle_rviz.rviz

命令后边还得跟一长串配置文件的路径,如果放在launch文件里,启动就优雅很多了:

ros2 launch learning_launch rviz.launch.py

文件解析

import os

from ament_index_python.packages import get_package_share_directory # 查询功能包路径的方法

from launch import LaunchDescription    # launch文件的描述类
from launch_ros.actions import Node     # 节点启动的描述类


def generate_launch_description():      # 自动生成launch文件的函数
   rviz_config = os.path.join(          # 找到配置文件的完整路径
      get_package_share_directory('learning_launch'),
      'rviz',
      'turtle_rviz.rviz'
      )

   return LaunchDescription([           # 返回launch文件的描述信息
      Node(                             # 配置一个节点的启动
         package='rviz2',               # 节点所在的功能包
         executable='rviz2',            # 节点的可执行文件名
         name='rviz2',                  # 对节点重新命名
         arguments=['-d', rviz_config]  # 加载命令行参数
      )
   ])

资源重映射

ROS社区中的资源非常多,当我们使用别人代码的时候,经常会发现通信的话题名称不太符合我们的要求,能否对类似的资源重新命名呢?

为了提高软件的复用性,ROS提供了资源重映射的机制,可以帮助我们解决类似的问题。

运行效果

启动一个终端,运行如下例程,很快会看到出现了两个小海龟仿真器界面;再打开一个终端,发布如下话题,让海龟1动起来,海龟2也会一起运动:

$ ros2 launch learning_launch rviz.launch.py
$ ros2 topic pub --rate 1 /turtle1/cmd_vel geometry_msgs/msg/Twist "{linear: {x: 2.0, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: 1.8}}"

文件解析

为什么两个海龟都会动呢?这里要用到turtlesim功能包里另外一个节点,叫做mimic,它的功能是订阅某一个海龟的Pose位置,通过计算,变换成一个同样运动的速度指令,发布出去。

至于mimic节点订阅或者发布的话题名叫什么呢?我们就可以通过重映射修改成对应任意海龟的名字。

from launch import LaunchDescription      # launch文件的描述类
from launch_ros.actions import Node       # 节点启动的描述类

def generate_launch_description():        # 自动生成launch文件的函数
    return LaunchDescription([            # 返回launch文件的描述信息
        Node(                             # 配置一个节点的启动
            package='turtlesim',          # 节点所在的功能包
            namespace='turtlesim1',       # 节点所在的命名空间
            executable='turtlesim_node',  # 节点的可执行文件名
            name='sim'                    # 对节点重新命名
        ),
        Node(                             # 配置一个节点的启动
            package='turtlesim',          # 节点所在的功能包
            namespace='turtlesim2',       # 节点所在的命名空间
            executable='turtlesim_node',  # 节点的可执行文件名
            name='sim'                    # 对节点重新命名
        ),
        Node(                             # 配置一个节点的启动
            package='turtlesim',          # 节点所在的功能包
            executable='mimic',           # 节点的可执行文件名
            name='mimic',                 # 对节点重新命名
            remappings=[                  # 资源重映射列表
                ('/input/pose', '/turtlesim1/turtle1/pose'),         # 将/input/pose话题名修改为/turtlesim1/turtle1/pose
                ('/output/cmd_vel', '/turtlesim2/turtle1/cmd_vel'),  # 将/output/cmd_vel话题名修改为/turtlesim2/turtle1/cmd_vel
            ]
        )
    ])

ROS参数设置

ROS系统中的参数,也可以在Launch文件中设置。

运行效果

启动一个终端,运行如下命令:

$ ros2 launch learning_launch parameters.launch.py

在启动的海龟仿真器中,我们看到背景颜色被改变了,这个颜色参数的设置就是在launch文件中完成的。

文件解析

from launch import LaunchDescription                   # launch文件的描述类
from launch.actions import DeclareLaunchArgument       # 声明launch文件内使用的Argument类
from launch.substitutions import LaunchConfiguration, TextSubstitution

from launch_ros.actions import Node                    # 节点启动的描述类


def generate_launch_description():                     # 自动生成launch文件的函数
   background_r_launch_arg = DeclareLaunchArgument(
      'background_r', default_value=TextSubstitution(text='0')     # 创建一个Launch文件内参数(arg)background_r
   )
   background_g_launch_arg = DeclareLaunchArgument(
      'background_g', default_value=TextSubstitution(text='84')    # 创建一个Launch文件内参数(arg)background_g
   )
   background_b_launch_arg = DeclareLaunchArgument(
      'background_b', default_value=TextSubstitution(text='122')   # 创建一个Launch文件内参数(arg)background_b
   )

   return LaunchDescription([                                      # 返回launch文件的描述信息
      background_r_launch_arg,                                     # 调用以上创建的参数(arg)
      background_g_launch_arg,
      background_b_launch_arg,
      Node(                                                        # 配置一个节点的启动
         package='turtlesim',
         executable='turtlesim_node',                              # 节点所在的功能包
         name='sim',                                               # 对节点重新命名
         parameters=[{                                             # ROS参数列表
            'background_r': LaunchConfiguration('background_r'),   # 创建参数background_r
            'background_g': LaunchConfiguration('background_g'),   # 创建参数background_g
            'background_b': LaunchConfiguration('background_b'),   # 创建参数background_b
         }]
      ),
   ])

加载参数文件

以上例程我们在launch文件中一个一个的设置参数,略显麻烦,当参数比较多的时候,建议使用参数文件进行加载。

import os

from ament_index_python.packages import get_package_share_directory  # 查询功能包路径的方法

from launch import LaunchDescription   # launch文件的描述类
from launch_ros.actions import Node    # 节点启动的描述类


def generate_launch_description():     # 自动生成launch文件的函数
   config = os.path.join(              # 找到参数文件的完整路径
      get_package_share_directory('learning_launch'),
      'config',
      'turtlesim.yaml'
      )

   return LaunchDescription([          # 返回launch文件的描述信息
      Node(                            # 配置一个节点的启动
         package='turtlesim',          # 节点所在的功能包
         executable='turtlesim_node',  # 节点的可执行文件名
         namespace='turtlesim2',       # 节点所在的命名空间
         name='sim',                   # 对节点重新命名
         parameters=[config]           # 加载参数文件
      )
   ])

Launch文件包含

在复杂的机器人系统中,launch文件也会有很多,此时我们可以使用类似编程中的include机制,让launch文件互相包含。

import os

from ament_index_python.packages import get_package_share_directory  # 查询功能包路径的方法

from launch import LaunchDescription                 # launch文件的描述类
from launch.actions import IncludeLaunchDescription  # 节点启动的描述类
from launch.launch_description_sources import PythonLaunchDescriptionSource
from launch.actions import GroupAction               # launch文件中的执行动作
from launch_ros.actions import PushRosNamespace      # ROS命名空间配置

def generate_launch_description():                   # 自动生成launch文件的函数
   parameter_yaml = IncludeLaunchDescription(        # 包含指定路径下的另外一个launch文件
      PythonLaunchDescriptionSource([os.path.join(
         get_package_share_directory('learning_launch'), 'launch'),
         '/parameters_nonamespace.launch.py'])
      )

   parameter_yaml_with_namespace = GroupAction(      # 对指定launch文件中启动的功能加上命名空间
      actions=[
         PushRosNamespace('turtlesim2'),
         parameter_yaml]
      )

   return LaunchDescription([                        # 返回launch文件的描述信息
      parameter_yaml_with_namespace
   ])

功能包编译配置

 data_files=[
        ('share/ament_index/resource_index/packages',
            ['resource/' + package_name]),
        ('share/' + package_name, ['package.xml']),
        (os.path.join('share', package_name, 'launch'), glob(os.path.join('launch', '*.launch.py'))),
        (os.path.join('share', package_name, 'config'), glob(os.path.join('config', '*.*'))),
        (os.path.join('share', package_name, 'rviz'), glob(os.path.join('rviz', '*.*'))),
    ],

    ...

Logo

DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。

更多推荐