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


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