123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352 |
- # Edgley Optica
- #
- # Engine management routines
- #
- # Gary Neely aka 'Buckaroo'
- #
- var UPDATE_INTERVAL = 0.2; # Update engines every 0.2 secs.
- var MAX_RPM = 2200; # Above this the engine starts to accumulate strain
- # from over-rev issues if MAX_RPM_T exceeded
- var MAX_RPM_CLK = 60; # Secs; Time up to this value doesn't count toward strain
- var MAX_IDLE = 800; # Above this the engine starts to accumulate strain
- # from oil viscosity issues
- var MAX_STRAIN = 20000; # Arbitrary strain value, when exceeded, engine dies
- var OILV_DELTA_WARMUP = 0.002; # Viscosity increment per cycle when warming up
- var OILV_DELTA_COOLDN = 0.0002; # Viscosity decrement per cycle when cooling down
- var VISC_PENALTY = 40; # Maximum reduction of oil pressure for viscosity
- var COLD_OIL_FACTOR = 25; # Arbitrary strain value for running engine over idle when cold
- # This is killer, so keep value small or users will whine
- var MAX_OK_OILT = 80; # Maximum best oil temperature
- var MIN_OK_OILT = 60; # Minimum best oil temperature
- var MIN_OILP = 70; # Minimum acceptable oil pressure
- var MAX_OILP = 150; # Highest value oil pressure will register
- var MAX_OK_OILP = 90; # Maximum best oil pressure
- var MIN_OK_OILP = 70; # Minimum best oil pressure
- var MIN_OILP = 50; # Minimum acceptable oil pressure, currently not used
- var TARGET_FUEL_P = 5; # Target best fuel pressure
- var CYLT_MAX = 270; # Engine always takes damage above this temp
- var TARGET_CYLT_HIGH = 230; # Target best cylinder head temp, high end
- # Engine takes damage above this temp after a time interval
- var TARGET_CYLT_LOW = 150; # Target best cylinder head temp, low end
- var CYLT_MIN = 100; # Low end of operating cylinder head temp
- var MAX_CYLT_STRAIN_CLK = 60; # Time in secs that must pass before engine under CYLT_MAX
- # but over CYLT_HIGH starts taking damage
- var DELTA_T_F = 0.1; # Change in temperature master factor,
- # inc to make changes faster, dec to slow temp changes
- var DELTA_T_ENV = 0.014; # Change in temp due to ambient air factor
- var DELTA_T_THRUST = 0.006; # Change in temp due to power factor
- var DELTA_T_AS = 0.02; # Change in temp due to airspeed-induced air factor
- var MIXTURE_CUTOFF = 0.8; # Mixture setting: below this value, no cooling benefits
- var MIXTURE_FACTOR = 14; # Mixture cooling benefits multiplier
- var OPOH = 50; # Engine operational overhead value
- var PROP_AIR = 0.01; # Factor for prop air moving over engine
- var AMBIENT_REDUCTION = 0.25; # Factor for reduction of ambient cooling under max operating range
- var MIXTURE_PEAK = 0.74; # Observed mixture setting for peak power
- var MIXTURE_BIAS = 10; # Max degrees C to augment cyl temp at mixture peak
- var ENGINE_MASS = 1; # Engine mass factor; not currently used, but the idea is to
- # inc this to slow temperature changes
- var eng_checks = props.globals.getNode("/sim/Optica/engine-checks");
- var eng_warns = props.globals.getNode("/sim/Optica/engine-warns");
- var tanks = props.globals.getNode("/consumables/fuel").getChildren("tank");
- var engine = props.globals.getNode("/engines/engine[0]");
- var controls = props.globals.getNode("controls/engines/engine[0]");
- var airspeed = props.globals.getNode("/velocities/airspeed-kt");
- var envtemp = props.globals.getNode("/environment/temperature-degc");
- # Defined under Optica-fuel.nas:
- #var fuel_scalar = props.globals.getNode("consumables/fuel/consumption-scalar").getValue();
- var oilt_target = (MAX_OK_OILT-MIN_OK_OILT)/2 + MIN_OK_OILT; # Calculate a nice mid-range temp value to seek
- var oilp_target = (MAX_OK_OILP-MIN_OK_OILP)/2 + MIN_OK_OILP; # Calculate a nice mid-range pressure value to seek
- # Functions for enabling engine options:
- var eng_checks_set = func {
- if (eng_checks.getValue()) { eng_checks.setValue(0); var mssg = "Engine parameter checks disabled."; }
- else { eng_checks.setValue(1); var mssg = "Engine parameter checks enabled."; }
- Optica_screenmssg.fg = [1, 1, 1, 1];
- Optica_screenmssg.write(mssg);
- }
- var eng_warns_set = func {
- if (eng_warns.getValue()) { eng_warns.setValue(0); var mssg = "Engine parameter warnings disabled."; }
- else { eng_warns.setValue(1); var mssg = "Engine parameter warnings enabled."; }
- Optica_screenmssg.fg = [1, 1, 1, 1];
- Optica_screenmssg.write(mssg);
- }
- # Engine warning messages:
- var eng_checks_overrev = func(i) {
- i+=1;
- var mssg = "Engine "~i~" over-rev warning.";
- Optica_screenmssg.fg = [1, 0.5, 0, 1];
- Optica_screenmssg.write(mssg);
- }
- var eng_checks_overheat = func(i) {
- i+=1;
- var mssg = "Engine "~i~" over-heating warning.";
- Optica_screenmssg.fg = [1, 0.5, 0, 1];
- Optica_screenmssg.write(mssg);
- }
- var eng_checks_oilcold = func(i) {
- i+=1;
- var mssg = "Engine "~i~" oil temperature warning.";
- Optica_screenmssg.fg = [1, 0.5, 0, 1];
- Optica_screenmssg.write(mssg);
- }
- var eng_checks_damage = func(i,y) {
- i+=1;
- var mssg = "Engine "~i~" has passed 25% damage.";
- if (y > MAX_STRAIN * 0.9) { mssg = "Engine "~i~" is critical."; }
- elsif (y > MAX_STRAIN * 0.75) { mssg = "Engine "~i~" has passed 75% damage."; }
- elsif (y > MAX_STRAIN * 0.5) { mssg = "Engine "~i~" has passed 50% damage."; }
- Optica_screenmssg2.fg = [1, 0.5, 0, 1];
- Optica_screenmssg2.write(mssg);
- }
- var eng_checks_failure = func(i) {
- i+=1;
- var mssg = "Engine "~i~" failure.";
- Optica_screenmssg2.fg = [1, 0, 0, 1];
- Optica_screenmssg2.write(mssg);
- }
- var FtoC = func (f) { # Fahrenheit to Celsius
- return (f-32) * 5 / 9;
- }
- # Primary engine loop:
- var engine_update = func {
- # Adjust fuel flow:
- engine.getNode("fuel-flow-adj").setValue(engine.getNode("fuel-flow-gph").getValue()*fuel_scalar);
- if (engine.getNode("running").getValue() == 1) { # Is engine running?
- var strain = engine.getNode("strain").getValue();
- if (strain > MAX_STRAIN) { # Don't let strain value get absurd when engine
- strain = MAX_STRAIN; # failure is disabled
- engine.getNode("strain").setValue(strain);
- }
- # Calculate engine strain due to over-rev
- var strain_clk_rev = engine.getNode("strain-clk-rev").getValue(); # Get time we've been operating over-revved
- if (engine.getNode("rpm").getValue() > MAX_RPM) {
- if (strain_clk_rev < MAX_RPM_CLK) { # Update time allowed for over-rev
- interpolate(engine.getNode("strain-clk-rev"),MAX_RPM_CLK,MAX_RPM_CLK-strain_clk_rev);
- }
- # If time allowed over RPM is exceeded:
- if (strain_clk_rev >= MAX_RPM_CLK) { # Add more strain to engine
- if (eng_checks.getValue()) {
- var strain_add = engine.getNode("rpm").getValue() - MAX_RPM;
- engine.getNode("strain").setValue(strain + strain_add);
- }
- if (eng_warns.getValue()) { eng_checks_overrev(i); } # Notice to user
- }
- }
- else {
- # Under max RPM, so allow timer to fall back to 0
- if (strain_clk_rev > 0) {
- interpolate(engine.getNode("strain-clk-rev"),0,strain_clk_rev);
- }
- }
- # Fuel pressure
- if (engine.getNode("fuel-press").getValue() < TARGET_FUEL_P) {
- interpolate(engine.getNode("fuel-press"), TARGET_FUEL_P, 3);
- }
- # Simplified oil temp, viscosity, pressure
- # Note that viscosity is a dimensionless normalized value,
- # not a true viscosity number
- var oilv = engine.getNode("oil-visc").getValue();
- if (oilv > 0) {
- oilv = oilv - OILV_DELTA_WARMUP; # Determine new oilv as necessary
- engine.getNode("oil-visc").setValue(oilv); # Save viscosity
- var oilp = ((MAX_OILP - oilp_target) * oilv) + oilp_target;# Viscosity determines position between max oilp and target best oilp
- engine.getNode("oil-press").setValue(oilp); # Save oilp, probably not necessary to interpolate
- # Oilt rises from ambient as viscosity falls
- var oilt = (oilt_target - envtemp.getValue()) * (1-oilv) + envtemp.getValue();
- engine.getNode("oiltempc").setValue(oilt); # Save oilt as deg C
- # Oil pressure check:
- if (oilp > MAX_OK_OILP and engine.getNode("rpm").getValue() > MAX_IDLE) {
- if (eng_checks.getValue()) {
- engine.getNode("strain").setValue(engine.getNode("strain").getValue() + (COLD_OIL_FACTOR/oilv));
- }
- if (eng_warns.getValue()) { eng_checks_oilcold(i); } # Notice to user
- }
- }
- # Cyl head temp change calculations:
- # Currently this accounts for environmental cooling,
- # airflow cooling, and thrust/rpm heating. It should
- # also account for cooling due to rich mixture, but
- # doesn't yet.
- var cyl_temp = engine.getNode("cyltempc").getValue();
- var thrust = engine.getNode("prop-thrust").getValue();
- var mixture = controls[i].getNode("mixture").getValue();
- var throttle = controls[i].getNode("throttle").getValue();
- var dT_env = (envtemp.getValue() - cyl_temp) * DELTA_T_ENV;
- var dT_thrust = (thrust + OPOH) * DELTA_T_THRUST;
- var dT_airspeed = airspeed.getValue() * DELTA_T_AS;
- var dT_mixture = mixture - MIXTURE_CUTOFF;
- if (dT_mixture < 0) { dT_mixture = 0; } # DeltaT due to rich mixture benefits
- else { dT_mixture = dT_mixture * MIXTURE_FACTOR; }
- if (cyl_temp <= TARGET_CYLT_LOW) { # When below best operating low temp:
- dT_env = dT_env * AMBIENT_REDUCTION; # DeltaT due to ambient reduction benefits
- dT_airspeed = 0; # No airspeed benefits below best operating temp low range
- # Linearly reduce mixture benefits down to ambient temp
- dT_mixture = 0; # No significant mixture benefits in this range
- }
- elsif (cyl_temp < TARGET_CYLT_HIGH) { # When withing best operating temp range:
- # Normalize temperature range:
- var temp_norm = (cyl_temp - TARGET_CYLT_LOW) / (TARGET_CYLT_HIGH - TARGET_CYLT_LOW);
- # Linearly reduce ambient benefits from full to AMBIENT_REDUCTION over range
- dT_env = dT_env * (AMBIENT_REDUCTION + (AMBIENT_REDUCTION * temp_norm));
- # Linearly reduce airspeed benefits from full to none
- dT_airspeed = dT_airspeed * temp_norm;
- # Linearly reduce mixture benefits from full to none
- if (dT_mixture > 0) { dT_mixture = dT_mixture * temp_norm; }
- }
- var delta_cyl_temp = (dT_env + dT_thrust - dT_airspeed - dT_mixture) * DELTA_T_F;
- cyl_temp = cyl_temp + delta_cyl_temp;
- engine.getNode("cyltempc").setValue(cyl_temp);
- # Calculate engine strain due to over-temp
- var strain_clk_temp = engine.getNode("strain-clk-temp").getValue(); # Get time that we've been operating over-temp
- if (cyl_temp > CYLT_MAX) { # Above this temp engine damage always occurs
- if (eng_checks.getValue()) {
- var strain_add = cyl_temp - TARGET_CYLT_HIGH;
- engine.getNode("strain").setValue(strain + strain_add);
- }
- if (eng_warns.getValue()) { eng_checks_overheat(i); } # Notice to user
- }
- if (cyl_temp > TARGET_CYLT_HIGH) { # Above this temp engine damage occurs after a period of time
- if (strain_clk_temp < MAX_CYLT_STRAIN_CLK) { # Update time allowed for over-temp
- interpolate(engine.getNode("strain-clk-temp"),MAX_CYLT_STRAIN_CLK,MAX_CYLT_STRAIN_CLK-strain_clk_temp);
- }
- # If time allowed over temp is exceeded:
- if (strain_clk_temp >= MAX_CYLT_STRAIN_CLK) { # Add more strain to engine
- if (eng_checks.getValue()) {
- var strain_add = cyl_temp - TARGET_CYLT_HIGH;
- engine.getNode("strain").setValue(strain + strain_add);
- }
- if (eng_warns.getValue()) { eng_checks_overheat(i); } # Notice to user
- }
- }
- else {
- # Under temp limits, so allow timer to fall back to 0
- if (strain_clk_temp > 0) {
- interpolate(engine.getNode("strain-clk-temp"),0,strain_clk_temp);
- }
- }
- # Mixture adjustments change thrust values by about 5% which
- # which is hard to notice on the gauge. So this is a fudged
- # addition to make the temperature peak more when the mixture
- # is leaned out to highest power output. This means the gauge
- # will read a little high, but failure tests will still be
- # based on true cyl temp.
- var bias = 0;
- if (mixture >= MIXTURE_PEAK) {
- bias = ((1 - mixture) / (1 - MIXTURE_PEAK)) * MIXTURE_BIAS * throttle;
- }
- else {
- bias = (mixture / MIXTURE_PEAK) * MIXTURE_BIAS * throttle;
- }
- engine.getNode("cyltempc-biased").setValue(cyl_temp+bias);
-
- # For testing and debugging:
- #engine.getNode("cyl-dt").setValue(delta_cyl_temp);
- #engine.getNode("cyl-dte").setValue(dT_env);
- #engine.getNode("cyl-dtt").setValue(dT_thrust);
- #engine.getNode("cyl-dta").setValue(dT_airspeed);
- #engine.getNode("cyl-dtm").setValue(dT_mixture);
- # Engine damage warnings
- if (eng_warns.getValue() and strain > MAX_STRAIN * 0.25) {
- eng_checks_damage(i,strain);
- }
- if (eng_checks.getValue()) {
- # Kill engine if over-strained
- strain = engine.getNode("strain").getValue();
- if (strain >= MAX_STRAIN) {
- engine.getNode("overrev").setValue(1);
- engine.getNode("running").setValue(0);
- engine.getNode("out-of-fuel").setValue(1);
- tanks[i].getNode("selected").setValue(0);
- interpolate(engine.getNode("fuel-press"), 0, 5);
- if (eng_warns.getValue()) { eng_checks_failure(i); }
- }
- }
- }
- else { # Engine not running
- # Kill fuel pressure
- if (engine.getNode("fuel-press").getValue() > 0) {
- interpolate(engine.getNode("fuel-press"), 0, 10);
- }
- # Cyl temp cool-down:
- var cyl_temp = engine.getNode("cyltempc").getValue();
- var dT_env = (envtemp.getValue() - cyl_temp) * DELTA_T_ENV;
- var dT_airspeed = airspeed.getValue() * DELTA_T_AS;
- if (cyl_temp <= TARGET_CYLT_LOW) { # When below best operating low temp:
- dT_env = dT_env * AMBIENT_REDUCTION; # Reduce ambient cooling
- dT_airspeed = 0; # No airspeed benefits below best operating temp low range
- }
- elsif (cyl_temp < TARGET_CYLT_HIGH) { # When withing best operating temp range:
- # Normalize range:
- var temp_norm = (cyl_temp - TARGET_CYLT_LOW) / (TARGET_CYLT_HIGH - TARGET_CYLT_LOW);
- # Linearly reduce ambient benefits from full to AMBIENT_REDUCTION over range
- dT_env = dT_env * (AMBIENT_REDUCTION + (AMBIENT_REDUCTION * temp_norm));
- dT_airspeed = dT_airspeed * temp_norm; # Linearly reduce airspeed benefits from full to none
- }
- var delta_cyl_temp = (dT_env - dT_airspeed) * DELTA_T_F;
- cyl_temp = cyl_temp + delta_cyl_temp;
- engine.getNode("cyltempc").setValue(cyl_temp);
- engine.getNode("cyltempc-biased").setValue(cyl_temp);
- # Oil cool-down; see main oil section for notes
- var oilv = engine.getNode("oil-visc").getValue();
- if (oilv < 1) {
- var oilt = (oilt_target - envtemp.getValue()) * (1-oilv) + envtemp.getValue();
- engine.getNode("oiltempc").setValue(oilt);
- var oilp = ((MAX_OILP - oilp_target) * oilv) + oilp_target;
- engine.getNode("oil-press").setValue(oilp);
- engine.getNode("oil-visc").setValue(oilv + OILV_DELTA_COOLDN);
- }
- } # end engines-not-running
- settimer(engine_loop, UPDATE_INTERVAL);
- }
- var engine_setup = func {
- # Set engines to ambient temp on startup
- engines[0].getNode("cyltempc").setValue(envtemp.getValue());
- engines[0].getNode("oiltempc").setValue(envtemp.getValue());
- # Uncomment to pre-warm engines for testing:
- #engines[0].getNode("cyltempc").setValue((TARGET_CYLT_HIGH-TARGET_CYLT_LOW)/2+TARGET_CYLT_LOW);
- engine_update();
- }
- var EngineInit = func {
- settimer(engine_setup, 1); # Give a few seconds for environment vars to initialize
- }
|