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 samples_type::iterator iter;
992 // remove samples older than 15 seconds
993 for (iter = samples.begin(); iter != samples.end() && globals->get_sim_time_sec() - (*iter).timestamp >= 15; iter = samples.begin())
997 samples.push_back(Sample<double>(agl));
1000 double new_value = 0;
1001 if (samples.size() > 0)
1003 for (iter = samples.begin(); iter != samples.end(); iter++)
1004 new_value += (*iter).value;
1005 new_value /= samples.size();
1009 if (new_value > value)
1016 MK_VIII::IOHandler::TerrainClearanceFilter::reset ()
1022 MK_VIII::IOHandler::IOHandler (MK_VIII *device)
1023 : mk(device), _lamp(LAMP_NONE)
1025 memset(&input_feeders, 0, sizeof(input_feeders));
1026 memset(&inputs.discretes, 0, sizeof(inputs.discretes));
1027 memset(&outputs, 0, sizeof(outputs));
1028 memset(&power_saved, 0, sizeof(power_saved));
1030 mk_dinput_feed(landing_gear) = true;
1031 mk_dinput_feed(landing_flaps) = true;
1032 mk_dinput_feed(glideslope_inhibit) = true;
1033 mk_dinput_feed(decision_height) = true;
1034 mk_dinput_feed(autopilot_engaged) = true;
1035 mk_ainput_feed(uncorrected_barometric_altitude) = true;
1036 mk_ainput_feed(barometric_altitude_rate) = true;
1037 mk_ainput_feed(radio_altitude) = true;
1038 mk_ainput_feed(glideslope_deviation) = true;
1039 mk_ainput_feed(roll_angle) = true;
1040 mk_ainput_feed(localizer_deviation) = true;
1041 mk_ainput_feed(computed_airspeed) = true;
1043 // will be unset on power on
1044 mk_doutput(gpws_inop) = true;
1045 mk_doutput(tad_inop) = true;
1049 MK_VIII::IOHandler::boot ()
1051 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
1054 mk_doutput(gpws_inop) = false;
1055 mk_doutput(tad_inop) = false;
1057 mk_doutput(glideslope_cancel) = power_saved.glideslope_cancel;
1059 altitude_samples.clear();
1063 MK_VIII::IOHandler::post_boot ()
1065 if (momentary_steep_approach_enabled())
1067 last_landing_gear = mk_dinput(landing_gear);
1068 last_real_flaps_down = real_flaps_down();
1071 // read externally-latching input discretes
1072 update_alternate_discrete_input(&mk_dinput(mode6_low_volume));
1073 update_alternate_discrete_input(&mk_dinput(audio_inhibit));
1077 MK_VIII::IOHandler::power_off ()
1079 power_saved.glideslope_cancel = mk_doutput(glideslope_cancel); // [SPEC] 6.2.5
1081 memset(&outputs, 0, sizeof(outputs));
1083 audio_inhibit_fault_timer.stop();
1084 landing_gear_fault_timer.stop();
1085 flaps_down_fault_timer.stop();
1086 momentary_flap_override_fault_timer.stop();
1087 self_test_fault_timer.stop();
1088 glideslope_cancel_fault_timer.stop();
1089 steep_approach_fault_timer.stop();
1090 gpws_inhibit_fault_timer.stop();
1091 ta_tcf_inhibit_fault_timer.stop();
1094 mk_doutput(gpws_inop) = true;
1095 mk_doutput(tad_inop) = true;
1099 MK_VIII::IOHandler::enter_ground ()
1101 reset_terrain_clearance();
1103 if (conf.momentary_flap_override_enabled)
1104 mk_doutput(flap_override) = false; // [INSTALL] 3.15.1.6
1108 MK_VIII::IOHandler::enter_takeoff ()
1110 reset_terrain_clearance();
1112 if (momentary_steep_approach_enabled())
1113 // landing or go-around, disable steep approach as per [SPEC] 6.2.1
1114 mk_doutput(steep_approach) = false;
1118 MK_VIII::IOHandler::update_inputs ()
1120 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
1123 // 1. process input feeders
1125 if (mk_dinput_feed(landing_gear))
1126 mk_dinput(landing_gear) = mk_node(gear_down)->getBoolValue();
1127 if (mk_dinput_feed(landing_flaps))
1128 mk_dinput(landing_flaps) = mk_node(flaps)->getDoubleValue() < 1;
1129 if (mk_dinput_feed(glideslope_inhibit))
1130 mk_dinput(glideslope_inhibit) = mk_node(nav0_gs_distance)->getDoubleValue() < 0;
1131 if (mk_dinput_feed(autopilot_engaged))
1135 mode = mk_node(autopilot_heading_lock)->getStringValue();
1136 mk_dinput(autopilot_engaged) = mode && *mode;
1139 if (mk_ainput_feed(uncorrected_barometric_altitude))
1141 if (mk_node(altimeter_serviceable)->getBoolValue())
1142 mk_ainput(uncorrected_barometric_altitude).set(mk_node(altimeter_altitude)->getDoubleValue());
1144 mk_ainput(uncorrected_barometric_altitude).unset();
1146 if (mk_ainput_feed(barometric_altitude_rate))
1147 // Do not use the vsi, because it is pressure-based only, and
1148 // therefore too laggy for GPWS alerting purposes. I guess that
1149 // a real ADC combines pressure-based and inerta-based altitude
1150 // rates to provide a non-laggy rate. That non-laggy rate is
1151 // best emulated by /velocities/vertical-speed-fps * 60.
1152 mk_ainput(barometric_altitude_rate).set(mk_node(vs)->getDoubleValue() * 60);
1153 if (mk_ainput_feed(radio_altitude))
1156 if (conf.use_gear_altitude)
1157 agl = mk_node(altitude_gear_agl)->getDoubleValue();
1159 agl = mk_node(altitude_agl)->getDoubleValue();
1160 // Some flight models may return negative values when on the
1161 // ground or after a crash; do not allow them.
1162 mk_ainput(radio_altitude).set(SG_MAX2(0.0, agl));
1164 if (mk_ainput_feed(glideslope_deviation))
1166 if (mk_node(nav0_serviceable)->getBoolValue()
1167 && mk_node(nav0_gs_serviceable)->getBoolValue()
1168 && mk_node(nav0_in_range)->getBoolValue()
1169 && mk_node(nav0_has_gs)->getBoolValue())
1170 // gs-needle-deflection is expressed in degrees, and 1 dot =
1171 // 0.32 degrees (according to
1172 // http://www.ntsb.gov/Recs/letters/2000/A00_41_45.pdf)
1173 mk_ainput(glideslope_deviation).set(mk_node(nav0_gs_needle_deflection)->getDoubleValue() / 0.32 * GLIDESLOPE_DOTS_TO_DDM);
1175 mk_ainput(glideslope_deviation).unset();
1177 if (mk_ainput_feed(roll_angle))
1179 if (conf.use_attitude_indicator)
1181 // read data from attitude indicator instrument (requires vacuum system to work)
1182 if (mk_node(ai_serviceable)->getBoolValue() && ! mk_node(ai_caged)->getBoolValue())
1183 mk_ainput(roll_angle).set(mk_node(ai_roll)->getDoubleValue());
1185 mk_ainput(roll_angle).unset();
1189 // use simulator source
1190 mk_ainput(roll_angle).set(mk_node(orientation_roll)->getDoubleValue());
1193 if (mk_ainput_feed(localizer_deviation))
1195 if (mk_node(nav0_serviceable)->getBoolValue()
1196 && mk_node(nav0_cdi_serviceable)->getBoolValue()
1197 && mk_node(nav0_in_range)->getBoolValue()
1198 && mk_node(nav0_nav_loc)->getBoolValue())
1199 // heading-needle-deflection is expressed in degrees, and 1
1200 // dot = 2 degrees (0.5 degrees for ILS, but navradio.cxx
1201 // performs the conversion)
1202 mk_ainput(localizer_deviation).set(mk_node(nav0_heading_needle_deflection)->getDoubleValue() / 2 * LOCALIZER_DOTS_TO_DDM);
1204 mk_ainput(localizer_deviation).unset();
1206 if (mk_ainput_feed(computed_airspeed))
1208 if (mk_node(asi_serviceable)->getBoolValue())
1209 mk_ainput(computed_airspeed).set(mk_node(asi_speed)->getDoubleValue());
1211 mk_ainput(computed_airspeed).unset();
1216 mk_data(decision_height).set(&mk_ainput(decision_height));
1217 mk_data(radio_altitude).set(&mk_ainput(radio_altitude));
1218 mk_data(roll_angle).set(&mk_ainput(roll_angle));
1220 // update barometric_altitude_rate as per [SPEC] 6.2.1: "During
1221 // normal conditions, the system will base Mode 1 computations upon
1222 // barometric rate from the ADC. If this computed data is not valid
1223 // or available then the system will use internally computed
1224 // barometric altitude rate."
1226 if (! mk_ainput(barometric_altitude_rate).ncd)
1227 // the altitude rate input is valid, use it
1228 mk_data(barometric_altitude_rate).set(mk_ainput(barometric_altitude_rate).get());
1233 // The altitude rate input is invalid. We can compute an
1234 // altitude rate if all the following conditions are true:
1236 // - the altitude input is valid
1237 // - there is an altitude sample with age >= 1 second
1238 // - that altitude sample is valid
1240 if (! mk_ainput(uncorrected_barometric_altitude).ncd)
1242 if (! altitude_samples.empty() && ! altitude_samples.front().value.ncd)
1244 double elapsed = globals->get_sim_time_sec() - altitude_samples.front().timestamp;
1248 mk_data(barometric_altitude_rate).set((mk_ainput(uncorrected_barometric_altitude).get() - altitude_samples.front().value.get()) / elapsed * 60);
1254 mk_data(barometric_altitude_rate).unset();
1257 altitude_samples.push_back(Sample< Parameter<double> >(mk_ainput(uncorrected_barometric_altitude)));
1259 // Erase everything from the beginning of the list up to the sample
1260 // preceding the most recent sample whose age is >= 1 second.
1262 altitude_samples_type::iterator erase_last = altitude_samples.begin();
1263 altitude_samples_type::iterator iter;
1265 for (iter = altitude_samples.begin(); iter != altitude_samples.end(); iter++)
1266 if (globals->get_sim_time_sec() - (*iter).timestamp >= 1)
1271 if (erase_last != altitude_samples.begin())
1272 altitude_samples.erase(altitude_samples.begin(), erase_last);
1276 if (conf.use_internal_gps)
1278 mk_data(gps_altitude).set(mk_node(altitude)->getDoubleValue());
1279 mk_data(gps_latitude).set(mk_node(latitude)->getDoubleValue());
1280 mk_data(gps_longitude).set(mk_node(longitude)->getDoubleValue());
1281 mk_data(gps_vertical_figure_of_merit).set(0.0);
1285 mk_data(gps_altitude).set(&mk_ainput(gps_altitude));
1286 mk_data(gps_latitude).set(&mk_ainput(gps_latitude));
1287 mk_data(gps_longitude).set(&mk_ainput(gps_longitude));
1288 mk_data(gps_vertical_figure_of_merit).set(&mk_ainput(gps_vertical_figure_of_merit));
1291 // update glideslope and localizer
1293 mk_data(glideslope_deviation_dots).set(&mk_ainput(glideslope_deviation), GLIDESLOPE_DDM_TO_DOTS);
1294 mk_data(localizer_deviation_dots).set(&mk_ainput(localizer_deviation), LOCALIZER_DDM_TO_DOTS);
1296 // Update geometric altitude; [SPEC] 6.7.8 provides an overview of a
1297 // complex algorithm which combines several input sources to
1298 // calculate the geometric altitude. Since the exact algorithm is
1299 // not given, we fallback to a much simpler method.
1301 if (! mk_data(gps_altitude).ncd)
1302 mk_data(geometric_altitude).set(mk_data(gps_altitude).get());
1303 else if (! mk_ainput(uncorrected_barometric_altitude).ncd)
1304 mk_data(geometric_altitude).set(mk_ainput(uncorrected_barometric_altitude).get());
1306 mk_data(geometric_altitude).unset();
1308 // update terrain clearance
1310 update_terrain_clearance();
1312 // 3. perform sanity checks
1314 if (! mk_data(radio_altitude).ncd && mk_data(radio_altitude).get() < 0)
1315 mk_data(radio_altitude).unset();
1317 if (! mk_data(decision_height).ncd && mk_data(decision_height).get() < 0)
1318 mk_data(decision_height).unset();
1320 if (! mk_data(gps_latitude).ncd
1321 && (mk_data(gps_latitude).get() < -90
1322 || mk_data(gps_latitude).get() > 90))
1323 mk_data(gps_latitude).unset();
1325 if (! mk_data(gps_longitude).ncd
1326 && (mk_data(gps_longitude).get() < -180
1327 || mk_data(gps_longitude).get() > 180))
1328 mk_data(gps_longitude).unset();
1330 if (! mk_data(roll_angle).ncd
1331 && ((mk_data(roll_angle).get() < -180)
1332 || (mk_data(roll_angle).get() > 180)))
1333 mk_data(roll_angle).unset();
1335 // 4. process input feeders requiring data computed above
1337 if (mk_dinput_feed(decision_height))
1338 mk_dinput(decision_height) = ! mk_data(radio_altitude).ncd
1339 && ! mk_data(decision_height).ncd
1340 && mk_data(radio_altitude).get() <= mk_data(decision_height).get();
1344 MK_VIII::IOHandler::update_terrain_clearance ()
1346 if (! mk_data(radio_altitude).ncd)
1347 mk_data(terrain_clearance).set(terrain_clearance_filter.update(mk_data(radio_altitude).get()));
1349 mk_data(terrain_clearance).unset();
1353 MK_VIII::IOHandler::reset_terrain_clearance ()
1355 terrain_clearance_filter.reset();
1356 update_terrain_clearance();
1360 MK_VIII::IOHandler::handle_input_fault (bool test, FaultHandler::Fault fault)
1364 if (! mk->fault_handler.faults[fault])
1365 mk->fault_handler.set_fault(fault);
1368 mk->fault_handler.unset_fault(fault);
1372 MK_VIII::IOHandler::handle_input_fault (bool test,
1374 double max_duration,
1375 FaultHandler::Fault fault)
1379 if (! mk->fault_handler.faults[fault])
1381 if (timer->start_or_elapsed() >= max_duration)
1382 mk->fault_handler.set_fault(fault);
1387 mk->fault_handler.unset_fault(fault);
1393 MK_VIII::IOHandler::update_input_faults ()
1395 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
1398 // [INSTALL] 3.15.1.3
1399 handle_input_fault(mk_dinput(audio_inhibit),
1400 &audio_inhibit_fault_timer,
1402 FaultHandler::FAULT_ALL_MODES_INHIBIT);
1404 // [INSTALL] appendix E 6.6.2
1405 handle_input_fault(mk_dinput(landing_gear)
1406 && ! mk_ainput(computed_airspeed).ncd
1407 && mk_ainput(computed_airspeed).get() > conf.faults->max_gear_down_airspeed,
1408 &landing_gear_fault_timer,
1410 FaultHandler::FAULT_GEAR_SWITCH);
1412 // [INSTALL] appendix E 6.6.4
1413 handle_input_fault(flaps_down()
1414 && ! mk_ainput(computed_airspeed).ncd
1415 && mk_ainput(computed_airspeed).get() > conf.faults->max_flaps_down_airspeed,
1416 &flaps_down_fault_timer,
1418 FaultHandler::FAULT_FLAPS_SWITCH);
1420 // [INSTALL] appendix E 6.6.6
1421 if (conf.momentary_flap_override_enabled)
1422 handle_input_fault(mk_dinput(momentary_flap_override),
1423 &momentary_flap_override_fault_timer,
1425 FaultHandler::FAULT_MOMENTARY_FLAP_OVERRIDE_INVALID);
1427 // [INSTALL] appendix E 6.6.7
1428 handle_input_fault(mk_dinput(self_test),
1429 &self_test_fault_timer,
1431 FaultHandler::FAULT_SELF_TEST_INVALID);
1433 // [INSTALL] appendix E 6.6.13
1434 handle_input_fault(mk_dinput(glideslope_cancel),
1435 &glideslope_cancel_fault_timer,
1437 FaultHandler::FAULT_GLIDESLOPE_CANCEL_INVALID);
1439 // [INSTALL] appendix E 6.6.25
1440 if (momentary_steep_approach_enabled())
1441 handle_input_fault(mk_dinput(steep_approach),
1442 &steep_approach_fault_timer,
1444 FaultHandler::FAULT_STEEP_APPROACH_INVALID);
1446 // [INSTALL] appendix E 6.6.27
1447 if (conf.gpws_inhibit_enabled)
1448 handle_input_fault(mk_dinput(gpws_inhibit),
1449 &gpws_inhibit_fault_timer,
1451 FaultHandler::FAULT_GPWS_INHIBIT);
1453 // [INSTALL] does not specify a fault for this one, but it makes
1454 // sense to have it behave like GPWS inhibit
1455 handle_input_fault(mk_dinput(ta_tcf_inhibit),
1456 &ta_tcf_inhibit_fault_timer,
1458 FaultHandler::FAULT_TA_TCF_INHIBIT);
1460 // [PILOT] page 48: "In the event that required data for a
1461 // particular function is not available, then that function is
1462 // automatically inhibited and annunciated"
1464 handle_input_fault(mk_data(radio_altitude).ncd
1465 || mk_ainput(uncorrected_barometric_altitude).ncd
1466 || mk_data(barometric_altitude_rate).ncd
1467 || mk_ainput(computed_airspeed).ncd
1468 || mk_data(terrain_clearance).ncd,
1469 FaultHandler::FAULT_MODES14_INPUTS_INVALID);
1471 if (! mk_dinput(glideslope_inhibit))
1472 handle_input_fault(mk_data(radio_altitude).ncd,
1473 FaultHandler::FAULT_MODE5_INPUTS_INVALID);
1475 if (mk->mode6_handler.altitude_callouts_enabled())
1476 handle_input_fault(mk->mode6_handler.conf.above_field_voice
1477 ? (mk_data(gps_latitude).ncd
1478 || mk_data(gps_longitude).ncd
1479 || mk_data(geometric_altitude).ncd)
1480 : mk_data(radio_altitude).ncd,
1481 FaultHandler::FAULT_MODE6_INPUTS_INVALID);
1483 if (mk->mode6_handler.conf.bank_angle_enabled)
1484 handle_input_fault(mk_data(roll_angle).ncd,
1485 FaultHandler::FAULT_BANK_ANGLE_INPUTS_INVALID);
1487 if (mk->tcf_handler.conf.enabled)
1488 handle_input_fault(mk_data(radio_altitude).ncd
1489 || mk_data(geometric_altitude).ncd
1490 || mk_data(gps_latitude).ncd
1491 || mk_data(gps_longitude).ncd
1492 || mk_data(gps_vertical_figure_of_merit).ncd,
1493 FaultHandler::FAULT_TCF_INPUTS_INVALID);
1497 MK_VIII::IOHandler::update_alternate_discrete_input (bool *ptr)
1499 assert(mk->system_handler.state == SystemHandler::STATE_ON);
1501 if (ptr == &mk_dinput(mode6_low_volume))
1503 if (mk->configuration_module.state == ConfigurationModule::STATE_OK
1504 && mk->self_test_handler.state == SelfTestHandler::STATE_NONE)
1505 mk->mode6_handler.set_volume(*ptr ? modify_amplitude(1.0, -6) : 1.0);
1507 else if (ptr == &mk_dinput(audio_inhibit))
1509 if (mk->configuration_module.state == ConfigurationModule::STATE_OK
1510 && mk->self_test_handler.state == SelfTestHandler::STATE_NONE)
1511 mk->voice_player.set_volume(*ptr ? 0.0 : mk->voice_player.conf.volume);
1516 MK_VIII::IOHandler::update_internal_latches ()
1518 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
1522 if (conf.momentary_flap_override_enabled
1523 && mk_doutput(flap_override)
1524 && ! mk->state_handler.takeoff
1525 && (mk_data(radio_altitude).ncd || mk_data(radio_altitude).get() <= 50))
1526 mk_doutput(flap_override) = false;
1529 if (momentary_steep_approach_enabled())
1531 if (mk_doutput(steep_approach)
1532 && ! mk->state_handler.takeoff
1533 && ((last_landing_gear && ! mk_dinput(landing_gear))
1534 || (last_real_flaps_down && ! real_flaps_down())))
1535 // gear or flaps raised during approach: it's a go-around
1536 mk_doutput(steep_approach) = false;
1538 last_landing_gear = mk_dinput(landing_gear);
1539 last_real_flaps_down = real_flaps_down();
1543 if (mk_doutput(glideslope_cancel)
1544 && (mk_data(glideslope_deviation_dots).ncd
1545 || mk_data(radio_altitude).ncd
1546 || mk_data(radio_altitude).get() > 2000
1547 || mk_data(radio_altitude).get() < 30))
1548 mk_doutput(glideslope_cancel) = false;
1552 MK_VIII::IOHandler::update_egpws_alert_discrete_1 ()
1554 if (mk->voice_player.voice)
1559 VoicePlayer::Voice *voice;
1561 { 11, mk_voice(sink_rate_pause_sink_rate) },
1562 { 12, mk_voice(pull_up) },
1563 { 13, mk_voice(terrain) },
1564 { 13, mk_voice(terrain_pause_terrain) },
1565 { 14, mk_voice(dont_sink_pause_dont_sink) },
1566 { 15, mk_voice(too_low_gear) },
1567 { 16, mk_voice(too_low_flaps) },
1568 { 17, mk_voice(too_low_terrain) },
1569 { 18, mk_voice(soft_glideslope) },
1570 { 18, mk_voice(hard_glideslope) },
1571 { 19, mk_voice(minimums_minimums) }
1574 for (size_t i = 0; i < n_elements(voices); i++)
1575 if (voices[i].voice == mk->voice_player.voice)
1577 mk_aoutput(egpws_alert_discrete_1) = 1 << voices[i].bit;
1582 mk_aoutput(egpws_alert_discrete_1) = 0;
1586 MK_VIII::IOHandler::update_egpwc_logic_discretes ()
1588 mk_aoutput(egpwc_logic_discretes) = 0;
1590 if (mk->state_handler.takeoff)
1591 mk_aoutput(egpwc_logic_discretes) |= 1 << 18;
1596 unsigned int alerts;
1598 { 13, mk_alert(TCF_TOO_LOW_TERRAIN) },
1599 { 19, mk_alert(MODE1_SINK_RATE) },
1600 { 20, mk_alert(MODE1_PULL_UP) },
1601 { 21, mk_alert(MODE2A_PREFACE) | mk_alert(MODE2B_PREFACE) | mk_alert(MODE2B_LANDING_MODE) | mk_alert(MODE2A_ALTITUDE_GAIN_TERRAIN_CLOSING) },
1602 { 22, mk_alert(MODE2A) | mk_alert(MODE2B) },
1603 { 23, mk_alert(MODE3) },
1604 { 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) },
1605 { 25, mk_alert(MODE5_SOFT) | mk_alert(MODE5_HARD) }
1608 for (size_t i = 0; i < n_elements(logic); i++)
1609 if (mk_test_alerts(logic[i].alerts))
1610 mk_aoutput(egpwc_logic_discretes) |= 1 << logic[i].bit;
1614 MK_VIII::IOHandler::update_mode6_callouts_discrete_1 ()
1616 if (mk->voice_player.voice)
1621 VoicePlayer::Voice *voice;
1623 { 11, mk_voice(minimums_minimums) },
1624 { 16, mk_altitude_voice(Mode6Handler::ALTITUDE_CALLOUT_10) },
1625 { 17, mk_altitude_voice(Mode6Handler::ALTITUDE_CALLOUT_20) },
1626 { 18, mk_altitude_voice(Mode6Handler::ALTITUDE_CALLOUT_30) },
1627 { 19, mk_altitude_voice(Mode6Handler::ALTITUDE_CALLOUT_40) },
1628 { 20, mk_altitude_voice(Mode6Handler::ALTITUDE_CALLOUT_50) },
1629 { 23, mk_altitude_voice(Mode6Handler::ALTITUDE_CALLOUT_100) },
1630 { 24, mk_altitude_voice(Mode6Handler::ALTITUDE_CALLOUT_200) },
1631 { 25, mk_altitude_voice(Mode6Handler::ALTITUDE_CALLOUT_300) }
1634 for (size_t i = 0; i < n_elements(voices); i++)
1635 if (voices[i].voice == mk->voice_player.voice)
1637 mk_aoutput(mode6_callouts_discrete_1) = 1 << voices[i].bit;
1642 mk_aoutput(mode6_callouts_discrete_1) = 0;
1646 MK_VIII::IOHandler::update_mode6_callouts_discrete_2 ()
1648 if (mk->voice_player.voice)
1653 VoicePlayer::Voice *voice;
1655 { 11, mk_altitude_voice(Mode6Handler::ALTITUDE_CALLOUT_400) },
1656 { 12, mk_altitude_voice(Mode6Handler::ALTITUDE_CALLOUT_500) },
1657 { 13, mk_altitude_voice(Mode6Handler::ALTITUDE_CALLOUT_1000) },
1658 { 18, mk_voice(bank_angle_pause_bank_angle) },
1659 { 18, mk_voice(bank_angle_pause_bank_angle_3) },
1660 { 23, mk_voice(five_hundred_above) }
1663 for (size_t i = 0; i < n_elements(voices); i++)
1664 if (voices[i].voice == mk->voice_player.voice)
1666 mk_aoutput(mode6_callouts_discrete_2) = 1 << voices[i].bit;
1671 mk_aoutput(mode6_callouts_discrete_2) = 0;
1675 MK_VIII::IOHandler::update_egpws_alert_discrete_2 ()
1677 mk_aoutput(egpws_alert_discrete_2) = 1 << 27; // Terrain NA
1679 if (mk_doutput(glideslope_cancel))
1680 mk_aoutput(egpws_alert_discrete_2) |= 1 << 11;
1681 if (mk_doutput(gpws_alert))
1682 mk_aoutput(egpws_alert_discrete_2) |= 1 << 12;
1683 if (mk_doutput(gpws_warning))
1684 mk_aoutput(egpws_alert_discrete_2) |= 1 << 13;
1685 if (mk_doutput(gpws_inop))
1686 mk_aoutput(egpws_alert_discrete_2) |= 1 << 14;
1687 if (mk_doutput(audio_on))
1688 mk_aoutput(egpws_alert_discrete_2) |= 1 << 16;
1689 if (mk_doutput(tad_inop))
1690 mk_aoutput(egpws_alert_discrete_2) |= 1 << 24;
1691 if (mk->fault_handler.has_faults())
1692 mk_aoutput(egpws_alert_discrete_2) |= 1 << 25;
1696 MK_VIII::IOHandler::update_egpwc_alert_discrete_3 ()
1698 mk_aoutput(egpwc_alert_discrete_3) = 1 << 17 | 1 << 18;
1700 if (mk->fault_handler.faults[FaultHandler::FAULT_MODES14_INPUTS_INVALID])
1701 mk_aoutput(egpwc_alert_discrete_3) |= 1 << 11;
1702 if (mk->fault_handler.faults[FaultHandler::FAULT_MODE5_INPUTS_INVALID])
1703 mk_aoutput(egpwc_alert_discrete_3) |= 1 << 12;
1704 if (mk->fault_handler.faults[FaultHandler::FAULT_MODE6_INPUTS_INVALID])
1705 mk_aoutput(egpwc_alert_discrete_3) |= 1 << 13;
1706 if (mk->fault_handler.faults[FaultHandler::FAULT_BANK_ANGLE_INPUTS_INVALID])
1707 mk_aoutput(egpwc_alert_discrete_3) |= 1 << 14;
1708 if (mk_doutput(tad_inop))
1709 mk_aoutput(egpwc_alert_discrete_3) |= 1 << 16;
1713 MK_VIII::IOHandler::update_outputs ()
1715 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
1718 mk_doutput(audio_on) = ! mk_dinput(audio_inhibit)
1719 && mk->voice_player.voice
1720 && ! mk->voice_player.voice->element->silence;
1722 update_egpws_alert_discrete_1();
1723 update_egpwc_logic_discretes();
1724 update_mode6_callouts_discrete_1();
1725 update_mode6_callouts_discrete_2();
1726 update_egpws_alert_discrete_2();
1727 update_egpwc_alert_discrete_3();
1731 MK_VIII::IOHandler::get_lamp_output (Lamp lamp)
1735 case LAMP_GLIDESLOPE:
1736 return &mk_doutput(gpws_alert);
1739 return conf.lamp->format2 ? &mk_doutput(gpws_alert) : &mk_doutput(gpws_warning);
1742 return &mk_doutput(gpws_warning);
1745 assert_not_reached();
1751 MK_VIII::IOHandler::update_lamps ()
1753 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
1756 if (_lamp != LAMP_NONE && conf.lamp->flashing)
1758 // [SPEC] 6.9.3: 70 cycles per minute
1759 if (lamp_timer.elapsed() >= 60.0 / 70.0 / 2.0)
1761 bool *output = get_lamp_output(_lamp);
1762 *output = ! *output;
1769 MK_VIII::IOHandler::set_lamp (Lamp lamp)
1776 mk_doutput(gpws_warning) = false;
1777 mk_doutput(gpws_alert) = false;
1779 if (lamp != LAMP_NONE)
1781 *get_lamp_output(lamp) = true;
1787 MK_VIII::IOHandler::gpws_inhibit () const
1789 return conf.gpws_inhibit_enabled && mk_dinput(gpws_inhibit);
1793 MK_VIII::IOHandler::real_flaps_down () const
1795 return conf.flap_reversal ? mk_dinput(landing_flaps) : ! mk_dinput(landing_flaps);
1799 MK_VIII::IOHandler::flaps_down () const
1801 return flap_override() ? true : real_flaps_down();
1805 MK_VIII::IOHandler::flap_override () const
1807 return conf.momentary_flap_override_enabled ? mk_doutput(flap_override) : false;
1811 MK_VIII::IOHandler::steep_approach () const
1813 if (conf.steep_approach_enabled)
1814 // If alternate action was configured in category 13, we return
1815 // the value of the input discrete (which should be connected to a
1816 // latching steep approach cockpit switch). Otherwise, we return
1817 // the value of the output discrete.
1818 return conf.alternate_steep_approach ? mk_dinput(steep_approach) : mk_doutput(steep_approach);
1824 MK_VIII::IOHandler::momentary_steep_approach_enabled () const
1826 return conf.steep_approach_enabled && ! conf.alternate_steep_approach;
1830 MK_VIII::IOHandler::tie_input (SGPropertyNode *node,
1835 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));
1837 mk->properties_handler.tie(node, (string("input-feeders/discretes/") + name).c_str(), SGRawValuePointer<bool>(feed));
1841 MK_VIII::IOHandler::tie_input (SGPropertyNode *node,
1843 Parameter<double> *input,
1846 mk->properties_handler.tie(node, (string("inputs/arinc429/") + name).c_str(), SGRawValuePointer<double>(input->get_pointer()));
1847 mk->properties_handler.tie(node, (string("inputs/arinc429/") + name + "-ncd").c_str(), SGRawValuePointer<bool>(&input->ncd));
1849 mk->properties_handler.tie(node, (string("input-feeders/arinc429/") + name).c_str(), SGRawValuePointer<bool>(feed));
1853 MK_VIII::IOHandler::tie_output (SGPropertyNode *node,
1857 SGPropertyNode *child = node->getNode((string("outputs/discretes/") + name).c_str(), true);
1859 mk->properties_handler.tie(child, SGRawValuePointer<bool>(output));
1860 child->setAttribute(SGPropertyNode::WRITE, false);
1864 MK_VIII::IOHandler::tie_output (SGPropertyNode *node,
1868 SGPropertyNode *child = node->getNode((string("outputs/arinc429/") + name).c_str(), true);
1870 mk->properties_handler.tie(child, SGRawValuePointer<int>(output));
1871 child->setAttribute(SGPropertyNode::WRITE, false);
1875 MK_VIII::IOHandler::bind (SGPropertyNode *node)
1877 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));
1879 tie_input(node, "landing-gear", &mk_dinput(landing_gear), &mk_dinput_feed(landing_gear));
1880 tie_input(node, "landing-flaps", &mk_dinput(landing_flaps), &mk_dinput_feed(landing_flaps));
1881 tie_input(node, "momentary-flap-override", &mk_dinput(momentary_flap_override));
1882 tie_input(node, "self-test", &mk_dinput(self_test));
1883 tie_input(node, "glideslope-inhibit", &mk_dinput(glideslope_inhibit), &mk_dinput_feed(glideslope_inhibit));
1884 tie_input(node, "glideslope-cancel", &mk_dinput(glideslope_cancel));
1885 tie_input(node, "decision-height", &mk_dinput(decision_height), &mk_dinput_feed(decision_height));
1886 tie_input(node, "mode6-low-volume", &mk_dinput(mode6_low_volume));
1887 tie_input(node, "audio-inhibit", &mk_dinput(audio_inhibit));
1888 tie_input(node, "ta-tcf-inhibit", &mk_dinput(ta_tcf_inhibit));
1889 tie_input(node, "autopilot-engaged", &mk_dinput(autopilot_engaged), &mk_dinput_feed(autopilot_engaged));
1890 tie_input(node, "steep-approach", &mk_dinput(steep_approach));
1891 tie_input(node, "gpws-inhibit", &mk_dinput(gpws_inhibit));
1893 tie_input(node, "uncorrected-barometric-altitude", &mk_ainput(uncorrected_barometric_altitude), &mk_ainput_feed(uncorrected_barometric_altitude));
1894 tie_input(node, "barometric-altitude-rate", &mk_ainput(barometric_altitude_rate), &mk_ainput_feed(barometric_altitude_rate));
1895 tie_input(node, "gps-altitude", &mk_ainput(gps_altitude));
1896 tie_input(node, "gps-latitude", &mk_ainput(gps_latitude));
1897 tie_input(node, "gps-longitude", &mk_ainput(gps_longitude));
1898 tie_input(node, "gps-vertical-figure-of-merit", &mk_ainput(gps_vertical_figure_of_merit));
1899 tie_input(node, "radio-altitude", &mk_ainput(radio_altitude), &mk_ainput_feed(radio_altitude));
1900 tie_input(node, "glideslope-deviation", &mk_ainput(glideslope_deviation), &mk_ainput_feed(glideslope_deviation));
1901 tie_input(node, "roll-angle", &mk_ainput(roll_angle), &mk_ainput_feed(roll_angle));
1902 tie_input(node, "localizer-deviation", &mk_ainput(localizer_deviation), &mk_ainput_feed(localizer_deviation));
1903 tie_input(node, "computed-airspeed", &mk_ainput(computed_airspeed), &mk_ainput_feed(computed_airspeed));
1904 tie_input(node, "decision-height", &mk_ainput(decision_height), &mk_ainput_feed(decision_height));
1906 tie_output(node, "gpws-warning", &mk_doutput(gpws_warning));
1907 tie_output(node, "gpws-alert", &mk_doutput(gpws_alert));
1908 tie_output(node, "audio-on", &mk_doutput(audio_on));
1909 tie_output(node, "gpws-inop", &mk_doutput(gpws_inop));
1910 tie_output(node, "tad-inop", &mk_doutput(tad_inop));
1911 tie_output(node, "flap-override", &mk_doutput(flap_override));
1912 tie_output(node, "glideslope-cancel", &mk_doutput(glideslope_cancel));
1913 tie_output(node, "steep-approach", &mk_doutput(steep_approach));
1915 tie_output(node, "egpws-alert-discrete-1", &mk_aoutput(egpws_alert_discrete_1));
1916 tie_output(node, "egpwc-logic-discretes", &mk_aoutput(egpwc_logic_discretes));
1917 tie_output(node, "mode6-callouts-discrete-1", &mk_aoutput(mode6_callouts_discrete_1));
1918 tie_output(node, "mode6-callouts-discrete-2", &mk_aoutput(mode6_callouts_discrete_2));
1919 tie_output(node, "egpws-alert-discrete-2", &mk_aoutput(egpws_alert_discrete_2));
1920 tie_output(node, "egpwc-alert-discrete-3", &mk_aoutput(egpwc_alert_discrete_3));
1924 MK_VIII::IOHandler::get_discrete_input (bool *ptr) const
1930 MK_VIII::IOHandler::set_discrete_input (bool *ptr, bool value)
1935 if (mk->system_handler.state == SystemHandler::STATE_ON)
1937 if (ptr == &mk_dinput(momentary_flap_override))
1939 if (mk->configuration_module.state == ConfigurationModule::STATE_OK
1940 && mk->self_test_handler.state == SelfTestHandler::STATE_NONE
1941 && conf.momentary_flap_override_enabled
1943 mk_doutput(flap_override) = ! mk_doutput(flap_override);
1945 else if (ptr == &mk_dinput(self_test))
1946 mk->self_test_handler.handle_button_event(value);
1947 else if (ptr == &mk_dinput(glideslope_cancel))
1949 if (mk->configuration_module.state == ConfigurationModule::STATE_OK
1950 && mk->self_test_handler.state == SelfTestHandler::STATE_NONE
1953 if (! mk_doutput(glideslope_cancel))
1957 // Although we are not called from update(), we are
1958 // sure that the inputs we use here are defined,
1959 // since state is STATE_ON.
1961 if (! mk_data(glideslope_deviation_dots).ncd
1962 && ! mk_data(radio_altitude).ncd
1963 && mk_data(radio_altitude).get() <= 2000
1964 && mk_data(radio_altitude).get() >= 30)
1965 mk_doutput(glideslope_cancel) = true;
1967 // else do nothing ([PILOT] page 22: "Glideslope Cancel
1968 // can not be deselected (reset) by again pressing the
1969 // Glideslope Cancel switch.")
1972 else if (ptr == &mk_dinput(steep_approach))
1974 if (mk->configuration_module.state == ConfigurationModule::STATE_OK
1975 && mk->self_test_handler.state == SelfTestHandler::STATE_NONE
1976 && momentary_steep_approach_enabled()
1978 mk_doutput(steep_approach) = ! mk_doutput(steep_approach);
1984 if (mk->system_handler.state == SystemHandler::STATE_ON)
1985 update_alternate_discrete_input(ptr);
1989 MK_VIII::IOHandler::present_status_section (const char *name)
1991 printf("%s\n", name);
1995 MK_VIII::IOHandler::present_status_item (const char *name, const char *value)
1998 printf("\t%-32s %s\n", name, value);
2000 printf("\t%s\n", name);
2004 MK_VIII::IOHandler::present_status_subitem (const char *name)
2006 printf("\t\t%s\n", name);
2010 MK_VIII::IOHandler::present_status ()
2014 if (mk->system_handler.state != SystemHandler::STATE_ON)
2017 present_status_section("EGPWC CONFIGURATION");
2018 present_status_item("PART NUMBER:", "965-1220-000"); // [SPEC] 1.1
2019 present_status_item("MOD STATUS:", "N/A");
2020 present_status_item("SERIAL NUMBER:", "N/A");
2022 present_status_item("APPLICATION S/W VERSION:", FLIGHTGEAR_VERSION);
2023 present_status_item("TERRAIN DATABASE VERSION:", FLIGHTGEAR_VERSION);
2024 present_status_item("ENVELOPE MOD DATABASE VERSION:", "N/A");
2025 present_status_item("BOOT CODE VERSION:", FLIGHTGEAR_VERSION);
2028 present_status_section("CURRENT FAULTS");
2029 if (mk->configuration_module.state == ConfigurationModule::STATE_OK && ! mk->fault_handler.has_faults())
2030 present_status_item("NO FAULTS");
2033 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
2035 present_status_item("GPWS COMPUTER FAULTS:");
2036 switch (mk->configuration_module.state)
2038 case ConfigurationModule::STATE_INVALID_DATABASE:
2039 present_status_subitem("APPLICATION DATABASE FAILED");
2042 case ConfigurationModule::STATE_INVALID_AIRCRAFT_TYPE:
2043 present_status_subitem("CONFIGURATION TYPE INVALID");
2047 assert_not_reached();
2053 present_status_item("GPWS COMPUTER OK");
2054 present_status_item("GPWS EXTERNAL FAULTS:");
2056 static const char *fault_names[] = {
2057 "ALL MODES INHIBIT",
2060 "MOMENTARY FLAP OVERRIDE INVALID",
2061 "SELF TEST INVALID",
2062 "GLIDESLOPE CANCEL INVALID",
2063 "STEEP APPROACH INVALID",
2066 "MODES 1-4 INPUTS INVALID",
2067 "MODE 5 INPUTS INVALID",
2068 "MODE 6 INPUTS INVALID",
2069 "BANK ANGLE INPUTS INVALID",
2070 "TCF INPUTS INVALID"
2073 for (size_t i = 0; i < n_elements(fault_names); i++)
2074 if (mk->fault_handler.faults[i])
2075 present_status_subitem(fault_names[i]);
2080 present_status_section("CONFIGURATION:");
2082 static const char *category_names[] = {
2085 "POSITION INPUT TYPE",
2086 "CALLOUTS OPTION TYPE",
2088 "TERRAIN DISPLAY TYPE",
2090 "RADIO ALTITUDE TYPE",
2091 "NAVIGATION INPUT TYPE",
2093 "MAGNETIC HEADING TYPE",
2094 "WINDSHEAR INPUT TYPE",
2099 for (size_t i = 0; i < n_elements(category_names); i++)
2101 std::ostringstream value;
2102 value << "= " << mk->configuration_module.effective_categories[i];
2103 present_status_item(category_names[i], value.str().c_str());
2108 MK_VIII::IOHandler::get_present_status () const
2114 MK_VIII::IOHandler::set_present_status (bool value)
2116 if (value) present_status();
2120 ///////////////////////////////////////////////////////////////////////////////
2121 // VoicePlayer ////////////////////////////////////////////////////////////////
2122 ///////////////////////////////////////////////////////////////////////////////
2125 MK_VIII::VoicePlayer::Speaker::bind (SGPropertyNode *node)
2127 // uses xmlsound property names
2128 tie(node, "volume", &volume);
2129 tie(node, "pitch", &pitch);
2133 MK_VIII::VoicePlayer::Speaker::update_configuration ()
2135 map< string, SGSharedPtr<SGSoundSample> >::iterator iter;
2136 for (iter = player->samples.begin(); iter != player->samples.end(); iter++)
2138 SGSoundSample *sample = (*iter).second;
2140 sample->set_pitch(pitch);
2144 player->voice->volume_changed();
2147 MK_VIII::VoicePlayer::Voice::~Voice ()
2149 for (iter = elements.begin(); iter != elements.end(); iter++)
2150 delete *iter; // we owned the element
2155 MK_VIII::VoicePlayer::Voice::play ()
2157 iter = elements.begin();
2160 element->play(get_volume());
2164 MK_VIII::VoicePlayer::Voice::stop (bool now)
2168 if (now || element->silence)
2174 iter = elements.end() - 1; // stop after the current element finishes
2179 MK_VIII::VoicePlayer::Voice::set_volume (float _volume)
2186 MK_VIII::VoicePlayer::Voice::volume_changed ()
2189 element->set_volume(get_volume());
2193 MK_VIII::VoicePlayer::Voice::update ()
2197 if (! element->is_playing())
2199 if (++iter == elements.end())
2204 element->play(get_volume());
2210 MK_VIII::VoicePlayer::~VoicePlayer ()
2212 vector<Voice *>::iterator iter1;
2213 for (iter1 = _voices.begin(); iter1 != _voices.end(); iter1++)
2220 MK_VIII::VoicePlayer::init ()
2222 #define STDPAUSE 0.75 // [SPEC] 6.4.4: "the standard 0.75 second delay"
2224 SGSoundMgr *smgr = globals->get_soundmgr();
2225 _sgr = smgr->find("avionics", true);
2226 _sgr->tie_to_listener();
2228 make_voice(&voices.application_data_base_failed, "application-data-base-failed");
2229 make_voice(&voices.bank_angle, "bank-angle");
2230 make_voice(&voices.bank_angle_bank_angle, "bank-angle", "bank-angle");
2231 make_voice(&voices.bank_angle_bank_angle_3, "bank-angle", "bank-angle", 3.0);
2232 make_voice(&voices.bank_angle_inop, "bank-angle-inop");
2233 make_voice(&voices.bank_angle_pause_bank_angle, "bank-angle", STDPAUSE, "bank-angle");
2234 make_voice(&voices.bank_angle_pause_bank_angle_3, "bank-angle", STDPAUSE, "bank-angle", 3.0);
2235 make_voice(&voices.callouts_inop, "callouts-inop");
2236 make_voice(&voices.configuration_type_invalid, "configuration-type-invalid");
2237 make_voice(&voices.dont_sink, "dont-sink");
2238 make_voice(&voices.dont_sink_pause_dont_sink, "dont-sink", STDPAUSE, "dont-sink");
2239 make_voice(&voices.five_hundred_above, "500-above");
2240 make_voice(&voices.glideslope, "glideslope");
2241 make_voice(&voices.glideslope_inop, "glideslope-inop");
2242 make_voice(&voices.gpws_inop, "gpws-inop");
2243 make_voice(&voices.hard_glideslope, "glideslope", "glideslope", 3.0);
2244 make_voice(&voices.minimums, "minimums");
2245 make_voice(&voices.minimums_minimums, "minimums", "minimums");
2246 make_voice(&voices.pull_up, "pull-up");
2247 make_voice(&voices.sink_rate, "sink-rate");
2248 make_voice(&voices.sink_rate_pause_sink_rate, "sink-rate", STDPAUSE, "sink-rate");
2249 make_voice(&voices.soft_glideslope, new Voice::SampleElement(get_sample("glideslope"), modify_amplitude(1.0, -6)));
2250 make_voice(&voices.terrain, "terrain");
2251 make_voice(&voices.terrain_pause_terrain, "terrain", STDPAUSE, "terrain");
2252 make_voice(&voices.too_low_flaps, "too-low-flaps");
2253 make_voice(&voices.too_low_gear, "too-low-gear");
2254 make_voice(&voices.too_low_terrain, "too-low-terrain");
2256 for (unsigned i = 0; i < n_altitude_callouts; i++)
2258 std::ostringstream name;
2259 name << "altitude-" << mk->mode6_handler.altitude_callout_definitions[i];
2260 make_voice(&voices.altitude_callouts[i], name.str().c_str());
2263 speaker.update_configuration();
2267 MK_VIII::VoicePlayer::get_sample (const char *name)
2269 std::ostringstream refname;
2270 refname << mk->name << "[" << mk->num << "]" << "/" << name;
2272 SGSoundSample *sample = _sgr->find(refname.str());
2275 SGPath sample_path(globals->get_fg_root());
2276 sample_path.append("Sounds/mk-viii");
2278 string filename = string(name) + ".wav";
2281 sample = new SGSoundSample(sample_path.c_str(), filename.c_str());
2283 catch (const sg_exception &e)
2285 SG_LOG(SG_INSTR, SG_ALERT, "Error loading MK VIII sound sample \"" + filename + "\": " + e.getFormattedMessage());
2289 _sgr->add(sample, refname.str());
2290 samples[refname.str()] = sample;
2297 MK_VIII::VoicePlayer::play (Voice *_voice, unsigned int flags)
2299 if (test_bits(flags, PLAY_NOW) || ! voice || voice->element->silence)
2305 looped = test_bits(flags, PLAY_LOOPED);
2308 next_looped = false;
2314 next_voice = _voice;
2315 next_looped = test_bits(flags, PLAY_LOOPED);
2320 MK_VIII::VoicePlayer::stop (unsigned int flags)
2324 voice->stop(test_bits(flags, STOP_NOW));
2334 MK_VIII::VoicePlayer::set_volume (float _volume)
2338 voice->volume_changed();
2342 MK_VIII::VoicePlayer::update ()
2350 if (! voice->element || voice->element->silence)
2353 looped = next_looped;
2356 next_looped = false;
2363 if (! voice->element)
2374 ///////////////////////////////////////////////////////////////////////////////
2375 // SelfTestHandler ////////////////////////////////////////////////////////////
2376 ///////////////////////////////////////////////////////////////////////////////
2379 MK_VIII::SelfTestHandler::_was_here (int position)
2381 if (position > current)
2390 MK_VIII::SelfTestHandler::Action
2391 MK_VIII::SelfTestHandler::sleep (double duration)
2393 Action _action = { ACTION_SLEEP, duration, NULL };
2397 MK_VIII::SelfTestHandler::Action
2398 MK_VIII::SelfTestHandler::play (VoicePlayer::Voice *voice)
2400 mk->voice_player.play(voice);
2401 Action _action = { ACTION_VOICE, 0, NULL };
2405 MK_VIII::SelfTestHandler::Action
2406 MK_VIII::SelfTestHandler::discrete_on (bool *discrete, double duration)
2409 return sleep(duration);
2412 MK_VIII::SelfTestHandler::Action
2413 MK_VIII::SelfTestHandler::discrete_on_off (bool *discrete, double duration)
2416 Action _action = { ACTION_SLEEP | ACTION_DISCRETE_ON_OFF, duration, discrete };
2420 MK_VIII::SelfTestHandler::Action
2421 MK_VIII::SelfTestHandler::discrete_on_off (bool *discrete, VoicePlayer::Voice *voice)
2424 mk->voice_player.play(voice);
2425 Action _action = { ACTION_VOICE | ACTION_DISCRETE_ON_OFF, 0, discrete };
2429 MK_VIII::SelfTestHandler::Action
2430 MK_VIII::SelfTestHandler::done ()
2432 Action _action = { ACTION_DONE, 0, NULL };
2436 MK_VIII::SelfTestHandler::Action
2437 MK_VIII::SelfTestHandler::run ()
2439 // Note that "Terrain INOP" and "Terrain NA" are or'ed to the same
2440 // output discrete (see [SPEC] 6.9.3.5).
2442 #define was_here() was_here_offset(0)
2443 #define was_here_offset(offset) _was_here(__LINE__ * 20 + (offset))
2447 if (mk->configuration_module.state == ConfigurationModule::STATE_INVALID_DATABASE)
2448 return play(mk_voice(application_data_base_failed));
2449 else if (mk->configuration_module.state == ConfigurationModule::STATE_INVALID_AIRCRAFT_TYPE)
2450 return play(mk_voice(configuration_type_invalid));
2452 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
2456 return discrete_on(&mk_doutput(gpws_inop), 0.7);
2458 return sleep(0.7); // W/S INOP
2460 return discrete_on(&mk_doutput(tad_inop), 0.7);
2463 mk_doutput(gpws_inop) = false;
2464 // do not disable tad_inop since we must enable Terrain NA
2465 // do not disable W/S INOP since we do not emulate it
2466 return sleep(0.7); // Terrain NA
2470 mk_doutput(tad_inop) = false; // disable Terrain NA
2471 if (mk->io_handler.conf.momentary_flap_override_enabled)
2472 return discrete_on_off(&mk_doutput(flap_override), 1.0);
2475 return discrete_on_off(&mk_doutput(audio_on), 1.0);
2478 if (mk->io_handler.momentary_steep_approach_enabled())
2479 return discrete_on_off(&mk_doutput(steep_approach), 1.0);
2484 if (mk_dinput(glideslope_inhibit))
2485 goto glideslope_end;
2488 if (mk->fault_handler.faults[FaultHandler::FAULT_MODE5_INPUTS_INVALID])
2489 goto glideslope_inop;
2493 return discrete_on_off(&mk_doutput(gpws_alert), mk_voice(glideslope));
2495 return discrete_on_off(&mk_doutput(glideslope_cancel), 0.7);
2496 goto glideslope_end;
2499 return play(mk_voice(glideslope_inop));
2504 if (mk->fault_handler.faults[FaultHandler::FAULT_MODES14_INPUTS_INVALID])
2508 return discrete_on_off(&mk_doutput(gpws_warning), mk_voice(pull_up));
2512 return play(mk_voice(gpws_inop));
2517 if (mk_dinput(self_test)) // proceed to long self test
2522 if (mk->mode6_handler.conf.bank_angle_enabled
2523 && mk->fault_handler.faults[FaultHandler::FAULT_BANK_ANGLE_INPUTS_INVALID])
2524 return play(mk_voice(bank_angle_inop));
2528 if (mk->mode6_handler.altitude_callouts_enabled()
2529 && mk->fault_handler.faults[FaultHandler::FAULT_MODE6_INPUTS_INVALID])
2530 return play(mk_voice(callouts_inop));
2538 mk_doutput(gpws_inop) = true;
2539 // do not enable W/S INOP, since we do not emulate it
2540 mk_doutput(tad_inop) = true; // Terrain INOP, Terrain NA
2542 return play(mk_voice(sink_rate));
2545 return play(mk_voice(pull_up));
2547 return play(mk_voice(terrain));
2549 return play(mk_voice(pull_up));
2551 return play(mk_voice(dont_sink));
2553 return play(mk_voice(too_low_terrain));
2555 return play(mk->mode4_handler.conf.voice_too_low_gear);
2557 return play(mk_voice(too_low_flaps));
2559 return play(mk_voice(too_low_terrain));
2561 return play(mk_voice(glideslope));
2564 if (mk->mode6_handler.conf.bank_angle_enabled)
2565 return play(mk_voice(bank_angle));
2570 if (! mk->mode6_handler.altitude_callouts_enabled())
2571 goto callouts_disabled;
2575 if (mk->mode6_handler.conf.minimums_enabled)
2576 return play(mk_voice(minimums));
2580 if (mk->mode6_handler.conf.above_field_voice)
2581 return play(mk->mode6_handler.conf.above_field_voice);
2583 for (unsigned i = 0; i < n_altitude_callouts; i++)
2584 if (! was_here_offset(i))
2586 if (mk->mode6_handler.conf.altitude_callouts_enabled[i])
2587 return play(mk_altitude_voice(i));
2591 if (mk->mode6_handler.conf.smart_500_enabled)
2592 return play(mk_altitude_voice(Mode6Handler::ALTITUDE_CALLOUT_500));
2597 return play(mk_voice(minimums_minimums));
2602 if (mk->tcf_handler.conf.enabled)
2603 return play(mk_voice(too_low_terrain));
2610 MK_VIII::SelfTestHandler::start ()
2612 assert(state == STATE_START);
2614 memcpy(&saved_outputs, &mk->io_handler.outputs, sizeof(mk->io_handler.outputs));
2615 memset(&mk->io_handler.outputs, 0, sizeof(mk->io_handler.outputs));
2617 // [SPEC] 6.10.6: "The self-test results are annunciated, at 6db
2618 // lower than the normal audio level selected for the aircraft"
2619 mk->voice_player.set_volume(modify_amplitude(mk->voice_player.conf.volume, -6));
2621 if (mk_dinput(mode6_low_volume))
2622 mk->mode6_handler.set_volume(1.0);
2624 state = STATE_RUNNING;
2625 cancel = CANCEL_NONE;
2626 memset(&action, 0, sizeof(action));
2631 MK_VIII::SelfTestHandler::stop ()
2633 if (state != STATE_NONE)
2635 if (state != STATE_START)
2637 mk->voice_player.stop(VoicePlayer::STOP_NOW);
2638 mk->voice_player.set_volume(mk_dinput(audio_inhibit) ? 0.0 : mk->voice_player.conf.volume);
2640 if (mk_dinput(mode6_low_volume))
2641 mk->mode6_handler.set_volume(modify_amplitude(1.0, -6));
2643 memcpy(&mk->io_handler.outputs, &saved_outputs, sizeof(mk->io_handler.outputs));
2646 button_pressed = false;
2648 // reset self-test handler position
2654 MK_VIII::SelfTestHandler::handle_button_event (bool value)
2656 if (state == STATE_NONE)
2659 state = STATE_START;
2661 else if (state == STATE_START)
2664 state = STATE_NONE; // cancel the not-yet-started test
2666 else if (cancel == CANCEL_NONE)
2670 assert(! button_pressed);
2671 button_pressed = true;
2672 button_press_timestamp = globals->get_sim_time_sec();
2678 if (globals->get_sim_time_sec() - button_press_timestamp < 2)
2679 cancel = CANCEL_SHORT;
2681 cancel = CANCEL_LONG;
2688 MK_VIII::SelfTestHandler::update ()
2690 if (state == STATE_NONE)
2692 else if (state == STATE_START)
2694 if (mk->state_handler.ground && ! mk->io_handler.steep_approach())
2704 if (button_pressed && cancel == CANCEL_NONE)
2706 if (globals->get_sim_time_sec() - button_press_timestamp >= 2)
2707 cancel = CANCEL_LONG;
2711 if (! mk->state_handler.ground || cancel != CANCEL_NONE)
2717 if (test_bits(action.flags, ACTION_SLEEP))
2719 if (globals->get_sim_time_sec() - sleep_start < action.sleep_duration)
2722 if (test_bits(action.flags, ACTION_VOICE))
2724 if (mk->voice_player.voice)
2727 if (test_bits(action.flags, ACTION_DISCRETE_ON_OFF))
2728 *action.discrete = false;
2732 if (test_bits(action.flags, ACTION_SLEEP))
2733 sleep_start = globals->get_sim_time_sec();
2734 if (test_bits(action.flags, ACTION_DONE))
2743 ///////////////////////////////////////////////////////////////////////////////
2744 // AlertHandler ///////////////////////////////////////////////////////////////
2745 ///////////////////////////////////////////////////////////////////////////////
2748 MK_VIII::AlertHandler::select_voice_alerts (unsigned int test)
2750 if (has_alerts(test))
2752 voice_alerts = alerts & test;
2757 voice_alerts &= ~test;
2758 if (voice_alerts == 0)
2759 mk->voice_player.stop();
2766 MK_VIII::AlertHandler::boot ()
2772 MK_VIII::AlertHandler::reposition ()
2776 mk->io_handler.set_lamp(IOHandler::LAMP_NONE);
2777 mk->voice_player.stop(VoicePlayer::STOP_NOW);
2781 MK_VIII::AlertHandler::reset ()
2786 repeated_alerts = 0;
2787 altitude_callout_voice = NULL;
2791 MK_VIII::AlertHandler::update ()
2793 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
2796 // Lamps and voices are prioritized according to [SPEC] 6.9.2.
2798 // Voices which can interrupt other voices (VoicePlayer::PLAY_NOW)
2799 // are specified by [INSTALL] appendix E 5.3.5.
2801 // When a voice is overriden by a higher priority voice and the
2802 // overriding voice stops, we restore the previous voice if it was
2803 // looped (this is not specified by [SPEC] but seems to make sense).
2807 if (has_alerts(ALERT_MODE1_PULL_UP | ALERT_MODE2A | ALERT_MODE2B))
2808 mk->io_handler.set_lamp(IOHandler::LAMP_WARNING);
2809 else if (has_alerts(ALERT_MODE1_SINK_RATE
2810 | ALERT_MODE2A_PREFACE
2811 | ALERT_MODE2B_PREFACE
2812 | ALERT_MODE2A_ALTITUDE_GAIN_TERRAIN_CLOSING
2813 | ALERT_MODE2A_ALTITUDE_GAIN
2814 | ALERT_MODE2B_LANDING_MODE
2816 | ALERT_MODE4_TOO_LOW_FLAPS
2817 | ALERT_MODE4_TOO_LOW_GEAR
2818 | ALERT_MODE4AB_TOO_LOW_TERRAIN
2819 | ALERT_MODE4C_TOO_LOW_TERRAIN
2820 | ALERT_TCF_TOO_LOW_TERRAIN))
2821 mk->io_handler.set_lamp(IOHandler::LAMP_CAUTION);
2822 else if (has_alerts(ALERT_MODE5_SOFT | ALERT_MODE5_HARD))
2823 mk->io_handler.set_lamp(IOHandler::LAMP_GLIDESLOPE);
2825 mk->io_handler.set_lamp(IOHandler::LAMP_NONE);
2829 if (select_voice_alerts(ALERT_MODE1_PULL_UP))
2831 if (! has_old_alerts(ALERT_MODE1_PULL_UP))
2833 if (mk->voice_player.voice != mk_voice(sink_rate_pause_sink_rate))
2834 mk->voice_player.play(mk_voice(sink_rate), VoicePlayer::PLAY_NOW);
2835 mk->voice_player.play(mk_voice(pull_up), VoicePlayer::PLAY_LOOPED);
2838 else if (select_voice_alerts(ALERT_MODE2A_PREFACE | ALERT_MODE2B_PREFACE))
2840 if (! has_old_alerts(ALERT_MODE2A_PREFACE | ALERT_MODE2B_PREFACE))
2841 mk->voice_player.play(mk_voice(terrain_pause_terrain), VoicePlayer::PLAY_NOW);
2843 else if (select_voice_alerts(ALERT_MODE2A | ALERT_MODE2B))
2845 if (mk->voice_player.voice != mk_voice(pull_up))
2846 mk->voice_player.play(mk_voice(pull_up), VoicePlayer::PLAY_NOW | VoicePlayer::PLAY_LOOPED);
2848 else if (select_voice_alerts(ALERT_MODE2A_ALTITUDE_GAIN_TERRAIN_CLOSING | ALERT_MODE2B_LANDING_MODE))
2850 if (mk->voice_player.voice != mk_voice(terrain))
2851 mk->voice_player.play(mk_voice(terrain), VoicePlayer::PLAY_LOOPED);
2853 else if (select_voice_alerts(ALERT_MODE6_MINIMUMS))
2855 if (! has_old_alerts(ALERT_MODE6_MINIMUMS))
2856 mk->voice_player.play(mk_voice(minimums_minimums));
2858 else if (select_voice_alerts(ALERT_MODE4AB_TOO_LOW_TERRAIN | ALERT_MODE4C_TOO_LOW_TERRAIN))
2860 if (must_play_voice(ALERT_MODE4AB_TOO_LOW_TERRAIN | ALERT_MODE4C_TOO_LOW_TERRAIN))
2861 mk->voice_player.play(mk_voice(too_low_terrain));
2863 else if (select_voice_alerts(ALERT_TCF_TOO_LOW_TERRAIN))
2865 if (must_play_voice(ALERT_TCF_TOO_LOW_TERRAIN))
2866 mk->voice_player.play(mk_voice(too_low_terrain));
2868 else if (select_voice_alerts(ALERT_MODE6_ALTITUDE_CALLOUT))
2870 if (! has_old_alerts(ALERT_MODE6_ALTITUDE_CALLOUT))
2872 assert(altitude_callout_voice != NULL);
2873 mk->voice_player.play(altitude_callout_voice);
2876 else if (select_voice_alerts(ALERT_MODE4_TOO_LOW_GEAR))
2878 if (must_play_voice(ALERT_MODE4_TOO_LOW_GEAR))
2879 mk->voice_player.play(mk->mode4_handler.conf.voice_too_low_gear);
2881 else if (select_voice_alerts(ALERT_MODE4_TOO_LOW_FLAPS))
2883 if (must_play_voice(ALERT_MODE4_TOO_LOW_FLAPS))
2884 mk->voice_player.play(mk_voice(too_low_flaps));
2886 else if (select_voice_alerts(ALERT_MODE1_SINK_RATE))
2888 if (must_play_voice(ALERT_MODE1_SINK_RATE))
2889 mk->voice_player.play(mk_voice(sink_rate_pause_sink_rate));
2890 // [SPEC] 6.2.1: "During the time that the voice message for the
2891 // outer envelope is inhibited and the caution/warning lamp is
2892 // on, the Mode 5 alert message is allowed to annunciate for
2893 // excessive glideslope deviation conditions."
2894 else if (mk->voice_player.voice != mk_voice(sink_rate_pause_sink_rate)
2895 && mk->voice_player.next_voice != mk_voice(sink_rate_pause_sink_rate))
2897 if (has_alerts(ALERT_MODE5_HARD))
2899 voice_alerts |= ALERT_MODE5_HARD;
2900 if (mk->voice_player.voice != mk_voice(hard_glideslope))
2901 mk->voice_player.play(mk_voice(hard_glideslope), VoicePlayer::PLAY_LOOPED);
2903 else if (has_alerts(ALERT_MODE5_SOFT))
2905 voice_alerts |= ALERT_MODE5_SOFT;
2906 if (must_play_voice(ALERT_MODE5_SOFT))
2907 mk->voice_player.play(mk_voice(soft_glideslope));
2911 else if (select_voice_alerts(ALERT_MODE3))
2913 if (must_play_voice(ALERT_MODE3))
2914 mk->voice_player.play(mk_voice(dont_sink_pause_dont_sink));
2916 else if (select_voice_alerts(ALERT_MODE5_HARD))
2918 if (mk->voice_player.voice != mk_voice(hard_glideslope))
2919 mk->voice_player.play(mk_voice(hard_glideslope), VoicePlayer::PLAY_LOOPED);
2921 else if (select_voice_alerts(ALERT_MODE5_SOFT))
2923 if (must_play_voice(ALERT_MODE5_SOFT))
2924 mk->voice_player.play(mk_voice(soft_glideslope));
2926 else if (select_voice_alerts(ALERT_MODE6_LOW_BANK_ANGLE_3))
2928 if (mk->voice_player.voice != mk_voice(bank_angle_bank_angle_3))
2929 mk->voice_player.play(mk_voice(bank_angle_bank_angle_3), VoicePlayer::PLAY_LOOPED);
2931 else if (select_voice_alerts(ALERT_MODE6_HIGH_BANK_ANGLE_3))
2933 if (mk->voice_player.voice != mk_voice(bank_angle_pause_bank_angle_3))
2934 mk->voice_player.play(mk_voice(bank_angle_pause_bank_angle_3), VoicePlayer::PLAY_LOOPED);
2936 else if (select_voice_alerts(ALERT_MODE6_LOW_BANK_ANGLE_2))
2938 if (! has_old_alerts(ALERT_MODE6_LOW_BANK_ANGLE_2 | ALERT_MODE6_HIGH_BANK_ANGLE_2))
2939 mk->voice_player.play(mk_voice(bank_angle_bank_angle));
2941 else if (select_voice_alerts(ALERT_MODE6_HIGH_BANK_ANGLE_2))
2943 if (! has_old_alerts(ALERT_MODE6_LOW_BANK_ANGLE_2 | ALERT_MODE6_HIGH_BANK_ANGLE_2))
2944 mk->voice_player.play(mk_voice(bank_angle_pause_bank_angle));
2946 else if (select_voice_alerts(ALERT_MODE6_LOW_BANK_ANGLE_1))
2948 if (! has_old_alerts(ALERT_MODE6_LOW_BANK_ANGLE_1 | ALERT_MODE6_HIGH_BANK_ANGLE_1))
2949 mk->voice_player.play(mk_voice(bank_angle_bank_angle));
2951 else if (select_voice_alerts(ALERT_MODE6_HIGH_BANK_ANGLE_1))
2953 if (! has_old_alerts(ALERT_MODE6_LOW_BANK_ANGLE_1 | ALERT_MODE6_HIGH_BANK_ANGLE_1))
2954 mk->voice_player.play(mk_voice(bank_angle_pause_bank_angle));
2959 old_alerts = alerts;
2960 repeated_alerts = 0;
2961 altitude_callout_voice = NULL;
2965 MK_VIII::AlertHandler::set_alerts (unsigned int _alerts,
2967 VoicePlayer::Voice *_altitude_callout_voice)
2970 if (test_bits(flags, ALERT_FLAG_REPEAT))
2971 repeated_alerts |= _alerts;
2972 if (_altitude_callout_voice)
2973 altitude_callout_voice = _altitude_callout_voice;
2977 MK_VIII::AlertHandler::unset_alerts (unsigned int _alerts)
2980 repeated_alerts &= ~_alerts;
2983 ///////////////////////////////////////////////////////////////////////////////
2984 // StateHandler ///////////////////////////////////////////////////////////////
2985 ///////////////////////////////////////////////////////////////////////////////
2988 MK_VIII::StateHandler::update_ground ()
2992 if (! mk_ainput(computed_airspeed).ncd && mk_ainput(computed_airspeed).get() > 60
2993 && ! mk_data(radio_altitude).ncd && mk_data(radio_altitude).get() > 30)
2995 if (potentially_airborne_timer.start_or_elapsed() > 1)
2999 potentially_airborne_timer.stop();
3003 if (! mk_ainput(computed_airspeed).ncd && mk_ainput(computed_airspeed).get() < 40
3004 && ! mk_data(radio_altitude).ncd && mk_data(radio_altitude).get() < 30)
3010 MK_VIII::StateHandler::enter_ground ()
3013 mk->io_handler.enter_ground();
3015 // [SPEC] 6.0.1 does not specify this, but it seems to be an
3016 // omission; at this point, we must make sure that we are in takeoff
3017 // mode (otherwise the next takeoff might happen in approach mode).
3023 MK_VIII::StateHandler::leave_ground ()
3026 mk->mode2_handler.leave_ground();
3030 MK_VIII::StateHandler::update_takeoff ()
3034 // [SPEC] 6.0.2 is somewhat confusing: it mentions hardcoded
3035 // terrain clearance (500, 750) and airspeed (178, 200) values,
3036 // but it also mentions the mode 4A boundary, which varies as a
3037 // function of aircraft type. We consider that the mentioned
3038 // values are examples, and that we should use the mode 4A upper
3041 if (! mk_data(terrain_clearance).ncd
3042 && ! mk_ainput(computed_airspeed).ncd
3043 && mk_data(terrain_clearance).get() > mk->mode4_handler.get_upper_agl(mk->mode4_handler.conf.modes->ac))
3048 if (! mk_data(radio_altitude).ncd
3049 && mk_data(radio_altitude).get() < mk->mode4_handler.conf.modes->b->min_agl1
3050 && mk->io_handler.flaps_down()
3051 && mk_dinput(landing_gear))
3057 MK_VIII::StateHandler::enter_takeoff ()
3060 mk->io_handler.enter_takeoff();
3061 mk->mode2_handler.enter_takeoff();
3062 mk->mode3_handler.enter_takeoff();
3063 mk->mode6_handler.enter_takeoff();
3067 MK_VIII::StateHandler::leave_takeoff ()
3070 mk->mode6_handler.leave_takeoff();
3074 MK_VIII::StateHandler::post_reposition ()
3076 // Synchronize the state with the current situation.
3079 (! mk_ainput(computed_airspeed).ncd && mk_ainput(computed_airspeed).get() > 60
3080 && ! mk_data(radio_altitude).ncd && mk_data(radio_altitude).get() > 30);
3082 bool _takeoff = _ground;
3084 if (ground && ! _ground)
3086 else if (! ground && _ground)
3089 if (takeoff && ! _takeoff)
3091 else if (! takeoff && _takeoff)
3096 MK_VIII::StateHandler::update ()
3098 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
3105 ///////////////////////////////////////////////////////////////////////////////
3106 // Mode1Handler ///////////////////////////////////////////////////////////////
3107 ///////////////////////////////////////////////////////////////////////////////
3110 MK_VIII::Mode1Handler::get_pull_up_bias ()
3112 // [PILOT] page 54: "Steep Approach has priority over Flap Override
3113 // if selected simultaneously."
3115 if (mk->io_handler.steep_approach())
3117 else if (conf.envelopes->flap_override_bias && mk->io_handler.flap_override())
3124 MK_VIII::Mode1Handler::is_pull_up ()
3126 if (! mk->io_handler.gpws_inhibit()
3127 && ! mk->state_handler.ground // [1]
3128 && ! mk_data(radio_altitude).ncd
3129 && ! mk_data(barometric_altitude_rate).ncd
3130 && mk_data(radio_altitude).get() > conf.envelopes->min_agl)
3132 if (mk_data(radio_altitude).get() < conf.envelopes->pull_up_max_agl1)
3134 if (mk_data(radio_altitude).get() < conf.envelopes->pull_up_min_agl1(mk_data(barometric_altitude_rate).get() + get_pull_up_bias()))
3137 else if (mk_data(radio_altitude).get() < conf.envelopes->pull_up_max_agl2)
3139 if (mk_data(radio_altitude).get() < conf.envelopes->pull_up_min_agl2(mk_data(barometric_altitude_rate).get() + get_pull_up_bias()))
3148 MK_VIII::Mode1Handler::update_pull_up ()
3152 if (! mk_test_alert(MODE1_PULL_UP))
3154 // [SPEC] 6.2.1: at least one sink rate must be issued
3155 // before switching to pull up; if no sink rate has
3156 // occurred, a 0.2 second delay is used.
3157 if (mk->voice_player.voice == mk_voice(sink_rate_pause_sink_rate)
3158 || pull_up_timer.start_or_elapsed() >= 0.2)
3159 mk_set_alerts(mk_alert(MODE1_PULL_UP));
3164 pull_up_timer.stop();
3165 mk_unset_alerts(mk_alert(MODE1_PULL_UP));
3170 MK_VIII::Mode1Handler::get_sink_rate_bias ()
3172 // [PILOT] page 54: "Steep Approach has priority over Flap Override
3173 // if selected simultaneously."
3175 if (mk->io_handler.steep_approach())
3177 else if (conf.envelopes->flap_override_bias && mk->io_handler.flap_override())
3179 else if (! mk_data(glideslope_deviation_dots).ncd)
3183 if (mk_data(glideslope_deviation_dots).get() <= -2)
3185 else if (mk_data(glideslope_deviation_dots).get() < 0)
3186 bias = -150 * mk_data(glideslope_deviation_dots).get();
3188 if (mk_data(radio_altitude).get() < 100)
3189 bias *= 0.01 * mk_data(radio_altitude).get();
3198 MK_VIII::Mode1Handler::is_sink_rate ()
3200 return ! mk->io_handler.gpws_inhibit()
3201 && ! mk->state_handler.ground // [1]
3202 && ! mk_data(radio_altitude).ncd
3203 && ! mk_data(barometric_altitude_rate).ncd
3204 && mk_data(radio_altitude).get() > conf.envelopes->min_agl
3205 && mk_data(radio_altitude).get() < 2450
3206 && mk_data(radio_altitude).get() < -572 - 0.6035 * (mk_data(barometric_altitude_rate).get() + get_sink_rate_bias());
3210 MK_VIII::Mode1Handler::get_sink_rate_tti ()
3212 return mk_data(radio_altitude).get() / fabs(mk_data(barometric_altitude_rate).get());
3216 MK_VIII::Mode1Handler::update_sink_rate ()
3220 if (mk_test_alert(MODE1_SINK_RATE))
3222 double tti = get_sink_rate_tti();
3223 if (tti <= sink_rate_tti * 0.8)
3225 // ~20% degradation, warn again and store the new reference tti
3226 sink_rate_tti = tti;
3227 mk_repeat_alert(mk_alert(MODE1_SINK_RATE));
3232 if (sink_rate_timer.start_or_elapsed() >= 0.8)
3234 mk_set_alerts(mk_alert(MODE1_SINK_RATE));
3235 sink_rate_tti = get_sink_rate_tti();
3241 sink_rate_timer.stop();
3242 mk_unset_alerts(mk_alert(MODE1_SINK_RATE));
3247 MK_VIII::Mode1Handler::update ()
3249 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
3256 ///////////////////////////////////////////////////////////////////////////////
3257 // Mode2Handler ///////////////////////////////////////////////////////////////
3258 ///////////////////////////////////////////////////////////////////////////////
3261 MK_VIII::Mode2Handler::ClosureRateFilter::limit_radio_altitude_rate (double r)
3263 // Limit radio altitude rate according to aircraft configuration,
3264 // allowing maximum sensitivity during cruise while providing
3265 // progressively less sensitivity during the landing phases of
3268 if (! mk_data(glideslope_deviation_dots).ncd && fabs(mk_data(glideslope_deviation_dots).get()) <= 2)
3269 { // gs deviation <= +- 2 dots
3270 if (mk_dinput(landing_gear) && mk->io_handler.flaps_down())
3271 SG_CLAMP_RANGE(r, -1000.0, 3000.0);
3272 else if (mk_dinput(landing_gear) || mk->io_handler.flaps_down())
3273 SG_CLAMP_RANGE(r, 0.0, 4000.0);
3275 SG_CLAMP_RANGE(r, 1000.0, 5000.0);
3278 { // no ILS, or gs deviation > +- 2 dots
3279 if (mk_dinput(landing_gear) && mk->io_handler.flaps_down())
3280 SG_CLAMP_RANGE(r, 0.0, 4000.0);
3281 else if (mk_dinput(landing_gear) || mk->io_handler.flaps_down())
3282 SG_CLAMP_RANGE(r, 1000.0, 5000.0);
3290 MK_VIII::Mode2Handler::ClosureRateFilter::init ()
3293 last_ra.set(&mk_data(radio_altitude));
3294 last_ba.set(&mk_ainput(uncorrected_barometric_altitude));
3301 MK_VIII::Mode2Handler::ClosureRateFilter::update ()
3303 double elapsed = timer.start_or_elapsed();
3307 if (! mk_data(radio_altitude).ncd && ! mk_ainput(uncorrected_barometric_altitude).ncd)
3309 if (! last_ra.ncd && ! last_ba.ncd)
3311 // compute radio and barometric altitude rates (positive value = descent)
3312 double ra_rate = -(mk_data(radio_altitude).get() - last_ra.get()) / elapsed * 60;
3313 double ba_rate = -(mk_ainput(uncorrected_barometric_altitude).get() - last_ba.get()) / elapsed * 60;
3315 // limit radio altitude rate according to aircraft configuration
3316 ra_rate = limit_radio_altitude_rate(ra_rate);
3318 // apply a low-pass filter to the radio altitude rate
3319 ra_rate = ra_filter.filter(ra_rate);
3320 // apply a high-pass filter to the barometric altitude rate
3321 ba_rate = ba_filter.filter(ba_rate);
3323 // combine both rates to obtain a closure rate
3324 output.set(ra_rate + ba_rate);
3337 last_ra.set(&mk_data(radio_altitude));
3338 last_ba.set(&mk_ainput(uncorrected_barometric_altitude));
3342 MK_VIII::Mode2Handler::b_conditions ()
3344 return mk->io_handler.flaps_down()
3345 || (! mk_data(glideslope_deviation_dots).ncd && fabs(mk_data(glideslope_deviation_dots).get()) < 2)
3346 || takeoff_timer.running;
3350 MK_VIII::Mode2Handler::is_a ()
3352 if (! mk->io_handler.gpws_inhibit()
3353 && ! mk->state_handler.ground // [1]
3354 && ! mk_data(radio_altitude).ncd
3355 && ! mk_ainput(computed_airspeed).ncd
3356 && ! closure_rate_filter.output.ncd
3357 && ! b_conditions())
3359 if (mk_data(radio_altitude).get() < 1220)
3361 if (mk_data(radio_altitude).get() < -1579 + 0.7895 * closure_rate_filter.output.get())
3368 if (mk_ainput(computed_airspeed).get() <= conf->airspeed1)
3370 else if (mk_ainput(computed_airspeed).get() >= conf->airspeed2)
3373 upper_limit = 1650 + 8.9 * (mk_ainput(computed_airspeed).get() - conf->airspeed1);
3375 if (mk_data(radio_altitude).get() < upper_limit)
3377 if (mk_data(radio_altitude).get() < 522 + 0.1968 * closure_rate_filter.output.get())
3387 MK_VIII::Mode2Handler::is_b ()
3389 if (! mk->io_handler.gpws_inhibit()
3390 && ! mk->state_handler.ground // [1]
3391 && ! mk_data(radio_altitude).ncd
3392 && ! mk_data(barometric_altitude_rate).ncd
3393 && ! closure_rate_filter.output.ncd
3395 && mk_data(radio_altitude).get() < 789)
3399 if (mk->io_handler.flaps_down())
3401 if (mk_data(barometric_altitude_rate).get() > -400)
3403 else if (mk_data(barometric_altitude_rate).get() < -1000)
3406 lower_limit = -66.777 - 0.667 * mk_data(barometric_altitude_rate).get();
3411 if (mk_data(radio_altitude).get() > lower_limit)
3413 if (mk_data(radio_altitude).get() < -1579 + 0.7895 * closure_rate_filter.output.get())
3422 MK_VIII::Mode2Handler::check_pull_up (unsigned int preface_alert,
3425 if (pull_up_timer.running)
3427 if (pull_up_timer.elapsed() >= 1)
3429 mk_unset_alerts(preface_alert);
3430 mk_set_alerts(alert);
3435 if (! mk->voice_player.voice)
3436 pull_up_timer.start();
3441 MK_VIII::Mode2Handler::update_a ()
3445 if (mk_test_alert(MODE2A_PREFACE))
3446 check_pull_up(mk_alert(MODE2A_PREFACE), mk_alert(MODE2A));
3447 else if (! mk_test_alert(MODE2A))
3449 mk_unset_alerts(mk_alert(MODE2A_ALTITUDE_GAIN) | mk_alert(MODE2A_ALTITUDE_GAIN_TERRAIN_CLOSING));
3450 mk_set_alerts(mk_alert(MODE2A_PREFACE));
3451 a_start_time = globals->get_sim_time_sec();
3452 pull_up_timer.stop();
3457 if (mk_test_alert(MODE2A_ALTITUDE_GAIN))
3459 if (mk->io_handler.gpws_inhibit()
3460 || mk->state_handler.ground // [1]
3461 || a_altitude_gain_timer.elapsed() >= 45
3462 || mk_data(radio_altitude).ncd
3463 || mk_ainput(uncorrected_barometric_altitude).ncd
3464 || mk_ainput(uncorrected_barometric_altitude).get() - a_altitude_gain_alt >= 300
3465 // [PILOT] page 12: "the visual alert will remain on
3466 // until [...] landing flaps or the flap override switch
3468 || mk->io_handler.flaps_down())
3470 // exit altitude gain mode
3471 a_altitude_gain_timer.stop();
3472 mk_unset_alerts(mk_alert(MODE2A_ALTITUDE_GAIN) | mk_alert(MODE2A_ALTITUDE_GAIN_TERRAIN_CLOSING));
3476 // [SPEC] 6.2.2.2: "If the terrain starts to fall away
3477 // during this altitude gain time, the voice will shut
3479 if (closure_rate_filter.output.get() < 0)
3480 mk_unset_alerts(mk_alert(MODE2A_ALTITUDE_GAIN_TERRAIN_CLOSING));
3483 else if (mk_test_alerts(mk_alert(MODE2A_PREFACE) | mk_alert(MODE2A)))
3485 mk_unset_alerts(mk_alert(MODE2A_PREFACE) | mk_alert(MODE2A));
3487 if (! mk->io_handler.gpws_inhibit()
3488 && ! mk->state_handler.ground // [1]
3489 && globals->get_sim_time_sec() - a_start_time > 3
3490 && ! mk->io_handler.flaps_down()
3491 && ! mk_data(radio_altitude).ncd
3492 && ! mk_ainput(uncorrected_barometric_altitude).ncd)
3494 // [SPEC] 6.2.2.2: mode 2A envelope violated for more
3495 // than 3 seconds, enable altitude gain feature
3497 a_altitude_gain_timer.start();
3498 a_altitude_gain_alt = mk_ainput(uncorrected_barometric_altitude).get();
3500 unsigned int alerts = mk_alert(MODE2A_ALTITUDE_GAIN);
3501 if (closure_rate_filter.output.get() > 0)
3502 alerts |= mk_alert(MODE2A_ALTITUDE_GAIN_TERRAIN_CLOSING);
3504 mk_set_alerts(alerts);
3511 MK_VIII::Mode2Handler::update_b ()
3515 // handle normal mode
3517 if (b && (! mk_dinput(landing_gear) || ! mk->io_handler.flaps_down()))
3519 if (mk_test_alert(MODE2B_PREFACE))
3520 check_pull_up(mk_alert(MODE2B_PREFACE), mk_alert(MODE2B));
3521 else if (! mk_test_alert(MODE2B))
3523 mk_set_alerts(mk_alert(MODE2B_PREFACE));
3524 pull_up_timer.stop();
3528 mk_unset_alerts(mk_alert(MODE2B_PREFACE) | mk_alert(MODE2B));
3530 // handle landing mode ([SPEC] 6.2.2.3: "If both landing gear and
3531 // flaps are in the landing configuration, then the message will be
3534 if (b && mk_dinput(landing_gear) && mk->io_handler.flaps_down())
3535 mk_set_alerts(mk_alert(MODE2B_LANDING_MODE));
3537 mk_unset_alerts(mk_alert(MODE2B_LANDING_MODE));
3541 MK_VIII::Mode2Handler::boot ()
3543 closure_rate_filter.init();
3547 MK_VIII::Mode2Handler::power_off ()
3549 // [SPEC] 6.0.4: "This latching function is not power saved and a
3550 // system reset will force it false."
3551 takeoff_timer.stop();
3555 MK_VIII::Mode2Handler::leave_ground ()
3557 // takeoff, reset timer
3558 takeoff_timer.start();
3562 MK_VIII::Mode2Handler::enter_takeoff ()
3564 // reset timer, in case it's a go-around
3565 takeoff_timer.start();
3569 MK_VIII::Mode2Handler::update ()
3571 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
3574 closure_rate_filter.update();
3576 if (takeoff_timer.running && takeoff_timer.elapsed() >= 60)
3577 takeoff_timer.stop();
3583 ///////////////////////////////////////////////////////////////////////////////
3584 // Mode3Handler ///////////////////////////////////////////////////////////////
3585 ///////////////////////////////////////////////////////////////////////////////
3588 MK_VIII::Mode3Handler::max_alt_loss (double _bias)
3590 return conf->max_alt_loss(mk->io_handler.flap_override(), mk_data(radio_altitude).get()) + mk_data(radio_altitude).get() * _bias;
3594 MK_VIII::Mode3Handler::get_bias (double initial_bias, double alt_loss)
3596 if (mk_data(radio_altitude).get() > 0)
3597 while (alt_loss > max_alt_loss(initial_bias))
3598 initial_bias += 0.2;
3600 return initial_bias;
3604 MK_VIII::Mode3Handler::is (double *alt_loss)
3606 if (has_descent_alt)
3608 int max_agl = conf->max_agl(mk->io_handler.flap_override());
3610 if (mk_ainput(uncorrected_barometric_altitude).ncd
3611 || mk_ainput(uncorrected_barometric_altitude).get() > descent_alt
3612 || mk_data(radio_altitude).ncd
3613 || mk_data(radio_altitude).get() > max_agl)
3616 has_descent_alt = false;
3620 // gear/flaps: [SPEC] 1.3.1.3
3621 if (! mk->io_handler.gpws_inhibit()
3622 && ! mk->state_handler.ground // [1]
3623 && (! mk_dinput(landing_gear) || ! mk->io_handler.flaps_down())
3624 && ! mk_data(barometric_altitude_rate).ncd
3625 && ! mk_ainput(uncorrected_barometric_altitude).ncd
3626 && ! mk_data(radio_altitude).ncd
3627 && mk_data(barometric_altitude_rate).get() < 0)
3629 double _alt_loss = descent_alt - mk_ainput(uncorrected_barometric_altitude).get();
3630 if (armed || (mk_data(radio_altitude).get() > conf->min_agl
3631 && mk_data(radio_altitude).get() < max_agl
3632 && _alt_loss > max_alt_loss(0)))
3634 *alt_loss = _alt_loss;
3642 if (! mk_data(barometric_altitude_rate).ncd
3643 && ! mk_ainput(uncorrected_barometric_altitude).ncd
3644 && mk_data(barometric_altitude_rate).get() < 0)
3646 has_descent_alt = true;
3647 descent_alt = mk_ainput(uncorrected_barometric_altitude).get();
3655 MK_VIII::Mode3Handler::enter_takeoff ()
3658 has_descent_alt = false;
3662 MK_VIII::Mode3Handler::update ()
3664 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
3667 if (mk->state_handler.takeoff)
3671 if (! mk->state_handler.ground /* [1] */ && is(&alt_loss))
3673 if (mk_test_alert(MODE3))
3675 double new_bias = get_bias(bias, alt_loss);
3676 if (new_bias > bias)
3679 mk_repeat_alert(mk_alert(MODE3));
3685 bias = get_bias(0.2, alt_loss);
3686 mk_set_alerts(mk_alert(MODE3));
3693 mk_unset_alerts(mk_alert(MODE3));
3696 ///////////////////////////////////////////////////////////////////////////////
3697 // Mode4Handler ///////////////////////////////////////////////////////////////
3698 ///////////////////////////////////////////////////////////////////////////////
3700 // FIXME: for turbofans, the upper limit should be reduced from 1000
3701 // to 800 ft if a sudden change in radio altitude is detected, in
3702 // order to reduce nuisance alerts caused by overflying another
3703 // aircraft (see [PILOT] page 16).
3706 MK_VIII::Mode4Handler::get_upper_agl (const EnvelopesConfiguration *c)
3708 if (mk_ainput(computed_airspeed).get() >= c->airspeed2)
3710 else if (mk_ainput(computed_airspeed).get() >= c->airspeed1)
3711 return c->min_agl2(mk_ainput(computed_airspeed).get());
3716 const MK_VIII::Mode4Handler::EnvelopesConfiguration *
3717 MK_VIII::Mode4Handler::get_ab_envelope ()
3719 // [SPEC] 6.2.4.1: "The Mode 4B envelope can also be selected by
3720 // setting flaps to landing configuration or by selecting flap
3722 return mk_dinput(landing_gear) || mk->io_handler.flaps_down()
3728 MK_VIII::Mode4Handler::get_bias (double initial_bias, double min_agl)
3730 while (mk_data(radio_altitude).get() < min_agl - min_agl * initial_bias)
3731 initial_bias += 0.2;
3733 return initial_bias;
3737 MK_VIII::Mode4Handler::handle_alert (unsigned int alert,
3741 if (mk_test_alerts(alert))
3743 double new_bias = get_bias(*bias, min_agl);
3744 if (new_bias > *bias)
3747 mk_repeat_alert(alert);
3752 *bias = get_bias(0.2, min_agl);
3753 mk_set_alerts(alert);
3758 MK_VIII::Mode4Handler::update_ab ()
3760 if (! mk->io_handler.gpws_inhibit()
3761 && ! mk->state_handler.ground
3762 && ! mk->state_handler.takeoff
3763 && ! mk_data(radio_altitude).ncd
3764 && ! mk_ainput(computed_airspeed).ncd
3765 && mk_data(radio_altitude).get() > 30)
3767 const EnvelopesConfiguration *c = get_ab_envelope();
3768 if (mk_ainput(computed_airspeed).get() < c->airspeed1)
3770 if (mk_dinput(landing_gear))
3772 if (! mk->io_handler.flaps_down() && mk_data(radio_altitude).get() < c->min_agl1)
3774 handle_alert(mk_alert(MODE4_TOO_LOW_FLAPS), c->min_agl1, &ab_bias);
3780 if (mk_data(radio_altitude).get() < c->min_agl1)
3782 handle_alert(mk_alert(MODE4_TOO_LOW_GEAR), c->min_agl1, &ab_bias);
3789 mk_unset_alerts(mk_alert(MODE4_TOO_LOW_FLAPS) | mk_alert(MODE4_TOO_LOW_GEAR));
3794 MK_VIII::Mode4Handler::update_ab_expanded ()
3796 if (! mk->io_handler.gpws_inhibit()
3797 && ! mk->state_handler.ground
3798 && ! mk->state_handler.takeoff
3799 && ! mk_data(radio_altitude).ncd
3800 && ! mk_ainput(computed_airspeed).ncd
3801 && mk_data(radio_altitude).get() > 30)
3803 const EnvelopesConfiguration *c = get_ab_envelope();
3804 if (mk_ainput(computed_airspeed).get() >= c->airspeed1)
3806 double min_agl = get_upper_agl(c);
3807 if (mk_data(radio_altitude).get() < min_agl)
3809 handle_alert(mk_alert(MODE4AB_TOO_LOW_TERRAIN), min_agl, &ab_expanded_bias);
3815 mk_unset_alerts(mk_alert(MODE4AB_TOO_LOW_TERRAIN));
3816 ab_expanded_bias=0.0;
3820 MK_VIII::Mode4Handler::update_c ()
3822 if (! mk->io_handler.gpws_inhibit()
3823 && ! mk->state_handler.ground // [1]
3824 && mk->state_handler.takeoff
3825 && ! mk_data(radio_altitude).ncd
3826 && ! mk_data(terrain_clearance).ncd
3827 && mk_data(radio_altitude).get() > 30
3828 && (! mk_dinput(landing_gear) || ! mk->io_handler.flaps_down())
3829 && mk_data(radio_altitude).get() < get_upper_agl(conf.modes->ac)
3830 && mk_data(radio_altitude).get() < mk_data(terrain_clearance).get())
3831 handle_alert(mk_alert(MODE4C_TOO_LOW_TERRAIN), mk_data(terrain_clearance).get(), &c_bias);
3834 mk_unset_alerts(mk_alert(MODE4C_TOO_LOW_TERRAIN));
3840 MK_VIII::Mode4Handler::update ()
3842 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
3846 update_ab_expanded();
3850 ///////////////////////////////////////////////////////////////////////////////
3851 // Mode5Handler ///////////////////////////////////////////////////////////////
3852 ///////////////////////////////////////////////////////////////////////////////
3855 MK_VIII::Mode5Handler::is_hard ()
3857 if (mk_data(radio_altitude).get() > 30
3858 && mk_data(radio_altitude).get() < 300
3859 && mk_data(glideslope_deviation_dots).get() > 2)
3861 if (mk_data(radio_altitude).get() < 150)
3863 if (mk_data(radio_altitude).get() > 293 - 71.43 * mk_data(glideslope_deviation_dots).get())
3874 MK_VIII::Mode5Handler::is_soft (double bias)
3876 if (mk_data(radio_altitude).get() > 30)
3878 double bias_dots = 1.3 * bias;
3879 if (mk_data(glideslope_deviation_dots).get() > 1.3 + bias_dots)
3881 if (mk_data(radio_altitude).get() < 150)
3883 if (mk_data(radio_altitude).get() > 243 - 71.43 * (mk_data(glideslope_deviation_dots).get() - bias_dots))
3890 if (mk_data(barometric_altitude_rate).ncd)
3894 if (mk_data(barometric_altitude_rate).get() >= 0)
3896 else if (mk_data(barometric_altitude_rate).get() < -500)
3899 upper_limit = -mk_data(barometric_altitude_rate).get() + 500;
3902 if (mk_data(radio_altitude).get() < upper_limit)
3912 MK_VIII::Mode5Handler::get_soft_bias (double initial_bias)
3914 while (is_soft(initial_bias))
3915 initial_bias += 0.2;
3917 return initial_bias;
3921 MK_VIII::Mode5Handler::update_hard (bool is)
3925 if (! mk_test_alert(MODE5_HARD))
3927 if (hard_timer.start_or_elapsed() >= 0.8)
3928 mk_set_alerts(mk_alert(MODE5_HARD));
3934 mk_unset_alerts(mk_alert(MODE5_HARD));
3939 MK_VIII::Mode5Handler::update_soft (bool is)
3943 if (! mk_test_alert(MODE5_SOFT))
3945 double new_bias = get_soft_bias(soft_bias);
3946 if (new_bias > soft_bias)
3948 soft_bias = new_bias;
3949 mk_repeat_alert(mk_alert(MODE5_SOFT));
3954 if (soft_timer.start_or_elapsed() >= 0.8)
3956 soft_bias = get_soft_bias(0.2);
3957 mk_set_alerts(mk_alert(MODE5_SOFT));
3964 mk_unset_alerts(mk_alert(MODE5_SOFT));
3969 MK_VIII::Mode5Handler::update ()
3971 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
3974 if (! mk->io_handler.gpws_inhibit()
3975 && ! mk->state_handler.ground // [1]
3976 && ! mk_dinput(glideslope_inhibit) // not on backcourse
3977 && ! mk_data(radio_altitude).ncd
3978 && ! mk_data(glideslope_deviation_dots).ncd
3979 && (! mk->io_handler.conf.localizer_enabled
3980 || mk_data(localizer_deviation_dots).ncd
3981 || mk_data(radio_altitude).get() < 500
3982 || fabs(mk_data(localizer_deviation_dots).get()) < 2)
3983 && (! mk->state_handler.takeoff || mk->io_handler.flaps_down())
3984 && mk_dinput(landing_gear)
3985 && ! mk_doutput(glideslope_cancel))
3987 update_hard(is_hard());
3988 update_soft(is_soft(0));
3997 ///////////////////////////////////////////////////////////////////////////////
3998 // Mode6Handler ///////////////////////////////////////////////////////////////
3999 ///////////////////////////////////////////////////////////////////////////////
4001 // keep sorted in descending order
4002 const int MK_VIII::Mode6Handler::altitude_callout_definitions[] =
4003 { 1000, 500, 400, 300, 200, 100, 50, 40, 30, 20, 10 };
4006 MK_VIII::Mode6Handler::reset_minimums ()
4008 minimums_issued = false;
4012 MK_VIII::Mode6Handler::reset_altitude_callouts ()
4014 for (unsigned i = 0; i < n_altitude_callouts; i++)
4015 altitude_callouts_issued[i] = false;
4019 MK_VIII::Mode6Handler::is_playing_altitude_callout ()
4021 for (unsigned i = 0; i < n_altitude_callouts; i++)
4022 if (mk->voice_player.voice == mk_altitude_voice(i)
4023 || mk->voice_player.next_voice == mk_altitude_voice(i))
4030 MK_VIII::Mode6Handler::is_near_minimums (double callout)
4034 if (! mk_data(decision_height).ncd)
4036 double diff = callout - mk_data(decision_height).get();
4038 if (mk_data(radio_altitude).get() >= 200)
4040 if (fabs(diff) <= 30)
4045 if (diff >= -3 && diff <= 6)
4054 MK_VIII::Mode6Handler::is_outside_band (double elevation, double callout)
4057 return elevation < callout - (elevation > 150 ? 20 : 10);
4061 MK_VIII::Mode6Handler::inhibit_smart_500 ()
4065 if (! mk_data(glideslope_deviation_dots).ncd
4066 && ! mk_dinput(glideslope_inhibit) // backcourse
4067 && ! mk_doutput(glideslope_cancel))
4069 if (mk->io_handler.conf.localizer_enabled
4070 && ! mk_data(localizer_deviation_dots).ncd)
4072 if (fabs(mk_data(localizer_deviation_dots).get()) <= 2)
4077 if (fabs(mk_data(glideslope_deviation_dots).get()) <= 2)
4086 MK_VIII::Mode6Handler::boot ()
4088 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
4091 last_decision_height = mk_dinput(decision_height);
4092 last_radio_altitude.set(&mk_data(radio_altitude));
4095 for (unsigned i = 0; i < n_altitude_callouts; i++)
4096 altitude_callouts_issued[i] = ! mk_data(radio_altitude).ncd
4097 && mk_data(radio_altitude).get() <= altitude_callout_definitions[i];
4099 // extrapolated from [SPEC] 6.4.2
4100 minimums_issued = mk_dinput(decision_height);
4102 if (conf.above_field_voice)
4105 get_altitude_above_field(&last_altitude_above_field);
4106 // extrapolated from [SPEC] 6.4.2
4107 above_field_issued = ! last_altitude_above_field.ncd
4108 && last_altitude_above_field.get() < 550;
4113 MK_VIII::Mode6Handler::power_off ()
4115 runway_timer.stop();
4119 MK_VIII::Mode6Handler::enter_takeoff ()
4121 reset_altitude_callouts(); // [SPEC] 6.4.2
4122 reset_minimums(); // omitted by [SPEC]; common sense
4126 MK_VIII::Mode6Handler::leave_takeoff ()
4128 reset_altitude_callouts(); // [SPEC] 6.0.2
4129 reset_minimums(); // [SPEC] 6.0.2
4133 MK_VIII::Mode6Handler::set_volume (float volume)
4135 mk_voice(minimums_minimums)->set_volume(volume);
4136 mk_voice(five_hundred_above)->set_volume(volume);
4137 for (unsigned i = 0; i < n_altitude_callouts; i++)
4138 mk_altitude_voice(i)->set_volume(volume);
4142 MK_VIII::Mode6Handler::altitude_callouts_enabled ()
4144 if (conf.minimums_enabled || conf.smart_500_enabled || conf.above_field_voice)
4147 for (unsigned i = 0; i < n_altitude_callouts; i++)
4148 if (conf.altitude_callouts_enabled[i])
4155 MK_VIII::Mode6Handler::update_minimums ()
4157 if (! mk->io_handler.gpws_inhibit()
4158 && (mk->voice_player.voice == mk_voice(minimums_minimums)
4159 || mk->voice_player.next_voice == mk_voice(minimums_minimums)))
4162 if (! mk->io_handler.gpws_inhibit()
4163 && ! mk->state_handler.ground // [1]
4164 && conf.minimums_enabled
4165 && ! minimums_issued
4166 && mk_dinput(landing_gear)
4167 && mk_dinput(decision_height)
4168 && ! last_decision_height)
4170 minimums_issued = true;
4172 // If an altitude callout is playing, stop it now so that the
4173 // minimums callout can be played (note that proper connection
4174 // and synchronization of the radio-altitude ARINC 529 input,
4175 // decision-height discrete and decision-height ARINC 529 input
4176 // will prevent an altitude callout from being played near the
4177 // decision height).
4179 if (is_playing_altitude_callout())
4180 mk->voice_player.stop(VoicePlayer::STOP_NOW);
4182 mk_set_alerts(mk_alert(MODE6_MINIMUMS));
4185 mk_unset_alerts(mk_alert(MODE6_MINIMUMS));
4188 last_decision_height = mk_dinput(decision_height);
4192 MK_VIII::Mode6Handler::update_altitude_callouts ()
4194 if (! mk->io_handler.gpws_inhibit() && is_playing_altitude_callout())
4197 if (! mk->io_handler.gpws_inhibit()
4198 && ! mk->state_handler.ground // [1]
4199 && ! mk_data(radio_altitude).ncd)
4200 for (unsigned i = 0; i < n_altitude_callouts && mk_data(radio_altitude).get() <= altitude_callout_definitions[i]; i++)
4201 if ((conf.altitude_callouts_enabled[i]
4202 || (altitude_callout_definitions[i] == 500
4203 && conf.smart_500_enabled))
4204 && ! altitude_callouts_issued[i]
4205 && (last_radio_altitude.ncd
4206 || last_radio_altitude.get() > altitude_callout_definitions[i]))
4208 // lock out all callouts superior or equal to this one
4209 for (unsigned j = 0; j <= i; j++)
4210 altitude_callouts_issued[j] = true;
4212 altitude_callouts_issued[i] = true;
4213 if (! is_near_minimums(altitude_callout_definitions[i])
4214 && ! is_outside_band(mk_data(radio_altitude).get(), altitude_callout_definitions[i])
4215 && (! conf.smart_500_enabled
4216 || altitude_callout_definitions[i] != 500
4217 || ! inhibit_smart_500()))
4219 mk->alert_handler.set_altitude_callout_alert(mk_altitude_voice(i));
4224 mk_unset_alerts(mk_alert(MODE6_ALTITUDE_CALLOUT));
4227 last_radio_altitude.set(&mk_data(radio_altitude));
4231 MK_VIII::Mode6Handler::test_runway (const FGRunway *_runway)
4233 if (_runway->lengthFt() < mk->conf.runway_database)
4237 SGGeod::fromDeg(mk_data(gps_longitude).get(), mk_data(gps_latitude).get()));
4239 // get distance to threshold
4240 double distance, az1, az2;
4241 SGGeodesy::inverse(pos, _runway->threshold(), az1, az2, distance);
4242 return distance * SG_METER_TO_NM <= 5;
4246 MK_VIII::Mode6Handler::test_airport (const FGAirport *airport)
4248 for (unsigned int r=0; r<airport->numRunways(); ++r) {
4249 FGRunway* rwy(airport->getRunwayByIndex(r));
4251 if (test_runway(rwy)) return true;
4257 bool MK_VIII::Mode6Handler::AirportFilter::passAirport(FGAirport* a) const
4259 bool ok = self->test_airport(a);
4264 MK_VIII::Mode6Handler::update_runway ()
4267 if (mk_data(gps_latitude).ncd || mk_data(gps_longitude).ncd) {
4272 // Search for the closest runway threshold in range 5
4273 // nm. Passing 30nm to
4274 // get_closest_airport() provides enough margin for large
4275 // airports, which may have a runway located far away from the
4276 // airport's reference point.
4277 AirportFilter filter(this);
4278 FGPositionedRef apt = FGPositioned::findClosest(
4279 SGGeod::fromDeg(mk_data(gps_longitude).get(), mk_data(gps_latitude).get()),
4282 runway.elevation = apt->elevation();
4285 has_runway.set(apt != NULL);
4289 MK_VIII::Mode6Handler::get_altitude_above_field (Parameter<double> *parameter)
4291 if (! has_runway.ncd && has_runway.get() && ! mk_data(geometric_altitude).ncd)
4292 parameter->set(mk_data(geometric_altitude).get() - runway.elevation);
4298 MK_VIII::Mode6Handler::update_above_field_callout ()
4300 if (! conf.above_field_voice)
4303 // update_runway() has to iterate over the whole runway database
4304 // (which contains thousands of runways), so it would be unwise to
4305 // run it every frame. Run it every 3 seconds. Note that the first
4306 // iteration is run in boot().
4307 if (runway_timer.start_or_elapsed() >= 3)
4310 runway_timer.start();
4313 Parameter<double> altitude_above_field;
4314 get_altitude_above_field(&altitude_above_field);
4316 if (! mk->io_handler.gpws_inhibit()
4317 && (mk->voice_player.voice == conf.above_field_voice
4318 || mk->voice_player.next_voice == conf.above_field_voice))
4321 // handle reset term
4322 if (above_field_issued)
4324 if ((! has_runway.ncd && ! has_runway.get())
4325 || (! altitude_above_field.ncd && altitude_above_field.get() > 700))
4326 above_field_issued = false;
4329 if (! mk->io_handler.gpws_inhibit()
4330 && ! mk->state_handler.ground // [1]
4331 && ! above_field_issued
4332 && ! altitude_above_field.ncd
4333 && altitude_above_field.get() < 550
4334 && (last_altitude_above_field.ncd
4335 || last_altitude_above_field.get() >= 550))
4337 above_field_issued = true;
4339 if (! is_outside_band(altitude_above_field.get(), 500))
4341 mk->alert_handler.set_altitude_callout_alert(conf.above_field_voice);
4346 mk_unset_alerts(mk_alert(MODE6_ALTITUDE_CALLOUT));
4349 last_altitude_above_field.set(&altitude_above_field);
4353 MK_VIII::Mode6Handler::is_bank_angle (double abs_roll_angle, double bias)
4355 return conf.is_bank_angle(&mk_data(radio_altitude),
4356 abs_roll_angle - abs_roll_angle * bias,
4357 mk_dinput(autopilot_engaged));
4361 MK_VIII::Mode6Handler::is_high_bank_angle ()
4363 return mk_data(radio_altitude).ncd || mk_data(radio_altitude).get() >= 210;
4367 MK_VIII::Mode6Handler::get_bank_angle_alerts ()
4369 if (! mk->io_handler.gpws_inhibit()
4370 && ! mk->state_handler.ground // [1]
4371 && ! mk_data(roll_angle).ncd)
4373 double abs_roll_angle = fabs(mk_data(roll_angle).get());
4375 if (is_bank_angle(abs_roll_angle, 0.4))
4376 return is_high_bank_angle()
4377 ? (mk_alert(MODE6_HIGH_BANK_ANGLE_1) | mk_alert(MODE6_HIGH_BANK_ANGLE_2) | mk_alert(MODE6_HIGH_BANK_ANGLE_3))
4378 : (mk_alert(MODE6_LOW_BANK_ANGLE_1) | mk_alert(MODE6_LOW_BANK_ANGLE_2) | mk_alert(MODE6_LOW_BANK_ANGLE_3));
4379 else if (is_bank_angle(abs_roll_angle, 0.2))
4380 return is_high_bank_angle()
4381 ? (mk_alert(MODE6_HIGH_BANK_ANGLE_1) | mk_alert(MODE6_HIGH_BANK_ANGLE_2))
4382 : (mk_alert(MODE6_LOW_BANK_ANGLE_1) | mk_alert(MODE6_LOW_BANK_ANGLE_2));
4383 else if (is_bank_angle(abs_roll_angle, 0))
4384 return is_high_bank_angle()
4385 ? mk_alert(MODE6_HIGH_BANK_ANGLE_1)
4386 : mk_alert(MODE6_LOW_BANK_ANGLE_1);
4393 MK_VIII::Mode6Handler::update_bank_angle ()
4395 if (! conf.bank_angle_enabled)
4398 unsigned int alerts = get_bank_angle_alerts();
4400 // [SPEC] 6.4.4 specifies that the non-continuous alerts
4401 // (MODE6_*_BANK_ANGLE_1 and MODE6_*_BANK_ANGLE_2) are locked out
4402 // until the initial envelope is exited.
4404 if (! test_bits(alerts, mk_alert(MODE6_LOW_BANK_ANGLE_3)))
4405 mk_unset_alerts(mk_alert(MODE6_LOW_BANK_ANGLE_3));
4406 if (! test_bits(alerts, mk_alert(MODE6_HIGH_BANK_ANGLE_3)))
4407 mk_unset_alerts(mk_alert(MODE6_HIGH_BANK_ANGLE_3));
4410 mk_set_alerts(alerts);
4412 mk_unset_alerts(mk_alert(MODE6_LOW_BANK_ANGLE_1)
4413 | mk_alert(MODE6_HIGH_BANK_ANGLE_1)
4414 | mk_alert(MODE6_LOW_BANK_ANGLE_2)
4415 | mk_alert(MODE6_HIGH_BANK_ANGLE_2));
4419 MK_VIII::Mode6Handler::update ()
4421 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
4424 if (! mk->state_handler.takeoff
4425 && ! mk_data(radio_altitude).ncd
4426 && mk_data(radio_altitude).get() > 1000)
4428 reset_altitude_callouts(); // [SPEC] 6.4.2
4429 reset_minimums(); // common sense
4433 update_altitude_callouts();
4434 update_above_field_callout();
4435 update_bank_angle();
4438 ///////////////////////////////////////////////////////////////////////////////
4439 // TCFHandler /////////////////////////////////////////////////////////////////
4440 ///////////////////////////////////////////////////////////////////////////////
4442 // Gets the difference between the azimuth from @from_lat,@from_lon to
4443 // @to_lat,@to_lon, and @to_heading, in degrees.
4445 MK_VIII::TCFHandler::get_azimuth_difference (double from_lat,
4451 double az1, az2, distance;
4452 geo_inverse_wgs_84(0, from_lat, from_lon, to_lat, to_lon, &az1, &az2, &distance);
4453 return get_heading_difference(az1, to_heading);
4456 // Gets the difference between the azimuth from the current GPS
4457 // position to the center of @_runway, and the heading of @_runway, in
4460 MK_VIII::TCFHandler::get_azimuth_difference (const FGRunway *_runway)
4462 return get_azimuth_difference(mk_data(gps_latitude).get(),
4463 mk_data(gps_longitude).get(),
4464 _runway->latitude(),
4465 _runway->longitude(),
4466 _runway->headingDeg());
4469 // Selects the most likely intended destination runway of @airport,
4470 // and returns it in @_runway. For each runway, the difference between
4471 // the azimuth from the current GPS position to the center of the
4472 // runway and its heading is computed. The runway having the smallest
4475 // This selection algorithm is not specified in [SPEC], but
4476 // http://www.egpws.com/general_information/description/runway_select.htm
4477 // talks about automatic runway selection.
4479 MK_VIII::TCFHandler::select_runway (const FGAirport *airport)
4481 FGRunway* _runway = 0;
4482 double min_diff = 360;
4484 for (unsigned int r=0; r<airport->numRunways(); ++r) {
4485 FGRunway* rwy(airport->getRunwayByIndex(r));
4486 double diff = get_azimuth_difference(rwy);
4487 if (diff < min_diff)
4492 } // of airport runways iteration
4496 bool MK_VIII::TCFHandler::AirportFilter::passAirport(FGAirport* aApt) const
4498 return aApt->hasHardRunwayOfLengthFt(mk->conf.runway_database);
4502 MK_VIII::TCFHandler::update_runway ()
4505 if (mk_data(gps_latitude).ncd || mk_data(gps_longitude).ncd) {
4509 // Search for the intended destination runway of the closest
4510 // airport in range 15 nm. Passing 30nm to get_closest_airport()
4511 // provides enough margin for
4512 // large airports, which may have a runway located far away from
4513 // the airport's reference point.
4514 AirportFilter filter(mk);
4515 FGAirport* apt = FGAirport::findClosest(
4516 SGGeod::fromDeg(mk_data(gps_longitude).get(), mk_data(gps_latitude).get()),
4523 FGRunway* _runway = select_runway(apt);
4525 runway.center.latitude = _runway->latitude();
4526 runway.center.longitude = _runway->longitude();
4528 runway.elevation = apt->elevation();
4530 double half_length_m = _runway->lengthM() * 0.5;
4531 runway.half_length = half_length_m * SG_METER_TO_NM;
4533 // b3 ________________ b0
4535 // h1>>> | e1<<<<<<<<e0 | <<<h0
4536 // |________________|
4539 // get heading to runway threshold (h0) and end (h1)
4540 runway.edges[0].heading = _runway->headingDeg();
4541 runway.edges[1].heading = get_reciprocal_heading(_runway->headingDeg());
4545 // get position of runway threshold (e0)
4546 geo_direct_wgs_84(0,
4547 runway.center.latitude,
4548 runway.center.longitude,
4549 runway.edges[1].heading,
4551 &runway.edges[0].position.latitude,
4552 &runway.edges[0].position.longitude,
4555 // get position of runway end (e1)
4556 geo_direct_wgs_84(0,
4557 runway.center.latitude,
4558 runway.center.longitude,
4559 runway.edges[0].heading,
4561 &runway.edges[1].position.latitude,
4562 &runway.edges[1].position.longitude,
4565 double half_width_m = _runway->widthM() * 0.5;
4567 // get position of threshold bias area edges (b0 and b1)
4568 get_bias_area_edges(&runway.edges[0].position,
4569 runway.edges[1].heading,
4571 &runway.bias_area[0],
4572 &runway.bias_area[1]);
4574 // get position of end bias area edges (b2 and b3)
4575 get_bias_area_edges(&runway.edges[1].position,
4576 runway.edges[0].heading,
4578 &runway.bias_area[2],
4579 &runway.bias_area[3]);
4583 MK_VIII::TCFHandler::get_bias_area_edges (Position *edge,
4585 double half_width_m,
4586 Position *bias_edge1,
4587 Position *bias_edge2)
4589 double half_bias_width_m = k * SG_NM_TO_METER + half_width_m;
4590 double tmp_latitude = 0.0, tmp_longitude = 0.0, az = 0.0;
4592 geo_direct_wgs_84(0,
4600 geo_direct_wgs_84(0,
4603 heading_substract(reciprocal, 90),
4605 &bias_edge1->latitude,
4606 &bias_edge1->longitude,
4608 geo_direct_wgs_84(0,
4611 heading_add(reciprocal, 90),
4613 &bias_edge2->latitude,
4614 &bias_edge2->longitude,
4618 // Returns true if the current GPS position is inside the edge
4619 // triangle of @edge. The edge triangle, which is specified and
4620 // represented in [SPEC] 6.3.1, is defined as an isocele right
4621 // triangle of infinite height, whose right angle is located at the
4622 // position of @edge, and whose height is parallel to the heading of
4625 MK_VIII::TCFHandler::is_inside_edge_triangle (RunwayEdge *edge)
4627 return get_azimuth_difference(mk_data(gps_latitude).get(),
4628 mk_data(gps_longitude).get(),
4629 edge->position.latitude,
4630 edge->position.longitude,
4631 edge->heading) <= 45;
4634 // Returns true if the current GPS position is inside the bias area of
4635 // the currently selected runway.
4637 MK_VIII::TCFHandler::is_inside_bias_area ()
4640 double angles_sum = 0;
4642 for (int i = 0; i < 4; i++)
4644 double az2, distance;
4645 geo_inverse_wgs_84(0,
4646 mk_data(gps_latitude).get(),
4647 mk_data(gps_longitude).get(),
4648 runway.bias_area[i].latitude,
4649 runway.bias_area[i].longitude,
4650 &az1[i], &az2, &distance);
4653 for (int i = 0; i < 4; i++)
4655 double angle = az1[i == 3 ? 0 : i + 1] - az1[i];
4659 angles_sum += angle;
4662 return angles_sum > 180;
4666 MK_VIII::TCFHandler::is_tcf ()
4668 if (mk_data(radio_altitude).get() > 10)
4672 double distance, az1, az2;
4674 geo_inverse_wgs_84(0,
4675 mk_data(gps_latitude).get(),
4676 mk_data(gps_longitude).get(),
4677 runway.center.latitude,
4678 runway.center.longitude,
4679 &az1, &az2, &distance);
4681 distance *= SG_METER_TO_NM;
4683 // distance to the inner envelope edge
4684 double edge_distance = distance - runway.half_length - k;
4686 if (edge_distance >= 15)
4688 if (mk_data(radio_altitude).get() < 700)
4691 else if (edge_distance >= 12)
4693 if (mk_data(radio_altitude).get() < 100 * edge_distance - 800)
4696 else if (edge_distance >= 4)
4698 if (mk_data(radio_altitude).get() < 400)
4701 else if (edge_distance >= 2.45)
4703 if (mk_data(radio_altitude).get() < 66.666667 * edge_distance + 133.33333)
4708 if (is_inside_edge_triangle(&runway.edges[0]) || is_inside_edge_triangle(&runway.edges[1]))
4710 if (edge_distance >= 1)
4712 if (mk_data(radio_altitude).get() < 66.666667 * edge_distance + 133.33333)
4715 else if (edge_distance >= 0.05)
4717 if (mk_data(radio_altitude).get() < 200 * edge_distance)
4723 if (! is_inside_bias_area())
4725 if (mk_data(radio_altitude).get() < 245)
4733 if (mk_data(radio_altitude).get() < 700)
4742 MK_VIII::TCFHandler::is_rfcf ()
4746 double distance, az1, az2;
4747 geo_inverse_wgs_84(0,
4748 mk_data(gps_latitude).get(),
4749 mk_data(gps_longitude).get(),
4750 runway.center.latitude,
4751 runway.center.longitude,
4752 &az1, &az2, &distance);
4754 double krf = k + mk_data(gps_vertical_figure_of_merit).get() / 200;
4755 distance = distance * SG_METER_TO_NM - runway.half_length - krf;
4759 double altitude_above_field = mk_data(geometric_altitude).get() - runway.elevation;
4761 if (distance >= 1.5)
4763 if (altitude_above_field < 300)
4766 else if (distance >= 0)
4768 if (altitude_above_field < 200 * distance)
4778 MK_VIII::TCFHandler::update ()
4780 if (mk->configuration_module.state != ConfigurationModule::STATE_OK || ! conf.enabled)
4783 // update_runway() has to iterate over the whole runway database
4784 // (which contains thousands of runways), so it would be unwise to
4785 // run it every frame. Run it every 3 seconds.
4786 if (! runway_timer.running || runway_timer.elapsed() >= 3)
4789 runway_timer.start();
4792 if (! mk_dinput(ta_tcf_inhibit)
4793 && ! mk->state_handler.ground // [1]
4794 && ! mk_data(gps_latitude).ncd
4795 && ! mk_data(gps_longitude).ncd
4796 && ! mk_data(radio_altitude).ncd
4797 && ! mk_data(geometric_altitude).ncd
4798 && ! mk_data(gps_vertical_figure_of_merit).ncd)
4803 _reference = mk_data(radio_altitude).get_pointer();
4805 _reference = mk_data(geometric_altitude).get_pointer();
4811 if (mk_test_alert(TCF_TOO_LOW_TERRAIN))
4813 double new_bias = bias;
4814 while (*reference < initial_value - initial_value * new_bias)
4817 if (new_bias > bias)
4820 mk_repeat_alert(mk_alert(TCF_TOO_LOW_TERRAIN));
4826 reference = _reference;
4827 initial_value = *reference;
4828 mk_set_alerts(mk_alert(TCF_TOO_LOW_TERRAIN));
4835 mk_unset_alerts(mk_alert(TCF_TOO_LOW_TERRAIN));
4838 ///////////////////////////////////////////////////////////////////////////////
4839 // MK_VIII ////////////////////////////////////////////////////////////////////
4840 ///////////////////////////////////////////////////////////////////////////////
4842 MK_VIII::MK_VIII (SGPropertyNode *node)
4843 : properties_handler(this),
4846 power_handler(this),
4847 system_handler(this),
4848 configuration_module(this),
4849 fault_handler(this),
4852 self_test_handler(this),
4853 alert_handler(this),
4854 state_handler(this),
4855 mode1_handler(this),
4856 mode2_handler(this),
4857 mode3_handler(this),
4858 mode4_handler(this),
4859 mode5_handler(this),
4860 mode6_handler(this),
4863 for (int i = 0; i < node->nChildren(); ++i)
4865 SGPropertyNode *child = node->getChild(i);
4866 string cname = child->getName();
4867 string cval = child->getStringValue();
4869 if (cname == "name")
4871 else if (cname == "number")
4872 num = child->getIntValue();
4875 SG_LOG(SG_INSTR, SG_WARN, "Error in mk-viii config logic");
4877 SG_LOG(SG_INSTR, SG_WARN, "Section = " << name);
4885 properties_handler.init();
4886 voice_player.init();
4892 SGPropertyNode *node = fgGetNode(("/instrumentation/" + name).c_str(), num, true);
4894 configuration_module.bind(node);
4895 power_handler.bind(node);
4896 io_handler.bind(node);
4897 voice_player.bind(node);
4903 properties_handler.unbind();
4907 MK_VIII::update (double dt)
4909 power_handler.update();
4910 system_handler.update();
4912 if (system_handler.state != SystemHandler::STATE_ON)
4915 io_handler.update_inputs();
4916 io_handler.update_input_faults();
4918 voice_player.update();
4919 state_handler.update();
4921 if (self_test_handler.update())
4924 io_handler.update_internal_latches();
4925 io_handler.update_lamps();
4927 mode1_handler.update();
4928 mode2_handler.update();
4929 mode3_handler.update();
4930 mode4_handler.update();
4931 mode5_handler.update();
4932 mode6_handler.update();
4933 tcf_handler.update();
4935 alert_handler.update();
4936 io_handler.update_outputs();