/** * Populates the NPS fdm struct after a simulation step. */ static void fetch_state(void) { FGPropertyManager* node = FDMExec->GetPropertyManager()->GetNode("simulation/sim-time-sec"); fdm.time = node->getDoubleValue(); #if DEBUG_NPS_JSBSIM printf("%f,",fdm.time); #endif FGPropagate* propagate = FDMExec->GetPropagate(); FGAccelerations* accelerations = FDMExec->GetAccelerations(); fdm.on_ground = FDMExec->GetGroundReactions()->GetWOW(); /* * position */ jsbsimloc_to_loc(&fdm.ecef_pos,&propagate->GetLocation()); fdm.hmsl = propagate->GetAltitudeASLmeters(); /* * linear speed and accelerations */ /* in body frame */ const FGColumnVector3& fg_body_ecef_vel = propagate->GetUVW(); jsbsimvec_to_vec(&fdm.body_ecef_vel, &fg_body_ecef_vel); const FGColumnVector3& fg_body_ecef_accel = accelerations->GetUVWdot(); jsbsimvec_to_vec(&fdm.body_ecef_accel,&fg_body_ecef_accel); #if DEBUG_NPS_JSBSIM printf("%f,%f,%f,%f,%f,%f,",(&fg_body_ecef_accel)->Entry(1),(&fg_body_ecef_accel)->Entry(2),(&fg_body_ecef_accel)->Entry(3),fdm.body_ecef_accel.x,fdm.body_ecef_accel.y,fdm.body_ecef_accel.z); #endif /* in LTP frame */ const FGMatrix33& body_to_ltp = propagate->GetTb2l(); const FGColumnVector3& fg_ltp_ecef_vel = body_to_ltp * fg_body_ecef_vel; jsbsimvec_to_vec((DoubleVect3*)&fdm.ltp_ecef_vel, &fg_ltp_ecef_vel); const FGColumnVector3& fg_ltp_ecef_accel = body_to_ltp * fg_body_ecef_accel; jsbsimvec_to_vec((DoubleVect3*)&fdm.ltp_ecef_accel, &fg_ltp_ecef_accel); #if DEBUG_NPS_JSBSIM printf("%f,%f,%f,%f,%f,%f,",(&fg_ltp_ecef_accel)->Entry(1),(&fg_ltp_ecef_accel)->Entry(2),(&fg_ltp_ecef_accel)->Entry(3),fdm.ltp_ecef_accel.x,fdm.ltp_ecef_accel.y,fdm.ltp_ecef_accel.z); #endif /* in ECEF frame */ const FGMatrix33& body_to_ecef = propagate->GetTb2ec(); const FGColumnVector3& fg_ecef_ecef_vel = body_to_ecef * fg_body_ecef_vel; jsbsimvec_to_vec((DoubleVect3*)&fdm.ecef_ecef_vel, &fg_ecef_ecef_vel); const FGColumnVector3& fg_ecef_ecef_accel = body_to_ecef * fg_body_ecef_accel; jsbsimvec_to_vec((DoubleVect3*)&fdm.ecef_ecef_accel, &fg_ecef_ecef_accel); #if DEBUG_NPS_JSBSIM printf("%f,%f,%f,%f,%f,%f,",(&fg_ecef_ecef_accel)->Entry(1),(&fg_ecef_ecef_accel)->Entry(2),(&fg_ecef_ecef_accel)->Entry(3),fdm.ecef_ecef_accel.x,fdm.ecef_ecef_accel.y,fdm.ecef_ecef_accel.z); #endif /* in LTP pprz */ ned_of_ecef_point_d(&fdm.ltpprz_pos, <pdef, &fdm.ecef_pos); ned_of_ecef_vect_d(&fdm.ltpprz_ecef_vel, <pdef, &fdm.ecef_ecef_vel); ned_of_ecef_vect_d(&fdm.ltpprz_ecef_accel, <pdef, &fdm.ecef_ecef_accel); #if DEBUG_NPS_JSBSIM printf("%f,%f,%f,",fdm.ltpprz_ecef_accel.z,fdm.ltpprz_ecef_accel.y,fdm.ltpprz_ecef_accel.z); #endif /* llh */ llh_from_jsbsim(&fdm.lla_pos, propagate); //for debug lla_from_jsbsim_geodetic(&fdm.lla_pos_geod, propagate); lla_from_jsbsim_geocentric(&fdm.lla_pos_geoc, propagate); lla_of_ecef_d(&fdm.lla_pos_pprz, &fdm.ecef_pos); fdm.agl = MetersOfFeet(propagate->GetDistanceAGL()); #if DEBUG_NPS_JSBSIM printf("%f\n",fdm.agl); #endif /* * attitude */ const FGQuaternion jsb_quat = propagate->GetQuaternion(); jsbsimquat_to_quat(&fdm.ltp_to_body_quat, &jsb_quat); /* convert to eulers */ DOUBLE_EULERS_OF_QUAT(fdm.ltp_to_body_eulers, fdm.ltp_to_body_quat); /* the "false" pprz lpt */ /* FIXME: use jsbsim ltp for now */ EULERS_COPY(fdm.ltpprz_to_body_eulers, fdm.ltp_to_body_eulers); QUAT_COPY(fdm.ltpprz_to_body_quat, fdm.ltp_to_body_quat); /* * rotational speed and accelerations */ jsbsimvec_to_rate(&fdm.body_ecef_rotvel, &propagate->GetPQR()); jsbsimvec_to_rate(&fdm.body_ecef_rotaccel, &accelerations->GetPQRdot()); /* * wind */ const FGColumnVector3& fg_wind_ned = FDMExec->GetWinds()->GetTotalWindNED(); jsbsimvec_to_vec(&fdm.wind, &fg_wind_ned); }
FGPropeller::FGPropeller(FGFDMExec* exec, Element* prop_element, int num) : FGThruster(exec, prop_element, num) { string token; Element *table_element, *local_element; string name=""; FGPropertyManager* PropertyManager = exec->GetPropertyManager(); MaxPitch = MinPitch = P_Factor = Pitch = Advance = MinRPM = MaxRPM = 0.0; Sense = 1; // default clockwise rotation ReversePitch = 0.0; Reversed = false; Feathered = false; Reverse_coef = 0.0; GearRatio = 1.0; CtFactor = CpFactor = 1.0; if (prop_element->FindElement("ixx")) Ixx = prop_element->FindElementValueAsNumberConvertTo("ixx", "SLUG*FT2"); if (prop_element->FindElement("diameter")) Diameter = prop_element->FindElementValueAsNumberConvertTo("diameter", "FT"); if (prop_element->FindElement("numblades")) numBlades = (int)prop_element->FindElementValueAsNumber("numblades"); if (prop_element->FindElement("gearratio")) GearRatio = prop_element->FindElementValueAsNumber("gearratio"); if (prop_element->FindElement("minpitch")) MinPitch = prop_element->FindElementValueAsNumber("minpitch"); if (prop_element->FindElement("maxpitch")) MaxPitch = prop_element->FindElementValueAsNumber("maxpitch"); if (prop_element->FindElement("minrpm")) MinRPM = prop_element->FindElementValueAsNumber("minrpm"); if (prop_element->FindElement("maxrpm")) MaxRPM = prop_element->FindElementValueAsNumber("maxrpm"); if (prop_element->FindElement("reversepitch")) ReversePitch = prop_element->FindElementValueAsNumber("reversepitch"); for (int i=0; i<2; i++) { table_element = prop_element->FindNextElement("table"); name = table_element->GetAttributeValue("name"); if (name == "C_THRUST") { cThrust = new FGTable(PropertyManager, table_element); } else if (name == "C_POWER") { cPower = new FGTable(PropertyManager, table_element); } else { cerr << "Unknown table type: " << name << " in propeller definition." << endl; } } local_element = prop_element->GetParent()->FindElement("sense"); if (local_element) { double Sense = local_element->GetDataAsNumber(); SetSense(fabs(Sense)/Sense); } local_element = prop_element->GetParent()->FindElement("p_factor"); if (local_element) { P_Factor = local_element->GetDataAsNumber(); } if (P_Factor < 0) { cerr << "P-Factor value in config file must be greater than zero" << endl; } if (prop_element->FindElement("ct_factor")) SetCtFactor( prop_element->FindElementValueAsNumber("ct_factor") ); if (prop_element->FindElement("cp_factor")) SetCpFactor( prop_element->FindElementValueAsNumber("cp_factor") ); Type = ttPropeller; RPM = 0; vTorque.InitMatrix(); D4 = Diameter*Diameter*Diameter*Diameter; D5 = D4*Diameter; string property_name, base_property_name; base_property_name = CreateIndexedPropertyName("propulsion/engine", EngineNum); property_name = base_property_name + "/advance-ratio"; PropertyManager->Tie( property_name.c_str(), &J ); property_name = base_property_name + "/blade-angle"; PropertyManager->Tie( property_name.c_str(), &Pitch ); property_name = base_property_name + "/thrust-coefficient"; PropertyManager->Tie( property_name.c_str(), this, &FGPropeller::GetThrustCoefficient ); property_name = base_property_name + "/propeller-rpm"; PropertyManager->Tie( property_name.c_str(), this, &FGPropeller::GetRPM ); Debug(0); }
FGPropeller::FGPropeller(FGFDMExec* exec, Element* prop_element, int num) : FGThruster(exec, prop_element, num) { string token; Element *table_element, *local_element; string name=""; FGPropertyManager* PropertyManager = exec->GetPropertyManager(); MaxPitch = MinPitch = P_Factor = Pitch = Advance = MinRPM = MaxRPM = 0.0; Sense = 1; // default clockwise rotation ReversePitch = 0.0; Reversed = false; Feathered = false; Reverse_coef = 0.0; GearRatio = 1.0; CtFactor = CpFactor = 1.0; ConstantSpeed = 0; cThrust = cPower = CtMach = CpMach = 0; Vinduced = 0.0; if (prop_element->FindElement("ixx")) Ixx = prop_element->FindElementValueAsNumberConvertTo("ixx", "SLUG*FT2"); if (prop_element->FindElement("diameter")) Diameter = prop_element->FindElementValueAsNumberConvertTo("diameter", "FT"); if (prop_element->FindElement("numblades")) numBlades = (int)prop_element->FindElementValueAsNumber("numblades"); if (prop_element->FindElement("gearratio")) GearRatio = prop_element->FindElementValueAsNumber("gearratio"); if (prop_element->FindElement("minpitch")) MinPitch = prop_element->FindElementValueAsNumber("minpitch"); if (prop_element->FindElement("maxpitch")) MaxPitch = prop_element->FindElementValueAsNumber("maxpitch"); if (prop_element->FindElement("minrpm")) MinRPM = prop_element->FindElementValueAsNumber("minrpm"); if (prop_element->FindElement("maxrpm")) { MaxRPM = prop_element->FindElementValueAsNumber("maxrpm"); ConstantSpeed = 1; } if (prop_element->FindElement("constspeed")) ConstantSpeed = (int)prop_element->FindElementValueAsNumber("constspeed"); if (prop_element->FindElement("reversepitch")) ReversePitch = prop_element->FindElementValueAsNumber("reversepitch"); while((table_element = prop_element->FindNextElement("table")) != 0) { name = table_element->GetAttributeValue("name"); try { if (name == "C_THRUST") { cThrust = new FGTable(PropertyManager, table_element); } else if (name == "C_POWER") { cPower = new FGTable(PropertyManager, table_element); } else if (name == "CT_MACH") { CtMach = new FGTable(PropertyManager, table_element); } else if (name == "CP_MACH") { CpMach = new FGTable(PropertyManager, table_element); } else { cerr << "Unknown table type: " << name << " in propeller definition." << endl; } } catch (std::string str) { throw("Error loading propeller table:" + name + ". " + str); } } if( (cPower == 0) || (cThrust == 0)){ cerr << "Propeller configuration must contain C_THRUST and C_POWER tables!" << endl; } local_element = prop_element->GetParent()->FindElement("sense"); if (local_element) { double Sense = local_element->GetDataAsNumber(); SetSense(Sense >= 0.0 ? 1.0 : -1.0); } local_element = prop_element->GetParent()->FindElement("p_factor"); if (local_element) { P_Factor = local_element->GetDataAsNumber(); } if (P_Factor < 0) { cerr << "P-Factor value in propeller configuration file must be greater than zero" << endl; } if (prop_element->FindElement("ct_factor")) SetCtFactor( prop_element->FindElementValueAsNumber("ct_factor") ); if (prop_element->FindElement("cp_factor")) SetCpFactor( prop_element->FindElementValueAsNumber("cp_factor") ); Type = ttPropeller; RPM = 0; vTorque.InitMatrix(); D4 = Diameter*Diameter*Diameter*Diameter; D5 = D4*Diameter; Pitch = MinPitch; string property_name, base_property_name; base_property_name = CreateIndexedPropertyName("propulsion/engine", EngineNum); property_name = base_property_name + "/engine-rpm"; PropertyManager->Tie( property_name.c_str(), this, &FGPropeller::GetEngineRPM ); property_name = base_property_name + "/advance-ratio"; PropertyManager->Tie( property_name.c_str(), &J ); property_name = base_property_name + "/blade-angle"; PropertyManager->Tie( property_name.c_str(), &Pitch ); property_name = base_property_name + "/thrust-coefficient"; PropertyManager->Tie( property_name.c_str(), this, &FGPropeller::GetThrustCoefficient ); property_name = base_property_name + "/propeller-rpm"; PropertyManager->Tie( property_name.c_str(), this, &FGPropeller::GetRPM ); property_name = base_property_name + "/helical-tip-Mach"; PropertyManager->Tie( property_name.c_str(), this, &FGPropeller::GetHelicalTipMach ); property_name = base_property_name + "/constant-speed-mode"; PropertyManager->Tie( property_name.c_str(), this, &FGPropeller::GetConstantSpeed, &FGPropeller::SetConstantSpeed ); property_name = base_property_name + "/prop-induced-velocity_fps"; PropertyManager->Tie( property_name.c_str(), this, &FGPropeller::GetInducedVelocity, &FGPropeller::SetInducedVelocity ); Debug(0); }
FGThruster::FGThruster(FGFDMExec *FDMExec, Element *el, int num ): FGForce(FDMExec) { Element* thruster_element = el->GetParent(); Element* element; FGColumnVector3 location, orientation, pointing; Type = ttDirect; SetTransformType(FGForce::tCustom); Name = el->GetAttributeValue("name"); GearRatio = 1.0; EngineNum = num; FGPropertyManager* PropertyManager = FDMExec->GetPropertyManager(); // Determine the initial location and orientation of this thruster and load the // thruster with this information. element = thruster_element->FindElement("location"); if (element) location = element->FindElementTripletConvertTo("IN"); else cerr << fgred << " No thruster location found." << reset << endl; SetLocation(location); string property_name, base_property_name; base_property_name = CreateIndexedPropertyName("propulsion/engine", EngineNum); element = thruster_element->FindElement("pointing"); if (element) { // This defines a fixed nozzle that has no public interface property to gimbal or reverse it. pointing = element->FindElementTripletConvertTo("RAD"); // The specification of RAD here is superfluous, // and simply precludes a conversion. mT.InitMatrix(); mT(1,1) = pointing(1); mT(2,1) = pointing(2); mT(3,1) = pointing(3); } else { element = thruster_element->FindElement("orient"); if (element) orientation = element->FindElementTripletConvertTo("RAD"); SetAnglesToBody(orientation); property_name = base_property_name + "/pitch-angle-rad"; PropertyManager->Tie( property_name.c_str(), (FGForce *)this, &FGForce::GetPitch, &FGForce::SetPitch); property_name = base_property_name + "/yaw-angle-rad"; PropertyManager->Tie( property_name.c_str(), (FGForce *)this, &FGForce::GetYaw, &FGForce::SetYaw); if (el->GetName() == "direct") // this is a direct thruster. At this time // only a direct thruster can be reversed. { property_name = base_property_name + "/reverser-angle-rad"; PropertyManager->Tie( property_name.c_str(), (FGThruster *)this, &FGThruster::GetReverserAngle, &FGThruster::SetReverserAngle); } } ResetToIC(); Debug(0); }
FGPiston::FGPiston(FGFDMExec* exec, Element* el, int engine_number, struct Inputs& input) : FGEngine(engine_number, input), R_air(287.3), // Gas constant for air J/Kg/K calorific_value_fuel(47.3e6), // J/Kg Cp_air(1005), // Specific heat (constant pressure) J/Kg/K Cp_fuel(1700), standard_pressure(101320.73) { Load(exec, el); Element *table_element; FGPropertyManager* PropertyManager = exec->GetPropertyManager(); // Defaults and initializations Type = etPiston; // These items are read from the configuration file // Defaults are from a Lycoming O-360, more or less Cycles = 4; IdleRPM = 600; MaxRPM = 2800; Displacement = 360; SparkFailDrop = 1.0; MaxHP = 200; MinManifoldPressure_inHg = 6.5; MaxManifoldPressure_inHg = 28.5; ManifoldPressureLag=1.0; ISFC = -1; volumetric_efficiency = 0.85; Bore = 5.125; Stroke = 4.375; Cylinders = 4; CylinderHeadMass = 2; //kg CompressionRatio = 8.5; Z_airbox = -999; Ram_Air_Factor = 1; PeakMeanPistonSpeed_fps = 100; FMEPDynamic= 18400; FMEPStatic = 46500; Cooling_Factor = 0.5144444; StaticFriction_HP = 1.5; StarterGain = 1.; StarterTorque = -1.; StarterRPM = -1.; // These are internal program variables Lookup_Combustion_Efficiency = 0; Mixture_Efficiency_Correlation = 0; crank_counter = 0; Magnetos = 0; minMAP = 21950; maxMAP = 96250; ResetToIC(); // Supercharging BoostSpeeds = 0; // Default to no supercharging BoostSpeed = 0; Boosted = false; BoostOverride = 0; BoostManual = 0; bBoostOverride = false; bTakeoffBoost = false; TakeoffBoost = 0.0; // Default to no extra takeoff-boost BoostLossFactor = 0.0; // Default to free boost int i; for (i=0; i<FG_MAX_BOOST_SPEEDS; i++) { RatedBoost[i] = 0.0; RatedPower[i] = 0.0; RatedAltitude[i] = 0.0; BoostMul[i] = 1.0; RatedMAP[i] = 100000; RatedRPM[i] = 2500; TakeoffMAP[i] = 100000; } for (i=0; i<FG_MAX_BOOST_SPEEDS-1; i++) { BoostSwitchAltitude[i] = 0.0; BoostSwitchPressure[i] = 0.0; } // Read inputs from engine data file where present. if (el->FindElement("minmp")) MinManifoldPressure_inHg = el->FindElementValueAsNumberConvertTo("minmp","INHG"); if (el->FindElement("maxmp")) MaxManifoldPressure_inHg = el->FindElementValueAsNumberConvertTo("maxmp","INHG"); if (el->FindElement("man-press-lag")) ManifoldPressureLag = el->FindElementValueAsNumber("man-press-lag"); if (el->FindElement("displacement")) Displacement = el->FindElementValueAsNumberConvertTo("displacement","IN3"); if (el->FindElement("maxhp")) MaxHP = el->FindElementValueAsNumberConvertTo("maxhp","HP"); if (el->FindElement("static-friction")) StaticFriction_HP = el->FindElementValueAsNumberConvertTo("static-friction","HP"); if (el->FindElement("sparkfaildrop")) SparkFailDrop = Constrain(0, 1 - el->FindElementValueAsNumber("sparkfaildrop"), 1); if (el->FindElement("cycles")) Cycles = el->FindElementValueAsNumber("cycles"); if (el->FindElement("idlerpm")) IdleRPM = el->FindElementValueAsNumber("idlerpm"); if (el->FindElement("maxrpm")) MaxRPM = el->FindElementValueAsNumber("maxrpm"); if (el->FindElement("maxthrottle")) MaxThrottle = el->FindElementValueAsNumber("maxthrottle"); if (el->FindElement("minthrottle")) MinThrottle = el->FindElementValueAsNumber("minthrottle"); if (el->FindElement("bsfc")) ISFC = el->FindElementValueAsNumberConvertTo("bsfc", "LBS/HP*HR"); if (el->FindElement("volumetric-efficiency")) volumetric_efficiency = el->FindElementValueAsNumber("volumetric-efficiency"); if (el->FindElement("compression-ratio")) CompressionRatio = el->FindElementValueAsNumber("compression-ratio"); if (el->FindElement("bore")) Bore = el->FindElementValueAsNumberConvertTo("bore","IN"); if (el->FindElement("stroke")) Stroke = el->FindElementValueAsNumberConvertTo("stroke","IN"); if (el->FindElement("cylinders")) Cylinders = el->FindElementValueAsNumber("cylinders"); if (el->FindElement("cylinder-head-mass")) CylinderHeadMass = el->FindElementValueAsNumberConvertTo("cylinder-head-mass","KG"); if (el->FindElement("air-intake-impedance-factor")) Z_airbox = el->FindElementValueAsNumber("air-intake-impedance-factor"); if (el->FindElement("ram-air-factor")) Ram_Air_Factor = el->FindElementValueAsNumber("ram-air-factor"); if (el->FindElement("cooling-factor")) Cooling_Factor = el->FindElementValueAsNumber("cooling-factor"); if (el->FindElement("starter-rpm")) StarterRPM = el->FindElementValueAsNumber("starter-rpm"); if (el->FindElement("starter-torque")) StarterTorque = el->FindElementValueAsNumber("starter-torque"); if (el->FindElement("dynamic-fmep")) FMEPDynamic= el->FindElementValueAsNumberConvertTo("dynamic-fmep","PA"); if (el->FindElement("static-fmep")) FMEPStatic = el->FindElementValueAsNumberConvertTo("static-fmep","PA"); if (el->FindElement("peak-piston-speed")) PeakMeanPistonSpeed_fps = el->FindElementValueAsNumber("peak-piston-speed"); if (el->FindElement("numboostspeeds")) { // Turbo- and super-charging parameters BoostSpeeds = (int)el->FindElementValueAsNumber("numboostspeeds"); if (el->FindElement("boostoverride")) BoostOverride = (int)el->FindElementValueAsNumber("boostoverride"); if (el->FindElement("boostmanual")) BoostManual = (int)el->FindElementValueAsNumber("boostmanual"); if (el->FindElement("takeoffboost")) TakeoffBoost = el->FindElementValueAsNumberConvertTo("takeoffboost", "PSI"); if (el->FindElement("boost-loss-factor")) BoostLossFactor = el->FindElementValueAsNumber("boost-loss-factor"); if (el->FindElement("ratedboost1")) RatedBoost[0] = el->FindElementValueAsNumberConvertTo("ratedboost1", "PSI"); if (el->FindElement("ratedboost2")) RatedBoost[1] = el->FindElementValueAsNumberConvertTo("ratedboost2", "PSI"); if (el->FindElement("ratedboost3")) RatedBoost[2] = el->FindElementValueAsNumberConvertTo("ratedboost3", "PSI"); if (el->FindElement("ratedpower1")) RatedPower[0] = el->FindElementValueAsNumberConvertTo("ratedpower1", "HP"); if (el->FindElement("ratedpower2")) RatedPower[1] = el->FindElementValueAsNumberConvertTo("ratedpower2", "HP"); if (el->FindElement("ratedpower3")) RatedPower[2] = el->FindElementValueAsNumberConvertTo("ratedpower3", "HP"); if (el->FindElement("ratedrpm1")) RatedRPM[0] = el->FindElementValueAsNumber("ratedrpm1"); if (el->FindElement("ratedrpm2")) RatedRPM[1] = el->FindElementValueAsNumber("ratedrpm2"); if (el->FindElement("ratedrpm3")) RatedRPM[2] = el->FindElementValueAsNumber("ratedrpm3"); if (el->FindElement("ratedaltitude1")) RatedAltitude[0] = el->FindElementValueAsNumberConvertTo("ratedaltitude1", "FT"); if (el->FindElement("ratedaltitude2")) RatedAltitude[1] = el->FindElementValueAsNumberConvertTo("ratedaltitude2", "FT"); if (el->FindElement("ratedaltitude3")) RatedAltitude[2] = el->FindElementValueAsNumberConvertTo("ratedaltitude3", "FT"); } Design_Oil_Temp = 358; // degK; Oil_Viscosity_Index = 0.25; Oil_Press_Relief_Valve = 60; // psi Oil_Press_RPM_Max = MaxRPM*0.75; if (el->FindElement("oil-pressure-relief-valve-psi")) Oil_Press_Relief_Valve = el->FindElementValueAsNumberConvertTo("oil-pressure-relief-valve-psi", "PSI"); if (el->FindElement("design-oil-temp-degK")) Design_Oil_Temp = el->FindElementValueAsNumberConvertTo("design-oil-temp-degK", "DEGK"); if (el->FindElement("oil-pressure-rpm-max")) Oil_Press_RPM_Max = el->FindElementValueAsNumber("oil-pressure-rpm-max"); if (el->FindElement("oil-viscosity-index")) Oil_Viscosity_Index = el->FindElementValueAsNumber("oil-viscosity-index"); while((table_element = el->FindNextElement("table")) != 0) { string name = table_element->GetAttributeValue("name"); try { if (name == "COMBUSTION") { Lookup_Combustion_Efficiency = new FGTable(PropertyManager, table_element); } else if (name == "MIXTURE") { Mixture_Efficiency_Correlation = new FGTable(PropertyManager, table_element); } else { cerr << "Unknown table type: " << name << " in piston engine definition." << endl; } } catch (std::string& str) { // Make sure allocated resources are freed before rethrowing. // (C++ standard guarantees that a null pointer deletion is no-op). delete Lookup_Combustion_Efficiency; delete Mixture_Efficiency_Correlation; throw("Error loading piston engine table:" + name + ". " + str); } } volumetric_efficiency_reduced = volumetric_efficiency; if(StarterRPM < 0.) StarterRPM = 2*IdleRPM; if(StarterTorque < 0) StarterTorque = (MaxHP)*0.4; //just a wag. displacement_SI = Displacement * in3tom3; RatedMeanPistonSpeed_fps = ( MaxRPM * Stroke) / (360); // AKA 2 * (RPM/60) * ( Stroke / 12) or 2NS // Create IFSC to match the engine if not provided if (ISFC < 0) { double pmep = 29.92 - MaxManifoldPressure_inHg; pmep *= inhgtopa * volumetric_efficiency; double fmep = (FMEPDynamic * RatedMeanPistonSpeed_fps * fttom + FMEPStatic); double hp_loss = ((pmep + fmep) * displacement_SI * MaxRPM)/(Cycles*22371); ISFC = ( 1.1*Displacement * MaxRPM * volumetric_efficiency *(MaxManifoldPressure_inHg / 29.92) ) / (9411 * (MaxHP+hp_loss-StaticFriction_HP)); // cout <<"FMEP: "<< fmep <<" PMEP: "<< pmep << " hp_loss: " <<hp_loss <<endl; } if ( MaxManifoldPressure_inHg > 29.9 ) { // Don't allow boosting with a bogus number MaxManifoldPressure_inHg = 29.9; } minMAP = MinManifoldPressure_inHg * inhgtopa; // inHg to Pa maxMAP = MaxManifoldPressure_inHg * inhgtopa; // For throttle /* * Pm = ( Ze / ( Ze + Zi + Zt ) ) * Pa * Where: * Pm = Manifold Pressure * Pa = Ambient Pressre * Ze = engine impedance, Ze is effectively 1 / Mean Piston Speed * Zi = airbox impedance * Zt = throttle impedance * * For the calculation below throttle is fully open or Zt = 0 * * * */ if(Z_airbox < 0.0){ double Ze=PeakMeanPistonSpeed_fps/RatedMeanPistonSpeed_fps; // engine impedence Z_airbox = (standard_pressure *Ze / maxMAP) - Ze; // impedence of airbox } // Constant for Throttle impedence Z_throttle=(PeakMeanPistonSpeed_fps/((IdleRPM * Stroke) / 360))*(standard_pressure/minMAP - 1) - Z_airbox; // Z_throttle=(MaxRPM/IdleRPM )*(standard_pressure/minMAP+2); // Constant for Throttle impedence // Default tables if not provided in the configuration file if(Lookup_Combustion_Efficiency == 0) { // First column is thi, second is neta (combustion efficiency) Lookup_Combustion_Efficiency = new FGTable(12); *Lookup_Combustion_Efficiency << 0.00 << 0.980; *Lookup_Combustion_Efficiency << 0.90 << 0.980; *Lookup_Combustion_Efficiency << 1.00 << 0.970; *Lookup_Combustion_Efficiency << 1.05 << 0.950; *Lookup_Combustion_Efficiency << 1.10 << 0.900; *Lookup_Combustion_Efficiency << 1.15 << 0.850; *Lookup_Combustion_Efficiency << 1.20 << 0.790; *Lookup_Combustion_Efficiency << 1.30 << 0.700; *Lookup_Combustion_Efficiency << 1.40 << 0.630; *Lookup_Combustion_Efficiency << 1.50 << 0.570; *Lookup_Combustion_Efficiency << 1.60 << 0.525; *Lookup_Combustion_Efficiency << 2.00 << 0.345; } // First column is Fuel/Air Ratio, second is neta (mixture efficiency) if( Mixture_Efficiency_Correlation == 0) { Mixture_Efficiency_Correlation = new FGTable(15); *Mixture_Efficiency_Correlation << 0.05000 << 0.00000; *Mixture_Efficiency_Correlation << 0.05137 << 0.00862; *Mixture_Efficiency_Correlation << 0.05179 << 0.21552; *Mixture_Efficiency_Correlation << 0.05430 << 0.48276; *Mixture_Efficiency_Correlation << 0.05842 << 0.70690; *Mixture_Efficiency_Correlation << 0.06312 << 0.83621; *Mixture_Efficiency_Correlation << 0.06942 << 0.93103; *Mixture_Efficiency_Correlation << 0.07786 << 1.00000; *Mixture_Efficiency_Correlation << 0.08845 << 1.00000; *Mixture_Efficiency_Correlation << 0.09270 << 0.98276; *Mixture_Efficiency_Correlation << 0.10120 << 0.93103; *Mixture_Efficiency_Correlation << 0.11455 << 0.72414; *Mixture_Efficiency_Correlation << 0.12158 << 0.45690; *Mixture_Efficiency_Correlation << 0.12435 << 0.23276; *Mixture_Efficiency_Correlation << 0.12500 << 0.00000; } string property_name, base_property_name; base_property_name = CreateIndexedPropertyName("propulsion/engine", EngineNumber); property_name = base_property_name + "/power-hp"; PropertyManager->Tie(property_name, &HP); property_name = base_property_name + "/friction-hp"; PropertyManager->Tie(property_name, &StaticFriction_HP); property_name = base_property_name + "/bsfc-lbs_hphr"; PropertyManager->Tie(property_name, &ISFC); property_name = base_property_name + "/starter-norm"; PropertyManager->Tie(property_name, &StarterGain); property_name = base_property_name + "/volumetric-efficiency"; PropertyManager->Tie(property_name, &volumetric_efficiency); property_name = base_property_name + "/map-pa"; PropertyManager->Tie(property_name, &MAP); property_name = base_property_name + "/map-inhg"; PropertyManager->Tie(property_name, &ManifoldPressure_inHg); property_name = base_property_name + "/air-intake-impedance-factor"; PropertyManager->Tie(property_name, &Z_airbox); property_name = base_property_name + "/ram-air-factor"; PropertyManager->Tie(property_name, &Ram_Air_Factor); property_name = base_property_name + "/cooling-factor"; PropertyManager->Tie(property_name, &Cooling_Factor); property_name = base_property_name + "/boost-speed"; PropertyManager->Tie(property_name, &BoostSpeed); property_name = base_property_name + "/cht-degF"; PropertyManager->Tie(property_name, this, &FGPiston::getCylinderHeadTemp_degF); property_name = base_property_name + "/oil-temperature-degF"; PropertyManager->Tie(property_name, this, &FGPiston::getOilTemp_degF); property_name = base_property_name + "/oil-pressure-psi"; PropertyManager->Tie(property_name, this, &FGPiston::getOilPressure_psi); property_name = base_property_name + "/egt-degF"; PropertyManager->Tie(property_name, this, &FGPiston::getExhaustGasTemp_degF); if(BoostLossFactor > 0.0) { property_name = base_property_name + "/boostloss-factor"; PropertyManager->Tie(property_name, &BoostLossFactor); property_name = base_property_name + "/boostloss-hp"; PropertyManager->Tie(property_name, &BoostLossHP); } // Set up and sanity-check the turbo/supercharging configuration based on the input values. if (TakeoffBoost > RatedBoost[0]) bTakeoffBoost = true; for (i=0; i<BoostSpeeds; ++i) { bool bad = false; if (RatedBoost[i] <= 0.0) bad = true; if (RatedPower[i] <= 0.0) bad = true; if (RatedAltitude[i] < 0.0) bad = true; // 0.0 is deliberately allowed - this corresponds to unregulated supercharging. if (i > 0 && RatedAltitude[i] < RatedAltitude[i - 1]) bad = true; if (bad) { // We can't recover from the above - don't use this supercharger speed. BoostSpeeds--; // TODO - put out a massive error message! break; } // Now sanity-check stuff that is recoverable. if (i < BoostSpeeds - 1) { if (BoostSwitchAltitude[i] < RatedAltitude[i]) { // TODO - put out an error message // But we can also make a reasonable estimate, as below. BoostSwitchAltitude[i] = RatedAltitude[i] + 1000; } BoostSwitchPressure[i] = GetStdPressure100K(BoostSwitchAltitude[i]) * psftopa; //cout << "BoostSwitchAlt = " << BoostSwitchAltitude[i] << ", pressure = " << BoostSwitchPressure[i] << '\n'; // Assume there is some hysteresis on the supercharger gear switch, and guess the value for now BoostSwitchHysteresis = 1000; } // Now work out the supercharger pressure multiplier of this speed from the rated boost and altitude. RatedMAP[i] = standard_pressure + RatedBoost[i] * 6895; // psi*6895 = Pa. // Sometimes a separate BCV setting for takeoff or extra power is fitted. if (TakeoffBoost > RatedBoost[0]) { // Assume that the effect on the BCV is the same whichever speed is in use. TakeoffMAP[i] = RatedMAP[i] + ((TakeoffBoost - RatedBoost[0]) * 6895); bTakeoffBoost = true; } else { TakeoffMAP[i] = RatedMAP[i]; bTakeoffBoost = false; } BoostMul[i] = RatedMAP[i] / (GetStdPressure100K(RatedAltitude[i]) * psftopa); } if (BoostSpeeds > 0) { Boosted = true; BoostSpeed = 0; } bBoostOverride = (BoostOverride == 1 ? true : false); bBoostManual = (BoostManual == 1 ? true : false); Debug(0); // Call Debug() routine from constructor if needed }
FGGasCell::FGGasCell(FGFDMExec* exec, Element* el, unsigned int num, const struct Inputs& input) : FGForce(exec), in(input) { string token; Element* element; FGPropertyManager* PropertyManager = exec->GetPropertyManager(); MassBalance = exec->GetMassBalance(); gasCellJ = FGMatrix33(); gasCellM = FGColumnVector3(); Buoyancy = MaxVolume = MaxOverpressure = Temperature = Pressure = Contents = Volume = dVolumeIdeal = 0.0; Xradius = Yradius = Zradius = Xwidth = Ywidth = Zwidth = 0.0; ValveCoefficient = ValveOpen = 0.0; CellNum = num; // NOTE: In the local system X points north, Y points east and Z points down. SetTransformType(FGForce::tLocalBody); type = el->GetAttributeValue("type"); if (type == "HYDROGEN") Type = ttHYDROGEN; else if (type == "HELIUM") Type = ttHELIUM; else if (type == "AIR") Type = ttAIR; else Type = ttUNKNOWN; element = el->FindElement("location"); if (element) { vXYZ = element->FindElementTripletConvertTo("IN"); } else { std::stringstream error; error << "Fatal Error: No location found for this gas cell." << endl; throw std::runtime_error(error.str()); } if ((el->FindElement("x_radius") || el->FindElement("x_width")) && (el->FindElement("y_radius") || el->FindElement("y_width")) && (el->FindElement("z_radius") || el->FindElement("z_width"))) { if (el->FindElement("x_radius")) { Xradius = el->FindElementValueAsNumberConvertTo("x_radius", "FT"); } if (el->FindElement("y_radius")) { Yradius = el->FindElementValueAsNumberConvertTo("y_radius", "FT"); } if (el->FindElement("z_radius")) { Zradius = el->FindElementValueAsNumberConvertTo("z_radius", "FT"); } if (el->FindElement("x_width")) { Xwidth = el->FindElementValueAsNumberConvertTo("x_width", "FT"); } if (el->FindElement("y_width")) { Ywidth = el->FindElementValueAsNumberConvertTo("y_width", "FT"); } if (el->FindElement("z_width")) { Zwidth = el->FindElementValueAsNumberConvertTo("z_width", "FT"); } // The volume is a (potentially) extruded ellipsoid. // However, currently only a few combinations of radius and width are // fully supported. if ((Xradius != 0.0) && (Yradius != 0.0) && (Zradius != 0.0) && (Xwidth == 0.0) && (Ywidth == 0.0) && (Zwidth == 0.0)) { // Ellipsoid volume. MaxVolume = 4.0 * M_PI * Xradius * Yradius * Zradius / 3.0; } else if ((Xradius == 0.0) && (Yradius != 0.0) && (Zradius != 0.0) && (Xwidth != 0.0) && (Ywidth == 0.0) && (Zwidth == 0.0)) { // Cylindrical volume. MaxVolume = M_PI * Yradius * Zradius * Xwidth; } else { cerr << "Warning: Unsupported gas cell shape." << endl; MaxVolume = (4.0 * M_PI * Xradius * Yradius * Zradius / 3.0 + M_PI * Yradius * Zradius * Xwidth + M_PI * Xradius * Zradius * Ywidth + M_PI * Xradius * Yradius * Zwidth + 2.0 * Xradius * Ywidth * Zwidth + 2.0 * Yradius * Xwidth * Zwidth + 2.0 * Zradius * Xwidth * Ywidth + Xwidth * Ywidth * Zwidth); } } else { std::stringstream error; error << "Fatal Error: Gas cell shape must be given." << endl; throw std::runtime_error(error.str()); } if (el->FindElement("max_overpressure")) { MaxOverpressure = el->FindElementValueAsNumberConvertTo("max_overpressure", "LBS/FT2"); } if (el->FindElement("fullness")) { const double Fullness = el->FindElementValueAsNumber("fullness"); if (0 <= Fullness) { Volume = Fullness * MaxVolume; } else { cerr << "Warning: Invalid initial gas cell fullness value." << endl; } } if (el->FindElement("valve_coefficient")) { ValveCoefficient = el->FindElementValueAsNumberConvertTo("valve_coefficient", "FT4*SEC/SLUG"); ValveCoefficient = max(ValveCoefficient, 0.0); } // Initialize state SetLocation(vXYZ); if (Temperature == 0.0) { Temperature = in.Temperature; } if (Pressure == 0.0) { Pressure = in.Pressure; } if (Volume != 0.0) { // Calculate initial gas content. Contents = Pressure * Volume / (R * Temperature); // Clip to max allowed value. const double IdealPressure = Contents * R * Temperature / MaxVolume; if (IdealPressure > Pressure + MaxOverpressure) { Contents = (Pressure + MaxOverpressure) * MaxVolume / (R * Temperature); Pressure = Pressure + MaxOverpressure; } else { Pressure = max(IdealPressure, Pressure); } } else { // Calculate initial gas content. Contents = Pressure * MaxVolume / (R * Temperature); } Volume = Contents * R * Temperature / Pressure; Mass = Contents * M_gas(); // Bind relevant properties string property_name, base_property_name; base_property_name = CreateIndexedPropertyName("buoyant_forces/gas-cell", CellNum); property_name = base_property_name + "/max_volume-ft3"; PropertyManager->Tie( property_name.c_str(), &MaxVolume, false ); PropertyManager->GetNode()->SetWritable( property_name, false ); property_name = base_property_name + "/temp-R"; PropertyManager->Tie( property_name.c_str(), &Temperature, false ); property_name = base_property_name + "/pressure-psf"; PropertyManager->Tie( property_name.c_str(), &Pressure, false ); property_name = base_property_name + "/volume-ft3"; PropertyManager->Tie( property_name.c_str(), &Volume, false ); property_name = base_property_name + "/buoyancy-lbs"; PropertyManager->Tie( property_name.c_str(), &Buoyancy, false ); property_name = base_property_name + "/contents-mol"; PropertyManager->Tie( property_name.c_str(), &Contents, false ); property_name = base_property_name + "/valve_open"; PropertyManager->Tie( property_name.c_str(), &ValveOpen, false ); Debug(0); // Read heat transfer coefficients if (Element* heat = el->FindElement("heat")) { Element* function_element = heat->FindElement("function"); while (function_element) { HeatTransferCoeff.push_back(new FGFunction(PropertyManager, function_element)); function_element = heat->FindNextElement("function"); } } // Load ballonets if there are any if (Element* ballonet_element = el->FindElement("ballonet")) { while (ballonet_element) { Ballonet.push_back(new FGBallonet(exec, ballonet_element, Ballonet.size(), this, in)); ballonet_element = el->FindNextElement("ballonet"); } } }
FGBallonet::FGBallonet(FGFDMExec* exec, Element* el, unsigned int num, FGGasCell* parent, const struct FGGasCell::Inputs& input) : in(input) { string token; Element* element; FGPropertyManager* PropertyManager = exec->GetPropertyManager(); MassBalance = exec->GetMassBalance(); ballonetJ = FGMatrix33(); MaxVolume = MaxOverpressure = Temperature = Pressure = Contents = Volume = dVolumeIdeal = dU = 0.0; Xradius = Yradius = Zradius = Xwidth = Ywidth = Zwidth = 0.0; ValveCoefficient = ValveOpen = 0.0; BlowerInput = NULL; CellNum = num; Parent = parent; // NOTE: In the local system X points north, Y points east and Z points down. element = el->FindElement("location"); if (element) { vXYZ = element->FindElementTripletConvertTo("IN"); } else { std::stringstream error; error << "Fatal Error: No location found for this ballonet." << endl; throw std::runtime_error(error.str()); } if ((el->FindElement("x_radius") || el->FindElement("x_width")) && (el->FindElement("y_radius") || el->FindElement("y_width")) && (el->FindElement("z_radius") || el->FindElement("z_width"))) { if (el->FindElement("x_radius")) { Xradius = el->FindElementValueAsNumberConvertTo("x_radius", "FT"); } if (el->FindElement("y_radius")) { Yradius = el->FindElementValueAsNumberConvertTo("y_radius", "FT"); } if (el->FindElement("z_radius")) { Zradius = el->FindElementValueAsNumberConvertTo("z_radius", "FT"); } if (el->FindElement("x_width")) { Xwidth = el->FindElementValueAsNumberConvertTo("x_width", "FT"); } if (el->FindElement("y_width")) { Ywidth = el->FindElementValueAsNumberConvertTo("y_width", "FT"); } if (el->FindElement("z_width")) { Zwidth = el->FindElementValueAsNumberConvertTo("z_width", "FT"); } // The volume is a (potentially) extruded ellipsoid. // FIXME: However, currently only a few combinations of radius and // width are fully supported. if ((Xradius != 0.0) && (Yradius != 0.0) && (Zradius != 0.0) && (Xwidth == 0.0) && (Ywidth == 0.0) && (Zwidth == 0.0)) { // Ellipsoid volume. MaxVolume = 4.0 * M_PI * Xradius * Yradius * Zradius / 3.0; } else if ((Xradius == 0.0) && (Yradius != 0.0) && (Zradius != 0.0) && (Xwidth != 0.0) && (Ywidth == 0.0) && (Zwidth == 0.0)) { // Cylindrical volume. MaxVolume = M_PI * Yradius * Zradius * Xwidth; } else { cerr << "Warning: Unsupported ballonet shape." << endl; MaxVolume = (4.0 * M_PI * Xradius * Yradius * Zradius / 3.0 + M_PI * Yradius * Zradius * Xwidth + M_PI * Xradius * Zradius * Ywidth + M_PI * Xradius * Yradius * Zwidth + 2.0 * Xradius * Ywidth * Zwidth + 2.0 * Yradius * Xwidth * Zwidth + 2.0 * Zradius * Xwidth * Ywidth + Xwidth * Ywidth * Zwidth); } } else { std::stringstream error; error << "Fatal Error: Ballonet shape must be given." << endl; throw std::runtime_error(error.str()); } if (el->FindElement("max_overpressure")) { MaxOverpressure = el->FindElementValueAsNumberConvertTo("max_overpressure", "LBS/FT2"); } if (el->FindElement("fullness")) { const double Fullness = el->FindElementValueAsNumber("fullness"); if (0 <= Fullness) { Volume = Fullness * MaxVolume; } else { cerr << "Warning: Invalid initial ballonet fullness value." << endl; } } if (el->FindElement("valve_coefficient")) { ValveCoefficient = el->FindElementValueAsNumberConvertTo("valve_coefficient", "FT4*SEC/SLUG"); ValveCoefficient = max(ValveCoefficient, 0.0); } // Initialize state if (Temperature == 0.0) { Temperature = Parent->GetTemperature(); } if (Pressure == 0.0) { Pressure = Parent->GetPressure(); } if (Volume != 0.0) { // Calculate initial air content. Contents = Pressure * Volume / (R * Temperature); // Clip to max allowed value. const double IdealPressure = Contents * R * Temperature / MaxVolume; if (IdealPressure > Pressure + MaxOverpressure) { Contents = (Pressure + MaxOverpressure) * MaxVolume / (R * Temperature); Pressure = Pressure + MaxOverpressure; } else { Pressure = max(IdealPressure, Pressure); } } else { // Calculate initial air content. Contents = Pressure * MaxVolume / (R * Temperature); } Volume = Contents * R * Temperature / Pressure; // Bind relevant properties string property_name, base_property_name; base_property_name = CreateIndexedPropertyName("buoyant_forces/gas-cell", Parent->GetIndex()); base_property_name = CreateIndexedPropertyName(base_property_name + "/ballonet", CellNum); property_name = base_property_name + "/max_volume-ft3"; PropertyManager->Tie( property_name, &MaxVolume, false ); PropertyManager->GetNode()->SetWritable( property_name, false ); property_name = base_property_name + "/temp-R"; PropertyManager->Tie( property_name, &Temperature, false ); property_name = base_property_name + "/pressure-psf"; PropertyManager->Tie( property_name, &Pressure, false ); property_name = base_property_name + "/volume-ft3"; PropertyManager->Tie( property_name, &Volume, false ); property_name = base_property_name + "/contents-mol"; PropertyManager->Tie( property_name, &Contents, false ); property_name = base_property_name + "/valve_open"; PropertyManager->Tie( property_name, &ValveOpen, false ); Debug(0); // Read heat transfer coefficients if (Element* heat = el->FindElement("heat")) { Element* function_element = heat->FindElement("function"); while (function_element) { HeatTransferCoeff.push_back(new FGFunction(PropertyManager, function_element)); function_element = heat->FindNextElement("function"); } } // Read blower input function if (Element* blower = el->FindElement("blower_input")) { Element* function_element = blower->FindElement("function"); BlowerInput = new FGFunction(PropertyManager, function_element); } }