ROS2服务通讯基础
- 3.1 服务通讯介绍
- 3.2 ROS2服务通讯的基本流程
- 3.2.1 创建ROS2服务通讯功能包的基本流程
- 3.2.2 创建ROS2服务通讯功能包示例
- 3.3 使用C/C++实现ROS2服务通讯
- 3.3.1 创建C/C++服务通讯服务端功能包并编写节点文件
- 3.3.2 配置C/C++服务通讯服务端功能包
- 3.3.3 编译并运行C/C++服务通讯服务端
- 3.3.4 创建C/C++服务通讯客户端功能包并编写节点文件
- 3.3.5 编译并运行C/C++服务通讯客户端节点
- 3.4 使用Python实现ROS2服务通讯
- 3.4.1 创建Python服务通讯服务端功能包并编写节点文件
- 3.4.2 配置Python服务通讯服务端功能包
- 3.4.3 编译并运行Python服务通讯服务端功能包
- 3.3.4 创建Python服务通讯客户端功能包并编写节点文件
- 3.4.5 编译并运行Python服务通讯客户端节点
- 3.5 服务通讯小结
- 其他ROS2学习笔记: ROS2学习笔记
- 代码仓库:Github连接地址
- 欢迎各位互相学习交流
3.1 服务通讯介绍
参考内容
Understanding services
服务通讯是ROS2的一种基于请求响应式的通讯方式,与之前的话题通讯不同的是服务通讯不是连续的数据流式的通讯,而是需要特定的触发才能收到回复的模式。
服务通讯可以是单个服务端和和单个客户端直接的通讯,一对一的模式
服务通讯也可以是单个服务端和多个客户端通讯,一对多的模式
3.2 ROS2服务通讯的基本流程
事实上,服务通讯和自定义的话题通讯的操作过程很类似,即将所需要利用到服务模块利用ament_camke工具编译成.c和.py的ROS2功能包,然后新建的功能包依赖这个服务功能包完成通讯。
3.2.1 创建ROS2服务通讯功能包的基本流程
-
- 创建ROS2功能包,功能包可以只用来作为存放自定义的msg/srv/action,不需要节点node的功能包,且--build-type必须是ament_camke ,因为目前来看,Python的自定义的消息或者服务也需要通过cmake编译出来再调用,功能包必须是下划线的推荐命名方法,而不是大小写的驼峰,否则会报错,如下所示:
rosidl_adapter.parser.InvalidResourceName: 'xxxxx' is an invalidpackage name. It should have the pattern'^(?!.*__)(?!.*_$)[a-z][a-z0-9_]*$'
-
- 在ROS2功能包内创建srv文件夹,里面存放自定义的.srv消息文件,并且 .srv文件必须是大写开头的符合类的命名规则
-
- 配置packages.xml文件,都需要配置下面的内容(下面的配置项与.srv文件无关,是默认的固定配置)
<!-- 这一项是针对在srv文件中可能用到的其他依赖项,例如geomtry_msgs等等,如过没有用到就不添加 --><depend>其他的depend</depend><!-- 下面三项是必须的 --><build_depend>rosidl_default_generators</build_depend><exec_depend>rosidl_default_runtime</exec_depend><member_of_group>rosidl_interface_packages</member_of_group>
-
- 配置CMakeLists.txt文件,find_package和rosidl_generate_interfaces
# 0. 是针对在srv文件中可能用到的其他依赖项,例如geomtry_msgs等等,则需要添加# find_package(geometry_msgs REQUIRED)# 1. rosidl_default_generators是必须添加的内容find_package(rosidl_default_generators REQUIRED)# 2. rosidl_generate_interfaces必须配置,里面添加srv文件位置rosidl_generate_interfaces(${PROJECT_NAME}# 2.1 添加自定义的srv位置,例如存放在功能包的srv文件夹下的xxx.srv"srv/xxx.srv"# 2.2 可选,如果xxx.srv依赖了其他的内容,例如依赖了geometry_msgsDEPENDENCIES geometry_msgs)
-
- 编译功能包:colcon build --packages-select <功能包名>
-
- 此时激活install目录下的setup.bash如. install/setup.bash,可以通过ros2 interface show <功能包名称>/srv/xxx.srv查看到xxx.srv内容,此时编译好的srv的.c文件存放在install/<功能包名>/include下,.py文件则存放在install/<功能包名>/local/
3.2.2 创建ROS2服务通讯功能包示例
创建自定义消息功能包srv_demo,采用ament_cmake方式:ros2 pkg create srv_demo --build-type ament_cmake
在功能包内创建srv文件夹,其中存放AddInt.srv文件,需要利用到---符号作为分割,文件在---上面的内容作为请求体的参数,在---下面的内容作为响应体的参数,文件内容如下:
int32 num1int32 num2---int32 sum
此时的功能包内部的目录结构如下:tree -a pldz@pldz-pc:~/share/ROS2_DEMO/3_Chapter/code/srv_demo$ tree -a.├── CMakeLists.txt├── include│ └── srv_demo├── package.xml├── src└── srv└── AddInt.srv
配置packages.xml添加依赖项,由于AddInt.srv没有依赖任何的东西因此不需要添加其他的depend: <?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>msg_demo</name><version>0.0.0</version><description>TODO: Package description</description><maintainer email="pldz@R7000.com">pldz</maintainer><license>TODO: License declaration</license><buildtool_depend>ament_cmake</buildtool_depend><!-- 构建自定义功能包的的必须依赖项 --><buildtool_depend>rosidl_default_generators</buildtool_depend><exec_depend>rosidl_default_runtime</exec_depend><member_of_group>rosidl_interface_packages</member_of_group><test_depend>ament_lint_auto</test_depend><test_depend>ament_lint_common</test_depend><export><build_type>ament_cmake</build_type></export></package>
配置CMakeLists.txt文件,添加构建自定义AddInt.srv的依赖: cmake_minimum_required(VERSION 3.8)project(srv_demo)if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")add_compile_options(-Wall -Wextra -Wpedantic)endif()# find dependenciesfind_package(ament_cmake REQUIRED)# uncomment the following section in order to fill in# further dependencies manually.# find_package(<dependency> REQUIRED)# 1. 构建自定义功能包必须的依赖find_package(rosidl_default_generators REQUIRED)# 2. 配置自定义的srv的位置rosidl_generate_interfaces(${PROJECT_NAME}"srv/AddInt.srv")if(BUILD_TESTING)find_package(ament_lint_auto REQUIRED)# the following line skips the linter which checks for copyrights# comment the line when a copyright and license is added to all source filesset(ament_cmake_copyright_FOUND TRUE)# the following line skips cpplint (only works in a git repo)# comment the line when this package is in a git repo and when# a copyright and license is added to all source filesset(ament_cmake_cpplint_FOUND TRUE)ament_lint_auto_find_test_dependencies()endif()ament_package()
构建功能包:colcon build --packages-select srv_demo
查看自定义的消息:激活环境:. install/setup.bash ,查看自定义消息ros2 interface show srv_demo/srv/AddInt
pldz@pldz-pc:~/share/ROS2_DEMO/3_Chapter/code$ colcon build --packages-select srv_demoStarting >>> srv_demoFinished <<< srv_demo [12.2s] Summary: 1 package finished [12.5s]pldz@pldz-pc:~/share/ROS2_DEMO/3_Chapter/code$ . install/setup.bash pldz@pldz-pc:~/share/ROS2_DEMO/3_Chapter/code$ ros2 interface show srv_demo/srv/AddInt int32 num1int32 num2---int32 sum
查看instll下面的文件:其中.c的class文件在install/<功能包名>/include/<功能包名>/<功能包名>/msg/**,.py在install/<功能包名>/local/lib/python3.10/dist-packages/<功能包名>/msg/**,后续Vscode可以通过配置settings.json添加提示 3.3 使用C/C++实现ROS2服务通讯
这里直接使用3.2.2创建的srv_demo服务功能包进行ROS2 C/C++的服务通讯的实现
参考内容:
Writing a simple service and client (C++)
2.3.3_服务通信_C++实现_01框架搭建
ROS2探索(三)service
3.3.1 创建C/C++服务通讯服务端功能包并编写节点文件
创建功能包,其中功能包名称为cpp_srv_server,节点名为cppSrvServerNode:ros2 pkg create cpp_srv_server --build-type ament_cmake --node-name cppSrvServerNode
配置Vscode环境,在工作空间创建.vscode文件夹,并加入settings.json文件,添加ROS2的include环境和当前工作空间的install文件夹下的srv_demo功能包的include路径:
{"C_Cpp.default.includePath": ["/opt/ros/humble/include/**","./install/srv_demo/include/**"],}
编写服务端节点文件,主要包括导入包,创建服务节点,实现回调函数: // 1. 调用自定义的服务文件// 1.1 rclcpp和srv_demo两个功能包的头文件// 如果有vscode的下划线提示,说明是settings.json没有配置好#include "rclcpp/rclcpp.hpp"#include "srv_demo/srv/add_int.hpp"// 1.2. 调用功能包下的自定义的服务,其中自定义的服务名已经变成了一个类名using srv_demo::srv::AddInt;// std_bind的占位符using std::placeholders::_1;using std::placeholders::_2;// 2.定义节点类;class CppSrvServer: public rclcpp::Node{public:// 2.1 构造函数,其中节点名直接赋予cppSrvServerNodeCppSrvServer():Node("cppSrvServerNode"){// 2.2 创建服务端server_ = this->create_service<AddInt>("mySrvName",std::bind(&CppSrvServer::addIntFunc, this, _1, _2));RCLCPP_INFO(this->get_logger(),"Server is starting ...");}private:// 3. 服务端的回调函数实现,其中参数的写法区分主要在于类型,参数数量包括请求体req和响应体rspvoid addIntFunc(const AddInt::Request::SharedPtr req, const AddInt::Response::SharedPtr rsp){rsp->sum = req->num1 + req->num2;RCLCPP_INFO(this->get_logger(),"request body :(%d,%d), response is :%d", req->num1, req->num2, rsp->sum);}// 4 服务的声明rclcpp::Service<AddInt>::SharedPtr server_;};int main(int argc, char const *argv[]){rclcpp::init(argc,argv);auto server_ = std::make_shared<CppSrvServer>();rclcpp::spin(server_);rclcpp::shutdown();return 0;}
3.3.2 配置C/C++服务通讯服务端功能包
配置packages.xml文件,添加依赖项有rclcpp用于构建节点,和srv_demo用于构建AddInt: <?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>cpp_srv_server</name><version>0.0.0</version><description>TODO: Package description</description><maintainer email="pldz@R7000.com">pldz</maintainer><license>TODO: License declaration</license><buildtool_depend>ament_cmake</buildtool_depend><test_depend>ament_lint_auto</test_depend><test_depend>ament_lint_common</test_depend><!-- 依赖rclcpp创建节点和自定义的服务功能包 --><depend>rclcpp</depend><depend>srv_demo</depend><export><build_type>ament_cmake</build_type></export></package>
配置CMakeLists.txt: cmake_minimum_required(VERSION 3.8)project(cpp_srv_server)if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")add_compile_options(-Wall -Wextra -Wpedantic)endif()# find dependenciesfind_package(ament_cmake REQUIRED)# uncomment the following section in order to fill in# further dependencies manually.# find_package(<dependency> REQUIRED)# 1. 列出依赖项的包find_package(rclcpp REQUIRED)find_package(srv_demo REQUIRED)# 2. 默认是已经创建了构建节点的文件配置add_executable(cppSrvServerNode src/cppSrvServerNode.cpp)# 3. 默认已经包括<INSTALL_INTERFACE>的路径也不用更改target_include_directories(cppSrvServerNode PUBLIC$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>$<INSTALL_INTERFACE:include>)target_compile_features(cppSrvServerNode PUBLIC c_std_99 cxx_std_17) # Require C99 and C++17# 4. ament工具构建节点的依赖配置ament_target_dependencies(cppSrvServerNoderclcppsrv_demo)# 5. Install配置,默认ros2 run <包名>的配置install(TARGETS cppSrvServerNodeDESTINATION lib/${PROJECT_NAME})if(BUILD_TESTING)find_package(ament_lint_auto REQUIRED)# the following line skips the linter which checks for copyrights# comment the line when a copyright and license is added to all source filesset(ament_cmake_copyright_FOUND TRUE)# the following line skips cpplint (only works in a git repo)# comment the line when this package is in a git repo and when# a copyright and license is added to all source filesset(ament_cmake_cpplint_FOUND TRUE)ament_lint_auto_find_test_dependencies()endif()# 6. 生成包的环境ament_package()
3.3.3 编译并运行C/C++服务通讯服务端
编译: colcon build --packages-select cpp_srv_server
激活环境:. install/setup.bash
运行:ros2 run cpp_srv_server cppSrvServerNode
pldz@pldz-pc:~/share/ROS2_DEMO/3_Chapter/code$ colcon build --packages-select cpp_srv_serverStarting >>> cpp_srv_serverFinished <<< cpp_srv_server [20.6s] Summary: 1 package finished [21.0s]pldz@pldz-pc:~/share/ROS2_DEMO/3_Chapter/code$ . install/setup.bash pldz@pldz-pc:~/share/ROS2_DEMO/3_Chapter/code$ ros2 run cpp_srv_server cppSrvServerNode [INFO] [1683125184.734934447] [cppSrvServerNode]: Server is starting ...^C[INFO] [1683125186.240946812] [rclcpp]: signal_handler(signum=2)
3.3.4 创建C/C++服务通讯客户端功能包并编写节点文件
创建功能包,功能包名为cpp_srv_client,节点名称为cppSrvClientNode,依赖rclcpp和srv_demo:ros2 pkg create cpp_srv_client --build-type ament_cmake --node-name cppSrvClientNode --dependencies rclcpp srv_demo
编写客户端节点,注意在此过程中需要和 服务端的连接进行判断,以及发送的请求是否能够收到返回值的用法的操作:
// 1. 调用自定义的服务文件// 1.1 rclcpp和srv_demo两个功能包的头文件#include "rclcpp/rclcpp.hpp"#include "srv_demo/srv/add_int.hpp"// 1.2. 调用功能包下的自定义的服务,其中自定义的服务名已经变成了一个类名using srv_demo::srv::AddInt;// 时间函数用于持续访问服务端using namespace std::chrono_literals;// 2.定义节点类;class CppSrvClient: public rclcpp::Node{public:CppSrvClient():Node("cppSrvClientNode"){// 2.1 创建客户端,并绑定服务通讯名称为mySrvNameclient_ = this->create_client<AddInt>("mySrvName");RCLCPP_INFO(this->get_logger(),"Client is starting ...");}// 2.2 等待与服务的连接bool connect_server(){while (!client_->wait_for_service(1s)){if (!rclcpp::ok()){RCLCPP_INFO(rclcpp::get_logger("rclcpp"),"Interrupted while waiting for the service. Exiting.");return false;}RCLCPP_INFO(this->get_logger(),"service not available, waiting again...");}return true;}// 2.3 客户端发送请求;rclcpp::Client<srv_demo::srv::AddInt>::FutureAndRequestId send_request(int32_t num1, int32_t num2){auto request = std::make_shared<AddInt::Request>();request->num1 = num1;request->num2 = num2;// 2.3.1 发送请求auto response = client_->async_send_request(request);return response;}private:// 2.4 服务通讯客户端的声明rclcpp::Client<AddInt>::SharedPtr client_;};int main(int argc, char ** argv){// 3. 初始化ROS2客户端rclcpp::init(argc,argv);// 3.1 创建对象指针并调用其功能;auto cppSrvClientNode = std::make_shared<CppSrvClient>();// 3.2 连接客户端bool flag = cppSrvClientNode->connect_server();if (!flag){RCLCPP_INFO(rclcpp::get_logger("rclcpp"),"Connect failed! ");return 0;}// 3.3 发送请求并等待响应auto response = cppSrvClientNode->send_request(100,200);// 3.4 节点处理响应if (rclcpp::spin_until_future_complete(cppSrvClientNode,response) == rclcpp::FutureReturnCode::SUCCESS){RCLCPP_INFO(cppSrvClientNode->get_logger(),"The response is :%d!", response.get()->sum);} else {RCLCPP_INFO(cppSrvClientNode->get_logger(),"Request error");}rclcpp::shutdown();return 0;}
3.3.5 编译并运行C/C++服务通讯客户端节点
由于在创建包的过程中已经指明了所有依赖项,不需要进行额外的配置,直接编译运行即可:colcon build --packages-select cpp_srv_client
激活环境并运行节点
3.4 使用Python实现ROS2服务通讯
参考内容
Writing a simple service and client (Python)
2.3.4_服务通信_Python实现_01框架搭建
3.4.1 创建Python服务通讯服务端功能包并编写节点文件
创建功能包,包名为python_srv_server,节点名为pythonSrvServerNode:ros2 pkg create python_srv_server --build-type ament_python --node-name pythonSrvServerNode
配置Vscode,主要添加功能包的提示环境,编辑settings.json:
{"C_Cpp.default.includePath": ["/opt/ros/humble/include/**","./install/srv_demo/include/**"],"python.analysis.include": ["/opt/ros/humble/local/lib/python3.10/dist-packages/**","./install/srv_demo/local/lib/python3.10/dist-packages/**"]}
编写Python服务端节点文件: import rclpyfrom rclpy.node import Node# 1. 导入包,其中的类名等于服务文件名称from srv_demo.srv import AddInt# 2. 定义服务端节点class PythonSrvServer(Node):def __init__(self):super().__init__('pythonSrvServerNode')# 2.1 创建服务端self.srver_ = self.create_service(AddInt, 'mySrvName', self.addIntFunc)self.get_logger().info("Server is starting ...")# 2.2 服务端的处理回调函数def addIntFunc(self, request:AddInt, response:AddInt):response.sum = request.num1 + request.num2self.get_logger().info('The request is :{} {},Response is :{}'.format(request.num1, request.num2, response.sum))return responsedef main():rclpy.init()pythonSrvServerNode = PythonSrvServer()rclpy.spin(pythonSrvServerNode)rclpy.shutdown()if __name__ == '__main__':main()
3.4.2 配置Python服务通讯服务端功能包
配置packages.xml增加rclpy和srv_demo两个依赖项,注意Python本身是可执行文件,因此它的为<exec_depend>与C/C++的<depend>关键字不同: <?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>python_srv_server</name><version>0.0.0</version><description>TODO: Package description</description><maintainer email="pldz@R7000.com">pldz</maintainer><license>TODO: License declaration</license><!-- 添加依赖项的包,注意是exec_depend --><exec_depend>rclpy</exec_depend><exec_depend>srv_demo</exec_depend><test_depend>ament_copyright</test_depend><test_depend>ament_flake8</test_depend><test_depend>ament_pep257</test_depend><test_depend>python3-pytest</test_depend><export><build_type>ament_python</build_type></export></package>
配置setup.py文件,事实上,我们已经创建包的时候已经指定了节点名,其实应该是不用配置生成节点的main入口的: from setuptools import setuppackage_name = 'python_srv_server'setup(name=package_name,version='0.0.0',packages=[package_name],data_files=[('share/ament_index/resource_index/packages',['resource/' + package_name]),('share/' + package_name, ['package.xml']),],install_requires=['setuptools'],zip_safe=True,maintainer='pldz',maintainer_email='pldz@R7000.com',description='TODO: Package description',license='TODO: License declaration',tests_require=['pytest'],# 配置节点main函数入口entry_points={'console_scripts': ['pythonSrvServerNode = python_srv_server.pythonSrvServerNode:main'],},)
3.4.3 编译并运行Python服务通讯服务端功能包
编译:colcon build --packages-select python_srv_server
激活环境:. install/setup.bash
运行节点:ros2 run python_srv_server pythonSrvServerNode
pldz@pldz-pc:~/share/ROS2_DEMO/3_Chapter/code$ colcon build --packages-select python_srv_serverStarting >>> python_srv_server--- stderr: python_srv_server /usr/lib/python3/dist-packages/setuptools/command/install.py:34: SetuptoolsDeprecationWarning: setup.py install is deprecated. Use build and pip and other standards-based tools.warnings.warn(---Finished <<< python_srv_server [2.18s]Summary: 1 package finished [2.71s]1 package had stderr output: python_srv_serverpldz@pldz-pc:~/share/ROS2_DEMO/3_Chapter/code$ . install/setup.bash pldz@pldz-pc:~/share/ROS2_DEMO/3_Chapter/code$ ros2 run python_srv_server pythonSrvServerNode [INFO] [1683157866.794236460] [pythonSrvServerNode]: Server is starting ...
3.3.4 创建Python服务通讯客户端功能包并编写节点文件
创建Python功能包时,包名为python_srv_client,节点名为pythonSrvClientNode,直接指定依赖项rclpy和srv_demo:ros2 pkg create python_srv_client --build-type ament_python --node-name pythonSrvClientNode --dependencies rclpy srv_demo
编写节点:主要是统一节点的订阅话题,异步接收返回的结果
import sysimport rclpyfrom rclpy.node import Node# 1. 导入包from srv_demo.srv import AddInt# 2.定义客户端节点class pythonSrvClient(Node):def __init__(self):# 2.1 继承node节点,节点名为pythonSrvClientNodesuper().__init__('pythonSrvClientNode')# 2.2 创建客户端self.client_ = self.create_client(AddInt, 'mySrvName')# 2.3 等待连接self.wait_for_connect()# 3. 实现等待函数def wait_for_connect(self):while not self.client_.wait_for_service(timeout_sec=1.0):self.get_logger().info('Waiting for connect ...')# 4. 实现发送请求函数def send_request(self, num1, num2):request = AddInt.Request()request.num1 = num1request.num2 = num2self.future = self.client_.call_async(request)def main():rclpy.init()# 5.创建客户端节点pythonSrvClientNode = pythonSrvClient()pythonSrvClientNode.send_request(200,300)# 6. 等待响应rclpy.spin_until_future_complete(pythonSrvClientNode,pythonSrvClientNode.future)try:response = pythonSrvClientNode.future.result()except Exception as e:pythonSrvClientNode.get_logger().info('Request error {}'.format(e))else:pythonSrvClientNode.get_logger().info('Response is {}'.format(response.sum))rclpy.shutdown()if __name__ == '__main__':main()
3.4.5 编译并运行Python服务通讯客户端节点
由于指定了依赖项,直接编译即可: colcon build --packages-select python_srv_client
激活环境: . install/setup.bash
运行所有节点:
3.5 服务通讯小结
-
- 创建功能包时,如果能够直接指定节点名和依赖项,可以省去很大部分的配置工作
-
- C/C++手动配置功能包时,packages.xml配置依赖项,CMakeLists.txt配置编译的内容,包括find_package;add_executable;target_include_directories;ament_target_dependencies;install;ament_package这六个部分
-
- Python手动配置功能包时,packages.xml配置可执行的依赖项exec_depend而不是depend,并且在setup.py中主要配置节点的main函数入口
-
- 服务通讯的导入本质是创建一个C/C++的功能包,将里面的自定义消息编译成可以使用的.c和.py文件,值得注意的是,生成好的中间文件是存放在 工作空间的install文件夹下,如果这个这个工作空间的install找不到或者不是一个,那是需要再进一步的手动配置的
-
- 服务通讯的请求响应是这里是异步实现的,即客户端需要等待服务端返回的结果,才能判断是否完成接收