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(asi_serviceable) = fgGetNode("/instrumentation/airspeed-indicator/serviceable", true);
186 mk_node(asi_speed) = fgGetNode("/instrumentation/airspeed-indicator/indicated-speed-kt", true);
187 mk_node(autopilot_heading_lock) = fgGetNode("/autopilot/locks/heading", true);
188 mk_node(flaps) = fgGetNode("/controls/flight/flaps", true);
189 mk_node(gear_down) = fgGetNode("/controls/gear/gear-down", true);
190 mk_node(latitude) = fgGetNode("/position/latitude-deg", true);
191 mk_node(longitude) = fgGetNode("/position/longitude-deg", true);
192 mk_node(nav0_cdi_serviceable) = fgGetNode("/instrumentation/nav/cdi/serviceable", true);
193 mk_node(nav0_gs_distance) = fgGetNode("/instrumentation/nav/gs-distance", true);
194 mk_node(nav0_gs_needle_deflection) = fgGetNode("/instrumentation/nav/gs-needle-deflection", true);
195 mk_node(nav0_gs_serviceable) = fgGetNode("/instrumentation/nav/gs/serviceable", true);
196 mk_node(nav0_has_gs) = fgGetNode("/instrumentation/nav/has-gs", true);
197 mk_node(nav0_heading_needle_deflection) = fgGetNode("/instrumentation/nav/heading-needle-deflection", true);
198 mk_node(nav0_in_range) = fgGetNode("/instrumentation/nav/in-range", true);
199 mk_node(nav0_nav_loc) = fgGetNode("/instrumentation/nav/nav-loc", true);
200 mk_node(nav0_serviceable) = fgGetNode("/instrumentation/nav/serviceable", true);
201 mk_node(power) = fgGetNode(("/systems/electrical/outputs/" + mk->name).c_str(), mk->num, true);
202 mk_node(replay_state) = fgGetNode("/sim/freeze/replay-state", true);
203 mk_node(vs) = fgGetNode("/velocities/vertical-speed-fps", true);
207 MK_VIII::PropertiesHandler::unbind ()
209 vector<SGPropertyNode_ptr>::iterator iter;
211 for (iter = tied_properties.begin(); iter != tied_properties.end(); iter++)
214 tied_properties.clear();
217 ///////////////////////////////////////////////////////////////////////////////
218 // PowerHandler ///////////////////////////////////////////////////////////////
219 ///////////////////////////////////////////////////////////////////////////////
222 MK_VIII::PowerHandler::bind (SGPropertyNode *node)
224 mk->properties_handler.tie(node, "serviceable", SGRawValuePointer<bool>(&serviceable));
228 MK_VIII::PowerHandler::handle_abnormal_voltage (bool abnormal,
234 if (timer->start_or_elapsed() >= max_duration)
235 return true; // power loss
244 MK_VIII::PowerHandler::update ()
246 double voltage = mk_node(power)->getDoubleValue();
247 bool now_powered = true;
255 if (handle_abnormal_voltage(voltage < 20.5, &low_surge_timer, 0.03))
257 if (handle_abnormal_voltage(voltage < 22.0 || voltage > 30.3, &abnormal_timer, 300))
259 if (handle_abnormal_voltage(voltage > 32.2, &high_surge_timer, 1))
261 if (handle_abnormal_voltage(voltage > 37.8, &very_high_surge_timer, 0.1))
271 power_loss_timer.stop();
274 if (power_loss_timer.start_or_elapsed() >= 0.2)
286 MK_VIII::PowerHandler::power_on ()
289 mk->system_handler.power_on();
293 MK_VIII::PowerHandler::power_off ()
296 mk->system_handler.power_off();
297 mk->voice_player.stop(VoicePlayer::STOP_NOW);
298 mk->self_test_handler.power_off(); // run before IOHandler::power_off()
299 mk->io_handler.power_off();
300 mk->mode2_handler.power_off();
301 mk->mode6_handler.power_off();
304 ///////////////////////////////////////////////////////////////////////////////
305 // SystemHandler //////////////////////////////////////////////////////////////
306 ///////////////////////////////////////////////////////////////////////////////
309 MK_VIII::SystemHandler::power_on ()
311 state = STATE_BOOTING;
313 // [SPEC] 3.5.2 mentions a 20 seconds maximum boot delay. We use a
314 // random delay between 3 and 5 seconds.
316 boot_delay = 3 + sg_random() * 2;
321 MK_VIII::SystemHandler::power_off ()
329 MK_VIII::SystemHandler::update ()
331 if (state == STATE_BOOTING)
333 if (boot_timer.elapsed() >= boot_delay)
335 last_replay_state = mk_node(replay_state)->getIntValue();
337 mk->configuration_module.boot();
339 mk->io_handler.boot();
340 mk->fault_handler.boot();
341 mk->alert_handler.boot();
343 // inputs are needed by the following boot() routines
344 mk->io_handler.update_inputs();
346 mk->mode2_handler.boot();
347 mk->mode6_handler.boot();
351 mk->io_handler.post_boot();
354 else if (state != STATE_OFF && mk->configuration_module.state == ConfigurationModule::STATE_OK)
356 // If the replay state changes, switch to reposition mode for 3
357 // seconds ([SPEC] 6.0.5) to avoid spurious alerts.
359 int replay_state = mk_node(replay_state)->getIntValue();
360 if (replay_state != last_replay_state)
362 mk->alert_handler.reposition();
364 last_replay_state = replay_state;
365 state = STATE_REPOSITION;
366 reposition_timer.start();
369 if (state == STATE_REPOSITION && reposition_timer.elapsed() >= 3)
371 // inputs are needed by StateHandler::post_reposition()
372 mk->io_handler.update_inputs();
374 mk->state_handler.post_reposition();
381 ///////////////////////////////////////////////////////////////////////////////
382 // ConfigurationModule ////////////////////////////////////////////////////////
383 ///////////////////////////////////////////////////////////////////////////////
385 MK_VIII::ConfigurationModule::ConfigurationModule (MK_VIII *device)
388 // arbitrary defaults
389 categories[CATEGORY_AIRCRAFT_MODE_TYPE_SELECT] = 0;
390 categories[CATEGORY_AIR_DATA_INPUT_SELECT] = 1;
391 categories[CATEGORY_POSITION_INPUT_SELECT] = 2;
392 categories[CATEGORY_ALTITUDE_CALLOUTS] = 13;
393 categories[CATEGORY_AUDIO_MENU_SELECT] = 0;
394 categories[CATEGORY_TERRAIN_DISPLAY_SELECT] = 1;
395 categories[CATEGORY_OPTIONS_SELECT_GROUP_1] = 124;
396 categories[CATEGORY_RADIO_ALTITUDE_INPUT_SELECT] = 2;
397 categories[CATEGORY_NAVIGATION_INPUT_SELECT] = 3;
398 categories[CATEGORY_ATTITUDE_INPUT_SELECT] = 6;
399 categories[CATEGORY_HEADING_INPUT_SELECT] = 2;
400 categories[CATEGORY_WINDSHEAR_INPUT_SELECT] = 0;
401 categories[CATEGORY_INPUT_OUTPUT_DISCRETE_TYPE_SELECT] = 7;
402 categories[CATEGORY_AUDIO_OUTPUT_LEVEL] = 0;
403 categories[CATEGORY_UNDEFINED_INPUT_SELECT_1] = 0;
404 categories[CATEGORY_UNDEFINED_INPUT_SELECT_2] = 0;
405 categories[CATEGORY_UNDEFINED_INPUT_SELECT_3] = 0;
408 static double m1_t1_min_agl1 (double vs) { return -1620 - 1.1133 * vs; }
409 static double m1_t1_min_agl2 (double vs) { return -400 - 0.4 * vs; }
410 static double m1_t4_min_agl1 (double vs) { return -1625.47 - 1.1167 * vs; }
411 static double m1_t4_min_agl2 (double vs) { return -0.1958 * vs; }
413 static int m3_t1_max_agl (bool flap_override) { return 1500; }
414 static double m3_t1_max_alt_loss (bool flap_override, double agl) { return 5.4 + 0.092 * agl; }
415 static int m3_t2_max_agl (bool flap_override) { return flap_override ? 815 : 925; }
416 static double m3_t2_max_alt_loss (bool flap_override, double agl)
418 int bias = agl > 700 ? 5 : 0;
421 return (9.0 + 0.184 * agl) + bias;
423 return (5.4 + 0.092 * agl) + bias;
426 static double m4_t1_min_agl2 (double airspeed) { return -1083 + 8.333 * airspeed; }
427 static double m4_t568_a_min_agl2 (double airspeed) { return -1523 + 11.36 * airspeed; }
428 static double m4_t79_a_min_agl2 (double airspeed) { return -1182 + 11.36 * airspeed; }
429 static double m4_t568_b_min_agl2 (double airspeed) { return -1570 + 11.6 * airspeed; }
430 static double m4_t79_b_min_agl2 (double airspeed) { return -1222 + 11.6 * airspeed; }
433 MK_VIII::ConfigurationModule::m6_t2_is_bank_angle (Parameter<double> *agl,
439 if (agl->ncd || agl->get() > 122)
440 return abs_roll_deg > 33;
444 if (agl->ncd || agl->get() > 2450)
445 return abs_roll_deg > 55;
446 else if (agl->get() > 150)
447 return agl->get() < 153.33333 * abs_roll_deg - 5983.3333;
451 return agl->get() < 4 * abs_roll_deg - 10;
452 else if (agl->get() > 5)
453 return abs_roll_deg > 10;
459 MK_VIII::ConfigurationModule::m6_t4_is_bank_angle (Parameter<double> *agl,
465 if (agl->ncd || agl->get() > 156)
466 return abs_roll_deg > 33;
470 if (agl->ncd || agl->get() > 210)
471 return abs_roll_deg > 50;
475 return agl->get() < 5.7142857 * abs_roll_deg - 75.714286;
481 MK_VIII::ConfigurationModule::read_aircraft_mode_type_select (int value)
483 static const Mode1Handler::EnvelopesConfiguration m1_t1 =
484 { false, 10, m1_t1_min_agl1, 284, m1_t1_min_agl2, 2450 };
485 static const Mode1Handler::EnvelopesConfiguration m1_t4 =
486 { true, 50, m1_t4_min_agl1, 346, m1_t4_min_agl2, 1958 };
488 static const Mode2Handler::Configuration m2_t1 = { 190, 280 };
489 static const Mode2Handler::Configuration m2_t4 = { 220, 310 };
490 static const Mode2Handler::Configuration m2_t5 = { 220, 310 };
492 static const Mode3Handler::Configuration m3_t1 =
493 { 30, m3_t1_max_agl, m3_t1_max_alt_loss };
494 static const Mode3Handler::Configuration m3_t2 =
495 { 50, m3_t2_max_agl, m3_t2_max_alt_loss };
497 static const Mode4Handler::EnvelopesConfiguration m4_t1_ac =
498 { 190, 250, 500, m4_t1_min_agl2, 1000 };
499 static const Mode4Handler::EnvelopesConfiguration m4_t5_ac =
500 { 178, 200, 500, m4_t568_a_min_agl2, 1000 };
501 static const Mode4Handler::EnvelopesConfiguration m4_t68_ac =
502 { 178, 200, 500, m4_t568_a_min_agl2, 750 };
503 static const Mode4Handler::EnvelopesConfiguration m4_t79_ac =
504 { 148, 170, 500, m4_t79_a_min_agl2, 750 };
506 static const Mode4Handler::EnvelopesConfiguration m4_t1_b =
507 { 159, 250, 245, m4_t1_min_agl2, 1000 };
508 static const Mode4Handler::EnvelopesConfiguration m4_t5_b =
509 { 148, 200, 200, m4_t568_b_min_agl2, 1000 };
510 static const Mode4Handler::EnvelopesConfiguration m4_t6_b =
511 { 150, 200, 170, m4_t568_b_min_agl2, 750 };
512 static const Mode4Handler::EnvelopesConfiguration m4_t7_b =
513 { 120, 170, 170, m4_t79_b_min_agl2, 750 };
514 static const Mode4Handler::EnvelopesConfiguration m4_t8_b =
515 { 148, 200, 150, m4_t568_b_min_agl2, 750 };
516 static const Mode4Handler::EnvelopesConfiguration m4_t9_b =
517 { 118, 170, 150, m4_t79_b_min_agl2, 750 };
519 static const Mode4Handler::ModesConfiguration m4_t1 = { &m4_t1_ac, &m4_t1_b };
520 static const Mode4Handler::ModesConfiguration m4_t5 = { &m4_t5_ac, &m4_t5_b };
521 static const Mode4Handler::ModesConfiguration m4_t6 = { &m4_t68_ac, &m4_t6_b };
522 static const Mode4Handler::ModesConfiguration m4_t7 = { &m4_t79_ac, &m4_t7_b };
523 static const Mode4Handler::ModesConfiguration m4_t8 = { &m4_t68_ac, &m4_t8_b };
524 static const Mode4Handler::ModesConfiguration m4_t9 = { &m4_t79_ac, &m4_t9_b };
526 static Mode6Handler::BankAnglePredicate m6_t2 = m6_t2_is_bank_angle;
527 static Mode6Handler::BankAnglePredicate m6_t4 = m6_t4_is_bank_angle;
529 static const IOHandler::FaultsConfiguration f_slow = { 180, 200 };
530 static const IOHandler::FaultsConfiguration f_fast = { 250, 290 };
535 const Mode1Handler::EnvelopesConfiguration *m1;
536 const Mode2Handler::Configuration *m2;
537 const Mode3Handler::Configuration *m3;
538 const Mode4Handler::ModesConfiguration *m4;
539 Mode6Handler::BankAnglePredicate m6;
540 const IOHandler::FaultsConfiguration *f;
542 } aircraft_types[] = {
543 { 0, &m1_t4, &m2_t4, &m3_t2, &m4_t6, m6_t4, &f_fast, 2000 },
544 { 1, &m1_t4, &m2_t4, &m3_t2, &m4_t8, m6_t4, &f_fast, 2000 },
545 { 2, &m1_t4, &m2_t4, &m3_t2, &m4_t5, m6_t4, &f_fast, 2000 },
546 { 3, &m1_t4, &m2_t5, &m3_t2, &m4_t7, m6_t4, &f_slow, 2000 },
547 { 4, &m1_t4, &m2_t5, &m3_t2, &m4_t9, m6_t4, &f_slow, 2000 },
548 { 254, &m1_t1, &m2_t1, &m3_t1, &m4_t1, m6_t2, &f_fast, 3500 },
549 { 255, &m1_t1, &m2_t1, &m3_t1, &m4_t1, m6_t2, &f_fast, 2000 }
552 for (size_t i = 0; i < n_elements(aircraft_types); i++)
553 if (aircraft_types[i].type == value)
555 mk->mode1_handler.conf.envelopes = aircraft_types[i].m1;
556 mk->mode2_handler.conf = aircraft_types[i].m2;
557 mk->mode3_handler.conf = aircraft_types[i].m3;
558 mk->mode4_handler.conf.modes = aircraft_types[i].m4;
559 mk->mode6_handler.conf.is_bank_angle = aircraft_types[i].m6;
560 mk->io_handler.conf.faults = aircraft_types[i].f;
561 mk->conf.runway_database = aircraft_types[i].runway_database;
565 state = STATE_INVALID_AIRCRAFT_TYPE;
570 MK_VIII::ConfigurationModule::read_air_data_input_select (int value)
573 return (value >= 0 && value <= 6) || (value >= 10 && value <= 13) || value == 255;
577 MK_VIII::ConfigurationModule::read_position_input_select (int value)
580 mk->io_handler.conf.use_internal_gps = true;
581 else if ((value >= 0 && value <= 5)
582 || (value >= 10 && value <= 13)
585 mk->io_handler.conf.use_internal_gps = false;
593 MK_VIII::ConfigurationModule::read_altitude_callouts (int value)
608 { 0, { MINIMUMS, SMART_500, 200, 100, 50, 40, 30, 20, 10, 0 } },
609 { 1, { MINIMUMS, SMART_500, 200, 0 } },
610 { 2, { MINIMUMS, SMART_500, 100, 50, 40, 30, 20, 10, 0 } },
611 { 3, { MINIMUMS, SMART_500, 0 } },
612 { 4, { MINIMUMS, 200, 100, 50, 40, 30, 20, 10, 0 } },
613 { 5, { MINIMUMS, 200, 0 } },
614 { 6, { MINIMUMS, 100, 50, 40, 30, 20, 10, 0 } },
616 { 8, { MINIMUMS, 0 } },
617 { 9, { MINIMUMS, 500, 200, 100, 50, 40, 30, 20, 10, 0 } },
618 { 10, { MINIMUMS, 500, 200, 0 } },
619 { 11, { MINIMUMS, 500, 100, 50, 40, 30, 20, 10, 0 } },
620 { 12, { MINIMUMS, 500, 0 } },
621 { 13, { MINIMUMS, 1000, 500, 400, 300, 200, 100, 50, 40, 30, 20, 10, 0 } },
622 { 14, { MINIMUMS, 100, 0 } },
623 { 15, { MINIMUMS, 200, 100, 0 } },
624 { 100, { FIELD_500, 0 } },
625 { 101, { FIELD_500_ABOVE, 0 } }
630 mk->mode6_handler.conf.minimums_enabled = false;
631 mk->mode6_handler.conf.smart_500_enabled = false;
632 mk->mode6_handler.conf.above_field_voice = NULL;
633 for (i = 0; i < n_altitude_callouts; i++)
634 mk->mode6_handler.conf.altitude_callouts_enabled[i] = false;
636 for (i = 0; i < n_elements(values); i++)
637 if (values[i].id == value)
639 for (int j = 0; values[i].callouts[j] != 0; j++)
640 switch (values[i].callouts[j])
643 mk->mode6_handler.conf.minimums_enabled = true;
647 mk->mode6_handler.conf.smart_500_enabled = true;
651 mk->mode6_handler.conf.above_field_voice = mk_altitude_voice(Mode6Handler::ALTITUDE_CALLOUT_500);
654 case FIELD_500_ABOVE:
655 mk->mode6_handler.conf.above_field_voice = mk_voice(five_hundred_above);
659 for (unsigned k = 0; k < n_altitude_callouts; k++)
660 if (mk->mode6_handler.altitude_callout_definitions[k] == values[i].callouts[j])
661 mk->mode6_handler.conf.altitude_callouts_enabled[k] = true;
672 MK_VIII::ConfigurationModule::read_audio_menu_select (int value)
674 if (value == 0 || value == 1)
675 mk->mode4_handler.conf.voice_too_low_gear = mk_voice(too_low_gear);
676 else if (value == 2 || value == 3)
677 mk->mode4_handler.conf.voice_too_low_gear = mk_voice(too_low_flaps);
685 MK_VIII::ConfigurationModule::read_terrain_display_select (int value)
688 mk->tcf_handler.conf.enabled = false;
689 else if (value == 0 || value == 1 || (value >= 3 && value <= 15)
690 || (value >= 18 && value <= 20) || (value >= 235 && value <= 255))
691 mk->tcf_handler.conf.enabled = true;
699 MK_VIII::ConfigurationModule::read_options_select_group_1 (int value)
701 if (value >= 0 && value < 128)
703 mk->io_handler.conf.flap_reversal = test_bits(value, 1 << 1);
704 mk->mode6_handler.conf.bank_angle_enabled = test_bits(value, 1 << 2);
705 mk->io_handler.conf.steep_approach_enabled = test_bits(value, 1 << 6);
713 MK_VIII::ConfigurationModule::read_radio_altitude_input_select (int value)
716 return (value >= 0 && value <= 4) || (value >= 251 && value <= 255);
720 MK_VIII::ConfigurationModule::read_navigation_input_select (int value)
722 if (value >= 0 && value <= 2)
723 mk->io_handler.conf.localizer_enabled = false;
724 else if ((value >= 3 && value <= 5) || (value >= 250 && value <= 255))
725 mk->io_handler.conf.localizer_enabled = true;
733 MK_VIII::ConfigurationModule::read_attitude_input_select (int value)
736 return (value >= 0 && value <= 6) || value == 253 || value == 255;
740 MK_VIII::ConfigurationModule::read_heading_input_select (int value)
743 return (value >= 0 && value <= 3) || value == 254 || value == 255;
747 MK_VIII::ConfigurationModule::read_windshear_input_select (int value)
750 return value == 0 || (value >= 253 && value <= 255);
754 MK_VIII::ConfigurationModule::read_input_output_discrete_type_select (int value)
759 IOHandler::LampConfiguration lamp_conf;
760 bool gpws_inhibit_enabled;
761 bool momentary_flap_override_enabled;
762 bool alternate_steep_approach;
764 { 0, { false, false }, false, true, true },
765 { 1, { true, false }, false, true, true },
766 { 2, { false, false }, true, true, true },
767 { 3, { true, false }, true, true, true },
768 { 4, { false, true }, true, true, true },
769 { 5, { true, true }, true, true, true },
770 { 6, { false, false }, true, true, false },
771 { 7, { true, false }, true, true, false },
772 { 254, { false, false }, true, false, true },
773 { 255, { false, false }, true, false, true }
776 for (size_t i = 0; i < n_elements(io_types); i++)
777 if (io_types[i].type == value)
779 mk->io_handler.conf.lamp = &io_types[i].lamp_conf;
780 mk->io_handler.conf.gpws_inhibit_enabled = io_types[i].gpws_inhibit_enabled;
781 mk->io_handler.conf.momentary_flap_override_enabled = io_types[i].momentary_flap_override_enabled;
782 mk->io_handler.conf.alternate_steep_approach = io_types[i].alternate_steep_approach;
790 MK_VIII::ConfigurationModule::read_audio_output_level (int value)
804 for (size_t i = 0; i < n_elements(values); i++)
805 if (values[i].id == value)
807 mk->voice_player.set_volume(mk->voice_player.conf.volume = modify_amplitude(1.0, values[i].relative_dB));
811 // The self test needs the voice player even when the configuration
812 // is invalid, so set a default value.
813 mk->voice_player.set_volume(mk->voice_player.conf.volume = 1.0);
818 MK_VIII::ConfigurationModule::read_undefined_input_select (int value)
825 MK_VIII::ConfigurationModule::boot ()
827 bool (MK_VIII::ConfigurationModule::*readers[N_CATEGORIES]) (int) = {
828 &MK_VIII::ConfigurationModule::read_aircraft_mode_type_select,
829 &MK_VIII::ConfigurationModule::read_air_data_input_select,
830 &MK_VIII::ConfigurationModule::read_position_input_select,
831 &MK_VIII::ConfigurationModule::read_altitude_callouts,
832 &MK_VIII::ConfigurationModule::read_audio_menu_select,
833 &MK_VIII::ConfigurationModule::read_terrain_display_select,
834 &MK_VIII::ConfigurationModule::read_options_select_group_1,
835 &MK_VIII::ConfigurationModule::read_radio_altitude_input_select,
836 &MK_VIII::ConfigurationModule::read_navigation_input_select,
837 &MK_VIII::ConfigurationModule::read_attitude_input_select,
838 &MK_VIII::ConfigurationModule::read_heading_input_select,
839 &MK_VIII::ConfigurationModule::read_windshear_input_select,
840 &MK_VIII::ConfigurationModule::read_input_output_discrete_type_select,
841 &MK_VIII::ConfigurationModule::read_audio_output_level,
842 &MK_VIII::ConfigurationModule::read_undefined_input_select,
843 &MK_VIII::ConfigurationModule::read_undefined_input_select,
844 &MK_VIII::ConfigurationModule::read_undefined_input_select
847 memcpy(effective_categories, categories, sizeof(categories));
850 for (int i = 0; i < N_CATEGORIES; i++)
851 if (! (this->*readers[i])(effective_categories[i]))
853 SG_LOG(SG_INSTR, SG_ALERT, "MK VIII EGPWS configuration category " << i + 1 << ": invalid value " << effective_categories[i]);
855 if (state == STATE_OK)
856 state = STATE_INVALID_DATABASE;
858 mk_doutput(gpws_inop) = true;
859 mk_doutput(tad_inop) = true;
866 if (mk->mode6_handler.conf.above_field_voice && ! mk->tcf_handler.conf.enabled)
869 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");
870 state = STATE_INVALID_DATABASE;
875 MK_VIII::ConfigurationModule::bind (SGPropertyNode *node)
877 for (int i = 0; i < N_CATEGORIES; i++)
879 std::ostringstream name;
880 name << "configuration-module/category-" << i + 1;
881 mk->properties_handler.tie(node, name.str().c_str(), SGRawValuePointer<int>(&categories[i]));
885 ///////////////////////////////////////////////////////////////////////////////
886 // FaultHandler ///////////////////////////////////////////////////////////////
887 ///////////////////////////////////////////////////////////////////////////////
889 // [INSTALL] only specifies that the faults cause a GPWS INOP
890 // indication. We arbitrarily set a TAD INOP when it makes sense.
891 const unsigned int MK_VIII::FaultHandler::fault_inops[] = {
892 INOP_GPWS | INOP_TAD, // [INSTALL] 3.15.1.3
893 INOP_GPWS, // [INSTALL] appendix E 6.6.2
894 INOP_GPWS, // [INSTALL] appendix E 6.6.4
895 INOP_GPWS, // [INSTALL] appendix E 6.6.6
896 INOP_GPWS | INOP_TAD, // [INSTALL] appendix E 6.6.7
897 INOP_GPWS, // [INSTALL] appendix E 6.6.13
898 INOP_GPWS, // [INSTALL] appendix E 6.6.25
899 INOP_GPWS, // [INSTALL] appendix E 6.6.27
900 INOP_TAD, // unspecified
901 INOP_GPWS, // unspecified
902 INOP_GPWS, // unspecified
903 // [INSTALL] 2.3.10.1 specifies that GPWS INOP is "activated during
904 // any detected partial or total failure of the GPWS modes 1-5", so
905 // do not set any INOP for mode 6 and bank angle.
908 INOP_TAD // unspecified
912 MK_VIII::FaultHandler::has_faults () const
914 for (int i = 0; i < N_FAULTS; i++)
922 MK_VIII::FaultHandler::has_faults (unsigned int inop)
924 for (int i = 0; i < N_FAULTS; i++)
925 if (faults[i] && test_bits(fault_inops[i], inop))
932 MK_VIII::FaultHandler::boot ()
934 memset(faults, 0, sizeof(faults));
938 MK_VIII::FaultHandler::set_fault (Fault fault)
942 faults[fault] = true;
944 mk->self_test_handler.set_inop();
946 if (test_bits(fault_inops[fault], INOP_GPWS))
948 mk_unset_alerts(~mk_alert(TCF_TOO_LOW_TERRAIN));
949 mk_doutput(gpws_inop) = true;
951 if (test_bits(fault_inops[fault], INOP_TAD))
953 mk_unset_alerts(mk_alert(TCF_TOO_LOW_TERRAIN));
954 mk_doutput(tad_inop) = true;
960 MK_VIII::FaultHandler::unset_fault (Fault fault)
964 faults[fault] = false;
965 if (! has_faults(INOP_GPWS))
966 mk_doutput(gpws_inop) = false;
967 if (! has_faults(INOP_TAD))
968 mk_doutput(tad_inop) = false;
972 ///////////////////////////////////////////////////////////////////////////////
973 // IOHandler //////////////////////////////////////////////////////////////////
974 ///////////////////////////////////////////////////////////////////////////////
977 MK_VIII::IOHandler::TerrainClearanceFilter::update (double agl)
979 // [PILOT] page 20 specifies that the terrain clearance is equal to
980 // 75% of the radio altitude, averaged over the previous 15 seconds.
982 samples_type::iterator iter;
984 // remove samples older than 15 seconds
985 for (iter = samples.begin(); iter != samples.end() && globals->get_sim_time_sec() - (*iter).timestamp >= 15; iter = samples.begin())
989 samples.push_back(Sample<double>(agl));
992 double new_value = 0;
993 if (samples.size() > 0)
995 for (iter = samples.begin(); iter != samples.end(); iter++)
996 new_value += (*iter).value;
997 new_value /= samples.size();
1001 if (new_value > value)
1008 MK_VIII::IOHandler::TerrainClearanceFilter::reset ()
1014 MK_VIII::IOHandler::IOHandler (MK_VIII *device)
1015 : mk(device), _lamp(LAMP_NONE)
1017 memset(&input_feeders, 0, sizeof(input_feeders));
1018 memset(&inputs.discretes, 0, sizeof(inputs.discretes));
1019 memset(&outputs, 0, sizeof(outputs));
1020 memset(&power_saved, 0, sizeof(power_saved));
1022 mk_dinput_feed(landing_gear) = true;
1023 mk_dinput_feed(landing_flaps) = true;
1024 mk_dinput_feed(glideslope_inhibit) = true;
1025 mk_dinput_feed(decision_height) = true;
1026 mk_dinput_feed(autopilot_engaged) = true;
1027 mk_ainput_feed(uncorrected_barometric_altitude) = true;
1028 mk_ainput_feed(barometric_altitude_rate) = true;
1029 mk_ainput_feed(radio_altitude) = true;
1030 mk_ainput_feed(glideslope_deviation) = true;
1031 mk_ainput_feed(roll_angle) = true;
1032 mk_ainput_feed(localizer_deviation) = true;
1033 mk_ainput_feed(computed_airspeed) = true;
1035 // will be unset on power on
1036 mk_doutput(gpws_inop) = true;
1037 mk_doutput(tad_inop) = true;
1041 MK_VIII::IOHandler::boot ()
1043 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
1046 mk_doutput(gpws_inop) = false;
1047 mk_doutput(tad_inop) = false;
1049 mk_doutput(glideslope_cancel) = power_saved.glideslope_cancel;
1051 altitude_samples.clear();
1055 MK_VIII::IOHandler::post_boot ()
1057 if (momentary_steep_approach_enabled())
1059 last_landing_gear = mk_dinput(landing_gear);
1060 last_real_flaps_down = real_flaps_down();
1063 // read externally-latching input discretes
1064 update_alternate_discrete_input(&mk_dinput(mode6_low_volume));
1065 update_alternate_discrete_input(&mk_dinput(audio_inhibit));
1069 MK_VIII::IOHandler::power_off ()
1071 power_saved.glideslope_cancel = mk_doutput(glideslope_cancel); // [SPEC] 6.2.5
1073 memset(&outputs, 0, sizeof(outputs));
1075 audio_inhibit_fault_timer.stop();
1076 landing_gear_fault_timer.stop();
1077 flaps_down_fault_timer.stop();
1078 momentary_flap_override_fault_timer.stop();
1079 self_test_fault_timer.stop();
1080 glideslope_cancel_fault_timer.stop();
1081 steep_approach_fault_timer.stop();
1082 gpws_inhibit_fault_timer.stop();
1083 ta_tcf_inhibit_fault_timer.stop();
1086 mk_doutput(gpws_inop) = true;
1087 mk_doutput(tad_inop) = true;
1091 MK_VIII::IOHandler::enter_ground ()
1093 reset_terrain_clearance();
1095 if (conf.momentary_flap_override_enabled)
1096 mk_doutput(flap_override) = false; // [INSTALL] 3.15.1.6
1100 MK_VIII::IOHandler::enter_takeoff ()
1102 reset_terrain_clearance();
1104 if (momentary_steep_approach_enabled())
1105 // landing or go-around, disable steep approach as per [SPEC] 6.2.1
1106 mk_doutput(steep_approach) = false;
1110 MK_VIII::IOHandler::update_inputs ()
1112 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
1115 // 1. process input feeders
1117 if (mk_dinput_feed(landing_gear))
1118 mk_dinput(landing_gear) = mk_node(gear_down)->getBoolValue();
1119 if (mk_dinput_feed(landing_flaps))
1120 mk_dinput(landing_flaps) = mk_node(flaps)->getDoubleValue() < 1;
1121 if (mk_dinput_feed(glideslope_inhibit))
1122 mk_dinput(glideslope_inhibit) = mk_node(nav0_gs_distance)->getDoubleValue() < 0;
1123 if (mk_dinput_feed(autopilot_engaged))
1127 mode = mk_node(autopilot_heading_lock)->getStringValue();
1128 mk_dinput(autopilot_engaged) = mode && *mode;
1131 if (mk_ainput_feed(uncorrected_barometric_altitude))
1133 if (mk_node(altimeter_serviceable)->getBoolValue())
1134 mk_ainput(uncorrected_barometric_altitude).set(mk_node(altimeter_altitude)->getDoubleValue());
1136 mk_ainput(uncorrected_barometric_altitude).unset();
1138 if (mk_ainput_feed(barometric_altitude_rate))
1139 // Do not use the vsi, because it is pressure-based only, and
1140 // therefore too laggy for GPWS alerting purposes. I guess that
1141 // a real ADC combines pressure-based and inerta-based altitude
1142 // rates to provide a non-laggy rate. That non-laggy rate is
1143 // best emulated by /velocities/vertical-speed-fps * 60.
1144 mk_ainput(barometric_altitude_rate).set(mk_node(vs)->getDoubleValue() * 60);
1145 if (mk_ainput_feed(radio_altitude))
1147 // Some flight models may return negative values when on the
1148 // ground or after a crash; do not allow them.
1149 double agl = mk_node(altitude_agl)->getDoubleValue();
1150 mk_ainput(radio_altitude).set(SG_MAX2(0.0, agl));
1152 if (mk_ainput_feed(glideslope_deviation))
1154 if (mk_node(nav0_serviceable)->getBoolValue()
1155 && mk_node(nav0_gs_serviceable)->getBoolValue()
1156 && mk_node(nav0_in_range)->getBoolValue()
1157 && mk_node(nav0_has_gs)->getBoolValue())
1158 // gs-needle-deflection is expressed in degrees, and 1 dot =
1159 // 0.32 degrees (according to
1160 // http://www.ntsb.gov/Recs/letters/2000/A00_41_45.pdf)
1161 mk_ainput(glideslope_deviation).set(mk_node(nav0_gs_needle_deflection)->getDoubleValue() / 0.32 * GLIDESLOPE_DOTS_TO_DDM);
1163 mk_ainput(glideslope_deviation).unset();
1165 if (mk_ainput_feed(roll_angle))
1167 if (mk_node(ai_serviceable)->getBoolValue() && ! mk_node(ai_caged)->getBoolValue())
1168 mk_ainput(roll_angle).set(mk_node(ai_roll)->getDoubleValue());
1170 mk_ainput(roll_angle).unset();
1172 if (mk_ainput_feed(localizer_deviation))
1174 if (mk_node(nav0_serviceable)->getBoolValue()
1175 && mk_node(nav0_cdi_serviceable)->getBoolValue()
1176 && mk_node(nav0_in_range)->getBoolValue()
1177 && mk_node(nav0_nav_loc)->getBoolValue())
1178 // heading-needle-deflection is expressed in degrees, and 1
1179 // dot = 2 degrees (0.5 degrees for ILS, but navradio.cxx
1180 // performs the conversion)
1181 mk_ainput(localizer_deviation).set(mk_node(nav0_heading_needle_deflection)->getDoubleValue() / 2 * LOCALIZER_DOTS_TO_DDM);
1183 mk_ainput(localizer_deviation).unset();
1185 if (mk_ainput_feed(computed_airspeed))
1187 if (mk_node(asi_serviceable)->getBoolValue())
1188 mk_ainput(computed_airspeed).set(mk_node(asi_speed)->getDoubleValue());
1190 mk_ainput(computed_airspeed).unset();
1195 mk_data(decision_height).set(&mk_ainput(decision_height));
1196 mk_data(radio_altitude).set(&mk_ainput(radio_altitude));
1197 mk_data(roll_angle).set(&mk_ainput(roll_angle));
1199 // update barometric_altitude_rate as per [SPEC] 6.2.1: "During
1200 // normal conditions, the system will base Mode 1 computations upon
1201 // barometric rate from the ADC. If this computed data is not valid
1202 // or available then the system will use internally computed
1203 // barometric altitude rate."
1205 if (! mk_ainput(barometric_altitude_rate).ncd)
1206 // the altitude rate input is valid, use it
1207 mk_data(barometric_altitude_rate).set(mk_ainput(barometric_altitude_rate).get());
1212 // The altitude rate input is invalid. We can compute an
1213 // altitude rate if all the following conditions are true:
1215 // - the altitude input is valid
1216 // - there is an altitude sample with age >= 1 second
1217 // - that altitude sample is valid
1219 if (! mk_ainput(uncorrected_barometric_altitude).ncd)
1221 if (! altitude_samples.empty() && ! altitude_samples.front().value.ncd)
1223 double elapsed = globals->get_sim_time_sec() - altitude_samples.front().timestamp;
1227 mk_data(barometric_altitude_rate).set((mk_ainput(uncorrected_barometric_altitude).get() - altitude_samples.front().value.get()) / elapsed * 60);
1233 mk_data(barometric_altitude_rate).unset();
1236 altitude_samples.push_back(Sample< Parameter<double> >(mk_ainput(uncorrected_barometric_altitude)));
1238 // Erase everything from the beginning of the list up to the sample
1239 // preceding the most recent sample whose age is >= 1 second.
1241 altitude_samples_type::iterator erase_last = altitude_samples.begin();
1242 altitude_samples_type::iterator iter;
1244 for (iter = altitude_samples.begin(); iter != altitude_samples.end(); iter++)
1245 if (globals->get_sim_time_sec() - (*iter).timestamp >= 1)
1250 if (erase_last != altitude_samples.begin())
1251 altitude_samples.erase(altitude_samples.begin(), erase_last);
1255 if (conf.use_internal_gps)
1257 mk_data(gps_altitude).set(mk_node(altitude)->getDoubleValue());
1258 mk_data(gps_latitude).set(mk_node(latitude)->getDoubleValue());
1259 mk_data(gps_longitude).set(mk_node(longitude)->getDoubleValue());
1260 mk_data(gps_vertical_figure_of_merit).set(0.0);
1264 mk_data(gps_altitude).set(&mk_ainput(gps_altitude));
1265 mk_data(gps_latitude).set(&mk_ainput(gps_latitude));
1266 mk_data(gps_longitude).set(&mk_ainput(gps_longitude));
1267 mk_data(gps_vertical_figure_of_merit).set(&mk_ainput(gps_vertical_figure_of_merit));
1270 // update glideslope and localizer
1272 mk_data(glideslope_deviation_dots).set(&mk_ainput(glideslope_deviation), GLIDESLOPE_DDM_TO_DOTS);
1273 mk_data(localizer_deviation_dots).set(&mk_ainput(localizer_deviation), LOCALIZER_DDM_TO_DOTS);
1275 // Update geometric altitude; [SPEC] 6.7.8 provides an overview of a
1276 // complex algorithm which combines several input sources to
1277 // calculate the geometric altitude. Since the exact algorithm is
1278 // not given, we fallback to a much simpler method.
1280 if (! mk_data(gps_altitude).ncd)
1281 mk_data(geometric_altitude).set(mk_data(gps_altitude).get());
1282 else if (! mk_ainput(uncorrected_barometric_altitude).ncd)
1283 mk_data(geometric_altitude).set(mk_ainput(uncorrected_barometric_altitude).get());
1285 mk_data(geometric_altitude).unset();
1287 // update terrain clearance
1289 update_terrain_clearance();
1291 // 3. perform sanity checks
1293 if (! mk_data(radio_altitude).ncd && mk_data(radio_altitude).get() < 0)
1294 mk_data(radio_altitude).unset();
1296 if (! mk_data(decision_height).ncd && mk_data(decision_height).get() < 0)
1297 mk_data(decision_height).unset();
1299 if (! mk_data(gps_latitude).ncd
1300 && (mk_data(gps_latitude).get() < -90
1301 || mk_data(gps_latitude).get() > 90))
1302 mk_data(gps_latitude).unset();
1304 if (! mk_data(gps_longitude).ncd
1305 && (mk_data(gps_longitude).get() < -180
1306 || mk_data(gps_longitude).get() > 180))
1307 mk_data(gps_longitude).unset();
1309 if (! mk_data(roll_angle).ncd
1310 && ((mk_data(roll_angle).get() < -180)
1311 || (mk_data(roll_angle).get() > 180)))
1312 mk_data(roll_angle).unset();
1314 // 4. process input feeders requiring data computed above
1316 if (mk_dinput_feed(decision_height))
1317 mk_dinput(decision_height) = ! mk_data(radio_altitude).ncd
1318 && ! mk_data(decision_height).ncd
1319 && mk_data(radio_altitude).get() <= mk_data(decision_height).get();
1323 MK_VIII::IOHandler::update_terrain_clearance ()
1325 if (! mk_data(radio_altitude).ncd)
1326 mk_data(terrain_clearance).set(terrain_clearance_filter.update(mk_data(radio_altitude).get()));
1328 mk_data(terrain_clearance).unset();
1332 MK_VIII::IOHandler::reset_terrain_clearance ()
1334 terrain_clearance_filter.reset();
1335 update_terrain_clearance();
1339 MK_VIII::IOHandler::handle_input_fault (bool test, FaultHandler::Fault fault)
1343 if (! mk->fault_handler.faults[fault])
1344 mk->fault_handler.set_fault(fault);
1347 mk->fault_handler.unset_fault(fault);
1351 MK_VIII::IOHandler::handle_input_fault (bool test,
1353 double max_duration,
1354 FaultHandler::Fault fault)
1358 if (! mk->fault_handler.faults[fault])
1360 if (timer->start_or_elapsed() >= max_duration)
1361 mk->fault_handler.set_fault(fault);
1366 mk->fault_handler.unset_fault(fault);
1372 MK_VIII::IOHandler::update_input_faults ()
1374 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
1377 // [INSTALL] 3.15.1.3
1378 handle_input_fault(mk_dinput(audio_inhibit),
1379 &audio_inhibit_fault_timer,
1381 FaultHandler::FAULT_ALL_MODES_INHIBIT);
1383 // [INSTALL] appendix E 6.6.2
1384 handle_input_fault(mk_dinput(landing_gear)
1385 && ! mk_ainput(computed_airspeed).ncd
1386 && mk_ainput(computed_airspeed).get() > conf.faults->max_gear_down_airspeed,
1387 &landing_gear_fault_timer,
1389 FaultHandler::FAULT_GEAR_SWITCH);
1391 // [INSTALL] appendix E 6.6.4
1392 handle_input_fault(flaps_down()
1393 && ! mk_ainput(computed_airspeed).ncd
1394 && mk_ainput(computed_airspeed).get() > conf.faults->max_flaps_down_airspeed,
1395 &flaps_down_fault_timer,
1397 FaultHandler::FAULT_FLAPS_SWITCH);
1399 // [INSTALL] appendix E 6.6.6
1400 if (conf.momentary_flap_override_enabled)
1401 handle_input_fault(mk_dinput(momentary_flap_override),
1402 &momentary_flap_override_fault_timer,
1404 FaultHandler::FAULT_MOMENTARY_FLAP_OVERRIDE_INVALID);
1406 // [INSTALL] appendix E 6.6.7
1407 handle_input_fault(mk_dinput(self_test),
1408 &self_test_fault_timer,
1410 FaultHandler::FAULT_SELF_TEST_INVALID);
1412 // [INSTALL] appendix E 6.6.13
1413 handle_input_fault(mk_dinput(glideslope_cancel),
1414 &glideslope_cancel_fault_timer,
1416 FaultHandler::FAULT_GLIDESLOPE_CANCEL_INVALID);
1418 // [INSTALL] appendix E 6.6.25
1419 if (momentary_steep_approach_enabled())
1420 handle_input_fault(mk_dinput(steep_approach),
1421 &steep_approach_fault_timer,
1423 FaultHandler::FAULT_STEEP_APPROACH_INVALID);
1425 // [INSTALL] appendix E 6.6.27
1426 if (conf.gpws_inhibit_enabled)
1427 handle_input_fault(mk_dinput(gpws_inhibit),
1428 &gpws_inhibit_fault_timer,
1430 FaultHandler::FAULT_GPWS_INHIBIT);
1432 // [INSTALL] does not specify a fault for this one, but it makes
1433 // sense to have it behave like GPWS inhibit
1434 handle_input_fault(mk_dinput(ta_tcf_inhibit),
1435 &ta_tcf_inhibit_fault_timer,
1437 FaultHandler::FAULT_TA_TCF_INHIBIT);
1439 // [PILOT] page 48: "In the event that required data for a
1440 // particular function is not available, then that function is
1441 // automatically inhibited and annunciated"
1443 handle_input_fault(mk_data(radio_altitude).ncd
1444 || mk_ainput(uncorrected_barometric_altitude).ncd
1445 || mk_data(barometric_altitude_rate).ncd
1446 || mk_ainput(computed_airspeed).ncd
1447 || mk_data(terrain_clearance).ncd,
1448 FaultHandler::FAULT_MODES14_INPUTS_INVALID);
1450 if (! mk_dinput(glideslope_inhibit))
1451 handle_input_fault(mk_data(radio_altitude).ncd,
1452 FaultHandler::FAULT_MODE5_INPUTS_INVALID);
1454 if (mk->mode6_handler.altitude_callouts_enabled())
1455 handle_input_fault(mk->mode6_handler.conf.above_field_voice
1456 ? (mk_data(gps_latitude).ncd
1457 || mk_data(gps_longitude).ncd
1458 || mk_data(geometric_altitude).ncd)
1459 : mk_data(radio_altitude).ncd,
1460 FaultHandler::FAULT_MODE6_INPUTS_INVALID);
1462 if (mk->mode6_handler.conf.bank_angle_enabled)
1463 handle_input_fault(mk_data(roll_angle).ncd,
1464 FaultHandler::FAULT_BANK_ANGLE_INPUTS_INVALID);
1466 if (mk->tcf_handler.conf.enabled)
1467 handle_input_fault(mk_data(radio_altitude).ncd
1468 || mk_data(geometric_altitude).ncd
1469 || mk_data(gps_latitude).ncd
1470 || mk_data(gps_longitude).ncd
1471 || mk_data(gps_vertical_figure_of_merit).ncd,
1472 FaultHandler::FAULT_TCF_INPUTS_INVALID);
1476 MK_VIII::IOHandler::update_alternate_discrete_input (bool *ptr)
1478 assert(mk->system_handler.state == SystemHandler::STATE_ON);
1480 if (ptr == &mk_dinput(mode6_low_volume))
1482 if (mk->configuration_module.state == ConfigurationModule::STATE_OK
1483 && mk->self_test_handler.state == SelfTestHandler::STATE_NONE)
1484 mk->mode6_handler.set_volume(*ptr ? modify_amplitude(1.0, -6) : 1.0);
1486 else if (ptr == &mk_dinput(audio_inhibit))
1488 if (mk->configuration_module.state == ConfigurationModule::STATE_OK
1489 && mk->self_test_handler.state == SelfTestHandler::STATE_NONE)
1490 mk->voice_player.set_volume(*ptr ? 0.0 : mk->voice_player.conf.volume);
1495 MK_VIII::IOHandler::update_internal_latches ()
1497 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
1501 if (conf.momentary_flap_override_enabled
1502 && mk_doutput(flap_override)
1503 && ! mk->state_handler.takeoff
1504 && (mk_data(radio_altitude).ncd || mk_data(radio_altitude).get() <= 50))
1505 mk_doutput(flap_override) = false;
1508 if (momentary_steep_approach_enabled())
1510 if (mk_doutput(steep_approach)
1511 && ! mk->state_handler.takeoff
1512 && ((last_landing_gear && ! mk_dinput(landing_gear))
1513 || (last_real_flaps_down && ! real_flaps_down())))
1514 // gear or flaps raised during approach: it's a go-around
1515 mk_doutput(steep_approach) = false;
1517 last_landing_gear = mk_dinput(landing_gear);
1518 last_real_flaps_down = real_flaps_down();
1522 if (mk_doutput(glideslope_cancel)
1523 && (mk_data(glideslope_deviation_dots).ncd
1524 || mk_data(radio_altitude).ncd
1525 || mk_data(radio_altitude).get() > 2000
1526 || mk_data(radio_altitude).get() < 30))
1527 mk_doutput(glideslope_cancel) = false;
1531 MK_VIII::IOHandler::update_egpws_alert_discrete_1 ()
1533 if (mk->voice_player.voice)
1538 VoicePlayer::Voice *voice;
1540 { 11, mk_voice(sink_rate_pause_sink_rate) },
1541 { 12, mk_voice(pull_up) },
1542 { 13, mk_voice(terrain) },
1543 { 13, mk_voice(terrain_pause_terrain) },
1544 { 14, mk_voice(dont_sink_pause_dont_sink) },
1545 { 15, mk_voice(too_low_gear) },
1546 { 16, mk_voice(too_low_flaps) },
1547 { 17, mk_voice(too_low_terrain) },
1548 { 18, mk_voice(soft_glideslope) },
1549 { 18, mk_voice(hard_glideslope) },
1550 { 19, mk_voice(minimums_minimums) }
1553 for (size_t i = 0; i < n_elements(voices); i++)
1554 if (voices[i].voice == mk->voice_player.voice)
1556 mk_aoutput(egpws_alert_discrete_1) = 1 << voices[i].bit;
1561 mk_aoutput(egpws_alert_discrete_1) = 0;
1565 MK_VIII::IOHandler::update_egpwc_logic_discretes ()
1567 mk_aoutput(egpwc_logic_discretes) = 0;
1569 if (mk->state_handler.takeoff)
1570 mk_aoutput(egpwc_logic_discretes) |= 1 << 18;
1575 unsigned int alerts;
1577 { 13, mk_alert(TCF_TOO_LOW_TERRAIN) },
1578 { 19, mk_alert(MODE1_SINK_RATE) },
1579 { 20, mk_alert(MODE1_PULL_UP) },
1580 { 21, mk_alert(MODE2A_PREFACE) | mk_alert(MODE2B_PREFACE) | mk_alert(MODE2B_LANDING_MODE) | mk_alert(MODE2A_ALTITUDE_GAIN_TERRAIN_CLOSING) },
1581 { 22, mk_alert(MODE2A) | mk_alert(MODE2B) },
1582 { 23, mk_alert(MODE3) },
1583 { 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) },
1584 { 25, mk_alert(MODE5_SOFT) | mk_alert(MODE5_HARD) }
1587 for (size_t i = 0; i < n_elements(logic); i++)
1588 if (mk_test_alerts(logic[i].alerts))
1589 mk_aoutput(egpwc_logic_discretes) |= 1 << logic[i].bit;
1593 MK_VIII::IOHandler::update_mode6_callouts_discrete_1 ()
1595 if (mk->voice_player.voice)
1600 VoicePlayer::Voice *voice;
1602 { 11, mk_voice(minimums_minimums) },
1603 { 16, mk_altitude_voice(Mode6Handler::ALTITUDE_CALLOUT_10) },
1604 { 17, mk_altitude_voice(Mode6Handler::ALTITUDE_CALLOUT_20) },
1605 { 18, mk_altitude_voice(Mode6Handler::ALTITUDE_CALLOUT_30) },
1606 { 19, mk_altitude_voice(Mode6Handler::ALTITUDE_CALLOUT_40) },
1607 { 20, mk_altitude_voice(Mode6Handler::ALTITUDE_CALLOUT_50) },
1608 { 23, mk_altitude_voice(Mode6Handler::ALTITUDE_CALLOUT_100) },
1609 { 24, mk_altitude_voice(Mode6Handler::ALTITUDE_CALLOUT_200) },
1610 { 25, mk_altitude_voice(Mode6Handler::ALTITUDE_CALLOUT_300) }
1613 for (size_t i = 0; i < n_elements(voices); i++)
1614 if (voices[i].voice == mk->voice_player.voice)
1616 mk_aoutput(mode6_callouts_discrete_1) = 1 << voices[i].bit;
1621 mk_aoutput(mode6_callouts_discrete_1) = 0;
1625 MK_VIII::IOHandler::update_mode6_callouts_discrete_2 ()
1627 if (mk->voice_player.voice)
1632 VoicePlayer::Voice *voice;
1634 { 11, mk_altitude_voice(Mode6Handler::ALTITUDE_CALLOUT_400) },
1635 { 12, mk_altitude_voice(Mode6Handler::ALTITUDE_CALLOUT_500) },
1636 { 13, mk_altitude_voice(Mode6Handler::ALTITUDE_CALLOUT_1000) },
1637 { 18, mk_voice(bank_angle_pause_bank_angle) },
1638 { 18, mk_voice(bank_angle_pause_bank_angle_3) },
1639 { 23, mk_voice(five_hundred_above) }
1642 for (size_t i = 0; i < n_elements(voices); i++)
1643 if (voices[i].voice == mk->voice_player.voice)
1645 mk_aoutput(mode6_callouts_discrete_2) = 1 << voices[i].bit;
1650 mk_aoutput(mode6_callouts_discrete_2) = 0;
1654 MK_VIII::IOHandler::update_egpws_alert_discrete_2 ()
1656 mk_aoutput(egpws_alert_discrete_2) = 1 << 27; // Terrain NA
1658 if (mk_doutput(glideslope_cancel))
1659 mk_aoutput(egpws_alert_discrete_2) |= 1 << 11;
1660 if (mk_doutput(gpws_alert))
1661 mk_aoutput(egpws_alert_discrete_2) |= 1 << 12;
1662 if (mk_doutput(gpws_warning))
1663 mk_aoutput(egpws_alert_discrete_2) |= 1 << 13;
1664 if (mk_doutput(gpws_inop))
1665 mk_aoutput(egpws_alert_discrete_2) |= 1 << 14;
1666 if (mk_doutput(audio_on))
1667 mk_aoutput(egpws_alert_discrete_2) |= 1 << 16;
1668 if (mk_doutput(tad_inop))
1669 mk_aoutput(egpws_alert_discrete_2) |= 1 << 24;
1670 if (mk->fault_handler.has_faults())
1671 mk_aoutput(egpws_alert_discrete_2) |= 1 << 25;
1675 MK_VIII::IOHandler::update_egpwc_alert_discrete_3 ()
1677 mk_aoutput(egpwc_alert_discrete_3) = 1 << 17 | 1 << 18;
1679 if (mk->fault_handler.faults[FaultHandler::FAULT_MODES14_INPUTS_INVALID])
1680 mk_aoutput(egpwc_alert_discrete_3) |= 1 << 11;
1681 if (mk->fault_handler.faults[FaultHandler::FAULT_MODE5_INPUTS_INVALID])
1682 mk_aoutput(egpwc_alert_discrete_3) |= 1 << 12;
1683 if (mk->fault_handler.faults[FaultHandler::FAULT_MODE6_INPUTS_INVALID])
1684 mk_aoutput(egpwc_alert_discrete_3) |= 1 << 13;
1685 if (mk->fault_handler.faults[FaultHandler::FAULT_BANK_ANGLE_INPUTS_INVALID])
1686 mk_aoutput(egpwc_alert_discrete_3) |= 1 << 14;
1687 if (mk_doutput(tad_inop))
1688 mk_aoutput(egpwc_alert_discrete_3) |= 1 << 16;
1692 MK_VIII::IOHandler::update_outputs ()
1694 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
1697 mk_doutput(audio_on) = ! mk_dinput(audio_inhibit)
1698 && mk->voice_player.voice
1699 && ! mk->voice_player.voice->element->silence;
1701 update_egpws_alert_discrete_1();
1702 update_egpwc_logic_discretes();
1703 update_mode6_callouts_discrete_1();
1704 update_mode6_callouts_discrete_2();
1705 update_egpws_alert_discrete_2();
1706 update_egpwc_alert_discrete_3();
1710 MK_VIII::IOHandler::get_lamp_output (Lamp lamp)
1714 case LAMP_GLIDESLOPE:
1715 return &mk_doutput(gpws_alert);
1718 return conf.lamp->format2 ? &mk_doutput(gpws_alert) : &mk_doutput(gpws_warning);
1721 return &mk_doutput(gpws_warning);
1724 assert_not_reached();
1730 MK_VIII::IOHandler::update_lamps ()
1732 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
1735 if (_lamp != LAMP_NONE && conf.lamp->flashing)
1737 // [SPEC] 6.9.3: 70 cycles per minute
1738 if (lamp_timer.elapsed() >= 60.0 / 70.0 / 2.0)
1740 bool *output = get_lamp_output(_lamp);
1741 *output = ! *output;
1748 MK_VIII::IOHandler::set_lamp (Lamp lamp)
1755 mk_doutput(gpws_warning) = false;
1756 mk_doutput(gpws_alert) = false;
1758 if (lamp != LAMP_NONE)
1760 *get_lamp_output(lamp) = true;
1766 MK_VIII::IOHandler::gpws_inhibit () const
1768 return conf.gpws_inhibit_enabled && mk_dinput(gpws_inhibit);
1772 MK_VIII::IOHandler::real_flaps_down () const
1774 return conf.flap_reversal ? mk_dinput(landing_flaps) : ! mk_dinput(landing_flaps);
1778 MK_VIII::IOHandler::flaps_down () const
1780 return flap_override() ? true : real_flaps_down();
1784 MK_VIII::IOHandler::flap_override () const
1786 return conf.momentary_flap_override_enabled ? mk_doutput(flap_override) : false;
1790 MK_VIII::IOHandler::steep_approach () const
1792 if (conf.steep_approach_enabled)
1793 // If alternate action was configured in category 13, we return
1794 // the value of the input discrete (which should be connected to a
1795 // latching steep approach cockpit switch). Otherwise, we return
1796 // the value of the output discrete.
1797 return conf.alternate_steep_approach ? mk_dinput(steep_approach) : mk_doutput(steep_approach);
1803 MK_VIII::IOHandler::momentary_steep_approach_enabled () const
1805 return conf.steep_approach_enabled && ! conf.alternate_steep_approach;
1809 MK_VIII::IOHandler::tie_input (SGPropertyNode *node,
1814 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));
1816 mk->properties_handler.tie(node, (string("input-feeders/discretes/") + name).c_str(), SGRawValuePointer<bool>(feed));
1820 MK_VIII::IOHandler::tie_input (SGPropertyNode *node,
1822 Parameter<double> *input,
1825 mk->properties_handler.tie(node, (string("inputs/arinc429/") + name).c_str(), SGRawValuePointer<double>(input->get_pointer()));
1826 mk->properties_handler.tie(node, (string("inputs/arinc429/") + name + "-ncd").c_str(), SGRawValuePointer<bool>(&input->ncd));
1828 mk->properties_handler.tie(node, (string("input-feeders/arinc429/") + name).c_str(), SGRawValuePointer<bool>(feed));
1832 MK_VIII::IOHandler::tie_output (SGPropertyNode *node,
1836 SGPropertyNode *child = node->getNode((string("outputs/discretes/") + name).c_str(), true);
1838 mk->properties_handler.tie(child, SGRawValuePointer<bool>(output));
1839 child->setAttribute(SGPropertyNode::WRITE, false);
1843 MK_VIII::IOHandler::tie_output (SGPropertyNode *node,
1847 SGPropertyNode *child = node->getNode((string("outputs/arinc429/") + name).c_str(), true);
1849 mk->properties_handler.tie(child, SGRawValuePointer<int>(output));
1850 child->setAttribute(SGPropertyNode::WRITE, false);
1854 MK_VIII::IOHandler::bind (SGPropertyNode *node)
1856 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));
1858 tie_input(node, "landing-gear", &mk_dinput(landing_gear), &mk_dinput_feed(landing_gear));
1859 tie_input(node, "landing-flaps", &mk_dinput(landing_flaps), &mk_dinput_feed(landing_flaps));
1860 tie_input(node, "momentary-flap-override", &mk_dinput(momentary_flap_override));
1861 tie_input(node, "self-test", &mk_dinput(self_test));
1862 tie_input(node, "glideslope-inhibit", &mk_dinput(glideslope_inhibit), &mk_dinput_feed(glideslope_inhibit));
1863 tie_input(node, "glideslope-cancel", &mk_dinput(glideslope_cancel));
1864 tie_input(node, "decision-height", &mk_dinput(decision_height), &mk_dinput_feed(decision_height));
1865 tie_input(node, "mode6-low-volume", &mk_dinput(mode6_low_volume));
1866 tie_input(node, "audio-inhibit", &mk_dinput(audio_inhibit));
1867 tie_input(node, "ta-tcf-inhibit", &mk_dinput(ta_tcf_inhibit));
1868 tie_input(node, "autopilot-engaged", &mk_dinput(autopilot_engaged), &mk_dinput_feed(autopilot_engaged));
1869 tie_input(node, "steep-approach", &mk_dinput(steep_approach));
1870 tie_input(node, "gpws-inhibit", &mk_dinput(gpws_inhibit));
1872 tie_input(node, "uncorrected-barometric-altitude", &mk_ainput(uncorrected_barometric_altitude), &mk_ainput_feed(uncorrected_barometric_altitude));
1873 tie_input(node, "barometric-altitude-rate", &mk_ainput(barometric_altitude_rate), &mk_ainput_feed(barometric_altitude_rate));
1874 tie_input(node, "gps-altitude", &mk_ainput(gps_altitude));
1875 tie_input(node, "gps-latitude", &mk_ainput(gps_latitude));
1876 tie_input(node, "gps-longitude", &mk_ainput(gps_longitude));
1877 tie_input(node, "gps-vertical-figure-of-merit", &mk_ainput(gps_vertical_figure_of_merit));
1878 tie_input(node, "radio-altitude", &mk_ainput(radio_altitude), &mk_ainput_feed(radio_altitude));
1879 tie_input(node, "glideslope-deviation", &mk_ainput(glideslope_deviation), &mk_ainput_feed(glideslope_deviation));
1880 tie_input(node, "roll-angle", &mk_ainput(roll_angle), &mk_ainput_feed(roll_angle));
1881 tie_input(node, "localizer-deviation", &mk_ainput(localizer_deviation), &mk_ainput_feed(localizer_deviation));
1882 tie_input(node, "computed-airspeed", &mk_ainput(computed_airspeed), &mk_ainput_feed(computed_airspeed));
1883 tie_input(node, "decision-height", &mk_ainput(decision_height), &mk_ainput_feed(decision_height));
1885 tie_output(node, "gpws-warning", &mk_doutput(gpws_warning));
1886 tie_output(node, "gpws-alert", &mk_doutput(gpws_alert));
1887 tie_output(node, "audio-on", &mk_doutput(audio_on));
1888 tie_output(node, "gpws-inop", &mk_doutput(gpws_inop));
1889 tie_output(node, "tad-inop", &mk_doutput(tad_inop));
1890 tie_output(node, "flap-override", &mk_doutput(flap_override));
1891 tie_output(node, "glideslope-cancel", &mk_doutput(glideslope_cancel));
1892 tie_output(node, "steep-approach", &mk_doutput(steep_approach));
1894 tie_output(node, "egpws-alert-discrete-1", &mk_aoutput(egpws_alert_discrete_1));
1895 tie_output(node, "egpwc-logic-discretes", &mk_aoutput(egpwc_logic_discretes));
1896 tie_output(node, "mode6-callouts-discrete-1", &mk_aoutput(mode6_callouts_discrete_1));
1897 tie_output(node, "mode6-callouts-discrete-2", &mk_aoutput(mode6_callouts_discrete_2));
1898 tie_output(node, "egpws-alert-discrete-2", &mk_aoutput(egpws_alert_discrete_2));
1899 tie_output(node, "egpwc-alert-discrete-3", &mk_aoutput(egpwc_alert_discrete_3));
1903 MK_VIII::IOHandler::get_discrete_input (bool *ptr) const
1909 MK_VIII::IOHandler::set_discrete_input (bool *ptr, bool value)
1914 if (mk->system_handler.state == SystemHandler::STATE_ON)
1916 if (ptr == &mk_dinput(momentary_flap_override))
1918 if (mk->configuration_module.state == ConfigurationModule::STATE_OK
1919 && mk->self_test_handler.state == SelfTestHandler::STATE_NONE
1920 && conf.momentary_flap_override_enabled
1922 mk_doutput(flap_override) = ! mk_doutput(flap_override);
1924 else if (ptr == &mk_dinput(self_test))
1925 mk->self_test_handler.handle_button_event(value);
1926 else if (ptr == &mk_dinput(glideslope_cancel))
1928 if (mk->configuration_module.state == ConfigurationModule::STATE_OK
1929 && mk->self_test_handler.state == SelfTestHandler::STATE_NONE
1932 if (! mk_doutput(glideslope_cancel))
1936 // Although we are not called from update(), we are
1937 // sure that the inputs we use here are defined,
1938 // since state is STATE_ON.
1940 if (! mk_data(glideslope_deviation_dots).ncd
1941 && ! mk_data(radio_altitude).ncd
1942 && mk_data(radio_altitude).get() <= 2000
1943 && mk_data(radio_altitude).get() >= 30)
1944 mk_doutput(glideslope_cancel) = true;
1946 // else do nothing ([PILOT] page 22: "Glideslope Cancel
1947 // can not be deselected (reset) by again pressing the
1948 // Glideslope Cancel switch.")
1951 else if (ptr == &mk_dinput(steep_approach))
1953 if (mk->configuration_module.state == ConfigurationModule::STATE_OK
1954 && mk->self_test_handler.state == SelfTestHandler::STATE_NONE
1955 && momentary_steep_approach_enabled()
1957 mk_doutput(steep_approach) = ! mk_doutput(steep_approach);
1963 if (mk->system_handler.state == SystemHandler::STATE_ON)
1964 update_alternate_discrete_input(ptr);
1968 MK_VIII::IOHandler::present_status_section (const char *name)
1970 printf("%s\n", name);
1974 MK_VIII::IOHandler::present_status_item (const char *name, const char *value)
1977 printf("\t%-32s %s\n", name, value);
1979 printf("\t%s\n", name);
1983 MK_VIII::IOHandler::present_status_subitem (const char *name)
1985 printf("\t\t%s\n", name);
1989 MK_VIII::IOHandler::present_status ()
1993 if (mk->system_handler.state != SystemHandler::STATE_ON)
1996 present_status_section("EGPWC CONFIGURATION");
1997 present_status_item("PART NUMBER:", "965-1220-000"); // [SPEC] 1.1
1998 present_status_item("MOD STATUS:", "N/A");
1999 present_status_item("SERIAL NUMBER:", "N/A");
2001 present_status_item("APPLICATION S/W VERSION:", FLIGHTGEAR_VERSION);
2002 present_status_item("TERRAIN DATABASE VERSION:", FLIGHTGEAR_VERSION);
2003 present_status_item("ENVELOPE MOD DATABASE VERSION:", "N/A");
2004 present_status_item("BOOT CODE VERSION:", FLIGHTGEAR_VERSION);
2007 present_status_section("CURRENT FAULTS");
2008 if (mk->configuration_module.state == ConfigurationModule::STATE_OK && ! mk->fault_handler.has_faults())
2009 present_status_item("NO FAULTS");
2012 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
2014 present_status_item("GPWS COMPUTER FAULTS:");
2015 switch (mk->configuration_module.state)
2017 case ConfigurationModule::STATE_INVALID_DATABASE:
2018 present_status_subitem("APPLICATION DATABASE FAILED");
2021 case ConfigurationModule::STATE_INVALID_AIRCRAFT_TYPE:
2022 present_status_subitem("CONFIGURATION TYPE INVALID");
2026 assert_not_reached();
2032 present_status_item("GPWS COMPUTER OK");
2033 present_status_item("GPWS EXTERNAL FAULTS:");
2035 static const char *fault_names[] = {
2036 "ALL MODES INHIBIT",
2039 "MOMENTARY FLAP OVERRIDE INVALID",
2040 "SELF TEST INVALID",
2041 "GLIDESLOPE CANCEL INVALID",
2042 "STEEP APPROACH INVALID",
2045 "MODES 1-4 INPUTS INVALID",
2046 "MODE 5 INPUTS INVALID",
2047 "MODE 6 INPUTS INVALID",
2048 "BANK ANGLE INPUTS INVALID",
2049 "TCF INPUTS INVALID"
2052 for (size_t i = 0; i < n_elements(fault_names); i++)
2053 if (mk->fault_handler.faults[i])
2054 present_status_subitem(fault_names[i]);
2059 present_status_section("CONFIGURATION:");
2061 static const char *category_names[] = {
2064 "POSITION INPUT TYPE",
2065 "CALLOUTS OPTION TYPE",
2067 "TERRAIN DISPLAY TYPE",
2069 "RADIO ALTITUDE TYPE",
2070 "NAVIGATION INPUT TYPE",
2072 "MAGNETIC HEADING TYPE",
2073 "WINDSHEAR INPUT TYPE",
2078 for (size_t i = 0; i < n_elements(category_names); i++)
2080 std::ostringstream value;
2081 value << "= " << mk->configuration_module.effective_categories[i];
2082 present_status_item(category_names[i], value.str().c_str());
2087 MK_VIII::IOHandler::get_present_status () const
2093 MK_VIII::IOHandler::set_present_status (bool value)
2095 if (value) present_status();
2099 ///////////////////////////////////////////////////////////////////////////////
2100 // VoicePlayer ////////////////////////////////////////////////////////////////
2101 ///////////////////////////////////////////////////////////////////////////////
2104 MK_VIII::VoicePlayer::Speaker::bind (SGPropertyNode *node)
2106 // uses xmlsound property names
2107 tie(node, "volume", &volume);
2108 tie(node, "pitch", &pitch);
2112 MK_VIII::VoicePlayer::Speaker::update_configuration ()
2114 map< string, SGSharedPtr<SGSoundSample> >::iterator iter;
2115 for (iter = player->samples.begin(); iter != player->samples.end(); iter++)
2117 SGSoundSample *sample = (*iter).second;
2119 sample->set_pitch(pitch);
2123 player->voice->volume_changed();
2126 MK_VIII::VoicePlayer::Voice::~Voice ()
2128 for (iter = elements.begin(); iter != elements.end(); iter++)
2129 delete *iter; // we owned the element
2134 MK_VIII::VoicePlayer::Voice::play ()
2136 iter = elements.begin();
2139 element->play(get_volume());
2143 MK_VIII::VoicePlayer::Voice::stop (bool now)
2147 if (now || element->silence)
2153 iter = elements.end() - 1; // stop after the current element finishes
2158 MK_VIII::VoicePlayer::Voice::set_volume (float _volume)
2165 MK_VIII::VoicePlayer::Voice::volume_changed ()
2168 element->set_volume(get_volume());
2172 MK_VIII::VoicePlayer::Voice::update ()
2176 if (! element->is_playing())
2178 if (++iter == elements.end())
2183 element->play(get_volume());
2189 MK_VIII::VoicePlayer::~VoicePlayer ()
2191 vector<Voice *>::iterator iter1;
2192 for (iter1 = _voices.begin(); iter1 != _voices.end(); iter1++)
2199 MK_VIII::VoicePlayer::init ()
2201 #define STDPAUSE 0.75 // [SPEC] 6.4.4: "the standard 0.75 second delay"
2203 SGSoundMgr *smgr = globals->get_soundmgr();
2204 _sgr = smgr->find("avionics", true);
2205 _sgr->tie_to_listener();
2207 make_voice(&voices.application_data_base_failed, "application-data-base-failed");
2208 make_voice(&voices.bank_angle, "bank-angle");
2209 make_voice(&voices.bank_angle_bank_angle, "bank-angle", "bank-angle");
2210 make_voice(&voices.bank_angle_bank_angle_3, "bank-angle", "bank-angle", 3.0);
2211 make_voice(&voices.bank_angle_inop, "bank-angle-inop");
2212 make_voice(&voices.bank_angle_pause_bank_angle, "bank-angle", STDPAUSE, "bank-angle");
2213 make_voice(&voices.bank_angle_pause_bank_angle_3, "bank-angle", STDPAUSE, "bank-angle", 3.0);
2214 make_voice(&voices.callouts_inop, "callouts-inop");
2215 make_voice(&voices.configuration_type_invalid, "configuration-type-invalid");
2216 make_voice(&voices.dont_sink, "dont-sink");
2217 make_voice(&voices.dont_sink_pause_dont_sink, "dont-sink", STDPAUSE, "dont-sink");
2218 make_voice(&voices.five_hundred_above, "500-above");
2219 make_voice(&voices.glideslope, "glideslope");
2220 make_voice(&voices.glideslope_inop, "glideslope-inop");
2221 make_voice(&voices.gpws_inop, "gpws-inop");
2222 make_voice(&voices.hard_glideslope, "glideslope", "glideslope", 3.0);
2223 make_voice(&voices.minimums, "minimums");
2224 make_voice(&voices.minimums_minimums, "minimums", "minimums");
2225 make_voice(&voices.pull_up, "pull-up");
2226 make_voice(&voices.sink_rate, "sink-rate");
2227 make_voice(&voices.sink_rate_pause_sink_rate, "sink-rate", STDPAUSE, "sink-rate");
2228 make_voice(&voices.soft_glideslope, new Voice::SampleElement(get_sample("glideslope"), modify_amplitude(1.0, -6)));
2229 make_voice(&voices.terrain, "terrain");
2230 make_voice(&voices.terrain_pause_terrain, "terrain", STDPAUSE, "terrain");
2231 make_voice(&voices.too_low_flaps, "too-low-flaps");
2232 make_voice(&voices.too_low_gear, "too-low-gear");
2233 make_voice(&voices.too_low_terrain, "too-low-terrain");
2235 for (unsigned i = 0; i < n_altitude_callouts; i++)
2237 std::ostringstream name;
2238 name << "altitude-" << mk->mode6_handler.altitude_callout_definitions[i];
2239 make_voice(&voices.altitude_callouts[i], name.str().c_str());
2242 speaker.update_configuration();
2246 MK_VIII::VoicePlayer::get_sample (const char *name)
2248 std::ostringstream refname;
2249 refname << mk->name << "[" << mk->num << "]" << "/" << name;
2251 SGSoundSample *sample = _sgr->find(refname.str());
2254 SGPath sample_path(globals->get_fg_root());
2255 sample_path.append("Sounds/mk-viii");
2257 string filename = string(name) + ".wav";
2260 sample = new SGSoundSample(sample_path.c_str(), filename.c_str());
2262 catch (const sg_exception &e)
2264 SG_LOG(SG_INSTR, SG_ALERT, "Error loading MK VIII sound sample \"" + filename + "\": " + e.getFormattedMessage());
2268 _sgr->add(sample, refname.str());
2269 samples[refname.str()] = sample;
2276 MK_VIII::VoicePlayer::play (Voice *_voice, unsigned int flags)
2278 if (test_bits(flags, PLAY_NOW) || ! voice || voice->element->silence)
2284 looped = test_bits(flags, PLAY_LOOPED);
2287 next_looped = false;
2293 next_voice = _voice;
2294 next_looped = test_bits(flags, PLAY_LOOPED);
2299 MK_VIII::VoicePlayer::stop (unsigned int flags)
2303 voice->stop(test_bits(flags, STOP_NOW));
2313 MK_VIII::VoicePlayer::set_volume (float _volume)
2317 voice->volume_changed();
2321 MK_VIII::VoicePlayer::update ()
2329 if (! voice->element || voice->element->silence)
2332 looped = next_looped;
2335 next_looped = false;
2342 if (! voice->element)
2353 ///////////////////////////////////////////////////////////////////////////////
2354 // SelfTestHandler ////////////////////////////////////////////////////////////
2355 ///////////////////////////////////////////////////////////////////////////////
2358 MK_VIII::SelfTestHandler::_was_here (int position)
2360 if (position > current)
2369 MK_VIII::SelfTestHandler::Action
2370 MK_VIII::SelfTestHandler::sleep (double duration)
2372 Action _action = { ACTION_SLEEP, duration, NULL };
2376 MK_VIII::SelfTestHandler::Action
2377 MK_VIII::SelfTestHandler::play (VoicePlayer::Voice *voice)
2379 mk->voice_player.play(voice);
2380 Action _action = { ACTION_VOICE, 0, NULL };
2384 MK_VIII::SelfTestHandler::Action
2385 MK_VIII::SelfTestHandler::discrete_on (bool *discrete, double duration)
2388 return sleep(duration);
2391 MK_VIII::SelfTestHandler::Action
2392 MK_VIII::SelfTestHandler::discrete_on_off (bool *discrete, double duration)
2395 Action _action = { ACTION_SLEEP | ACTION_DISCRETE_ON_OFF, duration, discrete };
2399 MK_VIII::SelfTestHandler::Action
2400 MK_VIII::SelfTestHandler::discrete_on_off (bool *discrete, VoicePlayer::Voice *voice)
2403 mk->voice_player.play(voice);
2404 Action _action = { ACTION_VOICE | ACTION_DISCRETE_ON_OFF, 0, discrete };
2408 MK_VIII::SelfTestHandler::Action
2409 MK_VIII::SelfTestHandler::done ()
2411 Action _action = { ACTION_DONE, 0, NULL };
2415 MK_VIII::SelfTestHandler::Action
2416 MK_VIII::SelfTestHandler::run ()
2418 // Note that "Terrain INOP" and "Terrain NA" are or'ed to the same
2419 // output discrete (see [SPEC] 6.9.3.5).
2421 #define was_here() was_here_offset(0)
2422 #define was_here_offset(offset) _was_here(__LINE__ * 20 + (offset))
2426 if (mk->configuration_module.state == ConfigurationModule::STATE_INVALID_DATABASE)
2427 return play(mk_voice(application_data_base_failed));
2428 else if (mk->configuration_module.state == ConfigurationModule::STATE_INVALID_AIRCRAFT_TYPE)
2429 return play(mk_voice(configuration_type_invalid));
2431 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
2435 return discrete_on(&mk_doutput(gpws_inop), 0.7);
2437 return sleep(0.7); // W/S INOP
2439 return discrete_on(&mk_doutput(tad_inop), 0.7);
2442 mk_doutput(gpws_inop) = false;
2443 // do not disable tad_inop since we must enable Terrain NA
2444 // do not disable W/S INOP since we do not emulate it
2445 return sleep(0.7); // Terrain NA
2449 mk_doutput(tad_inop) = false; // disable Terrain NA
2450 if (mk->io_handler.conf.momentary_flap_override_enabled)
2451 return discrete_on_off(&mk_doutput(flap_override), 1.0);
2454 return discrete_on_off(&mk_doutput(audio_on), 1.0);
2457 if (mk->io_handler.momentary_steep_approach_enabled())
2458 return discrete_on_off(&mk_doutput(steep_approach), 1.0);
2463 if (mk_dinput(glideslope_inhibit))
2464 goto glideslope_end;
2467 if (mk->fault_handler.faults[FaultHandler::FAULT_MODE5_INPUTS_INVALID])
2468 goto glideslope_inop;
2472 return discrete_on_off(&mk_doutput(gpws_alert), mk_voice(glideslope));
2474 return discrete_on_off(&mk_doutput(glideslope_cancel), 0.7);
2475 goto glideslope_end;
2478 return play(mk_voice(glideslope_inop));
2483 if (mk->fault_handler.faults[FaultHandler::FAULT_MODES14_INPUTS_INVALID])
2487 return discrete_on_off(&mk_doutput(gpws_warning), mk_voice(pull_up));
2491 return play(mk_voice(gpws_inop));
2496 if (mk_dinput(self_test)) // proceed to long self test
2501 if (mk->mode6_handler.conf.bank_angle_enabled
2502 && mk->fault_handler.faults[FaultHandler::FAULT_BANK_ANGLE_INPUTS_INVALID])
2503 return play(mk_voice(bank_angle_inop));
2507 if (mk->mode6_handler.altitude_callouts_enabled()
2508 && mk->fault_handler.faults[FaultHandler::FAULT_MODE6_INPUTS_INVALID])
2509 return play(mk_voice(callouts_inop));
2517 mk_doutput(gpws_inop) = true;
2518 // do not enable W/S INOP, since we do not emulate it
2519 mk_doutput(tad_inop) = true; // Terrain INOP, Terrain NA
2521 return play(mk_voice(sink_rate));
2524 return play(mk_voice(pull_up));
2526 return play(mk_voice(terrain));
2528 return play(mk_voice(pull_up));
2530 return play(mk_voice(dont_sink));
2532 return play(mk_voice(too_low_terrain));
2534 return play(mk->mode4_handler.conf.voice_too_low_gear);
2536 return play(mk_voice(too_low_flaps));
2538 return play(mk_voice(too_low_terrain));
2540 return play(mk_voice(glideslope));
2543 if (mk->mode6_handler.conf.bank_angle_enabled)
2544 return play(mk_voice(bank_angle));
2549 if (! mk->mode6_handler.altitude_callouts_enabled())
2550 goto callouts_disabled;
2554 if (mk->mode6_handler.conf.minimums_enabled)
2555 return play(mk_voice(minimums));
2559 if (mk->mode6_handler.conf.above_field_voice)
2560 return play(mk->mode6_handler.conf.above_field_voice);
2562 for (unsigned i = 0; i < n_altitude_callouts; i++)
2563 if (! was_here_offset(i))
2565 if (mk->mode6_handler.conf.altitude_callouts_enabled[i])
2566 return play(mk_altitude_voice(i));
2570 if (mk->mode6_handler.conf.smart_500_enabled)
2571 return play(mk_altitude_voice(Mode6Handler::ALTITUDE_CALLOUT_500));
2576 return play(mk_voice(minimums_minimums));
2581 if (mk->tcf_handler.conf.enabled)
2582 return play(mk_voice(too_low_terrain));
2589 MK_VIII::SelfTestHandler::start ()
2591 assert(state == STATE_START);
2593 memcpy(&saved_outputs, &mk->io_handler.outputs, sizeof(mk->io_handler.outputs));
2594 memset(&mk->io_handler.outputs, 0, sizeof(mk->io_handler.outputs));
2596 // [SPEC] 6.10.6: "The self-test results are annunciated, at 6db
2597 // lower than the normal audio level selected for the aircraft"
2598 mk->voice_player.set_volume(modify_amplitude(mk->voice_player.conf.volume, -6));
2600 if (mk_dinput(mode6_low_volume))
2601 mk->mode6_handler.set_volume(1.0);
2603 state = STATE_RUNNING;
2604 cancel = CANCEL_NONE;
2605 memset(&action, 0, sizeof(action));
2610 MK_VIII::SelfTestHandler::stop ()
2612 if (state != STATE_NONE)
2614 if (state != STATE_START)
2616 mk->voice_player.stop(VoicePlayer::STOP_NOW);
2617 mk->voice_player.set_volume(mk_dinput(audio_inhibit) ? 0.0 : mk->voice_player.conf.volume);
2619 if (mk_dinput(mode6_low_volume))
2620 mk->mode6_handler.set_volume(modify_amplitude(1.0, -6));
2622 memcpy(&mk->io_handler.outputs, &saved_outputs, sizeof(mk->io_handler.outputs));
2625 button_pressed = false;
2631 MK_VIII::SelfTestHandler::handle_button_event (bool value)
2633 if (state == STATE_NONE)
2636 state = STATE_START;
2638 else if (state == STATE_START)
2641 state = STATE_NONE; // cancel the not-yet-started test
2643 else if (cancel == CANCEL_NONE)
2647 assert(! button_pressed);
2648 button_pressed = true;
2649 button_press_timestamp = globals->get_sim_time_sec();
2655 if (globals->get_sim_time_sec() - button_press_timestamp < 2)
2656 cancel = CANCEL_SHORT;
2658 cancel = CANCEL_LONG;
2665 MK_VIII::SelfTestHandler::update ()
2667 if (state == STATE_NONE)
2669 else if (state == STATE_START)
2671 if (mk->state_handler.ground && ! mk->io_handler.steep_approach())
2681 if (button_pressed && cancel == CANCEL_NONE)
2683 if (globals->get_sim_time_sec() - button_press_timestamp >= 2)
2684 cancel = CANCEL_LONG;
2688 if (! mk->state_handler.ground || cancel != CANCEL_NONE)
2694 if (test_bits(action.flags, ACTION_SLEEP))
2696 if (globals->get_sim_time_sec() - sleep_start < action.sleep_duration)
2699 if (test_bits(action.flags, ACTION_VOICE))
2701 if (mk->voice_player.voice)
2704 if (test_bits(action.flags, ACTION_DISCRETE_ON_OFF))
2705 *action.discrete = false;
2709 if (test_bits(action.flags, ACTION_SLEEP))
2710 sleep_start = globals->get_sim_time_sec();
2711 if (test_bits(action.flags, ACTION_DONE))
2720 ///////////////////////////////////////////////////////////////////////////////
2721 // AlertHandler ///////////////////////////////////////////////////////////////
2722 ///////////////////////////////////////////////////////////////////////////////
2725 MK_VIII::AlertHandler::select_voice_alerts (unsigned int test)
2727 if (has_alerts(test))
2729 voice_alerts = alerts & test;
2734 voice_alerts &= ~test;
2735 if (voice_alerts == 0)
2736 mk->voice_player.stop();
2743 MK_VIII::AlertHandler::boot ()
2749 MK_VIII::AlertHandler::reposition ()
2753 mk->io_handler.set_lamp(IOHandler::LAMP_NONE);
2754 mk->voice_player.stop(VoicePlayer::STOP_NOW);
2758 MK_VIII::AlertHandler::reset ()
2763 repeated_alerts = 0;
2764 altitude_callout_voice = NULL;
2768 MK_VIII::AlertHandler::update ()
2770 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
2773 // Lamps and voices are prioritized according to [SPEC] 6.9.2.
2775 // Voices which can interrupt other voices (VoicePlayer::PLAY_NOW)
2776 // are specified by [INSTALL] appendix E 5.3.5.
2778 // When a voice is overriden by a higher priority voice and the
2779 // overriding voice stops, we restore the previous voice if it was
2780 // looped (this is not specified by [SPEC] but seems to make sense).
2784 if (has_alerts(ALERT_MODE1_PULL_UP | ALERT_MODE2A | ALERT_MODE2B))
2785 mk->io_handler.set_lamp(IOHandler::LAMP_WARNING);
2786 else if (has_alerts(ALERT_MODE1_SINK_RATE
2787 | ALERT_MODE2A_PREFACE
2788 | ALERT_MODE2B_PREFACE
2789 | ALERT_MODE2A_ALTITUDE_GAIN_TERRAIN_CLOSING
2790 | ALERT_MODE2A_ALTITUDE_GAIN
2791 | ALERT_MODE2B_LANDING_MODE
2793 | ALERT_MODE4_TOO_LOW_FLAPS
2794 | ALERT_MODE4_TOO_LOW_GEAR
2795 | ALERT_MODE4AB_TOO_LOW_TERRAIN
2796 | ALERT_MODE4C_TOO_LOW_TERRAIN
2797 | ALERT_TCF_TOO_LOW_TERRAIN))
2798 mk->io_handler.set_lamp(IOHandler::LAMP_CAUTION);
2799 else if (has_alerts(ALERT_MODE5_SOFT | ALERT_MODE5_HARD))
2800 mk->io_handler.set_lamp(IOHandler::LAMP_GLIDESLOPE);
2802 mk->io_handler.set_lamp(IOHandler::LAMP_NONE);
2806 if (select_voice_alerts(ALERT_MODE1_PULL_UP))
2808 if (! has_old_alerts(ALERT_MODE1_PULL_UP))
2810 if (mk->voice_player.voice != mk_voice(sink_rate_pause_sink_rate))
2811 mk->voice_player.play(mk_voice(sink_rate), VoicePlayer::PLAY_NOW);
2812 mk->voice_player.play(mk_voice(pull_up), VoicePlayer::PLAY_LOOPED);
2815 else if (select_voice_alerts(ALERT_MODE2A_PREFACE | ALERT_MODE2B_PREFACE))
2817 if (! has_old_alerts(ALERT_MODE2A_PREFACE | ALERT_MODE2B_PREFACE))
2818 mk->voice_player.play(mk_voice(terrain_pause_terrain), VoicePlayer::PLAY_NOW);
2820 else if (select_voice_alerts(ALERT_MODE2A | ALERT_MODE2B))
2822 if (mk->voice_player.voice != mk_voice(pull_up))
2823 mk->voice_player.play(mk_voice(pull_up), VoicePlayer::PLAY_NOW | VoicePlayer::PLAY_LOOPED);
2825 else if (select_voice_alerts(ALERT_MODE2A_ALTITUDE_GAIN_TERRAIN_CLOSING | ALERT_MODE2B_LANDING_MODE))
2827 if (mk->voice_player.voice != mk_voice(terrain))
2828 mk->voice_player.play(mk_voice(terrain), VoicePlayer::PLAY_LOOPED);
2830 else if (select_voice_alerts(ALERT_MODE6_MINIMUMS))
2832 if (! has_old_alerts(ALERT_MODE6_MINIMUMS))
2833 mk->voice_player.play(mk_voice(minimums_minimums));
2835 else if (select_voice_alerts(ALERT_MODE4AB_TOO_LOW_TERRAIN | ALERT_MODE4C_TOO_LOW_TERRAIN))
2837 if (must_play_voice(ALERT_MODE4AB_TOO_LOW_TERRAIN | ALERT_MODE4C_TOO_LOW_TERRAIN))
2838 mk->voice_player.play(mk_voice(too_low_terrain));
2840 else if (select_voice_alerts(ALERT_TCF_TOO_LOW_TERRAIN))
2842 if (must_play_voice(ALERT_TCF_TOO_LOW_TERRAIN))
2843 mk->voice_player.play(mk_voice(too_low_terrain));
2845 else if (select_voice_alerts(ALERT_MODE6_ALTITUDE_CALLOUT))
2847 if (! has_old_alerts(ALERT_MODE6_ALTITUDE_CALLOUT))
2849 assert(altitude_callout_voice != NULL);
2850 mk->voice_player.play(altitude_callout_voice);
2853 else if (select_voice_alerts(ALERT_MODE4_TOO_LOW_GEAR))
2855 if (must_play_voice(ALERT_MODE4_TOO_LOW_GEAR))
2856 mk->voice_player.play(mk->mode4_handler.conf.voice_too_low_gear);
2858 else if (select_voice_alerts(ALERT_MODE4_TOO_LOW_FLAPS))
2860 if (must_play_voice(ALERT_MODE4_TOO_LOW_FLAPS))
2861 mk->voice_player.play(mk_voice(too_low_flaps));
2863 else if (select_voice_alerts(ALERT_MODE1_SINK_RATE))
2865 if (must_play_voice(ALERT_MODE1_SINK_RATE))
2866 mk->voice_player.play(mk_voice(sink_rate_pause_sink_rate));
2867 // [SPEC] 6.2.1: "During the time that the voice message for the
2868 // outer envelope is inhibited and the caution/warning lamp is
2869 // on, the Mode 5 alert message is allowed to annunciate for
2870 // excessive glideslope deviation conditions."
2871 else if (mk->voice_player.voice != mk_voice(sink_rate_pause_sink_rate)
2872 && mk->voice_player.next_voice != mk_voice(sink_rate_pause_sink_rate))
2874 if (has_alerts(ALERT_MODE5_HARD))
2876 voice_alerts |= ALERT_MODE5_HARD;
2877 if (mk->voice_player.voice != mk_voice(hard_glideslope))
2878 mk->voice_player.play(mk_voice(hard_glideslope), VoicePlayer::PLAY_LOOPED);
2880 else if (has_alerts(ALERT_MODE5_SOFT))
2882 voice_alerts |= ALERT_MODE5_SOFT;
2883 if (must_play_voice(ALERT_MODE5_SOFT))
2884 mk->voice_player.play(mk_voice(soft_glideslope));
2888 else if (select_voice_alerts(ALERT_MODE3))
2890 if (must_play_voice(ALERT_MODE3))
2891 mk->voice_player.play(mk_voice(dont_sink_pause_dont_sink));
2893 else if (select_voice_alerts(ALERT_MODE5_HARD))
2895 if (mk->voice_player.voice != mk_voice(hard_glideslope))
2896 mk->voice_player.play(mk_voice(hard_glideslope), VoicePlayer::PLAY_LOOPED);
2898 else if (select_voice_alerts(ALERT_MODE5_SOFT))
2900 if (must_play_voice(ALERT_MODE5_SOFT))
2901 mk->voice_player.play(mk_voice(soft_glideslope));
2903 else if (select_voice_alerts(ALERT_MODE6_LOW_BANK_ANGLE_3))
2905 if (mk->voice_player.voice != mk_voice(bank_angle_bank_angle_3))
2906 mk->voice_player.play(mk_voice(bank_angle_bank_angle_3), VoicePlayer::PLAY_LOOPED);
2908 else if (select_voice_alerts(ALERT_MODE6_HIGH_BANK_ANGLE_3))
2910 if (mk->voice_player.voice != mk_voice(bank_angle_pause_bank_angle_3))
2911 mk->voice_player.play(mk_voice(bank_angle_pause_bank_angle_3), VoicePlayer::PLAY_LOOPED);
2913 else if (select_voice_alerts(ALERT_MODE6_LOW_BANK_ANGLE_2))
2915 if (! has_old_alerts(ALERT_MODE6_LOW_BANK_ANGLE_2 | ALERT_MODE6_HIGH_BANK_ANGLE_2))
2916 mk->voice_player.play(mk_voice(bank_angle_bank_angle));
2918 else if (select_voice_alerts(ALERT_MODE6_HIGH_BANK_ANGLE_2))
2920 if (! has_old_alerts(ALERT_MODE6_LOW_BANK_ANGLE_2 | ALERT_MODE6_HIGH_BANK_ANGLE_2))
2921 mk->voice_player.play(mk_voice(bank_angle_pause_bank_angle));
2923 else if (select_voice_alerts(ALERT_MODE6_LOW_BANK_ANGLE_1))
2925 if (! has_old_alerts(ALERT_MODE6_LOW_BANK_ANGLE_1 | ALERT_MODE6_HIGH_BANK_ANGLE_1))
2926 mk->voice_player.play(mk_voice(bank_angle_bank_angle));
2928 else if (select_voice_alerts(ALERT_MODE6_HIGH_BANK_ANGLE_1))
2930 if (! has_old_alerts(ALERT_MODE6_LOW_BANK_ANGLE_1 | ALERT_MODE6_HIGH_BANK_ANGLE_1))
2931 mk->voice_player.play(mk_voice(bank_angle_pause_bank_angle));
2936 old_alerts = alerts;
2937 repeated_alerts = 0;
2938 altitude_callout_voice = NULL;
2942 MK_VIII::AlertHandler::set_alerts (unsigned int _alerts,
2944 VoicePlayer::Voice *_altitude_callout_voice)
2947 if (test_bits(flags, ALERT_FLAG_REPEAT))
2948 repeated_alerts |= _alerts;
2949 if (_altitude_callout_voice)
2950 altitude_callout_voice = _altitude_callout_voice;
2954 MK_VIII::AlertHandler::unset_alerts (unsigned int _alerts)
2957 repeated_alerts &= ~_alerts;
2960 ///////////////////////////////////////////////////////////////////////////////
2961 // StateHandler ///////////////////////////////////////////////////////////////
2962 ///////////////////////////////////////////////////////////////////////////////
2965 MK_VIII::StateHandler::update_ground ()
2969 if (! mk_ainput(computed_airspeed).ncd && mk_ainput(computed_airspeed).get() > 60
2970 && ! mk_data(radio_altitude).ncd && mk_data(radio_altitude).get() > 30)
2972 if (potentially_airborne_timer.start_or_elapsed() > 1)
2976 potentially_airborne_timer.stop();
2980 if (! mk_ainput(computed_airspeed).ncd && mk_ainput(computed_airspeed).get() < 40
2981 && ! mk_data(radio_altitude).ncd && mk_data(radio_altitude).get() < 30)
2987 MK_VIII::StateHandler::enter_ground ()
2990 mk->io_handler.enter_ground();
2992 // [SPEC] 6.0.1 does not specify this, but it seems to be an
2993 // omission; at this point, we must make sure that we are in takeoff
2994 // mode (otherwise the next takeoff might happen in approach mode).
3000 MK_VIII::StateHandler::leave_ground ()
3003 mk->mode2_handler.leave_ground();
3007 MK_VIII::StateHandler::update_takeoff ()
3011 // [SPEC] 6.0.2 is somewhat confusing: it mentions hardcoded
3012 // terrain clearance (500, 750) and airspeed (178, 200) values,
3013 // but it also mentions the mode 4A boundary, which varies as a
3014 // function of aircraft type. We consider that the mentioned
3015 // values are examples, and that we should use the mode 4A upper
3018 if (! mk_data(terrain_clearance).ncd
3019 && ! mk_ainput(computed_airspeed).ncd
3020 && mk_data(terrain_clearance).get() > mk->mode4_handler.get_upper_agl(mk->mode4_handler.conf.modes->ac))
3025 if (! mk_data(radio_altitude).ncd
3026 && mk_data(radio_altitude).get() < mk->mode4_handler.conf.modes->b->min_agl1
3027 && mk->io_handler.flaps_down()
3028 && mk_dinput(landing_gear))
3034 MK_VIII::StateHandler::enter_takeoff ()
3037 mk->io_handler.enter_takeoff();
3038 mk->mode2_handler.enter_takeoff();
3039 mk->mode3_handler.enter_takeoff();
3040 mk->mode6_handler.enter_takeoff();
3044 MK_VIII::StateHandler::leave_takeoff ()
3047 mk->mode6_handler.leave_takeoff();
3051 MK_VIII::StateHandler::post_reposition ()
3053 // Synchronize the state with the current situation.
3056 (! mk_ainput(computed_airspeed).ncd && mk_ainput(computed_airspeed).get() > 60
3057 && ! mk_data(radio_altitude).ncd && mk_data(radio_altitude).get() > 30);
3059 bool _takeoff = _ground;
3061 if (ground && ! _ground)
3063 else if (! ground && _ground)
3066 if (takeoff && ! _takeoff)
3068 else if (! takeoff && _takeoff)
3073 MK_VIII::StateHandler::update ()
3075 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
3082 ///////////////////////////////////////////////////////////////////////////////
3083 // Mode1Handler ///////////////////////////////////////////////////////////////
3084 ///////////////////////////////////////////////////////////////////////////////
3087 MK_VIII::Mode1Handler::get_pull_up_bias ()
3089 // [PILOT] page 54: "Steep Approach has priority over Flap Override
3090 // if selected simultaneously."
3092 if (mk->io_handler.steep_approach())
3094 else if (conf.envelopes->flap_override_bias && mk->io_handler.flap_override())
3101 MK_VIII::Mode1Handler::is_pull_up ()
3103 if (! mk->io_handler.gpws_inhibit()
3104 && ! mk->state_handler.ground // [1]
3105 && ! mk_data(radio_altitude).ncd
3106 && ! mk_data(barometric_altitude_rate).ncd
3107 && mk_data(radio_altitude).get() > conf.envelopes->min_agl)
3109 if (mk_data(radio_altitude).get() < conf.envelopes->pull_up_max_agl1)
3111 if (mk_data(radio_altitude).get() < conf.envelopes->pull_up_min_agl1(mk_data(barometric_altitude_rate).get() + get_pull_up_bias()))
3114 else if (mk_data(radio_altitude).get() < conf.envelopes->pull_up_max_agl2)
3116 if (mk_data(radio_altitude).get() < conf.envelopes->pull_up_min_agl2(mk_data(barometric_altitude_rate).get() + get_pull_up_bias()))
3125 MK_VIII::Mode1Handler::update_pull_up ()
3129 if (! mk_test_alert(MODE1_PULL_UP))
3131 // [SPEC] 6.2.1: at least one sink rate must be issued
3132 // before switching to pull up; if no sink rate has
3133 // occurred, a 0.2 second delay is used.
3134 if (mk->voice_player.voice == mk_voice(sink_rate_pause_sink_rate)
3135 || pull_up_timer.start_or_elapsed() >= 0.2)
3136 mk_set_alerts(mk_alert(MODE1_PULL_UP));
3141 pull_up_timer.stop();
3142 mk_unset_alerts(mk_alert(MODE1_PULL_UP));
3147 MK_VIII::Mode1Handler::get_sink_rate_bias ()
3149 // [PILOT] page 54: "Steep Approach has priority over Flap Override
3150 // if selected simultaneously."
3152 if (mk->io_handler.steep_approach())
3154 else if (conf.envelopes->flap_override_bias && mk->io_handler.flap_override())
3156 else if (! mk_data(glideslope_deviation_dots).ncd)
3160 if (mk_data(glideslope_deviation_dots).get() <= -2)
3162 else if (mk_data(glideslope_deviation_dots).get() < 0)
3163 bias = -150 * mk_data(glideslope_deviation_dots).get();
3165 if (mk_data(radio_altitude).get() < 100)
3166 bias *= 0.01 * mk_data(radio_altitude).get();
3175 MK_VIII::Mode1Handler::is_sink_rate ()
3177 return ! mk->io_handler.gpws_inhibit()
3178 && ! mk->state_handler.ground // [1]
3179 && ! mk_data(radio_altitude).ncd
3180 && ! mk_data(barometric_altitude_rate).ncd
3181 && mk_data(radio_altitude).get() > conf.envelopes->min_agl
3182 && mk_data(radio_altitude).get() < 2450
3183 && mk_data(radio_altitude).get() < -572 - 0.6035 * (mk_data(barometric_altitude_rate).get() + get_sink_rate_bias());
3187 MK_VIII::Mode1Handler::get_sink_rate_tti ()
3189 return mk_data(radio_altitude).get() / fabs(mk_data(barometric_altitude_rate).get());
3193 MK_VIII::Mode1Handler::update_sink_rate ()
3197 if (mk_test_alert(MODE1_SINK_RATE))
3199 double tti = get_sink_rate_tti();
3200 if (tti <= sink_rate_tti * 0.8)
3202 // ~20% degradation, warn again and store the new reference tti
3203 sink_rate_tti = tti;
3204 mk_repeat_alert(mk_alert(MODE1_SINK_RATE));
3209 if (sink_rate_timer.start_or_elapsed() >= 0.8)
3211 mk_set_alerts(mk_alert(MODE1_SINK_RATE));
3212 sink_rate_tti = get_sink_rate_tti();
3218 sink_rate_timer.stop();
3219 mk_unset_alerts(mk_alert(MODE1_SINK_RATE));
3224 MK_VIII::Mode1Handler::update ()
3226 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
3233 ///////////////////////////////////////////////////////////////////////////////
3234 // Mode2Handler ///////////////////////////////////////////////////////////////
3235 ///////////////////////////////////////////////////////////////////////////////
3238 MK_VIII::Mode2Handler::ClosureRateFilter::limit_radio_altitude_rate (double r)
3240 // Limit radio altitude rate according to aircraft configuration,
3241 // allowing maximum sensitivity during cruise while providing
3242 // progressively less sensitivity during the landing phases of
3245 if (! mk_data(glideslope_deviation_dots).ncd && fabs(mk_data(glideslope_deviation_dots).get()) <= 2)
3246 { // gs deviation <= +- 2 dots
3247 if (mk_dinput(landing_gear) && mk->io_handler.flaps_down())
3248 SG_CLAMP_RANGE(r, -1000.0, 3000.0);
3249 else if (mk_dinput(landing_gear) || mk->io_handler.flaps_down())
3250 SG_CLAMP_RANGE(r, 0.0, 4000.0);
3252 SG_CLAMP_RANGE(r, 1000.0, 5000.0);
3255 { // no ILS, or gs deviation > +- 2 dots
3256 if (mk_dinput(landing_gear) && mk->io_handler.flaps_down())
3257 SG_CLAMP_RANGE(r, 0.0, 4000.0);
3258 else if (mk_dinput(landing_gear) || mk->io_handler.flaps_down())
3259 SG_CLAMP_RANGE(r, 1000.0, 5000.0);
3267 MK_VIII::Mode2Handler::ClosureRateFilter::init ()
3270 last_ra.set(&mk_data(radio_altitude));
3271 last_ba.set(&mk_ainput(uncorrected_barometric_altitude));
3278 MK_VIII::Mode2Handler::ClosureRateFilter::update ()
3280 double elapsed = timer.start_or_elapsed();
3284 if (! mk_data(radio_altitude).ncd && ! mk_ainput(uncorrected_barometric_altitude).ncd)
3286 if (! last_ra.ncd && ! last_ba.ncd)
3288 // compute radio and barometric altitude rates (positive value = descent)
3289 double ra_rate = -(mk_data(radio_altitude).get() - last_ra.get()) / elapsed * 60;
3290 double ba_rate = -(mk_ainput(uncorrected_barometric_altitude).get() - last_ba.get()) / elapsed * 60;
3292 // limit radio altitude rate according to aircraft configuration
3293 ra_rate = limit_radio_altitude_rate(ra_rate);
3295 // apply a low-pass filter to the radio altitude rate
3296 ra_rate = ra_filter.filter(ra_rate);
3297 // apply a high-pass filter to the barometric altitude rate
3298 ba_rate = ba_filter.filter(ba_rate);
3300 // combine both rates to obtain a closure rate
3301 output.set(ra_rate + ba_rate);
3314 last_ra.set(&mk_data(radio_altitude));
3315 last_ba.set(&mk_ainput(uncorrected_barometric_altitude));
3319 MK_VIII::Mode2Handler::b_conditions ()
3321 return mk->io_handler.flaps_down()
3322 || (! mk_data(glideslope_deviation_dots).ncd && fabs(mk_data(glideslope_deviation_dots).get()) < 2)
3323 || takeoff_timer.running;
3327 MK_VIII::Mode2Handler::is_a ()
3329 if (! mk->io_handler.gpws_inhibit()
3330 && ! mk->state_handler.ground // [1]
3331 && ! mk_data(radio_altitude).ncd
3332 && ! mk_ainput(computed_airspeed).ncd
3333 && ! closure_rate_filter.output.ncd
3334 && ! b_conditions())
3336 if (mk_data(radio_altitude).get() < 1220)
3338 if (mk_data(radio_altitude).get() < -1579 + 0.7895 * closure_rate_filter.output.get())
3345 if (mk_ainput(computed_airspeed).get() <= conf->airspeed1)
3347 else if (mk_ainput(computed_airspeed).get() >= conf->airspeed2)
3350 upper_limit = 1650 + 8.9 * (mk_ainput(computed_airspeed).get() - conf->airspeed1);
3352 if (mk_data(radio_altitude).get() < upper_limit)
3354 if (mk_data(radio_altitude).get() < 522 + 0.1968 * closure_rate_filter.output.get())
3364 MK_VIII::Mode2Handler::is_b ()
3366 if (! mk->io_handler.gpws_inhibit()
3367 && ! mk->state_handler.ground // [1]
3368 && ! mk_data(radio_altitude).ncd
3369 && ! mk_data(barometric_altitude_rate).ncd
3370 && ! closure_rate_filter.output.ncd
3372 && mk_data(radio_altitude).get() < 789)
3376 if (mk->io_handler.flaps_down())
3378 if (mk_data(barometric_altitude_rate).get() > -400)
3380 else if (mk_data(barometric_altitude_rate).get() < -1000)
3383 lower_limit = -66.777 - 0.667 * mk_data(barometric_altitude_rate).get();
3388 if (mk_data(radio_altitude).get() > lower_limit)
3390 if (mk_data(radio_altitude).get() < -1579 + 0.7895 * closure_rate_filter.output.get())
3399 MK_VIII::Mode2Handler::check_pull_up (unsigned int preface_alert,
3402 if (pull_up_timer.running)
3404 if (pull_up_timer.elapsed() >= 1)
3406 mk_unset_alerts(preface_alert);
3407 mk_set_alerts(alert);
3412 if (! mk->voice_player.voice)
3413 pull_up_timer.start();
3418 MK_VIII::Mode2Handler::update_a ()
3422 if (mk_test_alert(MODE2A_PREFACE))
3423 check_pull_up(mk_alert(MODE2A_PREFACE), mk_alert(MODE2A));
3424 else if (! mk_test_alert(MODE2A))
3426 mk_unset_alerts(mk_alert(MODE2A_ALTITUDE_GAIN) | mk_alert(MODE2A_ALTITUDE_GAIN_TERRAIN_CLOSING));
3427 mk_set_alerts(mk_alert(MODE2A_PREFACE));
3428 a_start_time = globals->get_sim_time_sec();
3429 pull_up_timer.stop();
3434 if (mk_test_alert(MODE2A_ALTITUDE_GAIN))
3436 if (mk->io_handler.gpws_inhibit()
3437 || mk->state_handler.ground // [1]
3438 || a_altitude_gain_timer.elapsed() >= 45
3439 || mk_data(radio_altitude).ncd
3440 || mk_ainput(uncorrected_barometric_altitude).ncd
3441 || mk_ainput(uncorrected_barometric_altitude).get() - a_altitude_gain_alt >= 300
3442 // [PILOT] page 12: "the visual alert will remain on
3443 // until [...] landing flaps or the flap override switch
3445 || mk->io_handler.flaps_down())
3447 // exit altitude gain mode
3448 a_altitude_gain_timer.stop();
3449 mk_unset_alerts(mk_alert(MODE2A_ALTITUDE_GAIN) | mk_alert(MODE2A_ALTITUDE_GAIN_TERRAIN_CLOSING));
3453 // [SPEC] 6.2.2.2: "If the terrain starts to fall away
3454 // during this altitude gain time, the voice will shut
3456 if (closure_rate_filter.output.get() < 0)
3457 mk_unset_alerts(mk_alert(MODE2A_ALTITUDE_GAIN_TERRAIN_CLOSING));
3460 else if (mk_test_alerts(mk_alert(MODE2A_PREFACE) | mk_alert(MODE2A)))
3462 mk_unset_alerts(mk_alert(MODE2A_PREFACE) | mk_alert(MODE2A));
3464 if (! mk->io_handler.gpws_inhibit()
3465 && ! mk->state_handler.ground // [1]
3466 && globals->get_sim_time_sec() - a_start_time > 3
3467 && ! mk->io_handler.flaps_down()
3468 && ! mk_data(radio_altitude).ncd
3469 && ! mk_ainput(uncorrected_barometric_altitude).ncd)
3471 // [SPEC] 6.2.2.2: mode 2A envelope violated for more
3472 // than 3 seconds, enable altitude gain feature
3474 a_altitude_gain_timer.start();
3475 a_altitude_gain_alt = mk_ainput(uncorrected_barometric_altitude).get();
3477 unsigned int alerts = mk_alert(MODE2A_ALTITUDE_GAIN);
3478 if (closure_rate_filter.output.get() > 0)
3479 alerts |= mk_alert(MODE2A_ALTITUDE_GAIN_TERRAIN_CLOSING);
3481 mk_set_alerts(alerts);
3488 MK_VIII::Mode2Handler::update_b ()
3492 // handle normal mode
3494 if (b && (! mk_dinput(landing_gear) || ! mk->io_handler.flaps_down()))
3496 if (mk_test_alert(MODE2B_PREFACE))
3497 check_pull_up(mk_alert(MODE2B_PREFACE), mk_alert(MODE2B));
3498 else if (! mk_test_alert(MODE2B))
3500 mk_set_alerts(mk_alert(MODE2B_PREFACE));
3501 pull_up_timer.stop();
3505 mk_unset_alerts(mk_alert(MODE2B_PREFACE) | mk_alert(MODE2B));
3507 // handle landing mode ([SPEC] 6.2.2.3: "If both landing gear and
3508 // flaps are in the landing configuration, then the message will be
3511 if (b && mk_dinput(landing_gear) && mk->io_handler.flaps_down())
3512 mk_set_alerts(mk_alert(MODE2B_LANDING_MODE));
3514 mk_unset_alerts(mk_alert(MODE2B_LANDING_MODE));
3518 MK_VIII::Mode2Handler::boot ()
3520 closure_rate_filter.init();
3524 MK_VIII::Mode2Handler::power_off ()
3526 // [SPEC] 6.0.4: "This latching function is not power saved and a
3527 // system reset will force it false."
3528 takeoff_timer.stop();
3532 MK_VIII::Mode2Handler::leave_ground ()
3534 // takeoff, reset timer
3535 takeoff_timer.start();
3539 MK_VIII::Mode2Handler::enter_takeoff ()
3541 // reset timer, in case it's a go-around
3542 takeoff_timer.start();
3546 MK_VIII::Mode2Handler::update ()
3548 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
3551 closure_rate_filter.update();
3553 if (takeoff_timer.running && takeoff_timer.elapsed() >= 60)
3554 takeoff_timer.stop();
3560 ///////////////////////////////////////////////////////////////////////////////
3561 // Mode3Handler ///////////////////////////////////////////////////////////////
3562 ///////////////////////////////////////////////////////////////////////////////
3565 MK_VIII::Mode3Handler::max_alt_loss (double _bias)
3567 return conf->max_alt_loss(mk->io_handler.flap_override(), mk_data(radio_altitude).get()) + mk_data(radio_altitude).get() * _bias;
3571 MK_VIII::Mode3Handler::get_bias (double initial_bias, double alt_loss)
3573 if (mk_data(radio_altitude).get() > 0)
3574 while (alt_loss > max_alt_loss(initial_bias))
3575 initial_bias += 0.2;
3577 return initial_bias;
3581 MK_VIII::Mode3Handler::is (double *alt_loss)
3583 if (has_descent_alt)
3585 int max_agl = conf->max_agl(mk->io_handler.flap_override());
3587 if (mk_ainput(uncorrected_barometric_altitude).ncd
3588 || mk_ainput(uncorrected_barometric_altitude).get() > descent_alt
3589 || mk_data(radio_altitude).ncd
3590 || mk_data(radio_altitude).get() > max_agl)
3593 has_descent_alt = false;
3597 // gear/flaps: [SPEC] 1.3.1.3
3598 if (! mk->io_handler.gpws_inhibit()
3599 && ! mk->state_handler.ground // [1]
3600 && (! mk_dinput(landing_gear) || ! mk->io_handler.flaps_down())
3601 && ! mk_data(barometric_altitude_rate).ncd
3602 && ! mk_ainput(uncorrected_barometric_altitude).ncd
3603 && ! mk_data(radio_altitude).ncd
3604 && mk_data(barometric_altitude_rate).get() < 0)
3606 double _alt_loss = descent_alt - mk_ainput(uncorrected_barometric_altitude).get();
3607 if (armed || (mk_data(radio_altitude).get() > conf->min_agl
3608 && mk_data(radio_altitude).get() < max_agl
3609 && _alt_loss > max_alt_loss(0)))
3611 *alt_loss = _alt_loss;
3619 if (! mk_data(barometric_altitude_rate).ncd
3620 && ! mk_ainput(uncorrected_barometric_altitude).ncd
3621 && mk_data(barometric_altitude_rate).get() < 0)
3623 has_descent_alt = true;
3624 descent_alt = mk_ainput(uncorrected_barometric_altitude).get();
3632 MK_VIII::Mode3Handler::enter_takeoff ()
3635 has_descent_alt = false;
3639 MK_VIII::Mode3Handler::update ()
3641 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
3644 if (mk->state_handler.takeoff)
3648 if (! mk->state_handler.ground /* [1] */ && is(&alt_loss))
3650 if (mk_test_alert(MODE3))
3652 double new_bias = get_bias(bias, alt_loss);
3653 if (new_bias > bias)
3656 mk_repeat_alert(mk_alert(MODE3));
3662 bias = get_bias(0.2, alt_loss);
3663 mk_set_alerts(mk_alert(MODE3));
3670 mk_unset_alerts(mk_alert(MODE3));
3673 ///////////////////////////////////////////////////////////////////////////////
3674 // Mode4Handler ///////////////////////////////////////////////////////////////
3675 ///////////////////////////////////////////////////////////////////////////////
3677 // FIXME: for turbofans, the upper limit should be reduced from 1000
3678 // to 800 ft if a sudden change in radio altitude is detected, in
3679 // order to reduce nuisance alerts caused by overflying another
3680 // aircraft (see [PILOT] page 16).
3683 MK_VIII::Mode4Handler::get_upper_agl (const EnvelopesConfiguration *c)
3685 if (mk_ainput(computed_airspeed).get() >= c->airspeed2)
3687 else if (mk_ainput(computed_airspeed).get() >= c->airspeed1)
3688 return c->min_agl2(mk_ainput(computed_airspeed).get());
3693 const MK_VIII::Mode4Handler::EnvelopesConfiguration *
3694 MK_VIII::Mode4Handler::get_ab_envelope ()
3696 // [SPEC] 6.2.4.1: "The Mode 4B envelope can also be selected by
3697 // setting flaps to landing configuration or by selecting flap
3699 return mk_dinput(landing_gear) || mk->io_handler.flaps_down()
3705 MK_VIII::Mode4Handler::get_bias (double initial_bias, double min_agl)
3707 while (mk_data(radio_altitude).get() < min_agl - min_agl * initial_bias)
3708 initial_bias += 0.2;
3710 return initial_bias;
3714 MK_VIII::Mode4Handler::handle_alert (unsigned int alert,
3718 if (mk_test_alerts(alert))
3720 double new_bias = get_bias(*bias, min_agl);
3721 if (new_bias > *bias)
3724 mk_repeat_alert(alert);
3729 *bias = get_bias(0.2, min_agl);
3730 mk_set_alerts(alert);
3735 MK_VIII::Mode4Handler::update_ab ()
3737 if (! mk->io_handler.gpws_inhibit()
3738 && ! mk->state_handler.ground
3739 && ! mk->state_handler.takeoff
3740 && ! mk_data(radio_altitude).ncd
3741 && ! mk_ainput(computed_airspeed).ncd
3742 && mk_data(radio_altitude).get() > 30)
3744 const EnvelopesConfiguration *c = get_ab_envelope();
3745 if (mk_ainput(computed_airspeed).get() < c->airspeed1)
3747 if (mk_dinput(landing_gear))
3749 if (! mk->io_handler.flaps_down() && mk_data(radio_altitude).get() < c->min_agl1)
3751 handle_alert(mk_alert(MODE4_TOO_LOW_FLAPS), c->min_agl1, &ab_bias);
3757 if (mk_data(radio_altitude).get() < c->min_agl1)
3759 handle_alert(mk_alert(MODE4_TOO_LOW_GEAR), c->min_agl1, &ab_bias);
3766 mk_unset_alerts(mk_alert(MODE4_TOO_LOW_FLAPS) | mk_alert(MODE4_TOO_LOW_GEAR));
3770 MK_VIII::Mode4Handler::update_ab_expanded ()
3772 if (! mk->io_handler.gpws_inhibit()
3773 && ! mk->state_handler.ground
3774 && ! mk->state_handler.takeoff
3775 && ! mk_data(radio_altitude).ncd
3776 && ! mk_ainput(computed_airspeed).ncd
3777 && mk_data(radio_altitude).get() > 30)
3779 const EnvelopesConfiguration *c = get_ab_envelope();
3780 if (mk_ainput(computed_airspeed).get() >= c->airspeed1)
3782 double min_agl = get_upper_agl(c);
3783 if (mk_data(radio_altitude).get() < min_agl)
3785 handle_alert(mk_alert(MODE4AB_TOO_LOW_TERRAIN), min_agl, &ab_expanded_bias);
3791 mk_unset_alerts(mk_alert(MODE4AB_TOO_LOW_TERRAIN));
3795 MK_VIII::Mode4Handler::update_c ()
3797 if (! mk->io_handler.gpws_inhibit()
3798 && ! mk->state_handler.ground // [1]
3799 && mk->state_handler.takeoff
3800 && ! mk_data(radio_altitude).ncd
3801 && ! mk_data(terrain_clearance).ncd
3802 && mk_data(radio_altitude).get() > 30
3803 && (! mk_dinput(landing_gear) || ! mk->io_handler.flaps_down())
3804 && mk_data(radio_altitude).get() < get_upper_agl(conf.modes->ac)
3805 && mk_data(radio_altitude).get() < mk_data(terrain_clearance).get())
3806 handle_alert(mk_alert(MODE4C_TOO_LOW_TERRAIN), mk_data(terrain_clearance).get(), &c_bias);
3808 mk_unset_alerts(mk_alert(MODE4C_TOO_LOW_TERRAIN));
3812 MK_VIII::Mode4Handler::update ()
3814 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
3818 update_ab_expanded();
3822 ///////////////////////////////////////////////////////////////////////////////
3823 // Mode5Handler ///////////////////////////////////////////////////////////////
3824 ///////////////////////////////////////////////////////////////////////////////
3827 MK_VIII::Mode5Handler::is_hard ()
3829 if (mk_data(radio_altitude).get() > 30
3830 && mk_data(radio_altitude).get() < 300
3831 && mk_data(glideslope_deviation_dots).get() > 2)
3833 if (mk_data(radio_altitude).get() < 150)
3835 if (mk_data(radio_altitude).get() > 293 - 71.43 * mk_data(glideslope_deviation_dots).get())
3846 MK_VIII::Mode5Handler::is_soft (double bias)
3848 if (mk_data(radio_altitude).get() > 30)
3850 double bias_dots = 1.3 * bias;
3851 if (mk_data(glideslope_deviation_dots).get() > 1.3 + bias_dots)
3853 if (mk_data(radio_altitude).get() < 150)
3855 if (mk_data(radio_altitude).get() > 243 - 71.43 * (mk_data(glideslope_deviation_dots).get() - bias_dots))
3862 if (mk_data(barometric_altitude_rate).ncd)
3866 if (mk_data(barometric_altitude_rate).get() >= 0)
3868 else if (mk_data(barometric_altitude_rate).get() < -500)
3871 upper_limit = -mk_data(barometric_altitude_rate).get() + 500;
3874 if (mk_data(radio_altitude).get() < upper_limit)
3884 MK_VIII::Mode5Handler::get_soft_bias (double initial_bias)
3886 while (is_soft(initial_bias))
3887 initial_bias += 0.2;
3889 return initial_bias;
3893 MK_VIII::Mode5Handler::update_hard (bool is)
3897 if (! mk_test_alert(MODE5_HARD))
3899 if (hard_timer.start_or_elapsed() >= 0.8)
3900 mk_set_alerts(mk_alert(MODE5_HARD));
3906 mk_unset_alerts(mk_alert(MODE5_HARD));
3911 MK_VIII::Mode5Handler::update_soft (bool is)
3915 if (! mk_test_alert(MODE5_SOFT))
3917 double new_bias = get_soft_bias(soft_bias);
3918 if (new_bias > soft_bias)
3920 soft_bias = new_bias;
3921 mk_repeat_alert(mk_alert(MODE5_SOFT));
3926 if (soft_timer.start_or_elapsed() >= 0.8)
3928 soft_bias = get_soft_bias(0.2);
3929 mk_set_alerts(mk_alert(MODE5_SOFT));
3936 mk_unset_alerts(mk_alert(MODE5_SOFT));
3941 MK_VIII::Mode5Handler::update ()
3943 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
3946 if (! mk->io_handler.gpws_inhibit()
3947 && ! mk->state_handler.ground // [1]
3948 && ! mk_dinput(glideslope_inhibit) // not on backcourse
3949 && ! mk_data(radio_altitude).ncd
3950 && ! mk_data(glideslope_deviation_dots).ncd
3951 && (! mk->io_handler.conf.localizer_enabled
3952 || mk_data(localizer_deviation_dots).ncd
3953 || mk_data(radio_altitude).get() < 500
3954 || fabs(mk_data(localizer_deviation_dots).get()) < 2)
3955 && (! mk->state_handler.takeoff || mk->io_handler.flaps_down())
3956 && mk_dinput(landing_gear)
3957 && ! mk_doutput(glideslope_cancel))
3959 update_hard(is_hard());
3960 update_soft(is_soft(0));
3969 ///////////////////////////////////////////////////////////////////////////////
3970 // Mode6Handler ///////////////////////////////////////////////////////////////
3971 ///////////////////////////////////////////////////////////////////////////////
3973 // keep sorted in descending order
3974 const int MK_VIII::Mode6Handler::altitude_callout_definitions[] =
3975 { 1000, 500, 400, 300, 200, 100, 50, 40, 30, 20, 10 };
3978 MK_VIII::Mode6Handler::reset_minimums ()
3980 minimums_issued = false;
3984 MK_VIII::Mode6Handler::reset_altitude_callouts ()
3986 for (unsigned i = 0; i < n_altitude_callouts; i++)
3987 altitude_callouts_issued[i] = false;
3991 MK_VIII::Mode6Handler::is_playing_altitude_callout ()
3993 for (unsigned i = 0; i < n_altitude_callouts; i++)
3994 if (mk->voice_player.voice == mk_altitude_voice(i)
3995 || mk->voice_player.next_voice == mk_altitude_voice(i))
4002 MK_VIII::Mode6Handler::is_near_minimums (double callout)
4006 if (! mk_data(decision_height).ncd)
4008 double diff = callout - mk_data(decision_height).get();
4010 if (mk_data(radio_altitude).get() >= 200)
4012 if (fabs(diff) <= 30)
4017 if (diff >= -3 && diff <= 6)
4026 MK_VIII::Mode6Handler::is_outside_band (double elevation, double callout)
4029 return elevation < callout - (elevation > 150 ? 20 : 10);
4033 MK_VIII::Mode6Handler::inhibit_smart_500 ()
4037 if (! mk_data(glideslope_deviation_dots).ncd
4038 && ! mk_dinput(glideslope_inhibit) // backcourse
4039 && ! mk_doutput(glideslope_cancel))
4041 if (mk->io_handler.conf.localizer_enabled
4042 && ! mk_data(localizer_deviation_dots).ncd)
4044 if (fabs(mk_data(localizer_deviation_dots).get()) <= 2)
4049 if (fabs(mk_data(glideslope_deviation_dots).get()) <= 2)
4058 MK_VIII::Mode6Handler::boot ()
4060 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
4063 last_decision_height = mk_dinput(decision_height);
4064 last_radio_altitude.set(&mk_data(radio_altitude));
4067 for (unsigned i = 0; i < n_altitude_callouts; i++)
4068 altitude_callouts_issued[i] = ! mk_data(radio_altitude).ncd
4069 && mk_data(radio_altitude).get() <= altitude_callout_definitions[i];
4071 // extrapolated from [SPEC] 6.4.2
4072 minimums_issued = mk_dinput(decision_height);
4074 if (conf.above_field_voice)
4077 get_altitude_above_field(&last_altitude_above_field);
4078 // extrapolated from [SPEC] 6.4.2
4079 above_field_issued = ! last_altitude_above_field.ncd
4080 && last_altitude_above_field.get() < 550;
4085 MK_VIII::Mode6Handler::power_off ()
4087 runway_timer.stop();
4091 MK_VIII::Mode6Handler::enter_takeoff ()
4093 reset_altitude_callouts(); // [SPEC] 6.4.2
4094 reset_minimums(); // omitted by [SPEC]; common sense
4098 MK_VIII::Mode6Handler::leave_takeoff ()
4100 reset_altitude_callouts(); // [SPEC] 6.0.2
4101 reset_minimums(); // [SPEC] 6.0.2
4105 MK_VIII::Mode6Handler::set_volume (float volume)
4107 mk_voice(minimums_minimums)->set_volume(volume);
4108 mk_voice(five_hundred_above)->set_volume(volume);
4109 for (unsigned i = 0; i < n_altitude_callouts; i++)
4110 mk_altitude_voice(i)->set_volume(volume);
4114 MK_VIII::Mode6Handler::altitude_callouts_enabled ()
4116 if (conf.minimums_enabled || conf.smart_500_enabled || conf.above_field_voice)
4119 for (unsigned i = 0; i < n_altitude_callouts; i++)
4120 if (conf.altitude_callouts_enabled[i])
4127 MK_VIII::Mode6Handler::update_minimums ()
4129 if (! mk->io_handler.gpws_inhibit()
4130 && (mk->voice_player.voice == mk_voice(minimums_minimums)
4131 || mk->voice_player.next_voice == mk_voice(minimums_minimums)))
4134 if (! mk->io_handler.gpws_inhibit()
4135 && ! mk->state_handler.ground // [1]
4136 && conf.minimums_enabled
4137 && ! minimums_issued
4138 && mk_dinput(landing_gear)
4139 && mk_dinput(decision_height)
4140 && ! last_decision_height)
4142 minimums_issued = true;
4144 // If an altitude callout is playing, stop it now so that the
4145 // minimums callout can be played (note that proper connection
4146 // and synchronization of the radio-altitude ARINC 529 input,
4147 // decision-height discrete and decision-height ARINC 529 input
4148 // will prevent an altitude callout from being played near the
4149 // decision height).
4151 if (is_playing_altitude_callout())
4152 mk->voice_player.stop(VoicePlayer::STOP_NOW);
4154 mk_set_alerts(mk_alert(MODE6_MINIMUMS));
4157 mk_unset_alerts(mk_alert(MODE6_MINIMUMS));
4160 last_decision_height = mk_dinput(decision_height);
4164 MK_VIII::Mode6Handler::update_altitude_callouts ()
4166 if (! mk->io_handler.gpws_inhibit() && is_playing_altitude_callout())
4169 if (! mk->io_handler.gpws_inhibit()
4170 && ! mk->state_handler.ground // [1]
4171 && ! mk_data(radio_altitude).ncd)
4172 for (unsigned i = 0; i < n_altitude_callouts && mk_data(radio_altitude).get() <= altitude_callout_definitions[i]; i++)
4173 if ((conf.altitude_callouts_enabled[i]
4174 || (altitude_callout_definitions[i] == 500
4175 && conf.smart_500_enabled))
4176 && ! altitude_callouts_issued[i]
4177 && (last_radio_altitude.ncd
4178 || last_radio_altitude.get() > altitude_callout_definitions[i]))
4180 // lock out all callouts superior or equal to this one
4181 for (unsigned j = 0; j <= i; j++)
4182 altitude_callouts_issued[j] = true;
4184 altitude_callouts_issued[i] = true;
4185 if (! is_near_minimums(altitude_callout_definitions[i])
4186 && ! is_outside_band(mk_data(radio_altitude).get(), altitude_callout_definitions[i])
4187 && (! conf.smart_500_enabled
4188 || altitude_callout_definitions[i] != 500
4189 || ! inhibit_smart_500()))
4191 mk->alert_handler.set_altitude_callout_alert(mk_altitude_voice(i));
4196 mk_unset_alerts(mk_alert(MODE6_ALTITUDE_CALLOUT));
4199 last_radio_altitude.set(&mk_data(radio_altitude));
4203 MK_VIII::Mode6Handler::test_runway (const FGRunway *_runway)
4205 if (_runway->lengthFt() < mk->conf.runway_database)
4209 SGGeod::fromDeg(mk_data(gps_longitude).get(), mk_data(gps_latitude).get()));
4211 // get distance to threshold
4212 double distance, az1, az2;
4213 SGGeodesy::inverse(pos, _runway->threshold(), az1, az2, distance);
4214 return distance * SG_METER_TO_NM <= 5;
4218 MK_VIII::Mode6Handler::test_airport (const FGAirport *airport)
4220 for (unsigned int r=0; r<airport->numRunways(); ++r) {
4221 FGRunway* rwy(airport->getRunwayByIndex(r));
4223 if (test_runway(rwy)) return true;
4229 bool MK_VIII::Mode6Handler::AirportFilter::passAirport(FGAirport* a) const
4231 bool ok = self->test_airport(a);
4236 MK_VIII::Mode6Handler::update_runway ()
4239 if (mk_data(gps_latitude).ncd || mk_data(gps_longitude).ncd) {
4244 // Search for the closest runway threshold in range 5
4245 // nm. Passing 30nm to
4246 // get_closest_airport() provides enough margin for large
4247 // airports, which may have a runway located far away from the
4248 // airport's reference point.
4249 AirportFilter filter(this);
4250 FGPositionedRef apt = FGPositioned::findClosest(
4251 SGGeod::fromDeg(mk_data(gps_longitude).get(), mk_data(gps_latitude).get()),
4254 runway.elevation = apt->elevation();
4257 has_runway.set(apt != NULL);
4261 MK_VIII::Mode6Handler::get_altitude_above_field (Parameter<double> *parameter)
4263 if (! has_runway.ncd && has_runway.get() && ! mk_data(geometric_altitude).ncd)
4264 parameter->set(mk_data(geometric_altitude).get() - runway.elevation);
4270 MK_VIII::Mode6Handler::update_above_field_callout ()
4272 if (! conf.above_field_voice)
4275 // update_runway() has to iterate over the whole runway database
4276 // (which contains thousands of runways), so it would be unwise to
4277 // run it every frame. Run it every 3 seconds. Note that the first
4278 // iteration is run in boot().
4279 if (runway_timer.start_or_elapsed() >= 3)
4282 runway_timer.start();
4285 Parameter<double> altitude_above_field;
4286 get_altitude_above_field(&altitude_above_field);
4288 if (! mk->io_handler.gpws_inhibit()
4289 && (mk->voice_player.voice == conf.above_field_voice
4290 || mk->voice_player.next_voice == conf.above_field_voice))
4293 // handle reset term
4294 if (above_field_issued)
4296 if ((! has_runway.ncd && ! has_runway.get())
4297 || (! altitude_above_field.ncd && altitude_above_field.get() > 700))
4298 above_field_issued = false;
4301 if (! mk->io_handler.gpws_inhibit()
4302 && ! mk->state_handler.ground // [1]
4303 && ! above_field_issued
4304 && ! altitude_above_field.ncd
4305 && altitude_above_field.get() < 550
4306 && (last_altitude_above_field.ncd
4307 || last_altitude_above_field.get() >= 550))
4309 above_field_issued = true;
4311 if (! is_outside_band(altitude_above_field.get(), 500))
4313 mk->alert_handler.set_altitude_callout_alert(conf.above_field_voice);
4318 mk_unset_alerts(mk_alert(MODE6_ALTITUDE_CALLOUT));
4321 last_altitude_above_field.set(&altitude_above_field);
4325 MK_VIII::Mode6Handler::is_bank_angle (double abs_roll_angle, double bias)
4327 return conf.is_bank_angle(&mk_data(radio_altitude),
4328 abs_roll_angle - abs_roll_angle * bias,
4329 mk_dinput(autopilot_engaged));
4333 MK_VIII::Mode6Handler::is_high_bank_angle ()
4335 return mk_data(radio_altitude).ncd || mk_data(radio_altitude).get() >= 210;
4339 MK_VIII::Mode6Handler::get_bank_angle_alerts ()
4341 if (! mk->io_handler.gpws_inhibit()
4342 && ! mk->state_handler.ground // [1]
4343 && ! mk_data(roll_angle).ncd)
4345 double abs_roll_angle = fabs(mk_data(roll_angle).get());
4347 if (is_bank_angle(abs_roll_angle, 0.4))
4348 return is_high_bank_angle()
4349 ? (mk_alert(MODE6_HIGH_BANK_ANGLE_1) | mk_alert(MODE6_HIGH_BANK_ANGLE_2) | mk_alert(MODE6_HIGH_BANK_ANGLE_3))
4350 : (mk_alert(MODE6_LOW_BANK_ANGLE_1) | mk_alert(MODE6_LOW_BANK_ANGLE_2) | mk_alert(MODE6_LOW_BANK_ANGLE_3));
4351 else if (is_bank_angle(abs_roll_angle, 0.2))
4352 return is_high_bank_angle()
4353 ? (mk_alert(MODE6_HIGH_BANK_ANGLE_1) | mk_alert(MODE6_HIGH_BANK_ANGLE_2))
4354 : (mk_alert(MODE6_LOW_BANK_ANGLE_1) | mk_alert(MODE6_LOW_BANK_ANGLE_2));
4355 else if (is_bank_angle(abs_roll_angle, 0))
4356 return is_high_bank_angle()
4357 ? mk_alert(MODE6_HIGH_BANK_ANGLE_1)
4358 : mk_alert(MODE6_LOW_BANK_ANGLE_1);
4365 MK_VIII::Mode6Handler::update_bank_angle ()
4367 if (! conf.bank_angle_enabled)
4370 unsigned int alerts = get_bank_angle_alerts();
4372 // [SPEC] 6.4.4 specifies that the non-continuous alerts
4373 // (MODE6_*_BANK_ANGLE_1 and MODE6_*_BANK_ANGLE_2) are locked out
4374 // until the initial envelope is exited.
4376 if (! test_bits(alerts, mk_alert(MODE6_LOW_BANK_ANGLE_3)))
4377 mk_unset_alerts(mk_alert(MODE6_LOW_BANK_ANGLE_3));
4378 if (! test_bits(alerts, mk_alert(MODE6_HIGH_BANK_ANGLE_3)))
4379 mk_unset_alerts(mk_alert(MODE6_HIGH_BANK_ANGLE_3));
4382 mk_set_alerts(alerts);
4384 mk_unset_alerts(mk_alert(MODE6_LOW_BANK_ANGLE_1)
4385 | mk_alert(MODE6_HIGH_BANK_ANGLE_1)
4386 | mk_alert(MODE6_LOW_BANK_ANGLE_2)
4387 | mk_alert(MODE6_HIGH_BANK_ANGLE_2));
4391 MK_VIII::Mode6Handler::update ()
4393 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
4396 if (! mk->state_handler.takeoff
4397 && ! mk_data(radio_altitude).ncd
4398 && mk_data(radio_altitude).get() > 1000)
4400 reset_altitude_callouts(); // [SPEC] 6.4.2
4401 reset_minimums(); // common sense
4405 update_altitude_callouts();
4406 update_above_field_callout();
4407 update_bank_angle();
4410 ///////////////////////////////////////////////////////////////////////////////
4411 // TCFHandler /////////////////////////////////////////////////////////////////
4412 ///////////////////////////////////////////////////////////////////////////////
4414 // Gets the difference between the azimuth from @from_lat,@from_lon to
4415 // @to_lat,@to_lon, and @to_heading, in degrees.
4417 MK_VIII::TCFHandler::get_azimuth_difference (double from_lat,
4423 double az1, az2, distance;
4424 geo_inverse_wgs_84(0, from_lat, from_lon, to_lat, to_lon, &az1, &az2, &distance);
4425 return get_heading_difference(az1, to_heading);
4428 // Gets the difference between the azimuth from the current GPS
4429 // position to the center of @_runway, and the heading of @_runway, in
4432 MK_VIII::TCFHandler::get_azimuth_difference (const FGRunway *_runway)
4434 return get_azimuth_difference(mk_data(gps_latitude).get(),
4435 mk_data(gps_longitude).get(),
4436 _runway->latitude(),
4437 _runway->longitude(),
4438 _runway->headingDeg());
4441 // Selects the most likely intended destination runway of @airport,
4442 // and returns it in @_runway. For each runway, the difference between
4443 // the azimuth from the current GPS position to the center of the
4444 // runway and its heading is computed. The runway having the smallest
4447 // This selection algorithm is not specified in [SPEC], but
4448 // http://www.egpws.com/general_information/description/runway_select.htm
4449 // talks about automatic runway selection.
4451 MK_VIII::TCFHandler::select_runway (const FGAirport *airport)
4453 FGRunway* _runway = 0;
4454 double min_diff = 360;
4456 for (unsigned int r=0; r<airport->numRunways(); ++r) {
4457 FGRunway* rwy(airport->getRunwayByIndex(r));
4458 double diff = get_azimuth_difference(rwy);
4459 if (diff < min_diff)
4464 } // of airport runways iteration
4468 bool MK_VIII::TCFHandler::AirportFilter::passAirport(FGAirport* aApt) const
4470 return aApt->hasHardRunwayOfLengthFt(mk->conf.runway_database);
4474 MK_VIII::TCFHandler::update_runway ()
4477 if (mk_data(gps_latitude).ncd || mk_data(gps_longitude).ncd) {
4481 // Search for the intended destination runway of the closest
4482 // airport in range 15 nm. Passing 30nm to get_closest_airport()
4483 // provides enough margin for
4484 // large airports, which may have a runway located far away from
4485 // the airport's reference point.
4486 AirportFilter filter(mk);
4487 FGAirport* apt = FGAirport::findClosest(
4488 SGGeod::fromDeg(mk_data(gps_longitude).get(), mk_data(gps_latitude).get()),
4495 FGRunway* _runway = select_runway(apt);
4497 runway.center.latitude = _runway->latitude();
4498 runway.center.longitude = _runway->longitude();
4500 runway.elevation = apt->elevation();
4502 double half_length_m = _runway->lengthM() * 0.5;
4503 runway.half_length = half_length_m * SG_METER_TO_NM;
4505 // b3 ________________ b0
4507 // h1>>> | e1<<<<<<<<e0 | <<<h0
4508 // |________________|
4511 // get heading to runway threshold (h0) and end (h1)
4512 runway.edges[0].heading = _runway->headingDeg();
4513 runway.edges[1].heading = get_reciprocal_heading(_runway->headingDeg());
4517 // get position of runway threshold (e0)
4518 geo_direct_wgs_84(0,
4519 runway.center.latitude,
4520 runway.center.longitude,
4521 runway.edges[1].heading,
4523 &runway.edges[0].position.latitude,
4524 &runway.edges[0].position.longitude,
4527 // get position of runway end (e1)
4528 geo_direct_wgs_84(0,
4529 runway.center.latitude,
4530 runway.center.longitude,
4531 runway.edges[0].heading,
4533 &runway.edges[1].position.latitude,
4534 &runway.edges[1].position.longitude,
4537 double half_width_m = _runway->widthM() * 0.5;
4539 // get position of threshold bias area edges (b0 and b1)
4540 get_bias_area_edges(&runway.edges[0].position,
4541 runway.edges[1].heading,
4543 &runway.bias_area[0],
4544 &runway.bias_area[1]);
4546 // get position of end bias area edges (b2 and b3)
4547 get_bias_area_edges(&runway.edges[1].position,
4548 runway.edges[0].heading,
4550 &runway.bias_area[2],
4551 &runway.bias_area[3]);
4555 MK_VIII::TCFHandler::get_bias_area_edges (Position *edge,
4557 double half_width_m,
4558 Position *bias_edge1,
4559 Position *bias_edge2)
4561 double half_bias_width_m = k * SG_NM_TO_METER + half_width_m;
4562 double tmp_latitude = 0.0, tmp_longitude = 0.0, az = 0.0;
4564 geo_direct_wgs_84(0,
4572 geo_direct_wgs_84(0,
4575 heading_substract(reciprocal, 90),
4577 &bias_edge1->latitude,
4578 &bias_edge1->longitude,
4580 geo_direct_wgs_84(0,
4583 heading_add(reciprocal, 90),
4585 &bias_edge2->latitude,
4586 &bias_edge2->longitude,
4590 // Returns true if the current GPS position is inside the edge
4591 // triangle of @edge. The edge triangle, which is specified and
4592 // represented in [SPEC] 6.3.1, is defined as an isocele right
4593 // triangle of infinite height, whose right angle is located at the
4594 // position of @edge, and whose height is parallel to the heading of
4597 MK_VIII::TCFHandler::is_inside_edge_triangle (RunwayEdge *edge)
4599 return get_azimuth_difference(mk_data(gps_latitude).get(),
4600 mk_data(gps_longitude).get(),
4601 edge->position.latitude,
4602 edge->position.longitude,
4603 edge->heading) <= 45;
4606 // Returns true if the current GPS position is inside the bias area of
4607 // the currently selected runway.
4609 MK_VIII::TCFHandler::is_inside_bias_area ()
4612 double angles_sum = 0;
4614 for (int i = 0; i < 4; i++)
4616 double az2, distance;
4617 geo_inverse_wgs_84(0,
4618 mk_data(gps_latitude).get(),
4619 mk_data(gps_longitude).get(),
4620 runway.bias_area[i].latitude,
4621 runway.bias_area[i].longitude,
4622 &az1[i], &az2, &distance);
4625 for (int i = 0; i < 4; i++)
4627 double angle = az1[i == 3 ? 0 : i + 1] - az1[i];
4631 angles_sum += angle;
4634 return angles_sum > 180;
4638 MK_VIII::TCFHandler::is_tcf ()
4640 if (mk_data(radio_altitude).get() > 10)
4644 double distance, az1, az2;
4646 geo_inverse_wgs_84(0,
4647 mk_data(gps_latitude).get(),
4648 mk_data(gps_longitude).get(),
4649 runway.center.latitude,
4650 runway.center.longitude,
4651 &az1, &az2, &distance);
4653 distance *= SG_METER_TO_NM;
4655 // distance to the inner envelope edge
4656 double edge_distance = distance - runway.half_length - k;
4658 if (edge_distance >= 15)
4660 if (mk_data(radio_altitude).get() < 700)
4663 else if (edge_distance >= 12)
4665 if (mk_data(radio_altitude).get() < 100 * edge_distance - 800)
4668 else if (edge_distance >= 4)
4670 if (mk_data(radio_altitude).get() < 400)
4673 else if (edge_distance >= 2.45)
4675 if (mk_data(radio_altitude).get() < 66.666667 * edge_distance + 133.33333)
4680 if (is_inside_edge_triangle(&runway.edges[0]) || is_inside_edge_triangle(&runway.edges[1]))
4682 if (edge_distance >= 1)
4684 if (mk_data(radio_altitude).get() < 66.666667 * edge_distance + 133.33333)
4687 else if (edge_distance >= 0.05)
4689 if (mk_data(radio_altitude).get() < 200 * edge_distance)
4695 if (! is_inside_bias_area())
4697 if (mk_data(radio_altitude).get() < 245)
4705 if (mk_data(radio_altitude).get() < 700)
4714 MK_VIII::TCFHandler::is_rfcf ()
4718 double distance, az1, az2;
4719 geo_inverse_wgs_84(0,
4720 mk_data(gps_latitude).get(),
4721 mk_data(gps_longitude).get(),
4722 runway.center.latitude,
4723 runway.center.longitude,
4724 &az1, &az2, &distance);
4726 double krf = k + mk_data(gps_vertical_figure_of_merit).get() / 200;
4727 distance = distance * SG_METER_TO_NM - runway.half_length - krf;
4731 double altitude_above_field = mk_data(geometric_altitude).get() - runway.elevation;
4733 if (distance >= 1.5)
4735 if (altitude_above_field < 300)
4738 else if (distance >= 0)
4740 if (altitude_above_field < 200 * distance)
4750 MK_VIII::TCFHandler::update ()
4752 if (mk->configuration_module.state != ConfigurationModule::STATE_OK || ! conf.enabled)
4755 // update_runway() has to iterate over the whole runway database
4756 // (which contains thousands of runways), so it would be unwise to
4757 // run it every frame. Run it every 3 seconds.
4758 if (! runway_timer.running || runway_timer.elapsed() >= 3)
4761 runway_timer.start();
4764 if (! mk_dinput(ta_tcf_inhibit)
4765 && ! mk->state_handler.ground // [1]
4766 && ! mk_data(gps_latitude).ncd
4767 && ! mk_data(gps_longitude).ncd
4768 && ! mk_data(radio_altitude).ncd
4769 && ! mk_data(geometric_altitude).ncd
4770 && ! mk_data(gps_vertical_figure_of_merit).ncd)
4775 _reference = mk_data(radio_altitude).get_pointer();
4777 _reference = mk_data(geometric_altitude).get_pointer();
4783 if (mk_test_alert(TCF_TOO_LOW_TERRAIN))
4785 double new_bias = bias;
4786 while (*reference < initial_value - initial_value * new_bias)
4789 if (new_bias > bias)
4792 mk_repeat_alert(mk_alert(TCF_TOO_LOW_TERRAIN));
4798 reference = _reference;
4799 initial_value = *reference;
4800 mk_set_alerts(mk_alert(TCF_TOO_LOW_TERRAIN));
4807 mk_unset_alerts(mk_alert(TCF_TOO_LOW_TERRAIN));
4810 ///////////////////////////////////////////////////////////////////////////////
4811 // MK_VIII ////////////////////////////////////////////////////////////////////
4812 ///////////////////////////////////////////////////////////////////////////////
4814 MK_VIII::MK_VIII (SGPropertyNode *node)
4815 : properties_handler(this),
4818 power_handler(this),
4819 system_handler(this),
4820 configuration_module(this),
4821 fault_handler(this),
4824 self_test_handler(this),
4825 alert_handler(this),
4826 state_handler(this),
4827 mode1_handler(this),
4828 mode2_handler(this),
4829 mode3_handler(this),
4830 mode4_handler(this),
4831 mode5_handler(this),
4832 mode6_handler(this),
4835 for (int i = 0; i < node->nChildren(); ++i)
4837 SGPropertyNode *child = node->getChild(i);
4838 string cname = child->getName();
4839 string cval = child->getStringValue();
4841 if (cname == "name")
4843 else if (cname == "number")
4844 num = child->getIntValue();
4847 SG_LOG(SG_INSTR, SG_WARN, "Error in mk-viii config logic");
4849 SG_LOG(SG_INSTR, SG_WARN, "Section = " << name);
4857 properties_handler.init();
4858 voice_player.init();
4864 SGPropertyNode *node = fgGetNode(("/instrumentation/" + name).c_str(), num, true);
4866 configuration_module.bind(node);
4867 power_handler.bind(node);
4868 io_handler.bind(node);
4869 voice_player.bind(node);
4875 properties_handler.unbind();
4879 MK_VIII::update (double dt)
4881 power_handler.update();
4882 system_handler.update();
4884 if (system_handler.state != SystemHandler::STATE_ON)
4887 io_handler.update_inputs();
4888 io_handler.update_input_faults();
4890 voice_player.update();
4891 state_handler.update();
4893 if (self_test_handler.update())
4896 io_handler.update_internal_latches();
4897 io_handler.update_lamps();
4899 mode1_handler.update();
4900 mode2_handler.update();
4901 mode3_handler.update();
4902 mode4_handler.update();
4903 mode5_handler.update();
4904 mode6_handler.update();
4905 tcf_handler.update();
4907 alert_handler.update();
4908 io_handler.update_outputs();