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();
365 mk->io_handler.reposition();
367 last_replay_state = replay_state;
368 state = STATE_REPOSITION;
369 reposition_timer.start();
372 if (state == STATE_REPOSITION && reposition_timer.elapsed() >= 3)
374 // inputs are needed by StateHandler::post_reposition()
375 mk->io_handler.update_inputs();
377 mk->state_handler.post_reposition();
384 ///////////////////////////////////////////////////////////////////////////////
385 // ConfigurationModule ////////////////////////////////////////////////////////
386 ///////////////////////////////////////////////////////////////////////////////
388 MK_VIII::ConfigurationModule::ConfigurationModule (MK_VIII *device)
391 // arbitrary defaults
392 categories[CATEGORY_AIRCRAFT_MODE_TYPE_SELECT] = 0;
393 categories[CATEGORY_AIR_DATA_INPUT_SELECT] = 1;
394 categories[CATEGORY_POSITION_INPUT_SELECT] = 2;
395 categories[CATEGORY_ALTITUDE_CALLOUTS] = 13;
396 categories[CATEGORY_AUDIO_MENU_SELECT] = 0;
397 categories[CATEGORY_TERRAIN_DISPLAY_SELECT] = 1;
398 categories[CATEGORY_OPTIONS_SELECT_GROUP_1] = 124;
399 categories[CATEGORY_RADIO_ALTITUDE_INPUT_SELECT] = 2;
400 categories[CATEGORY_NAVIGATION_INPUT_SELECT] = 3;
401 categories[CATEGORY_ATTITUDE_INPUT_SELECT] = 6;
402 categories[CATEGORY_HEADING_INPUT_SELECT] = 2;
403 categories[CATEGORY_WINDSHEAR_INPUT_SELECT] = 0;
404 categories[CATEGORY_INPUT_OUTPUT_DISCRETE_TYPE_SELECT] = 7;
405 categories[CATEGORY_AUDIO_OUTPUT_LEVEL] = 0;
406 categories[CATEGORY_UNDEFINED_INPUT_SELECT_1] = 0;
407 categories[CATEGORY_UNDEFINED_INPUT_SELECT_2] = 0;
408 categories[CATEGORY_UNDEFINED_INPUT_SELECT_3] = 0;
411 static double m1_t1_min_agl1 (double vs) { return -1620 - 1.1133 * vs; }
412 static double m1_t1_min_agl2 (double vs) { return -400 - 0.4 * vs; }
413 static double m1_t4_min_agl1 (double vs) { return -1625.47 - 1.1167 * vs; }
414 static double m1_t4_min_agl2 (double vs) { return -0.1958 * vs; }
416 static int m3_t1_max_agl (bool flap_override) { return 1500; }
417 static double m3_t1_max_alt_loss (bool flap_override, double agl) { return 5.4 + 0.092 * agl; }
418 static int m3_t2_max_agl (bool flap_override) { return flap_override ? 815 : 925; }
419 static double m3_t2_max_alt_loss (bool flap_override, double agl)
421 int bias = agl > 700 ? 5 : 0;
424 return (9.0 + 0.184 * agl) + bias;
426 return (5.4 + 0.092 * agl) + bias;
429 static double m4_t1_min_agl2 (double airspeed) { return -1083 + 8.333 * airspeed; }
430 static double m4_t568_a_min_agl2 (double airspeed) { return -1523 + 11.36 * airspeed; }
431 static double m4_t79_a_min_agl2 (double airspeed) { return -1182 + 11.36 * airspeed; }
432 static double m4_t568_b_min_agl2 (double airspeed) { return -1570 + 11.6 * airspeed; }
433 static double m4_t79_b_min_agl2 (double airspeed) { return -1222 + 11.6 * airspeed; }
436 MK_VIII::ConfigurationModule::m6_t2_is_bank_angle (Parameter<double> *agl,
442 if (agl->ncd || agl->get() > 122)
443 return abs_roll_deg > 33;
447 if (agl->ncd || agl->get() > 2450)
448 return abs_roll_deg > 55;
449 else if (agl->get() > 150)
450 return agl->get() < 153.33333 * abs_roll_deg - 5983.3333;
454 return agl->get() < 4 * abs_roll_deg - 10;
455 else if (agl->get() > 5)
456 return abs_roll_deg > 10;
462 MK_VIII::ConfigurationModule::m6_t4_is_bank_angle (Parameter<double> *agl,
468 if (agl->ncd || agl->get() > 156)
469 return abs_roll_deg > 33;
473 if (agl->ncd || agl->get() > 210)
474 return abs_roll_deg > 50;
478 return agl->get() < 5.7142857 * abs_roll_deg - 75.714286;
484 MK_VIII::ConfigurationModule::read_aircraft_mode_type_select (int value)
486 static const Mode1Handler::EnvelopesConfiguration m1_t1 =
487 { false, 10, m1_t1_min_agl1, 284, m1_t1_min_agl2, 2450 };
488 static const Mode1Handler::EnvelopesConfiguration m1_t4 =
489 { true, 50, m1_t4_min_agl1, 346, m1_t4_min_agl2, 1958 };
491 static const Mode2Handler::Configuration m2_t1 = { 190, 280 };
492 static const Mode2Handler::Configuration m2_t4 = { 220, 310 };
493 static const Mode2Handler::Configuration m2_t5 = { 220, 310 };
495 static const Mode3Handler::Configuration m3_t1 =
496 { 30, m3_t1_max_agl, m3_t1_max_alt_loss };
497 static const Mode3Handler::Configuration m3_t2 =
498 { 50, m3_t2_max_agl, m3_t2_max_alt_loss };
500 static const Mode4Handler::EnvelopesConfiguration m4_t1_ac =
501 { 190, 250, 500, m4_t1_min_agl2, 1000 };
502 static const Mode4Handler::EnvelopesConfiguration m4_t5_ac =
503 { 178, 200, 500, m4_t568_a_min_agl2, 1000 };
504 static const Mode4Handler::EnvelopesConfiguration m4_t68_ac =
505 { 178, 200, 500, m4_t568_a_min_agl2, 750 };
506 static const Mode4Handler::EnvelopesConfiguration m4_t79_ac =
507 { 148, 170, 500, m4_t79_a_min_agl2, 750 };
509 static const Mode4Handler::EnvelopesConfiguration m4_t1_b =
510 { 159, 250, 245, m4_t1_min_agl2, 1000 };
511 static const Mode4Handler::EnvelopesConfiguration m4_t5_b =
512 { 148, 200, 200, m4_t568_b_min_agl2, 1000 };
513 static const Mode4Handler::EnvelopesConfiguration m4_t6_b =
514 { 150, 200, 170, m4_t568_b_min_agl2, 750 };
515 static const Mode4Handler::EnvelopesConfiguration m4_t7_b =
516 { 120, 170, 170, m4_t79_b_min_agl2, 750 };
517 static const Mode4Handler::EnvelopesConfiguration m4_t8_b =
518 { 148, 200, 150, m4_t568_b_min_agl2, 750 };
519 static const Mode4Handler::EnvelopesConfiguration m4_t9_b =
520 { 118, 170, 150, m4_t79_b_min_agl2, 750 };
522 static const Mode4Handler::ModesConfiguration m4_t1 = { &m4_t1_ac, &m4_t1_b };
523 static const Mode4Handler::ModesConfiguration m4_t5 = { &m4_t5_ac, &m4_t5_b };
524 static const Mode4Handler::ModesConfiguration m4_t6 = { &m4_t68_ac, &m4_t6_b };
525 static const Mode4Handler::ModesConfiguration m4_t7 = { &m4_t79_ac, &m4_t7_b };
526 static const Mode4Handler::ModesConfiguration m4_t8 = { &m4_t68_ac, &m4_t8_b };
527 static const Mode4Handler::ModesConfiguration m4_t9 = { &m4_t79_ac, &m4_t9_b };
529 static Mode6Handler::BankAnglePredicate m6_t2 = m6_t2_is_bank_angle;
530 static Mode6Handler::BankAnglePredicate m6_t4 = m6_t4_is_bank_angle;
532 static const IOHandler::FaultsConfiguration f_slow = { 180, 200 };
533 static const IOHandler::FaultsConfiguration f_fast = { 250, 290 };
538 const Mode1Handler::EnvelopesConfiguration *m1;
539 const Mode2Handler::Configuration *m2;
540 const Mode3Handler::Configuration *m3;
541 const Mode4Handler::ModesConfiguration *m4;
542 Mode6Handler::BankAnglePredicate m6;
543 const IOHandler::FaultsConfiguration *f;
545 } aircraft_types[] = {
546 { 0, &m1_t4, &m2_t4, &m3_t2, &m4_t6, m6_t4, &f_fast, 2000 },
547 { 1, &m1_t4, &m2_t4, &m3_t2, &m4_t8, m6_t4, &f_fast, 2000 },
548 { 2, &m1_t4, &m2_t4, &m3_t2, &m4_t5, m6_t4, &f_fast, 2000 },
549 { 3, &m1_t4, &m2_t5, &m3_t2, &m4_t7, m6_t4, &f_slow, 2000 },
550 { 4, &m1_t4, &m2_t5, &m3_t2, &m4_t9, m6_t4, &f_slow, 2000 },
551 { 254, &m1_t1, &m2_t1, &m3_t1, &m4_t1, m6_t2, &f_fast, 3500 },
552 { 255, &m1_t1, &m2_t1, &m3_t1, &m4_t1, m6_t2, &f_fast, 2000 }
555 for (size_t i = 0; i < n_elements(aircraft_types); i++)
556 if (aircraft_types[i].type == value)
558 mk->mode1_handler.conf.envelopes = aircraft_types[i].m1;
559 mk->mode2_handler.conf = aircraft_types[i].m2;
560 mk->mode3_handler.conf = aircraft_types[i].m3;
561 mk->mode4_handler.conf.modes = aircraft_types[i].m4;
562 mk->mode6_handler.conf.is_bank_angle = aircraft_types[i].m6;
563 mk->io_handler.conf.faults = aircraft_types[i].f;
564 mk->conf.runway_database = aircraft_types[i].runway_database;
568 state = STATE_INVALID_AIRCRAFT_TYPE;
573 MK_VIII::ConfigurationModule::read_air_data_input_select (int value)
576 return (value >= 0 && value <= 6) || (value >= 10 && value <= 13) || value == 255;
580 MK_VIII::ConfigurationModule::read_position_input_select (int value)
583 mk->io_handler.conf.use_internal_gps = true;
584 else if ((value >= 0 && value <= 5)
585 || (value >= 10 && value <= 13)
588 mk->io_handler.conf.use_internal_gps = false;
596 MK_VIII::ConfigurationModule::read_altitude_callouts (int value)
611 { 0, { MINIMUMS, SMART_500, 200, 100, 50, 40, 30, 20, 10, 0 } },
612 { 1, { MINIMUMS, SMART_500, 200, 0 } },
613 { 2, { MINIMUMS, SMART_500, 100, 50, 40, 30, 20, 10, 0 } },
614 { 3, { MINIMUMS, SMART_500, 0 } },
615 { 4, { MINIMUMS, 200, 100, 50, 40, 30, 20, 10, 0 } },
616 { 5, { MINIMUMS, 200, 0 } },
617 { 6, { MINIMUMS, 100, 50, 40, 30, 20, 10, 0 } },
619 { 8, { MINIMUMS, 0 } },
620 { 9, { MINIMUMS, 500, 200, 100, 50, 40, 30, 20, 10, 0 } },
621 { 10, { MINIMUMS, 500, 200, 0 } },
622 { 11, { MINIMUMS, 500, 100, 50, 40, 30, 20, 10, 0 } },
623 { 12, { MINIMUMS, 500, 0 } },
624 { 13, { MINIMUMS, 1000, 500, 400, 300, 200, 100, 50, 40, 30, 20, 10, 0 } },
625 { 14, { MINIMUMS, 100, 0 } },
626 { 15, { MINIMUMS, 200, 100, 0 } },
627 { 100, { FIELD_500, 0 } },
628 { 101, { FIELD_500_ABOVE, 0 } }
633 mk->mode6_handler.conf.minimums_enabled = false;
634 mk->mode6_handler.conf.smart_500_enabled = false;
635 mk->mode6_handler.conf.above_field_voice = NULL;
636 for (i = 0; i < n_altitude_callouts; i++)
637 mk->mode6_handler.conf.altitude_callouts_enabled[i] = false;
639 for (i = 0; i < n_elements(values); i++)
640 if (values[i].id == value)
642 for (int j = 0; values[i].callouts[j] != 0; j++)
643 switch (values[i].callouts[j])
646 mk->mode6_handler.conf.minimums_enabled = true;
650 mk->mode6_handler.conf.smart_500_enabled = true;
654 mk->mode6_handler.conf.above_field_voice = mk_altitude_voice(Mode6Handler::ALTITUDE_CALLOUT_500);
657 case FIELD_500_ABOVE:
658 mk->mode6_handler.conf.above_field_voice = mk_voice(five_hundred_above);
662 for (unsigned k = 0; k < n_altitude_callouts; k++)
663 if (mk->mode6_handler.altitude_callout_definitions[k] == values[i].callouts[j])
664 mk->mode6_handler.conf.altitude_callouts_enabled[k] = true;
675 MK_VIII::ConfigurationModule::read_audio_menu_select (int value)
677 if (value == 0 || value == 1)
678 mk->mode4_handler.conf.voice_too_low_gear = mk_voice(too_low_gear);
679 else if (value == 2 || value == 3)
680 mk->mode4_handler.conf.voice_too_low_gear = mk_voice(too_low_flaps);
688 MK_VIII::ConfigurationModule::read_terrain_display_select (int value)
691 mk->tcf_handler.conf.enabled = false;
692 else if (value == 0 || value == 1 || (value >= 3 && value <= 15)
693 || (value >= 18 && value <= 20) || (value >= 235 && value <= 255))
694 mk->tcf_handler.conf.enabled = true;
702 MK_VIII::ConfigurationModule::read_options_select_group_1 (int value)
704 if (value >= 0 && value < 128)
706 mk->io_handler.conf.flap_reversal = test_bits(value, 1 << 1);
707 mk->mode6_handler.conf.bank_angle_enabled = test_bits(value, 1 << 2);
708 mk->io_handler.conf.steep_approach_enabled = test_bits(value, 1 << 6);
716 MK_VIII::ConfigurationModule::read_radio_altitude_input_select (int value)
718 if (value >= 0 && value <= 2)
719 mk->io_handler.conf.use_gear_altitude = true;
721 mk->io_handler.conf.use_gear_altitude = false;
722 return (value >= 0 && value <= 4) || (value >= 251 && value <= 255);
726 MK_VIII::ConfigurationModule::read_navigation_input_select (int value)
728 if (value >= 0 && value <= 2)
729 mk->io_handler.conf.localizer_enabled = false;
730 else if ((value >= 3 && value <= 5) || (value >= 250 && value <= 255))
731 mk->io_handler.conf.localizer_enabled = true;
739 MK_VIII::ConfigurationModule::read_attitude_input_select (int value)
742 mk->io_handler.conf.use_attitude_indicator=true;
744 mk->io_handler.conf.use_attitude_indicator=false;
745 return (value >= 0 && value <= 6) || value == 253 || value == 255;
749 MK_VIII::ConfigurationModule::read_heading_input_select (int value)
752 return (value >= 0 && value <= 3) || value == 254 || value == 255;
756 MK_VIII::ConfigurationModule::read_windshear_input_select (int value)
759 return value == 0 || (value >= 253 && value <= 255);
763 MK_VIII::ConfigurationModule::read_input_output_discrete_type_select (int value)
768 IOHandler::LampConfiguration lamp_conf;
769 bool gpws_inhibit_enabled;
770 bool momentary_flap_override_enabled;
771 bool alternate_steep_approach;
773 { 0, { false, false }, false, true, true },
774 { 1, { true, false }, false, true, true },
775 { 2, { false, false }, true, true, true },
776 { 3, { true, false }, true, true, true },
777 { 4, { false, true }, true, true, true },
778 { 5, { true, true }, true, true, true },
779 { 6, { false, false }, true, true, false },
780 { 7, { true, false }, true, true, false },
781 { 254, { false, false }, true, false, true },
782 { 255, { false, false }, true, false, true }
785 for (size_t i = 0; i < n_elements(io_types); i++)
786 if (io_types[i].type == value)
788 mk->io_handler.conf.lamp = &io_types[i].lamp_conf;
789 mk->io_handler.conf.gpws_inhibit_enabled = io_types[i].gpws_inhibit_enabled;
790 mk->io_handler.conf.momentary_flap_override_enabled = io_types[i].momentary_flap_override_enabled;
791 mk->io_handler.conf.alternate_steep_approach = io_types[i].alternate_steep_approach;
799 MK_VIII::ConfigurationModule::read_audio_output_level (int value)
813 for (size_t i = 0; i < n_elements(values); i++)
814 if (values[i].id == value)
816 mk->voice_player.set_volume(mk->voice_player.conf.volume = modify_amplitude(1.0, values[i].relative_dB));
820 // The self test needs the voice player even when the configuration
821 // is invalid, so set a default value.
822 mk->voice_player.set_volume(mk->voice_player.conf.volume = 1.0);
827 MK_VIII::ConfigurationModule::read_undefined_input_select (int value)
834 MK_VIII::ConfigurationModule::boot ()
836 bool (MK_VIII::ConfigurationModule::*readers[N_CATEGORIES]) (int) = {
837 &MK_VIII::ConfigurationModule::read_aircraft_mode_type_select,
838 &MK_VIII::ConfigurationModule::read_air_data_input_select,
839 &MK_VIII::ConfigurationModule::read_position_input_select,
840 &MK_VIII::ConfigurationModule::read_altitude_callouts,
841 &MK_VIII::ConfigurationModule::read_audio_menu_select,
842 &MK_VIII::ConfigurationModule::read_terrain_display_select,
843 &MK_VIII::ConfigurationModule::read_options_select_group_1,
844 &MK_VIII::ConfigurationModule::read_radio_altitude_input_select,
845 &MK_VIII::ConfigurationModule::read_navigation_input_select,
846 &MK_VIII::ConfigurationModule::read_attitude_input_select,
847 &MK_VIII::ConfigurationModule::read_heading_input_select,
848 &MK_VIII::ConfigurationModule::read_windshear_input_select,
849 &MK_VIII::ConfigurationModule::read_input_output_discrete_type_select,
850 &MK_VIII::ConfigurationModule::read_audio_output_level,
851 &MK_VIII::ConfigurationModule::read_undefined_input_select,
852 &MK_VIII::ConfigurationModule::read_undefined_input_select,
853 &MK_VIII::ConfigurationModule::read_undefined_input_select
856 memcpy(effective_categories, categories, sizeof(categories));
859 for (int i = 0; i < N_CATEGORIES; i++)
860 if (! (this->*readers[i])(effective_categories[i]))
862 SG_LOG(SG_INSTR, SG_ALERT, "MK VIII EGPWS configuration category " << i + 1 << ": invalid value " << effective_categories[i]);
864 if (state == STATE_OK)
865 state = STATE_INVALID_DATABASE;
867 mk_doutput(gpws_inop) = true;
868 mk_doutput(tad_inop) = true;
875 if (mk->mode6_handler.conf.above_field_voice && ! mk->tcf_handler.conf.enabled)
878 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");
879 state = STATE_INVALID_DATABASE;
884 MK_VIII::ConfigurationModule::bind (SGPropertyNode *node)
886 for (int i = 0; i < N_CATEGORIES; i++)
888 std::ostringstream name;
889 name << "configuration-module/category-" << i + 1;
890 mk->properties_handler.tie(node, name.str().c_str(), SGRawValuePointer<int>(&categories[i]));
894 ///////////////////////////////////////////////////////////////////////////////
895 // FaultHandler ///////////////////////////////////////////////////////////////
896 ///////////////////////////////////////////////////////////////////////////////
898 // [INSTALL] only specifies that the faults cause a GPWS INOP
899 // indication. We arbitrarily set a TAD INOP when it makes sense.
900 const unsigned int MK_VIII::FaultHandler::fault_inops[] = {
901 INOP_GPWS | INOP_TAD, // [INSTALL] 3.15.1.3
902 INOP_GPWS, // [INSTALL] appendix E 6.6.2
903 INOP_GPWS, // [INSTALL] appendix E 6.6.4
904 INOP_GPWS, // [INSTALL] appendix E 6.6.6
905 INOP_GPWS | INOP_TAD, // [INSTALL] appendix E 6.6.7
906 INOP_GPWS, // [INSTALL] appendix E 6.6.13
907 INOP_GPWS, // [INSTALL] appendix E 6.6.25
908 INOP_GPWS, // [INSTALL] appendix E 6.6.27
909 INOP_TAD, // unspecified
910 INOP_GPWS, // unspecified
911 INOP_GPWS, // unspecified
912 // [INSTALL] 2.3.10.1 specifies that GPWS INOP is "activated during
913 // any detected partial or total failure of the GPWS modes 1-5", so
914 // do not set any INOP for mode 6 and bank angle.
917 INOP_TAD // unspecified
921 MK_VIII::FaultHandler::has_faults () const
923 for (int i = 0; i < N_FAULTS; i++)
931 MK_VIII::FaultHandler::has_faults (unsigned int inop)
933 for (int i = 0; i < N_FAULTS; i++)
934 if (faults[i] && test_bits(fault_inops[i], inop))
941 MK_VIII::FaultHandler::boot ()
943 memset(faults, 0, sizeof(faults));
947 MK_VIII::FaultHandler::set_fault (Fault fault)
951 faults[fault] = true;
953 mk->self_test_handler.set_inop();
955 if (test_bits(fault_inops[fault], INOP_GPWS))
957 mk_unset_alerts(~mk_alert(TCF_TOO_LOW_TERRAIN));
958 mk_doutput(gpws_inop) = true;
960 if (test_bits(fault_inops[fault], INOP_TAD))
962 mk_unset_alerts(mk_alert(TCF_TOO_LOW_TERRAIN));
963 mk_doutput(tad_inop) = true;
969 MK_VIII::FaultHandler::unset_fault (Fault fault)
973 faults[fault] = false;
974 if (! has_faults(INOP_GPWS))
975 mk_doutput(gpws_inop) = false;
976 if (! has_faults(INOP_TAD))
977 mk_doutput(tad_inop) = false;
981 ///////////////////////////////////////////////////////////////////////////////
982 // IOHandler //////////////////////////////////////////////////////////////////
983 ///////////////////////////////////////////////////////////////////////////////
986 MK_VIII::IOHandler::TerrainClearanceFilter::update (double agl)
988 // [PILOT] page 20 specifies that the terrain clearance is equal to
989 // 75% of the radio altitude, averaged over the previous 15 seconds.
991 // no updates when simulation is paused (dt=0.0), and add 5 samples/second only
992 if (globals->get_sim_time_sec() - last_update < 0.2)
994 last_update = globals->get_sim_time_sec();
996 samples_type::iterator iter;
998 // remove samples older than 15 seconds
999 for (iter = samples.begin(); iter != samples.end() && globals->get_sim_time_sec() - (*iter).timestamp >= 15; iter = samples.begin())
1000 samples.erase(iter);
1002 // append new sample
1003 samples.push_back(Sample<double>(agl));
1005 // calculate average
1006 double new_value = 0;
1007 if (samples.size() > 0)
1009 // time consuming loop => queue limited to 75 samples
1010 // (= 15seconds * 5samples/second)
1011 for (iter = samples.begin(); iter != samples.end(); iter++)
1012 new_value += (*iter).value;
1013 new_value /= samples.size();
1017 if (new_value > value)
1024 MK_VIII::IOHandler::TerrainClearanceFilter::reset ()
1031 MK_VIII::IOHandler::IOHandler (MK_VIII *device)
1032 : mk(device), _lamp(LAMP_NONE)
1034 memset(&input_feeders, 0, sizeof(input_feeders));
1035 memset(&inputs.discretes, 0, sizeof(inputs.discretes));
1036 memset(&outputs, 0, sizeof(outputs));
1037 memset(&power_saved, 0, sizeof(power_saved));
1039 mk_dinput_feed(landing_gear) = true;
1040 mk_dinput_feed(landing_flaps) = true;
1041 mk_dinput_feed(glideslope_inhibit) = true;
1042 mk_dinput_feed(decision_height) = true;
1043 mk_dinput_feed(autopilot_engaged) = true;
1044 mk_ainput_feed(uncorrected_barometric_altitude) = true;
1045 mk_ainput_feed(barometric_altitude_rate) = true;
1046 mk_ainput_feed(radio_altitude) = true;
1047 mk_ainput_feed(glideslope_deviation) = true;
1048 mk_ainput_feed(roll_angle) = true;
1049 mk_ainput_feed(localizer_deviation) = true;
1050 mk_ainput_feed(computed_airspeed) = true;
1052 // will be unset on power on
1053 mk_doutput(gpws_inop) = true;
1054 mk_doutput(tad_inop) = true;
1058 MK_VIII::IOHandler::boot ()
1060 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
1063 mk_doutput(gpws_inop) = false;
1064 mk_doutput(tad_inop) = false;
1066 mk_doutput(glideslope_cancel) = power_saved.glideslope_cancel;
1068 altitude_samples.clear();
1069 reset_terrain_clearance();
1073 MK_VIII::IOHandler::post_boot ()
1075 if (momentary_steep_approach_enabled())
1077 last_landing_gear = mk_dinput(landing_gear);
1078 last_real_flaps_down = real_flaps_down();
1081 // read externally-latching input discretes
1082 update_alternate_discrete_input(&mk_dinput(mode6_low_volume));
1083 update_alternate_discrete_input(&mk_dinput(audio_inhibit));
1087 MK_VIII::IOHandler::power_off ()
1089 power_saved.glideslope_cancel = mk_doutput(glideslope_cancel); // [SPEC] 6.2.5
1091 memset(&outputs, 0, sizeof(outputs));
1093 audio_inhibit_fault_timer.stop();
1094 landing_gear_fault_timer.stop();
1095 flaps_down_fault_timer.stop();
1096 momentary_flap_override_fault_timer.stop();
1097 self_test_fault_timer.stop();
1098 glideslope_cancel_fault_timer.stop();
1099 steep_approach_fault_timer.stop();
1100 gpws_inhibit_fault_timer.stop();
1101 ta_tcf_inhibit_fault_timer.stop();
1104 mk_doutput(gpws_inop) = true;
1105 mk_doutput(tad_inop) = true;
1109 MK_VIII::IOHandler::enter_ground ()
1111 reset_terrain_clearance();
1113 if (conf.momentary_flap_override_enabled)
1114 mk_doutput(flap_override) = false; // [INSTALL] 3.15.1.6
1118 MK_VIII::IOHandler::enter_takeoff ()
1120 reset_terrain_clearance();
1122 if (momentary_steep_approach_enabled())
1123 // landing or go-around, disable steep approach as per [SPEC] 6.2.1
1124 mk_doutput(steep_approach) = false;
1128 MK_VIII::IOHandler::update_inputs ()
1130 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
1133 // 1. process input feeders
1135 if (mk_dinput_feed(landing_gear))
1136 mk_dinput(landing_gear) = mk_node(gear_down)->getBoolValue();
1137 if (mk_dinput_feed(landing_flaps))
1138 mk_dinput(landing_flaps) = mk_node(flaps)->getDoubleValue() < 1;
1139 if (mk_dinput_feed(glideslope_inhibit))
1140 mk_dinput(glideslope_inhibit) = mk_node(nav0_gs_distance)->getDoubleValue() < 0;
1141 if (mk_dinput_feed(autopilot_engaged))
1145 mode = mk_node(autopilot_heading_lock)->getStringValue();
1146 mk_dinput(autopilot_engaged) = mode && *mode;
1149 if (mk_ainput_feed(uncorrected_barometric_altitude))
1151 if (mk_node(altimeter_serviceable)->getBoolValue())
1152 mk_ainput(uncorrected_barometric_altitude).set(mk_node(altimeter_altitude)->getDoubleValue());
1154 mk_ainput(uncorrected_barometric_altitude).unset();
1156 if (mk_ainput_feed(barometric_altitude_rate))
1157 // Do not use the vsi, because it is pressure-based only, and
1158 // therefore too laggy for GPWS alerting purposes. I guess that
1159 // a real ADC combines pressure-based and inerta-based altitude
1160 // rates to provide a non-laggy rate. That non-laggy rate is
1161 // best emulated by /velocities/vertical-speed-fps * 60.
1162 mk_ainput(barometric_altitude_rate).set(mk_node(vs)->getDoubleValue() * 60);
1163 if (mk_ainput_feed(radio_altitude))
1166 if (conf.use_gear_altitude)
1167 agl = mk_node(altitude_gear_agl)->getDoubleValue();
1169 agl = mk_node(altitude_agl)->getDoubleValue();
1170 // Some flight models may return negative values when on the
1171 // ground or after a crash; do not allow them.
1172 mk_ainput(radio_altitude).set(SG_MAX2(0.0, agl));
1174 if (mk_ainput_feed(glideslope_deviation))
1176 if (mk_node(nav0_serviceable)->getBoolValue()
1177 && mk_node(nav0_gs_serviceable)->getBoolValue()
1178 && mk_node(nav0_in_range)->getBoolValue()
1179 && mk_node(nav0_has_gs)->getBoolValue())
1180 // gs-needle-deflection is expressed in degrees, and 1 dot =
1181 // 0.32 degrees (according to
1182 // http://www.ntsb.gov/Recs/letters/2000/A00_41_45.pdf)
1183 mk_ainput(glideslope_deviation).set(mk_node(nav0_gs_needle_deflection)->getDoubleValue() / 0.32 * GLIDESLOPE_DOTS_TO_DDM);
1185 mk_ainput(glideslope_deviation).unset();
1187 if (mk_ainput_feed(roll_angle))
1189 if (conf.use_attitude_indicator)
1191 // read data from attitude indicator instrument (requires vacuum system to work)
1192 if (mk_node(ai_serviceable)->getBoolValue() && ! mk_node(ai_caged)->getBoolValue())
1193 mk_ainput(roll_angle).set(mk_node(ai_roll)->getDoubleValue());
1195 mk_ainput(roll_angle).unset();
1199 // use simulator source
1200 mk_ainput(roll_angle).set(mk_node(orientation_roll)->getDoubleValue());
1203 if (mk_ainput_feed(localizer_deviation))
1205 if (mk_node(nav0_serviceable)->getBoolValue()
1206 && mk_node(nav0_cdi_serviceable)->getBoolValue()
1207 && mk_node(nav0_in_range)->getBoolValue()
1208 && mk_node(nav0_nav_loc)->getBoolValue())
1209 // heading-needle-deflection is expressed in degrees, and 1
1210 // dot = 2 degrees (0.5 degrees for ILS, but navradio.cxx
1211 // performs the conversion)
1212 mk_ainput(localizer_deviation).set(mk_node(nav0_heading_needle_deflection)->getDoubleValue() / 2 * LOCALIZER_DOTS_TO_DDM);
1214 mk_ainput(localizer_deviation).unset();
1216 if (mk_ainput_feed(computed_airspeed))
1218 if (mk_node(asi_serviceable)->getBoolValue())
1219 mk_ainput(computed_airspeed).set(mk_node(asi_speed)->getDoubleValue());
1221 mk_ainput(computed_airspeed).unset();
1226 mk_data(decision_height).set(&mk_ainput(decision_height));
1227 mk_data(radio_altitude).set(&mk_ainput(radio_altitude));
1228 mk_data(roll_angle).set(&mk_ainput(roll_angle));
1230 // update barometric_altitude_rate as per [SPEC] 6.2.1: "During
1231 // normal conditions, the system will base Mode 1 computations upon
1232 // barometric rate from the ADC. If this computed data is not valid
1233 // or available then the system will use internally computed
1234 // barometric altitude rate."
1236 if (! mk_ainput(barometric_altitude_rate).ncd)
1237 // the altitude rate input is valid, use it
1238 mk_data(barometric_altitude_rate).set(mk_ainput(barometric_altitude_rate).get());
1243 // The altitude rate input is invalid. We can compute an
1244 // altitude rate if all the following conditions are true:
1246 // - the altitude input is valid
1247 // - there is an altitude sample with age >= 1 second
1248 // - that altitude sample is valid
1250 if (! mk_ainput(uncorrected_barometric_altitude).ncd)
1252 if (! altitude_samples.empty() && ! altitude_samples.front().value.ncd)
1254 double elapsed = globals->get_sim_time_sec() - altitude_samples.front().timestamp;
1258 mk_data(barometric_altitude_rate).set((mk_ainput(uncorrected_barometric_altitude).get() - altitude_samples.front().value.get()) / elapsed * 60);
1264 mk_data(barometric_altitude_rate).unset();
1267 altitude_samples.push_back(Sample< Parameter<double> >(mk_ainput(uncorrected_barometric_altitude)));
1269 // Erase everything from the beginning of the list up to the sample
1270 // preceding the most recent sample whose age is >= 1 second.
1272 altitude_samples_type::iterator erase_last = altitude_samples.begin();
1273 altitude_samples_type::iterator iter;
1275 for (iter = altitude_samples.begin(); iter != altitude_samples.end(); iter++)
1276 if (globals->get_sim_time_sec() - (*iter).timestamp >= 1)
1281 if (erase_last != altitude_samples.begin())
1282 altitude_samples.erase(altitude_samples.begin(), erase_last);
1286 if (conf.use_internal_gps)
1288 mk_data(gps_altitude).set(mk_node(altitude)->getDoubleValue());
1289 mk_data(gps_latitude).set(mk_node(latitude)->getDoubleValue());
1290 mk_data(gps_longitude).set(mk_node(longitude)->getDoubleValue());
1291 mk_data(gps_vertical_figure_of_merit).set(0.0);
1295 mk_data(gps_altitude).set(&mk_ainput(gps_altitude));
1296 mk_data(gps_latitude).set(&mk_ainput(gps_latitude));
1297 mk_data(gps_longitude).set(&mk_ainput(gps_longitude));
1298 mk_data(gps_vertical_figure_of_merit).set(&mk_ainput(gps_vertical_figure_of_merit));
1301 // update glideslope and localizer
1303 mk_data(glideslope_deviation_dots).set(&mk_ainput(glideslope_deviation), GLIDESLOPE_DDM_TO_DOTS);
1304 mk_data(localizer_deviation_dots).set(&mk_ainput(localizer_deviation), LOCALIZER_DDM_TO_DOTS);
1306 // Update geometric altitude; [SPEC] 6.7.8 provides an overview of a
1307 // complex algorithm which combines several input sources to
1308 // calculate the geometric altitude. Since the exact algorithm is
1309 // not given, we fallback to a much simpler method.
1311 if (! mk_data(gps_altitude).ncd)
1312 mk_data(geometric_altitude).set(mk_data(gps_altitude).get());
1313 else if (! mk_ainput(uncorrected_barometric_altitude).ncd)
1314 mk_data(geometric_altitude).set(mk_ainput(uncorrected_barometric_altitude).get());
1316 mk_data(geometric_altitude).unset();
1318 // update terrain clearance
1320 update_terrain_clearance();
1322 // 3. perform sanity checks
1324 if (! mk_data(radio_altitude).ncd && mk_data(radio_altitude).get() < 0)
1325 mk_data(radio_altitude).unset();
1327 if (! mk_data(decision_height).ncd && mk_data(decision_height).get() < 0)
1328 mk_data(decision_height).unset();
1330 if (! mk_data(gps_latitude).ncd
1331 && (mk_data(gps_latitude).get() < -90
1332 || mk_data(gps_latitude).get() > 90))
1333 mk_data(gps_latitude).unset();
1335 if (! mk_data(gps_longitude).ncd
1336 && (mk_data(gps_longitude).get() < -180
1337 || mk_data(gps_longitude).get() > 180))
1338 mk_data(gps_longitude).unset();
1340 if (! mk_data(roll_angle).ncd
1341 && ((mk_data(roll_angle).get() < -180)
1342 || (mk_data(roll_angle).get() > 180)))
1343 mk_data(roll_angle).unset();
1345 // 4. process input feeders requiring data computed above
1347 if (mk_dinput_feed(decision_height))
1348 mk_dinput(decision_height) = ! mk_data(radio_altitude).ncd
1349 && ! mk_data(decision_height).ncd
1350 && mk_data(radio_altitude).get() <= mk_data(decision_height).get();
1354 MK_VIII::IOHandler::update_terrain_clearance ()
1356 if (! mk_data(radio_altitude).ncd)
1357 mk_data(terrain_clearance).set(terrain_clearance_filter.update(mk_data(radio_altitude).get()));
1359 mk_data(terrain_clearance).unset();
1363 MK_VIII::IOHandler::reset_terrain_clearance ()
1365 terrain_clearance_filter.reset();
1366 update_terrain_clearance();
1370 MK_VIII::IOHandler::reposition ()
1372 reset_terrain_clearance();
1376 MK_VIII::IOHandler::handle_input_fault (bool test, FaultHandler::Fault fault)
1380 if (! mk->fault_handler.faults[fault])
1381 mk->fault_handler.set_fault(fault);
1384 mk->fault_handler.unset_fault(fault);
1388 MK_VIII::IOHandler::handle_input_fault (bool test,
1390 double max_duration,
1391 FaultHandler::Fault fault)
1395 if (! mk->fault_handler.faults[fault])
1397 if (timer->start_or_elapsed() >= max_duration)
1398 mk->fault_handler.set_fault(fault);
1403 mk->fault_handler.unset_fault(fault);
1409 MK_VIII::IOHandler::update_input_faults ()
1411 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
1414 // [INSTALL] 3.15.1.3
1415 handle_input_fault(mk_dinput(audio_inhibit),
1416 &audio_inhibit_fault_timer,
1418 FaultHandler::FAULT_ALL_MODES_INHIBIT);
1420 // [INSTALL] appendix E 6.6.2
1421 handle_input_fault(mk_dinput(landing_gear)
1422 && ! mk_ainput(computed_airspeed).ncd
1423 && mk_ainput(computed_airspeed).get() > conf.faults->max_gear_down_airspeed,
1424 &landing_gear_fault_timer,
1426 FaultHandler::FAULT_GEAR_SWITCH);
1428 // [INSTALL] appendix E 6.6.4
1429 handle_input_fault(flaps_down()
1430 && ! mk_ainput(computed_airspeed).ncd
1431 && mk_ainput(computed_airspeed).get() > conf.faults->max_flaps_down_airspeed,
1432 &flaps_down_fault_timer,
1434 FaultHandler::FAULT_FLAPS_SWITCH);
1436 // [INSTALL] appendix E 6.6.6
1437 if (conf.momentary_flap_override_enabled)
1438 handle_input_fault(mk_dinput(momentary_flap_override),
1439 &momentary_flap_override_fault_timer,
1441 FaultHandler::FAULT_MOMENTARY_FLAP_OVERRIDE_INVALID);
1443 // [INSTALL] appendix E 6.6.7
1444 handle_input_fault(mk_dinput(self_test),
1445 &self_test_fault_timer,
1447 FaultHandler::FAULT_SELF_TEST_INVALID);
1449 // [INSTALL] appendix E 6.6.13
1450 handle_input_fault(mk_dinput(glideslope_cancel),
1451 &glideslope_cancel_fault_timer,
1453 FaultHandler::FAULT_GLIDESLOPE_CANCEL_INVALID);
1455 // [INSTALL] appendix E 6.6.25
1456 if (momentary_steep_approach_enabled())
1457 handle_input_fault(mk_dinput(steep_approach),
1458 &steep_approach_fault_timer,
1460 FaultHandler::FAULT_STEEP_APPROACH_INVALID);
1462 // [INSTALL] appendix E 6.6.27
1463 if (conf.gpws_inhibit_enabled)
1464 handle_input_fault(mk_dinput(gpws_inhibit),
1465 &gpws_inhibit_fault_timer,
1467 FaultHandler::FAULT_GPWS_INHIBIT);
1469 // [INSTALL] does not specify a fault for this one, but it makes
1470 // sense to have it behave like GPWS inhibit
1471 handle_input_fault(mk_dinput(ta_tcf_inhibit),
1472 &ta_tcf_inhibit_fault_timer,
1474 FaultHandler::FAULT_TA_TCF_INHIBIT);
1476 // [PILOT] page 48: "In the event that required data for a
1477 // particular function is not available, then that function is
1478 // automatically inhibited and annunciated"
1480 handle_input_fault(mk_data(radio_altitude).ncd
1481 || mk_ainput(uncorrected_barometric_altitude).ncd
1482 || mk_data(barometric_altitude_rate).ncd
1483 || mk_ainput(computed_airspeed).ncd
1484 || mk_data(terrain_clearance).ncd,
1485 FaultHandler::FAULT_MODES14_INPUTS_INVALID);
1487 if (! mk_dinput(glideslope_inhibit))
1488 handle_input_fault(mk_data(radio_altitude).ncd,
1489 FaultHandler::FAULT_MODE5_INPUTS_INVALID);
1491 if (mk->mode6_handler.altitude_callouts_enabled())
1492 handle_input_fault(mk->mode6_handler.conf.above_field_voice
1493 ? (mk_data(gps_latitude).ncd
1494 || mk_data(gps_longitude).ncd
1495 || mk_data(geometric_altitude).ncd)
1496 : mk_data(radio_altitude).ncd,
1497 FaultHandler::FAULT_MODE6_INPUTS_INVALID);
1499 if (mk->mode6_handler.conf.bank_angle_enabled)
1500 handle_input_fault(mk_data(roll_angle).ncd,
1501 FaultHandler::FAULT_BANK_ANGLE_INPUTS_INVALID);
1503 if (mk->tcf_handler.conf.enabled)
1504 handle_input_fault(mk_data(radio_altitude).ncd
1505 || mk_data(geometric_altitude).ncd
1506 || mk_data(gps_latitude).ncd
1507 || mk_data(gps_longitude).ncd
1508 || mk_data(gps_vertical_figure_of_merit).ncd,
1509 FaultHandler::FAULT_TCF_INPUTS_INVALID);
1513 MK_VIII::IOHandler::update_alternate_discrete_input (bool *ptr)
1515 assert(mk->system_handler.state == SystemHandler::STATE_ON);
1517 if (ptr == &mk_dinput(mode6_low_volume))
1519 if (mk->configuration_module.state == ConfigurationModule::STATE_OK
1520 && mk->self_test_handler.state == SelfTestHandler::STATE_NONE)
1521 mk->mode6_handler.set_volume(*ptr ? modify_amplitude(1.0, -6) : 1.0);
1523 else if (ptr == &mk_dinput(audio_inhibit))
1525 if (mk->configuration_module.state == ConfigurationModule::STATE_OK
1526 && mk->self_test_handler.state == SelfTestHandler::STATE_NONE)
1527 mk->voice_player.set_volume(*ptr ? 0.0 : mk->voice_player.conf.volume);
1532 MK_VIII::IOHandler::update_internal_latches ()
1534 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
1538 if (conf.momentary_flap_override_enabled
1539 && mk_doutput(flap_override)
1540 && ! mk->state_handler.takeoff
1541 && (mk_data(radio_altitude).ncd || mk_data(radio_altitude).get() <= 50))
1542 mk_doutput(flap_override) = false;
1545 if (momentary_steep_approach_enabled())
1547 if (mk_doutput(steep_approach)
1548 && ! mk->state_handler.takeoff
1549 && ((last_landing_gear && ! mk_dinput(landing_gear))
1550 || (last_real_flaps_down && ! real_flaps_down())))
1551 // gear or flaps raised during approach: it's a go-around
1552 mk_doutput(steep_approach) = false;
1554 last_landing_gear = mk_dinput(landing_gear);
1555 last_real_flaps_down = real_flaps_down();
1559 if (mk_doutput(glideslope_cancel)
1560 && (mk_data(glideslope_deviation_dots).ncd
1561 || mk_data(radio_altitude).ncd
1562 || mk_data(radio_altitude).get() > 2000
1563 || mk_data(radio_altitude).get() < 30))
1564 mk_doutput(glideslope_cancel) = false;
1568 MK_VIII::IOHandler::update_egpws_alert_discrete_1 ()
1570 if (mk->voice_player.voice)
1575 VoicePlayer::Voice *voice;
1577 { 11, mk_voice(sink_rate_pause_sink_rate) },
1578 { 12, mk_voice(pull_up) },
1579 { 13, mk_voice(terrain) },
1580 { 13, mk_voice(terrain_pause_terrain) },
1581 { 14, mk_voice(dont_sink_pause_dont_sink) },
1582 { 15, mk_voice(too_low_gear) },
1583 { 16, mk_voice(too_low_flaps) },
1584 { 17, mk_voice(too_low_terrain) },
1585 { 18, mk_voice(soft_glideslope) },
1586 { 18, mk_voice(hard_glideslope) },
1587 { 19, mk_voice(minimums_minimums) }
1590 for (size_t i = 0; i < n_elements(voices); i++)
1591 if (voices[i].voice == mk->voice_player.voice)
1593 mk_aoutput(egpws_alert_discrete_1) = 1 << voices[i].bit;
1598 mk_aoutput(egpws_alert_discrete_1) = 0;
1602 MK_VIII::IOHandler::update_egpwc_logic_discretes ()
1604 mk_aoutput(egpwc_logic_discretes) = 0;
1606 if (mk->state_handler.takeoff)
1607 mk_aoutput(egpwc_logic_discretes) |= 1 << 18;
1612 unsigned int alerts;
1614 { 13, mk_alert(TCF_TOO_LOW_TERRAIN) },
1615 { 19, mk_alert(MODE1_SINK_RATE) },
1616 { 20, mk_alert(MODE1_PULL_UP) },
1617 { 21, mk_alert(MODE2A_PREFACE) | mk_alert(MODE2B_PREFACE) | mk_alert(MODE2B_LANDING_MODE) | mk_alert(MODE2A_ALTITUDE_GAIN_TERRAIN_CLOSING) },
1618 { 22, mk_alert(MODE2A) | mk_alert(MODE2B) },
1619 { 23, mk_alert(MODE3) },
1620 { 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) },
1621 { 25, mk_alert(MODE5_SOFT) | mk_alert(MODE5_HARD) }
1624 for (size_t i = 0; i < n_elements(logic); i++)
1625 if (mk_test_alerts(logic[i].alerts))
1626 mk_aoutput(egpwc_logic_discretes) |= 1 << logic[i].bit;
1630 MK_VIII::IOHandler::update_mode6_callouts_discrete_1 ()
1632 if (mk->voice_player.voice)
1637 VoicePlayer::Voice *voice;
1639 { 11, mk_voice(minimums_minimums) },
1640 { 16, mk_altitude_voice(Mode6Handler::ALTITUDE_CALLOUT_10) },
1641 { 17, mk_altitude_voice(Mode6Handler::ALTITUDE_CALLOUT_20) },
1642 { 18, mk_altitude_voice(Mode6Handler::ALTITUDE_CALLOUT_30) },
1643 { 19, mk_altitude_voice(Mode6Handler::ALTITUDE_CALLOUT_40) },
1644 { 20, mk_altitude_voice(Mode6Handler::ALTITUDE_CALLOUT_50) },
1645 { 23, mk_altitude_voice(Mode6Handler::ALTITUDE_CALLOUT_100) },
1646 { 24, mk_altitude_voice(Mode6Handler::ALTITUDE_CALLOUT_200) },
1647 { 25, mk_altitude_voice(Mode6Handler::ALTITUDE_CALLOUT_300) }
1650 for (size_t i = 0; i < n_elements(voices); i++)
1651 if (voices[i].voice == mk->voice_player.voice)
1653 mk_aoutput(mode6_callouts_discrete_1) = 1 << voices[i].bit;
1658 mk_aoutput(mode6_callouts_discrete_1) = 0;
1662 MK_VIII::IOHandler::update_mode6_callouts_discrete_2 ()
1664 if (mk->voice_player.voice)
1669 VoicePlayer::Voice *voice;
1671 { 11, mk_altitude_voice(Mode6Handler::ALTITUDE_CALLOUT_400) },
1672 { 12, mk_altitude_voice(Mode6Handler::ALTITUDE_CALLOUT_500) },
1673 { 13, mk_altitude_voice(Mode6Handler::ALTITUDE_CALLOUT_1000) },
1674 { 18, mk_voice(bank_angle_pause_bank_angle) },
1675 { 18, mk_voice(bank_angle_pause_bank_angle_3) },
1676 { 23, mk_voice(five_hundred_above) }
1679 for (size_t i = 0; i < n_elements(voices); i++)
1680 if (voices[i].voice == mk->voice_player.voice)
1682 mk_aoutput(mode6_callouts_discrete_2) = 1 << voices[i].bit;
1687 mk_aoutput(mode6_callouts_discrete_2) = 0;
1691 MK_VIII::IOHandler::update_egpws_alert_discrete_2 ()
1693 mk_aoutput(egpws_alert_discrete_2) = 1 << 27; // Terrain NA
1695 if (mk_doutput(glideslope_cancel))
1696 mk_aoutput(egpws_alert_discrete_2) |= 1 << 11;
1697 if (mk_doutput(gpws_alert))
1698 mk_aoutput(egpws_alert_discrete_2) |= 1 << 12;
1699 if (mk_doutput(gpws_warning))
1700 mk_aoutput(egpws_alert_discrete_2) |= 1 << 13;
1701 if (mk_doutput(gpws_inop))
1702 mk_aoutput(egpws_alert_discrete_2) |= 1 << 14;
1703 if (mk_doutput(audio_on))
1704 mk_aoutput(egpws_alert_discrete_2) |= 1 << 16;
1705 if (mk_doutput(tad_inop))
1706 mk_aoutput(egpws_alert_discrete_2) |= 1 << 24;
1707 if (mk->fault_handler.has_faults())
1708 mk_aoutput(egpws_alert_discrete_2) |= 1 << 25;
1712 MK_VIII::IOHandler::update_egpwc_alert_discrete_3 ()
1714 mk_aoutput(egpwc_alert_discrete_3) = 1 << 17 | 1 << 18;
1716 if (mk->fault_handler.faults[FaultHandler::FAULT_MODES14_INPUTS_INVALID])
1717 mk_aoutput(egpwc_alert_discrete_3) |= 1 << 11;
1718 if (mk->fault_handler.faults[FaultHandler::FAULT_MODE5_INPUTS_INVALID])
1719 mk_aoutput(egpwc_alert_discrete_3) |= 1 << 12;
1720 if (mk->fault_handler.faults[FaultHandler::FAULT_MODE6_INPUTS_INVALID])
1721 mk_aoutput(egpwc_alert_discrete_3) |= 1 << 13;
1722 if (mk->fault_handler.faults[FaultHandler::FAULT_BANK_ANGLE_INPUTS_INVALID])
1723 mk_aoutput(egpwc_alert_discrete_3) |= 1 << 14;
1724 if (mk_doutput(tad_inop))
1725 mk_aoutput(egpwc_alert_discrete_3) |= 1 << 16;
1729 MK_VIII::IOHandler::update_outputs ()
1731 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
1734 mk_doutput(audio_on) = ! mk_dinput(audio_inhibit)
1735 && mk->voice_player.voice
1736 && ! mk->voice_player.voice->element->silence;
1738 update_egpws_alert_discrete_1();
1739 update_egpwc_logic_discretes();
1740 update_mode6_callouts_discrete_1();
1741 update_mode6_callouts_discrete_2();
1742 update_egpws_alert_discrete_2();
1743 update_egpwc_alert_discrete_3();
1747 MK_VIII::IOHandler::get_lamp_output (Lamp lamp)
1751 case LAMP_GLIDESLOPE:
1752 return &mk_doutput(gpws_alert);
1755 return conf.lamp->format2 ? &mk_doutput(gpws_alert) : &mk_doutput(gpws_warning);
1758 return &mk_doutput(gpws_warning);
1761 assert_not_reached();
1767 MK_VIII::IOHandler::update_lamps ()
1769 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
1772 if (_lamp != LAMP_NONE && conf.lamp->flashing)
1774 // [SPEC] 6.9.3: 70 cycles per minute
1775 if (lamp_timer.elapsed() >= 60.0 / 70.0 / 2.0)
1777 bool *output = get_lamp_output(_lamp);
1778 *output = ! *output;
1785 MK_VIII::IOHandler::set_lamp (Lamp lamp)
1792 mk_doutput(gpws_warning) = false;
1793 mk_doutput(gpws_alert) = false;
1795 if (lamp != LAMP_NONE)
1797 *get_lamp_output(lamp) = true;
1803 MK_VIII::IOHandler::gpws_inhibit () const
1805 return conf.gpws_inhibit_enabled && mk_dinput(gpws_inhibit);
1809 MK_VIII::IOHandler::real_flaps_down () const
1811 return conf.flap_reversal ? mk_dinput(landing_flaps) : ! mk_dinput(landing_flaps);
1815 MK_VIII::IOHandler::flaps_down () const
1817 return flap_override() ? true : real_flaps_down();
1821 MK_VIII::IOHandler::flap_override () const
1823 return conf.momentary_flap_override_enabled ? mk_doutput(flap_override) : false;
1827 MK_VIII::IOHandler::steep_approach () const
1829 if (conf.steep_approach_enabled)
1830 // If alternate action was configured in category 13, we return
1831 // the value of the input discrete (which should be connected to a
1832 // latching steep approach cockpit switch). Otherwise, we return
1833 // the value of the output discrete.
1834 return conf.alternate_steep_approach ? mk_dinput(steep_approach) : mk_doutput(steep_approach);
1840 MK_VIII::IOHandler::momentary_steep_approach_enabled () const
1842 return conf.steep_approach_enabled && ! conf.alternate_steep_approach;
1846 MK_VIII::IOHandler::tie_input (SGPropertyNode *node,
1851 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));
1853 mk->properties_handler.tie(node, (string("input-feeders/discretes/") + name).c_str(), SGRawValuePointer<bool>(feed));
1857 MK_VIII::IOHandler::tie_input (SGPropertyNode *node,
1859 Parameter<double> *input,
1862 mk->properties_handler.tie(node, (string("inputs/arinc429/") + name).c_str(), SGRawValuePointer<double>(input->get_pointer()));
1863 mk->properties_handler.tie(node, (string("inputs/arinc429/") + name + "-ncd").c_str(), SGRawValuePointer<bool>(&input->ncd));
1865 mk->properties_handler.tie(node, (string("input-feeders/arinc429/") + name).c_str(), SGRawValuePointer<bool>(feed));
1869 MK_VIII::IOHandler::tie_output (SGPropertyNode *node,
1873 SGPropertyNode *child = node->getNode((string("outputs/discretes/") + name).c_str(), true);
1875 mk->properties_handler.tie(child, SGRawValuePointer<bool>(output));
1876 child->setAttribute(SGPropertyNode::WRITE, false);
1880 MK_VIII::IOHandler::tie_output (SGPropertyNode *node,
1884 SGPropertyNode *child = node->getNode((string("outputs/arinc429/") + name).c_str(), true);
1886 mk->properties_handler.tie(child, SGRawValuePointer<int>(output));
1887 child->setAttribute(SGPropertyNode::WRITE, false);
1891 MK_VIII::IOHandler::bind (SGPropertyNode *node)
1893 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));
1895 tie_input(node, "landing-gear", &mk_dinput(landing_gear), &mk_dinput_feed(landing_gear));
1896 tie_input(node, "landing-flaps", &mk_dinput(landing_flaps), &mk_dinput_feed(landing_flaps));
1897 tie_input(node, "momentary-flap-override", &mk_dinput(momentary_flap_override));
1898 tie_input(node, "self-test", &mk_dinput(self_test));
1899 tie_input(node, "glideslope-inhibit", &mk_dinput(glideslope_inhibit), &mk_dinput_feed(glideslope_inhibit));
1900 tie_input(node, "glideslope-cancel", &mk_dinput(glideslope_cancel));
1901 tie_input(node, "decision-height", &mk_dinput(decision_height), &mk_dinput_feed(decision_height));
1902 tie_input(node, "mode6-low-volume", &mk_dinput(mode6_low_volume));
1903 tie_input(node, "audio-inhibit", &mk_dinput(audio_inhibit));
1904 tie_input(node, "ta-tcf-inhibit", &mk_dinput(ta_tcf_inhibit));
1905 tie_input(node, "autopilot-engaged", &mk_dinput(autopilot_engaged), &mk_dinput_feed(autopilot_engaged));
1906 tie_input(node, "steep-approach", &mk_dinput(steep_approach));
1907 tie_input(node, "gpws-inhibit", &mk_dinput(gpws_inhibit));
1909 tie_input(node, "uncorrected-barometric-altitude", &mk_ainput(uncorrected_barometric_altitude), &mk_ainput_feed(uncorrected_barometric_altitude));
1910 tie_input(node, "barometric-altitude-rate", &mk_ainput(barometric_altitude_rate), &mk_ainput_feed(barometric_altitude_rate));
1911 tie_input(node, "gps-altitude", &mk_ainput(gps_altitude));
1912 tie_input(node, "gps-latitude", &mk_ainput(gps_latitude));
1913 tie_input(node, "gps-longitude", &mk_ainput(gps_longitude));
1914 tie_input(node, "gps-vertical-figure-of-merit", &mk_ainput(gps_vertical_figure_of_merit));
1915 tie_input(node, "radio-altitude", &mk_ainput(radio_altitude), &mk_ainput_feed(radio_altitude));
1916 tie_input(node, "glideslope-deviation", &mk_ainput(glideslope_deviation), &mk_ainput_feed(glideslope_deviation));
1917 tie_input(node, "roll-angle", &mk_ainput(roll_angle), &mk_ainput_feed(roll_angle));
1918 tie_input(node, "localizer-deviation", &mk_ainput(localizer_deviation), &mk_ainput_feed(localizer_deviation));
1919 tie_input(node, "computed-airspeed", &mk_ainput(computed_airspeed), &mk_ainput_feed(computed_airspeed));
1920 tie_input(node, "decision-height", &mk_ainput(decision_height), &mk_ainput_feed(decision_height));
1922 tie_output(node, "gpws-warning", &mk_doutput(gpws_warning));
1923 tie_output(node, "gpws-alert", &mk_doutput(gpws_alert));
1924 tie_output(node, "audio-on", &mk_doutput(audio_on));
1925 tie_output(node, "gpws-inop", &mk_doutput(gpws_inop));
1926 tie_output(node, "tad-inop", &mk_doutput(tad_inop));
1927 tie_output(node, "flap-override", &mk_doutput(flap_override));
1928 tie_output(node, "glideslope-cancel", &mk_doutput(glideslope_cancel));
1929 tie_output(node, "steep-approach", &mk_doutput(steep_approach));
1931 tie_output(node, "egpws-alert-discrete-1", &mk_aoutput(egpws_alert_discrete_1));
1932 tie_output(node, "egpwc-logic-discretes", &mk_aoutput(egpwc_logic_discretes));
1933 tie_output(node, "mode6-callouts-discrete-1", &mk_aoutput(mode6_callouts_discrete_1));
1934 tie_output(node, "mode6-callouts-discrete-2", &mk_aoutput(mode6_callouts_discrete_2));
1935 tie_output(node, "egpws-alert-discrete-2", &mk_aoutput(egpws_alert_discrete_2));
1936 tie_output(node, "egpwc-alert-discrete-3", &mk_aoutput(egpwc_alert_discrete_3));
1940 MK_VIII::IOHandler::get_discrete_input (bool *ptr) const
1946 MK_VIII::IOHandler::set_discrete_input (bool *ptr, bool value)
1951 if (mk->system_handler.state == SystemHandler::STATE_ON)
1953 if (ptr == &mk_dinput(momentary_flap_override))
1955 if (mk->configuration_module.state == ConfigurationModule::STATE_OK
1956 && mk->self_test_handler.state == SelfTestHandler::STATE_NONE
1957 && conf.momentary_flap_override_enabled
1959 mk_doutput(flap_override) = ! mk_doutput(flap_override);
1961 else if (ptr == &mk_dinput(self_test))
1962 mk->self_test_handler.handle_button_event(value);
1963 else if (ptr == &mk_dinput(glideslope_cancel))
1965 if (mk->configuration_module.state == ConfigurationModule::STATE_OK
1966 && mk->self_test_handler.state == SelfTestHandler::STATE_NONE
1969 if (! mk_doutput(glideslope_cancel))
1973 // Although we are not called from update(), we are
1974 // sure that the inputs we use here are defined,
1975 // since state is STATE_ON.
1977 if (! mk_data(glideslope_deviation_dots).ncd
1978 && ! mk_data(radio_altitude).ncd
1979 && mk_data(radio_altitude).get() <= 2000
1980 && mk_data(radio_altitude).get() >= 30)
1981 mk_doutput(glideslope_cancel) = true;
1983 // else do nothing ([PILOT] page 22: "Glideslope Cancel
1984 // can not be deselected (reset) by again pressing the
1985 // Glideslope Cancel switch.")
1988 else if (ptr == &mk_dinput(steep_approach))
1990 if (mk->configuration_module.state == ConfigurationModule::STATE_OK
1991 && mk->self_test_handler.state == SelfTestHandler::STATE_NONE
1992 && momentary_steep_approach_enabled()
1994 mk_doutput(steep_approach) = ! mk_doutput(steep_approach);
2000 if (mk->system_handler.state == SystemHandler::STATE_ON)
2001 update_alternate_discrete_input(ptr);
2005 MK_VIII::IOHandler::present_status_section (const char *name)
2007 printf("%s\n", name);
2011 MK_VIII::IOHandler::present_status_item (const char *name, const char *value)
2014 printf("\t%-32s %s\n", name, value);
2016 printf("\t%s\n", name);
2020 MK_VIII::IOHandler::present_status_subitem (const char *name)
2022 printf("\t\t%s\n", name);
2026 MK_VIII::IOHandler::present_status ()
2030 if (mk->system_handler.state != SystemHandler::STATE_ON)
2033 present_status_section("EGPWC CONFIGURATION");
2034 present_status_item("PART NUMBER:", "965-1220-000"); // [SPEC] 1.1
2035 present_status_item("MOD STATUS:", "N/A");
2036 present_status_item("SERIAL NUMBER:", "N/A");
2038 present_status_item("APPLICATION S/W VERSION:", FLIGHTGEAR_VERSION);
2039 present_status_item("TERRAIN DATABASE VERSION:", FLIGHTGEAR_VERSION);
2040 present_status_item("ENVELOPE MOD DATABASE VERSION:", "N/A");
2041 present_status_item("BOOT CODE VERSION:", FLIGHTGEAR_VERSION);
2044 present_status_section("CURRENT FAULTS");
2045 if (mk->configuration_module.state == ConfigurationModule::STATE_OK && ! mk->fault_handler.has_faults())
2046 present_status_item("NO FAULTS");
2049 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
2051 present_status_item("GPWS COMPUTER FAULTS:");
2052 switch (mk->configuration_module.state)
2054 case ConfigurationModule::STATE_INVALID_DATABASE:
2055 present_status_subitem("APPLICATION DATABASE FAILED");
2058 case ConfigurationModule::STATE_INVALID_AIRCRAFT_TYPE:
2059 present_status_subitem("CONFIGURATION TYPE INVALID");
2063 assert_not_reached();
2069 present_status_item("GPWS COMPUTER OK");
2070 present_status_item("GPWS EXTERNAL FAULTS:");
2072 static const char *fault_names[] = {
2073 "ALL MODES INHIBIT",
2076 "MOMENTARY FLAP OVERRIDE INVALID",
2077 "SELF TEST INVALID",
2078 "GLIDESLOPE CANCEL INVALID",
2079 "STEEP APPROACH INVALID",
2082 "MODES 1-4 INPUTS INVALID",
2083 "MODE 5 INPUTS INVALID",
2084 "MODE 6 INPUTS INVALID",
2085 "BANK ANGLE INPUTS INVALID",
2086 "TCF INPUTS INVALID"
2089 for (size_t i = 0; i < n_elements(fault_names); i++)
2090 if (mk->fault_handler.faults[i])
2091 present_status_subitem(fault_names[i]);
2096 present_status_section("CONFIGURATION:");
2098 static const char *category_names[] = {
2101 "POSITION INPUT TYPE",
2102 "CALLOUTS OPTION TYPE",
2104 "TERRAIN DISPLAY TYPE",
2106 "RADIO ALTITUDE TYPE",
2107 "NAVIGATION INPUT TYPE",
2109 "MAGNETIC HEADING TYPE",
2110 "WINDSHEAR INPUT TYPE",
2115 for (size_t i = 0; i < n_elements(category_names); i++)
2117 std::ostringstream value;
2118 value << "= " << mk->configuration_module.effective_categories[i];
2119 present_status_item(category_names[i], value.str().c_str());
2124 MK_VIII::IOHandler::get_present_status () const
2130 MK_VIII::IOHandler::set_present_status (bool value)
2132 if (value) present_status();
2136 ///////////////////////////////////////////////////////////////////////////////
2137 // VoicePlayer ////////////////////////////////////////////////////////////////
2138 ///////////////////////////////////////////////////////////////////////////////
2141 MK_VIII::VoicePlayer::Speaker::bind (SGPropertyNode *node)
2143 // uses xmlsound property names
2144 tie(node, "volume", &volume);
2145 tie(node, "pitch", &pitch);
2149 MK_VIII::VoicePlayer::Speaker::update_configuration ()
2151 map< string, SGSharedPtr<SGSoundSample> >::iterator iter;
2152 for (iter = player->samples.begin(); iter != player->samples.end(); iter++)
2154 SGSoundSample *sample = (*iter).second;
2156 sample->set_pitch(pitch);
2160 player->voice->volume_changed();
2163 MK_VIII::VoicePlayer::Voice::~Voice ()
2165 for (iter = elements.begin(); iter != elements.end(); iter++)
2166 delete *iter; // we owned the element
2171 MK_VIII::VoicePlayer::Voice::play ()
2173 iter = elements.begin();
2176 element->play(get_volume());
2180 MK_VIII::VoicePlayer::Voice::stop (bool now)
2184 if (now || element->silence)
2190 iter = elements.end() - 1; // stop after the current element finishes
2195 MK_VIII::VoicePlayer::Voice::set_volume (float _volume)
2202 MK_VIII::VoicePlayer::Voice::volume_changed ()
2205 element->set_volume(get_volume());
2209 MK_VIII::VoicePlayer::Voice::update ()
2213 if (! element->is_playing())
2215 if (++iter == elements.end())
2220 element->play(get_volume());
2226 MK_VIII::VoicePlayer::~VoicePlayer ()
2228 vector<Voice *>::iterator iter1;
2229 for (iter1 = _voices.begin(); iter1 != _voices.end(); iter1++)
2236 MK_VIII::VoicePlayer::init ()
2238 #define STDPAUSE 0.75 // [SPEC] 6.4.4: "the standard 0.75 second delay"
2240 SGSoundMgr *smgr = globals->get_soundmgr();
2241 _sgr = smgr->find("avionics", true);
2242 _sgr->tie_to_listener();
2244 make_voice(&voices.application_data_base_failed, "application-data-base-failed");
2245 make_voice(&voices.bank_angle, "bank-angle");
2246 make_voice(&voices.bank_angle_bank_angle, "bank-angle", "bank-angle");
2247 make_voice(&voices.bank_angle_bank_angle_3, "bank-angle", "bank-angle", 3.0);
2248 make_voice(&voices.bank_angle_inop, "bank-angle-inop");
2249 make_voice(&voices.bank_angle_pause_bank_angle, "bank-angle", STDPAUSE, "bank-angle");
2250 make_voice(&voices.bank_angle_pause_bank_angle_3, "bank-angle", STDPAUSE, "bank-angle", 3.0);
2251 make_voice(&voices.callouts_inop, "callouts-inop");
2252 make_voice(&voices.configuration_type_invalid, "configuration-type-invalid");
2253 make_voice(&voices.dont_sink, "dont-sink");
2254 make_voice(&voices.dont_sink_pause_dont_sink, "dont-sink", STDPAUSE, "dont-sink");
2255 make_voice(&voices.five_hundred_above, "500-above");
2256 make_voice(&voices.glideslope, "glideslope");
2257 make_voice(&voices.glideslope_inop, "glideslope-inop");
2258 make_voice(&voices.gpws_inop, "gpws-inop");
2259 make_voice(&voices.hard_glideslope, "glideslope", "glideslope", 3.0);
2260 make_voice(&voices.minimums, "minimums");
2261 make_voice(&voices.minimums_minimums, "minimums", "minimums");
2262 make_voice(&voices.pull_up, "pull-up");
2263 make_voice(&voices.sink_rate, "sink-rate");
2264 make_voice(&voices.sink_rate_pause_sink_rate, "sink-rate", STDPAUSE, "sink-rate");
2265 make_voice(&voices.soft_glideslope, new Voice::SampleElement(get_sample("glideslope"), modify_amplitude(1.0, -6)));
2266 make_voice(&voices.terrain, "terrain");
2267 make_voice(&voices.terrain_pause_terrain, "terrain", STDPAUSE, "terrain");
2268 make_voice(&voices.too_low_flaps, "too-low-flaps");
2269 make_voice(&voices.too_low_gear, "too-low-gear");
2270 make_voice(&voices.too_low_terrain, "too-low-terrain");
2272 for (unsigned i = 0; i < n_altitude_callouts; i++)
2274 std::ostringstream name;
2275 name << "altitude-" << mk->mode6_handler.altitude_callout_definitions[i];
2276 make_voice(&voices.altitude_callouts[i], name.str().c_str());
2279 speaker.update_configuration();
2283 MK_VIII::VoicePlayer::get_sample (const char *name)
2285 std::ostringstream refname;
2286 refname << mk->name << "[" << mk->num << "]" << "/" << name;
2288 SGSoundSample *sample = _sgr->find(refname.str());
2291 string filename = "Sounds/mk-viii/" + string(name) + ".wav";
2294 sample = new SGSoundSample(filename.c_str(), SGPath());
2296 catch (const sg_exception &e)
2298 SG_LOG(SG_INSTR, SG_ALERT, "Error loading MK VIII sound sample \"" + filename + "\": " + e.getFormattedMessage());
2302 _sgr->add(sample, refname.str());
2303 samples[refname.str()] = sample;
2310 MK_VIII::VoicePlayer::play (Voice *_voice, unsigned int flags)
2312 if (test_bits(flags, PLAY_NOW) || ! voice || voice->element->silence)
2318 looped = test_bits(flags, PLAY_LOOPED);
2321 next_looped = false;
2327 next_voice = _voice;
2328 next_looped = test_bits(flags, PLAY_LOOPED);
2333 MK_VIII::VoicePlayer::stop (unsigned int flags)
2337 voice->stop(test_bits(flags, STOP_NOW));
2347 MK_VIII::VoicePlayer::set_volume (float _volume)
2351 voice->volume_changed();
2355 MK_VIII::VoicePlayer::update ()
2363 if (! voice->element || voice->element->silence)
2366 looped = next_looped;
2369 next_looped = false;
2376 if (! voice->element)
2387 ///////////////////////////////////////////////////////////////////////////////
2388 // SelfTestHandler ////////////////////////////////////////////////////////////
2389 ///////////////////////////////////////////////////////////////////////////////
2392 MK_VIII::SelfTestHandler::_was_here (int position)
2394 if (position > current)
2403 MK_VIII::SelfTestHandler::Action
2404 MK_VIII::SelfTestHandler::sleep (double duration)
2406 Action _action = { ACTION_SLEEP, duration, NULL };
2410 MK_VIII::SelfTestHandler::Action
2411 MK_VIII::SelfTestHandler::play (VoicePlayer::Voice *voice)
2413 mk->voice_player.play(voice);
2414 Action _action = { ACTION_VOICE, 0, NULL };
2418 MK_VIII::SelfTestHandler::Action
2419 MK_VIII::SelfTestHandler::discrete_on (bool *discrete, double duration)
2422 return sleep(duration);
2425 MK_VIII::SelfTestHandler::Action
2426 MK_VIII::SelfTestHandler::discrete_on_off (bool *discrete, double duration)
2429 Action _action = { ACTION_SLEEP | ACTION_DISCRETE_ON_OFF, duration, discrete };
2433 MK_VIII::SelfTestHandler::Action
2434 MK_VIII::SelfTestHandler::discrete_on_off (bool *discrete, VoicePlayer::Voice *voice)
2437 mk->voice_player.play(voice);
2438 Action _action = { ACTION_VOICE | ACTION_DISCRETE_ON_OFF, 0, discrete };
2442 MK_VIII::SelfTestHandler::Action
2443 MK_VIII::SelfTestHandler::done ()
2445 Action _action = { ACTION_DONE, 0, NULL };
2449 MK_VIII::SelfTestHandler::Action
2450 MK_VIII::SelfTestHandler::run ()
2452 // Note that "Terrain INOP" and "Terrain NA" are or'ed to the same
2453 // output discrete (see [SPEC] 6.9.3.5).
2455 #define was_here() was_here_offset(0)
2456 #define was_here_offset(offset) _was_here(__LINE__ * 20 + (offset))
2460 if (mk->configuration_module.state == ConfigurationModule::STATE_INVALID_DATABASE)
2461 return play(mk_voice(application_data_base_failed));
2462 else if (mk->configuration_module.state == ConfigurationModule::STATE_INVALID_AIRCRAFT_TYPE)
2463 return play(mk_voice(configuration_type_invalid));
2465 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
2469 return discrete_on(&mk_doutput(gpws_inop), 0.7);
2471 return sleep(0.7); // W/S INOP
2473 return discrete_on(&mk_doutput(tad_inop), 0.7);
2476 mk_doutput(gpws_inop) = false;
2477 // do not disable tad_inop since we must enable Terrain NA
2478 // do not disable W/S INOP since we do not emulate it
2479 return sleep(0.7); // Terrain NA
2483 mk_doutput(tad_inop) = false; // disable Terrain NA
2484 if (mk->io_handler.conf.momentary_flap_override_enabled)
2485 return discrete_on_off(&mk_doutput(flap_override), 1.0);
2488 return discrete_on_off(&mk_doutput(audio_on), 1.0);
2491 if (mk->io_handler.momentary_steep_approach_enabled())
2492 return discrete_on_off(&mk_doutput(steep_approach), 1.0);
2497 if (mk_dinput(glideslope_inhibit))
2498 goto glideslope_end;
2501 if (mk->fault_handler.faults[FaultHandler::FAULT_MODE5_INPUTS_INVALID])
2502 goto glideslope_inop;
2506 return discrete_on_off(&mk_doutput(gpws_alert), mk_voice(glideslope));
2508 return discrete_on_off(&mk_doutput(glideslope_cancel), 0.7);
2509 goto glideslope_end;
2512 return play(mk_voice(glideslope_inop));
2517 if (mk->fault_handler.faults[FaultHandler::FAULT_MODES14_INPUTS_INVALID])
2521 return discrete_on_off(&mk_doutput(gpws_warning), mk_voice(pull_up));
2525 return play(mk_voice(gpws_inop));
2530 if (mk_dinput(self_test)) // proceed to long self test
2535 if (mk->mode6_handler.conf.bank_angle_enabled
2536 && mk->fault_handler.faults[FaultHandler::FAULT_BANK_ANGLE_INPUTS_INVALID])
2537 return play(mk_voice(bank_angle_inop));
2541 if (mk->mode6_handler.altitude_callouts_enabled()
2542 && mk->fault_handler.faults[FaultHandler::FAULT_MODE6_INPUTS_INVALID])
2543 return play(mk_voice(callouts_inop));
2551 mk_doutput(gpws_inop) = true;
2552 // do not enable W/S INOP, since we do not emulate it
2553 mk_doutput(tad_inop) = true; // Terrain INOP, Terrain NA
2555 return play(mk_voice(sink_rate));
2558 return play(mk_voice(pull_up));
2560 return play(mk_voice(terrain));
2562 return play(mk_voice(pull_up));
2564 return play(mk_voice(dont_sink));
2566 return play(mk_voice(too_low_terrain));
2568 return play(mk->mode4_handler.conf.voice_too_low_gear);
2570 return play(mk_voice(too_low_flaps));
2572 return play(mk_voice(too_low_terrain));
2574 return play(mk_voice(glideslope));
2577 if (mk->mode6_handler.conf.bank_angle_enabled)
2578 return play(mk_voice(bank_angle));
2583 if (! mk->mode6_handler.altitude_callouts_enabled())
2584 goto callouts_disabled;
2588 if (mk->mode6_handler.conf.minimums_enabled)
2589 return play(mk_voice(minimums));
2593 if (mk->mode6_handler.conf.above_field_voice)
2594 return play(mk->mode6_handler.conf.above_field_voice);
2596 for (unsigned i = 0; i < n_altitude_callouts; i++)
2597 if (! was_here_offset(i))
2599 if (mk->mode6_handler.conf.altitude_callouts_enabled[i])
2600 return play(mk_altitude_voice(i));
2604 if (mk->mode6_handler.conf.smart_500_enabled)
2605 return play(mk_altitude_voice(Mode6Handler::ALTITUDE_CALLOUT_500));
2610 return play(mk_voice(minimums_minimums));
2615 if (mk->tcf_handler.conf.enabled)
2616 return play(mk_voice(too_low_terrain));
2623 MK_VIII::SelfTestHandler::start ()
2625 assert(state == STATE_START);
2627 memcpy(&saved_outputs, &mk->io_handler.outputs, sizeof(mk->io_handler.outputs));
2628 memset(&mk->io_handler.outputs, 0, sizeof(mk->io_handler.outputs));
2630 // [SPEC] 6.10.6: "The self-test results are annunciated, at 6db
2631 // lower than the normal audio level selected for the aircraft"
2632 mk->voice_player.set_volume(modify_amplitude(mk->voice_player.conf.volume, -6));
2634 if (mk_dinput(mode6_low_volume))
2635 mk->mode6_handler.set_volume(1.0);
2637 state = STATE_RUNNING;
2638 cancel = CANCEL_NONE;
2639 memset(&action, 0, sizeof(action));
2644 MK_VIII::SelfTestHandler::stop ()
2646 if (state != STATE_NONE)
2648 if (state != STATE_START)
2650 mk->voice_player.stop(VoicePlayer::STOP_NOW);
2651 mk->voice_player.set_volume(mk_dinput(audio_inhibit) ? 0.0 : mk->voice_player.conf.volume);
2653 if (mk_dinput(mode6_low_volume))
2654 mk->mode6_handler.set_volume(modify_amplitude(1.0, -6));
2656 memcpy(&mk->io_handler.outputs, &saved_outputs, sizeof(mk->io_handler.outputs));
2659 button_pressed = false;
2661 // reset self-test handler position
2667 MK_VIII::SelfTestHandler::handle_button_event (bool value)
2669 if (state == STATE_NONE)
2672 state = STATE_START;
2674 else if (state == STATE_START)
2677 state = STATE_NONE; // cancel the not-yet-started test
2679 else if (cancel == CANCEL_NONE)
2683 assert(! button_pressed);
2684 button_pressed = true;
2685 button_press_timestamp = globals->get_sim_time_sec();
2691 if (globals->get_sim_time_sec() - button_press_timestamp < 2)
2692 cancel = CANCEL_SHORT;
2694 cancel = CANCEL_LONG;
2701 MK_VIII::SelfTestHandler::update ()
2703 if (state == STATE_NONE)
2705 else if (state == STATE_START)
2707 if (mk->state_handler.ground && ! mk->io_handler.steep_approach())
2717 if (button_pressed && cancel == CANCEL_NONE)
2719 if (globals->get_sim_time_sec() - button_press_timestamp >= 2)
2720 cancel = CANCEL_LONG;
2724 if (! mk->state_handler.ground || cancel != CANCEL_NONE)
2730 if (test_bits(action.flags, ACTION_SLEEP))
2732 if (globals->get_sim_time_sec() - sleep_start < action.sleep_duration)
2735 if (test_bits(action.flags, ACTION_VOICE))
2737 if (mk->voice_player.voice)
2740 if (test_bits(action.flags, ACTION_DISCRETE_ON_OFF))
2741 *action.discrete = false;
2745 if (test_bits(action.flags, ACTION_SLEEP))
2746 sleep_start = globals->get_sim_time_sec();
2747 if (test_bits(action.flags, ACTION_DONE))
2756 ///////////////////////////////////////////////////////////////////////////////
2757 // AlertHandler ///////////////////////////////////////////////////////////////
2758 ///////////////////////////////////////////////////////////////////////////////
2761 MK_VIII::AlertHandler::select_voice_alerts (unsigned int test)
2763 if (has_alerts(test))
2765 voice_alerts = alerts & test;
2770 voice_alerts &= ~test;
2771 if (voice_alerts == 0)
2772 mk->voice_player.stop();
2779 MK_VIII::AlertHandler::boot ()
2785 MK_VIII::AlertHandler::reposition ()
2789 mk->io_handler.set_lamp(IOHandler::LAMP_NONE);
2790 mk->voice_player.stop(VoicePlayer::STOP_NOW);
2794 MK_VIII::AlertHandler::reset ()
2799 repeated_alerts = 0;
2800 altitude_callout_voice = NULL;
2804 MK_VIII::AlertHandler::update ()
2806 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
2809 // Lamps and voices are prioritized according to [SPEC] 6.9.2.
2811 // Voices which can interrupt other voices (VoicePlayer::PLAY_NOW)
2812 // are specified by [INSTALL] appendix E 5.3.5.
2814 // When a voice is overriden by a higher priority voice and the
2815 // overriding voice stops, we restore the previous voice if it was
2816 // looped (this is not specified by [SPEC] but seems to make sense).
2820 if (has_alerts(ALERT_MODE1_PULL_UP | ALERT_MODE2A | ALERT_MODE2B))
2821 mk->io_handler.set_lamp(IOHandler::LAMP_WARNING);
2822 else if (has_alerts(ALERT_MODE1_SINK_RATE
2823 | ALERT_MODE2A_PREFACE
2824 | ALERT_MODE2B_PREFACE
2825 | ALERT_MODE2A_ALTITUDE_GAIN_TERRAIN_CLOSING
2826 | ALERT_MODE2A_ALTITUDE_GAIN
2827 | ALERT_MODE2B_LANDING_MODE
2829 | ALERT_MODE4_TOO_LOW_FLAPS
2830 | ALERT_MODE4_TOO_LOW_GEAR
2831 | ALERT_MODE4AB_TOO_LOW_TERRAIN
2832 | ALERT_MODE4C_TOO_LOW_TERRAIN
2833 | ALERT_TCF_TOO_LOW_TERRAIN))
2834 mk->io_handler.set_lamp(IOHandler::LAMP_CAUTION);
2835 else if (has_alerts(ALERT_MODE5_SOFT | ALERT_MODE5_HARD))
2836 mk->io_handler.set_lamp(IOHandler::LAMP_GLIDESLOPE);
2838 mk->io_handler.set_lamp(IOHandler::LAMP_NONE);
2842 if (select_voice_alerts(ALERT_MODE1_PULL_UP))
2844 if (! has_old_alerts(ALERT_MODE1_PULL_UP))
2846 if (mk->voice_player.voice != mk_voice(sink_rate_pause_sink_rate))
2847 mk->voice_player.play(mk_voice(sink_rate), VoicePlayer::PLAY_NOW);
2848 mk->voice_player.play(mk_voice(pull_up), VoicePlayer::PLAY_LOOPED);
2851 else if (select_voice_alerts(ALERT_MODE2A_PREFACE | ALERT_MODE2B_PREFACE))
2853 if (! has_old_alerts(ALERT_MODE2A_PREFACE | ALERT_MODE2B_PREFACE))
2854 mk->voice_player.play(mk_voice(terrain_pause_terrain), VoicePlayer::PLAY_NOW);
2856 else if (select_voice_alerts(ALERT_MODE2A | ALERT_MODE2B))
2858 if (mk->voice_player.voice != mk_voice(pull_up))
2859 mk->voice_player.play(mk_voice(pull_up), VoicePlayer::PLAY_NOW | VoicePlayer::PLAY_LOOPED);
2861 else if (select_voice_alerts(ALERT_MODE2A_ALTITUDE_GAIN_TERRAIN_CLOSING | ALERT_MODE2B_LANDING_MODE))
2863 if (mk->voice_player.voice != mk_voice(terrain))
2864 mk->voice_player.play(mk_voice(terrain), VoicePlayer::PLAY_LOOPED);
2866 else if (select_voice_alerts(ALERT_MODE6_MINIMUMS))
2868 if (! has_old_alerts(ALERT_MODE6_MINIMUMS))
2869 mk->voice_player.play(mk_voice(minimums_minimums));
2871 else if (select_voice_alerts(ALERT_MODE4AB_TOO_LOW_TERRAIN | ALERT_MODE4C_TOO_LOW_TERRAIN))
2873 if (must_play_voice(ALERT_MODE4AB_TOO_LOW_TERRAIN | ALERT_MODE4C_TOO_LOW_TERRAIN))
2874 mk->voice_player.play(mk_voice(too_low_terrain));
2876 else if (select_voice_alerts(ALERT_TCF_TOO_LOW_TERRAIN))
2878 if (must_play_voice(ALERT_TCF_TOO_LOW_TERRAIN))
2879 mk->voice_player.play(mk_voice(too_low_terrain));
2881 else if (select_voice_alerts(ALERT_MODE6_ALTITUDE_CALLOUT))
2883 if (! has_old_alerts(ALERT_MODE6_ALTITUDE_CALLOUT))
2885 assert(altitude_callout_voice != NULL);
2886 mk->voice_player.play(altitude_callout_voice);
2887 altitude_callout_voice = NULL;
2890 else if (select_voice_alerts(ALERT_MODE4_TOO_LOW_GEAR))
2892 if (must_play_voice(ALERT_MODE4_TOO_LOW_GEAR))
2893 mk->voice_player.play(mk->mode4_handler.conf.voice_too_low_gear);
2895 else if (select_voice_alerts(ALERT_MODE4_TOO_LOW_FLAPS))
2897 if (must_play_voice(ALERT_MODE4_TOO_LOW_FLAPS))
2898 mk->voice_player.play(mk_voice(too_low_flaps));
2900 else if (select_voice_alerts(ALERT_MODE1_SINK_RATE))
2902 if (must_play_voice(ALERT_MODE1_SINK_RATE))
2903 mk->voice_player.play(mk_voice(sink_rate_pause_sink_rate));
2904 // [SPEC] 6.2.1: "During the time that the voice message for the
2905 // outer envelope is inhibited and the caution/warning lamp is
2906 // on, the Mode 5 alert message is allowed to annunciate for
2907 // excessive glideslope deviation conditions."
2908 else if (mk->voice_player.voice != mk_voice(sink_rate_pause_sink_rate)
2909 && mk->voice_player.next_voice != mk_voice(sink_rate_pause_sink_rate))
2911 if (has_alerts(ALERT_MODE5_HARD))
2913 voice_alerts |= ALERT_MODE5_HARD;
2914 if (mk->voice_player.voice != mk_voice(hard_glideslope))
2915 mk->voice_player.play(mk_voice(hard_glideslope), VoicePlayer::PLAY_LOOPED);
2917 else if (has_alerts(ALERT_MODE5_SOFT))
2919 voice_alerts |= ALERT_MODE5_SOFT;
2920 if (must_play_voice(ALERT_MODE5_SOFT))
2921 mk->voice_player.play(mk_voice(soft_glideslope));
2925 else if (select_voice_alerts(ALERT_MODE3))
2927 if (must_play_voice(ALERT_MODE3))
2928 mk->voice_player.play(mk_voice(dont_sink_pause_dont_sink));
2930 else if (select_voice_alerts(ALERT_MODE5_HARD))
2932 if (mk->voice_player.voice != mk_voice(hard_glideslope))
2933 mk->voice_player.play(mk_voice(hard_glideslope), VoicePlayer::PLAY_LOOPED);
2935 else if (select_voice_alerts(ALERT_MODE5_SOFT))
2937 if (must_play_voice(ALERT_MODE5_SOFT))
2938 mk->voice_player.play(mk_voice(soft_glideslope));
2940 else if (select_voice_alerts(ALERT_MODE6_LOW_BANK_ANGLE_3))
2942 if (mk->voice_player.voice != mk_voice(bank_angle_bank_angle_3))
2943 mk->voice_player.play(mk_voice(bank_angle_bank_angle_3), VoicePlayer::PLAY_LOOPED);
2945 else if (select_voice_alerts(ALERT_MODE6_HIGH_BANK_ANGLE_3))
2947 if (mk->voice_player.voice != mk_voice(bank_angle_pause_bank_angle_3))
2948 mk->voice_player.play(mk_voice(bank_angle_pause_bank_angle_3), VoicePlayer::PLAY_LOOPED);
2950 else if (select_voice_alerts(ALERT_MODE6_LOW_BANK_ANGLE_2))
2952 if (! has_old_alerts(ALERT_MODE6_LOW_BANK_ANGLE_2 | ALERT_MODE6_HIGH_BANK_ANGLE_2))
2953 mk->voice_player.play(mk_voice(bank_angle_bank_angle));
2955 else if (select_voice_alerts(ALERT_MODE6_HIGH_BANK_ANGLE_2))
2957 if (! has_old_alerts(ALERT_MODE6_LOW_BANK_ANGLE_2 | ALERT_MODE6_HIGH_BANK_ANGLE_2))
2958 mk->voice_player.play(mk_voice(bank_angle_pause_bank_angle));
2960 else if (select_voice_alerts(ALERT_MODE6_LOW_BANK_ANGLE_1))
2962 if (! has_old_alerts(ALERT_MODE6_LOW_BANK_ANGLE_1 | ALERT_MODE6_HIGH_BANK_ANGLE_1))
2963 mk->voice_player.play(mk_voice(bank_angle_bank_angle));
2965 else if (select_voice_alerts(ALERT_MODE6_HIGH_BANK_ANGLE_1))
2967 if (! has_old_alerts(ALERT_MODE6_LOW_BANK_ANGLE_1 | ALERT_MODE6_HIGH_BANK_ANGLE_1))
2968 mk->voice_player.play(mk_voice(bank_angle_pause_bank_angle));
2973 old_alerts = voice_alerts;
2974 repeated_alerts = 0;
2978 MK_VIII::AlertHandler::set_alerts (unsigned int _alerts,
2980 VoicePlayer::Voice *_altitude_callout_voice)
2983 if (test_bits(flags, ALERT_FLAG_REPEAT))
2984 repeated_alerts |= _alerts;
2985 if (_altitude_callout_voice)
2986 altitude_callout_voice = _altitude_callout_voice;
2990 MK_VIII::AlertHandler::unset_alerts (unsigned int _alerts)
2993 repeated_alerts &= ~_alerts;
2994 if (_alerts & ALERT_MODE6_ALTITUDE_CALLOUT)
2995 altitude_callout_voice = NULL;
2998 ///////////////////////////////////////////////////////////////////////////////
2999 // StateHandler ///////////////////////////////////////////////////////////////
3000 ///////////////////////////////////////////////////////////////////////////////
3003 MK_VIII::StateHandler::update_ground ()
3007 if (! mk_ainput(computed_airspeed).ncd && mk_ainput(computed_airspeed).get() > 60
3008 && ! mk_data(radio_altitude).ncd && mk_data(radio_altitude).get() > 30)
3010 if (potentially_airborne_timer.start_or_elapsed() > 1)
3014 potentially_airborne_timer.stop();
3018 if (! mk_ainput(computed_airspeed).ncd && mk_ainput(computed_airspeed).get() < 40
3019 && ! mk_data(radio_altitude).ncd && mk_data(radio_altitude).get() < 30)
3025 MK_VIII::StateHandler::enter_ground ()
3028 mk->io_handler.enter_ground();
3030 // [SPEC] 6.0.1 does not specify this, but it seems to be an
3031 // omission; at this point, we must make sure that we are in takeoff
3032 // mode (otherwise the next takeoff might happen in approach mode).
3038 MK_VIII::StateHandler::leave_ground ()
3041 mk->mode2_handler.leave_ground();
3045 MK_VIII::StateHandler::update_takeoff ()
3049 // [SPEC] 6.0.2 is somewhat confusing: it mentions hardcoded
3050 // terrain clearance (500, 750) and airspeed (178, 200) values,
3051 // but it also mentions the mode 4A boundary, which varies as a
3052 // function of aircraft type. We consider that the mentioned
3053 // values are examples, and that we should use the mode 4A upper
3056 if (! mk_data(terrain_clearance).ncd
3057 && ! mk_ainput(computed_airspeed).ncd
3058 && mk_data(terrain_clearance).get() > mk->mode4_handler.get_upper_agl(mk->mode4_handler.conf.modes->ac))
3063 if (! mk_data(radio_altitude).ncd
3064 && mk_data(radio_altitude).get() < mk->mode4_handler.conf.modes->b->min_agl1
3065 && mk->io_handler.flaps_down()
3066 && mk_dinput(landing_gear))
3072 MK_VIII::StateHandler::enter_takeoff ()
3075 mk->io_handler.enter_takeoff();
3076 mk->mode2_handler.enter_takeoff();
3077 mk->mode3_handler.enter_takeoff();
3078 mk->mode6_handler.enter_takeoff();
3082 MK_VIII::StateHandler::leave_takeoff ()
3085 mk->mode6_handler.leave_takeoff();
3089 MK_VIII::StateHandler::post_reposition ()
3091 // Synchronize the state with the current situation.
3094 (! mk_ainput(computed_airspeed).ncd && mk_ainput(computed_airspeed).get() > 60
3095 && ! mk_data(radio_altitude).ncd && mk_data(radio_altitude).get() > 30);
3097 bool _takeoff = _ground;
3099 if (ground && ! _ground)
3101 else if (! ground && _ground)
3104 if (takeoff && ! _takeoff)
3106 else if (! takeoff && _takeoff)
3111 MK_VIII::StateHandler::update ()
3113 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
3120 ///////////////////////////////////////////////////////////////////////////////
3121 // Mode1Handler ///////////////////////////////////////////////////////////////
3122 ///////////////////////////////////////////////////////////////////////////////
3125 MK_VIII::Mode1Handler::get_pull_up_bias ()
3127 // [PILOT] page 54: "Steep Approach has priority over Flap Override
3128 // if selected simultaneously."
3130 if (mk->io_handler.steep_approach())
3132 else if (conf.envelopes->flap_override_bias && mk->io_handler.flap_override())
3139 MK_VIII::Mode1Handler::is_pull_up ()
3141 if (! mk->io_handler.gpws_inhibit()
3142 && ! mk->state_handler.ground // [1]
3143 && ! mk_data(radio_altitude).ncd
3144 && ! mk_data(barometric_altitude_rate).ncd
3145 && mk_data(radio_altitude).get() > conf.envelopes->min_agl)
3147 if (mk_data(radio_altitude).get() < conf.envelopes->pull_up_max_agl1)
3149 if (mk_data(radio_altitude).get() < conf.envelopes->pull_up_min_agl1(mk_data(barometric_altitude_rate).get() + get_pull_up_bias()))
3152 else if (mk_data(radio_altitude).get() < conf.envelopes->pull_up_max_agl2)
3154 if (mk_data(radio_altitude).get() < conf.envelopes->pull_up_min_agl2(mk_data(barometric_altitude_rate).get() + get_pull_up_bias()))
3163 MK_VIII::Mode1Handler::update_pull_up ()
3167 if (! mk_test_alert(MODE1_PULL_UP))
3169 // [SPEC] 6.2.1: at least one sink rate must be issued
3170 // before switching to pull up; if no sink rate has
3171 // occurred, a 0.2 second delay is used.
3172 if (mk->voice_player.voice == mk_voice(sink_rate_pause_sink_rate)
3173 || pull_up_timer.start_or_elapsed() >= 0.2)
3174 mk_set_alerts(mk_alert(MODE1_PULL_UP));
3179 pull_up_timer.stop();
3180 mk_unset_alerts(mk_alert(MODE1_PULL_UP));
3185 MK_VIII::Mode1Handler::get_sink_rate_bias ()
3187 // [PILOT] page 54: "Steep Approach has priority over Flap Override
3188 // if selected simultaneously."
3190 if (mk->io_handler.steep_approach())
3192 else if (conf.envelopes->flap_override_bias && mk->io_handler.flap_override())
3194 else if (! mk_data(glideslope_deviation_dots).ncd)
3198 if (mk_data(glideslope_deviation_dots).get() <= -2)
3200 else if (mk_data(glideslope_deviation_dots).get() < 0)
3201 bias = -150 * mk_data(glideslope_deviation_dots).get();
3203 if (mk_data(radio_altitude).get() < 100)
3204 bias *= 0.01 * mk_data(radio_altitude).get();
3213 MK_VIII::Mode1Handler::is_sink_rate ()
3215 return ! mk->io_handler.gpws_inhibit()
3216 && ! mk->state_handler.ground // [1]
3217 && ! mk_data(radio_altitude).ncd
3218 && ! mk_data(barometric_altitude_rate).ncd
3219 && mk_data(radio_altitude).get() > conf.envelopes->min_agl
3220 && mk_data(radio_altitude).get() < 2450
3221 && mk_data(radio_altitude).get() < -572 - 0.6035 * (mk_data(barometric_altitude_rate).get() + get_sink_rate_bias());
3225 MK_VIII::Mode1Handler::get_sink_rate_tti ()
3227 return mk_data(radio_altitude).get() / fabs(mk_data(barometric_altitude_rate).get());
3231 MK_VIII::Mode1Handler::update_sink_rate ()
3235 if (mk_test_alert(MODE1_SINK_RATE))
3237 double tti = get_sink_rate_tti();
3238 if (tti <= sink_rate_tti * 0.8)
3240 // ~20% degradation, warn again and store the new reference tti
3241 sink_rate_tti = tti;
3242 mk_repeat_alert(mk_alert(MODE1_SINK_RATE));
3247 if (sink_rate_timer.start_or_elapsed() >= 0.8)
3249 mk_set_alerts(mk_alert(MODE1_SINK_RATE));
3250 sink_rate_tti = get_sink_rate_tti();
3256 sink_rate_timer.stop();
3257 mk_unset_alerts(mk_alert(MODE1_SINK_RATE));
3262 MK_VIII::Mode1Handler::update ()
3264 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
3271 ///////////////////////////////////////////////////////////////////////////////
3272 // Mode2Handler ///////////////////////////////////////////////////////////////
3273 ///////////////////////////////////////////////////////////////////////////////
3276 MK_VIII::Mode2Handler::ClosureRateFilter::limit_radio_altitude_rate (double r)
3278 // Limit radio altitude rate according to aircraft configuration,
3279 // allowing maximum sensitivity during cruise while providing
3280 // progressively less sensitivity during the landing phases of
3283 if (! mk_data(glideslope_deviation_dots).ncd && fabs(mk_data(glideslope_deviation_dots).get()) <= 2)
3284 { // gs deviation <= +- 2 dots
3285 if (mk_dinput(landing_gear) && mk->io_handler.flaps_down())
3286 SG_CLAMP_RANGE(r, -1000.0, 3000.0);
3287 else if (mk_dinput(landing_gear) || mk->io_handler.flaps_down())
3288 SG_CLAMP_RANGE(r, 0.0, 4000.0);
3290 SG_CLAMP_RANGE(r, 1000.0, 5000.0);
3293 { // no ILS, or gs deviation > +- 2 dots
3294 if (mk_dinput(landing_gear) && mk->io_handler.flaps_down())
3295 SG_CLAMP_RANGE(r, 0.0, 4000.0);
3296 else if (mk_dinput(landing_gear) || mk->io_handler.flaps_down())
3297 SG_CLAMP_RANGE(r, 1000.0, 5000.0);
3305 MK_VIII::Mode2Handler::ClosureRateFilter::init ()
3308 last_ra.set(&mk_data(radio_altitude));
3309 last_ba.set(&mk_ainput(uncorrected_barometric_altitude));
3316 MK_VIII::Mode2Handler::ClosureRateFilter::update ()
3318 double elapsed = timer.start_or_elapsed();
3322 if (! mk_data(radio_altitude).ncd && ! mk_ainput(uncorrected_barometric_altitude).ncd)
3324 if (! last_ra.ncd && ! last_ba.ncd)
3326 // compute radio and barometric altitude rates (positive value = descent)
3327 double ra_rate = -(mk_data(radio_altitude).get() - last_ra.get()) / elapsed * 60;
3328 double ba_rate = -(mk_ainput(uncorrected_barometric_altitude).get() - last_ba.get()) / elapsed * 60;
3330 // limit radio altitude rate according to aircraft configuration
3331 ra_rate = limit_radio_altitude_rate(ra_rate);
3333 // apply a low-pass filter to the radio altitude rate
3334 ra_rate = ra_filter.filter(ra_rate);
3335 // apply a high-pass filter to the barometric altitude rate
3336 ba_rate = ba_filter.filter(ba_rate);
3338 // combine both rates to obtain a closure rate
3339 output.set(ra_rate + ba_rate);
3352 last_ra.set(&mk_data(radio_altitude));
3353 last_ba.set(&mk_ainput(uncorrected_barometric_altitude));
3357 MK_VIII::Mode2Handler::b_conditions ()
3359 return mk->io_handler.flaps_down()
3360 || (! mk_data(glideslope_deviation_dots).ncd && fabs(mk_data(glideslope_deviation_dots).get()) < 2)
3361 || takeoff_timer.running;
3365 MK_VIII::Mode2Handler::is_a ()
3367 if (! mk->io_handler.gpws_inhibit()
3368 && ! mk->state_handler.ground // [1]
3369 && ! mk_data(radio_altitude).ncd
3370 && ! mk_ainput(computed_airspeed).ncd
3371 && ! closure_rate_filter.output.ncd
3372 && ! b_conditions())
3374 if (mk_data(radio_altitude).get() < 1220)
3376 if (mk_data(radio_altitude).get() < -1579 + 0.7895 * closure_rate_filter.output.get())
3383 if (mk_ainput(computed_airspeed).get() <= conf->airspeed1)
3385 else if (mk_ainput(computed_airspeed).get() >= conf->airspeed2)
3388 upper_limit = 1650 + 8.9 * (mk_ainput(computed_airspeed).get() - conf->airspeed1);
3390 if (mk_data(radio_altitude).get() < upper_limit)
3392 if (mk_data(radio_altitude).get() < 522 + 0.1968 * closure_rate_filter.output.get())
3402 MK_VIII::Mode2Handler::is_b ()
3404 if (! mk->io_handler.gpws_inhibit()
3405 && ! mk->state_handler.ground // [1]
3406 && ! mk_data(radio_altitude).ncd
3407 && ! mk_data(barometric_altitude_rate).ncd
3408 && ! closure_rate_filter.output.ncd
3410 && mk_data(radio_altitude).get() < 789)
3414 if (mk->io_handler.flaps_down())
3416 if (mk_data(barometric_altitude_rate).get() > -400)
3418 else if (mk_data(barometric_altitude_rate).get() < -1000)
3421 lower_limit = -66.777 - 0.667 * mk_data(barometric_altitude_rate).get();
3426 if (mk_data(radio_altitude).get() > lower_limit)
3428 if (mk_data(radio_altitude).get() < -1579 + 0.7895 * closure_rate_filter.output.get())
3437 MK_VIII::Mode2Handler::check_pull_up (unsigned int preface_alert,
3440 if (pull_up_timer.running)
3442 if (pull_up_timer.elapsed() >= 1)
3444 mk_unset_alerts(preface_alert);
3445 mk_set_alerts(alert);
3450 if (! mk->voice_player.voice)
3451 pull_up_timer.start();
3456 MK_VIII::Mode2Handler::update_a ()
3460 if (mk_test_alert(MODE2A_PREFACE))
3461 check_pull_up(mk_alert(MODE2A_PREFACE), mk_alert(MODE2A));
3462 else if (! mk_test_alert(MODE2A))
3464 mk_unset_alerts(mk_alert(MODE2A_ALTITUDE_GAIN) | mk_alert(MODE2A_ALTITUDE_GAIN_TERRAIN_CLOSING));
3465 mk_set_alerts(mk_alert(MODE2A_PREFACE));
3466 a_start_time = globals->get_sim_time_sec();
3467 pull_up_timer.stop();
3472 if (mk_test_alert(MODE2A_ALTITUDE_GAIN))
3474 if (mk->io_handler.gpws_inhibit()
3475 || mk->state_handler.ground // [1]
3476 || a_altitude_gain_timer.elapsed() >= 45
3477 || mk_data(radio_altitude).ncd
3478 || mk_ainput(uncorrected_barometric_altitude).ncd
3479 || mk_ainput(uncorrected_barometric_altitude).get() - a_altitude_gain_alt >= 300
3480 // [PILOT] page 12: "the visual alert will remain on
3481 // until [...] landing flaps or the flap override switch
3483 || mk->io_handler.flaps_down())
3485 // exit altitude gain mode
3486 a_altitude_gain_timer.stop();
3487 mk_unset_alerts(mk_alert(MODE2A_ALTITUDE_GAIN) | mk_alert(MODE2A_ALTITUDE_GAIN_TERRAIN_CLOSING));
3491 // [SPEC] 6.2.2.2: "If the terrain starts to fall away
3492 // during this altitude gain time, the voice will shut
3494 if (closure_rate_filter.output.get() < 0)
3495 mk_unset_alerts(mk_alert(MODE2A_ALTITUDE_GAIN_TERRAIN_CLOSING));
3498 else if (mk_test_alerts(mk_alert(MODE2A_PREFACE) | mk_alert(MODE2A)))
3500 mk_unset_alerts(mk_alert(MODE2A_PREFACE) | mk_alert(MODE2A));
3502 if (! mk->io_handler.gpws_inhibit()
3503 && ! mk->state_handler.ground // [1]
3504 && globals->get_sim_time_sec() - a_start_time > 3
3505 && ! mk->io_handler.flaps_down()
3506 && ! mk_data(radio_altitude).ncd
3507 && ! mk_ainput(uncorrected_barometric_altitude).ncd)
3509 // [SPEC] 6.2.2.2: mode 2A envelope violated for more
3510 // than 3 seconds, enable altitude gain feature
3512 a_altitude_gain_timer.start();
3513 a_altitude_gain_alt = mk_ainput(uncorrected_barometric_altitude).get();
3515 unsigned int alerts = mk_alert(MODE2A_ALTITUDE_GAIN);
3516 if (closure_rate_filter.output.get() > 0)
3517 alerts |= mk_alert(MODE2A_ALTITUDE_GAIN_TERRAIN_CLOSING);
3519 mk_set_alerts(alerts);
3526 MK_VIII::Mode2Handler::update_b ()
3530 // handle normal mode
3532 if (b && (! mk_dinput(landing_gear) || ! mk->io_handler.flaps_down()))
3534 if (mk_test_alert(MODE2B_PREFACE))
3535 check_pull_up(mk_alert(MODE2B_PREFACE), mk_alert(MODE2B));
3536 else if (! mk_test_alert(MODE2B))
3538 mk_set_alerts(mk_alert(MODE2B_PREFACE));
3539 pull_up_timer.stop();
3543 mk_unset_alerts(mk_alert(MODE2B_PREFACE) | mk_alert(MODE2B));
3545 // handle landing mode ([SPEC] 6.2.2.3: "If both landing gear and
3546 // flaps are in the landing configuration, then the message will be
3549 if (b && mk_dinput(landing_gear) && mk->io_handler.flaps_down())
3550 mk_set_alerts(mk_alert(MODE2B_LANDING_MODE));
3552 mk_unset_alerts(mk_alert(MODE2B_LANDING_MODE));
3556 MK_VIII::Mode2Handler::boot ()
3558 closure_rate_filter.init();
3562 MK_VIII::Mode2Handler::power_off ()
3564 // [SPEC] 6.0.4: "This latching function is not power saved and a
3565 // system reset will force it false."
3566 takeoff_timer.stop();
3570 MK_VIII::Mode2Handler::leave_ground ()
3572 // takeoff, reset timer
3573 takeoff_timer.start();
3577 MK_VIII::Mode2Handler::enter_takeoff ()
3579 // reset timer, in case it's a go-around
3580 takeoff_timer.start();
3584 MK_VIII::Mode2Handler::update ()
3586 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
3589 closure_rate_filter.update();
3591 if (takeoff_timer.running && takeoff_timer.elapsed() >= 60)
3592 takeoff_timer.stop();
3598 ///////////////////////////////////////////////////////////////////////////////
3599 // Mode3Handler ///////////////////////////////////////////////////////////////
3600 ///////////////////////////////////////////////////////////////////////////////
3603 MK_VIII::Mode3Handler::max_alt_loss (double _bias)
3605 return conf->max_alt_loss(mk->io_handler.flap_override(), mk_data(radio_altitude).get()) + mk_data(radio_altitude).get() * _bias;
3609 MK_VIII::Mode3Handler::get_bias (double initial_bias, double alt_loss)
3611 // do not repeat altitude-loss alerts below 30ft agl
3612 if (mk_data(radio_altitude).get() > 30)
3614 if (initial_bias < 0.0) // sanity check
3616 // mk-viii spec: repeat alerts whenever losing 20% of initial altitude
3617 while ((alt_loss > max_alt_loss(initial_bias))&&
3618 (initial_bias < 1.0))
3619 initial_bias += 0.2;
3622 return initial_bias;
3626 MK_VIII::Mode3Handler::is (double *alt_loss)
3628 if (has_descent_alt)
3630 int max_agl = conf->max_agl(mk->io_handler.flap_override());
3632 if (mk_ainput(uncorrected_barometric_altitude).ncd
3633 || mk_ainput(uncorrected_barometric_altitude).get() > descent_alt
3634 || mk_data(radio_altitude).ncd
3635 || mk_data(radio_altitude).get() > max_agl)
3638 has_descent_alt = false;
3642 // gear/flaps: [SPEC] 1.3.1.3
3643 if (! mk->io_handler.gpws_inhibit()
3644 && ! mk->state_handler.ground // [1]
3645 && (! mk_dinput(landing_gear) || ! mk->io_handler.flaps_down())
3646 && ! mk_data(barometric_altitude_rate).ncd
3647 && ! mk_ainput(uncorrected_barometric_altitude).ncd
3648 && ! mk_data(radio_altitude).ncd
3649 && mk_data(barometric_altitude_rate).get() < 0)
3651 double _alt_loss = descent_alt - mk_ainput(uncorrected_barometric_altitude).get();
3652 if (armed || (mk_data(radio_altitude).get() > conf->min_agl
3653 && mk_data(radio_altitude).get() < max_agl
3654 && _alt_loss > max_alt_loss(0)))
3656 *alt_loss = _alt_loss;
3664 if (! mk_data(barometric_altitude_rate).ncd
3665 && ! mk_ainput(uncorrected_barometric_altitude).ncd
3666 && mk_data(barometric_altitude_rate).get() < 0)
3668 has_descent_alt = true;
3669 descent_alt = mk_ainput(uncorrected_barometric_altitude).get();
3677 MK_VIII::Mode3Handler::enter_takeoff ()
3680 has_descent_alt = false;
3684 MK_VIII::Mode3Handler::update ()
3686 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
3689 if (mk->state_handler.takeoff)
3693 if (! mk->state_handler.ground /* [1] */ && is(&alt_loss))
3695 if (mk_test_alert(MODE3))
3697 double new_bias = get_bias(bias, alt_loss);
3698 if (new_bias > bias)
3701 mk_repeat_alert(mk_alert(MODE3));
3707 bias = get_bias(0.2, alt_loss);
3708 mk_set_alerts(mk_alert(MODE3));
3715 mk_unset_alerts(mk_alert(MODE3));
3718 ///////////////////////////////////////////////////////////////////////////////
3719 // Mode4Handler ///////////////////////////////////////////////////////////////
3720 ///////////////////////////////////////////////////////////////////////////////
3722 // FIXME: for turbofans, the upper limit should be reduced from 1000
3723 // to 800 ft if a sudden change in radio altitude is detected, in
3724 // order to reduce nuisance alerts caused by overflying another
3725 // aircraft (see [PILOT] page 16).
3728 MK_VIII::Mode4Handler::get_upper_agl (const EnvelopesConfiguration *c)
3730 if (mk_ainput(computed_airspeed).get() >= c->airspeed2)
3732 else if (mk_ainput(computed_airspeed).get() >= c->airspeed1)
3733 return c->min_agl2(mk_ainput(computed_airspeed).get());
3738 const MK_VIII::Mode4Handler::EnvelopesConfiguration *
3739 MK_VIII::Mode4Handler::get_ab_envelope ()
3741 // [SPEC] 6.2.4.1: "The Mode 4B envelope can also be selected by
3742 // setting flaps to landing configuration or by selecting flap
3744 return mk_dinput(landing_gear) || mk->io_handler.flaps_down()
3750 MK_VIII::Mode4Handler::get_bias (double initial_bias, double min_agl)
3752 // do not repeat terrain/gear/flap alerts below 30ft agl
3753 if (mk_data(radio_altitude).get() > 30.0)
3755 if (initial_bias < 0.0) // sanity check
3757 while ((mk_data(radio_altitude).get() < min_agl - min_agl * initial_bias)&&
3758 (initial_bias < 1.0))
3759 initial_bias += 0.2;
3762 return initial_bias;
3766 MK_VIII::Mode4Handler::handle_alert (unsigned int alert,
3770 if (mk_test_alerts(alert))
3772 double new_bias = get_bias(*bias, min_agl);
3773 if (new_bias > *bias)
3776 mk_repeat_alert(alert);
3781 *bias = get_bias(0.2, min_agl);
3782 mk_set_alerts(alert);
3787 MK_VIII::Mode4Handler::update_ab ()
3789 if (! mk->io_handler.gpws_inhibit()
3790 && ! mk->state_handler.ground
3791 && ! mk->state_handler.takeoff
3792 && ! mk_data(radio_altitude).ncd
3793 && ! mk_ainput(computed_airspeed).ncd
3794 && mk_data(radio_altitude).get() > 30)
3796 const EnvelopesConfiguration *c = get_ab_envelope();
3797 if (mk_ainput(computed_airspeed).get() < c->airspeed1)
3799 if (mk_dinput(landing_gear))
3801 if (! mk->io_handler.flaps_down() && mk_data(radio_altitude).get() < c->min_agl1)
3803 handle_alert(mk_alert(MODE4_TOO_LOW_FLAPS), c->min_agl1, &ab_bias);
3809 if (mk_data(radio_altitude).get() < c->min_agl1)
3811 handle_alert(mk_alert(MODE4_TOO_LOW_GEAR), c->min_agl1, &ab_bias);
3818 mk_unset_alerts(mk_alert(MODE4_TOO_LOW_FLAPS) | mk_alert(MODE4_TOO_LOW_GEAR));
3823 MK_VIII::Mode4Handler::update_ab_expanded ()
3825 if (! mk->io_handler.gpws_inhibit()
3826 && ! mk->state_handler.ground
3827 && ! mk->state_handler.takeoff
3828 && ! mk_data(radio_altitude).ncd
3829 && ! mk_ainput(computed_airspeed).ncd
3830 && mk_data(radio_altitude).get() > 30)
3832 const EnvelopesConfiguration *c = get_ab_envelope();
3833 if (mk_ainput(computed_airspeed).get() >= c->airspeed1)
3835 double min_agl = get_upper_agl(c);
3836 if (mk_data(radio_altitude).get() < min_agl)
3838 handle_alert(mk_alert(MODE4AB_TOO_LOW_TERRAIN), min_agl, &ab_expanded_bias);
3844 mk_unset_alerts(mk_alert(MODE4AB_TOO_LOW_TERRAIN));
3845 ab_expanded_bias=0.0;
3849 MK_VIII::Mode4Handler::update_c ()
3851 if (! mk->io_handler.gpws_inhibit()
3852 && ! mk->state_handler.ground // [1]
3853 && mk->state_handler.takeoff
3854 && ! mk_data(radio_altitude).ncd
3855 && ! mk_data(terrain_clearance).ncd
3856 && mk_data(radio_altitude).get() > 30
3857 && (! mk_dinput(landing_gear) || ! mk->io_handler.flaps_down())
3858 && mk_data(radio_altitude).get() < get_upper_agl(conf.modes->ac)
3859 && mk_data(radio_altitude).get() < mk_data(terrain_clearance).get())
3860 handle_alert(mk_alert(MODE4C_TOO_LOW_TERRAIN), mk_data(terrain_clearance).get(), &c_bias);
3863 mk_unset_alerts(mk_alert(MODE4C_TOO_LOW_TERRAIN));
3869 MK_VIII::Mode4Handler::update ()
3871 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
3875 update_ab_expanded();
3879 ///////////////////////////////////////////////////////////////////////////////
3880 // Mode5Handler ///////////////////////////////////////////////////////////////
3881 ///////////////////////////////////////////////////////////////////////////////
3884 MK_VIII::Mode5Handler::is_hard ()
3886 if (mk_data(radio_altitude).get() > 30
3887 && mk_data(radio_altitude).get() < 300
3888 && mk_data(glideslope_deviation_dots).get() > 2)
3890 if (mk_data(radio_altitude).get() < 150)
3892 if (mk_data(radio_altitude).get() > 293 - 71.43 * mk_data(glideslope_deviation_dots).get())
3903 MK_VIII::Mode5Handler::is_soft (double bias)
3905 // do not repeat glide-slope alerts below 30ft agl
3906 if (mk_data(radio_altitude).get() > 30)
3908 double bias_dots = 1.3 * bias;
3909 if (mk_data(glideslope_deviation_dots).get() > 1.3 + bias_dots)
3911 if (mk_data(radio_altitude).get() < 150)
3913 if (mk_data(radio_altitude).get() > 243 - 71.43 * (mk_data(glideslope_deviation_dots).get() - bias_dots))
3920 if (mk_data(barometric_altitude_rate).ncd)
3924 if (mk_data(barometric_altitude_rate).get() >= 0)
3926 else if (mk_data(barometric_altitude_rate).get() < -500)
3929 upper_limit = -mk_data(barometric_altitude_rate).get() + 500;
3932 if (mk_data(radio_altitude).get() < upper_limit)
3942 MK_VIII::Mode5Handler::get_soft_bias (double initial_bias)
3944 if (initial_bias < 0.0) // sanity check
3946 while ((is_soft(initial_bias))&&
3947 (initial_bias < 1.0))
3948 initial_bias += 0.2;
3950 return initial_bias;
3954 MK_VIII::Mode5Handler::update_hard (bool is)
3958 if (! mk_test_alert(MODE5_HARD))
3960 if (hard_timer.start_or_elapsed() >= 0.8)
3961 mk_set_alerts(mk_alert(MODE5_HARD));
3967 mk_unset_alerts(mk_alert(MODE5_HARD));
3972 MK_VIII::Mode5Handler::update_soft (bool is)
3976 if (! mk_test_alert(MODE5_SOFT))
3978 double new_bias = get_soft_bias(soft_bias);
3979 if (new_bias > soft_bias)
3981 soft_bias = new_bias;
3982 mk_repeat_alert(mk_alert(MODE5_SOFT));
3987 if (soft_timer.start_or_elapsed() >= 0.8)
3989 soft_bias = get_soft_bias(0.2);
3990 mk_set_alerts(mk_alert(MODE5_SOFT));
3997 mk_unset_alerts(mk_alert(MODE5_SOFT));
4002 MK_VIII::Mode5Handler::update ()
4004 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
4007 if (! mk->io_handler.gpws_inhibit()
4008 && ! mk->state_handler.ground // [1]
4009 && ! mk_dinput(glideslope_inhibit) // not on backcourse
4010 && ! mk_data(radio_altitude).ncd
4011 && ! mk_data(glideslope_deviation_dots).ncd
4012 && (! mk->io_handler.conf.localizer_enabled
4013 || mk_data(localizer_deviation_dots).ncd
4014 || mk_data(radio_altitude).get() < 500
4015 || fabs(mk_data(localizer_deviation_dots).get()) < 2)
4016 && (! mk->state_handler.takeoff || mk->io_handler.flaps_down())
4017 && mk_dinput(landing_gear)
4018 && ! mk_doutput(glideslope_cancel))
4020 update_hard(is_hard());
4021 update_soft(is_soft(0));
4030 ///////////////////////////////////////////////////////////////////////////////
4031 // Mode6Handler ///////////////////////////////////////////////////////////////
4032 ///////////////////////////////////////////////////////////////////////////////
4034 // keep sorted in descending order
4035 const int MK_VIII::Mode6Handler::altitude_callout_definitions[] =
4036 { 1000, 500, 400, 300, 200, 100, 50, 40, 30, 20, 10 };
4039 MK_VIII::Mode6Handler::reset_minimums ()
4041 minimums_issued = false;
4045 MK_VIII::Mode6Handler::reset_altitude_callouts ()
4047 for (unsigned i = 0; i < n_altitude_callouts; i++)
4048 altitude_callouts_issued[i] = false;
4052 MK_VIII::Mode6Handler::is_playing_altitude_callout ()
4054 for (unsigned i = 0; i < n_altitude_callouts; i++)
4055 if (mk->voice_player.voice == mk_altitude_voice(i)
4056 || mk->voice_player.next_voice == mk_altitude_voice(i))
4063 MK_VIII::Mode6Handler::is_near_minimums (double callout)
4067 if (! mk_data(decision_height).ncd)
4069 double diff = callout - mk_data(decision_height).get();
4071 if (mk_data(radio_altitude).get() >= 200)
4073 if (fabs(diff) <= 30)
4078 if (diff >= -3 && diff <= 6)
4087 MK_VIII::Mode6Handler::is_outside_band (double elevation, double callout)
4090 return elevation < callout - (elevation > 150 ? 20 : 10);
4094 MK_VIII::Mode6Handler::inhibit_smart_500 ()
4098 if (! mk_data(glideslope_deviation_dots).ncd
4099 && ! mk_dinput(glideslope_inhibit) // backcourse
4100 && ! mk_doutput(glideslope_cancel))
4102 if (mk->io_handler.conf.localizer_enabled
4103 && ! mk_data(localizer_deviation_dots).ncd)
4105 if (fabs(mk_data(localizer_deviation_dots).get()) <= 2)
4110 if (fabs(mk_data(glideslope_deviation_dots).get()) <= 2)
4119 MK_VIII::Mode6Handler::boot ()
4121 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
4124 last_decision_height = mk_dinput(decision_height);
4125 last_radio_altitude.set(&mk_data(radio_altitude));
4128 for (unsigned i = 0; i < n_altitude_callouts; i++)
4129 altitude_callouts_issued[i] = ! mk_data(radio_altitude).ncd
4130 && mk_data(radio_altitude).get() <= altitude_callout_definitions[i];
4132 // extrapolated from [SPEC] 6.4.2
4133 minimums_issued = mk_dinput(decision_height);
4135 if (conf.above_field_voice)
4138 get_altitude_above_field(&last_altitude_above_field);
4139 // extrapolated from [SPEC] 6.4.2
4140 above_field_issued = ! last_altitude_above_field.ncd
4141 && last_altitude_above_field.get() < 550;
4146 MK_VIII::Mode6Handler::power_off ()
4148 runway_timer.stop();
4152 MK_VIII::Mode6Handler::enter_takeoff ()
4154 reset_altitude_callouts(); // [SPEC] 6.4.2
4155 reset_minimums(); // omitted by [SPEC]; common sense
4159 MK_VIII::Mode6Handler::leave_takeoff ()
4161 reset_altitude_callouts(); // [SPEC] 6.0.2
4162 reset_minimums(); // [SPEC] 6.0.2
4166 MK_VIII::Mode6Handler::set_volume (float volume)
4168 mk_voice(minimums_minimums)->set_volume(volume);
4169 mk_voice(five_hundred_above)->set_volume(volume);
4170 for (unsigned i = 0; i < n_altitude_callouts; i++)
4171 mk_altitude_voice(i)->set_volume(volume);
4175 MK_VIII::Mode6Handler::altitude_callouts_enabled ()
4177 if (conf.minimums_enabled || conf.smart_500_enabled || conf.above_field_voice)
4180 for (unsigned i = 0; i < n_altitude_callouts; i++)
4181 if (conf.altitude_callouts_enabled[i])
4188 MK_VIII::Mode6Handler::update_minimums ()
4190 if (! mk->io_handler.gpws_inhibit()
4191 && (mk->voice_player.voice == mk_voice(minimums_minimums)
4192 || mk->voice_player.next_voice == mk_voice(minimums_minimums)))
4195 if (! mk->io_handler.gpws_inhibit()
4196 && ! mk->state_handler.ground // [1]
4197 && conf.minimums_enabled
4198 && ! minimums_issued
4199 && mk_dinput(landing_gear)
4200 && mk_dinput(decision_height)
4201 && ! last_decision_height)
4203 minimums_issued = true;
4205 // If an altitude callout is playing, stop it now so that the
4206 // minimums callout can be played (note that proper connection
4207 // and synchronization of the radio-altitude ARINC 529 input,
4208 // decision-height discrete and decision-height ARINC 529 input
4209 // will prevent an altitude callout from being played near the
4210 // decision height).
4212 if (is_playing_altitude_callout())
4213 mk->voice_player.stop(VoicePlayer::STOP_NOW);
4215 mk_set_alerts(mk_alert(MODE6_MINIMUMS));
4218 mk_unset_alerts(mk_alert(MODE6_MINIMUMS));
4221 last_decision_height = mk_dinput(decision_height);
4225 MK_VIII::Mode6Handler::update_altitude_callouts ()
4227 if (! mk->io_handler.gpws_inhibit() && is_playing_altitude_callout())
4230 if (! mk->io_handler.gpws_inhibit()
4231 && ! mk->state_handler.ground // [1]
4232 && ! mk_data(radio_altitude).ncd)
4233 for (unsigned i = 0; i < n_altitude_callouts && mk_data(radio_altitude).get() <= altitude_callout_definitions[i]; i++)
4234 if ((conf.altitude_callouts_enabled[i]
4235 || (altitude_callout_definitions[i] == 500
4236 && conf.smart_500_enabled))
4237 && ! altitude_callouts_issued[i]
4238 && (last_radio_altitude.ncd
4239 || last_radio_altitude.get() > altitude_callout_definitions[i]))
4241 // lock out all callouts superior or equal to this one
4242 for (unsigned j = 0; j <= i; j++)
4243 altitude_callouts_issued[j] = true;
4245 altitude_callouts_issued[i] = true;
4246 if (! is_near_minimums(altitude_callout_definitions[i])
4247 && ! is_outside_band(mk_data(radio_altitude).get(), altitude_callout_definitions[i])
4248 && (! conf.smart_500_enabled
4249 || altitude_callout_definitions[i] != 500
4250 || ! inhibit_smart_500()))
4252 mk->alert_handler.set_altitude_callout_alert(mk_altitude_voice(i));
4257 mk_unset_alerts(mk_alert(MODE6_ALTITUDE_CALLOUT));
4260 last_radio_altitude.set(&mk_data(radio_altitude));
4264 MK_VIII::Mode6Handler::test_runway (const FGRunway *_runway)
4266 if (_runway->lengthFt() < mk->conf.runway_database)
4270 SGGeod::fromDeg(mk_data(gps_longitude).get(), mk_data(gps_latitude).get()));
4272 // get distance to threshold
4273 double distance, az1, az2;
4274 SGGeodesy::inverse(pos, _runway->threshold(), az1, az2, distance);
4275 return distance * SG_METER_TO_NM <= 5;
4279 MK_VIII::Mode6Handler::test_airport (const FGAirport *airport)
4281 for (unsigned int r=0; r<airport->numRunways(); ++r) {
4282 FGRunway* rwy(airport->getRunwayByIndex(r));
4284 if (test_runway(rwy)) return true;
4290 bool MK_VIII::Mode6Handler::AirportFilter::passAirport(FGAirport* a) const
4292 bool ok = self->test_airport(a);
4297 MK_VIII::Mode6Handler::update_runway ()
4300 if (mk_data(gps_latitude).ncd || mk_data(gps_longitude).ncd) {
4305 // Search for the closest runway threshold in range 5
4306 // nm. Passing 30nm to
4307 // get_closest_airport() provides enough margin for large
4308 // airports, which may have a runway located far away from the
4309 // airport's reference point.
4310 AirportFilter filter(this);
4311 FGPositionedRef apt = FGPositioned::findClosest(
4312 SGGeod::fromDeg(mk_data(gps_longitude).get(), mk_data(gps_latitude).get()),
4315 runway.elevation = apt->elevation();
4318 has_runway.set(apt != NULL);
4322 MK_VIII::Mode6Handler::get_altitude_above_field (Parameter<double> *parameter)
4324 if (! has_runway.ncd && has_runway.get() && ! mk_data(geometric_altitude).ncd)
4325 parameter->set(mk_data(geometric_altitude).get() - runway.elevation);
4331 MK_VIII::Mode6Handler::update_above_field_callout ()
4333 if (! conf.above_field_voice)
4336 // update_runway() has to iterate over the whole runway database
4337 // (which contains thousands of runways), so it would be unwise to
4338 // run it every frame. Run it every 3 seconds. Note that the first
4339 // iteration is run in boot().
4340 if (runway_timer.start_or_elapsed() >= 3)
4343 runway_timer.start();
4346 Parameter<double> altitude_above_field;
4347 get_altitude_above_field(&altitude_above_field);
4349 if (! mk->io_handler.gpws_inhibit()
4350 && (mk->voice_player.voice == conf.above_field_voice
4351 || mk->voice_player.next_voice == conf.above_field_voice))
4354 // handle reset term
4355 if (above_field_issued)
4357 if ((! has_runway.ncd && ! has_runway.get())
4358 || (! altitude_above_field.ncd && altitude_above_field.get() > 700))
4359 above_field_issued = false;
4362 if (! mk->io_handler.gpws_inhibit()
4363 && ! mk->state_handler.ground // [1]
4364 && ! above_field_issued
4365 && ! altitude_above_field.ncd
4366 && altitude_above_field.get() < 550
4367 && (last_altitude_above_field.ncd
4368 || last_altitude_above_field.get() >= 550))
4370 above_field_issued = true;
4372 if (! is_outside_band(altitude_above_field.get(), 500))
4374 mk->alert_handler.set_altitude_callout_alert(conf.above_field_voice);
4379 mk_unset_alerts(mk_alert(MODE6_ALTITUDE_CALLOUT));
4382 last_altitude_above_field.set(&altitude_above_field);
4386 MK_VIII::Mode6Handler::is_bank_angle (double abs_roll_angle, double bias)
4388 return conf.is_bank_angle(&mk_data(radio_altitude),
4389 abs_roll_angle - abs_roll_angle * bias,
4390 mk_dinput(autopilot_engaged));
4394 MK_VIII::Mode6Handler::is_high_bank_angle ()
4396 return mk_data(radio_altitude).ncd || mk_data(radio_altitude).get() >= 210;
4400 MK_VIII::Mode6Handler::get_bank_angle_alerts ()
4402 if (! mk->io_handler.gpws_inhibit()
4403 && ! mk->state_handler.ground // [1]
4404 && ! mk_data(roll_angle).ncd)
4406 double abs_roll_angle = fabs(mk_data(roll_angle).get());
4408 if (is_bank_angle(abs_roll_angle, 0.4))
4409 return is_high_bank_angle()
4410 ? (mk_alert(MODE6_HIGH_BANK_ANGLE_1) | mk_alert(MODE6_HIGH_BANK_ANGLE_2) | mk_alert(MODE6_HIGH_BANK_ANGLE_3))
4411 : (mk_alert(MODE6_LOW_BANK_ANGLE_1) | mk_alert(MODE6_LOW_BANK_ANGLE_2) | mk_alert(MODE6_LOW_BANK_ANGLE_3));
4412 else if (is_bank_angle(abs_roll_angle, 0.2))
4413 return is_high_bank_angle()
4414 ? (mk_alert(MODE6_HIGH_BANK_ANGLE_1) | mk_alert(MODE6_HIGH_BANK_ANGLE_2))
4415 : (mk_alert(MODE6_LOW_BANK_ANGLE_1) | mk_alert(MODE6_LOW_BANK_ANGLE_2));
4416 else if (is_bank_angle(abs_roll_angle, 0))
4417 return is_high_bank_angle()
4418 ? mk_alert(MODE6_HIGH_BANK_ANGLE_1)
4419 : mk_alert(MODE6_LOW_BANK_ANGLE_1);
4426 MK_VIII::Mode6Handler::update_bank_angle ()
4428 if (! conf.bank_angle_enabled)
4431 unsigned int alerts = get_bank_angle_alerts();
4433 // [SPEC] 6.4.4 specifies that the non-continuous alerts
4434 // (MODE6_*_BANK_ANGLE_1 and MODE6_*_BANK_ANGLE_2) are locked out
4435 // until the initial envelope is exited.
4437 if (! test_bits(alerts, mk_alert(MODE6_LOW_BANK_ANGLE_3)))
4438 mk_unset_alerts(mk_alert(MODE6_LOW_BANK_ANGLE_3));
4439 if (! test_bits(alerts, mk_alert(MODE6_HIGH_BANK_ANGLE_3)))
4440 mk_unset_alerts(mk_alert(MODE6_HIGH_BANK_ANGLE_3));
4443 mk_set_alerts(alerts);
4445 mk_unset_alerts(mk_alert(MODE6_LOW_BANK_ANGLE_1)
4446 | mk_alert(MODE6_HIGH_BANK_ANGLE_1)
4447 | mk_alert(MODE6_LOW_BANK_ANGLE_2)
4448 | mk_alert(MODE6_HIGH_BANK_ANGLE_2));
4452 MK_VIII::Mode6Handler::update ()
4454 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
4457 if (! mk->state_handler.takeoff
4458 && ! mk_data(radio_altitude).ncd
4459 && mk_data(radio_altitude).get() > 1000)
4461 reset_altitude_callouts(); // [SPEC] 6.4.2
4462 reset_minimums(); // common sense
4466 update_altitude_callouts();
4467 update_above_field_callout();
4468 update_bank_angle();
4471 ///////////////////////////////////////////////////////////////////////////////
4472 // TCFHandler /////////////////////////////////////////////////////////////////
4473 ///////////////////////////////////////////////////////////////////////////////
4475 // Gets the difference between the azimuth from @from_lat,@from_lon to
4476 // @to_lat,@to_lon, and @to_heading, in degrees.
4478 MK_VIII::TCFHandler::get_azimuth_difference (double from_lat,
4484 double az1, az2, distance;
4485 geo_inverse_wgs_84(0, from_lat, from_lon, to_lat, to_lon, &az1, &az2, &distance);
4486 return get_heading_difference(az1, to_heading);
4489 // Gets the difference between the azimuth from the current GPS
4490 // position to the center of @_runway, and the heading of @_runway, in
4493 MK_VIII::TCFHandler::get_azimuth_difference (const FGRunway *_runway)
4495 return get_azimuth_difference(mk_data(gps_latitude).get(),
4496 mk_data(gps_longitude).get(),
4497 _runway->latitude(),
4498 _runway->longitude(),
4499 _runway->headingDeg());
4502 // Selects the most likely intended destination runway of @airport,
4503 // and returns it in @_runway. For each runway, the difference between
4504 // the azimuth from the current GPS position to the center of the
4505 // runway and its heading is computed. The runway having the smallest
4508 // This selection algorithm is not specified in [SPEC], but
4509 // http://www.egpws.com/general_information/description/runway_select.htm
4510 // talks about automatic runway selection.
4512 MK_VIII::TCFHandler::select_runway (const FGAirport *airport)
4514 FGRunway* _runway = 0;
4515 double min_diff = 360;
4517 for (unsigned int r=0; r<airport->numRunways(); ++r) {
4518 FGRunway* rwy(airport->getRunwayByIndex(r));
4519 double diff = get_azimuth_difference(rwy);
4520 if (diff < min_diff)
4525 } // of airport runways iteration
4529 bool MK_VIII::TCFHandler::AirportFilter::passAirport(FGAirport* aApt) const
4531 return aApt->hasHardRunwayOfLengthFt(mk->conf.runway_database);
4535 MK_VIII::TCFHandler::update_runway ()
4538 if (mk_data(gps_latitude).ncd || mk_data(gps_longitude).ncd) {
4542 // Search for the intended destination runway of the closest
4543 // airport in range 15 nm. Passing 30nm to get_closest_airport()
4544 // provides enough margin for
4545 // large airports, which may have a runway located far away from
4546 // the airport's reference point.
4547 AirportFilter filter(mk);
4548 FGAirport* apt = FGAirport::findClosest(
4549 SGGeod::fromDeg(mk_data(gps_longitude).get(), mk_data(gps_latitude).get()),
4554 FGRunway* _runway = select_runway(apt);
4556 if (!_runway) return;
4560 runway.center.latitude = _runway->latitude();
4561 runway.center.longitude = _runway->longitude();
4563 runway.elevation = apt->elevation();
4565 double half_length_m = _runway->lengthM() * 0.5;
4566 runway.half_length = half_length_m * SG_METER_TO_NM;
4568 // b3 ________________ b0
4570 // h1>>> | e1<<<<<<<<e0 | <<<h0
4571 // |________________|
4574 // get heading to runway threshold (h0) and end (h1)
4575 runway.edges[0].heading = _runway->headingDeg();
4576 runway.edges[1].heading = get_reciprocal_heading(_runway->headingDeg());
4580 // get position of runway threshold (e0)
4581 geo_direct_wgs_84(0,
4582 runway.center.latitude,
4583 runway.center.longitude,
4584 runway.edges[1].heading,
4586 &runway.edges[0].position.latitude,
4587 &runway.edges[0].position.longitude,
4590 // get position of runway end (e1)
4591 geo_direct_wgs_84(0,
4592 runway.center.latitude,
4593 runway.center.longitude,
4594 runway.edges[0].heading,
4596 &runway.edges[1].position.latitude,
4597 &runway.edges[1].position.longitude,
4600 double half_width_m = _runway->widthM() * 0.5;
4602 // get position of threshold bias area edges (b0 and b1)
4603 get_bias_area_edges(&runway.edges[0].position,
4604 runway.edges[1].heading,
4606 &runway.bias_area[0],
4607 &runway.bias_area[1]);
4609 // get position of end bias area edges (b2 and b3)
4610 get_bias_area_edges(&runway.edges[1].position,
4611 runway.edges[0].heading,
4613 &runway.bias_area[2],
4614 &runway.bias_area[3]);
4618 MK_VIII::TCFHandler::get_bias_area_edges (Position *edge,
4620 double half_width_m,
4621 Position *bias_edge1,
4622 Position *bias_edge2)
4624 double half_bias_width_m = k * SG_NM_TO_METER + half_width_m;
4625 double tmp_latitude = 0.0, tmp_longitude = 0.0, az = 0.0;
4627 geo_direct_wgs_84(0,
4635 geo_direct_wgs_84(0,
4638 heading_substract(reciprocal, 90),
4640 &bias_edge1->latitude,
4641 &bias_edge1->longitude,
4643 geo_direct_wgs_84(0,
4646 heading_add(reciprocal, 90),
4648 &bias_edge2->latitude,
4649 &bias_edge2->longitude,
4653 // Returns true if the current GPS position is inside the edge
4654 // triangle of @edge. The edge triangle, which is specified and
4655 // represented in [SPEC] 6.3.1, is defined as an isocele right
4656 // triangle of infinite height, whose right angle is located at the
4657 // position of @edge, and whose height is parallel to the heading of
4660 MK_VIII::TCFHandler::is_inside_edge_triangle (RunwayEdge *edge)
4662 return get_azimuth_difference(mk_data(gps_latitude).get(),
4663 mk_data(gps_longitude).get(),
4664 edge->position.latitude,
4665 edge->position.longitude,
4666 edge->heading) <= 45;
4669 // Returns true if the current GPS position is inside the bias area of
4670 // the currently selected runway.
4672 MK_VIII::TCFHandler::is_inside_bias_area ()
4675 double angles_sum = 0;
4677 for (int i = 0; i < 4; i++)
4679 double az2, distance;
4680 geo_inverse_wgs_84(0,
4681 mk_data(gps_latitude).get(),
4682 mk_data(gps_longitude).get(),
4683 runway.bias_area[i].latitude,
4684 runway.bias_area[i].longitude,
4685 &az1[i], &az2, &distance);
4688 for (int i = 0; i < 4; i++)
4690 double angle = az1[i == 3 ? 0 : i + 1] - az1[i];
4694 angles_sum += angle;
4697 return angles_sum > 180;
4701 MK_VIII::TCFHandler::is_tcf ()
4703 if (mk_data(radio_altitude).get() > 10)
4707 double distance, az1, az2;
4709 geo_inverse_wgs_84(0,
4710 mk_data(gps_latitude).get(),
4711 mk_data(gps_longitude).get(),
4712 runway.center.latitude,
4713 runway.center.longitude,
4714 &az1, &az2, &distance);
4716 distance *= SG_METER_TO_NM;
4718 // distance to the inner envelope edge
4719 double edge_distance = distance - runway.half_length - k;
4721 if (edge_distance >= 15)
4723 if (mk_data(radio_altitude).get() < 700)
4726 else if (edge_distance >= 12)
4728 if (mk_data(radio_altitude).get() < 100 * edge_distance - 800)
4731 else if (edge_distance >= 4)
4733 if (mk_data(radio_altitude).get() < 400)
4736 else if (edge_distance >= 2.45)
4738 if (mk_data(radio_altitude).get() < 66.666667 * edge_distance + 133.33333)
4743 if (is_inside_edge_triangle(&runway.edges[0]) || is_inside_edge_triangle(&runway.edges[1]))
4745 if (edge_distance >= 1)
4747 if (mk_data(radio_altitude).get() < 66.666667 * edge_distance + 133.33333)
4750 else if (edge_distance >= 0.05)
4752 if (mk_data(radio_altitude).get() < 200 * edge_distance)
4758 if (! is_inside_bias_area())
4760 if (mk_data(radio_altitude).get() < 245)
4768 if (mk_data(radio_altitude).get() < 700)
4777 MK_VIII::TCFHandler::is_rfcf ()
4781 double distance, az1, az2;
4782 geo_inverse_wgs_84(0,
4783 mk_data(gps_latitude).get(),
4784 mk_data(gps_longitude).get(),
4785 runway.center.latitude,
4786 runway.center.longitude,
4787 &az1, &az2, &distance);
4789 double krf = k + mk_data(gps_vertical_figure_of_merit).get() / 200;
4790 distance = distance * SG_METER_TO_NM - runway.half_length - krf;
4794 double altitude_above_field = mk_data(geometric_altitude).get() - runway.elevation;
4796 if (distance >= 1.5)
4798 if (altitude_above_field < 300)
4801 else if (distance >= 0)
4803 if (altitude_above_field < 200 * distance)
4813 MK_VIII::TCFHandler::update ()
4815 if (mk->configuration_module.state != ConfigurationModule::STATE_OK || ! conf.enabled)
4818 // update_runway() has to iterate over the whole runway database
4819 // (which contains thousands of runways), so it would be unwise to
4820 // run it every frame. Run it every 3 seconds.
4821 if (! runway_timer.running || runway_timer.elapsed() >= 3)
4824 runway_timer.start();
4827 if (! mk_dinput(ta_tcf_inhibit)
4828 && ! mk->state_handler.ground // [1]
4829 && ! mk_data(gps_latitude).ncd
4830 && ! mk_data(gps_longitude).ncd
4831 && ! mk_data(radio_altitude).ncd
4832 && ! mk_data(geometric_altitude).ncd
4833 && ! mk_data(gps_vertical_figure_of_merit).ncd)
4838 _reference = mk_data(radio_altitude).get_pointer();
4840 _reference = mk_data(geometric_altitude).get_pointer();
4846 if (mk_test_alert(TCF_TOO_LOW_TERRAIN))
4848 double new_bias = bias;
4849 // do not repeat terrain alerts below 30ft agl
4850 if (mk_data(radio_altitude).get() > 30)
4852 if (new_bias < 0.0) // sanity check
4854 while ((*reference < initial_value - initial_value * new_bias)&&
4859 if (new_bias > bias)
4862 mk_repeat_alert(mk_alert(TCF_TOO_LOW_TERRAIN));
4868 reference = _reference;
4869 initial_value = *reference;
4870 mk_set_alerts(mk_alert(TCF_TOO_LOW_TERRAIN));
4877 mk_unset_alerts(mk_alert(TCF_TOO_LOW_TERRAIN));
4880 ///////////////////////////////////////////////////////////////////////////////
4881 // MK_VIII ////////////////////////////////////////////////////////////////////
4882 ///////////////////////////////////////////////////////////////////////////////
4884 MK_VIII::MK_VIII (SGPropertyNode *node)
4885 : properties_handler(this),
4888 power_handler(this),
4889 system_handler(this),
4890 configuration_module(this),
4891 fault_handler(this),
4894 self_test_handler(this),
4895 alert_handler(this),
4896 state_handler(this),
4897 mode1_handler(this),
4898 mode2_handler(this),
4899 mode3_handler(this),
4900 mode4_handler(this),
4901 mode5_handler(this),
4902 mode6_handler(this),
4905 for (int i = 0; i < node->nChildren(); ++i)
4907 SGPropertyNode *child = node->getChild(i);
4908 string cname = child->getName();
4909 string cval = child->getStringValue();
4911 if (cname == "name")
4913 else if (cname == "number")
4914 num = child->getIntValue();
4917 SG_LOG(SG_INSTR, SG_WARN, "Error in mk-viii config logic");
4919 SG_LOG(SG_INSTR, SG_WARN, "Section = " << name);
4927 properties_handler.init();
4928 voice_player.init();
4934 SGPropertyNode *node = fgGetNode(("/instrumentation/" + name).c_str(), num, true);
4936 configuration_module.bind(node);
4937 power_handler.bind(node);
4938 io_handler.bind(node);
4939 voice_player.bind(node);
4945 properties_handler.unbind();
4949 MK_VIII::update (double dt)
4951 power_handler.update();
4952 system_handler.update();
4954 if (system_handler.state != SystemHandler::STATE_ON)
4957 io_handler.update_inputs();
4958 io_handler.update_input_faults();
4960 voice_player.update();
4961 state_handler.update();
4963 if (self_test_handler.update())
4966 io_handler.update_internal_latches();
4967 io_handler.update_lamps();
4969 mode1_handler.update();
4970 mode2_handler.update();
4971 mode3_handler.update();
4972 mode4_handler.update();
4973 mode5_handler.update();
4974 mode6_handler.update();
4975 tcf_handler.update();
4977 alert_handler.update();
4978 io_handler.update_outputs();