1 // mk_viii.cxx -- Honeywell MK VIII EGPWS emulation
3 // Written by Jean-Yves Lefort, started September 2005.
5 // Copyright (C) 2005, 2006 Jean-Yves Lefort - jylefort@FreeBSD.org
7 // This program is free software; you can redistribute it and/or
8 // modify it under the terms of the GNU General Public License as
9 // published by the Free Software Foundation; either version 2 of the
10 // License, or (at your option) any later version.
12 // This program is distributed in the hope that it will be useful, but
13 // WITHOUT ANY WARRANTY; without even the implied warranty of
14 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 // General Public License for more details.
17 // You should have received a copy of the GNU General Public License
18 // along with this program; if not, write to the Free Software
19 // Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
21 ///////////////////////////////////////////////////////////////////////////////
25 // [PILOT] Honeywell International Inc., "MK VI and MK VIII,
26 // Enhanced Ground Proximity Warning System (EGPWS),
27 // Pilot's Guide", May 2004
29 // http://www.egpws.com/engineering_support/documents/pilot_guides/060-4314-000.pdf
31 // [SPEC] Honeywell International Inc., "Product Specification
32 // for the MK VI and MK VIII Enhanced Ground Proximity
33 // Warning System (EGPWS)", June 2004
35 // http://www.egpws.com/engineering_support/documents/specs/965-1180-601.pdf
37 // [INSTALL] Honeywell Inc., "MK VI, MK VIII, Enhanced Ground
38 // Proximity Warning System (Class A TAWS), Installation
39 // Design Guide", July 2003
41 // http://www.egpws.com/engineering_support/documents/install/060-4314-150.pdf
45 // [1] [SPEC] does not specify the "must be airborne"
46 // condition; we use it to make sure that the alert
47 // will not trigger when on the ground, since it would
51 # pragma warning( disable: 4355 )
66 #include <simgear/constants.h>
67 #include <simgear/sg_inlines.h>
68 #include <simgear/debug/logstream.hxx>
69 #include <simgear/math/sg_geodesy.hxx>
70 #include <simgear/math/sg_random.h>
71 #include <simgear/misc/sg_path.hxx>
72 #include <simgear/sound/soundmgr_openal.hxx>
73 #include <simgear/structure/exception.hxx>
77 #include <Airports/runways.hxx>
78 #include <Airports/simple.hxx>
80 # include <Include/version.h>
82 #include <Main/fg_props.hxx>
83 #include <Main/globals.hxx>
84 #include "instrument_mgr.hxx"
85 #include "mk_viii.hxx"
87 ///////////////////////////////////////////////////////////////////////////////
88 // constants //////////////////////////////////////////////////////////////////
89 ///////////////////////////////////////////////////////////////////////////////
91 #define GLIDESLOPE_DOTS_TO_DDM 0.0875 // [INSTALL] 6.2.30
92 #define GLIDESLOPE_DDM_TO_DOTS (1 / GLIDESLOPE_DOTS_TO_DDM)
94 #define LOCALIZER_DOTS_TO_DDM 0.0775 // [INSTALL] 6.2.33
95 #define LOCALIZER_DDM_TO_DOTS (1 / LOCALIZER_DOTS_TO_DDM)
97 ///////////////////////////////////////////////////////////////////////////////
98 // helpers ////////////////////////////////////////////////////////////////////
99 ///////////////////////////////////////////////////////////////////////////////
101 #define assert_not_reached() assert(false)
102 #define n_elements(_array) (sizeof(_array) / sizeof((_array)[0]))
103 #define test_bits(_bits, _test) (((_bits) & (_test)) != 0)
105 #define mk_node(_name) (mk->properties_handler.external_properties._name)
107 #define mk_dinput_feed(_name) (mk->io_handler.input_feeders.discretes._name)
108 #define mk_dinput(_name) (mk->io_handler.inputs.discretes._name)
109 #define mk_ainput_feed(_name) (mk->io_handler.input_feeders.arinc429._name)
110 #define mk_ainput(_name) (mk->io_handler.inputs.arinc429._name)
111 #define mk_doutput(_name) (mk->io_handler.outputs.discretes._name)
112 #define mk_aoutput(_name) (mk->io_handler.outputs.arinc429._name)
113 #define mk_data(_name) (mk->io_handler.data._name)
115 #define mk_voice(_name) (mk->voice_player.voices._name)
116 #define mk_altitude_voice(_i) (mk->voice_player.voices.altitude_callouts[(_i)])
118 #define mk_alert(_name) (AlertHandler::ALERT_ ## _name)
119 #define mk_alert_flag(_name) (AlertHandler::ALERT_FLAG_ ## _name)
120 #define mk_set_alerts (mk->alert_handler.set_alerts)
121 #define mk_unset_alerts (mk->alert_handler.unset_alerts)
122 #define mk_repeat_alert (mk->alert_handler.repeat_alert)
123 #define mk_test_alert(_name) (mk_test_alerts(mk_alert(_name)))
124 #define mk_test_alerts(_test) (test_bits(mk->alert_handler.alerts, (_test)))
126 const double MK_VIII::TCFHandler::k = 0.25;
129 modify_amplitude (double amplitude, double dB)
131 return amplitude * pow(10.0, dB / 20.0);
135 heading_add (double h1, double h2)
137 double result = h1 + h2;
144 heading_substract (double h1, double h2)
146 double result = h1 - h2;
153 get_heading_difference (double h1, double h2)
155 double diff = h1 - h2;
166 get_reciprocal_heading (double h)
168 return heading_add(h, 180);
171 ///////////////////////////////////////////////////////////////////////////////
172 // PropertiesHandler //////////////////////////////////////////////////////////
173 ///////////////////////////////////////////////////////////////////////////////
176 MK_VIII::PropertiesHandler::init ()
178 mk_node(ai_caged) = fgGetNode("/instrumentation/attitude-indicator/caged-flag", true);
179 mk_node(ai_roll) = fgGetNode("/instrumentation/attitude-indicator/indicated-roll-deg", true);
180 mk_node(ai_serviceable) = fgGetNode("/instrumentation/attitude-indicator/serviceable", true);
181 mk_node(altimeter_altitude) = fgGetNode("/instrumentation/altimeter/indicated-altitude-ft", true);
182 mk_node(altimeter_serviceable) = fgGetNode("/instrumentation/altimeter/serviceable", true);
183 mk_node(altitude) = fgGetNode("/position/altitude-ft", true);
184 mk_node(altitude_agl) = fgGetNode("/position/altitude-agl-ft", true);
185 mk_node(altitude_gear_agl) = fgGetNode("/position/gear-agl-ft", true);
186 mk_node(orientation_roll) = fgGetNode("/orientation/roll-deg", true);
187 mk_node(asi_serviceable) = fgGetNode("/instrumentation/airspeed-indicator/serviceable", true);
188 mk_node(asi_speed) = fgGetNode("/instrumentation/airspeed-indicator/indicated-speed-kt", true);
189 mk_node(autopilot_heading_lock) = fgGetNode("/autopilot/locks/heading", true);
190 mk_node(flaps) = fgGetNode("/controls/flight/flaps", true);
191 mk_node(gear_down) = fgGetNode("/controls/gear/gear-down", true);
192 mk_node(latitude) = fgGetNode("/position/latitude-deg", true);
193 mk_node(longitude) = fgGetNode("/position/longitude-deg", true);
194 mk_node(nav0_cdi_serviceable) = fgGetNode("/instrumentation/nav/cdi/serviceable", true);
195 mk_node(nav0_gs_distance) = fgGetNode("/instrumentation/nav/gs-distance", true);
196 mk_node(nav0_gs_needle_deflection) = fgGetNode("/instrumentation/nav/gs-needle-deflection", true);
197 mk_node(nav0_gs_serviceable) = fgGetNode("/instrumentation/nav/gs/serviceable", true);
198 mk_node(nav0_has_gs) = fgGetNode("/instrumentation/nav/has-gs", true);
199 mk_node(nav0_heading_needle_deflection) = fgGetNode("/instrumentation/nav/heading-needle-deflection", true);
200 mk_node(nav0_in_range) = fgGetNode("/instrumentation/nav/in-range", true);
201 mk_node(nav0_nav_loc) = fgGetNode("/instrumentation/nav/nav-loc", true);
202 mk_node(nav0_serviceable) = fgGetNode("/instrumentation/nav/serviceable", true);
203 mk_node(power) = fgGetNode(("/systems/electrical/outputs/" + mk->name).c_str(), mk->num, true);
204 mk_node(replay_state) = fgGetNode("/sim/freeze/replay-state", true);
205 mk_node(vs) = fgGetNode("/velocities/vertical-speed-fps", true);
209 MK_VIII::PropertiesHandler::unbind ()
211 vector<SGPropertyNode_ptr>::iterator iter;
213 for (iter = tied_properties.begin(); iter != tied_properties.end(); iter++)
216 tied_properties.clear();
219 ///////////////////////////////////////////////////////////////////////////////
220 // PowerHandler ///////////////////////////////////////////////////////////////
221 ///////////////////////////////////////////////////////////////////////////////
224 MK_VIII::PowerHandler::bind (SGPropertyNode *node)
226 mk->properties_handler.tie(node, "serviceable", SGRawValuePointer<bool>(&serviceable));
230 MK_VIII::PowerHandler::handle_abnormal_voltage (bool abnormal,
236 if (timer->start_or_elapsed() >= max_duration)
237 return true; // power loss
246 MK_VIII::PowerHandler::update ()
248 double voltage = mk_node(power)->getDoubleValue();
249 bool now_powered = true;
257 if (handle_abnormal_voltage(voltage < 20.5, &low_surge_timer, 0.03))
259 if (handle_abnormal_voltage(voltage < 22.0 || voltage > 30.3, &abnormal_timer, 300))
261 if (handle_abnormal_voltage(voltage > 32.2, &high_surge_timer, 1))
263 if (handle_abnormal_voltage(voltage > 37.8, &very_high_surge_timer, 0.1))
273 power_loss_timer.stop();
276 if (power_loss_timer.start_or_elapsed() >= 0.2)
288 MK_VIII::PowerHandler::power_on ()
291 mk->system_handler.power_on();
295 MK_VIII::PowerHandler::power_off ()
298 mk->system_handler.power_off();
299 mk->voice_player.stop(VoicePlayer::STOP_NOW);
300 mk->self_test_handler.power_off(); // run before IOHandler::power_off()
301 mk->io_handler.power_off();
302 mk->mode2_handler.power_off();
303 mk->mode6_handler.power_off();
306 ///////////////////////////////////////////////////////////////////////////////
307 // SystemHandler //////////////////////////////////////////////////////////////
308 ///////////////////////////////////////////////////////////////////////////////
311 MK_VIII::SystemHandler::power_on ()
313 state = STATE_BOOTING;
315 // [SPEC] 3.5.2 mentions a 20 seconds maximum boot delay. We use a
316 // random delay between 3 and 5 seconds.
318 boot_delay = 3 + sg_random() * 2;
323 MK_VIII::SystemHandler::power_off ()
331 MK_VIII::SystemHandler::update ()
333 if (state == STATE_BOOTING)
335 if (boot_timer.elapsed() >= boot_delay)
337 last_replay_state = mk_node(replay_state)->getIntValue();
339 mk->configuration_module.boot();
341 mk->io_handler.boot();
342 mk->fault_handler.boot();
343 mk->alert_handler.boot();
345 // inputs are needed by the following boot() routines
346 mk->io_handler.update_inputs();
348 mk->mode2_handler.boot();
349 mk->mode6_handler.boot();
353 mk->io_handler.post_boot();
356 else if (state != STATE_OFF && mk->configuration_module.state == ConfigurationModule::STATE_OK)
358 // If the replay state changes, switch to reposition mode for 3
359 // seconds ([SPEC] 6.0.5) to avoid spurious alerts.
361 int replay_state = mk_node(replay_state)->getIntValue();
362 if (replay_state != last_replay_state)
364 mk->alert_handler.reposition();
366 last_replay_state = replay_state;
367 state = STATE_REPOSITION;
368 reposition_timer.start();
371 if (state == STATE_REPOSITION && reposition_timer.elapsed() >= 3)
373 // inputs are needed by StateHandler::post_reposition()
374 mk->io_handler.update_inputs();
376 mk->state_handler.post_reposition();
383 ///////////////////////////////////////////////////////////////////////////////
384 // ConfigurationModule ////////////////////////////////////////////////////////
385 ///////////////////////////////////////////////////////////////////////////////
387 MK_VIII::ConfigurationModule::ConfigurationModule (MK_VIII *device)
390 // arbitrary defaults
391 categories[CATEGORY_AIRCRAFT_MODE_TYPE_SELECT] = 0;
392 categories[CATEGORY_AIR_DATA_INPUT_SELECT] = 1;
393 categories[CATEGORY_POSITION_INPUT_SELECT] = 2;
394 categories[CATEGORY_ALTITUDE_CALLOUTS] = 13;
395 categories[CATEGORY_AUDIO_MENU_SELECT] = 0;
396 categories[CATEGORY_TERRAIN_DISPLAY_SELECT] = 1;
397 categories[CATEGORY_OPTIONS_SELECT_GROUP_1] = 124;
398 categories[CATEGORY_RADIO_ALTITUDE_INPUT_SELECT] = 2;
399 categories[CATEGORY_NAVIGATION_INPUT_SELECT] = 3;
400 categories[CATEGORY_ATTITUDE_INPUT_SELECT] = 6;
401 categories[CATEGORY_HEADING_INPUT_SELECT] = 2;
402 categories[CATEGORY_WINDSHEAR_INPUT_SELECT] = 0;
403 categories[CATEGORY_INPUT_OUTPUT_DISCRETE_TYPE_SELECT] = 7;
404 categories[CATEGORY_AUDIO_OUTPUT_LEVEL] = 0;
405 categories[CATEGORY_UNDEFINED_INPUT_SELECT_1] = 0;
406 categories[CATEGORY_UNDEFINED_INPUT_SELECT_2] = 0;
407 categories[CATEGORY_UNDEFINED_INPUT_SELECT_3] = 0;
410 static double m1_t1_min_agl1 (double vs) { return -1620 - 1.1133 * vs; }
411 static double m1_t1_min_agl2 (double vs) { return -400 - 0.4 * vs; }
412 static double m1_t4_min_agl1 (double vs) { return -1625.47 - 1.1167 * vs; }
413 static double m1_t4_min_agl2 (double vs) { return -0.1958 * vs; }
415 static int m3_t1_max_agl (bool flap_override) { return 1500; }
416 static double m3_t1_max_alt_loss (bool flap_override, double agl) { return 5.4 + 0.092 * agl; }
417 static int m3_t2_max_agl (bool flap_override) { return flap_override ? 815 : 925; }
418 static double m3_t2_max_alt_loss (bool flap_override, double agl)
420 int bias = agl > 700 ? 5 : 0;
423 return (9.0 + 0.184 * agl) + bias;
425 return (5.4 + 0.092 * agl) + bias;
428 static double m4_t1_min_agl2 (double airspeed) { return -1083 + 8.333 * airspeed; }
429 static double m4_t568_a_min_agl2 (double airspeed) { return -1523 + 11.36 * airspeed; }
430 static double m4_t79_a_min_agl2 (double airspeed) { return -1182 + 11.36 * airspeed; }
431 static double m4_t568_b_min_agl2 (double airspeed) { return -1570 + 11.6 * airspeed; }
432 static double m4_t79_b_min_agl2 (double airspeed) { return -1222 + 11.6 * airspeed; }
435 MK_VIII::ConfigurationModule::m6_t2_is_bank_angle (Parameter<double> *agl,
441 if (agl->ncd || agl->get() > 122)
442 return abs_roll_deg > 33;
446 if (agl->ncd || agl->get() > 2450)
447 return abs_roll_deg > 55;
448 else if (agl->get() > 150)
449 return agl->get() < 153.33333 * abs_roll_deg - 5983.3333;
453 return agl->get() < 4 * abs_roll_deg - 10;
454 else if (agl->get() > 5)
455 return abs_roll_deg > 10;
461 MK_VIII::ConfigurationModule::m6_t4_is_bank_angle (Parameter<double> *agl,
467 if (agl->ncd || agl->get() > 156)
468 return abs_roll_deg > 33;
472 if (agl->ncd || agl->get() > 210)
473 return abs_roll_deg > 50;
477 return agl->get() < 5.7142857 * abs_roll_deg - 75.714286;
483 MK_VIII::ConfigurationModule::read_aircraft_mode_type_select (int value)
485 static const Mode1Handler::EnvelopesConfiguration m1_t1 =
486 { false, 10, m1_t1_min_agl1, 284, m1_t1_min_agl2, 2450 };
487 static const Mode1Handler::EnvelopesConfiguration m1_t4 =
488 { true, 50, m1_t4_min_agl1, 346, m1_t4_min_agl2, 1958 };
490 static const Mode2Handler::Configuration m2_t1 = { 190, 280 };
491 static const Mode2Handler::Configuration m2_t4 = { 220, 310 };
492 static const Mode2Handler::Configuration m2_t5 = { 220, 310 };
494 static const Mode3Handler::Configuration m3_t1 =
495 { 30, m3_t1_max_agl, m3_t1_max_alt_loss };
496 static const Mode3Handler::Configuration m3_t2 =
497 { 50, m3_t2_max_agl, m3_t2_max_alt_loss };
499 static const Mode4Handler::EnvelopesConfiguration m4_t1_ac =
500 { 190, 250, 500, m4_t1_min_agl2, 1000 };
501 static const Mode4Handler::EnvelopesConfiguration m4_t5_ac =
502 { 178, 200, 500, m4_t568_a_min_agl2, 1000 };
503 static const Mode4Handler::EnvelopesConfiguration m4_t68_ac =
504 { 178, 200, 500, m4_t568_a_min_agl2, 750 };
505 static const Mode4Handler::EnvelopesConfiguration m4_t79_ac =
506 { 148, 170, 500, m4_t79_a_min_agl2, 750 };
508 static const Mode4Handler::EnvelopesConfiguration m4_t1_b =
509 { 159, 250, 245, m4_t1_min_agl2, 1000 };
510 static const Mode4Handler::EnvelopesConfiguration m4_t5_b =
511 { 148, 200, 200, m4_t568_b_min_agl2, 1000 };
512 static const Mode4Handler::EnvelopesConfiguration m4_t6_b =
513 { 150, 200, 170, m4_t568_b_min_agl2, 750 };
514 static const Mode4Handler::EnvelopesConfiguration m4_t7_b =
515 { 120, 170, 170, m4_t79_b_min_agl2, 750 };
516 static const Mode4Handler::EnvelopesConfiguration m4_t8_b =
517 { 148, 200, 150, m4_t568_b_min_agl2, 750 };
518 static const Mode4Handler::EnvelopesConfiguration m4_t9_b =
519 { 118, 170, 150, m4_t79_b_min_agl2, 750 };
521 static const Mode4Handler::ModesConfiguration m4_t1 = { &m4_t1_ac, &m4_t1_b };
522 static const Mode4Handler::ModesConfiguration m4_t5 = { &m4_t5_ac, &m4_t5_b };
523 static const Mode4Handler::ModesConfiguration m4_t6 = { &m4_t68_ac, &m4_t6_b };
524 static const Mode4Handler::ModesConfiguration m4_t7 = { &m4_t79_ac, &m4_t7_b };
525 static const Mode4Handler::ModesConfiguration m4_t8 = { &m4_t68_ac, &m4_t8_b };
526 static const Mode4Handler::ModesConfiguration m4_t9 = { &m4_t79_ac, &m4_t9_b };
528 static Mode6Handler::BankAnglePredicate m6_t2 = m6_t2_is_bank_angle;
529 static Mode6Handler::BankAnglePredicate m6_t4 = m6_t4_is_bank_angle;
531 static const IOHandler::FaultsConfiguration f_slow = { 180, 200 };
532 static const IOHandler::FaultsConfiguration f_fast = { 250, 290 };
537 const Mode1Handler::EnvelopesConfiguration *m1;
538 const Mode2Handler::Configuration *m2;
539 const Mode3Handler::Configuration *m3;
540 const Mode4Handler::ModesConfiguration *m4;
541 Mode6Handler::BankAnglePredicate m6;
542 const IOHandler::FaultsConfiguration *f;
544 } aircraft_types[] = {
545 { 0, &m1_t4, &m2_t4, &m3_t2, &m4_t6, m6_t4, &f_fast, 2000 },
546 { 1, &m1_t4, &m2_t4, &m3_t2, &m4_t8, m6_t4, &f_fast, 2000 },
547 { 2, &m1_t4, &m2_t4, &m3_t2, &m4_t5, m6_t4, &f_fast, 2000 },
548 { 3, &m1_t4, &m2_t5, &m3_t2, &m4_t7, m6_t4, &f_slow, 2000 },
549 { 4, &m1_t4, &m2_t5, &m3_t2, &m4_t9, m6_t4, &f_slow, 2000 },
550 { 254, &m1_t1, &m2_t1, &m3_t1, &m4_t1, m6_t2, &f_fast, 3500 },
551 { 255, &m1_t1, &m2_t1, &m3_t1, &m4_t1, m6_t2, &f_fast, 2000 }
554 for (size_t i = 0; i < n_elements(aircraft_types); i++)
555 if (aircraft_types[i].type == value)
557 mk->mode1_handler.conf.envelopes = aircraft_types[i].m1;
558 mk->mode2_handler.conf = aircraft_types[i].m2;
559 mk->mode3_handler.conf = aircraft_types[i].m3;
560 mk->mode4_handler.conf.modes = aircraft_types[i].m4;
561 mk->mode6_handler.conf.is_bank_angle = aircraft_types[i].m6;
562 mk->io_handler.conf.faults = aircraft_types[i].f;
563 mk->conf.runway_database = aircraft_types[i].runway_database;
567 state = STATE_INVALID_AIRCRAFT_TYPE;
572 MK_VIII::ConfigurationModule::read_air_data_input_select (int value)
575 return (value >= 0 && value <= 6) || (value >= 10 && value <= 13) || value == 255;
579 MK_VIII::ConfigurationModule::read_position_input_select (int value)
582 mk->io_handler.conf.use_internal_gps = true;
583 else if ((value >= 0 && value <= 5)
584 || (value >= 10 && value <= 13)
587 mk->io_handler.conf.use_internal_gps = false;
595 MK_VIII::ConfigurationModule::read_altitude_callouts (int value)
610 { 0, { MINIMUMS, SMART_500, 200, 100, 50, 40, 30, 20, 10, 0 } },
611 { 1, { MINIMUMS, SMART_500, 200, 0 } },
612 { 2, { MINIMUMS, SMART_500, 100, 50, 40, 30, 20, 10, 0 } },
613 { 3, { MINIMUMS, SMART_500, 0 } },
614 { 4, { MINIMUMS, 200, 100, 50, 40, 30, 20, 10, 0 } },
615 { 5, { MINIMUMS, 200, 0 } },
616 { 6, { MINIMUMS, 100, 50, 40, 30, 20, 10, 0 } },
618 { 8, { MINIMUMS, 0 } },
619 { 9, { MINIMUMS, 500, 200, 100, 50, 40, 30, 20, 10, 0 } },
620 { 10, { MINIMUMS, 500, 200, 0 } },
621 { 11, { MINIMUMS, 500, 100, 50, 40, 30, 20, 10, 0 } },
622 { 12, { MINIMUMS, 500, 0 } },
623 { 13, { MINIMUMS, 1000, 500, 400, 300, 200, 100, 50, 40, 30, 20, 10, 0 } },
624 { 14, { MINIMUMS, 100, 0 } },
625 { 15, { MINIMUMS, 200, 100, 0 } },
626 { 100, { FIELD_500, 0 } },
627 { 101, { FIELD_500_ABOVE, 0 } }
632 mk->mode6_handler.conf.minimums_enabled = false;
633 mk->mode6_handler.conf.smart_500_enabled = false;
634 mk->mode6_handler.conf.above_field_voice = NULL;
635 for (i = 0; i < n_altitude_callouts; i++)
636 mk->mode6_handler.conf.altitude_callouts_enabled[i] = false;
638 for (i = 0; i < n_elements(values); i++)
639 if (values[i].id == value)
641 for (int j = 0; values[i].callouts[j] != 0; j++)
642 switch (values[i].callouts[j])
645 mk->mode6_handler.conf.minimums_enabled = true;
649 mk->mode6_handler.conf.smart_500_enabled = true;
653 mk->mode6_handler.conf.above_field_voice = mk_altitude_voice(Mode6Handler::ALTITUDE_CALLOUT_500);
656 case FIELD_500_ABOVE:
657 mk->mode6_handler.conf.above_field_voice = mk_voice(five_hundred_above);
661 for (unsigned k = 0; k < n_altitude_callouts; k++)
662 if (mk->mode6_handler.altitude_callout_definitions[k] == values[i].callouts[j])
663 mk->mode6_handler.conf.altitude_callouts_enabled[k] = true;
674 MK_VIII::ConfigurationModule::read_audio_menu_select (int value)
676 if (value == 0 || value == 1)
677 mk->mode4_handler.conf.voice_too_low_gear = mk_voice(too_low_gear);
678 else if (value == 2 || value == 3)
679 mk->mode4_handler.conf.voice_too_low_gear = mk_voice(too_low_flaps);
687 MK_VIII::ConfigurationModule::read_terrain_display_select (int value)
690 mk->tcf_handler.conf.enabled = false;
691 else if (value == 0 || value == 1 || (value >= 3 && value <= 15)
692 || (value >= 18 && value <= 20) || (value >= 235 && value <= 255))
693 mk->tcf_handler.conf.enabled = true;
701 MK_VIII::ConfigurationModule::read_options_select_group_1 (int value)
703 if (value >= 0 && value < 128)
705 mk->io_handler.conf.flap_reversal = test_bits(value, 1 << 1);
706 mk->mode6_handler.conf.bank_angle_enabled = test_bits(value, 1 << 2);
707 mk->io_handler.conf.steep_approach_enabled = test_bits(value, 1 << 6);
715 MK_VIII::ConfigurationModule::read_radio_altitude_input_select (int value)
717 if (value >= 0 && value <= 2)
718 mk->io_handler.conf.use_gear_altitude = true;
720 mk->io_handler.conf.use_gear_altitude = false;
721 return (value >= 0 && value <= 4) || (value >= 251 && value <= 255);
725 MK_VIII::ConfigurationModule::read_navigation_input_select (int value)
727 if (value >= 0 && value <= 2)
728 mk->io_handler.conf.localizer_enabled = false;
729 else if ((value >= 3 && value <= 5) || (value >= 250 && value <= 255))
730 mk->io_handler.conf.localizer_enabled = true;
738 MK_VIII::ConfigurationModule::read_attitude_input_select (int value)
741 mk->io_handler.conf.use_attitude_indicator=true;
743 mk->io_handler.conf.use_attitude_indicator=false;
744 return (value >= 0 && value <= 6) || value == 253 || value == 255;
748 MK_VIII::ConfigurationModule::read_heading_input_select (int value)
751 return (value >= 0 && value <= 3) || value == 254 || value == 255;
755 MK_VIII::ConfigurationModule::read_windshear_input_select (int value)
758 return value == 0 || (value >= 253 && value <= 255);
762 MK_VIII::ConfigurationModule::read_input_output_discrete_type_select (int value)
767 IOHandler::LampConfiguration lamp_conf;
768 bool gpws_inhibit_enabled;
769 bool momentary_flap_override_enabled;
770 bool alternate_steep_approach;
772 { 0, { false, false }, false, true, true },
773 { 1, { true, false }, false, true, true },
774 { 2, { false, false }, true, true, true },
775 { 3, { true, false }, true, true, true },
776 { 4, { false, true }, true, true, true },
777 { 5, { true, true }, true, true, true },
778 { 6, { false, false }, true, true, false },
779 { 7, { true, false }, true, true, false },
780 { 254, { false, false }, true, false, true },
781 { 255, { false, false }, true, false, true }
784 for (size_t i = 0; i < n_elements(io_types); i++)
785 if (io_types[i].type == value)
787 mk->io_handler.conf.lamp = &io_types[i].lamp_conf;
788 mk->io_handler.conf.gpws_inhibit_enabled = io_types[i].gpws_inhibit_enabled;
789 mk->io_handler.conf.momentary_flap_override_enabled = io_types[i].momentary_flap_override_enabled;
790 mk->io_handler.conf.alternate_steep_approach = io_types[i].alternate_steep_approach;
798 MK_VIII::ConfigurationModule::read_audio_output_level (int value)
812 for (size_t i = 0; i < n_elements(values); i++)
813 if (values[i].id == value)
815 mk->voice_player.set_volume(mk->voice_player.conf.volume = modify_amplitude(1.0, values[i].relative_dB));
819 // The self test needs the voice player even when the configuration
820 // is invalid, so set a default value.
821 mk->voice_player.set_volume(mk->voice_player.conf.volume = 1.0);
826 MK_VIII::ConfigurationModule::read_undefined_input_select (int value)
833 MK_VIII::ConfigurationModule::boot ()
835 bool (MK_VIII::ConfigurationModule::*readers[N_CATEGORIES]) (int) = {
836 &MK_VIII::ConfigurationModule::read_aircraft_mode_type_select,
837 &MK_VIII::ConfigurationModule::read_air_data_input_select,
838 &MK_VIII::ConfigurationModule::read_position_input_select,
839 &MK_VIII::ConfigurationModule::read_altitude_callouts,
840 &MK_VIII::ConfigurationModule::read_audio_menu_select,
841 &MK_VIII::ConfigurationModule::read_terrain_display_select,
842 &MK_VIII::ConfigurationModule::read_options_select_group_1,
843 &MK_VIII::ConfigurationModule::read_radio_altitude_input_select,
844 &MK_VIII::ConfigurationModule::read_navigation_input_select,
845 &MK_VIII::ConfigurationModule::read_attitude_input_select,
846 &MK_VIII::ConfigurationModule::read_heading_input_select,
847 &MK_VIII::ConfigurationModule::read_windshear_input_select,
848 &MK_VIII::ConfigurationModule::read_input_output_discrete_type_select,
849 &MK_VIII::ConfigurationModule::read_audio_output_level,
850 &MK_VIII::ConfigurationModule::read_undefined_input_select,
851 &MK_VIII::ConfigurationModule::read_undefined_input_select,
852 &MK_VIII::ConfigurationModule::read_undefined_input_select
855 memcpy(effective_categories, categories, sizeof(categories));
858 for (int i = 0; i < N_CATEGORIES; i++)
859 if (! (this->*readers[i])(effective_categories[i]))
861 SG_LOG(SG_INSTR, SG_ALERT, "MK VIII EGPWS configuration category " << i + 1 << ": invalid value " << effective_categories[i]);
863 if (state == STATE_OK)
864 state = STATE_INVALID_DATABASE;
866 mk_doutput(gpws_inop) = true;
867 mk_doutput(tad_inop) = true;
874 if (mk->mode6_handler.conf.above_field_voice && ! mk->tcf_handler.conf.enabled)
877 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");
878 state = STATE_INVALID_DATABASE;
883 MK_VIII::ConfigurationModule::bind (SGPropertyNode *node)
885 for (int i = 0; i < N_CATEGORIES; i++)
887 std::ostringstream name;
888 name << "configuration-module/category-" << i + 1;
889 mk->properties_handler.tie(node, name.str().c_str(), SGRawValuePointer<int>(&categories[i]));
893 ///////////////////////////////////////////////////////////////////////////////
894 // FaultHandler ///////////////////////////////////////////////////////////////
895 ///////////////////////////////////////////////////////////////////////////////
897 // [INSTALL] only specifies that the faults cause a GPWS INOP
898 // indication. We arbitrarily set a TAD INOP when it makes sense.
899 const unsigned int MK_VIII::FaultHandler::fault_inops[] = {
900 INOP_GPWS | INOP_TAD, // [INSTALL] 3.15.1.3
901 INOP_GPWS, // [INSTALL] appendix E 6.6.2
902 INOP_GPWS, // [INSTALL] appendix E 6.6.4
903 INOP_GPWS, // [INSTALL] appendix E 6.6.6
904 INOP_GPWS | INOP_TAD, // [INSTALL] appendix E 6.6.7
905 INOP_GPWS, // [INSTALL] appendix E 6.6.13
906 INOP_GPWS, // [INSTALL] appendix E 6.6.25
907 INOP_GPWS, // [INSTALL] appendix E 6.6.27
908 INOP_TAD, // unspecified
909 INOP_GPWS, // unspecified
910 INOP_GPWS, // unspecified
911 // [INSTALL] 2.3.10.1 specifies that GPWS INOP is "activated during
912 // any detected partial or total failure of the GPWS modes 1-5", so
913 // do not set any INOP for mode 6 and bank angle.
916 INOP_TAD // unspecified
920 MK_VIII::FaultHandler::has_faults () const
922 for (int i = 0; i < N_FAULTS; i++)
930 MK_VIII::FaultHandler::has_faults (unsigned int inop)
932 for (int i = 0; i < N_FAULTS; i++)
933 if (faults[i] && test_bits(fault_inops[i], inop))
940 MK_VIII::FaultHandler::boot ()
942 memset(faults, 0, sizeof(faults));
946 MK_VIII::FaultHandler::set_fault (Fault fault)
950 faults[fault] = true;
952 mk->self_test_handler.set_inop();
954 if (test_bits(fault_inops[fault], INOP_GPWS))
956 mk_unset_alerts(~mk_alert(TCF_TOO_LOW_TERRAIN));
957 mk_doutput(gpws_inop) = true;
959 if (test_bits(fault_inops[fault], INOP_TAD))
961 mk_unset_alerts(mk_alert(TCF_TOO_LOW_TERRAIN));
962 mk_doutput(tad_inop) = true;
968 MK_VIII::FaultHandler::unset_fault (Fault fault)
972 faults[fault] = false;
973 if (! has_faults(INOP_GPWS))
974 mk_doutput(gpws_inop) = false;
975 if (! has_faults(INOP_TAD))
976 mk_doutput(tad_inop) = false;
980 ///////////////////////////////////////////////////////////////////////////////
981 // IOHandler //////////////////////////////////////////////////////////////////
982 ///////////////////////////////////////////////////////////////////////////////
985 MK_VIII::IOHandler::TerrainClearanceFilter::update (double agl)
987 // [PILOT] page 20 specifies that the terrain clearance is equal to
988 // 75% of the radio altitude, averaged over the previous 15 seconds.
990 // no updates when simulation is paused (dt=0.0), and add 5 samples/second only
991 if (globals->get_sim_time_sec() - last_update < 0.2)
993 last_update = globals->get_sim_time_sec();
995 samples_type::iterator iter;
997 // remove samples older than 15 seconds
998 for (iter = samples.begin(); iter != samples.end() && globals->get_sim_time_sec() - (*iter).timestamp >= 15; iter = samples.begin())
1001 // append new sample
1002 samples.push_back(Sample<double>(agl));
1004 // calculate average
1005 double new_value = 0;
1006 if (samples.size() > 0)
1008 // time consuming loop => queue limited to 75 samples
1009 // (= 15seconds * 5samples/second)
1010 for (iter = samples.begin(); iter != samples.end(); iter++)
1011 new_value += (*iter).value;
1012 new_value /= samples.size();
1016 if (new_value > value)
1023 MK_VIII::IOHandler::TerrainClearanceFilter::reset ()
1030 MK_VIII::IOHandler::IOHandler (MK_VIII *device)
1031 : mk(device), _lamp(LAMP_NONE)
1033 memset(&input_feeders, 0, sizeof(input_feeders));
1034 memset(&inputs.discretes, 0, sizeof(inputs.discretes));
1035 memset(&outputs, 0, sizeof(outputs));
1036 memset(&power_saved, 0, sizeof(power_saved));
1038 mk_dinput_feed(landing_gear) = true;
1039 mk_dinput_feed(landing_flaps) = true;
1040 mk_dinput_feed(glideslope_inhibit) = true;
1041 mk_dinput_feed(decision_height) = true;
1042 mk_dinput_feed(autopilot_engaged) = true;
1043 mk_ainput_feed(uncorrected_barometric_altitude) = true;
1044 mk_ainput_feed(barometric_altitude_rate) = true;
1045 mk_ainput_feed(radio_altitude) = true;
1046 mk_ainput_feed(glideslope_deviation) = true;
1047 mk_ainput_feed(roll_angle) = true;
1048 mk_ainput_feed(localizer_deviation) = true;
1049 mk_ainput_feed(computed_airspeed) = true;
1051 // will be unset on power on
1052 mk_doutput(gpws_inop) = true;
1053 mk_doutput(tad_inop) = true;
1057 MK_VIII::IOHandler::boot ()
1059 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
1062 mk_doutput(gpws_inop) = false;
1063 mk_doutput(tad_inop) = false;
1065 mk_doutput(glideslope_cancel) = power_saved.glideslope_cancel;
1067 altitude_samples.clear();
1071 MK_VIII::IOHandler::post_boot ()
1073 if (momentary_steep_approach_enabled())
1075 last_landing_gear = mk_dinput(landing_gear);
1076 last_real_flaps_down = real_flaps_down();
1079 // read externally-latching input discretes
1080 update_alternate_discrete_input(&mk_dinput(mode6_low_volume));
1081 update_alternate_discrete_input(&mk_dinput(audio_inhibit));
1085 MK_VIII::IOHandler::power_off ()
1087 power_saved.glideslope_cancel = mk_doutput(glideslope_cancel); // [SPEC] 6.2.5
1089 memset(&outputs, 0, sizeof(outputs));
1091 audio_inhibit_fault_timer.stop();
1092 landing_gear_fault_timer.stop();
1093 flaps_down_fault_timer.stop();
1094 momentary_flap_override_fault_timer.stop();
1095 self_test_fault_timer.stop();
1096 glideslope_cancel_fault_timer.stop();
1097 steep_approach_fault_timer.stop();
1098 gpws_inhibit_fault_timer.stop();
1099 ta_tcf_inhibit_fault_timer.stop();
1102 mk_doutput(gpws_inop) = true;
1103 mk_doutput(tad_inop) = true;
1107 MK_VIII::IOHandler::enter_ground ()
1109 reset_terrain_clearance();
1111 if (conf.momentary_flap_override_enabled)
1112 mk_doutput(flap_override) = false; // [INSTALL] 3.15.1.6
1116 MK_VIII::IOHandler::enter_takeoff ()
1118 reset_terrain_clearance();
1120 if (momentary_steep_approach_enabled())
1121 // landing or go-around, disable steep approach as per [SPEC] 6.2.1
1122 mk_doutput(steep_approach) = false;
1126 MK_VIII::IOHandler::update_inputs ()
1128 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
1131 // 1. process input feeders
1133 if (mk_dinput_feed(landing_gear))
1134 mk_dinput(landing_gear) = mk_node(gear_down)->getBoolValue();
1135 if (mk_dinput_feed(landing_flaps))
1136 mk_dinput(landing_flaps) = mk_node(flaps)->getDoubleValue() < 1;
1137 if (mk_dinput_feed(glideslope_inhibit))
1138 mk_dinput(glideslope_inhibit) = mk_node(nav0_gs_distance)->getDoubleValue() < 0;
1139 if (mk_dinput_feed(autopilot_engaged))
1143 mode = mk_node(autopilot_heading_lock)->getStringValue();
1144 mk_dinput(autopilot_engaged) = mode && *mode;
1147 if (mk_ainput_feed(uncorrected_barometric_altitude))
1149 if (mk_node(altimeter_serviceable)->getBoolValue())
1150 mk_ainput(uncorrected_barometric_altitude).set(mk_node(altimeter_altitude)->getDoubleValue());
1152 mk_ainput(uncorrected_barometric_altitude).unset();
1154 if (mk_ainput_feed(barometric_altitude_rate))
1155 // Do not use the vsi, because it is pressure-based only, and
1156 // therefore too laggy for GPWS alerting purposes. I guess that
1157 // a real ADC combines pressure-based and inerta-based altitude
1158 // rates to provide a non-laggy rate. That non-laggy rate is
1159 // best emulated by /velocities/vertical-speed-fps * 60.
1160 mk_ainput(barometric_altitude_rate).set(mk_node(vs)->getDoubleValue() * 60);
1161 if (mk_ainput_feed(radio_altitude))
1164 if (conf.use_gear_altitude)
1165 agl = mk_node(altitude_gear_agl)->getDoubleValue();
1167 agl = mk_node(altitude_agl)->getDoubleValue();
1168 // Some flight models may return negative values when on the
1169 // ground or after a crash; do not allow them.
1170 mk_ainput(radio_altitude).set(SG_MAX2(0.0, agl));
1172 if (mk_ainput_feed(glideslope_deviation))
1174 if (mk_node(nav0_serviceable)->getBoolValue()
1175 && mk_node(nav0_gs_serviceable)->getBoolValue()
1176 && mk_node(nav0_in_range)->getBoolValue()
1177 && mk_node(nav0_has_gs)->getBoolValue())
1178 // gs-needle-deflection is expressed in degrees, and 1 dot =
1179 // 0.32 degrees (according to
1180 // http://www.ntsb.gov/Recs/letters/2000/A00_41_45.pdf)
1181 mk_ainput(glideslope_deviation).set(mk_node(nav0_gs_needle_deflection)->getDoubleValue() / 0.32 * GLIDESLOPE_DOTS_TO_DDM);
1183 mk_ainput(glideslope_deviation).unset();
1185 if (mk_ainput_feed(roll_angle))
1187 if (conf.use_attitude_indicator)
1189 // read data from attitude indicator instrument (requires vacuum system to work)
1190 if (mk_node(ai_serviceable)->getBoolValue() && ! mk_node(ai_caged)->getBoolValue())
1191 mk_ainput(roll_angle).set(mk_node(ai_roll)->getDoubleValue());
1193 mk_ainput(roll_angle).unset();
1197 // use simulator source
1198 mk_ainput(roll_angle).set(mk_node(orientation_roll)->getDoubleValue());
1201 if (mk_ainput_feed(localizer_deviation))
1203 if (mk_node(nav0_serviceable)->getBoolValue()
1204 && mk_node(nav0_cdi_serviceable)->getBoolValue()
1205 && mk_node(nav0_in_range)->getBoolValue()
1206 && mk_node(nav0_nav_loc)->getBoolValue())
1207 // heading-needle-deflection is expressed in degrees, and 1
1208 // dot = 2 degrees (0.5 degrees for ILS, but navradio.cxx
1209 // performs the conversion)
1210 mk_ainput(localizer_deviation).set(mk_node(nav0_heading_needle_deflection)->getDoubleValue() / 2 * LOCALIZER_DOTS_TO_DDM);
1212 mk_ainput(localizer_deviation).unset();
1214 if (mk_ainput_feed(computed_airspeed))
1216 if (mk_node(asi_serviceable)->getBoolValue())
1217 mk_ainput(computed_airspeed).set(mk_node(asi_speed)->getDoubleValue());
1219 mk_ainput(computed_airspeed).unset();
1224 mk_data(decision_height).set(&mk_ainput(decision_height));
1225 mk_data(radio_altitude).set(&mk_ainput(radio_altitude));
1226 mk_data(roll_angle).set(&mk_ainput(roll_angle));
1228 // update barometric_altitude_rate as per [SPEC] 6.2.1: "During
1229 // normal conditions, the system will base Mode 1 computations upon
1230 // barometric rate from the ADC. If this computed data is not valid
1231 // or available then the system will use internally computed
1232 // barometric altitude rate."
1234 if (! mk_ainput(barometric_altitude_rate).ncd)
1235 // the altitude rate input is valid, use it
1236 mk_data(barometric_altitude_rate).set(mk_ainput(barometric_altitude_rate).get());
1241 // The altitude rate input is invalid. We can compute an
1242 // altitude rate if all the following conditions are true:
1244 // - the altitude input is valid
1245 // - there is an altitude sample with age >= 1 second
1246 // - that altitude sample is valid
1248 if (! mk_ainput(uncorrected_barometric_altitude).ncd)
1250 if (! altitude_samples.empty() && ! altitude_samples.front().value.ncd)
1252 double elapsed = globals->get_sim_time_sec() - altitude_samples.front().timestamp;
1256 mk_data(barometric_altitude_rate).set((mk_ainput(uncorrected_barometric_altitude).get() - altitude_samples.front().value.get()) / elapsed * 60);
1262 mk_data(barometric_altitude_rate).unset();
1265 altitude_samples.push_back(Sample< Parameter<double> >(mk_ainput(uncorrected_barometric_altitude)));
1267 // Erase everything from the beginning of the list up to the sample
1268 // preceding the most recent sample whose age is >= 1 second.
1270 altitude_samples_type::iterator erase_last = altitude_samples.begin();
1271 altitude_samples_type::iterator iter;
1273 for (iter = altitude_samples.begin(); iter != altitude_samples.end(); iter++)
1274 if (globals->get_sim_time_sec() - (*iter).timestamp >= 1)
1279 if (erase_last != altitude_samples.begin())
1280 altitude_samples.erase(altitude_samples.begin(), erase_last);
1284 if (conf.use_internal_gps)
1286 mk_data(gps_altitude).set(mk_node(altitude)->getDoubleValue());
1287 mk_data(gps_latitude).set(mk_node(latitude)->getDoubleValue());
1288 mk_data(gps_longitude).set(mk_node(longitude)->getDoubleValue());
1289 mk_data(gps_vertical_figure_of_merit).set(0.0);
1293 mk_data(gps_altitude).set(&mk_ainput(gps_altitude));
1294 mk_data(gps_latitude).set(&mk_ainput(gps_latitude));
1295 mk_data(gps_longitude).set(&mk_ainput(gps_longitude));
1296 mk_data(gps_vertical_figure_of_merit).set(&mk_ainput(gps_vertical_figure_of_merit));
1299 // update glideslope and localizer
1301 mk_data(glideslope_deviation_dots).set(&mk_ainput(glideslope_deviation), GLIDESLOPE_DDM_TO_DOTS);
1302 mk_data(localizer_deviation_dots).set(&mk_ainput(localizer_deviation), LOCALIZER_DDM_TO_DOTS);
1304 // Update geometric altitude; [SPEC] 6.7.8 provides an overview of a
1305 // complex algorithm which combines several input sources to
1306 // calculate the geometric altitude. Since the exact algorithm is
1307 // not given, we fallback to a much simpler method.
1309 if (! mk_data(gps_altitude).ncd)
1310 mk_data(geometric_altitude).set(mk_data(gps_altitude).get());
1311 else if (! mk_ainput(uncorrected_barometric_altitude).ncd)
1312 mk_data(geometric_altitude).set(mk_ainput(uncorrected_barometric_altitude).get());
1314 mk_data(geometric_altitude).unset();
1316 // update terrain clearance
1318 update_terrain_clearance();
1320 // 3. perform sanity checks
1322 if (! mk_data(radio_altitude).ncd && mk_data(radio_altitude).get() < 0)
1323 mk_data(radio_altitude).unset();
1325 if (! mk_data(decision_height).ncd && mk_data(decision_height).get() < 0)
1326 mk_data(decision_height).unset();
1328 if (! mk_data(gps_latitude).ncd
1329 && (mk_data(gps_latitude).get() < -90
1330 || mk_data(gps_latitude).get() > 90))
1331 mk_data(gps_latitude).unset();
1333 if (! mk_data(gps_longitude).ncd
1334 && (mk_data(gps_longitude).get() < -180
1335 || mk_data(gps_longitude).get() > 180))
1336 mk_data(gps_longitude).unset();
1338 if (! mk_data(roll_angle).ncd
1339 && ((mk_data(roll_angle).get() < -180)
1340 || (mk_data(roll_angle).get() > 180)))
1341 mk_data(roll_angle).unset();
1343 // 4. process input feeders requiring data computed above
1345 if (mk_dinput_feed(decision_height))
1346 mk_dinput(decision_height) = ! mk_data(radio_altitude).ncd
1347 && ! mk_data(decision_height).ncd
1348 && mk_data(radio_altitude).get() <= mk_data(decision_height).get();
1352 MK_VIII::IOHandler::update_terrain_clearance ()
1354 if (! mk_data(radio_altitude).ncd)
1355 mk_data(terrain_clearance).set(terrain_clearance_filter.update(mk_data(radio_altitude).get()));
1357 mk_data(terrain_clearance).unset();
1361 MK_VIII::IOHandler::reset_terrain_clearance ()
1363 terrain_clearance_filter.reset();
1364 update_terrain_clearance();
1368 MK_VIII::IOHandler::handle_input_fault (bool test, FaultHandler::Fault fault)
1372 if (! mk->fault_handler.faults[fault])
1373 mk->fault_handler.set_fault(fault);
1376 mk->fault_handler.unset_fault(fault);
1380 MK_VIII::IOHandler::handle_input_fault (bool test,
1382 double max_duration,
1383 FaultHandler::Fault fault)
1387 if (! mk->fault_handler.faults[fault])
1389 if (timer->start_or_elapsed() >= max_duration)
1390 mk->fault_handler.set_fault(fault);
1395 mk->fault_handler.unset_fault(fault);
1401 MK_VIII::IOHandler::update_input_faults ()
1403 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
1406 // [INSTALL] 3.15.1.3
1407 handle_input_fault(mk_dinput(audio_inhibit),
1408 &audio_inhibit_fault_timer,
1410 FaultHandler::FAULT_ALL_MODES_INHIBIT);
1412 // [INSTALL] appendix E 6.6.2
1413 handle_input_fault(mk_dinput(landing_gear)
1414 && ! mk_ainput(computed_airspeed).ncd
1415 && mk_ainput(computed_airspeed).get() > conf.faults->max_gear_down_airspeed,
1416 &landing_gear_fault_timer,
1418 FaultHandler::FAULT_GEAR_SWITCH);
1420 // [INSTALL] appendix E 6.6.4
1421 handle_input_fault(flaps_down()
1422 && ! mk_ainput(computed_airspeed).ncd
1423 && mk_ainput(computed_airspeed).get() > conf.faults->max_flaps_down_airspeed,
1424 &flaps_down_fault_timer,
1426 FaultHandler::FAULT_FLAPS_SWITCH);
1428 // [INSTALL] appendix E 6.6.6
1429 if (conf.momentary_flap_override_enabled)
1430 handle_input_fault(mk_dinput(momentary_flap_override),
1431 &momentary_flap_override_fault_timer,
1433 FaultHandler::FAULT_MOMENTARY_FLAP_OVERRIDE_INVALID);
1435 // [INSTALL] appendix E 6.6.7
1436 handle_input_fault(mk_dinput(self_test),
1437 &self_test_fault_timer,
1439 FaultHandler::FAULT_SELF_TEST_INVALID);
1441 // [INSTALL] appendix E 6.6.13
1442 handle_input_fault(mk_dinput(glideslope_cancel),
1443 &glideslope_cancel_fault_timer,
1445 FaultHandler::FAULT_GLIDESLOPE_CANCEL_INVALID);
1447 // [INSTALL] appendix E 6.6.25
1448 if (momentary_steep_approach_enabled())
1449 handle_input_fault(mk_dinput(steep_approach),
1450 &steep_approach_fault_timer,
1452 FaultHandler::FAULT_STEEP_APPROACH_INVALID);
1454 // [INSTALL] appendix E 6.6.27
1455 if (conf.gpws_inhibit_enabled)
1456 handle_input_fault(mk_dinput(gpws_inhibit),
1457 &gpws_inhibit_fault_timer,
1459 FaultHandler::FAULT_GPWS_INHIBIT);
1461 // [INSTALL] does not specify a fault for this one, but it makes
1462 // sense to have it behave like GPWS inhibit
1463 handle_input_fault(mk_dinput(ta_tcf_inhibit),
1464 &ta_tcf_inhibit_fault_timer,
1466 FaultHandler::FAULT_TA_TCF_INHIBIT);
1468 // [PILOT] page 48: "In the event that required data for a
1469 // particular function is not available, then that function is
1470 // automatically inhibited and annunciated"
1472 handle_input_fault(mk_data(radio_altitude).ncd
1473 || mk_ainput(uncorrected_barometric_altitude).ncd
1474 || mk_data(barometric_altitude_rate).ncd
1475 || mk_ainput(computed_airspeed).ncd
1476 || mk_data(terrain_clearance).ncd,
1477 FaultHandler::FAULT_MODES14_INPUTS_INVALID);
1479 if (! mk_dinput(glideslope_inhibit))
1480 handle_input_fault(mk_data(radio_altitude).ncd,
1481 FaultHandler::FAULT_MODE5_INPUTS_INVALID);
1483 if (mk->mode6_handler.altitude_callouts_enabled())
1484 handle_input_fault(mk->mode6_handler.conf.above_field_voice
1485 ? (mk_data(gps_latitude).ncd
1486 || mk_data(gps_longitude).ncd
1487 || mk_data(geometric_altitude).ncd)
1488 : mk_data(radio_altitude).ncd,
1489 FaultHandler::FAULT_MODE6_INPUTS_INVALID);
1491 if (mk->mode6_handler.conf.bank_angle_enabled)
1492 handle_input_fault(mk_data(roll_angle).ncd,
1493 FaultHandler::FAULT_BANK_ANGLE_INPUTS_INVALID);
1495 if (mk->tcf_handler.conf.enabled)
1496 handle_input_fault(mk_data(radio_altitude).ncd
1497 || mk_data(geometric_altitude).ncd
1498 || mk_data(gps_latitude).ncd
1499 || mk_data(gps_longitude).ncd
1500 || mk_data(gps_vertical_figure_of_merit).ncd,
1501 FaultHandler::FAULT_TCF_INPUTS_INVALID);
1505 MK_VIII::IOHandler::update_alternate_discrete_input (bool *ptr)
1507 assert(mk->system_handler.state == SystemHandler::STATE_ON);
1509 if (ptr == &mk_dinput(mode6_low_volume))
1511 if (mk->configuration_module.state == ConfigurationModule::STATE_OK
1512 && mk->self_test_handler.state == SelfTestHandler::STATE_NONE)
1513 mk->mode6_handler.set_volume(*ptr ? modify_amplitude(1.0, -6) : 1.0);
1515 else if (ptr == &mk_dinput(audio_inhibit))
1517 if (mk->configuration_module.state == ConfigurationModule::STATE_OK
1518 && mk->self_test_handler.state == SelfTestHandler::STATE_NONE)
1519 mk->voice_player.set_volume(*ptr ? 0.0 : mk->voice_player.conf.volume);
1524 MK_VIII::IOHandler::update_internal_latches ()
1526 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
1530 if (conf.momentary_flap_override_enabled
1531 && mk_doutput(flap_override)
1532 && ! mk->state_handler.takeoff
1533 && (mk_data(radio_altitude).ncd || mk_data(radio_altitude).get() <= 50))
1534 mk_doutput(flap_override) = false;
1537 if (momentary_steep_approach_enabled())
1539 if (mk_doutput(steep_approach)
1540 && ! mk->state_handler.takeoff
1541 && ((last_landing_gear && ! mk_dinput(landing_gear))
1542 || (last_real_flaps_down && ! real_flaps_down())))
1543 // gear or flaps raised during approach: it's a go-around
1544 mk_doutput(steep_approach) = false;
1546 last_landing_gear = mk_dinput(landing_gear);
1547 last_real_flaps_down = real_flaps_down();
1551 if (mk_doutput(glideslope_cancel)
1552 && (mk_data(glideslope_deviation_dots).ncd
1553 || mk_data(radio_altitude).ncd
1554 || mk_data(radio_altitude).get() > 2000
1555 || mk_data(radio_altitude).get() < 30))
1556 mk_doutput(glideslope_cancel) = false;
1560 MK_VIII::IOHandler::update_egpws_alert_discrete_1 ()
1562 if (mk->voice_player.voice)
1567 VoicePlayer::Voice *voice;
1569 { 11, mk_voice(sink_rate_pause_sink_rate) },
1570 { 12, mk_voice(pull_up) },
1571 { 13, mk_voice(terrain) },
1572 { 13, mk_voice(terrain_pause_terrain) },
1573 { 14, mk_voice(dont_sink_pause_dont_sink) },
1574 { 15, mk_voice(too_low_gear) },
1575 { 16, mk_voice(too_low_flaps) },
1576 { 17, mk_voice(too_low_terrain) },
1577 { 18, mk_voice(soft_glideslope) },
1578 { 18, mk_voice(hard_glideslope) },
1579 { 19, mk_voice(minimums_minimums) }
1582 for (size_t i = 0; i < n_elements(voices); i++)
1583 if (voices[i].voice == mk->voice_player.voice)
1585 mk_aoutput(egpws_alert_discrete_1) = 1 << voices[i].bit;
1590 mk_aoutput(egpws_alert_discrete_1) = 0;
1594 MK_VIII::IOHandler::update_egpwc_logic_discretes ()
1596 mk_aoutput(egpwc_logic_discretes) = 0;
1598 if (mk->state_handler.takeoff)
1599 mk_aoutput(egpwc_logic_discretes) |= 1 << 18;
1604 unsigned int alerts;
1606 { 13, mk_alert(TCF_TOO_LOW_TERRAIN) },
1607 { 19, mk_alert(MODE1_SINK_RATE) },
1608 { 20, mk_alert(MODE1_PULL_UP) },
1609 { 21, mk_alert(MODE2A_PREFACE) | mk_alert(MODE2B_PREFACE) | mk_alert(MODE2B_LANDING_MODE) | mk_alert(MODE2A_ALTITUDE_GAIN_TERRAIN_CLOSING) },
1610 { 22, mk_alert(MODE2A) | mk_alert(MODE2B) },
1611 { 23, mk_alert(MODE3) },
1612 { 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) },
1613 { 25, mk_alert(MODE5_SOFT) | mk_alert(MODE5_HARD) }
1616 for (size_t i = 0; i < n_elements(logic); i++)
1617 if (mk_test_alerts(logic[i].alerts))
1618 mk_aoutput(egpwc_logic_discretes) |= 1 << logic[i].bit;
1622 MK_VIII::IOHandler::update_mode6_callouts_discrete_1 ()
1624 if (mk->voice_player.voice)
1629 VoicePlayer::Voice *voice;
1631 { 11, mk_voice(minimums_minimums) },
1632 { 16, mk_altitude_voice(Mode6Handler::ALTITUDE_CALLOUT_10) },
1633 { 17, mk_altitude_voice(Mode6Handler::ALTITUDE_CALLOUT_20) },
1634 { 18, mk_altitude_voice(Mode6Handler::ALTITUDE_CALLOUT_30) },
1635 { 19, mk_altitude_voice(Mode6Handler::ALTITUDE_CALLOUT_40) },
1636 { 20, mk_altitude_voice(Mode6Handler::ALTITUDE_CALLOUT_50) },
1637 { 23, mk_altitude_voice(Mode6Handler::ALTITUDE_CALLOUT_100) },
1638 { 24, mk_altitude_voice(Mode6Handler::ALTITUDE_CALLOUT_200) },
1639 { 25, mk_altitude_voice(Mode6Handler::ALTITUDE_CALLOUT_300) }
1642 for (size_t i = 0; i < n_elements(voices); i++)
1643 if (voices[i].voice == mk->voice_player.voice)
1645 mk_aoutput(mode6_callouts_discrete_1) = 1 << voices[i].bit;
1650 mk_aoutput(mode6_callouts_discrete_1) = 0;
1654 MK_VIII::IOHandler::update_mode6_callouts_discrete_2 ()
1656 if (mk->voice_player.voice)
1661 VoicePlayer::Voice *voice;
1663 { 11, mk_altitude_voice(Mode6Handler::ALTITUDE_CALLOUT_400) },
1664 { 12, mk_altitude_voice(Mode6Handler::ALTITUDE_CALLOUT_500) },
1665 { 13, mk_altitude_voice(Mode6Handler::ALTITUDE_CALLOUT_1000) },
1666 { 18, mk_voice(bank_angle_pause_bank_angle) },
1667 { 18, mk_voice(bank_angle_pause_bank_angle_3) },
1668 { 23, mk_voice(five_hundred_above) }
1671 for (size_t i = 0; i < n_elements(voices); i++)
1672 if (voices[i].voice == mk->voice_player.voice)
1674 mk_aoutput(mode6_callouts_discrete_2) = 1 << voices[i].bit;
1679 mk_aoutput(mode6_callouts_discrete_2) = 0;
1683 MK_VIII::IOHandler::update_egpws_alert_discrete_2 ()
1685 mk_aoutput(egpws_alert_discrete_2) = 1 << 27; // Terrain NA
1687 if (mk_doutput(glideslope_cancel))
1688 mk_aoutput(egpws_alert_discrete_2) |= 1 << 11;
1689 if (mk_doutput(gpws_alert))
1690 mk_aoutput(egpws_alert_discrete_2) |= 1 << 12;
1691 if (mk_doutput(gpws_warning))
1692 mk_aoutput(egpws_alert_discrete_2) |= 1 << 13;
1693 if (mk_doutput(gpws_inop))
1694 mk_aoutput(egpws_alert_discrete_2) |= 1 << 14;
1695 if (mk_doutput(audio_on))
1696 mk_aoutput(egpws_alert_discrete_2) |= 1 << 16;
1697 if (mk_doutput(tad_inop))
1698 mk_aoutput(egpws_alert_discrete_2) |= 1 << 24;
1699 if (mk->fault_handler.has_faults())
1700 mk_aoutput(egpws_alert_discrete_2) |= 1 << 25;
1704 MK_VIII::IOHandler::update_egpwc_alert_discrete_3 ()
1706 mk_aoutput(egpwc_alert_discrete_3) = 1 << 17 | 1 << 18;
1708 if (mk->fault_handler.faults[FaultHandler::FAULT_MODES14_INPUTS_INVALID])
1709 mk_aoutput(egpwc_alert_discrete_3) |= 1 << 11;
1710 if (mk->fault_handler.faults[FaultHandler::FAULT_MODE5_INPUTS_INVALID])
1711 mk_aoutput(egpwc_alert_discrete_3) |= 1 << 12;
1712 if (mk->fault_handler.faults[FaultHandler::FAULT_MODE6_INPUTS_INVALID])
1713 mk_aoutput(egpwc_alert_discrete_3) |= 1 << 13;
1714 if (mk->fault_handler.faults[FaultHandler::FAULT_BANK_ANGLE_INPUTS_INVALID])
1715 mk_aoutput(egpwc_alert_discrete_3) |= 1 << 14;
1716 if (mk_doutput(tad_inop))
1717 mk_aoutput(egpwc_alert_discrete_3) |= 1 << 16;
1721 MK_VIII::IOHandler::update_outputs ()
1723 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
1726 mk_doutput(audio_on) = ! mk_dinput(audio_inhibit)
1727 && mk->voice_player.voice
1728 && ! mk->voice_player.voice->element->silence;
1730 update_egpws_alert_discrete_1();
1731 update_egpwc_logic_discretes();
1732 update_mode6_callouts_discrete_1();
1733 update_mode6_callouts_discrete_2();
1734 update_egpws_alert_discrete_2();
1735 update_egpwc_alert_discrete_3();
1739 MK_VIII::IOHandler::get_lamp_output (Lamp lamp)
1743 case LAMP_GLIDESLOPE:
1744 return &mk_doutput(gpws_alert);
1747 return conf.lamp->format2 ? &mk_doutput(gpws_alert) : &mk_doutput(gpws_warning);
1750 return &mk_doutput(gpws_warning);
1753 assert_not_reached();
1759 MK_VIII::IOHandler::update_lamps ()
1761 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
1764 if (_lamp != LAMP_NONE && conf.lamp->flashing)
1766 // [SPEC] 6.9.3: 70 cycles per minute
1767 if (lamp_timer.elapsed() >= 60.0 / 70.0 / 2.0)
1769 bool *output = get_lamp_output(_lamp);
1770 *output = ! *output;
1777 MK_VIII::IOHandler::set_lamp (Lamp lamp)
1784 mk_doutput(gpws_warning) = false;
1785 mk_doutput(gpws_alert) = false;
1787 if (lamp != LAMP_NONE)
1789 *get_lamp_output(lamp) = true;
1795 MK_VIII::IOHandler::gpws_inhibit () const
1797 return conf.gpws_inhibit_enabled && mk_dinput(gpws_inhibit);
1801 MK_VIII::IOHandler::real_flaps_down () const
1803 return conf.flap_reversal ? mk_dinput(landing_flaps) : ! mk_dinput(landing_flaps);
1807 MK_VIII::IOHandler::flaps_down () const
1809 return flap_override() ? true : real_flaps_down();
1813 MK_VIII::IOHandler::flap_override () const
1815 return conf.momentary_flap_override_enabled ? mk_doutput(flap_override) : false;
1819 MK_VIII::IOHandler::steep_approach () const
1821 if (conf.steep_approach_enabled)
1822 // If alternate action was configured in category 13, we return
1823 // the value of the input discrete (which should be connected to a
1824 // latching steep approach cockpit switch). Otherwise, we return
1825 // the value of the output discrete.
1826 return conf.alternate_steep_approach ? mk_dinput(steep_approach) : mk_doutput(steep_approach);
1832 MK_VIII::IOHandler::momentary_steep_approach_enabled () const
1834 return conf.steep_approach_enabled && ! conf.alternate_steep_approach;
1838 MK_VIII::IOHandler::tie_input (SGPropertyNode *node,
1843 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));
1845 mk->properties_handler.tie(node, (string("input-feeders/discretes/") + name).c_str(), SGRawValuePointer<bool>(feed));
1849 MK_VIII::IOHandler::tie_input (SGPropertyNode *node,
1851 Parameter<double> *input,
1854 mk->properties_handler.tie(node, (string("inputs/arinc429/") + name).c_str(), SGRawValuePointer<double>(input->get_pointer()));
1855 mk->properties_handler.tie(node, (string("inputs/arinc429/") + name + "-ncd").c_str(), SGRawValuePointer<bool>(&input->ncd));
1857 mk->properties_handler.tie(node, (string("input-feeders/arinc429/") + name).c_str(), SGRawValuePointer<bool>(feed));
1861 MK_VIII::IOHandler::tie_output (SGPropertyNode *node,
1865 SGPropertyNode *child = node->getNode((string("outputs/discretes/") + name).c_str(), true);
1867 mk->properties_handler.tie(child, SGRawValuePointer<bool>(output));
1868 child->setAttribute(SGPropertyNode::WRITE, false);
1872 MK_VIII::IOHandler::tie_output (SGPropertyNode *node,
1876 SGPropertyNode *child = node->getNode((string("outputs/arinc429/") + name).c_str(), true);
1878 mk->properties_handler.tie(child, SGRawValuePointer<int>(output));
1879 child->setAttribute(SGPropertyNode::WRITE, false);
1883 MK_VIII::IOHandler::bind (SGPropertyNode *node)
1885 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));
1887 tie_input(node, "landing-gear", &mk_dinput(landing_gear), &mk_dinput_feed(landing_gear));
1888 tie_input(node, "landing-flaps", &mk_dinput(landing_flaps), &mk_dinput_feed(landing_flaps));
1889 tie_input(node, "momentary-flap-override", &mk_dinput(momentary_flap_override));
1890 tie_input(node, "self-test", &mk_dinput(self_test));
1891 tie_input(node, "glideslope-inhibit", &mk_dinput(glideslope_inhibit), &mk_dinput_feed(glideslope_inhibit));
1892 tie_input(node, "glideslope-cancel", &mk_dinput(glideslope_cancel));
1893 tie_input(node, "decision-height", &mk_dinput(decision_height), &mk_dinput_feed(decision_height));
1894 tie_input(node, "mode6-low-volume", &mk_dinput(mode6_low_volume));
1895 tie_input(node, "audio-inhibit", &mk_dinput(audio_inhibit));
1896 tie_input(node, "ta-tcf-inhibit", &mk_dinput(ta_tcf_inhibit));
1897 tie_input(node, "autopilot-engaged", &mk_dinput(autopilot_engaged), &mk_dinput_feed(autopilot_engaged));
1898 tie_input(node, "steep-approach", &mk_dinput(steep_approach));
1899 tie_input(node, "gpws-inhibit", &mk_dinput(gpws_inhibit));
1901 tie_input(node, "uncorrected-barometric-altitude", &mk_ainput(uncorrected_barometric_altitude), &mk_ainput_feed(uncorrected_barometric_altitude));
1902 tie_input(node, "barometric-altitude-rate", &mk_ainput(barometric_altitude_rate), &mk_ainput_feed(barometric_altitude_rate));
1903 tie_input(node, "gps-altitude", &mk_ainput(gps_altitude));
1904 tie_input(node, "gps-latitude", &mk_ainput(gps_latitude));
1905 tie_input(node, "gps-longitude", &mk_ainput(gps_longitude));
1906 tie_input(node, "gps-vertical-figure-of-merit", &mk_ainput(gps_vertical_figure_of_merit));
1907 tie_input(node, "radio-altitude", &mk_ainput(radio_altitude), &mk_ainput_feed(radio_altitude));
1908 tie_input(node, "glideslope-deviation", &mk_ainput(glideslope_deviation), &mk_ainput_feed(glideslope_deviation));
1909 tie_input(node, "roll-angle", &mk_ainput(roll_angle), &mk_ainput_feed(roll_angle));
1910 tie_input(node, "localizer-deviation", &mk_ainput(localizer_deviation), &mk_ainput_feed(localizer_deviation));
1911 tie_input(node, "computed-airspeed", &mk_ainput(computed_airspeed), &mk_ainput_feed(computed_airspeed));
1912 tie_input(node, "decision-height", &mk_ainput(decision_height), &mk_ainput_feed(decision_height));
1914 tie_output(node, "gpws-warning", &mk_doutput(gpws_warning));
1915 tie_output(node, "gpws-alert", &mk_doutput(gpws_alert));
1916 tie_output(node, "audio-on", &mk_doutput(audio_on));
1917 tie_output(node, "gpws-inop", &mk_doutput(gpws_inop));
1918 tie_output(node, "tad-inop", &mk_doutput(tad_inop));
1919 tie_output(node, "flap-override", &mk_doutput(flap_override));
1920 tie_output(node, "glideslope-cancel", &mk_doutput(glideslope_cancel));
1921 tie_output(node, "steep-approach", &mk_doutput(steep_approach));
1923 tie_output(node, "egpws-alert-discrete-1", &mk_aoutput(egpws_alert_discrete_1));
1924 tie_output(node, "egpwc-logic-discretes", &mk_aoutput(egpwc_logic_discretes));
1925 tie_output(node, "mode6-callouts-discrete-1", &mk_aoutput(mode6_callouts_discrete_1));
1926 tie_output(node, "mode6-callouts-discrete-2", &mk_aoutput(mode6_callouts_discrete_2));
1927 tie_output(node, "egpws-alert-discrete-2", &mk_aoutput(egpws_alert_discrete_2));
1928 tie_output(node, "egpwc-alert-discrete-3", &mk_aoutput(egpwc_alert_discrete_3));
1932 MK_VIII::IOHandler::get_discrete_input (bool *ptr) const
1938 MK_VIII::IOHandler::set_discrete_input (bool *ptr, bool value)
1943 if (mk->system_handler.state == SystemHandler::STATE_ON)
1945 if (ptr == &mk_dinput(momentary_flap_override))
1947 if (mk->configuration_module.state == ConfigurationModule::STATE_OK
1948 && mk->self_test_handler.state == SelfTestHandler::STATE_NONE
1949 && conf.momentary_flap_override_enabled
1951 mk_doutput(flap_override) = ! mk_doutput(flap_override);
1953 else if (ptr == &mk_dinput(self_test))
1954 mk->self_test_handler.handle_button_event(value);
1955 else if (ptr == &mk_dinput(glideslope_cancel))
1957 if (mk->configuration_module.state == ConfigurationModule::STATE_OK
1958 && mk->self_test_handler.state == SelfTestHandler::STATE_NONE
1961 if (! mk_doutput(glideslope_cancel))
1965 // Although we are not called from update(), we are
1966 // sure that the inputs we use here are defined,
1967 // since state is STATE_ON.
1969 if (! mk_data(glideslope_deviation_dots).ncd
1970 && ! mk_data(radio_altitude).ncd
1971 && mk_data(radio_altitude).get() <= 2000
1972 && mk_data(radio_altitude).get() >= 30)
1973 mk_doutput(glideslope_cancel) = true;
1975 // else do nothing ([PILOT] page 22: "Glideslope Cancel
1976 // can not be deselected (reset) by again pressing the
1977 // Glideslope Cancel switch.")
1980 else if (ptr == &mk_dinput(steep_approach))
1982 if (mk->configuration_module.state == ConfigurationModule::STATE_OK
1983 && mk->self_test_handler.state == SelfTestHandler::STATE_NONE
1984 && momentary_steep_approach_enabled()
1986 mk_doutput(steep_approach) = ! mk_doutput(steep_approach);
1992 if (mk->system_handler.state == SystemHandler::STATE_ON)
1993 update_alternate_discrete_input(ptr);
1997 MK_VIII::IOHandler::present_status_section (const char *name)
1999 printf("%s\n", name);
2003 MK_VIII::IOHandler::present_status_item (const char *name, const char *value)
2006 printf("\t%-32s %s\n", name, value);
2008 printf("\t%s\n", name);
2012 MK_VIII::IOHandler::present_status_subitem (const char *name)
2014 printf("\t\t%s\n", name);
2018 MK_VIII::IOHandler::present_status ()
2022 if (mk->system_handler.state != SystemHandler::STATE_ON)
2025 present_status_section("EGPWC CONFIGURATION");
2026 present_status_item("PART NUMBER:", "965-1220-000"); // [SPEC] 1.1
2027 present_status_item("MOD STATUS:", "N/A");
2028 present_status_item("SERIAL NUMBER:", "N/A");
2030 present_status_item("APPLICATION S/W VERSION:", FLIGHTGEAR_VERSION);
2031 present_status_item("TERRAIN DATABASE VERSION:", FLIGHTGEAR_VERSION);
2032 present_status_item("ENVELOPE MOD DATABASE VERSION:", "N/A");
2033 present_status_item("BOOT CODE VERSION:", FLIGHTGEAR_VERSION);
2036 present_status_section("CURRENT FAULTS");
2037 if (mk->configuration_module.state == ConfigurationModule::STATE_OK && ! mk->fault_handler.has_faults())
2038 present_status_item("NO FAULTS");
2041 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
2043 present_status_item("GPWS COMPUTER FAULTS:");
2044 switch (mk->configuration_module.state)
2046 case ConfigurationModule::STATE_INVALID_DATABASE:
2047 present_status_subitem("APPLICATION DATABASE FAILED");
2050 case ConfigurationModule::STATE_INVALID_AIRCRAFT_TYPE:
2051 present_status_subitem("CONFIGURATION TYPE INVALID");
2055 assert_not_reached();
2061 present_status_item("GPWS COMPUTER OK");
2062 present_status_item("GPWS EXTERNAL FAULTS:");
2064 static const char *fault_names[] = {
2065 "ALL MODES INHIBIT",
2068 "MOMENTARY FLAP OVERRIDE INVALID",
2069 "SELF TEST INVALID",
2070 "GLIDESLOPE CANCEL INVALID",
2071 "STEEP APPROACH INVALID",
2074 "MODES 1-4 INPUTS INVALID",
2075 "MODE 5 INPUTS INVALID",
2076 "MODE 6 INPUTS INVALID",
2077 "BANK ANGLE INPUTS INVALID",
2078 "TCF INPUTS INVALID"
2081 for (size_t i = 0; i < n_elements(fault_names); i++)
2082 if (mk->fault_handler.faults[i])
2083 present_status_subitem(fault_names[i]);
2088 present_status_section("CONFIGURATION:");
2090 static const char *category_names[] = {
2093 "POSITION INPUT TYPE",
2094 "CALLOUTS OPTION TYPE",
2096 "TERRAIN DISPLAY TYPE",
2098 "RADIO ALTITUDE TYPE",
2099 "NAVIGATION INPUT TYPE",
2101 "MAGNETIC HEADING TYPE",
2102 "WINDSHEAR INPUT TYPE",
2107 for (size_t i = 0; i < n_elements(category_names); i++)
2109 std::ostringstream value;
2110 value << "= " << mk->configuration_module.effective_categories[i];
2111 present_status_item(category_names[i], value.str().c_str());
2116 MK_VIII::IOHandler::get_present_status () const
2122 MK_VIII::IOHandler::set_present_status (bool value)
2124 if (value) present_status();
2128 ///////////////////////////////////////////////////////////////////////////////
2129 // VoicePlayer ////////////////////////////////////////////////////////////////
2130 ///////////////////////////////////////////////////////////////////////////////
2133 MK_VIII::VoicePlayer::Speaker::bind (SGPropertyNode *node)
2135 // uses xmlsound property names
2136 tie(node, "volume", &volume);
2137 tie(node, "pitch", &pitch);
2141 MK_VIII::VoicePlayer::Speaker::update_configuration ()
2143 map< string, SGSharedPtr<SGSoundSample> >::iterator iter;
2144 for (iter = player->samples.begin(); iter != player->samples.end(); iter++)
2146 SGSoundSample *sample = (*iter).second;
2148 sample->set_pitch(pitch);
2152 player->voice->volume_changed();
2155 MK_VIII::VoicePlayer::Voice::~Voice ()
2157 for (iter = elements.begin(); iter != elements.end(); iter++)
2158 delete *iter; // we owned the element
2163 MK_VIII::VoicePlayer::Voice::play ()
2165 iter = elements.begin();
2168 element->play(get_volume());
2172 MK_VIII::VoicePlayer::Voice::stop (bool now)
2176 if (now || element->silence)
2182 iter = elements.end() - 1; // stop after the current element finishes
2187 MK_VIII::VoicePlayer::Voice::set_volume (float _volume)
2194 MK_VIII::VoicePlayer::Voice::volume_changed ()
2197 element->set_volume(get_volume());
2201 MK_VIII::VoicePlayer::Voice::update ()
2205 if (! element->is_playing())
2207 if (++iter == elements.end())
2212 element->play(get_volume());
2218 MK_VIII::VoicePlayer::~VoicePlayer ()
2220 vector<Voice *>::iterator iter1;
2221 for (iter1 = _voices.begin(); iter1 != _voices.end(); iter1++)
2228 MK_VIII::VoicePlayer::init ()
2230 #define STDPAUSE 0.75 // [SPEC] 6.4.4: "the standard 0.75 second delay"
2232 SGSoundMgr *smgr = globals->get_soundmgr();
2233 _sgr = smgr->find("avionics", true);
2234 _sgr->tie_to_listener();
2236 make_voice(&voices.application_data_base_failed, "application-data-base-failed");
2237 make_voice(&voices.bank_angle, "bank-angle");
2238 make_voice(&voices.bank_angle_bank_angle, "bank-angle", "bank-angle");
2239 make_voice(&voices.bank_angle_bank_angle_3, "bank-angle", "bank-angle", 3.0);
2240 make_voice(&voices.bank_angle_inop, "bank-angle-inop");
2241 make_voice(&voices.bank_angle_pause_bank_angle, "bank-angle", STDPAUSE, "bank-angle");
2242 make_voice(&voices.bank_angle_pause_bank_angle_3, "bank-angle", STDPAUSE, "bank-angle", 3.0);
2243 make_voice(&voices.callouts_inop, "callouts-inop");
2244 make_voice(&voices.configuration_type_invalid, "configuration-type-invalid");
2245 make_voice(&voices.dont_sink, "dont-sink");
2246 make_voice(&voices.dont_sink_pause_dont_sink, "dont-sink", STDPAUSE, "dont-sink");
2247 make_voice(&voices.five_hundred_above, "500-above");
2248 make_voice(&voices.glideslope, "glideslope");
2249 make_voice(&voices.glideslope_inop, "glideslope-inop");
2250 make_voice(&voices.gpws_inop, "gpws-inop");
2251 make_voice(&voices.hard_glideslope, "glideslope", "glideslope", 3.0);
2252 make_voice(&voices.minimums, "minimums");
2253 make_voice(&voices.minimums_minimums, "minimums", "minimums");
2254 make_voice(&voices.pull_up, "pull-up");
2255 make_voice(&voices.sink_rate, "sink-rate");
2256 make_voice(&voices.sink_rate_pause_sink_rate, "sink-rate", STDPAUSE, "sink-rate");
2257 make_voice(&voices.soft_glideslope, new Voice::SampleElement(get_sample("glideslope"), modify_amplitude(1.0, -6)));
2258 make_voice(&voices.terrain, "terrain");
2259 make_voice(&voices.terrain_pause_terrain, "terrain", STDPAUSE, "terrain");
2260 make_voice(&voices.too_low_flaps, "too-low-flaps");
2261 make_voice(&voices.too_low_gear, "too-low-gear");
2262 make_voice(&voices.too_low_terrain, "too-low-terrain");
2264 for (unsigned i = 0; i < n_altitude_callouts; i++)
2266 std::ostringstream name;
2267 name << "altitude-" << mk->mode6_handler.altitude_callout_definitions[i];
2268 make_voice(&voices.altitude_callouts[i], name.str().c_str());
2271 speaker.update_configuration();
2275 MK_VIII::VoicePlayer::get_sample (const char *name)
2277 std::ostringstream refname;
2278 refname << mk->name << "[" << mk->num << "]" << "/" << name;
2280 SGSoundSample *sample = _sgr->find(refname.str());
2283 string filename = "Sounds/mk-viii/" + string(name) + ".wav";
2286 sample = new SGSoundSample(filename.c_str(), SGPath());
2288 catch (const sg_exception &e)
2290 SG_LOG(SG_INSTR, SG_ALERT, "Error loading MK VIII sound sample \"" + filename + "\": " + e.getFormattedMessage());
2294 _sgr->add(sample, refname.str());
2295 samples[refname.str()] = sample;
2302 MK_VIII::VoicePlayer::play (Voice *_voice, unsigned int flags)
2304 if (test_bits(flags, PLAY_NOW) || ! voice || voice->element->silence)
2310 looped = test_bits(flags, PLAY_LOOPED);
2313 next_looped = false;
2319 next_voice = _voice;
2320 next_looped = test_bits(flags, PLAY_LOOPED);
2325 MK_VIII::VoicePlayer::stop (unsigned int flags)
2329 voice->stop(test_bits(flags, STOP_NOW));
2339 MK_VIII::VoicePlayer::set_volume (float _volume)
2343 voice->volume_changed();
2347 MK_VIII::VoicePlayer::update ()
2355 if (! voice->element || voice->element->silence)
2358 looped = next_looped;
2361 next_looped = false;
2368 if (! voice->element)
2379 ///////////////////////////////////////////////////////////////////////////////
2380 // SelfTestHandler ////////////////////////////////////////////////////////////
2381 ///////////////////////////////////////////////////////////////////////////////
2384 MK_VIII::SelfTestHandler::_was_here (int position)
2386 if (position > current)
2395 MK_VIII::SelfTestHandler::Action
2396 MK_VIII::SelfTestHandler::sleep (double duration)
2398 Action _action = { ACTION_SLEEP, duration, NULL };
2402 MK_VIII::SelfTestHandler::Action
2403 MK_VIII::SelfTestHandler::play (VoicePlayer::Voice *voice)
2405 mk->voice_player.play(voice);
2406 Action _action = { ACTION_VOICE, 0, NULL };
2410 MK_VIII::SelfTestHandler::Action
2411 MK_VIII::SelfTestHandler::discrete_on (bool *discrete, double duration)
2414 return sleep(duration);
2417 MK_VIII::SelfTestHandler::Action
2418 MK_VIII::SelfTestHandler::discrete_on_off (bool *discrete, double duration)
2421 Action _action = { ACTION_SLEEP | ACTION_DISCRETE_ON_OFF, duration, discrete };
2425 MK_VIII::SelfTestHandler::Action
2426 MK_VIII::SelfTestHandler::discrete_on_off (bool *discrete, VoicePlayer::Voice *voice)
2429 mk->voice_player.play(voice);
2430 Action _action = { ACTION_VOICE | ACTION_DISCRETE_ON_OFF, 0, discrete };
2434 MK_VIII::SelfTestHandler::Action
2435 MK_VIII::SelfTestHandler::done ()
2437 Action _action = { ACTION_DONE, 0, NULL };
2441 MK_VIII::SelfTestHandler::Action
2442 MK_VIII::SelfTestHandler::run ()
2444 // Note that "Terrain INOP" and "Terrain NA" are or'ed to the same
2445 // output discrete (see [SPEC] 6.9.3.5).
2447 #define was_here() was_here_offset(0)
2448 #define was_here_offset(offset) _was_here(__LINE__ * 20 + (offset))
2452 if (mk->configuration_module.state == ConfigurationModule::STATE_INVALID_DATABASE)
2453 return play(mk_voice(application_data_base_failed));
2454 else if (mk->configuration_module.state == ConfigurationModule::STATE_INVALID_AIRCRAFT_TYPE)
2455 return play(mk_voice(configuration_type_invalid));
2457 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
2461 return discrete_on(&mk_doutput(gpws_inop), 0.7);
2463 return sleep(0.7); // W/S INOP
2465 return discrete_on(&mk_doutput(tad_inop), 0.7);
2468 mk_doutput(gpws_inop) = false;
2469 // do not disable tad_inop since we must enable Terrain NA
2470 // do not disable W/S INOP since we do not emulate it
2471 return sleep(0.7); // Terrain NA
2475 mk_doutput(tad_inop) = false; // disable Terrain NA
2476 if (mk->io_handler.conf.momentary_flap_override_enabled)
2477 return discrete_on_off(&mk_doutput(flap_override), 1.0);
2480 return discrete_on_off(&mk_doutput(audio_on), 1.0);
2483 if (mk->io_handler.momentary_steep_approach_enabled())
2484 return discrete_on_off(&mk_doutput(steep_approach), 1.0);
2489 if (mk_dinput(glideslope_inhibit))
2490 goto glideslope_end;
2493 if (mk->fault_handler.faults[FaultHandler::FAULT_MODE5_INPUTS_INVALID])
2494 goto glideslope_inop;
2498 return discrete_on_off(&mk_doutput(gpws_alert), mk_voice(glideslope));
2500 return discrete_on_off(&mk_doutput(glideslope_cancel), 0.7);
2501 goto glideslope_end;
2504 return play(mk_voice(glideslope_inop));
2509 if (mk->fault_handler.faults[FaultHandler::FAULT_MODES14_INPUTS_INVALID])
2513 return discrete_on_off(&mk_doutput(gpws_warning), mk_voice(pull_up));
2517 return play(mk_voice(gpws_inop));
2522 if (mk_dinput(self_test)) // proceed to long self test
2527 if (mk->mode6_handler.conf.bank_angle_enabled
2528 && mk->fault_handler.faults[FaultHandler::FAULT_BANK_ANGLE_INPUTS_INVALID])
2529 return play(mk_voice(bank_angle_inop));
2533 if (mk->mode6_handler.altitude_callouts_enabled()
2534 && mk->fault_handler.faults[FaultHandler::FAULT_MODE6_INPUTS_INVALID])
2535 return play(mk_voice(callouts_inop));
2543 mk_doutput(gpws_inop) = true;
2544 // do not enable W/S INOP, since we do not emulate it
2545 mk_doutput(tad_inop) = true; // Terrain INOP, Terrain NA
2547 return play(mk_voice(sink_rate));
2550 return play(mk_voice(pull_up));
2552 return play(mk_voice(terrain));
2554 return play(mk_voice(pull_up));
2556 return play(mk_voice(dont_sink));
2558 return play(mk_voice(too_low_terrain));
2560 return play(mk->mode4_handler.conf.voice_too_low_gear);
2562 return play(mk_voice(too_low_flaps));
2564 return play(mk_voice(too_low_terrain));
2566 return play(mk_voice(glideslope));
2569 if (mk->mode6_handler.conf.bank_angle_enabled)
2570 return play(mk_voice(bank_angle));
2575 if (! mk->mode6_handler.altitude_callouts_enabled())
2576 goto callouts_disabled;
2580 if (mk->mode6_handler.conf.minimums_enabled)
2581 return play(mk_voice(minimums));
2585 if (mk->mode6_handler.conf.above_field_voice)
2586 return play(mk->mode6_handler.conf.above_field_voice);
2588 for (unsigned i = 0; i < n_altitude_callouts; i++)
2589 if (! was_here_offset(i))
2591 if (mk->mode6_handler.conf.altitude_callouts_enabled[i])
2592 return play(mk_altitude_voice(i));
2596 if (mk->mode6_handler.conf.smart_500_enabled)
2597 return play(mk_altitude_voice(Mode6Handler::ALTITUDE_CALLOUT_500));
2602 return play(mk_voice(minimums_minimums));
2607 if (mk->tcf_handler.conf.enabled)
2608 return play(mk_voice(too_low_terrain));
2615 MK_VIII::SelfTestHandler::start ()
2617 assert(state == STATE_START);
2619 memcpy(&saved_outputs, &mk->io_handler.outputs, sizeof(mk->io_handler.outputs));
2620 memset(&mk->io_handler.outputs, 0, sizeof(mk->io_handler.outputs));
2622 // [SPEC] 6.10.6: "The self-test results are annunciated, at 6db
2623 // lower than the normal audio level selected for the aircraft"
2624 mk->voice_player.set_volume(modify_amplitude(mk->voice_player.conf.volume, -6));
2626 if (mk_dinput(mode6_low_volume))
2627 mk->mode6_handler.set_volume(1.0);
2629 state = STATE_RUNNING;
2630 cancel = CANCEL_NONE;
2631 memset(&action, 0, sizeof(action));
2636 MK_VIII::SelfTestHandler::stop ()
2638 if (state != STATE_NONE)
2640 if (state != STATE_START)
2642 mk->voice_player.stop(VoicePlayer::STOP_NOW);
2643 mk->voice_player.set_volume(mk_dinput(audio_inhibit) ? 0.0 : mk->voice_player.conf.volume);
2645 if (mk_dinput(mode6_low_volume))
2646 mk->mode6_handler.set_volume(modify_amplitude(1.0, -6));
2648 memcpy(&mk->io_handler.outputs, &saved_outputs, sizeof(mk->io_handler.outputs));
2651 button_pressed = false;
2653 // reset self-test handler position
2659 MK_VIII::SelfTestHandler::handle_button_event (bool value)
2661 if (state == STATE_NONE)
2664 state = STATE_START;
2666 else if (state == STATE_START)
2669 state = STATE_NONE; // cancel the not-yet-started test
2671 else if (cancel == CANCEL_NONE)
2675 assert(! button_pressed);
2676 button_pressed = true;
2677 button_press_timestamp = globals->get_sim_time_sec();
2683 if (globals->get_sim_time_sec() - button_press_timestamp < 2)
2684 cancel = CANCEL_SHORT;
2686 cancel = CANCEL_LONG;
2693 MK_VIII::SelfTestHandler::update ()
2695 if (state == STATE_NONE)
2697 else if (state == STATE_START)
2699 if (mk->state_handler.ground && ! mk->io_handler.steep_approach())
2709 if (button_pressed && cancel == CANCEL_NONE)
2711 if (globals->get_sim_time_sec() - button_press_timestamp >= 2)
2712 cancel = CANCEL_LONG;
2716 if (! mk->state_handler.ground || cancel != CANCEL_NONE)
2722 if (test_bits(action.flags, ACTION_SLEEP))
2724 if (globals->get_sim_time_sec() - sleep_start < action.sleep_duration)
2727 if (test_bits(action.flags, ACTION_VOICE))
2729 if (mk->voice_player.voice)
2732 if (test_bits(action.flags, ACTION_DISCRETE_ON_OFF))
2733 *action.discrete = false;
2737 if (test_bits(action.flags, ACTION_SLEEP))
2738 sleep_start = globals->get_sim_time_sec();
2739 if (test_bits(action.flags, ACTION_DONE))
2748 ///////////////////////////////////////////////////////////////////////////////
2749 // AlertHandler ///////////////////////////////////////////////////////////////
2750 ///////////////////////////////////////////////////////////////////////////////
2753 MK_VIII::AlertHandler::select_voice_alerts (unsigned int test)
2755 if (has_alerts(test))
2757 voice_alerts = alerts & test;
2762 voice_alerts &= ~test;
2763 if (voice_alerts == 0)
2764 mk->voice_player.stop();
2771 MK_VIII::AlertHandler::boot ()
2777 MK_VIII::AlertHandler::reposition ()
2781 mk->io_handler.set_lamp(IOHandler::LAMP_NONE);
2782 mk->voice_player.stop(VoicePlayer::STOP_NOW);
2786 MK_VIII::AlertHandler::reset ()
2791 repeated_alerts = 0;
2792 altitude_callout_voice = NULL;
2796 MK_VIII::AlertHandler::update ()
2798 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
2801 // Lamps and voices are prioritized according to [SPEC] 6.9.2.
2803 // Voices which can interrupt other voices (VoicePlayer::PLAY_NOW)
2804 // are specified by [INSTALL] appendix E 5.3.5.
2806 // When a voice is overriden by a higher priority voice and the
2807 // overriding voice stops, we restore the previous voice if it was
2808 // looped (this is not specified by [SPEC] but seems to make sense).
2812 if (has_alerts(ALERT_MODE1_PULL_UP | ALERT_MODE2A | ALERT_MODE2B))
2813 mk->io_handler.set_lamp(IOHandler::LAMP_WARNING);
2814 else if (has_alerts(ALERT_MODE1_SINK_RATE
2815 | ALERT_MODE2A_PREFACE
2816 | ALERT_MODE2B_PREFACE
2817 | ALERT_MODE2A_ALTITUDE_GAIN_TERRAIN_CLOSING
2818 | ALERT_MODE2A_ALTITUDE_GAIN
2819 | ALERT_MODE2B_LANDING_MODE
2821 | ALERT_MODE4_TOO_LOW_FLAPS
2822 | ALERT_MODE4_TOO_LOW_GEAR
2823 | ALERT_MODE4AB_TOO_LOW_TERRAIN
2824 | ALERT_MODE4C_TOO_LOW_TERRAIN
2825 | ALERT_TCF_TOO_LOW_TERRAIN))
2826 mk->io_handler.set_lamp(IOHandler::LAMP_CAUTION);
2827 else if (has_alerts(ALERT_MODE5_SOFT | ALERT_MODE5_HARD))
2828 mk->io_handler.set_lamp(IOHandler::LAMP_GLIDESLOPE);
2830 mk->io_handler.set_lamp(IOHandler::LAMP_NONE);
2834 if (select_voice_alerts(ALERT_MODE1_PULL_UP))
2836 if (! has_old_alerts(ALERT_MODE1_PULL_UP))
2838 if (mk->voice_player.voice != mk_voice(sink_rate_pause_sink_rate))
2839 mk->voice_player.play(mk_voice(sink_rate), VoicePlayer::PLAY_NOW);
2840 mk->voice_player.play(mk_voice(pull_up), VoicePlayer::PLAY_LOOPED);
2843 else if (select_voice_alerts(ALERT_MODE2A_PREFACE | ALERT_MODE2B_PREFACE))
2845 if (! has_old_alerts(ALERT_MODE2A_PREFACE | ALERT_MODE2B_PREFACE))
2846 mk->voice_player.play(mk_voice(terrain_pause_terrain), VoicePlayer::PLAY_NOW);
2848 else if (select_voice_alerts(ALERT_MODE2A | ALERT_MODE2B))
2850 if (mk->voice_player.voice != mk_voice(pull_up))
2851 mk->voice_player.play(mk_voice(pull_up), VoicePlayer::PLAY_NOW | VoicePlayer::PLAY_LOOPED);
2853 else if (select_voice_alerts(ALERT_MODE2A_ALTITUDE_GAIN_TERRAIN_CLOSING | ALERT_MODE2B_LANDING_MODE))
2855 if (mk->voice_player.voice != mk_voice(terrain))
2856 mk->voice_player.play(mk_voice(terrain), VoicePlayer::PLAY_LOOPED);
2858 else if (select_voice_alerts(ALERT_MODE6_MINIMUMS))
2860 if (! has_old_alerts(ALERT_MODE6_MINIMUMS))
2861 mk->voice_player.play(mk_voice(minimums_minimums));
2863 else if (select_voice_alerts(ALERT_MODE4AB_TOO_LOW_TERRAIN | ALERT_MODE4C_TOO_LOW_TERRAIN))
2865 if (must_play_voice(ALERT_MODE4AB_TOO_LOW_TERRAIN | ALERT_MODE4C_TOO_LOW_TERRAIN))
2866 mk->voice_player.play(mk_voice(too_low_terrain));
2868 else if (select_voice_alerts(ALERT_TCF_TOO_LOW_TERRAIN))
2870 if (must_play_voice(ALERT_TCF_TOO_LOW_TERRAIN))
2871 mk->voice_player.play(mk_voice(too_low_terrain));
2873 else if (select_voice_alerts(ALERT_MODE6_ALTITUDE_CALLOUT))
2875 if (! has_old_alerts(ALERT_MODE6_ALTITUDE_CALLOUT))
2877 assert(altitude_callout_voice != NULL);
2878 mk->voice_player.play(altitude_callout_voice);
2879 altitude_callout_voice = NULL;
2882 else if (select_voice_alerts(ALERT_MODE4_TOO_LOW_GEAR))
2884 if (must_play_voice(ALERT_MODE4_TOO_LOW_GEAR))
2885 mk->voice_player.play(mk->mode4_handler.conf.voice_too_low_gear);
2887 else if (select_voice_alerts(ALERT_MODE4_TOO_LOW_FLAPS))
2889 if (must_play_voice(ALERT_MODE4_TOO_LOW_FLAPS))
2890 mk->voice_player.play(mk_voice(too_low_flaps));
2892 else if (select_voice_alerts(ALERT_MODE1_SINK_RATE))
2894 if (must_play_voice(ALERT_MODE1_SINK_RATE))
2895 mk->voice_player.play(mk_voice(sink_rate_pause_sink_rate));
2896 // [SPEC] 6.2.1: "During the time that the voice message for the
2897 // outer envelope is inhibited and the caution/warning lamp is
2898 // on, the Mode 5 alert message is allowed to annunciate for
2899 // excessive glideslope deviation conditions."
2900 else if (mk->voice_player.voice != mk_voice(sink_rate_pause_sink_rate)
2901 && mk->voice_player.next_voice != mk_voice(sink_rate_pause_sink_rate))
2903 if (has_alerts(ALERT_MODE5_HARD))
2905 voice_alerts |= ALERT_MODE5_HARD;
2906 if (mk->voice_player.voice != mk_voice(hard_glideslope))
2907 mk->voice_player.play(mk_voice(hard_glideslope), VoicePlayer::PLAY_LOOPED);
2909 else if (has_alerts(ALERT_MODE5_SOFT))
2911 voice_alerts |= ALERT_MODE5_SOFT;
2912 if (must_play_voice(ALERT_MODE5_SOFT))
2913 mk->voice_player.play(mk_voice(soft_glideslope));
2917 else if (select_voice_alerts(ALERT_MODE3))
2919 if (must_play_voice(ALERT_MODE3))
2920 mk->voice_player.play(mk_voice(dont_sink_pause_dont_sink));
2922 else if (select_voice_alerts(ALERT_MODE5_HARD))
2924 if (mk->voice_player.voice != mk_voice(hard_glideslope))
2925 mk->voice_player.play(mk_voice(hard_glideslope), VoicePlayer::PLAY_LOOPED);
2927 else if (select_voice_alerts(ALERT_MODE5_SOFT))
2929 if (must_play_voice(ALERT_MODE5_SOFT))
2930 mk->voice_player.play(mk_voice(soft_glideslope));
2932 else if (select_voice_alerts(ALERT_MODE6_LOW_BANK_ANGLE_3))
2934 if (mk->voice_player.voice != mk_voice(bank_angle_bank_angle_3))
2935 mk->voice_player.play(mk_voice(bank_angle_bank_angle_3), VoicePlayer::PLAY_LOOPED);
2937 else if (select_voice_alerts(ALERT_MODE6_HIGH_BANK_ANGLE_3))
2939 if (mk->voice_player.voice != mk_voice(bank_angle_pause_bank_angle_3))
2940 mk->voice_player.play(mk_voice(bank_angle_pause_bank_angle_3), VoicePlayer::PLAY_LOOPED);
2942 else if (select_voice_alerts(ALERT_MODE6_LOW_BANK_ANGLE_2))
2944 if (! has_old_alerts(ALERT_MODE6_LOW_BANK_ANGLE_2 | ALERT_MODE6_HIGH_BANK_ANGLE_2))
2945 mk->voice_player.play(mk_voice(bank_angle_bank_angle));
2947 else if (select_voice_alerts(ALERT_MODE6_HIGH_BANK_ANGLE_2))
2949 if (! has_old_alerts(ALERT_MODE6_LOW_BANK_ANGLE_2 | ALERT_MODE6_HIGH_BANK_ANGLE_2))
2950 mk->voice_player.play(mk_voice(bank_angle_pause_bank_angle));
2952 else if (select_voice_alerts(ALERT_MODE6_LOW_BANK_ANGLE_1))
2954 if (! has_old_alerts(ALERT_MODE6_LOW_BANK_ANGLE_1 | ALERT_MODE6_HIGH_BANK_ANGLE_1))
2955 mk->voice_player.play(mk_voice(bank_angle_bank_angle));
2957 else if (select_voice_alerts(ALERT_MODE6_HIGH_BANK_ANGLE_1))
2959 if (! has_old_alerts(ALERT_MODE6_LOW_BANK_ANGLE_1 | ALERT_MODE6_HIGH_BANK_ANGLE_1))
2960 mk->voice_player.play(mk_voice(bank_angle_pause_bank_angle));
2965 old_alerts = voice_alerts;
2966 repeated_alerts = 0;
2970 MK_VIII::AlertHandler::set_alerts (unsigned int _alerts,
2972 VoicePlayer::Voice *_altitude_callout_voice)
2975 if (test_bits(flags, ALERT_FLAG_REPEAT))
2976 repeated_alerts |= _alerts;
2977 if (_altitude_callout_voice)
2978 altitude_callout_voice = _altitude_callout_voice;
2982 MK_VIII::AlertHandler::unset_alerts (unsigned int _alerts)
2985 repeated_alerts &= ~_alerts;
2986 if (_alerts & ALERT_MODE6_ALTITUDE_CALLOUT)
2987 altitude_callout_voice = NULL;
2990 ///////////////////////////////////////////////////////////////////////////////
2991 // StateHandler ///////////////////////////////////////////////////////////////
2992 ///////////////////////////////////////////////////////////////////////////////
2995 MK_VIII::StateHandler::update_ground ()
2999 if (! mk_ainput(computed_airspeed).ncd && mk_ainput(computed_airspeed).get() > 60
3000 && ! mk_data(radio_altitude).ncd && mk_data(radio_altitude).get() > 30)
3002 if (potentially_airborne_timer.start_or_elapsed() > 1)
3006 potentially_airborne_timer.stop();
3010 if (! mk_ainput(computed_airspeed).ncd && mk_ainput(computed_airspeed).get() < 40
3011 && ! mk_data(radio_altitude).ncd && mk_data(radio_altitude).get() < 30)
3017 MK_VIII::StateHandler::enter_ground ()
3020 mk->io_handler.enter_ground();
3022 // [SPEC] 6.0.1 does not specify this, but it seems to be an
3023 // omission; at this point, we must make sure that we are in takeoff
3024 // mode (otherwise the next takeoff might happen in approach mode).
3030 MK_VIII::StateHandler::leave_ground ()
3033 mk->mode2_handler.leave_ground();
3037 MK_VIII::StateHandler::update_takeoff ()
3041 // [SPEC] 6.0.2 is somewhat confusing: it mentions hardcoded
3042 // terrain clearance (500, 750) and airspeed (178, 200) values,
3043 // but it also mentions the mode 4A boundary, which varies as a
3044 // function of aircraft type. We consider that the mentioned
3045 // values are examples, and that we should use the mode 4A upper
3048 if (! mk_data(terrain_clearance).ncd
3049 && ! mk_ainput(computed_airspeed).ncd
3050 && mk_data(terrain_clearance).get() > mk->mode4_handler.get_upper_agl(mk->mode4_handler.conf.modes->ac))
3055 if (! mk_data(radio_altitude).ncd
3056 && mk_data(radio_altitude).get() < mk->mode4_handler.conf.modes->b->min_agl1
3057 && mk->io_handler.flaps_down()
3058 && mk_dinput(landing_gear))
3064 MK_VIII::StateHandler::enter_takeoff ()
3067 mk->io_handler.enter_takeoff();
3068 mk->mode2_handler.enter_takeoff();
3069 mk->mode3_handler.enter_takeoff();
3070 mk->mode6_handler.enter_takeoff();
3074 MK_VIII::StateHandler::leave_takeoff ()
3077 mk->mode6_handler.leave_takeoff();
3081 MK_VIII::StateHandler::post_reposition ()
3083 // Synchronize the state with the current situation.
3086 (! mk_ainput(computed_airspeed).ncd && mk_ainput(computed_airspeed).get() > 60
3087 && ! mk_data(radio_altitude).ncd && mk_data(radio_altitude).get() > 30);
3089 bool _takeoff = _ground;
3091 if (ground && ! _ground)
3093 else if (! ground && _ground)
3096 if (takeoff && ! _takeoff)
3098 else if (! takeoff && _takeoff)
3103 MK_VIII::StateHandler::update ()
3105 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
3112 ///////////////////////////////////////////////////////////////////////////////
3113 // Mode1Handler ///////////////////////////////////////////////////////////////
3114 ///////////////////////////////////////////////////////////////////////////////
3117 MK_VIII::Mode1Handler::get_pull_up_bias ()
3119 // [PILOT] page 54: "Steep Approach has priority over Flap Override
3120 // if selected simultaneously."
3122 if (mk->io_handler.steep_approach())
3124 else if (conf.envelopes->flap_override_bias && mk->io_handler.flap_override())
3131 MK_VIII::Mode1Handler::is_pull_up ()
3133 if (! mk->io_handler.gpws_inhibit()
3134 && ! mk->state_handler.ground // [1]
3135 && ! mk_data(radio_altitude).ncd
3136 && ! mk_data(barometric_altitude_rate).ncd
3137 && mk_data(radio_altitude).get() > conf.envelopes->min_agl)
3139 if (mk_data(radio_altitude).get() < conf.envelopes->pull_up_max_agl1)
3141 if (mk_data(radio_altitude).get() < conf.envelopes->pull_up_min_agl1(mk_data(barometric_altitude_rate).get() + get_pull_up_bias()))
3144 else if (mk_data(radio_altitude).get() < conf.envelopes->pull_up_max_agl2)
3146 if (mk_data(radio_altitude).get() < conf.envelopes->pull_up_min_agl2(mk_data(barometric_altitude_rate).get() + get_pull_up_bias()))
3155 MK_VIII::Mode1Handler::update_pull_up ()
3159 if (! mk_test_alert(MODE1_PULL_UP))
3161 // [SPEC] 6.2.1: at least one sink rate must be issued
3162 // before switching to pull up; if no sink rate has
3163 // occurred, a 0.2 second delay is used.
3164 if (mk->voice_player.voice == mk_voice(sink_rate_pause_sink_rate)
3165 || pull_up_timer.start_or_elapsed() >= 0.2)
3166 mk_set_alerts(mk_alert(MODE1_PULL_UP));
3171 pull_up_timer.stop();
3172 mk_unset_alerts(mk_alert(MODE1_PULL_UP));
3177 MK_VIII::Mode1Handler::get_sink_rate_bias ()
3179 // [PILOT] page 54: "Steep Approach has priority over Flap Override
3180 // if selected simultaneously."
3182 if (mk->io_handler.steep_approach())
3184 else if (conf.envelopes->flap_override_bias && mk->io_handler.flap_override())
3186 else if (! mk_data(glideslope_deviation_dots).ncd)
3190 if (mk_data(glideslope_deviation_dots).get() <= -2)
3192 else if (mk_data(glideslope_deviation_dots).get() < 0)
3193 bias = -150 * mk_data(glideslope_deviation_dots).get();
3195 if (mk_data(radio_altitude).get() < 100)
3196 bias *= 0.01 * mk_data(radio_altitude).get();
3205 MK_VIII::Mode1Handler::is_sink_rate ()
3207 return ! mk->io_handler.gpws_inhibit()
3208 && ! mk->state_handler.ground // [1]
3209 && ! mk_data(radio_altitude).ncd
3210 && ! mk_data(barometric_altitude_rate).ncd
3211 && mk_data(radio_altitude).get() > conf.envelopes->min_agl
3212 && mk_data(radio_altitude).get() < 2450
3213 && mk_data(radio_altitude).get() < -572 - 0.6035 * (mk_data(barometric_altitude_rate).get() + get_sink_rate_bias());
3217 MK_VIII::Mode1Handler::get_sink_rate_tti ()
3219 return mk_data(radio_altitude).get() / fabs(mk_data(barometric_altitude_rate).get());
3223 MK_VIII::Mode1Handler::update_sink_rate ()
3227 if (mk_test_alert(MODE1_SINK_RATE))
3229 double tti = get_sink_rate_tti();
3230 if (tti <= sink_rate_tti * 0.8)
3232 // ~20% degradation, warn again and store the new reference tti
3233 sink_rate_tti = tti;
3234 mk_repeat_alert(mk_alert(MODE1_SINK_RATE));
3239 if (sink_rate_timer.start_or_elapsed() >= 0.8)
3241 mk_set_alerts(mk_alert(MODE1_SINK_RATE));
3242 sink_rate_tti = get_sink_rate_tti();
3248 sink_rate_timer.stop();
3249 mk_unset_alerts(mk_alert(MODE1_SINK_RATE));
3254 MK_VIII::Mode1Handler::update ()
3256 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
3263 ///////////////////////////////////////////////////////////////////////////////
3264 // Mode2Handler ///////////////////////////////////////////////////////////////
3265 ///////////////////////////////////////////////////////////////////////////////
3268 MK_VIII::Mode2Handler::ClosureRateFilter::limit_radio_altitude_rate (double r)
3270 // Limit radio altitude rate according to aircraft configuration,
3271 // allowing maximum sensitivity during cruise while providing
3272 // progressively less sensitivity during the landing phases of
3275 if (! mk_data(glideslope_deviation_dots).ncd && fabs(mk_data(glideslope_deviation_dots).get()) <= 2)
3276 { // gs deviation <= +- 2 dots
3277 if (mk_dinput(landing_gear) && mk->io_handler.flaps_down())
3278 SG_CLAMP_RANGE(r, -1000.0, 3000.0);
3279 else if (mk_dinput(landing_gear) || mk->io_handler.flaps_down())
3280 SG_CLAMP_RANGE(r, 0.0, 4000.0);
3282 SG_CLAMP_RANGE(r, 1000.0, 5000.0);
3285 { // no ILS, or gs deviation > +- 2 dots
3286 if (mk_dinput(landing_gear) && mk->io_handler.flaps_down())
3287 SG_CLAMP_RANGE(r, 0.0, 4000.0);
3288 else if (mk_dinput(landing_gear) || mk->io_handler.flaps_down())
3289 SG_CLAMP_RANGE(r, 1000.0, 5000.0);
3297 MK_VIII::Mode2Handler::ClosureRateFilter::init ()
3300 last_ra.set(&mk_data(radio_altitude));
3301 last_ba.set(&mk_ainput(uncorrected_barometric_altitude));
3308 MK_VIII::Mode2Handler::ClosureRateFilter::update ()
3310 double elapsed = timer.start_or_elapsed();
3314 if (! mk_data(radio_altitude).ncd && ! mk_ainput(uncorrected_barometric_altitude).ncd)
3316 if (! last_ra.ncd && ! last_ba.ncd)
3318 // compute radio and barometric altitude rates (positive value = descent)
3319 double ra_rate = -(mk_data(radio_altitude).get() - last_ra.get()) / elapsed * 60;
3320 double ba_rate = -(mk_ainput(uncorrected_barometric_altitude).get() - last_ba.get()) / elapsed * 60;
3322 // limit radio altitude rate according to aircraft configuration
3323 ra_rate = limit_radio_altitude_rate(ra_rate);
3325 // apply a low-pass filter to the radio altitude rate
3326 ra_rate = ra_filter.filter(ra_rate);
3327 // apply a high-pass filter to the barometric altitude rate
3328 ba_rate = ba_filter.filter(ba_rate);
3330 // combine both rates to obtain a closure rate
3331 output.set(ra_rate + ba_rate);
3344 last_ra.set(&mk_data(radio_altitude));
3345 last_ba.set(&mk_ainput(uncorrected_barometric_altitude));
3349 MK_VIII::Mode2Handler::b_conditions ()
3351 return mk->io_handler.flaps_down()
3352 || (! mk_data(glideslope_deviation_dots).ncd && fabs(mk_data(glideslope_deviation_dots).get()) < 2)
3353 || takeoff_timer.running;
3357 MK_VIII::Mode2Handler::is_a ()
3359 if (! mk->io_handler.gpws_inhibit()
3360 && ! mk->state_handler.ground // [1]
3361 && ! mk_data(radio_altitude).ncd
3362 && ! mk_ainput(computed_airspeed).ncd
3363 && ! closure_rate_filter.output.ncd
3364 && ! b_conditions())
3366 if (mk_data(radio_altitude).get() < 1220)
3368 if (mk_data(radio_altitude).get() < -1579 + 0.7895 * closure_rate_filter.output.get())
3375 if (mk_ainput(computed_airspeed).get() <= conf->airspeed1)
3377 else if (mk_ainput(computed_airspeed).get() >= conf->airspeed2)
3380 upper_limit = 1650 + 8.9 * (mk_ainput(computed_airspeed).get() - conf->airspeed1);
3382 if (mk_data(radio_altitude).get() < upper_limit)
3384 if (mk_data(radio_altitude).get() < 522 + 0.1968 * closure_rate_filter.output.get())
3394 MK_VIII::Mode2Handler::is_b ()
3396 if (! mk->io_handler.gpws_inhibit()
3397 && ! mk->state_handler.ground // [1]
3398 && ! mk_data(radio_altitude).ncd
3399 && ! mk_data(barometric_altitude_rate).ncd
3400 && ! closure_rate_filter.output.ncd
3402 && mk_data(radio_altitude).get() < 789)
3406 if (mk->io_handler.flaps_down())
3408 if (mk_data(barometric_altitude_rate).get() > -400)
3410 else if (mk_data(barometric_altitude_rate).get() < -1000)
3413 lower_limit = -66.777 - 0.667 * mk_data(barometric_altitude_rate).get();
3418 if (mk_data(radio_altitude).get() > lower_limit)
3420 if (mk_data(radio_altitude).get() < -1579 + 0.7895 * closure_rate_filter.output.get())
3429 MK_VIII::Mode2Handler::check_pull_up (unsigned int preface_alert,
3432 if (pull_up_timer.running)
3434 if (pull_up_timer.elapsed() >= 1)
3436 mk_unset_alerts(preface_alert);
3437 mk_set_alerts(alert);
3442 if (! mk->voice_player.voice)
3443 pull_up_timer.start();
3448 MK_VIII::Mode2Handler::update_a ()
3452 if (mk_test_alert(MODE2A_PREFACE))
3453 check_pull_up(mk_alert(MODE2A_PREFACE), mk_alert(MODE2A));
3454 else if (! mk_test_alert(MODE2A))
3456 mk_unset_alerts(mk_alert(MODE2A_ALTITUDE_GAIN) | mk_alert(MODE2A_ALTITUDE_GAIN_TERRAIN_CLOSING));
3457 mk_set_alerts(mk_alert(MODE2A_PREFACE));
3458 a_start_time = globals->get_sim_time_sec();
3459 pull_up_timer.stop();
3464 if (mk_test_alert(MODE2A_ALTITUDE_GAIN))
3466 if (mk->io_handler.gpws_inhibit()
3467 || mk->state_handler.ground // [1]
3468 || a_altitude_gain_timer.elapsed() >= 45
3469 || mk_data(radio_altitude).ncd
3470 || mk_ainput(uncorrected_barometric_altitude).ncd
3471 || mk_ainput(uncorrected_barometric_altitude).get() - a_altitude_gain_alt >= 300
3472 // [PILOT] page 12: "the visual alert will remain on
3473 // until [...] landing flaps or the flap override switch
3475 || mk->io_handler.flaps_down())
3477 // exit altitude gain mode
3478 a_altitude_gain_timer.stop();
3479 mk_unset_alerts(mk_alert(MODE2A_ALTITUDE_GAIN) | mk_alert(MODE2A_ALTITUDE_GAIN_TERRAIN_CLOSING));
3483 // [SPEC] 6.2.2.2: "If the terrain starts to fall away
3484 // during this altitude gain time, the voice will shut
3486 if (closure_rate_filter.output.get() < 0)
3487 mk_unset_alerts(mk_alert(MODE2A_ALTITUDE_GAIN_TERRAIN_CLOSING));
3490 else if (mk_test_alerts(mk_alert(MODE2A_PREFACE) | mk_alert(MODE2A)))
3492 mk_unset_alerts(mk_alert(MODE2A_PREFACE) | mk_alert(MODE2A));
3494 if (! mk->io_handler.gpws_inhibit()
3495 && ! mk->state_handler.ground // [1]
3496 && globals->get_sim_time_sec() - a_start_time > 3
3497 && ! mk->io_handler.flaps_down()
3498 && ! mk_data(radio_altitude).ncd
3499 && ! mk_ainput(uncorrected_barometric_altitude).ncd)
3501 // [SPEC] 6.2.2.2: mode 2A envelope violated for more
3502 // than 3 seconds, enable altitude gain feature
3504 a_altitude_gain_timer.start();
3505 a_altitude_gain_alt = mk_ainput(uncorrected_barometric_altitude).get();
3507 unsigned int alerts = mk_alert(MODE2A_ALTITUDE_GAIN);
3508 if (closure_rate_filter.output.get() > 0)
3509 alerts |= mk_alert(MODE2A_ALTITUDE_GAIN_TERRAIN_CLOSING);
3511 mk_set_alerts(alerts);
3518 MK_VIII::Mode2Handler::update_b ()
3522 // handle normal mode
3524 if (b && (! mk_dinput(landing_gear) || ! mk->io_handler.flaps_down()))
3526 if (mk_test_alert(MODE2B_PREFACE))
3527 check_pull_up(mk_alert(MODE2B_PREFACE), mk_alert(MODE2B));
3528 else if (! mk_test_alert(MODE2B))
3530 mk_set_alerts(mk_alert(MODE2B_PREFACE));
3531 pull_up_timer.stop();
3535 mk_unset_alerts(mk_alert(MODE2B_PREFACE) | mk_alert(MODE2B));
3537 // handle landing mode ([SPEC] 6.2.2.3: "If both landing gear and
3538 // flaps are in the landing configuration, then the message will be
3541 if (b && mk_dinput(landing_gear) && mk->io_handler.flaps_down())
3542 mk_set_alerts(mk_alert(MODE2B_LANDING_MODE));
3544 mk_unset_alerts(mk_alert(MODE2B_LANDING_MODE));
3548 MK_VIII::Mode2Handler::boot ()
3550 closure_rate_filter.init();
3554 MK_VIII::Mode2Handler::power_off ()
3556 // [SPEC] 6.0.4: "This latching function is not power saved and a
3557 // system reset will force it false."
3558 takeoff_timer.stop();
3562 MK_VIII::Mode2Handler::leave_ground ()
3564 // takeoff, reset timer
3565 takeoff_timer.start();
3569 MK_VIII::Mode2Handler::enter_takeoff ()
3571 // reset timer, in case it's a go-around
3572 takeoff_timer.start();
3576 MK_VIII::Mode2Handler::update ()
3578 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
3581 closure_rate_filter.update();
3583 if (takeoff_timer.running && takeoff_timer.elapsed() >= 60)
3584 takeoff_timer.stop();
3590 ///////////////////////////////////////////////////////////////////////////////
3591 // Mode3Handler ///////////////////////////////////////////////////////////////
3592 ///////////////////////////////////////////////////////////////////////////////
3595 MK_VIII::Mode3Handler::max_alt_loss (double _bias)
3597 return conf->max_alt_loss(mk->io_handler.flap_override(), mk_data(radio_altitude).get()) + mk_data(radio_altitude).get() * _bias;
3601 MK_VIII::Mode3Handler::get_bias (double initial_bias, double alt_loss)
3603 // do not repeat altitude-loss alerts below 30ft agl
3604 if (mk_data(radio_altitude).get() > 30)
3606 if (initial_bias < 0.0) // sanity check
3608 // mk-viii spec: repeat alerts whenever losing 20% of initial altitude
3609 while ((alt_loss > max_alt_loss(initial_bias))&&
3610 (initial_bias < 1.0))
3611 initial_bias += 0.2;
3614 return initial_bias;
3618 MK_VIII::Mode3Handler::is (double *alt_loss)
3620 if (has_descent_alt)
3622 int max_agl = conf->max_agl(mk->io_handler.flap_override());
3624 if (mk_ainput(uncorrected_barometric_altitude).ncd
3625 || mk_ainput(uncorrected_barometric_altitude).get() > descent_alt
3626 || mk_data(radio_altitude).ncd
3627 || mk_data(radio_altitude).get() > max_agl)
3630 has_descent_alt = false;
3634 // gear/flaps: [SPEC] 1.3.1.3
3635 if (! mk->io_handler.gpws_inhibit()
3636 && ! mk->state_handler.ground // [1]
3637 && (! mk_dinput(landing_gear) || ! mk->io_handler.flaps_down())
3638 && ! mk_data(barometric_altitude_rate).ncd
3639 && ! mk_ainput(uncorrected_barometric_altitude).ncd
3640 && ! mk_data(radio_altitude).ncd
3641 && mk_data(barometric_altitude_rate).get() < 0)
3643 double _alt_loss = descent_alt - mk_ainput(uncorrected_barometric_altitude).get();
3644 if (armed || (mk_data(radio_altitude).get() > conf->min_agl
3645 && mk_data(radio_altitude).get() < max_agl
3646 && _alt_loss > max_alt_loss(0)))
3648 *alt_loss = _alt_loss;
3656 if (! mk_data(barometric_altitude_rate).ncd
3657 && ! mk_ainput(uncorrected_barometric_altitude).ncd
3658 && mk_data(barometric_altitude_rate).get() < 0)
3660 has_descent_alt = true;
3661 descent_alt = mk_ainput(uncorrected_barometric_altitude).get();
3669 MK_VIII::Mode3Handler::enter_takeoff ()
3672 has_descent_alt = false;
3676 MK_VIII::Mode3Handler::update ()
3678 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
3681 if (mk->state_handler.takeoff)
3685 if (! mk->state_handler.ground /* [1] */ && is(&alt_loss))
3687 if (mk_test_alert(MODE3))
3689 double new_bias = get_bias(bias, alt_loss);
3690 if (new_bias > bias)
3693 mk_repeat_alert(mk_alert(MODE3));
3699 bias = get_bias(0.2, alt_loss);
3700 mk_set_alerts(mk_alert(MODE3));
3707 mk_unset_alerts(mk_alert(MODE3));
3710 ///////////////////////////////////////////////////////////////////////////////
3711 // Mode4Handler ///////////////////////////////////////////////////////////////
3712 ///////////////////////////////////////////////////////////////////////////////
3714 // FIXME: for turbofans, the upper limit should be reduced from 1000
3715 // to 800 ft if a sudden change in radio altitude is detected, in
3716 // order to reduce nuisance alerts caused by overflying another
3717 // aircraft (see [PILOT] page 16).
3720 MK_VIII::Mode4Handler::get_upper_agl (const EnvelopesConfiguration *c)
3722 if (mk_ainput(computed_airspeed).get() >= c->airspeed2)
3724 else if (mk_ainput(computed_airspeed).get() >= c->airspeed1)
3725 return c->min_agl2(mk_ainput(computed_airspeed).get());
3730 const MK_VIII::Mode4Handler::EnvelopesConfiguration *
3731 MK_VIII::Mode4Handler::get_ab_envelope ()
3733 // [SPEC] 6.2.4.1: "The Mode 4B envelope can also be selected by
3734 // setting flaps to landing configuration or by selecting flap
3736 return mk_dinput(landing_gear) || mk->io_handler.flaps_down()
3742 MK_VIII::Mode4Handler::get_bias (double initial_bias, double min_agl)
3744 // do not repeat terrain/gear/flap alerts below 30ft agl
3745 if (mk_data(radio_altitude).get() > 30.0)
3747 if (initial_bias < 0.0) // sanity check
3749 while ((mk_data(radio_altitude).get() < min_agl - min_agl * initial_bias)&&
3750 (initial_bias < 1.0))
3751 initial_bias += 0.2;
3754 return initial_bias;
3758 MK_VIII::Mode4Handler::handle_alert (unsigned int alert,
3762 if (mk_test_alerts(alert))
3764 double new_bias = get_bias(*bias, min_agl);
3765 if (new_bias > *bias)
3768 mk_repeat_alert(alert);
3773 *bias = get_bias(0.2, min_agl);
3774 mk_set_alerts(alert);
3779 MK_VIII::Mode4Handler::update_ab ()
3781 if (! mk->io_handler.gpws_inhibit()
3782 && ! mk->state_handler.ground
3783 && ! mk->state_handler.takeoff
3784 && ! mk_data(radio_altitude).ncd
3785 && ! mk_ainput(computed_airspeed).ncd
3786 && mk_data(radio_altitude).get() > 30)
3788 const EnvelopesConfiguration *c = get_ab_envelope();
3789 if (mk_ainput(computed_airspeed).get() < c->airspeed1)
3791 if (mk_dinput(landing_gear))
3793 if (! mk->io_handler.flaps_down() && mk_data(radio_altitude).get() < c->min_agl1)
3795 handle_alert(mk_alert(MODE4_TOO_LOW_FLAPS), c->min_agl1, &ab_bias);
3801 if (mk_data(radio_altitude).get() < c->min_agl1)
3803 handle_alert(mk_alert(MODE4_TOO_LOW_GEAR), c->min_agl1, &ab_bias);
3810 mk_unset_alerts(mk_alert(MODE4_TOO_LOW_FLAPS) | mk_alert(MODE4_TOO_LOW_GEAR));
3815 MK_VIII::Mode4Handler::update_ab_expanded ()
3817 if (! mk->io_handler.gpws_inhibit()
3818 && ! mk->state_handler.ground
3819 && ! mk->state_handler.takeoff
3820 && ! mk_data(radio_altitude).ncd
3821 && ! mk_ainput(computed_airspeed).ncd
3822 && mk_data(radio_altitude).get() > 30)
3824 const EnvelopesConfiguration *c = get_ab_envelope();
3825 if (mk_ainput(computed_airspeed).get() >= c->airspeed1)
3827 double min_agl = get_upper_agl(c);
3828 if (mk_data(radio_altitude).get() < min_agl)
3830 handle_alert(mk_alert(MODE4AB_TOO_LOW_TERRAIN), min_agl, &ab_expanded_bias);
3836 mk_unset_alerts(mk_alert(MODE4AB_TOO_LOW_TERRAIN));
3837 ab_expanded_bias=0.0;
3841 MK_VIII::Mode4Handler::update_c ()
3843 if (! mk->io_handler.gpws_inhibit()
3844 && ! mk->state_handler.ground // [1]
3845 && mk->state_handler.takeoff
3846 && ! mk_data(radio_altitude).ncd
3847 && ! mk_data(terrain_clearance).ncd
3848 && mk_data(radio_altitude).get() > 30
3849 && (! mk_dinput(landing_gear) || ! mk->io_handler.flaps_down())
3850 && mk_data(radio_altitude).get() < get_upper_agl(conf.modes->ac)
3851 && mk_data(radio_altitude).get() < mk_data(terrain_clearance).get())
3852 handle_alert(mk_alert(MODE4C_TOO_LOW_TERRAIN), mk_data(terrain_clearance).get(), &c_bias);
3855 mk_unset_alerts(mk_alert(MODE4C_TOO_LOW_TERRAIN));
3861 MK_VIII::Mode4Handler::update ()
3863 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
3867 update_ab_expanded();
3871 ///////////////////////////////////////////////////////////////////////////////
3872 // Mode5Handler ///////////////////////////////////////////////////////////////
3873 ///////////////////////////////////////////////////////////////////////////////
3876 MK_VIII::Mode5Handler::is_hard ()
3878 if (mk_data(radio_altitude).get() > 30
3879 && mk_data(radio_altitude).get() < 300
3880 && mk_data(glideslope_deviation_dots).get() > 2)
3882 if (mk_data(radio_altitude).get() < 150)
3884 if (mk_data(radio_altitude).get() > 293 - 71.43 * mk_data(glideslope_deviation_dots).get())
3895 MK_VIII::Mode5Handler::is_soft (double bias)
3897 // do not repeat glide-slope alerts below 30ft agl
3898 if (mk_data(radio_altitude).get() > 30)
3900 double bias_dots = 1.3 * bias;
3901 if (mk_data(glideslope_deviation_dots).get() > 1.3 + bias_dots)
3903 if (mk_data(radio_altitude).get() < 150)
3905 if (mk_data(radio_altitude).get() > 243 - 71.43 * (mk_data(glideslope_deviation_dots).get() - bias_dots))
3912 if (mk_data(barometric_altitude_rate).ncd)
3916 if (mk_data(barometric_altitude_rate).get() >= 0)
3918 else if (mk_data(barometric_altitude_rate).get() < -500)
3921 upper_limit = -mk_data(barometric_altitude_rate).get() + 500;
3924 if (mk_data(radio_altitude).get() < upper_limit)
3934 MK_VIII::Mode5Handler::get_soft_bias (double initial_bias)
3936 if (initial_bias < 0.0) // sanity check
3938 while ((is_soft(initial_bias))&&
3939 (initial_bias < 1.0))
3940 initial_bias += 0.2;
3942 return initial_bias;
3946 MK_VIII::Mode5Handler::update_hard (bool is)
3950 if (! mk_test_alert(MODE5_HARD))
3952 if (hard_timer.start_or_elapsed() >= 0.8)
3953 mk_set_alerts(mk_alert(MODE5_HARD));
3959 mk_unset_alerts(mk_alert(MODE5_HARD));
3964 MK_VIII::Mode5Handler::update_soft (bool is)
3968 if (! mk_test_alert(MODE5_SOFT))
3970 double new_bias = get_soft_bias(soft_bias);
3971 if (new_bias > soft_bias)
3973 soft_bias = new_bias;
3974 mk_repeat_alert(mk_alert(MODE5_SOFT));
3979 if (soft_timer.start_or_elapsed() >= 0.8)
3981 soft_bias = get_soft_bias(0.2);
3982 mk_set_alerts(mk_alert(MODE5_SOFT));
3989 mk_unset_alerts(mk_alert(MODE5_SOFT));
3994 MK_VIII::Mode5Handler::update ()
3996 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
3999 if (! mk->io_handler.gpws_inhibit()
4000 && ! mk->state_handler.ground // [1]
4001 && ! mk_dinput(glideslope_inhibit) // not on backcourse
4002 && ! mk_data(radio_altitude).ncd
4003 && ! mk_data(glideslope_deviation_dots).ncd
4004 && (! mk->io_handler.conf.localizer_enabled
4005 || mk_data(localizer_deviation_dots).ncd
4006 || mk_data(radio_altitude).get() < 500
4007 || fabs(mk_data(localizer_deviation_dots).get()) < 2)
4008 && (! mk->state_handler.takeoff || mk->io_handler.flaps_down())
4009 && mk_dinput(landing_gear)
4010 && ! mk_doutput(glideslope_cancel))
4012 update_hard(is_hard());
4013 update_soft(is_soft(0));
4022 ///////////////////////////////////////////////////////////////////////////////
4023 // Mode6Handler ///////////////////////////////////////////////////////////////
4024 ///////////////////////////////////////////////////////////////////////////////
4026 // keep sorted in descending order
4027 const int MK_VIII::Mode6Handler::altitude_callout_definitions[] =
4028 { 1000, 500, 400, 300, 200, 100, 50, 40, 30, 20, 10 };
4031 MK_VIII::Mode6Handler::reset_minimums ()
4033 minimums_issued = false;
4037 MK_VIII::Mode6Handler::reset_altitude_callouts ()
4039 for (unsigned i = 0; i < n_altitude_callouts; i++)
4040 altitude_callouts_issued[i] = false;
4044 MK_VIII::Mode6Handler::is_playing_altitude_callout ()
4046 for (unsigned i = 0; i < n_altitude_callouts; i++)
4047 if (mk->voice_player.voice == mk_altitude_voice(i)
4048 || mk->voice_player.next_voice == mk_altitude_voice(i))
4055 MK_VIII::Mode6Handler::is_near_minimums (double callout)
4059 if (! mk_data(decision_height).ncd)
4061 double diff = callout - mk_data(decision_height).get();
4063 if (mk_data(radio_altitude).get() >= 200)
4065 if (fabs(diff) <= 30)
4070 if (diff >= -3 && diff <= 6)
4079 MK_VIII::Mode6Handler::is_outside_band (double elevation, double callout)
4082 return elevation < callout - (elevation > 150 ? 20 : 10);
4086 MK_VIII::Mode6Handler::inhibit_smart_500 ()
4090 if (! mk_data(glideslope_deviation_dots).ncd
4091 && ! mk_dinput(glideslope_inhibit) // backcourse
4092 && ! mk_doutput(glideslope_cancel))
4094 if (mk->io_handler.conf.localizer_enabled
4095 && ! mk_data(localizer_deviation_dots).ncd)
4097 if (fabs(mk_data(localizer_deviation_dots).get()) <= 2)
4102 if (fabs(mk_data(glideslope_deviation_dots).get()) <= 2)
4111 MK_VIII::Mode6Handler::boot ()
4113 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
4116 last_decision_height = mk_dinput(decision_height);
4117 last_radio_altitude.set(&mk_data(radio_altitude));
4120 for (unsigned i = 0; i < n_altitude_callouts; i++)
4121 altitude_callouts_issued[i] = ! mk_data(radio_altitude).ncd
4122 && mk_data(radio_altitude).get() <= altitude_callout_definitions[i];
4124 // extrapolated from [SPEC] 6.4.2
4125 minimums_issued = mk_dinput(decision_height);
4127 if (conf.above_field_voice)
4130 get_altitude_above_field(&last_altitude_above_field);
4131 // extrapolated from [SPEC] 6.4.2
4132 above_field_issued = ! last_altitude_above_field.ncd
4133 && last_altitude_above_field.get() < 550;
4138 MK_VIII::Mode6Handler::power_off ()
4140 runway_timer.stop();
4144 MK_VIII::Mode6Handler::enter_takeoff ()
4146 reset_altitude_callouts(); // [SPEC] 6.4.2
4147 reset_minimums(); // omitted by [SPEC]; common sense
4151 MK_VIII::Mode6Handler::leave_takeoff ()
4153 reset_altitude_callouts(); // [SPEC] 6.0.2
4154 reset_minimums(); // [SPEC] 6.0.2
4158 MK_VIII::Mode6Handler::set_volume (float volume)
4160 mk_voice(minimums_minimums)->set_volume(volume);
4161 mk_voice(five_hundred_above)->set_volume(volume);
4162 for (unsigned i = 0; i < n_altitude_callouts; i++)
4163 mk_altitude_voice(i)->set_volume(volume);
4167 MK_VIII::Mode6Handler::altitude_callouts_enabled ()
4169 if (conf.minimums_enabled || conf.smart_500_enabled || conf.above_field_voice)
4172 for (unsigned i = 0; i < n_altitude_callouts; i++)
4173 if (conf.altitude_callouts_enabled[i])
4180 MK_VIII::Mode6Handler::update_minimums ()
4182 if (! mk->io_handler.gpws_inhibit()
4183 && (mk->voice_player.voice == mk_voice(minimums_minimums)
4184 || mk->voice_player.next_voice == mk_voice(minimums_minimums)))
4187 if (! mk->io_handler.gpws_inhibit()
4188 && ! mk->state_handler.ground // [1]
4189 && conf.minimums_enabled
4190 && ! minimums_issued
4191 && mk_dinput(landing_gear)
4192 && mk_dinput(decision_height)
4193 && ! last_decision_height)
4195 minimums_issued = true;
4197 // If an altitude callout is playing, stop it now so that the
4198 // minimums callout can be played (note that proper connection
4199 // and synchronization of the radio-altitude ARINC 529 input,
4200 // decision-height discrete and decision-height ARINC 529 input
4201 // will prevent an altitude callout from being played near the
4202 // decision height).
4204 if (is_playing_altitude_callout())
4205 mk->voice_player.stop(VoicePlayer::STOP_NOW);
4207 mk_set_alerts(mk_alert(MODE6_MINIMUMS));
4210 mk_unset_alerts(mk_alert(MODE6_MINIMUMS));
4213 last_decision_height = mk_dinput(decision_height);
4217 MK_VIII::Mode6Handler::update_altitude_callouts ()
4219 if (! mk->io_handler.gpws_inhibit() && is_playing_altitude_callout())
4222 if (! mk->io_handler.gpws_inhibit()
4223 && ! mk->state_handler.ground // [1]
4224 && ! mk_data(radio_altitude).ncd)
4225 for (unsigned i = 0; i < n_altitude_callouts && mk_data(radio_altitude).get() <= altitude_callout_definitions[i]; i++)
4226 if ((conf.altitude_callouts_enabled[i]
4227 || (altitude_callout_definitions[i] == 500
4228 && conf.smart_500_enabled))
4229 && ! altitude_callouts_issued[i]
4230 && (last_radio_altitude.ncd
4231 || last_radio_altitude.get() > altitude_callout_definitions[i]))
4233 // lock out all callouts superior or equal to this one
4234 for (unsigned j = 0; j <= i; j++)
4235 altitude_callouts_issued[j] = true;
4237 altitude_callouts_issued[i] = true;
4238 if (! is_near_minimums(altitude_callout_definitions[i])
4239 && ! is_outside_band(mk_data(radio_altitude).get(), altitude_callout_definitions[i])
4240 && (! conf.smart_500_enabled
4241 || altitude_callout_definitions[i] != 500
4242 || ! inhibit_smart_500()))
4244 mk->alert_handler.set_altitude_callout_alert(mk_altitude_voice(i));
4249 mk_unset_alerts(mk_alert(MODE6_ALTITUDE_CALLOUT));
4252 last_radio_altitude.set(&mk_data(radio_altitude));
4256 MK_VIII::Mode6Handler::test_runway (const FGRunway *_runway)
4258 if (_runway->lengthFt() < mk->conf.runway_database)
4262 SGGeod::fromDeg(mk_data(gps_longitude).get(), mk_data(gps_latitude).get()));
4264 // get distance to threshold
4265 double distance, az1, az2;
4266 SGGeodesy::inverse(pos, _runway->threshold(), az1, az2, distance);
4267 return distance * SG_METER_TO_NM <= 5;
4271 MK_VIII::Mode6Handler::test_airport (const FGAirport *airport)
4273 for (unsigned int r=0; r<airport->numRunways(); ++r) {
4274 FGRunway* rwy(airport->getRunwayByIndex(r));
4276 if (test_runway(rwy)) return true;
4282 bool MK_VIII::Mode6Handler::AirportFilter::passAirport(FGAirport* a) const
4284 bool ok = self->test_airport(a);
4289 MK_VIII::Mode6Handler::update_runway ()
4292 if (mk_data(gps_latitude).ncd || mk_data(gps_longitude).ncd) {
4297 // Search for the closest runway threshold in range 5
4298 // nm. Passing 30nm to
4299 // get_closest_airport() provides enough margin for large
4300 // airports, which may have a runway located far away from the
4301 // airport's reference point.
4302 AirportFilter filter(this);
4303 FGPositionedRef apt = FGPositioned::findClosest(
4304 SGGeod::fromDeg(mk_data(gps_longitude).get(), mk_data(gps_latitude).get()),
4307 runway.elevation = apt->elevation();
4310 has_runway.set(apt != NULL);
4314 MK_VIII::Mode6Handler::get_altitude_above_field (Parameter<double> *parameter)
4316 if (! has_runway.ncd && has_runway.get() && ! mk_data(geometric_altitude).ncd)
4317 parameter->set(mk_data(geometric_altitude).get() - runway.elevation);
4323 MK_VIII::Mode6Handler::update_above_field_callout ()
4325 if (! conf.above_field_voice)
4328 // update_runway() has to iterate over the whole runway database
4329 // (which contains thousands of runways), so it would be unwise to
4330 // run it every frame. Run it every 3 seconds. Note that the first
4331 // iteration is run in boot().
4332 if (runway_timer.start_or_elapsed() >= 3)
4335 runway_timer.start();
4338 Parameter<double> altitude_above_field;
4339 get_altitude_above_field(&altitude_above_field);
4341 if (! mk->io_handler.gpws_inhibit()
4342 && (mk->voice_player.voice == conf.above_field_voice
4343 || mk->voice_player.next_voice == conf.above_field_voice))
4346 // handle reset term
4347 if (above_field_issued)
4349 if ((! has_runway.ncd && ! has_runway.get())
4350 || (! altitude_above_field.ncd && altitude_above_field.get() > 700))
4351 above_field_issued = false;
4354 if (! mk->io_handler.gpws_inhibit()
4355 && ! mk->state_handler.ground // [1]
4356 && ! above_field_issued
4357 && ! altitude_above_field.ncd
4358 && altitude_above_field.get() < 550
4359 && (last_altitude_above_field.ncd
4360 || last_altitude_above_field.get() >= 550))
4362 above_field_issued = true;
4364 if (! is_outside_band(altitude_above_field.get(), 500))
4366 mk->alert_handler.set_altitude_callout_alert(conf.above_field_voice);
4371 mk_unset_alerts(mk_alert(MODE6_ALTITUDE_CALLOUT));
4374 last_altitude_above_field.set(&altitude_above_field);
4378 MK_VIII::Mode6Handler::is_bank_angle (double abs_roll_angle, double bias)
4380 return conf.is_bank_angle(&mk_data(radio_altitude),
4381 abs_roll_angle - abs_roll_angle * bias,
4382 mk_dinput(autopilot_engaged));
4386 MK_VIII::Mode6Handler::is_high_bank_angle ()
4388 return mk_data(radio_altitude).ncd || mk_data(radio_altitude).get() >= 210;
4392 MK_VIII::Mode6Handler::get_bank_angle_alerts ()
4394 if (! mk->io_handler.gpws_inhibit()
4395 && ! mk->state_handler.ground // [1]
4396 && ! mk_data(roll_angle).ncd)
4398 double abs_roll_angle = fabs(mk_data(roll_angle).get());
4400 if (is_bank_angle(abs_roll_angle, 0.4))
4401 return is_high_bank_angle()
4402 ? (mk_alert(MODE6_HIGH_BANK_ANGLE_1) | mk_alert(MODE6_HIGH_BANK_ANGLE_2) | mk_alert(MODE6_HIGH_BANK_ANGLE_3))
4403 : (mk_alert(MODE6_LOW_BANK_ANGLE_1) | mk_alert(MODE6_LOW_BANK_ANGLE_2) | mk_alert(MODE6_LOW_BANK_ANGLE_3));
4404 else if (is_bank_angle(abs_roll_angle, 0.2))
4405 return is_high_bank_angle()
4406 ? (mk_alert(MODE6_HIGH_BANK_ANGLE_1) | mk_alert(MODE6_HIGH_BANK_ANGLE_2))
4407 : (mk_alert(MODE6_LOW_BANK_ANGLE_1) | mk_alert(MODE6_LOW_BANK_ANGLE_2));
4408 else if (is_bank_angle(abs_roll_angle, 0))
4409 return is_high_bank_angle()
4410 ? mk_alert(MODE6_HIGH_BANK_ANGLE_1)
4411 : mk_alert(MODE6_LOW_BANK_ANGLE_1);
4418 MK_VIII::Mode6Handler::update_bank_angle ()
4420 if (! conf.bank_angle_enabled)
4423 unsigned int alerts = get_bank_angle_alerts();
4425 // [SPEC] 6.4.4 specifies that the non-continuous alerts
4426 // (MODE6_*_BANK_ANGLE_1 and MODE6_*_BANK_ANGLE_2) are locked out
4427 // until the initial envelope is exited.
4429 if (! test_bits(alerts, mk_alert(MODE6_LOW_BANK_ANGLE_3)))
4430 mk_unset_alerts(mk_alert(MODE6_LOW_BANK_ANGLE_3));
4431 if (! test_bits(alerts, mk_alert(MODE6_HIGH_BANK_ANGLE_3)))
4432 mk_unset_alerts(mk_alert(MODE6_HIGH_BANK_ANGLE_3));
4435 mk_set_alerts(alerts);
4437 mk_unset_alerts(mk_alert(MODE6_LOW_BANK_ANGLE_1)
4438 | mk_alert(MODE6_HIGH_BANK_ANGLE_1)
4439 | mk_alert(MODE6_LOW_BANK_ANGLE_2)
4440 | mk_alert(MODE6_HIGH_BANK_ANGLE_2));
4444 MK_VIII::Mode6Handler::update ()
4446 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
4449 if (! mk->state_handler.takeoff
4450 && ! mk_data(radio_altitude).ncd
4451 && mk_data(radio_altitude).get() > 1000)
4453 reset_altitude_callouts(); // [SPEC] 6.4.2
4454 reset_minimums(); // common sense
4458 update_altitude_callouts();
4459 update_above_field_callout();
4460 update_bank_angle();
4463 ///////////////////////////////////////////////////////////////////////////////
4464 // TCFHandler /////////////////////////////////////////////////////////////////
4465 ///////////////////////////////////////////////////////////////////////////////
4467 // Gets the difference between the azimuth from @from_lat,@from_lon to
4468 // @to_lat,@to_lon, and @to_heading, in degrees.
4470 MK_VIII::TCFHandler::get_azimuth_difference (double from_lat,
4476 double az1, az2, distance;
4477 geo_inverse_wgs_84(0, from_lat, from_lon, to_lat, to_lon, &az1, &az2, &distance);
4478 return get_heading_difference(az1, to_heading);
4481 // Gets the difference between the azimuth from the current GPS
4482 // position to the center of @_runway, and the heading of @_runway, in
4485 MK_VIII::TCFHandler::get_azimuth_difference (const FGRunway *_runway)
4487 return get_azimuth_difference(mk_data(gps_latitude).get(),
4488 mk_data(gps_longitude).get(),
4489 _runway->latitude(),
4490 _runway->longitude(),
4491 _runway->headingDeg());
4494 // Selects the most likely intended destination runway of @airport,
4495 // and returns it in @_runway. For each runway, the difference between
4496 // the azimuth from the current GPS position to the center of the
4497 // runway and its heading is computed. The runway having the smallest
4500 // This selection algorithm is not specified in [SPEC], but
4501 // http://www.egpws.com/general_information/description/runway_select.htm
4502 // talks about automatic runway selection.
4504 MK_VIII::TCFHandler::select_runway (const FGAirport *airport)
4506 FGRunway* _runway = 0;
4507 double min_diff = 360;
4509 for (unsigned int r=0; r<airport->numRunways(); ++r) {
4510 FGRunway* rwy(airport->getRunwayByIndex(r));
4511 double diff = get_azimuth_difference(rwy);
4512 if (diff < min_diff)
4517 } // of airport runways iteration
4521 bool MK_VIII::TCFHandler::AirportFilter::passAirport(FGAirport* aApt) const
4523 return aApt->hasHardRunwayOfLengthFt(mk->conf.runway_database);
4527 MK_VIII::TCFHandler::update_runway ()
4530 if (mk_data(gps_latitude).ncd || mk_data(gps_longitude).ncd) {
4534 // Search for the intended destination runway of the closest
4535 // airport in range 15 nm. Passing 30nm to get_closest_airport()
4536 // provides enough margin for
4537 // large airports, which may have a runway located far away from
4538 // the airport's reference point.
4539 AirportFilter filter(mk);
4540 FGAirport* apt = FGAirport::findClosest(
4541 SGGeod::fromDeg(mk_data(gps_longitude).get(), mk_data(gps_latitude).get()),
4546 FGRunway* _runway = select_runway(apt);
4548 if (!_runway) return;
4552 runway.center.latitude = _runway->latitude();
4553 runway.center.longitude = _runway->longitude();
4555 runway.elevation = apt->elevation();
4557 double half_length_m = _runway->lengthM() * 0.5;
4558 runway.half_length = half_length_m * SG_METER_TO_NM;
4560 // b3 ________________ b0
4562 // h1>>> | e1<<<<<<<<e0 | <<<h0
4563 // |________________|
4566 // get heading to runway threshold (h0) and end (h1)
4567 runway.edges[0].heading = _runway->headingDeg();
4568 runway.edges[1].heading = get_reciprocal_heading(_runway->headingDeg());
4572 // get position of runway threshold (e0)
4573 geo_direct_wgs_84(0,
4574 runway.center.latitude,
4575 runway.center.longitude,
4576 runway.edges[1].heading,
4578 &runway.edges[0].position.latitude,
4579 &runway.edges[0].position.longitude,
4582 // get position of runway end (e1)
4583 geo_direct_wgs_84(0,
4584 runway.center.latitude,
4585 runway.center.longitude,
4586 runway.edges[0].heading,
4588 &runway.edges[1].position.latitude,
4589 &runway.edges[1].position.longitude,
4592 double half_width_m = _runway->widthM() * 0.5;
4594 // get position of threshold bias area edges (b0 and b1)
4595 get_bias_area_edges(&runway.edges[0].position,
4596 runway.edges[1].heading,
4598 &runway.bias_area[0],
4599 &runway.bias_area[1]);
4601 // get position of end bias area edges (b2 and b3)
4602 get_bias_area_edges(&runway.edges[1].position,
4603 runway.edges[0].heading,
4605 &runway.bias_area[2],
4606 &runway.bias_area[3]);
4610 MK_VIII::TCFHandler::get_bias_area_edges (Position *edge,
4612 double half_width_m,
4613 Position *bias_edge1,
4614 Position *bias_edge2)
4616 double half_bias_width_m = k * SG_NM_TO_METER + half_width_m;
4617 double tmp_latitude = 0.0, tmp_longitude = 0.0, az = 0.0;
4619 geo_direct_wgs_84(0,
4627 geo_direct_wgs_84(0,
4630 heading_substract(reciprocal, 90),
4632 &bias_edge1->latitude,
4633 &bias_edge1->longitude,
4635 geo_direct_wgs_84(0,
4638 heading_add(reciprocal, 90),
4640 &bias_edge2->latitude,
4641 &bias_edge2->longitude,
4645 // Returns true if the current GPS position is inside the edge
4646 // triangle of @edge. The edge triangle, which is specified and
4647 // represented in [SPEC] 6.3.1, is defined as an isocele right
4648 // triangle of infinite height, whose right angle is located at the
4649 // position of @edge, and whose height is parallel to the heading of
4652 MK_VIII::TCFHandler::is_inside_edge_triangle (RunwayEdge *edge)
4654 return get_azimuth_difference(mk_data(gps_latitude).get(),
4655 mk_data(gps_longitude).get(),
4656 edge->position.latitude,
4657 edge->position.longitude,
4658 edge->heading) <= 45;
4661 // Returns true if the current GPS position is inside the bias area of
4662 // the currently selected runway.
4664 MK_VIII::TCFHandler::is_inside_bias_area ()
4667 double angles_sum = 0;
4669 for (int i = 0; i < 4; i++)
4671 double az2, distance;
4672 geo_inverse_wgs_84(0,
4673 mk_data(gps_latitude).get(),
4674 mk_data(gps_longitude).get(),
4675 runway.bias_area[i].latitude,
4676 runway.bias_area[i].longitude,
4677 &az1[i], &az2, &distance);
4680 for (int i = 0; i < 4; i++)
4682 double angle = az1[i == 3 ? 0 : i + 1] - az1[i];
4686 angles_sum += angle;
4689 return angles_sum > 180;
4693 MK_VIII::TCFHandler::is_tcf ()
4695 if (mk_data(radio_altitude).get() > 10)
4699 double distance, az1, az2;
4701 geo_inverse_wgs_84(0,
4702 mk_data(gps_latitude).get(),
4703 mk_data(gps_longitude).get(),
4704 runway.center.latitude,
4705 runway.center.longitude,
4706 &az1, &az2, &distance);
4708 distance *= SG_METER_TO_NM;
4710 // distance to the inner envelope edge
4711 double edge_distance = distance - runway.half_length - k;
4713 if (edge_distance >= 15)
4715 if (mk_data(radio_altitude).get() < 700)
4718 else if (edge_distance >= 12)
4720 if (mk_data(radio_altitude).get() < 100 * edge_distance - 800)
4723 else if (edge_distance >= 4)
4725 if (mk_data(radio_altitude).get() < 400)
4728 else if (edge_distance >= 2.45)
4730 if (mk_data(radio_altitude).get() < 66.666667 * edge_distance + 133.33333)
4735 if (is_inside_edge_triangle(&runway.edges[0]) || is_inside_edge_triangle(&runway.edges[1]))
4737 if (edge_distance >= 1)
4739 if (mk_data(radio_altitude).get() < 66.666667 * edge_distance + 133.33333)
4742 else if (edge_distance >= 0.05)
4744 if (mk_data(radio_altitude).get() < 200 * edge_distance)
4750 if (! is_inside_bias_area())
4752 if (mk_data(radio_altitude).get() < 245)
4760 if (mk_data(radio_altitude).get() < 700)
4769 MK_VIII::TCFHandler::is_rfcf ()
4773 double distance, az1, az2;
4774 geo_inverse_wgs_84(0,
4775 mk_data(gps_latitude).get(),
4776 mk_data(gps_longitude).get(),
4777 runway.center.latitude,
4778 runway.center.longitude,
4779 &az1, &az2, &distance);
4781 double krf = k + mk_data(gps_vertical_figure_of_merit).get() / 200;
4782 distance = distance * SG_METER_TO_NM - runway.half_length - krf;
4786 double altitude_above_field = mk_data(geometric_altitude).get() - runway.elevation;
4788 if (distance >= 1.5)
4790 if (altitude_above_field < 300)
4793 else if (distance >= 0)
4795 if (altitude_above_field < 200 * distance)
4805 MK_VIII::TCFHandler::update ()
4807 if (mk->configuration_module.state != ConfigurationModule::STATE_OK || ! conf.enabled)
4810 // update_runway() has to iterate over the whole runway database
4811 // (which contains thousands of runways), so it would be unwise to
4812 // run it every frame. Run it every 3 seconds.
4813 if (! runway_timer.running || runway_timer.elapsed() >= 3)
4816 runway_timer.start();
4819 if (! mk_dinput(ta_tcf_inhibit)
4820 && ! mk->state_handler.ground // [1]
4821 && ! mk_data(gps_latitude).ncd
4822 && ! mk_data(gps_longitude).ncd
4823 && ! mk_data(radio_altitude).ncd
4824 && ! mk_data(geometric_altitude).ncd
4825 && ! mk_data(gps_vertical_figure_of_merit).ncd)
4830 _reference = mk_data(radio_altitude).get_pointer();
4832 _reference = mk_data(geometric_altitude).get_pointer();
4838 if (mk_test_alert(TCF_TOO_LOW_TERRAIN))
4840 double new_bias = bias;
4841 // do not repeat terrain alerts below 30ft agl
4842 if (mk_data(radio_altitude).get() > 30)
4844 if (new_bias < 0.0) // sanity check
4846 while ((*reference < initial_value - initial_value * new_bias)&&
4851 if (new_bias > bias)
4854 mk_repeat_alert(mk_alert(TCF_TOO_LOW_TERRAIN));
4860 reference = _reference;
4861 initial_value = *reference;
4862 mk_set_alerts(mk_alert(TCF_TOO_LOW_TERRAIN));
4869 mk_unset_alerts(mk_alert(TCF_TOO_LOW_TERRAIN));
4872 ///////////////////////////////////////////////////////////////////////////////
4873 // MK_VIII ////////////////////////////////////////////////////////////////////
4874 ///////////////////////////////////////////////////////////////////////////////
4876 MK_VIII::MK_VIII (SGPropertyNode *node)
4877 : properties_handler(this),
4880 power_handler(this),
4881 system_handler(this),
4882 configuration_module(this),
4883 fault_handler(this),
4886 self_test_handler(this),
4887 alert_handler(this),
4888 state_handler(this),
4889 mode1_handler(this),
4890 mode2_handler(this),
4891 mode3_handler(this),
4892 mode4_handler(this),
4893 mode5_handler(this),
4894 mode6_handler(this),
4897 for (int i = 0; i < node->nChildren(); ++i)
4899 SGPropertyNode *child = node->getChild(i);
4900 string cname = child->getName();
4901 string cval = child->getStringValue();
4903 if (cname == "name")
4905 else if (cname == "number")
4906 num = child->getIntValue();
4909 SG_LOG(SG_INSTR, SG_WARN, "Error in mk-viii config logic");
4911 SG_LOG(SG_INSTR, SG_WARN, "Section = " << name);
4919 properties_handler.init();
4920 voice_player.init();
4926 SGPropertyNode *node = fgGetNode(("/instrumentation/" + name).c_str(), num, true);
4928 configuration_module.bind(node);
4929 power_handler.bind(node);
4930 io_handler.bind(node);
4931 voice_player.bind(node);
4937 properties_handler.unbind();
4941 MK_VIII::update (double dt)
4943 power_handler.update();
4944 system_handler.update();
4946 if (system_handler.state != SystemHandler::STATE_ON)
4949 io_handler.update_inputs();
4950 io_handler.update_input_faults();
4952 voice_player.update();
4953 state_handler.update();
4955 if (self_test_handler.update())
4958 io_handler.update_internal_latches();
4959 io_handler.update_lamps();
4961 mode1_handler.update();
4962 mode2_handler.update();
4963 mode3_handler.update();
4964 mode4_handler.update();
4965 mode5_handler.update();
4966 mode6_handler.update();
4967 tcf_handler.update();
4969 alert_handler.update();
4970 io_handler.update_outputs();