--- tags : DIT 2023寒假 -- ROS教學 --- # DAY 6 -- Param & Launch {%hackmd @HungPin/BkVDWAea3 %} ## <font color="orange"> 01. ROS Param </font> ### <font color='yellow'>簡介</font> **Rosparam**的全名叫做**Ros parameter**,顧名思義是和**參數**有關的功能,透過 `rosparam` 的指令,我們可以儲存並操縱Ros master底下的parameter server裡面的參數,==所以在使用rosparam時,一定要開啟master==。而這些參數和我們在程式碼中設定的參數有甚麼不同呢? 第一個差異是這些變數為**全域變數**,在啟動程式碼時,有些參數是一開始就固定好的,例如初始位置、初始速度等,但有些參數我們希望可以在測試中隨時調整的,例如機器人的目標點等,如果把參數寫在C++程式中,每改一次參數就要重新編譯一次,在測試時其實是很花時間的,而rosparam就可以解決這樣的問題。 ### <font color='yellow'>rosparam指令</font> | 指令 | 功能 | |:------------------------------ | ------------------------------------ | | `rosparam list` | 列出所有參數 | | `rosparam set [param name] [vale]` | 設置參數 | | `rosparam get [param name]` | 獲得參數值 | | `rosparam load [file path/file name]` | 從文件中加載參數到 parameter server | | `rosparam dump [file path/file name]` | 將 parameter server 中的參數寫入文件 | | `rosparam delete [param name]` | 刪除參數 | 我們可以將需要設置的參數保存在 yaml 文件中,然後使用 rosparam load [文件路徑\文件名] 命令一次將多個參數加載到 parameter server 中。 例如 : 現在有一個叫`practice.yaml`的檔案 ```yaml= obstacle_range: 3.0 raytrace_range: 3.5 footprint: [[-0.105, -0.105], [-0.105, 0.105], [0.041, 0.105], [0.041, -0.105]] #robot_radius: 0.105 inflation_radius: 0.15 cost_scaling_factor: 0.01 map_type: costmap observation_sources: scan scan: {sensor_frame: base_scan, data_type: LaserScan, topic: scan, marking: true, clearing: true} /print_freq: 1 ``` 我們想要把裡面的參數一次傳到 parameter server 中方便更改,就可以使用下列程式碼,注意要先移動到 yaml 檔的 package 底下 : ```cpp= cd winter2023/src/practice rosparam load practice/params/practice.yaml ``` 這樣 yaml 檔裡的參數就全部傳進 parameter server中了! ### <font color='yellow'>指令範例 </font> #### <font color='pink'> Step 1. 建立一個可以操作頻率的 node</font> ```cpp= code param_practice.cpp ``` ```cpp= #include <ros/ros.h> int main(int argc, char **argv){ ros::init(argc,argv,"param_practice"); ros::NodeHandle nh; double freq; while(ros::ok()){ nh.getParam("/print_freq",freq); ros::Rate rate(freq); ROS_INFO("%f", freq); rate.sleep(); } return 0; } ``` 記得修改 CMakeList.txt 和編譯。 #### <font color='pink'> Step 2. 設置 param </font> :::success :warning: **要記得先 `roscore`** ::: 使用 `rosparam set` 設置一個叫做 `/print_freq` 的參數值為 1。 ```cpp= rosparam set /print_freq 1 ``` 設置完後可以試試看用 `rosparam list` 和 `rosparam get` 查看。 #### <font color='pink'> Step 3. 執行結果 </font> ```cpp= rosrun [pkg_name] param_practice ``` node 以 `1Hz` 的頻率印出**當前的頻率** 。 如果此時重新設置 `/print_freq` 為 5 。 ```cpp= rosparam set /print_freq 5 ``` 後三筆 hello_world 就是以 `5Hz` 印出來的。 ![](https://i.imgur.com/zpa3Ncy.png) ## <font color="orange"> 02. Roslaunch </font> ### <font color='yellow'>簡介</font> Launch 檔是使用 xml 語言編寫的腳本檔,可以把需要執行的 node 貼給 Ros 一次執行,以東京威力比賽時的程式來說,機器上場時需要執行主程式、encoder odometry、IMU、雷射測距、STM 通訊等不同的 node,而如果是用遠端操控的話更是可能會面臨 node 開啟到一半失去連線的風險,launch 檔可以一次呼叫所有需要的 node ,並且編輯需要的參數,相較起來方便許多。另外,上面所提到的`rosparam`在關掉 master 的時候就會全部消失,因此 launch 檔另一個重要功能就是儲存與控制重要的參數。 ### <font color='yellow'>建立 Launch File</font> #### <font color='pink'> Step 1. 建立 launch 資料夾</font> 在pkg中新增一個launch資料夾,所有的 launch 檔都放在這個資料夾下,方便管理。 ```t= $ cd winter2023/src/practice #cd [pkg name] $ mkdir launch ``` #### <font color='pink'> Step 2. 建立 launch 檔</font> ```= $ code [launch_name].launch ``` ### <font color='yellow'>執行 Launch File</font> launch file 不需要編譯,可以直接執行,但新增 launch 檔需要執行 `$ source devel/setup.bash`。 <!-- $ roslaunch[package_name][ file_name.launch] --> ```= $ roslaunch practice my_launch.launch ``` <!--改完 launch 檔要執行 `$ source devel/setup.bash`--> :::warning 雖然 launch 會幫忙開啟 roscore,但還是記得先執行 roscore 喔! ::: ### <font color='yellow'>撰寫 Launch File</font> #### <font color='pink'> 1. 宣告 launch 檔</font> launch 檔的宣告需要以`<launch>`開頭,`</launch>` 結尾。 ```xml= <launch> [your code] </launch> ``` #### <font color='pink'> 2. launch 檔的架構</font> 在 launch 檔中會包含許多不同的 tag ,而這些 tag 宣告方式也都和 launch 檔的宣告一樣是以 `<tag>` 開頭,`</tag>` 結尾,而如果 tag 內的指令只有單行的話也可寫成`<tag... />`,下圖是大概的架構。 ```xml= <tag .... /> <!--單行--> <tag> <!--多行--> . . . </tag> ``` ![](https://i.imgur.com/W4jQ25C.png) #### <font color='pink'> 3. launch 檔常用指令</font> #### <font color='lightblue'> (1) <node></font> 用來啟動 node,但不保證 node 的啟動順序。 ```xml= <node type="talker" pkg="practice" name="publisher1" output="screen"/> ``` `pkg`:欲啟動的 node 所在的 package。 `type`:欲啟動的 node 的執行檔名稱,也就是在 CMakeLists.txt 中取的名字 <font color=orange>(橘字的部分)</font>。 ![](https://i.imgur.com/0MCpOM9.png) `name`:也是 node 的名稱,但可以重新命名覆蓋掉原本的名稱,以新的名稱表示。 :::info 舉例來說 ```xml= <launch> <node type="talker" pkg="practice" name="publisher1" output="screen" /> <node type="listener" pkg="practice" name="subscriber1" output="screen" /> <node type="listener" pkg="practice" name="subscriber2" output="screen" /> </launch> ``` 這個 launch 檔總共執行 ```talker``` 這個 node 1次,```listener``` 這個 node 兩次,並且在這個 launch 檔中,這些 node 的名字分別被覆蓋成 ```publisher1```、```subscriber1```、```subscriber2```,用 ```$rosnode list``` 可以發現這些 node 的名字確實被更改過了 ![](https://i.imgur.com/s47SqG5.png) 所以如果要啟動同一個 node 多次,launch 檔裡的 name 就必須取不同的名字喔 ! ::: `output`: 輸出log的目標。 `respawn / required`: 是當該節點由於不明原因停止執行的時候,會自動重新啟動。而 required 比較霸道一點,當該節點停止執行的時候,會讓整個 launch 檔都停止執行並且關閉。 #### <font color='lightblue'> (2) <remap></font> 當程式裡面寫的 topic 名稱不是最終想要的,remap 可以幫助你改變程式所知道的topic名稱。 ```xml= <remap from= "original topic_name" to= "new topic_name" /> <!-- 通常會寫在 <node> 底下 --> ``` ```xml= <node type="talker" pkg="practice" name="publisher1" output="screen" > <remap from="counter" to="counter_1"/> </node> <!-- publish 原本發佈到"counter",更改為"counter_1" --> <node type="listener" pkg="practice" name="subscriber1" output="screen" > <remap from="counter" to="counter_1"/> </node> <!-- subsrciber 原本接收到"counter",更改為"counter_1" --> <node type="listener" pkg="practice" name="subscriber2" output="screen" > <remap from="counter" to="counter_1"/> </node> ``` #### <font color='lightblue'> (3) <include></font> 允許在 launch 檔中包含其它的 roslaunch 檔案或 XML 檔案等。 ```xml= <include file ="$(find pkg_name) / launch_files / launch_name.launch" /> ``` 範例: ![](https://i.imgur.com/6v5Vqut.png) #### <font color='lightblue'> (4) <arg></font> <arg>的功能是**宣告變數**,讓我們可以**從 command-line 賦值**,或是**傳遞<include>檔案的變數**,但要注意 arg 標籤是==非全域性==的,一個宣告只針對一個 launch 檔案,如同區域性變數,如果要在其它檔案中使用,則必須要明確的進行值的傳遞。 :::info <arg>有兩種寫法,一個是使用`default`,`default`是給一個**預設值**,可以在執行 `roslaunch` 時修改,另一個是 `value`,`value`是吃**指定值**,無法修改,兩者**不能寫在同一行**。 ::: ```xml= <arg name="arg_name" default="arg_value" /> <arg name="arg_name" value="arg_value" /> ``` <font color=pink>my_launch.launch :</font> ```xml= <arg name="vel" default="100" /> <include file="$(find practice)/launch/included.launch" > <arg name="velocity" value="$(arg vel)" /> </include> ``` <arg> 也可以用在 <include> 中直接給定變數值,但是 arguments 是區域變數,因此從 command-line 更改的數值無法改到 <include> 標籤下的 arguments,所以上面的程式在 <include> 外多宣告了一個 argument,讓 <include> 裡的 argument 吃外面的值。 <font color=pink>included.launch :</font> ```xml= <launch> <!-- declare arg to be passed in --> <arg name="velocity" /> <node pkg="turtlesim" type="turtlesim_node" name="turtle" output="screen" /> <!-- read value of arg --> <param name="param" value="$(arg velocity)"/> <!-- $() 是獲取變量的意思 --> </launch> ``` 在 command window 執行 launch 檔時可以直接更改 default 的值 ```t= $ roslaunch practice my_launch.launch velocity:=50 ``` 但如果將 <font color=pink>my_launch.launch</font> 中的 `default` 改成 `value`,velocity 的值就沒辦法從 command-line 給。 ```xml= <include file="$(find practice)/launch/included.launch" > <arg name="velocity" value="100" /> </include> ``` #### <font color='lightblue'> (5) <param></font> `<param>` 的功能是宣告要在 parameter server 中設置的變數。 ```xml= <!-- string --> <param name="frame_id" type="string" value="laser_frame" /> <!-- double --> <param name="pose_x" type="double" value="20.5" /> <!-- int --> <param name="baudrate" type="int" value="512000" /> ``` `<param>` 和 `<arg>` 功能相似,都是負責**宣告變數**,而主要的差別在於 `<param>` 無法用在 `<include>` 中傳遞變量,`<arg>` 則是可以用在 `<include>` 和 `<node>` 中。 :::info 比較 <arg> & <param> <!--* 意義上 : `<param>` 是透過 param server傳遞參數的,nodes 可通過 rosparam get 得到 `<arg>` 是透過 command line 傳遞參數,nodes 要讀取他的值需要用到 args ex: ```xml= <node name="beacon server" pkg="map server" type="map server" args="$(find eurobot)/maps/beacon.yaml" /> ```--> * <param> : 寫在 <launch> 下當全域變數、從 launch 檔傳入參數給 node ( 用 `nh.getParam()` ),或是寫在 <node>下,用 args 傳遞變數。 * <arg> : 從 command-line 直接給參數值,或是寫在<include>下,從**另一個 launch file** 給值。 ::: launch 檔架構 ![](https://i.imgur.com/Lw6B9ZC.png) 如果加在 `<node>` 標籤底下會成為區域變數,但不論是全域變數還是區域變數,都可以用 `rosparam set / get` 接收,只是當它是區域變數時需加上 `namespace`。 :::info 例如 : 如果把 included.launch 的 <param> 移到 <node> 標籤底下,再次執行,使用 `$ rosparam list` 查看會發現 param 的名字從 /param 變成 /turtle/param。 ![](https://i.imgur.com/hWe9gRX.png) ::: #### <font color='lightblue'> (6) <rosparam></font> `<arg>`是原生xml的標籤,`<param>`和`<rosparam>`算是ros新增出來的標籤,`<param>`用法跟`<arg>`很像,但是可以被ros master管理。而`<rosparam>`標籤就是用來管理`<param>`的,跟cmd直接下`rosparam`有一樣的效果。 ```xml= <rosparam command="delete" param="/param_name" /> <rosparam command="load" file="$(find practice)/params/practice.yaml" /> <rosparam command="dump" file="$(find practice)/params/practice.yaml" /> ``` #### <font color='lightblue'> (7) <group></font> 可以把數個 node 組織成一組 node,並且一次對整組 node 進行操作, 通常用在大型的 launch 檔中,例如一次幫很多個 node 上 namespace;或是加入判斷式,在某些情況運行某個 group,某些情況則運行另一個 group等,。 ![](https://i.imgur.com/FbLAaCQ.png) ns :指定 node 到特定的名稱空間中,名稱空間可以是全域性或是區域性的 可以用來啟動同名 node,避免因為名稱衝突關閉上一個 node #### <font color='lightblue'> (8) 註解</font> ```xml= <!-- text --> ``` ## <font color="orange"> 03. HW </font> ==需要先把 DAY3 的作業完成 ~== 1. 寫一個 yaml 檔儲存小烏龜的**速度**、**目標點** 2. 創建一個新的 launch 檔。 3. 新的 launch 檔要包含 - 開啟 "發送速度與接收位置的 node" 的指令 (以下用 node_day3 代稱) - 讀取yaml 檔的參數值 - 把 publish 的 topic 名稱換掉,再用 <remap> 改回 cmd_vel 4. 把 node_day3 的速度值跟目標點用 `nh.getParam()` 取得。