[ROS](11)ROS通信 —— 服务(Service)通信编程之srv(C++)(Python)

  文章只是个人学习过程中学习笔记,主要参考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)通信

目录

  • 1、概述
  • 2、srv结构和类型
  • 3、srv命令rossrv
  • 4、srv的用法
    • 4.1 创建srv
    • 4.2 配置CMakeLists.txt
    • 4.3 配置package.xml
    • 4.4 编译package
  • 5、srv命令实操
    • 5.1 rossrv list
    • 5.2 rossrv packages
    • 5.3 rossrv package
    • 5.4 rossrv show / rossrv info
    • 5.5 rossrv md5
  • 6、实操编程 -- 服务通信使用自定义服务
    • 6.1 编写程序(C++)
      • 6.1.1 服务端程序(add_two_ints_server.cpp)
      • 6.1.2 客户端程序(add_two_ints_client.cpp)
      • 6.1.3 配置CMakeLists.txt
      • 6.1.4 编译、运行
    • 6.2 编写程序(python)
      • 6.2.1 服务端程序(add_two_ints_server.py)
      • 6.2.2 客户端程序(add_two_ints_client.py)
      • 6.2.3 配置CMakeLists.txt
      • 6.1.4 编译、运行

1、概述

  srv(服务)一个srv文件描述一个服务。它由两部分组成:请求(request)和响应(response)。
  srv文件则存放在srv目录下。

2、srv结构和类型

  srv4 文件和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

3、srv命令rossrv

  rossrv命令行工具显示有关 ROS 服务的信息。它的用法与rosmsg完全相同:

命令 功能
rossrv show 显示服务的描述(详细信息)
rossrv info rossrv show的别名,功能一样
rossrv list 列出所有服务
rossrv md5 显示md5加密的服务
rossrv package 列出某个软件包(package)的服务
rossrv packages 列出包含服务的软件包(packages)

Tips:使用rossrv -h 帮助选项获取更详细的用法。

4、srv的用法

4.1 创建srv

  还是在原来创建的软件包 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

4.2 配置CMakeLists.txt

  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 ...
  ...)

4.3 配置package.xml

  添加编译依赖与执行依赖。

  <build_depend>message_generationbuild_depend>
  <exec_depend>message_runtimeexec_depend>

Node :在构建时,其实只需要message_generation,而在运行时,我们只需要message_runtime

4.4 编译package

  已经创建了一些新消息,所以需要重新make一下软件包:

cd
cd catkin_ws
catkin_make

[ROS](11)ROS通信 —— 服务(Service)通信编程之srv(C++)(Python)_第1张图片

图4-1 使用rqt_service_caller工具模拟服务请求

  
  srv目录中的任何.srv文件都将生成所有支持语言的代码。

  • C++消息的头文件将生成在~/catkin_ws/devel/include/beginner_tutorials/

[ROS](11)ROS通信 —— 服务(Service)通信编程之srv(C++)(Python)_第2张图片

  • python脚本将创建在~/catkin_ws/devel/lib/python2.7/dist-packages/beginner_tutorials/srv

[ROS](11)ROS通信 —— 服务(Service)通信编程之srv(C++)(Python)_第3张图片

5、srv命令实操

5.1 rossrv list

  rossrv list 列出ROS中的所有服务(srv)。

5.2 rossrv packages

  rossrv packages [options] 列出包含服务的软件包。

# 列出包含服务的软件包,每行显示一个软件包名
rossrv packages
# 在一行中列出包含服务的软件包
rossrv packages -s 

5.3 rossrv package

  rossrv package [package-name] 列出某个软件包(package)的服务。

[ROS](11)ROS通信 —— 服务(Service)通信编程之srv(C++)(Python)_第4张图片

5.4 rossrv show / rossrv info

  rossrv show [service type] 显示服务的详细信息。
[ROS](11)ROS通信 —— 服务(Service)通信编程之srv(C++)(Python)_第5张图片

5.5 rossrv md5

  rossrv md5[service type] 显示md5加密的服务。如果编译的版本不匹配,这也会发出警告。

Node:md5 命令仅供专家用户使用。(??)

6、实操编程 – 服务通信使用自定义服务

  功能:基于服务通信,客户端提交两个整数至服务端,服务端求和并响应结果到客户端。
  实现
    1. 创建服务端(add_two_ints_server)节点,该节点将接收两个整数,并返回它们的和。
    2. 创建客户端(add_two_ints_client)节点,该节点将发送两个整数,并等待结果。
    3. 创建自定义服务(AddTwoInts.srv),(见4.1小节 创建srv
    4. 服务请求(client–>server)
    5. 服务响应(server–>client)

add_two_ints_client
add_two_ints_server

6.1 编写程序(C++)

  在beginner_tutorials软件包的src目录下创建发布者和订阅者源文件:

roscd beginner_tutorials
cd src
touch add_two_ints_server.cpp add_two_ints_client.cpp

6.1.1 服务端程序(add_two_ints_server.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)类型,并返回一个布尔值。

6.1.2 客户端程序(add_two_ints_client.cpp)

#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的值将不可用。

6.1.3 配置CMakeLists.txt

  只需将这几行添加到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)

6.1.4 编译、运行

  编译后,在终端中执行过程如下所示。
[ROS](11)ROS通信 —— 服务(Service)通信编程之srv(C++)(Python)_第6张图片

6.2 编写程序(python)

  在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

6.2.1 服务端程序(add_two_ints_server.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()

6.2.2 客户端程序(add_two_ints_client.py)

#!/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)

6.2.3 配置CMakeLists.txt

# 安装python可执行脚本
catkin_install_python(PROGRAMS
  scripts/add_two_ints_server.py 
  scripts/add_two_ints_client.py
  ...
  DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION}
  )

6.1.4 编译、运行

  编译后,在终端中执行过程如下所示。
[ROS](11)ROS通信 —— 服务(Service)通信编程之srv(C++)(Python)_第7张图片


  1. ROS.otg. ROS教程[EB/OL]. 2020-12-22[2022-7-5].
    http://wiki.ros.org/cn/ROS/Tutorials. ↩︎

  2. .ROS.org. 编写简单的服务和客户端(C++)[EB/OL]. 2020-12-28[2022-07-30]. https://wiki.ros.org/cn/ROS/Tutorials/WritingServiceClient%28c%2B%2B%29. ↩︎

  3. .ROS.org. 编写简单的服务和客户端(Python)[EB/OL]. 2020-12-28[2022-07-30]. https://wiki.ros.org/cn/ROS/Tutorials/WritingServiceClient%28python%29. ↩︎

  4. ROS.org. srv[EB/OL]. 2017-01-07[2022-07-30]. https://wiki.ros.org/srv. ↩︎

你可能感兴趣的:(ROS,笔记,ROS,service,srv,c++,python)