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 # include <Include/version.h>
82 #include <Main/fg_props.hxx>
83 #include <Main/globals.hxx>
84 #include "instrument_mgr.hxx"
85 #include "mk_viii.hxx"
87 ///////////////////////////////////////////////////////////////////////////////
88 // constants //////////////////////////////////////////////////////////////////
89 ///////////////////////////////////////////////////////////////////////////////
91 #define GLIDESLOPE_DOTS_TO_DDM 0.0875 // [INSTALL] 6.2.30
92 #define GLIDESLOPE_DDM_TO_DOTS (1 / GLIDESLOPE_DOTS_TO_DDM)
94 #define LOCALIZER_DOTS_TO_DDM 0.0775 // [INSTALL] 6.2.33
95 #define LOCALIZER_DDM_TO_DOTS (1 / LOCALIZER_DOTS_TO_DDM)
97 ///////////////////////////////////////////////////////////////////////////////
98 // helpers ////////////////////////////////////////////////////////////////////
99 ///////////////////////////////////////////////////////////////////////////////
101 #define assert_not_reached() assert(false)
102 #define n_elements(_array) (sizeof(_array) / sizeof((_array)[0]))
103 #define test_bits(_bits, _test) (((_bits) & (_test)) != 0)
105 #define mk_node(_name) (mk->properties_handler.external_properties._name)
107 #define mk_dinput_feed(_name) (mk->io_handler.input_feeders.discretes._name)
108 #define mk_dinput(_name) (mk->io_handler.inputs.discretes._name)
109 #define mk_ainput_feed(_name) (mk->io_handler.input_feeders.arinc429._name)
110 #define mk_ainput(_name) (mk->io_handler.inputs.arinc429._name)
111 #define mk_doutput(_name) (mk->io_handler.outputs.discretes._name)
112 #define mk_aoutput(_name) (mk->io_handler.outputs.arinc429._name)
113 #define mk_data(_name) (mk->io_handler.data._name)
115 #define mk_voice(_name) (mk->voice_player.voices._name)
116 #define mk_altitude_voice(_i) (mk->voice_player.voices.altitude_callouts[(_i)])
118 #define mk_alert(_name) (AlertHandler::ALERT_ ## _name)
119 #define mk_alert_flag(_name) (AlertHandler::ALERT_FLAG_ ## _name)
120 #define mk_set_alerts (mk->alert_handler.set_alerts)
121 #define mk_unset_alerts (mk->alert_handler.unset_alerts)
122 #define mk_repeat_alert (mk->alert_handler.repeat_alert)
123 #define mk_test_alert(_name) (mk_test_alerts(mk_alert(_name)))
124 #define mk_test_alerts(_test) (test_bits(mk->alert_handler.alerts, (_test)))
126 const double MK_VIII::TCFHandler::k = 0.25;
129 modify_amplitude (double amplitude, double dB)
131 return amplitude * pow(10.0, dB / 20.0);
135 heading_add (double h1, double h2)
137 double result = h1 + h2;
144 heading_substract (double h1, double h2)
146 double result = h1 - h2;
153 get_heading_difference (double h1, double h2)
155 double diff = h1 - h2;
166 get_reciprocal_heading (double h)
168 return heading_add(h, 180);
171 ///////////////////////////////////////////////////////////////////////////////
172 // PropertiesHandler //////////////////////////////////////////////////////////
173 ///////////////////////////////////////////////////////////////////////////////
176 MK_VIII::PropertiesHandler::init ()
178 mk_node(ai_caged) = fgGetNode("/instrumentation/attitude-indicator/caged-flag", true);
179 mk_node(ai_roll) = fgGetNode("/instrumentation/attitude-indicator/indicated-roll-deg", true);
180 mk_node(ai_serviceable) = fgGetNode("/instrumentation/attitude-indicator/serviceable", true);
181 mk_node(altimeter_altitude) = fgGetNode("/instrumentation/altimeter/indicated-altitude-ft", true);
182 mk_node(altimeter_serviceable) = fgGetNode("/instrumentation/altimeter/serviceable", true);
183 mk_node(altitude) = fgGetNode("/position/altitude-ft", true);
184 mk_node(altitude_agl) = fgGetNode("/position/altitude-agl-ft", true);
185 mk_node(asi_serviceable) = fgGetNode("/instrumentation/airspeed-indicator/serviceable", true);
186 mk_node(asi_speed) = fgGetNode("/instrumentation/airspeed-indicator/indicated-speed-kt", true);
187 mk_node(autopilot_heading_lock) = fgGetNode("/autopilot/locks/heading", true);
188 mk_node(flaps) = fgGetNode("/controls/flight/flaps", true);
189 mk_node(gear_down) = fgGetNode("/controls/gear/gear-down", true);
190 mk_node(latitude) = fgGetNode("/position/latitude-deg", true);
191 mk_node(longitude) = fgGetNode("/position/longitude-deg", true);
192 mk_node(nav0_cdi_serviceable) = fgGetNode("/instrumentation/nav/cdi/serviceable", true);
193 mk_node(nav0_gs_distance) = fgGetNode("/instrumentation/nav/gs-distance", true);
194 mk_node(nav0_gs_needle_deflection) = fgGetNode("/instrumentation/nav/gs-needle-deflection", true);
195 mk_node(nav0_gs_serviceable) = fgGetNode("/instrumentation/nav/gs/serviceable", true);
196 mk_node(nav0_has_gs) = fgGetNode("/instrumentation/nav/has-gs", true);
197 mk_node(nav0_heading_needle_deflection) = fgGetNode("/instrumentation/nav/heading-needle-deflection", true);
198 mk_node(nav0_in_range) = fgGetNode("/instrumentation/nav/in-range", true);
199 mk_node(nav0_nav_loc) = fgGetNode("/instrumentation/nav/nav-loc", true);
200 mk_node(nav0_serviceable) = fgGetNode("/instrumentation/nav/serviceable", true);
201 mk_node(power) = fgGetNode(("/systems/electrical/outputs/" + mk->name).c_str(), mk->num, true);
202 mk_node(replay_state) = fgGetNode("/sim/freeze/replay-state", true);
203 mk_node(vs) = fgGetNode("/velocities/vertical-speed-fps", true);
207 MK_VIII::PropertiesHandler::unbind ()
209 vector<SGPropertyNode_ptr>::iterator iter;
211 for (iter = tied_properties.begin(); iter != tied_properties.end(); iter++)
214 tied_properties.clear();
217 ///////////////////////////////////////////////////////////////////////////////
218 // PowerHandler ///////////////////////////////////////////////////////////////
219 ///////////////////////////////////////////////////////////////////////////////
222 MK_VIII::PowerHandler::bind (SGPropertyNode *node)
224 mk->properties_handler.tie(node, "serviceable", SGRawValuePointer<bool>(&serviceable));
228 MK_VIII::PowerHandler::handle_abnormal_voltage (bool abnormal,
234 if (timer->start_or_elapsed() >= max_duration)
235 return true; // power loss
244 MK_VIII::PowerHandler::update ()
246 double voltage = mk_node(power)->getDoubleValue();
247 bool now_powered = true;
255 if (handle_abnormal_voltage(voltage < 20.5, &low_surge_timer, 0.03))
257 if (handle_abnormal_voltage(voltage < 22.0 || voltage > 30.3, &abnormal_timer, 300))
259 if (handle_abnormal_voltage(voltage > 32.2, &high_surge_timer, 1))
261 if (handle_abnormal_voltage(voltage > 37.8, &very_high_surge_timer, 0.1))
271 power_loss_timer.stop();
274 if (power_loss_timer.start_or_elapsed() >= 0.2)
286 MK_VIII::PowerHandler::power_on ()
289 mk->system_handler.power_on();
293 MK_VIII::PowerHandler::power_off ()
296 mk->system_handler.power_off();
297 mk->voice_player.stop(VoicePlayer::STOP_NOW);
298 mk->self_test_handler.power_off(); // run before IOHandler::power_off()
299 mk->io_handler.power_off();
300 mk->mode2_handler.power_off();
301 mk->mode6_handler.power_off();
304 ///////////////////////////////////////////////////////////////////////////////
305 // SystemHandler //////////////////////////////////////////////////////////////
306 ///////////////////////////////////////////////////////////////////////////////
309 MK_VIII::SystemHandler::power_on ()
311 state = STATE_BOOTING;
313 // [SPEC] 3.5.2 mentions a 20 seconds maximum boot delay. We use a
314 // random delay between 3 and 5 seconds.
316 boot_delay = 3 + sg_random() * 2;
321 MK_VIII::SystemHandler::power_off ()
329 MK_VIII::SystemHandler::update ()
331 if (state == STATE_BOOTING)
333 if (boot_timer.elapsed() >= boot_delay)
335 last_replay_state = mk_node(replay_state)->getIntValue();
337 mk->configuration_module.boot();
339 mk->io_handler.boot();
340 mk->fault_handler.boot();
341 mk->alert_handler.boot();
343 // inputs are needed by the following boot() routines
344 mk->io_handler.update_inputs();
346 mk->mode2_handler.boot();
347 mk->mode6_handler.boot();
351 mk->io_handler.post_boot();
354 else if (state != STATE_OFF && mk->configuration_module.state == ConfigurationModule::STATE_OK)
356 // If the replay state changes, switch to reposition mode for 3
357 // seconds ([SPEC] 6.0.5) to avoid spurious alerts.
359 int replay_state = mk_node(replay_state)->getIntValue();
360 if (replay_state != last_replay_state)
362 mk->alert_handler.reposition();
364 last_replay_state = replay_state;
365 state = STATE_REPOSITION;
366 reposition_timer.start();
369 if (state == STATE_REPOSITION && reposition_timer.elapsed() >= 3)
371 // inputs are needed by StateHandler::post_reposition()
372 mk->io_handler.update_inputs();
374 mk->state_handler.post_reposition();
381 ///////////////////////////////////////////////////////////////////////////////
382 // ConfigurationModule ////////////////////////////////////////////////////////
383 ///////////////////////////////////////////////////////////////////////////////
385 MK_VIII::ConfigurationModule::ConfigurationModule (MK_VIII *device)
388 // arbitrary defaults
389 categories[CATEGORY_AIRCRAFT_MODE_TYPE_SELECT] = 0;
390 categories[CATEGORY_AIR_DATA_INPUT_SELECT] = 1;
391 categories[CATEGORY_POSITION_INPUT_SELECT] = 2;
392 categories[CATEGORY_ALTITUDE_CALLOUTS] = 13;
393 categories[CATEGORY_AUDIO_MENU_SELECT] = 0;
394 categories[CATEGORY_TERRAIN_DISPLAY_SELECT] = 1;
395 categories[CATEGORY_OPTIONS_SELECT_GROUP_1] = 124;
396 categories[CATEGORY_RADIO_ALTITUDE_INPUT_SELECT] = 2;
397 categories[CATEGORY_NAVIGATION_INPUT_SELECT] = 3;
398 categories[CATEGORY_ATTITUDE_INPUT_SELECT] = 6;
399 categories[CATEGORY_HEADING_INPUT_SELECT] = 2;
400 categories[CATEGORY_WINDSHEAR_INPUT_SELECT] = 0;
401 categories[CATEGORY_INPUT_OUTPUT_DISCRETE_TYPE_SELECT] = 7;
402 categories[CATEGORY_AUDIO_OUTPUT_LEVEL] = 0;
403 categories[CATEGORY_UNDEFINED_INPUT_SELECT_1] = 0;
404 categories[CATEGORY_UNDEFINED_INPUT_SELECT_2] = 0;
405 categories[CATEGORY_UNDEFINED_INPUT_SELECT_3] = 0;
408 static double m1_t1_min_agl1 (double vs) { return -1620 - 1.1133 * vs; }
409 static double m1_t1_min_agl2 (double vs) { return -400 - 0.4 * vs; }
410 static double m1_t4_min_agl1 (double vs) { return -1625.47 - 1.1167 * vs; }
411 static double m1_t4_min_agl2 (double vs) { return -0.1958 * vs; }
413 static int m3_t1_max_agl (bool flap_override) { return 1500; }
414 static double m3_t1_max_alt_loss (bool flap_override, double agl) { return 5.4 + 0.092 * agl; }
415 static int m3_t2_max_agl (bool flap_override) { return flap_override ? 815 : 925; }
416 static double m3_t2_max_alt_loss (bool flap_override, double agl)
418 int bias = agl > 700 ? 5 : 0;
421 return (9.0 + 0.184 * agl) + bias;
423 return (5.4 + 0.092 * agl) + bias;
426 static double m4_t1_min_agl2 (double airspeed) { return -1083 + 8.333 * airspeed; }
427 static double m4_t568_a_min_agl2 (double airspeed) { return -1523 + 11.36 * airspeed; }
428 static double m4_t79_a_min_agl2 (double airspeed) { return -1182 + 11.36 * airspeed; }
429 static double m4_t568_b_min_agl2 (double airspeed) { return -1570 + 11.6 * airspeed; }
430 static double m4_t79_b_min_agl2 (double airspeed) { return -1222 + 11.6 * airspeed; }
433 MK_VIII::ConfigurationModule::m6_t2_is_bank_angle (Parameter<double> *agl,
439 if (agl->ncd || agl->get() > 122)
440 return abs_roll_deg > 33;
444 if (agl->ncd || agl->get() > 2450)
445 return abs_roll_deg > 55;
446 else if (agl->get() > 150)
447 return agl->get() < 153.33333 * abs_roll_deg - 5983.3333;
451 return agl->get() < 4 * abs_roll_deg - 10;
452 else if (agl->get() > 5)
453 return abs_roll_deg > 10;
459 MK_VIII::ConfigurationModule::m6_t4_is_bank_angle (Parameter<double> *agl,
465 if (agl->ncd || agl->get() > 156)
466 return abs_roll_deg > 33;
470 if (agl->ncd || agl->get() > 210)
471 return abs_roll_deg > 50;
475 return agl->get() < 5.7142857 * abs_roll_deg - 75.714286;
481 MK_VIII::ConfigurationModule::read_aircraft_mode_type_select (int value)
483 static const Mode1Handler::EnvelopesConfiguration m1_t1 =
484 { false, 10, m1_t1_min_agl1, 284, m1_t1_min_agl2, 2450 };
485 static const Mode1Handler::EnvelopesConfiguration m1_t4 =
486 { true, 50, m1_t4_min_agl1, 346, m1_t4_min_agl2, 1958 };
488 static const Mode2Handler::Configuration m2_t1 = { 190, 280 };
489 static const Mode2Handler::Configuration m2_t4 = { 220, 310 };
490 static const Mode2Handler::Configuration m2_t5 = { 220, 310 };
492 static const Mode3Handler::Configuration m3_t1 =
493 { 30, m3_t1_max_agl, m3_t1_max_alt_loss };
494 static const Mode3Handler::Configuration m3_t2 =
495 { 50, m3_t2_max_agl, m3_t2_max_alt_loss };
497 static const Mode4Handler::EnvelopesConfiguration m4_t1_ac =
498 { 190, 250, 500, m4_t1_min_agl2, 1000 };
499 static const Mode4Handler::EnvelopesConfiguration m4_t5_ac =
500 { 178, 200, 500, m4_t568_a_min_agl2, 1000 };
501 static const Mode4Handler::EnvelopesConfiguration m4_t68_ac =
502 { 178, 200, 500, m4_t568_a_min_agl2, 750 };
503 static const Mode4Handler::EnvelopesConfiguration m4_t79_ac =
504 { 148, 170, 500, m4_t79_a_min_agl2, 750 };
506 static const Mode4Handler::EnvelopesConfiguration m4_t1_b =
507 { 159, 250, 245, m4_t1_min_agl2, 1000 };
508 static const Mode4Handler::EnvelopesConfiguration m4_t5_b =
509 { 148, 200, 200, m4_t568_b_min_agl2, 1000 };
510 static const Mode4Handler::EnvelopesConfiguration m4_t6_b =
511 { 150, 200, 170, m4_t568_b_min_agl2, 750 };
512 static const Mode4Handler::EnvelopesConfiguration m4_t7_b =
513 { 120, 170, 170, m4_t79_b_min_agl2, 750 };
514 static const Mode4Handler::EnvelopesConfiguration m4_t8_b =
515 { 148, 200, 150, m4_t568_b_min_agl2, 750 };
516 static const Mode4Handler::EnvelopesConfiguration m4_t9_b =
517 { 118, 170, 150, m4_t79_b_min_agl2, 750 };
519 static const Mode4Handler::ModesConfiguration m4_t1 = { &m4_t1_ac, &m4_t1_b };
520 static const Mode4Handler::ModesConfiguration m4_t5 = { &m4_t5_ac, &m4_t5_b };
521 static const Mode4Handler::ModesConfiguration m4_t6 = { &m4_t68_ac, &m4_t6_b };
522 static const Mode4Handler::ModesConfiguration m4_t7 = { &m4_t79_ac, &m4_t7_b };
523 static const Mode4Handler::ModesConfiguration m4_t8 = { &m4_t68_ac, &m4_t8_b };
524 static const Mode4Handler::ModesConfiguration m4_t9 = { &m4_t79_ac, &m4_t9_b };
526 static Mode6Handler::BankAnglePredicate m6_t2 = m6_t2_is_bank_angle;
527 static Mode6Handler::BankAnglePredicate m6_t4 = m6_t4_is_bank_angle;
529 static const IOHandler::FaultsConfiguration f_slow = { 180, 200 };
530 static const IOHandler::FaultsConfiguration f_fast = { 250, 290 };
535 const Mode1Handler::EnvelopesConfiguration *m1;
536 const Mode2Handler::Configuration *m2;
537 const Mode3Handler::Configuration *m3;
538 const Mode4Handler::ModesConfiguration *m4;
539 Mode6Handler::BankAnglePredicate m6;
540 const IOHandler::FaultsConfiguration *f;
542 } aircraft_types[] = {
543 { 0, &m1_t4, &m2_t4, &m3_t2, &m4_t6, m6_t4, &f_fast, 2000 },
544 { 1, &m1_t4, &m2_t4, &m3_t2, &m4_t8, m6_t4, &f_fast, 2000 },
545 { 2, &m1_t4, &m2_t4, &m3_t2, &m4_t5, m6_t4, &f_fast, 2000 },
546 { 3, &m1_t4, &m2_t5, &m3_t2, &m4_t7, m6_t4, &f_slow, 2000 },
547 { 4, &m1_t4, &m2_t5, &m3_t2, &m4_t9, m6_t4, &f_slow, 2000 },
548 { 254, &m1_t1, &m2_t1, &m3_t1, &m4_t1, m6_t2, &f_fast, 3500 },
549 { 255, &m1_t1, &m2_t1, &m3_t1, &m4_t1, m6_t2, &f_fast, 2000 }
552 for (size_t i = 0; i < n_elements(aircraft_types); i++)
553 if (aircraft_types[i].type == value)
555 mk->mode1_handler.conf.envelopes = aircraft_types[i].m1;
556 mk->mode2_handler.conf = aircraft_types[i].m2;
557 mk->mode3_handler.conf = aircraft_types[i].m3;
558 mk->mode4_handler.conf.modes = aircraft_types[i].m4;
559 mk->mode6_handler.conf.is_bank_angle = aircraft_types[i].m6;
560 mk->io_handler.conf.faults = aircraft_types[i].f;
561 mk->conf.runway_database = aircraft_types[i].runway_database;
565 state = STATE_INVALID_AIRCRAFT_TYPE;
570 MK_VIII::ConfigurationModule::read_air_data_input_select (int value)
573 return (value >= 0 && value <= 6) || (value >= 10 && value <= 13) || value == 255;
577 MK_VIII::ConfigurationModule::read_position_input_select (int value)
580 mk->io_handler.conf.use_internal_gps = true;
581 else if ((value >= 0 && value <= 5)
582 || (value >= 10 && value <= 13)
585 mk->io_handler.conf.use_internal_gps = false;
593 MK_VIII::ConfigurationModule::read_altitude_callouts (int value)
608 { 0, { MINIMUMS, SMART_500, 200, 100, 50, 40, 30, 20, 10, 0 } },
609 { 1, { MINIMUMS, SMART_500, 200, 0 } },
610 { 2, { MINIMUMS, SMART_500, 100, 50, 40, 30, 20, 10, 0 } },
611 { 3, { MINIMUMS, SMART_500, 0 } },
612 { 4, { MINIMUMS, 200, 100, 50, 40, 30, 20, 10, 0 } },
613 { 5, { MINIMUMS, 200, 0 } },
614 { 6, { MINIMUMS, 100, 50, 40, 30, 20, 10, 0 } },
616 { 8, { MINIMUMS, 0 } },
617 { 9, { MINIMUMS, 500, 200, 100, 50, 40, 30, 20, 10, 0 } },
618 { 10, { MINIMUMS, 500, 200, 0 } },
619 { 11, { MINIMUMS, 500, 100, 50, 40, 30, 20, 10, 0 } },
620 { 12, { MINIMUMS, 500, 0 } },
621 { 13, { MINIMUMS, 1000, 500, 400, 300, 200, 100, 50, 40, 30, 20, 10, 0 } },
622 { 14, { MINIMUMS, 100, 0 } },
623 { 15, { MINIMUMS, 200, 100, 0 } },
624 { 100, { FIELD_500, 0 } },
625 { 101, { FIELD_500_ABOVE, 0 } }
630 mk->mode6_handler.conf.minimums_enabled = false;
631 mk->mode6_handler.conf.smart_500_enabled = false;
632 mk->mode6_handler.conf.above_field_voice = NULL;
633 for (i = 0; i < n_altitude_callouts; i++)
634 mk->mode6_handler.conf.altitude_callouts_enabled[i] = false;
636 for (i = 0; i < n_elements(values); i++)
637 if (values[i].id == value)
639 for (int j = 0; values[i].callouts[j] != 0; j++)
640 switch (values[i].callouts[j])
643 mk->mode6_handler.conf.minimums_enabled = true;
647 mk->mode6_handler.conf.smart_500_enabled = true;
651 mk->mode6_handler.conf.above_field_voice = mk_altitude_voice(Mode6Handler::ALTITUDE_CALLOUT_500);
654 case FIELD_500_ABOVE:
655 mk->mode6_handler.conf.above_field_voice = mk_voice(five_hundred_above);
659 for (unsigned k = 0; k < n_altitude_callouts; k++)
660 if (mk->mode6_handler.altitude_callout_definitions[k] == values[i].callouts[j])
661 mk->mode6_handler.conf.altitude_callouts_enabled[k] = true;
672 MK_VIII::ConfigurationModule::read_audio_menu_select (int value)
674 if (value == 0 || value == 1)
675 mk->mode4_handler.conf.voice_too_low_gear = mk_voice(too_low_gear);
676 else if (value == 2 || value == 3)
677 mk->mode4_handler.conf.voice_too_low_gear = mk_voice(too_low_flaps);
685 MK_VIII::ConfigurationModule::read_terrain_display_select (int value)
688 mk->tcf_handler.conf.enabled = false;
689 else if (value == 0 || value == 1 || (value >= 3 && value <= 15)
690 || (value >= 18 && value <= 20) || (value >= 235 && value <= 255))
691 mk->tcf_handler.conf.enabled = true;
699 MK_VIII::ConfigurationModule::read_options_select_group_1 (int value)
701 if (value >= 0 && value < 128)
703 mk->io_handler.conf.flap_reversal = test_bits(value, 1 << 1);
704 mk->mode6_handler.conf.bank_angle_enabled = test_bits(value, 1 << 2);
705 mk->io_handler.conf.steep_approach_enabled = test_bits(value, 1 << 6);
713 MK_VIII::ConfigurationModule::read_radio_altitude_input_select (int value)
716 return (value >= 0 && value <= 4) || (value >= 251 && value <= 255);
720 MK_VIII::ConfigurationModule::read_navigation_input_select (int value)
722 if (value >= 0 && value <= 2)
723 mk->io_handler.conf.localizer_enabled = false;
724 else if ((value >= 3 && value <= 5) || (value >= 250 && value <= 255))
725 mk->io_handler.conf.localizer_enabled = true;
733 MK_VIII::ConfigurationModule::read_attitude_input_select (int value)
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 samples_type::iterator iter;
984 // remove samples older than 15 seconds
985 for (iter = samples.begin(); iter != samples.end() && globals->get_sim_time_sec() - (*iter).timestamp >= 15; iter = samples.begin())
989 samples.push_back(Sample<double>(agl));
992 double new_value = 0;
993 if (samples.size() > 0)
995 for (iter = samples.begin(); iter != samples.end(); iter++)
996 new_value += (*iter).value;
997 new_value /= samples.size();
1001 if (new_value > value)
1008 MK_VIII::IOHandler::TerrainClearanceFilter::reset ()
1014 MK_VIII::IOHandler::IOHandler (MK_VIII *device)
1015 : mk(device), _lamp(LAMP_NONE)
1017 memset(&input_feeders, 0, sizeof(input_feeders));
1018 memset(&inputs.discretes, 0, sizeof(inputs.discretes));
1019 memset(&outputs, 0, sizeof(outputs));
1020 memset(&power_saved, 0, sizeof(power_saved));
1022 mk_dinput_feed(landing_gear) = true;
1023 mk_dinput_feed(landing_flaps) = true;
1024 mk_dinput_feed(glideslope_inhibit) = true;
1025 mk_dinput_feed(decision_height) = true;
1026 mk_dinput_feed(autopilot_engaged) = true;
1027 mk_ainput_feed(uncorrected_barometric_altitude) = true;
1028 mk_ainput_feed(barometric_altitude_rate) = true;
1029 mk_ainput_feed(radio_altitude) = true;
1030 mk_ainput_feed(glideslope_deviation) = true;
1031 mk_ainput_feed(roll_angle) = true;
1032 mk_ainput_feed(localizer_deviation) = true;
1033 mk_ainput_feed(computed_airspeed) = true;
1035 // will be unset on power on
1036 mk_doutput(gpws_inop) = true;
1037 mk_doutput(tad_inop) = true;
1041 MK_VIII::IOHandler::boot ()
1043 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
1046 mk_doutput(gpws_inop) = false;
1047 mk_doutput(tad_inop) = false;
1049 mk_doutput(glideslope_cancel) = power_saved.glideslope_cancel;
1051 altitude_samples.clear();
1055 MK_VIII::IOHandler::post_boot ()
1057 if (momentary_steep_approach_enabled())
1059 last_landing_gear = mk_dinput(landing_gear);
1060 last_real_flaps_down = real_flaps_down();
1063 // read externally-latching input discretes
1064 update_alternate_discrete_input(&mk_dinput(mode6_low_volume));
1065 update_alternate_discrete_input(&mk_dinput(audio_inhibit));
1069 MK_VIII::IOHandler::power_off ()
1071 power_saved.glideslope_cancel = mk_doutput(glideslope_cancel); // [SPEC] 6.2.5
1073 memset(&outputs, 0, sizeof(outputs));
1075 audio_inhibit_fault_timer.stop();
1076 landing_gear_fault_timer.stop();
1077 flaps_down_fault_timer.stop();
1078 momentary_flap_override_fault_timer.stop();
1079 self_test_fault_timer.stop();
1080 glideslope_cancel_fault_timer.stop();
1081 steep_approach_fault_timer.stop();
1082 gpws_inhibit_fault_timer.stop();
1083 ta_tcf_inhibit_fault_timer.stop();
1086 mk_doutput(gpws_inop) = true;
1087 mk_doutput(tad_inop) = true;
1091 MK_VIII::IOHandler::enter_ground ()
1093 reset_terrain_clearance();
1095 if (conf.momentary_flap_override_enabled)
1096 mk_doutput(flap_override) = false; // [INSTALL] 3.15.1.6
1100 MK_VIII::IOHandler::enter_takeoff ()
1102 reset_terrain_clearance();
1104 if (momentary_steep_approach_enabled())
1105 // landing or go-around, disable steep approach as per [SPEC] 6.2.1
1106 mk_doutput(steep_approach) = false;
1110 MK_VIII::IOHandler::update_inputs ()
1112 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
1115 // 1. process input feeders
1117 if (mk_dinput_feed(landing_gear))
1118 mk_dinput(landing_gear) = mk_node(gear_down)->getBoolValue();
1119 if (mk_dinput_feed(landing_flaps))
1120 mk_dinput(landing_flaps) = mk_node(flaps)->getDoubleValue() < 1;
1121 if (mk_dinput_feed(glideslope_inhibit))
1122 mk_dinput(glideslope_inhibit) = mk_node(nav0_gs_distance)->getDoubleValue() < 0;
1123 if (mk_dinput_feed(autopilot_engaged))
1127 mode = mk_node(autopilot_heading_lock)->getStringValue();
1128 mk_dinput(autopilot_engaged) = mode && *mode;
1131 if (mk_ainput_feed(uncorrected_barometric_altitude))
1133 if (mk_node(altimeter_serviceable)->getBoolValue())
1134 mk_ainput(uncorrected_barometric_altitude).set(mk_node(altimeter_altitude)->getDoubleValue());
1136 mk_ainput(uncorrected_barometric_altitude).unset();
1138 if (mk_ainput_feed(barometric_altitude_rate))
1139 // Do not use the vsi, because it is pressure-based only, and
1140 // therefore too laggy for GPWS alerting purposes. I guess that
1141 // a real ADC combines pressure-based and inerta-based altitude
1142 // rates to provide a non-laggy rate. That non-laggy rate is
1143 // best emulated by /velocities/vertical-speed-fps * 60.
1144 mk_ainput(barometric_altitude_rate).set(mk_node(vs)->getDoubleValue() * 60);
1145 if (mk_ainput_feed(radio_altitude))
1147 // Some flight models may return negative values when on the
1148 // ground or after a crash; do not allow them.
1149 double agl = mk_node(altitude_agl)->getDoubleValue();
1150 mk_ainput(radio_altitude).set(SG_MAX2(0.0, agl));
1152 if (mk_ainput_feed(glideslope_deviation))
1154 if (mk_node(nav0_serviceable)->getBoolValue()
1155 && mk_node(nav0_gs_serviceable)->getBoolValue()
1156 && mk_node(nav0_in_range)->getBoolValue()
1157 && mk_node(nav0_has_gs)->getBoolValue())
1158 // gs-needle-deflection is expressed in degrees, and 1 dot =
1159 // 0.32 degrees (according to
1160 // http://www.ntsb.gov/Recs/letters/2000/A00_41_45.pdf)
1161 mk_ainput(glideslope_deviation).set(mk_node(nav0_gs_needle_deflection)->getDoubleValue() / 0.32 * GLIDESLOPE_DOTS_TO_DDM);
1163 mk_ainput(glideslope_deviation).unset();
1165 if (mk_ainput_feed(roll_angle))
1167 if (mk_node(ai_serviceable)->getBoolValue() && ! mk_node(ai_caged)->getBoolValue())
1168 mk_ainput(roll_angle).set(mk_node(ai_roll)->getDoubleValue());
1170 mk_ainput(roll_angle).unset();
1172 if (mk_ainput_feed(localizer_deviation))
1174 if (mk_node(nav0_serviceable)->getBoolValue()
1175 && mk_node(nav0_cdi_serviceable)->getBoolValue()
1176 && mk_node(nav0_in_range)->getBoolValue()
1177 && mk_node(nav0_nav_loc)->getBoolValue())
1178 // heading-needle-deflection is expressed in degrees, and 1
1179 // dot = 2 degrees (0.5 degrees for ILS, but navradio.cxx
1180 // performs the conversion)
1181 mk_ainput(localizer_deviation).set(mk_node(nav0_heading_needle_deflection)->getDoubleValue() / 2 * LOCALIZER_DOTS_TO_DDM);
1183 mk_ainput(localizer_deviation).unset();
1185 if (mk_ainput_feed(computed_airspeed))
1187 if (mk_node(asi_serviceable)->getBoolValue())
1188 mk_ainput(computed_airspeed).set(mk_node(asi_speed)->getDoubleValue());
1190 mk_ainput(computed_airspeed).unset();
1195 mk_data(decision_height).set(&mk_ainput(decision_height));
1196 mk_data(radio_altitude).set(&mk_ainput(radio_altitude));
1197 mk_data(roll_angle).set(&mk_ainput(roll_angle));
1199 // update barometric_altitude_rate as per [SPEC] 6.2.1: "During
1200 // normal conditions, the system will base Mode 1 computations upon
1201 // barometric rate from the ADC. If this computed data is not valid
1202 // or available then the system will use internally computed
1203 // barometric altitude rate."
1205 if (! mk_ainput(barometric_altitude_rate).ncd)
1206 // the altitude rate input is valid, use it
1207 mk_data(barometric_altitude_rate).set(mk_ainput(barometric_altitude_rate).get());
1212 // The altitude rate input is invalid. We can compute an
1213 // altitude rate if all the following conditions are true:
1215 // - the altitude input is valid
1216 // - there is an altitude sample with age >= 1 second
1217 // - that altitude sample is valid
1219 if (! mk_ainput(uncorrected_barometric_altitude).ncd)
1221 if (! altitude_samples.empty() && ! altitude_samples.front().value.ncd)
1223 double elapsed = globals->get_sim_time_sec() - altitude_samples.front().timestamp;
1227 mk_data(barometric_altitude_rate).set((mk_ainput(uncorrected_barometric_altitude).get() - altitude_samples.front().value.get()) / elapsed * 60);
1233 mk_data(barometric_altitude_rate).unset();
1236 altitude_samples.push_back(Sample< Parameter<double> >(mk_ainput(uncorrected_barometric_altitude)));
1238 // Erase everything from the beginning of the list up to the sample
1239 // preceding the most recent sample whose age is >= 1 second.
1241 altitude_samples_type::iterator erase_last = altitude_samples.begin();
1242 altitude_samples_type::iterator iter;
1244 for (iter = altitude_samples.begin(); iter != altitude_samples.end(); iter++)
1245 if (globals->get_sim_time_sec() - (*iter).timestamp >= 1)
1250 if (erase_last != altitude_samples.begin())
1251 altitude_samples.erase(altitude_samples.begin(), erase_last);
1255 if (conf.use_internal_gps)
1257 mk_data(gps_altitude).set(mk_node(altitude)->getDoubleValue());
1258 mk_data(gps_latitude).set(mk_node(latitude)->getDoubleValue());
1259 mk_data(gps_longitude).set(mk_node(longitude)->getDoubleValue());
1260 mk_data(gps_vertical_figure_of_merit).set(0.0);
1264 mk_data(gps_altitude).set(&mk_ainput(gps_altitude));
1265 mk_data(gps_latitude).set(&mk_ainput(gps_latitude));
1266 mk_data(gps_longitude).set(&mk_ainput(gps_longitude));
1267 mk_data(gps_vertical_figure_of_merit).set(&mk_ainput(gps_vertical_figure_of_merit));
1270 // update glideslope and localizer
1272 mk_data(glideslope_deviation_dots).set(&mk_ainput(glideslope_deviation), GLIDESLOPE_DDM_TO_DOTS);
1273 mk_data(localizer_deviation_dots).set(&mk_ainput(localizer_deviation), LOCALIZER_DDM_TO_DOTS);
1275 // Update geometric altitude; [SPEC] 6.7.8 provides an overview of a
1276 // complex algorithm which combines several input sources to
1277 // calculate the geometric altitude. Since the exact algorithm is
1278 // not given, we fallback to a much simpler method.
1280 if (! mk_data(gps_altitude).ncd)
1281 mk_data(geometric_altitude).set(mk_data(gps_altitude).get());
1282 else if (! mk_ainput(uncorrected_barometric_altitude).ncd)
1283 mk_data(geometric_altitude).set(mk_ainput(uncorrected_barometric_altitude).get());
1285 mk_data(geometric_altitude).unset();
1287 // update terrain clearance
1289 update_terrain_clearance();
1291 // 3. perform sanity checks
1293 if (! mk_data(radio_altitude).ncd && mk_data(radio_altitude).get() < 0)
1294 mk_data(radio_altitude).unset();
1296 if (! mk_data(decision_height).ncd && mk_data(decision_height).get() < 0)
1297 mk_data(decision_height).unset();
1299 if (! mk_data(gps_latitude).ncd
1300 && (mk_data(gps_latitude).get() < -90
1301 || mk_data(gps_latitude).get() > 90))
1302 mk_data(gps_latitude).unset();
1304 if (! mk_data(gps_longitude).ncd
1305 && (mk_data(gps_longitude).get() < -180
1306 || mk_data(gps_longitude).get() > 180))
1307 mk_data(gps_longitude).unset();
1309 if (! mk_data(roll_angle).ncd
1310 && ((mk_data(roll_angle).get() < -180)
1311 || (mk_data(roll_angle).get() > 180)))
1312 mk_data(roll_angle).unset();
1314 // 4. process input feeders requiring data computed above
1316 if (mk_dinput_feed(decision_height))
1317 mk_dinput(decision_height) = ! mk_data(radio_altitude).ncd
1318 && ! mk_data(decision_height).ncd
1319 && mk_data(radio_altitude).get() <= mk_data(decision_height).get();
1323 MK_VIII::IOHandler::update_terrain_clearance ()
1325 if (! mk_data(radio_altitude).ncd)
1326 mk_data(terrain_clearance).set(terrain_clearance_filter.update(mk_data(radio_altitude).get()));
1328 mk_data(terrain_clearance).unset();
1332 MK_VIII::IOHandler::reset_terrain_clearance ()
1334 terrain_clearance_filter.reset();
1335 update_terrain_clearance();
1339 MK_VIII::IOHandler::handle_input_fault (bool test, FaultHandler::Fault fault)
1343 if (! mk->fault_handler.faults[fault])
1344 mk->fault_handler.set_fault(fault);
1347 mk->fault_handler.unset_fault(fault);
1351 MK_VIII::IOHandler::handle_input_fault (bool test,
1353 double max_duration,
1354 FaultHandler::Fault fault)
1358 if (! mk->fault_handler.faults[fault])
1360 if (timer->start_or_elapsed() >= max_duration)
1361 mk->fault_handler.set_fault(fault);
1366 mk->fault_handler.unset_fault(fault);
1372 MK_VIII::IOHandler::update_input_faults ()
1374 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
1377 // [INSTALL] 3.15.1.3
1378 handle_input_fault(mk_dinput(audio_inhibit),
1379 &audio_inhibit_fault_timer,
1381 FaultHandler::FAULT_ALL_MODES_INHIBIT);
1383 // [INSTALL] appendix E 6.6.2
1384 handle_input_fault(mk_dinput(landing_gear)
1385 && ! mk_ainput(computed_airspeed).ncd
1386 && mk_ainput(computed_airspeed).get() > conf.faults->max_gear_down_airspeed,
1387 &landing_gear_fault_timer,
1389 FaultHandler::FAULT_GEAR_SWITCH);
1391 // [INSTALL] appendix E 6.6.4
1392 handle_input_fault(flaps_down()
1393 && ! mk_ainput(computed_airspeed).ncd
1394 && mk_ainput(computed_airspeed).get() > conf.faults->max_flaps_down_airspeed,
1395 &flaps_down_fault_timer,
1397 FaultHandler::FAULT_FLAPS_SWITCH);
1399 // [INSTALL] appendix E 6.6.6
1400 if (conf.momentary_flap_override_enabled)
1401 handle_input_fault(mk_dinput(momentary_flap_override),
1402 &momentary_flap_override_fault_timer,
1404 FaultHandler::FAULT_MOMENTARY_FLAP_OVERRIDE_INVALID);
1406 // [INSTALL] appendix E 6.6.7
1407 handle_input_fault(mk_dinput(self_test),
1408 &self_test_fault_timer,
1410 FaultHandler::FAULT_SELF_TEST_INVALID);
1412 // [INSTALL] appendix E 6.6.13
1413 handle_input_fault(mk_dinput(glideslope_cancel),
1414 &glideslope_cancel_fault_timer,
1416 FaultHandler::FAULT_GLIDESLOPE_CANCEL_INVALID);
1418 // [INSTALL] appendix E 6.6.25
1419 if (momentary_steep_approach_enabled())
1420 handle_input_fault(mk_dinput(steep_approach),
1421 &steep_approach_fault_timer,
1423 FaultHandler::FAULT_STEEP_APPROACH_INVALID);
1425 // [INSTALL] appendix E 6.6.27
1426 if (conf.gpws_inhibit_enabled)
1427 handle_input_fault(mk_dinput(gpws_inhibit),
1428 &gpws_inhibit_fault_timer,
1430 FaultHandler::FAULT_GPWS_INHIBIT);
1432 // [INSTALL] does not specify a fault for this one, but it makes
1433 // sense to have it behave like GPWS inhibit
1434 handle_input_fault(mk_dinput(ta_tcf_inhibit),
1435 &ta_tcf_inhibit_fault_timer,
1437 FaultHandler::FAULT_TA_TCF_INHIBIT);
1439 // [PILOT] page 48: "In the event that required data for a
1440 // particular function is not available, then that function is
1441 // automatically inhibited and annunciated"
1443 handle_input_fault(mk_data(radio_altitude).ncd
1444 || mk_ainput(uncorrected_barometric_altitude).ncd
1445 || mk_data(barometric_altitude_rate).ncd
1446 || mk_ainput(computed_airspeed).ncd
1447 || mk_data(terrain_clearance).ncd,
1448 FaultHandler::FAULT_MODES14_INPUTS_INVALID);
1450 if (! mk_dinput(glideslope_inhibit))
1451 handle_input_fault(mk_data(radio_altitude).ncd,
1452 FaultHandler::FAULT_MODE5_INPUTS_INVALID);
1454 if (mk->mode6_handler.altitude_callouts_enabled())
1455 handle_input_fault(mk->mode6_handler.conf.above_field_voice
1456 ? (mk_data(gps_latitude).ncd
1457 || mk_data(gps_longitude).ncd
1458 || mk_data(geometric_altitude).ncd)
1459 : mk_data(radio_altitude).ncd,
1460 FaultHandler::FAULT_MODE6_INPUTS_INVALID);
1462 if (mk->mode6_handler.conf.bank_angle_enabled)
1463 handle_input_fault(mk_data(roll_angle).ncd,
1464 FaultHandler::FAULT_BANK_ANGLE_INPUTS_INVALID);
1466 if (mk->tcf_handler.conf.enabled)
1467 handle_input_fault(mk_data(radio_altitude).ncd
1468 || mk_data(geometric_altitude).ncd
1469 || mk_data(gps_latitude).ncd
1470 || mk_data(gps_longitude).ncd
1471 || mk_data(gps_vertical_figure_of_merit).ncd,
1472 FaultHandler::FAULT_TCF_INPUTS_INVALID);
1476 MK_VIII::IOHandler::update_alternate_discrete_input (bool *ptr)
1478 assert(mk->system_handler.state == SystemHandler::STATE_ON);
1480 if (ptr == &mk_dinput(mode6_low_volume))
1482 if (mk->configuration_module.state == ConfigurationModule::STATE_OK
1483 && mk->self_test_handler.state == SelfTestHandler::STATE_NONE)
1484 mk->mode6_handler.set_volume(*ptr ? modify_amplitude(1.0, -6) : 1.0);
1486 else if (ptr == &mk_dinput(audio_inhibit))
1488 if (mk->configuration_module.state == ConfigurationModule::STATE_OK
1489 && mk->self_test_handler.state == SelfTestHandler::STATE_NONE)
1490 mk->voice_player.set_volume(*ptr ? 0.0 : mk->voice_player.conf.volume);
1495 MK_VIII::IOHandler::update_internal_latches ()
1497 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
1501 if (conf.momentary_flap_override_enabled
1502 && mk_doutput(flap_override)
1503 && ! mk->state_handler.takeoff
1504 && (mk_data(radio_altitude).ncd || mk_data(radio_altitude).get() <= 50))
1505 mk_doutput(flap_override) = false;
1508 if (momentary_steep_approach_enabled())
1510 if (mk_doutput(steep_approach)
1511 && ! mk->state_handler.takeoff
1512 && ((last_landing_gear && ! mk_dinput(landing_gear))
1513 || (last_real_flaps_down && ! real_flaps_down())))
1514 // gear or flaps raised during approach: it's a go-around
1515 mk_doutput(steep_approach) = false;
1517 last_landing_gear = mk_dinput(landing_gear);
1518 last_real_flaps_down = real_flaps_down();
1522 if (mk_doutput(glideslope_cancel)
1523 && (mk_data(glideslope_deviation_dots).ncd
1524 || mk_data(radio_altitude).ncd
1525 || mk_data(radio_altitude).get() > 2000
1526 || mk_data(radio_altitude).get() < 30))
1527 mk_doutput(glideslope_cancel) = false;
1531 MK_VIII::IOHandler::update_egpws_alert_discrete_1 ()
1533 if (mk->voice_player.voice)
1538 VoicePlayer::Voice *voice;
1540 { 11, mk_voice(sink_rate_pause_sink_rate) },
1541 { 12, mk_voice(pull_up) },
1542 { 13, mk_voice(terrain) },
1543 { 13, mk_voice(terrain_pause_terrain) },
1544 { 14, mk_voice(dont_sink_pause_dont_sink) },
1545 { 15, mk_voice(too_low_gear) },
1546 { 16, mk_voice(too_low_flaps) },
1547 { 17, mk_voice(too_low_terrain) },
1548 { 18, mk_voice(soft_glideslope) },
1549 { 18, mk_voice(hard_glideslope) },
1550 { 19, mk_voice(minimums_minimums) }
1553 for (size_t i = 0; i < n_elements(voices); i++)
1554 if (voices[i].voice == mk->voice_player.voice)
1556 mk_aoutput(egpws_alert_discrete_1) = 1 << voices[i].bit;
1561 mk_aoutput(egpws_alert_discrete_1) = 0;
1565 MK_VIII::IOHandler::update_egpwc_logic_discretes ()
1567 mk_aoutput(egpwc_logic_discretes) = 0;
1569 if (mk->state_handler.takeoff)
1570 mk_aoutput(egpwc_logic_discretes) |= 1 << 18;
1575 unsigned int alerts;
1577 { 13, mk_alert(TCF_TOO_LOW_TERRAIN) },
1578 { 19, mk_alert(MODE1_SINK_RATE) },
1579 { 20, mk_alert(MODE1_PULL_UP) },
1580 { 21, mk_alert(MODE2A_PREFACE) | mk_alert(MODE2B_PREFACE) | mk_alert(MODE2B_LANDING_MODE) | mk_alert(MODE2A_ALTITUDE_GAIN_TERRAIN_CLOSING) },
1581 { 22, mk_alert(MODE2A) | mk_alert(MODE2B) },
1582 { 23, mk_alert(MODE3) },
1583 { 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) },
1584 { 25, mk_alert(MODE5_SOFT) | mk_alert(MODE5_HARD) }
1587 for (size_t i = 0; i < n_elements(logic); i++)
1588 if (mk_test_alerts(logic[i].alerts))
1589 mk_aoutput(egpwc_logic_discretes) |= 1 << logic[i].bit;
1593 MK_VIII::IOHandler::update_mode6_callouts_discrete_1 ()
1595 if (mk->voice_player.voice)
1600 VoicePlayer::Voice *voice;
1602 { 11, mk_voice(minimums_minimums) },
1603 { 16, mk_altitude_voice(Mode6Handler::ALTITUDE_CALLOUT_10) },
1604 { 17, mk_altitude_voice(Mode6Handler::ALTITUDE_CALLOUT_20) },
1605 { 18, mk_altitude_voice(Mode6Handler::ALTITUDE_CALLOUT_30) },
1606 { 19, mk_altitude_voice(Mode6Handler::ALTITUDE_CALLOUT_40) },
1607 { 20, mk_altitude_voice(Mode6Handler::ALTITUDE_CALLOUT_50) },
1608 { 23, mk_altitude_voice(Mode6Handler::ALTITUDE_CALLOUT_100) },
1609 { 24, mk_altitude_voice(Mode6Handler::ALTITUDE_CALLOUT_200) },
1610 { 25, mk_altitude_voice(Mode6Handler::ALTITUDE_CALLOUT_300) }
1613 for (size_t i = 0; i < n_elements(voices); i++)
1614 if (voices[i].voice == mk->voice_player.voice)
1616 mk_aoutput(mode6_callouts_discrete_1) = 1 << voices[i].bit;
1621 mk_aoutput(mode6_callouts_discrete_1) = 0;
1625 MK_VIII::IOHandler::update_mode6_callouts_discrete_2 ()
1627 if (mk->voice_player.voice)
1632 VoicePlayer::Voice *voice;
1634 { 11, mk_altitude_voice(Mode6Handler::ALTITUDE_CALLOUT_400) },
1635 { 12, mk_altitude_voice(Mode6Handler::ALTITUDE_CALLOUT_500) },
1636 { 13, mk_altitude_voice(Mode6Handler::ALTITUDE_CALLOUT_1000) },
1637 { 18, mk_voice(bank_angle_pause_bank_angle) },
1638 { 18, mk_voice(bank_angle_pause_bank_angle_3) },
1639 { 23, mk_voice(five_hundred_above) }
1642 for (size_t i = 0; i < n_elements(voices); i++)
1643 if (voices[i].voice == mk->voice_player.voice)
1645 mk_aoutput(mode6_callouts_discrete_2) = 1 << voices[i].bit;
1650 mk_aoutput(mode6_callouts_discrete_2) = 0;
1654 MK_VIII::IOHandler::update_egpws_alert_discrete_2 ()
1656 mk_aoutput(egpws_alert_discrete_2) = 1 << 27; // Terrain NA
1658 if (mk_doutput(glideslope_cancel))
1659 mk_aoutput(egpws_alert_discrete_2) |= 1 << 11;
1660 if (mk_doutput(gpws_alert))
1661 mk_aoutput(egpws_alert_discrete_2) |= 1 << 12;
1662 if (mk_doutput(gpws_warning))
1663 mk_aoutput(egpws_alert_discrete_2) |= 1 << 13;
1664 if (mk_doutput(gpws_inop))
1665 mk_aoutput(egpws_alert_discrete_2) |= 1 << 14;
1666 if (mk_doutput(audio_on))
1667 mk_aoutput(egpws_alert_discrete_2) |= 1 << 16;
1668 if (mk_doutput(tad_inop))
1669 mk_aoutput(egpws_alert_discrete_2) |= 1 << 24;
1670 if (mk->fault_handler.has_faults())
1671 mk_aoutput(egpws_alert_discrete_2) |= 1 << 25;
1675 MK_VIII::IOHandler::update_egpwc_alert_discrete_3 ()
1677 mk_aoutput(egpwc_alert_discrete_3) = 1 << 17 | 1 << 18;
1679 if (mk->fault_handler.faults[FaultHandler::FAULT_MODES14_INPUTS_INVALID])
1680 mk_aoutput(egpwc_alert_discrete_3) |= 1 << 11;
1681 if (mk->fault_handler.faults[FaultHandler::FAULT_MODE5_INPUTS_INVALID])
1682 mk_aoutput(egpwc_alert_discrete_3) |= 1 << 12;
1683 if (mk->fault_handler.faults[FaultHandler::FAULT_MODE6_INPUTS_INVALID])
1684 mk_aoutput(egpwc_alert_discrete_3) |= 1 << 13;
1685 if (mk->fault_handler.faults[FaultHandler::FAULT_BANK_ANGLE_INPUTS_INVALID])
1686 mk_aoutput(egpwc_alert_discrete_3) |= 1 << 14;
1687 if (mk_doutput(tad_inop))
1688 mk_aoutput(egpwc_alert_discrete_3) |= 1 << 16;
1692 MK_VIII::IOHandler::update_outputs ()
1694 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
1697 mk_doutput(audio_on) = ! mk_dinput(audio_inhibit)
1698 && mk->voice_player.voice
1699 && ! mk->voice_player.voice->element->silence;
1701 update_egpws_alert_discrete_1();
1702 update_egpwc_logic_discretes();
1703 update_mode6_callouts_discrete_1();
1704 update_mode6_callouts_discrete_2();
1705 update_egpws_alert_discrete_2();
1706 update_egpwc_alert_discrete_3();
1710 MK_VIII::IOHandler::get_lamp_output (Lamp lamp)
1714 case LAMP_GLIDESLOPE:
1715 return &mk_doutput(gpws_alert);
1718 return conf.lamp->format2 ? &mk_doutput(gpws_alert) : &mk_doutput(gpws_warning);
1721 return &mk_doutput(gpws_warning);
1724 assert_not_reached();
1730 MK_VIII::IOHandler::update_lamps ()
1732 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
1735 if (_lamp != LAMP_NONE && conf.lamp->flashing)
1737 // [SPEC] 6.9.3: 70 cycles per minute
1738 if (lamp_timer.elapsed() >= 60.0 / 70.0 / 2.0)
1740 bool *output = get_lamp_output(_lamp);
1741 *output = ! *output;
1748 MK_VIII::IOHandler::set_lamp (Lamp lamp)
1755 mk_doutput(gpws_warning) = false;
1756 mk_doutput(gpws_alert) = false;
1758 if (lamp != LAMP_NONE)
1760 *get_lamp_output(lamp) = true;
1766 MK_VIII::IOHandler::gpws_inhibit () const
1768 return conf.gpws_inhibit_enabled && mk_dinput(gpws_inhibit);
1772 MK_VIII::IOHandler::real_flaps_down () const
1774 return conf.flap_reversal ? mk_dinput(landing_flaps) : ! mk_dinput(landing_flaps);
1778 MK_VIII::IOHandler::flaps_down () const
1780 return flap_override() ? true : real_flaps_down();
1784 MK_VIII::IOHandler::flap_override () const
1786 return conf.momentary_flap_override_enabled ? mk_doutput(flap_override) : false;
1790 MK_VIII::IOHandler::steep_approach () const
1792 if (conf.steep_approach_enabled)
1793 // If alternate action was configured in category 13, we return
1794 // the value of the input discrete (which should be connected to a
1795 // latching steep approach cockpit switch). Otherwise, we return
1796 // the value of the output discrete.
1797 return conf.alternate_steep_approach ? mk_dinput(steep_approach) : mk_doutput(steep_approach);
1803 MK_VIII::IOHandler::momentary_steep_approach_enabled () const
1805 return conf.steep_approach_enabled && ! conf.alternate_steep_approach;
1809 MK_VIII::IOHandler::tie_input (SGPropertyNode *node,
1814 mk->properties_handler.tie(node, (string("inputs/discretes/") + name).c_str(), RawValueMethodsData<MK_VIII::IOHandler, bool, bool *>(*this, input, &MK_VIII::IOHandler::get_discrete_input, &MK_VIII::IOHandler::set_discrete_input));
1816 mk->properties_handler.tie(node, (string("input-feeders/discretes/") + name).c_str(), SGRawValuePointer<bool>(feed));
1820 MK_VIII::IOHandler::tie_input (SGPropertyNode *node,
1822 Parameter<double> *input,
1825 mk->properties_handler.tie(node, (string("inputs/arinc429/") + name).c_str(), SGRawValuePointer<double>(input->get_pointer()));
1826 mk->properties_handler.tie(node, (string("inputs/arinc429/") + name + "-ncd").c_str(), SGRawValuePointer<bool>(&input->ncd));
1828 mk->properties_handler.tie(node, (string("input-feeders/arinc429/") + name).c_str(), SGRawValuePointer<bool>(feed));
1832 MK_VIII::IOHandler::tie_output (SGPropertyNode *node,
1836 SGPropertyNode *child = node->getNode((string("outputs/discretes/") + name).c_str(), true);
1838 mk->properties_handler.tie(child, SGRawValuePointer<bool>(output));
1839 child->setAttribute(SGPropertyNode::WRITE, false);
1843 MK_VIII::IOHandler::tie_output (SGPropertyNode *node,
1847 SGPropertyNode *child = node->getNode((string("outputs/arinc429/") + name).c_str(), true);
1849 mk->properties_handler.tie(child, SGRawValuePointer<int>(output));
1850 child->setAttribute(SGPropertyNode::WRITE, false);
1854 MK_VIII::IOHandler::bind (SGPropertyNode *node)
1856 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));
1858 tie_input(node, "landing-gear", &mk_dinput(landing_gear), &mk_dinput_feed(landing_gear));
1859 tie_input(node, "landing-flaps", &mk_dinput(landing_flaps), &mk_dinput_feed(landing_flaps));
1860 tie_input(node, "momentary-flap-override", &mk_dinput(momentary_flap_override));
1861 tie_input(node, "self-test", &mk_dinput(self_test));
1862 tie_input(node, "glideslope-inhibit", &mk_dinput(glideslope_inhibit), &mk_dinput_feed(glideslope_inhibit));
1863 tie_input(node, "glideslope-cancel", &mk_dinput(glideslope_cancel));
1864 tie_input(node, "decision-height", &mk_dinput(decision_height), &mk_dinput_feed(decision_height));
1865 tie_input(node, "mode6-low-volume", &mk_dinput(mode6_low_volume));
1866 tie_input(node, "audio-inhibit", &mk_dinput(audio_inhibit));
1867 tie_input(node, "ta-tcf-inhibit", &mk_dinput(ta_tcf_inhibit));
1868 tie_input(node, "autopilot-engaged", &mk_dinput(autopilot_engaged), &mk_dinput_feed(autopilot_engaged));
1869 tie_input(node, "steep-approach", &mk_dinput(steep_approach));
1870 tie_input(node, "gpws-inhibit", &mk_dinput(gpws_inhibit));
1872 tie_input(node, "uncorrected-barometric-altitude", &mk_ainput(uncorrected_barometric_altitude), &mk_ainput_feed(uncorrected_barometric_altitude));
1873 tie_input(node, "barometric-altitude-rate", &mk_ainput(barometric_altitude_rate), &mk_ainput_feed(barometric_altitude_rate));
1874 tie_input(node, "gps-altitude", &mk_ainput(gps_altitude));
1875 tie_input(node, "gps-latitude", &mk_ainput(gps_latitude));
1876 tie_input(node, "gps-longitude", &mk_ainput(gps_longitude));
1877 tie_input(node, "gps-vertical-figure-of-merit", &mk_ainput(gps_vertical_figure_of_merit));
1878 tie_input(node, "radio-altitude", &mk_ainput(radio_altitude), &mk_ainput_feed(radio_altitude));
1879 tie_input(node, "glideslope-deviation", &mk_ainput(glideslope_deviation), &mk_ainput_feed(glideslope_deviation));
1880 tie_input(node, "roll-angle", &mk_ainput(roll_angle), &mk_ainput_feed(roll_angle));
1881 tie_input(node, "localizer-deviation", &mk_ainput(localizer_deviation), &mk_ainput_feed(localizer_deviation));
1882 tie_input(node, "computed-airspeed", &mk_ainput(computed_airspeed), &mk_ainput_feed(computed_airspeed));
1883 tie_input(node, "decision-height", &mk_ainput(decision_height), &mk_ainput_feed(decision_height));
1885 tie_output(node, "gpws-warning", &mk_doutput(gpws_warning));
1886 tie_output(node, "gpws-alert", &mk_doutput(gpws_alert));
1887 tie_output(node, "audio-on", &mk_doutput(audio_on));
1888 tie_output(node, "gpws-inop", &mk_doutput(gpws_inop));
1889 tie_output(node, "tad-inop", &mk_doutput(tad_inop));
1890 tie_output(node, "flap-override", &mk_doutput(flap_override));
1891 tie_output(node, "glideslope-cancel", &mk_doutput(glideslope_cancel));
1892 tie_output(node, "steep-approach", &mk_doutput(steep_approach));
1894 tie_output(node, "egpws-alert-discrete-1", &mk_aoutput(egpws_alert_discrete_1));
1895 tie_output(node, "egpwc-logic-discretes", &mk_aoutput(egpwc_logic_discretes));
1896 tie_output(node, "mode6-callouts-discrete-1", &mk_aoutput(mode6_callouts_discrete_1));
1897 tie_output(node, "mode6-callouts-discrete-2", &mk_aoutput(mode6_callouts_discrete_2));
1898 tie_output(node, "egpws-alert-discrete-2", &mk_aoutput(egpws_alert_discrete_2));
1899 tie_output(node, "egpwc-alert-discrete-3", &mk_aoutput(egpwc_alert_discrete_3));
1903 MK_VIII::IOHandler::get_discrete_input (bool *ptr) const
1909 MK_VIII::IOHandler::set_discrete_input (bool *ptr, bool value)
1914 if (mk->system_handler.state == SystemHandler::STATE_ON)
1916 if (ptr == &mk_dinput(momentary_flap_override))
1918 if (mk->configuration_module.state == ConfigurationModule::STATE_OK
1919 && mk->self_test_handler.state == SelfTestHandler::STATE_NONE
1920 && conf.momentary_flap_override_enabled
1922 mk_doutput(flap_override) = ! mk_doutput(flap_override);
1924 else if (ptr == &mk_dinput(self_test))
1925 mk->self_test_handler.handle_button_event(value);
1926 else if (ptr == &mk_dinput(glideslope_cancel))
1928 if (mk->configuration_module.state == ConfigurationModule::STATE_OK
1929 && mk->self_test_handler.state == SelfTestHandler::STATE_NONE
1932 if (! mk_doutput(glideslope_cancel))
1936 // Although we are not called from update(), we are
1937 // sure that the inputs we use here are defined,
1938 // since state is STATE_ON.
1940 if (! mk_data(glideslope_deviation_dots).ncd
1941 && ! mk_data(radio_altitude).ncd
1942 && mk_data(radio_altitude).get() <= 2000
1943 && mk_data(radio_altitude).get() >= 30)
1944 mk_doutput(glideslope_cancel) = true;
1946 // else do nothing ([PILOT] page 22: "Glideslope Cancel
1947 // can not be deselected (reset) by again pressing the
1948 // Glideslope Cancel switch.")
1951 else if (ptr == &mk_dinput(steep_approach))
1953 if (mk->configuration_module.state == ConfigurationModule::STATE_OK
1954 && mk->self_test_handler.state == SelfTestHandler::STATE_NONE
1955 && momentary_steep_approach_enabled()
1957 mk_doutput(steep_approach) = ! mk_doutput(steep_approach);
1963 if (mk->system_handler.state == SystemHandler::STATE_ON)
1964 update_alternate_discrete_input(ptr);
1968 MK_VIII::IOHandler::present_status_section (const char *name)
1970 printf("%s\n", name);
1974 MK_VIII::IOHandler::present_status_item (const char *name, const char *value)
1977 printf("\t%-32s %s\n", name, value);
1979 printf("\t%s\n", name);
1983 MK_VIII::IOHandler::present_status_subitem (const char *name)
1985 printf("\t\t%s\n", name);
1989 MK_VIII::IOHandler::present_status ()
1993 if (mk->system_handler.state != SystemHandler::STATE_ON)
1996 present_status_section("EGPWC CONFIGURATION");
1997 present_status_item("PART NUMBER:", "965-1220-000"); // [SPEC] 1.1
1998 present_status_item("MOD STATUS:", "N/A");
1999 present_status_item("SERIAL NUMBER:", "N/A");
2001 present_status_item("APPLICATION S/W VERSION:", FLIGHTGEAR_VERSION);
2002 present_status_item("TERRAIN DATABASE VERSION:", FLIGHTGEAR_VERSION);
2003 present_status_item("ENVELOPE MOD DATABASE VERSION:", "N/A");
2004 present_status_item("BOOT CODE VERSION:", FLIGHTGEAR_VERSION);
2007 present_status_section("CURRENT FAULTS");
2008 if (mk->configuration_module.state == ConfigurationModule::STATE_OK && ! mk->fault_handler.has_faults())
2009 present_status_item("NO FAULTS");
2012 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
2014 present_status_item("GPWS COMPUTER FAULTS:");
2015 switch (mk->configuration_module.state)
2017 case ConfigurationModule::STATE_INVALID_DATABASE:
2018 present_status_subitem("APPLICATION DATABASE FAILED");
2021 case ConfigurationModule::STATE_INVALID_AIRCRAFT_TYPE:
2022 present_status_subitem("CONFIGURATION TYPE INVALID");
2026 assert_not_reached();
2032 present_status_item("GPWS COMPUTER OK");
2033 present_status_item("GPWS EXTERNAL FAULTS:");
2035 static const char *fault_names[] = {
2036 "ALL MODES INHIBIT",
2039 "MOMENTARY FLAP OVERRIDE INVALID",
2040 "SELF TEST INVALID",
2041 "GLIDESLOPE CANCEL INVALID",
2042 "STEEP APPROACH INVALID",
2045 "MODES 1-4 INPUTS INVALID",
2046 "MODE 5 INPUTS INVALID",
2047 "MODE 6 INPUTS INVALID",
2048 "BANK ANGLE INPUTS INVALID",
2049 "TCF INPUTS INVALID"
2052 for (size_t i = 0; i < n_elements(fault_names); i++)
2053 if (mk->fault_handler.faults[i])
2054 present_status_subitem(fault_names[i]);
2059 present_status_section("CONFIGURATION:");
2061 static const char *category_names[] = {
2064 "POSITION INPUT TYPE",
2065 "CALLOUTS OPTION TYPE",
2067 "TERRAIN DISPLAY TYPE",
2069 "RADIO ALTITUDE TYPE",
2070 "NAVIGATION INPUT TYPE",
2072 "MAGNETIC HEADING TYPE",
2073 "WINDSHEAR INPUT TYPE",
2078 for (size_t i = 0; i < n_elements(category_names); i++)
2080 std::ostringstream value;
2081 value << "= " << mk->configuration_module.effective_categories[i];
2082 present_status_item(category_names[i], value.str().c_str());
2087 MK_VIII::IOHandler::get_present_status () const
2093 MK_VIII::IOHandler::set_present_status (bool value)
2095 if (value) present_status();
2099 ///////////////////////////////////////////////////////////////////////////////
2100 // VoicePlayer ////////////////////////////////////////////////////////////////
2101 ///////////////////////////////////////////////////////////////////////////////
2104 MK_VIII::VoicePlayer::Speaker::bind (SGPropertyNode *node)
2106 // uses xmlsound property names
2107 tie(node, "volume", &volume);
2108 tie(node, "pitch", &pitch);
2109 tie(node, "position/x", &position[0]);
2110 tie(node, "position/y", &position[1]);
2111 tie(node, "position/z", &position[2]);
2112 tie(node, "orientation/x", &orientation[0]);
2113 tie(node, "orientation/y", &orientation[1]);
2114 tie(node, "orientation/z", &orientation[2]);
2115 tie(node, "orientation/inner-cone", &inner_cone);
2116 tie(node, "orientation/outer-cone", &outer_cone);
2117 tie(node, "reference-dist", &reference_dist);
2118 tie(node, "max-dist", &max_dist);
2122 MK_VIII::VoicePlayer::Speaker::update_configuration ()
2124 map<string, SGSoundSample *>::iterator iter;
2125 for (iter = player->samples.begin(); iter != player->samples.end(); iter++)
2127 SGSoundSample *sample = (*iter).second;
2129 sample->set_pitch(pitch);
2130 sample->set_base_position(position); // TODO: tie to listener pos
2131 sample->set_orientation(orientation);
2132 sample->set_audio_cone(inner_cone, outer_cone, outer_gain);
2133 sample->set_reference_dist(reference_dist);
2134 sample->set_max_dist(max_dist);
2138 player->voice->volume_changed();
2141 MK_VIII::VoicePlayer::Voice::~Voice ()
2143 for (iter = elements.begin(); iter != elements.end(); iter++)
2144 delete *iter; // we owned the element
2149 MK_VIII::VoicePlayer::Voice::play ()
2151 iter = elements.begin();
2154 element->play(get_volume());
2158 MK_VIII::VoicePlayer::Voice::stop (bool now)
2162 if (now || element->silence)
2168 iter = elements.end() - 1; // stop after the current element finishes
2173 MK_VIII::VoicePlayer::Voice::set_volume (float _volume)
2180 MK_VIII::VoicePlayer::Voice::volume_changed ()
2183 element->set_volume(get_volume());
2187 MK_VIII::VoicePlayer::Voice::update ()
2191 if (! element->is_playing())
2193 if (++iter == elements.end())
2198 element->play(get_volume());
2204 MK_VIII::VoicePlayer::~VoicePlayer ()
2206 vector<Voice *>::iterator iter1;
2207 for (iter1 = _voices.begin(); iter1 != _voices.end(); iter1++)
2214 MK_VIII::VoicePlayer::init ()
2216 #define STDPAUSE 0.75 // [SPEC] 6.4.4: "the standard 0.75 second delay"
2218 SGSoundMgr *smgr = (SGSoundMgr *)globals->get_subsystem("soundmgr");
2219 _sgr = smgr->find("avionics", true);
2221 make_voice(&voices.application_data_base_failed, "application-data-base-failed");
2222 make_voice(&voices.bank_angle, "bank-angle");
2223 make_voice(&voices.bank_angle_bank_angle, "bank-angle", "bank-angle");
2224 make_voice(&voices.bank_angle_bank_angle_3, "bank-angle", "bank-angle", 3.0);
2225 make_voice(&voices.bank_angle_inop, "bank-angle-inop");
2226 make_voice(&voices.bank_angle_pause_bank_angle, "bank-angle", STDPAUSE, "bank-angle");
2227 make_voice(&voices.bank_angle_pause_bank_angle_3, "bank-angle", STDPAUSE, "bank-angle", 3.0);
2228 make_voice(&voices.callouts_inop, "callouts-inop");
2229 make_voice(&voices.configuration_type_invalid, "configuration-type-invalid");
2230 make_voice(&voices.dont_sink, "dont-sink");
2231 make_voice(&voices.dont_sink_pause_dont_sink, "dont-sink", STDPAUSE, "dont-sink");
2232 make_voice(&voices.five_hundred_above, "500-above");
2233 make_voice(&voices.glideslope, "glideslope");
2234 make_voice(&voices.glideslope_inop, "glideslope-inop");
2235 make_voice(&voices.gpws_inop, "gpws-inop");
2236 make_voice(&voices.hard_glideslope, "glideslope", "glideslope", 3.0);
2237 make_voice(&voices.minimums, "minimums");
2238 make_voice(&voices.minimums_minimums, "minimums", "minimums");
2239 make_voice(&voices.pull_up, "pull-up");
2240 make_voice(&voices.sink_rate, "sink-rate");
2241 make_voice(&voices.sink_rate_pause_sink_rate, "sink-rate", STDPAUSE, "sink-rate");
2242 make_voice(&voices.soft_glideslope, new Voice::SampleElement(get_sample("glideslope"), modify_amplitude(1.0, -6)));
2243 make_voice(&voices.terrain, "terrain");
2244 make_voice(&voices.terrain_pause_terrain, "terrain", STDPAUSE, "terrain");
2245 make_voice(&voices.too_low_flaps, "too-low-flaps");
2246 make_voice(&voices.too_low_gear, "too-low-gear");
2247 make_voice(&voices.too_low_terrain, "too-low-terrain");
2249 for (unsigned i = 0; i < n_altitude_callouts; i++)
2251 std::ostringstream name;
2252 name << "altitude-" << mk->mode6_handler.altitude_callout_definitions[i];
2253 make_voice(&voices.altitude_callouts[i], name.str().c_str());
2256 speaker.update_configuration();
2260 MK_VIII::VoicePlayer::get_sample (const char *name)
2262 std::ostringstream refname;
2263 refname << mk->name << "[" << mk->num << "]" << "/" << name;
2265 SGSoundSample *sample = _sgr->find(refname.str());
2268 SGPath sample_path(globals->get_fg_root());
2269 sample_path.append("Sounds/mk-viii");
2271 string filename = string(name) + ".wav";
2274 sample = new SGSoundSample(sample_path.c_str(), filename.c_str());
2276 catch (const sg_exception &e)
2278 SG_LOG(SG_INSTR, SG_ALERT, "Error loading MK VIII sound sample \"" + filename + "\": " + e.getFormattedMessage());
2282 _sgr->add(sample, refname.str());
2283 samples[refname.str()] = sample;
2290 MK_VIII::VoicePlayer::play (Voice *_voice, unsigned int flags)
2292 if (test_bits(flags, PLAY_NOW) || ! voice || voice->element->silence)
2298 looped = test_bits(flags, PLAY_LOOPED);
2301 next_looped = false;
2307 next_voice = _voice;
2308 next_looped = test_bits(flags, PLAY_LOOPED);
2313 MK_VIII::VoicePlayer::stop (unsigned int flags)
2317 voice->stop(test_bits(flags, STOP_NOW));
2327 MK_VIII::VoicePlayer::set_volume (float _volume)
2331 voice->volume_changed();
2335 MK_VIII::VoicePlayer::update ()
2343 if (! voice->element || voice->element->silence)
2346 looped = next_looped;
2349 next_looped = false;
2356 if (! voice->element)
2367 ///////////////////////////////////////////////////////////////////////////////
2368 // SelfTestHandler ////////////////////////////////////////////////////////////
2369 ///////////////////////////////////////////////////////////////////////////////
2372 MK_VIII::SelfTestHandler::_was_here (int position)
2374 if (position > current)
2383 MK_VIII::SelfTestHandler::Action
2384 MK_VIII::SelfTestHandler::sleep (double duration)
2386 Action _action = { ACTION_SLEEP, duration, NULL };
2390 MK_VIII::SelfTestHandler::Action
2391 MK_VIII::SelfTestHandler::play (VoicePlayer::Voice *voice)
2393 mk->voice_player.play(voice);
2394 Action _action = { ACTION_VOICE, 0, NULL };
2398 MK_VIII::SelfTestHandler::Action
2399 MK_VIII::SelfTestHandler::discrete_on (bool *discrete, double duration)
2402 return sleep(duration);
2405 MK_VIII::SelfTestHandler::Action
2406 MK_VIII::SelfTestHandler::discrete_on_off (bool *discrete, double duration)
2409 Action _action = { ACTION_SLEEP | ACTION_DISCRETE_ON_OFF, duration, discrete };
2413 MK_VIII::SelfTestHandler::Action
2414 MK_VIII::SelfTestHandler::discrete_on_off (bool *discrete, VoicePlayer::Voice *voice)
2417 mk->voice_player.play(voice);
2418 Action _action = { ACTION_VOICE | ACTION_DISCRETE_ON_OFF, 0, discrete };
2422 MK_VIII::SelfTestHandler::Action
2423 MK_VIII::SelfTestHandler::done ()
2425 Action _action = { ACTION_DONE, 0, NULL };
2429 MK_VIII::SelfTestHandler::Action
2430 MK_VIII::SelfTestHandler::run ()
2432 // Note that "Terrain INOP" and "Terrain NA" are or'ed to the same
2433 // output discrete (see [SPEC] 6.9.3.5).
2435 #define was_here() was_here_offset(0)
2436 #define was_here_offset(offset) _was_here(__LINE__ * 20 + (offset))
2440 if (mk->configuration_module.state == ConfigurationModule::STATE_INVALID_DATABASE)
2441 return play(mk_voice(application_data_base_failed));
2442 else if (mk->configuration_module.state == ConfigurationModule::STATE_INVALID_AIRCRAFT_TYPE)
2443 return play(mk_voice(configuration_type_invalid));
2445 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
2449 return discrete_on(&mk_doutput(gpws_inop), 0.7);
2451 return sleep(0.7); // W/S INOP
2453 return discrete_on(&mk_doutput(tad_inop), 0.7);
2456 mk_doutput(gpws_inop) = false;
2457 // do not disable tad_inop since we must enable Terrain NA
2458 // do not disable W/S INOP since we do not emulate it
2459 return sleep(0.7); // Terrain NA
2463 mk_doutput(tad_inop) = false; // disable Terrain NA
2464 if (mk->io_handler.conf.momentary_flap_override_enabled)
2465 return discrete_on_off(&mk_doutput(flap_override), 1.0);
2468 return discrete_on_off(&mk_doutput(audio_on), 1.0);
2471 if (mk->io_handler.momentary_steep_approach_enabled())
2472 return discrete_on_off(&mk_doutput(steep_approach), 1.0);
2477 if (mk_dinput(glideslope_inhibit))
2478 goto glideslope_end;
2481 if (mk->fault_handler.faults[FaultHandler::FAULT_MODE5_INPUTS_INVALID])
2482 goto glideslope_inop;
2486 return discrete_on_off(&mk_doutput(gpws_alert), mk_voice(glideslope));
2488 return discrete_on_off(&mk_doutput(glideslope_cancel), 0.7);
2489 goto glideslope_end;
2492 return play(mk_voice(glideslope_inop));
2497 if (mk->fault_handler.faults[FaultHandler::FAULT_MODES14_INPUTS_INVALID])
2501 return discrete_on_off(&mk_doutput(gpws_warning), mk_voice(pull_up));
2505 return play(mk_voice(gpws_inop));
2510 if (mk_dinput(self_test)) // proceed to long self test
2515 if (mk->mode6_handler.conf.bank_angle_enabled
2516 && mk->fault_handler.faults[FaultHandler::FAULT_BANK_ANGLE_INPUTS_INVALID])
2517 return play(mk_voice(bank_angle_inop));
2521 if (mk->mode6_handler.altitude_callouts_enabled()
2522 && mk->fault_handler.faults[FaultHandler::FAULT_MODE6_INPUTS_INVALID])
2523 return play(mk_voice(callouts_inop));
2531 mk_doutput(gpws_inop) = true;
2532 // do not enable W/S INOP, since we do not emulate it
2533 mk_doutput(tad_inop) = true; // Terrain INOP, Terrain NA
2535 return play(mk_voice(sink_rate));
2538 return play(mk_voice(pull_up));
2540 return play(mk_voice(terrain));
2542 return play(mk_voice(pull_up));
2544 return play(mk_voice(dont_sink));
2546 return play(mk_voice(too_low_terrain));
2548 return play(mk->mode4_handler.conf.voice_too_low_gear);
2550 return play(mk_voice(too_low_flaps));
2552 return play(mk_voice(too_low_terrain));
2554 return play(mk_voice(glideslope));
2557 if (mk->mode6_handler.conf.bank_angle_enabled)
2558 return play(mk_voice(bank_angle));
2563 if (! mk->mode6_handler.altitude_callouts_enabled())
2564 goto callouts_disabled;
2568 if (mk->mode6_handler.conf.minimums_enabled)
2569 return play(mk_voice(minimums));
2573 if (mk->mode6_handler.conf.above_field_voice)
2574 return play(mk->mode6_handler.conf.above_field_voice);
2576 for (unsigned i = 0; i < n_altitude_callouts; i++)
2577 if (! was_here_offset(i))
2579 if (mk->mode6_handler.conf.altitude_callouts_enabled[i])
2580 return play(mk_altitude_voice(i));
2584 if (mk->mode6_handler.conf.smart_500_enabled)
2585 return play(mk_altitude_voice(Mode6Handler::ALTITUDE_CALLOUT_500));
2590 return play(mk_voice(minimums_minimums));
2595 if (mk->tcf_handler.conf.enabled)
2596 return play(mk_voice(too_low_terrain));
2603 MK_VIII::SelfTestHandler::start ()
2605 assert(state == STATE_START);
2607 memcpy(&saved_outputs, &mk->io_handler.outputs, sizeof(mk->io_handler.outputs));
2608 memset(&mk->io_handler.outputs, 0, sizeof(mk->io_handler.outputs));
2610 // [SPEC] 6.10.6: "The self-test results are annunciated, at 6db
2611 // lower than the normal audio level selected for the aircraft"
2612 mk->voice_player.set_volume(modify_amplitude(mk->voice_player.conf.volume, -6));
2614 if (mk_dinput(mode6_low_volume))
2615 mk->mode6_handler.set_volume(1.0);
2617 state = STATE_RUNNING;
2618 cancel = CANCEL_NONE;
2619 memset(&action, 0, sizeof(action));
2624 MK_VIII::SelfTestHandler::stop ()
2626 if (state != STATE_NONE)
2628 if (state != STATE_START)
2630 mk->voice_player.stop(VoicePlayer::STOP_NOW);
2631 mk->voice_player.set_volume(mk_dinput(audio_inhibit) ? 0.0 : mk->voice_player.conf.volume);
2633 if (mk_dinput(mode6_low_volume))
2634 mk->mode6_handler.set_volume(modify_amplitude(1.0, -6));
2636 memcpy(&mk->io_handler.outputs, &saved_outputs, sizeof(mk->io_handler.outputs));
2639 button_pressed = false;
2645 MK_VIII::SelfTestHandler::handle_button_event (bool value)
2647 if (state == STATE_NONE)
2650 state = STATE_START;
2652 else if (state == STATE_START)
2655 state = STATE_NONE; // cancel the not-yet-started test
2657 else if (cancel == CANCEL_NONE)
2661 assert(! button_pressed);
2662 button_pressed = true;
2663 button_press_timestamp = globals->get_sim_time_sec();
2669 if (globals->get_sim_time_sec() - button_press_timestamp < 2)
2670 cancel = CANCEL_SHORT;
2672 cancel = CANCEL_LONG;
2679 MK_VIII::SelfTestHandler::update ()
2681 if (state == STATE_NONE)
2683 else if (state == STATE_START)
2685 if (mk->state_handler.ground && ! mk->io_handler.steep_approach())
2695 if (button_pressed && cancel == CANCEL_NONE)
2697 if (globals->get_sim_time_sec() - button_press_timestamp >= 2)
2698 cancel = CANCEL_LONG;
2702 if (! mk->state_handler.ground || cancel != CANCEL_NONE)
2708 if (test_bits(action.flags, ACTION_SLEEP))
2710 if (globals->get_sim_time_sec() - sleep_start < action.sleep_duration)
2713 if (test_bits(action.flags, ACTION_VOICE))
2715 if (mk->voice_player.voice)
2718 if (test_bits(action.flags, ACTION_DISCRETE_ON_OFF))
2719 *action.discrete = false;
2723 if (test_bits(action.flags, ACTION_SLEEP))
2724 sleep_start = globals->get_sim_time_sec();
2725 if (test_bits(action.flags, ACTION_DONE))
2734 ///////////////////////////////////////////////////////////////////////////////
2735 // AlertHandler ///////////////////////////////////////////////////////////////
2736 ///////////////////////////////////////////////////////////////////////////////
2739 MK_VIII::AlertHandler::select_voice_alerts (unsigned int test)
2741 if (has_alerts(test))
2743 voice_alerts = alerts & test;
2748 voice_alerts &= ~test;
2749 if (voice_alerts == 0)
2750 mk->voice_player.stop();
2757 MK_VIII::AlertHandler::boot ()
2763 MK_VIII::AlertHandler::reposition ()
2767 mk->io_handler.set_lamp(IOHandler::LAMP_NONE);
2768 mk->voice_player.stop(VoicePlayer::STOP_NOW);
2772 MK_VIII::AlertHandler::reset ()
2777 repeated_alerts = 0;
2778 altitude_callout_voice = NULL;
2782 MK_VIII::AlertHandler::update ()
2784 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
2787 // Lamps and voices are prioritized according to [SPEC] 6.9.2.
2789 // Voices which can interrupt other voices (VoicePlayer::PLAY_NOW)
2790 // are specified by [INSTALL] appendix E 5.3.5.
2792 // When a voice is overriden by a higher priority voice and the
2793 // overriding voice stops, we restore the previous voice if it was
2794 // looped (this is not specified by [SPEC] but seems to make sense).
2798 if (has_alerts(ALERT_MODE1_PULL_UP | ALERT_MODE2A | ALERT_MODE2B))
2799 mk->io_handler.set_lamp(IOHandler::LAMP_WARNING);
2800 else if (has_alerts(ALERT_MODE1_SINK_RATE
2801 | ALERT_MODE2A_PREFACE
2802 | ALERT_MODE2B_PREFACE
2803 | ALERT_MODE2A_ALTITUDE_GAIN_TERRAIN_CLOSING
2804 | ALERT_MODE2A_ALTITUDE_GAIN
2805 | ALERT_MODE2B_LANDING_MODE
2807 | ALERT_MODE4_TOO_LOW_FLAPS
2808 | ALERT_MODE4_TOO_LOW_GEAR
2809 | ALERT_MODE4AB_TOO_LOW_TERRAIN
2810 | ALERT_MODE4C_TOO_LOW_TERRAIN
2811 | ALERT_TCF_TOO_LOW_TERRAIN))
2812 mk->io_handler.set_lamp(IOHandler::LAMP_CAUTION);
2813 else if (has_alerts(ALERT_MODE5_SOFT | ALERT_MODE5_HARD))
2814 mk->io_handler.set_lamp(IOHandler::LAMP_GLIDESLOPE);
2816 mk->io_handler.set_lamp(IOHandler::LAMP_NONE);
2820 if (select_voice_alerts(ALERT_MODE1_PULL_UP))
2822 if (! has_old_alerts(ALERT_MODE1_PULL_UP))
2824 if (mk->voice_player.voice != mk_voice(sink_rate_pause_sink_rate))
2825 mk->voice_player.play(mk_voice(sink_rate), VoicePlayer::PLAY_NOW);
2826 mk->voice_player.play(mk_voice(pull_up), VoicePlayer::PLAY_LOOPED);
2829 else if (select_voice_alerts(ALERT_MODE2A_PREFACE | ALERT_MODE2B_PREFACE))
2831 if (! has_old_alerts(ALERT_MODE2A_PREFACE | ALERT_MODE2B_PREFACE))
2832 mk->voice_player.play(mk_voice(terrain_pause_terrain), VoicePlayer::PLAY_NOW);
2834 else if (select_voice_alerts(ALERT_MODE2A | ALERT_MODE2B))
2836 if (mk->voice_player.voice != mk_voice(pull_up))
2837 mk->voice_player.play(mk_voice(pull_up), VoicePlayer::PLAY_NOW | VoicePlayer::PLAY_LOOPED);
2839 else if (select_voice_alerts(ALERT_MODE2A_ALTITUDE_GAIN_TERRAIN_CLOSING | ALERT_MODE2B_LANDING_MODE))
2841 if (mk->voice_player.voice != mk_voice(terrain))
2842 mk->voice_player.play(mk_voice(terrain), VoicePlayer::PLAY_LOOPED);
2844 else if (select_voice_alerts(ALERT_MODE6_MINIMUMS))
2846 if (! has_old_alerts(ALERT_MODE6_MINIMUMS))
2847 mk->voice_player.play(mk_voice(minimums_minimums));
2849 else if (select_voice_alerts(ALERT_MODE4AB_TOO_LOW_TERRAIN | ALERT_MODE4C_TOO_LOW_TERRAIN))
2851 if (must_play_voice(ALERT_MODE4AB_TOO_LOW_TERRAIN | ALERT_MODE4C_TOO_LOW_TERRAIN))
2852 mk->voice_player.play(mk_voice(too_low_terrain));
2854 else if (select_voice_alerts(ALERT_TCF_TOO_LOW_TERRAIN))
2856 if (must_play_voice(ALERT_TCF_TOO_LOW_TERRAIN))
2857 mk->voice_player.play(mk_voice(too_low_terrain));
2859 else if (select_voice_alerts(ALERT_MODE6_ALTITUDE_CALLOUT))
2861 if (! has_old_alerts(ALERT_MODE6_ALTITUDE_CALLOUT))
2863 assert(altitude_callout_voice != NULL);
2864 mk->voice_player.play(altitude_callout_voice);
2867 else if (select_voice_alerts(ALERT_MODE4_TOO_LOW_GEAR))
2869 if (must_play_voice(ALERT_MODE4_TOO_LOW_GEAR))
2870 mk->voice_player.play(mk->mode4_handler.conf.voice_too_low_gear);
2872 else if (select_voice_alerts(ALERT_MODE4_TOO_LOW_FLAPS))
2874 if (must_play_voice(ALERT_MODE4_TOO_LOW_FLAPS))
2875 mk->voice_player.play(mk_voice(too_low_flaps));
2877 else if (select_voice_alerts(ALERT_MODE1_SINK_RATE))
2879 if (must_play_voice(ALERT_MODE1_SINK_RATE))
2880 mk->voice_player.play(mk_voice(sink_rate_pause_sink_rate));
2881 // [SPEC] 6.2.1: "During the time that the voice message for the
2882 // outer envelope is inhibited and the caution/warning lamp is
2883 // on, the Mode 5 alert message is allowed to annunciate for
2884 // excessive glideslope deviation conditions."
2885 else if (mk->voice_player.voice != mk_voice(sink_rate_pause_sink_rate)
2886 && mk->voice_player.next_voice != mk_voice(sink_rate_pause_sink_rate))
2888 if (has_alerts(ALERT_MODE5_HARD))
2890 voice_alerts |= ALERT_MODE5_HARD;
2891 if (mk->voice_player.voice != mk_voice(hard_glideslope))
2892 mk->voice_player.play(mk_voice(hard_glideslope), VoicePlayer::PLAY_LOOPED);
2894 else if (has_alerts(ALERT_MODE5_SOFT))
2896 voice_alerts |= ALERT_MODE5_SOFT;
2897 if (must_play_voice(ALERT_MODE5_SOFT))
2898 mk->voice_player.play(mk_voice(soft_glideslope));
2902 else if (select_voice_alerts(ALERT_MODE3))
2904 if (must_play_voice(ALERT_MODE3))
2905 mk->voice_player.play(mk_voice(dont_sink_pause_dont_sink));
2907 else if (select_voice_alerts(ALERT_MODE5_HARD))
2909 if (mk->voice_player.voice != mk_voice(hard_glideslope))
2910 mk->voice_player.play(mk_voice(hard_glideslope), VoicePlayer::PLAY_LOOPED);
2912 else if (select_voice_alerts(ALERT_MODE5_SOFT))
2914 if (must_play_voice(ALERT_MODE5_SOFT))
2915 mk->voice_player.play(mk_voice(soft_glideslope));
2917 else if (select_voice_alerts(ALERT_MODE6_LOW_BANK_ANGLE_3))
2919 if (mk->voice_player.voice != mk_voice(bank_angle_bank_angle_3))
2920 mk->voice_player.play(mk_voice(bank_angle_bank_angle_3), VoicePlayer::PLAY_LOOPED);
2922 else if (select_voice_alerts(ALERT_MODE6_HIGH_BANK_ANGLE_3))
2924 if (mk->voice_player.voice != mk_voice(bank_angle_pause_bank_angle_3))
2925 mk->voice_player.play(mk_voice(bank_angle_pause_bank_angle_3), VoicePlayer::PLAY_LOOPED);
2927 else if (select_voice_alerts(ALERT_MODE6_LOW_BANK_ANGLE_2))
2929 if (! has_old_alerts(ALERT_MODE6_LOW_BANK_ANGLE_2 | ALERT_MODE6_HIGH_BANK_ANGLE_2))
2930 mk->voice_player.play(mk_voice(bank_angle_bank_angle));
2932 else if (select_voice_alerts(ALERT_MODE6_HIGH_BANK_ANGLE_2))
2934 if (! has_old_alerts(ALERT_MODE6_LOW_BANK_ANGLE_2 | ALERT_MODE6_HIGH_BANK_ANGLE_2))
2935 mk->voice_player.play(mk_voice(bank_angle_pause_bank_angle));
2937 else if (select_voice_alerts(ALERT_MODE6_LOW_BANK_ANGLE_1))
2939 if (! has_old_alerts(ALERT_MODE6_LOW_BANK_ANGLE_1 | ALERT_MODE6_HIGH_BANK_ANGLE_1))
2940 mk->voice_player.play(mk_voice(bank_angle_bank_angle));
2942 else if (select_voice_alerts(ALERT_MODE6_HIGH_BANK_ANGLE_1))
2944 if (! has_old_alerts(ALERT_MODE6_LOW_BANK_ANGLE_1 | ALERT_MODE6_HIGH_BANK_ANGLE_1))
2945 mk->voice_player.play(mk_voice(bank_angle_pause_bank_angle));
2950 old_alerts = alerts;
2951 repeated_alerts = 0;
2952 altitude_callout_voice = NULL;
2956 MK_VIII::AlertHandler::set_alerts (unsigned int _alerts,
2958 VoicePlayer::Voice *_altitude_callout_voice)
2961 if (test_bits(flags, ALERT_FLAG_REPEAT))
2962 repeated_alerts |= _alerts;
2963 if (_altitude_callout_voice)
2964 altitude_callout_voice = _altitude_callout_voice;
2968 MK_VIII::AlertHandler::unset_alerts (unsigned int _alerts)
2971 repeated_alerts &= ~_alerts;
2974 ///////////////////////////////////////////////////////////////////////////////
2975 // StateHandler ///////////////////////////////////////////////////////////////
2976 ///////////////////////////////////////////////////////////////////////////////
2979 MK_VIII::StateHandler::update_ground ()
2983 if (! mk_ainput(computed_airspeed).ncd && mk_ainput(computed_airspeed).get() > 60
2984 && ! mk_data(radio_altitude).ncd && mk_data(radio_altitude).get() > 30)
2986 if (potentially_airborne_timer.start_or_elapsed() > 1)
2990 potentially_airborne_timer.stop();
2994 if (! mk_ainput(computed_airspeed).ncd && mk_ainput(computed_airspeed).get() < 40
2995 && ! mk_data(radio_altitude).ncd && mk_data(radio_altitude).get() < 30)
3001 MK_VIII::StateHandler::enter_ground ()
3004 mk->io_handler.enter_ground();
3006 // [SPEC] 6.0.1 does not specify this, but it seems to be an
3007 // omission; at this point, we must make sure that we are in takeoff
3008 // mode (otherwise the next takeoff might happen in approach mode).
3014 MK_VIII::StateHandler::leave_ground ()
3017 mk->mode2_handler.leave_ground();
3021 MK_VIII::StateHandler::update_takeoff ()
3025 // [SPEC] 6.0.2 is somewhat confusing: it mentions hardcoded
3026 // terrain clearance (500, 750) and airspeed (178, 200) values,
3027 // but it also mentions the mode 4A boundary, which varies as a
3028 // function of aircraft type. We consider that the mentioned
3029 // values are examples, and that we should use the mode 4A upper
3032 if (! mk_data(terrain_clearance).ncd
3033 && ! mk_ainput(computed_airspeed).ncd
3034 && mk_data(terrain_clearance).get() > mk->mode4_handler.get_upper_agl(mk->mode4_handler.conf.modes->ac))
3039 if (! mk_data(radio_altitude).ncd
3040 && mk_data(radio_altitude).get() < mk->mode4_handler.conf.modes->b->min_agl1
3041 && mk->io_handler.flaps_down()
3042 && mk_dinput(landing_gear))
3048 MK_VIII::StateHandler::enter_takeoff ()
3051 mk->io_handler.enter_takeoff();
3052 mk->mode2_handler.enter_takeoff();
3053 mk->mode3_handler.enter_takeoff();
3054 mk->mode6_handler.enter_takeoff();
3058 MK_VIII::StateHandler::leave_takeoff ()
3061 mk->mode6_handler.leave_takeoff();
3065 MK_VIII::StateHandler::post_reposition ()
3067 // Synchronize the state with the current situation.
3070 (! mk_ainput(computed_airspeed).ncd && mk_ainput(computed_airspeed).get() > 60
3071 && ! mk_data(radio_altitude).ncd && mk_data(radio_altitude).get() > 30);
3073 bool _takeoff = _ground;
3075 if (ground && ! _ground)
3077 else if (! ground && _ground)
3080 if (takeoff && ! _takeoff)
3082 else if (! takeoff && _takeoff)
3087 MK_VIII::StateHandler::update ()
3089 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
3096 ///////////////////////////////////////////////////////////////////////////////
3097 // Mode1Handler ///////////////////////////////////////////////////////////////
3098 ///////////////////////////////////////////////////////////////////////////////
3101 MK_VIII::Mode1Handler::get_pull_up_bias ()
3103 // [PILOT] page 54: "Steep Approach has priority over Flap Override
3104 // if selected simultaneously."
3106 if (mk->io_handler.steep_approach())
3108 else if (conf.envelopes->flap_override_bias && mk->io_handler.flap_override())
3115 MK_VIII::Mode1Handler::is_pull_up ()
3117 if (! mk->io_handler.gpws_inhibit()
3118 && ! mk->state_handler.ground // [1]
3119 && ! mk_data(radio_altitude).ncd
3120 && ! mk_data(barometric_altitude_rate).ncd
3121 && mk_data(radio_altitude).get() > conf.envelopes->min_agl)
3123 if (mk_data(radio_altitude).get() < conf.envelopes->pull_up_max_agl1)
3125 if (mk_data(radio_altitude).get() < conf.envelopes->pull_up_min_agl1(mk_data(barometric_altitude_rate).get() + get_pull_up_bias()))
3128 else if (mk_data(radio_altitude).get() < conf.envelopes->pull_up_max_agl2)
3130 if (mk_data(radio_altitude).get() < conf.envelopes->pull_up_min_agl2(mk_data(barometric_altitude_rate).get() + get_pull_up_bias()))
3139 MK_VIII::Mode1Handler::update_pull_up ()
3143 if (! mk_test_alert(MODE1_PULL_UP))
3145 // [SPEC] 6.2.1: at least one sink rate must be issued
3146 // before switching to pull up; if no sink rate has
3147 // occurred, a 0.2 second delay is used.
3148 if (mk->voice_player.voice == mk_voice(sink_rate_pause_sink_rate)
3149 || pull_up_timer.start_or_elapsed() >= 0.2)
3150 mk_set_alerts(mk_alert(MODE1_PULL_UP));
3155 pull_up_timer.stop();
3156 mk_unset_alerts(mk_alert(MODE1_PULL_UP));
3161 MK_VIII::Mode1Handler::get_sink_rate_bias ()
3163 // [PILOT] page 54: "Steep Approach has priority over Flap Override
3164 // if selected simultaneously."
3166 if (mk->io_handler.steep_approach())
3168 else if (conf.envelopes->flap_override_bias && mk->io_handler.flap_override())
3170 else if (! mk_data(glideslope_deviation_dots).ncd)
3174 if (mk_data(glideslope_deviation_dots).get() <= -2)
3176 else if (mk_data(glideslope_deviation_dots).get() < 0)
3177 bias = -150 * mk_data(glideslope_deviation_dots).get();
3179 if (mk_data(radio_altitude).get() < 100)
3180 bias *= 0.01 * mk_data(radio_altitude).get();
3189 MK_VIII::Mode1Handler::is_sink_rate ()
3191 return ! mk->io_handler.gpws_inhibit()
3192 && ! mk->state_handler.ground // [1]
3193 && ! mk_data(radio_altitude).ncd
3194 && ! mk_data(barometric_altitude_rate).ncd
3195 && mk_data(radio_altitude).get() > conf.envelopes->min_agl
3196 && mk_data(radio_altitude).get() < 2450
3197 && mk_data(radio_altitude).get() < -572 - 0.6035 * (mk_data(barometric_altitude_rate).get() + get_sink_rate_bias());
3201 MK_VIII::Mode1Handler::get_sink_rate_tti ()
3203 return mk_data(radio_altitude).get() / fabs(mk_data(barometric_altitude_rate).get());
3207 MK_VIII::Mode1Handler::update_sink_rate ()
3211 if (mk_test_alert(MODE1_SINK_RATE))
3213 double tti = get_sink_rate_tti();
3214 if (tti <= sink_rate_tti * 0.8)
3216 // ~20% degradation, warn again and store the new reference tti
3217 sink_rate_tti = tti;
3218 mk_repeat_alert(mk_alert(MODE1_SINK_RATE));
3223 if (sink_rate_timer.start_or_elapsed() >= 0.8)
3225 mk_set_alerts(mk_alert(MODE1_SINK_RATE));
3226 sink_rate_tti = get_sink_rate_tti();
3232 sink_rate_timer.stop();
3233 mk_unset_alerts(mk_alert(MODE1_SINK_RATE));
3238 MK_VIII::Mode1Handler::update ()
3240 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
3247 ///////////////////////////////////////////////////////////////////////////////
3248 // Mode2Handler ///////////////////////////////////////////////////////////////
3249 ///////////////////////////////////////////////////////////////////////////////
3252 MK_VIII::Mode2Handler::ClosureRateFilter::limit_radio_altitude_rate (double r)
3254 // Limit radio altitude rate according to aircraft configuration,
3255 // allowing maximum sensitivity during cruise while providing
3256 // progressively less sensitivity during the landing phases of
3259 if (! mk_data(glideslope_deviation_dots).ncd && fabs(mk_data(glideslope_deviation_dots).get()) <= 2)
3260 { // gs deviation <= +- 2 dots
3261 if (mk_dinput(landing_gear) && mk->io_handler.flaps_down())
3262 SG_CLAMP_RANGE(r, -1000.0, 3000.0);
3263 else if (mk_dinput(landing_gear) || mk->io_handler.flaps_down())
3264 SG_CLAMP_RANGE(r, 0.0, 4000.0);
3266 SG_CLAMP_RANGE(r, 1000.0, 5000.0);
3269 { // no ILS, or gs deviation > +- 2 dots
3270 if (mk_dinput(landing_gear) && mk->io_handler.flaps_down())
3271 SG_CLAMP_RANGE(r, 0.0, 4000.0);
3272 else if (mk_dinput(landing_gear) || mk->io_handler.flaps_down())
3273 SG_CLAMP_RANGE(r, 1000.0, 5000.0);
3281 MK_VIII::Mode2Handler::ClosureRateFilter::init ()
3284 last_ra.set(&mk_data(radio_altitude));
3285 last_ba.set(&mk_ainput(uncorrected_barometric_altitude));
3292 MK_VIII::Mode2Handler::ClosureRateFilter::update ()
3294 double elapsed = timer.start_or_elapsed();
3298 if (! mk_data(radio_altitude).ncd && ! mk_ainput(uncorrected_barometric_altitude).ncd)
3300 if (! last_ra.ncd && ! last_ba.ncd)
3302 // compute radio and barometric altitude rates (positive value = descent)
3303 double ra_rate = -(mk_data(radio_altitude).get() - last_ra.get()) / elapsed * 60;
3304 double ba_rate = -(mk_ainput(uncorrected_barometric_altitude).get() - last_ba.get()) / elapsed * 60;
3306 // limit radio altitude rate according to aircraft configuration
3307 ra_rate = limit_radio_altitude_rate(ra_rate);
3309 // apply a low-pass filter to the radio altitude rate
3310 ra_rate = ra_filter.filter(ra_rate);
3311 // apply a high-pass filter to the barometric altitude rate
3312 ba_rate = ba_filter.filter(ba_rate);
3314 // combine both rates to obtain a closure rate
3315 output.set(ra_rate + ba_rate);
3328 last_ra.set(&mk_data(radio_altitude));
3329 last_ba.set(&mk_ainput(uncorrected_barometric_altitude));
3333 MK_VIII::Mode2Handler::b_conditions ()
3335 return mk->io_handler.flaps_down()
3336 || (! mk_data(glideslope_deviation_dots).ncd && fabs(mk_data(glideslope_deviation_dots).get()) < 2)
3337 || takeoff_timer.running;
3341 MK_VIII::Mode2Handler::is_a ()
3343 if (! mk->io_handler.gpws_inhibit()
3344 && ! mk->state_handler.ground // [1]
3345 && ! mk_data(radio_altitude).ncd
3346 && ! mk_ainput(computed_airspeed).ncd
3347 && ! closure_rate_filter.output.ncd
3348 && ! b_conditions())
3350 if (mk_data(radio_altitude).get() < 1220)
3352 if (mk_data(radio_altitude).get() < -1579 + 0.7895 * closure_rate_filter.output.get())
3359 if (mk_ainput(computed_airspeed).get() <= conf->airspeed1)
3361 else if (mk_ainput(computed_airspeed).get() >= conf->airspeed2)
3364 upper_limit = 1650 + 8.9 * (mk_ainput(computed_airspeed).get() - conf->airspeed1);
3366 if (mk_data(radio_altitude).get() < upper_limit)
3368 if (mk_data(radio_altitude).get() < 522 + 0.1968 * closure_rate_filter.output.get())
3378 MK_VIII::Mode2Handler::is_b ()
3380 if (! mk->io_handler.gpws_inhibit()
3381 && ! mk->state_handler.ground // [1]
3382 && ! mk_data(radio_altitude).ncd
3383 && ! mk_data(barometric_altitude_rate).ncd
3384 && ! closure_rate_filter.output.ncd
3386 && mk_data(radio_altitude).get() < 789)
3390 if (mk->io_handler.flaps_down())
3392 if (mk_data(barometric_altitude_rate).get() > -400)
3394 else if (mk_data(barometric_altitude_rate).get() < -1000)
3397 lower_limit = -66.777 - 0.667 * mk_data(barometric_altitude_rate).get();
3402 if (mk_data(radio_altitude).get() > lower_limit)
3404 if (mk_data(radio_altitude).get() < -1579 + 0.7895 * closure_rate_filter.output.get())
3413 MK_VIII::Mode2Handler::check_pull_up (unsigned int preface_alert,
3416 if (pull_up_timer.running)
3418 if (pull_up_timer.elapsed() >= 1)
3420 mk_unset_alerts(preface_alert);
3421 mk_set_alerts(alert);
3426 if (! mk->voice_player.voice)
3427 pull_up_timer.start();
3432 MK_VIII::Mode2Handler::update_a ()
3436 if (mk_test_alert(MODE2A_PREFACE))
3437 check_pull_up(mk_alert(MODE2A_PREFACE), mk_alert(MODE2A));
3438 else if (! mk_test_alert(MODE2A))
3440 mk_unset_alerts(mk_alert(MODE2A_ALTITUDE_GAIN) | mk_alert(MODE2A_ALTITUDE_GAIN_TERRAIN_CLOSING));
3441 mk_set_alerts(mk_alert(MODE2A_PREFACE));
3442 a_start_time = globals->get_sim_time_sec();
3443 pull_up_timer.stop();
3448 if (mk_test_alert(MODE2A_ALTITUDE_GAIN))
3450 if (mk->io_handler.gpws_inhibit()
3451 || mk->state_handler.ground // [1]
3452 || a_altitude_gain_timer.elapsed() >= 45
3453 || mk_data(radio_altitude).ncd
3454 || mk_ainput(uncorrected_barometric_altitude).ncd
3455 || mk_ainput(uncorrected_barometric_altitude).get() - a_altitude_gain_alt >= 300
3456 // [PILOT] page 12: "the visual alert will remain on
3457 // until [...] landing flaps or the flap override switch
3459 || mk->io_handler.flaps_down())
3461 // exit altitude gain mode
3462 a_altitude_gain_timer.stop();
3463 mk_unset_alerts(mk_alert(MODE2A_ALTITUDE_GAIN) | mk_alert(MODE2A_ALTITUDE_GAIN_TERRAIN_CLOSING));
3467 // [SPEC] 6.2.2.2: "If the terrain starts to fall away
3468 // during this altitude gain time, the voice will shut
3470 if (closure_rate_filter.output.get() < 0)
3471 mk_unset_alerts(mk_alert(MODE2A_ALTITUDE_GAIN_TERRAIN_CLOSING));
3474 else if (mk_test_alerts(mk_alert(MODE2A_PREFACE) | mk_alert(MODE2A)))
3476 mk_unset_alerts(mk_alert(MODE2A_PREFACE) | mk_alert(MODE2A));
3478 if (! mk->io_handler.gpws_inhibit()
3479 && ! mk->state_handler.ground // [1]
3480 && globals->get_sim_time_sec() - a_start_time > 3
3481 && ! mk->io_handler.flaps_down()
3482 && ! mk_data(radio_altitude).ncd
3483 && ! mk_ainput(uncorrected_barometric_altitude).ncd)
3485 // [SPEC] 6.2.2.2: mode 2A envelope violated for more
3486 // than 3 seconds, enable altitude gain feature
3488 a_altitude_gain_timer.start();
3489 a_altitude_gain_alt = mk_ainput(uncorrected_barometric_altitude).get();
3491 unsigned int alerts = mk_alert(MODE2A_ALTITUDE_GAIN);
3492 if (closure_rate_filter.output.get() > 0)
3493 alerts |= mk_alert(MODE2A_ALTITUDE_GAIN_TERRAIN_CLOSING);
3495 mk_set_alerts(alerts);
3502 MK_VIII::Mode2Handler::update_b ()
3506 // handle normal mode
3508 if (b && (! mk_dinput(landing_gear) || ! mk->io_handler.flaps_down()))
3510 if (mk_test_alert(MODE2B_PREFACE))
3511 check_pull_up(mk_alert(MODE2B_PREFACE), mk_alert(MODE2B));
3512 else if (! mk_test_alert(MODE2B))
3514 mk_set_alerts(mk_alert(MODE2B_PREFACE));
3515 pull_up_timer.stop();
3519 mk_unset_alerts(mk_alert(MODE2B_PREFACE) | mk_alert(MODE2B));
3521 // handle landing mode ([SPEC] 6.2.2.3: "If both landing gear and
3522 // flaps are in the landing configuration, then the message will be
3525 if (b && mk_dinput(landing_gear) && mk->io_handler.flaps_down())
3526 mk_set_alerts(mk_alert(MODE2B_LANDING_MODE));
3528 mk_unset_alerts(mk_alert(MODE2B_LANDING_MODE));
3532 MK_VIII::Mode2Handler::boot ()
3534 closure_rate_filter.init();
3538 MK_VIII::Mode2Handler::power_off ()
3540 // [SPEC] 6.0.4: "This latching function is not power saved and a
3541 // system reset will force it false."
3542 takeoff_timer.stop();
3546 MK_VIII::Mode2Handler::leave_ground ()
3548 // takeoff, reset timer
3549 takeoff_timer.start();
3553 MK_VIII::Mode2Handler::enter_takeoff ()
3555 // reset timer, in case it's a go-around
3556 takeoff_timer.start();
3560 MK_VIII::Mode2Handler::update ()
3562 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
3565 closure_rate_filter.update();
3567 if (takeoff_timer.running && takeoff_timer.elapsed() >= 60)
3568 takeoff_timer.stop();
3574 ///////////////////////////////////////////////////////////////////////////////
3575 // Mode3Handler ///////////////////////////////////////////////////////////////
3576 ///////////////////////////////////////////////////////////////////////////////
3579 MK_VIII::Mode3Handler::max_alt_loss (double _bias)
3581 return conf->max_alt_loss(mk->io_handler.flap_override(), mk_data(radio_altitude).get()) + mk_data(radio_altitude).get() * _bias;
3585 MK_VIII::Mode3Handler::get_bias (double initial_bias, double alt_loss)
3587 if (mk_data(radio_altitude).get() > 0)
3588 while (alt_loss > max_alt_loss(initial_bias))
3589 initial_bias += 0.2;
3591 return initial_bias;
3595 MK_VIII::Mode3Handler::is (double *alt_loss)
3597 if (has_descent_alt)
3599 int max_agl = conf->max_agl(mk->io_handler.flap_override());
3601 if (mk_ainput(uncorrected_barometric_altitude).ncd
3602 || mk_ainput(uncorrected_barometric_altitude).get() > descent_alt
3603 || mk_data(radio_altitude).ncd
3604 || mk_data(radio_altitude).get() > max_agl)
3607 has_descent_alt = false;
3611 // gear/flaps: [SPEC] 1.3.1.3
3612 if (! mk->io_handler.gpws_inhibit()
3613 && ! mk->state_handler.ground // [1]
3614 && (! mk_dinput(landing_gear) || ! mk->io_handler.flaps_down())
3615 && ! mk_data(barometric_altitude_rate).ncd
3616 && ! mk_ainput(uncorrected_barometric_altitude).ncd
3617 && ! mk_data(radio_altitude).ncd
3618 && mk_data(barometric_altitude_rate).get() < 0)
3620 double _alt_loss = descent_alt - mk_ainput(uncorrected_barometric_altitude).get();
3621 if (armed || (mk_data(radio_altitude).get() > conf->min_agl
3622 && mk_data(radio_altitude).get() < max_agl
3623 && _alt_loss > max_alt_loss(0)))
3625 *alt_loss = _alt_loss;
3633 if (! mk_data(barometric_altitude_rate).ncd
3634 && ! mk_ainput(uncorrected_barometric_altitude).ncd
3635 && mk_data(barometric_altitude_rate).get() < 0)
3637 has_descent_alt = true;
3638 descent_alt = mk_ainput(uncorrected_barometric_altitude).get();
3646 MK_VIII::Mode3Handler::enter_takeoff ()
3649 has_descent_alt = false;
3653 MK_VIII::Mode3Handler::update ()
3655 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
3658 if (mk->state_handler.takeoff)
3662 if (! mk->state_handler.ground /* [1] */ && is(&alt_loss))
3664 if (mk_test_alert(MODE3))
3666 double new_bias = get_bias(bias, alt_loss);
3667 if (new_bias > bias)
3670 mk_repeat_alert(mk_alert(MODE3));
3676 bias = get_bias(0.2, alt_loss);
3677 mk_set_alerts(mk_alert(MODE3));
3684 mk_unset_alerts(mk_alert(MODE3));
3687 ///////////////////////////////////////////////////////////////////////////////
3688 // Mode4Handler ///////////////////////////////////////////////////////////////
3689 ///////////////////////////////////////////////////////////////////////////////
3691 // FIXME: for turbofans, the upper limit should be reduced from 1000
3692 // to 800 ft if a sudden change in radio altitude is detected, in
3693 // order to reduce nuisance alerts caused by overflying another
3694 // aircraft (see [PILOT] page 16).
3697 MK_VIII::Mode4Handler::get_upper_agl (const EnvelopesConfiguration *c)
3699 if (mk_ainput(computed_airspeed).get() >= c->airspeed2)
3701 else if (mk_ainput(computed_airspeed).get() >= c->airspeed1)
3702 return c->min_agl2(mk_ainput(computed_airspeed).get());
3707 const MK_VIII::Mode4Handler::EnvelopesConfiguration *
3708 MK_VIII::Mode4Handler::get_ab_envelope ()
3710 // [SPEC] 6.2.4.1: "The Mode 4B envelope can also be selected by
3711 // setting flaps to landing configuration or by selecting flap
3713 return mk_dinput(landing_gear) || mk->io_handler.flaps_down()
3719 MK_VIII::Mode4Handler::get_bias (double initial_bias, double min_agl)
3721 while (mk_data(radio_altitude).get() < min_agl - min_agl * initial_bias)
3722 initial_bias += 0.2;
3724 return initial_bias;
3728 MK_VIII::Mode4Handler::handle_alert (unsigned int alert,
3732 if (mk_test_alerts(alert))
3734 double new_bias = get_bias(*bias, min_agl);
3735 if (new_bias > *bias)
3738 mk_repeat_alert(alert);
3743 *bias = get_bias(0.2, min_agl);
3744 mk_set_alerts(alert);
3749 MK_VIII::Mode4Handler::update_ab ()
3751 if (! mk->io_handler.gpws_inhibit()
3752 && ! mk->state_handler.ground
3753 && ! mk->state_handler.takeoff
3754 && ! mk_data(radio_altitude).ncd
3755 && ! mk_ainput(computed_airspeed).ncd
3756 && mk_data(radio_altitude).get() > 30)
3758 const EnvelopesConfiguration *c = get_ab_envelope();
3759 if (mk_ainput(computed_airspeed).get() < c->airspeed1)
3761 if (mk_dinput(landing_gear))
3763 if (! mk->io_handler.flaps_down() && mk_data(radio_altitude).get() < c->min_agl1)
3765 handle_alert(mk_alert(MODE4_TOO_LOW_FLAPS), c->min_agl1, &ab_bias);
3771 if (mk_data(radio_altitude).get() < c->min_agl1)
3773 handle_alert(mk_alert(MODE4_TOO_LOW_GEAR), c->min_agl1, &ab_bias);
3780 mk_unset_alerts(mk_alert(MODE4_TOO_LOW_FLAPS) | mk_alert(MODE4_TOO_LOW_GEAR));
3784 MK_VIII::Mode4Handler::update_ab_expanded ()
3786 if (! mk->io_handler.gpws_inhibit()
3787 && ! mk->state_handler.ground
3788 && ! mk->state_handler.takeoff
3789 && ! mk_data(radio_altitude).ncd
3790 && ! mk_ainput(computed_airspeed).ncd
3791 && mk_data(radio_altitude).get() > 30)
3793 const EnvelopesConfiguration *c = get_ab_envelope();
3794 if (mk_ainput(computed_airspeed).get() >= c->airspeed1)
3796 double min_agl = get_upper_agl(c);
3797 if (mk_data(radio_altitude).get() < min_agl)
3799 handle_alert(mk_alert(MODE4AB_TOO_LOW_TERRAIN), min_agl, &ab_expanded_bias);
3805 mk_unset_alerts(mk_alert(MODE4AB_TOO_LOW_TERRAIN));
3809 MK_VIII::Mode4Handler::update_c ()
3811 if (! mk->io_handler.gpws_inhibit()
3812 && ! mk->state_handler.ground // [1]
3813 && mk->state_handler.takeoff
3814 && ! mk_data(radio_altitude).ncd
3815 && ! mk_data(terrain_clearance).ncd
3816 && mk_data(radio_altitude).get() > 30
3817 && (! mk_dinput(landing_gear) || ! mk->io_handler.flaps_down())
3818 && mk_data(radio_altitude).get() < get_upper_agl(conf.modes->ac)
3819 && mk_data(radio_altitude).get() < mk_data(terrain_clearance).get())
3820 handle_alert(mk_alert(MODE4C_TOO_LOW_TERRAIN), mk_data(terrain_clearance).get(), &c_bias);
3822 mk_unset_alerts(mk_alert(MODE4C_TOO_LOW_TERRAIN));
3826 MK_VIII::Mode4Handler::update ()
3828 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
3832 update_ab_expanded();
3836 ///////////////////////////////////////////////////////////////////////////////
3837 // Mode5Handler ///////////////////////////////////////////////////////////////
3838 ///////////////////////////////////////////////////////////////////////////////
3841 MK_VIII::Mode5Handler::is_hard ()
3843 if (mk_data(radio_altitude).get() > 30
3844 && mk_data(radio_altitude).get() < 300
3845 && mk_data(glideslope_deviation_dots).get() > 2)
3847 if (mk_data(radio_altitude).get() < 150)
3849 if (mk_data(radio_altitude).get() > 293 - 71.43 * mk_data(glideslope_deviation_dots).get())
3860 MK_VIII::Mode5Handler::is_soft (double bias)
3862 if (mk_data(radio_altitude).get() > 30)
3864 double bias_dots = 1.3 * bias;
3865 if (mk_data(glideslope_deviation_dots).get() > 1.3 + bias_dots)
3867 if (mk_data(radio_altitude).get() < 150)
3869 if (mk_data(radio_altitude).get() > 243 - 71.43 * (mk_data(glideslope_deviation_dots).get() - bias_dots))
3876 if (mk_data(barometric_altitude_rate).ncd)
3880 if (mk_data(barometric_altitude_rate).get() >= 0)
3882 else if (mk_data(barometric_altitude_rate).get() < -500)
3885 upper_limit = -mk_data(barometric_altitude_rate).get() + 500;
3888 if (mk_data(radio_altitude).get() < upper_limit)
3898 MK_VIII::Mode5Handler::get_soft_bias (double initial_bias)
3900 while (is_soft(initial_bias))
3901 initial_bias += 0.2;
3903 return initial_bias;
3907 MK_VIII::Mode5Handler::update_hard (bool is)
3911 if (! mk_test_alert(MODE5_HARD))
3913 if (hard_timer.start_or_elapsed() >= 0.8)
3914 mk_set_alerts(mk_alert(MODE5_HARD));
3920 mk_unset_alerts(mk_alert(MODE5_HARD));
3925 MK_VIII::Mode5Handler::update_soft (bool is)
3929 if (! mk_test_alert(MODE5_SOFT))
3931 double new_bias = get_soft_bias(soft_bias);
3932 if (new_bias > soft_bias)
3934 soft_bias = new_bias;
3935 mk_repeat_alert(mk_alert(MODE5_SOFT));
3940 if (soft_timer.start_or_elapsed() >= 0.8)
3942 soft_bias = get_soft_bias(0.2);
3943 mk_set_alerts(mk_alert(MODE5_SOFT));
3950 mk_unset_alerts(mk_alert(MODE5_SOFT));
3955 MK_VIII::Mode5Handler::update ()
3957 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
3960 if (! mk->io_handler.gpws_inhibit()
3961 && ! mk->state_handler.ground // [1]
3962 && ! mk_dinput(glideslope_inhibit) // not on backcourse
3963 && ! mk_data(radio_altitude).ncd
3964 && ! mk_data(glideslope_deviation_dots).ncd
3965 && (! mk->io_handler.conf.localizer_enabled
3966 || mk_data(localizer_deviation_dots).ncd
3967 || mk_data(radio_altitude).get() < 500
3968 || fabs(mk_data(localizer_deviation_dots).get()) < 2)
3969 && (! mk->state_handler.takeoff || mk->io_handler.flaps_down())
3970 && mk_dinput(landing_gear)
3971 && ! mk_doutput(glideslope_cancel))
3973 update_hard(is_hard());
3974 update_soft(is_soft(0));
3983 ///////////////////////////////////////////////////////////////////////////////
3984 // Mode6Handler ///////////////////////////////////////////////////////////////
3985 ///////////////////////////////////////////////////////////////////////////////
3987 // keep sorted in descending order
3988 const int MK_VIII::Mode6Handler::altitude_callout_definitions[] =
3989 { 1000, 500, 400, 300, 200, 100, 50, 40, 30, 20, 10 };
3992 MK_VIII::Mode6Handler::reset_minimums ()
3994 minimums_issued = false;
3998 MK_VIII::Mode6Handler::reset_altitude_callouts ()
4000 for (unsigned i = 0; i < n_altitude_callouts; i++)
4001 altitude_callouts_issued[i] = false;
4005 MK_VIII::Mode6Handler::is_playing_altitude_callout ()
4007 for (unsigned i = 0; i < n_altitude_callouts; i++)
4008 if (mk->voice_player.voice == mk_altitude_voice(i)
4009 || mk->voice_player.next_voice == mk_altitude_voice(i))
4016 MK_VIII::Mode6Handler::is_near_minimums (double callout)
4020 if (! mk_data(decision_height).ncd)
4022 double diff = callout - mk_data(decision_height).get();
4024 if (mk_data(radio_altitude).get() >= 200)
4026 if (fabs(diff) <= 30)
4031 if (diff >= -3 && diff <= 6)
4040 MK_VIII::Mode6Handler::is_outside_band (double elevation, double callout)
4043 return elevation < callout - (elevation > 150 ? 20 : 10);
4047 MK_VIII::Mode6Handler::inhibit_smart_500 ()
4051 if (! mk_data(glideslope_deviation_dots).ncd
4052 && ! mk_dinput(glideslope_inhibit) // backcourse
4053 && ! mk_doutput(glideslope_cancel))
4055 if (mk->io_handler.conf.localizer_enabled
4056 && ! mk_data(localizer_deviation_dots).ncd)
4058 if (fabs(mk_data(localizer_deviation_dots).get()) <= 2)
4063 if (fabs(mk_data(glideslope_deviation_dots).get()) <= 2)
4072 MK_VIII::Mode6Handler::boot ()
4074 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
4077 last_decision_height = mk_dinput(decision_height);
4078 last_radio_altitude.set(&mk_data(radio_altitude));
4081 for (unsigned i = 0; i < n_altitude_callouts; i++)
4082 altitude_callouts_issued[i] = ! mk_data(radio_altitude).ncd
4083 && mk_data(radio_altitude).get() <= altitude_callout_definitions[i];
4085 // extrapolated from [SPEC] 6.4.2
4086 minimums_issued = mk_dinput(decision_height);
4088 if (conf.above_field_voice)
4091 get_altitude_above_field(&last_altitude_above_field);
4092 // extrapolated from [SPEC] 6.4.2
4093 above_field_issued = ! last_altitude_above_field.ncd
4094 && last_altitude_above_field.get() < 550;
4099 MK_VIII::Mode6Handler::power_off ()
4101 runway_timer.stop();
4105 MK_VIII::Mode6Handler::enter_takeoff ()
4107 reset_altitude_callouts(); // [SPEC] 6.4.2
4108 reset_minimums(); // omitted by [SPEC]; common sense
4112 MK_VIII::Mode6Handler::leave_takeoff ()
4114 reset_altitude_callouts(); // [SPEC] 6.0.2
4115 reset_minimums(); // [SPEC] 6.0.2
4119 MK_VIII::Mode6Handler::set_volume (float volume)
4121 mk_voice(minimums_minimums)->set_volume(volume);
4122 mk_voice(five_hundred_above)->set_volume(volume);
4123 for (unsigned i = 0; i < n_altitude_callouts; i++)
4124 mk_altitude_voice(i)->set_volume(volume);
4128 MK_VIII::Mode6Handler::altitude_callouts_enabled ()
4130 if (conf.minimums_enabled || conf.smart_500_enabled || conf.above_field_voice)
4133 for (unsigned i = 0; i < n_altitude_callouts; i++)
4134 if (conf.altitude_callouts_enabled[i])
4141 MK_VIII::Mode6Handler::update_minimums ()
4143 if (! mk->io_handler.gpws_inhibit()
4144 && (mk->voice_player.voice == mk_voice(minimums_minimums)
4145 || mk->voice_player.next_voice == mk_voice(minimums_minimums)))
4148 if (! mk->io_handler.gpws_inhibit()
4149 && ! mk->state_handler.ground // [1]
4150 && conf.minimums_enabled
4151 && ! minimums_issued
4152 && mk_dinput(landing_gear)
4153 && mk_dinput(decision_height)
4154 && ! last_decision_height)
4156 minimums_issued = true;
4158 // If an altitude callout is playing, stop it now so that the
4159 // minimums callout can be played (note that proper connection
4160 // and synchronization of the radio-altitude ARINC 529 input,
4161 // decision-height discrete and decision-height ARINC 529 input
4162 // will prevent an altitude callout from being played near the
4163 // decision height).
4165 if (is_playing_altitude_callout())
4166 mk->voice_player.stop(VoicePlayer::STOP_NOW);
4168 mk_set_alerts(mk_alert(MODE6_MINIMUMS));
4171 mk_unset_alerts(mk_alert(MODE6_MINIMUMS));
4174 last_decision_height = mk_dinput(decision_height);
4178 MK_VIII::Mode6Handler::update_altitude_callouts ()
4180 if (! mk->io_handler.gpws_inhibit() && is_playing_altitude_callout())
4183 if (! mk->io_handler.gpws_inhibit()
4184 && ! mk->state_handler.ground // [1]
4185 && ! mk_data(radio_altitude).ncd)
4186 for (unsigned i = 0; i < n_altitude_callouts && mk_data(radio_altitude).get() <= altitude_callout_definitions[i]; i++)
4187 if ((conf.altitude_callouts_enabled[i]
4188 || (altitude_callout_definitions[i] == 500
4189 && conf.smart_500_enabled))
4190 && ! altitude_callouts_issued[i]
4191 && (last_radio_altitude.ncd
4192 || last_radio_altitude.get() > altitude_callout_definitions[i]))
4194 // lock out all callouts superior or equal to this one
4195 for (unsigned j = 0; j <= i; j++)
4196 altitude_callouts_issued[j] = true;
4198 altitude_callouts_issued[i] = true;
4199 if (! is_near_minimums(altitude_callout_definitions[i])
4200 && ! is_outside_band(mk_data(radio_altitude).get(), altitude_callout_definitions[i])
4201 && (! conf.smart_500_enabled
4202 || altitude_callout_definitions[i] != 500
4203 || ! inhibit_smart_500()))
4205 mk->alert_handler.set_altitude_callout_alert(mk_altitude_voice(i));
4210 mk_unset_alerts(mk_alert(MODE6_ALTITUDE_CALLOUT));
4213 last_radio_altitude.set(&mk_data(radio_altitude));
4217 MK_VIII::Mode6Handler::test_runway (const FGRunway *_runway)
4219 if (_runway->lengthFt() < mk->conf.runway_database)
4223 SGGeod::fromDeg(mk_data(gps_longitude).get(), mk_data(gps_latitude).get()));
4225 // get distance to threshold
4226 double distance, az1, az2;
4227 SGGeodesy::inverse(pos, _runway->threshold(), az1, az2, distance);
4228 return distance * SG_METER_TO_NM <= 5;
4232 MK_VIII::Mode6Handler::test_airport (const FGAirport *airport)
4234 for (unsigned int r=0; r<airport->numRunways(); ++r) {
4235 FGRunway* rwy(airport->getRunwayByIndex(r));
4237 if (test_runway(rwy)) return true;
4243 bool MK_VIII::Mode6Handler::AirportFilter::passAirport(FGAirport* a) const
4245 bool ok = self->test_airport(a);
4250 MK_VIII::Mode6Handler::update_runway ()
4253 if (mk_data(gps_latitude).ncd || mk_data(gps_longitude).ncd) {
4258 // Search for the closest runway threshold in range 5
4259 // nm. Passing 30nm to
4260 // get_closest_airport() provides enough margin for large
4261 // airports, which may have a runway located far away from the
4262 // airport's reference point.
4263 AirportFilter filter(this);
4264 FGPositionedRef apt = FGPositioned::findClosest(
4265 SGGeod::fromDeg(mk_data(gps_longitude).get(), mk_data(gps_latitude).get()),
4268 runway.elevation = apt->elevation();
4271 has_runway.set(apt != NULL);
4275 MK_VIII::Mode6Handler::get_altitude_above_field (Parameter<double> *parameter)
4277 if (! has_runway.ncd && has_runway.get() && ! mk_data(geometric_altitude).ncd)
4278 parameter->set(mk_data(geometric_altitude).get() - runway.elevation);
4284 MK_VIII::Mode6Handler::update_above_field_callout ()
4286 if (! conf.above_field_voice)
4289 // update_runway() has to iterate over the whole runway database
4290 // (which contains thousands of runways), so it would be unwise to
4291 // run it every frame. Run it every 3 seconds. Note that the first
4292 // iteration is run in boot().
4293 if (runway_timer.start_or_elapsed() >= 3)
4296 runway_timer.start();
4299 Parameter<double> altitude_above_field;
4300 get_altitude_above_field(&altitude_above_field);
4302 if (! mk->io_handler.gpws_inhibit()
4303 && (mk->voice_player.voice == conf.above_field_voice
4304 || mk->voice_player.next_voice == conf.above_field_voice))
4307 // handle reset term
4308 if (above_field_issued)
4310 if ((! has_runway.ncd && ! has_runway.get())
4311 || (! altitude_above_field.ncd && altitude_above_field.get() > 700))
4312 above_field_issued = false;
4315 if (! mk->io_handler.gpws_inhibit()
4316 && ! mk->state_handler.ground // [1]
4317 && ! above_field_issued
4318 && ! altitude_above_field.ncd
4319 && altitude_above_field.get() < 550
4320 && (last_altitude_above_field.ncd
4321 || last_altitude_above_field.get() >= 550))
4323 above_field_issued = true;
4325 if (! is_outside_band(altitude_above_field.get(), 500))
4327 mk->alert_handler.set_altitude_callout_alert(conf.above_field_voice);
4332 mk_unset_alerts(mk_alert(MODE6_ALTITUDE_CALLOUT));
4335 last_altitude_above_field.set(&altitude_above_field);
4339 MK_VIII::Mode6Handler::is_bank_angle (double abs_roll_angle, double bias)
4341 return conf.is_bank_angle(&mk_data(radio_altitude),
4342 abs_roll_angle - abs_roll_angle * bias,
4343 mk_dinput(autopilot_engaged));
4347 MK_VIII::Mode6Handler::is_high_bank_angle ()
4349 return mk_data(radio_altitude).ncd || mk_data(radio_altitude).get() >= 210;
4353 MK_VIII::Mode6Handler::get_bank_angle_alerts ()
4355 if (! mk->io_handler.gpws_inhibit()
4356 && ! mk->state_handler.ground // [1]
4357 && ! mk_data(roll_angle).ncd)
4359 double abs_roll_angle = fabs(mk_data(roll_angle).get());
4361 if (is_bank_angle(abs_roll_angle, 0.4))
4362 return is_high_bank_angle()
4363 ? (mk_alert(MODE6_HIGH_BANK_ANGLE_1) | mk_alert(MODE6_HIGH_BANK_ANGLE_2) | mk_alert(MODE6_HIGH_BANK_ANGLE_3))
4364 : (mk_alert(MODE6_LOW_BANK_ANGLE_1) | mk_alert(MODE6_LOW_BANK_ANGLE_2) | mk_alert(MODE6_LOW_BANK_ANGLE_3));
4365 else if (is_bank_angle(abs_roll_angle, 0.2))
4366 return is_high_bank_angle()
4367 ? (mk_alert(MODE6_HIGH_BANK_ANGLE_1) | mk_alert(MODE6_HIGH_BANK_ANGLE_2))
4368 : (mk_alert(MODE6_LOW_BANK_ANGLE_1) | mk_alert(MODE6_LOW_BANK_ANGLE_2));
4369 else if (is_bank_angle(abs_roll_angle, 0))
4370 return is_high_bank_angle()
4371 ? mk_alert(MODE6_HIGH_BANK_ANGLE_1)
4372 : mk_alert(MODE6_LOW_BANK_ANGLE_1);
4379 MK_VIII::Mode6Handler::update_bank_angle ()
4381 if (! conf.bank_angle_enabled)
4384 unsigned int alerts = get_bank_angle_alerts();
4386 // [SPEC] 6.4.4 specifies that the non-continuous alerts
4387 // (MODE6_*_BANK_ANGLE_1 and MODE6_*_BANK_ANGLE_2) are locked out
4388 // until the initial envelope is exited.
4390 if (! test_bits(alerts, mk_alert(MODE6_LOW_BANK_ANGLE_3)))
4391 mk_unset_alerts(mk_alert(MODE6_LOW_BANK_ANGLE_3));
4392 if (! test_bits(alerts, mk_alert(MODE6_HIGH_BANK_ANGLE_3)))
4393 mk_unset_alerts(mk_alert(MODE6_HIGH_BANK_ANGLE_3));
4396 mk_set_alerts(alerts);
4398 mk_unset_alerts(mk_alert(MODE6_LOW_BANK_ANGLE_1)
4399 | mk_alert(MODE6_HIGH_BANK_ANGLE_1)
4400 | mk_alert(MODE6_LOW_BANK_ANGLE_2)
4401 | mk_alert(MODE6_HIGH_BANK_ANGLE_2));
4405 MK_VIII::Mode6Handler::update ()
4407 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
4410 if (! mk->state_handler.takeoff
4411 && ! mk_data(radio_altitude).ncd
4412 && mk_data(radio_altitude).get() > 1000)
4414 reset_altitude_callouts(); // [SPEC] 6.4.2
4415 reset_minimums(); // common sense
4419 update_altitude_callouts();
4420 update_above_field_callout();
4421 update_bank_angle();
4424 ///////////////////////////////////////////////////////////////////////////////
4425 // TCFHandler /////////////////////////////////////////////////////////////////
4426 ///////////////////////////////////////////////////////////////////////////////
4428 // Gets the difference between the azimuth from @from_lat,@from_lon to
4429 // @to_lat,@to_lon, and @to_heading, in degrees.
4431 MK_VIII::TCFHandler::get_azimuth_difference (double from_lat,
4437 double az1, az2, distance;
4438 geo_inverse_wgs_84(0, from_lat, from_lon, to_lat, to_lon, &az1, &az2, &distance);
4439 return get_heading_difference(az1, to_heading);
4442 // Gets the difference between the azimuth from the current GPS
4443 // position to the center of @_runway, and the heading of @_runway, in
4446 MK_VIII::TCFHandler::get_azimuth_difference (const FGRunway *_runway)
4448 return get_azimuth_difference(mk_data(gps_latitude).get(),
4449 mk_data(gps_longitude).get(),
4450 _runway->latitude(),
4451 _runway->longitude(),
4452 _runway->headingDeg());
4455 // Selects the most likely intended destination runway of @airport,
4456 // and returns it in @_runway. For each runway, the difference between
4457 // the azimuth from the current GPS position to the center of the
4458 // runway and its heading is computed. The runway having the smallest
4461 // This selection algorithm is not specified in [SPEC], but
4462 // http://www.egpws.com/general_information/description/runway_select.htm
4463 // talks about automatic runway selection.
4465 MK_VIII::TCFHandler::select_runway (const FGAirport *airport)
4467 FGRunway* _runway = 0;
4468 double min_diff = 360;
4470 for (unsigned int r=0; r<airport->numRunways(); ++r) {
4471 FGRunway* rwy(airport->getRunwayByIndex(r));
4472 double diff = get_azimuth_difference(rwy);
4473 if (diff < min_diff)
4478 } // of airport runways iteration
4482 bool MK_VIII::TCFHandler::AirportFilter::passAirport(FGAirport* aApt) const
4484 return aApt->hasHardRunwayOfLengthFt(mk->conf.runway_database);
4488 MK_VIII::TCFHandler::update_runway ()
4491 if (mk_data(gps_latitude).ncd || mk_data(gps_longitude).ncd) {
4495 // Search for the intended destination runway of the closest
4496 // airport in range 15 nm. Passing 30nm to get_closest_airport()
4497 // provides enough margin for
4498 // large airports, which may have a runway located far away from
4499 // the airport's reference point.
4500 AirportFilter filter(mk);
4501 FGAirport* apt = FGAirport::findClosest(
4502 SGGeod::fromDeg(mk_data(gps_longitude).get(), mk_data(gps_latitude).get()),
4509 FGRunway* _runway = select_runway(apt);
4511 runway.center.latitude = _runway->latitude();
4512 runway.center.longitude = _runway->longitude();
4514 runway.elevation = apt->elevation();
4516 double half_length_m = _runway->lengthM() * 0.5;
4517 runway.half_length = half_length_m * SG_METER_TO_NM;
4519 // b3 ________________ b0
4521 // h1>>> | e1<<<<<<<<e0 | <<<h0
4522 // |________________|
4525 // get heading to runway threshold (h0) and end (h1)
4526 runway.edges[0].heading = _runway->headingDeg();
4527 runway.edges[1].heading = get_reciprocal_heading(_runway->headingDeg());
4531 // get position of runway threshold (e0)
4532 geo_direct_wgs_84(0,
4533 runway.center.latitude,
4534 runway.center.longitude,
4535 runway.edges[1].heading,
4537 &runway.edges[0].position.latitude,
4538 &runway.edges[0].position.longitude,
4541 // get position of runway end (e1)
4542 geo_direct_wgs_84(0,
4543 runway.center.latitude,
4544 runway.center.longitude,
4545 runway.edges[0].heading,
4547 &runway.edges[1].position.latitude,
4548 &runway.edges[1].position.longitude,
4551 double half_width_m = _runway->widthM() * 0.5;
4553 // get position of threshold bias area edges (b0 and b1)
4554 get_bias_area_edges(&runway.edges[0].position,
4555 runway.edges[1].heading,
4557 &runway.bias_area[0],
4558 &runway.bias_area[1]);
4560 // get position of end bias area edges (b2 and b3)
4561 get_bias_area_edges(&runway.edges[1].position,
4562 runway.edges[0].heading,
4564 &runway.bias_area[2],
4565 &runway.bias_area[3]);
4569 MK_VIII::TCFHandler::get_bias_area_edges (Position *edge,
4571 double half_width_m,
4572 Position *bias_edge1,
4573 Position *bias_edge2)
4575 double half_bias_width_m = k * SG_NM_TO_METER + half_width_m;
4576 double tmp_latitude = 0.0, tmp_longitude = 0.0, az = 0.0;
4578 geo_direct_wgs_84(0,
4586 geo_direct_wgs_84(0,
4589 heading_substract(reciprocal, 90),
4591 &bias_edge1->latitude,
4592 &bias_edge1->longitude,
4594 geo_direct_wgs_84(0,
4597 heading_add(reciprocal, 90),
4599 &bias_edge2->latitude,
4600 &bias_edge2->longitude,
4604 // Returns true if the current GPS position is inside the edge
4605 // triangle of @edge. The edge triangle, which is specified and
4606 // represented in [SPEC] 6.3.1, is defined as an isocele right
4607 // triangle of infinite height, whose right angle is located at the
4608 // position of @edge, and whose height is parallel to the heading of
4611 MK_VIII::TCFHandler::is_inside_edge_triangle (RunwayEdge *edge)
4613 return get_azimuth_difference(mk_data(gps_latitude).get(),
4614 mk_data(gps_longitude).get(),
4615 edge->position.latitude,
4616 edge->position.longitude,
4617 edge->heading) <= 45;
4620 // Returns true if the current GPS position is inside the bias area of
4621 // the currently selected runway.
4623 MK_VIII::TCFHandler::is_inside_bias_area ()
4626 double angles_sum = 0;
4628 for (int i = 0; i < 4; i++)
4630 double az2, distance;
4631 geo_inverse_wgs_84(0,
4632 mk_data(gps_latitude).get(),
4633 mk_data(gps_longitude).get(),
4634 runway.bias_area[i].latitude,
4635 runway.bias_area[i].longitude,
4636 &az1[i], &az2, &distance);
4639 for (int i = 0; i < 4; i++)
4641 double angle = az1[i == 3 ? 0 : i + 1] - az1[i];
4645 angles_sum += angle;
4648 return angles_sum > 180;
4652 MK_VIII::TCFHandler::is_tcf ()
4654 if (mk_data(radio_altitude).get() > 10)
4658 double distance, az1, az2;
4660 geo_inverse_wgs_84(0,
4661 mk_data(gps_latitude).get(),
4662 mk_data(gps_longitude).get(),
4663 runway.center.latitude,
4664 runway.center.longitude,
4665 &az1, &az2, &distance);
4667 distance *= SG_METER_TO_NM;
4669 // distance to the inner envelope edge
4670 double edge_distance = distance - runway.half_length - k;
4672 if (edge_distance >= 15)
4674 if (mk_data(radio_altitude).get() < 700)
4677 else if (edge_distance >= 12)
4679 if (mk_data(radio_altitude).get() < 100 * edge_distance - 800)
4682 else if (edge_distance >= 4)
4684 if (mk_data(radio_altitude).get() < 400)
4687 else if (edge_distance >= 2.45)
4689 if (mk_data(radio_altitude).get() < 66.666667 * edge_distance + 133.33333)
4694 if (is_inside_edge_triangle(&runway.edges[0]) || is_inside_edge_triangle(&runway.edges[1]))
4696 if (edge_distance >= 1)
4698 if (mk_data(radio_altitude).get() < 66.666667 * edge_distance + 133.33333)
4701 else if (edge_distance >= 0.05)
4703 if (mk_data(radio_altitude).get() < 200 * edge_distance)
4709 if (! is_inside_bias_area())
4711 if (mk_data(radio_altitude).get() < 245)
4719 if (mk_data(radio_altitude).get() < 700)
4728 MK_VIII::TCFHandler::is_rfcf ()
4732 double distance, az1, az2;
4733 geo_inverse_wgs_84(0,
4734 mk_data(gps_latitude).get(),
4735 mk_data(gps_longitude).get(),
4736 runway.center.latitude,
4737 runway.center.longitude,
4738 &az1, &az2, &distance);
4740 double krf = k + mk_data(gps_vertical_figure_of_merit).get() / 200;
4741 distance = distance * SG_METER_TO_NM - runway.half_length - krf;
4745 double altitude_above_field = mk_data(geometric_altitude).get() - runway.elevation;
4747 if (distance >= 1.5)
4749 if (altitude_above_field < 300)
4752 else if (distance >= 0)
4754 if (altitude_above_field < 200 * distance)
4764 MK_VIII::TCFHandler::update ()
4766 if (mk->configuration_module.state != ConfigurationModule::STATE_OK || ! conf.enabled)
4769 // update_runway() has to iterate over the whole runway database
4770 // (which contains thousands of runways), so it would be unwise to
4771 // run it every frame. Run it every 3 seconds.
4772 if (! runway_timer.running || runway_timer.elapsed() >= 3)
4775 runway_timer.start();
4778 if (! mk_dinput(ta_tcf_inhibit)
4779 && ! mk->state_handler.ground // [1]
4780 && ! mk_data(gps_latitude).ncd
4781 && ! mk_data(gps_longitude).ncd
4782 && ! mk_data(radio_altitude).ncd
4783 && ! mk_data(geometric_altitude).ncd
4784 && ! mk_data(gps_vertical_figure_of_merit).ncd)
4789 _reference = mk_data(radio_altitude).get_pointer();
4791 _reference = mk_data(geometric_altitude).get_pointer();
4797 if (mk_test_alert(TCF_TOO_LOW_TERRAIN))
4799 double new_bias = bias;
4800 while (*reference < initial_value - initial_value * new_bias)
4803 if (new_bias > bias)
4806 mk_repeat_alert(mk_alert(TCF_TOO_LOW_TERRAIN));
4812 reference = _reference;
4813 initial_value = *reference;
4814 mk_set_alerts(mk_alert(TCF_TOO_LOW_TERRAIN));
4821 mk_unset_alerts(mk_alert(TCF_TOO_LOW_TERRAIN));
4824 ///////////////////////////////////////////////////////////////////////////////
4825 // MK_VIII ////////////////////////////////////////////////////////////////////
4826 ///////////////////////////////////////////////////////////////////////////////
4828 MK_VIII::MK_VIII (SGPropertyNode *node)
4829 : properties_handler(this),
4832 power_handler(this),
4833 system_handler(this),
4834 configuration_module(this),
4835 fault_handler(this),
4838 self_test_handler(this),
4839 alert_handler(this),
4840 state_handler(this),
4841 mode1_handler(this),
4842 mode2_handler(this),
4843 mode3_handler(this),
4844 mode4_handler(this),
4845 mode5_handler(this),
4846 mode6_handler(this),
4849 for (int i = 0; i < node->nChildren(); ++i)
4851 SGPropertyNode *child = node->getChild(i);
4852 string cname = child->getName();
4853 string cval = child->getStringValue();
4855 if (cname == "name")
4857 else if (cname == "number")
4858 num = child->getIntValue();
4861 SG_LOG(SG_INSTR, SG_WARN, "Error in mk-viii config logic");
4863 SG_LOG(SG_INSTR, SG_WARN, "Section = " << name);
4871 properties_handler.init();
4872 voice_player.init();
4878 SGPropertyNode *node = fgGetNode(("/instrumentation/" + name).c_str(), num, true);
4880 configuration_module.bind(node);
4881 power_handler.bind(node);
4882 io_handler.bind(node);
4883 voice_player.bind(node);
4889 properties_handler.unbind();
4893 MK_VIII::update (double dt)
4895 power_handler.update();
4896 system_handler.update();
4898 if (system_handler.state != SystemHandler::STATE_ON)
4901 io_handler.update_inputs();
4902 io_handler.update_input_faults();
4904 voice_player.update();
4905 state_handler.update();
4907 if (self_test_handler.update())
4910 io_handler.update_internal_latches();
4911 io_handler.update_lamps();
4913 mode1_handler.update();
4914 mode2_handler.update();
4915 mode3_handler.update();
4916 mode4_handler.update();
4917 mode5_handler.update();
4918 mode6_handler.update();
4919 tcf_handler.update();
4921 alert_handler.update();
4922 io_handler.update_outputs();