最近在学习赵虚左老师的ROS2课程,为此记录学习过程
目录
一、自定义接口
1.自己创建一个.srv文件,里面放置变量属性和变量名,这里我放在了base_interfaces_demo/srv中。
其中分隔符--- 用于明确区分请求和响应部分。具体来说:
请求部分(Request):在 ---
之前的部分定义了服务请求的参数。这个部分包含了客户端在调用服务时需要发送的数据。在下面的例子中,num1
和 num2
是客户端发送的请求参数。
响应部分(Response):在 ---
之后的部分定义了服务响应的参数。这个部分包含了服务器在处理请求后返回的数据。在下面的例子中,sum
是服务器计算后返回的结果。
2.修改xml文件,插入依赖,要根据自己的功能包名称进行修改
<!-- 编译依赖 -->
<build_depend>rosidl_default_generators</build_depend>
<!-- 执行依赖 -->
<exec_depend>rosidl_default_runtime</exec_depend>
<!-- 声明当前包所属的组 -->
<member_of_group>rosidl_interface_packages</member_of_group>
3.编辑txt文件,添加功能包目录下的对应文件
find_package(rosidl_default_generators REQUIRED)
#为接口文件生成源码/************/
rosidl_generate_interfaces( ${PROJECT_NAME}
"srv/AddInts.srv"
)
4.编译并测试文件
colcon build --packages-select base_interfaces_demo
ros2 interface show base_interfaces_demo/srv/AddInts
出现这个说明接口配置成功
二、服务端实现
1.先直接导入之前配置的模板,模板之前文章提到过,添加需要用到的头文件,将类名称与节点名称修改
2.声明服务端,放到private中
rclcpp::Service<AddInts>::SharedPtr server_;
3.创建服务端
server_ = this->create_service<AddInts>("addints",std::bind(&ADDIntServer::add_callback,this,_1,_2));
"addints"表示话题名称
std::bind(&ADDIntServer::add_callback,this,_1,_2)表示回调函数引用ADDIntServer类中的add_callback,由当前对象调用,_1,_2表示占位符
4.回调函数
void add_callback(const AddInts::Request::SharedPtr req,const AddInts::Response::SharedPtr res)
{
res->sum = req->num1 + req->num2;
RCLCPP_INFO(this->get_logger(),"%d + %d = %d",req->num1,req->num2,res->sum);
}
通常,回调函数的第一个参数会是请求消息(输入参数),第二个参数会是响应消息(输出参数),请求消息用于接收客户端发送的数据,而响应消息则用于填充服务器处理后的结果。
验证结果:
判断代码是否正确可以进行编译运行以下,然后开启新的终端,配置source之后使用
ros2 service call /addints base_interfaces_demo/srv/AddInts {"'num1': 10,'num2': 30"}
如果正确
完整代码
//包含头文件
#include "rclcpp/rclcpp.hpp"
#include "base_interfaces_demo/srv/add_ints.hpp"
using base_interfaces_demo::srv::AddInts;
using std::placeholders::_1;
using std::placeholders::_2;
// 自定义节点类
class ADDIntServer :public rclcpp::Node
{
public:
ADDIntServer():Node("add_int_server_node")
{
RCLCPP_INFO(this->get_logger(),"创建服务端");
server_ = this->create_service<AddInts>("addints",std::bind(&ADDIntServer::add_callback,this,_1,_2));
}
private:
void add_callback(const AddInts::Request::SharedPtr req,const AddInts::Response::SharedPtr res)
{
res->sum = req->num1 + req->num2;
RCLCPP_INFO(this->get_logger(),"%d + %d = %d",req->num1,req->num2,res->sum);
}
rclcpp::Service<AddInts>::SharedPtr server_;
};
int main(int argc,char **argv)
{
// 初始化ROS2客户端
rclcpp::init(argc,argv);
//调用spin函数,传入指针
rclcpp::spin(std::make_shared<ADDIntServer>());
//释放资源
rclcpp::shutdown();
return 0;
}
三、客户端实现
客户端的代码比较多,具体流程为
获取服务端参数,检查输入参数是否正确 -> 连接话题 -> 发送请求 -> 获取服务端返回的数据 -> 数据响应客户端
首先还是先将模板放上去,添加需要用到的头文件,修改对应类与节点名称
1.检查输入参数是否正确,如果正确进行之后进行初始化并创建一个客户端
if(argc != 3)
{
RCLCPP_ERROR(rclcpp::get_logger("rclcpp"),"请提交两个参数");
return 0;
}
rclcpp::init(argc,argv);
auto client = std::make_shared<ADDIntClient>();
.2.客户端尝试连接话题服务,连接成功返回true,连接失败返回false
bool flag = client->connect_serve();
if(!flag)
{
RCLCPP_INFO(rclcpp::get_logger("rclcpp"),"服务器连接失败");
}
/*********************************/
// 创建连接函数,放在class的public中
bool connect_serve()
{
while(!client_-> wait_for_service(1s))
{
//判断是否按下ctrl + c,使用的是rclcpp::getlogger
if(!rclcpp::ok())
{
// 因为按下ctrl + c 就会释放资源,此时clinet不存在了,所以不能用this->getlogger()
// 而rclcpp::get_logger不依赖于context的
RCLCPP_INFO(rclcpp::get_logger("rclcpp"),"取消连接");
return false;
}
RCLCPP_INFO(this->get_logger(),"等待连接中");
}
return true;
}
如果没有按下ctrl +c ,便会一直等待连接,否则直接return返回,这里使用rclcpp而不是this是因为ctrl +c之后便会释放内存,ADDIntClient也会消失,这是如果还是调用this的话便会报错
3.连接成功后发送请求
发送请求使用到的是async_send_request这个函数,输入参数为一个请求,输出为一个FutureAndRequestId instance
我们需要调用ADDIntClient类的函数,将返回值传给future
auto future = client->send_request(atoi(argv[1]),atoi(argv[2]));
/******************************************/
//创建请求函数
rclcpp::Client<AddInts>::FutureAndRequestId send_request(int num1,int num2)
{
/*
rclcpp::Client<base_interfaces_demo::srv::AddInts>
*/
auto request = std::make_shared<AddInts::Request>();
request->num1 = num1;
request->num2 = num2;
return client_->async_send_request(request);
}
private:
rclcpp::Client<AddInts>::SharedPtr client_;
我们直接构建一个函数,其功能是将我们之前传入的两个参数放到reques中,发送给 async_send_request,future得到相应的FutureAndRequestId。
4.判断客户端是否得到相应
需要调用spin_until_future_complete函数,输入参数为客户端与future,返回值由success 、 timeout 和 interrupted三个,我们只需要看是否成功即可
//处理响应
if(rclcpp::spin_until_future_complete(client,future) == rclcpp::FutureReturnCode::SUCCESS)
{
RCLCPP_INFO(rclcpp::get_logger("rclcpp"),"响应成功,sum=%d",future.get()->sum);
}
else
{
RCLCPP_INFO(rclcpp::get_logger("rclcpp"),"响应失败");
}
将服务端开启,运行客户端时附带两个参数,正确结果如下图
完整代码
//包含头文件
#include "rclcpp/rclcpp.hpp"
#include "base_interfaces_demo/srv/add_ints.hpp"
using base_interfaces_demo::srv::AddInts;
using namespace std::chrono_literals;
// 自定义节点类
class ADDIntClient :public rclcpp::Node
{
public:
ADDIntClient():Node("add_int_client_name")
{
RCLCPP_INFO(this->get_logger(),"客户端创建");
client_ = this->create_client<AddInts>("addints");
}
// 创建连接函数
bool connect_serve()
{
while(!client_-> wait_for_service(1s))
{
//判断是否按下ctrl + c,使用的是rclcpp::getlogger
if(!rclcpp::ok())
{
// 因为按下ctrl + c 就会释放资源,此时clinet不存在了,所以不能用this->getlogger()
// 而rclcpp::get_logger不依赖于context的
RCLCPP_INFO(rclcpp::get_logger("rclcpp"),"取消连接");
return false;
}
RCLCPP_INFO(this->get_logger(),"等待连接中");
}
return true;
}
//创建请求函数
rclcpp::Client<AddInts>::FutureAndRequestId send_request(int num1,int num2)
{
//rclcpp::Client<base_interfaces_demo::srv::AddInts>
auto request = std::make_shared<AddInts::Request>();
request->num1 = num1;
request->num2 = num2;
return client_->async_send_request(request);
}
private:
rclcpp::Client<AddInts>::SharedPtr client_;
};
int main(int argc,char **argv)
{
// 初始化ROS2客户端
if(argc != 3)
{
RCLCPP_ERROR(rclcpp::get_logger("rclcpp"),"请提交两个参数");
return 0;
}
rclcpp::init(argc,argv);
auto client = std::make_shared<ADDIntClient>();
bool flag = client->connect_serve();
if(!flag)
{
RCLCPP_INFO(rclcpp::get_logger("rclcpp"),"服务器连接失败");
}
auto future = client->send_request(atoi(argv[1]),atoi(argv[2]));
//处理响应
if(rclcpp::spin_until_future_complete(client,future) == rclcpp::FutureReturnCode::SUCCESS)
{
RCLCPP_INFO(rclcpp::get_logger("rclcpp"),"响应成功,sum=%d",future.get()->sum);
}
else
{
RCLCPP_INFO(rclcpp::get_logger("rclcpp"),"响应失败");
}
//释放资源
rclcpp::shutdown();
return 0;
}
四、总结:
服务端主要是构建一个回调函数,客户端在输入两个参数且连接到服务后,将参数传入话题后,由服务端得到最终值并返回到客户端,客户端在进行最后的响应