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"
68extern short debug_lvl;
76 Name =
"FGPropulsion";
96bool FGPropulsion::InitModel(
void)
100 if (!FGModel::InitModel())
return false;
102 vForces.InitMatrix();
103 vMoments.InitMatrix();
105 for (
auto& tank: Tanks) tank->ResetToIC();
106 TotalFuelQuantity = 0.0;
107 TotalOxidizerQuantity = 0.0;
108 refuel = dump =
false;
110 for (
auto& engine: Engines) engine->ResetToIC();
120 if (Holding)
return false;
124 vForces.InitMatrix();
125 vMoments.InitMatrix();
127 for (
auto& engine: Engines) {
129 ConsumeFuel(engine.get());
130 vForces += engine->GetBodyForces();
131 vMoments += engine->GetMoments();
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()) {
140 TotalFuelQuantity += tank->GetContents();
142 case FGTank::ttOXIDIZER:
143 TotalOxidizerQuantity += tank->GetContents();
150 if (refuel) DoRefuel( in.TotalDeltaT );
151 if (dump) DumpFuel( in.TotalDeltaT );
168void FGPropulsion::ConsumeFuel(
FGEngine* engine)
170 if (FuelFreeze)
return;
171 if (FDMExec->GetTrimStatus())
return;
173 unsigned int TanksWithFuel=0, CurrentFuelTankPriority=1;
174 unsigned int TanksWithOxidizer=0, CurrentOxidizerTankPriority=1;
175 vector <int> FeedListFuel, FeedListOxi;
177 bool hasOxTanks =
false;
185 size_t numTanks = Tanks.size();
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()) {
196 if ((Tank->GetContents() > Tank->GetUnusable()) && Tank->GetSelected() && (TankPriority == CurrentFuelTankPriority)) {
199 FeedListFuel.push_back(TankId);
202 case FGTank::ttOXIDIZER:
208 if (TanksWithFuel == 0) CurrentFuelTankPriority++;
211 bool FuelStarved = Starved;
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()) {
226 case FGTank::ttOXIDIZER:
228 if (Tank->GetContents() > Tank->GetUnusable() && Tank->GetSelected() && TankPriority == CurrentOxidizerTankPriority) {
230 if (TanksWithFuel > 0) Starved =
false;
231 FeedListOxi.push_back(TankId);
237 if (TanksWithOxidizer == 0) CurrentOxidizerTankPriority++;
241 bool OxiStarved = Starved;
243 engine->SetStarved(FuelStarved || (hasOxTanks && OxiStarved));
247 if (FuelStarved || (hasOxTanks && OxiStarved))
return;
250 double FuelNeededPerTank = FuelToBurn / TanksWithFuel;
251 for (
const auto& feed: FeedListFuel)
252 Tanks[feed]->Drain(FuelNeededPerTank);
254 if (engine->GetType() == FGEngine::etRocket) {
255 double OxidizerToBurn = engine->CalcOxidizerNeed();
256 double OxidizerNeededPerTank = 0;
257 if (TanksWithOxidizer > 0) OxidizerNeededPerTank = OxidizerToBurn / TanksWithOxidizer;
258 for (
const auto& feed: FeedListOxi)
259 Tanks[feed]->Drain(OxidizerNeededPerTank);
268 double currentThrust = 0, lastThrust = -1;
269 int steady_count = 0, j = 0;
271 bool TrimMode = FDMExec->GetTrimStatus();
274 vForces.InitMatrix();
275 vMoments.InitMatrix();
278 FDMExec->SetTrimStatus(
true);
281 in.TotalDeltaT = 0.5;
283 for (
auto& engine: Engines) {
287 while (!steady && j < 6000) {
289 lastThrust = currentThrust;
290 currentThrust = engine->GetThrust();
291 if (fabs(lastThrust-currentThrust) < 0.0001) {
293 if (steady_count > 120) {
301 vForces += engine->GetBodyForces();
302 vMoments += engine->GetMoments();
305 FDMExec->SetTrimStatus(TrimMode);
306 in.TotalDeltaT = TimeStep;
321 throw(
string(
"Tried to initialize a non-existent engine!"));
338void FGPropulsion::SetEngineRunning(
int engineIndex)
340 in.ThrottleCmd[engineIndex] = in.ThrottlePos[engineIndex] = 1;
341 in.MixtureCmd[engineIndex] = in.MixturePos[engineIndex] = 1;
342 FDMExec->
GetFCS()->SetMixturePos(engineIndex, 1);
343 FDMExec->
GetFCS()->SetMixtureCmd(engineIndex, 1);
354 ReadingEngine =
false;
355 double FuelDensity = 6.0;
366 unsigned int numTanks = 0;
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;
381 ReadingEngine =
true;
383 unsigned int numEngines = 0;
385 while (engine_element) {
386 if (!ModelLoader.Open(engine_element))
return false;
391 if (!thruster_element || !ModelLoader.Open(thruster_element))
392 throw(
"No thruster definition supplied with engine definition.");
394 if (engine_element->
FindElement(
"piston_engine")) {
396 Engines.push_back(make_shared<FGPiston>(FDMExec, element, numEngines, in));
397 }
else if (engine_element->
FindElement(
"turbine_engine")) {
399 Engines.push_back(make_shared<FGTurbine>(FDMExec, element, numEngines, in));
400 }
else if (engine_element->
FindElement(
"turboprop_engine")) {
402 Engines.push_back(make_shared<FGTurboProp>(FDMExec, element, numEngines, in));
403 }
else if (engine_element->
FindElement(
"rocket_engine")) {
405 Engines.push_back(make_shared<FGRocket>(FDMExec, element, numEngines, in));
406 }
else if (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")) {
411 Engines.push_back(make_shared<FGBrushLessDCMotor>(FDMExec, element, numEngines, in));
414 cerr << engine_element->
ReadFrom() <<
" Unknown engine type" << endl;
417 }
catch (std::string& str) {
418 cerr << endl <<
fgred << str <<
reset << endl;
427 if (numEngines) bind();
429 CalculateTankInertias();
436 for (
auto& engine: Engines)
437 engine->SetFuelDensity(FuelDensity);
439 PostLoad(el, FDMExec);
446SGPath FGPropulsion::FindFullPathName(
const SGPath& path)
const
448 SGPath name = FGModel::FindFullPathName(path);
449 if (!ReadingEngine && !name.isNull())
return name;
455 const array<string, 2> dir_names = {
"Engines",
"engine"};
458 const array<string, 4> dir_names = {
"Engines",
"engines",
"Engine",
"engine"};
461 for(
const string& dir_name: dir_names) {
463 if (!name.isNull())
return name;
471string FGPropulsion::GetPropulsionStrings(
const string& delimiter)
const
475 string PropulsionStrings;
476 bool firstime =
true;
479 for (
auto& engine: Engines) {
480 if (firstime) firstime =
false;
481 else PropulsionStrings += delimiter;
483 PropulsionStrings += engine->GetEngineLabels(delimiter);
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++;
489 const string& name = tank->GetName();
490 if (!name.empty()) buf <<
" (" << name <<
")";
493 PropulsionStrings += buf.str();
495 return PropulsionStrings;
500string FGPropulsion::GetPropulsionValues(
const string& delimiter)
const
502 string PropulsionValues;
503 bool firstime =
true;
506 for (
const auto& engine: Engines) {
507 if (firstime) firstime =
false;
508 else PropulsionValues += delimiter;
510 PropulsionValues += engine->GetEngineValues(delimiter);
512 for (
const auto& tank: Tanks) {
514 buf << tank->GetContents();
517 PropulsionValues += buf.str();
519 return PropulsionValues;
524string FGPropulsion::GetPropulsionTankReport()
527 stringstream outstream;
530 CalculateTankInertias();
532 for (
const auto& tank: Tanks) {
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) {
540 }
else if (tank->GetType() == FGTank::ttOXIDIZER) {
541 tankdesc +=
"Oxidizer";
543 tankdesc +=
"Unknown tank type";
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;
552 return outstream.str();
557const FGColumnVector3& FGPropulsion::GetTanksMoment(
void)
559 vXYZtank_arm.InitMatrix();
560 for (
const auto& tank: Tanks)
561 vXYZtank_arm += tank->GetXYZ() * tank->GetContents();
568double FGPropulsion::GetTanksWeight(
void)
const
572 for (
const auto& tank: Tanks) Tw += tank->GetContents();
579const FGMatrix33& FGPropulsion::CalculateTankInertias(
void)
581 if (Tanks.empty())
return tankJ;
585 for (
const auto& tank: Tanks) {
586 tankJ += FDMExec->
GetMassBalance()->GetPointmassInertia( lbtoslug * tank->GetContents(),
588 tankJ(1,1) += tank->GetIxx();
589 tankJ(2,2) += tank->GetIyy();
590 tankJ(3,3) += tank->GetIzz();
598void FGPropulsion::SetMagnetos(
int setting)
600 if (ActiveEngine < 0) {
601 for (
auto& engine: Engines) {
605 if (engine->GetType() == FGEngine::etPiston)
606 static_pointer_cast<FGPiston>(engine)->SetMagnetos(setting);
609 auto engine = dynamic_pointer_cast<FGPiston>(Engines[ActiveEngine]);
611 engine->SetMagnetos(setting);
617void FGPropulsion::SetStarter(
int setting)
619 if (ActiveEngine < 0) {
620 for (
auto& engine: Engines) {
622 engine->SetStarter(
false);
624 engine->SetStarter(
true);
628 Engines[ActiveEngine]->SetStarter(
false);
630 Engines[ActiveEngine]->SetStarter(
true);
636int FGPropulsion::GetStarter(
void)
const
638 if (ActiveEngine < 0) {
641 for (
auto& engine: Engines)
642 starter &= engine->GetStarter();
644 return starter ? 1 : 0;
646 return Engines[ActiveEngine]->GetStarter() ? 1: 0;
651void FGPropulsion::SetCutoff(
int setting)
653 bool bsetting = setting == 0 ? false :
true;
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);
661 case FGEngine::etTurboprop:
662 static_pointer_cast<FGTurboProp>(engine)->SetCutoff(bsetting);
669 auto engine = Engines[ActiveEngine];
670 switch (engine->GetType()) {
671 case FGEngine::etTurbine:
672 static_pointer_cast<FGTurbine>(engine)->SetCutoff(bsetting);
674 case FGEngine::etTurboprop:
675 static_pointer_cast<FGTurboProp>(engine)->SetCutoff(bsetting);
685int FGPropulsion::GetCutoff(
void)
const
687 if (ActiveEngine < 0) {
690 for (
auto& engine: Engines) {
691 switch (engine->GetType()) {
692 case FGEngine::etTurbine:
693 cutoff &= static_pointer_cast<FGTurbine>(engine)->GetCutoff();
695 case FGEngine::etTurboprop:
696 cutoff &= static_pointer_cast<FGTurboProp>(engine)->GetCutoff();
703 return cutoff ? 1 : 0;
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;
721void FGPropulsion::SetActiveEngine(
int engine)
723 if (engine >= (
int)Engines.size() || engine < 0)
726 ActiveEngine = engine;
731double FGPropulsion::Transfer(
int source,
int target,
double amount)
733 double shortage, overage;
738 shortage = Tanks[source]->Drain(amount);
743 overage = Tanks[target]->Fill(amount - shortage);
750void FGPropulsion::DoRefuel(
double time_slice)
752 double fillrate = RefuelRate / 60.0 * time_slice;
753 int TanksNotFull = 0;
755 for (
const auto& tank: Tanks) {
756 if (tank->GetPctFull() < 99.99) ++TanksNotFull;
761 for (
unsigned int i=0; i<Tanks.size(); i++) {
762 if (Tanks[i]->GetPctFull() < 99.99)
763 Transfer(-1, i, fillrate/TanksNotFull);
770void FGPropulsion::DumpFuel(
double time_slice)
772 int TanksDumping = 0;
774 for (
const auto& tank: Tanks) {
775 if (tank->GetContents() > tank->GetStandpipe()) ++TanksDumping;
778 if (TanksDumping == 0)
return;
780 double dump_rate_per_tank = DumpRate / 60.0 * time_slice / TanksDumping;
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);
791void FGPropulsion::SetFuelFreeze(
bool f)
794 for (
auto& engine: Engines) engine->SetFuelFreeze(f);
799void FGPropulsion::bind(
void)
801 typedef int (FGPropulsion::*iPMF)(void) const;
802 typedef bool (FGPropulsion::*bPMF)(void) const;
803 bool HavePistonEngine =
false;
804 bool HaveTurboEngine =
false;
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;
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);
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);
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);
857void FGPropulsion::Debug(
int from)
859 if (debug_lvl <= 0)
return;
863 cout << endl <<
" Propulsion:" << endl;
866 if (debug_lvl & 2 ) {
867 if (from == 0) cout <<
"Instantiated: FGPropulsion" << endl;
868 if (from == 1) cout <<
"Destroyed: FGPropulsion" << endl;
870 if (debug_lvl & 4 ) {
872 if (debug_lvl & 8 ) {
874 if (debug_lvl & 16) {
876 if (debug_lvl & 64) {
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.
virtual double CalcFuelNeed(void)
The fuel need is calculated based on power levels and flow rate for that power level.
virtual void Calculate(void)=0
Calculates the thrust of the engine, and other engine functions.
Encapsulates the JSBSim simulation executive.
const SGPath & GetEnginePath(void)
Retrieves the engine path.
std::shared_ptr< FGFCS > GetFCS(void) const
Returns the FGFCS pointer.
const SGPath & GetFullAircraftPath(void)
Retrieves the full aircraft path name.
double GetDeltaT(void) const
Returns the simulation delta T.
std::shared_ptr< FGMassBalance > GetMassBalance(void) const
Returns the FGAircraft pointer.
static char normint[6]
normal intensity text
static char fgred[6]
red text
static char reset[5]
resets text properties
static char highint[5]
highlights text
void InitMatrix(void)
Initialize the matrix.
Base class for all scheduled JSBSim models.
virtual bool Run(bool Holding)
Runs the model; called by the Executive.
bool Upload(Element *el, bool preLoad)
Uploads this model in memory.
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.