ROS service简单使用示例

news2024/9/17 8:27:36

1、为什么要使用ROS service

之前写过一篇关于ROS topic的内容。对于实时性、周期性的消息,使用topic来传输是最佳的选择。topic是一种点对点的单向通信方式,这里的“点”指的是node,也就是说node之间可以通过topic方式来传递信息。topic要经历下面几步的初始化过程:首先,publisher节点和subscriber节点都要到节点管理器进行注册,然后publisher会发布topic,subscriber在master的指挥下会订阅该topic,从而建立起sub-pub之间的通信。

在大部分情况下,ROS topic能够满足需求,但是在某些特殊情况下,比如当我传输给某个节点一个信号让对方处理的时候需要让它告诉我处理结果的时候,使用topic的机制就不是很适合了。虽然可能也能做哈,就是一个A通过topic发布处理信号,B通过subscriber订阅这个信号。处理完成后B再通过一个topic发布处理结果,A再通过一个subscriber订阅这个结果。但是这样子属实有点繁琐。

因此,这里的service机制就应运而生了。service服务通讯机制是一种双向同步数据传输模式。基于客户端/服务器模型,两部分通信数据类型:一个用于请求,一个用于应答,类似web服务器。当客户端发送请求信号时,服务端会处理信号同时返回处理结果,这样子就会把整个过程简化很多。

2、如何编写ROS service

ROS service的使用也需要定义一个msg,但是名称用的是srv。这块类似于ROS msg的用法。首先建立一个文件夹,这里我们就用service_test吧:

catkin_create_pkg service_test roscpp rospy

然后建立一个srv文件:

cd service_test
mkdir srv
cd srv
gedit service_test.srv

这时候会打开一个新建的service_test.srv文件,在其中输入:

float64 numb1
float64 numb2
----
float64 sum

这里与msg的写法有点不同,它其中的数据被分为了两部分,中间用“—”分割。这是在于msg是不带返回值的,但是srv是需要返回值的。分割线下面的数据就是srv中服务端到时候要返回给客户端的内容。

然后同样的需要修改cmakelist以及package.xml文件:

对于cmakelist文件需要修改下面三个内容:

首先需要在find_package中增加message_generagetion:

find_package(catkin REQUIRED COMPONENTS
  roscpp
  rospy
  message_generation
)

其次在add_service_files中添加刚才写的srv文件:

add_service_files(
   FILES
   service_test.srv
#   Service2.srv
 )

最后打开message_generation:

generate_messages(
   DEPENDENCIES
#   std_msgs  # Or other packages containing msgs
 )

对于package.xml文件中则只需要添加下面两行代码即可:

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

完成上述后catkin_make编译该文件夹,即可生成对应的.h文件:
在这里插入图片描述

3、如何使用ROS service

ROS service需要一个客户端一个服务端

3.1、cient

一个非常简单的客户端长这个样子:

#include"ros/ros.h"
#include "service_test/service_test.h"
using namespace std;
class service_client
{
private:
    /* data */
    ros::NodeHandle nh;
    int a,b;
public:
    
service_client();
~service_client();
ros::ServiceClient client;
void request();
};

service_client::service_client()
{
    a = 0;
    b = 0;    
    client = nh.serviceClient<service_test::service_test::Request>("/service_test");
}
void service_client::request()
{
    cout<<"request"<<endl;
    service_test::service_test req;
    req.request.numb1 = a;
    req.request.numb2 = b;
    if(client.call(req))
    {
        cout<<a<<"+"<<b<<"="<<req.response.sum<<endl;
    }
    else
    {
        cout<<"request falied"<<endl;
    }
    a++;
    b+=2;
}
service_client::~service_client()
{
}


int main(int argc, char **argv)
{
    ros::init(argc, argv, "service_client");
    ROS_INFO("service_client");
    service_client service_client;
    ros::Rate rate(0.5);
    int count=0;
    while(count < 30)
    {
        service_client.request();
        rate.sleep();
        count++;
    }
    return 0;
}

这里我用C++写了类函数,因为在ROS中很多地方C++类函数会比C好用,所以为了养成习惯就简单定义了下类函数。代码构造很简单,首先我们在头文件中引入之前写好的srv文件,注意编译后它已经变成了.h文件:

#include "service_test/service_test.h"

然后我们定义一个客户端,这里分为了两行,当然其实写成一行好像也是可以的:

ros::ServiceClient client;

以及:

client = nh.serviceClient<service_test::service_test::Request>("/service_test");

这里我们定义了一个ROSservice的客户端,使用的数据格式是service_test文件夹下的service_test数据格式,service的名称是"/service_test"

到这里声明就完成了,接下来如何使用?那就是调用这个client就行了,所以我们定义了一个函数request(),让它循环执行调用client。当然调用的时候是需要传参的。使用的参数就是我们之前定义好的service_test::service_test格式的参数,至于参数名称叫什么就无所谓了。

另外赋值的时候记得加上request,这是service标准所需要的。最后通过一个call函数调用这个client,成功情况下一般会返回true,所以也可以外接一个if语句判断一下service是否执行成功。

service_test::service_test req;
    req.request.numb1 = a;
    req.request.numb2 = b;
    if(client.call(req))
    {
        cout<<a<<"+"<<b<<"="<<req.response.sum<<endl;
    }
    else
    {
        cout<<"request falied"<<endl;
    }

3.2、server

然后我们再看server,先放程序:

#include"ros/ros.h"
#include "service_test/service_test.h"
using namespace std;
class service_server
{
private:
    /* data */
    
public:
    
service_server(/* args */);
    ~
service_server();
    ros::ServiceServer server;
    bool requestCallback(service_test::service_test::Request &request,service_test::service_test::Response &response);
};

service_server::service_server(/* args */)
{
    ros::NodeHandle nh;
    server = nh.advertiseService("/service_test",&service_server::requestCallback,this);
}
bool service_server::requestCallback(service_test::service_test::Request &request,service_test::service_test::Response &response)
{
    cout<<"a="<<request.numb1<<endl;
    cout<<"b="<<request.numb2<<endl;
    cout<<"a+b="<<request.numb1+request.numb2<<endl;
    response.sum = request.numb1+request.numb2;
    return true;
}

service_server::~service_server()
{
}

int main(int argc, char **argv)
{
    ros::init(argc, argv, "service_server");
    ROS_INFO("service_server");
    service_server server;
    ros::Rate rate(0.5);
    ros::spin();
    return 0;
}

server的程序比client看起来还短一点,内容其实差不多。前面定义的是client这边改成了server而已。但是它的注册函数是不太一样的,需要带回调函数处理请求:

nh.advertiseService("/service_test",&service_server::requestCallback,this);

这里包括了请求的话题名称以及使用的回调函数名称。

然后我们会在回调函数中处理我们的请求,其实也就是相当于对传参的处理而已了。注意到service不是所有都需要返回值的。比如这里因为我们写了返回值sum且我在client端使用了:

cout<<a<<"+"<<b<<"="<<req.response.sum<<endl;

所以server端处理的时候注意传值回去,如果client端不需要传值的话可以不用写也是无所谓的。

4、测试结果

在cmakelist中加入:

add_executable(service_client src/service_client.cpp)  
target_link_libraries(service_client
    ${catkin_LIBRARIES}) 
add_dependencies(service_client ${catkin_EXPORTED_TARGETS})

add_executable(service_server src/service_server.cpp)  
target_link_libraries(service_server
    ${catkin_LIBRARIES}) 
add_dependencies(service_server ${catkin_EXPORTED_TARGETS})

最后编译生成两个可执行文件,运行结果如下:

客户端:
在这里插入图片描述
服务端:
在这里插入图片描述

5、留下一个问题

另外注意到这里有个小问题:

当时在写客户端的时候,由于按照之前的习惯有时候我会将

ros::NodeHandle nh;

写到:

service_client::service_client()
{
    a = 0;
    b = 0;    
    client = nh.serviceClient<service_test::service_test::Request>("/service_test");
}

类函数初始化中,编译完虽然没有报错但是无法正常请求service,这里有点疑惑,因为之前好像也这么写过没有问题。如果有知道原因的小伙伴可以留言告知,感谢!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/65010.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

详细设计阶段复习

详细设计详细设计:确定具体实现方案,得出精确描述任务:结构程序设计:三种基本控制结构(选择[if]/顺序/循环[while|for])实现任何单入单出的程序人机界面设计:属于接口设计的重要组成问题设计指南设计工具:描述处理过程的工具程序流程图(历史悠久)盒图(N-S图): 不违背结构程序设…

[附源码]计算机毕业设计JAVA疫情环境下的酒店管理系统

[附源码]计算机毕业设计JAVA疫情环境下的酒店管理系统 项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM…

机器学习实战——股票close预测

前言 用股票历史的close预测未来的close。 另一篇用深度学习搞得&#xff0c;见&#xff1a;深度学习实战——CNNLSTMAttention预测股票 技术栈 xgboostpython 原理 都是很简单的小玩意&#xff0c;试了下发现预测的还不错&#xff0c;先上效果图&#xff1a; 有点惊讶&a…

CSS中 设置( 单行、多行 )超出显示省略号

1. 设置超出显示省略号 css设置超出显示省略号可分两种情况&#xff1a; 单行文本溢出显示省略号…多行文本溢出显示省略号… 但使用的核心代码是一样的&#xff1a;需要先使用 “overflow:hidden;” 来把超出的部分隐藏&#xff0c;然后使用“text-overflow:ellipsis;”当文…

Java进阶架构师之如何画好架构图?阿里大神手把手教你!

1、什么是架构 架构就是对系统中的实体以及实体之间的关系所进行的抽象描述&#xff0c;是一系列的决策。 架构是结构和愿景。 系统架构是概念的体现&#xff0c;是对物/信息的功能与形式元素之间的对应情况所做的分配&#xff0c;是对元素之间的关系以及元素同周边环境之间…

基于灰狼算法优化的lssvm回归预测-附代码

基于灰狼算法优化的lssvm回归预测 - 附代码 文章目录基于灰狼算法优化的lssvm回归预测 - 附代码1.数据集2.lssvm模型3.基于灰狼算法优化的LSSVM4.测试结果5.Matlab代码摘要&#xff1a;为了提高最小二乘支持向量机&#xff08;lssvm&#xff09;的回归预测准确率&#xff0c;对…

Java基础:Collection、泛型

第一章 Collection集合 1.1 集合概述 在前面使用过集合ArrayList&#xff0c;那么集合到底是什么呢&#xff1f; 集合&#xff1a;集合是java中提供的一种容器&#xff0c;可以用来存储多个数据。 集合和数组既然都是容器&#xff0c;它们有啥区别呢&#xff1f; 数组的长…

DPDK 数据传输流程

在进行正式的收发包之前&#xff0c;DPDK需要做一些初始化操作&#xff0c;包括&#xff1a; 初始化一个或多个mbuf_pool&#xff0c;用来存储从网卡中接受的数据包修改网卡配置&#xff0c;指定其接受队列的个数&#xff08;通常每个转发核一个&#xff09;&#xff0c;长度&…

【Hadoop 2.7.1】HDFS Shell操作的简单试验

【Hadoop 2.7.1】HDFS Shell操作的简单试验 HDFS提供了多种数据访问的方式&#xff0c;其中&#xff0c;命令行的形式是最简单的&#xff0c;同时也是开发者最容易掌握的方式 文章目录【Hadoop 2.7.1】HDFS Shell操作的简单试验HDFS Shell命令的使用上传文件(put)查看文件列表(…

全网最详细Centos7搭建Redis集群

1、准备三台服务器 没有服务器的话&#xff0c;虚拟机也一样 2、每台服务器安装上redis 相关网址&#xff1a; CentOS7安装Redis完整教程_长头发的程序猿的博客-CSDN博客_centos7 redis安装 3、修改“139.196.105.140&#xff08;主机&#xff09;”的配置文件 vim /etc/r…

路由策略简介、配置举例

路由策略简介、配置举例 定义 路由策略主要实现了路由过滤和路由属性等设置功能&#xff0c;他通过改变路由属性&#xff08;包括可达性&#xff09;来改变网络流量所经过的路径。 目的优势 目的 路由协议在发布、接收和引入路由信息时&#xff0c;根据实际组网需求实施一些策…

25个网络安全搜索引擎备忘录

©网络研究院 下面介绍一个包含 25 个网络安全搜索引擎的列表&#xff0c;每个网络爱好者都应该在互联网中了解这些搜索引擎。 此列表没有特定顺序&#xff0c;主要基于使用偏好。 1. 搜索连接到互联网的设备 https://www.shodan.io/ 2. 无线网络数据库&#xff0c;带…

矩阵篇(五)-- 特征值分解(EVD)和奇异值分解(SVD)

1 特征值分解&#xff08;EVD&#xff09; 设AnnA_{n \times n}Ann​有nnn个线性无关的特征向量x1,…,xn\boldsymbol{x}_{1}, \ldots, \boldsymbol{x}_{n}x1​,…,xn​&#xff0c;对应特征值分别为λ1,…,λn\lambda_{1}, \ldots, \lambda_{n}λ1​,…,λn​ A[x1⋯xn][λ1x1⋯…

基于jsp+ssm的家庭理财系统

项目介绍 在这科技不断的进步&#xff0c;让我们的生活改变了很多&#xff0c;信息技术的迅速发展&#xff0c;使各种行业在信息技术应用方面变得非常普遍。信息时代的到来&#xff0c;已成为一种必然趋势。本系统的标题是基于B/S模式的家庭理财系统的设计开发&#xff0c;其目…

公钥密码(非对称加密)

实例 投币寄物柜是这样使用的&#xff1a; 首先&#xff0c;将物品放人寄物柜中。然后&#xff0c;投入硬币并拔出钥匙&#xff0c;就可以将寄物柜关闭了。关闭后的寄物柜&#xff0c;没有钥匙是无法打开的。只要有硬币&#xff0c;任何人都可以关闭寄物柜&#xff0c;但寄物…

Locust学习记录2-locust用户类属性【HttpUser,wait_time,weight和fixed_count】

HttpUser 每个模拟用户定义的类&#xff0c;都必须继承HttpUser&#xff0c;目的时为每个用户提供一个client属性&#xff0c;该属性是一个实例HttpSession&#xff0c;可用于向我们要进行负载测试的目标系统发出HTTP请求 当测试开始时&#xff0c;locust将为它模拟的每个用户…

vue项目分环境打包的具体步骤 --- 区分测试环境与线上环境的打包引用路径

第一步&#xff1a; 安装cross-env npm install --save-dev cross-env 运行跨平台设置和使用环境变量的脚本 第二步&#xff1a;修改package.json 在package.json 里设置打包命令 --- 主要是基于使用vue-cli创建的项目&#xff0c;配置文件基于 NODE_ENVproduction 去处…

图文多模态模型CLIP

前言 CLIP带给我的震撼是超过transformer的&#xff0c;这是OpenAI的重要贡献之一。就如官网所描述的&#xff1a; CLIP: Connecting Text and Images 用对比学习&#xff08;Contrastive Learning&#xff09;来对齐约束图像模型和文本模型。用文本嵌入指导图像学习&#xff…

C++11特性-其他特性

1.字符串的原始字面量 表达式&#xff1a;R"xxx&#xff08;原始字符串&#xff09;xxx"或者R"(原始字符串)"&#xff0c;xxx要写的话&#xff0c;必须一样 //两种申明形式string a "aaa";//没有转义的时候就是原始字面量string b R"aaa(…

Ansible最佳实践之 AWX 作业创建和启动

写在前面 分享一些 AWX 作业创建和启动的笔记博文内容涉及&#xff1a; 创建作业模板涉及相关参数&#xff0c;作业模板角色配置介绍运行作业模板并测试的Demo 食用方式&#xff1a; 需要了解 Ansible理解不足小伙伴帮忙指正 傍晚时分&#xff0c;你坐在屋檐下&#xff0c;看着…