Program Listing for File ActionClient.hpp

Return to documentation for file (include/soar_ros/ActionClient.hpp)

// Copyright 2024 Moritz Schmidt
//
// 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.

#ifndef SOAR_ROS__ACTION_CLIENT_HPP_
#define SOAR_ROS__ACTION_CLIENT_HPP_

#include <memory>
#include <string>
#include <thread>
#include <atomic>

#include <rclcpp/rclcpp.hpp>
#include <rclcpp_action/rclcpp_action.hpp>
#include <sml_Client.h>

#include "Interface.hpp"
#include "SafeQueue.hpp"

namespace soar_ros
{

template<typename ActionT,
  typename pGoalMsg = typename ActionT::Goal::SharedPtr,
  typename pFeedbackMsg = typename ActionT::Feedback::SharedPtr,
  typename wrappedResultMsg = typename rclcpp_action::ClientGoalHandle<ActionT>::WrappedResult
>
class ActionClient
  : public Output<pGoalMsg>,
  public Input<bool>,
  public Input<pFeedbackMsg>,
  public Input<wrappedResultMsg>,
  public Interface
{
public:
  using GoalHandle = rclcpp_action::ClientGoalHandle<ActionT>;

  explicit ActionClient(
    sml::Agent * agent,
    rclcpp::Node::SharedPtr node,
    const std::string & action_name)
  : m_pAgent(agent), m_node(node), m_topic(action_name), isRunning(true)
  {
    client_ptr_ = rclcpp_action::create_client<ActionT>(node, action_name);
    m_action_thread = std::thread(&ActionClient::run, this);
  }

  virtual ~ActionClient()
  {
    isRunning.store(false);
    if (m_action_thread.joinable()) {
      m_action_thread.join();
    }
  }

  void send_goal_from_soar(const pGoalMsg & soar_goal)
  {
    RCLCPP_INFO(m_node->get_logger(), "Sending goal to action server: %s", m_topic.c_str());

    auto options = typename rclcpp_action::Client<ActionT>::SendGoalOptions();

    // Goal response callback
    options.goal_response_callback = [this](std::shared_ptr<GoalHandle> goal_handle) {
        if (!goal_handle) {
          this->template Input<bool>::m_r2sQueue.push(false);
        } else {
          this->template Input<bool>::m_r2sQueue.push(true);
        }
      };

    // Feedback callback
    options.feedback_callback = [this](GoalHandle::SharedPtr,
      const std::shared_ptr<const typename ActionT::Feedback> feedback) {
        // Create a non-const copy for the queue
        auto feedback_copy = std::make_shared<typename ActionT::Feedback>(*feedback);
        this->template Input<pFeedbackMsg>::m_r2sQueue.push(feedback_copy);
      };

    // Result callback
    options.result_callback = [this](const wrappedResultMsg & wrapped) {
        this->template Input<wrappedResultMsg>::m_r2sQueue.push(wrapped);
      };

    client_ptr_->async_send_goal(*soar_goal, options);
  }

  // Interface implementation
  std::string getTopic() override {return m_topic;}
  sml::Agent * getAgent() override {return m_pAgent;}

protected:
  using Input<pFeedbackMsg>::parse;
  using Input<wrappedResultMsg>::parse;
  using Input<bool>::parse;

  virtual pGoalMsg parse(sml::Identifier * id) = 0;
  virtual void parse(pFeedbackMsg msg) = 0;
  virtual void parse(wrappedResultMsg msg) = 0;

  void parse(bool msg) override
  {
    sml::Identifier * il = this->getAgent()->GetInputLink();
    sml::Identifier * ros_action_id = il->CreateIdWME(this->m_topic.c_str());
    ros_action_id->CreateStringWME("status", msg ? "accepted" : "rejected");
  }

private:
  sml::Agent * m_pAgent;
  rclcpp::Node::SharedPtr m_node;
  std::string m_topic;
  typename rclcpp_action::Client<ActionT>::SharedPtr client_ptr_;

  std::thread m_action_thread;
  std::atomic<bool> isRunning;

  // Background thread to process goals
  void run()
  {
    while (isRunning.load()) {
      // Wait for action server
      if (!client_ptr_->wait_for_action_server(std::chrono::seconds(1))) {
        if (!rclcpp::ok()) {
          RCLCPP_ERROR(m_node->get_logger(), "Interrupted while waiting for action server");
          isRunning.store(false);
          break;
        }
        continue;
      }

      // Process any pending goals
      auto goal = this->template Output<pGoalMsg>::m_s2rQueue.tryPop();
      if (goal.has_value()) {
        send_goal_from_soar(goal.value());
      }

      std::this_thread::sleep_for(std::chrono::milliseconds(10));
    }
  }
};

}  // namespace soar_ros

#endif  // SOAR_ROS__ACTION_CLIENT_HPP_