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