Program Listing for File as2_platform_gazebo.cpp

Return to documentation for file (/tmp/ws/src/aerostack2/as2_aerial_platforms/as2_platform_gazebo/src/as2_platform_gazebo.cpp)

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

#include "as2_platform_gazebo.hpp"

namespace gazebo_platform
{
GazeboPlatform::GazeboPlatform(const rclcpp::NodeOptions & options)
: as2::AerialPlatform(options)
{
  this->declare_parameter<std::string>("cmd_vel_topic");
  std::string cmd_vel_topic_param = this->get_parameter("cmd_vel_topic").as_string();

  this->declare_parameter<std::string>("arm_topic");
  std::string arm_topic_param = this->get_parameter("arm_topic").as_string();

  // Use takeoff and land with platform for debugging purposes
  this->declare_parameter<bool>("enable_takeoff_platform");
  enable_takeoff_ = this->get_parameter("enable_takeoff_platform").as_bool();
  if (enable_takeoff_) {
    RCLCPP_INFO(this->get_logger(), "Enabled takeoff platform");
  }

  this->declare_parameter<bool>("enable_land_platform");
  enable_land_ = this->get_parameter("enable_land_platform").as_bool();
  if (enable_land_) {
    RCLCPP_INFO(this->get_logger(), "Enabled land platform");
  }

  twist_pub_ =
    this->create_publisher<geometry_msgs::msg::Twist>(cmd_vel_topic_param, rclcpp::QoS(1));

  arm_pub_ = this->create_publisher<std_msgs::msg::Bool>(arm_topic_param, rclcpp::QoS(1));
}

void GazeboPlatform::resetCommandTwistMsg()
{
  geometry_msgs::msg::Twist twist_msg;
  twist_msg.linear.x = 0.0f;
  twist_msg.linear.y = 0.0f;
  twist_msg.linear.z = 0.0f;
  twist_msg.angular.x = 0.0f;
  twist_msg.angular.y = 0.0f;
  twist_msg.angular.z = 0.0f;
  twist_pub_->publish(twist_msg);
}

bool GazeboPlatform::ownSendCommand()
{
  if (control_in_.control_mode == as2_msgs::msg::ControlMode::HOVER) {
    geometry_msgs::msg::Twist twist_msg;
    twist_msg.linear.x = 0;
    twist_msg.linear.y = 0;
    twist_msg.linear.z = 0;
    twist_msg.angular.x = 0;
    twist_msg.angular.y = 0;
    twist_msg.angular.z = 0;
    twist_pub_->publish(twist_msg);
    return true;
  }

  if (command_twist_msg_.twist.angular.z > yaw_rate_limit_) {
    command_twist_msg_.twist.angular.z = yaw_rate_limit_;
  } else if (command_twist_msg_.twist.angular.z < -yaw_rate_limit_) {
    command_twist_msg_.twist.angular.z = -yaw_rate_limit_;
  }
  twist_pub_->publish(command_twist_msg_.twist);
  return true;
}

bool GazeboPlatform::ownSetArmingState(bool state)
{
  std_msgs::msg::Bool arm_msg;
  arm_msg.data = state;
  arm_pub_->publish(arm_msg);
  resetCommandTwistMsg();
  return true;
}

bool GazeboPlatform::ownSetOffboardControl(bool offboard) {return true;}

bool GazeboPlatform::ownSetPlatformControlMode(const as2_msgs::msg::ControlMode & control_in)
{
  RCLCPP_INFO(
    this->get_logger(), "Control mode: [%s]",
    as2::control_mode::controlModeToString(control_in).c_str());
  control_in_ = control_in;
  return true;
}

void GazeboPlatform::ownKillSwitch()
{
  ownSetArmingState(false);
  return;
}

void GazeboPlatform::ownStopPlatform()
{
  geometry_msgs::msg::Twist twist_msg_hover;
  twist_msg_hover.linear.x = 0.0;
  twist_msg_hover.linear.y = 0.0;
  twist_msg_hover.linear.z = 0.0;
  twist_msg_hover.angular.x = 0.0;
  twist_msg_hover.angular.y = 0.0;
  twist_msg_hover.angular.z = 0.0;

  twist_pub_->publish(twist_msg_hover);
  return;
}

bool GazeboPlatform::ownTakeoff()
{
  if (!enable_takeoff_) {
    RCLCPP_WARN(this->get_logger(), "Takeoff platform not enabled");
    return false;
  }

  // Initialize tf and state callbacks
  tf_handler_ = std::make_shared<as2::tf::TfHandler>(this);

  rclcpp::CallbackGroup::SharedPtr callback_group_;
  rclcpp::executors::SingleThreadedExecutor callback_group_executor_;

  callback_group_ =
    this->create_callback_group(rclcpp::CallbackGroupType::MutuallyExclusive, false);
  callback_group_executor_.add_callback_group(callback_group_, this->get_node_base_interface());

  rclcpp::SubscriptionOptions sub_option;
  sub_option.callback_group = callback_group_;

  twist_state_sub_ = this->create_subscription<geometry_msgs::msg::TwistStamped>(
    as2_names::topics::self_localization::twist, as2_names::topics::self_localization::qos,
    std::bind(&GazeboPlatform::state_callback, this, std::placeholders::_1), sub_option);

  state_received_ = false;
  rclcpp::Time time = this->now();
  while (!state_received_) {
    callback_group_executor_.spin_some();
    if ((this->now() - time).seconds() > 5.0) {
      RCLCPP_ERROR(this->get_logger(), "Timeout waiting for state");
      return false;
    }
  }

  RCLCPP_INFO(this->get_logger(), "Take off with Gazebo Platform");

  std::string base_link = as2::tf::generateTfName(this, "base_link");

  geometry_msgs::msg::TwistStamped twist_msg;
  twist_msg.header.stamp = this->now();
  twist_msg.header.frame_id = "earth";
  twist_msg.twist.linear.x = 0.0;
  twist_msg.twist.linear.y = 0.0;
  twist_msg.twist.linear.z = 0.5;
  twist_msg.twist.angular.x = 0.0;
  twist_msg.twist.angular.y = 0.0;
  twist_msg.twist.angular.z = 0.0;

  geometry_msgs::msg::Twist twist_msg_hover;
  twist_msg_hover.linear.x = 0.0;
  twist_msg_hover.linear.y = 0.0;
  twist_msg_hover.linear.z = 0.0;
  twist_msg_hover.angular.x = 0.0;
  twist_msg_hover.angular.y = 0.0;
  twist_msg_hover.angular.z = 0.0;

  callback_group_executor_.spin_some();

  double desired_height = current_height_ + 1.0;

  geometry_msgs::msg::TwistStamped twist_msg_flu;
  rclcpp::Rate rate(10);
  while ((desired_height - current_height_) > 0.0) {
    callback_group_executor_.spin_some();

    // Send command
    twist_msg_flu = twist_msg;
    if (tf_handler_->tryConvert(twist_msg_flu, base_link)) {
      twist_pub_->publish(twist_msg_flu.twist);
    }

    rate.sleep();
  }
  RCLCPP_INFO(this->get_logger(), "Takeoff complete");
  twist_pub_->publish(twist_msg_hover);

  // Clear pointers
  tf_handler_ = nullptr;
  twist_state_sub_ = nullptr;
  state_received_ = false;
  return true;
}

bool GazeboPlatform::ownLand()
{
  if (!enable_land_) {
    RCLCPP_WARN(this->get_logger(), "Land platform not enabled");
    return false;
  }

  // Initialize tf and state callbacks
  tf_handler_ = std::make_shared<as2::tf::TfHandler>(this);

  rclcpp::CallbackGroup::SharedPtr callback_group_;
  rclcpp::executors::SingleThreadedExecutor callback_group_executor_;

  callback_group_ =
    this->create_callback_group(rclcpp::CallbackGroupType::MutuallyExclusive, false);
  callback_group_executor_.add_callback_group(callback_group_, this->get_node_base_interface());

  rclcpp::SubscriptionOptions sub_option;
  sub_option.callback_group = callback_group_;

  twist_state_sub_ = this->create_subscription<geometry_msgs::msg::TwistStamped>(
    as2_names::topics::self_localization::twist, as2_names::topics::self_localization::qos,
    std::bind(&GazeboPlatform::state_callback, this, std::placeholders::_1), sub_option);

  state_received_ = false;
  rclcpp::Time time = this->now();
  while (!state_received_) {
    callback_group_executor_.spin_some();
    if ((this->now() - time).seconds() > 5.0) {
      RCLCPP_ERROR(this->get_logger(), "Timeout waiting for state");
      return false;
    }
  }

  RCLCPP_INFO(this->get_logger(), "Take off with Gazebo Platform");

  std::string base_link = as2::tf::generateTfName(this, "base_link");

  geometry_msgs::msg::TwistStamped twist_msg;
  twist_msg.header.stamp = this->now();
  twist_msg.header.frame_id = "earth";
  twist_msg.twist.linear.x = 0.0;
  twist_msg.twist.linear.y = 0.0;
  twist_msg.twist.linear.z = -0.5;
  twist_msg.twist.angular.x = 0.0;
  twist_msg.twist.angular.y = 0.0;
  twist_msg.twist.angular.z = 0.0;

  geometry_msgs::msg::Twist twist_msg_hover;
  twist_msg_hover.linear.x = 0.0;
  twist_msg_hover.linear.y = 0.0;
  twist_msg_hover.linear.z = 0.0;
  twist_msg_hover.angular.x = 0.0;
  twist_msg_hover.angular.y = 0.0;
  twist_msg_hover.angular.z = 0.0;

  callback_group_executor_.spin_some();

  double desired_height = current_height_ + 1.0;

  geometry_msgs::msg::TwistStamped twist_msg_flu;
  rclcpp::Rate rate(10);
  time = this->now();
  while ((desired_height - current_height_) > 0.0) {
    callback_group_executor_.spin_some();

    if (fabs(current_vertical_speed_) < 0.05) {
      if ((this->now() - time).seconds() > 2) {
        RCLCPP_INFO(this->get_logger(), "Land complete");
        break;
      }
    } else {
      time = this->now();
    }

    // Send command
    twist_msg_flu = tf_handler_->convert(twist_msg, base_link);
    twist_pub_->publish(twist_msg_flu.twist);

    rate.sleep();
  }
  RCLCPP_INFO(this->get_logger(), "Takeoff complete");
  twist_pub_->publish(twist_msg_hover);

  // Clear pointers
  tf_handler_ = nullptr;
  twist_state_sub_ = nullptr;
  state_received_ = false;
  return true;
}

void GazeboPlatform::state_callback(const geometry_msgs::msg::TwistStamped::SharedPtr _twist_msg)
{
  try {
    auto [pose_msg, twist_msg] = tf_handler_->getState(
      *_twist_msg, "earth", "earth",
      as2::tf::generateTfName(this, "base_link"));
    current_height_ = pose_msg.pose.position.z;
    current_vertical_speed_ = twist_msg.twist.linear.z;
    state_received_ = true;
  } catch (tf2::TransformException & ex) {
    RCLCPP_WARN(this->get_logger(), "Could not get transform: %s", ex.what());
  }
  return;
}

}  // namespace gazebo_platform