ebcfc739bd
Most control switches used with mismatching names. Make effects depend on electric power, not just switch state. Calculate some (more) meaningful electric load.
490 lines
13 KiB
Text
490 lines
13 KiB
Text
##
|
|
# Procedural model of a Cessna 172S electrical system. Includes a
|
|
# preliminary battery charge/discharge model and realistic ammeter
|
|
# gauge modeling.
|
|
#
|
|
|
|
|
|
##
|
|
# Initialize internal values
|
|
#
|
|
|
|
var battery = nil;
|
|
var alternator = nil;
|
|
|
|
var last_time = 0.0;
|
|
|
|
var vbus_volts = 0.0;
|
|
var ebus1_volts = 0.0;
|
|
var ebus2_volts = 0.0;
|
|
|
|
var ammeter_ave = 0.0;
|
|
|
|
##
|
|
# Initialize the electrical system
|
|
#
|
|
|
|
init_electrical = func {
|
|
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 function be called next frame
|
|
settimer(update_electrical, 0);
|
|
print("Electrical system initialized");
|
|
}
|
|
|
|
|
|
##
|
|
# Battery model class.
|
|
#
|
|
|
|
BatteryClass = {};
|
|
|
|
BatteryClass.new = func {
|
|
var 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 ) {
|
|
var amphrs_used = amps * dt / 3600.0;
|
|
var percent_used = amphrs_used / me.amp_hours;
|
|
var charge_percent = me.charge_percent;
|
|
charge_percent -= percent_used;
|
|
if ( charge_percent < 0.0 ) {
|
|
charge_percent = 0.0;
|
|
} elsif ( charge_percent > 1.0 ) {
|
|
charge_percent = 1.0;
|
|
}
|
|
if ((charge_percent < 0.1)and(me.charge_percent >= 0.1))
|
|
{
|
|
print("Warning: Low battery! Enable alternator or apply external power to recharge battery.");
|
|
}
|
|
me.charge_percent = charge_percent;
|
|
setprop("/systems/electrical/battery-charge-percent", charge_percent);
|
|
# print( "battery percent = ", charge_percent);
|
|
return me.amp_hours * charge_percent;
|
|
}
|
|
|
|
##
|
|
# Return output volts based on percent charged. Currently based on a simple
|
|
# polynomial percent charge vs. volts function.
|
|
#
|
|
|
|
BatteryClass.get_output_volts = func {
|
|
var x = 1.0 - me.charge_percent;
|
|
var tmp = -(3.0 * x - 1.0);
|
|
var 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 {
|
|
var x = 1.0 - me.charge_percent;
|
|
var tmp = -(3.0 * x - 1.0);
|
|
var factor = (tmp*tmp*tmp*tmp*tmp + 32) / 32;
|
|
return me.ideal_amps * factor;
|
|
}
|
|
|
|
|
|
##
|
|
# Alternator model class.
|
|
#
|
|
|
|
AlternatorClass = {};
|
|
|
|
AlternatorClass.new = func {
|
|
var 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.
|
|
var rpm = getprop( me.rpm_source );
|
|
var factor = rpm / me.rpm_threshold;
|
|
if ( factor > 1.0 ) {
|
|
factor = 1.0;
|
|
}
|
|
# print( "alternator amps = ", me.ideal_amps * factor );
|
|
var 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.
|
|
var rpm = getprop( me.rpm_source );
|
|
var 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.
|
|
var rpm = getprop( me.rpm_source );
|
|
var 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 {
|
|
var time = getprop("/sim/time/elapsed-sec");
|
|
var dt = time - last_time;
|
|
last_time = time;
|
|
|
|
update_virtual_bus( dt );
|
|
|
|
# Request that the update function 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 ) {
|
|
var serviceable = getprop("/systems/electrical/serviceable");
|
|
var external_volts = 0.0;
|
|
var load = 0.0;
|
|
var battery_volts = 0.0;
|
|
var alternator_volts = 0.0;
|
|
if ( serviceable ) {
|
|
battery_volts = battery.get_output_volts();
|
|
alternator_volts = alternator.get_output_volts();
|
|
}
|
|
|
|
# switch state
|
|
var master_bat = getprop("/controls/engines/engine[0]/master-bat");
|
|
var master_alt = getprop("/controls/engines/engine[0]/master-alt");
|
|
if (getprop("/controls/electric/external-power"))
|
|
{
|
|
external_volts = 28;
|
|
}
|
|
|
|
# determine power source
|
|
var bus_volts = 0.0;
|
|
var 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
|
|
var starter_switch = getprop("controls/switches/starter");
|
|
var starter_volts = 0.0;
|
|
if ( starter_switch ) {
|
|
starter_volts = bus_volts;
|
|
load += 12;
|
|
}
|
|
setprop("systems/electrical/outputs/starter[0]", starter_volts);
|
|
if (starter_volts > 12) {
|
|
setprop("controls/engines/engine[0]/starter",1);
|
|
setprop("controls/engines/engine[0]/magnetos",3);
|
|
} else {
|
|
setprop("controls/engines/engine[0]/starter",0);
|
|
}
|
|
|
|
# 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
|
|
var ammeter = 0.0;
|
|
if ( bus_volts > 1.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);
|
|
if (bus_volts > 12)
|
|
vbus_volts = bus_volts;
|
|
else
|
|
vbus_volts = 0.0;
|
|
|
|
return load;
|
|
}
|
|
|
|
|
|
electrical_bus_1 = func() {
|
|
# we are fed from the "virtual" bus
|
|
var bus_volts = vbus_volts;
|
|
var load = 0.0;
|
|
|
|
# Cabin Lights Power
|
|
if ( getprop("/controls/circuit-breakers/cabin-lights-pwr") ) {
|
|
setprop("/systems/electrical/outputs/cabin-lights", bus_volts);
|
|
load += bus_volts / 57;
|
|
} 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);
|
|
load += bus_volts / 28;
|
|
} else {
|
|
setprop("/systems/electrical/outputs/fuel-pump", 0.0);
|
|
}
|
|
|
|
# Landing Light Power
|
|
if ( getprop("/controls/lighting/landing-lights") ) {
|
|
setprop("/systems/electrical/outputs/landing-lights", bus_volts);
|
|
load += bus_volts / 5;
|
|
} else {
|
|
setprop("/systems/electrical/outputs/landing-lights", 0.0 );
|
|
}
|
|
|
|
# Beacon Power
|
|
if ( getprop("/controls/lighting/beacon" ) ) {
|
|
setprop("/systems/electrical/outputs/beacon", bus_volts);
|
|
load += bus_volts / 28;
|
|
} 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
|
|
var bus_volts = vbus_volts;
|
|
var load = 0.0;
|
|
|
|
# Nav Lights Power
|
|
if ( getprop("/controls/lighting/nav-lights" ) ) {
|
|
setprop("/systems/electrical/outputs/nav-lights", bus_volts);
|
|
load += bus_volts / 14;
|
|
} else {
|
|
setprop("/systems/electrical/outputs/nav-lights", 0.0);
|
|
}
|
|
|
|
# Instrument Lights Power
|
|
setprop("/systems/electrical/outputs/instrument-lights", bus_volts);
|
|
|
|
# Strobe Lights Power
|
|
if ( getprop("/controls/lighting/strobe" ) ) {
|
|
setprop("/systems/electrical/outputs/strobe", bus_volts);
|
|
load += bus_volts / 14;
|
|
} else {
|
|
setprop("/systems/electrical/outputs/strobe", 0.0);
|
|
}
|
|
|
|
# Taxi Lights Power
|
|
if ( getprop("/controls/lighting/taxi-light" ) ) {
|
|
setprop("/systems/electrical/outputs/taxi-light", bus_volts);
|
|
load += bus_volts / 10;
|
|
} else {
|
|
setprop("/systems/electrical/outputs/taxi-light", 0.0);
|
|
}
|
|
|
|
# Pitot Heat Power
|
|
if ( getprop("/controls/anti-ice/pitot-heat" ) ) {
|
|
setprop("/systems/electrical/outputs/pitot-heat", bus_volts);
|
|
load += bus_volts / 28;
|
|
} 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
|
|
var bus_volts = ebus2_volts;
|
|
if ( ebus1_volts > ebus2_volts ) {
|
|
bus_volts = ebus1_volts;
|
|
}
|
|
|
|
var load = 0.0;
|
|
|
|
setprop("/systems/electrical/outputs/annunciators", bus_volts);
|
|
|
|
# return cumulative load
|
|
return load;
|
|
}
|
|
|
|
|
|
avionics_bus_1 = func() {
|
|
var bus_volts = 0.0;
|
|
var load = 0.0;
|
|
|
|
# we are fed from the electrical bus 1
|
|
var master_av = getprop("/controls/switches/master-avionics");
|
|
if ( master_av ) {
|
|
bus_volts = ebus1_volts;
|
|
}
|
|
|
|
load += bus_volts / 20.0;
|
|
|
|
# Turn Coordinator Power
|
|
setprop("/systems/electrical/outputs/turn-coordinator", bus_volts);
|
|
|
|
# Directional Gyro Power
|
|
setprop("/systems/electrical/outputs/DG", bus_volts);
|
|
|
|
# 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);
|
|
|
|
# Com 1 Power
|
|
setprop("systems/electrical/outputs/comm[0]", bus_volts);
|
|
|
|
# return cumulative load
|
|
return load;
|
|
}
|
|
|
|
|
|
avionics_bus_2 = func() {
|
|
var master_av = getprop("/controls/switches/master-avionics");
|
|
# we are fed from the electrical bus 2
|
|
var bus_volts = 0.0;
|
|
if ( master_av ) {
|
|
bus_volts = ebus2_volts;
|
|
}
|
|
|
|
var load = bus_volts / 20.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);
|
|
|
|
# Com 2 Power
|
|
setprop("systems/electrical/outputs/comm[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);
|