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 callback service_ = 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 requests rclcpp::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_node client_node_class service_node_class DESTINATION 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>