自定义一个服务数据类型接口
创建sev目录和文件
服务的接口类型由两部分组成,请求和相应
在包的src的同级目录下创建sev文件夹,在sev文件夹里添加Xxx.srv文件(文件名首字母必须大写,不能有下划线)
- 请求和相应用—隔离开,上面是请求的数据类型,下面是相应的数据类型
# 文件名Borrow.srv
#request
string name
uint32 money
---
# response
bool success
uint32 money
# 文件名SellNovel.srv
uint32 money
---
string[] novel
修改包的CMakeLists.txt文件
需要在CMakeLists.txt文件中添加rosidl_generate_interfaces
表明srv文件所在的目录,如果有添加的数据类型需要添加find_package
# 如果有添加的数据类型需要添加find_package
find_package(std_msgs REQUIRED)
# rosidl_generate_interfaces表明srv文件所在的目录
rosidl_generate_interfaces(${PROJECT_NAME}
"srv/Borrow.srv"
"srv/SellNovel.srv"
DEPENDENCIES
)
修改包的package.xml文件
需要添加的
<!-- 下面两项是需要添加的东西 -->
<!-- <buildtool_depend>rosidl_default_generators</buildtool_depend> -->
<exec_depend>rosidl_default_runtime</exec_depend>
<member_of_group>rosidl_interface_packages</member_of_group>
查看是否成功
使用命令来查看ros2 interface list
,命令查看看到三个数据类型,表示在service_interface包里面srv目录下的Borrow类型
服务
- 同一个服务(名称相同)只能由有一个节点来提供
- 同一个服务可以被多个客户端调用
- 导入自定义的服务接口(需先在package.xml中添加自定义包的依赖)
- 修改packag.xml文件
<!-- 添加数据类型所在包名,即依赖,自定义数据类型在service_interface包中 -->
<depend>service_interface</depend>
- 修改CMakeLists.txt文件
//添加service_interface
find_package(service_interface REQUIRED)
// 添加service_interface
ament_target_dependencies(
my_services
"std_msgs"
"rclcpp"
service_interface
)
// 添加数据类型的头文件
// service_interface包的srv目录下有两个数据类型文件Borrow.srv和Buy.srv
// 导入的头文件都必须是小写
// srv文件名:Borrow.srv SellNovel.srv
#include "service_interface/srv/borrow.hpp"
#include "service_interface/srv/sell_novel.hpp"
- 创建服务端回调函数
对调函数是当服务端接受到数据做出反应的函数,参数是两个之只能指针
#include "service_interface/srv/sell_novel.hpp"
// 声明一个回调函数,用于接受导数据并做出相应
// 参数有两个一个是请求,一个是回应
//类型是共享指针类型,书写格式如下
void callback(const service_interface::srv::SellNovel::Request::SharedPtr request,
const service_interface::srv::SellNovel::Response::SharedPtr response
)
{
unsigned int num = (int)request->money/(1.0);
if(num>novel_queue.size())
{
// 等待
RCLCPP_INFO(this->get_logger(),"书不够,书库有:%d,要卖出:%d",novel_queue.size(),num);
// 设置睡眠的时间为一秒
rclcpp::Rate rate(1);
while (novel_queue.size()<num)
{
RCLCPP_INFO(this->get_logger(),"等待中,还差:%d本书",novel_queue.size());
// 循环中睡眠一秒
rate.sleep();
}
}
RCLCPP_INFO(this->get_logger(),"当前图书的数量有:%d,大于要卖出去的数量:%d",novel_queue.size(),num);
for(int i=0;i<(int)num;i++)
{
response->novel.push_back(novel_queue.front());
novel_queue.pop();
}
}
在定义回调函数时,我们为了不影响主线程的顺序执行,才用多线程的方式来处理一些回调函数中的流程
- 声明回调组
// 类中声明回调函数组-------------------
// ros2中使用多线程执行器和回调组来实现多线程,我们先在类中声明一个回调组成员变量
rclcpp::CallbackGroup::SharedPtr callback_group;
// 构造函数中初始化回调组----------------------------
// 自定义函数回调组
callback_group = this->create_callback_group(rclcpp::CallbackGroupType::MutuallyExclusive);
- 修改主函数有单线程执行器变为多线程执行器
// 单线程的主函数
int main(int argc, char ** argv)
{
//2.初始化客户端库
rclcpp::init(argc,argv);
//3.使用智能指针创建新的节点对象,在面相对象的这节点声明,智能指针就要输入类的类型,而不是rclcpp::Node
auto node = std::make_shared<my_server>("ros2");
//4.使用spin循环节点
rclcpp::spin(node);
//5. 关闭客户端库
rclcpp::shutdown();
}
// 多线程主函数
int main(int argc, char ** argv)
{
//2.初始化客户端库
rclcpp::init(argc,argv);
//3.使用智能指针创建新的节点对象,在面相对象的这节点声明,智能指针就要输入类的类型,而不是rclcpp::Node
auto node = std::make_shared<my_server>("ros2");
rclcpp::executors::MultiThreadedExecutor executor;
executor.add_node(node); // 第一个线程节点
executor.add_node(node); // 第二个线程节点
executor.spin();
rclcpp::shutdown();
}
- 声明并创建服务端
// 类中定义服务-------------
// 自定义函数回调组
callback_group = this->create_callback_group(rclcpp::CallbackGroupType::MutuallyExclusive);
// 服务端的声明,没有初始化,在构造函数中初始化
rclcpp::Service<service_interface::srv::SellNovel>::SharedPtr services_pub;
// 构造函数中声明服务-------------
services_pub = this->create_service<service_interface::srv::SellNovel>("service_name",
std::bind(&my_server::callback,this,_1,_2),
rmw_qos_profile_services_default,
callback_group);
- 第一个参数:发布的服务名称
- 第二个参数:bind函数将成员函数作为回调函数
- 第三个参数:就是默认值,但是我们要给第四个参数赋值,所以就不能跳过第三个参数
- 第四个参数:回调函数组
有了第四个参数,函数回调组之后,当接收到客户端的函数请求之后,就会在callback_group函数回调组里面执行callback函数。
全部代码
#include <cstdio>
#include <queue>
#include <string>
#include "rclcpp/rclcpp.hpp"
#include "service_interface/srv/borrow.hpp"
#include "service_interface/srv/sell_novel.hpp"
using std::placeholders::_1;
using std::placeholders::_2;
class my_server :public rclcpp::Node
{
private:
// 存放二手书的一个队列容器
std::queue<std::string> novel_queue;
// ros2中使用多线程执行器和回调组来实现多线程,我们先在类中声明一个回调组成员变量
rclcpp::CallbackGroup::SharedPtr callback_group;
// 服务端的声明,没有初始化,在构造函数中初始化
rclcpp::Service<service_interface::srv::SellNovel>::SharedPtr services_pub;
// 声明一个回调函数,用于接受导数据并做出相应
// 参数有两个一个是请求,一个是回应
//类型是共享指针类型,书写格式如下
void callback(const service_interface::srv::SellNovel::Request::SharedPtr request,
const service_interface::srv::SellNovel::Response::SharedPtr response
)
{
unsigned int num = (int)request->money/(1.0);
if(num>novel_queue.size())
{
// 等待
RCLCPP_INFO(this->get_logger(),"书不够,书库有:%d,要卖出:%d",novel_queue.size(),num);
// 设置睡眠的时间为一秒
rclcpp::Rate rate(1);
while (novel_queue.size()<num)
{
RCLCPP_INFO(this->get_logger(),"等待中,还差:%d本书",novel_queue.size());
// 循环中睡眠一秒
rate.sleep();
}
}
RCLCPP_INFO(this->get_logger(),"当前图书的数量有:%d,大于要卖出去的数量:%d",novel_queue.size(),num);
for(int i=0;i<(int)num;i++)
{
response->novel.push_back(novel_queue.front());
novel_queue.pop();
}
}
public:
my_server(std::string name);
~my_server();
};
my_server :: my_server(std::string name):Node(name)
{
RCLCPP_INFO(this->get_logger(),"构造 service");
// 自定义函数回调组
callback_group = this->create_callback_group(rclcpp::CallbackGroupType::MutuallyExclusive);
// 对服务对象进行实例化
services_pub = this->create_service<service_interface::srv::SellNovel>("service_name",
std::bind(&my_server::callback,this,_1,_2),
rmw_qos_profile_services_default,
callback_group);
}
my_server:: ~my_server()
{
RCLCPP_INFO(this->get_logger(),"结束service");
}
int main(int argc, char ** argv)
{
//2.初始化客户端库
rclcpp::init(argc,argv);
//3.使用智能指针创建新的节点对象,在面相对象的这节点声明,智能指针就要输入类的类型,而不是rclcpp::Node
auto node = std::make_shared<my_server>("ros2");
rclcpp::executors::MultiThreadedExecutor executor;
executor.add_node(node); // 第一个线程节点
// executor.add_node(node); // 第二个线程节点
executor.spin();
rclcpp::shutdown();
}