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"
79 #include "Main/fg_props.hxx"
80 #include "Main/globals.hxx"
81 #include "instrument_mgr.hxx"
82 #include "mk_viii.hxx"
84 ///////////////////////////////////////////////////////////////////////////////
85 // constants //////////////////////////////////////////////////////////////////
86 ///////////////////////////////////////////////////////////////////////////////
88 #define GLIDESLOPE_DOTS_TO_DDM 0.0875 // [INSTALL] 6.2.30
89 #define GLIDESLOPE_DDM_TO_DOTS (1 / GLIDESLOPE_DOTS_TO_DDM)
91 #define LOCALIZER_DOTS_TO_DDM 0.0775 // [INSTALL] 6.2.33
92 #define LOCALIZER_DDM_TO_DOTS (1 / LOCALIZER_DOTS_TO_DDM)
94 ///////////////////////////////////////////////////////////////////////////////
95 // helpers ////////////////////////////////////////////////////////////////////
96 ///////////////////////////////////////////////////////////////////////////////
98 #define assert_not_reached() assert(false)
99 #define n_elements(_array) (sizeof(_array) / sizeof((_array)[0]))
100 #define test_bits(_bits, _test) (((_bits) & (_test)) != 0)
102 #define mk_node(_name) (mk->properties_handler.external_properties._name)
104 #define mk_dinput_feed(_name) (mk->io_handler.input_feeders.discretes._name)
105 #define mk_dinput(_name) (mk->io_handler.inputs.discretes._name)
106 #define mk_ainput_feed(_name) (mk->io_handler.input_feeders.arinc429._name)
107 #define mk_ainput(_name) (mk->io_handler.inputs.arinc429._name)
108 #define mk_doutput(_name) (mk->io_handler.outputs.discretes._name)
109 #define mk_aoutput(_name) (mk->io_handler.outputs.arinc429._name)
110 #define mk_data(_name) (mk->io_handler.data._name)
112 #define mk_voice(_name) (mk->voice_player.voices._name)
113 #define mk_altitude_voice(_i) (mk->voice_player.voices.altitude_callouts[(_i)])
115 #define mk_alert(_name) (AlertHandler::ALERT_ ## _name)
116 #define mk_alert_flag(_name) (AlertHandler::ALERT_FLAG_ ## _name)
117 #define mk_set_alerts (mk->alert_handler.set_alerts)
118 #define mk_unset_alerts (mk->alert_handler.unset_alerts)
119 #define mk_repeat_alert (mk->alert_handler.repeat_alert)
120 #define mk_test_alert(_name) (mk_test_alerts(mk_alert(_name)))
121 #define mk_test_alerts(_test) (test_bits(mk->alert_handler.alerts, (_test)))
123 const double MK_VIII::TCFHandler::k = 0.25;
126 modify_amplitude (double amplitude, double dB)
128 return amplitude * pow(10.0, dB / 20.0);
132 heading_add (double h1, double h2)
134 double result = h1 + h2;
141 heading_substract (double h1, double h2)
143 double result = h1 - h2;
150 get_heading_difference (double h1, double h2)
152 double diff = h1 - h2;
163 get_reciprocal_heading (double h)
165 return heading_add(h, 180);
168 // Searches for the closest airport whose Manhattan distance to
169 // @lat,@lon is inferior to @min_manhattan_distance (expressed in
170 // degrees) and for which @test_airport returns true. Returns NULL if
171 // no airport was found.
173 static const FGAirport *
174 get_closest_airport (double lat,
176 double min_manhattan_distance,
178 bool (C::*test_airport) (const FGAirport *))
180 const FGAirport *airport = NULL;
181 const airport_list *airport_list = globals->get_airports()->getAirportList();
183 for (size_t i = 0; i < airport_list->size(); i++)
185 const FGAirport *a = (*airport_list)[i];
186 double dist = fabs(lat - a->getLatitude()) + fabs(lon - a->getLongitude());
187 if (dist < min_manhattan_distance && (obj.*test_airport)(a))
190 min_manhattan_distance = dist;
197 ///////////////////////////////////////////////////////////////////////////////
198 // PropertiesHandler //////////////////////////////////////////////////////////
199 ///////////////////////////////////////////////////////////////////////////////
202 MK_VIII::PropertiesHandler::init ()
204 mk_node(ai_caged) = fgGetNode("/instrumentation/attitude-indicator/caged-flag", true);
205 mk_node(ai_roll) = fgGetNode("/instrumentation/attitude-indicator/indicated-roll-deg", true);
206 mk_node(ai_serviceable) = fgGetNode("/instrumentation/attitude-indicator/serviceable", true);
207 mk_node(altimeter_altitude) = fgGetNode("/instrumentation/altimeter/indicated-altitude-ft", true);
208 mk_node(altimeter_serviceable) = fgGetNode("/instrumentation/altimeter/serviceable", true);
209 mk_node(altitude) = fgGetNode("/position/altitude-ft", true);
210 mk_node(altitude_agl) = fgGetNode("/position/altitude-agl-ft", true);
211 mk_node(asi_serviceable) = fgGetNode("/instrumentation/airspeed-indicator/serviceable", true);
212 mk_node(asi_speed) = fgGetNode("/instrumentation/airspeed-indicator/indicated-speed-kt", true);
213 mk_node(autopilot_heading_lock) = fgGetNode("/autopilot/locks/heading", true);
214 mk_node(flaps) = fgGetNode("/controls/flight/flaps", true);
215 mk_node(gear_down) = fgGetNode("/controls/gear/gear-down", true);
216 mk_node(latitude) = fgGetNode("/position/latitude-deg", true);
217 mk_node(longitude) = fgGetNode("/position/longitude-deg", true);
218 mk_node(nav0_cdi_serviceable) = fgGetNode("/instrumentation/nav/cdi/serviceable", true);
219 mk_node(nav0_gs_distance) = fgGetNode("/instrumentation/nav/gs-distance", true);
220 mk_node(nav0_gs_needle_deflection) = fgGetNode("/instrumentation/nav/gs-needle-deflection", true);
221 mk_node(nav0_gs_serviceable) = fgGetNode("/instrumentation/nav/gs/serviceable", true);
222 mk_node(nav0_has_gs) = fgGetNode("/instrumentation/nav/has-gs", true);
223 mk_node(nav0_heading_needle_deflection) = fgGetNode("/instrumentation/nav/heading-needle-deflection", true);
224 mk_node(nav0_in_range) = fgGetNode("/instrumentation/nav/in-range", true);
225 mk_node(nav0_nav_loc) = fgGetNode("/instrumentation/nav/nav-loc", true);
226 mk_node(nav0_serviceable) = fgGetNode("/instrumentation/nav/serviceable", true);
227 mk_node(power) = fgGetNode(("/systems/electrical/outputs/" + mk->name).c_str(), mk->num, true);
228 mk_node(replay_state) = fgGetNode("/sim/freeze/replay-state", true);
229 mk_node(vs) = fgGetNode("/velocities/vertical-speed-fps", true);
233 MK_VIII::PropertiesHandler::unbind ()
235 vector<SGPropertyNode *>::iterator iter;
237 for (iter = tied_properties.begin(); iter != tied_properties.end(); iter++)
240 tied_properties.clear();
243 ///////////////////////////////////////////////////////////////////////////////
244 // PowerHandler ///////////////////////////////////////////////////////////////
245 ///////////////////////////////////////////////////////////////////////////////
248 MK_VIII::PowerHandler::bind (SGPropertyNode *node)
250 mk->properties_handler.tie(node, "serviceable", SGRawValuePointer<bool>(&serviceable));
254 MK_VIII::PowerHandler::handle_abnormal_voltage (bool abnormal,
260 if (timer->start_or_elapsed() >= max_duration)
261 return true; // power loss
270 MK_VIII::PowerHandler::update ()
272 double voltage = mk_node(power)->getDoubleValue();
273 bool now_powered = true;
281 if (handle_abnormal_voltage(voltage < 20.5, &low_surge_timer, 0.03))
283 if (handle_abnormal_voltage(voltage < 22.0 || voltage > 30.3, &abnormal_timer, 300))
285 if (handle_abnormal_voltage(voltage > 32.2, &high_surge_timer, 1))
287 if (handle_abnormal_voltage(voltage > 37.8, &very_high_surge_timer, 0.1))
297 power_loss_timer.stop();
300 if (power_loss_timer.start_or_elapsed() >= 0.2)
312 MK_VIII::PowerHandler::power_on ()
315 mk->system_handler.power_on();
319 MK_VIII::PowerHandler::power_off ()
322 mk->system_handler.power_off();
323 mk->voice_player.stop(VoicePlayer::STOP_NOW);
324 mk->self_test_handler.power_off(); // run before IOHandler::power_off()
325 mk->io_handler.power_off();
326 mk->mode2_handler.power_off();
327 mk->mode6_handler.power_off();
330 ///////////////////////////////////////////////////////////////////////////////
331 // SystemHandler //////////////////////////////////////////////////////////////
332 ///////////////////////////////////////////////////////////////////////////////
335 MK_VIII::SystemHandler::power_on ()
337 state = STATE_BOOTING;
339 // [SPEC] 3.5.2 mentions a 20 seconds maximum boot delay. We use a
340 // random delay between 3 and 5 seconds.
342 boot_delay = 3 + sg_random() * 2;
347 MK_VIII::SystemHandler::power_off ()
355 MK_VIII::SystemHandler::update ()
357 if (state == STATE_BOOTING)
359 if (boot_timer.elapsed() >= boot_delay)
361 last_replay_state = mk_node(replay_state)->getIntValue();
363 mk->configuration_module.boot();
365 mk->io_handler.boot();
366 mk->fault_handler.boot();
367 mk->alert_handler.boot();
369 // inputs are needed by the following boot() routines
370 mk->io_handler.update_inputs();
372 mk->mode2_handler.boot();
373 mk->mode6_handler.boot();
377 mk->io_handler.post_boot();
380 else if (state != STATE_OFF && mk->configuration_module.state == ConfigurationModule::STATE_OK)
382 // If the replay state changes, switch to reposition mode for 3
383 // seconds ([SPEC] 6.0.5) to avoid spurious alerts.
385 int replay_state = mk_node(replay_state)->getIntValue();
386 if (replay_state != last_replay_state)
388 mk->alert_handler.reposition();
390 last_replay_state = replay_state;
391 state = STATE_REPOSITION;
392 reposition_timer.start();
395 if (state == STATE_REPOSITION && reposition_timer.elapsed() >= 3)
397 // inputs are needed by StateHandler::post_reposition()
398 mk->io_handler.update_inputs();
400 mk->state_handler.post_reposition();
407 ///////////////////////////////////////////////////////////////////////////////
408 // ConfigurationModule ////////////////////////////////////////////////////////
409 ///////////////////////////////////////////////////////////////////////////////
411 MK_VIII::ConfigurationModule::ConfigurationModule (MK_VIII *device)
414 // arbitrary defaults
415 categories[CATEGORY_AIRCRAFT_MODE_TYPE_SELECT] = 0;
416 categories[CATEGORY_AIR_DATA_INPUT_SELECT] = 1;
417 categories[CATEGORY_POSITION_INPUT_SELECT] = 2;
418 categories[CATEGORY_ALTITUDE_CALLOUTS] = 13;
419 categories[CATEGORY_AUDIO_MENU_SELECT] = 0;
420 categories[CATEGORY_TERRAIN_DISPLAY_SELECT] = 1;
421 categories[CATEGORY_OPTIONS_SELECT_GROUP_1] = 124;
422 categories[CATEGORY_RADIO_ALTITUDE_INPUT_SELECT] = 2;
423 categories[CATEGORY_NAVIGATION_INPUT_SELECT] = 3;
424 categories[CATEGORY_ATTITUDE_INPUT_SELECT] = 6;
425 categories[CATEGORY_HEADING_INPUT_SELECT] = 2;
426 categories[CATEGORY_WINDSHEAR_INPUT_SELECT] = 0;
427 categories[CATEGORY_INPUT_OUTPUT_DISCRETE_TYPE_SELECT] = 7;
428 categories[CATEGORY_AUDIO_OUTPUT_LEVEL] = 0;
429 categories[CATEGORY_UNDEFINED_INPUT_SELECT_1] = 0;
430 categories[CATEGORY_UNDEFINED_INPUT_SELECT_2] = 0;
431 categories[CATEGORY_UNDEFINED_INPUT_SELECT_3] = 0;
434 static double m1_t1_min_agl1 (double vs) { return -1620 - 1.1133 * vs; }
435 static double m1_t1_min_agl2 (double vs) { return -400 - 0.4 * vs; }
436 static double m1_t4_min_agl1 (double vs) { return -1625.47 - 1.1167 * vs; }
437 static double m1_t4_min_agl2 (double vs) { return -0.1958 * vs; }
439 static int m3_t1_max_agl (bool flap_override) { return 1500; }
440 static double m3_t1_max_alt_loss (bool flap_override, double agl) { return 5.4 + 0.092 * agl; }
441 static int m3_t2_max_agl (bool flap_override) { return flap_override ? 815 : 925; }
442 static double m3_t2_max_alt_loss (bool flap_override, double agl)
444 int bias = agl > 700 ? 5 : 0;
447 return (9.0 + 0.184 * agl) + bias;
449 return (5.4 + 0.092 * agl) + bias;
452 static double m4_t1_min_agl2 (double airspeed) { return -1083 + 8.333 * airspeed; }
453 static double m4_t568_a_min_agl2 (double airspeed) { return -1523 + 11.36 * airspeed; }
454 static double m4_t79_a_min_agl2 (double airspeed) { return -1182 + 11.36 * airspeed; }
455 static double m4_t568_b_min_agl2 (double airspeed) { return -1570 + 11.6 * airspeed; }
456 static double m4_t79_b_min_agl2 (double airspeed) { return -1222 + 11.6 * airspeed; }
459 MK_VIII::ConfigurationModule::m6_t2_is_bank_angle (Parameter<double> *agl,
465 if (agl->ncd || agl->get() > 122)
466 return abs_roll_deg > 33;
470 if (agl->ncd || agl->get() > 2450)
471 return abs_roll_deg > 55;
472 else if (agl->get() > 150)
473 return agl->get() < 153.33333 * abs_roll_deg - 5983.3333;
477 return agl->get() < 4 * abs_roll_deg - 10;
478 else if (agl->get() > 5)
479 return abs_roll_deg > 10;
485 MK_VIII::ConfigurationModule::m6_t4_is_bank_angle (Parameter<double> *agl,
491 if (agl->ncd || agl->get() > 156)
492 return abs_roll_deg > 33;
496 if (agl->ncd || agl->get() > 210)
497 return abs_roll_deg > 50;
501 return agl->get() < 5.7142857 * abs_roll_deg - 75.714286;
507 MK_VIII::ConfigurationModule::read_aircraft_mode_type_select (int value)
509 static const Mode1Handler::EnvelopesConfiguration m1_t1 =
510 { false, 10, m1_t1_min_agl1, 284, m1_t1_min_agl2, 2450 };
511 static const Mode1Handler::EnvelopesConfiguration m1_t4 =
512 { true, 50, m1_t4_min_agl1, 346, m1_t4_min_agl2, 1958 };
514 static const Mode2Handler::Configuration m2_t1 = { 190, 280 };
515 static const Mode2Handler::Configuration m2_t4 = { 220, 310 };
516 static const Mode2Handler::Configuration m2_t5 = { 220, 310 };
518 static const Mode3Handler::Configuration m3_t1 =
519 { 30, m3_t1_max_agl, m3_t1_max_alt_loss };
520 static const Mode3Handler::Configuration m3_t2 =
521 { 50, m3_t2_max_agl, m3_t2_max_alt_loss };
523 static const Mode4Handler::EnvelopesConfiguration m4_t1_ac =
524 { 190, 250, 500, m4_t1_min_agl2, 1000 };
525 static const Mode4Handler::EnvelopesConfiguration m4_t5_ac =
526 { 178, 200, 500, m4_t568_a_min_agl2, 1000 };
527 static const Mode4Handler::EnvelopesConfiguration m4_t68_ac =
528 { 178, 200, 500, m4_t568_a_min_agl2, 750 };
529 static const Mode4Handler::EnvelopesConfiguration m4_t79_ac =
530 { 148, 170, 500, m4_t79_a_min_agl2, 750 };
532 static const Mode4Handler::EnvelopesConfiguration m4_t1_b =
533 { 159, 250, 245, m4_t1_min_agl2, 1000 };
534 static const Mode4Handler::EnvelopesConfiguration m4_t5_b =
535 { 148, 200, 200, m4_t568_b_min_agl2, 1000 };
536 static const Mode4Handler::EnvelopesConfiguration m4_t6_b =
537 { 150, 200, 170, m4_t568_b_min_agl2, 750 };
538 static const Mode4Handler::EnvelopesConfiguration m4_t7_b =
539 { 120, 170, 170, m4_t79_b_min_agl2, 750 };
540 static const Mode4Handler::EnvelopesConfiguration m4_t8_b =
541 { 148, 200, 150, m4_t568_b_min_agl2, 750 };
542 static const Mode4Handler::EnvelopesConfiguration m4_t9_b =
543 { 118, 170, 150, m4_t79_b_min_agl2, 750 };
545 static const Mode4Handler::ModesConfiguration m4_t1 = { &m4_t1_ac, &m4_t1_b };
546 static const Mode4Handler::ModesConfiguration m4_t5 = { &m4_t5_ac, &m4_t5_b };
547 static const Mode4Handler::ModesConfiguration m4_t6 = { &m4_t68_ac, &m4_t6_b };
548 static const Mode4Handler::ModesConfiguration m4_t7 = { &m4_t79_ac, &m4_t7_b };
549 static const Mode4Handler::ModesConfiguration m4_t8 = { &m4_t68_ac, &m4_t8_b };
550 static const Mode4Handler::ModesConfiguration m4_t9 = { &m4_t79_ac, &m4_t9_b };
552 static Mode6Handler::BankAnglePredicate m6_t2 = m6_t2_is_bank_angle;
553 static Mode6Handler::BankAnglePredicate m6_t4 = m6_t4_is_bank_angle;
555 static const IOHandler::FaultsConfiguration f_slow = { 180, 200 };
556 static const IOHandler::FaultsConfiguration f_fast = { 250, 290 };
561 const Mode1Handler::EnvelopesConfiguration *m1;
562 const Mode2Handler::Configuration *m2;
563 const Mode3Handler::Configuration *m3;
564 const Mode4Handler::ModesConfiguration *m4;
565 Mode6Handler::BankAnglePredicate m6;
566 const IOHandler::FaultsConfiguration *f;
568 } aircraft_types[] = {
569 { 0, &m1_t4, &m2_t4, &m3_t2, &m4_t6, m6_t4, &f_fast, 2000 },
570 { 1, &m1_t4, &m2_t4, &m3_t2, &m4_t8, m6_t4, &f_fast, 2000 },
571 { 2, &m1_t4, &m2_t4, &m3_t2, &m4_t5, m6_t4, &f_fast, 2000 },
572 { 3, &m1_t4, &m2_t5, &m3_t2, &m4_t7, m6_t4, &f_slow, 2000 },
573 { 4, &m1_t4, &m2_t5, &m3_t2, &m4_t9, m6_t4, &f_slow, 2000 },
574 { 254, &m1_t1, &m2_t1, &m3_t1, &m4_t1, m6_t2, &f_fast, 3500 },
575 { 255, &m1_t1, &m2_t1, &m3_t1, &m4_t1, m6_t2, &f_fast, 2000 }
578 for (int i = 0; i < n_elements(aircraft_types); i++)
579 if (aircraft_types[i].type == value)
581 mk->mode1_handler.conf.envelopes = aircraft_types[i].m1;
582 mk->mode2_handler.conf = aircraft_types[i].m2;
583 mk->mode3_handler.conf = aircraft_types[i].m3;
584 mk->mode4_handler.conf.modes = aircraft_types[i].m4;
585 mk->mode6_handler.conf.is_bank_angle = aircraft_types[i].m6;
586 mk->io_handler.conf.faults = aircraft_types[i].f;
587 mk->conf.runway_database = aircraft_types[i].runway_database;
591 state = STATE_INVALID_AIRCRAFT_TYPE;
596 MK_VIII::ConfigurationModule::read_air_data_input_select (int value)
599 return (value >= 0 && value <= 6) || (value >= 10 && value <= 13) || value == 255;
603 MK_VIII::ConfigurationModule::read_position_input_select (int value)
606 mk->io_handler.conf.use_internal_gps = true;
607 else if ((value >= 0 && value <= 5)
608 || (value >= 10 && value <= 13)
611 mk->io_handler.conf.use_internal_gps = false;
619 MK_VIII::ConfigurationModule::read_altitude_callouts (int value)
634 { 0, { MINIMUMS, SMART_500, 200, 100, 50, 40, 30, 20, 10, 0 } },
635 { 1, { MINIMUMS, SMART_500, 200, 0 } },
636 { 2, { MINIMUMS, SMART_500, 100, 50, 40, 30, 20, 10, 0 } },
637 { 3, { MINIMUMS, SMART_500, 0 } },
638 { 4, { MINIMUMS, 200, 100, 50, 40, 30, 20, 10, 0 } },
639 { 5, { MINIMUMS, 200, 0 } },
640 { 6, { MINIMUMS, 100, 50, 40, 30, 20, 10, 0 } },
642 { 8, { MINIMUMS, 0 } },
643 { 9, { MINIMUMS, 500, 200, 100, 50, 40, 30, 20, 10, 0 } },
644 { 10, { MINIMUMS, 500, 200, 0 } },
645 { 11, { MINIMUMS, 500, 100, 50, 40, 30, 20, 10, 0 } },
646 { 12, { MINIMUMS, 500, 0 } },
647 { 13, { MINIMUMS, 1000, 500, 400, 300, 200, 100, 50, 40, 30, 20, 10, 0 } },
648 { 14, { MINIMUMS, 100, 0 } },
649 { 15, { MINIMUMS, 200, 100, 0 } },
650 { 100, { FIELD_500, 0 } },
651 { 101, { FIELD_500_ABOVE, 0 } }
656 mk->mode6_handler.conf.minimums_enabled = false;
657 mk->mode6_handler.conf.smart_500_enabled = false;
658 mk->mode6_handler.conf.above_field_voice = NULL;
659 for (i = 0; i < n_altitude_callouts; i++)
660 mk->mode6_handler.conf.altitude_callouts_enabled[i] = false;
662 for (i = 0; i < n_elements(values); i++)
663 if (values[i].id == value)
665 for (int j = 0; values[i].callouts[j] != 0; j++)
666 switch (values[i].callouts[j])
669 mk->mode6_handler.conf.minimums_enabled = true;
673 mk->mode6_handler.conf.smart_500_enabled = true;
677 mk->mode6_handler.conf.above_field_voice = mk_altitude_voice(Mode6Handler::ALTITUDE_CALLOUT_500);
680 case FIELD_500_ABOVE:
681 mk->mode6_handler.conf.above_field_voice = mk_voice(five_hundred_above);
685 for (int k = 0; k < n_altitude_callouts; k++)
686 if (mk->mode6_handler.altitude_callout_definitions[k] == values[i].callouts[j])
687 mk->mode6_handler.conf.altitude_callouts_enabled[k] = true;
698 MK_VIII::ConfigurationModule::read_audio_menu_select (int value)
700 if (value == 0 || value == 1)
701 mk->mode4_handler.conf.voice_too_low_gear = mk_voice(too_low_gear);
702 else if (value == 2 || value == 3)
703 mk->mode4_handler.conf.voice_too_low_gear = mk_voice(too_low_flaps);
711 MK_VIII::ConfigurationModule::read_terrain_display_select (int value)
714 mk->tcf_handler.conf.enabled = false;
715 else if (value == 0 || value == 1 || (value >= 3 && value <= 15)
716 || (value >= 18 && value <= 20) || (value >= 235 && value <= 255))
717 mk->tcf_handler.conf.enabled = true;
725 MK_VIII::ConfigurationModule::read_options_select_group_1 (int value)
727 if (value >= 0 && value < 128)
729 mk->io_handler.conf.flap_reversal = test_bits(value, 1 << 1);
730 mk->mode6_handler.conf.bank_angle_enabled = test_bits(value, 1 << 2);
731 mk->io_handler.conf.steep_approach_enabled = test_bits(value, 1 << 6);
739 MK_VIII::ConfigurationModule::read_radio_altitude_input_select (int value)
742 return (value >= 0 && value <= 4) || (value >= 251 && value <= 255);
746 MK_VIII::ConfigurationModule::read_navigation_input_select (int value)
748 if (value >= 0 && value <= 2)
749 mk->io_handler.conf.localizer_enabled = false;
750 else if ((value >= 3 && value <= 5) || (value >= 250 && value <= 255))
751 mk->io_handler.conf.localizer_enabled = true;
759 MK_VIII::ConfigurationModule::read_attitude_input_select (int value)
762 return (value >= 0 && value <= 6) || value == 253 || value == 255;
766 MK_VIII::ConfigurationModule::read_heading_input_select (int value)
769 return (value >= 0 && value <= 3) || value == 254 || value == 255;
773 MK_VIII::ConfigurationModule::read_windshear_input_select (int value)
776 return value == 0 || (value >= 253 && value <= 255);
780 MK_VIII::ConfigurationModule::read_input_output_discrete_type_select (int value)
785 IOHandler::LampConfiguration lamp_conf;
786 bool gpws_inhibit_enabled;
787 bool momentary_flap_override_enabled;
788 bool alternate_steep_approach;
790 { 0, { false, false }, false, true, true },
791 { 1, { true, false }, false, true, true },
792 { 2, { false, false }, true, true, true },
793 { 3, { true, false }, true, true, true },
794 { 4, { false, true }, true, true, true },
795 { 5, { true, true }, true, true, true },
796 { 6, { false, false }, true, true, false },
797 { 7, { true, false }, true, true, false },
798 { 254, { false, false }, true, false, true },
799 { 255, { false, false }, true, false, true }
802 for (int i = 0; i < n_elements(io_types); i++)
803 if (io_types[i].type == value)
805 mk->io_handler.conf.lamp = &io_types[i].lamp_conf;
806 mk->io_handler.conf.gpws_inhibit_enabled = io_types[i].gpws_inhibit_enabled;
807 mk->io_handler.conf.momentary_flap_override_enabled = io_types[i].momentary_flap_override_enabled;
808 mk->io_handler.conf.alternate_steep_approach = io_types[i].alternate_steep_approach;
816 MK_VIII::ConfigurationModule::read_audio_output_level (int value)
830 for (int i = 0; i < n_elements(values); i++)
831 if (values[i].id == value)
833 mk->voice_player.set_volume(mk->voice_player.conf.volume = modify_amplitude(1.0, values[i].relative_dB));
837 // The self test needs the voice player even when the configuration
838 // is invalid, so set a default value.
839 mk->voice_player.set_volume(mk->voice_player.conf.volume = 1.0);
844 MK_VIII::ConfigurationModule::read_undefined_input_select (int value)
851 MK_VIII::ConfigurationModule::boot ()
853 bool (MK_VIII::ConfigurationModule::*readers[N_CATEGORIES]) (int) = {
854 &MK_VIII::ConfigurationModule::read_aircraft_mode_type_select,
855 &MK_VIII::ConfigurationModule::read_air_data_input_select,
856 &MK_VIII::ConfigurationModule::read_position_input_select,
857 &MK_VIII::ConfigurationModule::read_altitude_callouts,
858 &MK_VIII::ConfigurationModule::read_audio_menu_select,
859 &MK_VIII::ConfigurationModule::read_terrain_display_select,
860 &MK_VIII::ConfigurationModule::read_options_select_group_1,
861 &MK_VIII::ConfigurationModule::read_radio_altitude_input_select,
862 &MK_VIII::ConfigurationModule::read_navigation_input_select,
863 &MK_VIII::ConfigurationModule::read_attitude_input_select,
864 &MK_VIII::ConfigurationModule::read_heading_input_select,
865 &MK_VIII::ConfigurationModule::read_windshear_input_select,
866 &MK_VIII::ConfigurationModule::read_input_output_discrete_type_select,
867 &MK_VIII::ConfigurationModule::read_audio_output_level,
868 &MK_VIII::ConfigurationModule::read_undefined_input_select,
869 &MK_VIII::ConfigurationModule::read_undefined_input_select,
870 &MK_VIII::ConfigurationModule::read_undefined_input_select
873 memcpy(effective_categories, categories, sizeof(categories));
876 for (int i = 0; i < N_CATEGORIES; i++)
877 if (! (this->*readers[i])(effective_categories[i]))
879 SG_LOG(SG_INSTR, SG_ALERT, "MK VIII EGPWS configuration category " << i + 1 << ": invalid value " << effective_categories[i]);
881 if (state == STATE_OK)
882 state = STATE_INVALID_DATABASE;
884 mk_doutput(gpws_inop) = true;
885 mk_doutput(tad_inop) = true;
892 if (mk->mode6_handler.conf.above_field_voice && ! mk->tcf_handler.conf.enabled)
895 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");
896 state = STATE_INVALID_DATABASE;
901 MK_VIII::ConfigurationModule::bind (SGPropertyNode *node)
903 for (int i = 0; i < N_CATEGORIES; i++)
905 std::ostringstream name;
906 name << "configuration-module/category-" << i + 1;
907 mk->properties_handler.tie(node, name.str().c_str(), SGRawValuePointer<int>(&categories[i]));
911 ///////////////////////////////////////////////////////////////////////////////
912 // FaultHandler ///////////////////////////////////////////////////////////////
913 ///////////////////////////////////////////////////////////////////////////////
915 // [INSTALL] only specifies that the faults cause a GPWS INOP
916 // indication. We arbitrarily set a TAD INOP when it makes sense.
917 const unsigned int MK_VIII::FaultHandler::fault_inops[] = {
918 INOP_GPWS | INOP_TAD, // [INSTALL] 3.15.1.3
919 INOP_GPWS, // [INSTALL] appendix E 6.6.2
920 INOP_GPWS, // [INSTALL] appendix E 6.6.4
921 INOP_GPWS, // [INSTALL] appendix E 6.6.6
922 INOP_GPWS | INOP_TAD, // [INSTALL] appendix E 6.6.7
923 INOP_GPWS, // [INSTALL] appendix E 6.6.13
924 INOP_GPWS, // [INSTALL] appendix E 6.6.25
925 INOP_GPWS, // [INSTALL] appendix E 6.6.27
926 INOP_TAD, // unspecified
927 INOP_GPWS, // unspecified
928 INOP_GPWS, // unspecified
929 // [INSTALL] 2.3.10.1 specifies that GPWS INOP is "activated during
930 // any detected partial or total failure of the GPWS modes 1-5", so
931 // do not set any INOP for mode 6 and bank angle.
934 INOP_TAD // unspecified
938 MK_VIII::FaultHandler::has_faults () const
940 for (int i = 0; i < N_FAULTS; i++)
948 MK_VIII::FaultHandler::has_faults (unsigned int inop)
950 for (int i = 0; i < N_FAULTS; i++)
951 if (faults[i] && test_bits(fault_inops[i], inop))
958 MK_VIII::FaultHandler::boot ()
960 memset(faults, 0, sizeof(faults));
964 MK_VIII::FaultHandler::set_fault (Fault fault)
968 faults[fault] = true;
970 mk->self_test_handler.set_inop();
972 if (test_bits(fault_inops[fault], INOP_GPWS))
974 mk_unset_alerts(~mk_alert(TCF_TOO_LOW_TERRAIN));
975 mk_doutput(gpws_inop) = true;
977 if (test_bits(fault_inops[fault], INOP_TAD))
979 mk_unset_alerts(mk_alert(TCF_TOO_LOW_TERRAIN));
980 mk_doutput(tad_inop) = true;
986 MK_VIII::FaultHandler::unset_fault (Fault fault)
990 faults[fault] = false;
991 if (! has_faults(INOP_GPWS))
992 mk_doutput(gpws_inop) = false;
993 if (! has_faults(INOP_TAD))
994 mk_doutput(tad_inop) = false;
998 ///////////////////////////////////////////////////////////////////////////////
999 // IOHandler //////////////////////////////////////////////////////////////////
1000 ///////////////////////////////////////////////////////////////////////////////
1003 MK_VIII::IOHandler::TerrainClearanceFilter::update (double agl)
1005 // [PILOT] page 20 specifies that the terrain clearance is equal to
1006 // 75% of the radio altitude, averaged over the previous 15 seconds.
1008 deque< Sample<double> >::iterator iter;
1010 // remove samples older than 15 seconds
1011 for (iter = samples.begin(); iter != samples.end() && globals->get_sim_time_sec() - (*iter).timestamp >= 15; iter = samples.begin())
1012 samples.erase(iter);
1014 // append new sample
1015 samples.push_back(Sample<double>(agl));
1017 // calculate average
1018 double new_value = 0;
1019 if (samples.size() > 0)
1021 for (iter = samples.begin(); iter != samples.end(); iter++)
1022 new_value += (*iter).value;
1023 new_value /= samples.size();
1027 if (new_value > value)
1034 MK_VIII::IOHandler::TerrainClearanceFilter::reset ()
1040 MK_VIII::IOHandler::IOHandler (MK_VIII *device)
1041 : mk(device), _lamp(LAMP_NONE)
1043 memset(&input_feeders, 0, sizeof(input_feeders));
1044 memset(&inputs.discretes, 0, sizeof(inputs.discretes));
1045 memset(&outputs, 0, sizeof(outputs));
1046 memset(&power_saved, 0, sizeof(power_saved));
1048 mk_dinput_feed(landing_gear) = true;
1049 mk_dinput_feed(landing_flaps) = true;
1050 mk_dinput_feed(glideslope_inhibit) = true;
1051 mk_dinput_feed(decision_height) = true;
1052 mk_dinput_feed(autopilot_engaged) = true;
1053 mk_ainput_feed(uncorrected_barometric_altitude) = true;
1054 mk_ainput_feed(barometric_altitude_rate) = true;
1055 mk_ainput_feed(radio_altitude) = true;
1056 mk_ainput_feed(glideslope_deviation) = true;
1057 mk_ainput_feed(roll_angle) = true;
1058 mk_ainput_feed(localizer_deviation) = true;
1059 mk_ainput_feed(computed_airspeed) = true;
1061 // will be unset on power on
1062 mk_doutput(gpws_inop) = true;
1063 mk_doutput(tad_inop) = true;
1067 MK_VIII::IOHandler::boot ()
1069 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
1072 mk_doutput(gpws_inop) = false;
1073 mk_doutput(tad_inop) = false;
1075 mk_doutput(glideslope_cancel) = power_saved.glideslope_cancel;
1077 altitude_samples.clear();
1081 MK_VIII::IOHandler::post_boot ()
1083 if (momentary_steep_approach_enabled())
1085 last_landing_gear = mk_dinput(landing_gear);
1086 last_real_flaps_down = real_flaps_down();
1089 // read externally-latching input discretes
1090 update_alternate_discrete_input(&mk_dinput(mode6_low_volume));
1091 update_alternate_discrete_input(&mk_dinput(audio_inhibit));
1095 MK_VIII::IOHandler::power_off ()
1097 power_saved.glideslope_cancel = mk_doutput(glideslope_cancel); // [SPEC] 6.2.5
1099 memset(&outputs, 0, sizeof(outputs));
1101 audio_inhibit_fault_timer.stop();
1102 landing_gear_fault_timer.stop();
1103 flaps_down_fault_timer.stop();
1104 momentary_flap_override_fault_timer.stop();
1105 self_test_fault_timer.stop();
1106 glideslope_cancel_fault_timer.stop();
1107 steep_approach_fault_timer.stop();
1108 gpws_inhibit_fault_timer.stop();
1109 ta_tcf_inhibit_fault_timer.stop();
1112 mk_doutput(gpws_inop) = true;
1113 mk_doutput(tad_inop) = true;
1117 MK_VIII::IOHandler::enter_ground ()
1119 reset_terrain_clearance();
1121 if (conf.momentary_flap_override_enabled)
1122 mk_doutput(flap_override) = false; // [INSTALL] 3.15.1.6
1126 MK_VIII::IOHandler::enter_takeoff ()
1128 reset_terrain_clearance();
1130 if (momentary_steep_approach_enabled())
1131 // landing or go-around, disable steep approach as per [SPEC] 6.2.1
1132 mk_doutput(steep_approach) = false;
1136 MK_VIII::IOHandler::update_inputs ()
1138 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
1141 // 1. process input feeders
1143 if (mk_dinput_feed(landing_gear))
1144 mk_dinput(landing_gear) = mk_node(gear_down)->getBoolValue();
1145 if (mk_dinput_feed(landing_flaps))
1146 mk_dinput(landing_flaps) = mk_node(flaps)->getDoubleValue() < 1;
1147 if (mk_dinput_feed(glideslope_inhibit))
1148 mk_dinput(glideslope_inhibit) = mk_node(nav0_gs_distance)->getDoubleValue() < 0;
1149 if (mk_dinput_feed(autopilot_engaged))
1153 mode = mk_node(autopilot_heading_lock)->getStringValue();
1154 mk_dinput(autopilot_engaged) = mode && *mode;
1157 if (mk_ainput_feed(uncorrected_barometric_altitude))
1159 if (mk_node(altimeter_serviceable)->getBoolValue())
1160 mk_ainput(uncorrected_barometric_altitude).set(mk_node(altimeter_altitude)->getDoubleValue());
1162 mk_ainput(uncorrected_barometric_altitude).unset();
1164 if (mk_ainput_feed(barometric_altitude_rate))
1165 // Do not use the vsi, because it is pressure-based only, and
1166 // therefore too laggy for GPWS alerting purposes. I guess that
1167 // a real ADC combines pressure-based and inerta-based altitude
1168 // rates to provide a non-laggy rate. That non-laggy rate is
1169 // best emulated by /velocities/vertical-speed-fps * 60.
1170 mk_ainput(barometric_altitude_rate).set(mk_node(vs)->getDoubleValue() * 60);
1171 if (mk_ainput_feed(radio_altitude))
1173 // Some flight models may return negative values when on the
1174 // ground or after a crash; do not allow them.
1175 double agl = mk_node(altitude_agl)->getDoubleValue();
1176 mk_ainput(radio_altitude).set(SG_MAX2(0.0, agl));
1178 if (mk_ainput_feed(glideslope_deviation))
1180 if (mk_node(nav0_serviceable)->getBoolValue()
1181 && mk_node(nav0_gs_serviceable)->getBoolValue()
1182 && mk_node(nav0_in_range)->getBoolValue()
1183 && mk_node(nav0_has_gs)->getBoolValue())
1184 // gs-needle-deflection is expressed in degrees, and 1 dot =
1185 // 0.32 degrees (according to
1186 // http://www.ntsb.gov/Recs/letters/2000/A00_41_45.pdf)
1187 mk_ainput(glideslope_deviation).set(mk_node(nav0_gs_needle_deflection)->getDoubleValue() / 0.32 * GLIDESLOPE_DOTS_TO_DDM);
1189 mk_ainput(glideslope_deviation).unset();
1191 if (mk_ainput_feed(roll_angle))
1193 if (mk_node(ai_serviceable)->getBoolValue() && ! mk_node(ai_caged)->getBoolValue())
1194 mk_ainput(roll_angle).set(mk_node(ai_roll)->getDoubleValue());
1196 mk_ainput(roll_angle).unset();
1198 if (mk_ainput_feed(localizer_deviation))
1200 if (mk_node(nav0_serviceable)->getBoolValue()
1201 && mk_node(nav0_cdi_serviceable)->getBoolValue()
1202 && mk_node(nav0_in_range)->getBoolValue()
1203 && mk_node(nav0_nav_loc)->getBoolValue())
1204 // heading-needle-deflection is expressed in degrees, and 1
1205 // dot = 2 degrees (0.5 degrees for ILS, but navradio.cxx
1206 // performs the conversion)
1207 mk_ainput(localizer_deviation).set(mk_node(nav0_heading_needle_deflection)->getDoubleValue() / 2 * LOCALIZER_DOTS_TO_DDM);
1209 mk_ainput(localizer_deviation).unset();
1211 if (mk_ainput_feed(computed_airspeed))
1213 if (mk_node(asi_serviceable)->getBoolValue())
1214 mk_ainput(computed_airspeed).set(mk_node(asi_speed)->getDoubleValue());
1216 mk_ainput(computed_airspeed).unset();
1221 mk_data(decision_height).set(&mk_ainput(decision_height));
1222 mk_data(radio_altitude).set(&mk_ainput(radio_altitude));
1223 mk_data(roll_angle).set(&mk_ainput(roll_angle));
1225 // update barometric_altitude_rate as per [SPEC] 6.2.1: "During
1226 // normal conditions, the system will base Mode 1 computations upon
1227 // barometric rate from the ADC. If this computed data is not valid
1228 // or available then the system will use internally computed
1229 // barometric altitude rate."
1231 if (! mk_ainput(barometric_altitude_rate).ncd)
1232 // the altitude rate input is valid, use it
1233 mk_data(barometric_altitude_rate).set(mk_ainput(barometric_altitude_rate).get());
1238 // The altitude rate input is invalid. We can compute an
1239 // altitude rate if all the following conditions are true:
1241 // - the altitude input is valid
1242 // - there is an altitude sample with age >= 1 second
1243 // - that altitude sample is valid
1245 if (! mk_ainput(uncorrected_barometric_altitude).ncd)
1247 if (! altitude_samples.empty() && ! altitude_samples.front().value.ncd)
1249 double elapsed = globals->get_sim_time_sec() - altitude_samples.front().timestamp;
1253 mk_data(barometric_altitude_rate).set((mk_ainput(uncorrected_barometric_altitude).get() - altitude_samples.front().value.get()) / elapsed * 60);
1259 mk_data(barometric_altitude_rate).unset();
1262 altitude_samples.push_back(Sample< Parameter<double> >(mk_ainput(uncorrected_barometric_altitude)));
1264 // Erase everything from the beginning of the list up to the sample
1265 // preceding the most recent sample whose age is >= 1 second.
1267 deque< Sample< Parameter<double> > >::iterator erase_last = altitude_samples.begin();
1268 deque< Sample< Parameter<double> > >::iterator iter;
1270 for (iter = altitude_samples.begin(); iter != altitude_samples.end(); iter++)
1271 if (globals->get_sim_time_sec() - (*iter).timestamp >= 1)
1276 if (erase_last != altitude_samples.begin())
1277 altitude_samples.erase(altitude_samples.begin(), erase_last);
1281 if (conf.use_internal_gps)
1283 mk_data(gps_altitude).set(mk_node(altitude)->getDoubleValue());
1284 mk_data(gps_latitude).set(mk_node(latitude)->getDoubleValue());
1285 mk_data(gps_longitude).set(mk_node(longitude)->getDoubleValue());
1286 mk_data(gps_vertical_figure_of_merit).set(0.0);
1290 mk_data(gps_altitude).set(&mk_ainput(gps_altitude));
1291 mk_data(gps_latitude).set(&mk_ainput(gps_latitude));
1292 mk_data(gps_longitude).set(&mk_ainput(gps_longitude));
1293 mk_data(gps_vertical_figure_of_merit).set(&mk_ainput(gps_vertical_figure_of_merit));
1296 // update glideslope and localizer
1298 mk_data(glideslope_deviation_dots).set(&mk_ainput(glideslope_deviation), GLIDESLOPE_DDM_TO_DOTS);
1299 mk_data(localizer_deviation_dots).set(&mk_ainput(localizer_deviation), LOCALIZER_DDM_TO_DOTS);
1301 // Update geometric altitude; [SPEC] 6.7.8 provides an overview of a
1302 // complex algorithm which combines several input sources to
1303 // calculate the geometric altitude. Since the exact algorithm is
1304 // not given, we fallback to a much simpler method.
1306 if (! mk_data(gps_altitude).ncd)
1307 mk_data(geometric_altitude).set(mk_data(gps_altitude).get());
1308 else if (! mk_ainput(uncorrected_barometric_altitude).ncd)
1309 mk_data(geometric_altitude).set(mk_ainput(uncorrected_barometric_altitude).get());
1311 mk_data(geometric_altitude).unset();
1313 // update terrain clearance
1315 update_terrain_clearance();
1317 // 3. perform sanity checks
1319 if (! mk_data(radio_altitude).ncd && mk_data(radio_altitude).get() < 0)
1320 mk_data(radio_altitude).unset();
1322 if (! mk_data(decision_height).ncd && mk_data(decision_height).get() < 0)
1323 mk_data(decision_height).unset();
1325 if (! mk_data(gps_latitude).ncd
1326 && (mk_data(gps_latitude).get() < -90
1327 || mk_data(gps_latitude).get() > 90))
1328 mk_data(gps_latitude).unset();
1330 if (! mk_data(gps_longitude).ncd
1331 && (mk_data(gps_longitude).get() < -180
1332 || mk_data(gps_longitude).get() > 180))
1333 mk_data(gps_longitude).unset();
1335 if (! mk_data(roll_angle).ncd
1336 && ((mk_data(roll_angle).get() < -180)
1337 || (mk_data(roll_angle).get() > 180)))
1338 mk_data(roll_angle).unset();
1340 // 4. process input feeders requiring data computed above
1342 if (mk_dinput_feed(decision_height))
1343 mk_dinput(decision_height) = ! mk_data(radio_altitude).ncd
1344 && ! mk_data(decision_height).ncd
1345 && mk_data(radio_altitude).get() <= mk_data(decision_height).get();
1349 MK_VIII::IOHandler::update_terrain_clearance ()
1351 if (! mk_data(radio_altitude).ncd)
1352 mk_data(terrain_clearance).set(terrain_clearance_filter.update(mk_data(radio_altitude).get()));
1354 mk_data(terrain_clearance).unset();
1358 MK_VIII::IOHandler::reset_terrain_clearance ()
1360 terrain_clearance_filter.reset();
1361 update_terrain_clearance();
1365 MK_VIII::IOHandler::handle_input_fault (bool test, FaultHandler::Fault fault)
1369 if (! mk->fault_handler.faults[fault])
1370 mk->fault_handler.set_fault(fault);
1373 mk->fault_handler.unset_fault(fault);
1377 MK_VIII::IOHandler::handle_input_fault (bool test,
1379 double max_duration,
1380 FaultHandler::Fault fault)
1384 if (! mk->fault_handler.faults[fault])
1386 if (timer->start_or_elapsed() >= max_duration)
1387 mk->fault_handler.set_fault(fault);
1392 mk->fault_handler.unset_fault(fault);
1398 MK_VIII::IOHandler::update_input_faults ()
1400 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
1403 // [INSTALL] 3.15.1.3
1404 handle_input_fault(mk_dinput(audio_inhibit),
1405 &audio_inhibit_fault_timer,
1407 FaultHandler::FAULT_ALL_MODES_INHIBIT);
1409 // [INSTALL] appendix E 6.6.2
1410 handle_input_fault(mk_dinput(landing_gear)
1411 && ! mk_ainput(computed_airspeed).ncd
1412 && mk_ainput(computed_airspeed).get() > conf.faults->max_gear_down_airspeed,
1413 &landing_gear_fault_timer,
1415 FaultHandler::FAULT_GEAR_SWITCH);
1417 // [INSTALL] appendix E 6.6.4
1418 handle_input_fault(flaps_down()
1419 && ! mk_ainput(computed_airspeed).ncd
1420 && mk_ainput(computed_airspeed).get() > conf.faults->max_flaps_down_airspeed,
1421 &flaps_down_fault_timer,
1423 FaultHandler::FAULT_FLAPS_SWITCH);
1425 // [INSTALL] appendix E 6.6.6
1426 if (conf.momentary_flap_override_enabled)
1427 handle_input_fault(mk_dinput(momentary_flap_override),
1428 &momentary_flap_override_fault_timer,
1430 FaultHandler::FAULT_MOMENTARY_FLAP_OVERRIDE_INVALID);
1432 // [INSTALL] appendix E 6.6.7
1433 handle_input_fault(mk_dinput(self_test),
1434 &self_test_fault_timer,
1436 FaultHandler::FAULT_SELF_TEST_INVALID);
1438 // [INSTALL] appendix E 6.6.13
1439 handle_input_fault(mk_dinput(glideslope_cancel),
1440 &glideslope_cancel_fault_timer,
1442 FaultHandler::FAULT_GLIDESLOPE_CANCEL_INVALID);
1444 // [INSTALL] appendix E 6.6.25
1445 if (momentary_steep_approach_enabled())
1446 handle_input_fault(mk_dinput(steep_approach),
1447 &steep_approach_fault_timer,
1449 FaultHandler::FAULT_STEEP_APPROACH_INVALID);
1451 // [INSTALL] appendix E 6.6.27
1452 if (conf.gpws_inhibit_enabled)
1453 handle_input_fault(mk_dinput(gpws_inhibit),
1454 &gpws_inhibit_fault_timer,
1456 FaultHandler::FAULT_GPWS_INHIBIT);
1458 // [INSTALL] does not specify a fault for this one, but it makes
1459 // sense to have it behave like GPWS inhibit
1460 handle_input_fault(mk_dinput(ta_tcf_inhibit),
1461 &ta_tcf_inhibit_fault_timer,
1463 FaultHandler::FAULT_TA_TCF_INHIBIT);
1465 // [PILOT] page 48: "In the event that required data for a
1466 // particular function is not available, then that function is
1467 // automatically inhibited and annunciated"
1469 handle_input_fault(mk_data(radio_altitude).ncd
1470 || mk_ainput(uncorrected_barometric_altitude).ncd
1471 || mk_data(barometric_altitude_rate).ncd
1472 || mk_ainput(computed_airspeed).ncd
1473 || mk_data(terrain_clearance).ncd,
1474 FaultHandler::FAULT_MODES14_INPUTS_INVALID);
1476 if (! mk_dinput(glideslope_inhibit))
1477 handle_input_fault(mk_data(radio_altitude).ncd,
1478 FaultHandler::FAULT_MODE5_INPUTS_INVALID);
1480 if (mk->mode6_handler.altitude_callouts_enabled())
1481 handle_input_fault(mk->mode6_handler.conf.above_field_voice
1482 ? (mk_data(gps_latitude).ncd
1483 || mk_data(gps_longitude).ncd
1484 || mk_data(geometric_altitude).ncd)
1485 : mk_data(radio_altitude).ncd,
1486 FaultHandler::FAULT_MODE6_INPUTS_INVALID);
1488 if (mk->mode6_handler.conf.bank_angle_enabled)
1489 handle_input_fault(mk_data(roll_angle).ncd,
1490 FaultHandler::FAULT_BANK_ANGLE_INPUTS_INVALID);
1492 if (mk->tcf_handler.conf.enabled)
1493 handle_input_fault(mk_data(radio_altitude).ncd
1494 || mk_data(geometric_altitude).ncd
1495 || mk_data(gps_latitude).ncd
1496 || mk_data(gps_longitude).ncd
1497 || mk_data(gps_vertical_figure_of_merit).ncd,
1498 FaultHandler::FAULT_TCF_INPUTS_INVALID);
1502 MK_VIII::IOHandler::update_alternate_discrete_input (bool *ptr)
1504 assert(mk->system_handler.state == SystemHandler::STATE_ON);
1506 if (ptr == &mk_dinput(mode6_low_volume))
1508 if (mk->configuration_module.state == ConfigurationModule::STATE_OK
1509 && mk->self_test_handler.state == SelfTestHandler::STATE_NONE)
1510 mk->mode6_handler.set_volume(*ptr ? modify_amplitude(1.0, -6) : 1.0);
1512 else if (ptr == &mk_dinput(audio_inhibit))
1514 if (mk->configuration_module.state == ConfigurationModule::STATE_OK
1515 && mk->self_test_handler.state == SelfTestHandler::STATE_NONE)
1516 mk->voice_player.set_volume(*ptr ? 0.0 : mk->voice_player.conf.volume);
1521 MK_VIII::IOHandler::update_internal_latches ()
1523 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
1527 if (conf.momentary_flap_override_enabled
1528 && mk_doutput(flap_override)
1529 && ! mk->state_handler.takeoff
1530 && (mk_data(radio_altitude).ncd || mk_data(radio_altitude).get() <= 50))
1531 mk_doutput(flap_override) = false;
1534 if (momentary_steep_approach_enabled())
1536 if (mk_doutput(steep_approach)
1537 && ! mk->state_handler.takeoff
1538 && ((last_landing_gear && ! mk_dinput(landing_gear))
1539 || (last_real_flaps_down && ! real_flaps_down())))
1540 // gear or flaps raised during approach: it's a go-around
1541 mk_doutput(steep_approach) = false;
1543 last_landing_gear = mk_dinput(landing_gear);
1544 last_real_flaps_down = real_flaps_down();
1548 if (mk_doutput(glideslope_cancel)
1549 && (mk_data(glideslope_deviation_dots).ncd
1550 || mk_data(radio_altitude).ncd
1551 || mk_data(radio_altitude).get() > 2000
1552 || mk_data(radio_altitude).get() < 30))
1553 mk_doutput(glideslope_cancel) = false;
1557 MK_VIII::IOHandler::update_egpws_alert_discrete_1 ()
1559 if (mk->voice_player.voice)
1564 VoicePlayer::Voice *voice;
1566 { 11, mk_voice(sink_rate_pause_sink_rate) },
1567 { 12, mk_voice(pull_up) },
1568 { 13, mk_voice(terrain) },
1569 { 13, mk_voice(terrain_pause_terrain) },
1570 { 14, mk_voice(dont_sink_pause_dont_sink) },
1571 { 15, mk_voice(too_low_gear) },
1572 { 16, mk_voice(too_low_flaps) },
1573 { 17, mk_voice(too_low_terrain) },
1574 { 18, mk_voice(soft_glideslope) },
1575 { 18, mk_voice(hard_glideslope) },
1576 { 19, mk_voice(minimums_minimums) }
1579 for (int i = 0; i < n_elements(voices); i++)
1580 if (voices[i].voice == mk->voice_player.voice)
1582 mk_aoutput(egpws_alert_discrete_1) = 1 << voices[i].bit;
1587 mk_aoutput(egpws_alert_discrete_1) = 0;
1591 MK_VIII::IOHandler::update_egpwc_logic_discretes ()
1593 mk_aoutput(egpwc_logic_discretes) = 0;
1595 if (mk->state_handler.takeoff)
1596 mk_aoutput(egpwc_logic_discretes) |= 1 << 18;
1601 unsigned int alerts;
1603 { 13, mk_alert(TCF_TOO_LOW_TERRAIN) },
1604 { 19, mk_alert(MODE1_SINK_RATE) },
1605 { 20, mk_alert(MODE1_PULL_UP) },
1606 { 21, mk_alert(MODE2A_PREFACE) | mk_alert(MODE2B_PREFACE) | mk_alert(MODE2B_LANDING_MODE) | mk_alert(MODE2A_ALTITUDE_GAIN_TERRAIN_CLOSING) },
1607 { 22, mk_alert(MODE2A) | mk_alert(MODE2B) },
1608 { 23, mk_alert(MODE3) },
1609 { 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) },
1610 { 25, mk_alert(MODE5_SOFT) | mk_alert(MODE5_HARD) }
1613 for (int i = 0; i < n_elements(logic); i++)
1614 if (mk_test_alerts(logic[i].alerts))
1615 mk_aoutput(egpwc_logic_discretes) |= 1 << logic[i].bit;
1619 MK_VIII::IOHandler::update_mode6_callouts_discrete_1 ()
1621 if (mk->voice_player.voice)
1626 VoicePlayer::Voice *voice;
1628 { 11, mk_voice(minimums_minimums) },
1629 { 16, mk_altitude_voice(Mode6Handler::ALTITUDE_CALLOUT_10) },
1630 { 17, mk_altitude_voice(Mode6Handler::ALTITUDE_CALLOUT_20) },
1631 { 18, mk_altitude_voice(Mode6Handler::ALTITUDE_CALLOUT_30) },
1632 { 19, mk_altitude_voice(Mode6Handler::ALTITUDE_CALLOUT_40) },
1633 { 20, mk_altitude_voice(Mode6Handler::ALTITUDE_CALLOUT_50) },
1634 { 23, mk_altitude_voice(Mode6Handler::ALTITUDE_CALLOUT_100) },
1635 { 24, mk_altitude_voice(Mode6Handler::ALTITUDE_CALLOUT_200) },
1636 { 25, mk_altitude_voice(Mode6Handler::ALTITUDE_CALLOUT_300) }
1639 for (int i = 0; i < n_elements(voices); i++)
1640 if (voices[i].voice == mk->voice_player.voice)
1642 mk_aoutput(mode6_callouts_discrete_1) = 1 << voices[i].bit;
1647 mk_aoutput(mode6_callouts_discrete_1) = 0;
1651 MK_VIII::IOHandler::update_mode6_callouts_discrete_2 ()
1653 if (mk->voice_player.voice)
1658 VoicePlayer::Voice *voice;
1660 { 11, mk_altitude_voice(Mode6Handler::ALTITUDE_CALLOUT_400) },
1661 { 12, mk_altitude_voice(Mode6Handler::ALTITUDE_CALLOUT_500) },
1662 { 13, mk_altitude_voice(Mode6Handler::ALTITUDE_CALLOUT_1000) },
1663 { 18, mk_voice(bank_angle_pause_bank_angle) },
1664 { 18, mk_voice(bank_angle_pause_bank_angle_3) },
1665 { 23, mk_voice(five_hundred_above) }
1668 for (int i = 0; i < n_elements(voices); i++)
1669 if (voices[i].voice == mk->voice_player.voice)
1671 mk_aoutput(mode6_callouts_discrete_2) = 1 << voices[i].bit;
1676 mk_aoutput(mode6_callouts_discrete_2) = 0;
1680 MK_VIII::IOHandler::update_egpws_alert_discrete_2 ()
1682 mk_aoutput(egpws_alert_discrete_2) = 1 << 27; // Terrain NA
1684 if (mk_doutput(glideslope_cancel))
1685 mk_aoutput(egpws_alert_discrete_2) |= 1 << 11;
1686 if (mk_doutput(gpws_alert))
1687 mk_aoutput(egpws_alert_discrete_2) |= 1 << 12;
1688 if (mk_doutput(gpws_warning))
1689 mk_aoutput(egpws_alert_discrete_2) |= 1 << 13;
1690 if (mk_doutput(gpws_inop))
1691 mk_aoutput(egpws_alert_discrete_2) |= 1 << 14;
1692 if (mk_doutput(audio_on))
1693 mk_aoutput(egpws_alert_discrete_2) |= 1 << 16;
1694 if (mk_doutput(tad_inop))
1695 mk_aoutput(egpws_alert_discrete_2) |= 1 << 24;
1696 if (mk->fault_handler.has_faults())
1697 mk_aoutput(egpws_alert_discrete_2) |= 1 << 25;
1701 MK_VIII::IOHandler::update_egpwc_alert_discrete_3 ()
1703 mk_aoutput(egpwc_alert_discrete_3) = 1 << 17 | 1 << 18;
1705 if (mk->fault_handler.faults[FaultHandler::FAULT_MODES14_INPUTS_INVALID])
1706 mk_aoutput(egpwc_alert_discrete_3) |= 1 << 11;
1707 if (mk->fault_handler.faults[FaultHandler::FAULT_MODE5_INPUTS_INVALID])
1708 mk_aoutput(egpwc_alert_discrete_3) |= 1 << 12;
1709 if (mk->fault_handler.faults[FaultHandler::FAULT_MODE6_INPUTS_INVALID])
1710 mk_aoutput(egpwc_alert_discrete_3) |= 1 << 13;
1711 if (mk->fault_handler.faults[FaultHandler::FAULT_BANK_ANGLE_INPUTS_INVALID])
1712 mk_aoutput(egpwc_alert_discrete_3) |= 1 << 14;
1713 if (mk_doutput(tad_inop))
1714 mk_aoutput(egpwc_alert_discrete_3) |= 1 << 16;
1718 MK_VIII::IOHandler::update_outputs ()
1720 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
1723 mk_doutput(audio_on) = ! mk_dinput(audio_inhibit)
1724 && mk->voice_player.voice
1725 && ! mk->voice_player.voice->element->silence;
1727 update_egpws_alert_discrete_1();
1728 update_egpwc_logic_discretes();
1729 update_mode6_callouts_discrete_1();
1730 update_mode6_callouts_discrete_2();
1731 update_egpws_alert_discrete_2();
1732 update_egpwc_alert_discrete_3();
1736 MK_VIII::IOHandler::get_lamp_output (Lamp lamp)
1740 case LAMP_GLIDESLOPE:
1741 return &mk_doutput(gpws_alert);
1744 return conf.lamp->format2 ? &mk_doutput(gpws_alert) : &mk_doutput(gpws_warning);
1747 return &mk_doutput(gpws_warning);
1750 assert_not_reached();
1756 MK_VIII::IOHandler::update_lamps ()
1758 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
1761 if (_lamp != LAMP_NONE && conf.lamp->flashing)
1763 // [SPEC] 6.9.3: 70 cycles per minute
1764 if (lamp_timer.elapsed() >= 60.0 / 70.0 / 2.0)
1766 bool *output = get_lamp_output(_lamp);
1767 *output = ! *output;
1774 MK_VIII::IOHandler::set_lamp (Lamp lamp)
1781 mk_doutput(gpws_warning) = false;
1782 mk_doutput(gpws_alert) = false;
1784 if (lamp != LAMP_NONE)
1786 *get_lamp_output(lamp) = true;
1792 MK_VIII::IOHandler::gpws_inhibit () const
1794 return conf.gpws_inhibit_enabled && mk_dinput(gpws_inhibit);
1798 MK_VIII::IOHandler::real_flaps_down () const
1800 return conf.flap_reversal ? mk_dinput(landing_flaps) : ! mk_dinput(landing_flaps);
1804 MK_VIII::IOHandler::flaps_down () const
1806 return flap_override() ? true : real_flaps_down();
1810 MK_VIII::IOHandler::flap_override () const
1812 return conf.momentary_flap_override_enabled ? mk_doutput(flap_override) : false;
1816 MK_VIII::IOHandler::steep_approach () const
1818 if (conf.steep_approach_enabled)
1819 // If alternate action was configured in category 13, we return
1820 // the value of the input discrete (which should be connected to a
1821 // latching steep approach cockpit switch). Otherwise, we return
1822 // the value of the output discrete.
1823 return conf.alternate_steep_approach ? mk_dinput(steep_approach) : mk_doutput(steep_approach);
1829 MK_VIII::IOHandler::momentary_steep_approach_enabled () const
1831 return conf.steep_approach_enabled && ! conf.alternate_steep_approach;
1835 MK_VIII::IOHandler::tie_input (SGPropertyNode *node,
1840 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));
1842 mk->properties_handler.tie(node, (string("input-feeders/discretes/") + name).c_str(), SGRawValuePointer<bool>(feed));
1846 MK_VIII::IOHandler::tie_input (SGPropertyNode *node,
1848 Parameter<double> *input,
1851 mk->properties_handler.tie(node, (string("inputs/arinc429/") + name).c_str(), SGRawValuePointer<double>(input->get_pointer()));
1852 mk->properties_handler.tie(node, (string("inputs/arinc429/") + name + "-ncd").c_str(), SGRawValuePointer<bool>(&input->ncd));
1854 mk->properties_handler.tie(node, (string("input-feeders/arinc429/") + name).c_str(), SGRawValuePointer<bool>(feed));
1858 MK_VIII::IOHandler::tie_output (SGPropertyNode *node,
1862 SGPropertyNode *child = node->getNode((string("outputs/discretes/") + name).c_str(), true);
1864 mk->properties_handler.tie(child, SGRawValuePointer<bool>(output));
1865 child->setAttribute(SGPropertyNode::WRITE, false);
1869 MK_VIII::IOHandler::tie_output (SGPropertyNode *node,
1873 SGPropertyNode *child = node->getNode((string("outputs/arinc429/") + name).c_str(), true);
1875 mk->properties_handler.tie(child, SGRawValuePointer<int>(output));
1876 child->setAttribute(SGPropertyNode::WRITE, false);
1880 MK_VIII::IOHandler::bind (SGPropertyNode *node)
1882 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));
1884 tie_input(node, "landing-gear", &mk_dinput(landing_gear), &mk_dinput_feed(landing_gear));
1885 tie_input(node, "landing-flaps", &mk_dinput(landing_flaps), &mk_dinput_feed(landing_flaps));
1886 tie_input(node, "momentary-flap-override", &mk_dinput(momentary_flap_override));
1887 tie_input(node, "self-test", &mk_dinput(self_test));
1888 tie_input(node, "glideslope-inhibit", &mk_dinput(glideslope_inhibit), &mk_dinput_feed(glideslope_inhibit));
1889 tie_input(node, "glideslope-cancel", &mk_dinput(glideslope_cancel));
1890 tie_input(node, "decision-height", &mk_dinput(decision_height), &mk_dinput_feed(decision_height));
1891 tie_input(node, "mode6-low-volume", &mk_dinput(mode6_low_volume));
1892 tie_input(node, "audio-inhibit", &mk_dinput(audio_inhibit));
1893 tie_input(node, "ta-tcf-inhibit", &mk_dinput(ta_tcf_inhibit));
1894 tie_input(node, "autopilot-engaged", &mk_dinput(autopilot_engaged), &mk_dinput_feed(autopilot_engaged));
1895 tie_input(node, "steep-approach", &mk_dinput(steep_approach));
1896 tie_input(node, "gpws-inhibit", &mk_dinput(gpws_inhibit));
1898 tie_input(node, "uncorrected-barometric-altitude", &mk_ainput(uncorrected_barometric_altitude), &mk_ainput_feed(uncorrected_barometric_altitude));
1899 tie_input(node, "barometric-altitude-rate", &mk_ainput(barometric_altitude_rate), &mk_ainput_feed(barometric_altitude_rate));
1900 tie_input(node, "gps-altitude", &mk_ainput(gps_altitude));
1901 tie_input(node, "gps-latitude", &mk_ainput(gps_latitude));
1902 tie_input(node, "gps-longitude", &mk_ainput(gps_longitude));
1903 tie_input(node, "gps-vertical-figure-of-merit", &mk_ainput(gps_vertical_figure_of_merit));
1904 tie_input(node, "radio-altitude", &mk_ainput(radio_altitude), &mk_ainput_feed(radio_altitude));
1905 tie_input(node, "glideslope-deviation", &mk_ainput(glideslope_deviation), &mk_ainput_feed(glideslope_deviation));
1906 tie_input(node, "roll-angle", &mk_ainput(roll_angle), &mk_ainput_feed(roll_angle));
1907 tie_input(node, "localizer-deviation", &mk_ainput(localizer_deviation), &mk_ainput_feed(localizer_deviation));
1908 tie_input(node, "computed-airspeed", &mk_ainput(computed_airspeed), &mk_ainput_feed(computed_airspeed));
1909 tie_input(node, "decision-height", &mk_ainput(decision_height), &mk_ainput_feed(decision_height));
1911 tie_output(node, "gpws-warning", &mk_doutput(gpws_warning));
1912 tie_output(node, "gpws-alert", &mk_doutput(gpws_alert));
1913 tie_output(node, "audio-on", &mk_doutput(audio_on));
1914 tie_output(node, "gpws-inop", &mk_doutput(gpws_inop));
1915 tie_output(node, "tad-inop", &mk_doutput(tad_inop));
1916 tie_output(node, "flap-override", &mk_doutput(flap_override));
1917 tie_output(node, "glideslope-cancel", &mk_doutput(glideslope_cancel));
1918 tie_output(node, "steep-approach", &mk_doutput(steep_approach));
1920 tie_output(node, "egpws-alert-discrete-1", &mk_aoutput(egpws_alert_discrete_1));
1921 tie_output(node, "egpwc-logic-discretes", &mk_aoutput(egpwc_logic_discretes));
1922 tie_output(node, "mode6-callouts-discrete-1", &mk_aoutput(mode6_callouts_discrete_1));
1923 tie_output(node, "mode6-callouts-discrete-2", &mk_aoutput(mode6_callouts_discrete_2));
1924 tie_output(node, "egpws-alert-discrete-2", &mk_aoutput(egpws_alert_discrete_2));
1925 tie_output(node, "egpwc-alert-discrete-3", &mk_aoutput(egpwc_alert_discrete_3));
1929 MK_VIII::IOHandler::get_discrete_input (bool *ptr) const
1935 MK_VIII::IOHandler::set_discrete_input (bool *ptr, bool value)
1940 if (mk->system_handler.state == SystemHandler::STATE_ON)
1942 if (ptr == &mk_dinput(momentary_flap_override))
1944 if (mk->configuration_module.state == ConfigurationModule::STATE_OK
1945 && mk->self_test_handler.state == SelfTestHandler::STATE_NONE
1946 && conf.momentary_flap_override_enabled
1948 mk_doutput(flap_override) = ! mk_doutput(flap_override);
1950 else if (ptr == &mk_dinput(self_test))
1951 mk->self_test_handler.handle_button_event(value);
1952 else if (ptr == &mk_dinput(glideslope_cancel))
1954 if (mk->configuration_module.state == ConfigurationModule::STATE_OK
1955 && mk->self_test_handler.state == SelfTestHandler::STATE_NONE
1958 if (! mk_doutput(glideslope_cancel))
1962 // Although we are not called from update(), we are
1963 // sure that the inputs we use here are defined,
1964 // since state is STATE_ON.
1966 if (! mk_data(glideslope_deviation_dots).ncd
1967 && ! mk_data(radio_altitude).ncd
1968 && mk_data(radio_altitude).get() <= 2000
1969 && mk_data(radio_altitude).get() >= 30)
1970 mk_doutput(glideslope_cancel) = true;
1972 // else do nothing ([PILOT] page 22: "Glideslope Cancel
1973 // can not be deselected (reset) by again pressing the
1974 // Glideslope Cancel switch.")
1977 else if (ptr == &mk_dinput(steep_approach))
1979 if (mk->configuration_module.state == ConfigurationModule::STATE_OK
1980 && mk->self_test_handler.state == SelfTestHandler::STATE_NONE
1981 && momentary_steep_approach_enabled()
1983 mk_doutput(steep_approach) = ! mk_doutput(steep_approach);
1989 if (mk->system_handler.state == SystemHandler::STATE_ON)
1990 update_alternate_discrete_input(ptr);
1994 MK_VIII::IOHandler::present_status_section (const char *name)
1996 printf("%s\n", name);
2000 MK_VIII::IOHandler::present_status_item (const char *name, const char *value)
2003 printf("\t%-32s %s\n", name, value);
2005 printf("\t%s\n", name);
2009 MK_VIII::IOHandler::present_status_subitem (const char *name)
2011 printf("\t\t%s\n", name);
2015 MK_VIII::IOHandler::present_status ()
2019 if (mk->system_handler.state != SystemHandler::STATE_ON)
2022 present_status_section("EGPWC CONFIGURATION");
2023 present_status_item("PART NUMBER:", "965-1220-000"); // [SPEC] 1.1
2024 present_status_item("MOD STATUS:", "N/A");
2025 present_status_item("SERIAL NUMBER:", "N/A");
2027 present_status_item("APPLICATION S/W VERSION:", FLIGHTGEAR_VERSION);
2028 present_status_item("TERRAIN DATABASE VERSION:", FLIGHTGEAR_VERSION);
2029 present_status_item("ENVELOPE MOD DATABASE VERSION:", "N/A");
2030 present_status_item("BOOT CODE VERSION:", FLIGHTGEAR_VERSION);
2033 present_status_section("CURRENT FAULTS");
2034 if (mk->configuration_module.state == ConfigurationModule::STATE_OK && ! mk->fault_handler.has_faults())
2035 present_status_item("NO FAULTS");
2038 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
2040 present_status_item("GPWS COMPUTER FAULTS:");
2041 switch (mk->configuration_module.state)
2043 case ConfigurationModule::STATE_INVALID_DATABASE:
2044 present_status_subitem("APPLICATION DATABASE FAILED");
2047 case ConfigurationModule::STATE_INVALID_AIRCRAFT_TYPE:
2048 present_status_subitem("CONFIGURATION TYPE INVALID");
2052 assert_not_reached();
2058 present_status_item("GPWS COMPUTER OK");
2059 present_status_item("GPWS EXTERNAL FAULTS:");
2061 static const char *fault_names[] = {
2062 "ALL MODES INHIBIT",
2065 "MOMENTARY FLAP OVERRIDE INVALID",
2066 "SELF TEST INVALID",
2067 "GLIDESLOPE CANCEL INVALID",
2068 "STEEP APPROACH INVALID",
2071 "MODES 1-4 INPUTS INVALID",
2072 "MODE 5 INPUTS INVALID",
2073 "MODE 6 INPUTS INVALID",
2074 "BANK ANGLE INPUTS INVALID",
2075 "TCF INPUTS INVALID"
2078 for (int i = 0; i < n_elements(fault_names); i++)
2079 if (mk->fault_handler.faults[i])
2080 present_status_subitem(fault_names[i]);
2085 present_status_section("CONFIGURATION:");
2087 static const char *category_names[] = {
2090 "POSITION INPUT TYPE",
2091 "CALLOUTS OPTION TYPE",
2093 "TERRAIN DISPLAY TYPE",
2095 "RADIO ALTITUDE TYPE",
2096 "NAVIGATION INPUT TYPE",
2098 "MAGNETIC HEADING TYPE",
2099 "WINDSHEAR INPUT TYPE",
2104 for (int i = 0; i < n_elements(category_names); i++)
2106 std::ostringstream value;
2107 value << "= " << mk->configuration_module.effective_categories[i];
2108 present_status_item(category_names[i], value.str().c_str());
2113 MK_VIII::IOHandler::get_present_status () const
2119 MK_VIII::IOHandler::set_present_status (bool value)
2121 if (value) present_status();
2125 ///////////////////////////////////////////////////////////////////////////////
2126 // VoicePlayer ////////////////////////////////////////////////////////////////
2127 ///////////////////////////////////////////////////////////////////////////////
2130 MK_VIII::VoicePlayer::Speaker::bind (SGPropertyNode *node)
2132 // uses xmlsound property names
2133 tie(node, "volume", &volume);
2134 tie(node, "pitch", &pitch);
2135 tie(node, "position/x", &position[0]);
2136 tie(node, "position/y", &position[1]);
2137 tie(node, "position/z", &position[2]);
2138 tie(node, "orientation/x", &orientation[0]);
2139 tie(node, "orientation/y", &orientation[1]);
2140 tie(node, "orientation/z", &orientation[2]);
2141 tie(node, "orientation/inner-cone", &inner_cone);
2142 tie(node, "orientation/outer-cone", &outer_cone);
2143 tie(node, "reference-dist", &reference_dist);
2144 tie(node, "max-dist", &max_dist);
2148 MK_VIII::VoicePlayer::Speaker::update_configuration ()
2150 map<string, SGSoundSample *>::iterator iter;
2151 for (iter = player->samples.begin(); iter != player->samples.end(); iter++)
2153 SGSoundSample *sample = (*iter).second;
2155 sample->set_pitch(pitch);
2156 sample->set_offset_pos(position);
2157 sample->set_orientation(orientation,
2161 sample->set_reference_dist(reference_dist);
2162 sample->set_max_dist(max_dist);
2166 player->voice->volume_changed();
2169 MK_VIII::VoicePlayer::Voice::~Voice ()
2171 for (iter = elements.begin(); iter != elements.end(); iter++)
2172 delete *iter; // we owned the element
2177 MK_VIII::VoicePlayer::Voice::play ()
2179 iter = elements.begin();
2182 element->play(get_volume());
2186 MK_VIII::VoicePlayer::Voice::stop (bool now)
2190 if (now || element->silence)
2196 iter = elements.end() - 1; // stop after the current element finishes
2201 MK_VIII::VoicePlayer::Voice::set_volume (double _volume)
2208 MK_VIII::VoicePlayer::Voice::volume_changed ()
2211 element->set_volume(get_volume());
2215 MK_VIII::VoicePlayer::Voice::update ()
2219 if (! element->is_playing())
2221 if (++iter == elements.end())
2226 element->play(get_volume());
2232 MK_VIII::VoicePlayer::~VoicePlayer ()
2234 vector<Voice *>::iterator iter1;
2235 for (iter1 = _voices.begin(); iter1 != _voices.end(); iter1++)
2239 /* sound mgr already destroyed - samples already deleted
2240 map<string, SGSoundSample *>::iterator iter2;
2241 for (iter2 = samples.begin(); iter2 != samples.end(); iter2++)
2243 bool status = globals->get_soundmgr()->remove((*iter2).first);
2251 MK_VIII::VoicePlayer::init ()
2253 #define STDPAUSE 0.75 // [SPEC] 6.4.4: "the standard 0.75 second delay"
2255 make_voice(&voices.application_data_base_failed, "application-data-base-failed");
2256 make_voice(&voices.bank_angle, "bank-angle");
2257 make_voice(&voices.bank_angle_bank_angle, "bank-angle", "bank-angle");
2258 make_voice(&voices.bank_angle_bank_angle_3, "bank-angle", "bank-angle", 3.0);
2259 make_voice(&voices.bank_angle_inop, "bank-angle-inop");
2260 make_voice(&voices.bank_angle_pause_bank_angle, "bank-angle", STDPAUSE, "bank-angle");
2261 make_voice(&voices.bank_angle_pause_bank_angle_3, "bank-angle", STDPAUSE, "bank-angle", 3.0);
2262 make_voice(&voices.callouts_inop, "callouts-inop");
2263 make_voice(&voices.configuration_type_invalid, "configuration-type-invalid");
2264 make_voice(&voices.dont_sink, "dont-sink");
2265 make_voice(&voices.dont_sink_pause_dont_sink, "dont-sink", STDPAUSE, "dont-sink");
2266 make_voice(&voices.five_hundred_above, "500-above");
2267 make_voice(&voices.glideslope, "glideslope");
2268 make_voice(&voices.glideslope_inop, "glideslope-inop");
2269 make_voice(&voices.gpws_inop, "gpws-inop");
2270 make_voice(&voices.hard_glideslope, "glideslope", "glideslope", 3.0);
2271 make_voice(&voices.minimums, "minimums");
2272 make_voice(&voices.minimums_minimums, "minimums", "minimums");
2273 make_voice(&voices.pull_up, "pull-up");
2274 make_voice(&voices.sink_rate, "sink-rate");
2275 make_voice(&voices.sink_rate_pause_sink_rate, "sink-rate", STDPAUSE, "sink-rate");
2276 make_voice(&voices.soft_glideslope, new Voice::SampleElement(get_sample("glideslope"), modify_amplitude(1.0, -6)));
2277 make_voice(&voices.terrain, "terrain");
2278 make_voice(&voices.terrain_pause_terrain, "terrain", STDPAUSE, "terrain");
2279 make_voice(&voices.too_low_flaps, "too-low-flaps");
2280 make_voice(&voices.too_low_gear, "too-low-gear");
2281 make_voice(&voices.too_low_terrain, "too-low-terrain");
2283 for (int i = 0; i < n_altitude_callouts; i++)
2285 std::ostringstream name;
2286 name << "altitude-" << mk->mode6_handler.altitude_callout_definitions[i];
2287 make_voice(&voices.altitude_callouts[i], name.str().c_str());
2290 speaker.update_configuration();
2294 MK_VIII::VoicePlayer::get_sample (const char *name)
2296 std::ostringstream refname;
2297 refname << mk->name << "[" << mk->num << "]" << "/" << name;
2299 SGSoundSample *sample = globals->get_soundmgr()->find(refname.str());
2302 SGPath sample_path(globals->get_fg_root());
2303 sample_path.append("Sounds/mk-viii");
2305 string filename = string(name) + ".wav";
2308 sample = new SGSoundSample(sample_path.c_str(), filename.c_str());
2310 catch (const sg_exception &e)
2312 SG_LOG(SG_INSTR, SG_ALERT, "Error loading MK VIII sound sample \"" + filename + "\": " + e.getFormattedMessage());
2316 globals->get_soundmgr()->add(sample, refname.str());
2317 samples[refname.str()] = sample;
2324 MK_VIII::VoicePlayer::play (Voice *_voice, unsigned int flags)
2326 if (test_bits(flags, PLAY_NOW) || ! voice || voice->element->silence)
2332 looped = test_bits(flags, PLAY_LOOPED);
2335 next_looped = false;
2341 next_voice = _voice;
2342 next_looped = test_bits(flags, PLAY_LOOPED);
2347 MK_VIII::VoicePlayer::stop (unsigned int flags)
2351 voice->stop(test_bits(flags, STOP_NOW));
2361 MK_VIII::VoicePlayer::set_volume (double _volume)
2365 voice->volume_changed();
2369 MK_VIII::VoicePlayer::update ()
2377 if (! voice->element || voice->element->silence)
2380 looped = next_looped;
2383 next_looped = false;
2390 if (! voice->element)
2401 ///////////////////////////////////////////////////////////////////////////////
2402 // SelfTestHandler ////////////////////////////////////////////////////////////
2403 ///////////////////////////////////////////////////////////////////////////////
2406 MK_VIII::SelfTestHandler::_was_here (int position)
2408 if (position > current)
2417 MK_VIII::SelfTestHandler::Action
2418 MK_VIII::SelfTestHandler::sleep (double duration)
2420 Action _action = { ACTION_SLEEP, duration, NULL };
2424 MK_VIII::SelfTestHandler::Action
2425 MK_VIII::SelfTestHandler::play (VoicePlayer::Voice *voice)
2427 mk->voice_player.play(voice);
2428 Action _action = { ACTION_VOICE, 0, NULL };
2432 MK_VIII::SelfTestHandler::Action
2433 MK_VIII::SelfTestHandler::discrete_on (bool *discrete, double duration)
2436 return sleep(duration);
2439 MK_VIII::SelfTestHandler::Action
2440 MK_VIII::SelfTestHandler::discrete_on_off (bool *discrete, double duration)
2443 Action _action = { ACTION_SLEEP | ACTION_DISCRETE_ON_OFF, duration, discrete };
2447 MK_VIII::SelfTestHandler::Action
2448 MK_VIII::SelfTestHandler::discrete_on_off (bool *discrete, VoicePlayer::Voice *voice)
2451 mk->voice_player.play(voice);
2452 Action _action = { ACTION_VOICE | ACTION_DISCRETE_ON_OFF, 0, discrete };
2456 MK_VIII::SelfTestHandler::Action
2457 MK_VIII::SelfTestHandler::done ()
2459 Action _action = { ACTION_DONE, 0, NULL };
2463 MK_VIII::SelfTestHandler::Action
2464 MK_VIII::SelfTestHandler::run ()
2466 // Note that "Terrain INOP" and "Terrain NA" are or'ed to the same
2467 // output discrete (see [SPEC] 6.9.3.5).
2469 #define was_here() was_here_offset(0)
2470 #define was_here_offset(offset) _was_here(__LINE__ * 20 + (offset))
2474 if (mk->configuration_module.state == ConfigurationModule::STATE_INVALID_DATABASE)
2475 return play(mk_voice(application_data_base_failed));
2476 else if (mk->configuration_module.state == ConfigurationModule::STATE_INVALID_AIRCRAFT_TYPE)
2477 return play(mk_voice(configuration_type_invalid));
2479 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
2483 return discrete_on(&mk_doutput(gpws_inop), 0.7);
2485 return sleep(0.7); // W/S INOP
2487 return discrete_on(&mk_doutput(tad_inop), 0.7);
2490 mk_doutput(gpws_inop) = false;
2491 // do not disable tad_inop since we must enable Terrain NA
2492 // do not disable W/S INOP since we do not emulate it
2493 return sleep(0.7); // Terrain NA
2497 mk_doutput(tad_inop) = false; // disable Terrain NA
2498 if (mk->io_handler.conf.momentary_flap_override_enabled)
2499 return discrete_on_off(&mk_doutput(flap_override), 1.0);
2502 return discrete_on_off(&mk_doutput(audio_on), 1.0);
2505 if (mk->io_handler.momentary_steep_approach_enabled())
2506 return discrete_on_off(&mk_doutput(steep_approach), 1.0);
2511 if (mk_dinput(glideslope_inhibit))
2512 goto glideslope_end;
2515 if (mk->fault_handler.faults[FaultHandler::FAULT_MODE5_INPUTS_INVALID])
2516 goto glideslope_inop;
2520 return discrete_on_off(&mk_doutput(gpws_alert), mk_voice(glideslope));
2522 return discrete_on_off(&mk_doutput(glideslope_cancel), 0.7);
2523 goto glideslope_end;
2526 return play(mk_voice(glideslope_inop));
2531 if (mk->fault_handler.faults[FaultHandler::FAULT_MODES14_INPUTS_INVALID])
2535 return discrete_on_off(&mk_doutput(gpws_warning), mk_voice(pull_up));
2539 return play(mk_voice(gpws_inop));
2544 if (mk_dinput(self_test)) // proceed to long self test
2549 if (mk->mode6_handler.conf.bank_angle_enabled
2550 && mk->fault_handler.faults[FaultHandler::FAULT_BANK_ANGLE_INPUTS_INVALID])
2551 return play(mk_voice(bank_angle_inop));
2555 if (mk->mode6_handler.altitude_callouts_enabled()
2556 && mk->fault_handler.faults[FaultHandler::FAULT_MODE6_INPUTS_INVALID])
2557 return play(mk_voice(callouts_inop));
2565 mk_doutput(gpws_inop) = true;
2566 // do not enable W/S INOP, since we do not emulate it
2567 mk_doutput(tad_inop) = true; // Terrain INOP, Terrain NA
2569 return play(mk_voice(sink_rate));
2572 return play(mk_voice(pull_up));
2574 return play(mk_voice(terrain));
2576 return play(mk_voice(pull_up));
2578 return play(mk_voice(dont_sink));
2580 return play(mk_voice(too_low_terrain));
2582 return play(mk->mode4_handler.conf.voice_too_low_gear);
2584 return play(mk_voice(too_low_flaps));
2586 return play(mk_voice(too_low_terrain));
2588 return play(mk_voice(glideslope));
2591 if (mk->mode6_handler.conf.bank_angle_enabled)
2592 return play(mk_voice(bank_angle));
2597 if (! mk->mode6_handler.altitude_callouts_enabled())
2598 goto callouts_disabled;
2602 if (mk->mode6_handler.conf.minimums_enabled)
2603 return play(mk_voice(minimums));
2607 if (mk->mode6_handler.conf.above_field_voice)
2608 return play(mk->mode6_handler.conf.above_field_voice);
2610 for (int i = 0; i < n_altitude_callouts; i++)
2611 if (! was_here_offset(i))
2613 if (mk->mode6_handler.conf.altitude_callouts_enabled[i])
2614 return play(mk_altitude_voice(i));
2618 if (mk->mode6_handler.conf.smart_500_enabled)
2619 return play(mk_altitude_voice(Mode6Handler::ALTITUDE_CALLOUT_500));
2624 return play(mk_voice(minimums_minimums));
2629 if (mk->tcf_handler.conf.enabled)
2630 return play(mk_voice(too_low_terrain));
2637 MK_VIII::SelfTestHandler::start ()
2639 assert(state == STATE_START);
2641 memcpy(&saved_outputs, &mk->io_handler.outputs, sizeof(mk->io_handler.outputs));
2642 memset(&mk->io_handler.outputs, 0, sizeof(mk->io_handler.outputs));
2644 // [SPEC] 6.10.6: "The self-test results are annunciated, at 6db
2645 // lower than the normal audio level selected for the aircraft"
2646 mk->voice_player.set_volume(modify_amplitude(mk->voice_player.conf.volume, -6));
2648 if (mk_dinput(mode6_low_volume))
2649 mk->mode6_handler.set_volume(1.0);
2651 state = STATE_RUNNING;
2652 cancel = CANCEL_NONE;
2653 memset(&action, 0, sizeof(action));
2658 MK_VIII::SelfTestHandler::stop ()
2660 if (state != STATE_NONE)
2662 if (state != STATE_START)
2664 mk->voice_player.stop(VoicePlayer::STOP_NOW);
2665 mk->voice_player.set_volume(mk_dinput(audio_inhibit) ? 0.0 : mk->voice_player.conf.volume);
2667 if (mk_dinput(mode6_low_volume))
2668 mk->mode6_handler.set_volume(modify_amplitude(1.0, -6));
2670 memcpy(&mk->io_handler.outputs, &saved_outputs, sizeof(mk->io_handler.outputs));
2673 button_pressed = false;
2679 MK_VIII::SelfTestHandler::handle_button_event (bool value)
2681 if (state == STATE_NONE)
2684 state = STATE_START;
2686 else if (state == STATE_START)
2689 state = STATE_NONE; // cancel the not-yet-started test
2691 else if (cancel == CANCEL_NONE)
2695 assert(! button_pressed);
2696 button_pressed = true;
2697 button_press_timestamp = globals->get_sim_time_sec();
2703 if (globals->get_sim_time_sec() - button_press_timestamp < 2)
2704 cancel = CANCEL_SHORT;
2706 cancel = CANCEL_LONG;
2713 MK_VIII::SelfTestHandler::update ()
2715 if (state == STATE_NONE)
2717 else if (state == STATE_START)
2719 if (mk->state_handler.ground && ! mk->io_handler.steep_approach())
2729 if (button_pressed && cancel == CANCEL_NONE)
2731 if (globals->get_sim_time_sec() - button_press_timestamp >= 2)
2732 cancel = CANCEL_LONG;
2736 if (! mk->state_handler.ground || cancel != CANCEL_NONE)
2742 if (test_bits(action.flags, ACTION_SLEEP))
2744 if (globals->get_sim_time_sec() - sleep_start < action.sleep_duration)
2747 if (test_bits(action.flags, ACTION_VOICE))
2749 if (mk->voice_player.voice)
2752 if (test_bits(action.flags, ACTION_DISCRETE_ON_OFF))
2753 *action.discrete = false;
2757 if (test_bits(action.flags, ACTION_SLEEP))
2758 sleep_start = globals->get_sim_time_sec();
2759 if (test_bits(action.flags, ACTION_DONE))
2768 ///////////////////////////////////////////////////////////////////////////////
2769 // AlertHandler ///////////////////////////////////////////////////////////////
2770 ///////////////////////////////////////////////////////////////////////////////
2773 MK_VIII::AlertHandler::select_voice_alerts (unsigned int test)
2775 if (has_alerts(test))
2777 voice_alerts = alerts & test;
2782 voice_alerts &= ~test;
2783 if (voice_alerts == 0)
2784 mk->voice_player.stop();
2791 MK_VIII::AlertHandler::boot ()
2797 MK_VIII::AlertHandler::reposition ()
2801 mk->io_handler.set_lamp(IOHandler::LAMP_NONE);
2802 mk->voice_player.stop(VoicePlayer::STOP_NOW);
2806 MK_VIII::AlertHandler::reset ()
2811 repeated_alerts = 0;
2812 altitude_callout_voice = NULL;
2816 MK_VIII::AlertHandler::update ()
2818 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
2821 // Lamps and voices are prioritized according to [SPEC] 6.9.2.
2823 // Voices which can interrupt other voices (VoicePlayer::PLAY_NOW)
2824 // are specified by [INSTALL] appendix E 5.3.5.
2826 // When a voice is overriden by a higher priority voice and the
2827 // overriding voice stops, we restore the previous voice if it was
2828 // looped (this is not specified by [SPEC] but seems to make sense).
2832 if (has_alerts(ALERT_MODE1_PULL_UP | ALERT_MODE2A | ALERT_MODE2B))
2833 mk->io_handler.set_lamp(IOHandler::LAMP_WARNING);
2834 else if (has_alerts(ALERT_MODE1_SINK_RATE
2835 | ALERT_MODE2A_PREFACE
2836 | ALERT_MODE2B_PREFACE
2837 | ALERT_MODE2A_ALTITUDE_GAIN_TERRAIN_CLOSING
2838 | ALERT_MODE2A_ALTITUDE_GAIN
2839 | ALERT_MODE2B_LANDING_MODE
2841 | ALERT_MODE4_TOO_LOW_FLAPS
2842 | ALERT_MODE4_TOO_LOW_GEAR
2843 | ALERT_MODE4AB_TOO_LOW_TERRAIN
2844 | ALERT_MODE4C_TOO_LOW_TERRAIN
2845 | ALERT_TCF_TOO_LOW_TERRAIN))
2846 mk->io_handler.set_lamp(IOHandler::LAMP_CAUTION);
2847 else if (has_alerts(ALERT_MODE5_SOFT | ALERT_MODE5_HARD))
2848 mk->io_handler.set_lamp(IOHandler::LAMP_GLIDESLOPE);
2850 mk->io_handler.set_lamp(IOHandler::LAMP_NONE);
2854 if (select_voice_alerts(ALERT_MODE1_PULL_UP))
2856 if (! has_old_alerts(ALERT_MODE1_PULL_UP))
2858 if (mk->voice_player.voice != mk_voice(sink_rate_pause_sink_rate))
2859 mk->voice_player.play(mk_voice(sink_rate), VoicePlayer::PLAY_NOW);
2860 mk->voice_player.play(mk_voice(pull_up), VoicePlayer::PLAY_LOOPED);
2863 else if (select_voice_alerts(ALERT_MODE2A_PREFACE | ALERT_MODE2B_PREFACE))
2865 if (! has_old_alerts(ALERT_MODE2A_PREFACE | ALERT_MODE2B_PREFACE))
2866 mk->voice_player.play(mk_voice(terrain_pause_terrain), VoicePlayer::PLAY_NOW);
2868 else if (select_voice_alerts(ALERT_MODE2A | ALERT_MODE2B))
2870 if (mk->voice_player.voice != mk_voice(pull_up))
2871 mk->voice_player.play(mk_voice(pull_up), VoicePlayer::PLAY_NOW | VoicePlayer::PLAY_LOOPED);
2873 else if (select_voice_alerts(ALERT_MODE2A_ALTITUDE_GAIN_TERRAIN_CLOSING | ALERT_MODE2B_LANDING_MODE))
2875 if (mk->voice_player.voice != mk_voice(terrain))
2876 mk->voice_player.play(mk_voice(terrain), VoicePlayer::PLAY_LOOPED);
2878 else if (select_voice_alerts(ALERT_MODE6_MINIMUMS))
2880 if (! has_old_alerts(ALERT_MODE6_MINIMUMS))
2881 mk->voice_player.play(mk_voice(minimums_minimums));
2883 else if (select_voice_alerts(ALERT_MODE4AB_TOO_LOW_TERRAIN | ALERT_MODE4C_TOO_LOW_TERRAIN))
2885 if (must_play_voice(ALERT_MODE4AB_TOO_LOW_TERRAIN | ALERT_MODE4C_TOO_LOW_TERRAIN))
2886 mk->voice_player.play(mk_voice(too_low_terrain));
2888 else if (select_voice_alerts(ALERT_TCF_TOO_LOW_TERRAIN))
2890 if (must_play_voice(ALERT_TCF_TOO_LOW_TERRAIN))
2891 mk->voice_player.play(mk_voice(too_low_terrain));
2893 else if (select_voice_alerts(ALERT_MODE6_ALTITUDE_CALLOUT))
2895 if (! has_old_alerts(ALERT_MODE6_ALTITUDE_CALLOUT))
2897 assert(altitude_callout_voice != NULL);
2898 mk->voice_player.play(altitude_callout_voice);
2901 else if (select_voice_alerts(ALERT_MODE4_TOO_LOW_GEAR))
2903 if (must_play_voice(ALERT_MODE4_TOO_LOW_GEAR))
2904 mk->voice_player.play(mk->mode4_handler.conf.voice_too_low_gear);
2906 else if (select_voice_alerts(ALERT_MODE4_TOO_LOW_FLAPS))
2908 if (must_play_voice(ALERT_MODE4_TOO_LOW_FLAPS))
2909 mk->voice_player.play(mk_voice(too_low_flaps));
2911 else if (select_voice_alerts(ALERT_MODE1_SINK_RATE))
2913 if (must_play_voice(ALERT_MODE1_SINK_RATE))
2914 mk->voice_player.play(mk_voice(sink_rate_pause_sink_rate));
2915 // [SPEC] 6.2.1: "During the time that the voice message for the
2916 // outer envelope is inhibited and the caution/warning lamp is
2917 // on, the Mode 5 alert message is allowed to annunciate for
2918 // excessive glideslope deviation conditions."
2919 else if (mk->voice_player.voice != mk_voice(sink_rate_pause_sink_rate)
2920 && mk->voice_player.next_voice != mk_voice(sink_rate_pause_sink_rate))
2922 if (has_alerts(ALERT_MODE5_HARD))
2924 voice_alerts |= ALERT_MODE5_HARD;
2925 if (mk->voice_player.voice != mk_voice(hard_glideslope))
2926 mk->voice_player.play(mk_voice(hard_glideslope), VoicePlayer::PLAY_LOOPED);
2928 else if (has_alerts(ALERT_MODE5_SOFT))
2930 voice_alerts |= ALERT_MODE5_SOFT;
2931 if (must_play_voice(ALERT_MODE5_SOFT))
2932 mk->voice_player.play(mk_voice(soft_glideslope));
2936 else if (select_voice_alerts(ALERT_MODE3))
2938 if (must_play_voice(ALERT_MODE3))
2939 mk->voice_player.play(mk_voice(dont_sink_pause_dont_sink));
2941 else if (select_voice_alerts(ALERT_MODE5_HARD))
2943 if (mk->voice_player.voice != mk_voice(hard_glideslope))
2944 mk->voice_player.play(mk_voice(hard_glideslope), VoicePlayer::PLAY_LOOPED);
2946 else if (select_voice_alerts(ALERT_MODE5_SOFT))
2948 if (must_play_voice(ALERT_MODE5_SOFT))
2949 mk->voice_player.play(mk_voice(soft_glideslope));
2951 else if (select_voice_alerts(ALERT_MODE6_LOW_BANK_ANGLE_3))
2953 if (mk->voice_player.voice != mk_voice(bank_angle_bank_angle_3))
2954 mk->voice_player.play(mk_voice(bank_angle_bank_angle_3), VoicePlayer::PLAY_LOOPED);
2956 else if (select_voice_alerts(ALERT_MODE6_HIGH_BANK_ANGLE_3))
2958 if (mk->voice_player.voice != mk_voice(bank_angle_pause_bank_angle_3))
2959 mk->voice_player.play(mk_voice(bank_angle_pause_bank_angle_3), VoicePlayer::PLAY_LOOPED);
2961 else if (select_voice_alerts(ALERT_MODE6_LOW_BANK_ANGLE_2))
2963 if (! has_old_alerts(ALERT_MODE6_LOW_BANK_ANGLE_2 | ALERT_MODE6_HIGH_BANK_ANGLE_2))
2964 mk->voice_player.play(mk_voice(bank_angle_bank_angle));
2966 else if (select_voice_alerts(ALERT_MODE6_HIGH_BANK_ANGLE_2))
2968 if (! has_old_alerts(ALERT_MODE6_LOW_BANK_ANGLE_2 | ALERT_MODE6_HIGH_BANK_ANGLE_2))
2969 mk->voice_player.play(mk_voice(bank_angle_pause_bank_angle));
2971 else if (select_voice_alerts(ALERT_MODE6_LOW_BANK_ANGLE_1))
2973 if (! has_old_alerts(ALERT_MODE6_LOW_BANK_ANGLE_1 | ALERT_MODE6_HIGH_BANK_ANGLE_1))
2974 mk->voice_player.play(mk_voice(bank_angle_bank_angle));
2976 else if (select_voice_alerts(ALERT_MODE6_HIGH_BANK_ANGLE_1))
2978 if (! has_old_alerts(ALERT_MODE6_LOW_BANK_ANGLE_1 | ALERT_MODE6_HIGH_BANK_ANGLE_1))
2979 mk->voice_player.play(mk_voice(bank_angle_pause_bank_angle));
2984 old_alerts = alerts;
2985 repeated_alerts = 0;
2986 altitude_callout_voice = NULL;
2990 MK_VIII::AlertHandler::set_alerts (unsigned int _alerts,
2992 VoicePlayer::Voice *_altitude_callout_voice)
2995 if (test_bits(flags, ALERT_FLAG_REPEAT))
2996 repeated_alerts |= _alerts;
2997 if (_altitude_callout_voice)
2998 altitude_callout_voice = _altitude_callout_voice;
3002 MK_VIII::AlertHandler::unset_alerts (unsigned int _alerts)
3005 repeated_alerts &= ~_alerts;
3008 ///////////////////////////////////////////////////////////////////////////////
3009 // StateHandler ///////////////////////////////////////////////////////////////
3010 ///////////////////////////////////////////////////////////////////////////////
3013 MK_VIII::StateHandler::update_ground ()
3017 if (! mk_ainput(computed_airspeed).ncd && mk_ainput(computed_airspeed).get() > 60
3018 && ! mk_data(radio_altitude).ncd && mk_data(radio_altitude).get() > 30)
3020 if (potentially_airborne_timer.start_or_elapsed() > 1)
3024 potentially_airborne_timer.stop();
3028 if (! mk_ainput(computed_airspeed).ncd && mk_ainput(computed_airspeed).get() < 40
3029 && ! mk_data(radio_altitude).ncd && mk_data(radio_altitude).get() < 30)
3035 MK_VIII::StateHandler::enter_ground ()
3038 mk->io_handler.enter_ground();
3040 // [SPEC] 6.0.1 does not specify this, but it seems to be an
3041 // omission; at this point, we must make sure that we are in takeoff
3042 // mode (otherwise the next takeoff might happen in approach mode).
3048 MK_VIII::StateHandler::leave_ground ()
3051 mk->mode2_handler.leave_ground();
3055 MK_VIII::StateHandler::update_takeoff ()
3059 // [SPEC] 6.0.2 is somewhat confusing: it mentions hardcoded
3060 // terrain clearance (500, 750) and airspeed (178, 200) values,
3061 // but it also mentions the mode 4A boundary, which varies as a
3062 // function of aircraft type. We consider that the mentioned
3063 // values are examples, and that we should use the mode 4A upper
3066 if (! mk_data(terrain_clearance).ncd
3067 && ! mk_ainput(computed_airspeed).ncd
3068 && mk_data(terrain_clearance).get() > mk->mode4_handler.get_upper_agl(mk->mode4_handler.conf.modes->ac))
3073 if (! mk_data(radio_altitude).ncd
3074 && mk_data(radio_altitude).get() < mk->mode4_handler.conf.modes->b->min_agl1
3075 && mk->io_handler.flaps_down()
3076 && mk_dinput(landing_gear))
3082 MK_VIII::StateHandler::enter_takeoff ()
3085 mk->io_handler.enter_takeoff();
3086 mk->mode2_handler.enter_takeoff();
3087 mk->mode3_handler.enter_takeoff();
3088 mk->mode6_handler.enter_takeoff();
3092 MK_VIII::StateHandler::leave_takeoff ()
3095 mk->mode6_handler.leave_takeoff();
3099 MK_VIII::StateHandler::post_reposition ()
3101 // Synchronize the state with the current situation.
3104 (! mk_ainput(computed_airspeed).ncd && mk_ainput(computed_airspeed).get() > 60
3105 && ! mk_data(radio_altitude).ncd && mk_data(radio_altitude).get() > 30);
3107 bool _takeoff = _ground;
3109 if (ground && ! _ground)
3111 else if (! ground && _ground)
3114 if (takeoff && ! _takeoff)
3116 else if (! takeoff && _takeoff)
3121 MK_VIII::StateHandler::update ()
3123 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
3130 ///////////////////////////////////////////////////////////////////////////////
3131 // Mode1Handler ///////////////////////////////////////////////////////////////
3132 ///////////////////////////////////////////////////////////////////////////////
3135 MK_VIII::Mode1Handler::get_pull_up_bias ()
3137 // [PILOT] page 54: "Steep Approach has priority over Flap Override
3138 // if selected simultaneously."
3140 if (mk->io_handler.steep_approach())
3142 else if (conf.envelopes->flap_override_bias && mk->io_handler.flap_override())
3149 MK_VIII::Mode1Handler::is_pull_up ()
3151 if (! mk->io_handler.gpws_inhibit()
3152 && ! mk->state_handler.ground // [1]
3153 && ! mk_data(radio_altitude).ncd
3154 && ! mk_data(barometric_altitude_rate).ncd
3155 && mk_data(radio_altitude).get() > conf.envelopes->min_agl)
3157 if (mk_data(radio_altitude).get() < conf.envelopes->pull_up_max_agl1)
3159 if (mk_data(radio_altitude).get() < conf.envelopes->pull_up_min_agl1(mk_data(barometric_altitude_rate).get() + get_pull_up_bias()))
3162 else if (mk_data(radio_altitude).get() < conf.envelopes->pull_up_max_agl2)
3164 if (mk_data(radio_altitude).get() < conf.envelopes->pull_up_min_agl2(mk_data(barometric_altitude_rate).get() + get_pull_up_bias()))
3173 MK_VIII::Mode1Handler::update_pull_up ()
3177 if (! mk_test_alert(MODE1_PULL_UP))
3179 // [SPEC] 6.2.1: at least one sink rate must be issued
3180 // before switching to pull up; if no sink rate has
3181 // occurred, a 0.2 second delay is used.
3182 if (mk->voice_player.voice == mk_voice(sink_rate_pause_sink_rate)
3183 || pull_up_timer.start_or_elapsed() >= 0.2)
3184 mk_set_alerts(mk_alert(MODE1_PULL_UP));
3189 pull_up_timer.stop();
3190 mk_unset_alerts(mk_alert(MODE1_PULL_UP));
3195 MK_VIII::Mode1Handler::get_sink_rate_bias ()
3197 // [PILOT] page 54: "Steep Approach has priority over Flap Override
3198 // if selected simultaneously."
3200 if (mk->io_handler.steep_approach())
3202 else if (conf.envelopes->flap_override_bias && mk->io_handler.flap_override())
3204 else if (! mk_data(glideslope_deviation_dots).ncd)
3208 if (mk_data(glideslope_deviation_dots).get() <= -2)
3210 else if (mk_data(glideslope_deviation_dots).get() < 0)
3211 bias = -150 * mk_data(glideslope_deviation_dots).get();
3213 if (mk_data(radio_altitude).get() < 100)
3214 bias *= 0.01 * mk_data(radio_altitude).get();
3223 MK_VIII::Mode1Handler::is_sink_rate ()
3225 return ! mk->io_handler.gpws_inhibit()
3226 && ! mk->state_handler.ground // [1]
3227 && ! mk_data(radio_altitude).ncd
3228 && ! mk_data(barometric_altitude_rate).ncd
3229 && mk_data(radio_altitude).get() > conf.envelopes->min_agl
3230 && mk_data(radio_altitude).get() < 2450
3231 && mk_data(radio_altitude).get() < -572 - 0.6035 * (mk_data(barometric_altitude_rate).get() + get_sink_rate_bias());
3235 MK_VIII::Mode1Handler::get_sink_rate_tti ()
3237 return mk_data(radio_altitude).get() / fabs(mk_data(barometric_altitude_rate).get());
3241 MK_VIII::Mode1Handler::update_sink_rate ()
3245 if (mk_test_alert(MODE1_SINK_RATE))
3247 double tti = get_sink_rate_tti();
3248 if (tti <= sink_rate_tti * 0.8)
3250 // ~20% degradation, warn again and store the new reference tti
3251 sink_rate_tti = tti;
3252 mk_repeat_alert(mk_alert(MODE1_SINK_RATE));
3257 if (sink_rate_timer.start_or_elapsed() >= 0.8)
3259 mk_set_alerts(mk_alert(MODE1_SINK_RATE));
3260 sink_rate_tti = get_sink_rate_tti();
3266 sink_rate_timer.stop();
3267 mk_unset_alerts(mk_alert(MODE1_SINK_RATE));
3272 MK_VIII::Mode1Handler::update ()
3274 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
3281 ///////////////////////////////////////////////////////////////////////////////
3282 // Mode2Handler ///////////////////////////////////////////////////////////////
3283 ///////////////////////////////////////////////////////////////////////////////
3286 MK_VIII::Mode2Handler::ClosureRateFilter::limit_radio_altitude_rate (double r)
3288 // Limit radio altitude rate according to aircraft configuration,
3289 // allowing maximum sensitivity during cruise while providing
3290 // progressively less sensitivity during the landing phases of
3293 if (! mk_data(glideslope_deviation_dots).ncd && fabs(mk_data(glideslope_deviation_dots).get()) <= 2)
3294 { // gs deviation <= +- 2 dots
3295 if (mk_dinput(landing_gear) && mk->io_handler.flaps_down())
3296 SG_CLAMP_RANGE(r, -1000.0, 3000.0);
3297 else if (mk_dinput(landing_gear) || mk->io_handler.flaps_down())
3298 SG_CLAMP_RANGE(r, 0.0, 4000.0);
3300 SG_CLAMP_RANGE(r, 1000.0, 5000.0);
3303 { // no ILS, or gs deviation > +- 2 dots
3304 if (mk_dinput(landing_gear) && mk->io_handler.flaps_down())
3305 SG_CLAMP_RANGE(r, 0.0, 4000.0);
3306 else if (mk_dinput(landing_gear) || mk->io_handler.flaps_down())
3307 SG_CLAMP_RANGE(r, 1000.0, 5000.0);
3315 MK_VIII::Mode2Handler::ClosureRateFilter::init ()
3318 last_ra.set(&mk_data(radio_altitude));
3319 last_ba.set(&mk_ainput(uncorrected_barometric_altitude));
3326 MK_VIII::Mode2Handler::ClosureRateFilter::update ()
3328 double elapsed = timer.start_or_elapsed();
3332 if (! mk_data(radio_altitude).ncd && ! mk_ainput(uncorrected_barometric_altitude).ncd)
3334 if (! last_ra.ncd && ! last_ba.ncd)
3336 // compute radio and barometric altitude rates (positive value = descent)
3337 double ra_rate = -(mk_data(radio_altitude).get() - last_ra.get()) / elapsed * 60;
3338 double ba_rate = -(mk_ainput(uncorrected_barometric_altitude).get() - last_ba.get()) / elapsed * 60;
3340 // limit radio altitude rate according to aircraft configuration
3341 ra_rate = limit_radio_altitude_rate(ra_rate);
3343 // apply a low-pass filter to the radio altitude rate
3344 ra_rate = ra_filter.filter(ra_rate);
3345 // apply a high-pass filter to the barometric altitude rate
3346 ba_rate = ba_filter.filter(ba_rate);
3348 // combine both rates to obtain a closure rate
3349 output.set(ra_rate + ba_rate);
3362 last_ra.set(&mk_data(radio_altitude));
3363 last_ba.set(&mk_ainput(uncorrected_barometric_altitude));
3367 MK_VIII::Mode2Handler::b_conditions ()
3369 return mk->io_handler.flaps_down()
3370 || (! mk_data(glideslope_deviation_dots).ncd && fabs(mk_data(glideslope_deviation_dots).get()) < 2)
3371 || takeoff_timer.running;
3375 MK_VIII::Mode2Handler::is_a ()
3377 if (! mk->io_handler.gpws_inhibit()
3378 && ! mk->state_handler.ground // [1]
3379 && ! mk_data(radio_altitude).ncd
3380 && ! mk_ainput(computed_airspeed).ncd
3381 && ! closure_rate_filter.output.ncd
3382 && ! b_conditions())
3384 if (mk_data(radio_altitude).get() < 1220)
3386 if (mk_data(radio_altitude).get() < -1579 + 0.7895 * closure_rate_filter.output.get())
3393 if (mk_ainput(computed_airspeed).get() <= conf->airspeed1)
3395 else if (mk_ainput(computed_airspeed).get() >= conf->airspeed2)
3398 upper_limit = 1650 + 8.9 * (mk_ainput(computed_airspeed).get() - conf->airspeed1);
3400 if (mk_data(radio_altitude).get() < upper_limit)
3402 if (mk_data(radio_altitude).get() < 522 + 0.1968 * closure_rate_filter.output.get())
3412 MK_VIII::Mode2Handler::is_b ()
3414 if (! mk->io_handler.gpws_inhibit()
3415 && ! mk->state_handler.ground // [1]
3416 && ! mk_data(radio_altitude).ncd
3417 && ! mk_data(barometric_altitude_rate).ncd
3418 && ! closure_rate_filter.output.ncd
3420 && mk_data(radio_altitude).get() < 789)
3424 if (mk->io_handler.flaps_down())
3426 if (mk_data(barometric_altitude_rate).get() > -400)
3428 else if (mk_data(barometric_altitude_rate).get() < -1000)
3431 lower_limit = -66.777 - 0.667 * mk_data(barometric_altitude_rate).get();
3436 if (mk_data(radio_altitude).get() > lower_limit)
3438 if (mk_data(radio_altitude).get() < -1579 + 0.7895 * closure_rate_filter.output.get())
3447 MK_VIII::Mode2Handler::check_pull_up (unsigned int preface_alert,
3450 if (pull_up_timer.running)
3452 if (pull_up_timer.elapsed() >= 1)
3454 mk_unset_alerts(preface_alert);
3455 mk_set_alerts(alert);
3460 if (! mk->voice_player.voice)
3461 pull_up_timer.start();
3466 MK_VIII::Mode2Handler::update_a ()
3470 if (mk_test_alert(MODE2A_PREFACE))
3471 check_pull_up(mk_alert(MODE2A_PREFACE), mk_alert(MODE2A));
3472 else if (! mk_test_alert(MODE2A))
3474 mk_unset_alerts(mk_alert(MODE2A_ALTITUDE_GAIN) | mk_alert(MODE2A_ALTITUDE_GAIN_TERRAIN_CLOSING));
3475 mk_set_alerts(mk_alert(MODE2A_PREFACE));
3476 a_start_time = globals->get_sim_time_sec();
3477 pull_up_timer.stop();
3482 if (mk_test_alert(MODE2A_ALTITUDE_GAIN))
3484 if (mk->io_handler.gpws_inhibit()
3485 || mk->state_handler.ground // [1]
3486 || a_altitude_gain_timer.elapsed() >= 45
3487 || mk_data(radio_altitude).ncd
3488 || mk_ainput(uncorrected_barometric_altitude).ncd
3489 || mk_ainput(uncorrected_barometric_altitude).get() - a_altitude_gain_alt >= 300
3490 // [PILOT] page 12: "the visual alert will remain on
3491 // until [...] landing flaps or the flap override switch
3493 || mk->io_handler.flaps_down())
3495 // exit altitude gain mode
3496 a_altitude_gain_timer.stop();
3497 mk_unset_alerts(mk_alert(MODE2A_ALTITUDE_GAIN) | mk_alert(MODE2A_ALTITUDE_GAIN_TERRAIN_CLOSING));
3501 // [SPEC] 6.2.2.2: "If the terrain starts to fall away
3502 // during this altitude gain time, the voice will shut
3504 if (closure_rate_filter.output.get() < 0)
3505 mk_unset_alerts(mk_alert(MODE2A_ALTITUDE_GAIN_TERRAIN_CLOSING));
3508 else if (mk_test_alerts(mk_alert(MODE2A_PREFACE) | mk_alert(MODE2A)))
3510 mk_unset_alerts(mk_alert(MODE2A_PREFACE) | mk_alert(MODE2A));
3512 if (! mk->io_handler.gpws_inhibit()
3513 && ! mk->state_handler.ground // [1]
3514 && globals->get_sim_time_sec() - a_start_time > 3
3515 && ! mk->io_handler.flaps_down()
3516 && ! mk_data(radio_altitude).ncd
3517 && ! mk_ainput(uncorrected_barometric_altitude).ncd)
3519 // [SPEC] 6.2.2.2: mode 2A envelope violated for more
3520 // than 3 seconds, enable altitude gain feature
3522 a_altitude_gain_timer.start();
3523 a_altitude_gain_alt = mk_ainput(uncorrected_barometric_altitude).get();
3525 unsigned int alerts = mk_alert(MODE2A_ALTITUDE_GAIN);
3526 if (closure_rate_filter.output.get() > 0)
3527 alerts |= mk_alert(MODE2A_ALTITUDE_GAIN_TERRAIN_CLOSING);
3529 mk_set_alerts(alerts);
3536 MK_VIII::Mode2Handler::update_b ()
3540 // handle normal mode
3542 if (b && (! mk_dinput(landing_gear) || ! mk->io_handler.flaps_down()))
3544 if (mk_test_alert(MODE2B_PREFACE))
3545 check_pull_up(mk_alert(MODE2B_PREFACE), mk_alert(MODE2B));
3546 else if (! mk_test_alert(MODE2B))
3548 mk_set_alerts(mk_alert(MODE2B_PREFACE));
3549 pull_up_timer.stop();
3553 mk_unset_alerts(mk_alert(MODE2B_PREFACE) | mk_alert(MODE2B));
3555 // handle landing mode ([SPEC] 6.2.2.3: "If both landing gear and
3556 // flaps are in the landing configuration, then the message will be
3559 if (b && mk_dinput(landing_gear) && mk->io_handler.flaps_down())
3560 mk_set_alerts(mk_alert(MODE2B_LANDING_MODE));
3562 mk_unset_alerts(mk_alert(MODE2B_LANDING_MODE));
3566 MK_VIII::Mode2Handler::boot ()
3568 closure_rate_filter.init();
3572 MK_VIII::Mode2Handler::power_off ()
3574 // [SPEC] 6.0.4: "This latching function is not power saved and a
3575 // system reset will force it false."
3576 takeoff_timer.stop();
3580 MK_VIII::Mode2Handler::leave_ground ()
3582 // takeoff, reset timer
3583 takeoff_timer.start();
3587 MK_VIII::Mode2Handler::enter_takeoff ()
3589 // reset timer, in case it's a go-around
3590 takeoff_timer.start();
3594 MK_VIII::Mode2Handler::update ()
3596 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
3599 closure_rate_filter.update();
3601 if (takeoff_timer.running && takeoff_timer.elapsed() >= 60)
3602 takeoff_timer.stop();
3608 ///////////////////////////////////////////////////////////////////////////////
3609 // Mode3Handler ///////////////////////////////////////////////////////////////
3610 ///////////////////////////////////////////////////////////////////////////////
3613 MK_VIII::Mode3Handler::max_alt_loss (double _bias)
3615 return conf->max_alt_loss(mk->io_handler.flap_override(), mk_data(radio_altitude).get()) + mk_data(radio_altitude).get() * _bias;
3619 MK_VIII::Mode3Handler::get_bias (double initial_bias, double alt_loss)
3621 if (mk_data(radio_altitude).get() > 0)
3622 while (alt_loss > max_alt_loss(initial_bias))
3623 initial_bias += 0.2;
3625 return initial_bias;
3629 MK_VIII::Mode3Handler::is (double *alt_loss)
3631 if (has_descent_alt)
3633 int max_agl = conf->max_agl(mk->io_handler.flap_override());
3635 if (mk_ainput(uncorrected_barometric_altitude).ncd
3636 || mk_ainput(uncorrected_barometric_altitude).get() > descent_alt
3637 || mk_data(radio_altitude).ncd
3638 || mk_data(radio_altitude).get() > max_agl)
3641 has_descent_alt = false;
3645 // gear/flaps: [SPEC] 1.3.1.3
3646 if (! mk->io_handler.gpws_inhibit()
3647 && ! mk->state_handler.ground // [1]
3648 && (! mk_dinput(landing_gear) || ! mk->io_handler.flaps_down())
3649 && ! mk_data(barometric_altitude_rate).ncd
3650 && ! mk_ainput(uncorrected_barometric_altitude).ncd
3651 && ! mk_data(radio_altitude).ncd
3652 && mk_data(barometric_altitude_rate).get() < 0)
3654 double _alt_loss = descent_alt - mk_ainput(uncorrected_barometric_altitude).get();
3655 if (armed || (mk_data(radio_altitude).get() > conf->min_agl
3656 && mk_data(radio_altitude).get() < max_agl
3657 && _alt_loss > max_alt_loss(0)))
3659 *alt_loss = _alt_loss;
3667 if (! mk_data(barometric_altitude_rate).ncd
3668 && ! mk_ainput(uncorrected_barometric_altitude).ncd
3669 && mk_data(barometric_altitude_rate).get() < 0)
3671 has_descent_alt = true;
3672 descent_alt = mk_ainput(uncorrected_barometric_altitude).get();
3680 MK_VIII::Mode3Handler::enter_takeoff ()
3683 has_descent_alt = false;
3687 MK_VIII::Mode3Handler::update ()
3689 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
3692 if (mk->state_handler.takeoff)
3696 if (! mk->state_handler.ground /* [1] */ && is(&alt_loss))
3698 if (mk_test_alert(MODE3))
3700 double new_bias = get_bias(bias, alt_loss);
3701 if (new_bias > bias)
3704 mk_repeat_alert(mk_alert(MODE3));
3710 bias = get_bias(0.2, alt_loss);
3711 mk_set_alerts(mk_alert(MODE3));
3718 mk_unset_alerts(mk_alert(MODE3));
3721 ///////////////////////////////////////////////////////////////////////////////
3722 // Mode4Handler ///////////////////////////////////////////////////////////////
3723 ///////////////////////////////////////////////////////////////////////////////
3725 // FIXME: for turbofans, the upper limit should be reduced from 1000
3726 // to 800 ft if a sudden change in radio altitude is detected, in
3727 // order to reduce nuisance alerts caused by overflying another
3728 // aircraft (see [PILOT] page 16).
3731 MK_VIII::Mode4Handler::get_upper_agl (const EnvelopesConfiguration *c)
3733 if (mk_ainput(computed_airspeed).get() >= c->airspeed2)
3735 else if (mk_ainput(computed_airspeed).get() >= c->airspeed1)
3736 return c->min_agl2(mk_ainput(computed_airspeed).get());
3741 const MK_VIII::Mode4Handler::EnvelopesConfiguration *
3742 MK_VIII::Mode4Handler::get_ab_envelope ()
3744 // [SPEC] 6.2.4.1: "The Mode 4B envelope can also be selected by
3745 // setting flaps to landing configuration or by selecting flap
3747 return mk_dinput(landing_gear) || mk->io_handler.flaps_down()
3753 MK_VIII::Mode4Handler::get_bias (double initial_bias, double min_agl)
3755 while (mk_data(radio_altitude).get() < min_agl - min_agl * initial_bias)
3756 initial_bias += 0.2;
3758 return initial_bias;
3762 MK_VIII::Mode4Handler::handle_alert (unsigned int alert,
3766 if (mk_test_alerts(alert))
3768 double new_bias = get_bias(*bias, min_agl);
3769 if (new_bias > *bias)
3772 mk_repeat_alert(alert);
3777 *bias = get_bias(0.2, min_agl);
3778 mk_set_alerts(alert);
3783 MK_VIII::Mode4Handler::update_ab ()
3785 if (! mk->io_handler.gpws_inhibit()
3786 && ! mk->state_handler.ground
3787 && ! mk->state_handler.takeoff
3788 && ! mk_data(radio_altitude).ncd
3789 && ! mk_ainput(computed_airspeed).ncd
3790 && mk_data(radio_altitude).get() > 30)
3792 const EnvelopesConfiguration *c = get_ab_envelope();
3793 if (mk_ainput(computed_airspeed).get() < c->airspeed1)
3795 if (mk_dinput(landing_gear))
3797 if (! mk->io_handler.flaps_down() && mk_data(radio_altitude).get() < c->min_agl1)
3799 handle_alert(mk_alert(MODE4_TOO_LOW_FLAPS), c->min_agl1, &ab_bias);
3805 if (mk_data(radio_altitude).get() < c->min_agl1)
3807 handle_alert(mk_alert(MODE4_TOO_LOW_GEAR), c->min_agl1, &ab_bias);
3814 mk_unset_alerts(mk_alert(MODE4_TOO_LOW_FLAPS) | mk_alert(MODE4_TOO_LOW_GEAR));
3818 MK_VIII::Mode4Handler::update_ab_expanded ()
3820 if (! mk->io_handler.gpws_inhibit()
3821 && ! mk->state_handler.ground
3822 && ! mk->state_handler.takeoff
3823 && ! mk_data(radio_altitude).ncd
3824 && ! mk_ainput(computed_airspeed).ncd
3825 && mk_data(radio_altitude).get() > 30)
3827 const EnvelopesConfiguration *c = get_ab_envelope();
3828 if (mk_ainput(computed_airspeed).get() >= c->airspeed1)
3830 double min_agl = get_upper_agl(c);
3831 if (mk_data(radio_altitude).get() < min_agl)
3833 handle_alert(mk_alert(MODE4AB_TOO_LOW_TERRAIN), min_agl, &ab_expanded_bias);
3839 mk_unset_alerts(mk_alert(MODE4AB_TOO_LOW_TERRAIN));
3843 MK_VIII::Mode4Handler::update_c ()
3845 if (! mk->io_handler.gpws_inhibit()
3846 && ! mk->state_handler.ground // [1]
3847 && mk->state_handler.takeoff
3848 && ! mk_data(radio_altitude).ncd
3849 && ! mk_data(terrain_clearance).ncd
3850 && mk_data(radio_altitude).get() > 30
3851 && (! mk_dinput(landing_gear) || ! mk->io_handler.flaps_down())
3852 && mk_data(radio_altitude).get() < get_upper_agl(conf.modes->ac)
3853 && mk_data(radio_altitude).get() < mk_data(terrain_clearance).get())
3854 handle_alert(mk_alert(MODE4C_TOO_LOW_TERRAIN), mk_data(terrain_clearance).get(), &c_bias);
3856 mk_unset_alerts(mk_alert(MODE4C_TOO_LOW_TERRAIN));
3860 MK_VIII::Mode4Handler::update ()
3862 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
3866 update_ab_expanded();
3870 ///////////////////////////////////////////////////////////////////////////////
3871 // Mode5Handler ///////////////////////////////////////////////////////////////
3872 ///////////////////////////////////////////////////////////////////////////////
3875 MK_VIII::Mode5Handler::is_hard ()
3877 if (mk_data(radio_altitude).get() > 30
3878 && mk_data(radio_altitude).get() < 300
3879 && mk_data(glideslope_deviation_dots).get() > 2)
3881 if (mk_data(radio_altitude).get() < 150)
3883 if (mk_data(radio_altitude).get() > 293 - 71.43 * mk_data(glideslope_deviation_dots).get())
3894 MK_VIII::Mode5Handler::is_soft (double bias)
3896 if (mk_data(radio_altitude).get() > 30)
3898 double bias_dots = 1.3 * bias;
3899 if (mk_data(glideslope_deviation_dots).get() > 1.3 + bias_dots)
3901 if (mk_data(radio_altitude).get() < 150)
3903 if (mk_data(radio_altitude).get() > 243 - 71.43 * (mk_data(glideslope_deviation_dots).get() - bias_dots))
3910 if (mk_data(barometric_altitude_rate).ncd)
3914 if (mk_data(barometric_altitude_rate).get() >= 0)
3916 else if (mk_data(barometric_altitude_rate).get() < -500)
3919 upper_limit = -mk_data(barometric_altitude_rate).get() + 500;
3922 if (mk_data(radio_altitude).get() < upper_limit)
3932 MK_VIII::Mode5Handler::get_soft_bias (double initial_bias)
3934 while (is_soft(initial_bias))
3935 initial_bias += 0.2;
3937 return initial_bias;
3941 MK_VIII::Mode5Handler::update_hard (bool is)
3945 if (! mk_test_alert(MODE5_HARD))
3947 if (hard_timer.start_or_elapsed() >= 0.8)
3948 mk_set_alerts(mk_alert(MODE5_HARD));
3954 mk_unset_alerts(mk_alert(MODE5_HARD));
3959 MK_VIII::Mode5Handler::update_soft (bool is)
3963 if (! mk_test_alert(MODE5_SOFT))
3965 double new_bias = get_soft_bias(soft_bias);
3966 if (new_bias > soft_bias)
3968 soft_bias = new_bias;
3969 mk_repeat_alert(mk_alert(MODE5_SOFT));
3974 if (soft_timer.start_or_elapsed() >= 0.8)
3976 soft_bias = get_soft_bias(0.2);
3977 mk_set_alerts(mk_alert(MODE5_SOFT));
3984 mk_unset_alerts(mk_alert(MODE5_SOFT));
3989 MK_VIII::Mode5Handler::update ()
3991 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
3994 if (! mk->io_handler.gpws_inhibit()
3995 && ! mk->state_handler.ground // [1]
3996 && ! mk_dinput(glideslope_inhibit) // not on backcourse
3997 && ! mk_data(radio_altitude).ncd
3998 && ! mk_data(glideslope_deviation_dots).ncd
3999 && (! mk->io_handler.conf.localizer_enabled
4000 || mk_data(localizer_deviation_dots).ncd
4001 || mk_data(radio_altitude).get() < 500
4002 || fabs(mk_data(localizer_deviation_dots).get()) < 2)
4003 && (! mk->state_handler.takeoff || mk->io_handler.flaps_down())
4004 && mk_dinput(landing_gear)
4005 && ! mk_doutput(glideslope_cancel))
4007 update_hard(is_hard());
4008 update_soft(is_soft(0));
4017 ///////////////////////////////////////////////////////////////////////////////
4018 // Mode6Handler ///////////////////////////////////////////////////////////////
4019 ///////////////////////////////////////////////////////////////////////////////
4021 // keep sorted in descending order
4022 const int MK_VIII::Mode6Handler::altitude_callout_definitions[] =
4023 { 1000, 500, 400, 300, 200, 100, 50, 40, 30, 20, 10 };
4026 MK_VIII::Mode6Handler::reset_minimums ()
4028 minimums_issued = false;
4032 MK_VIII::Mode6Handler::reset_altitude_callouts ()
4034 for (int i = 0; i < n_altitude_callouts; i++)
4035 altitude_callouts_issued[i] = false;
4039 MK_VIII::Mode6Handler::is_playing_altitude_callout ()
4041 for (int i = 0; i < n_altitude_callouts; i++)
4042 if (mk->voice_player.voice == mk_altitude_voice(i)
4043 || mk->voice_player.next_voice == mk_altitude_voice(i))
4050 MK_VIII::Mode6Handler::is_near_minimums (double callout)
4054 if (! mk_data(decision_height).ncd)
4056 double diff = callout - mk_data(decision_height).get();
4058 if (mk_data(radio_altitude).get() >= 200)
4060 if (fabs(diff) <= 30)
4065 if (diff >= -3 && diff <= 6)
4074 MK_VIII::Mode6Handler::is_outside_band (double elevation, double callout)
4077 return elevation < callout - (elevation > 150 ? 20 : 10);
4081 MK_VIII::Mode6Handler::inhibit_smart_500 ()
4085 if (! mk_data(glideslope_deviation_dots).ncd
4086 && ! mk_dinput(glideslope_inhibit) // backcourse
4087 && ! mk_doutput(glideslope_cancel))
4089 if (mk->io_handler.conf.localizer_enabled
4090 && ! mk_data(localizer_deviation_dots).ncd)
4092 if (fabs(mk_data(localizer_deviation_dots).get()) <= 2)
4097 if (fabs(mk_data(glideslope_deviation_dots).get()) <= 2)
4106 MK_VIII::Mode6Handler::boot ()
4108 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
4111 last_decision_height = mk_dinput(decision_height);
4112 last_radio_altitude.set(&mk_data(radio_altitude));
4115 for (int i = 0; i < n_altitude_callouts; i++)
4116 altitude_callouts_issued[i] = ! mk_data(radio_altitude).ncd
4117 && mk_data(radio_altitude).get() <= altitude_callout_definitions[i];
4119 // extrapolated from [SPEC] 6.4.2
4120 minimums_issued = mk_dinput(decision_height);
4122 if (conf.above_field_voice)
4125 get_altitude_above_field(&last_altitude_above_field);
4126 // extrapolated from [SPEC] 6.4.2
4127 above_field_issued = ! last_altitude_above_field.ncd
4128 && last_altitude_above_field.get() < 550;
4133 MK_VIII::Mode6Handler::power_off ()
4135 runway_timer.stop();
4139 MK_VIII::Mode6Handler::enter_takeoff ()
4141 reset_altitude_callouts(); // [SPEC] 6.4.2
4142 reset_minimums(); // omitted by [SPEC]; common sense
4146 MK_VIII::Mode6Handler::leave_takeoff ()
4148 reset_altitude_callouts(); // [SPEC] 6.0.2
4149 reset_minimums(); // [SPEC] 6.0.2
4153 MK_VIII::Mode6Handler::set_volume (double volume)
4155 mk_voice(minimums_minimums)->set_volume(volume);
4156 mk_voice(five_hundred_above)->set_volume(volume);
4157 for (int i = 0; i < n_altitude_callouts; i++)
4158 mk_altitude_voice(i)->set_volume(volume);
4162 MK_VIII::Mode6Handler::altitude_callouts_enabled ()
4164 if (conf.minimums_enabled || conf.smart_500_enabled || conf.above_field_voice)
4167 for (int i = 0; i < n_altitude_callouts; i++)
4168 if (conf.altitude_callouts_enabled[i])
4175 MK_VIII::Mode6Handler::update_minimums ()
4177 if (! mk->io_handler.gpws_inhibit()
4178 && (mk->voice_player.voice == mk_voice(minimums_minimums)
4179 || mk->voice_player.next_voice == mk_voice(minimums_minimums)))
4182 if (! mk->io_handler.gpws_inhibit()
4183 && ! mk->state_handler.ground // [1]
4184 && conf.minimums_enabled
4185 && ! minimums_issued
4186 && mk_dinput(landing_gear)
4187 && mk_dinput(decision_height)
4188 && ! last_decision_height)
4190 minimums_issued = true;
4192 // If an altitude callout is playing, stop it now so that the
4193 // minimums callout can be played (note that proper connection
4194 // and synchronization of the radio-altitude ARINC 529 input,
4195 // decision-height discrete and decision-height ARINC 529 input
4196 // will prevent an altitude callout from being played near the
4197 // decision height).
4199 if (is_playing_altitude_callout())
4200 mk->voice_player.stop(VoicePlayer::STOP_NOW);
4202 mk_set_alerts(mk_alert(MODE6_MINIMUMS));
4205 mk_unset_alerts(mk_alert(MODE6_MINIMUMS));
4208 last_decision_height = mk_dinput(decision_height);
4212 MK_VIII::Mode6Handler::update_altitude_callouts ()
4214 if (! mk->io_handler.gpws_inhibit() && is_playing_altitude_callout())
4217 if (! mk->io_handler.gpws_inhibit()
4218 && ! mk->state_handler.ground // [1]
4219 && ! mk_data(radio_altitude).ncd)
4220 for (int i = 0; i < n_altitude_callouts && mk_data(radio_altitude).get() <= altitude_callout_definitions[i]; i++)
4221 if ((conf.altitude_callouts_enabled[i]
4222 || (altitude_callout_definitions[i] == 500
4223 && conf.smart_500_enabled))
4224 && ! altitude_callouts_issued[i]
4225 && (last_radio_altitude.ncd
4226 || last_radio_altitude.get() > altitude_callout_definitions[i]))
4228 // lock out all callouts superior or equal to this one
4229 for (int j = 0; j <= i; j++)
4230 altitude_callouts_issued[j] = true;
4232 altitude_callouts_issued[i] = true;
4233 if (! is_near_minimums(altitude_callout_definitions[i])
4234 && ! is_outside_band(mk_data(radio_altitude).get(), altitude_callout_definitions[i])
4235 && (! conf.smart_500_enabled
4236 || altitude_callout_definitions[i] != 500
4237 || ! inhibit_smart_500()))
4239 mk->alert_handler.set_altitude_callout_alert(mk_altitude_voice(i));
4244 mk_unset_alerts(mk_alert(MODE6_ALTITUDE_CALLOUT));
4247 last_radio_altitude.set(&mk_data(radio_altitude));
4251 MK_VIII::Mode6Handler::test_runway (const FGRunway *_runway)
4253 if (_runway->_length < mk->conf.runway_database)
4256 // get position of threshold
4257 double latitude, longitude, az;
4258 geo_direct_wgs_84(0,
4261 get_reciprocal_heading(_runway->_heading),
4262 _runway->_length / 2 * SG_FEET_TO_METER,
4267 // get distance to threshold
4268 double distance, az1, az2;
4269 geo_inverse_wgs_84(0,
4270 mk_data(gps_latitude).get(),
4271 mk_data(gps_longitude).get(),
4274 &az1, &az2, &distance);
4276 return distance * SG_METER_TO_NM <= 5;
4280 MK_VIII::Mode6Handler::test_airport (const FGAirport *airport)
4283 if (globals->get_runways()->search(airport->getId(), &r))
4286 if (test_runway(&r))
4289 // reciprocal runway
4290 r._heading = get_reciprocal_heading(r._heading);
4291 if (test_runway(&r))
4294 while (globals->get_runways()->next(&r) && r._id == airport->getId());
4300 MK_VIII::Mode6Handler::update_runway ()
4302 if (! mk_data(gps_latitude).ncd && ! mk_data(gps_longitude).ncd)
4304 // Search for the closest runway threshold in range 5
4305 // nm. Passing 0.5 degrees (approximatively 30 nm) to
4306 // get_closest_airport() provides enough margin for large
4307 // airports, which may have a runway located far away from the
4308 // airport's reference point.
4310 const FGAirport *airport = get_closest_airport(mk_data(gps_latitude).get(),
4311 mk_data(gps_longitude).get(),
4314 &MK_VIII::Mode6Handler::test_airport);
4317 runway.elevation = airport->getElevation();
4319 has_runway.set(airport != NULL);
4326 MK_VIII::Mode6Handler::get_altitude_above_field (Parameter<double> *parameter)
4328 if (! has_runway.ncd && has_runway.get() && ! mk_data(geometric_altitude).ncd)
4329 parameter->set(mk_data(geometric_altitude).get() - runway.elevation);
4335 MK_VIII::Mode6Handler::update_above_field_callout ()
4337 if (! conf.above_field_voice)
4340 // update_runway() has to iterate over the whole runway database
4341 // (which contains thousands of runways), so it would be unwise to
4342 // run it every frame. Run it every 3 seconds. Note that the first
4343 // iteration is run in boot().
4344 if (runway_timer.start_or_elapsed() >= 3)
4347 runway_timer.start();
4350 Parameter<double> altitude_above_field;
4351 get_altitude_above_field(&altitude_above_field);
4353 if (! mk->io_handler.gpws_inhibit()
4354 && (mk->voice_player.voice == conf.above_field_voice
4355 || mk->voice_player.next_voice == conf.above_field_voice))
4358 // handle reset term
4359 if (above_field_issued)
4361 if ((! has_runway.ncd && ! has_runway.get())
4362 || (! altitude_above_field.ncd && altitude_above_field.get() > 700))
4363 above_field_issued = false;
4366 if (! mk->io_handler.gpws_inhibit()
4367 && ! mk->state_handler.ground // [1]
4368 && ! above_field_issued
4369 && ! altitude_above_field.ncd
4370 && altitude_above_field.get() < 550
4371 && (last_altitude_above_field.ncd
4372 || last_altitude_above_field.get() >= 550))
4374 above_field_issued = true;
4376 if (! is_outside_band(altitude_above_field.get(), 500))
4378 mk->alert_handler.set_altitude_callout_alert(conf.above_field_voice);
4383 mk_unset_alerts(mk_alert(MODE6_ALTITUDE_CALLOUT));
4386 last_altitude_above_field.set(&altitude_above_field);
4390 MK_VIII::Mode6Handler::is_bank_angle (double abs_roll_angle, double bias)
4392 return conf.is_bank_angle(&mk_data(radio_altitude),
4393 abs_roll_angle - abs_roll_angle * bias,
4394 mk_dinput(autopilot_engaged));
4398 MK_VIII::Mode6Handler::is_high_bank_angle ()
4400 return mk_data(radio_altitude).ncd || mk_data(radio_altitude).get() >= 210;
4404 MK_VIII::Mode6Handler::get_bank_angle_alerts ()
4406 if (! mk->io_handler.gpws_inhibit()
4407 && ! mk->state_handler.ground // [1]
4408 && ! mk_data(roll_angle).ncd)
4410 double abs_roll_angle = fabs(mk_data(roll_angle).get());
4412 if (is_bank_angle(abs_roll_angle, 0.4))
4413 return is_high_bank_angle()
4414 ? (mk_alert(MODE6_HIGH_BANK_ANGLE_1) | mk_alert(MODE6_HIGH_BANK_ANGLE_2) | mk_alert(MODE6_HIGH_BANK_ANGLE_3))
4415 : (mk_alert(MODE6_LOW_BANK_ANGLE_1) | mk_alert(MODE6_LOW_BANK_ANGLE_2) | mk_alert(MODE6_LOW_BANK_ANGLE_3));
4416 else if (is_bank_angle(abs_roll_angle, 0.2))
4417 return is_high_bank_angle()
4418 ? (mk_alert(MODE6_HIGH_BANK_ANGLE_1) | mk_alert(MODE6_HIGH_BANK_ANGLE_2))
4419 : (mk_alert(MODE6_LOW_BANK_ANGLE_1) | mk_alert(MODE6_LOW_BANK_ANGLE_2));
4420 else if (is_bank_angle(abs_roll_angle, 0))
4421 return is_high_bank_angle()
4422 ? mk_alert(MODE6_HIGH_BANK_ANGLE_1)
4423 : mk_alert(MODE6_LOW_BANK_ANGLE_1);
4430 MK_VIII::Mode6Handler::update_bank_angle ()
4432 if (! conf.bank_angle_enabled)
4435 unsigned int alerts = get_bank_angle_alerts();
4437 // [SPEC] 6.4.4 specifies that the non-continuous alerts
4438 // (MODE6_*_BANK_ANGLE_1 and MODE6_*_BANK_ANGLE_2) are locked out
4439 // until the initial envelope is exited.
4441 if (! test_bits(alerts, mk_alert(MODE6_LOW_BANK_ANGLE_3)))
4442 mk_unset_alerts(mk_alert(MODE6_LOW_BANK_ANGLE_3));
4443 if (! test_bits(alerts, mk_alert(MODE6_HIGH_BANK_ANGLE_3)))
4444 mk_unset_alerts(mk_alert(MODE6_HIGH_BANK_ANGLE_3));
4447 mk_set_alerts(alerts);
4449 mk_unset_alerts(mk_alert(MODE6_LOW_BANK_ANGLE_1)
4450 | mk_alert(MODE6_HIGH_BANK_ANGLE_1)
4451 | mk_alert(MODE6_LOW_BANK_ANGLE_2)
4452 | mk_alert(MODE6_HIGH_BANK_ANGLE_2));
4456 MK_VIII::Mode6Handler::update ()
4458 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
4461 if (! mk->state_handler.takeoff
4462 && ! mk_data(radio_altitude).ncd
4463 && mk_data(radio_altitude).get() > 1000)
4465 reset_altitude_callouts(); // [SPEC] 6.4.2
4466 reset_minimums(); // common sense
4470 update_altitude_callouts();
4471 update_above_field_callout();
4472 update_bank_angle();
4475 ///////////////////////////////////////////////////////////////////////////////
4476 // TCFHandler /////////////////////////////////////////////////////////////////
4477 ///////////////////////////////////////////////////////////////////////////////
4479 // Gets the difference between the azimuth from @from_lat,@from_lon to
4480 // @to_lat,@to_lon, and @to_heading, in degrees.
4482 MK_VIII::TCFHandler::get_azimuth_difference (double from_lat,
4488 double az1, az2, distance;
4489 geo_inverse_wgs_84(0, from_lat, from_lon, to_lat, to_lon, &az1, &az2, &distance);
4490 return get_heading_difference(az1, to_heading);
4493 // Gets the difference between the azimuth from the current GPS
4494 // position to the center of @_runway, and the heading of @_runway, in
4497 MK_VIII::TCFHandler::get_azimuth_difference (const FGRunway *_runway)
4499 return get_azimuth_difference(mk_data(gps_latitude).get(),
4500 mk_data(gps_longitude).get(),
4506 // Selects the most likely intended destination runway of @airport,
4507 // and returns it in @_runway. For each runway, the difference between
4508 // the azimuth from the current GPS position to the center of the
4509 // runway and its heading is computed. The runway having the smallest
4512 // This selection algorithm is not specified in [SPEC], but
4513 // http://www.egpws.com/general_information/description/runway_select.htm
4514 // talks about automatic runway selection.
4516 MK_VIII::TCFHandler::select_runway (const FGAirport *airport,
4520 bool status = globals->get_runways()->search(airport->getId(), &r);
4523 double min_diff = 360;
4528 diff = get_azimuth_difference(&r);
4529 if (diff < min_diff)
4535 // reciprocal runway
4536 r._heading = get_reciprocal_heading(r._heading);
4537 diff = get_azimuth_difference(&r);
4538 if (diff < min_diff)
4544 while (globals->get_runways()->next(&r) && r._id == airport->getId());
4548 MK_VIII::TCFHandler::test_airport (const FGAirport *airport)
4551 if (globals->get_runways()->search(airport->getId(), &r))
4554 if (r._length >= mk->conf.runway_database)
4557 while (globals->get_runways()->next(&r) && r._id == airport->getId());
4563 MK_VIII::TCFHandler::update_runway ()
4566 if (! mk_data(gps_latitude).ncd && ! mk_data(gps_longitude).ncd)
4568 // Search for the intended destination runway of the closest
4569 // airport in range 15 nm. Passing 0.5 degrees (approximatively
4570 // 30 nm) to get_closest_airport() provides enough margin for
4571 // large airports, which may have a runway located far away from
4572 // the airport's reference point.
4574 const FGAirport *airport = get_closest_airport(mk_data(gps_latitude).get(),
4575 mk_data(gps_longitude).get(),
4578 &MK_VIII::TCFHandler::test_airport);
4585 select_runway(airport, &_runway);
4587 runway.center.latitude = _runway._lat;
4588 runway.center.longitude = _runway._lon;
4590 runway.elevation = airport->getElevation();
4592 double half_length_m = _runway._length / 2 * SG_FEET_TO_METER;
4593 runway.half_length = half_length_m * SG_METER_TO_NM;
4595 // b3 ________________ b0
4597 // h1>>> | e1<<<<<<<<e0 | <<<h0
4598 // |________________|
4601 // get heading to runway threshold (h0) and end (h1)
4602 runway.edges[0].heading = _runway._heading;
4603 runway.edges[1].heading = get_reciprocal_heading(_runway._heading);
4607 // get position of runway threshold (e0)
4608 geo_direct_wgs_84(0,
4609 runway.center.latitude,
4610 runway.center.longitude,
4611 runway.edges[1].heading,
4613 &runway.edges[0].position.latitude,
4614 &runway.edges[0].position.longitude,
4617 // get position of runway end (e1)
4618 geo_direct_wgs_84(0,
4619 runway.center.latitude,
4620 runway.center.longitude,
4621 runway.edges[0].heading,
4623 &runway.edges[1].position.latitude,
4624 &runway.edges[1].position.longitude,
4627 double half_width_m = _runway._width / 2 * SG_FEET_TO_METER;
4629 // get position of threshold bias area edges (b0 and b1)
4630 get_bias_area_edges(&runway.edges[0].position,
4631 runway.edges[1].heading,
4633 &runway.bias_area[0],
4634 &runway.bias_area[1]);
4636 // get position of end bias area edges (b2 and b3)
4637 get_bias_area_edges(&runway.edges[1].position,
4638 runway.edges[0].heading,
4640 &runway.bias_area[2],
4641 &runway.bias_area[3]);
4647 MK_VIII::TCFHandler::get_bias_area_edges (Position *edge,
4649 double half_width_m,
4650 Position *bias_edge1,
4651 Position *bias_edge2)
4653 double half_bias_width_m = k * SG_NM_TO_METER + half_width_m;
4654 double tmp_latitude, tmp_longitude, az;
4656 geo_direct_wgs_84(0,
4664 geo_direct_wgs_84(0,
4667 heading_substract(reciprocal, 90),
4669 &bias_edge1->latitude,
4670 &bias_edge1->longitude,
4672 geo_direct_wgs_84(0,
4675 heading_add(reciprocal, 90),
4677 &bias_edge2->latitude,
4678 &bias_edge2->longitude,
4682 // Returns true if the current GPS position is inside the edge
4683 // triangle of @edge. The edge triangle, which is specified and
4684 // represented in [SPEC] 6.3.1, is defined as an isocele right
4685 // triangle of infinite height, whose right angle is located at the
4686 // position of @edge, and whose height is parallel to the heading of
4689 MK_VIII::TCFHandler::is_inside_edge_triangle (RunwayEdge *edge)
4691 return get_azimuth_difference(mk_data(gps_latitude).get(),
4692 mk_data(gps_longitude).get(),
4693 edge->position.latitude,
4694 edge->position.longitude,
4695 edge->heading) <= 45;
4698 // Returns true if the current GPS position is inside the bias area of
4699 // the currently selected runway.
4701 MK_VIII::TCFHandler::is_inside_bias_area ()
4704 double angles_sum = 0;
4706 for (int i = 0; i < 4; i++)
4708 double az2, distance;
4709 geo_inverse_wgs_84(0,
4710 mk_data(gps_latitude).get(),
4711 mk_data(gps_longitude).get(),
4712 runway.bias_area[i].latitude,
4713 runway.bias_area[i].longitude,
4714 &az1[i], &az2, &distance);
4717 for (int i = 0; i < 4; i++)
4719 double angle = az1[i == 3 ? 0 : i + 1] - az1[i];
4723 angles_sum += angle;
4726 return angles_sum > 180;
4730 MK_VIII::TCFHandler::is_tcf ()
4732 if (mk_data(radio_altitude).get() > 10)
4736 double distance, az1, az2;
4738 geo_inverse_wgs_84(0,
4739 mk_data(gps_latitude).get(),
4740 mk_data(gps_longitude).get(),
4741 runway.center.latitude,
4742 runway.center.longitude,
4743 &az1, &az2, &distance);
4745 distance *= SG_METER_TO_NM;
4747 // distance to the inner envelope edge
4748 double edge_distance = distance - runway.half_length - k;
4750 if (edge_distance >= 15)
4752 if (mk_data(radio_altitude).get() < 700)
4755 else if (edge_distance >= 12)
4757 if (mk_data(radio_altitude).get() < 100 * edge_distance - 800)
4760 else if (edge_distance >= 4)
4762 if (mk_data(radio_altitude).get() < 400)
4765 else if (edge_distance >= 2.45)
4767 if (mk_data(radio_altitude).get() < 66.666667 * edge_distance + 133.33333)
4772 if (is_inside_edge_triangle(&runway.edges[0]) || is_inside_edge_triangle(&runway.edges[1]))
4774 if (edge_distance >= 1)
4776 if (mk_data(radio_altitude).get() < 66.666667 * edge_distance + 133.33333)
4779 else if (edge_distance >= 0.05)
4781 if (mk_data(radio_altitude).get() < 200 * edge_distance)
4787 if (! is_inside_bias_area())
4789 if (mk_data(radio_altitude).get() < 245)
4797 if (mk_data(radio_altitude).get() < 700)
4806 MK_VIII::TCFHandler::is_rfcf ()
4810 double distance, az1, az2;
4811 geo_inverse_wgs_84(0,
4812 mk_data(gps_latitude).get(),
4813 mk_data(gps_longitude).get(),
4814 runway.center.latitude,
4815 runway.center.longitude,
4816 &az1, &az2, &distance);
4818 double krf = k + mk_data(gps_vertical_figure_of_merit).get() / 200;
4819 distance = distance * SG_METER_TO_NM - runway.half_length - krf;
4823 double altitude_above_field = mk_data(geometric_altitude).get() - runway.elevation;
4825 if (distance >= 1.5)
4827 if (altitude_above_field < 300)
4830 else if (distance >= 0)
4832 if (altitude_above_field < 200 * distance)
4842 MK_VIII::TCFHandler::update ()
4844 if (mk->configuration_module.state != ConfigurationModule::STATE_OK || ! conf.enabled)
4847 // update_runway() has to iterate over the whole runway database
4848 // (which contains thousands of runways), so it would be unwise to
4849 // run it every frame. Run it every 3 seconds.
4850 if (! runway_timer.running || runway_timer.elapsed() >= 3)
4853 runway_timer.start();
4856 if (! mk_dinput(ta_tcf_inhibit)
4857 && ! mk->state_handler.ground // [1]
4858 && ! mk_data(gps_latitude).ncd
4859 && ! mk_data(gps_longitude).ncd
4860 && ! mk_data(radio_altitude).ncd
4861 && ! mk_data(geometric_altitude).ncd
4862 && ! mk_data(gps_vertical_figure_of_merit).ncd)
4867 _reference = mk_data(radio_altitude).get_pointer();
4869 _reference = mk_data(geometric_altitude).get_pointer();
4875 if (mk_test_alert(TCF_TOO_LOW_TERRAIN))
4877 double new_bias = bias;
4878 while (*reference < initial_value - initial_value * new_bias)
4881 if (new_bias > bias)
4884 mk_repeat_alert(mk_alert(TCF_TOO_LOW_TERRAIN));
4890 reference = _reference;
4891 initial_value = *reference;
4892 mk_set_alerts(mk_alert(TCF_TOO_LOW_TERRAIN));
4899 mk_unset_alerts(mk_alert(TCF_TOO_LOW_TERRAIN));
4902 ///////////////////////////////////////////////////////////////////////////////
4903 // MK_VIII ////////////////////////////////////////////////////////////////////
4904 ///////////////////////////////////////////////////////////////////////////////
4906 MK_VIII::MK_VIII (SGPropertyNode *node)
4910 properties_handler(this),
4911 power_handler(this),
4912 system_handler(this),
4913 configuration_module(this),
4914 fault_handler(this),
4917 self_test_handler(this),
4918 alert_handler(this),
4919 state_handler(this),
4920 mode1_handler(this),
4921 mode2_handler(this),
4922 mode3_handler(this),
4923 mode4_handler(this),
4924 mode5_handler(this),
4925 mode6_handler(this),
4928 for (int i = 0; i < node->nChildren(); ++i)
4930 SGPropertyNode *child = node->getChild(i);
4931 string cname = child->getName();
4932 string cval = child->getStringValue();
4934 if (cname == "name")
4936 else if (cname == "number")
4937 num = child->getIntValue();
4940 SG_LOG(SG_INSTR, SG_WARN, "Error in mk-viii config logic");
4942 SG_LOG(SG_INSTR, SG_WARN, "Section = " << name);
4950 properties_handler.init();
4951 voice_player.init();
4957 SGPropertyNode *node = fgGetNode(("/instrumentation/" + name).c_str(), num, true);
4959 configuration_module.bind(node);
4960 power_handler.bind(node);
4961 io_handler.bind(node);
4962 voice_player.bind(node);
4968 properties_handler.unbind();
4972 MK_VIII::update (double dt)
4974 power_handler.update();
4975 system_handler.update();
4977 if (system_handler.state != SystemHandler::STATE_ON)
4980 io_handler.update_inputs();
4981 io_handler.update_input_faults();
4983 voice_player.update();
4984 state_handler.update();
4986 if (self_test_handler.update())
4989 io_handler.update_internal_latches();
4990 io_handler.update_lamps();
4992 mode1_handler.update();
4993 mode2_handler.update();
4994 mode3_handler.update();
4995 mode4_handler.update();
4996 mode5_handler.update();
4997 mode6_handler.update();
4998 tcf_handler.update();
5000 alert_handler.update();
5001 io_handler.update_outputs();