1 // mk_viii.cxx -- Honeywell MK VIII EGPWS emulation
3 // Written by Jean-Yves Lefort, started September 2005.
5 // Copyright (C) 2005, 2006 Jean-Yves Lefort - jylefort@FreeBSD.org
7 // This program is free software; you can redistribute it and/or
8 // modify it under the terms of the GNU General Public License as
9 // published by the Free Software Foundation; either version 2 of the
10 // License, or (at your option) any later version.
12 // This program is distributed in the hope that it will be useful, but
13 // WITHOUT ANY WARRANTY; without even the implied warranty of
14 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 // General Public License for more details.
17 // You should have received a copy of the GNU General Public License
18 // along with this program; if not, write to the Free Software
19 // Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
21 ///////////////////////////////////////////////////////////////////////////////
25 // [PILOT] Honeywell International Inc., "MK VI and MK VIII,
26 // Enhanced Ground Proximity Warning System (EGPWS),
27 // Pilot's Guide", May 2004
29 // http://www.egpws.com/engineering_support/documents/pilot_guides/060-4314-000.pdf
31 // [SPEC] Honeywell International Inc., "Product Specification
32 // for the MK VI and MK VIII Enhanced Ground Proximity
33 // Warning System (EGPWS)", June 2004
35 // http://www.egpws.com/engineering_support/documents/specs/965-1180-601.pdf
37 // [INSTALL] Honeywell Inc., "MK VI, MK VIII, Enhanced Ground
38 // Proximity Warning System (Class A TAWS), Installation
39 // Design Guide", July 2003
41 // http://www.egpws.com/engineering_support/documents/install/060-4314-150.pdf
45 // [1] [SPEC] does not specify the "must be airborne"
46 // condition; we use it to make sure that the alert
47 // will not trigger when on the ground, since it would
51 # pragma warning( disable: 4355 )
66 #include <simgear/constants.h>
67 #include <simgear/sg_inlines.h>
68 #include <simgear/debug/logstream.hxx>
69 #include <simgear/math/sg_geodesy.hxx>
70 #include <simgear/math/sg_random.h>
71 #include <simgear/misc/sg_path.hxx>
72 #include <simgear/sound/soundmgr_openal.hxx>
73 #include <simgear/structure/exception.hxx>
77 #include "Airports/runways.hxx"
78 #include "Airports/simple.hxx"
80 # include "Include/version.h"
82 #include "Main/fg_props.hxx"
83 #include "Main/globals.hxx"
84 #include "instrument_mgr.hxx"
85 #include "mk_viii.hxx"
87 ///////////////////////////////////////////////////////////////////////////////
88 // constants //////////////////////////////////////////////////////////////////
89 ///////////////////////////////////////////////////////////////////////////////
91 #define GLIDESLOPE_DOTS_TO_DDM 0.0875 // [INSTALL] 6.2.30
92 #define GLIDESLOPE_DDM_TO_DOTS (1 / GLIDESLOPE_DOTS_TO_DDM)
94 #define LOCALIZER_DOTS_TO_DDM 0.0775 // [INSTALL] 6.2.33
95 #define LOCALIZER_DDM_TO_DOTS (1 / LOCALIZER_DOTS_TO_DDM)
97 ///////////////////////////////////////////////////////////////////////////////
98 // helpers ////////////////////////////////////////////////////////////////////
99 ///////////////////////////////////////////////////////////////////////////////
101 #define assert_not_reached() assert(false)
102 #define n_elements(_array) (sizeof(_array) / sizeof((_array)[0]))
103 #define test_bits(_bits, _test) (((_bits) & (_test)) != 0)
105 #define mk_node(_name) (mk->properties_handler.external_properties._name)
107 #define mk_dinput_feed(_name) (mk->io_handler.input_feeders.discretes._name)
108 #define mk_dinput(_name) (mk->io_handler.inputs.discretes._name)
109 #define mk_ainput_feed(_name) (mk->io_handler.input_feeders.arinc429._name)
110 #define mk_ainput(_name) (mk->io_handler.inputs.arinc429._name)
111 #define mk_doutput(_name) (mk->io_handler.outputs.discretes._name)
112 #define mk_aoutput(_name) (mk->io_handler.outputs.arinc429._name)
113 #define mk_data(_name) (mk->io_handler.data._name)
115 #define mk_voice(_name) (mk->voice_player.voices._name)
116 #define mk_altitude_voice(_i) (mk->voice_player.voices.altitude_callouts[(_i)])
118 #define mk_alert(_name) (AlertHandler::ALERT_ ## _name)
119 #define mk_alert_flag(_name) (AlertHandler::ALERT_FLAG_ ## _name)
120 #define mk_set_alerts (mk->alert_handler.set_alerts)
121 #define mk_unset_alerts (mk->alert_handler.unset_alerts)
122 #define mk_repeat_alert (mk->alert_handler.repeat_alert)
123 #define mk_test_alert(_name) (mk_test_alerts(mk_alert(_name)))
124 #define mk_test_alerts(_test) (test_bits(mk->alert_handler.alerts, (_test)))
126 const double MK_VIII::TCFHandler::k = 0.25;
129 modify_amplitude (double amplitude, double dB)
131 return amplitude * pow(10.0, dB / 20.0);
135 heading_add (double h1, double h2)
137 double result = h1 + h2;
144 heading_substract (double h1, double h2)
146 double result = h1 - h2;
153 get_heading_difference (double h1, double h2)
155 double diff = h1 - h2;
166 get_reciprocal_heading (double h)
168 return heading_add(h, 180);
171 // Searches for the closest airport whose Manhattan distance to
172 // @lat,@lon is inferior to @min_manhattan_distance (expressed in
173 // degrees) and for which @test_airport returns true. Returns NULL if
174 // no airport was found.
176 static const FGAirport *
177 get_closest_airport (double lat,
179 double min_manhattan_distance,
181 bool (C::*test_airport) (const FGAirport *))
183 const FGAirport *airport = NULL;
184 const airport_list *airport_list = globals->get_airports()->getAirportList();
186 for (size_t i = 0; i < airport_list->size(); i++)
188 const FGAirport *a = (*airport_list)[i];
189 double dist = fabs(lat - a->getLatitude()) + fabs(lon - a->getLongitude());
190 if (dist < min_manhattan_distance && (obj.*test_airport)(a))
193 min_manhattan_distance = dist;
200 ///////////////////////////////////////////////////////////////////////////////
201 // PropertiesHandler //////////////////////////////////////////////////////////
202 ///////////////////////////////////////////////////////////////////////////////
205 MK_VIII::PropertiesHandler::init ()
207 mk_node(ai_caged) = fgGetNode("/instrumentation/attitude-indicator/caged-flag", true);
208 mk_node(ai_roll) = fgGetNode("/instrumentation/attitude-indicator/indicated-roll-deg", true);
209 mk_node(ai_serviceable) = fgGetNode("/instrumentation/attitude-indicator/serviceable", true);
210 mk_node(altimeter_altitude) = fgGetNode("/instrumentation/altimeter/indicated-altitude-ft", true);
211 mk_node(altimeter_serviceable) = fgGetNode("/instrumentation/altimeter/serviceable", true);
212 mk_node(altitude) = fgGetNode("/position/altitude-ft", true);
213 mk_node(altitude_agl) = fgGetNode("/position/altitude-agl-ft", true);
214 mk_node(asi_serviceable) = fgGetNode("/instrumentation/airspeed-indicator/serviceable", true);
215 mk_node(asi_speed) = fgGetNode("/instrumentation/airspeed-indicator/indicated-speed-kt", true);
216 mk_node(autopilot_heading_lock) = fgGetNode("/autopilot/locks/heading", true);
217 mk_node(flaps) = fgGetNode("/controls/flight/flaps", true);
218 mk_node(gear_down) = fgGetNode("/controls/gear/gear-down", true);
219 mk_node(latitude) = fgGetNode("/position/latitude-deg", true);
220 mk_node(longitude) = fgGetNode("/position/longitude-deg", true);
221 mk_node(nav0_cdi_serviceable) = fgGetNode("/instrumentation/nav/cdi/serviceable", true);
222 mk_node(nav0_gs_distance) = fgGetNode("/instrumentation/nav/gs-distance", true);
223 mk_node(nav0_gs_needle_deflection) = fgGetNode("/instrumentation/nav/gs-needle-deflection", true);
224 mk_node(nav0_gs_serviceable) = fgGetNode("/instrumentation/nav/gs/serviceable", true);
225 mk_node(nav0_has_gs) = fgGetNode("/instrumentation/nav/has-gs", true);
226 mk_node(nav0_heading_needle_deflection) = fgGetNode("/instrumentation/nav/heading-needle-deflection", true);
227 mk_node(nav0_in_range) = fgGetNode("/instrumentation/nav/in-range", true);
228 mk_node(nav0_nav_loc) = fgGetNode("/instrumentation/nav/nav-loc", true);
229 mk_node(nav0_serviceable) = fgGetNode("/instrumentation/nav/serviceable", true);
230 mk_node(power) = fgGetNode(("/systems/electrical/outputs/" + mk->name).c_str(), mk->num, true);
231 mk_node(replay_state) = fgGetNode("/sim/freeze/replay-state", true);
232 mk_node(vs) = fgGetNode("/velocities/vertical-speed-fps", true);
236 MK_VIII::PropertiesHandler::unbind ()
238 vector<SGPropertyNode_ptr>::iterator iter;
240 for (iter = tied_properties.begin(); iter != tied_properties.end(); iter++)
243 tied_properties.clear();
246 ///////////////////////////////////////////////////////////////////////////////
247 // PowerHandler ///////////////////////////////////////////////////////////////
248 ///////////////////////////////////////////////////////////////////////////////
251 MK_VIII::PowerHandler::bind (SGPropertyNode *node)
253 mk->properties_handler.tie(node, "serviceable", SGRawValuePointer<bool>(&serviceable));
257 MK_VIII::PowerHandler::handle_abnormal_voltage (bool abnormal,
263 if (timer->start_or_elapsed() >= max_duration)
264 return true; // power loss
273 MK_VIII::PowerHandler::update ()
275 double voltage = mk_node(power)->getDoubleValue();
276 bool now_powered = true;
284 if (handle_abnormal_voltage(voltage < 20.5, &low_surge_timer, 0.03))
286 if (handle_abnormal_voltage(voltage < 22.0 || voltage > 30.3, &abnormal_timer, 300))
288 if (handle_abnormal_voltage(voltage > 32.2, &high_surge_timer, 1))
290 if (handle_abnormal_voltage(voltage > 37.8, &very_high_surge_timer, 0.1))
300 power_loss_timer.stop();
303 if (power_loss_timer.start_or_elapsed() >= 0.2)
315 MK_VIII::PowerHandler::power_on ()
318 mk->system_handler.power_on();
322 MK_VIII::PowerHandler::power_off ()
325 mk->system_handler.power_off();
326 mk->voice_player.stop(VoicePlayer::STOP_NOW);
327 mk->self_test_handler.power_off(); // run before IOHandler::power_off()
328 mk->io_handler.power_off();
329 mk->mode2_handler.power_off();
330 mk->mode6_handler.power_off();
333 ///////////////////////////////////////////////////////////////////////////////
334 // SystemHandler //////////////////////////////////////////////////////////////
335 ///////////////////////////////////////////////////////////////////////////////
338 MK_VIII::SystemHandler::power_on ()
340 state = STATE_BOOTING;
342 // [SPEC] 3.5.2 mentions a 20 seconds maximum boot delay. We use a
343 // random delay between 3 and 5 seconds.
345 boot_delay = 3 + sg_random() * 2;
350 MK_VIII::SystemHandler::power_off ()
358 MK_VIII::SystemHandler::update ()
360 if (state == STATE_BOOTING)
362 if (boot_timer.elapsed() >= boot_delay)
364 last_replay_state = mk_node(replay_state)->getIntValue();
366 mk->configuration_module.boot();
368 mk->io_handler.boot();
369 mk->fault_handler.boot();
370 mk->alert_handler.boot();
372 // inputs are needed by the following boot() routines
373 mk->io_handler.update_inputs();
375 mk->mode2_handler.boot();
376 mk->mode6_handler.boot();
380 mk->io_handler.post_boot();
383 else if (state != STATE_OFF && mk->configuration_module.state == ConfigurationModule::STATE_OK)
385 // If the replay state changes, switch to reposition mode for 3
386 // seconds ([SPEC] 6.0.5) to avoid spurious alerts.
388 int replay_state = mk_node(replay_state)->getIntValue();
389 if (replay_state != last_replay_state)
391 mk->alert_handler.reposition();
393 last_replay_state = replay_state;
394 state = STATE_REPOSITION;
395 reposition_timer.start();
398 if (state == STATE_REPOSITION && reposition_timer.elapsed() >= 3)
400 // inputs are needed by StateHandler::post_reposition()
401 mk->io_handler.update_inputs();
403 mk->state_handler.post_reposition();
410 ///////////////////////////////////////////////////////////////////////////////
411 // ConfigurationModule ////////////////////////////////////////////////////////
412 ///////////////////////////////////////////////////////////////////////////////
414 MK_VIII::ConfigurationModule::ConfigurationModule (MK_VIII *device)
417 // arbitrary defaults
418 categories[CATEGORY_AIRCRAFT_MODE_TYPE_SELECT] = 0;
419 categories[CATEGORY_AIR_DATA_INPUT_SELECT] = 1;
420 categories[CATEGORY_POSITION_INPUT_SELECT] = 2;
421 categories[CATEGORY_ALTITUDE_CALLOUTS] = 13;
422 categories[CATEGORY_AUDIO_MENU_SELECT] = 0;
423 categories[CATEGORY_TERRAIN_DISPLAY_SELECT] = 1;
424 categories[CATEGORY_OPTIONS_SELECT_GROUP_1] = 124;
425 categories[CATEGORY_RADIO_ALTITUDE_INPUT_SELECT] = 2;
426 categories[CATEGORY_NAVIGATION_INPUT_SELECT] = 3;
427 categories[CATEGORY_ATTITUDE_INPUT_SELECT] = 6;
428 categories[CATEGORY_HEADING_INPUT_SELECT] = 2;
429 categories[CATEGORY_WINDSHEAR_INPUT_SELECT] = 0;
430 categories[CATEGORY_INPUT_OUTPUT_DISCRETE_TYPE_SELECT] = 7;
431 categories[CATEGORY_AUDIO_OUTPUT_LEVEL] = 0;
432 categories[CATEGORY_UNDEFINED_INPUT_SELECT_1] = 0;
433 categories[CATEGORY_UNDEFINED_INPUT_SELECT_2] = 0;
434 categories[CATEGORY_UNDEFINED_INPUT_SELECT_3] = 0;
437 static double m1_t1_min_agl1 (double vs) { return -1620 - 1.1133 * vs; }
438 static double m1_t1_min_agl2 (double vs) { return -400 - 0.4 * vs; }
439 static double m1_t4_min_agl1 (double vs) { return -1625.47 - 1.1167 * vs; }
440 static double m1_t4_min_agl2 (double vs) { return -0.1958 * vs; }
442 static int m3_t1_max_agl (bool flap_override) { return 1500; }
443 static double m3_t1_max_alt_loss (bool flap_override, double agl) { return 5.4 + 0.092 * agl; }
444 static int m3_t2_max_agl (bool flap_override) { return flap_override ? 815 : 925; }
445 static double m3_t2_max_alt_loss (bool flap_override, double agl)
447 int bias = agl > 700 ? 5 : 0;
450 return (9.0 + 0.184 * agl) + bias;
452 return (5.4 + 0.092 * agl) + bias;
455 static double m4_t1_min_agl2 (double airspeed) { return -1083 + 8.333 * airspeed; }
456 static double m4_t568_a_min_agl2 (double airspeed) { return -1523 + 11.36 * airspeed; }
457 static double m4_t79_a_min_agl2 (double airspeed) { return -1182 + 11.36 * airspeed; }
458 static double m4_t568_b_min_agl2 (double airspeed) { return -1570 + 11.6 * airspeed; }
459 static double m4_t79_b_min_agl2 (double airspeed) { return -1222 + 11.6 * airspeed; }
462 MK_VIII::ConfigurationModule::m6_t2_is_bank_angle (Parameter<double> *agl,
468 if (agl->ncd || agl->get() > 122)
469 return abs_roll_deg > 33;
473 if (agl->ncd || agl->get() > 2450)
474 return abs_roll_deg > 55;
475 else if (agl->get() > 150)
476 return agl->get() < 153.33333 * abs_roll_deg - 5983.3333;
480 return agl->get() < 4 * abs_roll_deg - 10;
481 else if (agl->get() > 5)
482 return abs_roll_deg > 10;
488 MK_VIII::ConfigurationModule::m6_t4_is_bank_angle (Parameter<double> *agl,
494 if (agl->ncd || agl->get() > 156)
495 return abs_roll_deg > 33;
499 if (agl->ncd || agl->get() > 210)
500 return abs_roll_deg > 50;
504 return agl->get() < 5.7142857 * abs_roll_deg - 75.714286;
510 MK_VIII::ConfigurationModule::read_aircraft_mode_type_select (int value)
512 static const Mode1Handler::EnvelopesConfiguration m1_t1 =
513 { false, 10, m1_t1_min_agl1, 284, m1_t1_min_agl2, 2450 };
514 static const Mode1Handler::EnvelopesConfiguration m1_t4 =
515 { true, 50, m1_t4_min_agl1, 346, m1_t4_min_agl2, 1958 };
517 static const Mode2Handler::Configuration m2_t1 = { 190, 280 };
518 static const Mode2Handler::Configuration m2_t4 = { 220, 310 };
519 static const Mode2Handler::Configuration m2_t5 = { 220, 310 };
521 static const Mode3Handler::Configuration m3_t1 =
522 { 30, m3_t1_max_agl, m3_t1_max_alt_loss };
523 static const Mode3Handler::Configuration m3_t2 =
524 { 50, m3_t2_max_agl, m3_t2_max_alt_loss };
526 static const Mode4Handler::EnvelopesConfiguration m4_t1_ac =
527 { 190, 250, 500, m4_t1_min_agl2, 1000 };
528 static const Mode4Handler::EnvelopesConfiguration m4_t5_ac =
529 { 178, 200, 500, m4_t568_a_min_agl2, 1000 };
530 static const Mode4Handler::EnvelopesConfiguration m4_t68_ac =
531 { 178, 200, 500, m4_t568_a_min_agl2, 750 };
532 static const Mode4Handler::EnvelopesConfiguration m4_t79_ac =
533 { 148, 170, 500, m4_t79_a_min_agl2, 750 };
535 static const Mode4Handler::EnvelopesConfiguration m4_t1_b =
536 { 159, 250, 245, m4_t1_min_agl2, 1000 };
537 static const Mode4Handler::EnvelopesConfiguration m4_t5_b =
538 { 148, 200, 200, m4_t568_b_min_agl2, 1000 };
539 static const Mode4Handler::EnvelopesConfiguration m4_t6_b =
540 { 150, 200, 170, m4_t568_b_min_agl2, 750 };
541 static const Mode4Handler::EnvelopesConfiguration m4_t7_b =
542 { 120, 170, 170, m4_t79_b_min_agl2, 750 };
543 static const Mode4Handler::EnvelopesConfiguration m4_t8_b =
544 { 148, 200, 150, m4_t568_b_min_agl2, 750 };
545 static const Mode4Handler::EnvelopesConfiguration m4_t9_b =
546 { 118, 170, 150, m4_t79_b_min_agl2, 750 };
548 static const Mode4Handler::ModesConfiguration m4_t1 = { &m4_t1_ac, &m4_t1_b };
549 static const Mode4Handler::ModesConfiguration m4_t5 = { &m4_t5_ac, &m4_t5_b };
550 static const Mode4Handler::ModesConfiguration m4_t6 = { &m4_t68_ac, &m4_t6_b };
551 static const Mode4Handler::ModesConfiguration m4_t7 = { &m4_t79_ac, &m4_t7_b };
552 static const Mode4Handler::ModesConfiguration m4_t8 = { &m4_t68_ac, &m4_t8_b };
553 static const Mode4Handler::ModesConfiguration m4_t9 = { &m4_t79_ac, &m4_t9_b };
555 static Mode6Handler::BankAnglePredicate m6_t2 = m6_t2_is_bank_angle;
556 static Mode6Handler::BankAnglePredicate m6_t4 = m6_t4_is_bank_angle;
558 static const IOHandler::FaultsConfiguration f_slow = { 180, 200 };
559 static const IOHandler::FaultsConfiguration f_fast = { 250, 290 };
564 const Mode1Handler::EnvelopesConfiguration *m1;
565 const Mode2Handler::Configuration *m2;
566 const Mode3Handler::Configuration *m3;
567 const Mode4Handler::ModesConfiguration *m4;
568 Mode6Handler::BankAnglePredicate m6;
569 const IOHandler::FaultsConfiguration *f;
571 } aircraft_types[] = {
572 { 0, &m1_t4, &m2_t4, &m3_t2, &m4_t6, m6_t4, &f_fast, 2000 },
573 { 1, &m1_t4, &m2_t4, &m3_t2, &m4_t8, m6_t4, &f_fast, 2000 },
574 { 2, &m1_t4, &m2_t4, &m3_t2, &m4_t5, m6_t4, &f_fast, 2000 },
575 { 3, &m1_t4, &m2_t5, &m3_t2, &m4_t7, m6_t4, &f_slow, 2000 },
576 { 4, &m1_t4, &m2_t5, &m3_t2, &m4_t9, m6_t4, &f_slow, 2000 },
577 { 254, &m1_t1, &m2_t1, &m3_t1, &m4_t1, m6_t2, &f_fast, 3500 },
578 { 255, &m1_t1, &m2_t1, &m3_t1, &m4_t1, m6_t2, &f_fast, 2000 }
581 for (int i = 0; i < n_elements(aircraft_types); i++)
582 if (aircraft_types[i].type == value)
584 mk->mode1_handler.conf.envelopes = aircraft_types[i].m1;
585 mk->mode2_handler.conf = aircraft_types[i].m2;
586 mk->mode3_handler.conf = aircraft_types[i].m3;
587 mk->mode4_handler.conf.modes = aircraft_types[i].m4;
588 mk->mode6_handler.conf.is_bank_angle = aircraft_types[i].m6;
589 mk->io_handler.conf.faults = aircraft_types[i].f;
590 mk->conf.runway_database = aircraft_types[i].runway_database;
594 state = STATE_INVALID_AIRCRAFT_TYPE;
599 MK_VIII::ConfigurationModule::read_air_data_input_select (int value)
602 return (value >= 0 && value <= 6) || (value >= 10 && value <= 13) || value == 255;
606 MK_VIII::ConfigurationModule::read_position_input_select (int value)
609 mk->io_handler.conf.use_internal_gps = true;
610 else if ((value >= 0 && value <= 5)
611 || (value >= 10 && value <= 13)
614 mk->io_handler.conf.use_internal_gps = false;
622 MK_VIII::ConfigurationModule::read_altitude_callouts (int value)
637 { 0, { MINIMUMS, SMART_500, 200, 100, 50, 40, 30, 20, 10, 0 } },
638 { 1, { MINIMUMS, SMART_500, 200, 0 } },
639 { 2, { MINIMUMS, SMART_500, 100, 50, 40, 30, 20, 10, 0 } },
640 { 3, { MINIMUMS, SMART_500, 0 } },
641 { 4, { MINIMUMS, 200, 100, 50, 40, 30, 20, 10, 0 } },
642 { 5, { MINIMUMS, 200, 0 } },
643 { 6, { MINIMUMS, 100, 50, 40, 30, 20, 10, 0 } },
645 { 8, { MINIMUMS, 0 } },
646 { 9, { MINIMUMS, 500, 200, 100, 50, 40, 30, 20, 10, 0 } },
647 { 10, { MINIMUMS, 500, 200, 0 } },
648 { 11, { MINIMUMS, 500, 100, 50, 40, 30, 20, 10, 0 } },
649 { 12, { MINIMUMS, 500, 0 } },
650 { 13, { MINIMUMS, 1000, 500, 400, 300, 200, 100, 50, 40, 30, 20, 10, 0 } },
651 { 14, { MINIMUMS, 100, 0 } },
652 { 15, { MINIMUMS, 200, 100, 0 } },
653 { 100, { FIELD_500, 0 } },
654 { 101, { FIELD_500_ABOVE, 0 } }
659 mk->mode6_handler.conf.minimums_enabled = false;
660 mk->mode6_handler.conf.smart_500_enabled = false;
661 mk->mode6_handler.conf.above_field_voice = NULL;
662 for (i = 0; i < n_altitude_callouts; i++)
663 mk->mode6_handler.conf.altitude_callouts_enabled[i] = false;
665 for (i = 0; i < n_elements(values); i++)
666 if (values[i].id == value)
668 for (int j = 0; values[i].callouts[j] != 0; j++)
669 switch (values[i].callouts[j])
672 mk->mode6_handler.conf.minimums_enabled = true;
676 mk->mode6_handler.conf.smart_500_enabled = true;
680 mk->mode6_handler.conf.above_field_voice = mk_altitude_voice(Mode6Handler::ALTITUDE_CALLOUT_500);
683 case FIELD_500_ABOVE:
684 mk->mode6_handler.conf.above_field_voice = mk_voice(five_hundred_above);
688 for (int k = 0; k < n_altitude_callouts; k++)
689 if (mk->mode6_handler.altitude_callout_definitions[k] == values[i].callouts[j])
690 mk->mode6_handler.conf.altitude_callouts_enabled[k] = true;
701 MK_VIII::ConfigurationModule::read_audio_menu_select (int value)
703 if (value == 0 || value == 1)
704 mk->mode4_handler.conf.voice_too_low_gear = mk_voice(too_low_gear);
705 else if (value == 2 || value == 3)
706 mk->mode4_handler.conf.voice_too_low_gear = mk_voice(too_low_flaps);
714 MK_VIII::ConfigurationModule::read_terrain_display_select (int value)
717 mk->tcf_handler.conf.enabled = false;
718 else if (value == 0 || value == 1 || (value >= 3 && value <= 15)
719 || (value >= 18 && value <= 20) || (value >= 235 && value <= 255))
720 mk->tcf_handler.conf.enabled = true;
728 MK_VIII::ConfigurationModule::read_options_select_group_1 (int value)
730 if (value >= 0 && value < 128)
732 mk->io_handler.conf.flap_reversal = test_bits(value, 1 << 1);
733 mk->mode6_handler.conf.bank_angle_enabled = test_bits(value, 1 << 2);
734 mk->io_handler.conf.steep_approach_enabled = test_bits(value, 1 << 6);
742 MK_VIII::ConfigurationModule::read_radio_altitude_input_select (int value)
745 return (value >= 0 && value <= 4) || (value >= 251 && value <= 255);
749 MK_VIII::ConfigurationModule::read_navigation_input_select (int value)
751 if (value >= 0 && value <= 2)
752 mk->io_handler.conf.localizer_enabled = false;
753 else if ((value >= 3 && value <= 5) || (value >= 250 && value <= 255))
754 mk->io_handler.conf.localizer_enabled = true;
762 MK_VIII::ConfigurationModule::read_attitude_input_select (int value)
765 return (value >= 0 && value <= 6) || value == 253 || value == 255;
769 MK_VIII::ConfigurationModule::read_heading_input_select (int value)
772 return (value >= 0 && value <= 3) || value == 254 || value == 255;
776 MK_VIII::ConfigurationModule::read_windshear_input_select (int value)
779 return value == 0 || (value >= 253 && value <= 255);
783 MK_VIII::ConfigurationModule::read_input_output_discrete_type_select (int value)
788 IOHandler::LampConfiguration lamp_conf;
789 bool gpws_inhibit_enabled;
790 bool momentary_flap_override_enabled;
791 bool alternate_steep_approach;
793 { 0, { false, false }, false, true, true },
794 { 1, { true, false }, false, true, true },
795 { 2, { false, false }, true, true, true },
796 { 3, { true, false }, true, true, true },
797 { 4, { false, true }, true, true, true },
798 { 5, { true, true }, true, true, true },
799 { 6, { false, false }, true, true, false },
800 { 7, { true, false }, true, true, false },
801 { 254, { false, false }, true, false, true },
802 { 255, { false, false }, true, false, true }
805 for (int i = 0; i < n_elements(io_types); i++)
806 if (io_types[i].type == value)
808 mk->io_handler.conf.lamp = &io_types[i].lamp_conf;
809 mk->io_handler.conf.gpws_inhibit_enabled = io_types[i].gpws_inhibit_enabled;
810 mk->io_handler.conf.momentary_flap_override_enabled = io_types[i].momentary_flap_override_enabled;
811 mk->io_handler.conf.alternate_steep_approach = io_types[i].alternate_steep_approach;
819 MK_VIII::ConfigurationModule::read_audio_output_level (int value)
833 for (int i = 0; i < n_elements(values); i++)
834 if (values[i].id == value)
836 mk->voice_player.set_volume(mk->voice_player.conf.volume = modify_amplitude(1.0, values[i].relative_dB));
840 // The self test needs the voice player even when the configuration
841 // is invalid, so set a default value.
842 mk->voice_player.set_volume(mk->voice_player.conf.volume = 1.0);
847 MK_VIII::ConfigurationModule::read_undefined_input_select (int value)
854 MK_VIII::ConfigurationModule::boot ()
856 bool (MK_VIII::ConfigurationModule::*readers[N_CATEGORIES]) (int) = {
857 &MK_VIII::ConfigurationModule::read_aircraft_mode_type_select,
858 &MK_VIII::ConfigurationModule::read_air_data_input_select,
859 &MK_VIII::ConfigurationModule::read_position_input_select,
860 &MK_VIII::ConfigurationModule::read_altitude_callouts,
861 &MK_VIII::ConfigurationModule::read_audio_menu_select,
862 &MK_VIII::ConfigurationModule::read_terrain_display_select,
863 &MK_VIII::ConfigurationModule::read_options_select_group_1,
864 &MK_VIII::ConfigurationModule::read_radio_altitude_input_select,
865 &MK_VIII::ConfigurationModule::read_navigation_input_select,
866 &MK_VIII::ConfigurationModule::read_attitude_input_select,
867 &MK_VIII::ConfigurationModule::read_heading_input_select,
868 &MK_VIII::ConfigurationModule::read_windshear_input_select,
869 &MK_VIII::ConfigurationModule::read_input_output_discrete_type_select,
870 &MK_VIII::ConfigurationModule::read_audio_output_level,
871 &MK_VIII::ConfigurationModule::read_undefined_input_select,
872 &MK_VIII::ConfigurationModule::read_undefined_input_select,
873 &MK_VIII::ConfigurationModule::read_undefined_input_select
876 memcpy(effective_categories, categories, sizeof(categories));
879 for (int i = 0; i < N_CATEGORIES; i++)
880 if (! (this->*readers[i])(effective_categories[i]))
882 SG_LOG(SG_INSTR, SG_ALERT, "MK VIII EGPWS configuration category " << i + 1 << ": invalid value " << effective_categories[i]);
884 if (state == STATE_OK)
885 state = STATE_INVALID_DATABASE;
887 mk_doutput(gpws_inop) = true;
888 mk_doutput(tad_inop) = true;
895 if (mk->mode6_handler.conf.above_field_voice && ! mk->tcf_handler.conf.enabled)
898 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");
899 state = STATE_INVALID_DATABASE;
904 MK_VIII::ConfigurationModule::bind (SGPropertyNode *node)
906 for (int i = 0; i < N_CATEGORIES; i++)
908 std::ostringstream name;
909 name << "configuration-module/category-" << i + 1;
910 mk->properties_handler.tie(node, name.str().c_str(), SGRawValuePointer<int>(&categories[i]));
914 ///////////////////////////////////////////////////////////////////////////////
915 // FaultHandler ///////////////////////////////////////////////////////////////
916 ///////////////////////////////////////////////////////////////////////////////
918 // [INSTALL] only specifies that the faults cause a GPWS INOP
919 // indication. We arbitrarily set a TAD INOP when it makes sense.
920 const unsigned int MK_VIII::FaultHandler::fault_inops[] = {
921 INOP_GPWS | INOP_TAD, // [INSTALL] 3.15.1.3
922 INOP_GPWS, // [INSTALL] appendix E 6.6.2
923 INOP_GPWS, // [INSTALL] appendix E 6.6.4
924 INOP_GPWS, // [INSTALL] appendix E 6.6.6
925 INOP_GPWS | INOP_TAD, // [INSTALL] appendix E 6.6.7
926 INOP_GPWS, // [INSTALL] appendix E 6.6.13
927 INOP_GPWS, // [INSTALL] appendix E 6.6.25
928 INOP_GPWS, // [INSTALL] appendix E 6.6.27
929 INOP_TAD, // unspecified
930 INOP_GPWS, // unspecified
931 INOP_GPWS, // unspecified
932 // [INSTALL] 2.3.10.1 specifies that GPWS INOP is "activated during
933 // any detected partial or total failure of the GPWS modes 1-5", so
934 // do not set any INOP for mode 6 and bank angle.
937 INOP_TAD // unspecified
941 MK_VIII::FaultHandler::has_faults () const
943 for (int i = 0; i < N_FAULTS; i++)
951 MK_VIII::FaultHandler::has_faults (unsigned int inop)
953 for (int i = 0; i < N_FAULTS; i++)
954 if (faults[i] && test_bits(fault_inops[i], inop))
961 MK_VIII::FaultHandler::boot ()
963 memset(faults, 0, sizeof(faults));
967 MK_VIII::FaultHandler::set_fault (Fault fault)
971 faults[fault] = true;
973 mk->self_test_handler.set_inop();
975 if (test_bits(fault_inops[fault], INOP_GPWS))
977 mk_unset_alerts(~mk_alert(TCF_TOO_LOW_TERRAIN));
978 mk_doutput(gpws_inop) = true;
980 if (test_bits(fault_inops[fault], INOP_TAD))
982 mk_unset_alerts(mk_alert(TCF_TOO_LOW_TERRAIN));
983 mk_doutput(tad_inop) = true;
989 MK_VIII::FaultHandler::unset_fault (Fault fault)
993 faults[fault] = false;
994 if (! has_faults(INOP_GPWS))
995 mk_doutput(gpws_inop) = false;
996 if (! has_faults(INOP_TAD))
997 mk_doutput(tad_inop) = false;
1001 ///////////////////////////////////////////////////////////////////////////////
1002 // IOHandler //////////////////////////////////////////////////////////////////
1003 ///////////////////////////////////////////////////////////////////////////////
1006 MK_VIII::IOHandler::TerrainClearanceFilter::update (double agl)
1008 // [PILOT] page 20 specifies that the terrain clearance is equal to
1009 // 75% of the radio altitude, averaged over the previous 15 seconds.
1011 deque< Sample<double> >::iterator iter;
1013 // remove samples older than 15 seconds
1014 for (iter = samples.begin(); iter != samples.end() && globals->get_sim_time_sec() - (*iter).timestamp >= 15; iter = samples.begin())
1015 samples.erase(iter);
1017 // append new sample
1018 samples.push_back(Sample<double>(agl));
1020 // calculate average
1021 double new_value = 0;
1022 if (samples.size() > 0)
1024 for (iter = samples.begin(); iter != samples.end(); iter++)
1025 new_value += (*iter).value;
1026 new_value /= samples.size();
1030 if (new_value > value)
1037 MK_VIII::IOHandler::TerrainClearanceFilter::reset ()
1043 MK_VIII::IOHandler::IOHandler (MK_VIII *device)
1044 : mk(device), _lamp(LAMP_NONE)
1046 memset(&input_feeders, 0, sizeof(input_feeders));
1047 memset(&inputs.discretes, 0, sizeof(inputs.discretes));
1048 memset(&outputs, 0, sizeof(outputs));
1049 memset(&power_saved, 0, sizeof(power_saved));
1051 mk_dinput_feed(landing_gear) = true;
1052 mk_dinput_feed(landing_flaps) = true;
1053 mk_dinput_feed(glideslope_inhibit) = true;
1054 mk_dinput_feed(decision_height) = true;
1055 mk_dinput_feed(autopilot_engaged) = true;
1056 mk_ainput_feed(uncorrected_barometric_altitude) = true;
1057 mk_ainput_feed(barometric_altitude_rate) = true;
1058 mk_ainput_feed(radio_altitude) = true;
1059 mk_ainput_feed(glideslope_deviation) = true;
1060 mk_ainput_feed(roll_angle) = true;
1061 mk_ainput_feed(localizer_deviation) = true;
1062 mk_ainput_feed(computed_airspeed) = true;
1064 // will be unset on power on
1065 mk_doutput(gpws_inop) = true;
1066 mk_doutput(tad_inop) = true;
1070 MK_VIII::IOHandler::boot ()
1072 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
1075 mk_doutput(gpws_inop) = false;
1076 mk_doutput(tad_inop) = false;
1078 mk_doutput(glideslope_cancel) = power_saved.glideslope_cancel;
1080 altitude_samples.clear();
1084 MK_VIII::IOHandler::post_boot ()
1086 if (momentary_steep_approach_enabled())
1088 last_landing_gear = mk_dinput(landing_gear);
1089 last_real_flaps_down = real_flaps_down();
1092 // read externally-latching input discretes
1093 update_alternate_discrete_input(&mk_dinput(mode6_low_volume));
1094 update_alternate_discrete_input(&mk_dinput(audio_inhibit));
1098 MK_VIII::IOHandler::power_off ()
1100 power_saved.glideslope_cancel = mk_doutput(glideslope_cancel); // [SPEC] 6.2.5
1102 memset(&outputs, 0, sizeof(outputs));
1104 audio_inhibit_fault_timer.stop();
1105 landing_gear_fault_timer.stop();
1106 flaps_down_fault_timer.stop();
1107 momentary_flap_override_fault_timer.stop();
1108 self_test_fault_timer.stop();
1109 glideslope_cancel_fault_timer.stop();
1110 steep_approach_fault_timer.stop();
1111 gpws_inhibit_fault_timer.stop();
1112 ta_tcf_inhibit_fault_timer.stop();
1115 mk_doutput(gpws_inop) = true;
1116 mk_doutput(tad_inop) = true;
1120 MK_VIII::IOHandler::enter_ground ()
1122 reset_terrain_clearance();
1124 if (conf.momentary_flap_override_enabled)
1125 mk_doutput(flap_override) = false; // [INSTALL] 3.15.1.6
1129 MK_VIII::IOHandler::enter_takeoff ()
1131 reset_terrain_clearance();
1133 if (momentary_steep_approach_enabled())
1134 // landing or go-around, disable steep approach as per [SPEC] 6.2.1
1135 mk_doutput(steep_approach) = false;
1139 MK_VIII::IOHandler::update_inputs ()
1141 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
1144 // 1. process input feeders
1146 if (mk_dinput_feed(landing_gear))
1147 mk_dinput(landing_gear) = mk_node(gear_down)->getBoolValue();
1148 if (mk_dinput_feed(landing_flaps))
1149 mk_dinput(landing_flaps) = mk_node(flaps)->getDoubleValue() < 1;
1150 if (mk_dinput_feed(glideslope_inhibit))
1151 mk_dinput(glideslope_inhibit) = mk_node(nav0_gs_distance)->getDoubleValue() < 0;
1152 if (mk_dinput_feed(autopilot_engaged))
1156 mode = mk_node(autopilot_heading_lock)->getStringValue();
1157 mk_dinput(autopilot_engaged) = mode && *mode;
1160 if (mk_ainput_feed(uncorrected_barometric_altitude))
1162 if (mk_node(altimeter_serviceable)->getBoolValue())
1163 mk_ainput(uncorrected_barometric_altitude).set(mk_node(altimeter_altitude)->getDoubleValue());
1165 mk_ainput(uncorrected_barometric_altitude).unset();
1167 if (mk_ainput_feed(barometric_altitude_rate))
1168 // Do not use the vsi, because it is pressure-based only, and
1169 // therefore too laggy for GPWS alerting purposes. I guess that
1170 // a real ADC combines pressure-based and inerta-based altitude
1171 // rates to provide a non-laggy rate. That non-laggy rate is
1172 // best emulated by /velocities/vertical-speed-fps * 60.
1173 mk_ainput(barometric_altitude_rate).set(mk_node(vs)->getDoubleValue() * 60);
1174 if (mk_ainput_feed(radio_altitude))
1176 // Some flight models may return negative values when on the
1177 // ground or after a crash; do not allow them.
1178 double agl = mk_node(altitude_agl)->getDoubleValue();
1179 mk_ainput(radio_altitude).set(SG_MAX2(0.0, agl));
1181 if (mk_ainput_feed(glideslope_deviation))
1183 if (mk_node(nav0_serviceable)->getBoolValue()
1184 && mk_node(nav0_gs_serviceable)->getBoolValue()
1185 && mk_node(nav0_in_range)->getBoolValue()
1186 && mk_node(nav0_has_gs)->getBoolValue())
1187 // gs-needle-deflection is expressed in degrees, and 1 dot =
1188 // 0.32 degrees (according to
1189 // http://www.ntsb.gov/Recs/letters/2000/A00_41_45.pdf)
1190 mk_ainput(glideslope_deviation).set(mk_node(nav0_gs_needle_deflection)->getDoubleValue() / 0.32 * GLIDESLOPE_DOTS_TO_DDM);
1192 mk_ainput(glideslope_deviation).unset();
1194 if (mk_ainput_feed(roll_angle))
1196 if (mk_node(ai_serviceable)->getBoolValue() && ! mk_node(ai_caged)->getBoolValue())
1197 mk_ainput(roll_angle).set(mk_node(ai_roll)->getDoubleValue());
1199 mk_ainput(roll_angle).unset();
1201 if (mk_ainput_feed(localizer_deviation))
1203 if (mk_node(nav0_serviceable)->getBoolValue()
1204 && mk_node(nav0_cdi_serviceable)->getBoolValue()
1205 && mk_node(nav0_in_range)->getBoolValue()
1206 && mk_node(nav0_nav_loc)->getBoolValue())
1207 // heading-needle-deflection is expressed in degrees, and 1
1208 // dot = 2 degrees (0.5 degrees for ILS, but navradio.cxx
1209 // performs the conversion)
1210 mk_ainput(localizer_deviation).set(mk_node(nav0_heading_needle_deflection)->getDoubleValue() / 2 * LOCALIZER_DOTS_TO_DDM);
1212 mk_ainput(localizer_deviation).unset();
1214 if (mk_ainput_feed(computed_airspeed))
1216 if (mk_node(asi_serviceable)->getBoolValue())
1217 mk_ainput(computed_airspeed).set(mk_node(asi_speed)->getDoubleValue());
1219 mk_ainput(computed_airspeed).unset();
1224 mk_data(decision_height).set(&mk_ainput(decision_height));
1225 mk_data(radio_altitude).set(&mk_ainput(radio_altitude));
1226 mk_data(roll_angle).set(&mk_ainput(roll_angle));
1228 // update barometric_altitude_rate as per [SPEC] 6.2.1: "During
1229 // normal conditions, the system will base Mode 1 computations upon
1230 // barometric rate from the ADC. If this computed data is not valid
1231 // or available then the system will use internally computed
1232 // barometric altitude rate."
1234 if (! mk_ainput(barometric_altitude_rate).ncd)
1235 // the altitude rate input is valid, use it
1236 mk_data(barometric_altitude_rate).set(mk_ainput(barometric_altitude_rate).get());
1241 // The altitude rate input is invalid. We can compute an
1242 // altitude rate if all the following conditions are true:
1244 // - the altitude input is valid
1245 // - there is an altitude sample with age >= 1 second
1246 // - that altitude sample is valid
1248 if (! mk_ainput(uncorrected_barometric_altitude).ncd)
1250 if (! altitude_samples.empty() && ! altitude_samples.front().value.ncd)
1252 double elapsed = globals->get_sim_time_sec() - altitude_samples.front().timestamp;
1256 mk_data(barometric_altitude_rate).set((mk_ainput(uncorrected_barometric_altitude).get() - altitude_samples.front().value.get()) / elapsed * 60);
1262 mk_data(barometric_altitude_rate).unset();
1265 altitude_samples.push_back(Sample< Parameter<double> >(mk_ainput(uncorrected_barometric_altitude)));
1267 // Erase everything from the beginning of the list up to the sample
1268 // preceding the most recent sample whose age is >= 1 second.
1270 deque< Sample< Parameter<double> > >::iterator erase_last = altitude_samples.begin();
1271 deque< Sample< Parameter<double> > >::iterator iter;
1273 for (iter = altitude_samples.begin(); iter != altitude_samples.end(); iter++)
1274 if (globals->get_sim_time_sec() - (*iter).timestamp >= 1)
1279 if (erase_last != altitude_samples.begin())
1280 altitude_samples.erase(altitude_samples.begin(), erase_last);
1284 if (conf.use_internal_gps)
1286 mk_data(gps_altitude).set(mk_node(altitude)->getDoubleValue());
1287 mk_data(gps_latitude).set(mk_node(latitude)->getDoubleValue());
1288 mk_data(gps_longitude).set(mk_node(longitude)->getDoubleValue());
1289 mk_data(gps_vertical_figure_of_merit).set(0.0);
1293 mk_data(gps_altitude).set(&mk_ainput(gps_altitude));
1294 mk_data(gps_latitude).set(&mk_ainput(gps_latitude));
1295 mk_data(gps_longitude).set(&mk_ainput(gps_longitude));
1296 mk_data(gps_vertical_figure_of_merit).set(&mk_ainput(gps_vertical_figure_of_merit));
1299 // update glideslope and localizer
1301 mk_data(glideslope_deviation_dots).set(&mk_ainput(glideslope_deviation), GLIDESLOPE_DDM_TO_DOTS);
1302 mk_data(localizer_deviation_dots).set(&mk_ainput(localizer_deviation), LOCALIZER_DDM_TO_DOTS);
1304 // Update geometric altitude; [SPEC] 6.7.8 provides an overview of a
1305 // complex algorithm which combines several input sources to
1306 // calculate the geometric altitude. Since the exact algorithm is
1307 // not given, we fallback to a much simpler method.
1309 if (! mk_data(gps_altitude).ncd)
1310 mk_data(geometric_altitude).set(mk_data(gps_altitude).get());
1311 else if (! mk_ainput(uncorrected_barometric_altitude).ncd)
1312 mk_data(geometric_altitude).set(mk_ainput(uncorrected_barometric_altitude).get());
1314 mk_data(geometric_altitude).unset();
1316 // update terrain clearance
1318 update_terrain_clearance();
1320 // 3. perform sanity checks
1322 if (! mk_data(radio_altitude).ncd && mk_data(radio_altitude).get() < 0)
1323 mk_data(radio_altitude).unset();
1325 if (! mk_data(decision_height).ncd && mk_data(decision_height).get() < 0)
1326 mk_data(decision_height).unset();
1328 if (! mk_data(gps_latitude).ncd
1329 && (mk_data(gps_latitude).get() < -90
1330 || mk_data(gps_latitude).get() > 90))
1331 mk_data(gps_latitude).unset();
1333 if (! mk_data(gps_longitude).ncd
1334 && (mk_data(gps_longitude).get() < -180
1335 || mk_data(gps_longitude).get() > 180))
1336 mk_data(gps_longitude).unset();
1338 if (! mk_data(roll_angle).ncd
1339 && ((mk_data(roll_angle).get() < -180)
1340 || (mk_data(roll_angle).get() > 180)))
1341 mk_data(roll_angle).unset();
1343 // 4. process input feeders requiring data computed above
1345 if (mk_dinput_feed(decision_height))
1346 mk_dinput(decision_height) = ! mk_data(radio_altitude).ncd
1347 && ! mk_data(decision_height).ncd
1348 && mk_data(radio_altitude).get() <= mk_data(decision_height).get();
1352 MK_VIII::IOHandler::update_terrain_clearance ()
1354 if (! mk_data(radio_altitude).ncd)
1355 mk_data(terrain_clearance).set(terrain_clearance_filter.update(mk_data(radio_altitude).get()));
1357 mk_data(terrain_clearance).unset();
1361 MK_VIII::IOHandler::reset_terrain_clearance ()
1363 terrain_clearance_filter.reset();
1364 update_terrain_clearance();
1368 MK_VIII::IOHandler::handle_input_fault (bool test, FaultHandler::Fault fault)
1372 if (! mk->fault_handler.faults[fault])
1373 mk->fault_handler.set_fault(fault);
1376 mk->fault_handler.unset_fault(fault);
1380 MK_VIII::IOHandler::handle_input_fault (bool test,
1382 double max_duration,
1383 FaultHandler::Fault fault)
1387 if (! mk->fault_handler.faults[fault])
1389 if (timer->start_or_elapsed() >= max_duration)
1390 mk->fault_handler.set_fault(fault);
1395 mk->fault_handler.unset_fault(fault);
1401 MK_VIII::IOHandler::update_input_faults ()
1403 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
1406 // [INSTALL] 3.15.1.3
1407 handle_input_fault(mk_dinput(audio_inhibit),
1408 &audio_inhibit_fault_timer,
1410 FaultHandler::FAULT_ALL_MODES_INHIBIT);
1412 // [INSTALL] appendix E 6.6.2
1413 handle_input_fault(mk_dinput(landing_gear)
1414 && ! mk_ainput(computed_airspeed).ncd
1415 && mk_ainput(computed_airspeed).get() > conf.faults->max_gear_down_airspeed,
1416 &landing_gear_fault_timer,
1418 FaultHandler::FAULT_GEAR_SWITCH);
1420 // [INSTALL] appendix E 6.6.4
1421 handle_input_fault(flaps_down()
1422 && ! mk_ainput(computed_airspeed).ncd
1423 && mk_ainput(computed_airspeed).get() > conf.faults->max_flaps_down_airspeed,
1424 &flaps_down_fault_timer,
1426 FaultHandler::FAULT_FLAPS_SWITCH);
1428 // [INSTALL] appendix E 6.6.6
1429 if (conf.momentary_flap_override_enabled)
1430 handle_input_fault(mk_dinput(momentary_flap_override),
1431 &momentary_flap_override_fault_timer,
1433 FaultHandler::FAULT_MOMENTARY_FLAP_OVERRIDE_INVALID);
1435 // [INSTALL] appendix E 6.6.7
1436 handle_input_fault(mk_dinput(self_test),
1437 &self_test_fault_timer,
1439 FaultHandler::FAULT_SELF_TEST_INVALID);
1441 // [INSTALL] appendix E 6.6.13
1442 handle_input_fault(mk_dinput(glideslope_cancel),
1443 &glideslope_cancel_fault_timer,
1445 FaultHandler::FAULT_GLIDESLOPE_CANCEL_INVALID);
1447 // [INSTALL] appendix E 6.6.25
1448 if (momentary_steep_approach_enabled())
1449 handle_input_fault(mk_dinput(steep_approach),
1450 &steep_approach_fault_timer,
1452 FaultHandler::FAULT_STEEP_APPROACH_INVALID);
1454 // [INSTALL] appendix E 6.6.27
1455 if (conf.gpws_inhibit_enabled)
1456 handle_input_fault(mk_dinput(gpws_inhibit),
1457 &gpws_inhibit_fault_timer,
1459 FaultHandler::FAULT_GPWS_INHIBIT);
1461 // [INSTALL] does not specify a fault for this one, but it makes
1462 // sense to have it behave like GPWS inhibit
1463 handle_input_fault(mk_dinput(ta_tcf_inhibit),
1464 &ta_tcf_inhibit_fault_timer,
1466 FaultHandler::FAULT_TA_TCF_INHIBIT);
1468 // [PILOT] page 48: "In the event that required data for a
1469 // particular function is not available, then that function is
1470 // automatically inhibited and annunciated"
1472 handle_input_fault(mk_data(radio_altitude).ncd
1473 || mk_ainput(uncorrected_barometric_altitude).ncd
1474 || mk_data(barometric_altitude_rate).ncd
1475 || mk_ainput(computed_airspeed).ncd
1476 || mk_data(terrain_clearance).ncd,
1477 FaultHandler::FAULT_MODES14_INPUTS_INVALID);
1479 if (! mk_dinput(glideslope_inhibit))
1480 handle_input_fault(mk_data(radio_altitude).ncd,
1481 FaultHandler::FAULT_MODE5_INPUTS_INVALID);
1483 if (mk->mode6_handler.altitude_callouts_enabled())
1484 handle_input_fault(mk->mode6_handler.conf.above_field_voice
1485 ? (mk_data(gps_latitude).ncd
1486 || mk_data(gps_longitude).ncd
1487 || mk_data(geometric_altitude).ncd)
1488 : mk_data(radio_altitude).ncd,
1489 FaultHandler::FAULT_MODE6_INPUTS_INVALID);
1491 if (mk->mode6_handler.conf.bank_angle_enabled)
1492 handle_input_fault(mk_data(roll_angle).ncd,
1493 FaultHandler::FAULT_BANK_ANGLE_INPUTS_INVALID);
1495 if (mk->tcf_handler.conf.enabled)
1496 handle_input_fault(mk_data(radio_altitude).ncd
1497 || mk_data(geometric_altitude).ncd
1498 || mk_data(gps_latitude).ncd
1499 || mk_data(gps_longitude).ncd
1500 || mk_data(gps_vertical_figure_of_merit).ncd,
1501 FaultHandler::FAULT_TCF_INPUTS_INVALID);
1505 MK_VIII::IOHandler::update_alternate_discrete_input (bool *ptr)
1507 assert(mk->system_handler.state == SystemHandler::STATE_ON);
1509 if (ptr == &mk_dinput(mode6_low_volume))
1511 if (mk->configuration_module.state == ConfigurationModule::STATE_OK
1512 && mk->self_test_handler.state == SelfTestHandler::STATE_NONE)
1513 mk->mode6_handler.set_volume(*ptr ? modify_amplitude(1.0, -6) : 1.0);
1515 else if (ptr == &mk_dinput(audio_inhibit))
1517 if (mk->configuration_module.state == ConfigurationModule::STATE_OK
1518 && mk->self_test_handler.state == SelfTestHandler::STATE_NONE)
1519 mk->voice_player.set_volume(*ptr ? 0.0 : mk->voice_player.conf.volume);
1524 MK_VIII::IOHandler::update_internal_latches ()
1526 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
1530 if (conf.momentary_flap_override_enabled
1531 && mk_doutput(flap_override)
1532 && ! mk->state_handler.takeoff
1533 && (mk_data(radio_altitude).ncd || mk_data(radio_altitude).get() <= 50))
1534 mk_doutput(flap_override) = false;
1537 if (momentary_steep_approach_enabled())
1539 if (mk_doutput(steep_approach)
1540 && ! mk->state_handler.takeoff
1541 && ((last_landing_gear && ! mk_dinput(landing_gear))
1542 || (last_real_flaps_down && ! real_flaps_down())))
1543 // gear or flaps raised during approach: it's a go-around
1544 mk_doutput(steep_approach) = false;
1546 last_landing_gear = mk_dinput(landing_gear);
1547 last_real_flaps_down = real_flaps_down();
1551 if (mk_doutput(glideslope_cancel)
1552 && (mk_data(glideslope_deviation_dots).ncd
1553 || mk_data(radio_altitude).ncd
1554 || mk_data(radio_altitude).get() > 2000
1555 || mk_data(radio_altitude).get() < 30))
1556 mk_doutput(glideslope_cancel) = false;
1560 MK_VIII::IOHandler::update_egpws_alert_discrete_1 ()
1562 if (mk->voice_player.voice)
1567 VoicePlayer::Voice *voice;
1569 { 11, mk_voice(sink_rate_pause_sink_rate) },
1570 { 12, mk_voice(pull_up) },
1571 { 13, mk_voice(terrain) },
1572 { 13, mk_voice(terrain_pause_terrain) },
1573 { 14, mk_voice(dont_sink_pause_dont_sink) },
1574 { 15, mk_voice(too_low_gear) },
1575 { 16, mk_voice(too_low_flaps) },
1576 { 17, mk_voice(too_low_terrain) },
1577 { 18, mk_voice(soft_glideslope) },
1578 { 18, mk_voice(hard_glideslope) },
1579 { 19, mk_voice(minimums_minimums) }
1582 for (int i = 0; i < n_elements(voices); i++)
1583 if (voices[i].voice == mk->voice_player.voice)
1585 mk_aoutput(egpws_alert_discrete_1) = 1 << voices[i].bit;
1590 mk_aoutput(egpws_alert_discrete_1) = 0;
1594 MK_VIII::IOHandler::update_egpwc_logic_discretes ()
1596 mk_aoutput(egpwc_logic_discretes) = 0;
1598 if (mk->state_handler.takeoff)
1599 mk_aoutput(egpwc_logic_discretes) |= 1 << 18;
1604 unsigned int alerts;
1606 { 13, mk_alert(TCF_TOO_LOW_TERRAIN) },
1607 { 19, mk_alert(MODE1_SINK_RATE) },
1608 { 20, mk_alert(MODE1_PULL_UP) },
1609 { 21, mk_alert(MODE2A_PREFACE) | mk_alert(MODE2B_PREFACE) | mk_alert(MODE2B_LANDING_MODE) | mk_alert(MODE2A_ALTITUDE_GAIN_TERRAIN_CLOSING) },
1610 { 22, mk_alert(MODE2A) | mk_alert(MODE2B) },
1611 { 23, mk_alert(MODE3) },
1612 { 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) },
1613 { 25, mk_alert(MODE5_SOFT) | mk_alert(MODE5_HARD) }
1616 for (int i = 0; i < n_elements(logic); i++)
1617 if (mk_test_alerts(logic[i].alerts))
1618 mk_aoutput(egpwc_logic_discretes) |= 1 << logic[i].bit;
1622 MK_VIII::IOHandler::update_mode6_callouts_discrete_1 ()
1624 if (mk->voice_player.voice)
1629 VoicePlayer::Voice *voice;
1631 { 11, mk_voice(minimums_minimums) },
1632 { 16, mk_altitude_voice(Mode6Handler::ALTITUDE_CALLOUT_10) },
1633 { 17, mk_altitude_voice(Mode6Handler::ALTITUDE_CALLOUT_20) },
1634 { 18, mk_altitude_voice(Mode6Handler::ALTITUDE_CALLOUT_30) },
1635 { 19, mk_altitude_voice(Mode6Handler::ALTITUDE_CALLOUT_40) },
1636 { 20, mk_altitude_voice(Mode6Handler::ALTITUDE_CALLOUT_50) },
1637 { 23, mk_altitude_voice(Mode6Handler::ALTITUDE_CALLOUT_100) },
1638 { 24, mk_altitude_voice(Mode6Handler::ALTITUDE_CALLOUT_200) },
1639 { 25, mk_altitude_voice(Mode6Handler::ALTITUDE_CALLOUT_300) }
1642 for (int i = 0; i < n_elements(voices); i++)
1643 if (voices[i].voice == mk->voice_player.voice)
1645 mk_aoutput(mode6_callouts_discrete_1) = 1 << voices[i].bit;
1650 mk_aoutput(mode6_callouts_discrete_1) = 0;
1654 MK_VIII::IOHandler::update_mode6_callouts_discrete_2 ()
1656 if (mk->voice_player.voice)
1661 VoicePlayer::Voice *voice;
1663 { 11, mk_altitude_voice(Mode6Handler::ALTITUDE_CALLOUT_400) },
1664 { 12, mk_altitude_voice(Mode6Handler::ALTITUDE_CALLOUT_500) },
1665 { 13, mk_altitude_voice(Mode6Handler::ALTITUDE_CALLOUT_1000) },
1666 { 18, mk_voice(bank_angle_pause_bank_angle) },
1667 { 18, mk_voice(bank_angle_pause_bank_angle_3) },
1668 { 23, mk_voice(five_hundred_above) }
1671 for (int i = 0; i < n_elements(voices); i++)
1672 if (voices[i].voice == mk->voice_player.voice)
1674 mk_aoutput(mode6_callouts_discrete_2) = 1 << voices[i].bit;
1679 mk_aoutput(mode6_callouts_discrete_2) = 0;
1683 MK_VIII::IOHandler::update_egpws_alert_discrete_2 ()
1685 mk_aoutput(egpws_alert_discrete_2) = 1 << 27; // Terrain NA
1687 if (mk_doutput(glideslope_cancel))
1688 mk_aoutput(egpws_alert_discrete_2) |= 1 << 11;
1689 if (mk_doutput(gpws_alert))
1690 mk_aoutput(egpws_alert_discrete_2) |= 1 << 12;
1691 if (mk_doutput(gpws_warning))
1692 mk_aoutput(egpws_alert_discrete_2) |= 1 << 13;
1693 if (mk_doutput(gpws_inop))
1694 mk_aoutput(egpws_alert_discrete_2) |= 1 << 14;
1695 if (mk_doutput(audio_on))
1696 mk_aoutput(egpws_alert_discrete_2) |= 1 << 16;
1697 if (mk_doutput(tad_inop))
1698 mk_aoutput(egpws_alert_discrete_2) |= 1 << 24;
1699 if (mk->fault_handler.has_faults())
1700 mk_aoutput(egpws_alert_discrete_2) |= 1 << 25;
1704 MK_VIII::IOHandler::update_egpwc_alert_discrete_3 ()
1706 mk_aoutput(egpwc_alert_discrete_3) = 1 << 17 | 1 << 18;
1708 if (mk->fault_handler.faults[FaultHandler::FAULT_MODES14_INPUTS_INVALID])
1709 mk_aoutput(egpwc_alert_discrete_3) |= 1 << 11;
1710 if (mk->fault_handler.faults[FaultHandler::FAULT_MODE5_INPUTS_INVALID])
1711 mk_aoutput(egpwc_alert_discrete_3) |= 1 << 12;
1712 if (mk->fault_handler.faults[FaultHandler::FAULT_MODE6_INPUTS_INVALID])
1713 mk_aoutput(egpwc_alert_discrete_3) |= 1 << 13;
1714 if (mk->fault_handler.faults[FaultHandler::FAULT_BANK_ANGLE_INPUTS_INVALID])
1715 mk_aoutput(egpwc_alert_discrete_3) |= 1 << 14;
1716 if (mk_doutput(tad_inop))
1717 mk_aoutput(egpwc_alert_discrete_3) |= 1 << 16;
1721 MK_VIII::IOHandler::update_outputs ()
1723 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
1726 mk_doutput(audio_on) = ! mk_dinput(audio_inhibit)
1727 && mk->voice_player.voice
1728 && ! mk->voice_player.voice->element->silence;
1730 update_egpws_alert_discrete_1();
1731 update_egpwc_logic_discretes();
1732 update_mode6_callouts_discrete_1();
1733 update_mode6_callouts_discrete_2();
1734 update_egpws_alert_discrete_2();
1735 update_egpwc_alert_discrete_3();
1739 MK_VIII::IOHandler::get_lamp_output (Lamp lamp)
1743 case LAMP_GLIDESLOPE:
1744 return &mk_doutput(gpws_alert);
1747 return conf.lamp->format2 ? &mk_doutput(gpws_alert) : &mk_doutput(gpws_warning);
1750 return &mk_doutput(gpws_warning);
1753 assert_not_reached();
1759 MK_VIII::IOHandler::update_lamps ()
1761 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
1764 if (_lamp != LAMP_NONE && conf.lamp->flashing)
1766 // [SPEC] 6.9.3: 70 cycles per minute
1767 if (lamp_timer.elapsed() >= 60.0 / 70.0 / 2.0)
1769 bool *output = get_lamp_output(_lamp);
1770 *output = ! *output;
1777 MK_VIII::IOHandler::set_lamp (Lamp lamp)
1784 mk_doutput(gpws_warning) = false;
1785 mk_doutput(gpws_alert) = false;
1787 if (lamp != LAMP_NONE)
1789 *get_lamp_output(lamp) = true;
1795 MK_VIII::IOHandler::gpws_inhibit () const
1797 return conf.gpws_inhibit_enabled && mk_dinput(gpws_inhibit);
1801 MK_VIII::IOHandler::real_flaps_down () const
1803 return conf.flap_reversal ? mk_dinput(landing_flaps) : ! mk_dinput(landing_flaps);
1807 MK_VIII::IOHandler::flaps_down () const
1809 return flap_override() ? true : real_flaps_down();
1813 MK_VIII::IOHandler::flap_override () const
1815 return conf.momentary_flap_override_enabled ? mk_doutput(flap_override) : false;
1819 MK_VIII::IOHandler::steep_approach () const
1821 if (conf.steep_approach_enabled)
1822 // If alternate action was configured in category 13, we return
1823 // the value of the input discrete (which should be connected to a
1824 // latching steep approach cockpit switch). Otherwise, we return
1825 // the value of the output discrete.
1826 return conf.alternate_steep_approach ? mk_dinput(steep_approach) : mk_doutput(steep_approach);
1832 MK_VIII::IOHandler::momentary_steep_approach_enabled () const
1834 return conf.steep_approach_enabled && ! conf.alternate_steep_approach;
1838 MK_VIII::IOHandler::tie_input (SGPropertyNode *node,
1843 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));
1845 mk->properties_handler.tie(node, (string("input-feeders/discretes/") + name).c_str(), SGRawValuePointer<bool>(feed));
1849 MK_VIII::IOHandler::tie_input (SGPropertyNode *node,
1851 Parameter<double> *input,
1854 mk->properties_handler.tie(node, (string("inputs/arinc429/") + name).c_str(), SGRawValuePointer<double>(input->get_pointer()));
1855 mk->properties_handler.tie(node, (string("inputs/arinc429/") + name + "-ncd").c_str(), SGRawValuePointer<bool>(&input->ncd));
1857 mk->properties_handler.tie(node, (string("input-feeders/arinc429/") + name).c_str(), SGRawValuePointer<bool>(feed));
1861 MK_VIII::IOHandler::tie_output (SGPropertyNode *node,
1865 SGPropertyNode *child = node->getNode((string("outputs/discretes/") + name).c_str(), true);
1867 mk->properties_handler.tie(child, SGRawValuePointer<bool>(output));
1868 child->setAttribute(SGPropertyNode::WRITE, false);
1872 MK_VIII::IOHandler::tie_output (SGPropertyNode *node,
1876 SGPropertyNode *child = node->getNode((string("outputs/arinc429/") + name).c_str(), true);
1878 mk->properties_handler.tie(child, SGRawValuePointer<int>(output));
1879 child->setAttribute(SGPropertyNode::WRITE, false);
1883 MK_VIII::IOHandler::bind (SGPropertyNode *node)
1885 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));
1887 tie_input(node, "landing-gear", &mk_dinput(landing_gear), &mk_dinput_feed(landing_gear));
1888 tie_input(node, "landing-flaps", &mk_dinput(landing_flaps), &mk_dinput_feed(landing_flaps));
1889 tie_input(node, "momentary-flap-override", &mk_dinput(momentary_flap_override));
1890 tie_input(node, "self-test", &mk_dinput(self_test));
1891 tie_input(node, "glideslope-inhibit", &mk_dinput(glideslope_inhibit), &mk_dinput_feed(glideslope_inhibit));
1892 tie_input(node, "glideslope-cancel", &mk_dinput(glideslope_cancel));
1893 tie_input(node, "decision-height", &mk_dinput(decision_height), &mk_dinput_feed(decision_height));
1894 tie_input(node, "mode6-low-volume", &mk_dinput(mode6_low_volume));
1895 tie_input(node, "audio-inhibit", &mk_dinput(audio_inhibit));
1896 tie_input(node, "ta-tcf-inhibit", &mk_dinput(ta_tcf_inhibit));
1897 tie_input(node, "autopilot-engaged", &mk_dinput(autopilot_engaged), &mk_dinput_feed(autopilot_engaged));
1898 tie_input(node, "steep-approach", &mk_dinput(steep_approach));
1899 tie_input(node, "gpws-inhibit", &mk_dinput(gpws_inhibit));
1901 tie_input(node, "uncorrected-barometric-altitude", &mk_ainput(uncorrected_barometric_altitude), &mk_ainput_feed(uncorrected_barometric_altitude));
1902 tie_input(node, "barometric-altitude-rate", &mk_ainput(barometric_altitude_rate), &mk_ainput_feed(barometric_altitude_rate));
1903 tie_input(node, "gps-altitude", &mk_ainput(gps_altitude));
1904 tie_input(node, "gps-latitude", &mk_ainput(gps_latitude));
1905 tie_input(node, "gps-longitude", &mk_ainput(gps_longitude));
1906 tie_input(node, "gps-vertical-figure-of-merit", &mk_ainput(gps_vertical_figure_of_merit));
1907 tie_input(node, "radio-altitude", &mk_ainput(radio_altitude), &mk_ainput_feed(radio_altitude));
1908 tie_input(node, "glideslope-deviation", &mk_ainput(glideslope_deviation), &mk_ainput_feed(glideslope_deviation));
1909 tie_input(node, "roll-angle", &mk_ainput(roll_angle), &mk_ainput_feed(roll_angle));
1910 tie_input(node, "localizer-deviation", &mk_ainput(localizer_deviation), &mk_ainput_feed(localizer_deviation));
1911 tie_input(node, "computed-airspeed", &mk_ainput(computed_airspeed), &mk_ainput_feed(computed_airspeed));
1912 tie_input(node, "decision-height", &mk_ainput(decision_height), &mk_ainput_feed(decision_height));
1914 tie_output(node, "gpws-warning", &mk_doutput(gpws_warning));
1915 tie_output(node, "gpws-alert", &mk_doutput(gpws_alert));
1916 tie_output(node, "audio-on", &mk_doutput(audio_on));
1917 tie_output(node, "gpws-inop", &mk_doutput(gpws_inop));
1918 tie_output(node, "tad-inop", &mk_doutput(tad_inop));
1919 tie_output(node, "flap-override", &mk_doutput(flap_override));
1920 tie_output(node, "glideslope-cancel", &mk_doutput(glideslope_cancel));
1921 tie_output(node, "steep-approach", &mk_doutput(steep_approach));
1923 tie_output(node, "egpws-alert-discrete-1", &mk_aoutput(egpws_alert_discrete_1));
1924 tie_output(node, "egpwc-logic-discretes", &mk_aoutput(egpwc_logic_discretes));
1925 tie_output(node, "mode6-callouts-discrete-1", &mk_aoutput(mode6_callouts_discrete_1));
1926 tie_output(node, "mode6-callouts-discrete-2", &mk_aoutput(mode6_callouts_discrete_2));
1927 tie_output(node, "egpws-alert-discrete-2", &mk_aoutput(egpws_alert_discrete_2));
1928 tie_output(node, "egpwc-alert-discrete-3", &mk_aoutput(egpwc_alert_discrete_3));
1932 MK_VIII::IOHandler::get_discrete_input (bool *ptr) const
1938 MK_VIII::IOHandler::set_discrete_input (bool *ptr, bool value)
1943 if (mk->system_handler.state == SystemHandler::STATE_ON)
1945 if (ptr == &mk_dinput(momentary_flap_override))
1947 if (mk->configuration_module.state == ConfigurationModule::STATE_OK
1948 && mk->self_test_handler.state == SelfTestHandler::STATE_NONE
1949 && conf.momentary_flap_override_enabled
1951 mk_doutput(flap_override) = ! mk_doutput(flap_override);
1953 else if (ptr == &mk_dinput(self_test))
1954 mk->self_test_handler.handle_button_event(value);
1955 else if (ptr == &mk_dinput(glideslope_cancel))
1957 if (mk->configuration_module.state == ConfigurationModule::STATE_OK
1958 && mk->self_test_handler.state == SelfTestHandler::STATE_NONE
1961 if (! mk_doutput(glideslope_cancel))
1965 // Although we are not called from update(), we are
1966 // sure that the inputs we use here are defined,
1967 // since state is STATE_ON.
1969 if (! mk_data(glideslope_deviation_dots).ncd
1970 && ! mk_data(radio_altitude).ncd
1971 && mk_data(radio_altitude).get() <= 2000
1972 && mk_data(radio_altitude).get() >= 30)
1973 mk_doutput(glideslope_cancel) = true;
1975 // else do nothing ([PILOT] page 22: "Glideslope Cancel
1976 // can not be deselected (reset) by again pressing the
1977 // Glideslope Cancel switch.")
1980 else if (ptr == &mk_dinput(steep_approach))
1982 if (mk->configuration_module.state == ConfigurationModule::STATE_OK
1983 && mk->self_test_handler.state == SelfTestHandler::STATE_NONE
1984 && momentary_steep_approach_enabled()
1986 mk_doutput(steep_approach) = ! mk_doutput(steep_approach);
1992 if (mk->system_handler.state == SystemHandler::STATE_ON)
1993 update_alternate_discrete_input(ptr);
1997 MK_VIII::IOHandler::present_status_section (const char *name)
1999 printf("%s\n", name);
2003 MK_VIII::IOHandler::present_status_item (const char *name, const char *value)
2006 printf("\t%-32s %s\n", name, value);
2008 printf("\t%s\n", name);
2012 MK_VIII::IOHandler::present_status_subitem (const char *name)
2014 printf("\t\t%s\n", name);
2018 MK_VIII::IOHandler::present_status ()
2022 if (mk->system_handler.state != SystemHandler::STATE_ON)
2025 present_status_section("EGPWC CONFIGURATION");
2026 present_status_item("PART NUMBER:", "965-1220-000"); // [SPEC] 1.1
2027 present_status_item("MOD STATUS:", "N/A");
2028 present_status_item("SERIAL NUMBER:", "N/A");
2030 present_status_item("APPLICATION S/W VERSION:", FLIGHTGEAR_VERSION);
2031 present_status_item("TERRAIN DATABASE VERSION:", FLIGHTGEAR_VERSION);
2032 present_status_item("ENVELOPE MOD DATABASE VERSION:", "N/A");
2033 present_status_item("BOOT CODE VERSION:", FLIGHTGEAR_VERSION);
2036 present_status_section("CURRENT FAULTS");
2037 if (mk->configuration_module.state == ConfigurationModule::STATE_OK && ! mk->fault_handler.has_faults())
2038 present_status_item("NO FAULTS");
2041 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
2043 present_status_item("GPWS COMPUTER FAULTS:");
2044 switch (mk->configuration_module.state)
2046 case ConfigurationModule::STATE_INVALID_DATABASE:
2047 present_status_subitem("APPLICATION DATABASE FAILED");
2050 case ConfigurationModule::STATE_INVALID_AIRCRAFT_TYPE:
2051 present_status_subitem("CONFIGURATION TYPE INVALID");
2055 assert_not_reached();
2061 present_status_item("GPWS COMPUTER OK");
2062 present_status_item("GPWS EXTERNAL FAULTS:");
2064 static const char *fault_names[] = {
2065 "ALL MODES INHIBIT",
2068 "MOMENTARY FLAP OVERRIDE INVALID",
2069 "SELF TEST INVALID",
2070 "GLIDESLOPE CANCEL INVALID",
2071 "STEEP APPROACH INVALID",
2074 "MODES 1-4 INPUTS INVALID",
2075 "MODE 5 INPUTS INVALID",
2076 "MODE 6 INPUTS INVALID",
2077 "BANK ANGLE INPUTS INVALID",
2078 "TCF INPUTS INVALID"
2081 for (int i = 0; i < n_elements(fault_names); i++)
2082 if (mk->fault_handler.faults[i])
2083 present_status_subitem(fault_names[i]);
2088 present_status_section("CONFIGURATION:");
2090 static const char *category_names[] = {
2093 "POSITION INPUT TYPE",
2094 "CALLOUTS OPTION TYPE",
2096 "TERRAIN DISPLAY TYPE",
2098 "RADIO ALTITUDE TYPE",
2099 "NAVIGATION INPUT TYPE",
2101 "MAGNETIC HEADING TYPE",
2102 "WINDSHEAR INPUT TYPE",
2107 for (int i = 0; i < n_elements(category_names); i++)
2109 std::ostringstream value;
2110 value << "= " << mk->configuration_module.effective_categories[i];
2111 present_status_item(category_names[i], value.str().c_str());
2116 MK_VIII::IOHandler::get_present_status () const
2122 MK_VIII::IOHandler::set_present_status (bool value)
2124 if (value) present_status();
2128 ///////////////////////////////////////////////////////////////////////////////
2129 // VoicePlayer ////////////////////////////////////////////////////////////////
2130 ///////////////////////////////////////////////////////////////////////////////
2133 MK_VIII::VoicePlayer::Speaker::bind (SGPropertyNode *node)
2135 // uses xmlsound property names
2136 tie(node, "volume", &volume);
2137 tie(node, "pitch", &pitch);
2138 tie(node, "position/x", &position[0]);
2139 tie(node, "position/y", &position[1]);
2140 tie(node, "position/z", &position[2]);
2141 tie(node, "orientation/x", &orientation[0]);
2142 tie(node, "orientation/y", &orientation[1]);
2143 tie(node, "orientation/z", &orientation[2]);
2144 tie(node, "orientation/inner-cone", &inner_cone);
2145 tie(node, "orientation/outer-cone", &outer_cone);
2146 tie(node, "reference-dist", &reference_dist);
2147 tie(node, "max-dist", &max_dist);
2151 MK_VIII::VoicePlayer::Speaker::update_configuration ()
2153 map<string, SGSoundSample *>::iterator iter;
2154 for (iter = player->samples.begin(); iter != player->samples.end(); iter++)
2156 SGSoundSample *sample = (*iter).second;
2158 sample->set_pitch(pitch);
2159 sample->set_offset_pos(position);
2160 sample->set_orientation(orientation,
2164 sample->set_reference_dist(reference_dist);
2165 sample->set_max_dist(max_dist);
2169 player->voice->volume_changed();
2172 MK_VIII::VoicePlayer::Voice::~Voice ()
2174 for (iter = elements.begin(); iter != elements.end(); iter++)
2175 delete *iter; // we owned the element
2180 MK_VIII::VoicePlayer::Voice::play ()
2182 iter = elements.begin();
2185 element->play(get_volume());
2189 MK_VIII::VoicePlayer::Voice::stop (bool now)
2193 if (now || element->silence)
2199 iter = elements.end() - 1; // stop after the current element finishes
2204 MK_VIII::VoicePlayer::Voice::set_volume (double _volume)
2211 MK_VIII::VoicePlayer::Voice::volume_changed ()
2214 element->set_volume(get_volume());
2218 MK_VIII::VoicePlayer::Voice::update ()
2222 if (! element->is_playing())
2224 if (++iter == elements.end())
2229 element->play(get_volume());
2235 MK_VIII::VoicePlayer::~VoicePlayer ()
2237 vector<Voice *>::iterator iter1;
2238 for (iter1 = _voices.begin(); iter1 != _voices.end(); iter1++)
2242 /* sound mgr already destroyed - samples already deleted
2243 map<string, SGSoundSample *>::iterator iter2;
2244 for (iter2 = samples.begin(); iter2 != samples.end(); iter2++)
2246 bool status = globals->get_soundmgr()->remove((*iter2).first);
2254 MK_VIII::VoicePlayer::init ()
2256 #define STDPAUSE 0.75 // [SPEC] 6.4.4: "the standard 0.75 second delay"
2258 make_voice(&voices.application_data_base_failed, "application-data-base-failed");
2259 make_voice(&voices.bank_angle, "bank-angle");
2260 make_voice(&voices.bank_angle_bank_angle, "bank-angle", "bank-angle");
2261 make_voice(&voices.bank_angle_bank_angle_3, "bank-angle", "bank-angle", 3.0);
2262 make_voice(&voices.bank_angle_inop, "bank-angle-inop");
2263 make_voice(&voices.bank_angle_pause_bank_angle, "bank-angle", STDPAUSE, "bank-angle");
2264 make_voice(&voices.bank_angle_pause_bank_angle_3, "bank-angle", STDPAUSE, "bank-angle", 3.0);
2265 make_voice(&voices.callouts_inop, "callouts-inop");
2266 make_voice(&voices.configuration_type_invalid, "configuration-type-invalid");
2267 make_voice(&voices.dont_sink, "dont-sink");
2268 make_voice(&voices.dont_sink_pause_dont_sink, "dont-sink", STDPAUSE, "dont-sink");
2269 make_voice(&voices.five_hundred_above, "500-above");
2270 make_voice(&voices.glideslope, "glideslope");
2271 make_voice(&voices.glideslope_inop, "glideslope-inop");
2272 make_voice(&voices.gpws_inop, "gpws-inop");
2273 make_voice(&voices.hard_glideslope, "glideslope", "glideslope", 3.0);
2274 make_voice(&voices.minimums, "minimums");
2275 make_voice(&voices.minimums_minimums, "minimums", "minimums");
2276 make_voice(&voices.pull_up, "pull-up");
2277 make_voice(&voices.sink_rate, "sink-rate");
2278 make_voice(&voices.sink_rate_pause_sink_rate, "sink-rate", STDPAUSE, "sink-rate");
2279 make_voice(&voices.soft_glideslope, new Voice::SampleElement(get_sample("glideslope"), modify_amplitude(1.0, -6)));
2280 make_voice(&voices.terrain, "terrain");
2281 make_voice(&voices.terrain_pause_terrain, "terrain", STDPAUSE, "terrain");
2282 make_voice(&voices.too_low_flaps, "too-low-flaps");
2283 make_voice(&voices.too_low_gear, "too-low-gear");
2284 make_voice(&voices.too_low_terrain, "too-low-terrain");
2286 for (int i = 0; i < n_altitude_callouts; i++)
2288 std::ostringstream name;
2289 name << "altitude-" << mk->mode6_handler.altitude_callout_definitions[i];
2290 make_voice(&voices.altitude_callouts[i], name.str().c_str());
2293 speaker.update_configuration();
2297 MK_VIII::VoicePlayer::get_sample (const char *name)
2299 std::ostringstream refname;
2300 refname << mk->name << "[" << mk->num << "]" << "/" << name;
2302 SGSoundMgr *soundmgr = globals->get_soundmgr();
2303 if (soundmgr->is_working() == false)
2308 SGSoundSample *sample = soundmgr->find(refname.str());
2311 SGPath sample_path(globals->get_fg_root());
2312 sample_path.append("Sounds/mk-viii");
2314 string filename = string(name) + ".wav";
2317 sample = new SGSoundSample(sample_path.c_str(), filename.c_str());
2319 catch (const sg_exception &e)
2321 SG_LOG(SG_INSTR, SG_ALERT, "Error loading MK VIII sound sample \"" + filename + "\": " + e.getFormattedMessage());
2325 soundmgr->add(sample, refname.str());
2326 samples[refname.str()] = sample;
2333 MK_VIII::VoicePlayer::play (Voice *_voice, unsigned int flags)
2335 if (test_bits(flags, PLAY_NOW) || ! voice || voice->element->silence)
2341 looped = test_bits(flags, PLAY_LOOPED);
2344 next_looped = false;
2350 next_voice = _voice;
2351 next_looped = test_bits(flags, PLAY_LOOPED);
2356 MK_VIII::VoicePlayer::stop (unsigned int flags)
2360 voice->stop(test_bits(flags, STOP_NOW));
2370 MK_VIII::VoicePlayer::set_volume (double _volume)
2374 voice->volume_changed();
2378 MK_VIII::VoicePlayer::update ()
2386 if (! voice->element || voice->element->silence)
2389 looped = next_looped;
2392 next_looped = false;
2399 if (! voice->element)
2410 ///////////////////////////////////////////////////////////////////////////////
2411 // SelfTestHandler ////////////////////////////////////////////////////////////
2412 ///////////////////////////////////////////////////////////////////////////////
2415 MK_VIII::SelfTestHandler::_was_here (int position)
2417 if (position > current)
2426 MK_VIII::SelfTestHandler::Action
2427 MK_VIII::SelfTestHandler::sleep (double duration)
2429 Action _action = { ACTION_SLEEP, duration, NULL };
2433 MK_VIII::SelfTestHandler::Action
2434 MK_VIII::SelfTestHandler::play (VoicePlayer::Voice *voice)
2436 mk->voice_player.play(voice);
2437 Action _action = { ACTION_VOICE, 0, NULL };
2441 MK_VIII::SelfTestHandler::Action
2442 MK_VIII::SelfTestHandler::discrete_on (bool *discrete, double duration)
2445 return sleep(duration);
2448 MK_VIII::SelfTestHandler::Action
2449 MK_VIII::SelfTestHandler::discrete_on_off (bool *discrete, double duration)
2452 Action _action = { ACTION_SLEEP | ACTION_DISCRETE_ON_OFF, duration, discrete };
2456 MK_VIII::SelfTestHandler::Action
2457 MK_VIII::SelfTestHandler::discrete_on_off (bool *discrete, VoicePlayer::Voice *voice)
2460 mk->voice_player.play(voice);
2461 Action _action = { ACTION_VOICE | ACTION_DISCRETE_ON_OFF, 0, discrete };
2465 MK_VIII::SelfTestHandler::Action
2466 MK_VIII::SelfTestHandler::done ()
2468 Action _action = { ACTION_DONE, 0, NULL };
2472 MK_VIII::SelfTestHandler::Action
2473 MK_VIII::SelfTestHandler::run ()
2475 // Note that "Terrain INOP" and "Terrain NA" are or'ed to the same
2476 // output discrete (see [SPEC] 6.9.3.5).
2478 #define was_here() was_here_offset(0)
2479 #define was_here_offset(offset) _was_here(__LINE__ * 20 + (offset))
2483 if (mk->configuration_module.state == ConfigurationModule::STATE_INVALID_DATABASE)
2484 return play(mk_voice(application_data_base_failed));
2485 else if (mk->configuration_module.state == ConfigurationModule::STATE_INVALID_AIRCRAFT_TYPE)
2486 return play(mk_voice(configuration_type_invalid));
2488 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
2492 return discrete_on(&mk_doutput(gpws_inop), 0.7);
2494 return sleep(0.7); // W/S INOP
2496 return discrete_on(&mk_doutput(tad_inop), 0.7);
2499 mk_doutput(gpws_inop) = false;
2500 // do not disable tad_inop since we must enable Terrain NA
2501 // do not disable W/S INOP since we do not emulate it
2502 return sleep(0.7); // Terrain NA
2506 mk_doutput(tad_inop) = false; // disable Terrain NA
2507 if (mk->io_handler.conf.momentary_flap_override_enabled)
2508 return discrete_on_off(&mk_doutput(flap_override), 1.0);
2511 return discrete_on_off(&mk_doutput(audio_on), 1.0);
2514 if (mk->io_handler.momentary_steep_approach_enabled())
2515 return discrete_on_off(&mk_doutput(steep_approach), 1.0);
2520 if (mk_dinput(glideslope_inhibit))
2521 goto glideslope_end;
2524 if (mk->fault_handler.faults[FaultHandler::FAULT_MODE5_INPUTS_INVALID])
2525 goto glideslope_inop;
2529 return discrete_on_off(&mk_doutput(gpws_alert), mk_voice(glideslope));
2531 return discrete_on_off(&mk_doutput(glideslope_cancel), 0.7);
2532 goto glideslope_end;
2535 return play(mk_voice(glideslope_inop));
2540 if (mk->fault_handler.faults[FaultHandler::FAULT_MODES14_INPUTS_INVALID])
2544 return discrete_on_off(&mk_doutput(gpws_warning), mk_voice(pull_up));
2548 return play(mk_voice(gpws_inop));
2553 if (mk_dinput(self_test)) // proceed to long self test
2558 if (mk->mode6_handler.conf.bank_angle_enabled
2559 && mk->fault_handler.faults[FaultHandler::FAULT_BANK_ANGLE_INPUTS_INVALID])
2560 return play(mk_voice(bank_angle_inop));
2564 if (mk->mode6_handler.altitude_callouts_enabled()
2565 && mk->fault_handler.faults[FaultHandler::FAULT_MODE6_INPUTS_INVALID])
2566 return play(mk_voice(callouts_inop));
2574 mk_doutput(gpws_inop) = true;
2575 // do not enable W/S INOP, since we do not emulate it
2576 mk_doutput(tad_inop) = true; // Terrain INOP, Terrain NA
2578 return play(mk_voice(sink_rate));
2581 return play(mk_voice(pull_up));
2583 return play(mk_voice(terrain));
2585 return play(mk_voice(pull_up));
2587 return play(mk_voice(dont_sink));
2589 return play(mk_voice(too_low_terrain));
2591 return play(mk->mode4_handler.conf.voice_too_low_gear);
2593 return play(mk_voice(too_low_flaps));
2595 return play(mk_voice(too_low_terrain));
2597 return play(mk_voice(glideslope));
2600 if (mk->mode6_handler.conf.bank_angle_enabled)
2601 return play(mk_voice(bank_angle));
2606 if (! mk->mode6_handler.altitude_callouts_enabled())
2607 goto callouts_disabled;
2611 if (mk->mode6_handler.conf.minimums_enabled)
2612 return play(mk_voice(minimums));
2616 if (mk->mode6_handler.conf.above_field_voice)
2617 return play(mk->mode6_handler.conf.above_field_voice);
2619 for (int i = 0; i < n_altitude_callouts; i++)
2620 if (! was_here_offset(i))
2622 if (mk->mode6_handler.conf.altitude_callouts_enabled[i])
2623 return play(mk_altitude_voice(i));
2627 if (mk->mode6_handler.conf.smart_500_enabled)
2628 return play(mk_altitude_voice(Mode6Handler::ALTITUDE_CALLOUT_500));
2633 return play(mk_voice(minimums_minimums));
2638 if (mk->tcf_handler.conf.enabled)
2639 return play(mk_voice(too_low_terrain));
2646 MK_VIII::SelfTestHandler::start ()
2648 assert(state == STATE_START);
2650 memcpy(&saved_outputs, &mk->io_handler.outputs, sizeof(mk->io_handler.outputs));
2651 memset(&mk->io_handler.outputs, 0, sizeof(mk->io_handler.outputs));
2653 // [SPEC] 6.10.6: "The self-test results are annunciated, at 6db
2654 // lower than the normal audio level selected for the aircraft"
2655 mk->voice_player.set_volume(modify_amplitude(mk->voice_player.conf.volume, -6));
2657 if (mk_dinput(mode6_low_volume))
2658 mk->mode6_handler.set_volume(1.0);
2660 state = STATE_RUNNING;
2661 cancel = CANCEL_NONE;
2662 memset(&action, 0, sizeof(action));
2667 MK_VIII::SelfTestHandler::stop ()
2669 if (state != STATE_NONE)
2671 if (state != STATE_START)
2673 mk->voice_player.stop(VoicePlayer::STOP_NOW);
2674 mk->voice_player.set_volume(mk_dinput(audio_inhibit) ? 0.0 : mk->voice_player.conf.volume);
2676 if (mk_dinput(mode6_low_volume))
2677 mk->mode6_handler.set_volume(modify_amplitude(1.0, -6));
2679 memcpy(&mk->io_handler.outputs, &saved_outputs, sizeof(mk->io_handler.outputs));
2682 button_pressed = false;
2688 MK_VIII::SelfTestHandler::handle_button_event (bool value)
2690 if (state == STATE_NONE)
2693 state = STATE_START;
2695 else if (state == STATE_START)
2698 state = STATE_NONE; // cancel the not-yet-started test
2700 else if (cancel == CANCEL_NONE)
2704 assert(! button_pressed);
2705 button_pressed = true;
2706 button_press_timestamp = globals->get_sim_time_sec();
2712 if (globals->get_sim_time_sec() - button_press_timestamp < 2)
2713 cancel = CANCEL_SHORT;
2715 cancel = CANCEL_LONG;
2722 MK_VIII::SelfTestHandler::update ()
2724 if (state == STATE_NONE)
2726 else if (state == STATE_START)
2728 if (mk->state_handler.ground && ! mk->io_handler.steep_approach())
2738 if (button_pressed && cancel == CANCEL_NONE)
2740 if (globals->get_sim_time_sec() - button_press_timestamp >= 2)
2741 cancel = CANCEL_LONG;
2745 if (! mk->state_handler.ground || cancel != CANCEL_NONE)
2751 if (test_bits(action.flags, ACTION_SLEEP))
2753 if (globals->get_sim_time_sec() - sleep_start < action.sleep_duration)
2756 if (test_bits(action.flags, ACTION_VOICE))
2758 if (mk->voice_player.voice)
2761 if (test_bits(action.flags, ACTION_DISCRETE_ON_OFF))
2762 *action.discrete = false;
2766 if (test_bits(action.flags, ACTION_SLEEP))
2767 sleep_start = globals->get_sim_time_sec();
2768 if (test_bits(action.flags, ACTION_DONE))
2777 ///////////////////////////////////////////////////////////////////////////////
2778 // AlertHandler ///////////////////////////////////////////////////////////////
2779 ///////////////////////////////////////////////////////////////////////////////
2782 MK_VIII::AlertHandler::select_voice_alerts (unsigned int test)
2784 if (has_alerts(test))
2786 voice_alerts = alerts & test;
2791 voice_alerts &= ~test;
2792 if (voice_alerts == 0)
2793 mk->voice_player.stop();
2800 MK_VIII::AlertHandler::boot ()
2806 MK_VIII::AlertHandler::reposition ()
2810 mk->io_handler.set_lamp(IOHandler::LAMP_NONE);
2811 mk->voice_player.stop(VoicePlayer::STOP_NOW);
2815 MK_VIII::AlertHandler::reset ()
2820 repeated_alerts = 0;
2821 altitude_callout_voice = NULL;
2825 MK_VIII::AlertHandler::update ()
2827 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
2830 // Lamps and voices are prioritized according to [SPEC] 6.9.2.
2832 // Voices which can interrupt other voices (VoicePlayer::PLAY_NOW)
2833 // are specified by [INSTALL] appendix E 5.3.5.
2835 // When a voice is overriden by a higher priority voice and the
2836 // overriding voice stops, we restore the previous voice if it was
2837 // looped (this is not specified by [SPEC] but seems to make sense).
2841 if (has_alerts(ALERT_MODE1_PULL_UP | ALERT_MODE2A | ALERT_MODE2B))
2842 mk->io_handler.set_lamp(IOHandler::LAMP_WARNING);
2843 else if (has_alerts(ALERT_MODE1_SINK_RATE
2844 | ALERT_MODE2A_PREFACE
2845 | ALERT_MODE2B_PREFACE
2846 | ALERT_MODE2A_ALTITUDE_GAIN_TERRAIN_CLOSING
2847 | ALERT_MODE2A_ALTITUDE_GAIN
2848 | ALERT_MODE2B_LANDING_MODE
2850 | ALERT_MODE4_TOO_LOW_FLAPS
2851 | ALERT_MODE4_TOO_LOW_GEAR
2852 | ALERT_MODE4AB_TOO_LOW_TERRAIN
2853 | ALERT_MODE4C_TOO_LOW_TERRAIN
2854 | ALERT_TCF_TOO_LOW_TERRAIN))
2855 mk->io_handler.set_lamp(IOHandler::LAMP_CAUTION);
2856 else if (has_alerts(ALERT_MODE5_SOFT | ALERT_MODE5_HARD))
2857 mk->io_handler.set_lamp(IOHandler::LAMP_GLIDESLOPE);
2859 mk->io_handler.set_lamp(IOHandler::LAMP_NONE);
2863 if (select_voice_alerts(ALERT_MODE1_PULL_UP))
2865 if (! has_old_alerts(ALERT_MODE1_PULL_UP))
2867 if (mk->voice_player.voice != mk_voice(sink_rate_pause_sink_rate))
2868 mk->voice_player.play(mk_voice(sink_rate), VoicePlayer::PLAY_NOW);
2869 mk->voice_player.play(mk_voice(pull_up), VoicePlayer::PLAY_LOOPED);
2872 else if (select_voice_alerts(ALERT_MODE2A_PREFACE | ALERT_MODE2B_PREFACE))
2874 if (! has_old_alerts(ALERT_MODE2A_PREFACE | ALERT_MODE2B_PREFACE))
2875 mk->voice_player.play(mk_voice(terrain_pause_terrain), VoicePlayer::PLAY_NOW);
2877 else if (select_voice_alerts(ALERT_MODE2A | ALERT_MODE2B))
2879 if (mk->voice_player.voice != mk_voice(pull_up))
2880 mk->voice_player.play(mk_voice(pull_up), VoicePlayer::PLAY_NOW | VoicePlayer::PLAY_LOOPED);
2882 else if (select_voice_alerts(ALERT_MODE2A_ALTITUDE_GAIN_TERRAIN_CLOSING | ALERT_MODE2B_LANDING_MODE))
2884 if (mk->voice_player.voice != mk_voice(terrain))
2885 mk->voice_player.play(mk_voice(terrain), VoicePlayer::PLAY_LOOPED);
2887 else if (select_voice_alerts(ALERT_MODE6_MINIMUMS))
2889 if (! has_old_alerts(ALERT_MODE6_MINIMUMS))
2890 mk->voice_player.play(mk_voice(minimums_minimums));
2892 else if (select_voice_alerts(ALERT_MODE4AB_TOO_LOW_TERRAIN | ALERT_MODE4C_TOO_LOW_TERRAIN))
2894 if (must_play_voice(ALERT_MODE4AB_TOO_LOW_TERRAIN | ALERT_MODE4C_TOO_LOW_TERRAIN))
2895 mk->voice_player.play(mk_voice(too_low_terrain));
2897 else if (select_voice_alerts(ALERT_TCF_TOO_LOW_TERRAIN))
2899 if (must_play_voice(ALERT_TCF_TOO_LOW_TERRAIN))
2900 mk->voice_player.play(mk_voice(too_low_terrain));
2902 else if (select_voice_alerts(ALERT_MODE6_ALTITUDE_CALLOUT))
2904 if (! has_old_alerts(ALERT_MODE6_ALTITUDE_CALLOUT))
2906 assert(altitude_callout_voice != NULL);
2907 mk->voice_player.play(altitude_callout_voice);
2910 else if (select_voice_alerts(ALERT_MODE4_TOO_LOW_GEAR))
2912 if (must_play_voice(ALERT_MODE4_TOO_LOW_GEAR))
2913 mk->voice_player.play(mk->mode4_handler.conf.voice_too_low_gear);
2915 else if (select_voice_alerts(ALERT_MODE4_TOO_LOW_FLAPS))
2917 if (must_play_voice(ALERT_MODE4_TOO_LOW_FLAPS))
2918 mk->voice_player.play(mk_voice(too_low_flaps));
2920 else if (select_voice_alerts(ALERT_MODE1_SINK_RATE))
2922 if (must_play_voice(ALERT_MODE1_SINK_RATE))
2923 mk->voice_player.play(mk_voice(sink_rate_pause_sink_rate));
2924 // [SPEC] 6.2.1: "During the time that the voice message for the
2925 // outer envelope is inhibited and the caution/warning lamp is
2926 // on, the Mode 5 alert message is allowed to annunciate for
2927 // excessive glideslope deviation conditions."
2928 else if (mk->voice_player.voice != mk_voice(sink_rate_pause_sink_rate)
2929 && mk->voice_player.next_voice != mk_voice(sink_rate_pause_sink_rate))
2931 if (has_alerts(ALERT_MODE5_HARD))
2933 voice_alerts |= ALERT_MODE5_HARD;
2934 if (mk->voice_player.voice != mk_voice(hard_glideslope))
2935 mk->voice_player.play(mk_voice(hard_glideslope), VoicePlayer::PLAY_LOOPED);
2937 else if (has_alerts(ALERT_MODE5_SOFT))
2939 voice_alerts |= ALERT_MODE5_SOFT;
2940 if (must_play_voice(ALERT_MODE5_SOFT))
2941 mk->voice_player.play(mk_voice(soft_glideslope));
2945 else if (select_voice_alerts(ALERT_MODE3))
2947 if (must_play_voice(ALERT_MODE3))
2948 mk->voice_player.play(mk_voice(dont_sink_pause_dont_sink));
2950 else if (select_voice_alerts(ALERT_MODE5_HARD))
2952 if (mk->voice_player.voice != mk_voice(hard_glideslope))
2953 mk->voice_player.play(mk_voice(hard_glideslope), VoicePlayer::PLAY_LOOPED);
2955 else if (select_voice_alerts(ALERT_MODE5_SOFT))
2957 if (must_play_voice(ALERT_MODE5_SOFT))
2958 mk->voice_player.play(mk_voice(soft_glideslope));
2960 else if (select_voice_alerts(ALERT_MODE6_LOW_BANK_ANGLE_3))
2962 if (mk->voice_player.voice != mk_voice(bank_angle_bank_angle_3))
2963 mk->voice_player.play(mk_voice(bank_angle_bank_angle_3), VoicePlayer::PLAY_LOOPED);
2965 else if (select_voice_alerts(ALERT_MODE6_HIGH_BANK_ANGLE_3))
2967 if (mk->voice_player.voice != mk_voice(bank_angle_pause_bank_angle_3))
2968 mk->voice_player.play(mk_voice(bank_angle_pause_bank_angle_3), VoicePlayer::PLAY_LOOPED);
2970 else if (select_voice_alerts(ALERT_MODE6_LOW_BANK_ANGLE_2))
2972 if (! has_old_alerts(ALERT_MODE6_LOW_BANK_ANGLE_2 | ALERT_MODE6_HIGH_BANK_ANGLE_2))
2973 mk->voice_player.play(mk_voice(bank_angle_bank_angle));
2975 else if (select_voice_alerts(ALERT_MODE6_HIGH_BANK_ANGLE_2))
2977 if (! has_old_alerts(ALERT_MODE6_LOW_BANK_ANGLE_2 | ALERT_MODE6_HIGH_BANK_ANGLE_2))
2978 mk->voice_player.play(mk_voice(bank_angle_pause_bank_angle));
2980 else if (select_voice_alerts(ALERT_MODE6_LOW_BANK_ANGLE_1))
2982 if (! has_old_alerts(ALERT_MODE6_LOW_BANK_ANGLE_1 | ALERT_MODE6_HIGH_BANK_ANGLE_1))
2983 mk->voice_player.play(mk_voice(bank_angle_bank_angle));
2985 else if (select_voice_alerts(ALERT_MODE6_HIGH_BANK_ANGLE_1))
2987 if (! has_old_alerts(ALERT_MODE6_LOW_BANK_ANGLE_1 | ALERT_MODE6_HIGH_BANK_ANGLE_1))
2988 mk->voice_player.play(mk_voice(bank_angle_pause_bank_angle));
2993 old_alerts = alerts;
2994 repeated_alerts = 0;
2995 altitude_callout_voice = NULL;
2999 MK_VIII::AlertHandler::set_alerts (unsigned int _alerts,
3001 VoicePlayer::Voice *_altitude_callout_voice)
3004 if (test_bits(flags, ALERT_FLAG_REPEAT))
3005 repeated_alerts |= _alerts;
3006 if (_altitude_callout_voice)
3007 altitude_callout_voice = _altitude_callout_voice;
3011 MK_VIII::AlertHandler::unset_alerts (unsigned int _alerts)
3014 repeated_alerts &= ~_alerts;
3017 ///////////////////////////////////////////////////////////////////////////////
3018 // StateHandler ///////////////////////////////////////////////////////////////
3019 ///////////////////////////////////////////////////////////////////////////////
3022 MK_VIII::StateHandler::update_ground ()
3026 if (! mk_ainput(computed_airspeed).ncd && mk_ainput(computed_airspeed).get() > 60
3027 && ! mk_data(radio_altitude).ncd && mk_data(radio_altitude).get() > 30)
3029 if (potentially_airborne_timer.start_or_elapsed() > 1)
3033 potentially_airborne_timer.stop();
3037 if (! mk_ainput(computed_airspeed).ncd && mk_ainput(computed_airspeed).get() < 40
3038 && ! mk_data(radio_altitude).ncd && mk_data(radio_altitude).get() < 30)
3044 MK_VIII::StateHandler::enter_ground ()
3047 mk->io_handler.enter_ground();
3049 // [SPEC] 6.0.1 does not specify this, but it seems to be an
3050 // omission; at this point, we must make sure that we are in takeoff
3051 // mode (otherwise the next takeoff might happen in approach mode).
3057 MK_VIII::StateHandler::leave_ground ()
3060 mk->mode2_handler.leave_ground();
3064 MK_VIII::StateHandler::update_takeoff ()
3068 // [SPEC] 6.0.2 is somewhat confusing: it mentions hardcoded
3069 // terrain clearance (500, 750) and airspeed (178, 200) values,
3070 // but it also mentions the mode 4A boundary, which varies as a
3071 // function of aircraft type. We consider that the mentioned
3072 // values are examples, and that we should use the mode 4A upper
3075 if (! mk_data(terrain_clearance).ncd
3076 && ! mk_ainput(computed_airspeed).ncd
3077 && mk_data(terrain_clearance).get() > mk->mode4_handler.get_upper_agl(mk->mode4_handler.conf.modes->ac))
3082 if (! mk_data(radio_altitude).ncd
3083 && mk_data(radio_altitude).get() < mk->mode4_handler.conf.modes->b->min_agl1
3084 && mk->io_handler.flaps_down()
3085 && mk_dinput(landing_gear))
3091 MK_VIII::StateHandler::enter_takeoff ()
3094 mk->io_handler.enter_takeoff();
3095 mk->mode2_handler.enter_takeoff();
3096 mk->mode3_handler.enter_takeoff();
3097 mk->mode6_handler.enter_takeoff();
3101 MK_VIII::StateHandler::leave_takeoff ()
3104 mk->mode6_handler.leave_takeoff();
3108 MK_VIII::StateHandler::post_reposition ()
3110 // Synchronize the state with the current situation.
3113 (! mk_ainput(computed_airspeed).ncd && mk_ainput(computed_airspeed).get() > 60
3114 && ! mk_data(radio_altitude).ncd && mk_data(radio_altitude).get() > 30);
3116 bool _takeoff = _ground;
3118 if (ground && ! _ground)
3120 else if (! ground && _ground)
3123 if (takeoff && ! _takeoff)
3125 else if (! takeoff && _takeoff)
3130 MK_VIII::StateHandler::update ()
3132 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
3139 ///////////////////////////////////////////////////////////////////////////////
3140 // Mode1Handler ///////////////////////////////////////////////////////////////
3141 ///////////////////////////////////////////////////////////////////////////////
3144 MK_VIII::Mode1Handler::get_pull_up_bias ()
3146 // [PILOT] page 54: "Steep Approach has priority over Flap Override
3147 // if selected simultaneously."
3149 if (mk->io_handler.steep_approach())
3151 else if (conf.envelopes->flap_override_bias && mk->io_handler.flap_override())
3158 MK_VIII::Mode1Handler::is_pull_up ()
3160 if (! mk->io_handler.gpws_inhibit()
3161 && ! mk->state_handler.ground // [1]
3162 && ! mk_data(radio_altitude).ncd
3163 && ! mk_data(barometric_altitude_rate).ncd
3164 && mk_data(radio_altitude).get() > conf.envelopes->min_agl)
3166 if (mk_data(radio_altitude).get() < conf.envelopes->pull_up_max_agl1)
3168 if (mk_data(radio_altitude).get() < conf.envelopes->pull_up_min_agl1(mk_data(barometric_altitude_rate).get() + get_pull_up_bias()))
3171 else if (mk_data(radio_altitude).get() < conf.envelopes->pull_up_max_agl2)
3173 if (mk_data(radio_altitude).get() < conf.envelopes->pull_up_min_agl2(mk_data(barometric_altitude_rate).get() + get_pull_up_bias()))
3182 MK_VIII::Mode1Handler::update_pull_up ()
3186 if (! mk_test_alert(MODE1_PULL_UP))
3188 // [SPEC] 6.2.1: at least one sink rate must be issued
3189 // before switching to pull up; if no sink rate has
3190 // occurred, a 0.2 second delay is used.
3191 if (mk->voice_player.voice == mk_voice(sink_rate_pause_sink_rate)
3192 || pull_up_timer.start_or_elapsed() >= 0.2)
3193 mk_set_alerts(mk_alert(MODE1_PULL_UP));
3198 pull_up_timer.stop();
3199 mk_unset_alerts(mk_alert(MODE1_PULL_UP));
3204 MK_VIII::Mode1Handler::get_sink_rate_bias ()
3206 // [PILOT] page 54: "Steep Approach has priority over Flap Override
3207 // if selected simultaneously."
3209 if (mk->io_handler.steep_approach())
3211 else if (conf.envelopes->flap_override_bias && mk->io_handler.flap_override())
3213 else if (! mk_data(glideslope_deviation_dots).ncd)
3217 if (mk_data(glideslope_deviation_dots).get() <= -2)
3219 else if (mk_data(glideslope_deviation_dots).get() < 0)
3220 bias = -150 * mk_data(glideslope_deviation_dots).get();
3222 if (mk_data(radio_altitude).get() < 100)
3223 bias *= 0.01 * mk_data(radio_altitude).get();
3232 MK_VIII::Mode1Handler::is_sink_rate ()
3234 return ! mk->io_handler.gpws_inhibit()
3235 && ! mk->state_handler.ground // [1]
3236 && ! mk_data(radio_altitude).ncd
3237 && ! mk_data(barometric_altitude_rate).ncd
3238 && mk_data(radio_altitude).get() > conf.envelopes->min_agl
3239 && mk_data(radio_altitude).get() < 2450
3240 && mk_data(radio_altitude).get() < -572 - 0.6035 * (mk_data(barometric_altitude_rate).get() + get_sink_rate_bias());
3244 MK_VIII::Mode1Handler::get_sink_rate_tti ()
3246 return mk_data(radio_altitude).get() / fabs(mk_data(barometric_altitude_rate).get());
3250 MK_VIII::Mode1Handler::update_sink_rate ()
3254 if (mk_test_alert(MODE1_SINK_RATE))
3256 double tti = get_sink_rate_tti();
3257 if (tti <= sink_rate_tti * 0.8)
3259 // ~20% degradation, warn again and store the new reference tti
3260 sink_rate_tti = tti;
3261 mk_repeat_alert(mk_alert(MODE1_SINK_RATE));
3266 if (sink_rate_timer.start_or_elapsed() >= 0.8)
3268 mk_set_alerts(mk_alert(MODE1_SINK_RATE));
3269 sink_rate_tti = get_sink_rate_tti();
3275 sink_rate_timer.stop();
3276 mk_unset_alerts(mk_alert(MODE1_SINK_RATE));
3281 MK_VIII::Mode1Handler::update ()
3283 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
3290 ///////////////////////////////////////////////////////////////////////////////
3291 // Mode2Handler ///////////////////////////////////////////////////////////////
3292 ///////////////////////////////////////////////////////////////////////////////
3295 MK_VIII::Mode2Handler::ClosureRateFilter::limit_radio_altitude_rate (double r)
3297 // Limit radio altitude rate according to aircraft configuration,
3298 // allowing maximum sensitivity during cruise while providing
3299 // progressively less sensitivity during the landing phases of
3302 if (! mk_data(glideslope_deviation_dots).ncd && fabs(mk_data(glideslope_deviation_dots).get()) <= 2)
3303 { // gs deviation <= +- 2 dots
3304 if (mk_dinput(landing_gear) && mk->io_handler.flaps_down())
3305 SG_CLAMP_RANGE(r, -1000.0, 3000.0);
3306 else if (mk_dinput(landing_gear) || mk->io_handler.flaps_down())
3307 SG_CLAMP_RANGE(r, 0.0, 4000.0);
3309 SG_CLAMP_RANGE(r, 1000.0, 5000.0);
3312 { // no ILS, or gs deviation > +- 2 dots
3313 if (mk_dinput(landing_gear) && mk->io_handler.flaps_down())
3314 SG_CLAMP_RANGE(r, 0.0, 4000.0);
3315 else if (mk_dinput(landing_gear) || mk->io_handler.flaps_down())
3316 SG_CLAMP_RANGE(r, 1000.0, 5000.0);
3324 MK_VIII::Mode2Handler::ClosureRateFilter::init ()
3327 last_ra.set(&mk_data(radio_altitude));
3328 last_ba.set(&mk_ainput(uncorrected_barometric_altitude));
3335 MK_VIII::Mode2Handler::ClosureRateFilter::update ()
3337 double elapsed = timer.start_or_elapsed();
3341 if (! mk_data(radio_altitude).ncd && ! mk_ainput(uncorrected_barometric_altitude).ncd)
3343 if (! last_ra.ncd && ! last_ba.ncd)
3345 // compute radio and barometric altitude rates (positive value = descent)
3346 double ra_rate = -(mk_data(radio_altitude).get() - last_ra.get()) / elapsed * 60;
3347 double ba_rate = -(mk_ainput(uncorrected_barometric_altitude).get() - last_ba.get()) / elapsed * 60;
3349 // limit radio altitude rate according to aircraft configuration
3350 ra_rate = limit_radio_altitude_rate(ra_rate);
3352 // apply a low-pass filter to the radio altitude rate
3353 ra_rate = ra_filter.filter(ra_rate);
3354 // apply a high-pass filter to the barometric altitude rate
3355 ba_rate = ba_filter.filter(ba_rate);
3357 // combine both rates to obtain a closure rate
3358 output.set(ra_rate + ba_rate);
3371 last_ra.set(&mk_data(radio_altitude));
3372 last_ba.set(&mk_ainput(uncorrected_barometric_altitude));
3376 MK_VIII::Mode2Handler::b_conditions ()
3378 return mk->io_handler.flaps_down()
3379 || (! mk_data(glideslope_deviation_dots).ncd && fabs(mk_data(glideslope_deviation_dots).get()) < 2)
3380 || takeoff_timer.running;
3384 MK_VIII::Mode2Handler::is_a ()
3386 if (! mk->io_handler.gpws_inhibit()
3387 && ! mk->state_handler.ground // [1]
3388 && ! mk_data(radio_altitude).ncd
3389 && ! mk_ainput(computed_airspeed).ncd
3390 && ! closure_rate_filter.output.ncd
3391 && ! b_conditions())
3393 if (mk_data(radio_altitude).get() < 1220)
3395 if (mk_data(radio_altitude).get() < -1579 + 0.7895 * closure_rate_filter.output.get())
3402 if (mk_ainput(computed_airspeed).get() <= conf->airspeed1)
3404 else if (mk_ainput(computed_airspeed).get() >= conf->airspeed2)
3407 upper_limit = 1650 + 8.9 * (mk_ainput(computed_airspeed).get() - conf->airspeed1);
3409 if (mk_data(radio_altitude).get() < upper_limit)
3411 if (mk_data(radio_altitude).get() < 522 + 0.1968 * closure_rate_filter.output.get())
3421 MK_VIII::Mode2Handler::is_b ()
3423 if (! mk->io_handler.gpws_inhibit()
3424 && ! mk->state_handler.ground // [1]
3425 && ! mk_data(radio_altitude).ncd
3426 && ! mk_data(barometric_altitude_rate).ncd
3427 && ! closure_rate_filter.output.ncd
3429 && mk_data(radio_altitude).get() < 789)
3433 if (mk->io_handler.flaps_down())
3435 if (mk_data(barometric_altitude_rate).get() > -400)
3437 else if (mk_data(barometric_altitude_rate).get() < -1000)
3440 lower_limit = -66.777 - 0.667 * mk_data(barometric_altitude_rate).get();
3445 if (mk_data(radio_altitude).get() > lower_limit)
3447 if (mk_data(radio_altitude).get() < -1579 + 0.7895 * closure_rate_filter.output.get())
3456 MK_VIII::Mode2Handler::check_pull_up (unsigned int preface_alert,
3459 if (pull_up_timer.running)
3461 if (pull_up_timer.elapsed() >= 1)
3463 mk_unset_alerts(preface_alert);
3464 mk_set_alerts(alert);
3469 if (! mk->voice_player.voice)
3470 pull_up_timer.start();
3475 MK_VIII::Mode2Handler::update_a ()
3479 if (mk_test_alert(MODE2A_PREFACE))
3480 check_pull_up(mk_alert(MODE2A_PREFACE), mk_alert(MODE2A));
3481 else if (! mk_test_alert(MODE2A))
3483 mk_unset_alerts(mk_alert(MODE2A_ALTITUDE_GAIN) | mk_alert(MODE2A_ALTITUDE_GAIN_TERRAIN_CLOSING));
3484 mk_set_alerts(mk_alert(MODE2A_PREFACE));
3485 a_start_time = globals->get_sim_time_sec();
3486 pull_up_timer.stop();
3491 if (mk_test_alert(MODE2A_ALTITUDE_GAIN))
3493 if (mk->io_handler.gpws_inhibit()
3494 || mk->state_handler.ground // [1]
3495 || a_altitude_gain_timer.elapsed() >= 45
3496 || mk_data(radio_altitude).ncd
3497 || mk_ainput(uncorrected_barometric_altitude).ncd
3498 || mk_ainput(uncorrected_barometric_altitude).get() - a_altitude_gain_alt >= 300
3499 // [PILOT] page 12: "the visual alert will remain on
3500 // until [...] landing flaps or the flap override switch
3502 || mk->io_handler.flaps_down())
3504 // exit altitude gain mode
3505 a_altitude_gain_timer.stop();
3506 mk_unset_alerts(mk_alert(MODE2A_ALTITUDE_GAIN) | mk_alert(MODE2A_ALTITUDE_GAIN_TERRAIN_CLOSING));
3510 // [SPEC] 6.2.2.2: "If the terrain starts to fall away
3511 // during this altitude gain time, the voice will shut
3513 if (closure_rate_filter.output.get() < 0)
3514 mk_unset_alerts(mk_alert(MODE2A_ALTITUDE_GAIN_TERRAIN_CLOSING));
3517 else if (mk_test_alerts(mk_alert(MODE2A_PREFACE) | mk_alert(MODE2A)))
3519 mk_unset_alerts(mk_alert(MODE2A_PREFACE) | mk_alert(MODE2A));
3521 if (! mk->io_handler.gpws_inhibit()
3522 && ! mk->state_handler.ground // [1]
3523 && globals->get_sim_time_sec() - a_start_time > 3
3524 && ! mk->io_handler.flaps_down()
3525 && ! mk_data(radio_altitude).ncd
3526 && ! mk_ainput(uncorrected_barometric_altitude).ncd)
3528 // [SPEC] 6.2.2.2: mode 2A envelope violated for more
3529 // than 3 seconds, enable altitude gain feature
3531 a_altitude_gain_timer.start();
3532 a_altitude_gain_alt = mk_ainput(uncorrected_barometric_altitude).get();
3534 unsigned int alerts = mk_alert(MODE2A_ALTITUDE_GAIN);
3535 if (closure_rate_filter.output.get() > 0)
3536 alerts |= mk_alert(MODE2A_ALTITUDE_GAIN_TERRAIN_CLOSING);
3538 mk_set_alerts(alerts);
3545 MK_VIII::Mode2Handler::update_b ()
3549 // handle normal mode
3551 if (b && (! mk_dinput(landing_gear) || ! mk->io_handler.flaps_down()))
3553 if (mk_test_alert(MODE2B_PREFACE))
3554 check_pull_up(mk_alert(MODE2B_PREFACE), mk_alert(MODE2B));
3555 else if (! mk_test_alert(MODE2B))
3557 mk_set_alerts(mk_alert(MODE2B_PREFACE));
3558 pull_up_timer.stop();
3562 mk_unset_alerts(mk_alert(MODE2B_PREFACE) | mk_alert(MODE2B));
3564 // handle landing mode ([SPEC] 6.2.2.3: "If both landing gear and
3565 // flaps are in the landing configuration, then the message will be
3568 if (b && mk_dinput(landing_gear) && mk->io_handler.flaps_down())
3569 mk_set_alerts(mk_alert(MODE2B_LANDING_MODE));
3571 mk_unset_alerts(mk_alert(MODE2B_LANDING_MODE));
3575 MK_VIII::Mode2Handler::boot ()
3577 closure_rate_filter.init();
3581 MK_VIII::Mode2Handler::power_off ()
3583 // [SPEC] 6.0.4: "This latching function is not power saved and a
3584 // system reset will force it false."
3585 takeoff_timer.stop();
3589 MK_VIII::Mode2Handler::leave_ground ()
3591 // takeoff, reset timer
3592 takeoff_timer.start();
3596 MK_VIII::Mode2Handler::enter_takeoff ()
3598 // reset timer, in case it's a go-around
3599 takeoff_timer.start();
3603 MK_VIII::Mode2Handler::update ()
3605 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
3608 closure_rate_filter.update();
3610 if (takeoff_timer.running && takeoff_timer.elapsed() >= 60)
3611 takeoff_timer.stop();
3617 ///////////////////////////////////////////////////////////////////////////////
3618 // Mode3Handler ///////////////////////////////////////////////////////////////
3619 ///////////////////////////////////////////////////////////////////////////////
3622 MK_VIII::Mode3Handler::max_alt_loss (double _bias)
3624 return conf->max_alt_loss(mk->io_handler.flap_override(), mk_data(radio_altitude).get()) + mk_data(radio_altitude).get() * _bias;
3628 MK_VIII::Mode3Handler::get_bias (double initial_bias, double alt_loss)
3630 if (mk_data(radio_altitude).get() > 0)
3631 while (alt_loss > max_alt_loss(initial_bias))
3632 initial_bias += 0.2;
3634 return initial_bias;
3638 MK_VIII::Mode3Handler::is (double *alt_loss)
3640 if (has_descent_alt)
3642 int max_agl = conf->max_agl(mk->io_handler.flap_override());
3644 if (mk_ainput(uncorrected_barometric_altitude).ncd
3645 || mk_ainput(uncorrected_barometric_altitude).get() > descent_alt
3646 || mk_data(radio_altitude).ncd
3647 || mk_data(radio_altitude).get() > max_agl)
3650 has_descent_alt = false;
3654 // gear/flaps: [SPEC] 1.3.1.3
3655 if (! mk->io_handler.gpws_inhibit()
3656 && ! mk->state_handler.ground // [1]
3657 && (! mk_dinput(landing_gear) || ! mk->io_handler.flaps_down())
3658 && ! mk_data(barometric_altitude_rate).ncd
3659 && ! mk_ainput(uncorrected_barometric_altitude).ncd
3660 && ! mk_data(radio_altitude).ncd
3661 && mk_data(barometric_altitude_rate).get() < 0)
3663 double _alt_loss = descent_alt - mk_ainput(uncorrected_barometric_altitude).get();
3664 if (armed || (mk_data(radio_altitude).get() > conf->min_agl
3665 && mk_data(radio_altitude).get() < max_agl
3666 && _alt_loss > max_alt_loss(0)))
3668 *alt_loss = _alt_loss;
3676 if (! mk_data(barometric_altitude_rate).ncd
3677 && ! mk_ainput(uncorrected_barometric_altitude).ncd
3678 && mk_data(barometric_altitude_rate).get() < 0)
3680 has_descent_alt = true;
3681 descent_alt = mk_ainput(uncorrected_barometric_altitude).get();
3689 MK_VIII::Mode3Handler::enter_takeoff ()
3692 has_descent_alt = false;
3696 MK_VIII::Mode3Handler::update ()
3698 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
3701 if (mk->state_handler.takeoff)
3705 if (! mk->state_handler.ground /* [1] */ && is(&alt_loss))
3707 if (mk_test_alert(MODE3))
3709 double new_bias = get_bias(bias, alt_loss);
3710 if (new_bias > bias)
3713 mk_repeat_alert(mk_alert(MODE3));
3719 bias = get_bias(0.2, alt_loss);
3720 mk_set_alerts(mk_alert(MODE3));
3727 mk_unset_alerts(mk_alert(MODE3));
3730 ///////////////////////////////////////////////////////////////////////////////
3731 // Mode4Handler ///////////////////////////////////////////////////////////////
3732 ///////////////////////////////////////////////////////////////////////////////
3734 // FIXME: for turbofans, the upper limit should be reduced from 1000
3735 // to 800 ft if a sudden change in radio altitude is detected, in
3736 // order to reduce nuisance alerts caused by overflying another
3737 // aircraft (see [PILOT] page 16).
3740 MK_VIII::Mode4Handler::get_upper_agl (const EnvelopesConfiguration *c)
3742 if (mk_ainput(computed_airspeed).get() >= c->airspeed2)
3744 else if (mk_ainput(computed_airspeed).get() >= c->airspeed1)
3745 return c->min_agl2(mk_ainput(computed_airspeed).get());
3750 const MK_VIII::Mode4Handler::EnvelopesConfiguration *
3751 MK_VIII::Mode4Handler::get_ab_envelope ()
3753 // [SPEC] 6.2.4.1: "The Mode 4B envelope can also be selected by
3754 // setting flaps to landing configuration or by selecting flap
3756 return mk_dinput(landing_gear) || mk->io_handler.flaps_down()
3762 MK_VIII::Mode4Handler::get_bias (double initial_bias, double min_agl)
3764 while (mk_data(radio_altitude).get() < min_agl - min_agl * initial_bias)
3765 initial_bias += 0.2;
3767 return initial_bias;
3771 MK_VIII::Mode4Handler::handle_alert (unsigned int alert,
3775 if (mk_test_alerts(alert))
3777 double new_bias = get_bias(*bias, min_agl);
3778 if (new_bias > *bias)
3781 mk_repeat_alert(alert);
3786 *bias = get_bias(0.2, min_agl);
3787 mk_set_alerts(alert);
3792 MK_VIII::Mode4Handler::update_ab ()
3794 if (! mk->io_handler.gpws_inhibit()
3795 && ! mk->state_handler.ground
3796 && ! mk->state_handler.takeoff
3797 && ! mk_data(radio_altitude).ncd
3798 && ! mk_ainput(computed_airspeed).ncd
3799 && mk_data(radio_altitude).get() > 30)
3801 const EnvelopesConfiguration *c = get_ab_envelope();
3802 if (mk_ainput(computed_airspeed).get() < c->airspeed1)
3804 if (mk_dinput(landing_gear))
3806 if (! mk->io_handler.flaps_down() && mk_data(radio_altitude).get() < c->min_agl1)
3808 handle_alert(mk_alert(MODE4_TOO_LOW_FLAPS), c->min_agl1, &ab_bias);
3814 if (mk_data(radio_altitude).get() < c->min_agl1)
3816 handle_alert(mk_alert(MODE4_TOO_LOW_GEAR), c->min_agl1, &ab_bias);
3823 mk_unset_alerts(mk_alert(MODE4_TOO_LOW_FLAPS) | mk_alert(MODE4_TOO_LOW_GEAR));
3827 MK_VIII::Mode4Handler::update_ab_expanded ()
3829 if (! mk->io_handler.gpws_inhibit()
3830 && ! mk->state_handler.ground
3831 && ! mk->state_handler.takeoff
3832 && ! mk_data(radio_altitude).ncd
3833 && ! mk_ainput(computed_airspeed).ncd
3834 && mk_data(radio_altitude).get() > 30)
3836 const EnvelopesConfiguration *c = get_ab_envelope();
3837 if (mk_ainput(computed_airspeed).get() >= c->airspeed1)
3839 double min_agl = get_upper_agl(c);
3840 if (mk_data(radio_altitude).get() < min_agl)
3842 handle_alert(mk_alert(MODE4AB_TOO_LOW_TERRAIN), min_agl, &ab_expanded_bias);
3848 mk_unset_alerts(mk_alert(MODE4AB_TOO_LOW_TERRAIN));
3852 MK_VIII::Mode4Handler::update_c ()
3854 if (! mk->io_handler.gpws_inhibit()
3855 && ! mk->state_handler.ground // [1]
3856 && mk->state_handler.takeoff
3857 && ! mk_data(radio_altitude).ncd
3858 && ! mk_data(terrain_clearance).ncd
3859 && mk_data(radio_altitude).get() > 30
3860 && (! mk_dinput(landing_gear) || ! mk->io_handler.flaps_down())
3861 && mk_data(radio_altitude).get() < get_upper_agl(conf.modes->ac)
3862 && mk_data(radio_altitude).get() < mk_data(terrain_clearance).get())
3863 handle_alert(mk_alert(MODE4C_TOO_LOW_TERRAIN), mk_data(terrain_clearance).get(), &c_bias);
3865 mk_unset_alerts(mk_alert(MODE4C_TOO_LOW_TERRAIN));
3869 MK_VIII::Mode4Handler::update ()
3871 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
3875 update_ab_expanded();
3879 ///////////////////////////////////////////////////////////////////////////////
3880 // Mode5Handler ///////////////////////////////////////////////////////////////
3881 ///////////////////////////////////////////////////////////////////////////////
3884 MK_VIII::Mode5Handler::is_hard ()
3886 if (mk_data(radio_altitude).get() > 30
3887 && mk_data(radio_altitude).get() < 300
3888 && mk_data(glideslope_deviation_dots).get() > 2)
3890 if (mk_data(radio_altitude).get() < 150)
3892 if (mk_data(radio_altitude).get() > 293 - 71.43 * mk_data(glideslope_deviation_dots).get())
3903 MK_VIII::Mode5Handler::is_soft (double bias)
3905 if (mk_data(radio_altitude).get() > 30)
3907 double bias_dots = 1.3 * bias;
3908 if (mk_data(glideslope_deviation_dots).get() > 1.3 + bias_dots)
3910 if (mk_data(radio_altitude).get() < 150)
3912 if (mk_data(radio_altitude).get() > 243 - 71.43 * (mk_data(glideslope_deviation_dots).get() - bias_dots))
3919 if (mk_data(barometric_altitude_rate).ncd)
3923 if (mk_data(barometric_altitude_rate).get() >= 0)
3925 else if (mk_data(barometric_altitude_rate).get() < -500)
3928 upper_limit = -mk_data(barometric_altitude_rate).get() + 500;
3931 if (mk_data(radio_altitude).get() < upper_limit)
3941 MK_VIII::Mode5Handler::get_soft_bias (double initial_bias)
3943 while (is_soft(initial_bias))
3944 initial_bias += 0.2;
3946 return initial_bias;
3950 MK_VIII::Mode5Handler::update_hard (bool is)
3954 if (! mk_test_alert(MODE5_HARD))
3956 if (hard_timer.start_or_elapsed() >= 0.8)
3957 mk_set_alerts(mk_alert(MODE5_HARD));
3963 mk_unset_alerts(mk_alert(MODE5_HARD));
3968 MK_VIII::Mode5Handler::update_soft (bool is)
3972 if (! mk_test_alert(MODE5_SOFT))
3974 double new_bias = get_soft_bias(soft_bias);
3975 if (new_bias > soft_bias)
3977 soft_bias = new_bias;
3978 mk_repeat_alert(mk_alert(MODE5_SOFT));
3983 if (soft_timer.start_or_elapsed() >= 0.8)
3985 soft_bias = get_soft_bias(0.2);
3986 mk_set_alerts(mk_alert(MODE5_SOFT));
3993 mk_unset_alerts(mk_alert(MODE5_SOFT));
3998 MK_VIII::Mode5Handler::update ()
4000 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
4003 if (! mk->io_handler.gpws_inhibit()
4004 && ! mk->state_handler.ground // [1]
4005 && ! mk_dinput(glideslope_inhibit) // not on backcourse
4006 && ! mk_data(radio_altitude).ncd
4007 && ! mk_data(glideslope_deviation_dots).ncd
4008 && (! mk->io_handler.conf.localizer_enabled
4009 || mk_data(localizer_deviation_dots).ncd
4010 || mk_data(radio_altitude).get() < 500
4011 || fabs(mk_data(localizer_deviation_dots).get()) < 2)
4012 && (! mk->state_handler.takeoff || mk->io_handler.flaps_down())
4013 && mk_dinput(landing_gear)
4014 && ! mk_doutput(glideslope_cancel))
4016 update_hard(is_hard());
4017 update_soft(is_soft(0));
4026 ///////////////////////////////////////////////////////////////////////////////
4027 // Mode6Handler ///////////////////////////////////////////////////////////////
4028 ///////////////////////////////////////////////////////////////////////////////
4030 // keep sorted in descending order
4031 const int MK_VIII::Mode6Handler::altitude_callout_definitions[] =
4032 { 1000, 500, 400, 300, 200, 100, 50, 40, 30, 20, 10 };
4035 MK_VIII::Mode6Handler::reset_minimums ()
4037 minimums_issued = false;
4041 MK_VIII::Mode6Handler::reset_altitude_callouts ()
4043 for (int i = 0; i < n_altitude_callouts; i++)
4044 altitude_callouts_issued[i] = false;
4048 MK_VIII::Mode6Handler::is_playing_altitude_callout ()
4050 for (int i = 0; i < n_altitude_callouts; i++)
4051 if (mk->voice_player.voice == mk_altitude_voice(i)
4052 || mk->voice_player.next_voice == mk_altitude_voice(i))
4059 MK_VIII::Mode6Handler::is_near_minimums (double callout)
4063 if (! mk_data(decision_height).ncd)
4065 double diff = callout - mk_data(decision_height).get();
4067 if (mk_data(radio_altitude).get() >= 200)
4069 if (fabs(diff) <= 30)
4074 if (diff >= -3 && diff <= 6)
4083 MK_VIII::Mode6Handler::is_outside_band (double elevation, double callout)
4086 return elevation < callout - (elevation > 150 ? 20 : 10);
4090 MK_VIII::Mode6Handler::inhibit_smart_500 ()
4094 if (! mk_data(glideslope_deviation_dots).ncd
4095 && ! mk_dinput(glideslope_inhibit) // backcourse
4096 && ! mk_doutput(glideslope_cancel))
4098 if (mk->io_handler.conf.localizer_enabled
4099 && ! mk_data(localizer_deviation_dots).ncd)
4101 if (fabs(mk_data(localizer_deviation_dots).get()) <= 2)
4106 if (fabs(mk_data(glideslope_deviation_dots).get()) <= 2)
4115 MK_VIII::Mode6Handler::boot ()
4117 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
4120 last_decision_height = mk_dinput(decision_height);
4121 last_radio_altitude.set(&mk_data(radio_altitude));
4124 for (int i = 0; i < n_altitude_callouts; i++)
4125 altitude_callouts_issued[i] = ! mk_data(radio_altitude).ncd
4126 && mk_data(radio_altitude).get() <= altitude_callout_definitions[i];
4128 // extrapolated from [SPEC] 6.4.2
4129 minimums_issued = mk_dinput(decision_height);
4131 if (conf.above_field_voice)
4134 get_altitude_above_field(&last_altitude_above_field);
4135 // extrapolated from [SPEC] 6.4.2
4136 above_field_issued = ! last_altitude_above_field.ncd
4137 && last_altitude_above_field.get() < 550;
4142 MK_VIII::Mode6Handler::power_off ()
4144 runway_timer.stop();
4148 MK_VIII::Mode6Handler::enter_takeoff ()
4150 reset_altitude_callouts(); // [SPEC] 6.4.2
4151 reset_minimums(); // omitted by [SPEC]; common sense
4155 MK_VIII::Mode6Handler::leave_takeoff ()
4157 reset_altitude_callouts(); // [SPEC] 6.0.2
4158 reset_minimums(); // [SPEC] 6.0.2
4162 MK_VIII::Mode6Handler::set_volume (double volume)
4164 mk_voice(minimums_minimums)->set_volume(volume);
4165 mk_voice(five_hundred_above)->set_volume(volume);
4166 for (int i = 0; i < n_altitude_callouts; i++)
4167 mk_altitude_voice(i)->set_volume(volume);
4171 MK_VIII::Mode6Handler::altitude_callouts_enabled ()
4173 if (conf.minimums_enabled || conf.smart_500_enabled || conf.above_field_voice)
4176 for (int i = 0; i < n_altitude_callouts; i++)
4177 if (conf.altitude_callouts_enabled[i])
4184 MK_VIII::Mode6Handler::update_minimums ()
4186 if (! mk->io_handler.gpws_inhibit()
4187 && (mk->voice_player.voice == mk_voice(minimums_minimums)
4188 || mk->voice_player.next_voice == mk_voice(minimums_minimums)))
4191 if (! mk->io_handler.gpws_inhibit()
4192 && ! mk->state_handler.ground // [1]
4193 && conf.minimums_enabled
4194 && ! minimums_issued
4195 && mk_dinput(landing_gear)
4196 && mk_dinput(decision_height)
4197 && ! last_decision_height)
4199 minimums_issued = true;
4201 // If an altitude callout is playing, stop it now so that the
4202 // minimums callout can be played (note that proper connection
4203 // and synchronization of the radio-altitude ARINC 529 input,
4204 // decision-height discrete and decision-height ARINC 529 input
4205 // will prevent an altitude callout from being played near the
4206 // decision height).
4208 if (is_playing_altitude_callout())
4209 mk->voice_player.stop(VoicePlayer::STOP_NOW);
4211 mk_set_alerts(mk_alert(MODE6_MINIMUMS));
4214 mk_unset_alerts(mk_alert(MODE6_MINIMUMS));
4217 last_decision_height = mk_dinput(decision_height);
4221 MK_VIII::Mode6Handler::update_altitude_callouts ()
4223 if (! mk->io_handler.gpws_inhibit() && is_playing_altitude_callout())
4226 if (! mk->io_handler.gpws_inhibit()
4227 && ! mk->state_handler.ground // [1]
4228 && ! mk_data(radio_altitude).ncd)
4229 for (int i = 0; i < n_altitude_callouts && mk_data(radio_altitude).get() <= altitude_callout_definitions[i]; i++)
4230 if ((conf.altitude_callouts_enabled[i]
4231 || (altitude_callout_definitions[i] == 500
4232 && conf.smart_500_enabled))
4233 && ! altitude_callouts_issued[i]
4234 && (last_radio_altitude.ncd
4235 || last_radio_altitude.get() > altitude_callout_definitions[i]))
4237 // lock out all callouts superior or equal to this one
4238 for (int j = 0; j <= i; j++)
4239 altitude_callouts_issued[j] = true;
4241 altitude_callouts_issued[i] = true;
4242 if (! is_near_minimums(altitude_callout_definitions[i])
4243 && ! is_outside_band(mk_data(radio_altitude).get(), altitude_callout_definitions[i])
4244 && (! conf.smart_500_enabled
4245 || altitude_callout_definitions[i] != 500
4246 || ! inhibit_smart_500()))
4248 mk->alert_handler.set_altitude_callout_alert(mk_altitude_voice(i));
4253 mk_unset_alerts(mk_alert(MODE6_ALTITUDE_CALLOUT));
4256 last_radio_altitude.set(&mk_data(radio_altitude));
4260 MK_VIII::Mode6Handler::test_runway (const FGRunway *_runway)
4262 if (_runway->_length < mk->conf.runway_database)
4265 // get position of threshold
4266 double latitude, longitude, az;
4267 geo_direct_wgs_84(0,
4270 get_reciprocal_heading(_runway->_heading),
4271 _runway->_length / 2 * SG_FEET_TO_METER,
4276 // get distance to threshold
4277 double distance, az1, az2;
4278 geo_inverse_wgs_84(0,
4279 mk_data(gps_latitude).get(),
4280 mk_data(gps_longitude).get(),
4283 &az1, &az2, &distance);
4285 return distance * SG_METER_TO_NM <= 5;
4289 MK_VIII::Mode6Handler::test_airport (const FGAirport *airport)
4292 if (globals->get_runways()->search(airport->getId(), &r))
4295 if (test_runway(&r))
4298 // reciprocal runway
4299 r._heading = get_reciprocal_heading(r._heading);
4300 if (test_runway(&r))
4303 while (globals->get_runways()->next(&r) && r._id == airport->getId());
4309 MK_VIII::Mode6Handler::update_runway ()
4311 if (! mk_data(gps_latitude).ncd && ! mk_data(gps_longitude).ncd)
4313 // Search for the closest runway threshold in range 5
4314 // nm. Passing 0.5 degrees (approximatively 30 nm) to
4315 // get_closest_airport() provides enough margin for large
4316 // airports, which may have a runway located far away from the
4317 // airport's reference point.
4319 const FGAirport *airport = get_closest_airport(mk_data(gps_latitude).get(),
4320 mk_data(gps_longitude).get(),
4323 &MK_VIII::Mode6Handler::test_airport);
4326 runway.elevation = airport->getElevation();
4328 has_runway.set(airport != NULL);
4335 MK_VIII::Mode6Handler::get_altitude_above_field (Parameter<double> *parameter)
4337 if (! has_runway.ncd && has_runway.get() && ! mk_data(geometric_altitude).ncd)
4338 parameter->set(mk_data(geometric_altitude).get() - runway.elevation);
4344 MK_VIII::Mode6Handler::update_above_field_callout ()
4346 if (! conf.above_field_voice)
4349 // update_runway() has to iterate over the whole runway database
4350 // (which contains thousands of runways), so it would be unwise to
4351 // run it every frame. Run it every 3 seconds. Note that the first
4352 // iteration is run in boot().
4353 if (runway_timer.start_or_elapsed() >= 3)
4356 runway_timer.start();
4359 Parameter<double> altitude_above_field;
4360 get_altitude_above_field(&altitude_above_field);
4362 if (! mk->io_handler.gpws_inhibit()
4363 && (mk->voice_player.voice == conf.above_field_voice
4364 || mk->voice_player.next_voice == conf.above_field_voice))
4367 // handle reset term
4368 if (above_field_issued)
4370 if ((! has_runway.ncd && ! has_runway.get())
4371 || (! altitude_above_field.ncd && altitude_above_field.get() > 700))
4372 above_field_issued = false;
4375 if (! mk->io_handler.gpws_inhibit()
4376 && ! mk->state_handler.ground // [1]
4377 && ! above_field_issued
4378 && ! altitude_above_field.ncd
4379 && altitude_above_field.get() < 550
4380 && (last_altitude_above_field.ncd
4381 || last_altitude_above_field.get() >= 550))
4383 above_field_issued = true;
4385 if (! is_outside_band(altitude_above_field.get(), 500))
4387 mk->alert_handler.set_altitude_callout_alert(conf.above_field_voice);
4392 mk_unset_alerts(mk_alert(MODE6_ALTITUDE_CALLOUT));
4395 last_altitude_above_field.set(&altitude_above_field);
4399 MK_VIII::Mode6Handler::is_bank_angle (double abs_roll_angle, double bias)
4401 return conf.is_bank_angle(&mk_data(radio_altitude),
4402 abs_roll_angle - abs_roll_angle * bias,
4403 mk_dinput(autopilot_engaged));
4407 MK_VIII::Mode6Handler::is_high_bank_angle ()
4409 return mk_data(radio_altitude).ncd || mk_data(radio_altitude).get() >= 210;
4413 MK_VIII::Mode6Handler::get_bank_angle_alerts ()
4415 if (! mk->io_handler.gpws_inhibit()
4416 && ! mk->state_handler.ground // [1]
4417 && ! mk_data(roll_angle).ncd)
4419 double abs_roll_angle = fabs(mk_data(roll_angle).get());
4421 if (is_bank_angle(abs_roll_angle, 0.4))
4422 return is_high_bank_angle()
4423 ? (mk_alert(MODE6_HIGH_BANK_ANGLE_1) | mk_alert(MODE6_HIGH_BANK_ANGLE_2) | mk_alert(MODE6_HIGH_BANK_ANGLE_3))
4424 : (mk_alert(MODE6_LOW_BANK_ANGLE_1) | mk_alert(MODE6_LOW_BANK_ANGLE_2) | mk_alert(MODE6_LOW_BANK_ANGLE_3));
4425 else if (is_bank_angle(abs_roll_angle, 0.2))
4426 return is_high_bank_angle()
4427 ? (mk_alert(MODE6_HIGH_BANK_ANGLE_1) | mk_alert(MODE6_HIGH_BANK_ANGLE_2))
4428 : (mk_alert(MODE6_LOW_BANK_ANGLE_1) | mk_alert(MODE6_LOW_BANK_ANGLE_2));
4429 else if (is_bank_angle(abs_roll_angle, 0))
4430 return is_high_bank_angle()
4431 ? mk_alert(MODE6_HIGH_BANK_ANGLE_1)
4432 : mk_alert(MODE6_LOW_BANK_ANGLE_1);
4439 MK_VIII::Mode6Handler::update_bank_angle ()
4441 if (! conf.bank_angle_enabled)
4444 unsigned int alerts = get_bank_angle_alerts();
4446 // [SPEC] 6.4.4 specifies that the non-continuous alerts
4447 // (MODE6_*_BANK_ANGLE_1 and MODE6_*_BANK_ANGLE_2) are locked out
4448 // until the initial envelope is exited.
4450 if (! test_bits(alerts, mk_alert(MODE6_LOW_BANK_ANGLE_3)))
4451 mk_unset_alerts(mk_alert(MODE6_LOW_BANK_ANGLE_3));
4452 if (! test_bits(alerts, mk_alert(MODE6_HIGH_BANK_ANGLE_3)))
4453 mk_unset_alerts(mk_alert(MODE6_HIGH_BANK_ANGLE_3));
4456 mk_set_alerts(alerts);
4458 mk_unset_alerts(mk_alert(MODE6_LOW_BANK_ANGLE_1)
4459 | mk_alert(MODE6_HIGH_BANK_ANGLE_1)
4460 | mk_alert(MODE6_LOW_BANK_ANGLE_2)
4461 | mk_alert(MODE6_HIGH_BANK_ANGLE_2));
4465 MK_VIII::Mode6Handler::update ()
4467 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
4470 if (! mk->state_handler.takeoff
4471 && ! mk_data(radio_altitude).ncd
4472 && mk_data(radio_altitude).get() > 1000)
4474 reset_altitude_callouts(); // [SPEC] 6.4.2
4475 reset_minimums(); // common sense
4479 update_altitude_callouts();
4480 update_above_field_callout();
4481 update_bank_angle();
4484 ///////////////////////////////////////////////////////////////////////////////
4485 // TCFHandler /////////////////////////////////////////////////////////////////
4486 ///////////////////////////////////////////////////////////////////////////////
4488 // Gets the difference between the azimuth from @from_lat,@from_lon to
4489 // @to_lat,@to_lon, and @to_heading, in degrees.
4491 MK_VIII::TCFHandler::get_azimuth_difference (double from_lat,
4497 double az1, az2, distance;
4498 geo_inverse_wgs_84(0, from_lat, from_lon, to_lat, to_lon, &az1, &az2, &distance);
4499 return get_heading_difference(az1, to_heading);
4502 // Gets the difference between the azimuth from the current GPS
4503 // position to the center of @_runway, and the heading of @_runway, in
4506 MK_VIII::TCFHandler::get_azimuth_difference (const FGRunway *_runway)
4508 return get_azimuth_difference(mk_data(gps_latitude).get(),
4509 mk_data(gps_longitude).get(),
4515 // Selects the most likely intended destination runway of @airport,
4516 // and returns it in @_runway. For each runway, the difference between
4517 // the azimuth from the current GPS position to the center of the
4518 // runway and its heading is computed. The runway having the smallest
4521 // This selection algorithm is not specified in [SPEC], but
4522 // http://www.egpws.com/general_information/description/runway_select.htm
4523 // talks about automatic runway selection.
4525 MK_VIII::TCFHandler::select_runway (const FGAirport *airport,
4529 bool status = globals->get_runways()->search(airport->getId(), &r);
4532 double min_diff = 360;
4537 diff = get_azimuth_difference(&r);
4538 if (diff < min_diff)
4544 // reciprocal runway
4545 r._heading = get_reciprocal_heading(r._heading);
4546 diff = get_azimuth_difference(&r);
4547 if (diff < min_diff)
4553 while (globals->get_runways()->next(&r) && r._id == airport->getId());
4557 MK_VIII::TCFHandler::test_airport (const FGAirport *airport)
4560 if (globals->get_runways()->search(airport->getId(), &r))
4563 if (r._length >= mk->conf.runway_database)
4566 while (globals->get_runways()->next(&r) && r._id == airport->getId());
4572 MK_VIII::TCFHandler::update_runway ()
4575 if (! mk_data(gps_latitude).ncd && ! mk_data(gps_longitude).ncd)
4577 // Search for the intended destination runway of the closest
4578 // airport in range 15 nm. Passing 0.5 degrees (approximatively
4579 // 30 nm) to get_closest_airport() provides enough margin for
4580 // large airports, which may have a runway located far away from
4581 // the airport's reference point.
4583 const FGAirport *airport = get_closest_airport(mk_data(gps_latitude).get(),
4584 mk_data(gps_longitude).get(),
4587 &MK_VIII::TCFHandler::test_airport);
4594 select_runway(airport, &_runway);
4596 runway.center.latitude = _runway._lat;
4597 runway.center.longitude = _runway._lon;
4599 runway.elevation = airport->getElevation();
4601 double half_length_m = _runway._length / 2 * SG_FEET_TO_METER;
4602 runway.half_length = half_length_m * SG_METER_TO_NM;
4604 // b3 ________________ b0
4606 // h1>>> | e1<<<<<<<<e0 | <<<h0
4607 // |________________|
4610 // get heading to runway threshold (h0) and end (h1)
4611 runway.edges[0].heading = _runway._heading;
4612 runway.edges[1].heading = get_reciprocal_heading(_runway._heading);
4616 // get position of runway threshold (e0)
4617 geo_direct_wgs_84(0,
4618 runway.center.latitude,
4619 runway.center.longitude,
4620 runway.edges[1].heading,
4622 &runway.edges[0].position.latitude,
4623 &runway.edges[0].position.longitude,
4626 // get position of runway end (e1)
4627 geo_direct_wgs_84(0,
4628 runway.center.latitude,
4629 runway.center.longitude,
4630 runway.edges[0].heading,
4632 &runway.edges[1].position.latitude,
4633 &runway.edges[1].position.longitude,
4636 double half_width_m = _runway._width / 2 * SG_FEET_TO_METER;
4638 // get position of threshold bias area edges (b0 and b1)
4639 get_bias_area_edges(&runway.edges[0].position,
4640 runway.edges[1].heading,
4642 &runway.bias_area[0],
4643 &runway.bias_area[1]);
4645 // get position of end bias area edges (b2 and b3)
4646 get_bias_area_edges(&runway.edges[1].position,
4647 runway.edges[0].heading,
4649 &runway.bias_area[2],
4650 &runway.bias_area[3]);
4656 MK_VIII::TCFHandler::get_bias_area_edges (Position *edge,
4658 double half_width_m,
4659 Position *bias_edge1,
4660 Position *bias_edge2)
4662 double half_bias_width_m = k * SG_NM_TO_METER + half_width_m;
4663 double tmp_latitude, tmp_longitude, az;
4665 geo_direct_wgs_84(0,
4673 geo_direct_wgs_84(0,
4676 heading_substract(reciprocal, 90),
4678 &bias_edge1->latitude,
4679 &bias_edge1->longitude,
4681 geo_direct_wgs_84(0,
4684 heading_add(reciprocal, 90),
4686 &bias_edge2->latitude,
4687 &bias_edge2->longitude,
4691 // Returns true if the current GPS position is inside the edge
4692 // triangle of @edge. The edge triangle, which is specified and
4693 // represented in [SPEC] 6.3.1, is defined as an isocele right
4694 // triangle of infinite height, whose right angle is located at the
4695 // position of @edge, and whose height is parallel to the heading of
4698 MK_VIII::TCFHandler::is_inside_edge_triangle (RunwayEdge *edge)
4700 return get_azimuth_difference(mk_data(gps_latitude).get(),
4701 mk_data(gps_longitude).get(),
4702 edge->position.latitude,
4703 edge->position.longitude,
4704 edge->heading) <= 45;
4707 // Returns true if the current GPS position is inside the bias area of
4708 // the currently selected runway.
4710 MK_VIII::TCFHandler::is_inside_bias_area ()
4713 double angles_sum = 0;
4715 for (int i = 0; i < 4; i++)
4717 double az2, distance;
4718 geo_inverse_wgs_84(0,
4719 mk_data(gps_latitude).get(),
4720 mk_data(gps_longitude).get(),
4721 runway.bias_area[i].latitude,
4722 runway.bias_area[i].longitude,
4723 &az1[i], &az2, &distance);
4726 for (int i = 0; i < 4; i++)
4728 double angle = az1[i == 3 ? 0 : i + 1] - az1[i];
4732 angles_sum += angle;
4735 return angles_sum > 180;
4739 MK_VIII::TCFHandler::is_tcf ()
4741 if (mk_data(radio_altitude).get() > 10)
4745 double distance, az1, az2;
4747 geo_inverse_wgs_84(0,
4748 mk_data(gps_latitude).get(),
4749 mk_data(gps_longitude).get(),
4750 runway.center.latitude,
4751 runway.center.longitude,
4752 &az1, &az2, &distance);
4754 distance *= SG_METER_TO_NM;
4756 // distance to the inner envelope edge
4757 double edge_distance = distance - runway.half_length - k;
4759 if (edge_distance >= 15)
4761 if (mk_data(radio_altitude).get() < 700)
4764 else if (edge_distance >= 12)
4766 if (mk_data(radio_altitude).get() < 100 * edge_distance - 800)
4769 else if (edge_distance >= 4)
4771 if (mk_data(radio_altitude).get() < 400)
4774 else if (edge_distance >= 2.45)
4776 if (mk_data(radio_altitude).get() < 66.666667 * edge_distance + 133.33333)
4781 if (is_inside_edge_triangle(&runway.edges[0]) || is_inside_edge_triangle(&runway.edges[1]))
4783 if (edge_distance >= 1)
4785 if (mk_data(radio_altitude).get() < 66.666667 * edge_distance + 133.33333)
4788 else if (edge_distance >= 0.05)
4790 if (mk_data(radio_altitude).get() < 200 * edge_distance)
4796 if (! is_inside_bias_area())
4798 if (mk_data(radio_altitude).get() < 245)
4806 if (mk_data(radio_altitude).get() < 700)
4815 MK_VIII::TCFHandler::is_rfcf ()
4819 double distance, az1, az2;
4820 geo_inverse_wgs_84(0,
4821 mk_data(gps_latitude).get(),
4822 mk_data(gps_longitude).get(),
4823 runway.center.latitude,
4824 runway.center.longitude,
4825 &az1, &az2, &distance);
4827 double krf = k + mk_data(gps_vertical_figure_of_merit).get() / 200;
4828 distance = distance * SG_METER_TO_NM - runway.half_length - krf;
4832 double altitude_above_field = mk_data(geometric_altitude).get() - runway.elevation;
4834 if (distance >= 1.5)
4836 if (altitude_above_field < 300)
4839 else if (distance >= 0)
4841 if (altitude_above_field < 200 * distance)
4851 MK_VIII::TCFHandler::update ()
4853 if (mk->configuration_module.state != ConfigurationModule::STATE_OK || ! conf.enabled)
4856 // update_runway() has to iterate over the whole runway database
4857 // (which contains thousands of runways), so it would be unwise to
4858 // run it every frame. Run it every 3 seconds.
4859 if (! runway_timer.running || runway_timer.elapsed() >= 3)
4862 runway_timer.start();
4865 if (! mk_dinput(ta_tcf_inhibit)
4866 && ! mk->state_handler.ground // [1]
4867 && ! mk_data(gps_latitude).ncd
4868 && ! mk_data(gps_longitude).ncd
4869 && ! mk_data(radio_altitude).ncd
4870 && ! mk_data(geometric_altitude).ncd
4871 && ! mk_data(gps_vertical_figure_of_merit).ncd)
4876 _reference = mk_data(radio_altitude).get_pointer();
4878 _reference = mk_data(geometric_altitude).get_pointer();
4884 if (mk_test_alert(TCF_TOO_LOW_TERRAIN))
4886 double new_bias = bias;
4887 while (*reference < initial_value - initial_value * new_bias)
4890 if (new_bias > bias)
4893 mk_repeat_alert(mk_alert(TCF_TOO_LOW_TERRAIN));
4899 reference = _reference;
4900 initial_value = *reference;
4901 mk_set_alerts(mk_alert(TCF_TOO_LOW_TERRAIN));
4908 mk_unset_alerts(mk_alert(TCF_TOO_LOW_TERRAIN));
4911 ///////////////////////////////////////////////////////////////////////////////
4912 // MK_VIII ////////////////////////////////////////////////////////////////////
4913 ///////////////////////////////////////////////////////////////////////////////
4915 MK_VIII::MK_VIII (SGPropertyNode *node)
4919 properties_handler(this),
4920 power_handler(this),
4921 system_handler(this),
4922 configuration_module(this),
4923 fault_handler(this),
4926 self_test_handler(this),
4927 alert_handler(this),
4928 state_handler(this),
4929 mode1_handler(this),
4930 mode2_handler(this),
4931 mode3_handler(this),
4932 mode4_handler(this),
4933 mode5_handler(this),
4934 mode6_handler(this),
4937 for (int i = 0; i < node->nChildren(); ++i)
4939 SGPropertyNode *child = node->getChild(i);
4940 string cname = child->getName();
4941 string cval = child->getStringValue();
4943 if (cname == "name")
4945 else if (cname == "number")
4946 num = child->getIntValue();
4949 SG_LOG(SG_INSTR, SG_WARN, "Error in mk-viii config logic");
4951 SG_LOG(SG_INSTR, SG_WARN, "Section = " << name);
4959 properties_handler.init();
4960 voice_player.init();
4966 SGPropertyNode *node = fgGetNode(("/instrumentation/" + name).c_str(), num, true);
4968 configuration_module.bind(node);
4969 power_handler.bind(node);
4970 io_handler.bind(node);
4971 voice_player.bind(node);
4977 properties_handler.unbind();
4981 MK_VIII::update (double dt)
4983 power_handler.update();
4984 system_handler.update();
4986 if (system_handler.state != SystemHandler::STATE_ON)
4989 io_handler.update_inputs();
4990 io_handler.update_input_faults();
4992 voice_player.update();
4993 state_handler.update();
4995 if (self_test_handler.update())
4998 io_handler.update_internal_latches();
4999 io_handler.update_lamps();
5001 mode1_handler.update();
5002 mode2_handler.update();
5003 mode3_handler.update();
5004 mode4_handler.update();
5005 mode5_handler.update();
5006 mode6_handler.update();
5007 tcf_handler.update();
5009 alert_handler.update();
5010 io_handler.update_outputs();