JSBSim Flight Dynamics Model  1.1.11 (13 Feb 2022)
An Open Source Flight Dynamics and Control Software Library in C++
FGPropulsion.cpp
1 /*%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2 
3  Module: FGPropulsion.cpp
4  Author: Jon S. Berndt
5  Date started: 08/20/00
6  Purpose: Encapsulates the set of engines and tanks associated
7  with this aircraft
8 
9  ------------- Copyright (C) 2000 Jon S. Berndt (jon@jsbsim.org) -------------
10 
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
13  Software Foundation; either version 2 of the License, or (at your option) any
14  later version.
15 
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
19  details.
20 
21  You should have received a copy of the GNU Lesser General Public License along
22  with this program; if not, write to the Free Software Foundation, Inc., 59
23  Temple Place - Suite 330, Boston, MA 02111-1307, USA.
24 
25  Further information about the GNU Lesser General Public License can also be
26  found on the world wide web at http://www.gnu.org.
27 
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:
33 
34 1) The appropriate engine type instance is created
35 2) At least one tank object is created, and is linked to an engine.
36 
37 At Run time each engines Calculate() method is called.
38 
39 HISTORY
40 --------------------------------------------------------------------------------
41 08/20/00 JSB Created
42 
43 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
44 INCLUDES
45 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/
46 
47 #include <iomanip>
48 
49 #include "FGFDMExec.h"
50 #include "FGPropulsion.h"
51 #include "models/FGMassBalance.h"
52 #include "models/propulsion/FGRocket.h"
53 #include "models/propulsion/FGTurbine.h"
54 #include "models/propulsion/FGPiston.h"
55 #include "models/propulsion/FGElectric.h"
56 #include "models/propulsion/FGTurboProp.h"
57 #include "models/propulsion/FGTank.h"
58 #include "input_output/FGModelLoader.h"
59 
60 using namespace std;
61 
62 namespace JSBSim {
63 
64 extern short debug_lvl;
65 
66 /*%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
67 CLASS IMPLEMENTATION
68 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/
69 
70 FGPropulsion::FGPropulsion(FGFDMExec* exec) : FGModel(exec)
71 {
72  Name = "FGPropulsion";
73 
74  numSelectedFuelTanks = numSelectedOxiTanks = 0;
75  numTanks = numEngines = 0;
76  numOxiTanks = numFuelTanks = 0;
77  ActiveEngine = -1; // -1: ALL, 0: Engine 1, 1: Engine 2 ...
78  tankJ.InitMatrix();
79  DumpRate = 0.0;
80  RefuelRate = 6000.0;
81  FuelFreeze = false;
82  IsBound =
83  HavePistonEngine =
84  HaveTurbineEngine =
85  HaveRocketEngine =
86  HaveTurboPropEngine =
87  HaveElectricEngine = false;
88 
89  Debug(0);
90 }
91 
92 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
93 
95 {
96  for (unsigned int i=0; i<Engines.size(); i++) delete Engines[i];
97  Engines.clear();
98  for (unsigned int i=0; i<Tanks.size(); i++) delete Tanks[i];
99  Tanks.clear();
100  Debug(1);
101 }
102 
103 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
104 
105 bool FGPropulsion::InitModel(void)
106 {
107  bool result = true;
108 
109  if (!FGModel::InitModel()) return false;
110 
111  vForces.InitMatrix();
112  vMoments.InitMatrix();
113 
114  for (unsigned int i=0; i<numTanks; i++) Tanks[i]->ResetToIC();
115  TotalFuelQuantity = 0.0;
116  TotalOxidizerQuantity = 0.0;
117  refuel = dump = false;
118 
119  for (unsigned int i=0; i<numEngines; i++)
120  Engines[i]->ResetToIC();
121 
122  return result;
123 }
124 
125 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
126 
127 bool FGPropulsion::Run(bool Holding)
128 {
129  unsigned int i;
130 
131  if (FGModel::Run(Holding)) return true;
132  if (Holding) return false;
133 
134  RunPreFunctions();
135 
136  vForces.InitMatrix();
137  vMoments.InitMatrix();
138 
139  for (i=0; i<numEngines; i++) {
140  Engines[i]->Calculate();
141  ConsumeFuel(Engines[i]);
142  vForces += Engines[i]->GetBodyForces(); // sum body frame forces
143  vMoments += Engines[i]->GetMoments(); // sum body frame moments
144  }
145 
146  TotalFuelQuantity = 0.0;
147  TotalOxidizerQuantity = 0.0;
148  for (i=0; i<numTanks; i++) {
149  Tanks[i]->Calculate( in.TotalDeltaT, in.TAT_c);
150  switch (Tanks[i]->GetType()) {
151  case FGTank::ttFUEL:
152  TotalFuelQuantity += Tanks[i]->GetContents();
153  break;
154  case FGTank::ttOXIDIZER:
155  TotalOxidizerQuantity += Tanks[i]->GetContents();
156  break;
157  default:
158  break;
159  }
160  }
161 
162  if (refuel.node() && refuel) DoRefuel( in.TotalDeltaT );
163  if (dump.node() && dump) DumpFuel( in.TotalDeltaT );
164 
165  RunPostFunctions();
166 
167  return false;
168 }
169 
170 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
171 //
172 // The engine can tell us how much fuel it needs, but it is up to the propulsion
173 // subsystem manager class FGPropulsion to manage fuel flow amongst tanks. Engines
174 // May burn fuel from more than one tank at a time, and may burn from one tank
175 // before another - that is, may burn from one tank until the tank is depleted,
176 // then burn from the next highest priority tank. This can be accompished
177 // by defining a fuel management system, but this way of specifying priorities
178 // is more automatic from a user perspective.
179 
180 void FGPropulsion::ConsumeFuel(FGEngine* engine)
181 {
182  if (FuelFreeze) return;
183  if (FDMExec->GetTrimStatus()) return;
184 
185  unsigned int TanksWithFuel=0, CurrentFuelTankPriority=1;
186  unsigned int TanksWithOxidizer=0, CurrentOxidizerTankPriority=1;
187  vector <int> FeedListFuel, FeedListOxi;
188  bool Starved = true; // Initially set Starved to true. Set to false in code below.
189  bool hasOxTanks = false;
190 
191  // For this engine,
192  // 1) Count how many fuel tanks with the current priority level have fuel
193  // 2) If there none, then try next lower priority (higher number) - that is,
194  // increment CurrentPriority.
195  // 3) Build the feed list.
196  // 4) Do the same for oxidizer tanks, if needed.
197 
198  // Process fuel tanks, if any
199  while ((TanksWithFuel == 0) && (CurrentFuelTankPriority <= numTanks)) {
200  for (unsigned int i=0; i<engine->GetNumSourceTanks(); i++) {
201  unsigned int TankId = engine->GetSourceTank(i);
202  FGTank* Tank = Tanks[TankId];
203  unsigned int TankPriority = Tank->GetPriority();
204  if (TankPriority != 0) {
205  switch(Tank->GetType()) {
206  case FGTank::ttFUEL:
207  if ((Tank->GetContents() > Tank->GetUnusable()) && Tank->GetSelected() && (TankPriority == CurrentFuelTankPriority)) {
208  TanksWithFuel++;
209  Starved = false;
210  FeedListFuel.push_back(TankId);
211  }
212  break;
213  case FGTank::ttOXIDIZER:
214  // Skip this here (done below)
215  break;
216  }
217  }
218  }
219  if (TanksWithFuel == 0) CurrentFuelTankPriority++; // No tanks at this priority, try next priority
220  }
221 
222  bool FuelStarved = Starved;
223  Starved = true;
224 
225  // Process Oxidizer tanks, if any
226  if (engine->GetType() == FGEngine::etRocket) {
227  while ((TanksWithOxidizer == 0) && (CurrentOxidizerTankPriority <= numTanks)) {
228  for (unsigned int i=0; i<engine->GetNumSourceTanks(); i++) {
229  unsigned int TankId = engine->GetSourceTank(i);
230  FGTank* Tank = Tanks[TankId];
231  unsigned int TankPriority = Tank->GetPriority();
232  if (TankPriority != 0) {
233  switch(Tank->GetType()) {
234  case FGTank::ttFUEL:
235  // Skip this here (done above)
236  break;
237  case FGTank::ttOXIDIZER:
238  hasOxTanks = true;
239  if (Tank->GetContents() > Tank->GetUnusable() && Tank->GetSelected() && TankPriority == CurrentOxidizerTankPriority) {
240  TanksWithOxidizer++;
241  if (TanksWithFuel > 0) Starved = false;
242  FeedListOxi.push_back(TankId);
243  }
244  break;
245  }
246  }
247  }
248  if (TanksWithOxidizer == 0) CurrentOxidizerTankPriority++; // No tanks at this priority, try next priority
249  }
250  }
251 
252  bool OxiStarved = Starved;
253 
254  engine->SetStarved(FuelStarved || (hasOxTanks && OxiStarved)); // Tanks can be refilled, so be sure to reset engine Starved flag here.
255 
256  // No fuel or fuel/oxidizer found at any priority!
257 // if (Starved) return;
258  if (FuelStarved || (hasOxTanks && OxiStarved)) return;
259 
260  double FuelToBurn = engine->CalcFuelNeed(); // How much fuel does this engine need?
261  double FuelNeededPerTank = FuelToBurn / TanksWithFuel; // Determine fuel needed per tank.
262  for (unsigned int i=0; i<FeedListFuel.size(); i++) {
263  Tanks[FeedListFuel[i]]->Drain(FuelNeededPerTank);
264  }
265 
266  if (engine->GetType() == FGEngine::etRocket) {
267  double OxidizerToBurn = engine->CalcOxidizerNeed(); // How much fuel does this engine need?
268  double OxidizerNeededPerTank = 0;
269  if (TanksWithOxidizer > 0) OxidizerNeededPerTank = OxidizerToBurn / TanksWithOxidizer; // Determine fuel needed per tank.
270  for (unsigned int i=0; i<FeedListOxi.size(); i++) {
271  Tanks[FeedListOxi[i]]->Drain(OxidizerNeededPerTank);
272  }
273  }
274 
275 }
276 
277 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
278 
280 {
281  double currentThrust = 0, lastThrust = -1;
282  int steady_count = 0, j = 0;
283  bool steady = false;
284  bool TrimMode = FDMExec->GetTrimStatus();
285  double TimeStep = FDMExec->GetDeltaT();
286 
287  vForces.InitMatrix();
288  vMoments.InitMatrix();
289 
290  if (!FGModel::Run(false)) {
291  FDMExec->SetTrimStatus(true);
292  // This is a time marching algorithm so it needs a non-zero time step to
293  // reach a steady state.
294  in.TotalDeltaT = 0.5;
295 
296  for (unsigned int i=0; i<numEngines; i++) {
297  steady=false;
298  steady_count=0;
299  j=0;
300  while (!steady && j < 6000) {
301  Engines[i]->Calculate();
302  lastThrust = currentThrust;
303  currentThrust = Engines[i]->GetThrust();
304  if (fabs(lastThrust-currentThrust) < 0.0001) {
305  steady_count++;
306  if (steady_count > 120) {
307  steady=true;
308  }
309  } else {
310  steady_count=0;
311  }
312  j++;
313  }
314  vForces += Engines[i]->GetBodyForces(); // sum body frame forces
315  vMoments += Engines[i]->GetMoments(); // sum body frame moments
316  }
317 
318  FDMExec->SetTrimStatus(TrimMode);
319  in.TotalDeltaT = TimeStep;
320 
321  return false;
322  } else {
323  return true;
324  }
325 }
326 
327 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
328 
330 {
331  if (n >= 0) { // A specific engine is supposed to be initialized
332 
333  if (n >= (int)GetNumEngines() ) {
334  throw(string("Tried to initialize a non-existent engine!"));
335  }
336 
337  in.ThrottleCmd[n] = in.ThrottlePos[n] = 1; // Set the throttle command and position
338  in.MixtureCmd[n] = in.MixturePos[n] = 1; // Set the mixture command and position
339 
340  GetEngine(n)->InitRunning();
341  GetSteadyState();
342 
343  } else if (n < 0) { // -1 refers to "All Engines"
344 
345  for (unsigned int i=0; i<GetNumEngines(); i++) {
346  in.ThrottleCmd[i] = in.ThrottlePos[i] = 1; // Set the throttle command and position
347  in.MixtureCmd[i] = in.MixturePos[i] = 1; // Set the mixture command and position
348  GetEngine(i)->InitRunning();
349  }
350 
351  GetSteadyState();
352  }
353 }
354 
355 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
356 
358 {
359  FGModelLoader ModelLoader(this);
360 
361  Debug(2);
362  ReadingEngine = false;
363  double FuelDensity = 6.0;
364 
365  Name = "Propulsion Model: " + el->GetAttributeValue("name");
366 
367  // Perform base class Pre-Load
368  if (!FGModel::Upload(el, true))
369  return false;
370 
371  // Process tank definitions first to establish the number of fuel tanks
372 
373  Element* tank_element = el->FindElement("tank");
374  while (tank_element) {
375  Tanks.push_back(new FGTank(FDMExec, tank_element, numTanks));
376  if (Tanks.back()->GetType() == FGTank::ttFUEL) {
377  FuelDensity = Tanks[numFuelTanks]->GetDensity();
378  numFuelTanks++;
379  }
380  else if (Tanks.back()->GetType() == FGTank::ttOXIDIZER) numOxiTanks++;
381  else {cerr << "Unknown tank type specified." << endl; return false;}
382  numTanks++;
383  tank_element = el->FindNextElement("tank");
384  }
385  numSelectedFuelTanks = numFuelTanks;
386  numSelectedOxiTanks = numOxiTanks;
387 
388  ReadingEngine = true;
389  Element* engine_element = el->FindElement("engine");
390  while (engine_element) {
391  if (!ModelLoader.Open(engine_element)) return false;
392 
393  try {
394  // Locate the thruster definition
395  Element* thruster_element = engine_element->FindElement("thruster");
396  if (!thruster_element || !ModelLoader.Open(thruster_element))
397  throw("No thruster definition supplied with engine definition.");
398 
399  if (engine_element->FindElement("piston_engine")) {
400  HavePistonEngine = true;
401  if (!IsBound) bind();
402  Element *element = engine_element->FindElement("piston_engine");
403  Engines.push_back(new FGPiston(FDMExec, element, numEngines, in));
404  } else if (engine_element->FindElement("turbine_engine")) {
405  HaveTurbineEngine = true;
406  if (!IsBound) bind();
407  Element *element = engine_element->FindElement("turbine_engine");
408  Engines.push_back(new FGTurbine(FDMExec, element, numEngines, in));
409  } else if (engine_element->FindElement("turboprop_engine")) {
410  HaveTurboPropEngine = true;
411  if (!IsBound) bind();
412  Element *element = engine_element->FindElement("turboprop_engine");
413  Engines.push_back(new FGTurboProp(FDMExec, element, numEngines, in));
414  } else if (engine_element->FindElement("rocket_engine")) {
415  HaveRocketEngine = true;
416  if (!IsBound) bind();
417  Element *element = engine_element->FindElement("rocket_engine");
418  Engines.push_back(new FGRocket(FDMExec, element, numEngines, in));
419  } else if (engine_element->FindElement("electric_engine")) {
420  HaveElectricEngine = true;
421  if (!IsBound) bind();
422  Element *element = engine_element->FindElement("electric_engine");
423  Engines.push_back(new FGElectric(FDMExec, element, numEngines, in));
424  } else {
425  cerr << engine_element->ReadFrom() << " Unknown engine type" << endl;
426  return false;
427  }
428  } catch (std::string& str) {
429  cerr << endl << fgred << str << reset << endl;
430  return false;
431  }
432 
433  numEngines++;
434 
435  engine_element = el->FindNextElement("engine");
436  }
437 
438  CalculateTankInertias();
439 
440  if (el->FindElement("dump-rate"))
441  DumpRate = el->FindElementValueAsNumberConvertTo("dump-rate", "LBS/MIN");
442  if (el->FindElement("refuel-rate"))
443  RefuelRate = el->FindElementValueAsNumberConvertTo("refuel-rate", "LBS/MIN");
444 
445  unsigned int i;
446  for (i=0; i<Engines.size(); i++) {
447  Engines[i]->SetFuelDensity(FuelDensity);
448  }
449 
450 
451  PostLoad(el, FDMExec);
452 
453  return true;
454 }
455 
456 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
457 
458 SGPath FGPropulsion::FindFullPathName(const SGPath& path) const
459 {
460  if (!ReadingEngine) return FGModel::FindFullPathName(path);
461 
462  SGPath name = CheckPathName(FDMExec->GetFullAircraftPath()/string("Engines"),
463  path);
464  if (!name.isNull()) return name;
465 
466  return CheckPathName(FDMExec->GetEnginePath(), path);
467 }
468 
469 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
470 
471 string FGPropulsion::GetPropulsionStrings(const string& delimiter) const
472 {
473  unsigned int i;
474 
475  string PropulsionStrings = "";
476  bool firstime = true;
477  stringstream buf;
478 
479  for (i=0; i<Engines.size(); i++) {
480  if (firstime) firstime = false;
481  else PropulsionStrings += delimiter;
482 
483  PropulsionStrings += Engines[i]->GetEngineLabels(delimiter);
484  }
485  for (i=0; i<Tanks.size(); i++) {
486  if (Tanks[i]->GetType() == FGTank::ttFUEL) buf << delimiter << "Fuel Tank " << i;
487  else if (Tanks[i]->GetType() == FGTank::ttOXIDIZER) buf << delimiter << "Oxidizer Tank " << i;
488  }
489 
490  PropulsionStrings += buf.str();
491  buf.str("");
492 
493  return PropulsionStrings;
494 }
495 
496 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
497 
498 string FGPropulsion::GetPropulsionValues(const string& delimiter) const
499 {
500  unsigned int i;
501 
502  string PropulsionValues = "";
503  bool firstime = true;
504  stringstream buf;
505 
506  for (i=0; i<Engines.size(); i++) {
507  if (firstime) firstime = false;
508  else PropulsionValues += delimiter;
509 
510  PropulsionValues += Engines[i]->GetEngineValues(delimiter);
511  }
512  for (i=0; i<Tanks.size(); i++) {
513  buf << delimiter;
514  buf << Tanks[i]->GetContents();
515  }
516 
517  PropulsionValues += buf.str();
518  buf.str("");
519 
520  return PropulsionValues;
521 }
522 
523 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
524 
525 string FGPropulsion::GetPropulsionTankReport()
526 {
527  string out="";
528  stringstream outstream;
529 
530  /*const FGMatrix33& mTkI =*/ CalculateTankInertias();
531 
532  for (unsigned int i=0; i<numTanks; i++)
533  {
534  FGTank* tank = Tanks[i];
535  string tankname="";
536  if (tank->GetType() == FGTank::ttFUEL && tank->GetGrainType() != FGTank::gtUNKNOWN) {
537  tankname = "Solid Fuel";
538  } else if (tank->GetType() == FGTank::ttFUEL) {
539  tankname = "Fuel";
540  } else if (tank->GetType() == FGTank::ttOXIDIZER) {
541  tankname = "Oxidizer";
542  } else {
543  tankname = "(Unknown tank type)";
544  }
545  outstream << highint << left << setw(4) << i << setw(30) << tankname << normint
546  << right << setw(10) << tank->GetContents() << setw(8) << tank->GetXYZ(eX)
547  << setw(8) << tank->GetXYZ(eY) << setw(8) << tank->GetXYZ(eZ)
548  << setw(12) << tank->GetIxx() << setw(12) << tank->GetIyy()
549  << setw(12) << tank->GetIzz() << endl;
550  }
551  return outstream.str();
552 }
553 
554 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
555 
556 const FGColumnVector3& FGPropulsion::GetTanksMoment(void)
557 {
558  vXYZtank_arm.InitMatrix();
559  for (unsigned int i=0; i<Tanks.size(); i++) {
560  vXYZtank_arm += Tanks[i]->GetXYZ() * Tanks[i]->GetContents();
561  }
562  return vXYZtank_arm;
563 }
564 
565 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
566 
567 double FGPropulsion::GetTanksWeight(void) const
568 {
569  double Tw = 0.0;
570 
571  for (unsigned int i=0; i<Tanks.size(); i++) Tw += Tanks[i]->GetContents();
572 
573  return Tw;
574 }
575 
576 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
577 
578 const FGMatrix33& FGPropulsion::CalculateTankInertias(void)
579 {
580  size_t size = Tanks.size();
581 
582  if (size == 0) return tankJ;
583 
584  tankJ.InitMatrix();
585 
586  for (unsigned int i=0; i<size; i++) {
587  tankJ += FDMExec->GetMassBalance()->GetPointmassInertia( lbtoslug * Tanks[i]->GetContents(),
588  Tanks[i]->GetXYZ());
589  tankJ(1,1) += Tanks[i]->GetIxx();
590  tankJ(2,2) += Tanks[i]->GetIyy();
591  tankJ(3,3) += Tanks[i]->GetIzz();
592  }
593 
594  return tankJ;
595 }
596 
597 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
598 
599 void FGPropulsion::SetMagnetos(int setting)
600 {
601  if (ActiveEngine < 0) {
602  for (unsigned i=0; i<Engines.size(); i++) {
603  // ToDo: first need to make sure the engine Type is really appropriate:
604  // do a check to see if it is of type Piston. This should be done for
605  // all of this kind of possibly across-the-board settings.
606  if (Engines[i]->GetType() == FGEngine::etPiston)
607  ((FGPiston*)Engines[i])->SetMagnetos(setting);
608  }
609  } else {
610  ((FGPiston*)Engines[ActiveEngine])->SetMagnetos(setting);
611  }
612 }
613 
614 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
615 
616 void FGPropulsion::SetStarter(int setting)
617 {
618  if (ActiveEngine < 0) {
619  for (unsigned i=0; i<Engines.size(); i++) {
620  if (setting == 0)
621  Engines[i]->SetStarter(false);
622  else
623  Engines[i]->SetStarter(true);
624  }
625  } else {
626  if (setting == 0)
627  Engines[ActiveEngine]->SetStarter(false);
628  else
629  Engines[ActiveEngine]->SetStarter(true);
630  }
631 }
632 
633 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
634 
635 int FGPropulsion::GetStarter(void) const
636 {
637  if (ActiveEngine < 0) {
638  bool starter = true;
639 
640  for (unsigned i=0; i<Engines.size(); i++)
641  starter &= Engines[i]->GetStarter();
642 
643  return starter ? 1 : 0;
644  } else
645  return Engines[ActiveEngine]->GetStarter() ? 1: 0;
646 }
647 
648 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
649 
650 void FGPropulsion::SetCutoff(int setting)
651 {
652  bool bsetting = setting == 0 ? false : true;
653 
654  if (ActiveEngine < 0) {
655  for (unsigned i=0; i<Engines.size(); i++) {
656  switch (Engines[i]->GetType()) {
657  case FGEngine::etTurbine:
658  ((FGTurbine*)Engines[i])->SetCutoff(bsetting);
659  break;
660  case FGEngine::etTurboprop:
661  ((FGTurboProp*)Engines[i])->SetCutoff(bsetting);
662  break;
663  default:
664  break;
665  }
666  }
667  } else {
668  switch (Engines[ActiveEngine]->GetType()) {
669  case FGEngine::etTurbine:
670  ((FGTurbine*)Engines[ActiveEngine])->SetCutoff(bsetting);
671  break;
672  case FGEngine::etTurboprop:
673  ((FGTurboProp*)Engines[ActiveEngine])->SetCutoff(bsetting);
674  break;
675  default:
676  break;
677  }
678  }
679 }
680 
681 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
682 
683 int FGPropulsion::GetCutoff(void) const
684 {
685  if (ActiveEngine < 0) {
686  bool cutoff = true;
687 
688  for (unsigned i=0; i<Engines.size(); i++) {
689  switch (Engines[i]->GetType()) {
690  case FGEngine::etTurbine:
691  cutoff &= ((FGTurbine*)Engines[i])->GetCutoff();
692  break;
693  case FGEngine::etTurboprop:
694  cutoff &= ((FGTurboProp*)Engines[i])->GetCutoff();
695  break;
696  default:
697  return -1;
698  }
699  }
700 
701  return cutoff ? 1 : 0;
702  } else {
703  switch (Engines[ActiveEngine]->GetType()) {
704  case FGEngine::etTurbine:
705  return ((FGTurbine*)Engines[ActiveEngine])->GetCutoff() ? 1 : 0;
706  case FGEngine::etTurboprop:
707  return ((FGTurboProp*)Engines[ActiveEngine])->GetCutoff() ? 1 : 0;
708  default:
709  break;
710  }
711  }
712 
713  return -1;
714 }
715 
716 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
717 
718 void FGPropulsion::SetActiveEngine(int engine)
719 {
720  if (engine >= (int)Engines.size() || engine < 0)
721  ActiveEngine = -1;
722  else
723  ActiveEngine = engine;
724 }
725 
726 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
727 
728 double FGPropulsion::Transfer(int source, int target, double amount)
729 {
730  double shortage, overage;
731 
732  if (source == -1) {
733  shortage = 0.0;
734  } else {
735  shortage = Tanks[source]->Drain(amount);
736  }
737  if (target == -1) {
738  overage = 0.0;
739  } else {
740  overage = Tanks[target]->Fill(amount - shortage);
741  }
742  return overage;
743 }
744 
745 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
746 
747 void FGPropulsion::DoRefuel(double time_slice)
748 {
749  unsigned int i;
750 
751  double fillrate = RefuelRate / 60.0 * time_slice;
752  int TanksNotFull = 0;
753 
754  for (i=0; i<numTanks; i++) {
755  if (Tanks[i]->GetPctFull() < 99.99) ++TanksNotFull;
756  }
757 
758  // adds fuel equally to all tanks that are not full
759  if (TanksNotFull) {
760  for (i=0; i<numTanks; i++) {
761  if (Tanks[i]->GetPctFull() < 99.99)
762  Transfer(-1, i, fillrate/TanksNotFull);
763  }
764  }
765 }
766 
767 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
768 
769 void FGPropulsion::DumpFuel(double time_slice)
770 {
771  unsigned int i;
772  int TanksDumping = 0;
773 
774  for (i=0; i<numTanks; i++) {
775  if (Tanks[i]->GetContents() > Tanks[i]->GetStandpipe()) ++TanksDumping;
776  }
777 
778  if (TanksDumping == 0) return;
779 
780  double dump_rate_per_tank = DumpRate / 60.0 * time_slice / TanksDumping;
781 
782  for (i=0; i<numTanks; i++) {
783  if (Tanks[i]->GetContents() > Tanks[i]->GetStandpipe()) {
784  Transfer(i, -1, dump_rate_per_tank);
785  }
786  }
787 }
788 
789 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
790 
791 void FGPropulsion::SetFuelFreeze(bool f)
792 {
793  FuelFreeze = f;
794  for (unsigned int i=0; i<numEngines; i++) {
795  Engines[i]->SetFuelFreeze(f);
796  }
797 }
798 
799 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
800 
801 void FGPropulsion::bind(void)
802 {
803  typedef double (FGPropulsion::*PMF)(int) const;
804  typedef int (FGPropulsion::*iPMF)(void) const;
805  typedef bool (FGPropulsion::*bPMF)(void) const;
806 
807  IsBound = true;
808  PropertyManager->Tie("propulsion/set-running", this, (iPMF)0, &FGPropulsion::InitRunning);
809  if (HaveTurbineEngine || HaveTurboPropEngine) {
810  PropertyManager->Tie("propulsion/starter_cmd", this, &FGPropulsion::GetStarter, &FGPropulsion::SetStarter);
811  PropertyManager->Tie("propulsion/cutoff_cmd", this, &FGPropulsion::GetCutoff, &FGPropulsion::SetCutoff);
812  }
813 
814  if (HavePistonEngine) {
815  PropertyManager->Tie("propulsion/starter_cmd", this, &FGPropulsion::GetStarter, &FGPropulsion::SetStarter);
816  PropertyManager->Tie("propulsion/magneto_cmd", this, (iPMF)0, &FGPropulsion::SetMagnetos);
817  }
818 
819  PropertyManager->Tie("propulsion/active_engine", this, (iPMF)&FGPropulsion::GetActiveEngine,
820  &FGPropulsion::SetActiveEngine);
821  PropertyManager->Tie("forces/fbx-prop-lbs", this, eX, (PMF)&FGPropulsion::GetForces);
822  PropertyManager->Tie("forces/fby-prop-lbs", this, eY, (PMF)&FGPropulsion::GetForces);
823  PropertyManager->Tie("forces/fbz-prop-lbs", this, eZ, (PMF)&FGPropulsion::GetForces);
824  PropertyManager->Tie("moments/l-prop-lbsft", this, eX, (PMF)&FGPropulsion::GetMoments);
825  PropertyManager->Tie("moments/m-prop-lbsft", this, eY, (PMF)&FGPropulsion::GetMoments);
826  PropertyManager->Tie("moments/n-prop-lbsft", this, eZ, (PMF)&FGPropulsion::GetMoments);
827  TotalFuelQuantity = PropertyManager->CreatePropertyObject<double>("propulsion/total-fuel-lbs");
828  TotalOxidizerQuantity = PropertyManager->CreatePropertyObject<double>("propulsion/total-oxidizer-lbs");
829  refuel = PropertyManager->CreatePropertyObject<bool>("propulsion/refuel");
830  dump = PropertyManager->CreatePropertyObject<bool>("propulsion/fuel_dump");
831  PropertyManager->Tie("propulsion/fuel_freeze", this, (bPMF)nullptr, &FGPropulsion::SetFuelFreeze);
832 }
833 
834 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
835 // The bitmasked value choices are as follows:
836 // unset: In this case (the default) JSBSim would only print
837 // out the normally expected messages, essentially echoing
838 // the config files as they are read. If the environment
839 // variable is not set, debug_lvl is set to 1 internally
840 // 0: This requests JSBSim not to output any messages
841 // whatsoever.
842 // 1: This value explicity requests the normal JSBSim
843 // startup messages
844 // 2: This value asks for a message to be printed out when
845 // a class is instantiated
846 // 4: When this value is set, a message is displayed when a
847 // FGModel object executes its Run() method
848 // 8: When this value is set, various runtime state variables
849 // are printed out periodically
850 // 16: When set various parameters are sanity checked and
851 // a message is printed out when they go out of bounds
852 
853 void FGPropulsion::Debug(int from)
854 {
855  if (debug_lvl <= 0) return;
856 
857  if (debug_lvl & 1) { // Standard console startup message output
858  if (from == 2) { // Loader
859  cout << endl << " Propulsion:" << endl;
860  }
861  }
862  if (debug_lvl & 2 ) { // Instantiation/Destruction notification
863  if (from == 0) cout << "Instantiated: FGPropulsion" << endl;
864  if (from == 1) cout << "Destroyed: FGPropulsion" << endl;
865  }
866  if (debug_lvl & 4 ) { // Run() method entry print for FGModel-derived objects
867  }
868  if (debug_lvl & 8 ) { // Runtime state variables
869  }
870  if (debug_lvl & 16) { // Sanity checking
871  }
872  if (debug_lvl & 64) {
873  if (from == 0) { // Constructor
874  }
875  }
876 }
877 }
JSBSim::FGFDMExec
Encapsulates the JSBSim simulation executive.
Definition: FGFDMExec.h:185
JSBSim::FGTank::GetContents
double GetContents(void) const
Gets the contents of the tank.
Definition: FGTank.h:260
JSBSim::FGModel::Upload
bool Upload(Element *el, bool preLoad)
Uploads this model in memory.
Definition: FGModel.cpp:110
JSBSim::FGTank::GetType
int GetType(void) const
Retrieves the type of tank: Fuel or Oxidizer.
Definition: FGTank.h:237
JSBSim::FGModel
Base class for all scheduled JSBSim models.
Definition: FGModel.h:68
JSBSim::FGPropulsion::FGPropulsion
FGPropulsion(FGFDMExec *)
Constructor.
Definition: FGPropulsion.cpp:70
JSBSim::Element::GetAttributeValue
std::string GetAttributeValue(const std::string &key)
Retrieves an attribute.
Definition: FGXMLElement.cpp:260
JSBSim::FGTank::GetUnusable
double GetUnusable(void) const
Returns the amount of unusable fuel in the tank.
Definition: FGTank.h:286
JSBSim::Element::FindElement
Element * FindElement(const std::string &el="")
Searches for a specified element.
Definition: FGXMLElement.cpp:389
JSBSim::FGTank
Models a fuel tank.
Definition: FGTank.h:201
JSBSim::FGPropulsion::GetEngine
FGEngine * GetEngine(unsigned int index) const
Retrieves an engine object pointer from the list of engines.
Definition: FGPropulsion.h:133
JSBSim::Element::FindElementValueAsNumberConvertTo
double FindElementValueAsNumberConvertTo(const std::string &el, const std::string &target_units)
Searches for the named element and converts and returns the data belonging to it.
Definition: FGXMLElement.cpp:480
JSBSim::FGEngine::CalcFuelNeed
virtual double CalcFuelNeed(void)
The fuel need is calculated based on power levels and flow rate for that power level.
Definition: FGEngine.cpp:93
JSBSim::FGElectric
Models an electric motor.
Definition: FGElectric.h:67
JSBSim::FGTurboProp
Turboprop engine model.
Definition: FGTurboProp.h:92
JSBSim::FGPropulsion::GetSteadyState
bool GetSteadyState(void)
Loops the engines until thrust output steady (used for trimming)
Definition: FGPropulsion.cpp:279
JSBSim::FGJSBBase::fgred
static char fgred[6]
red text
Definition: FGJSBBase.h:139
JSBSim::FGRocket
Models a generic rocket engine.
Definition: FGRocket.h:125
JSBSim::FGTank::GetSelected
bool GetSelected(void) const
If the tank is set to supply fuel, this function returns true.
Definition: FGTank.h:244
JSBSim::FGFDMExec::GetMassBalance
FGMassBalance * GetMassBalance(void)
Returns the FGAircraft pointer.
Definition: FGFDMExec.h:363
JSBSim::FGJSBBase::reset
static char reset[5]
resets text properties
Definition: FGJSBBase.h:129
JSBSim::FGPropulsion::InitRunning
void InitRunning(int n)
Sets up the engines as running.
Definition: FGPropulsion.cpp:329
JSBSim::FGFDMExec::GetDeltaT
double GetDeltaT(void) const
Returns the simulation delta T.
Definition: FGFDMExec.h:545
JSBSim::FGModel::Run
virtual bool Run(bool Holding)
Runs the model; called by the Executive.
Definition: FGModel.cpp:89
JSBSim::FGPropulsion::GetNumEngines
unsigned int GetNumEngines(void) const
Retrieves the number of engines defined for the aircraft.
Definition: FGPropulsion.h:127
JSBSim::FGPropulsion::Run
bool Run(bool Holding) override
Executes the propulsion model.
Definition: FGPropulsion.cpp:127
JSBSim::FGFDMExec::GetEnginePath
const SGPath & GetEnginePath(void)
Retrieves the engine path.
Definition: FGFDMExec.h:393
JSBSim::FGJSBBase::normint
static char normint[6]
normal intensity text
Definition: FGJSBBase.h:127
JSBSim::FGPiston
Models a Supercharged Piston engine.
Definition: FGPiston.h:223
JSBSim::Element::ReadFrom
std::string ReadFrom(void) const
Return a string that contains a description of the location where the current XML element was read fr...
Definition: FGXMLElement.cpp:738
JSBSim::FGJSBBase::highint
static char highint[5]
highlights text
Definition: FGJSBBase.h:123
JSBSim::Element::FindNextElement
Element * FindNextElement(const std::string &el="")
Searches for the next element as specified.
Definition: FGXMLElement.cpp:407
JSBSim::FGEngine
Base class for all engines.
Definition: FGEngine.h:103
JSBSim::FGModelLoader
Definition: FGModelLoader.h:62
JSBSim::FGPropulsion::~FGPropulsion
~FGPropulsion() override
Destructor.
Definition: FGPropulsion.cpp:94
JSBSim::FGPropulsion::Load
bool Load(Element *el) override
Loads the propulsion system (engine[s] and tank[s]).
Definition: FGPropulsion.cpp:357
JSBSim::FGMatrix33::InitMatrix
void InitMatrix(void)
Initialize the matrix.
Definition: FGMatrix33.cpp:259
JSBSim::FGTurbine
This class models a turbine engine.
Definition: FGTurbine.h:173
JSBSim::FGFDMExec::GetFullAircraftPath
const SGPath & GetFullAircraftPath(void)
Retrieves the full aircraft path name.
Definition: FGFDMExec.h:399
JSBSim::FGMassBalance::GetPointmassInertia
FGMatrix33 GetPointmassInertia(double mass_sl, const FGColumnVector3 &r) const
Computes the inertia contribution of a pointmass.
Definition: FGMassBalance.h:159
JSBSim::Element
Definition: FGXMLElement.h:143
JSBSim::FGPropertyManager::Tie
void Tie(const std::string &name, T *pointer)
Tie a property to an external variable.
Definition: FGPropertyManager.h:449