JSBSim Flight Dynamics Model 1.3.3 (16 Oct 2026)
An Open Source Flight Dynamics and Control Software Library in C++
Loading...
Searching...
No Matches
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
28FUNCTIONAL DESCRIPTION
29--------------------------------------------------------------------------------
30The Propulsion class is the container for the entire propulsion system, which is
31comprised of engines and tanks. Once the Propulsion class gets the config file,
32it reads in information which is specific to a type of engine. Then:
33
341) The appropriate engine type instance is created
352) At least one tank object is created, and is linked to an engine.
36
37At Run time each engines Calculate() method is called.
38
39HISTORY
40--------------------------------------------------------------------------------
4108/20/00 JSB Created
42
43%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
44INCLUDES
45%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/
46
47#include <iomanip>
48#include <array>
49
50#include "FGFDMExec.h"
51#include "FGPropulsion.h"
52#include "input_output/FGModelLoader.h"
53#include "input_output/FGLog.h"
54#include "models/FGMassBalance.h"
55#include "models/propulsion/FGRocket.h"
56#include "models/propulsion/FGTurbine.h"
57#include "models/propulsion/FGPiston.h"
58#include "models/propulsion/FGElectric.h"
59#include "models/propulsion/FGTurboProp.h"
60#include "models/propulsion/FGTank.h"
61#include "models/propulsion/FGBrushLessDCMotor.h"
62#include "models/FGFCS.h"
63
64
65using namespace std;
66
67namespace JSBSim {
68
69extern short debug_lvl;
70
71/*%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
72CLASS IMPLEMENTATION
73%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/
74
75FGPropulsion::FGPropulsion(FGFDMExec* exec) : FGModel(exec, "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
95bool 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
116bool 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
167void 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 LogException err;
321 err << "Tried to initialize a non-existent engine!";
322 throw err;
323 }
324
325 SetEngineRunning(n);
326
327 } else if (n < 0) { // -1 refers to "All Engines"
328
329 for (unsigned int i=0; i<GetNumEngines(); i++) {
330 SetEngineRunning(i);
331 }
332 }
333
335}
336
337//%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
338
339void FGPropulsion::SetEngineRunning(int engineIndex)
340{
341 in.ThrottleCmd[engineIndex] = in.ThrottlePos[engineIndex] = 1; // Set the throttle command and position
342 in.MixtureCmd[engineIndex] = in.MixturePos[engineIndex] = 1; // Set the mixture command and position
343 FDMExec->GetFCS()->SetMixturePos(engineIndex, 1); // Also set FCS values
344 FDMExec->GetFCS()->SetMixtureCmd(engineIndex, 1);
345 GetEngine(engineIndex)->InitRunning();
346}
347
348//%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
349
351{
352 FGModelLoader ModelLoader(this);
353
354 Debug(2);
355 ReadingEngine = false;
356 double FuelDensity = 6.0;
357
358 Name = "Propulsion Model: " + el->GetAttributeValue("name");
359
360 // Perform base class Pre-Load
361 if (!FGModel::Upload(el, true))
362 return false;
363
364 // Process tank definitions first to establish the number of fuel tanks
365
366 Element* tank_element = el->FindElement("tank");
367 unsigned int numTanks = 0;
368
369 while (tank_element) {
370 Tanks.push_back(make_shared<FGTank>(FDMExec, tank_element, numTanks));
371 const auto& tank = Tanks.back();
372 if (tank->GetType() == FGTank::ttFUEL)
373 FuelDensity = tank->GetDensity();
374 else if (tank->GetType() != FGTank::ttOXIDIZER) {
375 FGXMLLogging log(tank_element, LogLevel::ERROR);
376 log << "Unknown tank type specified.\n";
377 return false;
378 }
379 numTanks++;
380 tank_element = el->FindNextElement("tank");
381 }
382
383 ReadingEngine = true;
384 Element* engine_element = el->FindElement("engine");
385 unsigned int numEngines = 0;
386
387 while (engine_element) {
388 if (!ModelLoader.Open(engine_element)) return false;
389
390 try {
391 // Locate the thruster definition
392 Element* thruster_element = engine_element->FindElement("thruster");
393 if (!thruster_element) {
394 XMLLogException err(engine_element);
395 err << "No thruster definition supplied with engine definition.";
396 throw err;
397 }
398 if (!ModelLoader.Open(thruster_element)) {
399 XMLLogException err(thruster_element);
400 err << "Cannot open the thruster element.";
401 throw err;
402 }
403
404 if (engine_element->FindElement("piston_engine")) {
405 Element *element = engine_element->FindElement("piston_engine");
406 Engines.push_back(make_shared<FGPiston>(FDMExec, element, numEngines, in));
407 } else if (engine_element->FindElement("turbine_engine")) {
408 Element *element = engine_element->FindElement("turbine_engine");
409 Engines.push_back(make_shared<FGTurbine>(FDMExec, element, numEngines, in));
410 } else if (engine_element->FindElement("turboprop_engine")) {
411 Element *element = engine_element->FindElement("turboprop_engine");
412 Engines.push_back(make_shared<FGTurboProp>(FDMExec, element, numEngines, in));
413 } else if (engine_element->FindElement("rocket_engine")) {
414 Element *element = engine_element->FindElement("rocket_engine");
415 Engines.push_back(make_shared<FGRocket>(FDMExec, element, numEngines, in));
416 } else if (engine_element->FindElement("electric_engine")) {
417 Element *element = engine_element->FindElement("electric_engine");
418 Engines.push_back(make_shared<FGElectric>(FDMExec, element, numEngines, in));
419 } else if (engine_element->FindElement("brushless_dc_motor")) {
420 Element *element = engine_element->FindElement("brushless_dc_motor");
421 Engines.push_back(make_shared<FGBrushLessDCMotor>(FDMExec, element, numEngines, in));
422 }
423 else {
424 FGXMLLogging log(engine_element, LogLevel::ERROR);
425 log << " Unknown engine type\n";
426 return false;
427 }
428 } catch (XMLLogException& err) {
429 err << "Cannot load " << Name << "\n";
430 return false;
431 } catch (LogException& e) {
432 XMLLogException err(e, engine_element);
433 err << "Cannot load " << Name << "\n";
434 return false;
435 } catch (const BaseException& e) {
436 FGXMLLogging err(engine_element, LogLevel::FATAL);
437 err << "\n" << LogFormat::RED << e.what() << LogFormat::RESET
438 << "\nCannot load " << Name << "\n";
439 return false;
440 }
441
442 numEngines++;
443
444 engine_element = el->FindNextElement("engine");
445 }
446
447 if (numEngines) bind();
448
449 CalculateTankInertias();
450
451 if (el->FindElement("dump-rate"))
452 DumpRate = el->FindElementValueAsNumberConvertTo("dump-rate", "LBS/MIN");
453 if (el->FindElement("refuel-rate"))
454 RefuelRate = el->FindElementValueAsNumberConvertTo("refuel-rate", "LBS/MIN");
455
456 for (auto& engine: Engines)
457 engine->SetFuelDensity(FuelDensity);
458
459 PostLoad(el, FDMExec);
460
461 return true;
462}
463
464//%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
465
466SGPath FGPropulsion::FindFullPathName(const SGPath& path) const
467{
468 SGPath name = FGModel::FindFullPathName(path);
469 if (!ReadingEngine && !name.isNull()) return name;
470
471#ifdef _WIN32
472 // Singular and plural are allowed for the folder names for consistency with
473 // the default engine folder name "engine" and for backward compatibility
474 // regarding the folder name "Engines".
475 const array<string, 2> dir_names = {"Engines", "engine"};
476#else
477 // Allow alternative capitalization for case sensitive OSes.
478 const array<string, 4> dir_names = {"Engines", "engines", "Engine", "engine"};
479#endif
480
481 for(const string& dir_name: dir_names) {
482 name = CheckPathName(FDMExec->GetFullAircraftPath()/dir_name, path);
483 if (!name.isNull()) return name;
484 }
485
486 return CheckPathName(FDMExec->GetEnginePath(), path);
487}
488
489//%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
490
491string FGPropulsion::GetPropulsionStrings(const string& delimiter) const
492{
493 unsigned int i = 0;
494
495 string PropulsionStrings;
496 bool firstime = true;
497 stringstream buf;
498
499 for (auto& engine: Engines) {
500 if (firstime) firstime = false;
501 else PropulsionStrings += delimiter;
502
503 PropulsionStrings += engine->GetEngineLabels(delimiter);
504 }
505 for (auto& tank: Tanks) {
506 if (tank->GetType() == FGTank::ttFUEL) buf << delimiter << "Fuel Tank " << i++;
507 else if (tank->GetType() == FGTank::ttOXIDIZER) buf << delimiter << "Oxidizer Tank " << i++;
508
509 const string& name = tank->GetName();
510 if (!name.empty()) buf << " (" << name << ")";
511 }
512
513 PropulsionStrings += buf.str();
514
515 return PropulsionStrings;
516}
517
518//%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
519
520string FGPropulsion::GetPropulsionValues(const string& delimiter) const
521{
522 string PropulsionValues;
523 bool firstime = true;
524 stringstream buf;
525
526 for (const auto& engine: Engines) {
527 if (firstime) firstime = false;
528 else PropulsionValues += delimiter;
529
530 PropulsionValues += engine->GetEngineValues(delimiter);
531 }
532 for (const auto& tank: Tanks) {
533 buf << delimiter;
534 buf << tank->GetContents();
535 }
536
537 PropulsionValues += buf.str();
538
539 return PropulsionValues;
540}
541
542//%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
543
544string FGPropulsion::GetPropulsionTankReport()
545{
546 string out;
547 stringstream outstream;
548 unsigned int i = 0;
549
550 /*const FGMatrix33& mTkI =*/ CalculateTankInertias();
551
552 for (const auto& tank: Tanks) {
553 string tankdesc;
554 const string& tankname = tank->GetName();
555 if (!tankname.empty()) tankdesc = tankname + " (";
556 if (tank->GetType() == FGTank::ttFUEL && tank->GetGrainType() != FGTank::gtUNKNOWN) {
557 tankdesc += "Solid Fuel";
558 } else if (tank->GetType() == FGTank::ttFUEL) {
559 tankdesc += "Fuel";
560 } else if (tank->GetType() == FGTank::ttOXIDIZER) {
561 tankdesc += "Oxidizer";
562 } else {
563 tankdesc += "Unknown tank type";
564 }
565 if (!tankname.empty()) tankdesc += ")";
566 outstream << fixed << setprecision(1)
567 << left << setw(4) << i++ << setw(30) << tankdesc
568 << right << setw(12) << tank->GetContents() << setw(8) << tank->GetXYZ(eX)
569 << setw(8) << tank->GetXYZ(eY) << setw(8) << tank->GetXYZ(eZ)
570 << setw(12) << tank->GetIxx() << setw(12) << tank->GetIyy()
571 << setw(12) << tank->GetIzz() << "\n";
572 }
573 return outstream.str();
574}
575
576//%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
577
578const FGColumnVector3& FGPropulsion::GetTanksMoment(void)
579{
580 vXYZtank_arm.InitMatrix();
581 for (const auto& tank: Tanks)
582 vXYZtank_arm += tank->GetXYZ() * tank->GetContents();
583
584 return vXYZtank_arm;
585}
586
587//%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
588
589double FGPropulsion::GetTanksWeight(void) const
590{
591 double Tw = 0.0;
592
593 for (const auto& tank: Tanks) Tw += tank->GetContents();
594
595 return Tw;
596}
597
598//%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
599
600const FGMatrix33& FGPropulsion::CalculateTankInertias(void)
601{
602 if (Tanks.empty()) return tankJ;
603
604 tankJ.InitMatrix();
605
606 for (const auto& tank: Tanks) {
607 tankJ += FDMExec->GetMassBalance()->GetPointmassInertia( lbtoslug * tank->GetContents(),
608 tank->GetXYZ());
609 tankJ(1,1) += tank->GetIxx();
610 tankJ(2,2) += tank->GetIyy();
611 tankJ(3,3) += tank->GetIzz();
612 }
613
614 return tankJ;
615}
616
617//%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
618
619void FGPropulsion::SetMagnetos(int setting)
620{
621 if (ActiveEngine < 0) {
622 for (auto& engine: Engines) {
623 // ToDo: first need to make sure the engine Type is really appropriate:
624 // do a check to see if it is of type Piston. This should be done for
625 // all of this kind of possibly across-the-board settings.
626 if (engine->GetType() == FGEngine::etPiston)
627 static_pointer_cast<FGPiston>(engine)->SetMagnetos(setting);
628 }
629 } else {
630 auto engine = dynamic_pointer_cast<FGPiston>(Engines[ActiveEngine]);
631 if (engine)
632 engine->SetMagnetos(setting);
633 }
634}
635
636//%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
637
638void FGPropulsion::SetStarter(int setting)
639{
640 if (ActiveEngine < 0) {
641 for (auto& engine: Engines) {
642 if (setting == 0)
643 engine->SetStarter(false);
644 else
645 engine->SetStarter(true);
646 }
647 } else {
648 if (setting == 0)
649 Engines[ActiveEngine]->SetStarter(false);
650 else
651 Engines[ActiveEngine]->SetStarter(true);
652 }
653}
654
655//%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
656
657int FGPropulsion::GetStarter(void) const
658{
659 if (ActiveEngine < 0) {
660 bool starter = true;
661
662 for (auto& engine: Engines)
663 starter &= engine->GetStarter();
664
665 return starter ? 1 : 0;
666 } else
667 return Engines[ActiveEngine]->GetStarter() ? 1: 0;
668}
669
670//%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
671
672void FGPropulsion::SetCutoff(int setting)
673{
674 bool bsetting = setting == 0 ? false : true;
675
676 if (ActiveEngine < 0) {
677 for (auto& engine: Engines) {
678 switch (engine->GetType()) {
679 case FGEngine::etTurbine:
680 static_pointer_cast<FGTurbine>(engine)->SetCutoff(bsetting);
681 break;
682 case FGEngine::etTurboprop:
683 static_pointer_cast<FGTurboProp>(engine)->SetCutoff(bsetting);
684 break;
685 default:
686 break;
687 }
688 }
689 } else {
690 auto engine = Engines[ActiveEngine];
691 switch (engine->GetType()) {
692 case FGEngine::etTurbine:
693 static_pointer_cast<FGTurbine>(engine)->SetCutoff(bsetting);
694 break;
695 case FGEngine::etTurboprop:
696 static_pointer_cast<FGTurboProp>(engine)->SetCutoff(bsetting);
697 break;
698 default:
699 break;
700 }
701 }
702}
703
704//%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
705
706int FGPropulsion::GetCutoff(void) const
707{
708 if (ActiveEngine < 0) {
709 bool cutoff = true;
710
711 for (auto& engine: Engines) {
712 switch (engine->GetType()) {
713 case FGEngine::etTurbine:
714 cutoff &= static_pointer_cast<FGTurbine>(engine)->GetCutoff();
715 break;
716 case FGEngine::etTurboprop:
717 cutoff &= static_pointer_cast<FGTurboProp>(engine)->GetCutoff();
718 break;
719 default:
720 return -1;
721 }
722 }
723
724 return cutoff ? 1 : 0;
725 } else {
726 auto engine = Engines[ActiveEngine];
727 switch (engine->GetType()) {
728 case FGEngine::etTurbine:
729 return static_pointer_cast<FGTurbine>(engine)->GetCutoff() ? 1 : 0;
730 case FGEngine::etTurboprop:
731 return static_pointer_cast<FGTurboProp>(engine)->GetCutoff() ? 1 : 0;
732 default:
733 break;
734 }
735 }
736
737 return -1;
738}
739
740//%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
741
742void FGPropulsion::SetActiveEngine(int engine)
743{
744 if (engine >= (int)Engines.size() || engine < 0)
745 ActiveEngine = -1;
746 else
747 ActiveEngine = engine;
748}
749
750//%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
751
752double FGPropulsion::Transfer(int source, int target, double amount)
753{
754 double shortage, overage;
755
756 if (source == -1) {
757 shortage = 0.0;
758 } else {
759 shortage = Tanks[source]->Drain(amount);
760 }
761 if (target == -1) {
762 overage = 0.0;
763 } else {
764 overage = Tanks[target]->Fill(amount - shortage);
765 }
766 return overage;
767}
768
769//%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
770
771void FGPropulsion::DoRefuel(double time_slice)
772{
773 double fillrate = RefuelRate / 60.0 * time_slice;
774 int TanksNotFull = 0;
775
776 for (const auto& tank: Tanks) {
777 if (tank->GetPctFull() < 99.99) ++TanksNotFull;
778 }
779
780 // adds fuel equally to all tanks that are not full
781 if (TanksNotFull) {
782 for (unsigned int i=0; i<Tanks.size(); i++) {
783 if (Tanks[i]->GetPctFull() < 99.99)
784 Transfer(-1, i, fillrate/TanksNotFull);
785 }
786 }
787}
788
789//%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
790
791void FGPropulsion::DumpFuel(double time_slice)
792{
793 int TanksDumping = 0;
794
795 for (const auto& tank: Tanks) {
796 if (tank->GetContents() > tank->GetStandpipe()) ++TanksDumping;
797 }
798
799 if (TanksDumping == 0) return;
800
801 double dump_rate_per_tank = DumpRate / 60.0 * time_slice / TanksDumping;
802
803 for (unsigned int i=0; i<Tanks.size(); i++) {
804 if (Tanks[i]->GetContents() > Tanks[i]->GetStandpipe()) {
805 Transfer(i, -1, dump_rate_per_tank);
806 }
807 }
808}
809
810//%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
811
812void FGPropulsion::SetFuelFreeze(bool f)
813{
814 FuelFreeze = f;
815 for (auto& engine: Engines) engine->SetFuelFreeze(f);
816}
817
818//%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
819
820void FGPropulsion::bind(void)
821{
822 bool HavePistonEngine = false;
823 bool HaveTurboEngine = false;
824
825 for (const auto& engine: Engines) {
826 if (!HavePistonEngine && engine->GetType() == FGEngine::etPiston) HavePistonEngine = true;
827 if (!HaveTurboEngine && engine->GetType() == FGEngine::etTurbine) HaveTurboEngine = true;
828 if (!HaveTurboEngine && engine->GetType() == FGEngine::etTurboprop) HaveTurboEngine = true;
829 }
830
831 PropertyManager->Tie<FGPropulsion, int>("propulsion/set-running", this, nullptr,
833 if (HaveTurboEngine) {
834 PropertyManager->Tie("propulsion/starter_cmd", this, &FGPropulsion::GetStarter, &FGPropulsion::SetStarter);
835 PropertyManager->Tie("propulsion/cutoff_cmd", this, &FGPropulsion::GetCutoff, &FGPropulsion::SetCutoff);
836 }
837
838 if (HavePistonEngine) {
839 PropertyManager->Tie("propulsion/starter_cmd", this, &FGPropulsion::GetStarter, &FGPropulsion::SetStarter);
840 PropertyManager->Tie<FGPropulsion, int>("propulsion/magneto_cmd", this,
841 nullptr, &FGPropulsion::SetMagnetos);
842 }
843
844 PropertyManager->Tie("propulsion/active_engine", this, &FGPropulsion::GetActiveEngine,
845 &FGPropulsion::SetActiveEngine);
846 PropertyManager->Tie("forces/fbx-prop-lbs", this, eX, &FGPropulsion::GetForces);
847 PropertyManager->Tie("forces/fby-prop-lbs", this, eY, &FGPropulsion::GetForces);
848 PropertyManager->Tie("forces/fbz-prop-lbs", this, eZ, &FGPropulsion::GetForces);
849 PropertyManager->Tie("moments/l-prop-lbsft", this, eX, &FGPropulsion::GetMoments);
850 PropertyManager->Tie("moments/m-prop-lbsft", this, eY, &FGPropulsion::GetMoments);
851 PropertyManager->Tie("moments/n-prop-lbsft", this, eZ, &FGPropulsion::GetMoments);
852 PropertyManager->Tie("propulsion/total-fuel-lbs", &TotalFuelQuantity);
853 PropertyManager->Tie("propulsion/total-oxidizer-lbs", &TotalOxidizerQuantity);
854 PropertyManager->Tie("propulsion/refuel", &refuel);
855 PropertyManager->Tie("propulsion/fuel_dump", &dump);
856 PropertyManager->Tie<FGPropulsion, bool>("propulsion/fuel_freeze", this,
857 nullptr, &FGPropulsion::SetFuelFreeze);
858}
859
860//%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
861// The bitmasked value choices are as follows:
862// unset: In this case (the default) JSBSim would only print
863// out the normally expected messages, essentially echoing
864// the config files as they are read. If the environment
865// variable is not set, debug_lvl is set to 1 internally
866// 0: This requests JSBSim not to output any messages
867// whatsoever.
868// 1: This value explicity requests the normal JSBSim
869// startup messages
870// 2: This value asks for a message to be printed out when
871// a class is instantiated
872// 4: When this value is set, a message is displayed when a
873// FGModel object executes its Run() method
874// 8: When this value is set, various runtime state variables
875// are printed out periodically
876// 16: When set various parameters are sanity checked and
877// a message is printed out when they go out of bounds
878
879void FGPropulsion::Debug(int from)
880{
881 if (debug_lvl <= 0) return;
882
883 if (debug_lvl & 1) { // Standard console startup message output
884 if (from == 2) { // Loader
885 FGLogging log(LogLevel::DEBUG);
886 log << "\n Propulsion:\n";
887 }
888 }
889 if (debug_lvl & 2 ) { // Instantiation/Destruction notification
890 FGLogging log(LogLevel::DEBUG);
891 if (from == 0) log << "Instantiated: FGPropulsion\n";
892 if (from == 1) log << "Destroyed: FGPropulsion\n";
893 }
894 if (debug_lvl & 4 ) { // Run() method entry print for FGModel-derived objects
895 }
896 if (debug_lvl & 8 ) { // Runtime state variables
897 }
898 if (debug_lvl & 16) { // Sanity checking
899 }
900 if (debug_lvl & 64) {
901 if (from == 0) { // Constructor
902 }
903 }
904}
905}
Element * FindElement(const std::string &el="")
Searches for a specified element.
std::string GetAttributeValue(const std::string &key)
Retrieves an attribute.
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:185
const SGPath & GetEnginePath(void)
Retrieves the engine path.
Definition FGFDMExec.h:396
std::shared_ptr< FGFCS > GetFCS(void) const
Returns the FGFCS pointer.
const SGPath & GetFullAircraftPath(void)
Retrieves the full aircraft path name.
Definition FGFDMExec.h:402
double GetDeltaT(void) const
Returns the simulation delta T.
Definition FGFDMExec.h:557
std::shared_ptr< FGMassBalance > GetMassBalance(void) const
Returns the FGAircraft pointer.
void InitMatrix(void)
Initialize the matrix.
Base class for all scheduled JSBSim models.
Definition FGModel.h:79
virtual bool Run(bool Holding)
Runs the model; called by the Executive.
Definition FGModel.cpp:101
bool Upload(Element *el, bool preLoad)
Uploads this model in memory.
Definition FGModel.cpp:123
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.
size_t GetNumEngines(void) const
Retrieves the number of engines defined for the aircraft.
void InitRunning(int n)
Sets up the engines as running.
Main namespace for the JSBSim Flight Dynamics Model.
Definition FGFDMExec.cpp:73