---
# System prepended metadata

title: DAY 2 -- ROS 架構
tags: [DIT 12th 教學 -- ROS 1]

---

---
tags : DIT 11th 教學 -- ROS
---
# DAY 1 -- ROS 架構
{%hackmd @HungPin/Dark %}

######  <font color="gray">Original Author：[柚子](https://hackmd.io/@925)</font>




## <font color="orange"> 04. File System</font>
這是 ROS 的資料夾結構：

![](https://i.imgur.com/24f9Id4.png =95%x)

 現在來創建上圖的資料結構。在終端機輸入以下指令：

<font color ="yellow">Step 1. 建立 Workspace，名稱為 winter2023。</font>
```=
mkdir winter2023
```
<font color ="yellow">Step 2. 進入 workspace 資料夾，並創建 src 資料夾。</font>
```=
cd winter2023
mkdir src
```

<font color ="yellow">Step 3. 編譯並查看當前資料夾內容，注意必須在 workspace 資料夾層級才可以編譯。</font>
```=
catkin_make
ls
```
:::info
**這裡的編譯是對整個 workspace 進行編譯，除了將來我們寫的程式本身，還有其他 ROS 內建的資料結構也會一併編譯。**
:::

編譯完成後，可以輸入 `ls` 查看目前的資料夾結構，應該會和粉紅色區域相同。
其中 /build  和 /devel 是編譯後自動生成的。

![](https://i.imgur.com/ntnDAId.png =80%x)

<font color ="yellow">Step 4. 進入 src 資料夾，並創建名稱為 practice 的 package。</font>

```=
cd src
catkin_create_pkg practice roscpp rospy std_msgs
```
後綴的 `roscpp` 等是我們的 package 將來會用到的 dependencies，之後會詳細說明 ~

:::warning
**注意建立 package 不可以用 mkdir 的方式創建喔 !**
:::

<font color ="yellow">Step 5. 進入 practice，查看內容。</font>

```=
cd practice
ls
```
這時會看到有自動生成的 /include 、 CMakeLists.txt 和 package.xml。


## <font color="orange"> 05. 那些自動生成的東西們</font>

我們一開始先不用管 workspace 底下那層的自動生成檔案。
因為通常只需要修改 package 裡的 CmakeLists.txt 和 package.xml。

* <font color="magenza">src folder</font>
    全名是 source folder ，我們寫的主程式（C++、Python）會放在 source 裡面。
    （未來會有其他程式需要放在其他自建的資料夾內，到時候再學就可以了。）
    
* <font color="magenza">include folder</font>
    寫過程式專案的會比較熟悉「標頭檔案 Header files」，通常會把標頭檔放在這個資料夾中。
    不熟悉標頭檔的話可以先不用理會它！
	
    
* <font color="magenza">CMakeLists.txt</font>
    可簡單理解成它是一份「設定如何編譯」的文件，包括「編譯順序」、「編譯對象」等等。
	
	catkin 編譯系統在工作時首先會找到每個 package 下的CMakeLists.txt，然後按照規則來編譯構建。

* <font color="magenza">package.xml</font>
    說明這個 package 有哪些資訊，像是作者、版本號、編譯工具、相依模組等等。
	
    在創建 package 時打的後綴 `roscpp`、`rospy` 等，會被放入這個檔案中（去看看！）。
    相較於 CMakeLists.txt，我們比較不常去修改它。
    

## <font color="orange"> 06. 編譯與執行</font>

我們接續 01. 的內容，準備寫 Hello World。

<font color ="yellow"> Step 1. 進入 （package的） src 資料夾。</font> 
```=
cd src
```

<font color ="yellow"> Step 2. 建立 hello.cpp。</font> 
```=
code hello.cpp
```
這個 `code` 是指用 vscode 新建一個 hello.cpp，所以會跳出 vscode 視窗喔。
     


<font color ="yellow"> Step 3. 測試程式，先直接複製貼上。</font> 
```cpp=
#include "ros/ros.h" 

int main(int argc, char ** argv){
   ros::init(argc, argv, "demo"); 
   ros::NodeHandle nh;

   while(ros::ok()){ 
      ROS_INFO("Hello World"); 
      ros::Duration(1).sleep(); 
   }
}
```

<font color ="yellow"> Step 4. ctrl+s 存檔後修改 CMakeLists.txt。</font>

```txt=
add_executable(hello src/hello.cpp)
target_link_libraries(hello ${catkin_LIBRARIES})
```
![](https://i.imgur.com/SdWOEMf.png =80%x)

程式說明：
* 要寫在 #Build# 區底下， #install# 區之上。（就是#Build# 區裡面，應該是 147 行附近。）
* `add_executable`：生成 src 資料夾下 `hello.cpp` 的執行檔。
* `target_link_libraries` ：連結 hello.cpp 和 catkin 本身的 library。


<font color ="yellow"> Step 5. 退回到 workspace 資料夾編譯。 </font>
```=
cd ~
cd winter2023
catkin_make
```

![](https://i.imgur.com/sQmDqZP.png =80%x)


<font color ="yellow"> Step 6. 按下 ctrl + alt + T 開新視窗，並在新視窗開啟 ros master。</font>
```=
roscore
```
這個指令是啟動 ROS 系統，啟動後才能跑程式。

<font color ="yellow"> Step 7. 回到原本視窗，查看 worskspace 的環境變數（要在 workspace 資料夾底下）。</font>
```=
roscd 
```
應該會跳出下面這個，代表這個 workspace 目前是吃整個系統的環境變數。
```
/opt/ros/noetic
```
我們希望他也吃到自己內部 package 的環境變數，這樣他才抓得到 `hello.cpp`。
:::spoiler setup.bash 詳細說明
想深入了解這個指令的意義請參考[ROS中的setup.bash](https://blog.csdn.net/qq_28087491/article/details/109179151)。
:::
```=
source devel/setup.bash
roscd 
```


結果如下：
``` 
/winter2023/devel
```

<font color ="yellow"> Step 8. 執行 hello.cpp。</font>
```=
rosrun practice hello
```
rosrun 是執行，接著先輸入 package 名稱，接著是程式名稱。

成功的話會像下面這樣，一秒印出一次 Hello World !

![](https://i.imgur.com/spKKonX.png =80%x)

<font color ="yellow"> Step 9. 關閉程式按 ctrl + C。</font> 


## <font color="orange"> 07. ROS 程式架構初探</font>

現在我們要來看這份測試程式碼囉~

```cpp=
#include "ros/ros.h" 

int main(int argc, char** argv){
   ros::init(argc, argv, "demo"); 
   ros::NodeHandle nh;

   while(ros::ok()){ 
      ROS_INFO("Hello World"); 
      ros:: Duration(1).sleep(); 
   }
}
```
那我們就一行行來看吧 !

<font color ="yellow"> 1. 引入 ros 的 library </font>
```cpp=
#include "ros/ros.h" 
```


<font color ="yellow"> 2. 寫 main function </font>
```cpp=
int main(int argc, char** argv){
  //...
}
```

1. argc
	a. 存取命令行的參數總數量。
	b. 第 0 個參數是節點名稱。
	c. 後面初始化節點時，會需要 argc 和 argv 參數。
2. argv
	a. 保存命令行參數的雙重指標，儲存成二維陣列
	b. 例如輸入 `TEST 123 AB` ， 那麼 `argc[0]=TEST` 、 `argc[1]=123` 、 `argc[2]=AB`。
	c. 承上， `argc[0][0]=T`、`argc[0][1]=E`、`argc[0][2]=S`、`argc[0][3]=T`。

:::info
**這邊如果沒有很懂實際原理沒關係，等後面學到 service 時會用到！**
:::

<font color ="yellow">3. 初始化節點，以及註冊 nodehandle。 </font>

```cpp=
ros::init(argc , argv , "demo"); 
ros::NodeHandle nh;
```
* node（節點） 初始化，需要傳入 argc 和 argv。
* 向 ros master 註冊一個名為 nh 的 nodehandle 來管理這個 node。

<font color ="yellow">4. while(ros::ok()) 迴圈。 </font>

```cpp=
while(ros::ok()){ 
   //...
}
```
`ros::ok()` 只有在以下狀況會返回 `false`：

1. 在終端機按下 Ctrl+C 時。
2. 我們被一個同名的節點從網絡中踢出。
4. 所有的ros::NodeHandles都被銷毀了。


:::spoiler ros::ok() 補充資料
:::success

**Q：如果改成 while(1) 再按下 ctrl + C 會發生什麼事情 ?**
（希望大家都能實際練習修改程式並編譯，然後實際測試一次。）

會發生這個現象的原因是，一個節點在生命結束前必須先和 master 斷線，但 ctrl +c 會讓程式直接結束，因此終端機就卡在那裡，不做事也不結束。而 roscpp 有一個 signal handler 可以接收 ctrl + c 的指令，當收到時會傳給 ros::ok()，並斷開和 master 的連接。

所以未來在使用任何 while 迴圈時，都必須加上 ros::ok() 的條件才能正常關閉。

**※ 這個番外篇真正想讓你們知道的事情 ※**
學到這裡，你們會發現 ROS 是一套機器人開發系統，我們用的都是別人定義好的東西。
方便的同時也會不曉得很多東西內部的實作是如何，就像一個簡單的 ros::ok() 背後就有這麼一個坑。這會是初期學 ROS 時陣痛期的原因，常常出 bug 卻不知道是哪裡錯了。前面其實也有很多東西省略了沒說，原因是希望你們「先用就對了」，先把基礎架構弄熟後，有興趣再來研究偏底層的實作內容，會是比較有效率的學習方式喔~
:::
$\;$
<font color ="yellow">5. 列印文字。 </font>

```cpp=
ROS_INFO("Hello World!")
```

`ROS_INFO()` 語法和表現幾乎和 C 的 `printf()` 一樣。但 `ROS_INFO()` 會提供時間戳記。

<font color ="yellow">6. 延遲一秒。 </font>
```cpp=
ros::Duration(1).sleep();
```
這邊還有另種寫法，這個 while 迴圈會「2 赫茲」地跑，也就是一秒印出兩次：
```cpp=
ros::Rate r(2); // 2 hz
while (ros::ok()){
  ROS_INFO("Hello World!")
  r.sleep();
}
```

<font color ="yellow">7. 相同的程式碼，Python 版本：</font>
```python=
import rospy                             # import rospy 模組

rospy.init_node('hello_python_node')     # 初始化 hello_python_node

while not rospy.is_shutdown():           # 在 rospy 還沒結束前，執行下列指令:
    rospy.loginfo('Hello World')         # 印出 Hello World
    rospy.sleep(1)                       # 間隔 1 秒
```
如果 C++ 版本有看懂的話，相信 Python 應該一目了然，只是語法稍稍不同 ~
:::success
**Python 程式執行方法**
直接在 pacakge 的 src 資料夾底下執行就可以囉，像下面這樣：
```
python hello.py
```

![](https://i.imgur.com/fy1bJg7.png =90%x)

**不需要設定 CMakelists.txt 或 catkin_make 編譯。**
:::



## <font color="orange"> 08. Linux 和 ROS 常用指令集</font>

> 這邊 [...] 只是代表要輸入名稱之類的東西，打的時候不需要加中括號喔~
另外要注意空格之類ㄉ。

`cd ..` ：回上一層 
`cd [...]`：進入資料夾。
`cd ~`：回到最上層。
`ls`：列出資料夾內容。
`la`：列出資料夾內容（含隱藏文件）。
`rm [...]`：刪除該資料夾或文件（會詢問是否要刪除，打 y 確定）。
`rm -rf [...]`：直接刪除該資料夾或文件。

`roscore`：啟動 ROS。
`catkin_create_pkg [pkg_name] [dependencies] ...`：建立 pkg，可引入多個模組。
`catkin_make`：編譯（須在 workspace 資料夾底下）。
`roscd`：查看環境變數位置（須在 workspace 資料夾底下）。

`ctrl+C`：關閉程式。
`ctrl+Z`：暫停程式。
`jobs`：查看哪些工作正在執行。
`kill %2`：強制結束 jobs 中的第 2 項工作（可以自行選擇要 kill 第幾項）。
`ps`：查看當前執行任務項目，及其 pid。
`kill [PID]`：強制結束 ps 中的 pid 的對應工作。例如：`kill 2321`。

`roscore &`：讓 roscore 變成背景執行，ctrl+C 後不會關閉 roscore。

:::success
**小知識：**
* **如果不喜歡開多個視窗，可以用 roscore & 節省 terminal 占用的畫面空間。**
* **如果有程式結束不掉，可以先用 ctrl+Z 暫停程式變回輸入狀態，再用 「jobs 並 kill %」 或是 「ps 並 kill [PID]」 的方式來關掉喔。**
* **如果有發生上一點的情況，可以確認是不是 while 和 ros::ok() 的問題。**
:::


---
:::warning
:bulb:**回家作業**

**再建立一次 Workspace 和生成 package。
並寫以 3 Hz 頻率印出數字的程式，從 0 開始，每次加一。（C++ 或 Python 都可以）**

:::

![](https://hackmd.io/_uploads/rkVUwSoZi.gif)
