2008-10-11 23:17:23 +00:00
|
|
|
##
|
|
|
|
# Procedural model of a Cessna 172S electrical system. Includes a
|
|
|
|
# preliminary battery charge/discharge model and realistic ammeter
|
|
|
|
# gauge modeling.
|
|
|
|
#
|
|
|
|
|
|
|
|
|
|
|
|
##
|
|
|
|
# Initialize internal values
|
|
|
|
#
|
|
|
|
|
|
|
|
battery = nil;
|
|
|
|
alternator = nil;
|
|
|
|
|
|
|
|
last_time = 0.0;
|
|
|
|
|
|
|
|
vbus_volts = 0.0;
|
|
|
|
ebus1_volts = 0.0;
|
|
|
|
ebus2_volts = 0.0;
|
|
|
|
|
|
|
|
ammeter_ave = 0.0;
|
|
|
|
|
|
|
|
##
|
|
|
|
# Initialize the electrical system
|
|
|
|
#
|
|
|
|
|
|
|
|
init_electrical = func {
|
|
|
|
print("Initializing Nasal Electrical System");
|
|
|
|
battery = BatteryClass.new();
|
|
|
|
alternator = AlternatorClass.new();
|
|
|
|
|
|
|
|
# set initial switch positions
|
|
|
|
setprop("/controls/engines/engine[0]/master-bat", 1);
|
|
|
|
setprop("/controls/engines/engine[0]/master-alt", 1);
|
|
|
|
setprop("/controls/switches/master-avionics", 1);
|
|
|
|
setprop("/systems/electrical/outputs/autopilot",0.0);
|
|
|
|
|
|
|
|
# Request that the update fuction be called next frame
|
|
|
|
settimer(update_electrical, 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
##
|
|
|
|
# Battery model class.
|
|
|
|
#
|
|
|
|
|
|
|
|
BatteryClass = {};
|
|
|
|
|
|
|
|
BatteryClass.new = func {
|
|
|
|
obj = { parents : [BatteryClass],
|
|
|
|
ideal_volts : 24.0,
|
|
|
|
ideal_amps : 30.0,
|
|
|
|
amp_hours : 12.75,
|
|
|
|
charge_percent : 1.0,
|
|
|
|
charge_amps : 7.0 };
|
|
|
|
return obj;
|
|
|
|
}
|
|
|
|
|
|
|
|
##
|
|
|
|
# Passing in positive amps means the battery will be discharged.
|
|
|
|
# Negative amps indicates a battery charge.
|
|
|
|
#
|
|
|
|
|
|
|
|
BatteryClass.apply_load = func( amps, dt ) {
|
|
|
|
amphrs_used = amps * dt / 3600.0;
|
|
|
|
percent_used = amphrs_used / me.amp_hours;
|
|
|
|
me.charge_percent -= percent_used;
|
|
|
|
if ( me.charge_percent < 0.0 ) {
|
|
|
|
me.charge_percent = 0.0;
|
|
|
|
} elsif ( me.charge_percent > 1.0 ) {
|
|
|
|
me.charge_percent = 1.0;
|
|
|
|
}
|
|
|
|
# print( "battery percent = ", me.charge_percent);
|
|
|
|
return me.amp_hours * me.charge_percent;
|
|
|
|
}
|
|
|
|
|
|
|
|
##
|
|
|
|
# Return output volts based on percent charged. Currently based on a simple
|
|
|
|
# polynomal percent charge vs. volts function.
|
|
|
|
#
|
|
|
|
|
|
|
|
BatteryClass.get_output_volts = func {
|
|
|
|
x = 1.0 - me.charge_percent;
|
|
|
|
tmp = -(3.0 * x - 1.0);
|
|
|
|
factor = (tmp*tmp*tmp*tmp*tmp + 32) / 32;
|
|
|
|
return me.ideal_volts * factor;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
##
|
|
|
|
# Return output amps available. This function is totally wrong and should be
|
|
|
|
# fixed at some point with a more sensible function based on charge percent.
|
|
|
|
# There is probably some physical limits to the number of instantaneous amps
|
|
|
|
# a battery can produce (cold cranking amps?)
|
|
|
|
#
|
|
|
|
|
|
|
|
BatteryClass.get_output_amps = func {
|
|
|
|
x = 1.0 - me.charge_percent;
|
|
|
|
tmp = -(3.0 * x - 1.0);
|
|
|
|
factor = (tmp*tmp*tmp*tmp*tmp + 32) / 32;
|
|
|
|
return me.ideal_amps * factor;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
##
|
|
|
|
# Alternator model class.
|
|
|
|
#
|
|
|
|
|
|
|
|
AlternatorClass = {};
|
|
|
|
|
|
|
|
AlternatorClass.new = func {
|
|
|
|
obj = { parents : [AlternatorClass],
|
|
|
|
rpm_source : "/engines/engine[0]/rpm",
|
|
|
|
rpm_threshold : 800.0,
|
|
|
|
ideal_volts : 28.0,
|
|
|
|
ideal_amps : 60.0 };
|
|
|
|
setprop( obj.rpm_source, 0.0 );
|
|
|
|
return obj;
|
|
|
|
}
|
|
|
|
|
|
|
|
##
|
|
|
|
# Computes available amps and returns remaining amps after load is applied
|
|
|
|
#
|
|
|
|
|
|
|
|
AlternatorClass.apply_load = func( amps, dt ) {
|
|
|
|
# Scale alternator output for rpms < 800. For rpms >= 800
|
|
|
|
# give full output. This is just a WAG, and probably not how
|
|
|
|
# it really works but I'm keeping things "simple" to start.
|
|
|
|
rpm = getprop( me.rpm_source );
|
|
|
|
factor = rpm / me.rpm_threshold;
|
|
|
|
if ( factor > 1.0 ) {
|
|
|
|
factor = 1.0;
|
|
|
|
}
|
|
|
|
# print( "alternator amps = ", me.ideal_amps * factor );
|
|
|
|
available_amps = me.ideal_amps * factor;
|
|
|
|
return available_amps - amps;
|
|
|
|
}
|
|
|
|
|
|
|
|
##
|
|
|
|
# Return output volts based on rpm
|
|
|
|
#
|
|
|
|
|
|
|
|
AlternatorClass.get_output_volts = func {
|
|
|
|
# scale alternator output for rpms < 800. For rpms >= 800
|
|
|
|
# give full output. This is just a WAG, and probably not how
|
|
|
|
# it really works but I'm keeping things "simple" to start.
|
|
|
|
rpm = getprop( me.rpm_source );
|
|
|
|
factor = rpm / me.rpm_threshold;
|
|
|
|
if ( factor > 1.0 ) {
|
|
|
|
factor = 1.0;
|
|
|
|
}
|
|
|
|
# print( "alternator volts = ", me.ideal_volts * factor );
|
|
|
|
return me.ideal_volts * factor;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
##
|
|
|
|
# Return output amps available based on rpm.
|
|
|
|
#
|
|
|
|
|
|
|
|
AlternatorClass.get_output_amps = func {
|
|
|
|
# scale alternator output for rpms < 800. For rpms >= 800
|
|
|
|
# give full output. This is just a WAG, and probably not how
|
|
|
|
# it really works but I'm keeping things "simple" to start.
|
|
|
|
rpm = getprop( me.rpm_source );
|
|
|
|
factor = rpm / me.rpm_threshold;
|
|
|
|
if ( factor > 1.0 ) {
|
|
|
|
factor = 1.0;
|
|
|
|
}
|
|
|
|
# print( "alternator amps = ", ideal_amps * factor );
|
|
|
|
return me.ideal_amps * factor;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
##
|
|
|
|
# This is the main electrical system update function.
|
|
|
|
#
|
|
|
|
|
|
|
|
update_electrical = func {
|
|
|
|
time = getprop("/sim/time/elapsed-sec");
|
|
|
|
dt = time - last_time;
|
|
|
|
last_time = time;
|
|
|
|
|
|
|
|
update_virtual_bus( dt );
|
|
|
|
|
|
|
|
# Request that the update fuction be called again next frame
|
|
|
|
settimer(update_electrical, 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
##
|
|
|
|
# Model the system of relays and connections that join the battery,
|
|
|
|
# alternator, starter, master/alt switches, external power supply.
|
|
|
|
#
|
|
|
|
|
|
|
|
update_virtual_bus = func( dt ) {
|
|
|
|
serviceable = getprop("/systems/electrical/serviceable");
|
|
|
|
if ( serviceable ) {
|
|
|
|
battery_volts = battery.get_output_volts();
|
|
|
|
alternator_volts = alternator.get_output_volts();
|
|
|
|
} else {
|
|
|
|
battery_volts = 0.0;
|
|
|
|
alternator_volts = 0.0;
|
|
|
|
}
|
|
|
|
external_volts = 0.0;
|
|
|
|
load = 0.0;
|
|
|
|
|
|
|
|
# switch state
|
|
|
|
master_bat = getprop("/controls/engines/engine[0]/master-bat");
|
|
|
|
master_alt = getprop("/controls/engines/engine[0]/master-alt");
|
|
|
|
|
|
|
|
# determine power source
|
|
|
|
bus_volts = 0.0;
|
|
|
|
power_source = nil;
|
|
|
|
if ( master_bat ) {
|
|
|
|
bus_volts = battery_volts;
|
|
|
|
power_source = "battery";
|
|
|
|
}
|
|
|
|
if ( master_alt and (alternator_volts > bus_volts) ) {
|
|
|
|
bus_volts = alternator_volts;
|
|
|
|
power_source = "alternator";
|
|
|
|
}
|
|
|
|
if ( external_volts > bus_volts ) {
|
|
|
|
bus_volts = external_volts;
|
|
|
|
power_source = "external";
|
|
|
|
}
|
|
|
|
# print( "virtual bus volts = ", bus_volts );
|
|
|
|
|
|
|
|
# starter motor
|
|
|
|
starter_switch = getprop("/controls/engines/engine[0]/starter");
|
|
|
|
starter_volts = 0.0;
|
|
|
|
if ( starter_switch ) {
|
|
|
|
starter_volts = bus_volts;
|
|
|
|
}
|
|
|
|
setprop("/systems/electrical/outputs/starter[0]", starter_volts);
|
|
|
|
|
|
|
|
# bus network (1. these must be called in the right order, 2. the
|
|
|
|
# bus routine itself determins where it draws power from.)
|
|
|
|
load += electrical_bus_1();
|
|
|
|
load += electrical_bus_2();
|
|
|
|
load += cross_feed_bus();
|
|
|
|
load += avionics_bus_1();
|
|
|
|
load += avionics_bus_2();
|
|
|
|
|
|
|
|
# system loads and ammeter gauge
|
|
|
|
ammeter = 0.0;
|
|
|
|
if ( bus_volts > 1.0 ) {
|
|
|
|
# normal load
|
|
|
|
load += 15.0;
|
|
|
|
|
|
|
|
# ammeter gauge
|
|
|
|
if ( power_source == "battery" ) {
|
|
|
|
ammeter = -load;
|
|
|
|
} else {
|
|
|
|
ammeter = battery.charge_amps;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
# print( "ammeter = ", ammeter );
|
|
|
|
|
|
|
|
# charge/discharge the battery
|
|
|
|
if ( power_source == "battery" ) {
|
|
|
|
battery.apply_load( load, dt );
|
|
|
|
} elsif ( bus_volts > battery_volts ) {
|
|
|
|
battery.apply_load( -battery.charge_amps, dt );
|
|
|
|
}
|
|
|
|
|
|
|
|
# filter ammeter needle pos
|
|
|
|
ammeter_ave = 0.8 * ammeter_ave + 0.2 * ammeter;
|
|
|
|
|
|
|
|
# outputs
|
|
|
|
setprop("/systems/electrical/amps", ammeter_ave);
|
|
|
|
setprop("/systems/electrical/volts", bus_volts);
|
|
|
|
vbus_volts = bus_volts;
|
|
|
|
|
|
|
|
return load;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
electrical_bus_1 = func() {
|
|
|
|
# we are fed from the "virtual" bus
|
|
|
|
bus_volts = vbus_volts;
|
|
|
|
load = 0.0;
|
|
|
|
|
|
|
|
# Cabin Lights Power
|
|
|
|
if ( getprop("/controls/circuit-breakers/cabin-lights-pwr") ) {
|
|
|
|
setprop("/systems/electrical/outputs/cabin-lights", bus_volts);
|
|
|
|
} else {
|
|
|
|
setprop("/systems/electrical/outputs/cabin-lights", 0.0);
|
|
|
|
}
|
|
|
|
|
|
|
|
# Instrument Power
|
|
|
|
setprop("/systems/electrical/outputs/instr-ignition-switch", bus_volts);
|
|
|
|
|
|
|
|
# Fuel Pump Power
|
|
|
|
if ( getprop("/controls/engines/engine[0]/fuel-pump") ) {
|
|
|
|
setprop("/systems/electrical/outputs/fuel-pump", bus_volts);
|
|
|
|
} else {
|
|
|
|
setprop("/systems/electrical/outputs/fuel-pump", 0.0);
|
|
|
|
}
|
|
|
|
|
|
|
|
# Landing Light Power
|
|
|
|
if ( getprop("/controls/switches/landing-light") ) {
|
|
|
|
setprop("/systems/electrical/outputs/landing-light", bus_volts);
|
|
|
|
} else {
|
|
|
|
setprop("/systems/electrical/outputs/landing-light", 0.0 );
|
|
|
|
}
|
|
|
|
|
|
|
|
# Beacon Power
|
|
|
|
if ( getprop("/controls/switches/flashing-beacon" ) ) {
|
|
|
|
setprop("/systems/electrical/outputs/beacon", bus_volts);
|
|
|
|
if ( bus_volts > 1.0 ) { load += 7.5; }
|
|
|
|
} else {
|
|
|
|
setprop("/systems/electrical/outputs/beacon", 0.0);
|
|
|
|
}
|
|
|
|
|
|
|
|
# Flaps Power
|
|
|
|
setprop("/systems/electrical/outputs/flaps", bus_volts);
|
|
|
|
|
|
|
|
# register bus voltage
|
|
|
|
ebus1_volts = bus_volts;
|
|
|
|
|
|
|
|
# return cumulative load
|
|
|
|
return load;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
electrical_bus_2 = func() {
|
|
|
|
# we are fed from the "virtual" bus
|
|
|
|
bus_volts = vbus_volts;
|
|
|
|
load = 0.0;
|
|
|
|
|
|
|
|
# Turn Coordinator Power
|
|
|
|
setprop("/systems/electrical/outputs/turn-coordinator", bus_volts);
|
|
|
|
|
|
|
|
# Map Lights Power
|
|
|
|
if ( getprop("/controls/switches/nav-lights" ) ) {
|
|
|
|
setprop("/systems/electrical/outputs/map-lights", bus_volts);
|
|
|
|
if ( bus_volts > 1.0 ) { load += 7.0; }
|
|
|
|
} else {
|
|
|
|
setprop("/systems/electrical/outputs/map-lights", 0.0);
|
|
|
|
}
|
|
|
|
|
|
|
|
# Instrument Lights Power
|
|
|
|
setprop("/systems/electrical/outputs/instrument-lights", bus_volts);
|
|
|
|
|
|
|
|
# Strobe Lights Power
|
|
|
|
if ( getprop("/controls/switches/strobe-lights" ) ) {
|
|
|
|
setprop("/systems/electrical/outputs/strobe-lights", bus_volts);
|
|
|
|
} else {
|
|
|
|
setprop("/systems/electrical/outputs/strobe-lights", 0.0);
|
|
|
|
}
|
|
|
|
|
|
|
|
# Taxi Lights Power
|
|
|
|
if ( getprop("/controls/switches/taxi-lights" ) ) {
|
|
|
|
setprop("/systems/electrical/outputs/taxi-lights", bus_volts);
|
|
|
|
} else {
|
|
|
|
setprop("/systems/electrical/outputs/taxi-lights", 0.0);
|
|
|
|
}
|
|
|
|
|
|
|
|
# Pitot Heat Power
|
|
|
|
if ( getprop("/controls/switches/pitot-heat" ) ) {
|
|
|
|
setprop("/systems/electrical/outputs/pitot-heat", bus_volts);
|
|
|
|
} else {
|
|
|
|
setprop("/systems/electrical/outputs/pitot-heat", 0.0);
|
|
|
|
}
|
|
|
|
|
|
|
|
# register bus voltage
|
|
|
|
ebus2_volts = bus_volts;
|
|
|
|
|
|
|
|
# return cumulative load
|
|
|
|
return load;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
cross_feed_bus = func() {
|
|
|
|
# we are fed from either of the electrical bus 1 or 2
|
|
|
|
if ( ebus1_volts > ebus2_volts ) {
|
|
|
|
bus_volts = ebus1_volts;
|
|
|
|
} else {
|
|
|
|
bus_volts = ebus2_volts;
|
|
|
|
}
|
|
|
|
|
|
|
|
load = 0.0;
|
|
|
|
|
|
|
|
setprop("/systems/electrical/outputs/annunciators", bus_volts);
|
|
|
|
|
|
|
|
# return cumulative load
|
|
|
|
return load;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
avionics_bus_1 = func() {
|
|
|
|
master_av = getprop("/controls/switches/master-avionics");
|
|
|
|
|
|
|
|
# we are fed from the electrical bus 1
|
|
|
|
if ( master_av ) {
|
|
|
|
bus_volts = ebus1_volts;
|
|
|
|
} else {
|
|
|
|
bus_volts = 0.0;
|
|
|
|
}
|
|
|
|
|
|
|
|
load = 0.0;
|
|
|
|
|
|
|
|
# Avionics Fan Power
|
|
|
|
setprop("/systems/electrical/outputs/avionics-fan", bus_volts);
|
|
|
|
|
|
|
|
# GPS Power
|
|
|
|
setprop("/systems/electrical/outputs/gps", bus_volts);
|
|
|
|
|
|
|
|
# HSI Power
|
|
|
|
setprop("/systems/electrical/outputs/hsi", bus_volts);
|
|
|
|
|
|
|
|
# NavCom 1 Power
|
|
|
|
setprop("/systems/electrical/outputs/nav[0]", bus_volts);
|
|
|
|
|
|
|
|
# DME Power
|
|
|
|
setprop("/systems/electrical/outputs/dme", bus_volts);
|
|
|
|
|
|
|
|
# Audio Panel 1 Power
|
|
|
|
setprop("/systems/electrical/outputs/audio-panel[0]", bus_volts);
|
|
|
|
|
|
|
|
# return cumulative load
|
|
|
|
return load;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
avionics_bus_2 = func() {
|
|
|
|
master_av = getprop("/controls/switches/master-avionics");
|
|
|
|
|
|
|
|
# we are fed from the electrical bus 2
|
|
|
|
if ( master_av ) {
|
|
|
|
bus_volts = ebus2_volts;
|
|
|
|
} else {
|
|
|
|
bus_volts = 0.0;
|
|
|
|
}
|
|
|
|
load = 0.0;
|
|
|
|
|
|
|
|
# NavCom 2 Power
|
|
|
|
setprop("/systems/electrical/outputs/nav[1]", bus_volts);
|
|
|
|
|
|
|
|
# Audio Panel 2 Power
|
|
|
|
setprop("/systems/electrical/outputs/audio-panel[1]", bus_volts);
|
|
|
|
|
|
|
|
# Transponder Power
|
|
|
|
setprop("/systems/electrical/outputs/transponder", bus_volts);
|
|
|
|
|
|
|
|
# Autopilot Power
|
|
|
|
setprop("/systems/electrical/outputs/autopilot", bus_volts);
|
|
|
|
|
|
|
|
# ADF Power
|
|
|
|
setprop("/systems/electrical/outputs/adf", bus_volts);
|
|
|
|
|
|
|
|
# return cumulative load
|
|
|
|
return load;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
# Setup a timer based call to initialized the electrical system as
|
|
|
|
# soon as possible.
|
|
|
|
settimer(init_electrical, 0);
|