這是 ROS 的資料夾結構:
現在來創建上圖的資料結構。在終端機輸入以下指令:
Step 1. 建立 Workspace,名稱為 winter2023。
mkdir winter2023
Step 2. 進入 workspace 資料夾,並創建 src 資料夾。
cd winter2023
mkdir src
Step 3. 編譯並查看當前資料夾內容,注意必須在 workspace 資料夾層級才可以編譯。
catkin_make
ls
這裡的編譯是對整個 workspace 進行編譯,除了將來我們寫的程式本身,還有其他 ROS 內建的資料結構也會一併編譯。
編譯完成後,可以輸入 ls
查看目前的資料夾結構,應該會和粉紅色區域相同。
其中 /build 和 /devel 是編譯後自動生成的。
Step 4. 進入 src 資料夾,並創建名稱為 practice 的 package。
cd src
catkin_create_pkg practice roscpp rospy std_msgs
後綴的 roscpp
等是我們的 package 將來會用到的 dependencies,之後會詳細說明 ~
注意建立 package 不可以用 mkdir 的方式創建喔 !
Step 5. 進入 practice,查看內容。
cd practice
ls
這時會看到有自動生成的 /include 、 CMakeLists.txt 和 package.xml。
我們一開始先不用管 workspace 底下那層的自動生成檔案。
因為通常只需要修改 package 裡的 CmakeLists.txt 和 package.xml。
src folder
全名是 source folder ,我們寫的主程式(C++、Python)會放在 source 裡面。
(未來會有其他程式需要放在其他自建的資料夾內,到時候再學就可以了。)
include folder
寫過程式專案的會比較熟悉「標頭檔案 Header files」,通常會把標頭檔放在這個資料夾中。
不熟悉標頭檔的話可以先不用理會它!
CMakeLists.txt
可簡單理解成它是一份「設定如何編譯」的文件,包括「編譯順序」、「編譯對象」等等。
catkin 編譯系統在工作時首先會找到每個 package 下的CMakeLists.txt,然後按照規則來編譯構建。
package.xml
說明這個 package 有哪些資訊,像是作者、版本號、編譯工具、相依模組等等。
在創建 package 時打的後綴 roscpp
、rospy
等,會被放入這個檔案中(去看看!)。
相較於 CMakeLists.txt,我們比較不常去修改它。
我們接續 01. 的內容,準備寫 Hello World。
Step 1. 進入 (package的) src 資料夾。
cd src
Step 2. 建立 hello.cpp。
code hello.cpp
這個 code
是指用 vscode 新建一個 hello.cpp,所以會跳出 vscode 視窗喔。
Step 3. 測試程式,先直接複製貼上。
#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();
}
}
Step 4. ctrl+s 存檔後修改 CMakeLists.txt。
add_executable(hello src/hello.cpp)
target_link_libraries(hello ${catkin_LIBRARIES})
程式說明:
add_executable
:生成 src 資料夾下 hello.cpp
的執行檔。target_link_libraries
:連結 hello.cpp 和 catkin 本身的 library。Step 5. 退回到 workspace 資料夾編譯。
cd ~
cd winter2023
catkin_make
如果 workspace 中有很多個 package,但是我們只想要編譯其中幾個的話就可以用以下指令:
$ catkin_make -DCATKIN_WHITELIST_PACKAGES="<pkg1 name>; <pkg2 name>"
Step 6. 按下 ctrl + alt + T 開新視窗,並在新視窗開啟 ros master。
roscore
這個指令是啟動 ROS 系統,啟動後才能跑程式。
Step 7. 回到原本視窗,查看 worskspace 的環境變數(要在 workspace 資料夾底下)。
roscd
應該會跳出下面這個,代表這個 workspace 目前是吃整個系統的環境變數。
/opt/ros/noetic
我們希望他也吃到自己內部 package 的環境變數,這樣他才抓得到 hello.cpp
。
想深入了解這個指令的意義請參考ROS中的setup.bash。
source devel/setup.bash
roscd
結果如下:
/winter2023/devel
Step 8. 執行 hello.cpp。
rosrun practice hello
rosrun 是執行,接著先輸入 package 名稱,接著是程式名稱。
成功的話會像下面這樣,一秒印出一次 Hello World !
Step 9. 關閉程式按 ctrl + C。
現在我們要來看這份測試程式碼囉~
#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();
}
}
那我們就一行行來看吧 !
1. 引入 ros 的 library
#include "ros/ros.h"
2. 寫 main function
int main(int argc, char** argv){
//...
}
TEST 123 AB
, 那麼 argc[0]=TEST
、 argc[1]=123
、 argc[2]=AB
。argc[0][0]=T
、argc[0][1]=E
、argc[0][2]=S
、argc[0][3]=T
。這邊如果沒有很懂實際原理沒關係,等後面學到 service 時會用到!
3. 初始化節點,以及註冊 nodehandle。
ros::init(argc , argv , "demo");
ros::NodeHandle nh;
4. while(ros::ok()) 迴圈。
while(ros::ok()){
//...
}
ros::ok()
只有在以下狀況會返回 false
:
Q:如果改成 while(1) 再按下 ctrl + C 會發生什麼事情 ?
(希望大家都能實際練習修改程式並編譯,然後實際測試一次。)
會發生這個現象的原因是,一個節點在生命結束前必須先和 master 斷線,但 ctrl +c 會讓程式直接結束,因此終端機就卡在那裡,不做事也不結束。而 roscpp 有一個 signal handler 可以接收 ctrl + c 的指令,當收到時會傳給 ros::ok(),並斷開和 master 的連接。
所以未來在使用任何 while 迴圈時,都必須加上 ros::ok() 的條件才能正常關閉。
※ 這個番外篇真正想讓你們知道的事情 ※
學到這裡,你們會發現 ROS 是一套機器人開發系統,我們用的都是別人定義好的東西。
方便的同時也會不曉得很多東西內部的實作是如何,就像一個簡單的 ros::ok() 背後就有這麼一個坑。這會是初期學 ROS 時陣痛期的原因,常常出 bug 卻不知道是哪裡錯了。前面其實也有很多東西省略了沒說,原因是希望你們「先用就對了」,先把基礎架構弄熟後,有興趣再來研究偏底層的實作內容,會是比較有效率的學習方式喔~
5. 列印文字。
ROS_INFO("Hello World!")
ROS_INFO()
語法和表現幾乎和 C 的 printf()
一樣。但 ROS_INFO()
會提供時間戳記。
6. 延遲一秒。
ros::Duration(1).sleep();
這邊還有另種寫法,這個 while 迴圈會「2 赫茲」地跑,也就是一秒印出兩次:
ros::Rate r(2); // 2 hz
while (ros::ok()){
ROS_INFO("Hello World!")
r.sleep();
}
7. 相同的程式碼,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 應該一目了然,只是語法稍稍不同 ~
Python 程式執行方法
直接在 pacakge 的 src 資料夾底下執行就可以囉,像下面這樣:
python hello.py
不需要設定 CMakelists.txt 或 catkin_make 編譯。
這邊 […] 只是代表要輸入名稱之類的東西,打的時候不需要加中括號喔~
另外要注意空格之類ㄉ。
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。
小知識:
:bulb:回家作業
再建立一次 Workspace 和生成 package。
並寫以 3 Hz 頻率印出數字的程式,從 0 開始,每次加一。(C++ 或 Python 都可以)