Optica_fuel.nas 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225
  1. # Optica
  2. #
  3. # Fuel Update routines
  4. #
  5. # The Optica has two 33 gallon tanks located in the leading edge of the wings forward of the flaps.
  6. # There is no sump tank, at least none listed on certification sheets.
  7. # Control is via a single tank selection swtich having the positions Left, Right, and Off. There is
  8. # no setting for Both. One documented procedure was to feed from one tank for half an hour, then
  9. # switch over to the other tank for half an hour, etc.
  10. #
  11. # Gary Neely aka 'Buckaroo'
  12. #
  13. # Standard fuel densities:
  14. # JetA 6.72 (6.47-7.01) lb/gal, 0.775-0.840 kg/l @15C
  15. # Avgas 6.02 lb/gal, 0.721 kg/l @15C
  16. #
  17. var FUEL_UPDATE = 0.3; # Update interval in secs for main fuel routine
  18. var FUEL_UPDATE_LONG = 10; # Update interval if fuel state frozen
  19. var PPG = 6.02; # Standard fuel density
  20. var MAX_TANKS = 1; # Standard tanks to consider, 0..MAX_TANKS
  21. var FUEL_PRESS_MIN = 12; # Minimum fuel pressure requirements
  22. var FUEL_PRESS_TARGET = 35; # Best fuel pressure
  23. var tank_list = [];
  24. var engine = nil;
  25. var fuel_freeze = nil;
  26. var total_gals = nil;
  27. var total_lbs = nil;
  28. var total_norm = nil;
  29. var selected_tank = props.globals.getNode("/controls/fuel/selected-tank");
  30. var fuel_pump = props.globals.getNode("/systems/electrical/outputs/fuelpump");
  31. # Map tank selector switch to tanks
  32. # -1 = off, 0 = left, 1 = right
  33. var tank_switch = func(i) { # i: inc/dec indicator
  34. var s = selected_tank.getValue();
  35. if (i > 0) { # Move selector knob counter-clockwise
  36. if (s == -1) { s = 1; } # off moves to right
  37. elsif (s == 1) { s = 0; } # right moves to left
  38. }
  39. else { # Move selector clockwise
  40. if (s == 0) { s = 1; } # left moves to right
  41. elsif (s == 1) { s = -1; } # right moves to off
  42. }
  43. selected_tank.setValue(s);
  44. }
  45. var fuel_update_loop = func { # Subtract consumed fuel from tanks
  46. if (fuel_freeze) { # Fuel simulation frozen, engines consume no fuel
  47. engine.getNode("fuel-consumed-lbs").setDoubleValue(0); # Reset engine's consumed fuel
  48. settimer(fuel_update_loop, FUEL_UPDATE_LONG); # Update leisurely
  49. return 0;
  50. }
  51. var selected = selected_tank.getValue(); # Get tank selection & fuel cutoff status
  52. # Fuel pressure:
  53. # A Lycoming 260 has a mechanical pump and is typically provided
  54. # with an electric boost pump. The mechanical pump is always on
  55. # (unless it fails), and the electric pump is effectively a back-up,
  56. # but is likely required for cold-start priming of the system.
  57. var fp = engine.getNode("fuel-press").getValue();
  58. if (fp > 0) { # Fuel pressure present:
  59. # Kill pressure if fuel off
  60. if (selected == -1) {
  61. interpolate(engine.getNode("fuel-press"), 0, (fp/FUEL_PRESS_TARGET) * 10);
  62. }
  63. # Kill pressure if selected tank is empty
  64. elsif (tank_list[selected].getChild("level-gal_us").getValue() == 0 ) {
  65. interpolate(engine.getNode("fuel-press"), 0, (fp/FUEL_PRESS_TARGET) * 10);
  66. }
  67. # Kill pressure if no pumps active
  68. elsif (!fuel_pump.getValue() and !engine.getNode("running").getValue()) {
  69. interpolate(engine.getNode("fuel-press"), 0, (fp/FUEL_PRESS_TARGET) * 45);
  70. }
  71. elsif (fp < FUEL_PRESS_TARGET) { # All good, make sure pressure is rising
  72. interpolate(engine.getNode("fuel-press"), FUEL_PRESS_TARGET, (FUEL_PRESS_TARGET - fp)/FUEL_PRESS_TARGET * 3)
  73. }
  74. }
  75. else { # No fuel pressure:
  76. # Good fuel pressure if fueled and 1+ pump active
  77. if (selected > -1 and
  78. tank_list[selected].getChild("level-gal_us").getValue() > 0 and
  79. (fuel_pump.getValue() or engine.getNode("running").getValue())) {
  80. interpolate(engine.getNode("fuel-press"), FUEL_PRESS_TARGET, (FUEL_PRESS_TARGET - fp)/FUEL_PRESS_TARGET * 3);
  81. }
  82. }
  83. # Fuel consumption:
  84. if (engine.getNode("fuel-press").getValue() < FUEL_PRESS_MIN) { # No fuel pressure
  85. engine.getNode("out-of-fuel").setBoolValue(1); # Kill engine
  86. }
  87. elsif (selected == -1) { # Switch set to fuel cutoff position
  88. engine.getNode("out-of-fuel").setBoolValue(1); # Kill engine
  89. }
  90. else { # Attempt to draw fuel from a tank:
  91. var consumed = engine.getNode("fuel-consumed-lbs").getValue(); # Fuel consumed in lbs from FDM calculation
  92. # Get tank's initial fuel load in lbs:
  93. #var tank_lbs = tank_list[selected].getChild("level-gal_us").getValue() * PPG; # Deprecated by FG 2.4
  94. var tank_lbs = tank_list[selected].getChild("level-lbs").getValue();
  95. # Update engine's OOF status based on fuel pressure
  96. if (engine.getNode("out-of-fuel").getValue() and
  97. tank_lbs > 0 and
  98. engine.getNode("fuel-press").getValue() >= FUEL_PRESS_MIN) {
  99. engine.getNode("out-of-fuel").setBoolValue(0); # Reset fueled status
  100. }
  101. if (engine.getNode("running").getValue()) {
  102. var satisfied = 0; # Value of 1 will indicate engine's fuel needs met
  103. # Subtract consumed fuel from tank. We might get a little freebie fuel
  104. # usage when a tank is almost empty, but it will be very small.
  105. tank_lbs -= consumed;
  106. if (tank_lbs < 0) { # Test for empty tank; fuel usage was not satisfied
  107. tank_lbs = 0; # Reset tank as empty (no negative fuel values)
  108. }
  109. else {
  110. satisfied = 1; # Tank satisfied fuel needs
  111. }
  112. # Update tank properties
  113. #tank_list[selected].getChild("level-gal_us").setDoubleValue(tank_lbs/PPG); # Deprecated by FG 2.4
  114. tank_list[selected].getChild("level-lbs").setDoubleValue(tank_lbs);
  115. if (!satisfied) { # Engine's fuel needs met?
  116. engine.getNode("out-of-fuel").setBoolValue(1); # If not, kill engine
  117. }
  118. }
  119. }
  120. engine.getNode("fuel-consumed-lbs").setDoubleValue(0); # Reset engine's consumed fuel
  121. # Total fuel properties
  122. var lbs = 0;
  123. var gals = 0;
  124. var cap = 0;
  125. for(var i=0; i<=MAX_TANKS; i+=1) {
  126. lbs += tank_list[i].getNode("level-lbs").getValue();
  127. gals += tank_list[i].getNode("level-gal_us").getValue();
  128. cap += tank_list[i].getNode("capacity-gal_us").getValue();
  129. }
  130. total_lbs.setDoubleValue(lbs);
  131. total_gals.setDoubleValue(gals);
  132. total_norm.setDoubleValue(gals / cap); # Capacity will never reasonably be 0
  133. settimer(fuel_update_loop, FUEL_UPDATE); # You go back, Jack, do it again...
  134. }
  135. var fuel_startup = func {
  136. # Deal with fuel menu select boxes
  137. # Note that these are not cutoff valves;
  138. # Listeners are used to re-enable oof status
  139. # if the user plays with the selection boxes
  140. var tank0_select = props.globals.getNode("/consumables/fuel/tank[0]/selected");
  141. var tank1_select = props.globals.getNode("/consumables/fuel/tank[1]/selected");
  142. setlistener(tank0_select, func {
  143. if (tank0_select.getValue() or tank1_select.getValue()) { engine.getNode("out-of-fuel").setBoolValue(0); }
  144. });
  145. setlistener(tank1_select, func {
  146. if (tank0_select.getValue() or tank1_select.getValue()) { engine.getNode("out-of-fuel").setBoolValue(0); }
  147. });
  148. # Reset oof on tank selection:
  149. setlistener("/controls/fuel/selected-tank", func() {
  150. if (selected_tank.getValue() > -1 and
  151. (tank_list[0].getChild("level-gal_us").getValue() or
  152. tank_list[1].getChild("level-gal_us").getValue()))
  153. { engine.getNode("out-of-fuel").setBoolValue(0); }
  154. });
  155. fuel_update_loop(); # Initiate fuel update sequence
  156. }
  157. # Support for clean property initialization
  158. # For backward compatibility; FG 2+ has a better method
  159. var init_double_prop = func(node, prop, val) {
  160. if (node.getNode(prop) != nil) {
  161. val = num(node.getNode(prop).getValue());
  162. }
  163. node.getNode(prop,1).setDoubleValue(val);
  164. }
  165. var FuelInit = func {
  166. fuel.update = func{}; # Remove default fuel fuel system
  167. # Listen for sim suspended fuel usage toggle
  168. setlistener("/sim/freeze/fuel", func(n) { fuel_freeze = n.getBoolValue() }, 1);
  169. # Set up fuel summary properties
  170. total_gals = props.globals.getNode("/consumables/fuel/total-fuel-gals",1);
  171. total_lbs = props.globals.getNode("/consumables/fuel/total-fuel-lbs",1);
  172. total_norm = props.globals.getNode("/consumables/fuel/total-fuel-norm",1);
  173. # Set up fuel-related engine properties
  174. engine = props.globals.getNode("engines/engine[0]");
  175. engine.getNode("fuel-consumed-lbs",1).setDoubleValue(0);
  176. engine.getNode("out-of-fuel",1).setBoolValue(1); # Begin with engines shutdown
  177. # Fetch the tank list:
  178. tank_list = props.globals.getNode("/consumables/fuel",1).getChildren("tank");
  179. # Set up tank properties
  180. # We only need to deal with the tanks that matter (0-MAX_TANKS),
  181. # the rest are FDM zombie tanks
  182. for(var i=0; i<=MAX_TANKS; i+=1) {
  183. init_double_prop(tank_list[i], "level-gal_us", 0);
  184. init_double_prop(tank_list[i], "level-lbs", 0);
  185. init_double_prop(tank_list[i], "capacity-gal_us", 0.01); # Not zero (div/zero issue)
  186. # init_double_prop(tank_list[i], "density-ppg", PPG); # Deprecated by FG 2.4
  187. if (tank_list[i].getNode("selected") == nil) # This value should always be true
  188. tank_list[i].getNode("selected",1).setBoolValue(1);
  189. }
  190. settimer(fuel_startup, 2); # Delay startup a bit to allow things to initialize
  191. }