roslaunch在大型專案中的使用技巧

課程描述: 本教程主要介紹roslaunch在大型專案中的使用技巧。重點關注如何構建launch檔案使得它能夠在不同的情況下重複利用。我們將使用2dnav_pr2功能包作為學習案例。

課程難度: 中級

目錄

  1. 簡介
  2. 高層結構
  3. 機器標籤和環境變數
  4. 引數、名稱空間和yaml檔案
  5. launch檔案的重用
  6. 引數過載
  7. roslaunch引數

官網文件:中文 英文

友情提醒,本章實驗需要啟動模擬,需要較多資源,線上系統可能較慢,推薦通過文件學習保證知識體系完備性,為保證實驗體驗,請按之前章節學習過的基礎,自學安裝Linux和ROS並進行實驗。

簡介

機器人上的應用經常涉及到節點之間的互動,每個節點都有很多的引數。在二維平面上的導航就是一個很好的例子。2dnav_pr2功能包包含了基本的運動節點、定位、地面識別、底層控制器和地圖伺服器。 同時, 還有幾百個ROS引數影響著這些節點的行為模式。此外,也還存在著一些額外的約束。例如為了提高效率,地面識別節點應該跟傾斜的鐳射節點執行在同一臺機器上。

一個roslaunch檔案能夠讓你一次性將這些都配置好。在一個機器人上,用roslaunch啟動2dnav_pr2功能包裡的2dnav_pr2.launch檔案就可以啟動機器人導航時所需的所有東西。在本教程中,我們將仔細研究launch檔案和它所具有的功能。

我們希望launch檔案能夠儘可能的重用,這樣在不同的機器人平臺上就不需要修改這些launch檔案就能使用。即使是從真實的環境轉到模擬環境中也只要稍微修改即可。接下來,我們將要研究如何構建launch檔案才能實現其最大化的重用。

高層結構

這是一個高層級的launch檔案 (利用指令 "rospack find 2dnav_pr2/move_base/2dnav_pr2.launch"可以找到)。

<launch>
  <group name="wg">
    <include file="$(find pr2_alpha)/$(env ROBOT).machine" />
    <include file="$(find 2dnav_pr2)/config/new_amcl_node.xml" />
    <include file="$(find 2dnav_pr2)/config/base_odom_teleop.xml" />
    <include file="$(find 2dnav_pr2)/config/lasers_and_filters.xml" />
    <include file="$(find 2dnav_pr2)/config/map_server.xml" />
    <include file="$(find 2dnav_pr2)/config/ground_plane.xml" />

    <!-- The navigation stack and associated parameters -->
    <include file="$(find 2dnav_pr2)/move_base/move_base.xml" />
  </group>
</launch>

這個檔案引用了其他的檔案。在這些被引用的檔案中都包含有與系統有關的節點和引數(甚至是巢狀引用),比如定位、感測器處理和路徑規劃。

編寫技巧提示:高層級的launch檔案應該簡短,利用include指令將系統的組成部分和ROS引數引用過來即可。

接下來我們將會看到,這種技巧使得我們可以很容易的替換掉系統的某個部分。

想要在PR2執行這個應用,我們需要啟動核心,接著roslaunch一個具體機器人的launch檔案,例如在pr2_alpha功能包裡的pre.launch檔案,最後roslaunch啟動2dnav_pr2.launch。與其分開這麼多的roslaunch檔案,我們可以一次性將它們roslaunch起來。這會有一些利弊權衡:

優點: 我們可以少做幾個 "開啟新終端, roslaunch" 的步驟。

缺陷1: roslaunch一個launch檔案會有一個持續一段時間的校準過程。如果2dnav_pr2的launch檔案引用了機器人的launch檔案,當我們用control-c終止這個roslaunch程序,然後又再次開啟這個程序,校準過程還得再來一遍。

缺陷2: 一些導航node要求校準過程必須在它啟動之前完成。roslaunch目前還沒有對節點的啟動時間和順序進行控制。最完美的方案當然是讓導航節點等到校準過程完成後再啟動,但就目前的情況來看,把他們分別放在兩個launch檔案裡,直到校準過程結束再啟動導航節點是個可行的方案。

因此,對於是否應該把多個啟動項放到一個launch檔案裡並沒有一個統一的標準。就本教程的案例而言,我們把他們放到兩個launch檔案裡。

編寫技巧提示:在決定應用需要多少個高層級的launch檔案時,你要考慮利弊的權衡。

機器標籤和環境變數

為了平衡負載和管理頻寬,我們需要控制哪些節點在哪個機器上執行。比如,我們希望amcl跟base laser在同一臺機器上執行。同時,考慮到重用性,我們不希望把具體的機器名寫入launch檔案。roslaunch使用機器標籤(machine tags)來解決這個問題。

第一個引用如下:

<include file="$(find pr2_alpha)/$(env ROBOT).machine" />

首先應該注意到這個檔案使用env置換符來使用ROBOT變數的值。例如,在roslaunch指令前執行:

export ROBOT=pre

將會使得pre.machine 被引用。

編寫技巧提示: 使用env置換符可以使launch檔案的一部分依賴於環境變數的值。

接下來,我們來看看pr2_alpha功能包裡的pre.machine 檔案。

<launch>
  <machine name="c1" address="pre1" ros-root="$(env ROS_ROOT)" ros-package-path="$(env ROS_PACKAGE_PATH)" default="true" />
  <machine name="c2" address="pre2" ros-root="$(env ROS_ROOT)" ros-package-path="$(env ROS_PACKAGE_PATH)" />
</launch>

這個檔案對本地機器名進行了一個對映,例如"c1" 和 "c2"分別對應於機器名"pre1"和"pre2"。甚至可以控制你登入的使用者名稱。(前提是你有ssh證書)

一旦這個對映建立好了之後,就可以用於控制節點的啟動。比如,2dnav_pr2功能包裡所引用的config/new_amcl_node.xml檔案包含這樣的語句:

<node pkg="amcl" type="amcl" name="amcl" machine="c1">

這可以控制amcl節點在機器名為c1的機器上執行。(檢視其他的launch檔案,你可以看到大多數鐳射感測器處理節點都在這個機器上執行)。

當我們要在另一個機器人上執行程式,比如說機器人prf,我們只要修改ROBOT環境變數的值就可以了。相應的機器配置檔案(pr2_alpha功能包裡的prf.machine檔案)就會被載入。我們甚至可以通過設定ROBOT為sim從而是得程式可以在一個模擬機器人上執行。檢視pr2_alpha功能包裡的sim.machine檔案,它只是將所有的機器名對映到了本地主機名。

編寫技巧提示: 使用機器標籤machine tags來平衡負載並控制節點在機器上的啟動,同時也需考慮將機器配置檔案(.machine)跟系統變數關聯起來以便於重複利用。

引數、名稱空間和yaml檔案

我們來看一下引用的move_base.xml檔案。檔案的一部分如下:

<node pkg="move_base" type="move_base" name="move_base" machine="c2">
  <remap from="odom" to="pr2_base_odometry/odom" />
  <param name="controller_frequency" value="10.0" />
  <param name="footprint_padding" value="0.015" />
  <param name="controller_patience" value="15.0" />
  <param name="clearing_radius" value="0.59" />
  <rosparam file="$(find 2dnav_pr2)/config/costmap_common_params.yaml" command="load" ns="global_costmap" />
  <rosparam file="$(find 2dnav_pr2)/config/costmap_common_params.yaml" command="load" ns="local_costmap" />
  <rosparam file="$(find 2dnav_pr2)/move_base/local_costmap_params.yaml" command="load" />
  <rosparam file="$(find 2dnav_pr2)/move_base/global_costmap_params.yaml" command="load" />
  <rosparam file="$(find 2dnav_pr2)/move_base/navfn_params.yaml" command="load" />
  <rosparam file="$(find 2dnav_pr2)/move_base/base_local_planner_params.yaml" command="load" />
</node>

這一小段程式碼負責啟動move_base節點。 第一個引用元素是remapping。設計Move_base時是希望它從"odom"話題接收里程計資訊的。在這個pr2案例裡,里程計資訊是釋出在pr2_base_odometry話題上,所以我們要重新對映一下。

編寫技巧提示: 當一個給定型別的資訊在不同的情況下發布在不同的話題上,我們可以使用topic remapping

這個檔案有好幾個標籤。這些引數是節點的內部元素(因為它們都寫在之前),因此它們是節點的私有引數私有引數。比如,第一個引數將move_base/controller_frequency設定為10.0。

在元素之後,還有一些元素,它們將從yaml檔案中讀取引數。yaml是一種易於我們讀取的檔案格式,支援複雜資料的結構。這是第一個所載入的costmap_common_params.yaml檔案一部分:

raytrace_range: 3.0
footprint: [[-0.325, -0.325], [-0.325, 0.325], [0.325, 0.325], [0.46, 0.0], [0.325, -0.325]]
inflation_radius: 0.55

# BEGIN VOXEL STUFF
observation_sources: base_scan_marking base_scan tilt_scan ground_object_cloud

base_scan_marking: {sensor_frame: base_laser, topic: /base_scan_marking, data_type: PointCloud, expected_update_rate: 0.2,
  observation_persistence: 0.0, marking: true, clearing: false, min_obstacle_height: 0.08, max_obstacle_height: 2.0}

我們看到yaml支援向量等資料結構(如上邊的footprint就是向量)。它同樣支援將巢狀的域名空間,比如base_laser被歸屬到了base_scan_marking/sensor_frame這樣的巢狀域名下。注意這些域名都是歸屬於yaml檔案自身域名global_costmap之下,而yaml檔案的域名是由ns變數來控制的。同樣的,由於rosparam都被包含在節點裡,所以引數的完整名稱就是/move_base/global_costmap/base_scan_marking/sensor_frame.

接著一行是:

<rosparam file="$(find 2dnav_pr2)/config/costmap_common_params.yaml" command="load" ns="local_costmap" />

這跟上一行引用的是完全一樣的yaml檔案,只不過他們的域名空間不一樣(local_costmap域名隻影響運動路徑控制器,而global_costmap影響到全域性的導航規劃)。這樣可以避免重新給同樣的變數再次賦值。

再下一行是:

<rosparam file="$(find 2dnav_pr2)/move_base/local_costmap_params.yaml" command="load"/>

跟上一行不同,這一行沒有ns屬性。因此這個yaml檔案的域名就是/move_base。但是再仔細檢視一下這個yaml檔案的前幾行:

local_costmap:
  #Independent settings for the local costmap
  publish_voxel_map: true
  global_frame: odom_combined
  robot_base_frame: base_link

最終我們可以確定引數都是歸屬於/move_base/local_costmap域名之下。

編寫技巧提示: Yaml檔案允許複雜的巢狀域名的引數,相同的引數值可以在多個地方重複使用。

launch檔案的重用

上述的編寫技巧都是為了使得launch檔案能再不同的環境下更易於重用。從上邊的一個例子我們已經知道,使用env子變數可以在不改動launch檔案的情況下就改變其行為模式。但是仍然在某些情況下,重用launch檔案還是很麻煩甚至不可能。我們來看一下pr2_2dnav_gazebo功能包。它有2d導航功能,但是隻是為Gazebo模擬器而設計的。 對於導航來說,唯一改變了的就是我們所使用的Gazebo環境是一張靜態地圖,因此map_server節點必須過載其引數。當然這裡我們可以使用另外一個env變數,但這會使得使用者還得設定一大堆變數才能夠roslaunch。因此,2dnav gazebo有它自己的高層級launch檔案,叫'2dnav-stack-amcl.launch',如下所示:

<launch>
  <include file="$(find pr2_alpha)/sim.machine" />
  <include file="$(find 2dnav_pr2)/config/new_amcl_node.xml" />
  <include file="$(find 2dnav_pr2)/config/base_odom_teleop.xml" />
  <include file="$(find 2dnav_pr2)/config/lasers_and_filters.xml" />
  <node name="map_server" pkg="map_server" type="map_server" args="$(find gazebo_worlds)/Media/materials/textures/map3.png 0.1" respawn="true" machine="c1" />
  <include file="$(find 2dnav_pr2)/config/ground_plane.xml" />
  <!-- The naviagtion stack and associated parameters -->
  <include file="$(find 2dnav_pr2)/move_base/move_base.xml" />
</launch>

首先,因為我們知道這是一個模擬器,所以直接使用sim.machine檔案,而不必再使用$(env ROBOT)變數來選擇。其次,原來的:

<include file="$(find 2dnav_pr2)/config/map_server.xml" />

已經被

<node name="map_server" pkg="map_server" type="map_server" args="$(find gazebo_worlds)/Media/materials/textures/map3.png 0.1" respawn="true" machine="c1" />

所替換。兩者都包含了節點的宣告,但它們來自不同的檔案。

編寫技巧提示: 想要改變應用的高層級功能,只要修改launch檔案的相應部分即可。

引數過載

在某些情況下,上述技巧會很不方便。比如,使用2dnav_pr2但希望修改local costmap的解析度為0.5。我們只需要修改local_costmap_params.yaml檔案即可。我們本來只是想臨時的修改,但這種方法卻意味著它被永久修改了。也許我們可以將local_costmap_params.yaml檔案拷貝一份並做修改,但這樣我們還需要修改move_base.xml檔案去引用修改後的yaml檔案。 接著我們還得修改 2dnav_pr2.launch檔案去引用被修改後的xml檔案。這是很花時間的工作,而且假如我們使用了版本控制,我卻看不到版本間有任何的改變。另外一種方法是新建立一個launch檔案,這樣就可以在2dnav_pr2.launch檔案中定義move_base/local_costmap/resolution引數,修改這個引數就可以滿足我們都要求。如果我們能夠提前知道哪些引數有可能被修改,這會是一個很好的辦法。

更好的方法是利用roslaunch的過載特性:引數按順序賦值(在引用被執行之後)。這樣,我們可以構建另外一個可以過載引數的高層級檔案:

<launch>
<include file="$(find 2dnav_pr2)/move_base/2dnav_pr2.launch" />
<param name="move_base/local_costmap/resolution" value="0.5"/>
</launch>

這個方法的主要缺點在於它使得檔案難以理解:想要知道一個引數的值需要追溯launch的引用。但它確實避免了多次拷貝檔案然後修改它們。

編寫技巧提示: 利用roslaunch過載功能來修改一個深深巢狀在launch檔案分支中的引數。

roslaunch引數

在C Turtle版本中,roslaunch還有一個和標籤(tags)類似的引數替換特性,它允許依據變數的值來有條件啟動某個launch檔案。這比上述的引數過載和launch檔案重用來得更簡潔,適用性更強。但它需要一些額外操作來完成這個功能:修改原始launch檔案來指定哪些變數是可變的。請參考roslaunch XML文件

編寫技巧提示: 在能夠修改原始launch檔案的情況下,優先選擇使用roslaunch變數,而不是引數過載和拷貝launch檔案的方法。

補充說明

在ros_ws/src中有示例教程,其中有大量launch檔案,可以通過檢視學習進一步掌握roslaunch在大型專案中的使用技巧。