Robot Operating System——Action通信机制的服务端

《Robot Operating System——Action通信机制概念及Client端》一文中,我们介绍了Action客户端的主要流程。本文我们将介绍Action服务端的编写。

回顾下Action的构成:

  1. 目标(Goal):这是由Action客户端(Client)发送给Action服务器(Server)的请求,指示服务器需要执行的具体任务。例如,在控制一个机器人旋转的任务中,目标可能是指定机器人需要旋转的角度。
  2. 反馈(Feedback):在执行目标的过程中,Action服务器会定期向客户端发送反馈,以报告当前的执行状态和进度。这种反馈机制使得客户端能够实时了解任务的执行情况,并在必要时进行干预或调整。
  3. 结果(Result):当目标执行完成后,Action服务器会向客户端发送一个结果,表明任务的最终执行状态。这个结果可以是成功完成、失败或者部分完成等状态信息。

我们看到Action客户端通过设置与上述组成对应的回调函数来实现整体功能。

但是Action的服务端则不是如此。它也由三部分组成,分别是:

  1. 处理Client发送请求的回调。用于表示是“接受”还是“拒绝”该请求。
  2. 处理Client发送“终止”请求任务的回调。Action服务端在处理任务时,是可以接收客户端发送的“终止”指令的。这个回调可以“接受”还是“拒绝”该请求。
  3. 处理执行任务的回调。这个就是主要的处理逻辑。

我们通过action_tutorials/action_tutorials_cpp/src/fibonacci_action_server.cpp看下其具体实现。

回调

接受或者拒绝请求

class FibonacciActionServer : public rclcpp::Node
{
public:
  using Fibonacci = action_tutorials_interfaces::action::Fibonacci;
  using GoalHandleFibonacci = rclcpp_action::ServerGoalHandle<Fibonacci>;

  ACTION_TUTORIALS_CPP_PUBLIC
  explicit FibonacciActionServer(const rclcpp::NodeOptions & options = rclcpp::NodeOptions())
  : Node("fibonacci_action_server", options)
  {
    using namespace std::placeholders;

    auto handle_goal = [this](
      const rclcpp_action::GoalUUID & uuid,
      std::shared_ptr<const Fibonacci::Goal> goal)
      {
        (void)uuid;
        RCLCPP_INFO(this->get_logger(), "Received goal request with order %d", goal->order);
        // The Fibonacci action uses int32 for the return of sequences, which means it can only hold
        // 2^31-1 (2147483647) before wrapping negative in two's complement. Based on empirical
        // tests, that means that an order of > 46 will cause wrapping, so we don't allow that here.
        if (goal->order > 46) {
          return rclcpp_action::GoalResponse::REJECT;
        }
        return rclcpp_action::GoalResponse::ACCEPT_AND_EXECUTE;
      };

本例中,handle_goal会根据客户端传递过来的order值来判断是否“接受”或“拒绝”处理该请求。
如果拒绝就返回rclcpp_action::GoalResponse::REJECT;如果接受,可以返回rclcpp_action::GoalResponse::ACCEPT_AND_EXECUTE或者rclcpp_action::GoalResponse::ACCEPT_AND_DEFER。
ACCEPT_AND_EXECUTE表示该回调后会立即调用执行任务的回调;ACCEPT_AND_DEFER表示过会儿再调用。

执行任务的回调

    auto handle_accepted = [this](
      const std::shared_ptr<GoalHandleFibonacci> goal_handle)
      {
        // this needs to return quickly to avoid blocking the executor,
        // so we declare a lambda function to be called inside a new thread
        auto execute_in_thread = [this, goal_handle]() {return this->execute(goal_handle);};
        std::thread{execute_in_thread}.detach();
      };

由于我们处理任务的回调执行时间很长(它内部要Sleep一段时间),所以为了让该回调可以立即返回,不阻碍后续流程,于是主流程会放在线程中执行。

  ACTION_TUTORIALS_CPP_LOCAL
  void execute(const std::shared_ptr<GoalHandleFibonacci> goal_handle)
  {
    RCLCPP_INFO(this->get_logger(), "Executing goal");
    rclcpp::Rate loop_rate(1);
    const auto goal = goal_handle->get_goal();
    auto feedback = std::make_shared<Fibonacci::Feedback>();
    auto & sequence = feedback->partial_sequence;
    sequence.push_back(0);
    sequence.push_back(1);
    auto result = std::make_shared<Fibonacci::Result>();

我们先构建Action客户端2个回调函数需要的参数:

  • feedback用于告知客户端FeedbackCallback回调结果。
  • result用于告知客户端ResultCallback回调结果。
    for (int i = 1; (i < goal->order) && rclcpp::ok(); ++i) {
      // Check if there is a cancel request
      if (goal_handle->is_canceling()) {
        result->sequence = sequence;
        goal_handle->canceled(result);
        RCLCPP_INFO(this->get_logger(), "Goal canceled");
        return;
      }
      // Update sequence
      sequence.push_back(sequence[i] + sequence[i - 1]);
      // Publish feedback
      goal_handle->publish_feedback(feedback);
      RCLCPP_INFO(this->get_logger(), "Publish feedback");

      loop_rate.sleep();
    }

然后For循环中会不停计算斐波那契数列。在计算过程中,通过GoalHandleFibonacci::is_canceling检查是否服务端接受了客户端“终止”任务的请求。如果终止,则会通过GoalHandleFibonacci::canceled返回Result。这样客户端的ResultCallback回调会收到结果,且状态是rclcpp_action::ResultCode::CANCELED。
在这里插入图片描述
如果没有终止,就会通过GoalHandleFibonacci::publish_feedback来将中间过程返回给客户端。这样客户端的FeedbackCallback回调会收到响应。
在这里插入图片描述
如果任务执行结束,就调用GoalHandleFibonacci::succeed来返回最终结果。

    // Check if goal is done
    if (rclcpp::ok()) {
      result->sequence = sequence;
      goal_handle->succeed(result);
      RCLCPP_INFO(this->get_logger(), "Goal succeeded");
    }
  }

终止任务回调

    auto handle_cancel = [this](
      const std::shared_ptr<GoalHandleFibonacci> goal_handle)
      {
        RCLCPP_INFO(this->get_logger(), "Received request to cancel goal");
        (void)goal_handle;
        return rclcpp_action::CancelResponse::ACCEPT;
      };

本例中,我们直接接受了客户端的“终止”请求,返回了rclcpp_action::CancelResponse::ACCEPT。现实中,我们还可以通过rclcpp_action::CancelResponse::REJECT来拒绝“终止”请求,让任务继续进行。

如果这个回调我们返回了rclcpp_action::CancelResponse::ACCEPT,则GoalHandleFibonacci::is_canceling会返回true。这样主处理流程会做相应的动作来“终止”任务。

创建服务

最后我们只要将上述3个回调传递给rclcpp_action::create_server来构造对象即可。

    this->action_server_ = rclcpp_action::create_server<Fibonacci>(
      this,
      "fibonacci",
      handle_goal,
      handle_cancel,
      handle_accepted);
  }

private:
  rclcpp_action::Server<Fibonacci>::SharedPtr action_server_;

完整代码

// Copyright 2019 Open Source Robotics Foundation, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#include <memory>

#include "action_tutorials_interfaces/action/fibonacci.hpp"
#include "rclcpp/rclcpp.hpp"
#include "rclcpp_action/rclcpp_action.hpp"
#include "rclcpp_components/register_node_macro.hpp"

#include "action_tutorials_cpp/visibility_control.h"

namespace action_tutorials_cpp
{
class FibonacciActionServer : public rclcpp::Node
{
public:
  using Fibonacci = action_tutorials_interfaces::action::Fibonacci;
  using GoalHandleFibonacci = rclcpp_action::ServerGoalHandle<Fibonacci>;

  ACTION_TUTORIALS_CPP_PUBLIC
  explicit FibonacciActionServer(const rclcpp::NodeOptions & options = rclcpp::NodeOptions())
  : Node("fibonacci_action_server", options)
  {
    using namespace std::placeholders;

    auto handle_goal = [this](
      const rclcpp_action::GoalUUID & uuid,
      std::shared_ptr<const Fibonacci::Goal> goal)
      {
        (void)uuid;
        RCLCPP_INFO(this->get_logger(), "Received goal request with order %d", goal->order);
        // The Fibonacci action uses int32 for the return of sequences, which means it can only hold
        // 2^31-1 (2147483647) before wrapping negative in two's complement. Based on empirical
        // tests, that means that an order of > 46 will cause wrapping, so we don't allow that here.
        if (goal->order > 46) {
          return rclcpp_action::GoalResponse::REJECT;
        }
        return rclcpp_action::GoalResponse::ACCEPT_AND_EXECUTE;
      };

    auto handle_cancel = [this](
      const std::shared_ptr<GoalHandleFibonacci> goal_handle)
      {
        RCLCPP_INFO(this->get_logger(), "Received request to cancel goal");
        (void)goal_handle;
        return rclcpp_action::CancelResponse::ACCEPT;
      };

    auto handle_accepted = [this](
      const std::shared_ptr<GoalHandleFibonacci> goal_handle)
      {
        // this needs to return quickly to avoid blocking the executor,
        // so we declare a lambda function to be called inside a new thread
        auto execute_in_thread = [this, goal_handle]() {return this->execute(goal_handle);};
        std::thread{execute_in_thread}.detach();
      };

    this->action_server_ = rclcpp_action::create_server<Fibonacci>(
      this,
      "fibonacci",
      handle_goal,
      handle_cancel,
      handle_accepted);
  }

private:
  rclcpp_action::Server<Fibonacci>::SharedPtr action_server_;

  ACTION_TUTORIALS_CPP_LOCAL
  void execute(const std::shared_ptr<GoalHandleFibonacci> goal_handle)
  {
    RCLCPP_INFO(this->get_logger(), "Executing goal");
    rclcpp::Rate loop_rate(1);
    const auto goal = goal_handle->get_goal();
    auto feedback = std::make_shared<Fibonacci::Feedback>();
    auto & sequence = feedback->partial_sequence;
    sequence.push_back(0);
    sequence.push_back(1);
    auto result = std::make_shared<Fibonacci::Result>();

    for (int i = 1; (i < goal->order) && rclcpp::ok(); ++i) {
      // Check if there is a cancel request
      if (goal_handle->is_canceling()) {
        result->sequence = sequence;
        goal_handle->canceled(result);
        RCLCPP_INFO(this->get_logger(), "Goal canceled");
        return;
      }
      // Update sequence
      sequence.push_back(sequence[i] + sequence[i - 1]);
      // Publish feedback
      goal_handle->publish_feedback(feedback);
      RCLCPP_INFO(this->get_logger(), "Publish feedback");

      loop_rate.sleep();
    }

    // Check if goal is done
    if (rclcpp::ok()) {
      result->sequence = sequence;
      goal_handle->succeed(result);
      RCLCPP_INFO(this->get_logger(), "Goal succeeded");
    }
  }
};  // class FibonacciActionServer

}  // namespace action_tutorials_cpp

RCLCPP_COMPONENTS_REGISTER_NODE(action_tutorials_cpp::FibonacciActionServer)

总结

  • Action服务端主要由3个回调构成:
    • 是否接受执行任务请求的handle_goal;
    • 是否接受终止任务请求的handle_cancel;
    • handle_goal中返回rclcpp_action::GoalResponse::ACCEPT_AND_EXECUTE或者rclcpp_action::GoalResponse::ACCEPT_AND_DEFER后执行的handle_accepted。
  • Action客户端主要由3个回调构成:
    • 请求Action服务端handle_goal回调返回结果的goal_response_callback。用于知晓服务端是接受了还是拒绝了该任务请求。
    • 用于接收Action服务端在handle_accepted中通过调用GoalHandleFibonacci::publish_feedback回传过程的 feedback_callback。
    • 用于接收接收Action服务端在handle_accepted中通过调用GoalHandleFibonacci::canceled或者GoalHandleFibonacci::succeed的result_callback。
  • Action服务端的handle_goal返回结果会决定了handle_accepted是否执行。
  • Action服务端的handle_cancel返回结果会决定GoalHandleFibonacci::is_canceling的返回结果,进而可以干涉主任务流程的处理逻辑。
  • 20
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

breaksoftware

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值