1 /*%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3 Module: FGPropulsion.cpp
6 Purpose: Encapsulates the set of engines, tanks, and thrusters associated
9 ------------- Copyright (C) 2000 Jon S. Berndt (jsb@hal-pc.org) -------------
11 This program is free software; you can redistribute it and/or modify it under
12 the terms of the GNU General Public License as published by the Free Software
13 Foundation; either version 2 of the License, or (at your option) any later
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 General Public License for more
21 You should have received a copy of the GNU General Public License along with
22 this program; if not, write to the Free Software Foundation, Inc., 59 Temple
23 Place - Suite 330, Boston, MA 02111-1307, USA.
25 Further information about the GNU General Public License can also be found on
26 the world wide web at http://www.gnu.org.
28 FUNCTIONAL DESCRIPTION
29 --------------------------------------------------------------------------------
30 The Propulsion class is the container for the entire propulsion system, which is
31 comprised of engines, tanks, and "thrusters" (the device that transforms the
32 engine power into a force that acts on the aircraft, such as a nozzle or
33 propeller). Once the Propulsion class gets the config file, it reads in
34 information which is specific to a type of engine. Then:
36 1) The appropriate engine type instance is created
37 2) A thruster object is instantiated, and is linked to the engine
38 3) At least one tank object is created, and is linked to an engine.
40 At Run time each engines Calculate() method is called to return the excess power
41 generated during that iteration. The drag from the previous iteration is sub-
42 tracted to give the excess power available for thrust this pass. That quantity
43 is passed to the thrusters associated with a particular engine - perhaps with a
44 scaling mechanism (gearing?) to allow the engine to give its associated thrust-
45 ers specific distributed portions of the excess power.
48 --------------------------------------------------------------------------------
51 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
53 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/
55 #include "FGPropulsion.h"
57 static const char *IdSrc = "$Id$";
58 static const char *IdHdr = ID_PROPULSION;
60 /*%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
62 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/
64 FGPropulsion::FGPropulsion(FGFDMExec* exec) : FGModel(exec)
66 Name = "FGPropulsion";
67 numSelectedFuelTanks = numSelectedOxiTanks = 0;
68 numTanks = numEngines = numThrusters = 0;
69 numOxiTanks = numFuelTanks = 0;
74 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
76 FGPropulsion::~FGPropulsion()
78 for (unsigned int i=0; i<Engines.size(); i++) delete Engines[i];
83 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
85 bool FGPropulsion::Run(void)
87 double PowerAvailable;
91 vMoments.InitMatrix();
93 if (!FGModel::Run()) {
94 for (unsigned int i=0; i<numEngines; i++) {
95 Thrusters[i]->SetdeltaT(dt*rate);
96 PowerAvailable = Engines[i]->Calculate(Thrusters[i]->GetPowerRequired());
97 Thrusters[i]->Calculate(PowerAvailable);
98 vForces += Thrusters[i]->GetBodyForces(); // sum body frame forces
99 vMoments += Thrusters[i]->GetMoments(); // sum body frame moments
107 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
109 bool FGPropulsion::GetSteadyState(void)
111 double PowerAvailable;
112 double currentThrust = 0, lastThrust=-1;
114 int steady_count,j=0;
117 vForces.InitMatrix();
118 vMoments.InitMatrix();
120 if (!FGModel::Run()) {
121 for (unsigned int i=0; i<numEngines; i++) {
122 Engines[i]->SetTrimMode(true);
123 Thrusters[i]->SetdeltaT(dt*rate);
126 while (!steady && j < 6000) {
127 PowerAvailable = Engines[i]->Calculate(Thrusters[i]->GetPowerRequired());
128 lastThrust = currentThrust;
129 currentThrust = Thrusters[i]->Calculate(PowerAvailable);
130 if(fabs(lastThrust-currentThrust) < 0.0001) {
132 if(steady_count > 120) { steady=true; }
138 vForces += Thrusters[i]->GetBodyForces(); // sum body frame forces
139 vMoments += Thrusters[i]->GetMoments(); // sum body frame moments
140 Engines[i]->SetTrimMode(false);
149 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
151 bool FGPropulsion::ICEngineStart(void)
153 double PowerAvailable;
157 vForces.InitMatrix();
158 vMoments.InitMatrix();
160 for (unsigned int i=0; i<numEngines; i++) {
161 Engines[i]->SetTrimMode(true);
162 Thrusters[i]->SetdeltaT(dt*rate);
164 while (!Engines[i]->GetRunning() && j < 2000) {
165 PowerAvailable = Engines[i]->Calculate(Thrusters[i]->GetPowerRequired());
166 Thrusters[i]->Calculate(PowerAvailable);
169 vForces += Thrusters[i]->GetBodyForces(); // sum body frame forces
170 vMoments += Thrusters[i]->GetMoments(); // sum body frame moments
171 Engines[i]->SetTrimMode(false);
176 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
178 bool FGPropulsion::Load(FGConfigFile* AC_cfg)
180 string token, fullpath;
181 string engineFileName, engType;
182 string thrusterFileName, thrType;
184 string enginePath = FDMExec->GetEnginePath();
185 double xLoc, yLoc, zLoc, Pitch, Yaw;
186 double P_Factor = 0, Sense = 0.0;
188 bool ThrottleAdded = false;
191 fullpath = enginePath + "/";
193 fullpath = enginePath + ";";
196 AC_cfg->GetNextConfigLine();
198 while ((token = AC_cfg->GetValue()) != string("/PROPULSION")) {
200 if (token == "AC_ENGINE") { // ============ READING ENGINES
202 engineFileName = AC_cfg->GetValue("FILE");
204 if (debug_lvl > 0) cout << "\n Reading engine from file: " << fullpath
205 + engineFileName + ".xml"<< endl;
206 FGConfigFile Eng_cfg(fullpath + engineFileName + ".xml");
208 if (Eng_cfg.IsOpen()) {
209 Eng_cfg.GetNextConfigLine();
210 engType = Eng_cfg.GetValue();
213 ThrottleAdded = true;
215 if (engType == "FG_ROCKET") {
216 Engines.push_back(new FGRocket(FDMExec, &Eng_cfg));
217 } else if (engType == "FG_PISTON") {
218 Engines.push_back(new FGPiston(FDMExec, &Eng_cfg));
219 } else if (engType == "FG_TURBOJET") {
220 Engines.push_back(new FGTurboJet(FDMExec, &Eng_cfg));
221 } else if (engType == "FG_TURBOSHAFT") {
222 Engines.push_back(new FGTurboShaft(FDMExec, &Eng_cfg));
223 } else if (engType == "FG_TURBOPROP") {
224 Engines.push_back(new FGTurboProp(FDMExec, &Eng_cfg));
226 cerr << fgred << " Unrecognized engine type: " << underon << engType
227 << underoff << " found in config file." << fgdef << endl;
231 AC_cfg->GetNextConfigLine();
232 while ((token = AC_cfg->GetValue()) != string("/AC_ENGINE")) {
234 if (token == "XLOC") { *AC_cfg >> xLoc; }
235 else if (token == "YLOC") { *AC_cfg >> yLoc; }
236 else if (token == "ZLOC") { *AC_cfg >> zLoc; }
237 else if (token == "PITCH") { *AC_cfg >> Pitch;}
238 else if (token == "YAW") { *AC_cfg >> Yaw;}
239 else if (token == "FEED") {
241 Engines[numEngines]->AddFeedTank(Feed);
242 if (debug_lvl > 0) cout << " Feed tank: " << Feed << endl;
243 } else cerr << "Unknown identifier: " << token << " in engine file: "
244 << engineFileName << endl;
248 cout << " X = " << xLoc << endl;
249 cout << " Y = " << yLoc << endl;
250 cout << " Z = " << zLoc << endl;
251 cout << " Pitch = " << Pitch << endl;
252 cout << " Yaw = " << Yaw << endl;
255 Engines[numEngines]->SetPlacement(xLoc, yLoc, zLoc, Pitch, Yaw);
256 Engines[numEngines]->SetEngineNumber(numEngines);
261 cerr << fgred << "\n Could not read engine config file: " << underon <<
262 fullpath + engineFileName + ".xml" << underoff << fgdef << endl;
266 } else if (token == "AC_TANK") { // ============== READING TANKS
268 if (debug_lvl > 0) cout << "\n Reading tank definition" << endl;
269 Tanks.push_back(new FGTank(AC_cfg));
270 switch(Tanks[numTanks]->GetType()) {
272 numSelectedFuelTanks++;
275 case FGTank::ttOXIDIZER:
276 numSelectedOxiTanks++;
283 } else if (token == "AC_THRUSTER") { // ========== READING THRUSTERS
285 thrusterFileName = AC_cfg->GetValue("FILE");
287 if (debug_lvl > 0) cout << "\n Reading thruster from file: " <<
288 fullpath + thrusterFileName + ".xml" << endl;
289 FGConfigFile Thruster_cfg(fullpath + thrusterFileName + ".xml");
291 if (Thruster_cfg.IsOpen()) {
292 Thruster_cfg.GetNextConfigLine();
293 thrType = Thruster_cfg.GetValue();
295 if (thrType == "FG_PROPELLER") {
296 Thrusters.push_back(new FGPropeller(FDMExec, &Thruster_cfg));
297 } else if (thrType == "FG_NOZZLE") {
298 Thrusters.push_back(new FGNozzle(FDMExec, &Thruster_cfg));
301 AC_cfg->GetNextConfigLine();
302 while ((token = AC_cfg->GetValue()) != string("/AC_THRUSTER")) {
304 if (token == "XLOC") *AC_cfg >> xLoc;
305 else if (token == "YLOC") *AC_cfg >> yLoc;
306 else if (token == "ZLOC") *AC_cfg >> zLoc;
307 else if (token == "PITCH") *AC_cfg >> Pitch;
308 else if (token == "YAW") *AC_cfg >> Yaw;
309 else if (token == "P_FACTOR") *AC_cfg >> P_Factor;
310 else if (token == "SENSE") *AC_cfg >> Sense;
311 else cerr << "Unknown identifier: " << token << " in engine file: "
312 << engineFileName << endl;
315 Thrusters[numThrusters]->SetLocation(xLoc, yLoc, zLoc);
316 Thrusters[numThrusters]->SetAnglesToBody(0, Pitch, Yaw);
317 if (thrType == "FG_PROPELLER" && P_Factor > 0.001) {
318 ((FGPropeller*)Thrusters[numThrusters])->SetPFactor(P_Factor);
319 if (debug_lvl > 0) cout << " P-Factor: " << P_Factor << endl;
320 ((FGPropeller*)Thrusters[numThrusters])->SetSense(fabs(Sense)/Sense);
321 if (debug_lvl > 0) cout << " Sense: " << Sense << endl;
323 Thrusters[numThrusters]->SetdeltaT(dt*rate);
324 Thrusters[numThrusters]->SetThrusterNumber(numThrusters);
328 cerr << "Could not read thruster config file: " << fullpath
329 + thrusterFileName + ".xml" << endl;
334 AC_cfg->GetNextConfigLine();
337 if (!ThrottleAdded) FCS->AddThrottle(); // need to have at least one throttle
342 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
344 string FGPropulsion::GetPropulsionStrings(void)
346 string PropulsionStrings = "";
347 bool firstime = true;
350 for (unsigned int i=0;i<Engines.size();i++) {
351 if (firstime) firstime = false;
352 else PropulsionStrings += ", ";
354 sprintf(buffer, "%d", i);
356 switch(Engines[i]->GetType()) {
357 case FGEngine::etPiston:
358 PropulsionStrings += (Engines[i]->GetName() + "_PwrAvail[" + buffer + "]");
360 case FGEngine::etRocket:
361 PropulsionStrings += (Engines[i]->GetName() + "_ChamberPress[" + buffer + "]");
363 case FGEngine::etTurboJet:
364 case FGEngine::etTurboProp:
365 case FGEngine::etTurboShaft:
368 PropulsionStrings += "INVALID ENGINE TYPE";
372 PropulsionStrings += ", ";
374 FGPropeller* Propeller = (FGPropeller*)Thrusters[i];
375 switch(Thrusters[i]->GetType()) {
376 case FGThruster::ttNozzle:
377 PropulsionStrings += (Thrusters[i]->GetName() + "_Thrust[" + buffer + "]");
379 case FGThruster::ttRotor:
381 case FGThruster::ttPropeller:
382 PropulsionStrings += (Thrusters[i]->GetName() + "_Torque[" + buffer + "], ");
383 PropulsionStrings += (Thrusters[i]->GetName() + "_PFactor_Roll[" + buffer + "], ");
384 PropulsionStrings += (Thrusters[i]->GetName() + "_PFactor_Pitch[" + buffer + "], ");
385 PropulsionStrings += (Thrusters[i]->GetName() + "_PFactor_Yaw[" + buffer + "], ");
386 PropulsionStrings += (Thrusters[i]->GetName() + "_Thrust[" + buffer + "], ");
387 if (Propeller->IsVPitch())
388 PropulsionStrings += (Thrusters[i]->GetName() + "_Pitch[" + buffer + "], ");
389 PropulsionStrings += (Thrusters[i]->GetName() + "_RPM[" + buffer + "]");
392 PropulsionStrings += "INVALID THRUSTER TYPE";
397 return PropulsionStrings;
400 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
402 string FGPropulsion::GetPropulsionValues(void)
405 string PropulsionValues = "";
406 bool firstime = true;
408 for (unsigned int i=0;i<Engines.size();i++) {
409 if (firstime) firstime = false;
410 else PropulsionValues += ", ";
412 switch(Engines[i]->GetType()) {
413 case FGEngine::etPiston:
414 PropulsionValues += (string(gcvt(((FGPiston*)Engines[i])->GetPowerAvailable(), 10, buff)));
416 case FGEngine::etRocket:
417 PropulsionValues += (string(gcvt(((FGRocket*)Engines[i])->GetChamberPressure(), 10, buff)));
419 case FGEngine::etTurboJet:
420 case FGEngine::etTurboProp:
421 case FGEngine::etTurboShaft:
425 PropulsionValues += ", ";
427 switch(Thrusters[i]->GetType()) {
428 case FGThruster::ttNozzle:
429 PropulsionValues += (string(gcvt(((FGNozzle*)Thrusters[i])->GetThrust(), 10, buff)));
431 case FGThruster::ttRotor:
433 case FGThruster::ttPropeller:
434 FGPropeller* Propeller = (FGPropeller*)Thrusters[i];
435 FGColumnVector3 vPFactor = Propeller->GetPFactor();
436 PropulsionValues += string(gcvt(Propeller->GetTorque(), 10, buff)) + ", ";
437 PropulsionValues += string(gcvt(vPFactor(eRoll), 10, buff)) + ", ";
438 PropulsionValues += string(gcvt(vPFactor(ePitch), 10, buff)) + ", ";
439 PropulsionValues += string(gcvt(vPFactor(eYaw), 10, buff)) + ", ";
440 PropulsionValues += string(gcvt(Propeller->GetThrust(), 10, buff)) + ", ";
441 if (Propeller->IsVPitch())
442 PropulsionValues += string(gcvt(Propeller->GetPitch(), 10, buff)) + ", ";
443 PropulsionValues += string(gcvt(Propeller->GetRPM(), 10, buff));
448 return PropulsionValues;
451 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
453 FGColumnVector3& FGPropulsion::GetTanksCG(void)
455 iTank = Tanks.begin();
456 vXYZtank.InitMatrix();
457 while (iTank < Tanks.end()) {
458 vXYZtank(eX) += (*iTank)->GetX()*(*iTank)->GetContents();
459 vXYZtank(eY) += (*iTank)->GetY()*(*iTank)->GetContents();
460 vXYZtank(eZ) += (*iTank)->GetZ()*(*iTank)->GetContents();
466 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
468 double FGPropulsion::GetTanksWeight(void)
472 iTank = Tanks.begin();
473 while (iTank < Tanks.end()) {
474 Tw += (*iTank)->GetContents();
480 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
482 double FGPropulsion::GetTanksIxx(const FGColumnVector3& vXYZcg)
485 iTank = Tanks.begin();
486 while (iTank < Tanks.end()) {
487 I += ((*iTank)->GetX() - vXYZcg(eX))*((*iTank)->GetX() - vXYZcg(eX)) * (*iTank)->GetContents()/(144.0*Inertial->gravity());
493 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
495 double FGPropulsion::GetTanksIyy(const FGColumnVector3& vXYZcg)
498 iTank = Tanks.begin();
499 while (iTank < Tanks.end()) {
500 I += ((*iTank)->GetY() - vXYZcg(eY))*((*iTank)->GetY() - vXYZcg(eY)) * (*iTank)->GetContents()/(144.0*Inertial->gravity());
506 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
508 double FGPropulsion::GetTanksIzz(const FGColumnVector3& vXYZcg)
511 iTank = Tanks.begin();
512 while (iTank < Tanks.end()) {
513 I += ((*iTank)->GetZ() - vXYZcg(eZ))*((*iTank)->GetZ() - vXYZcg(eZ)) * (*iTank)->GetContents()/(144.0*Inertial->gravity());
519 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
521 double FGPropulsion::GetTanksIxz(const FGColumnVector3& vXYZcg)
524 iTank = Tanks.begin();
525 while (iTank < Tanks.end()) {
526 I += ((*iTank)->GetX() - vXYZcg(eX))*((*iTank)->GetZ() - vXYZcg(eZ)) * (*iTank)->GetContents()/(144.0*Inertial->gravity());
532 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
534 double FGPropulsion::GetTanksIxy(const FGColumnVector3& vXYZcg)
537 iTank = Tanks.begin();
538 while (iTank != Tanks.end()) {
539 I += ((*iTank)->GetX() - vXYZcg(eX))*((*iTank)->GetY() - vXYZcg(eY)) * (*iTank)->GetContents()/(144.0*Inertial->gravity());
545 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
546 // The bitmasked value choices are as follows:
547 // unset: In this case (the default) JSBSim would only print
548 // out the normally expected messages, essentially echoing
549 // the config files as they are read. If the environment
550 // variable is not set, debug_lvl is set to 1 internally
551 // 0: This requests JSBSim not to output any messages
553 // 1: This value explicity requests the normal JSBSim
555 // 2: This value asks for a message to be printed out when
556 // a class is instantiated
557 // 4: When this value is set, a message is displayed when a
558 // FGModel object executes its Run() method
559 // 8: When this value is set, various runtime state variables
560 // are printed out periodically
561 // 16: When set various parameters are sanity checked and
562 // a message is printed out when they go out of bounds
564 void FGPropulsion::Debug(int from)
566 if (debug_lvl <= 0) return;
568 if (debug_lvl & 1) { // Standard console startup message output
569 if (from == 0) { // Constructor
573 if (debug_lvl & 2 ) { // Instantiation/Destruction notification
574 if (from == 0) cout << "Instantiated: FGPropulsion" << endl;
575 if (from == 1) cout << "Destroyed: FGPropulsion" << endl;
577 if (debug_lvl & 4 ) { // Run() method entry print for FGModel-derived objects
579 if (debug_lvl & 8 ) { // Runtime state variables
581 if (debug_lvl & 16) { // Sanity checking
583 if (debug_lvl & 64) {
584 if (from == 0) { // Constructor
585 cout << IdSrc << endl;
586 cout << IdHdr << endl;