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 (int 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 (int 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 (int 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 (int 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 (int 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 (int 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 (int 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 (int 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 (int 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 (int 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_offset_pos(position);
2131 sample->set_orientation(orientation,
2135 sample->set_reference_dist(reference_dist);
2136 sample->set_max_dist(max_dist);
2140 player->voice->volume_changed();
2143 MK_VIII::VoicePlayer::Voice::~Voice ()
2145 for (iter = elements.begin(); iter != elements.end(); iter++)
2146 delete *iter; // we owned the element
2151 MK_VIII::VoicePlayer::Voice::play ()
2153 iter = elements.begin();
2156 element->play(get_volume());
2160 MK_VIII::VoicePlayer::Voice::stop (bool now)
2164 if (now || element->silence)
2170 iter = elements.end() - 1; // stop after the current element finishes
2175 MK_VIII::VoicePlayer::Voice::set_volume (double _volume)
2182 MK_VIII::VoicePlayer::Voice::volume_changed ()
2185 element->set_volume(get_volume());
2189 MK_VIII::VoicePlayer::Voice::update ()
2193 if (! element->is_playing())
2195 if (++iter == elements.end())
2200 element->play(get_volume());
2206 MK_VIII::VoicePlayer::~VoicePlayer ()
2208 vector<Voice *>::iterator iter1;
2209 for (iter1 = _voices.begin(); iter1 != _voices.end(); iter1++)
2213 /* sound mgr already destroyed - samples already deleted
2214 map<string, SGSoundSample *>::iterator iter2;
2215 for (iter2 = samples.begin(); iter2 != samples.end(); iter2++)
2217 bool status = globals->get_soundmgr()->remove((*iter2).first);
2225 MK_VIII::VoicePlayer::init ()
2227 #define STDPAUSE 0.75 // [SPEC] 6.4.4: "the standard 0.75 second delay"
2229 make_voice(&voices.application_data_base_failed, "application-data-base-failed");
2230 make_voice(&voices.bank_angle, "bank-angle");
2231 make_voice(&voices.bank_angle_bank_angle, "bank-angle", "bank-angle");
2232 make_voice(&voices.bank_angle_bank_angle_3, "bank-angle", "bank-angle", 3.0);
2233 make_voice(&voices.bank_angle_inop, "bank-angle-inop");
2234 make_voice(&voices.bank_angle_pause_bank_angle, "bank-angle", STDPAUSE, "bank-angle");
2235 make_voice(&voices.bank_angle_pause_bank_angle_3, "bank-angle", STDPAUSE, "bank-angle", 3.0);
2236 make_voice(&voices.callouts_inop, "callouts-inop");
2237 make_voice(&voices.configuration_type_invalid, "configuration-type-invalid");
2238 make_voice(&voices.dont_sink, "dont-sink");
2239 make_voice(&voices.dont_sink_pause_dont_sink, "dont-sink", STDPAUSE, "dont-sink");
2240 make_voice(&voices.five_hundred_above, "500-above");
2241 make_voice(&voices.glideslope, "glideslope");
2242 make_voice(&voices.glideslope_inop, "glideslope-inop");
2243 make_voice(&voices.gpws_inop, "gpws-inop");
2244 make_voice(&voices.hard_glideslope, "glideslope", "glideslope", 3.0);
2245 make_voice(&voices.minimums, "minimums");
2246 make_voice(&voices.minimums_minimums, "minimums", "minimums");
2247 make_voice(&voices.pull_up, "pull-up");
2248 make_voice(&voices.sink_rate, "sink-rate");
2249 make_voice(&voices.sink_rate_pause_sink_rate, "sink-rate", STDPAUSE, "sink-rate");
2250 make_voice(&voices.soft_glideslope, new Voice::SampleElement(get_sample("glideslope"), modify_amplitude(1.0, -6)));
2251 make_voice(&voices.terrain, "terrain");
2252 make_voice(&voices.terrain_pause_terrain, "terrain", STDPAUSE, "terrain");
2253 make_voice(&voices.too_low_flaps, "too-low-flaps");
2254 make_voice(&voices.too_low_gear, "too-low-gear");
2255 make_voice(&voices.too_low_terrain, "too-low-terrain");
2257 for (int i = 0; i < n_altitude_callouts; i++)
2259 std::ostringstream name;
2260 name << "altitude-" << mk->mode6_handler.altitude_callout_definitions[i];
2261 make_voice(&voices.altitude_callouts[i], name.str().c_str());
2264 speaker.update_configuration();
2268 MK_VIII::VoicePlayer::get_sample (const char *name)
2270 std::ostringstream refname;
2271 refname << mk->name << "[" << mk->num << "]" << "/" << name;
2273 SGSoundMgr *soundmgr = globals->get_soundmgr();
2274 if (soundmgr->is_working() == false)
2279 SGSoundSample *sample = soundmgr->find(refname.str());
2282 SGPath sample_path(globals->get_fg_root());
2283 sample_path.append("Sounds/mk-viii");
2285 string filename = string(name) + ".wav";
2288 sample = new SGSoundSample(sample_path.c_str(), filename.c_str());
2290 catch (const sg_exception &e)
2292 SG_LOG(SG_INSTR, SG_ALERT, "Error loading MK VIII sound sample \"" + filename + "\": " + e.getFormattedMessage());
2296 soundmgr->add(sample, refname.str());
2297 samples[refname.str()] = sample;
2304 MK_VIII::VoicePlayer::play (Voice *_voice, unsigned int flags)
2306 if (test_bits(flags, PLAY_NOW) || ! voice || voice->element->silence)
2312 looped = test_bits(flags, PLAY_LOOPED);
2315 next_looped = false;
2321 next_voice = _voice;
2322 next_looped = test_bits(flags, PLAY_LOOPED);
2327 MK_VIII::VoicePlayer::stop (unsigned int flags)
2331 voice->stop(test_bits(flags, STOP_NOW));
2341 MK_VIII::VoicePlayer::set_volume (double _volume)
2345 voice->volume_changed();
2349 MK_VIII::VoicePlayer::update ()
2357 if (! voice->element || voice->element->silence)
2360 looped = next_looped;
2363 next_looped = false;
2370 if (! voice->element)
2381 ///////////////////////////////////////////////////////////////////////////////
2382 // SelfTestHandler ////////////////////////////////////////////////////////////
2383 ///////////////////////////////////////////////////////////////////////////////
2386 MK_VIII::SelfTestHandler::_was_here (int position)
2388 if (position > current)
2397 MK_VIII::SelfTestHandler::Action
2398 MK_VIII::SelfTestHandler::sleep (double duration)
2400 Action _action = { ACTION_SLEEP, duration, NULL };
2404 MK_VIII::SelfTestHandler::Action
2405 MK_VIII::SelfTestHandler::play (VoicePlayer::Voice *voice)
2407 mk->voice_player.play(voice);
2408 Action _action = { ACTION_VOICE, 0, NULL };
2412 MK_VIII::SelfTestHandler::Action
2413 MK_VIII::SelfTestHandler::discrete_on (bool *discrete, double duration)
2416 return sleep(duration);
2419 MK_VIII::SelfTestHandler::Action
2420 MK_VIII::SelfTestHandler::discrete_on_off (bool *discrete, double duration)
2423 Action _action = { ACTION_SLEEP | ACTION_DISCRETE_ON_OFF, duration, discrete };
2427 MK_VIII::SelfTestHandler::Action
2428 MK_VIII::SelfTestHandler::discrete_on_off (bool *discrete, VoicePlayer::Voice *voice)
2431 mk->voice_player.play(voice);
2432 Action _action = { ACTION_VOICE | ACTION_DISCRETE_ON_OFF, 0, discrete };
2436 MK_VIII::SelfTestHandler::Action
2437 MK_VIII::SelfTestHandler::done ()
2439 Action _action = { ACTION_DONE, 0, NULL };
2443 MK_VIII::SelfTestHandler::Action
2444 MK_VIII::SelfTestHandler::run ()
2446 // Note that "Terrain INOP" and "Terrain NA" are or'ed to the same
2447 // output discrete (see [SPEC] 6.9.3.5).
2449 #define was_here() was_here_offset(0)
2450 #define was_here_offset(offset) _was_here(__LINE__ * 20 + (offset))
2454 if (mk->configuration_module.state == ConfigurationModule::STATE_INVALID_DATABASE)
2455 return play(mk_voice(application_data_base_failed));
2456 else if (mk->configuration_module.state == ConfigurationModule::STATE_INVALID_AIRCRAFT_TYPE)
2457 return play(mk_voice(configuration_type_invalid));
2459 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
2463 return discrete_on(&mk_doutput(gpws_inop), 0.7);
2465 return sleep(0.7); // W/S INOP
2467 return discrete_on(&mk_doutput(tad_inop), 0.7);
2470 mk_doutput(gpws_inop) = false;
2471 // do not disable tad_inop since we must enable Terrain NA
2472 // do not disable W/S INOP since we do not emulate it
2473 return sleep(0.7); // Terrain NA
2477 mk_doutput(tad_inop) = false; // disable Terrain NA
2478 if (mk->io_handler.conf.momentary_flap_override_enabled)
2479 return discrete_on_off(&mk_doutput(flap_override), 1.0);
2482 return discrete_on_off(&mk_doutput(audio_on), 1.0);
2485 if (mk->io_handler.momentary_steep_approach_enabled())
2486 return discrete_on_off(&mk_doutput(steep_approach), 1.0);
2491 if (mk_dinput(glideslope_inhibit))
2492 goto glideslope_end;
2495 if (mk->fault_handler.faults[FaultHandler::FAULT_MODE5_INPUTS_INVALID])
2496 goto glideslope_inop;
2500 return discrete_on_off(&mk_doutput(gpws_alert), mk_voice(glideslope));
2502 return discrete_on_off(&mk_doutput(glideslope_cancel), 0.7);
2503 goto glideslope_end;
2506 return play(mk_voice(glideslope_inop));
2511 if (mk->fault_handler.faults[FaultHandler::FAULT_MODES14_INPUTS_INVALID])
2515 return discrete_on_off(&mk_doutput(gpws_warning), mk_voice(pull_up));
2519 return play(mk_voice(gpws_inop));
2524 if (mk_dinput(self_test)) // proceed to long self test
2529 if (mk->mode6_handler.conf.bank_angle_enabled
2530 && mk->fault_handler.faults[FaultHandler::FAULT_BANK_ANGLE_INPUTS_INVALID])
2531 return play(mk_voice(bank_angle_inop));
2535 if (mk->mode6_handler.altitude_callouts_enabled()
2536 && mk->fault_handler.faults[FaultHandler::FAULT_MODE6_INPUTS_INVALID])
2537 return play(mk_voice(callouts_inop));
2545 mk_doutput(gpws_inop) = true;
2546 // do not enable W/S INOP, since we do not emulate it
2547 mk_doutput(tad_inop) = true; // Terrain INOP, Terrain NA
2549 return play(mk_voice(sink_rate));
2552 return play(mk_voice(pull_up));
2554 return play(mk_voice(terrain));
2556 return play(mk_voice(pull_up));
2558 return play(mk_voice(dont_sink));
2560 return play(mk_voice(too_low_terrain));
2562 return play(mk->mode4_handler.conf.voice_too_low_gear);
2564 return play(mk_voice(too_low_flaps));
2566 return play(mk_voice(too_low_terrain));
2568 return play(mk_voice(glideslope));
2571 if (mk->mode6_handler.conf.bank_angle_enabled)
2572 return play(mk_voice(bank_angle));
2577 if (! mk->mode6_handler.altitude_callouts_enabled())
2578 goto callouts_disabled;
2582 if (mk->mode6_handler.conf.minimums_enabled)
2583 return play(mk_voice(minimums));
2587 if (mk->mode6_handler.conf.above_field_voice)
2588 return play(mk->mode6_handler.conf.above_field_voice);
2590 for (int i = 0; i < n_altitude_callouts; i++)
2591 if (! was_here_offset(i))
2593 if (mk->mode6_handler.conf.altitude_callouts_enabled[i])
2594 return play(mk_altitude_voice(i));
2598 if (mk->mode6_handler.conf.smart_500_enabled)
2599 return play(mk_altitude_voice(Mode6Handler::ALTITUDE_CALLOUT_500));
2604 return play(mk_voice(minimums_minimums));
2609 if (mk->tcf_handler.conf.enabled)
2610 return play(mk_voice(too_low_terrain));
2617 MK_VIII::SelfTestHandler::start ()
2619 assert(state == STATE_START);
2621 memcpy(&saved_outputs, &mk->io_handler.outputs, sizeof(mk->io_handler.outputs));
2622 memset(&mk->io_handler.outputs, 0, sizeof(mk->io_handler.outputs));
2624 // [SPEC] 6.10.6: "The self-test results are annunciated, at 6db
2625 // lower than the normal audio level selected for the aircraft"
2626 mk->voice_player.set_volume(modify_amplitude(mk->voice_player.conf.volume, -6));
2628 if (mk_dinput(mode6_low_volume))
2629 mk->mode6_handler.set_volume(1.0);
2631 state = STATE_RUNNING;
2632 cancel = CANCEL_NONE;
2633 memset(&action, 0, sizeof(action));
2638 MK_VIII::SelfTestHandler::stop ()
2640 if (state != STATE_NONE)
2642 if (state != STATE_START)
2644 mk->voice_player.stop(VoicePlayer::STOP_NOW);
2645 mk->voice_player.set_volume(mk_dinput(audio_inhibit) ? 0.0 : mk->voice_player.conf.volume);
2647 if (mk_dinput(mode6_low_volume))
2648 mk->mode6_handler.set_volume(modify_amplitude(1.0, -6));
2650 memcpy(&mk->io_handler.outputs, &saved_outputs, sizeof(mk->io_handler.outputs));
2653 button_pressed = false;
2659 MK_VIII::SelfTestHandler::handle_button_event (bool value)
2661 if (state == STATE_NONE)
2664 state = STATE_START;
2666 else if (state == STATE_START)
2669 state = STATE_NONE; // cancel the not-yet-started test
2671 else if (cancel == CANCEL_NONE)
2675 assert(! button_pressed);
2676 button_pressed = true;
2677 button_press_timestamp = globals->get_sim_time_sec();
2683 if (globals->get_sim_time_sec() - button_press_timestamp < 2)
2684 cancel = CANCEL_SHORT;
2686 cancel = CANCEL_LONG;
2693 MK_VIII::SelfTestHandler::update ()
2695 if (state == STATE_NONE)
2697 else if (state == STATE_START)
2699 if (mk->state_handler.ground && ! mk->io_handler.steep_approach())
2709 if (button_pressed && cancel == CANCEL_NONE)
2711 if (globals->get_sim_time_sec() - button_press_timestamp >= 2)
2712 cancel = CANCEL_LONG;
2716 if (! mk->state_handler.ground || cancel != CANCEL_NONE)
2722 if (test_bits(action.flags, ACTION_SLEEP))
2724 if (globals->get_sim_time_sec() - sleep_start < action.sleep_duration)
2727 if (test_bits(action.flags, ACTION_VOICE))
2729 if (mk->voice_player.voice)
2732 if (test_bits(action.flags, ACTION_DISCRETE_ON_OFF))
2733 *action.discrete = false;
2737 if (test_bits(action.flags, ACTION_SLEEP))
2738 sleep_start = globals->get_sim_time_sec();
2739 if (test_bits(action.flags, ACTION_DONE))
2748 ///////////////////////////////////////////////////////////////////////////////
2749 // AlertHandler ///////////////////////////////////////////////////////////////
2750 ///////////////////////////////////////////////////////////////////////////////
2753 MK_VIII::AlertHandler::select_voice_alerts (unsigned int test)
2755 if (has_alerts(test))
2757 voice_alerts = alerts & test;
2762 voice_alerts &= ~test;
2763 if (voice_alerts == 0)
2764 mk->voice_player.stop();
2771 MK_VIII::AlertHandler::boot ()
2777 MK_VIII::AlertHandler::reposition ()
2781 mk->io_handler.set_lamp(IOHandler::LAMP_NONE);
2782 mk->voice_player.stop(VoicePlayer::STOP_NOW);
2786 MK_VIII::AlertHandler::reset ()
2791 repeated_alerts = 0;
2792 altitude_callout_voice = NULL;
2796 MK_VIII::AlertHandler::update ()
2798 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
2801 // Lamps and voices are prioritized according to [SPEC] 6.9.2.
2803 // Voices which can interrupt other voices (VoicePlayer::PLAY_NOW)
2804 // are specified by [INSTALL] appendix E 5.3.5.
2806 // When a voice is overriden by a higher priority voice and the
2807 // overriding voice stops, we restore the previous voice if it was
2808 // looped (this is not specified by [SPEC] but seems to make sense).
2812 if (has_alerts(ALERT_MODE1_PULL_UP | ALERT_MODE2A | ALERT_MODE2B))
2813 mk->io_handler.set_lamp(IOHandler::LAMP_WARNING);
2814 else if (has_alerts(ALERT_MODE1_SINK_RATE
2815 | ALERT_MODE2A_PREFACE
2816 | ALERT_MODE2B_PREFACE
2817 | ALERT_MODE2A_ALTITUDE_GAIN_TERRAIN_CLOSING
2818 | ALERT_MODE2A_ALTITUDE_GAIN
2819 | ALERT_MODE2B_LANDING_MODE
2821 | ALERT_MODE4_TOO_LOW_FLAPS
2822 | ALERT_MODE4_TOO_LOW_GEAR
2823 | ALERT_MODE4AB_TOO_LOW_TERRAIN
2824 | ALERT_MODE4C_TOO_LOW_TERRAIN
2825 | ALERT_TCF_TOO_LOW_TERRAIN))
2826 mk->io_handler.set_lamp(IOHandler::LAMP_CAUTION);
2827 else if (has_alerts(ALERT_MODE5_SOFT | ALERT_MODE5_HARD))
2828 mk->io_handler.set_lamp(IOHandler::LAMP_GLIDESLOPE);
2830 mk->io_handler.set_lamp(IOHandler::LAMP_NONE);
2834 if (select_voice_alerts(ALERT_MODE1_PULL_UP))
2836 if (! has_old_alerts(ALERT_MODE1_PULL_UP))
2838 if (mk->voice_player.voice != mk_voice(sink_rate_pause_sink_rate))
2839 mk->voice_player.play(mk_voice(sink_rate), VoicePlayer::PLAY_NOW);
2840 mk->voice_player.play(mk_voice(pull_up), VoicePlayer::PLAY_LOOPED);
2843 else if (select_voice_alerts(ALERT_MODE2A_PREFACE | ALERT_MODE2B_PREFACE))
2845 if (! has_old_alerts(ALERT_MODE2A_PREFACE | ALERT_MODE2B_PREFACE))
2846 mk->voice_player.play(mk_voice(terrain_pause_terrain), VoicePlayer::PLAY_NOW);
2848 else if (select_voice_alerts(ALERT_MODE2A | ALERT_MODE2B))
2850 if (mk->voice_player.voice != mk_voice(pull_up))
2851 mk->voice_player.play(mk_voice(pull_up), VoicePlayer::PLAY_NOW | VoicePlayer::PLAY_LOOPED);
2853 else if (select_voice_alerts(ALERT_MODE2A_ALTITUDE_GAIN_TERRAIN_CLOSING | ALERT_MODE2B_LANDING_MODE))
2855 if (mk->voice_player.voice != mk_voice(terrain))
2856 mk->voice_player.play(mk_voice(terrain), VoicePlayer::PLAY_LOOPED);
2858 else if (select_voice_alerts(ALERT_MODE6_MINIMUMS))
2860 if (! has_old_alerts(ALERT_MODE6_MINIMUMS))
2861 mk->voice_player.play(mk_voice(minimums_minimums));
2863 else if (select_voice_alerts(ALERT_MODE4AB_TOO_LOW_TERRAIN | ALERT_MODE4C_TOO_LOW_TERRAIN))
2865 if (must_play_voice(ALERT_MODE4AB_TOO_LOW_TERRAIN | ALERT_MODE4C_TOO_LOW_TERRAIN))
2866 mk->voice_player.play(mk_voice(too_low_terrain));
2868 else if (select_voice_alerts(ALERT_TCF_TOO_LOW_TERRAIN))
2870 if (must_play_voice(ALERT_TCF_TOO_LOW_TERRAIN))
2871 mk->voice_player.play(mk_voice(too_low_terrain));
2873 else if (select_voice_alerts(ALERT_MODE6_ALTITUDE_CALLOUT))
2875 if (! has_old_alerts(ALERT_MODE6_ALTITUDE_CALLOUT))
2877 assert(altitude_callout_voice != NULL);
2878 mk->voice_player.play(altitude_callout_voice);
2881 else if (select_voice_alerts(ALERT_MODE4_TOO_LOW_GEAR))
2883 if (must_play_voice(ALERT_MODE4_TOO_LOW_GEAR))
2884 mk->voice_player.play(mk->mode4_handler.conf.voice_too_low_gear);
2886 else if (select_voice_alerts(ALERT_MODE4_TOO_LOW_FLAPS))
2888 if (must_play_voice(ALERT_MODE4_TOO_LOW_FLAPS))
2889 mk->voice_player.play(mk_voice(too_low_flaps));
2891 else if (select_voice_alerts(ALERT_MODE1_SINK_RATE))
2893 if (must_play_voice(ALERT_MODE1_SINK_RATE))
2894 mk->voice_player.play(mk_voice(sink_rate_pause_sink_rate));
2895 // [SPEC] 6.2.1: "During the time that the voice message for the
2896 // outer envelope is inhibited and the caution/warning lamp is
2897 // on, the Mode 5 alert message is allowed to annunciate for
2898 // excessive glideslope deviation conditions."
2899 else if (mk->voice_player.voice != mk_voice(sink_rate_pause_sink_rate)
2900 && mk->voice_player.next_voice != mk_voice(sink_rate_pause_sink_rate))
2902 if (has_alerts(ALERT_MODE5_HARD))
2904 voice_alerts |= ALERT_MODE5_HARD;
2905 if (mk->voice_player.voice != mk_voice(hard_glideslope))
2906 mk->voice_player.play(mk_voice(hard_glideslope), VoicePlayer::PLAY_LOOPED);
2908 else if (has_alerts(ALERT_MODE5_SOFT))
2910 voice_alerts |= ALERT_MODE5_SOFT;
2911 if (must_play_voice(ALERT_MODE5_SOFT))
2912 mk->voice_player.play(mk_voice(soft_glideslope));
2916 else if (select_voice_alerts(ALERT_MODE3))
2918 if (must_play_voice(ALERT_MODE3))
2919 mk->voice_player.play(mk_voice(dont_sink_pause_dont_sink));
2921 else if (select_voice_alerts(ALERT_MODE5_HARD))
2923 if (mk->voice_player.voice != mk_voice(hard_glideslope))
2924 mk->voice_player.play(mk_voice(hard_glideslope), VoicePlayer::PLAY_LOOPED);
2926 else if (select_voice_alerts(ALERT_MODE5_SOFT))
2928 if (must_play_voice(ALERT_MODE5_SOFT))
2929 mk->voice_player.play(mk_voice(soft_glideslope));
2931 else if (select_voice_alerts(ALERT_MODE6_LOW_BANK_ANGLE_3))
2933 if (mk->voice_player.voice != mk_voice(bank_angle_bank_angle_3))
2934 mk->voice_player.play(mk_voice(bank_angle_bank_angle_3), VoicePlayer::PLAY_LOOPED);
2936 else if (select_voice_alerts(ALERT_MODE6_HIGH_BANK_ANGLE_3))
2938 if (mk->voice_player.voice != mk_voice(bank_angle_pause_bank_angle_3))
2939 mk->voice_player.play(mk_voice(bank_angle_pause_bank_angle_3), VoicePlayer::PLAY_LOOPED);
2941 else if (select_voice_alerts(ALERT_MODE6_LOW_BANK_ANGLE_2))
2943 if (! has_old_alerts(ALERT_MODE6_LOW_BANK_ANGLE_2 | ALERT_MODE6_HIGH_BANK_ANGLE_2))
2944 mk->voice_player.play(mk_voice(bank_angle_bank_angle));
2946 else if (select_voice_alerts(ALERT_MODE6_HIGH_BANK_ANGLE_2))
2948 if (! has_old_alerts(ALERT_MODE6_LOW_BANK_ANGLE_2 | ALERT_MODE6_HIGH_BANK_ANGLE_2))
2949 mk->voice_player.play(mk_voice(bank_angle_pause_bank_angle));
2951 else if (select_voice_alerts(ALERT_MODE6_LOW_BANK_ANGLE_1))
2953 if (! has_old_alerts(ALERT_MODE6_LOW_BANK_ANGLE_1 | ALERT_MODE6_HIGH_BANK_ANGLE_1))
2954 mk->voice_player.play(mk_voice(bank_angle_bank_angle));
2956 else if (select_voice_alerts(ALERT_MODE6_HIGH_BANK_ANGLE_1))
2958 if (! has_old_alerts(ALERT_MODE6_LOW_BANK_ANGLE_1 | ALERT_MODE6_HIGH_BANK_ANGLE_1))
2959 mk->voice_player.play(mk_voice(bank_angle_pause_bank_angle));
2964 old_alerts = alerts;
2965 repeated_alerts = 0;
2966 altitude_callout_voice = NULL;
2970 MK_VIII::AlertHandler::set_alerts (unsigned int _alerts,
2972 VoicePlayer::Voice *_altitude_callout_voice)
2975 if (test_bits(flags, ALERT_FLAG_REPEAT))
2976 repeated_alerts |= _alerts;
2977 if (_altitude_callout_voice)
2978 altitude_callout_voice = _altitude_callout_voice;
2982 MK_VIII::AlertHandler::unset_alerts (unsigned int _alerts)
2985 repeated_alerts &= ~_alerts;
2988 ///////////////////////////////////////////////////////////////////////////////
2989 // StateHandler ///////////////////////////////////////////////////////////////
2990 ///////////////////////////////////////////////////////////////////////////////
2993 MK_VIII::StateHandler::update_ground ()
2997 if (! mk_ainput(computed_airspeed).ncd && mk_ainput(computed_airspeed).get() > 60
2998 && ! mk_data(radio_altitude).ncd && mk_data(radio_altitude).get() > 30)
3000 if (potentially_airborne_timer.start_or_elapsed() > 1)
3004 potentially_airborne_timer.stop();
3008 if (! mk_ainput(computed_airspeed).ncd && mk_ainput(computed_airspeed).get() < 40
3009 && ! mk_data(radio_altitude).ncd && mk_data(radio_altitude).get() < 30)
3015 MK_VIII::StateHandler::enter_ground ()
3018 mk->io_handler.enter_ground();
3020 // [SPEC] 6.0.1 does not specify this, but it seems to be an
3021 // omission; at this point, we must make sure that we are in takeoff
3022 // mode (otherwise the next takeoff might happen in approach mode).
3028 MK_VIII::StateHandler::leave_ground ()
3031 mk->mode2_handler.leave_ground();
3035 MK_VIII::StateHandler::update_takeoff ()
3039 // [SPEC] 6.0.2 is somewhat confusing: it mentions hardcoded
3040 // terrain clearance (500, 750) and airspeed (178, 200) values,
3041 // but it also mentions the mode 4A boundary, which varies as a
3042 // function of aircraft type. We consider that the mentioned
3043 // values are examples, and that we should use the mode 4A upper
3046 if (! mk_data(terrain_clearance).ncd
3047 && ! mk_ainput(computed_airspeed).ncd
3048 && mk_data(terrain_clearance).get() > mk->mode4_handler.get_upper_agl(mk->mode4_handler.conf.modes->ac))
3053 if (! mk_data(radio_altitude).ncd
3054 && mk_data(radio_altitude).get() < mk->mode4_handler.conf.modes->b->min_agl1
3055 && mk->io_handler.flaps_down()
3056 && mk_dinput(landing_gear))
3062 MK_VIII::StateHandler::enter_takeoff ()
3065 mk->io_handler.enter_takeoff();
3066 mk->mode2_handler.enter_takeoff();
3067 mk->mode3_handler.enter_takeoff();
3068 mk->mode6_handler.enter_takeoff();
3072 MK_VIII::StateHandler::leave_takeoff ()
3075 mk->mode6_handler.leave_takeoff();
3079 MK_VIII::StateHandler::post_reposition ()
3081 // Synchronize the state with the current situation.
3084 (! mk_ainput(computed_airspeed).ncd && mk_ainput(computed_airspeed).get() > 60
3085 && ! mk_data(radio_altitude).ncd && mk_data(radio_altitude).get() > 30);
3087 bool _takeoff = _ground;
3089 if (ground && ! _ground)
3091 else if (! ground && _ground)
3094 if (takeoff && ! _takeoff)
3096 else if (! takeoff && _takeoff)
3101 MK_VIII::StateHandler::update ()
3103 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
3110 ///////////////////////////////////////////////////////////////////////////////
3111 // Mode1Handler ///////////////////////////////////////////////////////////////
3112 ///////////////////////////////////////////////////////////////////////////////
3115 MK_VIII::Mode1Handler::get_pull_up_bias ()
3117 // [PILOT] page 54: "Steep Approach has priority over Flap Override
3118 // if selected simultaneously."
3120 if (mk->io_handler.steep_approach())
3122 else if (conf.envelopes->flap_override_bias && mk->io_handler.flap_override())
3129 MK_VIII::Mode1Handler::is_pull_up ()
3131 if (! mk->io_handler.gpws_inhibit()
3132 && ! mk->state_handler.ground // [1]
3133 && ! mk_data(radio_altitude).ncd
3134 && ! mk_data(barometric_altitude_rate).ncd
3135 && mk_data(radio_altitude).get() > conf.envelopes->min_agl)
3137 if (mk_data(radio_altitude).get() < conf.envelopes->pull_up_max_agl1)
3139 if (mk_data(radio_altitude).get() < conf.envelopes->pull_up_min_agl1(mk_data(barometric_altitude_rate).get() + get_pull_up_bias()))
3142 else if (mk_data(radio_altitude).get() < conf.envelopes->pull_up_max_agl2)
3144 if (mk_data(radio_altitude).get() < conf.envelopes->pull_up_min_agl2(mk_data(barometric_altitude_rate).get() + get_pull_up_bias()))
3153 MK_VIII::Mode1Handler::update_pull_up ()
3157 if (! mk_test_alert(MODE1_PULL_UP))
3159 // [SPEC] 6.2.1: at least one sink rate must be issued
3160 // before switching to pull up; if no sink rate has
3161 // occurred, a 0.2 second delay is used.
3162 if (mk->voice_player.voice == mk_voice(sink_rate_pause_sink_rate)
3163 || pull_up_timer.start_or_elapsed() >= 0.2)
3164 mk_set_alerts(mk_alert(MODE1_PULL_UP));
3169 pull_up_timer.stop();
3170 mk_unset_alerts(mk_alert(MODE1_PULL_UP));
3175 MK_VIII::Mode1Handler::get_sink_rate_bias ()
3177 // [PILOT] page 54: "Steep Approach has priority over Flap Override
3178 // if selected simultaneously."
3180 if (mk->io_handler.steep_approach())
3182 else if (conf.envelopes->flap_override_bias && mk->io_handler.flap_override())
3184 else if (! mk_data(glideslope_deviation_dots).ncd)
3188 if (mk_data(glideslope_deviation_dots).get() <= -2)
3190 else if (mk_data(glideslope_deviation_dots).get() < 0)
3191 bias = -150 * mk_data(glideslope_deviation_dots).get();
3193 if (mk_data(radio_altitude).get() < 100)
3194 bias *= 0.01 * mk_data(radio_altitude).get();
3203 MK_VIII::Mode1Handler::is_sink_rate ()
3205 return ! mk->io_handler.gpws_inhibit()
3206 && ! mk->state_handler.ground // [1]
3207 && ! mk_data(radio_altitude).ncd
3208 && ! mk_data(barometric_altitude_rate).ncd
3209 && mk_data(radio_altitude).get() > conf.envelopes->min_agl
3210 && mk_data(radio_altitude).get() < 2450
3211 && mk_data(radio_altitude).get() < -572 - 0.6035 * (mk_data(barometric_altitude_rate).get() + get_sink_rate_bias());
3215 MK_VIII::Mode1Handler::get_sink_rate_tti ()
3217 return mk_data(radio_altitude).get() / fabs(mk_data(barometric_altitude_rate).get());
3221 MK_VIII::Mode1Handler::update_sink_rate ()
3225 if (mk_test_alert(MODE1_SINK_RATE))
3227 double tti = get_sink_rate_tti();
3228 if (tti <= sink_rate_tti * 0.8)
3230 // ~20% degradation, warn again and store the new reference tti
3231 sink_rate_tti = tti;
3232 mk_repeat_alert(mk_alert(MODE1_SINK_RATE));
3237 if (sink_rate_timer.start_or_elapsed() >= 0.8)
3239 mk_set_alerts(mk_alert(MODE1_SINK_RATE));
3240 sink_rate_tti = get_sink_rate_tti();
3246 sink_rate_timer.stop();
3247 mk_unset_alerts(mk_alert(MODE1_SINK_RATE));
3252 MK_VIII::Mode1Handler::update ()
3254 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
3261 ///////////////////////////////////////////////////////////////////////////////
3262 // Mode2Handler ///////////////////////////////////////////////////////////////
3263 ///////////////////////////////////////////////////////////////////////////////
3266 MK_VIII::Mode2Handler::ClosureRateFilter::limit_radio_altitude_rate (double r)
3268 // Limit radio altitude rate according to aircraft configuration,
3269 // allowing maximum sensitivity during cruise while providing
3270 // progressively less sensitivity during the landing phases of
3273 if (! mk_data(glideslope_deviation_dots).ncd && fabs(mk_data(glideslope_deviation_dots).get()) <= 2)
3274 { // gs deviation <= +- 2 dots
3275 if (mk_dinput(landing_gear) && mk->io_handler.flaps_down())
3276 SG_CLAMP_RANGE(r, -1000.0, 3000.0);
3277 else if (mk_dinput(landing_gear) || mk->io_handler.flaps_down())
3278 SG_CLAMP_RANGE(r, 0.0, 4000.0);
3280 SG_CLAMP_RANGE(r, 1000.0, 5000.0);
3283 { // no ILS, or gs deviation > +- 2 dots
3284 if (mk_dinput(landing_gear) && mk->io_handler.flaps_down())
3285 SG_CLAMP_RANGE(r, 0.0, 4000.0);
3286 else if (mk_dinput(landing_gear) || mk->io_handler.flaps_down())
3287 SG_CLAMP_RANGE(r, 1000.0, 5000.0);
3295 MK_VIII::Mode2Handler::ClosureRateFilter::init ()
3298 last_ra.set(&mk_data(radio_altitude));
3299 last_ba.set(&mk_ainput(uncorrected_barometric_altitude));
3306 MK_VIII::Mode2Handler::ClosureRateFilter::update ()
3308 double elapsed = timer.start_or_elapsed();
3312 if (! mk_data(radio_altitude).ncd && ! mk_ainput(uncorrected_barometric_altitude).ncd)
3314 if (! last_ra.ncd && ! last_ba.ncd)
3316 // compute radio and barometric altitude rates (positive value = descent)
3317 double ra_rate = -(mk_data(radio_altitude).get() - last_ra.get()) / elapsed * 60;
3318 double ba_rate = -(mk_ainput(uncorrected_barometric_altitude).get() - last_ba.get()) / elapsed * 60;
3320 // limit radio altitude rate according to aircraft configuration
3321 ra_rate = limit_radio_altitude_rate(ra_rate);
3323 // apply a low-pass filter to the radio altitude rate
3324 ra_rate = ra_filter.filter(ra_rate);
3325 // apply a high-pass filter to the barometric altitude rate
3326 ba_rate = ba_filter.filter(ba_rate);
3328 // combine both rates to obtain a closure rate
3329 output.set(ra_rate + ba_rate);
3342 last_ra.set(&mk_data(radio_altitude));
3343 last_ba.set(&mk_ainput(uncorrected_barometric_altitude));
3347 MK_VIII::Mode2Handler::b_conditions ()
3349 return mk->io_handler.flaps_down()
3350 || (! mk_data(glideslope_deviation_dots).ncd && fabs(mk_data(glideslope_deviation_dots).get()) < 2)
3351 || takeoff_timer.running;
3355 MK_VIII::Mode2Handler::is_a ()
3357 if (! mk->io_handler.gpws_inhibit()
3358 && ! mk->state_handler.ground // [1]
3359 && ! mk_data(radio_altitude).ncd
3360 && ! mk_ainput(computed_airspeed).ncd
3361 && ! closure_rate_filter.output.ncd
3362 && ! b_conditions())
3364 if (mk_data(radio_altitude).get() < 1220)
3366 if (mk_data(radio_altitude).get() < -1579 + 0.7895 * closure_rate_filter.output.get())
3373 if (mk_ainput(computed_airspeed).get() <= conf->airspeed1)
3375 else if (mk_ainput(computed_airspeed).get() >= conf->airspeed2)
3378 upper_limit = 1650 + 8.9 * (mk_ainput(computed_airspeed).get() - conf->airspeed1);
3380 if (mk_data(radio_altitude).get() < upper_limit)
3382 if (mk_data(radio_altitude).get() < 522 + 0.1968 * closure_rate_filter.output.get())
3392 MK_VIII::Mode2Handler::is_b ()
3394 if (! mk->io_handler.gpws_inhibit()
3395 && ! mk->state_handler.ground // [1]
3396 && ! mk_data(radio_altitude).ncd
3397 && ! mk_data(barometric_altitude_rate).ncd
3398 && ! closure_rate_filter.output.ncd
3400 && mk_data(radio_altitude).get() < 789)
3404 if (mk->io_handler.flaps_down())
3406 if (mk_data(barometric_altitude_rate).get() > -400)
3408 else if (mk_data(barometric_altitude_rate).get() < -1000)
3411 lower_limit = -66.777 - 0.667 * mk_data(barometric_altitude_rate).get();
3416 if (mk_data(radio_altitude).get() > lower_limit)
3418 if (mk_data(radio_altitude).get() < -1579 + 0.7895 * closure_rate_filter.output.get())
3427 MK_VIII::Mode2Handler::check_pull_up (unsigned int preface_alert,
3430 if (pull_up_timer.running)
3432 if (pull_up_timer.elapsed() >= 1)
3434 mk_unset_alerts(preface_alert);
3435 mk_set_alerts(alert);
3440 if (! mk->voice_player.voice)
3441 pull_up_timer.start();
3446 MK_VIII::Mode2Handler::update_a ()
3450 if (mk_test_alert(MODE2A_PREFACE))
3451 check_pull_up(mk_alert(MODE2A_PREFACE), mk_alert(MODE2A));
3452 else if (! mk_test_alert(MODE2A))
3454 mk_unset_alerts(mk_alert(MODE2A_ALTITUDE_GAIN) | mk_alert(MODE2A_ALTITUDE_GAIN_TERRAIN_CLOSING));
3455 mk_set_alerts(mk_alert(MODE2A_PREFACE));
3456 a_start_time = globals->get_sim_time_sec();
3457 pull_up_timer.stop();
3462 if (mk_test_alert(MODE2A_ALTITUDE_GAIN))
3464 if (mk->io_handler.gpws_inhibit()
3465 || mk->state_handler.ground // [1]
3466 || a_altitude_gain_timer.elapsed() >= 45
3467 || mk_data(radio_altitude).ncd
3468 || mk_ainput(uncorrected_barometric_altitude).ncd
3469 || mk_ainput(uncorrected_barometric_altitude).get() - a_altitude_gain_alt >= 300
3470 // [PILOT] page 12: "the visual alert will remain on
3471 // until [...] landing flaps or the flap override switch
3473 || mk->io_handler.flaps_down())
3475 // exit altitude gain mode
3476 a_altitude_gain_timer.stop();
3477 mk_unset_alerts(mk_alert(MODE2A_ALTITUDE_GAIN) | mk_alert(MODE2A_ALTITUDE_GAIN_TERRAIN_CLOSING));
3481 // [SPEC] 6.2.2.2: "If the terrain starts to fall away
3482 // during this altitude gain time, the voice will shut
3484 if (closure_rate_filter.output.get() < 0)
3485 mk_unset_alerts(mk_alert(MODE2A_ALTITUDE_GAIN_TERRAIN_CLOSING));
3488 else if (mk_test_alerts(mk_alert(MODE2A_PREFACE) | mk_alert(MODE2A)))
3490 mk_unset_alerts(mk_alert(MODE2A_PREFACE) | mk_alert(MODE2A));
3492 if (! mk->io_handler.gpws_inhibit()
3493 && ! mk->state_handler.ground // [1]
3494 && globals->get_sim_time_sec() - a_start_time > 3
3495 && ! mk->io_handler.flaps_down()
3496 && ! mk_data(radio_altitude).ncd
3497 && ! mk_ainput(uncorrected_barometric_altitude).ncd)
3499 // [SPEC] 6.2.2.2: mode 2A envelope violated for more
3500 // than 3 seconds, enable altitude gain feature
3502 a_altitude_gain_timer.start();
3503 a_altitude_gain_alt = mk_ainput(uncorrected_barometric_altitude).get();
3505 unsigned int alerts = mk_alert(MODE2A_ALTITUDE_GAIN);
3506 if (closure_rate_filter.output.get() > 0)
3507 alerts |= mk_alert(MODE2A_ALTITUDE_GAIN_TERRAIN_CLOSING);
3509 mk_set_alerts(alerts);
3516 MK_VIII::Mode2Handler::update_b ()
3520 // handle normal mode
3522 if (b && (! mk_dinput(landing_gear) || ! mk->io_handler.flaps_down()))
3524 if (mk_test_alert(MODE2B_PREFACE))
3525 check_pull_up(mk_alert(MODE2B_PREFACE), mk_alert(MODE2B));
3526 else if (! mk_test_alert(MODE2B))
3528 mk_set_alerts(mk_alert(MODE2B_PREFACE));
3529 pull_up_timer.stop();
3533 mk_unset_alerts(mk_alert(MODE2B_PREFACE) | mk_alert(MODE2B));
3535 // handle landing mode ([SPEC] 6.2.2.3: "If both landing gear and
3536 // flaps are in the landing configuration, then the message will be
3539 if (b && mk_dinput(landing_gear) && mk->io_handler.flaps_down())
3540 mk_set_alerts(mk_alert(MODE2B_LANDING_MODE));
3542 mk_unset_alerts(mk_alert(MODE2B_LANDING_MODE));
3546 MK_VIII::Mode2Handler::boot ()
3548 closure_rate_filter.init();
3552 MK_VIII::Mode2Handler::power_off ()
3554 // [SPEC] 6.0.4: "This latching function is not power saved and a
3555 // system reset will force it false."
3556 takeoff_timer.stop();
3560 MK_VIII::Mode2Handler::leave_ground ()
3562 // takeoff, reset timer
3563 takeoff_timer.start();
3567 MK_VIII::Mode2Handler::enter_takeoff ()
3569 // reset timer, in case it's a go-around
3570 takeoff_timer.start();
3574 MK_VIII::Mode2Handler::update ()
3576 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
3579 closure_rate_filter.update();
3581 if (takeoff_timer.running && takeoff_timer.elapsed() >= 60)
3582 takeoff_timer.stop();
3588 ///////////////////////////////////////////////////////////////////////////////
3589 // Mode3Handler ///////////////////////////////////////////////////////////////
3590 ///////////////////////////////////////////////////////////////////////////////
3593 MK_VIII::Mode3Handler::max_alt_loss (double _bias)
3595 return conf->max_alt_loss(mk->io_handler.flap_override(), mk_data(radio_altitude).get()) + mk_data(radio_altitude).get() * _bias;
3599 MK_VIII::Mode3Handler::get_bias (double initial_bias, double alt_loss)
3601 if (mk_data(radio_altitude).get() > 0)
3602 while (alt_loss > max_alt_loss(initial_bias))
3603 initial_bias += 0.2;
3605 return initial_bias;
3609 MK_VIII::Mode3Handler::is (double *alt_loss)
3611 if (has_descent_alt)
3613 int max_agl = conf->max_agl(mk->io_handler.flap_override());
3615 if (mk_ainput(uncorrected_barometric_altitude).ncd
3616 || mk_ainput(uncorrected_barometric_altitude).get() > descent_alt
3617 || mk_data(radio_altitude).ncd
3618 || mk_data(radio_altitude).get() > max_agl)
3621 has_descent_alt = false;
3625 // gear/flaps: [SPEC] 1.3.1.3
3626 if (! mk->io_handler.gpws_inhibit()
3627 && ! mk->state_handler.ground // [1]
3628 && (! mk_dinput(landing_gear) || ! mk->io_handler.flaps_down())
3629 && ! mk_data(barometric_altitude_rate).ncd
3630 && ! mk_ainput(uncorrected_barometric_altitude).ncd
3631 && ! mk_data(radio_altitude).ncd
3632 && mk_data(barometric_altitude_rate).get() < 0)
3634 double _alt_loss = descent_alt - mk_ainput(uncorrected_barometric_altitude).get();
3635 if (armed || (mk_data(radio_altitude).get() > conf->min_agl
3636 && mk_data(radio_altitude).get() < max_agl
3637 && _alt_loss > max_alt_loss(0)))
3639 *alt_loss = _alt_loss;
3647 if (! mk_data(barometric_altitude_rate).ncd
3648 && ! mk_ainput(uncorrected_barometric_altitude).ncd
3649 && mk_data(barometric_altitude_rate).get() < 0)
3651 has_descent_alt = true;
3652 descent_alt = mk_ainput(uncorrected_barometric_altitude).get();
3660 MK_VIII::Mode3Handler::enter_takeoff ()
3663 has_descent_alt = false;
3667 MK_VIII::Mode3Handler::update ()
3669 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
3672 if (mk->state_handler.takeoff)
3676 if (! mk->state_handler.ground /* [1] */ && is(&alt_loss))
3678 if (mk_test_alert(MODE3))
3680 double new_bias = get_bias(bias, alt_loss);
3681 if (new_bias > bias)
3684 mk_repeat_alert(mk_alert(MODE3));
3690 bias = get_bias(0.2, alt_loss);
3691 mk_set_alerts(mk_alert(MODE3));
3698 mk_unset_alerts(mk_alert(MODE3));
3701 ///////////////////////////////////////////////////////////////////////////////
3702 // Mode4Handler ///////////////////////////////////////////////////////////////
3703 ///////////////////////////////////////////////////////////////////////////////
3705 // FIXME: for turbofans, the upper limit should be reduced from 1000
3706 // to 800 ft if a sudden change in radio altitude is detected, in
3707 // order to reduce nuisance alerts caused by overflying another
3708 // aircraft (see [PILOT] page 16).
3711 MK_VIII::Mode4Handler::get_upper_agl (const EnvelopesConfiguration *c)
3713 if (mk_ainput(computed_airspeed).get() >= c->airspeed2)
3715 else if (mk_ainput(computed_airspeed).get() >= c->airspeed1)
3716 return c->min_agl2(mk_ainput(computed_airspeed).get());
3721 const MK_VIII::Mode4Handler::EnvelopesConfiguration *
3722 MK_VIII::Mode4Handler::get_ab_envelope ()
3724 // [SPEC] 6.2.4.1: "The Mode 4B envelope can also be selected by
3725 // setting flaps to landing configuration or by selecting flap
3727 return mk_dinput(landing_gear) || mk->io_handler.flaps_down()
3733 MK_VIII::Mode4Handler::get_bias (double initial_bias, double min_agl)
3735 while (mk_data(radio_altitude).get() < min_agl - min_agl * initial_bias)
3736 initial_bias += 0.2;
3738 return initial_bias;
3742 MK_VIII::Mode4Handler::handle_alert (unsigned int alert,
3746 if (mk_test_alerts(alert))
3748 double new_bias = get_bias(*bias, min_agl);
3749 if (new_bias > *bias)
3752 mk_repeat_alert(alert);
3757 *bias = get_bias(0.2, min_agl);
3758 mk_set_alerts(alert);
3763 MK_VIII::Mode4Handler::update_ab ()
3765 if (! mk->io_handler.gpws_inhibit()
3766 && ! mk->state_handler.ground
3767 && ! mk->state_handler.takeoff
3768 && ! mk_data(radio_altitude).ncd
3769 && ! mk_ainput(computed_airspeed).ncd
3770 && mk_data(radio_altitude).get() > 30)
3772 const EnvelopesConfiguration *c = get_ab_envelope();
3773 if (mk_ainput(computed_airspeed).get() < c->airspeed1)
3775 if (mk_dinput(landing_gear))
3777 if (! mk->io_handler.flaps_down() && mk_data(radio_altitude).get() < c->min_agl1)
3779 handle_alert(mk_alert(MODE4_TOO_LOW_FLAPS), c->min_agl1, &ab_bias);
3785 if (mk_data(radio_altitude).get() < c->min_agl1)
3787 handle_alert(mk_alert(MODE4_TOO_LOW_GEAR), c->min_agl1, &ab_bias);
3794 mk_unset_alerts(mk_alert(MODE4_TOO_LOW_FLAPS) | mk_alert(MODE4_TOO_LOW_GEAR));
3798 MK_VIII::Mode4Handler::update_ab_expanded ()
3800 if (! mk->io_handler.gpws_inhibit()
3801 && ! mk->state_handler.ground
3802 && ! mk->state_handler.takeoff
3803 && ! mk_data(radio_altitude).ncd
3804 && ! mk_ainput(computed_airspeed).ncd
3805 && mk_data(radio_altitude).get() > 30)
3807 const EnvelopesConfiguration *c = get_ab_envelope();
3808 if (mk_ainput(computed_airspeed).get() >= c->airspeed1)
3810 double min_agl = get_upper_agl(c);
3811 if (mk_data(radio_altitude).get() < min_agl)
3813 handle_alert(mk_alert(MODE4AB_TOO_LOW_TERRAIN), min_agl, &ab_expanded_bias);
3819 mk_unset_alerts(mk_alert(MODE4AB_TOO_LOW_TERRAIN));
3823 MK_VIII::Mode4Handler::update_c ()
3825 if (! mk->io_handler.gpws_inhibit()
3826 && ! mk->state_handler.ground // [1]
3827 && mk->state_handler.takeoff
3828 && ! mk_data(radio_altitude).ncd
3829 && ! mk_data(terrain_clearance).ncd
3830 && mk_data(radio_altitude).get() > 30
3831 && (! mk_dinput(landing_gear) || ! mk->io_handler.flaps_down())
3832 && mk_data(radio_altitude).get() < get_upper_agl(conf.modes->ac)
3833 && mk_data(radio_altitude).get() < mk_data(terrain_clearance).get())
3834 handle_alert(mk_alert(MODE4C_TOO_LOW_TERRAIN), mk_data(terrain_clearance).get(), &c_bias);
3836 mk_unset_alerts(mk_alert(MODE4C_TOO_LOW_TERRAIN));
3840 MK_VIII::Mode4Handler::update ()
3842 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
3846 update_ab_expanded();
3850 ///////////////////////////////////////////////////////////////////////////////
3851 // Mode5Handler ///////////////////////////////////////////////////////////////
3852 ///////////////////////////////////////////////////////////////////////////////
3855 MK_VIII::Mode5Handler::is_hard ()
3857 if (mk_data(radio_altitude).get() > 30
3858 && mk_data(radio_altitude).get() < 300
3859 && mk_data(glideslope_deviation_dots).get() > 2)
3861 if (mk_data(radio_altitude).get() < 150)
3863 if (mk_data(radio_altitude).get() > 293 - 71.43 * mk_data(glideslope_deviation_dots).get())
3874 MK_VIII::Mode5Handler::is_soft (double bias)
3876 if (mk_data(radio_altitude).get() > 30)
3878 double bias_dots = 1.3 * bias;
3879 if (mk_data(glideslope_deviation_dots).get() > 1.3 + bias_dots)
3881 if (mk_data(radio_altitude).get() < 150)
3883 if (mk_data(radio_altitude).get() > 243 - 71.43 * (mk_data(glideslope_deviation_dots).get() - bias_dots))
3890 if (mk_data(barometric_altitude_rate).ncd)
3894 if (mk_data(barometric_altitude_rate).get() >= 0)
3896 else if (mk_data(barometric_altitude_rate).get() < -500)
3899 upper_limit = -mk_data(barometric_altitude_rate).get() + 500;
3902 if (mk_data(radio_altitude).get() < upper_limit)
3912 MK_VIII::Mode5Handler::get_soft_bias (double initial_bias)
3914 while (is_soft(initial_bias))
3915 initial_bias += 0.2;
3917 return initial_bias;
3921 MK_VIII::Mode5Handler::update_hard (bool is)
3925 if (! mk_test_alert(MODE5_HARD))
3927 if (hard_timer.start_or_elapsed() >= 0.8)
3928 mk_set_alerts(mk_alert(MODE5_HARD));
3934 mk_unset_alerts(mk_alert(MODE5_HARD));
3939 MK_VIII::Mode5Handler::update_soft (bool is)
3943 if (! mk_test_alert(MODE5_SOFT))
3945 double new_bias = get_soft_bias(soft_bias);
3946 if (new_bias > soft_bias)
3948 soft_bias = new_bias;
3949 mk_repeat_alert(mk_alert(MODE5_SOFT));
3954 if (soft_timer.start_or_elapsed() >= 0.8)
3956 soft_bias = get_soft_bias(0.2);
3957 mk_set_alerts(mk_alert(MODE5_SOFT));
3964 mk_unset_alerts(mk_alert(MODE5_SOFT));
3969 MK_VIII::Mode5Handler::update ()
3971 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
3974 if (! mk->io_handler.gpws_inhibit()
3975 && ! mk->state_handler.ground // [1]
3976 && ! mk_dinput(glideslope_inhibit) // not on backcourse
3977 && ! mk_data(radio_altitude).ncd
3978 && ! mk_data(glideslope_deviation_dots).ncd
3979 && (! mk->io_handler.conf.localizer_enabled
3980 || mk_data(localizer_deviation_dots).ncd
3981 || mk_data(radio_altitude).get() < 500
3982 || fabs(mk_data(localizer_deviation_dots).get()) < 2)
3983 && (! mk->state_handler.takeoff || mk->io_handler.flaps_down())
3984 && mk_dinput(landing_gear)
3985 && ! mk_doutput(glideslope_cancel))
3987 update_hard(is_hard());
3988 update_soft(is_soft(0));
3997 ///////////////////////////////////////////////////////////////////////////////
3998 // Mode6Handler ///////////////////////////////////////////////////////////////
3999 ///////////////////////////////////////////////////////////////////////////////
4001 // keep sorted in descending order
4002 const int MK_VIII::Mode6Handler::altitude_callout_definitions[] =
4003 { 1000, 500, 400, 300, 200, 100, 50, 40, 30, 20, 10 };
4006 MK_VIII::Mode6Handler::reset_minimums ()
4008 minimums_issued = false;
4012 MK_VIII::Mode6Handler::reset_altitude_callouts ()
4014 for (int i = 0; i < n_altitude_callouts; i++)
4015 altitude_callouts_issued[i] = false;
4019 MK_VIII::Mode6Handler::is_playing_altitude_callout ()
4021 for (int i = 0; i < n_altitude_callouts; i++)
4022 if (mk->voice_player.voice == mk_altitude_voice(i)
4023 || mk->voice_player.next_voice == mk_altitude_voice(i))
4030 MK_VIII::Mode6Handler::is_near_minimums (double callout)
4034 if (! mk_data(decision_height).ncd)
4036 double diff = callout - mk_data(decision_height).get();
4038 if (mk_data(radio_altitude).get() >= 200)
4040 if (fabs(diff) <= 30)
4045 if (diff >= -3 && diff <= 6)
4054 MK_VIII::Mode6Handler::is_outside_band (double elevation, double callout)
4057 return elevation < callout - (elevation > 150 ? 20 : 10);
4061 MK_VIII::Mode6Handler::inhibit_smart_500 ()
4065 if (! mk_data(glideslope_deviation_dots).ncd
4066 && ! mk_dinput(glideslope_inhibit) // backcourse
4067 && ! mk_doutput(glideslope_cancel))
4069 if (mk->io_handler.conf.localizer_enabled
4070 && ! mk_data(localizer_deviation_dots).ncd)
4072 if (fabs(mk_data(localizer_deviation_dots).get()) <= 2)
4077 if (fabs(mk_data(glideslope_deviation_dots).get()) <= 2)
4086 MK_VIII::Mode6Handler::boot ()
4088 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
4091 last_decision_height = mk_dinput(decision_height);
4092 last_radio_altitude.set(&mk_data(radio_altitude));
4095 for (int i = 0; i < n_altitude_callouts; i++)
4096 altitude_callouts_issued[i] = ! mk_data(radio_altitude).ncd
4097 && mk_data(radio_altitude).get() <= altitude_callout_definitions[i];
4099 // extrapolated from [SPEC] 6.4.2
4100 minimums_issued = mk_dinput(decision_height);
4102 if (conf.above_field_voice)
4105 get_altitude_above_field(&last_altitude_above_field);
4106 // extrapolated from [SPEC] 6.4.2
4107 above_field_issued = ! last_altitude_above_field.ncd
4108 && last_altitude_above_field.get() < 550;
4113 MK_VIII::Mode6Handler::power_off ()
4115 runway_timer.stop();
4119 MK_VIII::Mode6Handler::enter_takeoff ()
4121 reset_altitude_callouts(); // [SPEC] 6.4.2
4122 reset_minimums(); // omitted by [SPEC]; common sense
4126 MK_VIII::Mode6Handler::leave_takeoff ()
4128 reset_altitude_callouts(); // [SPEC] 6.0.2
4129 reset_minimums(); // [SPEC] 6.0.2
4133 MK_VIII::Mode6Handler::set_volume (double volume)
4135 mk_voice(minimums_minimums)->set_volume(volume);
4136 mk_voice(five_hundred_above)->set_volume(volume);
4137 for (int i = 0; i < n_altitude_callouts; i++)
4138 mk_altitude_voice(i)->set_volume(volume);
4142 MK_VIII::Mode6Handler::altitude_callouts_enabled ()
4144 if (conf.minimums_enabled || conf.smart_500_enabled || conf.above_field_voice)
4147 for (int i = 0; i < n_altitude_callouts; i++)
4148 if (conf.altitude_callouts_enabled[i])
4155 MK_VIII::Mode6Handler::update_minimums ()
4157 if (! mk->io_handler.gpws_inhibit()
4158 && (mk->voice_player.voice == mk_voice(minimums_minimums)
4159 || mk->voice_player.next_voice == mk_voice(minimums_minimums)))
4162 if (! mk->io_handler.gpws_inhibit()
4163 && ! mk->state_handler.ground // [1]
4164 && conf.minimums_enabled
4165 && ! minimums_issued
4166 && mk_dinput(landing_gear)
4167 && mk_dinput(decision_height)
4168 && ! last_decision_height)
4170 minimums_issued = true;
4172 // If an altitude callout is playing, stop it now so that the
4173 // minimums callout can be played (note that proper connection
4174 // and synchronization of the radio-altitude ARINC 529 input,
4175 // decision-height discrete and decision-height ARINC 529 input
4176 // will prevent an altitude callout from being played near the
4177 // decision height).
4179 if (is_playing_altitude_callout())
4180 mk->voice_player.stop(VoicePlayer::STOP_NOW);
4182 mk_set_alerts(mk_alert(MODE6_MINIMUMS));
4185 mk_unset_alerts(mk_alert(MODE6_MINIMUMS));
4188 last_decision_height = mk_dinput(decision_height);
4192 MK_VIII::Mode6Handler::update_altitude_callouts ()
4194 if (! mk->io_handler.gpws_inhibit() && is_playing_altitude_callout())
4197 if (! mk->io_handler.gpws_inhibit()
4198 && ! mk->state_handler.ground // [1]
4199 && ! mk_data(radio_altitude).ncd)
4200 for (int i = 0; i < n_altitude_callouts && mk_data(radio_altitude).get() <= altitude_callout_definitions[i]; i++)
4201 if ((conf.altitude_callouts_enabled[i]
4202 || (altitude_callout_definitions[i] == 500
4203 && conf.smart_500_enabled))
4204 && ! altitude_callouts_issued[i]
4205 && (last_radio_altitude.ncd
4206 || last_radio_altitude.get() > altitude_callout_definitions[i]))
4208 // lock out all callouts superior or equal to this one
4209 for (int j = 0; j <= i; j++)
4210 altitude_callouts_issued[j] = true;
4212 altitude_callouts_issued[i] = true;
4213 if (! is_near_minimums(altitude_callout_definitions[i])
4214 && ! is_outside_band(mk_data(radio_altitude).get(), altitude_callout_definitions[i])
4215 && (! conf.smart_500_enabled
4216 || altitude_callout_definitions[i] != 500
4217 || ! inhibit_smart_500()))
4219 mk->alert_handler.set_altitude_callout_alert(mk_altitude_voice(i));
4224 mk_unset_alerts(mk_alert(MODE6_ALTITUDE_CALLOUT));
4227 last_radio_altitude.set(&mk_data(radio_altitude));
4231 MK_VIII::Mode6Handler::test_runway (const FGRunway *_runway)
4233 if (_runway->lengthFt() < mk->conf.runway_database)
4237 SGGeod::fromDeg(mk_data(gps_longitude).get(), mk_data(gps_latitude).get()));
4239 // get distance to threshold
4240 double distance, az1, az2;
4241 SGGeodesy::inverse(pos, _runway->threshold(), az1, az2, distance);
4242 return distance * SG_METER_TO_NM <= 5;
4246 MK_VIII::Mode6Handler::test_airport (const FGAirport *airport)
4248 for (unsigned int r=0; r<airport->numRunways(); ++r) {
4249 FGRunway* rwy(airport->getRunwayByIndex(r));
4251 if (test_runway(rwy)) return true;
4257 bool MK_VIII::Mode6Handler::AirportFilter::passAirport(FGAirport* a) const
4259 bool ok = self->test_airport(a);
4264 MK_VIII::Mode6Handler::update_runway ()
4267 if (mk_data(gps_latitude).ncd || mk_data(gps_longitude).ncd) {
4272 // Search for the closest runway threshold in range 5
4273 // nm. Passing 30nm to
4274 // get_closest_airport() provides enough margin for large
4275 // airports, which may have a runway located far away from the
4276 // airport's reference point.
4277 AirportFilter filter(this);
4278 FGPositionedRef apt = FGPositioned::findClosest(
4279 SGGeod::fromDeg(mk_data(gps_longitude).get(), mk_data(gps_latitude).get()),
4282 runway.elevation = apt->elevation();
4285 has_runway.set(apt != NULL);
4289 MK_VIII::Mode6Handler::get_altitude_above_field (Parameter<double> *parameter)
4291 if (! has_runway.ncd && has_runway.get() && ! mk_data(geometric_altitude).ncd)
4292 parameter->set(mk_data(geometric_altitude).get() - runway.elevation);
4298 MK_VIII::Mode6Handler::update_above_field_callout ()
4300 if (! conf.above_field_voice)
4303 // update_runway() has to iterate over the whole runway database
4304 // (which contains thousands of runways), so it would be unwise to
4305 // run it every frame. Run it every 3 seconds. Note that the first
4306 // iteration is run in boot().
4307 if (runway_timer.start_or_elapsed() >= 3)
4310 runway_timer.start();
4313 Parameter<double> altitude_above_field;
4314 get_altitude_above_field(&altitude_above_field);
4316 if (! mk->io_handler.gpws_inhibit()
4317 && (mk->voice_player.voice == conf.above_field_voice
4318 || mk->voice_player.next_voice == conf.above_field_voice))
4321 // handle reset term
4322 if (above_field_issued)
4324 if ((! has_runway.ncd && ! has_runway.get())
4325 || (! altitude_above_field.ncd && altitude_above_field.get() > 700))
4326 above_field_issued = false;
4329 if (! mk->io_handler.gpws_inhibit()
4330 && ! mk->state_handler.ground // [1]
4331 && ! above_field_issued
4332 && ! altitude_above_field.ncd
4333 && altitude_above_field.get() < 550
4334 && (last_altitude_above_field.ncd
4335 || last_altitude_above_field.get() >= 550))
4337 above_field_issued = true;
4339 if (! is_outside_band(altitude_above_field.get(), 500))
4341 mk->alert_handler.set_altitude_callout_alert(conf.above_field_voice);
4346 mk_unset_alerts(mk_alert(MODE6_ALTITUDE_CALLOUT));
4349 last_altitude_above_field.set(&altitude_above_field);
4353 MK_VIII::Mode6Handler::is_bank_angle (double abs_roll_angle, double bias)
4355 return conf.is_bank_angle(&mk_data(radio_altitude),
4356 abs_roll_angle - abs_roll_angle * bias,
4357 mk_dinput(autopilot_engaged));
4361 MK_VIII::Mode6Handler::is_high_bank_angle ()
4363 return mk_data(radio_altitude).ncd || mk_data(radio_altitude).get() >= 210;
4367 MK_VIII::Mode6Handler::get_bank_angle_alerts ()
4369 if (! mk->io_handler.gpws_inhibit()
4370 && ! mk->state_handler.ground // [1]
4371 && ! mk_data(roll_angle).ncd)
4373 double abs_roll_angle = fabs(mk_data(roll_angle).get());
4375 if (is_bank_angle(abs_roll_angle, 0.4))
4376 return is_high_bank_angle()
4377 ? (mk_alert(MODE6_HIGH_BANK_ANGLE_1) | mk_alert(MODE6_HIGH_BANK_ANGLE_2) | mk_alert(MODE6_HIGH_BANK_ANGLE_3))
4378 : (mk_alert(MODE6_LOW_BANK_ANGLE_1) | mk_alert(MODE6_LOW_BANK_ANGLE_2) | mk_alert(MODE6_LOW_BANK_ANGLE_3));
4379 else if (is_bank_angle(abs_roll_angle, 0.2))
4380 return is_high_bank_angle()
4381 ? (mk_alert(MODE6_HIGH_BANK_ANGLE_1) | mk_alert(MODE6_HIGH_BANK_ANGLE_2))
4382 : (mk_alert(MODE6_LOW_BANK_ANGLE_1) | mk_alert(MODE6_LOW_BANK_ANGLE_2));
4383 else if (is_bank_angle(abs_roll_angle, 0))
4384 return is_high_bank_angle()
4385 ? mk_alert(MODE6_HIGH_BANK_ANGLE_1)
4386 : mk_alert(MODE6_LOW_BANK_ANGLE_1);
4393 MK_VIII::Mode6Handler::update_bank_angle ()
4395 if (! conf.bank_angle_enabled)
4398 unsigned int alerts = get_bank_angle_alerts();
4400 // [SPEC] 6.4.4 specifies that the non-continuous alerts
4401 // (MODE6_*_BANK_ANGLE_1 and MODE6_*_BANK_ANGLE_2) are locked out
4402 // until the initial envelope is exited.
4404 if (! test_bits(alerts, mk_alert(MODE6_LOW_BANK_ANGLE_3)))
4405 mk_unset_alerts(mk_alert(MODE6_LOW_BANK_ANGLE_3));
4406 if (! test_bits(alerts, mk_alert(MODE6_HIGH_BANK_ANGLE_3)))
4407 mk_unset_alerts(mk_alert(MODE6_HIGH_BANK_ANGLE_3));
4410 mk_set_alerts(alerts);
4412 mk_unset_alerts(mk_alert(MODE6_LOW_BANK_ANGLE_1)
4413 | mk_alert(MODE6_HIGH_BANK_ANGLE_1)
4414 | mk_alert(MODE6_LOW_BANK_ANGLE_2)
4415 | mk_alert(MODE6_HIGH_BANK_ANGLE_2));
4419 MK_VIII::Mode6Handler::update ()
4421 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
4424 if (! mk->state_handler.takeoff
4425 && ! mk_data(radio_altitude).ncd
4426 && mk_data(radio_altitude).get() > 1000)
4428 reset_altitude_callouts(); // [SPEC] 6.4.2
4429 reset_minimums(); // common sense
4433 update_altitude_callouts();
4434 update_above_field_callout();
4435 update_bank_angle();
4438 ///////////////////////////////////////////////////////////////////////////////
4439 // TCFHandler /////////////////////////////////////////////////////////////////
4440 ///////////////////////////////////////////////////////////////////////////////
4442 // Gets the difference between the azimuth from @from_lat,@from_lon to
4443 // @to_lat,@to_lon, and @to_heading, in degrees.
4445 MK_VIII::TCFHandler::get_azimuth_difference (double from_lat,
4451 double az1, az2, distance;
4452 geo_inverse_wgs_84(0, from_lat, from_lon, to_lat, to_lon, &az1, &az2, &distance);
4453 return get_heading_difference(az1, to_heading);
4456 // Gets the difference between the azimuth from the current GPS
4457 // position to the center of @_runway, and the heading of @_runway, in
4460 MK_VIII::TCFHandler::get_azimuth_difference (const FGRunway *_runway)
4462 return get_azimuth_difference(mk_data(gps_latitude).get(),
4463 mk_data(gps_longitude).get(),
4464 _runway->latitude(),
4465 _runway->longitude(),
4466 _runway->headingDeg());
4469 // Selects the most likely intended destination runway of @airport,
4470 // and returns it in @_runway. For each runway, the difference between
4471 // the azimuth from the current GPS position to the center of the
4472 // runway and its heading is computed. The runway having the smallest
4475 // This selection algorithm is not specified in [SPEC], but
4476 // http://www.egpws.com/general_information/description/runway_select.htm
4477 // talks about automatic runway selection.
4479 MK_VIII::TCFHandler::select_runway (const FGAirport *airport)
4481 FGRunway* _runway = 0;
4482 double min_diff = 360;
4484 for (unsigned int r=0; r<airport->numRunways(); ++r) {
4485 FGRunway* rwy(airport->getRunwayByIndex(r));
4486 double diff = get_azimuth_difference(rwy);
4487 if (diff < min_diff)
4492 } // of airport runways iteration
4496 bool MK_VIII::TCFHandler::AirportFilter::passAirport(FGAirport* aApt) const
4498 return aApt->hasHardRunwayOfLengthFt(mk->conf.runway_database);
4502 MK_VIII::TCFHandler::update_runway ()
4505 if (mk_data(gps_latitude).ncd || mk_data(gps_longitude).ncd) {
4509 // Search for the intended destination runway of the closest
4510 // airport in range 15 nm. Passing 30nm to get_closest_airport()
4511 // provides enough margin for
4512 // large airports, which may have a runway located far away from
4513 // the airport's reference point.
4514 AirportFilter filter(mk);
4515 FGAirport* apt = FGAirport::findClosest(
4516 SGGeod::fromDeg(mk_data(gps_longitude).get(), mk_data(gps_latitude).get()),
4523 FGRunway* _runway = select_runway(apt);
4525 runway.center.latitude = _runway->latitude();
4526 runway.center.longitude = _runway->longitude();
4528 runway.elevation = apt->elevation();
4530 double half_length_m = _runway->lengthM() * 0.5;
4531 runway.half_length = half_length_m * SG_METER_TO_NM;
4533 // b3 ________________ b0
4535 // h1>>> | e1<<<<<<<<e0 | <<<h0
4536 // |________________|
4539 // get heading to runway threshold (h0) and end (h1)
4540 runway.edges[0].heading = _runway->headingDeg();
4541 runway.edges[1].heading = get_reciprocal_heading(_runway->headingDeg());
4545 // get position of runway threshold (e0)
4546 geo_direct_wgs_84(0,
4547 runway.center.latitude,
4548 runway.center.longitude,
4549 runway.edges[1].heading,
4551 &runway.edges[0].position.latitude,
4552 &runway.edges[0].position.longitude,
4555 // get position of runway end (e1)
4556 geo_direct_wgs_84(0,
4557 runway.center.latitude,
4558 runway.center.longitude,
4559 runway.edges[0].heading,
4561 &runway.edges[1].position.latitude,
4562 &runway.edges[1].position.longitude,
4565 double half_width_m = _runway->widthM() * 0.5;
4567 // get position of threshold bias area edges (b0 and b1)
4568 get_bias_area_edges(&runway.edges[0].position,
4569 runway.edges[1].heading,
4571 &runway.bias_area[0],
4572 &runway.bias_area[1]);
4574 // get position of end bias area edges (b2 and b3)
4575 get_bias_area_edges(&runway.edges[1].position,
4576 runway.edges[0].heading,
4578 &runway.bias_area[2],
4579 &runway.bias_area[3]);
4583 MK_VIII::TCFHandler::get_bias_area_edges (Position *edge,
4585 double half_width_m,
4586 Position *bias_edge1,
4587 Position *bias_edge2)
4589 double half_bias_width_m = k * SG_NM_TO_METER + half_width_m;
4590 double tmp_latitude, tmp_longitude, az;
4592 geo_direct_wgs_84(0,
4600 geo_direct_wgs_84(0,
4603 heading_substract(reciprocal, 90),
4605 &bias_edge1->latitude,
4606 &bias_edge1->longitude,
4608 geo_direct_wgs_84(0,
4611 heading_add(reciprocal, 90),
4613 &bias_edge2->latitude,
4614 &bias_edge2->longitude,
4618 // Returns true if the current GPS position is inside the edge
4619 // triangle of @edge. The edge triangle, which is specified and
4620 // represented in [SPEC] 6.3.1, is defined as an isocele right
4621 // triangle of infinite height, whose right angle is located at the
4622 // position of @edge, and whose height is parallel to the heading of
4625 MK_VIII::TCFHandler::is_inside_edge_triangle (RunwayEdge *edge)
4627 return get_azimuth_difference(mk_data(gps_latitude).get(),
4628 mk_data(gps_longitude).get(),
4629 edge->position.latitude,
4630 edge->position.longitude,
4631 edge->heading) <= 45;
4634 // Returns true if the current GPS position is inside the bias area of
4635 // the currently selected runway.
4637 MK_VIII::TCFHandler::is_inside_bias_area ()
4640 double angles_sum = 0;
4642 for (int i = 0; i < 4; i++)
4644 double az2, distance;
4645 geo_inverse_wgs_84(0,
4646 mk_data(gps_latitude).get(),
4647 mk_data(gps_longitude).get(),
4648 runway.bias_area[i].latitude,
4649 runway.bias_area[i].longitude,
4650 &az1[i], &az2, &distance);
4653 for (int i = 0; i < 4; i++)
4655 double angle = az1[i == 3 ? 0 : i + 1] - az1[i];
4659 angles_sum += angle;
4662 return angles_sum > 180;
4666 MK_VIII::TCFHandler::is_tcf ()
4668 if (mk_data(radio_altitude).get() > 10)
4672 double distance, az1, az2;
4674 geo_inverse_wgs_84(0,
4675 mk_data(gps_latitude).get(),
4676 mk_data(gps_longitude).get(),
4677 runway.center.latitude,
4678 runway.center.longitude,
4679 &az1, &az2, &distance);
4681 distance *= SG_METER_TO_NM;
4683 // distance to the inner envelope edge
4684 double edge_distance = distance - runway.half_length - k;
4686 if (edge_distance >= 15)
4688 if (mk_data(radio_altitude).get() < 700)
4691 else if (edge_distance >= 12)
4693 if (mk_data(radio_altitude).get() < 100 * edge_distance - 800)
4696 else if (edge_distance >= 4)
4698 if (mk_data(radio_altitude).get() < 400)
4701 else if (edge_distance >= 2.45)
4703 if (mk_data(radio_altitude).get() < 66.666667 * edge_distance + 133.33333)
4708 if (is_inside_edge_triangle(&runway.edges[0]) || is_inside_edge_triangle(&runway.edges[1]))
4710 if (edge_distance >= 1)
4712 if (mk_data(radio_altitude).get() < 66.666667 * edge_distance + 133.33333)
4715 else if (edge_distance >= 0.05)
4717 if (mk_data(radio_altitude).get() < 200 * edge_distance)
4723 if (! is_inside_bias_area())
4725 if (mk_data(radio_altitude).get() < 245)
4733 if (mk_data(radio_altitude).get() < 700)
4742 MK_VIII::TCFHandler::is_rfcf ()
4746 double distance, az1, az2;
4747 geo_inverse_wgs_84(0,
4748 mk_data(gps_latitude).get(),
4749 mk_data(gps_longitude).get(),
4750 runway.center.latitude,
4751 runway.center.longitude,
4752 &az1, &az2, &distance);
4754 double krf = k + mk_data(gps_vertical_figure_of_merit).get() / 200;
4755 distance = distance * SG_METER_TO_NM - runway.half_length - krf;
4759 double altitude_above_field = mk_data(geometric_altitude).get() - runway.elevation;
4761 if (distance >= 1.5)
4763 if (altitude_above_field < 300)
4766 else if (distance >= 0)
4768 if (altitude_above_field < 200 * distance)
4778 MK_VIII::TCFHandler::update ()
4780 if (mk->configuration_module.state != ConfigurationModule::STATE_OK || ! conf.enabled)
4783 // update_runway() has to iterate over the whole runway database
4784 // (which contains thousands of runways), so it would be unwise to
4785 // run it every frame. Run it every 3 seconds.
4786 if (! runway_timer.running || runway_timer.elapsed() >= 3)
4789 runway_timer.start();
4792 if (! mk_dinput(ta_tcf_inhibit)
4793 && ! mk->state_handler.ground // [1]
4794 && ! mk_data(gps_latitude).ncd
4795 && ! mk_data(gps_longitude).ncd
4796 && ! mk_data(radio_altitude).ncd
4797 && ! mk_data(geometric_altitude).ncd
4798 && ! mk_data(gps_vertical_figure_of_merit).ncd)
4803 _reference = mk_data(radio_altitude).get_pointer();
4805 _reference = mk_data(geometric_altitude).get_pointer();
4811 if (mk_test_alert(TCF_TOO_LOW_TERRAIN))
4813 double new_bias = bias;
4814 while (*reference < initial_value - initial_value * new_bias)
4817 if (new_bias > bias)
4820 mk_repeat_alert(mk_alert(TCF_TOO_LOW_TERRAIN));
4826 reference = _reference;
4827 initial_value = *reference;
4828 mk_set_alerts(mk_alert(TCF_TOO_LOW_TERRAIN));
4835 mk_unset_alerts(mk_alert(TCF_TOO_LOW_TERRAIN));
4838 ///////////////////////////////////////////////////////////////////////////////
4839 // MK_VIII ////////////////////////////////////////////////////////////////////
4840 ///////////////////////////////////////////////////////////////////////////////
4842 MK_VIII::MK_VIII (SGPropertyNode *node)
4846 properties_handler(this),
4847 power_handler(this),
4848 system_handler(this),
4849 configuration_module(this),
4850 fault_handler(this),
4853 self_test_handler(this),
4854 alert_handler(this),
4855 state_handler(this),
4856 mode1_handler(this),
4857 mode2_handler(this),
4858 mode3_handler(this),
4859 mode4_handler(this),
4860 mode5_handler(this),
4861 mode6_handler(this),
4864 for (int i = 0; i < node->nChildren(); ++i)
4866 SGPropertyNode *child = node->getChild(i);
4867 string cname = child->getName();
4868 string cval = child->getStringValue();
4870 if (cname == "name")
4872 else if (cname == "number")
4873 num = child->getIntValue();
4876 SG_LOG(SG_INSTR, SG_WARN, "Error in mk-viii config logic");
4878 SG_LOG(SG_INSTR, SG_WARN, "Section = " << name);
4886 properties_handler.init();
4887 voice_player.init();
4893 SGPropertyNode *node = fgGetNode(("/instrumentation/" + name).c_str(), num, true);
4895 configuration_module.bind(node);
4896 power_handler.bind(node);
4897 io_handler.bind(node);
4898 voice_player.bind(node);
4904 properties_handler.unbind();
4908 MK_VIII::update (double dt)
4910 power_handler.update();
4911 system_handler.update();
4913 if (system_handler.state != SystemHandler::STATE_ON)
4916 io_handler.update_inputs();
4917 io_handler.update_input_faults();
4919 voice_player.update();
4920 state_handler.update();
4922 if (self_test_handler.update())
4925 io_handler.update_internal_latches();
4926 io_handler.update_lamps();
4928 mode1_handler.update();
4929 mode2_handler.update();
4930 mode3_handler.update();
4931 mode4_handler.update();
4932 mode5_handler.update();
4933 mode6_handler.update();
4934 tcf_handler.update();
4936 alert_handler.update();
4937 io_handler.update_outputs();