asctec_hl_interface_adapter.cpp
Go to the documentation of this file.
00001 /*********************************************************************
00002  * Software License Agreement (BSD License)
00003  *
00004  *  Copyright (c) 2012, UC Regents
00005  *  All rights reserved.
00006  *
00007  *  Redistribution and use in source and binary forms, with or without
00008  *  modification, are permitted provided that the following conditions
00009  *  are met:
00010  *
00011  *   * Redistributions of source code must retain the above copyright
00012  *     notice, this list of conditions and the following disclaimer.
00013  *   * Redistributions in binary form must reproduce the above
00014  *     copyright notice, this list of conditions and the following
00015  *     disclaimer in the documentation and/or other materials provided
00016  *     with the distribution.
00017  *   * Neither the name of the University of California nor the names of its
00018  *     contributors may be used to endorse or promote products derived
00019  *     from this software without specific prior written permission.
00020  *
00021  *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
00022  *  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
00023  *  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
00024  *  FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
00025  *  COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
00026  *  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
00027  *  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
00028  *  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
00029  *  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
00030  *  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
00031  *  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
00032  *  POSSIBILITY OF SUCH DAMAGE.
00033  *********************************************************************/
00034 #include <algorithm>
00035 #include <cmath>
00036 #include <ros/ros.h>
00037 #include <nodelet/nodelet.h>
00038 #include <pluginlib/class_list_macros.h>
00039 #include <diagnostic_updater/diagnostic_updater.h>
00040 #include <diagnostic_updater/update_functions.h>
00041 #include <dynamic_reconfigure/server.h>
00042 #include <starmac_robots_asctec/AscTecAdapterConfig.h>
00043 #include <string>
00044 #include <angles/angles.h>
00045 //#include "asctec_msgs/CtrlInput.h"
00046 //#include "asctec_msgs/LLStatus.h"
00047 //#include "asctec_msgs/IMUCalcData.h"
00048 #include "asctec_hl_comm/mav_status.h"
00049 #include "asctec_hl_comm/mav_ctrl.h"
00050 #include "std_msgs/Bool.h"
00051 #include "flyer_controller/control_mode_output.h"
00052 #include "starmac_robots_asctec/pid.h"
00053 #include <nav_msgs/Odometry.h>
00054 #include <sensor_msgs/Imu.h>
00055 //#include <sensor_msgs/Imu.h>
00056 #include <tf/tf.h>
00057 #include <tf/transform_datatypes.h>
00058 #include "starmac_robots_asctec/misc.h"
00059 
00060 //#define _USE_MATH_DEFINES
00061 
00062 using namespace std;
00063 
00064 // Never output commands greater than these values no matter what parameters are set:
00065 static double ROLL_SCALE = M_PI / 180.0; // rad/deg
00066 static double PITCH_SCALE = M_PI / 180.0; // rad/deg
00067 static double MAX_ROLL_CMD = M_PI_2; // rad
00068 static double MAX_PITCH_CMD = M_PI_2; // rad
00069 static double MAX_YAW_RATE_CMD = 2.0 * M_PI; // rad/s
00070 static double MAX_THRUST_CMD = 32; // N
00071 
00072 // Per email from AscTec,
00073 // """The standard parameter for K_stick_yaw is 120, resulting in a maximum rate of
00074 // 254.760 degrees per second. I.e. a 360° turn takes about 1.5 seconds."""
00075 static double YAW_SCALE = M_PI / 180.0; // rad/s per deg/s
00076 
00077 static double THRUST_SCALE = 1.0 / 32.0; // 1/N - approximate
00078 
00079 namespace starmac_robots_asctec
00080 {
00081 
00082 class AscTecHLInterfaceAdapterNodelet : public nodelet::Nodelet
00083 {
00084 private:
00085   ros::NodeHandle nh;
00086   ros::NodeHandle nh_priv;
00087   // Diagnostic Updater
00088   diagnostic_updater::Updater diag_updater;
00089   double min_freq;
00090   double max_freq;
00091   diagnostic_updater::FrequencyStatus freq_status;
00092   // Dynamic Reconfigure Server
00093   dynamic_reconfigure::Server<starmac_robots_asctec::AscTecAdapterConfig> dyn_reconfig_srv;
00094   // Parameters
00095   //string output_topic; // Topic on which to publish output
00096   //string estop_topic; // Topic on which to publish emergency stop command
00097   ros::Duration deadman_timeout; // timeout in seconds
00098   double alt_KP; // N per meter
00099   double alt_KI; // N per meter-second
00100   double alt_KD; // N per m/s
00101   double alt_KDD; // N per m/s/s
00102   double alt_Ilimit; // N
00103   double yaw_KP; // deg/s per deg
00104   double yaw_KI; // deg/s per deg-second
00105   double yaw_KD; // deg/s per deg/s
00106   double yaw_Ilimit; // deg/s
00107   double yaw_rate_limit; // deg/s
00108   double roll_slew_rate_limit; // deg/s
00109   double pitch_slew_rate_limit; // deg/s
00110   double yaw_slew_rate_limit; // deg/s
00111   double alt_slew_rate_limit; // m/s/s
00112   double nominal_thrust; // N TODO: change to mass?
00113   double thrust_mult; // 1.0 means full power
00114   double max_thrust; // N
00115   bool thrust_autoadjust; // set to true to multiply thrust by 1/(cos(roll)*cos(pitch))
00116   double accel_bias; // m/s/s - for a perfect sensor this would be 9.81
00117   unsigned int land_now_thrust_decrease_rate; // [N/s] - how fast to decrease commanded thrust
00118   double land_now_min_thrust_ratio; // reduce thrust to this level (as fraction of nominal_thrust) then keep it constant
00119   double pitch_trim; // deg - will be added to commanded pitch (AscTec sign convention)
00120   double roll_trim; // deg - will be added to commanded roll (AscTec sign convention)
00121   double yaw_offset; // deg
00122   // Publishers
00123   ros::Publisher output;
00124   ros::Publisher estop_pub;
00125   ros::Publisher imu_pub;
00126   // Subscribers
00127   ros::Subscriber motor_enable_sub;
00128   ros::Subscriber control_input_sub;
00129   ros::Subscriber state_sub;
00130   ros::Subscriber mav_status_sub;
00131   ros::Subscriber imu_sub;
00132   ros::Subscriber land_now_sub;
00133   // Timers
00134   ros::Timer deadman_timer;
00135   // Members
00136   bool latest_motor_enable;
00137   bool deadman; // latches to true if motor enable not seen for more than deadman_timeout
00138   ros::Time prev_control_input_time;
00139   ros::Time last_motor_enable_time;
00140   ros::Time last_mav_status_time;
00141   ros::Time last_state_time;
00142   ros::Time last_imu_time;
00143   starmac_robots_asctec::Pid alt_pid;
00144   starmac_robots_asctec::Pid yaw_pid;
00145   nav_msgs::Odometry latest_state;
00146   asctec_hl_comm::mav_status latest_mav_status;
00147   flyer_controller::control_mode_output prev_control_input_msg;
00148   ros::Duration max_motor_enable_age;
00149   ros::Duration max_mav_status_age;
00150   ros::Duration max_control_input_age;
00151   ros::Duration max_state_age;
00152   double alt_p_term;
00153   double alt_i_term;
00154   double alt_d_term;
00155   double alt_dd_term;
00156   double last_z_accel;
00157   bool land_now;
00158   bool estop_sent;
00159   bool got_first_state;
00160   double latest_battery_voltage;
00161   double min_battery_voltage;
00162   bool battery_low_warning_sent;
00163   bool mav_status_cb_called;
00164   bool got_first_control_input;
00165   double last_thrust;
00166 
00167 public:
00168   AscTecHLInterfaceAdapterNodelet() :
00169         diag_updater(),
00170         min_freq(0.1),
00171         max_freq(1000),
00172         freq_status(diagnostic_updater::FrequencyStatusParam(&min_freq, &max_freq)),
00173         // Params:
00174         //output_topic("autopilot/CTRL_INPUT"),
00175         //estop_topic("autopilot/ESTOP"),
00176         deadman_timeout(0.5), alt_KP(2.0),
00177         alt_KI(0.1),
00178         alt_KD(3.0),
00179         alt_KDD(0.0),
00180         alt_Ilimit(5.0),
00181         yaw_KP(1.0),
00182         yaw_KI(1.0),
00183         yaw_KD(0.0),
00184         yaw_Ilimit(5.0),
00185         yaw_rate_limit(4.0), //
00186         roll_slew_rate_limit(10),
00187         pitch_slew_rate_limit(10),
00188         yaw_slew_rate_limit(5),
00189         alt_slew_rate_limit(0.1), //
00190         nominal_thrust(14.0),
00191         thrust_mult(1.0),
00192         max_thrust(18.0),
00193         thrust_autoadjust(true),
00194         accel_bias(9.81),
00195         land_now_thrust_decrease_rate(200),
00196         // Members:
00197         latest_motor_enable(false), deadman(false), prev_control_input_time(0), last_motor_enable_time(0), //
00198         last_mav_status_time(0), last_state_time(0), max_motor_enable_age(ros::Duration(0)),
00199         max_mav_status_age(ros::Duration(0)), max_control_input_age(ros::Duration(0)), max_state_age(ros::Duration(0)),
00200         land_now_min_thrust_ratio(0.5), pitch_trim(0), roll_trim(0), yaw_offset(0),
00201         last_z_accel(0), land_now(false), estop_sent(false), got_first_state(false), latest_battery_voltage(0), min_battery_voltage(0),
00202         battery_low_warning_sent(false), mav_status_cb_called(false), got_first_control_input(false),
00203         last_thrust(0)
00204   {
00205   }
00206 
00207   void onInit()
00208   {
00209     nh_priv = getPrivateNodeHandle();
00210     initDiagnostics();
00211     //initDynamicReconfigure();
00212     initParameters();
00213     initPublishers();
00214     initSubscribers();
00215     initTimers();
00216   }
00217 
00218 private:
00219 
00220   void initDiagnostics()
00221   {
00222     // Diagnostics
00223     diag_updater.add("AscTecAdapter Status", this, &AscTecHLInterfaceAdapterNodelet::diagnostics);
00224     diag_updater.add(freq_status);
00225     diag_updater.setHardwareID("none");
00226     diag_updater.force_update();
00227   }
00228 
00229   void initDynamicReconfigure()
00230   {
00231     // Dynamic Reconfigure Server
00232     dynamic_reconfigure::Server<starmac_robots_asctec::AscTecAdapterConfig>::CallbackType f =
00233         boost::bind(&AscTecHLInterfaceAdapterNodelet::cfgCallback, this, _1, _2);
00234     dyn_reconfig_srv.setCallback(f);
00235   }
00236 
00237   void initParameters()
00238   {
00239     // Parameters
00240     //nh_priv.param("output_topic", output_topic, output_topic);
00241     //nh_priv.param("estop_topic", estop_topic, estop_topic);
00242     double temp = deadman_timeout.toSec();
00243     nh_priv.param("deadman_timeout", temp, temp);
00244     deadman_timeout.fromSec(temp);
00245     nh_priv.param("alt_KP", alt_KP, alt_KP);
00246     nh_priv.param("alt_KI", alt_KI, alt_KI);
00247     nh_priv.param("alt_KD", alt_KD, alt_KD);
00248     nh_priv.param("alt_KDD", alt_KDD, alt_KDD);
00249     nh_priv.param("alt_Ilimit", alt_Ilimit, alt_Ilimit);
00250     nh_priv.param("yaw_KP", yaw_KP, yaw_KP);
00251     nh_priv.param("yaw_KI", yaw_KI, yaw_KI);
00252     nh_priv.param("yaw_KD", yaw_KD, yaw_KD);
00253     nh_priv.param("yaw_Ilimit", yaw_Ilimit, yaw_Ilimit);
00254     nh_priv.param("yaw_rate_limit", yaw_rate_limit, yaw_rate_limit);
00255     nh_priv.param("roll_slew_rate_limit", roll_slew_rate_limit, roll_slew_rate_limit);
00256     nh_priv.param("pitch_slew_rate_limit", pitch_slew_rate_limit, pitch_slew_rate_limit);
00257     nh_priv.param("yaw_slew_rate_limit", yaw_slew_rate_limit, yaw_slew_rate_limit);
00258     nh_priv.param("alt_slew_rate_limit", alt_slew_rate_limit, alt_slew_rate_limit);
00259     nh_priv.param("nominal_thrust", nominal_thrust, nominal_thrust);
00260     nh_priv.param("thrust_mult", thrust_mult, thrust_mult);
00261     nh_priv.param("max_thrust", max_thrust, max_thrust);
00262     nh_priv.param("thrust_autoadjust", thrust_autoadjust, thrust_autoadjust);
00263     nh_priv.param("accel_bias", accel_bias, accel_bias);
00264     int temp_uint; // ROS parameters can't be unsigned
00265     nh_priv.param("land_now_thrust_decrease_rate", temp_uint, (int)land_now_thrust_decrease_rate);
00266     land_now_thrust_decrease_rate = (unsigned int)temp_uint;
00267     nh_priv.param("land_now_min_thrust_ratio", land_now_min_thrust_ratio, land_now_min_thrust_ratio);
00268     nh_priv.param("pitch_trim", pitch_trim, pitch_trim);
00269     nh_priv.param("roll_trim", roll_trim, roll_trim);
00270     nh_priv.param("yaw_offset", yaw_offset, yaw_offset);
00271     // Altitude and Yaw PID control
00272     setAltControllerParams(alt_KP, alt_KI, alt_KD, alt_Ilimit);
00273     setYawControllerParams(yaw_KP, yaw_KI, yaw_KD, yaw_Ilimit);
00274   }
00275 
00276   void initPublishers()
00277   {
00278     // Publishers
00279     output = nh.advertise<asctec_hl_comm::mav_ctrl> ("fcu/control", 10);
00280     //estop_pub = nh.advertise<std_msgs::Bool> ("asctec/ESTOP", 10);
00281     imu_pub = nh.advertise<sensor_msgs::Imu> ("asctec/imu", 10);
00282   }
00283 
00284   void initTimers()
00285   {
00286     // Timers
00287     double deadman_timer_period = deadman_timeout.toSec() / 2;
00288     min_freq = 0.95 / deadman_timer_period;
00289     max_freq = 1.05 / deadman_timer_period;
00290     deadman_timer = nh.createTimer(ros::Duration(deadman_timer_period),
00291                                    &AscTecHLInterfaceAdapterNodelet::deadmanCallback, this);
00292   }
00293 
00294   void initSubscribers()
00295   {
00296     // Subscribers
00297     motor_enable_sub = nh.subscribe("teleop_flyer/motor_enable", 1,
00298                                     &AscTecHLInterfaceAdapterNodelet::motorEnableCallback, this,
00299                                     ros::TransportHints().tcpNoDelay());
00300     control_input_sub = nh.subscribe("controller_mux/output", 1,
00301                                      &AscTecHLInterfaceAdapterNodelet::controlInputCallback, this,
00302                                      ros::TransportHints().tcpNoDelay());
00303     state_sub = nh.subscribe("odom", 1, &AscTecHLInterfaceAdapterNodelet::stateCallback, this,
00304                              ros::TransportHints().tcpNoDelay());
00305 
00306     mav_status_sub = nh.subscribe("fcu/status", 1, &AscTecHLInterfaceAdapterNodelet::mavStatusCallback, this,
00307                                   ros::TransportHints().tcpNoDelay());
00308     //    imu_sub = nh.subscribe("asctec/IMU_CALCDATA", 1, &AscTecMavFrameworkAdapterNodelet::imuCallback, this,
00309     //                           ros::TransportHints().tcpNoDelay());
00310     land_now_sub = nh_priv.subscribe("land_now", 10, &AscTecHLInterfaceAdapterNodelet::landNowCallback, this,
00311                                      ros::TransportHints().tcpNoDelay());
00312   }
00313 
00314   void cfgCallback(starmac_robots_asctec::AscTecAdapterConfig &config, uint32_t level)
00315   {
00316     ROS_INFO("Reconfigure request");
00317     if (isSafeToReconfigure())
00318     {
00319       setYawControllerParams(config.yaw_KP, config.yaw_KI, config.yaw_KD, config.yaw_Ilimit);
00320       roll_trim = config.roll_trim;
00321       pitch_trim = config.pitch_trim;
00322     }
00323     else
00324     {
00325       ROS_ERROR("Cannot reconfigure; isSafeToReconfigure() returned false");
00326     }
00327   }
00328 
00329   void setAltControllerParams(double KP, double KI, double KD, double Ilimit)
00330   {
00331     alt_pid.initPid(KP, KI, KD, Ilimit, -Ilimit);
00332     ROS_INFO("Altitude controller gains set: KP=%f, KI=%f, KD=%f, Ilimit=%f", KP, KI, KD, Ilimit);
00333   }
00334 
00335   void setYawControllerParams(double KP, double KI, double KD, double Ilimit)
00336   {
00337     yaw_pid.initPid(KP, KI, KD, Ilimit, -Ilimit);
00338     ROS_INFO("Yaw controller gains set: KP=%f, KI=%f, KD=%f, Ilimit=%f", KP, KI, KD, Ilimit);
00339   }
00340 
00341   void diagnostics(diagnostic_updater::DiagnosticStatusWrapper& stat)
00342   {
00343     if (deadman)
00344     {
00345       stat.summary(diagnostic_msgs::DiagnosticStatus::ERROR, "Deadman Tripped");
00346     }
00347     else
00348     {
00349       if (min_battery_voltage < 10.0)
00350       {
00351         stat.summary(diagnostic_msgs::DiagnosticStatus::WARN, "Low Battery");
00352         if (not battery_low_warning_sent)
00353         {
00354           ROS_WARN("Battery voltage below 10V");
00355           battery_low_warning_sent = true;
00356         }
00357       }
00358       else
00359       {
00360         stat.summary(diagnostic_msgs::DiagnosticStatus::OK, "OK");
00361       }
00362     }
00363     stat.add("Motor Enable", latest_motor_enable);
00364     stat.add("Deadman Tripped", deadman);
00365     stat.add("max ll_status age", max_mav_status_age);
00366     stat.add("max control inputs age", max_control_input_age);
00367     stat.add("max motor_enable age", max_motor_enable_age);
00368     stat.add("max state age", max_state_age);
00369     stat.add("alt P term", alt_p_term);
00370     stat.add("alt I term", alt_i_term);
00371     stat.add("alt D term", alt_d_term);
00372     stat.add("alt DD term", alt_dd_term);
00373     stat.add("Battery Voltage (current)", latest_battery_voltage);
00374     stat.add("Battery Voltage (minimum)", min_battery_voltage);
00375   }
00376 
00377   void motorEnableCallback(const std_msgs::BoolConstPtr& msg)
00378   {
00379     static bool first = true;
00380     latest_motor_enable = msg->data;
00381     ros::Time now = ros::Time::now();
00382     if (!first)
00383     {
00384       max_motor_enable_age = max(now - last_motor_enable_time, max_motor_enable_age);
00385     }
00386     else
00387     {
00388       first = false;
00389     }
00390     last_motor_enable_time = now;
00391   }
00392 
00393   void stateCallback(const nav_msgs::OdometryConstPtr& msg)
00394   {
00395     static bool first = true;
00396     latest_state = *msg;
00397     ROS_DEBUG_STREAM("Got state with z = " << latest_state.pose.pose.position.z);
00398     ros::Time now = ros::Time::now();
00399     if (!first)
00400     {
00401       max_state_age = max(now - last_state_time, max_state_age);
00402     }
00403     else
00404     {
00405       first = false;
00406       got_first_state = true;
00407     }
00408     last_state_time = now;
00409   }
00410 
00411   //  TODO: replace IMU callback (or is it necessary anymore??)
00412   //  void imuCallback(const asctec_msgs::IMUCalcDataConstPtr& msg)
00413   //  {
00414   //    //    static double avg_accel = 0;
00415   //    //    static int n_meas = 0;
00416   //    ros::Time now = ros::Time::now();
00417   //    last_imu_time = now;
00418   //    // calculate quaternion orientation - minus signs convert to ENU frame
00419   //    btQuaternion orientation;
00420   //    orientation.setRPY(msg->angle_roll * ASC_TO_ROS_ANGLE * -1.0, msg->angle_nick * ASC_TO_ROS_ANGLE,
00421   //                       msg->angle_yaw * ASC_TO_ROS_ANGLE * -1.0);
00422   //
00423   //    btMatrix3x3 R_enu_imu(orientation);
00424   //
00425   //    btVector3 acc_imu(msg->acc_x_calib * ASC_TO_ROS_ACC, msg->acc_y_calib * ASC_TO_ROS_ACC,
00426   //                      msg->acc_z_calib * ASC_TO_ROS_ACC);
00427   //    btVector3 acc_enu = R_enu_imu * acc_imu;
00428   //    //    ROS_INFO("acc_inertial = %g, %g, %g", acc_inertial[0], acc_inertial[1], acc_inertial[2]);
00429   //    last_z_accel = acc_enu[2] + accel_bias; // -ve values = ascending
00430   //    //    ROS_INFO("last_z_accel = %g", last_z_accel);
00431   //    //    ROS_INFO("norm of accel = %g (raw), %g (rotated), %g (avg z)", acc_imu.length(), acc_inertial.length(), avg_accel);
00432   //    //    n_meas++;
00433   //    //    avg_accel = (avg_accel * (n_meas - 1) + acc_inertial[2]) / n_meas;
00434   //
00435   //    sensor_msgs::Imu imu_msg;
00436   //
00437   //    imu_msg.header.stamp = msg->header.stamp;
00438   //    imu_msg.header.frame_id = ros::this_node::getNamespace() + "/imu";
00439   //
00440   //    imu_msg.linear_acceleration.x = msg->acc_x_calib * ASC_TO_ROS_ACC;
00441   //    imu_msg.linear_acceleration.y = msg->acc_y_calib * ASC_TO_ROS_ACC;
00442   //    imu_msg.linear_acceleration.z = msg->acc_z_calib * ASC_TO_ROS_ACC;
00443   //
00444   //    for (int i = 0; i < 8; i++)
00445   //    {
00446   //      imu_msg.linear_acceleration_covariance[i] = 0.0;
00447   //      imu_msg.angular_velocity_covariance[i] = 0.0;
00448   //    }
00449   //
00450   //    imu_msg.angular_velocity.x = msg->angvel_roll * ASC_TO_ROS_ANGVEL * -1.0;
00451   //    imu_msg.angular_velocity.y = msg->angvel_nick * ASC_TO_ROS_ANGVEL;
00452   //    imu_msg.angular_velocity.z = msg->angvel_yaw * ASC_TO_ROS_ANGVEL * -1.0;
00453   //
00454   //    imu_msg.orientation.x = orientation.getX();
00455   //    imu_msg.orientation.y = orientation.getY();
00456   //    imu_msg.orientation.z = orientation.getZ();
00457   //    imu_msg.orientation.w = orientation.getW();
00458   //
00459   //    imu_pub.publish(imu_msg);
00460   //  }
00461 
00462   void landNowCallback(const std_msgs::BoolConstPtr& msg)
00463   {
00464     land_now = msg->data;
00465   }
00466 
00467   void mavStatusCallback(const asctec_hl_comm::mav_statusConstPtr& msg)
00468   {
00469     latest_mav_status = *msg;
00470     ros::Time now = ros::Time::now();
00471     if (mav_status_cb_called)
00472     {
00473       max_mav_status_age = max(now - last_mav_status_time, max_mav_status_age);
00474     }
00475     else
00476     {
00477       min_battery_voltage = 9999;
00478       mav_status_cb_called = true;
00479     }
00480     last_mav_status_time = now;
00481     latest_battery_voltage = (double)latest_mav_status.battery_voltage;
00482     min_battery_voltage = min(latest_battery_voltage, min_battery_voltage);
00483   }
00484 
00485   void controlInputCallback(const flyer_controller::control_mode_outputConstPtr& msg)
00486   {
00487     ros::Time now = ros::Time::now();
00488     ros::Duration dt = now - prev_control_input_time;
00489     static bool prev_motor_enable = false;
00490     if (got_first_control_input)
00491     {
00492       max_control_input_age = max(dt, max_control_input_age);
00493     }
00494     else
00495     {
00496       got_first_control_input = true;
00497     }
00498 
00499     // Slew limiting
00500     double roll_cmd_limited, pitch_cmd_limited, yaw_cmd_limited, alt_cmd_limited;
00501     double dt_sec = dt.toSec();
00502     // TODO: Think about whether when limiting occurs, it should occur on all axes proportionally
00503     // or at least, if roll is being limited by some factor x, then pitch should also, otherwise the overall
00504     // direction of the commanded thrust vector will be different
00505     limitSlewRate(msg->roll_cmd, prev_control_input_msg.roll_cmd, dt_sec, roll_slew_rate_limit, roll_cmd_limited);
00506     //    if (roll_cmd_limited != msg.roll_cmd) {
00507     //      ROS_INFO_STREAM("Roll command was slew rate limited. Orig cmd: " << msg.roll_cmd << "; New cmd: " << roll_cmd_limited);
00508     //    }
00509     limitSlewRate(msg->pitch_cmd, prev_control_input_msg.pitch_cmd, dt_sec, pitch_slew_rate_limit, pitch_cmd_limited);
00510 
00511     if (not msg->direct_yaw_rate_commands)
00512       limitSlewRate(msg->yaw_cmd, prev_control_input_msg.yaw_cmd, dt_sec, yaw_slew_rate_limit, yaw_cmd_limited);
00513 
00514     if (not msg->direct_thrust_commands)
00515       limitSlewRate(msg->alt_cmd, prev_control_input_msg.alt_cmd, dt_sec, alt_slew_rate_limit, alt_cmd_limited);
00516 
00517     if (latest_motor_enable and not prev_motor_enable)
00518     {
00519       double pe, de, ie;
00520       yaw_pid.getCurrentPIDErrors(&pe, &de, &ie);
00521       ROS_INFO_STREAM("Yaw integrator was at:" << ie);
00522       alt_pid.reset();
00523       yaw_pid.reset();
00524       ROS_INFO("Reset yaw & alt integrators");
00525     }
00526     prev_motor_enable = latest_motor_enable;
00527     double thrust_cmd = 0; // [N]
00528     if (got_first_state)
00529     {
00530       if (not msg->direct_thrust_commands)
00531       {
00532         //ROS_DEBUG("Running computeThrust, alt_cmd_limited = %f, dt = %f", alt_cmd_limited, dt.toSec());
00533         thrust_cmd = computeThrust(alt_cmd_limited, dt);
00534       }
00535       else
00536       {
00537         double yaw, pitch, roll;
00538         getLatestYPR(yaw, pitch, roll);
00539         double tfactor = 1;
00540         if (thrust_autoadjust)
00541           tfactor = 1 / (cos(pitch) * cos(roll));
00542         thrust_cmd = tfactor * msg->thrust_cmd;
00543       }
00544     }
00545     else
00546     {
00547       ROS_DEBUG("got_first_state was false..");
00548     }
00549     double yaw_rate_cmd = 0; // [deg/s]
00550     if (not land_now)
00551     {
00552       // Don't want to try and control yaw during rapid landing since once on the ground
00553       // (which we can't assume we have a way of sensing, since we might be landing precisely
00554       //  *because* we've lost our state estimate) the yaw controller will try hard to
00555       // take out yaw error resulting in one pair of motors spinning up.
00556       if (msg->direct_yaw_rate_commands)
00557         yaw_rate_cmd = msg->yaw_rate_cmd;
00558       else if (got_first_state)
00559         yaw_rate_cmd = computeYawRate(yaw_cmd_limited, dt);
00560     }
00561     else
00562     {
00563       // Also zero out pitch and roll during rapid landing
00564       roll_cmd_limited = roll_trim;
00565       pitch_cmd_limited = pitch_trim;
00566     }
00567     asctec_hl_comm::mav_ctrlPtr ctrl_msg(new asctec_hl_comm::mav_ctrl);
00568     ctrl_msg->type = asctec_hl_comm::mav_ctrl::acceleration;
00569     ctrl_msg->header.stamp = now;
00570     double roll_cmd_rotated, pitch_cmd_rotated; // deg
00571     rotateRollAndPitch(roll_cmd_limited, pitch_cmd_limited, roll_cmd_rotated, pitch_cmd_rotated);
00572     roll_cmd_rotated += roll_trim;
00573     pitch_cmd_rotated += pitch_trim;
00574     if (!deadman && dt.toSec() < 1.0)
00575     {
00576       bool motors = msg->motors_on and latest_motor_enable;
00577       if (not motors)
00578       {
00579         thrust_cmd = 0;
00580       }
00581       assemble_command(roll_cmd_rotated, pitch_cmd_rotated, yaw_rate_cmd, thrust_cmd, ctrl_msg);
00582     }
00583     if (deadman)
00584     {
00585 
00586       // TODO: call motor off service if deadman tripped (?)
00587       //      if (not estop_sent)
00588       //      {
00589       //        std_msgs::Bool estop_msg;
00590       //        estop_msg.data = true;
00591       //        estop_pub.publish(estop_msg);
00592       //        estop_sent = true;
00593       //      }
00594       //      if (latest_mav_status.flying == 1)
00595       //        ctrl_msg = zero_thrust_full_left_yaw; // send motor shutoff
00596       //      else
00597       //        ctrl_msg = assemble_command(roll_cmd_limited, pitch_cmd_limited, yaw_rate_cmd, thrust_cmd, false, false, false,
00598       //                                    false); // Send roll, pitch, yaw values but don't enable those bits
00599     }
00600     ctrl_msg->header.stamp = now;
00601     output.publish(ctrl_msg);
00602     prev_control_input_time = now;
00603     prev_control_input_msg = *msg;
00604   }
00605 
00606   void rotateRollAndPitch(const double roll_cmd, const double pitch_cmd, double& roll_cmd_rotated,
00607                           double& pitch_cmd_rotated)
00608   {
00609     double cy = cos(angles::from_degrees(yaw_offset));
00610     double sy = sin(angles::from_degrees(yaw_offset));
00611     roll_cmd_rotated = cy * roll_cmd - sy * pitch_cmd;
00612     pitch_cmd_rotated = sy * roll_cmd + cy * pitch_cmd;
00613   }
00614 
00615   double computeThrust(const double alt_cmd, ros::Duration& dt)
00616   {
00617     // TODO: probably shouldn't have statics here, they should be member vars
00618     // (though as long as there's only one instance of AscTecAdapter in the program
00619     //  it's really just academic)
00620     static bool landing_now = false;
00621     static int land_now_start_thrust = 0;
00622     static ros::Time land_now_start_time;
00623     if (land_now)
00624     {
00625       if (!landing_now)
00626       {
00627         ROS_INFO("Received land_now instruction. Decreasing thrust at %d counts/s to %d counts",
00628                  land_now_thrust_decrease_rate, int(land_now_min_thrust_ratio * double(nominal_thrust)));
00629         land_now_start_thrust = last_thrust;
00630         land_now_start_time = ros::Time::now();
00631         landing_now = true;
00632       }
00633       last_thrust = max(
00634                         land_now_min_thrust_ratio * nominal_thrust,
00635                         land_now_start_thrust - (ros::Time::now() - land_now_start_time).toSec()
00636                             * land_now_thrust_decrease_rate);
00637     }
00638     else
00639     {
00640       if (landing_now)
00641       {
00642         // We were landing but not anymore, so reset things:
00643         landing_now = false;
00644       }
00645       double alt_err = (-latest_state.pose.pose.position.z) - alt_cmd;
00646       double alt_vel_err = -latest_state.twist.twist.linear.z;
00647       alt_pid.updatePid(alt_err, alt_vel_err, dt);
00648       double pe, ie, de;
00649       alt_pid.getCurrentPIDErrors(&pe, &ie, &de);
00650       alt_p_term = alt_KP * pe;
00651       alt_i_term = alt_KI * ie;
00652       alt_d_term = alt_KD * de;
00653       alt_dd_term = alt_KDD * last_z_accel;
00654       double yaw, pitch, roll;
00655       getLatestYPR(yaw, pitch, roll);
00656       double tfactor = 1;
00657       if (thrust_autoadjust)
00658         tfactor = 1 / (cos(pitch) * cos(roll));
00659       last_thrust = tfactor * (nominal_thrust + alt_pid.getCurrentCmd() + alt_dd_term);
00660     }
00661     return last_thrust;
00662   }
00663 
00664   double computeYawRate(double yaw_cmd, // deg
00665                         ros::Duration& dt)
00666   {
00667     double yaw, pitch, roll; // rad
00668     getLatestYPR(yaw, pitch, roll);
00669     //double yaw_err = angles::to_degrees(yaw) - yaw_cmd;
00670     double yaw_err = angles::to_degrees(
00671                                         angles::shortest_angular_distance(angles::from_degrees(yaw_cmd),
00672                                                                           yaw + angles::from_degrees(yaw_offset))); // deg
00673     //ROS_INFO_STREAM("Yaw error = " << yaw_err << " [deg]");
00674     yaw_pid.updatePid(yaw_err, dt);
00675     double yaw_rate_cmd = min(yaw_rate_limit, max(-yaw_rate_limit, yaw_pid.getCurrentCmd())); // deg/s
00676     //ROS_INFO_STREAM("Yaw rate command = " << yaw_rate_cmd << " [deg/s]");
00677     return yaw_rate_cmd;
00678   }
00679 
00680   void getLatestYPR(double& yaw, // rad
00681                     double& pitch, // rad
00682                     double& roll // rad
00683   )
00684   {
00685     tf::Quaternion temp_q;
00686     //    ROS_INFO("About to do tf::quaternionMsgToTF..");
00687     //    ROS_INFO_STREAM("orientation: " << latest_state.pose.pose.orientation.x << " "
00688     //        << latest_state.pose.pose.orientation.y << " " << latest_state.pose.pose.orientation.z << " "
00689     //        << latest_state.pose.pose.orientation.w);
00690     tf::quaternionMsgToTF(latest_state.pose.pose.orientation, temp_q);
00691     //    ROS_INFO("done tf::quaternionMsgToTF.. ");
00692     tf::Matrix3x3 temp_mat = tf::Matrix3x3(temp_q);
00693     temp_mat.getEulerYPR(yaw, pitch, roll);
00694   }
00695 
00696   void assemble_command(double roll, // deg
00697                         double pitch, // deg
00698                         double yaw_rate, // deg/s
00699                         double thrust, // N,
00700                         asctec_hl_comm::mav_ctrlPtr ctrl_msg)
00701   {
00702     ctrl_msg->y = -float(min(MAX_ROLL_CMD, max(-MAX_ROLL_CMD, roll * ROLL_SCALE)));
00703     ctrl_msg->x = -float(min(MAX_PITCH_CMD, max(-MAX_PITCH_CMD, pitch * PITCH_SCALE)));
00704     ctrl_msg->yaw = -float(min(MAX_YAW_RATE_CMD, max(-MAX_YAW_RATE_CMD, yaw_rate * YAW_SCALE)));
00705     ctrl_msg->z = float(min(MAX_THRUST_CMD, min(max_thrust, max(0.0, thrust * thrust_mult * THRUST_SCALE))));
00706   }
00707 
00708   void deadmanCallback(const ros::TimerEvent& e)
00709   {
00710     // TODO: figure out what to do for deadman now..
00711     //    // This method, called periodically by a timer, checks for the various conditions that
00712     //    // result in a 'dead man' determination:
00713     //    // - motor_enable signal from teleop_flyer too old
00714     //    // - control inputs too old
00715     //    // - ll_status from asctec too old
00716     //    // - state estimate too old
00717     //    ros::Time now = ros::Time::now();
00718     //    //    double motor_enable_age = (now - last_motor_enable_time).toSec();
00719     //    //    double control_inputs_age = (now - last_control_input_time).toSec();
00720     //    //    double ll_status_age = (now - last_ll_status_time).toSec();
00721     //    //    double state_estimate_age = (now - last_state_time).toSec();
00722     //    string deadman_timeout_reason;
00723     //    ros::Duration deadman_timeout_dt(0);
00724     //    freq_status.tick();
00725     //
00726     //    if (max_motor_enable_age > deadman_timeout)
00727     //    {
00728     //      deadman = true;
00729     //      deadman_timeout_reason = "motor enable";
00730     //      deadman_timeout_dt = max_motor_enable_age;
00731     //    }
00732     //    if (max_control_input_age > deadman_timeout)
00733     //    {
00734     //      deadman = true;
00735     //      deadman_timeout_reason = "control inputs";
00736     //      deadman_timeout_dt = max_control_input_age;
00737     //    }
00738     //    if (max_mav_status_age > deadman_timeout)
00739     //    {
00740     //      deadman = true;
00741     //      deadman_timeout_reason = "LL_Status";
00742     //      deadman_timeout_dt = max_mav_status_age;
00743     //    }
00744     //    if (max_state_age > deadman_timeout)
00745     //    {
00746     //      deadman = true;
00747     //      deadman_timeout_reason = "state";
00748     //      deadman_timeout_dt = max_state_age;
00749     //    }
00750     //    // Otherwise, if deadman conditions are no longer present,
00751     //    // and the ll_status (which must be recent if the above is true)
00752     //    // is indicating that the vehicle is not flying
00753     //    // (i.e. motors not spinning), then reset the deadman condition.
00754     //
00755     //    else if (latest_mav_status.motor_status == 0)
00756     //    {
00757     //      deadman = false;
00758     //    }
00759     //    if (deadman)
00760     //    {
00761     //      ROS_ERROR_STREAM(
00762     //                       "Deadman timeout exceeded(" << deadman_timeout_reason << "), dt = "
00763     //                           << deadman_timeout_dt.toSec());
00764     //    }
00765     diag_updater.update();
00766   }
00767 
00768   bool isSafeToReconfigure()
00769   {
00770     return (not latest_motor_enable); // for now this should suffice
00771   }
00772 };
00773 PLUGINLIB_DECLARE_CLASS(starmac_robots_asctec, AscTecHLInterfaceAdapterNodelet, starmac_robots_asctec::AscTecHLInterfaceAdapterNodelet, nodelet::Nodelet)
00774 
00775 } // namespace
00776 


starmac_robots_asctec
Author(s): Patrick Bouffard
autogenerated on Sun Jan 5 2014 11:38:03