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., 675 Mass Ave, Cambridge, MA 02139, 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/structure/exception.hxx>
77 #include <Airports/runways.hxx>
78 #include <Airports/simple.hxx>
80 #if defined( HAVE_VERSION_H ) && HAVE_VERSION_H
81 # include <Include/version.h>
83 # include <Include/no_version.h>
86 #include <Main/fg_props.hxx>
87 #include <Main/globals.hxx>
88 #include "instrument_mgr.hxx"
89 #include "mk_viii.hxx"
91 ///////////////////////////////////////////////////////////////////////////////
92 // constants //////////////////////////////////////////////////////////////////
93 ///////////////////////////////////////////////////////////////////////////////
95 #define GLIDESLOPE_DOTS_TO_DDM 0.0875 // [INSTALL] 6.2.30
96 #define GLIDESLOPE_DDM_TO_DOTS (1 / GLIDESLOPE_DOTS_TO_DDM)
98 #define LOCALIZER_DOTS_TO_DDM 0.0775 // [INSTALL] 6.2.33
99 #define LOCALIZER_DDM_TO_DOTS (1 / LOCALIZER_DOTS_TO_DDM)
101 ///////////////////////////////////////////////////////////////////////////////
102 // helpers ////////////////////////////////////////////////////////////////////
103 ///////////////////////////////////////////////////////////////////////////////
105 #define assert_not_reached() assert(false)
106 #define n_elements(_array) (sizeof(_array) / sizeof((_array)[0]))
107 #define test_bits(_bits, _test) (((_bits) & (_test)) != 0)
109 #define mk_node(_name) (mk->properties_handler.external_properties._name)
111 #define mk_dinput_feed(_name) (mk->io_handler.input_feeders.discretes._name)
112 #define mk_dinput(_name) (mk->io_handler.inputs.discretes._name)
113 #define mk_ainput_feed(_name) (mk->io_handler.input_feeders.arinc429._name)
114 #define mk_ainput(_name) (mk->io_handler.inputs.arinc429._name)
115 #define mk_doutput(_name) (mk->io_handler.outputs.discretes._name)
116 #define mk_aoutput(_name) (mk->io_handler.outputs.arinc429._name)
117 #define mk_data(_name) (mk->io_handler.data._name)
119 #define mk_voice(_name) (mk->voice_player.voices._name)
120 #define mk_altitude_voice(_i) (mk->voice_player.voices.altitude_callouts[(_i)])
122 #define mk_alert(_name) (AlertHandler::ALERT_ ## _name)
123 #define mk_alert_flag(_name) (AlertHandler::ALERT_FLAG_ ## _name)
124 #define mk_set_alerts (mk->alert_handler.set_alerts)
125 #define mk_unset_alerts (mk->alert_handler.unset_alerts)
126 #define mk_repeat_alert (mk->alert_handler.repeat_alert)
127 #define mk_test_alert(_name) (mk_test_alerts(mk_alert(_name)))
128 #define mk_test_alerts(_test) (test_bits(mk->alert_handler.alerts, (_test)))
130 const double MK_VIII::TCFHandler::k = 0.25;
133 modify_amplitude (double amplitude, double dB)
135 return amplitude * pow(10.0, dB / 20.0);
139 heading_add (double h1, double h2)
141 double result = h1 + h2;
148 heading_substract (double h1, double h2)
150 double result = h1 - h2;
157 get_heading_difference (double h1, double h2)
159 double diff = h1 - h2;
170 get_reciprocal_heading (double h)
172 return heading_add(h, 180);
175 ///////////////////////////////////////////////////////////////////////////////
176 // PropertiesHandler //////////////////////////////////////////////////////////
177 ///////////////////////////////////////////////////////////////////////////////
180 MK_VIII::PropertiesHandler::init ()
182 mk_node(ai_caged) = fgGetNode("/instrumentation/attitude-indicator/caged-flag", true);
183 mk_node(ai_roll) = fgGetNode("/instrumentation/attitude-indicator/indicated-roll-deg", true);
184 mk_node(ai_serviceable) = fgGetNode("/instrumentation/attitude-indicator/serviceable", true);
185 mk_node(altimeter_altitude) = fgGetNode("/instrumentation/altimeter/indicated-altitude-ft", true);
186 mk_node(altimeter_serviceable) = fgGetNode("/instrumentation/altimeter/serviceable", true);
187 mk_node(altitude) = fgGetNode("/position/altitude-ft", true);
188 mk_node(altitude_agl) = fgGetNode("/position/altitude-agl-ft", true);
189 mk_node(altitude_gear_agl) = fgGetNode("/position/gear-agl-ft", true);
190 mk_node(altitude_radar_agl) = fgGetNode("/instrumentation/radar-altimeter/radar-altitude-ft", true);
191 mk_node(orientation_roll) = fgGetNode("/orientation/roll-deg", true);
192 mk_node(asi_serviceable) = fgGetNode("/instrumentation/airspeed-indicator/serviceable", true);
193 mk_node(asi_speed) = fgGetNode("/instrumentation/airspeed-indicator/indicated-speed-kt", true);
194 mk_node(autopilot_heading_lock) = fgGetNode("/autopilot/locks/heading", true);
195 mk_node(flaps) = fgGetNode("/controls/flight/flaps", true);
196 mk_node(gear_down) = fgGetNode("/controls/gear/gear-down", true);
197 mk_node(latitude) = fgGetNode("/position/latitude-deg", true);
198 mk_node(longitude) = fgGetNode("/position/longitude-deg", true);
199 mk_node(nav0_cdi_serviceable) = fgGetNode("/instrumentation/nav/cdi/serviceable", true);
200 mk_node(nav0_gs_distance) = fgGetNode("/instrumentation/nav/gs-distance", true);
201 mk_node(nav0_gs_needle_deflection) = fgGetNode("/instrumentation/nav/gs-needle-deflection", true);
202 mk_node(nav0_gs_serviceable) = fgGetNode("/instrumentation/nav/gs/serviceable", true);
203 mk_node(nav0_has_gs) = fgGetNode("/instrumentation/nav/has-gs", true);
204 mk_node(nav0_heading_needle_deflection) = fgGetNode("/instrumentation/nav/heading-needle-deflection", true);
205 mk_node(nav0_in_range) = fgGetNode("/instrumentation/nav/in-range", true);
206 mk_node(nav0_nav_loc) = fgGetNode("/instrumentation/nav/nav-loc", true);
207 mk_node(nav0_serviceable) = fgGetNode("/instrumentation/nav/serviceable", true);
208 mk_node(power) = fgGetNode(("/systems/electrical/outputs/" + mk->name).c_str(), mk->num, true);
209 mk_node(replay_state) = fgGetNode("/sim/freeze/replay-state", true);
210 mk_node(vs) = fgGetNode("/velocities/vertical-speed-fps", true);
214 MK_VIII::PropertiesHandler::unbind ()
216 vector<SGPropertyNode_ptr>::iterator iter;
218 for (iter = tied_properties.begin(); iter != tied_properties.end(); iter++)
221 tied_properties.clear();
224 ///////////////////////////////////////////////////////////////////////////////
225 // PowerHandler ///////////////////////////////////////////////////////////////
226 ///////////////////////////////////////////////////////////////////////////////
229 MK_VIII::PowerHandler::bind (SGPropertyNode *node)
231 mk->properties_handler.tie(node, "serviceable", SGRawValuePointer<bool>(&serviceable));
235 MK_VIII::PowerHandler::handle_abnormal_voltage (bool abnormal,
241 if (timer->start_or_elapsed() >= max_duration)
242 return true; // power loss
251 MK_VIII::PowerHandler::update ()
253 double voltage = mk_node(power)->getDoubleValue();
254 bool now_powered = true;
262 if (handle_abnormal_voltage(voltage < 20.5, &low_surge_timer, 0.03))
264 if (handle_abnormal_voltage(voltage < 22.0 || voltage > 30.3, &abnormal_timer, 300))
266 if (handle_abnormal_voltage(voltage > 32.2, &high_surge_timer, 1))
268 if (handle_abnormal_voltage(voltage > 37.8, &very_high_surge_timer, 0.1))
278 power_loss_timer.stop();
281 if (power_loss_timer.start_or_elapsed() >= 0.2)
293 MK_VIII::PowerHandler::power_on ()
296 mk->system_handler.power_on();
300 MK_VIII::PowerHandler::power_off ()
303 mk->system_handler.power_off();
304 mk->voice_player.stop(VoicePlayer::STOP_NOW);
305 mk->self_test_handler.power_off(); // run before IOHandler::power_off()
306 mk->io_handler.power_off();
307 mk->mode2_handler.power_off();
308 mk->mode6_handler.power_off();
311 ///////////////////////////////////////////////////////////////////////////////
312 // SystemHandler //////////////////////////////////////////////////////////////
313 ///////////////////////////////////////////////////////////////////////////////
316 MK_VIII::SystemHandler::power_on ()
318 state = STATE_BOOTING;
320 // [SPEC] 3.5.2 mentions a 20 seconds maximum boot delay. We use a
321 // random delay between 3 and 5 seconds.
323 boot_delay = 3 + sg_random() * 2;
328 MK_VIII::SystemHandler::power_off ()
336 MK_VIII::SystemHandler::update ()
338 if (state == STATE_BOOTING)
340 if (boot_timer.elapsed() >= boot_delay)
342 last_replay_state = mk_node(replay_state)->getIntValue();
344 mk->configuration_module.boot();
346 mk->io_handler.boot();
347 mk->fault_handler.boot();
348 mk->alert_handler.boot();
350 // inputs are needed by the following boot() routines
351 mk->io_handler.update_inputs();
353 mk->mode2_handler.boot();
354 mk->mode6_handler.boot();
358 mk->io_handler.post_boot();
361 else if (state != STATE_OFF && mk->configuration_module.state == ConfigurationModule::STATE_OK)
363 // If the replay state changes, switch to reposition mode for 3
364 // seconds ([SPEC] 6.0.5) to avoid spurious alerts.
366 int replay_state = mk_node(replay_state)->getIntValue();
367 if (replay_state != last_replay_state)
369 mk->alert_handler.reposition();
370 mk->io_handler.reposition();
372 last_replay_state = replay_state;
373 state = STATE_REPOSITION;
374 reposition_timer.start();
377 if (state == STATE_REPOSITION && reposition_timer.elapsed() >= 3)
379 // inputs are needed by StateHandler::post_reposition()
380 mk->io_handler.update_inputs();
382 mk->state_handler.post_reposition();
389 ///////////////////////////////////////////////////////////////////////////////
390 // ConfigurationModule ////////////////////////////////////////////////////////
391 ///////////////////////////////////////////////////////////////////////////////
393 MK_VIII::ConfigurationModule::ConfigurationModule (MK_VIII *device)
396 // arbitrary defaults
397 categories[CATEGORY_AIRCRAFT_MODE_TYPE_SELECT] = 0;
398 categories[CATEGORY_AIR_DATA_INPUT_SELECT] = 1;
399 categories[CATEGORY_POSITION_INPUT_SELECT] = 2;
400 categories[CATEGORY_ALTITUDE_CALLOUTS] = 13;
401 categories[CATEGORY_AUDIO_MENU_SELECT] = 0;
402 categories[CATEGORY_TERRAIN_DISPLAY_SELECT] = 1;
403 categories[CATEGORY_OPTIONS_SELECT_GROUP_1] = 124;
404 categories[CATEGORY_RADIO_ALTITUDE_INPUT_SELECT] = 2;
405 categories[CATEGORY_NAVIGATION_INPUT_SELECT] = 3;
406 categories[CATEGORY_ATTITUDE_INPUT_SELECT] = 6;
407 categories[CATEGORY_HEADING_INPUT_SELECT] = 2;
408 categories[CATEGORY_WINDSHEAR_INPUT_SELECT] = 0;
409 categories[CATEGORY_INPUT_OUTPUT_DISCRETE_TYPE_SELECT] = 7;
410 categories[CATEGORY_AUDIO_OUTPUT_LEVEL] = 0;
411 categories[CATEGORY_UNDEFINED_INPUT_SELECT_1] = 0;
412 categories[CATEGORY_UNDEFINED_INPUT_SELECT_2] = 0;
413 categories[CATEGORY_UNDEFINED_INPUT_SELECT_3] = 0;
416 static double m1_t1_min_agl1 (double vs) { return -1620 - 1.1133 * vs; }
417 static double m1_t1_min_agl2 (double vs) { return -400 - 0.4 * vs; }
418 static double m1_t4_min_agl1 (double vs) { return -1625.47 - 1.1167 * vs; }
419 static double m1_t4_min_agl2 (double vs) { return -0.1958 * vs; }
421 static int m3_t1_max_agl (bool flap_override) { return 1500; }
422 static double m3_t1_max_alt_loss (bool flap_override, double agl) { return 5.4 + 0.092 * agl; }
423 static int m3_t2_max_agl (bool flap_override) { return flap_override ? 815 : 925; }
424 static double m3_t2_max_alt_loss (bool flap_override, double agl)
426 int bias = agl > 700 ? 5 : 0;
429 return (9.0 + 0.184 * agl) + bias;
431 return (5.4 + 0.092 * agl) + bias;
434 static double m4_t1_min_agl2 (double airspeed) { return -1083 + 8.333 * airspeed; }
435 static double m4_t568_a_min_agl2 (double airspeed) { return -1523 + 11.36 * airspeed; }
436 static double m4_t79_a_min_agl2 (double airspeed) { return -1182 + 11.36 * airspeed; }
437 static double m4_t568_b_min_agl2 (double airspeed) { return -1570 + 11.6 * airspeed; }
438 static double m4_t79_b_min_agl2 (double airspeed) { return -1222 + 11.6 * airspeed; }
441 MK_VIII::ConfigurationModule::m6_t2_is_bank_angle (Parameter<double> *agl,
447 if (agl->ncd || agl->get() > 122)
448 return abs_roll_deg > 33;
452 if (agl->ncd || agl->get() > 2450)
453 return abs_roll_deg > 55;
454 else if (agl->get() > 150)
455 return agl->get() < 153.33333 * abs_roll_deg - 5983.3333;
459 return agl->get() < 4 * abs_roll_deg - 10;
460 else if (agl->get() > 5)
461 return abs_roll_deg > 10;
467 MK_VIII::ConfigurationModule::m6_t4_is_bank_angle (Parameter<double> *agl,
473 if (agl->ncd || agl->get() > 156)
474 return abs_roll_deg > 33;
478 if (agl->ncd || agl->get() > 210)
479 return abs_roll_deg > 50;
483 return agl->get() < 5.7142857 * abs_roll_deg - 75.714286;
489 MK_VIII::ConfigurationModule::read_aircraft_mode_type_select (int value)
491 static const Mode1Handler::EnvelopesConfiguration m1_t1 =
492 { false, 10, m1_t1_min_agl1, 284, m1_t1_min_agl2, 2450 };
493 static const Mode1Handler::EnvelopesConfiguration m1_t4 =
494 { true, 50, m1_t4_min_agl1, 346, m1_t4_min_agl2, 1958 };
496 static const Mode2Handler::Configuration m2_t1 = { 190, 280 };
497 static const Mode2Handler::Configuration m2_t4 = { 220, 310 };
498 static const Mode2Handler::Configuration m2_t5 = { 220, 310 };
500 static const Mode3Handler::Configuration m3_t1 =
501 { 30, m3_t1_max_agl, m3_t1_max_alt_loss };
502 static const Mode3Handler::Configuration m3_t2 =
503 { 50, m3_t2_max_agl, m3_t2_max_alt_loss };
505 static const Mode4Handler::EnvelopesConfiguration m4_t1_ac =
506 { 190, 250, 500, m4_t1_min_agl2, 1000 };
507 static const Mode4Handler::EnvelopesConfiguration m4_t5_ac =
508 { 178, 200, 500, m4_t568_a_min_agl2, 1000 };
509 static const Mode4Handler::EnvelopesConfiguration m4_t68_ac =
510 { 178, 200, 500, m4_t568_a_min_agl2, 750 };
511 static const Mode4Handler::EnvelopesConfiguration m4_t79_ac =
512 { 148, 170, 500, m4_t79_a_min_agl2, 750 };
514 static const Mode4Handler::EnvelopesConfiguration m4_t1_b =
515 { 159, 250, 245, m4_t1_min_agl2, 1000 };
516 static const Mode4Handler::EnvelopesConfiguration m4_t5_b =
517 { 148, 200, 200, m4_t568_b_min_agl2, 1000 };
518 static const Mode4Handler::EnvelopesConfiguration m4_t6_b =
519 { 150, 200, 170, m4_t568_b_min_agl2, 750 };
520 static const Mode4Handler::EnvelopesConfiguration m4_t7_b =
521 { 120, 170, 170, m4_t79_b_min_agl2, 750 };
522 static const Mode4Handler::EnvelopesConfiguration m4_t8_b =
523 { 148, 200, 150, m4_t568_b_min_agl2, 750 };
524 static const Mode4Handler::EnvelopesConfiguration m4_t9_b =
525 { 118, 170, 150, m4_t79_b_min_agl2, 750 };
527 static const Mode4Handler::ModesConfiguration m4_t1 = { &m4_t1_ac, &m4_t1_b };
528 static const Mode4Handler::ModesConfiguration m4_t5 = { &m4_t5_ac, &m4_t5_b };
529 static const Mode4Handler::ModesConfiguration m4_t6 = { &m4_t68_ac, &m4_t6_b };
530 static const Mode4Handler::ModesConfiguration m4_t7 = { &m4_t79_ac, &m4_t7_b };
531 static const Mode4Handler::ModesConfiguration m4_t8 = { &m4_t68_ac, &m4_t8_b };
532 static const Mode4Handler::ModesConfiguration m4_t9 = { &m4_t79_ac, &m4_t9_b };
534 static Mode6Handler::BankAnglePredicate m6_t2 = m6_t2_is_bank_angle;
535 static Mode6Handler::BankAnglePredicate m6_t4 = m6_t4_is_bank_angle;
537 static const IOHandler::FaultsConfiguration f_slow = { 180, 200 };
538 static const IOHandler::FaultsConfiguration f_fast = { 250, 290 };
543 const Mode1Handler::EnvelopesConfiguration *m1;
544 const Mode2Handler::Configuration *m2;
545 const Mode3Handler::Configuration *m3;
546 const Mode4Handler::ModesConfiguration *m4;
547 Mode6Handler::BankAnglePredicate m6;
548 const IOHandler::FaultsConfiguration *f;
550 } aircraft_types[] = {
551 { 0, &m1_t4, &m2_t4, &m3_t2, &m4_t6, m6_t4, &f_fast, 2000 },
552 { 1, &m1_t4, &m2_t4, &m3_t2, &m4_t8, m6_t4, &f_fast, 2000 },
553 { 2, &m1_t4, &m2_t4, &m3_t2, &m4_t5, m6_t4, &f_fast, 2000 },
554 { 3, &m1_t4, &m2_t5, &m3_t2, &m4_t7, m6_t4, &f_slow, 2000 },
555 { 4, &m1_t4, &m2_t5, &m3_t2, &m4_t9, m6_t4, &f_slow, 2000 },
556 { 254, &m1_t1, &m2_t1, &m3_t1, &m4_t1, m6_t2, &f_fast, 3500 },
557 { 255, &m1_t1, &m2_t1, &m3_t1, &m4_t1, m6_t2, &f_fast, 2000 }
560 for (size_t i = 0; i < n_elements(aircraft_types); i++)
561 if (aircraft_types[i].type == value)
563 mk->mode1_handler.conf.envelopes = aircraft_types[i].m1;
564 mk->mode2_handler.conf = aircraft_types[i].m2;
565 mk->mode3_handler.conf = aircraft_types[i].m3;
566 mk->mode4_handler.conf.modes = aircraft_types[i].m4;
567 mk->mode6_handler.conf.is_bank_angle = aircraft_types[i].m6;
568 mk->io_handler.conf.faults = aircraft_types[i].f;
569 mk->conf.runway_database = aircraft_types[i].runway_database;
573 state = STATE_INVALID_AIRCRAFT_TYPE;
578 MK_VIII::ConfigurationModule::read_air_data_input_select (int value)
581 return (value >= 0 && value <= 6) || (value >= 10 && value <= 13) || value == 255;
585 MK_VIII::ConfigurationModule::read_position_input_select (int value)
588 mk->io_handler.conf.use_internal_gps = true;
589 else if ((value >= 0 && value <= 5)
590 || (value >= 10 && value <= 13)
593 mk->io_handler.conf.use_internal_gps = false;
601 MK_VIII::ConfigurationModule::read_altitude_callouts (int value)
616 { 0, { MINIMUMS, SMART_500, 200, 100, 50, 40, 30, 20, 10, 0 } },
617 { 1, { MINIMUMS, SMART_500, 200, 0 } },
618 { 2, { MINIMUMS, SMART_500, 100, 50, 40, 30, 20, 10, 0 } },
619 { 3, { MINIMUMS, SMART_500, 0 } },
620 { 4, { MINIMUMS, 200, 100, 50, 40, 30, 20, 10, 0 } },
621 { 5, { MINIMUMS, 200, 0 } },
622 { 6, { MINIMUMS, 100, 50, 40, 30, 20, 10, 0 } },
624 { 8, { MINIMUMS, 0 } },
625 { 9, { MINIMUMS, 500, 200, 100, 50, 40, 30, 20, 10, 0 } },
626 { 10, { MINIMUMS, 500, 200, 0 } },
627 { 11, { MINIMUMS, 500, 100, 50, 40, 30, 20, 10, 0 } },
628 { 12, { MINIMUMS, 500, 0 } },
629 { 13, { MINIMUMS, 1000, 500, 400, 300, 200, 100, 50, 40, 30, 20, 10, 0 } },
630 { 14, { MINIMUMS, 100, 0 } },
631 { 15, { MINIMUMS, 200, 100, 0 } },
632 { 100, { FIELD_500, 0 } },
633 { 101, { FIELD_500_ABOVE, 0 } }
638 mk->mode6_handler.conf.minimums_enabled = false;
639 mk->mode6_handler.conf.smart_500_enabled = false;
640 mk->mode6_handler.conf.above_field_voice = NULL;
641 for (i = 0; i < n_altitude_callouts; i++)
642 mk->mode6_handler.conf.altitude_callouts_enabled[i] = false;
644 for (i = 0; i < n_elements(values); i++)
645 if (values[i].id == value)
647 for (int j = 0; values[i].callouts[j] != 0; j++)
648 switch (values[i].callouts[j])
651 mk->mode6_handler.conf.minimums_enabled = true;
655 mk->mode6_handler.conf.smart_500_enabled = true;
659 mk->mode6_handler.conf.above_field_voice = mk_altitude_voice(Mode6Handler::ALTITUDE_CALLOUT_500);
662 case FIELD_500_ABOVE:
663 mk->mode6_handler.conf.above_field_voice = mk_voice(five_hundred_above);
667 for (unsigned k = 0; k < n_altitude_callouts; k++)
668 if (mk->mode6_handler.altitude_callout_definitions[k] == values[i].callouts[j])
669 mk->mode6_handler.conf.altitude_callouts_enabled[k] = true;
680 MK_VIII::ConfigurationModule::read_audio_menu_select (int value)
682 if (value == 0 || value == 1)
683 mk->mode4_handler.conf.voice_too_low_gear = mk_voice(too_low_gear);
684 else if (value == 2 || value == 3)
685 mk->mode4_handler.conf.voice_too_low_gear = mk_voice(too_low_flaps);
693 MK_VIII::ConfigurationModule::read_terrain_display_select (int value)
696 mk->tcf_handler.conf.enabled = false;
697 else if (value == 0 || value == 1 || (value >= 3 && value <= 15)
698 || (value >= 18 && value <= 20) || (value >= 235 && value <= 255))
699 mk->tcf_handler.conf.enabled = true;
707 MK_VIII::ConfigurationModule::read_options_select_group_1 (int value)
709 if (value >= 0 && value < 128)
711 mk->io_handler.conf.flap_reversal = test_bits(value, 1 << 1);
712 mk->mode6_handler.conf.bank_angle_enabled = test_bits(value, 1 << 2);
713 mk->io_handler.conf.steep_approach_enabled = test_bits(value, 1 << 6);
721 MK_VIII::ConfigurationModule::read_radio_altitude_input_select (int value)
723 mk->io_handler.conf.altitude_source = value;
724 return (value >= 0 && value <= 4) || (value >= 251 && value <= 255);
728 MK_VIII::ConfigurationModule::read_navigation_input_select (int value)
730 if (value >= 0 && value <= 2)
731 mk->io_handler.conf.localizer_enabled = false;
732 else if ((value >= 3 && value <= 5) || (value >= 250 && value <= 255))
733 mk->io_handler.conf.localizer_enabled = true;
741 MK_VIII::ConfigurationModule::read_attitude_input_select (int value)
744 mk->io_handler.conf.use_attitude_indicator=true;
746 mk->io_handler.conf.use_attitude_indicator=false;
747 return (value >= 0 && value <= 6) || value == 253 || value == 255;
751 MK_VIII::ConfigurationModule::read_heading_input_select (int value)
754 return (value >= 0 && value <= 3) || value == 254 || value == 255;
758 MK_VIII::ConfigurationModule::read_windshear_input_select (int value)
761 return value == 0 || (value >= 253 && value <= 255);
765 MK_VIII::ConfigurationModule::read_input_output_discrete_type_select (int value)
770 IOHandler::LampConfiguration lamp_conf;
771 bool gpws_inhibit_enabled;
772 bool momentary_flap_override_enabled;
773 bool alternate_steep_approach;
775 { 0, { false, false }, false, true, true },
776 { 1, { true, false }, false, true, true },
777 { 2, { false, false }, true, true, true },
778 { 3, { true, false }, true, true, true },
779 { 4, { false, true }, true, true, true },
780 { 5, { true, true }, true, true, true },
781 { 6, { false, false }, true, true, false },
782 { 7, { true, false }, true, true, false },
783 { 254, { false, false }, true, false, true },
784 { 255, { false, false }, true, false, true }
787 for (size_t i = 0; i < n_elements(io_types); i++)
788 if (io_types[i].type == value)
790 mk->io_handler.conf.lamp = &io_types[i].lamp_conf;
791 mk->io_handler.conf.gpws_inhibit_enabled = io_types[i].gpws_inhibit_enabled;
792 mk->io_handler.conf.momentary_flap_override_enabled = io_types[i].momentary_flap_override_enabled;
793 mk->io_handler.conf.alternate_steep_approach = io_types[i].alternate_steep_approach;
801 MK_VIII::ConfigurationModule::read_audio_output_level (int value)
815 for (size_t i = 0; i < n_elements(values); i++)
816 if (values[i].id == value)
818 mk->voice_player.set_volume(mk->voice_player.conf.volume = modify_amplitude(1.0, values[i].relative_dB));
822 // The self test needs the voice player even when the configuration
823 // is invalid, so set a default value.
824 mk->voice_player.set_volume(mk->voice_player.conf.volume = 1.0);
829 MK_VIII::ConfigurationModule::read_undefined_input_select (int value)
836 MK_VIII::ConfigurationModule::boot ()
838 bool (MK_VIII::ConfigurationModule::*readers[N_CATEGORIES]) (int) = {
839 &MK_VIII::ConfigurationModule::read_aircraft_mode_type_select,
840 &MK_VIII::ConfigurationModule::read_air_data_input_select,
841 &MK_VIII::ConfigurationModule::read_position_input_select,
842 &MK_VIII::ConfigurationModule::read_altitude_callouts,
843 &MK_VIII::ConfigurationModule::read_audio_menu_select,
844 &MK_VIII::ConfigurationModule::read_terrain_display_select,
845 &MK_VIII::ConfigurationModule::read_options_select_group_1,
846 &MK_VIII::ConfigurationModule::read_radio_altitude_input_select,
847 &MK_VIII::ConfigurationModule::read_navigation_input_select,
848 &MK_VIII::ConfigurationModule::read_attitude_input_select,
849 &MK_VIII::ConfigurationModule::read_heading_input_select,
850 &MK_VIII::ConfigurationModule::read_windshear_input_select,
851 &MK_VIII::ConfigurationModule::read_input_output_discrete_type_select,
852 &MK_VIII::ConfigurationModule::read_audio_output_level,
853 &MK_VIII::ConfigurationModule::read_undefined_input_select,
854 &MK_VIII::ConfigurationModule::read_undefined_input_select,
855 &MK_VIII::ConfigurationModule::read_undefined_input_select
858 memcpy(effective_categories, categories, sizeof(categories));
861 for (int i = 0; i < N_CATEGORIES; i++)
862 if (! (this->*readers[i])(effective_categories[i]))
864 SG_LOG(SG_INSTR, SG_ALERT, "MK VIII EGPWS configuration category " << i + 1 << ": invalid value " << effective_categories[i]);
866 if (state == STATE_OK)
867 state = STATE_INVALID_DATABASE;
869 mk_doutput(gpws_inop) = true;
870 mk_doutput(tad_inop) = true;
877 if (mk->mode6_handler.conf.above_field_voice && ! mk->tcf_handler.conf.enabled)
880 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");
881 state = STATE_INVALID_DATABASE;
886 MK_VIII::ConfigurationModule::bind (SGPropertyNode *node)
888 for (int i = 0; i < N_CATEGORIES; i++)
890 std::ostringstream name;
891 name << "configuration-module/category-" << i + 1;
892 mk->properties_handler.tie(node, name.str().c_str(), SGRawValuePointer<int>(&categories[i]));
896 ///////////////////////////////////////////////////////////////////////////////
897 // FaultHandler ///////////////////////////////////////////////////////////////
898 ///////////////////////////////////////////////////////////////////////////////
900 // [INSTALL] only specifies that the faults cause a GPWS INOP
901 // indication. We arbitrarily set a TAD INOP when it makes sense.
902 const unsigned int MK_VIII::FaultHandler::fault_inops[] = {
903 INOP_GPWS | INOP_TAD, // [INSTALL] 3.15.1.3
904 INOP_GPWS, // [INSTALL] appendix E 6.6.2
905 INOP_GPWS, // [INSTALL] appendix E 6.6.4
906 INOP_GPWS, // [INSTALL] appendix E 6.6.6
907 INOP_GPWS | INOP_TAD, // [INSTALL] appendix E 6.6.7
908 INOP_GPWS, // [INSTALL] appendix E 6.6.13
909 INOP_GPWS, // [INSTALL] appendix E 6.6.25
910 INOP_GPWS, // [INSTALL] appendix E 6.6.27
911 INOP_TAD, // unspecified
912 INOP_GPWS, // unspecified
913 INOP_GPWS, // unspecified
914 // [INSTALL] 2.3.10.1 specifies that GPWS INOP is "activated during
915 // any detected partial or total failure of the GPWS modes 1-5", so
916 // do not set any INOP for mode 6 and bank angle.
919 INOP_TAD // unspecified
923 MK_VIII::FaultHandler::has_faults () const
925 for (int i = 0; i < N_FAULTS; i++)
933 MK_VIII::FaultHandler::has_faults (unsigned int inop)
935 for (int i = 0; i < N_FAULTS; i++)
936 if (faults[i] && test_bits(fault_inops[i], inop))
943 MK_VIII::FaultHandler::boot ()
945 memset(faults, 0, sizeof(faults));
949 MK_VIII::FaultHandler::set_fault (Fault fault)
953 faults[fault] = true;
955 mk->self_test_handler.set_inop();
957 if (test_bits(fault_inops[fault], INOP_GPWS))
959 mk_unset_alerts(~mk_alert(TCF_TOO_LOW_TERRAIN));
960 mk_doutput(gpws_inop) = true;
962 if (test_bits(fault_inops[fault], INOP_TAD))
964 mk_unset_alerts(mk_alert(TCF_TOO_LOW_TERRAIN));
965 mk_doutput(tad_inop) = true;
971 MK_VIII::FaultHandler::unset_fault (Fault fault)
975 faults[fault] = false;
976 if (! has_faults(INOP_GPWS))
977 mk_doutput(gpws_inop) = false;
978 if (! has_faults(INOP_TAD))
979 mk_doutput(tad_inop) = false;
983 ///////////////////////////////////////////////////////////////////////////////
984 // IOHandler //////////////////////////////////////////////////////////////////
985 ///////////////////////////////////////////////////////////////////////////////
988 MK_VIII::IOHandler::TerrainClearanceFilter::update (double agl)
990 // [PILOT] page 20 specifies that the terrain clearance is equal to
991 // 75% of the radio altitude, averaged over the previous 15 seconds.
993 // no updates when simulation is paused (dt=0.0), and add 5 samples/second only
994 if (globals->get_sim_time_sec() - last_update < 0.2)
996 last_update = globals->get_sim_time_sec();
998 samples_type::iterator iter;
1000 // remove samples older than 15 seconds
1001 for (iter = samples.begin(); iter != samples.end() && globals->get_sim_time_sec() - (*iter).timestamp >= 15; iter = samples.begin())
1002 samples.erase(iter);
1004 // append new sample
1005 samples.push_back(Sample<double>(agl));
1007 // calculate average
1008 double new_value = 0;
1009 if (samples.size() > 0)
1011 // time consuming loop => queue limited to 75 samples
1012 // (= 15seconds * 5samples/second)
1013 for (iter = samples.begin(); iter != samples.end(); iter++)
1014 new_value += (*iter).value;
1015 new_value /= samples.size();
1019 if (new_value > value)
1026 MK_VIII::IOHandler::TerrainClearanceFilter::reset ()
1033 MK_VIII::IOHandler::IOHandler (MK_VIII *device)
1034 : mk(device), _lamp(LAMP_NONE)
1036 memset(&input_feeders, 0, sizeof(input_feeders));
1037 memset(&inputs.discretes, 0, sizeof(inputs.discretes));
1038 memset(&outputs, 0, sizeof(outputs));
1039 memset(&power_saved, 0, sizeof(power_saved));
1041 mk_dinput_feed(landing_gear) = true;
1042 mk_dinput_feed(landing_flaps) = true;
1043 mk_dinput_feed(glideslope_inhibit) = true;
1044 mk_dinput_feed(decision_height) = true;
1045 mk_dinput_feed(autopilot_engaged) = true;
1046 mk_ainput_feed(uncorrected_barometric_altitude) = true;
1047 mk_ainput_feed(barometric_altitude_rate) = true;
1048 mk_ainput_feed(radio_altitude) = true;
1049 mk_ainput_feed(glideslope_deviation) = true;
1050 mk_ainput_feed(roll_angle) = true;
1051 mk_ainput_feed(localizer_deviation) = true;
1052 mk_ainput_feed(computed_airspeed) = true;
1054 // will be unset on power on
1055 mk_doutput(gpws_inop) = true;
1056 mk_doutput(tad_inop) = true;
1060 MK_VIII::IOHandler::boot ()
1062 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
1065 mk_doutput(gpws_inop) = false;
1066 mk_doutput(tad_inop) = false;
1068 mk_doutput(glideslope_cancel) = power_saved.glideslope_cancel;
1070 altitude_samples.clear();
1071 reset_terrain_clearance();
1075 MK_VIII::IOHandler::post_boot ()
1077 if (momentary_steep_approach_enabled())
1079 last_landing_gear = mk_dinput(landing_gear);
1080 last_real_flaps_down = real_flaps_down();
1083 // read externally-latching input discretes
1084 update_alternate_discrete_input(&mk_dinput(mode6_low_volume));
1085 update_alternate_discrete_input(&mk_dinput(audio_inhibit));
1089 MK_VIII::IOHandler::power_off ()
1091 power_saved.glideslope_cancel = mk_doutput(glideslope_cancel); // [SPEC] 6.2.5
1093 memset(&outputs, 0, sizeof(outputs));
1095 audio_inhibit_fault_timer.stop();
1096 landing_gear_fault_timer.stop();
1097 flaps_down_fault_timer.stop();
1098 momentary_flap_override_fault_timer.stop();
1099 self_test_fault_timer.stop();
1100 glideslope_cancel_fault_timer.stop();
1101 steep_approach_fault_timer.stop();
1102 gpws_inhibit_fault_timer.stop();
1103 ta_tcf_inhibit_fault_timer.stop();
1106 mk_doutput(gpws_inop) = true;
1107 mk_doutput(tad_inop) = true;
1111 MK_VIII::IOHandler::enter_ground ()
1113 reset_terrain_clearance();
1115 if (conf.momentary_flap_override_enabled)
1116 mk_doutput(flap_override) = false; // [INSTALL] 3.15.1.6
1120 MK_VIII::IOHandler::enter_takeoff ()
1122 reset_terrain_clearance();
1124 if (momentary_steep_approach_enabled())
1125 // landing or go-around, disable steep approach as per [SPEC] 6.2.1
1126 mk_doutput(steep_approach) = false;
1130 MK_VIII::IOHandler::update_inputs ()
1132 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
1135 // 1. process input feeders
1137 if (mk_dinput_feed(landing_gear))
1138 mk_dinput(landing_gear) = mk_node(gear_down)->getBoolValue();
1139 if (mk_dinput_feed(landing_flaps))
1140 mk_dinput(landing_flaps) = mk_node(flaps)->getDoubleValue() < 1;
1141 if (mk_dinput_feed(glideslope_inhibit))
1142 mk_dinput(glideslope_inhibit) = mk_node(nav0_gs_distance)->getDoubleValue() < 0;
1143 if (mk_dinput_feed(autopilot_engaged))
1147 mode = mk_node(autopilot_heading_lock)->getStringValue();
1148 mk_dinput(autopilot_engaged) = mode && *mode;
1151 if (mk_ainput_feed(uncorrected_barometric_altitude))
1153 if (mk_node(altimeter_serviceable)->getBoolValue())
1154 mk_ainput(uncorrected_barometric_altitude).set(mk_node(altimeter_altitude)->getDoubleValue());
1156 mk_ainput(uncorrected_barometric_altitude).unset();
1158 if (mk_ainput_feed(barometric_altitude_rate))
1159 // Do not use the vsi, because it is pressure-based only, and
1160 // therefore too laggy for GPWS alerting purposes. I guess that
1161 // a real ADC combines pressure-based and inerta-based altitude
1162 // rates to provide a non-laggy rate. That non-laggy rate is
1163 // best emulated by /velocities/vertical-speed-fps * 60.
1164 mk_ainput(barometric_altitude_rate).set(mk_node(vs)->getDoubleValue() * 60);
1165 if (mk_ainput_feed(radio_altitude))
1168 switch (conf.altitude_source)
1171 agl = mk_node(altitude_gear_agl)->getDoubleValue();
1174 agl = mk_node(altitude_radar_agl)->getDoubleValue();
1176 default: // 0,1,2 (and any currently unsupported values)
1177 agl = mk_node(altitude_agl)->getDoubleValue();
1180 // Some flight models may return negative values when on the
1181 // ground or after a crash; do not allow them.
1182 mk_ainput(radio_altitude).set(SG_MAX2(0.0, agl));
1184 if (mk_ainput_feed(glideslope_deviation))
1186 if (mk_node(nav0_serviceable)->getBoolValue()
1187 && mk_node(nav0_gs_serviceable)->getBoolValue()
1188 && mk_node(nav0_in_range)->getBoolValue()
1189 && mk_node(nav0_has_gs)->getBoolValue())
1190 // gs-needle-deflection is expressed in degrees, and 1 dot =
1191 // 0.32 degrees (according to
1192 // http://www.ntsb.gov/Recs/letters/2000/A00_41_45.pdf)
1193 mk_ainput(glideslope_deviation).set(mk_node(nav0_gs_needle_deflection)->getDoubleValue() / 0.32 * GLIDESLOPE_DOTS_TO_DDM);
1195 mk_ainput(glideslope_deviation).unset();
1197 if (mk_ainput_feed(roll_angle))
1199 if (conf.use_attitude_indicator)
1201 // read data from attitude indicator instrument (requires vacuum system to work)
1202 if (mk_node(ai_serviceable)->getBoolValue() && ! mk_node(ai_caged)->getBoolValue())
1203 mk_ainput(roll_angle).set(mk_node(ai_roll)->getDoubleValue());
1205 mk_ainput(roll_angle).unset();
1209 // use simulator source
1210 mk_ainput(roll_angle).set(mk_node(orientation_roll)->getDoubleValue());
1213 if (mk_ainput_feed(localizer_deviation))
1215 if (mk_node(nav0_serviceable)->getBoolValue()
1216 && mk_node(nav0_cdi_serviceable)->getBoolValue()
1217 && mk_node(nav0_in_range)->getBoolValue()
1218 && mk_node(nav0_nav_loc)->getBoolValue())
1219 // heading-needle-deflection is expressed in degrees, and 1
1220 // dot = 2 degrees (0.5 degrees for ILS, but navradio.cxx
1221 // performs the conversion)
1222 mk_ainput(localizer_deviation).set(mk_node(nav0_heading_needle_deflection)->getDoubleValue() / 2 * LOCALIZER_DOTS_TO_DDM);
1224 mk_ainput(localizer_deviation).unset();
1226 if (mk_ainput_feed(computed_airspeed))
1228 if (mk_node(asi_serviceable)->getBoolValue())
1229 mk_ainput(computed_airspeed).set(mk_node(asi_speed)->getDoubleValue());
1231 mk_ainput(computed_airspeed).unset();
1236 mk_data(decision_height).set(&mk_ainput(decision_height));
1237 mk_data(radio_altitude).set(&mk_ainput(radio_altitude));
1238 mk_data(roll_angle).set(&mk_ainput(roll_angle));
1240 // update barometric_altitude_rate as per [SPEC] 6.2.1: "During
1241 // normal conditions, the system will base Mode 1 computations upon
1242 // barometric rate from the ADC. If this computed data is not valid
1243 // or available then the system will use internally computed
1244 // barometric altitude rate."
1246 if (! mk_ainput(barometric_altitude_rate).ncd)
1247 // the altitude rate input is valid, use it
1248 mk_data(barometric_altitude_rate).set(mk_ainput(barometric_altitude_rate).get());
1253 // The altitude rate input is invalid. We can compute an
1254 // altitude rate if all the following conditions are true:
1256 // - the altitude input is valid
1257 // - there is an altitude sample with age >= 1 second
1258 // - that altitude sample is valid
1260 if (! mk_ainput(uncorrected_barometric_altitude).ncd)
1262 if (! altitude_samples.empty() && ! altitude_samples.front().value.ncd)
1264 double elapsed = globals->get_sim_time_sec() - altitude_samples.front().timestamp;
1268 mk_data(barometric_altitude_rate).set((mk_ainput(uncorrected_barometric_altitude).get() - altitude_samples.front().value.get()) / elapsed * 60);
1274 mk_data(barometric_altitude_rate).unset();
1277 altitude_samples.push_back(Sample< Parameter<double> >(mk_ainput(uncorrected_barometric_altitude)));
1279 // Erase everything from the beginning of the list up to the sample
1280 // preceding the most recent sample whose age is >= 1 second.
1282 altitude_samples_type::iterator erase_last = altitude_samples.begin();
1283 altitude_samples_type::iterator iter;
1285 for (iter = altitude_samples.begin(); iter != altitude_samples.end(); iter++)
1286 if (globals->get_sim_time_sec() - (*iter).timestamp >= 1)
1291 if (erase_last != altitude_samples.begin())
1292 altitude_samples.erase(altitude_samples.begin(), erase_last);
1296 if (conf.use_internal_gps)
1298 mk_data(gps_altitude).set(mk_node(altitude)->getDoubleValue());
1299 mk_data(gps_latitude).set(mk_node(latitude)->getDoubleValue());
1300 mk_data(gps_longitude).set(mk_node(longitude)->getDoubleValue());
1301 mk_data(gps_vertical_figure_of_merit).set(0.0);
1305 mk_data(gps_altitude).set(&mk_ainput(gps_altitude));
1306 mk_data(gps_latitude).set(&mk_ainput(gps_latitude));
1307 mk_data(gps_longitude).set(&mk_ainput(gps_longitude));
1308 mk_data(gps_vertical_figure_of_merit).set(&mk_ainput(gps_vertical_figure_of_merit));
1311 // update glideslope and localizer
1313 mk_data(glideslope_deviation_dots).set(&mk_ainput(glideslope_deviation), GLIDESLOPE_DDM_TO_DOTS);
1314 mk_data(localizer_deviation_dots).set(&mk_ainput(localizer_deviation), LOCALIZER_DDM_TO_DOTS);
1316 // Update geometric altitude; [SPEC] 6.7.8 provides an overview of a
1317 // complex algorithm which combines several input sources to
1318 // calculate the geometric altitude. Since the exact algorithm is
1319 // not given, we fallback to a much simpler method.
1321 if (! mk_data(gps_altitude).ncd)
1322 mk_data(geometric_altitude).set(mk_data(gps_altitude).get());
1323 else if (! mk_ainput(uncorrected_barometric_altitude).ncd)
1324 mk_data(geometric_altitude).set(mk_ainput(uncorrected_barometric_altitude).get());
1326 mk_data(geometric_altitude).unset();
1328 // update terrain clearance
1330 update_terrain_clearance();
1332 // 3. perform sanity checks
1334 if (! mk_data(radio_altitude).ncd && mk_data(radio_altitude).get() < 0)
1335 mk_data(radio_altitude).unset();
1337 if (! mk_data(decision_height).ncd && mk_data(decision_height).get() < 0)
1338 mk_data(decision_height).unset();
1340 if (! mk_data(gps_latitude).ncd
1341 && (mk_data(gps_latitude).get() < -90
1342 || mk_data(gps_latitude).get() > 90))
1343 mk_data(gps_latitude).unset();
1345 if (! mk_data(gps_longitude).ncd
1346 && (mk_data(gps_longitude).get() < -180
1347 || mk_data(gps_longitude).get() > 180))
1348 mk_data(gps_longitude).unset();
1350 if (! mk_data(roll_angle).ncd
1351 && ((mk_data(roll_angle).get() < -180)
1352 || (mk_data(roll_angle).get() > 180)))
1353 mk_data(roll_angle).unset();
1355 // 4. process input feeders requiring data computed above
1357 if (mk_dinput_feed(decision_height))
1358 mk_dinput(decision_height) = ! mk_data(radio_altitude).ncd
1359 && ! mk_data(decision_height).ncd
1360 && mk_data(radio_altitude).get() <= mk_data(decision_height).get();
1364 MK_VIII::IOHandler::update_terrain_clearance ()
1366 if (! mk_data(radio_altitude).ncd)
1367 mk_data(terrain_clearance).set(terrain_clearance_filter.update(mk_data(radio_altitude).get()));
1369 mk_data(terrain_clearance).unset();
1373 MK_VIII::IOHandler::reset_terrain_clearance ()
1375 terrain_clearance_filter.reset();
1376 update_terrain_clearance();
1380 MK_VIII::IOHandler::reposition ()
1382 reset_terrain_clearance();
1386 MK_VIII::IOHandler::handle_input_fault (bool test, FaultHandler::Fault fault)
1390 if (! mk->fault_handler.faults[fault])
1391 mk->fault_handler.set_fault(fault);
1394 mk->fault_handler.unset_fault(fault);
1398 MK_VIII::IOHandler::handle_input_fault (bool test,
1400 double max_duration,
1401 FaultHandler::Fault fault)
1405 if (! mk->fault_handler.faults[fault])
1407 if (timer->start_or_elapsed() >= max_duration)
1408 mk->fault_handler.set_fault(fault);
1413 mk->fault_handler.unset_fault(fault);
1419 MK_VIII::IOHandler::update_input_faults ()
1421 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
1424 // [INSTALL] 3.15.1.3
1425 handle_input_fault(mk_dinput(audio_inhibit),
1426 &audio_inhibit_fault_timer,
1428 FaultHandler::FAULT_ALL_MODES_INHIBIT);
1430 // [INSTALL] appendix E 6.6.2
1431 handle_input_fault(mk_dinput(landing_gear)
1432 && ! mk_ainput(computed_airspeed).ncd
1433 && mk_ainput(computed_airspeed).get() > conf.faults->max_gear_down_airspeed,
1434 &landing_gear_fault_timer,
1436 FaultHandler::FAULT_GEAR_SWITCH);
1438 // [INSTALL] appendix E 6.6.4
1439 handle_input_fault(flaps_down()
1440 && ! mk_ainput(computed_airspeed).ncd
1441 && mk_ainput(computed_airspeed).get() > conf.faults->max_flaps_down_airspeed,
1442 &flaps_down_fault_timer,
1444 FaultHandler::FAULT_FLAPS_SWITCH);
1446 // [INSTALL] appendix E 6.6.6
1447 if (conf.momentary_flap_override_enabled)
1448 handle_input_fault(mk_dinput(momentary_flap_override),
1449 &momentary_flap_override_fault_timer,
1451 FaultHandler::FAULT_MOMENTARY_FLAP_OVERRIDE_INVALID);
1453 // [INSTALL] appendix E 6.6.7
1454 handle_input_fault(mk_dinput(self_test),
1455 &self_test_fault_timer,
1457 FaultHandler::FAULT_SELF_TEST_INVALID);
1459 // [INSTALL] appendix E 6.6.13
1460 handle_input_fault(mk_dinput(glideslope_cancel),
1461 &glideslope_cancel_fault_timer,
1463 FaultHandler::FAULT_GLIDESLOPE_CANCEL_INVALID);
1465 // [INSTALL] appendix E 6.6.25
1466 if (momentary_steep_approach_enabled())
1467 handle_input_fault(mk_dinput(steep_approach),
1468 &steep_approach_fault_timer,
1470 FaultHandler::FAULT_STEEP_APPROACH_INVALID);
1472 // [INSTALL] appendix E 6.6.27
1473 if (conf.gpws_inhibit_enabled)
1474 handle_input_fault(mk_dinput(gpws_inhibit),
1475 &gpws_inhibit_fault_timer,
1477 FaultHandler::FAULT_GPWS_INHIBIT);
1479 // [INSTALL] does not specify a fault for this one, but it makes
1480 // sense to have it behave like GPWS inhibit
1481 handle_input_fault(mk_dinput(ta_tcf_inhibit),
1482 &ta_tcf_inhibit_fault_timer,
1484 FaultHandler::FAULT_TA_TCF_INHIBIT);
1486 // [PILOT] page 48: "In the event that required data for a
1487 // particular function is not available, then that function is
1488 // automatically inhibited and annunciated"
1490 handle_input_fault(mk_data(radio_altitude).ncd
1491 || mk_ainput(uncorrected_barometric_altitude).ncd
1492 || mk_data(barometric_altitude_rate).ncd
1493 || mk_ainput(computed_airspeed).ncd
1494 || mk_data(terrain_clearance).ncd,
1495 FaultHandler::FAULT_MODES14_INPUTS_INVALID);
1497 if (! mk_dinput(glideslope_inhibit))
1498 handle_input_fault(mk_data(radio_altitude).ncd,
1499 FaultHandler::FAULT_MODE5_INPUTS_INVALID);
1501 if (mk->mode6_handler.altitude_callouts_enabled())
1502 handle_input_fault(mk->mode6_handler.conf.above_field_voice
1503 ? (mk_data(gps_latitude).ncd
1504 || mk_data(gps_longitude).ncd
1505 || mk_data(geometric_altitude).ncd)
1506 : mk_data(radio_altitude).ncd,
1507 FaultHandler::FAULT_MODE6_INPUTS_INVALID);
1509 if (mk->mode6_handler.conf.bank_angle_enabled)
1510 handle_input_fault(mk_data(roll_angle).ncd,
1511 FaultHandler::FAULT_BANK_ANGLE_INPUTS_INVALID);
1513 if (mk->tcf_handler.conf.enabled)
1514 handle_input_fault(mk_data(radio_altitude).ncd
1515 || mk_data(geometric_altitude).ncd
1516 || mk_data(gps_latitude).ncd
1517 || mk_data(gps_longitude).ncd
1518 || mk_data(gps_vertical_figure_of_merit).ncd,
1519 FaultHandler::FAULT_TCF_INPUTS_INVALID);
1523 MK_VIII::IOHandler::update_alternate_discrete_input (bool *ptr)
1525 assert(mk->system_handler.state == SystemHandler::STATE_ON);
1527 if (ptr == &mk_dinput(mode6_low_volume))
1529 if (mk->configuration_module.state == ConfigurationModule::STATE_OK
1530 && mk->self_test_handler.state == SelfTestHandler::STATE_NONE)
1531 mk->mode6_handler.set_volume(*ptr ? modify_amplitude(1.0, -6) : 1.0);
1533 else if (ptr == &mk_dinput(audio_inhibit))
1535 if (mk->configuration_module.state == ConfigurationModule::STATE_OK
1536 && mk->self_test_handler.state == SelfTestHandler::STATE_NONE)
1537 mk->voice_player.set_volume(*ptr ? 0.0 : mk->voice_player.conf.volume);
1542 MK_VIII::IOHandler::update_internal_latches ()
1544 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
1548 if (conf.momentary_flap_override_enabled
1549 && mk_doutput(flap_override)
1550 && ! mk->state_handler.takeoff
1551 && (mk_data(radio_altitude).ncd || mk_data(radio_altitude).get() <= 50))
1552 mk_doutput(flap_override) = false;
1555 if (momentary_steep_approach_enabled())
1557 if (mk_doutput(steep_approach)
1558 && ! mk->state_handler.takeoff
1559 && ((last_landing_gear && ! mk_dinput(landing_gear))
1560 || (last_real_flaps_down && ! real_flaps_down())))
1561 // gear or flaps raised during approach: it's a go-around
1562 mk_doutput(steep_approach) = false;
1564 last_landing_gear = mk_dinput(landing_gear);
1565 last_real_flaps_down = real_flaps_down();
1569 if (mk_doutput(glideslope_cancel)
1570 && (mk_data(glideslope_deviation_dots).ncd
1571 || mk_data(radio_altitude).ncd
1572 || mk_data(radio_altitude).get() > 2000
1573 || mk_data(radio_altitude).get() < 30))
1574 mk_doutput(glideslope_cancel) = false;
1578 MK_VIII::IOHandler::update_egpws_alert_discrete_1 ()
1580 if (mk->voice_player.voice)
1585 VoicePlayer::Voice *voice;
1587 { 11, mk_voice(sink_rate_pause_sink_rate) },
1588 { 12, mk_voice(pull_up) },
1589 { 13, mk_voice(terrain) },
1590 { 13, mk_voice(terrain_pause_terrain) },
1591 { 14, mk_voice(dont_sink_pause_dont_sink) },
1592 { 15, mk_voice(too_low_gear) },
1593 { 16, mk_voice(too_low_flaps) },
1594 { 17, mk_voice(too_low_terrain) },
1595 { 18, mk_voice(soft_glideslope) },
1596 { 18, mk_voice(hard_glideslope) },
1597 { 19, mk_voice(minimums_minimums) }
1600 for (size_t i = 0; i < n_elements(voices); i++)
1601 if (voices[i].voice == mk->voice_player.voice)
1603 mk_aoutput(egpws_alert_discrete_1) = 1 << voices[i].bit;
1608 mk_aoutput(egpws_alert_discrete_1) = 0;
1612 MK_VIII::IOHandler::update_egpwc_logic_discretes ()
1614 mk_aoutput(egpwc_logic_discretes) = 0;
1616 if (mk->state_handler.takeoff)
1617 mk_aoutput(egpwc_logic_discretes) |= 1 << 18;
1622 unsigned int alerts;
1624 { 13, mk_alert(TCF_TOO_LOW_TERRAIN) },
1625 { 19, mk_alert(MODE1_SINK_RATE) },
1626 { 20, mk_alert(MODE1_PULL_UP) },
1627 { 21, mk_alert(MODE2A_PREFACE) | mk_alert(MODE2B_PREFACE) | mk_alert(MODE2B_LANDING_MODE) | mk_alert(MODE2A_ALTITUDE_GAIN_TERRAIN_CLOSING) },
1628 { 22, mk_alert(MODE2A) | mk_alert(MODE2B) },
1629 { 23, mk_alert(MODE3) },
1630 { 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) },
1631 { 25, mk_alert(MODE5_SOFT) | mk_alert(MODE5_HARD) }
1634 for (size_t i = 0; i < n_elements(logic); i++)
1635 if (mk_test_alerts(logic[i].alerts))
1636 mk_aoutput(egpwc_logic_discretes) |= 1 << logic[i].bit;
1640 MK_VIII::IOHandler::update_mode6_callouts_discrete_1 ()
1642 if (mk->voice_player.voice)
1647 VoicePlayer::Voice *voice;
1649 { 11, mk_voice(minimums_minimums) },
1650 { 16, mk_altitude_voice(Mode6Handler::ALTITUDE_CALLOUT_10) },
1651 { 17, mk_altitude_voice(Mode6Handler::ALTITUDE_CALLOUT_20) },
1652 { 18, mk_altitude_voice(Mode6Handler::ALTITUDE_CALLOUT_30) },
1653 { 19, mk_altitude_voice(Mode6Handler::ALTITUDE_CALLOUT_40) },
1654 { 20, mk_altitude_voice(Mode6Handler::ALTITUDE_CALLOUT_50) },
1655 { 23, mk_altitude_voice(Mode6Handler::ALTITUDE_CALLOUT_100) },
1656 { 24, mk_altitude_voice(Mode6Handler::ALTITUDE_CALLOUT_200) },
1657 { 25, mk_altitude_voice(Mode6Handler::ALTITUDE_CALLOUT_300) }
1660 for (size_t i = 0; i < n_elements(voices); i++)
1661 if (voices[i].voice == mk->voice_player.voice)
1663 mk_aoutput(mode6_callouts_discrete_1) = 1 << voices[i].bit;
1668 mk_aoutput(mode6_callouts_discrete_1) = 0;
1672 MK_VIII::IOHandler::update_mode6_callouts_discrete_2 ()
1674 if (mk->voice_player.voice)
1679 VoicePlayer::Voice *voice;
1681 { 11, mk_altitude_voice(Mode6Handler::ALTITUDE_CALLOUT_400) },
1682 { 12, mk_altitude_voice(Mode6Handler::ALTITUDE_CALLOUT_500) },
1683 { 13, mk_altitude_voice(Mode6Handler::ALTITUDE_CALLOUT_1000) },
1684 { 18, mk_voice(bank_angle_pause_bank_angle) },
1685 { 18, mk_voice(bank_angle_pause_bank_angle_3) },
1686 { 23, mk_voice(five_hundred_above) }
1689 for (size_t i = 0; i < n_elements(voices); i++)
1690 if (voices[i].voice == mk->voice_player.voice)
1692 mk_aoutput(mode6_callouts_discrete_2) = 1 << voices[i].bit;
1697 mk_aoutput(mode6_callouts_discrete_2) = 0;
1701 MK_VIII::IOHandler::update_egpws_alert_discrete_2 ()
1703 mk_aoutput(egpws_alert_discrete_2) = 1 << 27; // Terrain NA
1705 if (mk_doutput(glideslope_cancel))
1706 mk_aoutput(egpws_alert_discrete_2) |= 1 << 11;
1707 if (mk_doutput(gpws_alert))
1708 mk_aoutput(egpws_alert_discrete_2) |= 1 << 12;
1709 if (mk_doutput(gpws_warning))
1710 mk_aoutput(egpws_alert_discrete_2) |= 1 << 13;
1711 if (mk_doutput(gpws_inop))
1712 mk_aoutput(egpws_alert_discrete_2) |= 1 << 14;
1713 if (mk_doutput(audio_on))
1714 mk_aoutput(egpws_alert_discrete_2) |= 1 << 16;
1715 if (mk_doutput(tad_inop))
1716 mk_aoutput(egpws_alert_discrete_2) |= 1 << 24;
1717 if (mk->fault_handler.has_faults())
1718 mk_aoutput(egpws_alert_discrete_2) |= 1 << 25;
1722 MK_VIII::IOHandler::update_egpwc_alert_discrete_3 ()
1724 mk_aoutput(egpwc_alert_discrete_3) = 1 << 17 | 1 << 18;
1726 if (mk->fault_handler.faults[FaultHandler::FAULT_MODES14_INPUTS_INVALID])
1727 mk_aoutput(egpwc_alert_discrete_3) |= 1 << 11;
1728 if (mk->fault_handler.faults[FaultHandler::FAULT_MODE5_INPUTS_INVALID])
1729 mk_aoutput(egpwc_alert_discrete_3) |= 1 << 12;
1730 if (mk->fault_handler.faults[FaultHandler::FAULT_MODE6_INPUTS_INVALID])
1731 mk_aoutput(egpwc_alert_discrete_3) |= 1 << 13;
1732 if (mk->fault_handler.faults[FaultHandler::FAULT_BANK_ANGLE_INPUTS_INVALID])
1733 mk_aoutput(egpwc_alert_discrete_3) |= 1 << 14;
1734 if (mk_doutput(tad_inop))
1735 mk_aoutput(egpwc_alert_discrete_3) |= 1 << 16;
1739 MK_VIII::IOHandler::update_outputs ()
1741 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
1744 mk_doutput(audio_on) = ! mk_dinput(audio_inhibit)
1745 && mk->voice_player.voice
1746 && ! mk->voice_player.voice->element->silence;
1748 update_egpws_alert_discrete_1();
1749 update_egpwc_logic_discretes();
1750 update_mode6_callouts_discrete_1();
1751 update_mode6_callouts_discrete_2();
1752 update_egpws_alert_discrete_2();
1753 update_egpwc_alert_discrete_3();
1757 MK_VIII::IOHandler::get_lamp_output (Lamp lamp)
1761 case LAMP_GLIDESLOPE:
1762 return &mk_doutput(gpws_alert);
1765 return conf.lamp->format2 ? &mk_doutput(gpws_alert) : &mk_doutput(gpws_warning);
1768 return &mk_doutput(gpws_warning);
1771 assert_not_reached();
1777 MK_VIII::IOHandler::update_lamps ()
1779 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
1782 if (_lamp != LAMP_NONE && conf.lamp->flashing)
1784 // [SPEC] 6.9.3: 70 cycles per minute
1785 if (lamp_timer.elapsed() >= 60.0 / 70.0 / 2.0)
1787 bool *output = get_lamp_output(_lamp);
1788 *output = ! *output;
1795 MK_VIII::IOHandler::set_lamp (Lamp lamp)
1802 mk_doutput(gpws_warning) = false;
1803 mk_doutput(gpws_alert) = false;
1805 if (lamp != LAMP_NONE)
1807 *get_lamp_output(lamp) = true;
1813 MK_VIII::IOHandler::gpws_inhibit () const
1815 return conf.gpws_inhibit_enabled && mk_dinput(gpws_inhibit);
1819 MK_VIII::IOHandler::real_flaps_down () const
1821 return conf.flap_reversal ? mk_dinput(landing_flaps) : ! mk_dinput(landing_flaps);
1825 MK_VIII::IOHandler::flaps_down () const
1827 return flap_override() ? true : real_flaps_down();
1831 MK_VIII::IOHandler::flap_override () const
1833 return conf.momentary_flap_override_enabled ? mk_doutput(flap_override) : false;
1837 MK_VIII::IOHandler::steep_approach () const
1839 if (conf.steep_approach_enabled)
1840 // If alternate action was configured in category 13, we return
1841 // the value of the input discrete (which should be connected to a
1842 // latching steep approach cockpit switch). Otherwise, we return
1843 // the value of the output discrete.
1844 return conf.alternate_steep_approach ? mk_dinput(steep_approach) : mk_doutput(steep_approach);
1850 MK_VIII::IOHandler::momentary_steep_approach_enabled () const
1852 return conf.steep_approach_enabled && ! conf.alternate_steep_approach;
1856 MK_VIII::IOHandler::tie_input (SGPropertyNode *node,
1861 mk->properties_handler.tie(node, (string("inputs/discretes/") + name).c_str(), RawValueMethodsData<MK_VIII::IOHandler, bool, bool *>(*this, input, &MK_VIII::IOHandler::get_discrete_input, &MK_VIII::IOHandler::set_discrete_input));
1863 mk->properties_handler.tie(node, (string("input-feeders/discretes/") + name).c_str(), SGRawValuePointer<bool>(feed));
1867 MK_VIII::IOHandler::tie_input (SGPropertyNode *node,
1869 Parameter<double> *input,
1872 mk->properties_handler.tie(node, (string("inputs/arinc429/") + name).c_str(), SGRawValuePointer<double>(input->get_pointer()));
1873 mk->properties_handler.tie(node, (string("inputs/arinc429/") + name + "-ncd").c_str(), SGRawValuePointer<bool>(&input->ncd));
1875 mk->properties_handler.tie(node, (string("input-feeders/arinc429/") + name).c_str(), SGRawValuePointer<bool>(feed));
1879 MK_VIII::IOHandler::tie_output (SGPropertyNode *node,
1883 SGPropertyNode *child = node->getNode((string("outputs/discretes/") + name).c_str(), true);
1885 mk->properties_handler.tie(child, SGRawValuePointer<bool>(output));
1886 child->setAttribute(SGPropertyNode::WRITE, false);
1890 MK_VIII::IOHandler::tie_output (SGPropertyNode *node,
1894 SGPropertyNode *child = node->getNode((string("outputs/arinc429/") + name).c_str(), true);
1896 mk->properties_handler.tie(child, SGRawValuePointer<int>(output));
1897 child->setAttribute(SGPropertyNode::WRITE, false);
1901 MK_VIII::IOHandler::bind (SGPropertyNode *node)
1903 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));
1905 tie_input(node, "landing-gear", &mk_dinput(landing_gear), &mk_dinput_feed(landing_gear));
1906 tie_input(node, "landing-flaps", &mk_dinput(landing_flaps), &mk_dinput_feed(landing_flaps));
1907 tie_input(node, "momentary-flap-override", &mk_dinput(momentary_flap_override));
1908 tie_input(node, "self-test", &mk_dinput(self_test));
1909 tie_input(node, "glideslope-inhibit", &mk_dinput(glideslope_inhibit), &mk_dinput_feed(glideslope_inhibit));
1910 tie_input(node, "glideslope-cancel", &mk_dinput(glideslope_cancel));
1911 tie_input(node, "decision-height", &mk_dinput(decision_height), &mk_dinput_feed(decision_height));
1912 tie_input(node, "mode6-low-volume", &mk_dinput(mode6_low_volume));
1913 tie_input(node, "audio-inhibit", &mk_dinput(audio_inhibit));
1914 tie_input(node, "ta-tcf-inhibit", &mk_dinput(ta_tcf_inhibit));
1915 tie_input(node, "autopilot-engaged", &mk_dinput(autopilot_engaged), &mk_dinput_feed(autopilot_engaged));
1916 tie_input(node, "steep-approach", &mk_dinput(steep_approach));
1917 tie_input(node, "gpws-inhibit", &mk_dinput(gpws_inhibit));
1919 tie_input(node, "uncorrected-barometric-altitude", &mk_ainput(uncorrected_barometric_altitude), &mk_ainput_feed(uncorrected_barometric_altitude));
1920 tie_input(node, "barometric-altitude-rate", &mk_ainput(barometric_altitude_rate), &mk_ainput_feed(barometric_altitude_rate));
1921 tie_input(node, "gps-altitude", &mk_ainput(gps_altitude));
1922 tie_input(node, "gps-latitude", &mk_ainput(gps_latitude));
1923 tie_input(node, "gps-longitude", &mk_ainput(gps_longitude));
1924 tie_input(node, "gps-vertical-figure-of-merit", &mk_ainput(gps_vertical_figure_of_merit));
1925 tie_input(node, "radio-altitude", &mk_ainput(radio_altitude), &mk_ainput_feed(radio_altitude));
1926 tie_input(node, "glideslope-deviation", &mk_ainput(glideslope_deviation), &mk_ainput_feed(glideslope_deviation));
1927 tie_input(node, "roll-angle", &mk_ainput(roll_angle), &mk_ainput_feed(roll_angle));
1928 tie_input(node, "localizer-deviation", &mk_ainput(localizer_deviation), &mk_ainput_feed(localizer_deviation));
1929 tie_input(node, "computed-airspeed", &mk_ainput(computed_airspeed), &mk_ainput_feed(computed_airspeed));
1930 tie_input(node, "decision-height", &mk_ainput(decision_height), &mk_ainput_feed(decision_height));
1932 tie_output(node, "gpws-warning", &mk_doutput(gpws_warning));
1933 tie_output(node, "gpws-alert", &mk_doutput(gpws_alert));
1934 tie_output(node, "audio-on", &mk_doutput(audio_on));
1935 tie_output(node, "gpws-inop", &mk_doutput(gpws_inop));
1936 tie_output(node, "tad-inop", &mk_doutput(tad_inop));
1937 tie_output(node, "flap-override", &mk_doutput(flap_override));
1938 tie_output(node, "glideslope-cancel", &mk_doutput(glideslope_cancel));
1939 tie_output(node, "steep-approach", &mk_doutput(steep_approach));
1941 tie_output(node, "egpws-alert-discrete-1", &mk_aoutput(egpws_alert_discrete_1));
1942 tie_output(node, "egpwc-logic-discretes", &mk_aoutput(egpwc_logic_discretes));
1943 tie_output(node, "mode6-callouts-discrete-1", &mk_aoutput(mode6_callouts_discrete_1));
1944 tie_output(node, "mode6-callouts-discrete-2", &mk_aoutput(mode6_callouts_discrete_2));
1945 tie_output(node, "egpws-alert-discrete-2", &mk_aoutput(egpws_alert_discrete_2));
1946 tie_output(node, "egpwc-alert-discrete-3", &mk_aoutput(egpwc_alert_discrete_3));
1950 MK_VIII::IOHandler::get_discrete_input (bool *ptr) const
1956 MK_VIII::IOHandler::set_discrete_input (bool *ptr, bool value)
1961 if (mk->system_handler.state == SystemHandler::STATE_ON)
1963 if (ptr == &mk_dinput(momentary_flap_override))
1965 if (mk->configuration_module.state == ConfigurationModule::STATE_OK
1966 && mk->self_test_handler.state == SelfTestHandler::STATE_NONE
1967 && conf.momentary_flap_override_enabled
1969 mk_doutput(flap_override) = ! mk_doutput(flap_override);
1971 else if (ptr == &mk_dinput(self_test))
1972 mk->self_test_handler.handle_button_event(value);
1973 else if (ptr == &mk_dinput(glideslope_cancel))
1975 if (mk->configuration_module.state == ConfigurationModule::STATE_OK
1976 && mk->self_test_handler.state == SelfTestHandler::STATE_NONE
1979 if (! mk_doutput(glideslope_cancel))
1983 // Although we are not called from update(), we are
1984 // sure that the inputs we use here are defined,
1985 // since state is STATE_ON.
1987 if (! mk_data(glideslope_deviation_dots).ncd
1988 && ! mk_data(radio_altitude).ncd
1989 && mk_data(radio_altitude).get() <= 2000
1990 && mk_data(radio_altitude).get() >= 30)
1991 mk_doutput(glideslope_cancel) = true;
1993 // else do nothing ([PILOT] page 22: "Glideslope Cancel
1994 // can not be deselected (reset) by again pressing the
1995 // Glideslope Cancel switch.")
1998 else if (ptr == &mk_dinput(steep_approach))
2000 if (mk->configuration_module.state == ConfigurationModule::STATE_OK
2001 && mk->self_test_handler.state == SelfTestHandler::STATE_NONE
2002 && momentary_steep_approach_enabled()
2004 mk_doutput(steep_approach) = ! mk_doutput(steep_approach);
2010 if (mk->system_handler.state == SystemHandler::STATE_ON)
2011 update_alternate_discrete_input(ptr);
2015 MK_VIII::IOHandler::present_status_section (const char *name)
2017 printf("%s\n", name);
2021 MK_VIII::IOHandler::present_status_item (const char *name, const char *value)
2024 printf("\t%-32s %s\n", name, value);
2026 printf("\t%s\n", name);
2030 MK_VIII::IOHandler::present_status_subitem (const char *name)
2032 printf("\t\t%s\n", name);
2036 MK_VIII::IOHandler::present_status ()
2040 if (mk->system_handler.state != SystemHandler::STATE_ON)
2043 present_status_section("EGPWC CONFIGURATION");
2044 present_status_item("PART NUMBER:", "965-1220-000"); // [SPEC] 1.1
2045 present_status_item("MOD STATUS:", "N/A");
2046 present_status_item("SERIAL NUMBER:", "N/A");
2048 present_status_item("APPLICATION S/W VERSION:", FLIGHTGEAR_VERSION);
2049 present_status_item("TERRAIN DATABASE VERSION:", FLIGHTGEAR_VERSION);
2050 present_status_item("ENVELOPE MOD DATABASE VERSION:", "N/A");
2051 present_status_item("BOOT CODE VERSION:", FLIGHTGEAR_VERSION);
2054 present_status_section("CURRENT FAULTS");
2055 if (mk->configuration_module.state == ConfigurationModule::STATE_OK && ! mk->fault_handler.has_faults())
2056 present_status_item("NO FAULTS");
2059 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
2061 present_status_item("GPWS COMPUTER FAULTS:");
2062 switch (mk->configuration_module.state)
2064 case ConfigurationModule::STATE_INVALID_DATABASE:
2065 present_status_subitem("APPLICATION DATABASE FAILED");
2068 case ConfigurationModule::STATE_INVALID_AIRCRAFT_TYPE:
2069 present_status_subitem("CONFIGURATION TYPE INVALID");
2073 assert_not_reached();
2079 present_status_item("GPWS COMPUTER OK");
2080 present_status_item("GPWS EXTERNAL FAULTS:");
2082 static const char *fault_names[] = {
2083 "ALL MODES INHIBIT",
2086 "MOMENTARY FLAP OVERRIDE INVALID",
2087 "SELF TEST INVALID",
2088 "GLIDESLOPE CANCEL INVALID",
2089 "STEEP APPROACH INVALID",
2092 "MODES 1-4 INPUTS INVALID",
2093 "MODE 5 INPUTS INVALID",
2094 "MODE 6 INPUTS INVALID",
2095 "BANK ANGLE INPUTS INVALID",
2096 "TCF INPUTS INVALID"
2099 for (size_t i = 0; i < n_elements(fault_names); i++)
2100 if (mk->fault_handler.faults[i])
2101 present_status_subitem(fault_names[i]);
2106 present_status_section("CONFIGURATION:");
2108 static const char *category_names[] = {
2111 "POSITION INPUT TYPE",
2112 "CALLOUTS OPTION TYPE",
2114 "TERRAIN DISPLAY TYPE",
2116 "RADIO ALTITUDE TYPE",
2117 "NAVIGATION INPUT TYPE",
2119 "MAGNETIC HEADING TYPE",
2120 "WINDSHEAR INPUT TYPE",
2125 for (size_t i = 0; i < n_elements(category_names); i++)
2127 std::ostringstream value;
2128 value << "= " << mk->configuration_module.effective_categories[i];
2129 present_status_item(category_names[i], value.str().c_str());
2134 MK_VIII::IOHandler::get_present_status () const
2140 MK_VIII::IOHandler::set_present_status (bool value)
2142 if (value) present_status();
2146 ///////////////////////////////////////////////////////////////////////////////
2147 // VoicePlayer ////////////////////////////////////////////////////////////////
2148 ///////////////////////////////////////////////////////////////////////////////
2151 MK_VIII::VoicePlayer::Speaker::bind (SGPropertyNode *node)
2153 // uses xmlsound property names
2154 tie(node, "volume", &volume);
2155 tie(node, "pitch", &pitch);
2159 MK_VIII::VoicePlayer::Speaker::update_configuration ()
2161 map< string, SGSharedPtr<SGSoundSample> >::iterator iter;
2162 for (iter = player->samples.begin(); iter != player->samples.end(); iter++)
2164 SGSoundSample *sample = (*iter).second;
2166 sample->set_pitch(pitch);
2170 player->voice->volume_changed();
2173 MK_VIII::VoicePlayer::Voice::~Voice ()
2175 for (iter = elements.begin(); iter != elements.end(); iter++)
2176 delete *iter; // we owned the element
2181 MK_VIII::VoicePlayer::Voice::play ()
2183 iter = elements.begin();
2186 element->play(get_volume());
2190 MK_VIII::VoicePlayer::Voice::stop (bool now)
2194 if (now || element->silence)
2200 iter = elements.end() - 1; // stop after the current element finishes
2205 MK_VIII::VoicePlayer::Voice::set_volume (float _volume)
2212 MK_VIII::VoicePlayer::Voice::volume_changed ()
2215 element->set_volume(get_volume());
2219 MK_VIII::VoicePlayer::Voice::update ()
2223 if (! element->is_playing())
2225 if (++iter == elements.end())
2230 element->play(get_volume());
2236 MK_VIII::VoicePlayer::~VoicePlayer ()
2238 vector<Voice *>::iterator iter1;
2239 for (iter1 = _voices.begin(); iter1 != _voices.end(); iter1++)
2246 MK_VIII::VoicePlayer::init ()
2248 #define STDPAUSE 0.75 // [SPEC] 6.4.4: "the standard 0.75 second delay"
2250 SGSoundMgr *smgr = globals->get_soundmgr();
2251 _sgr = smgr->find("avionics", true);
2252 _sgr->tie_to_listener();
2254 make_voice(&voices.application_data_base_failed, "application-data-base-failed");
2255 make_voice(&voices.bank_angle, "bank-angle");
2256 make_voice(&voices.bank_angle_bank_angle, "bank-angle", "bank-angle");
2257 make_voice(&voices.bank_angle_bank_angle_3, "bank-angle", "bank-angle", 3.0);
2258 make_voice(&voices.bank_angle_inop, "bank-angle-inop");
2259 make_voice(&voices.bank_angle_pause_bank_angle, "bank-angle", STDPAUSE, "bank-angle");
2260 make_voice(&voices.bank_angle_pause_bank_angle_3, "bank-angle", STDPAUSE, "bank-angle", 3.0);
2261 make_voice(&voices.callouts_inop, "callouts-inop");
2262 make_voice(&voices.configuration_type_invalid, "configuration-type-invalid");
2263 make_voice(&voices.dont_sink, "dont-sink");
2264 make_voice(&voices.dont_sink_pause_dont_sink, "dont-sink", STDPAUSE, "dont-sink");
2265 make_voice(&voices.five_hundred_above, "500-above");
2266 make_voice(&voices.glideslope, "glideslope");
2267 make_voice(&voices.glideslope_inop, "glideslope-inop");
2268 make_voice(&voices.gpws_inop, "gpws-inop");
2269 make_voice(&voices.hard_glideslope, "glideslope", "glideslope", 3.0);
2270 make_voice(&voices.minimums, "minimums");
2271 make_voice(&voices.minimums_minimums, "minimums", "minimums");
2272 make_voice(&voices.pull_up, "pull-up");
2273 make_voice(&voices.sink_rate, "sink-rate");
2274 make_voice(&voices.sink_rate_pause_sink_rate, "sink-rate", STDPAUSE, "sink-rate");
2275 make_voice(&voices.soft_glideslope, new Voice::SampleElement(get_sample("glideslope"), modify_amplitude(1.0, -6)));
2276 make_voice(&voices.terrain, "terrain");
2277 make_voice(&voices.terrain_pause_terrain, "terrain", STDPAUSE, "terrain");
2278 make_voice(&voices.too_low_flaps, "too-low-flaps");
2279 make_voice(&voices.too_low_gear, "too-low-gear");
2280 make_voice(&voices.too_low_terrain, "too-low-terrain");
2282 for (unsigned i = 0; i < n_altitude_callouts; i++)
2284 std::ostringstream name;
2285 name << "altitude-" << mk->mode6_handler.altitude_callout_definitions[i];
2286 make_voice(&voices.altitude_callouts[i], name.str().c_str());
2289 speaker.update_configuration();
2293 MK_VIII::VoicePlayer::get_sample (const char *name)
2295 std::ostringstream refname;
2296 refname << mk->name << "[" << mk->num << "]" << "/" << name;
2298 SGSoundSample *sample = _sgr->find(refname.str());
2301 string filename = "Sounds/mk-viii/" + string(name) + ".wav";
2304 sample = new SGSoundSample(filename.c_str(), SGPath());
2306 catch (const sg_exception &e)
2308 SG_LOG(SG_INSTR, SG_ALERT, "Error loading MK VIII sound sample \"" + filename + "\": " + e.getFormattedMessage());
2312 _sgr->add(sample, refname.str());
2313 samples[refname.str()] = sample;
2320 MK_VIII::VoicePlayer::play (Voice *_voice, unsigned int flags)
2324 if (test_bits(flags, PLAY_NOW) || ! voice || voice->element->silence)
2330 looped = test_bits(flags, PLAY_LOOPED);
2333 next_looped = false;
2339 next_voice = _voice;
2340 next_looped = test_bits(flags, PLAY_LOOPED);
2345 MK_VIII::VoicePlayer::stop (unsigned int flags)
2349 voice->stop(test_bits(flags, STOP_NOW));
2359 MK_VIII::VoicePlayer::set_volume (float _volume)
2363 voice->volume_changed();
2367 MK_VIII::VoicePlayer::update ()
2375 if (! voice->element || voice->element->silence)
2378 looped = next_looped;
2381 next_looped = false;
2388 if (! voice->element)
2399 ///////////////////////////////////////////////////////////////////////////////
2400 // SelfTestHandler ////////////////////////////////////////////////////////////
2401 ///////////////////////////////////////////////////////////////////////////////
2404 MK_VIII::SelfTestHandler::_was_here (int position)
2406 if (position > current)
2415 MK_VIII::SelfTestHandler::Action
2416 MK_VIII::SelfTestHandler::sleep (double duration)
2418 Action _action = { ACTION_SLEEP, duration, NULL };
2422 MK_VIII::SelfTestHandler::Action
2423 MK_VIII::SelfTestHandler::play (VoicePlayer::Voice *voice)
2425 mk->voice_player.play(voice);
2426 Action _action = { ACTION_VOICE, 0, NULL };
2430 MK_VIII::SelfTestHandler::Action
2431 MK_VIII::SelfTestHandler::discrete_on (bool *discrete, double duration)
2434 return sleep(duration);
2437 MK_VIII::SelfTestHandler::Action
2438 MK_VIII::SelfTestHandler::discrete_on_off (bool *discrete, double duration)
2441 Action _action = { ACTION_SLEEP | ACTION_DISCRETE_ON_OFF, duration, discrete };
2445 MK_VIII::SelfTestHandler::Action
2446 MK_VIII::SelfTestHandler::discrete_on_off (bool *discrete, VoicePlayer::Voice *voice)
2449 mk->voice_player.play(voice);
2450 Action _action = { ACTION_VOICE | ACTION_DISCRETE_ON_OFF, 0, discrete };
2454 MK_VIII::SelfTestHandler::Action
2455 MK_VIII::SelfTestHandler::done ()
2457 Action _action = { ACTION_DONE, 0, NULL };
2461 MK_VIII::SelfTestHandler::Action
2462 MK_VIII::SelfTestHandler::run ()
2464 // Note that "Terrain INOP" and "Terrain NA" are or'ed to the same
2465 // output discrete (see [SPEC] 6.9.3.5).
2467 #define was_here() was_here_offset(0)
2468 #define was_here_offset(offset) _was_here(__LINE__ * 20 + (offset))
2472 if (mk->configuration_module.state == ConfigurationModule::STATE_INVALID_DATABASE)
2473 return play(mk_voice(application_data_base_failed));
2474 else if (mk->configuration_module.state == ConfigurationModule::STATE_INVALID_AIRCRAFT_TYPE)
2475 return play(mk_voice(configuration_type_invalid));
2477 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
2481 return discrete_on(&mk_doutput(gpws_inop), 0.7);
2483 return sleep(0.7); // W/S INOP
2485 return discrete_on(&mk_doutput(tad_inop), 0.7);
2488 mk_doutput(gpws_inop) = false;
2489 // do not disable tad_inop since we must enable Terrain NA
2490 // do not disable W/S INOP since we do not emulate it
2491 return sleep(0.7); // Terrain NA
2495 mk_doutput(tad_inop) = false; // disable Terrain NA
2496 if (mk->io_handler.conf.momentary_flap_override_enabled)
2497 return discrete_on_off(&mk_doutput(flap_override), 1.0);
2500 return discrete_on_off(&mk_doutput(audio_on), 1.0);
2503 if (mk->io_handler.momentary_steep_approach_enabled())
2504 return discrete_on_off(&mk_doutput(steep_approach), 1.0);
2509 if (mk_dinput(glideslope_inhibit))
2510 goto glideslope_end;
2513 if (mk->fault_handler.faults[FaultHandler::FAULT_MODE5_INPUTS_INVALID])
2514 goto glideslope_inop;
2518 return discrete_on_off(&mk_doutput(gpws_alert), mk_voice(glideslope));
2520 return discrete_on_off(&mk_doutput(glideslope_cancel), 0.7);
2521 goto glideslope_end;
2524 return play(mk_voice(glideslope_inop));
2529 if (mk->fault_handler.faults[FaultHandler::FAULT_MODES14_INPUTS_INVALID])
2533 return discrete_on_off(&mk_doutput(gpws_warning), mk_voice(pull_up));
2537 return play(mk_voice(gpws_inop));
2542 if (mk_dinput(self_test)) // proceed to long self test
2547 if (mk->mode6_handler.conf.bank_angle_enabled
2548 && mk->fault_handler.faults[FaultHandler::FAULT_BANK_ANGLE_INPUTS_INVALID])
2549 return play(mk_voice(bank_angle_inop));
2553 if (mk->mode6_handler.altitude_callouts_enabled()
2554 && mk->fault_handler.faults[FaultHandler::FAULT_MODE6_INPUTS_INVALID])
2555 return play(mk_voice(callouts_inop));
2563 mk_doutput(gpws_inop) = true;
2564 // do not enable W/S INOP, since we do not emulate it
2565 mk_doutput(tad_inop) = true; // Terrain INOP, Terrain NA
2567 return play(mk_voice(sink_rate));
2570 return play(mk_voice(pull_up));
2572 return play(mk_voice(terrain));
2574 return play(mk_voice(pull_up));
2576 return play(mk_voice(dont_sink));
2578 return play(mk_voice(too_low_terrain));
2580 return play(mk->mode4_handler.conf.voice_too_low_gear);
2582 return play(mk_voice(too_low_flaps));
2584 return play(mk_voice(too_low_terrain));
2586 return play(mk_voice(glideslope));
2589 if (mk->mode6_handler.conf.bank_angle_enabled)
2590 return play(mk_voice(bank_angle));
2595 if (! mk->mode6_handler.altitude_callouts_enabled())
2596 goto callouts_disabled;
2600 if (mk->mode6_handler.conf.minimums_enabled)
2601 return play(mk_voice(minimums));
2605 if (mk->mode6_handler.conf.above_field_voice)
2606 return play(mk->mode6_handler.conf.above_field_voice);
2608 for (unsigned i = 0; i < n_altitude_callouts; i++)
2609 if (! was_here_offset(i))
2611 if (mk->mode6_handler.conf.altitude_callouts_enabled[i])
2612 return play(mk_altitude_voice(i));
2616 if (mk->mode6_handler.conf.smart_500_enabled)
2617 return play(mk_altitude_voice(Mode6Handler::ALTITUDE_CALLOUT_500));
2622 return play(mk_voice(minimums_minimums));
2627 if (mk->tcf_handler.conf.enabled)
2628 return play(mk_voice(too_low_terrain));
2635 MK_VIII::SelfTestHandler::start ()
2637 assert(state == STATE_START);
2639 memcpy(&saved_outputs, &mk->io_handler.outputs, sizeof(mk->io_handler.outputs));
2640 memset(&mk->io_handler.outputs, 0, sizeof(mk->io_handler.outputs));
2642 // [SPEC] 6.10.6: "The self-test results are annunciated, at 6db
2643 // lower than the normal audio level selected for the aircraft"
2644 mk->voice_player.set_volume(modify_amplitude(mk->voice_player.conf.volume, -6));
2646 if (mk_dinput(mode6_low_volume))
2647 mk->mode6_handler.set_volume(1.0);
2649 state = STATE_RUNNING;
2650 cancel = CANCEL_NONE;
2651 memset(&action, 0, sizeof(action));
2656 MK_VIII::SelfTestHandler::stop ()
2658 if (state != STATE_NONE)
2660 if (state != STATE_START)
2662 mk->voice_player.stop(VoicePlayer::STOP_NOW);
2663 mk->voice_player.set_volume(mk_dinput(audio_inhibit) ? 0.0 : mk->voice_player.conf.volume);
2665 if (mk_dinput(mode6_low_volume))
2666 mk->mode6_handler.set_volume(modify_amplitude(1.0, -6));
2668 memcpy(&mk->io_handler.outputs, &saved_outputs, sizeof(mk->io_handler.outputs));
2671 button_pressed = false;
2673 // reset self-test handler position
2679 MK_VIII::SelfTestHandler::handle_button_event (bool value)
2681 if (state == STATE_NONE)
2684 state = STATE_START;
2686 else if (state == STATE_START)
2689 state = STATE_NONE; // cancel the not-yet-started test
2691 else if (cancel == CANCEL_NONE)
2695 assert(! button_pressed);
2696 button_pressed = true;
2697 button_press_timestamp = globals->get_sim_time_sec();
2703 if (globals->get_sim_time_sec() - button_press_timestamp < 2)
2704 cancel = CANCEL_SHORT;
2706 cancel = CANCEL_LONG;
2713 MK_VIII::SelfTestHandler::update ()
2715 if (state == STATE_NONE)
2717 else if (state == STATE_START)
2719 if (mk->state_handler.ground && ! mk->io_handler.steep_approach())
2729 if (button_pressed && cancel == CANCEL_NONE)
2731 if (globals->get_sim_time_sec() - button_press_timestamp >= 2)
2732 cancel = CANCEL_LONG;
2736 if (! mk->state_handler.ground || cancel != CANCEL_NONE)
2742 if (test_bits(action.flags, ACTION_SLEEP))
2744 if (globals->get_sim_time_sec() - sleep_start < action.sleep_duration)
2747 if (test_bits(action.flags, ACTION_VOICE))
2749 if (mk->voice_player.voice)
2752 if (test_bits(action.flags, ACTION_DISCRETE_ON_OFF))
2753 *action.discrete = false;
2757 if (test_bits(action.flags, ACTION_SLEEP))
2758 sleep_start = globals->get_sim_time_sec();
2759 if (test_bits(action.flags, ACTION_DONE))
2768 ///////////////////////////////////////////////////////////////////////////////
2769 // AlertHandler ///////////////////////////////////////////////////////////////
2770 ///////////////////////////////////////////////////////////////////////////////
2773 MK_VIII::AlertHandler::select_voice_alerts (unsigned int test)
2775 if (has_alerts(test))
2777 voice_alerts = alerts & test;
2782 voice_alerts &= ~test;
2783 if (voice_alerts == 0)
2784 mk->voice_player.stop();
2791 MK_VIII::AlertHandler::boot ()
2797 MK_VIII::AlertHandler::reposition ()
2801 mk->io_handler.set_lamp(IOHandler::LAMP_NONE);
2802 mk->voice_player.stop(VoicePlayer::STOP_NOW);
2806 MK_VIII::AlertHandler::reset ()
2811 repeated_alerts = 0;
2812 altitude_callout_voice = NULL;
2816 MK_VIII::AlertHandler::update ()
2818 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
2821 // Lamps and voices are prioritized according to [SPEC] 6.9.2.
2823 // Voices which can interrupt other voices (VoicePlayer::PLAY_NOW)
2824 // are specified by [INSTALL] appendix E 5.3.5.
2826 // When a voice is overriden by a higher priority voice and the
2827 // overriding voice stops, we restore the previous voice if it was
2828 // looped (this is not specified by [SPEC] but seems to make sense).
2832 if (has_alerts(ALERT_MODE1_PULL_UP | ALERT_MODE2A | ALERT_MODE2B))
2833 mk->io_handler.set_lamp(IOHandler::LAMP_WARNING);
2834 else if (has_alerts(ALERT_MODE1_SINK_RATE
2835 | ALERT_MODE2A_PREFACE
2836 | ALERT_MODE2B_PREFACE
2837 | ALERT_MODE2A_ALTITUDE_GAIN_TERRAIN_CLOSING
2838 | ALERT_MODE2A_ALTITUDE_GAIN
2839 | ALERT_MODE2B_LANDING_MODE
2841 | ALERT_MODE4_TOO_LOW_FLAPS
2842 | ALERT_MODE4_TOO_LOW_GEAR
2843 | ALERT_MODE4AB_TOO_LOW_TERRAIN
2844 | ALERT_MODE4C_TOO_LOW_TERRAIN
2845 | ALERT_TCF_TOO_LOW_TERRAIN))
2846 mk->io_handler.set_lamp(IOHandler::LAMP_CAUTION);
2847 else if (has_alerts(ALERT_MODE5_SOFT | ALERT_MODE5_HARD))
2848 mk->io_handler.set_lamp(IOHandler::LAMP_GLIDESLOPE);
2850 mk->io_handler.set_lamp(IOHandler::LAMP_NONE);
2854 if (select_voice_alerts(ALERT_MODE1_PULL_UP))
2856 if (! has_old_alerts(ALERT_MODE1_PULL_UP))
2858 if (mk->voice_player.voice != mk_voice(sink_rate_pause_sink_rate))
2859 mk->voice_player.play(mk_voice(sink_rate), VoicePlayer::PLAY_NOW);
2860 mk->voice_player.play(mk_voice(pull_up), VoicePlayer::PLAY_LOOPED);
2863 else if (select_voice_alerts(ALERT_MODE2A_PREFACE | ALERT_MODE2B_PREFACE))
2865 if (! has_old_alerts(ALERT_MODE2A_PREFACE | ALERT_MODE2B_PREFACE))
2866 mk->voice_player.play(mk_voice(terrain_pause_terrain), VoicePlayer::PLAY_NOW);
2868 else if (select_voice_alerts(ALERT_MODE2A | ALERT_MODE2B))
2870 if (mk->voice_player.voice != mk_voice(pull_up))
2871 mk->voice_player.play(mk_voice(pull_up), VoicePlayer::PLAY_NOW | VoicePlayer::PLAY_LOOPED);
2873 else if (select_voice_alerts(ALERT_MODE2A_ALTITUDE_GAIN_TERRAIN_CLOSING | ALERT_MODE2B_LANDING_MODE))
2875 if (mk->voice_player.voice != mk_voice(terrain))
2876 mk->voice_player.play(mk_voice(terrain), VoicePlayer::PLAY_LOOPED);
2878 else if (select_voice_alerts(ALERT_MODE6_MINIMUMS))
2880 if (! has_old_alerts(ALERT_MODE6_MINIMUMS))
2881 mk->voice_player.play(mk_voice(minimums_minimums));
2883 else if (select_voice_alerts(ALERT_MODE4AB_TOO_LOW_TERRAIN | ALERT_MODE4C_TOO_LOW_TERRAIN))
2885 if (must_play_voice(ALERT_MODE4AB_TOO_LOW_TERRAIN | ALERT_MODE4C_TOO_LOW_TERRAIN))
2886 mk->voice_player.play(mk_voice(too_low_terrain));
2888 else if (select_voice_alerts(ALERT_TCF_TOO_LOW_TERRAIN))
2890 if (must_play_voice(ALERT_TCF_TOO_LOW_TERRAIN))
2891 mk->voice_player.play(mk_voice(too_low_terrain));
2893 else if (select_voice_alerts(ALERT_MODE6_ALTITUDE_CALLOUT))
2895 if (! has_old_alerts(ALERT_MODE6_ALTITUDE_CALLOUT))
2897 assert(altitude_callout_voice != NULL);
2898 mk->voice_player.play(altitude_callout_voice);
2899 altitude_callout_voice = NULL;
2902 else if (select_voice_alerts(ALERT_MODE4_TOO_LOW_GEAR))
2904 if (must_play_voice(ALERT_MODE4_TOO_LOW_GEAR))
2905 mk->voice_player.play(mk->mode4_handler.conf.voice_too_low_gear);
2907 else if (select_voice_alerts(ALERT_MODE4_TOO_LOW_FLAPS))
2909 if (must_play_voice(ALERT_MODE4_TOO_LOW_FLAPS))
2910 mk->voice_player.play(mk_voice(too_low_flaps));
2912 else if (select_voice_alerts(ALERT_MODE1_SINK_RATE))
2914 if (must_play_voice(ALERT_MODE1_SINK_RATE))
2915 mk->voice_player.play(mk_voice(sink_rate_pause_sink_rate));
2916 // [SPEC] 6.2.1: "During the time that the voice message for the
2917 // outer envelope is inhibited and the caution/warning lamp is
2918 // on, the Mode 5 alert message is allowed to annunciate for
2919 // excessive glideslope deviation conditions."
2920 else if (mk->voice_player.voice != mk_voice(sink_rate_pause_sink_rate)
2921 && mk->voice_player.next_voice != mk_voice(sink_rate_pause_sink_rate))
2923 if (has_alerts(ALERT_MODE5_HARD))
2925 voice_alerts |= ALERT_MODE5_HARD;
2926 if (mk->voice_player.voice != mk_voice(hard_glideslope))
2927 mk->voice_player.play(mk_voice(hard_glideslope), VoicePlayer::PLAY_LOOPED);
2929 else if (has_alerts(ALERT_MODE5_SOFT))
2931 voice_alerts |= ALERT_MODE5_SOFT;
2932 if (must_play_voice(ALERT_MODE5_SOFT))
2933 mk->voice_player.play(mk_voice(soft_glideslope));
2937 else if (select_voice_alerts(ALERT_MODE3))
2939 if (must_play_voice(ALERT_MODE3))
2940 mk->voice_player.play(mk_voice(dont_sink_pause_dont_sink));
2942 else if (select_voice_alerts(ALERT_MODE5_HARD))
2944 if (mk->voice_player.voice != mk_voice(hard_glideslope))
2945 mk->voice_player.play(mk_voice(hard_glideslope), VoicePlayer::PLAY_LOOPED);
2947 else if (select_voice_alerts(ALERT_MODE5_SOFT))
2949 if (must_play_voice(ALERT_MODE5_SOFT))
2950 mk->voice_player.play(mk_voice(soft_glideslope));
2952 else if (select_voice_alerts(ALERT_MODE6_LOW_BANK_ANGLE_3))
2954 if (mk->voice_player.voice != mk_voice(bank_angle_bank_angle_3))
2955 mk->voice_player.play(mk_voice(bank_angle_bank_angle_3), VoicePlayer::PLAY_LOOPED);
2957 else if (select_voice_alerts(ALERT_MODE6_HIGH_BANK_ANGLE_3))
2959 if (mk->voice_player.voice != mk_voice(bank_angle_pause_bank_angle_3))
2960 mk->voice_player.play(mk_voice(bank_angle_pause_bank_angle_3), VoicePlayer::PLAY_LOOPED);
2962 else if (select_voice_alerts(ALERT_MODE6_LOW_BANK_ANGLE_2))
2964 if (! has_old_alerts(ALERT_MODE6_LOW_BANK_ANGLE_2 | ALERT_MODE6_HIGH_BANK_ANGLE_2))
2965 mk->voice_player.play(mk_voice(bank_angle_bank_angle));
2967 else if (select_voice_alerts(ALERT_MODE6_HIGH_BANK_ANGLE_2))
2969 if (! has_old_alerts(ALERT_MODE6_LOW_BANK_ANGLE_2 | ALERT_MODE6_HIGH_BANK_ANGLE_2))
2970 mk->voice_player.play(mk_voice(bank_angle_pause_bank_angle));
2972 else if (select_voice_alerts(ALERT_MODE6_LOW_BANK_ANGLE_1))
2974 if (! has_old_alerts(ALERT_MODE6_LOW_BANK_ANGLE_1 | ALERT_MODE6_HIGH_BANK_ANGLE_1))
2975 mk->voice_player.play(mk_voice(bank_angle_bank_angle));
2977 else if (select_voice_alerts(ALERT_MODE6_HIGH_BANK_ANGLE_1))
2979 if (! has_old_alerts(ALERT_MODE6_LOW_BANK_ANGLE_1 | ALERT_MODE6_HIGH_BANK_ANGLE_1))
2980 mk->voice_player.play(mk_voice(bank_angle_pause_bank_angle));
2983 // remember all alerts voiced so far...
2984 old_alerts |= voice_alerts;
2985 // ... forget those no longer active
2986 old_alerts &= alerts;
2987 repeated_alerts = 0;
2991 MK_VIII::AlertHandler::set_alerts (unsigned int _alerts,
2993 VoicePlayer::Voice *_altitude_callout_voice)
2996 if (test_bits(flags, ALERT_FLAG_REPEAT))
2997 repeated_alerts |= _alerts;
2998 if (_altitude_callout_voice)
2999 altitude_callout_voice = _altitude_callout_voice;
3003 MK_VIII::AlertHandler::unset_alerts (unsigned int _alerts)
3006 repeated_alerts &= ~_alerts;
3007 if (_alerts & ALERT_MODE6_ALTITUDE_CALLOUT)
3008 altitude_callout_voice = NULL;
3011 ///////////////////////////////////////////////////////////////////////////////
3012 // StateHandler ///////////////////////////////////////////////////////////////
3013 ///////////////////////////////////////////////////////////////////////////////
3016 MK_VIII::StateHandler::update_ground ()
3020 if (! mk_ainput(computed_airspeed).ncd && mk_ainput(computed_airspeed).get() > 60
3021 && ! mk_data(radio_altitude).ncd && mk_data(radio_altitude).get() > 30)
3023 if (potentially_airborne_timer.start_or_elapsed() > 1)
3027 potentially_airborne_timer.stop();
3031 if (! mk_ainput(computed_airspeed).ncd && mk_ainput(computed_airspeed).get() < 40
3032 && ! mk_data(radio_altitude).ncd && mk_data(radio_altitude).get() < 30)
3038 MK_VIII::StateHandler::enter_ground ()
3041 mk->io_handler.enter_ground();
3043 // [SPEC] 6.0.1 does not specify this, but it seems to be an
3044 // omission; at this point, we must make sure that we are in takeoff
3045 // mode (otherwise the next takeoff might happen in approach mode).
3051 MK_VIII::StateHandler::leave_ground ()
3054 mk->mode2_handler.leave_ground();
3058 MK_VIII::StateHandler::update_takeoff ()
3062 // [SPEC] 6.0.2 is somewhat confusing: it mentions hardcoded
3063 // terrain clearance (500, 750) and airspeed (178, 200) values,
3064 // but it also mentions the mode 4A boundary, which varies as a
3065 // function of aircraft type. We consider that the mentioned
3066 // values are examples, and that we should use the mode 4A upper
3069 if (! mk_data(terrain_clearance).ncd
3070 && ! mk_ainput(computed_airspeed).ncd
3071 && mk_data(terrain_clearance).get() > mk->mode4_handler.get_upper_agl(mk->mode4_handler.conf.modes->ac))
3076 if (! mk_data(radio_altitude).ncd
3077 && mk_data(radio_altitude).get() < mk->mode4_handler.conf.modes->b->min_agl1
3078 && mk->io_handler.flaps_down()
3079 && mk_dinput(landing_gear))
3085 MK_VIII::StateHandler::enter_takeoff ()
3088 mk->io_handler.enter_takeoff();
3089 mk->mode2_handler.enter_takeoff();
3090 mk->mode3_handler.enter_takeoff();
3091 mk->mode6_handler.enter_takeoff();
3095 MK_VIII::StateHandler::leave_takeoff ()
3098 mk->mode6_handler.leave_takeoff();
3102 MK_VIII::StateHandler::post_reposition ()
3104 // Synchronize the state with the current situation.
3107 (! mk_ainput(computed_airspeed).ncd && mk_ainput(computed_airspeed).get() > 60
3108 && ! mk_data(radio_altitude).ncd && mk_data(radio_altitude).get() > 30);
3110 bool _takeoff = _ground;
3112 if (ground && ! _ground)
3114 else if (! ground && _ground)
3117 if (takeoff && ! _takeoff)
3119 else if (! takeoff && _takeoff)
3124 MK_VIII::StateHandler::update ()
3126 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
3133 ///////////////////////////////////////////////////////////////////////////////
3134 // Mode1Handler ///////////////////////////////////////////////////////////////
3135 ///////////////////////////////////////////////////////////////////////////////
3138 MK_VIII::Mode1Handler::get_pull_up_bias ()
3140 // [PILOT] page 54: "Steep Approach has priority over Flap Override
3141 // if selected simultaneously."
3143 if (mk->io_handler.steep_approach())
3145 else if (conf.envelopes->flap_override_bias && mk->io_handler.flap_override())
3152 MK_VIII::Mode1Handler::is_pull_up ()
3154 if (! mk->io_handler.gpws_inhibit()
3155 && ! mk->state_handler.ground // [1]
3156 && ! mk_data(radio_altitude).ncd
3157 && ! mk_data(barometric_altitude_rate).ncd
3158 && mk_data(radio_altitude).get() > conf.envelopes->min_agl)
3160 if (mk_data(radio_altitude).get() < conf.envelopes->pull_up_max_agl1)
3162 if (mk_data(radio_altitude).get() < conf.envelopes->pull_up_min_agl1(mk_data(barometric_altitude_rate).get() + get_pull_up_bias()))
3165 else if (mk_data(radio_altitude).get() < conf.envelopes->pull_up_max_agl2)
3167 if (mk_data(radio_altitude).get() < conf.envelopes->pull_up_min_agl2(mk_data(barometric_altitude_rate).get() + get_pull_up_bias()))
3176 MK_VIII::Mode1Handler::update_pull_up ()
3180 if (! mk_test_alert(MODE1_PULL_UP))
3182 // [SPEC] 6.2.1: at least one sink rate must be issued
3183 // before switching to pull up; if no sink rate has
3184 // occurred, a 0.2 second delay is used.
3185 if (mk->voice_player.voice == mk_voice(sink_rate_pause_sink_rate)
3186 || pull_up_timer.start_or_elapsed() >= 0.2)
3187 mk_set_alerts(mk_alert(MODE1_PULL_UP));
3192 pull_up_timer.stop();
3193 mk_unset_alerts(mk_alert(MODE1_PULL_UP));
3198 MK_VIII::Mode1Handler::get_sink_rate_bias ()
3200 // [PILOT] page 54: "Steep Approach has priority over Flap Override
3201 // if selected simultaneously."
3203 if (mk->io_handler.steep_approach())
3205 else if (conf.envelopes->flap_override_bias && mk->io_handler.flap_override())
3207 else if (! mk_data(glideslope_deviation_dots).ncd)
3211 if (mk_data(glideslope_deviation_dots).get() <= -2)
3213 else if (mk_data(glideslope_deviation_dots).get() < 0)
3214 bias = -150 * mk_data(glideslope_deviation_dots).get();
3216 if (mk_data(radio_altitude).get() < 100)
3217 bias *= 0.01 * mk_data(radio_altitude).get();
3226 MK_VIII::Mode1Handler::is_sink_rate ()
3228 return ! mk->io_handler.gpws_inhibit()
3229 && ! mk->state_handler.ground // [1]
3230 && ! mk_data(radio_altitude).ncd
3231 && ! mk_data(barometric_altitude_rate).ncd
3232 && mk_data(radio_altitude).get() > conf.envelopes->min_agl
3233 && mk_data(radio_altitude).get() < 2450
3234 && mk_data(radio_altitude).get() < -572 - 0.6035 * (mk_data(barometric_altitude_rate).get() + get_sink_rate_bias());
3238 MK_VIII::Mode1Handler::get_sink_rate_tti ()
3240 return mk_data(radio_altitude).get() / fabs(mk_data(barometric_altitude_rate).get());
3244 MK_VIII::Mode1Handler::update_sink_rate ()
3248 if (mk_test_alert(MODE1_SINK_RATE))
3250 double tti = get_sink_rate_tti();
3251 if (tti <= sink_rate_tti * 0.8)
3253 // ~20% degradation, warn again and store the new reference tti
3254 sink_rate_tti = tti;
3255 mk_repeat_alert(mk_alert(MODE1_SINK_RATE));
3260 if (sink_rate_timer.start_or_elapsed() >= 0.8)
3262 mk_set_alerts(mk_alert(MODE1_SINK_RATE));
3263 sink_rate_tti = get_sink_rate_tti();
3269 sink_rate_timer.stop();
3270 mk_unset_alerts(mk_alert(MODE1_SINK_RATE));
3275 MK_VIII::Mode1Handler::update ()
3277 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
3284 ///////////////////////////////////////////////////////////////////////////////
3285 // Mode2Handler ///////////////////////////////////////////////////////////////
3286 ///////////////////////////////////////////////////////////////////////////////
3289 MK_VIII::Mode2Handler::ClosureRateFilter::limit_radio_altitude_rate (double r)
3291 // Limit radio altitude rate according to aircraft configuration,
3292 // allowing maximum sensitivity during cruise while providing
3293 // progressively less sensitivity during the landing phases of
3296 if (! mk_data(glideslope_deviation_dots).ncd && fabs(mk_data(glideslope_deviation_dots).get()) <= 2)
3297 { // gs deviation <= +- 2 dots
3298 if (mk_dinput(landing_gear) && mk->io_handler.flaps_down())
3299 SG_CLAMP_RANGE(r, -1000.0, 3000.0);
3300 else if (mk_dinput(landing_gear) || mk->io_handler.flaps_down())
3301 SG_CLAMP_RANGE(r, 0.0, 4000.0);
3303 SG_CLAMP_RANGE(r, 1000.0, 5000.0);
3306 { // no ILS, or gs deviation > +- 2 dots
3307 if (mk_dinput(landing_gear) && mk->io_handler.flaps_down())
3308 SG_CLAMP_RANGE(r, 0.0, 4000.0);
3309 else if (mk_dinput(landing_gear) || mk->io_handler.flaps_down())
3310 SG_CLAMP_RANGE(r, 1000.0, 5000.0);
3318 MK_VIII::Mode2Handler::ClosureRateFilter::init ()
3321 last_ra.set(&mk_data(radio_altitude));
3322 last_ba.set(&mk_ainput(uncorrected_barometric_altitude));
3329 MK_VIII::Mode2Handler::ClosureRateFilter::update ()
3331 double elapsed = timer.start_or_elapsed();
3335 if (! mk_data(radio_altitude).ncd && ! mk_ainput(uncorrected_barometric_altitude).ncd)
3337 if (! last_ra.ncd && ! last_ba.ncd)
3339 // compute radio and barometric altitude rates (positive value = descent)
3340 double ra_rate = -(mk_data(radio_altitude).get() - last_ra.get()) / elapsed * 60;
3341 double ba_rate = -(mk_ainput(uncorrected_barometric_altitude).get() - last_ba.get()) / elapsed * 60;
3343 // limit radio altitude rate according to aircraft configuration
3344 ra_rate = limit_radio_altitude_rate(ra_rate);
3346 // apply a low-pass filter to the radio altitude rate
3347 ra_rate = ra_filter.filter(ra_rate);
3348 // apply a high-pass filter to the barometric altitude rate
3349 ba_rate = ba_filter.filter(ba_rate);
3351 // combine both rates to obtain a closure rate
3352 output.set(ra_rate + ba_rate);
3365 last_ra.set(&mk_data(radio_altitude));
3366 last_ba.set(&mk_ainput(uncorrected_barometric_altitude));
3370 MK_VIII::Mode2Handler::b_conditions ()
3372 return mk->io_handler.flaps_down()
3373 || (! mk_data(glideslope_deviation_dots).ncd && fabs(mk_data(glideslope_deviation_dots).get()) < 2)
3374 || takeoff_timer.running;
3378 MK_VIII::Mode2Handler::is_a ()
3380 if (! mk->io_handler.gpws_inhibit()
3381 && ! mk->state_handler.ground // [1]
3382 && ! mk_data(radio_altitude).ncd
3383 && ! mk_ainput(computed_airspeed).ncd
3384 && ! closure_rate_filter.output.ncd
3385 && ! b_conditions())
3387 if (mk_data(radio_altitude).get() < 1220)
3389 if (mk_data(radio_altitude).get() < -1579 + 0.7895 * closure_rate_filter.output.get())
3396 if (mk_ainput(computed_airspeed).get() <= conf->airspeed1)
3398 else if (mk_ainput(computed_airspeed).get() >= conf->airspeed2)
3401 upper_limit = 1650 + 8.9 * (mk_ainput(computed_airspeed).get() - conf->airspeed1);
3403 if (mk_data(radio_altitude).get() < upper_limit)
3405 if (mk_data(radio_altitude).get() < 522 + 0.1968 * closure_rate_filter.output.get())
3415 MK_VIII::Mode2Handler::is_b ()
3417 if (! mk->io_handler.gpws_inhibit()
3418 && ! mk->state_handler.ground // [1]
3419 && ! mk_data(radio_altitude).ncd
3420 && ! mk_data(barometric_altitude_rate).ncd
3421 && ! closure_rate_filter.output.ncd
3423 && mk_data(radio_altitude).get() < 789)
3427 if (mk->io_handler.flaps_down())
3429 if (mk_data(barometric_altitude_rate).get() > -400)
3431 else if (mk_data(barometric_altitude_rate).get() < -1000)
3434 lower_limit = -66.777 - 0.667 * mk_data(barometric_altitude_rate).get();
3439 if (mk_data(radio_altitude).get() > lower_limit)
3441 if (mk_data(radio_altitude).get() < -1579 + 0.7895 * closure_rate_filter.output.get())
3450 MK_VIII::Mode2Handler::check_pull_up (unsigned int preface_alert,
3453 if (pull_up_timer.running)
3455 if (pull_up_timer.elapsed() >= 1)
3457 mk_unset_alerts(preface_alert);
3458 mk_set_alerts(alert);
3463 if (! mk->voice_player.voice)
3464 pull_up_timer.start();
3469 MK_VIII::Mode2Handler::update_a ()
3473 if (mk_test_alert(MODE2A_PREFACE))
3474 check_pull_up(mk_alert(MODE2A_PREFACE), mk_alert(MODE2A));
3475 else if (! mk_test_alert(MODE2A))
3477 mk_unset_alerts(mk_alert(MODE2A_ALTITUDE_GAIN) | mk_alert(MODE2A_ALTITUDE_GAIN_TERRAIN_CLOSING));
3478 mk_set_alerts(mk_alert(MODE2A_PREFACE));
3479 a_start_time = globals->get_sim_time_sec();
3480 pull_up_timer.stop();
3485 if (mk_test_alert(MODE2A_ALTITUDE_GAIN))
3487 if (mk->io_handler.gpws_inhibit()
3488 || mk->state_handler.ground // [1]
3489 || a_altitude_gain_timer.elapsed() >= 45
3490 || mk_data(radio_altitude).ncd
3491 || mk_ainput(uncorrected_barometric_altitude).ncd
3492 || mk_ainput(uncorrected_barometric_altitude).get() - a_altitude_gain_alt >= 300
3493 // [PILOT] page 12: "the visual alert will remain on
3494 // until [...] landing flaps or the flap override switch
3496 || mk->io_handler.flaps_down())
3498 // exit altitude gain mode
3499 a_altitude_gain_timer.stop();
3500 mk_unset_alerts(mk_alert(MODE2A_ALTITUDE_GAIN) | mk_alert(MODE2A_ALTITUDE_GAIN_TERRAIN_CLOSING));
3504 // [SPEC] 6.2.2.2: "If the terrain starts to fall away
3505 // during this altitude gain time, the voice will shut
3507 if (closure_rate_filter.output.get() < 0)
3508 mk_unset_alerts(mk_alert(MODE2A_ALTITUDE_GAIN_TERRAIN_CLOSING));
3511 else if (mk_test_alerts(mk_alert(MODE2A_PREFACE) | mk_alert(MODE2A)))
3513 mk_unset_alerts(mk_alert(MODE2A_PREFACE) | mk_alert(MODE2A));
3515 if (! mk->io_handler.gpws_inhibit()
3516 && ! mk->state_handler.ground // [1]
3517 && globals->get_sim_time_sec() - a_start_time > 3
3518 && ! mk->io_handler.flaps_down()
3519 && ! mk_data(radio_altitude).ncd
3520 && ! mk_ainput(uncorrected_barometric_altitude).ncd)
3522 // [SPEC] 6.2.2.2: mode 2A envelope violated for more
3523 // than 3 seconds, enable altitude gain feature
3525 a_altitude_gain_timer.start();
3526 a_altitude_gain_alt = mk_ainput(uncorrected_barometric_altitude).get();
3528 unsigned int alerts = mk_alert(MODE2A_ALTITUDE_GAIN);
3529 if (closure_rate_filter.output.get() > 0)
3530 alerts |= mk_alert(MODE2A_ALTITUDE_GAIN_TERRAIN_CLOSING);
3532 mk_set_alerts(alerts);
3539 MK_VIII::Mode2Handler::update_b ()
3543 // handle normal mode
3545 if (b && (! mk_dinput(landing_gear) || ! mk->io_handler.flaps_down()))
3547 if (mk_test_alert(MODE2B_PREFACE))
3548 check_pull_up(mk_alert(MODE2B_PREFACE), mk_alert(MODE2B));
3549 else if (! mk_test_alert(MODE2B))
3551 mk_set_alerts(mk_alert(MODE2B_PREFACE));
3552 pull_up_timer.stop();
3556 mk_unset_alerts(mk_alert(MODE2B_PREFACE) | mk_alert(MODE2B));
3558 // handle landing mode ([SPEC] 6.2.2.3: "If both landing gear and
3559 // flaps are in the landing configuration, then the message will be
3562 if (b && mk_dinput(landing_gear) && mk->io_handler.flaps_down())
3563 mk_set_alerts(mk_alert(MODE2B_LANDING_MODE));
3565 mk_unset_alerts(mk_alert(MODE2B_LANDING_MODE));
3569 MK_VIII::Mode2Handler::boot ()
3571 closure_rate_filter.init();
3575 MK_VIII::Mode2Handler::power_off ()
3577 // [SPEC] 6.0.4: "This latching function is not power saved and a
3578 // system reset will force it false."
3579 takeoff_timer.stop();
3583 MK_VIII::Mode2Handler::leave_ground ()
3585 // takeoff, reset timer
3586 takeoff_timer.start();
3590 MK_VIII::Mode2Handler::enter_takeoff ()
3592 // reset timer, in case it's a go-around
3593 takeoff_timer.start();
3597 MK_VIII::Mode2Handler::update ()
3599 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
3602 closure_rate_filter.update();
3604 if (takeoff_timer.running && takeoff_timer.elapsed() >= 60)
3605 takeoff_timer.stop();
3611 ///////////////////////////////////////////////////////////////////////////////
3612 // Mode3Handler ///////////////////////////////////////////////////////////////
3613 ///////////////////////////////////////////////////////////////////////////////
3616 MK_VIII::Mode3Handler::max_alt_loss (double _bias)
3618 return conf->max_alt_loss(mk->io_handler.flap_override(), mk_data(radio_altitude).get()) + mk_data(radio_altitude).get() * _bias;
3622 MK_VIII::Mode3Handler::get_bias (double initial_bias, double alt_loss)
3624 // do not repeat altitude-loss alerts below 30ft agl
3625 if (mk_data(radio_altitude).get() > 30)
3627 if (initial_bias < 0.0) // sanity check
3629 // mk-viii spec: repeat alerts whenever losing 20% of initial altitude
3630 while ((alt_loss > max_alt_loss(initial_bias))&&
3631 (initial_bias < 1.0))
3632 initial_bias += 0.2;
3635 return initial_bias;
3639 MK_VIII::Mode3Handler::is (double *alt_loss)
3641 if (has_descent_alt)
3643 int max_agl = conf->max_agl(mk->io_handler.flap_override());
3645 if (mk_ainput(uncorrected_barometric_altitude).ncd
3646 || mk_ainput(uncorrected_barometric_altitude).get() > descent_alt
3647 || mk_data(radio_altitude).ncd
3648 || mk_data(radio_altitude).get() > max_agl)
3651 has_descent_alt = false;
3655 // gear/flaps: [SPEC] 1.3.1.3
3656 if (! mk->io_handler.gpws_inhibit()
3657 && ! mk->state_handler.ground // [1]
3658 && (! mk_dinput(landing_gear) || ! mk->io_handler.flaps_down())
3659 && ! mk_data(barometric_altitude_rate).ncd
3660 && ! mk_ainput(uncorrected_barometric_altitude).ncd
3661 && ! mk_data(radio_altitude).ncd
3662 && mk_data(barometric_altitude_rate).get() < 0)
3664 double _alt_loss = descent_alt - mk_ainput(uncorrected_barometric_altitude).get();
3665 if (armed || (mk_data(radio_altitude).get() > conf->min_agl
3666 && mk_data(radio_altitude).get() < max_agl
3667 && _alt_loss > max_alt_loss(0)))
3669 *alt_loss = _alt_loss;
3677 if (! mk_data(barometric_altitude_rate).ncd
3678 && ! mk_ainput(uncorrected_barometric_altitude).ncd
3679 && mk_data(barometric_altitude_rate).get() < 0)
3681 has_descent_alt = true;
3682 descent_alt = mk_ainput(uncorrected_barometric_altitude).get();
3690 MK_VIII::Mode3Handler::enter_takeoff ()
3693 has_descent_alt = false;
3697 MK_VIII::Mode3Handler::update ()
3699 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
3702 if (mk->state_handler.takeoff)
3706 if (! mk->state_handler.ground /* [1] */ && is(&alt_loss))
3708 if (mk_test_alert(MODE3))
3710 double new_bias = get_bias(bias, alt_loss);
3711 if (new_bias > bias)
3714 mk_repeat_alert(mk_alert(MODE3));
3720 bias = get_bias(0.2, alt_loss);
3721 mk_set_alerts(mk_alert(MODE3));
3728 mk_unset_alerts(mk_alert(MODE3));
3731 ///////////////////////////////////////////////////////////////////////////////
3732 // Mode4Handler ///////////////////////////////////////////////////////////////
3733 ///////////////////////////////////////////////////////////////////////////////
3735 // FIXME: for turbofans, the upper limit should be reduced from 1000
3736 // to 800 ft if a sudden change in radio altitude is detected, in
3737 // order to reduce nuisance alerts caused by overflying another
3738 // aircraft (see [PILOT] page 16).
3741 MK_VIII::Mode4Handler::get_upper_agl (const EnvelopesConfiguration *c)
3743 if (mk_ainput(computed_airspeed).get() >= c->airspeed2)
3745 else if (mk_ainput(computed_airspeed).get() >= c->airspeed1)
3746 return c->min_agl2(mk_ainput(computed_airspeed).get());
3751 const MK_VIII::Mode4Handler::EnvelopesConfiguration *
3752 MK_VIII::Mode4Handler::get_ab_envelope ()
3754 // [SPEC] 6.2.4.1: "The Mode 4B envelope can also be selected by
3755 // setting flaps to landing configuration or by selecting flap
3757 return mk_dinput(landing_gear) || mk->io_handler.flaps_down()
3763 MK_VIII::Mode4Handler::get_bias (double initial_bias, double min_agl)
3765 // do not repeat terrain/gear/flap alerts below 30ft agl
3766 if (mk_data(radio_altitude).get() > 30.0)
3768 if (initial_bias < 0.0) // sanity check
3770 while ((mk_data(radio_altitude).get() < min_agl - min_agl * initial_bias)&&
3771 (initial_bias < 1.0))
3772 initial_bias += 0.2;
3775 return initial_bias;
3779 MK_VIII::Mode4Handler::handle_alert (unsigned int alert,
3783 if (mk_test_alerts(alert))
3785 double new_bias = get_bias(*bias, min_agl);
3786 if (new_bias > *bias)
3789 mk_repeat_alert(alert);
3794 *bias = get_bias(0.2, min_agl);
3795 mk_set_alerts(alert);
3800 MK_VIII::Mode4Handler::update_ab ()
3802 if (! mk->io_handler.gpws_inhibit()
3803 && ! mk->state_handler.ground
3804 && ! mk->state_handler.takeoff
3805 && ! mk_data(radio_altitude).ncd
3806 && ! mk_ainput(computed_airspeed).ncd
3807 && mk_data(radio_altitude).get() > 30)
3809 const EnvelopesConfiguration *c = get_ab_envelope();
3810 if (mk_ainput(computed_airspeed).get() < c->airspeed1)
3812 if (mk_dinput(landing_gear))
3814 if (! mk->io_handler.flaps_down() && mk_data(radio_altitude).get() < c->min_agl1)
3816 handle_alert(mk_alert(MODE4_TOO_LOW_FLAPS), c->min_agl1, &ab_bias);
3822 if (mk_data(radio_altitude).get() < c->min_agl1)
3824 handle_alert(mk_alert(MODE4_TOO_LOW_GEAR), c->min_agl1, &ab_bias);
3831 mk_unset_alerts(mk_alert(MODE4_TOO_LOW_FLAPS) | mk_alert(MODE4_TOO_LOW_GEAR));
3836 MK_VIII::Mode4Handler::update_ab_expanded ()
3838 if (! mk->io_handler.gpws_inhibit()
3839 && ! mk->state_handler.ground
3840 && ! mk->state_handler.takeoff
3841 && ! mk_data(radio_altitude).ncd
3842 && ! mk_ainput(computed_airspeed).ncd
3843 && mk_data(radio_altitude).get() > 30)
3845 const EnvelopesConfiguration *c = get_ab_envelope();
3846 if (mk_ainput(computed_airspeed).get() >= c->airspeed1)
3848 double min_agl = get_upper_agl(c);
3849 if (mk_data(radio_altitude).get() < min_agl)
3851 handle_alert(mk_alert(MODE4AB_TOO_LOW_TERRAIN), min_agl, &ab_expanded_bias);
3857 mk_unset_alerts(mk_alert(MODE4AB_TOO_LOW_TERRAIN));
3858 ab_expanded_bias=0.0;
3862 MK_VIII::Mode4Handler::update_c ()
3864 if (! mk->io_handler.gpws_inhibit()
3865 && ! mk->state_handler.ground // [1]
3866 && mk->state_handler.takeoff
3867 && ! mk_data(radio_altitude).ncd
3868 && ! mk_data(terrain_clearance).ncd
3869 && mk_data(radio_altitude).get() > 30
3870 && (! mk_dinput(landing_gear) || ! mk->io_handler.flaps_down())
3871 && mk_data(radio_altitude).get() < get_upper_agl(conf.modes->ac)
3872 && mk_data(radio_altitude).get() < mk_data(terrain_clearance).get())
3873 handle_alert(mk_alert(MODE4C_TOO_LOW_TERRAIN), mk_data(terrain_clearance).get(), &c_bias);
3876 mk_unset_alerts(mk_alert(MODE4C_TOO_LOW_TERRAIN));
3882 MK_VIII::Mode4Handler::update ()
3884 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
3888 update_ab_expanded();
3892 ///////////////////////////////////////////////////////////////////////////////
3893 // Mode5Handler ///////////////////////////////////////////////////////////////
3894 ///////////////////////////////////////////////////////////////////////////////
3897 MK_VIII::Mode5Handler::is_hard ()
3899 if (mk_data(radio_altitude).get() > 30
3900 && mk_data(radio_altitude).get() < 300
3901 && mk_data(glideslope_deviation_dots).get() > 2)
3903 if (mk_data(radio_altitude).get() < 150)
3905 if (mk_data(radio_altitude).get() > 293 - 71.43 * mk_data(glideslope_deviation_dots).get())
3916 MK_VIII::Mode5Handler::is_soft (double bias)
3918 // do not repeat glide-slope alerts below 30ft agl
3919 if (mk_data(radio_altitude).get() > 30)
3921 double bias_dots = 1.3 * bias;
3922 if (mk_data(glideslope_deviation_dots).get() > 1.3 + bias_dots)
3924 if (mk_data(radio_altitude).get() < 150)
3926 if (mk_data(radio_altitude).get() > 243 - 71.43 * (mk_data(glideslope_deviation_dots).get() - bias_dots))
3933 if (mk_data(barometric_altitude_rate).ncd)
3937 if (mk_data(barometric_altitude_rate).get() >= 0)
3939 else if (mk_data(barometric_altitude_rate).get() < -500)
3942 upper_limit = -mk_data(barometric_altitude_rate).get() + 500;
3945 if (mk_data(radio_altitude).get() < upper_limit)
3955 MK_VIII::Mode5Handler::get_soft_bias (double initial_bias)
3957 if (initial_bias < 0.0) // sanity check
3959 while ((is_soft(initial_bias))&&
3960 (initial_bias < 1.0))
3961 initial_bias += 0.2;
3963 return initial_bias;
3967 MK_VIII::Mode5Handler::update_hard (bool is)
3971 if (! mk_test_alert(MODE5_HARD))
3973 if (hard_timer.start_or_elapsed() >= 0.8)
3974 mk_set_alerts(mk_alert(MODE5_HARD));
3980 mk_unset_alerts(mk_alert(MODE5_HARD));
3985 MK_VIII::Mode5Handler::update_soft (bool is)
3989 if (! mk_test_alert(MODE5_SOFT))
3991 double new_bias = get_soft_bias(soft_bias);
3992 if (new_bias > soft_bias)
3994 soft_bias = new_bias;
3995 mk_repeat_alert(mk_alert(MODE5_SOFT));
4000 if (soft_timer.start_or_elapsed() >= 0.8)
4002 soft_bias = get_soft_bias(0.2);
4003 mk_set_alerts(mk_alert(MODE5_SOFT));
4010 mk_unset_alerts(mk_alert(MODE5_SOFT));
4015 MK_VIII::Mode5Handler::update ()
4017 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
4020 if (! mk->io_handler.gpws_inhibit()
4021 && ! mk->state_handler.ground // [1]
4022 && ! mk_dinput(glideslope_inhibit) // not on backcourse
4023 && ! mk_data(radio_altitude).ncd
4024 && ! mk_data(glideslope_deviation_dots).ncd
4025 && (! mk->io_handler.conf.localizer_enabled
4026 || mk_data(localizer_deviation_dots).ncd
4027 || mk_data(radio_altitude).get() < 500
4028 || fabs(mk_data(localizer_deviation_dots).get()) < 2)
4029 && (! mk->state_handler.takeoff || mk->io_handler.flaps_down())
4030 && mk_dinput(landing_gear)
4031 && ! mk_doutput(glideslope_cancel))
4033 update_hard(is_hard());
4034 update_soft(is_soft(0));
4043 ///////////////////////////////////////////////////////////////////////////////
4044 // Mode6Handler ///////////////////////////////////////////////////////////////
4045 ///////////////////////////////////////////////////////////////////////////////
4047 // keep sorted in descending order
4048 const int MK_VIII::Mode6Handler::altitude_callout_definitions[] =
4049 { 1000, 500, 400, 300, 200, 100, 50, 40, 30, 20, 10 };
4052 MK_VIII::Mode6Handler::reset_minimums ()
4054 minimums_issued = false;
4058 MK_VIII::Mode6Handler::reset_altitude_callouts ()
4060 for (unsigned i = 0; i < n_altitude_callouts; i++)
4061 altitude_callouts_issued[i] = false;
4065 MK_VIII::Mode6Handler::is_playing_altitude_callout ()
4067 for (unsigned i = 0; i < n_altitude_callouts; i++)
4068 if (mk->voice_player.voice == mk_altitude_voice(i)
4069 || mk->voice_player.next_voice == mk_altitude_voice(i))
4076 MK_VIII::Mode6Handler::is_near_minimums (double callout)
4080 if (! mk_data(decision_height).ncd)
4082 double diff = callout - mk_data(decision_height).get();
4084 if (mk_data(radio_altitude).get() >= 200)
4086 if (fabs(diff) <= 30)
4091 if (diff >= -3 && diff <= 6)
4100 MK_VIII::Mode6Handler::is_outside_band (double elevation, double callout)
4103 return elevation < callout - (elevation > 150 ? 20 : 10);
4107 MK_VIII::Mode6Handler::inhibit_smart_500 ()
4111 if (! mk_data(glideslope_deviation_dots).ncd
4112 && ! mk_dinput(glideslope_inhibit) // backcourse
4113 && ! mk_doutput(glideslope_cancel))
4115 if (mk->io_handler.conf.localizer_enabled
4116 && ! mk_data(localizer_deviation_dots).ncd)
4118 if (fabs(mk_data(localizer_deviation_dots).get()) <= 2)
4123 if (fabs(mk_data(glideslope_deviation_dots).get()) <= 2)
4132 MK_VIII::Mode6Handler::boot ()
4134 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
4137 last_decision_height = mk_dinput(decision_height);
4138 last_radio_altitude.set(&mk_data(radio_altitude));
4141 for (unsigned i = 0; i < n_altitude_callouts; i++)
4142 altitude_callouts_issued[i] = ! mk_data(radio_altitude).ncd
4143 && mk_data(radio_altitude).get() <= altitude_callout_definitions[i];
4145 // extrapolated from [SPEC] 6.4.2
4146 minimums_issued = mk_dinput(decision_height);
4148 if (conf.above_field_voice)
4151 get_altitude_above_field(&last_altitude_above_field);
4152 // extrapolated from [SPEC] 6.4.2
4153 above_field_issued = ! last_altitude_above_field.ncd
4154 && last_altitude_above_field.get() < 550;
4159 MK_VIII::Mode6Handler::power_off ()
4161 runway_timer.stop();
4165 MK_VIII::Mode6Handler::enter_takeoff ()
4167 reset_altitude_callouts(); // [SPEC] 6.4.2
4168 reset_minimums(); // omitted by [SPEC]; common sense
4172 MK_VIII::Mode6Handler::leave_takeoff ()
4174 reset_altitude_callouts(); // [SPEC] 6.0.2
4175 reset_minimums(); // [SPEC] 6.0.2
4179 MK_VIII::Mode6Handler::set_volume (float volume)
4181 mk_voice(minimums_minimums)->set_volume(volume);
4182 mk_voice(five_hundred_above)->set_volume(volume);
4183 for (unsigned i = 0; i < n_altitude_callouts; i++)
4184 mk_altitude_voice(i)->set_volume(volume);
4188 MK_VIII::Mode6Handler::altitude_callouts_enabled ()
4190 if (conf.minimums_enabled || conf.smart_500_enabled || conf.above_field_voice)
4193 for (unsigned i = 0; i < n_altitude_callouts; i++)
4194 if (conf.altitude_callouts_enabled[i])
4201 MK_VIII::Mode6Handler::update_minimums ()
4203 if (! mk->io_handler.gpws_inhibit()
4204 && (mk->voice_player.voice == mk_voice(minimums_minimums)
4205 || mk->voice_player.next_voice == mk_voice(minimums_minimums)))
4208 if (! mk->io_handler.gpws_inhibit()
4209 && ! mk->state_handler.ground // [1]
4210 && conf.minimums_enabled
4211 && ! minimums_issued
4212 && mk_dinput(landing_gear)
4213 && mk_dinput(decision_height)
4214 && ! last_decision_height)
4216 minimums_issued = true;
4218 // If an altitude callout is playing, stop it now so that the
4219 // minimums callout can be played (note that proper connection
4220 // and synchronization of the radio-altitude ARINC 529 input,
4221 // decision-height discrete and decision-height ARINC 529 input
4222 // will prevent an altitude callout from being played near the
4223 // decision height).
4225 if (is_playing_altitude_callout())
4226 mk->voice_player.stop(VoicePlayer::STOP_NOW);
4228 mk_set_alerts(mk_alert(MODE6_MINIMUMS));
4231 mk_unset_alerts(mk_alert(MODE6_MINIMUMS));
4234 last_decision_height = mk_dinput(decision_height);
4238 MK_VIII::Mode6Handler::update_altitude_callouts ()
4240 if (! mk->io_handler.gpws_inhibit() && is_playing_altitude_callout())
4243 if (! mk->io_handler.gpws_inhibit()
4244 && ! mk->state_handler.ground // [1]
4245 && ! mk_data(radio_altitude).ncd)
4246 for (unsigned i = 0; i < n_altitude_callouts && mk_data(radio_altitude).get() <= altitude_callout_definitions[i]; i++)
4247 if ((conf.altitude_callouts_enabled[i]
4248 || (altitude_callout_definitions[i] == 500
4249 && conf.smart_500_enabled))
4250 && ! altitude_callouts_issued[i]
4251 && (last_radio_altitude.ncd
4252 || last_radio_altitude.get() > altitude_callout_definitions[i]))
4254 // lock out all callouts superior or equal to this one
4255 for (unsigned j = 0; j <= i; j++)
4256 altitude_callouts_issued[j] = true;
4258 altitude_callouts_issued[i] = true;
4259 if (! is_near_minimums(altitude_callout_definitions[i])
4260 && ! is_outside_band(mk_data(radio_altitude).get(), altitude_callout_definitions[i])
4261 && (! conf.smart_500_enabled
4262 || altitude_callout_definitions[i] != 500
4263 || ! inhibit_smart_500()))
4265 mk->alert_handler.set_altitude_callout_alert(mk_altitude_voice(i));
4270 mk_unset_alerts(mk_alert(MODE6_ALTITUDE_CALLOUT));
4273 last_radio_altitude.set(&mk_data(radio_altitude));
4277 MK_VIII::Mode6Handler::test_runway (const FGRunway *_runway)
4279 if (_runway->lengthFt() < mk->conf.runway_database)
4283 SGGeod::fromDeg(mk_data(gps_longitude).get(), mk_data(gps_latitude).get()));
4285 // get distance to threshold
4286 double distance, az1, az2;
4287 SGGeodesy::inverse(pos, _runway->threshold(), az1, az2, distance);
4288 return distance * SG_METER_TO_NM <= 5;
4292 MK_VIII::Mode6Handler::test_airport (const FGAirport *airport)
4294 for (unsigned int r=0; r<airport->numRunways(); ++r) {
4295 FGRunway* rwy(airport->getRunwayByIndex(r));
4297 if (test_runway(rwy)) return true;
4303 bool MK_VIII::Mode6Handler::AirportFilter::passAirport(FGAirport* a) const
4305 bool ok = self->test_airport(a);
4310 MK_VIII::Mode6Handler::update_runway ()
4313 if (mk_data(gps_latitude).ncd || mk_data(gps_longitude).ncd) {
4318 // Search for the closest runway threshold in range 5
4319 // nm. Passing 30nm to
4320 // get_closest_airport() provides enough margin for large
4321 // airports, which may have a runway located far away from the
4322 // airport's reference point.
4323 AirportFilter filter(this);
4324 FGPositionedRef apt = FGPositioned::findClosest(
4325 SGGeod::fromDeg(mk_data(gps_longitude).get(), mk_data(gps_latitude).get()),
4328 runway.elevation = apt->elevation();
4331 has_runway.set(apt != NULL);
4335 MK_VIII::Mode6Handler::get_altitude_above_field (Parameter<double> *parameter)
4337 if (! has_runway.ncd && has_runway.get() && ! mk_data(geometric_altitude).ncd)
4338 parameter->set(mk_data(geometric_altitude).get() - runway.elevation);
4344 MK_VIII::Mode6Handler::update_above_field_callout ()
4346 if (! conf.above_field_voice)
4349 // update_runway() has to iterate over the whole runway database
4350 // (which contains thousands of runways), so it would be unwise to
4351 // run it every frame. Run it every 3 seconds. Note that the first
4352 // iteration is run in boot().
4353 if (runway_timer.start_or_elapsed() >= 3)
4356 runway_timer.start();
4359 Parameter<double> altitude_above_field;
4360 get_altitude_above_field(&altitude_above_field);
4362 if (! mk->io_handler.gpws_inhibit()
4363 && (mk->voice_player.voice == conf.above_field_voice
4364 || mk->voice_player.next_voice == conf.above_field_voice))
4367 // handle reset term
4368 if (above_field_issued)
4370 if ((! has_runway.ncd && ! has_runway.get())
4371 || (! altitude_above_field.ncd && altitude_above_field.get() > 700))
4372 above_field_issued = false;
4375 if (! mk->io_handler.gpws_inhibit()
4376 && ! mk->state_handler.ground // [1]
4377 && ! above_field_issued
4378 && ! altitude_above_field.ncd
4379 && altitude_above_field.get() < 550
4380 && (last_altitude_above_field.ncd
4381 || last_altitude_above_field.get() >= 550))
4383 above_field_issued = true;
4385 if (! is_outside_band(altitude_above_field.get(), 500))
4387 mk->alert_handler.set_altitude_callout_alert(conf.above_field_voice);
4392 mk_unset_alerts(mk_alert(MODE6_ALTITUDE_CALLOUT));
4395 last_altitude_above_field.set(&altitude_above_field);
4399 MK_VIII::Mode6Handler::is_bank_angle (double abs_roll_angle, double bias)
4401 return conf.is_bank_angle(&mk_data(radio_altitude),
4402 abs_roll_angle - abs_roll_angle * bias,
4403 mk_dinput(autopilot_engaged));
4407 MK_VIII::Mode6Handler::is_high_bank_angle ()
4409 return mk_data(radio_altitude).ncd || mk_data(radio_altitude).get() >= 210;
4413 MK_VIII::Mode6Handler::get_bank_angle_alerts ()
4415 if (! mk->io_handler.gpws_inhibit()
4416 && ! mk->state_handler.ground // [1]
4417 && ! mk_data(roll_angle).ncd)
4419 double abs_roll_angle = fabs(mk_data(roll_angle).get());
4421 if (is_bank_angle(abs_roll_angle, 0.4))
4422 return is_high_bank_angle()
4423 ? (mk_alert(MODE6_HIGH_BANK_ANGLE_1) | mk_alert(MODE6_HIGH_BANK_ANGLE_2) | mk_alert(MODE6_HIGH_BANK_ANGLE_3))
4424 : (mk_alert(MODE6_LOW_BANK_ANGLE_1) | mk_alert(MODE6_LOW_BANK_ANGLE_2) | mk_alert(MODE6_LOW_BANK_ANGLE_3));
4425 else if (is_bank_angle(abs_roll_angle, 0.2))
4426 return is_high_bank_angle()
4427 ? (mk_alert(MODE6_HIGH_BANK_ANGLE_1) | mk_alert(MODE6_HIGH_BANK_ANGLE_2))
4428 : (mk_alert(MODE6_LOW_BANK_ANGLE_1) | mk_alert(MODE6_LOW_BANK_ANGLE_2));
4429 else if (is_bank_angle(abs_roll_angle, 0))
4430 return is_high_bank_angle()
4431 ? mk_alert(MODE6_HIGH_BANK_ANGLE_1)
4432 : mk_alert(MODE6_LOW_BANK_ANGLE_1);
4439 MK_VIII::Mode6Handler::update_bank_angle ()
4441 if (! conf.bank_angle_enabled)
4444 unsigned int alerts = get_bank_angle_alerts();
4446 // [SPEC] 6.4.4 specifies that the non-continuous alerts
4447 // (MODE6_*_BANK_ANGLE_1 and MODE6_*_BANK_ANGLE_2) are locked out
4448 // until the initial envelope is exited.
4450 if (! test_bits(alerts, mk_alert(MODE6_LOW_BANK_ANGLE_3)))
4451 mk_unset_alerts(mk_alert(MODE6_LOW_BANK_ANGLE_3));
4452 if (! test_bits(alerts, mk_alert(MODE6_HIGH_BANK_ANGLE_3)))
4453 mk_unset_alerts(mk_alert(MODE6_HIGH_BANK_ANGLE_3));
4456 mk_set_alerts(alerts);
4458 mk_unset_alerts(mk_alert(MODE6_LOW_BANK_ANGLE_1)
4459 | mk_alert(MODE6_HIGH_BANK_ANGLE_1)
4460 | mk_alert(MODE6_LOW_BANK_ANGLE_2)
4461 | mk_alert(MODE6_HIGH_BANK_ANGLE_2));
4465 MK_VIII::Mode6Handler::update ()
4467 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
4470 if (! mk->state_handler.takeoff
4471 && ! mk_data(radio_altitude).ncd
4472 && mk_data(radio_altitude).get() > 1000)
4474 reset_altitude_callouts(); // [SPEC] 6.4.2
4475 reset_minimums(); // common sense
4479 update_altitude_callouts();
4480 update_above_field_callout();
4481 update_bank_angle();
4484 ///////////////////////////////////////////////////////////////////////////////
4485 // TCFHandler /////////////////////////////////////////////////////////////////
4486 ///////////////////////////////////////////////////////////////////////////////
4488 // Gets the difference between the azimuth from @from_lat,@from_lon to
4489 // @to_lat,@to_lon, and @to_heading, in degrees.
4491 MK_VIII::TCFHandler::get_azimuth_difference (double from_lat,
4497 double az1, az2, distance;
4498 geo_inverse_wgs_84(0, from_lat, from_lon, to_lat, to_lon, &az1, &az2, &distance);
4499 return get_heading_difference(az1, to_heading);
4502 // Gets the difference between the azimuth from the current GPS
4503 // position to the center of @_runway, and the heading of @_runway, in
4506 MK_VIII::TCFHandler::get_azimuth_difference (const FGRunway *_runway)
4508 return get_azimuth_difference(mk_data(gps_latitude).get(),
4509 mk_data(gps_longitude).get(),
4510 _runway->latitude(),
4511 _runway->longitude(),
4512 _runway->headingDeg());
4515 // Selects the most likely intended destination runway of @airport,
4516 // and returns it in @_runway. For each runway, the difference between
4517 // the azimuth from the current GPS position to the center of the
4518 // runway and its heading is computed. The runway having the smallest
4521 // This selection algorithm is not specified in [SPEC], but
4522 // http://www.egpws.com/general_information/description/runway_select.htm
4523 // talks about automatic runway selection.
4525 MK_VIII::TCFHandler::select_runway (const FGAirport *airport)
4527 FGRunway* _runway = 0;
4528 double min_diff = 360;
4530 for (unsigned int r=0; r<airport->numRunways(); ++r) {
4531 FGRunway* rwy(airport->getRunwayByIndex(r));
4532 double diff = get_azimuth_difference(rwy);
4533 if (diff < min_diff)
4538 } // of airport runways iteration
4542 bool MK_VIII::TCFHandler::AirportFilter::passAirport(FGAirport* aApt) const
4544 return aApt->hasHardRunwayOfLengthFt(mk->conf.runway_database);
4548 MK_VIII::TCFHandler::update_runway ()
4551 if (mk_data(gps_latitude).ncd || mk_data(gps_longitude).ncd) {
4555 // Search for the intended destination runway of the closest
4556 // airport in range 15 nm. Passing 30nm to get_closest_airport()
4557 // provides enough margin for
4558 // large airports, which may have a runway located far away from
4559 // the airport's reference point.
4560 AirportFilter filter(mk);
4561 FGAirport* apt = FGAirport::findClosest(
4562 SGGeod::fromDeg(mk_data(gps_longitude).get(), mk_data(gps_latitude).get()),
4567 FGRunway* _runway = select_runway(apt);
4569 if (!_runway) return;
4573 runway.center.latitude = _runway->latitude();
4574 runway.center.longitude = _runway->longitude();
4576 runway.elevation = apt->elevation();
4578 double half_length_m = _runway->lengthM() * 0.5;
4579 runway.half_length = half_length_m * SG_METER_TO_NM;
4581 // b3 ________________ b0
4583 // h1>>> | e1<<<<<<<<e0 | <<<h0
4584 // |________________|
4587 // get heading to runway threshold (h0) and end (h1)
4588 runway.edges[0].heading = _runway->headingDeg();
4589 runway.edges[1].heading = get_reciprocal_heading(_runway->headingDeg());
4593 // get position of runway threshold (e0)
4594 geo_direct_wgs_84(0,
4595 runway.center.latitude,
4596 runway.center.longitude,
4597 runway.edges[1].heading,
4599 &runway.edges[0].position.latitude,
4600 &runway.edges[0].position.longitude,
4603 // get position of runway end (e1)
4604 geo_direct_wgs_84(0,
4605 runway.center.latitude,
4606 runway.center.longitude,
4607 runway.edges[0].heading,
4609 &runway.edges[1].position.latitude,
4610 &runway.edges[1].position.longitude,
4613 double half_width_m = _runway->widthM() * 0.5;
4615 // get position of threshold bias area edges (b0 and b1)
4616 get_bias_area_edges(&runway.edges[0].position,
4617 runway.edges[1].heading,
4619 &runway.bias_area[0],
4620 &runway.bias_area[1]);
4622 // get position of end bias area edges (b2 and b3)
4623 get_bias_area_edges(&runway.edges[1].position,
4624 runway.edges[0].heading,
4626 &runway.bias_area[2],
4627 &runway.bias_area[3]);
4631 MK_VIII::TCFHandler::get_bias_area_edges (Position *edge,
4633 double half_width_m,
4634 Position *bias_edge1,
4635 Position *bias_edge2)
4637 double half_bias_width_m = k * SG_NM_TO_METER + half_width_m;
4638 double tmp_latitude = 0.0, tmp_longitude = 0.0, az = 0.0;
4640 geo_direct_wgs_84(0,
4648 geo_direct_wgs_84(0,
4651 heading_substract(reciprocal, 90),
4653 &bias_edge1->latitude,
4654 &bias_edge1->longitude,
4656 geo_direct_wgs_84(0,
4659 heading_add(reciprocal, 90),
4661 &bias_edge2->latitude,
4662 &bias_edge2->longitude,
4666 // Returns true if the current GPS position is inside the edge
4667 // triangle of @edge. The edge triangle, which is specified and
4668 // represented in [SPEC] 6.3.1, is defined as an isocele right
4669 // triangle of infinite height, whose right angle is located at the
4670 // position of @edge, and whose height is parallel to the heading of
4673 MK_VIII::TCFHandler::is_inside_edge_triangle (RunwayEdge *edge)
4675 return get_azimuth_difference(mk_data(gps_latitude).get(),
4676 mk_data(gps_longitude).get(),
4677 edge->position.latitude,
4678 edge->position.longitude,
4679 edge->heading) <= 45;
4682 // Returns true if the current GPS position is inside the bias area of
4683 // the currently selected runway.
4685 MK_VIII::TCFHandler::is_inside_bias_area ()
4688 double angles_sum = 0;
4690 for (int i = 0; i < 4; i++)
4692 double az2, distance;
4693 geo_inverse_wgs_84(0,
4694 mk_data(gps_latitude).get(),
4695 mk_data(gps_longitude).get(),
4696 runway.bias_area[i].latitude,
4697 runway.bias_area[i].longitude,
4698 &az1[i], &az2, &distance);
4701 for (int i = 0; i < 4; i++)
4703 double angle = az1[i == 3 ? 0 : i + 1] - az1[i];
4707 angles_sum += angle;
4710 return angles_sum > 180;
4714 MK_VIII::TCFHandler::is_tcf ()
4716 if (mk_data(radio_altitude).get() > 10)
4720 double distance, az1, az2;
4722 geo_inverse_wgs_84(0,
4723 mk_data(gps_latitude).get(),
4724 mk_data(gps_longitude).get(),
4725 runway.center.latitude,
4726 runway.center.longitude,
4727 &az1, &az2, &distance);
4729 distance *= SG_METER_TO_NM;
4731 // distance to the inner envelope edge
4732 double edge_distance = distance - runway.half_length - k;
4734 if (edge_distance >= 15)
4736 if (mk_data(radio_altitude).get() < 700)
4739 else if (edge_distance >= 12)
4741 if (mk_data(radio_altitude).get() < 100 * edge_distance - 800)
4744 else if (edge_distance >= 4)
4746 if (mk_data(radio_altitude).get() < 400)
4749 else if (edge_distance >= 2.45)
4751 if (mk_data(radio_altitude).get() < 66.666667 * edge_distance + 133.33333)
4756 if (is_inside_edge_triangle(&runway.edges[0]) || is_inside_edge_triangle(&runway.edges[1]))
4758 if (edge_distance >= 1)
4760 if (mk_data(radio_altitude).get() < 66.666667 * edge_distance + 133.33333)
4763 else if (edge_distance >= 0.05)
4765 if (mk_data(radio_altitude).get() < 200 * edge_distance)
4771 if (! is_inside_bias_area())
4773 if (mk_data(radio_altitude).get() < 245)
4781 if (mk_data(radio_altitude).get() < 700)
4790 MK_VIII::TCFHandler::is_rfcf ()
4794 double distance, az1, az2;
4795 geo_inverse_wgs_84(0,
4796 mk_data(gps_latitude).get(),
4797 mk_data(gps_longitude).get(),
4798 runway.center.latitude,
4799 runway.center.longitude,
4800 &az1, &az2, &distance);
4802 double krf = k + mk_data(gps_vertical_figure_of_merit).get() / 200;
4803 distance = distance * SG_METER_TO_NM - runway.half_length - krf;
4807 double altitude_above_field = mk_data(geometric_altitude).get() - runway.elevation;
4809 if (distance >= 1.5)
4811 if (altitude_above_field < 300)
4814 else if (distance >= 0)
4816 if (altitude_above_field < 200 * distance)
4826 MK_VIII::TCFHandler::update ()
4828 if (mk->configuration_module.state != ConfigurationModule::STATE_OK || ! conf.enabled)
4831 // update_runway() has to iterate over the whole runway database
4832 // (which contains thousands of runways), so it would be unwise to
4833 // run it every frame. Run it every 3 seconds.
4834 if (! runway_timer.running || runway_timer.elapsed() >= 3)
4837 runway_timer.start();
4840 if (! mk_dinput(ta_tcf_inhibit)
4841 && ! mk->state_handler.ground // [1]
4842 && ! mk_data(gps_latitude).ncd
4843 && ! mk_data(gps_longitude).ncd
4844 && ! mk_data(radio_altitude).ncd
4845 && ! mk_data(geometric_altitude).ncd
4846 && ! mk_data(gps_vertical_figure_of_merit).ncd)
4851 _reference = mk_data(radio_altitude).get_pointer();
4853 _reference = mk_data(geometric_altitude).get_pointer();
4859 if (mk_test_alert(TCF_TOO_LOW_TERRAIN))
4861 double new_bias = bias;
4862 // do not repeat terrain alerts below 30ft agl
4863 if (mk_data(radio_altitude).get() > 30)
4865 if (new_bias < 0.0) // sanity check
4867 while ((*reference < initial_value - initial_value * new_bias)&&
4872 if (new_bias > bias)
4875 mk_repeat_alert(mk_alert(TCF_TOO_LOW_TERRAIN));
4881 reference = _reference;
4882 initial_value = *reference;
4883 mk_set_alerts(mk_alert(TCF_TOO_LOW_TERRAIN));
4890 mk_unset_alerts(mk_alert(TCF_TOO_LOW_TERRAIN));
4893 ///////////////////////////////////////////////////////////////////////////////
4894 // MK_VIII ////////////////////////////////////////////////////////////////////
4895 ///////////////////////////////////////////////////////////////////////////////
4897 MK_VIII::MK_VIII (SGPropertyNode *node)
4898 : properties_handler(this),
4901 power_handler(this),
4902 system_handler(this),
4903 configuration_module(this),
4904 fault_handler(this),
4907 self_test_handler(this),
4908 alert_handler(this),
4909 state_handler(this),
4910 mode1_handler(this),
4911 mode2_handler(this),
4912 mode3_handler(this),
4913 mode4_handler(this),
4914 mode5_handler(this),
4915 mode6_handler(this),
4918 for (int i = 0; i < node->nChildren(); ++i)
4920 SGPropertyNode *child = node->getChild(i);
4921 string cname = child->getName();
4922 string cval = child->getStringValue();
4924 if (cname == "name")
4926 else if (cname == "number")
4927 num = child->getIntValue();
4930 SG_LOG(SG_INSTR, SG_WARN, "Error in mk-viii config logic");
4932 SG_LOG(SG_INSTR, SG_WARN, "Section = " << name);
4940 properties_handler.init();
4941 voice_player.init();
4947 SGPropertyNode *node = fgGetNode(("/instrumentation/" + name).c_str(), num, true);
4949 configuration_module.bind(node);
4950 power_handler.bind(node);
4951 io_handler.bind(node);
4952 voice_player.bind(node);
4958 properties_handler.unbind();
4962 MK_VIII::update (double dt)
4964 power_handler.update();
4965 system_handler.update();
4967 if (system_handler.state != SystemHandler::STATE_ON)
4970 io_handler.update_inputs();
4971 io_handler.update_input_faults();
4973 voice_player.update();
4974 state_handler.update();
4976 if (self_test_handler.update())
4979 io_handler.update_internal_latches();
4980 io_handler.update_lamps();
4982 mode1_handler.update();
4983 mode2_handler.update();
4984 mode3_handler.update();
4985 mode4_handler.update();
4986 mode5_handler.update();
4987 mode6_handler.update();
4988 tcf_handler.update();
4990 alert_handler.update();
4991 io_handler.update_outputs();