說明
本文中包括服務、客戶端等名稱,其中服務包括兩重含義,既可以理解為與客戶端相對應的服務器,又可以理解為在服務器與客戶端之間傳遞數據的格式,需根據上下文進行理解。
srv文件
srv文件是ROS服務與客戶端機制中傳遞數據的格式,包括請求與響應兩個部分,請求部分是客戶端傳遞給服務的數據,響應部分則是服務返回給客戶端的數據。
srv文件會在catkin_make后自動生成一個類,這個類以“軟件包名稱::服務名”命名,使用這個類需要引入頭文件“軟件包名稱/服務名.h”(此處服務名指數據格式,與srv文件名相同),類中包含兩個成員變量request和response,以及兩個類定義Request和Response。成員變量request和response通過“.”運算符,可取得srv服務中請求或響應部分的具體值,以參與運算;而類Request和Response主要用於聲明變量,以供在函數間傳遞參數。
服務
在服務中(此處服務指與客戶端對應的服務器),需要完成如下內容:
- 在主函數中
- 初始化ROS,並指定節點名稱
- 為節點創建句柄
- 創建服務,同時指出服務名和服務中執行功能的回調函數名稱
- 循環運行回調函數
- 在主函數外
- 編寫回調函數
在服務中,一些重要的變量如下:
- 節點名,通過ros::init指定運行節點時用到的節點名,用引號包圍,對應下圖中的"kinematics_server"
- 句柄,聲明句柄后,可利用句柄通過“.”運算符調用advertiseService方法,創建服務,對應下圖中的n
- 服務名,通過上述advertiseService方法指定服務名,用引號包圍,創建客戶端時需要指明對應的服務名,對應下圖中的"trans_pose"
- 服務(服務器)對象,屬於ros::ServiceServer類型,作為advertiseService方法的返回值,對應下圖中的service
- 回調函數,同樣在advertiseService方法中指出,無需引號,當服務被請求時回調函數被執行以進行響應,回調函數通常需要以服務(此處服務指srv表明的數據格式)自動生成的類中定義的類型變量作為形參,從而實現根據服務傳遞的數據運算並返回運算結果,對應下圖中無引號的trans_pose
客戶端
在客戶端中,需要完成如下內容:
- 在主函數中
- 初始化ROS,並指定節點名稱
- 為節點創建句柄
- 創建客戶端,同時指出此客戶端請求的服務(指與客戶端對應的服務)名稱
- 聲明服務類對象,通過此對象引用服務(指srv文件)中的請求與響應數據
- 調用服務,傳入上述聲明的服務類對象
在客戶端中,一些重要的名稱與變量如下:
節點名,通過ros::init指定運行節點時用到的節點名,用引號包圍,對應下圖中的"kinematics_client"
- 句柄,聲明句柄后,可利用句柄通過“.”運算符調用serviceClient方法,創建客戶端,對應下圖中的n
- 服務名,在上述創建客戶端的serviceClient方法中需要指明客戶端所請求的服務名,用引號包圍,對應下圖中的"trans_pose"
- 客戶端對象,屬於ros::ServiceClient類型,作為serviceClient方法的返回值,其具有call方法可用於調用服務,對應下圖中的client
- 服務類型,在上述創建客戶端的serviceClient方法中需要指明客戶端所請求的服務類型,即傳遞的數據類型,用尖括號包圍,對應下圖中的<kinematics_demo::trans>
- 服務類(srv文件生成的類)對象,用於引用服務(srv文件)中的請求與響應數據,對應下圖中的srv
附:
srv文件
float64 init_x float64 init_y float64 init_z float64 delta_x float64 delta_y float64 delta_z --- float64 x float64 y float64 z
服務
// 包括了ROS常用的頭文件 #include "ros/ros.h" // 創建srv文件時生成的頭文件,其中kinematics_demo為軟件包名稱,trans為srv文件名稱,並在其后加上.h擴展名 #include "kinematics_demo/trans.h" // 軟件包名與srv文件名共同組成一個類名(kinematics_demo::trans),此類包括兩個成員變量request、response和兩個類定義Request、Response // 服務中執行主要功能的回調函數的原型 bool trans_pose(kinematics_demo::trans::Request &req, kinematics_demo::trans::Response &res); int main(int argc, char **argv) { // 初始化ROS,並指定節點名稱為kindmatics_sever ros::init(argc, argv, "kinematics_server"); // 為節點創建句柄n ros::NodeHandle n; // 創建名稱是trans_pose的服務,並注冊回調函數trans_pose // 第一個trans_pose為服務名稱,第二個trans_pose為回調函數名 ros::ServiceServer service = n.advertiseService("trans_pose", trans_pose); ROS_INFO("Kinematics server"); // 循環等待回調函數 ros::spin(); return 0; } bool trans_pose(kinematics_demo::trans::Request &req, kinematics_demo::trans::Response &res) { res.x = req.init_x + req.delta_x; res.y = req.init_y + req.delta_y; res.z = req.init_z + req.delta_z; ROS_INFO("Pose before trans: (%f, %f, %f)", (double)req.init_x, (double)req.init_y, (double)req.init_z); ROS_INFO("Pose trans: (%f, %f, %f)", (double)req.delta_x, (double)req.delta_y, (double)req.delta_z); ROS_INFO("Pose after trans: (%f, %f, %f)", (double)res.x, (double)res.y, (double)res.z); return true; }
客戶端
// 包括了ROS常用的頭文件 #include "ros/ros.h" // 創建srv文件時生成的頭文件,其中kinematics_demo為軟件包名稱,trans為srv文件名稱,並在其后加上.h擴展名 #include "kinematics_demo/trans.h" #include <cstdlib> // 第一個形參argc(argument counter)表示main函數的參數個數 // 第二個形參argv(argument value)表示main函數的參數值 // 也可寫為int main(int argc, char *argv[]),即由指針組成的數組,數組中每個元素都是指向char的指針 // 當不帶參數運行主函數時,操作系統向主函數傳遞的參數argc為1,而argv[0](是一個指針)指向程序的路徑及名稱 // 當帶參數運行主函數時,操作系統向主函數傳遞的參數argc為1加上參數個數,argv[0]意義不變,從arg[1]開始依次指向參數字符串 int main(int argc, char **argv) { // 初始化ROS,並指定節點名稱為kindmatics_client ros::init(argc, argv, "kinematics_client"); // 當參數個數不為7(即未傳入6個參數,與默認的1個參數共7個) if (argc != 7) { ROS_INFO("usage: kinematics_client init_x, init_y, init_z, delta_x, delta_y, delta_z"); return 1; } // 為節點創建句柄n ros::NodeHandle n; // 為名稱是trans_pose的服務創建客戶端,並賦給名稱為client的ros::ServiceClient的對象 // 尖括號中為<軟件包名::srv文件名> ros::ServiceClient client = n.serviceClient<kinematics_demo::trans>("trans_pose"); // 實例化一個自動生成的服務類,即聲明一個kinematics_demo::trans對象srv // *注:此處服務的意義與服務器的服務並不相同,此服務強調服務內容而非動作,與srv文件所表示的服務相同 // 即軟件包名與srv文件名二者共同組成一個類名,用此類名聲明一個對象srv kinematics_demo::trans srv; // 上面實例化的名為srv的服務類,包括兩個成員變量request、response和兩個類定義Request、Response // 為request成員賦值 srv.request.init_x = atof(argv[1]); srv.request.init_y = atof(argv[2]); srv.request.init_z = atof(argv[3]); srv.request.delta_x = atof(argv[4]); srv.request.delta_y = atof(argv[5]); srv.request.delta_z = atof(argv[6]); // 客戶端調用服務 if(client.call(srv)) { ROS_INFO("Pose after trans: (%f, %f, %f)", (double)srv.response.x, (double)srv.response.y, (double)srv.response.z); } else { ROS_ERROR("Failed to call service"); return 1; } return 0; }