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);
213 ///////////////////////////////////////////////////////////////////////////////
214 // PowerHandler ///////////////////////////////////////////////////////////////
215 ///////////////////////////////////////////////////////////////////////////////
218 MK_VIII::PowerHandler::bind (SGPropertyNode *node)
220 mk->properties_handler.tie(node, "serviceable", SGRawValuePointer<bool>(&serviceable));
224 MK_VIII::PowerHandler::handle_abnormal_voltage (bool abnormal,
230 if (timer->start_or_elapsed() >= max_duration)
231 return true; // power loss
240 MK_VIII::PowerHandler::update ()
242 double voltage = mk_node(power)->getDoubleValue();
243 bool now_powered = true;
251 if (handle_abnormal_voltage(voltage < 20.5, &low_surge_timer, 0.03))
253 if (handle_abnormal_voltage(voltage < 22.0 || voltage > 30.3, &abnormal_timer, 300))
255 if (handle_abnormal_voltage(voltage > 32.2, &high_surge_timer, 1))
257 if (handle_abnormal_voltage(voltage > 37.8, &very_high_surge_timer, 0.1))
267 power_loss_timer.stop();
270 if (power_loss_timer.start_or_elapsed() >= 0.2)
282 MK_VIII::PowerHandler::power_on ()
285 mk->system_handler.power_on();
289 MK_VIII::PowerHandler::power_off ()
292 mk->system_handler.power_off();
293 mk->voice_player.stop(VoicePlayer::STOP_NOW);
294 mk->self_test_handler.power_off(); // run before IOHandler::power_off()
295 mk->io_handler.power_off();
296 mk->mode2_handler.power_off();
297 mk->mode6_handler.power_off();
300 ///////////////////////////////////////////////////////////////////////////////
301 // SystemHandler //////////////////////////////////////////////////////////////
302 ///////////////////////////////////////////////////////////////////////////////
305 MK_VIII::SystemHandler::power_on ()
307 state = STATE_BOOTING;
309 // [SPEC] 3.5.2 mentions a 20 seconds maximum boot delay. We use a
310 // random delay between 3 and 5 seconds.
312 boot_delay = 3 + sg_random() * 2;
317 MK_VIII::SystemHandler::power_off ()
325 MK_VIII::SystemHandler::update ()
327 if (state == STATE_BOOTING)
329 if (boot_timer.elapsed() >= boot_delay)
331 last_replay_state = mk_node(replay_state)->getIntValue();
333 mk->configuration_module.boot();
335 mk->io_handler.boot();
336 mk->fault_handler.boot();
337 mk->alert_handler.boot();
339 // inputs are needed by the following boot() routines
340 mk->io_handler.update_inputs();
342 mk->mode2_handler.boot();
343 mk->mode6_handler.boot();
347 mk->io_handler.post_boot();
350 else if (state != STATE_OFF && mk->configuration_module.state == ConfigurationModule::STATE_OK)
352 // If the replay state changes, switch to reposition mode for 3
353 // seconds ([SPEC] 6.0.5) to avoid spurious alerts.
355 int replay_state = mk_node(replay_state)->getIntValue();
356 if (replay_state != last_replay_state)
358 mk->alert_handler.reposition();
359 mk->io_handler.reposition();
361 last_replay_state = replay_state;
362 state = STATE_REPOSITION;
363 reposition_timer.start();
366 if (state == STATE_REPOSITION && reposition_timer.elapsed() >= 3)
368 // inputs are needed by StateHandler::post_reposition()
369 mk->io_handler.update_inputs();
371 mk->state_handler.post_reposition();
378 ///////////////////////////////////////////////////////////////////////////////
379 // ConfigurationModule ////////////////////////////////////////////////////////
380 ///////////////////////////////////////////////////////////////////////////////
382 MK_VIII::ConfigurationModule::ConfigurationModule (MK_VIII *device)
385 // arbitrary defaults
386 categories[CATEGORY_AIRCRAFT_MODE_TYPE_SELECT] = 0;
387 categories[CATEGORY_AIR_DATA_INPUT_SELECT] = 1;
388 categories[CATEGORY_POSITION_INPUT_SELECT] = 2;
389 categories[CATEGORY_ALTITUDE_CALLOUTS] = 13;
390 categories[CATEGORY_AUDIO_MENU_SELECT] = 0;
391 categories[CATEGORY_TERRAIN_DISPLAY_SELECT] = 1;
392 categories[CATEGORY_OPTIONS_SELECT_GROUP_1] = 124;
393 categories[CATEGORY_RADIO_ALTITUDE_INPUT_SELECT] = 2;
394 categories[CATEGORY_NAVIGATION_INPUT_SELECT] = 3;
395 categories[CATEGORY_ATTITUDE_INPUT_SELECT] = 6;
396 categories[CATEGORY_HEADING_INPUT_SELECT] = 2;
397 categories[CATEGORY_WINDSHEAR_INPUT_SELECT] = 0;
398 categories[CATEGORY_INPUT_OUTPUT_DISCRETE_TYPE_SELECT] = 7;
399 categories[CATEGORY_AUDIO_OUTPUT_LEVEL] = 0;
400 categories[CATEGORY_UNDEFINED_INPUT_SELECT_1] = 0;
401 categories[CATEGORY_UNDEFINED_INPUT_SELECT_2] = 0;
402 categories[CATEGORY_UNDEFINED_INPUT_SELECT_3] = 0;
405 static double m1_t1_min_agl1 (double vs) { return -1620 - 1.1133 * vs; }
406 static double m1_t1_min_agl2 (double vs) { return -400 - 0.4 * vs; }
407 static double m1_t4_min_agl1 (double vs) { return -1625.47 - 1.1167 * vs; }
408 static double m1_t4_min_agl2 (double vs) { return -0.1958 * vs; }
410 static int m3_t1_max_agl (bool flap_override) { return 1500; }
411 static double m3_t1_max_alt_loss (bool flap_override, double agl) { return 5.4 + 0.092 * agl; }
412 static int m3_t2_max_agl (bool flap_override) { return flap_override ? 815 : 925; }
413 static double m3_t2_max_alt_loss (bool flap_override, double agl)
415 int bias = agl > 700 ? 5 : 0;
418 return (9.0 + 0.184 * agl) + bias;
420 return (5.4 + 0.092 * agl) + bias;
423 static double m4_t1_min_agl2 (double airspeed) { return -1083 + 8.333 * airspeed; }
424 static double m4_t568_a_min_agl2 (double airspeed) { return -1523 + 11.36 * airspeed; }
425 static double m4_t79_a_min_agl2 (double airspeed) { return -1182 + 11.36 * airspeed; }
426 static double m4_t568_b_min_agl2 (double airspeed) { return -1570 + 11.6 * airspeed; }
427 static double m4_t79_b_min_agl2 (double airspeed) { return -1222 + 11.6 * airspeed; }
430 MK_VIII::ConfigurationModule::m6_t2_is_bank_angle (Parameter<double> *agl,
436 if (agl->ncd || agl->get() > 122)
437 return abs_roll_deg > 33;
441 if (agl->ncd || agl->get() > 2450)
442 return abs_roll_deg > 55;
443 else if (agl->get() > 150)
444 return agl->get() < 153.33333 * abs_roll_deg - 5983.3333;
448 return agl->get() < 4 * abs_roll_deg - 10;
449 else if (agl->get() > 5)
450 return abs_roll_deg > 10;
456 MK_VIII::ConfigurationModule::m6_t4_is_bank_angle (Parameter<double> *agl,
462 if (agl->ncd || agl->get() > 156)
463 return abs_roll_deg > 33;
467 if (agl->ncd || agl->get() > 210)
468 return abs_roll_deg > 50;
472 return agl->get() < 5.7142857 * abs_roll_deg - 75.714286;
478 MK_VIII::ConfigurationModule::read_aircraft_mode_type_select (int value)
480 static const Mode1Handler::EnvelopesConfiguration m1_t1 =
481 { false, 10, m1_t1_min_agl1, 284, m1_t1_min_agl2, 2450 };
482 static const Mode1Handler::EnvelopesConfiguration m1_t4 =
483 { true, 50, m1_t4_min_agl1, 346, m1_t4_min_agl2, 1958 };
485 static const Mode2Handler::Configuration m2_t1 = { 190, 280 };
486 static const Mode2Handler::Configuration m2_t4 = { 220, 310 };
487 static const Mode2Handler::Configuration m2_t5 = { 220, 310 };
489 static const Mode3Handler::Configuration m3_t1 =
490 { 30, m3_t1_max_agl, m3_t1_max_alt_loss };
491 static const Mode3Handler::Configuration m3_t2 =
492 { 50, m3_t2_max_agl, m3_t2_max_alt_loss };
494 static const Mode4Handler::EnvelopesConfiguration m4_t1_ac =
495 { 190, 250, 500, m4_t1_min_agl2, 1000 };
496 static const Mode4Handler::EnvelopesConfiguration m4_t5_ac =
497 { 178, 200, 500, m4_t568_a_min_agl2, 1000 };
498 static const Mode4Handler::EnvelopesConfiguration m4_t68_ac =
499 { 178, 200, 500, m4_t568_a_min_agl2, 750 };
500 static const Mode4Handler::EnvelopesConfiguration m4_t79_ac =
501 { 148, 170, 500, m4_t79_a_min_agl2, 750 };
503 static const Mode4Handler::EnvelopesConfiguration m4_t1_b =
504 { 159, 250, 245, m4_t1_min_agl2, 1000 };
505 static const Mode4Handler::EnvelopesConfiguration m4_t5_b =
506 { 148, 200, 200, m4_t568_b_min_agl2, 1000 };
507 static const Mode4Handler::EnvelopesConfiguration m4_t6_b =
508 { 150, 200, 170, m4_t568_b_min_agl2, 750 };
509 static const Mode4Handler::EnvelopesConfiguration m4_t7_b =
510 { 120, 170, 170, m4_t79_b_min_agl2, 750 };
511 static const Mode4Handler::EnvelopesConfiguration m4_t8_b =
512 { 148, 200, 150, m4_t568_b_min_agl2, 750 };
513 static const Mode4Handler::EnvelopesConfiguration m4_t9_b =
514 { 118, 170, 150, m4_t79_b_min_agl2, 750 };
516 static const Mode4Handler::ModesConfiguration m4_t1 = { &m4_t1_ac, &m4_t1_b };
517 static const Mode4Handler::ModesConfiguration m4_t5 = { &m4_t5_ac, &m4_t5_b };
518 static const Mode4Handler::ModesConfiguration m4_t6 = { &m4_t68_ac, &m4_t6_b };
519 static const Mode4Handler::ModesConfiguration m4_t7 = { &m4_t79_ac, &m4_t7_b };
520 static const Mode4Handler::ModesConfiguration m4_t8 = { &m4_t68_ac, &m4_t8_b };
521 static const Mode4Handler::ModesConfiguration m4_t9 = { &m4_t79_ac, &m4_t9_b };
523 static Mode6Handler::BankAnglePredicate m6_t2 = m6_t2_is_bank_angle;
524 static Mode6Handler::BankAnglePredicate m6_t4 = m6_t4_is_bank_angle;
526 static const IOHandler::FaultsConfiguration f_slow = { 180, 200 };
527 static const IOHandler::FaultsConfiguration f_fast = { 250, 290 };
532 const Mode1Handler::EnvelopesConfiguration *m1;
533 const Mode2Handler::Configuration *m2;
534 const Mode3Handler::Configuration *m3;
535 const Mode4Handler::ModesConfiguration *m4;
536 Mode6Handler::BankAnglePredicate m6;
537 const IOHandler::FaultsConfiguration *f;
539 } aircraft_types[] = {
540 { 0, &m1_t4, &m2_t4, &m3_t2, &m4_t6, m6_t4, &f_fast, 2000 },
541 { 1, &m1_t4, &m2_t4, &m3_t2, &m4_t8, m6_t4, &f_fast, 2000 },
542 { 2, &m1_t4, &m2_t4, &m3_t2, &m4_t5, m6_t4, &f_fast, 2000 },
543 { 3, &m1_t4, &m2_t5, &m3_t2, &m4_t7, m6_t4, &f_slow, 2000 },
544 { 4, &m1_t4, &m2_t5, &m3_t2, &m4_t9, m6_t4, &f_slow, 2000 },
545 { 254, &m1_t1, &m2_t1, &m3_t1, &m4_t1, m6_t2, &f_fast, 3500 },
546 { 255, &m1_t1, &m2_t1, &m3_t1, &m4_t1, m6_t2, &f_fast, 2000 }
549 for (size_t i = 0; i < n_elements(aircraft_types); i++)
550 if (aircraft_types[i].type == value)
552 mk->mode1_handler.conf.envelopes = aircraft_types[i].m1;
553 mk->mode2_handler.conf = aircraft_types[i].m2;
554 mk->mode3_handler.conf = aircraft_types[i].m3;
555 mk->mode4_handler.conf.modes = aircraft_types[i].m4;
556 mk->mode6_handler.conf.is_bank_angle = aircraft_types[i].m6;
557 mk->io_handler.conf.faults = aircraft_types[i].f;
558 mk->conf.runway_database = aircraft_types[i].runway_database;
562 state = STATE_INVALID_AIRCRAFT_TYPE;
567 MK_VIII::ConfigurationModule::read_air_data_input_select (int value)
570 return (value >= 0 && value <= 6) || (value >= 10 && value <= 13) || value == 255;
574 MK_VIII::ConfigurationModule::read_position_input_select (int value)
577 mk->io_handler.conf.use_internal_gps = true;
578 else if ((value >= 0 && value <= 5)
579 || (value >= 10 && value <= 13)
582 mk->io_handler.conf.use_internal_gps = false;
590 MK_VIII::ConfigurationModule::read_altitude_callouts (int value)
605 { 0, { MINIMUMS, SMART_500, 200, 100, 50, 40, 30, 20, 10, 0 } },
606 { 1, { MINIMUMS, SMART_500, 200, 0 } },
607 { 2, { MINIMUMS, SMART_500, 100, 50, 40, 30, 20, 10, 0 } },
608 { 3, { MINIMUMS, SMART_500, 0 } },
609 { 4, { MINIMUMS, 200, 100, 50, 40, 30, 20, 10, 0 } },
610 { 5, { MINIMUMS, 200, 0 } },
611 { 6, { MINIMUMS, 100, 50, 40, 30, 20, 10, 0 } },
613 { 8, { MINIMUMS, 0 } },
614 { 9, { MINIMUMS, 500, 200, 100, 50, 40, 30, 20, 10, 0 } },
615 { 10, { MINIMUMS, 500, 200, 0 } },
616 { 11, { MINIMUMS, 500, 100, 50, 40, 30, 20, 10, 0 } },
617 { 12, { MINIMUMS, 500, 0 } },
618 { 13, { MINIMUMS, 1000, 500, 400, 300, 200, 100, 50, 40, 30, 20, 10, 0 } },
619 { 14, { MINIMUMS, 100, 0 } },
620 { 15, { MINIMUMS, 200, 100, 0 } },
621 { 100, { FIELD_500, 0 } },
622 { 101, { FIELD_500_ABOVE, 0 } }
627 mk->mode6_handler.conf.minimums_enabled = false;
628 mk->mode6_handler.conf.smart_500_enabled = false;
629 mk->mode6_handler.conf.above_field_voice = NULL;
630 for (i = 0; i < n_altitude_callouts; i++)
631 mk->mode6_handler.conf.altitude_callouts_enabled[i] = false;
633 for (i = 0; i < n_elements(values); i++)
634 if (values[i].id == value)
636 for (int j = 0; values[i].callouts[j] != 0; j++)
637 switch (values[i].callouts[j])
640 mk->mode6_handler.conf.minimums_enabled = true;
644 mk->mode6_handler.conf.smart_500_enabled = true;
648 mk->mode6_handler.conf.above_field_voice = mk_altitude_voice(Mode6Handler::ALTITUDE_CALLOUT_500);
651 case FIELD_500_ABOVE:
652 mk->mode6_handler.conf.above_field_voice = mk_voice(five_hundred_above);
656 for (unsigned k = 0; k < n_altitude_callouts; k++)
657 if (mk->mode6_handler.altitude_callout_definitions[k] == values[i].callouts[j])
658 mk->mode6_handler.conf.altitude_callouts_enabled[k] = true;
669 MK_VIII::ConfigurationModule::read_audio_menu_select (int value)
671 if (value == 0 || value == 1)
672 mk->mode4_handler.conf.voice_too_low_gear = mk_voice(too_low_gear);
673 else if (value == 2 || value == 3)
674 mk->mode4_handler.conf.voice_too_low_gear = mk_voice(too_low_flaps);
682 MK_VIII::ConfigurationModule::read_terrain_display_select (int value)
685 mk->tcf_handler.conf.enabled = false;
686 else if (value == 0 || value == 1 || (value >= 3 && value <= 15)
687 || (value >= 18 && value <= 20) || (value >= 235 && value <= 255))
688 mk->tcf_handler.conf.enabled = true;
696 MK_VIII::ConfigurationModule::read_options_select_group_1 (int value)
698 if (value >= 0 && value < 128)
700 mk->io_handler.conf.flap_reversal = test_bits(value, 1 << 1);
701 mk->mode6_handler.conf.bank_angle_enabled = test_bits(value, 1 << 2);
702 mk->io_handler.conf.steep_approach_enabled = test_bits(value, 1 << 6);
710 MK_VIII::ConfigurationModule::read_radio_altitude_input_select (int value)
712 mk->io_handler.conf.altitude_source = value;
713 return (value >= 0 && value <= 4) || (value >= 251 && value <= 255);
717 MK_VIII::ConfigurationModule::read_navigation_input_select (int value)
719 if (value >= 0 && value <= 2)
720 mk->io_handler.conf.localizer_enabled = false;
721 else if ((value >= 3 && value <= 5) || (value >= 250 && value <= 255))
722 mk->io_handler.conf.localizer_enabled = true;
730 MK_VIII::ConfigurationModule::read_attitude_input_select (int value)
733 mk->io_handler.conf.use_attitude_indicator=true;
735 mk->io_handler.conf.use_attitude_indicator=false;
736 return (value >= 0 && value <= 6) || value == 253 || value == 255;
740 MK_VIII::ConfigurationModule::read_heading_input_select (int value)
743 return (value >= 0 && value <= 3) || value == 254 || value == 255;
747 MK_VIII::ConfigurationModule::read_windshear_input_select (int value)
750 return value == 0 || (value >= 253 && value <= 255);
754 MK_VIII::ConfigurationModule::read_input_output_discrete_type_select (int value)
759 IOHandler::LampConfiguration lamp_conf;
760 bool gpws_inhibit_enabled;
761 bool momentary_flap_override_enabled;
762 bool alternate_steep_approach;
764 { 0, { false, false }, false, true, true },
765 { 1, { true, false }, false, true, true },
766 { 2, { false, false }, true, true, true },
767 { 3, { true, false }, true, true, true },
768 { 4, { false, true }, true, true, true },
769 { 5, { true, true }, true, true, true },
770 { 6, { false, false }, true, true, false },
771 { 7, { true, false }, true, true, false },
772 { 254, { false, false }, true, false, true },
773 { 255, { false, false }, true, false, true }
776 for (size_t i = 0; i < n_elements(io_types); i++)
777 if (io_types[i].type == value)
779 mk->io_handler.conf.lamp = &io_types[i].lamp_conf;
780 mk->io_handler.conf.gpws_inhibit_enabled = io_types[i].gpws_inhibit_enabled;
781 mk->io_handler.conf.momentary_flap_override_enabled = io_types[i].momentary_flap_override_enabled;
782 mk->io_handler.conf.alternate_steep_approach = io_types[i].alternate_steep_approach;
790 MK_VIII::ConfigurationModule::read_audio_output_level (int value)
804 for (size_t i = 0; i < n_elements(values); i++)
805 if (values[i].id == value)
807 mk->voice_player.set_volume(mk->voice_player.conf.volume = modify_amplitude(1.0, values[i].relative_dB));
811 // The self test needs the voice player even when the configuration
812 // is invalid, so set a default value.
813 mk->voice_player.set_volume(mk->voice_player.conf.volume = 1.0);
818 MK_VIII::ConfigurationModule::read_undefined_input_select (int value)
825 MK_VIII::ConfigurationModule::boot ()
827 bool (MK_VIII::ConfigurationModule::*readers[N_CATEGORIES]) (int) = {
828 &MK_VIII::ConfigurationModule::read_aircraft_mode_type_select,
829 &MK_VIII::ConfigurationModule::read_air_data_input_select,
830 &MK_VIII::ConfigurationModule::read_position_input_select,
831 &MK_VIII::ConfigurationModule::read_altitude_callouts,
832 &MK_VIII::ConfigurationModule::read_audio_menu_select,
833 &MK_VIII::ConfigurationModule::read_terrain_display_select,
834 &MK_VIII::ConfigurationModule::read_options_select_group_1,
835 &MK_VIII::ConfigurationModule::read_radio_altitude_input_select,
836 &MK_VIII::ConfigurationModule::read_navigation_input_select,
837 &MK_VIII::ConfigurationModule::read_attitude_input_select,
838 &MK_VIII::ConfigurationModule::read_heading_input_select,
839 &MK_VIII::ConfigurationModule::read_windshear_input_select,
840 &MK_VIII::ConfigurationModule::read_input_output_discrete_type_select,
841 &MK_VIII::ConfigurationModule::read_audio_output_level,
842 &MK_VIII::ConfigurationModule::read_undefined_input_select,
843 &MK_VIII::ConfigurationModule::read_undefined_input_select,
844 &MK_VIII::ConfigurationModule::read_undefined_input_select
847 memcpy(effective_categories, categories, sizeof(categories));
850 for (int i = 0; i < N_CATEGORIES; i++)
851 if (! (this->*readers[i])(effective_categories[i]))
853 SG_LOG(SG_INSTR, SG_ALERT, "MK VIII EGPWS configuration category " << i + 1 << ": invalid value " << effective_categories[i]);
855 if (state == STATE_OK)
856 state = STATE_INVALID_DATABASE;
858 mk_doutput(gpws_inop) = true;
859 mk_doutput(tad_inop) = true;
866 if (mk->mode6_handler.conf.above_field_voice && ! mk->tcf_handler.conf.enabled)
869 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");
870 state = STATE_INVALID_DATABASE;
875 MK_VIII::ConfigurationModule::bind (SGPropertyNode *node)
877 for (int i = 0; i < N_CATEGORIES; i++)
879 std::ostringstream name;
880 name << "configuration-module/category-" << i + 1;
881 mk->properties_handler.tie(node, name.str().c_str(), SGRawValuePointer<int>(&categories[i]));
885 ///////////////////////////////////////////////////////////////////////////////
886 // FaultHandler ///////////////////////////////////////////////////////////////
887 ///////////////////////////////////////////////////////////////////////////////
889 // [INSTALL] only specifies that the faults cause a GPWS INOP
890 // indication. We arbitrarily set a TAD INOP when it makes sense.
891 const unsigned int MK_VIII::FaultHandler::fault_inops[] = {
892 INOP_GPWS | INOP_TAD, // [INSTALL] 3.15.1.3
893 INOP_GPWS, // [INSTALL] appendix E 6.6.2
894 INOP_GPWS, // [INSTALL] appendix E 6.6.4
895 INOP_GPWS, // [INSTALL] appendix E 6.6.6
896 INOP_GPWS | INOP_TAD, // [INSTALL] appendix E 6.6.7
897 INOP_GPWS, // [INSTALL] appendix E 6.6.13
898 INOP_GPWS, // [INSTALL] appendix E 6.6.25
899 INOP_GPWS, // [INSTALL] appendix E 6.6.27
900 INOP_TAD, // unspecified
901 INOP_GPWS, // unspecified
902 INOP_GPWS, // unspecified
903 // [INSTALL] 2.3.10.1 specifies that GPWS INOP is "activated during
904 // any detected partial or total failure of the GPWS modes 1-5", so
905 // do not set any INOP for mode 6 and bank angle.
908 INOP_TAD // unspecified
912 MK_VIII::FaultHandler::has_faults () const
914 for (int i = 0; i < N_FAULTS; i++)
922 MK_VIII::FaultHandler::has_faults (unsigned int inop)
924 for (int i = 0; i < N_FAULTS; i++)
925 if (faults[i] && test_bits(fault_inops[i], inop))
932 MK_VIII::FaultHandler::boot ()
934 memset(faults, 0, sizeof(faults));
938 MK_VIII::FaultHandler::set_fault (Fault fault)
942 faults[fault] = true;
944 mk->self_test_handler.set_inop();
946 if (test_bits(fault_inops[fault], INOP_GPWS))
948 mk_unset_alerts(~mk_alert(TCF_TOO_LOW_TERRAIN));
949 mk_doutput(gpws_inop) = true;
951 if (test_bits(fault_inops[fault], INOP_TAD))
953 mk_unset_alerts(mk_alert(TCF_TOO_LOW_TERRAIN));
954 mk_doutput(tad_inop) = true;
960 MK_VIII::FaultHandler::unset_fault (Fault fault)
964 faults[fault] = false;
965 if (! has_faults(INOP_GPWS))
966 mk_doutput(gpws_inop) = false;
967 if (! has_faults(INOP_TAD))
968 mk_doutput(tad_inop) = false;
972 ///////////////////////////////////////////////////////////////////////////////
973 // IOHandler //////////////////////////////////////////////////////////////////
974 ///////////////////////////////////////////////////////////////////////////////
977 MK_VIII::IOHandler::TerrainClearanceFilter::update (double agl)
979 // [PILOT] page 20 specifies that the terrain clearance is equal to
980 // 75% of the radio altitude, averaged over the previous 15 seconds.
982 // no updates when simulation is paused (dt=0.0), and add 5 samples/second only
983 if (globals->get_sim_time_sec() - last_update < 0.2)
985 last_update = globals->get_sim_time_sec();
987 samples_type::iterator iter;
989 // remove samples older than 15 seconds
990 for (iter = samples.begin(); iter != samples.end() && globals->get_sim_time_sec() - (*iter).timestamp >= 15; iter = samples.begin())
994 samples.push_back(Sample<double>(agl));
997 double new_value = 0;
998 if (samples.size() > 0)
1000 // time consuming loop => queue limited to 75 samples
1001 // (= 15seconds * 5samples/second)
1002 for (iter = samples.begin(); iter != samples.end(); iter++)
1003 new_value += (*iter).value;
1004 new_value /= samples.size();
1008 if (new_value > value)
1015 MK_VIII::IOHandler::TerrainClearanceFilter::reset ()
1022 MK_VIII::IOHandler::IOHandler (MK_VIII *device)
1023 : mk(device), _lamp(LAMP_NONE)
1025 memset(&input_feeders, 0, sizeof(input_feeders));
1026 memset(&inputs.discretes, 0, sizeof(inputs.discretes));
1027 memset(&outputs, 0, sizeof(outputs));
1028 memset(&power_saved, 0, sizeof(power_saved));
1030 mk_dinput_feed(landing_gear) = true;
1031 mk_dinput_feed(landing_flaps) = true;
1032 mk_dinput_feed(glideslope_inhibit) = true;
1033 mk_dinput_feed(decision_height) = true;
1034 mk_dinput_feed(autopilot_engaged) = true;
1035 mk_ainput_feed(uncorrected_barometric_altitude) = true;
1036 mk_ainput_feed(barometric_altitude_rate) = true;
1037 mk_ainput_feed(radio_altitude) = true;
1038 mk_ainput_feed(glideslope_deviation) = true;
1039 mk_ainput_feed(roll_angle) = true;
1040 mk_ainput_feed(localizer_deviation) = true;
1041 mk_ainput_feed(computed_airspeed) = true;
1043 // will be unset on power on
1044 mk_doutput(gpws_inop) = true;
1045 mk_doutput(tad_inop) = true;
1049 MK_VIII::IOHandler::boot ()
1051 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
1054 mk_doutput(gpws_inop) = false;
1055 mk_doutput(tad_inop) = false;
1057 mk_doutput(glideslope_cancel) = power_saved.glideslope_cancel;
1059 altitude_samples.clear();
1060 reset_terrain_clearance();
1064 MK_VIII::IOHandler::post_boot ()
1066 if (momentary_steep_approach_enabled())
1068 last_landing_gear = mk_dinput(landing_gear);
1069 last_real_flaps_down = real_flaps_down();
1072 // read externally-latching input discretes
1073 update_alternate_discrete_input(&mk_dinput(mode6_low_volume));
1074 update_alternate_discrete_input(&mk_dinput(audio_inhibit));
1078 MK_VIII::IOHandler::power_off ()
1080 power_saved.glideslope_cancel = mk_doutput(glideslope_cancel); // [SPEC] 6.2.5
1082 memset(&outputs, 0, sizeof(outputs));
1084 audio_inhibit_fault_timer.stop();
1085 landing_gear_fault_timer.stop();
1086 flaps_down_fault_timer.stop();
1087 momentary_flap_override_fault_timer.stop();
1088 self_test_fault_timer.stop();
1089 glideslope_cancel_fault_timer.stop();
1090 steep_approach_fault_timer.stop();
1091 gpws_inhibit_fault_timer.stop();
1092 ta_tcf_inhibit_fault_timer.stop();
1095 mk_doutput(gpws_inop) = true;
1096 mk_doutput(tad_inop) = true;
1100 MK_VIII::IOHandler::enter_ground ()
1102 reset_terrain_clearance();
1104 if (conf.momentary_flap_override_enabled)
1105 mk_doutput(flap_override) = false; // [INSTALL] 3.15.1.6
1109 MK_VIII::IOHandler::enter_takeoff ()
1111 reset_terrain_clearance();
1113 if (momentary_steep_approach_enabled())
1114 // landing or go-around, disable steep approach as per [SPEC] 6.2.1
1115 mk_doutput(steep_approach) = false;
1119 MK_VIII::IOHandler::update_inputs ()
1121 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
1124 // 1. process input feeders
1126 if (mk_dinput_feed(landing_gear))
1127 mk_dinput(landing_gear) = mk_node(gear_down)->getBoolValue();
1128 if (mk_dinput_feed(landing_flaps))
1129 mk_dinput(landing_flaps) = mk_node(flaps)->getDoubleValue() < 1;
1130 if (mk_dinput_feed(glideslope_inhibit))
1131 mk_dinput(glideslope_inhibit) = mk_node(nav0_gs_distance)->getDoubleValue() < 0;
1132 if (mk_dinput_feed(autopilot_engaged))
1136 mode = mk_node(autopilot_heading_lock)->getStringValue();
1137 mk_dinput(autopilot_engaged) = mode && *mode;
1140 if (mk_ainput_feed(uncorrected_barometric_altitude))
1142 if (mk_node(altimeter_serviceable)->getBoolValue())
1143 mk_ainput(uncorrected_barometric_altitude).set(mk_node(altimeter_altitude)->getDoubleValue());
1145 mk_ainput(uncorrected_barometric_altitude).unset();
1147 if (mk_ainput_feed(barometric_altitude_rate))
1148 // Do not use the vsi, because it is pressure-based only, and
1149 // therefore too laggy for GPWS alerting purposes. I guess that
1150 // a real ADC combines pressure-based and inerta-based altitude
1151 // rates to provide a non-laggy rate. That non-laggy rate is
1152 // best emulated by /velocities/vertical-speed-fps * 60.
1153 mk_ainput(barometric_altitude_rate).set(mk_node(vs)->getDoubleValue() * 60);
1154 if (mk_ainput_feed(radio_altitude))
1157 switch (conf.altitude_source)
1160 agl = mk_node(altitude_gear_agl)->getDoubleValue();
1163 agl = mk_node(altitude_radar_agl)->getDoubleValue();
1165 default: // 0,1,2 (and any currently unsupported values)
1166 agl = mk_node(altitude_agl)->getDoubleValue();
1169 // Some flight models may return negative values when on the
1170 // ground or after a crash; do not allow them.
1171 mk_ainput(radio_altitude).set(SG_MAX2(0.0, agl));
1173 if (mk_ainput_feed(glideslope_deviation))
1175 if (mk_node(nav0_serviceable)->getBoolValue()
1176 && mk_node(nav0_gs_serviceable)->getBoolValue()
1177 && mk_node(nav0_in_range)->getBoolValue()
1178 && mk_node(nav0_has_gs)->getBoolValue())
1179 // gs-needle-deflection is expressed in degrees, and 1 dot =
1180 // 0.32 degrees (according to
1181 // http://www.ntsb.gov/Recs/letters/2000/A00_41_45.pdf)
1182 mk_ainput(glideslope_deviation).set(mk_node(nav0_gs_needle_deflection)->getDoubleValue() / 0.32 * GLIDESLOPE_DOTS_TO_DDM);
1184 mk_ainput(glideslope_deviation).unset();
1186 if (mk_ainput_feed(roll_angle))
1188 if (conf.use_attitude_indicator)
1190 // read data from attitude indicator instrument (requires vacuum system to work)
1191 if (mk_node(ai_serviceable)->getBoolValue() && ! mk_node(ai_caged)->getBoolValue())
1192 mk_ainput(roll_angle).set(mk_node(ai_roll)->getDoubleValue());
1194 mk_ainput(roll_angle).unset();
1198 // use simulator source
1199 mk_ainput(roll_angle).set(mk_node(orientation_roll)->getDoubleValue());
1202 if (mk_ainput_feed(localizer_deviation))
1204 if (mk_node(nav0_serviceable)->getBoolValue()
1205 && mk_node(nav0_cdi_serviceable)->getBoolValue()
1206 && mk_node(nav0_in_range)->getBoolValue()
1207 && mk_node(nav0_nav_loc)->getBoolValue())
1208 // heading-needle-deflection is expressed in degrees, and 1
1209 // dot = 2 degrees (0.5 degrees for ILS, but navradio.cxx
1210 // performs the conversion)
1211 mk_ainput(localizer_deviation).set(mk_node(nav0_heading_needle_deflection)->getDoubleValue() / 2 * LOCALIZER_DOTS_TO_DDM);
1213 mk_ainput(localizer_deviation).unset();
1215 if (mk_ainput_feed(computed_airspeed))
1217 if (mk_node(asi_serviceable)->getBoolValue())
1218 mk_ainput(computed_airspeed).set(mk_node(asi_speed)->getDoubleValue());
1220 mk_ainput(computed_airspeed).unset();
1225 mk_data(decision_height).set(&mk_ainput(decision_height));
1226 mk_data(radio_altitude).set(&mk_ainput(radio_altitude));
1227 mk_data(roll_angle).set(&mk_ainput(roll_angle));
1229 // update barometric_altitude_rate as per [SPEC] 6.2.1: "During
1230 // normal conditions, the system will base Mode 1 computations upon
1231 // barometric rate from the ADC. If this computed data is not valid
1232 // or available then the system will use internally computed
1233 // barometric altitude rate."
1235 if (! mk_ainput(barometric_altitude_rate).ncd)
1236 // the altitude rate input is valid, use it
1237 mk_data(barometric_altitude_rate).set(mk_ainput(barometric_altitude_rate).get());
1242 // The altitude rate input is invalid. We can compute an
1243 // altitude rate if all the following conditions are true:
1245 // - the altitude input is valid
1246 // - there is an altitude sample with age >= 1 second
1247 // - that altitude sample is valid
1249 if (! mk_ainput(uncorrected_barometric_altitude).ncd)
1251 if (! altitude_samples.empty() && ! altitude_samples.front().value.ncd)
1253 double elapsed = globals->get_sim_time_sec() - altitude_samples.front().timestamp;
1257 mk_data(barometric_altitude_rate).set((mk_ainput(uncorrected_barometric_altitude).get() - altitude_samples.front().value.get()) / elapsed * 60);
1263 mk_data(barometric_altitude_rate).unset();
1266 altitude_samples.push_back(Sample< Parameter<double> >(mk_ainput(uncorrected_barometric_altitude)));
1268 // Erase everything from the beginning of the list up to the sample
1269 // preceding the most recent sample whose age is >= 1 second.
1271 altitude_samples_type::iterator erase_last = altitude_samples.begin();
1272 altitude_samples_type::iterator iter;
1274 for (iter = altitude_samples.begin(); iter != altitude_samples.end(); iter++)
1275 if (globals->get_sim_time_sec() - (*iter).timestamp >= 1)
1280 if (erase_last != altitude_samples.begin())
1281 altitude_samples.erase(altitude_samples.begin(), erase_last);
1285 if (conf.use_internal_gps)
1287 mk_data(gps_altitude).set(mk_node(altitude)->getDoubleValue());
1288 mk_data(gps_latitude).set(mk_node(latitude)->getDoubleValue());
1289 mk_data(gps_longitude).set(mk_node(longitude)->getDoubleValue());
1290 mk_data(gps_vertical_figure_of_merit).set(0.0);
1294 mk_data(gps_altitude).set(&mk_ainput(gps_altitude));
1295 mk_data(gps_latitude).set(&mk_ainput(gps_latitude));
1296 mk_data(gps_longitude).set(&mk_ainput(gps_longitude));
1297 mk_data(gps_vertical_figure_of_merit).set(&mk_ainput(gps_vertical_figure_of_merit));
1300 // update glideslope and localizer
1302 mk_data(glideslope_deviation_dots).set(&mk_ainput(glideslope_deviation), GLIDESLOPE_DDM_TO_DOTS);
1303 mk_data(localizer_deviation_dots).set(&mk_ainput(localizer_deviation), LOCALIZER_DDM_TO_DOTS);
1305 // Update geometric altitude; [SPEC] 6.7.8 provides an overview of a
1306 // complex algorithm which combines several input sources to
1307 // calculate the geometric altitude. Since the exact algorithm is
1308 // not given, we fallback to a much simpler method.
1310 if (! mk_data(gps_altitude).ncd)
1311 mk_data(geometric_altitude).set(mk_data(gps_altitude).get());
1312 else if (! mk_ainput(uncorrected_barometric_altitude).ncd)
1313 mk_data(geometric_altitude).set(mk_ainput(uncorrected_barometric_altitude).get());
1315 mk_data(geometric_altitude).unset();
1317 // update terrain clearance
1319 update_terrain_clearance();
1321 // 3. perform sanity checks
1323 if (! mk_data(radio_altitude).ncd && mk_data(radio_altitude).get() < 0)
1324 mk_data(radio_altitude).unset();
1326 if (! mk_data(decision_height).ncd && mk_data(decision_height).get() < 0)
1327 mk_data(decision_height).unset();
1329 if (! mk_data(gps_latitude).ncd
1330 && (mk_data(gps_latitude).get() < -90
1331 || mk_data(gps_latitude).get() > 90))
1332 mk_data(gps_latitude).unset();
1334 if (! mk_data(gps_longitude).ncd
1335 && (mk_data(gps_longitude).get() < -180
1336 || mk_data(gps_longitude).get() > 180))
1337 mk_data(gps_longitude).unset();
1339 if (! mk_data(roll_angle).ncd
1340 && ((mk_data(roll_angle).get() < -180)
1341 || (mk_data(roll_angle).get() > 180)))
1342 mk_data(roll_angle).unset();
1344 // 4. process input feeders requiring data computed above
1346 if (mk_dinput_feed(decision_height))
1347 mk_dinput(decision_height) = ! mk_data(radio_altitude).ncd
1348 && ! mk_data(decision_height).ncd
1349 && mk_data(radio_altitude).get() <= mk_data(decision_height).get();
1353 MK_VIII::IOHandler::update_terrain_clearance ()
1355 if (! mk_data(radio_altitude).ncd)
1356 mk_data(terrain_clearance).set(terrain_clearance_filter.update(mk_data(radio_altitude).get()));
1358 mk_data(terrain_clearance).unset();
1362 MK_VIII::IOHandler::reset_terrain_clearance ()
1364 terrain_clearance_filter.reset();
1365 update_terrain_clearance();
1369 MK_VIII::IOHandler::reposition ()
1371 reset_terrain_clearance();
1375 MK_VIII::IOHandler::handle_input_fault (bool test, FaultHandler::Fault fault)
1379 if (! mk->fault_handler.faults[fault])
1380 mk->fault_handler.set_fault(fault);
1383 mk->fault_handler.unset_fault(fault);
1387 MK_VIII::IOHandler::handle_input_fault (bool test,
1389 double max_duration,
1390 FaultHandler::Fault fault)
1394 if (! mk->fault_handler.faults[fault])
1396 if (timer->start_or_elapsed() >= max_duration)
1397 mk->fault_handler.set_fault(fault);
1402 mk->fault_handler.unset_fault(fault);
1408 MK_VIII::IOHandler::update_input_faults ()
1410 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
1413 // [INSTALL] 3.15.1.3
1414 handle_input_fault(mk_dinput(audio_inhibit),
1415 &audio_inhibit_fault_timer,
1417 FaultHandler::FAULT_ALL_MODES_INHIBIT);
1419 // [INSTALL] appendix E 6.6.2
1420 handle_input_fault(mk_dinput(landing_gear)
1421 && ! mk_ainput(computed_airspeed).ncd
1422 && mk_ainput(computed_airspeed).get() > conf.faults->max_gear_down_airspeed,
1423 &landing_gear_fault_timer,
1425 FaultHandler::FAULT_GEAR_SWITCH);
1427 // [INSTALL] appendix E 6.6.4
1428 handle_input_fault(flaps_down()
1429 && ! mk_ainput(computed_airspeed).ncd
1430 && mk_ainput(computed_airspeed).get() > conf.faults->max_flaps_down_airspeed,
1431 &flaps_down_fault_timer,
1433 FaultHandler::FAULT_FLAPS_SWITCH);
1435 // [INSTALL] appendix E 6.6.6
1436 if (conf.momentary_flap_override_enabled)
1437 handle_input_fault(mk_dinput(momentary_flap_override),
1438 &momentary_flap_override_fault_timer,
1440 FaultHandler::FAULT_MOMENTARY_FLAP_OVERRIDE_INVALID);
1442 // [INSTALL] appendix E 6.6.7
1443 handle_input_fault(mk_dinput(self_test),
1444 &self_test_fault_timer,
1446 FaultHandler::FAULT_SELF_TEST_INVALID);
1448 // [INSTALL] appendix E 6.6.13
1449 handle_input_fault(mk_dinput(glideslope_cancel),
1450 &glideslope_cancel_fault_timer,
1452 FaultHandler::FAULT_GLIDESLOPE_CANCEL_INVALID);
1454 // [INSTALL] appendix E 6.6.25
1455 if (momentary_steep_approach_enabled())
1456 handle_input_fault(mk_dinput(steep_approach),
1457 &steep_approach_fault_timer,
1459 FaultHandler::FAULT_STEEP_APPROACH_INVALID);
1461 // [INSTALL] appendix E 6.6.27
1462 if (conf.gpws_inhibit_enabled)
1463 handle_input_fault(mk_dinput(gpws_inhibit),
1464 &gpws_inhibit_fault_timer,
1466 FaultHandler::FAULT_GPWS_INHIBIT);
1468 // [INSTALL] does not specify a fault for this one, but it makes
1469 // sense to have it behave like GPWS inhibit
1470 handle_input_fault(mk_dinput(ta_tcf_inhibit),
1471 &ta_tcf_inhibit_fault_timer,
1473 FaultHandler::FAULT_TA_TCF_INHIBIT);
1475 // [PILOT] page 48: "In the event that required data for a
1476 // particular function is not available, then that function is
1477 // automatically inhibited and annunciated"
1479 handle_input_fault(mk_data(radio_altitude).ncd
1480 || mk_ainput(uncorrected_barometric_altitude).ncd
1481 || mk_data(barometric_altitude_rate).ncd
1482 || mk_ainput(computed_airspeed).ncd
1483 || mk_data(terrain_clearance).ncd,
1484 FaultHandler::FAULT_MODES14_INPUTS_INVALID);
1486 if (! mk_dinput(glideslope_inhibit))
1487 handle_input_fault(mk_data(radio_altitude).ncd,
1488 FaultHandler::FAULT_MODE5_INPUTS_INVALID);
1490 if (mk->mode6_handler.altitude_callouts_enabled())
1491 handle_input_fault(mk->mode6_handler.conf.above_field_voice
1492 ? (mk_data(gps_latitude).ncd
1493 || mk_data(gps_longitude).ncd
1494 || mk_data(geometric_altitude).ncd)
1495 : mk_data(radio_altitude).ncd,
1496 FaultHandler::FAULT_MODE6_INPUTS_INVALID);
1498 if (mk->mode6_handler.conf.bank_angle_enabled)
1499 handle_input_fault(mk_data(roll_angle).ncd,
1500 FaultHandler::FAULT_BANK_ANGLE_INPUTS_INVALID);
1502 if (mk->tcf_handler.conf.enabled)
1503 handle_input_fault(mk_data(radio_altitude).ncd
1504 || mk_data(geometric_altitude).ncd
1505 || mk_data(gps_latitude).ncd
1506 || mk_data(gps_longitude).ncd
1507 || mk_data(gps_vertical_figure_of_merit).ncd,
1508 FaultHandler::FAULT_TCF_INPUTS_INVALID);
1512 MK_VIII::IOHandler::update_alternate_discrete_input (bool *ptr)
1514 assert(mk->system_handler.state == SystemHandler::STATE_ON);
1516 if (ptr == &mk_dinput(mode6_low_volume))
1518 if (mk->configuration_module.state == ConfigurationModule::STATE_OK
1519 && mk->self_test_handler.state == SelfTestHandler::STATE_NONE)
1520 mk->mode6_handler.set_volume(*ptr ? modify_amplitude(1.0, -6) : 1.0);
1522 else if (ptr == &mk_dinput(audio_inhibit))
1524 if (mk->configuration_module.state == ConfigurationModule::STATE_OK
1525 && mk->self_test_handler.state == SelfTestHandler::STATE_NONE)
1526 mk->voice_player.set_volume(*ptr ? 0.0 : mk->voice_player.conf.volume);
1531 MK_VIII::IOHandler::update_internal_latches ()
1533 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
1537 if (conf.momentary_flap_override_enabled
1538 && mk_doutput(flap_override)
1539 && ! mk->state_handler.takeoff
1540 && (mk_data(radio_altitude).ncd || mk_data(radio_altitude).get() <= 50))
1541 mk_doutput(flap_override) = false;
1544 if (momentary_steep_approach_enabled())
1546 if (mk_doutput(steep_approach)
1547 && ! mk->state_handler.takeoff
1548 && ((last_landing_gear && ! mk_dinput(landing_gear))
1549 || (last_real_flaps_down && ! real_flaps_down())))
1550 // gear or flaps raised during approach: it's a go-around
1551 mk_doutput(steep_approach) = false;
1553 last_landing_gear = mk_dinput(landing_gear);
1554 last_real_flaps_down = real_flaps_down();
1558 if (mk_doutput(glideslope_cancel)
1559 && (mk_data(glideslope_deviation_dots).ncd
1560 || mk_data(radio_altitude).ncd
1561 || mk_data(radio_altitude).get() > 2000
1562 || mk_data(radio_altitude).get() < 30))
1563 mk_doutput(glideslope_cancel) = false;
1567 MK_VIII::IOHandler::update_egpws_alert_discrete_1 ()
1569 if (mk->voice_player.voice)
1574 VoicePlayer::Voice *voice;
1576 { 11, mk_voice(sink_rate_pause_sink_rate) },
1577 { 12, mk_voice(pull_up) },
1578 { 13, mk_voice(terrain) },
1579 { 13, mk_voice(terrain_pause_terrain) },
1580 { 14, mk_voice(dont_sink_pause_dont_sink) },
1581 { 15, mk_voice(too_low_gear) },
1582 { 16, mk_voice(too_low_flaps) },
1583 { 17, mk_voice(too_low_terrain) },
1584 { 18, mk_voice(soft_glideslope) },
1585 { 18, mk_voice(hard_glideslope) },
1586 { 19, mk_voice(minimums_minimums) }
1589 for (size_t i = 0; i < n_elements(voices); i++)
1590 if (voices[i].voice == mk->voice_player.voice)
1592 mk_aoutput(egpws_alert_discrete_1) = 1 << voices[i].bit;
1597 mk_aoutput(egpws_alert_discrete_1) = 0;
1601 MK_VIII::IOHandler::update_egpwc_logic_discretes ()
1603 mk_aoutput(egpwc_logic_discretes) = 0;
1605 if (mk->state_handler.takeoff)
1606 mk_aoutput(egpwc_logic_discretes) |= 1 << 18;
1611 unsigned int alerts;
1613 { 13, mk_alert(TCF_TOO_LOW_TERRAIN) },
1614 { 19, mk_alert(MODE1_SINK_RATE) },
1615 { 20, mk_alert(MODE1_PULL_UP) },
1616 { 21, mk_alert(MODE2A_PREFACE) | mk_alert(MODE2B_PREFACE) | mk_alert(MODE2B_LANDING_MODE) | mk_alert(MODE2A_ALTITUDE_GAIN_TERRAIN_CLOSING) },
1617 { 22, mk_alert(MODE2A) | mk_alert(MODE2B) },
1618 { 23, mk_alert(MODE3) },
1619 { 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) },
1620 { 25, mk_alert(MODE5_SOFT) | mk_alert(MODE5_HARD) }
1623 for (size_t i = 0; i < n_elements(logic); i++)
1624 if (mk_test_alerts(logic[i].alerts))
1625 mk_aoutput(egpwc_logic_discretes) |= 1 << logic[i].bit;
1629 MK_VIII::IOHandler::update_mode6_callouts_discrete_1 ()
1631 if (mk->voice_player.voice)
1636 VoicePlayer::Voice *voice;
1638 { 11, mk_voice(minimums_minimums) },
1639 { 16, mk_altitude_voice(Mode6Handler::ALTITUDE_CALLOUT_10) },
1640 { 17, mk_altitude_voice(Mode6Handler::ALTITUDE_CALLOUT_20) },
1641 { 18, mk_altitude_voice(Mode6Handler::ALTITUDE_CALLOUT_30) },
1642 { 19, mk_altitude_voice(Mode6Handler::ALTITUDE_CALLOUT_40) },
1643 { 20, mk_altitude_voice(Mode6Handler::ALTITUDE_CALLOUT_50) },
1644 { 23, mk_altitude_voice(Mode6Handler::ALTITUDE_CALLOUT_100) },
1645 { 24, mk_altitude_voice(Mode6Handler::ALTITUDE_CALLOUT_200) },
1646 { 25, mk_altitude_voice(Mode6Handler::ALTITUDE_CALLOUT_300) }
1649 for (size_t i = 0; i < n_elements(voices); i++)
1650 if (voices[i].voice == mk->voice_player.voice)
1652 mk_aoutput(mode6_callouts_discrete_1) = 1 << voices[i].bit;
1657 mk_aoutput(mode6_callouts_discrete_1) = 0;
1661 MK_VIII::IOHandler::update_mode6_callouts_discrete_2 ()
1663 if (mk->voice_player.voice)
1668 VoicePlayer::Voice *voice;
1670 { 11, mk_altitude_voice(Mode6Handler::ALTITUDE_CALLOUT_400) },
1671 { 12, mk_altitude_voice(Mode6Handler::ALTITUDE_CALLOUT_500) },
1672 { 13, mk_altitude_voice(Mode6Handler::ALTITUDE_CALLOUT_1000) },
1673 { 18, mk_voice(bank_angle_pause_bank_angle) },
1674 { 18, mk_voice(bank_angle_pause_bank_angle_3) },
1675 { 23, mk_voice(five_hundred_above) }
1678 for (size_t i = 0; i < n_elements(voices); i++)
1679 if (voices[i].voice == mk->voice_player.voice)
1681 mk_aoutput(mode6_callouts_discrete_2) = 1 << voices[i].bit;
1686 mk_aoutput(mode6_callouts_discrete_2) = 0;
1690 MK_VIII::IOHandler::update_egpws_alert_discrete_2 ()
1692 mk_aoutput(egpws_alert_discrete_2) = 1 << 27; // Terrain NA
1694 if (mk_doutput(glideslope_cancel))
1695 mk_aoutput(egpws_alert_discrete_2) |= 1 << 11;
1696 if (mk_doutput(gpws_alert))
1697 mk_aoutput(egpws_alert_discrete_2) |= 1 << 12;
1698 if (mk_doutput(gpws_warning))
1699 mk_aoutput(egpws_alert_discrete_2) |= 1 << 13;
1700 if (mk_doutput(gpws_inop))
1701 mk_aoutput(egpws_alert_discrete_2) |= 1 << 14;
1702 if (mk_doutput(audio_on))
1703 mk_aoutput(egpws_alert_discrete_2) |= 1 << 16;
1704 if (mk_doutput(tad_inop))
1705 mk_aoutput(egpws_alert_discrete_2) |= 1 << 24;
1706 if (mk->fault_handler.has_faults())
1707 mk_aoutput(egpws_alert_discrete_2) |= 1 << 25;
1711 MK_VIII::IOHandler::update_egpwc_alert_discrete_3 ()
1713 mk_aoutput(egpwc_alert_discrete_3) = 1 << 17 | 1 << 18;
1715 if (mk->fault_handler.faults[FaultHandler::FAULT_MODES14_INPUTS_INVALID])
1716 mk_aoutput(egpwc_alert_discrete_3) |= 1 << 11;
1717 if (mk->fault_handler.faults[FaultHandler::FAULT_MODE5_INPUTS_INVALID])
1718 mk_aoutput(egpwc_alert_discrete_3) |= 1 << 12;
1719 if (mk->fault_handler.faults[FaultHandler::FAULT_MODE6_INPUTS_INVALID])
1720 mk_aoutput(egpwc_alert_discrete_3) |= 1 << 13;
1721 if (mk->fault_handler.faults[FaultHandler::FAULT_BANK_ANGLE_INPUTS_INVALID])
1722 mk_aoutput(egpwc_alert_discrete_3) |= 1 << 14;
1723 if (mk_doutput(tad_inop))
1724 mk_aoutput(egpwc_alert_discrete_3) |= 1 << 16;
1728 MK_VIII::IOHandler::update_outputs ()
1730 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
1733 mk_doutput(audio_on) = ! mk_dinput(audio_inhibit)
1734 && mk->voice_player.voice
1735 && ! mk->voice_player.voice->element->silence;
1737 update_egpws_alert_discrete_1();
1738 update_egpwc_logic_discretes();
1739 update_mode6_callouts_discrete_1();
1740 update_mode6_callouts_discrete_2();
1741 update_egpws_alert_discrete_2();
1742 update_egpwc_alert_discrete_3();
1746 MK_VIII::IOHandler::get_lamp_output (Lamp lamp)
1750 case LAMP_GLIDESLOPE:
1751 return &mk_doutput(gpws_alert);
1754 return conf.lamp->format2 ? &mk_doutput(gpws_alert) : &mk_doutput(gpws_warning);
1757 return &mk_doutput(gpws_warning);
1760 assert_not_reached();
1766 MK_VIII::IOHandler::update_lamps ()
1768 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
1771 if (_lamp != LAMP_NONE && conf.lamp->flashing)
1773 // [SPEC] 6.9.3: 70 cycles per minute
1774 if (lamp_timer.elapsed() >= 60.0 / 70.0 / 2.0)
1776 bool *output = get_lamp_output(_lamp);
1777 *output = ! *output;
1784 MK_VIII::IOHandler::set_lamp (Lamp lamp)
1791 mk_doutput(gpws_warning) = false;
1792 mk_doutput(gpws_alert) = false;
1794 if (lamp != LAMP_NONE)
1796 *get_lamp_output(lamp) = true;
1802 MK_VIII::IOHandler::gpws_inhibit () const
1804 return conf.gpws_inhibit_enabled && mk_dinput(gpws_inhibit);
1808 MK_VIII::IOHandler::real_flaps_down () const
1810 return conf.flap_reversal ? mk_dinput(landing_flaps) : ! mk_dinput(landing_flaps);
1814 MK_VIII::IOHandler::flaps_down () const
1816 return flap_override() ? true : real_flaps_down();
1820 MK_VIII::IOHandler::flap_override () const
1822 return conf.momentary_flap_override_enabled ? mk_doutput(flap_override) : false;
1826 MK_VIII::IOHandler::steep_approach () const
1828 if (conf.steep_approach_enabled)
1829 // If alternate action was configured in category 13, we return
1830 // the value of the input discrete (which should be connected to a
1831 // latching steep approach cockpit switch). Otherwise, we return
1832 // the value of the output discrete.
1833 return conf.alternate_steep_approach ? mk_dinput(steep_approach) : mk_doutput(steep_approach);
1839 MK_VIII::IOHandler::momentary_steep_approach_enabled () const
1841 return conf.steep_approach_enabled && ! conf.alternate_steep_approach;
1845 MK_VIII::IOHandler::tie_input (SGPropertyNode *node,
1850 mk->properties_handler.tie(node, (string("inputs/discretes/") + name).c_str(),
1851 FGVoicePlayer::RawValueMethodsData<MK_VIII::IOHandler, bool, bool *>(*this, input, &MK_VIII::IOHandler::get_discrete_input, &MK_VIII::IOHandler::set_discrete_input));
1853 mk->properties_handler.tie(node, (string("input-feeders/discretes/") + name).c_str(), SGRawValuePointer<bool>(feed));
1857 MK_VIII::IOHandler::tie_input (SGPropertyNode *node,
1859 Parameter<double> *input,
1862 mk->properties_handler.tie(node, (string("inputs/arinc429/") + name).c_str(), SGRawValuePointer<double>(input->get_pointer()));
1863 mk->properties_handler.tie(node, (string("inputs/arinc429/") + name + "-ncd").c_str(), SGRawValuePointer<bool>(&input->ncd));
1865 mk->properties_handler.tie(node, (string("input-feeders/arinc429/") + name).c_str(), SGRawValuePointer<bool>(feed));
1869 MK_VIII::IOHandler::tie_output (SGPropertyNode *node,
1873 SGPropertyNode *child = node->getNode((string("outputs/discretes/") + name).c_str(), true);
1875 mk->properties_handler.tie(child, SGRawValuePointer<bool>(output));
1876 child->setAttribute(SGPropertyNode::WRITE, false);
1880 MK_VIII::IOHandler::tie_output (SGPropertyNode *node,
1884 SGPropertyNode *child = node->getNode((string("outputs/arinc429/") + name).c_str(), true);
1886 mk->properties_handler.tie(child, SGRawValuePointer<int>(output));
1887 child->setAttribute(SGPropertyNode::WRITE, false);
1891 MK_VIII::IOHandler::bind (SGPropertyNode *node)
1893 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));
1895 tie_input(node, "landing-gear", &mk_dinput(landing_gear), &mk_dinput_feed(landing_gear));
1896 tie_input(node, "landing-flaps", &mk_dinput(landing_flaps), &mk_dinput_feed(landing_flaps));
1897 tie_input(node, "momentary-flap-override", &mk_dinput(momentary_flap_override));
1898 tie_input(node, "self-test", &mk_dinput(self_test));
1899 tie_input(node, "glideslope-inhibit", &mk_dinput(glideslope_inhibit), &mk_dinput_feed(glideslope_inhibit));
1900 tie_input(node, "glideslope-cancel", &mk_dinput(glideslope_cancel));
1901 tie_input(node, "decision-height", &mk_dinput(decision_height), &mk_dinput_feed(decision_height));
1902 tie_input(node, "mode6-low-volume", &mk_dinput(mode6_low_volume));
1903 tie_input(node, "audio-inhibit", &mk_dinput(audio_inhibit));
1904 tie_input(node, "ta-tcf-inhibit", &mk_dinput(ta_tcf_inhibit));
1905 tie_input(node, "autopilot-engaged", &mk_dinput(autopilot_engaged), &mk_dinput_feed(autopilot_engaged));
1906 tie_input(node, "steep-approach", &mk_dinput(steep_approach));
1907 tie_input(node, "gpws-inhibit", &mk_dinput(gpws_inhibit));
1909 tie_input(node, "uncorrected-barometric-altitude", &mk_ainput(uncorrected_barometric_altitude), &mk_ainput_feed(uncorrected_barometric_altitude));
1910 tie_input(node, "barometric-altitude-rate", &mk_ainput(barometric_altitude_rate), &mk_ainput_feed(barometric_altitude_rate));
1911 tie_input(node, "gps-altitude", &mk_ainput(gps_altitude));
1912 tie_input(node, "gps-latitude", &mk_ainput(gps_latitude));
1913 tie_input(node, "gps-longitude", &mk_ainput(gps_longitude));
1914 tie_input(node, "gps-vertical-figure-of-merit", &mk_ainput(gps_vertical_figure_of_merit));
1915 tie_input(node, "radio-altitude", &mk_ainput(radio_altitude), &mk_ainput_feed(radio_altitude));
1916 tie_input(node, "glideslope-deviation", &mk_ainput(glideslope_deviation), &mk_ainput_feed(glideslope_deviation));
1917 tie_input(node, "roll-angle", &mk_ainput(roll_angle), &mk_ainput_feed(roll_angle));
1918 tie_input(node, "localizer-deviation", &mk_ainput(localizer_deviation), &mk_ainput_feed(localizer_deviation));
1919 tie_input(node, "computed-airspeed", &mk_ainput(computed_airspeed), &mk_ainput_feed(computed_airspeed));
1920 tie_input(node, "decision-height", &mk_ainput(decision_height), &mk_ainput_feed(decision_height));
1922 tie_output(node, "gpws-warning", &mk_doutput(gpws_warning));
1923 tie_output(node, "gpws-alert", &mk_doutput(gpws_alert));
1924 tie_output(node, "audio-on", &mk_doutput(audio_on));
1925 tie_output(node, "gpws-inop", &mk_doutput(gpws_inop));
1926 tie_output(node, "tad-inop", &mk_doutput(tad_inop));
1927 tie_output(node, "flap-override", &mk_doutput(flap_override));
1928 tie_output(node, "glideslope-cancel", &mk_doutput(glideslope_cancel));
1929 tie_output(node, "steep-approach", &mk_doutput(steep_approach));
1931 tie_output(node, "egpws-alert-discrete-1", &mk_aoutput(egpws_alert_discrete_1));
1932 tie_output(node, "egpwc-logic-discretes", &mk_aoutput(egpwc_logic_discretes));
1933 tie_output(node, "mode6-callouts-discrete-1", &mk_aoutput(mode6_callouts_discrete_1));
1934 tie_output(node, "mode6-callouts-discrete-2", &mk_aoutput(mode6_callouts_discrete_2));
1935 tie_output(node, "egpws-alert-discrete-2", &mk_aoutput(egpws_alert_discrete_2));
1936 tie_output(node, "egpwc-alert-discrete-3", &mk_aoutput(egpwc_alert_discrete_3));
1940 MK_VIII::IOHandler::get_discrete_input (bool *ptr) const
1946 MK_VIII::IOHandler::set_discrete_input (bool *ptr, bool value)
1951 if (mk->system_handler.state == SystemHandler::STATE_ON)
1953 if (ptr == &mk_dinput(momentary_flap_override))
1955 if (mk->configuration_module.state == ConfigurationModule::STATE_OK
1956 && mk->self_test_handler.state == SelfTestHandler::STATE_NONE
1957 && conf.momentary_flap_override_enabled
1959 mk_doutput(flap_override) = ! mk_doutput(flap_override);
1961 else if (ptr == &mk_dinput(self_test))
1962 mk->self_test_handler.handle_button_event(value);
1963 else if (ptr == &mk_dinput(glideslope_cancel))
1965 if (mk->configuration_module.state == ConfigurationModule::STATE_OK
1966 && mk->self_test_handler.state == SelfTestHandler::STATE_NONE
1969 if (! mk_doutput(glideslope_cancel))
1973 // Although we are not called from update(), we are
1974 // sure that the inputs we use here are defined,
1975 // since state is STATE_ON.
1977 if (! mk_data(glideslope_deviation_dots).ncd
1978 && ! mk_data(radio_altitude).ncd
1979 && mk_data(radio_altitude).get() <= 2000
1980 && mk_data(radio_altitude).get() >= 30)
1981 mk_doutput(glideslope_cancel) = true;
1983 // else do nothing ([PILOT] page 22: "Glideslope Cancel
1984 // can not be deselected (reset) by again pressing the
1985 // Glideslope Cancel switch.")
1988 else if (ptr == &mk_dinput(steep_approach))
1990 if (mk->configuration_module.state == ConfigurationModule::STATE_OK
1991 && mk->self_test_handler.state == SelfTestHandler::STATE_NONE
1992 && momentary_steep_approach_enabled()
1994 mk_doutput(steep_approach) = ! mk_doutput(steep_approach);
2000 if (mk->system_handler.state == SystemHandler::STATE_ON)
2001 update_alternate_discrete_input(ptr);
2005 MK_VIII::IOHandler::present_status_section (const char *name)
2007 printf("%s\n", name);
2011 MK_VIII::IOHandler::present_status_item (const char *name, const char *value)
2014 printf("\t%-32s %s\n", name, value);
2016 printf("\t%s\n", name);
2020 MK_VIII::IOHandler::present_status_subitem (const char *name)
2022 printf("\t\t%s\n", name);
2026 MK_VIII::IOHandler::present_status ()
2030 if (mk->system_handler.state != SystemHandler::STATE_ON)
2033 present_status_section("EGPWC CONFIGURATION");
2034 present_status_item("PART NUMBER:", "965-1220-000"); // [SPEC] 1.1
2035 present_status_item("MOD STATUS:", "N/A");
2036 present_status_item("SERIAL NUMBER:", "N/A");
2038 present_status_item("APPLICATION S/W VERSION:", FLIGHTGEAR_VERSION);
2039 present_status_item("TERRAIN DATABASE VERSION:", FLIGHTGEAR_VERSION);
2040 present_status_item("ENVELOPE MOD DATABASE VERSION:", "N/A");
2041 present_status_item("BOOT CODE VERSION:", FLIGHTGEAR_VERSION);
2044 present_status_section("CURRENT FAULTS");
2045 if (mk->configuration_module.state == ConfigurationModule::STATE_OK && ! mk->fault_handler.has_faults())
2046 present_status_item("NO FAULTS");
2049 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
2051 present_status_item("GPWS COMPUTER FAULTS:");
2052 switch (mk->configuration_module.state)
2054 case ConfigurationModule::STATE_INVALID_DATABASE:
2055 present_status_subitem("APPLICATION DATABASE FAILED");
2058 case ConfigurationModule::STATE_INVALID_AIRCRAFT_TYPE:
2059 present_status_subitem("CONFIGURATION TYPE INVALID");
2063 assert_not_reached();
2069 present_status_item("GPWS COMPUTER OK");
2070 present_status_item("GPWS EXTERNAL FAULTS:");
2072 static const char *fault_names[] = {
2073 "ALL MODES INHIBIT",
2076 "MOMENTARY FLAP OVERRIDE INVALID",
2077 "SELF TEST INVALID",
2078 "GLIDESLOPE CANCEL INVALID",
2079 "STEEP APPROACH INVALID",
2082 "MODES 1-4 INPUTS INVALID",
2083 "MODE 5 INPUTS INVALID",
2084 "MODE 6 INPUTS INVALID",
2085 "BANK ANGLE INPUTS INVALID",
2086 "TCF INPUTS INVALID"
2089 for (size_t i = 0; i < n_elements(fault_names); i++)
2090 if (mk->fault_handler.faults[i])
2091 present_status_subitem(fault_names[i]);
2096 present_status_section("CONFIGURATION:");
2098 static const char *category_names[] = {
2101 "POSITION INPUT TYPE",
2102 "CALLOUTS OPTION TYPE",
2104 "TERRAIN DISPLAY TYPE",
2106 "RADIO ALTITUDE TYPE",
2107 "NAVIGATION INPUT TYPE",
2109 "MAGNETIC HEADING TYPE",
2110 "WINDSHEAR INPUT TYPE",
2115 for (size_t i = 0; i < n_elements(category_names); i++)
2117 std::ostringstream value;
2118 value << "= " << mk->configuration_module.effective_categories[i];
2119 present_status_item(category_names[i], value.str().c_str());
2124 MK_VIII::IOHandler::get_present_status () const
2130 MK_VIII::IOHandler::set_present_status (bool value)
2132 if (value) present_status();
2136 ///////////////////////////////////////////////////////////////////////////////
2137 // FGVoicePlayer //////////////////////////////////////////////////////////////
2138 ///////////////////////////////////////////////////////////////////////////////
2141 FGVoicePlayer::PropertiesHandler::unbind ()
2143 vector<SGPropertyNode_ptr>::iterator iter;
2145 for (iter = tied_properties.begin(); iter != tied_properties.end(); iter++)
2148 tied_properties.clear();
2152 FGVoicePlayer::Speaker::bind (SGPropertyNode *node)
2154 // uses xmlsound property names
2155 tie(node, "volume", &volume);
2156 tie(node, "pitch", &pitch);
2160 FGVoicePlayer::Speaker::update_configuration ()
2162 map< string, SGSharedPtr<SGSoundSample> >::iterator iter;
2163 for (iter = player->samples.begin(); iter != player->samples.end(); iter++)
2165 SGSoundSample *sample = (*iter).second;
2167 sample->set_pitch(pitch);
2171 player->voice->volume_changed();
2174 FGVoicePlayer::Voice::~Voice ()
2176 for (iter = elements.begin(); iter != elements.end(); iter++)
2177 delete *iter; // we owned the element
2182 FGVoicePlayer::Voice::play ()
2184 iter = elements.begin();
2187 element->play(get_volume());
2191 FGVoicePlayer::Voice::stop (bool now)
2195 if (now || element->silence)
2201 iter = elements.end() - 1; // stop after the current element finishes
2206 FGVoicePlayer::Voice::set_volume (float _volume)
2213 FGVoicePlayer::Voice::volume_changed ()
2216 element->set_volume(get_volume());
2220 FGVoicePlayer::Voice::update ()
2224 if (! element->is_playing())
2226 if (++iter == elements.end())
2231 element->play(get_volume());
2237 FGVoicePlayer::~FGVoicePlayer ()
2239 vector<Voice *>::iterator iter1;
2240 for (iter1 = _voices.begin(); iter1 != _voices.end(); iter1++)
2247 FGVoicePlayer::bind (SGPropertyNode *node, const char* default_dir_prefix)
2249 dir_prefix = node->getStringValue("voice/file-prefix", default_dir_prefix);
2254 FGVoicePlayer::init ()
2256 SGSoundMgr *smgr = globals->get_soundmgr();
2257 _sgr = smgr->find("avionics", true);
2258 _sgr->tie_to_listener();
2259 speaker.update_configuration();
2263 FGVoicePlayer::pause()
2276 FGVoicePlayer::resume()
2288 FGVoicePlayer::get_sample (const char *name)
2291 refname = dev_name + "/" + dir_prefix + name;
2293 SGSoundSample *sample = _sgr->find(refname);
2296 string filename = dir_prefix + string(name) + ".wav";
2299 sample = new SGSoundSample(filename.c_str(), SGPath());
2301 catch (const sg_exception &e)
2303 SG_LOG(SG_INSTR, SG_ALERT, "Error loading sound sample \"" + filename + "\": " + e.getFormattedMessage());
2307 _sgr->add(sample, refname);
2308 samples[refname] = sample;
2315 FGVoicePlayer::play (Voice *_voice, unsigned int flags)
2319 if (test_bits(flags, PLAY_NOW) || ! voice ||
2320 (voice->element && voice->element->silence))
2326 looped = test_bits(flags, PLAY_LOOPED);
2329 next_looped = false;
2336 next_voice = _voice;
2337 next_looped = test_bits(flags, PLAY_LOOPED);
2342 FGVoicePlayer::stop (unsigned int flags)
2346 voice->stop(test_bits(flags, STOP_NOW));
2356 FGVoicePlayer::set_volume (float _volume)
2360 voice->volume_changed();
2364 FGVoicePlayer::update ()
2372 if (! voice->element || voice->element->silence)
2375 looped = next_looped;
2378 next_looped = false;
2385 if (! voice->element)
2396 ///////////////////////////////////////////////////////////////////////////////
2397 // MK_VIII::VoicePlayer ///////////////////////////////////////////////////////
2398 ///////////////////////////////////////////////////////////////////////////////
2401 MK_VIII::VoicePlayer::init ()
2403 FGVoicePlayer::init();
2405 #define STDPAUSE 0.75 // [SPEC] 6.4.4: "the standard 0.75 second delay"
2406 make_voice(&voices.application_data_base_failed, "application-data-base-failed");
2407 make_voice(&voices.bank_angle, "bank-angle");
2408 make_voice(&voices.bank_angle_bank_angle, "bank-angle", "bank-angle");
2409 make_voice(&voices.bank_angle_bank_angle_3, "bank-angle", "bank-angle", 3.0);
2410 make_voice(&voices.bank_angle_inop, "bank-angle-inop");
2411 make_voice(&voices.bank_angle_pause_bank_angle, "bank-angle", STDPAUSE, "bank-angle");
2412 make_voice(&voices.bank_angle_pause_bank_angle_3, "bank-angle", STDPAUSE, "bank-angle", 3.0);
2413 make_voice(&voices.callouts_inop, "callouts-inop");
2414 make_voice(&voices.configuration_type_invalid, "configuration-type-invalid");
2415 make_voice(&voices.dont_sink, "dont-sink");
2416 make_voice(&voices.dont_sink_pause_dont_sink, "dont-sink", STDPAUSE, "dont-sink");
2417 make_voice(&voices.five_hundred_above, "500-above");
2418 make_voice(&voices.glideslope, "glideslope");
2419 make_voice(&voices.glideslope_inop, "glideslope-inop");
2420 make_voice(&voices.gpws_inop, "gpws-inop");
2421 make_voice(&voices.hard_glideslope, "glideslope", "glideslope", 3.0);
2422 make_voice(&voices.minimums, "minimums");
2423 make_voice(&voices.minimums_minimums, "minimums", "minimums");
2424 make_voice(&voices.pull_up, "pull-up");
2425 make_voice(&voices.sink_rate, "sink-rate");
2426 make_voice(&voices.sink_rate_pause_sink_rate, "sink-rate", STDPAUSE, "sink-rate");
2427 make_voice(&voices.soft_glideslope, new Voice::SampleElement(get_sample("glideslope"), modify_amplitude(1.0, -6)));
2428 make_voice(&voices.terrain, "terrain");
2429 make_voice(&voices.terrain_pause_terrain, "terrain", STDPAUSE, "terrain");
2430 make_voice(&voices.too_low_flaps, "too-low-flaps");
2431 make_voice(&voices.too_low_gear, "too-low-gear");
2432 make_voice(&voices.too_low_terrain, "too-low-terrain");
2434 for (unsigned i = 0; i < n_altitude_callouts; i++)
2436 std::ostringstream name;
2437 name << "altitude-" << MK_VIII::Mode6Handler::altitude_callout_definitions[i];
2438 make_voice(&voices.altitude_callouts[i], name.str().c_str());
2440 speaker.update_configuration();
2443 ///////////////////////////////////////////////////////////////////////////////
2444 // SelfTestHandler ////////////////////////////////////////////////////////////
2445 ///////////////////////////////////////////////////////////////////////////////
2448 MK_VIII::SelfTestHandler::_was_here (int position)
2450 if (position > current)
2459 MK_VIII::SelfTestHandler::Action
2460 MK_VIII::SelfTestHandler::sleep (double duration)
2462 Action _action = { ACTION_SLEEP, duration, NULL };
2466 MK_VIII::SelfTestHandler::Action
2467 MK_VIII::SelfTestHandler::play (VoicePlayer::Voice *voice)
2469 mk->voice_player.play(voice);
2470 Action _action = { ACTION_VOICE, 0, NULL };
2474 MK_VIII::SelfTestHandler::Action
2475 MK_VIII::SelfTestHandler::discrete_on (bool *discrete, double duration)
2478 return sleep(duration);
2481 MK_VIII::SelfTestHandler::Action
2482 MK_VIII::SelfTestHandler::discrete_on_off (bool *discrete, double duration)
2485 Action _action = { ACTION_SLEEP | ACTION_DISCRETE_ON_OFF, duration, discrete };
2489 MK_VIII::SelfTestHandler::Action
2490 MK_VIII::SelfTestHandler::discrete_on_off (bool *discrete, VoicePlayer::Voice *voice)
2493 mk->voice_player.play(voice);
2494 Action _action = { ACTION_VOICE | ACTION_DISCRETE_ON_OFF, 0, discrete };
2498 MK_VIII::SelfTestHandler::Action
2499 MK_VIII::SelfTestHandler::done ()
2501 Action _action = { ACTION_DONE, 0, NULL };
2505 MK_VIII::SelfTestHandler::Action
2506 MK_VIII::SelfTestHandler::run ()
2508 // Note that "Terrain INOP" and "Terrain NA" are or'ed to the same
2509 // output discrete (see [SPEC] 6.9.3.5).
2511 #define was_here() was_here_offset(0)
2512 #define was_here_offset(offset) _was_here(__LINE__ * 20 + (offset))
2516 if (mk->configuration_module.state == ConfigurationModule::STATE_INVALID_DATABASE)
2517 return play(mk_voice(application_data_base_failed));
2518 else if (mk->configuration_module.state == ConfigurationModule::STATE_INVALID_AIRCRAFT_TYPE)
2519 return play(mk_voice(configuration_type_invalid));
2521 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
2525 return discrete_on(&mk_doutput(gpws_inop), 0.7);
2527 return sleep(0.7); // W/S INOP
2529 return discrete_on(&mk_doutput(tad_inop), 0.7);
2532 mk_doutput(gpws_inop) = false;
2533 // do not disable tad_inop since we must enable Terrain NA
2534 // do not disable W/S INOP since we do not emulate it
2535 return sleep(0.7); // Terrain NA
2539 mk_doutput(tad_inop) = false; // disable Terrain NA
2540 if (mk->io_handler.conf.momentary_flap_override_enabled)
2541 return discrete_on_off(&mk_doutput(flap_override), 1.0);
2544 return discrete_on_off(&mk_doutput(audio_on), 1.0);
2547 if (mk->io_handler.momentary_steep_approach_enabled())
2548 return discrete_on_off(&mk_doutput(steep_approach), 1.0);
2553 if (mk_dinput(glideslope_inhibit))
2554 goto glideslope_end;
2557 if (mk->fault_handler.faults[FaultHandler::FAULT_MODE5_INPUTS_INVALID])
2558 goto glideslope_inop;
2562 return discrete_on_off(&mk_doutput(gpws_alert), mk_voice(glideslope));
2564 return discrete_on_off(&mk_doutput(glideslope_cancel), 0.7);
2565 goto glideslope_end;
2568 return play(mk_voice(glideslope_inop));
2573 if (mk->fault_handler.faults[FaultHandler::FAULT_MODES14_INPUTS_INVALID])
2577 return discrete_on_off(&mk_doutput(gpws_warning), mk_voice(pull_up));
2581 return play(mk_voice(gpws_inop));
2586 if (mk_dinput(self_test)) // proceed to long self test
2591 if (mk->mode6_handler.conf.bank_angle_enabled
2592 && mk->fault_handler.faults[FaultHandler::FAULT_BANK_ANGLE_INPUTS_INVALID])
2593 return play(mk_voice(bank_angle_inop));
2597 if (mk->mode6_handler.altitude_callouts_enabled()
2598 && mk->fault_handler.faults[FaultHandler::FAULT_MODE6_INPUTS_INVALID])
2599 return play(mk_voice(callouts_inop));
2607 mk_doutput(gpws_inop) = true;
2608 // do not enable W/S INOP, since we do not emulate it
2609 mk_doutput(tad_inop) = true; // Terrain INOP, Terrain NA
2611 return play(mk_voice(sink_rate));
2614 return play(mk_voice(pull_up));
2616 return play(mk_voice(terrain));
2618 return play(mk_voice(pull_up));
2620 return play(mk_voice(dont_sink));
2622 return play(mk_voice(too_low_terrain));
2624 return play(mk->mode4_handler.conf.voice_too_low_gear);
2626 return play(mk_voice(too_low_flaps));
2628 return play(mk_voice(too_low_terrain));
2630 return play(mk_voice(glideslope));
2633 if (mk->mode6_handler.conf.bank_angle_enabled)
2634 return play(mk_voice(bank_angle));
2639 if (! mk->mode6_handler.altitude_callouts_enabled())
2640 goto callouts_disabled;
2644 if (mk->mode6_handler.conf.minimums_enabled)
2645 return play(mk_voice(minimums));
2649 if (mk->mode6_handler.conf.above_field_voice)
2650 return play(mk->mode6_handler.conf.above_field_voice);
2652 for (unsigned i = 0; i < n_altitude_callouts; i++)
2653 if (! was_here_offset(i))
2655 if (mk->mode6_handler.conf.altitude_callouts_enabled[i])
2656 return play(mk_altitude_voice(i));
2660 if (mk->mode6_handler.conf.smart_500_enabled)
2661 return play(mk_altitude_voice(Mode6Handler::ALTITUDE_CALLOUT_500));
2666 return play(mk_voice(minimums_minimums));
2671 if (mk->tcf_handler.conf.enabled)
2672 return play(mk_voice(too_low_terrain));
2679 MK_VIII::SelfTestHandler::start ()
2681 assert(state == STATE_START);
2683 memcpy(&saved_outputs, &mk->io_handler.outputs, sizeof(mk->io_handler.outputs));
2684 memset(&mk->io_handler.outputs, 0, sizeof(mk->io_handler.outputs));
2686 // [SPEC] 6.10.6: "The self-test results are annunciated, at 6db
2687 // lower than the normal audio level selected for the aircraft"
2688 mk->voice_player.set_volume(modify_amplitude(mk->voice_player.conf.volume, -6));
2690 if (mk_dinput(mode6_low_volume))
2691 mk->mode6_handler.set_volume(1.0);
2693 state = STATE_RUNNING;
2694 cancel = CANCEL_NONE;
2695 memset(&action, 0, sizeof(action));
2700 MK_VIII::SelfTestHandler::stop ()
2702 if (state != STATE_NONE)
2704 if (state != STATE_START)
2706 mk->voice_player.stop(VoicePlayer::STOP_NOW);
2707 mk->voice_player.set_volume(mk_dinput(audio_inhibit) ? 0.0 : mk->voice_player.conf.volume);
2709 if (mk_dinput(mode6_low_volume))
2710 mk->mode6_handler.set_volume(modify_amplitude(1.0, -6));
2712 memcpy(&mk->io_handler.outputs, &saved_outputs, sizeof(mk->io_handler.outputs));
2715 button_pressed = false;
2717 // reset self-test handler position
2723 MK_VIII::SelfTestHandler::handle_button_event (bool value)
2725 if (state == STATE_NONE)
2728 state = STATE_START;
2730 else if (state == STATE_START)
2733 state = STATE_NONE; // cancel the not-yet-started test
2735 else if (cancel == CANCEL_NONE)
2739 assert(! button_pressed);
2740 button_pressed = true;
2741 button_press_timestamp = globals->get_sim_time_sec();
2747 if (globals->get_sim_time_sec() - button_press_timestamp < 2)
2748 cancel = CANCEL_SHORT;
2750 cancel = CANCEL_LONG;
2757 MK_VIII::SelfTestHandler::update ()
2759 if (state == STATE_NONE)
2761 else if (state == STATE_START)
2763 if (mk->state_handler.ground && ! mk->io_handler.steep_approach())
2773 if (button_pressed && cancel == CANCEL_NONE)
2775 if (globals->get_sim_time_sec() - button_press_timestamp >= 2)
2776 cancel = CANCEL_LONG;
2780 if (! mk->state_handler.ground || cancel != CANCEL_NONE)
2786 if (test_bits(action.flags, ACTION_SLEEP))
2788 if (globals->get_sim_time_sec() - sleep_start < action.sleep_duration)
2791 if (test_bits(action.flags, ACTION_VOICE))
2793 if (mk->voice_player.voice)
2796 if (test_bits(action.flags, ACTION_DISCRETE_ON_OFF))
2797 *action.discrete = false;
2801 if (test_bits(action.flags, ACTION_SLEEP))
2802 sleep_start = globals->get_sim_time_sec();
2803 if (test_bits(action.flags, ACTION_DONE))
2812 ///////////////////////////////////////////////////////////////////////////////
2813 // AlertHandler ///////////////////////////////////////////////////////////////
2814 ///////////////////////////////////////////////////////////////////////////////
2817 MK_VIII::AlertHandler::select_voice_alerts (unsigned int test)
2819 if (has_alerts(test))
2821 voice_alerts = alerts & test;
2826 voice_alerts &= ~test;
2827 if (voice_alerts == 0)
2828 mk->voice_player.stop();
2835 MK_VIII::AlertHandler::boot ()
2841 MK_VIII::AlertHandler::reposition ()
2845 mk->io_handler.set_lamp(IOHandler::LAMP_NONE);
2846 mk->voice_player.stop(VoicePlayer::STOP_NOW);
2850 MK_VIII::AlertHandler::reset ()
2855 repeated_alerts = 0;
2856 altitude_callout_voice = NULL;
2860 MK_VIII::AlertHandler::update ()
2862 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
2865 // Lamps and voices are prioritized according to [SPEC] 6.9.2.
2867 // Voices which can interrupt other voices (VoicePlayer::PLAY_NOW)
2868 // are specified by [INSTALL] appendix E 5.3.5.
2870 // When a voice is overriden by a higher priority voice and the
2871 // overriding voice stops, we restore the previous voice if it was
2872 // looped (this is not specified by [SPEC] but seems to make sense).
2876 if (has_alerts(ALERT_MODE1_PULL_UP | ALERT_MODE2A | ALERT_MODE2B))
2877 mk->io_handler.set_lamp(IOHandler::LAMP_WARNING);
2878 else if (has_alerts(ALERT_MODE1_SINK_RATE
2879 | ALERT_MODE2A_PREFACE
2880 | ALERT_MODE2B_PREFACE
2881 | ALERT_MODE2A_ALTITUDE_GAIN_TERRAIN_CLOSING
2882 | ALERT_MODE2A_ALTITUDE_GAIN
2883 | ALERT_MODE2B_LANDING_MODE
2885 | ALERT_MODE4_TOO_LOW_FLAPS
2886 | ALERT_MODE4_TOO_LOW_GEAR
2887 | ALERT_MODE4AB_TOO_LOW_TERRAIN
2888 | ALERT_MODE4C_TOO_LOW_TERRAIN
2889 | ALERT_TCF_TOO_LOW_TERRAIN))
2890 mk->io_handler.set_lamp(IOHandler::LAMP_CAUTION);
2891 else if (has_alerts(ALERT_MODE5_SOFT | ALERT_MODE5_HARD))
2892 mk->io_handler.set_lamp(IOHandler::LAMP_GLIDESLOPE);
2894 mk->io_handler.set_lamp(IOHandler::LAMP_NONE);
2898 if (select_voice_alerts(ALERT_MODE1_PULL_UP))
2900 if (! has_old_alerts(ALERT_MODE1_PULL_UP))
2902 if (mk->voice_player.voice != mk_voice(sink_rate_pause_sink_rate))
2903 mk->voice_player.play(mk_voice(sink_rate), VoicePlayer::PLAY_NOW);
2904 mk->voice_player.play(mk_voice(pull_up), VoicePlayer::PLAY_LOOPED);
2907 else if (select_voice_alerts(ALERT_MODE2A_PREFACE | ALERT_MODE2B_PREFACE))
2909 if (! has_old_alerts(ALERT_MODE2A_PREFACE | ALERT_MODE2B_PREFACE))
2910 mk->voice_player.play(mk_voice(terrain_pause_terrain), VoicePlayer::PLAY_NOW);
2912 else if (select_voice_alerts(ALERT_MODE2A | ALERT_MODE2B))
2914 if (mk->voice_player.voice != mk_voice(pull_up))
2915 mk->voice_player.play(mk_voice(pull_up), VoicePlayer::PLAY_NOW | VoicePlayer::PLAY_LOOPED);
2917 else if (select_voice_alerts(ALERT_MODE2A_ALTITUDE_GAIN_TERRAIN_CLOSING | ALERT_MODE2B_LANDING_MODE))
2919 if (mk->voice_player.voice != mk_voice(terrain))
2920 mk->voice_player.play(mk_voice(terrain), VoicePlayer::PLAY_LOOPED);
2922 else if (select_voice_alerts(ALERT_MODE6_MINIMUMS))
2924 if (! has_old_alerts(ALERT_MODE6_MINIMUMS))
2925 mk->voice_player.play(mk_voice(minimums_minimums));
2927 else if (select_voice_alerts(ALERT_MODE4AB_TOO_LOW_TERRAIN | ALERT_MODE4C_TOO_LOW_TERRAIN))
2929 if (must_play_voice(ALERT_MODE4AB_TOO_LOW_TERRAIN | ALERT_MODE4C_TOO_LOW_TERRAIN))
2930 mk->voice_player.play(mk_voice(too_low_terrain));
2932 else if (select_voice_alerts(ALERT_TCF_TOO_LOW_TERRAIN))
2934 if (must_play_voice(ALERT_TCF_TOO_LOW_TERRAIN))
2935 mk->voice_player.play(mk_voice(too_low_terrain));
2937 else if (select_voice_alerts(ALERT_MODE6_ALTITUDE_CALLOUT))
2939 if (! has_old_alerts(ALERT_MODE6_ALTITUDE_CALLOUT))
2941 assert(altitude_callout_voice != NULL);
2942 mk->voice_player.play(altitude_callout_voice);
2943 altitude_callout_voice = NULL;
2946 else if (select_voice_alerts(ALERT_MODE4_TOO_LOW_GEAR))
2948 if (must_play_voice(ALERT_MODE4_TOO_LOW_GEAR))
2949 mk->voice_player.play(mk->mode4_handler.conf.voice_too_low_gear);
2951 else if (select_voice_alerts(ALERT_MODE4_TOO_LOW_FLAPS))
2953 if (must_play_voice(ALERT_MODE4_TOO_LOW_FLAPS))
2954 mk->voice_player.play(mk_voice(too_low_flaps));
2956 else if (select_voice_alerts(ALERT_MODE1_SINK_RATE))
2958 if (must_play_voice(ALERT_MODE1_SINK_RATE))
2959 mk->voice_player.play(mk_voice(sink_rate_pause_sink_rate));
2960 // [SPEC] 6.2.1: "During the time that the voice message for the
2961 // outer envelope is inhibited and the caution/warning lamp is
2962 // on, the Mode 5 alert message is allowed to annunciate for
2963 // excessive glideslope deviation conditions."
2964 else if (mk->voice_player.voice != mk_voice(sink_rate_pause_sink_rate)
2965 && mk->voice_player.next_voice != mk_voice(sink_rate_pause_sink_rate))
2967 if (has_alerts(ALERT_MODE5_HARD))
2969 voice_alerts |= ALERT_MODE5_HARD;
2970 if (mk->voice_player.voice != mk_voice(hard_glideslope))
2971 mk->voice_player.play(mk_voice(hard_glideslope), VoicePlayer::PLAY_LOOPED);
2973 else if (has_alerts(ALERT_MODE5_SOFT))
2975 voice_alerts |= ALERT_MODE5_SOFT;
2976 if (must_play_voice(ALERT_MODE5_SOFT))
2977 mk->voice_player.play(mk_voice(soft_glideslope));
2981 else if (select_voice_alerts(ALERT_MODE3))
2983 if (must_play_voice(ALERT_MODE3))
2984 mk->voice_player.play(mk_voice(dont_sink_pause_dont_sink));
2986 else if (select_voice_alerts(ALERT_MODE5_HARD))
2988 if (mk->voice_player.voice != mk_voice(hard_glideslope))
2989 mk->voice_player.play(mk_voice(hard_glideslope), VoicePlayer::PLAY_LOOPED);
2991 else if (select_voice_alerts(ALERT_MODE5_SOFT))
2993 if (must_play_voice(ALERT_MODE5_SOFT))
2994 mk->voice_player.play(mk_voice(soft_glideslope));
2996 else if (select_voice_alerts(ALERT_MODE6_LOW_BANK_ANGLE_3))
2998 if (mk->voice_player.voice != mk_voice(bank_angle_bank_angle_3))
2999 mk->voice_player.play(mk_voice(bank_angle_bank_angle_3), VoicePlayer::PLAY_LOOPED);
3001 else if (select_voice_alerts(ALERT_MODE6_HIGH_BANK_ANGLE_3))
3003 if (mk->voice_player.voice != mk_voice(bank_angle_pause_bank_angle_3))
3004 mk->voice_player.play(mk_voice(bank_angle_pause_bank_angle_3), VoicePlayer::PLAY_LOOPED);
3006 else if (select_voice_alerts(ALERT_MODE6_LOW_BANK_ANGLE_2))
3008 if (! has_old_alerts(ALERT_MODE6_LOW_BANK_ANGLE_2 | ALERT_MODE6_HIGH_BANK_ANGLE_2))
3009 mk->voice_player.play(mk_voice(bank_angle_bank_angle));
3011 else if (select_voice_alerts(ALERT_MODE6_HIGH_BANK_ANGLE_2))
3013 if (! has_old_alerts(ALERT_MODE6_LOW_BANK_ANGLE_2 | ALERT_MODE6_HIGH_BANK_ANGLE_2))
3014 mk->voice_player.play(mk_voice(bank_angle_pause_bank_angle));
3016 else if (select_voice_alerts(ALERT_MODE6_LOW_BANK_ANGLE_1))
3018 if (! has_old_alerts(ALERT_MODE6_LOW_BANK_ANGLE_1 | ALERT_MODE6_HIGH_BANK_ANGLE_1))
3019 mk->voice_player.play(mk_voice(bank_angle_bank_angle));
3021 else if (select_voice_alerts(ALERT_MODE6_HIGH_BANK_ANGLE_1))
3023 if (! has_old_alerts(ALERT_MODE6_LOW_BANK_ANGLE_1 | ALERT_MODE6_HIGH_BANK_ANGLE_1))
3024 mk->voice_player.play(mk_voice(bank_angle_pause_bank_angle));
3027 // remember all alerts voiced so far...
3028 old_alerts |= voice_alerts;
3029 // ... forget those no longer active
3030 old_alerts &= alerts;
3031 repeated_alerts = 0;
3035 MK_VIII::AlertHandler::set_alerts (unsigned int _alerts,
3037 VoicePlayer::Voice *_altitude_callout_voice)
3040 if (test_bits(flags, ALERT_FLAG_REPEAT))
3041 repeated_alerts |= _alerts;
3042 if (_altitude_callout_voice)
3043 altitude_callout_voice = _altitude_callout_voice;
3047 MK_VIII::AlertHandler::unset_alerts (unsigned int _alerts)
3050 repeated_alerts &= ~_alerts;
3051 if (_alerts & ALERT_MODE6_ALTITUDE_CALLOUT)
3052 altitude_callout_voice = NULL;
3055 ///////////////////////////////////////////////////////////////////////////////
3056 // StateHandler ///////////////////////////////////////////////////////////////
3057 ///////////////////////////////////////////////////////////////////////////////
3060 MK_VIII::StateHandler::update_ground ()
3064 if (! mk_ainput(computed_airspeed).ncd && mk_ainput(computed_airspeed).get() > 60
3065 && ! mk_data(radio_altitude).ncd && mk_data(radio_altitude).get() > 30)
3067 if (potentially_airborne_timer.start_or_elapsed() > 1)
3071 potentially_airborne_timer.stop();
3075 if (! mk_ainput(computed_airspeed).ncd && mk_ainput(computed_airspeed).get() < 40
3076 && ! mk_data(radio_altitude).ncd && mk_data(radio_altitude).get() < 30)
3082 MK_VIII::StateHandler::enter_ground ()
3085 mk->io_handler.enter_ground();
3087 // [SPEC] 6.0.1 does not specify this, but it seems to be an
3088 // omission; at this point, we must make sure that we are in takeoff
3089 // mode (otherwise the next takeoff might happen in approach mode).
3095 MK_VIII::StateHandler::leave_ground ()
3098 mk->mode2_handler.leave_ground();
3102 MK_VIII::StateHandler::update_takeoff ()
3106 // [SPEC] 6.0.2 is somewhat confusing: it mentions hardcoded
3107 // terrain clearance (500, 750) and airspeed (178, 200) values,
3108 // but it also mentions the mode 4A boundary, which varies as a
3109 // function of aircraft type. We consider that the mentioned
3110 // values are examples, and that we should use the mode 4A upper
3113 if (! mk_data(terrain_clearance).ncd
3114 && ! mk_ainput(computed_airspeed).ncd
3115 && mk_data(terrain_clearance).get() > mk->mode4_handler.get_upper_agl(mk->mode4_handler.conf.modes->ac))
3120 if (! mk_data(radio_altitude).ncd
3121 && mk_data(radio_altitude).get() < mk->mode4_handler.conf.modes->b->min_agl1
3122 && mk->io_handler.flaps_down()
3123 && mk_dinput(landing_gear))
3129 MK_VIII::StateHandler::enter_takeoff ()
3132 mk->io_handler.enter_takeoff();
3133 mk->mode2_handler.enter_takeoff();
3134 mk->mode3_handler.enter_takeoff();
3135 mk->mode6_handler.enter_takeoff();
3139 MK_VIII::StateHandler::leave_takeoff ()
3142 mk->mode6_handler.leave_takeoff();
3146 MK_VIII::StateHandler::post_reposition ()
3148 // Synchronize the state with the current situation.
3151 (! mk_ainput(computed_airspeed).ncd && mk_ainput(computed_airspeed).get() > 60
3152 && ! mk_data(radio_altitude).ncd && mk_data(radio_altitude).get() > 30);
3154 bool _takeoff = _ground;
3156 if (ground && ! _ground)
3158 else if (! ground && _ground)
3161 if (takeoff && ! _takeoff)
3163 else if (! takeoff && _takeoff)
3168 MK_VIII::StateHandler::update ()
3170 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
3177 ///////////////////////////////////////////////////////////////////////////////
3178 // Mode1Handler ///////////////////////////////////////////////////////////////
3179 ///////////////////////////////////////////////////////////////////////////////
3182 MK_VIII::Mode1Handler::get_pull_up_bias ()
3184 // [PILOT] page 54: "Steep Approach has priority over Flap Override
3185 // if selected simultaneously."
3187 if (mk->io_handler.steep_approach())
3189 else if (conf.envelopes->flap_override_bias && mk->io_handler.flap_override())
3196 MK_VIII::Mode1Handler::is_pull_up ()
3198 if (! mk->io_handler.gpws_inhibit()
3199 && ! mk->state_handler.ground // [1]
3200 && ! mk_data(radio_altitude).ncd
3201 && ! mk_data(barometric_altitude_rate).ncd
3202 && mk_data(radio_altitude).get() > conf.envelopes->min_agl)
3204 if (mk_data(radio_altitude).get() < conf.envelopes->pull_up_max_agl1)
3206 if (mk_data(radio_altitude).get() < conf.envelopes->pull_up_min_agl1(mk_data(barometric_altitude_rate).get() + get_pull_up_bias()))
3209 else if (mk_data(radio_altitude).get() < conf.envelopes->pull_up_max_agl2)
3211 if (mk_data(radio_altitude).get() < conf.envelopes->pull_up_min_agl2(mk_data(barometric_altitude_rate).get() + get_pull_up_bias()))
3220 MK_VIII::Mode1Handler::update_pull_up ()
3224 if (! mk_test_alert(MODE1_PULL_UP))
3226 // [SPEC] 6.2.1: at least one sink rate must be issued
3227 // before switching to pull up; if no sink rate has
3228 // occurred, a 0.2 second delay is used.
3229 if (mk->voice_player.voice == mk_voice(sink_rate_pause_sink_rate)
3230 || pull_up_timer.start_or_elapsed() >= 0.2)
3231 mk_set_alerts(mk_alert(MODE1_PULL_UP));
3236 pull_up_timer.stop();
3237 mk_unset_alerts(mk_alert(MODE1_PULL_UP));
3242 MK_VIII::Mode1Handler::get_sink_rate_bias ()
3244 // [PILOT] page 54: "Steep Approach has priority over Flap Override
3245 // if selected simultaneously."
3247 if (mk->io_handler.steep_approach())
3249 else if (conf.envelopes->flap_override_bias && mk->io_handler.flap_override())
3251 else if (! mk_data(glideslope_deviation_dots).ncd)
3255 if (mk_data(glideslope_deviation_dots).get() <= -2)
3257 else if (mk_data(glideslope_deviation_dots).get() < 0)
3258 bias = -150 * mk_data(glideslope_deviation_dots).get();
3260 if (mk_data(radio_altitude).get() < 100)
3261 bias *= 0.01 * mk_data(radio_altitude).get();
3270 MK_VIII::Mode1Handler::is_sink_rate ()
3272 return ! mk->io_handler.gpws_inhibit()
3273 && ! mk->state_handler.ground // [1]
3274 && ! mk_data(radio_altitude).ncd
3275 && ! mk_data(barometric_altitude_rate).ncd
3276 && mk_data(radio_altitude).get() > conf.envelopes->min_agl
3277 && mk_data(radio_altitude).get() < 2450
3278 && mk_data(radio_altitude).get() < -572 - 0.6035 * (mk_data(barometric_altitude_rate).get() + get_sink_rate_bias());
3282 MK_VIII::Mode1Handler::get_sink_rate_tti ()
3284 return mk_data(radio_altitude).get() / fabs(mk_data(barometric_altitude_rate).get());
3288 MK_VIII::Mode1Handler::update_sink_rate ()
3292 if (mk_test_alert(MODE1_SINK_RATE))
3294 double tti = get_sink_rate_tti();
3295 if (tti <= sink_rate_tti * 0.8)
3297 // ~20% degradation, warn again and store the new reference tti
3298 sink_rate_tti = tti;
3299 mk_repeat_alert(mk_alert(MODE1_SINK_RATE));
3304 if (sink_rate_timer.start_or_elapsed() >= 0.8)
3306 mk_set_alerts(mk_alert(MODE1_SINK_RATE));
3307 sink_rate_tti = get_sink_rate_tti();
3313 sink_rate_timer.stop();
3314 mk_unset_alerts(mk_alert(MODE1_SINK_RATE));
3319 MK_VIII::Mode1Handler::update ()
3321 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
3328 ///////////////////////////////////////////////////////////////////////////////
3329 // Mode2Handler ///////////////////////////////////////////////////////////////
3330 ///////////////////////////////////////////////////////////////////////////////
3333 MK_VIII::Mode2Handler::ClosureRateFilter::limit_radio_altitude_rate (double r)
3335 // Limit radio altitude rate according to aircraft configuration,
3336 // allowing maximum sensitivity during cruise while providing
3337 // progressively less sensitivity during the landing phases of
3340 if (! mk_data(glideslope_deviation_dots).ncd && fabs(mk_data(glideslope_deviation_dots).get()) <= 2)
3341 { // gs deviation <= +- 2 dots
3342 if (mk_dinput(landing_gear) && mk->io_handler.flaps_down())
3343 SG_CLAMP_RANGE(r, -1000.0, 3000.0);
3344 else if (mk_dinput(landing_gear) || mk->io_handler.flaps_down())
3345 SG_CLAMP_RANGE(r, 0.0, 4000.0);
3347 SG_CLAMP_RANGE(r, 1000.0, 5000.0);
3350 { // no ILS, or gs deviation > +- 2 dots
3351 if (mk_dinput(landing_gear) && mk->io_handler.flaps_down())
3352 SG_CLAMP_RANGE(r, 0.0, 4000.0);
3353 else if (mk_dinput(landing_gear) || mk->io_handler.flaps_down())
3354 SG_CLAMP_RANGE(r, 1000.0, 5000.0);
3362 MK_VIII::Mode2Handler::ClosureRateFilter::init ()
3365 last_ra.set(&mk_data(radio_altitude));
3366 last_ba.set(&mk_ainput(uncorrected_barometric_altitude));
3373 MK_VIII::Mode2Handler::ClosureRateFilter::update ()
3375 double elapsed = timer.start_or_elapsed();
3379 if (! mk_data(radio_altitude).ncd && ! mk_ainput(uncorrected_barometric_altitude).ncd)
3381 if (! last_ra.ncd && ! last_ba.ncd)
3383 // compute radio and barometric altitude rates (positive value = descent)
3384 double ra_rate = -(mk_data(radio_altitude).get() - last_ra.get()) / elapsed * 60;
3385 double ba_rate = -(mk_ainput(uncorrected_barometric_altitude).get() - last_ba.get()) / elapsed * 60;
3387 // limit radio altitude rate according to aircraft configuration
3388 ra_rate = limit_radio_altitude_rate(ra_rate);
3390 // apply a low-pass filter to the radio altitude rate
3391 ra_rate = ra_filter.filter(ra_rate);
3392 // apply a high-pass filter to the barometric altitude rate
3393 ba_rate = ba_filter.filter(ba_rate);
3395 // combine both rates to obtain a closure rate
3396 output.set(ra_rate + ba_rate);
3409 last_ra.set(&mk_data(radio_altitude));
3410 last_ba.set(&mk_ainput(uncorrected_barometric_altitude));
3414 MK_VIII::Mode2Handler::b_conditions ()
3416 return mk->io_handler.flaps_down()
3417 || (! mk_data(glideslope_deviation_dots).ncd && fabs(mk_data(glideslope_deviation_dots).get()) < 2)
3418 || takeoff_timer.running;
3422 MK_VIII::Mode2Handler::is_a ()
3424 if (! mk->io_handler.gpws_inhibit()
3425 && ! mk->state_handler.ground // [1]
3426 && ! mk_data(radio_altitude).ncd
3427 && ! mk_ainput(computed_airspeed).ncd
3428 && ! closure_rate_filter.output.ncd
3429 && ! b_conditions())
3431 if (mk_data(radio_altitude).get() < 1220)
3433 if (mk_data(radio_altitude).get() < -1579 + 0.7895 * closure_rate_filter.output.get())
3440 if (mk_ainput(computed_airspeed).get() <= conf->airspeed1)
3442 else if (mk_ainput(computed_airspeed).get() >= conf->airspeed2)
3445 upper_limit = 1650 + 8.9 * (mk_ainput(computed_airspeed).get() - conf->airspeed1);
3447 if (mk_data(radio_altitude).get() < upper_limit)
3449 if (mk_data(radio_altitude).get() < 522 + 0.1968 * closure_rate_filter.output.get())
3459 MK_VIII::Mode2Handler::is_b ()
3461 if (! mk->io_handler.gpws_inhibit()
3462 && ! mk->state_handler.ground // [1]
3463 && ! mk_data(radio_altitude).ncd
3464 && ! mk_data(barometric_altitude_rate).ncd
3465 && ! closure_rate_filter.output.ncd
3467 && mk_data(radio_altitude).get() < 789)
3471 if (mk->io_handler.flaps_down())
3473 if (mk_data(barometric_altitude_rate).get() > -400)
3475 else if (mk_data(barometric_altitude_rate).get() < -1000)
3478 lower_limit = -66.777 - 0.667 * mk_data(barometric_altitude_rate).get();
3483 if (mk_data(radio_altitude).get() > lower_limit)
3485 if (mk_data(radio_altitude).get() < -1579 + 0.7895 * closure_rate_filter.output.get())
3494 MK_VIII::Mode2Handler::check_pull_up (unsigned int preface_alert,
3497 if (pull_up_timer.running)
3499 if (pull_up_timer.elapsed() >= 1)
3501 mk_unset_alerts(preface_alert);
3502 mk_set_alerts(alert);
3507 if (! mk->voice_player.voice)
3508 pull_up_timer.start();
3513 MK_VIII::Mode2Handler::update_a ()
3517 if (mk_test_alert(MODE2A_PREFACE))
3518 check_pull_up(mk_alert(MODE2A_PREFACE), mk_alert(MODE2A));
3519 else if (! mk_test_alert(MODE2A))
3521 mk_unset_alerts(mk_alert(MODE2A_ALTITUDE_GAIN) | mk_alert(MODE2A_ALTITUDE_GAIN_TERRAIN_CLOSING));
3522 mk_set_alerts(mk_alert(MODE2A_PREFACE));
3523 a_start_time = globals->get_sim_time_sec();
3524 pull_up_timer.stop();
3529 if (mk_test_alert(MODE2A_ALTITUDE_GAIN))
3531 if (mk->io_handler.gpws_inhibit()
3532 || mk->state_handler.ground // [1]
3533 || a_altitude_gain_timer.elapsed() >= 45
3534 || mk_data(radio_altitude).ncd
3535 || mk_ainput(uncorrected_barometric_altitude).ncd
3536 || mk_ainput(uncorrected_barometric_altitude).get() - a_altitude_gain_alt >= 300
3537 // [PILOT] page 12: "the visual alert will remain on
3538 // until [...] landing flaps or the flap override switch
3540 || mk->io_handler.flaps_down())
3542 // exit altitude gain mode
3543 a_altitude_gain_timer.stop();
3544 mk_unset_alerts(mk_alert(MODE2A_ALTITUDE_GAIN) | mk_alert(MODE2A_ALTITUDE_GAIN_TERRAIN_CLOSING));
3548 // [SPEC] 6.2.2.2: "If the terrain starts to fall away
3549 // during this altitude gain time, the voice will shut
3551 if (closure_rate_filter.output.get() < 0)
3552 mk_unset_alerts(mk_alert(MODE2A_ALTITUDE_GAIN_TERRAIN_CLOSING));
3555 else if (mk_test_alerts(mk_alert(MODE2A_PREFACE) | mk_alert(MODE2A)))
3557 mk_unset_alerts(mk_alert(MODE2A_PREFACE) | mk_alert(MODE2A));
3559 if (! mk->io_handler.gpws_inhibit()
3560 && ! mk->state_handler.ground // [1]
3561 && globals->get_sim_time_sec() - a_start_time > 3
3562 && ! mk->io_handler.flaps_down()
3563 && ! mk_data(radio_altitude).ncd
3564 && ! mk_ainput(uncorrected_barometric_altitude).ncd)
3566 // [SPEC] 6.2.2.2: mode 2A envelope violated for more
3567 // than 3 seconds, enable altitude gain feature
3569 a_altitude_gain_timer.start();
3570 a_altitude_gain_alt = mk_ainput(uncorrected_barometric_altitude).get();
3572 unsigned int alerts = mk_alert(MODE2A_ALTITUDE_GAIN);
3573 if (closure_rate_filter.output.get() > 0)
3574 alerts |= mk_alert(MODE2A_ALTITUDE_GAIN_TERRAIN_CLOSING);
3576 mk_set_alerts(alerts);
3583 MK_VIII::Mode2Handler::update_b ()
3587 // handle normal mode
3589 if (b && (! mk_dinput(landing_gear) || ! mk->io_handler.flaps_down()))
3591 if (mk_test_alert(MODE2B_PREFACE))
3592 check_pull_up(mk_alert(MODE2B_PREFACE), mk_alert(MODE2B));
3593 else if (! mk_test_alert(MODE2B))
3595 mk_set_alerts(mk_alert(MODE2B_PREFACE));
3596 pull_up_timer.stop();
3600 mk_unset_alerts(mk_alert(MODE2B_PREFACE) | mk_alert(MODE2B));
3602 // handle landing mode ([SPEC] 6.2.2.3: "If both landing gear and
3603 // flaps are in the landing configuration, then the message will be
3606 if (b && mk_dinput(landing_gear) && mk->io_handler.flaps_down())
3607 mk_set_alerts(mk_alert(MODE2B_LANDING_MODE));
3609 mk_unset_alerts(mk_alert(MODE2B_LANDING_MODE));
3613 MK_VIII::Mode2Handler::boot ()
3615 closure_rate_filter.init();
3619 MK_VIII::Mode2Handler::power_off ()
3621 // [SPEC] 6.0.4: "This latching function is not power saved and a
3622 // system reset will force it false."
3623 takeoff_timer.stop();
3627 MK_VIII::Mode2Handler::leave_ground ()
3629 // takeoff, reset timer
3630 takeoff_timer.start();
3634 MK_VIII::Mode2Handler::enter_takeoff ()
3636 // reset timer, in case it's a go-around
3637 takeoff_timer.start();
3641 MK_VIII::Mode2Handler::update ()
3643 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
3646 closure_rate_filter.update();
3648 if (takeoff_timer.running && takeoff_timer.elapsed() >= 60)
3649 takeoff_timer.stop();
3655 ///////////////////////////////////////////////////////////////////////////////
3656 // Mode3Handler ///////////////////////////////////////////////////////////////
3657 ///////////////////////////////////////////////////////////////////////////////
3660 MK_VIII::Mode3Handler::max_alt_loss (double _bias)
3662 return conf->max_alt_loss(mk->io_handler.flap_override(), mk_data(radio_altitude).get()) + mk_data(radio_altitude).get() * _bias;
3666 MK_VIII::Mode3Handler::get_bias (double initial_bias, double alt_loss)
3668 // do not repeat altitude-loss alerts below 30ft agl
3669 if (mk_data(radio_altitude).get() > 30)
3671 if (initial_bias < 0.0) // sanity check
3673 // mk-viii spec: repeat alerts whenever losing 20% of initial altitude
3674 while ((alt_loss > max_alt_loss(initial_bias))&&
3675 (initial_bias < 1.0))
3676 initial_bias += 0.2;
3679 return initial_bias;
3683 MK_VIII::Mode3Handler::is (double *alt_loss)
3685 if (has_descent_alt)
3687 int max_agl = conf->max_agl(mk->io_handler.flap_override());
3689 if (mk_ainput(uncorrected_barometric_altitude).ncd
3690 || mk_ainput(uncorrected_barometric_altitude).get() > descent_alt
3691 || mk_data(radio_altitude).ncd
3692 || mk_data(radio_altitude).get() > max_agl)
3695 has_descent_alt = false;
3699 // gear/flaps: [SPEC] 1.3.1.3
3700 if (! mk->io_handler.gpws_inhibit()
3701 && ! mk->state_handler.ground // [1]
3702 && (! mk_dinput(landing_gear) || ! mk->io_handler.flaps_down())
3703 && ! mk_data(barometric_altitude_rate).ncd
3704 && ! mk_ainput(uncorrected_barometric_altitude).ncd
3705 && ! mk_data(radio_altitude).ncd
3706 && mk_data(barometric_altitude_rate).get() < 0)
3708 double _alt_loss = descent_alt - mk_ainput(uncorrected_barometric_altitude).get();
3709 if (armed || (mk_data(radio_altitude).get() > conf->min_agl
3710 && mk_data(radio_altitude).get() < max_agl
3711 && _alt_loss > max_alt_loss(0)))
3713 *alt_loss = _alt_loss;
3721 if (! mk_data(barometric_altitude_rate).ncd
3722 && ! mk_ainput(uncorrected_barometric_altitude).ncd
3723 && mk_data(barometric_altitude_rate).get() < 0)
3725 has_descent_alt = true;
3726 descent_alt = mk_ainput(uncorrected_barometric_altitude).get();
3734 MK_VIII::Mode3Handler::enter_takeoff ()
3737 has_descent_alt = false;
3741 MK_VIII::Mode3Handler::update ()
3743 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
3746 if (mk->state_handler.takeoff)
3750 if (! mk->state_handler.ground /* [1] */ && is(&alt_loss))
3752 if (mk_test_alert(MODE3))
3754 double new_bias = get_bias(bias, alt_loss);
3755 if (new_bias > bias)
3758 mk_repeat_alert(mk_alert(MODE3));
3764 bias = get_bias(0.2, alt_loss);
3765 mk_set_alerts(mk_alert(MODE3));
3772 mk_unset_alerts(mk_alert(MODE3));
3775 ///////////////////////////////////////////////////////////////////////////////
3776 // Mode4Handler ///////////////////////////////////////////////////////////////
3777 ///////////////////////////////////////////////////////////////////////////////
3779 // FIXME: for turbofans, the upper limit should be reduced from 1000
3780 // to 800 ft if a sudden change in radio altitude is detected, in
3781 // order to reduce nuisance alerts caused by overflying another
3782 // aircraft (see [PILOT] page 16).
3785 MK_VIII::Mode4Handler::get_upper_agl (const EnvelopesConfiguration *c)
3787 if (mk_ainput(computed_airspeed).get() >= c->airspeed2)
3789 else if (mk_ainput(computed_airspeed).get() >= c->airspeed1)
3790 return c->min_agl2(mk_ainput(computed_airspeed).get());
3795 const MK_VIII::Mode4Handler::EnvelopesConfiguration *
3796 MK_VIII::Mode4Handler::get_ab_envelope ()
3798 // [SPEC] 6.2.4.1: "The Mode 4B envelope can also be selected by
3799 // setting flaps to landing configuration or by selecting flap
3801 return mk_dinput(landing_gear) || mk->io_handler.flaps_down()
3807 MK_VIII::Mode4Handler::get_bias (double initial_bias, double min_agl)
3809 // do not repeat terrain/gear/flap alerts below 30ft agl
3810 if (mk_data(radio_altitude).get() > 30.0)
3812 if (initial_bias < 0.0) // sanity check
3814 while ((mk_data(radio_altitude).get() < min_agl - min_agl * initial_bias)&&
3815 (initial_bias < 1.0))
3816 initial_bias += 0.2;
3819 return initial_bias;
3823 MK_VIII::Mode4Handler::handle_alert (unsigned int alert,
3827 if (mk_test_alerts(alert))
3829 double new_bias = get_bias(*bias, min_agl);
3830 if (new_bias > *bias)
3833 mk_repeat_alert(alert);
3838 *bias = get_bias(0.2, min_agl);
3839 mk_set_alerts(alert);
3844 MK_VIII::Mode4Handler::update_ab ()
3846 if (! mk->io_handler.gpws_inhibit()
3847 && ! mk->state_handler.ground
3848 && ! mk->state_handler.takeoff
3849 && ! mk_data(radio_altitude).ncd
3850 && ! mk_ainput(computed_airspeed).ncd
3851 && mk_data(radio_altitude).get() > 30)
3853 const EnvelopesConfiguration *c = get_ab_envelope();
3854 if (mk_ainput(computed_airspeed).get() < c->airspeed1)
3856 if (mk_dinput(landing_gear))
3858 if (! mk->io_handler.flaps_down() && mk_data(radio_altitude).get() < c->min_agl1)
3860 handle_alert(mk_alert(MODE4_TOO_LOW_FLAPS), c->min_agl1, &ab_bias);
3866 if (mk_data(radio_altitude).get() < c->min_agl1)
3868 handle_alert(mk_alert(MODE4_TOO_LOW_GEAR), c->min_agl1, &ab_bias);
3875 mk_unset_alerts(mk_alert(MODE4_TOO_LOW_FLAPS) | mk_alert(MODE4_TOO_LOW_GEAR));
3880 MK_VIII::Mode4Handler::update_ab_expanded ()
3882 if (! mk->io_handler.gpws_inhibit()
3883 && ! mk->state_handler.ground
3884 && ! mk->state_handler.takeoff
3885 && ! mk_data(radio_altitude).ncd
3886 && ! mk_ainput(computed_airspeed).ncd
3887 && mk_data(radio_altitude).get() > 30)
3889 const EnvelopesConfiguration *c = get_ab_envelope();
3890 if (mk_ainput(computed_airspeed).get() >= c->airspeed1)
3892 double min_agl = get_upper_agl(c);
3893 if (mk_data(radio_altitude).get() < min_agl)
3895 handle_alert(mk_alert(MODE4AB_TOO_LOW_TERRAIN), min_agl, &ab_expanded_bias);
3901 mk_unset_alerts(mk_alert(MODE4AB_TOO_LOW_TERRAIN));
3902 ab_expanded_bias=0.0;
3906 MK_VIII::Mode4Handler::update_c ()
3908 if (! mk->io_handler.gpws_inhibit()
3909 && ! mk->state_handler.ground // [1]
3910 && mk->state_handler.takeoff
3911 && ! mk_data(radio_altitude).ncd
3912 && ! mk_data(terrain_clearance).ncd
3913 && mk_data(radio_altitude).get() > 30
3914 && (! mk_dinput(landing_gear) || ! mk->io_handler.flaps_down())
3915 && mk_data(radio_altitude).get() < get_upper_agl(conf.modes->ac)
3916 && mk_data(radio_altitude).get() < mk_data(terrain_clearance).get())
3917 handle_alert(mk_alert(MODE4C_TOO_LOW_TERRAIN), mk_data(terrain_clearance).get(), &c_bias);
3920 mk_unset_alerts(mk_alert(MODE4C_TOO_LOW_TERRAIN));
3926 MK_VIII::Mode4Handler::update ()
3928 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
3932 update_ab_expanded();
3936 ///////////////////////////////////////////////////////////////////////////////
3937 // Mode5Handler ///////////////////////////////////////////////////////////////
3938 ///////////////////////////////////////////////////////////////////////////////
3941 MK_VIII::Mode5Handler::is_hard ()
3943 if (mk_data(radio_altitude).get() > 30
3944 && mk_data(radio_altitude).get() < 300
3945 && mk_data(glideslope_deviation_dots).get() > 2)
3947 if (mk_data(radio_altitude).get() < 150)
3949 if (mk_data(radio_altitude).get() > 293 - 71.43 * mk_data(glideslope_deviation_dots).get())
3960 MK_VIII::Mode5Handler::is_soft (double bias)
3962 // do not repeat glide-slope alerts below 30ft agl
3963 if (mk_data(radio_altitude).get() > 30)
3965 double bias_dots = 1.3 * bias;
3966 if (mk_data(glideslope_deviation_dots).get() > 1.3 + bias_dots)
3968 if (mk_data(radio_altitude).get() < 150)
3970 if (mk_data(radio_altitude).get() > 243 - 71.43 * (mk_data(glideslope_deviation_dots).get() - bias_dots))
3977 if (mk_data(barometric_altitude_rate).ncd)
3981 if (mk_data(barometric_altitude_rate).get() >= 0)
3983 else if (mk_data(barometric_altitude_rate).get() < -500)
3986 upper_limit = -mk_data(barometric_altitude_rate).get() + 500;
3989 if (mk_data(radio_altitude).get() < upper_limit)
3999 MK_VIII::Mode5Handler::get_soft_bias (double initial_bias)
4001 if (initial_bias < 0.0) // sanity check
4003 while ((is_soft(initial_bias))&&
4004 (initial_bias < 1.0))
4005 initial_bias += 0.2;
4007 return initial_bias;
4011 MK_VIII::Mode5Handler::update_hard (bool is)
4015 if (! mk_test_alert(MODE5_HARD))
4017 if (hard_timer.start_or_elapsed() >= 0.8)
4018 mk_set_alerts(mk_alert(MODE5_HARD));
4024 mk_unset_alerts(mk_alert(MODE5_HARD));
4029 MK_VIII::Mode5Handler::update_soft (bool is)
4033 if (! mk_test_alert(MODE5_SOFT))
4035 double new_bias = get_soft_bias(soft_bias);
4036 if (new_bias > soft_bias)
4038 soft_bias = new_bias;
4039 mk_repeat_alert(mk_alert(MODE5_SOFT));
4044 if (soft_timer.start_or_elapsed() >= 0.8)
4046 soft_bias = get_soft_bias(0.2);
4047 mk_set_alerts(mk_alert(MODE5_SOFT));
4054 mk_unset_alerts(mk_alert(MODE5_SOFT));
4059 MK_VIII::Mode5Handler::update ()
4061 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
4064 if (! mk->io_handler.gpws_inhibit()
4065 && ! mk->state_handler.ground // [1]
4066 && ! mk_dinput(glideslope_inhibit) // not on backcourse
4067 && ! mk_data(radio_altitude).ncd
4068 && ! mk_data(glideslope_deviation_dots).ncd
4069 && (! mk->io_handler.conf.localizer_enabled
4070 || mk_data(localizer_deviation_dots).ncd
4071 || mk_data(radio_altitude).get() < 500
4072 || fabs(mk_data(localizer_deviation_dots).get()) < 2)
4073 && (! mk->state_handler.takeoff || mk->io_handler.flaps_down())
4074 && mk_dinput(landing_gear)
4075 && ! mk_doutput(glideslope_cancel))
4077 update_hard(is_hard());
4078 update_soft(is_soft(0));
4087 ///////////////////////////////////////////////////////////////////////////////
4088 // Mode6Handler ///////////////////////////////////////////////////////////////
4089 ///////////////////////////////////////////////////////////////////////////////
4091 // keep sorted in descending order
4092 const int MK_VIII::Mode6Handler::altitude_callout_definitions[] =
4093 { 1000, 500, 400, 300, 200, 100, 50, 40, 30, 20, 10 };
4096 MK_VIII::Mode6Handler::reset_minimums ()
4098 minimums_issued = false;
4102 MK_VIII::Mode6Handler::reset_altitude_callouts ()
4104 for (unsigned i = 0; i < n_altitude_callouts; i++)
4105 altitude_callouts_issued[i] = false;
4109 MK_VIII::Mode6Handler::is_playing_altitude_callout ()
4111 for (unsigned i = 0; i < n_altitude_callouts; i++)
4112 if (mk->voice_player.voice == mk_altitude_voice(i)
4113 || mk->voice_player.next_voice == mk_altitude_voice(i))
4120 MK_VIII::Mode6Handler::is_near_minimums (double callout)
4124 if (! mk_data(decision_height).ncd)
4126 double diff = callout - mk_data(decision_height).get();
4128 if (mk_data(radio_altitude).get() >= 200)
4130 if (fabs(diff) <= 30)
4135 if (diff >= -3 && diff <= 6)
4144 MK_VIII::Mode6Handler::is_outside_band (double elevation, double callout)
4147 return elevation < callout - (elevation > 150 ? 20 : 10);
4151 MK_VIII::Mode6Handler::inhibit_smart_500 ()
4155 if (! mk_data(glideslope_deviation_dots).ncd
4156 && ! mk_dinput(glideslope_inhibit) // backcourse
4157 && ! mk_doutput(glideslope_cancel))
4159 if (mk->io_handler.conf.localizer_enabled
4160 && ! mk_data(localizer_deviation_dots).ncd)
4162 if (fabs(mk_data(localizer_deviation_dots).get()) <= 2)
4167 if (fabs(mk_data(glideslope_deviation_dots).get()) <= 2)
4176 MK_VIII::Mode6Handler::boot ()
4178 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
4181 last_decision_height = mk_dinput(decision_height);
4182 last_radio_altitude.set(&mk_data(radio_altitude));
4185 for (unsigned i = 0; i < n_altitude_callouts; i++)
4186 altitude_callouts_issued[i] = ! mk_data(radio_altitude).ncd
4187 && mk_data(radio_altitude).get() <= altitude_callout_definitions[i];
4189 // extrapolated from [SPEC] 6.4.2
4190 minimums_issued = mk_dinput(decision_height);
4192 if (conf.above_field_voice)
4195 get_altitude_above_field(&last_altitude_above_field);
4196 // extrapolated from [SPEC] 6.4.2
4197 above_field_issued = ! last_altitude_above_field.ncd
4198 && last_altitude_above_field.get() < 550;
4203 MK_VIII::Mode6Handler::power_off ()
4205 runway_timer.stop();
4209 MK_VIII::Mode6Handler::enter_takeoff ()
4211 reset_altitude_callouts(); // [SPEC] 6.4.2
4212 reset_minimums(); // omitted by [SPEC]; common sense
4216 MK_VIII::Mode6Handler::leave_takeoff ()
4218 reset_altitude_callouts(); // [SPEC] 6.0.2
4219 reset_minimums(); // [SPEC] 6.0.2
4223 MK_VIII::Mode6Handler::set_volume (float volume)
4225 mk_voice(minimums_minimums)->set_volume(volume);
4226 mk_voice(five_hundred_above)->set_volume(volume);
4227 for (unsigned i = 0; i < n_altitude_callouts; i++)
4228 mk_altitude_voice(i)->set_volume(volume);
4232 MK_VIII::Mode6Handler::altitude_callouts_enabled ()
4234 if (conf.minimums_enabled || conf.smart_500_enabled || conf.above_field_voice)
4237 for (unsigned i = 0; i < n_altitude_callouts; i++)
4238 if (conf.altitude_callouts_enabled[i])
4245 MK_VIII::Mode6Handler::update_minimums ()
4247 if (! mk->io_handler.gpws_inhibit()
4248 && (mk->voice_player.voice == mk_voice(minimums_minimums)
4249 || mk->voice_player.next_voice == mk_voice(minimums_minimums)))
4252 if (! mk->io_handler.gpws_inhibit()
4253 && ! mk->state_handler.ground // [1]
4254 && conf.minimums_enabled
4255 && ! minimums_issued
4256 && mk_dinput(landing_gear)
4257 && mk_dinput(decision_height)
4258 && ! last_decision_height)
4260 minimums_issued = true;
4262 // If an altitude callout is playing, stop it now so that the
4263 // minimums callout can be played (note that proper connection
4264 // and synchronization of the radio-altitude ARINC 529 input,
4265 // decision-height discrete and decision-height ARINC 529 input
4266 // will prevent an altitude callout from being played near the
4267 // decision height).
4269 if (is_playing_altitude_callout())
4270 mk->voice_player.stop(VoicePlayer::STOP_NOW);
4272 mk_set_alerts(mk_alert(MODE6_MINIMUMS));
4275 mk_unset_alerts(mk_alert(MODE6_MINIMUMS));
4278 last_decision_height = mk_dinput(decision_height);
4282 MK_VIII::Mode6Handler::update_altitude_callouts ()
4284 if (! mk->io_handler.gpws_inhibit() && is_playing_altitude_callout())
4287 if (! mk->io_handler.gpws_inhibit()
4288 && ! mk->state_handler.ground // [1]
4289 && ! mk_data(radio_altitude).ncd)
4290 for (unsigned i = 0; i < n_altitude_callouts && mk_data(radio_altitude).get() <= altitude_callout_definitions[i]; i++)
4291 if ((conf.altitude_callouts_enabled[i]
4292 || (altitude_callout_definitions[i] == 500
4293 && conf.smart_500_enabled))
4294 && ! altitude_callouts_issued[i]
4295 && (last_radio_altitude.ncd
4296 || last_radio_altitude.get() > altitude_callout_definitions[i]))
4298 // lock out all callouts superior or equal to this one
4299 for (unsigned j = 0; j <= i; j++)
4300 altitude_callouts_issued[j] = true;
4302 altitude_callouts_issued[i] = true;
4303 if (! is_near_minimums(altitude_callout_definitions[i])
4304 && ! is_outside_band(mk_data(radio_altitude).get(), altitude_callout_definitions[i])
4305 && (! conf.smart_500_enabled
4306 || altitude_callout_definitions[i] != 500
4307 || ! inhibit_smart_500()))
4309 mk->alert_handler.set_altitude_callout_alert(mk_altitude_voice(i));
4314 mk_unset_alerts(mk_alert(MODE6_ALTITUDE_CALLOUT));
4317 last_radio_altitude.set(&mk_data(radio_altitude));
4321 MK_VIII::Mode6Handler::test_runway (const FGRunway *_runway)
4323 if (_runway->lengthFt() < mk->conf.runway_database)
4327 SGGeod::fromDeg(mk_data(gps_longitude).get(), mk_data(gps_latitude).get()));
4329 // get distance to threshold
4330 double distance, az1, az2;
4331 SGGeodesy::inverse(pos, _runway->threshold(), az1, az2, distance);
4332 return distance * SG_METER_TO_NM <= 5;
4336 MK_VIII::Mode6Handler::test_airport (const FGAirport *airport)
4338 for (unsigned int r=0; r<airport->numRunways(); ++r) {
4339 FGRunway* rwy(airport->getRunwayByIndex(r));
4341 if (test_runway(rwy)) return true;
4347 bool MK_VIII::Mode6Handler::AirportFilter::passAirport(FGAirport* a) const
4349 bool ok = self->test_airport(a);
4354 MK_VIII::Mode6Handler::update_runway ()
4357 if (mk_data(gps_latitude).ncd || mk_data(gps_longitude).ncd) {
4362 // Search for the closest runway threshold in range 5
4363 // nm. Passing 30nm to
4364 // get_closest_airport() provides enough margin for large
4365 // airports, which may have a runway located far away from the
4366 // airport's reference point.
4367 AirportFilter filter(this);
4368 FGPositionedRef apt = FGPositioned::findClosest(
4369 SGGeod::fromDeg(mk_data(gps_longitude).get(), mk_data(gps_latitude).get()),
4372 runway.elevation = apt->elevation();
4375 has_runway.set(apt != NULL);
4379 MK_VIII::Mode6Handler::get_altitude_above_field (Parameter<double> *parameter)
4381 if (! has_runway.ncd && has_runway.get() && ! mk_data(geometric_altitude).ncd)
4382 parameter->set(mk_data(geometric_altitude).get() - runway.elevation);
4388 MK_VIII::Mode6Handler::update_above_field_callout ()
4390 if (! conf.above_field_voice)
4393 // update_runway() has to iterate over the whole runway database
4394 // (which contains thousands of runways), so it would be unwise to
4395 // run it every frame. Run it every 3 seconds. Note that the first
4396 // iteration is run in boot().
4397 if (runway_timer.start_or_elapsed() >= 3)
4400 runway_timer.start();
4403 Parameter<double> altitude_above_field;
4404 get_altitude_above_field(&altitude_above_field);
4406 if (! mk->io_handler.gpws_inhibit()
4407 && (mk->voice_player.voice == conf.above_field_voice
4408 || mk->voice_player.next_voice == conf.above_field_voice))
4411 // handle reset term
4412 if (above_field_issued)
4414 if ((! has_runway.ncd && ! has_runway.get())
4415 || (! altitude_above_field.ncd && altitude_above_field.get() > 700))
4416 above_field_issued = false;
4419 if (! mk->io_handler.gpws_inhibit()
4420 && ! mk->state_handler.ground // [1]
4421 && ! above_field_issued
4422 && ! altitude_above_field.ncd
4423 && altitude_above_field.get() < 550
4424 && (last_altitude_above_field.ncd
4425 || last_altitude_above_field.get() >= 550))
4427 above_field_issued = true;
4429 if (! is_outside_band(altitude_above_field.get(), 500))
4431 mk->alert_handler.set_altitude_callout_alert(conf.above_field_voice);
4436 mk_unset_alerts(mk_alert(MODE6_ALTITUDE_CALLOUT));
4439 last_altitude_above_field.set(&altitude_above_field);
4443 MK_VIII::Mode6Handler::is_bank_angle (double abs_roll_angle, double bias)
4445 return conf.is_bank_angle(&mk_data(radio_altitude),
4446 abs_roll_angle - abs_roll_angle * bias,
4447 mk_dinput(autopilot_engaged));
4451 MK_VIII::Mode6Handler::is_high_bank_angle ()
4453 return mk_data(radio_altitude).ncd || mk_data(radio_altitude).get() >= 210;
4457 MK_VIII::Mode6Handler::get_bank_angle_alerts ()
4459 if (! mk->io_handler.gpws_inhibit()
4460 && ! mk->state_handler.ground // [1]
4461 && ! mk_data(roll_angle).ncd)
4463 double abs_roll_angle = fabs(mk_data(roll_angle).get());
4465 if (is_bank_angle(abs_roll_angle, 0.4))
4466 return is_high_bank_angle()
4467 ? (mk_alert(MODE6_HIGH_BANK_ANGLE_1) | mk_alert(MODE6_HIGH_BANK_ANGLE_2) | mk_alert(MODE6_HIGH_BANK_ANGLE_3))
4468 : (mk_alert(MODE6_LOW_BANK_ANGLE_1) | mk_alert(MODE6_LOW_BANK_ANGLE_2) | mk_alert(MODE6_LOW_BANK_ANGLE_3));
4469 else if (is_bank_angle(abs_roll_angle, 0.2))
4470 return is_high_bank_angle()
4471 ? (mk_alert(MODE6_HIGH_BANK_ANGLE_1) | mk_alert(MODE6_HIGH_BANK_ANGLE_2))
4472 : (mk_alert(MODE6_LOW_BANK_ANGLE_1) | mk_alert(MODE6_LOW_BANK_ANGLE_2));
4473 else if (is_bank_angle(abs_roll_angle, 0))
4474 return is_high_bank_angle()
4475 ? mk_alert(MODE6_HIGH_BANK_ANGLE_1)
4476 : mk_alert(MODE6_LOW_BANK_ANGLE_1);
4483 MK_VIII::Mode6Handler::update_bank_angle ()
4485 if (! conf.bank_angle_enabled)
4488 unsigned int alerts = get_bank_angle_alerts();
4490 // [SPEC] 6.4.4 specifies that the non-continuous alerts
4491 // (MODE6_*_BANK_ANGLE_1 and MODE6_*_BANK_ANGLE_2) are locked out
4492 // until the initial envelope is exited.
4494 if (! test_bits(alerts, mk_alert(MODE6_LOW_BANK_ANGLE_3)))
4495 mk_unset_alerts(mk_alert(MODE6_LOW_BANK_ANGLE_3));
4496 if (! test_bits(alerts, mk_alert(MODE6_HIGH_BANK_ANGLE_3)))
4497 mk_unset_alerts(mk_alert(MODE6_HIGH_BANK_ANGLE_3));
4500 mk_set_alerts(alerts);
4502 mk_unset_alerts(mk_alert(MODE6_LOW_BANK_ANGLE_1)
4503 | mk_alert(MODE6_HIGH_BANK_ANGLE_1)
4504 | mk_alert(MODE6_LOW_BANK_ANGLE_2)
4505 | mk_alert(MODE6_HIGH_BANK_ANGLE_2));
4509 MK_VIII::Mode6Handler::update ()
4511 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
4514 if (! mk->state_handler.takeoff
4515 && ! mk_data(radio_altitude).ncd
4516 && mk_data(radio_altitude).get() > 1000)
4518 reset_altitude_callouts(); // [SPEC] 6.4.2
4519 reset_minimums(); // common sense
4523 update_altitude_callouts();
4524 update_above_field_callout();
4525 update_bank_angle();
4528 ///////////////////////////////////////////////////////////////////////////////
4529 // TCFHandler /////////////////////////////////////////////////////////////////
4530 ///////////////////////////////////////////////////////////////////////////////
4532 // Gets the difference between the azimuth from @from_lat,@from_lon to
4533 // @to_lat,@to_lon, and @to_heading, in degrees.
4535 MK_VIII::TCFHandler::get_azimuth_difference (double from_lat,
4541 double az1, az2, distance;
4542 geo_inverse_wgs_84(0, from_lat, from_lon, to_lat, to_lon, &az1, &az2, &distance);
4543 return get_heading_difference(az1, to_heading);
4546 // Gets the difference between the azimuth from the current GPS
4547 // position to the center of @_runway, and the heading of @_runway, in
4550 MK_VIII::TCFHandler::get_azimuth_difference (const FGRunway *_runway)
4552 return get_azimuth_difference(mk_data(gps_latitude).get(),
4553 mk_data(gps_longitude).get(),
4554 _runway->latitude(),
4555 _runway->longitude(),
4556 _runway->headingDeg());
4559 // Selects the most likely intended destination runway of @airport,
4560 // and returns it in @_runway. For each runway, the difference between
4561 // the azimuth from the current GPS position to the center of the
4562 // runway and its heading is computed. The runway having the smallest
4565 // This selection algorithm is not specified in [SPEC], but
4566 // http://www.egpws.com/general_information/description/runway_select.htm
4567 // talks about automatic runway selection.
4569 MK_VIII::TCFHandler::select_runway (const FGAirport *airport)
4571 FGRunway* _runway = 0;
4572 double min_diff = 360;
4574 for (unsigned int r=0; r<airport->numRunways(); ++r) {
4575 FGRunway* rwy(airport->getRunwayByIndex(r));
4576 double diff = get_azimuth_difference(rwy);
4577 if (diff < min_diff)
4582 } // of airport runways iteration
4586 bool MK_VIII::TCFHandler::AirportFilter::passAirport(FGAirport* aApt) const
4588 return aApt->hasHardRunwayOfLengthFt(mk->conf.runway_database);
4592 MK_VIII::TCFHandler::update_runway ()
4595 if (mk_data(gps_latitude).ncd || mk_data(gps_longitude).ncd) {
4599 // Search for the intended destination runway of the closest
4600 // airport in range 15 nm. Passing 30nm to get_closest_airport()
4601 // provides enough margin for
4602 // large airports, which may have a runway located far away from
4603 // the airport's reference point.
4604 AirportFilter filter(mk);
4605 FGAirport* apt = FGAirport::findClosest(
4606 SGGeod::fromDeg(mk_data(gps_longitude).get(), mk_data(gps_latitude).get()),
4611 FGRunway* _runway = select_runway(apt);
4613 if (!_runway) return;
4617 runway.center.latitude = _runway->latitude();
4618 runway.center.longitude = _runway->longitude();
4620 runway.elevation = apt->elevation();
4622 double half_length_m = _runway->lengthM() * 0.5;
4623 runway.half_length = half_length_m * SG_METER_TO_NM;
4625 // b3 ________________ b0
4627 // h1>>> | e1<<<<<<<<e0 | <<<h0
4628 // |________________|
4631 // get heading to runway threshold (h0) and end (h1)
4632 runway.edges[0].heading = _runway->headingDeg();
4633 runway.edges[1].heading = get_reciprocal_heading(_runway->headingDeg());
4637 // get position of runway threshold (e0)
4638 geo_direct_wgs_84(0,
4639 runway.center.latitude,
4640 runway.center.longitude,
4641 runway.edges[1].heading,
4643 &runway.edges[0].position.latitude,
4644 &runway.edges[0].position.longitude,
4647 // get position of runway end (e1)
4648 geo_direct_wgs_84(0,
4649 runway.center.latitude,
4650 runway.center.longitude,
4651 runway.edges[0].heading,
4653 &runway.edges[1].position.latitude,
4654 &runway.edges[1].position.longitude,
4657 double half_width_m = _runway->widthM() * 0.5;
4659 // get position of threshold bias area edges (b0 and b1)
4660 get_bias_area_edges(&runway.edges[0].position,
4661 runway.edges[1].heading,
4663 &runway.bias_area[0],
4664 &runway.bias_area[1]);
4666 // get position of end bias area edges (b2 and b3)
4667 get_bias_area_edges(&runway.edges[1].position,
4668 runway.edges[0].heading,
4670 &runway.bias_area[2],
4671 &runway.bias_area[3]);
4675 MK_VIII::TCFHandler::get_bias_area_edges (Position *edge,
4677 double half_width_m,
4678 Position *bias_edge1,
4679 Position *bias_edge2)
4681 double half_bias_width_m = k * SG_NM_TO_METER + half_width_m;
4682 double tmp_latitude = 0.0, tmp_longitude = 0.0, az = 0.0;
4684 geo_direct_wgs_84(0,
4692 geo_direct_wgs_84(0,
4695 heading_substract(reciprocal, 90),
4697 &bias_edge1->latitude,
4698 &bias_edge1->longitude,
4700 geo_direct_wgs_84(0,
4703 heading_add(reciprocal, 90),
4705 &bias_edge2->latitude,
4706 &bias_edge2->longitude,
4710 // Returns true if the current GPS position is inside the edge
4711 // triangle of @edge. The edge triangle, which is specified and
4712 // represented in [SPEC] 6.3.1, is defined as an isocele right
4713 // triangle of infinite height, whose right angle is located at the
4714 // position of @edge, and whose height is parallel to the heading of
4717 MK_VIII::TCFHandler::is_inside_edge_triangle (RunwayEdge *edge)
4719 return get_azimuth_difference(mk_data(gps_latitude).get(),
4720 mk_data(gps_longitude).get(),
4721 edge->position.latitude,
4722 edge->position.longitude,
4723 edge->heading) <= 45;
4726 // Returns true if the current GPS position is inside the bias area of
4727 // the currently selected runway.
4729 MK_VIII::TCFHandler::is_inside_bias_area ()
4732 double angles_sum = 0;
4734 for (int i = 0; i < 4; i++)
4736 double az2, distance;
4737 geo_inverse_wgs_84(0,
4738 mk_data(gps_latitude).get(),
4739 mk_data(gps_longitude).get(),
4740 runway.bias_area[i].latitude,
4741 runway.bias_area[i].longitude,
4742 &az1[i], &az2, &distance);
4745 for (int i = 0; i < 4; i++)
4747 double angle = az1[i == 3 ? 0 : i + 1] - az1[i];
4751 angles_sum += angle;
4754 return angles_sum > 180;
4758 MK_VIII::TCFHandler::is_tcf ()
4760 if (mk_data(radio_altitude).get() > 10)
4764 double distance, az1, az2;
4766 geo_inverse_wgs_84(0,
4767 mk_data(gps_latitude).get(),
4768 mk_data(gps_longitude).get(),
4769 runway.center.latitude,
4770 runway.center.longitude,
4771 &az1, &az2, &distance);
4773 distance *= SG_METER_TO_NM;
4775 // distance to the inner envelope edge
4776 double edge_distance = distance - runway.half_length - k;
4778 if (edge_distance >= 15)
4780 if (mk_data(radio_altitude).get() < 700)
4783 else if (edge_distance >= 12)
4785 if (mk_data(radio_altitude).get() < 100 * edge_distance - 800)
4788 else if (edge_distance >= 4)
4790 if (mk_data(radio_altitude).get() < 400)
4793 else if (edge_distance >= 2.45)
4795 if (mk_data(radio_altitude).get() < 66.666667 * edge_distance + 133.33333)
4800 if (is_inside_edge_triangle(&runway.edges[0]) || is_inside_edge_triangle(&runway.edges[1]))
4802 if (edge_distance >= 1)
4804 if (mk_data(radio_altitude).get() < 66.666667 * edge_distance + 133.33333)
4807 else if (edge_distance >= 0.05)
4809 if (mk_data(radio_altitude).get() < 200 * edge_distance)
4815 if (! is_inside_bias_area())
4817 if (mk_data(radio_altitude).get() < 245)
4825 if (mk_data(radio_altitude).get() < 700)
4834 MK_VIII::TCFHandler::is_rfcf ()
4838 double distance, az1, az2;
4839 geo_inverse_wgs_84(0,
4840 mk_data(gps_latitude).get(),
4841 mk_data(gps_longitude).get(),
4842 runway.center.latitude,
4843 runway.center.longitude,
4844 &az1, &az2, &distance);
4846 double krf = k + mk_data(gps_vertical_figure_of_merit).get() / 200;
4847 distance = distance * SG_METER_TO_NM - runway.half_length - krf;
4851 double altitude_above_field = mk_data(geometric_altitude).get() - runway.elevation;
4853 if (distance >= 1.5)
4855 if (altitude_above_field < 300)
4858 else if (distance >= 0)
4860 if (altitude_above_field < 200 * distance)
4870 MK_VIII::TCFHandler::update ()
4872 if (mk->configuration_module.state != ConfigurationModule::STATE_OK || ! conf.enabled)
4875 // update_runway() has to iterate over the whole runway database
4876 // (which contains thousands of runways), so it would be unwise to
4877 // run it every frame. Run it every 3 seconds.
4878 if (! runway_timer.running || runway_timer.elapsed() >= 3)
4881 runway_timer.start();
4884 if (! mk_dinput(ta_tcf_inhibit)
4885 && ! mk->state_handler.ground // [1]
4886 && ! mk_data(gps_latitude).ncd
4887 && ! mk_data(gps_longitude).ncd
4888 && ! mk_data(radio_altitude).ncd
4889 && ! mk_data(geometric_altitude).ncd
4890 && ! mk_data(gps_vertical_figure_of_merit).ncd)
4895 _reference = mk_data(radio_altitude).get_pointer();
4897 _reference = mk_data(geometric_altitude).get_pointer();
4903 if (mk_test_alert(TCF_TOO_LOW_TERRAIN))
4905 double new_bias = bias;
4906 // do not repeat terrain alerts below 30ft agl
4907 if (mk_data(radio_altitude).get() > 30)
4909 if (new_bias < 0.0) // sanity check
4911 while ((*reference < initial_value - initial_value * new_bias)&&
4916 if (new_bias > bias)
4919 mk_repeat_alert(mk_alert(TCF_TOO_LOW_TERRAIN));
4925 reference = _reference;
4926 initial_value = *reference;
4927 mk_set_alerts(mk_alert(TCF_TOO_LOW_TERRAIN));
4934 mk_unset_alerts(mk_alert(TCF_TOO_LOW_TERRAIN));
4937 ///////////////////////////////////////////////////////////////////////////////
4938 // MK_VIII ////////////////////////////////////////////////////////////////////
4939 ///////////////////////////////////////////////////////////////////////////////
4941 MK_VIII::MK_VIII (SGPropertyNode *node)
4942 : properties_handler(this),
4945 power_handler(this),
4946 system_handler(this),
4947 configuration_module(this),
4948 fault_handler(this),
4951 self_test_handler(this),
4952 alert_handler(this),
4953 state_handler(this),
4954 mode1_handler(this),
4955 mode2_handler(this),
4956 mode3_handler(this),
4957 mode4_handler(this),
4958 mode5_handler(this),
4959 mode6_handler(this),
4962 for (int i = 0; i < node->nChildren(); ++i)
4964 SGPropertyNode *child = node->getChild(i);
4965 string cname = child->getName();
4966 string cval = child->getStringValue();
4968 if (cname == "name")
4970 else if (cname == "number")
4971 num = child->getIntValue();
4974 SG_LOG(SG_INSTR, SG_WARN, "Error in mk-viii config logic");
4976 SG_LOG(SG_INSTR, SG_WARN, "Section = " << name);
4984 properties_handler.init();
4985 voice_player.init();
4991 SGPropertyNode *node = fgGetNode(("/instrumentation/" + name).c_str(), num, true);
4993 configuration_module.bind(node);
4994 power_handler.bind(node);
4995 io_handler.bind(node);
4996 voice_player.bind(node, "Sounds/mk-viii/");
5002 properties_handler.unbind();
5006 MK_VIII::update (double dt)
5008 power_handler.update();
5009 system_handler.update();
5011 if (system_handler.state != SystemHandler::STATE_ON)
5014 io_handler.update_inputs();
5015 io_handler.update_input_faults();
5017 voice_player.update();
5018 state_handler.update();
5020 if (self_test_handler.update())
5023 io_handler.update_internal_latches();
5024 io_handler.update_lamps();
5026 mode1_handler.update();
5027 mode2_handler.update();
5028 mode3_handler.update();
5029 mode4_handler.update();
5030 mode5_handler.update();
5031 mode6_handler.update();
5032 tcf_handler.update();
5034 alert_handler.update();
5035 io_handler.update_outputs();