Program Listing for File service_state.hpp

Return to documentation for file (include/yasmin_ros/service_state.hpp)

// Copyright (C) 2023 Miguel Ángel González Santamarta
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <https://www.gnu.org/licenses/>.

#ifndef YASMIN_ROS__SERVICE_STATE_HPP
#define YASMIN_ROS__SERVICE_STATE_HPP

#include <functional>
#include <memory>
#include <set>
#include <string>

#include "rclcpp/rclcpp.hpp"

#include "yasmin/blackboard/blackboard.hpp"
#include "yasmin/state.hpp"
#include "yasmin_ros/basic_outcomes.hpp"
#include "yasmin_ros/yasmin_node.hpp"

namespace yasmin_ros {

template <typename ServiceT> class ServiceState : public yasmin::State {
  using Request = typename ServiceT::Request::SharedPtr;
  using Response = typename ServiceT::Response::SharedPtr;

  using CreateRequestHandler =
      std::function<Request(std::shared_ptr<yasmin::blackboard::Blackboard>)>;
  using ResponseHandler = std::function<std::string(
      std::shared_ptr<yasmin::blackboard::Blackboard>, Response)>;

public:
  ServiceState(std::string srv_name,
               CreateRequestHandler create_request_handler,
               std::set<std::string> outcomes, int timeout = -1.0)
      : ServiceState(srv_name, create_request_handler, outcomes, nullptr,
                     timeout) {}

  ServiceState(std::string srv_name,
               CreateRequestHandler create_request_handler,
               std::set<std::string> outcomes, ResponseHandler response_handler,
               int timeout = -1.0)
      : ServiceState(nullptr, srv_name, create_request_handler, outcomes,
                     response_handler, timeout) {}

  ServiceState(const rclcpp::Node::SharedPtr &node, std::string srv_name,
               CreateRequestHandler create_request_handler,
               std::set<std::string> outcomes, ResponseHandler response_handler,
               int timeout = -1.0)
      : State({}), srv_name(srv_name), timeout(timeout) {

    this->outcomes = {basic_outcomes::SUCCEED, basic_outcomes::ABORT};

    if (this->timeout >= 0) {
      this->outcomes.insert(basic_outcomes::TIMEOUT);
    }

    if (!outcomes.empty()) {
      this->outcomes.insert(outcomes.begin(), outcomes.end());
    }

    // Assign the appropriate ROS 2 node
    if (node == nullptr) {
      this->node_ = YasminNode::get_instance();
    } else {
      this->node_ = node;
    }

    // Create a service client
    this->service_client =
        this->node_->template create_client<ServiceT>(srv_name);

    // Set the request and response handlers
    this->create_request_handler = create_request_handler;
    this->response_handler = response_handler;

    // Validate request handler
    if (this->create_request_handler == nullptr) {
      throw std::invalid_argument("create_request_handler is needed");
    }
  }

  std::string
  execute(std::shared_ptr<yasmin::blackboard::Blackboard> blackboard) override {

    Request request = this->create_request(blackboard);

    // Wait for the service to become available
    RCLCPP_INFO(this->node_->get_logger(), "Waiting for service '%s'",
                this->srv_name.c_str());
    bool srv_available = this->service_client->wait_for_service(
        std::chrono::duration<int64_t, std::ratio<1>>(this->timeout));

    if (!srv_available) {
      RCLCPP_WARN(this->node_->get_logger(),
                  "Timeout reached, service '%s' is not available",
                  this->srv_name.c_str());
      return basic_outcomes::TIMEOUT;
    }

    // Send the service request
    RCLCPP_INFO(this->node_->get_logger(), "Sending request to service '%s'",
                this->srv_name.c_str());
    auto future = this->service_client->async_send_request(request);

    // Wait for the response
    future.wait();
    Response response = future.get();

    if (response) {
      if (response_handler != nullptr) {
        std::string outcome = this->response_handler(blackboard, response);
        return outcome;
      }
      return basic_outcomes::SUCCEED;
    } else {
      return basic_outcomes::ABORT;
    }
  }

private:
  rclcpp::Node::SharedPtr node_;
  std::shared_ptr<rclcpp::Client<ServiceT>> service_client;
  CreateRequestHandler create_request_handler;
  ResponseHandler response_handler;
  std::string srv_name;
  int timeout;

  Request
  create_request(std::shared_ptr<yasmin::blackboard::Blackboard> blackboard) {
    return this->create_request_handler(blackboard);
  }
};

} // namespace yasmin_ros

#endif // YASMIN_ROS__SERVICE_STATE_HPP