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;
66 /*%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
68 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/
70 FGPropulsion::FGPropulsion(FGFDMExec* exec) : FGModel(exec)
72 Name = "FGPropulsion";
74 numSelectedFuelTanks = numSelectedOxiTanks = 0;
75 numTanks = numEngines = 0;
76 numOxiTanks = numFuelTanks = 0;
77 ActiveEngine = -1; // -1: ALL, 0: Engine 1, 1: Engine 2 ...
81 TotalFuelQuantity = 0.0;
87 HaveElectricEngine = false;
92 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
94 FGPropulsion::~FGPropulsion()
96 for (unsigned int i=0; i<Engines.size(); i++) delete Engines[i];
102 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
104 bool FGPropulsion::Run(void)
108 if (FGModel::Run()) return true;
109 if (FDMExec->Holding()) return false;
111 double dt = State->Getdt();
113 vForces.InitMatrix();
114 vMoments.InitMatrix();
116 for (i=0; i<numEngines; i++) {
117 Engines[i]->Calculate();
118 vForces += Engines[i]->GetBodyForces(); // sum body frame forces
119 vMoments += Engines[i]->GetMoments(); // sum body frame moments
122 TotalFuelQuantity = 0.0;
123 for (i=0; i<numTanks; i++) {
124 Tanks[i]->Calculate( dt * rate );
125 if (Tanks[i]->GetType() == FGTank::ttFUEL) {
126 TotalFuelQuantity += Tanks[i]->GetContents();
130 if (refuel) DoRefuel( dt * rate );
135 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
137 bool FGPropulsion::GetSteadyState(void)
139 double currentThrust = 0, lastThrust=-1;
140 int steady_count,j=0;
143 vForces.InitMatrix();
144 vMoments.InitMatrix();
146 if (!FGModel::Run()) {
147 for (unsigned int i=0; i<numEngines; i++) {
148 Engines[i]->SetTrimMode(true);
151 while (!steady && j < 6000) {
152 Engines[i]->Calculate();
153 lastThrust = currentThrust;
154 currentThrust = Engines[i]->GetThrust();
155 if (fabs(lastThrust-currentThrust) < 0.0001) {
157 if (steady_count > 120) { steady=true; }
163 vForces += Engines[i]->GetBodyForces(); // sum body frame forces
164 vMoments += Engines[i]->GetMoments(); // sum body frame moments
165 Engines[i]->SetTrimMode(false);
174 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
176 bool FGPropulsion::ICEngineStart(void)
180 vForces.InitMatrix();
181 vMoments.InitMatrix();
183 for (unsigned int i=0; i<numEngines; i++) {
184 Engines[i]->SetTrimMode(true);
186 while (!Engines[i]->GetRunning() && j < 2000) {
187 Engines[i]->Calculate();
190 vForces += Engines[i]->GetBodyForces(); // sum body frame forces
191 vMoments += Engines[i]->GetMoments(); // sum body frame moments
192 Engines[i]->SetTrimMode(false);
197 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
199 bool FGPropulsion::Load(Element* el)
201 string type, engine_filename;
202 bool ThrottleAdded = false;
206 Element* engine_element = el->FindElement("engine");
207 while (engine_element) {
208 engine_filename = engine_element->GetAttributeValue("file");
210 if (engine_filename.empty()) {
211 cerr << "Engine definition did not supply an engine file." << endl;
215 engine_filename = FindEngineFullPathname(engine_filename);
216 document = LoadXMLDocument(engine_filename);
217 document->SetParent(engine_element);
219 type = document->GetName();
220 if (type == "piston_engine") {
221 HavePistonEngine = true;
222 if (!IsBound) bind();
223 Engines.push_back(new FGPiston(FDMExec, document, numEngines));
224 } else if (type == "turbine_engine") {
225 HaveTurbineEngine = true;
226 if (!IsBound) bind();
227 Engines.push_back(new FGTurbine(FDMExec, document, numEngines));
228 } else if (type == "turboprop_engine") {
229 HaveTurboPropEngine = true;
230 if (!IsBound) bind();
231 Engines.push_back(new FGTurboProp(FDMExec, document, numEngines));
232 } else if (type == "rocket_engine") {
233 HaveRocketEngine = true;
234 if (!IsBound) bind();
235 Engines.push_back(new FGRocket(FDMExec, document, numEngines));
236 } else if (type == "electric_engine") {
237 HaveElectricEngine = true;
238 if (!IsBound) bind();
239 Engines.push_back(new FGElectric(FDMExec, document, numEngines));
241 cerr << "Unknown engine type: " << type << endl;
246 ThrottleAdded = true;
250 engine_element = el->FindNextElement("engine");
254 // Process tank definitions
256 Element* tank_element = el->FindElement("tank");
257 while (tank_element) {
258 Tanks.push_back(new FGTank(FDMExec, tank_element));
259 if (Tanks.back()->GetType() == FGTank::ttFUEL) numFuelTanks++;
260 else if (Tanks.back()->GetType() == FGTank::ttOXIDIZER) numOxiTanks++;
261 else {cerr << "Unknown tank type specified." << endl; return false;}
263 tank_element = el->FindNextElement("tank");
265 numSelectedFuelTanks = numFuelTanks;
266 numSelectedOxiTanks = numOxiTanks;
268 CalculateTankInertias();
269 if (!ThrottleAdded) FCS->AddThrottle(); // need to have at least one throttle
274 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
276 string FGPropulsion::FindEngineFullPathname(string engine_filename)
278 string fullpath, localpath;
279 string enginePath = FDMExec->GetEnginePath();
280 string aircraftPath = FDMExec->GetFullAircraftPath();
281 ifstream* engine_file = new ifstream();
283 string separator = "/";
288 fullpath = enginePath + separator;
289 localpath = aircraftPath + separator + "Engines" + separator;
291 engine_file->open(string(fullpath + engine_filename + ".xml").c_str());
292 if ( !engine_file->is_open()) {
293 engine_file->open(string(localpath + engine_filename + ".xml").c_str());
294 if ( !engine_file->is_open()) {
295 cerr << " Could not open engine file: " << engine_filename << " in path "
296 << fullpath << " or " << localpath << endl;
299 return string(localpath + engine_filename + ".xml");
302 return string(fullpath + engine_filename + ".xml");
305 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
307 ifstream* FGPropulsion::FindEngineFile(string engine_filename)
309 string fullpath, localpath;
310 string enginePath = FDMExec->GetEnginePath();
311 string aircraftPath = FDMExec->GetFullAircraftPath();
312 ifstream* engine_file = new ifstream();
314 string separator = "/";
319 fullpath = enginePath + separator;
320 localpath = aircraftPath + separator + "Engines" + separator;
322 engine_file->open(string(fullpath + engine_filename + ".xml").c_str());
323 if ( !engine_file->is_open()) {
324 engine_file->open(string(localpath + engine_filename + ".xml").c_str());
325 if ( !engine_file->is_open()) {
326 cerr << " Could not open engine file: " << engine_filename << " in path "
327 << fullpath << " or " << localpath << endl;
333 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
335 string FGPropulsion::GetPropulsionStrings(string delimeter)
339 string PropulsionStrings = "";
340 bool firstime = true;
343 for (i=0; i<Engines.size(); i++) {
344 if (firstime) firstime = false;
345 else PropulsionStrings += delimeter;
347 PropulsionStrings += Engines[i]->GetEngineLabels(delimeter);
349 for (i=0; i<Tanks.size(); i++) {
350 if (Tanks[i]->GetType() == FGTank::ttFUEL) buf << delimeter << "Fuel Tank " << i;
351 else if (Tanks[i]->GetType() == FGTank::ttOXIDIZER) buf << delimeter << "Oxidizer Tank " << i;
354 return PropulsionStrings;
357 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
359 string FGPropulsion::GetPropulsionValues(string delimeter)
363 string PropulsionValues = "";
364 bool firstime = true;
367 for (i=0; i<Engines.size(); i++) {
368 if (firstime) firstime = false;
369 else PropulsionValues += delimeter;
371 PropulsionValues += Engines[i]->GetEngineValues(delimeter);
373 for (i=0; i<Tanks.size(); i++) {
375 buf << Tanks[i]->GetContents();
378 return PropulsionValues;
381 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
383 FGColumnVector3& FGPropulsion::GetTanksMoment(void)
385 iTank = Tanks.begin();
386 vXYZtank_arm.InitMatrix();
387 while (iTank < Tanks.end()) {
388 vXYZtank_arm(eX) += (*iTank)->GetXYZ(eX)*(*iTank)->GetContents();
389 vXYZtank_arm(eY) += (*iTank)->GetXYZ(eY)*(*iTank)->GetContents();
390 vXYZtank_arm(eZ) += (*iTank)->GetXYZ(eZ)*(*iTank)->GetContents();
396 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
398 double FGPropulsion::GetTanksWeight(void)
402 iTank = Tanks.begin();
403 while (iTank < Tanks.end()) {
404 Tw += (*iTank)->GetContents();
410 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
412 FGMatrix33& FGPropulsion::CalculateTankInertias(void)
417 if (size == 0) return tankJ;
419 tankJ = FGMatrix33();
421 for (unsigned int i=0; i<size; i++)
422 tankJ += MassBalance->GetPointmassInertia( lbtoslug * Tanks[i]->GetContents(),
423 Tanks[i]->GetXYZ() );
428 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
430 void FGPropulsion::SetMagnetos(int setting)
432 if (ActiveEngine < 0) {
433 for (unsigned i=0; i<Engines.size(); i++) {
434 // ToDo: first need to make sure the engine Type is really appropriate:
435 // do a check to see if it is of type Piston. This should be done for
436 // all of this kind of possibly across-the-board settings.
437 ((FGPiston*)Engines[i])->SetMagnetos(setting);
440 ((FGPiston*)Engines[ActiveEngine])->SetMagnetos(setting);
444 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
446 void FGPropulsion::SetStarter(int setting)
448 if (ActiveEngine < 0) {
449 for (unsigned i=0; i<Engines.size(); i++) {
451 Engines[i]->SetStarter(false);
453 Engines[i]->SetStarter(true);
457 Engines[ActiveEngine]->SetStarter(false);
459 Engines[ActiveEngine]->SetStarter(true);
463 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
465 void FGPropulsion::SetCutoff(int setting)
467 if (ActiveEngine < 0) {
468 for (unsigned i=0; i<Engines.size(); i++) {
470 ((FGTurbine*)Engines[i])->SetCutoff(false);
472 ((FGTurbine*)Engines[i])->SetCutoff(true);
476 ((FGTurbine*)Engines[ActiveEngine])->SetCutoff(false);
478 ((FGTurbine*)Engines[ActiveEngine])->SetCutoff(true);
482 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
484 void FGPropulsion::SetActiveEngine(int engine)
486 if (engine >= (int)Engines.size() || engine < 0)
489 ActiveEngine = engine;
492 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
494 double FGPropulsion::Transfer(int source, int target, double amount)
496 double shortage, overage;
501 shortage = Tanks[source]->Drain(amount);
506 overage = Tanks[target]->Fill(amount - shortage);
511 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
513 void FGPropulsion::DoRefuel(double time_slice)
517 double fillrate = 100 * time_slice; // 100 lbs/sec = 6000 lbs/min
518 int TanksNotFull = 0;
520 for (i=0; i<numTanks; i++) {
521 if (Tanks[i]->GetPctFull() < 99.99) ++TanksNotFull;
525 for (i=0; i<numTanks; i++) {
526 if (Tanks[i]->GetPctFull() < 99.99)
527 Transfer(-1, i, fillrate/TanksNotFull);
532 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
534 void FGPropulsion::SetFuelFreeze(bool f)
537 for (unsigned int i=0; i<numEngines; i++) {
538 Engines[i]->SetFuelFreeze(f);
542 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
544 void FGPropulsion::bind(void)
546 typedef double (FGPropulsion::*PMF)(int) const;
547 typedef int (FGPropulsion::*iPMF)(void) const;
551 if (HaveTurbineEngine) {
552 PropertyManager->Tie("propulsion/starter_cmd", this, (iPMF)0, &FGPropulsion::SetStarter, true);
553 PropertyManager->Tie("propulsion/cutoff_cmd", this, (iPMF)0, &FGPropulsion::SetCutoff, true);
556 if (HavePistonEngine) {
557 PropertyManager->Tie("propulsion/starter_cmd", this, (iPMF)0, &FGPropulsion::SetStarter, true);
558 PropertyManager->Tie("propulsion/magneto_cmd", this, (iPMF)0, &FGPropulsion::SetMagnetos, true);
561 PropertyManager->Tie("propulsion/active_engine", this, (iPMF)&FGPropulsion::GetActiveEngine,
562 &FGPropulsion::SetActiveEngine, true);
563 PropertyManager->Tie("propulsion/total-fuel-lbs", this, &FGPropulsion::GetTotalFuelQuantity);
566 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
568 void FGPropulsion::unbind(void)
570 if (!IsBound) return;
572 if (HaveTurbineEngine) {
573 PropertyManager->Untie("propulsion/starter_cmd");
574 PropertyManager->Untie("propulsion/cutoff_cmd");
576 if (HavePistonEngine) {
577 PropertyManager->Untie("propulsion/starter_cmd");
578 PropertyManager->Untie("propulsion/magneto_cmd");
580 PropertyManager->Untie("propulsion/active_engine");
581 PropertyManager->Untie("propulsion/total-fuel-lbs");
584 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
585 // The bitmasked value choices are as follows:
586 // unset: In this case (the default) JSBSim would only print
587 // out the normally expected messages, essentially echoing
588 // the config files as they are read. If the environment
589 // variable is not set, debug_lvl is set to 1 internally
590 // 0: This requests JSBSim not to output any messages
592 // 1: This value explicity requests the normal JSBSim
594 // 2: This value asks for a message to be printed out when
595 // a class is instantiated
596 // 4: When this value is set, a message is displayed when a
597 // FGModel object executes its Run() method
598 // 8: When this value is set, various runtime state variables
599 // are printed out periodically
600 // 16: When set various parameters are sanity checked and
601 // a message is printed out when they go out of bounds
603 void FGPropulsion::Debug(int from)
605 if (debug_lvl <= 0) return;
607 if (debug_lvl & 1) { // Standard console startup message output
608 if (from == 2) { // Loader
609 cout << endl << " Propulsion:" << endl;
612 if (debug_lvl & 2 ) { // Instantiation/Destruction notification
613 if (from == 0) cout << "Instantiated: FGPropulsion" << endl;
614 if (from == 1) cout << "Destroyed: FGPropulsion" << endl;
616 if (debug_lvl & 4 ) { // Run() method entry print for FGModel-derived objects
618 if (debug_lvl & 8 ) { // Runtime state variables
620 if (debug_lvl & 16) { // Sanity checking
622 if (debug_lvl & 64) {
623 if (from == 0) { // Constructor
624 cout << IdSrc << endl;
625 cout << IdHdr << endl;