ROS2: 手把手教你动作通信案例实现(客户端篇C++)

动作通信客户端概要

 在动作通信中,客户端主要完成三个任务:

  1. 目标请求发送与请求结果接收。
  2. 反馈数据接收。
  3. 最终结果接收。
    与服务端类似的,这三个任务也分别对应了三个回调函数:
//请求响应结果回调: 
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需要绑定三个回调函数,如下图:
在这里插入图片描述
 当然,具体的函数实现在上一小结以及介绍了,这里提一下这三个函数编写的注意点:

  1. 全都无返回值。
  2. 传入的参数的类型大部分都在命名空间rclcpp_action::ClientGoalHandle<Myaction>下面,并且只有feedback有两个传入值。
  3. 对于goal_callback,需要根据句柄判断目标是否被接受。
  4. 对于feedbcak_callback,可以处理反馈结果。例如打印。
  5. 对于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

可以观察到以下输出:
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值