ROS基础(二):ros通讯之服务(service)机制

上一章内容链接:

ROS基础(一):ROS通讯之话题(topic)通讯

一、概念

service服务通讯机制是一种双向同步数据传输模式。基于客户端/服务器模型,两部分通信数据类型:一个用于请求,一个用于应答,类似web服务器。
ROS中只允许一个节点提供指定命名的服务。

与topic通讯机制的区别:

比较列表 话题topic 服务service
同步性 异步 同步
通信模型 发布+订阅 客户端+服务器端
反馈机制
缓冲区
节点关系 多对多 一(server)对多
传输数据格式 *.msg *.srv
适合场景 数据传输 逻辑处理

二、实例

1. 小乌龟例程中的service

首先启动第一个小乌龟:

rosrun turtlesim turtlesim_node

然后查看能够调用的所有service:
在这里插入图片描述
调用其中名为/spawn的服务:
在这里插入图片描述

如上图红框部分所示,服务器返回指定生成的乌龟名字。同时,在小乌龟仿真中在指定位置出现第二只名为turtle7的小乌龟,如下图所示。这乌龟可以的,还在听音乐。

在这里插入图片描述
最后,我们查看该service的具体数据格式:

在这里插入图片描述
与我们的预期一致,—上方是client请求发出的内容,其中包括指定新生成乌龟的位置、朝向、名字,—下方是server返回的内容,string格式的乌龟名字,说明创建成功。

2. 自定义service

与topic话题中需要定义的message文件相似,当我们想要自定义某种服务时,需要提供对应的srv文件来对两个交互节点中具体传输的数据格式进行约束。

这里我们创建一个乘法运算案例,客户端提供两个整数a、b,服务器端计算完成后返回乘法结果到客户端。
将该srv文件命名为multinum.srv,该文件中的内容如下:

float32 a
float32 b
---
float64 c

将该文件放在名为srv的文件夹下,将文件夹放在功能包目录下。最终的功能包目录如下所示:

在这里插入图片描述

与定义message一样,我们同样要修改对应的package.xml文件和CmakeLists.txt文件的对应部分:

package.xml文件:

  <build_depend>message_generation</build_depend>
  <exec_depend>message_runtime</exec_depend>

CmakeLists.txt文件:

第一块:修改find_package部分,确保编译时找到对应文件


find_package(catkin REQUIRED COMPONENTS
  roscpp
  rospy
  std_msgs
  message_generation
)

第二块:设置srv文件

 add_service_files(
   FILES
   multinum.srv
 )

 generate_messages(
   DEPENDENCIES
   std_msgs
 )

第三块:catkin依赖部分

如果我们编写的ros程序不打算给别人使用,这块就无所谓。

catkin_package(
...
  CATKIN_DEPENDS roscpp rospy std_msgs message_runtime
...
)

最后,在工作空间编译,通过相关命令测试是否已经安装成功:
在这里插入图片描述如上图所示,我们的service服务查询得到,没问题。

3. 创建服务器节点与客户端节点(c++)

我们利用第二小节中自定义的service格式,来设计两个node进行service通讯。主要功能是client随机生成两个数字传递给service,然后service计算完成之后,将结果回传给client。具体的代码如下所示

server节点编写

#include <ros/ros.h>
#include <learn_service/multinum.h>

bool multi_callback(learn_service::multinum::Request &req, learn_service::multinum::Response &res){
    
    
    res.c = req.a * req.b;
    ROS_INFO("computing result is %f", res.c);
    return true;
}

int main(int argc, char **argv){
    
    
    ros::init(argc, argv, "multi_server");
    ros::NodeHandle nh;
    ros::ServiceServer server = nh.advertiseService("multi_two_num",multi_callback);
    ROS_INFO("waiting two numbers...");
    ros::spin();
    return 0;
}

client节点编写


#include <ros/ros.h>
#include <learn_service/multinum.h>
#include<cstdlib>
#include <time.h>
int main(int argc, char **argv){
    
    
    srand(int(time(0)));
    double a = rand()/(double(RAND_MAX)/100);
    double b = rand()/(double(RAND_MAX)/100);
    ros::init(argc, argv, "multi_client");
    ros::NodeHandle nh;
    ros::ServiceClient client = nh.serviceClient<learn_service::multinum>("multi_two_num");

    learn_service::multinum srv;
    srv.request.a = a;
    srv.request.b = b;

    if (client.call(srv)){
    
    
        ROS_INFO("%f * %f = %f", a, b, srv.response.c);
    }else{
    
    
        ROS_ERROR("Failed to call service");
    }
    return 0;
}

运行结果

首先我们将服务器节点开启,然后需要计算的时候调用client节点,服务器计算好结果之后回传给client。具体效果如下所示:
在这里插入图片描述

4. 创建服务器节点与客户端节点(python)

同样的东西,我们用python再实现一次。

server节点编写


#!/usr/bin/env python
# coding:utf-8

import rospy
from learn_service.srv import *

def callback_func(req):
    answer = req.a*req.b
    rospy.loginfo("result is %f", answer)
    return multinumResponse(answer)


def server():
    rospy.init_node("python_server")
    s = rospy.Service("multi_2num",multinum,callback_func)
    rospy.loginfo("Waiting 2 numbers...")
    rospy.spin()

if __name__=="__main__":
    server()

重点:

  1. 启动服务的函数接口:rospy.Service("service名", srv数据类型, 回调函数)
  2. 回调函数中传入的是请求request,返回的是response
  3. 返回的数据类型是自定义的,比如上例中,我们自定义的srv数据类型是multinum,那么对应的response数据类型为multinumResponse

client节点编写


#!/usr/bin/env python
# coding:utf-8

import rospy
from learn_service.srv import *
import random

def client():
    rospy.init_node("python_client")
    rospy.wait_for_service("multi_2num")
    try:
        c = rospy.ServiceProxy("multi_2num",multinum)
        a = random.random() * 100
        b = random.random() * 100
        response = c.call(a,b)

        rospy.loginfo("%f * %f = %f", a, b, response.c)
    except rospy.ServiceException, e:
        rospy.logerr("service call failed: %s", e)

if __name__=="__main__":
    client()

重点:

  1. rospy.wait_for_service("service名称")可以使得该client_node节点直到对应service服务器开始工作之后,代码才继续往下运行
  2. 启动client的函数接口:rospy.ServiceProxy("service名称",自定义srv数据类型)
  3. 向服务器发送请求使用2步骤中返回类的call函数:response = client.call(srv中request相关数据),call函数的参数为request的具体参数,本例中就是要乘的a,b两数;或者为对应的request数据格式,本例为multinumRequest(a,b)

运行结果

如下所示,跟预期一致,没有问题。
在这里插入图片描述

三、总结

到此,我们用两章的内容,基本涵盖ros通讯中的两大最常用的方式。ros还有一种action通讯方式,平时基本用不到。所以接下来教程也不会涉及到这部分内容。

下一章,我们来一起探索ros中的tf坐标变换。

猜你喜欢

转载自blog.csdn.net/allenhsu6/article/details/112384549