Bootstrap

ROS2中,实车电脑端的动作通信

客户端:

/*
    需求:简单的模拟导航功能,向机器人发送一个前进N米的请求,机器人就会以 0.1m/s的速度前进,
         当与目标点的距离小于0.05m时,机器人就会停止运动,返回机器人的停止坐标,并且在此过程中,
         会连续反馈机器人与目标点之间的剩余距离。
    流程:
        1.包含头文件;
        2.初始化ROS2客户端;
        3.自定义节点类;
          3-1.创建参数服务客户端,连接到速度发布节点;
          3-2.创建订阅方,订阅机器人的里程计以获取机器人坐标;
          3-3.创建动作服务端,解析客户端的相关并生成响应。
        4.调用spin函数,并传入节点对象指针;
        5.资源释放。
*/
// 1.包含头文件;
// 包含头文件;
#include "rclcpp/rclcpp.hpp"
#include "rclcpp_action/rclcpp_action.hpp"
#include "myexer_interfaces/action/nav.hpp"
#include "nav_msgs/msg/odometry.hpp"

using myexer_interfaces::action::Nav;
using namespace std::placeholders;
using namespace std::chrono_literals;

// 自定义节点类;
class NavServer : public rclcpp::Node{
public:
    NavServer(): Node("nav_server_node_cpp"){
        RCLCPP_INFO(this->get_logger(), "NavServer服务端创建!}");
        last_x = last_y = 0.0;

        //创建参数客户端,连接小车的速度发布节点
        paramClient = std::make_shared<rclcpp::AsyncParametersClient>(this,"pub_vel_node_cpp");
        
        while(!paramClient->wait_for_service(5s))
        {
            if(!rclcpp::ok())
            {
                return;
            }

            RCLCPP_ERROR(this->get_logger(),"服务超时未连接");
        }

        RCLCPP_INFO(this->get_logger(),"已经成功连接速度发布节点了,可以进rqt动态设置线速度和角速度了!");

        sub_odom_ = this->create_subscription<nav_msgs::msg::Odometry>("odom",10,std::bind(&NavServer::on_timer,this,_1));

        //创建动作服务端
        nav_action_server_ = rclcpp_action::create_server<Nav>(
            /*NodeT node,
             const std::string &name,
            rclcpp_action::Server<ActionT>::GoalCallback handle_goal,
            rclcpp_action::Server<ActionT>::CancelCallback handle_cancel, 
            rclcpp_action::Server<ActionT>::AcceptedCallback handle_accepted,*/
            this,
            "nav",
            std::bind(&NavServer::handle_goal,this,_1,_2),
            std::bind(&NavServer::handle_cancel,this,_1),
            std::bind(&NavServer::handle_accepted,this,_1)

        );
        
    }

private:

    rclcpp_action::Server<Nav>::SharedPtr nav_action_server_;
    rclcpp::AsyncParametersClient::SharedPtr paramClient;
    rclcpp::Subscription<nav_msgs::msg::Odometry>::SharedPtr sub_odom_;
    double x,y;
    double last_x,last_y; // 每次导航时,机器人起点坐标

    //处理订阅到的里程计数据
    void on_timer(const nav_msgs::msg::Odometry &odom)
    {   
        x = odom.pose.pose.position.x;
        y = odom.pose.pose.position.y;
    }

    //using GoalCallback = std::function<GoalResponse(const GoalUUID &, std::shared_ptr<const typename ActionT::Goal>)>;
    //处理动作客户端请求
    rclcpp_action::GoalResponse handle_goal(const rclcpp_action::GoalUUID &goal_uuid,std::shared_ptr<const Nav::Goal> goal)
    {
        (void)goal_uuid;
        double x = goal->goal;
        if(x > 0)
        {
            RCLCPP_INFO(this->get_logger(),"小车将前进 %.2f 米",x);
        }else{
            RCLCPP_INFO(this->get_logger(),"输入数据小于0,不符合要求,请重新输入");
            return rclcpp_action::GoalResponse::REJECT; //表示拒绝
        }
        paramClient->set_parameters({rclcpp::Parameter("linear",0.2),rclcpp::Parameter("angular",0.0)});


        return rclcpp_action::GoalResponse::ACCEPT_AND_EXECUTE;    //这里代表同意
    }


    // using CancelCallback = std::function<CancelResponse(std::shared_ptr<ServerGoalHandle<ActionT>>)>;
    //处理取消请求
    rclcpp_action::CancelResponse handle_cancel(std::shared_ptr<rclcpp_action::ServerGoalHandle<Nav>> goal_handle)
    {
        (void)goal_handle;
        RCLCPP_INFO(this->get_logger(),"任务取消中......");

        paramClient->set_parameters({rclcpp::Parameter("linear",0.0),rclcpp::Parameter("angular",0.0)});

        return rclcpp_action::CancelResponse::REJECT;   //表示拒绝
    }

    void execute(std::shared_ptr<rclcpp_action::ServerGoalHandle<Nav>> goal_handle)
    {
        RCLCPP_INFO(this->get_logger(),"开始执行任务");
        //获取目标点
        float goal = goal_handle->get_goal()->goal;

        //连续反馈
        auto feedback = std::make_shared<Nav::Feedback>();

        //最终结果
        auto result = std::make_shared<Nav::Result>();

        //最终结果

        //1、设置连续反馈
        rclcpp::Rate rate(1.0);
        while(rclcpp::ok())
        {
            //检测任务是否被取消
            if(goal_handle->is_canceling())
            {
                result->x = x;   //最终结果x = 里程计x
                result->y = y;   //最终结y = 里程计y
                goal_handle->canceled(result);
                RCLCPP_INFO(this->get_logger(),"任务取消");

                last_x = x;   //机器人起点 = 里程计x
                last_y = y;   //机器人起点 = 里程计y
                paramClient->set_parameters({rclcpp::Parameter("linear",0.0),rclcpp::Parameter("angular",0.0)});
                return;
            }

            double distance = goal - (x-last_x); //x代表里程计 last_x代表初始距离,x-last_x等于走的距离,goal是客户端设置过来走多少米
            feedback->distance = distance;
            goal_handle->publish_feedback(feedback);

            if(distance <= 0.05)
            {
                break;
            }
            rate.sleep();

        }

        //2、设置最终反馈
        if(rclcpp::ok())
        {
            result->x = x;
            result->y = y;

            last_x = x;
            last_y = y;
            goal_handle->succeed(result);
            paramClient->set_parameters({rclcpp::Parameter("linear",0.0),rclcpp::Parameter("angular",0.0)});
            RCLCPP_INFO(this->get_logger(),"任务完成!");
        }

    }

    
    //  using AcceptedCallback = std::function<void (std::shared_ptr<ServerGoalHandle<ActionT>>)>;

    //处理连续反馈、最终响应   
    void handle_accepted(std::shared_ptr<rclcpp_action::ServerGoalHandle<Nav>> goal_handle)     //这里做的是一个多线程
    {
        std::thread{std::bind(&NavServer::execute,this,_1),goal_handle}.detach();
    }

  
};

int main(int argc, char * argv[]){
    // 初始化 ROS2 客户端;
    rclcpp::init(argc, argv);
    // 调用 spin 函数,并传入节点对象指针。
    rclcpp::spin(std::make_shared<NavServer>());
    // 释放资源;
    rclcpp::shutdown();
    return 0;
}

服务端:

/*
   需求:向导航动作服务端发送目标点数据,并处理服务端的响应数据。   需要像服务端提交数据,前进距离是浮点类型
   步骤:
       1.包含头文件;
       2.初始化 ROS2 客户端;
       3.定义节点类;    (处理终端提交的数据)
            3-1.创建动作客户端;   
            3-2.发送请求数据,并处理服务端响应;   //提供一个函数
            3-3.处理目标响应;          //处理服务端返回的数据是否合法
            3-4.处理响应的连续反馈;     //处理连续反馈
            3-5.处理最终响应。          //处理最终响应
            3-6 特定条件下发送取消请求   客户端按下取消  rclcpp::ok()
                                     发送取消任务请求   action_client_ ->async_cancel_goal
       4.调用spin函数,并传入节点对象指针;
       5.释放资源。
*/
// 1.包含头文件;

// 包含头文件;
#include "rclcpp/rclcpp.hpp"
#include "rclcpp_action/rclcpp_action.hpp"   //动作通信的头文件
#include "myexer_interfaces/action/nav.hpp"   //自定义类型

using namespace std::chrono_literals;
using namespace std::placeholders;
using myexer_interfaces::action::Nav;


// 包含头文件;
#include "rclcpp/rclcpp.hpp"

// 自定义节点类;
class NavClient : public rclcpp::Node{
public:
    NavClient(): Node("exe_nav_action_client"){
        RCLCPP_INFO(this->get_logger(), "NavClient动作客户端创建!}");

        //创建动作客户端
        nav_clienr = rclcpp_action::create_client<Nav>(this,"nav");
        
    }
    //发送数据请求
    void send_goal(float x)
    {
        //连接服务端,超时退出
        if(!nav_clienr->wait_for_action_server(5s))
        {
            RCLCPP_INFO(this->get_logger(),"服务连接失败");
            return;
        }

        //组织请求数据
        auto goal_msg = myexer_interfaces::action::Nav::Goal();
        goal_msg.goal = x;

        rclcpp_action::Client<Nav>::SendGoalOptions options;
        options.goal_response_callback = std::bind(&NavClient::goal_response_callback, this, _1);
        options.feedback_callback = std::bind(&NavClient::feedback_callback, this, _1, _2);
        options.result_callback = std::bind(&NavClient::result_callback, this, _1);

        //发送数据
        nav_clienr->async_send_goal(goal_msg,options);

    }

private:
    rclcpp_action::Client<Nav>::SharedPtr nav_clienr;

    //处理目标响应
     void goal_response_callback(rclcpp_action::ClientGoalHandle<myexer_interfaces::action::Nav>::SharedPtr goal_handle)
     {
        if(!goal_handle)
        {
            RCLCPP_INFO(this->get_logger(),"目标请求被服务器拒绝");
        }else{
            RCLCPP_INFO(this->get_logger(),"目标请求被接收");
            std::thread(&NavClient::cancel_goals,this,goal_handle).detach();
        }
     }
    void cancel_goals(rclcpp_action::ClientGoalHandle<myexer_interfaces::action::Nav>::SharedPtr goal_handle)
     {
        while(rclcpp::ok()){}
        nav_clienr->async_cancel_goal(goal_handle);   //处理取消请求
     }

         // 3-4.处理响应的连续反馈;
     void feedback_callback(rclcpp_action::ClientGoalHandle<myexer_interfaces::action::Nav>::SharedPtr goal_handle, 
        const std::shared_ptr<const myexer_interfaces::action::Nav::Feedback> feedback)
        {
            (void)goal_handle;
            RCLCPP_INFO(this->get_logger(),"距离目标还有 %.2f 米",feedback->distance);
        }


            // 3-5.处理最终响应。
     void result_callback(const rclcpp_action::ClientGoalHandle<myexer_interfaces::action::Nav>::WrappedResult & result)
     {
        if(result.code == rclcpp_action::ResultCode::SUCCEEDED)
        {
            RCLCPP_INFO(this->get_logger(),"小车的最终坐为:(%.2f,%.2f)",result.result->x,result.result->y);
        }
        else{
            RCLCPP_INFO(this->get_logger(),"异常");
        }
        rclcpp::shutdown();
     }

    



};

int main(int argc, char * argv[]){
    if(argc != 2)
    {
        RCLCPP_ERROR(rclcpp::get_logger("rclcpp"),"输入错误,请输入一个数");
    }
    // 初始化 ROS2 客户端;
    rclcpp::init(argc, argv);
    // 调用 spin 函数,并传入节点对象指针。
    auto client = std::make_shared<NavClient>();
    
    client ->send_goal(atof(argv[1]));


    rclcpp::spin(client);
    rclcpp::shutdown();
    return 0;
}

;