Try   HackMD

DAY 2 ROS 架構

Author:柚子

01. File System

這是 ROS 的資料夾結構:

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

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

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 是編譯後自動生成的。

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

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。

02. 那些自動生成的東西們

我們一開始先不用管 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 時打的後綴 roscpprospy 等,會被放入這個檔案中(去看看!)。
    相較於 CMakeLists.txt,我們比較不常去修改它。

03. 編譯與執行

我們接續 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})

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

程式說明:

  • 要寫在 #Build# 區底下, #install# 區之上。(就是#Build# 區裡面,應該是 147 行附近。)
  • 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>"

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

Step 6. 按下 ctrl + alt + T 開新視窗,並在新視窗開啟 ros master。

roscore

這個指令是啟動 ROS 系統,啟動後才能跑程式。

Step 7. 回到原本視窗,查看 worskspace 的環境變數(要在 workspace 資料夾底下)。

roscd

應該會跳出下面這個,代表這個 workspace 目前是吃整個系統的環境變數。

/opt/ros/noetic

我們希望他也吃到自己內部 package 的環境變數,這樣他才抓得到 hello.cpp

setup.bash 詳細說明

想深入了解這個指令的意義請參考ROS中的setup.bash

source devel/setup.bash roscd

結果如下:

/winter2023/devel

Step 8. 執行 hello.cpp。

rosrun practice hello

rosrun 是執行,接著先輸入 package 名稱,接著是程式名稱。

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

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

Step 9. 關閉程式按 ctrl + C。

04. ROS 程式架構初探

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

#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){ //... }
  1. argc
    a. 存取命令行的參數總數量。
    b. 第 0 個參數是節點名稱。
    c. 後面初始化節點時,會需要 argc 和 argv 參數。
  2. argv
    a. 保存命令行參數的雙重指標,儲存成二維陣列
    b. 例如輸入 TEST 123 AB , 那麼 argc[0]=TESTargc[1]=123argc[2]=AB
    c. 承上, argc[0][0]=Targc[0][1]=Eargc[0][2]=Sargc[0][3]=T

這邊如果沒有很懂實際原理沒關係,等後面學到 service 時會用到!

3. 初始化節點,以及註冊 nodehandle。

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

4. while(ros::ok()) 迴圈。

while(ros::ok()){ //... }

ros::ok() 只有在以下狀況會返回 false

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

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 編譯。

05. Linux 和 ROS 常用指令集

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

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。

小知識:

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

:bulb:回家作業

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