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