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;
77 Name =
"FGPropulsion";
97bool FGPropulsion::InitModel(
void)
101 if (!FGModel::InitModel())
return false;
103 vForces.InitMatrix();
104 vMoments.InitMatrix();
106 for (
auto& tank: Tanks) tank->ResetToIC();
107 TotalFuelQuantity = 0.0;
108 TotalOxidizerQuantity = 0.0;
109 refuel = dump =
false;
111 for (
auto& engine: Engines) engine->ResetToIC();
121 if (Holding)
return false;
125 vForces.InitMatrix();
126 vMoments.InitMatrix();
128 for (
auto& engine: Engines) {
130 ConsumeFuel(engine.get());
131 vForces += engine->GetBodyForces();
132 vMoments += engine->GetMoments();
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()) {
141 TotalFuelQuantity += tank->GetContents();
143 case FGTank::ttOXIDIZER:
144 TotalOxidizerQuantity += tank->GetContents();
151 if (refuel) DoRefuel( in.TotalDeltaT );
152 if (dump) DumpFuel( in.TotalDeltaT );
169void FGPropulsion::ConsumeFuel(
FGEngine* engine)
171 if (FuelFreeze)
return;
172 if (FDMExec->GetTrimStatus())
return;
174 unsigned int TanksWithFuel=0, CurrentFuelTankPriority=1;
175 unsigned int TanksWithOxidizer=0, CurrentOxidizerTankPriority=1;
176 vector <int> FeedListFuel, FeedListOxi;
178 bool hasOxTanks =
false;
186 size_t numTanks = Tanks.size();
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()) {
197 if ((Tank->GetContents() > Tank->GetUnusable()) && Tank->GetSelected() && (TankPriority == CurrentFuelTankPriority)) {
200 FeedListFuel.push_back(TankId);
203 case FGTank::ttOXIDIZER:
209 if (TanksWithFuel == 0) CurrentFuelTankPriority++;
212 bool FuelStarved = Starved;
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()) {
227 case FGTank::ttOXIDIZER:
229 if (Tank->GetContents() > Tank->GetUnusable() && Tank->GetSelected() && TankPriority == CurrentOxidizerTankPriority) {
231 if (TanksWithFuel > 0) Starved =
false;
232 FeedListOxi.push_back(TankId);
238 if (TanksWithOxidizer == 0) CurrentOxidizerTankPriority++;
242 bool OxiStarved = Starved;
244 engine->SetStarved(FuelStarved || (hasOxTanks && OxiStarved));
248 if (FuelStarved || (hasOxTanks && OxiStarved))
return;
251 double FuelNeededPerTank = FuelToBurn / TanksWithFuel;
252 for (
const auto& feed: FeedListFuel)
253 Tanks[feed]->Drain(FuelNeededPerTank);
255 if (engine->GetType() == FGEngine::etRocket) {
256 double OxidizerToBurn = engine->CalcOxidizerNeed();
257 double OxidizerNeededPerTank = 0;
258 if (TanksWithOxidizer > 0) OxidizerNeededPerTank = OxidizerToBurn / TanksWithOxidizer;
259 for (
const auto& feed: FeedListOxi)
260 Tanks[feed]->Drain(OxidizerNeededPerTank);
269 double currentThrust = 0, lastThrust = -1;
270 int steady_count = 0, j = 0;
272 bool TrimMode = FDMExec->GetTrimStatus();
275 vForces.InitMatrix();
276 vMoments.InitMatrix();
279 FDMExec->SetTrimStatus(
true);
282 in.TotalDeltaT = 0.5;
284 for (
auto& engine: Engines) {
288 while (!steady && j < 6000) {
290 lastThrust = currentThrust;
291 currentThrust = engine->GetThrust();
292 if (fabs(lastThrust-currentThrust) < 0.0001) {
294 if (steady_count > 120) {
302 vForces += engine->GetBodyForces();
303 vMoments += engine->GetMoments();
306 FDMExec->SetTrimStatus(TrimMode);
307 in.TotalDeltaT = TimeStep;
323 err <<
"Tried to initialize a non-existent engine!";
341void FGPropulsion::SetEngineRunning(
int engineIndex)
343 in.ThrottleCmd[engineIndex] = in.ThrottlePos[engineIndex] = 1;
344 in.MixtureCmd[engineIndex] = in.MixturePos[engineIndex] = 1;
345 FDMExec->
GetFCS()->SetMixturePos(engineIndex, 1);
346 FDMExec->
GetFCS()->SetMixtureCmd(engineIndex, 1);
357 ReadingEngine =
false;
358 double FuelDensity = 6.0;
369 unsigned int numTanks = 0;
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) {
378 log <<
"Unknown tank type specified.\n";
385 ReadingEngine =
true;
387 unsigned int numEngines = 0;
389 while (engine_element) {
390 if (!ModelLoader.Open(engine_element))
return false;
395 if (!thruster_element) {
397 err <<
"No thruster definition supplied with engine definition.";
400 if (!ModelLoader.Open(thruster_element)) {
402 err <<
"Cannot open the thruster element.";
406 if (engine_element->
FindElement(
"piston_engine")) {
408 Engines.push_back(make_shared<FGPiston>(FDMExec, element, numEngines, in));
409 }
else if (engine_element->
FindElement(
"turbine_engine")) {
411 Engines.push_back(make_shared<FGTurbine>(FDMExec, element, numEngines, in));
412 }
else if (engine_element->
FindElement(
"turboprop_engine")) {
414 Engines.push_back(make_shared<FGTurboProp>(FDMExec, element, numEngines, in));
415 }
else if (engine_element->
FindElement(
"rocket_engine")) {
417 Engines.push_back(make_shared<FGRocket>(FDMExec, element, numEngines, in));
418 }
else if (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")) {
423 Engines.push_back(make_shared<FGBrushLessDCMotor>(FDMExec, element, numEngines, in));
427 log <<
" Unknown engine type\n";
431 err <<
"Cannot load " << Name <<
"\n";
435 err <<
"Cannot load " << Name <<
"\n";
439 err <<
"\n" << LogFormat::RED << e.what() << LogFormat::RESET
440 <<
"\nCannot load " << Name <<
"\n";
449 if (numEngines) bind();
451 CalculateTankInertias();
458 for (
auto& engine: Engines)
459 engine->SetFuelDensity(FuelDensity);
461 PostLoad(el, FDMExec);
468SGPath FGPropulsion::FindFullPathName(
const SGPath& path)
const
470 SGPath name = FGModel::FindFullPathName(path);
471 if (!ReadingEngine && !name.isNull())
return name;
477 const array<string, 2> dir_names = {
"Engines",
"engine"};
480 const array<string, 4> dir_names = {
"Engines",
"engines",
"Engine",
"engine"};
483 for(
const string& dir_name: dir_names) {
485 if (!name.isNull())
return name;
493string FGPropulsion::GetPropulsionStrings(
const string& delimiter)
const
497 string PropulsionStrings;
498 bool firstime =
true;
501 for (
auto& engine: Engines) {
502 if (firstime) firstime =
false;
503 else PropulsionStrings += delimiter;
505 PropulsionStrings += engine->GetEngineLabels(delimiter);
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++;
511 const string& name = tank->GetName();
512 if (!name.empty()) buf <<
" (" << name <<
")";
515 PropulsionStrings += buf.str();
517 return PropulsionStrings;
522string FGPropulsion::GetPropulsionValues(
const string& delimiter)
const
524 string PropulsionValues;
525 bool firstime =
true;
528 for (
const auto& engine: Engines) {
529 if (firstime) firstime =
false;
530 else PropulsionValues += delimiter;
532 PropulsionValues += engine->GetEngineValues(delimiter);
534 for (
const auto& tank: Tanks) {
536 buf << tank->GetContents();
539 PropulsionValues += buf.str();
541 return PropulsionValues;
546string FGPropulsion::GetPropulsionTankReport()
549 stringstream outstream;
552 CalculateTankInertias();
554 for (
const auto& tank: Tanks) {
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) {
562 }
else if (tank->GetType() == FGTank::ttOXIDIZER) {
563 tankdesc +=
"Oxidizer";
565 tankdesc +=
"Unknown tank type";
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";
574 return outstream.str();
579const FGColumnVector3& FGPropulsion::GetTanksMoment(
void)
581 vXYZtank_arm.InitMatrix();
582 for (
const auto& tank: Tanks)
583 vXYZtank_arm += tank->GetXYZ() * tank->GetContents();
590double FGPropulsion::GetTanksWeight(
void)
const
594 for (
const auto& tank: Tanks) Tw += tank->GetContents();
601const FGMatrix33& FGPropulsion::CalculateTankInertias(
void)
603 if (Tanks.empty())
return tankJ;
607 for (
const auto& tank: Tanks) {
608 tankJ += FDMExec->
GetMassBalance()->GetPointmassInertia( lbtoslug * tank->GetContents(),
610 tankJ(1,1) += tank->GetIxx();
611 tankJ(2,2) += tank->GetIyy();
612 tankJ(3,3) += tank->GetIzz();
620void FGPropulsion::SetMagnetos(
int setting)
622 if (ActiveEngine < 0) {
623 for (
auto& engine: Engines) {
627 if (engine->GetType() == FGEngine::etPiston)
628 static_pointer_cast<FGPiston>(engine)->SetMagnetos(setting);
631 auto engine = dynamic_pointer_cast<FGPiston>(Engines[ActiveEngine]);
633 engine->SetMagnetos(setting);
639void FGPropulsion::SetStarter(
int setting)
641 if (ActiveEngine < 0) {
642 for (
auto& engine: Engines) {
644 engine->SetStarter(
false);
646 engine->SetStarter(
true);
650 Engines[ActiveEngine]->SetStarter(
false);
652 Engines[ActiveEngine]->SetStarter(
true);
658int FGPropulsion::GetStarter(
void)
const
660 if (ActiveEngine < 0) {
663 for (
auto& engine: Engines)
664 starter &= engine->GetStarter();
666 return starter ? 1 : 0;
668 return Engines[ActiveEngine]->GetStarter() ? 1: 0;
673void FGPropulsion::SetCutoff(
int setting)
675 bool bsetting = setting == 0 ? false :
true;
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);
683 case FGEngine::etTurboprop:
684 static_pointer_cast<FGTurboProp>(engine)->SetCutoff(bsetting);
691 auto engine = Engines[ActiveEngine];
692 switch (engine->GetType()) {
693 case FGEngine::etTurbine:
694 static_pointer_cast<FGTurbine>(engine)->SetCutoff(bsetting);
696 case FGEngine::etTurboprop:
697 static_pointer_cast<FGTurboProp>(engine)->SetCutoff(bsetting);
707int FGPropulsion::GetCutoff(
void)
const
709 if (ActiveEngine < 0) {
712 for (
auto& engine: Engines) {
713 switch (engine->GetType()) {
714 case FGEngine::etTurbine:
715 cutoff &= static_pointer_cast<FGTurbine>(engine)->GetCutoff();
717 case FGEngine::etTurboprop:
718 cutoff &= static_pointer_cast<FGTurboProp>(engine)->GetCutoff();
725 return cutoff ? 1 : 0;
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;
743void FGPropulsion::SetActiveEngine(
int engine)
745 if (engine >= (
int)Engines.size() || engine < 0)
748 ActiveEngine = engine;
753double FGPropulsion::Transfer(
int source,
int target,
double amount)
755 double shortage, overage;
760 shortage = Tanks[source]->Drain(amount);
765 overage = Tanks[target]->Fill(amount - shortage);
772void FGPropulsion::DoRefuel(
double time_slice)
774 double fillrate = RefuelRate / 60.0 * time_slice;
775 int TanksNotFull = 0;
777 for (
const auto& tank: Tanks) {
778 if (tank->GetPctFull() < 99.99) ++TanksNotFull;
783 for (
unsigned int i=0; i<Tanks.size(); i++) {
784 if (Tanks[i]->GetPctFull() < 99.99)
785 Transfer(-1, i, fillrate/TanksNotFull);
792void FGPropulsion::DumpFuel(
double time_slice)
794 int TanksDumping = 0;
796 for (
const auto& tank: Tanks) {
797 if (tank->GetContents() > tank->GetStandpipe()) ++TanksDumping;
800 if (TanksDumping == 0)
return;
802 double dump_rate_per_tank = DumpRate / 60.0 * time_slice / TanksDumping;
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);
813void FGPropulsion::SetFuelFreeze(
bool f)
816 for (
auto& engine: Engines) engine->SetFuelFreeze(f);
821void FGPropulsion::bind(
void)
823 bool HavePistonEngine =
false;
824 bool HaveTurboEngine =
false;
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;
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);
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);
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);
880void FGPropulsion::Debug(
int from)
882 if (debug_lvl <= 0)
return;
886 FGLogging log(LogLevel::DEBUG);
887 log <<
"\n Propulsion:\n";
890 if (debug_lvl & 2 ) {
891 FGLogging log(LogLevel::DEBUG);
892 if (from == 0) log <<
"Instantiated: FGPropulsion\n";
893 if (from == 1) log <<
"Destroyed: FGPropulsion\n";
895 if (debug_lvl & 4 ) {
897 if (debug_lvl & 8 ) {
899 if (debug_lvl & 16) {
901 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.
static char normint[6]
normal intensity text
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.
Main namespace for the JSBSim Flight Dynamics Model.