• [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、概述

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

    2、srv结构和类型

      srv4 文件和msg文件一样,只是它们包含两个部分:请求响应。这两部分用一条 --- 线隔开。任何两个用---连接在一起的.msg文件都是合法的服务描述。下面是一个srv文件的示例:

    int64 a
    int64 b
    ---
    int64 sum
    
    • 1
    • 2
    • 3
    • 4

    在上面的例子中,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 inforossrv 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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    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
    )
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

      2. 添加自定义的消息文件。将你srv文件夹中自定义的*.srv文件添加在这里。

    add_service_files(
      FILES
      AddTwoInts.srv
    )
    
    • 1
    • 2
    • 3
    • 4

      3. 用于生成所有定义的message文件,需要添加本文件需要依赖的packages。其实就是告诉编译器,编译 *.srv 文件时,需要依赖的库或package。

    generate_messages(
      DEPENDENCIES
      std_msgs
    )
    
    • 1
    • 2
    • 3
    • 4

      4. 导出消息的运行时依赖关系。

    catkin_package(
      ...
      CATKIN_DEPENDS message_runtime ...
      ...)
    
    • 1
    • 2
    • 3
    • 4

    4.3 配置package.xml

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

      <build_depend>message_generationbuild_depend>
      <exec_depend>message_runtimeexec_depend>
    
    • 1
    • 2

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

    4.4 编译package

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

    cd
    cd catkin_ws
    catkin_make
    
    • 1
    • 2
    • 3

    srv bulid

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

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

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

    srv生成C++头文件的位置.png

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

    srv生成python头文件的位置.png

    5、srv命令实操

    5.1 rossrv list

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

    5.2 rossrv packages

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

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

    5.3 rossrv package

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

    rossrv package

    5.4 rossrv show / rossrv info

      rossrv show [service type] 显示服务的详细信息。
    rossrv show

    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
    
    • 1
    • 2
    • 3

    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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

      回调函数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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37

    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)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    6.1.4 编译、运行

      编译后,在终端中执行过程如下所示。
    service run server client C++

    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
    
    • 1
    • 2
    • 3
    • 4

    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()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    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)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49

    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}
      )
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    6.1.4 编译、运行

      编译后,在终端中执行过程如下所示。
    service run server and client python


    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. ↩︎

  • 相关阅读:
    微信小程序 | IM交友聊天功能大汇总
    vue2的基础知识巩固
    [hive]中的字段的数据类型有哪些
    语音噪声---学习笔记
    rhel7.0解决yum无法使用(system is not registered to Red Hat Subscription Management)
    在ios系统上实现更改IP地址
    RetroArch 接入两个同款手柄只能识别到一个导致无法双打的问题
    Vscode连接远程服务器(一套配置成功)
    发电机负载测试:专业指南
    Linux:linux getopt_long()函数(命令行解析)(getopt、getopt_long_only)(短选项 -,长选项 --)
  • 原文地址:https://blog.csdn.net/CynalFly/article/details/126162297