1 service介紹
1.1 service概念
話題通信是基于訂閱/發布機制的,無論有沒有訂閱者,發布者都會周期發布數據,這種模式適合持續數據的收發,比如傳感器數據。機器人系統中還有另外一些配置性質的數據,并不需要周期處理,此時就要用到另外一種ROS通信方式——服務(Service)。服務是基于客戶端/服務器模型的通信機制,服務器端只在接收到客戶端請求時才會提供反饋數據。
1.2 service特點
-
同一個服務(名稱相同)有且只能有一個節點來提供
-
同一個服務可以被多個客戶端調用(可以一對一也可一對多)
-
同步通信
這個過程一般要求越快越好,假設服務器半天沒有反應,你的瀏覽器一直轉圈圈,那有可能是服務器宕機了,或者是網絡不好,所以相比話題通信,在服務通信中,客戶端可以通過接收到的應答信息,判斷服務器端的狀態,我們也稱之為同步通信。
2 自定義通信接口
和話題通信類似,服務通信的核心還是要傳遞數據,數據變成了兩個部分,一個請求的數據,比如請求蘋果位置的命令,還有一個反饋的數據,比如反饋蘋果坐標位置的數據,這些數據和話題消息一樣,在ROS中也是要標準定義的,話題使用.msg文件定義,服務使用的是.srv文件定義。
2.1 新建工作空間
mkdir -p dev_ws/src
cd dev_ws/src
ros2 pkg create custom_interface --build-type ament_cmake
2.2 編寫srv接口文件
# 創建srv目錄
cd dev_ws/src/custom_interface
mkdir srv
touch CapitalFullName.srv
CapitalFullName.srv
string name
string surname
---
string capitalfullname
2.3 編譯
colcon build
3 service代碼實現
上面第二步已經實現了自定義接口,并在install目錄生成了對應的頭文件,我們下面的代碼中可以直接引用這個自定義接口了。
3.1 新建工作空間
前面已經講解過,這里不再贅述
這里創建工作空間名為learning03_service
3.2 server_node_class.cpp
/*** @file service_node_class.cpp** @brief A basic ROS2 service server node with class implementation that gets two * strings as request and answer with a capitalized full string as response.* It's necessary to use the custom message defined in the external* package "Custom msg and srv"* To call the service from a terminal use on a single line:* ros2 service call /create_cap_full_name * custom_interface/srv/CapitalFullName "{name: x, surname: y}"** @author Antonio Mauro Galiano* Contact: https://www.linkedin.com/in/antoniomaurogaliano/**/#include "rclcpp/rclcpp.hpp"
#include "custom_interface/srv/capital_full_name.hpp"
#include <boost/algorithm/string.hpp>class MyServiceNode : public rclcpp::Node
{
private:rclcpp::Service<custom_interface::srv::CapitalFullName>::SharedPtr service_;void ComposeFullName(const std::shared_ptr<custom_interface::srv::CapitalFullName::Request> request,std::shared_ptr<custom_interface::srv::CapitalFullName::Response> response);public:MyServiceNode(std::string passedNodeName="VOID"): Node(passedNodeName){RCLCPP_INFO(this->get_logger(), "I am ready to capitalize your full name");// like the subscriber class node it's needed the boost::bind to acces the member method // with 2 placeholders to pass request and response to the callbackservice_ = this->create_service<custom_interface::srv::CapitalFullName>("create_cap_full_name", std::bind(&MyServiceNode::ComposeFullName, this, std::placeholders::_1, std::placeholders::_2 ));}};// method to handle the client request and give back a response
// the service gets the name and surname and responses with a capitalized full name
void MyServiceNode::ComposeFullName(const std::shared_ptr<custom_interface::srv::CapitalFullName::Request> request,std::shared_ptr<custom_interface::srv::CapitalFullName::Response> response)
{std::string fullName = request->name + " " + request->surname;std::string capitalFullName = boost::to_upper_copy<std::string>(fullName);response->capitalfullname = capitalFullName; RCLCPP_INFO(rclcpp::get_logger("rclcpp"), "Incoming name: %s" "\tIncoming surname: %s",request->name.c_str(), request->surname.c_str());RCLCPP_INFO(rclcpp::get_logger("rclcpp"), "Sending back the capitalize full name: [%s]", response->capitalfullname.c_str());
}int main(int argc, char **argv)
{rclcpp::init(argc, argv);auto node = std::make_shared<MyServiceNode>("service_node");rclcpp::spin(node); // the service starts to wait and manage requestsrclcpp::shutdown();
}
3.3 client_node_class.cpp
/*** @file client_node_class.cpp** @brief A basic ROS2 service client node with class implementation that asks the user * to input twostrings and gets back a capitalized full string from the server service.* It's necessary to use the custom message defined in the external * package "custom_interface"** @author Antonio Mauro Galiano* Contact: https://www.linkedin.com/in/antoniomaurogaliano/**/#include "custom_interface/srv/capital_full_name.hpp"
#include "rclcpp/rclcpp.hpp"using namespace std::chrono_literals;class MyClientNode : public rclcpp::Node
{
private:const std::string name_;const std::string surname_;rclcpp::Client<custom_interface::srv::CapitalFullName>::SharedPtr client_;void ServerResponse();public:MyClientNode(std::string passedNodeName="VOID", std::string passedName="VOID", std::string passedSurname="VOID"): Node(passedNodeName), name_(passedName), surname_(passedSurname){client_ = this->create_client<custom_interface::srv::CapitalFullName>("create_cap_full_name");this->ServerResponse();}};void MyClientNode::ServerResponse()
{auto request =std::make_shared<custom_interface::srv::CapitalFullName::Request>();request->name = name_;request->surname = surname_;std::chrono::seconds myPause = 1s;while (!client_->wait_for_service(1s)) {if (!rclcpp::ok()) {RCLCPP_ERROR(rclcpp::get_logger("rclcpp"),"Interrupted while waiting for the service. Exiting.");}RCLCPP_INFO(rclcpp::get_logger("rclcpp"),"SERVICE NOT AVAILABLE, waiting again...");}auto result = client_->async_send_request(request);// note that for the MyClientNode object it's used the get_node_base_interface// to allow the client spinning for a server response// it returns the needed rclcpp::node_interfaces::NodeBaseInterface::SharedPtr if (rclcpp::spin_until_future_complete(this->get_node_base_interface(), result) ==rclcpp::FutureReturnCode::SUCCESS){RCLCPP_INFO(rclcpp::get_logger("rclcpp"), "Capitalized full name: %s",result.get()->capitalfullname.c_str());} else{RCLCPP_ERROR(rclcpp::get_logger("rclcpp"),"Failed to call service create_cap_full_name");}
}int main(int argc, char **argv)
{rclcpp::init(argc, argv);std::string name = "";std::string surname = "";std::cout << "Insert the name -> ";std::cin >> name;std::cout << "Insert the surname -> ";std::cin >> surname;auto node = std::make_shared<MyClientNode>("client_node", name, surname);rclcpp::shutdown();
}
3.4 CMakeLists.txt
cmake_minimum_required(VERSION 3.5)
project(learning03_service)# Default to C99
if(NOT CMAKE_C_STANDARD)set(CMAKE_C_STANDARD 99)
endif()# Default to C++14
if(NOT CMAKE_CXX_STANDARD)set(CMAKE_CXX_STANDARD 14)
endif()if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")add_compile_options(-Wall -Wextra -Wpedantic)
endif()# find dependencies
find_package(ament_cmake REQUIRED)
find_package(rclcpp REQUIRED)
find_package(custom_interface REQUIRED)if(BUILD_TESTING)find_package(ament_lint_auto REQUIRED)# the following line skips the linter which checks for copyrights# uncomment the line when a copyright and license is not present in all source files#set(ament_cmake_copyright_FOUND TRUE)# the following line skips cpplint (only works in a git repo)# uncomment the line when this package is not in a git repo#set(ament_cmake_cpplint_FOUND TRUE)ament_lint_auto_find_test_dependencies()
endif()# add_executable(service_node src/service_node.cpp)
# ament_target_dependencies(service_node rclcpp custom_interface)# add_executable(client_node src/client_node.cpp)
# ament_target_dependencies(client_node rclcpp custom_interface)add_executable(client_node_class src/client_node_class.cpp)
ament_target_dependencies(client_node_class rclcpp custom_interface)add_executable(service_node_class src/service_node_class.cpp)
ament_target_dependencies(service_node_class rclcpp custom_interface)install(TARGETS# service_node# client_nodeclient_node_classservice_node_classDESTINATION lib/${PROJECT_NAME})ament_export_dependencies(rosidl_default_runtime)
ament_package()
3.5 package.xml
<?xml version="1.0"?>
<?xml-model href="http://download.ros.org/schema/package_format3.xsd" schematypens="http://www.w3.org/2001/XMLSchema"?>
<package format="3"><name>learning03_service</name><version>0.0.0</version><description>Example nodes to create service server and service client</description><maintainer email="foo@foo.foo">Antonio Mauro Galiano</maintainer><license>TODO: License declaration</license><buildtool_depend>ament_cmake</buildtool_depend><build_depend>rclcpp</build_depend><exec_depend>rclcpp</exec_depend><test_depend>ament_lint_auto</test_depend><test_depend>ament_lint_common</test_depend><depend>custom_interface</depend><export><build_type>ament_cmake</build_type></export>
</package>
4 編譯運行
# 編譯
colcon build# source環境變量
source install/setup.sh# 運行publisher
ros2 run learning03_service client_node_class# 運行subsriber
ros2 run learning03_service server_node_class
5 service常用指令
# 查看service列表
ros2 service list# 查看所有service類型
ros2 service list -t# 查看service類型
ros2 service type <service_name># 查看同一類型所有在運行的service
ros2 service find <service_type># 查看service內容
ros2 interface show <service_interface>
ros2 interface show <service_name># 調用service
ros2 service call <service_name> <service_type>