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