Table of Contents
1. launch文件的作用:
- .launch文件是ROS中用于同时启动多个节点的重要文件,在大型的ROS项目中使用频繁,所以掌握其主要元素与属性对ROS系统的应用至关重要。
- 前面已经提过关于launch 档的角色,很类似bash 档,基本上就是把所有为了执行某个特定功能所需要的指令都写在一张纸上,交给ROS 一次执行开来。举例来说,今天我想执行SLAM (即时建图及定位)的功能,但是这个演算法并不简单,我必须喂好几个输入资料进去,所以,在不知道launch 档好处的情况下,我就必须在终端机上分别打开。可以自订输入的参数,可以说是相当方便。
案例:
<launch>
<arg name="gdb" default="false" />
<arg name="use_sim_time" default="false"/>
<group if="$(arg gdb)" >
<param name="use_sim_time" value="$(arg use_sim_time)"/>
<node name="hybrid_locator_node" pkg="hybrid_locator_ros"
type="hybrid_locator_node" output="screen" launch-prefix="gdb -ex run --args">
</node>
</group>
<group unless="$(arg gdb)">
<param name="use_sim_time" value="$(arg use_sim_time)"/>
<node name="hybrid_locator_node" pkg="hybrid_locator_ros"
type="hybrid_locator_node" output="screen">
</node>
</group>
<!-- gmapping -->
<node pkg="gmapping" type="slam_gmapping" name="slam_gmapping" output="screen">
<remap from="scan" to="scan"/>
<param name="map_update_interval" value="5.0"/>
<param name="maxUrange" value="28.0"/>
<param name="sigma" value="0.05"/>
<param name="kernelSize" value="1"/>
<param name="lstep" value="0.05"/>
<param name="astep" value="0.05"/>
<param name="iterations" value="5"/>
<param name="lsigma" value="0.075"/>
<param name="ogain" value="3.0"/>
<param name="lskip" value="0"/>
<param name="srr" value="0.1"/>
<param name="srt" value="0.2"/>
<param name="str" value="0.1"/>
<param name="stt" value="0.2"/>
<param name="linearUpdate" value="1.0"/>
<param name="angularUpdate" value="0.5"/>
<param name="temporalUpdate" value="3.0"/>
<param name="resampleThreshold" value="0.5"/>
<param name="particles" value="30"/>
<param name="xmin" value="-50.0"/>
<param name="ymin" value="-50.0"/>
<param name="xmax" value="50.0"/>
<param name="ymax" value="50.0"/>
<param name="delta" value="0.05"/>
<param name="llsamplerange" value="0.01"/>
<param name="llsamplestep" value="0.01"/>
<param name="lasamplerange" value="0.005"/>
<param name="lasamplestep" value="0.005"/>
</node>
<include file="$(find carto_slam_cad)/launch/start_bag_rviz.launch"/>
<arg name="bag_filename_laser" default="/home/megvii/data/laser_mark/2020-08-05-07-27-54.bag"/>
<node name="playbag" pkg="rosbag" type="play" output="screen" args="--clock -r 1 --start 20 $(arg bag_filename_laser)" />
<!-- tf trasform-->
<!-- <node pkg="tf" type="static_transform_publisher" name="link1_broadcaster"
args="1 0 0 0 0 0 1 base_link front_laser_link 100" />-->
<!-- 启动rviz -->
<arg name="rviz" default="false" />
<group if="$(arg rviz)">
<node launch-prefix="nice" pkg="rviz" type="rviz" name="rviz" required="false" args="-d $(find carto_mapping)/launch/laser_feature.rviz" />
</group>
</launch>
2 内容详解
2.1 launch文件详解
- Launch 档实际上使用YAML 格式,说穿了,Launch 档就是一种脚本语言(Duh!)所以如果你有碰过XML, XAML, HTML等语言过,便可驾轻就熟。有几种关键语法:
2.2 宣告launch 档
<launch> … </launch>
在launch文件一开头和结尾都必须用这个宣告框出来,像这样:
<launch>
…
</launch>
2.3 引数<arg>
引数通常用来作为执行各节点或launch 档所需要的输入参数,换句话说,设定区域变数。
- 通常需要使用者输入所需的数值,但也可以事先写好预设的数值。
- 引数作为一个逻辑判断,决定那些节点要执行,哪些不用。
引数的语法会像这样:
- <arg name="…" value="…">
其中name是参数的名称。Value 是参数的值。有时候也用default=”…”来设定预设值。
以下举几个例子:
<arg name=”max_value” value=”0.5”>
<arg name=”height_above_ground” default=”1.6”>
<arg name=”camera_input” value=”/camera”>
<arg name=”sensors_on” value=”true”> <!—下面章节会再提及这个指令的用法–>
2.4 注解<!– –>
举几个例子:
<!—Turn on laser–>
<!—Fire up Rviz–>
<!—Just want to comment out this line–>
<!–<node name=”foo” pkg=”foo_pkg” type=”foo”>–>
2.5 呼叫节点<node />
呼叫节点会包含以下几个参数:
- <node pkg="…" type="…" name="…" respawn=true ns="…" args=”….”/>
<!—记得后面要写成/>要不然执行的时候会出错!–>
里面的参数及其公用:
参数 | 功用 |
---|---|
pkg | 表示该节点所在的包裹 |
type | 表示这个节点实际的名称,也就是开发的时候取的名字 |
name | 虽然也是指该节点的名称,不过你可以再另外帮这个节点取名字,那么该节点便会把原名给覆盖掉, 以这个名称表示。你可以在执行时,用rqt或者rosnode list, rosnode info等指令查看到。 |
respawn/required | respawn是当该节点由于不明原因停止执行的时候,会自动重新启动。 required比较霸道一点,当该节点停止执行的时候,会让整个launch 档都停止执行、关闭。 |
ns | 指明在哪一个工作区间(workspace)的时候执行该节点,当你必须在多个子类别的实体(instance)中执行同一个节点的时候会很用。 |
若要设定该节点的印用参数,可以在节点内下以下指令:<args name="" value=””>
基本上跟上述的引数用法差不多,但是当要引用使用者在上面小节给的数值的话,记得这样打:
<arg name=”camera” value=”/camera/rgb/image_raw”> <!—这是文件一开头时的引数–>….
<node pkg=”foo_pkg” type=”foo” name=”foo”>
<param name=”camera_namespace” value=”$(arg camera)”>
</node>
<!—记得要加入这个做结尾呦–>
其中,$(arg ….) 会自动去前面的<arg>找数值读进去。
除了<args>以外,还有其他选项,如以下:
参数 | 功用 |
---|---|
<remap> | 用法是<remap from=”…” to=”…”>。将原本节点的输入管道,接"到"新的topics 上面。 |
<env> | 让该节点读入环境变数 |
<rosparam> | 让该节点读进参数设定档 |
<param> | 设定该节点所需的参数 |
这边只是列举几个比较常见参数。当然,还有更多参数选项,可以参考ROS Wiki文件。
2.6 呼叫其他launch 档<include>
它的语法其实就是让ROS去找目标launch档的路径。
$(find <pkg>)这种语法来直接找包裹下的路径,所以不管这个包裹的路径被更改,程式照样能找得到目标。请看下面范例:
<include file="$(find openni2_launch)/launch/openni2.launch">
<arg name="camera" value="rgbd_front_top"/>
<arg name="device_id" value="#1″/>
<arg name="depth_registration" default="true"/>
</include>
以上是一个启动openni2.launch这个launch 档的语法,包含在<include>里面的则是其引数。那又要怎么知道设定那些引数呢?最简单的方法就是去看看目标launch档一开头的<arg> 标签,看看有那些设定可以更改。
2.7 逻辑判断式if & unless
讲到这边,可能你会有一个疑问。那这样的脚本语言有没有判断式,在某个情况下执行特定节点,另外一个特定情况不要执行呢?有的,但是并不像是你看过的任何高阶语言那样:
If (foo=true){
Return yes ;
}
Else
{
Return no ;
}
那怎么办?其实只要转念一想,我们可以拿作为逻辑判断的方式,但是必须搭配标签使用,写法如下:
<arg name="load_driver" default="true"/>
<group if="$(arg load_driver)">
<include file="$(find openni2_launch)/launch/openni2.launch"/>
</group>
同样的,也可以把<group>标签中的if 换成unless,整个设定就变得像是"直到收到值为真或1时,执行该节点或launch档"。
到时候在终端机执行这个launch 档的时候,如果要关闭或执行某节点或launch档,请输入:
$ roslaunch pkg node load_driver:=false
$ roslaunch pkg node load_driver:=true
要打”:=”,否则launch档要不就不理你继续执行,或者是跳出语法错误的讯息。
3、开发一个大型专案的Launch 写法
功能众多,有许多节点互相连结,所以会被隔成一层层的,所以,一个rule of thumb就是最上层的节点尽量解结的呼叫下一层的
launch 档,然后下一层的launch档在呼叫下一层的launch档。而参数的设定尽量不要越级,该层级的参数设定就直接写在该层的launch档内,而不要上面好几层的launch档直接介入。这样的方法在除错和阅读上会清晰不少。
从这个错误中我学到的教训是,不要把别人的档案库直接加进自己的档案库内,而是各别克隆(git clone)和编译,然后自己的档案库只负责自己写的程式和launch档。这样做的好处有四:
- 可以独立测试各包裹,方便除错
- 由于每个包裹仍然是个别的档案库,因此仍然可以更新成最新版本,也方便维护与同步
- 在不同的平台编译的时候,方便除错与维护。
- 我们不应该很大喇喇地窃取人家的智慧结晶,占为己有却不说声谢谢。
3.1 怎么在终端机下指令
在终端机时,只要用roslaunch指令即可,语法是:
$ roslaunch <pkg name> <launch file> <arg1>:=… <arg2>:=… <arg3>:=…
先宣告launch档所在的包裹名称,再来是launch档名称,后面的引数arg则是前面小节已经提到过的标签,其值可以被终端机上的指令覆盖掉。为了加快并简化launch的指令,其实可以直接把自打到一半,按Tab键,会自动补齐,按两下Tab键则会跳出更多选项让使用者输入正确的launch档,但是注意,有时候电脑不会帮你写后面的.launch,需要自己写完或在按Tab补齐。如果你按Tab老半天,电脑都没有反应,有两个选项,一个就是把名字自己打完执行看看,要不然就是直接source,让ROS连结到正在使用的工作空间上,如下 然后再试试看roslaunch一次。
$ souce ~/your_ws/devel/setup.bash
4 标签(元素)说明
4.1 group
1. 标签定义命名空间
<group ns="turtlesim1">
<node pkg="turtlesim" name="sim" type="turtlesim_node"/>
</group>
<group ns="turtlesim2">
<node pkg="turtlesim" name="sim" type="turtlesim_node"/>
</group>
在这里我们创建了两个节点分组并以’命名空间(namespace)’标签来区分,其中一个名为turtulesim1,另一个名为turtlesim2,两个组里面都使用相同的turtlesim节点并命名为’sim’。 这样可以让我们同时启动两个turtlesim模拟器而不会产生命名冲突
2.判别条件
组(groups)可以判别条件通过 来启用或禁用节点(nodes):
<group if="0-or-1">
. . .
</group>
<group unless="1-or-0">
. . .
</group>
4.2 node标签
任何一个launch文件的重点都是:节点(node)元素的集合。启动的每一个节点(node)都要有自己独一无二的名字(name)。
- <node pkg="" type="" name=""></node>
pkg 和 type 它们分别是:程序包名字和可执行文件的名字。 ros::init() 函数 提供的 name 信息将会全面的覆盖命名信息(launch文件中node标签里面的name 属性)
- output = “screen”
- respawn=”true” 启动完所有请求启动的节点之后,roslaunch 监测每一个节点,让它们保持正常的运行状态。对于每一个节点,当它终止时,我们可以要求 roslaunch 重新启动它.
- required=”true” 当一个必需的节点终止时,roslaunch会做出响应,终止其他所有的节点并退出它自己。 注意: 由于 required 属性和 respawn 属性的含义,所以如果你给单个的一个节点同时设置了这2个属性,roslaunch 命令会抱怨。所以不要这样设置。
- launch-prefix = “command-prefix” roslaunch 则是让所有的节点共享同一个终端。 依赖终端输入的节点,它可能要优先的保留在独立的终端上。 在例子launch文件中,我们给 teleoperation 节点使用了这个属性:launch-prefix=”xterm -e”.因为这个属性,启动这个 node 元素的 rosrun 命令大致相当于:xterm -e rosrun turtlesim turtle_teleop_key. xterm 命令会开一个新的终端窗口。 -e 参数告诉 xterm :执行其命令行剩余部分(rosrun turtlesim turtle_teleop_key)。
- ns = “namespace” 在例子launch文件中,使用这个属性创建了两个无关的 turtlesim 模拟器。 turtlesim 话题名字(turtle1/cmd_vel、turtle1/color_sensor 和 turtle1/pose )被从全局命名空间移动 到 /sim1 和 /sim2 的单独命名空间里。
- args属性 传递参数到节点. 个人理解,arg参数只在launch文件中合法(相当于局部变量),不直接传给节点,所以需 要通过node中的args属性进行传递.
4.3 remap标签
在launch文件中重新命名:使用 remap 元素:
- <remap from="original-name" to="new-name" />
如果这个 remap 是 launch 元素的一个child(子类),与 node 元素同一层级, 并在 launch 元素内的最顶层。那么这个 remapping 将会作用于后续所有的节点。
4.4 include标签
1. file属性
- <include file="path-to-launch-file" />
包含其他文件.这个 file属性期望我们添加想要包含的文件的完整路径。但是大多数时候,include 元素使用一个 find 命令来搜索一个程序包,代替一个明确的完整路径:
- <include file="$(find package-name)/launch-file-name" />
这样做是正确的:
- <include file = "find learning_tutrols"/launch/start_demo.launch" / >
这样做是错误的:
- <include file = "find learning_tutrols"/start_demo.launch" />
2. ns属性
include 元素也支持 ns 属性,可以让这个文件里的内容推送到一个命名空间里面:
<include file=". . . " ns="namespace" />
一般我们都会给 include 元素设置一个 ns 属性。
4.5 arg标签
在launch文件中,要声明一个参数的存在,我们使用arg元素:
- <arg name="arg-name" />
声明里面只有一个 name 是起不上什么作用的,这就像是你在程序中定义了一个 int 类型的变量,但 是你并没有使用它一样。(你至少还需要给 arg 元素分配 default 属性或 value 属性)
给arg赋值
在launch文件中使用的每一个argument必须给它分配一个 value(赋值)。两种方法:
在命令行中你可以这样做:给 roslaunch 提供一个 value :
- roslaunch package-name launch-file-name arg-name:=arg-value
在launch文件中,你可以提供一个 value(赋值) 作为 arg 声明的一部分,使用下面的两种语法之一就可以:
- <arg name="arg-name" default="arg-value" />
- <arg name="arg-name" value="arg-value" />
这两种语法的唯一不同是:命令行可以覆盖default的值,但是不能覆盖 value 。
获取argument的数值
- $(arg arg-name)
$() 这个符号出现的任何地方,roslaunch命令都将会把它替换成给定argument 的值(value)。
关于argument的继承
在argument的传递上有一个限制,就是argument不能传递给 include 元素里包含的子launch文件 使用。这个问题非常重要,因为这个 argument 就像是一个局部变量,它不能被包含的launch文件 所 “继承” 。 解决这个问题的方法:在 include 元素中插入 arg 元素作为 include 元素的子类(children),就像是这样:
<include file="path-to-launch-file">
<arg name="arg-name" value="arg-value"/>
. . .
</include>
这里的 arg 元素不同于我们已经知道的 arg 声明,在 inchude 标签内的arguments是给包含 (included) 的launch文件提供的arguments,不是为本launch文件提供的。
一种常见的情况是,被包含(included)的launch文件和本launch文件会有共同的参数。在这种情况下,我们希望这些值(values)永远不变。像这样的元素,在这两个地方使用相同的argument name (参数名),要这样做:
- <arg name="arg-name" value="$(arg arg-name)" />
在这种情况下,第一个 arg-name 和往常一样。第二个 arg-name 是launch文件中提供的。结果是,这两个launch文件中给定的argument具有相同的值(value)。
5. 拓展说明
使用 roslaunch 命令 和 使用 rosrun 命令 单独运行每个节点之间的重要区别 。默认情况下,roslaunch 命令 从启动节点开始,标准输出信息会重定向到一个日志文件中,而不会像 rosrun 命令那样,将 log 信息显示在终端(console)上. 日志文件所在路径: ∼/.ros/log/run_id/node_name-number-stdout.log
node 元素的 output 属性只能影响这个节点自己。除了 output 属性,我们可以使用 roslaunch命令行工具的 –screen 命令行选项强制性的在终端的窗口中显示所有节点的输出信息。
- $ roslaunch --screen package-name launch-file-name
5.1 重映射名字
在启动一个节点的时候,有两种方法创建重映射:
在终端命令行中启动一个节点时,要重新给这个节点命名:给出一个节点原来的名字和新的名字, 中间用:=分开。
终端命名
- original-name:=new-name
- 例如: $ rosrun turtlesim turtlesim_node turtle1/pose:=tim
launch 重命名
在launch文件中重新命名:使用 remap 元素:
- <remap from="original-name" to="new-name" />
launch 重命名作用:
- from 一端都是你代码中的话题名
1.重命名一个已经存在的主题。
在自己的lanuch文件中,修改自己的发布的主题名字为别人要订阅的主题的名字。
- from=“original-name”: 你节点中原来发布的主题名字
- to=“new-name”: 重映射的目标名字
2.将别人发布的主题映射到自己订阅的主题上。
在自己的lanuch文件中,修改别人发布的主题为自己要订阅的主题的名字。
- from=“original-name”: 你节点中订阅的主题名字
- to=“des-name”: 别人发布的主题名字
如果这个 remap 是 launch 元素的一个child(子类),与 node 元素同一层级, 并在launch 元素内的最顶 层。 那么这个remapping 将会作用于后续所有的节点。 这个 remap 元素也可以作为 node 元素的一个child(子类)出现。下面是使用模板:
<node node-attributes >
<remap from="original-name" to="new-name" />
. . .
</node>
- 尽管术语argument和parameter在许多计算机环境中稍微可以互换使用,它们的含义在ROS中有很大的不同。Parameters(参数)在一个运行的ROS系统中是变量(values),它被存储在parameter服务器中,活动(或者叫:运行)的节点通过ros::param::get()函数访问它,并且用户可以通过 rosparam 命令行工具使用它。相比之下,arguments只有在launch文件里合法,它们的值不是直接提供给节点。
5.2 parameter说明
在C++文件中,set或者get处理parameter参数
- r o s : : param : : s e t ( ” background_r ” , 2 5 5 ) ;
- b o o l ok = r o s : : param : : g e t (PARAM_NAME, maxVel ) ;
在launch文件中设置parameter,使用param标签:
- <param name="param-name" value="param-value" />
作为节点元素的子元素的param元素中给出的参数名称始终被解析为私有名称,无论它们是以〜还是偶数开头。
在launch文件中也支持等同与rosparam load 功能的rosparam标签
- <rosparam command="load" file="path-to-param-file" />
这里的file是.yaml类型.