最近面臨一個將ROS與機器人結合起來以擴展現有機器人的功能的任務,譬如將ABB的2400機器人接上ROS的moveit,然後通過ROS去控制機器人的運動,對於IRB2400這樣的機器人實現還是蠻簡單的,但是這種情況下,只知其然而不知其所以然,感覺就不好了,所以我得將這一套東西徹底的理解清楚,整理明白。在這裏碼下來記錄一下我的學習與經歷的過程,在面對這個問題時,首先想弄明白的就是roslaunch使用的.launch文件中到底寫了些什麼鬼!
對了,roslaunch文件的擴展名爲.launch,是按照XML格式編寫的,所以XML格式的文件也一樣能解析。
1. 文件解析順序
roslaunch 以單程的方式解析XML格式文件,include是以深度優先的方式解析內容,而標籤Tag以串行的方式處理,所以一個參數的最後一次設置被認定爲有效值。
也就是說在.launch文件中同樣的設置可能存在多處,比如在開頭定義了一個變量,遵循着儘量節省空間的想法,在後面的定義中又對這個變量進行了重新賦值,以便重新利用,在這種情況下,在整個launch文件解析完成後,最終進行的那次設置是有效的,但也並不保證一定有效,不排除在其它的文件中對變量進行了重命名,所以推薦使用$(arg)/<arg>
的方式進行覆寫。
2. 置換參數
在roslaunch文件中,置換參數也稱做置換符,是最常用的,可以提高變量的重用性。下面可用的置換參數如下所示:
2.1 $(env ENVIRONMENT_VARIABLE)
其中的“$”是一個置換處理標誌(我是這麼理解的),而“env”則是說明後面的變量被當做環境變量處理,畢竟“env”是“environment”(環境)的前面仨字母是吧。這行代碼的意思就是用當前環境變量ENVIRONMENT_VARIABLE
的值置換目標變量值,不能被<env>
覆寫,爲什麼不能覆寫呢,後面有說明,如果想起來就把說明放這裏,想不起來的話就到後面<env>
裏找找吧。所以環境變量是比較嚴肅的,如果用於置換的環境變量未設置,當你啓動launch文件時則會顯示失敗。
對着例子再來囉嗦一下(感覺比較笨怕以後看到理解不了…想像一下這裏配了一張悲傷臉的表情)
<param name="variable_name" value="$(env NUM_CPUS)" />
上面例子中param
是launch文件中進行參數定義的關鍵字,name
屬性指定了要定義的變量的名稱,value
屬性是對你前面指定的變量進行賦值或進行值替代,$(env NUM_CPUS)
就是你指定要用於進行目標變量值替代的選用值,這個時候問題就來了,如果你的電腦環境變量中有名叫NUM_CPUS
的環境變量,那沒事,這行代碼可以輕鬆通過,但是如果你的電腦裏沒有這個環境變量,那你運行這個launch文件的時候就是報錯,所以相對來說,optenv
更具有魯棒性。
2.2 $(optenv ENVIRONMENT_VARIABLE)
optenv
這個標識符就沒有env
那麼嚴肅,畢竟它在env
的前面加了opt
,那就變成了optional environment
了是吧,這一行代碼就是說如果ENVIRONMENT_VARIABLE
已設置,則用其替代目標變量,若沒有設置,則用空字符串表示。但我覺得直接理解下面一條更全面。
2.3 $(optenv ENVIRONMENT_VARIABLE default_value)
此行代碼可以這樣理解,先看ENVIRONMENT_VARIABLE
環境變量有沒有設置, 若ENVIRONMENT_VARIABLE
有設置,就用ENVIRONMENT_VARIABLE
的值替代目標變量值,如果ENVIRONMENT_VARIABLE
沒有設置,那就看default_value
是否有設置,如果default_value
設置過了,那就用default_value
的值去替代目標變量的值,如果default_value
未給定則用空字符串去初始化目標變量值。
給以下三個例子:
<param name="variable_name" value="$(optenv NUM_CPUS 1)" />
上面這一行代碼中,在我的計算機中NUM_CPUS
這個環境變量不存在,所以最後variable
的值就是1這個缺省值。如果這個缺省值也沒有設置,那最後將用空字符串替代目標變量值。
<param name="pkg_path_name" value="$(optenv PATH /home/catkin_ws/src)" />
這一行代碼中,PATH
這個環境變量是存在的,所以名爲pkg_path
的變量值便爲PATH
的值。
<param name="variable_name" value="$(optenv VARIABLE ros rocks)" />
這一行是想說缺省設置的值可以是一箇中間有空格隔開的字符串。
2.4 $(find pkg)
這行代碼是用來進行相對路徑的提取。
例如:
$(find rospy)/manifest.xml
便是找到包rospy的路徑,並且會被內聯替換。
2.5 $(anon name)
產生基於名稱的匿名ID,主要用於節點名稱屬性中創建匿名節點,並檢查是否有相同的匿名名字,因爲ROS中名字作爲標識符要求具有唯一性。其中的anon是anonymous的簡寫。
例如:
<node name="$(anon talk_01)" pkg="rospy_tutorials" type="talker.py" />
<node name="$(anon talk_01)" pkg="rospy_tutorials" type="talker.py" />
則會產生錯誤,因爲系統檢測到了兩個節點具有相同的匿名名字。
2.6 $(arg varible_name)
解析由<arg>
標籤指定的變量值。
例如:
<param name="varible_name" value="$(arg my_varible)" />
要求在同一文件中my_varible
已經由<arg>
標籤所指定。即在launch文件中存在以下定義:
<arg name="my_varible_name" value="defined_value">
這樣在解析my_varible
的值的時候才能正常進行。就如同你在表達式中用一個變量進行計算,你必須在計算之前就已經定義過這個變量纔可以。
再如:
<node name="add_client" pkg="beginner" type="add_client" args="$(arg a) $(arg b)" />
其中的$(arg a)
和$(arg b)
則是對節點所需要的變量進行聲明,這樣你在對節點進行參數傳遞的時候就可以用以下代碼進行:
roslaunch beginner launch_file.launch a:=1 b:=5
假設以上代碼是launch_file.launch中的片段,則在調用文件時要按所聲明的變量名稱要求進行參數的賦值。
2.7 $(eval <expression>)
允許計算任意複雜的表達式,而標識符(Tag)或說是標籤eval其實是evaluation的前幾個字母,表示計算評估的意思,而後面則可以使用表達式。
例如:
<param name="circumference" value="$(eval 2.* 3.1415 * arg('radius'))"/>
會根據給定的'radius'
進行計算後對circumference
賦值。作爲限制,$(eval)
的作用範圍要跨越表示整個表達式的字符串,如果在其中插入其它屬性是不可行的。
例如:
<param name="a_name" value="$(arg r_dis) $(eval 6*7) bar"/>
是不可行的。但可以:
"$(eval arg('sth') + env('PATH') + 'bar' + find('pkg'))"
在內部進行字符串相加操作。
3. IF 和 UNLESS 屬性
所有的標籤都支持if和unless屬性,此類屬性是基於計算的值包含或是排除一個標籤的內容。
if = value (optional)
如果value的值爲真則包含標籤及內容。
unless = value (optional)
如果值爲假,則包括此內容。
<group if="$(arg value)">
<!-- 如果值爲真,則包含寫在group裏的內容 -->
</group>
<param name="varible_name" value="sth" unless="$(arg value)" />
<!-- 如果判斷的值爲真則不包含parma定義的變量 -->
4. 參考標籤
4.1 <launch/>
<launch>
標籤爲所有roslaunch文件的根標籤,可以將其理解爲所有其它標籤的容器。以下爲<launch>
標籤與其它標籤的關係:
<launch>
與其它標籤的關係:
<launch>
<node/>
<param/>
<remap/>
<machine/>
<rosparam/>
<include/>
<env/>
<test/>
<arg/>
<group/>
</launch>
4.2 <node/>
用來啓動節點,但不保證節點的啓動順序。其包含的屬性如下所示:
pkg="mypackage"
<!--指定所用的包-->
type="nodetype"
<!--指定對應的可執行程序的名字-->
name="nodename"
<!--指定節點名稱,但不能在這裏加入命名空間名稱-->
args="arg1 arg2 arg3"
<!--聲明或傳遞參數(optional)-->
machine="machine-name"
<!--在指定的機器上啓動節點(optional)-->
respawn="true"
<!--如果節點失敗則重新啓動(optional)-->
respawn_delay="30"
<!--如果上rospawn屬性爲真,則等待一定的時間之後重新啓動節點(optional, New in indigo)-->
required="true"
<!--將節點定義爲必需節點,如果本節點失效,則關閉整個文件(optional)-->
ns="nnss"
<!--在一個命名空間中啓動節點(optional)-->
clear_params="true|false"
<!--在節點啓動之前是否清空所在命名空間中的變量(optional)-->
output="log|screen"
<!--指定輸出的對象(optional)-->
cwd="ROS_HOME|node"
<!--指定node則節點工作路徑與其可執行程序路徑相同(optional)-->
launch-prefix="prefix arguments"
<!--預先處理指令或參數(optional,支持多種工具)-->
在<node>
節點作用範圍之內可以使用的標籤如下:
<node>
<env/>
<remap/>
<rosparam/>
<param/>
</node>
4.3 <param/>
用於在參數服務器中定義參數。其屬性參數如下所示:
name="namespace/name"
參數名稱。在這個名稱定義中是可以使用命名空間的,而在node的名稱定義中不能使用命名空間,這個是有區別的。但是應該避免指定全局的名稱。
value="value"
定義參數的值,如果這一屬性忽略掉了,但必須指定其它的文件或是命令等(optional)。
type="str|int|double|bool|yaml"
指定參數的類型(optional),如果不指定則會自動識別類型,規則如下:
1.有“.”的認爲是浮點數,沒有的則認爲是整型。
2.”true” 和 “false” 被認爲是邏輯變量 (not case-sensitive)。
3.其它的所有都爲string型。
textfile="$(find pkg-name)/path/file.txt"
文件的內容被存儲爲string類型用以替代目標變量(optional)。
binfile="$(find pkg-name)/path/file"
內容將被存儲爲 base64-encoded XML-RPC 二進制對象用以替代目標變量(optional)。
command="$(find pkg-name)/exe '$(find pkg-name)/arg.txt'"(optional)
執行某項命令,命令的輸出將被存儲爲一個string類型以替代目標變量。
舉個栗子:
<param name="publish_frequency" type="double" value="10.0" />
以上代碼就是在參數服務器中定義一個名爲:publish_frequency,類型爲:double,值爲:10.0 的變量。
如果是加載YAML文件可以用以下代碼:
<rosparam command="load" file="FILENAME" />
其中的file屬性要指定路徑和文件名。
4.4 <rosparam/>
支持從YAML文件進行參數的讀取與卸載。其屬性如下所示:
command="load|dump|delete" (optional, default=load)
rosparam的命令,可以指定 加載|卸載|刪除 對應的參數。
file="$(find pkg-name)/path/my.yaml" (load or dump commands)
指定文件所要求的路徑。
舉個例子:
<rosparam command="load" file="$(find rosparam)/example.yaml" />
<rosparam command="delete" param="my/param" />
<rosparam param="a_list">[1, 2, 3, 4]</rosparam>
第一行代碼是用來加載對應的YAML文件,第二代碼是刪除名爲my的命名空間下的param變量,第三行代碼是定義一個變量,而變量的值即爲標籤之間的所有內容 ,即將中間的所有內容作爲字符串放入到變量a_list中。
param="param-name"
指定參數名稱。
ns="namespace" (optional)
將參數設置到指定的命名空間。
subst_value=true|false (optional)
設置在YAML文本中是否允許使用替代標籤。
舉個栗子:
<arg name="whitelist" default="[3, 2]"/>
<rosparam param="blacklist" subst_value="True">$(arg whitelist)</rosparam>
如果在上一行代碼中subst_value
爲false
,則輸出的blacklist
只是一個字符串$(arg whitelist)
,表示不允許使用替代標籤,但如果爲true
則表示允許使用替代標籤,則blacklist
輸出的值爲[3,2]
。
4.5 <remap/>
用來通過名稱進行參數之間的映射。其屬性如下:
from="original-name"
指定節點原來要監聽的話題的名稱。
to="new-name"
指定節點實際要監聽的話題的名稱。
例如:
<remap from="chatter" to="hello"/>
此行代碼表明要將原來應監聽chatter話題的節點改到監聽hello話題。
4.6 <machine/>
如果在本地啓動所有節點,則並不需要此標籤,且此標籤在F版本的ROS前後語法上是不同的,要參考對應的版本,這一標籤內容在當前的任務暫時用不到,先挖個坑放這裏。
4.7 <include/>
此標籤允許在launch文件中包含其它的roslaunch文件或XML文件等。其屬性如下所示:
file="$(find pkg-name)/path/filename.xml"
指定要包含的文件路徑。
ns="namespace_name" (optional)
指定要導入文件的命名空間。
clear_params="true|false" (optional Default: false)
以上代碼意味着在文件加載之前是否要清空所有命名空間中的參數,缺省爲false,此參數要謹慎使用,用之前要覈對好命名空間名稱。
pass_all_args="true|false" (optional Default: false)
這一屬性選擇是否將當前情景下的參數傳遞到子情景之中。
在<include>
標籤範圍內可以使用的標籤如下:
<include>
<env/>
<arg/>
</include>
4.8 <env/>
此標籤用來設置接下來要啓動的節點的環境變量,其可以在<launch>
,<include>
,<node>
和<machine>
標籤中使用。當在<launch>
中使用時,<env>
標籤作用於其後聲明的所有節點。但是用此標籤聲明的環境變量通過$(env …)
不可見,所以並不能用$(env …)
對其它變量的值進行置換,所以<env>
標籤不能用來參數化launch文件。其屬性如下所示:
name="environment-variable-name"
此屬性用來設置環境變量名稱。
value="environment-variable-value"
此屬性用來設置環境變量值。
4.9 <test/>
<test>
標籤在語法上類似於<node>
標籤,都是指定一個ROS節點運行,但是<test>
標籤表明當前要運行的節點是一個測試節點。
<test>
與 <node>
具有大部分相同的屬性,但以下內容是不同的:
• 沒有respawn屬性 (測試節點必須要被終止,所以它們沒有重啓屬性)
• 沒有輸出屬性,因爲測試節點有其輸出記錄機制。
• Machine屬性被忽略。
<test>
標籤所必須的幾個屬性如下:
pkg="mypackage"
這一屬性是必需的屬性,指定節點的包名稱。
test-name="test_name"
必須的屬性,指定測試節點的名稱。
type="nodetype"
必須的屬性,指定測試節點所對應的可執行程序名稱。
<test>
標籤可選擇的屬性如下:
name="nodename"
節點名稱。PS:名稱中不能包含命名空間,如果要指定要使用ns屬性,如果此屬性未指定,則test-name將被作爲節點名稱。
args="arg1 arg2 arg3"
用測試節點傳遞參數。
clear_params="true|false"
如果是true,則在啓動之前清空當前節點私有命名空間中的全部參數。
cwd="ROS_HOME|node"
指定工作路徑。如果是node則節點的工作目錄與節點的可執行程序目錄相同。
launch-prefix="prefix arguments"
節點啓動之前的預置參數或命令。
ns="namespace_name"
在指定的命名空間中啓動節點。
retry="0"
用於有時可能失效的隨機過程,設置重新嘗試的次數。
time-limit="60.0"
認定節點啓動失敗之前要經歷的時間,缺省爲60s。
例如:
<test test-name="test" pkg="mypkg" type="test.py" time-limit="10.0" args="--test1 --test2" />
上行代碼指定要測試的節點的名稱,包名稱,可執行程序名稱,測試時間跨度和要傳遞的參數。
標籤的作用範圍中可以使用以下標籤:
<test>
<env/>
<remap/>
<rosparam/>
<param/>
</test>
4.10 <arg/>
<arg>
指令允許創建可以通過傳遞參數值進行重複使用與配置的launch文件。但<arg>
標籤是非全局的,一個聲明只針對一個launch文件,如同局部變量一樣,如果要在其它文件中使用,則必須要明確的進行值傳遞。
標籤的屬性有以下幾種:
name="arg_name"
指定變量名。
default="default value" (optional)
指定變量缺省值,不能與value屬性一起使用。
value="value" (optional)
指定變量值,不能與default屬性一起使用。
doc="description for this arg" (optional) New in Indigo
進行變量描述。
<arg>
可以通過以下三種方式使用:
(1) 聲明變量的存在
<arg name="variable_name" />
對於只進行了變量聲明的變量,必須通過參數傳遞進行使用,通過命令行傳遞或通過<include>
標籤進行傳遞。
(2) 聲明變量,並賦予一個缺省值
<arg name="variable_name" default="1" />
可以通過命令行或<include>
標籤進行值傳遞使用。
(3) 用一個常量定義變量,值不能被覆蓋
<arg name="variable_name" value="defined_somevalue" />
這種用法可以內部參數化而不需要將參數化過程暴露於更高層次,意思就是如果你是用value屬性對值直接進行定義的,那麼你是無法再你其傳遞參數,即不能對變量值進行覆寫。
例如:在test.launch中寫入以下代碼:
<launch>
<include file="$(find your_pkg)/launch/included.launch">
<arg name="s_name" value="rose" />
</include>
</launch>
而在included.launch中寫入以下代碼:
<launch>
<arg name="s_name" />
<param name="param" value="$(arg s_name)"/>
</launch>
則當運行test.launch文件時,s_name
參數會從test.launch的<include>
中傳遞進入included.launch文件中,從而產生一個變量名爲param
而值爲rose
的變量。但是由於<arg>
定義的爲一個文件內部的局部變量(類似於類內的私有變量),無法從更高一級或外部進行訪問,即不能通過命令行進行賦值,所以,當運行:
roslaunch %YOUR_ROS_PKG% test.launch s_name:=my_value
時,s_name
的值還是原來設定的value值,而不管你在局部的<arg>
屬性中是用value屬性還是default屬性。如果想用自己的定義值在命令行中進行覆蓋,則<arg>
標籤要使用更高一層級的default屬性指定。即修改test.launch如下所示:
<launch>
<arg name=”temp” default=”rose”/>
<include file="$(find your_pkg)/launch/included.launch">
<arg name="s_name" value="$(arg temp)" />
</include>
</launch>
即定義一個更高一層次的<arg>
標籤來執行值傳遞,從更高一層級傳遞到局部再傳遞到included.launch文件中。
4.11 <group/>
<group>
標籤可以對一組節點進行設置,並且可以通過ns屬性將一組節點放到一個隔離開的命名空間中。
其具有以下屬性:
ns="namespace" (optional)
將一組節點指定到一個特定的命名空間中,命名空間可以是全局的或是局部的,但並有推薦使用全局的命名空間。
clear_params="true|false" (optional)
在節點啓動之前清空<group>
命名空間中的所有參數,謹慎使用。
內部可使用的標籤如下所示:
<group>
<node/>
<param/>
<remap/>
<machine/>
<rosparam/>
<include/>
<env/>
<test/>
<arg/>
<group/>
5. launch文件的例子
5.1 一個簡單的例子:
<launch>
<node name="talker" pkg="rospy_tutorials" type="talker" />
</launch>
在本地使用當前ros環境啓動rospy_tutorials
包中的可執行程序爲talker
的節點,節點名稱定義爲talker
。
5.2 一個複雜一點的例子:
<launch>
<machine name="local_alt" address="localhost" default="true" ros-root="/u/user/ros/ros/" ros-package-path="/u/user/ros/ros-pkg" />
<!-- 本地的機器已經進行了缺省定義,這個標籤的作用是用指定的ROS_ROOT和ROS_PACKAGE_PATH值對其重新賦值-->
<node name="listener-1" pkg="rospy_tutorials" type="listener" />
<!-- 啓動一個收聽節點 -->
<node name="listener-2" pkg="rospy_tutorials" type="listener" args="-foo arg2" />
<!-- 向監聽節點中傳遞參數 -->
<node name="listener-3" pkg="rospy_tutorials" type="listener" respawn="true" />
<!-- 一個可以重新啓動的節點 -->
<node ns="wg1" name="listener-wg1" pkg="rospy_tutorials" type="listener" respawn="true" />
<!-- 在 'wg1' 命名空間中啓動一個節點 -->
<group ns="wg2">
<!-- 在 'wg2' 命名空間中啓動一組節點 -->
<remap from="chatter" to="hello"/>
<!-- remap在group範圍內作用於在其後聲明的所有節點 -->
<node pkg="rospy_tutorials" type="listener" name="listener" args="--test" respawn="true" />
<node pkg="rospy_tutorials" type="talker" name="talker">
<param name="talker_1_param" value="a value" />
<!-- 設置局部變量 -->
<remap from="chatter" to="hello-1"/>
<!-- 節點可以有其自己私有的remap指令 -->
<env name="ENV_EXAMPLE" value="some value" />
<!-- 爲節點設置環境變量 -->
</node>
</group>
</launch>
5.3 設置參數
<launch>
<param name="somestring1" value="bar" />
<!-- 值類型爲string類型 -->
<param name="somestring2" value="10" type="str" />
<!-- 強制使用string類型而不是整型定義參數 -->
<param name="someinteger1" value="1" type="int" />
<!-- 指定爲整型 -->
<param name="someinteger2" value="2" />
<!-- 自動識別爲整型 -->
<param name="somefloat1" value="3.14159" type="double" />
<!-- 指定爲浮點型數據 -->
<param name="somefloat2" value="3.0" />
<!-- 自動識別爲浮點型數據 -->
<param name="wg/childparam" value="a child namespace parameter" />
<!-- 在子命名空間中設置參數 -->
<param name="configfile" textfile="$(find roslaunch)/example.xml" />
<!-- 向參數服務器中加載文本文件內容 -->
<param name="binaryfile" binfile="$(find roslaunch)/example.xml" />
<!-- 向參數服務器中加載二進制文件內容 -->
</launch>