文章目录
ROS机器人入门第六课:服务通信自定义srv
一、服务通信简介
服务通信也是ROS中一种极其常用的通信模式,服务通信是基于请求响应模式的,是一种应答机制。也即: 一个节点A向另一个节点B发送请求,B接收处理请求并产生响应结果返回给A。比如如下场景:
机器人巡逻过程中,控制系统分析传感器数据发现可疑物体或人… 此时需要拍摄照片并留存。
在上述场景中,就使用到了服务通信。
- 一个节点需要向相机节点发送拍照请求,相机节点处理请求,并返回处理结果
与上述应用类似的,服务通信更适用于对时时性有要求、具有一定逻辑处理的应用场景。
- 概念
以请求响应的方式实现不同节点之间数据交互的通信模式。
- 作用
用于偶然的、对时时性有要求、有一定逻辑处理需求的数据传输场景。
- 需求
服务通信中,客户端提交两个整数至服务端,服务端求和并响应结果到客户端,请创建服务器与客户端通信的数据载体。
二、流程
srv 文件内的可用数据类型与 msg 文件一致,且定义 srv 实现流程与自定义 msg 实现流程类似:
-
按照固定格式创建srv文件
-
编辑配置文件
-
编译生成中间文件
(一)定义srv文件
服务通信中,数据分成两部分,请求与响应,在 srv 文件中请求和响应使用—分割,具体实现如下:
功能包下新建 srv 目录,添加 xxx.srv 文件,内容:
# 客户端请求时发送的两个数字
int32 num1
int32 num2
---
# 服务器响应发送的数据
int32 sum
(三)编辑配置文件
- package.xml中添加编译依赖与执行依赖
<build_depend>message_generation</build_depend>
<exec_depend>message_runtime</exec_depend>
<!--
exce_depend 以前对应的是 run_depend 现在非法
-->
2. CMakeLists.txt编辑 srv 相关配置
find_package(catkin REQUIRED COMPONENTS
roscpp
rospy
std_msgs
message_generation
)
# 需要加入 message_generation,必须有 std_msgs
add_service_files(
FILES
AddInts.srv
)
generate_messages(
DEPENDENCIES
std_msgs
)
注意: 官网没有在 catkin_package 中配置 message_runtime,经测试配置也可以
- 编译
Python 需要调用的中间文件(…/工作空间/devel/lib/python3/dist-packages/包名/srv)
后续调用相关 srv 时,是从这些中间文件调用的
三、服务通信自定义srv调用
需求:
编写服务通信,客户端提交两个整数至服务端,服务端求和并响应结果到客户端。
分析:
在模型实现中,ROS master 不需要实现,而连接的建立也已经被封装了,需要关注的关键点有三个:
- 服务端
- 客户端
- 数据
(一)自定义srv调用流程
- 编写服务端实现;
- 编写客户端实现;
- 为python文件添加可执行权限;
- 编辑配置文件;
- 编译并执行。
0.vscode配置
需要像之前自定义 msg 实现一样配置settings.json 文件,如果以前已经配置且没有变更工作空间,可以忽略,如果需要配置,配置方式与之前相同:
{
"python.autoComplete.extraPaths": [
"/opt/ros/noetic/lib/python3/dist-packages",
]
}
1.客户端
以下是对您提供的ROS Python客户端代码的详细注释:
#! /usr/bin/env python
# - coding: utf-8 -*-
#1.导包
import rospy # 导入rospy,这是Python版本的ROS客户端库,提供ROS节点的初始化、发布、订阅等功能。
from srv.srv import addnum, addnumRequest, addnumResponse # 从srv模块导入自定义的服务消息类型。
import sys # 导入sys模块,这个模块提供了一系列有关Python运行环境的变量和函数。
if __name__ == "__main__":
# 检查命令行参数,确保提供了正确数量的参数。
if len(sys.argv) != 3:
rospy.logerr("请正确提交参数") # 如果参数数量不对,记录一条错误日志。
sys.exit(1) # 退出程序。
# 2.初始化 ROS 节点
rospy.init_node("AddInts_Client_p") # 初始化一个名为"AddInts_Client_p"的ROS节点。
# 3.创建服务客户端
client = rospy.ServiceProxy("AddInts", addnum) # 创建一个服务客户端,用于调用名为"AddInts"的服务。服务类型为addnum。
# 请求前,等待服务已经就绪。
# 方式1: 使用rospy.wait_for_service函数等待服务
# rospy.wait_for_service("addnum")
# 方式2: 使用ServiceProxy实例的wait_for_service方法等待服务
client.wait_for_service()
# 4.发送请求,接收并处理响应
# 方式1: 直接通过服务客户端传递参数调用服务。
# resp = client(3,4)
# 方式2: 创建特定的服务请求对象并调用服务。
# resp = client(addnumRequest(1,5))
# 方式3: 创建一个请求对象,设置参数。
req = addnumRequest()
# req.num1 = 100 # 设置请求的第一个数字(未使用优化的硬编码值)。
# req.num2 = 200 # 设置请求的第二个数字(未使用优化的硬编码值)。
# 优化: 使用命令行参数设置请求对象的属性。
req.num1 = int(sys.argv[1]) # 将命令行第一个参数转换为整数并赋给请求的num1。
req.num2 = int(sys.argv[2]) # 将命令行第二个参数转换为整数并赋给请求的num2。
# 调用服务并等待响应。
resp = client.call(req)
# 输出响应结果。
rospy.loginfo("响应结果:%d", resp.sum) # 使用rospy.loginfo记录服务调用的结果。
代码中的ROS函数功能如下:
rospy.init_node('node_name')
:初始化ROS节点,'node_name’是节点的名称。rospy.logerr(msg)
:记录错误消息。rospy.loginfo(msg, *args)
:记录信息消息,可传递参数。rospy.ServiceProxy('service', service_type)
:定义一个服务客户端,'service’是服务名,'service_type’是服务的类型。ServiceProxy.wait_for_service()
:服务代理实例将等待服务变为可用状态。ServiceProxy.call(srv)
:调用服务并发送srv
请求对象,等待并返回响应。
在ROS中,服务是双向通信,客户端发送请求给服务端,服务端处理请求并返回响应。这种通信方式通常用于不连续的、一次性的数据交换,例如在本例中的整数相加服务。
代码中需要修改的地方
from srv.srv import addnum, addnumRequest, addnumResponse
:from 包名.srv import 自定义srv文件名, 自定义srv文件名Request , 自定义srv文件名Responseclient = rospy.ServiceProxy("AddInts", addnum)
:client = rospy.ServiceProxy(“AddInts”, 自定义srv文件名Request)rospy.wait_for_service("addnum")
:rospy.wait_for_service(“自定义srv文件名”)
注意:sys.argv[0]
是python文件名
当客户端先于服务端启动,会抛出异常
如果要求客户端先于服务端启动,不要抛出异常而是挂起,等待服务启动后,再次发送请求
实现:
ROS中内置了相关函数,这些函数可以判断服务器的状态,如果服务没有启动,那么就让客户端挂起
- 方式1: 使用rospy.wait_for_service函数等待服务
rospy.wait_for_service("addnum")
- 方式2: 使用ServiceProxy实例的wait_for_service方法等待服务
client.wait_for_service()
2.服务端
以下是您提供的ROS服务端Python脚本的注释详情:
#! /usr/bin/env python
# - coding: utf-8 -*-
# 1. 导包
import rospy # 导入rospy模块,提供ROS节点的基本功能。
from srv.srv import addnum, addnumRequest, addnumResponse # 从srv包导入自定义服务消息类型。
# 定义服务处理函数
def doReq(req):
# 解析请求数据
sum = req.num1 + req.num2 # 将请求对象中的num1和num2相加。
# 记录信息,显示请求的数据。
rospy.loginfo("提交的数据:num1 = %d, num2 = %d, sum = %d", req.num1, req.num2, sum)
# 创建响应对象,设置结果,并返回该响应对象。
resp = addnumResponse(sum) # 使用相加后的和初始化addnumResponse对象。
return resp # 返回响应对象。
if __name__ == "__main__":
# 2. 初始化 ROS 节点
rospy.init_node("addints_server_p") # 初始化一个名为"addints_server_p"的ROS节点。
# 3. 创建服务对象
server = rospy.Service("AddInts", addnum, doReq) # 创建一个名为"AddInts"的服务,指定服务消息类型为addnum,处理函数为doReq。
# 4. 回调函数处理请求并产生响应
# 这部分由ROS内部管理,当有服务请求到来时,doReq函数将被调用。
# 5. spin 函数
rospy.spin() # 进入循环,等待服务请求,不会退出直到节点被显式关闭。
ROS函数功能如下:
rospy.init_node('node_name')
:初始化一个ROS节点,'node_name’是节点的名称。这是ROS程序的起始步骤,注册节点到ROS master。rospy.loginfo(msg, *args)
:记录日志类型为info的消息,功能类似于打印输出信息,但它同时会发送到ROS系统的日志记录系统中。rospy.Service('service_name', service_type, handler)
:创建一个新的服务。'service_name’是服务的名称,'service_type’是服务的类型,'handler’是当有请求到来时处理该请求的函数。rospy.spin()
:使你的节点进入阻塞等待状态,等待并处理回调函数,直到节点被关闭。它通常被用于保持你的程序运行并等待回调函数的触发。
在ROS中服务是一种双向通信方式,当客户端调用服务并发送请求时,服务端会处理这个请求并返回一个响应。您的代码中的doReq
函数就是服务端处理请求的回调函数。
3.设置权限
终端下进入 scripts 执行:chmod +x *.py
4.配置 CMakeLists.txt
CMakeLists.txt
catkin_install_python(PROGRAMS
scripts/AddInts_Server_p.py
scripts/AddInts_Client_p.py
DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION}
)
5.执行
流程:
-
需要先启动服务:rosrun 包名 服务
-
然后再调用客户端 :rosrun 包名 客户端 参数1 参数2
结果:
会根据提交的数据响应相加后的结果。