Optica_engine.nas 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242
  1. # Edgley Optica
  2. #
  3. # Engine management routines
  4. #
  5. # Gary Neely aka 'Buckaroo'
  6. #
  7. var UPDATE_INTERVAL = 0.2; # Update engines every 0.2 secs
  8. var MAX_RPM = 2200; # Above this the engine starts to accumulate strain
  9. # from over-rev issues if MAX_RPM_T exceeded
  10. var MAX_RPM_CLK = 60; # Secs; Time up to this value doesn't count toward strain
  11. var MAX_IDLE = 1200; # Above this the engine starts to accumulate strain
  12. # from oil viscosity issues
  13. var MAX_STRAIN = 20000; # Arbitrary strain value, when exceeded, engine fails
  14. var OILV_DELTA_WARMUP = 0.002; # Viscosity increment per cycle when warming up
  15. var OILV_DELTA_COOLDN = 0.0002; # Viscosity decrement per cycle when cooling down
  16. var VISC_PENALTY = 40; # Maximum reduction of oil pressure for viscosity
  17. var COLD_OIL_FACTOR = 25; # Arbitrary strain value for running engine over idle when cold;
  18. # this is killer, so keep value small or users will whine
  19. var MAX_OK_OILT = 118; # Maximum best oil temperature
  20. var MIN_OK_OILT = 60; # Minimum best oil temperature
  21. var MIN_OILP = 25; # Minimum acceptable oil pressure
  22. var MAX_OILP = 115; # Highest value oil pressure will register
  23. var MAX_OK_OILP = 95; # Maximum best oil pressure
  24. var MIN_OK_OILP = 55; # Minimum best oil pressure
  25. var CYLT_MAX = 260; # Engine always takes damage above this temp
  26. var TARGET_CYLT_HIGH = 220; # Target best cylinder head temp, high end
  27. # Engine takes damage above this temp after a time interval
  28. var TARGET_CYLT_LOW = 150; # Target best cylinder head temp, low end
  29. var CYLT_MIN = 100; # Low end of operating cylinder head temp
  30. var MAX_CYLT_STRAIN_CLK = 60; # Time in secs that must pass before engine under CYLT_MAX
  31. # but over CYLT_HIGH starts taking damage
  32. var DELTA_T_F = 0.1; # Change in temperature master factor,
  33. # inc to make changes faster, dec to slow temp changes
  34. var DELTA_T_ENV = 0.014; # Change in temp due to ambient air factor
  35. var DELTA_T_THRUST = 0.0075; # Change in temp due to power factor
  36. var DELTA_T_AS = 0.022; # Change in temp due to airspeed-induced air factor
  37. var MIXTURE_CUTOFF = 0.8; # Mixture setting: below this value, no cooling benefits
  38. var MIXTURE_FACTOR = 14; # Mixture cooling benefits multiplier
  39. var OPOH = 50; # Engine operational overhead value
  40. var PROP_AIR = 0.01; # Factor for prop air moving over engine
  41. var AMBIENT_REDUCTION = 0.25; # Factor for reduction of ambient cooling under max operating range
  42. var MIXTURE_PEAK = 0.74; # Observed mixture setting for peak power
  43. var MIXTURE_BIAS = 10; # Max degrees C to augment cyl temp at mixture peak
  44. var ENGINE_MASS = 1; # Engine mass factor; not currently used, but the idea is to
  45. # inc this to slow temperature changes
  46. var eng_checks = props.globals.getNode("/sim/Optica/engine-checks");
  47. var eng_warns = props.globals.getNode("/sim/Optica/engine-warns");
  48. #var tanks = props.globals.getNode("/consumables/fuel").getChildren("tank");
  49. var engine = props.globals.getNode("/engines/engine[0]");
  50. var engcontrols = props.globals.getNode("controls/engines/engine[0]");
  51. var airspeed = props.globals.getNode("/velocities/airspeed-kt");
  52. var envtemp = props.globals.getNode("/environment/temperature-degc");
  53. # Defined under Optica-fuel.nas:
  54. #var fuel_scalar = props.globals.getNode("consumables/fuel/consumption-scalar").getValue();
  55. var oilt_target = (MAX_OK_OILT-MIN_OK_OILT)/2 + MIN_OK_OILT; # Calculate a nice mid-range temp value to seek
  56. var oilp_target = (MAX_OK_OILP-MIN_OK_OILP)/2 + MIN_OK_OILP; # Calculate a nice mid-range pressure value to seek
  57. # Functions for enabling engine options:
  58. var eng_checks_set = func {
  59. if (eng_checks.getValue()) { eng_checks.setValue(0); var mssg = "Engine parameter checks disabled."; }
  60. else { eng_checks.setValue(1); var mssg = "Engine parameter checks enabled."; }
  61. Optica_screenmssg.fg = [1, 1, 1, 1];
  62. Optica_screenmssg.write(mssg);
  63. }
  64. var eng_warns_set = func {
  65. if (eng_warns.getValue()) { eng_warns.setValue(0); var mssg = "Engine parameter warnings disabled."; }
  66. else { eng_warns.setValue(1); var mssg = "Engine parameter warnings enabled."; }
  67. Optica_screenmssg.fg = [1, 1, 1, 1];
  68. Optica_screenmssg.write(mssg);
  69. }
  70. # Engine warning messages:
  71. var eng_checks_overrev = func {
  72. var mssg = "Engine over-rev warning.";
  73. Optica_screenmssg.fg = [1, 0.5, 0, 1];
  74. Optica_screenmssg.write(mssg);
  75. }
  76. var eng_checks_overheat = func {
  77. var mssg = "Engine over-heating warning.";
  78. Optica_screenmssg.fg = [1, 0.5, 0, 1];
  79. Optica_screenmssg.write(mssg);
  80. }
  81. var eng_checks_oilcold = func {
  82. var mssg = "Engine oil temperature warning.";
  83. Optica_screenmssg.fg = [1, 0.5, 0, 1];
  84. Optica_screenmssg.write(mssg);
  85. }
  86. var eng_checks_damage = func(y) {
  87. var mssg = "Engine has passed 25% damage.";
  88. if (y > MAX_STRAIN * 0.9) { mssg = "Engine is critical."; }
  89. elsif (y > MAX_STRAIN * 0.75) { mssg = "Engine has passed 75% damage."; }
  90. elsif (y > MAX_STRAIN * 0.5) { mssg = "Engine has passed 50% damage."; }
  91. Optica_screenmssg2.fg = [1, 0.5, 0, 1];
  92. Optica_screenmssg2.write(mssg);
  93. }
  94. var eng_checks_failure = func {
  95. var mssg = "Engine failure.";
  96. Optica_screenmssg2.fg = [1, 0, 0, 1];
  97. Optica_screenmssg2.write(mssg);
  98. }
  99. var FtoC = func (f) { # Fahrenheit to Celsius
  100. return (f-32) * 5 / 9;
  101. }
  102. # Primary engine loop:
  103. var engine_update = func {
  104. if (engine.getNode("running").getValue() == 1) { # Is engine running?
  105. # Simplified oil temp, viscosity, pressure
  106. # Note that viscosity is a dimensionless normalized value,
  107. # not a true viscosity number
  108. var oilv = engine.getNode("oil-visc").getValue();
  109. if (oilv > 0) {
  110. oilv = oilv - OILV_DELTA_WARMUP; # Determine new oilv as necessary
  111. engine.getNode("oil-visc").setValue(oilv); # Save viscosity
  112. var oilp = ((MAX_OILP - oilp_target) * oilv) + oilp_target;# Viscosity determines position between max oilp and target best oilp
  113. engine.getNode("oil-press").setValue(oilp); # Save oilp, probably not necessary to interpolate
  114. # Oilt rises from ambient as viscosity falls
  115. var oilt = (oilt_target - envtemp.getValue()) * (1-oilv) + envtemp.getValue();
  116. engine.getNode("oiltempc").setValue(oilt); # Save oilt as deg C
  117. # Oil pressure check:
  118. if (oilp > MAX_OK_OILP and engine.getNode("rpm").getValue() > MAX_IDLE) {
  119. if (eng_checks.getValue()) {
  120. engine.getNode("strain").setValue(engine.getNode("strain").getValue() + (COLD_OIL_FACTOR/oilv));
  121. }
  122. if (eng_warns.getValue()) { eng_checks_oilcold(i); } # Notice to user
  123. }
  124. }
  125. # Cyl head temp change calculations:
  126. # Currently this accounts for environmental cooling,
  127. # airflow cooling, and thrust/rpm heating.
  128. var cyl_temp = engine.getNode("cyltempc").getValue();
  129. var thrust = engine.getNode("prop-thrust").getValue();
  130. var mixture = engcontrols.getNode("mixture").getValue();
  131. var throttle = engcontrols.getNode("throttle").getValue();
  132. var dT_env = (envtemp.getValue() - cyl_temp) * DELTA_T_ENV;
  133. var dT_thrust = (thrust + OPOH) * DELTA_T_THRUST;
  134. var dT_airspeed = airspeed.getValue() * DELTA_T_AS;
  135. var dT_mixture = mixture - MIXTURE_CUTOFF;
  136. if (dT_mixture < 0) { dT_mixture = 0; } # DeltaT due to rich mixture benefits
  137. else { dT_mixture = dT_mixture * MIXTURE_FACTOR; }
  138. if (cyl_temp <= TARGET_CYLT_LOW) { # When below best operating low temp:
  139. dT_env = dT_env * AMBIENT_REDUCTION; # DeltaT due to ambient reduction benefits
  140. dT_airspeed = 0; # No airspeed benefits below best operating temp low range
  141. # Linearly reduce mixture benefits down to ambient temp
  142. dT_mixture = 0; # No significant mixture benefits in this range
  143. }
  144. elsif (cyl_temp < TARGET_CYLT_HIGH) { # When within best operating temp range:
  145. # Normalize temperature range:
  146. var temp_norm = (cyl_temp - TARGET_CYLT_LOW) / (TARGET_CYLT_HIGH - TARGET_CYLT_LOW);
  147. # Linearly reduce ambient benefits from full to AMBIENT_REDUCTION over range
  148. dT_env = dT_env * (AMBIENT_REDUCTION + (AMBIENT_REDUCTION * temp_norm));
  149. # Linearly reduce airspeed benefits from full to none
  150. dT_airspeed = dT_airspeed * temp_norm;
  151. # Linearly reduce mixture benefits from full to none
  152. if (dT_mixture > 0) { dT_mixture = dT_mixture * temp_norm; }
  153. }
  154. var delta_cyl_temp = (dT_env + dT_thrust - dT_airspeed - dT_mixture) * DELTA_T_F;
  155. cyl_temp = cyl_temp + delta_cyl_temp;
  156. engine.getNode("cyltempc").setValue(cyl_temp);
  157. # For testing and debugging:
  158. #engine.getNode("cyl-dt").setValue(delta_cyl_temp);
  159. #engine.getNode("cyl-dte").setValue(dT_env);
  160. #engine.getNode("cyl-dtt").setValue(dT_thrust);
  161. #engine.getNode("cyl-dta").setValue(dT_airspeed);
  162. #engine.getNode("cyl-dtm").setValue(dT_mixture);
  163. }
  164. else { # Engine not running
  165. # Cyl temp cool-down:
  166. var cyl_temp = engine.getNode("cyltempc").getValue();
  167. var dT_env = (envtemp.getValue() - cyl_temp) * DELTA_T_ENV;
  168. var dT_airspeed = airspeed.getValue() * DELTA_T_AS;
  169. if (cyl_temp <= TARGET_CYLT_LOW) { # When below best operating low temp:
  170. dT_env = dT_env * AMBIENT_REDUCTION; # Reduce ambient cooling
  171. dT_airspeed = 0; # No airspeed benefits below best operating temp low range
  172. }
  173. elsif (cyl_temp < TARGET_CYLT_HIGH) { # When withing best operating temp range:
  174. # Normalize range:
  175. var temp_norm = (cyl_temp - TARGET_CYLT_LOW) / (TARGET_CYLT_HIGH - TARGET_CYLT_LOW);
  176. # Linearly reduce ambient benefits from full to AMBIENT_REDUCTION over range
  177. dT_env = dT_env * (AMBIENT_REDUCTION + (AMBIENT_REDUCTION * temp_norm));
  178. dT_airspeed = dT_airspeed * temp_norm; # Linearly reduce airspeed benefits from full to none
  179. }
  180. var delta_cyl_temp = (dT_env - dT_airspeed) * DELTA_T_F;
  181. cyl_temp = cyl_temp + delta_cyl_temp;
  182. engine.getNode("cyltempc").setValue(cyl_temp);
  183. engine.getNode("cyltempc-biased").setValue(cyl_temp);
  184. # Oil cool-down; see main oil section for notes
  185. var oilv = engine.getNode("oil-visc").getValue();
  186. if (oilv < 1) {
  187. var oilt = (oilt_target - envtemp.getValue()) * (1-oilv) + envtemp.getValue();
  188. engine.getNode("oiltempc").setValue(oilt);
  189. var oilp = ((MAX_OILP - oilp_target) * oilv) + oilp_target;
  190. engine.getNode("oil-press").setValue(oilp);
  191. engine.getNode("oil-visc").setValue(oilv + OILV_DELTA_COOLDN);
  192. }
  193. } # end engines-not-running
  194. settimer(engine_update, UPDATE_INTERVAL);
  195. }
  196. var engine_setup = func {
  197. # Set engines to ambient temp on startup
  198. engine.getNode("cyltempc").setValue(envtemp.getValue());
  199. engine.getNode("oiltempc").setValue(envtemp.getValue());
  200. # Uncomment to pre-warm engines for testing:
  201. #engine.getNode("cyltempc").setValue((TARGET_CYLT_HIGH-TARGET_CYLT_LOW)/2+TARGET_CYLT_LOW);
  202. engine_update();
  203. }
  204. var EngineInit = func {
  205. settimer(engine_setup, 1); # Give a few seconds for environment vars to initialize
  206. }