00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022
00023
00024
00025
00026
00027
00028
00029 #include <hector_quadrotor_controller/quadrotor_propulsion.h>
00030 #include <gazebo/common/Events.hh>
00031 #include <gazebo/physics/physics.hh>
00032
00033 #include <rosgraph_msgs/Clock.h>
00034
00035 extern "C" {
00036 #include "quadrotorPropulsion/quadrotorPropulsion.h"
00037 #include "quadrotorPropulsion/quadrotorPropulsion_initialize.h"
00038 }
00039
00040 #include <boost/array.hpp>
00041
00042 namespace gazebo {
00043
00044 using namespace common;
00045 using namespace math;
00046
00047 template <typename T> static inline T checknan(const T& value, const std::string& text = "") {
00048 if (!(value == value)) {
00049 if (!text.empty()) std::cerr << text << " contains **!?* Nan values!" << std::endl;
00050 return T();
00051 }
00052 return value;
00053 }
00054
00055
00056 struct GazeboQuadrotorPropulsion::PropulsionModel {
00057 PropulsionParameters parameters_;
00058 boost::array<real_T,4> x;
00059 boost::array<real_T,4> x_pred;
00060 boost::array<real_T,10> u;
00061 boost::array<real_T,14> y;
00062 };
00063
00064 GazeboQuadrotorPropulsion::GazeboQuadrotorPropulsion()
00065 {
00066
00067 quadrotorPropulsion_initialize();
00068 propulsion_model_ = new PropulsionModel;
00069 }
00070
00072
00073 GazeboQuadrotorPropulsion::~GazeboQuadrotorPropulsion()
00074 {
00075 event::Events::DisconnectWorldUpdateStart(updateConnection);
00076 node_handle_->shutdown();
00077 callback_queue_thread_.join();
00078 delete node_handle_;
00079
00080 delete propulsion_model_;
00081 }
00082
00084
00085 void GazeboQuadrotorPropulsion::Load(physics::ModelPtr _model, sdf::ElementPtr _sdf)
00086 {
00087 world = _model->GetWorld();
00088 link = _model->GetLink();
00089
00090
00091 if (!_sdf->HasElement("robotNamespace"))
00092 namespace_.clear();
00093 else
00094 namespace_ = _sdf->GetElement("robotNamespace")->GetValueString() + "/";
00095
00096 if (!_sdf->HasElement("paramNamespace"))
00097 param_namespace_ = "~/quadrotor_propulsion/";
00098 else
00099 param_namespace_ = _sdf->GetElement("paramNamespace")->GetValueString() + "/";
00100
00101 if (!_sdf->HasElement("triggerTopic"))
00102 trigger_topic_ = "quadro/trigger";
00103 else
00104 trigger_topic_ = _sdf->GetElement("triggerTopic")->GetValueString();
00105
00106 if (!_sdf->HasElement("voltageTopicName"))
00107 voltage_topic_ = "motor_pwm";
00108 else
00109 voltage_topic_ = _sdf->GetElement("voltageTopicName")->GetValueString();
00110
00111 if (!_sdf->HasElement("wrenchTopic"))
00112 wrench_topic_ = "wrench_out";
00113 else
00114 wrench_topic_ = _sdf->GetElement("wrenchTopic")->GetValueString();
00115
00116 if (!_sdf->HasElement("supplyTopic"))
00117 supply_topic_ = "supply";
00118 else
00119 supply_topic_ = _sdf->GetElement("supplyTopic")->GetValueString();
00120
00121 if (!_sdf->HasElement("statusTopic"))
00122 status_topic_ = "motor_status";
00123 else
00124 status_topic_ = _sdf->GetElement("statusTopic")->GetValueString();
00125
00126
00127 controlTimer.Load(world, _sdf, "control");
00128 control_tolerance_ = Time();
00129 if (_sdf->HasElement("controlTolerance")) control_tolerance_ = _sdf->GetElement("controlTolerance")->GetValueDouble();
00130 control_delay_ = Time();
00131 if (_sdf->HasElement("controlDelay")) control_delay_ = _sdf->GetElement("controlDelay")->GetValueDouble();
00132
00133
00134 supply_.voltage.resize(1);
00135 supply_.voltage[0] = 14.8;
00136 if (_sdf->HasElement("supplyVoltage")) supply_.voltage[0] = _sdf->GetElement("supplyVoltage")->GetValueDouble();
00137
00138
00139 if (!ros::isInitialized())
00140 {
00141 int argc = 0;
00142 char **argv = NULL;
00143 ros::init(argc,argv,"gazebo",ros::init_options::NoSigintHandler|ros::init_options::AnonymousName);
00144 }
00145
00146 node_handle_ = new ros::NodeHandle(namespace_);
00147
00148
00149
00150 if (!trigger_topic_.empty())
00151 {
00152 ros::AdvertiseOptions ops = ros::AdvertiseOptions::create<rosgraph_msgs::Clock>(
00153 trigger_topic_, 10,
00154 ros::SubscriberStatusCallback(), ros::SubscriberStatusCallback(),
00155 ros::VoidConstPtr(), &callback_queue_);
00156 trigger_publisher_ = node_handle_->advertise(ops);
00157 }
00158
00159
00160 if (!voltage_topic_.empty())
00161 {
00162 ros::SubscribeOptions ops = ros::SubscribeOptions::create<hector_uav_msgs::MotorPWM>(
00163 voltage_topic_, 1,
00164 boost::bind(&GazeboQuadrotorPropulsion::CommandCallback, this, _1),
00165 ros::VoidPtr(), &callback_queue_);
00166 voltage_subscriber_ = node_handle_->subscribe(ops);
00167 }
00168
00169
00170 if (!wrench_topic_.empty())
00171 {
00172 ros::AdvertiseOptions ops = ros::AdvertiseOptions::create<geometry_msgs::Wrench>(
00173 wrench_topic_, 10,
00174 ros::SubscriberStatusCallback(), ros::SubscriberStatusCallback(),
00175 ros::VoidConstPtr(), &callback_queue_);
00176 wrench_publisher_ = node_handle_->advertise(ops);
00177 }
00178
00179
00180 if (!supply_topic_.empty())
00181 {
00182 ros::AdvertiseOptions ops = ros::AdvertiseOptions::create<hector_uav_msgs::Supply>(
00183 supply_topic_, 10,
00184 ros::SubscriberStatusCallback(), ros::SubscriberStatusCallback(),
00185 ros::VoidConstPtr(), &callback_queue_);
00186 ops.latch = true;
00187 supply_publisher_ = node_handle_->advertise(ops);
00188
00189 supply_publisher_.publish(supply_);
00190 }
00191
00192
00193 if (!status_topic_.empty())
00194 {
00195 ros::AdvertiseOptions ops = ros::AdvertiseOptions::create<hector_uav_msgs::MotorStatus>(
00196 status_topic_, 10,
00197 ros::SubscriberStatusCallback(), ros::SubscriberStatusCallback(),
00198 ros::VoidConstPtr(), &callback_queue_);
00199 motor_status_publisher_ = node_handle_->advertise(ops);
00200 }
00201
00202
00203
00204 Reset();
00205
00206
00207 ros::NodeHandle param(param_namespace_);
00208 param.getParam("k_m", propulsion_model_->parameters_.k_m);
00209 param.getParam("k_t", propulsion_model_->parameters_.k_t);
00210 param.getParam("CT0s", propulsion_model_->parameters_.CT0s);
00211 param.getParam("CT1s", propulsion_model_->parameters_.CT1s);
00212 param.getParam("CT2s", propulsion_model_->parameters_.CT2s);
00213 param.getParam("J_M", propulsion_model_->parameters_.J_M);
00214 param.getParam("l_m", propulsion_model_->parameters_.l_m);
00215 param.getParam("Psi", propulsion_model_->parameters_.Psi);
00216 param.getParam("R_A", propulsion_model_->parameters_.R_A);
00217 param.getParam("alpha_m", propulsion_model_->parameters_.alpha_m);
00218 param.getParam("beta_m", propulsion_model_->parameters_.beta_m);
00219
00220 std::cout << "Loaded the following quadrotor propulsion model parameters from namespace " << param.getNamespace() << ":" << std::endl;
00221 std::cout << "k_m = " << propulsion_model_->parameters_.k_m << std::endl;
00222 std::cout << "k_t = " << propulsion_model_->parameters_.k_t << std::endl;
00223 std::cout << "CT2s = " << propulsion_model_->parameters_.CT2s << std::endl;
00224 std::cout << "CT1s = " << propulsion_model_->parameters_.CT1s << std::endl;
00225 std::cout << "CT0s = " << propulsion_model_->parameters_.CT0s << std::endl;
00226 std::cout << "Psi = " << propulsion_model_->parameters_.Psi << std::endl;
00227 std::cout << "J_M = " << propulsion_model_->parameters_.J_M << std::endl;
00228 std::cout << "R_A = " << propulsion_model_->parameters_.R_A << std::endl;
00229 std::cout << "l_m = " << propulsion_model_->parameters_.l_m << std::endl;
00230 std::cout << "alpha_m = " << propulsion_model_->parameters_.alpha_m << std::endl;
00231 std::cout << "beta_m = " << propulsion_model_->parameters_.beta_m << std::endl;
00232
00233
00234
00235
00236
00237 updateConnection = event::Events::ConnectWorldUpdateStart(
00238 boost::bind(&GazeboQuadrotorPropulsion::Update, this));
00239 }
00240
00242
00243 void GazeboQuadrotorPropulsion::CommandCallback(const hector_uav_msgs::MotorPWMConstPtr& command)
00244 {
00245
00246 if (!motor_status_.on)
00247 ROS_WARN_NAMED("quadrotor_propulsion", "Received new motor command. Enabled motors.");
00248
00249 motor_status_.on = true;
00250 new_motor_voltages_.push(command);
00251 ROS_DEBUG_STREAM_NAMED("quadrotor_propulsion", "Received motor command valid at " << command->header.stamp);
00252 command_condition_.notify_all();
00253 }
00254
00256
00257 void GazeboQuadrotorPropulsion::Update()
00258 {
00259 Vector3 force, torque;
00260 boost::mutex::scoped_lock lock(command_mutex_);
00261
00262
00263 Time current_time = world->GetSimTime();
00264 Time dt = current_time - last_time_;
00265 last_time_ = current_time;
00266 if (dt <= 0.0) return;
00267
00268
00269 bool trigger = controlTimer.update();
00270 if (trigger && trigger_publisher_) {
00271 rosgraph_msgs::Clock clock;
00272 clock.clock = ros::Time(current_time.sec, current_time.nsec);
00273 trigger_publisher_.publish(clock);
00274
00275 ROS_DEBUG_STREAM_NAMED("quadrotor_propulsion", "Sent a trigger message at t = " << current_time.Double() << " (dt = " << (current_time - last_trigger_time_).Double() << ")");
00276 last_trigger_time_ = current_time;
00277 }
00278
00279
00280 callback_queue_.callAvailable();
00281
00282 do {
00283 if (!new_motor_voltages_.empty()) {
00284 hector_uav_msgs::MotorPWMConstPtr new_motor_voltage = new_motor_voltages_.front();
00285 Time new_time = Time(new_motor_voltage->header.stamp.sec, new_motor_voltage->header.stamp.nsec);
00286
00287 if (new_time == Time() || (new_time >= current_time - control_delay_ - dt - control_tolerance_ && new_time <= current_time - control_delay_ + control_tolerance_)) {
00288 motor_voltage_ = new_motor_voltage;
00289 new_motor_voltages_.pop();
00290 last_control_time_ = current_time;
00291 last_trigger_time_ = current_time;
00292 ROS_DEBUG_STREAM_NAMED("quadrotor_propulsion", "Using motor command valid at t = " << new_time.Double() << "s for simulation step at t = " << current_time.Double() << "s (dt = " << (current_time - new_time).Double() << "s)");
00293
00294
00295 } else if (new_time < current_time - control_delay_ - control_tolerance_) {
00296 ROS_DEBUG_NAMED("quadrotor_propulsion", "command received was too old: %fs", (new_time - current_time).Double());
00297 new_motor_voltages_.pop();
00298 continue;
00299
00300
00301 } else {
00302
00303 }
00304
00305 } else {
00306 if (!motor_status_.on || !trigger) break;
00307
00308
00309 ROS_DEBUG_NAMED("quadrotor_propulsion", "Waiting for command at simulation step t = %fs... last update was %fs ago", current_time.Double(), (current_time - last_control_time_).Double());
00310
00311 callback_queue_.callAvailable(ros::WallDuration(1.0));
00312 if (!new_motor_voltages_.empty()) continue;
00313
00314 ROS_ERROR_NAMED("quadrotor_propulsion", "Command timed out. Disabled motors.");
00315 motor_status_.on = false;
00316 }
00317
00318 } while(false);
00319
00320
00321 velocity = link->GetRelativeLinearVel();
00322 rate = link->GetRelativeAngularVel();
00323 propulsion_model_->u[0] = velocity.x;
00324 propulsion_model_->u[1] = -velocity.y;
00325 propulsion_model_->u[2] = -velocity.z;
00326 propulsion_model_->u[3] = rate.x;
00327 propulsion_model_->u[4] = -rate.y;
00328 propulsion_model_->u[5] = -rate.z;
00329 if (motor_status_.on && motor_voltage_ && motor_voltage_->pwm.size() >= 4) {
00330 propulsion_model_->u[6] = motor_voltage_->pwm[0] * supply_.voltage[0] / 255.0;
00331 propulsion_model_->u[7] = motor_voltage_->pwm[1] * supply_.voltage[0] / 255.0;
00332 propulsion_model_->u[8] = motor_voltage_->pwm[2] * supply_.voltage[0] / 255.0;
00333 propulsion_model_->u[9] = motor_voltage_->pwm[3] * supply_.voltage[0] / 255.0;
00334 } else {
00335 propulsion_model_->u[6] = 0.0;
00336 propulsion_model_->u[7] = 0.0;
00337 propulsion_model_->u[8] = 0.0;
00338 propulsion_model_->u[9] = 0.0;
00339 }
00340
00341
00342
00343
00344
00345
00346 checknan(propulsion_model_->u, "propulsion model input");
00347 checknan(propulsion_model_->x, "propulsion model state");
00348
00349
00350 quadrotorPropulsion(propulsion_model_->x.data(), propulsion_model_->u.data(), propulsion_model_->parameters_, dt.Double(), propulsion_model_->y.data(), propulsion_model_->x_pred.data());
00351 propulsion_model_->x = propulsion_model_->x_pred;
00352 force = force + checknan(Vector3(propulsion_model_->y[0], -propulsion_model_->y[1], propulsion_model_->y[2]), "propulsion model force");
00353 torque = torque + checknan(Vector3(propulsion_model_->y[3], -propulsion_model_->y[4], -propulsion_model_->y[5]), "propulsion model torque");
00354
00355
00356
00357
00358
00359
00360
00361 if (wrench_publisher_) {
00362 wrench_.force.x = force.x;
00363 wrench_.force.y = force.y;
00364 wrench_.force.z = force.z;
00365 wrench_.torque.x = torque.x;
00366 wrench_.torque.y = torque.y;
00367 wrench_.torque.z = torque.z;
00368 wrench_publisher_.publish(wrench_);
00369 }
00370
00371
00372 link->AddRelativeForce(force);
00373 link->AddRelativeTorque(torque - link->GetInertial()->GetCoG().GetCrossProd(force));
00374
00375
00376 if (motor_status_publisher_ && trigger ) {
00377 motor_status_.header.stamp = ros::Time(current_time.sec, current_time.nsec);
00378
00379 motor_status_.voltage.resize(4);
00380 motor_status_.voltage[0] = propulsion_model_->u[6];
00381 motor_status_.voltage[1] = propulsion_model_->u[7];
00382 motor_status_.voltage[2] = propulsion_model_->u[8];
00383 motor_status_.voltage[3] = propulsion_model_->u[9];
00384
00385 motor_status_.frequency.resize(4);
00386 motor_status_.frequency[0] = propulsion_model_->y[6];
00387 motor_status_.frequency[1] = propulsion_model_->y[7];
00388 motor_status_.frequency[2] = propulsion_model_->y[8];
00389 motor_status_.frequency[3] = propulsion_model_->y[9];
00390 motor_status_.running = motor_status_.frequency[0] > 1.0 && motor_status_.frequency[1] > 1.0 && motor_status_.frequency[2] > 1.0 && motor_status_.frequency[3] > 1.0;
00391
00392 motor_status_.current.resize(4);
00393 motor_status_.current[0] = propulsion_model_->y[10];
00394 motor_status_.current[1] = propulsion_model_->y[11];
00395 motor_status_.current[2] = propulsion_model_->y[12];
00396 motor_status_.current[3] = propulsion_model_->y[13];
00397 motor_status_publisher_.publish(motor_status_);
00398 last_motor_status_time_ = current_time;
00399 }
00400 }
00401
00403
00404 void GazeboQuadrotorPropulsion::Reset()
00405 {
00406 propulsion_model_->x.assign(0.0);
00407 propulsion_model_->x_pred.assign(0.0);
00408 last_control_time_ = Time();
00409 last_motor_status_time_ = Time();
00410 new_motor_voltages_ = std::queue<hector_uav_msgs::MotorPWMConstPtr>();
00411 motor_voltage_.reset();
00412 }
00413
00415
00416 void GazeboQuadrotorPropulsion::QueueThread()
00417 {
00418 static const double timeout = 0.01;
00419
00420 while (node_handle_->ok())
00421 {
00422 callback_queue_.callAvailable(ros::WallDuration(timeout));
00423 }
00424 }
00425
00426
00427 GZ_REGISTER_MODEL_PLUGIN(GazeboQuadrotorPropulsion)
00428
00429 }