1 // mk_viii.cxx -- Honeywell MK VIII EGPWS emulation
3 // Written by Jean-Yves Lefort, started September 2005.
5 // Copyright (C) 2005, 2006 Jean-Yves Lefort - jylefort@FreeBSD.org
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.
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.
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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
21 ///////////////////////////////////////////////////////////////////////////////
25 // [PILOT] Honeywell International Inc., "MK VI and MK VIII,
26 // Enhanced Ground Proximity Warning System (EGPWS),
27 // Pilot's Guide", May 2004
29 // http://www.egpws.com/engineering_support/documents/pilot_guides/060-4314-000.pdf
31 // [SPEC] Honeywell International Inc., "Product Specification
32 // for the MK VI and MK VIII Enhanced Ground Proximity
33 // Warning System (EGPWS)", June 2004
35 // http://www.egpws.com/engineering_support/documents/specs/965-1180-601.pdf
37 // [INSTALL] Honeywell Inc., "MK VI, MK VIII, Enhanced Ground
38 // Proximity Warning System (Class A TAWS), Installation
39 // Design Guide", July 2003
41 // http://www.egpws.com/engineering_support/documents/install/060-4314-150.pdf
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
51 # pragma warning( disable: 4355 )
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/sound/sample_group.hxx>
74 #include <simgear/structure/exception.hxx>
78 #include <Airports/runways.hxx>
79 #include <Airports/simple.hxx>
81 #if defined( HAVE_VERSION_H ) && HAVE_VERSION_H
82 # include <Include/version.h>
84 # include <Include/no_version.h>
87 #include <Main/fg_props.hxx>
88 #include <Main/globals.hxx>
89 #include "instrument_mgr.hxx"
90 #include "mk_viii.hxx"
92 ///////////////////////////////////////////////////////////////////////////////
93 // constants //////////////////////////////////////////////////////////////////
94 ///////////////////////////////////////////////////////////////////////////////
96 #define GLIDESLOPE_DOTS_TO_DDM 0.0875 // [INSTALL] 6.2.30
97 #define GLIDESLOPE_DDM_TO_DOTS (1 / GLIDESLOPE_DOTS_TO_DDM)
99 #define LOCALIZER_DOTS_TO_DDM 0.0775 // [INSTALL] 6.2.33
100 #define LOCALIZER_DDM_TO_DOTS (1 / LOCALIZER_DOTS_TO_DDM)
102 ///////////////////////////////////////////////////////////////////////////////
103 // helpers ////////////////////////////////////////////////////////////////////
104 ///////////////////////////////////////////////////////////////////////////////
106 #define assert_not_reached() assert(false)
107 #define n_elements(_array) (sizeof(_array) / sizeof((_array)[0]))
108 #define test_bits(_bits, _test) (((_bits) & (_test)) != 0)
110 #define mk_node(_name) (mk->properties_handler.external_properties._name)
112 #define mk_dinput_feed(_name) (mk->io_handler.input_feeders.discretes._name)
113 #define mk_dinput(_name) (mk->io_handler.inputs.discretes._name)
114 #define mk_ainput_feed(_name) (mk->io_handler.input_feeders.arinc429._name)
115 #define mk_ainput(_name) (mk->io_handler.inputs.arinc429._name)
116 #define mk_doutput(_name) (mk->io_handler.outputs.discretes._name)
117 #define mk_aoutput(_name) (mk->io_handler.outputs.arinc429._name)
118 #define mk_data(_name) (mk->io_handler.data._name)
120 #define mk_voice(_name) (mk->voice_player.voices._name)
121 #define mk_altitude_voice(_i) (mk->voice_player.voices.altitude_callouts[(_i)])
123 #define mk_alert(_name) (AlertHandler::ALERT_ ## _name)
124 #define mk_alert_flag(_name) (AlertHandler::ALERT_FLAG_ ## _name)
125 #define mk_set_alerts (mk->alert_handler.set_alerts)
126 #define mk_unset_alerts (mk->alert_handler.unset_alerts)
127 #define mk_repeat_alert (mk->alert_handler.repeat_alert)
128 #define mk_test_alert(_name) (mk_test_alerts(mk_alert(_name)))
129 #define mk_test_alerts(_test) (test_bits(mk->alert_handler.alerts, (_test)))
131 const double MK_VIII::TCFHandler::k = 0.25;
134 modify_amplitude (double amplitude, double dB)
136 return amplitude * pow(10.0, dB / 20.0);
140 heading_add (double h1, double h2)
142 double result = h1 + h2;
149 heading_substract (double h1, double h2)
151 double result = h1 - h2;
158 get_heading_difference (double h1, double h2)
160 double diff = h1 - h2;
171 get_reciprocal_heading (double h)
173 return heading_add(h, 180);
176 ///////////////////////////////////////////////////////////////////////////////
177 // PropertiesHandler //////////////////////////////////////////////////////////
178 ///////////////////////////////////////////////////////////////////////////////
181 MK_VIII::PropertiesHandler::init ()
183 mk_node(ai_caged) = fgGetNode("/instrumentation/attitude-indicator/caged-flag", true);
184 mk_node(ai_roll) = fgGetNode("/instrumentation/attitude-indicator/indicated-roll-deg", true);
185 mk_node(ai_serviceable) = fgGetNode("/instrumentation/attitude-indicator/serviceable", true);
186 mk_node(altimeter_altitude) = fgGetNode("/instrumentation/altimeter/indicated-altitude-ft", true);
187 mk_node(altimeter_serviceable) = fgGetNode("/instrumentation/altimeter/serviceable", true);
188 mk_node(altitude) = fgGetNode("/position/altitude-ft", true);
189 mk_node(altitude_agl) = fgGetNode("/position/altitude-agl-ft", true);
190 mk_node(altitude_gear_agl) = fgGetNode("/position/gear-agl-ft", true);
191 mk_node(altitude_radar_agl) = fgGetNode("/instrumentation/radar-altimeter/radar-altitude-ft", true);
192 mk_node(orientation_roll) = fgGetNode("/orientation/roll-deg", true);
193 mk_node(asi_serviceable) = fgGetNode("/instrumentation/airspeed-indicator/serviceable", true);
194 mk_node(asi_speed) = fgGetNode("/instrumentation/airspeed-indicator/indicated-speed-kt", true);
195 mk_node(autopilot_heading_lock) = fgGetNode("/autopilot/locks/heading", true);
196 mk_node(flaps) = fgGetNode("/controls/flight/flaps", true);
197 mk_node(gear_down) = fgGetNode("/controls/gear/gear-down", true);
198 mk_node(latitude) = fgGetNode("/position/latitude-deg", true);
199 mk_node(longitude) = fgGetNode("/position/longitude-deg", true);
200 mk_node(nav0_cdi_serviceable) = fgGetNode("/instrumentation/nav/cdi/serviceable", true);
201 mk_node(nav0_gs_distance) = fgGetNode("/instrumentation/nav/gs-distance", true);
202 mk_node(nav0_gs_needle_deflection) = fgGetNode("/instrumentation/nav/gs-needle-deflection", true);
203 mk_node(nav0_gs_serviceable) = fgGetNode("/instrumentation/nav/gs/serviceable", true);
204 mk_node(nav0_has_gs) = fgGetNode("/instrumentation/nav/has-gs", true);
205 mk_node(nav0_heading_needle_deflection) = fgGetNode("/instrumentation/nav/heading-needle-deflection", true);
206 mk_node(nav0_in_range) = fgGetNode("/instrumentation/nav/in-range", true);
207 mk_node(nav0_nav_loc) = fgGetNode("/instrumentation/nav/nav-loc", true);
208 mk_node(nav0_serviceable) = fgGetNode("/instrumentation/nav/serviceable", true);
209 mk_node(power) = fgGetNode(("/systems/electrical/outputs/" + mk->name).c_str(), mk->num, true);
210 mk_node(replay_state) = fgGetNode("/sim/freeze/replay-state", true);
211 mk_node(vs) = fgGetNode("/velocities/vertical-speed-fps", true);
214 ///////////////////////////////////////////////////////////////////////////////
215 // PowerHandler ///////////////////////////////////////////////////////////////
216 ///////////////////////////////////////////////////////////////////////////////
219 MK_VIII::PowerHandler::bind (SGPropertyNode *node)
221 mk->properties_handler.tie(node, "serviceable", SGRawValuePointer<bool>(&serviceable));
225 MK_VIII::PowerHandler::handle_abnormal_voltage (bool abnormal,
231 if (timer->start_or_elapsed() >= max_duration)
232 return true; // power loss
241 MK_VIII::PowerHandler::update ()
243 double voltage = mk_node(power)->getDoubleValue();
244 bool now_powered = true;
252 if (handle_abnormal_voltage(voltage < 20.5, &low_surge_timer, 0.03))
254 if (handle_abnormal_voltage(voltage < 22.0 || voltage > 30.3, &abnormal_timer, 300))
256 if (handle_abnormal_voltage(voltage > 32.2, &high_surge_timer, 1))
258 if (handle_abnormal_voltage(voltage > 37.8, &very_high_surge_timer, 0.1))
268 power_loss_timer.stop();
271 if (power_loss_timer.start_or_elapsed() >= 0.2)
283 MK_VIII::PowerHandler::power_on ()
286 mk->system_handler.power_on();
290 MK_VIII::PowerHandler::power_off ()
293 mk->system_handler.power_off();
294 mk->voice_player.stop(VoicePlayer::STOP_NOW);
295 mk->self_test_handler.power_off(); // run before IOHandler::power_off()
296 mk->io_handler.power_off();
297 mk->mode2_handler.power_off();
298 mk->mode6_handler.power_off();
301 ///////////////////////////////////////////////////////////////////////////////
302 // SystemHandler //////////////////////////////////////////////////////////////
303 ///////////////////////////////////////////////////////////////////////////////
306 MK_VIII::SystemHandler::power_on ()
308 state = STATE_BOOTING;
310 // [SPEC] 3.5.2 mentions a 20 seconds maximum boot delay. We use a
311 // random delay between 3 and 5 seconds.
313 boot_delay = 3 + sg_random() * 2;
318 MK_VIII::SystemHandler::power_off ()
326 MK_VIII::SystemHandler::update ()
328 if (state == STATE_BOOTING)
330 if (boot_timer.elapsed() >= boot_delay)
332 last_replay_state = mk_node(replay_state)->getIntValue();
334 mk->configuration_module.boot();
336 mk->io_handler.boot();
337 mk->fault_handler.boot();
338 mk->alert_handler.boot();
340 // inputs are needed by the following boot() routines
341 mk->io_handler.update_inputs();
343 mk->mode2_handler.boot();
344 mk->mode6_handler.boot();
348 mk->io_handler.post_boot();
351 else if (state != STATE_OFF && mk->configuration_module.state == ConfigurationModule::STATE_OK)
353 // If the replay state changes, switch to reposition mode for 3
354 // seconds ([SPEC] 6.0.5) to avoid spurious alerts.
356 int replay_state = mk_node(replay_state)->getIntValue();
357 if (replay_state != last_replay_state)
359 mk->alert_handler.reposition();
360 mk->io_handler.reposition();
362 last_replay_state = replay_state;
363 state = STATE_REPOSITION;
364 reposition_timer.start();
367 if (state == STATE_REPOSITION && reposition_timer.elapsed() >= 3)
369 // inputs are needed by StateHandler::post_reposition()
370 mk->io_handler.update_inputs();
372 mk->state_handler.post_reposition();
379 ///////////////////////////////////////////////////////////////////////////////
380 // ConfigurationModule ////////////////////////////////////////////////////////
381 ///////////////////////////////////////////////////////////////////////////////
383 MK_VIII::ConfigurationModule::ConfigurationModule (MK_VIII *device)
386 // arbitrary defaults
387 categories[CATEGORY_AIRCRAFT_MODE_TYPE_SELECT] = 0;
388 categories[CATEGORY_AIR_DATA_INPUT_SELECT] = 1;
389 categories[CATEGORY_POSITION_INPUT_SELECT] = 2;
390 categories[CATEGORY_ALTITUDE_CALLOUTS] = 13;
391 categories[CATEGORY_AUDIO_MENU_SELECT] = 0;
392 categories[CATEGORY_TERRAIN_DISPLAY_SELECT] = 1;
393 categories[CATEGORY_OPTIONS_SELECT_GROUP_1] = 124;
394 categories[CATEGORY_RADIO_ALTITUDE_INPUT_SELECT] = 2;
395 categories[CATEGORY_NAVIGATION_INPUT_SELECT] = 3;
396 categories[CATEGORY_ATTITUDE_INPUT_SELECT] = 6;
397 categories[CATEGORY_HEADING_INPUT_SELECT] = 2;
398 categories[CATEGORY_WINDSHEAR_INPUT_SELECT] = 0;
399 categories[CATEGORY_INPUT_OUTPUT_DISCRETE_TYPE_SELECT] = 7;
400 categories[CATEGORY_AUDIO_OUTPUT_LEVEL] = 0;
401 categories[CATEGORY_UNDEFINED_INPUT_SELECT_1] = 0;
402 categories[CATEGORY_UNDEFINED_INPUT_SELECT_2] = 0;
403 categories[CATEGORY_UNDEFINED_INPUT_SELECT_3] = 0;
406 static double m1_t1_min_agl1 (double vs) { return -1620 - 1.1133 * vs; }
407 static double m1_t1_min_agl2 (double vs) { return -400 - 0.4 * vs; }
408 static double m1_t4_min_agl1 (double vs) { return -1625.47 - 1.1167 * vs; }
409 static double m1_t4_min_agl2 (double vs) { return -0.1958 * vs; }
411 static int m3_t1_max_agl (bool flap_override) { return 1500; }
412 static double m3_t1_max_alt_loss (bool flap_override, double agl) { return 5.4 + 0.092 * agl; }
413 static int m3_t2_max_agl (bool flap_override) { return flap_override ? 815 : 925; }
414 static double m3_t2_max_alt_loss (bool flap_override, double agl)
416 int bias = agl > 700 ? 5 : 0;
419 return (9.0 + 0.184 * agl) + bias;
421 return (5.4 + 0.092 * agl) + bias;
424 static double m4_t1_min_agl2 (double airspeed) { return -1083 + 8.333 * airspeed; }
425 static double m4_t568_a_min_agl2 (double airspeed) { return -1523 + 11.36 * airspeed; }
426 static double m4_t79_a_min_agl2 (double airspeed) { return -1182 + 11.36 * airspeed; }
427 static double m4_t568_b_min_agl2 (double airspeed) { return -1570 + 11.6 * airspeed; }
428 static double m4_t79_b_min_agl2 (double airspeed) { return -1222 + 11.6 * airspeed; }
431 MK_VIII::ConfigurationModule::m6_t2_is_bank_angle (Parameter<double> *agl,
437 if (agl->ncd || agl->get() > 122)
438 return abs_roll_deg > 33;
442 if (agl->ncd || agl->get() > 2450)
443 return abs_roll_deg > 55;
444 else if (agl->get() > 150)
445 return agl->get() < 153.33333 * abs_roll_deg - 5983.3333;
449 return agl->get() < 4 * abs_roll_deg - 10;
450 else if (agl->get() > 5)
451 return abs_roll_deg > 10;
457 MK_VIII::ConfigurationModule::m6_t4_is_bank_angle (Parameter<double> *agl,
463 if (agl->ncd || agl->get() > 156)
464 return abs_roll_deg > 33;
468 if (agl->ncd || agl->get() > 210)
469 return abs_roll_deg > 50;
473 return agl->get() < 5.7142857 * abs_roll_deg - 75.714286;
479 MK_VIII::ConfigurationModule::read_aircraft_mode_type_select (int value)
481 static const Mode1Handler::EnvelopesConfiguration m1_t1 =
482 { false, 10, m1_t1_min_agl1, 284, m1_t1_min_agl2, 2450 };
483 static const Mode1Handler::EnvelopesConfiguration m1_t4 =
484 { true, 50, m1_t4_min_agl1, 346, m1_t4_min_agl2, 1958 };
486 static const Mode2Handler::Configuration m2_t1 = { 190, 280 };
487 static const Mode2Handler::Configuration m2_t4 = { 220, 310 };
488 static const Mode2Handler::Configuration m2_t5 = { 220, 310 };
490 static const Mode3Handler::Configuration m3_t1 =
491 { 30, m3_t1_max_agl, m3_t1_max_alt_loss };
492 static const Mode3Handler::Configuration m3_t2 =
493 { 50, m3_t2_max_agl, m3_t2_max_alt_loss };
495 static const Mode4Handler::EnvelopesConfiguration m4_t1_ac =
496 { 190, 250, 500, m4_t1_min_agl2, 1000 };
497 static const Mode4Handler::EnvelopesConfiguration m4_t5_ac =
498 { 178, 200, 500, m4_t568_a_min_agl2, 1000 };
499 static const Mode4Handler::EnvelopesConfiguration m4_t68_ac =
500 { 178, 200, 500, m4_t568_a_min_agl2, 750 };
501 static const Mode4Handler::EnvelopesConfiguration m4_t79_ac =
502 { 148, 170, 500, m4_t79_a_min_agl2, 750 };
504 static const Mode4Handler::EnvelopesConfiguration m4_t1_b =
505 { 159, 250, 245, m4_t1_min_agl2, 1000 };
506 static const Mode4Handler::EnvelopesConfiguration m4_t5_b =
507 { 148, 200, 200, m4_t568_b_min_agl2, 1000 };
508 static const Mode4Handler::EnvelopesConfiguration m4_t6_b =
509 { 150, 200, 170, m4_t568_b_min_agl2, 750 };
510 static const Mode4Handler::EnvelopesConfiguration m4_t7_b =
511 { 120, 170, 170, m4_t79_b_min_agl2, 750 };
512 static const Mode4Handler::EnvelopesConfiguration m4_t8_b =
513 { 148, 200, 150, m4_t568_b_min_agl2, 750 };
514 static const Mode4Handler::EnvelopesConfiguration m4_t9_b =
515 { 118, 170, 150, m4_t79_b_min_agl2, 750 };
517 static const Mode4Handler::ModesConfiguration m4_t1 = { &m4_t1_ac, &m4_t1_b };
518 static const Mode4Handler::ModesConfiguration m4_t5 = { &m4_t5_ac, &m4_t5_b };
519 static const Mode4Handler::ModesConfiguration m4_t6 = { &m4_t68_ac, &m4_t6_b };
520 static const Mode4Handler::ModesConfiguration m4_t7 = { &m4_t79_ac, &m4_t7_b };
521 static const Mode4Handler::ModesConfiguration m4_t8 = { &m4_t68_ac, &m4_t8_b };
522 static const Mode4Handler::ModesConfiguration m4_t9 = { &m4_t79_ac, &m4_t9_b };
524 static Mode6Handler::BankAnglePredicate m6_t2 = m6_t2_is_bank_angle;
525 static Mode6Handler::BankAnglePredicate m6_t4 = m6_t4_is_bank_angle;
527 static const IOHandler::FaultsConfiguration f_slow = { 180, 200 };
528 static const IOHandler::FaultsConfiguration f_fast = { 250, 290 };
533 const Mode1Handler::EnvelopesConfiguration *m1;
534 const Mode2Handler::Configuration *m2;
535 const Mode3Handler::Configuration *m3;
536 const Mode4Handler::ModesConfiguration *m4;
537 Mode6Handler::BankAnglePredicate m6;
538 const IOHandler::FaultsConfiguration *f;
540 } aircraft_types[] = {
541 { 0, &m1_t4, &m2_t4, &m3_t2, &m4_t6, m6_t4, &f_fast, 2000 },
542 { 1, &m1_t4, &m2_t4, &m3_t2, &m4_t8, m6_t4, &f_fast, 2000 },
543 { 2, &m1_t4, &m2_t4, &m3_t2, &m4_t5, m6_t4, &f_fast, 2000 },
544 { 3, &m1_t4, &m2_t5, &m3_t2, &m4_t7, m6_t4, &f_slow, 2000 },
545 { 4, &m1_t4, &m2_t5, &m3_t2, &m4_t9, m6_t4, &f_slow, 2000 },
546 { 254, &m1_t1, &m2_t1, &m3_t1, &m4_t1, m6_t2, &f_fast, 3500 },
547 { 255, &m1_t1, &m2_t1, &m3_t1, &m4_t1, m6_t2, &f_fast, 2000 }
550 for (size_t i = 0; i < n_elements(aircraft_types); i++)
551 if (aircraft_types[i].type == value)
553 mk->mode1_handler.conf.envelopes = aircraft_types[i].m1;
554 mk->mode2_handler.conf = aircraft_types[i].m2;
555 mk->mode3_handler.conf = aircraft_types[i].m3;
556 mk->mode4_handler.conf.modes = aircraft_types[i].m4;
557 mk->mode6_handler.conf.is_bank_angle = aircraft_types[i].m6;
558 mk->io_handler.conf.faults = aircraft_types[i].f;
559 mk->conf.runway_database = aircraft_types[i].runway_database;
563 state = STATE_INVALID_AIRCRAFT_TYPE;
568 MK_VIII::ConfigurationModule::read_air_data_input_select (int value)
571 return (value >= 0 && value <= 6) || (value >= 10 && value <= 13) || value == 255;
575 MK_VIII::ConfigurationModule::read_position_input_select (int value)
578 mk->io_handler.conf.use_internal_gps = true;
579 else if ((value >= 0 && value <= 5)
580 || (value >= 10 && value <= 13)
583 mk->io_handler.conf.use_internal_gps = false;
591 MK_VIII::ConfigurationModule::read_altitude_callouts (int value)
606 { 0, { MINIMUMS, SMART_500, 200, 100, 50, 40, 30, 20, 10, 0 } },
607 { 1, { MINIMUMS, SMART_500, 200, 0 } },
608 { 2, { MINIMUMS, SMART_500, 100, 50, 40, 30, 20, 10, 0 } },
609 { 3, { MINIMUMS, SMART_500, 0 } },
610 { 4, { MINIMUMS, 200, 100, 50, 40, 30, 20, 10, 0 } },
611 { 5, { MINIMUMS, 200, 0 } },
612 { 6, { MINIMUMS, 100, 50, 40, 30, 20, 10, 0 } },
614 { 8, { MINIMUMS, 0 } },
615 { 9, { MINIMUMS, 500, 200, 100, 50, 40, 30, 20, 10, 0 } },
616 { 10, { MINIMUMS, 500, 200, 0 } },
617 { 11, { MINIMUMS, 500, 100, 50, 40, 30, 20, 10, 0 } },
618 { 12, { MINIMUMS, 500, 0 } },
619 { 13, { MINIMUMS, 1000, 500, 400, 300, 200, 100, 50, 40, 30, 20, 10, 0 } },
620 { 14, { MINIMUMS, 100, 0 } },
621 { 15, { MINIMUMS, 200, 100, 0 } },
622 { 100, { FIELD_500, 0 } },
623 { 101, { FIELD_500_ABOVE, 0 } }
628 mk->mode6_handler.conf.minimums_enabled = false;
629 mk->mode6_handler.conf.smart_500_enabled = false;
630 mk->mode6_handler.conf.above_field_voice = NULL;
631 for (i = 0; i < n_altitude_callouts; i++)
632 mk->mode6_handler.conf.altitude_callouts_enabled[i] = false;
634 for (i = 0; i < n_elements(values); i++)
635 if (values[i].id == value)
637 for (int j = 0; values[i].callouts[j] != 0; j++)
638 switch (values[i].callouts[j])
641 mk->mode6_handler.conf.minimums_enabled = true;
645 mk->mode6_handler.conf.smart_500_enabled = true;
649 mk->mode6_handler.conf.above_field_voice = mk_altitude_voice(Mode6Handler::ALTITUDE_CALLOUT_500);
652 case FIELD_500_ABOVE:
653 mk->mode6_handler.conf.above_field_voice = mk_voice(five_hundred_above);
657 for (unsigned k = 0; k < n_altitude_callouts; k++)
658 if (mk->mode6_handler.altitude_callout_definitions[k] == values[i].callouts[j])
659 mk->mode6_handler.conf.altitude_callouts_enabled[k] = true;
670 MK_VIII::ConfigurationModule::read_audio_menu_select (int value)
672 if (value == 0 || value == 1)
673 mk->mode4_handler.conf.voice_too_low_gear = mk_voice(too_low_gear);
674 else if (value == 2 || value == 3)
675 mk->mode4_handler.conf.voice_too_low_gear = mk_voice(too_low_flaps);
683 MK_VIII::ConfigurationModule::read_terrain_display_select (int value)
686 mk->tcf_handler.conf.enabled = false;
687 else if (value == 0 || value == 1 || (value >= 3 && value <= 15)
688 || (value >= 18 && value <= 20) || (value >= 235 && value <= 255))
689 mk->tcf_handler.conf.enabled = true;
697 MK_VIII::ConfigurationModule::read_options_select_group_1 (int value)
699 if (value >= 0 && value < 128)
701 mk->io_handler.conf.flap_reversal = test_bits(value, 1 << 1);
702 mk->mode6_handler.conf.bank_angle_enabled = test_bits(value, 1 << 2);
703 mk->io_handler.conf.steep_approach_enabled = test_bits(value, 1 << 6);
711 MK_VIII::ConfigurationModule::read_radio_altitude_input_select (int value)
713 mk->io_handler.conf.altitude_source = value;
714 return (value >= 0 && value <= 4) || (value >= 251 && value <= 255);
718 MK_VIII::ConfigurationModule::read_navigation_input_select (int value)
720 if (value >= 0 && value <= 2)
721 mk->io_handler.conf.localizer_enabled = false;
722 else if ((value >= 3 && value <= 5) || (value >= 250 && value <= 255))
723 mk->io_handler.conf.localizer_enabled = true;
731 MK_VIII::ConfigurationModule::read_attitude_input_select (int value)
734 mk->io_handler.conf.use_attitude_indicator=true;
736 mk->io_handler.conf.use_attitude_indicator=false;
737 return (value >= 0 && value <= 6) || value == 253 || value == 255;
741 MK_VIII::ConfigurationModule::read_heading_input_select (int value)
744 return (value >= 0 && value <= 3) || value == 254 || value == 255;
748 MK_VIII::ConfigurationModule::read_windshear_input_select (int value)
751 return value == 0 || (value >= 253 && value <= 255);
755 MK_VIII::ConfigurationModule::read_input_output_discrete_type_select (int value)
760 IOHandler::LampConfiguration lamp_conf;
761 bool gpws_inhibit_enabled;
762 bool momentary_flap_override_enabled;
763 bool alternate_steep_approach;
765 { 0, { false, false }, false, true, true },
766 { 1, { true, false }, false, true, true },
767 { 2, { false, false }, true, true, true },
768 { 3, { true, false }, true, true, true },
769 { 4, { false, true }, true, true, true },
770 { 5, { true, true }, true, true, true },
771 { 6, { false, false }, true, true, false },
772 { 7, { true, false }, true, true, false },
773 { 254, { false, false }, true, false, true },
774 { 255, { false, false }, true, false, true }
777 for (size_t i = 0; i < n_elements(io_types); i++)
778 if (io_types[i].type == value)
780 mk->io_handler.conf.lamp = &io_types[i].lamp_conf;
781 mk->io_handler.conf.gpws_inhibit_enabled = io_types[i].gpws_inhibit_enabled;
782 mk->io_handler.conf.momentary_flap_override_enabled = io_types[i].momentary_flap_override_enabled;
783 mk->io_handler.conf.alternate_steep_approach = io_types[i].alternate_steep_approach;
791 MK_VIII::ConfigurationModule::read_audio_output_level (int value)
805 for (size_t i = 0; i < n_elements(values); i++)
806 if (values[i].id == value)
808 mk->voice_player.set_volume(mk->voice_player.conf.volume = modify_amplitude(1.0, values[i].relative_dB));
812 // The self test needs the voice player even when the configuration
813 // is invalid, so set a default value.
814 mk->voice_player.set_volume(mk->voice_player.conf.volume = 1.0);
819 MK_VIII::ConfigurationModule::read_undefined_input_select (int value)
826 MK_VIII::ConfigurationModule::boot ()
828 bool (MK_VIII::ConfigurationModule::*readers[N_CATEGORIES]) (int) = {
829 &MK_VIII::ConfigurationModule::read_aircraft_mode_type_select,
830 &MK_VIII::ConfigurationModule::read_air_data_input_select,
831 &MK_VIII::ConfigurationModule::read_position_input_select,
832 &MK_VIII::ConfigurationModule::read_altitude_callouts,
833 &MK_VIII::ConfigurationModule::read_audio_menu_select,
834 &MK_VIII::ConfigurationModule::read_terrain_display_select,
835 &MK_VIII::ConfigurationModule::read_options_select_group_1,
836 &MK_VIII::ConfigurationModule::read_radio_altitude_input_select,
837 &MK_VIII::ConfigurationModule::read_navigation_input_select,
838 &MK_VIII::ConfigurationModule::read_attitude_input_select,
839 &MK_VIII::ConfigurationModule::read_heading_input_select,
840 &MK_VIII::ConfigurationModule::read_windshear_input_select,
841 &MK_VIII::ConfigurationModule::read_input_output_discrete_type_select,
842 &MK_VIII::ConfigurationModule::read_audio_output_level,
843 &MK_VIII::ConfigurationModule::read_undefined_input_select,
844 &MK_VIII::ConfigurationModule::read_undefined_input_select,
845 &MK_VIII::ConfigurationModule::read_undefined_input_select
848 memcpy(effective_categories, categories, sizeof(categories));
851 for (int i = 0; i < N_CATEGORIES; i++)
852 if (! (this->*readers[i])(effective_categories[i]))
854 SG_LOG(SG_INSTR, SG_ALERT, "MK VIII EGPWS configuration category " << i + 1 << ": invalid value " << effective_categories[i]);
856 if (state == STATE_OK)
857 state = STATE_INVALID_DATABASE;
859 mk_doutput(gpws_inop) = true;
860 mk_doutput(tad_inop) = true;
867 if (mk->mode6_handler.conf.above_field_voice && ! mk->tcf_handler.conf.enabled)
870 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");
871 state = STATE_INVALID_DATABASE;
876 MK_VIII::ConfigurationModule::bind (SGPropertyNode *node)
878 for (int i = 0; i < N_CATEGORIES; i++)
880 std::ostringstream name;
881 name << "configuration-module/category-" << i + 1;
882 mk->properties_handler.tie(node, name.str().c_str(), SGRawValuePointer<int>(&categories[i]));
886 ///////////////////////////////////////////////////////////////////////////////
887 // FaultHandler ///////////////////////////////////////////////////////////////
888 ///////////////////////////////////////////////////////////////////////////////
890 // [INSTALL] only specifies that the faults cause a GPWS INOP
891 // indication. We arbitrarily set a TAD INOP when it makes sense.
892 const unsigned int MK_VIII::FaultHandler::fault_inops[] = {
893 INOP_GPWS | INOP_TAD, // [INSTALL] 3.15.1.3
894 INOP_GPWS, // [INSTALL] appendix E 6.6.2
895 INOP_GPWS, // [INSTALL] appendix E 6.6.4
896 INOP_GPWS, // [INSTALL] appendix E 6.6.6
897 INOP_GPWS | INOP_TAD, // [INSTALL] appendix E 6.6.7
898 INOP_GPWS, // [INSTALL] appendix E 6.6.13
899 INOP_GPWS, // [INSTALL] appendix E 6.6.25
900 INOP_GPWS, // [INSTALL] appendix E 6.6.27
901 INOP_TAD, // unspecified
902 INOP_GPWS, // unspecified
903 INOP_GPWS, // unspecified
904 // [INSTALL] 2.3.10.1 specifies that GPWS INOP is "activated during
905 // any detected partial or total failure of the GPWS modes 1-5", so
906 // do not set any INOP for mode 6 and bank angle.
909 INOP_TAD // unspecified
913 MK_VIII::FaultHandler::has_faults () const
915 for (int i = 0; i < N_FAULTS; i++)
923 MK_VIII::FaultHandler::has_faults (unsigned int inop)
925 for (int i = 0; i < N_FAULTS; i++)
926 if (faults[i] && test_bits(fault_inops[i], inop))
933 MK_VIII::FaultHandler::boot ()
935 memset(faults, 0, sizeof(faults));
939 MK_VIII::FaultHandler::set_fault (Fault fault)
943 faults[fault] = true;
945 mk->self_test_handler.set_inop();
947 if (test_bits(fault_inops[fault], INOP_GPWS))
949 mk_unset_alerts(~mk_alert(TCF_TOO_LOW_TERRAIN));
950 mk_doutput(gpws_inop) = true;
952 if (test_bits(fault_inops[fault], INOP_TAD))
954 mk_unset_alerts(mk_alert(TCF_TOO_LOW_TERRAIN));
955 mk_doutput(tad_inop) = true;
961 MK_VIII::FaultHandler::unset_fault (Fault fault)
965 faults[fault] = false;
966 if (! has_faults(INOP_GPWS))
967 mk_doutput(gpws_inop) = false;
968 if (! has_faults(INOP_TAD))
969 mk_doutput(tad_inop) = false;
973 ///////////////////////////////////////////////////////////////////////////////
974 // IOHandler //////////////////////////////////////////////////////////////////
975 ///////////////////////////////////////////////////////////////////////////////
978 MK_VIII::IOHandler::TerrainClearanceFilter::update (double agl)
980 // [PILOT] page 20 specifies that the terrain clearance is equal to
981 // 75% of the radio altitude, averaged over the previous 15 seconds.
983 // no updates when simulation is paused (dt=0.0), and add 5 samples/second only
984 if (globals->get_sim_time_sec() - last_update < 0.2)
986 last_update = globals->get_sim_time_sec();
988 samples_type::iterator iter;
990 // remove samples older than 15 seconds
991 for (iter = samples.begin(); iter != samples.end() && globals->get_sim_time_sec() - (*iter).timestamp >= 15; iter = samples.begin())
995 samples.push_back(Sample<double>(agl));
998 double new_value = 0;
999 if (samples.size() > 0)
1001 // time consuming loop => queue limited to 75 samples
1002 // (= 15seconds * 5samples/second)
1003 for (iter = samples.begin(); iter != samples.end(); iter++)
1004 new_value += (*iter).value;
1005 new_value /= samples.size();
1009 if (new_value > value)
1016 MK_VIII::IOHandler::TerrainClearanceFilter::reset ()
1023 MK_VIII::IOHandler::IOHandler (MK_VIII *device)
1024 : mk(device), _lamp(LAMP_NONE)
1026 memset(&input_feeders, 0, sizeof(input_feeders));
1027 memset(&inputs.discretes, 0, sizeof(inputs.discretes));
1028 memset(&outputs, 0, sizeof(outputs));
1029 memset(&power_saved, 0, sizeof(power_saved));
1031 mk_dinput_feed(landing_gear) = true;
1032 mk_dinput_feed(landing_flaps) = true;
1033 mk_dinput_feed(glideslope_inhibit) = true;
1034 mk_dinput_feed(decision_height) = true;
1035 mk_dinput_feed(autopilot_engaged) = true;
1036 mk_ainput_feed(uncorrected_barometric_altitude) = true;
1037 mk_ainput_feed(barometric_altitude_rate) = true;
1038 mk_ainput_feed(radio_altitude) = true;
1039 mk_ainput_feed(glideslope_deviation) = true;
1040 mk_ainput_feed(roll_angle) = true;
1041 mk_ainput_feed(localizer_deviation) = true;
1042 mk_ainput_feed(computed_airspeed) = true;
1044 // will be unset on power on
1045 mk_doutput(gpws_inop) = true;
1046 mk_doutput(tad_inop) = true;
1050 MK_VIII::IOHandler::boot ()
1052 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
1055 mk_doutput(gpws_inop) = false;
1056 mk_doutput(tad_inop) = false;
1058 mk_doutput(glideslope_cancel) = power_saved.glideslope_cancel;
1060 altitude_samples.clear();
1061 reset_terrain_clearance();
1065 MK_VIII::IOHandler::post_boot ()
1067 if (momentary_steep_approach_enabled())
1069 last_landing_gear = mk_dinput(landing_gear);
1070 last_real_flaps_down = real_flaps_down();
1073 // read externally-latching input discretes
1074 update_alternate_discrete_input(&mk_dinput(mode6_low_volume));
1075 update_alternate_discrete_input(&mk_dinput(audio_inhibit));
1079 MK_VIII::IOHandler::power_off ()
1081 power_saved.glideslope_cancel = mk_doutput(glideslope_cancel); // [SPEC] 6.2.5
1083 memset(&outputs, 0, sizeof(outputs));
1085 audio_inhibit_fault_timer.stop();
1086 landing_gear_fault_timer.stop();
1087 flaps_down_fault_timer.stop();
1088 momentary_flap_override_fault_timer.stop();
1089 self_test_fault_timer.stop();
1090 glideslope_cancel_fault_timer.stop();
1091 steep_approach_fault_timer.stop();
1092 gpws_inhibit_fault_timer.stop();
1093 ta_tcf_inhibit_fault_timer.stop();
1096 mk_doutput(gpws_inop) = true;
1097 mk_doutput(tad_inop) = true;
1101 MK_VIII::IOHandler::enter_ground ()
1103 reset_terrain_clearance();
1105 if (conf.momentary_flap_override_enabled)
1106 mk_doutput(flap_override) = false; // [INSTALL] 3.15.1.6
1110 MK_VIII::IOHandler::enter_takeoff ()
1112 reset_terrain_clearance();
1114 if (momentary_steep_approach_enabled())
1115 // landing or go-around, disable steep approach as per [SPEC] 6.2.1
1116 mk_doutput(steep_approach) = false;
1120 MK_VIII::IOHandler::update_inputs ()
1122 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
1125 // 1. process input feeders
1127 if (mk_dinput_feed(landing_gear))
1128 mk_dinput(landing_gear) = mk_node(gear_down)->getBoolValue();
1129 if (mk_dinput_feed(landing_flaps))
1130 mk_dinput(landing_flaps) = mk_node(flaps)->getDoubleValue() < 1;
1131 if (mk_dinput_feed(glideslope_inhibit))
1132 mk_dinput(glideslope_inhibit) = mk_node(nav0_gs_distance)->getDoubleValue() < 0;
1133 if (mk_dinput_feed(autopilot_engaged))
1137 mode = mk_node(autopilot_heading_lock)->getStringValue();
1138 mk_dinput(autopilot_engaged) = mode && *mode;
1141 if (mk_ainput_feed(uncorrected_barometric_altitude))
1143 if (mk_node(altimeter_serviceable)->getBoolValue())
1144 mk_ainput(uncorrected_barometric_altitude).set(mk_node(altimeter_altitude)->getDoubleValue());
1146 mk_ainput(uncorrected_barometric_altitude).unset();
1148 if (mk_ainput_feed(barometric_altitude_rate))
1149 // Do not use the vsi, because it is pressure-based only, and
1150 // therefore too laggy for GPWS alerting purposes. I guess that
1151 // a real ADC combines pressure-based and inerta-based altitude
1152 // rates to provide a non-laggy rate. That non-laggy rate is
1153 // best emulated by /velocities/vertical-speed-fps * 60.
1154 mk_ainput(barometric_altitude_rate).set(mk_node(vs)->getDoubleValue() * 60);
1155 if (mk_ainput_feed(radio_altitude))
1158 switch (conf.altitude_source)
1161 agl = mk_node(altitude_gear_agl)->getDoubleValue();
1164 agl = mk_node(altitude_radar_agl)->getDoubleValue();
1166 default: // 0,1,2 (and any currently unsupported values)
1167 agl = mk_node(altitude_agl)->getDoubleValue();
1170 // Some flight models may return negative values when on the
1171 // ground or after a crash; do not allow them.
1172 mk_ainput(radio_altitude).set(SG_MAX2(0.0, agl));
1174 if (mk_ainput_feed(glideslope_deviation))
1176 if (mk_node(nav0_serviceable)->getBoolValue()
1177 && mk_node(nav0_gs_serviceable)->getBoolValue()
1178 && mk_node(nav0_in_range)->getBoolValue()
1179 && mk_node(nav0_has_gs)->getBoolValue())
1180 // gs-needle-deflection is expressed in degrees, and 1 dot =
1181 // 0.32 degrees (according to
1182 // http://www.ntsb.gov/Recs/letters/2000/A00_41_45.pdf)
1183 mk_ainput(glideslope_deviation).set(mk_node(nav0_gs_needle_deflection)->getDoubleValue() / 0.32 * GLIDESLOPE_DOTS_TO_DDM);
1185 mk_ainput(glideslope_deviation).unset();
1187 if (mk_ainput_feed(roll_angle))
1189 if (conf.use_attitude_indicator)
1191 // read data from attitude indicator instrument (requires vacuum system to work)
1192 if (mk_node(ai_serviceable)->getBoolValue() && ! mk_node(ai_caged)->getBoolValue())
1193 mk_ainput(roll_angle).set(mk_node(ai_roll)->getDoubleValue());
1195 mk_ainput(roll_angle).unset();
1199 // use simulator source
1200 mk_ainput(roll_angle).set(mk_node(orientation_roll)->getDoubleValue());
1203 if (mk_ainput_feed(localizer_deviation))
1205 if (mk_node(nav0_serviceable)->getBoolValue()
1206 && mk_node(nav0_cdi_serviceable)->getBoolValue()
1207 && mk_node(nav0_in_range)->getBoolValue()
1208 && mk_node(nav0_nav_loc)->getBoolValue())
1209 // heading-needle-deflection is expressed in degrees, and 1
1210 // dot = 2 degrees (0.5 degrees for ILS, but navradio.cxx
1211 // performs the conversion)
1212 mk_ainput(localizer_deviation).set(mk_node(nav0_heading_needle_deflection)->getDoubleValue() / 2 * LOCALIZER_DOTS_TO_DDM);
1214 mk_ainput(localizer_deviation).unset();
1216 if (mk_ainput_feed(computed_airspeed))
1218 if (mk_node(asi_serviceable)->getBoolValue())
1219 mk_ainput(computed_airspeed).set(mk_node(asi_speed)->getDoubleValue());
1221 mk_ainput(computed_airspeed).unset();
1226 mk_data(decision_height).set(&mk_ainput(decision_height));
1227 mk_data(radio_altitude).set(&mk_ainput(radio_altitude));
1228 mk_data(roll_angle).set(&mk_ainput(roll_angle));
1230 // update barometric_altitude_rate as per [SPEC] 6.2.1: "During
1231 // normal conditions, the system will base Mode 1 computations upon
1232 // barometric rate from the ADC. If this computed data is not valid
1233 // or available then the system will use internally computed
1234 // barometric altitude rate."
1236 if (! mk_ainput(barometric_altitude_rate).ncd)
1237 // the altitude rate input is valid, use it
1238 mk_data(barometric_altitude_rate).set(mk_ainput(barometric_altitude_rate).get());
1243 // The altitude rate input is invalid. We can compute an
1244 // altitude rate if all the following conditions are true:
1246 // - the altitude input is valid
1247 // - there is an altitude sample with age >= 1 second
1248 // - that altitude sample is valid
1250 if (! mk_ainput(uncorrected_barometric_altitude).ncd)
1252 if (! altitude_samples.empty() && ! altitude_samples.front().value.ncd)
1254 double elapsed = globals->get_sim_time_sec() - altitude_samples.front().timestamp;
1258 mk_data(barometric_altitude_rate).set((mk_ainput(uncorrected_barometric_altitude).get() - altitude_samples.front().value.get()) / elapsed * 60);
1264 mk_data(barometric_altitude_rate).unset();
1267 altitude_samples.push_back(Sample< Parameter<double> >(mk_ainput(uncorrected_barometric_altitude)));
1269 // Erase everything from the beginning of the list up to the sample
1270 // preceding the most recent sample whose age is >= 1 second.
1272 altitude_samples_type::iterator erase_last = altitude_samples.begin();
1273 altitude_samples_type::iterator iter;
1275 for (iter = altitude_samples.begin(); iter != altitude_samples.end(); iter++)
1276 if (globals->get_sim_time_sec() - (*iter).timestamp >= 1)
1281 if (erase_last != altitude_samples.begin())
1282 altitude_samples.erase(altitude_samples.begin(), erase_last);
1286 if (conf.use_internal_gps)
1288 mk_data(gps_altitude).set(mk_node(altitude)->getDoubleValue());
1289 mk_data(gps_latitude).set(mk_node(latitude)->getDoubleValue());
1290 mk_data(gps_longitude).set(mk_node(longitude)->getDoubleValue());
1291 mk_data(gps_vertical_figure_of_merit).set(0.0);
1295 mk_data(gps_altitude).set(&mk_ainput(gps_altitude));
1296 mk_data(gps_latitude).set(&mk_ainput(gps_latitude));
1297 mk_data(gps_longitude).set(&mk_ainput(gps_longitude));
1298 mk_data(gps_vertical_figure_of_merit).set(&mk_ainput(gps_vertical_figure_of_merit));
1301 // update glideslope and localizer
1303 mk_data(glideslope_deviation_dots).set(&mk_ainput(glideslope_deviation), GLIDESLOPE_DDM_TO_DOTS);
1304 mk_data(localizer_deviation_dots).set(&mk_ainput(localizer_deviation), LOCALIZER_DDM_TO_DOTS);
1306 // Update geometric altitude; [SPEC] 6.7.8 provides an overview of a
1307 // complex algorithm which combines several input sources to
1308 // calculate the geometric altitude. Since the exact algorithm is
1309 // not given, we fallback to a much simpler method.
1311 if (! mk_data(gps_altitude).ncd)
1312 mk_data(geometric_altitude).set(mk_data(gps_altitude).get());
1313 else if (! mk_ainput(uncorrected_barometric_altitude).ncd)
1314 mk_data(geometric_altitude).set(mk_ainput(uncorrected_barometric_altitude).get());
1316 mk_data(geometric_altitude).unset();
1318 // update terrain clearance
1320 update_terrain_clearance();
1322 // 3. perform sanity checks
1324 if (! mk_data(radio_altitude).ncd && mk_data(radio_altitude).get() < 0)
1325 mk_data(radio_altitude).unset();
1327 if (! mk_data(decision_height).ncd && mk_data(decision_height).get() < 0)
1328 mk_data(decision_height).unset();
1330 if (! mk_data(gps_latitude).ncd
1331 && (mk_data(gps_latitude).get() < -90
1332 || mk_data(gps_latitude).get() > 90))
1333 mk_data(gps_latitude).unset();
1335 if (! mk_data(gps_longitude).ncd
1336 && (mk_data(gps_longitude).get() < -180
1337 || mk_data(gps_longitude).get() > 180))
1338 mk_data(gps_longitude).unset();
1340 if (! mk_data(roll_angle).ncd
1341 && ((mk_data(roll_angle).get() < -180)
1342 || (mk_data(roll_angle).get() > 180)))
1343 mk_data(roll_angle).unset();
1345 // 4. process input feeders requiring data computed above
1347 if (mk_dinput_feed(decision_height))
1348 mk_dinput(decision_height) = ! mk_data(radio_altitude).ncd
1349 && ! mk_data(decision_height).ncd
1350 && mk_data(radio_altitude).get() <= mk_data(decision_height).get();
1354 MK_VIII::IOHandler::update_terrain_clearance ()
1356 if (! mk_data(radio_altitude).ncd)
1357 mk_data(terrain_clearance).set(terrain_clearance_filter.update(mk_data(radio_altitude).get()));
1359 mk_data(terrain_clearance).unset();
1363 MK_VIII::IOHandler::reset_terrain_clearance ()
1365 terrain_clearance_filter.reset();
1366 update_terrain_clearance();
1370 MK_VIII::IOHandler::reposition ()
1372 reset_terrain_clearance();
1376 MK_VIII::IOHandler::handle_input_fault (bool test, FaultHandler::Fault fault)
1380 if (! mk->fault_handler.faults[fault])
1381 mk->fault_handler.set_fault(fault);
1384 mk->fault_handler.unset_fault(fault);
1388 MK_VIII::IOHandler::handle_input_fault (bool test,
1390 double max_duration,
1391 FaultHandler::Fault fault)
1395 if (! mk->fault_handler.faults[fault])
1397 if (timer->start_or_elapsed() >= max_duration)
1398 mk->fault_handler.set_fault(fault);
1403 mk->fault_handler.unset_fault(fault);
1409 MK_VIII::IOHandler::update_input_faults ()
1411 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
1414 // [INSTALL] 3.15.1.3
1415 handle_input_fault(mk_dinput(audio_inhibit),
1416 &audio_inhibit_fault_timer,
1418 FaultHandler::FAULT_ALL_MODES_INHIBIT);
1420 // [INSTALL] appendix E 6.6.2
1421 handle_input_fault(mk_dinput(landing_gear)
1422 && ! mk_ainput(computed_airspeed).ncd
1423 && mk_ainput(computed_airspeed).get() > conf.faults->max_gear_down_airspeed,
1424 &landing_gear_fault_timer,
1426 FaultHandler::FAULT_GEAR_SWITCH);
1428 // [INSTALL] appendix E 6.6.4
1429 handle_input_fault(flaps_down()
1430 && ! mk_ainput(computed_airspeed).ncd
1431 && mk_ainput(computed_airspeed).get() > conf.faults->max_flaps_down_airspeed,
1432 &flaps_down_fault_timer,
1434 FaultHandler::FAULT_FLAPS_SWITCH);
1436 // [INSTALL] appendix E 6.6.6
1437 if (conf.momentary_flap_override_enabled)
1438 handle_input_fault(mk_dinput(momentary_flap_override),
1439 &momentary_flap_override_fault_timer,
1441 FaultHandler::FAULT_MOMENTARY_FLAP_OVERRIDE_INVALID);
1443 // [INSTALL] appendix E 6.6.7
1444 handle_input_fault(mk_dinput(self_test),
1445 &self_test_fault_timer,
1447 FaultHandler::FAULT_SELF_TEST_INVALID);
1449 // [INSTALL] appendix E 6.6.13
1450 handle_input_fault(mk_dinput(glideslope_cancel),
1451 &glideslope_cancel_fault_timer,
1453 FaultHandler::FAULT_GLIDESLOPE_CANCEL_INVALID);
1455 // [INSTALL] appendix E 6.6.25
1456 if (momentary_steep_approach_enabled())
1457 handle_input_fault(mk_dinput(steep_approach),
1458 &steep_approach_fault_timer,
1460 FaultHandler::FAULT_STEEP_APPROACH_INVALID);
1462 // [INSTALL] appendix E 6.6.27
1463 if (conf.gpws_inhibit_enabled)
1464 handle_input_fault(mk_dinput(gpws_inhibit),
1465 &gpws_inhibit_fault_timer,
1467 FaultHandler::FAULT_GPWS_INHIBIT);
1469 // [INSTALL] does not specify a fault for this one, but it makes
1470 // sense to have it behave like GPWS inhibit
1471 handle_input_fault(mk_dinput(ta_tcf_inhibit),
1472 &ta_tcf_inhibit_fault_timer,
1474 FaultHandler::FAULT_TA_TCF_INHIBIT);
1476 // [PILOT] page 48: "In the event that required data for a
1477 // particular function is not available, then that function is
1478 // automatically inhibited and annunciated"
1480 handle_input_fault(mk_data(radio_altitude).ncd
1481 || mk_ainput(uncorrected_barometric_altitude).ncd
1482 || mk_data(barometric_altitude_rate).ncd
1483 || mk_ainput(computed_airspeed).ncd
1484 || mk_data(terrain_clearance).ncd,
1485 FaultHandler::FAULT_MODES14_INPUTS_INVALID);
1487 if (! mk_dinput(glideslope_inhibit))
1488 handle_input_fault(mk_data(radio_altitude).ncd,
1489 FaultHandler::FAULT_MODE5_INPUTS_INVALID);
1491 if (mk->mode6_handler.altitude_callouts_enabled())
1492 handle_input_fault(mk->mode6_handler.conf.above_field_voice
1493 ? (mk_data(gps_latitude).ncd
1494 || mk_data(gps_longitude).ncd
1495 || mk_data(geometric_altitude).ncd)
1496 : mk_data(radio_altitude).ncd,
1497 FaultHandler::FAULT_MODE6_INPUTS_INVALID);
1499 if (mk->mode6_handler.conf.bank_angle_enabled)
1500 handle_input_fault(mk_data(roll_angle).ncd,
1501 FaultHandler::FAULT_BANK_ANGLE_INPUTS_INVALID);
1503 if (mk->tcf_handler.conf.enabled)
1504 handle_input_fault(mk_data(radio_altitude).ncd
1505 || mk_data(geometric_altitude).ncd
1506 || mk_data(gps_latitude).ncd
1507 || mk_data(gps_longitude).ncd
1508 || mk_data(gps_vertical_figure_of_merit).ncd,
1509 FaultHandler::FAULT_TCF_INPUTS_INVALID);
1513 MK_VIII::IOHandler::update_alternate_discrete_input (bool *ptr)
1515 assert(mk->system_handler.state == SystemHandler::STATE_ON);
1517 if (ptr == &mk_dinput(mode6_low_volume))
1519 if (mk->configuration_module.state == ConfigurationModule::STATE_OK
1520 && mk->self_test_handler.state == SelfTestHandler::STATE_NONE)
1521 mk->mode6_handler.set_volume(*ptr ? modify_amplitude(1.0, -6) : 1.0);
1523 else if (ptr == &mk_dinput(audio_inhibit))
1525 if (mk->configuration_module.state == ConfigurationModule::STATE_OK
1526 && mk->self_test_handler.state == SelfTestHandler::STATE_NONE)
1527 mk->voice_player.set_volume(*ptr ? 0.0 : mk->voice_player.conf.volume);
1532 MK_VIII::IOHandler::update_internal_latches ()
1534 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
1538 if (conf.momentary_flap_override_enabled
1539 && mk_doutput(flap_override)
1540 && ! mk->state_handler.takeoff
1541 && (mk_data(radio_altitude).ncd || mk_data(radio_altitude).get() <= 50))
1542 mk_doutput(flap_override) = false;
1545 if (momentary_steep_approach_enabled())
1547 if (mk_doutput(steep_approach)
1548 && ! mk->state_handler.takeoff
1549 && ((last_landing_gear && ! mk_dinput(landing_gear))
1550 || (last_real_flaps_down && ! real_flaps_down())))
1551 // gear or flaps raised during approach: it's a go-around
1552 mk_doutput(steep_approach) = false;
1554 last_landing_gear = mk_dinput(landing_gear);
1555 last_real_flaps_down = real_flaps_down();
1559 if (mk_doutput(glideslope_cancel)
1560 && (mk_data(glideslope_deviation_dots).ncd
1561 || mk_data(radio_altitude).ncd
1562 || mk_data(radio_altitude).get() > 2000
1563 || mk_data(radio_altitude).get() < 30))
1564 mk_doutput(glideslope_cancel) = false;
1568 MK_VIII::IOHandler::update_egpws_alert_discrete_1 ()
1570 if (mk->voice_player.voice)
1575 VoicePlayer::Voice *voice;
1577 { 11, mk_voice(sink_rate_pause_sink_rate) },
1578 { 12, mk_voice(pull_up) },
1579 { 13, mk_voice(terrain) },
1580 { 13, mk_voice(terrain_pause_terrain) },
1581 { 14, mk_voice(dont_sink_pause_dont_sink) },
1582 { 15, mk_voice(too_low_gear) },
1583 { 16, mk_voice(too_low_flaps) },
1584 { 17, mk_voice(too_low_terrain) },
1585 { 18, mk_voice(soft_glideslope) },
1586 { 18, mk_voice(hard_glideslope) },
1587 { 19, mk_voice(minimums_minimums) }
1590 for (size_t i = 0; i < n_elements(voices); i++)
1591 if (voices[i].voice == mk->voice_player.voice)
1593 mk_aoutput(egpws_alert_discrete_1) = 1 << voices[i].bit;
1598 mk_aoutput(egpws_alert_discrete_1) = 0;
1602 MK_VIII::IOHandler::update_egpwc_logic_discretes ()
1604 mk_aoutput(egpwc_logic_discretes) = 0;
1606 if (mk->state_handler.takeoff)
1607 mk_aoutput(egpwc_logic_discretes) |= 1 << 18;
1612 unsigned int alerts;
1614 { 13, mk_alert(TCF_TOO_LOW_TERRAIN) },
1615 { 19, mk_alert(MODE1_SINK_RATE) },
1616 { 20, mk_alert(MODE1_PULL_UP) },
1617 { 21, mk_alert(MODE2A_PREFACE) | mk_alert(MODE2B_PREFACE) | mk_alert(MODE2B_LANDING_MODE) | mk_alert(MODE2A_ALTITUDE_GAIN_TERRAIN_CLOSING) },
1618 { 22, mk_alert(MODE2A) | mk_alert(MODE2B) },
1619 { 23, mk_alert(MODE3) },
1620 { 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) },
1621 { 25, mk_alert(MODE5_SOFT) | mk_alert(MODE5_HARD) }
1624 for (size_t i = 0; i < n_elements(logic); i++)
1625 if (mk_test_alerts(logic[i].alerts))
1626 mk_aoutput(egpwc_logic_discretes) |= 1 << logic[i].bit;
1630 MK_VIII::IOHandler::update_mode6_callouts_discrete_1 ()
1632 if (mk->voice_player.voice)
1637 VoicePlayer::Voice *voice;
1639 { 11, mk_voice(minimums_minimums) },
1640 { 16, mk_altitude_voice(Mode6Handler::ALTITUDE_CALLOUT_10) },
1641 { 17, mk_altitude_voice(Mode6Handler::ALTITUDE_CALLOUT_20) },
1642 { 18, mk_altitude_voice(Mode6Handler::ALTITUDE_CALLOUT_30) },
1643 { 19, mk_altitude_voice(Mode6Handler::ALTITUDE_CALLOUT_40) },
1644 { 20, mk_altitude_voice(Mode6Handler::ALTITUDE_CALLOUT_50) },
1645 { 23, mk_altitude_voice(Mode6Handler::ALTITUDE_CALLOUT_100) },
1646 { 24, mk_altitude_voice(Mode6Handler::ALTITUDE_CALLOUT_200) },
1647 { 25, mk_altitude_voice(Mode6Handler::ALTITUDE_CALLOUT_300) }
1650 for (size_t i = 0; i < n_elements(voices); i++)
1651 if (voices[i].voice == mk->voice_player.voice)
1653 mk_aoutput(mode6_callouts_discrete_1) = 1 << voices[i].bit;
1658 mk_aoutput(mode6_callouts_discrete_1) = 0;
1662 MK_VIII::IOHandler::update_mode6_callouts_discrete_2 ()
1664 if (mk->voice_player.voice)
1669 VoicePlayer::Voice *voice;
1671 { 11, mk_altitude_voice(Mode6Handler::ALTITUDE_CALLOUT_400) },
1672 { 12, mk_altitude_voice(Mode6Handler::ALTITUDE_CALLOUT_500) },
1673 { 13, mk_altitude_voice(Mode6Handler::ALTITUDE_CALLOUT_1000) },
1674 { 18, mk_voice(bank_angle_pause_bank_angle) },
1675 { 18, mk_voice(bank_angle_pause_bank_angle_3) },
1676 { 23, mk_voice(five_hundred_above) }
1679 for (size_t i = 0; i < n_elements(voices); i++)
1680 if (voices[i].voice == mk->voice_player.voice)
1682 mk_aoutput(mode6_callouts_discrete_2) = 1 << voices[i].bit;
1687 mk_aoutput(mode6_callouts_discrete_2) = 0;
1691 MK_VIII::IOHandler::update_egpws_alert_discrete_2 ()
1693 mk_aoutput(egpws_alert_discrete_2) = 1 << 27; // Terrain NA
1695 if (mk_doutput(glideslope_cancel))
1696 mk_aoutput(egpws_alert_discrete_2) |= 1 << 11;
1697 if (mk_doutput(gpws_alert))
1698 mk_aoutput(egpws_alert_discrete_2) |= 1 << 12;
1699 if (mk_doutput(gpws_warning))
1700 mk_aoutput(egpws_alert_discrete_2) |= 1 << 13;
1701 if (mk_doutput(gpws_inop))
1702 mk_aoutput(egpws_alert_discrete_2) |= 1 << 14;
1703 if (mk_doutput(audio_on))
1704 mk_aoutput(egpws_alert_discrete_2) |= 1 << 16;
1705 if (mk_doutput(tad_inop))
1706 mk_aoutput(egpws_alert_discrete_2) |= 1 << 24;
1707 if (mk->fault_handler.has_faults())
1708 mk_aoutput(egpws_alert_discrete_2) |= 1 << 25;
1712 MK_VIII::IOHandler::update_egpwc_alert_discrete_3 ()
1714 mk_aoutput(egpwc_alert_discrete_3) = 1 << 17 | 1 << 18;
1716 if (mk->fault_handler.faults[FaultHandler::FAULT_MODES14_INPUTS_INVALID])
1717 mk_aoutput(egpwc_alert_discrete_3) |= 1 << 11;
1718 if (mk->fault_handler.faults[FaultHandler::FAULT_MODE5_INPUTS_INVALID])
1719 mk_aoutput(egpwc_alert_discrete_3) |= 1 << 12;
1720 if (mk->fault_handler.faults[FaultHandler::FAULT_MODE6_INPUTS_INVALID])
1721 mk_aoutput(egpwc_alert_discrete_3) |= 1 << 13;
1722 if (mk->fault_handler.faults[FaultHandler::FAULT_BANK_ANGLE_INPUTS_INVALID])
1723 mk_aoutput(egpwc_alert_discrete_3) |= 1 << 14;
1724 if (mk_doutput(tad_inop))
1725 mk_aoutput(egpwc_alert_discrete_3) |= 1 << 16;
1729 MK_VIII::IOHandler::update_outputs ()
1731 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
1734 mk_doutput(audio_on) = ! mk_dinput(audio_inhibit)
1735 && mk->voice_player.voice
1736 && ! mk->voice_player.voice->element->silence;
1738 update_egpws_alert_discrete_1();
1739 update_egpwc_logic_discretes();
1740 update_mode6_callouts_discrete_1();
1741 update_mode6_callouts_discrete_2();
1742 update_egpws_alert_discrete_2();
1743 update_egpwc_alert_discrete_3();
1747 MK_VIII::IOHandler::get_lamp_output (Lamp lamp)
1751 case LAMP_GLIDESLOPE:
1752 return &mk_doutput(gpws_alert);
1755 return conf.lamp->format2 ? &mk_doutput(gpws_alert) : &mk_doutput(gpws_warning);
1758 return &mk_doutput(gpws_warning);
1761 assert_not_reached();
1767 MK_VIII::IOHandler::update_lamps ()
1769 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
1772 if (_lamp != LAMP_NONE && conf.lamp->flashing)
1774 // [SPEC] 6.9.3: 70 cycles per minute
1775 if (lamp_timer.elapsed() >= 60.0 / 70.0 / 2.0)
1777 bool *output = get_lamp_output(_lamp);
1778 *output = ! *output;
1785 MK_VIII::IOHandler::set_lamp (Lamp lamp)
1792 mk_doutput(gpws_warning) = false;
1793 mk_doutput(gpws_alert) = false;
1795 if (lamp != LAMP_NONE)
1797 *get_lamp_output(lamp) = true;
1803 MK_VIII::IOHandler::gpws_inhibit () const
1805 return conf.gpws_inhibit_enabled && mk_dinput(gpws_inhibit);
1809 MK_VIII::IOHandler::real_flaps_down () const
1811 return conf.flap_reversal ? mk_dinput(landing_flaps) : ! mk_dinput(landing_flaps);
1815 MK_VIII::IOHandler::flaps_down () const
1817 return flap_override() ? true : real_flaps_down();
1821 MK_VIII::IOHandler::flap_override () const
1823 return conf.momentary_flap_override_enabled ? mk_doutput(flap_override) : false;
1827 MK_VIII::IOHandler::steep_approach () const
1829 if (conf.steep_approach_enabled)
1830 // If alternate action was configured in category 13, we return
1831 // the value of the input discrete (which should be connected to a
1832 // latching steep approach cockpit switch). Otherwise, we return
1833 // the value of the output discrete.
1834 return conf.alternate_steep_approach ? mk_dinput(steep_approach) : mk_doutput(steep_approach);
1840 MK_VIII::IOHandler::momentary_steep_approach_enabled () const
1842 return conf.steep_approach_enabled && ! conf.alternate_steep_approach;
1846 MK_VIII::IOHandler::tie_input (SGPropertyNode *node,
1851 mk->properties_handler.tie(node, (string("inputs/discretes/") + name).c_str(),
1852 FGVoicePlayer::RawValueMethodsData<MK_VIII::IOHandler, bool, bool *>(*this, input, &MK_VIII::IOHandler::get_discrete_input, &MK_VIII::IOHandler::set_discrete_input));
1854 mk->properties_handler.tie(node, (string("input-feeders/discretes/") + name).c_str(), SGRawValuePointer<bool>(feed));
1858 MK_VIII::IOHandler::tie_input (SGPropertyNode *node,
1860 Parameter<double> *input,
1863 mk->properties_handler.tie(node, (string("inputs/arinc429/") + name).c_str(), SGRawValuePointer<double>(input->get_pointer()));
1864 mk->properties_handler.tie(node, (string("inputs/arinc429/") + name + "-ncd").c_str(), SGRawValuePointer<bool>(&input->ncd));
1866 mk->properties_handler.tie(node, (string("input-feeders/arinc429/") + name).c_str(), SGRawValuePointer<bool>(feed));
1870 MK_VIII::IOHandler::tie_output (SGPropertyNode *node,
1874 SGPropertyNode *child = node->getNode((string("outputs/discretes/") + name).c_str(), true);
1876 mk->properties_handler.tie(child, SGRawValuePointer<bool>(output));
1877 child->setAttribute(SGPropertyNode::WRITE, false);
1881 MK_VIII::IOHandler::tie_output (SGPropertyNode *node,
1885 SGPropertyNode *child = node->getNode((string("outputs/arinc429/") + name).c_str(), true);
1887 mk->properties_handler.tie(child, SGRawValuePointer<int>(output));
1888 child->setAttribute(SGPropertyNode::WRITE, false);
1892 MK_VIII::IOHandler::bind (SGPropertyNode *node)
1894 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));
1896 tie_input(node, "landing-gear", &mk_dinput(landing_gear), &mk_dinput_feed(landing_gear));
1897 tie_input(node, "landing-flaps", &mk_dinput(landing_flaps), &mk_dinput_feed(landing_flaps));
1898 tie_input(node, "momentary-flap-override", &mk_dinput(momentary_flap_override));
1899 tie_input(node, "self-test", &mk_dinput(self_test));
1900 tie_input(node, "glideslope-inhibit", &mk_dinput(glideslope_inhibit), &mk_dinput_feed(glideslope_inhibit));
1901 tie_input(node, "glideslope-cancel", &mk_dinput(glideslope_cancel));
1902 tie_input(node, "decision-height", &mk_dinput(decision_height), &mk_dinput_feed(decision_height));
1903 tie_input(node, "mode6-low-volume", &mk_dinput(mode6_low_volume));
1904 tie_input(node, "audio-inhibit", &mk_dinput(audio_inhibit));
1905 tie_input(node, "ta-tcf-inhibit", &mk_dinput(ta_tcf_inhibit));
1906 tie_input(node, "autopilot-engaged", &mk_dinput(autopilot_engaged), &mk_dinput_feed(autopilot_engaged));
1907 tie_input(node, "steep-approach", &mk_dinput(steep_approach));
1908 tie_input(node, "gpws-inhibit", &mk_dinput(gpws_inhibit));
1910 tie_input(node, "uncorrected-barometric-altitude", &mk_ainput(uncorrected_barometric_altitude), &mk_ainput_feed(uncorrected_barometric_altitude));
1911 tie_input(node, "barometric-altitude-rate", &mk_ainput(barometric_altitude_rate), &mk_ainput_feed(barometric_altitude_rate));
1912 tie_input(node, "gps-altitude", &mk_ainput(gps_altitude));
1913 tie_input(node, "gps-latitude", &mk_ainput(gps_latitude));
1914 tie_input(node, "gps-longitude", &mk_ainput(gps_longitude));
1915 tie_input(node, "gps-vertical-figure-of-merit", &mk_ainput(gps_vertical_figure_of_merit));
1916 tie_input(node, "radio-altitude", &mk_ainput(radio_altitude), &mk_ainput_feed(radio_altitude));
1917 tie_input(node, "glideslope-deviation", &mk_ainput(glideslope_deviation), &mk_ainput_feed(glideslope_deviation));
1918 tie_input(node, "roll-angle", &mk_ainput(roll_angle), &mk_ainput_feed(roll_angle));
1919 tie_input(node, "localizer-deviation", &mk_ainput(localizer_deviation), &mk_ainput_feed(localizer_deviation));
1920 tie_input(node, "computed-airspeed", &mk_ainput(computed_airspeed), &mk_ainput_feed(computed_airspeed));
1921 tie_input(node, "decision-height", &mk_ainput(decision_height), &mk_ainput_feed(decision_height));
1923 tie_output(node, "gpws-warning", &mk_doutput(gpws_warning));
1924 tie_output(node, "gpws-alert", &mk_doutput(gpws_alert));
1925 tie_output(node, "audio-on", &mk_doutput(audio_on));
1926 tie_output(node, "gpws-inop", &mk_doutput(gpws_inop));
1927 tie_output(node, "tad-inop", &mk_doutput(tad_inop));
1928 tie_output(node, "flap-override", &mk_doutput(flap_override));
1929 tie_output(node, "glideslope-cancel", &mk_doutput(glideslope_cancel));
1930 tie_output(node, "steep-approach", &mk_doutput(steep_approach));
1932 tie_output(node, "egpws-alert-discrete-1", &mk_aoutput(egpws_alert_discrete_1));
1933 tie_output(node, "egpwc-logic-discretes", &mk_aoutput(egpwc_logic_discretes));
1934 tie_output(node, "mode6-callouts-discrete-1", &mk_aoutput(mode6_callouts_discrete_1));
1935 tie_output(node, "mode6-callouts-discrete-2", &mk_aoutput(mode6_callouts_discrete_2));
1936 tie_output(node, "egpws-alert-discrete-2", &mk_aoutput(egpws_alert_discrete_2));
1937 tie_output(node, "egpwc-alert-discrete-3", &mk_aoutput(egpwc_alert_discrete_3));
1941 MK_VIII::IOHandler::get_discrete_input (bool *ptr) const
1947 MK_VIII::IOHandler::set_discrete_input (bool *ptr, bool value)
1952 if (mk->system_handler.state == SystemHandler::STATE_ON)
1954 if (ptr == &mk_dinput(momentary_flap_override))
1956 if (mk->configuration_module.state == ConfigurationModule::STATE_OK
1957 && mk->self_test_handler.state == SelfTestHandler::STATE_NONE
1958 && conf.momentary_flap_override_enabled
1960 mk_doutput(flap_override) = ! mk_doutput(flap_override);
1962 else if (ptr == &mk_dinput(self_test))
1963 mk->self_test_handler.handle_button_event(value);
1964 else if (ptr == &mk_dinput(glideslope_cancel))
1966 if (mk->configuration_module.state == ConfigurationModule::STATE_OK
1967 && mk->self_test_handler.state == SelfTestHandler::STATE_NONE
1970 if (! mk_doutput(glideslope_cancel))
1974 // Although we are not called from update(), we are
1975 // sure that the inputs we use here are defined,
1976 // since state is STATE_ON.
1978 if (! mk_data(glideslope_deviation_dots).ncd
1979 && ! mk_data(radio_altitude).ncd
1980 && mk_data(radio_altitude).get() <= 2000
1981 && mk_data(radio_altitude).get() >= 30)
1982 mk_doutput(glideslope_cancel) = true;
1984 // else do nothing ([PILOT] page 22: "Glideslope Cancel
1985 // can not be deselected (reset) by again pressing the
1986 // Glideslope Cancel switch.")
1989 else if (ptr == &mk_dinput(steep_approach))
1991 if (mk->configuration_module.state == ConfigurationModule::STATE_OK
1992 && mk->self_test_handler.state == SelfTestHandler::STATE_NONE
1993 && momentary_steep_approach_enabled()
1995 mk_doutput(steep_approach) = ! mk_doutput(steep_approach);
2001 if (mk->system_handler.state == SystemHandler::STATE_ON)
2002 update_alternate_discrete_input(ptr);
2006 MK_VIII::IOHandler::present_status_section (const char *name)
2008 printf("%s\n", name);
2012 MK_VIII::IOHandler::present_status_item (const char *name, const char *value)
2015 printf("\t%-32s %s\n", name, value);
2017 printf("\t%s\n", name);
2021 MK_VIII::IOHandler::present_status_subitem (const char *name)
2023 printf("\t\t%s\n", name);
2027 MK_VIII::IOHandler::present_status ()
2031 if (mk->system_handler.state != SystemHandler::STATE_ON)
2034 present_status_section("EGPWC CONFIGURATION");
2035 present_status_item("PART NUMBER:", "965-1220-000"); // [SPEC] 1.1
2036 present_status_item("MOD STATUS:", "N/A");
2037 present_status_item("SERIAL NUMBER:", "N/A");
2039 present_status_item("APPLICATION S/W VERSION:", FLIGHTGEAR_VERSION);
2040 present_status_item("TERRAIN DATABASE VERSION:", FLIGHTGEAR_VERSION);
2041 present_status_item("ENVELOPE MOD DATABASE VERSION:", "N/A");
2042 present_status_item("BOOT CODE VERSION:", FLIGHTGEAR_VERSION);
2045 present_status_section("CURRENT FAULTS");
2046 if (mk->configuration_module.state == ConfigurationModule::STATE_OK && ! mk->fault_handler.has_faults())
2047 present_status_item("NO FAULTS");
2050 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
2052 present_status_item("GPWS COMPUTER FAULTS:");
2053 switch (mk->configuration_module.state)
2055 case ConfigurationModule::STATE_INVALID_DATABASE:
2056 present_status_subitem("APPLICATION DATABASE FAILED");
2059 case ConfigurationModule::STATE_INVALID_AIRCRAFT_TYPE:
2060 present_status_subitem("CONFIGURATION TYPE INVALID");
2064 assert_not_reached();
2070 present_status_item("GPWS COMPUTER OK");
2071 present_status_item("GPWS EXTERNAL FAULTS:");
2073 static const char *fault_names[] = {
2074 "ALL MODES INHIBIT",
2077 "MOMENTARY FLAP OVERRIDE INVALID",
2078 "SELF TEST INVALID",
2079 "GLIDESLOPE CANCEL INVALID",
2080 "STEEP APPROACH INVALID",
2083 "MODES 1-4 INPUTS INVALID",
2084 "MODE 5 INPUTS INVALID",
2085 "MODE 6 INPUTS INVALID",
2086 "BANK ANGLE INPUTS INVALID",
2087 "TCF INPUTS INVALID"
2090 for (size_t i = 0; i < n_elements(fault_names); i++)
2091 if (mk->fault_handler.faults[i])
2092 present_status_subitem(fault_names[i]);
2097 present_status_section("CONFIGURATION:");
2099 static const char *category_names[] = {
2102 "POSITION INPUT TYPE",
2103 "CALLOUTS OPTION TYPE",
2105 "TERRAIN DISPLAY TYPE",
2107 "RADIO ALTITUDE TYPE",
2108 "NAVIGATION INPUT TYPE",
2110 "MAGNETIC HEADING TYPE",
2111 "WINDSHEAR INPUT TYPE",
2116 for (size_t i = 0; i < n_elements(category_names); i++)
2118 std::ostringstream value;
2119 value << "= " << mk->configuration_module.effective_categories[i];
2120 present_status_item(category_names[i], value.str().c_str());
2125 MK_VIII::IOHandler::get_present_status () const
2131 MK_VIII::IOHandler::set_present_status (bool value)
2133 if (value) present_status();
2137 ///////////////////////////////////////////////////////////////////////////////
2138 // MK_VIII::VoicePlayer ///////////////////////////////////////////////////////
2139 ///////////////////////////////////////////////////////////////////////////////
2142 MK_VIII::VoicePlayer::init ()
2144 FGVoicePlayer::init();
2146 #define STDPAUSE 0.75 // [SPEC] 6.4.4: "the standard 0.75 second delay"
2147 make_voice(&voices.application_data_base_failed, "application-data-base-failed");
2148 make_voice(&voices.bank_angle, "bank-angle");
2149 make_voice(&voices.bank_angle_bank_angle, "bank-angle", "bank-angle");
2150 make_voice(&voices.bank_angle_bank_angle_3, "bank-angle", "bank-angle", 3.0);
2151 make_voice(&voices.bank_angle_inop, "bank-angle-inop");
2152 make_voice(&voices.bank_angle_pause_bank_angle, "bank-angle", STDPAUSE, "bank-angle");
2153 make_voice(&voices.bank_angle_pause_bank_angle_3, "bank-angle", STDPAUSE, "bank-angle", 3.0);
2154 make_voice(&voices.callouts_inop, "callouts-inop");
2155 make_voice(&voices.configuration_type_invalid, "configuration-type-invalid");
2156 make_voice(&voices.dont_sink, "dont-sink");
2157 make_voice(&voices.dont_sink_pause_dont_sink, "dont-sink", STDPAUSE, "dont-sink");
2158 make_voice(&voices.five_hundred_above, "500-above");
2159 make_voice(&voices.glideslope, "glideslope");
2160 make_voice(&voices.glideslope_inop, "glideslope-inop");
2161 make_voice(&voices.gpws_inop, "gpws-inop");
2162 make_voice(&voices.hard_glideslope, "glideslope", "glideslope", 3.0);
2163 make_voice(&voices.minimums, "minimums");
2164 make_voice(&voices.minimums_minimums, "minimums", "minimums");
2165 make_voice(&voices.pull_up, "pull-up");
2166 make_voice(&voices.sink_rate, "sink-rate");
2167 make_voice(&voices.sink_rate_pause_sink_rate, "sink-rate", STDPAUSE, "sink-rate");
2168 make_voice(&voices.soft_glideslope, new Voice::SampleElement(get_sample("glideslope"), modify_amplitude(1.0, -6)));
2169 make_voice(&voices.terrain, "terrain");
2170 make_voice(&voices.terrain_pause_terrain, "terrain", STDPAUSE, "terrain");
2171 make_voice(&voices.too_low_flaps, "too-low-flaps");
2172 make_voice(&voices.too_low_gear, "too-low-gear");
2173 make_voice(&voices.too_low_terrain, "too-low-terrain");
2175 for (unsigned i = 0; i < n_altitude_callouts; i++)
2177 std::ostringstream name;
2178 name << "altitude-" << MK_VIII::Mode6Handler::altitude_callout_definitions[i];
2179 make_voice(&voices.altitude_callouts[i], name.str().c_str());
2181 speaker.update_configuration();
2184 ///////////////////////////////////////////////////////////////////////////////
2185 // SelfTestHandler ////////////////////////////////////////////////////////////
2186 ///////////////////////////////////////////////////////////////////////////////
2189 MK_VIII::SelfTestHandler::_was_here (int position)
2191 if (position > current)
2200 MK_VIII::SelfTestHandler::Action
2201 MK_VIII::SelfTestHandler::sleep (double duration)
2203 Action _action = { ACTION_SLEEP, duration, NULL };
2207 MK_VIII::SelfTestHandler::Action
2208 MK_VIII::SelfTestHandler::play (VoicePlayer::Voice *voice)
2210 mk->voice_player.play(voice);
2211 Action _action = { ACTION_VOICE, 0, NULL };
2215 MK_VIII::SelfTestHandler::Action
2216 MK_VIII::SelfTestHandler::discrete_on (bool *discrete, double duration)
2219 return sleep(duration);
2222 MK_VIII::SelfTestHandler::Action
2223 MK_VIII::SelfTestHandler::discrete_on_off (bool *discrete, double duration)
2226 Action _action = { ACTION_SLEEP | ACTION_DISCRETE_ON_OFF, duration, discrete };
2230 MK_VIII::SelfTestHandler::Action
2231 MK_VIII::SelfTestHandler::discrete_on_off (bool *discrete, VoicePlayer::Voice *voice)
2234 mk->voice_player.play(voice);
2235 Action _action = { ACTION_VOICE | ACTION_DISCRETE_ON_OFF, 0, discrete };
2239 MK_VIII::SelfTestHandler::Action
2240 MK_VIII::SelfTestHandler::done ()
2242 Action _action = { ACTION_DONE, 0, NULL };
2246 MK_VIII::SelfTestHandler::Action
2247 MK_VIII::SelfTestHandler::run ()
2249 // Note that "Terrain INOP" and "Terrain NA" are or'ed to the same
2250 // output discrete (see [SPEC] 6.9.3.5).
2252 #define was_here() was_here_offset(0)
2253 #define was_here_offset(offset) _was_here(__LINE__ * 20 + (offset))
2257 if (mk->configuration_module.state == ConfigurationModule::STATE_INVALID_DATABASE)
2258 return play(mk_voice(application_data_base_failed));
2259 else if (mk->configuration_module.state == ConfigurationModule::STATE_INVALID_AIRCRAFT_TYPE)
2260 return play(mk_voice(configuration_type_invalid));
2262 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
2266 return discrete_on(&mk_doutput(gpws_inop), 0.7);
2268 return sleep(0.7); // W/S INOP
2270 return discrete_on(&mk_doutput(tad_inop), 0.7);
2273 mk_doutput(gpws_inop) = false;
2274 // do not disable tad_inop since we must enable Terrain NA
2275 // do not disable W/S INOP since we do not emulate it
2276 return sleep(0.7); // Terrain NA
2280 mk_doutput(tad_inop) = false; // disable Terrain NA
2281 if (mk->io_handler.conf.momentary_flap_override_enabled)
2282 return discrete_on_off(&mk_doutput(flap_override), 1.0);
2285 return discrete_on_off(&mk_doutput(audio_on), 1.0);
2288 if (mk->io_handler.momentary_steep_approach_enabled())
2289 return discrete_on_off(&mk_doutput(steep_approach), 1.0);
2294 if (mk_dinput(glideslope_inhibit))
2295 goto glideslope_end;
2298 if (mk->fault_handler.faults[FaultHandler::FAULT_MODE5_INPUTS_INVALID])
2299 goto glideslope_inop;
2303 return discrete_on_off(&mk_doutput(gpws_alert), mk_voice(glideslope));
2305 return discrete_on_off(&mk_doutput(glideslope_cancel), 0.7);
2306 goto glideslope_end;
2309 return play(mk_voice(glideslope_inop));
2314 if (mk->fault_handler.faults[FaultHandler::FAULT_MODES14_INPUTS_INVALID])
2318 return discrete_on_off(&mk_doutput(gpws_warning), mk_voice(pull_up));
2322 return play(mk_voice(gpws_inop));
2327 if (mk_dinput(self_test)) // proceed to long self test
2332 if (mk->mode6_handler.conf.bank_angle_enabled
2333 && mk->fault_handler.faults[FaultHandler::FAULT_BANK_ANGLE_INPUTS_INVALID])
2334 return play(mk_voice(bank_angle_inop));
2338 if (mk->mode6_handler.altitude_callouts_enabled()
2339 && mk->fault_handler.faults[FaultHandler::FAULT_MODE6_INPUTS_INVALID])
2340 return play(mk_voice(callouts_inop));
2348 mk_doutput(gpws_inop) = true;
2349 // do not enable W/S INOP, since we do not emulate it
2350 mk_doutput(tad_inop) = true; // Terrain INOP, Terrain NA
2352 return play(mk_voice(sink_rate));
2355 return play(mk_voice(pull_up));
2357 return play(mk_voice(terrain));
2359 return play(mk_voice(pull_up));
2361 return play(mk_voice(dont_sink));
2363 return play(mk_voice(too_low_terrain));
2365 return play(mk->mode4_handler.conf.voice_too_low_gear);
2367 return play(mk_voice(too_low_flaps));
2369 return play(mk_voice(too_low_terrain));
2371 return play(mk_voice(glideslope));
2374 if (mk->mode6_handler.conf.bank_angle_enabled)
2375 return play(mk_voice(bank_angle));
2380 if (! mk->mode6_handler.altitude_callouts_enabled())
2381 goto callouts_disabled;
2385 if (mk->mode6_handler.conf.minimums_enabled)
2386 return play(mk_voice(minimums));
2390 if (mk->mode6_handler.conf.above_field_voice)
2391 return play(mk->mode6_handler.conf.above_field_voice);
2393 for (unsigned i = 0; i < n_altitude_callouts; i++)
2394 if (! was_here_offset(i))
2396 if (mk->mode6_handler.conf.altitude_callouts_enabled[i])
2397 return play(mk_altitude_voice(i));
2401 if (mk->mode6_handler.conf.smart_500_enabled)
2402 return play(mk_altitude_voice(Mode6Handler::ALTITUDE_CALLOUT_500));
2407 return play(mk_voice(minimums_minimums));
2412 if (mk->tcf_handler.conf.enabled)
2413 return play(mk_voice(too_low_terrain));
2420 MK_VIII::SelfTestHandler::start ()
2422 assert(state == STATE_START);
2424 memcpy(&saved_outputs, &mk->io_handler.outputs, sizeof(mk->io_handler.outputs));
2425 memset(&mk->io_handler.outputs, 0, sizeof(mk->io_handler.outputs));
2427 // [SPEC] 6.10.6: "The self-test results are annunciated, at 6db
2428 // lower than the normal audio level selected for the aircraft"
2429 mk->voice_player.set_volume(modify_amplitude(mk->voice_player.conf.volume, -6));
2431 if (mk_dinput(mode6_low_volume))
2432 mk->mode6_handler.set_volume(1.0);
2434 state = STATE_RUNNING;
2435 cancel = CANCEL_NONE;
2436 memset(&action, 0, sizeof(action));
2441 MK_VIII::SelfTestHandler::stop ()
2443 if (state != STATE_NONE)
2445 if (state != STATE_START)
2447 mk->voice_player.stop(VoicePlayer::STOP_NOW);
2448 mk->voice_player.set_volume(mk_dinput(audio_inhibit) ? 0.0 : mk->voice_player.conf.volume);
2450 if (mk_dinput(mode6_low_volume))
2451 mk->mode6_handler.set_volume(modify_amplitude(1.0, -6));
2453 memcpy(&mk->io_handler.outputs, &saved_outputs, sizeof(mk->io_handler.outputs));
2456 button_pressed = false;
2458 // reset self-test handler position
2464 MK_VIII::SelfTestHandler::handle_button_event (bool value)
2466 if (state == STATE_NONE)
2469 state = STATE_START;
2471 else if (state == STATE_START)
2474 state = STATE_NONE; // cancel the not-yet-started test
2476 else if (cancel == CANCEL_NONE)
2480 assert(! button_pressed);
2481 button_pressed = true;
2482 button_press_timestamp = globals->get_sim_time_sec();
2488 if (globals->get_sim_time_sec() - button_press_timestamp < 2)
2489 cancel = CANCEL_SHORT;
2491 cancel = CANCEL_LONG;
2498 MK_VIII::SelfTestHandler::update ()
2500 if (state == STATE_NONE)
2502 else if (state == STATE_START)
2504 if (mk->state_handler.ground && ! mk->io_handler.steep_approach())
2514 if (button_pressed && cancel == CANCEL_NONE)
2516 if (globals->get_sim_time_sec() - button_press_timestamp >= 2)
2517 cancel = CANCEL_LONG;
2521 if (! mk->state_handler.ground || cancel != CANCEL_NONE)
2527 if (test_bits(action.flags, ACTION_SLEEP))
2529 if (globals->get_sim_time_sec() - sleep_start < action.sleep_duration)
2532 if (test_bits(action.flags, ACTION_VOICE))
2534 if (mk->voice_player.voice)
2537 if (test_bits(action.flags, ACTION_DISCRETE_ON_OFF))
2538 *action.discrete = false;
2542 if (test_bits(action.flags, ACTION_SLEEP))
2543 sleep_start = globals->get_sim_time_sec();
2544 if (test_bits(action.flags, ACTION_DONE))
2553 ///////////////////////////////////////////////////////////////////////////////
2554 // AlertHandler ///////////////////////////////////////////////////////////////
2555 ///////////////////////////////////////////////////////////////////////////////
2558 MK_VIII::AlertHandler::select_voice_alerts (unsigned int test)
2560 if (has_alerts(test))
2562 voice_alerts = alerts & test;
2567 voice_alerts &= ~test;
2568 if (voice_alerts == 0)
2569 mk->voice_player.stop();
2576 MK_VIII::AlertHandler::boot ()
2582 MK_VIII::AlertHandler::reposition ()
2586 mk->io_handler.set_lamp(IOHandler::LAMP_NONE);
2587 mk->voice_player.stop(VoicePlayer::STOP_NOW);
2591 MK_VIII::AlertHandler::reset ()
2596 repeated_alerts = 0;
2597 altitude_callout_voice = NULL;
2601 MK_VIII::AlertHandler::update ()
2603 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
2606 // Lamps and voices are prioritized according to [SPEC] 6.9.2.
2608 // Voices which can interrupt other voices (VoicePlayer::PLAY_NOW)
2609 // are specified by [INSTALL] appendix E 5.3.5.
2611 // When a voice is overriden by a higher priority voice and the
2612 // overriding voice stops, we restore the previous voice if it was
2613 // looped (this is not specified by [SPEC] but seems to make sense).
2617 if (has_alerts(ALERT_MODE1_PULL_UP | ALERT_MODE2A | ALERT_MODE2B))
2618 mk->io_handler.set_lamp(IOHandler::LAMP_WARNING);
2619 else if (has_alerts(ALERT_MODE1_SINK_RATE
2620 | ALERT_MODE2A_PREFACE
2621 | ALERT_MODE2B_PREFACE
2622 | ALERT_MODE2A_ALTITUDE_GAIN_TERRAIN_CLOSING
2623 | ALERT_MODE2A_ALTITUDE_GAIN
2624 | ALERT_MODE2B_LANDING_MODE
2626 | ALERT_MODE4_TOO_LOW_FLAPS
2627 | ALERT_MODE4_TOO_LOW_GEAR
2628 | ALERT_MODE4AB_TOO_LOW_TERRAIN
2629 | ALERT_MODE4C_TOO_LOW_TERRAIN
2630 | ALERT_TCF_TOO_LOW_TERRAIN))
2631 mk->io_handler.set_lamp(IOHandler::LAMP_CAUTION);
2632 else if (has_alerts(ALERT_MODE5_SOFT | ALERT_MODE5_HARD))
2633 mk->io_handler.set_lamp(IOHandler::LAMP_GLIDESLOPE);
2635 mk->io_handler.set_lamp(IOHandler::LAMP_NONE);
2639 if (select_voice_alerts(ALERT_MODE1_PULL_UP))
2641 if (! has_old_alerts(ALERT_MODE1_PULL_UP))
2643 if (mk->voice_player.voice != mk_voice(sink_rate_pause_sink_rate))
2644 mk->voice_player.play(mk_voice(sink_rate), VoicePlayer::PLAY_NOW);
2645 mk->voice_player.play(mk_voice(pull_up), VoicePlayer::PLAY_LOOPED);
2648 else if (select_voice_alerts(ALERT_MODE2A_PREFACE | ALERT_MODE2B_PREFACE))
2650 if (! has_old_alerts(ALERT_MODE2A_PREFACE | ALERT_MODE2B_PREFACE))
2651 mk->voice_player.play(mk_voice(terrain_pause_terrain), VoicePlayer::PLAY_NOW);
2653 else if (select_voice_alerts(ALERT_MODE2A | ALERT_MODE2B))
2655 if (mk->voice_player.voice != mk_voice(pull_up))
2656 mk->voice_player.play(mk_voice(pull_up), VoicePlayer::PLAY_NOW | VoicePlayer::PLAY_LOOPED);
2658 else if (select_voice_alerts(ALERT_MODE2A_ALTITUDE_GAIN_TERRAIN_CLOSING | ALERT_MODE2B_LANDING_MODE))
2660 if (mk->voice_player.voice != mk_voice(terrain))
2661 mk->voice_player.play(mk_voice(terrain), VoicePlayer::PLAY_LOOPED);
2663 else if (select_voice_alerts(ALERT_MODE6_MINIMUMS))
2665 if (! has_old_alerts(ALERT_MODE6_MINIMUMS))
2666 mk->voice_player.play(mk_voice(minimums_minimums));
2668 else if (select_voice_alerts(ALERT_MODE4AB_TOO_LOW_TERRAIN | ALERT_MODE4C_TOO_LOW_TERRAIN))
2670 if (must_play_voice(ALERT_MODE4AB_TOO_LOW_TERRAIN | ALERT_MODE4C_TOO_LOW_TERRAIN))
2671 mk->voice_player.play(mk_voice(too_low_terrain));
2673 else if (select_voice_alerts(ALERT_TCF_TOO_LOW_TERRAIN))
2675 if (must_play_voice(ALERT_TCF_TOO_LOW_TERRAIN))
2676 mk->voice_player.play(mk_voice(too_low_terrain));
2678 else if (select_voice_alerts(ALERT_MODE6_ALTITUDE_CALLOUT))
2680 if (! has_old_alerts(ALERT_MODE6_ALTITUDE_CALLOUT))
2682 assert(altitude_callout_voice != NULL);
2683 mk->voice_player.play(altitude_callout_voice);
2684 altitude_callout_voice = NULL;
2687 else if (select_voice_alerts(ALERT_MODE4_TOO_LOW_GEAR))
2689 if (must_play_voice(ALERT_MODE4_TOO_LOW_GEAR))
2690 mk->voice_player.play(mk->mode4_handler.conf.voice_too_low_gear);
2692 else if (select_voice_alerts(ALERT_MODE4_TOO_LOW_FLAPS))
2694 if (must_play_voice(ALERT_MODE4_TOO_LOW_FLAPS))
2695 mk->voice_player.play(mk_voice(too_low_flaps));
2697 else if (select_voice_alerts(ALERT_MODE1_SINK_RATE))
2699 if (must_play_voice(ALERT_MODE1_SINK_RATE))
2700 mk->voice_player.play(mk_voice(sink_rate_pause_sink_rate));
2701 // [SPEC] 6.2.1: "During the time that the voice message for the
2702 // outer envelope is inhibited and the caution/warning lamp is
2703 // on, the Mode 5 alert message is allowed to annunciate for
2704 // excessive glideslope deviation conditions."
2705 else if (mk->voice_player.voice != mk_voice(sink_rate_pause_sink_rate)
2706 && mk->voice_player.next_voice != mk_voice(sink_rate_pause_sink_rate))
2708 if (has_alerts(ALERT_MODE5_HARD))
2710 voice_alerts |= ALERT_MODE5_HARD;
2711 if (mk->voice_player.voice != mk_voice(hard_glideslope))
2712 mk->voice_player.play(mk_voice(hard_glideslope), VoicePlayer::PLAY_LOOPED);
2714 else if (has_alerts(ALERT_MODE5_SOFT))
2716 voice_alerts |= ALERT_MODE5_SOFT;
2717 if (must_play_voice(ALERT_MODE5_SOFT))
2718 mk->voice_player.play(mk_voice(soft_glideslope));
2722 else if (select_voice_alerts(ALERT_MODE3))
2724 if (must_play_voice(ALERT_MODE3))
2725 mk->voice_player.play(mk_voice(dont_sink_pause_dont_sink));
2727 else if (select_voice_alerts(ALERT_MODE5_HARD))
2729 if (mk->voice_player.voice != mk_voice(hard_glideslope))
2730 mk->voice_player.play(mk_voice(hard_glideslope), VoicePlayer::PLAY_LOOPED);
2732 else if (select_voice_alerts(ALERT_MODE5_SOFT))
2734 if (must_play_voice(ALERT_MODE5_SOFT))
2735 mk->voice_player.play(mk_voice(soft_glideslope));
2737 else if (select_voice_alerts(ALERT_MODE6_LOW_BANK_ANGLE_3))
2739 if (mk->voice_player.voice != mk_voice(bank_angle_bank_angle_3))
2740 mk->voice_player.play(mk_voice(bank_angle_bank_angle_3), VoicePlayer::PLAY_LOOPED);
2742 else if (select_voice_alerts(ALERT_MODE6_HIGH_BANK_ANGLE_3))
2744 if (mk->voice_player.voice != mk_voice(bank_angle_pause_bank_angle_3))
2745 mk->voice_player.play(mk_voice(bank_angle_pause_bank_angle_3), VoicePlayer::PLAY_LOOPED);
2747 else if (select_voice_alerts(ALERT_MODE6_LOW_BANK_ANGLE_2))
2749 if (! has_old_alerts(ALERT_MODE6_LOW_BANK_ANGLE_2 | ALERT_MODE6_HIGH_BANK_ANGLE_2))
2750 mk->voice_player.play(mk_voice(bank_angle_bank_angle));
2752 else if (select_voice_alerts(ALERT_MODE6_HIGH_BANK_ANGLE_2))
2754 if (! has_old_alerts(ALERT_MODE6_LOW_BANK_ANGLE_2 | ALERT_MODE6_HIGH_BANK_ANGLE_2))
2755 mk->voice_player.play(mk_voice(bank_angle_pause_bank_angle));
2757 else if (select_voice_alerts(ALERT_MODE6_LOW_BANK_ANGLE_1))
2759 if (! has_old_alerts(ALERT_MODE6_LOW_BANK_ANGLE_1 | ALERT_MODE6_HIGH_BANK_ANGLE_1))
2760 mk->voice_player.play(mk_voice(bank_angle_bank_angle));
2762 else if (select_voice_alerts(ALERT_MODE6_HIGH_BANK_ANGLE_1))
2764 if (! has_old_alerts(ALERT_MODE6_LOW_BANK_ANGLE_1 | ALERT_MODE6_HIGH_BANK_ANGLE_1))
2765 mk->voice_player.play(mk_voice(bank_angle_pause_bank_angle));
2768 // remember all alerts voiced so far...
2769 old_alerts |= voice_alerts;
2770 // ... forget those no longer active
2771 old_alerts &= alerts;
2772 repeated_alerts = 0;
2776 MK_VIII::AlertHandler::set_alerts (unsigned int _alerts,
2778 VoicePlayer::Voice *_altitude_callout_voice)
2781 if (test_bits(flags, ALERT_FLAG_REPEAT))
2782 repeated_alerts |= _alerts;
2783 if (_altitude_callout_voice)
2784 altitude_callout_voice = _altitude_callout_voice;
2788 MK_VIII::AlertHandler::unset_alerts (unsigned int _alerts)
2791 repeated_alerts &= ~_alerts;
2792 if (_alerts & ALERT_MODE6_ALTITUDE_CALLOUT)
2793 altitude_callout_voice = NULL;
2796 ///////////////////////////////////////////////////////////////////////////////
2797 // StateHandler ///////////////////////////////////////////////////////////////
2798 ///////////////////////////////////////////////////////////////////////////////
2801 MK_VIII::StateHandler::update_ground ()
2805 if (! mk_ainput(computed_airspeed).ncd && mk_ainput(computed_airspeed).get() > 60
2806 && ! mk_data(radio_altitude).ncd && mk_data(radio_altitude).get() > 30)
2808 if (potentially_airborne_timer.start_or_elapsed() > 1)
2812 potentially_airborne_timer.stop();
2816 if (! mk_ainput(computed_airspeed).ncd && mk_ainput(computed_airspeed).get() < 40
2817 && ! mk_data(radio_altitude).ncd && mk_data(radio_altitude).get() < 30)
2823 MK_VIII::StateHandler::enter_ground ()
2826 mk->io_handler.enter_ground();
2828 // [SPEC] 6.0.1 does not specify this, but it seems to be an
2829 // omission; at this point, we must make sure that we are in takeoff
2830 // mode (otherwise the next takeoff might happen in approach mode).
2836 MK_VIII::StateHandler::leave_ground ()
2839 mk->mode2_handler.leave_ground();
2843 MK_VIII::StateHandler::update_takeoff ()
2847 // [SPEC] 6.0.2 is somewhat confusing: it mentions hardcoded
2848 // terrain clearance (500, 750) and airspeed (178, 200) values,
2849 // but it also mentions the mode 4A boundary, which varies as a
2850 // function of aircraft type. We consider that the mentioned
2851 // values are examples, and that we should use the mode 4A upper
2854 if (! mk_data(terrain_clearance).ncd
2855 && ! mk_ainput(computed_airspeed).ncd
2856 && mk_data(terrain_clearance).get() > mk->mode4_handler.get_upper_agl(mk->mode4_handler.conf.modes->ac))
2861 if (! mk_data(radio_altitude).ncd
2862 && mk_data(radio_altitude).get() < mk->mode4_handler.conf.modes->b->min_agl1
2863 && mk->io_handler.flaps_down()
2864 && mk_dinput(landing_gear))
2870 MK_VIII::StateHandler::enter_takeoff ()
2873 mk->io_handler.enter_takeoff();
2874 mk->mode2_handler.enter_takeoff();
2875 mk->mode3_handler.enter_takeoff();
2876 mk->mode6_handler.enter_takeoff();
2880 MK_VIII::StateHandler::leave_takeoff ()
2883 mk->mode6_handler.leave_takeoff();
2887 MK_VIII::StateHandler::post_reposition ()
2889 // Synchronize the state with the current situation.
2892 (! mk_ainput(computed_airspeed).ncd && mk_ainput(computed_airspeed).get() > 60
2893 && ! mk_data(radio_altitude).ncd && mk_data(radio_altitude).get() > 30);
2895 bool _takeoff = _ground;
2897 if (ground && ! _ground)
2899 else if (! ground && _ground)
2902 if (takeoff && ! _takeoff)
2904 else if (! takeoff && _takeoff)
2909 MK_VIII::StateHandler::update ()
2911 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
2918 ///////////////////////////////////////////////////////////////////////////////
2919 // Mode1Handler ///////////////////////////////////////////////////////////////
2920 ///////////////////////////////////////////////////////////////////////////////
2923 MK_VIII::Mode1Handler::get_pull_up_bias ()
2925 // [PILOT] page 54: "Steep Approach has priority over Flap Override
2926 // if selected simultaneously."
2928 if (mk->io_handler.steep_approach())
2930 else if (conf.envelopes->flap_override_bias && mk->io_handler.flap_override())
2937 MK_VIII::Mode1Handler::is_pull_up ()
2939 if (! mk->io_handler.gpws_inhibit()
2940 && ! mk->state_handler.ground // [1]
2941 && ! mk_data(radio_altitude).ncd
2942 && ! mk_data(barometric_altitude_rate).ncd
2943 && mk_data(radio_altitude).get() > conf.envelopes->min_agl)
2945 if (mk_data(radio_altitude).get() < conf.envelopes->pull_up_max_agl1)
2947 if (mk_data(radio_altitude).get() < conf.envelopes->pull_up_min_agl1(mk_data(barometric_altitude_rate).get() + get_pull_up_bias()))
2950 else if (mk_data(radio_altitude).get() < conf.envelopes->pull_up_max_agl2)
2952 if (mk_data(radio_altitude).get() < conf.envelopes->pull_up_min_agl2(mk_data(barometric_altitude_rate).get() + get_pull_up_bias()))
2961 MK_VIII::Mode1Handler::update_pull_up ()
2965 if (! mk_test_alert(MODE1_PULL_UP))
2967 // [SPEC] 6.2.1: at least one sink rate must be issued
2968 // before switching to pull up; if no sink rate has
2969 // occurred, a 0.2 second delay is used.
2970 if (mk->voice_player.voice == mk_voice(sink_rate_pause_sink_rate)
2971 || pull_up_timer.start_or_elapsed() >= 0.2)
2972 mk_set_alerts(mk_alert(MODE1_PULL_UP));
2977 pull_up_timer.stop();
2978 mk_unset_alerts(mk_alert(MODE1_PULL_UP));
2983 MK_VIII::Mode1Handler::get_sink_rate_bias ()
2985 // [PILOT] page 54: "Steep Approach has priority over Flap Override
2986 // if selected simultaneously."
2988 if (mk->io_handler.steep_approach())
2990 else if (conf.envelopes->flap_override_bias && mk->io_handler.flap_override())
2992 else if (! mk_data(glideslope_deviation_dots).ncd)
2996 if (mk_data(glideslope_deviation_dots).get() <= -2)
2998 else if (mk_data(glideslope_deviation_dots).get() < 0)
2999 bias = -150 * mk_data(glideslope_deviation_dots).get();
3001 if (mk_data(radio_altitude).get() < 100)
3002 bias *= 0.01 * mk_data(radio_altitude).get();
3011 MK_VIII::Mode1Handler::is_sink_rate ()
3013 return ! mk->io_handler.gpws_inhibit()
3014 && ! mk->state_handler.ground // [1]
3015 && ! mk_data(radio_altitude).ncd
3016 && ! mk_data(barometric_altitude_rate).ncd
3017 && mk_data(radio_altitude).get() > conf.envelopes->min_agl
3018 && mk_data(radio_altitude).get() < 2450
3019 && mk_data(radio_altitude).get() < -572 - 0.6035 * (mk_data(barometric_altitude_rate).get() + get_sink_rate_bias());
3023 MK_VIII::Mode1Handler::get_sink_rate_tti ()
3025 return mk_data(radio_altitude).get() / fabs(mk_data(barometric_altitude_rate).get());
3029 MK_VIII::Mode1Handler::update_sink_rate ()
3033 if (mk_test_alert(MODE1_SINK_RATE))
3035 double tti = get_sink_rate_tti();
3036 if (tti <= sink_rate_tti * 0.8)
3038 // ~20% degradation, warn again and store the new reference tti
3039 sink_rate_tti = tti;
3040 mk_repeat_alert(mk_alert(MODE1_SINK_RATE));
3045 if (sink_rate_timer.start_or_elapsed() >= 0.8)
3047 mk_set_alerts(mk_alert(MODE1_SINK_RATE));
3048 sink_rate_tti = get_sink_rate_tti();
3054 sink_rate_timer.stop();
3055 mk_unset_alerts(mk_alert(MODE1_SINK_RATE));
3060 MK_VIII::Mode1Handler::update ()
3062 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
3069 ///////////////////////////////////////////////////////////////////////////////
3070 // Mode2Handler ///////////////////////////////////////////////////////////////
3071 ///////////////////////////////////////////////////////////////////////////////
3074 MK_VIII::Mode2Handler::ClosureRateFilter::limit_radio_altitude_rate (double r)
3076 // Limit radio altitude rate according to aircraft configuration,
3077 // allowing maximum sensitivity during cruise while providing
3078 // progressively less sensitivity during the landing phases of
3081 if (! mk_data(glideslope_deviation_dots).ncd && fabs(mk_data(glideslope_deviation_dots).get()) <= 2)
3082 { // gs deviation <= +- 2 dots
3083 if (mk_dinput(landing_gear) && mk->io_handler.flaps_down())
3084 SG_CLAMP_RANGE(r, -1000.0, 3000.0);
3085 else if (mk_dinput(landing_gear) || mk->io_handler.flaps_down())
3086 SG_CLAMP_RANGE(r, 0.0, 4000.0);
3088 SG_CLAMP_RANGE(r, 1000.0, 5000.0);
3091 { // no ILS, or gs deviation > +- 2 dots
3092 if (mk_dinput(landing_gear) && mk->io_handler.flaps_down())
3093 SG_CLAMP_RANGE(r, 0.0, 4000.0);
3094 else if (mk_dinput(landing_gear) || mk->io_handler.flaps_down())
3095 SG_CLAMP_RANGE(r, 1000.0, 5000.0);
3103 MK_VIII::Mode2Handler::ClosureRateFilter::init ()
3106 last_ra.set(&mk_data(radio_altitude));
3107 last_ba.set(&mk_ainput(uncorrected_barometric_altitude));
3114 MK_VIII::Mode2Handler::ClosureRateFilter::update ()
3116 double elapsed = timer.start_or_elapsed();
3120 if (! mk_data(radio_altitude).ncd && ! mk_ainput(uncorrected_barometric_altitude).ncd)
3122 if (! last_ra.ncd && ! last_ba.ncd)
3124 // compute radio and barometric altitude rates (positive value = descent)
3125 double ra_rate = -(mk_data(radio_altitude).get() - last_ra.get()) / elapsed * 60;
3126 double ba_rate = -(mk_ainput(uncorrected_barometric_altitude).get() - last_ba.get()) / elapsed * 60;
3128 // limit radio altitude rate according to aircraft configuration
3129 ra_rate = limit_radio_altitude_rate(ra_rate);
3131 // apply a low-pass filter to the radio altitude rate
3132 ra_rate = ra_filter.filter(ra_rate);
3133 // apply a high-pass filter to the barometric altitude rate
3134 ba_rate = ba_filter.filter(ba_rate);
3136 // combine both rates to obtain a closure rate
3137 output.set(ra_rate + ba_rate);
3150 last_ra.set(&mk_data(radio_altitude));
3151 last_ba.set(&mk_ainput(uncorrected_barometric_altitude));
3155 MK_VIII::Mode2Handler::b_conditions ()
3157 return mk->io_handler.flaps_down()
3158 || (! mk_data(glideslope_deviation_dots).ncd && fabs(mk_data(glideslope_deviation_dots).get()) < 2)
3159 || takeoff_timer.running;
3163 MK_VIII::Mode2Handler::is_a ()
3165 if (! mk->io_handler.gpws_inhibit()
3166 && ! mk->state_handler.ground // [1]
3167 && ! mk_data(radio_altitude).ncd
3168 && ! mk_ainput(computed_airspeed).ncd
3169 && ! closure_rate_filter.output.ncd
3170 && ! b_conditions())
3172 if (mk_data(radio_altitude).get() < 1220)
3174 if (mk_data(radio_altitude).get() < -1579 + 0.7895 * closure_rate_filter.output.get())
3181 if (mk_ainput(computed_airspeed).get() <= conf->airspeed1)
3183 else if (mk_ainput(computed_airspeed).get() >= conf->airspeed2)
3186 upper_limit = 1650 + 8.9 * (mk_ainput(computed_airspeed).get() - conf->airspeed1);
3188 if (mk_data(radio_altitude).get() < upper_limit)
3190 if (mk_data(radio_altitude).get() < 522 + 0.1968 * closure_rate_filter.output.get())
3200 MK_VIII::Mode2Handler::is_b ()
3202 if (! mk->io_handler.gpws_inhibit()
3203 && ! mk->state_handler.ground // [1]
3204 && ! mk_data(radio_altitude).ncd
3205 && ! mk_data(barometric_altitude_rate).ncd
3206 && ! closure_rate_filter.output.ncd
3208 && mk_data(radio_altitude).get() < 789)
3212 if (mk->io_handler.flaps_down())
3214 if (mk_data(barometric_altitude_rate).get() > -400)
3216 else if (mk_data(barometric_altitude_rate).get() < -1000)
3219 lower_limit = -66.777 - 0.667 * mk_data(barometric_altitude_rate).get();
3224 if (mk_data(radio_altitude).get() > lower_limit)
3226 if (mk_data(radio_altitude).get() < -1579 + 0.7895 * closure_rate_filter.output.get())
3235 MK_VIII::Mode2Handler::check_pull_up (unsigned int preface_alert,
3238 if (pull_up_timer.running)
3240 if (pull_up_timer.elapsed() >= 1)
3242 mk_unset_alerts(preface_alert);
3243 mk_set_alerts(alert);
3248 if (! mk->voice_player.voice)
3249 pull_up_timer.start();
3254 MK_VIII::Mode2Handler::update_a ()
3258 if (mk_test_alert(MODE2A_PREFACE))
3259 check_pull_up(mk_alert(MODE2A_PREFACE), mk_alert(MODE2A));
3260 else if (! mk_test_alert(MODE2A))
3262 mk_unset_alerts(mk_alert(MODE2A_ALTITUDE_GAIN) | mk_alert(MODE2A_ALTITUDE_GAIN_TERRAIN_CLOSING));
3263 mk_set_alerts(mk_alert(MODE2A_PREFACE));
3264 a_start_time = globals->get_sim_time_sec();
3265 pull_up_timer.stop();
3270 if (mk_test_alert(MODE2A_ALTITUDE_GAIN))
3272 if (mk->io_handler.gpws_inhibit()
3273 || mk->state_handler.ground // [1]
3274 || a_altitude_gain_timer.elapsed() >= 45
3275 || mk_data(radio_altitude).ncd
3276 || mk_ainput(uncorrected_barometric_altitude).ncd
3277 || mk_ainput(uncorrected_barometric_altitude).get() - a_altitude_gain_alt >= 300
3278 // [PILOT] page 12: "the visual alert will remain on
3279 // until [...] landing flaps or the flap override switch
3281 || mk->io_handler.flaps_down())
3283 // exit altitude gain mode
3284 a_altitude_gain_timer.stop();
3285 mk_unset_alerts(mk_alert(MODE2A_ALTITUDE_GAIN) | mk_alert(MODE2A_ALTITUDE_GAIN_TERRAIN_CLOSING));
3289 // [SPEC] 6.2.2.2: "If the terrain starts to fall away
3290 // during this altitude gain time, the voice will shut
3292 if (closure_rate_filter.output.get() < 0)
3293 mk_unset_alerts(mk_alert(MODE2A_ALTITUDE_GAIN_TERRAIN_CLOSING));
3296 else if (mk_test_alerts(mk_alert(MODE2A_PREFACE) | mk_alert(MODE2A)))
3298 mk_unset_alerts(mk_alert(MODE2A_PREFACE) | mk_alert(MODE2A));
3300 if (! mk->io_handler.gpws_inhibit()
3301 && ! mk->state_handler.ground // [1]
3302 && globals->get_sim_time_sec() - a_start_time > 3
3303 && ! mk->io_handler.flaps_down()
3304 && ! mk_data(radio_altitude).ncd
3305 && ! mk_ainput(uncorrected_barometric_altitude).ncd)
3307 // [SPEC] 6.2.2.2: mode 2A envelope violated for more
3308 // than 3 seconds, enable altitude gain feature
3310 a_altitude_gain_timer.start();
3311 a_altitude_gain_alt = mk_ainput(uncorrected_barometric_altitude).get();
3313 unsigned int alerts = mk_alert(MODE2A_ALTITUDE_GAIN);
3314 if (closure_rate_filter.output.get() > 0)
3315 alerts |= mk_alert(MODE2A_ALTITUDE_GAIN_TERRAIN_CLOSING);
3317 mk_set_alerts(alerts);
3324 MK_VIII::Mode2Handler::update_b ()
3328 // handle normal mode
3330 if (b && (! mk_dinput(landing_gear) || ! mk->io_handler.flaps_down()))
3332 if (mk_test_alert(MODE2B_PREFACE))
3333 check_pull_up(mk_alert(MODE2B_PREFACE), mk_alert(MODE2B));
3334 else if (! mk_test_alert(MODE2B))
3336 mk_set_alerts(mk_alert(MODE2B_PREFACE));
3337 pull_up_timer.stop();
3341 mk_unset_alerts(mk_alert(MODE2B_PREFACE) | mk_alert(MODE2B));
3343 // handle landing mode ([SPEC] 6.2.2.3: "If both landing gear and
3344 // flaps are in the landing configuration, then the message will be
3347 if (b && mk_dinput(landing_gear) && mk->io_handler.flaps_down())
3348 mk_set_alerts(mk_alert(MODE2B_LANDING_MODE));
3350 mk_unset_alerts(mk_alert(MODE2B_LANDING_MODE));
3354 MK_VIII::Mode2Handler::boot ()
3356 closure_rate_filter.init();
3360 MK_VIII::Mode2Handler::power_off ()
3362 // [SPEC] 6.0.4: "This latching function is not power saved and a
3363 // system reset will force it false."
3364 takeoff_timer.stop();
3368 MK_VIII::Mode2Handler::leave_ground ()
3370 // takeoff, reset timer
3371 takeoff_timer.start();
3375 MK_VIII::Mode2Handler::enter_takeoff ()
3377 // reset timer, in case it's a go-around
3378 takeoff_timer.start();
3382 MK_VIII::Mode2Handler::update ()
3384 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
3387 closure_rate_filter.update();
3389 if (takeoff_timer.running && takeoff_timer.elapsed() >= 60)
3390 takeoff_timer.stop();
3396 ///////////////////////////////////////////////////////////////////////////////
3397 // Mode3Handler ///////////////////////////////////////////////////////////////
3398 ///////////////////////////////////////////////////////////////////////////////
3401 MK_VIII::Mode3Handler::max_alt_loss (double _bias)
3403 return conf->max_alt_loss(mk->io_handler.flap_override(), mk_data(radio_altitude).get()) + mk_data(radio_altitude).get() * _bias;
3407 MK_VIII::Mode3Handler::get_bias (double initial_bias, double alt_loss)
3409 // do not repeat altitude-loss alerts below 30ft agl
3410 if (mk_data(radio_altitude).get() > 30)
3412 if (initial_bias < 0.0) // sanity check
3414 // mk-viii spec: repeat alerts whenever losing 20% of initial altitude
3415 while ((alt_loss > max_alt_loss(initial_bias))&&
3416 (initial_bias < 1.0))
3417 initial_bias += 0.2;
3420 return initial_bias;
3424 MK_VIII::Mode3Handler::is (double *alt_loss)
3426 if (has_descent_alt)
3428 int max_agl = conf->max_agl(mk->io_handler.flap_override());
3430 if (mk_ainput(uncorrected_barometric_altitude).ncd
3431 || mk_ainput(uncorrected_barometric_altitude).get() > descent_alt
3432 || mk_data(radio_altitude).ncd
3433 || mk_data(radio_altitude).get() > max_agl)
3436 has_descent_alt = false;
3440 // gear/flaps: [SPEC] 1.3.1.3
3441 if (! mk->io_handler.gpws_inhibit()
3442 && ! mk->state_handler.ground // [1]
3443 && (! mk_dinput(landing_gear) || ! mk->io_handler.flaps_down())
3444 && ! mk_data(barometric_altitude_rate).ncd
3445 && ! mk_ainput(uncorrected_barometric_altitude).ncd
3446 && ! mk_data(radio_altitude).ncd
3447 && mk_data(barometric_altitude_rate).get() < 0)
3449 double _alt_loss = descent_alt - mk_ainput(uncorrected_barometric_altitude).get();
3450 if (armed || (mk_data(radio_altitude).get() > conf->min_agl
3451 && mk_data(radio_altitude).get() < max_agl
3452 && _alt_loss > max_alt_loss(0)))
3454 *alt_loss = _alt_loss;
3462 if (! mk_data(barometric_altitude_rate).ncd
3463 && ! mk_ainput(uncorrected_barometric_altitude).ncd
3464 && mk_data(barometric_altitude_rate).get() < 0)
3466 has_descent_alt = true;
3467 descent_alt = mk_ainput(uncorrected_barometric_altitude).get();
3475 MK_VIII::Mode3Handler::enter_takeoff ()
3478 has_descent_alt = false;
3482 MK_VIII::Mode3Handler::update ()
3484 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
3487 if (mk->state_handler.takeoff)
3491 if (! mk->state_handler.ground /* [1] */ && is(&alt_loss))
3493 if (mk_test_alert(MODE3))
3495 double new_bias = get_bias(bias, alt_loss);
3496 if (new_bias > bias)
3499 mk_repeat_alert(mk_alert(MODE3));
3505 bias = get_bias(0.2, alt_loss);
3506 mk_set_alerts(mk_alert(MODE3));
3513 mk_unset_alerts(mk_alert(MODE3));
3516 ///////////////////////////////////////////////////////////////////////////////
3517 // Mode4Handler ///////////////////////////////////////////////////////////////
3518 ///////////////////////////////////////////////////////////////////////////////
3520 // FIXME: for turbofans, the upper limit should be reduced from 1000
3521 // to 800 ft if a sudden change in radio altitude is detected, in
3522 // order to reduce nuisance alerts caused by overflying another
3523 // aircraft (see [PILOT] page 16).
3526 MK_VIII::Mode4Handler::get_upper_agl (const EnvelopesConfiguration *c)
3528 if (mk_ainput(computed_airspeed).get() >= c->airspeed2)
3530 else if (mk_ainput(computed_airspeed).get() >= c->airspeed1)
3531 return c->min_agl2(mk_ainput(computed_airspeed).get());
3536 const MK_VIII::Mode4Handler::EnvelopesConfiguration *
3537 MK_VIII::Mode4Handler::get_ab_envelope ()
3539 // [SPEC] 6.2.4.1: "The Mode 4B envelope can also be selected by
3540 // setting flaps to landing configuration or by selecting flap
3542 return mk_dinput(landing_gear) || mk->io_handler.flaps_down()
3548 MK_VIII::Mode4Handler::get_bias (double initial_bias, double min_agl)
3550 // do not repeat terrain/gear/flap alerts below 30ft agl
3551 if (mk_data(radio_altitude).get() > 30.0)
3553 if (initial_bias < 0.0) // sanity check
3555 while ((mk_data(radio_altitude).get() < min_agl - min_agl * initial_bias)&&
3556 (initial_bias < 1.0))
3557 initial_bias += 0.2;
3560 return initial_bias;
3564 MK_VIII::Mode4Handler::handle_alert (unsigned int alert,
3568 if (mk_test_alerts(alert))
3570 double new_bias = get_bias(*bias, min_agl);
3571 if (new_bias > *bias)
3574 mk_repeat_alert(alert);
3579 *bias = get_bias(0.2, min_agl);
3580 mk_set_alerts(alert);
3585 MK_VIII::Mode4Handler::update_ab ()
3587 if (! mk->io_handler.gpws_inhibit()
3588 && ! mk->state_handler.ground
3589 && ! mk->state_handler.takeoff
3590 && ! mk_data(radio_altitude).ncd
3591 && ! mk_ainput(computed_airspeed).ncd
3592 && mk_data(radio_altitude).get() > 30)
3594 const EnvelopesConfiguration *c = get_ab_envelope();
3595 if (mk_ainput(computed_airspeed).get() < c->airspeed1)
3597 if (mk_dinput(landing_gear))
3599 if (! mk->io_handler.flaps_down() && mk_data(radio_altitude).get() < c->min_agl1)
3601 handle_alert(mk_alert(MODE4_TOO_LOW_FLAPS), c->min_agl1, &ab_bias);
3607 if (mk_data(radio_altitude).get() < c->min_agl1)
3609 handle_alert(mk_alert(MODE4_TOO_LOW_GEAR), c->min_agl1, &ab_bias);
3616 mk_unset_alerts(mk_alert(MODE4_TOO_LOW_FLAPS) | mk_alert(MODE4_TOO_LOW_GEAR));
3621 MK_VIII::Mode4Handler::update_ab_expanded ()
3623 if (! mk->io_handler.gpws_inhibit()
3624 && ! mk->state_handler.ground
3625 && ! mk->state_handler.takeoff
3626 && ! mk_data(radio_altitude).ncd
3627 && ! mk_ainput(computed_airspeed).ncd
3628 && mk_data(radio_altitude).get() > 30)
3630 const EnvelopesConfiguration *c = get_ab_envelope();
3631 if (mk_ainput(computed_airspeed).get() >= c->airspeed1)
3633 double min_agl = get_upper_agl(c);
3634 if (mk_data(radio_altitude).get() < min_agl)
3636 handle_alert(mk_alert(MODE4AB_TOO_LOW_TERRAIN), min_agl, &ab_expanded_bias);
3642 mk_unset_alerts(mk_alert(MODE4AB_TOO_LOW_TERRAIN));
3643 ab_expanded_bias=0.0;
3647 MK_VIII::Mode4Handler::update_c ()
3649 if (! mk->io_handler.gpws_inhibit()
3650 && ! mk->state_handler.ground // [1]
3651 && mk->state_handler.takeoff
3652 && ! mk_data(radio_altitude).ncd
3653 && ! mk_data(terrain_clearance).ncd
3654 && mk_data(radio_altitude).get() > 30
3655 && (! mk_dinput(landing_gear) || ! mk->io_handler.flaps_down())
3656 && mk_data(radio_altitude).get() < get_upper_agl(conf.modes->ac)
3657 && mk_data(radio_altitude).get() < mk_data(terrain_clearance).get())
3658 handle_alert(mk_alert(MODE4C_TOO_LOW_TERRAIN), mk_data(terrain_clearance).get(), &c_bias);
3661 mk_unset_alerts(mk_alert(MODE4C_TOO_LOW_TERRAIN));
3667 MK_VIII::Mode4Handler::update ()
3669 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
3673 update_ab_expanded();
3677 ///////////////////////////////////////////////////////////////////////////////
3678 // Mode5Handler ///////////////////////////////////////////////////////////////
3679 ///////////////////////////////////////////////////////////////////////////////
3682 MK_VIII::Mode5Handler::is_hard ()
3684 if (mk_data(radio_altitude).get() > 30
3685 && mk_data(radio_altitude).get() < 300
3686 && mk_data(glideslope_deviation_dots).get() > 2)
3688 if (mk_data(radio_altitude).get() < 150)
3690 if (mk_data(radio_altitude).get() > 293 - 71.43 * mk_data(glideslope_deviation_dots).get())
3701 MK_VIII::Mode5Handler::is_soft (double bias)
3703 // do not repeat glide-slope alerts below 30ft agl
3704 if (mk_data(radio_altitude).get() > 30)
3706 double bias_dots = 1.3 * bias;
3707 if (mk_data(glideslope_deviation_dots).get() > 1.3 + bias_dots)
3709 if (mk_data(radio_altitude).get() < 150)
3711 if (mk_data(radio_altitude).get() > 243 - 71.43 * (mk_data(glideslope_deviation_dots).get() - bias_dots))
3718 if (mk_data(barometric_altitude_rate).ncd)
3722 if (mk_data(barometric_altitude_rate).get() >= 0)
3724 else if (mk_data(barometric_altitude_rate).get() < -500)
3727 upper_limit = -mk_data(barometric_altitude_rate).get() + 500;
3730 if (mk_data(radio_altitude).get() < upper_limit)
3740 MK_VIII::Mode5Handler::get_soft_bias (double initial_bias)
3742 if (initial_bias < 0.0) // sanity check
3744 while ((is_soft(initial_bias))&&
3745 (initial_bias < 1.0))
3746 initial_bias += 0.2;
3748 return initial_bias;
3752 MK_VIII::Mode5Handler::update_hard (bool is)
3756 if (! mk_test_alert(MODE5_HARD))
3758 if (hard_timer.start_or_elapsed() >= 0.8)
3759 mk_set_alerts(mk_alert(MODE5_HARD));
3765 mk_unset_alerts(mk_alert(MODE5_HARD));
3770 MK_VIII::Mode5Handler::update_soft (bool is)
3774 if (! mk_test_alert(MODE5_SOFT))
3776 double new_bias = get_soft_bias(soft_bias);
3777 if (new_bias > soft_bias)
3779 soft_bias = new_bias;
3780 mk_repeat_alert(mk_alert(MODE5_SOFT));
3785 if (soft_timer.start_or_elapsed() >= 0.8)
3787 soft_bias = get_soft_bias(0.2);
3788 mk_set_alerts(mk_alert(MODE5_SOFT));
3795 mk_unset_alerts(mk_alert(MODE5_SOFT));
3800 MK_VIII::Mode5Handler::update ()
3802 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
3805 if (! mk->io_handler.gpws_inhibit()
3806 && ! mk->state_handler.ground // [1]
3807 && ! mk_dinput(glideslope_inhibit) // not on backcourse
3808 && ! mk_data(radio_altitude).ncd
3809 && ! mk_data(glideslope_deviation_dots).ncd
3810 && (! mk->io_handler.conf.localizer_enabled
3811 || mk_data(localizer_deviation_dots).ncd
3812 || mk_data(radio_altitude).get() < 500
3813 || fabs(mk_data(localizer_deviation_dots).get()) < 2)
3814 && (! mk->state_handler.takeoff || mk->io_handler.flaps_down())
3815 && mk_dinput(landing_gear)
3816 && ! mk_doutput(glideslope_cancel))
3818 update_hard(is_hard());
3819 update_soft(is_soft(0));
3828 ///////////////////////////////////////////////////////////////////////////////
3829 // Mode6Handler ///////////////////////////////////////////////////////////////
3830 ///////////////////////////////////////////////////////////////////////////////
3832 // keep sorted in descending order
3833 const int MK_VIII::Mode6Handler::altitude_callout_definitions[] =
3834 { 1000, 500, 400, 300, 200, 100, 50, 40, 30, 20, 10 };
3837 MK_VIII::Mode6Handler::reset_minimums ()
3839 minimums_issued = false;
3843 MK_VIII::Mode6Handler::reset_altitude_callouts ()
3845 for (unsigned i = 0; i < n_altitude_callouts; i++)
3846 altitude_callouts_issued[i] = false;
3850 MK_VIII::Mode6Handler::is_playing_altitude_callout ()
3852 for (unsigned i = 0; i < n_altitude_callouts; i++)
3853 if (mk->voice_player.voice == mk_altitude_voice(i)
3854 || mk->voice_player.next_voice == mk_altitude_voice(i))
3861 MK_VIII::Mode6Handler::is_near_minimums (double callout)
3865 if (! mk_data(decision_height).ncd)
3867 double diff = callout - mk_data(decision_height).get();
3869 if (mk_data(radio_altitude).get() >= 200)
3871 if (fabs(diff) <= 30)
3876 if (diff >= -3 && diff <= 6)
3885 MK_VIII::Mode6Handler::is_outside_band (double elevation, double callout)
3888 return elevation < callout - (elevation > 150 ? 20 : 10);
3892 MK_VIII::Mode6Handler::inhibit_smart_500 ()
3896 if (! mk_data(glideslope_deviation_dots).ncd
3897 && ! mk_dinput(glideslope_inhibit) // backcourse
3898 && ! mk_doutput(glideslope_cancel))
3900 if (mk->io_handler.conf.localizer_enabled
3901 && ! mk_data(localizer_deviation_dots).ncd)
3903 if (fabs(mk_data(localizer_deviation_dots).get()) <= 2)
3908 if (fabs(mk_data(glideslope_deviation_dots).get()) <= 2)
3917 MK_VIII::Mode6Handler::boot ()
3919 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
3922 last_decision_height = mk_dinput(decision_height);
3923 last_radio_altitude.set(&mk_data(radio_altitude));
3926 for (unsigned i = 0; i < n_altitude_callouts; i++)
3927 altitude_callouts_issued[i] = ! mk_data(radio_altitude).ncd
3928 && mk_data(radio_altitude).get() <= altitude_callout_definitions[i];
3930 // extrapolated from [SPEC] 6.4.2
3931 minimums_issued = mk_dinput(decision_height);
3933 if (conf.above_field_voice)
3936 get_altitude_above_field(&last_altitude_above_field);
3937 // extrapolated from [SPEC] 6.4.2
3938 above_field_issued = ! last_altitude_above_field.ncd
3939 && last_altitude_above_field.get() < 550;
3944 MK_VIII::Mode6Handler::power_off ()
3946 runway_timer.stop();
3950 MK_VIII::Mode6Handler::enter_takeoff ()
3952 reset_altitude_callouts(); // [SPEC] 6.4.2
3953 reset_minimums(); // omitted by [SPEC]; common sense
3957 MK_VIII::Mode6Handler::leave_takeoff ()
3959 reset_altitude_callouts(); // [SPEC] 6.0.2
3960 reset_minimums(); // [SPEC] 6.0.2
3964 MK_VIII::Mode6Handler::set_volume (float volume)
3966 mk_voice(minimums_minimums)->set_volume(volume);
3967 mk_voice(five_hundred_above)->set_volume(volume);
3968 for (unsigned i = 0; i < n_altitude_callouts; i++)
3969 mk_altitude_voice(i)->set_volume(volume);
3973 MK_VIII::Mode6Handler::altitude_callouts_enabled ()
3975 if (conf.minimums_enabled || conf.smart_500_enabled || conf.above_field_voice)
3978 for (unsigned i = 0; i < n_altitude_callouts; i++)
3979 if (conf.altitude_callouts_enabled[i])
3986 MK_VIII::Mode6Handler::update_minimums ()
3988 if (! mk->io_handler.gpws_inhibit()
3989 && (mk->voice_player.voice == mk_voice(minimums_minimums)
3990 || mk->voice_player.next_voice == mk_voice(minimums_minimums)))
3993 if (! mk->io_handler.gpws_inhibit()
3994 && ! mk->state_handler.ground // [1]
3995 && conf.minimums_enabled
3996 && ! minimums_issued
3997 && mk_dinput(landing_gear)
3998 && mk_dinput(decision_height)
3999 && ! last_decision_height)
4001 minimums_issued = true;
4003 // If an altitude callout is playing, stop it now so that the
4004 // minimums callout can be played (note that proper connection
4005 // and synchronization of the radio-altitude ARINC 529 input,
4006 // decision-height discrete and decision-height ARINC 529 input
4007 // will prevent an altitude callout from being played near the
4008 // decision height).
4010 if (is_playing_altitude_callout())
4011 mk->voice_player.stop(VoicePlayer::STOP_NOW);
4013 mk_set_alerts(mk_alert(MODE6_MINIMUMS));
4016 mk_unset_alerts(mk_alert(MODE6_MINIMUMS));
4019 last_decision_height = mk_dinput(decision_height);
4023 MK_VIII::Mode6Handler::update_altitude_callouts ()
4025 if (! mk->io_handler.gpws_inhibit() && is_playing_altitude_callout())
4028 if (! mk->io_handler.gpws_inhibit()
4029 && ! mk->state_handler.ground // [1]
4030 && ! mk_data(radio_altitude).ncd)
4031 for (unsigned i = 0; i < n_altitude_callouts && mk_data(radio_altitude).get() <= altitude_callout_definitions[i]; i++)
4032 if ((conf.altitude_callouts_enabled[i]
4033 || (altitude_callout_definitions[i] == 500
4034 && conf.smart_500_enabled))
4035 && ! altitude_callouts_issued[i]
4036 && (last_radio_altitude.ncd
4037 || last_radio_altitude.get() > altitude_callout_definitions[i]))
4039 // lock out all callouts superior or equal to this one
4040 for (unsigned j = 0; j <= i; j++)
4041 altitude_callouts_issued[j] = true;
4043 altitude_callouts_issued[i] = true;
4044 if (! is_near_minimums(altitude_callout_definitions[i])
4045 && ! is_outside_band(mk_data(radio_altitude).get(), altitude_callout_definitions[i])
4046 && (! conf.smart_500_enabled
4047 || altitude_callout_definitions[i] != 500
4048 || ! inhibit_smart_500()))
4050 mk->alert_handler.set_altitude_callout_alert(mk_altitude_voice(i));
4055 mk_unset_alerts(mk_alert(MODE6_ALTITUDE_CALLOUT));
4058 last_radio_altitude.set(&mk_data(radio_altitude));
4062 MK_VIII::Mode6Handler::test_runway (const FGRunway *_runway)
4064 if (_runway->lengthFt() < mk->conf.runway_database)
4068 SGGeod::fromDeg(mk_data(gps_longitude).get(), mk_data(gps_latitude).get()));
4070 // get distance to threshold
4071 double distance, az1, az2;
4072 SGGeodesy::inverse(pos, _runway->threshold(), az1, az2, distance);
4073 return distance * SG_METER_TO_NM <= 5;
4077 MK_VIII::Mode6Handler::test_airport (const FGAirport *airport)
4079 for (unsigned int r=0; r<airport->numRunways(); ++r) {
4080 FGRunway* rwy(airport->getRunwayByIndex(r));
4082 if (test_runway(rwy)) return true;
4088 bool MK_VIII::Mode6Handler::AirportFilter::passAirport(FGAirport* a) const
4090 bool ok = self->test_airport(a);
4095 MK_VIII::Mode6Handler::update_runway ()
4098 if (mk_data(gps_latitude).ncd || mk_data(gps_longitude).ncd) {
4103 // Search for the closest runway threshold in range 5
4104 // nm. Passing 30nm to
4105 // get_closest_airport() provides enough margin for large
4106 // airports, which may have a runway located far away from the
4107 // airport's reference point.
4108 AirportFilter filter(this);
4109 FGPositionedRef apt = FGPositioned::findClosest(
4110 SGGeod::fromDeg(mk_data(gps_longitude).get(), mk_data(gps_latitude).get()),
4113 runway.elevation = apt->elevation();
4116 has_runway.set(apt != NULL);
4120 MK_VIII::Mode6Handler::get_altitude_above_field (Parameter<double> *parameter)
4122 if (! has_runway.ncd && has_runway.get() && ! mk_data(geometric_altitude).ncd)
4123 parameter->set(mk_data(geometric_altitude).get() - runway.elevation);
4129 MK_VIII::Mode6Handler::update_above_field_callout ()
4131 if (! conf.above_field_voice)
4134 // update_runway() has to iterate over the whole runway database
4135 // (which contains thousands of runways), so it would be unwise to
4136 // run it every frame. Run it every 3 seconds. Note that the first
4137 // iteration is run in boot().
4138 if (runway_timer.start_or_elapsed() >= 3)
4141 runway_timer.start();
4144 Parameter<double> altitude_above_field;
4145 get_altitude_above_field(&altitude_above_field);
4147 if (! mk->io_handler.gpws_inhibit()
4148 && (mk->voice_player.voice == conf.above_field_voice
4149 || mk->voice_player.next_voice == conf.above_field_voice))
4152 // handle reset term
4153 if (above_field_issued)
4155 if ((! has_runway.ncd && ! has_runway.get())
4156 || (! altitude_above_field.ncd && altitude_above_field.get() > 700))
4157 above_field_issued = false;
4160 if (! mk->io_handler.gpws_inhibit()
4161 && ! mk->state_handler.ground // [1]
4162 && ! above_field_issued
4163 && ! altitude_above_field.ncd
4164 && altitude_above_field.get() < 550
4165 && (last_altitude_above_field.ncd
4166 || last_altitude_above_field.get() >= 550))
4168 above_field_issued = true;
4170 if (! is_outside_band(altitude_above_field.get(), 500))
4172 mk->alert_handler.set_altitude_callout_alert(conf.above_field_voice);
4177 mk_unset_alerts(mk_alert(MODE6_ALTITUDE_CALLOUT));
4180 last_altitude_above_field.set(&altitude_above_field);
4184 MK_VIII::Mode6Handler::is_bank_angle (double abs_roll_angle, double bias)
4186 return conf.is_bank_angle(&mk_data(radio_altitude),
4187 abs_roll_angle - abs_roll_angle * bias,
4188 mk_dinput(autopilot_engaged));
4192 MK_VIII::Mode6Handler::is_high_bank_angle ()
4194 return mk_data(radio_altitude).ncd || mk_data(radio_altitude).get() >= 210;
4198 MK_VIII::Mode6Handler::get_bank_angle_alerts ()
4200 if (! mk->io_handler.gpws_inhibit()
4201 && ! mk->state_handler.ground // [1]
4202 && ! mk_data(roll_angle).ncd)
4204 double abs_roll_angle = fabs(mk_data(roll_angle).get());
4206 if (is_bank_angle(abs_roll_angle, 0.4))
4207 return is_high_bank_angle()
4208 ? (mk_alert(MODE6_HIGH_BANK_ANGLE_1) | mk_alert(MODE6_HIGH_BANK_ANGLE_2) | mk_alert(MODE6_HIGH_BANK_ANGLE_3))
4209 : (mk_alert(MODE6_LOW_BANK_ANGLE_1) | mk_alert(MODE6_LOW_BANK_ANGLE_2) | mk_alert(MODE6_LOW_BANK_ANGLE_3));
4210 else if (is_bank_angle(abs_roll_angle, 0.2))
4211 return is_high_bank_angle()
4212 ? (mk_alert(MODE6_HIGH_BANK_ANGLE_1) | mk_alert(MODE6_HIGH_BANK_ANGLE_2))
4213 : (mk_alert(MODE6_LOW_BANK_ANGLE_1) | mk_alert(MODE6_LOW_BANK_ANGLE_2));
4214 else if (is_bank_angle(abs_roll_angle, 0))
4215 return is_high_bank_angle()
4216 ? mk_alert(MODE6_HIGH_BANK_ANGLE_1)
4217 : mk_alert(MODE6_LOW_BANK_ANGLE_1);
4224 MK_VIII::Mode6Handler::update_bank_angle ()
4226 if (! conf.bank_angle_enabled)
4229 unsigned int alerts = get_bank_angle_alerts();
4231 // [SPEC] 6.4.4 specifies that the non-continuous alerts
4232 // (MODE6_*_BANK_ANGLE_1 and MODE6_*_BANK_ANGLE_2) are locked out
4233 // until the initial envelope is exited.
4235 if (! test_bits(alerts, mk_alert(MODE6_LOW_BANK_ANGLE_3)))
4236 mk_unset_alerts(mk_alert(MODE6_LOW_BANK_ANGLE_3));
4237 if (! test_bits(alerts, mk_alert(MODE6_HIGH_BANK_ANGLE_3)))
4238 mk_unset_alerts(mk_alert(MODE6_HIGH_BANK_ANGLE_3));
4241 mk_set_alerts(alerts);
4243 mk_unset_alerts(mk_alert(MODE6_LOW_BANK_ANGLE_1)
4244 | mk_alert(MODE6_HIGH_BANK_ANGLE_1)
4245 | mk_alert(MODE6_LOW_BANK_ANGLE_2)
4246 | mk_alert(MODE6_HIGH_BANK_ANGLE_2));
4250 MK_VIII::Mode6Handler::update ()
4252 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
4255 if (! mk->state_handler.takeoff
4256 && ! mk_data(radio_altitude).ncd
4257 && mk_data(radio_altitude).get() > 1000)
4259 reset_altitude_callouts(); // [SPEC] 6.4.2
4260 reset_minimums(); // common sense
4264 update_altitude_callouts();
4265 update_above_field_callout();
4266 update_bank_angle();
4269 ///////////////////////////////////////////////////////////////////////////////
4270 // TCFHandler /////////////////////////////////////////////////////////////////
4271 ///////////////////////////////////////////////////////////////////////////////
4273 // Gets the difference between the azimuth from @from_lat,@from_lon to
4274 // @to_lat,@to_lon, and @to_heading, in degrees.
4276 MK_VIII::TCFHandler::get_azimuth_difference (double from_lat,
4282 double az1, az2, distance;
4283 geo_inverse_wgs_84(0, from_lat, from_lon, to_lat, to_lon, &az1, &az2, &distance);
4284 return get_heading_difference(az1, to_heading);
4287 // Gets the difference between the azimuth from the current GPS
4288 // position to the center of @_runway, and the heading of @_runway, in
4291 MK_VIII::TCFHandler::get_azimuth_difference (const FGRunway *_runway)
4293 return get_azimuth_difference(mk_data(gps_latitude).get(),
4294 mk_data(gps_longitude).get(),
4295 _runway->latitude(),
4296 _runway->longitude(),
4297 _runway->headingDeg());
4300 // Selects the most likely intended destination runway of @airport,
4301 // and returns it in @_runway. For each runway, the difference between
4302 // the azimuth from the current GPS position to the center of the
4303 // runway and its heading is computed. The runway having the smallest
4306 // This selection algorithm is not specified in [SPEC], but
4307 // http://www.egpws.com/general_information/description/runway_select.htm
4308 // talks about automatic runway selection.
4310 MK_VIII::TCFHandler::select_runway (const FGAirport *airport)
4312 FGRunway* _runway = 0;
4313 double min_diff = 360;
4315 for (unsigned int r=0; r<airport->numRunways(); ++r) {
4316 FGRunway* rwy(airport->getRunwayByIndex(r));
4317 double diff = get_azimuth_difference(rwy);
4318 if (diff < min_diff)
4323 } // of airport runways iteration
4327 bool MK_VIII::TCFHandler::AirportFilter::passAirport(FGAirport* aApt) const
4329 return aApt->hasHardRunwayOfLengthFt(mk->conf.runway_database);
4333 MK_VIII::TCFHandler::update_runway ()
4336 if (mk_data(gps_latitude).ncd || mk_data(gps_longitude).ncd) {
4340 // Search for the intended destination runway of the closest
4341 // airport in range 15 nm. Passing 30nm to get_closest_airport()
4342 // provides enough margin for
4343 // large airports, which may have a runway located far away from
4344 // the airport's reference point.
4345 AirportFilter filter(mk);
4346 FGAirport* apt = FGAirport::findClosest(
4347 SGGeod::fromDeg(mk_data(gps_longitude).get(), mk_data(gps_latitude).get()),
4352 FGRunway* _runway = select_runway(apt);
4354 if (!_runway) return;
4358 runway.center.latitude = _runway->latitude();
4359 runway.center.longitude = _runway->longitude();
4361 runway.elevation = apt->elevation();
4363 double half_length_m = _runway->lengthM() * 0.5;
4364 runway.half_length = half_length_m * SG_METER_TO_NM;
4366 // b3 ________________ b0
4368 // h1>>> | e1<<<<<<<<e0 | <<<h0
4369 // |________________|
4372 // get heading to runway threshold (h0) and end (h1)
4373 runway.edges[0].heading = _runway->headingDeg();
4374 runway.edges[1].heading = get_reciprocal_heading(_runway->headingDeg());
4378 // get position of runway threshold (e0)
4379 geo_direct_wgs_84(0,
4380 runway.center.latitude,
4381 runway.center.longitude,
4382 runway.edges[1].heading,
4384 &runway.edges[0].position.latitude,
4385 &runway.edges[0].position.longitude,
4388 // get position of runway end (e1)
4389 geo_direct_wgs_84(0,
4390 runway.center.latitude,
4391 runway.center.longitude,
4392 runway.edges[0].heading,
4394 &runway.edges[1].position.latitude,
4395 &runway.edges[1].position.longitude,
4398 double half_width_m = _runway->widthM() * 0.5;
4400 // get position of threshold bias area edges (b0 and b1)
4401 get_bias_area_edges(&runway.edges[0].position,
4402 runway.edges[1].heading,
4404 &runway.bias_area[0],
4405 &runway.bias_area[1]);
4407 // get position of end bias area edges (b2 and b3)
4408 get_bias_area_edges(&runway.edges[1].position,
4409 runway.edges[0].heading,
4411 &runway.bias_area[2],
4412 &runway.bias_area[3]);
4416 MK_VIII::TCFHandler::get_bias_area_edges (Position *edge,
4418 double half_width_m,
4419 Position *bias_edge1,
4420 Position *bias_edge2)
4422 double half_bias_width_m = k * SG_NM_TO_METER + half_width_m;
4423 double tmp_latitude = 0.0, tmp_longitude = 0.0, az = 0.0;
4425 geo_direct_wgs_84(0,
4433 geo_direct_wgs_84(0,
4436 heading_substract(reciprocal, 90),
4438 &bias_edge1->latitude,
4439 &bias_edge1->longitude,
4441 geo_direct_wgs_84(0,
4444 heading_add(reciprocal, 90),
4446 &bias_edge2->latitude,
4447 &bias_edge2->longitude,
4451 // Returns true if the current GPS position is inside the edge
4452 // triangle of @edge. The edge triangle, which is specified and
4453 // represented in [SPEC] 6.3.1, is defined as an isocele right
4454 // triangle of infinite height, whose right angle is located at the
4455 // position of @edge, and whose height is parallel to the heading of
4458 MK_VIII::TCFHandler::is_inside_edge_triangle (RunwayEdge *edge)
4460 return get_azimuth_difference(mk_data(gps_latitude).get(),
4461 mk_data(gps_longitude).get(),
4462 edge->position.latitude,
4463 edge->position.longitude,
4464 edge->heading) <= 45;
4467 // Returns true if the current GPS position is inside the bias area of
4468 // the currently selected runway.
4470 MK_VIII::TCFHandler::is_inside_bias_area ()
4473 double angles_sum = 0;
4475 for (int i = 0; i < 4; i++)
4477 double az2, distance;
4478 geo_inverse_wgs_84(0,
4479 mk_data(gps_latitude).get(),
4480 mk_data(gps_longitude).get(),
4481 runway.bias_area[i].latitude,
4482 runway.bias_area[i].longitude,
4483 &az1[i], &az2, &distance);
4486 for (int i = 0; i < 4; i++)
4488 double angle = az1[i == 3 ? 0 : i + 1] - az1[i];
4492 angles_sum += angle;
4495 return angles_sum > 180;
4499 MK_VIII::TCFHandler::is_tcf ()
4501 if (mk_data(radio_altitude).get() > 10)
4505 double distance, az1, az2;
4507 geo_inverse_wgs_84(0,
4508 mk_data(gps_latitude).get(),
4509 mk_data(gps_longitude).get(),
4510 runway.center.latitude,
4511 runway.center.longitude,
4512 &az1, &az2, &distance);
4514 distance *= SG_METER_TO_NM;
4516 // distance to the inner envelope edge
4517 double edge_distance = distance - runway.half_length - k;
4519 if (edge_distance >= 15)
4521 if (mk_data(radio_altitude).get() < 700)
4524 else if (edge_distance >= 12)
4526 if (mk_data(radio_altitude).get() < 100 * edge_distance - 800)
4529 else if (edge_distance >= 4)
4531 if (mk_data(radio_altitude).get() < 400)
4534 else if (edge_distance >= 2.45)
4536 if (mk_data(radio_altitude).get() < 66.666667 * edge_distance + 133.33333)
4541 if (is_inside_edge_triangle(&runway.edges[0]) || is_inside_edge_triangle(&runway.edges[1]))
4543 if (edge_distance >= 1)
4545 if (mk_data(radio_altitude).get() < 66.666667 * edge_distance + 133.33333)
4548 else if (edge_distance >= 0.05)
4550 if (mk_data(radio_altitude).get() < 200 * edge_distance)
4556 if (! is_inside_bias_area())
4558 if (mk_data(radio_altitude).get() < 245)
4566 if (mk_data(radio_altitude).get() < 700)
4575 MK_VIII::TCFHandler::is_rfcf ()
4579 double distance, az1, az2;
4580 geo_inverse_wgs_84(0,
4581 mk_data(gps_latitude).get(),
4582 mk_data(gps_longitude).get(),
4583 runway.center.latitude,
4584 runway.center.longitude,
4585 &az1, &az2, &distance);
4587 double krf = k + mk_data(gps_vertical_figure_of_merit).get() / 200;
4588 distance = distance * SG_METER_TO_NM - runway.half_length - krf;
4592 double altitude_above_field = mk_data(geometric_altitude).get() - runway.elevation;
4594 if (distance >= 1.5)
4596 if (altitude_above_field < 300)
4599 else if (distance >= 0)
4601 if (altitude_above_field < 200 * distance)
4611 MK_VIII::TCFHandler::update ()
4613 if (mk->configuration_module.state != ConfigurationModule::STATE_OK || ! conf.enabled)
4616 // update_runway() has to iterate over the whole runway database
4617 // (which contains thousands of runways), so it would be unwise to
4618 // run it every frame. Run it every 3 seconds.
4619 if (! runway_timer.running || runway_timer.elapsed() >= 3)
4622 runway_timer.start();
4625 if (! mk_dinput(ta_tcf_inhibit)
4626 && ! mk->state_handler.ground // [1]
4627 && ! mk_data(gps_latitude).ncd
4628 && ! mk_data(gps_longitude).ncd
4629 && ! mk_data(radio_altitude).ncd
4630 && ! mk_data(geometric_altitude).ncd
4631 && ! mk_data(gps_vertical_figure_of_merit).ncd)
4636 _reference = mk_data(radio_altitude).get_pointer();
4638 _reference = mk_data(geometric_altitude).get_pointer();
4644 if (mk_test_alert(TCF_TOO_LOW_TERRAIN))
4646 double new_bias = bias;
4647 // do not repeat terrain alerts below 30ft agl
4648 if (mk_data(radio_altitude).get() > 30)
4650 if (new_bias < 0.0) // sanity check
4652 while ((*reference < initial_value - initial_value * new_bias)&&
4657 if (new_bias > bias)
4660 mk_repeat_alert(mk_alert(TCF_TOO_LOW_TERRAIN));
4666 reference = _reference;
4667 initial_value = *reference;
4668 mk_set_alerts(mk_alert(TCF_TOO_LOW_TERRAIN));
4675 mk_unset_alerts(mk_alert(TCF_TOO_LOW_TERRAIN));
4678 ///////////////////////////////////////////////////////////////////////////////
4679 // MK_VIII ////////////////////////////////////////////////////////////////////
4680 ///////////////////////////////////////////////////////////////////////////////
4682 MK_VIII::MK_VIII (SGPropertyNode *node)
4683 : properties_handler(this),
4686 power_handler(this),
4687 system_handler(this),
4688 configuration_module(this),
4689 fault_handler(this),
4692 self_test_handler(this),
4693 alert_handler(this),
4694 state_handler(this),
4695 mode1_handler(this),
4696 mode2_handler(this),
4697 mode3_handler(this),
4698 mode4_handler(this),
4699 mode5_handler(this),
4700 mode6_handler(this),
4703 for (int i = 0; i < node->nChildren(); ++i)
4705 SGPropertyNode *child = node->getChild(i);
4706 string cname = child->getName();
4707 string cval = child->getStringValue();
4709 if (cname == "name")
4711 else if (cname == "number")
4712 num = child->getIntValue();
4715 SG_LOG(SG_INSTR, SG_WARN, "Error in mk-viii config logic");
4717 SG_LOG(SG_INSTR, SG_WARN, "Section = " << name);
4725 properties_handler.init();
4726 voice_player.init();
4732 SGPropertyNode *node = fgGetNode(("/instrumentation/" + name).c_str(), num, true);
4734 configuration_module.bind(node);
4735 power_handler.bind(node);
4736 io_handler.bind(node);
4737 voice_player.bind(node, "Sounds/mk-viii/");
4743 properties_handler.unbind();
4747 MK_VIII::update (double dt)
4749 power_handler.update();
4750 system_handler.update();
4752 if (system_handler.state != SystemHandler::STATE_ON)
4755 io_handler.update_inputs();
4756 io_handler.update_input_faults();
4758 voice_player.update();
4759 state_handler.update();
4761 if (self_test_handler.update())
4764 io_handler.update_internal_latches();
4765 io_handler.update_lamps();
4767 mode1_handler.update();
4768 mode2_handler.update();
4769 mode3_handler.update();
4770 mode4_handler.update();
4771 mode5_handler.update();
4772 mode6_handler.update();
4773 tcf_handler.update();
4775 alert_handler.update();
4776 io_handler.update_outputs();