ROS launch文件解析
1. 宣告launch 檔<launch> … </launch>
在launch檔案一開頭和結尾都必須用這個宣告框出來,像這樣:
<launch>
…
</launch>
2.引數
引數通常用來作為執行各節點或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”> <!—下面章節會再提及這個指令的用法–>
而在Indigo新版本中arg引數還存在選項doc=“description for this arg” (optional),這個選項是用來描述引數的說明文字。
3.註釋程式碼<!– –>
舉幾個例子:
<!—Turn on laser–>
<!—Fire up Rviz–>
<!—Just want to comment out this line–>
<!–<node name=”foo” pkg=”foo_pkg” type=”foo”>–>
4.節點<node />
呼叫節點會包含以下幾個引數:
<node pkg="…" type="…" name="…" respawn=true ns="…" args=”….” output="screen"/>
<!—記得後面要寫成/>要不然執行的時候會出錯!–> 裡面的引數及其公用:
引數 | 功用 |
---|---|
pkg | 表示要啟動的節點所在的package |
type | 表示自己寫的節點.cpp程式通過編譯生產的可執行檔案的名字,你最初編譯.cpp程式的時候要在CMakeLists.txt新增cpp程式編譯的設定,這個可執行檔案的名字在CMakeLists.txt中就可以找到。如果是Python檔案時,則是filename.py檔案,如:type=“inplace_pick_place_demo.py” |
name | 指該節點的名稱,不過可以再另外幫這個節點取名字,那麼該節點便會把原名給覆蓋掉,以這個名稱表示。你可以在執行時,用rqt或者rosnode list, rosnode info等指令檢視到。 |
respawn/required | 是當該節點由於不明原因停止執行的時候,會自動重新啟動。而required比較霸道一點,當該節點停止執行的時候,會讓整個launch 檔都停止執行、關閉。 |
ns | 指明在哪一個工作區間(workspace)的時候執行該節點,當必須在多個子類別的實體(instance)中執行同一個節點的時候會很用。 |
output | 某個單獨的節點在控制檯中輸出資訊,只需在節點元素中配置:output=”screen”配置了該屬性的節點會將標準輸出顯示在螢幕上而不是記錄到日誌文件。如果記錄到log日誌文件,即為output=“log”。 |
若要設定該節點的引用引數,可以在節點內下以下指令:
<args name=”” value=””>
基本上跟上述的引數用法差不多,但是當要引用使用者在上面小節給的數值的話,可以這樣寫:
<arg name=”camera” value=”/camera/rgb/image_raw”> <!—這是檔案一開頭時的引數–>….
<node pkg=”foo_pkg” type=”foo” name=”foo”>
<args name=”camera_namespace” value=”$(arg camera)”>
</node>
<!—記得要加入這個做結尾–>
其中,$(arg ….)
會自動去前面的<arg>
找數值讀進去。
除了<args>
以外,還有其他選項,如以下:
引數 | 功用 |
---|---|
<remap> |
用法是<remap from=”…” to=”…”> 。宣告一個名稱的對映,允許你通過名稱對映引數到ROS 節點(通過更結構化的方式而不是直接設定節點引數屬性來啟動的節點)。 |
<env> |
讓該節點讀入環境變數, 制定啟動節點的環境變數 |
<rosparam> |
讓該節點讀進引數設定檔,使用rosparam 檔案設定啟動要用的ROS 引數 |
<param> |
設定該節點所需的引數 |
<node> |
啟動一個節點. |
<machine> |
宣告啟動要使用的機器. |
<include> |
包含roslaunch 檔案. |
<test> |
啟動一個測試節點see rostest). |
<arg> |
宣告引數 |
<group> |
共享一個名稱空間或對映的封閉的元素組。 |
這邊只是列舉幾個比較常見引數。當然,還有更多引數選項,可以參考ROS Wiki檔案。
5.新增其他launch 檔
它的語法其實就是讓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>
標籤,看看有那些設定可以更改。
6.邏輯判斷式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>
同樣的,也可以把標籤中的if 換成unless,整個設定就變得像是"直到收到值為真或1時,執行該節點或launch檔"。 到時候在終端機執行這個launch 檔的時候,如果要關閉或執行某節點或launch檔,請輸入:
$ roslaunch pkg node load_driver:=false
或者
$ roslaunch pkg node load_driver:=true
這樣就能決定是否執行或跳過某部分不執行。還有,要打”:=”,否則launch檔要不就不理你繼續執行,或者是跳出語法錯誤的訊息。
7.可替代引數substitution
args Roslaunch標籤屬性可以使用可替代引數args,這將在roslaunch啟動節點之前解析。 目前支援的替換引數是:
$(env ENVIRONMENT_VARIABLE)
替換當前環境中的變數值。 如果未設定環境變數,則啟動將失敗。 標記無法覆蓋此值。
$(optenv ENVIRONMENT_VARIABLE)
$(optenv ENVIRONMENT_VARIABLE default_value)
如果已設定,則替換環境變數的值。 如果提供了default_value,則在未設定環境變數時將使用它。 如果未提供default_value,則將使用空字串。 default_value可以是由空格分隔的多個單詞。
Examples:
<param name="foo" value="$(optenv NUM_CPUS 1)" />
<param name="foo" value="$(optenv CONFIG_PATH /home/marvin/ros_workspace)" />
<param name="foo" value="$(optenv VARIABLE ros rocks)" />
$(find pkg)
例如
$(findrospy)/manifest.xml.
指定包相對路徑。
包目錄的檔案系統路徑將被內聯替換。
由於硬編碼路徑(絕對路徑)會抑制啟動配置的可移植性,因此強烈建議使用與程式包相關的路徑。正向和反向斜槓將被解析為本地檔案系統約定。
$(anon name)
e.g. $(anon rviz-1).
根據名稱生成匿名ID。name本身是一個唯一識別符號:$(anon foo)
的多次使用將建立相同的“匿名”名稱。
這用於name屬性以建立具有匿名名稱的節點,因為ROS要求節點具有唯一名稱。
例如:
<node name="$(anon foo)"
pkg="rospy_tutorials" type="talker.py" />
<node name="$(anon
foo)" pkg="rospy_tutorials" type="talker.py"
/>
$(arg foo)
$(arg foo)計算為<arg>標記指定的值。
在同一個啟動檔案中必須有一個相應的標籤來宣告arg。 例如:
<param name="foo" value="$(arg
my_foo)" />
將my_foo引數分配給foo引數。
另一個例子:
<node name="add_two_ints_server"
pkg="beginner_tutorials" type="add_two_ints_server"
/>
<node
name="add_two_ints_client" pkg="beginner_tutorials"
type="add_two_ints_client" args="$(arg a) $(arg b)"
/>
將從<add_two_ints>示例啟動伺服器和客戶端,將值a和b作為引數傳遞。 生成的啟動專案可以按如下方式呼叫:
roslaunch beginner_tutorials launch_file.launch
a:=1 b:=5
$(eval <expression>) New in Kinetic
$(eval <expression>)允許評估任意複雜的python表示式。
例如:
<param
name="circumference" value="$(eval 2.* 3.1415 *arg('radius'))"/>
將從radius引數計算周長並將結果分配給適當的引數。 注意:作為限制,$(eval)表示式需要跨越整個屬性字串。 不可能在單個字串中混合使用eval的其他替換args:
<param name="foo" value="$(arg foo)$(eval 6*7)bar"/>
要解決此限制,所有替換命令也可作為eval中的函式使用:
"$(eval arg('foo') + env('PATH') + 'bar' +find('pkg')"
為方便起見,還隱式解析了引數,即以下兩個表示式是相同的:
"$(eval arg('foo'))"
"$(eval foo)"
替換args目前已在本地計算機上解決。 換句話說,環境變數和ROS包路徑將在當前環境中設定為它們的值,即使對於遠端啟動的程序也是如此。
8.if和unless屬性
所有標記都支援if和unless屬性,這些屬性包含或排除基於標記的評估值。 “1”和“真”被認為是真值。 “0”和“假”被認為是假值。 其他值將出錯。
if = value(可選)
如果value的計算結果為true,則包含標記及其內容。
unless=value(可選)
除非value計算為true(這意味著如果value的計算結果為false),則包括標記及其內容。
例子:
<group
if="$(arg foo)">
<!-- stuff that will
only be evaluated if foo is true -->
</group>
<param name="foo" value="bar" unless="$(arg
foo)" /> <!-- This param won't be set when "unless"
condition is met
9.示例——啟動XML配置檔案
按照慣例,roslaunch XML檔案以副檔名.launch命名,例如example.launch。 一個較為複雜的例子:
<launch>
<! - 本地機器預設有一個定義。此標記用於覆蓋預設定義特定的ROS_ROOT和ROS_PACKAGE_PATH值 - >
<machine name =“local_alt”address =“localhost”default =“true”ros-root =“/ u / user / ros / ros /”ros-package-path =“/ u / user / ros / ros-pkg” />
<! - 一個基本的listener 節點 - >
<node name =“listener-1”pkg =“rospy_tutorials”type =“listener”/>
<! - 將args傳遞給listener 節點 - >
<node name =“listener-2”pkg =“rospy_tutorials”type =“listener”args =“ - foo arg2”/>
<! - 一個可重新生成的listener 節點 - >
<node name =“listener-3”pkg =“rospy_tutorials”type =“listener”respawn =“true”/>
<! - 在'wg1'名稱空間中啟動listener 節點 - >
<node ns =“wg1”name =“listener-wg1”pkg =“rospy_tutorials”type =“listener”respawn =“true”/>
<! - 在'wg2'名稱空間中啟動一組節點 - >
<group ns =“wg2”>
<! - remap適用於此範圍內的所有未來語句。 - >
<remap from =“chatter”to =“hello”/>
<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”/>
<! - 節點可以有自己的重對映args - >
<remap from =“chatter”to =“hello-1”/>
<! - 您可以為節點設定環境變數 - >
<env name =“ENV_EXAMPLE”value =“some value”/>
</node>
</group>
</launch>
10.開發一個大型專案的Launch 寫法
最重要的,就是專案由於功能眾多,有許多節點互相連結,所以會被隔成一層層的,所以,一個rule of thumb就是最上層的節點儘量解結的呼叫下一層的launch 檔,然後下一層的launch檔在呼叫下一層的launch檔。而引數的設定儘量不要越級,該層級的引數設定就直接寫在該層的launch檔內,而不要上面好幾層的launch檔直接介入。這樣的方法在除錯和閱讀上會清晰不少。 另外一個我之前開發碰到的問題就是,直接將他人的包裹直接加進自己的專案內部。站在版本控制的觀念而言,每個包裹都是一個檔案庫(repository),除了在本地端維護外,更新的版本也會隨時上傳到雲端。問題就發上在,一旦你將別人的檔案庫加進自己的專案,然後推上遠端自己的檔案庫後,這些檔案庫變成你專案的一部分,再也不是他人的檔案庫,因此也無法更新成最新的版本。當我要把我的這個擁腫的包裹下載到另外一臺電腦編譯時,又與我之前安裝的他人的同樣的包裹名稱起衝突。在把他人的包裹去掉,安裝自己的包裹後,發現編譯出錯,但是密密麻麻的訊息,已經讓我很難知道錯誤的源頭。因此,後來也就決定把他人的智慧結晶從我的專案中移除,往後有需要使用到他人包裹中的某些功能時,直接用launch檔呼叫即可。 從這個錯誤中我學到的教訓是,不要把別人的檔案庫直接加進自己的檔案庫內,而是各別克隆(git clone)和編譯,然後自己的檔案庫只負責自己寫的程式和launch檔。
11.怎麼在終端機輸入指令
檔可以在自己的包裹內呼叫其他包裹的launch檔或節點,在實用上更方便。那麼在終端機時,只要用roslaunch指令即可,語法是:
$ roslaunch <pkg name> <launch file> <arg1>:=… <arg2>:=… <arg3>:=…
先宣告launch檔所在的包裹名稱,再來是launch檔名稱,後面的引數arg則是前面小節已經提到過的標籤,其值可以被終端機上的指令覆蓋掉。實際的例子:
$ roslaunch rtabmap_ros rgbd_mapping.launch rviz:=true rtabmapviz:=false
讓我們來細看上面這行指令。
Pkg name | rtabmap_ros |
---|---|
Launch file | rgbd_mapping.launch |
<arg 1> | rviz:=true |
<arg 2> | rtabmapviz:=false |
為了加快並簡化launch的指令,其實可以直接把自打到一半,按Tab鍵,會自動補齊,按兩下Tab鍵則會跳出更多選項讓使用者輸入正確的launch檔,但是注意,有時候電腦不會幫你寫後面的.launch,需要自己寫完或在按Tab補齊。如果你按Tab老半天,電腦都沒有反應,有兩個選項,一個就是把名字自己打完執行看看,要不然就是直接source,讓ROS連結到正在使用的工作空間上,如下 然後再試試看roslaunch一次。
$ souce ~/your_ws/devel/setup.bash