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 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 General Public License for more
21 You should have received a copy of the GNU 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 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;
203 bool ThrottleAdded = false;
205 FGXMLParse engine_file_parser;
206 ifstream* engine_file;
210 Element* engine_element = el->FindElement("engine");
211 while (engine_element) {
212 engine_filename = engine_element->GetAttributeValue("file");
214 if (engine_filename.empty()) {
215 cerr << "Engine definition did not supply an engine file." << endl;
219 engine_filename = FindEngineFullPathname(engine_filename);
220 readXML(engine_filename, engine_file_parser);
221 document = engine_file_parser.GetDocument(); // document holds the engine description
222 document->SetParent(engine_element);
224 type = document->GetName();
225 if (type == "piston_engine") {
226 HavePistonEngine = true;
227 if (!IsBound) bind();
228 Engines.push_back(new FGPiston(FDMExec, document, numEngines));
229 } else if (type == "turbine_engine") {
230 HaveTurbineEngine = true;
231 if (!IsBound) bind();
232 Engines.push_back(new FGTurbine(FDMExec, document, numEngines));
233 } else if (type == "turboprop_engine") {
234 HaveTurboPropEngine = true;
235 if (!IsBound) bind();
236 Engines.push_back(new FGTurboProp(FDMExec, document, numEngines));
237 } else if (type == "rocket_engine") {
238 HaveRocketEngine = true;
239 if (!IsBound) bind();
240 Engines.push_back(new FGRocket(FDMExec, document, numEngines));
241 } else if (type == "electric_engine") {
242 HaveElectricEngine = true;
243 if (!IsBound) bind();
244 Engines.push_back(new FGElectric(FDMExec, document, numEngines));
246 cerr << "Unknown engine type: " << type << endl;
251 ThrottleAdded = true;
255 engine_element = el->FindNextElement("engine");
256 engine_file_parser.reset();
259 // Process tank definitions
261 Element* tank_element = el->FindElement("tank");
262 while (tank_element) {
263 Tanks.push_back(new FGTank(FDMExec, tank_element));
264 if (Tanks.back()->GetType() == FGTank::ttFUEL) numFuelTanks++;
265 else if (Tanks.back()->GetType() == FGTank::ttOXIDIZER) numOxiTanks++;
266 else {cerr << "Unknown tank type specified." << endl; return false;}
268 tank_element = el->FindNextElement("tank");
270 numSelectedFuelTanks = numFuelTanks;
271 numSelectedOxiTanks = numOxiTanks;
273 CalculateTankInertias();
274 if (!ThrottleAdded) FCS->AddThrottle(); // need to have at least one throttle
279 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
281 string FGPropulsion::FindEngineFullPathname(string engine_filename)
283 string fullpath, localpath;
284 string enginePath = FDMExec->GetEnginePath();
285 string aircraftPath = FDMExec->GetAircraftPath();
286 ifstream* engine_file = new ifstream();
288 string separator = "/";
293 fullpath = enginePath + separator;
294 localpath = aircraftPath + separator + "Engines" + separator;
296 engine_file->open(string(fullpath + engine_filename + ".xml").c_str());
297 if ( !engine_file->is_open()) {
298 engine_file->open(string(localpath + engine_filename + ".xml").c_str());
299 if ( !engine_file->is_open()) {
300 cerr << " Could not open engine file: " << engine_filename << " in path "
301 << fullpath << " or " << localpath << endl;
304 return string(localpath + engine_filename + ".xml");
307 return string(fullpath + engine_filename + ".xml");
310 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
312 ifstream* FGPropulsion::FindEngineFile(string engine_filename)
314 string fullpath, localpath;
315 string enginePath = FDMExec->GetEnginePath();
316 string aircraftPath = FDMExec->GetAircraftPath();
317 ifstream* engine_file = new ifstream();
319 string separator = "/";
324 fullpath = enginePath + separator;
325 localpath = aircraftPath + separator + "Engines" + separator;
327 engine_file->open(string(fullpath + engine_filename + ".xml").c_str());
328 if ( !engine_file->is_open()) {
329 engine_file->open(string(localpath + engine_filename + ".xml").c_str());
330 if ( !engine_file->is_open()) {
331 cerr << " Could not open engine file: " << engine_filename << " in path "
332 << fullpath << " or " << localpath << endl;
338 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
340 string FGPropulsion::GetPropulsionStrings(string delimeter)
344 string PropulsionStrings = "";
345 bool firstime = true;
348 for (i=0; i<Engines.size(); i++) {
349 if (firstime) firstime = false;
350 else PropulsionStrings += delimeter;
352 PropulsionStrings += Engines[i]->GetEngineLabels(delimeter);
354 for (i=0; i<Tanks.size(); i++) {
355 if (Tanks[i]->GetType() == FGTank::ttFUEL) buf << delimeter << "Fuel Tank " << i;
356 else if (Tanks[i]->GetType() == FGTank::ttOXIDIZER) buf << delimeter << "Oxidizer Tank " << i;
359 return PropulsionStrings;
362 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
364 string FGPropulsion::GetPropulsionValues(string delimeter)
368 string PropulsionValues = "";
369 bool firstime = true;
372 for (i=0; i<Engines.size(); i++) {
373 if (firstime) firstime = false;
374 else PropulsionValues += delimeter;
376 PropulsionValues += Engines[i]->GetEngineValues(delimeter);
378 for (i=0; i<Tanks.size(); i++) {
380 buf << Tanks[i]->GetContents();
383 return PropulsionValues;
386 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
388 FGColumnVector3& FGPropulsion::GetTanksMoment(void)
390 iTank = Tanks.begin();
391 vXYZtank_arm.InitMatrix();
392 while (iTank < Tanks.end()) {
393 vXYZtank_arm(eX) += (*iTank)->GetXYZ(eX)*(*iTank)->GetContents();
394 vXYZtank_arm(eY) += (*iTank)->GetXYZ(eY)*(*iTank)->GetContents();
395 vXYZtank_arm(eZ) += (*iTank)->GetXYZ(eZ)*(*iTank)->GetContents();
401 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
403 double FGPropulsion::GetTanksWeight(void)
407 iTank = Tanks.begin();
408 while (iTank < Tanks.end()) {
409 Tw += (*iTank)->GetContents();
415 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
417 FGMatrix33& FGPropulsion::CalculateTankInertias(void)
422 if (size == 0) return tankJ;
424 tankJ = FGMatrix33();
426 for (unsigned int i=0; i<size; i++)
427 tankJ += MassBalance->GetPointmassInertia( lbtoslug * Tanks[i]->GetContents(),
428 Tanks[i]->GetXYZ() );
433 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
435 void FGPropulsion::SetMagnetos(int setting)
437 if (ActiveEngine < 0) {
438 for (unsigned i=0; i<Engines.size(); i++) {
439 // ToDo: first need to make sure the engine Type is really appropriate:
440 // do a check to see if it is of type Piston. This should be done for
441 // all of this kind of possibly across-the-board settings.
442 ((FGPiston*)Engines[i])->SetMagnetos(setting);
445 ((FGPiston*)Engines[ActiveEngine])->SetMagnetos(setting);
449 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
451 void FGPropulsion::SetStarter(int setting)
453 if (ActiveEngine < 0) {
454 for (unsigned i=0; i<Engines.size(); i++) {
456 Engines[i]->SetStarter(false);
458 Engines[i]->SetStarter(true);
462 Engines[ActiveEngine]->SetStarter(false);
464 Engines[ActiveEngine]->SetStarter(true);
468 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
470 void FGPropulsion::SetCutoff(int setting)
472 if (ActiveEngine < 0) {
473 for (unsigned i=0; i<Engines.size(); i++) {
475 ((FGTurbine*)Engines[i])->SetCutoff(false);
477 ((FGTurbine*)Engines[i])->SetCutoff(true);
481 ((FGTurbine*)Engines[ActiveEngine])->SetCutoff(false);
483 ((FGTurbine*)Engines[ActiveEngine])->SetCutoff(true);
487 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
489 void FGPropulsion::SetActiveEngine(int engine)
491 if (engine >= Engines.size() || engine < 0)
494 ActiveEngine = engine;
497 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
499 double FGPropulsion::Transfer(int source, int target, double amount)
501 double shortage, overage;
506 shortage = Tanks[source]->Drain(amount);
511 overage = Tanks[target]->Fill(amount - shortage);
516 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
518 void FGPropulsion::DoRefuel(double time_slice)
522 double fillrate = 100 * time_slice; // 100 lbs/sec = 6000 lbs/min
523 int TanksNotFull = 0;
525 for (i=0; i<numTanks; i++) {
526 if (Tanks[i]->GetPctFull() < 99.99) ++TanksNotFull;
530 for (i=0; i<numTanks; i++) {
531 if (Tanks[i]->GetPctFull() < 99.99)
532 Transfer(-1, i, fillrate/TanksNotFull);
537 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
539 void FGPropulsion::SetFuelFreeze(bool f)
542 for (unsigned int i=0; i<numEngines; i++) {
543 Engines[i]->SetFuelFreeze(f);
547 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
549 void FGPropulsion::bind(void)
551 typedef double (FGPropulsion::*PMF)(int) const;
552 typedef int (FGPropulsion::*iPMF)(void) const;
556 if (HaveTurbineEngine) {
557 PropertyManager->Tie("propulsion/starter_cmd", this, (iPMF)0, &FGPropulsion::SetStarter, true);
558 PropertyManager->Tie("propulsion/cutoff_cmd", this, (iPMF)0, &FGPropulsion::SetCutoff, true);
561 if (HavePistonEngine) {
562 PropertyManager->Tie("propulsion/starter_cmd", this, (iPMF)0, &FGPropulsion::SetStarter, true);
563 PropertyManager->Tie("propulsion/magneto_cmd", this, (iPMF)0, &FGPropulsion::SetMagnetos, true);
566 PropertyManager->Tie("propulsion/active_engine", this, (iPMF)&FGPropulsion::GetActiveEngine,
567 &FGPropulsion::SetActiveEngine, true);
568 PropertyManager->Tie("propulsion/total-fuel-lbs", this, &FGPropulsion::GetTotalFuelQuantity);
571 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
573 void FGPropulsion::unbind(void)
575 if (HaveTurbineEngine) {
576 PropertyManager->Untie("propulsion/starter_cmd");
577 PropertyManager->Untie("propulsion/cutoff_cmd");
579 if (HavePistonEngine) {
580 PropertyManager->Untie("propulsion/starter_cmd");
581 PropertyManager->Untie("propulsion/magneto_cmd");
583 PropertyManager->Untie("propulsion/active_engine");
584 PropertyManager->Untie("propulsion/total-fuel-lbs");
587 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
588 // The bitmasked value choices are as follows:
589 // unset: In this case (the default) JSBSim would only print
590 // out the normally expected messages, essentially echoing
591 // the config files as they are read. If the environment
592 // variable is not set, debug_lvl is set to 1 internally
593 // 0: This requests JSBSim not to output any messages
595 // 1: This value explicity requests the normal JSBSim
597 // 2: This value asks for a message to be printed out when
598 // a class is instantiated
599 // 4: When this value is set, a message is displayed when a
600 // FGModel object executes its Run() method
601 // 8: When this value is set, various runtime state variables
602 // are printed out periodically
603 // 16: When set various parameters are sanity checked and
604 // a message is printed out when they go out of bounds
606 void FGPropulsion::Debug(int from)
608 if (debug_lvl <= 0) return;
610 if (debug_lvl & 1) { // Standard console startup message output
611 if (from == 2) { // Loader
612 cout << endl << " Propulsion:" << endl;
615 if (debug_lvl & 2 ) { // Instantiation/Destruction notification
616 if (from == 0) cout << "Instantiated: FGPropulsion" << endl;
617 if (from == 1) cout << "Destroyed: FGPropulsion" << endl;
619 if (debug_lvl & 4 ) { // Run() method entry print for FGModel-derived objects
621 if (debug_lvl & 8 ) { // Runtime state variables
623 if (debug_lvl & 16) { // Sanity checking
625 if (debug_lvl & 64) {
626 if (from == 0) { // Constructor
627 cout << IdSrc << endl;
628 cout << IdHdr << endl;