ROS 入门 —— 数据服务的定义与使用

ROS 入门 —— 数据服务的定义与使用

前面我们已经使用了 spawn 和 trigger 两种已经定义好的数据类型的使用方式,但是很多情况下,ROS 中已经定义好的数据结构并不能满足我们的需求,本篇文章我们来根据我们自己的需求定义服务数据的类型并使用

服务模型

前面我们已经实现了我们的 publisher 在不断发送数据, subscriber 不断接受并显示信息,但我们并不希望 publisher 不断发送数据,而是使用 service 这样一种数据,每请求一次就发布一次,同时接收到我们是否发送成功

client:发送显示某人个人信息的请求并把信息通过自定义的 service 发送出去

server:收到请求包括所有的个人信息,会通过日志进行显示,并通过 response 反馈显示的结果

service:show_person,使用 learning_service::Person 我们自定义的一个数据类型

自定义服务数据

这里我们与前面写的 msg 文件中的数据是对应的,不同就是我们在 srv 文件中会有反馈 response,这里把数据分成两个部分,我们通过 --- 进行区分,以上是 request 数据,以下是 response 数据我们在编译的时候会通过 ROS 固定的编译方式产生对应的头文件

下面的步骤与我们前面创建 msg 并进行编译的时候基本相似

这里我们在功能包目录下创建一个 srv 文件夹,里面存放我们的 srv 文件,我们新建一个 Person.srv:

string name
uint8  age
uint8  sex

uint8 unknown = 0
uint8 male    = 1
uint8 female  = 2
---
string result

然后我们在 Package.xml 中添加功能包依赖:

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

最后只需要在 CMakeLists.txt 中添加编译选项与依赖:

首先在 find_package 中添加 message_generation 功能包依赖:

然后添加我们要定义的文件,通过 add_service_files 添加 srv 文件,会自动的搜索我们在 srv 下创建的文件,generate_messages 会根据文件定义和相关依赖生成对应的头文件

最后就是 catkin_package 这里要添加相关依赖:

我们进行编译,我们就可以得到相关头文件:

这里我们可以看到,我们已经成功的编译出了一些头文件,这里我们就要考虑如何使用这些头文件

client 与 server 的定义

这里我们的 client 与 server 端与前面我们在写 msg 时候的基本相似,这里不同的就是

点击查看完整代码:

person_client.cpp
/**
 * 该例程将请求/show_person服务,服务数据类型learning_service::Person
 */

#include <ros/ros.h>
#include "learning_service/Person.h"

int main(int argc, char** argv)
{
    // 初始化ROS节点
	ros::init(argc, argv, "person_client");

    // 创建节点句柄
	ros::NodeHandle node;

    // 发现/spawn服务后,创建一个服务客户端,连接名为/spawn的service
	ros::service::waitForService("/show_person");
	ros::ServiceClient person_client = node.serviceClient<learning_service::Person>("/show_person");

    // 初始化learning_service::Person的请求数据
	learning_service::Person srv;
	srv.request.name = "Tom";
	srv.request.age  = 20;
	srv.request.sex  = learning_service::Person::Request::male;

    // 请求服务调用
	ROS_INFO("Call service to show person[name:%s, age:%d, sex:%d]", 
			 srv.request.name.c_str(), srv.request.age, srv.request.sex);

	person_client.call(srv);

	// 显示服务调用结果
	ROS_INFO("Show person result : %s", srv.response.result.c_str());

	return 0;
};
person_server.cpp
/**
 * 该例程将执行/show_person服务,服务数据类型learning_service::Person
 */

#include <ros/ros.h>
#include "learning_service/Person.h"

// service回调函数,输入参数req,输出参数res
bool personCallback(learning_service::Person::Request  &req,
         			learning_service::Person::Response &res)
{
    // 显示请求数据
    ROS_INFO("Person: name:%s  age:%d  sex:%d", req.name.c_str(), req.age, req.sex);

	// 设置反馈数据
	res.result = "OK";

    return true;
}

int main(int argc, char **argv)
{
    // ROS节点初始化
    ros::init(argc, argv, "person_server");

    // 创建节点句柄
    ros::NodeHandle n;

    // 创建一个名为/show_person的server,注册回调函数personCallback
    ros::ServiceServer person_service = n.advertiseService("/show_person", personCallback);

    // 循环等待回调函数
    ROS_INFO("Ready to show person informtion.");
    ros::spin();

    return 0;
}

这里我们的原理与前面我们在发布 msg 后使用的原理基本相同,唯一的区别就是我们这里并不是一直接受,而是每次只接受一次就结束程序,知道下一次运行该节点再继续接收

编译运行

在编译成功之后,我们先运行 client 节点,这里可以看到我们正在等待接收:

这里我们可以清晰的看到,我们每发送一次就只接收一次,而不是在一直接收,当时 server 端是一直在发送,每当我们运行一次 client ,当 server 端检测到我们开启了 client 端并正在准备接收数据就会发送一次数据

猜你喜欢

转载自blog.csdn.net/m0_59161987/article/details/129631602