1. 程式人生 > >第一個訂閱者程式

第一個訂閱者程式

1 寫在開頭的話

      首先確保自己已經有工作區間,且已經將該工作區間的setup.bash加入當前使用者的環境變數中,若是則直接看第二章節,否則繼續往下看。

     若沒有工作區間則檢視順序為:1.1——>1.2——>2——>1.3——》3

     若有工作區間但是沒有將setup.bash加入當前使用者的環境變數中,則檢視順序為:1.2——》1.3——》2——》3

    若是已經有工作區間且已經將該工作區間的setup.bash加入當前使用者的環境變數中,則檢視順序1.2—》2—》3

1.1 新建工作區間

  在當前使用者home目錄下呼叫mkdir命令新建資料夾test,cd進入該資料夾,再次呼叫mkdir新建資料夾src,如下

1.2建立功能包

cd進入工作區間的src目錄,注意:圖中test就是本人的工作區間名字

在src目錄下呼叫catkin_create_pkg,建立功能包subpose,如下

  呼叫ls檢視是否建立成功,成功會在src下出現subpose資料夾,如下

cd進入資料夾subpose,通過ls命令可以看到如下2個檔案

 第一個配置檔案,叫做package.xml

 第二個檔案,叫做CMakeLists.txt,是一個Cmake的指令碼檔案,Cmake是一個符合工業標準的跨平臺編譯系統。

1.3  新增環境變數

       執行此部分需要兩個條件(滿足其一即可),條件為:其一,在檢視本文前已經有工作區間但是沒有將該工作區間的setuo.bash配置到當前使用者的環境變數中;其二,已經執行了本文的第二部分

      新增環境變數到當前目錄步驟如下:

      a.在當前使用者目錄的工作區目錄下,即test目錄下【此時的test為本人的工作區名稱,若查閱本文者檢視此文前已經有自己的工作區間,則此步驟的test為查閱者的工作區間名稱】,呼叫ls檢視此目錄下內容,如下:

   b.呼叫cd進入devel目錄,呼叫ls檢視此目錄下內容:

c. 注意:a、b步驟只是為了確保該工作區間中存在該工作區間環境變數的配置檔案setup.bash,沒有做任何實質性的工作

d。再次進入當前使用者的home目錄,呼叫ls -a檢視該目錄下包含隱藏檔案的全部檔案,如下

可以看到,上圖中有隱藏檔案.bashrc

e。呼叫vim編輯此檔案,如下

在最後加入一行:source  ~/test/devel/setup.bash,注意此處的test是工作區間的名字,根據需要修改,如下

按esc退出編輯,繼而輸入:wq儲存退出


2  建立編譯執行訂閱者程式

2.1 建立訂閱者程式‘

2.1.1 新增cpp檔案

    在subpose資料夾下通過vim建立訂閱者程式的cpp檔案,並新增如下程式碼段

   

   程式subpose.cpp訂閱turtlesim機器人釋出的位姿資料

2.1.2 對subpose.cpp檔案進行解釋

2.1.2.1 標頭檔案分析

    A.標頭檔案ros/ros.h包含了標準ROS類的宣告,你將會在每一個你寫的ROS程式中包含它。

    B. 本例程中,回撥函式poseMessageReceived接收型別為turtlesim::Pose的訊息,所以我們需要的標頭檔案是turtlesim/Pose.h。

    C.iomanip.h是I/O流控制標頭檔案,就像C裡面的格式化輸出一樣.,該標頭檔案中包含的函式大概有如下幾種:                                         setfill(c) 設填充字元為c因為程式碼中                                                                                                                                                     setprecision(n) 設顯示小數精度為n位
  setw(n) 設域寬為n個字元
  setiosflags(ios::fixed) 固定的浮點顯示
  setiosflags(ios::scientific) 指數表示
  setiosflags(ios::left) 左對齊
  setiosflags(ios::right) 右對齊
  setiosflags(ios::skipws 忽略前導空白
  setiosflags(ios::uppercase) 16進位制數大寫輸出
  setiosflags(ios::lowercase) 16進位制小寫輸出
  setiosflags(ios::showpoint) 強制顯示小數點
  setiosflags(ios::showpos) 強制顯示符號

2.1.2.2  回撥函式poseMessageReceived說明

        釋出和訂閱訊息的一個重要的區別是訂閱者節點無法知道訊息什麼時候到達。為了應對這一事實,我們必須把響應收到訊息事件的程式碼放到回撥函式裡,ROS每接收到一個新的訊息將呼叫一次這個函式。訂閱者的回撥函式類似於:
void function_name(const package_name::type_name &msg){}

       其中引數package_name 和type_name和釋出訊息時的相同,它們指明瞭我們想訂閱的話題的訊息類

       回撥函式的主體有許可權訪問接收到訊息的所有域,並以它認為合適的方式儲存、使用或丟棄接收到的資料

       此例中這個回撥函式僅僅是通過ROS_INFO_STREAM在終端列印訊息資料,包括x、y和theta資料成員

       注意訂閱者的回撥函式的返回值型別為void。其實這樣安排是合理的,因為呼叫此函式是ROS的工作,返回值也要交給ROS,所以我們的程式無法獲得返回值

2.1.2.3  main函式內程式碼段說明

 A.ros::init函式初始化ROS客戶端庫。請在你程式的起始處呼叫一次該函式3。函式最後的引數是一個包含節點預設名的字串。

 B.ros::NodeHandle(節點控制代碼)物件是你的程式用於和ROS系統互動的主要機制4。建立此物件會將你的程式註冊為ROS節點管    理器的節點。最簡單的方法就是在整個程式中只建立一個NodeHandle物件。

C.建立訂閱者物件

       為了訂閱一個話題,我們需要建立一個ros::Subscriber物件
                      ros::Subscriber sub = node_handle.subscribe (topic_name,queue_size, pointer_to_callback_function);

        node_handle是ros::NodeHandle類的一個物件,是你在程式的開始處建立的,即ng。我們將呼叫這個物件的subscribe方法;                                                                                                                                                                                                           topic_name是我們想要訂閱的話題的名稱,以字串的形式表示。   

       queue_size是本訂閱者接收訊息的佇列大小,是一個整數。通常,你可以使用一個較大的整數,例如1000,而不用太多關心佇列處理過程。當新的訊息到達時,它們會被儲存在一個佇列中,直到ROS有機會去執行相應的回撥函式。此引數表示ROS在佇列中同一時刻可以儲存的訊息的最大值。如果新訊息到達時佇列已滿,最早到達的還沒有被處理的訊息將會被丟棄以便騰出空間來.ROS清空一個釋出序列的速率取決於實際上給訂閱者傳輸訊息所佔用的時間,而這個時間在很大程度上是不受控制的。ROS清空訂閱序列的速度取決於我們處理回撥函式有多快。因此,我們可以通過如下兩個方法減少訂閱者佇列溢位的可能性:(1)通過呼叫ros::spin或者ros:spinOnce確保允許回調發生;(2)減少每個回撥函式的計算時間

        pointer_to_callback_function是指向回撥函式的指標,當有訊息到達時要通過這個指標找到回撥函式。在C++中,你可以通過對函式名使用符號運算子(&,“取址”)來獲得函式的指標

       建立ros::Subscriber物件時,我們沒有在任何地方顯式地提到訊息型別。實際上,subscribe方法是模板化的,C++編譯器會根據我們提供的函式指標中的資料型別判斷出正確的訊息型別。

D.給ROS控制權

         最後的複雜之處在於只有當我們明確給ROS許可時,它才會執行我們的回撥函式13。實際上有兩個略微不同的方式來做到這一點,如下所示:

          ros::spinOnce();這個程式碼要求ROS去執行所有掛起的回撥函式,然後將控制權限返回給我們。

          ros::spin();這個方法要求ROS等待並且執行回撥函式,直到這個節點關機。換句話說,ros::spin()大體等於這樣一個迴圈:
                       while(ros::ok( )   {   ros::spinOnce()    }

         使用ros::spinOnce()還是使用ros::spin()的建議如下:你的程式除了響應回撥函式,還有其他重複性工作要做嗎?如果答案是“否”,那麼使用ros::spin();否則,合理的選擇是寫一個迴圈,做其他需要做的事情,並且週期性地呼叫ros::spinOnce()來處理回撥。本例中使用ros::spin(),因為程式唯一的工作就是接收和列印接收到的位姿訊息。

          訂閱者程式中常見的一個錯誤是不小心忽略了呼叫ros::spinOnce和ros::spin。在這種情況下,ROS永遠沒有機會去執行你的回撥函式。忽略ros::spin會導致你的程式在開始執行後不久就退出。忽略ros::spinOnce使程式表現的好像沒有接收到任何訊息。

 2.2 編譯subpose程式

 2.2.1 新增依賴庫

   A.因為subpose使用了來自turtlesim包的訊息型別和roscpp中的ros.h標頭檔案,我們必須宣告對這兩個包的依賴關係

     進入工作區間目錄下的src目錄,cd再次進入subpose功能包,vim開啟該目錄下的CMakeLists.txt

    

     CMakeLists.txt檔案的預設版本含有如下行:find_package(catkin REQUIRED)

    

       所依賴的其他catkin包可以新增到這一行的COMPONENTS關鍵字後面,如下所示:

                     find_package(catkin REQUIRED COMPONENTS package-names)

        對於subpose例程,我們需要新增名為roscpp和turtlesim的依賴庫。因此,修改後的find_package行如下所示:

  

2.2.2 在package.xml檔案中宣告訊息型別依賴庫 

       我們同樣需要在包的清單檔案package.xml中列出依賴庫,通過使用build_depend (編譯依賴)和run_depend(執行依賴)兩個關鍵字實 現【注意:格式有2種,具體見https://blog.csdn.net/guosuling/article/details/83214460中的3.2】::
                                              <build_export_depend>package-name</build_export_depend>
                                             <exec_depend>package-name</run_depend>

        開啟package.xml檔案後,找到檔案偏下方的綠色字型<buildtool_depend>catkin</buildtoo;_depend>,在其後新增我們的依賴庫,新增後效果如下。按esc,然後:wq退出儲存

 

        在清單檔案中宣告的依賴庫並沒有在編譯過程中用到;如果你在此處忽略它們,你可能不會看到任何錯誤訊息,直到釋出你的包給其他人,他們可能在沒有安裝所需包的情況下編譯你釋出的包而導致報錯。

2.3  宣告可執行檔案

       在功能包的CMakeLists.txt中新增兩行,來宣告我們需要建立的可執行檔案。其一般形式是:
                                         add_executable(executable-name source-files)
                                          target_link_libraries(executable-name ${catkin_LIBRARIES})

       第一行聲明瞭我們想要的可執行檔案的檔名,以及生成此可執行檔案所需的原始檔列表。如果你有多個原始檔,把它們列在此處,並用空格將其區分開

        第二行告訴Cmake當連結此可執行檔案時需要連結哪些庫(在上面的find_package中定義)。如果你的包中包括多個可執行檔案,為每一個可執行檔案複製和修改上述兩行程式碼。

       在我們的例程中,我們需要一個名為subpose的可執行檔案,它通過名為subpose.cpp的原始檔編譯而來。所以我們需要新增如下幾行程式碼到CMakeLists.txt中。注意:在CMakeLists.txt檔案中有很多註釋和例句,找到對應的例句,在其下方輸入我們要求的命令,儲存退出

2.4. 編譯工作區

         使用下列命令在工作區目錄下編譯包中所有的可執行檔案 :catkin_make

        

        執行結果如下表示成功

       

        若出現如下找不到標頭檔案的錯誤,首先詳細檢查自己程式碼中相關標頭檔案和函式是否書寫出錯,若已經確保直接書寫正確則見另外一篇文章https://mp.csdn.net/postedit/83214460的2.4.3

       

      若出現大面積的錯誤資訊:未定義的引用,如下所示

           從上圖中綠色字型處資訊得知:是程式的連結出現了問題,故應檢查自己的CMakeLists.txt檔案中連結庫的宣告target_link_libraries(executable-name ${catkin_LIBRARIES}),正確的宣告如下圖中黃色字型


3 執行subpose程式

        此例程訂閱的是turtle1/pose話題,而turtlesim節點會發布訊息到該話題,所以為了驗證我們的程式,可以新建turtlesim節點,令其釋出該訊息到話題,從而使我們的程式可以從該話題獲取該訊息。兩個步驟驗證我們的程式,如下3.1和3.2

        注意:不論哪種驗證方法都必須已經執行過本文的1.3部分,且重新開啟一個shell用於執行roscore(節點管理器)

3.1 只執行turtlesim_node節點

       若只執行該節點,那麼海龜的位姿並沒有改變,所以執行我們的程式時輸出一直不變。此步驟驗證步驟如下:

  A.單獨的shell中執行roscore

 

 B.新建一個shell,執行turtlesim_node節點,此時會出現一個海龜

      C. 新建一個shell,執行我們的subpose程式,可以看到右上角的shell中,輸出位姿不變

3.2 執行turtlesim_node節點,同時再執行turtle_teleop_key節點

A .如已經進行步驟3.1,且roscore沒有關閉,可以直接跳過此步;否則執行如下步驟:單獨的shell中執行roscore,截圖如3.1的A

B.如已經進行步驟3.2,且turtlesim_node節點每一個關閉,那麼可以直接跳過此步;否則執行如下步驟:新建一個shell,執行turtlesim_node節點,此時會出現一個海龜,截圖如3.1的B

C.新建一個shell,執行turtle_teleop_key節點。截圖如下

D.新建一個shell,執行我們的subpose程式【若已經3.1且沒有關閉我們之前執行的程式,那麼跳過此步】,此時因為海龜的位姿依舊沒有改變,故我們的輸出還是沒有改變

E.將滑鼠焦點放到turtle_teleop_key,按鍵盤的上下左右箭頭,可以觀察到我們的海歸根據我們的箭頭運動,同時我們自己的節點subpose的輸出也根據我們的箭頭而改變,如下