########################################################################## # Simple Brake Simulation System # 2010, Thorsten Brehm # # Simple simulation of brake energy absorption and cooling effects. # # This module computes (approximates... :-) ) an energy level which # (faintly) resembles the kinetic energy absorption and cooling effects # of a brake system. But instead of computing real temperatures, this # is just meant to distinguish normal energy levels from exceptionally # high levels. The target is to drive EICAS "brakes overheat" messages # and gear effects only, to "reward" pilots with exceptionally bad # landings... # # To avoid complicated calculations of different braking effects (roll/air # drag, reverse thrust etc), we simply assume the brake system to cause a # fixed deceleration (me.BrakeDecel). With this deceleration we approximate # the speed difference which would be caused by the brake system alone for # any given simulation interval. The difference of the kinetic energy level # at the current speed and the decelerated speed are then added up to the # total absorbed brake energy. # Units (knots/lbs/Kg) do not matter much here. Eventually a magic scaling # divisor is used to scale the output level. Any output > 1 means # "overheated brakes", any level <=1 means "brake temperature OK". # No exact science here - but good enough for now :-). ########################################################################## var BrakeSystem = { new : func() { var m = { parents : [BrakeSystem]}; # deceleration caused by brakes alone (knots/s2) m.BrakeDecel = 1.0; # kt/s^2 #m.LBrakeDecel = getprop("systems/hydraulic/brakes/pressure-left-psi") / 1000 * getprop("controls/autobrake/decel-error"); # kt/s^2 #m.RBrakeDecel = getprop("systems/hydraulic/brakes/pressure-right-psi") / 1000 * getprop("controls/autobrake/decel-error"); # kt/s^2 # Higher value means quicker cooling m.CoolingFactor = 0.005; # Scaling divisor. Use this to scale the energy output. # Manually tune this value: a total energy output # at "/gear/brake-thermal-energy" > 1.0 means overheated brakes, # anything below <= 1.0 means energy absorbed by brakes is OK. #m.ScalingDivisor= 700000*450.0; m.ScalingDivisor= 1; m.LSmokeActive = 0; m.LSmokeToggle = 0; m.RSmokeActive = 0; m.RSmokeToggle = 0; m.nCoolFactor = math.ln(1-m.CoolingFactor); m.reset(); return m; }, reset : func() { # Initial thermal energy setprop("gear/Lbrake-thermal-energy",0.0); setprop("gear/Rbrake-thermal-energy",0.0); setprop("gear/Lbrake-smoke",0); setprop("gear/Rbrake-smoke",0); setprop("sim/animation/fire-services",0); me.LastSimTime = 0.0; }, # update brake energy update : func() { var CurrentTime = getprop("sim/time/elapsed-sec"); var dt = CurrentTime - me.LastSimTime; if (dt<1.0) { var OnGround = getprop("gear/gear[1]/wow"); var LThermalEnergy = getprop("gear/Lbrake-thermal-energy"); var RThermalEnergy = getprop("gear/Rbrake-thermal-energy"); if (getprop("controls/gear/brake-parking")) { var LBrakeLevel=1.0; var RBrakeLevel=1.0; var BrakeLevel = (LBrakeLevel + RBrakeLevel)/2; } else var LBrakeLevel = getprop("fdm/jsbsim/fcs/left-brake-cmd-norm"); var RBrakeLevel = getprop("fdm/jsbsim/fcs/right-brake-cmd-norm"); var BrakeLevel = (LBrakeLevel + RBrakeLevel)/2; if ((OnGround)and(BrakeLevel>0)) { # absorb more energy var V1 = getprop("velocities/groundspeed-kt"); var Mass = getprop("fdm/jsbsim/inertia/weight-lbs")/(me.ScalingDivisor*200000000); # absorb some kinetic energy: # dE= 1/2 * m * V1^2 - 1/2 * m * V2^2) var V2_L = V1 - me.BrakeDecel*dt * LBrakeLevel; var V2_R = V1 - me.BrakeDecel*dt * RBrakeLevel; # do not absorb more energy when plane is (almost) stopped if (V2_L>0) LThermalEnergy += Mass * (V1*V1 - V2_L*V2_L)/2; if (V2_R>0) RThermalEnergy += Mass * (V1*V1 - V2_R*V2_R)/2; } # cooling effect: reduce thermal energy by factor (1-m.CoolingFactor)^dt LThermalEnergy = LThermalEnergy * math.exp(me.nCoolFactor * dt); RThermalEnergy = RThermalEnergy * math.exp(me.nCoolFactor * dt); setprop("gear/Lbrake-thermal-energy",LThermalEnergy); setprop("gear/Rbrake-thermal-energy",RThermalEnergy); if ((LThermalEnergy>1)and(!me.LSmokeActive)) { # start smoke processing me.LSmokeActive = 1; settimer(func { BrakeSys.Lsmoke(); },0); } if ((RThermalEnergy>1)and(!me.RSmokeActive)) { # start smoke processing me.RSmokeActive = 1; settimer(func { BrakeSys.Rsmoke(); },0); } } me.LastSimTime = CurrentTime; # 5 updates per second are good enough settimer(func { BrakeSys.update(); },0.2); }, # smoke processing Lsmoke : func() { if ((me.LSmokeActive)and(getprop("gear/Lbrake-thermal-energy")>1)) { # make density of smoke effect depend on energy level var LSmokeDelay=0; var LThermalEnergy = getprop("gear/Lbrake-thermal-energy"); if (LThermalEnergy < 1.5) LSmokeDelay=(1.5-LThermalEnergy); # No smoke when gear retracted var LSmokeValue = (getprop("gear/gear[1]/position-norm")>0.5); # toggle smoke to interpolate different densities if (LSmokeDelay>0.05) { me.LSmokeToggle = !me.LSmokeToggle; if (!me.LSmokeToggle) LSmokeValue = 0; else LSmokeDelay = 0; } setprop("gear/Lbrake-smoke",LSmokeValue); settimer(func { BrakeSys.Lsmoke(); },LSmokeDelay); } else { # stop smoke processing setprop("gear/Lbrake-smoke",0); setprop("sim/animation/fire-services",0); me.LSmokeActive = 0; } if (getprop("gear/Lbrake-thermal-energy") > 1.5) setprop("sim/animation/fire-services",1); else setprop("sim/animation/fire-services",0); }, # smoke processing Rsmoke : func() { if ((me.RSmokeActive)and(getprop("gear/Rbrake-thermal-energy")>1)) { # make density of smoke effect depend on energy level var RSmokeDelay=0; var RThermalEnergy = getprop("gear/Rbrake-thermal-energy"); if (RThermalEnergy < 1.5) RSmokeDelay=(1.5-RThermalEnergy); # No smoke when gear retracted var RSmokeValue = (getprop("gear/gear[2]/position-norm")>0.5); # toggle smoke to interpolate different densities if (RSmokeDelay>0.05) { me.RSmokeToggle = !me.RSmokeToggle; if (!me.RSmokeToggle) RSmokeValue = 0; else RSmokeDelay = 0; } setprop("gear/Rbrake-smoke",RSmokeValue); settimer(func { BrakeSys.Rsmoke(); },RSmokeDelay); } else { # stop smoke processing setprop("gear/Rbrake-smoke",0); me.RSmokeActive = 0; } if (getprop("gear/Rbrake-thermal-energy") > 1.5) setprop("sim/animation/fire-services",1); else setprop("sim/animation/fire-services",0); }, }; var BrakeSys = BrakeSystem.new(); setlistener("sim/signals/fdm-initialized", # executed on _every_ FDM reset (but not installing new listeners) func(idle) { BrakeSys.reset(); }, 0,0); settimer(func() { BrakeSys.update(); }, 5);