1 // mk_viii.cxx -- Honeywell MK VIII EGPWS emulation
3 // Written by Jean-Yves Lefort, started September 2005.
5 // Copyright (C) 2005, 2006 Jean-Yves Lefort - jylefort@FreeBSD.org
7 // This program is free software; you can redistribute it and/or
8 // modify it under the terms of the GNU General Public License as
9 // published by the Free Software Foundation; either version 2 of the
10 // License, or (at your option) any later version.
12 // This program is distributed in the hope that it will be useful, but
13 // WITHOUT ANY WARRANTY; without even the implied warranty of
14 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 // General Public License for more details.
17 // You should have received a copy of the GNU General Public License
18 // along with this program; if not, write to the Free Software
19 // Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
21 ///////////////////////////////////////////////////////////////////////////////
25 // [PILOT] Honeywell International Inc., "MK VI and MK VIII,
26 // Enhanced Ground Proximity Warning System (EGPWS),
27 // Pilot's Guide", May 2004
29 // http://www.egpws.com/engineering_support/documents/pilot_guides/060-4314-000.pdf
31 // [SPEC] Honeywell International Inc., "Product Specification
32 // for the MK VI and MK VIII Enhanced Ground Proximity
33 // Warning System (EGPWS)", June 2004
35 // http://www.egpws.com/engineering_support/documents/specs/965-1180-601.pdf
37 // [INSTALL] Honeywell Inc., "MK VI, MK VIII, Enhanced Ground
38 // Proximity Warning System (Class A TAWS), Installation
39 // Design Guide", July 2003
41 // http://www.egpws.com/engineering_support/documents/install/060-4314-150.pdf
45 // [1] [SPEC] does not specify the "must be airborne"
46 // condition; we use it to make sure that the alert
47 // will not trigger when on the ground, since it would
51 # pragma warning( disable: 4355 )
66 #include <simgear/constants.h>
67 #include <simgear/sg_inlines.h>
68 #include <simgear/debug/logstream.hxx>
69 #include <simgear/math/sg_geodesy.hxx>
70 #include <simgear/math/sg_random.h>
71 #include <simgear/misc/sg_path.hxx>
72 #include <simgear/sound/soundmgr_openal.hxx>
73 #include <simgear/structure/exception.hxx>
77 #include <Airports/runways.hxx>
78 #include <Airports/simple.hxx>
80 #if defined( HAVE_VERSION_H ) && HAVE_VERSION_H
81 # include <Include/version.h>
83 # include <Include/no_version.h>
86 #include <Main/fg_props.hxx>
87 #include <Main/globals.hxx>
88 #include "instrument_mgr.hxx"
89 #include "mk_viii.hxx"
91 ///////////////////////////////////////////////////////////////////////////////
92 // constants //////////////////////////////////////////////////////////////////
93 ///////////////////////////////////////////////////////////////////////////////
95 #define GLIDESLOPE_DOTS_TO_DDM 0.0875 // [INSTALL] 6.2.30
96 #define GLIDESLOPE_DDM_TO_DOTS (1 / GLIDESLOPE_DOTS_TO_DDM)
98 #define LOCALIZER_DOTS_TO_DDM 0.0775 // [INSTALL] 6.2.33
99 #define LOCALIZER_DDM_TO_DOTS (1 / LOCALIZER_DOTS_TO_DDM)
101 ///////////////////////////////////////////////////////////////////////////////
102 // helpers ////////////////////////////////////////////////////////////////////
103 ///////////////////////////////////////////////////////////////////////////////
105 #define assert_not_reached() assert(false)
106 #define n_elements(_array) (sizeof(_array) / sizeof((_array)[0]))
107 #define test_bits(_bits, _test) (((_bits) & (_test)) != 0)
109 #define mk_node(_name) (mk->properties_handler.external_properties._name)
111 #define mk_dinput_feed(_name) (mk->io_handler.input_feeders.discretes._name)
112 #define mk_dinput(_name) (mk->io_handler.inputs.discretes._name)
113 #define mk_ainput_feed(_name) (mk->io_handler.input_feeders.arinc429._name)
114 #define mk_ainput(_name) (mk->io_handler.inputs.arinc429._name)
115 #define mk_doutput(_name) (mk->io_handler.outputs.discretes._name)
116 #define mk_aoutput(_name) (mk->io_handler.outputs.arinc429._name)
117 #define mk_data(_name) (mk->io_handler.data._name)
119 #define mk_voice(_name) (mk->voice_player.voices._name)
120 #define mk_altitude_voice(_i) (mk->voice_player.voices.altitude_callouts[(_i)])
122 #define mk_alert(_name) (AlertHandler::ALERT_ ## _name)
123 #define mk_alert_flag(_name) (AlertHandler::ALERT_FLAG_ ## _name)
124 #define mk_set_alerts (mk->alert_handler.set_alerts)
125 #define mk_unset_alerts (mk->alert_handler.unset_alerts)
126 #define mk_repeat_alert (mk->alert_handler.repeat_alert)
127 #define mk_test_alert(_name) (mk_test_alerts(mk_alert(_name)))
128 #define mk_test_alerts(_test) (test_bits(mk->alert_handler.alerts, (_test)))
130 const double MK_VIII::TCFHandler::k = 0.25;
133 modify_amplitude (double amplitude, double dB)
135 return amplitude * pow(10.0, dB / 20.0);
139 heading_add (double h1, double h2)
141 double result = h1 + h2;
148 heading_substract (double h1, double h2)
150 double result = h1 - h2;
157 get_heading_difference (double h1, double h2)
159 double diff = h1 - h2;
170 get_reciprocal_heading (double h)
172 return heading_add(h, 180);
175 ///////////////////////////////////////////////////////////////////////////////
176 // PropertiesHandler //////////////////////////////////////////////////////////
177 ///////////////////////////////////////////////////////////////////////////////
180 MK_VIII::PropertiesHandler::init ()
182 mk_node(ai_caged) = fgGetNode("/instrumentation/attitude-indicator/caged-flag", true);
183 mk_node(ai_roll) = fgGetNode("/instrumentation/attitude-indicator/indicated-roll-deg", true);
184 mk_node(ai_serviceable) = fgGetNode("/instrumentation/attitude-indicator/serviceable", true);
185 mk_node(altimeter_altitude) = fgGetNode("/instrumentation/altimeter/indicated-altitude-ft", true);
186 mk_node(altimeter_serviceable) = fgGetNode("/instrumentation/altimeter/serviceable", true);
187 mk_node(altitude) = fgGetNode("/position/altitude-ft", true);
188 mk_node(altitude_agl) = fgGetNode("/position/altitude-agl-ft", true);
189 mk_node(altitude_gear_agl) = fgGetNode("/position/gear-agl-ft", true);
190 mk_node(orientation_roll) = fgGetNode("/orientation/roll-deg", true);
191 mk_node(asi_serviceable) = fgGetNode("/instrumentation/airspeed-indicator/serviceable", true);
192 mk_node(asi_speed) = fgGetNode("/instrumentation/airspeed-indicator/indicated-speed-kt", true);
193 mk_node(autopilot_heading_lock) = fgGetNode("/autopilot/locks/heading", true);
194 mk_node(flaps) = fgGetNode("/controls/flight/flaps", true);
195 mk_node(gear_down) = fgGetNode("/controls/gear/gear-down", true);
196 mk_node(latitude) = fgGetNode("/position/latitude-deg", true);
197 mk_node(longitude) = fgGetNode("/position/longitude-deg", true);
198 mk_node(nav0_cdi_serviceable) = fgGetNode("/instrumentation/nav/cdi/serviceable", true);
199 mk_node(nav0_gs_distance) = fgGetNode("/instrumentation/nav/gs-distance", true);
200 mk_node(nav0_gs_needle_deflection) = fgGetNode("/instrumentation/nav/gs-needle-deflection", true);
201 mk_node(nav0_gs_serviceable) = fgGetNode("/instrumentation/nav/gs/serviceable", true);
202 mk_node(nav0_has_gs) = fgGetNode("/instrumentation/nav/has-gs", true);
203 mk_node(nav0_heading_needle_deflection) = fgGetNode("/instrumentation/nav/heading-needle-deflection", true);
204 mk_node(nav0_in_range) = fgGetNode("/instrumentation/nav/in-range", true);
205 mk_node(nav0_nav_loc) = fgGetNode("/instrumentation/nav/nav-loc", true);
206 mk_node(nav0_serviceable) = fgGetNode("/instrumentation/nav/serviceable", true);
207 mk_node(power) = fgGetNode(("/systems/electrical/outputs/" + mk->name).c_str(), mk->num, true);
208 mk_node(replay_state) = fgGetNode("/sim/freeze/replay-state", true);
209 mk_node(vs) = fgGetNode("/velocities/vertical-speed-fps", true);
213 MK_VIII::PropertiesHandler::unbind ()
215 vector<SGPropertyNode_ptr>::iterator iter;
217 for (iter = tied_properties.begin(); iter != tied_properties.end(); iter++)
220 tied_properties.clear();
223 ///////////////////////////////////////////////////////////////////////////////
224 // PowerHandler ///////////////////////////////////////////////////////////////
225 ///////////////////////////////////////////////////////////////////////////////
228 MK_VIII::PowerHandler::bind (SGPropertyNode *node)
230 mk->properties_handler.tie(node, "serviceable", SGRawValuePointer<bool>(&serviceable));
234 MK_VIII::PowerHandler::handle_abnormal_voltage (bool abnormal,
240 if (timer->start_or_elapsed() >= max_duration)
241 return true; // power loss
250 MK_VIII::PowerHandler::update ()
252 double voltage = mk_node(power)->getDoubleValue();
253 bool now_powered = true;
261 if (handle_abnormal_voltage(voltage < 20.5, &low_surge_timer, 0.03))
263 if (handle_abnormal_voltage(voltage < 22.0 || voltage > 30.3, &abnormal_timer, 300))
265 if (handle_abnormal_voltage(voltage > 32.2, &high_surge_timer, 1))
267 if (handle_abnormal_voltage(voltage > 37.8, &very_high_surge_timer, 0.1))
277 power_loss_timer.stop();
280 if (power_loss_timer.start_or_elapsed() >= 0.2)
292 MK_VIII::PowerHandler::power_on ()
295 mk->system_handler.power_on();
299 MK_VIII::PowerHandler::power_off ()
302 mk->system_handler.power_off();
303 mk->voice_player.stop(VoicePlayer::STOP_NOW);
304 mk->self_test_handler.power_off(); // run before IOHandler::power_off()
305 mk->io_handler.power_off();
306 mk->mode2_handler.power_off();
307 mk->mode6_handler.power_off();
310 ///////////////////////////////////////////////////////////////////////////////
311 // SystemHandler //////////////////////////////////////////////////////////////
312 ///////////////////////////////////////////////////////////////////////////////
315 MK_VIII::SystemHandler::power_on ()
317 state = STATE_BOOTING;
319 // [SPEC] 3.5.2 mentions a 20 seconds maximum boot delay. We use a
320 // random delay between 3 and 5 seconds.
322 boot_delay = 3 + sg_random() * 2;
327 MK_VIII::SystemHandler::power_off ()
335 MK_VIII::SystemHandler::update ()
337 if (state == STATE_BOOTING)
339 if (boot_timer.elapsed() >= boot_delay)
341 last_replay_state = mk_node(replay_state)->getIntValue();
343 mk->configuration_module.boot();
345 mk->io_handler.boot();
346 mk->fault_handler.boot();
347 mk->alert_handler.boot();
349 // inputs are needed by the following boot() routines
350 mk->io_handler.update_inputs();
352 mk->mode2_handler.boot();
353 mk->mode6_handler.boot();
357 mk->io_handler.post_boot();
360 else if (state != STATE_OFF && mk->configuration_module.state == ConfigurationModule::STATE_OK)
362 // If the replay state changes, switch to reposition mode for 3
363 // seconds ([SPEC] 6.0.5) to avoid spurious alerts.
365 int replay_state = mk_node(replay_state)->getIntValue();
366 if (replay_state != last_replay_state)
368 mk->alert_handler.reposition();
369 mk->io_handler.reposition();
371 last_replay_state = replay_state;
372 state = STATE_REPOSITION;
373 reposition_timer.start();
376 if (state == STATE_REPOSITION && reposition_timer.elapsed() >= 3)
378 // inputs are needed by StateHandler::post_reposition()
379 mk->io_handler.update_inputs();
381 mk->state_handler.post_reposition();
388 ///////////////////////////////////////////////////////////////////////////////
389 // ConfigurationModule ////////////////////////////////////////////////////////
390 ///////////////////////////////////////////////////////////////////////////////
392 MK_VIII::ConfigurationModule::ConfigurationModule (MK_VIII *device)
395 // arbitrary defaults
396 categories[CATEGORY_AIRCRAFT_MODE_TYPE_SELECT] = 0;
397 categories[CATEGORY_AIR_DATA_INPUT_SELECT] = 1;
398 categories[CATEGORY_POSITION_INPUT_SELECT] = 2;
399 categories[CATEGORY_ALTITUDE_CALLOUTS] = 13;
400 categories[CATEGORY_AUDIO_MENU_SELECT] = 0;
401 categories[CATEGORY_TERRAIN_DISPLAY_SELECT] = 1;
402 categories[CATEGORY_OPTIONS_SELECT_GROUP_1] = 124;
403 categories[CATEGORY_RADIO_ALTITUDE_INPUT_SELECT] = 2;
404 categories[CATEGORY_NAVIGATION_INPUT_SELECT] = 3;
405 categories[CATEGORY_ATTITUDE_INPUT_SELECT] = 6;
406 categories[CATEGORY_HEADING_INPUT_SELECT] = 2;
407 categories[CATEGORY_WINDSHEAR_INPUT_SELECT] = 0;
408 categories[CATEGORY_INPUT_OUTPUT_DISCRETE_TYPE_SELECT] = 7;
409 categories[CATEGORY_AUDIO_OUTPUT_LEVEL] = 0;
410 categories[CATEGORY_UNDEFINED_INPUT_SELECT_1] = 0;
411 categories[CATEGORY_UNDEFINED_INPUT_SELECT_2] = 0;
412 categories[CATEGORY_UNDEFINED_INPUT_SELECT_3] = 0;
415 static double m1_t1_min_agl1 (double vs) { return -1620 - 1.1133 * vs; }
416 static double m1_t1_min_agl2 (double vs) { return -400 - 0.4 * vs; }
417 static double m1_t4_min_agl1 (double vs) { return -1625.47 - 1.1167 * vs; }
418 static double m1_t4_min_agl2 (double vs) { return -0.1958 * vs; }
420 static int m3_t1_max_agl (bool flap_override) { return 1500; }
421 static double m3_t1_max_alt_loss (bool flap_override, double agl) { return 5.4 + 0.092 * agl; }
422 static int m3_t2_max_agl (bool flap_override) { return flap_override ? 815 : 925; }
423 static double m3_t2_max_alt_loss (bool flap_override, double agl)
425 int bias = agl > 700 ? 5 : 0;
428 return (9.0 + 0.184 * agl) + bias;
430 return (5.4 + 0.092 * agl) + bias;
433 static double m4_t1_min_agl2 (double airspeed) { return -1083 + 8.333 * airspeed; }
434 static double m4_t568_a_min_agl2 (double airspeed) { return -1523 + 11.36 * airspeed; }
435 static double m4_t79_a_min_agl2 (double airspeed) { return -1182 + 11.36 * airspeed; }
436 static double m4_t568_b_min_agl2 (double airspeed) { return -1570 + 11.6 * airspeed; }
437 static double m4_t79_b_min_agl2 (double airspeed) { return -1222 + 11.6 * airspeed; }
440 MK_VIII::ConfigurationModule::m6_t2_is_bank_angle (Parameter<double> *agl,
446 if (agl->ncd || agl->get() > 122)
447 return abs_roll_deg > 33;
451 if (agl->ncd || agl->get() > 2450)
452 return abs_roll_deg > 55;
453 else if (agl->get() > 150)
454 return agl->get() < 153.33333 * abs_roll_deg - 5983.3333;
458 return agl->get() < 4 * abs_roll_deg - 10;
459 else if (agl->get() > 5)
460 return abs_roll_deg > 10;
466 MK_VIII::ConfigurationModule::m6_t4_is_bank_angle (Parameter<double> *agl,
472 if (agl->ncd || agl->get() > 156)
473 return abs_roll_deg > 33;
477 if (agl->ncd || agl->get() > 210)
478 return abs_roll_deg > 50;
482 return agl->get() < 5.7142857 * abs_roll_deg - 75.714286;
488 MK_VIII::ConfigurationModule::read_aircraft_mode_type_select (int value)
490 static const Mode1Handler::EnvelopesConfiguration m1_t1 =
491 { false, 10, m1_t1_min_agl1, 284, m1_t1_min_agl2, 2450 };
492 static const Mode1Handler::EnvelopesConfiguration m1_t4 =
493 { true, 50, m1_t4_min_agl1, 346, m1_t4_min_agl2, 1958 };
495 static const Mode2Handler::Configuration m2_t1 = { 190, 280 };
496 static const Mode2Handler::Configuration m2_t4 = { 220, 310 };
497 static const Mode2Handler::Configuration m2_t5 = { 220, 310 };
499 static const Mode3Handler::Configuration m3_t1 =
500 { 30, m3_t1_max_agl, m3_t1_max_alt_loss };
501 static const Mode3Handler::Configuration m3_t2 =
502 { 50, m3_t2_max_agl, m3_t2_max_alt_loss };
504 static const Mode4Handler::EnvelopesConfiguration m4_t1_ac =
505 { 190, 250, 500, m4_t1_min_agl2, 1000 };
506 static const Mode4Handler::EnvelopesConfiguration m4_t5_ac =
507 { 178, 200, 500, m4_t568_a_min_agl2, 1000 };
508 static const Mode4Handler::EnvelopesConfiguration m4_t68_ac =
509 { 178, 200, 500, m4_t568_a_min_agl2, 750 };
510 static const Mode4Handler::EnvelopesConfiguration m4_t79_ac =
511 { 148, 170, 500, m4_t79_a_min_agl2, 750 };
513 static const Mode4Handler::EnvelopesConfiguration m4_t1_b =
514 { 159, 250, 245, m4_t1_min_agl2, 1000 };
515 static const Mode4Handler::EnvelopesConfiguration m4_t5_b =
516 { 148, 200, 200, m4_t568_b_min_agl2, 1000 };
517 static const Mode4Handler::EnvelopesConfiguration m4_t6_b =
518 { 150, 200, 170, m4_t568_b_min_agl2, 750 };
519 static const Mode4Handler::EnvelopesConfiguration m4_t7_b =
520 { 120, 170, 170, m4_t79_b_min_agl2, 750 };
521 static const Mode4Handler::EnvelopesConfiguration m4_t8_b =
522 { 148, 200, 150, m4_t568_b_min_agl2, 750 };
523 static const Mode4Handler::EnvelopesConfiguration m4_t9_b =
524 { 118, 170, 150, m4_t79_b_min_agl2, 750 };
526 static const Mode4Handler::ModesConfiguration m4_t1 = { &m4_t1_ac, &m4_t1_b };
527 static const Mode4Handler::ModesConfiguration m4_t5 = { &m4_t5_ac, &m4_t5_b };
528 static const Mode4Handler::ModesConfiguration m4_t6 = { &m4_t68_ac, &m4_t6_b };
529 static const Mode4Handler::ModesConfiguration m4_t7 = { &m4_t79_ac, &m4_t7_b };
530 static const Mode4Handler::ModesConfiguration m4_t8 = { &m4_t68_ac, &m4_t8_b };
531 static const Mode4Handler::ModesConfiguration m4_t9 = { &m4_t79_ac, &m4_t9_b };
533 static Mode6Handler::BankAnglePredicate m6_t2 = m6_t2_is_bank_angle;
534 static Mode6Handler::BankAnglePredicate m6_t4 = m6_t4_is_bank_angle;
536 static const IOHandler::FaultsConfiguration f_slow = { 180, 200 };
537 static const IOHandler::FaultsConfiguration f_fast = { 250, 290 };
542 const Mode1Handler::EnvelopesConfiguration *m1;
543 const Mode2Handler::Configuration *m2;
544 const Mode3Handler::Configuration *m3;
545 const Mode4Handler::ModesConfiguration *m4;
546 Mode6Handler::BankAnglePredicate m6;
547 const IOHandler::FaultsConfiguration *f;
549 } aircraft_types[] = {
550 { 0, &m1_t4, &m2_t4, &m3_t2, &m4_t6, m6_t4, &f_fast, 2000 },
551 { 1, &m1_t4, &m2_t4, &m3_t2, &m4_t8, m6_t4, &f_fast, 2000 },
552 { 2, &m1_t4, &m2_t4, &m3_t2, &m4_t5, m6_t4, &f_fast, 2000 },
553 { 3, &m1_t4, &m2_t5, &m3_t2, &m4_t7, m6_t4, &f_slow, 2000 },
554 { 4, &m1_t4, &m2_t5, &m3_t2, &m4_t9, m6_t4, &f_slow, 2000 },
555 { 254, &m1_t1, &m2_t1, &m3_t1, &m4_t1, m6_t2, &f_fast, 3500 },
556 { 255, &m1_t1, &m2_t1, &m3_t1, &m4_t1, m6_t2, &f_fast, 2000 }
559 for (size_t i = 0; i < n_elements(aircraft_types); i++)
560 if (aircraft_types[i].type == value)
562 mk->mode1_handler.conf.envelopes = aircraft_types[i].m1;
563 mk->mode2_handler.conf = aircraft_types[i].m2;
564 mk->mode3_handler.conf = aircraft_types[i].m3;
565 mk->mode4_handler.conf.modes = aircraft_types[i].m4;
566 mk->mode6_handler.conf.is_bank_angle = aircraft_types[i].m6;
567 mk->io_handler.conf.faults = aircraft_types[i].f;
568 mk->conf.runway_database = aircraft_types[i].runway_database;
572 state = STATE_INVALID_AIRCRAFT_TYPE;
577 MK_VIII::ConfigurationModule::read_air_data_input_select (int value)
580 return (value >= 0 && value <= 6) || (value >= 10 && value <= 13) || value == 255;
584 MK_VIII::ConfigurationModule::read_position_input_select (int value)
587 mk->io_handler.conf.use_internal_gps = true;
588 else if ((value >= 0 && value <= 5)
589 || (value >= 10 && value <= 13)
592 mk->io_handler.conf.use_internal_gps = false;
600 MK_VIII::ConfigurationModule::read_altitude_callouts (int value)
615 { 0, { MINIMUMS, SMART_500, 200, 100, 50, 40, 30, 20, 10, 0 } },
616 { 1, { MINIMUMS, SMART_500, 200, 0 } },
617 { 2, { MINIMUMS, SMART_500, 100, 50, 40, 30, 20, 10, 0 } },
618 { 3, { MINIMUMS, SMART_500, 0 } },
619 { 4, { MINIMUMS, 200, 100, 50, 40, 30, 20, 10, 0 } },
620 { 5, { MINIMUMS, 200, 0 } },
621 { 6, { MINIMUMS, 100, 50, 40, 30, 20, 10, 0 } },
623 { 8, { MINIMUMS, 0 } },
624 { 9, { MINIMUMS, 500, 200, 100, 50, 40, 30, 20, 10, 0 } },
625 { 10, { MINIMUMS, 500, 200, 0 } },
626 { 11, { MINIMUMS, 500, 100, 50, 40, 30, 20, 10, 0 } },
627 { 12, { MINIMUMS, 500, 0 } },
628 { 13, { MINIMUMS, 1000, 500, 400, 300, 200, 100, 50, 40, 30, 20, 10, 0 } },
629 { 14, { MINIMUMS, 100, 0 } },
630 { 15, { MINIMUMS, 200, 100, 0 } },
631 { 100, { FIELD_500, 0 } },
632 { 101, { FIELD_500_ABOVE, 0 } }
637 mk->mode6_handler.conf.minimums_enabled = false;
638 mk->mode6_handler.conf.smart_500_enabled = false;
639 mk->mode6_handler.conf.above_field_voice = NULL;
640 for (i = 0; i < n_altitude_callouts; i++)
641 mk->mode6_handler.conf.altitude_callouts_enabled[i] = false;
643 for (i = 0; i < n_elements(values); i++)
644 if (values[i].id == value)
646 for (int j = 0; values[i].callouts[j] != 0; j++)
647 switch (values[i].callouts[j])
650 mk->mode6_handler.conf.minimums_enabled = true;
654 mk->mode6_handler.conf.smart_500_enabled = true;
658 mk->mode6_handler.conf.above_field_voice = mk_altitude_voice(Mode6Handler::ALTITUDE_CALLOUT_500);
661 case FIELD_500_ABOVE:
662 mk->mode6_handler.conf.above_field_voice = mk_voice(five_hundred_above);
666 for (unsigned k = 0; k < n_altitude_callouts; k++)
667 if (mk->mode6_handler.altitude_callout_definitions[k] == values[i].callouts[j])
668 mk->mode6_handler.conf.altitude_callouts_enabled[k] = true;
679 MK_VIII::ConfigurationModule::read_audio_menu_select (int value)
681 if (value == 0 || value == 1)
682 mk->mode4_handler.conf.voice_too_low_gear = mk_voice(too_low_gear);
683 else if (value == 2 || value == 3)
684 mk->mode4_handler.conf.voice_too_low_gear = mk_voice(too_low_flaps);
692 MK_VIII::ConfigurationModule::read_terrain_display_select (int value)
695 mk->tcf_handler.conf.enabled = false;
696 else if (value == 0 || value == 1 || (value >= 3 && value <= 15)
697 || (value >= 18 && value <= 20) || (value >= 235 && value <= 255))
698 mk->tcf_handler.conf.enabled = true;
706 MK_VIII::ConfigurationModule::read_options_select_group_1 (int value)
708 if (value >= 0 && value < 128)
710 mk->io_handler.conf.flap_reversal = test_bits(value, 1 << 1);
711 mk->mode6_handler.conf.bank_angle_enabled = test_bits(value, 1 << 2);
712 mk->io_handler.conf.steep_approach_enabled = test_bits(value, 1 << 6);
720 MK_VIII::ConfigurationModule::read_radio_altitude_input_select (int value)
722 if (value >= 0 && value <= 2)
723 mk->io_handler.conf.use_gear_altitude = true;
725 mk->io_handler.conf.use_gear_altitude = false;
726 return (value >= 0 && value <= 4) || (value >= 251 && value <= 255);
730 MK_VIII::ConfigurationModule::read_navigation_input_select (int value)
732 if (value >= 0 && value <= 2)
733 mk->io_handler.conf.localizer_enabled = false;
734 else if ((value >= 3 && value <= 5) || (value >= 250 && value <= 255))
735 mk->io_handler.conf.localizer_enabled = true;
743 MK_VIII::ConfigurationModule::read_attitude_input_select (int value)
746 mk->io_handler.conf.use_attitude_indicator=true;
748 mk->io_handler.conf.use_attitude_indicator=false;
749 return (value >= 0 && value <= 6) || value == 253 || value == 255;
753 MK_VIII::ConfigurationModule::read_heading_input_select (int value)
756 return (value >= 0 && value <= 3) || value == 254 || value == 255;
760 MK_VIII::ConfigurationModule::read_windshear_input_select (int value)
763 return value == 0 || (value >= 253 && value <= 255);
767 MK_VIII::ConfigurationModule::read_input_output_discrete_type_select (int value)
772 IOHandler::LampConfiguration lamp_conf;
773 bool gpws_inhibit_enabled;
774 bool momentary_flap_override_enabled;
775 bool alternate_steep_approach;
777 { 0, { false, false }, false, true, true },
778 { 1, { true, false }, false, true, true },
779 { 2, { false, false }, true, true, true },
780 { 3, { true, false }, true, true, true },
781 { 4, { false, true }, true, true, true },
782 { 5, { true, true }, true, true, true },
783 { 6, { false, false }, true, true, false },
784 { 7, { true, false }, true, true, false },
785 { 254, { false, false }, true, false, true },
786 { 255, { false, false }, true, false, true }
789 for (size_t i = 0; i < n_elements(io_types); i++)
790 if (io_types[i].type == value)
792 mk->io_handler.conf.lamp = &io_types[i].lamp_conf;
793 mk->io_handler.conf.gpws_inhibit_enabled = io_types[i].gpws_inhibit_enabled;
794 mk->io_handler.conf.momentary_flap_override_enabled = io_types[i].momentary_flap_override_enabled;
795 mk->io_handler.conf.alternate_steep_approach = io_types[i].alternate_steep_approach;
803 MK_VIII::ConfigurationModule::read_audio_output_level (int value)
817 for (size_t i = 0; i < n_elements(values); i++)
818 if (values[i].id == value)
820 mk->voice_player.set_volume(mk->voice_player.conf.volume = modify_amplitude(1.0, values[i].relative_dB));
824 // The self test needs the voice player even when the configuration
825 // is invalid, so set a default value.
826 mk->voice_player.set_volume(mk->voice_player.conf.volume = 1.0);
831 MK_VIII::ConfigurationModule::read_undefined_input_select (int value)
838 MK_VIII::ConfigurationModule::boot ()
840 bool (MK_VIII::ConfigurationModule::*readers[N_CATEGORIES]) (int) = {
841 &MK_VIII::ConfigurationModule::read_aircraft_mode_type_select,
842 &MK_VIII::ConfigurationModule::read_air_data_input_select,
843 &MK_VIII::ConfigurationModule::read_position_input_select,
844 &MK_VIII::ConfigurationModule::read_altitude_callouts,
845 &MK_VIII::ConfigurationModule::read_audio_menu_select,
846 &MK_VIII::ConfigurationModule::read_terrain_display_select,
847 &MK_VIII::ConfigurationModule::read_options_select_group_1,
848 &MK_VIII::ConfigurationModule::read_radio_altitude_input_select,
849 &MK_VIII::ConfigurationModule::read_navigation_input_select,
850 &MK_VIII::ConfigurationModule::read_attitude_input_select,
851 &MK_VIII::ConfigurationModule::read_heading_input_select,
852 &MK_VIII::ConfigurationModule::read_windshear_input_select,
853 &MK_VIII::ConfigurationModule::read_input_output_discrete_type_select,
854 &MK_VIII::ConfigurationModule::read_audio_output_level,
855 &MK_VIII::ConfigurationModule::read_undefined_input_select,
856 &MK_VIII::ConfigurationModule::read_undefined_input_select,
857 &MK_VIII::ConfigurationModule::read_undefined_input_select
860 memcpy(effective_categories, categories, sizeof(categories));
863 for (int i = 0; i < N_CATEGORIES; i++)
864 if (! (this->*readers[i])(effective_categories[i]))
866 SG_LOG(SG_INSTR, SG_ALERT, "MK VIII EGPWS configuration category " << i + 1 << ": invalid value " << effective_categories[i]);
868 if (state == STATE_OK)
869 state = STATE_INVALID_DATABASE;
871 mk_doutput(gpws_inop) = true;
872 mk_doutput(tad_inop) = true;
879 if (mk->mode6_handler.conf.above_field_voice && ! mk->tcf_handler.conf.enabled)
882 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");
883 state = STATE_INVALID_DATABASE;
888 MK_VIII::ConfigurationModule::bind (SGPropertyNode *node)
890 for (int i = 0; i < N_CATEGORIES; i++)
892 std::ostringstream name;
893 name << "configuration-module/category-" << i + 1;
894 mk->properties_handler.tie(node, name.str().c_str(), SGRawValuePointer<int>(&categories[i]));
898 ///////////////////////////////////////////////////////////////////////////////
899 // FaultHandler ///////////////////////////////////////////////////////////////
900 ///////////////////////////////////////////////////////////////////////////////
902 // [INSTALL] only specifies that the faults cause a GPWS INOP
903 // indication. We arbitrarily set a TAD INOP when it makes sense.
904 const unsigned int MK_VIII::FaultHandler::fault_inops[] = {
905 INOP_GPWS | INOP_TAD, // [INSTALL] 3.15.1.3
906 INOP_GPWS, // [INSTALL] appendix E 6.6.2
907 INOP_GPWS, // [INSTALL] appendix E 6.6.4
908 INOP_GPWS, // [INSTALL] appendix E 6.6.6
909 INOP_GPWS | INOP_TAD, // [INSTALL] appendix E 6.6.7
910 INOP_GPWS, // [INSTALL] appendix E 6.6.13
911 INOP_GPWS, // [INSTALL] appendix E 6.6.25
912 INOP_GPWS, // [INSTALL] appendix E 6.6.27
913 INOP_TAD, // unspecified
914 INOP_GPWS, // unspecified
915 INOP_GPWS, // unspecified
916 // [INSTALL] 2.3.10.1 specifies that GPWS INOP is "activated during
917 // any detected partial or total failure of the GPWS modes 1-5", so
918 // do not set any INOP for mode 6 and bank angle.
921 INOP_TAD // unspecified
925 MK_VIII::FaultHandler::has_faults () const
927 for (int i = 0; i < N_FAULTS; i++)
935 MK_VIII::FaultHandler::has_faults (unsigned int inop)
937 for (int i = 0; i < N_FAULTS; i++)
938 if (faults[i] && test_bits(fault_inops[i], inop))
945 MK_VIII::FaultHandler::boot ()
947 memset(faults, 0, sizeof(faults));
951 MK_VIII::FaultHandler::set_fault (Fault fault)
955 faults[fault] = true;
957 mk->self_test_handler.set_inop();
959 if (test_bits(fault_inops[fault], INOP_GPWS))
961 mk_unset_alerts(~mk_alert(TCF_TOO_LOW_TERRAIN));
962 mk_doutput(gpws_inop) = true;
964 if (test_bits(fault_inops[fault], INOP_TAD))
966 mk_unset_alerts(mk_alert(TCF_TOO_LOW_TERRAIN));
967 mk_doutput(tad_inop) = true;
973 MK_VIII::FaultHandler::unset_fault (Fault fault)
977 faults[fault] = false;
978 if (! has_faults(INOP_GPWS))
979 mk_doutput(gpws_inop) = false;
980 if (! has_faults(INOP_TAD))
981 mk_doutput(tad_inop) = false;
985 ///////////////////////////////////////////////////////////////////////////////
986 // IOHandler //////////////////////////////////////////////////////////////////
987 ///////////////////////////////////////////////////////////////////////////////
990 MK_VIII::IOHandler::TerrainClearanceFilter::update (double agl)
992 // [PILOT] page 20 specifies that the terrain clearance is equal to
993 // 75% of the radio altitude, averaged over the previous 15 seconds.
995 // no updates when simulation is paused (dt=0.0), and add 5 samples/second only
996 if (globals->get_sim_time_sec() - last_update < 0.2)
998 last_update = globals->get_sim_time_sec();
1000 samples_type::iterator iter;
1002 // remove samples older than 15 seconds
1003 for (iter = samples.begin(); iter != samples.end() && globals->get_sim_time_sec() - (*iter).timestamp >= 15; iter = samples.begin())
1004 samples.erase(iter);
1006 // append new sample
1007 samples.push_back(Sample<double>(agl));
1009 // calculate average
1010 double new_value = 0;
1011 if (samples.size() > 0)
1013 // time consuming loop => queue limited to 75 samples
1014 // (= 15seconds * 5samples/second)
1015 for (iter = samples.begin(); iter != samples.end(); iter++)
1016 new_value += (*iter).value;
1017 new_value /= samples.size();
1021 if (new_value > value)
1028 MK_VIII::IOHandler::TerrainClearanceFilter::reset ()
1035 MK_VIII::IOHandler::IOHandler (MK_VIII *device)
1036 : mk(device), _lamp(LAMP_NONE)
1038 memset(&input_feeders, 0, sizeof(input_feeders));
1039 memset(&inputs.discretes, 0, sizeof(inputs.discretes));
1040 memset(&outputs, 0, sizeof(outputs));
1041 memset(&power_saved, 0, sizeof(power_saved));
1043 mk_dinput_feed(landing_gear) = true;
1044 mk_dinput_feed(landing_flaps) = true;
1045 mk_dinput_feed(glideslope_inhibit) = true;
1046 mk_dinput_feed(decision_height) = true;
1047 mk_dinput_feed(autopilot_engaged) = true;
1048 mk_ainput_feed(uncorrected_barometric_altitude) = true;
1049 mk_ainput_feed(barometric_altitude_rate) = true;
1050 mk_ainput_feed(radio_altitude) = true;
1051 mk_ainput_feed(glideslope_deviation) = true;
1052 mk_ainput_feed(roll_angle) = true;
1053 mk_ainput_feed(localizer_deviation) = true;
1054 mk_ainput_feed(computed_airspeed) = true;
1056 // will be unset on power on
1057 mk_doutput(gpws_inop) = true;
1058 mk_doutput(tad_inop) = true;
1062 MK_VIII::IOHandler::boot ()
1064 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
1067 mk_doutput(gpws_inop) = false;
1068 mk_doutput(tad_inop) = false;
1070 mk_doutput(glideslope_cancel) = power_saved.glideslope_cancel;
1072 altitude_samples.clear();
1073 reset_terrain_clearance();
1077 MK_VIII::IOHandler::post_boot ()
1079 if (momentary_steep_approach_enabled())
1081 last_landing_gear = mk_dinput(landing_gear);
1082 last_real_flaps_down = real_flaps_down();
1085 // read externally-latching input discretes
1086 update_alternate_discrete_input(&mk_dinput(mode6_low_volume));
1087 update_alternate_discrete_input(&mk_dinput(audio_inhibit));
1091 MK_VIII::IOHandler::power_off ()
1093 power_saved.glideslope_cancel = mk_doutput(glideslope_cancel); // [SPEC] 6.2.5
1095 memset(&outputs, 0, sizeof(outputs));
1097 audio_inhibit_fault_timer.stop();
1098 landing_gear_fault_timer.stop();
1099 flaps_down_fault_timer.stop();
1100 momentary_flap_override_fault_timer.stop();
1101 self_test_fault_timer.stop();
1102 glideslope_cancel_fault_timer.stop();
1103 steep_approach_fault_timer.stop();
1104 gpws_inhibit_fault_timer.stop();
1105 ta_tcf_inhibit_fault_timer.stop();
1108 mk_doutput(gpws_inop) = true;
1109 mk_doutput(tad_inop) = true;
1113 MK_VIII::IOHandler::enter_ground ()
1115 reset_terrain_clearance();
1117 if (conf.momentary_flap_override_enabled)
1118 mk_doutput(flap_override) = false; // [INSTALL] 3.15.1.6
1122 MK_VIII::IOHandler::enter_takeoff ()
1124 reset_terrain_clearance();
1126 if (momentary_steep_approach_enabled())
1127 // landing or go-around, disable steep approach as per [SPEC] 6.2.1
1128 mk_doutput(steep_approach) = false;
1132 MK_VIII::IOHandler::update_inputs ()
1134 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
1137 // 1. process input feeders
1139 if (mk_dinput_feed(landing_gear))
1140 mk_dinput(landing_gear) = mk_node(gear_down)->getBoolValue();
1141 if (mk_dinput_feed(landing_flaps))
1142 mk_dinput(landing_flaps) = mk_node(flaps)->getDoubleValue() < 1;
1143 if (mk_dinput_feed(glideslope_inhibit))
1144 mk_dinput(glideslope_inhibit) = mk_node(nav0_gs_distance)->getDoubleValue() < 0;
1145 if (mk_dinput_feed(autopilot_engaged))
1149 mode = mk_node(autopilot_heading_lock)->getStringValue();
1150 mk_dinput(autopilot_engaged) = mode && *mode;
1153 if (mk_ainput_feed(uncorrected_barometric_altitude))
1155 if (mk_node(altimeter_serviceable)->getBoolValue())
1156 mk_ainput(uncorrected_barometric_altitude).set(mk_node(altimeter_altitude)->getDoubleValue());
1158 mk_ainput(uncorrected_barometric_altitude).unset();
1160 if (mk_ainput_feed(barometric_altitude_rate))
1161 // Do not use the vsi, because it is pressure-based only, and
1162 // therefore too laggy for GPWS alerting purposes. I guess that
1163 // a real ADC combines pressure-based and inerta-based altitude
1164 // rates to provide a non-laggy rate. That non-laggy rate is
1165 // best emulated by /velocities/vertical-speed-fps * 60.
1166 mk_ainput(barometric_altitude_rate).set(mk_node(vs)->getDoubleValue() * 60);
1167 if (mk_ainput_feed(radio_altitude))
1170 if (conf.use_gear_altitude)
1171 agl = mk_node(altitude_gear_agl)->getDoubleValue();
1173 agl = mk_node(altitude_agl)->getDoubleValue();
1174 // Some flight models may return negative values when on the
1175 // ground or after a crash; do not allow them.
1176 mk_ainput(radio_altitude).set(SG_MAX2(0.0, agl));
1178 if (mk_ainput_feed(glideslope_deviation))
1180 if (mk_node(nav0_serviceable)->getBoolValue()
1181 && mk_node(nav0_gs_serviceable)->getBoolValue()
1182 && mk_node(nav0_in_range)->getBoolValue()
1183 && mk_node(nav0_has_gs)->getBoolValue())
1184 // gs-needle-deflection is expressed in degrees, and 1 dot =
1185 // 0.32 degrees (according to
1186 // http://www.ntsb.gov/Recs/letters/2000/A00_41_45.pdf)
1187 mk_ainput(glideslope_deviation).set(mk_node(nav0_gs_needle_deflection)->getDoubleValue() / 0.32 * GLIDESLOPE_DOTS_TO_DDM);
1189 mk_ainput(glideslope_deviation).unset();
1191 if (mk_ainput_feed(roll_angle))
1193 if (conf.use_attitude_indicator)
1195 // read data from attitude indicator instrument (requires vacuum system to work)
1196 if (mk_node(ai_serviceable)->getBoolValue() && ! mk_node(ai_caged)->getBoolValue())
1197 mk_ainput(roll_angle).set(mk_node(ai_roll)->getDoubleValue());
1199 mk_ainput(roll_angle).unset();
1203 // use simulator source
1204 mk_ainput(roll_angle).set(mk_node(orientation_roll)->getDoubleValue());
1207 if (mk_ainput_feed(localizer_deviation))
1209 if (mk_node(nav0_serviceable)->getBoolValue()
1210 && mk_node(nav0_cdi_serviceable)->getBoolValue()
1211 && mk_node(nav0_in_range)->getBoolValue()
1212 && mk_node(nav0_nav_loc)->getBoolValue())
1213 // heading-needle-deflection is expressed in degrees, and 1
1214 // dot = 2 degrees (0.5 degrees for ILS, but navradio.cxx
1215 // performs the conversion)
1216 mk_ainput(localizer_deviation).set(mk_node(nav0_heading_needle_deflection)->getDoubleValue() / 2 * LOCALIZER_DOTS_TO_DDM);
1218 mk_ainput(localizer_deviation).unset();
1220 if (mk_ainput_feed(computed_airspeed))
1222 if (mk_node(asi_serviceable)->getBoolValue())
1223 mk_ainput(computed_airspeed).set(mk_node(asi_speed)->getDoubleValue());
1225 mk_ainput(computed_airspeed).unset();
1230 mk_data(decision_height).set(&mk_ainput(decision_height));
1231 mk_data(radio_altitude).set(&mk_ainput(radio_altitude));
1232 mk_data(roll_angle).set(&mk_ainput(roll_angle));
1234 // update barometric_altitude_rate as per [SPEC] 6.2.1: "During
1235 // normal conditions, the system will base Mode 1 computations upon
1236 // barometric rate from the ADC. If this computed data is not valid
1237 // or available then the system will use internally computed
1238 // barometric altitude rate."
1240 if (! mk_ainput(barometric_altitude_rate).ncd)
1241 // the altitude rate input is valid, use it
1242 mk_data(barometric_altitude_rate).set(mk_ainput(barometric_altitude_rate).get());
1247 // The altitude rate input is invalid. We can compute an
1248 // altitude rate if all the following conditions are true:
1250 // - the altitude input is valid
1251 // - there is an altitude sample with age >= 1 second
1252 // - that altitude sample is valid
1254 if (! mk_ainput(uncorrected_barometric_altitude).ncd)
1256 if (! altitude_samples.empty() && ! altitude_samples.front().value.ncd)
1258 double elapsed = globals->get_sim_time_sec() - altitude_samples.front().timestamp;
1262 mk_data(barometric_altitude_rate).set((mk_ainput(uncorrected_barometric_altitude).get() - altitude_samples.front().value.get()) / elapsed * 60);
1268 mk_data(barometric_altitude_rate).unset();
1271 altitude_samples.push_back(Sample< Parameter<double> >(mk_ainput(uncorrected_barometric_altitude)));
1273 // Erase everything from the beginning of the list up to the sample
1274 // preceding the most recent sample whose age is >= 1 second.
1276 altitude_samples_type::iterator erase_last = altitude_samples.begin();
1277 altitude_samples_type::iterator iter;
1279 for (iter = altitude_samples.begin(); iter != altitude_samples.end(); iter++)
1280 if (globals->get_sim_time_sec() - (*iter).timestamp >= 1)
1285 if (erase_last != altitude_samples.begin())
1286 altitude_samples.erase(altitude_samples.begin(), erase_last);
1290 if (conf.use_internal_gps)
1292 mk_data(gps_altitude).set(mk_node(altitude)->getDoubleValue());
1293 mk_data(gps_latitude).set(mk_node(latitude)->getDoubleValue());
1294 mk_data(gps_longitude).set(mk_node(longitude)->getDoubleValue());
1295 mk_data(gps_vertical_figure_of_merit).set(0.0);
1299 mk_data(gps_altitude).set(&mk_ainput(gps_altitude));
1300 mk_data(gps_latitude).set(&mk_ainput(gps_latitude));
1301 mk_data(gps_longitude).set(&mk_ainput(gps_longitude));
1302 mk_data(gps_vertical_figure_of_merit).set(&mk_ainput(gps_vertical_figure_of_merit));
1305 // update glideslope and localizer
1307 mk_data(glideslope_deviation_dots).set(&mk_ainput(glideslope_deviation), GLIDESLOPE_DDM_TO_DOTS);
1308 mk_data(localizer_deviation_dots).set(&mk_ainput(localizer_deviation), LOCALIZER_DDM_TO_DOTS);
1310 // Update geometric altitude; [SPEC] 6.7.8 provides an overview of a
1311 // complex algorithm which combines several input sources to
1312 // calculate the geometric altitude. Since the exact algorithm is
1313 // not given, we fallback to a much simpler method.
1315 if (! mk_data(gps_altitude).ncd)
1316 mk_data(geometric_altitude).set(mk_data(gps_altitude).get());
1317 else if (! mk_ainput(uncorrected_barometric_altitude).ncd)
1318 mk_data(geometric_altitude).set(mk_ainput(uncorrected_barometric_altitude).get());
1320 mk_data(geometric_altitude).unset();
1322 // update terrain clearance
1324 update_terrain_clearance();
1326 // 3. perform sanity checks
1328 if (! mk_data(radio_altitude).ncd && mk_data(radio_altitude).get() < 0)
1329 mk_data(radio_altitude).unset();
1331 if (! mk_data(decision_height).ncd && mk_data(decision_height).get() < 0)
1332 mk_data(decision_height).unset();
1334 if (! mk_data(gps_latitude).ncd
1335 && (mk_data(gps_latitude).get() < -90
1336 || mk_data(gps_latitude).get() > 90))
1337 mk_data(gps_latitude).unset();
1339 if (! mk_data(gps_longitude).ncd
1340 && (mk_data(gps_longitude).get() < -180
1341 || mk_data(gps_longitude).get() > 180))
1342 mk_data(gps_longitude).unset();
1344 if (! mk_data(roll_angle).ncd
1345 && ((mk_data(roll_angle).get() < -180)
1346 || (mk_data(roll_angle).get() > 180)))
1347 mk_data(roll_angle).unset();
1349 // 4. process input feeders requiring data computed above
1351 if (mk_dinput_feed(decision_height))
1352 mk_dinput(decision_height) = ! mk_data(radio_altitude).ncd
1353 && ! mk_data(decision_height).ncd
1354 && mk_data(radio_altitude).get() <= mk_data(decision_height).get();
1358 MK_VIII::IOHandler::update_terrain_clearance ()
1360 if (! mk_data(radio_altitude).ncd)
1361 mk_data(terrain_clearance).set(terrain_clearance_filter.update(mk_data(radio_altitude).get()));
1363 mk_data(terrain_clearance).unset();
1367 MK_VIII::IOHandler::reset_terrain_clearance ()
1369 terrain_clearance_filter.reset();
1370 update_terrain_clearance();
1374 MK_VIII::IOHandler::reposition ()
1376 reset_terrain_clearance();
1380 MK_VIII::IOHandler::handle_input_fault (bool test, FaultHandler::Fault fault)
1384 if (! mk->fault_handler.faults[fault])
1385 mk->fault_handler.set_fault(fault);
1388 mk->fault_handler.unset_fault(fault);
1392 MK_VIII::IOHandler::handle_input_fault (bool test,
1394 double max_duration,
1395 FaultHandler::Fault fault)
1399 if (! mk->fault_handler.faults[fault])
1401 if (timer->start_or_elapsed() >= max_duration)
1402 mk->fault_handler.set_fault(fault);
1407 mk->fault_handler.unset_fault(fault);
1413 MK_VIII::IOHandler::update_input_faults ()
1415 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
1418 // [INSTALL] 3.15.1.3
1419 handle_input_fault(mk_dinput(audio_inhibit),
1420 &audio_inhibit_fault_timer,
1422 FaultHandler::FAULT_ALL_MODES_INHIBIT);
1424 // [INSTALL] appendix E 6.6.2
1425 handle_input_fault(mk_dinput(landing_gear)
1426 && ! mk_ainput(computed_airspeed).ncd
1427 && mk_ainput(computed_airspeed).get() > conf.faults->max_gear_down_airspeed,
1428 &landing_gear_fault_timer,
1430 FaultHandler::FAULT_GEAR_SWITCH);
1432 // [INSTALL] appendix E 6.6.4
1433 handle_input_fault(flaps_down()
1434 && ! mk_ainput(computed_airspeed).ncd
1435 && mk_ainput(computed_airspeed).get() > conf.faults->max_flaps_down_airspeed,
1436 &flaps_down_fault_timer,
1438 FaultHandler::FAULT_FLAPS_SWITCH);
1440 // [INSTALL] appendix E 6.6.6
1441 if (conf.momentary_flap_override_enabled)
1442 handle_input_fault(mk_dinput(momentary_flap_override),
1443 &momentary_flap_override_fault_timer,
1445 FaultHandler::FAULT_MOMENTARY_FLAP_OVERRIDE_INVALID);
1447 // [INSTALL] appendix E 6.6.7
1448 handle_input_fault(mk_dinput(self_test),
1449 &self_test_fault_timer,
1451 FaultHandler::FAULT_SELF_TEST_INVALID);
1453 // [INSTALL] appendix E 6.6.13
1454 handle_input_fault(mk_dinput(glideslope_cancel),
1455 &glideslope_cancel_fault_timer,
1457 FaultHandler::FAULT_GLIDESLOPE_CANCEL_INVALID);
1459 // [INSTALL] appendix E 6.6.25
1460 if (momentary_steep_approach_enabled())
1461 handle_input_fault(mk_dinput(steep_approach),
1462 &steep_approach_fault_timer,
1464 FaultHandler::FAULT_STEEP_APPROACH_INVALID);
1466 // [INSTALL] appendix E 6.6.27
1467 if (conf.gpws_inhibit_enabled)
1468 handle_input_fault(mk_dinput(gpws_inhibit),
1469 &gpws_inhibit_fault_timer,
1471 FaultHandler::FAULT_GPWS_INHIBIT);
1473 // [INSTALL] does not specify a fault for this one, but it makes
1474 // sense to have it behave like GPWS inhibit
1475 handle_input_fault(mk_dinput(ta_tcf_inhibit),
1476 &ta_tcf_inhibit_fault_timer,
1478 FaultHandler::FAULT_TA_TCF_INHIBIT);
1480 // [PILOT] page 48: "In the event that required data for a
1481 // particular function is not available, then that function is
1482 // automatically inhibited and annunciated"
1484 handle_input_fault(mk_data(radio_altitude).ncd
1485 || mk_ainput(uncorrected_barometric_altitude).ncd
1486 || mk_data(barometric_altitude_rate).ncd
1487 || mk_ainput(computed_airspeed).ncd
1488 || mk_data(terrain_clearance).ncd,
1489 FaultHandler::FAULT_MODES14_INPUTS_INVALID);
1491 if (! mk_dinput(glideslope_inhibit))
1492 handle_input_fault(mk_data(radio_altitude).ncd,
1493 FaultHandler::FAULT_MODE5_INPUTS_INVALID);
1495 if (mk->mode6_handler.altitude_callouts_enabled())
1496 handle_input_fault(mk->mode6_handler.conf.above_field_voice
1497 ? (mk_data(gps_latitude).ncd
1498 || mk_data(gps_longitude).ncd
1499 || mk_data(geometric_altitude).ncd)
1500 : mk_data(radio_altitude).ncd,
1501 FaultHandler::FAULT_MODE6_INPUTS_INVALID);
1503 if (mk->mode6_handler.conf.bank_angle_enabled)
1504 handle_input_fault(mk_data(roll_angle).ncd,
1505 FaultHandler::FAULT_BANK_ANGLE_INPUTS_INVALID);
1507 if (mk->tcf_handler.conf.enabled)
1508 handle_input_fault(mk_data(radio_altitude).ncd
1509 || mk_data(geometric_altitude).ncd
1510 || mk_data(gps_latitude).ncd
1511 || mk_data(gps_longitude).ncd
1512 || mk_data(gps_vertical_figure_of_merit).ncd,
1513 FaultHandler::FAULT_TCF_INPUTS_INVALID);
1517 MK_VIII::IOHandler::update_alternate_discrete_input (bool *ptr)
1519 assert(mk->system_handler.state == SystemHandler::STATE_ON);
1521 if (ptr == &mk_dinput(mode6_low_volume))
1523 if (mk->configuration_module.state == ConfigurationModule::STATE_OK
1524 && mk->self_test_handler.state == SelfTestHandler::STATE_NONE)
1525 mk->mode6_handler.set_volume(*ptr ? modify_amplitude(1.0, -6) : 1.0);
1527 else if (ptr == &mk_dinput(audio_inhibit))
1529 if (mk->configuration_module.state == ConfigurationModule::STATE_OK
1530 && mk->self_test_handler.state == SelfTestHandler::STATE_NONE)
1531 mk->voice_player.set_volume(*ptr ? 0.0 : mk->voice_player.conf.volume);
1536 MK_VIII::IOHandler::update_internal_latches ()
1538 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
1542 if (conf.momentary_flap_override_enabled
1543 && mk_doutput(flap_override)
1544 && ! mk->state_handler.takeoff
1545 && (mk_data(radio_altitude).ncd || mk_data(radio_altitude).get() <= 50))
1546 mk_doutput(flap_override) = false;
1549 if (momentary_steep_approach_enabled())
1551 if (mk_doutput(steep_approach)
1552 && ! mk->state_handler.takeoff
1553 && ((last_landing_gear && ! mk_dinput(landing_gear))
1554 || (last_real_flaps_down && ! real_flaps_down())))
1555 // gear or flaps raised during approach: it's a go-around
1556 mk_doutput(steep_approach) = false;
1558 last_landing_gear = mk_dinput(landing_gear);
1559 last_real_flaps_down = real_flaps_down();
1563 if (mk_doutput(glideslope_cancel)
1564 && (mk_data(glideslope_deviation_dots).ncd
1565 || mk_data(radio_altitude).ncd
1566 || mk_data(radio_altitude).get() > 2000
1567 || mk_data(radio_altitude).get() < 30))
1568 mk_doutput(glideslope_cancel) = false;
1572 MK_VIII::IOHandler::update_egpws_alert_discrete_1 ()
1574 if (mk->voice_player.voice)
1579 VoicePlayer::Voice *voice;
1581 { 11, mk_voice(sink_rate_pause_sink_rate) },
1582 { 12, mk_voice(pull_up) },
1583 { 13, mk_voice(terrain) },
1584 { 13, mk_voice(terrain_pause_terrain) },
1585 { 14, mk_voice(dont_sink_pause_dont_sink) },
1586 { 15, mk_voice(too_low_gear) },
1587 { 16, mk_voice(too_low_flaps) },
1588 { 17, mk_voice(too_low_terrain) },
1589 { 18, mk_voice(soft_glideslope) },
1590 { 18, mk_voice(hard_glideslope) },
1591 { 19, mk_voice(minimums_minimums) }
1594 for (size_t i = 0; i < n_elements(voices); i++)
1595 if (voices[i].voice == mk->voice_player.voice)
1597 mk_aoutput(egpws_alert_discrete_1) = 1 << voices[i].bit;
1602 mk_aoutput(egpws_alert_discrete_1) = 0;
1606 MK_VIII::IOHandler::update_egpwc_logic_discretes ()
1608 mk_aoutput(egpwc_logic_discretes) = 0;
1610 if (mk->state_handler.takeoff)
1611 mk_aoutput(egpwc_logic_discretes) |= 1 << 18;
1616 unsigned int alerts;
1618 { 13, mk_alert(TCF_TOO_LOW_TERRAIN) },
1619 { 19, mk_alert(MODE1_SINK_RATE) },
1620 { 20, mk_alert(MODE1_PULL_UP) },
1621 { 21, mk_alert(MODE2A_PREFACE) | mk_alert(MODE2B_PREFACE) | mk_alert(MODE2B_LANDING_MODE) | mk_alert(MODE2A_ALTITUDE_GAIN_TERRAIN_CLOSING) },
1622 { 22, mk_alert(MODE2A) | mk_alert(MODE2B) },
1623 { 23, mk_alert(MODE3) },
1624 { 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) },
1625 { 25, mk_alert(MODE5_SOFT) | mk_alert(MODE5_HARD) }
1628 for (size_t i = 0; i < n_elements(logic); i++)
1629 if (mk_test_alerts(logic[i].alerts))
1630 mk_aoutput(egpwc_logic_discretes) |= 1 << logic[i].bit;
1634 MK_VIII::IOHandler::update_mode6_callouts_discrete_1 ()
1636 if (mk->voice_player.voice)
1641 VoicePlayer::Voice *voice;
1643 { 11, mk_voice(minimums_minimums) },
1644 { 16, mk_altitude_voice(Mode6Handler::ALTITUDE_CALLOUT_10) },
1645 { 17, mk_altitude_voice(Mode6Handler::ALTITUDE_CALLOUT_20) },
1646 { 18, mk_altitude_voice(Mode6Handler::ALTITUDE_CALLOUT_30) },
1647 { 19, mk_altitude_voice(Mode6Handler::ALTITUDE_CALLOUT_40) },
1648 { 20, mk_altitude_voice(Mode6Handler::ALTITUDE_CALLOUT_50) },
1649 { 23, mk_altitude_voice(Mode6Handler::ALTITUDE_CALLOUT_100) },
1650 { 24, mk_altitude_voice(Mode6Handler::ALTITUDE_CALLOUT_200) },
1651 { 25, mk_altitude_voice(Mode6Handler::ALTITUDE_CALLOUT_300) }
1654 for (size_t i = 0; i < n_elements(voices); i++)
1655 if (voices[i].voice == mk->voice_player.voice)
1657 mk_aoutput(mode6_callouts_discrete_1) = 1 << voices[i].bit;
1662 mk_aoutput(mode6_callouts_discrete_1) = 0;
1666 MK_VIII::IOHandler::update_mode6_callouts_discrete_2 ()
1668 if (mk->voice_player.voice)
1673 VoicePlayer::Voice *voice;
1675 { 11, mk_altitude_voice(Mode6Handler::ALTITUDE_CALLOUT_400) },
1676 { 12, mk_altitude_voice(Mode6Handler::ALTITUDE_CALLOUT_500) },
1677 { 13, mk_altitude_voice(Mode6Handler::ALTITUDE_CALLOUT_1000) },
1678 { 18, mk_voice(bank_angle_pause_bank_angle) },
1679 { 18, mk_voice(bank_angle_pause_bank_angle_3) },
1680 { 23, mk_voice(five_hundred_above) }
1683 for (size_t i = 0; i < n_elements(voices); i++)
1684 if (voices[i].voice == mk->voice_player.voice)
1686 mk_aoutput(mode6_callouts_discrete_2) = 1 << voices[i].bit;
1691 mk_aoutput(mode6_callouts_discrete_2) = 0;
1695 MK_VIII::IOHandler::update_egpws_alert_discrete_2 ()
1697 mk_aoutput(egpws_alert_discrete_2) = 1 << 27; // Terrain NA
1699 if (mk_doutput(glideslope_cancel))
1700 mk_aoutput(egpws_alert_discrete_2) |= 1 << 11;
1701 if (mk_doutput(gpws_alert))
1702 mk_aoutput(egpws_alert_discrete_2) |= 1 << 12;
1703 if (mk_doutput(gpws_warning))
1704 mk_aoutput(egpws_alert_discrete_2) |= 1 << 13;
1705 if (mk_doutput(gpws_inop))
1706 mk_aoutput(egpws_alert_discrete_2) |= 1 << 14;
1707 if (mk_doutput(audio_on))
1708 mk_aoutput(egpws_alert_discrete_2) |= 1 << 16;
1709 if (mk_doutput(tad_inop))
1710 mk_aoutput(egpws_alert_discrete_2) |= 1 << 24;
1711 if (mk->fault_handler.has_faults())
1712 mk_aoutput(egpws_alert_discrete_2) |= 1 << 25;
1716 MK_VIII::IOHandler::update_egpwc_alert_discrete_3 ()
1718 mk_aoutput(egpwc_alert_discrete_3) = 1 << 17 | 1 << 18;
1720 if (mk->fault_handler.faults[FaultHandler::FAULT_MODES14_INPUTS_INVALID])
1721 mk_aoutput(egpwc_alert_discrete_3) |= 1 << 11;
1722 if (mk->fault_handler.faults[FaultHandler::FAULT_MODE5_INPUTS_INVALID])
1723 mk_aoutput(egpwc_alert_discrete_3) |= 1 << 12;
1724 if (mk->fault_handler.faults[FaultHandler::FAULT_MODE6_INPUTS_INVALID])
1725 mk_aoutput(egpwc_alert_discrete_3) |= 1 << 13;
1726 if (mk->fault_handler.faults[FaultHandler::FAULT_BANK_ANGLE_INPUTS_INVALID])
1727 mk_aoutput(egpwc_alert_discrete_3) |= 1 << 14;
1728 if (mk_doutput(tad_inop))
1729 mk_aoutput(egpwc_alert_discrete_3) |= 1 << 16;
1733 MK_VIII::IOHandler::update_outputs ()
1735 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
1738 mk_doutput(audio_on) = ! mk_dinput(audio_inhibit)
1739 && mk->voice_player.voice
1740 && ! mk->voice_player.voice->element->silence;
1742 update_egpws_alert_discrete_1();
1743 update_egpwc_logic_discretes();
1744 update_mode6_callouts_discrete_1();
1745 update_mode6_callouts_discrete_2();
1746 update_egpws_alert_discrete_2();
1747 update_egpwc_alert_discrete_3();
1751 MK_VIII::IOHandler::get_lamp_output (Lamp lamp)
1755 case LAMP_GLIDESLOPE:
1756 return &mk_doutput(gpws_alert);
1759 return conf.lamp->format2 ? &mk_doutput(gpws_alert) : &mk_doutput(gpws_warning);
1762 return &mk_doutput(gpws_warning);
1765 assert_not_reached();
1771 MK_VIII::IOHandler::update_lamps ()
1773 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
1776 if (_lamp != LAMP_NONE && conf.lamp->flashing)
1778 // [SPEC] 6.9.3: 70 cycles per minute
1779 if (lamp_timer.elapsed() >= 60.0 / 70.0 / 2.0)
1781 bool *output = get_lamp_output(_lamp);
1782 *output = ! *output;
1789 MK_VIII::IOHandler::set_lamp (Lamp lamp)
1796 mk_doutput(gpws_warning) = false;
1797 mk_doutput(gpws_alert) = false;
1799 if (lamp != LAMP_NONE)
1801 *get_lamp_output(lamp) = true;
1807 MK_VIII::IOHandler::gpws_inhibit () const
1809 return conf.gpws_inhibit_enabled && mk_dinput(gpws_inhibit);
1813 MK_VIII::IOHandler::real_flaps_down () const
1815 return conf.flap_reversal ? mk_dinput(landing_flaps) : ! mk_dinput(landing_flaps);
1819 MK_VIII::IOHandler::flaps_down () const
1821 return flap_override() ? true : real_flaps_down();
1825 MK_VIII::IOHandler::flap_override () const
1827 return conf.momentary_flap_override_enabled ? mk_doutput(flap_override) : false;
1831 MK_VIII::IOHandler::steep_approach () const
1833 if (conf.steep_approach_enabled)
1834 // If alternate action was configured in category 13, we return
1835 // the value of the input discrete (which should be connected to a
1836 // latching steep approach cockpit switch). Otherwise, we return
1837 // the value of the output discrete.
1838 return conf.alternate_steep_approach ? mk_dinput(steep_approach) : mk_doutput(steep_approach);
1844 MK_VIII::IOHandler::momentary_steep_approach_enabled () const
1846 return conf.steep_approach_enabled && ! conf.alternate_steep_approach;
1850 MK_VIII::IOHandler::tie_input (SGPropertyNode *node,
1855 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));
1857 mk->properties_handler.tie(node, (string("input-feeders/discretes/") + name).c_str(), SGRawValuePointer<bool>(feed));
1861 MK_VIII::IOHandler::tie_input (SGPropertyNode *node,
1863 Parameter<double> *input,
1866 mk->properties_handler.tie(node, (string("inputs/arinc429/") + name).c_str(), SGRawValuePointer<double>(input->get_pointer()));
1867 mk->properties_handler.tie(node, (string("inputs/arinc429/") + name + "-ncd").c_str(), SGRawValuePointer<bool>(&input->ncd));
1869 mk->properties_handler.tie(node, (string("input-feeders/arinc429/") + name).c_str(), SGRawValuePointer<bool>(feed));
1873 MK_VIII::IOHandler::tie_output (SGPropertyNode *node,
1877 SGPropertyNode *child = node->getNode((string("outputs/discretes/") + name).c_str(), true);
1879 mk->properties_handler.tie(child, SGRawValuePointer<bool>(output));
1880 child->setAttribute(SGPropertyNode::WRITE, false);
1884 MK_VIII::IOHandler::tie_output (SGPropertyNode *node,
1888 SGPropertyNode *child = node->getNode((string("outputs/arinc429/") + name).c_str(), true);
1890 mk->properties_handler.tie(child, SGRawValuePointer<int>(output));
1891 child->setAttribute(SGPropertyNode::WRITE, false);
1895 MK_VIII::IOHandler::bind (SGPropertyNode *node)
1897 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));
1899 tie_input(node, "landing-gear", &mk_dinput(landing_gear), &mk_dinput_feed(landing_gear));
1900 tie_input(node, "landing-flaps", &mk_dinput(landing_flaps), &mk_dinput_feed(landing_flaps));
1901 tie_input(node, "momentary-flap-override", &mk_dinput(momentary_flap_override));
1902 tie_input(node, "self-test", &mk_dinput(self_test));
1903 tie_input(node, "glideslope-inhibit", &mk_dinput(glideslope_inhibit), &mk_dinput_feed(glideslope_inhibit));
1904 tie_input(node, "glideslope-cancel", &mk_dinput(glideslope_cancel));
1905 tie_input(node, "decision-height", &mk_dinput(decision_height), &mk_dinput_feed(decision_height));
1906 tie_input(node, "mode6-low-volume", &mk_dinput(mode6_low_volume));
1907 tie_input(node, "audio-inhibit", &mk_dinput(audio_inhibit));
1908 tie_input(node, "ta-tcf-inhibit", &mk_dinput(ta_tcf_inhibit));
1909 tie_input(node, "autopilot-engaged", &mk_dinput(autopilot_engaged), &mk_dinput_feed(autopilot_engaged));
1910 tie_input(node, "steep-approach", &mk_dinput(steep_approach));
1911 tie_input(node, "gpws-inhibit", &mk_dinput(gpws_inhibit));
1913 tie_input(node, "uncorrected-barometric-altitude", &mk_ainput(uncorrected_barometric_altitude), &mk_ainput_feed(uncorrected_barometric_altitude));
1914 tie_input(node, "barometric-altitude-rate", &mk_ainput(barometric_altitude_rate), &mk_ainput_feed(barometric_altitude_rate));
1915 tie_input(node, "gps-altitude", &mk_ainput(gps_altitude));
1916 tie_input(node, "gps-latitude", &mk_ainput(gps_latitude));
1917 tie_input(node, "gps-longitude", &mk_ainput(gps_longitude));
1918 tie_input(node, "gps-vertical-figure-of-merit", &mk_ainput(gps_vertical_figure_of_merit));
1919 tie_input(node, "radio-altitude", &mk_ainput(radio_altitude), &mk_ainput_feed(radio_altitude));
1920 tie_input(node, "glideslope-deviation", &mk_ainput(glideslope_deviation), &mk_ainput_feed(glideslope_deviation));
1921 tie_input(node, "roll-angle", &mk_ainput(roll_angle), &mk_ainput_feed(roll_angle));
1922 tie_input(node, "localizer-deviation", &mk_ainput(localizer_deviation), &mk_ainput_feed(localizer_deviation));
1923 tie_input(node, "computed-airspeed", &mk_ainput(computed_airspeed), &mk_ainput_feed(computed_airspeed));
1924 tie_input(node, "decision-height", &mk_ainput(decision_height), &mk_ainput_feed(decision_height));
1926 tie_output(node, "gpws-warning", &mk_doutput(gpws_warning));
1927 tie_output(node, "gpws-alert", &mk_doutput(gpws_alert));
1928 tie_output(node, "audio-on", &mk_doutput(audio_on));
1929 tie_output(node, "gpws-inop", &mk_doutput(gpws_inop));
1930 tie_output(node, "tad-inop", &mk_doutput(tad_inop));
1931 tie_output(node, "flap-override", &mk_doutput(flap_override));
1932 tie_output(node, "glideslope-cancel", &mk_doutput(glideslope_cancel));
1933 tie_output(node, "steep-approach", &mk_doutput(steep_approach));
1935 tie_output(node, "egpws-alert-discrete-1", &mk_aoutput(egpws_alert_discrete_1));
1936 tie_output(node, "egpwc-logic-discretes", &mk_aoutput(egpwc_logic_discretes));
1937 tie_output(node, "mode6-callouts-discrete-1", &mk_aoutput(mode6_callouts_discrete_1));
1938 tie_output(node, "mode6-callouts-discrete-2", &mk_aoutput(mode6_callouts_discrete_2));
1939 tie_output(node, "egpws-alert-discrete-2", &mk_aoutput(egpws_alert_discrete_2));
1940 tie_output(node, "egpwc-alert-discrete-3", &mk_aoutput(egpwc_alert_discrete_3));
1944 MK_VIII::IOHandler::get_discrete_input (bool *ptr) const
1950 MK_VIII::IOHandler::set_discrete_input (bool *ptr, bool value)
1955 if (mk->system_handler.state == SystemHandler::STATE_ON)
1957 if (ptr == &mk_dinput(momentary_flap_override))
1959 if (mk->configuration_module.state == ConfigurationModule::STATE_OK
1960 && mk->self_test_handler.state == SelfTestHandler::STATE_NONE
1961 && conf.momentary_flap_override_enabled
1963 mk_doutput(flap_override) = ! mk_doutput(flap_override);
1965 else if (ptr == &mk_dinput(self_test))
1966 mk->self_test_handler.handle_button_event(value);
1967 else if (ptr == &mk_dinput(glideslope_cancel))
1969 if (mk->configuration_module.state == ConfigurationModule::STATE_OK
1970 && mk->self_test_handler.state == SelfTestHandler::STATE_NONE
1973 if (! mk_doutput(glideslope_cancel))
1977 // Although we are not called from update(), we are
1978 // sure that the inputs we use here are defined,
1979 // since state is STATE_ON.
1981 if (! mk_data(glideslope_deviation_dots).ncd
1982 && ! mk_data(radio_altitude).ncd
1983 && mk_data(radio_altitude).get() <= 2000
1984 && mk_data(radio_altitude).get() >= 30)
1985 mk_doutput(glideslope_cancel) = true;
1987 // else do nothing ([PILOT] page 22: "Glideslope Cancel
1988 // can not be deselected (reset) by again pressing the
1989 // Glideslope Cancel switch.")
1992 else if (ptr == &mk_dinput(steep_approach))
1994 if (mk->configuration_module.state == ConfigurationModule::STATE_OK
1995 && mk->self_test_handler.state == SelfTestHandler::STATE_NONE
1996 && momentary_steep_approach_enabled()
1998 mk_doutput(steep_approach) = ! mk_doutput(steep_approach);
2004 if (mk->system_handler.state == SystemHandler::STATE_ON)
2005 update_alternate_discrete_input(ptr);
2009 MK_VIII::IOHandler::present_status_section (const char *name)
2011 printf("%s\n", name);
2015 MK_VIII::IOHandler::present_status_item (const char *name, const char *value)
2018 printf("\t%-32s %s\n", name, value);
2020 printf("\t%s\n", name);
2024 MK_VIII::IOHandler::present_status_subitem (const char *name)
2026 printf("\t\t%s\n", name);
2030 MK_VIII::IOHandler::present_status ()
2034 if (mk->system_handler.state != SystemHandler::STATE_ON)
2037 present_status_section("EGPWC CONFIGURATION");
2038 present_status_item("PART NUMBER:", "965-1220-000"); // [SPEC] 1.1
2039 present_status_item("MOD STATUS:", "N/A");
2040 present_status_item("SERIAL NUMBER:", "N/A");
2042 present_status_item("APPLICATION S/W VERSION:", FLIGHTGEAR_VERSION);
2043 present_status_item("TERRAIN DATABASE VERSION:", FLIGHTGEAR_VERSION);
2044 present_status_item("ENVELOPE MOD DATABASE VERSION:", "N/A");
2045 present_status_item("BOOT CODE VERSION:", FLIGHTGEAR_VERSION);
2048 present_status_section("CURRENT FAULTS");
2049 if (mk->configuration_module.state == ConfigurationModule::STATE_OK && ! mk->fault_handler.has_faults())
2050 present_status_item("NO FAULTS");
2053 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
2055 present_status_item("GPWS COMPUTER FAULTS:");
2056 switch (mk->configuration_module.state)
2058 case ConfigurationModule::STATE_INVALID_DATABASE:
2059 present_status_subitem("APPLICATION DATABASE FAILED");
2062 case ConfigurationModule::STATE_INVALID_AIRCRAFT_TYPE:
2063 present_status_subitem("CONFIGURATION TYPE INVALID");
2067 assert_not_reached();
2073 present_status_item("GPWS COMPUTER OK");
2074 present_status_item("GPWS EXTERNAL FAULTS:");
2076 static const char *fault_names[] = {
2077 "ALL MODES INHIBIT",
2080 "MOMENTARY FLAP OVERRIDE INVALID",
2081 "SELF TEST INVALID",
2082 "GLIDESLOPE CANCEL INVALID",
2083 "STEEP APPROACH INVALID",
2086 "MODES 1-4 INPUTS INVALID",
2087 "MODE 5 INPUTS INVALID",
2088 "MODE 6 INPUTS INVALID",
2089 "BANK ANGLE INPUTS INVALID",
2090 "TCF INPUTS INVALID"
2093 for (size_t i = 0; i < n_elements(fault_names); i++)
2094 if (mk->fault_handler.faults[i])
2095 present_status_subitem(fault_names[i]);
2100 present_status_section("CONFIGURATION:");
2102 static const char *category_names[] = {
2105 "POSITION INPUT TYPE",
2106 "CALLOUTS OPTION TYPE",
2108 "TERRAIN DISPLAY TYPE",
2110 "RADIO ALTITUDE TYPE",
2111 "NAVIGATION INPUT TYPE",
2113 "MAGNETIC HEADING TYPE",
2114 "WINDSHEAR INPUT TYPE",
2119 for (size_t i = 0; i < n_elements(category_names); i++)
2121 std::ostringstream value;
2122 value << "= " << mk->configuration_module.effective_categories[i];
2123 present_status_item(category_names[i], value.str().c_str());
2128 MK_VIII::IOHandler::get_present_status () const
2134 MK_VIII::IOHandler::set_present_status (bool value)
2136 if (value) present_status();
2140 ///////////////////////////////////////////////////////////////////////////////
2141 // VoicePlayer ////////////////////////////////////////////////////////////////
2142 ///////////////////////////////////////////////////////////////////////////////
2145 MK_VIII::VoicePlayer::Speaker::bind (SGPropertyNode *node)
2147 // uses xmlsound property names
2148 tie(node, "volume", &volume);
2149 tie(node, "pitch", &pitch);
2153 MK_VIII::VoicePlayer::Speaker::update_configuration ()
2155 map< string, SGSharedPtr<SGSoundSample> >::iterator iter;
2156 for (iter = player->samples.begin(); iter != player->samples.end(); iter++)
2158 SGSoundSample *sample = (*iter).second;
2160 sample->set_pitch(pitch);
2164 player->voice->volume_changed();
2167 MK_VIII::VoicePlayer::Voice::~Voice ()
2169 for (iter = elements.begin(); iter != elements.end(); iter++)
2170 delete *iter; // we owned the element
2175 MK_VIII::VoicePlayer::Voice::play ()
2177 iter = elements.begin();
2180 element->play(get_volume());
2184 MK_VIII::VoicePlayer::Voice::stop (bool now)
2188 if (now || element->silence)
2194 iter = elements.end() - 1; // stop after the current element finishes
2199 MK_VIII::VoicePlayer::Voice::set_volume (float _volume)
2206 MK_VIII::VoicePlayer::Voice::volume_changed ()
2209 element->set_volume(get_volume());
2213 MK_VIII::VoicePlayer::Voice::update ()
2217 if (! element->is_playing())
2219 if (++iter == elements.end())
2224 element->play(get_volume());
2230 MK_VIII::VoicePlayer::~VoicePlayer ()
2232 vector<Voice *>::iterator iter1;
2233 for (iter1 = _voices.begin(); iter1 != _voices.end(); iter1++)
2240 MK_VIII::VoicePlayer::init ()
2242 #define STDPAUSE 0.75 // [SPEC] 6.4.4: "the standard 0.75 second delay"
2244 SGSoundMgr *smgr = globals->get_soundmgr();
2245 _sgr = smgr->find("avionics", true);
2246 _sgr->tie_to_listener();
2248 make_voice(&voices.application_data_base_failed, "application-data-base-failed");
2249 make_voice(&voices.bank_angle, "bank-angle");
2250 make_voice(&voices.bank_angle_bank_angle, "bank-angle", "bank-angle");
2251 make_voice(&voices.bank_angle_bank_angle_3, "bank-angle", "bank-angle", 3.0);
2252 make_voice(&voices.bank_angle_inop, "bank-angle-inop");
2253 make_voice(&voices.bank_angle_pause_bank_angle, "bank-angle", STDPAUSE, "bank-angle");
2254 make_voice(&voices.bank_angle_pause_bank_angle_3, "bank-angle", STDPAUSE, "bank-angle", 3.0);
2255 make_voice(&voices.callouts_inop, "callouts-inop");
2256 make_voice(&voices.configuration_type_invalid, "configuration-type-invalid");
2257 make_voice(&voices.dont_sink, "dont-sink");
2258 make_voice(&voices.dont_sink_pause_dont_sink, "dont-sink", STDPAUSE, "dont-sink");
2259 make_voice(&voices.five_hundred_above, "500-above");
2260 make_voice(&voices.glideslope, "glideslope");
2261 make_voice(&voices.glideslope_inop, "glideslope-inop");
2262 make_voice(&voices.gpws_inop, "gpws-inop");
2263 make_voice(&voices.hard_glideslope, "glideslope", "glideslope", 3.0);
2264 make_voice(&voices.minimums, "minimums");
2265 make_voice(&voices.minimums_minimums, "minimums", "minimums");
2266 make_voice(&voices.pull_up, "pull-up");
2267 make_voice(&voices.sink_rate, "sink-rate");
2268 make_voice(&voices.sink_rate_pause_sink_rate, "sink-rate", STDPAUSE, "sink-rate");
2269 make_voice(&voices.soft_glideslope, new Voice::SampleElement(get_sample("glideslope"), modify_amplitude(1.0, -6)));
2270 make_voice(&voices.terrain, "terrain");
2271 make_voice(&voices.terrain_pause_terrain, "terrain", STDPAUSE, "terrain");
2272 make_voice(&voices.too_low_flaps, "too-low-flaps");
2273 make_voice(&voices.too_low_gear, "too-low-gear");
2274 make_voice(&voices.too_low_terrain, "too-low-terrain");
2276 for (unsigned i = 0; i < n_altitude_callouts; i++)
2278 std::ostringstream name;
2279 name << "altitude-" << mk->mode6_handler.altitude_callout_definitions[i];
2280 make_voice(&voices.altitude_callouts[i], name.str().c_str());
2283 speaker.update_configuration();
2287 MK_VIII::VoicePlayer::get_sample (const char *name)
2289 std::ostringstream refname;
2290 refname << mk->name << "[" << mk->num << "]" << "/" << name;
2292 SGSoundSample *sample = _sgr->find(refname.str());
2295 string filename = "Sounds/mk-viii/" + string(name) + ".wav";
2298 sample = new SGSoundSample(filename.c_str(), SGPath());
2300 catch (const sg_exception &e)
2302 SG_LOG(SG_INSTR, SG_ALERT, "Error loading MK VIII sound sample \"" + filename + "\": " + e.getFormattedMessage());
2306 _sgr->add(sample, refname.str());
2307 samples[refname.str()] = sample;
2314 MK_VIII::VoicePlayer::play (Voice *_voice, unsigned int flags)
2316 if (test_bits(flags, PLAY_NOW) || ! voice || voice->element->silence)
2322 looped = test_bits(flags, PLAY_LOOPED);
2325 next_looped = false;
2331 next_voice = _voice;
2332 next_looped = test_bits(flags, PLAY_LOOPED);
2337 MK_VIII::VoicePlayer::stop (unsigned int flags)
2341 voice->stop(test_bits(flags, STOP_NOW));
2351 MK_VIII::VoicePlayer::set_volume (float _volume)
2355 voice->volume_changed();
2359 MK_VIII::VoicePlayer::update ()
2367 if (! voice->element || voice->element->silence)
2370 looped = next_looped;
2373 next_looped = false;
2380 if (! voice->element)
2391 ///////////////////////////////////////////////////////////////////////////////
2392 // SelfTestHandler ////////////////////////////////////////////////////////////
2393 ///////////////////////////////////////////////////////////////////////////////
2396 MK_VIII::SelfTestHandler::_was_here (int position)
2398 if (position > current)
2407 MK_VIII::SelfTestHandler::Action
2408 MK_VIII::SelfTestHandler::sleep (double duration)
2410 Action _action = { ACTION_SLEEP, duration, NULL };
2414 MK_VIII::SelfTestHandler::Action
2415 MK_VIII::SelfTestHandler::play (VoicePlayer::Voice *voice)
2417 mk->voice_player.play(voice);
2418 Action _action = { ACTION_VOICE, 0, NULL };
2422 MK_VIII::SelfTestHandler::Action
2423 MK_VIII::SelfTestHandler::discrete_on (bool *discrete, double duration)
2426 return sleep(duration);
2429 MK_VIII::SelfTestHandler::Action
2430 MK_VIII::SelfTestHandler::discrete_on_off (bool *discrete, double duration)
2433 Action _action = { ACTION_SLEEP | ACTION_DISCRETE_ON_OFF, duration, discrete };
2437 MK_VIII::SelfTestHandler::Action
2438 MK_VIII::SelfTestHandler::discrete_on_off (bool *discrete, VoicePlayer::Voice *voice)
2441 mk->voice_player.play(voice);
2442 Action _action = { ACTION_VOICE | ACTION_DISCRETE_ON_OFF, 0, discrete };
2446 MK_VIII::SelfTestHandler::Action
2447 MK_VIII::SelfTestHandler::done ()
2449 Action _action = { ACTION_DONE, 0, NULL };
2453 MK_VIII::SelfTestHandler::Action
2454 MK_VIII::SelfTestHandler::run ()
2456 // Note that "Terrain INOP" and "Terrain NA" are or'ed to the same
2457 // output discrete (see [SPEC] 6.9.3.5).
2459 #define was_here() was_here_offset(0)
2460 #define was_here_offset(offset) _was_here(__LINE__ * 20 + (offset))
2464 if (mk->configuration_module.state == ConfigurationModule::STATE_INVALID_DATABASE)
2465 return play(mk_voice(application_data_base_failed));
2466 else if (mk->configuration_module.state == ConfigurationModule::STATE_INVALID_AIRCRAFT_TYPE)
2467 return play(mk_voice(configuration_type_invalid));
2469 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
2473 return discrete_on(&mk_doutput(gpws_inop), 0.7);
2475 return sleep(0.7); // W/S INOP
2477 return discrete_on(&mk_doutput(tad_inop), 0.7);
2480 mk_doutput(gpws_inop) = false;
2481 // do not disable tad_inop since we must enable Terrain NA
2482 // do not disable W/S INOP since we do not emulate it
2483 return sleep(0.7); // Terrain NA
2487 mk_doutput(tad_inop) = false; // disable Terrain NA
2488 if (mk->io_handler.conf.momentary_flap_override_enabled)
2489 return discrete_on_off(&mk_doutput(flap_override), 1.0);
2492 return discrete_on_off(&mk_doutput(audio_on), 1.0);
2495 if (mk->io_handler.momentary_steep_approach_enabled())
2496 return discrete_on_off(&mk_doutput(steep_approach), 1.0);
2501 if (mk_dinput(glideslope_inhibit))
2502 goto glideslope_end;
2505 if (mk->fault_handler.faults[FaultHandler::FAULT_MODE5_INPUTS_INVALID])
2506 goto glideslope_inop;
2510 return discrete_on_off(&mk_doutput(gpws_alert), mk_voice(glideslope));
2512 return discrete_on_off(&mk_doutput(glideslope_cancel), 0.7);
2513 goto glideslope_end;
2516 return play(mk_voice(glideslope_inop));
2521 if (mk->fault_handler.faults[FaultHandler::FAULT_MODES14_INPUTS_INVALID])
2525 return discrete_on_off(&mk_doutput(gpws_warning), mk_voice(pull_up));
2529 return play(mk_voice(gpws_inop));
2534 if (mk_dinput(self_test)) // proceed to long self test
2539 if (mk->mode6_handler.conf.bank_angle_enabled
2540 && mk->fault_handler.faults[FaultHandler::FAULT_BANK_ANGLE_INPUTS_INVALID])
2541 return play(mk_voice(bank_angle_inop));
2545 if (mk->mode6_handler.altitude_callouts_enabled()
2546 && mk->fault_handler.faults[FaultHandler::FAULT_MODE6_INPUTS_INVALID])
2547 return play(mk_voice(callouts_inop));
2555 mk_doutput(gpws_inop) = true;
2556 // do not enable W/S INOP, since we do not emulate it
2557 mk_doutput(tad_inop) = true; // Terrain INOP, Terrain NA
2559 return play(mk_voice(sink_rate));
2562 return play(mk_voice(pull_up));
2564 return play(mk_voice(terrain));
2566 return play(mk_voice(pull_up));
2568 return play(mk_voice(dont_sink));
2570 return play(mk_voice(too_low_terrain));
2572 return play(mk->mode4_handler.conf.voice_too_low_gear);
2574 return play(mk_voice(too_low_flaps));
2576 return play(mk_voice(too_low_terrain));
2578 return play(mk_voice(glideslope));
2581 if (mk->mode6_handler.conf.bank_angle_enabled)
2582 return play(mk_voice(bank_angle));
2587 if (! mk->mode6_handler.altitude_callouts_enabled())
2588 goto callouts_disabled;
2592 if (mk->mode6_handler.conf.minimums_enabled)
2593 return play(mk_voice(minimums));
2597 if (mk->mode6_handler.conf.above_field_voice)
2598 return play(mk->mode6_handler.conf.above_field_voice);
2600 for (unsigned i = 0; i < n_altitude_callouts; i++)
2601 if (! was_here_offset(i))
2603 if (mk->mode6_handler.conf.altitude_callouts_enabled[i])
2604 return play(mk_altitude_voice(i));
2608 if (mk->mode6_handler.conf.smart_500_enabled)
2609 return play(mk_altitude_voice(Mode6Handler::ALTITUDE_CALLOUT_500));
2614 return play(mk_voice(minimums_minimums));
2619 if (mk->tcf_handler.conf.enabled)
2620 return play(mk_voice(too_low_terrain));
2627 MK_VIII::SelfTestHandler::start ()
2629 assert(state == STATE_START);
2631 memcpy(&saved_outputs, &mk->io_handler.outputs, sizeof(mk->io_handler.outputs));
2632 memset(&mk->io_handler.outputs, 0, sizeof(mk->io_handler.outputs));
2634 // [SPEC] 6.10.6: "The self-test results are annunciated, at 6db
2635 // lower than the normal audio level selected for the aircraft"
2636 mk->voice_player.set_volume(modify_amplitude(mk->voice_player.conf.volume, -6));
2638 if (mk_dinput(mode6_low_volume))
2639 mk->mode6_handler.set_volume(1.0);
2641 state = STATE_RUNNING;
2642 cancel = CANCEL_NONE;
2643 memset(&action, 0, sizeof(action));
2648 MK_VIII::SelfTestHandler::stop ()
2650 if (state != STATE_NONE)
2652 if (state != STATE_START)
2654 mk->voice_player.stop(VoicePlayer::STOP_NOW);
2655 mk->voice_player.set_volume(mk_dinput(audio_inhibit) ? 0.0 : mk->voice_player.conf.volume);
2657 if (mk_dinput(mode6_low_volume))
2658 mk->mode6_handler.set_volume(modify_amplitude(1.0, -6));
2660 memcpy(&mk->io_handler.outputs, &saved_outputs, sizeof(mk->io_handler.outputs));
2663 button_pressed = false;
2665 // reset self-test handler position
2671 MK_VIII::SelfTestHandler::handle_button_event (bool value)
2673 if (state == STATE_NONE)
2676 state = STATE_START;
2678 else if (state == STATE_START)
2681 state = STATE_NONE; // cancel the not-yet-started test
2683 else if (cancel == CANCEL_NONE)
2687 assert(! button_pressed);
2688 button_pressed = true;
2689 button_press_timestamp = globals->get_sim_time_sec();
2695 if (globals->get_sim_time_sec() - button_press_timestamp < 2)
2696 cancel = CANCEL_SHORT;
2698 cancel = CANCEL_LONG;
2705 MK_VIII::SelfTestHandler::update ()
2707 if (state == STATE_NONE)
2709 else if (state == STATE_START)
2711 if (mk->state_handler.ground && ! mk->io_handler.steep_approach())
2721 if (button_pressed && cancel == CANCEL_NONE)
2723 if (globals->get_sim_time_sec() - button_press_timestamp >= 2)
2724 cancel = CANCEL_LONG;
2728 if (! mk->state_handler.ground || cancel != CANCEL_NONE)
2734 if (test_bits(action.flags, ACTION_SLEEP))
2736 if (globals->get_sim_time_sec() - sleep_start < action.sleep_duration)
2739 if (test_bits(action.flags, ACTION_VOICE))
2741 if (mk->voice_player.voice)
2744 if (test_bits(action.flags, ACTION_DISCRETE_ON_OFF))
2745 *action.discrete = false;
2749 if (test_bits(action.flags, ACTION_SLEEP))
2750 sleep_start = globals->get_sim_time_sec();
2751 if (test_bits(action.flags, ACTION_DONE))
2760 ///////////////////////////////////////////////////////////////////////////////
2761 // AlertHandler ///////////////////////////////////////////////////////////////
2762 ///////////////////////////////////////////////////////////////////////////////
2765 MK_VIII::AlertHandler::select_voice_alerts (unsigned int test)
2767 if (has_alerts(test))
2769 voice_alerts = alerts & test;
2774 voice_alerts &= ~test;
2775 if (voice_alerts == 0)
2776 mk->voice_player.stop();
2783 MK_VIII::AlertHandler::boot ()
2789 MK_VIII::AlertHandler::reposition ()
2793 mk->io_handler.set_lamp(IOHandler::LAMP_NONE);
2794 mk->voice_player.stop(VoicePlayer::STOP_NOW);
2798 MK_VIII::AlertHandler::reset ()
2803 repeated_alerts = 0;
2804 altitude_callout_voice = NULL;
2808 MK_VIII::AlertHandler::update ()
2810 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
2813 // Lamps and voices are prioritized according to [SPEC] 6.9.2.
2815 // Voices which can interrupt other voices (VoicePlayer::PLAY_NOW)
2816 // are specified by [INSTALL] appendix E 5.3.5.
2818 // When a voice is overriden by a higher priority voice and the
2819 // overriding voice stops, we restore the previous voice if it was
2820 // looped (this is not specified by [SPEC] but seems to make sense).
2824 if (has_alerts(ALERT_MODE1_PULL_UP | ALERT_MODE2A | ALERT_MODE2B))
2825 mk->io_handler.set_lamp(IOHandler::LAMP_WARNING);
2826 else if (has_alerts(ALERT_MODE1_SINK_RATE
2827 | ALERT_MODE2A_PREFACE
2828 | ALERT_MODE2B_PREFACE
2829 | ALERT_MODE2A_ALTITUDE_GAIN_TERRAIN_CLOSING
2830 | ALERT_MODE2A_ALTITUDE_GAIN
2831 | ALERT_MODE2B_LANDING_MODE
2833 | ALERT_MODE4_TOO_LOW_FLAPS
2834 | ALERT_MODE4_TOO_LOW_GEAR
2835 | ALERT_MODE4AB_TOO_LOW_TERRAIN
2836 | ALERT_MODE4C_TOO_LOW_TERRAIN
2837 | ALERT_TCF_TOO_LOW_TERRAIN))
2838 mk->io_handler.set_lamp(IOHandler::LAMP_CAUTION);
2839 else if (has_alerts(ALERT_MODE5_SOFT | ALERT_MODE5_HARD))
2840 mk->io_handler.set_lamp(IOHandler::LAMP_GLIDESLOPE);
2842 mk->io_handler.set_lamp(IOHandler::LAMP_NONE);
2846 if (select_voice_alerts(ALERT_MODE1_PULL_UP))
2848 if (! has_old_alerts(ALERT_MODE1_PULL_UP))
2850 if (mk->voice_player.voice != mk_voice(sink_rate_pause_sink_rate))
2851 mk->voice_player.play(mk_voice(sink_rate), VoicePlayer::PLAY_NOW);
2852 mk->voice_player.play(mk_voice(pull_up), VoicePlayer::PLAY_LOOPED);
2855 else if (select_voice_alerts(ALERT_MODE2A_PREFACE | ALERT_MODE2B_PREFACE))
2857 if (! has_old_alerts(ALERT_MODE2A_PREFACE | ALERT_MODE2B_PREFACE))
2858 mk->voice_player.play(mk_voice(terrain_pause_terrain), VoicePlayer::PLAY_NOW);
2860 else if (select_voice_alerts(ALERT_MODE2A | ALERT_MODE2B))
2862 if (mk->voice_player.voice != mk_voice(pull_up))
2863 mk->voice_player.play(mk_voice(pull_up), VoicePlayer::PLAY_NOW | VoicePlayer::PLAY_LOOPED);
2865 else if (select_voice_alerts(ALERT_MODE2A_ALTITUDE_GAIN_TERRAIN_CLOSING | ALERT_MODE2B_LANDING_MODE))
2867 if (mk->voice_player.voice != mk_voice(terrain))
2868 mk->voice_player.play(mk_voice(terrain), VoicePlayer::PLAY_LOOPED);
2870 else if (select_voice_alerts(ALERT_MODE6_MINIMUMS))
2872 if (! has_old_alerts(ALERT_MODE6_MINIMUMS))
2873 mk->voice_player.play(mk_voice(minimums_minimums));
2875 else if (select_voice_alerts(ALERT_MODE4AB_TOO_LOW_TERRAIN | ALERT_MODE4C_TOO_LOW_TERRAIN))
2877 if (must_play_voice(ALERT_MODE4AB_TOO_LOW_TERRAIN | ALERT_MODE4C_TOO_LOW_TERRAIN))
2878 mk->voice_player.play(mk_voice(too_low_terrain));
2880 else if (select_voice_alerts(ALERT_TCF_TOO_LOW_TERRAIN))
2882 if (must_play_voice(ALERT_TCF_TOO_LOW_TERRAIN))
2883 mk->voice_player.play(mk_voice(too_low_terrain));
2885 else if (select_voice_alerts(ALERT_MODE6_ALTITUDE_CALLOUT))
2887 if (! has_old_alerts(ALERT_MODE6_ALTITUDE_CALLOUT))
2889 assert(altitude_callout_voice != NULL);
2890 mk->voice_player.play(altitude_callout_voice);
2891 altitude_callout_voice = NULL;
2894 else if (select_voice_alerts(ALERT_MODE4_TOO_LOW_GEAR))
2896 if (must_play_voice(ALERT_MODE4_TOO_LOW_GEAR))
2897 mk->voice_player.play(mk->mode4_handler.conf.voice_too_low_gear);
2899 else if (select_voice_alerts(ALERT_MODE4_TOO_LOW_FLAPS))
2901 if (must_play_voice(ALERT_MODE4_TOO_LOW_FLAPS))
2902 mk->voice_player.play(mk_voice(too_low_flaps));
2904 else if (select_voice_alerts(ALERT_MODE1_SINK_RATE))
2906 if (must_play_voice(ALERT_MODE1_SINK_RATE))
2907 mk->voice_player.play(mk_voice(sink_rate_pause_sink_rate));
2908 // [SPEC] 6.2.1: "During the time that the voice message for the
2909 // outer envelope is inhibited and the caution/warning lamp is
2910 // on, the Mode 5 alert message is allowed to annunciate for
2911 // excessive glideslope deviation conditions."
2912 else if (mk->voice_player.voice != mk_voice(sink_rate_pause_sink_rate)
2913 && mk->voice_player.next_voice != mk_voice(sink_rate_pause_sink_rate))
2915 if (has_alerts(ALERT_MODE5_HARD))
2917 voice_alerts |= ALERT_MODE5_HARD;
2918 if (mk->voice_player.voice != mk_voice(hard_glideslope))
2919 mk->voice_player.play(mk_voice(hard_glideslope), VoicePlayer::PLAY_LOOPED);
2921 else if (has_alerts(ALERT_MODE5_SOFT))
2923 voice_alerts |= ALERT_MODE5_SOFT;
2924 if (must_play_voice(ALERT_MODE5_SOFT))
2925 mk->voice_player.play(mk_voice(soft_glideslope));
2929 else if (select_voice_alerts(ALERT_MODE3))
2931 if (must_play_voice(ALERT_MODE3))
2932 mk->voice_player.play(mk_voice(dont_sink_pause_dont_sink));
2934 else if (select_voice_alerts(ALERT_MODE5_HARD))
2936 if (mk->voice_player.voice != mk_voice(hard_glideslope))
2937 mk->voice_player.play(mk_voice(hard_glideslope), VoicePlayer::PLAY_LOOPED);
2939 else if (select_voice_alerts(ALERT_MODE5_SOFT))
2941 if (must_play_voice(ALERT_MODE5_SOFT))
2942 mk->voice_player.play(mk_voice(soft_glideslope));
2944 else if (select_voice_alerts(ALERT_MODE6_LOW_BANK_ANGLE_3))
2946 if (mk->voice_player.voice != mk_voice(bank_angle_bank_angle_3))
2947 mk->voice_player.play(mk_voice(bank_angle_bank_angle_3), VoicePlayer::PLAY_LOOPED);
2949 else if (select_voice_alerts(ALERT_MODE6_HIGH_BANK_ANGLE_3))
2951 if (mk->voice_player.voice != mk_voice(bank_angle_pause_bank_angle_3))
2952 mk->voice_player.play(mk_voice(bank_angle_pause_bank_angle_3), VoicePlayer::PLAY_LOOPED);
2954 else if (select_voice_alerts(ALERT_MODE6_LOW_BANK_ANGLE_2))
2956 if (! has_old_alerts(ALERT_MODE6_LOW_BANK_ANGLE_2 | ALERT_MODE6_HIGH_BANK_ANGLE_2))
2957 mk->voice_player.play(mk_voice(bank_angle_bank_angle));
2959 else if (select_voice_alerts(ALERT_MODE6_HIGH_BANK_ANGLE_2))
2961 if (! has_old_alerts(ALERT_MODE6_LOW_BANK_ANGLE_2 | ALERT_MODE6_HIGH_BANK_ANGLE_2))
2962 mk->voice_player.play(mk_voice(bank_angle_pause_bank_angle));
2964 else if (select_voice_alerts(ALERT_MODE6_LOW_BANK_ANGLE_1))
2966 if (! has_old_alerts(ALERT_MODE6_LOW_BANK_ANGLE_1 | ALERT_MODE6_HIGH_BANK_ANGLE_1))
2967 mk->voice_player.play(mk_voice(bank_angle_bank_angle));
2969 else if (select_voice_alerts(ALERT_MODE6_HIGH_BANK_ANGLE_1))
2971 if (! has_old_alerts(ALERT_MODE6_LOW_BANK_ANGLE_1 | ALERT_MODE6_HIGH_BANK_ANGLE_1))
2972 mk->voice_player.play(mk_voice(bank_angle_pause_bank_angle));
2977 old_alerts = voice_alerts;
2978 repeated_alerts = 0;
2982 MK_VIII::AlertHandler::set_alerts (unsigned int _alerts,
2984 VoicePlayer::Voice *_altitude_callout_voice)
2987 if (test_bits(flags, ALERT_FLAG_REPEAT))
2988 repeated_alerts |= _alerts;
2989 if (_altitude_callout_voice)
2990 altitude_callout_voice = _altitude_callout_voice;
2994 MK_VIII::AlertHandler::unset_alerts (unsigned int _alerts)
2997 repeated_alerts &= ~_alerts;
2998 if (_alerts & ALERT_MODE6_ALTITUDE_CALLOUT)
2999 altitude_callout_voice = NULL;
3002 ///////////////////////////////////////////////////////////////////////////////
3003 // StateHandler ///////////////////////////////////////////////////////////////
3004 ///////////////////////////////////////////////////////////////////////////////
3007 MK_VIII::StateHandler::update_ground ()
3011 if (! mk_ainput(computed_airspeed).ncd && mk_ainput(computed_airspeed).get() > 60
3012 && ! mk_data(radio_altitude).ncd && mk_data(radio_altitude).get() > 30)
3014 if (potentially_airborne_timer.start_or_elapsed() > 1)
3018 potentially_airborne_timer.stop();
3022 if (! mk_ainput(computed_airspeed).ncd && mk_ainput(computed_airspeed).get() < 40
3023 && ! mk_data(radio_altitude).ncd && mk_data(radio_altitude).get() < 30)
3029 MK_VIII::StateHandler::enter_ground ()
3032 mk->io_handler.enter_ground();
3034 // [SPEC] 6.0.1 does not specify this, but it seems to be an
3035 // omission; at this point, we must make sure that we are in takeoff
3036 // mode (otherwise the next takeoff might happen in approach mode).
3042 MK_VIII::StateHandler::leave_ground ()
3045 mk->mode2_handler.leave_ground();
3049 MK_VIII::StateHandler::update_takeoff ()
3053 // [SPEC] 6.0.2 is somewhat confusing: it mentions hardcoded
3054 // terrain clearance (500, 750) and airspeed (178, 200) values,
3055 // but it also mentions the mode 4A boundary, which varies as a
3056 // function of aircraft type. We consider that the mentioned
3057 // values are examples, and that we should use the mode 4A upper
3060 if (! mk_data(terrain_clearance).ncd
3061 && ! mk_ainput(computed_airspeed).ncd
3062 && mk_data(terrain_clearance).get() > mk->mode4_handler.get_upper_agl(mk->mode4_handler.conf.modes->ac))
3067 if (! mk_data(radio_altitude).ncd
3068 && mk_data(radio_altitude).get() < mk->mode4_handler.conf.modes->b->min_agl1
3069 && mk->io_handler.flaps_down()
3070 && mk_dinput(landing_gear))
3076 MK_VIII::StateHandler::enter_takeoff ()
3079 mk->io_handler.enter_takeoff();
3080 mk->mode2_handler.enter_takeoff();
3081 mk->mode3_handler.enter_takeoff();
3082 mk->mode6_handler.enter_takeoff();
3086 MK_VIII::StateHandler::leave_takeoff ()
3089 mk->mode6_handler.leave_takeoff();
3093 MK_VIII::StateHandler::post_reposition ()
3095 // Synchronize the state with the current situation.
3098 (! mk_ainput(computed_airspeed).ncd && mk_ainput(computed_airspeed).get() > 60
3099 && ! mk_data(radio_altitude).ncd && mk_data(radio_altitude).get() > 30);
3101 bool _takeoff = _ground;
3103 if (ground && ! _ground)
3105 else if (! ground && _ground)
3108 if (takeoff && ! _takeoff)
3110 else if (! takeoff && _takeoff)
3115 MK_VIII::StateHandler::update ()
3117 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
3124 ///////////////////////////////////////////////////////////////////////////////
3125 // Mode1Handler ///////////////////////////////////////////////////////////////
3126 ///////////////////////////////////////////////////////////////////////////////
3129 MK_VIII::Mode1Handler::get_pull_up_bias ()
3131 // [PILOT] page 54: "Steep Approach has priority over Flap Override
3132 // if selected simultaneously."
3134 if (mk->io_handler.steep_approach())
3136 else if (conf.envelopes->flap_override_bias && mk->io_handler.flap_override())
3143 MK_VIII::Mode1Handler::is_pull_up ()
3145 if (! mk->io_handler.gpws_inhibit()
3146 && ! mk->state_handler.ground // [1]
3147 && ! mk_data(radio_altitude).ncd
3148 && ! mk_data(barometric_altitude_rate).ncd
3149 && mk_data(radio_altitude).get() > conf.envelopes->min_agl)
3151 if (mk_data(radio_altitude).get() < conf.envelopes->pull_up_max_agl1)
3153 if (mk_data(radio_altitude).get() < conf.envelopes->pull_up_min_agl1(mk_data(barometric_altitude_rate).get() + get_pull_up_bias()))
3156 else if (mk_data(radio_altitude).get() < conf.envelopes->pull_up_max_agl2)
3158 if (mk_data(radio_altitude).get() < conf.envelopes->pull_up_min_agl2(mk_data(barometric_altitude_rate).get() + get_pull_up_bias()))
3167 MK_VIII::Mode1Handler::update_pull_up ()
3171 if (! mk_test_alert(MODE1_PULL_UP))
3173 // [SPEC] 6.2.1: at least one sink rate must be issued
3174 // before switching to pull up; if no sink rate has
3175 // occurred, a 0.2 second delay is used.
3176 if (mk->voice_player.voice == mk_voice(sink_rate_pause_sink_rate)
3177 || pull_up_timer.start_or_elapsed() >= 0.2)
3178 mk_set_alerts(mk_alert(MODE1_PULL_UP));
3183 pull_up_timer.stop();
3184 mk_unset_alerts(mk_alert(MODE1_PULL_UP));
3189 MK_VIII::Mode1Handler::get_sink_rate_bias ()
3191 // [PILOT] page 54: "Steep Approach has priority over Flap Override
3192 // if selected simultaneously."
3194 if (mk->io_handler.steep_approach())
3196 else if (conf.envelopes->flap_override_bias && mk->io_handler.flap_override())
3198 else if (! mk_data(glideslope_deviation_dots).ncd)
3202 if (mk_data(glideslope_deviation_dots).get() <= -2)
3204 else if (mk_data(glideslope_deviation_dots).get() < 0)
3205 bias = -150 * mk_data(glideslope_deviation_dots).get();
3207 if (mk_data(radio_altitude).get() < 100)
3208 bias *= 0.01 * mk_data(radio_altitude).get();
3217 MK_VIII::Mode1Handler::is_sink_rate ()
3219 return ! mk->io_handler.gpws_inhibit()
3220 && ! mk->state_handler.ground // [1]
3221 && ! mk_data(radio_altitude).ncd
3222 && ! mk_data(barometric_altitude_rate).ncd
3223 && mk_data(radio_altitude).get() > conf.envelopes->min_agl
3224 && mk_data(radio_altitude).get() < 2450
3225 && mk_data(radio_altitude).get() < -572 - 0.6035 * (mk_data(barometric_altitude_rate).get() + get_sink_rate_bias());
3229 MK_VIII::Mode1Handler::get_sink_rate_tti ()
3231 return mk_data(radio_altitude).get() / fabs(mk_data(barometric_altitude_rate).get());
3235 MK_VIII::Mode1Handler::update_sink_rate ()
3239 if (mk_test_alert(MODE1_SINK_RATE))
3241 double tti = get_sink_rate_tti();
3242 if (tti <= sink_rate_tti * 0.8)
3244 // ~20% degradation, warn again and store the new reference tti
3245 sink_rate_tti = tti;
3246 mk_repeat_alert(mk_alert(MODE1_SINK_RATE));
3251 if (sink_rate_timer.start_or_elapsed() >= 0.8)
3253 mk_set_alerts(mk_alert(MODE1_SINK_RATE));
3254 sink_rate_tti = get_sink_rate_tti();
3260 sink_rate_timer.stop();
3261 mk_unset_alerts(mk_alert(MODE1_SINK_RATE));
3266 MK_VIII::Mode1Handler::update ()
3268 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
3275 ///////////////////////////////////////////////////////////////////////////////
3276 // Mode2Handler ///////////////////////////////////////////////////////////////
3277 ///////////////////////////////////////////////////////////////////////////////
3280 MK_VIII::Mode2Handler::ClosureRateFilter::limit_radio_altitude_rate (double r)
3282 // Limit radio altitude rate according to aircraft configuration,
3283 // allowing maximum sensitivity during cruise while providing
3284 // progressively less sensitivity during the landing phases of
3287 if (! mk_data(glideslope_deviation_dots).ncd && fabs(mk_data(glideslope_deviation_dots).get()) <= 2)
3288 { // gs deviation <= +- 2 dots
3289 if (mk_dinput(landing_gear) && mk->io_handler.flaps_down())
3290 SG_CLAMP_RANGE(r, -1000.0, 3000.0);
3291 else if (mk_dinput(landing_gear) || mk->io_handler.flaps_down())
3292 SG_CLAMP_RANGE(r, 0.0, 4000.0);
3294 SG_CLAMP_RANGE(r, 1000.0, 5000.0);
3297 { // no ILS, or gs deviation > +- 2 dots
3298 if (mk_dinput(landing_gear) && mk->io_handler.flaps_down())
3299 SG_CLAMP_RANGE(r, 0.0, 4000.0);
3300 else if (mk_dinput(landing_gear) || mk->io_handler.flaps_down())
3301 SG_CLAMP_RANGE(r, 1000.0, 5000.0);
3309 MK_VIII::Mode2Handler::ClosureRateFilter::init ()
3312 last_ra.set(&mk_data(radio_altitude));
3313 last_ba.set(&mk_ainput(uncorrected_barometric_altitude));
3320 MK_VIII::Mode2Handler::ClosureRateFilter::update ()
3322 double elapsed = timer.start_or_elapsed();
3326 if (! mk_data(radio_altitude).ncd && ! mk_ainput(uncorrected_barometric_altitude).ncd)
3328 if (! last_ra.ncd && ! last_ba.ncd)
3330 // compute radio and barometric altitude rates (positive value = descent)
3331 double ra_rate = -(mk_data(radio_altitude).get() - last_ra.get()) / elapsed * 60;
3332 double ba_rate = -(mk_ainput(uncorrected_barometric_altitude).get() - last_ba.get()) / elapsed * 60;
3334 // limit radio altitude rate according to aircraft configuration
3335 ra_rate = limit_radio_altitude_rate(ra_rate);
3337 // apply a low-pass filter to the radio altitude rate
3338 ra_rate = ra_filter.filter(ra_rate);
3339 // apply a high-pass filter to the barometric altitude rate
3340 ba_rate = ba_filter.filter(ba_rate);
3342 // combine both rates to obtain a closure rate
3343 output.set(ra_rate + ba_rate);
3356 last_ra.set(&mk_data(radio_altitude));
3357 last_ba.set(&mk_ainput(uncorrected_barometric_altitude));
3361 MK_VIII::Mode2Handler::b_conditions ()
3363 return mk->io_handler.flaps_down()
3364 || (! mk_data(glideslope_deviation_dots).ncd && fabs(mk_data(glideslope_deviation_dots).get()) < 2)
3365 || takeoff_timer.running;
3369 MK_VIII::Mode2Handler::is_a ()
3371 if (! mk->io_handler.gpws_inhibit()
3372 && ! mk->state_handler.ground // [1]
3373 && ! mk_data(radio_altitude).ncd
3374 && ! mk_ainput(computed_airspeed).ncd
3375 && ! closure_rate_filter.output.ncd
3376 && ! b_conditions())
3378 if (mk_data(radio_altitude).get() < 1220)
3380 if (mk_data(radio_altitude).get() < -1579 + 0.7895 * closure_rate_filter.output.get())
3387 if (mk_ainput(computed_airspeed).get() <= conf->airspeed1)
3389 else if (mk_ainput(computed_airspeed).get() >= conf->airspeed2)
3392 upper_limit = 1650 + 8.9 * (mk_ainput(computed_airspeed).get() - conf->airspeed1);
3394 if (mk_data(radio_altitude).get() < upper_limit)
3396 if (mk_data(radio_altitude).get() < 522 + 0.1968 * closure_rate_filter.output.get())
3406 MK_VIII::Mode2Handler::is_b ()
3408 if (! mk->io_handler.gpws_inhibit()
3409 && ! mk->state_handler.ground // [1]
3410 && ! mk_data(radio_altitude).ncd
3411 && ! mk_data(barometric_altitude_rate).ncd
3412 && ! closure_rate_filter.output.ncd
3414 && mk_data(radio_altitude).get() < 789)
3418 if (mk->io_handler.flaps_down())
3420 if (mk_data(barometric_altitude_rate).get() > -400)
3422 else if (mk_data(barometric_altitude_rate).get() < -1000)
3425 lower_limit = -66.777 - 0.667 * mk_data(barometric_altitude_rate).get();
3430 if (mk_data(radio_altitude).get() > lower_limit)
3432 if (mk_data(radio_altitude).get() < -1579 + 0.7895 * closure_rate_filter.output.get())
3441 MK_VIII::Mode2Handler::check_pull_up (unsigned int preface_alert,
3444 if (pull_up_timer.running)
3446 if (pull_up_timer.elapsed() >= 1)
3448 mk_unset_alerts(preface_alert);
3449 mk_set_alerts(alert);
3454 if (! mk->voice_player.voice)
3455 pull_up_timer.start();
3460 MK_VIII::Mode2Handler::update_a ()
3464 if (mk_test_alert(MODE2A_PREFACE))
3465 check_pull_up(mk_alert(MODE2A_PREFACE), mk_alert(MODE2A));
3466 else if (! mk_test_alert(MODE2A))
3468 mk_unset_alerts(mk_alert(MODE2A_ALTITUDE_GAIN) | mk_alert(MODE2A_ALTITUDE_GAIN_TERRAIN_CLOSING));
3469 mk_set_alerts(mk_alert(MODE2A_PREFACE));
3470 a_start_time = globals->get_sim_time_sec();
3471 pull_up_timer.stop();
3476 if (mk_test_alert(MODE2A_ALTITUDE_GAIN))
3478 if (mk->io_handler.gpws_inhibit()
3479 || mk->state_handler.ground // [1]
3480 || a_altitude_gain_timer.elapsed() >= 45
3481 || mk_data(radio_altitude).ncd
3482 || mk_ainput(uncorrected_barometric_altitude).ncd
3483 || mk_ainput(uncorrected_barometric_altitude).get() - a_altitude_gain_alt >= 300
3484 // [PILOT] page 12: "the visual alert will remain on
3485 // until [...] landing flaps or the flap override switch
3487 || mk->io_handler.flaps_down())
3489 // exit altitude gain mode
3490 a_altitude_gain_timer.stop();
3491 mk_unset_alerts(mk_alert(MODE2A_ALTITUDE_GAIN) | mk_alert(MODE2A_ALTITUDE_GAIN_TERRAIN_CLOSING));
3495 // [SPEC] 6.2.2.2: "If the terrain starts to fall away
3496 // during this altitude gain time, the voice will shut
3498 if (closure_rate_filter.output.get() < 0)
3499 mk_unset_alerts(mk_alert(MODE2A_ALTITUDE_GAIN_TERRAIN_CLOSING));
3502 else if (mk_test_alerts(mk_alert(MODE2A_PREFACE) | mk_alert(MODE2A)))
3504 mk_unset_alerts(mk_alert(MODE2A_PREFACE) | mk_alert(MODE2A));
3506 if (! mk->io_handler.gpws_inhibit()
3507 && ! mk->state_handler.ground // [1]
3508 && globals->get_sim_time_sec() - a_start_time > 3
3509 && ! mk->io_handler.flaps_down()
3510 && ! mk_data(radio_altitude).ncd
3511 && ! mk_ainput(uncorrected_barometric_altitude).ncd)
3513 // [SPEC] 6.2.2.2: mode 2A envelope violated for more
3514 // than 3 seconds, enable altitude gain feature
3516 a_altitude_gain_timer.start();
3517 a_altitude_gain_alt = mk_ainput(uncorrected_barometric_altitude).get();
3519 unsigned int alerts = mk_alert(MODE2A_ALTITUDE_GAIN);
3520 if (closure_rate_filter.output.get() > 0)
3521 alerts |= mk_alert(MODE2A_ALTITUDE_GAIN_TERRAIN_CLOSING);
3523 mk_set_alerts(alerts);
3530 MK_VIII::Mode2Handler::update_b ()
3534 // handle normal mode
3536 if (b && (! mk_dinput(landing_gear) || ! mk->io_handler.flaps_down()))
3538 if (mk_test_alert(MODE2B_PREFACE))
3539 check_pull_up(mk_alert(MODE2B_PREFACE), mk_alert(MODE2B));
3540 else if (! mk_test_alert(MODE2B))
3542 mk_set_alerts(mk_alert(MODE2B_PREFACE));
3543 pull_up_timer.stop();
3547 mk_unset_alerts(mk_alert(MODE2B_PREFACE) | mk_alert(MODE2B));
3549 // handle landing mode ([SPEC] 6.2.2.3: "If both landing gear and
3550 // flaps are in the landing configuration, then the message will be
3553 if (b && mk_dinput(landing_gear) && mk->io_handler.flaps_down())
3554 mk_set_alerts(mk_alert(MODE2B_LANDING_MODE));
3556 mk_unset_alerts(mk_alert(MODE2B_LANDING_MODE));
3560 MK_VIII::Mode2Handler::boot ()
3562 closure_rate_filter.init();
3566 MK_VIII::Mode2Handler::power_off ()
3568 // [SPEC] 6.0.4: "This latching function is not power saved and a
3569 // system reset will force it false."
3570 takeoff_timer.stop();
3574 MK_VIII::Mode2Handler::leave_ground ()
3576 // takeoff, reset timer
3577 takeoff_timer.start();
3581 MK_VIII::Mode2Handler::enter_takeoff ()
3583 // reset timer, in case it's a go-around
3584 takeoff_timer.start();
3588 MK_VIII::Mode2Handler::update ()
3590 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
3593 closure_rate_filter.update();
3595 if (takeoff_timer.running && takeoff_timer.elapsed() >= 60)
3596 takeoff_timer.stop();
3602 ///////////////////////////////////////////////////////////////////////////////
3603 // Mode3Handler ///////////////////////////////////////////////////////////////
3604 ///////////////////////////////////////////////////////////////////////////////
3607 MK_VIII::Mode3Handler::max_alt_loss (double _bias)
3609 return conf->max_alt_loss(mk->io_handler.flap_override(), mk_data(radio_altitude).get()) + mk_data(radio_altitude).get() * _bias;
3613 MK_VIII::Mode3Handler::get_bias (double initial_bias, double alt_loss)
3615 // do not repeat altitude-loss alerts below 30ft agl
3616 if (mk_data(radio_altitude).get() > 30)
3618 if (initial_bias < 0.0) // sanity check
3620 // mk-viii spec: repeat alerts whenever losing 20% of initial altitude
3621 while ((alt_loss > max_alt_loss(initial_bias))&&
3622 (initial_bias < 1.0))
3623 initial_bias += 0.2;
3626 return initial_bias;
3630 MK_VIII::Mode3Handler::is (double *alt_loss)
3632 if (has_descent_alt)
3634 int max_agl = conf->max_agl(mk->io_handler.flap_override());
3636 if (mk_ainput(uncorrected_barometric_altitude).ncd
3637 || mk_ainput(uncorrected_barometric_altitude).get() > descent_alt
3638 || mk_data(radio_altitude).ncd
3639 || mk_data(radio_altitude).get() > max_agl)
3642 has_descent_alt = false;
3646 // gear/flaps: [SPEC] 1.3.1.3
3647 if (! mk->io_handler.gpws_inhibit()
3648 && ! mk->state_handler.ground // [1]
3649 && (! mk_dinput(landing_gear) || ! mk->io_handler.flaps_down())
3650 && ! mk_data(barometric_altitude_rate).ncd
3651 && ! mk_ainput(uncorrected_barometric_altitude).ncd
3652 && ! mk_data(radio_altitude).ncd
3653 && mk_data(barometric_altitude_rate).get() < 0)
3655 double _alt_loss = descent_alt - mk_ainput(uncorrected_barometric_altitude).get();
3656 if (armed || (mk_data(radio_altitude).get() > conf->min_agl
3657 && mk_data(radio_altitude).get() < max_agl
3658 && _alt_loss > max_alt_loss(0)))
3660 *alt_loss = _alt_loss;
3668 if (! mk_data(barometric_altitude_rate).ncd
3669 && ! mk_ainput(uncorrected_barometric_altitude).ncd
3670 && mk_data(barometric_altitude_rate).get() < 0)
3672 has_descent_alt = true;
3673 descent_alt = mk_ainput(uncorrected_barometric_altitude).get();
3681 MK_VIII::Mode3Handler::enter_takeoff ()
3684 has_descent_alt = false;
3688 MK_VIII::Mode3Handler::update ()
3690 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
3693 if (mk->state_handler.takeoff)
3697 if (! mk->state_handler.ground /* [1] */ && is(&alt_loss))
3699 if (mk_test_alert(MODE3))
3701 double new_bias = get_bias(bias, alt_loss);
3702 if (new_bias > bias)
3705 mk_repeat_alert(mk_alert(MODE3));
3711 bias = get_bias(0.2, alt_loss);
3712 mk_set_alerts(mk_alert(MODE3));
3719 mk_unset_alerts(mk_alert(MODE3));
3722 ///////////////////////////////////////////////////////////////////////////////
3723 // Mode4Handler ///////////////////////////////////////////////////////////////
3724 ///////////////////////////////////////////////////////////////////////////////
3726 // FIXME: for turbofans, the upper limit should be reduced from 1000
3727 // to 800 ft if a sudden change in radio altitude is detected, in
3728 // order to reduce nuisance alerts caused by overflying another
3729 // aircraft (see [PILOT] page 16).
3732 MK_VIII::Mode4Handler::get_upper_agl (const EnvelopesConfiguration *c)
3734 if (mk_ainput(computed_airspeed).get() >= c->airspeed2)
3736 else if (mk_ainput(computed_airspeed).get() >= c->airspeed1)
3737 return c->min_agl2(mk_ainput(computed_airspeed).get());
3742 const MK_VIII::Mode4Handler::EnvelopesConfiguration *
3743 MK_VIII::Mode4Handler::get_ab_envelope ()
3745 // [SPEC] 6.2.4.1: "The Mode 4B envelope can also be selected by
3746 // setting flaps to landing configuration or by selecting flap
3748 return mk_dinput(landing_gear) || mk->io_handler.flaps_down()
3754 MK_VIII::Mode4Handler::get_bias (double initial_bias, double min_agl)
3756 // do not repeat terrain/gear/flap alerts below 30ft agl
3757 if (mk_data(radio_altitude).get() > 30.0)
3759 if (initial_bias < 0.0) // sanity check
3761 while ((mk_data(radio_altitude).get() < min_agl - min_agl * initial_bias)&&
3762 (initial_bias < 1.0))
3763 initial_bias += 0.2;
3766 return initial_bias;
3770 MK_VIII::Mode4Handler::handle_alert (unsigned int alert,
3774 if (mk_test_alerts(alert))
3776 double new_bias = get_bias(*bias, min_agl);
3777 if (new_bias > *bias)
3780 mk_repeat_alert(alert);
3785 *bias = get_bias(0.2, min_agl);
3786 mk_set_alerts(alert);
3791 MK_VIII::Mode4Handler::update_ab ()
3793 if (! mk->io_handler.gpws_inhibit()
3794 && ! mk->state_handler.ground
3795 && ! mk->state_handler.takeoff
3796 && ! mk_data(radio_altitude).ncd
3797 && ! mk_ainput(computed_airspeed).ncd
3798 && mk_data(radio_altitude).get() > 30)
3800 const EnvelopesConfiguration *c = get_ab_envelope();
3801 if (mk_ainput(computed_airspeed).get() < c->airspeed1)
3803 if (mk_dinput(landing_gear))
3805 if (! mk->io_handler.flaps_down() && mk_data(radio_altitude).get() < c->min_agl1)
3807 handle_alert(mk_alert(MODE4_TOO_LOW_FLAPS), c->min_agl1, &ab_bias);
3813 if (mk_data(radio_altitude).get() < c->min_agl1)
3815 handle_alert(mk_alert(MODE4_TOO_LOW_GEAR), c->min_agl1, &ab_bias);
3822 mk_unset_alerts(mk_alert(MODE4_TOO_LOW_FLAPS) | mk_alert(MODE4_TOO_LOW_GEAR));
3827 MK_VIII::Mode4Handler::update_ab_expanded ()
3829 if (! mk->io_handler.gpws_inhibit()
3830 && ! mk->state_handler.ground
3831 && ! mk->state_handler.takeoff
3832 && ! mk_data(radio_altitude).ncd
3833 && ! mk_ainput(computed_airspeed).ncd
3834 && mk_data(radio_altitude).get() > 30)
3836 const EnvelopesConfiguration *c = get_ab_envelope();
3837 if (mk_ainput(computed_airspeed).get() >= c->airspeed1)
3839 double min_agl = get_upper_agl(c);
3840 if (mk_data(radio_altitude).get() < min_agl)
3842 handle_alert(mk_alert(MODE4AB_TOO_LOW_TERRAIN), min_agl, &ab_expanded_bias);
3848 mk_unset_alerts(mk_alert(MODE4AB_TOO_LOW_TERRAIN));
3849 ab_expanded_bias=0.0;
3853 MK_VIII::Mode4Handler::update_c ()
3855 if (! mk->io_handler.gpws_inhibit()
3856 && ! mk->state_handler.ground // [1]
3857 && mk->state_handler.takeoff
3858 && ! mk_data(radio_altitude).ncd
3859 && ! mk_data(terrain_clearance).ncd
3860 && mk_data(radio_altitude).get() > 30
3861 && (! mk_dinput(landing_gear) || ! mk->io_handler.flaps_down())
3862 && mk_data(radio_altitude).get() < get_upper_agl(conf.modes->ac)
3863 && mk_data(radio_altitude).get() < mk_data(terrain_clearance).get())
3864 handle_alert(mk_alert(MODE4C_TOO_LOW_TERRAIN), mk_data(terrain_clearance).get(), &c_bias);
3867 mk_unset_alerts(mk_alert(MODE4C_TOO_LOW_TERRAIN));
3873 MK_VIII::Mode4Handler::update ()
3875 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
3879 update_ab_expanded();
3883 ///////////////////////////////////////////////////////////////////////////////
3884 // Mode5Handler ///////////////////////////////////////////////////////////////
3885 ///////////////////////////////////////////////////////////////////////////////
3888 MK_VIII::Mode5Handler::is_hard ()
3890 if (mk_data(radio_altitude).get() > 30
3891 && mk_data(radio_altitude).get() < 300
3892 && mk_data(glideslope_deviation_dots).get() > 2)
3894 if (mk_data(radio_altitude).get() < 150)
3896 if (mk_data(radio_altitude).get() > 293 - 71.43 * mk_data(glideslope_deviation_dots).get())
3907 MK_VIII::Mode5Handler::is_soft (double bias)
3909 // do not repeat glide-slope alerts below 30ft agl
3910 if (mk_data(radio_altitude).get() > 30)
3912 double bias_dots = 1.3 * bias;
3913 if (mk_data(glideslope_deviation_dots).get() > 1.3 + bias_dots)
3915 if (mk_data(radio_altitude).get() < 150)
3917 if (mk_data(radio_altitude).get() > 243 - 71.43 * (mk_data(glideslope_deviation_dots).get() - bias_dots))
3924 if (mk_data(barometric_altitude_rate).ncd)
3928 if (mk_data(barometric_altitude_rate).get() >= 0)
3930 else if (mk_data(barometric_altitude_rate).get() < -500)
3933 upper_limit = -mk_data(barometric_altitude_rate).get() + 500;
3936 if (mk_data(radio_altitude).get() < upper_limit)
3946 MK_VIII::Mode5Handler::get_soft_bias (double initial_bias)
3948 if (initial_bias < 0.0) // sanity check
3950 while ((is_soft(initial_bias))&&
3951 (initial_bias < 1.0))
3952 initial_bias += 0.2;
3954 return initial_bias;
3958 MK_VIII::Mode5Handler::update_hard (bool is)
3962 if (! mk_test_alert(MODE5_HARD))
3964 if (hard_timer.start_or_elapsed() >= 0.8)
3965 mk_set_alerts(mk_alert(MODE5_HARD));
3971 mk_unset_alerts(mk_alert(MODE5_HARD));
3976 MK_VIII::Mode5Handler::update_soft (bool is)
3980 if (! mk_test_alert(MODE5_SOFT))
3982 double new_bias = get_soft_bias(soft_bias);
3983 if (new_bias > soft_bias)
3985 soft_bias = new_bias;
3986 mk_repeat_alert(mk_alert(MODE5_SOFT));
3991 if (soft_timer.start_or_elapsed() >= 0.8)
3993 soft_bias = get_soft_bias(0.2);
3994 mk_set_alerts(mk_alert(MODE5_SOFT));
4001 mk_unset_alerts(mk_alert(MODE5_SOFT));
4006 MK_VIII::Mode5Handler::update ()
4008 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
4011 if (! mk->io_handler.gpws_inhibit()
4012 && ! mk->state_handler.ground // [1]
4013 && ! mk_dinput(glideslope_inhibit) // not on backcourse
4014 && ! mk_data(radio_altitude).ncd
4015 && ! mk_data(glideslope_deviation_dots).ncd
4016 && (! mk->io_handler.conf.localizer_enabled
4017 || mk_data(localizer_deviation_dots).ncd
4018 || mk_data(radio_altitude).get() < 500
4019 || fabs(mk_data(localizer_deviation_dots).get()) < 2)
4020 && (! mk->state_handler.takeoff || mk->io_handler.flaps_down())
4021 && mk_dinput(landing_gear)
4022 && ! mk_doutput(glideslope_cancel))
4024 update_hard(is_hard());
4025 update_soft(is_soft(0));
4034 ///////////////////////////////////////////////////////////////////////////////
4035 // Mode6Handler ///////////////////////////////////////////////////////////////
4036 ///////////////////////////////////////////////////////////////////////////////
4038 // keep sorted in descending order
4039 const int MK_VIII::Mode6Handler::altitude_callout_definitions[] =
4040 { 1000, 500, 400, 300, 200, 100, 50, 40, 30, 20, 10 };
4043 MK_VIII::Mode6Handler::reset_minimums ()
4045 minimums_issued = false;
4049 MK_VIII::Mode6Handler::reset_altitude_callouts ()
4051 for (unsigned i = 0; i < n_altitude_callouts; i++)
4052 altitude_callouts_issued[i] = false;
4056 MK_VIII::Mode6Handler::is_playing_altitude_callout ()
4058 for (unsigned i = 0; i < n_altitude_callouts; i++)
4059 if (mk->voice_player.voice == mk_altitude_voice(i)
4060 || mk->voice_player.next_voice == mk_altitude_voice(i))
4067 MK_VIII::Mode6Handler::is_near_minimums (double callout)
4071 if (! mk_data(decision_height).ncd)
4073 double diff = callout - mk_data(decision_height).get();
4075 if (mk_data(radio_altitude).get() >= 200)
4077 if (fabs(diff) <= 30)
4082 if (diff >= -3 && diff <= 6)
4091 MK_VIII::Mode6Handler::is_outside_band (double elevation, double callout)
4094 return elevation < callout - (elevation > 150 ? 20 : 10);
4098 MK_VIII::Mode6Handler::inhibit_smart_500 ()
4102 if (! mk_data(glideslope_deviation_dots).ncd
4103 && ! mk_dinput(glideslope_inhibit) // backcourse
4104 && ! mk_doutput(glideslope_cancel))
4106 if (mk->io_handler.conf.localizer_enabled
4107 && ! mk_data(localizer_deviation_dots).ncd)
4109 if (fabs(mk_data(localizer_deviation_dots).get()) <= 2)
4114 if (fabs(mk_data(glideslope_deviation_dots).get()) <= 2)
4123 MK_VIII::Mode6Handler::boot ()
4125 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
4128 last_decision_height = mk_dinput(decision_height);
4129 last_radio_altitude.set(&mk_data(radio_altitude));
4132 for (unsigned i = 0; i < n_altitude_callouts; i++)
4133 altitude_callouts_issued[i] = ! mk_data(radio_altitude).ncd
4134 && mk_data(radio_altitude).get() <= altitude_callout_definitions[i];
4136 // extrapolated from [SPEC] 6.4.2
4137 minimums_issued = mk_dinput(decision_height);
4139 if (conf.above_field_voice)
4142 get_altitude_above_field(&last_altitude_above_field);
4143 // extrapolated from [SPEC] 6.4.2
4144 above_field_issued = ! last_altitude_above_field.ncd
4145 && last_altitude_above_field.get() < 550;
4150 MK_VIII::Mode6Handler::power_off ()
4152 runway_timer.stop();
4156 MK_VIII::Mode6Handler::enter_takeoff ()
4158 reset_altitude_callouts(); // [SPEC] 6.4.2
4159 reset_minimums(); // omitted by [SPEC]; common sense
4163 MK_VIII::Mode6Handler::leave_takeoff ()
4165 reset_altitude_callouts(); // [SPEC] 6.0.2
4166 reset_minimums(); // [SPEC] 6.0.2
4170 MK_VIII::Mode6Handler::set_volume (float volume)
4172 mk_voice(minimums_minimums)->set_volume(volume);
4173 mk_voice(five_hundred_above)->set_volume(volume);
4174 for (unsigned i = 0; i < n_altitude_callouts; i++)
4175 mk_altitude_voice(i)->set_volume(volume);
4179 MK_VIII::Mode6Handler::altitude_callouts_enabled ()
4181 if (conf.minimums_enabled || conf.smart_500_enabled || conf.above_field_voice)
4184 for (unsigned i = 0; i < n_altitude_callouts; i++)
4185 if (conf.altitude_callouts_enabled[i])
4192 MK_VIII::Mode6Handler::update_minimums ()
4194 if (! mk->io_handler.gpws_inhibit()
4195 && (mk->voice_player.voice == mk_voice(minimums_minimums)
4196 || mk->voice_player.next_voice == mk_voice(minimums_minimums)))
4199 if (! mk->io_handler.gpws_inhibit()
4200 && ! mk->state_handler.ground // [1]
4201 && conf.minimums_enabled
4202 && ! minimums_issued
4203 && mk_dinput(landing_gear)
4204 && mk_dinput(decision_height)
4205 && ! last_decision_height)
4207 minimums_issued = true;
4209 // If an altitude callout is playing, stop it now so that the
4210 // minimums callout can be played (note that proper connection
4211 // and synchronization of the radio-altitude ARINC 529 input,
4212 // decision-height discrete and decision-height ARINC 529 input
4213 // will prevent an altitude callout from being played near the
4214 // decision height).
4216 if (is_playing_altitude_callout())
4217 mk->voice_player.stop(VoicePlayer::STOP_NOW);
4219 mk_set_alerts(mk_alert(MODE6_MINIMUMS));
4222 mk_unset_alerts(mk_alert(MODE6_MINIMUMS));
4225 last_decision_height = mk_dinput(decision_height);
4229 MK_VIII::Mode6Handler::update_altitude_callouts ()
4231 if (! mk->io_handler.gpws_inhibit() && is_playing_altitude_callout())
4234 if (! mk->io_handler.gpws_inhibit()
4235 && ! mk->state_handler.ground // [1]
4236 && ! mk_data(radio_altitude).ncd)
4237 for (unsigned i = 0; i < n_altitude_callouts && mk_data(radio_altitude).get() <= altitude_callout_definitions[i]; i++)
4238 if ((conf.altitude_callouts_enabled[i]
4239 || (altitude_callout_definitions[i] == 500
4240 && conf.smart_500_enabled))
4241 && ! altitude_callouts_issued[i]
4242 && (last_radio_altitude.ncd
4243 || last_radio_altitude.get() > altitude_callout_definitions[i]))
4245 // lock out all callouts superior or equal to this one
4246 for (unsigned j = 0; j <= i; j++)
4247 altitude_callouts_issued[j] = true;
4249 altitude_callouts_issued[i] = true;
4250 if (! is_near_minimums(altitude_callout_definitions[i])
4251 && ! is_outside_band(mk_data(radio_altitude).get(), altitude_callout_definitions[i])
4252 && (! conf.smart_500_enabled
4253 || altitude_callout_definitions[i] != 500
4254 || ! inhibit_smart_500()))
4256 mk->alert_handler.set_altitude_callout_alert(mk_altitude_voice(i));
4261 mk_unset_alerts(mk_alert(MODE6_ALTITUDE_CALLOUT));
4264 last_radio_altitude.set(&mk_data(radio_altitude));
4268 MK_VIII::Mode6Handler::test_runway (const FGRunway *_runway)
4270 if (_runway->lengthFt() < mk->conf.runway_database)
4274 SGGeod::fromDeg(mk_data(gps_longitude).get(), mk_data(gps_latitude).get()));
4276 // get distance to threshold
4277 double distance, az1, az2;
4278 SGGeodesy::inverse(pos, _runway->threshold(), az1, az2, distance);
4279 return distance * SG_METER_TO_NM <= 5;
4283 MK_VIII::Mode6Handler::test_airport (const FGAirport *airport)
4285 for (unsigned int r=0; r<airport->numRunways(); ++r) {
4286 FGRunway* rwy(airport->getRunwayByIndex(r));
4288 if (test_runway(rwy)) return true;
4294 bool MK_VIII::Mode6Handler::AirportFilter::passAirport(FGAirport* a) const
4296 bool ok = self->test_airport(a);
4301 MK_VIII::Mode6Handler::update_runway ()
4304 if (mk_data(gps_latitude).ncd || mk_data(gps_longitude).ncd) {
4309 // Search for the closest runway threshold in range 5
4310 // nm. Passing 30nm to
4311 // get_closest_airport() provides enough margin for large
4312 // airports, which may have a runway located far away from the
4313 // airport's reference point.
4314 AirportFilter filter(this);
4315 FGPositionedRef apt = FGPositioned::findClosest(
4316 SGGeod::fromDeg(mk_data(gps_longitude).get(), mk_data(gps_latitude).get()),
4319 runway.elevation = apt->elevation();
4322 has_runway.set(apt != NULL);
4326 MK_VIII::Mode6Handler::get_altitude_above_field (Parameter<double> *parameter)
4328 if (! has_runway.ncd && has_runway.get() && ! mk_data(geometric_altitude).ncd)
4329 parameter->set(mk_data(geometric_altitude).get() - runway.elevation);
4335 MK_VIII::Mode6Handler::update_above_field_callout ()
4337 if (! conf.above_field_voice)
4340 // update_runway() has to iterate over the whole runway database
4341 // (which contains thousands of runways), so it would be unwise to
4342 // run it every frame. Run it every 3 seconds. Note that the first
4343 // iteration is run in boot().
4344 if (runway_timer.start_or_elapsed() >= 3)
4347 runway_timer.start();
4350 Parameter<double> altitude_above_field;
4351 get_altitude_above_field(&altitude_above_field);
4353 if (! mk->io_handler.gpws_inhibit()
4354 && (mk->voice_player.voice == conf.above_field_voice
4355 || mk->voice_player.next_voice == conf.above_field_voice))
4358 // handle reset term
4359 if (above_field_issued)
4361 if ((! has_runway.ncd && ! has_runway.get())
4362 || (! altitude_above_field.ncd && altitude_above_field.get() > 700))
4363 above_field_issued = false;
4366 if (! mk->io_handler.gpws_inhibit()
4367 && ! mk->state_handler.ground // [1]
4368 && ! above_field_issued
4369 && ! altitude_above_field.ncd
4370 && altitude_above_field.get() < 550
4371 && (last_altitude_above_field.ncd
4372 || last_altitude_above_field.get() >= 550))
4374 above_field_issued = true;
4376 if (! is_outside_band(altitude_above_field.get(), 500))
4378 mk->alert_handler.set_altitude_callout_alert(conf.above_field_voice);
4383 mk_unset_alerts(mk_alert(MODE6_ALTITUDE_CALLOUT));
4386 last_altitude_above_field.set(&altitude_above_field);
4390 MK_VIII::Mode6Handler::is_bank_angle (double abs_roll_angle, double bias)
4392 return conf.is_bank_angle(&mk_data(radio_altitude),
4393 abs_roll_angle - abs_roll_angle * bias,
4394 mk_dinput(autopilot_engaged));
4398 MK_VIII::Mode6Handler::is_high_bank_angle ()
4400 return mk_data(radio_altitude).ncd || mk_data(radio_altitude).get() >= 210;
4404 MK_VIII::Mode6Handler::get_bank_angle_alerts ()
4406 if (! mk->io_handler.gpws_inhibit()
4407 && ! mk->state_handler.ground // [1]
4408 && ! mk_data(roll_angle).ncd)
4410 double abs_roll_angle = fabs(mk_data(roll_angle).get());
4412 if (is_bank_angle(abs_roll_angle, 0.4))
4413 return is_high_bank_angle()
4414 ? (mk_alert(MODE6_HIGH_BANK_ANGLE_1) | mk_alert(MODE6_HIGH_BANK_ANGLE_2) | mk_alert(MODE6_HIGH_BANK_ANGLE_3))
4415 : (mk_alert(MODE6_LOW_BANK_ANGLE_1) | mk_alert(MODE6_LOW_BANK_ANGLE_2) | mk_alert(MODE6_LOW_BANK_ANGLE_3));
4416 else if (is_bank_angle(abs_roll_angle, 0.2))
4417 return is_high_bank_angle()
4418 ? (mk_alert(MODE6_HIGH_BANK_ANGLE_1) | mk_alert(MODE6_HIGH_BANK_ANGLE_2))
4419 : (mk_alert(MODE6_LOW_BANK_ANGLE_1) | mk_alert(MODE6_LOW_BANK_ANGLE_2));
4420 else if (is_bank_angle(abs_roll_angle, 0))
4421 return is_high_bank_angle()
4422 ? mk_alert(MODE6_HIGH_BANK_ANGLE_1)
4423 : mk_alert(MODE6_LOW_BANK_ANGLE_1);
4430 MK_VIII::Mode6Handler::update_bank_angle ()
4432 if (! conf.bank_angle_enabled)
4435 unsigned int alerts = get_bank_angle_alerts();
4437 // [SPEC] 6.4.4 specifies that the non-continuous alerts
4438 // (MODE6_*_BANK_ANGLE_1 and MODE6_*_BANK_ANGLE_2) are locked out
4439 // until the initial envelope is exited.
4441 if (! test_bits(alerts, mk_alert(MODE6_LOW_BANK_ANGLE_3)))
4442 mk_unset_alerts(mk_alert(MODE6_LOW_BANK_ANGLE_3));
4443 if (! test_bits(alerts, mk_alert(MODE6_HIGH_BANK_ANGLE_3)))
4444 mk_unset_alerts(mk_alert(MODE6_HIGH_BANK_ANGLE_3));
4447 mk_set_alerts(alerts);
4449 mk_unset_alerts(mk_alert(MODE6_LOW_BANK_ANGLE_1)
4450 | mk_alert(MODE6_HIGH_BANK_ANGLE_1)
4451 | mk_alert(MODE6_LOW_BANK_ANGLE_2)
4452 | mk_alert(MODE6_HIGH_BANK_ANGLE_2));
4456 MK_VIII::Mode6Handler::update ()
4458 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
4461 if (! mk->state_handler.takeoff
4462 && ! mk_data(radio_altitude).ncd
4463 && mk_data(radio_altitude).get() > 1000)
4465 reset_altitude_callouts(); // [SPEC] 6.4.2
4466 reset_minimums(); // common sense
4470 update_altitude_callouts();
4471 update_above_field_callout();
4472 update_bank_angle();
4475 ///////////////////////////////////////////////////////////////////////////////
4476 // TCFHandler /////////////////////////////////////////////////////////////////
4477 ///////////////////////////////////////////////////////////////////////////////
4479 // Gets the difference between the azimuth from @from_lat,@from_lon to
4480 // @to_lat,@to_lon, and @to_heading, in degrees.
4482 MK_VIII::TCFHandler::get_azimuth_difference (double from_lat,
4488 double az1, az2, distance;
4489 geo_inverse_wgs_84(0, from_lat, from_lon, to_lat, to_lon, &az1, &az2, &distance);
4490 return get_heading_difference(az1, to_heading);
4493 // Gets the difference between the azimuth from the current GPS
4494 // position to the center of @_runway, and the heading of @_runway, in
4497 MK_VIII::TCFHandler::get_azimuth_difference (const FGRunway *_runway)
4499 return get_azimuth_difference(mk_data(gps_latitude).get(),
4500 mk_data(gps_longitude).get(),
4501 _runway->latitude(),
4502 _runway->longitude(),
4503 _runway->headingDeg());
4506 // Selects the most likely intended destination runway of @airport,
4507 // and returns it in @_runway. For each runway, the difference between
4508 // the azimuth from the current GPS position to the center of the
4509 // runway and its heading is computed. The runway having the smallest
4512 // This selection algorithm is not specified in [SPEC], but
4513 // http://www.egpws.com/general_information/description/runway_select.htm
4514 // talks about automatic runway selection.
4516 MK_VIII::TCFHandler::select_runway (const FGAirport *airport)
4518 FGRunway* _runway = 0;
4519 double min_diff = 360;
4521 for (unsigned int r=0; r<airport->numRunways(); ++r) {
4522 FGRunway* rwy(airport->getRunwayByIndex(r));
4523 double diff = get_azimuth_difference(rwy);
4524 if (diff < min_diff)
4529 } // of airport runways iteration
4533 bool MK_VIII::TCFHandler::AirportFilter::passAirport(FGAirport* aApt) const
4535 return aApt->hasHardRunwayOfLengthFt(mk->conf.runway_database);
4539 MK_VIII::TCFHandler::update_runway ()
4542 if (mk_data(gps_latitude).ncd || mk_data(gps_longitude).ncd) {
4546 // Search for the intended destination runway of the closest
4547 // airport in range 15 nm. Passing 30nm to get_closest_airport()
4548 // provides enough margin for
4549 // large airports, which may have a runway located far away from
4550 // the airport's reference point.
4551 AirportFilter filter(mk);
4552 FGAirport* apt = FGAirport::findClosest(
4553 SGGeod::fromDeg(mk_data(gps_longitude).get(), mk_data(gps_latitude).get()),
4558 FGRunway* _runway = select_runway(apt);
4560 if (!_runway) return;
4564 runway.center.latitude = _runway->latitude();
4565 runway.center.longitude = _runway->longitude();
4567 runway.elevation = apt->elevation();
4569 double half_length_m = _runway->lengthM() * 0.5;
4570 runway.half_length = half_length_m * SG_METER_TO_NM;
4572 // b3 ________________ b0
4574 // h1>>> | e1<<<<<<<<e0 | <<<h0
4575 // |________________|
4578 // get heading to runway threshold (h0) and end (h1)
4579 runway.edges[0].heading = _runway->headingDeg();
4580 runway.edges[1].heading = get_reciprocal_heading(_runway->headingDeg());
4584 // get position of runway threshold (e0)
4585 geo_direct_wgs_84(0,
4586 runway.center.latitude,
4587 runway.center.longitude,
4588 runway.edges[1].heading,
4590 &runway.edges[0].position.latitude,
4591 &runway.edges[0].position.longitude,
4594 // get position of runway end (e1)
4595 geo_direct_wgs_84(0,
4596 runway.center.latitude,
4597 runway.center.longitude,
4598 runway.edges[0].heading,
4600 &runway.edges[1].position.latitude,
4601 &runway.edges[1].position.longitude,
4604 double half_width_m = _runway->widthM() * 0.5;
4606 // get position of threshold bias area edges (b0 and b1)
4607 get_bias_area_edges(&runway.edges[0].position,
4608 runway.edges[1].heading,
4610 &runway.bias_area[0],
4611 &runway.bias_area[1]);
4613 // get position of end bias area edges (b2 and b3)
4614 get_bias_area_edges(&runway.edges[1].position,
4615 runway.edges[0].heading,
4617 &runway.bias_area[2],
4618 &runway.bias_area[3]);
4622 MK_VIII::TCFHandler::get_bias_area_edges (Position *edge,
4624 double half_width_m,
4625 Position *bias_edge1,
4626 Position *bias_edge2)
4628 double half_bias_width_m = k * SG_NM_TO_METER + half_width_m;
4629 double tmp_latitude = 0.0, tmp_longitude = 0.0, az = 0.0;
4631 geo_direct_wgs_84(0,
4639 geo_direct_wgs_84(0,
4642 heading_substract(reciprocal, 90),
4644 &bias_edge1->latitude,
4645 &bias_edge1->longitude,
4647 geo_direct_wgs_84(0,
4650 heading_add(reciprocal, 90),
4652 &bias_edge2->latitude,
4653 &bias_edge2->longitude,
4657 // Returns true if the current GPS position is inside the edge
4658 // triangle of @edge. The edge triangle, which is specified and
4659 // represented in [SPEC] 6.3.1, is defined as an isocele right
4660 // triangle of infinite height, whose right angle is located at the
4661 // position of @edge, and whose height is parallel to the heading of
4664 MK_VIII::TCFHandler::is_inside_edge_triangle (RunwayEdge *edge)
4666 return get_azimuth_difference(mk_data(gps_latitude).get(),
4667 mk_data(gps_longitude).get(),
4668 edge->position.latitude,
4669 edge->position.longitude,
4670 edge->heading) <= 45;
4673 // Returns true if the current GPS position is inside the bias area of
4674 // the currently selected runway.
4676 MK_VIII::TCFHandler::is_inside_bias_area ()
4679 double angles_sum = 0;
4681 for (int i = 0; i < 4; i++)
4683 double az2, distance;
4684 geo_inverse_wgs_84(0,
4685 mk_data(gps_latitude).get(),
4686 mk_data(gps_longitude).get(),
4687 runway.bias_area[i].latitude,
4688 runway.bias_area[i].longitude,
4689 &az1[i], &az2, &distance);
4692 for (int i = 0; i < 4; i++)
4694 double angle = az1[i == 3 ? 0 : i + 1] - az1[i];
4698 angles_sum += angle;
4701 return angles_sum > 180;
4705 MK_VIII::TCFHandler::is_tcf ()
4707 if (mk_data(radio_altitude).get() > 10)
4711 double distance, az1, az2;
4713 geo_inverse_wgs_84(0,
4714 mk_data(gps_latitude).get(),
4715 mk_data(gps_longitude).get(),
4716 runway.center.latitude,
4717 runway.center.longitude,
4718 &az1, &az2, &distance);
4720 distance *= SG_METER_TO_NM;
4722 // distance to the inner envelope edge
4723 double edge_distance = distance - runway.half_length - k;
4725 if (edge_distance >= 15)
4727 if (mk_data(radio_altitude).get() < 700)
4730 else if (edge_distance >= 12)
4732 if (mk_data(radio_altitude).get() < 100 * edge_distance - 800)
4735 else if (edge_distance >= 4)
4737 if (mk_data(radio_altitude).get() < 400)
4740 else if (edge_distance >= 2.45)
4742 if (mk_data(radio_altitude).get() < 66.666667 * edge_distance + 133.33333)
4747 if (is_inside_edge_triangle(&runway.edges[0]) || is_inside_edge_triangle(&runway.edges[1]))
4749 if (edge_distance >= 1)
4751 if (mk_data(radio_altitude).get() < 66.666667 * edge_distance + 133.33333)
4754 else if (edge_distance >= 0.05)
4756 if (mk_data(radio_altitude).get() < 200 * edge_distance)
4762 if (! is_inside_bias_area())
4764 if (mk_data(radio_altitude).get() < 245)
4772 if (mk_data(radio_altitude).get() < 700)
4781 MK_VIII::TCFHandler::is_rfcf ()
4785 double distance, az1, az2;
4786 geo_inverse_wgs_84(0,
4787 mk_data(gps_latitude).get(),
4788 mk_data(gps_longitude).get(),
4789 runway.center.latitude,
4790 runway.center.longitude,
4791 &az1, &az2, &distance);
4793 double krf = k + mk_data(gps_vertical_figure_of_merit).get() / 200;
4794 distance = distance * SG_METER_TO_NM - runway.half_length - krf;
4798 double altitude_above_field = mk_data(geometric_altitude).get() - runway.elevation;
4800 if (distance >= 1.5)
4802 if (altitude_above_field < 300)
4805 else if (distance >= 0)
4807 if (altitude_above_field < 200 * distance)
4817 MK_VIII::TCFHandler::update ()
4819 if (mk->configuration_module.state != ConfigurationModule::STATE_OK || ! conf.enabled)
4822 // update_runway() has to iterate over the whole runway database
4823 // (which contains thousands of runways), so it would be unwise to
4824 // run it every frame. Run it every 3 seconds.
4825 if (! runway_timer.running || runway_timer.elapsed() >= 3)
4828 runway_timer.start();
4831 if (! mk_dinput(ta_tcf_inhibit)
4832 && ! mk->state_handler.ground // [1]
4833 && ! mk_data(gps_latitude).ncd
4834 && ! mk_data(gps_longitude).ncd
4835 && ! mk_data(radio_altitude).ncd
4836 && ! mk_data(geometric_altitude).ncd
4837 && ! mk_data(gps_vertical_figure_of_merit).ncd)
4842 _reference = mk_data(radio_altitude).get_pointer();
4844 _reference = mk_data(geometric_altitude).get_pointer();
4850 if (mk_test_alert(TCF_TOO_LOW_TERRAIN))
4852 double new_bias = bias;
4853 // do not repeat terrain alerts below 30ft agl
4854 if (mk_data(radio_altitude).get() > 30)
4856 if (new_bias < 0.0) // sanity check
4858 while ((*reference < initial_value - initial_value * new_bias)&&
4863 if (new_bias > bias)
4866 mk_repeat_alert(mk_alert(TCF_TOO_LOW_TERRAIN));
4872 reference = _reference;
4873 initial_value = *reference;
4874 mk_set_alerts(mk_alert(TCF_TOO_LOW_TERRAIN));
4881 mk_unset_alerts(mk_alert(TCF_TOO_LOW_TERRAIN));
4884 ///////////////////////////////////////////////////////////////////////////////
4885 // MK_VIII ////////////////////////////////////////////////////////////////////
4886 ///////////////////////////////////////////////////////////////////////////////
4888 MK_VIII::MK_VIII (SGPropertyNode *node)
4889 : properties_handler(this),
4892 power_handler(this),
4893 system_handler(this),
4894 configuration_module(this),
4895 fault_handler(this),
4898 self_test_handler(this),
4899 alert_handler(this),
4900 state_handler(this),
4901 mode1_handler(this),
4902 mode2_handler(this),
4903 mode3_handler(this),
4904 mode4_handler(this),
4905 mode5_handler(this),
4906 mode6_handler(this),
4909 for (int i = 0; i < node->nChildren(); ++i)
4911 SGPropertyNode *child = node->getChild(i);
4912 string cname = child->getName();
4913 string cval = child->getStringValue();
4915 if (cname == "name")
4917 else if (cname == "number")
4918 num = child->getIntValue();
4921 SG_LOG(SG_INSTR, SG_WARN, "Error in mk-viii config logic");
4923 SG_LOG(SG_INSTR, SG_WARN, "Section = " << name);
4931 properties_handler.init();
4932 voice_player.init();
4938 SGPropertyNode *node = fgGetNode(("/instrumentation/" + name).c_str(), num, true);
4940 configuration_module.bind(node);
4941 power_handler.bind(node);
4942 io_handler.bind(node);
4943 voice_player.bind(node);
4949 properties_handler.unbind();
4953 MK_VIII::update (double dt)
4955 power_handler.update();
4956 system_handler.update();
4958 if (system_handler.state != SystemHandler::STATE_ON)
4961 io_handler.update_inputs();
4962 io_handler.update_input_faults();
4964 voice_player.update();
4965 state_handler.update();
4967 if (self_test_handler.update())
4970 io_handler.update_internal_latches();
4971 io_handler.update_lamps();
4973 mode1_handler.update();
4974 mode2_handler.update();
4975 mode3_handler.update();
4976 mode4_handler.update();
4977 mode5_handler.update();
4978 mode6_handler.update();
4979 tcf_handler.update();
4981 alert_handler.update();
4982 io_handler.update_outputs();