---
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` 印出來的。

## <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>
```

#### <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>。

`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 的名字確實被更改過了

所以如果要啟動同一個 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" />
```
範例:
    

  
    
#### <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 檔架構
    

 如果加在 `<node>` 標籤底下會成為區域變數,但不論是全域變數還是區域變數,都可以用 `rosparam set / get` 接收,只是當它是區域變數時需加上 `namespace`。
:::info
例如 : 
如果把 included.launch 的 <param> 移到 <node> 標籤底下,再次執行,使用  `$ rosparam list` 查看會發現 param 的名字從 /param 變成 /turtle/param。
    

:::
#### <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等,。
    

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()` 取得。