1 // mk_viii.cxx -- Honeywell MK VIII EGPWS emulation
3 // Written by Jean-Yves Lefort, started September 2005.
5 // Copyright (C) 2005, 2006 Jean-Yves Lefort - jylefort@FreeBSD.org
7 // This program is free software; you can redistribute it and/or
8 // modify it under the terms of the GNU General Public License as
9 // published by the Free Software Foundation; either version 2 of the
10 // License, or (at your option) any later version.
12 // This program is distributed in the hope that it will be useful, but
13 // WITHOUT ANY WARRANTY; without even the implied warranty of
14 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 // General Public License for more details.
17 // You should have received a copy of the GNU General Public License
18 // along with this program; if not, write to the Free Software
19 // Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
21 ///////////////////////////////////////////////////////////////////////////////
25 // [PILOT] Honeywell International Inc., "MK VI and MK VIII,
26 // Enhanced Ground Proximity Warning System (EGPWS),
27 // Pilot's Guide", May 2004
29 // http://www.egpws.com/engineering_support/documents/pilot_guides/060-4314-000.pdf
31 // [SPEC] Honeywell International Inc., "Product Specification
32 // for the MK VI and MK VIII Enhanced Ground Proximity
33 // Warning System (EGPWS)", June 2004
35 // http://www.egpws.com/engineering_support/documents/specs/965-1180-601.pdf
37 // [INSTALL] Honeywell Inc., "MK VI, MK VIII, Enhanced Ground
38 // Proximity Warning System (Class A TAWS), Installation
39 // Design Guide", July 2003
41 // http://www.egpws.com/engineering_support/documents/install/060-4314-150.pdf
45 // [1] [SPEC] does not specify the "must be airborne"
46 // condition; we use it to make sure that the alert
47 // will not trigger when on the ground, since it would
51 # pragma warning( disable: 4355 )
66 #include <simgear/constants.h>
67 #include <simgear/sg_inlines.h>
68 #include <simgear/debug/logstream.hxx>
69 #include <simgear/math/sg_geodesy.hxx>
70 #include <simgear/math/sg_random.h>
71 #include <simgear/misc/sg_path.hxx>
72 #include <simgear/sound/soundmgr_openal.hxx>
73 #include <simgear/structure/exception.hxx>
77 #include <Airports/runways.hxx>
78 #include <Airports/simple.hxx>
80 # include <Include/version.h>
82 #include <Main/fg_props.hxx>
83 #include <Main/globals.hxx>
84 #include "instrument_mgr.hxx"
85 #include "mk_viii.hxx"
87 ///////////////////////////////////////////////////////////////////////////////
88 // constants //////////////////////////////////////////////////////////////////
89 ///////////////////////////////////////////////////////////////////////////////
91 #define GLIDESLOPE_DOTS_TO_DDM 0.0875 // [INSTALL] 6.2.30
92 #define GLIDESLOPE_DDM_TO_DOTS (1 / GLIDESLOPE_DOTS_TO_DDM)
94 #define LOCALIZER_DOTS_TO_DDM 0.0775 // [INSTALL] 6.2.33
95 #define LOCALIZER_DDM_TO_DOTS (1 / LOCALIZER_DOTS_TO_DDM)
97 ///////////////////////////////////////////////////////////////////////////////
98 // helpers ////////////////////////////////////////////////////////////////////
99 ///////////////////////////////////////////////////////////////////////////////
101 #define assert_not_reached() assert(false)
102 #define n_elements(_array) (sizeof(_array) / sizeof((_array)[0]))
103 #define test_bits(_bits, _test) (((_bits) & (_test)) != 0)
105 #define mk_node(_name) (mk->properties_handler.external_properties._name)
107 #define mk_dinput_feed(_name) (mk->io_handler.input_feeders.discretes._name)
108 #define mk_dinput(_name) (mk->io_handler.inputs.discretes._name)
109 #define mk_ainput_feed(_name) (mk->io_handler.input_feeders.arinc429._name)
110 #define mk_ainput(_name) (mk->io_handler.inputs.arinc429._name)
111 #define mk_doutput(_name) (mk->io_handler.outputs.discretes._name)
112 #define mk_aoutput(_name) (mk->io_handler.outputs.arinc429._name)
113 #define mk_data(_name) (mk->io_handler.data._name)
115 #define mk_voice(_name) (mk->voice_player.voices._name)
116 #define mk_altitude_voice(_i) (mk->voice_player.voices.altitude_callouts[(_i)])
118 #define mk_alert(_name) (AlertHandler::ALERT_ ## _name)
119 #define mk_alert_flag(_name) (AlertHandler::ALERT_FLAG_ ## _name)
120 #define mk_set_alerts (mk->alert_handler.set_alerts)
121 #define mk_unset_alerts (mk->alert_handler.unset_alerts)
122 #define mk_repeat_alert (mk->alert_handler.repeat_alert)
123 #define mk_test_alert(_name) (mk_test_alerts(mk_alert(_name)))
124 #define mk_test_alerts(_test) (test_bits(mk->alert_handler.alerts, (_test)))
126 const double MK_VIII::TCFHandler::k = 0.25;
129 modify_amplitude (double amplitude, double dB)
131 return amplitude * pow(10.0, dB / 20.0);
135 heading_add (double h1, double h2)
137 double result = h1 + h2;
144 heading_substract (double h1, double h2)
146 double result = h1 - h2;
153 get_heading_difference (double h1, double h2)
155 double diff = h1 - h2;
166 get_reciprocal_heading (double h)
168 return heading_add(h, 180);
171 ///////////////////////////////////////////////////////////////////////////////
172 // PropertiesHandler //////////////////////////////////////////////////////////
173 ///////////////////////////////////////////////////////////////////////////////
176 MK_VIII::PropertiesHandler::init ()
178 mk_node(ai_caged) = fgGetNode("/instrumentation/attitude-indicator/caged-flag", true);
179 mk_node(ai_roll) = fgGetNode("/instrumentation/attitude-indicator/indicated-roll-deg", true);
180 mk_node(ai_serviceable) = fgGetNode("/instrumentation/attitude-indicator/serviceable", true);
181 mk_node(altimeter_altitude) = fgGetNode("/instrumentation/altimeter/indicated-altitude-ft", true);
182 mk_node(altimeter_serviceable) = fgGetNode("/instrumentation/altimeter/serviceable", true);
183 mk_node(altitude) = fgGetNode("/position/altitude-ft", true);
184 mk_node(altitude_agl) = fgGetNode("/position/altitude-agl-ft", true);
185 mk_node(altitude_gear_agl) = fgGetNode("/position/gear-agl-ft", true);
186 mk_node(orientation_roll) = fgGetNode("/orientation/roll-deg", true);
187 mk_node(asi_serviceable) = fgGetNode("/instrumentation/airspeed-indicator/serviceable", true);
188 mk_node(asi_speed) = fgGetNode("/instrumentation/airspeed-indicator/indicated-speed-kt", true);
189 mk_node(autopilot_heading_lock) = fgGetNode("/autopilot/locks/heading", true);
190 mk_node(flaps) = fgGetNode("/controls/flight/flaps", true);
191 mk_node(gear_down) = fgGetNode("/controls/gear/gear-down", true);
192 mk_node(latitude) = fgGetNode("/position/latitude-deg", true);
193 mk_node(longitude) = fgGetNode("/position/longitude-deg", true);
194 mk_node(nav0_cdi_serviceable) = fgGetNode("/instrumentation/nav/cdi/serviceable", true);
195 mk_node(nav0_gs_distance) = fgGetNode("/instrumentation/nav/gs-distance", true);
196 mk_node(nav0_gs_needle_deflection) = fgGetNode("/instrumentation/nav/gs-needle-deflection", true);
197 mk_node(nav0_gs_serviceable) = fgGetNode("/instrumentation/nav/gs/serviceable", true);
198 mk_node(nav0_has_gs) = fgGetNode("/instrumentation/nav/has-gs", true);
199 mk_node(nav0_heading_needle_deflection) = fgGetNode("/instrumentation/nav/heading-needle-deflection", true);
200 mk_node(nav0_in_range) = fgGetNode("/instrumentation/nav/in-range", true);
201 mk_node(nav0_nav_loc) = fgGetNode("/instrumentation/nav/nav-loc", true);
202 mk_node(nav0_serviceable) = fgGetNode("/instrumentation/nav/serviceable", true);
203 mk_node(power) = fgGetNode(("/systems/electrical/outputs/" + mk->name).c_str(), mk->num, true);
204 mk_node(replay_state) = fgGetNode("/sim/freeze/replay-state", true);
205 mk_node(vs) = fgGetNode("/velocities/vertical-speed-fps", true);
209 MK_VIII::PropertiesHandler::unbind ()
211 vector<SGPropertyNode_ptr>::iterator iter;
213 for (iter = tied_properties.begin(); iter != tied_properties.end(); iter++)
216 tied_properties.clear();
219 ///////////////////////////////////////////////////////////////////////////////
220 // PowerHandler ///////////////////////////////////////////////////////////////
221 ///////////////////////////////////////////////////////////////////////////////
224 MK_VIII::PowerHandler::bind (SGPropertyNode *node)
226 mk->properties_handler.tie(node, "serviceable", SGRawValuePointer<bool>(&serviceable));
230 MK_VIII::PowerHandler::handle_abnormal_voltage (bool abnormal,
236 if (timer->start_or_elapsed() >= max_duration)
237 return true; // power loss
246 MK_VIII::PowerHandler::update ()
248 double voltage = mk_node(power)->getDoubleValue();
249 bool now_powered = true;
257 if (handle_abnormal_voltage(voltage < 20.5, &low_surge_timer, 0.03))
259 if (handle_abnormal_voltage(voltage < 22.0 || voltage > 30.3, &abnormal_timer, 300))
261 if (handle_abnormal_voltage(voltage > 32.2, &high_surge_timer, 1))
263 if (handle_abnormal_voltage(voltage > 37.8, &very_high_surge_timer, 0.1))
273 power_loss_timer.stop();
276 if (power_loss_timer.start_or_elapsed() >= 0.2)
288 MK_VIII::PowerHandler::power_on ()
291 mk->system_handler.power_on();
295 MK_VIII::PowerHandler::power_off ()
298 mk->system_handler.power_off();
299 mk->voice_player.stop(VoicePlayer::STOP_NOW);
300 mk->self_test_handler.power_off(); // run before IOHandler::power_off()
301 mk->io_handler.power_off();
302 mk->mode2_handler.power_off();
303 mk->mode6_handler.power_off();
306 ///////////////////////////////////////////////////////////////////////////////
307 // SystemHandler //////////////////////////////////////////////////////////////
308 ///////////////////////////////////////////////////////////////////////////////
311 MK_VIII::SystemHandler::power_on ()
313 state = STATE_BOOTING;
315 // [SPEC] 3.5.2 mentions a 20 seconds maximum boot delay. We use a
316 // random delay between 3 and 5 seconds.
318 boot_delay = 3 + sg_random() * 2;
323 MK_VIII::SystemHandler::power_off ()
331 MK_VIII::SystemHandler::update ()
333 if (state == STATE_BOOTING)
335 if (boot_timer.elapsed() >= boot_delay)
337 last_replay_state = mk_node(replay_state)->getIntValue();
339 mk->configuration_module.boot();
341 mk->io_handler.boot();
342 mk->fault_handler.boot();
343 mk->alert_handler.boot();
345 // inputs are needed by the following boot() routines
346 mk->io_handler.update_inputs();
348 mk->mode2_handler.boot();
349 mk->mode6_handler.boot();
353 mk->io_handler.post_boot();
356 else if (state != STATE_OFF && mk->configuration_module.state == ConfigurationModule::STATE_OK)
358 // If the replay state changes, switch to reposition mode for 3
359 // seconds ([SPEC] 6.0.5) to avoid spurious alerts.
361 int replay_state = mk_node(replay_state)->getIntValue();
362 if (replay_state != last_replay_state)
364 mk->alert_handler.reposition();
366 last_replay_state = replay_state;
367 state = STATE_REPOSITION;
368 reposition_timer.start();
371 if (state == STATE_REPOSITION && reposition_timer.elapsed() >= 3)
373 // inputs are needed by StateHandler::post_reposition()
374 mk->io_handler.update_inputs();
376 mk->state_handler.post_reposition();
383 ///////////////////////////////////////////////////////////////////////////////
384 // ConfigurationModule ////////////////////////////////////////////////////////
385 ///////////////////////////////////////////////////////////////////////////////
387 MK_VIII::ConfigurationModule::ConfigurationModule (MK_VIII *device)
390 // arbitrary defaults
391 categories[CATEGORY_AIRCRAFT_MODE_TYPE_SELECT] = 0;
392 categories[CATEGORY_AIR_DATA_INPUT_SELECT] = 1;
393 categories[CATEGORY_POSITION_INPUT_SELECT] = 2;
394 categories[CATEGORY_ALTITUDE_CALLOUTS] = 13;
395 categories[CATEGORY_AUDIO_MENU_SELECT] = 0;
396 categories[CATEGORY_TERRAIN_DISPLAY_SELECT] = 1;
397 categories[CATEGORY_OPTIONS_SELECT_GROUP_1] = 124;
398 categories[CATEGORY_RADIO_ALTITUDE_INPUT_SELECT] = 2;
399 categories[CATEGORY_NAVIGATION_INPUT_SELECT] = 3;
400 categories[CATEGORY_ATTITUDE_INPUT_SELECT] = 6;
401 categories[CATEGORY_HEADING_INPUT_SELECT] = 2;
402 categories[CATEGORY_WINDSHEAR_INPUT_SELECT] = 0;
403 categories[CATEGORY_INPUT_OUTPUT_DISCRETE_TYPE_SELECT] = 7;
404 categories[CATEGORY_AUDIO_OUTPUT_LEVEL] = 0;
405 categories[CATEGORY_UNDEFINED_INPUT_SELECT_1] = 0;
406 categories[CATEGORY_UNDEFINED_INPUT_SELECT_2] = 0;
407 categories[CATEGORY_UNDEFINED_INPUT_SELECT_3] = 0;
410 static double m1_t1_min_agl1 (double vs) { return -1620 - 1.1133 * vs; }
411 static double m1_t1_min_agl2 (double vs) { return -400 - 0.4 * vs; }
412 static double m1_t4_min_agl1 (double vs) { return -1625.47 - 1.1167 * vs; }
413 static double m1_t4_min_agl2 (double vs) { return -0.1958 * vs; }
415 static int m3_t1_max_agl (bool flap_override) { return 1500; }
416 static double m3_t1_max_alt_loss (bool flap_override, double agl) { return 5.4 + 0.092 * agl; }
417 static int m3_t2_max_agl (bool flap_override) { return flap_override ? 815 : 925; }
418 static double m3_t2_max_alt_loss (bool flap_override, double agl)
420 int bias = agl > 700 ? 5 : 0;
423 return (9.0 + 0.184 * agl) + bias;
425 return (5.4 + 0.092 * agl) + bias;
428 static double m4_t1_min_agl2 (double airspeed) { return -1083 + 8.333 * airspeed; }
429 static double m4_t568_a_min_agl2 (double airspeed) { return -1523 + 11.36 * airspeed; }
430 static double m4_t79_a_min_agl2 (double airspeed) { return -1182 + 11.36 * airspeed; }
431 static double m4_t568_b_min_agl2 (double airspeed) { return -1570 + 11.6 * airspeed; }
432 static double m4_t79_b_min_agl2 (double airspeed) { return -1222 + 11.6 * airspeed; }
435 MK_VIII::ConfigurationModule::m6_t2_is_bank_angle (Parameter<double> *agl,
441 if (agl->ncd || agl->get() > 122)
442 return abs_roll_deg > 33;
446 if (agl->ncd || agl->get() > 2450)
447 return abs_roll_deg > 55;
448 else if (agl->get() > 150)
449 return agl->get() < 153.33333 * abs_roll_deg - 5983.3333;
453 return agl->get() < 4 * abs_roll_deg - 10;
454 else if (agl->get() > 5)
455 return abs_roll_deg > 10;
461 MK_VIII::ConfigurationModule::m6_t4_is_bank_angle (Parameter<double> *agl,
467 if (agl->ncd || agl->get() > 156)
468 return abs_roll_deg > 33;
472 if (agl->ncd || agl->get() > 210)
473 return abs_roll_deg > 50;
477 return agl->get() < 5.7142857 * abs_roll_deg - 75.714286;
483 MK_VIII::ConfigurationModule::read_aircraft_mode_type_select (int value)
485 static const Mode1Handler::EnvelopesConfiguration m1_t1 =
486 { false, 10, m1_t1_min_agl1, 284, m1_t1_min_agl2, 2450 };
487 static const Mode1Handler::EnvelopesConfiguration m1_t4 =
488 { true, 50, m1_t4_min_agl1, 346, m1_t4_min_agl2, 1958 };
490 static const Mode2Handler::Configuration m2_t1 = { 190, 280 };
491 static const Mode2Handler::Configuration m2_t4 = { 220, 310 };
492 static const Mode2Handler::Configuration m2_t5 = { 220, 310 };
494 static const Mode3Handler::Configuration m3_t1 =
495 { 30, m3_t1_max_agl, m3_t1_max_alt_loss };
496 static const Mode3Handler::Configuration m3_t2 =
497 { 50, m3_t2_max_agl, m3_t2_max_alt_loss };
499 static const Mode4Handler::EnvelopesConfiguration m4_t1_ac =
500 { 190, 250, 500, m4_t1_min_agl2, 1000 };
501 static const Mode4Handler::EnvelopesConfiguration m4_t5_ac =
502 { 178, 200, 500, m4_t568_a_min_agl2, 1000 };
503 static const Mode4Handler::EnvelopesConfiguration m4_t68_ac =
504 { 178, 200, 500, m4_t568_a_min_agl2, 750 };
505 static const Mode4Handler::EnvelopesConfiguration m4_t79_ac =
506 { 148, 170, 500, m4_t79_a_min_agl2, 750 };
508 static const Mode4Handler::EnvelopesConfiguration m4_t1_b =
509 { 159, 250, 245, m4_t1_min_agl2, 1000 };
510 static const Mode4Handler::EnvelopesConfiguration m4_t5_b =
511 { 148, 200, 200, m4_t568_b_min_agl2, 1000 };
512 static const Mode4Handler::EnvelopesConfiguration m4_t6_b =
513 { 150, 200, 170, m4_t568_b_min_agl2, 750 };
514 static const Mode4Handler::EnvelopesConfiguration m4_t7_b =
515 { 120, 170, 170, m4_t79_b_min_agl2, 750 };
516 static const Mode4Handler::EnvelopesConfiguration m4_t8_b =
517 { 148, 200, 150, m4_t568_b_min_agl2, 750 };
518 static const Mode4Handler::EnvelopesConfiguration m4_t9_b =
519 { 118, 170, 150, m4_t79_b_min_agl2, 750 };
521 static const Mode4Handler::ModesConfiguration m4_t1 = { &m4_t1_ac, &m4_t1_b };
522 static const Mode4Handler::ModesConfiguration m4_t5 = { &m4_t5_ac, &m4_t5_b };
523 static const Mode4Handler::ModesConfiguration m4_t6 = { &m4_t68_ac, &m4_t6_b };
524 static const Mode4Handler::ModesConfiguration m4_t7 = { &m4_t79_ac, &m4_t7_b };
525 static const Mode4Handler::ModesConfiguration m4_t8 = { &m4_t68_ac, &m4_t8_b };
526 static const Mode4Handler::ModesConfiguration m4_t9 = { &m4_t79_ac, &m4_t9_b };
528 static Mode6Handler::BankAnglePredicate m6_t2 = m6_t2_is_bank_angle;
529 static Mode6Handler::BankAnglePredicate m6_t4 = m6_t4_is_bank_angle;
531 static const IOHandler::FaultsConfiguration f_slow = { 180, 200 };
532 static const IOHandler::FaultsConfiguration f_fast = { 250, 290 };
537 const Mode1Handler::EnvelopesConfiguration *m1;
538 const Mode2Handler::Configuration *m2;
539 const Mode3Handler::Configuration *m3;
540 const Mode4Handler::ModesConfiguration *m4;
541 Mode6Handler::BankAnglePredicate m6;
542 const IOHandler::FaultsConfiguration *f;
544 } aircraft_types[] = {
545 { 0, &m1_t4, &m2_t4, &m3_t2, &m4_t6, m6_t4, &f_fast, 2000 },
546 { 1, &m1_t4, &m2_t4, &m3_t2, &m4_t8, m6_t4, &f_fast, 2000 },
547 { 2, &m1_t4, &m2_t4, &m3_t2, &m4_t5, m6_t4, &f_fast, 2000 },
548 { 3, &m1_t4, &m2_t5, &m3_t2, &m4_t7, m6_t4, &f_slow, 2000 },
549 { 4, &m1_t4, &m2_t5, &m3_t2, &m4_t9, m6_t4, &f_slow, 2000 },
550 { 254, &m1_t1, &m2_t1, &m3_t1, &m4_t1, m6_t2, &f_fast, 3500 },
551 { 255, &m1_t1, &m2_t1, &m3_t1, &m4_t1, m6_t2, &f_fast, 2000 }
554 for (size_t i = 0; i < n_elements(aircraft_types); i++)
555 if (aircraft_types[i].type == value)
557 mk->mode1_handler.conf.envelopes = aircraft_types[i].m1;
558 mk->mode2_handler.conf = aircraft_types[i].m2;
559 mk->mode3_handler.conf = aircraft_types[i].m3;
560 mk->mode4_handler.conf.modes = aircraft_types[i].m4;
561 mk->mode6_handler.conf.is_bank_angle = aircraft_types[i].m6;
562 mk->io_handler.conf.faults = aircraft_types[i].f;
563 mk->conf.runway_database = aircraft_types[i].runway_database;
567 state = STATE_INVALID_AIRCRAFT_TYPE;
572 MK_VIII::ConfigurationModule::read_air_data_input_select (int value)
575 return (value >= 0 && value <= 6) || (value >= 10 && value <= 13) || value == 255;
579 MK_VIII::ConfigurationModule::read_position_input_select (int value)
582 mk->io_handler.conf.use_internal_gps = true;
583 else if ((value >= 0 && value <= 5)
584 || (value >= 10 && value <= 13)
587 mk->io_handler.conf.use_internal_gps = false;
595 MK_VIII::ConfigurationModule::read_altitude_callouts (int value)
610 { 0, { MINIMUMS, SMART_500, 200, 100, 50, 40, 30, 20, 10, 0 } },
611 { 1, { MINIMUMS, SMART_500, 200, 0 } },
612 { 2, { MINIMUMS, SMART_500, 100, 50, 40, 30, 20, 10, 0 } },
613 { 3, { MINIMUMS, SMART_500, 0 } },
614 { 4, { MINIMUMS, 200, 100, 50, 40, 30, 20, 10, 0 } },
615 { 5, { MINIMUMS, 200, 0 } },
616 { 6, { MINIMUMS, 100, 50, 40, 30, 20, 10, 0 } },
618 { 8, { MINIMUMS, 0 } },
619 { 9, { MINIMUMS, 500, 200, 100, 50, 40, 30, 20, 10, 0 } },
620 { 10, { MINIMUMS, 500, 200, 0 } },
621 { 11, { MINIMUMS, 500, 100, 50, 40, 30, 20, 10, 0 } },
622 { 12, { MINIMUMS, 500, 0 } },
623 { 13, { MINIMUMS, 1000, 500, 400, 300, 200, 100, 50, 40, 30, 20, 10, 0 } },
624 { 14, { MINIMUMS, 100, 0 } },
625 { 15, { MINIMUMS, 200, 100, 0 } },
626 { 100, { FIELD_500, 0 } },
627 { 101, { FIELD_500_ABOVE, 0 } }
632 mk->mode6_handler.conf.minimums_enabled = false;
633 mk->mode6_handler.conf.smart_500_enabled = false;
634 mk->mode6_handler.conf.above_field_voice = NULL;
635 for (i = 0; i < n_altitude_callouts; i++)
636 mk->mode6_handler.conf.altitude_callouts_enabled[i] = false;
638 for (i = 0; i < n_elements(values); i++)
639 if (values[i].id == value)
641 for (int j = 0; values[i].callouts[j] != 0; j++)
642 switch (values[i].callouts[j])
645 mk->mode6_handler.conf.minimums_enabled = true;
649 mk->mode6_handler.conf.smart_500_enabled = true;
653 mk->mode6_handler.conf.above_field_voice = mk_altitude_voice(Mode6Handler::ALTITUDE_CALLOUT_500);
656 case FIELD_500_ABOVE:
657 mk->mode6_handler.conf.above_field_voice = mk_voice(five_hundred_above);
661 for (unsigned k = 0; k < n_altitude_callouts; k++)
662 if (mk->mode6_handler.altitude_callout_definitions[k] == values[i].callouts[j])
663 mk->mode6_handler.conf.altitude_callouts_enabled[k] = true;
674 MK_VIII::ConfigurationModule::read_audio_menu_select (int value)
676 if (value == 0 || value == 1)
677 mk->mode4_handler.conf.voice_too_low_gear = mk_voice(too_low_gear);
678 else if (value == 2 || value == 3)
679 mk->mode4_handler.conf.voice_too_low_gear = mk_voice(too_low_flaps);
687 MK_VIII::ConfigurationModule::read_terrain_display_select (int value)
690 mk->tcf_handler.conf.enabled = false;
691 else if (value == 0 || value == 1 || (value >= 3 && value <= 15)
692 || (value >= 18 && value <= 20) || (value >= 235 && value <= 255))
693 mk->tcf_handler.conf.enabled = true;
701 MK_VIII::ConfigurationModule::read_options_select_group_1 (int value)
703 if (value >= 0 && value < 128)
705 mk->io_handler.conf.flap_reversal = test_bits(value, 1 << 1);
706 mk->mode6_handler.conf.bank_angle_enabled = test_bits(value, 1 << 2);
707 mk->io_handler.conf.steep_approach_enabled = test_bits(value, 1 << 6);
715 MK_VIII::ConfigurationModule::read_radio_altitude_input_select (int value)
717 if (value >= 0 && value <= 2)
718 mk->io_handler.conf.use_gear_altitude = true;
720 mk->io_handler.conf.use_gear_altitude = false;
721 return (value >= 0 && value <= 4) || (value >= 251 && value <= 255);
725 MK_VIII::ConfigurationModule::read_navigation_input_select (int value)
727 if (value >= 0 && value <= 2)
728 mk->io_handler.conf.localizer_enabled = false;
729 else if ((value >= 3 && value <= 5) || (value >= 250 && value <= 255))
730 mk->io_handler.conf.localizer_enabled = true;
738 MK_VIII::ConfigurationModule::read_attitude_input_select (int value)
741 mk->io_handler.conf.use_attitude_indicator=true;
743 mk->io_handler.conf.use_attitude_indicator=false;
744 return (value >= 0 && value <= 6) || value == 253 || value == 255;
748 MK_VIII::ConfigurationModule::read_heading_input_select (int value)
751 return (value >= 0 && value <= 3) || value == 254 || value == 255;
755 MK_VIII::ConfigurationModule::read_windshear_input_select (int value)
758 return value == 0 || (value >= 253 && value <= 255);
762 MK_VIII::ConfigurationModule::read_input_output_discrete_type_select (int value)
767 IOHandler::LampConfiguration lamp_conf;
768 bool gpws_inhibit_enabled;
769 bool momentary_flap_override_enabled;
770 bool alternate_steep_approach;
772 { 0, { false, false }, false, true, true },
773 { 1, { true, false }, false, true, true },
774 { 2, { false, false }, true, true, true },
775 { 3, { true, false }, true, true, true },
776 { 4, { false, true }, true, true, true },
777 { 5, { true, true }, true, true, true },
778 { 6, { false, false }, true, true, false },
779 { 7, { true, false }, true, true, false },
780 { 254, { false, false }, true, false, true },
781 { 255, { false, false }, true, false, true }
784 for (size_t i = 0; i < n_elements(io_types); i++)
785 if (io_types[i].type == value)
787 mk->io_handler.conf.lamp = &io_types[i].lamp_conf;
788 mk->io_handler.conf.gpws_inhibit_enabled = io_types[i].gpws_inhibit_enabled;
789 mk->io_handler.conf.momentary_flap_override_enabled = io_types[i].momentary_flap_override_enabled;
790 mk->io_handler.conf.alternate_steep_approach = io_types[i].alternate_steep_approach;
798 MK_VIII::ConfigurationModule::read_audio_output_level (int value)
812 for (size_t i = 0; i < n_elements(values); i++)
813 if (values[i].id == value)
815 mk->voice_player.set_volume(mk->voice_player.conf.volume = modify_amplitude(1.0, values[i].relative_dB));
819 // The self test needs the voice player even when the configuration
820 // is invalid, so set a default value.
821 mk->voice_player.set_volume(mk->voice_player.conf.volume = 1.0);
826 MK_VIII::ConfigurationModule::read_undefined_input_select (int value)
833 MK_VIII::ConfigurationModule::boot ()
835 bool (MK_VIII::ConfigurationModule::*readers[N_CATEGORIES]) (int) = {
836 &MK_VIII::ConfigurationModule::read_aircraft_mode_type_select,
837 &MK_VIII::ConfigurationModule::read_air_data_input_select,
838 &MK_VIII::ConfigurationModule::read_position_input_select,
839 &MK_VIII::ConfigurationModule::read_altitude_callouts,
840 &MK_VIII::ConfigurationModule::read_audio_menu_select,
841 &MK_VIII::ConfigurationModule::read_terrain_display_select,
842 &MK_VIII::ConfigurationModule::read_options_select_group_1,
843 &MK_VIII::ConfigurationModule::read_radio_altitude_input_select,
844 &MK_VIII::ConfigurationModule::read_navigation_input_select,
845 &MK_VIII::ConfigurationModule::read_attitude_input_select,
846 &MK_VIII::ConfigurationModule::read_heading_input_select,
847 &MK_VIII::ConfigurationModule::read_windshear_input_select,
848 &MK_VIII::ConfigurationModule::read_input_output_discrete_type_select,
849 &MK_VIII::ConfigurationModule::read_audio_output_level,
850 &MK_VIII::ConfigurationModule::read_undefined_input_select,
851 &MK_VIII::ConfigurationModule::read_undefined_input_select,
852 &MK_VIII::ConfigurationModule::read_undefined_input_select
855 memcpy(effective_categories, categories, sizeof(categories));
858 for (int i = 0; i < N_CATEGORIES; i++)
859 if (! (this->*readers[i])(effective_categories[i]))
861 SG_LOG(SG_INSTR, SG_ALERT, "MK VIII EGPWS configuration category " << i + 1 << ": invalid value " << effective_categories[i]);
863 if (state == STATE_OK)
864 state = STATE_INVALID_DATABASE;
866 mk_doutput(gpws_inop) = true;
867 mk_doutput(tad_inop) = true;
874 if (mk->mode6_handler.conf.above_field_voice && ! mk->tcf_handler.conf.enabled)
877 SG_LOG(SG_INSTR, SG_ALERT, "MK VIII EGPWS configuration module: when category 4 is set to 100 or 101, category 6 must not be set to 2");
878 state = STATE_INVALID_DATABASE;
883 MK_VIII::ConfigurationModule::bind (SGPropertyNode *node)
885 for (int i = 0; i < N_CATEGORIES; i++)
887 std::ostringstream name;
888 name << "configuration-module/category-" << i + 1;
889 mk->properties_handler.tie(node, name.str().c_str(), SGRawValuePointer<int>(&categories[i]));
893 ///////////////////////////////////////////////////////////////////////////////
894 // FaultHandler ///////////////////////////////////////////////////////////////
895 ///////////////////////////////////////////////////////////////////////////////
897 // [INSTALL] only specifies that the faults cause a GPWS INOP
898 // indication. We arbitrarily set a TAD INOP when it makes sense.
899 const unsigned int MK_VIII::FaultHandler::fault_inops[] = {
900 INOP_GPWS | INOP_TAD, // [INSTALL] 3.15.1.3
901 INOP_GPWS, // [INSTALL] appendix E 6.6.2
902 INOP_GPWS, // [INSTALL] appendix E 6.6.4
903 INOP_GPWS, // [INSTALL] appendix E 6.6.6
904 INOP_GPWS | INOP_TAD, // [INSTALL] appendix E 6.6.7
905 INOP_GPWS, // [INSTALL] appendix E 6.6.13
906 INOP_GPWS, // [INSTALL] appendix E 6.6.25
907 INOP_GPWS, // [INSTALL] appendix E 6.6.27
908 INOP_TAD, // unspecified
909 INOP_GPWS, // unspecified
910 INOP_GPWS, // unspecified
911 // [INSTALL] 2.3.10.1 specifies that GPWS INOP is "activated during
912 // any detected partial or total failure of the GPWS modes 1-5", so
913 // do not set any INOP for mode 6 and bank angle.
916 INOP_TAD // unspecified
920 MK_VIII::FaultHandler::has_faults () const
922 for (int i = 0; i < N_FAULTS; i++)
930 MK_VIII::FaultHandler::has_faults (unsigned int inop)
932 for (int i = 0; i < N_FAULTS; i++)
933 if (faults[i] && test_bits(fault_inops[i], inop))
940 MK_VIII::FaultHandler::boot ()
942 memset(faults, 0, sizeof(faults));
946 MK_VIII::FaultHandler::set_fault (Fault fault)
950 faults[fault] = true;
952 mk->self_test_handler.set_inop();
954 if (test_bits(fault_inops[fault], INOP_GPWS))
956 mk_unset_alerts(~mk_alert(TCF_TOO_LOW_TERRAIN));
957 mk_doutput(gpws_inop) = true;
959 if (test_bits(fault_inops[fault], INOP_TAD))
961 mk_unset_alerts(mk_alert(TCF_TOO_LOW_TERRAIN));
962 mk_doutput(tad_inop) = true;
968 MK_VIII::FaultHandler::unset_fault (Fault fault)
972 faults[fault] = false;
973 if (! has_faults(INOP_GPWS))
974 mk_doutput(gpws_inop) = false;
975 if (! has_faults(INOP_TAD))
976 mk_doutput(tad_inop) = false;
980 ///////////////////////////////////////////////////////////////////////////////
981 // IOHandler //////////////////////////////////////////////////////////////////
982 ///////////////////////////////////////////////////////////////////////////////
985 MK_VIII::IOHandler::TerrainClearanceFilter::update (double agl)
987 // [PILOT] page 20 specifies that the terrain clearance is equal to
988 // 75% of the radio altitude, averaged over the previous 15 seconds.
990 samples_type::iterator iter;
992 // remove samples older than 15 seconds
993 for (iter = samples.begin(); iter != samples.end() && globals->get_sim_time_sec() - (*iter).timestamp >= 15; iter = samples.begin())
997 samples.push_back(Sample<double>(agl));
1000 double new_value = 0;
1001 if (samples.size() > 0)
1003 for (iter = samples.begin(); iter != samples.end(); iter++)
1004 new_value += (*iter).value;
1005 new_value /= samples.size();
1009 if (new_value > value)
1016 MK_VIII::IOHandler::TerrainClearanceFilter::reset ()
1022 MK_VIII::IOHandler::IOHandler (MK_VIII *device)
1023 : mk(device), _lamp(LAMP_NONE)
1025 memset(&input_feeders, 0, sizeof(input_feeders));
1026 memset(&inputs.discretes, 0, sizeof(inputs.discretes));
1027 memset(&outputs, 0, sizeof(outputs));
1028 memset(&power_saved, 0, sizeof(power_saved));
1030 mk_dinput_feed(landing_gear) = true;
1031 mk_dinput_feed(landing_flaps) = true;
1032 mk_dinput_feed(glideslope_inhibit) = true;
1033 mk_dinput_feed(decision_height) = true;
1034 mk_dinput_feed(autopilot_engaged) = true;
1035 mk_ainput_feed(uncorrected_barometric_altitude) = true;
1036 mk_ainput_feed(barometric_altitude_rate) = true;
1037 mk_ainput_feed(radio_altitude) = true;
1038 mk_ainput_feed(glideslope_deviation) = true;
1039 mk_ainput_feed(roll_angle) = true;
1040 mk_ainput_feed(localizer_deviation) = true;
1041 mk_ainput_feed(computed_airspeed) = true;
1043 // will be unset on power on
1044 mk_doutput(gpws_inop) = true;
1045 mk_doutput(tad_inop) = true;
1049 MK_VIII::IOHandler::boot ()
1051 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
1054 mk_doutput(gpws_inop) = false;
1055 mk_doutput(tad_inop) = false;
1057 mk_doutput(glideslope_cancel) = power_saved.glideslope_cancel;
1059 altitude_samples.clear();
1063 MK_VIII::IOHandler::post_boot ()
1065 if (momentary_steep_approach_enabled())
1067 last_landing_gear = mk_dinput(landing_gear);
1068 last_real_flaps_down = real_flaps_down();
1071 // read externally-latching input discretes
1072 update_alternate_discrete_input(&mk_dinput(mode6_low_volume));
1073 update_alternate_discrete_input(&mk_dinput(audio_inhibit));
1077 MK_VIII::IOHandler::power_off ()
1079 power_saved.glideslope_cancel = mk_doutput(glideslope_cancel); // [SPEC] 6.2.5
1081 memset(&outputs, 0, sizeof(outputs));
1083 audio_inhibit_fault_timer.stop();
1084 landing_gear_fault_timer.stop();
1085 flaps_down_fault_timer.stop();
1086 momentary_flap_override_fault_timer.stop();
1087 self_test_fault_timer.stop();
1088 glideslope_cancel_fault_timer.stop();
1089 steep_approach_fault_timer.stop();
1090 gpws_inhibit_fault_timer.stop();
1091 ta_tcf_inhibit_fault_timer.stop();
1094 mk_doutput(gpws_inop) = true;
1095 mk_doutput(tad_inop) = true;
1099 MK_VIII::IOHandler::enter_ground ()
1101 reset_terrain_clearance();
1103 if (conf.momentary_flap_override_enabled)
1104 mk_doutput(flap_override) = false; // [INSTALL] 3.15.1.6
1108 MK_VIII::IOHandler::enter_takeoff ()
1110 reset_terrain_clearance();
1112 if (momentary_steep_approach_enabled())
1113 // landing or go-around, disable steep approach as per [SPEC] 6.2.1
1114 mk_doutput(steep_approach) = false;
1118 MK_VIII::IOHandler::update_inputs ()
1120 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
1123 // 1. process input feeders
1125 if (mk_dinput_feed(landing_gear))
1126 mk_dinput(landing_gear) = mk_node(gear_down)->getBoolValue();
1127 if (mk_dinput_feed(landing_flaps))
1128 mk_dinput(landing_flaps) = mk_node(flaps)->getDoubleValue() < 1;
1129 if (mk_dinput_feed(glideslope_inhibit))
1130 mk_dinput(glideslope_inhibit) = mk_node(nav0_gs_distance)->getDoubleValue() < 0;
1131 if (mk_dinput_feed(autopilot_engaged))
1135 mode = mk_node(autopilot_heading_lock)->getStringValue();
1136 mk_dinput(autopilot_engaged) = mode && *mode;
1139 if (mk_ainput_feed(uncorrected_barometric_altitude))
1141 if (mk_node(altimeter_serviceable)->getBoolValue())
1142 mk_ainput(uncorrected_barometric_altitude).set(mk_node(altimeter_altitude)->getDoubleValue());
1144 mk_ainput(uncorrected_barometric_altitude).unset();
1146 if (mk_ainput_feed(barometric_altitude_rate))
1147 // Do not use the vsi, because it is pressure-based only, and
1148 // therefore too laggy for GPWS alerting purposes. I guess that
1149 // a real ADC combines pressure-based and inerta-based altitude
1150 // rates to provide a non-laggy rate. That non-laggy rate is
1151 // best emulated by /velocities/vertical-speed-fps * 60.
1152 mk_ainput(barometric_altitude_rate).set(mk_node(vs)->getDoubleValue() * 60);
1153 if (mk_ainput_feed(radio_altitude))
1156 if (conf.use_gear_altitude)
1157 agl = mk_node(altitude_gear_agl)->getDoubleValue();
1159 agl = mk_node(altitude_agl)->getDoubleValue();
1160 // Some flight models may return negative values when on the
1161 // ground or after a crash; do not allow them.
1162 mk_ainput(radio_altitude).set(SG_MAX2(0.0, agl));
1164 if (mk_ainput_feed(glideslope_deviation))
1166 if (mk_node(nav0_serviceable)->getBoolValue()
1167 && mk_node(nav0_gs_serviceable)->getBoolValue()
1168 && mk_node(nav0_in_range)->getBoolValue()
1169 && mk_node(nav0_has_gs)->getBoolValue())
1170 // gs-needle-deflection is expressed in degrees, and 1 dot =
1171 // 0.32 degrees (according to
1172 // http://www.ntsb.gov/Recs/letters/2000/A00_41_45.pdf)
1173 mk_ainput(glideslope_deviation).set(mk_node(nav0_gs_needle_deflection)->getDoubleValue() / 0.32 * GLIDESLOPE_DOTS_TO_DDM);
1175 mk_ainput(glideslope_deviation).unset();
1177 if (mk_ainput_feed(roll_angle))
1179 if (conf.use_attitude_indicator)
1181 // read data from attitude indicator instrument (requires vacuum system to work)
1182 if (mk_node(ai_serviceable)->getBoolValue() && ! mk_node(ai_caged)->getBoolValue())
1183 mk_ainput(roll_angle).set(mk_node(ai_roll)->getDoubleValue());
1185 mk_ainput(roll_angle).unset();
1189 // use simulator source
1190 mk_ainput(roll_angle).set(mk_node(orientation_roll)->getDoubleValue());
1193 if (mk_ainput_feed(localizer_deviation))
1195 if (mk_node(nav0_serviceable)->getBoolValue()
1196 && mk_node(nav0_cdi_serviceable)->getBoolValue()
1197 && mk_node(nav0_in_range)->getBoolValue()
1198 && mk_node(nav0_nav_loc)->getBoolValue())
1199 // heading-needle-deflection is expressed in degrees, and 1
1200 // dot = 2 degrees (0.5 degrees for ILS, but navradio.cxx
1201 // performs the conversion)
1202 mk_ainput(localizer_deviation).set(mk_node(nav0_heading_needle_deflection)->getDoubleValue() / 2 * LOCALIZER_DOTS_TO_DDM);
1204 mk_ainput(localizer_deviation).unset();
1206 if (mk_ainput_feed(computed_airspeed))
1208 if (mk_node(asi_serviceable)->getBoolValue())
1209 mk_ainput(computed_airspeed).set(mk_node(asi_speed)->getDoubleValue());
1211 mk_ainput(computed_airspeed).unset();
1216 mk_data(decision_height).set(&mk_ainput(decision_height));
1217 mk_data(radio_altitude).set(&mk_ainput(radio_altitude));
1218 mk_data(roll_angle).set(&mk_ainput(roll_angle));
1220 // update barometric_altitude_rate as per [SPEC] 6.2.1: "During
1221 // normal conditions, the system will base Mode 1 computations upon
1222 // barometric rate from the ADC. If this computed data is not valid
1223 // or available then the system will use internally computed
1224 // barometric altitude rate."
1226 if (! mk_ainput(barometric_altitude_rate).ncd)
1227 // the altitude rate input is valid, use it
1228 mk_data(barometric_altitude_rate).set(mk_ainput(barometric_altitude_rate).get());
1233 // The altitude rate input is invalid. We can compute an
1234 // altitude rate if all the following conditions are true:
1236 // - the altitude input is valid
1237 // - there is an altitude sample with age >= 1 second
1238 // - that altitude sample is valid
1240 if (! mk_ainput(uncorrected_barometric_altitude).ncd)
1242 if (! altitude_samples.empty() && ! altitude_samples.front().value.ncd)
1244 double elapsed = globals->get_sim_time_sec() - altitude_samples.front().timestamp;
1248 mk_data(barometric_altitude_rate).set((mk_ainput(uncorrected_barometric_altitude).get() - altitude_samples.front().value.get()) / elapsed * 60);
1254 mk_data(barometric_altitude_rate).unset();
1257 altitude_samples.push_back(Sample< Parameter<double> >(mk_ainput(uncorrected_barometric_altitude)));
1259 // Erase everything from the beginning of the list up to the sample
1260 // preceding the most recent sample whose age is >= 1 second.
1262 altitude_samples_type::iterator erase_last = altitude_samples.begin();
1263 altitude_samples_type::iterator iter;
1265 for (iter = altitude_samples.begin(); iter != altitude_samples.end(); iter++)
1266 if (globals->get_sim_time_sec() - (*iter).timestamp >= 1)
1271 if (erase_last != altitude_samples.begin())
1272 altitude_samples.erase(altitude_samples.begin(), erase_last);
1276 if (conf.use_internal_gps)
1278 mk_data(gps_altitude).set(mk_node(altitude)->getDoubleValue());
1279 mk_data(gps_latitude).set(mk_node(latitude)->getDoubleValue());
1280 mk_data(gps_longitude).set(mk_node(longitude)->getDoubleValue());
1281 mk_data(gps_vertical_figure_of_merit).set(0.0);
1285 mk_data(gps_altitude).set(&mk_ainput(gps_altitude));
1286 mk_data(gps_latitude).set(&mk_ainput(gps_latitude));
1287 mk_data(gps_longitude).set(&mk_ainput(gps_longitude));
1288 mk_data(gps_vertical_figure_of_merit).set(&mk_ainput(gps_vertical_figure_of_merit));
1291 // update glideslope and localizer
1293 mk_data(glideslope_deviation_dots).set(&mk_ainput(glideslope_deviation), GLIDESLOPE_DDM_TO_DOTS);
1294 mk_data(localizer_deviation_dots).set(&mk_ainput(localizer_deviation), LOCALIZER_DDM_TO_DOTS);
1296 // Update geometric altitude; [SPEC] 6.7.8 provides an overview of a
1297 // complex algorithm which combines several input sources to
1298 // calculate the geometric altitude. Since the exact algorithm is
1299 // not given, we fallback to a much simpler method.
1301 if (! mk_data(gps_altitude).ncd)
1302 mk_data(geometric_altitude).set(mk_data(gps_altitude).get());
1303 else if (! mk_ainput(uncorrected_barometric_altitude).ncd)
1304 mk_data(geometric_altitude).set(mk_ainput(uncorrected_barometric_altitude).get());
1306 mk_data(geometric_altitude).unset();
1308 // update terrain clearance
1310 update_terrain_clearance();
1312 // 3. perform sanity checks
1314 if (! mk_data(radio_altitude).ncd && mk_data(radio_altitude).get() < 0)
1315 mk_data(radio_altitude).unset();
1317 if (! mk_data(decision_height).ncd && mk_data(decision_height).get() < 0)
1318 mk_data(decision_height).unset();
1320 if (! mk_data(gps_latitude).ncd
1321 && (mk_data(gps_latitude).get() < -90
1322 || mk_data(gps_latitude).get() > 90))
1323 mk_data(gps_latitude).unset();
1325 if (! mk_data(gps_longitude).ncd
1326 && (mk_data(gps_longitude).get() < -180
1327 || mk_data(gps_longitude).get() > 180))
1328 mk_data(gps_longitude).unset();
1330 if (! mk_data(roll_angle).ncd
1331 && ((mk_data(roll_angle).get() < -180)
1332 || (mk_data(roll_angle).get() > 180)))
1333 mk_data(roll_angle).unset();
1335 // 4. process input feeders requiring data computed above
1337 if (mk_dinput_feed(decision_height))
1338 mk_dinput(decision_height) = ! mk_data(radio_altitude).ncd
1339 && ! mk_data(decision_height).ncd
1340 && mk_data(radio_altitude).get() <= mk_data(decision_height).get();
1344 MK_VIII::IOHandler::update_terrain_clearance ()
1346 if (! mk_data(radio_altitude).ncd)
1347 mk_data(terrain_clearance).set(terrain_clearance_filter.update(mk_data(radio_altitude).get()));
1349 mk_data(terrain_clearance).unset();
1353 MK_VIII::IOHandler::reset_terrain_clearance ()
1355 terrain_clearance_filter.reset();
1356 update_terrain_clearance();
1360 MK_VIII::IOHandler::handle_input_fault (bool test, FaultHandler::Fault fault)
1364 if (! mk->fault_handler.faults[fault])
1365 mk->fault_handler.set_fault(fault);
1368 mk->fault_handler.unset_fault(fault);
1372 MK_VIII::IOHandler::handle_input_fault (bool test,
1374 double max_duration,
1375 FaultHandler::Fault fault)
1379 if (! mk->fault_handler.faults[fault])
1381 if (timer->start_or_elapsed() >= max_duration)
1382 mk->fault_handler.set_fault(fault);
1387 mk->fault_handler.unset_fault(fault);
1393 MK_VIII::IOHandler::update_input_faults ()
1395 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
1398 // [INSTALL] 3.15.1.3
1399 handle_input_fault(mk_dinput(audio_inhibit),
1400 &audio_inhibit_fault_timer,
1402 FaultHandler::FAULT_ALL_MODES_INHIBIT);
1404 // [INSTALL] appendix E 6.6.2
1405 handle_input_fault(mk_dinput(landing_gear)
1406 && ! mk_ainput(computed_airspeed).ncd
1407 && mk_ainput(computed_airspeed).get() > conf.faults->max_gear_down_airspeed,
1408 &landing_gear_fault_timer,
1410 FaultHandler::FAULT_GEAR_SWITCH);
1412 // [INSTALL] appendix E 6.6.4
1413 handle_input_fault(flaps_down()
1414 && ! mk_ainput(computed_airspeed).ncd
1415 && mk_ainput(computed_airspeed).get() > conf.faults->max_flaps_down_airspeed,
1416 &flaps_down_fault_timer,
1418 FaultHandler::FAULT_FLAPS_SWITCH);
1420 // [INSTALL] appendix E 6.6.6
1421 if (conf.momentary_flap_override_enabled)
1422 handle_input_fault(mk_dinput(momentary_flap_override),
1423 &momentary_flap_override_fault_timer,
1425 FaultHandler::FAULT_MOMENTARY_FLAP_OVERRIDE_INVALID);
1427 // [INSTALL] appendix E 6.6.7
1428 handle_input_fault(mk_dinput(self_test),
1429 &self_test_fault_timer,
1431 FaultHandler::FAULT_SELF_TEST_INVALID);
1433 // [INSTALL] appendix E 6.6.13
1434 handle_input_fault(mk_dinput(glideslope_cancel),
1435 &glideslope_cancel_fault_timer,
1437 FaultHandler::FAULT_GLIDESLOPE_CANCEL_INVALID);
1439 // [INSTALL] appendix E 6.6.25
1440 if (momentary_steep_approach_enabled())
1441 handle_input_fault(mk_dinput(steep_approach),
1442 &steep_approach_fault_timer,
1444 FaultHandler::FAULT_STEEP_APPROACH_INVALID);
1446 // [INSTALL] appendix E 6.6.27
1447 if (conf.gpws_inhibit_enabled)
1448 handle_input_fault(mk_dinput(gpws_inhibit),
1449 &gpws_inhibit_fault_timer,
1451 FaultHandler::FAULT_GPWS_INHIBIT);
1453 // [INSTALL] does not specify a fault for this one, but it makes
1454 // sense to have it behave like GPWS inhibit
1455 handle_input_fault(mk_dinput(ta_tcf_inhibit),
1456 &ta_tcf_inhibit_fault_timer,
1458 FaultHandler::FAULT_TA_TCF_INHIBIT);
1460 // [PILOT] page 48: "In the event that required data for a
1461 // particular function is not available, then that function is
1462 // automatically inhibited and annunciated"
1464 handle_input_fault(mk_data(radio_altitude).ncd
1465 || mk_ainput(uncorrected_barometric_altitude).ncd
1466 || mk_data(barometric_altitude_rate).ncd
1467 || mk_ainput(computed_airspeed).ncd
1468 || mk_data(terrain_clearance).ncd,
1469 FaultHandler::FAULT_MODES14_INPUTS_INVALID);
1471 if (! mk_dinput(glideslope_inhibit))
1472 handle_input_fault(mk_data(radio_altitude).ncd,
1473 FaultHandler::FAULT_MODE5_INPUTS_INVALID);
1475 if (mk->mode6_handler.altitude_callouts_enabled())
1476 handle_input_fault(mk->mode6_handler.conf.above_field_voice
1477 ? (mk_data(gps_latitude).ncd
1478 || mk_data(gps_longitude).ncd
1479 || mk_data(geometric_altitude).ncd)
1480 : mk_data(radio_altitude).ncd,
1481 FaultHandler::FAULT_MODE6_INPUTS_INVALID);
1483 if (mk->mode6_handler.conf.bank_angle_enabled)
1484 handle_input_fault(mk_data(roll_angle).ncd,
1485 FaultHandler::FAULT_BANK_ANGLE_INPUTS_INVALID);
1487 if (mk->tcf_handler.conf.enabled)
1488 handle_input_fault(mk_data(radio_altitude).ncd
1489 || mk_data(geometric_altitude).ncd
1490 || mk_data(gps_latitude).ncd
1491 || mk_data(gps_longitude).ncd
1492 || mk_data(gps_vertical_figure_of_merit).ncd,
1493 FaultHandler::FAULT_TCF_INPUTS_INVALID);
1497 MK_VIII::IOHandler::update_alternate_discrete_input (bool *ptr)
1499 assert(mk->system_handler.state == SystemHandler::STATE_ON);
1501 if (ptr == &mk_dinput(mode6_low_volume))
1503 if (mk->configuration_module.state == ConfigurationModule::STATE_OK
1504 && mk->self_test_handler.state == SelfTestHandler::STATE_NONE)
1505 mk->mode6_handler.set_volume(*ptr ? modify_amplitude(1.0, -6) : 1.0);
1507 else if (ptr == &mk_dinput(audio_inhibit))
1509 if (mk->configuration_module.state == ConfigurationModule::STATE_OK
1510 && mk->self_test_handler.state == SelfTestHandler::STATE_NONE)
1511 mk->voice_player.set_volume(*ptr ? 0.0 : mk->voice_player.conf.volume);
1516 MK_VIII::IOHandler::update_internal_latches ()
1518 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
1522 if (conf.momentary_flap_override_enabled
1523 && mk_doutput(flap_override)
1524 && ! mk->state_handler.takeoff
1525 && (mk_data(radio_altitude).ncd || mk_data(radio_altitude).get() <= 50))
1526 mk_doutput(flap_override) = false;
1529 if (momentary_steep_approach_enabled())
1531 if (mk_doutput(steep_approach)
1532 && ! mk->state_handler.takeoff
1533 && ((last_landing_gear && ! mk_dinput(landing_gear))
1534 || (last_real_flaps_down && ! real_flaps_down())))
1535 // gear or flaps raised during approach: it's a go-around
1536 mk_doutput(steep_approach) = false;
1538 last_landing_gear = mk_dinput(landing_gear);
1539 last_real_flaps_down = real_flaps_down();
1543 if (mk_doutput(glideslope_cancel)
1544 && (mk_data(glideslope_deviation_dots).ncd
1545 || mk_data(radio_altitude).ncd
1546 || mk_data(radio_altitude).get() > 2000
1547 || mk_data(radio_altitude).get() < 30))
1548 mk_doutput(glideslope_cancel) = false;
1552 MK_VIII::IOHandler::update_egpws_alert_discrete_1 ()
1554 if (mk->voice_player.voice)
1559 VoicePlayer::Voice *voice;
1561 { 11, mk_voice(sink_rate_pause_sink_rate) },
1562 { 12, mk_voice(pull_up) },
1563 { 13, mk_voice(terrain) },
1564 { 13, mk_voice(terrain_pause_terrain) },
1565 { 14, mk_voice(dont_sink_pause_dont_sink) },
1566 { 15, mk_voice(too_low_gear) },
1567 { 16, mk_voice(too_low_flaps) },
1568 { 17, mk_voice(too_low_terrain) },
1569 { 18, mk_voice(soft_glideslope) },
1570 { 18, mk_voice(hard_glideslope) },
1571 { 19, mk_voice(minimums_minimums) }
1574 for (size_t i = 0; i < n_elements(voices); i++)
1575 if (voices[i].voice == mk->voice_player.voice)
1577 mk_aoutput(egpws_alert_discrete_1) = 1 << voices[i].bit;
1582 mk_aoutput(egpws_alert_discrete_1) = 0;
1586 MK_VIII::IOHandler::update_egpwc_logic_discretes ()
1588 mk_aoutput(egpwc_logic_discretes) = 0;
1590 if (mk->state_handler.takeoff)
1591 mk_aoutput(egpwc_logic_discretes) |= 1 << 18;
1596 unsigned int alerts;
1598 { 13, mk_alert(TCF_TOO_LOW_TERRAIN) },
1599 { 19, mk_alert(MODE1_SINK_RATE) },
1600 { 20, mk_alert(MODE1_PULL_UP) },
1601 { 21, mk_alert(MODE2A_PREFACE) | mk_alert(MODE2B_PREFACE) | mk_alert(MODE2B_LANDING_MODE) | mk_alert(MODE2A_ALTITUDE_GAIN_TERRAIN_CLOSING) },
1602 { 22, mk_alert(MODE2A) | mk_alert(MODE2B) },
1603 { 23, mk_alert(MODE3) },
1604 { 24, mk_alert(MODE4_TOO_LOW_FLAPS) | mk_alert(MODE4_TOO_LOW_GEAR) | mk_alert(MODE4AB_TOO_LOW_TERRAIN) | mk_alert(MODE4C_TOO_LOW_TERRAIN) },
1605 { 25, mk_alert(MODE5_SOFT) | mk_alert(MODE5_HARD) }
1608 for (size_t i = 0; i < n_elements(logic); i++)
1609 if (mk_test_alerts(logic[i].alerts))
1610 mk_aoutput(egpwc_logic_discretes) |= 1 << logic[i].bit;
1614 MK_VIII::IOHandler::update_mode6_callouts_discrete_1 ()
1616 if (mk->voice_player.voice)
1621 VoicePlayer::Voice *voice;
1623 { 11, mk_voice(minimums_minimums) },
1624 { 16, mk_altitude_voice(Mode6Handler::ALTITUDE_CALLOUT_10) },
1625 { 17, mk_altitude_voice(Mode6Handler::ALTITUDE_CALLOUT_20) },
1626 { 18, mk_altitude_voice(Mode6Handler::ALTITUDE_CALLOUT_30) },
1627 { 19, mk_altitude_voice(Mode6Handler::ALTITUDE_CALLOUT_40) },
1628 { 20, mk_altitude_voice(Mode6Handler::ALTITUDE_CALLOUT_50) },
1629 { 23, mk_altitude_voice(Mode6Handler::ALTITUDE_CALLOUT_100) },
1630 { 24, mk_altitude_voice(Mode6Handler::ALTITUDE_CALLOUT_200) },
1631 { 25, mk_altitude_voice(Mode6Handler::ALTITUDE_CALLOUT_300) }
1634 for (size_t i = 0; i < n_elements(voices); i++)
1635 if (voices[i].voice == mk->voice_player.voice)
1637 mk_aoutput(mode6_callouts_discrete_1) = 1 << voices[i].bit;
1642 mk_aoutput(mode6_callouts_discrete_1) = 0;
1646 MK_VIII::IOHandler::update_mode6_callouts_discrete_2 ()
1648 if (mk->voice_player.voice)
1653 VoicePlayer::Voice *voice;
1655 { 11, mk_altitude_voice(Mode6Handler::ALTITUDE_CALLOUT_400) },
1656 { 12, mk_altitude_voice(Mode6Handler::ALTITUDE_CALLOUT_500) },
1657 { 13, mk_altitude_voice(Mode6Handler::ALTITUDE_CALLOUT_1000) },
1658 { 18, mk_voice(bank_angle_pause_bank_angle) },
1659 { 18, mk_voice(bank_angle_pause_bank_angle_3) },
1660 { 23, mk_voice(five_hundred_above) }
1663 for (size_t i = 0; i < n_elements(voices); i++)
1664 if (voices[i].voice == mk->voice_player.voice)
1666 mk_aoutput(mode6_callouts_discrete_2) = 1 << voices[i].bit;
1671 mk_aoutput(mode6_callouts_discrete_2) = 0;
1675 MK_VIII::IOHandler::update_egpws_alert_discrete_2 ()
1677 mk_aoutput(egpws_alert_discrete_2) = 1 << 27; // Terrain NA
1679 if (mk_doutput(glideslope_cancel))
1680 mk_aoutput(egpws_alert_discrete_2) |= 1 << 11;
1681 if (mk_doutput(gpws_alert))
1682 mk_aoutput(egpws_alert_discrete_2) |= 1 << 12;
1683 if (mk_doutput(gpws_warning))
1684 mk_aoutput(egpws_alert_discrete_2) |= 1 << 13;
1685 if (mk_doutput(gpws_inop))
1686 mk_aoutput(egpws_alert_discrete_2) |= 1 << 14;
1687 if (mk_doutput(audio_on))
1688 mk_aoutput(egpws_alert_discrete_2) |= 1 << 16;
1689 if (mk_doutput(tad_inop))
1690 mk_aoutput(egpws_alert_discrete_2) |= 1 << 24;
1691 if (mk->fault_handler.has_faults())
1692 mk_aoutput(egpws_alert_discrete_2) |= 1 << 25;
1696 MK_VIII::IOHandler::update_egpwc_alert_discrete_3 ()
1698 mk_aoutput(egpwc_alert_discrete_3) = 1 << 17 | 1 << 18;
1700 if (mk->fault_handler.faults[FaultHandler::FAULT_MODES14_INPUTS_INVALID])
1701 mk_aoutput(egpwc_alert_discrete_3) |= 1 << 11;
1702 if (mk->fault_handler.faults[FaultHandler::FAULT_MODE5_INPUTS_INVALID])
1703 mk_aoutput(egpwc_alert_discrete_3) |= 1 << 12;
1704 if (mk->fault_handler.faults[FaultHandler::FAULT_MODE6_INPUTS_INVALID])
1705 mk_aoutput(egpwc_alert_discrete_3) |= 1 << 13;
1706 if (mk->fault_handler.faults[FaultHandler::FAULT_BANK_ANGLE_INPUTS_INVALID])
1707 mk_aoutput(egpwc_alert_discrete_3) |= 1 << 14;
1708 if (mk_doutput(tad_inop))
1709 mk_aoutput(egpwc_alert_discrete_3) |= 1 << 16;
1713 MK_VIII::IOHandler::update_outputs ()
1715 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
1718 mk_doutput(audio_on) = ! mk_dinput(audio_inhibit)
1719 && mk->voice_player.voice
1720 && ! mk->voice_player.voice->element->silence;
1722 update_egpws_alert_discrete_1();
1723 update_egpwc_logic_discretes();
1724 update_mode6_callouts_discrete_1();
1725 update_mode6_callouts_discrete_2();
1726 update_egpws_alert_discrete_2();
1727 update_egpwc_alert_discrete_3();
1731 MK_VIII::IOHandler::get_lamp_output (Lamp lamp)
1735 case LAMP_GLIDESLOPE:
1736 return &mk_doutput(gpws_alert);
1739 return conf.lamp->format2 ? &mk_doutput(gpws_alert) : &mk_doutput(gpws_warning);
1742 return &mk_doutput(gpws_warning);
1745 assert_not_reached();
1751 MK_VIII::IOHandler::update_lamps ()
1753 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
1756 if (_lamp != LAMP_NONE && conf.lamp->flashing)
1758 // [SPEC] 6.9.3: 70 cycles per minute
1759 if (lamp_timer.elapsed() >= 60.0 / 70.0 / 2.0)
1761 bool *output = get_lamp_output(_lamp);
1762 *output = ! *output;
1769 MK_VIII::IOHandler::set_lamp (Lamp lamp)
1776 mk_doutput(gpws_warning) = false;
1777 mk_doutput(gpws_alert) = false;
1779 if (lamp != LAMP_NONE)
1781 *get_lamp_output(lamp) = true;
1787 MK_VIII::IOHandler::gpws_inhibit () const
1789 return conf.gpws_inhibit_enabled && mk_dinput(gpws_inhibit);
1793 MK_VIII::IOHandler::real_flaps_down () const
1795 return conf.flap_reversal ? mk_dinput(landing_flaps) : ! mk_dinput(landing_flaps);
1799 MK_VIII::IOHandler::flaps_down () const
1801 return flap_override() ? true : real_flaps_down();
1805 MK_VIII::IOHandler::flap_override () const
1807 return conf.momentary_flap_override_enabled ? mk_doutput(flap_override) : false;
1811 MK_VIII::IOHandler::steep_approach () const
1813 if (conf.steep_approach_enabled)
1814 // If alternate action was configured in category 13, we return
1815 // the value of the input discrete (which should be connected to a
1816 // latching steep approach cockpit switch). Otherwise, we return
1817 // the value of the output discrete.
1818 return conf.alternate_steep_approach ? mk_dinput(steep_approach) : mk_doutput(steep_approach);
1824 MK_VIII::IOHandler::momentary_steep_approach_enabled () const
1826 return conf.steep_approach_enabled && ! conf.alternate_steep_approach;
1830 MK_VIII::IOHandler::tie_input (SGPropertyNode *node,
1835 mk->properties_handler.tie(node, (string("inputs/discretes/") + name).c_str(), RawValueMethodsData<MK_VIII::IOHandler, bool, bool *>(*this, input, &MK_VIII::IOHandler::get_discrete_input, &MK_VIII::IOHandler::set_discrete_input));
1837 mk->properties_handler.tie(node, (string("input-feeders/discretes/") + name).c_str(), SGRawValuePointer<bool>(feed));
1841 MK_VIII::IOHandler::tie_input (SGPropertyNode *node,
1843 Parameter<double> *input,
1846 mk->properties_handler.tie(node, (string("inputs/arinc429/") + name).c_str(), SGRawValuePointer<double>(input->get_pointer()));
1847 mk->properties_handler.tie(node, (string("inputs/arinc429/") + name + "-ncd").c_str(), SGRawValuePointer<bool>(&input->ncd));
1849 mk->properties_handler.tie(node, (string("input-feeders/arinc429/") + name).c_str(), SGRawValuePointer<bool>(feed));
1853 MK_VIII::IOHandler::tie_output (SGPropertyNode *node,
1857 SGPropertyNode *child = node->getNode((string("outputs/discretes/") + name).c_str(), true);
1859 mk->properties_handler.tie(child, SGRawValuePointer<bool>(output));
1860 child->setAttribute(SGPropertyNode::WRITE, false);
1864 MK_VIII::IOHandler::tie_output (SGPropertyNode *node,
1868 SGPropertyNode *child = node->getNode((string("outputs/arinc429/") + name).c_str(), true);
1870 mk->properties_handler.tie(child, SGRawValuePointer<int>(output));
1871 child->setAttribute(SGPropertyNode::WRITE, false);
1875 MK_VIII::IOHandler::bind (SGPropertyNode *node)
1877 mk->properties_handler.tie(node, "inputs/rs-232/present-status", SGRawValueMethods<MK_VIII::IOHandler, bool>(*this, &MK_VIII::IOHandler::get_present_status, &MK_VIII::IOHandler::set_present_status));
1879 tie_input(node, "landing-gear", &mk_dinput(landing_gear), &mk_dinput_feed(landing_gear));
1880 tie_input(node, "landing-flaps", &mk_dinput(landing_flaps), &mk_dinput_feed(landing_flaps));
1881 tie_input(node, "momentary-flap-override", &mk_dinput(momentary_flap_override));
1882 tie_input(node, "self-test", &mk_dinput(self_test));
1883 tie_input(node, "glideslope-inhibit", &mk_dinput(glideslope_inhibit), &mk_dinput_feed(glideslope_inhibit));
1884 tie_input(node, "glideslope-cancel", &mk_dinput(glideslope_cancel));
1885 tie_input(node, "decision-height", &mk_dinput(decision_height), &mk_dinput_feed(decision_height));
1886 tie_input(node, "mode6-low-volume", &mk_dinput(mode6_low_volume));
1887 tie_input(node, "audio-inhibit", &mk_dinput(audio_inhibit));
1888 tie_input(node, "ta-tcf-inhibit", &mk_dinput(ta_tcf_inhibit));
1889 tie_input(node, "autopilot-engaged", &mk_dinput(autopilot_engaged), &mk_dinput_feed(autopilot_engaged));
1890 tie_input(node, "steep-approach", &mk_dinput(steep_approach));
1891 tie_input(node, "gpws-inhibit", &mk_dinput(gpws_inhibit));
1893 tie_input(node, "uncorrected-barometric-altitude", &mk_ainput(uncorrected_barometric_altitude), &mk_ainput_feed(uncorrected_barometric_altitude));
1894 tie_input(node, "barometric-altitude-rate", &mk_ainput(barometric_altitude_rate), &mk_ainput_feed(barometric_altitude_rate));
1895 tie_input(node, "gps-altitude", &mk_ainput(gps_altitude));
1896 tie_input(node, "gps-latitude", &mk_ainput(gps_latitude));
1897 tie_input(node, "gps-longitude", &mk_ainput(gps_longitude));
1898 tie_input(node, "gps-vertical-figure-of-merit", &mk_ainput(gps_vertical_figure_of_merit));
1899 tie_input(node, "radio-altitude", &mk_ainput(radio_altitude), &mk_ainput_feed(radio_altitude));
1900 tie_input(node, "glideslope-deviation", &mk_ainput(glideslope_deviation), &mk_ainput_feed(glideslope_deviation));
1901 tie_input(node, "roll-angle", &mk_ainput(roll_angle), &mk_ainput_feed(roll_angle));
1902 tie_input(node, "localizer-deviation", &mk_ainput(localizer_deviation), &mk_ainput_feed(localizer_deviation));
1903 tie_input(node, "computed-airspeed", &mk_ainput(computed_airspeed), &mk_ainput_feed(computed_airspeed));
1904 tie_input(node, "decision-height", &mk_ainput(decision_height), &mk_ainput_feed(decision_height));
1906 tie_output(node, "gpws-warning", &mk_doutput(gpws_warning));
1907 tie_output(node, "gpws-alert", &mk_doutput(gpws_alert));
1908 tie_output(node, "audio-on", &mk_doutput(audio_on));
1909 tie_output(node, "gpws-inop", &mk_doutput(gpws_inop));
1910 tie_output(node, "tad-inop", &mk_doutput(tad_inop));
1911 tie_output(node, "flap-override", &mk_doutput(flap_override));
1912 tie_output(node, "glideslope-cancel", &mk_doutput(glideslope_cancel));
1913 tie_output(node, "steep-approach", &mk_doutput(steep_approach));
1915 tie_output(node, "egpws-alert-discrete-1", &mk_aoutput(egpws_alert_discrete_1));
1916 tie_output(node, "egpwc-logic-discretes", &mk_aoutput(egpwc_logic_discretes));
1917 tie_output(node, "mode6-callouts-discrete-1", &mk_aoutput(mode6_callouts_discrete_1));
1918 tie_output(node, "mode6-callouts-discrete-2", &mk_aoutput(mode6_callouts_discrete_2));
1919 tie_output(node, "egpws-alert-discrete-2", &mk_aoutput(egpws_alert_discrete_2));
1920 tie_output(node, "egpwc-alert-discrete-3", &mk_aoutput(egpwc_alert_discrete_3));
1924 MK_VIII::IOHandler::get_discrete_input (bool *ptr) const
1930 MK_VIII::IOHandler::set_discrete_input (bool *ptr, bool value)
1935 if (mk->system_handler.state == SystemHandler::STATE_ON)
1937 if (ptr == &mk_dinput(momentary_flap_override))
1939 if (mk->configuration_module.state == ConfigurationModule::STATE_OK
1940 && mk->self_test_handler.state == SelfTestHandler::STATE_NONE
1941 && conf.momentary_flap_override_enabled
1943 mk_doutput(flap_override) = ! mk_doutput(flap_override);
1945 else if (ptr == &mk_dinput(self_test))
1946 mk->self_test_handler.handle_button_event(value);
1947 else if (ptr == &mk_dinput(glideslope_cancel))
1949 if (mk->configuration_module.state == ConfigurationModule::STATE_OK
1950 && mk->self_test_handler.state == SelfTestHandler::STATE_NONE
1953 if (! mk_doutput(glideslope_cancel))
1957 // Although we are not called from update(), we are
1958 // sure that the inputs we use here are defined,
1959 // since state is STATE_ON.
1961 if (! mk_data(glideslope_deviation_dots).ncd
1962 && ! mk_data(radio_altitude).ncd
1963 && mk_data(radio_altitude).get() <= 2000
1964 && mk_data(radio_altitude).get() >= 30)
1965 mk_doutput(glideslope_cancel) = true;
1967 // else do nothing ([PILOT] page 22: "Glideslope Cancel
1968 // can not be deselected (reset) by again pressing the
1969 // Glideslope Cancel switch.")
1972 else if (ptr == &mk_dinput(steep_approach))
1974 if (mk->configuration_module.state == ConfigurationModule::STATE_OK
1975 && mk->self_test_handler.state == SelfTestHandler::STATE_NONE
1976 && momentary_steep_approach_enabled()
1978 mk_doutput(steep_approach) = ! mk_doutput(steep_approach);
1984 if (mk->system_handler.state == SystemHandler::STATE_ON)
1985 update_alternate_discrete_input(ptr);
1989 MK_VIII::IOHandler::present_status_section (const char *name)
1991 printf("%s\n", name);
1995 MK_VIII::IOHandler::present_status_item (const char *name, const char *value)
1998 printf("\t%-32s %s\n", name, value);
2000 printf("\t%s\n", name);
2004 MK_VIII::IOHandler::present_status_subitem (const char *name)
2006 printf("\t\t%s\n", name);
2010 MK_VIII::IOHandler::present_status ()
2014 if (mk->system_handler.state != SystemHandler::STATE_ON)
2017 present_status_section("EGPWC CONFIGURATION");
2018 present_status_item("PART NUMBER:", "965-1220-000"); // [SPEC] 1.1
2019 present_status_item("MOD STATUS:", "N/A");
2020 present_status_item("SERIAL NUMBER:", "N/A");
2022 present_status_item("APPLICATION S/W VERSION:", FLIGHTGEAR_VERSION);
2023 present_status_item("TERRAIN DATABASE VERSION:", FLIGHTGEAR_VERSION);
2024 present_status_item("ENVELOPE MOD DATABASE VERSION:", "N/A");
2025 present_status_item("BOOT CODE VERSION:", FLIGHTGEAR_VERSION);
2028 present_status_section("CURRENT FAULTS");
2029 if (mk->configuration_module.state == ConfigurationModule::STATE_OK && ! mk->fault_handler.has_faults())
2030 present_status_item("NO FAULTS");
2033 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
2035 present_status_item("GPWS COMPUTER FAULTS:");
2036 switch (mk->configuration_module.state)
2038 case ConfigurationModule::STATE_INVALID_DATABASE:
2039 present_status_subitem("APPLICATION DATABASE FAILED");
2042 case ConfigurationModule::STATE_INVALID_AIRCRAFT_TYPE:
2043 present_status_subitem("CONFIGURATION TYPE INVALID");
2047 assert_not_reached();
2053 present_status_item("GPWS COMPUTER OK");
2054 present_status_item("GPWS EXTERNAL FAULTS:");
2056 static const char *fault_names[] = {
2057 "ALL MODES INHIBIT",
2060 "MOMENTARY FLAP OVERRIDE INVALID",
2061 "SELF TEST INVALID",
2062 "GLIDESLOPE CANCEL INVALID",
2063 "STEEP APPROACH INVALID",
2066 "MODES 1-4 INPUTS INVALID",
2067 "MODE 5 INPUTS INVALID",
2068 "MODE 6 INPUTS INVALID",
2069 "BANK ANGLE INPUTS INVALID",
2070 "TCF INPUTS INVALID"
2073 for (size_t i = 0; i < n_elements(fault_names); i++)
2074 if (mk->fault_handler.faults[i])
2075 present_status_subitem(fault_names[i]);
2080 present_status_section("CONFIGURATION:");
2082 static const char *category_names[] = {
2085 "POSITION INPUT TYPE",
2086 "CALLOUTS OPTION TYPE",
2088 "TERRAIN DISPLAY TYPE",
2090 "RADIO ALTITUDE TYPE",
2091 "NAVIGATION INPUT TYPE",
2093 "MAGNETIC HEADING TYPE",
2094 "WINDSHEAR INPUT TYPE",
2099 for (size_t i = 0; i < n_elements(category_names); i++)
2101 std::ostringstream value;
2102 value << "= " << mk->configuration_module.effective_categories[i];
2103 present_status_item(category_names[i], value.str().c_str());
2108 MK_VIII::IOHandler::get_present_status () const
2114 MK_VIII::IOHandler::set_present_status (bool value)
2116 if (value) present_status();
2120 ///////////////////////////////////////////////////////////////////////////////
2121 // VoicePlayer ////////////////////////////////////////////////////////////////
2122 ///////////////////////////////////////////////////////////////////////////////
2125 MK_VIII::VoicePlayer::Speaker::bind (SGPropertyNode *node)
2127 // uses xmlsound property names
2128 tie(node, "volume", &volume);
2129 tie(node, "pitch", &pitch);
2133 MK_VIII::VoicePlayer::Speaker::update_configuration ()
2135 map< string, SGSharedPtr<SGSoundSample> >::iterator iter;
2136 for (iter = player->samples.begin(); iter != player->samples.end(); iter++)
2138 SGSoundSample *sample = (*iter).second;
2140 sample->set_pitch(pitch);
2144 player->voice->volume_changed();
2147 MK_VIII::VoicePlayer::Voice::~Voice ()
2149 for (iter = elements.begin(); iter != elements.end(); iter++)
2150 delete *iter; // we owned the element
2155 MK_VIII::VoicePlayer::Voice::play ()
2157 iter = elements.begin();
2160 element->play(get_volume());
2164 MK_VIII::VoicePlayer::Voice::stop (bool now)
2168 if (now || element->silence)
2174 iter = elements.end() - 1; // stop after the current element finishes
2179 MK_VIII::VoicePlayer::Voice::set_volume (float _volume)
2186 MK_VIII::VoicePlayer::Voice::volume_changed ()
2189 element->set_volume(get_volume());
2193 MK_VIII::VoicePlayer::Voice::update ()
2197 if (! element->is_playing())
2199 if (++iter == elements.end())
2204 element->play(get_volume());
2210 MK_VIII::VoicePlayer::~VoicePlayer ()
2212 vector<Voice *>::iterator iter1;
2213 for (iter1 = _voices.begin(); iter1 != _voices.end(); iter1++)
2220 MK_VIII::VoicePlayer::init ()
2222 #define STDPAUSE 0.75 // [SPEC] 6.4.4: "the standard 0.75 second delay"
2224 SGSoundMgr *smgr = globals->get_soundmgr();
2225 _sgr = smgr->find("avionics", true);
2226 _sgr->tie_to_listener();
2228 make_voice(&voices.application_data_base_failed, "application-data-base-failed");
2229 make_voice(&voices.bank_angle, "bank-angle");
2230 make_voice(&voices.bank_angle_bank_angle, "bank-angle", "bank-angle");
2231 make_voice(&voices.bank_angle_bank_angle_3, "bank-angle", "bank-angle", 3.0);
2232 make_voice(&voices.bank_angle_inop, "bank-angle-inop");
2233 make_voice(&voices.bank_angle_pause_bank_angle, "bank-angle", STDPAUSE, "bank-angle");
2234 make_voice(&voices.bank_angle_pause_bank_angle_3, "bank-angle", STDPAUSE, "bank-angle", 3.0);
2235 make_voice(&voices.callouts_inop, "callouts-inop");
2236 make_voice(&voices.configuration_type_invalid, "configuration-type-invalid");
2237 make_voice(&voices.dont_sink, "dont-sink");
2238 make_voice(&voices.dont_sink_pause_dont_sink, "dont-sink", STDPAUSE, "dont-sink");
2239 make_voice(&voices.five_hundred_above, "500-above");
2240 make_voice(&voices.glideslope, "glideslope");
2241 make_voice(&voices.glideslope_inop, "glideslope-inop");
2242 make_voice(&voices.gpws_inop, "gpws-inop");
2243 make_voice(&voices.hard_glideslope, "glideslope", "glideslope", 3.0);
2244 make_voice(&voices.minimums, "minimums");
2245 make_voice(&voices.minimums_minimums, "minimums", "minimums");
2246 make_voice(&voices.pull_up, "pull-up");
2247 make_voice(&voices.sink_rate, "sink-rate");
2248 make_voice(&voices.sink_rate_pause_sink_rate, "sink-rate", STDPAUSE, "sink-rate");
2249 make_voice(&voices.soft_glideslope, new Voice::SampleElement(get_sample("glideslope"), modify_amplitude(1.0, -6)));
2250 make_voice(&voices.terrain, "terrain");
2251 make_voice(&voices.terrain_pause_terrain, "terrain", STDPAUSE, "terrain");
2252 make_voice(&voices.too_low_flaps, "too-low-flaps");
2253 make_voice(&voices.too_low_gear, "too-low-gear");
2254 make_voice(&voices.too_low_terrain, "too-low-terrain");
2256 for (unsigned i = 0; i < n_altitude_callouts; i++)
2258 std::ostringstream name;
2259 name << "altitude-" << mk->mode6_handler.altitude_callout_definitions[i];
2260 make_voice(&voices.altitude_callouts[i], name.str().c_str());
2263 speaker.update_configuration();
2267 MK_VIII::VoicePlayer::get_sample (const char *name)
2269 std::ostringstream refname;
2270 refname << mk->name << "[" << mk->num << "]" << "/" << name;
2272 SGSoundSample *sample = _sgr->find(refname.str());
2275 string filename = "Sounds/mk-viii" + string(name) + ".wav";
2278 sample = new SGSoundSample(filename.c_str(), SGPath());
2280 catch (const sg_exception &e)
2282 SG_LOG(SG_INSTR, SG_ALERT, "Error loading MK VIII sound sample \"" + filename + "\": " + e.getFormattedMessage());
2286 _sgr->add(sample, refname.str());
2287 samples[refname.str()] = sample;
2294 MK_VIII::VoicePlayer::play (Voice *_voice, unsigned int flags)
2296 if (test_bits(flags, PLAY_NOW) || ! voice || voice->element->silence)
2302 looped = test_bits(flags, PLAY_LOOPED);
2305 next_looped = false;
2311 next_voice = _voice;
2312 next_looped = test_bits(flags, PLAY_LOOPED);
2317 MK_VIII::VoicePlayer::stop (unsigned int flags)
2321 voice->stop(test_bits(flags, STOP_NOW));
2331 MK_VIII::VoicePlayer::set_volume (float _volume)
2335 voice->volume_changed();
2339 MK_VIII::VoicePlayer::update ()
2347 if (! voice->element || voice->element->silence)
2350 looped = next_looped;
2353 next_looped = false;
2360 if (! voice->element)
2371 ///////////////////////////////////////////////////////////////////////////////
2372 // SelfTestHandler ////////////////////////////////////////////////////////////
2373 ///////////////////////////////////////////////////////////////////////////////
2376 MK_VIII::SelfTestHandler::_was_here (int position)
2378 if (position > current)
2387 MK_VIII::SelfTestHandler::Action
2388 MK_VIII::SelfTestHandler::sleep (double duration)
2390 Action _action = { ACTION_SLEEP, duration, NULL };
2394 MK_VIII::SelfTestHandler::Action
2395 MK_VIII::SelfTestHandler::play (VoicePlayer::Voice *voice)
2397 mk->voice_player.play(voice);
2398 Action _action = { ACTION_VOICE, 0, NULL };
2402 MK_VIII::SelfTestHandler::Action
2403 MK_VIII::SelfTestHandler::discrete_on (bool *discrete, double duration)
2406 return sleep(duration);
2409 MK_VIII::SelfTestHandler::Action
2410 MK_VIII::SelfTestHandler::discrete_on_off (bool *discrete, double duration)
2413 Action _action = { ACTION_SLEEP | ACTION_DISCRETE_ON_OFF, duration, discrete };
2417 MK_VIII::SelfTestHandler::Action
2418 MK_VIII::SelfTestHandler::discrete_on_off (bool *discrete, VoicePlayer::Voice *voice)
2421 mk->voice_player.play(voice);
2422 Action _action = { ACTION_VOICE | ACTION_DISCRETE_ON_OFF, 0, discrete };
2426 MK_VIII::SelfTestHandler::Action
2427 MK_VIII::SelfTestHandler::done ()
2429 Action _action = { ACTION_DONE, 0, NULL };
2433 MK_VIII::SelfTestHandler::Action
2434 MK_VIII::SelfTestHandler::run ()
2436 // Note that "Terrain INOP" and "Terrain NA" are or'ed to the same
2437 // output discrete (see [SPEC] 6.9.3.5).
2439 #define was_here() was_here_offset(0)
2440 #define was_here_offset(offset) _was_here(__LINE__ * 20 + (offset))
2444 if (mk->configuration_module.state == ConfigurationModule::STATE_INVALID_DATABASE)
2445 return play(mk_voice(application_data_base_failed));
2446 else if (mk->configuration_module.state == ConfigurationModule::STATE_INVALID_AIRCRAFT_TYPE)
2447 return play(mk_voice(configuration_type_invalid));
2449 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
2453 return discrete_on(&mk_doutput(gpws_inop), 0.7);
2455 return sleep(0.7); // W/S INOP
2457 return discrete_on(&mk_doutput(tad_inop), 0.7);
2460 mk_doutput(gpws_inop) = false;
2461 // do not disable tad_inop since we must enable Terrain NA
2462 // do not disable W/S INOP since we do not emulate it
2463 return sleep(0.7); // Terrain NA
2467 mk_doutput(tad_inop) = false; // disable Terrain NA
2468 if (mk->io_handler.conf.momentary_flap_override_enabled)
2469 return discrete_on_off(&mk_doutput(flap_override), 1.0);
2472 return discrete_on_off(&mk_doutput(audio_on), 1.0);
2475 if (mk->io_handler.momentary_steep_approach_enabled())
2476 return discrete_on_off(&mk_doutput(steep_approach), 1.0);
2481 if (mk_dinput(glideslope_inhibit))
2482 goto glideslope_end;
2485 if (mk->fault_handler.faults[FaultHandler::FAULT_MODE5_INPUTS_INVALID])
2486 goto glideslope_inop;
2490 return discrete_on_off(&mk_doutput(gpws_alert), mk_voice(glideslope));
2492 return discrete_on_off(&mk_doutput(glideslope_cancel), 0.7);
2493 goto glideslope_end;
2496 return play(mk_voice(glideslope_inop));
2501 if (mk->fault_handler.faults[FaultHandler::FAULT_MODES14_INPUTS_INVALID])
2505 return discrete_on_off(&mk_doutput(gpws_warning), mk_voice(pull_up));
2509 return play(mk_voice(gpws_inop));
2514 if (mk_dinput(self_test)) // proceed to long self test
2519 if (mk->mode6_handler.conf.bank_angle_enabled
2520 && mk->fault_handler.faults[FaultHandler::FAULT_BANK_ANGLE_INPUTS_INVALID])
2521 return play(mk_voice(bank_angle_inop));
2525 if (mk->mode6_handler.altitude_callouts_enabled()
2526 && mk->fault_handler.faults[FaultHandler::FAULT_MODE6_INPUTS_INVALID])
2527 return play(mk_voice(callouts_inop));
2535 mk_doutput(gpws_inop) = true;
2536 // do not enable W/S INOP, since we do not emulate it
2537 mk_doutput(tad_inop) = true; // Terrain INOP, Terrain NA
2539 return play(mk_voice(sink_rate));
2542 return play(mk_voice(pull_up));
2544 return play(mk_voice(terrain));
2546 return play(mk_voice(pull_up));
2548 return play(mk_voice(dont_sink));
2550 return play(mk_voice(too_low_terrain));
2552 return play(mk->mode4_handler.conf.voice_too_low_gear);
2554 return play(mk_voice(too_low_flaps));
2556 return play(mk_voice(too_low_terrain));
2558 return play(mk_voice(glideslope));
2561 if (mk->mode6_handler.conf.bank_angle_enabled)
2562 return play(mk_voice(bank_angle));
2567 if (! mk->mode6_handler.altitude_callouts_enabled())
2568 goto callouts_disabled;
2572 if (mk->mode6_handler.conf.minimums_enabled)
2573 return play(mk_voice(minimums));
2577 if (mk->mode6_handler.conf.above_field_voice)
2578 return play(mk->mode6_handler.conf.above_field_voice);
2580 for (unsigned i = 0; i < n_altitude_callouts; i++)
2581 if (! was_here_offset(i))
2583 if (mk->mode6_handler.conf.altitude_callouts_enabled[i])
2584 return play(mk_altitude_voice(i));
2588 if (mk->mode6_handler.conf.smart_500_enabled)
2589 return play(mk_altitude_voice(Mode6Handler::ALTITUDE_CALLOUT_500));
2594 return play(mk_voice(minimums_minimums));
2599 if (mk->tcf_handler.conf.enabled)
2600 return play(mk_voice(too_low_terrain));
2607 MK_VIII::SelfTestHandler::start ()
2609 assert(state == STATE_START);
2611 memcpy(&saved_outputs, &mk->io_handler.outputs, sizeof(mk->io_handler.outputs));
2612 memset(&mk->io_handler.outputs, 0, sizeof(mk->io_handler.outputs));
2614 // [SPEC] 6.10.6: "The self-test results are annunciated, at 6db
2615 // lower than the normal audio level selected for the aircraft"
2616 mk->voice_player.set_volume(modify_amplitude(mk->voice_player.conf.volume, -6));
2618 if (mk_dinput(mode6_low_volume))
2619 mk->mode6_handler.set_volume(1.0);
2621 state = STATE_RUNNING;
2622 cancel = CANCEL_NONE;
2623 memset(&action, 0, sizeof(action));
2628 MK_VIII::SelfTestHandler::stop ()
2630 if (state != STATE_NONE)
2632 if (state != STATE_START)
2634 mk->voice_player.stop(VoicePlayer::STOP_NOW);
2635 mk->voice_player.set_volume(mk_dinput(audio_inhibit) ? 0.0 : mk->voice_player.conf.volume);
2637 if (mk_dinput(mode6_low_volume))
2638 mk->mode6_handler.set_volume(modify_amplitude(1.0, -6));
2640 memcpy(&mk->io_handler.outputs, &saved_outputs, sizeof(mk->io_handler.outputs));
2643 button_pressed = false;
2645 // reset self-test handler position
2651 MK_VIII::SelfTestHandler::handle_button_event (bool value)
2653 if (state == STATE_NONE)
2656 state = STATE_START;
2658 else if (state == STATE_START)
2661 state = STATE_NONE; // cancel the not-yet-started test
2663 else if (cancel == CANCEL_NONE)
2667 assert(! button_pressed);
2668 button_pressed = true;
2669 button_press_timestamp = globals->get_sim_time_sec();
2675 if (globals->get_sim_time_sec() - button_press_timestamp < 2)
2676 cancel = CANCEL_SHORT;
2678 cancel = CANCEL_LONG;
2685 MK_VIII::SelfTestHandler::update ()
2687 if (state == STATE_NONE)
2689 else if (state == STATE_START)
2691 if (mk->state_handler.ground && ! mk->io_handler.steep_approach())
2701 if (button_pressed && cancel == CANCEL_NONE)
2703 if (globals->get_sim_time_sec() - button_press_timestamp >= 2)
2704 cancel = CANCEL_LONG;
2708 if (! mk->state_handler.ground || cancel != CANCEL_NONE)
2714 if (test_bits(action.flags, ACTION_SLEEP))
2716 if (globals->get_sim_time_sec() - sleep_start < action.sleep_duration)
2719 if (test_bits(action.flags, ACTION_VOICE))
2721 if (mk->voice_player.voice)
2724 if (test_bits(action.flags, ACTION_DISCRETE_ON_OFF))
2725 *action.discrete = false;
2729 if (test_bits(action.flags, ACTION_SLEEP))
2730 sleep_start = globals->get_sim_time_sec();
2731 if (test_bits(action.flags, ACTION_DONE))
2740 ///////////////////////////////////////////////////////////////////////////////
2741 // AlertHandler ///////////////////////////////////////////////////////////////
2742 ///////////////////////////////////////////////////////////////////////////////
2745 MK_VIII::AlertHandler::select_voice_alerts (unsigned int test)
2747 if (has_alerts(test))
2749 voice_alerts = alerts & test;
2754 voice_alerts &= ~test;
2755 if (voice_alerts == 0)
2756 mk->voice_player.stop();
2763 MK_VIII::AlertHandler::boot ()
2769 MK_VIII::AlertHandler::reposition ()
2773 mk->io_handler.set_lamp(IOHandler::LAMP_NONE);
2774 mk->voice_player.stop(VoicePlayer::STOP_NOW);
2778 MK_VIII::AlertHandler::reset ()
2783 repeated_alerts = 0;
2784 altitude_callout_voice = NULL;
2788 MK_VIII::AlertHandler::update ()
2790 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
2793 // Lamps and voices are prioritized according to [SPEC] 6.9.2.
2795 // Voices which can interrupt other voices (VoicePlayer::PLAY_NOW)
2796 // are specified by [INSTALL] appendix E 5.3.5.
2798 // When a voice is overriden by a higher priority voice and the
2799 // overriding voice stops, we restore the previous voice if it was
2800 // looped (this is not specified by [SPEC] but seems to make sense).
2804 if (has_alerts(ALERT_MODE1_PULL_UP | ALERT_MODE2A | ALERT_MODE2B))
2805 mk->io_handler.set_lamp(IOHandler::LAMP_WARNING);
2806 else if (has_alerts(ALERT_MODE1_SINK_RATE
2807 | ALERT_MODE2A_PREFACE
2808 | ALERT_MODE2B_PREFACE
2809 | ALERT_MODE2A_ALTITUDE_GAIN_TERRAIN_CLOSING
2810 | ALERT_MODE2A_ALTITUDE_GAIN
2811 | ALERT_MODE2B_LANDING_MODE
2813 | ALERT_MODE4_TOO_LOW_FLAPS
2814 | ALERT_MODE4_TOO_LOW_GEAR
2815 | ALERT_MODE4AB_TOO_LOW_TERRAIN
2816 | ALERT_MODE4C_TOO_LOW_TERRAIN
2817 | ALERT_TCF_TOO_LOW_TERRAIN))
2818 mk->io_handler.set_lamp(IOHandler::LAMP_CAUTION);
2819 else if (has_alerts(ALERT_MODE5_SOFT | ALERT_MODE5_HARD))
2820 mk->io_handler.set_lamp(IOHandler::LAMP_GLIDESLOPE);
2822 mk->io_handler.set_lamp(IOHandler::LAMP_NONE);
2826 if (select_voice_alerts(ALERT_MODE1_PULL_UP))
2828 if (! has_old_alerts(ALERT_MODE1_PULL_UP))
2830 if (mk->voice_player.voice != mk_voice(sink_rate_pause_sink_rate))
2831 mk->voice_player.play(mk_voice(sink_rate), VoicePlayer::PLAY_NOW);
2832 mk->voice_player.play(mk_voice(pull_up), VoicePlayer::PLAY_LOOPED);
2835 else if (select_voice_alerts(ALERT_MODE2A_PREFACE | ALERT_MODE2B_PREFACE))
2837 if (! has_old_alerts(ALERT_MODE2A_PREFACE | ALERT_MODE2B_PREFACE))
2838 mk->voice_player.play(mk_voice(terrain_pause_terrain), VoicePlayer::PLAY_NOW);
2840 else if (select_voice_alerts(ALERT_MODE2A | ALERT_MODE2B))
2842 if (mk->voice_player.voice != mk_voice(pull_up))
2843 mk->voice_player.play(mk_voice(pull_up), VoicePlayer::PLAY_NOW | VoicePlayer::PLAY_LOOPED);
2845 else if (select_voice_alerts(ALERT_MODE2A_ALTITUDE_GAIN_TERRAIN_CLOSING | ALERT_MODE2B_LANDING_MODE))
2847 if (mk->voice_player.voice != mk_voice(terrain))
2848 mk->voice_player.play(mk_voice(terrain), VoicePlayer::PLAY_LOOPED);
2850 else if (select_voice_alerts(ALERT_MODE6_MINIMUMS))
2852 if (! has_old_alerts(ALERT_MODE6_MINIMUMS))
2853 mk->voice_player.play(mk_voice(minimums_minimums));
2855 else if (select_voice_alerts(ALERT_MODE4AB_TOO_LOW_TERRAIN | ALERT_MODE4C_TOO_LOW_TERRAIN))
2857 if (must_play_voice(ALERT_MODE4AB_TOO_LOW_TERRAIN | ALERT_MODE4C_TOO_LOW_TERRAIN))
2858 mk->voice_player.play(mk_voice(too_low_terrain));
2860 else if (select_voice_alerts(ALERT_TCF_TOO_LOW_TERRAIN))
2862 if (must_play_voice(ALERT_TCF_TOO_LOW_TERRAIN))
2863 mk->voice_player.play(mk_voice(too_low_terrain));
2865 else if (select_voice_alerts(ALERT_MODE6_ALTITUDE_CALLOUT))
2867 if (! has_old_alerts(ALERT_MODE6_ALTITUDE_CALLOUT))
2869 assert(altitude_callout_voice != NULL);
2870 mk->voice_player.play(altitude_callout_voice);
2873 else if (select_voice_alerts(ALERT_MODE4_TOO_LOW_GEAR))
2875 if (must_play_voice(ALERT_MODE4_TOO_LOW_GEAR))
2876 mk->voice_player.play(mk->mode4_handler.conf.voice_too_low_gear);
2878 else if (select_voice_alerts(ALERT_MODE4_TOO_LOW_FLAPS))
2880 if (must_play_voice(ALERT_MODE4_TOO_LOW_FLAPS))
2881 mk->voice_player.play(mk_voice(too_low_flaps));
2883 else if (select_voice_alerts(ALERT_MODE1_SINK_RATE))
2885 if (must_play_voice(ALERT_MODE1_SINK_RATE))
2886 mk->voice_player.play(mk_voice(sink_rate_pause_sink_rate));
2887 // [SPEC] 6.2.1: "During the time that the voice message for the
2888 // outer envelope is inhibited and the caution/warning lamp is
2889 // on, the Mode 5 alert message is allowed to annunciate for
2890 // excessive glideslope deviation conditions."
2891 else if (mk->voice_player.voice != mk_voice(sink_rate_pause_sink_rate)
2892 && mk->voice_player.next_voice != mk_voice(sink_rate_pause_sink_rate))
2894 if (has_alerts(ALERT_MODE5_HARD))
2896 voice_alerts |= ALERT_MODE5_HARD;
2897 if (mk->voice_player.voice != mk_voice(hard_glideslope))
2898 mk->voice_player.play(mk_voice(hard_glideslope), VoicePlayer::PLAY_LOOPED);
2900 else if (has_alerts(ALERT_MODE5_SOFT))
2902 voice_alerts |= ALERT_MODE5_SOFT;
2903 if (must_play_voice(ALERT_MODE5_SOFT))
2904 mk->voice_player.play(mk_voice(soft_glideslope));
2908 else if (select_voice_alerts(ALERT_MODE3))
2910 if (must_play_voice(ALERT_MODE3))
2911 mk->voice_player.play(mk_voice(dont_sink_pause_dont_sink));
2913 else if (select_voice_alerts(ALERT_MODE5_HARD))
2915 if (mk->voice_player.voice != mk_voice(hard_glideslope))
2916 mk->voice_player.play(mk_voice(hard_glideslope), VoicePlayer::PLAY_LOOPED);
2918 else if (select_voice_alerts(ALERT_MODE5_SOFT))
2920 if (must_play_voice(ALERT_MODE5_SOFT))
2921 mk->voice_player.play(mk_voice(soft_glideslope));
2923 else if (select_voice_alerts(ALERT_MODE6_LOW_BANK_ANGLE_3))
2925 if (mk->voice_player.voice != mk_voice(bank_angle_bank_angle_3))
2926 mk->voice_player.play(mk_voice(bank_angle_bank_angle_3), VoicePlayer::PLAY_LOOPED);
2928 else if (select_voice_alerts(ALERT_MODE6_HIGH_BANK_ANGLE_3))
2930 if (mk->voice_player.voice != mk_voice(bank_angle_pause_bank_angle_3))
2931 mk->voice_player.play(mk_voice(bank_angle_pause_bank_angle_3), VoicePlayer::PLAY_LOOPED);
2933 else if (select_voice_alerts(ALERT_MODE6_LOW_BANK_ANGLE_2))
2935 if (! has_old_alerts(ALERT_MODE6_LOW_BANK_ANGLE_2 | ALERT_MODE6_HIGH_BANK_ANGLE_2))
2936 mk->voice_player.play(mk_voice(bank_angle_bank_angle));
2938 else if (select_voice_alerts(ALERT_MODE6_HIGH_BANK_ANGLE_2))
2940 if (! has_old_alerts(ALERT_MODE6_LOW_BANK_ANGLE_2 | ALERT_MODE6_HIGH_BANK_ANGLE_2))
2941 mk->voice_player.play(mk_voice(bank_angle_pause_bank_angle));
2943 else if (select_voice_alerts(ALERT_MODE6_LOW_BANK_ANGLE_1))
2945 if (! has_old_alerts(ALERT_MODE6_LOW_BANK_ANGLE_1 | ALERT_MODE6_HIGH_BANK_ANGLE_1))
2946 mk->voice_player.play(mk_voice(bank_angle_bank_angle));
2948 else if (select_voice_alerts(ALERT_MODE6_HIGH_BANK_ANGLE_1))
2950 if (! has_old_alerts(ALERT_MODE6_LOW_BANK_ANGLE_1 | ALERT_MODE6_HIGH_BANK_ANGLE_1))
2951 mk->voice_player.play(mk_voice(bank_angle_pause_bank_angle));
2956 old_alerts = voice_alerts;
2957 repeated_alerts = 0;
2958 altitude_callout_voice = NULL;
2962 MK_VIII::AlertHandler::set_alerts (unsigned int _alerts,
2964 VoicePlayer::Voice *_altitude_callout_voice)
2967 if (test_bits(flags, ALERT_FLAG_REPEAT))
2968 repeated_alerts |= _alerts;
2969 if (_altitude_callout_voice)
2970 altitude_callout_voice = _altitude_callout_voice;
2974 MK_VIII::AlertHandler::unset_alerts (unsigned int _alerts)
2977 repeated_alerts &= ~_alerts;
2980 ///////////////////////////////////////////////////////////////////////////////
2981 // StateHandler ///////////////////////////////////////////////////////////////
2982 ///////////////////////////////////////////////////////////////////////////////
2985 MK_VIII::StateHandler::update_ground ()
2989 if (! mk_ainput(computed_airspeed).ncd && mk_ainput(computed_airspeed).get() > 60
2990 && ! mk_data(radio_altitude).ncd && mk_data(radio_altitude).get() > 30)
2992 if (potentially_airborne_timer.start_or_elapsed() > 1)
2996 potentially_airborne_timer.stop();
3000 if (! mk_ainput(computed_airspeed).ncd && mk_ainput(computed_airspeed).get() < 40
3001 && ! mk_data(radio_altitude).ncd && mk_data(radio_altitude).get() < 30)
3007 MK_VIII::StateHandler::enter_ground ()
3010 mk->io_handler.enter_ground();
3012 // [SPEC] 6.0.1 does not specify this, but it seems to be an
3013 // omission; at this point, we must make sure that we are in takeoff
3014 // mode (otherwise the next takeoff might happen in approach mode).
3020 MK_VIII::StateHandler::leave_ground ()
3023 mk->mode2_handler.leave_ground();
3027 MK_VIII::StateHandler::update_takeoff ()
3031 // [SPEC] 6.0.2 is somewhat confusing: it mentions hardcoded
3032 // terrain clearance (500, 750) and airspeed (178, 200) values,
3033 // but it also mentions the mode 4A boundary, which varies as a
3034 // function of aircraft type. We consider that the mentioned
3035 // values are examples, and that we should use the mode 4A upper
3038 if (! mk_data(terrain_clearance).ncd
3039 && ! mk_ainput(computed_airspeed).ncd
3040 && mk_data(terrain_clearance).get() > mk->mode4_handler.get_upper_agl(mk->mode4_handler.conf.modes->ac))
3045 if (! mk_data(radio_altitude).ncd
3046 && mk_data(radio_altitude).get() < mk->mode4_handler.conf.modes->b->min_agl1
3047 && mk->io_handler.flaps_down()
3048 && mk_dinput(landing_gear))
3054 MK_VIII::StateHandler::enter_takeoff ()
3057 mk->io_handler.enter_takeoff();
3058 mk->mode2_handler.enter_takeoff();
3059 mk->mode3_handler.enter_takeoff();
3060 mk->mode6_handler.enter_takeoff();
3064 MK_VIII::StateHandler::leave_takeoff ()
3067 mk->mode6_handler.leave_takeoff();
3071 MK_VIII::StateHandler::post_reposition ()
3073 // Synchronize the state with the current situation.
3076 (! mk_ainput(computed_airspeed).ncd && mk_ainput(computed_airspeed).get() > 60
3077 && ! mk_data(radio_altitude).ncd && mk_data(radio_altitude).get() > 30);
3079 bool _takeoff = _ground;
3081 if (ground && ! _ground)
3083 else if (! ground && _ground)
3086 if (takeoff && ! _takeoff)
3088 else if (! takeoff && _takeoff)
3093 MK_VIII::StateHandler::update ()
3095 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
3102 ///////////////////////////////////////////////////////////////////////////////
3103 // Mode1Handler ///////////////////////////////////////////////////////////////
3104 ///////////////////////////////////////////////////////////////////////////////
3107 MK_VIII::Mode1Handler::get_pull_up_bias ()
3109 // [PILOT] page 54: "Steep Approach has priority over Flap Override
3110 // if selected simultaneously."
3112 if (mk->io_handler.steep_approach())
3114 else if (conf.envelopes->flap_override_bias && mk->io_handler.flap_override())
3121 MK_VIII::Mode1Handler::is_pull_up ()
3123 if (! mk->io_handler.gpws_inhibit()
3124 && ! mk->state_handler.ground // [1]
3125 && ! mk_data(radio_altitude).ncd
3126 && ! mk_data(barometric_altitude_rate).ncd
3127 && mk_data(radio_altitude).get() > conf.envelopes->min_agl)
3129 if (mk_data(radio_altitude).get() < conf.envelopes->pull_up_max_agl1)
3131 if (mk_data(radio_altitude).get() < conf.envelopes->pull_up_min_agl1(mk_data(barometric_altitude_rate).get() + get_pull_up_bias()))
3134 else if (mk_data(radio_altitude).get() < conf.envelopes->pull_up_max_agl2)
3136 if (mk_data(radio_altitude).get() < conf.envelopes->pull_up_min_agl2(mk_data(barometric_altitude_rate).get() + get_pull_up_bias()))
3145 MK_VIII::Mode1Handler::update_pull_up ()
3149 if (! mk_test_alert(MODE1_PULL_UP))
3151 // [SPEC] 6.2.1: at least one sink rate must be issued
3152 // before switching to pull up; if no sink rate has
3153 // occurred, a 0.2 second delay is used.
3154 if (mk->voice_player.voice == mk_voice(sink_rate_pause_sink_rate)
3155 || pull_up_timer.start_or_elapsed() >= 0.2)
3156 mk_set_alerts(mk_alert(MODE1_PULL_UP));
3161 pull_up_timer.stop();
3162 mk_unset_alerts(mk_alert(MODE1_PULL_UP));
3167 MK_VIII::Mode1Handler::get_sink_rate_bias ()
3169 // [PILOT] page 54: "Steep Approach has priority over Flap Override
3170 // if selected simultaneously."
3172 if (mk->io_handler.steep_approach())
3174 else if (conf.envelopes->flap_override_bias && mk->io_handler.flap_override())
3176 else if (! mk_data(glideslope_deviation_dots).ncd)
3180 if (mk_data(glideslope_deviation_dots).get() <= -2)
3182 else if (mk_data(glideslope_deviation_dots).get() < 0)
3183 bias = -150 * mk_data(glideslope_deviation_dots).get();
3185 if (mk_data(radio_altitude).get() < 100)
3186 bias *= 0.01 * mk_data(radio_altitude).get();
3195 MK_VIII::Mode1Handler::is_sink_rate ()
3197 return ! mk->io_handler.gpws_inhibit()
3198 && ! mk->state_handler.ground // [1]
3199 && ! mk_data(radio_altitude).ncd
3200 && ! mk_data(barometric_altitude_rate).ncd
3201 && mk_data(radio_altitude).get() > conf.envelopes->min_agl
3202 && mk_data(radio_altitude).get() < 2450
3203 && mk_data(radio_altitude).get() < -572 - 0.6035 * (mk_data(barometric_altitude_rate).get() + get_sink_rate_bias());
3207 MK_VIII::Mode1Handler::get_sink_rate_tti ()
3209 return mk_data(radio_altitude).get() / fabs(mk_data(barometric_altitude_rate).get());
3213 MK_VIII::Mode1Handler::update_sink_rate ()
3217 if (mk_test_alert(MODE1_SINK_RATE))
3219 double tti = get_sink_rate_tti();
3220 if (tti <= sink_rate_tti * 0.8)
3222 // ~20% degradation, warn again and store the new reference tti
3223 sink_rate_tti = tti;
3224 mk_repeat_alert(mk_alert(MODE1_SINK_RATE));
3229 if (sink_rate_timer.start_or_elapsed() >= 0.8)
3231 mk_set_alerts(mk_alert(MODE1_SINK_RATE));
3232 sink_rate_tti = get_sink_rate_tti();
3238 sink_rate_timer.stop();
3239 mk_unset_alerts(mk_alert(MODE1_SINK_RATE));
3244 MK_VIII::Mode1Handler::update ()
3246 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
3253 ///////////////////////////////////////////////////////////////////////////////
3254 // Mode2Handler ///////////////////////////////////////////////////////////////
3255 ///////////////////////////////////////////////////////////////////////////////
3258 MK_VIII::Mode2Handler::ClosureRateFilter::limit_radio_altitude_rate (double r)
3260 // Limit radio altitude rate according to aircraft configuration,
3261 // allowing maximum sensitivity during cruise while providing
3262 // progressively less sensitivity during the landing phases of
3265 if (! mk_data(glideslope_deviation_dots).ncd && fabs(mk_data(glideslope_deviation_dots).get()) <= 2)
3266 { // gs deviation <= +- 2 dots
3267 if (mk_dinput(landing_gear) && mk->io_handler.flaps_down())
3268 SG_CLAMP_RANGE(r, -1000.0, 3000.0);
3269 else if (mk_dinput(landing_gear) || mk->io_handler.flaps_down())
3270 SG_CLAMP_RANGE(r, 0.0, 4000.0);
3272 SG_CLAMP_RANGE(r, 1000.0, 5000.0);
3275 { // no ILS, or gs deviation > +- 2 dots
3276 if (mk_dinput(landing_gear) && mk->io_handler.flaps_down())
3277 SG_CLAMP_RANGE(r, 0.0, 4000.0);
3278 else if (mk_dinput(landing_gear) || mk->io_handler.flaps_down())
3279 SG_CLAMP_RANGE(r, 1000.0, 5000.0);
3287 MK_VIII::Mode2Handler::ClosureRateFilter::init ()
3290 last_ra.set(&mk_data(radio_altitude));
3291 last_ba.set(&mk_ainput(uncorrected_barometric_altitude));
3298 MK_VIII::Mode2Handler::ClosureRateFilter::update ()
3300 double elapsed = timer.start_or_elapsed();
3304 if (! mk_data(radio_altitude).ncd && ! mk_ainput(uncorrected_barometric_altitude).ncd)
3306 if (! last_ra.ncd && ! last_ba.ncd)
3308 // compute radio and barometric altitude rates (positive value = descent)
3309 double ra_rate = -(mk_data(radio_altitude).get() - last_ra.get()) / elapsed * 60;
3310 double ba_rate = -(mk_ainput(uncorrected_barometric_altitude).get() - last_ba.get()) / elapsed * 60;
3312 // limit radio altitude rate according to aircraft configuration
3313 ra_rate = limit_radio_altitude_rate(ra_rate);
3315 // apply a low-pass filter to the radio altitude rate
3316 ra_rate = ra_filter.filter(ra_rate);
3317 // apply a high-pass filter to the barometric altitude rate
3318 ba_rate = ba_filter.filter(ba_rate);
3320 // combine both rates to obtain a closure rate
3321 output.set(ra_rate + ba_rate);
3334 last_ra.set(&mk_data(radio_altitude));
3335 last_ba.set(&mk_ainput(uncorrected_barometric_altitude));
3339 MK_VIII::Mode2Handler::b_conditions ()
3341 return mk->io_handler.flaps_down()
3342 || (! mk_data(glideslope_deviation_dots).ncd && fabs(mk_data(glideslope_deviation_dots).get()) < 2)
3343 || takeoff_timer.running;
3347 MK_VIII::Mode2Handler::is_a ()
3349 if (! mk->io_handler.gpws_inhibit()
3350 && ! mk->state_handler.ground // [1]
3351 && ! mk_data(radio_altitude).ncd
3352 && ! mk_ainput(computed_airspeed).ncd
3353 && ! closure_rate_filter.output.ncd
3354 && ! b_conditions())
3356 if (mk_data(radio_altitude).get() < 1220)
3358 if (mk_data(radio_altitude).get() < -1579 + 0.7895 * closure_rate_filter.output.get())
3365 if (mk_ainput(computed_airspeed).get() <= conf->airspeed1)
3367 else if (mk_ainput(computed_airspeed).get() >= conf->airspeed2)
3370 upper_limit = 1650 + 8.9 * (mk_ainput(computed_airspeed).get() - conf->airspeed1);
3372 if (mk_data(radio_altitude).get() < upper_limit)
3374 if (mk_data(radio_altitude).get() < 522 + 0.1968 * closure_rate_filter.output.get())
3384 MK_VIII::Mode2Handler::is_b ()
3386 if (! mk->io_handler.gpws_inhibit()
3387 && ! mk->state_handler.ground // [1]
3388 && ! mk_data(radio_altitude).ncd
3389 && ! mk_data(barometric_altitude_rate).ncd
3390 && ! closure_rate_filter.output.ncd
3392 && mk_data(radio_altitude).get() < 789)
3396 if (mk->io_handler.flaps_down())
3398 if (mk_data(barometric_altitude_rate).get() > -400)
3400 else if (mk_data(barometric_altitude_rate).get() < -1000)
3403 lower_limit = -66.777 - 0.667 * mk_data(barometric_altitude_rate).get();
3408 if (mk_data(radio_altitude).get() > lower_limit)
3410 if (mk_data(radio_altitude).get() < -1579 + 0.7895 * closure_rate_filter.output.get())
3419 MK_VIII::Mode2Handler::check_pull_up (unsigned int preface_alert,
3422 if (pull_up_timer.running)
3424 if (pull_up_timer.elapsed() >= 1)
3426 mk_unset_alerts(preface_alert);
3427 mk_set_alerts(alert);
3432 if (! mk->voice_player.voice)
3433 pull_up_timer.start();
3438 MK_VIII::Mode2Handler::update_a ()
3442 if (mk_test_alert(MODE2A_PREFACE))
3443 check_pull_up(mk_alert(MODE2A_PREFACE), mk_alert(MODE2A));
3444 else if (! mk_test_alert(MODE2A))
3446 mk_unset_alerts(mk_alert(MODE2A_ALTITUDE_GAIN) | mk_alert(MODE2A_ALTITUDE_GAIN_TERRAIN_CLOSING));
3447 mk_set_alerts(mk_alert(MODE2A_PREFACE));
3448 a_start_time = globals->get_sim_time_sec();
3449 pull_up_timer.stop();
3454 if (mk_test_alert(MODE2A_ALTITUDE_GAIN))
3456 if (mk->io_handler.gpws_inhibit()
3457 || mk->state_handler.ground // [1]
3458 || a_altitude_gain_timer.elapsed() >= 45
3459 || mk_data(radio_altitude).ncd
3460 || mk_ainput(uncorrected_barometric_altitude).ncd
3461 || mk_ainput(uncorrected_barometric_altitude).get() - a_altitude_gain_alt >= 300
3462 // [PILOT] page 12: "the visual alert will remain on
3463 // until [...] landing flaps or the flap override switch
3465 || mk->io_handler.flaps_down())
3467 // exit altitude gain mode
3468 a_altitude_gain_timer.stop();
3469 mk_unset_alerts(mk_alert(MODE2A_ALTITUDE_GAIN) | mk_alert(MODE2A_ALTITUDE_GAIN_TERRAIN_CLOSING));
3473 // [SPEC] 6.2.2.2: "If the terrain starts to fall away
3474 // during this altitude gain time, the voice will shut
3476 if (closure_rate_filter.output.get() < 0)
3477 mk_unset_alerts(mk_alert(MODE2A_ALTITUDE_GAIN_TERRAIN_CLOSING));
3480 else if (mk_test_alerts(mk_alert(MODE2A_PREFACE) | mk_alert(MODE2A)))
3482 mk_unset_alerts(mk_alert(MODE2A_PREFACE) | mk_alert(MODE2A));
3484 if (! mk->io_handler.gpws_inhibit()
3485 && ! mk->state_handler.ground // [1]
3486 && globals->get_sim_time_sec() - a_start_time > 3
3487 && ! mk->io_handler.flaps_down()
3488 && ! mk_data(radio_altitude).ncd
3489 && ! mk_ainput(uncorrected_barometric_altitude).ncd)
3491 // [SPEC] 6.2.2.2: mode 2A envelope violated for more
3492 // than 3 seconds, enable altitude gain feature
3494 a_altitude_gain_timer.start();
3495 a_altitude_gain_alt = mk_ainput(uncorrected_barometric_altitude).get();
3497 unsigned int alerts = mk_alert(MODE2A_ALTITUDE_GAIN);
3498 if (closure_rate_filter.output.get() > 0)
3499 alerts |= mk_alert(MODE2A_ALTITUDE_GAIN_TERRAIN_CLOSING);
3501 mk_set_alerts(alerts);
3508 MK_VIII::Mode2Handler::update_b ()
3512 // handle normal mode
3514 if (b && (! mk_dinput(landing_gear) || ! mk->io_handler.flaps_down()))
3516 if (mk_test_alert(MODE2B_PREFACE))
3517 check_pull_up(mk_alert(MODE2B_PREFACE), mk_alert(MODE2B));
3518 else if (! mk_test_alert(MODE2B))
3520 mk_set_alerts(mk_alert(MODE2B_PREFACE));
3521 pull_up_timer.stop();
3525 mk_unset_alerts(mk_alert(MODE2B_PREFACE) | mk_alert(MODE2B));
3527 // handle landing mode ([SPEC] 6.2.2.3: "If both landing gear and
3528 // flaps are in the landing configuration, then the message will be
3531 if (b && mk_dinput(landing_gear) && mk->io_handler.flaps_down())
3532 mk_set_alerts(mk_alert(MODE2B_LANDING_MODE));
3534 mk_unset_alerts(mk_alert(MODE2B_LANDING_MODE));
3538 MK_VIII::Mode2Handler::boot ()
3540 closure_rate_filter.init();
3544 MK_VIII::Mode2Handler::power_off ()
3546 // [SPEC] 6.0.4: "This latching function is not power saved and a
3547 // system reset will force it false."
3548 takeoff_timer.stop();
3552 MK_VIII::Mode2Handler::leave_ground ()
3554 // takeoff, reset timer
3555 takeoff_timer.start();
3559 MK_VIII::Mode2Handler::enter_takeoff ()
3561 // reset timer, in case it's a go-around
3562 takeoff_timer.start();
3566 MK_VIII::Mode2Handler::update ()
3568 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
3571 closure_rate_filter.update();
3573 if (takeoff_timer.running && takeoff_timer.elapsed() >= 60)
3574 takeoff_timer.stop();
3580 ///////////////////////////////////////////////////////////////////////////////
3581 // Mode3Handler ///////////////////////////////////////////////////////////////
3582 ///////////////////////////////////////////////////////////////////////////////
3585 MK_VIII::Mode3Handler::max_alt_loss (double _bias)
3587 return conf->max_alt_loss(mk->io_handler.flap_override(), mk_data(radio_altitude).get()) + mk_data(radio_altitude).get() * _bias;
3591 MK_VIII::Mode3Handler::get_bias (double initial_bias, double alt_loss)
3593 // do not repeat altitude-loss alerts below 30ft agl
3594 if (mk_data(radio_altitude).get() > 30)
3596 if (initial_bias < 0.0) // sanity check
3598 // mk-viii spec: repeat alerts whenever losing 20% of initial altitude
3599 while ((alt_loss > max_alt_loss(initial_bias))&&
3600 (initial_bias < 1.0))
3601 initial_bias += 0.2;
3604 return initial_bias;
3608 MK_VIII::Mode3Handler::is (double *alt_loss)
3610 if (has_descent_alt)
3612 int max_agl = conf->max_agl(mk->io_handler.flap_override());
3614 if (mk_ainput(uncorrected_barometric_altitude).ncd
3615 || mk_ainput(uncorrected_barometric_altitude).get() > descent_alt
3616 || mk_data(radio_altitude).ncd
3617 || mk_data(radio_altitude).get() > max_agl)
3620 has_descent_alt = false;
3624 // gear/flaps: [SPEC] 1.3.1.3
3625 if (! mk->io_handler.gpws_inhibit()
3626 && ! mk->state_handler.ground // [1]
3627 && (! mk_dinput(landing_gear) || ! mk->io_handler.flaps_down())
3628 && ! mk_data(barometric_altitude_rate).ncd
3629 && ! mk_ainput(uncorrected_barometric_altitude).ncd
3630 && ! mk_data(radio_altitude).ncd
3631 && mk_data(barometric_altitude_rate).get() < 0)
3633 double _alt_loss = descent_alt - mk_ainput(uncorrected_barometric_altitude).get();
3634 if (armed || (mk_data(radio_altitude).get() > conf->min_agl
3635 && mk_data(radio_altitude).get() < max_agl
3636 && _alt_loss > max_alt_loss(0)))
3638 *alt_loss = _alt_loss;
3646 if (! mk_data(barometric_altitude_rate).ncd
3647 && ! mk_ainput(uncorrected_barometric_altitude).ncd
3648 && mk_data(barometric_altitude_rate).get() < 0)
3650 has_descent_alt = true;
3651 descent_alt = mk_ainput(uncorrected_barometric_altitude).get();
3659 MK_VIII::Mode3Handler::enter_takeoff ()
3662 has_descent_alt = false;
3666 MK_VIII::Mode3Handler::update ()
3668 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
3671 if (mk->state_handler.takeoff)
3675 if (! mk->state_handler.ground /* [1] */ && is(&alt_loss))
3677 if (mk_test_alert(MODE3))
3679 double new_bias = get_bias(bias, alt_loss);
3680 if (new_bias > bias)
3683 mk_repeat_alert(mk_alert(MODE3));
3689 bias = get_bias(0.2, alt_loss);
3690 mk_set_alerts(mk_alert(MODE3));
3697 mk_unset_alerts(mk_alert(MODE3));
3700 ///////////////////////////////////////////////////////////////////////////////
3701 // Mode4Handler ///////////////////////////////////////////////////////////////
3702 ///////////////////////////////////////////////////////////////////////////////
3704 // FIXME: for turbofans, the upper limit should be reduced from 1000
3705 // to 800 ft if a sudden change in radio altitude is detected, in
3706 // order to reduce nuisance alerts caused by overflying another
3707 // aircraft (see [PILOT] page 16).
3710 MK_VIII::Mode4Handler::get_upper_agl (const EnvelopesConfiguration *c)
3712 if (mk_ainput(computed_airspeed).get() >= c->airspeed2)
3714 else if (mk_ainput(computed_airspeed).get() >= c->airspeed1)
3715 return c->min_agl2(mk_ainput(computed_airspeed).get());
3720 const MK_VIII::Mode4Handler::EnvelopesConfiguration *
3721 MK_VIII::Mode4Handler::get_ab_envelope ()
3723 // [SPEC] 6.2.4.1: "The Mode 4B envelope can also be selected by
3724 // setting flaps to landing configuration or by selecting flap
3726 return mk_dinput(landing_gear) || mk->io_handler.flaps_down()
3732 MK_VIII::Mode4Handler::get_bias (double initial_bias, double min_agl)
3734 // do not repeat terrain/gear/flap alerts below 30ft agl
3735 if (mk_data(radio_altitude).get() > 30.0)
3737 if (initial_bias < 0.0) // sanity check
3739 while ((mk_data(radio_altitude).get() < min_agl - min_agl * initial_bias)&&
3740 (initial_bias < 1.0))
3741 initial_bias += 0.2;
3744 return initial_bias;
3748 MK_VIII::Mode4Handler::handle_alert (unsigned int alert,
3752 if (mk_test_alerts(alert))
3754 double new_bias = get_bias(*bias, min_agl);
3755 if (new_bias > *bias)
3758 mk_repeat_alert(alert);
3763 *bias = get_bias(0.2, min_agl);
3764 mk_set_alerts(alert);
3769 MK_VIII::Mode4Handler::update_ab ()
3771 if (! mk->io_handler.gpws_inhibit()
3772 && ! mk->state_handler.ground
3773 && ! mk->state_handler.takeoff
3774 && ! mk_data(radio_altitude).ncd
3775 && ! mk_ainput(computed_airspeed).ncd
3776 && mk_data(radio_altitude).get() > 30)
3778 const EnvelopesConfiguration *c = get_ab_envelope();
3779 if (mk_ainput(computed_airspeed).get() < c->airspeed1)
3781 if (mk_dinput(landing_gear))
3783 if (! mk->io_handler.flaps_down() && mk_data(radio_altitude).get() < c->min_agl1)
3785 handle_alert(mk_alert(MODE4_TOO_LOW_FLAPS), c->min_agl1, &ab_bias);
3791 if (mk_data(radio_altitude).get() < c->min_agl1)
3793 handle_alert(mk_alert(MODE4_TOO_LOW_GEAR), c->min_agl1, &ab_bias);
3800 mk_unset_alerts(mk_alert(MODE4_TOO_LOW_FLAPS) | mk_alert(MODE4_TOO_LOW_GEAR));
3805 MK_VIII::Mode4Handler::update_ab_expanded ()
3807 if (! mk->io_handler.gpws_inhibit()
3808 && ! mk->state_handler.ground
3809 && ! mk->state_handler.takeoff
3810 && ! mk_data(radio_altitude).ncd
3811 && ! mk_ainput(computed_airspeed).ncd
3812 && mk_data(radio_altitude).get() > 30)
3814 const EnvelopesConfiguration *c = get_ab_envelope();
3815 if (mk_ainput(computed_airspeed).get() >= c->airspeed1)
3817 double min_agl = get_upper_agl(c);
3818 if (mk_data(radio_altitude).get() < min_agl)
3820 handle_alert(mk_alert(MODE4AB_TOO_LOW_TERRAIN), min_agl, &ab_expanded_bias);
3826 mk_unset_alerts(mk_alert(MODE4AB_TOO_LOW_TERRAIN));
3827 ab_expanded_bias=0.0;
3831 MK_VIII::Mode4Handler::update_c ()
3833 if (! mk->io_handler.gpws_inhibit()
3834 && ! mk->state_handler.ground // [1]
3835 && mk->state_handler.takeoff
3836 && ! mk_data(radio_altitude).ncd
3837 && ! mk_data(terrain_clearance).ncd
3838 && mk_data(radio_altitude).get() > 30
3839 && (! mk_dinput(landing_gear) || ! mk->io_handler.flaps_down())
3840 && mk_data(radio_altitude).get() < get_upper_agl(conf.modes->ac)
3841 && mk_data(radio_altitude).get() < mk_data(terrain_clearance).get())
3842 handle_alert(mk_alert(MODE4C_TOO_LOW_TERRAIN), mk_data(terrain_clearance).get(), &c_bias);
3845 mk_unset_alerts(mk_alert(MODE4C_TOO_LOW_TERRAIN));
3851 MK_VIII::Mode4Handler::update ()
3853 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
3857 update_ab_expanded();
3861 ///////////////////////////////////////////////////////////////////////////////
3862 // Mode5Handler ///////////////////////////////////////////////////////////////
3863 ///////////////////////////////////////////////////////////////////////////////
3866 MK_VIII::Mode5Handler::is_hard ()
3868 if (mk_data(radio_altitude).get() > 30
3869 && mk_data(radio_altitude).get() < 300
3870 && mk_data(glideslope_deviation_dots).get() > 2)
3872 if (mk_data(radio_altitude).get() < 150)
3874 if (mk_data(radio_altitude).get() > 293 - 71.43 * mk_data(glideslope_deviation_dots).get())
3885 MK_VIII::Mode5Handler::is_soft (double bias)
3887 // do not repeat glide-slope alerts below 30ft agl
3888 if (mk_data(radio_altitude).get() > 30)
3890 double bias_dots = 1.3 * bias;
3891 if (mk_data(glideslope_deviation_dots).get() > 1.3 + bias_dots)
3893 if (mk_data(radio_altitude).get() < 150)
3895 if (mk_data(radio_altitude).get() > 243 - 71.43 * (mk_data(glideslope_deviation_dots).get() - bias_dots))
3902 if (mk_data(barometric_altitude_rate).ncd)
3906 if (mk_data(barometric_altitude_rate).get() >= 0)
3908 else if (mk_data(barometric_altitude_rate).get() < -500)
3911 upper_limit = -mk_data(barometric_altitude_rate).get() + 500;
3914 if (mk_data(radio_altitude).get() < upper_limit)
3924 MK_VIII::Mode5Handler::get_soft_bias (double initial_bias)
3926 if (initial_bias < 0.0) // sanity check
3928 while ((is_soft(initial_bias))&&
3929 (initial_bias < 1.0))
3930 initial_bias += 0.2;
3932 return initial_bias;
3936 MK_VIII::Mode5Handler::update_hard (bool is)
3940 if (! mk_test_alert(MODE5_HARD))
3942 if (hard_timer.start_or_elapsed() >= 0.8)
3943 mk_set_alerts(mk_alert(MODE5_HARD));
3949 mk_unset_alerts(mk_alert(MODE5_HARD));
3954 MK_VIII::Mode5Handler::update_soft (bool is)
3958 if (! mk_test_alert(MODE5_SOFT))
3960 double new_bias = get_soft_bias(soft_bias);
3961 if (new_bias > soft_bias)
3963 soft_bias = new_bias;
3964 mk_repeat_alert(mk_alert(MODE5_SOFT));
3969 if (soft_timer.start_or_elapsed() >= 0.8)
3971 soft_bias = get_soft_bias(0.2);
3972 mk_set_alerts(mk_alert(MODE5_SOFT));
3979 mk_unset_alerts(mk_alert(MODE5_SOFT));
3984 MK_VIII::Mode5Handler::update ()
3986 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
3989 if (! mk->io_handler.gpws_inhibit()
3990 && ! mk->state_handler.ground // [1]
3991 && ! mk_dinput(glideslope_inhibit) // not on backcourse
3992 && ! mk_data(radio_altitude).ncd
3993 && ! mk_data(glideslope_deviation_dots).ncd
3994 && (! mk->io_handler.conf.localizer_enabled
3995 || mk_data(localizer_deviation_dots).ncd
3996 || mk_data(radio_altitude).get() < 500
3997 || fabs(mk_data(localizer_deviation_dots).get()) < 2)
3998 && (! mk->state_handler.takeoff || mk->io_handler.flaps_down())
3999 && mk_dinput(landing_gear)
4000 && ! mk_doutput(glideslope_cancel))
4002 update_hard(is_hard());
4003 update_soft(is_soft(0));
4012 ///////////////////////////////////////////////////////////////////////////////
4013 // Mode6Handler ///////////////////////////////////////////////////////////////
4014 ///////////////////////////////////////////////////////////////////////////////
4016 // keep sorted in descending order
4017 const int MK_VIII::Mode6Handler::altitude_callout_definitions[] =
4018 { 1000, 500, 400, 300, 200, 100, 50, 40, 30, 20, 10 };
4021 MK_VIII::Mode6Handler::reset_minimums ()
4023 minimums_issued = false;
4027 MK_VIII::Mode6Handler::reset_altitude_callouts ()
4029 for (unsigned i = 0; i < n_altitude_callouts; i++)
4030 altitude_callouts_issued[i] = false;
4034 MK_VIII::Mode6Handler::is_playing_altitude_callout ()
4036 for (unsigned i = 0; i < n_altitude_callouts; i++)
4037 if (mk->voice_player.voice == mk_altitude_voice(i)
4038 || mk->voice_player.next_voice == mk_altitude_voice(i))
4045 MK_VIII::Mode6Handler::is_near_minimums (double callout)
4049 if (! mk_data(decision_height).ncd)
4051 double diff = callout - mk_data(decision_height).get();
4053 if (mk_data(radio_altitude).get() >= 200)
4055 if (fabs(diff) <= 30)
4060 if (diff >= -3 && diff <= 6)
4069 MK_VIII::Mode6Handler::is_outside_band (double elevation, double callout)
4072 return elevation < callout - (elevation > 150 ? 20 : 10);
4076 MK_VIII::Mode6Handler::inhibit_smart_500 ()
4080 if (! mk_data(glideslope_deviation_dots).ncd
4081 && ! mk_dinput(glideslope_inhibit) // backcourse
4082 && ! mk_doutput(glideslope_cancel))
4084 if (mk->io_handler.conf.localizer_enabled
4085 && ! mk_data(localizer_deviation_dots).ncd)
4087 if (fabs(mk_data(localizer_deviation_dots).get()) <= 2)
4092 if (fabs(mk_data(glideslope_deviation_dots).get()) <= 2)
4101 MK_VIII::Mode6Handler::boot ()
4103 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
4106 last_decision_height = mk_dinput(decision_height);
4107 last_radio_altitude.set(&mk_data(radio_altitude));
4110 for (unsigned i = 0; i < n_altitude_callouts; i++)
4111 altitude_callouts_issued[i] = ! mk_data(radio_altitude).ncd
4112 && mk_data(radio_altitude).get() <= altitude_callout_definitions[i];
4114 // extrapolated from [SPEC] 6.4.2
4115 minimums_issued = mk_dinput(decision_height);
4117 if (conf.above_field_voice)
4120 get_altitude_above_field(&last_altitude_above_field);
4121 // extrapolated from [SPEC] 6.4.2
4122 above_field_issued = ! last_altitude_above_field.ncd
4123 && last_altitude_above_field.get() < 550;
4128 MK_VIII::Mode6Handler::power_off ()
4130 runway_timer.stop();
4134 MK_VIII::Mode6Handler::enter_takeoff ()
4136 reset_altitude_callouts(); // [SPEC] 6.4.2
4137 reset_minimums(); // omitted by [SPEC]; common sense
4141 MK_VIII::Mode6Handler::leave_takeoff ()
4143 reset_altitude_callouts(); // [SPEC] 6.0.2
4144 reset_minimums(); // [SPEC] 6.0.2
4148 MK_VIII::Mode6Handler::set_volume (float volume)
4150 mk_voice(minimums_minimums)->set_volume(volume);
4151 mk_voice(five_hundred_above)->set_volume(volume);
4152 for (unsigned i = 0; i < n_altitude_callouts; i++)
4153 mk_altitude_voice(i)->set_volume(volume);
4157 MK_VIII::Mode6Handler::altitude_callouts_enabled ()
4159 if (conf.minimums_enabled || conf.smart_500_enabled || conf.above_field_voice)
4162 for (unsigned i = 0; i < n_altitude_callouts; i++)
4163 if (conf.altitude_callouts_enabled[i])
4170 MK_VIII::Mode6Handler::update_minimums ()
4172 if (! mk->io_handler.gpws_inhibit()
4173 && (mk->voice_player.voice == mk_voice(minimums_minimums)
4174 || mk->voice_player.next_voice == mk_voice(minimums_minimums)))
4177 if (! mk->io_handler.gpws_inhibit()
4178 && ! mk->state_handler.ground // [1]
4179 && conf.minimums_enabled
4180 && ! minimums_issued
4181 && mk_dinput(landing_gear)
4182 && mk_dinput(decision_height)
4183 && ! last_decision_height)
4185 minimums_issued = true;
4187 // If an altitude callout is playing, stop it now so that the
4188 // minimums callout can be played (note that proper connection
4189 // and synchronization of the radio-altitude ARINC 529 input,
4190 // decision-height discrete and decision-height ARINC 529 input
4191 // will prevent an altitude callout from being played near the
4192 // decision height).
4194 if (is_playing_altitude_callout())
4195 mk->voice_player.stop(VoicePlayer::STOP_NOW);
4197 mk_set_alerts(mk_alert(MODE6_MINIMUMS));
4200 mk_unset_alerts(mk_alert(MODE6_MINIMUMS));
4203 last_decision_height = mk_dinput(decision_height);
4207 MK_VIII::Mode6Handler::update_altitude_callouts ()
4209 if (! mk->io_handler.gpws_inhibit() && is_playing_altitude_callout())
4212 if (! mk->io_handler.gpws_inhibit()
4213 && ! mk->state_handler.ground // [1]
4214 && ! mk_data(radio_altitude).ncd)
4215 for (unsigned i = 0; i < n_altitude_callouts && mk_data(radio_altitude).get() <= altitude_callout_definitions[i]; i++)
4216 if ((conf.altitude_callouts_enabled[i]
4217 || (altitude_callout_definitions[i] == 500
4218 && conf.smart_500_enabled))
4219 && ! altitude_callouts_issued[i]
4220 && (last_radio_altitude.ncd
4221 || last_radio_altitude.get() > altitude_callout_definitions[i]))
4223 // lock out all callouts superior or equal to this one
4224 for (unsigned j = 0; j <= i; j++)
4225 altitude_callouts_issued[j] = true;
4227 altitude_callouts_issued[i] = true;
4228 if (! is_near_minimums(altitude_callout_definitions[i])
4229 && ! is_outside_band(mk_data(radio_altitude).get(), altitude_callout_definitions[i])
4230 && (! conf.smart_500_enabled
4231 || altitude_callout_definitions[i] != 500
4232 || ! inhibit_smart_500()))
4234 mk->alert_handler.set_altitude_callout_alert(mk_altitude_voice(i));
4239 mk_unset_alerts(mk_alert(MODE6_ALTITUDE_CALLOUT));
4242 last_radio_altitude.set(&mk_data(radio_altitude));
4246 MK_VIII::Mode6Handler::test_runway (const FGRunway *_runway)
4248 if (_runway->lengthFt() < mk->conf.runway_database)
4252 SGGeod::fromDeg(mk_data(gps_longitude).get(), mk_data(gps_latitude).get()));
4254 // get distance to threshold
4255 double distance, az1, az2;
4256 SGGeodesy::inverse(pos, _runway->threshold(), az1, az2, distance);
4257 return distance * SG_METER_TO_NM <= 5;
4261 MK_VIII::Mode6Handler::test_airport (const FGAirport *airport)
4263 for (unsigned int r=0; r<airport->numRunways(); ++r) {
4264 FGRunway* rwy(airport->getRunwayByIndex(r));
4266 if (test_runway(rwy)) return true;
4272 bool MK_VIII::Mode6Handler::AirportFilter::passAirport(FGAirport* a) const
4274 bool ok = self->test_airport(a);
4279 MK_VIII::Mode6Handler::update_runway ()
4282 if (mk_data(gps_latitude).ncd || mk_data(gps_longitude).ncd) {
4287 // Search for the closest runway threshold in range 5
4288 // nm. Passing 30nm to
4289 // get_closest_airport() provides enough margin for large
4290 // airports, which may have a runway located far away from the
4291 // airport's reference point.
4292 AirportFilter filter(this);
4293 FGPositionedRef apt = FGPositioned::findClosest(
4294 SGGeod::fromDeg(mk_data(gps_longitude).get(), mk_data(gps_latitude).get()),
4297 runway.elevation = apt->elevation();
4300 has_runway.set(apt != NULL);
4304 MK_VIII::Mode6Handler::get_altitude_above_field (Parameter<double> *parameter)
4306 if (! has_runway.ncd && has_runway.get() && ! mk_data(geometric_altitude).ncd)
4307 parameter->set(mk_data(geometric_altitude).get() - runway.elevation);
4313 MK_VIII::Mode6Handler::update_above_field_callout ()
4315 if (! conf.above_field_voice)
4318 // update_runway() has to iterate over the whole runway database
4319 // (which contains thousands of runways), so it would be unwise to
4320 // run it every frame. Run it every 3 seconds. Note that the first
4321 // iteration is run in boot().
4322 if (runway_timer.start_or_elapsed() >= 3)
4325 runway_timer.start();
4328 Parameter<double> altitude_above_field;
4329 get_altitude_above_field(&altitude_above_field);
4331 if (! mk->io_handler.gpws_inhibit()
4332 && (mk->voice_player.voice == conf.above_field_voice
4333 || mk->voice_player.next_voice == conf.above_field_voice))
4336 // handle reset term
4337 if (above_field_issued)
4339 if ((! has_runway.ncd && ! has_runway.get())
4340 || (! altitude_above_field.ncd && altitude_above_field.get() > 700))
4341 above_field_issued = false;
4344 if (! mk->io_handler.gpws_inhibit()
4345 && ! mk->state_handler.ground // [1]
4346 && ! above_field_issued
4347 && ! altitude_above_field.ncd
4348 && altitude_above_field.get() < 550
4349 && (last_altitude_above_field.ncd
4350 || last_altitude_above_field.get() >= 550))
4352 above_field_issued = true;
4354 if (! is_outside_band(altitude_above_field.get(), 500))
4356 mk->alert_handler.set_altitude_callout_alert(conf.above_field_voice);
4361 mk_unset_alerts(mk_alert(MODE6_ALTITUDE_CALLOUT));
4364 last_altitude_above_field.set(&altitude_above_field);
4368 MK_VIII::Mode6Handler::is_bank_angle (double abs_roll_angle, double bias)
4370 return conf.is_bank_angle(&mk_data(radio_altitude),
4371 abs_roll_angle - abs_roll_angle * bias,
4372 mk_dinput(autopilot_engaged));
4376 MK_VIII::Mode6Handler::is_high_bank_angle ()
4378 return mk_data(radio_altitude).ncd || mk_data(radio_altitude).get() >= 210;
4382 MK_VIII::Mode6Handler::get_bank_angle_alerts ()
4384 if (! mk->io_handler.gpws_inhibit()
4385 && ! mk->state_handler.ground // [1]
4386 && ! mk_data(roll_angle).ncd)
4388 double abs_roll_angle = fabs(mk_data(roll_angle).get());
4390 if (is_bank_angle(abs_roll_angle, 0.4))
4391 return is_high_bank_angle()
4392 ? (mk_alert(MODE6_HIGH_BANK_ANGLE_1) | mk_alert(MODE6_HIGH_BANK_ANGLE_2) | mk_alert(MODE6_HIGH_BANK_ANGLE_3))
4393 : (mk_alert(MODE6_LOW_BANK_ANGLE_1) | mk_alert(MODE6_LOW_BANK_ANGLE_2) | mk_alert(MODE6_LOW_BANK_ANGLE_3));
4394 else if (is_bank_angle(abs_roll_angle, 0.2))
4395 return is_high_bank_angle()
4396 ? (mk_alert(MODE6_HIGH_BANK_ANGLE_1) | mk_alert(MODE6_HIGH_BANK_ANGLE_2))
4397 : (mk_alert(MODE6_LOW_BANK_ANGLE_1) | mk_alert(MODE6_LOW_BANK_ANGLE_2));
4398 else if (is_bank_angle(abs_roll_angle, 0))
4399 return is_high_bank_angle()
4400 ? mk_alert(MODE6_HIGH_BANK_ANGLE_1)
4401 : mk_alert(MODE6_LOW_BANK_ANGLE_1);
4408 MK_VIII::Mode6Handler::update_bank_angle ()
4410 if (! conf.bank_angle_enabled)
4413 unsigned int alerts = get_bank_angle_alerts();
4415 // [SPEC] 6.4.4 specifies that the non-continuous alerts
4416 // (MODE6_*_BANK_ANGLE_1 and MODE6_*_BANK_ANGLE_2) are locked out
4417 // until the initial envelope is exited.
4419 if (! test_bits(alerts, mk_alert(MODE6_LOW_BANK_ANGLE_3)))
4420 mk_unset_alerts(mk_alert(MODE6_LOW_BANK_ANGLE_3));
4421 if (! test_bits(alerts, mk_alert(MODE6_HIGH_BANK_ANGLE_3)))
4422 mk_unset_alerts(mk_alert(MODE6_HIGH_BANK_ANGLE_3));
4425 mk_set_alerts(alerts);
4427 mk_unset_alerts(mk_alert(MODE6_LOW_BANK_ANGLE_1)
4428 | mk_alert(MODE6_HIGH_BANK_ANGLE_1)
4429 | mk_alert(MODE6_LOW_BANK_ANGLE_2)
4430 | mk_alert(MODE6_HIGH_BANK_ANGLE_2));
4434 MK_VIII::Mode6Handler::update ()
4436 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
4439 if (! mk->state_handler.takeoff
4440 && ! mk_data(radio_altitude).ncd
4441 && mk_data(radio_altitude).get() > 1000)
4443 reset_altitude_callouts(); // [SPEC] 6.4.2
4444 reset_minimums(); // common sense
4448 update_altitude_callouts();
4449 update_above_field_callout();
4450 update_bank_angle();
4453 ///////////////////////////////////////////////////////////////////////////////
4454 // TCFHandler /////////////////////////////////////////////////////////////////
4455 ///////////////////////////////////////////////////////////////////////////////
4457 // Gets the difference between the azimuth from @from_lat,@from_lon to
4458 // @to_lat,@to_lon, and @to_heading, in degrees.
4460 MK_VIII::TCFHandler::get_azimuth_difference (double from_lat,
4466 double az1, az2, distance;
4467 geo_inverse_wgs_84(0, from_lat, from_lon, to_lat, to_lon, &az1, &az2, &distance);
4468 return get_heading_difference(az1, to_heading);
4471 // Gets the difference between the azimuth from the current GPS
4472 // position to the center of @_runway, and the heading of @_runway, in
4475 MK_VIII::TCFHandler::get_azimuth_difference (const FGRunway *_runway)
4477 return get_azimuth_difference(mk_data(gps_latitude).get(),
4478 mk_data(gps_longitude).get(),
4479 _runway->latitude(),
4480 _runway->longitude(),
4481 _runway->headingDeg());
4484 // Selects the most likely intended destination runway of @airport,
4485 // and returns it in @_runway. For each runway, the difference between
4486 // the azimuth from the current GPS position to the center of the
4487 // runway and its heading is computed. The runway having the smallest
4490 // This selection algorithm is not specified in [SPEC], but
4491 // http://www.egpws.com/general_information/description/runway_select.htm
4492 // talks about automatic runway selection.
4494 MK_VIII::TCFHandler::select_runway (const FGAirport *airport)
4496 FGRunway* _runway = 0;
4497 double min_diff = 360;
4499 for (unsigned int r=0; r<airport->numRunways(); ++r) {
4500 FGRunway* rwy(airport->getRunwayByIndex(r));
4501 double diff = get_azimuth_difference(rwy);
4502 if (diff < min_diff)
4507 } // of airport runways iteration
4511 bool MK_VIII::TCFHandler::AirportFilter::passAirport(FGAirport* aApt) const
4513 return aApt->hasHardRunwayOfLengthFt(mk->conf.runway_database);
4517 MK_VIII::TCFHandler::update_runway ()
4520 if (mk_data(gps_latitude).ncd || mk_data(gps_longitude).ncd) {
4524 // Search for the intended destination runway of the closest
4525 // airport in range 15 nm. Passing 30nm to get_closest_airport()
4526 // provides enough margin for
4527 // large airports, which may have a runway located far away from
4528 // the airport's reference point.
4529 AirportFilter filter(mk);
4530 FGAirport* apt = FGAirport::findClosest(
4531 SGGeod::fromDeg(mk_data(gps_longitude).get(), mk_data(gps_latitude).get()),
4536 FGRunway* _runway = select_runway(apt);
4538 if (!_runway) return;
4542 runway.center.latitude = _runway->latitude();
4543 runway.center.longitude = _runway->longitude();
4545 runway.elevation = apt->elevation();
4547 double half_length_m = _runway->lengthM() * 0.5;
4548 runway.half_length = half_length_m * SG_METER_TO_NM;
4550 // b3 ________________ b0
4552 // h1>>> | e1<<<<<<<<e0 | <<<h0
4553 // |________________|
4556 // get heading to runway threshold (h0) and end (h1)
4557 runway.edges[0].heading = _runway->headingDeg();
4558 runway.edges[1].heading = get_reciprocal_heading(_runway->headingDeg());
4562 // get position of runway threshold (e0)
4563 geo_direct_wgs_84(0,
4564 runway.center.latitude,
4565 runway.center.longitude,
4566 runway.edges[1].heading,
4568 &runway.edges[0].position.latitude,
4569 &runway.edges[0].position.longitude,
4572 // get position of runway end (e1)
4573 geo_direct_wgs_84(0,
4574 runway.center.latitude,
4575 runway.center.longitude,
4576 runway.edges[0].heading,
4578 &runway.edges[1].position.latitude,
4579 &runway.edges[1].position.longitude,
4582 double half_width_m = _runway->widthM() * 0.5;
4584 // get position of threshold bias area edges (b0 and b1)
4585 get_bias_area_edges(&runway.edges[0].position,
4586 runway.edges[1].heading,
4588 &runway.bias_area[0],
4589 &runway.bias_area[1]);
4591 // get position of end bias area edges (b2 and b3)
4592 get_bias_area_edges(&runway.edges[1].position,
4593 runway.edges[0].heading,
4595 &runway.bias_area[2],
4596 &runway.bias_area[3]);
4600 MK_VIII::TCFHandler::get_bias_area_edges (Position *edge,
4602 double half_width_m,
4603 Position *bias_edge1,
4604 Position *bias_edge2)
4606 double half_bias_width_m = k * SG_NM_TO_METER + half_width_m;
4607 double tmp_latitude = 0.0, tmp_longitude = 0.0, az = 0.0;
4609 geo_direct_wgs_84(0,
4617 geo_direct_wgs_84(0,
4620 heading_substract(reciprocal, 90),
4622 &bias_edge1->latitude,
4623 &bias_edge1->longitude,
4625 geo_direct_wgs_84(0,
4628 heading_add(reciprocal, 90),
4630 &bias_edge2->latitude,
4631 &bias_edge2->longitude,
4635 // Returns true if the current GPS position is inside the edge
4636 // triangle of @edge. The edge triangle, which is specified and
4637 // represented in [SPEC] 6.3.1, is defined as an isocele right
4638 // triangle of infinite height, whose right angle is located at the
4639 // position of @edge, and whose height is parallel to the heading of
4642 MK_VIII::TCFHandler::is_inside_edge_triangle (RunwayEdge *edge)
4644 return get_azimuth_difference(mk_data(gps_latitude).get(),
4645 mk_data(gps_longitude).get(),
4646 edge->position.latitude,
4647 edge->position.longitude,
4648 edge->heading) <= 45;
4651 // Returns true if the current GPS position is inside the bias area of
4652 // the currently selected runway.
4654 MK_VIII::TCFHandler::is_inside_bias_area ()
4657 double angles_sum = 0;
4659 for (int i = 0; i < 4; i++)
4661 double az2, distance;
4662 geo_inverse_wgs_84(0,
4663 mk_data(gps_latitude).get(),
4664 mk_data(gps_longitude).get(),
4665 runway.bias_area[i].latitude,
4666 runway.bias_area[i].longitude,
4667 &az1[i], &az2, &distance);
4670 for (int i = 0; i < 4; i++)
4672 double angle = az1[i == 3 ? 0 : i + 1] - az1[i];
4676 angles_sum += angle;
4679 return angles_sum > 180;
4683 MK_VIII::TCFHandler::is_tcf ()
4685 if (mk_data(radio_altitude).get() > 10)
4689 double distance, az1, az2;
4691 geo_inverse_wgs_84(0,
4692 mk_data(gps_latitude).get(),
4693 mk_data(gps_longitude).get(),
4694 runway.center.latitude,
4695 runway.center.longitude,
4696 &az1, &az2, &distance);
4698 distance *= SG_METER_TO_NM;
4700 // distance to the inner envelope edge
4701 double edge_distance = distance - runway.half_length - k;
4703 if (edge_distance >= 15)
4705 if (mk_data(radio_altitude).get() < 700)
4708 else if (edge_distance >= 12)
4710 if (mk_data(radio_altitude).get() < 100 * edge_distance - 800)
4713 else if (edge_distance >= 4)
4715 if (mk_data(radio_altitude).get() < 400)
4718 else if (edge_distance >= 2.45)
4720 if (mk_data(radio_altitude).get() < 66.666667 * edge_distance + 133.33333)
4725 if (is_inside_edge_triangle(&runway.edges[0]) || is_inside_edge_triangle(&runway.edges[1]))
4727 if (edge_distance >= 1)
4729 if (mk_data(radio_altitude).get() < 66.666667 * edge_distance + 133.33333)
4732 else if (edge_distance >= 0.05)
4734 if (mk_data(radio_altitude).get() < 200 * edge_distance)
4740 if (! is_inside_bias_area())
4742 if (mk_data(radio_altitude).get() < 245)
4750 if (mk_data(radio_altitude).get() < 700)
4759 MK_VIII::TCFHandler::is_rfcf ()
4763 double distance, az1, az2;
4764 geo_inverse_wgs_84(0,
4765 mk_data(gps_latitude).get(),
4766 mk_data(gps_longitude).get(),
4767 runway.center.latitude,
4768 runway.center.longitude,
4769 &az1, &az2, &distance);
4771 double krf = k + mk_data(gps_vertical_figure_of_merit).get() / 200;
4772 distance = distance * SG_METER_TO_NM - runway.half_length - krf;
4776 double altitude_above_field = mk_data(geometric_altitude).get() - runway.elevation;
4778 if (distance >= 1.5)
4780 if (altitude_above_field < 300)
4783 else if (distance >= 0)
4785 if (altitude_above_field < 200 * distance)
4795 MK_VIII::TCFHandler::update ()
4797 if (mk->configuration_module.state != ConfigurationModule::STATE_OK || ! conf.enabled)
4800 // update_runway() has to iterate over the whole runway database
4801 // (which contains thousands of runways), so it would be unwise to
4802 // run it every frame. Run it every 3 seconds.
4803 if (! runway_timer.running || runway_timer.elapsed() >= 3)
4806 runway_timer.start();
4809 if (! mk_dinput(ta_tcf_inhibit)
4810 && ! mk->state_handler.ground // [1]
4811 && ! mk_data(gps_latitude).ncd
4812 && ! mk_data(gps_longitude).ncd
4813 && ! mk_data(radio_altitude).ncd
4814 && ! mk_data(geometric_altitude).ncd
4815 && ! mk_data(gps_vertical_figure_of_merit).ncd)
4820 _reference = mk_data(radio_altitude).get_pointer();
4822 _reference = mk_data(geometric_altitude).get_pointer();
4828 if (mk_test_alert(TCF_TOO_LOW_TERRAIN))
4830 double new_bias = bias;
4831 // do not repeat terrain alerts below 30ft agl
4832 if (mk_data(radio_altitude).get() > 30)
4834 if (new_bias < 0.0) // sanity check
4836 while ((*reference < initial_value - initial_value * new_bias)&&
4841 if (new_bias > bias)
4844 mk_repeat_alert(mk_alert(TCF_TOO_LOW_TERRAIN));
4850 reference = _reference;
4851 initial_value = *reference;
4852 mk_set_alerts(mk_alert(TCF_TOO_LOW_TERRAIN));
4859 mk_unset_alerts(mk_alert(TCF_TOO_LOW_TERRAIN));
4862 ///////////////////////////////////////////////////////////////////////////////
4863 // MK_VIII ////////////////////////////////////////////////////////////////////
4864 ///////////////////////////////////////////////////////////////////////////////
4866 MK_VIII::MK_VIII (SGPropertyNode *node)
4867 : properties_handler(this),
4870 power_handler(this),
4871 system_handler(this),
4872 configuration_module(this),
4873 fault_handler(this),
4876 self_test_handler(this),
4877 alert_handler(this),
4878 state_handler(this),
4879 mode1_handler(this),
4880 mode2_handler(this),
4881 mode3_handler(this),
4882 mode4_handler(this),
4883 mode5_handler(this),
4884 mode6_handler(this),
4887 for (int i = 0; i < node->nChildren(); ++i)
4889 SGPropertyNode *child = node->getChild(i);
4890 string cname = child->getName();
4891 string cval = child->getStringValue();
4893 if (cname == "name")
4895 else if (cname == "number")
4896 num = child->getIntValue();
4899 SG_LOG(SG_INSTR, SG_WARN, "Error in mk-viii config logic");
4901 SG_LOG(SG_INSTR, SG_WARN, "Section = " << name);
4909 properties_handler.init();
4910 voice_player.init();
4916 SGPropertyNode *node = fgGetNode(("/instrumentation/" + name).c_str(), num, true);
4918 configuration_module.bind(node);
4919 power_handler.bind(node);
4920 io_handler.bind(node);
4921 voice_player.bind(node);
4927 properties_handler.unbind();
4931 MK_VIII::update (double dt)
4933 power_handler.update();
4934 system_handler.update();
4936 if (system_handler.state != SystemHandler::STATE_ON)
4939 io_handler.update_inputs();
4940 io_handler.update_input_faults();
4942 voice_player.update();
4943 state_handler.update();
4945 if (self_test_handler.update())
4948 io_handler.update_internal_latches();
4949 io_handler.update_lamps();
4951 mode1_handler.update();
4952 mode2_handler.update();
4953 mode3_handler.update();
4954 mode4_handler.update();
4955 mode5_handler.update();
4956 mode6_handler.update();
4957 tcf_handler.update();
4959 alert_handler.update();
4960 io_handler.update_outputs();