123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225 |
- # Optica
- #
- # Fuel Update routines
- #
- # The Optica has two 33 gallon tanks located in the leading edge of the wings forward of the flaps.
- # There is no sump tank, at least none listed on certification sheets.
- # Control is via a single tank selection swtich having the positions Left, Right, and Off. There is
- # no setting for Both. One documented procedure was to feed from one tank for half an hour, then
- # switch over to the other tank for half an hour, etc.
- #
- # Gary Neely aka 'Buckaroo'
- #
- # Standard fuel densities:
- # JetA 6.72 (6.47-7.01) lb/gal, 0.775-0.840 kg/l @15C
- # Avgas 6.02 lb/gal, 0.721 kg/l @15C
- #
- var FUEL_UPDATE = 0.3; # Update interval in secs for main fuel routine
- var FUEL_UPDATE_LONG = 10; # Update interval if fuel state frozen
- var PPG = 6.02; # Standard fuel density
- var MAX_TANKS = 1; # Standard tanks to consider, 0..MAX_TANKS
- var FUEL_PRESS_MIN = 12; # Minimum fuel pressure requirements
- var FUEL_PRESS_TARGET = 35; # Best fuel pressure
- var tank_list = [];
- var engine = nil;
- var fuel_freeze = nil;
- var total_gals = nil;
- var total_lbs = nil;
- var total_norm = nil;
- var selected_tank = props.globals.getNode("/controls/fuel/selected-tank");
- var fuel_pump = props.globals.getNode("/systems/electrical/outputs/fuelpump");
- # Map tank selector switch to tanks
- # -1 = off, 0 = left, 1 = right
- var tank_switch = func(i) { # i: inc/dec indicator
- var s = selected_tank.getValue();
- if (i > 0) { # Move selector knob counter-clockwise
- if (s == -1) { s = 1; } # off moves to right
- elsif (s == 1) { s = 0; } # right moves to left
- }
- else { # Move selector clockwise
- if (s == 0) { s = 1; } # left moves to right
- elsif (s == 1) { s = -1; } # right moves to off
- }
- selected_tank.setValue(s);
- }
- var fuel_update_loop = func { # Subtract consumed fuel from tanks
- if (fuel_freeze) { # Fuel simulation frozen, engines consume no fuel
- engine.getNode("fuel-consumed-lbs").setDoubleValue(0); # Reset engine's consumed fuel
- settimer(fuel_update_loop, FUEL_UPDATE_LONG); # Update leisurely
- return 0;
- }
- var selected = selected_tank.getValue(); # Get tank selection & fuel cutoff status
- # Fuel pressure:
- # A Lycoming 260 has a mechanical pump and is typically provided
- # with an electric boost pump. The mechanical pump is always on
- # (unless it fails), and the electric pump is effectively a back-up,
- # but is likely required for cold-start priming of the system.
- var fp = engine.getNode("fuel-press").getValue();
- if (fp > 0) { # Fuel pressure present:
- # Kill pressure if fuel off
- if (selected == -1) {
- interpolate(engine.getNode("fuel-press"), 0, (fp/FUEL_PRESS_TARGET) * 10);
- }
- # Kill pressure if selected tank is empty
- elsif (tank_list[selected].getChild("level-gal_us").getValue() == 0 ) {
- interpolate(engine.getNode("fuel-press"), 0, (fp/FUEL_PRESS_TARGET) * 10);
- }
- # Kill pressure if no pumps active
- elsif (!fuel_pump.getValue() and !engine.getNode("running").getValue()) {
- interpolate(engine.getNode("fuel-press"), 0, (fp/FUEL_PRESS_TARGET) * 45);
- }
- elsif (fp < FUEL_PRESS_TARGET) { # All good, make sure pressure is rising
- interpolate(engine.getNode("fuel-press"), FUEL_PRESS_TARGET, (FUEL_PRESS_TARGET - fp)/FUEL_PRESS_TARGET * 3)
- }
- }
- else { # No fuel pressure:
- # Good fuel pressure if fueled and 1+ pump active
- if (selected > -1 and
- tank_list[selected].getChild("level-gal_us").getValue() > 0 and
- (fuel_pump.getValue() or engine.getNode("running").getValue())) {
- interpolate(engine.getNode("fuel-press"), FUEL_PRESS_TARGET, (FUEL_PRESS_TARGET - fp)/FUEL_PRESS_TARGET * 3);
- }
- }
- # Fuel consumption:
- if (engine.getNode("fuel-press").getValue() < FUEL_PRESS_MIN) { # No fuel pressure
- engine.getNode("out-of-fuel").setBoolValue(1); # Kill engine
- }
- elsif (selected == -1) { # Switch set to fuel cutoff position
- engine.getNode("out-of-fuel").setBoolValue(1); # Kill engine
- }
- else { # Attempt to draw fuel from a tank:
- var consumed = engine.getNode("fuel-consumed-lbs").getValue(); # Fuel consumed in lbs from FDM calculation
- # Get tank's initial fuel load in lbs:
- #var tank_lbs = tank_list[selected].getChild("level-gal_us").getValue() * PPG; # Deprecated by FG 2.4
- var tank_lbs = tank_list[selected].getChild("level-lbs").getValue();
- # Update engine's OOF status based on fuel pressure
- if (engine.getNode("out-of-fuel").getValue() and
- tank_lbs > 0 and
- engine.getNode("fuel-press").getValue() >= FUEL_PRESS_MIN) {
- engine.getNode("out-of-fuel").setBoolValue(0); # Reset fueled status
- }
- if (engine.getNode("running").getValue()) {
- var satisfied = 0; # Value of 1 will indicate engine's fuel needs met
- # Subtract consumed fuel from tank. We might get a little freebie fuel
- # usage when a tank is almost empty, but it will be very small.
- tank_lbs -= consumed;
- if (tank_lbs < 0) { # Test for empty tank; fuel usage was not satisfied
- tank_lbs = 0; # Reset tank as empty (no negative fuel values)
- }
- else {
- satisfied = 1; # Tank satisfied fuel needs
- }
- # Update tank properties
- #tank_list[selected].getChild("level-gal_us").setDoubleValue(tank_lbs/PPG); # Deprecated by FG 2.4
- tank_list[selected].getChild("level-lbs").setDoubleValue(tank_lbs);
- if (!satisfied) { # Engine's fuel needs met?
- engine.getNode("out-of-fuel").setBoolValue(1); # If not, kill engine
- }
- }
- }
- engine.getNode("fuel-consumed-lbs").setDoubleValue(0); # Reset engine's consumed fuel
- # Total fuel properties
- var lbs = 0;
- var gals = 0;
- var cap = 0;
- for(var i=0; i<=MAX_TANKS; i+=1) {
- lbs += tank_list[i].getNode("level-lbs").getValue();
- gals += tank_list[i].getNode("level-gal_us").getValue();
- cap += tank_list[i].getNode("capacity-gal_us").getValue();
- }
- total_lbs.setDoubleValue(lbs);
- total_gals.setDoubleValue(gals);
- total_norm.setDoubleValue(gals / cap); # Capacity will never reasonably be 0
- settimer(fuel_update_loop, FUEL_UPDATE); # You go back, Jack, do it again...
- }
- var fuel_startup = func {
- # Deal with fuel menu select boxes
- # Note that these are not cutoff valves;
- # Listeners are used to re-enable oof status
- # if the user plays with the selection boxes
- var tank0_select = props.globals.getNode("/consumables/fuel/tank[0]/selected");
- var tank1_select = props.globals.getNode("/consumables/fuel/tank[1]/selected");
-
- setlistener(tank0_select, func {
- if (tank0_select.getValue() or tank1_select.getValue()) { engine.getNode("out-of-fuel").setBoolValue(0); }
- });
- setlistener(tank1_select, func {
- if (tank0_select.getValue() or tank1_select.getValue()) { engine.getNode("out-of-fuel").setBoolValue(0); }
- });
- # Reset oof on tank selection:
- setlistener("/controls/fuel/selected-tank", func() {
- if (selected_tank.getValue() > -1 and
- (tank_list[0].getChild("level-gal_us").getValue() or
- tank_list[1].getChild("level-gal_us").getValue()))
- { engine.getNode("out-of-fuel").setBoolValue(0); }
- });
- fuel_update_loop(); # Initiate fuel update sequence
- }
- # Support for clean property initialization
- # For backward compatibility; FG 2+ has a better method
- var init_double_prop = func(node, prop, val) {
- if (node.getNode(prop) != nil) {
- val = num(node.getNode(prop).getValue());
- }
- node.getNode(prop,1).setDoubleValue(val);
- }
- var FuelInit = func {
- fuel.update = func{}; # Remove default fuel fuel system
- # Listen for sim suspended fuel usage toggle
- setlistener("/sim/freeze/fuel", func(n) { fuel_freeze = n.getBoolValue() }, 1);
- # Set up fuel summary properties
- total_gals = props.globals.getNode("/consumables/fuel/total-fuel-gals",1);
- total_lbs = props.globals.getNode("/consumables/fuel/total-fuel-lbs",1);
- total_norm = props.globals.getNode("/consumables/fuel/total-fuel-norm",1);
- # Set up fuel-related engine properties
- engine = props.globals.getNode("engines/engine[0]");
- engine.getNode("fuel-consumed-lbs",1).setDoubleValue(0);
- engine.getNode("out-of-fuel",1).setBoolValue(1); # Begin with engines shutdown
- # Fetch the tank list:
- tank_list = props.globals.getNode("/consumables/fuel",1).getChildren("tank");
- # Set up tank properties
- # We only need to deal with the tanks that matter (0-MAX_TANKS),
- # the rest are FDM zombie tanks
- for(var i=0; i<=MAX_TANKS; i+=1) {
- init_double_prop(tank_list[i], "level-gal_us", 0);
- init_double_prop(tank_list[i], "level-lbs", 0);
- init_double_prop(tank_list[i], "capacity-gal_us", 0.01); # Not zero (div/zero issue)
- # init_double_prop(tank_list[i], "density-ppg", PPG); # Deprecated by FG 2.4
- if (tank_list[i].getNode("selected") == nil) # This value should always be true
- tank_list[i].getNode("selected",1).setBoolValue(1);
- }
- settimer(fuel_startup, 2); # Delay startup a bit to allow things to initialize
- }
|