$search
00001 //================================================================================================= 00002 // Copyright (c) 2012, Johannes Meyer, TU Darmstadt 00003 // All rights reserved. 00004 00005 // Redistribution and use in source and binary forms, with or without 00006 // modification, are permitted provided that the following conditions are met: 00007 // * Redistributions of source code must retain the above copyright 00008 // notice, this list of conditions and the following disclaimer. 00009 // * Redistributions in binary form must reproduce the above copyright 00010 // notice, this list of conditions and the following disclaimer in the 00011 // documentation and/or other materials provided with the distribution. 00012 // * Neither the name of the Flight Systems and Automatic Control group, 00013 // TU Darmstadt, nor the names of its contributors may be used to 00014 // endorse or promote products derived from this software without 00015 // specific prior written permission. 00016 00017 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 00018 // ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 00019 // WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 00020 // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY 00021 // DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 00022 // (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 00023 // LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 00024 // ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 00025 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 00026 // SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 00027 //================================================================================================= 00028 00029 #include <hector_quadrotor_controller/quadrotor_propulsion.h> 00030 00031 #include <gazebo/Sensor.hh> 00032 #include <gazebo/Global.hh> 00033 #include <gazebo/XMLConfig.hh> 00034 #include <gazebo/Simulator.hh> 00035 #include <gazebo/gazebo.h> 00036 #include <gazebo/World.hh> 00037 #include <gazebo/PhysicsEngine.hh> 00038 #include <gazebo/GazeboError.hh> 00039 #include <gazebo/ControllerFactory.hh> 00040 00041 extern "C" { 00042 #include "quadrotorPropulsion/quadrotorPropulsion.h" 00043 #include "quadrotorPropulsion/quadrotorPropulsion_initialize.h" 00044 } 00045 00046 #include <boost/array.hpp> 00047 00048 using namespace gazebo; 00049 00050 GZ_REGISTER_DYNAMIC_CONTROLLER("hector_gazebo_quadrotor_propulsion", GazeboQuadrotorPropulsion) 00051 00052 template <typename T> static inline T checknan(const T& value, const std::string& text = "") { 00053 if (!(value == value)) { 00054 if (!text.empty()) std::cerr << text << " contains **!?* Nan values!" << std::endl; 00055 return T(); 00056 } 00057 return value; 00058 } 00059 00060 // extern void quadrotorPropulsion(const real_T xin[4], const real_T uin[10], const PropulsionParameters parameter, real_T dt, real_T y[6], real_T xpred[4]); 00061 struct GazeboQuadrotorPropulsion::PropulsionModel { 00062 PropulsionParameters parameters_; 00063 boost::array<real_T,4> x; 00064 boost::array<real_T,4> x_pred; 00065 boost::array<real_T,10> u; 00066 boost::array<real_T,14> y; 00067 }; 00068 00069 GazeboQuadrotorPropulsion::GazeboQuadrotorPropulsion(Entity *parent) 00070 : Controller(parent) 00071 { 00072 parent_ = dynamic_cast<Model*>(parent); 00073 if (!parent_) gzthrow("GazeboQuadrotorPropulsion controller requires a Model as its parent"); 00074 00075 if (!ros::isInitialized()) 00076 { 00077 int argc = 0; 00078 char** argv = NULL; 00079 ros::init(argc,argv, "gazebo", ros::init_options::NoSigintHandler|ros::init_options::AnonymousName); 00080 } 00081 00082 Param::Begin(¶meters); 00083 namespace_ = new ParamT<std::string>("robotNamespace", "", false); 00084 param_namespace_ = new ParamT<std::string>("paramNamespace", "~/quadrotor_propulsion", false); 00085 body_name_ = new ParamT<std::string>("bodyName", "", true); 00086 control_rate_ = new ParamT<Time>("controlRate", 100.0, false); 00087 voltage_topic_ = new ParamT<std::string>("voltageTopicName", "motor_pwm", false); 00088 wrench_topic_ = new ParamT<std::string>("wrenchTopic", "wrench_out", false); 00089 status_topic_ = new ParamT<std::string>("statusTopic", "motor_status", false); 00090 control_delay_param_ = new ParamT<Time>("controlDelay", 0.0, false); 00091 Param::End(); 00092 00093 // initialize propulsion model 00094 quadrotorPropulsion_initialize(); 00095 propulsion_model_ = new PropulsionModel; 00096 } 00097 00099 // Destructor 00100 GazeboQuadrotorPropulsion::~GazeboQuadrotorPropulsion() 00101 { 00102 delete namespace_; 00103 delete param_namespace_; 00104 delete body_name_; 00105 delete control_rate_; 00106 delete voltage_topic_; 00107 delete wrench_topic_; 00108 delete status_topic_; 00109 delete control_delay_param_; 00110 00111 delete propulsion_model_; 00112 } 00113 00115 // Load the controller 00116 void GazeboQuadrotorPropulsion::LoadChild(XMLConfigNode *node) 00117 { 00118 namespace_->Load(node); 00119 param_namespace_->Load(node); 00120 body_name_->Load(node); 00121 control_rate_->Load(node); 00122 voltage_topic_->Load(node); 00123 wrench_topic_->Load(node); 00124 status_topic_->Load(node); 00125 control_delay_param_->Load(node); 00126 00127 // assert that the body by body_name_ exists 00128 body_ = dynamic_cast<Body*>(parent_->GetBody(**body_name_)); 00129 if (!body_) gzthrow("gazebo_aerodynamics plugin error: body_name_: " << **body_name_ << "does not exist\n"); 00130 00131 // set control timer/delay parameters 00132 control_period_ = (**control_rate_ > 0) ? (1.0 / **control_rate_) : 0.0; 00133 control_delay_ = (**control_delay_param_ > 0) ? **control_delay_param_ : Time(control_period_.Double() / 2); 00134 control_tolerance_ = Time((control_period_> 0 ? control_period_.Double() : World::Instance()->GetPhysicsEngine()->GetStepTime().Double()) / 2); 00135 00136 // check update rate against world physics update rate 00137 // should be equal or higher to guarantee the wrench applied is not "diluted" 00138 if (this->updatePeriod > 0 && 00139 (gazebo::World::Instance()->GetPhysicsEngine()->GetUpdateRate() > 1.0/this->updatePeriod)) 00140 ROS_ERROR_NAMED(this->GetName(), "gazebo_aerodynamics controller update rate is less than physics update rate, wrench applied will be diluted (applied intermittently)"); 00141 00142 // get model parameters 00143 ros::NodeHandle param(**param_namespace_); 00144 param.getParam("k_t", propulsion_model_->parameters_.k_t); 00145 param.getParam("CT0s", propulsion_model_->parameters_.CT0s); 00146 param.getParam("CT1s", propulsion_model_->parameters_.CT1s); 00147 param.getParam("CT2s", propulsion_model_->parameters_.CT2s); 00148 param.getParam("J_M", propulsion_model_->parameters_.J_M); 00149 param.getParam("l_m", propulsion_model_->parameters_.l_m); 00150 param.getParam("Psi", propulsion_model_->parameters_.Psi); 00151 param.getParam("R_A", propulsion_model_->parameters_.R_A); 00152 00153 00154 std::cout << "Loaded the following quadrotor propulsion model parameters from namespace " << param.getNamespace() << ":" << std::endl; 00155 std::cout << "k_t = " << propulsion_model_->parameters_.k_t << std::endl; 00156 std::cout << "CT2s = " << propulsion_model_->parameters_.CT2s << std::endl; 00157 std::cout << "CT1s = " << propulsion_model_->parameters_.CT1s << std::endl; 00158 std::cout << "CT0s = " << propulsion_model_->parameters_.CT0s << std::endl; 00159 std::cout << "Psi = " << propulsion_model_->parameters_.Psi << std::endl; 00160 std::cout << "J_M = " << propulsion_model_->parameters_.J_M << std::endl; 00161 std::cout << "R_A = " << propulsion_model_->parameters_.R_A << std::endl; 00162 std::cout << "l_m = " << propulsion_model_->parameters_.l_m << std::endl; 00163 } 00164 00166 // Initialize the controller 00167 void GazeboQuadrotorPropulsion::InitChild() 00168 { 00169 node_handle_ = new ros::NodeHandle(**namespace_); 00170 00171 // subscribe command 00172 if (!(**voltage_topic_).empty()) 00173 { 00174 ros::SubscribeOptions ops = ros::SubscribeOptions::create<hector_uav_msgs::MotorPWM>( 00175 **voltage_topic_, 1, 00176 boost::bind(&GazeboQuadrotorPropulsion::CommandCallback, this, _1), 00177 ros::VoidPtr(), &callback_queue_); 00178 voltage_subscriber_ = node_handle_->subscribe(ops); 00179 } 00180 00181 // publish wrench 00182 if (!(**wrench_topic_).empty()) 00183 { 00184 ros::AdvertiseOptions ops = ros::AdvertiseOptions::create<geometry_msgs::Wrench>( 00185 **wrench_topic_, 10, 00186 ros::SubscriberStatusCallback(), ros::SubscriberStatusCallback(), 00187 ros::VoidConstPtr(), &callback_queue_); 00188 wrench_publisher_ = node_handle_->advertise(ops); 00189 } 00190 00191 // publish motor_status 00192 if (!(**status_topic_).empty()) 00193 { 00194 ros::AdvertiseOptions ops = ros::AdvertiseOptions::create<hector_uav_msgs::MotorStatus>( 00195 **status_topic_, 10, 00196 ros::SubscriberStatusCallback(), ros::SubscriberStatusCallback(), 00197 ros::VoidConstPtr(), &callback_queue_); 00198 motor_status_publisher_ = node_handle_->advertise(ops); 00199 } 00200 00201 callback_queue_thread_ = boost::thread( boost::bind( &GazeboQuadrotorPropulsion::QueueThread,this ) ); 00202 00203 ResetChild(); 00204 } 00205 00207 // Callbacks 00208 void GazeboQuadrotorPropulsion::CommandCallback(const hector_uav_msgs::MotorPWMConstPtr& command) 00209 { 00210 boost::mutex::scoped_lock lock(command_mutex_); 00211 motor_status_.on = true; 00212 new_motor_voltages_.push_back(command); 00213 ROS_DEBUG_STREAM("Received motor command valid at " << command->header.stamp); 00214 command_condition_.notify_all(); 00215 } 00216 00218 // Update the controller 00219 void GazeboQuadrotorPropulsion::UpdateChild() 00220 { 00221 Vector3 force, torque; 00222 boost::mutex::scoped_lock lock(command_mutex_); 00223 00224 // Get simulator time 00225 Time current_time = Simulator::Instance()->GetSimTime(); 00226 double dt = current_time - lastUpdate; 00227 if (dt <= 0.0) return; 00228 00229 // Get new commands/state 00230 // callback_queue_.callAvailable(); 00231 00232 while(1) { 00233 if (!new_motor_voltages_.empty()) { 00234 hector_uav_msgs::MotorPWMConstPtr new_motor_voltage = new_motor_voltages_.front(); 00235 Time new_time = Time(new_motor_voltage->header.stamp.sec, new_motor_voltage->header.stamp.nsec); 00236 if (new_time == Time() || (new_time >= current_time - control_delay_ - control_tolerance_ && new_time <= current_time - control_delay_ + control_tolerance_)) { 00237 motor_voltage_ = new_motor_voltage; 00238 new_motor_voltages_.pop_front(); 00239 last_control_time_ = current_time - control_delay_; 00240 ROS_DEBUG_STREAM("Using motor command valid at " << new_time << " for simulation step at " << current_time << " (dt = " << (current_time - new_time) << ")"); 00241 break; 00242 } else if (new_time < current_time - control_delay_ - control_tolerance_) { 00243 ROS_DEBUG_NAMED("quadrotor_propulsion", "command received was too old: %f s", (new_time - current_time).Double()); 00244 new_motor_voltages_.pop_front(); 00245 continue; 00246 } 00247 } 00248 00249 if (new_motor_voltages_.empty() && motor_status_.on && control_period_ > 0 && current_time > last_control_time_ + control_period_ + control_tolerance_) { 00250 ROS_DEBUG_NAMED("quadrotor_propulsion", "waiting for command..."); 00251 if (command_condition_.timed_wait(lock, ros::Duration(100.0 * control_period_).toBoost())) continue; 00252 ROS_ERROR_NAMED("quadrotor_propulsion", "command timed out."); 00253 motor_status_.on = false; 00254 } 00255 00256 break; 00257 } 00258 00259 // fill input vector u for propulsion model 00260 velocity = body_->GetRelativeLinearVel(); 00261 rate = body_->GetRelativeAngularVel(); 00262 propulsion_model_->u[0] = velocity.x; 00263 propulsion_model_->u[1] = -velocity.y; 00264 propulsion_model_->u[2] = -velocity.z; 00265 propulsion_model_->u[3] = rate.x; 00266 propulsion_model_->u[4] = -rate.y; 00267 propulsion_model_->u[5] = -rate.z; 00268 if (motor_voltage_ && motor_voltage_->pwm.size() >= 4) { 00269 propulsion_model_->u[6] = motor_voltage_->pwm[0] * 14.8 / 255.0; 00270 propulsion_model_->u[7] = motor_voltage_->pwm[1] * 14.8 / 255.0; 00271 propulsion_model_->u[8] = motor_voltage_->pwm[2] * 14.8 / 255.0; 00272 propulsion_model_->u[9] = motor_voltage_->pwm[3] * 14.8 / 255.0; 00273 } else { 00274 propulsion_model_->u[6] = 0.0; 00275 propulsion_model_->u[7] = 0.0; 00276 propulsion_model_->u[8] = 0.0; 00277 propulsion_model_->u[9] = 0.0; 00278 } 00279 00280 // std::cout << "u = [ "; 00281 // for(std::size_t i = 0; i < propulsion_model_->u.size(); ++i) 00282 // std::cout << propulsion_model_->u[i] << " "; 00283 // std::cout << "]" << std::endl; 00284 00285 checknan(propulsion_model_->u, "propulsion model input"); 00286 checknan(propulsion_model_->x, "propulsion model state"); 00287 00288 // update propulsion model 00289 quadrotorPropulsion(propulsion_model_->x.data(), propulsion_model_->u.data(), propulsion_model_->parameters_, dt, propulsion_model_->y.data(), propulsion_model_->x_pred.data()); 00290 propulsion_model_->x = propulsion_model_->x_pred; 00291 force = force + checknan(Vector3(propulsion_model_->y[0], -propulsion_model_->y[1], propulsion_model_->y[2]), "propulsion model force"); 00292 torque = torque + checknan(Vector3(propulsion_model_->y[3], -propulsion_model_->y[4], -propulsion_model_->y[5]), "propulsion model torque"); 00293 00294 // std::cout << "y = [ "; 00295 // for(std::size_t i = 0; i < propulsion_model_->y.size(); ++i) 00296 // std::cout << propulsion_model_->y[i] << " "; 00297 // std::cout << "]" << std::endl; 00298 00299 // publish wrench 00300 if (wrench_publisher_) { 00301 wrench_.force.x = force.x; 00302 wrench_.force.y = force.y; 00303 wrench_.force.z = force.z; 00304 wrench_.torque.x = torque.x; 00305 wrench_.torque.y = torque.y; 00306 wrench_.torque.z = torque.z; 00307 wrench_publisher_.publish(wrench_); 00308 } 00309 00310 if (motor_status_publisher_ && current_time >= last_motor_status_time_ + control_period_) { 00311 motor_status_.header.stamp = ros::Time(current_time.sec, current_time.nsec); 00312 motor_status_.running = propulsion_model_->x[0] > 0.0 && propulsion_model_->x[1] > 0.0 && propulsion_model_->x[2] > 0.0 && propulsion_model_->x[3] > 0.0; 00313 motor_status_.frequency.resize(4); 00314 motor_status_.frequency[0] = propulsion_model_->y[6]; 00315 motor_status_.frequency[1] = propulsion_model_->y[7]; 00316 motor_status_.frequency[2] = propulsion_model_->y[8]; 00317 motor_status_.frequency[3] = propulsion_model_->y[9]; 00318 motor_status_.current.resize(4); 00319 motor_status_.current[0] = propulsion_model_->y[10]; 00320 motor_status_.current[1] = propulsion_model_->y[11]; 00321 motor_status_.current[2] = propulsion_model_->y[12]; 00322 motor_status_.current[3] = propulsion_model_->y[13]; 00323 motor_status_publisher_.publish(motor_status_); 00324 last_motor_status_time_ = current_time; 00325 } 00326 00327 // set force and torque in gazebo 00328 body_->SetForce(force); 00329 body_->SetTorque(torque - body_->GetMass().GetCoG().GetCrossProd(force)); 00330 } 00331 00333 // Finalize the controller 00334 void GazeboQuadrotorPropulsion::FiniChild() 00335 { 00336 node_handle_->shutdown(); 00337 callback_queue_thread_.join(); 00338 00339 delete node_handle_; 00340 } 00341 00343 // Reset the controller 00344 void GazeboQuadrotorPropulsion::ResetChild() 00345 { 00346 propulsion_model_->x.assign(0.0); 00347 propulsion_model_->x_pred.assign(0.0); 00348 last_control_time_ = Time(); 00349 last_motor_status_time_ = Time(); 00350 new_motor_voltages_.clear(); 00351 motor_voltage_.reset(); 00352 } 00353 00355 // custom callback queue thread 00356 void GazeboQuadrotorPropulsion::QueueThread() 00357 { 00358 static const double timeout = 0.01; 00359 00360 while (node_handle_->ok()) 00361 { 00362 callback_queue_.callAvailable(ros::WallDuration(timeout)); 00363 } 00364 }