--- tags: DIT 2023寒假 -- ROS教學 --- # DAY 2 -- ROS 架構 {%hackmd BJrTq20hE %} ###### <font color="gray">Author:[柚子](https://hackmd.io/@925)</font> ## <font color="orange"> 01. File System</font> 這是 ROS 的資料夾結構:  現在來創建上圖的資料結構。在終端機輸入以下指令: <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 是編譯後自動生成的。  <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"> 02. 那些自動生成的東西們</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"> 03. 編譯與執行</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}) ```  程式說明: * 要寫在 #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 ```  <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 !  <font color ="yellow"> Step 9. 關閉程式按 ctrl + C。</font> ## <font color="orange"> 04. 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 ```  **不需要設定 CMakelists.txt 或 catkin_make 編譯。** ::: ## <font color="orange"> 05. 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 都可以)** :::
×
Sign in
Email
Password
Forgot password
or
By clicking below, you agree to our
terms of service
.
Sign in via Facebook
Sign in via Twitter
Sign in via GitHub
Sign in via Dropbox
Sign in with Wallet
Wallet (
)
Connect another wallet
New to HackMD?
Sign up