]> git.mxchange.org Git - flightgear.git/blob - src/Instrumentation/mk_viii.cxx
942c679041bcd2cfd45b34bf428c6bc6a6867d15
[flightgear.git] / src / Instrumentation / mk_viii.cxx
1 // mk_viii.cxx -- Honeywell MK VIII EGPWS emulation
2 //
3 // Written by Jean-Yves Lefort, started September 2005.
4 //
5 // Copyright (C) 2005, 2006  Jean-Yves Lefort - jylefort@FreeBSD.org
6 //
7 // This program is free software; you can redistribute it and/or
8 // modify it under the terms of the GNU General Public License as
9 // published by the Free Software Foundation; either version 2 of the
10 // License, or (at your option) any later version.
11 //
12 // This program is distributed in the hope that it will be useful, but
13 // WITHOUT ANY WARRANTY; without even the implied warranty of
14 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15 // General Public License for more details.
16 //
17 // You should have received a copy of the GNU General Public License
18 // along with this program; if not, write to the Free Software
19 // Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
20 //
21 ///////////////////////////////////////////////////////////////////////////////
22 //
23 // References:
24 //
25 //      [PILOT]         Honeywell International Inc., "MK VI and MK VIII,
26 //                      Enhanced Ground Proximity Warning System (EGPWS),
27 //                      Pilot's Guide", May 2004
28 //
29 //                      http://www.egpws.com/engineering_support/documents/pilot_guides/060-4314-000.pdf
30 //
31 //      [SPEC]          Honeywell International Inc., "Product Specification
32 //                      for the MK VI and MK VIII Enhanced Ground Proximity
33 //                      Warning System (EGPWS)", June 2004
34 //
35 //                      http://www.egpws.com/engineering_support/documents/specs/965-1180-601.pdf
36 //
37 //      [INSTALL]       Honeywell Inc., "MK VI, MK VIII, Enhanced Ground
38 //                      Proximity Warning System (Class A TAWS), Installation
39 //                      Design Guide", July 2003
40 //
41 //                      http://www.egpws.com/engineering_support/documents/install/060-4314-150.pdf
42 //
43 // Notes:
44 //
45 //      [1]             [SPEC] does not specify the "must be airborne"
46 //                      condition; we use it to make sure that the alert
47 //                      will not trigger when on the ground, since it would
48 //                      make no sense.
49
50 #ifdef _MSC_VER
51 #  pragma warning( disable: 4355 )
52 #endif
53
54 #ifdef HAVE_CONFIG_H
55 #  include <config.h>
56 #endif
57
58 #include <stdio.h>
59 #include <string.h>
60 #include <assert.h>
61 #include <math.h>
62
63 #include <string>
64 #include <sstream>
65
66 #include <simgear/constants.h>
67 #include <simgear/sg_inlines.h>
68 #include <simgear/debug/logstream.hxx>
69 #include <simgear/math/sg_geodesy.hxx>
70 #include <simgear/math/sg_random.h>
71 #include <simgear/misc/sg_path.hxx>
72 #include <simgear/sound/soundmgr_openal.hxx>
73 #include <simgear/structure/exception.hxx>
74
75 SG_USING_STD(string);
76
77 #include "Airports/runways.hxx"
78 #include "Airports/simple.hxx"
79 #ifndef _MSC_VER
80 #  include "Include/version.h"
81 #endif
82 #include "Main/fg_props.hxx"
83 #include "Main/globals.hxx"
84 #include "instrument_mgr.hxx"
85 #include "mk_viii.hxx"
86
87 ///////////////////////////////////////////////////////////////////////////////
88 // constants //////////////////////////////////////////////////////////////////
89 ///////////////////////////////////////////////////////////////////////////////
90
91 #define GLIDESLOPE_DOTS_TO_DDM          0.0875          // [INSTALL] 6.2.30
92 #define GLIDESLOPE_DDM_TO_DOTS          (1 / GLIDESLOPE_DOTS_TO_DDM)
93
94 #define LOCALIZER_DOTS_TO_DDM           0.0775          // [INSTALL] 6.2.33
95 #define LOCALIZER_DDM_TO_DOTS           (1 / LOCALIZER_DOTS_TO_DDM)
96
97 ///////////////////////////////////////////////////////////////////////////////
98 // helpers ////////////////////////////////////////////////////////////////////
99 ///////////////////////////////////////////////////////////////////////////////
100
101 #define assert_not_reached()    assert(false)
102 #define n_elements(_array)      (sizeof(_array) / sizeof((_array)[0]))
103 #define test_bits(_bits, _test) (((_bits) & (_test)) != 0)
104
105 #define mk_node(_name)          (mk->properties_handler.external_properties._name)
106
107 #define mk_dinput_feed(_name)   (mk->io_handler.input_feeders.discretes._name)
108 #define mk_dinput(_name)        (mk->io_handler.inputs.discretes._name)
109 #define mk_ainput_feed(_name)   (mk->io_handler.input_feeders.arinc429._name)
110 #define mk_ainput(_name)        (mk->io_handler.inputs.arinc429._name)
111 #define mk_doutput(_name)       (mk->io_handler.outputs.discretes._name)
112 #define mk_aoutput(_name)       (mk->io_handler.outputs.arinc429._name)
113 #define mk_data(_name)          (mk->io_handler.data._name)
114
115 #define mk_voice(_name)         (mk->voice_player.voices._name)
116 #define mk_altitude_voice(_i)   (mk->voice_player.voices.altitude_callouts[(_i)])
117
118 #define mk_alert(_name)         (AlertHandler::ALERT_ ## _name)
119 #define mk_alert_flag(_name)    (AlertHandler::ALERT_FLAG_ ## _name)
120 #define mk_set_alerts           (mk->alert_handler.set_alerts)
121 #define mk_unset_alerts         (mk->alert_handler.unset_alerts)
122 #define mk_repeat_alert         (mk->alert_handler.repeat_alert)
123 #define mk_test_alert(_name)    (mk_test_alerts(mk_alert(_name)))
124 #define mk_test_alerts(_test)   (test_bits(mk->alert_handler.alerts, (_test)))
125
126 const double MK_VIII::TCFHandler::k = 0.25;
127
128 static double
129 modify_amplitude (double amplitude, double dB)
130 {
131   return amplitude * pow(10.0, dB / 20.0);
132 }
133
134 static double
135 heading_add (double h1, double h2)
136 {
137   double result = h1 + h2;
138   if (result >= 360)
139     result -= 360;
140   return result;
141 }
142
143 static double
144 heading_substract (double h1, double h2)
145 {
146   double result = h1 - h2;
147   if (result < 0)
148     result += 360;
149   return result;
150 }
151
152 static double
153 get_heading_difference (double h1, double h2)
154 {
155   double diff = h1 - h2;
156
157   if (diff < -180)
158     diff += 360;
159   else if (diff > 180)
160     diff -= 360;
161
162   return fabs(diff);
163 }
164
165 static double
166 get_reciprocal_heading (double h)
167 {
168   return heading_add(h, 180);
169 }
170
171 // Searches for the closest airport whose Manhattan distance to
172 // @lat,@lon is inferior to @min_manhattan_distance (expressed in
173 // degrees) and for which @test_airport returns true. Returns NULL if
174 // no airport was found.
175 template <class C>
176 static const FGAirport *
177 get_closest_airport (double lat,
178                      double lon,
179                      double min_manhattan_distance,
180                      C &obj,
181                      bool (C::*test_airport) (const FGAirport *))
182 {
183   const FGAirport *airport = NULL;
184   const airport_list *airport_list = globals->get_airports()->getAirportList();
185
186   for (size_t i = 0; i < airport_list->size(); i++)
187     {
188       const FGAirport *a = (*airport_list)[i];
189       double dist = fabs(lat - a->getLatitude()) + fabs(lon - a->getLongitude());
190       if (dist < min_manhattan_distance && (obj.*test_airport)(a))
191         {
192           airport = a;
193           min_manhattan_distance = dist;
194         }
195     }
196
197   return airport;
198 }
199
200 ///////////////////////////////////////////////////////////////////////////////
201 // PropertiesHandler //////////////////////////////////////////////////////////
202 ///////////////////////////////////////////////////////////////////////////////
203
204 void
205 MK_VIII::PropertiesHandler::init ()
206 {
207   mk_node(ai_caged) = fgGetNode("/instrumentation/attitude-indicator/caged-flag", true);
208   mk_node(ai_roll) = fgGetNode("/instrumentation/attitude-indicator/indicated-roll-deg", true);
209   mk_node(ai_serviceable) = fgGetNode("/instrumentation/attitude-indicator/serviceable", true);
210   mk_node(altimeter_altitude) = fgGetNode("/instrumentation/altimeter/indicated-altitude-ft", true);
211   mk_node(altimeter_serviceable) = fgGetNode("/instrumentation/altimeter/serviceable", true);
212   mk_node(altitude) = fgGetNode("/position/altitude-ft", true);
213   mk_node(altitude_agl) = fgGetNode("/position/altitude-agl-ft", true);
214   mk_node(asi_serviceable) = fgGetNode("/instrumentation/airspeed-indicator/serviceable", true);
215   mk_node(asi_speed) = fgGetNode("/instrumentation/airspeed-indicator/indicated-speed-kt", true);
216   mk_node(autopilot_heading_lock) = fgGetNode("/autopilot/locks/heading", true);
217   mk_node(flaps) = fgGetNode("/controls/flight/flaps", true);
218   mk_node(gear_down) = fgGetNode("/controls/gear/gear-down", true);
219   mk_node(latitude) = fgGetNode("/position/latitude-deg", true);
220   mk_node(longitude) = fgGetNode("/position/longitude-deg", true);
221   mk_node(nav0_cdi_serviceable) = fgGetNode("/instrumentation/nav/cdi/serviceable", true);
222   mk_node(nav0_gs_distance) = fgGetNode("/instrumentation/nav/gs-distance", true);
223   mk_node(nav0_gs_needle_deflection) = fgGetNode("/instrumentation/nav/gs-needle-deflection", true);
224   mk_node(nav0_gs_serviceable) = fgGetNode("/instrumentation/nav/gs/serviceable", true);
225   mk_node(nav0_has_gs) = fgGetNode("/instrumentation/nav/has-gs", true);
226   mk_node(nav0_heading_needle_deflection) = fgGetNode("/instrumentation/nav/heading-needle-deflection", true);
227   mk_node(nav0_in_range) = fgGetNode("/instrumentation/nav/in-range", true);
228   mk_node(nav0_nav_loc) = fgGetNode("/instrumentation/nav/nav-loc", true);
229   mk_node(nav0_serviceable) = fgGetNode("/instrumentation/nav/serviceable", true);
230   mk_node(power) = fgGetNode(("/systems/electrical/outputs/" + mk->name).c_str(), mk->num, true);
231   mk_node(replay_state) = fgGetNode("/sim/freeze/replay-state", true);
232   mk_node(vs) = fgGetNode("/velocities/vertical-speed-fps", true);
233 }
234
235 void
236 MK_VIII::PropertiesHandler::unbind ()
237 {
238   vector<SGPropertyNode *>::iterator iter;
239
240   for (iter = tied_properties.begin(); iter != tied_properties.end(); iter++)
241     (*iter)->untie();
242
243   tied_properties.clear();
244 }
245
246 ///////////////////////////////////////////////////////////////////////////////
247 // PowerHandler ///////////////////////////////////////////////////////////////
248 ///////////////////////////////////////////////////////////////////////////////
249
250 void
251 MK_VIII::PowerHandler::bind (SGPropertyNode *node)
252 {
253   mk->properties_handler.tie(node, "serviceable", SGRawValuePointer<bool>(&serviceable));
254 }
255
256 bool
257 MK_VIII::PowerHandler::handle_abnormal_voltage (bool abnormal,
258                                                 Timer *timer,
259                                                 double max_duration)
260 {
261   if (abnormal)
262     {
263       if (timer->start_or_elapsed() >= max_duration)
264         return true;            // power loss
265     }
266   else
267     timer->stop();
268
269   return false;
270 }
271
272 void
273 MK_VIII::PowerHandler::update ()
274 {
275   double voltage = mk_node(power)->getDoubleValue();
276   bool now_powered = true;
277
278   // [SPEC] 3.2.1
279
280   if (! serviceable)
281     now_powered = false;
282   if (voltage < 15)
283     now_powered = false;
284   if (handle_abnormal_voltage(voltage < 20.5, &low_surge_timer, 0.03))
285     now_powered = false;
286   if (handle_abnormal_voltage(voltage < 22.0 || voltage > 30.3, &abnormal_timer, 300))
287     now_powered = false;
288   if (handle_abnormal_voltage(voltage > 32.2, &high_surge_timer, 1))
289     now_powered = false;
290   if (handle_abnormal_voltage(voltage > 37.8, &very_high_surge_timer, 0.1))
291     now_powered = false;
292   if (voltage > 46.3)
293     now_powered = false;
294
295   if (powered)
296     {
297       // [SPEC] 3.5.2
298
299       if (now_powered)
300         power_loss_timer.stop();
301       else
302         {
303           if (power_loss_timer.start_or_elapsed() >= 0.2)
304             power_off();
305         }
306     }
307   else
308     {
309       if (now_powered)
310         power_on();
311     }
312 }
313
314 void
315 MK_VIII::PowerHandler::power_on ()
316 {
317   powered = true;
318   mk->system_handler.power_on();
319 }
320
321 void
322 MK_VIII::PowerHandler::power_off ()
323 {
324   powered = false;
325   mk->system_handler.power_off();
326   mk->voice_player.stop(VoicePlayer::STOP_NOW);
327   mk->self_test_handler.power_off(); // run before IOHandler::power_off()
328   mk->io_handler.power_off();
329   mk->mode2_handler.power_off();
330   mk->mode6_handler.power_off();
331 }
332
333 ///////////////////////////////////////////////////////////////////////////////
334 // SystemHandler //////////////////////////////////////////////////////////////
335 ///////////////////////////////////////////////////////////////////////////////
336
337 void
338 MK_VIII::SystemHandler::power_on ()
339 {
340   state = STATE_BOOTING;
341
342   // [SPEC] 3.5.2 mentions a 20 seconds maximum boot delay. We use a
343   // random delay between 3 and 5 seconds.
344
345   boot_delay = 3 + sg_random() * 2;
346   boot_timer.start();
347 }
348
349 void
350 MK_VIII::SystemHandler::power_off ()
351 {
352   state = STATE_OFF;
353
354   boot_timer.stop();
355 }
356
357 void
358 MK_VIII::SystemHandler::update ()
359 {
360   if (state == STATE_BOOTING)
361     {
362       if (boot_timer.elapsed() >= boot_delay)
363         {
364           last_replay_state = mk_node(replay_state)->getIntValue();
365
366           mk->configuration_module.boot();
367
368           mk->io_handler.boot();
369           mk->fault_handler.boot();
370           mk->alert_handler.boot();
371
372           // inputs are needed by the following boot() routines
373           mk->io_handler.update_inputs();
374
375           mk->mode2_handler.boot();
376           mk->mode6_handler.boot();
377
378           state = STATE_ON;
379
380           mk->io_handler.post_boot();
381         }
382     }
383   else if (state != STATE_OFF && mk->configuration_module.state == ConfigurationModule::STATE_OK)
384     {
385       // If the replay state changes, switch to reposition mode for 3
386       // seconds ([SPEC] 6.0.5) to avoid spurious alerts.
387
388       int replay_state = mk_node(replay_state)->getIntValue();
389       if (replay_state != last_replay_state)
390         {
391           mk->alert_handler.reposition();
392
393           last_replay_state = replay_state;
394           state = STATE_REPOSITION;
395           reposition_timer.start();
396         }
397
398       if (state == STATE_REPOSITION && reposition_timer.elapsed() >= 3)
399         {
400           // inputs are needed by StateHandler::post_reposition()
401           mk->io_handler.update_inputs();
402
403           mk->state_handler.post_reposition();
404
405           state = STATE_ON;
406         }
407     }
408 }
409
410 ///////////////////////////////////////////////////////////////////////////////
411 // ConfigurationModule ////////////////////////////////////////////////////////
412 ///////////////////////////////////////////////////////////////////////////////
413
414 MK_VIII::ConfigurationModule::ConfigurationModule (MK_VIII *device)
415   : mk(device)
416 {
417   // arbitrary defaults
418   categories[CATEGORY_AIRCRAFT_MODE_TYPE_SELECT] = 0;
419   categories[CATEGORY_AIR_DATA_INPUT_SELECT] = 1;
420   categories[CATEGORY_POSITION_INPUT_SELECT] = 2;
421   categories[CATEGORY_ALTITUDE_CALLOUTS] = 13;
422   categories[CATEGORY_AUDIO_MENU_SELECT] = 0;
423   categories[CATEGORY_TERRAIN_DISPLAY_SELECT] = 1;
424   categories[CATEGORY_OPTIONS_SELECT_GROUP_1] = 124;
425   categories[CATEGORY_RADIO_ALTITUDE_INPUT_SELECT] = 2;
426   categories[CATEGORY_NAVIGATION_INPUT_SELECT] = 3;
427   categories[CATEGORY_ATTITUDE_INPUT_SELECT] = 6;
428   categories[CATEGORY_HEADING_INPUT_SELECT] = 2;
429   categories[CATEGORY_WINDSHEAR_INPUT_SELECT] = 0;
430   categories[CATEGORY_INPUT_OUTPUT_DISCRETE_TYPE_SELECT] = 7;
431   categories[CATEGORY_AUDIO_OUTPUT_LEVEL] = 0;
432   categories[CATEGORY_UNDEFINED_INPUT_SELECT_1] = 0;
433   categories[CATEGORY_UNDEFINED_INPUT_SELECT_2] = 0;
434   categories[CATEGORY_UNDEFINED_INPUT_SELECT_3] = 0;
435 }
436
437 static double m1_t1_min_agl1 (double vs) { return -1620 - 1.1133 * vs; }
438 static double m1_t1_min_agl2 (double vs) { return -400 - 0.4 * vs; }
439 static double m1_t4_min_agl1 (double vs) { return -1625.47 - 1.1167 * vs; }
440 static double m1_t4_min_agl2 (double vs) { return -0.1958 * vs; }
441
442 static int m3_t1_max_agl (bool flap_override) { return 1500; }
443 static double m3_t1_max_alt_loss (bool flap_override, double agl) { return 5.4 + 0.092 * agl; }
444 static int m3_t2_max_agl (bool flap_override) { return flap_override ? 815 : 925; }
445 static double m3_t2_max_alt_loss (bool flap_override, double agl)
446 {
447   int bias = agl > 700 ? 5 : 0;
448
449   if (flap_override)
450     return (9.0 + 0.184 * agl) + bias;
451   else
452     return (5.4 + 0.092 * agl) + bias;
453 }
454
455 static double m4_t1_min_agl2 (double airspeed) { return -1083 + 8.333 * airspeed; }
456 static double m4_t568_a_min_agl2 (double airspeed) { return -1523 + 11.36 * airspeed; }
457 static double m4_t79_a_min_agl2 (double airspeed) { return -1182 + 11.36 * airspeed; }
458 static double m4_t568_b_min_agl2 (double airspeed) { return -1570 + 11.6 * airspeed; }
459 static double m4_t79_b_min_agl2 (double airspeed) { return -1222 + 11.6 * airspeed; }
460
461 bool
462 MK_VIII::ConfigurationModule::m6_t2_is_bank_angle (Parameter<double> *agl,
463                                                    double abs_roll_deg,
464                                                    bool ap_engaged)
465 {
466   if (ap_engaged)
467     {
468       if (agl->ncd || agl->get() > 122)
469         return abs_roll_deg > 33;
470     }
471   else
472     {
473       if (agl->ncd || agl->get() > 2450)
474         return abs_roll_deg > 55;
475       else if (agl->get() > 150)
476         return agl->get() < 153.33333 * abs_roll_deg - 5983.3333;
477     }
478
479   if (agl->get() > 30)
480     return agl->get() < 4 * abs_roll_deg - 10;
481   else if (agl->get() > 5)
482     return abs_roll_deg > 10;
483
484   return false;
485 }
486
487 bool
488 MK_VIII::ConfigurationModule::m6_t4_is_bank_angle (Parameter<double> *agl,
489                                                    double abs_roll_deg,
490                                                    bool ap_engaged)
491 {
492   if (ap_engaged)
493     {
494       if (agl->ncd || agl->get() > 156)
495         return abs_roll_deg > 33;
496     }
497   else
498     {
499       if (agl->ncd || agl->get() > 210)
500         return abs_roll_deg > 50;
501     }
502
503   if (agl->get() > 10)
504     return agl->get() < 5.7142857 * abs_roll_deg - 75.714286;
505
506   return false;
507 }
508
509 bool
510 MK_VIII::ConfigurationModule::read_aircraft_mode_type_select (int value)
511 {
512   static const Mode1Handler::EnvelopesConfiguration m1_t1 =
513     { false, 10, m1_t1_min_agl1, 284, m1_t1_min_agl2, 2450 };
514   static const Mode1Handler::EnvelopesConfiguration m1_t4 =
515     { true, 50, m1_t4_min_agl1, 346, m1_t4_min_agl2, 1958 };
516
517   static const Mode2Handler::Configuration m2_t1 = { 190, 280 };
518   static const Mode2Handler::Configuration m2_t4 = { 220, 310 };
519   static const Mode2Handler::Configuration m2_t5 = { 220, 310 };
520
521   static const Mode3Handler::Configuration m3_t1 =
522     { 30, m3_t1_max_agl, m3_t1_max_alt_loss };
523   static const Mode3Handler::Configuration m3_t2 = 
524     { 50, m3_t2_max_agl, m3_t2_max_alt_loss };
525
526   static const Mode4Handler::EnvelopesConfiguration m4_t1_ac =
527     { 190, 250, 500, m4_t1_min_agl2, 1000 };
528   static const Mode4Handler::EnvelopesConfiguration m4_t5_ac =
529     { 178, 200, 500, m4_t568_a_min_agl2, 1000 };
530   static const Mode4Handler::EnvelopesConfiguration m4_t68_ac =
531     { 178, 200, 500, m4_t568_a_min_agl2, 750 };
532   static const Mode4Handler::EnvelopesConfiguration m4_t79_ac =
533     { 148, 170, 500, m4_t79_a_min_agl2, 750 };
534
535   static const Mode4Handler::EnvelopesConfiguration m4_t1_b =
536     { 159, 250, 245, m4_t1_min_agl2, 1000 };
537   static const Mode4Handler::EnvelopesConfiguration m4_t5_b =
538     { 148, 200, 200, m4_t568_b_min_agl2, 1000 };
539   static const Mode4Handler::EnvelopesConfiguration m4_t6_b =
540     { 150, 200, 170, m4_t568_b_min_agl2, 750 };
541   static const Mode4Handler::EnvelopesConfiguration m4_t7_b =
542     { 120, 170, 170, m4_t79_b_min_agl2, 750 };
543   static const Mode4Handler::EnvelopesConfiguration m4_t8_b =
544     { 148, 200, 150, m4_t568_b_min_agl2, 750 };
545   static const Mode4Handler::EnvelopesConfiguration m4_t9_b =
546     { 118, 170, 150, m4_t79_b_min_agl2, 750 };
547
548   static const Mode4Handler::ModesConfiguration m4_t1 = { &m4_t1_ac, &m4_t1_b };
549   static const Mode4Handler::ModesConfiguration m4_t5 = { &m4_t5_ac, &m4_t5_b };
550   static const Mode4Handler::ModesConfiguration m4_t6 = { &m4_t68_ac, &m4_t6_b };
551   static const Mode4Handler::ModesConfiguration m4_t7 = { &m4_t79_ac, &m4_t7_b };
552   static const Mode4Handler::ModesConfiguration m4_t8 = { &m4_t68_ac, &m4_t8_b };
553   static const Mode4Handler::ModesConfiguration m4_t9 = { &m4_t79_ac, &m4_t9_b };
554
555   static Mode6Handler::BankAnglePredicate m6_t2 = m6_t2_is_bank_angle;
556   static Mode6Handler::BankAnglePredicate m6_t4 = m6_t4_is_bank_angle;
557
558   static const IOHandler::FaultsConfiguration f_slow = { 180, 200 };
559   static const IOHandler::FaultsConfiguration f_fast = { 250, 290 };
560
561   static const struct
562   {
563     int                                         type;
564     const Mode1Handler::EnvelopesConfiguration  *m1;
565     const Mode2Handler::Configuration           *m2;
566     const Mode3Handler::Configuration           *m3;
567     const Mode4Handler::ModesConfiguration      *m4;
568     Mode6Handler::BankAnglePredicate            m6;
569     const IOHandler::FaultsConfiguration        *f;
570     int                                         runway_database;
571   } aircraft_types[] = {
572     { 0,        &m1_t4, &m2_t4, &m3_t2, &m4_t6, m6_t4, &f_fast, 2000 },
573     { 1,        &m1_t4, &m2_t4, &m3_t2, &m4_t8, m6_t4, &f_fast, 2000 },
574     { 2,        &m1_t4, &m2_t4, &m3_t2, &m4_t5, m6_t4, &f_fast, 2000 },
575     { 3,        &m1_t4, &m2_t5, &m3_t2, &m4_t7, m6_t4, &f_slow, 2000 },
576     { 4,        &m1_t4, &m2_t5, &m3_t2, &m4_t9, m6_t4, &f_slow, 2000 },
577     { 254,      &m1_t1, &m2_t1, &m3_t1, &m4_t1, m6_t2, &f_fast, 3500 },
578     { 255,      &m1_t1, &m2_t1, &m3_t1, &m4_t1, m6_t2, &f_fast, 2000 }
579   };
580
581   for (int i = 0; i < n_elements(aircraft_types); i++)
582     if (aircraft_types[i].type == value)
583       {
584         mk->mode1_handler.conf.envelopes = aircraft_types[i].m1;
585         mk->mode2_handler.conf = aircraft_types[i].m2;
586         mk->mode3_handler.conf = aircraft_types[i].m3;
587         mk->mode4_handler.conf.modes = aircraft_types[i].m4;
588         mk->mode6_handler.conf.is_bank_angle = aircraft_types[i].m6;
589         mk->io_handler.conf.faults = aircraft_types[i].f;
590         mk->conf.runway_database = aircraft_types[i].runway_database;
591         return true;
592       }
593
594   state = STATE_INVALID_AIRCRAFT_TYPE;
595   return false;
596 }
597
598 bool
599 MK_VIII::ConfigurationModule::read_air_data_input_select (int value)
600 {
601   // unimplemented
602   return (value >= 0 && value <= 6) || (value >= 10 && value <= 13) || value == 255;
603 }
604
605 bool
606 MK_VIII::ConfigurationModule::read_position_input_select (int value)
607 {
608   if (value == 2)
609     mk->io_handler.conf.use_internal_gps = true;
610   else if ((value >= 0 && value <= 5)
611            || (value >= 10 && value <= 13)
612            || (value == 253)
613            || (value == 255))
614     mk->io_handler.conf.use_internal_gps = false;
615   else
616     return false;
617
618   return true;
619 }
620
621 bool
622 MK_VIII::ConfigurationModule::read_altitude_callouts (int value)
623 {
624   enum
625   {
626     MINIMUMS            = -1,
627     SMART_500           = -2,
628     FIELD_500           = -3,
629     FIELD_500_ABOVE     = -4
630   };
631
632   static const struct
633   {
634     int id;
635     int callouts[13];
636   } values[] = {
637     { 0, { MINIMUMS, SMART_500, 200, 100, 50, 40, 30, 20, 10, 0 } },
638     { 1, { MINIMUMS, SMART_500, 200, 0 } },
639     { 2, { MINIMUMS, SMART_500, 100, 50, 40, 30, 20, 10, 0 } },
640     { 3, { MINIMUMS, SMART_500, 0 } },
641     { 4, { MINIMUMS, 200, 100, 50, 40, 30, 20, 10, 0 } },
642     { 5, { MINIMUMS, 200, 0 } },
643     { 6, { MINIMUMS, 100, 50, 40, 30, 20, 10, 0 } },
644     { 7, { 0 } },
645     { 8, { MINIMUMS, 0 } },
646     { 9, { MINIMUMS, 500, 200, 100, 50, 40, 30, 20, 10, 0 } },
647     { 10, { MINIMUMS, 500, 200, 0 } },
648     { 11, { MINIMUMS, 500, 100, 50, 40, 30, 20, 10, 0 } },
649     { 12, { MINIMUMS, 500, 0 } },
650     { 13, { MINIMUMS, 1000, 500, 400, 300, 200, 100, 50, 40, 30, 20, 10, 0 } },
651     { 14, { MINIMUMS, 100, 0 } },
652     { 15, { MINIMUMS, 200, 100, 0 } },
653     { 100, { FIELD_500, 0 } },
654     { 101, { FIELD_500_ABOVE, 0 } }
655   };
656
657   int i;
658
659   mk->mode6_handler.conf.minimums_enabled = false;
660   mk->mode6_handler.conf.smart_500_enabled = false;
661   mk->mode6_handler.conf.above_field_voice = NULL;
662   for (i = 0; i < n_altitude_callouts; i++)
663     mk->mode6_handler.conf.altitude_callouts_enabled[i] = false;
664
665   for (i = 0; i < n_elements(values); i++)
666     if (values[i].id == value)
667       {
668         for (int j = 0; values[i].callouts[j] != 0; j++)
669           switch (values[i].callouts[j])
670             {
671             case MINIMUMS:
672               mk->mode6_handler.conf.minimums_enabled = true;
673               break;
674
675             case SMART_500:
676               mk->mode6_handler.conf.smart_500_enabled = true;
677               break;
678
679             case FIELD_500:
680               mk->mode6_handler.conf.above_field_voice = mk_altitude_voice(Mode6Handler::ALTITUDE_CALLOUT_500);
681               break;
682
683             case FIELD_500_ABOVE:
684               mk->mode6_handler.conf.above_field_voice = mk_voice(five_hundred_above);
685               break;
686
687             default:
688               for (int k = 0; k < n_altitude_callouts; k++)
689                 if (mk->mode6_handler.altitude_callout_definitions[k] == values[i].callouts[j])
690                   mk->mode6_handler.conf.altitude_callouts_enabled[k] = true;
691               break;
692             }
693
694         return true;
695       }
696
697   return false;
698 }
699
700 bool
701 MK_VIII::ConfigurationModule::read_audio_menu_select (int value)
702 {
703   if (value == 0 || value == 1)
704     mk->mode4_handler.conf.voice_too_low_gear = mk_voice(too_low_gear);
705   else if (value == 2 || value == 3)
706     mk->mode4_handler.conf.voice_too_low_gear = mk_voice(too_low_flaps);
707   else
708     return false;
709
710   return true;
711 }
712
713 bool
714 MK_VIII::ConfigurationModule::read_terrain_display_select (int value)
715 {
716   if (value == 2)
717     mk->tcf_handler.conf.enabled = false;
718   else if (value == 0 || value == 1 || (value >= 3 && value <= 15)
719            || (value >= 18 && value <= 20) || (value >= 235 && value <= 255))
720     mk->tcf_handler.conf.enabled = true;
721   else
722     return false;
723
724   return true;
725 }
726
727 bool
728 MK_VIII::ConfigurationModule::read_options_select_group_1 (int value)
729 {
730   if (value >= 0 && value < 128)
731     {
732       mk->io_handler.conf.flap_reversal = test_bits(value, 1 << 1);
733       mk->mode6_handler.conf.bank_angle_enabled = test_bits(value, 1 << 2);
734       mk->io_handler.conf.steep_approach_enabled = test_bits(value, 1 << 6);
735       return true;
736     }
737   else
738     return false;
739 }
740
741 bool
742 MK_VIII::ConfigurationModule::read_radio_altitude_input_select (int value)
743 {
744   // unimplemented
745   return (value >= 0 && value <= 4) || (value >= 251 && value <= 255);
746 }
747
748 bool
749 MK_VIII::ConfigurationModule::read_navigation_input_select (int value)
750 {
751   if (value >= 0 && value <= 2)
752     mk->io_handler.conf.localizer_enabled = false;
753   else if ((value >= 3 && value <= 5) || (value >= 250 && value <= 255))
754     mk->io_handler.conf.localizer_enabled = true;
755   else
756     return false;
757
758   return true;
759 }
760
761 bool
762 MK_VIII::ConfigurationModule::read_attitude_input_select (int value)
763 {
764   // unimplemented
765   return (value >= 0 && value <= 6) || value == 253 || value == 255;
766 }
767
768 bool
769 MK_VIII::ConfigurationModule::read_heading_input_select (int value)
770 {
771   // unimplemented
772   return (value >= 0 && value <= 3) || value == 254 || value == 255;
773 }
774
775 bool
776 MK_VIII::ConfigurationModule::read_windshear_input_select (int value)
777 {
778   // unimplemented
779   return value == 0 || (value >= 253 && value <= 255);
780 }
781
782 bool
783 MK_VIII::ConfigurationModule::read_input_output_discrete_type_select (int value)
784 {
785   static const struct
786   {
787     int                                 type;
788     IOHandler::LampConfiguration        lamp_conf;
789     bool                                gpws_inhibit_enabled;
790     bool                                momentary_flap_override_enabled;
791     bool                                alternate_steep_approach;
792   } io_types[] = {
793     { 0,        { false, false }, false, true, true },
794     { 1,        { true, false }, false, true, true },
795     { 2,        { false, false }, true, true, true },
796     { 3,        { true, false }, true, true, true },
797     { 4,        { false, true }, true, true, true },
798     { 5,        { true, true }, true, true, true },
799     { 6,        { false, false }, true, true, false },
800     { 7,        { true, false }, true, true, false },
801     { 254,      { false, false }, true, false, true },
802     { 255,      { false, false }, true, false, true }
803   };
804
805   for (int i = 0; i < n_elements(io_types); i++)
806     if (io_types[i].type == value)
807       {
808         mk->io_handler.conf.lamp = &io_types[i].lamp_conf;
809         mk->io_handler.conf.gpws_inhibit_enabled = io_types[i].gpws_inhibit_enabled;
810         mk->io_handler.conf.momentary_flap_override_enabled = io_types[i].momentary_flap_override_enabled;
811         mk->io_handler.conf.alternate_steep_approach = io_types[i].alternate_steep_approach;
812         return true;
813       }
814
815   return false;
816 }
817
818 bool
819 MK_VIII::ConfigurationModule::read_audio_output_level (int value)
820 {
821   static const struct
822   {
823     int id;
824     int relative_dB;
825   } values[] = {
826     { 0, 0 },
827     { 1, -6 },
828     { 2, -12 },
829     { 3, -18 },
830     { 4, -24 }
831   };
832
833   for (int i = 0; i < n_elements(values); i++)
834     if (values[i].id == value)
835       {
836         mk->voice_player.set_volume(mk->voice_player.conf.volume = modify_amplitude(1.0, values[i].relative_dB));
837         return true;
838       }
839
840   // The self test needs the voice player even when the configuration
841   // is invalid, so set a default value.
842   mk->voice_player.set_volume(mk->voice_player.conf.volume = 1.0);
843   return false;
844 }
845
846 bool
847 MK_VIII::ConfigurationModule::read_undefined_input_select (int value)
848 {
849   // unimplemented
850   return value == 0;
851 }
852
853 void
854 MK_VIII::ConfigurationModule::boot ()
855 {
856   bool (MK_VIII::ConfigurationModule::*readers[N_CATEGORIES]) (int) = {
857     &MK_VIII::ConfigurationModule::read_aircraft_mode_type_select,
858     &MK_VIII::ConfigurationModule::read_air_data_input_select,
859     &MK_VIII::ConfigurationModule::read_position_input_select,
860     &MK_VIII::ConfigurationModule::read_altitude_callouts,
861     &MK_VIII::ConfigurationModule::read_audio_menu_select,
862     &MK_VIII::ConfigurationModule::read_terrain_display_select,
863     &MK_VIII::ConfigurationModule::read_options_select_group_1,
864     &MK_VIII::ConfigurationModule::read_radio_altitude_input_select,
865     &MK_VIII::ConfigurationModule::read_navigation_input_select,
866     &MK_VIII::ConfigurationModule::read_attitude_input_select,
867     &MK_VIII::ConfigurationModule::read_heading_input_select,
868     &MK_VIII::ConfigurationModule::read_windshear_input_select,
869     &MK_VIII::ConfigurationModule::read_input_output_discrete_type_select,
870     &MK_VIII::ConfigurationModule::read_audio_output_level,
871     &MK_VIII::ConfigurationModule::read_undefined_input_select,
872     &MK_VIII::ConfigurationModule::read_undefined_input_select,
873     &MK_VIII::ConfigurationModule::read_undefined_input_select
874   };
875
876   memcpy(effective_categories, categories, sizeof(categories));
877   state = STATE_OK;
878
879   for (int i = 0; i < N_CATEGORIES; i++)
880     if (! (this->*readers[i])(effective_categories[i]))
881       {
882         SG_LOG(SG_INSTR, SG_ALERT, "MK VIII EGPWS configuration category " << i + 1 << ": invalid value " << effective_categories[i]);
883
884         if (state == STATE_OK)
885           state = STATE_INVALID_DATABASE;
886
887         mk_doutput(gpws_inop) = true;
888         mk_doutput(tad_inop) = true;
889
890         return;
891       }
892
893   // handle conflicts
894
895   if (mk->mode6_handler.conf.above_field_voice && ! mk->tcf_handler.conf.enabled)
896     {
897       // [INSTALL] 3.6
898       SG_LOG(SG_INSTR, SG_ALERT, "MK VIII EGPWS configuration module: when category 4 is set to 100 or 101, category 6 must not be set to 2");
899       state = STATE_INVALID_DATABASE;
900     }
901 }
902
903 void
904 MK_VIII::ConfigurationModule::bind (SGPropertyNode *node)
905 {
906   for (int i = 0; i < N_CATEGORIES; i++)
907     {
908       std::ostringstream name;
909       name << "configuration-module/category-" << i + 1;
910       mk->properties_handler.tie(node, name.str().c_str(), SGRawValuePointer<int>(&categories[i]));
911     }
912 }
913
914 ///////////////////////////////////////////////////////////////////////////////
915 // FaultHandler ///////////////////////////////////////////////////////////////
916 ///////////////////////////////////////////////////////////////////////////////
917
918 // [INSTALL] only specifies that the faults cause a GPWS INOP
919 // indication. We arbitrarily set a TAD INOP when it makes sense.
920 const unsigned int MK_VIII::FaultHandler::fault_inops[] = {
921   INOP_GPWS | INOP_TAD,         // [INSTALL] 3.15.1.3
922   INOP_GPWS,                    // [INSTALL] appendix E 6.6.2
923   INOP_GPWS,                    // [INSTALL] appendix E 6.6.4
924   INOP_GPWS,                    // [INSTALL] appendix E 6.6.6
925   INOP_GPWS | INOP_TAD,         // [INSTALL] appendix E 6.6.7
926   INOP_GPWS,                    // [INSTALL] appendix E 6.6.13
927   INOP_GPWS,                    // [INSTALL] appendix E 6.6.25
928   INOP_GPWS,                    // [INSTALL] appendix E 6.6.27
929   INOP_TAD,                     // unspecified
930   INOP_GPWS,                    // unspecified
931   INOP_GPWS,                    // unspecified
932   // [INSTALL] 2.3.10.1 specifies that GPWS INOP is "activated during
933   // any detected partial or total failure of the GPWS modes 1-5", so
934   // do not set any INOP for mode 6 and bank angle.
935   0,                            // unspecified
936   0,                            // unspecified
937   INOP_TAD                      // unspecified
938 };
939
940 bool
941 MK_VIII::FaultHandler::has_faults () const
942 {
943   for (int i = 0; i < N_FAULTS; i++)
944     if (faults[i])
945       return true;
946
947   return false;
948 }
949
950 bool
951 MK_VIII::FaultHandler::has_faults (unsigned int inop)
952 {
953   for (int i = 0; i < N_FAULTS; i++)
954     if (faults[i] && test_bits(fault_inops[i], inop))
955       return true;
956
957   return false;
958 }
959
960 void
961 MK_VIII::FaultHandler::boot ()
962 {
963   memset(faults, 0, sizeof(faults));
964 }
965
966 void
967 MK_VIII::FaultHandler::set_fault (Fault fault)
968 {
969   if (! faults[fault])
970     {
971       faults[fault] = true;
972
973       mk->self_test_handler.set_inop();
974
975       if (test_bits(fault_inops[fault], INOP_GPWS))
976         {
977           mk_unset_alerts(~mk_alert(TCF_TOO_LOW_TERRAIN));
978           mk_doutput(gpws_inop) = true;
979         }
980       if (test_bits(fault_inops[fault], INOP_TAD))
981         {
982           mk_unset_alerts(mk_alert(TCF_TOO_LOW_TERRAIN));
983           mk_doutput(tad_inop) = true;
984         }
985     }
986 }
987
988 void
989 MK_VIII::FaultHandler::unset_fault (Fault fault)
990 {
991   if (faults[fault])
992     {
993       faults[fault] = false;
994       if (! has_faults(INOP_GPWS))
995         mk_doutput(gpws_inop) = false;
996       if (! has_faults(INOP_TAD))
997         mk_doutput(tad_inop) = false;
998     }
999 }
1000
1001 ///////////////////////////////////////////////////////////////////////////////
1002 // IOHandler //////////////////////////////////////////////////////////////////
1003 ///////////////////////////////////////////////////////////////////////////////
1004
1005 double
1006 MK_VIII::IOHandler::TerrainClearanceFilter::update (double agl)
1007 {
1008   // [PILOT] page 20 specifies that the terrain clearance is equal to
1009   // 75% of the radio altitude, averaged over the previous 15 seconds.
1010
1011   deque< Sample<double> >::iterator iter;
1012
1013   // remove samples older than 15 seconds
1014   for (iter = samples.begin(); iter != samples.end() && globals->get_sim_time_sec() - (*iter).timestamp >= 15; iter = samples.begin())
1015     samples.erase(iter);
1016
1017   // append new sample
1018   samples.push_back(Sample<double>(agl));
1019
1020   // calculate average
1021   double new_value = 0;
1022   if (samples.size() > 0)
1023     {
1024       for (iter = samples.begin(); iter != samples.end(); iter++)
1025         new_value += (*iter).value;
1026       new_value /= samples.size();
1027     }
1028   new_value *= 0.75;
1029
1030   if (new_value > value)
1031     value = new_value;
1032
1033   return value;
1034 }
1035
1036 void
1037 MK_VIII::IOHandler::TerrainClearanceFilter::reset ()
1038 {
1039   samples.clear();
1040   value = 0;
1041 }
1042
1043 MK_VIII::IOHandler::IOHandler (MK_VIII *device)
1044   : mk(device), _lamp(LAMP_NONE)
1045 {
1046   memset(&input_feeders, 0, sizeof(input_feeders));
1047   memset(&inputs.discretes, 0, sizeof(inputs.discretes));
1048   memset(&outputs, 0, sizeof(outputs));
1049   memset(&power_saved, 0, sizeof(power_saved));
1050
1051   mk_dinput_feed(landing_gear) = true;
1052   mk_dinput_feed(landing_flaps) = true;
1053   mk_dinput_feed(glideslope_inhibit) = true;
1054   mk_dinput_feed(decision_height) = true;
1055   mk_dinput_feed(autopilot_engaged) = true;
1056   mk_ainput_feed(uncorrected_barometric_altitude) = true;
1057   mk_ainput_feed(barometric_altitude_rate) = true;
1058   mk_ainput_feed(radio_altitude) = true;
1059   mk_ainput_feed(glideslope_deviation) = true;
1060   mk_ainput_feed(roll_angle) = true;
1061   mk_ainput_feed(localizer_deviation) = true;
1062   mk_ainput_feed(computed_airspeed) = true;
1063
1064   // will be unset on power on
1065   mk_doutput(gpws_inop) = true;
1066   mk_doutput(tad_inop) = true;
1067 }
1068
1069 void
1070 MK_VIII::IOHandler::boot ()
1071 {
1072   if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
1073     return;
1074
1075   mk_doutput(gpws_inop) = false;
1076   mk_doutput(tad_inop) = false;
1077
1078   mk_doutput(glideslope_cancel) = power_saved.glideslope_cancel;
1079
1080   altitude_samples.clear();
1081 }
1082
1083 void
1084 MK_VIII::IOHandler::post_boot ()
1085 {
1086   if (momentary_steep_approach_enabled())
1087     {
1088       last_landing_gear = mk_dinput(landing_gear);
1089       last_real_flaps_down = real_flaps_down();
1090     }
1091
1092   // read externally-latching input discretes
1093   update_alternate_discrete_input(&mk_dinput(mode6_low_volume));
1094   update_alternate_discrete_input(&mk_dinput(audio_inhibit));
1095 }
1096
1097 void
1098 MK_VIII::IOHandler::power_off ()
1099 {
1100   power_saved.glideslope_cancel = mk_doutput(glideslope_cancel); // [SPEC] 6.2.5
1101
1102   memset(&outputs, 0, sizeof(outputs));
1103
1104   audio_inhibit_fault_timer.stop();
1105   landing_gear_fault_timer.stop();
1106   flaps_down_fault_timer.stop();
1107   momentary_flap_override_fault_timer.stop();
1108   self_test_fault_timer.stop();
1109   glideslope_cancel_fault_timer.stop();
1110   steep_approach_fault_timer.stop();
1111   gpws_inhibit_fault_timer.stop();
1112   ta_tcf_inhibit_fault_timer.stop();
1113
1114   // [SPEC] 6.9.3.5
1115   mk_doutput(gpws_inop) = true;
1116   mk_doutput(tad_inop) = true;
1117 }
1118
1119 void
1120 MK_VIII::IOHandler::enter_ground ()
1121 {
1122   reset_terrain_clearance();
1123
1124   if (conf.momentary_flap_override_enabled)
1125     mk_doutput(flap_override) = false;  // [INSTALL] 3.15.1.6
1126 }
1127
1128 void
1129 MK_VIII::IOHandler::enter_takeoff ()
1130 {
1131   reset_terrain_clearance();
1132
1133   if (momentary_steep_approach_enabled())
1134     // landing or go-around, disable steep approach as per [SPEC] 6.2.1
1135     mk_doutput(steep_approach) = false;
1136 }
1137
1138 void
1139 MK_VIII::IOHandler::update_inputs ()
1140 {
1141   if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
1142     return;
1143
1144   // 1. process input feeders
1145
1146   if (mk_dinput_feed(landing_gear))
1147     mk_dinput(landing_gear) = mk_node(gear_down)->getBoolValue();
1148   if (mk_dinput_feed(landing_flaps))
1149     mk_dinput(landing_flaps) = mk_node(flaps)->getDoubleValue() < 1;
1150   if (mk_dinput_feed(glideslope_inhibit))
1151     mk_dinput(glideslope_inhibit) = mk_node(nav0_gs_distance)->getDoubleValue() < 0;
1152   if (mk_dinput_feed(autopilot_engaged))
1153     {
1154       const char *mode;
1155
1156       mode = mk_node(autopilot_heading_lock)->getStringValue();
1157       mk_dinput(autopilot_engaged) = mode && *mode;
1158     }
1159
1160   if (mk_ainput_feed(uncorrected_barometric_altitude))
1161     {
1162       if (mk_node(altimeter_serviceable)->getBoolValue())
1163         mk_ainput(uncorrected_barometric_altitude).set(mk_node(altimeter_altitude)->getDoubleValue());
1164       else
1165         mk_ainput(uncorrected_barometric_altitude).unset();
1166     }
1167   if (mk_ainput_feed(barometric_altitude_rate))
1168     // Do not use the vsi, because it is pressure-based only, and
1169     // therefore too laggy for GPWS alerting purposes. I guess that
1170     // a real ADC combines pressure-based and inerta-based altitude
1171     // rates to provide a non-laggy rate. That non-laggy rate is
1172     // best emulated by /velocities/vertical-speed-fps * 60.
1173     mk_ainput(barometric_altitude_rate).set(mk_node(vs)->getDoubleValue() * 60);
1174   if (mk_ainput_feed(radio_altitude))
1175     {
1176       // Some flight models may return negative values when on the
1177       // ground or after a crash; do not allow them.
1178       double agl = mk_node(altitude_agl)->getDoubleValue();
1179       mk_ainput(radio_altitude).set(SG_MAX2(0.0, agl));
1180     }
1181   if (mk_ainput_feed(glideslope_deviation))
1182     {
1183       if (mk_node(nav0_serviceable)->getBoolValue()
1184           && mk_node(nav0_gs_serviceable)->getBoolValue()
1185           && mk_node(nav0_in_range)->getBoolValue()
1186           && mk_node(nav0_has_gs)->getBoolValue())
1187         // gs-needle-deflection is expressed in degrees, and 1 dot =
1188         // 0.32 degrees (according to
1189         // http://www.ntsb.gov/Recs/letters/2000/A00_41_45.pdf)
1190         mk_ainput(glideslope_deviation).set(mk_node(nav0_gs_needle_deflection)->getDoubleValue() / 0.32 * GLIDESLOPE_DOTS_TO_DDM);
1191       else
1192         mk_ainput(glideslope_deviation).unset();
1193     }
1194   if (mk_ainput_feed(roll_angle))
1195     {
1196       if (mk_node(ai_serviceable)->getBoolValue() && ! mk_node(ai_caged)->getBoolValue())
1197         mk_ainput(roll_angle).set(mk_node(ai_roll)->getDoubleValue());
1198       else
1199         mk_ainput(roll_angle).unset();
1200     }
1201   if (mk_ainput_feed(localizer_deviation))
1202     {
1203       if (mk_node(nav0_serviceable)->getBoolValue()
1204           && mk_node(nav0_cdi_serviceable)->getBoolValue()
1205           && mk_node(nav0_in_range)->getBoolValue()
1206           && mk_node(nav0_nav_loc)->getBoolValue())
1207         // heading-needle-deflection is expressed in degrees, and 1
1208         // dot = 2 degrees (0.5 degrees for ILS, but navradio.cxx
1209         // performs the conversion)
1210         mk_ainput(localizer_deviation).set(mk_node(nav0_heading_needle_deflection)->getDoubleValue() / 2 * LOCALIZER_DOTS_TO_DDM);
1211       else
1212         mk_ainput(localizer_deviation).unset();
1213     }
1214   if (mk_ainput_feed(computed_airspeed))
1215     {
1216       if (mk_node(asi_serviceable)->getBoolValue())
1217         mk_ainput(computed_airspeed).set(mk_node(asi_speed)->getDoubleValue());
1218       else
1219         mk_ainput(computed_airspeed).unset();
1220     }
1221
1222   // 2. compute data
1223
1224   mk_data(decision_height).set(&mk_ainput(decision_height));
1225   mk_data(radio_altitude).set(&mk_ainput(radio_altitude));
1226   mk_data(roll_angle).set(&mk_ainput(roll_angle));
1227
1228   // update barometric_altitude_rate as per [SPEC] 6.2.1: "During
1229   // normal conditions, the system will base Mode 1 computations upon
1230   // barometric rate from the ADC. If this computed data is not valid
1231   // or available then the system will use internally computed
1232   // barometric altitude rate."
1233
1234   if (! mk_ainput(barometric_altitude_rate).ncd)
1235     // the altitude rate input is valid, use it
1236     mk_data(barometric_altitude_rate).set(mk_ainput(barometric_altitude_rate).get());
1237   else
1238     {
1239       bool has = false;
1240
1241       // The altitude rate input is invalid. We can compute an
1242       // altitude rate if all the following conditions are true:
1243       //
1244       //   - the altitude input is valid
1245       //   - there is an altitude sample with age >= 1 second
1246       //   - that altitude sample is valid
1247
1248       if (! mk_ainput(uncorrected_barometric_altitude).ncd)
1249         {
1250           if (! altitude_samples.empty() && ! altitude_samples.front().value.ncd)
1251             {
1252               double elapsed = globals->get_sim_time_sec() - altitude_samples.front().timestamp;
1253               if (elapsed >= 1)
1254                 {
1255                   has = true;
1256                   mk_data(barometric_altitude_rate).set((mk_ainput(uncorrected_barometric_altitude).get() - altitude_samples.front().value.get()) / elapsed * 60);
1257                 }
1258             }
1259         }
1260
1261       if (! has)
1262         mk_data(barometric_altitude_rate).unset();
1263     }
1264
1265   altitude_samples.push_back(Sample< Parameter<double> >(mk_ainput(uncorrected_barometric_altitude)));
1266
1267   // Erase everything from the beginning of the list up to the sample
1268   // preceding the most recent sample whose age is >= 1 second.
1269
1270   deque< Sample< Parameter<double> > >::iterator erase_last = altitude_samples.begin();
1271   deque< Sample< Parameter<double> > >::iterator iter;
1272
1273   for (iter = altitude_samples.begin(); iter != altitude_samples.end(); iter++)
1274     if (globals->get_sim_time_sec() - (*iter).timestamp >= 1)
1275       erase_last = iter;
1276     else
1277       break;
1278
1279   if (erase_last != altitude_samples.begin())
1280     altitude_samples.erase(altitude_samples.begin(), erase_last);
1281
1282   // update GPS data
1283
1284   if (conf.use_internal_gps)
1285     {
1286       mk_data(gps_altitude).set(mk_node(altitude)->getDoubleValue());
1287       mk_data(gps_latitude).set(mk_node(latitude)->getDoubleValue());
1288       mk_data(gps_longitude).set(mk_node(longitude)->getDoubleValue());
1289       mk_data(gps_vertical_figure_of_merit).set(0.0);
1290     }
1291   else
1292     {
1293       mk_data(gps_altitude).set(&mk_ainput(gps_altitude));
1294       mk_data(gps_latitude).set(&mk_ainput(gps_latitude));
1295       mk_data(gps_longitude).set(&mk_ainput(gps_longitude));
1296       mk_data(gps_vertical_figure_of_merit).set(&mk_ainput(gps_vertical_figure_of_merit));
1297     }
1298
1299   // update glideslope and localizer
1300
1301   mk_data(glideslope_deviation_dots).set(&mk_ainput(glideslope_deviation), GLIDESLOPE_DDM_TO_DOTS);
1302   mk_data(localizer_deviation_dots).set(&mk_ainput(localizer_deviation), LOCALIZER_DDM_TO_DOTS);
1303
1304   // Update geometric altitude; [SPEC] 6.7.8 provides an overview of a
1305   // complex algorithm which combines several input sources to
1306   // calculate the geometric altitude. Since the exact algorithm is
1307   // not given, we fallback to a much simpler method.
1308
1309   if (! mk_data(gps_altitude).ncd)
1310     mk_data(geometric_altitude).set(mk_data(gps_altitude).get());
1311   else if (! mk_ainput(uncorrected_barometric_altitude).ncd)
1312     mk_data(geometric_altitude).set(mk_ainput(uncorrected_barometric_altitude).get());
1313   else
1314     mk_data(geometric_altitude).unset();
1315
1316   // update terrain clearance
1317
1318   update_terrain_clearance();
1319
1320   // 3. perform sanity checks
1321
1322   if (! mk_data(radio_altitude).ncd && mk_data(radio_altitude).get() < 0)
1323     mk_data(radio_altitude).unset();
1324
1325   if (! mk_data(decision_height).ncd && mk_data(decision_height).get() < 0)
1326     mk_data(decision_height).unset();
1327
1328   if (! mk_data(gps_latitude).ncd
1329       && (mk_data(gps_latitude).get() < -90
1330           || mk_data(gps_latitude).get() > 90))
1331     mk_data(gps_latitude).unset();
1332
1333   if (! mk_data(gps_longitude).ncd
1334       && (mk_data(gps_longitude).get() < -180
1335           || mk_data(gps_longitude).get() > 180))
1336     mk_data(gps_longitude).unset();
1337
1338   if (! mk_data(roll_angle).ncd
1339       && ((mk_data(roll_angle).get() < -180)
1340           || (mk_data(roll_angle).get() > 180)))
1341     mk_data(roll_angle).unset();
1342
1343   // 4. process input feeders requiring data computed above
1344
1345   if (mk_dinput_feed(decision_height))
1346     mk_dinput(decision_height) = ! mk_data(radio_altitude).ncd
1347       && ! mk_data(decision_height).ncd
1348       && mk_data(radio_altitude).get() <= mk_data(decision_height).get();
1349 }
1350
1351 void
1352 MK_VIII::IOHandler::update_terrain_clearance ()
1353 {
1354   if (! mk_data(radio_altitude).ncd)
1355     mk_data(terrain_clearance).set(terrain_clearance_filter.update(mk_data(radio_altitude).get()));
1356   else
1357     mk_data(terrain_clearance).unset();
1358 }
1359
1360 void
1361 MK_VIII::IOHandler::reset_terrain_clearance ()
1362 {
1363   terrain_clearance_filter.reset();
1364   update_terrain_clearance();
1365 }
1366
1367 void
1368 MK_VIII::IOHandler::handle_input_fault (bool test, FaultHandler::Fault fault)
1369 {
1370   if (test)
1371     {
1372       if (! mk->fault_handler.faults[fault])
1373         mk->fault_handler.set_fault(fault);
1374     }
1375   else
1376     mk->fault_handler.unset_fault(fault);
1377 }
1378
1379 void
1380 MK_VIII::IOHandler::handle_input_fault (bool test,
1381                                         Timer *timer,
1382                                         double max_duration,
1383                                         FaultHandler::Fault fault)
1384 {
1385   if (test)
1386     {
1387       if (! mk->fault_handler.faults[fault])
1388         {
1389           if (timer->start_or_elapsed() >= max_duration)
1390             mk->fault_handler.set_fault(fault);
1391         }
1392     }
1393   else
1394     {
1395       mk->fault_handler.unset_fault(fault);
1396       timer->stop();
1397     }
1398 }
1399
1400 void
1401 MK_VIII::IOHandler::update_input_faults ()
1402 {
1403   if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
1404     return;
1405
1406   // [INSTALL] 3.15.1.3
1407   handle_input_fault(mk_dinput(audio_inhibit),
1408                      &audio_inhibit_fault_timer,
1409                      60,
1410                      FaultHandler::FAULT_ALL_MODES_INHIBIT);
1411
1412   // [INSTALL] appendix E 6.6.2
1413   handle_input_fault(mk_dinput(landing_gear)
1414                      && ! mk_ainput(computed_airspeed).ncd
1415                      && mk_ainput(computed_airspeed).get() > conf.faults->max_gear_down_airspeed,
1416                      &landing_gear_fault_timer,
1417                      60,
1418                      FaultHandler::FAULT_GEAR_SWITCH);
1419
1420   // [INSTALL] appendix E 6.6.4
1421   handle_input_fault(flaps_down()
1422                      && ! mk_ainput(computed_airspeed).ncd
1423                      && mk_ainput(computed_airspeed).get() > conf.faults->max_flaps_down_airspeed,
1424                      &flaps_down_fault_timer,
1425                      60,
1426                      FaultHandler::FAULT_FLAPS_SWITCH);
1427
1428   // [INSTALL] appendix E 6.6.6
1429   if (conf.momentary_flap_override_enabled)
1430     handle_input_fault(mk_dinput(momentary_flap_override),
1431                        &momentary_flap_override_fault_timer,
1432                        15,
1433                        FaultHandler::FAULT_MOMENTARY_FLAP_OVERRIDE_INVALID);
1434
1435   // [INSTALL] appendix E 6.6.7
1436   handle_input_fault(mk_dinput(self_test),
1437                      &self_test_fault_timer,
1438                      60,
1439                      FaultHandler::FAULT_SELF_TEST_INVALID);
1440
1441   // [INSTALL] appendix E 6.6.13
1442   handle_input_fault(mk_dinput(glideslope_cancel),
1443                      &glideslope_cancel_fault_timer,
1444                      15,
1445                      FaultHandler::FAULT_GLIDESLOPE_CANCEL_INVALID);
1446
1447   // [INSTALL] appendix E 6.6.25
1448   if (momentary_steep_approach_enabled())
1449     handle_input_fault(mk_dinput(steep_approach),
1450                        &steep_approach_fault_timer,
1451                        15,
1452                        FaultHandler::FAULT_STEEP_APPROACH_INVALID);
1453
1454   // [INSTALL] appendix E 6.6.27
1455   if (conf.gpws_inhibit_enabled)
1456     handle_input_fault(mk_dinput(gpws_inhibit),
1457                        &gpws_inhibit_fault_timer,
1458                        5,
1459                        FaultHandler::FAULT_GPWS_INHIBIT);
1460
1461   // [INSTALL] does not specify a fault for this one, but it makes
1462   // sense to have it behave like GPWS inhibit
1463   handle_input_fault(mk_dinput(ta_tcf_inhibit),
1464                      &ta_tcf_inhibit_fault_timer,
1465                      5,
1466                      FaultHandler::FAULT_TA_TCF_INHIBIT);
1467
1468   // [PILOT] page 48: "In the event that required data for a
1469   // particular function is not available, then that function is
1470   // automatically inhibited and annunciated"
1471
1472   handle_input_fault(mk_data(radio_altitude).ncd
1473                      || mk_ainput(uncorrected_barometric_altitude).ncd
1474                      || mk_data(barometric_altitude_rate).ncd
1475                      || mk_ainput(computed_airspeed).ncd
1476                      || mk_data(terrain_clearance).ncd,
1477                      FaultHandler::FAULT_MODES14_INPUTS_INVALID);
1478
1479   if (! mk_dinput(glideslope_inhibit))
1480     handle_input_fault(mk_data(radio_altitude).ncd,
1481                        FaultHandler::FAULT_MODE5_INPUTS_INVALID);
1482
1483   if (mk->mode6_handler.altitude_callouts_enabled())
1484     handle_input_fault(mk->mode6_handler.conf.above_field_voice
1485                        ? (mk_data(gps_latitude).ncd
1486                           || mk_data(gps_longitude).ncd
1487                           || mk_data(geometric_altitude).ncd)
1488                        : mk_data(radio_altitude).ncd,
1489                        FaultHandler::FAULT_MODE6_INPUTS_INVALID);
1490
1491   if (mk->mode6_handler.conf.bank_angle_enabled)
1492     handle_input_fault(mk_data(roll_angle).ncd,
1493                        FaultHandler::FAULT_BANK_ANGLE_INPUTS_INVALID);
1494
1495   if (mk->tcf_handler.conf.enabled)
1496     handle_input_fault(mk_data(radio_altitude).ncd
1497                        || mk_data(geometric_altitude).ncd
1498                        || mk_data(gps_latitude).ncd
1499                        || mk_data(gps_longitude).ncd
1500                        || mk_data(gps_vertical_figure_of_merit).ncd,
1501                        FaultHandler::FAULT_TCF_INPUTS_INVALID);
1502 }
1503
1504 void
1505 MK_VIII::IOHandler::update_alternate_discrete_input (bool *ptr)
1506 {
1507   assert(mk->system_handler.state == SystemHandler::STATE_ON);
1508
1509   if (ptr == &mk_dinput(mode6_low_volume))
1510     {
1511       if (mk->configuration_module.state == ConfigurationModule::STATE_OK
1512           && mk->self_test_handler.state == SelfTestHandler::STATE_NONE)
1513         mk->mode6_handler.set_volume(*ptr ? modify_amplitude(1.0, -6) : 1.0);
1514     }
1515   else if (ptr == &mk_dinput(audio_inhibit))
1516     {
1517       if (mk->configuration_module.state == ConfigurationModule::STATE_OK
1518           && mk->self_test_handler.state == SelfTestHandler::STATE_NONE)
1519         mk->voice_player.set_volume(*ptr ? 0.0 : mk->voice_player.conf.volume);
1520     }
1521 }
1522
1523 void
1524 MK_VIII::IOHandler::update_internal_latches ()
1525 {
1526   if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
1527     return;
1528
1529   // [SPEC] 6.2.1
1530   if (conf.momentary_flap_override_enabled
1531       && mk_doutput(flap_override)
1532       && ! mk->state_handler.takeoff
1533       && (mk_data(radio_altitude).ncd || mk_data(radio_altitude).get() <= 50))
1534     mk_doutput(flap_override) = false;
1535
1536   // [SPEC] 6.2.1
1537   if (momentary_steep_approach_enabled())
1538     {
1539       if (mk_doutput(steep_approach)
1540           && ! mk->state_handler.takeoff
1541           && ((last_landing_gear && ! mk_dinput(landing_gear))
1542               || (last_real_flaps_down && ! real_flaps_down())))
1543         // gear or flaps raised during approach: it's a go-around
1544         mk_doutput(steep_approach) = false;
1545
1546       last_landing_gear = mk_dinput(landing_gear);
1547       last_real_flaps_down = real_flaps_down();
1548     }
1549
1550   // [SPEC] 6.2.5
1551   if (mk_doutput(glideslope_cancel)
1552       && (mk_data(glideslope_deviation_dots).ncd
1553           || mk_data(radio_altitude).ncd
1554           || mk_data(radio_altitude).get() > 2000
1555           || mk_data(radio_altitude).get() < 30))
1556     mk_doutput(glideslope_cancel) = false;
1557 }
1558
1559 void
1560 MK_VIII::IOHandler::update_egpws_alert_discrete_1 ()
1561 {
1562   if (mk->voice_player.voice)
1563     {
1564       const struct
1565       {
1566         int                     bit;
1567         VoicePlayer::Voice      *voice;
1568       } voices[] = {
1569         { 11, mk_voice(sink_rate_pause_sink_rate) },
1570         { 12, mk_voice(pull_up) },
1571         { 13, mk_voice(terrain) },
1572         { 13, mk_voice(terrain_pause_terrain) },
1573         { 14, mk_voice(dont_sink_pause_dont_sink) },
1574         { 15, mk_voice(too_low_gear) },
1575         { 16, mk_voice(too_low_flaps) },
1576         { 17, mk_voice(too_low_terrain) },
1577         { 18, mk_voice(soft_glideslope) },
1578         { 18, mk_voice(hard_glideslope) },
1579         { 19, mk_voice(minimums_minimums) }
1580       };
1581
1582       for (int i = 0; i < n_elements(voices); i++)
1583         if (voices[i].voice == mk->voice_player.voice)
1584           {
1585             mk_aoutput(egpws_alert_discrete_1) = 1 << voices[i].bit;
1586             return;
1587           }
1588     }
1589
1590   mk_aoutput(egpws_alert_discrete_1) = 0;
1591 }
1592
1593 void
1594 MK_VIII::IOHandler::update_egpwc_logic_discretes ()
1595 {
1596   mk_aoutput(egpwc_logic_discretes) = 0;
1597
1598   if (mk->state_handler.takeoff)
1599     mk_aoutput(egpwc_logic_discretes) |= 1 << 18;
1600
1601   static const struct
1602   {
1603     int                 bit;
1604     unsigned int        alerts;
1605   } logic[] = {
1606     { 13, mk_alert(TCF_TOO_LOW_TERRAIN) },
1607     { 19, mk_alert(MODE1_SINK_RATE) },
1608     { 20, mk_alert(MODE1_PULL_UP) },
1609     { 21, mk_alert(MODE2A_PREFACE) | mk_alert(MODE2B_PREFACE) | mk_alert(MODE2B_LANDING_MODE) | mk_alert(MODE2A_ALTITUDE_GAIN_TERRAIN_CLOSING) },
1610     { 22, mk_alert(MODE2A) | mk_alert(MODE2B) },
1611     { 23, mk_alert(MODE3) },
1612     { 24, mk_alert(MODE4_TOO_LOW_FLAPS) | mk_alert(MODE4_TOO_LOW_GEAR) | mk_alert(MODE4AB_TOO_LOW_TERRAIN) | mk_alert(MODE4C_TOO_LOW_TERRAIN) },
1613     { 25, mk_alert(MODE5_SOFT) | mk_alert(MODE5_HARD) }
1614   };
1615
1616   for (int i = 0; i < n_elements(logic); i++)
1617     if (mk_test_alerts(logic[i].alerts))
1618       mk_aoutput(egpwc_logic_discretes) |= 1 << logic[i].bit;
1619 }
1620
1621 void
1622 MK_VIII::IOHandler::update_mode6_callouts_discrete_1 ()
1623 {
1624   if (mk->voice_player.voice)
1625     {
1626       const struct
1627       {
1628         int                     bit;
1629         VoicePlayer::Voice      *voice;
1630       } voices[] = {
1631         { 11, mk_voice(minimums_minimums) },
1632         { 16, mk_altitude_voice(Mode6Handler::ALTITUDE_CALLOUT_10) },
1633         { 17, mk_altitude_voice(Mode6Handler::ALTITUDE_CALLOUT_20) },
1634         { 18, mk_altitude_voice(Mode6Handler::ALTITUDE_CALLOUT_30) },
1635         { 19, mk_altitude_voice(Mode6Handler::ALTITUDE_CALLOUT_40) },
1636         { 20, mk_altitude_voice(Mode6Handler::ALTITUDE_CALLOUT_50) },
1637         { 23, mk_altitude_voice(Mode6Handler::ALTITUDE_CALLOUT_100) },
1638         { 24, mk_altitude_voice(Mode6Handler::ALTITUDE_CALLOUT_200) },
1639         { 25, mk_altitude_voice(Mode6Handler::ALTITUDE_CALLOUT_300) }
1640       };
1641
1642       for (int i = 0; i < n_elements(voices); i++)
1643         if (voices[i].voice == mk->voice_player.voice)
1644           {
1645             mk_aoutput(mode6_callouts_discrete_1) = 1 << voices[i].bit;
1646             return;
1647           }
1648     }
1649
1650   mk_aoutput(mode6_callouts_discrete_1) = 0;
1651 }
1652
1653 void
1654 MK_VIII::IOHandler::update_mode6_callouts_discrete_2 ()
1655 {
1656   if (mk->voice_player.voice)
1657     {
1658       const struct
1659       {
1660         int                     bit;
1661         VoicePlayer::Voice      *voice;
1662       } voices[] = {
1663         { 11, mk_altitude_voice(Mode6Handler::ALTITUDE_CALLOUT_400) },
1664         { 12, mk_altitude_voice(Mode6Handler::ALTITUDE_CALLOUT_500) },
1665         { 13, mk_altitude_voice(Mode6Handler::ALTITUDE_CALLOUT_1000) },
1666         { 18, mk_voice(bank_angle_pause_bank_angle) },
1667         { 18, mk_voice(bank_angle_pause_bank_angle_3) },
1668         { 23, mk_voice(five_hundred_above) }
1669       };
1670
1671       for (int i = 0; i < n_elements(voices); i++)
1672         if (voices[i].voice == mk->voice_player.voice)
1673           {
1674             mk_aoutput(mode6_callouts_discrete_2) = 1 << voices[i].bit;
1675             return;
1676           }
1677     }
1678
1679   mk_aoutput(mode6_callouts_discrete_2) = 0;
1680 }
1681
1682 void
1683 MK_VIII::IOHandler::update_egpws_alert_discrete_2 ()
1684 {
1685   mk_aoutput(egpws_alert_discrete_2) = 1 << 27; // Terrain NA
1686
1687   if (mk_doutput(glideslope_cancel))
1688     mk_aoutput(egpws_alert_discrete_2) |= 1 << 11;
1689   if (mk_doutput(gpws_alert))
1690     mk_aoutput(egpws_alert_discrete_2) |= 1 << 12;
1691   if (mk_doutput(gpws_warning))
1692     mk_aoutput(egpws_alert_discrete_2) |= 1 << 13;
1693   if (mk_doutput(gpws_inop))
1694     mk_aoutput(egpws_alert_discrete_2) |= 1 << 14;
1695   if (mk_doutput(audio_on))
1696     mk_aoutput(egpws_alert_discrete_2) |= 1 << 16;
1697   if (mk_doutput(tad_inop))
1698     mk_aoutput(egpws_alert_discrete_2) |= 1 << 24;
1699   if (mk->fault_handler.has_faults())
1700     mk_aoutput(egpws_alert_discrete_2) |= 1 << 25;
1701 }
1702
1703 void
1704 MK_VIII::IOHandler::update_egpwc_alert_discrete_3 ()
1705 {
1706   mk_aoutput(egpwc_alert_discrete_3) = 1 << 17 | 1 << 18;
1707
1708   if (mk->fault_handler.faults[FaultHandler::FAULT_MODES14_INPUTS_INVALID])
1709     mk_aoutput(egpwc_alert_discrete_3) |= 1 << 11;
1710   if (mk->fault_handler.faults[FaultHandler::FAULT_MODE5_INPUTS_INVALID])
1711     mk_aoutput(egpwc_alert_discrete_3) |= 1 << 12;
1712   if (mk->fault_handler.faults[FaultHandler::FAULT_MODE6_INPUTS_INVALID])
1713     mk_aoutput(egpwc_alert_discrete_3) |= 1 << 13;
1714   if (mk->fault_handler.faults[FaultHandler::FAULT_BANK_ANGLE_INPUTS_INVALID])
1715     mk_aoutput(egpwc_alert_discrete_3) |= 1 << 14;
1716   if (mk_doutput(tad_inop))
1717     mk_aoutput(egpwc_alert_discrete_3) |= 1 << 16;
1718 }
1719
1720 void
1721 MK_VIII::IOHandler::update_outputs ()
1722 {
1723   if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
1724     return;
1725
1726   mk_doutput(audio_on) = ! mk_dinput(audio_inhibit)
1727     && mk->voice_player.voice
1728     && ! mk->voice_player.voice->element->silence;
1729
1730   update_egpws_alert_discrete_1();
1731   update_egpwc_logic_discretes();
1732   update_mode6_callouts_discrete_1();
1733   update_mode6_callouts_discrete_2();
1734   update_egpws_alert_discrete_2();
1735   update_egpwc_alert_discrete_3();
1736 }
1737
1738 bool *
1739 MK_VIII::IOHandler::get_lamp_output (Lamp lamp)
1740 {
1741   switch (lamp)
1742     {
1743     case LAMP_GLIDESLOPE:
1744       return &mk_doutput(gpws_alert);
1745
1746     case LAMP_CAUTION:
1747       return conf.lamp->format2 ? &mk_doutput(gpws_alert) : &mk_doutput(gpws_warning);
1748
1749     case LAMP_WARNING:
1750       return &mk_doutput(gpws_warning);
1751
1752     default:
1753       assert_not_reached();
1754       return NULL;
1755     }
1756 }
1757
1758 void
1759 MK_VIII::IOHandler::update_lamps ()
1760 {
1761   if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
1762     return;
1763
1764   if (_lamp != LAMP_NONE && conf.lamp->flashing)
1765     {
1766       // [SPEC] 6.9.3: 70 cycles per minute
1767       if (lamp_timer.elapsed() >= 60.0 / 70.0 / 2.0)
1768         {
1769           bool *output = get_lamp_output(_lamp);
1770           *output = ! *output;
1771           lamp_timer.start();
1772         }
1773     }
1774 }
1775
1776 void
1777 MK_VIII::IOHandler::set_lamp (Lamp lamp)
1778 {
1779   if (lamp == _lamp)
1780     return;
1781
1782   _lamp = lamp;
1783
1784   mk_doutput(gpws_warning) = false;
1785   mk_doutput(gpws_alert) = false;
1786
1787   if (lamp != LAMP_NONE)
1788     {
1789       *get_lamp_output(lamp) = true;
1790       lamp_timer.start();
1791     }
1792 }
1793
1794 bool
1795 MK_VIII::IOHandler::gpws_inhibit () const
1796 {
1797   return conf.gpws_inhibit_enabled && mk_dinput(gpws_inhibit);
1798 }
1799
1800 bool
1801 MK_VIII::IOHandler::real_flaps_down () const
1802 {
1803   return conf.flap_reversal ? mk_dinput(landing_flaps) : ! mk_dinput(landing_flaps);
1804 }
1805
1806 bool
1807 MK_VIII::IOHandler::flaps_down () const
1808 {
1809   return flap_override() ? true : real_flaps_down();
1810 }
1811
1812 bool
1813 MK_VIII::IOHandler::flap_override () const
1814 {
1815   return conf.momentary_flap_override_enabled ? mk_doutput(flap_override) : false;
1816 }
1817
1818 bool
1819 MK_VIII::IOHandler::steep_approach () const
1820 {
1821   if (conf.steep_approach_enabled)
1822     // If alternate action was configured in category 13, we return
1823     // the value of the input discrete (which should be connected to a
1824     // latching steep approach cockpit switch). Otherwise, we return
1825     // the value of the output discrete.
1826     return conf.alternate_steep_approach ? mk_dinput(steep_approach) : mk_doutput(steep_approach);
1827   else
1828     return false;
1829 }
1830
1831 bool
1832 MK_VIII::IOHandler::momentary_steep_approach_enabled () const
1833 {
1834   return conf.steep_approach_enabled && ! conf.alternate_steep_approach;
1835 }
1836
1837 void
1838 MK_VIII::IOHandler::tie_input (SGPropertyNode *node,
1839                                const char *name,
1840                                bool *input,
1841                                bool *feed)
1842 {
1843   mk->properties_handler.tie(node, (string("inputs/discretes/") + name).c_str(), RawValueMethodsData<MK_VIII::IOHandler, bool, bool *>(*this, input, &MK_VIII::IOHandler::get_discrete_input, &MK_VIII::IOHandler::set_discrete_input));
1844   if (feed)
1845     mk->properties_handler.tie(node, (string("input-feeders/discretes/") + name).c_str(), SGRawValuePointer<bool>(feed));
1846 }
1847
1848 void
1849 MK_VIII::IOHandler::tie_input (SGPropertyNode *node,
1850                                const char *name,
1851                                Parameter<double> *input,
1852                                bool *feed)
1853 {
1854   mk->properties_handler.tie(node, (string("inputs/arinc429/") + name).c_str(), SGRawValuePointer<double>(input->get_pointer()));
1855   mk->properties_handler.tie(node, (string("inputs/arinc429/") + name + "-ncd").c_str(), SGRawValuePointer<bool>(&input->ncd));
1856   if (feed)
1857     mk->properties_handler.tie(node, (string("input-feeders/arinc429/") + name).c_str(), SGRawValuePointer<bool>(feed));
1858 }
1859
1860 void
1861 MK_VIII::IOHandler::tie_output (SGPropertyNode *node,
1862                                 const char *name,
1863                                 bool *output)
1864 {
1865   SGPropertyNode *child = node->getNode((string("outputs/discretes/") + name).c_str(), true);
1866
1867   mk->properties_handler.tie(child, SGRawValuePointer<bool>(output));
1868   child->setAttribute(SGPropertyNode::WRITE, false);
1869 }
1870
1871 void
1872 MK_VIII::IOHandler::tie_output (SGPropertyNode *node,
1873                                 const char *name,
1874                                 int *output)
1875 {
1876   SGPropertyNode *child = node->getNode((string("outputs/arinc429/") + name).c_str(), true);
1877
1878   mk->properties_handler.tie(child, SGRawValuePointer<int>(output));
1879   child->setAttribute(SGPropertyNode::WRITE, false);
1880 }
1881
1882 void
1883 MK_VIII::IOHandler::bind (SGPropertyNode *node)
1884 {
1885   mk->properties_handler.tie(node, "inputs/rs-232/present-status", SGRawValueMethods<MK_VIII::IOHandler, bool>(*this, &MK_VIII::IOHandler::get_present_status, &MK_VIII::IOHandler::set_present_status));
1886
1887   tie_input(node, "landing-gear", &mk_dinput(landing_gear), &mk_dinput_feed(landing_gear));
1888   tie_input(node, "landing-flaps", &mk_dinput(landing_flaps), &mk_dinput_feed(landing_flaps));
1889   tie_input(node, "momentary-flap-override", &mk_dinput(momentary_flap_override));
1890   tie_input(node, "self-test", &mk_dinput(self_test));
1891   tie_input(node, "glideslope-inhibit", &mk_dinput(glideslope_inhibit), &mk_dinput_feed(glideslope_inhibit));
1892   tie_input(node, "glideslope-cancel", &mk_dinput(glideslope_cancel));
1893   tie_input(node, "decision-height", &mk_dinput(decision_height), &mk_dinput_feed(decision_height));
1894   tie_input(node, "mode6-low-volume", &mk_dinput(mode6_low_volume));
1895   tie_input(node, "audio-inhibit", &mk_dinput(audio_inhibit));
1896   tie_input(node, "ta-tcf-inhibit", &mk_dinput(ta_tcf_inhibit));
1897   tie_input(node, "autopilot-engaged", &mk_dinput(autopilot_engaged), &mk_dinput_feed(autopilot_engaged));
1898   tie_input(node, "steep-approach", &mk_dinput(steep_approach));
1899   tie_input(node, "gpws-inhibit", &mk_dinput(gpws_inhibit));
1900
1901   tie_input(node, "uncorrected-barometric-altitude", &mk_ainput(uncorrected_barometric_altitude), &mk_ainput_feed(uncorrected_barometric_altitude));
1902   tie_input(node, "barometric-altitude-rate", &mk_ainput(barometric_altitude_rate), &mk_ainput_feed(barometric_altitude_rate));
1903   tie_input(node, "gps-altitude", &mk_ainput(gps_altitude));
1904   tie_input(node, "gps-latitude", &mk_ainput(gps_latitude));
1905   tie_input(node, "gps-longitude", &mk_ainput(gps_longitude));
1906   tie_input(node, "gps-vertical-figure-of-merit", &mk_ainput(gps_vertical_figure_of_merit));
1907   tie_input(node, "radio-altitude", &mk_ainput(radio_altitude), &mk_ainput_feed(radio_altitude));
1908   tie_input(node, "glideslope-deviation", &mk_ainput(glideslope_deviation), &mk_ainput_feed(glideslope_deviation));
1909   tie_input(node, "roll-angle", &mk_ainput(roll_angle), &mk_ainput_feed(roll_angle));
1910   tie_input(node, "localizer-deviation", &mk_ainput(localizer_deviation), &mk_ainput_feed(localizer_deviation));
1911   tie_input(node, "computed-airspeed", &mk_ainput(computed_airspeed), &mk_ainput_feed(computed_airspeed));
1912   tie_input(node, "decision-height", &mk_ainput(decision_height), &mk_ainput_feed(decision_height));
1913
1914   tie_output(node, "gpws-warning", &mk_doutput(gpws_warning));
1915   tie_output(node, "gpws-alert", &mk_doutput(gpws_alert));
1916   tie_output(node, "audio-on", &mk_doutput(audio_on));
1917   tie_output(node, "gpws-inop", &mk_doutput(gpws_inop));
1918   tie_output(node, "tad-inop", &mk_doutput(tad_inop));
1919   tie_output(node, "flap-override", &mk_doutput(flap_override));
1920   tie_output(node, "glideslope-cancel", &mk_doutput(glideslope_cancel));
1921   tie_output(node, "steep-approach", &mk_doutput(steep_approach));
1922
1923   tie_output(node, "egpws-alert-discrete-1", &mk_aoutput(egpws_alert_discrete_1));
1924   tie_output(node, "egpwc-logic-discretes", &mk_aoutput(egpwc_logic_discretes));
1925   tie_output(node, "mode6-callouts-discrete-1", &mk_aoutput(mode6_callouts_discrete_1));
1926   tie_output(node, "mode6-callouts-discrete-2", &mk_aoutput(mode6_callouts_discrete_2));
1927   tie_output(node, "egpws-alert-discrete-2", &mk_aoutput(egpws_alert_discrete_2));
1928   tie_output(node, "egpwc-alert-discrete-3", &mk_aoutput(egpwc_alert_discrete_3));
1929 }
1930
1931 bool
1932 MK_VIII::IOHandler::get_discrete_input (bool *ptr) const
1933 {
1934    return *ptr;
1935 }
1936
1937 void
1938 MK_VIII::IOHandler::set_discrete_input (bool *ptr, bool value)
1939 {
1940   if (value == *ptr)
1941     return;
1942
1943   if (mk->system_handler.state == SystemHandler::STATE_ON)
1944     {
1945       if (ptr == &mk_dinput(momentary_flap_override))
1946         {
1947           if (mk->configuration_module.state == ConfigurationModule::STATE_OK
1948               && mk->self_test_handler.state == SelfTestHandler::STATE_NONE
1949               && conf.momentary_flap_override_enabled
1950               && value)
1951             mk_doutput(flap_override) = ! mk_doutput(flap_override);
1952         }
1953       else if (ptr == &mk_dinput(self_test))
1954         mk->self_test_handler.handle_button_event(value);
1955       else if (ptr == &mk_dinput(glideslope_cancel))
1956         {
1957           if (mk->configuration_module.state == ConfigurationModule::STATE_OK
1958               && mk->self_test_handler.state == SelfTestHandler::STATE_NONE
1959               && value)
1960             {
1961               if (! mk_doutput(glideslope_cancel))
1962                 {
1963                   // [SPEC] 6.2.5
1964
1965                   // Although we are not called from update(), we are
1966                   // sure that the inputs we use here are defined,
1967                   // since state is STATE_ON.
1968
1969                   if (! mk_data(glideslope_deviation_dots).ncd
1970                       && ! mk_data(radio_altitude).ncd
1971                       && mk_data(radio_altitude).get() <= 2000
1972                       && mk_data(radio_altitude).get() >= 30)
1973                     mk_doutput(glideslope_cancel) = true;
1974                 }
1975               // else do nothing ([PILOT] page 22: "Glideslope Cancel
1976               // can not be deselected (reset) by again pressing the
1977               // Glideslope Cancel switch.")
1978             }
1979         }
1980       else if (ptr == &mk_dinput(steep_approach))
1981         {
1982           if (mk->configuration_module.state == ConfigurationModule::STATE_OK
1983               && mk->self_test_handler.state == SelfTestHandler::STATE_NONE
1984               && momentary_steep_approach_enabled()
1985               && value)
1986             mk_doutput(steep_approach) = ! mk_doutput(steep_approach);
1987         }
1988     }
1989
1990   *ptr = value;
1991
1992   if (mk->system_handler.state == SystemHandler::STATE_ON)
1993     update_alternate_discrete_input(ptr);
1994 }
1995
1996 void
1997 MK_VIII::IOHandler::present_status_section (const char *name)
1998 {
1999   printf("%s\n", name);
2000 }
2001
2002 void
2003 MK_VIII::IOHandler::present_status_item (const char *name, const char *value)
2004 {
2005   if (value)
2006     printf("\t%-32s %s\n", name, value);
2007   else
2008     printf("\t%s\n", name);
2009 }
2010
2011 void
2012 MK_VIII::IOHandler::present_status_subitem (const char *name)
2013 {
2014   printf("\t\t%s\n", name);
2015 }
2016
2017 void
2018 MK_VIII::IOHandler::present_status ()
2019 {
2020   // [SPEC] 6.10.13
2021
2022   if (mk->system_handler.state != SystemHandler::STATE_ON)
2023     return;
2024
2025   present_status_section("EGPWC CONFIGURATION");
2026   present_status_item("PART NUMBER:", "965-1220-000"); // [SPEC] 1.1
2027   present_status_item("MOD STATUS:", "N/A");
2028   present_status_item("SERIAL NUMBER:", "N/A");
2029   printf("\n");
2030   present_status_item("APPLICATION S/W VERSION:", FLIGHTGEAR_VERSION);
2031   present_status_item("TERRAIN DATABASE VERSION:", FLIGHTGEAR_VERSION);
2032   present_status_item("ENVELOPE MOD DATABASE VERSION:", "N/A");
2033   present_status_item("BOOT CODE VERSION:", FLIGHTGEAR_VERSION);
2034   printf("\n");
2035
2036   present_status_section("CURRENT FAULTS");
2037   if (mk->configuration_module.state == ConfigurationModule::STATE_OK && ! mk->fault_handler.has_faults())
2038     present_status_item("NO FAULTS");
2039   else
2040     {
2041       if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
2042         {
2043           present_status_item("GPWS COMPUTER FAULTS:");
2044           switch (mk->configuration_module.state)
2045             {
2046             case ConfigurationModule::STATE_INVALID_DATABASE:
2047               present_status_subitem("APPLICATION DATABASE FAILED");
2048               break;
2049
2050             case ConfigurationModule::STATE_INVALID_AIRCRAFT_TYPE:
2051               present_status_subitem("CONFIGURATION TYPE INVALID");
2052               break;
2053
2054             default:
2055               assert_not_reached();
2056               break;
2057             }
2058         }
2059       else
2060         {
2061           present_status_item("GPWS COMPUTER OK");
2062           present_status_item("GPWS EXTERNAL FAULTS:");
2063
2064           static const char *fault_names[] = {
2065             "ALL MODES INHIBIT",
2066             "GEAR SWITCH",
2067             "FLAPS SWITCH",
2068             "MOMENTARY FLAP OVERRIDE INVALID",
2069             "SELF TEST INVALID",
2070             "GLIDESLOPE CANCEL INVALID",
2071             "STEEP APPROACH INVALID",
2072             "GPWS INHIBIT",
2073             "TA & TCF INHIBIT",
2074             "MODES 1-4 INPUTS INVALID",
2075             "MODE 5 INPUTS INVALID",
2076             "MODE 6 INPUTS INVALID",
2077             "BANK ANGLE INPUTS INVALID",
2078             "TCF INPUTS INVALID"
2079           };
2080
2081           for (int i = 0; i < n_elements(fault_names); i++)
2082             if (mk->fault_handler.faults[i])
2083               present_status_subitem(fault_names[i]);
2084         }
2085     }
2086   printf("\n");
2087
2088   present_status_section("CONFIGURATION:");
2089
2090   static const char *category_names[] = {
2091     "AIRCRAFT TYPE",
2092     "AIR DATA TYPE",
2093     "POSITION INPUT TYPE",
2094     "CALLOUTS OPTION TYPE",
2095     "AUDIO MENU TYPE",
2096     "TERRAIN DISPLAY TYPE",
2097     "OPTIONS 1 TYPE",
2098     "RADIO ALTITUDE TYPE",
2099     "NAVIGATION INPUT TYPE",
2100     "ATTITUDE TYPE",
2101     "MAGNETIC HEADING TYPE",
2102     "WINDSHEAR INPUT TYPE",
2103     "IO DISCRETE TYPE",
2104     "VOLUME SELECT"
2105   };
2106
2107   for (int i = 0; i < n_elements(category_names); i++)
2108     {
2109       std::ostringstream value;
2110       value << "= " << mk->configuration_module.effective_categories[i];
2111       present_status_item(category_names[i], value.str().c_str());
2112     }
2113 }
2114
2115 bool
2116 MK_VIII::IOHandler::get_present_status () const
2117 {
2118    return false;
2119 }
2120
2121 void
2122 MK_VIII::IOHandler::set_present_status (bool value)
2123
2124    if (value) present_status();
2125 }
2126
2127
2128 ///////////////////////////////////////////////////////////////////////////////
2129 // VoicePlayer ////////////////////////////////////////////////////////////////
2130 ///////////////////////////////////////////////////////////////////////////////
2131
2132 void
2133 MK_VIII::VoicePlayer::Speaker::bind (SGPropertyNode *node)
2134 {
2135   // uses xmlsound property names
2136   tie(node, "volume", &volume);
2137   tie(node, "pitch", &pitch);
2138   tie(node, "position/x", &position[0]);
2139   tie(node, "position/y", &position[1]);
2140   tie(node, "position/z", &position[2]);
2141   tie(node, "orientation/x", &orientation[0]);
2142   tie(node, "orientation/y", &orientation[1]);
2143   tie(node, "orientation/z", &orientation[2]);
2144   tie(node, "orientation/inner-cone", &inner_cone);
2145   tie(node, "orientation/outer-cone", &outer_cone);
2146   tie(node, "reference-dist", &reference_dist);
2147   tie(node, "max-dist", &max_dist);
2148 }
2149
2150 void
2151 MK_VIII::VoicePlayer::Speaker::update_configuration ()
2152 {
2153   map<string, SGSoundSample *>::iterator iter;
2154   for (iter = player->samples.begin(); iter != player->samples.end(); iter++)
2155     {
2156       SGSoundSample *sample = (*iter).second;
2157
2158       sample->set_pitch(pitch);
2159       sample->set_offset_pos(position);
2160       sample->set_orientation(orientation,
2161                               inner_cone,
2162                               outer_cone,
2163                               outer_gain);
2164       sample->set_reference_dist(reference_dist);
2165       sample->set_max_dist(max_dist);
2166     }
2167
2168   if (player->voice)
2169     player->voice->volume_changed();
2170 }
2171
2172 MK_VIII::VoicePlayer::Voice::~Voice ()
2173 {
2174   for (iter = elements.begin(); iter != elements.end(); iter++)
2175     delete *iter;               // we owned the element
2176   elements.clear();
2177 }
2178
2179 void
2180 MK_VIII::VoicePlayer::Voice::play ()
2181 {
2182   iter = elements.begin();
2183   element = *iter;
2184
2185   element->play(get_volume());
2186 }
2187
2188 void
2189 MK_VIII::VoicePlayer::Voice::stop (bool now)
2190 {
2191   if (element)
2192     {
2193       if (now || element->silence)
2194         {
2195           element->stop();
2196           element = NULL;
2197         }
2198       else
2199         iter = elements.end() - 1; // stop after the current element finishes
2200     }
2201 }
2202
2203 void
2204 MK_VIII::VoicePlayer::Voice::set_volume (double _volume)
2205 {
2206   volume = _volume;
2207   volume_changed();
2208 }
2209
2210 void
2211 MK_VIII::VoicePlayer::Voice::volume_changed ()
2212 {
2213   if (element)
2214     element->set_volume(get_volume());
2215 }
2216
2217 void
2218 MK_VIII::VoicePlayer::Voice::update ()
2219 {
2220   if (element)
2221     {
2222       if (! element->is_playing())
2223         {
2224           if (++iter == elements.end())
2225             element = NULL;
2226           else
2227             {
2228               element = *iter;
2229               element->play(get_volume());
2230             }
2231         }
2232     }
2233 }
2234
2235 MK_VIII::VoicePlayer::~VoicePlayer ()
2236 {
2237   vector<Voice *>::iterator iter1;
2238   for (iter1 = _voices.begin(); iter1 != _voices.end(); iter1++)
2239     delete *iter1;
2240   _voices.clear();
2241
2242 /* sound mgr already destroyed - samples already deleted
2243   map<string, SGSoundSample *>::iterator iter2;
2244   for (iter2 = samples.begin(); iter2 != samples.end(); iter2++)
2245     {
2246       bool status = globals->get_soundmgr()->remove((*iter2).first);
2247       assert(status);
2248     }
2249 */
2250   samples.clear();
2251 }
2252
2253 void
2254 MK_VIII::VoicePlayer::init ()
2255 {
2256 #define STDPAUSE 0.75   // [SPEC] 6.4.4: "the standard 0.75 second delay"
2257
2258   make_voice(&voices.application_data_base_failed, "application-data-base-failed");
2259   make_voice(&voices.bank_angle, "bank-angle");
2260   make_voice(&voices.bank_angle_bank_angle, "bank-angle", "bank-angle");
2261   make_voice(&voices.bank_angle_bank_angle_3, "bank-angle", "bank-angle", 3.0);
2262   make_voice(&voices.bank_angle_inop, "bank-angle-inop");
2263   make_voice(&voices.bank_angle_pause_bank_angle, "bank-angle", STDPAUSE, "bank-angle");
2264   make_voice(&voices.bank_angle_pause_bank_angle_3, "bank-angle", STDPAUSE, "bank-angle", 3.0);
2265   make_voice(&voices.callouts_inop, "callouts-inop");
2266   make_voice(&voices.configuration_type_invalid, "configuration-type-invalid");
2267   make_voice(&voices.dont_sink, "dont-sink");
2268   make_voice(&voices.dont_sink_pause_dont_sink, "dont-sink", STDPAUSE, "dont-sink");
2269   make_voice(&voices.five_hundred_above, "500-above");
2270   make_voice(&voices.glideslope, "glideslope");
2271   make_voice(&voices.glideslope_inop, "glideslope-inop");
2272   make_voice(&voices.gpws_inop, "gpws-inop");
2273   make_voice(&voices.hard_glideslope, "glideslope", "glideslope", 3.0);
2274   make_voice(&voices.minimums, "minimums");
2275   make_voice(&voices.minimums_minimums, "minimums", "minimums");
2276   make_voice(&voices.pull_up, "pull-up");
2277   make_voice(&voices.sink_rate, "sink-rate");
2278   make_voice(&voices.sink_rate_pause_sink_rate, "sink-rate", STDPAUSE, "sink-rate");
2279   make_voice(&voices.soft_glideslope, new Voice::SampleElement(get_sample("glideslope"), modify_amplitude(1.0, -6)));
2280   make_voice(&voices.terrain, "terrain");
2281   make_voice(&voices.terrain_pause_terrain, "terrain", STDPAUSE, "terrain");
2282   make_voice(&voices.too_low_flaps, "too-low-flaps");
2283   make_voice(&voices.too_low_gear, "too-low-gear");
2284   make_voice(&voices.too_low_terrain, "too-low-terrain");
2285
2286   for (int i = 0; i < n_altitude_callouts; i++)
2287     {
2288       std::ostringstream name;
2289       name << "altitude-" << mk->mode6_handler.altitude_callout_definitions[i];
2290       make_voice(&voices.altitude_callouts[i], name.str().c_str());
2291     }
2292
2293   speaker.update_configuration();
2294 }
2295
2296 SGSoundSample *
2297 MK_VIII::VoicePlayer::get_sample (const char *name)
2298 {
2299   std::ostringstream refname;
2300   refname << mk->name << "[" << mk->num << "]" << "/" << name;
2301
2302   SGSoundSample *sample = globals->get_soundmgr()->find(refname.str());
2303   if (! sample)
2304     {
2305       SGPath sample_path(globals->get_fg_root());
2306       sample_path.append("Sounds/mk-viii");
2307
2308       string filename = string(name) + ".wav";
2309       try
2310         {
2311           sample = new SGSoundSample(sample_path.c_str(), filename.c_str());
2312         }
2313       catch (const sg_exception &e)
2314         {
2315           SG_LOG(SG_INSTR, SG_ALERT, "Error loading MK VIII sound sample \"" + filename + "\": " + e.getFormattedMessage());
2316           exit(1);
2317         }
2318
2319       globals->get_soundmgr()->add(sample, refname.str());
2320       samples[refname.str()] = sample;
2321     }
2322
2323   return sample;
2324 }
2325
2326 void
2327 MK_VIII::VoicePlayer::play (Voice *_voice, unsigned int flags)
2328 {
2329   if (test_bits(flags, PLAY_NOW) || ! voice || voice->element->silence)
2330     {
2331       if (voice)
2332         voice->stop(true);
2333
2334       voice = _voice;
2335       looped = test_bits(flags, PLAY_LOOPED);
2336
2337       next_voice = NULL;
2338       next_looped = false;
2339
2340       voice->play();
2341     }
2342   else
2343     {
2344       next_voice = _voice;
2345       next_looped = test_bits(flags, PLAY_LOOPED);
2346     }
2347 }
2348
2349 void
2350 MK_VIII::VoicePlayer::stop (unsigned int flags)
2351 {
2352   if (voice)
2353     {
2354       voice->stop(test_bits(flags, STOP_NOW));
2355       if (voice->element)
2356         looped = false;
2357       else
2358         voice = NULL;
2359       next_voice = NULL;
2360     }
2361 }
2362
2363 void
2364 MK_VIII::VoicePlayer::set_volume (double _volume)
2365 {
2366   volume = _volume;
2367   if (voice)
2368     voice->volume_changed();
2369 }
2370
2371 void
2372 MK_VIII::VoicePlayer::update ()
2373 {
2374   if (voice)
2375     {
2376       voice->update();
2377
2378       if (next_voice)
2379         {
2380           if (! voice->element || voice->element->silence)
2381             {
2382               voice = next_voice;
2383               looped = next_looped;
2384
2385               next_voice = NULL;
2386               next_looped = false;
2387
2388               voice->play();
2389             }
2390         }
2391       else
2392         {
2393           if (! voice->element)
2394             {
2395               if (looped)
2396                 voice->play();
2397               else
2398                 voice = NULL;
2399             }
2400         }
2401     }
2402 }
2403
2404 ///////////////////////////////////////////////////////////////////////////////
2405 // SelfTestHandler ////////////////////////////////////////////////////////////
2406 ///////////////////////////////////////////////////////////////////////////////
2407
2408 bool
2409 MK_VIII::SelfTestHandler::_was_here (int position)
2410 {
2411   if (position > current)
2412     {
2413       current = position;
2414       return false;
2415     }
2416   else
2417     return true;
2418 }
2419
2420 MK_VIII::SelfTestHandler::Action
2421 MK_VIII::SelfTestHandler::sleep (double duration)
2422 {
2423   Action _action = { ACTION_SLEEP, duration, NULL };
2424   return _action;
2425 }
2426
2427 MK_VIII::SelfTestHandler::Action
2428 MK_VIII::SelfTestHandler::play (VoicePlayer::Voice *voice)
2429 {
2430   mk->voice_player.play(voice);
2431   Action _action = { ACTION_VOICE, 0, NULL };
2432   return _action;
2433 }
2434
2435 MK_VIII::SelfTestHandler::Action
2436 MK_VIII::SelfTestHandler::discrete_on (bool *discrete, double duration)
2437 {
2438   *discrete = true;
2439   return sleep(duration);
2440 }
2441
2442 MK_VIII::SelfTestHandler::Action
2443 MK_VIII::SelfTestHandler::discrete_on_off (bool *discrete, double duration)
2444 {
2445   *discrete = true;
2446   Action _action = { ACTION_SLEEP | ACTION_DISCRETE_ON_OFF, duration, discrete };
2447   return _action;
2448 }
2449
2450 MK_VIII::SelfTestHandler::Action
2451 MK_VIII::SelfTestHandler::discrete_on_off (bool *discrete, VoicePlayer::Voice *voice)
2452 {
2453   *discrete = true;
2454   mk->voice_player.play(voice);
2455   Action _action = { ACTION_VOICE | ACTION_DISCRETE_ON_OFF, 0, discrete };
2456   return _action;
2457 }
2458
2459 MK_VIII::SelfTestHandler::Action
2460 MK_VIII::SelfTestHandler::done ()
2461 {
2462   Action _action = { ACTION_DONE, 0, NULL };
2463   return _action;
2464 }
2465
2466 MK_VIII::SelfTestHandler::Action
2467 MK_VIII::SelfTestHandler::run ()
2468 {
2469   // Note that "Terrain INOP" and "Terrain NA" are or'ed to the same
2470   // output discrete (see [SPEC] 6.9.3.5).
2471
2472 #define was_here()              was_here_offset(0)
2473 #define was_here_offset(offset) _was_here(__LINE__ * 20 + (offset))
2474
2475   if (! was_here())
2476     {
2477       if (mk->configuration_module.state == ConfigurationModule::STATE_INVALID_DATABASE)
2478         return play(mk_voice(application_data_base_failed));
2479       else if (mk->configuration_module.state == ConfigurationModule::STATE_INVALID_AIRCRAFT_TYPE)
2480         return play(mk_voice(configuration_type_invalid));
2481     }
2482   if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
2483     return done();
2484
2485   if (! was_here())
2486     return discrete_on(&mk_doutput(gpws_inop), 0.7);
2487   if (! was_here())
2488     return sleep(0.7);          // W/S INOP
2489   if (! was_here())
2490     return discrete_on(&mk_doutput(tad_inop), 0.7);
2491   if (! was_here())
2492     {
2493       mk_doutput(gpws_inop) = false;
2494       // do not disable tad_inop since we must enable Terrain NA
2495       // do not disable W/S INOP since we do not emulate it
2496       return sleep(0.7);        // Terrain NA
2497     }
2498   if (! was_here())
2499     {
2500       mk_doutput(tad_inop) = false; // disable Terrain NA
2501       if (mk->io_handler.conf.momentary_flap_override_enabled)
2502         return discrete_on_off(&mk_doutput(flap_override), 1.0);
2503     }
2504   if (! was_here())
2505     return discrete_on_off(&mk_doutput(audio_on), 1.0);
2506   if (! was_here())
2507     {
2508       if (mk->io_handler.momentary_steep_approach_enabled())
2509         return discrete_on_off(&mk_doutput(steep_approach), 1.0);
2510     }
2511
2512   if (! was_here())
2513     {
2514       if (mk_dinput(glideslope_inhibit))
2515         goto glideslope_end;
2516       else
2517         {
2518           if (mk->fault_handler.faults[FaultHandler::FAULT_MODE5_INPUTS_INVALID])
2519             goto glideslope_inop;
2520         }
2521     }
2522   if (! was_here())
2523     return discrete_on_off(&mk_doutput(gpws_alert), mk_voice(glideslope));
2524   if (! was_here())
2525     return discrete_on_off(&mk_doutput(glideslope_cancel), 0.7);
2526   goto glideslope_end;
2527  glideslope_inop:
2528   if (! was_here())
2529     return play(mk_voice(glideslope_inop));
2530  glideslope_end:
2531
2532   if (! was_here())
2533     {
2534       if (mk->fault_handler.faults[FaultHandler::FAULT_MODES14_INPUTS_INVALID])
2535         goto gpws_inop;
2536     }
2537   if (! was_here())
2538     return discrete_on_off(&mk_doutput(gpws_warning), mk_voice(pull_up));
2539   goto gpws_end;
2540  gpws_inop:
2541   if (! was_here())
2542     return play(mk_voice(gpws_inop));
2543  gpws_end:
2544
2545   if (! was_here())
2546     {
2547       if (mk_dinput(self_test)) // proceed to long self test
2548         goto long_test;
2549     }
2550   if (! was_here())
2551     {
2552       if (mk->mode6_handler.conf.bank_angle_enabled
2553           && mk->fault_handler.faults[FaultHandler::FAULT_BANK_ANGLE_INPUTS_INVALID])
2554         return play(mk_voice(bank_angle_inop));
2555     }
2556   if (! was_here())
2557     {
2558       if (mk->mode6_handler.altitude_callouts_enabled()
2559           && mk->fault_handler.faults[FaultHandler::FAULT_MODE6_INPUTS_INVALID])
2560         return play(mk_voice(callouts_inop));
2561     }
2562   if (! was_here())
2563     return done();
2564
2565  long_test:
2566   if (! was_here())
2567     {
2568       mk_doutput(gpws_inop) = true;
2569       // do not enable W/S INOP, since we do not emulate it
2570       mk_doutput(tad_inop) = true; // Terrain INOP, Terrain NA
2571
2572       return play(mk_voice(sink_rate));
2573     }
2574   if (! was_here())
2575     return play(mk_voice(pull_up));
2576   if (! was_here())
2577     return play(mk_voice(terrain));
2578   if (! was_here())
2579     return play(mk_voice(pull_up));
2580   if (! was_here())
2581     return play(mk_voice(dont_sink));
2582   if (! was_here())
2583     return play(mk_voice(too_low_terrain));
2584   if (! was_here())
2585     return play(mk->mode4_handler.conf.voice_too_low_gear);
2586   if (! was_here())
2587     return play(mk_voice(too_low_flaps));
2588   if (! was_here())
2589     return play(mk_voice(too_low_terrain));
2590   if (! was_here())
2591     return play(mk_voice(glideslope));
2592   if (! was_here())
2593     {
2594       if (mk->mode6_handler.conf.bank_angle_enabled)
2595         return play(mk_voice(bank_angle));
2596     }
2597
2598   if (! was_here())
2599     {
2600       if (! mk->mode6_handler.altitude_callouts_enabled())
2601         goto callouts_disabled;
2602     }
2603   if (! was_here())
2604     {
2605       if (mk->mode6_handler.conf.minimums_enabled)
2606         return play(mk_voice(minimums));
2607     }
2608   if (! was_here())
2609     {
2610       if (mk->mode6_handler.conf.above_field_voice)
2611         return play(mk->mode6_handler.conf.above_field_voice);
2612     }
2613   for (int i = 0; i < n_altitude_callouts; i++)
2614     if (! was_here_offset(i))
2615       {
2616         if (mk->mode6_handler.conf.altitude_callouts_enabled[i])
2617           return play(mk_altitude_voice(i));
2618       }
2619   if (! was_here())
2620     {
2621       if (mk->mode6_handler.conf.smart_500_enabled)
2622         return play(mk_altitude_voice(Mode6Handler::ALTITUDE_CALLOUT_500));
2623     }
2624   goto callouts_end;
2625  callouts_disabled:
2626   if (! was_here())
2627     return play(mk_voice(minimums_minimums));
2628  callouts_end:
2629
2630   if (! was_here())
2631     {
2632       if (mk->tcf_handler.conf.enabled)
2633         return play(mk_voice(too_low_terrain));
2634     }
2635
2636   return done();
2637 }
2638
2639 void
2640 MK_VIII::SelfTestHandler::start ()
2641 {
2642   assert(state == STATE_START);
2643
2644   memcpy(&saved_outputs, &mk->io_handler.outputs, sizeof(mk->io_handler.outputs));
2645   memset(&mk->io_handler.outputs, 0, sizeof(mk->io_handler.outputs));
2646
2647   // [SPEC] 6.10.6: "The self-test results are annunciated, at 6db
2648   // lower than the normal audio level selected for the aircraft"
2649   mk->voice_player.set_volume(modify_amplitude(mk->voice_player.conf.volume, -6));
2650
2651   if (mk_dinput(mode6_low_volume))
2652     mk->mode6_handler.set_volume(1.0);
2653
2654   state = STATE_RUNNING;
2655   cancel = CANCEL_NONE;
2656   memset(&action, 0, sizeof(action));
2657   current = 0;
2658 }
2659
2660 void
2661 MK_VIII::SelfTestHandler::stop ()
2662 {
2663   if (state != STATE_NONE)
2664     {
2665       if (state != STATE_START)
2666         {
2667           mk->voice_player.stop(VoicePlayer::STOP_NOW);
2668           mk->voice_player.set_volume(mk_dinput(audio_inhibit) ? 0.0 : mk->voice_player.conf.volume);
2669
2670           if (mk_dinput(mode6_low_volume))
2671             mk->mode6_handler.set_volume(modify_amplitude(1.0, -6));
2672
2673           memcpy(&mk->io_handler.outputs, &saved_outputs, sizeof(mk->io_handler.outputs));
2674         }
2675
2676       button_pressed = false;
2677       state = STATE_NONE;
2678     }
2679 }
2680
2681 void
2682 MK_VIII::SelfTestHandler::handle_button_event (bool value)
2683 {
2684   if (state == STATE_NONE)
2685     {
2686       if (value)
2687         state = STATE_START;
2688     }
2689   else if (state == STATE_START)
2690     {
2691       if (value)
2692         state = STATE_NONE;     // cancel the not-yet-started test
2693     }
2694   else if (cancel == CANCEL_NONE)
2695     {
2696       if (value)
2697         {
2698           assert(! button_pressed);
2699           button_pressed = true;
2700           button_press_timestamp = globals->get_sim_time_sec();
2701         }
2702       else
2703         {
2704           if (button_pressed)
2705             {
2706               if (globals->get_sim_time_sec() - button_press_timestamp < 2)
2707                 cancel = CANCEL_SHORT;
2708               else
2709                 cancel = CANCEL_LONG;
2710             }
2711         }
2712     }
2713 }
2714
2715 bool
2716 MK_VIII::SelfTestHandler::update ()
2717 {
2718   if (state == STATE_NONE)
2719     return false;
2720   else if (state == STATE_START)
2721     {
2722       if (mk->state_handler.ground && ! mk->io_handler.steep_approach())
2723         start();
2724       else
2725         {
2726           state = STATE_NONE;
2727           return false;
2728         }
2729     }
2730   else
2731     {
2732       if (button_pressed && cancel == CANCEL_NONE)
2733         {
2734           if (globals->get_sim_time_sec() - button_press_timestamp >= 2)
2735             cancel = CANCEL_LONG;
2736         }
2737     }
2738
2739   if (! mk->state_handler.ground || cancel != CANCEL_NONE)
2740     {
2741       stop();
2742       return false;
2743     }
2744
2745   if (test_bits(action.flags, ACTION_SLEEP))
2746     {
2747       if (globals->get_sim_time_sec() - sleep_start < action.sleep_duration)
2748         return true;
2749     }
2750   if (test_bits(action.flags, ACTION_VOICE))
2751     {
2752       if (mk->voice_player.voice)
2753         return true;
2754     }
2755   if (test_bits(action.flags, ACTION_DISCRETE_ON_OFF))
2756     *action.discrete = false;
2757
2758   action = run();
2759
2760   if (test_bits(action.flags, ACTION_SLEEP))
2761     sleep_start = globals->get_sim_time_sec();
2762   if (test_bits(action.flags, ACTION_DONE))
2763     {
2764       stop();
2765       return false;
2766     }
2767
2768   return true;
2769 }
2770
2771 ///////////////////////////////////////////////////////////////////////////////
2772 // AlertHandler ///////////////////////////////////////////////////////////////
2773 ///////////////////////////////////////////////////////////////////////////////
2774
2775 bool
2776 MK_VIII::AlertHandler::select_voice_alerts (unsigned int test)
2777 {
2778   if (has_alerts(test))
2779     {
2780       voice_alerts = alerts & test;
2781       return true;
2782     }
2783   else
2784     {
2785       voice_alerts &= ~test;
2786       if (voice_alerts == 0)
2787         mk->voice_player.stop();
2788
2789       return false;
2790     }
2791 }
2792
2793 void
2794 MK_VIII::AlertHandler::boot ()
2795 {
2796   reset();
2797 }
2798
2799 void
2800 MK_VIII::AlertHandler::reposition ()
2801 {
2802   reset();
2803
2804   mk->io_handler.set_lamp(IOHandler::LAMP_NONE);
2805   mk->voice_player.stop(VoicePlayer::STOP_NOW);
2806 }
2807
2808 void
2809 MK_VIII::AlertHandler::reset ()
2810 {
2811   alerts = 0;
2812   old_alerts = 0;
2813   voice_alerts = 0;
2814   repeated_alerts = 0;
2815   altitude_callout_voice = NULL;
2816 }
2817
2818 void
2819 MK_VIII::AlertHandler::update ()
2820 {
2821   if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
2822     return;
2823
2824   // Lamps and voices are prioritized according to [SPEC] 6.9.2.
2825   //
2826   // Voices which can interrupt other voices (VoicePlayer::PLAY_NOW)
2827   // are specified by [INSTALL] appendix E 5.3.5.
2828   //
2829   // When a voice is overriden by a higher priority voice and the
2830   // overriding voice stops, we restore the previous voice if it was
2831   // looped (this is not specified by [SPEC] but seems to make sense).
2832
2833   // update lamp
2834
2835   if (has_alerts(ALERT_MODE1_PULL_UP | ALERT_MODE2A | ALERT_MODE2B))
2836     mk->io_handler.set_lamp(IOHandler::LAMP_WARNING);
2837   else if (has_alerts(ALERT_MODE1_SINK_RATE
2838                       | ALERT_MODE2A_PREFACE
2839                       | ALERT_MODE2B_PREFACE
2840                       | ALERT_MODE2A_ALTITUDE_GAIN_TERRAIN_CLOSING
2841                       | ALERT_MODE2A_ALTITUDE_GAIN
2842                       | ALERT_MODE2B_LANDING_MODE
2843                       | ALERT_MODE3
2844                       | ALERT_MODE4_TOO_LOW_FLAPS
2845                       | ALERT_MODE4_TOO_LOW_GEAR
2846                       | ALERT_MODE4AB_TOO_LOW_TERRAIN
2847                       | ALERT_MODE4C_TOO_LOW_TERRAIN
2848                       | ALERT_TCF_TOO_LOW_TERRAIN))
2849     mk->io_handler.set_lamp(IOHandler::LAMP_CAUTION);
2850   else if (has_alerts(ALERT_MODE5_SOFT | ALERT_MODE5_HARD))
2851     mk->io_handler.set_lamp(IOHandler::LAMP_GLIDESLOPE);
2852   else
2853     mk->io_handler.set_lamp(IOHandler::LAMP_NONE);
2854
2855   // update voice
2856
2857   if (select_voice_alerts(ALERT_MODE1_PULL_UP))
2858     {
2859       if (! has_old_alerts(ALERT_MODE1_PULL_UP))
2860         {
2861           if (mk->voice_player.voice != mk_voice(sink_rate_pause_sink_rate))
2862             mk->voice_player.play(mk_voice(sink_rate), VoicePlayer::PLAY_NOW);
2863           mk->voice_player.play(mk_voice(pull_up), VoicePlayer::PLAY_LOOPED);
2864         }
2865     }
2866   else if (select_voice_alerts(ALERT_MODE2A_PREFACE | ALERT_MODE2B_PREFACE))
2867     {
2868       if (! has_old_alerts(ALERT_MODE2A_PREFACE | ALERT_MODE2B_PREFACE))
2869         mk->voice_player.play(mk_voice(terrain_pause_terrain), VoicePlayer::PLAY_NOW);
2870     }
2871   else if (select_voice_alerts(ALERT_MODE2A | ALERT_MODE2B))
2872     {
2873       if (mk->voice_player.voice != mk_voice(pull_up))
2874         mk->voice_player.play(mk_voice(pull_up), VoicePlayer::PLAY_NOW | VoicePlayer::PLAY_LOOPED);
2875     }
2876   else if (select_voice_alerts(ALERT_MODE2A_ALTITUDE_GAIN_TERRAIN_CLOSING | ALERT_MODE2B_LANDING_MODE))
2877     {
2878       if (mk->voice_player.voice != mk_voice(terrain))
2879         mk->voice_player.play(mk_voice(terrain), VoicePlayer::PLAY_LOOPED);
2880     }
2881   else if (select_voice_alerts(ALERT_MODE6_MINIMUMS))
2882     {
2883       if (! has_old_alerts(ALERT_MODE6_MINIMUMS))
2884         mk->voice_player.play(mk_voice(minimums_minimums));
2885     }
2886   else if (select_voice_alerts(ALERT_MODE4AB_TOO_LOW_TERRAIN | ALERT_MODE4C_TOO_LOW_TERRAIN))
2887     {
2888       if (must_play_voice(ALERT_MODE4AB_TOO_LOW_TERRAIN | ALERT_MODE4C_TOO_LOW_TERRAIN))
2889         mk->voice_player.play(mk_voice(too_low_terrain));
2890     }
2891   else if (select_voice_alerts(ALERT_TCF_TOO_LOW_TERRAIN))
2892     {
2893       if (must_play_voice(ALERT_TCF_TOO_LOW_TERRAIN))
2894         mk->voice_player.play(mk_voice(too_low_terrain));
2895     }
2896   else if (select_voice_alerts(ALERT_MODE6_ALTITUDE_CALLOUT))
2897     {
2898       if (! has_old_alerts(ALERT_MODE6_ALTITUDE_CALLOUT))
2899         {
2900           assert(altitude_callout_voice != NULL);
2901           mk->voice_player.play(altitude_callout_voice);
2902         }
2903     }
2904   else if (select_voice_alerts(ALERT_MODE4_TOO_LOW_GEAR))
2905     {
2906       if (must_play_voice(ALERT_MODE4_TOO_LOW_GEAR))
2907         mk->voice_player.play(mk->mode4_handler.conf.voice_too_low_gear);
2908     }
2909   else if (select_voice_alerts(ALERT_MODE4_TOO_LOW_FLAPS))
2910     {
2911       if (must_play_voice(ALERT_MODE4_TOO_LOW_FLAPS))
2912         mk->voice_player.play(mk_voice(too_low_flaps));
2913     }
2914   else if (select_voice_alerts(ALERT_MODE1_SINK_RATE))
2915     {
2916       if (must_play_voice(ALERT_MODE1_SINK_RATE))
2917         mk->voice_player.play(mk_voice(sink_rate_pause_sink_rate));
2918       // [SPEC] 6.2.1: "During the time that the voice message for the
2919       // outer envelope is inhibited and the caution/warning lamp is
2920       // on, the Mode 5 alert message is allowed to annunciate for
2921       // excessive glideslope deviation conditions."
2922       else if (mk->voice_player.voice != mk_voice(sink_rate_pause_sink_rate)
2923                && mk->voice_player.next_voice != mk_voice(sink_rate_pause_sink_rate))
2924         {
2925           if (has_alerts(ALERT_MODE5_HARD))
2926             {
2927               voice_alerts |= ALERT_MODE5_HARD;
2928               if (mk->voice_player.voice != mk_voice(hard_glideslope))
2929                 mk->voice_player.play(mk_voice(hard_glideslope), VoicePlayer::PLAY_LOOPED);
2930             }
2931           else if (has_alerts(ALERT_MODE5_SOFT))
2932             {
2933               voice_alerts |= ALERT_MODE5_SOFT;
2934               if (must_play_voice(ALERT_MODE5_SOFT))
2935                 mk->voice_player.play(mk_voice(soft_glideslope));
2936             }
2937         }
2938     }
2939   else if (select_voice_alerts(ALERT_MODE3))
2940     {
2941       if (must_play_voice(ALERT_MODE3))
2942         mk->voice_player.play(mk_voice(dont_sink_pause_dont_sink));
2943     }
2944   else if (select_voice_alerts(ALERT_MODE5_HARD))
2945     {
2946       if (mk->voice_player.voice != mk_voice(hard_glideslope))
2947         mk->voice_player.play(mk_voice(hard_glideslope), VoicePlayer::PLAY_LOOPED);
2948     }
2949   else if (select_voice_alerts(ALERT_MODE5_SOFT))
2950     {
2951       if (must_play_voice(ALERT_MODE5_SOFT))
2952         mk->voice_player.play(mk_voice(soft_glideslope));
2953     }
2954   else if (select_voice_alerts(ALERT_MODE6_LOW_BANK_ANGLE_3))
2955     {
2956       if (mk->voice_player.voice != mk_voice(bank_angle_bank_angle_3))
2957         mk->voice_player.play(mk_voice(bank_angle_bank_angle_3), VoicePlayer::PLAY_LOOPED);
2958     }
2959   else if (select_voice_alerts(ALERT_MODE6_HIGH_BANK_ANGLE_3))
2960     {
2961       if (mk->voice_player.voice != mk_voice(bank_angle_pause_bank_angle_3))
2962         mk->voice_player.play(mk_voice(bank_angle_pause_bank_angle_3), VoicePlayer::PLAY_LOOPED);
2963     }
2964   else if (select_voice_alerts(ALERT_MODE6_LOW_BANK_ANGLE_2))
2965     {
2966       if (! has_old_alerts(ALERT_MODE6_LOW_BANK_ANGLE_2 | ALERT_MODE6_HIGH_BANK_ANGLE_2))
2967         mk->voice_player.play(mk_voice(bank_angle_bank_angle));
2968     }
2969   else if (select_voice_alerts(ALERT_MODE6_HIGH_BANK_ANGLE_2))
2970     {
2971       if (! has_old_alerts(ALERT_MODE6_LOW_BANK_ANGLE_2 | ALERT_MODE6_HIGH_BANK_ANGLE_2))
2972         mk->voice_player.play(mk_voice(bank_angle_pause_bank_angle));
2973     }
2974   else if (select_voice_alerts(ALERT_MODE6_LOW_BANK_ANGLE_1))
2975     {
2976       if (! has_old_alerts(ALERT_MODE6_LOW_BANK_ANGLE_1 | ALERT_MODE6_HIGH_BANK_ANGLE_1))
2977         mk->voice_player.play(mk_voice(bank_angle_bank_angle));
2978     }
2979   else if (select_voice_alerts(ALERT_MODE6_HIGH_BANK_ANGLE_1))
2980     {
2981       if (! has_old_alerts(ALERT_MODE6_LOW_BANK_ANGLE_1 | ALERT_MODE6_HIGH_BANK_ANGLE_1))
2982         mk->voice_player.play(mk_voice(bank_angle_pause_bank_angle));
2983     }
2984
2985   // set new state
2986
2987   old_alerts = alerts;
2988   repeated_alerts = 0;
2989   altitude_callout_voice = NULL;
2990 }
2991
2992 void
2993 MK_VIII::AlertHandler::set_alerts (unsigned int _alerts,
2994                                    unsigned int flags,
2995                                    VoicePlayer::Voice *_altitude_callout_voice)
2996 {
2997   alerts |= _alerts;
2998   if (test_bits(flags, ALERT_FLAG_REPEAT))
2999     repeated_alerts |= _alerts;
3000   if (_altitude_callout_voice)
3001     altitude_callout_voice = _altitude_callout_voice;
3002 }
3003
3004 void
3005 MK_VIII::AlertHandler::unset_alerts (unsigned int _alerts)
3006 {
3007   alerts &= ~_alerts;
3008   repeated_alerts &= ~_alerts;
3009 }
3010
3011 ///////////////////////////////////////////////////////////////////////////////
3012 // StateHandler ///////////////////////////////////////////////////////////////
3013 ///////////////////////////////////////////////////////////////////////////////
3014
3015 void
3016 MK_VIII::StateHandler::update_ground ()
3017 {
3018   if (ground)
3019     {
3020       if (! mk_ainput(computed_airspeed).ncd && mk_ainput(computed_airspeed).get() > 60
3021           && ! mk_data(radio_altitude).ncd && mk_data(radio_altitude).get() > 30)
3022         {
3023           if (potentially_airborne_timer.start_or_elapsed() > 1)
3024             leave_ground();
3025         }
3026       else
3027         potentially_airborne_timer.stop();
3028     }
3029   else
3030     {
3031       if (! mk_ainput(computed_airspeed).ncd && mk_ainput(computed_airspeed).get() < 40
3032           && ! mk_data(radio_altitude).ncd && mk_data(radio_altitude).get() < 30)
3033         enter_ground();
3034     }
3035 }
3036
3037 void
3038 MK_VIII::StateHandler::enter_ground ()
3039 {
3040   ground = true;
3041   mk->io_handler.enter_ground();
3042
3043   // [SPEC] 6.0.1 does not specify this, but it seems to be an
3044   // omission; at this point, we must make sure that we are in takeoff
3045   // mode (otherwise the next takeoff might happen in approach mode).
3046   if (! takeoff)
3047     enter_takeoff();
3048 }
3049
3050 void
3051 MK_VIII::StateHandler::leave_ground ()
3052 {
3053   ground = false;
3054   mk->mode2_handler.leave_ground();
3055 }
3056
3057 void
3058 MK_VIII::StateHandler::update_takeoff ()
3059 {
3060   if (takeoff)
3061     {
3062       // [SPEC] 6.0.2 is somewhat confusing: it mentions hardcoded
3063       // terrain clearance (500, 750) and airspeed (178, 200) values,
3064       // but it also mentions the mode 4A boundary, which varies as a
3065       // function of aircraft type. We consider that the mentioned
3066       // values are examples, and that we should use the mode 4A upper
3067       // boundary.
3068
3069       if (! mk_data(terrain_clearance).ncd
3070           && ! mk_ainput(computed_airspeed).ncd
3071           && mk_data(terrain_clearance).get() > mk->mode4_handler.get_upper_agl(mk->mode4_handler.conf.modes->ac))
3072         leave_takeoff();
3073     }
3074   else
3075     {
3076       if (! mk_data(radio_altitude).ncd
3077           && mk_data(radio_altitude).get() < mk->mode4_handler.conf.modes->b->min_agl1
3078           && mk->io_handler.flaps_down()
3079           && mk_dinput(landing_gear))
3080         enter_takeoff();
3081     }
3082 }
3083
3084 void
3085 MK_VIII::StateHandler::enter_takeoff ()
3086 {
3087   takeoff = true;
3088   mk->io_handler.enter_takeoff();
3089   mk->mode2_handler.enter_takeoff();
3090   mk->mode3_handler.enter_takeoff();
3091   mk->mode6_handler.enter_takeoff();
3092 }
3093
3094 void
3095 MK_VIII::StateHandler::leave_takeoff ()
3096 {
3097   takeoff = false;
3098   mk->mode6_handler.leave_takeoff();
3099 }
3100
3101 void
3102 MK_VIII::StateHandler::post_reposition ()
3103 {
3104   // Synchronize the state with the current situation.
3105
3106   bool _ground = !
3107     (! mk_ainput(computed_airspeed).ncd && mk_ainput(computed_airspeed).get() > 60
3108      && ! mk_data(radio_altitude).ncd && mk_data(radio_altitude).get() > 30);
3109
3110   bool _takeoff = _ground;
3111
3112   if (ground && ! _ground)
3113     leave_ground();
3114   else if (! ground && _ground)
3115     enter_ground();
3116
3117   if (takeoff && ! _takeoff)
3118     leave_takeoff();
3119   else if (! takeoff && _takeoff)
3120     enter_takeoff();
3121 }
3122
3123 void
3124 MK_VIII::StateHandler::update ()
3125 {
3126   if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
3127     return;
3128
3129   update_ground();
3130   update_takeoff();
3131 }
3132
3133 ///////////////////////////////////////////////////////////////////////////////
3134 // Mode1Handler ///////////////////////////////////////////////////////////////
3135 ///////////////////////////////////////////////////////////////////////////////
3136
3137 double
3138 MK_VIII::Mode1Handler::get_pull_up_bias ()
3139 {
3140   // [PILOT] page 54: "Steep Approach has priority over Flap Override
3141   // if selected simultaneously."
3142
3143   if (mk->io_handler.steep_approach())
3144     return 200;
3145   else if (conf.envelopes->flap_override_bias && mk->io_handler.flap_override())
3146     return 300;
3147   else
3148     return 0;
3149 }
3150
3151 bool
3152 MK_VIII::Mode1Handler::is_pull_up ()
3153 {
3154   if (! mk->io_handler.gpws_inhibit()
3155       && ! mk->state_handler.ground // [1]
3156       && ! mk_data(radio_altitude).ncd
3157       && ! mk_data(barometric_altitude_rate).ncd
3158       && mk_data(radio_altitude).get() > conf.envelopes->min_agl)
3159     {
3160       if (mk_data(radio_altitude).get() < conf.envelopes->pull_up_max_agl1)
3161         {
3162           if (mk_data(radio_altitude).get() < conf.envelopes->pull_up_min_agl1(mk_data(barometric_altitude_rate).get() + get_pull_up_bias()))
3163             return true;
3164         }
3165       else if (mk_data(radio_altitude).get() < conf.envelopes->pull_up_max_agl2)
3166         {
3167           if (mk_data(radio_altitude).get() < conf.envelopes->pull_up_min_agl2(mk_data(barometric_altitude_rate).get() + get_pull_up_bias()))
3168             return true;
3169         }
3170     }
3171
3172   return false;
3173 }
3174
3175 void
3176 MK_VIII::Mode1Handler::update_pull_up ()
3177 {
3178   if (is_pull_up())
3179     {
3180       if (! mk_test_alert(MODE1_PULL_UP))
3181         {
3182           // [SPEC] 6.2.1: at least one sink rate must be issued
3183           // before switching to pull up; if no sink rate has
3184           // occurred, a 0.2 second delay is used.
3185           if (mk->voice_player.voice == mk_voice(sink_rate_pause_sink_rate)
3186               || pull_up_timer.start_or_elapsed() >= 0.2)
3187             mk_set_alerts(mk_alert(MODE1_PULL_UP));
3188         }
3189     }
3190   else
3191     {
3192       pull_up_timer.stop();
3193       mk_unset_alerts(mk_alert(MODE1_PULL_UP));
3194     }
3195 }
3196
3197 double
3198 MK_VIII::Mode1Handler::get_sink_rate_bias ()
3199 {
3200   // [PILOT] page 54: "Steep Approach has priority over Flap Override
3201   // if selected simultaneously."
3202
3203   if (mk->io_handler.steep_approach())
3204     return 500;
3205   else if (conf.envelopes->flap_override_bias && mk->io_handler.flap_override())
3206     return 300;
3207   else if (! mk_data(glideslope_deviation_dots).ncd)
3208     {
3209       double bias = 0;
3210
3211       if (mk_data(glideslope_deviation_dots).get() <= -2)
3212         bias = 300;
3213       else if (mk_data(glideslope_deviation_dots).get() < 0)
3214         bias = -150 * mk_data(glideslope_deviation_dots).get();
3215
3216       if (mk_data(radio_altitude).get() < 100)
3217         bias *= 0.01 * mk_data(radio_altitude).get();
3218
3219       return bias;
3220     }
3221   else
3222     return 0;
3223 }
3224
3225 bool
3226 MK_VIII::Mode1Handler::is_sink_rate ()
3227 {
3228   return ! mk->io_handler.gpws_inhibit()
3229     && ! mk->state_handler.ground // [1]
3230     && ! mk_data(radio_altitude).ncd
3231     && ! mk_data(barometric_altitude_rate).ncd
3232     && mk_data(radio_altitude).get() > conf.envelopes->min_agl
3233     && mk_data(radio_altitude).get() < 2450
3234     && mk_data(radio_altitude).get() < -572 - 0.6035 * (mk_data(barometric_altitude_rate).get() + get_sink_rate_bias());
3235 }
3236
3237 double
3238 MK_VIII::Mode1Handler::get_sink_rate_tti ()
3239 {
3240   return mk_data(radio_altitude).get() / fabs(mk_data(barometric_altitude_rate).get());
3241 }
3242
3243 void
3244 MK_VIII::Mode1Handler::update_sink_rate ()
3245 {
3246   if (is_sink_rate())
3247     {
3248       if (mk_test_alert(MODE1_SINK_RATE))
3249         {
3250           double tti = get_sink_rate_tti();
3251           if (tti <= sink_rate_tti * 0.8)
3252             {
3253               // ~20% degradation, warn again and store the new reference tti
3254               sink_rate_tti = tti;
3255               mk_repeat_alert(mk_alert(MODE1_SINK_RATE));
3256             }
3257         }
3258       else
3259         {
3260           if (sink_rate_timer.start_or_elapsed() >= 0.8)
3261             {
3262               mk_set_alerts(mk_alert(MODE1_SINK_RATE));
3263               sink_rate_tti = get_sink_rate_tti();
3264             }
3265         }
3266     }
3267   else
3268     {
3269       sink_rate_timer.stop();
3270       mk_unset_alerts(mk_alert(MODE1_SINK_RATE));
3271     }
3272 }
3273
3274 void
3275 MK_VIII::Mode1Handler::update ()
3276 {
3277   if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
3278     return;
3279
3280   update_pull_up();
3281   update_sink_rate();
3282 }
3283
3284 ///////////////////////////////////////////////////////////////////////////////
3285 // Mode2Handler ///////////////////////////////////////////////////////////////
3286 ///////////////////////////////////////////////////////////////////////////////
3287
3288 double
3289 MK_VIII::Mode2Handler::ClosureRateFilter::limit_radio_altitude_rate (double r)
3290 {
3291   // Limit radio altitude rate according to aircraft configuration,
3292   // allowing maximum sensitivity during cruise while providing
3293   // progressively less sensitivity during the landing phases of
3294   // flight.
3295
3296   if (! mk_data(glideslope_deviation_dots).ncd && fabs(mk_data(glideslope_deviation_dots).get()) <= 2)
3297     {                           // gs deviation <= +- 2 dots
3298       if (mk_dinput(landing_gear) && mk->io_handler.flaps_down())
3299         SG_CLAMP_RANGE(r, -1000.0, 3000.0);
3300       else if (mk_dinput(landing_gear) || mk->io_handler.flaps_down())
3301         SG_CLAMP_RANGE(r, 0.0, 4000.0);
3302       else
3303         SG_CLAMP_RANGE(r, 1000.0, 5000.0);
3304     }
3305   else
3306     {                           // no ILS, or gs deviation > +- 2 dots
3307       if (mk_dinput(landing_gear) && mk->io_handler.flaps_down())
3308         SG_CLAMP_RANGE(r, 0.0, 4000.0);
3309       else if (mk_dinput(landing_gear) || mk->io_handler.flaps_down())
3310         SG_CLAMP_RANGE(r, 1000.0, 5000.0);
3311       // else no clamp
3312     }
3313
3314   return r;
3315 }
3316
3317 void
3318 MK_VIII::Mode2Handler::ClosureRateFilter::init ()
3319 {
3320   timer.stop();
3321   last_ra.set(&mk_data(radio_altitude));
3322   last_ba.set(&mk_ainput(uncorrected_barometric_altitude));
3323   ra_filter.reset();
3324   ba_filter.reset();
3325   output.unset();
3326 }
3327
3328 void
3329 MK_VIII::Mode2Handler::ClosureRateFilter::update ()
3330 {
3331   double elapsed = timer.start_or_elapsed();
3332   if (elapsed < 1)
3333     return;
3334
3335   if (! mk_data(radio_altitude).ncd && ! mk_ainput(uncorrected_barometric_altitude).ncd)
3336     {
3337       if (! last_ra.ncd && ! last_ba.ncd)
3338         {
3339           // compute radio and barometric altitude rates (positive value = descent)
3340           double ra_rate = -(mk_data(radio_altitude).get() - last_ra.get()) / elapsed * 60;
3341           double ba_rate = -(mk_ainput(uncorrected_barometric_altitude).get() - last_ba.get()) / elapsed * 60;
3342
3343           // limit radio altitude rate according to aircraft configuration
3344           ra_rate = limit_radio_altitude_rate(ra_rate);
3345
3346           // apply a low-pass filter to the radio altitude rate
3347           ra_rate = ra_filter.filter(ra_rate);
3348           // apply a high-pass filter to the barometric altitude rate
3349           ba_rate = ba_filter.filter(ba_rate);
3350
3351           // combine both rates to obtain a closure rate
3352           output.set(ra_rate + ba_rate);
3353         }
3354       else
3355         output.unset();
3356     }
3357   else
3358     {
3359       ra_filter.reset();
3360       ba_filter.reset();
3361       output.unset();
3362     }
3363
3364   timer.start();
3365   last_ra.set(&mk_data(radio_altitude));
3366   last_ba.set(&mk_ainput(uncorrected_barometric_altitude));
3367 }
3368
3369 bool
3370 MK_VIII::Mode2Handler::b_conditions ()
3371 {
3372   return mk->io_handler.flaps_down()
3373     || (! mk_data(glideslope_deviation_dots).ncd && fabs(mk_data(glideslope_deviation_dots).get()) < 2)
3374     || takeoff_timer.running;
3375 }
3376
3377 bool
3378 MK_VIII::Mode2Handler::is_a ()
3379 {
3380   if (! mk->io_handler.gpws_inhibit()
3381       && ! mk->state_handler.ground // [1]
3382       && ! mk_data(radio_altitude).ncd
3383       && ! mk_ainput(computed_airspeed).ncd
3384       && ! closure_rate_filter.output.ncd
3385       && ! b_conditions())
3386     {
3387       if (mk_data(radio_altitude).get() < 1220)
3388         {
3389           if (mk_data(radio_altitude).get() < -1579 + 0.7895 * closure_rate_filter.output.get())
3390             return true;
3391         }
3392       else
3393         {
3394           double upper_limit;
3395
3396           if (mk_ainput(computed_airspeed).get() <= conf->airspeed1)
3397             upper_limit = 1650;
3398           else if (mk_ainput(computed_airspeed).get() >= conf->airspeed2)
3399             upper_limit = 2450;
3400           else
3401             upper_limit = 1650 + 8.9 * (mk_ainput(computed_airspeed).get() - conf->airspeed1);
3402
3403           if (mk_data(radio_altitude).get() < upper_limit)
3404             {
3405               if (mk_data(radio_altitude).get() < 522 + 0.1968 * closure_rate_filter.output.get())
3406                 return true;
3407             }
3408         }
3409     }
3410
3411   return false;
3412 }
3413
3414 bool
3415 MK_VIII::Mode2Handler::is_b ()
3416 {
3417   if (! mk->io_handler.gpws_inhibit()
3418       && ! mk->state_handler.ground // [1]
3419       && ! mk_data(radio_altitude).ncd
3420       && ! mk_data(barometric_altitude_rate).ncd
3421       && ! closure_rate_filter.output.ncd
3422       && b_conditions()
3423       && mk_data(radio_altitude).get() < 789)
3424     {
3425       double lower_limit;
3426
3427       if (mk->io_handler.flaps_down())
3428         {
3429           if (mk_data(barometric_altitude_rate).get() > -400)
3430             lower_limit = 200;
3431           else if (mk_data(barometric_altitude_rate).get() < -1000)
3432             lower_limit = 600;
3433           else
3434             lower_limit = -66.777 - 0.667 * mk_data(barometric_altitude_rate).get();
3435         }
3436       else
3437         lower_limit = 30;
3438
3439       if (mk_data(radio_altitude).get() > lower_limit)
3440         {
3441           if (mk_data(radio_altitude).get() < -1579 + 0.7895 * closure_rate_filter.output.get())
3442             return true;
3443         }
3444     }
3445
3446   return false;
3447 }
3448
3449 void
3450 MK_VIII::Mode2Handler::check_pull_up (unsigned int preface_alert,
3451                                       unsigned int alert)
3452 {
3453   if (pull_up_timer.running)
3454     {
3455       if (pull_up_timer.elapsed() >= 1)
3456         {
3457           mk_unset_alerts(preface_alert);
3458           mk_set_alerts(alert);
3459         }
3460     }
3461   else
3462     {
3463       if (! mk->voice_player.voice)
3464         pull_up_timer.start();
3465     }
3466 }
3467
3468 void
3469 MK_VIII::Mode2Handler::update_a ()
3470 {
3471   if (is_a())
3472     {
3473       if (mk_test_alert(MODE2A_PREFACE))
3474         check_pull_up(mk_alert(MODE2A_PREFACE), mk_alert(MODE2A));
3475       else if (! mk_test_alert(MODE2A))
3476         {
3477           mk_unset_alerts(mk_alert(MODE2A_ALTITUDE_GAIN) | mk_alert(MODE2A_ALTITUDE_GAIN_TERRAIN_CLOSING));
3478           mk_set_alerts(mk_alert(MODE2A_PREFACE));
3479           a_start_time = globals->get_sim_time_sec();
3480           pull_up_timer.stop();
3481         }
3482     }
3483   else
3484     {
3485       if (mk_test_alert(MODE2A_ALTITUDE_GAIN))
3486         {
3487           if (mk->io_handler.gpws_inhibit()
3488               || mk->state_handler.ground // [1]
3489               || a_altitude_gain_timer.elapsed() >= 45
3490               || mk_data(radio_altitude).ncd
3491               || mk_ainput(uncorrected_barometric_altitude).ncd
3492               || mk_ainput(uncorrected_barometric_altitude).get() - a_altitude_gain_alt >= 300
3493               // [PILOT] page 12: "the visual alert will remain on
3494               // until [...] landing flaps or the flap override switch
3495               // is activated"
3496               || mk->io_handler.flaps_down())
3497             {
3498               // exit altitude gain mode
3499               a_altitude_gain_timer.stop();
3500               mk_unset_alerts(mk_alert(MODE2A_ALTITUDE_GAIN) | mk_alert(MODE2A_ALTITUDE_GAIN_TERRAIN_CLOSING));
3501             }
3502           else
3503             {
3504               // [SPEC] 6.2.2.2: "If the terrain starts to fall away
3505               // during this altitude gain time, the voice will shut
3506               // off"
3507               if (closure_rate_filter.output.get() < 0)
3508                 mk_unset_alerts(mk_alert(MODE2A_ALTITUDE_GAIN_TERRAIN_CLOSING));
3509             }
3510         }
3511       else if (mk_test_alerts(mk_alert(MODE2A_PREFACE) | mk_alert(MODE2A)))
3512         {
3513           mk_unset_alerts(mk_alert(MODE2A_PREFACE) | mk_alert(MODE2A));
3514
3515           if (! mk->io_handler.gpws_inhibit()
3516               && ! mk->state_handler.ground // [1]
3517               && globals->get_sim_time_sec() - a_start_time > 3
3518               && ! mk->io_handler.flaps_down()
3519               && ! mk_data(radio_altitude).ncd
3520               && ! mk_ainput(uncorrected_barometric_altitude).ncd)
3521             {
3522               // [SPEC] 6.2.2.2: mode 2A envelope violated for more
3523               // than 3 seconds, enable altitude gain feature
3524
3525               a_altitude_gain_timer.start();
3526               a_altitude_gain_alt = mk_ainput(uncorrected_barometric_altitude).get();
3527
3528               unsigned int alerts = mk_alert(MODE2A_ALTITUDE_GAIN);
3529               if (closure_rate_filter.output.get() > 0)
3530                 alerts |= mk_alert(MODE2A_ALTITUDE_GAIN_TERRAIN_CLOSING);
3531
3532               mk_set_alerts(alerts);
3533             }
3534         }
3535     }
3536 }
3537
3538 void
3539 MK_VIII::Mode2Handler::update_b ()
3540 {
3541   bool b = is_b();
3542
3543   // handle normal mode
3544
3545   if (b && (! mk_dinput(landing_gear) || ! mk->io_handler.flaps_down()))
3546     {
3547       if (mk_test_alert(MODE2B_PREFACE))
3548         check_pull_up(mk_alert(MODE2B_PREFACE), mk_alert(MODE2B));
3549       else if (! mk_test_alert(MODE2B))
3550         {
3551           mk_set_alerts(mk_alert(MODE2B_PREFACE));
3552           pull_up_timer.stop();
3553         }
3554     }
3555   else
3556     mk_unset_alerts(mk_alert(MODE2B_PREFACE) | mk_alert(MODE2B));
3557
3558   // handle landing mode ([SPEC] 6.2.2.3: "If both landing gear and
3559   // flaps are in the landing configuration, then the message will be
3560   // Terrain")
3561
3562   if (b && mk_dinput(landing_gear) && mk->io_handler.flaps_down())
3563     mk_set_alerts(mk_alert(MODE2B_LANDING_MODE));
3564   else
3565     mk_unset_alerts(mk_alert(MODE2B_LANDING_MODE));
3566 }
3567
3568 void
3569 MK_VIII::Mode2Handler::boot ()
3570 {
3571   closure_rate_filter.init();
3572 }
3573
3574 void
3575 MK_VIII::Mode2Handler::power_off ()
3576 {
3577   // [SPEC] 6.0.4: "This latching function is not power saved and a
3578   // system reset will force it false."
3579   takeoff_timer.stop();
3580 }
3581
3582 void
3583 MK_VIII::Mode2Handler::leave_ground ()
3584 {
3585   // takeoff, reset timer
3586   takeoff_timer.start();
3587 }
3588
3589 void
3590 MK_VIII::Mode2Handler::enter_takeoff ()
3591 {
3592   // reset timer, in case it's a go-around
3593   takeoff_timer.start();
3594 }
3595
3596 void
3597 MK_VIII::Mode2Handler::update ()
3598 {
3599   if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
3600     return;
3601
3602   closure_rate_filter.update();
3603
3604   if (takeoff_timer.running && takeoff_timer.elapsed() >= 60)
3605     takeoff_timer.stop();
3606
3607   update_a();
3608   update_b();
3609 }
3610
3611 ///////////////////////////////////////////////////////////////////////////////
3612 // Mode3Handler ///////////////////////////////////////////////////////////////
3613 ///////////////////////////////////////////////////////////////////////////////
3614
3615 double
3616 MK_VIII::Mode3Handler::max_alt_loss (double _bias)
3617 {
3618   return conf->max_alt_loss(mk->io_handler.flap_override(), mk_data(radio_altitude).get()) + mk_data(radio_altitude).get() * _bias;
3619 }
3620
3621 double
3622 MK_VIII::Mode3Handler::get_bias (double initial_bias, double alt_loss)
3623 {
3624   if (mk_data(radio_altitude).get() > 0)
3625     while (alt_loss > max_alt_loss(initial_bias))
3626       initial_bias += 0.2;
3627
3628   return initial_bias;
3629 }
3630
3631 bool
3632 MK_VIII::Mode3Handler::is (double *alt_loss)
3633 {
3634   if (has_descent_alt)
3635     {
3636       int max_agl = conf->max_agl(mk->io_handler.flap_override());
3637
3638       if (mk_ainput(uncorrected_barometric_altitude).ncd
3639           || mk_ainput(uncorrected_barometric_altitude).get() > descent_alt
3640           || mk_data(radio_altitude).ncd
3641           || mk_data(radio_altitude).get() > max_agl)
3642         {
3643           armed = false;
3644           has_descent_alt = false;
3645         }
3646       else
3647         {
3648           // gear/flaps: [SPEC] 1.3.1.3
3649           if (! mk->io_handler.gpws_inhibit()
3650               && ! mk->state_handler.ground // [1]
3651               && (! mk_dinput(landing_gear) || ! mk->io_handler.flaps_down())
3652               && ! mk_data(barometric_altitude_rate).ncd
3653               && ! mk_ainput(uncorrected_barometric_altitude).ncd
3654               && ! mk_data(radio_altitude).ncd
3655               && mk_data(barometric_altitude_rate).get() < 0)
3656             {
3657               double _alt_loss = descent_alt - mk_ainput(uncorrected_barometric_altitude).get();
3658               if (armed || (mk_data(radio_altitude).get() > conf->min_agl
3659                             && mk_data(radio_altitude).get() < max_agl
3660                             && _alt_loss > max_alt_loss(0)))
3661                 {
3662                   *alt_loss = _alt_loss;
3663                   return true;
3664                 }
3665             }
3666         }
3667     }
3668   else
3669     {
3670       if (! mk_data(barometric_altitude_rate).ncd
3671           && ! mk_ainput(uncorrected_barometric_altitude).ncd
3672           && mk_data(barometric_altitude_rate).get() < 0)
3673         {
3674           has_descent_alt = true;
3675           descent_alt = mk_ainput(uncorrected_barometric_altitude).get();
3676         }
3677     }
3678
3679   return false;
3680 }
3681
3682 void
3683 MK_VIII::Mode3Handler::enter_takeoff ()
3684 {
3685   armed = false;
3686   has_descent_alt = false;
3687 }
3688
3689 void
3690 MK_VIII::Mode3Handler::update ()
3691 {
3692   if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
3693     return;
3694
3695   if (mk->state_handler.takeoff)
3696     {
3697       double alt_loss;
3698
3699       if (! mk->state_handler.ground /* [1] */ && is(&alt_loss))
3700         {
3701           if (mk_test_alert(MODE3))
3702             {
3703               double new_bias = get_bias(bias, alt_loss);
3704               if (new_bias > bias)
3705                 {
3706                   bias = new_bias;
3707                   mk_repeat_alert(mk_alert(MODE3));
3708                 }
3709             }
3710           else
3711             {
3712               armed = true;
3713               bias = get_bias(0.2, alt_loss);
3714               mk_set_alerts(mk_alert(MODE3));
3715             }
3716
3717           return;
3718         }
3719     }
3720
3721   mk_unset_alerts(mk_alert(MODE3));
3722 }
3723
3724 ///////////////////////////////////////////////////////////////////////////////
3725 // Mode4Handler ///////////////////////////////////////////////////////////////
3726 ///////////////////////////////////////////////////////////////////////////////
3727
3728 // FIXME: for turbofans, the upper limit should be reduced from 1000
3729 // to 800 ft if a sudden change in radio altitude is detected, in
3730 // order to reduce nuisance alerts caused by overflying another
3731 // aircraft (see [PILOT] page 16).
3732
3733 double
3734 MK_VIII::Mode4Handler::get_upper_agl (const EnvelopesConfiguration *c)
3735 {
3736   if (mk_ainput(computed_airspeed).get() >= c->airspeed2)
3737     return c->min_agl3;
3738   else if (mk_ainput(computed_airspeed).get() >= c->airspeed1)
3739     return c->min_agl2(mk_ainput(computed_airspeed).get());
3740   else
3741     return c->min_agl1;
3742 }
3743
3744 const MK_VIII::Mode4Handler::EnvelopesConfiguration *
3745 MK_VIII::Mode4Handler::get_ab_envelope ()
3746 {
3747   // [SPEC] 6.2.4.1: "The Mode 4B envelope can also be selected by
3748   // setting flaps to landing configuration or by selecting flap
3749   // override."
3750   return mk_dinput(landing_gear) || mk->io_handler.flaps_down()
3751     ? conf.modes->b
3752     : conf.modes->ac;
3753 }
3754
3755 double
3756 MK_VIII::Mode4Handler::get_bias (double initial_bias, double min_agl)
3757 {
3758   while (mk_data(radio_altitude).get() < min_agl - min_agl * initial_bias)
3759     initial_bias += 0.2;
3760
3761   return initial_bias;
3762 }
3763
3764 void
3765 MK_VIII::Mode4Handler::handle_alert (unsigned int alert,
3766                                      double min_agl,
3767                                      double *bias)
3768 {
3769   if (mk_test_alerts(alert))
3770     {
3771       double new_bias = get_bias(*bias, min_agl);
3772       if (new_bias > *bias)
3773         {
3774           *bias = new_bias;
3775           mk_repeat_alert(alert);
3776         }
3777     }
3778   else
3779     {
3780       *bias = get_bias(0.2, min_agl);
3781       mk_set_alerts(alert);
3782     }
3783 }
3784
3785 void
3786 MK_VIII::Mode4Handler::update_ab ()
3787 {
3788   if (! mk->io_handler.gpws_inhibit()
3789       && ! mk->state_handler.ground
3790       && ! mk->state_handler.takeoff
3791       && ! mk_data(radio_altitude).ncd
3792       && ! mk_ainput(computed_airspeed).ncd
3793       && mk_data(radio_altitude).get() > 30)
3794     {
3795       const EnvelopesConfiguration *c = get_ab_envelope();
3796       if (mk_ainput(computed_airspeed).get() < c->airspeed1)
3797         {
3798           if (mk_dinput(landing_gear))
3799             {
3800               if (! mk->io_handler.flaps_down() && mk_data(radio_altitude).get() < c->min_agl1)
3801                 {
3802                   handle_alert(mk_alert(MODE4_TOO_LOW_FLAPS), c->min_agl1, &ab_bias);
3803                   return;
3804                 }
3805             }
3806           else
3807             {
3808               if (mk_data(radio_altitude).get() < c->min_agl1)
3809                 {
3810                   handle_alert(mk_alert(MODE4_TOO_LOW_GEAR), c->min_agl1, &ab_bias);
3811                   return;
3812                 }
3813             }
3814         }
3815     }
3816
3817   mk_unset_alerts(mk_alert(MODE4_TOO_LOW_FLAPS) | mk_alert(MODE4_TOO_LOW_GEAR));
3818 }
3819
3820 void
3821 MK_VIII::Mode4Handler::update_ab_expanded ()
3822 {
3823   if (! mk->io_handler.gpws_inhibit()
3824       && ! mk->state_handler.ground
3825       && ! mk->state_handler.takeoff
3826       && ! mk_data(radio_altitude).ncd
3827       && ! mk_ainput(computed_airspeed).ncd
3828       && mk_data(radio_altitude).get() > 30)
3829     {
3830       const EnvelopesConfiguration *c = get_ab_envelope();
3831       if (mk_ainput(computed_airspeed).get() >= c->airspeed1)
3832         {
3833           double min_agl = get_upper_agl(c);
3834           if (mk_data(radio_altitude).get() < min_agl)
3835             {
3836               handle_alert(mk_alert(MODE4AB_TOO_LOW_TERRAIN), min_agl, &ab_expanded_bias);
3837               return;
3838             }
3839         }
3840     }
3841
3842   mk_unset_alerts(mk_alert(MODE4AB_TOO_LOW_TERRAIN));
3843 }
3844
3845 void
3846 MK_VIII::Mode4Handler::update_c ()
3847 {
3848   if (! mk->io_handler.gpws_inhibit()
3849       && ! mk->state_handler.ground // [1]
3850       && mk->state_handler.takeoff
3851       && ! mk_data(radio_altitude).ncd
3852       && ! mk_data(terrain_clearance).ncd
3853       && mk_data(radio_altitude).get() > 30
3854       && (! mk_dinput(landing_gear) || ! mk->io_handler.flaps_down())
3855       && mk_data(radio_altitude).get() < get_upper_agl(conf.modes->ac)
3856       && mk_data(radio_altitude).get() < mk_data(terrain_clearance).get())
3857     handle_alert(mk_alert(MODE4C_TOO_LOW_TERRAIN), mk_data(terrain_clearance).get(), &c_bias);
3858   else
3859     mk_unset_alerts(mk_alert(MODE4C_TOO_LOW_TERRAIN));
3860 }
3861
3862 void
3863 MK_VIII::Mode4Handler::update ()
3864 {
3865   if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
3866     return;
3867
3868   update_ab();
3869   update_ab_expanded();
3870   update_c();
3871 }
3872
3873 ///////////////////////////////////////////////////////////////////////////////
3874 // Mode5Handler ///////////////////////////////////////////////////////////////
3875 ///////////////////////////////////////////////////////////////////////////////
3876
3877 bool
3878 MK_VIII::Mode5Handler::is_hard ()
3879 {
3880   if (mk_data(radio_altitude).get() > 30
3881       && mk_data(radio_altitude).get() < 300
3882       && mk_data(glideslope_deviation_dots).get() > 2)
3883     {
3884       if (mk_data(radio_altitude).get() < 150)
3885         {
3886           if (mk_data(radio_altitude).get() > 293 - 71.43 * mk_data(glideslope_deviation_dots).get())
3887             return true;
3888         }
3889       else
3890         return true;
3891     }
3892
3893   return false;
3894 }
3895
3896 bool
3897 MK_VIII::Mode5Handler::is_soft (double bias)
3898 {
3899   if (mk_data(radio_altitude).get() > 30)
3900     {
3901       double bias_dots = 1.3 * bias;
3902       if (mk_data(glideslope_deviation_dots).get() > 1.3 + bias_dots)
3903         {
3904           if (mk_data(radio_altitude).get() < 150)
3905             {
3906               if (mk_data(radio_altitude).get() > 243 - 71.43 * (mk_data(glideslope_deviation_dots).get() - bias_dots))
3907                 return true;
3908             }
3909           else
3910             {
3911               double upper_limit;
3912
3913               if (mk_data(barometric_altitude_rate).ncd)
3914                 upper_limit = 1000;
3915               else
3916                 {
3917                   if (mk_data(barometric_altitude_rate).get() >= 0)
3918                     upper_limit = 500;
3919                   else if (mk_data(barometric_altitude_rate).get() < -500)
3920                     upper_limit = 1000;
3921                   else
3922                     upper_limit = -mk_data(barometric_altitude_rate).get() + 500;
3923                 }
3924
3925               if (mk_data(radio_altitude).get() < upper_limit)
3926                 return true;
3927             }
3928         }
3929     }
3930
3931   return false;
3932 }
3933
3934 double
3935 MK_VIII::Mode5Handler::get_soft_bias (double initial_bias)
3936 {
3937   while (is_soft(initial_bias))
3938     initial_bias += 0.2;
3939
3940   return initial_bias;
3941 }
3942
3943 void
3944 MK_VIII::Mode5Handler::update_hard (bool is)
3945 {
3946   if (is)
3947     {
3948       if (! mk_test_alert(MODE5_HARD))
3949         {
3950           if (hard_timer.start_or_elapsed() >= 0.8)
3951             mk_set_alerts(mk_alert(MODE5_HARD));
3952         }
3953     }
3954   else
3955     {
3956       hard_timer.stop();
3957       mk_unset_alerts(mk_alert(MODE5_HARD));
3958     }
3959 }
3960
3961 void
3962 MK_VIII::Mode5Handler::update_soft (bool is)
3963 {
3964   if (is)
3965     {
3966       if (! mk_test_alert(MODE5_SOFT))
3967         {
3968           double new_bias = get_soft_bias(soft_bias);
3969           if (new_bias > soft_bias)
3970             {
3971               soft_bias = new_bias;
3972               mk_repeat_alert(mk_alert(MODE5_SOFT));
3973             }
3974         }
3975       else
3976         {
3977           if (soft_timer.start_or_elapsed() >= 0.8)
3978             {
3979               soft_bias = get_soft_bias(0.2);
3980               mk_set_alerts(mk_alert(MODE5_SOFT));
3981             }
3982         }
3983     }
3984   else
3985     {
3986       soft_timer.stop();
3987       mk_unset_alerts(mk_alert(MODE5_SOFT));
3988     }
3989 }
3990
3991 void
3992 MK_VIII::Mode5Handler::update ()
3993 {
3994   if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
3995     return;
3996
3997   if (! mk->io_handler.gpws_inhibit()
3998       && ! mk->state_handler.ground // [1]
3999       && ! mk_dinput(glideslope_inhibit) // not on backcourse
4000       && ! mk_data(radio_altitude).ncd
4001       && ! mk_data(glideslope_deviation_dots).ncd
4002       && (! mk->io_handler.conf.localizer_enabled
4003           || mk_data(localizer_deviation_dots).ncd
4004           || mk_data(radio_altitude).get() < 500
4005           || fabs(mk_data(localizer_deviation_dots).get()) < 2)
4006       && (! mk->state_handler.takeoff || mk->io_handler.flaps_down())
4007       && mk_dinput(landing_gear)
4008       && ! mk_doutput(glideslope_cancel))
4009     {
4010       update_hard(is_hard());
4011       update_soft(is_soft(0));
4012     }
4013   else
4014     {
4015       update_hard(false);
4016       update_soft(false);
4017     }
4018 }
4019
4020 ///////////////////////////////////////////////////////////////////////////////
4021 // Mode6Handler ///////////////////////////////////////////////////////////////
4022 ///////////////////////////////////////////////////////////////////////////////
4023
4024 // keep sorted in descending order
4025 const int MK_VIII::Mode6Handler::altitude_callout_definitions[] = 
4026   { 1000, 500, 400, 300, 200, 100, 50, 40, 30, 20, 10 };
4027
4028 void
4029 MK_VIII::Mode6Handler::reset_minimums ()
4030 {
4031   minimums_issued = false;
4032 }
4033
4034 void
4035 MK_VIII::Mode6Handler::reset_altitude_callouts ()
4036 {
4037   for (int i = 0; i < n_altitude_callouts; i++)
4038     altitude_callouts_issued[i] = false;
4039 }
4040
4041 bool
4042 MK_VIII::Mode6Handler::is_playing_altitude_callout ()
4043 {
4044   for (int i = 0; i < n_altitude_callouts; i++)
4045     if (mk->voice_player.voice == mk_altitude_voice(i)
4046         || mk->voice_player.next_voice == mk_altitude_voice(i))
4047       return true;
4048
4049   return false;
4050 }
4051
4052 bool
4053 MK_VIII::Mode6Handler::is_near_minimums (double callout)
4054 {
4055   // [SPEC] 6.4.2
4056
4057   if (! mk_data(decision_height).ncd)
4058     {
4059       double diff = callout - mk_data(decision_height).get();
4060
4061       if (mk_data(radio_altitude).get() >= 200)
4062         {
4063           if (fabs(diff) <= 30)
4064             return true;
4065         }
4066       else
4067         {
4068           if (diff >= -3 && diff <= 6)
4069             return true;
4070         }
4071     }
4072
4073   return false;
4074 }
4075
4076 bool
4077 MK_VIII::Mode6Handler::is_outside_band (double elevation, double callout)
4078 {
4079   // [SPEC] 6.4.2
4080   return elevation < callout - (elevation > 150 ? 20 : 10);
4081 }
4082
4083 bool
4084 MK_VIII::Mode6Handler::inhibit_smart_500 ()
4085 {
4086   // [SPEC] 6.4.3
4087
4088   if (! mk_data(glideslope_deviation_dots).ncd
4089       && ! mk_dinput(glideslope_inhibit) // backcourse
4090       && ! mk_doutput(glideslope_cancel))
4091     {
4092       if (mk->io_handler.conf.localizer_enabled
4093           && ! mk_data(localizer_deviation_dots).ncd)
4094         {
4095           if (fabs(mk_data(localizer_deviation_dots).get()) <= 2)
4096             return true;
4097         }
4098       else
4099         {
4100           if (fabs(mk_data(glideslope_deviation_dots).get()) <= 2)
4101             return true;
4102         }
4103     }
4104
4105   return false;
4106 }
4107
4108 void
4109 MK_VIII::Mode6Handler::boot ()
4110 {
4111   if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
4112     return;
4113
4114   last_decision_height = mk_dinput(decision_height);
4115   last_radio_altitude.set(&mk_data(radio_altitude));
4116
4117   // [SPEC] 6.4.2
4118   for (int i = 0; i < n_altitude_callouts; i++)
4119     altitude_callouts_issued[i] = ! mk_data(radio_altitude).ncd
4120       && mk_data(radio_altitude).get() <= altitude_callout_definitions[i];
4121
4122   // extrapolated from [SPEC] 6.4.2
4123   minimums_issued = mk_dinput(decision_height);
4124
4125   if (conf.above_field_voice)
4126     {
4127       update_runway();
4128       get_altitude_above_field(&last_altitude_above_field);
4129       // extrapolated from [SPEC] 6.4.2
4130       above_field_issued = ! last_altitude_above_field.ncd
4131         && last_altitude_above_field.get() < 550;
4132     }
4133 }
4134
4135 void
4136 MK_VIII::Mode6Handler::power_off ()
4137 {
4138   runway_timer.stop();
4139 }
4140
4141 void
4142 MK_VIII::Mode6Handler::enter_takeoff ()
4143 {
4144   reset_altitude_callouts();    // [SPEC] 6.4.2
4145   reset_minimums();             // omitted by [SPEC]; common sense
4146 }
4147
4148 void
4149 MK_VIII::Mode6Handler::leave_takeoff ()
4150 {
4151   reset_altitude_callouts();    // [SPEC] 6.0.2
4152   reset_minimums();             // [SPEC] 6.0.2
4153 }
4154
4155 void
4156 MK_VIII::Mode6Handler::set_volume (double volume)
4157 {
4158   mk_voice(minimums_minimums)->set_volume(volume);
4159   mk_voice(five_hundred_above)->set_volume(volume);
4160   for (int i = 0; i < n_altitude_callouts; i++)
4161     mk_altitude_voice(i)->set_volume(volume);
4162 }
4163
4164 bool
4165 MK_VIII::Mode6Handler::altitude_callouts_enabled ()
4166 {
4167   if (conf.minimums_enabled || conf.smart_500_enabled || conf.above_field_voice)
4168     return true;
4169
4170   for (int i = 0; i < n_altitude_callouts; i++)
4171     if (conf.altitude_callouts_enabled[i])
4172       return true;
4173
4174   return false;
4175 }
4176
4177 void
4178 MK_VIII::Mode6Handler::update_minimums ()
4179 {
4180   if (! mk->io_handler.gpws_inhibit()
4181       && (mk->voice_player.voice == mk_voice(minimums_minimums)
4182           || mk->voice_player.next_voice == mk_voice(minimums_minimums)))
4183     goto end;
4184
4185   if (! mk->io_handler.gpws_inhibit()
4186       && ! mk->state_handler.ground // [1]
4187       && conf.minimums_enabled
4188       && ! minimums_issued
4189       && mk_dinput(landing_gear)
4190       && mk_dinput(decision_height)
4191       && ! last_decision_height)
4192     {
4193       minimums_issued = true;
4194
4195       // If an altitude callout is playing, stop it now so that the
4196       // minimums callout can be played (note that proper connection
4197       // and synchronization of the radio-altitude ARINC 529 input,
4198       // decision-height discrete and decision-height ARINC 529 input
4199       // will prevent an altitude callout from being played near the
4200       // decision height).
4201
4202       if (is_playing_altitude_callout())
4203         mk->voice_player.stop(VoicePlayer::STOP_NOW);
4204
4205       mk_set_alerts(mk_alert(MODE6_MINIMUMS));
4206     }
4207   else
4208     mk_unset_alerts(mk_alert(MODE6_MINIMUMS));
4209
4210  end:
4211   last_decision_height = mk_dinput(decision_height);
4212 }
4213
4214 void
4215 MK_VIII::Mode6Handler::update_altitude_callouts ()
4216 {
4217   if (! mk->io_handler.gpws_inhibit() && is_playing_altitude_callout())
4218     goto end;
4219
4220   if (! mk->io_handler.gpws_inhibit()
4221       && ! mk->state_handler.ground // [1]
4222       && ! mk_data(radio_altitude).ncd)
4223     for (int i = 0; i < n_altitude_callouts && mk_data(radio_altitude).get() <= altitude_callout_definitions[i]; i++)
4224       if ((conf.altitude_callouts_enabled[i]
4225            || (altitude_callout_definitions[i] == 500
4226                && conf.smart_500_enabled))
4227           && ! altitude_callouts_issued[i]
4228           && (last_radio_altitude.ncd
4229               || last_radio_altitude.get() > altitude_callout_definitions[i]))
4230         {
4231           // lock out all callouts superior or equal to this one
4232           for (int j = 0; j <= i; j++)
4233             altitude_callouts_issued[j] = true;
4234
4235           altitude_callouts_issued[i] = true;
4236           if (! is_near_minimums(altitude_callout_definitions[i])
4237               && ! is_outside_band(mk_data(radio_altitude).get(), altitude_callout_definitions[i])
4238               && (! conf.smart_500_enabled
4239                   || altitude_callout_definitions[i] != 500
4240                   || ! inhibit_smart_500()))
4241             {
4242               mk->alert_handler.set_altitude_callout_alert(mk_altitude_voice(i));
4243               goto end;
4244             }
4245         }
4246
4247   mk_unset_alerts(mk_alert(MODE6_ALTITUDE_CALLOUT));
4248
4249  end:
4250   last_radio_altitude.set(&mk_data(radio_altitude));
4251 }
4252
4253 bool
4254 MK_VIII::Mode6Handler::test_runway (const FGRunway *_runway)
4255 {
4256   if (_runway->_length < mk->conf.runway_database)
4257     return false;
4258
4259   // get position of threshold
4260   double latitude, longitude, az;
4261   geo_direct_wgs_84(0,
4262                     _runway->_lat,
4263                     _runway->_lon,
4264                     get_reciprocal_heading(_runway->_heading),
4265                     _runway->_length / 2 * SG_FEET_TO_METER,
4266                     &latitude,
4267                     &longitude,
4268                     &az);
4269
4270   // get distance to threshold
4271   double distance, az1, az2;
4272   geo_inverse_wgs_84(0,
4273                      mk_data(gps_latitude).get(),
4274                      mk_data(gps_longitude).get(),
4275                      latitude,
4276                      longitude,
4277                      &az1, &az2, &distance);
4278
4279   return distance * SG_METER_TO_NM <= 5;
4280 }
4281
4282 bool
4283 MK_VIII::Mode6Handler::test_airport (const FGAirport *airport)
4284 {
4285   FGRunway r;
4286   if (globals->get_runways()->search(airport->getId(), &r))
4287     do
4288       {
4289         if (test_runway(&r))
4290           return true;
4291
4292         // reciprocal runway
4293         r._heading = get_reciprocal_heading(r._heading);
4294         if (test_runway(&r))
4295           return true;
4296       }
4297     while (globals->get_runways()->next(&r) && r._id == airport->getId());
4298
4299   return false;
4300 }
4301
4302 void
4303 MK_VIII::Mode6Handler::update_runway ()
4304 {
4305   if (! mk_data(gps_latitude).ncd && ! mk_data(gps_longitude).ncd)
4306     {
4307       // Search for the closest runway threshold in range 5
4308       // nm. Passing 0.5 degrees (approximatively 30 nm) to
4309       // get_closest_airport() provides enough margin for large
4310       // airports, which may have a runway located far away from the
4311       // airport's reference point.
4312
4313       const FGAirport *airport = get_closest_airport(mk_data(gps_latitude).get(),
4314                                                      mk_data(gps_longitude).get(),
4315                                                      0.5,
4316                                                      *this,
4317                                                      &MK_VIII::Mode6Handler::test_airport);
4318
4319       if (airport)
4320         runway.elevation = airport->getElevation();
4321
4322       has_runway.set(airport != NULL);
4323     }
4324   else
4325     has_runway.unset();
4326 }
4327
4328 void
4329 MK_VIII::Mode6Handler::get_altitude_above_field (Parameter<double> *parameter)
4330 {
4331   if (! has_runway.ncd && has_runway.get() && ! mk_data(geometric_altitude).ncd)
4332     parameter->set(mk_data(geometric_altitude).get() - runway.elevation);
4333   else
4334     parameter->unset();
4335 }
4336
4337 void
4338 MK_VIII::Mode6Handler::update_above_field_callout ()
4339 {
4340   if (! conf.above_field_voice)
4341     return;
4342
4343   // update_runway() has to iterate over the whole runway database
4344   // (which contains thousands of runways), so it would be unwise to
4345   // run it every frame. Run it every 3 seconds. Note that the first
4346   // iteration is run in boot().
4347   if (runway_timer.start_or_elapsed() >= 3)
4348     {
4349       update_runway();
4350       runway_timer.start();
4351     }
4352
4353   Parameter<double> altitude_above_field;
4354   get_altitude_above_field(&altitude_above_field);
4355
4356   if (! mk->io_handler.gpws_inhibit()
4357       && (mk->voice_player.voice == conf.above_field_voice
4358           || mk->voice_player.next_voice == conf.above_field_voice))
4359     goto end;
4360
4361   // handle reset term
4362   if (above_field_issued)
4363     {
4364       if ((! has_runway.ncd && ! has_runway.get())
4365           || (! altitude_above_field.ncd && altitude_above_field.get() > 700))
4366         above_field_issued = false;
4367     }
4368
4369   if (! mk->io_handler.gpws_inhibit()
4370       && ! mk->state_handler.ground // [1]
4371       && ! above_field_issued
4372       && ! altitude_above_field.ncd
4373       && altitude_above_field.get() < 550
4374       && (last_altitude_above_field.ncd
4375           || last_altitude_above_field.get() >= 550))
4376     {
4377       above_field_issued = true;
4378
4379       if (! is_outside_band(altitude_above_field.get(), 500))
4380         {
4381           mk->alert_handler.set_altitude_callout_alert(conf.above_field_voice);
4382           goto end;
4383         }
4384     }
4385
4386   mk_unset_alerts(mk_alert(MODE6_ALTITUDE_CALLOUT));
4387
4388  end:
4389   last_altitude_above_field.set(&altitude_above_field);
4390 }
4391
4392 bool
4393 MK_VIII::Mode6Handler::is_bank_angle (double abs_roll_angle, double bias)
4394 {
4395   return conf.is_bank_angle(&mk_data(radio_altitude),
4396                             abs_roll_angle - abs_roll_angle * bias,
4397                             mk_dinput(autopilot_engaged));
4398 }
4399
4400 bool
4401 MK_VIII::Mode6Handler::is_high_bank_angle ()
4402 {
4403   return mk_data(radio_altitude).ncd || mk_data(radio_altitude).get() >= 210;
4404 }
4405
4406 unsigned int
4407 MK_VIII::Mode6Handler::get_bank_angle_alerts ()
4408 {
4409   if (! mk->io_handler.gpws_inhibit()
4410       && ! mk->state_handler.ground // [1]
4411       && ! mk_data(roll_angle).ncd)
4412     {
4413       double abs_roll_angle = fabs(mk_data(roll_angle).get());
4414
4415       if (is_bank_angle(abs_roll_angle, 0.4))
4416         return is_high_bank_angle()
4417           ? (mk_alert(MODE6_HIGH_BANK_ANGLE_1) | mk_alert(MODE6_HIGH_BANK_ANGLE_2) | mk_alert(MODE6_HIGH_BANK_ANGLE_3))
4418           : (mk_alert(MODE6_LOW_BANK_ANGLE_1) | mk_alert(MODE6_LOW_BANK_ANGLE_2) | mk_alert(MODE6_LOW_BANK_ANGLE_3));
4419       else if (is_bank_angle(abs_roll_angle, 0.2))
4420         return is_high_bank_angle()
4421           ? (mk_alert(MODE6_HIGH_BANK_ANGLE_1) | mk_alert(MODE6_HIGH_BANK_ANGLE_2))
4422           : (mk_alert(MODE6_LOW_BANK_ANGLE_1) | mk_alert(MODE6_LOW_BANK_ANGLE_2));
4423       else if (is_bank_angle(abs_roll_angle, 0))
4424         return is_high_bank_angle()
4425           ? mk_alert(MODE6_HIGH_BANK_ANGLE_1)
4426           : mk_alert(MODE6_LOW_BANK_ANGLE_1);
4427     }
4428
4429   return 0;
4430 }
4431
4432 void
4433 MK_VIII::Mode6Handler::update_bank_angle ()
4434 {
4435   if (! conf.bank_angle_enabled)
4436     return;
4437
4438   unsigned int alerts = get_bank_angle_alerts();
4439
4440   // [SPEC] 6.4.4 specifies that the non-continuous alerts
4441   // (MODE6_*_BANK_ANGLE_1 and MODE6_*_BANK_ANGLE_2) are locked out
4442   // until the initial envelope is exited.
4443
4444   if (! test_bits(alerts, mk_alert(MODE6_LOW_BANK_ANGLE_3)))
4445     mk_unset_alerts(mk_alert(MODE6_LOW_BANK_ANGLE_3));
4446   if (! test_bits(alerts, mk_alert(MODE6_HIGH_BANK_ANGLE_3)))
4447     mk_unset_alerts(mk_alert(MODE6_HIGH_BANK_ANGLE_3));
4448
4449   if (alerts != 0)
4450     mk_set_alerts(alerts);
4451   else
4452     mk_unset_alerts(mk_alert(MODE6_LOW_BANK_ANGLE_1)
4453                     | mk_alert(MODE6_HIGH_BANK_ANGLE_1)
4454                     | mk_alert(MODE6_LOW_BANK_ANGLE_2)
4455                     | mk_alert(MODE6_HIGH_BANK_ANGLE_2));
4456 }
4457
4458 void
4459 MK_VIII::Mode6Handler::update ()
4460 {
4461   if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
4462     return;
4463
4464   if (! mk->state_handler.takeoff
4465       && ! mk_data(radio_altitude).ncd
4466       && mk_data(radio_altitude).get() > 1000)
4467     {
4468       reset_altitude_callouts();        // [SPEC] 6.4.2
4469       reset_minimums();                 // common sense
4470     }
4471
4472   update_minimums();
4473   update_altitude_callouts();
4474   update_above_field_callout();
4475   update_bank_angle();
4476 }
4477
4478 ///////////////////////////////////////////////////////////////////////////////
4479 // TCFHandler /////////////////////////////////////////////////////////////////
4480 ///////////////////////////////////////////////////////////////////////////////
4481
4482 // Gets the difference between the azimuth from @from_lat,@from_lon to
4483 // @to_lat,@to_lon, and @to_heading, in degrees.
4484 double
4485 MK_VIII::TCFHandler::get_azimuth_difference (double from_lat,
4486                                              double from_lon,
4487                                              double to_lat,
4488                                              double to_lon,
4489                                              double to_heading)
4490 {
4491   double az1, az2, distance;
4492   geo_inverse_wgs_84(0, from_lat, from_lon, to_lat, to_lon, &az1, &az2, &distance);
4493   return get_heading_difference(az1, to_heading);
4494 }
4495
4496 // Gets the difference between the azimuth from the current GPS
4497 // position to the center of @_runway, and the heading of @_runway, in
4498 // degrees.
4499 double
4500 MK_VIII::TCFHandler::get_azimuth_difference (const FGRunway *_runway)
4501 {
4502   return get_azimuth_difference(mk_data(gps_latitude).get(),
4503                                 mk_data(gps_longitude).get(),
4504                                 _runway->_lat,
4505                                 _runway->_lon,
4506                                 _runway->_heading);
4507 }
4508
4509 // Selects the most likely intended destination runway of @airport,
4510 // and returns it in @_runway. For each runway, the difference between
4511 // the azimuth from the current GPS position to the center of the
4512 // runway and its heading is computed. The runway having the smallest
4513 // difference wins.
4514 //
4515 // This selection algorithm is not specified in [SPEC], but
4516 // http://www.egpws.com/general_information/description/runway_select.htm
4517 // talks about automatic runway selection.
4518 void
4519 MK_VIII::TCFHandler::select_runway (const FGAirport *airport,
4520                                     FGRunway *_runway)
4521 {
4522   FGRunway r;
4523   bool status = globals->get_runways()->search(airport->getId(), &r);
4524   assert(status);
4525
4526   double min_diff = 360;
4527   do
4528     {
4529       double diff;
4530
4531       diff = get_azimuth_difference(&r);
4532       if (diff < min_diff)
4533         {
4534           min_diff = diff;
4535           *_runway = r;
4536         }
4537
4538       // reciprocal runway
4539       r._heading = get_reciprocal_heading(r._heading);
4540       diff = get_azimuth_difference(&r);
4541       if (diff < min_diff)
4542         {
4543           min_diff = diff;
4544           *_runway = r;
4545         }
4546     }
4547   while (globals->get_runways()->next(&r) && r._id == airport->getId());
4548 }
4549
4550 bool
4551 MK_VIII::TCFHandler::test_airport (const FGAirport *airport)
4552 {
4553   FGRunway r;
4554   if (globals->get_runways()->search(airport->getId(), &r))
4555     do
4556       {
4557         if (r._length >= mk->conf.runway_database)
4558           return true;
4559       }
4560     while (globals->get_runways()->next(&r) && r._id == airport->getId());
4561
4562   return false;
4563 }
4564
4565 void
4566 MK_VIII::TCFHandler::update_runway ()
4567 {
4568   has_runway = false;
4569   if (! mk_data(gps_latitude).ncd && ! mk_data(gps_longitude).ncd)
4570     {
4571       // Search for the intended destination runway of the closest
4572       // airport in range 15 nm. Passing 0.5 degrees (approximatively
4573       // 30 nm) to get_closest_airport() provides enough margin for
4574       // large airports, which may have a runway located far away from
4575       // the airport's reference point.
4576
4577       const FGAirport *airport = get_closest_airport(mk_data(gps_latitude).get(),
4578                                                      mk_data(gps_longitude).get(),
4579                                                      0.5,
4580                                                      *this,
4581                                                      &MK_VIII::TCFHandler::test_airport);
4582
4583       if (airport)
4584         {
4585           has_runway = true;
4586
4587           FGRunway _runway;
4588           select_runway(airport, &_runway);
4589
4590           runway.center.latitude = _runway._lat;
4591           runway.center.longitude = _runway._lon;
4592
4593           runway.elevation = airport->getElevation();
4594
4595           double half_length_m = _runway._length / 2 * SG_FEET_TO_METER;
4596           runway.half_length = half_length_m * SG_METER_TO_NM;
4597
4598           //        b3 ________________ b0
4599           //          |                |
4600           //    h1>>> |  e1<<<<<<<<e0  | <<<h0
4601           //          |________________|
4602           //        b2                  b1
4603
4604           // get heading to runway threshold (h0) and end (h1)
4605           runway.edges[0].heading = _runway._heading;
4606           runway.edges[1].heading = get_reciprocal_heading(_runway._heading);
4607
4608           double az;
4609
4610           // get position of runway threshold (e0)
4611           geo_direct_wgs_84(0,
4612                             runway.center.latitude,
4613                             runway.center.longitude,
4614                             runway.edges[1].heading,
4615                             half_length_m,
4616                             &runway.edges[0].position.latitude,
4617                             &runway.edges[0].position.longitude,
4618                             &az);
4619
4620           // get position of runway end (e1)
4621           geo_direct_wgs_84(0,
4622                             runway.center.latitude,
4623                             runway.center.longitude,
4624                             runway.edges[0].heading,
4625                             half_length_m,
4626                             &runway.edges[1].position.latitude,
4627                             &runway.edges[1].position.longitude,
4628                             &az);
4629
4630           double half_width_m = _runway._width / 2 * SG_FEET_TO_METER;
4631
4632           // get position of threshold bias area edges (b0 and b1)
4633           get_bias_area_edges(&runway.edges[0].position,
4634                               runway.edges[1].heading,
4635                               half_width_m,
4636                               &runway.bias_area[0],
4637                               &runway.bias_area[1]);
4638
4639           // get position of end bias area edges (b2 and b3)
4640           get_bias_area_edges(&runway.edges[1].position,
4641                               runway.edges[0].heading,
4642                               half_width_m,
4643                               &runway.bias_area[2],
4644                               &runway.bias_area[3]);
4645         }
4646     }
4647 }
4648
4649 void
4650 MK_VIII::TCFHandler::get_bias_area_edges (Position *edge,
4651                                           double reciprocal,
4652                                           double half_width_m,
4653                                           Position *bias_edge1,
4654                                           Position *bias_edge2)
4655 {
4656   double half_bias_width_m = k * SG_NM_TO_METER + half_width_m;
4657   double tmp_latitude, tmp_longitude, az;
4658
4659   geo_direct_wgs_84(0,
4660                     edge->latitude,
4661                     edge->longitude,
4662                     reciprocal,
4663                     k * SG_NM_TO_METER,
4664                     &tmp_latitude,
4665                     &tmp_longitude,
4666                     &az);
4667   geo_direct_wgs_84(0,
4668                     tmp_latitude,
4669                     tmp_longitude,
4670                     heading_substract(reciprocal, 90),
4671                     half_bias_width_m,
4672                     &bias_edge1->latitude,
4673                     &bias_edge1->longitude,
4674                     &az);
4675   geo_direct_wgs_84(0,
4676                     tmp_latitude,
4677                     tmp_longitude,
4678                     heading_add(reciprocal, 90),
4679                     half_bias_width_m,
4680                     &bias_edge2->latitude,
4681                     &bias_edge2->longitude,
4682                     &az);
4683 }
4684
4685 // Returns true if the current GPS position is inside the edge
4686 // triangle of @edge. The edge triangle, which is specified and
4687 // represented in [SPEC] 6.3.1, is defined as an isocele right
4688 // triangle of infinite height, whose right angle is located at the
4689 // position of @edge, and whose height is parallel to the heading of
4690 // @edge.
4691 bool
4692 MK_VIII::TCFHandler::is_inside_edge_triangle (RunwayEdge *edge)
4693 {
4694   return get_azimuth_difference(mk_data(gps_latitude).get(),
4695                                 mk_data(gps_longitude).get(),
4696                                 edge->position.latitude,
4697                                 edge->position.longitude,
4698                                 edge->heading) <= 45;
4699 }
4700
4701 // Returns true if the current GPS position is inside the bias area of
4702 // the currently selected runway.
4703 bool
4704 MK_VIII::TCFHandler::is_inside_bias_area ()
4705 {
4706   double az1[4];
4707   double angles_sum = 0;
4708
4709   for (int i = 0; i < 4; i++)
4710     {
4711       double az2, distance;
4712       geo_inverse_wgs_84(0,
4713                          mk_data(gps_latitude).get(),
4714                          mk_data(gps_longitude).get(),
4715                          runway.bias_area[i].latitude,
4716                          runway.bias_area[i].longitude,
4717                          &az1[i], &az2, &distance);
4718     }
4719
4720   for (int i = 0; i < 4; i++)
4721     {
4722       double angle = az1[i == 3 ? 0 : i + 1] - az1[i];
4723       if (angle < -180)
4724         angle += 360;
4725
4726       angles_sum += angle;
4727     }
4728
4729   return angles_sum > 180;
4730 }
4731
4732 bool
4733 MK_VIII::TCFHandler::is_tcf ()
4734 {
4735   if (mk_data(radio_altitude).get() > 10)
4736     {
4737       if (has_runway)
4738         {
4739           double distance, az1, az2;
4740
4741           geo_inverse_wgs_84(0,
4742                              mk_data(gps_latitude).get(),
4743                              mk_data(gps_longitude).get(),
4744                              runway.center.latitude,
4745                              runway.center.longitude,
4746                              &az1, &az2, &distance);
4747
4748           distance *= SG_METER_TO_NM;
4749
4750           // distance to the inner envelope edge
4751           double edge_distance = distance - runway.half_length - k;
4752
4753           if (edge_distance >= 15)
4754             {
4755               if (mk_data(radio_altitude).get() < 700)
4756                 return true;
4757             }
4758           else if (edge_distance >= 12)
4759             {
4760               if (mk_data(radio_altitude).get() < 100 * edge_distance - 800)
4761                 return true;
4762             }
4763           else if (edge_distance >= 4)
4764             {
4765               if (mk_data(radio_altitude).get() < 400)
4766                 return true;
4767             }
4768           else if (edge_distance >= 2.45)
4769             {
4770               if (mk_data(radio_altitude).get() < 66.666667 * edge_distance + 133.33333)
4771                 return true;
4772             }
4773           else
4774             {
4775               if (is_inside_edge_triangle(&runway.edges[0]) || is_inside_edge_triangle(&runway.edges[1]))
4776                 {
4777                   if (edge_distance >= 1)
4778                     {
4779                       if (mk_data(radio_altitude).get() < 66.666667 * edge_distance + 133.33333)
4780                         return true;
4781                     }
4782                   else if (edge_distance >= 0.05)
4783                     {
4784                       if (mk_data(radio_altitude).get() < 200 * edge_distance)
4785                         return true;
4786                     }
4787                 }
4788               else
4789                 {
4790                   if (! is_inside_bias_area())
4791                     {
4792                       if (mk_data(radio_altitude).get() < 245)
4793                         return true;
4794                     }
4795                 }
4796             }
4797         }
4798       else
4799         {
4800           if (mk_data(radio_altitude).get() < 700)
4801             return true;
4802         }
4803     }
4804
4805   return false;
4806 }
4807
4808 bool
4809 MK_VIII::TCFHandler::is_rfcf ()
4810 {
4811   if (has_runway)
4812     {
4813       double distance, az1, az2;
4814       geo_inverse_wgs_84(0,
4815                          mk_data(gps_latitude).get(),
4816                          mk_data(gps_longitude).get(),
4817                          runway.center.latitude,
4818                          runway.center.longitude,
4819                          &az1, &az2, &distance);
4820
4821       double krf = k + mk_data(gps_vertical_figure_of_merit).get() / 200;
4822       distance = distance * SG_METER_TO_NM - runway.half_length - krf;
4823
4824       if (distance <= 5)
4825         {
4826           double altitude_above_field = mk_data(geometric_altitude).get() - runway.elevation;
4827
4828           if (distance >= 1.5)
4829             {
4830               if (altitude_above_field < 300)
4831                 return true;
4832             }
4833           else if (distance >= 0)
4834             {
4835               if (altitude_above_field < 200 * distance)
4836                 return true;
4837             }
4838         }
4839     }
4840
4841   return false;
4842 }
4843
4844 void
4845 MK_VIII::TCFHandler::update ()
4846 {
4847   if (mk->configuration_module.state != ConfigurationModule::STATE_OK || ! conf.enabled)
4848     return;
4849
4850   // update_runway() has to iterate over the whole runway database
4851   // (which contains thousands of runways), so it would be unwise to
4852   // run it every frame. Run it every 3 seconds.
4853   if (! runway_timer.running || runway_timer.elapsed() >= 3)
4854     {
4855       update_runway();
4856       runway_timer.start();
4857     }
4858
4859   if (! mk_dinput(ta_tcf_inhibit)
4860       && ! mk->state_handler.ground // [1]
4861       && ! mk_data(gps_latitude).ncd
4862       && ! mk_data(gps_longitude).ncd
4863       && ! mk_data(radio_altitude).ncd
4864       && ! mk_data(geometric_altitude).ncd
4865       && ! mk_data(gps_vertical_figure_of_merit).ncd)
4866     {
4867       double *_reference;
4868
4869       if (is_tcf())
4870         _reference = mk_data(radio_altitude).get_pointer();
4871       else if (is_rfcf())
4872         _reference = mk_data(geometric_altitude).get_pointer();
4873       else
4874         _reference = NULL;
4875
4876       if (_reference)
4877         {
4878           if (mk_test_alert(TCF_TOO_LOW_TERRAIN))
4879             {
4880               double new_bias = bias;
4881               while (*reference < initial_value - initial_value * new_bias)
4882                 new_bias += 0.2;
4883
4884               if (new_bias > bias)
4885                 {
4886                   bias = new_bias;
4887                   mk_repeat_alert(mk_alert(TCF_TOO_LOW_TERRAIN));
4888                 }
4889             }
4890           else
4891             {
4892               bias = 0.2;
4893               reference = _reference;
4894               initial_value = *reference;
4895               mk_set_alerts(mk_alert(TCF_TOO_LOW_TERRAIN));
4896             }
4897
4898           return;
4899         }
4900     }
4901
4902   mk_unset_alerts(mk_alert(TCF_TOO_LOW_TERRAIN));
4903 }
4904
4905 ///////////////////////////////////////////////////////////////////////////////
4906 // MK_VIII ////////////////////////////////////////////////////////////////////
4907 ///////////////////////////////////////////////////////////////////////////////
4908
4909 MK_VIII::MK_VIII (SGPropertyNode *node)
4910   : name("mk-viii"),
4911     num(0),
4912
4913     properties_handler(this),
4914     power_handler(this),
4915     system_handler(this),
4916     configuration_module(this),
4917     fault_handler(this),
4918     io_handler(this),
4919     voice_player(this),
4920     self_test_handler(this),
4921     alert_handler(this),
4922     state_handler(this),
4923     mode1_handler(this),
4924     mode2_handler(this),
4925     mode3_handler(this),
4926     mode4_handler(this),
4927     mode5_handler(this),
4928     mode6_handler(this),
4929     tcf_handler(this)
4930 {
4931   for (int i = 0; i < node->nChildren(); ++i)
4932     {
4933       SGPropertyNode *child = node->getChild(i);
4934       string cname = child->getName();
4935       string cval = child->getStringValue();
4936
4937       if (cname == "name")
4938         name = cval;
4939       else if (cname == "number")
4940         num = child->getIntValue();
4941       else
4942         {
4943           SG_LOG(SG_INSTR, SG_WARN, "Error in mk-viii config logic");
4944           if (name.length())
4945             SG_LOG(SG_INSTR, SG_WARN, "Section = " << name);
4946         }
4947     }
4948 }
4949
4950 void
4951 MK_VIII::init ()
4952 {
4953   properties_handler.init();
4954   voice_player.init();
4955 }
4956
4957 void
4958 MK_VIII::bind ()
4959 {
4960   SGPropertyNode *node = fgGetNode(("/instrumentation/" + name).c_str(), num, true);
4961
4962   configuration_module.bind(node);
4963   power_handler.bind(node);
4964   io_handler.bind(node);
4965   voice_player.bind(node);
4966 }
4967
4968 void
4969 MK_VIII::unbind ()
4970 {
4971   properties_handler.unbind();
4972 }
4973
4974 void
4975 MK_VIII::update (double dt)
4976 {
4977   power_handler.update();
4978   system_handler.update();
4979
4980   if (system_handler.state != SystemHandler::STATE_ON)
4981     return;
4982
4983   io_handler.update_inputs();
4984   io_handler.update_input_faults();
4985
4986   voice_player.update();
4987   state_handler.update();
4988
4989   if (self_test_handler.update())
4990     return;
4991
4992   io_handler.update_internal_latches();
4993   io_handler.update_lamps();
4994
4995   mode1_handler.update();
4996   mode2_handler.update();
4997   mode3_handler.update();
4998   mode4_handler.update();
4999   mode5_handler.update();
5000   mode6_handler.update();
5001   tcf_handler.update();
5002
5003   alert_handler.update();
5004   io_handler.update_outputs();
5005 }