ROS2 服务Service客户端 服务端完整代码梳理文档第一版简易加法示例目的是让了速了解整 个流程核心看点ROS2 中服务端代码中可以包含订阅功能里常规标准写法。单个节点既能创建订阅者接收图像数据同时又创建服务端对外提供查询接口完全合在一起解决事情。一、ROS2 服务通信整体概述1. 通信模型分类ROS2 两大常用同步 / 异步通信模式话题Topic异步单向流式通信发布者不停发、订阅者被动接收一对多无应答机制。服务Service同步问答式点对点通信严格一问一答固定客户端发请求、服务端处理后返回应答一对一交互。2. 服务通信核心角色服务端Server常驻后台运行注册服务名称监听客户端请求执行业务逻辑计算结果后返回应答你截图里service_adder_server.py就是服务端。客户端Client主动发起请求携带请求数据等待服务端运算完成后接收返回结果service_adder_client.py为客户端。自定义服务接口.srv 文件约定请求数据格式和应答数据格式是两端统一的数据协议相当于双方通信的 “数据契约”。接口独立存放于单独功能包learning_interface不和客户端、服务端业务代码混在一起实现解耦复用。3. 完整通信流程先启动服务端节点初始化 → 创建服务绑定服务名 回调函数 → 持续自旋等待客户端接入。后启动客户端节点初始化 → 创建客户端指定相同服务名循环等待服务端上线。客户端封装请求参数异步发送请求。服务端捕获请求触发回调函数执行业务加法计算填充应答数据。应答原路返回客户端客户端解析结果一次完整问答通信结束。二、本次示例任务说明任务目标搭建 ROS2 标准服务通信框架实现远程两整数加法运算服务端永久运行提供加法计算能力接收客户端传入的两个整数a、b求和后把sum返回。客户端从命令行读取两个数字打包成请求发给服务端等待并打印求和结果。配套文件分工意思是本次涉及到三个文件表格文件所属功能包作用AddTwoInts.srvlearning_interface自定义服务接口定义请求 a、b应答 sumservice_adder_server.pylearning_service服务端节点代码提供加法服务service_adder_client.pylearning_service客户端节点代码发起加法请求三、逐段代码完整解析一服务端代码 service_adder_server.pypython#!/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 # 导入自定义服务接口 # 自定义服务端节点类继承Node父类 class adderServer(Node): def __init__(self, name): super().__init__(name) # 调用父类构造初始化节点 # 核心创建服务对象 # 参数1接口类型AddTwoInts参数2全局服务名add_two_ints参数3收到请求后的回调函数 self.srv self.create_service(AddTwoInts, add_two_ints, self.adder_callback) # 服务回调函数客户端每发一次请求自动触发一次 def adder_callback(self, request, response): # 取出请求里的a、b相加赋值给应答的sum response.sum request.a request.b # 日志打印收到的请求参数 self.get_logger().info(fIncoming request\na: {request.a} b: {request.b}) # 必须return应答对象数据传回客户端 return response # 程序入口函数 def main(argsNone): rclpy.init(argsargs) # ROS2环境初始化 node adderServer(service_adder_server) # 实例化服务端节点 rclpy.spin(node) # 节点持续自旋阻塞等待客户端请求 node.destroy_node() # 销毁节点 rclpy.shutdown() # 关闭ROS2环境 if __name__ __main__: main()关键代码拆解create_service(接口类型, 服务名, 回调函数)接口类型AddTwoInts来自独立接口包规定收发数据结构服务名add_two_ints全网唯一标识客户端必须填写完全一致的名称才能连上回调函数专门处理业务逻辑入参固定request请求、response应答。rclpy.spin(node)让节点持续运行、监听服务请求不会运行完直接退出。二客户端代码 service_adder_client.pypython#!/usr/bin/env python3 # -*- coding: utf-8 -*- 作者: 古月居(www.guyuehome.com) 说明: ROS2服务示例-发送两个加数请求加法器计算 import sys import rclpy from rclpy.node import Node from learning_interface.srv import AddTwoInts # 和服务端导入完全相同的接口 # 自定义客户端节点类 class adderClient(Node): def __init__(self, name): super().__init__(name) # 创建客户端对象同样填写接口类型完全一致的服务名 self.client self.create_client(AddTwoInts, add_two_ints) # 循环轮询等待服务端启动上线1秒超时重试 while not self.client.wait_for_service(timeout_sec1.0): self.get_logger().info(service not available, waiting again...) # 创建请求数据包实例 self.request AddTwoInts.Request() # 封装发送请求逻辑 def send_request(self): # 从终端命令行参数读取两个数字赋值给请求里的a、b self.request.a int(sys.argv[1]) self.request.b int(sys.argv[2]) # 异步发送请求不会阻塞程序卡死 self.future self.client.call_async(self.request) def main(argsNone): rclpy.init(argsargs) node adderClient(service_adder_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(fService call failed %r % (e,)) else: # 打印最终求和结果 node.get_logger().info(fResult of add_two_ints: {response.sum}) break node.destroy_node() rclpy.shutdown() if __name__ __main__: main()关键代码拆解create_client接口类型、服务名必须和服务端一字不差否则无法配对通信wait_for_service容错机制防止客户端先启动、服务端没开导致连接失败call_async异步调用非阻塞发送请求不会卡死终端配合spin_once轮询等待结果sys.argv[1]/[2]运行客户端时在命令行追加两个数字动态传入加法参数。三配套自定义接口 AddTwoInts.srv 内容接口文件分为上下两段---分隔请求、应答plaintext# 请求段客户端发给服务端 int64 a int64 b --- # 应答段服务端返回客户端 int64 sum编译后 ROS2 自动生成 Python 可导入的类两端都能调用Request()、Response()封装解析数据。四、代码运行操作步骤编译工作空间bashcolcon build --packages-select learning_interface learning_service source install/setup.bash终端 1启动服务端bashros2 run learning_service service_adder_server终端持续阻塞等待客户端接入。终端 2启动客户端传入两个加数示例 10 20bashros2 run learning_service service_adder_client 10 20客户端日志输出最终sum30服务端日志打印收到的a10,b20一次问答通信完成。五、以下是如果使用ai 开发本程的提示词参考估记再过一段时间都 不需要以下提示词了学习的目产最好自己会代码1. 接口开发心得接口单独新建独立功能包存放示例learning_interface多个业务包可以重复调用不用重复定义.srv请求、应答变量名、数据类型两端严格统一类型不匹配会直接通信报错CMakeLists.txt、package.xml 必须开启接口编译配置否则编译不会自动生成 Python 接口类代码 import 会爆红。2. 服务端通用开发套路固定 5 步模板导入rclpy、Node 自定义服务接口新建节点类继承 Node构造函数内调用父类初始化create_service()绑定接口、服务名、回调函数编写回调函数读取 request 数据 → 业务逻辑处理 → 填充 response → return responsemain 函数初始化 rclpy、实例节点、spin()常驻运行最后销毁节点关闭环境。3. 客户端通用开发套路固定 6 步模板导入依赖导入和服务端一模一样的接口构造函数创建客户端同名服务号循环等待服务上线实例化Request()对象填充请求参数call_async()异步发送请求循环spin_once轮询 future 状态等待应答返回解析 result () 拿到应答数据做后续业务处理。4. 开发特别要注意思的地方服务名必须两端完全一致大小写、字符不能有差别必须先编译接口包再编译业务包否则找不到接口每次新开终端必须source install/setup.bash否则 ros2 run 找不到可执行文件服务是一问一答服务端 spin 持续运行客户端单次请求执行完毕自动退出回调函数必须 return response否则客户端收不到任何返回值。接下我们还做一个更复杂的示例这个示例是从摄像头中读出识别物本的坐标ROS2 进阶版图像目标识别服务端完整解析文档承接上一篇简易加法服务示例本次是订阅图像话题 OpenCV 颜色识别 服务应答坐标复合型服务端融合「话题订阅」「服务通信」两大 ROS2 核心机制下面分模块完整梳理。一、整体功能总览1. 节点双重能力这个节点不再是单纯的服务端同时兼具两个角色话题订阅者持续订阅相机发布的image_raw图像话题实时接收画面帧服务端 Server对外提供get_target_position服务客户端发起查询请求时把识别到的红色物体像素坐标 X、Y 原路返回。2. 完整业务流水线相机发布图像话题 → 当前节点订阅拿到 ROS 图像消息 → CvBridge 转 OpenCV 格式 → HSV 红色阈值筛选 轮廓检测 → 计算物体中心像素坐标并全局保存 → 客户端调用服务 → 服务回调把坐标打包成应答发回客户端。3. 用到的依赖拆解表格导入库作用rclpy / NodeROS2 节点基础框架sensor_msgs.msg.ImageROS 标准图像话题消息类型CvBridgeROS 图像消息 ↔ OpenCV Mat 格式互转核心桥梁numpy、cv2图像处理、阈值分割、轮廓检测GetObjectPosition自定义.srv 服务接口请求带布尔指令应答返回 x、y 两个整数坐标二、自定义接口约定GetObjectPosition.srv先明确接口结构服务端、客户端必须统一srv# 请求区客户端发给服务端 bool get --- # 应答区服务端返回客户端 int32 x int32 yget: True代表合法查询指令返回最新识别到的目标坐标get: False非法指令应答 x、y 强制置 0 并打印提示。三、逐段代码详细拆解1. 全局常量定义pythonlower_red np.array([0, 90, 128]) # HSV红色下限阈值 upper_red np.array([180, 255, 255])# HSV红色上限阈值固定红色 HSV 筛选区间用来在画面里抠出红色物体。2. 节点类初始化initpythonclass ImageSubscriber(Node): def __init__(self, name): super().__init__(name) # 1. 创建图像订阅者订阅/image_raw话题 self.sub self.create_subscription( Image, image_raw, self.listener_callback, 10) self.cv_bridge CvBridge() # 图像转换实例 # 2. 创建服务端绑定接口、服务名、服务回调函数 self.srv self.create_service( GetObjectPosition, get_target_position, self.object_position_callback) # 全局变量缓存最新目标物体中心坐标 self.objectX 0 self.objectY 0关键要点同一个节点内可以同时创建订阅者 服务端ROS2 允许单节点多通信实体self.objectX/Y是成员变量图像回调实时更新服务回调直接读取实现数据跨函数共享。3. 图像核心处理函数 object_detect (image)pythondef object_detect(self, image): hsv_img cv2.cvtColor(image, cv2.COLOR_BGR2HSV) # BGR转HSV mask_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 (x, y, w, h) cv2.boundingRect(cnt) cv2.drawContours(image, [cnt], -1, (0, 255, 0), 2) # 绘制轮廓 cv2.circle(image, (int(xw/2), int(yh/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)图像处理完整步骤色彩空间转换OpenCV 默认 BGRHSV 做颜色分割鲁棒性更强二值掩码只保留红色像素其余全部变黑轮廓提取 噪声过滤太小的色块直接丢弃避免噪点干扰外接矩形计算中心像素点赋值给节点成员变量持久保存。4. 话题订阅回调 listener_callback相机每发布一帧图像该函数自动触发pythondef listener_callback(self, data): self.get_logger().info(Receiving video frame) # ROS图像消息 → OpenCV图像Mat image self.cv_bridge.imgmsg_to_cv2(data, bgr8) self.object_detect(image) # 送入识别函数更新坐标核心CvBridge做格式转换ROS 的Image消息不能直接给 cv2 处理必须转换。5. 服务回调函数 object_position_callback应答客户端请求pythondef object_position_callback(self, request, response): if request.get True: # 合法请求把缓存好的坐标填入应答 response.x self.objectX response.y self.objectY self.get_logger().info(Object position\nx: %d y: %d % (response.x, response.y)) else: # 非法指令坐标清零 response.x 0 response.y 0 self.get_logger().info(Invalid command) return response服务端标准固定格式入参固定request、response修改 response 字段后必须 return 返回。6. main 入口函数pythondef main(argsNone): rclpy.init(argsargs) node ImageSubscriber(service_object_server) rclpy.spin(node) # 持续自旋同时监听话题等待服务调用 node.destroy_node() rclpy.shutdown()rclpy.spin(node)会同时驱动订阅回调、服务回调两个事件不用额外多写自旋逻辑。四、完整运行逻辑时序梳理终端 1启动相机节点持续发布/image_raw图像话题终端 2运行本图像识别服务端节点自动订阅图像话题每一帧实时做红色物体检测不断刷新objectX/Y同时服务端静默等待客户端连接终端 3运行客户端发送get: True请求服务端触发服务回调把最新识别好的像素坐标打包返回客户端客户端接收并打印 X、Y 坐标单次服务问答完成。ROS2 视觉目标坐标查询客户端完整代码解析承接上一节图像识别复合服务端代码这份是配套客户端service_object_client.py作用是主动发起请求向服务端索要识别到的目标物体像素坐标下面完整拆解、注释、梳理逻辑。一、客户端整体功能说明客户端节点启动后持续尝试连接名为get_target_position的服务连接成功后自动封装请求指令getTrue发送给服务端阻塞等待服务端返回目标物体x、y像素坐标接收应答后打印坐标程序自动结束。二、完整带注释客户端代码python#!/usr/bin/env python3 # -*- coding: utf-8 -*- 作者: 古月居(www.guyuehome.com) 说明: ROS2服务示例-请求目标识别等待目标位置应答 # 1. 导入依赖库 import rclpy from rclpy.node import Node # 导入和服务端完全一致的自定义服务接口 from learning_interface.srv import GetObjectPosition # 自定义客户端节点类继承ROS2基类Node class objectClient(Node): def __init__(self, name): super().__init__(name) # 创建客户端对象接口类型 和服务端一模一样的服务名 self.client self.create_client(GetObjectPosition, get_target_position) # 循环轮询每隔1秒检测一次服务端是否上线 while not self.client.wait_for_service(timeout_sec1.0): self.get_logger().info(service not available, waiting again...) # 实例化请求数据包对象 self.request GetObjectPosition.Request() # 封装发送请求函数 def send_request(self): # 填充请求指令True代表请求获取目标坐标 self.request.get True # 异步发送请求非阻塞 self.future self.client.call_async(self.request) # 程序入口主函数 def main(argsNone): # ROS2环境初始化 rclpy.init(argsargs) # 实例化客户端节点命名service_object_client node 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: # 正常接收坐标打印x、y像素值 node.get_logger().info( Result of object position:\n x: %d y: %d % (response.x, response.y)) # 拿到结果后跳出循环 break # 销毁节点、关闭ROS2环境 node.destroy_node() rclpy.shutdown() if __name__ __main__: main()三、逐模块逻辑拆解1. 导入部分pythonfrom learning_interface.srv import GetObjectPosition必须和服务端导入同一个自定义接口包、同一个服务类型.srv中定义的bool get请求字段、int32 x/y应答字段两端完全对齐。2. 客户端构造函数__init__create_client(接口类型, 服务名)服务名字符串get_target_position必须和服务端create_service里填写的名称一字不差大小写、符号不能改动否则无法建立通信wait_for_service(timeout_sec1.0)容错机制如果客户端先启动、视觉服务端还没运行不会直接报错退出每隔 1 秒重试连接控制台持续打印等待日志。3.send_request()请求发送函数pythonself.request.get True self.future self.client.call_async(self.request)给请求包的get赋值为True对应服务端判断合法查询call_async()异步调用不会阻塞终端通过future对象异步接收返回结果是 ROS2 推荐写法。4. 结果等待与解析主循环pythonwhile rclpy.ok(): rclpy.spin_once(node) if node.future.done():spin_once(node)单次驱动节点回调不永久阻塞future.done()标记异步请求是否完成、是否收到服务端回包future.result()安全取出应答数据里面包含response.x、response.y目标像素坐标。5. 收尾销毁流程客户端单次问答完成后主动执行destroy_node()销毁节点 →shutdown()关闭 ROS2 运行环境客户端程序直接退出符合服务一问一答的通信特性。四、整套视觉服务完整运行流程服务端 客户端 相机终端 A启动相机节点持续发布/image_raw图像话题终端 B运行视觉识别服务端bashros2 run learning_service service_object_server自动订阅图像、实时颜色轮廓检测、持续更新目标物体 XY 坐标静默等待客户端调用终端 C运行本次客户端代码bashros2 run learning_service service_object_client自动连接服务、发送查询指令、立刻打印识别出来的像素坐标客户端运行结束。五、和之前加法客户端的共性与区别共性ROS2 客户端标准通用模板固定四步结构创建客户端 → 等待服务上线 → 填充请求异步发送 → 循环自旋等待应答都用future异步回调机制、异常捕获、spin_once轮询结果最后统一销毁节点、关闭 rclpy。本次视觉客户端独有差异请求不再是两个数字 a、b而是布尔控制位get应答不再是单一求和 sum而是两个坐标 x、y服务端不是当场计算数值而是提前订阅图像持续预处理客户端只是按需读取缓存好的结果。六、开发踩坑注意点服务名严格匹配客户端create_client第二个字符串必须和服务端create_service第二个字符串完全一致空格、大小写不能出错接口包必须提前编译一定要先编译learning_interface接口包再编译业务包否则import GetObjectPosition会爆红新开终端务必 source 环境bashsource install/setup.bash不然ros2 run找不到客户端可执行文件必须先开服务端再运行客户端客户端虽然有等待重试逻辑但服务端未启动图像识别时返回的 x/y 默认是 0拿不到真实坐标。
python核心基础,这关于基于Moveltg加 Ros2实战Python编程基础实课
发布时间:2026/6/14 22:44:02
ROS2 服务Service客户端 服务端完整代码梳理文档第一版简易加法示例目的是让了速了解整 个流程核心看点ROS2 中服务端代码中可以包含订阅功能里常规标准写法。单个节点既能创建订阅者接收图像数据同时又创建服务端对外提供查询接口完全合在一起解决事情。一、ROS2 服务通信整体概述1. 通信模型分类ROS2 两大常用同步 / 异步通信模式话题Topic异步单向流式通信发布者不停发、订阅者被动接收一对多无应答机制。服务Service同步问答式点对点通信严格一问一答固定客户端发请求、服务端处理后返回应答一对一交互。2. 服务通信核心角色服务端Server常驻后台运行注册服务名称监听客户端请求执行业务逻辑计算结果后返回应答你截图里service_adder_server.py就是服务端。客户端Client主动发起请求携带请求数据等待服务端运算完成后接收返回结果service_adder_client.py为客户端。自定义服务接口.srv 文件约定请求数据格式和应答数据格式是两端统一的数据协议相当于双方通信的 “数据契约”。接口独立存放于单独功能包learning_interface不和客户端、服务端业务代码混在一起实现解耦复用。3. 完整通信流程先启动服务端节点初始化 → 创建服务绑定服务名 回调函数 → 持续自旋等待客户端接入。后启动客户端节点初始化 → 创建客户端指定相同服务名循环等待服务端上线。客户端封装请求参数异步发送请求。服务端捕获请求触发回调函数执行业务加法计算填充应答数据。应答原路返回客户端客户端解析结果一次完整问答通信结束。二、本次示例任务说明任务目标搭建 ROS2 标准服务通信框架实现远程两整数加法运算服务端永久运行提供加法计算能力接收客户端传入的两个整数a、b求和后把sum返回。客户端从命令行读取两个数字打包成请求发给服务端等待并打印求和结果。配套文件分工意思是本次涉及到三个文件表格文件所属功能包作用AddTwoInts.srvlearning_interface自定义服务接口定义请求 a、b应答 sumservice_adder_server.pylearning_service服务端节点代码提供加法服务service_adder_client.pylearning_service客户端节点代码发起加法请求三、逐段代码完整解析一服务端代码 service_adder_server.pypython#!/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 # 导入自定义服务接口 # 自定义服务端节点类继承Node父类 class adderServer(Node): def __init__(self, name): super().__init__(name) # 调用父类构造初始化节点 # 核心创建服务对象 # 参数1接口类型AddTwoInts参数2全局服务名add_two_ints参数3收到请求后的回调函数 self.srv self.create_service(AddTwoInts, add_two_ints, self.adder_callback) # 服务回调函数客户端每发一次请求自动触发一次 def adder_callback(self, request, response): # 取出请求里的a、b相加赋值给应答的sum response.sum request.a request.b # 日志打印收到的请求参数 self.get_logger().info(fIncoming request\na: {request.a} b: {request.b}) # 必须return应答对象数据传回客户端 return response # 程序入口函数 def main(argsNone): rclpy.init(argsargs) # ROS2环境初始化 node adderServer(service_adder_server) # 实例化服务端节点 rclpy.spin(node) # 节点持续自旋阻塞等待客户端请求 node.destroy_node() # 销毁节点 rclpy.shutdown() # 关闭ROS2环境 if __name__ __main__: main()关键代码拆解create_service(接口类型, 服务名, 回调函数)接口类型AddTwoInts来自独立接口包规定收发数据结构服务名add_two_ints全网唯一标识客户端必须填写完全一致的名称才能连上回调函数专门处理业务逻辑入参固定request请求、response应答。rclpy.spin(node)让节点持续运行、监听服务请求不会运行完直接退出。二客户端代码 service_adder_client.pypython#!/usr/bin/env python3 # -*- coding: utf-8 -*- 作者: 古月居(www.guyuehome.com) 说明: ROS2服务示例-发送两个加数请求加法器计算 import sys import rclpy from rclpy.node import Node from learning_interface.srv import AddTwoInts # 和服务端导入完全相同的接口 # 自定义客户端节点类 class adderClient(Node): def __init__(self, name): super().__init__(name) # 创建客户端对象同样填写接口类型完全一致的服务名 self.client self.create_client(AddTwoInts, add_two_ints) # 循环轮询等待服务端启动上线1秒超时重试 while not self.client.wait_for_service(timeout_sec1.0): self.get_logger().info(service not available, waiting again...) # 创建请求数据包实例 self.request AddTwoInts.Request() # 封装发送请求逻辑 def send_request(self): # 从终端命令行参数读取两个数字赋值给请求里的a、b self.request.a int(sys.argv[1]) self.request.b int(sys.argv[2]) # 异步发送请求不会阻塞程序卡死 self.future self.client.call_async(self.request) def main(argsNone): rclpy.init(argsargs) node adderClient(service_adder_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(fService call failed %r % (e,)) else: # 打印最终求和结果 node.get_logger().info(fResult of add_two_ints: {response.sum}) break node.destroy_node() rclpy.shutdown() if __name__ __main__: main()关键代码拆解create_client接口类型、服务名必须和服务端一字不差否则无法配对通信wait_for_service容错机制防止客户端先启动、服务端没开导致连接失败call_async异步调用非阻塞发送请求不会卡死终端配合spin_once轮询等待结果sys.argv[1]/[2]运行客户端时在命令行追加两个数字动态传入加法参数。三配套自定义接口 AddTwoInts.srv 内容接口文件分为上下两段---分隔请求、应答plaintext# 请求段客户端发给服务端 int64 a int64 b --- # 应答段服务端返回客户端 int64 sum编译后 ROS2 自动生成 Python 可导入的类两端都能调用Request()、Response()封装解析数据。四、代码运行操作步骤编译工作空间bashcolcon build --packages-select learning_interface learning_service source install/setup.bash终端 1启动服务端bashros2 run learning_service service_adder_server终端持续阻塞等待客户端接入。终端 2启动客户端传入两个加数示例 10 20bashros2 run learning_service service_adder_client 10 20客户端日志输出最终sum30服务端日志打印收到的a10,b20一次问答通信完成。五、以下是如果使用ai 开发本程的提示词参考估记再过一段时间都 不需要以下提示词了学习的目产最好自己会代码1. 接口开发心得接口单独新建独立功能包存放示例learning_interface多个业务包可以重复调用不用重复定义.srv请求、应答变量名、数据类型两端严格统一类型不匹配会直接通信报错CMakeLists.txt、package.xml 必须开启接口编译配置否则编译不会自动生成 Python 接口类代码 import 会爆红。2. 服务端通用开发套路固定 5 步模板导入rclpy、Node 自定义服务接口新建节点类继承 Node构造函数内调用父类初始化create_service()绑定接口、服务名、回调函数编写回调函数读取 request 数据 → 业务逻辑处理 → 填充 response → return responsemain 函数初始化 rclpy、实例节点、spin()常驻运行最后销毁节点关闭环境。3. 客户端通用开发套路固定 6 步模板导入依赖导入和服务端一模一样的接口构造函数创建客户端同名服务号循环等待服务上线实例化Request()对象填充请求参数call_async()异步发送请求循环spin_once轮询 future 状态等待应答返回解析 result () 拿到应答数据做后续业务处理。4. 开发特别要注意思的地方服务名必须两端完全一致大小写、字符不能有差别必须先编译接口包再编译业务包否则找不到接口每次新开终端必须source install/setup.bash否则 ros2 run 找不到可执行文件服务是一问一答服务端 spin 持续运行客户端单次请求执行完毕自动退出回调函数必须 return response否则客户端收不到任何返回值。接下我们还做一个更复杂的示例这个示例是从摄像头中读出识别物本的坐标ROS2 进阶版图像目标识别服务端完整解析文档承接上一篇简易加法服务示例本次是订阅图像话题 OpenCV 颜色识别 服务应答坐标复合型服务端融合「话题订阅」「服务通信」两大 ROS2 核心机制下面分模块完整梳理。一、整体功能总览1. 节点双重能力这个节点不再是单纯的服务端同时兼具两个角色话题订阅者持续订阅相机发布的image_raw图像话题实时接收画面帧服务端 Server对外提供get_target_position服务客户端发起查询请求时把识别到的红色物体像素坐标 X、Y 原路返回。2. 完整业务流水线相机发布图像话题 → 当前节点订阅拿到 ROS 图像消息 → CvBridge 转 OpenCV 格式 → HSV 红色阈值筛选 轮廓检测 → 计算物体中心像素坐标并全局保存 → 客户端调用服务 → 服务回调把坐标打包成应答发回客户端。3. 用到的依赖拆解表格导入库作用rclpy / NodeROS2 节点基础框架sensor_msgs.msg.ImageROS 标准图像话题消息类型CvBridgeROS 图像消息 ↔ OpenCV Mat 格式互转核心桥梁numpy、cv2图像处理、阈值分割、轮廓检测GetObjectPosition自定义.srv 服务接口请求带布尔指令应答返回 x、y 两个整数坐标二、自定义接口约定GetObjectPosition.srv先明确接口结构服务端、客户端必须统一srv# 请求区客户端发给服务端 bool get --- # 应答区服务端返回客户端 int32 x int32 yget: True代表合法查询指令返回最新识别到的目标坐标get: False非法指令应答 x、y 强制置 0 并打印提示。三、逐段代码详细拆解1. 全局常量定义pythonlower_red np.array([0, 90, 128]) # HSV红色下限阈值 upper_red np.array([180, 255, 255])# HSV红色上限阈值固定红色 HSV 筛选区间用来在画面里抠出红色物体。2. 节点类初始化initpythonclass ImageSubscriber(Node): def __init__(self, name): super().__init__(name) # 1. 创建图像订阅者订阅/image_raw话题 self.sub self.create_subscription( Image, image_raw, self.listener_callback, 10) self.cv_bridge CvBridge() # 图像转换实例 # 2. 创建服务端绑定接口、服务名、服务回调函数 self.srv self.create_service( GetObjectPosition, get_target_position, self.object_position_callback) # 全局变量缓存最新目标物体中心坐标 self.objectX 0 self.objectY 0关键要点同一个节点内可以同时创建订阅者 服务端ROS2 允许单节点多通信实体self.objectX/Y是成员变量图像回调实时更新服务回调直接读取实现数据跨函数共享。3. 图像核心处理函数 object_detect (image)pythondef object_detect(self, image): hsv_img cv2.cvtColor(image, cv2.COLOR_BGR2HSV) # BGR转HSV mask_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 (x, y, w, h) cv2.boundingRect(cnt) cv2.drawContours(image, [cnt], -1, (0, 255, 0), 2) # 绘制轮廓 cv2.circle(image, (int(xw/2), int(yh/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)图像处理完整步骤色彩空间转换OpenCV 默认 BGRHSV 做颜色分割鲁棒性更强二值掩码只保留红色像素其余全部变黑轮廓提取 噪声过滤太小的色块直接丢弃避免噪点干扰外接矩形计算中心像素点赋值给节点成员变量持久保存。4. 话题订阅回调 listener_callback相机每发布一帧图像该函数自动触发pythondef listener_callback(self, data): self.get_logger().info(Receiving video frame) # ROS图像消息 → OpenCV图像Mat image self.cv_bridge.imgmsg_to_cv2(data, bgr8) self.object_detect(image) # 送入识别函数更新坐标核心CvBridge做格式转换ROS 的Image消息不能直接给 cv2 处理必须转换。5. 服务回调函数 object_position_callback应答客户端请求pythondef object_position_callback(self, request, response): if request.get True: # 合法请求把缓存好的坐标填入应答 response.x self.objectX response.y self.objectY self.get_logger().info(Object position\nx: %d y: %d % (response.x, response.y)) else: # 非法指令坐标清零 response.x 0 response.y 0 self.get_logger().info(Invalid command) return response服务端标准固定格式入参固定request、response修改 response 字段后必须 return 返回。6. main 入口函数pythondef main(argsNone): rclpy.init(argsargs) node ImageSubscriber(service_object_server) rclpy.spin(node) # 持续自旋同时监听话题等待服务调用 node.destroy_node() rclpy.shutdown()rclpy.spin(node)会同时驱动订阅回调、服务回调两个事件不用额外多写自旋逻辑。四、完整运行逻辑时序梳理终端 1启动相机节点持续发布/image_raw图像话题终端 2运行本图像识别服务端节点自动订阅图像话题每一帧实时做红色物体检测不断刷新objectX/Y同时服务端静默等待客户端连接终端 3运行客户端发送get: True请求服务端触发服务回调把最新识别好的像素坐标打包返回客户端客户端接收并打印 X、Y 坐标单次服务问答完成。ROS2 视觉目标坐标查询客户端完整代码解析承接上一节图像识别复合服务端代码这份是配套客户端service_object_client.py作用是主动发起请求向服务端索要识别到的目标物体像素坐标下面完整拆解、注释、梳理逻辑。一、客户端整体功能说明客户端节点启动后持续尝试连接名为get_target_position的服务连接成功后自动封装请求指令getTrue发送给服务端阻塞等待服务端返回目标物体x、y像素坐标接收应答后打印坐标程序自动结束。二、完整带注释客户端代码python#!/usr/bin/env python3 # -*- coding: utf-8 -*- 作者: 古月居(www.guyuehome.com) 说明: ROS2服务示例-请求目标识别等待目标位置应答 # 1. 导入依赖库 import rclpy from rclpy.node import Node # 导入和服务端完全一致的自定义服务接口 from learning_interface.srv import GetObjectPosition # 自定义客户端节点类继承ROS2基类Node class objectClient(Node): def __init__(self, name): super().__init__(name) # 创建客户端对象接口类型 和服务端一模一样的服务名 self.client self.create_client(GetObjectPosition, get_target_position) # 循环轮询每隔1秒检测一次服务端是否上线 while not self.client.wait_for_service(timeout_sec1.0): self.get_logger().info(service not available, waiting again...) # 实例化请求数据包对象 self.request GetObjectPosition.Request() # 封装发送请求函数 def send_request(self): # 填充请求指令True代表请求获取目标坐标 self.request.get True # 异步发送请求非阻塞 self.future self.client.call_async(self.request) # 程序入口主函数 def main(argsNone): # ROS2环境初始化 rclpy.init(argsargs) # 实例化客户端节点命名service_object_client node 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: # 正常接收坐标打印x、y像素值 node.get_logger().info( Result of object position:\n x: %d y: %d % (response.x, response.y)) # 拿到结果后跳出循环 break # 销毁节点、关闭ROS2环境 node.destroy_node() rclpy.shutdown() if __name__ __main__: main()三、逐模块逻辑拆解1. 导入部分pythonfrom learning_interface.srv import GetObjectPosition必须和服务端导入同一个自定义接口包、同一个服务类型.srv中定义的bool get请求字段、int32 x/y应答字段两端完全对齐。2. 客户端构造函数__init__create_client(接口类型, 服务名)服务名字符串get_target_position必须和服务端create_service里填写的名称一字不差大小写、符号不能改动否则无法建立通信wait_for_service(timeout_sec1.0)容错机制如果客户端先启动、视觉服务端还没运行不会直接报错退出每隔 1 秒重试连接控制台持续打印等待日志。3.send_request()请求发送函数pythonself.request.get True self.future self.client.call_async(self.request)给请求包的get赋值为True对应服务端判断合法查询call_async()异步调用不会阻塞终端通过future对象异步接收返回结果是 ROS2 推荐写法。4. 结果等待与解析主循环pythonwhile rclpy.ok(): rclpy.spin_once(node) if node.future.done():spin_once(node)单次驱动节点回调不永久阻塞future.done()标记异步请求是否完成、是否收到服务端回包future.result()安全取出应答数据里面包含response.x、response.y目标像素坐标。5. 收尾销毁流程客户端单次问答完成后主动执行destroy_node()销毁节点 →shutdown()关闭 ROS2 运行环境客户端程序直接退出符合服务一问一答的通信特性。四、整套视觉服务完整运行流程服务端 客户端 相机终端 A启动相机节点持续发布/image_raw图像话题终端 B运行视觉识别服务端bashros2 run learning_service service_object_server自动订阅图像、实时颜色轮廓检测、持续更新目标物体 XY 坐标静默等待客户端调用终端 C运行本次客户端代码bashros2 run learning_service service_object_client自动连接服务、发送查询指令、立刻打印识别出来的像素坐标客户端运行结束。五、和之前加法客户端的共性与区别共性ROS2 客户端标准通用模板固定四步结构创建客户端 → 等待服务上线 → 填充请求异步发送 → 循环自旋等待应答都用future异步回调机制、异常捕获、spin_once轮询结果最后统一销毁节点、关闭 rclpy。本次视觉客户端独有差异请求不再是两个数字 a、b而是布尔控制位get应答不再是单一求和 sum而是两个坐标 x、y服务端不是当场计算数值而是提前订阅图像持续预处理客户端只是按需读取缓存好的结果。六、开发踩坑注意点服务名严格匹配客户端create_client第二个字符串必须和服务端create_service第二个字符串完全一致空格、大小写不能出错接口包必须提前编译一定要先编译learning_interface接口包再编译业务包否则import GetObjectPosition会爆红新开终端务必 source 环境bashsource install/setup.bash不然ros2 run找不到客户端可执行文件必须先开服务端再运行客户端客户端虽然有等待重试逻辑但服务端未启动图像识别时返回的 x/y 默认是 0拿不到真实坐标。