当前位置:网站首页>ROS2——Service服务(九)
ROS2——Service服务(九)
2022-07-05 06:35:00 【我与nano】
ROS2机器人操作系统
前言
话题通信可以实现多个ROS节点之间数据的单向传输,使用这种异步通信机制,发布者无法准确知道订阅者是否收到消息,本讲我们将一起学习ROS另外一种常用的通信方法——服务,可以实现类似你问我答的同步通信效果。
一、服务详解
客户端/服务器模型
从服务的实现机制上来看,这种你问我答的形式叫做客户端/服务器模型,简称为CS模型,客户端在需要某些数据的时候,针对某个具体的服务,发送请求信息,服务器端收到请求之后,就会进行处理并反馈应答信息。
这种通信机制在生活中也很常见,比如我们经常浏览的各种网页,此时你的电脑浏览器就是客户端,通过域名或者各种操作,向网站服务器发送请求,服务器收到之后返回需要展现的页面数据。这个在之前学习爬虫的时候客户端渲染使用的就是这种方式。
同步通信
这个过程一般要求越快越好,假设服务器半天没有反应,你的浏览器一直转圈圈,那有可能是服务器宕机了,或者是网络不好,所以相比话题通信,在服务通信中,客户端可以通过接收到的应答信息,判断服务器端的状态,我们也称之为同步通信
一对多通信
比如古月居这个网站,服务器是唯一存在的,并没有多个完全一样的古月居网站,但是可以访问古月居网站的客户端是不唯一的,大家每一个人都可以看到同样的界面。所以服务通信模型中,服务器端唯一,但客户端可以不唯一。
服务接口
和话题通信类似,服务通信的核心还是要传递数据,数据变成了两个部分,一个请求的数据,比如请求苹果位置的命令,还有一个反馈的数据,比如反馈苹果坐标位置的数据,这些数据和话题消息一样,在ROS中也是要标准定义的,话题使用.msg文件定义,服务使用的是.srv文件定义
二、服务案例
1.加法求解器
大家现在对ROS服务通信应该有了基本了解,接下来我们就要开始编写代码啦。还是从一个相对简单的例程开始,也是ROS官方的一个例程,通过服务实现一个加法求解器的功能。
当我们需要计算两个加数的求和结果时,就通过客户端节点,将两个加数封装成请求数据,针对服务“add_two_ints”发送出去,提供这个服务的服务器端节点,收到请求数据后,开始进行加法计算,并将求和结果封装成应答数据,反馈给客户端,之后客户端就可以得到想要的结果啦。
启动两个终端,并运行如下节点,第一个节点是服务端,等待请求数据并提供求和功能,第二个节点是客户端,发送传入的两个加数并等待求和结果。
ros2 run learning_service service_adder_server
ros2 run learning_service service_adder_client 2 3
服务端代码实现
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接口
客户端代码
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):
rclpy.init(args=args) # ROS2 Python接口初始化
node = adderClient("service_adder_client") # 创建ROS2节点对象并进行初始化
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(
'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接口
完成代码的编写后需要设置功能包的编译选项,让系统知道Python程序的入口,打开功能包的setup.py文件,加入如下入口点的配置:
entry_points={
'console_scripts': [
'service_adder_client = learning_service.service_adder_client:main',
],
},
流程总结
对以上程序进行分析,如果我们想要实现一个客户端,流程如下:
编程接口初始化
创建节点并初始化
创建客户端对象
创建并发送请求数据
等待服务器端应答数据
销毁节点并关闭接口
对以上程序进行分析,如果我们想要实现一个服务端,流程如下:
编程接口初始化
创建节点并初始化
创建服务器端对象
通过回调函数处进行服务
向客户端反馈应答结果
销毁节点并关闭接口
2.机器视觉识别
好啦,加法求解器已经实现了,回想下刚才我们提到的视觉识别流程,当我们需要知道目标物体位置的时候,通过服务通信的机制,岂不是更加合理
运行效果
此时会有三个节点出现:
相机驱动节点,发布图像数据;
视觉识别节点,订阅图像数据,并且集成了一个服务器端对象,随时准备提供目标位置;
客户端节点,我们可以认为是一个机器人目标跟踪的节点,当需要根据目标运动时,就发送一次请求,然后拿到一个当前的目标位置。
启动三个终端,分别运行上述三个节点:
ros2 run usb_cam usb_cam_node_exe
ros2 run learning_service service_object_server
ros2 run learning_service service_object_client
客户端代码解析
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):
rclpy.init(args=args) # ROS2 Python接口初始化
node = objectClient("service_object_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 object position:\n x: %d y: %d' %
(response.x, response.y))
break
node.destroy_node() # 销毁节点对象
rclpy.shutdown() # 关闭ROS2 Python接口
服务端代码解析
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 # 自定义的服务接口
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() # 创建一个图像转换对象,用于OpenCV图像与ROS的图像消息的互相转换
self.srv = self.create_service(GetObjectPosition, # 创建服务器对象(接口类型、服务名、服务器回调函数)
'get_target_position',
self.object_position_callback)
self.objectX = 0
self.objectY = 0
def 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) = cv2.boundingRect(cnt) # 得到苹果所在轮廓的左上角xy像素坐标及轮廓范围的宽和高
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) # 使用OpenCV显示处理后的图像效果
cv2.waitKey(50)
def listener_callback(self, data):
self.get_logger().info('Receiving video frame') # 输出日志信息,提示已进入回调函数
image = self.cv_bridge.imgmsg_to_cv2(data, 'bgr8') # 将ROS的图像消息转化成OpenCV图像
self.object_detect(image) # 苹果检测
def object_position_callback(self, request, response): # 创建回调函数,执行收到请求后对数据的处理
if request.get == True:
response.x = self.objectX # 目标物体的XY坐标
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
def main(args=None): # ROS2节点主入口main函数
rclpy.init(args=args) # ROS2 Python接口初始化
node = ImageSubscriber("service_object_server") # 创建ROS2节点对象并进行初始化
rclpy.spin(node) # 循环等待ROS2退出
node.destroy_node() # 销毁节点对象
rclpy.shutdown() # 关闭ROS2 Python接口
三、服务命令行操作
$ ros2 service list # 查看服务列表
$ ros2 service type <service_name> # 查看服务数据类型
$ ros2 service call <service_name> <service_type> <service_data> # 发送服务请求
总结
话题和服务是ROS中最为常用的两种数据通信方法,前者适合传感器、控制指令等周期性、单向传输的数据,后者适合一问一答,同步性要求更高的数据,比如获取机器视觉识别到的目标位置。
在机器人开发过程中,类似的通信应用比比皆是,ROS针对绝大部分通用场景,都设计了标准的话题和服务数据类型,比如图像数据、雷达数据、里程计数据等等,不过机器人软硬件繁杂,很多时候这些标准定义也无法满足我们的需求,这个时候,我们就要自定义通信接口了。
边栏推荐
- Genesis builds a new generation of credit system
- Cloud native related technology learning
- 6-2 sequence table operation set
- Database mysql all
- Game theory acwing 891 Nim games
- Redis-02.Redis命令
- H5 module suspension drag effect
- Get class files and attributes by reflection
- [wustctf2020] plain_ WP
- MySQL (UDF authorization)
猜你喜欢
随机推荐
Redis-02. Redis command
Skywalking all
Sre core system understanding
Configuration method and configuration file of SolidWorks GB profile library
The differences and connections among cookies, sessions, JWT, and tokens
Orin 安装CUDA环境
How to answer when you encounter a jet on CSDN?
Time is fast, please do more meaningful things
Record of problems in ollvm compilation
H5 module suspension drag effect
A brief introduction to heading/pitch/roll and omega/phi/kappa
Qt项目中的日志库log4qt使用
数据库Mysql全部
微信小程序路由再次跳转不触发onload
mysql设置触发器问题
Sum of two numbers, the numbers in the array are converted to decimal, added, and output inversely
Get class files and attributes by reflection
UIO driven framework
Orin installs CUDA environment
Modnet matting model reproduction