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 // MK_VIII::VoicePlayer ///////////////////////////////////////////////////////
2138 ///////////////////////////////////////////////////////////////////////////////
2141 MK_VIII::VoicePlayer::init ()
2143 FGVoicePlayer::init();
2145 #define STDPAUSE 0.75 // [SPEC] 6.4.4: "the standard 0.75 second delay"
2146 make_voice(&voices.application_data_base_failed, "application-data-base-failed");
2147 make_voice(&voices.bank_angle, "bank-angle");
2148 make_voice(&voices.bank_angle_bank_angle, "bank-angle", "bank-angle");
2149 make_voice(&voices.bank_angle_bank_angle_3, "bank-angle", "bank-angle", 3.0);
2150 make_voice(&voices.bank_angle_inop, "bank-angle-inop");
2151 make_voice(&voices.bank_angle_pause_bank_angle, "bank-angle", STDPAUSE, "bank-angle");
2152 make_voice(&voices.bank_angle_pause_bank_angle_3, "bank-angle", STDPAUSE, "bank-angle", 3.0);
2153 make_voice(&voices.callouts_inop, "callouts-inop");
2154 make_voice(&voices.configuration_type_invalid, "configuration-type-invalid");
2155 make_voice(&voices.dont_sink, "dont-sink");
2156 make_voice(&voices.dont_sink_pause_dont_sink, "dont-sink", STDPAUSE, "dont-sink");
2157 make_voice(&voices.five_hundred_above, "500-above");
2158 make_voice(&voices.glideslope, "glideslope");
2159 make_voice(&voices.glideslope_inop, "glideslope-inop");
2160 make_voice(&voices.gpws_inop, "gpws-inop");
2161 make_voice(&voices.hard_glideslope, "glideslope", "glideslope", 3.0);
2162 make_voice(&voices.minimums, "minimums");
2163 make_voice(&voices.minimums_minimums, "minimums", "minimums");
2164 make_voice(&voices.pull_up, "pull-up");
2165 make_voice(&voices.sink_rate, "sink-rate");
2166 make_voice(&voices.sink_rate_pause_sink_rate, "sink-rate", STDPAUSE, "sink-rate");
2167 make_voice(&voices.soft_glideslope, new Voice::SampleElement(get_sample("glideslope"), modify_amplitude(1.0, -6)));
2168 make_voice(&voices.terrain, "terrain");
2169 make_voice(&voices.terrain_pause_terrain, "terrain", STDPAUSE, "terrain");
2170 make_voice(&voices.too_low_flaps, "too-low-flaps");
2171 make_voice(&voices.too_low_gear, "too-low-gear");
2172 make_voice(&voices.too_low_terrain, "too-low-terrain");
2174 for (unsigned i = 0; i < n_altitude_callouts; i++)
2176 std::ostringstream name;
2177 name << "altitude-" << MK_VIII::Mode6Handler::altitude_callout_definitions[i];
2178 make_voice(&voices.altitude_callouts[i], name.str().c_str());
2180 speaker.update_configuration();
2183 ///////////////////////////////////////////////////////////////////////////////
2184 // SelfTestHandler ////////////////////////////////////////////////////////////
2185 ///////////////////////////////////////////////////////////////////////////////
2188 MK_VIII::SelfTestHandler::_was_here (int position)
2190 if (position > current)
2199 MK_VIII::SelfTestHandler::Action
2200 MK_VIII::SelfTestHandler::sleep (double duration)
2202 Action _action = { ACTION_SLEEP, duration, NULL };
2206 MK_VIII::SelfTestHandler::Action
2207 MK_VIII::SelfTestHandler::play (VoicePlayer::Voice *voice)
2209 mk->voice_player.play(voice);
2210 Action _action = { ACTION_VOICE, 0, NULL };
2214 MK_VIII::SelfTestHandler::Action
2215 MK_VIII::SelfTestHandler::discrete_on (bool *discrete, double duration)
2218 return sleep(duration);
2221 MK_VIII::SelfTestHandler::Action
2222 MK_VIII::SelfTestHandler::discrete_on_off (bool *discrete, double duration)
2225 Action _action = { ACTION_SLEEP | ACTION_DISCRETE_ON_OFF, duration, discrete };
2229 MK_VIII::SelfTestHandler::Action
2230 MK_VIII::SelfTestHandler::discrete_on_off (bool *discrete, VoicePlayer::Voice *voice)
2233 mk->voice_player.play(voice);
2234 Action _action = { ACTION_VOICE | ACTION_DISCRETE_ON_OFF, 0, discrete };
2238 MK_VIII::SelfTestHandler::Action
2239 MK_VIII::SelfTestHandler::done ()
2241 Action _action = { ACTION_DONE, 0, NULL };
2245 MK_VIII::SelfTestHandler::Action
2246 MK_VIII::SelfTestHandler::run ()
2248 // Note that "Terrain INOP" and "Terrain NA" are or'ed to the same
2249 // output discrete (see [SPEC] 6.9.3.5).
2251 #define was_here() was_here_offset(0)
2252 #define was_here_offset(offset) _was_here(__LINE__ * 20 + (offset))
2256 if (mk->configuration_module.state == ConfigurationModule::STATE_INVALID_DATABASE)
2257 return play(mk_voice(application_data_base_failed));
2258 else if (mk->configuration_module.state == ConfigurationModule::STATE_INVALID_AIRCRAFT_TYPE)
2259 return play(mk_voice(configuration_type_invalid));
2261 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
2265 return discrete_on(&mk_doutput(gpws_inop), 0.7);
2267 return sleep(0.7); // W/S INOP
2269 return discrete_on(&mk_doutput(tad_inop), 0.7);
2272 mk_doutput(gpws_inop) = false;
2273 // do not disable tad_inop since we must enable Terrain NA
2274 // do not disable W/S INOP since we do not emulate it
2275 return sleep(0.7); // Terrain NA
2279 mk_doutput(tad_inop) = false; // disable Terrain NA
2280 if (mk->io_handler.conf.momentary_flap_override_enabled)
2281 return discrete_on_off(&mk_doutput(flap_override), 1.0);
2284 return discrete_on_off(&mk_doutput(audio_on), 1.0);
2287 if (mk->io_handler.momentary_steep_approach_enabled())
2288 return discrete_on_off(&mk_doutput(steep_approach), 1.0);
2293 if (mk_dinput(glideslope_inhibit))
2294 goto glideslope_end;
2297 if (mk->fault_handler.faults[FaultHandler::FAULT_MODE5_INPUTS_INVALID])
2298 goto glideslope_inop;
2302 return discrete_on_off(&mk_doutput(gpws_alert), mk_voice(glideslope));
2304 return discrete_on_off(&mk_doutput(glideslope_cancel), 0.7);
2305 goto glideslope_end;
2308 return play(mk_voice(glideslope_inop));
2313 if (mk->fault_handler.faults[FaultHandler::FAULT_MODES14_INPUTS_INVALID])
2317 return discrete_on_off(&mk_doutput(gpws_warning), mk_voice(pull_up));
2321 return play(mk_voice(gpws_inop));
2326 if (mk_dinput(self_test)) // proceed to long self test
2331 if (mk->mode6_handler.conf.bank_angle_enabled
2332 && mk->fault_handler.faults[FaultHandler::FAULT_BANK_ANGLE_INPUTS_INVALID])
2333 return play(mk_voice(bank_angle_inop));
2337 if (mk->mode6_handler.altitude_callouts_enabled()
2338 && mk->fault_handler.faults[FaultHandler::FAULT_MODE6_INPUTS_INVALID])
2339 return play(mk_voice(callouts_inop));
2347 mk_doutput(gpws_inop) = true;
2348 // do not enable W/S INOP, since we do not emulate it
2349 mk_doutput(tad_inop) = true; // Terrain INOP, Terrain NA
2351 return play(mk_voice(sink_rate));
2354 return play(mk_voice(pull_up));
2356 return play(mk_voice(terrain));
2358 return play(mk_voice(pull_up));
2360 return play(mk_voice(dont_sink));
2362 return play(mk_voice(too_low_terrain));
2364 return play(mk->mode4_handler.conf.voice_too_low_gear);
2366 return play(mk_voice(too_low_flaps));
2368 return play(mk_voice(too_low_terrain));
2370 return play(mk_voice(glideslope));
2373 if (mk->mode6_handler.conf.bank_angle_enabled)
2374 return play(mk_voice(bank_angle));
2379 if (! mk->mode6_handler.altitude_callouts_enabled())
2380 goto callouts_disabled;
2384 if (mk->mode6_handler.conf.minimums_enabled)
2385 return play(mk_voice(minimums));
2389 if (mk->mode6_handler.conf.above_field_voice)
2390 return play(mk->mode6_handler.conf.above_field_voice);
2392 for (unsigned i = 0; i < n_altitude_callouts; i++)
2393 if (! was_here_offset(i))
2395 if (mk->mode6_handler.conf.altitude_callouts_enabled[i])
2396 return play(mk_altitude_voice(i));
2400 if (mk->mode6_handler.conf.smart_500_enabled)
2401 return play(mk_altitude_voice(Mode6Handler::ALTITUDE_CALLOUT_500));
2406 return play(mk_voice(minimums_minimums));
2411 if (mk->tcf_handler.conf.enabled)
2412 return play(mk_voice(too_low_terrain));
2419 MK_VIII::SelfTestHandler::start ()
2421 assert(state == STATE_START);
2423 memcpy(&saved_outputs, &mk->io_handler.outputs, sizeof(mk->io_handler.outputs));
2424 memset(&mk->io_handler.outputs, 0, sizeof(mk->io_handler.outputs));
2426 // [SPEC] 6.10.6: "The self-test results are annunciated, at 6db
2427 // lower than the normal audio level selected for the aircraft"
2428 mk->voice_player.set_volume(modify_amplitude(mk->voice_player.conf.volume, -6));
2430 if (mk_dinput(mode6_low_volume))
2431 mk->mode6_handler.set_volume(1.0);
2433 state = STATE_RUNNING;
2434 cancel = CANCEL_NONE;
2435 memset(&action, 0, sizeof(action));
2440 MK_VIII::SelfTestHandler::stop ()
2442 if (state != STATE_NONE)
2444 if (state != STATE_START)
2446 mk->voice_player.stop(VoicePlayer::STOP_NOW);
2447 mk->voice_player.set_volume(mk_dinput(audio_inhibit) ? 0.0 : mk->voice_player.conf.volume);
2449 if (mk_dinput(mode6_low_volume))
2450 mk->mode6_handler.set_volume(modify_amplitude(1.0, -6));
2452 memcpy(&mk->io_handler.outputs, &saved_outputs, sizeof(mk->io_handler.outputs));
2455 button_pressed = false;
2457 // reset self-test handler position
2463 MK_VIII::SelfTestHandler::handle_button_event (bool value)
2465 if (state == STATE_NONE)
2468 state = STATE_START;
2470 else if (state == STATE_START)
2473 state = STATE_NONE; // cancel the not-yet-started test
2475 else if (cancel == CANCEL_NONE)
2479 assert(! button_pressed);
2480 button_pressed = true;
2481 button_press_timestamp = globals->get_sim_time_sec();
2487 if (globals->get_sim_time_sec() - button_press_timestamp < 2)
2488 cancel = CANCEL_SHORT;
2490 cancel = CANCEL_LONG;
2497 MK_VIII::SelfTestHandler::update ()
2499 if (state == STATE_NONE)
2501 else if (state == STATE_START)
2503 if (mk->state_handler.ground && ! mk->io_handler.steep_approach())
2513 if (button_pressed && cancel == CANCEL_NONE)
2515 if (globals->get_sim_time_sec() - button_press_timestamp >= 2)
2516 cancel = CANCEL_LONG;
2520 if (! mk->state_handler.ground || cancel != CANCEL_NONE)
2526 if (test_bits(action.flags, ACTION_SLEEP))
2528 if (globals->get_sim_time_sec() - sleep_start < action.sleep_duration)
2531 if (test_bits(action.flags, ACTION_VOICE))
2533 if (mk->voice_player.voice)
2536 if (test_bits(action.flags, ACTION_DISCRETE_ON_OFF))
2537 *action.discrete = false;
2541 if (test_bits(action.flags, ACTION_SLEEP))
2542 sleep_start = globals->get_sim_time_sec();
2543 if (test_bits(action.flags, ACTION_DONE))
2552 ///////////////////////////////////////////////////////////////////////////////
2553 // AlertHandler ///////////////////////////////////////////////////////////////
2554 ///////////////////////////////////////////////////////////////////////////////
2557 MK_VIII::AlertHandler::select_voice_alerts (unsigned int test)
2559 if (has_alerts(test))
2561 voice_alerts = alerts & test;
2566 voice_alerts &= ~test;
2567 if (voice_alerts == 0)
2568 mk->voice_player.stop();
2575 MK_VIII::AlertHandler::boot ()
2581 MK_VIII::AlertHandler::reposition ()
2585 mk->io_handler.set_lamp(IOHandler::LAMP_NONE);
2586 mk->voice_player.stop(VoicePlayer::STOP_NOW);
2590 MK_VIII::AlertHandler::reset ()
2595 repeated_alerts = 0;
2596 altitude_callout_voice = NULL;
2600 MK_VIII::AlertHandler::update ()
2602 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
2605 // Lamps and voices are prioritized according to [SPEC] 6.9.2.
2607 // Voices which can interrupt other voices (VoicePlayer::PLAY_NOW)
2608 // are specified by [INSTALL] appendix E 5.3.5.
2610 // When a voice is overriden by a higher priority voice and the
2611 // overriding voice stops, we restore the previous voice if it was
2612 // looped (this is not specified by [SPEC] but seems to make sense).
2616 if (has_alerts(ALERT_MODE1_PULL_UP | ALERT_MODE2A | ALERT_MODE2B))
2617 mk->io_handler.set_lamp(IOHandler::LAMP_WARNING);
2618 else if (has_alerts(ALERT_MODE1_SINK_RATE
2619 | ALERT_MODE2A_PREFACE
2620 | ALERT_MODE2B_PREFACE
2621 | ALERT_MODE2A_ALTITUDE_GAIN_TERRAIN_CLOSING
2622 | ALERT_MODE2A_ALTITUDE_GAIN
2623 | ALERT_MODE2B_LANDING_MODE
2625 | ALERT_MODE4_TOO_LOW_FLAPS
2626 | ALERT_MODE4_TOO_LOW_GEAR
2627 | ALERT_MODE4AB_TOO_LOW_TERRAIN
2628 | ALERT_MODE4C_TOO_LOW_TERRAIN
2629 | ALERT_TCF_TOO_LOW_TERRAIN))
2630 mk->io_handler.set_lamp(IOHandler::LAMP_CAUTION);
2631 else if (has_alerts(ALERT_MODE5_SOFT | ALERT_MODE5_HARD))
2632 mk->io_handler.set_lamp(IOHandler::LAMP_GLIDESLOPE);
2634 mk->io_handler.set_lamp(IOHandler::LAMP_NONE);
2638 if (select_voice_alerts(ALERT_MODE1_PULL_UP))
2640 if (! has_old_alerts(ALERT_MODE1_PULL_UP))
2642 if (mk->voice_player.voice != mk_voice(sink_rate_pause_sink_rate))
2643 mk->voice_player.play(mk_voice(sink_rate), VoicePlayer::PLAY_NOW);
2644 mk->voice_player.play(mk_voice(pull_up), VoicePlayer::PLAY_LOOPED);
2647 else if (select_voice_alerts(ALERT_MODE2A_PREFACE | ALERT_MODE2B_PREFACE))
2649 if (! has_old_alerts(ALERT_MODE2A_PREFACE | ALERT_MODE2B_PREFACE))
2650 mk->voice_player.play(mk_voice(terrain_pause_terrain), VoicePlayer::PLAY_NOW);
2652 else if (select_voice_alerts(ALERT_MODE2A | ALERT_MODE2B))
2654 if (mk->voice_player.voice != mk_voice(pull_up))
2655 mk->voice_player.play(mk_voice(pull_up), VoicePlayer::PLAY_NOW | VoicePlayer::PLAY_LOOPED);
2657 else if (select_voice_alerts(ALERT_MODE2A_ALTITUDE_GAIN_TERRAIN_CLOSING | ALERT_MODE2B_LANDING_MODE))
2659 if (mk->voice_player.voice != mk_voice(terrain))
2660 mk->voice_player.play(mk_voice(terrain), VoicePlayer::PLAY_LOOPED);
2662 else if (select_voice_alerts(ALERT_MODE6_MINIMUMS))
2664 if (! has_old_alerts(ALERT_MODE6_MINIMUMS))
2665 mk->voice_player.play(mk_voice(minimums_minimums));
2667 else if (select_voice_alerts(ALERT_MODE4AB_TOO_LOW_TERRAIN | ALERT_MODE4C_TOO_LOW_TERRAIN))
2669 if (must_play_voice(ALERT_MODE4AB_TOO_LOW_TERRAIN | ALERT_MODE4C_TOO_LOW_TERRAIN))
2670 mk->voice_player.play(mk_voice(too_low_terrain));
2672 else if (select_voice_alerts(ALERT_TCF_TOO_LOW_TERRAIN))
2674 if (must_play_voice(ALERT_TCF_TOO_LOW_TERRAIN))
2675 mk->voice_player.play(mk_voice(too_low_terrain));
2677 else if (select_voice_alerts(ALERT_MODE6_ALTITUDE_CALLOUT))
2679 if (! has_old_alerts(ALERT_MODE6_ALTITUDE_CALLOUT))
2681 assert(altitude_callout_voice != NULL);
2682 mk->voice_player.play(altitude_callout_voice);
2683 altitude_callout_voice = NULL;
2686 else if (select_voice_alerts(ALERT_MODE4_TOO_LOW_GEAR))
2688 if (must_play_voice(ALERT_MODE4_TOO_LOW_GEAR))
2689 mk->voice_player.play(mk->mode4_handler.conf.voice_too_low_gear);
2691 else if (select_voice_alerts(ALERT_MODE4_TOO_LOW_FLAPS))
2693 if (must_play_voice(ALERT_MODE4_TOO_LOW_FLAPS))
2694 mk->voice_player.play(mk_voice(too_low_flaps));
2696 else if (select_voice_alerts(ALERT_MODE1_SINK_RATE))
2698 if (must_play_voice(ALERT_MODE1_SINK_RATE))
2699 mk->voice_player.play(mk_voice(sink_rate_pause_sink_rate));
2700 // [SPEC] 6.2.1: "During the time that the voice message for the
2701 // outer envelope is inhibited and the caution/warning lamp is
2702 // on, the Mode 5 alert message is allowed to annunciate for
2703 // excessive glideslope deviation conditions."
2704 else if (mk->voice_player.voice != mk_voice(sink_rate_pause_sink_rate)
2705 && mk->voice_player.next_voice != mk_voice(sink_rate_pause_sink_rate))
2707 if (has_alerts(ALERT_MODE5_HARD))
2709 voice_alerts |= ALERT_MODE5_HARD;
2710 if (mk->voice_player.voice != mk_voice(hard_glideslope))
2711 mk->voice_player.play(mk_voice(hard_glideslope), VoicePlayer::PLAY_LOOPED);
2713 else if (has_alerts(ALERT_MODE5_SOFT))
2715 voice_alerts |= ALERT_MODE5_SOFT;
2716 if (must_play_voice(ALERT_MODE5_SOFT))
2717 mk->voice_player.play(mk_voice(soft_glideslope));
2721 else if (select_voice_alerts(ALERT_MODE3))
2723 if (must_play_voice(ALERT_MODE3))
2724 mk->voice_player.play(mk_voice(dont_sink_pause_dont_sink));
2726 else if (select_voice_alerts(ALERT_MODE5_HARD))
2728 if (mk->voice_player.voice != mk_voice(hard_glideslope))
2729 mk->voice_player.play(mk_voice(hard_glideslope), VoicePlayer::PLAY_LOOPED);
2731 else if (select_voice_alerts(ALERT_MODE5_SOFT))
2733 if (must_play_voice(ALERT_MODE5_SOFT))
2734 mk->voice_player.play(mk_voice(soft_glideslope));
2736 else if (select_voice_alerts(ALERT_MODE6_LOW_BANK_ANGLE_3))
2738 if (mk->voice_player.voice != mk_voice(bank_angle_bank_angle_3))
2739 mk->voice_player.play(mk_voice(bank_angle_bank_angle_3), VoicePlayer::PLAY_LOOPED);
2741 else if (select_voice_alerts(ALERT_MODE6_HIGH_BANK_ANGLE_3))
2743 if (mk->voice_player.voice != mk_voice(bank_angle_pause_bank_angle_3))
2744 mk->voice_player.play(mk_voice(bank_angle_pause_bank_angle_3), VoicePlayer::PLAY_LOOPED);
2746 else if (select_voice_alerts(ALERT_MODE6_LOW_BANK_ANGLE_2))
2748 if (! has_old_alerts(ALERT_MODE6_LOW_BANK_ANGLE_2 | ALERT_MODE6_HIGH_BANK_ANGLE_2))
2749 mk->voice_player.play(mk_voice(bank_angle_bank_angle));
2751 else if (select_voice_alerts(ALERT_MODE6_HIGH_BANK_ANGLE_2))
2753 if (! has_old_alerts(ALERT_MODE6_LOW_BANK_ANGLE_2 | ALERT_MODE6_HIGH_BANK_ANGLE_2))
2754 mk->voice_player.play(mk_voice(bank_angle_pause_bank_angle));
2756 else if (select_voice_alerts(ALERT_MODE6_LOW_BANK_ANGLE_1))
2758 if (! has_old_alerts(ALERT_MODE6_LOW_BANK_ANGLE_1 | ALERT_MODE6_HIGH_BANK_ANGLE_1))
2759 mk->voice_player.play(mk_voice(bank_angle_bank_angle));
2761 else if (select_voice_alerts(ALERT_MODE6_HIGH_BANK_ANGLE_1))
2763 if (! has_old_alerts(ALERT_MODE6_LOW_BANK_ANGLE_1 | ALERT_MODE6_HIGH_BANK_ANGLE_1))
2764 mk->voice_player.play(mk_voice(bank_angle_pause_bank_angle));
2767 // remember all alerts voiced so far...
2768 old_alerts |= voice_alerts;
2769 // ... forget those no longer active
2770 old_alerts &= alerts;
2771 repeated_alerts = 0;
2775 MK_VIII::AlertHandler::set_alerts (unsigned int _alerts,
2777 VoicePlayer::Voice *_altitude_callout_voice)
2780 if (test_bits(flags, ALERT_FLAG_REPEAT))
2781 repeated_alerts |= _alerts;
2782 if (_altitude_callout_voice)
2783 altitude_callout_voice = _altitude_callout_voice;
2787 MK_VIII::AlertHandler::unset_alerts (unsigned int _alerts)
2790 repeated_alerts &= ~_alerts;
2791 if (_alerts & ALERT_MODE6_ALTITUDE_CALLOUT)
2792 altitude_callout_voice = NULL;
2795 ///////////////////////////////////////////////////////////////////////////////
2796 // StateHandler ///////////////////////////////////////////////////////////////
2797 ///////////////////////////////////////////////////////////////////////////////
2800 MK_VIII::StateHandler::update_ground ()
2804 if (! mk_ainput(computed_airspeed).ncd && mk_ainput(computed_airspeed).get() > 60
2805 && ! mk_data(radio_altitude).ncd && mk_data(radio_altitude).get() > 30)
2807 if (potentially_airborne_timer.start_or_elapsed() > 1)
2811 potentially_airborne_timer.stop();
2815 if (! mk_ainput(computed_airspeed).ncd && mk_ainput(computed_airspeed).get() < 40
2816 && ! mk_data(radio_altitude).ncd && mk_data(radio_altitude).get() < 30)
2822 MK_VIII::StateHandler::enter_ground ()
2825 mk->io_handler.enter_ground();
2827 // [SPEC] 6.0.1 does not specify this, but it seems to be an
2828 // omission; at this point, we must make sure that we are in takeoff
2829 // mode (otherwise the next takeoff might happen in approach mode).
2835 MK_VIII::StateHandler::leave_ground ()
2838 mk->mode2_handler.leave_ground();
2842 MK_VIII::StateHandler::update_takeoff ()
2846 // [SPEC] 6.0.2 is somewhat confusing: it mentions hardcoded
2847 // terrain clearance (500, 750) and airspeed (178, 200) values,
2848 // but it also mentions the mode 4A boundary, which varies as a
2849 // function of aircraft type. We consider that the mentioned
2850 // values are examples, and that we should use the mode 4A upper
2853 if (! mk_data(terrain_clearance).ncd
2854 && ! mk_ainput(computed_airspeed).ncd
2855 && mk_data(terrain_clearance).get() > mk->mode4_handler.get_upper_agl(mk->mode4_handler.conf.modes->ac))
2860 if (! mk_data(radio_altitude).ncd
2861 && mk_data(radio_altitude).get() < mk->mode4_handler.conf.modes->b->min_agl1
2862 && mk->io_handler.flaps_down()
2863 && mk_dinput(landing_gear))
2869 MK_VIII::StateHandler::enter_takeoff ()
2872 mk->io_handler.enter_takeoff();
2873 mk->mode2_handler.enter_takeoff();
2874 mk->mode3_handler.enter_takeoff();
2875 mk->mode6_handler.enter_takeoff();
2879 MK_VIII::StateHandler::leave_takeoff ()
2882 mk->mode6_handler.leave_takeoff();
2886 MK_VIII::StateHandler::post_reposition ()
2888 // Synchronize the state with the current situation.
2891 (! mk_ainput(computed_airspeed).ncd && mk_ainput(computed_airspeed).get() > 60
2892 && ! mk_data(radio_altitude).ncd && mk_data(radio_altitude).get() > 30);
2894 bool _takeoff = _ground;
2896 if (ground && ! _ground)
2898 else if (! ground && _ground)
2901 if (takeoff && ! _takeoff)
2903 else if (! takeoff && _takeoff)
2908 MK_VIII::StateHandler::update ()
2910 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
2917 ///////////////////////////////////////////////////////////////////////////////
2918 // Mode1Handler ///////////////////////////////////////////////////////////////
2919 ///////////////////////////////////////////////////////////////////////////////
2922 MK_VIII::Mode1Handler::get_pull_up_bias ()
2924 // [PILOT] page 54: "Steep Approach has priority over Flap Override
2925 // if selected simultaneously."
2927 if (mk->io_handler.steep_approach())
2929 else if (conf.envelopes->flap_override_bias && mk->io_handler.flap_override())
2936 MK_VIII::Mode1Handler::is_pull_up ()
2938 if (! mk->io_handler.gpws_inhibit()
2939 && ! mk->state_handler.ground // [1]
2940 && ! mk_data(radio_altitude).ncd
2941 && ! mk_data(barometric_altitude_rate).ncd
2942 && mk_data(radio_altitude).get() > conf.envelopes->min_agl)
2944 if (mk_data(radio_altitude).get() < conf.envelopes->pull_up_max_agl1)
2946 if (mk_data(radio_altitude).get() < conf.envelopes->pull_up_min_agl1(mk_data(barometric_altitude_rate).get() + get_pull_up_bias()))
2949 else if (mk_data(radio_altitude).get() < conf.envelopes->pull_up_max_agl2)
2951 if (mk_data(radio_altitude).get() < conf.envelopes->pull_up_min_agl2(mk_data(barometric_altitude_rate).get() + get_pull_up_bias()))
2960 MK_VIII::Mode1Handler::update_pull_up ()
2964 if (! mk_test_alert(MODE1_PULL_UP))
2966 // [SPEC] 6.2.1: at least one sink rate must be issued
2967 // before switching to pull up; if no sink rate has
2968 // occurred, a 0.2 second delay is used.
2969 if (mk->voice_player.voice == mk_voice(sink_rate_pause_sink_rate)
2970 || pull_up_timer.start_or_elapsed() >= 0.2)
2971 mk_set_alerts(mk_alert(MODE1_PULL_UP));
2976 pull_up_timer.stop();
2977 mk_unset_alerts(mk_alert(MODE1_PULL_UP));
2982 MK_VIII::Mode1Handler::get_sink_rate_bias ()
2984 // [PILOT] page 54: "Steep Approach has priority over Flap Override
2985 // if selected simultaneously."
2987 if (mk->io_handler.steep_approach())
2989 else if (conf.envelopes->flap_override_bias && mk->io_handler.flap_override())
2991 else if (! mk_data(glideslope_deviation_dots).ncd)
2995 if (mk_data(glideslope_deviation_dots).get() <= -2)
2997 else if (mk_data(glideslope_deviation_dots).get() < 0)
2998 bias = -150 * mk_data(glideslope_deviation_dots).get();
3000 if (mk_data(radio_altitude).get() < 100)
3001 bias *= 0.01 * mk_data(radio_altitude).get();
3010 MK_VIII::Mode1Handler::is_sink_rate ()
3012 return ! mk->io_handler.gpws_inhibit()
3013 && ! mk->state_handler.ground // [1]
3014 && ! mk_data(radio_altitude).ncd
3015 && ! mk_data(barometric_altitude_rate).ncd
3016 && mk_data(radio_altitude).get() > conf.envelopes->min_agl
3017 && mk_data(radio_altitude).get() < 2450
3018 && mk_data(radio_altitude).get() < -572 - 0.6035 * (mk_data(barometric_altitude_rate).get() + get_sink_rate_bias());
3022 MK_VIII::Mode1Handler::get_sink_rate_tti ()
3024 return mk_data(radio_altitude).get() / fabs(mk_data(barometric_altitude_rate).get());
3028 MK_VIII::Mode1Handler::update_sink_rate ()
3032 if (mk_test_alert(MODE1_SINK_RATE))
3034 double tti = get_sink_rate_tti();
3035 if (tti <= sink_rate_tti * 0.8)
3037 // ~20% degradation, warn again and store the new reference tti
3038 sink_rate_tti = tti;
3039 mk_repeat_alert(mk_alert(MODE1_SINK_RATE));
3044 if (sink_rate_timer.start_or_elapsed() >= 0.8)
3046 mk_set_alerts(mk_alert(MODE1_SINK_RATE));
3047 sink_rate_tti = get_sink_rate_tti();
3053 sink_rate_timer.stop();
3054 mk_unset_alerts(mk_alert(MODE1_SINK_RATE));
3059 MK_VIII::Mode1Handler::update ()
3061 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
3068 ///////////////////////////////////////////////////////////////////////////////
3069 // Mode2Handler ///////////////////////////////////////////////////////////////
3070 ///////////////////////////////////////////////////////////////////////////////
3073 MK_VIII::Mode2Handler::ClosureRateFilter::limit_radio_altitude_rate (double r)
3075 // Limit radio altitude rate according to aircraft configuration,
3076 // allowing maximum sensitivity during cruise while providing
3077 // progressively less sensitivity during the landing phases of
3080 if (! mk_data(glideslope_deviation_dots).ncd && fabs(mk_data(glideslope_deviation_dots).get()) <= 2)
3081 { // gs deviation <= +- 2 dots
3082 if (mk_dinput(landing_gear) && mk->io_handler.flaps_down())
3083 SG_CLAMP_RANGE(r, -1000.0, 3000.0);
3084 else if (mk_dinput(landing_gear) || mk->io_handler.flaps_down())
3085 SG_CLAMP_RANGE(r, 0.0, 4000.0);
3087 SG_CLAMP_RANGE(r, 1000.0, 5000.0);
3090 { // no ILS, or gs deviation > +- 2 dots
3091 if (mk_dinput(landing_gear) && mk->io_handler.flaps_down())
3092 SG_CLAMP_RANGE(r, 0.0, 4000.0);
3093 else if (mk_dinput(landing_gear) || mk->io_handler.flaps_down())
3094 SG_CLAMP_RANGE(r, 1000.0, 5000.0);
3102 MK_VIII::Mode2Handler::ClosureRateFilter::init ()
3105 last_ra.set(&mk_data(radio_altitude));
3106 last_ba.set(&mk_ainput(uncorrected_barometric_altitude));
3113 MK_VIII::Mode2Handler::ClosureRateFilter::update ()
3115 double elapsed = timer.start_or_elapsed();
3119 if (! mk_data(radio_altitude).ncd && ! mk_ainput(uncorrected_barometric_altitude).ncd)
3121 if (! last_ra.ncd && ! last_ba.ncd)
3123 // compute radio and barometric altitude rates (positive value = descent)
3124 double ra_rate = -(mk_data(radio_altitude).get() - last_ra.get()) / elapsed * 60;
3125 double ba_rate = -(mk_ainput(uncorrected_barometric_altitude).get() - last_ba.get()) / elapsed * 60;
3127 // limit radio altitude rate according to aircraft configuration
3128 ra_rate = limit_radio_altitude_rate(ra_rate);
3130 // apply a low-pass filter to the radio altitude rate
3131 ra_rate = ra_filter.filter(ra_rate);
3132 // apply a high-pass filter to the barometric altitude rate
3133 ba_rate = ba_filter.filter(ba_rate);
3135 // combine both rates to obtain a closure rate
3136 output.set(ra_rate + ba_rate);
3149 last_ra.set(&mk_data(radio_altitude));
3150 last_ba.set(&mk_ainput(uncorrected_barometric_altitude));
3154 MK_VIII::Mode2Handler::b_conditions ()
3156 return mk->io_handler.flaps_down()
3157 || (! mk_data(glideslope_deviation_dots).ncd && fabs(mk_data(glideslope_deviation_dots).get()) < 2)
3158 || takeoff_timer.running;
3162 MK_VIII::Mode2Handler::is_a ()
3164 if (! mk->io_handler.gpws_inhibit()
3165 && ! mk->state_handler.ground // [1]
3166 && ! mk_data(radio_altitude).ncd
3167 && ! mk_ainput(computed_airspeed).ncd
3168 && ! closure_rate_filter.output.ncd
3169 && ! b_conditions())
3171 if (mk_data(radio_altitude).get() < 1220)
3173 if (mk_data(radio_altitude).get() < -1579 + 0.7895 * closure_rate_filter.output.get())
3180 if (mk_ainput(computed_airspeed).get() <= conf->airspeed1)
3182 else if (mk_ainput(computed_airspeed).get() >= conf->airspeed2)
3185 upper_limit = 1650 + 8.9 * (mk_ainput(computed_airspeed).get() - conf->airspeed1);
3187 if (mk_data(radio_altitude).get() < upper_limit)
3189 if (mk_data(radio_altitude).get() < 522 + 0.1968 * closure_rate_filter.output.get())
3199 MK_VIII::Mode2Handler::is_b ()
3201 if (! mk->io_handler.gpws_inhibit()
3202 && ! mk->state_handler.ground // [1]
3203 && ! mk_data(radio_altitude).ncd
3204 && ! mk_data(barometric_altitude_rate).ncd
3205 && ! closure_rate_filter.output.ncd
3207 && mk_data(radio_altitude).get() < 789)
3211 if (mk->io_handler.flaps_down())
3213 if (mk_data(barometric_altitude_rate).get() > -400)
3215 else if (mk_data(barometric_altitude_rate).get() < -1000)
3218 lower_limit = -66.777 - 0.667 * mk_data(barometric_altitude_rate).get();
3223 if (mk_data(radio_altitude).get() > lower_limit)
3225 if (mk_data(radio_altitude).get() < -1579 + 0.7895 * closure_rate_filter.output.get())
3234 MK_VIII::Mode2Handler::check_pull_up (unsigned int preface_alert,
3237 if (pull_up_timer.running)
3239 if (pull_up_timer.elapsed() >= 1)
3241 mk_unset_alerts(preface_alert);
3242 mk_set_alerts(alert);
3247 if (! mk->voice_player.voice)
3248 pull_up_timer.start();
3253 MK_VIII::Mode2Handler::update_a ()
3257 if (mk_test_alert(MODE2A_PREFACE))
3258 check_pull_up(mk_alert(MODE2A_PREFACE), mk_alert(MODE2A));
3259 else if (! mk_test_alert(MODE2A))
3261 mk_unset_alerts(mk_alert(MODE2A_ALTITUDE_GAIN) | mk_alert(MODE2A_ALTITUDE_GAIN_TERRAIN_CLOSING));
3262 mk_set_alerts(mk_alert(MODE2A_PREFACE));
3263 a_start_time = globals->get_sim_time_sec();
3264 pull_up_timer.stop();
3269 if (mk_test_alert(MODE2A_ALTITUDE_GAIN))
3271 if (mk->io_handler.gpws_inhibit()
3272 || mk->state_handler.ground // [1]
3273 || a_altitude_gain_timer.elapsed() >= 45
3274 || mk_data(radio_altitude).ncd
3275 || mk_ainput(uncorrected_barometric_altitude).ncd
3276 || mk_ainput(uncorrected_barometric_altitude).get() - a_altitude_gain_alt >= 300
3277 // [PILOT] page 12: "the visual alert will remain on
3278 // until [...] landing flaps or the flap override switch
3280 || mk->io_handler.flaps_down())
3282 // exit altitude gain mode
3283 a_altitude_gain_timer.stop();
3284 mk_unset_alerts(mk_alert(MODE2A_ALTITUDE_GAIN) | mk_alert(MODE2A_ALTITUDE_GAIN_TERRAIN_CLOSING));
3288 // [SPEC] 6.2.2.2: "If the terrain starts to fall away
3289 // during this altitude gain time, the voice will shut
3291 if (closure_rate_filter.output.get() < 0)
3292 mk_unset_alerts(mk_alert(MODE2A_ALTITUDE_GAIN_TERRAIN_CLOSING));
3295 else if (mk_test_alerts(mk_alert(MODE2A_PREFACE) | mk_alert(MODE2A)))
3297 mk_unset_alerts(mk_alert(MODE2A_PREFACE) | mk_alert(MODE2A));
3299 if (! mk->io_handler.gpws_inhibit()
3300 && ! mk->state_handler.ground // [1]
3301 && globals->get_sim_time_sec() - a_start_time > 3
3302 && ! mk->io_handler.flaps_down()
3303 && ! mk_data(radio_altitude).ncd
3304 && ! mk_ainput(uncorrected_barometric_altitude).ncd)
3306 // [SPEC] 6.2.2.2: mode 2A envelope violated for more
3307 // than 3 seconds, enable altitude gain feature
3309 a_altitude_gain_timer.start();
3310 a_altitude_gain_alt = mk_ainput(uncorrected_barometric_altitude).get();
3312 unsigned int alerts = mk_alert(MODE2A_ALTITUDE_GAIN);
3313 if (closure_rate_filter.output.get() > 0)
3314 alerts |= mk_alert(MODE2A_ALTITUDE_GAIN_TERRAIN_CLOSING);
3316 mk_set_alerts(alerts);
3323 MK_VIII::Mode2Handler::update_b ()
3327 // handle normal mode
3329 if (b && (! mk_dinput(landing_gear) || ! mk->io_handler.flaps_down()))
3331 if (mk_test_alert(MODE2B_PREFACE))
3332 check_pull_up(mk_alert(MODE2B_PREFACE), mk_alert(MODE2B));
3333 else if (! mk_test_alert(MODE2B))
3335 mk_set_alerts(mk_alert(MODE2B_PREFACE));
3336 pull_up_timer.stop();
3340 mk_unset_alerts(mk_alert(MODE2B_PREFACE) | mk_alert(MODE2B));
3342 // handle landing mode ([SPEC] 6.2.2.3: "If both landing gear and
3343 // flaps are in the landing configuration, then the message will be
3346 if (b && mk_dinput(landing_gear) && mk->io_handler.flaps_down())
3347 mk_set_alerts(mk_alert(MODE2B_LANDING_MODE));
3349 mk_unset_alerts(mk_alert(MODE2B_LANDING_MODE));
3353 MK_VIII::Mode2Handler::boot ()
3355 closure_rate_filter.init();
3359 MK_VIII::Mode2Handler::power_off ()
3361 // [SPEC] 6.0.4: "This latching function is not power saved and a
3362 // system reset will force it false."
3363 takeoff_timer.stop();
3367 MK_VIII::Mode2Handler::leave_ground ()
3369 // takeoff, reset timer
3370 takeoff_timer.start();
3374 MK_VIII::Mode2Handler::enter_takeoff ()
3376 // reset timer, in case it's a go-around
3377 takeoff_timer.start();
3381 MK_VIII::Mode2Handler::update ()
3383 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
3386 closure_rate_filter.update();
3388 if (takeoff_timer.running && takeoff_timer.elapsed() >= 60)
3389 takeoff_timer.stop();
3395 ///////////////////////////////////////////////////////////////////////////////
3396 // Mode3Handler ///////////////////////////////////////////////////////////////
3397 ///////////////////////////////////////////////////////////////////////////////
3400 MK_VIII::Mode3Handler::max_alt_loss (double _bias)
3402 return conf->max_alt_loss(mk->io_handler.flap_override(), mk_data(radio_altitude).get()) + mk_data(radio_altitude).get() * _bias;
3406 MK_VIII::Mode3Handler::get_bias (double initial_bias, double alt_loss)
3408 // do not repeat altitude-loss alerts below 30ft agl
3409 if (mk_data(radio_altitude).get() > 30)
3411 if (initial_bias < 0.0) // sanity check
3413 // mk-viii spec: repeat alerts whenever losing 20% of initial altitude
3414 while ((alt_loss > max_alt_loss(initial_bias))&&
3415 (initial_bias < 1.0))
3416 initial_bias += 0.2;
3419 return initial_bias;
3423 MK_VIII::Mode3Handler::is (double *alt_loss)
3425 if (has_descent_alt)
3427 int max_agl = conf->max_agl(mk->io_handler.flap_override());
3429 if (mk_ainput(uncorrected_barometric_altitude).ncd
3430 || mk_ainput(uncorrected_barometric_altitude).get() > descent_alt
3431 || mk_data(radio_altitude).ncd
3432 || mk_data(radio_altitude).get() > max_agl)
3435 has_descent_alt = false;
3439 // gear/flaps: [SPEC] 1.3.1.3
3440 if (! mk->io_handler.gpws_inhibit()
3441 && ! mk->state_handler.ground // [1]
3442 && (! mk_dinput(landing_gear) || ! mk->io_handler.flaps_down())
3443 && ! mk_data(barometric_altitude_rate).ncd
3444 && ! mk_ainput(uncorrected_barometric_altitude).ncd
3445 && ! mk_data(radio_altitude).ncd
3446 && mk_data(barometric_altitude_rate).get() < 0)
3448 double _alt_loss = descent_alt - mk_ainput(uncorrected_barometric_altitude).get();
3449 if (armed || (mk_data(radio_altitude).get() > conf->min_agl
3450 && mk_data(radio_altitude).get() < max_agl
3451 && _alt_loss > max_alt_loss(0)))
3453 *alt_loss = _alt_loss;
3461 if (! mk_data(barometric_altitude_rate).ncd
3462 && ! mk_ainput(uncorrected_barometric_altitude).ncd
3463 && mk_data(barometric_altitude_rate).get() < 0)
3465 has_descent_alt = true;
3466 descent_alt = mk_ainput(uncorrected_barometric_altitude).get();
3474 MK_VIII::Mode3Handler::enter_takeoff ()
3477 has_descent_alt = false;
3481 MK_VIII::Mode3Handler::update ()
3483 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
3486 if (mk->state_handler.takeoff)
3490 if (! mk->state_handler.ground /* [1] */ && is(&alt_loss))
3492 if (mk_test_alert(MODE3))
3494 double new_bias = get_bias(bias, alt_loss);
3495 if (new_bias > bias)
3498 mk_repeat_alert(mk_alert(MODE3));
3504 bias = get_bias(0.2, alt_loss);
3505 mk_set_alerts(mk_alert(MODE3));
3512 mk_unset_alerts(mk_alert(MODE3));
3515 ///////////////////////////////////////////////////////////////////////////////
3516 // Mode4Handler ///////////////////////////////////////////////////////////////
3517 ///////////////////////////////////////////////////////////////////////////////
3519 // FIXME: for turbofans, the upper limit should be reduced from 1000
3520 // to 800 ft if a sudden change in radio altitude is detected, in
3521 // order to reduce nuisance alerts caused by overflying another
3522 // aircraft (see [PILOT] page 16).
3525 MK_VIII::Mode4Handler::get_upper_agl (const EnvelopesConfiguration *c)
3527 if (mk_ainput(computed_airspeed).get() >= c->airspeed2)
3529 else if (mk_ainput(computed_airspeed).get() >= c->airspeed1)
3530 return c->min_agl2(mk_ainput(computed_airspeed).get());
3535 const MK_VIII::Mode4Handler::EnvelopesConfiguration *
3536 MK_VIII::Mode4Handler::get_ab_envelope ()
3538 // [SPEC] 6.2.4.1: "The Mode 4B envelope can also be selected by
3539 // setting flaps to landing configuration or by selecting flap
3541 return mk_dinput(landing_gear) || mk->io_handler.flaps_down()
3547 MK_VIII::Mode4Handler::get_bias (double initial_bias, double min_agl)
3549 // do not repeat terrain/gear/flap alerts below 30ft agl
3550 if (mk_data(radio_altitude).get() > 30.0)
3552 if (initial_bias < 0.0) // sanity check
3554 while ((mk_data(radio_altitude).get() < min_agl - min_agl * initial_bias)&&
3555 (initial_bias < 1.0))
3556 initial_bias += 0.2;
3559 return initial_bias;
3563 MK_VIII::Mode4Handler::handle_alert (unsigned int alert,
3567 if (mk_test_alerts(alert))
3569 double new_bias = get_bias(*bias, min_agl);
3570 if (new_bias > *bias)
3573 mk_repeat_alert(alert);
3578 *bias = get_bias(0.2, min_agl);
3579 mk_set_alerts(alert);
3584 MK_VIII::Mode4Handler::update_ab ()
3586 if (! mk->io_handler.gpws_inhibit()
3587 && ! mk->state_handler.ground
3588 && ! mk->state_handler.takeoff
3589 && ! mk_data(radio_altitude).ncd
3590 && ! mk_ainput(computed_airspeed).ncd
3591 && mk_data(radio_altitude).get() > 30)
3593 const EnvelopesConfiguration *c = get_ab_envelope();
3594 if (mk_ainput(computed_airspeed).get() < c->airspeed1)
3596 if (mk_dinput(landing_gear))
3598 if (! mk->io_handler.flaps_down() && mk_data(radio_altitude).get() < c->min_agl1)
3600 handle_alert(mk_alert(MODE4_TOO_LOW_FLAPS), c->min_agl1, &ab_bias);
3606 if (mk_data(radio_altitude).get() < c->min_agl1)
3608 handle_alert(mk_alert(MODE4_TOO_LOW_GEAR), c->min_agl1, &ab_bias);
3615 mk_unset_alerts(mk_alert(MODE4_TOO_LOW_FLAPS) | mk_alert(MODE4_TOO_LOW_GEAR));
3620 MK_VIII::Mode4Handler::update_ab_expanded ()
3622 if (! mk->io_handler.gpws_inhibit()
3623 && ! mk->state_handler.ground
3624 && ! mk->state_handler.takeoff
3625 && ! mk_data(radio_altitude).ncd
3626 && ! mk_ainput(computed_airspeed).ncd
3627 && mk_data(radio_altitude).get() > 30)
3629 const EnvelopesConfiguration *c = get_ab_envelope();
3630 if (mk_ainput(computed_airspeed).get() >= c->airspeed1)
3632 double min_agl = get_upper_agl(c);
3633 if (mk_data(radio_altitude).get() < min_agl)
3635 handle_alert(mk_alert(MODE4AB_TOO_LOW_TERRAIN), min_agl, &ab_expanded_bias);
3641 mk_unset_alerts(mk_alert(MODE4AB_TOO_LOW_TERRAIN));
3642 ab_expanded_bias=0.0;
3646 MK_VIII::Mode4Handler::update_c ()
3648 if (! mk->io_handler.gpws_inhibit()
3649 && ! mk->state_handler.ground // [1]
3650 && mk->state_handler.takeoff
3651 && ! mk_data(radio_altitude).ncd
3652 && ! mk_data(terrain_clearance).ncd
3653 && mk_data(radio_altitude).get() > 30
3654 && (! mk_dinput(landing_gear) || ! mk->io_handler.flaps_down())
3655 && mk_data(radio_altitude).get() < get_upper_agl(conf.modes->ac)
3656 && mk_data(radio_altitude).get() < mk_data(terrain_clearance).get())
3657 handle_alert(mk_alert(MODE4C_TOO_LOW_TERRAIN), mk_data(terrain_clearance).get(), &c_bias);
3660 mk_unset_alerts(mk_alert(MODE4C_TOO_LOW_TERRAIN));
3666 MK_VIII::Mode4Handler::update ()
3668 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
3672 update_ab_expanded();
3676 ///////////////////////////////////////////////////////////////////////////////
3677 // Mode5Handler ///////////////////////////////////////////////////////////////
3678 ///////////////////////////////////////////////////////////////////////////////
3681 MK_VIII::Mode5Handler::is_hard ()
3683 if (mk_data(radio_altitude).get() > 30
3684 && mk_data(radio_altitude).get() < 300
3685 && mk_data(glideslope_deviation_dots).get() > 2)
3687 if (mk_data(radio_altitude).get() < 150)
3689 if (mk_data(radio_altitude).get() > 293 - 71.43 * mk_data(glideslope_deviation_dots).get())
3700 MK_VIII::Mode5Handler::is_soft (double bias)
3702 // do not repeat glide-slope alerts below 30ft agl
3703 if (mk_data(radio_altitude).get() > 30)
3705 double bias_dots = 1.3 * bias;
3706 if (mk_data(glideslope_deviation_dots).get() > 1.3 + bias_dots)
3708 if (mk_data(radio_altitude).get() < 150)
3710 if (mk_data(radio_altitude).get() > 243 - 71.43 * (mk_data(glideslope_deviation_dots).get() - bias_dots))
3717 if (mk_data(barometric_altitude_rate).ncd)
3721 if (mk_data(barometric_altitude_rate).get() >= 0)
3723 else if (mk_data(barometric_altitude_rate).get() < -500)
3726 upper_limit = -mk_data(barometric_altitude_rate).get() + 500;
3729 if (mk_data(radio_altitude).get() < upper_limit)
3739 MK_VIII::Mode5Handler::get_soft_bias (double initial_bias)
3741 if (initial_bias < 0.0) // sanity check
3743 while ((is_soft(initial_bias))&&
3744 (initial_bias < 1.0))
3745 initial_bias += 0.2;
3747 return initial_bias;
3751 MK_VIII::Mode5Handler::update_hard (bool is)
3755 if (! mk_test_alert(MODE5_HARD))
3757 if (hard_timer.start_or_elapsed() >= 0.8)
3758 mk_set_alerts(mk_alert(MODE5_HARD));
3764 mk_unset_alerts(mk_alert(MODE5_HARD));
3769 MK_VIII::Mode5Handler::update_soft (bool is)
3773 if (! mk_test_alert(MODE5_SOFT))
3775 double new_bias = get_soft_bias(soft_bias);
3776 if (new_bias > soft_bias)
3778 soft_bias = new_bias;
3779 mk_repeat_alert(mk_alert(MODE5_SOFT));
3784 if (soft_timer.start_or_elapsed() >= 0.8)
3786 soft_bias = get_soft_bias(0.2);
3787 mk_set_alerts(mk_alert(MODE5_SOFT));
3794 mk_unset_alerts(mk_alert(MODE5_SOFT));
3799 MK_VIII::Mode5Handler::update ()
3801 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
3804 if (! mk->io_handler.gpws_inhibit()
3805 && ! mk->state_handler.ground // [1]
3806 && ! mk_dinput(glideslope_inhibit) // not on backcourse
3807 && ! mk_data(radio_altitude).ncd
3808 && ! mk_data(glideslope_deviation_dots).ncd
3809 && (! mk->io_handler.conf.localizer_enabled
3810 || mk_data(localizer_deviation_dots).ncd
3811 || mk_data(radio_altitude).get() < 500
3812 || fabs(mk_data(localizer_deviation_dots).get()) < 2)
3813 && (! mk->state_handler.takeoff || mk->io_handler.flaps_down())
3814 && mk_dinput(landing_gear)
3815 && ! mk_doutput(glideslope_cancel))
3817 update_hard(is_hard());
3818 update_soft(is_soft(0));
3827 ///////////////////////////////////////////////////////////////////////////////
3828 // Mode6Handler ///////////////////////////////////////////////////////////////
3829 ///////////////////////////////////////////////////////////////////////////////
3831 // keep sorted in descending order
3832 const int MK_VIII::Mode6Handler::altitude_callout_definitions[] =
3833 { 1000, 500, 400, 300, 200, 100, 50, 40, 30, 20, 10 };
3836 MK_VIII::Mode6Handler::reset_minimums ()
3838 minimums_issued = false;
3842 MK_VIII::Mode6Handler::reset_altitude_callouts ()
3844 for (unsigned i = 0; i < n_altitude_callouts; i++)
3845 altitude_callouts_issued[i] = false;
3849 MK_VIII::Mode6Handler::is_playing_altitude_callout ()
3851 for (unsigned i = 0; i < n_altitude_callouts; i++)
3852 if (mk->voice_player.voice == mk_altitude_voice(i)
3853 || mk->voice_player.next_voice == mk_altitude_voice(i))
3860 MK_VIII::Mode6Handler::is_near_minimums (double callout)
3864 if (! mk_data(decision_height).ncd)
3866 double diff = callout - mk_data(decision_height).get();
3868 if (mk_data(radio_altitude).get() >= 200)
3870 if (fabs(diff) <= 30)
3875 if (diff >= -3 && diff <= 6)
3884 MK_VIII::Mode6Handler::is_outside_band (double elevation, double callout)
3887 return elevation < callout - (elevation > 150 ? 20 : 10);
3891 MK_VIII::Mode6Handler::inhibit_smart_500 ()
3895 if (! mk_data(glideslope_deviation_dots).ncd
3896 && ! mk_dinput(glideslope_inhibit) // backcourse
3897 && ! mk_doutput(glideslope_cancel))
3899 if (mk->io_handler.conf.localizer_enabled
3900 && ! mk_data(localizer_deviation_dots).ncd)
3902 if (fabs(mk_data(localizer_deviation_dots).get()) <= 2)
3907 if (fabs(mk_data(glideslope_deviation_dots).get()) <= 2)
3916 MK_VIII::Mode6Handler::boot ()
3918 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
3921 last_decision_height = mk_dinput(decision_height);
3922 last_radio_altitude.set(&mk_data(radio_altitude));
3925 for (unsigned i = 0; i < n_altitude_callouts; i++)
3926 altitude_callouts_issued[i] = ! mk_data(radio_altitude).ncd
3927 && mk_data(radio_altitude).get() <= altitude_callout_definitions[i];
3929 // extrapolated from [SPEC] 6.4.2
3930 minimums_issued = mk_dinput(decision_height);
3932 if (conf.above_field_voice)
3935 get_altitude_above_field(&last_altitude_above_field);
3936 // extrapolated from [SPEC] 6.4.2
3937 above_field_issued = ! last_altitude_above_field.ncd
3938 && last_altitude_above_field.get() < 550;
3943 MK_VIII::Mode6Handler::power_off ()
3945 runway_timer.stop();
3949 MK_VIII::Mode6Handler::enter_takeoff ()
3951 reset_altitude_callouts(); // [SPEC] 6.4.2
3952 reset_minimums(); // omitted by [SPEC]; common sense
3956 MK_VIII::Mode6Handler::leave_takeoff ()
3958 reset_altitude_callouts(); // [SPEC] 6.0.2
3959 reset_minimums(); // [SPEC] 6.0.2
3963 MK_VIII::Mode6Handler::set_volume (float volume)
3965 mk_voice(minimums_minimums)->set_volume(volume);
3966 mk_voice(five_hundred_above)->set_volume(volume);
3967 for (unsigned i = 0; i < n_altitude_callouts; i++)
3968 mk_altitude_voice(i)->set_volume(volume);
3972 MK_VIII::Mode6Handler::altitude_callouts_enabled ()
3974 if (conf.minimums_enabled || conf.smart_500_enabled || conf.above_field_voice)
3977 for (unsigned i = 0; i < n_altitude_callouts; i++)
3978 if (conf.altitude_callouts_enabled[i])
3985 MK_VIII::Mode6Handler::update_minimums ()
3987 if (! mk->io_handler.gpws_inhibit()
3988 && (mk->voice_player.voice == mk_voice(minimums_minimums)
3989 || mk->voice_player.next_voice == mk_voice(minimums_minimums)))
3992 if (! mk->io_handler.gpws_inhibit()
3993 && ! mk->state_handler.ground // [1]
3994 && conf.minimums_enabled
3995 && ! minimums_issued
3996 && mk_dinput(landing_gear)
3997 && mk_dinput(decision_height)
3998 && ! last_decision_height)
4000 minimums_issued = true;
4002 // If an altitude callout is playing, stop it now so that the
4003 // minimums callout can be played (note that proper connection
4004 // and synchronization of the radio-altitude ARINC 529 input,
4005 // decision-height discrete and decision-height ARINC 529 input
4006 // will prevent an altitude callout from being played near the
4007 // decision height).
4009 if (is_playing_altitude_callout())
4010 mk->voice_player.stop(VoicePlayer::STOP_NOW);
4012 mk_set_alerts(mk_alert(MODE6_MINIMUMS));
4015 mk_unset_alerts(mk_alert(MODE6_MINIMUMS));
4018 last_decision_height = mk_dinput(decision_height);
4022 MK_VIII::Mode6Handler::update_altitude_callouts ()
4024 if (! mk->io_handler.gpws_inhibit() && is_playing_altitude_callout())
4027 if (! mk->io_handler.gpws_inhibit()
4028 && ! mk->state_handler.ground // [1]
4029 && ! mk_data(radio_altitude).ncd)
4030 for (unsigned i = 0; i < n_altitude_callouts && mk_data(radio_altitude).get() <= altitude_callout_definitions[i]; i++)
4031 if ((conf.altitude_callouts_enabled[i]
4032 || (altitude_callout_definitions[i] == 500
4033 && conf.smart_500_enabled))
4034 && ! altitude_callouts_issued[i]
4035 && (last_radio_altitude.ncd
4036 || last_radio_altitude.get() > altitude_callout_definitions[i]))
4038 // lock out all callouts superior or equal to this one
4039 for (unsigned j = 0; j <= i; j++)
4040 altitude_callouts_issued[j] = true;
4042 altitude_callouts_issued[i] = true;
4043 if (! is_near_minimums(altitude_callout_definitions[i])
4044 && ! is_outside_band(mk_data(radio_altitude).get(), altitude_callout_definitions[i])
4045 && (! conf.smart_500_enabled
4046 || altitude_callout_definitions[i] != 500
4047 || ! inhibit_smart_500()))
4049 mk->alert_handler.set_altitude_callout_alert(mk_altitude_voice(i));
4054 mk_unset_alerts(mk_alert(MODE6_ALTITUDE_CALLOUT));
4057 last_radio_altitude.set(&mk_data(radio_altitude));
4061 MK_VIII::Mode6Handler::test_runway (const FGRunway *_runway)
4063 if (_runway->lengthFt() < mk->conf.runway_database)
4067 SGGeod::fromDeg(mk_data(gps_longitude).get(), mk_data(gps_latitude).get()));
4069 // get distance to threshold
4070 double distance, az1, az2;
4071 SGGeodesy::inverse(pos, _runway->threshold(), az1, az2, distance);
4072 return distance * SG_METER_TO_NM <= 5;
4076 MK_VIII::Mode6Handler::test_airport (const FGAirport *airport)
4078 for (unsigned int r=0; r<airport->numRunways(); ++r) {
4079 FGRunway* rwy(airport->getRunwayByIndex(r));
4081 if (test_runway(rwy)) return true;
4087 bool MK_VIII::Mode6Handler::AirportFilter::passAirport(FGAirport* a) const
4089 bool ok = self->test_airport(a);
4094 MK_VIII::Mode6Handler::update_runway ()
4097 if (mk_data(gps_latitude).ncd || mk_data(gps_longitude).ncd) {
4102 // Search for the closest runway threshold in range 5
4103 // nm. Passing 30nm to
4104 // get_closest_airport() provides enough margin for large
4105 // airports, which may have a runway located far away from the
4106 // airport's reference point.
4107 AirportFilter filter(this);
4108 FGPositionedRef apt = FGPositioned::findClosest(
4109 SGGeod::fromDeg(mk_data(gps_longitude).get(), mk_data(gps_latitude).get()),
4112 runway.elevation = apt->elevation();
4115 has_runway.set(apt != NULL);
4119 MK_VIII::Mode6Handler::get_altitude_above_field (Parameter<double> *parameter)
4121 if (! has_runway.ncd && has_runway.get() && ! mk_data(geometric_altitude).ncd)
4122 parameter->set(mk_data(geometric_altitude).get() - runway.elevation);
4128 MK_VIII::Mode6Handler::update_above_field_callout ()
4130 if (! conf.above_field_voice)
4133 // update_runway() has to iterate over the whole runway database
4134 // (which contains thousands of runways), so it would be unwise to
4135 // run it every frame. Run it every 3 seconds. Note that the first
4136 // iteration is run in boot().
4137 if (runway_timer.start_or_elapsed() >= 3)
4140 runway_timer.start();
4143 Parameter<double> altitude_above_field;
4144 get_altitude_above_field(&altitude_above_field);
4146 if (! mk->io_handler.gpws_inhibit()
4147 && (mk->voice_player.voice == conf.above_field_voice
4148 || mk->voice_player.next_voice == conf.above_field_voice))
4151 // handle reset term
4152 if (above_field_issued)
4154 if ((! has_runway.ncd && ! has_runway.get())
4155 || (! altitude_above_field.ncd && altitude_above_field.get() > 700))
4156 above_field_issued = false;
4159 if (! mk->io_handler.gpws_inhibit()
4160 && ! mk->state_handler.ground // [1]
4161 && ! above_field_issued
4162 && ! altitude_above_field.ncd
4163 && altitude_above_field.get() < 550
4164 && (last_altitude_above_field.ncd
4165 || last_altitude_above_field.get() >= 550))
4167 above_field_issued = true;
4169 if (! is_outside_band(altitude_above_field.get(), 500))
4171 mk->alert_handler.set_altitude_callout_alert(conf.above_field_voice);
4176 mk_unset_alerts(mk_alert(MODE6_ALTITUDE_CALLOUT));
4179 last_altitude_above_field.set(&altitude_above_field);
4183 MK_VIII::Mode6Handler::is_bank_angle (double abs_roll_angle, double bias)
4185 return conf.is_bank_angle(&mk_data(radio_altitude),
4186 abs_roll_angle - abs_roll_angle * bias,
4187 mk_dinput(autopilot_engaged));
4191 MK_VIII::Mode6Handler::is_high_bank_angle ()
4193 return mk_data(radio_altitude).ncd || mk_data(radio_altitude).get() >= 210;
4197 MK_VIII::Mode6Handler::get_bank_angle_alerts ()
4199 if (! mk->io_handler.gpws_inhibit()
4200 && ! mk->state_handler.ground // [1]
4201 && ! mk_data(roll_angle).ncd)
4203 double abs_roll_angle = fabs(mk_data(roll_angle).get());
4205 if (is_bank_angle(abs_roll_angle, 0.4))
4206 return is_high_bank_angle()
4207 ? (mk_alert(MODE6_HIGH_BANK_ANGLE_1) | mk_alert(MODE6_HIGH_BANK_ANGLE_2) | mk_alert(MODE6_HIGH_BANK_ANGLE_3))
4208 : (mk_alert(MODE6_LOW_BANK_ANGLE_1) | mk_alert(MODE6_LOW_BANK_ANGLE_2) | mk_alert(MODE6_LOW_BANK_ANGLE_3));
4209 else if (is_bank_angle(abs_roll_angle, 0.2))
4210 return is_high_bank_angle()
4211 ? (mk_alert(MODE6_HIGH_BANK_ANGLE_1) | mk_alert(MODE6_HIGH_BANK_ANGLE_2))
4212 : (mk_alert(MODE6_LOW_BANK_ANGLE_1) | mk_alert(MODE6_LOW_BANK_ANGLE_2));
4213 else if (is_bank_angle(abs_roll_angle, 0))
4214 return is_high_bank_angle()
4215 ? mk_alert(MODE6_HIGH_BANK_ANGLE_1)
4216 : mk_alert(MODE6_LOW_BANK_ANGLE_1);
4223 MK_VIII::Mode6Handler::update_bank_angle ()
4225 if (! conf.bank_angle_enabled)
4228 unsigned int alerts = get_bank_angle_alerts();
4230 // [SPEC] 6.4.4 specifies that the non-continuous alerts
4231 // (MODE6_*_BANK_ANGLE_1 and MODE6_*_BANK_ANGLE_2) are locked out
4232 // until the initial envelope is exited.
4234 if (! test_bits(alerts, mk_alert(MODE6_LOW_BANK_ANGLE_3)))
4235 mk_unset_alerts(mk_alert(MODE6_LOW_BANK_ANGLE_3));
4236 if (! test_bits(alerts, mk_alert(MODE6_HIGH_BANK_ANGLE_3)))
4237 mk_unset_alerts(mk_alert(MODE6_HIGH_BANK_ANGLE_3));
4240 mk_set_alerts(alerts);
4242 mk_unset_alerts(mk_alert(MODE6_LOW_BANK_ANGLE_1)
4243 | mk_alert(MODE6_HIGH_BANK_ANGLE_1)
4244 | mk_alert(MODE6_LOW_BANK_ANGLE_2)
4245 | mk_alert(MODE6_HIGH_BANK_ANGLE_2));
4249 MK_VIII::Mode6Handler::update ()
4251 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
4254 if (! mk->state_handler.takeoff
4255 && ! mk_data(radio_altitude).ncd
4256 && mk_data(radio_altitude).get() > 1000)
4258 reset_altitude_callouts(); // [SPEC] 6.4.2
4259 reset_minimums(); // common sense
4263 update_altitude_callouts();
4264 update_above_field_callout();
4265 update_bank_angle();
4268 ///////////////////////////////////////////////////////////////////////////////
4269 // TCFHandler /////////////////////////////////////////////////////////////////
4270 ///////////////////////////////////////////////////////////////////////////////
4272 // Gets the difference between the azimuth from @from_lat,@from_lon to
4273 // @to_lat,@to_lon, and @to_heading, in degrees.
4275 MK_VIII::TCFHandler::get_azimuth_difference (double from_lat,
4281 double az1, az2, distance;
4282 geo_inverse_wgs_84(0, from_lat, from_lon, to_lat, to_lon, &az1, &az2, &distance);
4283 return get_heading_difference(az1, to_heading);
4286 // Gets the difference between the azimuth from the current GPS
4287 // position to the center of @_runway, and the heading of @_runway, in
4290 MK_VIII::TCFHandler::get_azimuth_difference (const FGRunway *_runway)
4292 return get_azimuth_difference(mk_data(gps_latitude).get(),
4293 mk_data(gps_longitude).get(),
4294 _runway->latitude(),
4295 _runway->longitude(),
4296 _runway->headingDeg());
4299 // Selects the most likely intended destination runway of @airport,
4300 // and returns it in @_runway. For each runway, the difference between
4301 // the azimuth from the current GPS position to the center of the
4302 // runway and its heading is computed. The runway having the smallest
4305 // This selection algorithm is not specified in [SPEC], but
4306 // http://www.egpws.com/general_information/description/runway_select.htm
4307 // talks about automatic runway selection.
4309 MK_VIII::TCFHandler::select_runway (const FGAirport *airport)
4311 FGRunway* _runway = 0;
4312 double min_diff = 360;
4314 for (unsigned int r=0; r<airport->numRunways(); ++r) {
4315 FGRunway* rwy(airport->getRunwayByIndex(r));
4316 double diff = get_azimuth_difference(rwy);
4317 if (diff < min_diff)
4322 } // of airport runways iteration
4326 bool MK_VIII::TCFHandler::AirportFilter::passAirport(FGAirport* aApt) const
4328 return aApt->hasHardRunwayOfLengthFt(mk->conf.runway_database);
4332 MK_VIII::TCFHandler::update_runway ()
4335 if (mk_data(gps_latitude).ncd || mk_data(gps_longitude).ncd) {
4339 // Search for the intended destination runway of the closest
4340 // airport in range 15 nm. Passing 30nm to get_closest_airport()
4341 // provides enough margin for
4342 // large airports, which may have a runway located far away from
4343 // the airport's reference point.
4344 AirportFilter filter(mk);
4345 FGAirport* apt = FGAirport::findClosest(
4346 SGGeod::fromDeg(mk_data(gps_longitude).get(), mk_data(gps_latitude).get()),
4351 FGRunway* _runway = select_runway(apt);
4353 if (!_runway) return;
4357 runway.center.latitude = _runway->latitude();
4358 runway.center.longitude = _runway->longitude();
4360 runway.elevation = apt->elevation();
4362 double half_length_m = _runway->lengthM() * 0.5;
4363 runway.half_length = half_length_m * SG_METER_TO_NM;
4365 // b3 ________________ b0
4367 // h1>>> | e1<<<<<<<<e0 | <<<h0
4368 // |________________|
4371 // get heading to runway threshold (h0) and end (h1)
4372 runway.edges[0].heading = _runway->headingDeg();
4373 runway.edges[1].heading = get_reciprocal_heading(_runway->headingDeg());
4377 // get position of runway threshold (e0)
4378 geo_direct_wgs_84(0,
4379 runway.center.latitude,
4380 runway.center.longitude,
4381 runway.edges[1].heading,
4383 &runway.edges[0].position.latitude,
4384 &runway.edges[0].position.longitude,
4387 // get position of runway end (e1)
4388 geo_direct_wgs_84(0,
4389 runway.center.latitude,
4390 runway.center.longitude,
4391 runway.edges[0].heading,
4393 &runway.edges[1].position.latitude,
4394 &runway.edges[1].position.longitude,
4397 double half_width_m = _runway->widthM() * 0.5;
4399 // get position of threshold bias area edges (b0 and b1)
4400 get_bias_area_edges(&runway.edges[0].position,
4401 runway.edges[1].heading,
4403 &runway.bias_area[0],
4404 &runway.bias_area[1]);
4406 // get position of end bias area edges (b2 and b3)
4407 get_bias_area_edges(&runway.edges[1].position,
4408 runway.edges[0].heading,
4410 &runway.bias_area[2],
4411 &runway.bias_area[3]);
4415 MK_VIII::TCFHandler::get_bias_area_edges (Position *edge,
4417 double half_width_m,
4418 Position *bias_edge1,
4419 Position *bias_edge2)
4421 double half_bias_width_m = k * SG_NM_TO_METER + half_width_m;
4422 double tmp_latitude = 0.0, tmp_longitude = 0.0, az = 0.0;
4424 geo_direct_wgs_84(0,
4432 geo_direct_wgs_84(0,
4435 heading_substract(reciprocal, 90),
4437 &bias_edge1->latitude,
4438 &bias_edge1->longitude,
4440 geo_direct_wgs_84(0,
4443 heading_add(reciprocal, 90),
4445 &bias_edge2->latitude,
4446 &bias_edge2->longitude,
4450 // Returns true if the current GPS position is inside the edge
4451 // triangle of @edge. The edge triangle, which is specified and
4452 // represented in [SPEC] 6.3.1, is defined as an isocele right
4453 // triangle of infinite height, whose right angle is located at the
4454 // position of @edge, and whose height is parallel to the heading of
4457 MK_VIII::TCFHandler::is_inside_edge_triangle (RunwayEdge *edge)
4459 return get_azimuth_difference(mk_data(gps_latitude).get(),
4460 mk_data(gps_longitude).get(),
4461 edge->position.latitude,
4462 edge->position.longitude,
4463 edge->heading) <= 45;
4466 // Returns true if the current GPS position is inside the bias area of
4467 // the currently selected runway.
4469 MK_VIII::TCFHandler::is_inside_bias_area ()
4472 double angles_sum = 0;
4474 for (int i = 0; i < 4; i++)
4476 double az2, distance;
4477 geo_inverse_wgs_84(0,
4478 mk_data(gps_latitude).get(),
4479 mk_data(gps_longitude).get(),
4480 runway.bias_area[i].latitude,
4481 runway.bias_area[i].longitude,
4482 &az1[i], &az2, &distance);
4485 for (int i = 0; i < 4; i++)
4487 double angle = az1[i == 3 ? 0 : i + 1] - az1[i];
4491 angles_sum += angle;
4494 return angles_sum > 180;
4498 MK_VIII::TCFHandler::is_tcf ()
4500 if (mk_data(radio_altitude).get() > 10)
4504 double distance, az1, az2;
4506 geo_inverse_wgs_84(0,
4507 mk_data(gps_latitude).get(),
4508 mk_data(gps_longitude).get(),
4509 runway.center.latitude,
4510 runway.center.longitude,
4511 &az1, &az2, &distance);
4513 distance *= SG_METER_TO_NM;
4515 // distance to the inner envelope edge
4516 double edge_distance = distance - runway.half_length - k;
4518 if (edge_distance >= 15)
4520 if (mk_data(radio_altitude).get() < 700)
4523 else if (edge_distance >= 12)
4525 if (mk_data(radio_altitude).get() < 100 * edge_distance - 800)
4528 else if (edge_distance >= 4)
4530 if (mk_data(radio_altitude).get() < 400)
4533 else if (edge_distance >= 2.45)
4535 if (mk_data(radio_altitude).get() < 66.666667 * edge_distance + 133.33333)
4540 if (is_inside_edge_triangle(&runway.edges[0]) || is_inside_edge_triangle(&runway.edges[1]))
4542 if (edge_distance >= 1)
4544 if (mk_data(radio_altitude).get() < 66.666667 * edge_distance + 133.33333)
4547 else if (edge_distance >= 0.05)
4549 if (mk_data(radio_altitude).get() < 200 * edge_distance)
4555 if (! is_inside_bias_area())
4557 if (mk_data(radio_altitude).get() < 245)
4565 if (mk_data(radio_altitude).get() < 700)
4574 MK_VIII::TCFHandler::is_rfcf ()
4578 double distance, az1, az2;
4579 geo_inverse_wgs_84(0,
4580 mk_data(gps_latitude).get(),
4581 mk_data(gps_longitude).get(),
4582 runway.center.latitude,
4583 runway.center.longitude,
4584 &az1, &az2, &distance);
4586 double krf = k + mk_data(gps_vertical_figure_of_merit).get() / 200;
4587 distance = distance * SG_METER_TO_NM - runway.half_length - krf;
4591 double altitude_above_field = mk_data(geometric_altitude).get() - runway.elevation;
4593 if (distance >= 1.5)
4595 if (altitude_above_field < 300)
4598 else if (distance >= 0)
4600 if (altitude_above_field < 200 * distance)
4610 MK_VIII::TCFHandler::update ()
4612 if (mk->configuration_module.state != ConfigurationModule::STATE_OK || ! conf.enabled)
4615 // update_runway() has to iterate over the whole runway database
4616 // (which contains thousands of runways), so it would be unwise to
4617 // run it every frame. Run it every 3 seconds.
4618 if (! runway_timer.running || runway_timer.elapsed() >= 3)
4621 runway_timer.start();
4624 if (! mk_dinput(ta_tcf_inhibit)
4625 && ! mk->state_handler.ground // [1]
4626 && ! mk_data(gps_latitude).ncd
4627 && ! mk_data(gps_longitude).ncd
4628 && ! mk_data(radio_altitude).ncd
4629 && ! mk_data(geometric_altitude).ncd
4630 && ! mk_data(gps_vertical_figure_of_merit).ncd)
4635 _reference = mk_data(radio_altitude).get_pointer();
4637 _reference = mk_data(geometric_altitude).get_pointer();
4643 if (mk_test_alert(TCF_TOO_LOW_TERRAIN))
4645 double new_bias = bias;
4646 // do not repeat terrain alerts below 30ft agl
4647 if (mk_data(radio_altitude).get() > 30)
4649 if (new_bias < 0.0) // sanity check
4651 while ((*reference < initial_value - initial_value * new_bias)&&
4656 if (new_bias > bias)
4659 mk_repeat_alert(mk_alert(TCF_TOO_LOW_TERRAIN));
4665 reference = _reference;
4666 initial_value = *reference;
4667 mk_set_alerts(mk_alert(TCF_TOO_LOW_TERRAIN));
4674 mk_unset_alerts(mk_alert(TCF_TOO_LOW_TERRAIN));
4677 ///////////////////////////////////////////////////////////////////////////////
4678 // MK_VIII ////////////////////////////////////////////////////////////////////
4679 ///////////////////////////////////////////////////////////////////////////////
4681 MK_VIII::MK_VIII (SGPropertyNode *node)
4682 : properties_handler(this),
4685 power_handler(this),
4686 system_handler(this),
4687 configuration_module(this),
4688 fault_handler(this),
4691 self_test_handler(this),
4692 alert_handler(this),
4693 state_handler(this),
4694 mode1_handler(this),
4695 mode2_handler(this),
4696 mode3_handler(this),
4697 mode4_handler(this),
4698 mode5_handler(this),
4699 mode6_handler(this),
4702 for (int i = 0; i < node->nChildren(); ++i)
4704 SGPropertyNode *child = node->getChild(i);
4705 string cname = child->getName();
4706 string cval = child->getStringValue();
4708 if (cname == "name")
4710 else if (cname == "number")
4711 num = child->getIntValue();
4714 SG_LOG(SG_INSTR, SG_WARN, "Error in mk-viii config logic");
4716 SG_LOG(SG_INSTR, SG_WARN, "Section = " << name);
4724 properties_handler.init();
4725 voice_player.init();
4731 SGPropertyNode *node = fgGetNode(("/instrumentation/" + name).c_str(), num, true);
4733 configuration_module.bind(node);
4734 power_handler.bind(node);
4735 io_handler.bind(node);
4736 voice_player.bind(node, "Sounds/mk-viii/");
4742 properties_handler.unbind();
4746 MK_VIII::update (double dt)
4748 power_handler.update();
4749 system_handler.update();
4751 if (system_handler.state != SystemHandler::STATE_ON)
4754 io_handler.update_inputs();
4755 io_handler.update_input_faults();
4757 voice_player.update();
4758 state_handler.update();
4760 if (self_test_handler.update())
4763 io_handler.update_internal_latches();
4764 io_handler.update_lamps();
4766 mode1_handler.update();
4767 mode2_handler.update();
4768 mode3_handler.update();
4769 mode4_handler.update();
4770 mode5_handler.update();
4771 mode6_handler.update();
4772 tcf_handler.update();
4774 alert_handler.update();
4775 io_handler.update_outputs();