Fix Inverted 777- engine paintings and typo
[fg:toms-fgdata.git] / Aircraft / 777 / Nasal / warnsystem.nas
1 ##########################################################################
2 # Warning Electronic System
3 # 2010, Thorsten Brehm
4 #
5 # The Boeing 777's warning electronic system (WES) uses two redundant
6 # warning electronic units (WEUs).
7 #
8 # The WEUs control:
9 #   - Master warning light
10 #   - Aural alerts
11 #   - Landing/takeoff configuration warnings
12 #   - Speedbrake alert
13 #   - Altitude alerts
14 #   - Stall warning
15 #   - Stick shaker
16 #   - Speed tape parameter calculation
17 #
18 ##########################################################################
19
20 # TODOs:
21 #  Check overspeed (EICAS message "OVERSPEED" + clicking sound)
22 #  Need more detailed flap/speed and flap/speed/stall envelopes
23
24 ##############################################
25 # WEU specific class
26 # ie: var Weu = WEU.new("instrumentation/weu");
27 ##############################################
28 var WEU =
29 {
30     new : func(prop1)
31     {
32         var m = { parents : [WEU]};
33         m.weu = props.globals.getNode(prop1);
34
35         # output lights
36         m.master_warning = m.weu.initNode("light/master-warning", 0,"BOOL");
37         m.master_caution = m.weu.initNode("light/master-caution", 0,"BOOL");
38         m.serviceable    = m.weu.initNode("serviceable", 1,"BOOL");
39         # output sounds
40         m.siren        = m.weu.initNode("sound/config-warning",   0,"BOOL");
41         m.stallhorn    = m.weu.initNode("sound/stall-horn", 0,"BOOL");
42         m.apwarning    = m.weu.initNode("sound/autopilot-warning", 0,"BOOL");
43         # actuators
44         m.stickshaker  = m.weu.initNode("actuators/stick-shaker",0,"BOOL");
45         # status information
46                 m.takeoff_mode = m.weu.initNode("state/takeoff-mode",1,"BOOL");
47         m.stallspeed   = m.weu.initNode("state/stall-speed",-100,"DOUBLE");
48         m.targetspeed   = m.weu.initNode("state/target-speed",-100,"DOUBLE");
49                 m.stall_warning = m.weu.initNode("state/stall-warning", 0, "BOOL");
50                 m.vref = m.weu.initNode("state/vref",0,"DOUBLE");
51                 m.v1 = m.weu.initNode("state/v1",0,"DOUBLE");
52                 m.vr = m.weu.initNode("state/vr",0,"DOUBLE");
53                 m.v2 = m.weu.initNode("state/v2",0,"DOUBLE");
54                 m.flap = m.weu.initNode("state/flap",0,"DOUBLE");
55                 m.fl1 = m.weu.initNode("state/fl1",0,"DOUBLE");
56                 m.fl5 = m.weu.initNode("state/fl5",0,"DOUBLE");
57                 m.fl15 = m.weu.initNode("state/fl15",0,"DOUBLE");
58                 m.flap_on = m.weu.initNode("state/flap-on",0,"BOOL");
59                 m.fl1_on = m.weu.initNode("state/fl1-on",0,"BOOL");
60                 m.fl5_on = m.weu.initNode("state/fl5-on",0,"BOOL");
61                 m.fl15_on = m.weu.initNode("state/fl15-on",0,"BOOL");
62         # EICAS output 
63         m.msgs_alert   = [];
64         m.msgs_caution = [];
65         m.msgs_info    = [];
66
67         # inputs
68         m.node_flap_override = props.globals.getNode("instrumentation/mk-viii/outputs/discretes/flap-override");
69         m.node_radio_alt     = props.globals.getNode("position/gear-agl-ft");
70         m.node_flaps_tgt     = props.globals.getNode("controls/flight/flaps");
71         m.node_flaps         = props.globals.getNode("surface-positions/flap-pos-norm");
72         m.node_speed         = props.globals.getNode("velocities/airspeed-kt");
73
74         # input values
75         m.enabled       = 0;
76         m.throttle      = 0;
77         m.radio_alt     = 0;
78         m.flaps_tgt     = 0;
79         m.flaps         = 0;
80         m.speedbrake    = 0;
81         m.spdbrk_armed  = 0;
82         m.parkbrake     = 0;
83         m.speed         = 0;
84         m.reverser      = 0;
85         m.apu_running   = 0;
86         m.gear_down     = 0;
87         m.gear_override = 0;
88         m.flap_override = 0;
89         m.ap_mode       = 0;
90         m.ap_disengaged = 0;
91         me.rudder_trim  = 0;
92         me.elev_trim    = 0;
93
94         # internal states
95         m.active_warnings = 0;
96         m.active_caution  = 0;
97         m.warn_mute       = 0;
98
99         # add some listeners
100         setlistener("controls/gear/gear-down",          func { Weu.update_listener_inputs() } );
101         setlistener("controls/flight/speedbrake",       func { Weu.update_listener_inputs() } );
102         setlistener("controls/flight/speedbrake-lever", func { Weu.update_listener_inputs() } );
103         setlistener("controls/gear/brake-parking",      func { Weu.update_listener_inputs() } );
104         setlistener("controls/engines/engine/reverser", func { Weu.update_listener_inputs() } );
105         setlistener("controls/electric/APU-generator",  func { Weu.update_listener_inputs() } );
106         setlistener("/systems/electrical/outputs/avionics",func { Weu.update_listener_inputs() } );
107         setlistener("controls/flight/rudder-trim",      func { Weu.update_listener_inputs() } );
108         setlistener("controls/flight/elevator-trim",    func { Weu.update_listener_inputs() } );
109         setlistener("sim/freeze/replay-state",          func { Weu.update_listener_inputs() } );
110         setlistener(prop1 ~ "/serviceable",             func { Weu.update_listener_inputs() } );
111
112         setlistener("instrumentation/mk-viii/inputs/discretes/gear-override", func { Weu.update_listener_inputs() } );
113         setlistener("controls/engines/engine/throttle", func { Weu.update_throttle_input() } );
114         setlistener("instrumentation/afds/inputs/AP",     func { Weu.update_ap_mode();});
115         m.update_listener_inputs();
116         
117         # update inputs now and then...
118         settimer(weu_update_feeder,0.5);
119
120         print("Warning Electronic System ... ok");
121         return m;
122     },
123
124 #### mute ####
125     mute_warnings : func
126     {
127        me.warn_mute = 1;
128     },
129
130 #### takeoff config warnings ####
131     takeoff_config_warnings : func
132     {
133         if (me.speed >= getprop("instrumentation/afds/max-airspeed-kts")+5)
134             append(me.msgs_alert,">OVERSPEED");
135
136         if (me.radio_alt<=30)
137         {
138            # T/O warnings
139
140            # 777: T/O warnings trigger when either throttle is at least at 0.667
141            # 777: T/O warnings disabled after rotation with at lease 5 degrees nose-up
142            if ((me.throttle>=0.667)and
143                (me.gear_down)and
144                (!me.reverser)and
145                (getprop("orientation/pitch-deg")<5))
146            {
147                # Take-off attempt!
148                if ((!me.flap_override)and
149                    ((me.flaps<0.16)or(me.flaps>0.7)))
150                  append(me.msgs_alert,">CONFIG FLAPS");
151
152                # 777 manual: The EICAS warning message CONFIG SPOILERS indicates
153                # that the speedbrake lever is not in its down detent
154                # when either the left or right engine thrust exceeds the
155                # takeoff threshold and the airplane is on the ground.
156                if (me.speedbrake>0.1)
157                  append(me.msgs_alert,">CONFIG SPOILERS");
158
159                # 777 manual: Rudder trim must be within 2 units from center at T/O.
160                if (abs(me.rudder_trim)>0.04)
161                  append(me.msgs_alert,">CONFIG RUDDER TRIM");
162
163                if (abs(me.elev_trim)>0.04)
164                  append(me.msgs_alert,">CONFIG ELEV TRIM");
165            }
166         }
167     },
168
169 #### approach config warnings ####
170     approach_config_warnings : func
171     {
172         # approach warnings below 800ft when thrust lever in idle...
173         # ... and flaps in landing configuration
174         if ((me.radio_alt<800)and
175             (me.throttle<0.5)and
176             (me.flaps>0.6))
177         {
178             if ((!me.gear_override)and
179                 (!me.gear_down))
180             {
181                 append(me.msgs_alert,">CONFIG GEAR");
182             }
183          }
184     },
185
186 #### caution messages ####
187     caution_messages : func
188     {
189         if (me.ap_disengaged)
190             append(me.msgs_caution,">AP DISCONNECT");
191                 if(getprop("instrumentation/afds/inputs/vnav-mcp-reset") == 1)
192                 {
193             append(me.msgs_caution,">FMC MESSAGE");
194                 }
195         if ((getprop("/gear/brake-thermal-energy") or 0)>1)
196             append(me.msgs_caution,">L R BRAKE OVERHEAT");
197         if (me.speedbrake)
198         {
199             # 777 manual: EICAS caution message SPEEDBRAKE EXTENDED indicates
200             # that the speedbrake lever is more than the armed position with
201             # the airplane above 15 feet of radio altitude and one of these conditions:
202             # Airplane below 800 feet radio altitude, Flaps at landing position, thrust lever is more than 5 degrees above idle.
203             if ((me.radio_alt>15)and
204                 (me.radio_alt<800)and
205                 (me.throttle>0.1)and
206                 (me.flaps>0.6))
207                 append(me.msgs_caution,">SPEEDBRAKE EXTENDED");
208         }
209         if (me.parkbrake)
210             append(me.msgs_info,">PARK BRK SET");
211         if (me.reverser)
212             append(me.msgs_info,">L R THRUST REV SET");
213         if (me.spdbrk_armed)
214             append(me.msgs_info,">SPEEDBRAKE ARMED");
215         if (me.apu_running)
216             append(me.msgs_info,">APU RUNNING");
217     },
218
219 #### stall warnings and other sounds ####
220     update_sounds : func
221     {
222         var target_speed = 0;
223         var horn   = 0;
224         var shaker = 0;
225         var siren  = (size(me.msgs_alert)!=0);
226         var vgrosswt = math.sqrt(getprop("/yasim/gross-weight-lbs")/730284);
227                 me.vref.setValue(vgrosswt * 166);
228         # calculate Flap Maneuver Speed
229                 me.flap.setValue(vgrosswt * 166 + 80);
230                 me.fl1.setValue(vgrosswt * 166 + 60);
231                 me.fl5.setValue(vgrosswt * 166 + 40);
232                 me.fl15.setValue(vgrosswt * 166 + 20);
233
234         # calculate stall speed
235                 var vref_table = [
236                         [0, vgrosswt * 166 + 80],
237                         [0.033, vgrosswt * 166 + 60],
238                         [0.166, vgrosswt * 166 + 40],
239                         [0.500, vgrosswt * 166 + 20],
240                         [0.666, vgrosswt * 180],
241                         [0.833, vgrosswt * 174],
242                         [1.000, vgrosswt * 166]];
243
244                 var vref_flap = interpolate_table(vref_table, me.flaps);
245                 var stallspeed = (vref_flap - 10 + getprop("instrumentation/altimeter/indicated-altitude-ft") / 1000);
246                 me.stallspeed.setValue(stallspeed);
247
248                 var weight_diff = getprop("/yasim/gross-weight-lbs")-308700;
249                 me.v1.setValue(weight_diff*0.00018424+92);
250                 me.vr.setValue(weight_diff*0.000164399+104);
251                 me.v2.setValue(weight_diff*0.000138889+119);
252
253         # calculate flap target speed
254         if (me.flaps_tgt<0.01)            # flap up
255         {
256                         if(!(getprop("gear/gear[1]/wow") or getprop("gear/gear[2]/wow")))
257                         {
258                                 me.takeoff_mode.setValue(0);
259                         }
260         }
261         elsif (me.flaps_tgt<0.034)        # flap 1
262         {
263             target_speed = me.flap.getValue();
264         }
265         elsif (me.flaps_tgt<0.167)        # flap 5
266         {
267             target_speed = me.fl1.getValue();
268                         if(getprop("gear/gear[1]/wow") and getprop("gear/gear[2]/wow"))
269                         {
270                                 me.takeoff_mode.setValue(1);
271                         }
272         }
273         elsif (me.flaps_tgt<0.501)        # flap 15
274         {
275             target_speed = me.fl5.getValue();
276         }
277         elsif (me.flaps_tgt<0.667)        # flap 20
278         {
279             target_speed = me.fl15.getValue();
280         }
281         elsif (me.flaps_tgt<0.834)        # flap 25
282         {
283             target_speed = vgrosswt * 180;
284         }
285         else                          # flap 30
286         {
287             target_speed = vgrosswt * 174;
288         }
289                 target_speed += 5;
290
291                 if(target_speed > 250) target_speed = 250;
292         me.targetspeed.setValue(target_speed);
293
294                 # Flap placard speed display switch
295                 target_speed = getprop("autopilot/settings/target-speed-kt");
296                 if(abs(target_speed - me.flap.getValue()) < 30) me.flap_on.setValue(1);
297                 else me.flap_on.setValue(0);
298                 if(abs(target_speed - me.fl1.getValue()) < 30) me.fl1_on.setValue(1);
299                 else me.fl1_on.setValue(0);
300                 if(abs(target_speed - me.fl5.getValue()) < 30) me.fl5_on.setValue(1);
301                 else me.fl5_on.setValue(0);
302                 if(abs(target_speed - me.fl15.getValue()) < 30) me.fl15_on.setValue(1);
303                 else me.fl15_on.setValue(0);
304                 if(me.flaps_tgt >= 0.833)
305                 {
306                         me.flap_on.setValue(0);
307                         me.fl1_on.setValue(0);
308                         me.fl5_on.setValue(0);
309                         me.fl15_on.setValue(0);
310                 }
311
312                 # Stall warning display switch
313                 if((me.stall_warning.getValue() == 0) and (getprop("position/gear-agl-ft") > 400))
314                 {
315                         me.stall_warning.setValue(1);
316                 }
317                 elsif(getprop("gear/gear[1]/wow") or getprop("gear/gear[2]/wow"))
318                 {
319                         me.stall_warning.setValue(0);
320                 }
321
322         if ((me.speed <= (stallspeed - 5))
323                                 and (me.enabled)
324                                 and (me.stall_warning.getValue() == 1))
325         {
326             horn = 1;
327             shaker = 1;
328             # disable autopilot when stalled
329                         setprop("instrumentation/afds/inputs/AP", 0);
330         }
331
332         var caution_state = (size(me.msgs_caution)>0);
333
334         if ((me.active_warnings)or(me.active_caution)or(caution_state)or(siren)or(shaker)or(horn))
335         {
336             if (horn) siren=0;
337             me.siren.setBoolValue(siren and (!me.warn_mute));
338             me.stallhorn.setBoolValue(horn and (!me.warn_mute));
339             me.stickshaker.setBoolValue(shaker);
340             
341             me.active_warnings = (siren or shaker or horn);
342             me.active_caution = caution_state;
343             
344             if (!me.active_warnings) me.warn_mute = 0;
345             
346             me.master_warning.setBoolValue(me.active_warnings);
347             me.master_caution.setBoolValue(me.active_caution);
348         }
349         else
350             me.warn_mute = 0;
351     },
352
353 #### update listener inputs ####
354     update_listener_inputs : func()
355     {
356         # be nice to sim: some inputs rarely change. use listeners.
357         me.enabled       = (getprop("/systems/electrical/outputs/avionics") and
358                             (getprop("sim/freeze/replay-state")!=1) and
359                             me.serviceable.getValue());
360         me.speedbrake    = getprop("controls/flight/speedbrake");
361         me.spdbrk_armed  = (getprop("controls/flight/speedbrake-lever")==1); #2=extended (not armed...)
362         me.reverser      = getprop("controls/engines/engine/reverser");
363         me.gear_down     = getprop("controls/gear/gear-down");
364         me.parkbrake     = getprop("controls/gear/brake-parking");
365         me.gear_override = getprop("instrumentation/mk-viii/inputs/discretes/gear-override");
366         me.apu_running   = getprop("controls/electric/APU-generator");
367         me.rudder_trim   = getprop("controls/flight/rudder-trim");
368         me.elev_trim     = getprop("controls/flight/elevator-trim");
369     },
370
371 #### update throttle input ####
372     update_throttle_input : func()
373     {
374         me.throttle = getprop("controls/engines/engine/throttle");
375     },
376
377 #### update autopilot mode ####
378     update_ap_mode : func()
379     {
380        var ap_mode = getprop("instrumentation/afds/inputs/AP");
381        if ((!ap_mode)and(me.ap_mode))
382        {
383            # AP has disengaged
384            me.ap_disengaged = 1;
385            # display "AP DISCONNECT" for 5 seconds
386            settimer(func { Weu.update_ap_mode() }, 5);
387        }
388        else
389        {
390            me.ap_disengaged = 0;
391        }
392        me.apwarning.setBoolValue(me.ap_disengaged);
393        me.ap_mode = ap_mode;
394     },
395
396 #### main WEU update ####
397     update : func()
398     {
399         me.msgs_alert   = [];
400         me.msgs_caution = [];
401         me.msgs_info    = [];
402
403         if (me.enabled)
404         {
405             me.radio_alt  = me.node_radio_alt.getValue();
406             me.flaps_tgt  = me.node_flaps_tgt.getValue();
407             me.flaps      = me.node_flaps.getValue();
408             me.speed      = me.node_speed.getValue();
409             me.flap_override = me.node_flap_override.getBoolValue();
410
411             me.takeoff_config_warnings();
412             me.approach_config_warnings();
413             me.caution_messages();
414
415             if ((me.parkbrake>0.1)and((me.throttle>=0.667)or(me.radio_alt>30)))
416                 append(me.msgs_alert,">CONFIG PARK BRK");
417         }
418
419         me.update_sounds();
420
421         # update EICAS message display
422         Efis.update_eicas(me.msgs_alert,me.msgs_caution,me.msgs_info);
423
424         # be nice: updates every 0.5 seconds is enough
425         settimer(weu_update_feeder,0.5);
426     },
427 };
428
429 # interpolates a value
430 var interpolate_table = func(table, v)
431  {
432  var x = 0;
433  forindex (i; table)
434   {
435   if (v >= table[i][0])
436    {
437    x = i + 1 < size(table) ? (v - table[i][0]) / (table[i + 1][0] - table[i][0]) * (table[i + 1][1] - table[i][1]) + table[i][1] : table[i][1];
438    }
439   }
440  return x;
441  };
442 ##############################################
443 # timer callbacks
444 ##############################################
445 weu_update_feeder = func
446 {
447     Weu.update();
448 }
449
450 ##############################################
451 # main
452 ##############################################
453 Weu = WEU.new("instrumentation/weu");