ROS2服务

0 阅读5分钟

1.服务是什么

1.1 概述

ROS2服务(Service)是ROS2中一种同步通信机制,采用客户端-服务器模型,允许节点之间进行请求-响应式的交互。 也可以简单理解为客户端发送web请求,服务器端给出响应。

1.2 服务通信模型

如下图所示,客户端在需要某些数据时,针对某个具体的服务,发送请求信息, 提供该服务的服务端收到请求后,会进行处理并反馈应答信息。

1.png

1.3 同步通信

同步通信是ROS2中服务通信的特点。但是客户端代码是可以异步调用的。客户端代码的“异步调用”与底层通信的同步特性不矛盾。 ROS 2 服务的同步通信有以下特点:

  • 一个请求对应一个回复:客户端发送一个请求,服务器必须且只能回复一个响应。
  • 时序上的配对:请求和回复是一一对应的。服务器在处理完请求之前,不会发送其他消息。

1.4 服务接口

和话题通信类似,服务通信的核心是传递数据,数据变成了两部分,即一个请求数据和一个 响应数据,这些数据和话题消息一样,在ROS 2中也需要标准定义,话题使用.msg 文件定义, 服务使用.srv 文件定义。

2.服务通信编程示例

该示例的功能:客户端发送两个整数,服务端返回它们的和。

2.1创建接口功能包

创建接口功能包(把接口统一放在该功能包下)

ros2 pkg create --build-type ament_cmake learn_interface --license Apache-2.0

这里创建了一个C++功能包,你也可以创建python功能包。官方推荐使用C++功能包存放服务接口。

2.2创建服务接口

在learn_interface目录下创建srv目录 接着在srv目录下创建AddTwoInts.srv:

int64 a
int64 b
---
int64 sum

2.3修改CMakeLists.txt和package.xml

在CMakeLists.txt添加内容:

#查找依赖的功能包rosidl_default_generators,
find_package(rosidl_default_generators REQUIRED)

#罗列需要编译的通信接口文件
rosidl_generate_interfaces(${PROJECT_NAME}
  "srv/AddTwoInts.srv"
 )

package.xml需要添加代码生成的功能依赖,以便编译时快速 定位所需要的功能包 在package.xml添加内容:

 <build_depend>rosidl_default_generators</build_depend>
 <exec_depend>rosidl_default_runtime</exec_depend>
 <member_of_group>rosidl_interface_packages</member_of_group>

2.4创建服务功能包

创建服务功能包(为方便起见,服务端和客户端放在同一个功能包)

#在ROS2工作空间的src目录下执行
ros2 pkg create --build-type ament_python learn_service --license Apache-2.0

2.5编写服务端节点(Python)

在learn_service/learn_service目录下创建service_adder_server.py:

import rclpy                                     
from rclpy.node   import Node                    
from learn_interface.srv import AddTwoInts    # 自定义的服务接口

class adderServer(Node):
    def __init__(self, name):
        super().__init__(name)                                                             
        self.srv = self.create_service(AddTwoInts, 'add_two_ints', self.adder_callback)    # 创建服务器对象(接口类型、服务名、服务器回调函数)

    def adder_callback(self, request, response):                                           # 创建回调函数,执行收到请求后对数据的处理
        response.sum = request.a + request.b                                              
        self.get_logger().info('请求数据\na: %d b: %d' % (request.a, request.b))   # 输出日志信息,提示已经完成加法求和计算
        return response                                                                   

def main(args=None):                                 
    rclpy.init(args=args)                            
    node = adderServer("service_adder_server")       # 创建ROS2节点对象并进行初始化
    rclpy.spin(node)                                 # 循环等待ROS2退出
    node.destroy_node()                              
    rclpy.shutdown()                                 

2.6编写客户端节点(Python)

在learn_service/learn_service目录下创建service_adder_client.py:

import random
import rclpy                                                                      
from rclpy.node   import Node                                                   
from learn_interface.srv import AddTwoInts                                     # 自定义的服务接口

class adderClient(Node):
    def __init__(self, name):
        super().__init__(name)                                                   
        self.client = self.create_client(AddTwoInts, 'add_two_ints')              # 创建服务客户端对象(服务接口类型,服务名)
        while not self.client.wait_for_service(timeout_sec=1.0):                  # 循环等待服务器端成功启动
            self.get_logger().info('服务不可用, 请等待...') 
        self.request = AddTwoInts.Request()                                       # 创建服务请求的数据对象
                    
    def send_request(self):                                                      
        self.request.a = random.randint(0, 100)
        self.request.b = random.randint(0, 100)
        self.future = self.client.call_async(self.request)                        # 异步方式发送服务请求

def main(args=None):
    rclpy.init(args=args)                                                         # ROS2 Python接口初始化
    node = adderClient("service_adder_client")                                    
    node.send_request()                                                           # 发送服务请求
    
    while rclpy.ok():                                                             # ROS2系统正常运行
        rclpy.spin_once(node)                                                     # 循环执行一次节点

        if node.future.done():                                                    # 数据是否处理完成
            try:
                response = node.future.result()                                   # 接收服务器端的反馈数据
            except Exception as e:
                node.get_logger().info(
                    '服务调用失败 %r' % (e,))
            else:
                node.get_logger().info(                                           # 将收到的反馈信息打印输出
                    '调用结果 %d + %d = %d' % 
                    (node.request.a, node.request.b, response.sum))
            break
            
    node.destroy_node()                                                           
    rclpy.shutdown()                                                              

2.7 修改setup.py和package.xml

setup.py文件:

entry_points={
        'console_scripts': [
            'service_adder_client = learn_service.service_adder_client:main',
            'service_adder_server = learn_service.service_adder_server:main',
        ],
    },

不同功能包相互隔离,要使用服务接口,需要在package.xml文件添加依赖:

<depend>learn_interface</depend>

2.8 构建并运行节点

2.8.1构建功能包

#构建服务接口功能包
colcon build --packages-select learn_interface
#构建服务功能包
colcon build --packages-select learn_service

或者直接运行

colcon build

2.8.2运行节点

#运行客户端节点
source install/setup.bash
ros2 run learn_service service_adder_client

#运行服务器端节点
source install/setup.bash
ros2 run learn_service service_adder_server

2.9 输出示例

服务器端:

ros2 run learn_service service_adder_server
[INFO] [1775789969.935082632] [service_adder_server]: 请求数据
a: 90 b: 47
[INFO] [1775789985.368041400] [service_adder_server]: 请求数据
a: 92 b: 90

客户端:

ros2 run learn_service service_adder_client
[INFO] [1775789969.939193882] [service_adder_client]: 调用结果 90 + 47 = 137
ros2 run learn_service service_adder_client
[INFO] [1775789985.372322300] [service_adder_client]: 调用结果 92 + 90 = 182

3.服务的命令行操作

3.1 查看服务列表

输入:

ros2 service list

显示结果:

/add_two_ints
/service_adder_server/describe_parameters
/service_adder_server/get_parameter_types
/service_adder_server/get_parameters
/service_adder_server/list_parameters
/service_adder_server/set_parameters
/service_adder_server/set_parameters_atomically

/add_two_ints就是之前我们运行的服务名称。

3.2 查看服务数据类型

ros2 service type [service_name]

输入:

ros2 service type /add_two_ints

显示结果:

learn_interface/srv/AddTwoInts

3.3 发送服务请求

ros2 service call [service_name] [service_type] [service_data]

输入:

ros2 service call /add_two_ints learn_interface/srv/AddTwoInts "{a: 16, b: 25}"

显示结果:

requester: making request: learn_interface.srv.AddTwoInts_Request(a=16, b=25)

response:
learn_interface.srv.AddTwoInts_Response(sum=41)