文章只是个人学习过程中学习笔记,主要参考ROS教程1 2 3。
[ROS](01)创建ROS工作空间
[ROS](02)创建&编译ROS软件包Package
[ROS](03)CMakeLists.txt详解
[ROS](04)package.xml详解
[ROS](09)ROS通信 —— 话题(Topic)通信编程之msg(C++)(Python)
[ROS](10)ROS通信 —— 服务(Service)通信
srv
(服务)一个srv文件描述一个服务。它由两部分组成:请求(request)和响应(response)。
srv
文件则存放在srv目录下。
srv
4 文件和msg
文件一样,只是它们包含两个部分:请求
和响应
。这两部分用一条 ---
线隔开。任何两个用---
连接在一起的.msg
文件都是合法的服务描述。下面是一个srv文件的示例:
int64 a
int64 b
---
int64 sum
在上面的例子中,a和b是请求, sum是响应。
该srv
文件见rospy_tutorials
软件包(位于/opt/ros/melodic/share
)的srv目录下。
msg文件可参考: https://blog.csdn.net/CynalFly/article/details/126072684
rossrv
命令行工具显示有关 ROS 服务的信息。它的用法与rosmsg
完全相同:
命令 | 功能 |
---|---|
rossrv show |
显示服务的描述(详细信息) |
rossrv info |
rossrv show 的别名,功能一样 |
rossrv list |
列出所有服务 |
rossrv md5 |
显示md5加密的服务 |
rossrv package |
列出某个软件包(package)的服务 |
rossrv packages |
列出包含服务的软件包(packages) |
Tips
:使用rossrv
帮助选项获取更详细的用法。-h
还是在原来创建的软件包 beginner_tutorials
中定义一个新的服务。
# 切换到软件包的目录路径
roscd beginner_tutorials
# 创建srv文件夹目录
mkdir srv
# 创建名为AddTwoInts.srv的文件
touch AddTwoInts.srv
Tips
:如果想使用现有的srv文件,而不是手动创建新的srv。roscp是一个实用的命令行工具,用于将文件从一个包复制到另一个包。
用法:roscp [package_name] [file_to_copy_path] [copy_path]
举例:roscp
rospy_tutorials
AddTwoInts.srv
srv/AddTwoInts.srv
1. 为已经存在里面的find_package调用添加message_generation
依赖项(message_generation对msg和srv都适用),这样就能生成消息了。(添加 message_generation
依赖项,必须有 std_msgs
。)
find_package(catkin REQUIRED COMPONENTS
roscpp
rospy
std_msgs
message_generation
)
2. 添加自定义的消息文件。将你srv
文件夹中自定义的*.srv
文件添加在这里。
add_service_files(
FILES
AddTwoInts.srv
)
3. 用于生成所有定义的message文件,需要添加本文件需要依赖的packages。其实就是告诉编译器,编译 *.srv
文件时,需要依赖的库或package。
generate_messages(
DEPENDENCIES
std_msgs
)
4. 导出消息的运行时依赖关系。
catkin_package(
...
CATKIN_DEPENDS message_runtime ...
...)
添加编译依赖与执行依赖。
<build_depend>message_generationbuild_depend>
<exec_depend>message_runtimeexec_depend>
Node
:在构建时,其实只需要message_generation
,而在运行时,我们只需要message_runtime
。
已经创建了一些新消息,所以需要重新make一下软件包:
cd
cd catkin_ws
catkin_make
srv目录中的任何.srv文件都将生成所有支持语言的代码。
~/catkin_ws/devel/include/beginner_tutorials/
~/catkin_ws/devel/lib/python2.7/dist-packages/beginner_tutorials/srv
rossrv list
列出ROS中的所有服务(srv)。
rossrv packages [options]
列出包含服务的软件包。
# 列出包含服务的软件包,每行显示一个软件包名
rossrv packages
# 在一行中列出包含服务的软件包
rossrv packages -s
rossrv package [package-name]
列出某个软件包(package)的服务。
rossrv show [service type]
显示服务的详细信息。
rossrv md5[service type]
显示md5加密的服务。如果编译的版本不匹配,这也会发出警告。
Node
:md5 命令仅供专家用户使用。(??)
功能:基于服务通信,客户端提交两个整数至服务端,服务端求和并响应结果到客户端。
实现:
1. 创建服务端(add_two_ints_server
)节点,该节点将接收两个整数,并返回它们的和。
2. 创建客户端(add_two_ints_client
)节点,该节点将发送两个整数,并等待结果。
3. 创建自定义服务(AddTwoInts.srv
),(见4.1小节 创建srv
)
4. 服务请求(client–>server)
5. 服务响应(server–>client)
在beginner_tutorials
软件包的src目录下创建发布者和订阅者源文件:
roscd beginner_tutorials
cd src
touch add_two_ints_server.cpp add_two_ints_client.cpp
#include "ros/ros.h"
#include "beginner_tutorials/AddTwoInts.h"
/* 回调函数 */
bool addCallback(beginner_tutorials::AddTwoInts::Request &req,
beginner_tutorials::AddTwoInts::Response &res)
{
ROS_INFO("服务器收到的请求: x=%ld, y=%ld", req.a, req.b);
res.sum = req.a + req.b;
ROS_INFO("服务器发送的响应: sum=%ld",res.sum);
return true;
}
int main(int argc, char **argv)
{
setlocale(LC_ALL,"");
/* 初始化ROS节点 */
ros::init(argc,argv,"add_two_ints_server");
/* 为这个节点创建句柄 */
ros::NodeHandle nh;
/* 创建服务服务端 */
ros::ServiceServer server = nh.advertiseService("add_two_ints",addCallback);
ROS_INFO("服务器Ready.");
/* 阻塞,等待回调函数处理 */
ros::spin();
return 0;
}
回调函数
addCallback()
提供了AddTwoInts服务,它接受srv文件中定义的请求(request)和响应(response)类型,并返回一个布尔值。
#include "ros/ros.h"
#include "beginner_tutorials/AddTwoInts.h"
int main(int argc, char **argv)
{
setlocale(LC_ALL,"");
/* 初始化ROS节点 */
ros::init(argc,argv,"add_two_ints_client");
if(argc != 3)
{
ROS_INFO("用法:输入两个整数 X Y");
return 1;
}
/* 为这个节点创建句柄 */
ros::NodeHandle nh;
/* 为add_two_ints服务创建一个客户端,连接名为add_two_ints的service */
ros::ServiceClient client = nh.serviceClient<beginner_tutorials::AddTwoInts>("add_two_ints");
/* 初始化beginner_tutorials::AddTwoInts的请求数据 */
beginner_tutorials::AddTwoInts srv;
srv.request.a = atoll(argv[1]);
srv.request.b = atoll(argv[2]);
if(client.call(srv))
{
ROS_INFO("请求服务成功, 求和结果sum:%ld", srv.response.sum);
}
else
{
ROS_ERROR("请求服务失败...");
}
return 0;
}
if(client.call(srv))
:此处实际上调用了服务。由于服务调用被阻塞,它将在调用完成后返回。如果服务调用成功,call()将返回true,并且srv.response中的值将是有效的。如果调用不成功,则call()将返回false且srv.response的值将不可用。
只需将这几行添加到CMakeLists.txt文件的底部:
add_executable(add_two_ints_server src/add_two_ints_server.cpp)
target_link_libraries(add_two_ints_server ${catkin_LIBRARIES})
add_dependencies(add_two_ints_server ${PROJECT_NAME}_gencpp)
add_executable(add_two_ints_client src/add_two_ints_client.cpp)
target_link_libraries(add_two_ints_client ${catkin_LIBRARIES})
add_dependencies(add_two_ints_client ${PROJECT_NAME}_gencpp)
在beginner_tutorials
软件包的scripts
目录下创建服务端和客户端源文件:
roscd beginner_tutorials
cd scripts
touch add_two_ints_server.py add_two_ints_client.py
chmod +x add_two_ints_server.py add_two_ints_client.py
#!/usr/bin/env python
# encoding: utf-8
import rospy
from beginner_tutorials.srv import AddTwoInts,AddTwoIntsResponse
def addCallback(req):
rospy.loginfo("服务器收到的请求: x=%ld, y=%ld", req.a, req.b)
sum = req.a + req.b
rospy.loginfo("服务器发送的响应: sum=%ld",sum)
return AddTwoIntsResponse(sum)
def add_two_ints_server():
# 初始化ROS节点
rospy.init_node("add_two_ints_server")
# 创建一个名为add_two_ints的server,注册回调函数addCallback
# Request请求信息将作为参数传递给addCallback进行处理
server = rospy.Service("add_two_ints",AddTwoInts, addCallback)
rospy.loginfo("服务器Ready.")
rospy.spin()
if __name__ == '__main__':
add_two_ints_server()
#!/usr/bin/env python
# encoding: utf-8
import rospy
import sys
from beginner_tutorials.srv import *
def add_two_ints_client(x,y):
# 阻塞,等待add_two_ints服务可用
rospy.wait_for_service("add_two_ints")
try:
# 创建sevice client
# 声明了服务类型 AddTwoInts ,它生成AddTwoIntsRequest对象
# 返回值是一个AddTwoIntsResponse对象
add_two_ints = rospy.ServiceProxy('add_two_ints',AddTwoInts)
# 参数传递的两种方式
# (1) 显式:创建 Request 实例传递
# req = AddTwoIntsRequest(x, y)
# resp = add_two_ints(req)
# (2) 隐式
resp = add_two_ints(x,y)
rospy.loginfo("请求服务成功, 求和结果sum:%ld", resp.sum)
# 当请求服务时发生错误,就会抛出该异常内容
except rospy.ServiceException as e:
rospy.loginfo("请求服务失败: %s"%e)
def usage():
return "%s [x y]"%sys.argv[0]
if __name__ == '__main__':
# 初始化ROS节点
# 对于客户端来说可以不是节点,不需要调用init_node()
rospy.init_node("add_two_ints_client")
if len(sys.argv) == 3:
# sys.argv[]是字符串,需要强制转化成整型
x = int(sys.argv[1])
y = int(sys.argv[2])
else:
# 如果没有初始化节点,日志信息是看不到的,可使用print
# print(usage())
rospy.logerr(usage())
sys.exit(1)
add_two_ints_client(x,y)
# 安装python可执行脚本
catkin_install_python(PROGRAMS
scripts/add_two_ints_server.py
scripts/add_two_ints_client.py
...
DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION}
)
ROS.otg. ROS教程[EB/OL]. 2020-12-22[2022-7-5].
http://wiki.ros.org/cn/ROS/Tutorials. ↩︎
.ROS.org. 编写简单的服务和客户端(C++)[EB/OL]. 2020-12-28[2022-07-30]. https://wiki.ros.org/cn/ROS/Tutorials/WritingServiceClient%28c%2B%2B%29. ↩︎
.ROS.org. 编写简单的服务和客户端(Python)[EB/OL]. 2020-12-28[2022-07-30]. https://wiki.ros.org/cn/ROS/Tutorials/WritingServiceClient%28python%29. ↩︎
ROS.org. srv[EB/OL]. 2017-01-07[2022-07-30]. https://wiki.ros.org/srv. ↩︎