ros2 service 编程之自定义服务
- 1 前言和资料
- 2 正文
- 2.1 两种自定义服务的讨论
- 2.2 自定义服务独立成包
- 2.2.1 自定义服务(diy_interface)
- 2.2.2 service_cpp使用自定义服务
- 2.2.3 service_py使用自定义服务
- 2.3 自定义服务放在模块包里(service_mix )
- 3 总结
1 前言和资料
在ROS2高效学习第二章 – ros2常用命令和相关概念学习,熟练玩起来小乌龟样例这篇博客里,我们讲解了 ros2 service 的基本概念和调试命令,本文我们讲解 ros2 service 编程。内容编排上,我们将复用ROS2高效学习第四章 – ros2 topic 编程之自定义 msg的结构和内容,也使用两种方式实现自定义服务:一是自定义服务独立成包(复用 diy_interface 包);二是自定义服务与终端包放在一起,并实现 cpp 和 python 混合编程。
本文参考资料如下:
(1)ROS2高效学习第二章 – ros2常用命令和相关概念学习,熟练玩起来小乌龟样例 第2.5节
(2)ROS2高效学习第四章 – ros2 topic 编程之自定义 msg
(3)Writing-A-Simple-Cpp-Service-And-Client
(4)Writing-A-Simple-Py-Service-And-Client
(5)Custom-ROS2-Interfaces
2 正文
2.1 两种自定义服务的讨论
在ROS2高效学习第四章 – ros2 topic 编程之自定义 msg的2.1节,我们对自定义 msg 有详细的讨论,连带讲解了 ros2 cpp 和 python混合编程的实现方式。而自定义服务与自定义 msg 具有非常强的雷同性,同样的讨论对自定义服务完全适用,这里不再赘述,请读者移步另一篇博客阅读。
2.2 自定义服务独立成包
2.2.1 自定义服务(diy_interface)
(1)在 diy_interface 中,创建自定义服务文件
cd ~/colcon_ws/src/diy_interface
mkdir srv
touch srv/QuestionAndAnswer.srv
(2)编写QuestionAndAnswer.srv,内容就是一问一答。
string question
---
string answer
(3)添加 CMakeLists.txt
rosidl_generate_interfaces(${PROJECT_NAME}"msg/Student.msg""msg/Sphere.msg""srv/QuestionAndAnswer.srv"DEPENDENCIES geometry_msgs # Add packages that above messages depend on, in this case geometry_msgs for Sphere.msg
)
(4)编译并测试
~/colcon_ws
colcon build --packages-select diy_interface
source install/local_setup.bash
ros2 interface show diy_interface/srv/QuestionAndAnswer
2.2.2 service_cpp使用自定义服务
(1)创建 service_cpp 包和相关文件
cd ~/colcon_ws/src
ros2 pkg create --build-type ament_cmake --license Apache-2.0 service_cpp --dependencies rclcpp
cd service_cpp
mkdir launch
touch launch/service_diy_launch.py
touch src/client_diy.cpp src/server_diy.cpp
(2)编写client_diy.cpp
#include <memory>
#include <chrono>#include "rclcpp/rclcpp.hpp"
#include "diy_interface/srv/question_and_answer.hpp"class ServiceClient : public rclcpp::Node
{
public:ServiceClient() : Node("service_client") {client_ = this->create_client<diy_interface::srv::QuestionAndAnswer>("question_and_answer");}int init() {while (!client_->wait_for_service(std::chrono::seconds(1))) {if (!rclcpp::ok()) {RCLCPP_ERROR(this->get_logger(), "Interrupted while waiting for the service. Exiting.");return 0;}RCLCPP_INFO(this->get_logger(), "service not available, waiting again...");}return 0;}void run(const std::string &str) {auto request = std::make_shared<diy_interface::srv::QuestionAndAnswer::Request>();request->question = str;auto result = client_->async_send_request(request);if (rclcpp::spin_until_future_complete(this->get_node_base_interface(), result) == rclcpp::FutureReturnCode::SUCCESS) {RCLCPP_INFO(this->get_logger(), "[cpp client] send: %s, receive: %s", request->question.c_str(), result.get()->answer.c_str()); } else {RCLCPP_ERROR(this->get_logger(), "Failed to call service add_two_ints");} }private:rclcpp::Client<diy_interface::srv::QuestionAndAnswer>::SharedPtr client_;
};int main(int argc, char * argv[]) {rclcpp::init(argc, argv);auto node = std::make_shared<ServiceClient>();node->init();while (rclcpp::ok()) {std::string str = "how are you?";node->run(str);std::this_thread::sleep_for(std::chrono::seconds(1));}rclcpp::shutdown();return 0;
}
(3)编写server_diy.cpp
#include <memory>#include "rclcpp/rclcpp.hpp"
#include "diy_interface/srv/question_and_answer.hpp"class ServiceServer : public rclcpp::Node
{
public:ServiceServer() : Node("service_server") {server_ = this->create_service<diy_interface::srv::QuestionAndAnswer>("question_and_answer", std::bind(&ServiceServer::answer_callback, this, std::placeholders::_1, std::placeholders::_2));}
private:void answer_callback(const std::shared_ptr<diy_interface::srv::QuestionAndAnswer::Request> request,std::shared_ptr<diy_interface::srv::QuestionAndAnswer::Response> response) {response->answer = "I'm fine, thank you.";RCLCPP_INFO(this->get_logger(), "[cpp server] receive: %s, return: %s", request->question.c_str(), response->answer.c_str());}private:rclcpp::Service<diy_interface::srv::QuestionAndAnswer>::SharedPtr server_;
};int main(int argc, char * argv[]) {rclcpp::init(argc, argv);rclcpp::spin(std::make_shared<ServiceServer>());rclcpp::shutdown();return 0;
}
(4)编写CMakeLists.txt
cmake_minimum_required(VERSION 3.8)
project(service_cpp)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(diy_interface REQUIRED)add_executable(server_diy src/server_diy.cpp)
ament_target_dependencies(server_diy rclcpp diy_interface)add_executable(client_diy src/client_diy.cpp)
ament_target_dependencies(client_diy rclcpp diy_interface)install(TARGETSserver_diyclient_diyDESTINATION lib/${PROJECT_NAME})install(DIRECTORYlaunchDESTINATION share/${PROJECT_NAME}
)
ament_package()
(5)添加 package.xml
<depend>rclcpp</depend><depend>diy_interface</depend>
(6)编写 service_diy_launch.py
from launch import LaunchDescription
from launch_ros.actions import Nodedef generate_launch_description():return LaunchDescription([Node(package='service_cpp',namespace='cpp',executable='server_diy',name='server_diy'),Node(package='service_cpp',namespace='cpp',executable='client_diy',name='client_diy')])
(7)编译并运行
~/colcon_ws
colcon build --packages-select diy_interface service_cpp
source install/local_setup.bash
ros2 launch service_cpp service_diy_launch.py
2.2.3 service_py使用自定义服务
(1)service_py 是 service_cpp 的 python 版实现,功能完全一样,这里不再详述创建步骤,具体请参考源码:service_py,源码说明一切!
(2)编译并运行
~/colcon_ws
colcon build --packages-select diy_interface service_py
source install/local_setup.bash
ros2 launch service_py service_diy_launch.py
2.3 自定义服务放在模块包里(service_mix )
(1)service_mix 使用 cpp 和 python 混合编程, server 端使用 cpp 实现,client 端使用 python 实现。在 service_mix 包里,添加了自定义服务:给圆半径,求圆面积。关于创建步骤,service_mix 与 ROS2高效学习第四章 – ros2 topic 编程之自定义 msg 第2.3节的 pubsub_mix 基本相同,这里不再赘述,具体请参考源码:service_mix,源码说明一切!
(2)编译并运行
~/colcon_ws
colcon build --packages-select service_mix
source install/local_setup.bash
ros2 launch service_mix service_mix_launch.py
3 总结
本文的代码托管在本人的github上:diy_interface,service_cpp,service_py,service_mix