动作通信客户端概要
在动作通信中,客户端主要完成三个任务:
- 目标请求发送与请求结果接收。
- 反馈数据接收。
- 最终结果接收。
与服务端类似的,这三个任务也分别对应了三个回调函数:
//请求响应结果回调:
using GoalResponseCallback = std::function<void (typename GoalHandle::SharedPtr)>;
//反馈数据回调:
using FeedbackCallback =std::function<void (typename ClientGoalHandle<ActionT>::SharedPtr,const std::shared_ptr<const Feedback>)>;
//最终结果回调
using ResultCallback = std::function<void (const WrappedResult & result)>;
动作通信客户端编码
本节案例实现的效果为:启动客户端节点时传入一个整形数据作为请求,然后客户端择机发送请求数据出去,并且器件不断打印来自服务端的反馈数据,最后再输出服务端的最终响应结果
1. 更改CMakeList.txt文件
由于在新建功能包时已经添加了相关的依赖,因此功能包下的package.xml
文件无需进行修改,只要往CMakeList.txt中添加三句真言
即可实现客户端代码的编译以及工作空间下的注册。
#创建可执行文件
add_executable(cpp02_client src/cpp02_client.cpp)
#添加依赖
ament_target_dependencies(
cpp02_client
"rclcpp"
"rclcpp_action"
"based_interfaces"
)
#注册
install(TARGETS
cpp01_server
cpp02_client
DESTINATION lib/${PROJECT_NAME})
2. 编写核心业务实现
动作客户端实现的核心在于发送数据函数,即async_send_goal,下面是从官方文档中摘抄出来的关于async_send_goal的介绍:
/* async_send_goal注释
std::shared_future<rclcpp_action::ClientGoalHandle<based_interfaces::action::Myaction>::
SharedPtr> async_send_goal(const based_interfaces::action::Myaction::Goal &goal, const
rclcpp_action::Client<based_interfaces::action::Myaction>::SendGoalOptions &options =
rclcpp_action::Client<based_interfaces::action::Myaction>::SendGoalOptions())
options – will receive callbacks, as long as you hold a reference to the shared pointer
contained in the returned future, or rclcpp_action::Client is destroyed. Dropping the shared
pointer to the goal handle will not cancel the goal. In order to cancel it, you must
explicitly call async_cancel_goal.
*/
从上面的介绍我们可以看出,发送函数需要传入两个参数,分别是const based_interfaces::action::Myaction::Goal &goal
类型的待发送数据,以及const rclcpp_action::Client<based_interfaces::action::Myaction>::SendGoalOptions
类型的option。其中option需要绑定三个回调函数,如下图:
当然,具体的函数实现在上一小结以及介绍了,这里提一下这三个函数编写的注意点:
- 全都无返回值。
- 传入的参数的类型大部分都在命名空间
rclcpp_action::ClientGoalHandle<Myaction>
下面,并且只有feedback有两个传入值。 - 对于goal_callback,需要根据句柄判断目标是否被接受。
- 对于feedbcak_callback,可以处理反馈结果。例如打印。
- 对于resul_callback,根据
result.code
对象的取值来确定任务执行的情况(result.code == rclcpp_action.ResultCode::XXX)
客户端Demo
#include "rclcpp/rclcpp.hpp"
#include "rclcpp_action/rclcpp_action.hpp"
#include "based_interfaces/action/myaction.hpp"
using based_interfaces::action::Myaction;
using namespace std::chrono_literals;
using namespace std::placeholders;
class My_Client : public rclcpp::Node{
private:
rclcpp_action::Client<Myaction>::SharedPtr client_;
void goal_res_callback(rclcpp_action::ClientGoalHandle<Myaction>::SharedPtr goal_handle){
}
void feedback_callback(rclcpp_action::ClientGoalHandle<Myaction>::SharedPtr goal_handle,const std::shared_ptr<const Myaction::Feedback> feedback){
}
void result_callback(const rclcpp_action::ClientGoalHandle<Myaction>::WrappedResult & result){
if(result.code == rclcpp_action::ResultCode::SUCCEEDED)
{
RCLCPP_INFO(this->get_logger(),"服务端执行完成");
RCLCPP_INFO(this->get_logger(),"result = %d",result.result->result);
}else if(result.code == rclcpp_action::ResultCode::CANCELED)
{
RCLCPP_INFO(this->get_logger(),"任务取消");
}else if (result.code == rclcpp_action::ResultCode::ABORTED)
{
RCLCPP_INFO(this->get_logger(),"任务中断");
/* code */
}
}
public:
My_Client():Node("action_client_node"){
RCLCPP_INFO(this->get_logger(),"action client has been created!");
//创建客户端对象
client_ = rclcpp_action::create_client<Myaction>(this,"add_sum");
}
//连接服务端
bool connect(void){
while (rclcpp::ok())
{
if(client_->wait_for_action_server(1s)){
RCLCPP_INFO(this->get_logger(),"connecting action server");
return true;
}
}
RCLCPP_INFO(rclcpp::get_logger("ac_client"),"terminal has been shutdown!");
return false;
}
//
void send_goal(int tar){
//1.装载数据
Myaction::Goal Tar;
Tar.tar = tar;
//2.绑定回调函数
//const rclcpp_action::Client<based_interfaces::action::Myaction>::SendGoalOptions
rclcpp_action::Client<Myaction>::SendGoalOptions options;
options.feedback_callback = std::bind(&My_Client::feedback_callback,this,_1);
options.goal_response_callback= std::bind(&My_Client::goal_res_callback,this,_1,_2);
options.result_callback = std::bind(&My_Client::result_callback,this,_1);
//3.发送数据
auto future = client_->async_send_goal(Tar,options);
}
};
int main(int argc,char** argv){
rclcpp::init(argc,argv);
auto client = std::make_shared<My_Client>();
if(client->connect())
{
client->send_goal(atoi(argv[1]));
rclcpp::spin(client);
}
rclcpp::shutdown();
return 0;
}
客户端完整代码
#include "rclcpp/rclcpp.hpp"
#include "rclcpp_action/rclcpp_action.hpp"
#include "based_interfaces/action/myaction.hpp"
using based_interfaces::action::Myaction;
using namespace std::chrono_literals;
using namespace std::placeholders;
class My_Client : public rclcpp::Node{
private:
rclcpp_action::Client<Myaction>::SharedPtr client_;
void goal_res_callback(rclcpp_action::ClientGoalHandle<Myaction>::SharedPtr goal_handle){
if(goal_handle->get_status() == rclcpp_action::GoalStatus::STATUS_ACCEPTED)
{
RCLCPP_INFO(this->get_logger(),"目标被成功接收");
}else{
RCLCPP_INFO(this->get_logger(),"目标被拒绝!");
}
}
void feedback_callback(rclcpp_action::ClientGoalHandle<Myaction>::SharedPtr goal_handle,const std::shared_ptr<const Myaction::Feedback> feedback){
(void) goal_handle;
RCLCPP_INFO(this->get_logger(),"收到反馈数据: temp=%d,state = %s",feedback->temp,feedback->state.c_str());
}
void result_callback(const rclcpp_action::ClientGoalHandle<Myaction>::WrappedResult & result){
if(result.code == rclcpp_action::ResultCode::SUCCEEDED)
{
RCLCPP_INFO(this->get_logger(),"服务端执行完成");
RCLCPP_INFO(this->get_logger(),"result = %d",result.result->result);
}else if(result.code == rclcpp_action::ResultCode::CANCELED)
{
RCLCPP_INFO(this->get_logger(),"任务取消");
}else if (result.code == rclcpp_action::ResultCode::ABORTED)
{
RCLCPP_INFO(this->get_logger(),"任务中断");
/* code */
}
}
public:
My_Client():Node("action_client_node"){
RCLCPP_INFO(this->get_logger(),"action client has been created!");
//创建客户端对象
client_ = rclcpp_action::create_client<Myaction>(this,"add_sum");
}
//连接服务端
bool connect(void){
while (rclcpp::ok())
{
if(client_->wait_for_action_server(1s)){
RCLCPP_INFO(this->get_logger(),"connecting action server");
return true;
}
}
RCLCPP_INFO(rclcpp::get_logger("ac_client"),"terminal has been shutdown!");
return false;
}
//
void send_goal(int tar){
//1.装载数据
Myaction::Goal Tar;
Tar.tar = tar;
//2.绑定回调函数
//const rclcpp_action::Client<based_interfaces::action::Myaction>::SendGoalOptions
rclcpp_action::Client<Myaction>::SendGoalOptions options;
options.feedback_callback = std::bind(&My_Client::feedback_callback,this,_1,_2);
options.goal_response_callback= std::bind(&My_Client::goal_res_callback,this,_1);
options.result_callback = std::bind(&My_Client::result_callback,this,_1);
//3.发送数据
auto future = client_->async_send_goal(Tar,options);
}
};
int main(int argc,char** argv){
rclcpp::init(argc,argv);
auto client = std::make_shared<My_Client>();
if(client->connect())
{
client->send_goal(atoi(argv[1]));
rclcpp::spin(client);
}
rclcpp::shutdown();
return 0;
}
3. 编译
在工作空间目录下执行:
colcon build --packages-select demo01_action_test
4. 运行动作客户端
首先注册环境变量:在工作空间目录下
. install/setup.bash
然后先运行服务端:
ros2 run demo01_action_test cpp01_server
再运行客户端代码(注意,对于本例来说,一定要传参)
ros2 run demo01_action_test cpp02_action 10
可以观察到以下输出: