1 /*%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3 Module: FGPropulsion.cpp
6 Purpose: Encapsulates the set of engines and tanks associated
9 ------------- Copyright (C) 2000 Jon S. Berndt (jsb@hal-pc.org) -------------
11 This program is free software; you can redistribute it and/or modify it under
12 the terms of the GNU Lesser General Public License as published by the Free Software
13 Foundation; either version 2 of the License, or (at your option) any later
16 This program is distributed in the hope that it will be useful, but WITHOUT
17 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
18 FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
21 You should have received a copy of the GNU Lesser General Public License along with
22 this program; if not, write to the Free Software Foundation, Inc., 59 Temple
23 Place - Suite 330, Boston, MA 02111-1307, USA.
25 Further information about the GNU Lesser General Public License can also be found on
26 the world wide web at http://www.gnu.org.
28 FUNCTIONAL DESCRIPTION
29 --------------------------------------------------------------------------------
30 The Propulsion class is the container for the entire propulsion system, which is
31 comprised of engines and tanks. Once the Propulsion class gets the config file,
32 it reads in information which is specific to a type of engine. Then:
34 1) The appropriate engine type instance is created
35 2) At least one tank object is created, and is linked to an engine.
37 At Run time each engines Calculate() method is called.
40 --------------------------------------------------------------------------------
43 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
45 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/
47 #include "FGPropulsion.h"
48 #include <models/propulsion/FGRocket.h>
49 #include <models/propulsion/FGTurbine.h>
50 #include <models/propulsion/FGPiston.h>
51 #include <models/propulsion/FGElectric.h>
52 #include <models/propulsion/FGTurboProp.h>
53 #include <input_output/FGPropertyManager.h>
54 #include <input_output/FGXMLParse.h>
55 #include <math/FGColumnVector3.h>
60 static const char *IdSrc = "$Id$";
61 static const char *IdHdr = ID_PROPULSION;
63 extern short debug_lvl;
67 /*%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
69 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/
71 FGPropulsion::FGPropulsion(FGFDMExec* exec) : FGModel(exec)
73 Name = "FGPropulsion";
75 numSelectedFuelTanks = numSelectedOxiTanks = 0;
76 numTanks = numEngines = 0;
77 numOxiTanks = numFuelTanks = 0;
78 ActiveEngine = -1; // -1: ALL, 0: Engine 1, 1: Engine 2 ...
82 TotalFuelQuantity = 0.0;
88 HaveElectricEngine = false;
93 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
95 FGPropulsion::~FGPropulsion()
97 for (unsigned int i=0; i<Engines.size(); i++) delete Engines[i];
103 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
105 bool FGPropulsion::Run(void)
109 if (FGModel::Run()) return true;
110 if (FDMExec->Holding()) return false;
112 double dt = State->Getdt();
114 vForces.InitMatrix();
115 vMoments.InitMatrix();
117 for (i=0; i<numEngines; i++) {
118 Engines[i]->Calculate();
119 vForces += Engines[i]->GetBodyForces(); // sum body frame forces
120 vMoments += Engines[i]->GetMoments(); // sum body frame moments
123 TotalFuelQuantity = 0.0;
124 for (i=0; i<numTanks; i++) {
125 Tanks[i]->Calculate( dt * rate );
126 if (Tanks[i]->GetType() == FGTank::ttFUEL) {
127 TotalFuelQuantity += Tanks[i]->GetContents();
131 if (refuel) DoRefuel( dt * rate );
136 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
138 bool FGPropulsion::GetSteadyState(void)
140 double currentThrust = 0, lastThrust=-1;
141 int steady_count,j=0;
144 vForces.InitMatrix();
145 vMoments.InitMatrix();
147 if (!FGModel::Run()) {
148 for (unsigned int i=0; i<numEngines; i++) {
149 Engines[i]->SetTrimMode(true);
152 while (!steady && j < 6000) {
153 Engines[i]->Calculate();
154 lastThrust = currentThrust;
155 currentThrust = Engines[i]->GetThrust();
156 if (fabs(lastThrust-currentThrust) < 0.0001) {
158 if (steady_count > 120) { steady=true; }
164 vForces += Engines[i]->GetBodyForces(); // sum body frame forces
165 vMoments += Engines[i]->GetMoments(); // sum body frame moments
166 Engines[i]->SetTrimMode(false);
175 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
177 bool FGPropulsion::ICEngineStart(void)
181 vForces.InitMatrix();
182 vMoments.InitMatrix();
184 for (unsigned int i=0; i<numEngines; i++) {
185 Engines[i]->SetTrimMode(true);
187 while (!Engines[i]->GetRunning() && j < 2000) {
188 Engines[i]->Calculate();
191 vForces += Engines[i]->GetBodyForces(); // sum body frame forces
192 vMoments += Engines[i]->GetMoments(); // sum body frame moments
193 Engines[i]->SetTrimMode(false);
198 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
200 bool FGPropulsion::Load(Element* el)
202 string type, engine_filename;
203 bool ThrottleAdded = false;
207 Element* engine_element = el->FindElement("engine");
208 while (engine_element) {
209 engine_filename = engine_element->GetAttributeValue("file");
211 if (engine_filename.empty()) {
212 cerr << "Engine definition did not supply an engine file." << endl;
216 engine_filename = FindEngineFullPathname(engine_filename);
217 document = LoadXMLDocument(engine_filename);
218 document->SetParent(engine_element);
220 type = document->GetName();
221 if (type == "piston_engine") {
222 HavePistonEngine = true;
223 if (!IsBound) bind();
224 Engines.push_back(new FGPiston(FDMExec, document, numEngines));
225 } else if (type == "turbine_engine") {
226 HaveTurbineEngine = true;
227 if (!IsBound) bind();
228 Engines.push_back(new FGTurbine(FDMExec, document, numEngines));
229 } else if (type == "turboprop_engine") {
230 HaveTurboPropEngine = true;
231 if (!IsBound) bind();
232 Engines.push_back(new FGTurboProp(FDMExec, document, numEngines));
233 } else if (type == "rocket_engine") {
234 HaveRocketEngine = true;
235 if (!IsBound) bind();
236 Engines.push_back(new FGRocket(FDMExec, document, numEngines));
237 } else if (type == "electric_engine") {
238 HaveElectricEngine = true;
239 if (!IsBound) bind();
240 Engines.push_back(new FGElectric(FDMExec, document, numEngines));
242 cerr << "Unknown engine type: " << type << endl;
247 ThrottleAdded = true;
251 engine_element = el->FindNextElement("engine");
255 // Process tank definitions
257 Element* tank_element = el->FindElement("tank");
258 while (tank_element) {
259 Tanks.push_back(new FGTank(FDMExec, tank_element));
260 if (Tanks.back()->GetType() == FGTank::ttFUEL) numFuelTanks++;
261 else if (Tanks.back()->GetType() == FGTank::ttOXIDIZER) numOxiTanks++;
262 else {cerr << "Unknown tank type specified." << endl; return false;}
264 tank_element = el->FindNextElement("tank");
266 numSelectedFuelTanks = numFuelTanks;
267 numSelectedOxiTanks = numOxiTanks;
269 CalculateTankInertias();
270 if (!ThrottleAdded) FCS->AddThrottle(); // need to have at least one throttle
275 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
277 string FGPropulsion::FindEngineFullPathname(string engine_filename)
279 string fullpath, localpath;
280 string enginePath = FDMExec->GetEnginePath();
281 string aircraftPath = FDMExec->GetFullAircraftPath();
282 ifstream* engine_file = new ifstream();
284 string separator = "/";
289 fullpath = enginePath + separator;
290 localpath = aircraftPath + separator + "Engines" + separator;
292 engine_file->open(string(fullpath + engine_filename + ".xml").c_str());
293 if ( !engine_file->is_open()) {
294 engine_file->open(string(localpath + engine_filename + ".xml").c_str());
295 if ( !engine_file->is_open()) {
296 cerr << " Could not open engine file: " << engine_filename << " in path "
297 << fullpath << " or " << localpath << endl;
300 return string(localpath + engine_filename + ".xml");
303 return string(fullpath + engine_filename + ".xml");
306 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
308 ifstream* FGPropulsion::FindEngineFile(string engine_filename)
310 string fullpath, localpath;
311 string enginePath = FDMExec->GetEnginePath();
312 string aircraftPath = FDMExec->GetFullAircraftPath();
313 ifstream* engine_file = new ifstream();
315 string separator = "/";
320 fullpath = enginePath + separator;
321 localpath = aircraftPath + separator + "Engines" + separator;
323 engine_file->open(string(fullpath + engine_filename + ".xml").c_str());
324 if ( !engine_file->is_open()) {
325 engine_file->open(string(localpath + engine_filename + ".xml").c_str());
326 if ( !engine_file->is_open()) {
327 cerr << " Could not open engine file: " << engine_filename << " in path "
328 << fullpath << " or " << localpath << endl;
334 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
336 string FGPropulsion::GetPropulsionStrings(string delimeter)
340 string PropulsionStrings = "";
341 bool firstime = true;
344 for (i=0; i<Engines.size(); i++) {
345 if (firstime) firstime = false;
346 else PropulsionStrings += delimeter;
348 PropulsionStrings += Engines[i]->GetEngineLabels(delimeter);
350 for (i=0; i<Tanks.size(); i++) {
351 if (Tanks[i]->GetType() == FGTank::ttFUEL) buf << delimeter << "Fuel Tank " << i;
352 else if (Tanks[i]->GetType() == FGTank::ttOXIDIZER) buf << delimeter << "Oxidizer Tank " << i;
355 return PropulsionStrings;
358 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
360 string FGPropulsion::GetPropulsionValues(string delimeter)
364 string PropulsionValues = "";
365 bool firstime = true;
368 for (i=0; i<Engines.size(); i++) {
369 if (firstime) firstime = false;
370 else PropulsionValues += delimeter;
372 PropulsionValues += Engines[i]->GetEngineValues(delimeter);
374 for (i=0; i<Tanks.size(); i++) {
376 buf << Tanks[i]->GetContents();
379 return PropulsionValues;
382 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
384 FGColumnVector3& FGPropulsion::GetTanksMoment(void)
386 iTank = Tanks.begin();
387 vXYZtank_arm.InitMatrix();
388 while (iTank < Tanks.end()) {
389 vXYZtank_arm(eX) += (*iTank)->GetXYZ(eX)*(*iTank)->GetContents();
390 vXYZtank_arm(eY) += (*iTank)->GetXYZ(eY)*(*iTank)->GetContents();
391 vXYZtank_arm(eZ) += (*iTank)->GetXYZ(eZ)*(*iTank)->GetContents();
397 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
399 double FGPropulsion::GetTanksWeight(void)
403 iTank = Tanks.begin();
404 while (iTank < Tanks.end()) {
405 Tw += (*iTank)->GetContents();
411 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
413 FGMatrix33& FGPropulsion::CalculateTankInertias(void)
418 if (size == 0) return tankJ;
420 tankJ = FGMatrix33();
422 for (unsigned int i=0; i<size; i++)
423 tankJ += MassBalance->GetPointmassInertia( lbtoslug * Tanks[i]->GetContents(),
424 Tanks[i]->GetXYZ() );
429 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
431 void FGPropulsion::SetMagnetos(int setting)
433 if (ActiveEngine < 0) {
434 for (unsigned i=0; i<Engines.size(); i++) {
435 // ToDo: first need to make sure the engine Type is really appropriate:
436 // do a check to see if it is of type Piston. This should be done for
437 // all of this kind of possibly across-the-board settings.
438 ((FGPiston*)Engines[i])->SetMagnetos(setting);
441 ((FGPiston*)Engines[ActiveEngine])->SetMagnetos(setting);
445 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
447 void FGPropulsion::SetStarter(int setting)
449 if (ActiveEngine < 0) {
450 for (unsigned i=0; i<Engines.size(); i++) {
452 Engines[i]->SetStarter(false);
454 Engines[i]->SetStarter(true);
458 Engines[ActiveEngine]->SetStarter(false);
460 Engines[ActiveEngine]->SetStarter(true);
464 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
466 void FGPropulsion::SetCutoff(int setting)
468 if (ActiveEngine < 0) {
469 for (unsigned i=0; i<Engines.size(); i++) {
471 ((FGTurbine*)Engines[i])->SetCutoff(false);
473 ((FGTurbine*)Engines[i])->SetCutoff(true);
477 ((FGTurbine*)Engines[ActiveEngine])->SetCutoff(false);
479 ((FGTurbine*)Engines[ActiveEngine])->SetCutoff(true);
483 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
485 void FGPropulsion::SetActiveEngine(int engine)
487 if (engine >= (int)Engines.size() || engine < 0)
490 ActiveEngine = engine;
493 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
495 double FGPropulsion::Transfer(int source, int target, double amount)
497 double shortage, overage;
502 shortage = Tanks[source]->Drain(amount);
507 overage = Tanks[target]->Fill(amount - shortage);
512 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
514 void FGPropulsion::DoRefuel(double time_slice)
518 double fillrate = 100 * time_slice; // 100 lbs/sec = 6000 lbs/min
519 int TanksNotFull = 0;
521 for (i=0; i<numTanks; i++) {
522 if (Tanks[i]->GetPctFull() < 99.99) ++TanksNotFull;
526 for (i=0; i<numTanks; i++) {
527 if (Tanks[i]->GetPctFull() < 99.99)
528 Transfer(-1, i, fillrate/TanksNotFull);
533 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
535 void FGPropulsion::SetFuelFreeze(bool f)
538 for (unsigned int i=0; i<numEngines; i++) {
539 Engines[i]->SetFuelFreeze(f);
543 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
545 void FGPropulsion::bind(void)
547 typedef double (FGPropulsion::*PMF)(int) const;
548 typedef int (FGPropulsion::*iPMF)(void) const;
552 if (HaveTurbineEngine) {
553 PropertyManager->Tie("propulsion/starter_cmd", this, (iPMF)0, &FGPropulsion::SetStarter, true);
554 PropertyManager->Tie("propulsion/cutoff_cmd", this, (iPMF)0, &FGPropulsion::SetCutoff, true);
557 if (HavePistonEngine) {
558 PropertyManager->Tie("propulsion/starter_cmd", this, (iPMF)0, &FGPropulsion::SetStarter, true);
559 PropertyManager->Tie("propulsion/magneto_cmd", this, (iPMF)0, &FGPropulsion::SetMagnetos, true);
562 PropertyManager->Tie("propulsion/active_engine", this, (iPMF)&FGPropulsion::GetActiveEngine,
563 &FGPropulsion::SetActiveEngine, true);
564 PropertyManager->Tie("propulsion/total-fuel-lbs", this, &FGPropulsion::GetTotalFuelQuantity);
567 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
569 void FGPropulsion::unbind(void)
571 if (!IsBound) return;
573 if (HaveTurbineEngine) {
574 PropertyManager->Untie("propulsion/starter_cmd");
575 PropertyManager->Untie("propulsion/cutoff_cmd");
577 if (HavePistonEngine) {
578 PropertyManager->Untie("propulsion/starter_cmd");
579 PropertyManager->Untie("propulsion/magneto_cmd");
581 PropertyManager->Untie("propulsion/active_engine");
582 PropertyManager->Untie("propulsion/total-fuel-lbs");
585 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
586 // The bitmasked value choices are as follows:
587 // unset: In this case (the default) JSBSim would only print
588 // out the normally expected messages, essentially echoing
589 // the config files as they are read. If the environment
590 // variable is not set, debug_lvl is set to 1 internally
591 // 0: This requests JSBSim not to output any messages
593 // 1: This value explicity requests the normal JSBSim
595 // 2: This value asks for a message to be printed out when
596 // a class is instantiated
597 // 4: When this value is set, a message is displayed when a
598 // FGModel object executes its Run() method
599 // 8: When this value is set, various runtime state variables
600 // are printed out periodically
601 // 16: When set various parameters are sanity checked and
602 // a message is printed out when they go out of bounds
604 void FGPropulsion::Debug(int from)
606 if (debug_lvl <= 0) return;
608 if (debug_lvl & 1) { // Standard console startup message output
609 if (from == 2) { // Loader
610 cout << endl << " Propulsion:" << endl;
613 if (debug_lvl & 2 ) { // Instantiation/Destruction notification
614 if (from == 0) cout << "Instantiated: FGPropulsion" << endl;
615 if (from == 1) cout << "Destroyed: FGPropulsion" << endl;
617 if (debug_lvl & 4 ) { // Run() method entry print for FGModel-derived objects
619 if (debug_lvl & 8 ) { // Runtime state variables
621 if (debug_lvl & 16) { // Sanity checking
623 if (debug_lvl & 64) {
624 if (from == 0) { // Constructor
625 cout << IdSrc << endl;
626 cout << IdHdr << endl;