1
0
Fork 0
fgdata/Aircraft/c172p/Nasal/engine.nas
Stuart Buchanan 8814847d59 Merge c172p-detailed from c172p-team for 2018.1
https://github.com/c172p-team/c172p-detailed/tree/version/2018.1
b90f547039

Changelog:
    New images for the new splash screen and thumbnails system
    FDM improvements:
        Fixed deflection angles of ailerons and roll moment due to ailerons
        Added spiraling propwash effect (requiring right rudder at full throttle, low airspeed)
        Made the elevator action at high AoA asymmetric in order to take into account the screening effect of the horizontal stabilizer in such stall conditions
        Increasing the side force due to rudder, to make it consistent with the exerted moment
        Decreasing the adverse yaw (the aileron trim gave too much slip-skid-ball deviation at cruise)
    Increased maximum rotation of yoke from 70 to 90 degrees
    Use shift + q to reset view
    Added avionics sound
    Fixed bug with oil temperature and pressure gauges (they were not working when complex engine procedures was toggled off)
    Fixed bug with lighting of pontoon wake effect
    Eliminated wingtip and tail sparks when over water
    Eliminated sparks of broken gear while aircrafts sits on the flight deck of a moving carrier
    Fixed some bugs with the walker
2018-02-10 20:55:32 +00:00

370 lines
16 KiB
Text

# Manages the engine
#
# Fuel system: based on the Spitfire. Manages primer and negGCutoff
# Hobbs meter
# =============================== DEFINITIONS ===========================================
# set the update period
var UPDATE_PERIOD = 0.3;
# =============================== Hobbs meter =======================================
# this property is saved by aircraft.timer
var hobbsmeter_engine_160hp = aircraft.timer.new("/sim/time/hobbs/engine[0]", 60, 1);
var hobbsmeter_engine_180hp = aircraft.timer.new("/sim/time/hobbs/engine[1]", 60, 1);
var init_hobbs_meter = func(index, meter) {
setlistener("/engines/engine[" ~ index ~ "]/running", func {
if (getprop("/engines/engine[" ~ index ~ "]/running")) {
meter.start();
print("Hobbs system started");
} else {
meter.stop();
print("Hobbs system stopped");
}
}, 1, 0);
};
init_hobbs_meter(0, hobbsmeter_engine_160hp);
init_hobbs_meter(1, hobbsmeter_engine_180hp);
var update_hobbs_meter = func {
# in seconds
var hobbs_160hp = getprop("/sim/time/hobbs/engine[0]") or 0.0;
var hobbs_180hp = getprop("/sim/time/hobbs/engine[1]") or 0.0;
# This uses minutes, for testing
#hobbs = hobbs / 60.0;
# in hours
hobbs = (hobbs_160hp + hobbs_180hp) / 3600.0;
# tenths of hour
setprop("/instrumentation/hobbs-meter/digits0", math.mod(int(hobbs * 10), 10));
# rest of digits
setprop("/instrumentation/hobbs-meter/digits1", math.mod(int(hobbs), 10));
setprop("/instrumentation/hobbs-meter/digits2", math.mod(int(hobbs / 10), 10));
setprop("/instrumentation/hobbs-meter/digits3", math.mod(int(hobbs / 100), 10));
setprop("/instrumentation/hobbs-meter/digits4", math.mod(int(hobbs / 1000), 10));
};
setlistener("/sim/time/hobbs/engine[0]", update_hobbs_meter, 1, 0);
setlistener("/sim/time/hobbs/engine[1]", update_hobbs_meter, 1, 0);
# ========== primer stuff ======================
# Toggles the state of the primer
var pumpPrimer = func {
var push = getprop("/controls/engines/engine/primer-lever") or 0;
if (push) {
var pump = getprop("/controls/engines/engine/primer") or 0;
setprop("/controls/engines/engine/primer", pump + 1);
setprop("/controls/engines/engine/primer-lever", 0);
}
else {
setprop("/controls/engines/engine/primer-lever", 1);
}
};
# Primes the engine automatically. This function takes several seconds
var autoPrime = func {
var p = getprop("/controls/engines/engine/primer") or 0;
if (p < 3) {
pumpPrimer();
settimer(autoPrime, 1);
}
};
# Mixture will be calculated using the primer during 5 seconds AFTER the pilot used the starter
# This prevents the engine to start just after releasing the starter: the propeller will be running
# thanks to the electric starter, but carburator has not yet enough mixture
var primerTimer = maketimer(5, func {
setprop("/controls/engines/engine/use-primer", 0);
# Reset the number of times the pilot used the primer only AFTER using the starter
setprop("/controls/engines/engine/primer", 0);
print("Primer reset to 0");
primerTimer.stop();
});
# ========== oil consumption ======================
var oil_consumption = maketimer(1.0, func {
var oil_level = getprop("/engines/active-engine/oil-level");
if (getprop("/controls/engines/active-engine") == 0)
var oil_full = 7;
if (getprop("/controls/engines/active-engine") == 1)
var oil_full = 8;
var oil_lacking = oil_full - oil_level;
setprop("/engines/active-engine/oil-lacking", oil_lacking);
if (getprop("/engines/active-engine/oil_consumption_allowed")) {
var rpm = getprop("/engines/active-engine/rpm");
# Quadratic formula which outputs 1.0 for input 2300 RPM (cruise value),
# 0.6 for 700 RPM (idle) and 1.2 for 2700 RPM (max)
var rpm_factor = 0.00000012 * math.pow(rpm, 2) - 0.0001 * rpm + 0.62;
# Consumption rate defined as 1.5 quarter per 10 hours (36000 seconds)
# at cruise RPM
var consumption_rate = 1.5 / 36000;
if (getprop("/engines/active-engine/running")) {
oil_level = oil_level - consumption_rate * rpm_factor;
setprop("/engines/active-engine/oil-level", oil_level);
}
var low_oil_pressure_factor = 1.0;
var low_oil_temperature_factor = 1.0;
# If oil gets low (< 3.0), pressure should drop and temperature should rise
var oil_level_limited = std.min(oil_level, 3.0);
# Should give 1.0 for oil_level = 3 and 0.1 for oil_level 1.97,
# which is the min before the engine stops
low_oil_pressure_factor = 0.873786408 * oil_level_limited - 1.621359224;
# Should give 1.0 for oil_level = 3 and 1.5 for oil_level 1.97
low_oil_temperature_factor = -0.485436893 * oil_level_limited + 2.456310679;
setprop("/engines/active-engine/low-oil-pressure-factor", low_oil_pressure_factor);
setprop("/engines/active-engine/low-oil-temperature-factor", low_oil_temperature_factor);
}
else {
# if oil consumption is not allowed, the oil level is set to full and pressure and temp factors are set to 1.0
if (getprop("/controls/engines/active-engine") == 0)
setprop("/engines/active-engine/oil-level", 7);
if (getprop("/controls/engines/active-engine") == 1)
setprop("/engines/active-engine/oil-level", 8);
setprop("/engines/active-engine/low-oil-pressure-factor", 1.0);
setprop("/engines/active-engine/low-oil-temperature-factor", 1.0);
}
});
# ========== carburetor icing ======================
var carb_icing_function = maketimer(1.0, func {
if (getprop("/engines/active-engine/carb_icing_allowed")) {
var rpm = getprop("/engines/active-engine/rpm");
var dewpointC = getprop("/environment/dewpoint-degc");
var dewpointF = dewpointC * 9.0 / 5.0 + 32;
var airtempF = getprop("/environment/temperature-degf");
var oil_temp = getprop("/engines/active-engine/oil-temperature-degf");
var egt_degf = getprop("/engines/active-engine/egt-degf");
var engine_running = getprop("/engines/active-engine/running");
var carb_ice = getprop("/engines/active-engine/carb_ice");
# the formula below attempts to model the graph found in the POH which relates air temperature, dew point and RPM to icing
# conditions. The outputs of carb_icing_formula ranges from 0.65 to -0.35 (positive means ice is accumulating, negative
# means that ice is melting)
var factorX = 13.2 - 3.2 * math.atan2 ( ((rpm - 2000.0) * 0.008), 1);
var factorY = 7.0 - 2.0 * math.atan2 ( ((rpm - 2000.0) * 0.008), 1);
var carb_icing_formula = (math.exp( math.pow((0.6 * airtempF + 0.3 * dewpointF - 42.0),2) / (-2 * math.pow(factorX,2))) * math.exp( math.pow((0.3 * airtempF - 0.6 * dewpointF + 14.0),2) / (-2 * math.pow(factorY,2))) - 0.35) * engine_running;
# the efficacy of carb heat depends on the EGT. With a typical EGT of ~1500, the carb_heat_rate will be around -1.5.
# This value is an educated guess of the RL effect, and should melt ice regardless of the icing rate
if (getprop("/controls/engines/current-engine/carb-heat"))
var carb_heat_rate = -0.001 * egt_degf;
else
var carb_heat_rate = 0.0;
# a warm engine will accumulate less ice than a cold one, which is what oil temp factor is used for. oil_temp_factor
# ranges from 0 to aprox -0.2 (at 250 oF). These values are educated guesses of the RL effect
var oil_temp_factor = oil_temp / -1250;
# the final rate of icing or melting is then calculated by all these effects together
var carb_icing_rate = carb_icing_formula + carb_heat_rate + oil_temp_factor;
# since the carb_icing_rate gives an arbitrary final value, the rate is then scaled down by 0.00001 to ensure ice
# accumulates as slowly as expected
carb_ice = carb_ice + carb_icing_rate * 0.00001;
carb_ice = std.max(0.0, std.min(carb_ice, 1.0));
# this property is used to lower the RPM of the engine as ice accumulates (more ice in the carburator == less power)
var vol_eff_factor = std.max(0.0, 0.85 - 1.72 * carb_ice);
setprop("/engines/active-engine/carb_ice", carb_ice);
setprop("/engines/active-engine/carb_icing_rate", carb_icing_rate);
setprop("/engines/active-engine/volumetric-efficiency-factor", vol_eff_factor);
setprop("/engines/active-engine/oil_temp_factor", oil_temp_factor);
}
else {
setprop("/engines/active-engine/carb_ice", 0.0);
setprop("/engines/active-engine/carb_icing_rate", 0.0);
setprop("/engines/active-engine/volumetric-efficiency-factor", 0.85);
setprop("/engines/active-engine/oil_temp_factor", 0.0);
};
});
# ========== engine coughing ======================
var engine_coughing = func(){
var coughing = getprop("/engines/active-engine/coughing");
var running = getprop("/engines/active-engine/running");
if (coughing and running) {
# the code below kills the engine and then brings it back to life after 0.25 seconds, simulating a cough
setprop("/engines/active-engine/kill-engine", 1);
settimer(func {
setprop("/engines/active-engine/kill-engine", 0);
}, 0.25);
};
# basic value for the delay (interval between consecutive coughs), in case no fuel contamination nor carb ice are present
var delay = 2;
# if coughing due to fuel contamination, then cough interval depends on quantity of water
var water_contamination0 = getprop("/consumables/fuel/tank[0]/water-contamination");
var water_contamination1 = getprop("/consumables/fuel/tank[1]/water-contamination");
var total_water_contamination = std.min((water_contamination0 + water_contamination1), 0.4);
if (total_water_contamination > 0) {
# if contamination is near 0, then interval is between 17 and 20 seconds, but if contamination is near the
# engine stopping value of 0.4, then interval falls to around 0.5 and 3.5 seconds
delay = 3.0 * rand() + 17 - 41.25 * total_water_contamination;
};
# if coughing due to carb ice melting, then cough depends on quantity of ice in the carburettor
var carb_ice = getprop("/engines/active-engine/carb_ice");
if (carb_ice > 0) {
# if carb_ice is near 0, then interval is between 17 and 20 seconds, but if carb_ice is near the
# engine stopping value of 0.3, then interval falls to around 0.5 and 3.5 seconds
delay = 3.0 * rand() + 17 - 41.25 * carb_ice;
};
coughing_timer.restart(delay);
}
var coughing_timer = maketimer(1, engine_coughing);
# ========== Main loop ======================
var update = func {
var leftTankUsable = getprop("/consumables/fuel/tank[0]/selected") and getprop("/consumables/fuel/tank[0]/level-gal_us") > 0;
var rightTankUsable = getprop("/consumables/fuel/tank[1]/selected") and getprop("/consumables/fuel/tank[1]/level-gal_us") > 0;
var outOfFuel = !(leftTankUsable or rightTankUsable);
# We use the mixture to control the engines, so set the mixture
var usePrimer = getprop("/controls/engines/engine/use-primer") or 0;
var engine_running = getprop("/engines/active-engine/running");
if (outOfFuel and (engine_running or usePrimer)) {
print("Out of fuel!");
gui.popupTip("Out of fuel!");
}
elsif (usePrimer and !engine_running and getprop("/engines/active-engine/oil-temperature-degf") <= 75) {
# Mixture is controlled by start conditions
var primer = getprop("/controls/engines/engine/primer");
if (!getprop("/fdm/jsbsim/fcs/mixture-primer-cmd") and getprop("/controls/switches/starter")) {
if (primer < 3) {
print("Use the primer!");
gui.popupTip("Use the primer!");
}
elsif (primer > 6) {
print("Flooded engine!");
gui.popupTip("Flooded engine!");
}
else {
print("Check the throttle!");
gui.popupTip("Check the throttle!");
}
}
}
};
setlistener("/controls/switches/starter", func {
var v = getprop("/controls/switches/starter") or 0;
if (v == 0) {
print("Starter off");
# notice the starter will be reset after 5 seconds
primerTimer.restart(5);
}
else {
print("Starter on");
setprop("/controls/engines/engine/use-primer", 1);
if (primerTimer.isRunning) {
primerTimer.stop();
}
}
}, 1, 0);
# ================================ Initalize ======================================
# Make sure all needed properties are present and accounted
# for, and that they have sane default values.
# =============== Variables ================
controls.incThrottle = func {
var delta = arg[1] * controls.THROTTLE_RATE * getprop("/sim/time/delta-realtime-sec");
var old_value = getprop("/controls/engines/current-engine/throttle");
var new_value = std.max(0.0, std.min(old_value + delta, 1.0));
setprop("/controls/engines/current-engine/throttle", new_value);
};
controls.throttleMouse = func {
if (!getprop("/devices/status/mice/mouse[0]/button[1]")) {
return;
}
var delta = cmdarg().getNode("offset").getValue() * -4;
var old_value = getprop("/controls/engines/current-engine/throttle");
var new_value = std.max(0.0, std.min(old_value + delta, 1.0));
setprop("/controls/engines/current-engine/throttle", new_value);
};
controls.throttleAxis = func {
var value = (1 - cmdarg().getNode("setting").getValue()) / 2;
var new_value = std.max(0.0, std.min(value, 1.0));
setprop("/controls/engines/current-engine/throttle", new_value);
};
controls.adjMixture = func {
var delta = arg[0] * controls.THROTTLE_RATE * getprop("/sim/time/delta-realtime-sec");
var old_value = getprop("/controls/engines/current-engine/mixture");
var new_value = std.max(0.0, std.min(old_value + delta, 1.0));
setprop("/controls/engines/current-engine/mixture", new_value);
};
controls.mixtureAxis = func {
var value = (1 - cmdarg().getNode("setting").getValue()) / 2;
var new_value = std.max(0.0, std.min(value, 1.0));
setprop("/controls/engines/current-engine/mixture", new_value);
};
controls.stepMagnetos = func {
var old_value = getprop("/controls/switches/magnetos");
var new_value = std.max(0, std.min(old_value + arg[0], 3));
setprop("/controls/switches/magnetos", new_value);
};
# key 's' calls to this function when it is pressed DOWN even if I overwrite the binding in the -set.xml file!
# fun fact: the key UP event can be overwriten!
controls.startEngine = func(v = 1) {
# Only operate in non-walker mode ('s' is also bound to walk-backward)
var view_name = getprop("/sim/current-view/name");
if (view_name == getprop("/sim/view[110]/name") or view_name == getprop("/sim/view[111]/name")) {
return;
}
if (getprop("/engines/active-engine/running"))
{
setprop("/controls/switches/starter", 0);
return;
}
else {
setprop("/controls/switches/magnetos", 3);
setprop("/controls/switches/starter", v);
}
};
setlistener("/sim/signals/fdm-initialized", func {
var engine_timer = maketimer(UPDATE_PERIOD, func { update(); });
engine_timer.start();
oil_consumption.start();
carb_icing_function.start();
coughing_timer.singleShot = 1;
coughing_timer.start();
});