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"
69extern short debug_lvl;
95bool FGPropulsion::InitModel(
void)
99 if (!FGModel::InitModel())
return false;
101 vForces.InitMatrix();
102 vMoments.InitMatrix();
104 for (
auto& tank: Tanks) tank->ResetToIC();
105 TotalFuelQuantity = 0.0;
106 TotalOxidizerQuantity = 0.0;
107 refuel = dump =
false;
109 for (
auto& engine: Engines) engine->ResetToIC();
119 if (Holding)
return false;
123 vForces.InitMatrix();
124 vMoments.InitMatrix();
126 for (
auto& engine: Engines) {
128 ConsumeFuel(engine.get());
129 vForces += engine->GetBodyForces();
130 vMoments += engine->GetMoments();
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()) {
139 TotalFuelQuantity += tank->GetContents();
141 case FGTank::ttOXIDIZER:
142 TotalOxidizerQuantity += tank->GetContents();
149 if (refuel) DoRefuel( in.TotalDeltaT );
150 if (dump) DumpFuel( in.TotalDeltaT );
167void FGPropulsion::ConsumeFuel(
FGEngine* engine)
169 if (FuelFreeze)
return;
170 if (FDMExec->GetTrimStatus())
return;
172 unsigned int TanksWithFuel=0, CurrentFuelTankPriority=1;
173 unsigned int TanksWithOxidizer=0, CurrentOxidizerTankPriority=1;
174 vector <int> FeedListFuel, FeedListOxi;
176 bool hasOxTanks =
false;
184 size_t numTanks = Tanks.size();
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()) {
195 if ((Tank->GetContents() > Tank->GetUnusable()) && Tank->GetSelected() && (TankPriority == CurrentFuelTankPriority)) {
198 FeedListFuel.push_back(TankId);
201 case FGTank::ttOXIDIZER:
207 if (TanksWithFuel == 0) CurrentFuelTankPriority++;
210 bool FuelStarved = Starved;
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()) {
225 case FGTank::ttOXIDIZER:
227 if (Tank->GetContents() > Tank->GetUnusable() && Tank->GetSelected() && TankPriority == CurrentOxidizerTankPriority) {
229 if (TanksWithFuel > 0) Starved =
false;
230 FeedListOxi.push_back(TankId);
236 if (TanksWithOxidizer == 0) CurrentOxidizerTankPriority++;
240 bool OxiStarved = Starved;
242 engine->SetStarved(FuelStarved || (hasOxTanks && OxiStarved));
246 if (FuelStarved || (hasOxTanks && OxiStarved))
return;
249 double FuelNeededPerTank = FuelToBurn / TanksWithFuel;
250 for (
const auto& feed: FeedListFuel)
251 Tanks[feed]->Drain(FuelNeededPerTank);
253 if (engine->GetType() == FGEngine::etRocket) {
254 double OxidizerToBurn = engine->CalcOxidizerNeed();
255 double OxidizerNeededPerTank = 0;
256 if (TanksWithOxidizer > 0) OxidizerNeededPerTank = OxidizerToBurn / TanksWithOxidizer;
257 for (
const auto& feed: FeedListOxi)
258 Tanks[feed]->Drain(OxidizerNeededPerTank);
267 double currentThrust = 0, lastThrust = -1;
268 int steady_count = 0, j = 0;
270 bool TrimMode = FDMExec->GetTrimStatus();
273 vForces.InitMatrix();
274 vMoments.InitMatrix();
277 FDMExec->SetTrimStatus(
true);
280 in.TotalDeltaT = 0.5;
282 for (
auto& engine: Engines) {
286 while (!steady && j < 6000) {
288 lastThrust = currentThrust;
289 currentThrust = engine->GetThrust();
290 if (fabs(lastThrust-currentThrust) < 0.0001) {
292 if (steady_count > 120) {
300 vForces += engine->GetBodyForces();
301 vMoments += engine->GetMoments();
304 FDMExec->SetTrimStatus(TrimMode);
305 in.TotalDeltaT = TimeStep;
321 err <<
"Tried to initialize a non-existent engine!";
339void FGPropulsion::SetEngineRunning(
int engineIndex)
341 in.ThrottleCmd[engineIndex] = in.ThrottlePos[engineIndex] = 1;
342 in.MixtureCmd[engineIndex] = in.MixturePos[engineIndex] = 1;
343 FDMExec->
GetFCS()->SetMixturePos(engineIndex, 1);
344 FDMExec->
GetFCS()->SetMixtureCmd(engineIndex, 1);
355 ReadingEngine =
false;
356 double FuelDensity = 6.0;
367 unsigned int numTanks = 0;
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) {
376 log <<
"Unknown tank type specified.\n";
383 ReadingEngine =
true;
385 unsigned int numEngines = 0;
387 while (engine_element) {
388 if (!ModelLoader.Open(engine_element))
return false;
393 if (!thruster_element) {
395 err <<
"No thruster definition supplied with engine definition.";
398 if (!ModelLoader.Open(thruster_element)) {
400 err <<
"Cannot open the thruster element.";
404 if (engine_element->
FindElement(
"piston_engine")) {
406 Engines.push_back(make_shared<FGPiston>(FDMExec, element, numEngines, in));
407 }
else if (engine_element->
FindElement(
"turbine_engine")) {
409 Engines.push_back(make_shared<FGTurbine>(FDMExec, element, numEngines, in));
410 }
else if (engine_element->
FindElement(
"turboprop_engine")) {
412 Engines.push_back(make_shared<FGTurboProp>(FDMExec, element, numEngines, in));
413 }
else if (engine_element->
FindElement(
"rocket_engine")) {
415 Engines.push_back(make_shared<FGRocket>(FDMExec, element, numEngines, in));
416 }
else if (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")) {
421 Engines.push_back(make_shared<FGBrushLessDCMotor>(FDMExec, element, numEngines, in));
425 log <<
" Unknown engine type\n";
429 err <<
"Cannot load " << Name <<
"\n";
433 err <<
"Cannot load " << Name <<
"\n";
437 err <<
"\n" << LogFormat::RED << e.what() << LogFormat::RESET
438 <<
"\nCannot load " << Name <<
"\n";
447 if (numEngines) bind();
449 CalculateTankInertias();
456 for (
auto& engine: Engines)
457 engine->SetFuelDensity(FuelDensity);
459 PostLoad(el, FDMExec);
466SGPath FGPropulsion::FindFullPathName(
const SGPath& path)
const
468 SGPath name = FGModel::FindFullPathName(path);
469 if (!ReadingEngine && !name.isNull())
return name;
475 const array<string, 2> dir_names = {
"Engines",
"engine"};
478 const array<string, 4> dir_names = {
"Engines",
"engines",
"Engine",
"engine"};
481 for(
const string& dir_name: dir_names) {
483 if (!name.isNull())
return name;
491string FGPropulsion::GetPropulsionStrings(
const string& delimiter)
const
495 string PropulsionStrings;
496 bool firstime =
true;
499 for (
auto& engine: Engines) {
500 if (firstime) firstime =
false;
501 else PropulsionStrings += delimiter;
503 PropulsionStrings += engine->GetEngineLabels(delimiter);
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++;
509 const string& name = tank->GetName();
510 if (!name.empty()) buf <<
" (" << name <<
")";
513 PropulsionStrings += buf.str();
515 return PropulsionStrings;
520string FGPropulsion::GetPropulsionValues(
const string& delimiter)
const
522 string PropulsionValues;
523 bool firstime =
true;
526 for (
const auto& engine: Engines) {
527 if (firstime) firstime =
false;
528 else PropulsionValues += delimiter;
530 PropulsionValues += engine->GetEngineValues(delimiter);
532 for (
const auto& tank: Tanks) {
534 buf << tank->GetContents();
537 PropulsionValues += buf.str();
539 return PropulsionValues;
544string FGPropulsion::GetPropulsionTankReport()
547 stringstream outstream;
550 CalculateTankInertias();
552 for (
const auto& tank: Tanks) {
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) {
560 }
else if (tank->GetType() == FGTank::ttOXIDIZER) {
561 tankdesc +=
"Oxidizer";
563 tankdesc +=
"Unknown tank type";
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";
573 return outstream.str();
578const FGColumnVector3& FGPropulsion::GetTanksMoment(
void)
580 vXYZtank_arm.InitMatrix();
581 for (
const auto& tank: Tanks)
582 vXYZtank_arm += tank->GetXYZ() * tank->GetContents();
589double FGPropulsion::GetTanksWeight(
void)
const
593 for (
const auto& tank: Tanks) Tw += tank->GetContents();
600const FGMatrix33& FGPropulsion::CalculateTankInertias(
void)
602 if (Tanks.empty())
return tankJ;
606 for (
const auto& tank: Tanks) {
607 tankJ += FDMExec->
GetMassBalance()->GetPointmassInertia( lbtoslug * tank->GetContents(),
609 tankJ(1,1) += tank->GetIxx();
610 tankJ(2,2) += tank->GetIyy();
611 tankJ(3,3) += tank->GetIzz();
619void FGPropulsion::SetMagnetos(
int setting)
621 if (ActiveEngine < 0) {
622 for (
auto& engine: Engines) {
626 if (engine->GetType() == FGEngine::etPiston)
627 static_pointer_cast<FGPiston>(engine)->SetMagnetos(setting);
630 auto engine = dynamic_pointer_cast<FGPiston>(Engines[ActiveEngine]);
632 engine->SetMagnetos(setting);
638void FGPropulsion::SetStarter(
int setting)
640 if (ActiveEngine < 0) {
641 for (
auto& engine: Engines) {
643 engine->SetStarter(
false);
645 engine->SetStarter(
true);
649 Engines[ActiveEngine]->SetStarter(
false);
651 Engines[ActiveEngine]->SetStarter(
true);
657int FGPropulsion::GetStarter(
void)
const
659 if (ActiveEngine < 0) {
662 for (
auto& engine: Engines)
663 starter &= engine->GetStarter();
665 return starter ? 1 : 0;
667 return Engines[ActiveEngine]->GetStarter() ? 1: 0;
672void FGPropulsion::SetCutoff(
int setting)
674 bool bsetting = setting == 0 ? false :
true;
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);
682 case FGEngine::etTurboprop:
683 static_pointer_cast<FGTurboProp>(engine)->SetCutoff(bsetting);
690 auto engine = Engines[ActiveEngine];
691 switch (engine->GetType()) {
692 case FGEngine::etTurbine:
693 static_pointer_cast<FGTurbine>(engine)->SetCutoff(bsetting);
695 case FGEngine::etTurboprop:
696 static_pointer_cast<FGTurboProp>(engine)->SetCutoff(bsetting);
706int FGPropulsion::GetCutoff(
void)
const
708 if (ActiveEngine < 0) {
711 for (
auto& engine: Engines) {
712 switch (engine->GetType()) {
713 case FGEngine::etTurbine:
714 cutoff &= static_pointer_cast<FGTurbine>(engine)->GetCutoff();
716 case FGEngine::etTurboprop:
717 cutoff &= static_pointer_cast<FGTurboProp>(engine)->GetCutoff();
724 return cutoff ? 1 : 0;
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;
742void FGPropulsion::SetActiveEngine(
int engine)
744 if (engine >= (
int)Engines.size() || engine < 0)
747 ActiveEngine = engine;
752double FGPropulsion::Transfer(
int source,
int target,
double amount)
754 double shortage, overage;
759 shortage = Tanks[source]->Drain(amount);
764 overage = Tanks[target]->Fill(amount - shortage);
771void FGPropulsion::DoRefuel(
double time_slice)
773 double fillrate = RefuelRate / 60.0 * time_slice;
774 int TanksNotFull = 0;
776 for (
const auto& tank: Tanks) {
777 if (tank->GetPctFull() < 99.99) ++TanksNotFull;
782 for (
unsigned int i=0; i<Tanks.size(); i++) {
783 if (Tanks[i]->GetPctFull() < 99.99)
784 Transfer(-1, i, fillrate/TanksNotFull);
791void FGPropulsion::DumpFuel(
double time_slice)
793 int TanksDumping = 0;
795 for (
const auto& tank: Tanks) {
796 if (tank->GetContents() > tank->GetStandpipe()) ++TanksDumping;
799 if (TanksDumping == 0)
return;
801 double dump_rate_per_tank = DumpRate / 60.0 * time_slice / TanksDumping;
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);
812void FGPropulsion::SetFuelFreeze(
bool f)
815 for (
auto& engine: Engines) engine->SetFuelFreeze(f);
820void FGPropulsion::bind(
void)
822 bool HavePistonEngine =
false;
823 bool HaveTurboEngine =
false;
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;
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);
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);
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);
879void FGPropulsion::Debug(
int from)
881 if (debug_lvl <= 0)
return;
885 FGLogging log(LogLevel::DEBUG);
886 log <<
"\n Propulsion:\n";
889 if (debug_lvl & 2 ) {
890 FGLogging log(LogLevel::DEBUG);
891 if (from == 0) log <<
"Instantiated: FGPropulsion\n";
892 if (from == 1) log <<
"Destroyed: FGPropulsion\n";
894 if (debug_lvl & 4 ) {
896 if (debug_lvl & 8 ) {
898 if (debug_lvl & 16) {
900 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.
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.
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.
Main namespace for the JSBSim Flight Dynamics Model.