Howto:A system engine in Nasal

From FlightGear wiki
Jump to navigation Jump to search

This page describes an engine for aircraft systems, or rather for staging an impression of such a system. It doesn’t handle potentials, pressures or Kirchhoff's laws. It checks if switches are on or off and if minimum criterions are met.


The system is defined in a text-file with rows of entries, connections. Each connection is a comma separated list consisting of: depends,limit,in,out,off,ramp

depends: The output depends on the value of this property. Must be of type number or boolean.

limit: A value or property that sets the lower limit. If depends is greater than limit then the state will be considered on.

in: A property or value if the state is on. If in is set to . the depends value is copied to out regardless of state.

out: The output property. Set to in if the state is on and off if not.

off: A value or property that out is set to if the state is off.

ramp: max change in out per second. Unlimited if 0.


Examples

An example:

engines/engine[0]/n1,27,1,systems/electrical/generator_enabled,0,0
systems/electrical/generator_enabled,0,controls/electric/engine/generator,systems/electrical/generator_on,0,0
systems/electrical/generator_on,0,200,systems/electrical/outputs/main_ac,0,0
systems/electrical/generator_on,0,45,systems/electrical/outputs/inst_ac,0,0
systems/electrical/generator_on,0,29,systems/electrical/battery_voltage,24,0
controls/electric/battery-switch,0,systems/electrical/battery_voltage,systems/electrical/outputs/battery,0,0
systems/electrical/outputs/battery,0,systems/electrical/outputs/main_ac,systems/electrical/outputs/fuel,0,0
systems/electrical/battery_voltage,0,.,systems/electrical/outputs/comm[0],0,0
systems/electrical/outputs/battery,0,.,systems/electrical/outputs/turn-coordinator,0,0
systems/electrical/outputs/battery,0,.,systems/electrical/outputs/nav[0],0,0
systems/electrical/outputs/battery,0,.,systems/electrical/outputs/gps,0,0
systems/electrical/outputs/inst_ac,0,.,systems/electrical/outputs/adf,0,0

The first two rows creates a "and-effect". If n1 is larger than 27% and the generator cockpit switch is on, then the property systems/electrical/generator_on is set to 1 otherwise 0. This simulates an automatic disconnect of the generator at engine start or stop.

The next two rows sets the two AC circuits voltage. Both are fed by the generator.

The fifth row sets the battery voltage to 29 V if charged by the generator and 24 if not.

The next row simulates the main battery switch in the cockpit. The property systems/electrical/outputs/battery is the voltage of the main battery bus that feeds most instruments.

The seventh row is another "and-effect". The fuel measurement systems need both battery power and AC power to work.

The rest of the rows copies the main battery bus output to the properties needed for some of the standard FG instrument implementations.


To simulate pumps or other parts that takes time to go from one state to another ramp is useful:

systems/electrical/outputs/main_ac,0,5,systems/hydraulic/pumps/pump1,0,2.5

The above simulates the hydraulic pump1 go from 0 to 5 (or 5 to 0) in 2 seconds when the main AC is switched on (or off).


An "or-effect" can be created by combining two connections like this:

systems/electrical/generator_on,0,200,systems/electrical/outputs/gen_ac,0,0
systems/electrical/emergency_gen_on,0,200,systems/electrical/outputs/main_ac,systems/electrical/outputs/gen_ac,0

This makes the main AC bus supplied by either the generator or the emergency generator (or both).


The code

# Potemkin system

# Connections
#dep: depends on this property.
#limit: value or property, lower limit for true. dep > limit eq. true
#in: input property eg. orientation/pitch-deg or number constant. 
#    Use . to copy dep value to out.
#out output property eg.instruments/ai/spin
#off: value if not dep true, can be a property to be read
#ramp: max change per second of out, 0 no limit

var Connection = {
  new : func(cv) {
    var m = {parents:[Connection] };
    m.dep=cv[0];
    m.limit=cv[1];
    m.in=cv[2];
    m.out=cv[3];
    m.off=cv[4];
    m.ramp=num(cv[5]);
    return m;
  },
  
};

var System_P = {

  new : func(system_file) {
    var m = {parents:[System_P] };
    m.file=system_file;
    m.connections = [];
    m.verbose = 2;
    m.running=0;
    m.dt=0;
    m.oldtime=0;
    return m;
    },

  read_connections : func {
    if (me.verbose > 0) print("Reading connections");
    var fh = io.open(getprop("/sim/aircraft-dir")~"/"~me.file, "r");
    var line="";
    while (line != nil) {
      line = io.readln(fh);
      if (line != nil) {
        var c_arr=split(",", line);
        if (size(c_arr) == 6) {
          append(me.connections, Connection.new(c_arr));
          if (me.verbose > 1) print("Adding: "~line);
        } else if (me.verbose > 1) print("Skipping: "~line);
      }
    }
    io.close(fh); 
    if (me.verbose > 0) print("Read connections");
  },

  change_value : func(prop, value, ramp) {
    if (ramp == 0) setprop(prop, value);
    else {
      var ov=num(getprop(prop));
      if (ov<value and value-ov > ramp*me.dt) setprop(prop, ov+ramp*me.dt);
      else if (ov>value and ov-value > ramp*me.dt) setprop(prop, ov-ramp*me.dt);
      else setprop(prop, value);
    }
  },

  update : func {
    if (!me.running) return;
    var time=getprop("/sim/time/elapsed-sec");
    me.dt= time-me.oldtime;
    foreach (con; me.connections) {
      dp=getprop(con.dep);
      if (dp != nil) {
        if (num(con.limit) != nil) limit=num(con.limit); else limit=getprop(con.limit);
        if (con.in == ".") me.change_value(con.out, dp, con.ramp); #copy dep value to out
        else if (dp <= limit) {
            if (num(con.off) != nil) me.change_value(con.out, num(con.off), con.ramp);
            else me.change_value(con.out, getprop(con.off), con.ramp);
        } else {
          if (num(con.in) != nil) me.change_value(con.out, num(con.in), con.ramp);
          else me.change_value(con.out, getprop(con.in), con.ramp);
        }
      }
    }
    me.oldtime=time;
    settimer( func me.update(), 0.05);    
  },

  init : func {
    me.read_connections();
    foreach (con; me.connections) {
      if (num(con.off) != nil) setprop(con.out, num(con.off));
      else setprop(con.out, getprop(con.off));
    }
    me.running=1;
    me.oldtime= getprop("/sim/time/elapsed-sec");
    if (me.verbose > 0) print("Initialized system");
    me.update();
  },
};


Use the system by first creating the system with a reference to the connection file and then init it, eg:

var el = System_P.new("Systems/electric.txt");
el.init();