Program Listing for File aerial_platform.cpp

Return to documentation for file (src/aerial_platform.cpp)

// Copyright 2023 Universidad Politécnica de Madrid
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
//    * Redistributions of source code must retain the above copyright
//      notice, this list of conditions and the following disclaimer.
//
//    * Redistributions in binary form must reproduce the above copyright
//      notice, this list of conditions and the following disclaimer in the
//      documentation and/or other materials provided with the distribution.
//
//    * Neither the name of the Universidad Politécnica de Madrid nor the names of its
//      contributors may be used to endorse or promote products derived from
//      this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.

/*!*******************************************************************************************
 *  \file       aerial_platform.cpp
 *  \brief      Aerostack2 Aerial Platformm class implementation file.
 *  \authors    Miguel Fernandez Cortizas
 *  \copyright  Copyright (c) 2022 Universidad Politécnica de Madrid
 *              All Rights Reserved
 *
 ********************************************************************************/

#include "as2_core/aerial_platform.hpp"

namespace as2
{
void AerialPlatform::initialize()
{
  {
    platform_info_msg_.armed = false;
    platform_info_msg_.offboard = false;
    platform_info_msg_.connected = true;  // TODO(miferco97): Check if connected
    platform_info_msg_.current_control_mode.control_mode = as2_msgs::msg::ControlMode::UNSET;

    this->declare_parameter<float>("cmd_freq", 100.0);
    this->declare_parameter<float>("info_freq", 10.0);

    try {
      this->declare_parameter<std::string>("control_modes_file");
    } catch (const rclcpp::ParameterTypeException & e) {
      RCLCPP_FATAL(
        this->get_logger(), "Launch argument <control_modes_file> not defined or malformed: %s",
        e.what());
      this->~AerialPlatform();
    }

    this->get_parameter("cmd_freq", cmd_freq_);
    this->get_parameter("info_freq", info_freq_);

    std::string control_modes_file;
    this->get_parameter("control_modes_file", control_modes_file);

    this->loadControlModes(control_modes_file);

    trajectory_command_sub_ = this->create_subscription<as2_msgs::msg::TrajectoryPoint>(
      this->generate_global_name(as2_names::topics::actuator_command::trajectory),
      as2_names::topics::actuator_command::qos,
      [this](const as2_msgs::msg::TrajectoryPoint::ConstSharedPtr msg) {
        this->command_trajectory_msg_ = *msg.get();
        has_new_references_ = true;
      });
    pose_command_sub_ = this->create_subscription<geometry_msgs::msg::PoseStamped>(
      this->generate_global_name(as2_names::topics::actuator_command::pose),
      as2_names::topics::actuator_command::qos,
      [this](const geometry_msgs::msg::PoseStamped::ConstSharedPtr msg) {
        this->command_pose_msg_ = *msg.get();
        has_new_references_ = true;
      });

    twist_command_sub_ = this->create_subscription<geometry_msgs::msg::TwistStamped>(
      this->generate_global_name(as2_names::topics::actuator_command::twist),
      as2_names::topics::actuator_command::qos,
      [this](const geometry_msgs::msg::TwistStamped::ConstSharedPtr msg) {
        this->command_twist_msg_ = *msg.get();
        has_new_references_ = true;
      });
    thrust_command_sub_ = this->create_subscription<as2_msgs::msg::Thrust>(
      this->generate_global_name(as2_names::topics::actuator_command::thrust),
      as2_names::topics::actuator_command::qos,
      [this](const as2_msgs::msg::Thrust::ConstSharedPtr msg) {
        this->command_thrust_msg_ = *msg.get();
        has_new_references_ = true;
      });

    alert_event_sub_ = this->create_subscription<as2_msgs::msg::AlertEvent>(
      this->generate_global_name(as2_names::topics::global::alert_event),
      as2_names::topics::global::qos,
      std::bind(&AerialPlatform::alertEventCallback, this, std::placeholders::_1));

    set_platform_mode_srv_ = this->create_service<as2_msgs::srv::SetControlMode>(
      as2_names::services::platform::set_platform_control_mode,
      std::bind(
        &AerialPlatform::setPlatformControlModeSrvCall, this,
        std::placeholders::_1,  // Corresponds to the 'request'  input
        std::placeholders::_2   // Corresponds to the 'response' input
    ));

    set_arming_state_srv_ = this->create_service<std_srvs::srv::SetBool>(
      as2_names::services::platform::set_arming_state,
      std::bind(
        &AerialPlatform::setArmingStateSrvCall, this,
        std::placeholders::_1,  // Corresponds to the 'request'  input
        std::placeholders::_2   // Corresponds to the 'response' input
    ));

    set_offboard_mode_srv_ = this->create_service<std_srvs::srv::SetBool>(
      as2_names::services::platform::set_offboard_mode,
      std::bind(
        &AerialPlatform::setOffboardModeSrvCall, this,
        std::placeholders::_1,  // Corresponds to the 'request'  input
        std::placeholders::_2   // Corresponds to the 'response' input
    ));

    platform_takeoff_srv_ = this->create_service<std_srvs::srv::SetBool>(
      as2_names::services::platform::takeoff,
      std::bind(
        &AerialPlatform::platformTakeoffSrvCall, this,
        std::placeholders::_1,  // Corresponds to the 'request'  input
        std::placeholders::_2   // Corresponds to the 'response' input
    ));

    platform_land_srv_ = this->create_service<std_srvs::srv::SetBool>(
      as2_names::services::platform::land,
      std::bind(
        &AerialPlatform::platformLandSrvCall, this,
        std::placeholders::_1,  // Corresponds to the 'request'  input
        std::placeholders::_2   // Corresponds to the 'response' input
    ));

    list_control_modes_srv_ = this->create_service<as2_msgs::srv::ListControlModes>(
      as2_names::services::platform::list_control_modes,
      std::bind(
        &AerialPlatform::listControlModesSrvCall, this,
        std::placeholders::_1,  // Corresponds to the 'request'  input
        std::placeholders::_2   // Corresponds to the 'response' input
    ));

    platform_info_pub_ = this->create_publisher<as2_msgs::msg::PlatformInfo>(
      this->generate_global_name(as2_names::topics::platform::info),
      as2_names::topics::platform::qos);

    platform_info_timer_ = this->create_timer(
      std::chrono::duration<double>(1.0f / info_freq_),
      std::bind(&AerialPlatform::publishPlatformInfo, this));

    platform_cmd_timer_ = this->create_timer(
      std::chrono::duration<double>(1.0f / cmd_freq_),
      std::bind(&AerialPlatform::sendCommand, this));
  }
}

AerialPlatform::AerialPlatform(const rclcpp::NodeOptions & options)
: as2::Node(std::string("platform"), options), state_machine_(as2::PlatformStateMachine(this))
{
  initialize();
}
AerialPlatform::AerialPlatform(const std::string & ns, const rclcpp::NodeOptions & options)
: as2::Node(std::string("platform"), ns, options), state_machine_(as2::PlatformStateMachine(this))
{
  initialize();
}

bool AerialPlatform::setArmingState(bool state)
{
  if (state == platform_info_msg_.armed && state == true) {
    RCLCPP_WARN(this->get_logger(), "UAV is already armed");
  } else if (state == platform_info_msg_.armed && state == false) {
    RCLCPP_WARN(this->get_logger(), "UAV is already disarmed");
  } else {
    if (ownSetArmingState(state)) {
      platform_info_msg_.armed = state;
      if (state) {
        handleStateMachineEvent(as2_msgs::msg::PlatformStateMachineEvent::ARM);
      } else {
        handleStateMachineEvent(as2_msgs::msg::PlatformStateMachineEvent::DISARM);
      }
      return true;
    }
    RCLCPP_WARN(this->get_logger(), "Unable to set arming state %s", state ? "ON" : "OFF");
  }
  return false;
}

bool AerialPlatform::setOffboardControl(bool offboard)
{
  if (offboard == platform_info_msg_.offboard && offboard == true) {
    RCLCPP_WARN(this->get_logger(), "UAV is already in OFFBOARD mode");
  } else if (offboard == platform_info_msg_.offboard && offboard == false) {
    RCLCPP_WARN(this->get_logger(), "UAV is already in MANUAL mode");
  } else {
    if (ownSetOffboardControl(offboard)) {
      platform_info_msg_.offboard = offboard;
      return true;
    }
    RCLCPP_WARN(this->get_logger(), "Unable to set offboard mode %s", offboard ? "ON" : "OFF");
  }
  return false;
}

bool AerialPlatform::setPlatformControlMode(const as2_msgs::msg::ControlMode & msg)
{
  if (ownSetPlatformControlMode(msg)) {
    has_new_references_ = false;
    platform_info_msg_.current_control_mode = msg;
    return true;
  }
  RCLCPP_ERROR(this->get_logger(), "Unable to set control mode %d", msg.control_mode);
  return false;
}

bool AerialPlatform::takeoff()
{
  // TODO(miferco97): Implement STATE MACHINE check
  if (ownTakeoff()) {
    handleStateMachineEvent(as2_msgs::msg::PlatformStateMachineEvent::TOOK_OFF);
    return true;
  }
  RCLCPP_ERROR(this->get_logger(), "Unable to takeoff");
  return false;
}

bool AerialPlatform::land()
{
  // TODO(miferco97): Implement STATE MACHINE check
  if (ownLand()) {
    handleStateMachineEvent(as2_msgs::msg::PlatformStateMachineEvent::LANDED);
    return true;
  }
  RCLCPP_ERROR(this->get_logger(), "Unable to land");
  return false;
}

void AerialPlatform::alertEvent(const as2_msgs::msg::AlertEvent & msg)
{
  if (msg.alert > 0) {
    return;
  }
  if (!msg.description.empty()) {
    RCLCPP_WARN(this->get_logger(), "Alert event received: %s", msg.description.c_str());
  }
  switch (msg.alert) {
    case as2_msgs::msg::AlertEvent::KILL_SWITCH: {
        state_machine_.processEvent(as2_msgs::msg::PlatformStateMachineEvent::EMERGENCY);
        RCLCPP_WARN(this->get_logger(), "KILL SWITCH ACTIVATED");
        ownKillSwitch();
      } break;
    case as2_msgs::msg::AlertEvent::EMERGENCY_HOVER: {
        state_machine_.processEvent(as2_msgs::msg::PlatformStateMachineEvent::EMERGENCY);
        RCLCPP_WARN(this->get_logger(), "EMERGENCY HOVER ACTIVATED");
        ownStopPlatform();
      } break;
    default:
      break;
  }
}

void AerialPlatform::sendCommand()
{
  auto & clk = *this->get_clock();
  if (!isControlModeSettled()) {
    RCLCPP_DEBUG_THROTTLE(
      this->get_logger(), clk, 5000, "Platform control mode is not settled yet");
    return;
  }
  if (!getConnectedStatus()) {
    RCLCPP_DEBUG_THROTTLE(this->get_logger(), clk, 5000, "Platform is not connected");
    return;
  } else if (!getArmingState()) {
    RCLCPP_DEBUG_THROTTLE(this->get_logger(), clk, 5000, "Platform is not armed yet");
    return;
  } else if (!getOffboardMode()) {
    RCLCPP_DEBUG_THROTTLE(this->get_logger(), clk, 5000, "Platform is not in offboard mode");
    return;
  }

  if (state_machine_.getState().state == as2_msgs::msg::PlatformStatus::EMERGENCY) {
    RCLCPP_WARN_THROTTLE(this->get_logger(), clk, 1000, "SEND PLATFORM STOP COMMAND");
    ownStopPlatform();
  } else if (!ownSendCommand()) {
    RCLCPP_DEBUG_THROTTLE(this->get_logger(), clk, 5000, "Platform command failed");
  }
}  // namespace as2

void AerialPlatform::loadControlModes(const std::string & filename)
{
  std::vector<std::string> modes = as2::yaml::find_tag_in_yaml_file(filename, "available_modes");

  for (std::vector<std::string>::iterator it = modes.begin(); it != modes.end(); ++it) {
    uint8_t m = as2::yaml::parse_uint_from_string(it->c_str());
    as2::control_mode::printControlMode(m);
    available_control_modes_.emplace_back(m);
  }
}

// Services Callbacks
void AerialPlatform::setPlatformControlModeSrvCall(
  const std::shared_ptr<as2_msgs::srv::SetControlMode::Request> request,
  std::shared_ptr<as2_msgs::srv::SetControlMode::Response> response)
{
  response->success = setPlatformControlMode(request->control_mode);
}

void AerialPlatform::setOffboardModeSrvCall(
  const std::shared_ptr<std_srvs::srv::SetBool::Request> request,
  std::shared_ptr<std_srvs::srv::SetBool::Response> response)
{
  response->success = setOffboardControl(request->data);
}

void AerialPlatform::setArmingStateSrvCall(
  const std::shared_ptr<std_srvs::srv::SetBool::Request> request,
  std::shared_ptr<std_srvs::srv::SetBool::Response> response)
{
  response->success = setArmingState(request->data);
}

void AerialPlatform::platformTakeoffSrvCall(
  const std::shared_ptr<std_srvs::srv::SetBool::Request> request,
  std::shared_ptr<std_srvs::srv::SetBool::Response> response)
{
  (void)request;
  response->success = takeoff();
}

void AerialPlatform::platformLandSrvCall(
  const std::shared_ptr<std_srvs::srv::SetBool::Request> request,
  std::shared_ptr<std_srvs::srv::SetBool::Response> response)
{
  (void)request;
  response->success = land();
}

void AerialPlatform::listControlModesSrvCall(
  const std::shared_ptr<as2_msgs::srv::ListControlModes::Request> request,
  std::shared_ptr<as2_msgs::srv::ListControlModes::Response> response)
{
  (void)request;
  response->control_modes = this->available_control_modes_;
  response->source = "Platform";
}

void AerialPlatform::alertEventCallback(const as2_msgs::msg::AlertEvent::SharedPtr msg)
{
  alertEvent(*msg.get());
}
}  // namespace as2