SLAM+語音機器人DIY系列:(二)ROS入門——6.編寫簡單的service和client
摘要
ROS 機器人作業系統 在機器人應用領域很流行,依託程式碼開源和模組間協作等特性,給機器人開發者帶來了很大的方便。我們的機器人“ miiboo ”中的大部分程式也採用ROS進行開發,所以本文就重點對ROS基礎知識進行詳細的講解,給不熟悉ROS的朋友起到一個拋磚引玉的作用。本章節主要內容:
1.ROS是什麼
2.ROS系統整體架構
3.在ubuntu16.04中安裝ROS kinetic
4.如何編寫ROS的第一個程式hello_world
5.編寫簡單的訊息釋出器和訂閱器
6.編寫簡單的service和client
7.理解tf的原理
8.理解roslaunch在大型專案中的作用
9.熟練使用rviz
10.在實際機器人上執行ROS高階功能預覽
6.編寫簡單的service和client
上一節介紹了兩個 ros 節點通過釋出與訂閱訊息的通訊方式,現在就介紹 ros 節點間通訊的另外一種方式服務。我們將學到:如何自定義服務型別、 server 端節點編寫、 client 端節點編寫等。我就以實現兩個整數求和為例, client 端節點向 server 端節點發送 a 、 b 的請求, server 端節點返回響應 sum=a+b 給 client 端節點,通訊網路結構如圖 20 。
( 圖 20 ) 服務請求與響應 ROS 通訊網路結構圖
( 1 )功能包的建立
在 catkin_ws/src/ 目錄下新建功能包 service_example ,並在建立時顯式的指明依賴 roscpp 和 std_msgs ,依賴 std_msgs 將作為基本資料型別用於定義我們的服務型別。開啟命令列終端,輸入命令:
cd ~/catkin_ws/src/ #建立功能包service_example時,顯式的指明依賴roscpp和std_msgs, #依賴會被預設寫到功能包的CMakeLists.txt和package.xml中 catkin_create_pkg service_example roscpp std_msgs
( 2 )在功能包中建立自定義服務型別
通過前面的學習,我們知道服務通訊過程中服務的資料型別需要使用者自己定義,與訊息不同,節點並不提供標準服務型別。服務型別的定義檔案都是以 *.srv 為副檔名,並且被放在功能包的 srv/ 資料夾下。
服務型別定義:
首先,在功能包 service_example 目錄下新建 srv 目錄,然後在 service_example/srv/ 目錄中建立 AddTwoInts.srv 檔案,檔案內容如下:
int64 a int64 b --- int64 sum
服務型別編譯配置:
定義好我們的服務型別後,要想讓該服務型別能在 c++ 、 python 等程式碼中被使用,必須要做相應的編譯與執行配置。編譯依賴 message_generation ,執行依賴 message_runtime 。
開啟功能包中的 CMakeLists.txt 檔案,找到下面這段程式碼:
find_package(catkin REQUIRED COMPONENTS roscpp std_msgs )
將 message_generation 新增進去,新增後的程式碼如下:
find_package(catkin REQUIRED COMPONENTS roscpp std_msgs message_generation )
繼續,找到這段程式碼:
# add_service_files( #FILES #Service1.srv #Service2.srv # )
去掉這段程式碼的 # 註釋,將自己的服務型別定義檔案 AddTwoInts.srv 填入,修改好後的程式碼如下:
add_service_files( FILES AddTwoInts.srv )
繼續,找到這段程式碼:
# generate_messages( #DEPENDENCIES #std_msgs # )
去掉這段程式碼的 # 註釋, generate_messages 的作用是自動建立我們自定義的訊息型別 *.msg 與服務型別 *.srv 相對應的 *.h ,由於我們定義的服務型別使用了 std_msgs 中的 int64 基本型別,所以必須向 generate_messages 指明該依賴,修改好後的程式碼如下:
generate_messages( DEPENDENCIES std_msgs )
然後開啟功能包中的 package.xml 檔案,填入下面三句依賴:
<build_depend>message_generation</build_depend> <build_export_depend>message_generation</build_export_depend> <exec_depend>message_runtime</exec_depend>
檢查 ROS 是否識別新建的服務型別:
我們通過 < 功能包名 / 服務型別名 > 找到該服務,開啟命令列終端,輸入命令:
source ~/catkin_ws/devel/setup.bash rossrv show service_example/AddTwoInts
看到下面的輸出,如圖 21 ,就說明新建服務型別能被 ROS 識別,新建服務型別成功了。
( 圖 21 ) 新建服務型別能被 ROS 識別
( 3 )功能包的原始碼編寫
功能包中需要編寫兩個獨立可執行的節點,一個節點用來作為 client 端發起請求,另一個節點用來作為 server 端響應請求,所以需要在新建的功能包 service_example/src/ 目錄下新建兩個檔案 server_node.cpp 和 client_node.cpp ,並將下面的程式碼分別填入。
首先,介紹 server 節點 server_node.cpp ,程式碼內容如下:
#include "ros/ros.h" #include "service_example/AddTwoInts.h" bool add_execute(service_example::AddTwoInts::Request &req, service_example::AddTwoInts::Response &res) { res.sum = req.a + req.b; ROS_INFO("recieve request: a=%ld,b=%ld",(long int)req.a,(long int)req.b); ROS_INFO("send response: sum=%ld",(long int)res.sum); return true; } int main(int argc,char **argv) { ros::init(argc,argv,"server_node"); ros::NodeHandle nh; ros::ServiceServer service = nh.advertiseService("add_two_ints",add_execute); ROS_INFO("service is ready!!!"); ros::spin(); return 0; }
對 server 節點程式碼進行解析。
#include “ros/ros.h”
#include “service_example/AddTwoInts.h”
包含標頭檔案 ros/ros.h ,這是 ROS 提供的 C++ 客戶端庫,是必須包含的標頭檔案 , 就不多說了。 service_example/AddTwoInts.h 是由編譯系統自動根據我們的功能包和在功能包下建立的 *.srv 檔案生成的對應的標頭檔案,包含這個標頭檔案,程式中就可以使用我們自定義服務的資料型別了。
bool add_execute(...)
這個函式實現兩個 int64 整數求和的服務,兩個 int64 值從 request 獲取,返回求和結果裝入 response 裡, request 與 response 的具體資料型別都在前面建立的 *.srv 檔案中被定義,這個函式返回值為 bool 型。
ros::init(argc,argv,”server_node”);
ros::NodeHandle nh;
初始化 ros 節點並指明節點的名稱,宣告一個 ros 節點的控制代碼, , 就不多說了。
ros::ServiceServer service = nh.advertiseService(“add_two_ints”,add_execute);
這一句是建立服務,並將服務加入到 ROS 網路中,並且這個服務在 ROS 網路中以名稱 add_two_ints 唯一標識,以便於其他節點通過服務名稱進行請求。
ros::spin();
這一句話讓程式進入自迴圈的掛起狀態,從而讓程式以最好的效率接收客戶端的請求並呼叫回撥函式,就不多說了。
接著,介紹 client 節點 client_node.cpp ,程式碼內容如下:
#include "ros/ros.h" #include "service_example/AddTwoInts.h" #include <iostream> int main(int argc,char **argv) { ros::init(argc,argv,"client_node"); ros::NodeHandle nh; ros::ServiceClient client = nh.serviceClient<service_example::AddTwoInts>("add_two_ints"); service_example::AddTwoInts srv; while(ros::ok()) { long int a_in,b_in; std::cout<<"please input a and b:"; std::cin>>a_in>>b_in; srv.request.a = a_in; srv.request.b = b_in; if(client.call(srv)) { ROS_INFO("sum=%ld",(long int)srv.response.sum); } else { ROS_INFO("failed to call service add_two_ints"); } } return 0; }
對 client 節點程式碼進行解析。
之前解釋過的類似的程式碼就不做過多的解釋了,這裡重點解釋一下前面沒遇到過的程式碼。
ros::ServiceClient client =
nh.serviceClient<service_example::AddTwoInts>("add_two_ints");
這一句建立 client 物件,用來向 ROS 網路中名稱叫 add_two_ints 的 service 發起請求。
service_example::AddTwoInts srv;
定義了一個 service_example::AddTwoInts 服務型別的物件,該物件中的成員正是我們在 *.srv 檔案中定義的 a 、 b 、 sum ,我們將待請求的資料填充到資料成員 a 、 b ,請求成功後返回結果會被自動填充到資料成員 sum 中。
if(client.call(srv)){...}
這一句便是通過 client 的方法 call 來向 service 發起請求,請求傳入的引數 srv 在上面已經介紹過了。
( 4 )功能包的編譯配置及編譯
建立功能包 service_example 時,顯式的指明依賴 roscpp 和 std_msgs ,依賴會被預設寫到功能包的 CMakeLists.txt 和 package.xml 中,並且在功能包中建立 *.srv 服務型別時已經對服務的編譯與執行做了相關配置,所以只需要在 CMakeLists.txt 檔案的末尾行加入以下幾句用於宣告可執行檔案就可以了:
add_executable(server_node src/server_node.cpp) target_link_libraries(server_node ${catkin_LIBRARIES}) add_dependencies(server_node service_example_gencpp) add_executable(client_node src/client_node.cpp) target_link_libraries(client_node ${catkin_LIBRARIES}) add_dependencies(client_node service_example_gencpp)
這裡面的 add_executable 用於宣告可執行檔案。 target_link_libraries 用於宣告可執行檔案建立時需要連結的庫。 add_dependencies 用於宣告可執行檔案的依賴項,由於我們自定義了 *.s rv , service_example_gencpp 的作用是讓編譯 系統自動根據我們的功能包和在功能包下建立的 *.srv 檔案生成的對應的標頭檔案和庫 檔案, service_example_gencpp 這個名稱是由功能包名稱 service_example 加上 _gencpp 字尾而來的,字尾很好理解:生成 c++ 檔案就是 _gencpp ,
生成 python 檔案就是 _genpy 。
接下來,就可以用下面的命令對功能包進行編譯了:
cd ~/catkin_ws/ catkin_make -DCATKIN_WHITELIST_PACKAGES="service_example"
( 5 )功能包的啟動執行
首先,需要用 roscore 命令來啟動 ROS 節點管理器, ROS 節點管理器是所有節點執行的基礎。
開啟命令列終端,輸入命令:
roscore
然後,用 source devel/setup.bash 啟用 catkin_ws 工作空間,用 rosrun <package_name> <node_name> 啟動功能包中的 server 節點。
再開啟一個 命令列終端,分別輸入命令:
cd ~/catkin_ws/ source devel/setup.bash rosrun service_example server_node
看到有輸出 “ servive is ready!!! ”,就說明 server 節點已經正常啟動並開始等待 client 節點向自己發起請求了,如圖 22 。
(圖 22 ) server 節點已經正常啟動
最後,用 source devel/setup.bash 啟用 catkin_ws 工作空間,用 rosrun <package_name> <node_name> 啟動功能包中的 client 節點。
再開啟一個 命令列終端,分別輸入命令:
cd ~/catkin_ws/ source devel/setup.bash rosrun service_example client_node
看到有輸出提示資訊 “ please input a and b: ”後,鍵盤鍵入兩個整數,以空格分割,輸入完畢後回車。如果看到輸出資訊“ sum=xxx ”,就說明 client 節點向 server 端發起的請求得到了響應,打印出來的 sum 就是響應結果,這樣就完成了一次服務請求的通訊過程,如圖 23 。
(圖 23 ) client 節點已經正常啟動
到這裡,我們編寫的 server 和 client 就大功告成了,為了加深對整個程式工作流程的理解,我再把 server 與 client 的 ROS 通訊網路結構圖拿出來,加深一下理解。