当前位置: 首页 > news >正文

【ROS2学习笔记】服务

前言


本系列博文是本人的学习笔记,自用为主,不是教程,学习请移步其他大佬的相关教程。前几篇学习资源来自鱼香ROS大佬的详细教程,适合深入学习,但对本人这样的初学者不算友好,而且涉及python与C++混合编程内容,增加了学习成本,后续笔记将以@古月居的ROS2入门21讲为主,侵权即删。

一、服务通信概述

ROS2中的服务通信是一种同步通信机制,与话题通信(发布-订阅模型)不同,它采用客户端/服务器(CS)模型,类似于"你问我答"的交互方式。当客户端需要数据时,发送请求给服务器;服务器处理请求后返回应答,客户端等待应答结果。

服务通信 vs 话题通信

特性服务通信话题通信
模式请求-响应发布-订阅
方向双向单向
同步性同步异步
典型应用执行动作/获取数据持续数据流(如图像、传感器数据)

为什么需要服务通信?
比如在机器视觉应用中,我们不需要持续订阅目标位置(话题通信),而是在需要时发送请求获取最新位置(服务通信),这样更高效、更合理。


二、服务通信核心概念

1. 客户端/服务器模型

  • 服务器:提供服务,等待客户端请求
  • 客户端:发送请求,等待服务器响应
  • 服务名:客户端和服务器共同识别的服务标识

2. 同步通信

客户端发送请求后会等待服务器响应,如果服务器长时间无响应,客户端可以判断服务器可能宕机或网络问题。这与话题通信(异步)有本质区别。

3. 一对多通信

  • 一个服务器可以被多个客户端使用
  • 服务器是唯一的,但客户端可以有多个

三、服务接口定义(.srv文件)

服务通信的核心是数据的定义,数据分为请求数据响应数据

1. .srv文件结构

# 请求数据
int32 num1
int32 num2
---
# 响应数据
int32 sum
  • --- 分隔请求和响应
  • 每行定义一个数据字段,格式为数据类型 字段名

2. 创建.srv文件

创建服务接口文件的步骤:

  1. 创建功能包(如果还没有)
  2. 在功能包中创建srv文件夹
  3. srv文件夹中创建.srv文件

四、实战案例:加法求解器

1. 创建工作空间和功能包

# 创建工作空间
mkdir -p ~/ros2_ws/src
cd ~/ros2_ws/src# 创建功能包(使用Python)
ros2 pkg create --build-type ament_python learning_service --dependencies rclpy

说明

  • --build-type ament_python:指定使用Python构建系统
  • --dependencies rclpy:添加依赖rclpy(ROS2 Python客户端库)
  • learning_service:功能包名称

2. 创建服务接口文件

# 创建srv文件夹
mkdir -p ~/ros2_ws/src/learning_service/srv# 创建AddTwoInts.srv文件
touch ~/ros2_ws/src/learning_service/srv/AddTwoInts.srv# 编辑AddTwoInts.srv文件
echo -e "int32 a\nint32 b\n---\nint32 sum" > ~/ros2_ws/src/learning_service/srv/AddTwoInts.srv

说明

  • 定义了两个请求参数ab,一个响应参数sum
  • 这是ROS2官方示例服务接口

3. 修改功能包配置文件

编辑package.xml,确保添加了rosidl_default_generators依赖:

<build_depend>rosidl_default_generators</build_depend>

编辑CMakeLists.txt,添加服务接口生成配置:

find_package(rosidl_default_generators REQUIRED)
rosidl_generate_interfaces(${PROJECT_NAME}"srv/AddTwoInts.srv"
)

说明

  • rosidl_generate_interfaces:指定要生成接口代码的服务文件
  • 这行代码告诉ROS2在编译时生成服务接口的Python/C++代码

4. 编译功能包

cd ~/ros2_ws
colcon build --packages-select learning_service

说明

  • colcon build:编译工作空间
  • --packages-select learning_service:只编译learning_service功能包

5. 服务端实现

创建服务端文件:~/ros2_ws/src/learning_service/learning_service/service_adder_server.py

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
@作者: 古月居(www.guyuehome.com)
@说明: ROS2服务示例-提供加法器的服务器处理功能
"""import rclpy                                     # ROS2 Python接口库
from rclpy.node   import Node                    # ROS2 节点类
from learning_interface.srv import AddTwoInts    # 自定义的服务接口class adderServer(Node):def __init__(self, name):super().__init__(name)                      # ROS2节点父类初始化# 创建服务器对象(接口类型、服务名、服务器回调函数)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('Incoming request\na: %d b: %d' % (request.a, request.b))return response                             # 返回计算结果def main(args=None):"""ROS2节点主入口main函数"""rclpy.init(args=args)                           # ROS2 Python接口初始化node = adderServer("service_adder_server")      # 创建ROS2节点对象并进行初始化rclpy.spin(node)                                # 循环等待ROS2退出node.destroy_node()                             # 销毁节点对象rclpy.shutdown()                                # 关闭ROS2 Python接口if __name__ == '__main__':main()

6. 客户端实现

创建客户端文件:~/ros2_ws/src/learning_service/learning_service/service_adder_client.py

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
@作者: 古月居(www.guyuehome.com)
@说明: ROS2服务示例-发送两个加数,请求加法器计算
"""import sys
import rclpy                                  # ROS2 Python接口库
from rclpy.node   import Node                 # ROS2 节点类
from learning_interface.srv import AddTwoInts # 自定义的服务接口class adderClient(Node):def __init__(self, name):super().__init__(name)                   # ROS2节点父类初始化# 创建服务客户端对象(服务接口类型,服务名)self.client = self.create_client(AddTwoInts, 'add_two_ints')# 循环等待服务器端成功启动while not self.client.wait_for_service(timeout_sec=1.0):self.get_logger().info('service not available, waiting again...')# 创建服务请求的数据对象self.request = AddTwoInts.Request()def send_request(self):"""发送服务请求的函数"""self.request.a = int(sys.argv[1])  # 从命令行参数获取第一个整数self.request.b = int(sys.argv[2])  # 从命令行参数获取第二个整数# 异步方式发送服务请求self.future = self.client.call_async(self.request)def main(args=None):"""ROS2节点主入口main函数"""rclpy.init(args=args)                       # ROS2 Python接口初始化node = adderClient("service_adder_client")  # 创建ROS2节点对象并进行初始化node.send_request()                         # 发送服务请求# 循环执行节点,等待服务响应while rclpy.ok():rclpy.spin_once(node)                   # 执行一次节点回调# 检查数据是否处理完成if node.future.done():try:response = node.future.result()  # 获取服务器端的反馈数据except Exception as e:node.get_logger().info('Service call failed %r' % (e,))else:# 打印收到的反馈信息node.get_logger().info('Result of add_two_ints: for %d + %d = %d' % (node.request.a, node.request.b, response.sum))break  # 退出循环node.destroy_node()                         # 销毁节点对象rclpy.shutdown()                            # 关闭ROS2 Python接口if __name__ == '__main__':main()

7. 修改setup.py文件

编辑~/ros2_ws/src/learning_service/setup.py,添加入口点:

from setuptools import setuppackage_name = 'learning_service'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='your_name',maintainer_email='your_email@example.com',description='TODO: Package description',license='TODO: License declaration',tests_require=['pytest'],entry_points={'console_scripts': ['service_adder_client = learning_service.service_adder_client:main','service_adder_server = learning_service.service_adder_server:main',],},
)

8. 重新编译功能包

cd ~/ros2_ws
colcon build --packages-select learning_service

9. 运行服务端和客户端

# 服务端(在第一个终端)
source install/setup.bash
ros2 run learning_service service_adder_server# 客户端(在第二个终端)
source install/setup.bash
ros2 run learning_service service_adder_client 2 3

输出结果

[INFO] [service_adder_server]: Incoming request
a: 2 b: 3
[INFO] [service_adder_client]: Result of add_two_ints: for 2 + 3 = 5

五、实战案例:目标物体识别

1. 创建服务接口

learning_service/srv目录下创建GetObjectPosition.srv

echo -e "bool get\n---\nint32 x\nint32 y" > ~/ros2_ws/src/learning_service/srv/GetObjectPosition.srv

说明

  • 请求参数:get(布尔值,表示是否需要位置)
  • 响应参数:xy(目标物体的坐标)

2. 服务端实现

创建~/ros2_ws/src/learning_service/learning_service/service_object_server.py

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
@作者: 古月居(www.guyuehome.com)
@说明: ROS2服务示例-提供目标识别服务
"""import rclpy                           # ROS2 Python接口库
from rclpy.node import Node            # ROS2 节点类
from sensor_msgs.msg import Image      # 图像消息类型
import numpy as np                     # Python数值计算库
from cv_bridge import CvBridge         # ROS与OpenCV图像转换类
import cv2                             # Opencv图像处理库
from learning_interface.srv import GetObjectPosition   # 自定义的服务接口# 红色的HSV阈值
lower_red = np.array([0, 90, 128])     # 红色的HSV阈值下限
upper_red = np.array([180, 255, 255])  # 红色的HSV阈值上限class ImageSubscriber(Node):def __init__(self, name):super().__init__(name)                              # ROS2节点父类初始化# 创建图像订阅者(消息类型、话题名、回调函数、队列长度)self.sub = self.create_subscription(Image, 'image_raw', self.listener_callback, 10)self.cv_bridge = CvBridge()                         # 创建图像转换对象# 创建服务服务器(接口类型、服务名、回调函数)self.srv = self.create_service(GetObjectPosition, 'get_target_position', self.object_position_callback)self.objectX = 0self.objectY = 0def object_detect(self, image):"""目标检测函数,识别红色物体并计算中心坐标"""hsv_img = cv2.cvtColor(image, cv2.COLOR_BGR2HSV)      # 图像从BGR转HSVmask_red = cv2.inRange(hsv_img, lower_red, upper_red) # 图像二值化# 轮廓检测contours, hierarchy = cv2.findContours(mask_red, cv2.RETR_LIST, cv2.CHAIN_APPROX_NONE)# 遍历所有轮廓for cnt in contours:# 过滤小轮廓(噪声)if cnt.shape[0] < 150:continue# 获取轮廓的边界框(x, y, w, h) = cv2.boundingRect(cnt)# 画出轮廓cv2.drawContours(image, [cnt], -1, (0, 255, 0), 2)# 画出中心点cv2.circle(image, (int(x+w/2), int(y+h/2)), 5, (0, 255, 0), -1)# 更新目标坐标self.objectX = int(x+w/2)self.objectY = int(y+h/2)# 显示处理后的图像cv2.imshow("object", image)cv2.waitKey(50)def listener_callback(self, data):"""图像订阅回调函数"""self.get_logger().info('Receiving video frame')  # 日志提示# 将ROS图像消息转换为OpenCV图像image = self.cv_bridge.imgmsg_to_cv2(data, 'bgr8')# 执行目标检测self.object_detect(image)def object_position_callback(self, request, response):"""服务回调函数,处理目标位置请求"""if request.get == True:  # 如果请求需要位置response.x = self.objectX  # 设置x坐标response.y = self.objectY  # 设置y坐标self.get_logger().info('Object position\nx: %d y: %d' % (response.x, response.y))else:  # 无效请求response.x = 0response.y = 0self.get_logger().info('Invalid command')return response  # 返回响应def main(args=None):"""ROS2节点主入口main函数"""rclpy.init(args=args)  # 初始化ROS2node = ImageSubscriber("service_object_server")  # 创建节点rclpy.spin(node)  # 等待ROS2退出node.destroy_node()  # 销毁节点rclpy.shutdown()  # 关闭ROS2if __name__ == '__main__':main()

3. 客户端实现

创建~/ros2_ws/src/learning_service/learning_service/service_object_client.py

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
@作者: 古月居(www.guyuehome.com)
@说明: ROS2服务示例-请求目标识别,等待目标位置应答
"""import rclpy                            # ROS2 Python接口库
from rclpy.node   import Node           # ROS2 节点类
from learning_interface.srv import GetObjectPosition  # 自定义的服务接口class objectClient(Node):def __init__(self, name):super().__init__(name)                  # ROS2节点父类初始化# 创建服务客户端(接口类型、服务名)self.client = self.create_client(GetObjectPosition, 'get_target_position')# 等待服务可用while not self.client.wait_for_service(timeout_sec=1.0):self.get_logger().info('service not available, waiting again...')# 创建服务请求对象self.request = GetObjectPosition.Request()def send_request(self):"""发送服务请求的函数"""self.request.get = True  # 请求获取目标位置# 异步发送服务请求self.future = self.client.call_async(self.request)def main(args=None):"""ROS2节点主入口main函数"""rclpy.init(args=args)  # 初始化ROS2node = objectClient("service_object_client")  # 创建节点node.send_request()  # 发送服务请求# 等待服务响应while rclpy.ok():rclpy.spin_once(node)  # 执行一次节点回调# 检查请求是否完成if node.future.done():try:response = node.future.result()  # 获取响应except Exception as e:node.get_logger().info('Service call failed %r' % (e,))else:# 打印目标位置node.get_logger().info('Result of object position:\n x: %d y: %d' % (response.x, response.y))break  # 退出循环node.destroy_node()  # 销毁节点rclpy.shutdown()  # 关闭ROS2if __name__ == '__main__':main()

4. 修改setup.py文件

setup.py中添加客户端的入口点:

entry_points={'console_scripts': ['service_adder_client = learning_service.service_adder_client:main','service_adder_server = learning_service.service_adder_server:main','service_object_client = learning_service.service_object_client:main','service_object_server = learning_service.service_object_server:main',],
},

5. 重新编译功能包

cd ~/ros2_ws
colcon build --packages-select learning_service

6. 运行全流程

# 终端1:启动相机节点(需要安装usb_cam包)
source install/setup.bash
ros2 run usb_cam usb_cam_node_exe# 终端2:启动服务端(目标识别服务)
source install/setup.bash
ros2 run learning_service service_object_server# 终端3:启动客户端(请求目标位置)
source install/setup.bash
ros2 run learning_service service_object_client

注意:需要确保已安装usb_cam包,用于获取摄像头图像。如果未安装,可以使用sudo apt install ros-<distro>-usb-cam安装。


六、服务命令行操作

ROS2提供了命令行工具来查看和测试服务:

# 查看所有可用服务
ros2 service list# 查看特定服务的类型
ros2 service type <service_name># 调用服务(示例:调用加法服务)
ros2 service call /add_two_ints learning_interface/srv/AddTwoInts "{a: 5, b: 3}"# 调用目标位置服务
ros2 service call /get_target_position learning_interface/srv/GetObjectPosition "{get: true}"

说明

  • learning_interface/srv/AddTwoInts:服务类型
  • "{a: 5, b: 3}":请求数据,格式为JSON

七、服务通信总结

服务通信流程

  1. 创建服务接口:定义.srv文件
  2. 修改功能包配置:添加依赖和接口生成配置
  3. 实现服务端
    • 创建节点
    • 创建服务对象
    • 实现回调函数
  4. 实现客户端
    • 创建节点
    • 创建客户端对象
    • 发送请求并处理响应
  5. 编译和运行:编译功能包并运行服务端和客户端

服务通信优势

  • 同步交互:客户端可以确认服务器响应状态
  • 按需获取:只在需要数据时获取,避免持续通信
  • 明确接口:通过.srv文件定义清晰的请求和响应结构

八、常见问题

1. 服务无法找到?

  • 检查服务名是否正确
  • 确保服务端已启动
  • 检查ROS_DOMAIN_ID是否一致(多机通信时)

2. 服务请求超时?

  • 服务端未启动
  • 服务名不匹配
  • 服务端未正确注册服务

3. 如何添加更多服务?

  • 创建新的.srv文件
  • 修改CMakeLists.txt添加新服务
  • 实现新的服务端和客户端

九、扩展学习

  1. 自定义服务接口:尝试创建更复杂的请求/响应结构
  2. 多服务集成:在一个节点中提供多个服务
  3. 服务QoS配置:调整服务通信的可靠性、延迟等参数
  4. ROS1与ROS2通信:使用ros1_bridge实现跨版本通信
http://www.dtcms.com/a/434641.html

相关文章:

  • 建站是什么东西建公司网站需要自己有系统吗
  • Leetcode热题100(8-12)
  • 六站合一的优势123上网之家网址
  • C++中的多线程编程及线程同步
  • 湛江做网站从微信运营方案
  • 伊吖学C笔记(8、结构体、链表、union、enum、typedef)
  • 2022 年真题配套词汇单词笔记(考研真相)
  • HTML5消费收入矩阵计算器
  • 霸州做阿里巴巴网站庆安建设局网站
  • PCB学习——STM32F103VET6-STM32主控部分
  • 大学生作业做网站网站建设公司比较
  • 写一个星河社区aistudio大模型部署之后的AI agent转发程序
  • 网站上的中英文切换是怎么做的wordpress页面移动端
  • 八、Scala 集合与函数式编程
  • 腾讯CODING Maven的aar制品添加上传流程
  • Effective Modern C++ 条款29: 移动语义的局限性与实践指南
  • 2025年渗透测试面试题总结-98(题目+回答)
  • 深圳网站制作 公司wordpress备份百度云
  • 《时序数据监控平台优化指南:从查询超时到秒级响应,指标下的存储与检索重构实践》
  • 新版android studio创建项目的一些问题
  • 做企业网站有哪些好处软件技术买什么笔记本好
  • 【Redis】Redis的5种核心数据结构和实战场景对应(在项目中的用法)
  • Vue 与 React 深度对比:技术差异、选型建议与未来趋势
  • 创意网站页面wordpress预约小程序
  • Android_framework-odex优化
  • RAG核心特性:文档过滤和检索
  • 26.awk 使用手册
  • AI应用开发新范式:从模型API到交互式网页的极速实现路径
  • 网站建设2017主流代码语言太原百度快照优化排名
  • Python学习之day02学习(函数模块的上传、数据类型+)