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 *>::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 SGSoundSample *sample = globals->get_soundmgr()->find(refname.str());
2305 SGPath sample_path(globals->get_fg_root());
2306 sample_path.append("Sounds/mk-viii");
2308 string filename = string(name) + ".wav";
2311 sample = new SGSoundSample(sample_path.c_str(), filename.c_str());
2313 catch (const sg_exception &e)
2315 SG_LOG(SG_INSTR, SG_ALERT, "Error loading MK VIII sound sample \"" + filename + "\": " + e.getFormattedMessage());
2319 globals->get_soundmgr()->add(sample, refname.str());
2320 samples[refname.str()] = sample;
2327 MK_VIII::VoicePlayer::play (Voice *_voice, unsigned int flags)
2329 if (test_bits(flags, PLAY_NOW) || ! voice || voice->element->silence)
2335 looped = test_bits(flags, PLAY_LOOPED);
2338 next_looped = false;
2344 next_voice = _voice;
2345 next_looped = test_bits(flags, PLAY_LOOPED);
2350 MK_VIII::VoicePlayer::stop (unsigned int flags)
2354 voice->stop(test_bits(flags, STOP_NOW));
2364 MK_VIII::VoicePlayer::set_volume (double _volume)
2368 voice->volume_changed();
2372 MK_VIII::VoicePlayer::update ()
2380 if (! voice->element || voice->element->silence)
2383 looped = next_looped;
2386 next_looped = false;
2393 if (! voice->element)
2404 ///////////////////////////////////////////////////////////////////////////////
2405 // SelfTestHandler ////////////////////////////////////////////////////////////
2406 ///////////////////////////////////////////////////////////////////////////////
2409 MK_VIII::SelfTestHandler::_was_here (int position)
2411 if (position > current)
2420 MK_VIII::SelfTestHandler::Action
2421 MK_VIII::SelfTestHandler::sleep (double duration)
2423 Action _action = { ACTION_SLEEP, duration, NULL };
2427 MK_VIII::SelfTestHandler::Action
2428 MK_VIII::SelfTestHandler::play (VoicePlayer::Voice *voice)
2430 mk->voice_player.play(voice);
2431 Action _action = { ACTION_VOICE, 0, NULL };
2435 MK_VIII::SelfTestHandler::Action
2436 MK_VIII::SelfTestHandler::discrete_on (bool *discrete, double duration)
2439 return sleep(duration);
2442 MK_VIII::SelfTestHandler::Action
2443 MK_VIII::SelfTestHandler::discrete_on_off (bool *discrete, double duration)
2446 Action _action = { ACTION_SLEEP | ACTION_DISCRETE_ON_OFF, duration, discrete };
2450 MK_VIII::SelfTestHandler::Action
2451 MK_VIII::SelfTestHandler::discrete_on_off (bool *discrete, VoicePlayer::Voice *voice)
2454 mk->voice_player.play(voice);
2455 Action _action = { ACTION_VOICE | ACTION_DISCRETE_ON_OFF, 0, discrete };
2459 MK_VIII::SelfTestHandler::Action
2460 MK_VIII::SelfTestHandler::done ()
2462 Action _action = { ACTION_DONE, 0, NULL };
2466 MK_VIII::SelfTestHandler::Action
2467 MK_VIII::SelfTestHandler::run ()
2469 // Note that "Terrain INOP" and "Terrain NA" are or'ed to the same
2470 // output discrete (see [SPEC] 6.9.3.5).
2472 #define was_here() was_here_offset(0)
2473 #define was_here_offset(offset) _was_here(__LINE__ * 20 + (offset))
2477 if (mk->configuration_module.state == ConfigurationModule::STATE_INVALID_DATABASE)
2478 return play(mk_voice(application_data_base_failed));
2479 else if (mk->configuration_module.state == ConfigurationModule::STATE_INVALID_AIRCRAFT_TYPE)
2480 return play(mk_voice(configuration_type_invalid));
2482 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
2486 return discrete_on(&mk_doutput(gpws_inop), 0.7);
2488 return sleep(0.7); // W/S INOP
2490 return discrete_on(&mk_doutput(tad_inop), 0.7);
2493 mk_doutput(gpws_inop) = false;
2494 // do not disable tad_inop since we must enable Terrain NA
2495 // do not disable W/S INOP since we do not emulate it
2496 return sleep(0.7); // Terrain NA
2500 mk_doutput(tad_inop) = false; // disable Terrain NA
2501 if (mk->io_handler.conf.momentary_flap_override_enabled)
2502 return discrete_on_off(&mk_doutput(flap_override), 1.0);
2505 return discrete_on_off(&mk_doutput(audio_on), 1.0);
2508 if (mk->io_handler.momentary_steep_approach_enabled())
2509 return discrete_on_off(&mk_doutput(steep_approach), 1.0);
2514 if (mk_dinput(glideslope_inhibit))
2515 goto glideslope_end;
2518 if (mk->fault_handler.faults[FaultHandler::FAULT_MODE5_INPUTS_INVALID])
2519 goto glideslope_inop;
2523 return discrete_on_off(&mk_doutput(gpws_alert), mk_voice(glideslope));
2525 return discrete_on_off(&mk_doutput(glideslope_cancel), 0.7);
2526 goto glideslope_end;
2529 return play(mk_voice(glideslope_inop));
2534 if (mk->fault_handler.faults[FaultHandler::FAULT_MODES14_INPUTS_INVALID])
2538 return discrete_on_off(&mk_doutput(gpws_warning), mk_voice(pull_up));
2542 return play(mk_voice(gpws_inop));
2547 if (mk_dinput(self_test)) // proceed to long self test
2552 if (mk->mode6_handler.conf.bank_angle_enabled
2553 && mk->fault_handler.faults[FaultHandler::FAULT_BANK_ANGLE_INPUTS_INVALID])
2554 return play(mk_voice(bank_angle_inop));
2558 if (mk->mode6_handler.altitude_callouts_enabled()
2559 && mk->fault_handler.faults[FaultHandler::FAULT_MODE6_INPUTS_INVALID])
2560 return play(mk_voice(callouts_inop));
2568 mk_doutput(gpws_inop) = true;
2569 // do not enable W/S INOP, since we do not emulate it
2570 mk_doutput(tad_inop) = true; // Terrain INOP, Terrain NA
2572 return play(mk_voice(sink_rate));
2575 return play(mk_voice(pull_up));
2577 return play(mk_voice(terrain));
2579 return play(mk_voice(pull_up));
2581 return play(mk_voice(dont_sink));
2583 return play(mk_voice(too_low_terrain));
2585 return play(mk->mode4_handler.conf.voice_too_low_gear);
2587 return play(mk_voice(too_low_flaps));
2589 return play(mk_voice(too_low_terrain));
2591 return play(mk_voice(glideslope));
2594 if (mk->mode6_handler.conf.bank_angle_enabled)
2595 return play(mk_voice(bank_angle));
2600 if (! mk->mode6_handler.altitude_callouts_enabled())
2601 goto callouts_disabled;
2605 if (mk->mode6_handler.conf.minimums_enabled)
2606 return play(mk_voice(minimums));
2610 if (mk->mode6_handler.conf.above_field_voice)
2611 return play(mk->mode6_handler.conf.above_field_voice);
2613 for (int i = 0; i < n_altitude_callouts; i++)
2614 if (! was_here_offset(i))
2616 if (mk->mode6_handler.conf.altitude_callouts_enabled[i])
2617 return play(mk_altitude_voice(i));
2621 if (mk->mode6_handler.conf.smart_500_enabled)
2622 return play(mk_altitude_voice(Mode6Handler::ALTITUDE_CALLOUT_500));
2627 return play(mk_voice(minimums_minimums));
2632 if (mk->tcf_handler.conf.enabled)
2633 return play(mk_voice(too_low_terrain));
2640 MK_VIII::SelfTestHandler::start ()
2642 assert(state == STATE_START);
2644 memcpy(&saved_outputs, &mk->io_handler.outputs, sizeof(mk->io_handler.outputs));
2645 memset(&mk->io_handler.outputs, 0, sizeof(mk->io_handler.outputs));
2647 // [SPEC] 6.10.6: "The self-test results are annunciated, at 6db
2648 // lower than the normal audio level selected for the aircraft"
2649 mk->voice_player.set_volume(modify_amplitude(mk->voice_player.conf.volume, -6));
2651 if (mk_dinput(mode6_low_volume))
2652 mk->mode6_handler.set_volume(1.0);
2654 state = STATE_RUNNING;
2655 cancel = CANCEL_NONE;
2656 memset(&action, 0, sizeof(action));
2661 MK_VIII::SelfTestHandler::stop ()
2663 if (state != STATE_NONE)
2665 if (state != STATE_START)
2667 mk->voice_player.stop(VoicePlayer::STOP_NOW);
2668 mk->voice_player.set_volume(mk_dinput(audio_inhibit) ? 0.0 : mk->voice_player.conf.volume);
2670 if (mk_dinput(mode6_low_volume))
2671 mk->mode6_handler.set_volume(modify_amplitude(1.0, -6));
2673 memcpy(&mk->io_handler.outputs, &saved_outputs, sizeof(mk->io_handler.outputs));
2676 button_pressed = false;
2682 MK_VIII::SelfTestHandler::handle_button_event (bool value)
2684 if (state == STATE_NONE)
2687 state = STATE_START;
2689 else if (state == STATE_START)
2692 state = STATE_NONE; // cancel the not-yet-started test
2694 else if (cancel == CANCEL_NONE)
2698 assert(! button_pressed);
2699 button_pressed = true;
2700 button_press_timestamp = globals->get_sim_time_sec();
2706 if (globals->get_sim_time_sec() - button_press_timestamp < 2)
2707 cancel = CANCEL_SHORT;
2709 cancel = CANCEL_LONG;
2716 MK_VIII::SelfTestHandler::update ()
2718 if (state == STATE_NONE)
2720 else if (state == STATE_START)
2722 if (mk->state_handler.ground && ! mk->io_handler.steep_approach())
2732 if (button_pressed && cancel == CANCEL_NONE)
2734 if (globals->get_sim_time_sec() - button_press_timestamp >= 2)
2735 cancel = CANCEL_LONG;
2739 if (! mk->state_handler.ground || cancel != CANCEL_NONE)
2745 if (test_bits(action.flags, ACTION_SLEEP))
2747 if (globals->get_sim_time_sec() - sleep_start < action.sleep_duration)
2750 if (test_bits(action.flags, ACTION_VOICE))
2752 if (mk->voice_player.voice)
2755 if (test_bits(action.flags, ACTION_DISCRETE_ON_OFF))
2756 *action.discrete = false;
2760 if (test_bits(action.flags, ACTION_SLEEP))
2761 sleep_start = globals->get_sim_time_sec();
2762 if (test_bits(action.flags, ACTION_DONE))
2771 ///////////////////////////////////////////////////////////////////////////////
2772 // AlertHandler ///////////////////////////////////////////////////////////////
2773 ///////////////////////////////////////////////////////////////////////////////
2776 MK_VIII::AlertHandler::select_voice_alerts (unsigned int test)
2778 if (has_alerts(test))
2780 voice_alerts = alerts & test;
2785 voice_alerts &= ~test;
2786 if (voice_alerts == 0)
2787 mk->voice_player.stop();
2794 MK_VIII::AlertHandler::boot ()
2800 MK_VIII::AlertHandler::reposition ()
2804 mk->io_handler.set_lamp(IOHandler::LAMP_NONE);
2805 mk->voice_player.stop(VoicePlayer::STOP_NOW);
2809 MK_VIII::AlertHandler::reset ()
2814 repeated_alerts = 0;
2815 altitude_callout_voice = NULL;
2819 MK_VIII::AlertHandler::update ()
2821 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
2824 // Lamps and voices are prioritized according to [SPEC] 6.9.2.
2826 // Voices which can interrupt other voices (VoicePlayer::PLAY_NOW)
2827 // are specified by [INSTALL] appendix E 5.3.5.
2829 // When a voice is overriden by a higher priority voice and the
2830 // overriding voice stops, we restore the previous voice if it was
2831 // looped (this is not specified by [SPEC] but seems to make sense).
2835 if (has_alerts(ALERT_MODE1_PULL_UP | ALERT_MODE2A | ALERT_MODE2B))
2836 mk->io_handler.set_lamp(IOHandler::LAMP_WARNING);
2837 else if (has_alerts(ALERT_MODE1_SINK_RATE
2838 | ALERT_MODE2A_PREFACE
2839 | ALERT_MODE2B_PREFACE
2840 | ALERT_MODE2A_ALTITUDE_GAIN_TERRAIN_CLOSING
2841 | ALERT_MODE2A_ALTITUDE_GAIN
2842 | ALERT_MODE2B_LANDING_MODE
2844 | ALERT_MODE4_TOO_LOW_FLAPS
2845 | ALERT_MODE4_TOO_LOW_GEAR
2846 | ALERT_MODE4AB_TOO_LOW_TERRAIN
2847 | ALERT_MODE4C_TOO_LOW_TERRAIN
2848 | ALERT_TCF_TOO_LOW_TERRAIN))
2849 mk->io_handler.set_lamp(IOHandler::LAMP_CAUTION);
2850 else if (has_alerts(ALERT_MODE5_SOFT | ALERT_MODE5_HARD))
2851 mk->io_handler.set_lamp(IOHandler::LAMP_GLIDESLOPE);
2853 mk->io_handler.set_lamp(IOHandler::LAMP_NONE);
2857 if (select_voice_alerts(ALERT_MODE1_PULL_UP))
2859 if (! has_old_alerts(ALERT_MODE1_PULL_UP))
2861 if (mk->voice_player.voice != mk_voice(sink_rate_pause_sink_rate))
2862 mk->voice_player.play(mk_voice(sink_rate), VoicePlayer::PLAY_NOW);
2863 mk->voice_player.play(mk_voice(pull_up), VoicePlayer::PLAY_LOOPED);
2866 else if (select_voice_alerts(ALERT_MODE2A_PREFACE | ALERT_MODE2B_PREFACE))
2868 if (! has_old_alerts(ALERT_MODE2A_PREFACE | ALERT_MODE2B_PREFACE))
2869 mk->voice_player.play(mk_voice(terrain_pause_terrain), VoicePlayer::PLAY_NOW);
2871 else if (select_voice_alerts(ALERT_MODE2A | ALERT_MODE2B))
2873 if (mk->voice_player.voice != mk_voice(pull_up))
2874 mk->voice_player.play(mk_voice(pull_up), VoicePlayer::PLAY_NOW | VoicePlayer::PLAY_LOOPED);
2876 else if (select_voice_alerts(ALERT_MODE2A_ALTITUDE_GAIN_TERRAIN_CLOSING | ALERT_MODE2B_LANDING_MODE))
2878 if (mk->voice_player.voice != mk_voice(terrain))
2879 mk->voice_player.play(mk_voice(terrain), VoicePlayer::PLAY_LOOPED);
2881 else if (select_voice_alerts(ALERT_MODE6_MINIMUMS))
2883 if (! has_old_alerts(ALERT_MODE6_MINIMUMS))
2884 mk->voice_player.play(mk_voice(minimums_minimums));
2886 else if (select_voice_alerts(ALERT_MODE4AB_TOO_LOW_TERRAIN | ALERT_MODE4C_TOO_LOW_TERRAIN))
2888 if (must_play_voice(ALERT_MODE4AB_TOO_LOW_TERRAIN | ALERT_MODE4C_TOO_LOW_TERRAIN))
2889 mk->voice_player.play(mk_voice(too_low_terrain));
2891 else if (select_voice_alerts(ALERT_TCF_TOO_LOW_TERRAIN))
2893 if (must_play_voice(ALERT_TCF_TOO_LOW_TERRAIN))
2894 mk->voice_player.play(mk_voice(too_low_terrain));
2896 else if (select_voice_alerts(ALERT_MODE6_ALTITUDE_CALLOUT))
2898 if (! has_old_alerts(ALERT_MODE6_ALTITUDE_CALLOUT))
2900 assert(altitude_callout_voice != NULL);
2901 mk->voice_player.play(altitude_callout_voice);
2904 else if (select_voice_alerts(ALERT_MODE4_TOO_LOW_GEAR))
2906 if (must_play_voice(ALERT_MODE4_TOO_LOW_GEAR))
2907 mk->voice_player.play(mk->mode4_handler.conf.voice_too_low_gear);
2909 else if (select_voice_alerts(ALERT_MODE4_TOO_LOW_FLAPS))
2911 if (must_play_voice(ALERT_MODE4_TOO_LOW_FLAPS))
2912 mk->voice_player.play(mk_voice(too_low_flaps));
2914 else if (select_voice_alerts(ALERT_MODE1_SINK_RATE))
2916 if (must_play_voice(ALERT_MODE1_SINK_RATE))
2917 mk->voice_player.play(mk_voice(sink_rate_pause_sink_rate));
2918 // [SPEC] 6.2.1: "During the time that the voice message for the
2919 // outer envelope is inhibited and the caution/warning lamp is
2920 // on, the Mode 5 alert message is allowed to annunciate for
2921 // excessive glideslope deviation conditions."
2922 else if (mk->voice_player.voice != mk_voice(sink_rate_pause_sink_rate)
2923 && mk->voice_player.next_voice != mk_voice(sink_rate_pause_sink_rate))
2925 if (has_alerts(ALERT_MODE5_HARD))
2927 voice_alerts |= ALERT_MODE5_HARD;
2928 if (mk->voice_player.voice != mk_voice(hard_glideslope))
2929 mk->voice_player.play(mk_voice(hard_glideslope), VoicePlayer::PLAY_LOOPED);
2931 else if (has_alerts(ALERT_MODE5_SOFT))
2933 voice_alerts |= ALERT_MODE5_SOFT;
2934 if (must_play_voice(ALERT_MODE5_SOFT))
2935 mk->voice_player.play(mk_voice(soft_glideslope));
2939 else if (select_voice_alerts(ALERT_MODE3))
2941 if (must_play_voice(ALERT_MODE3))
2942 mk->voice_player.play(mk_voice(dont_sink_pause_dont_sink));
2944 else if (select_voice_alerts(ALERT_MODE5_HARD))
2946 if (mk->voice_player.voice != mk_voice(hard_glideslope))
2947 mk->voice_player.play(mk_voice(hard_glideslope), VoicePlayer::PLAY_LOOPED);
2949 else if (select_voice_alerts(ALERT_MODE5_SOFT))
2951 if (must_play_voice(ALERT_MODE5_SOFT))
2952 mk->voice_player.play(mk_voice(soft_glideslope));
2954 else if (select_voice_alerts(ALERT_MODE6_LOW_BANK_ANGLE_3))
2956 if (mk->voice_player.voice != mk_voice(bank_angle_bank_angle_3))
2957 mk->voice_player.play(mk_voice(bank_angle_bank_angle_3), VoicePlayer::PLAY_LOOPED);
2959 else if (select_voice_alerts(ALERT_MODE6_HIGH_BANK_ANGLE_3))
2961 if (mk->voice_player.voice != mk_voice(bank_angle_pause_bank_angle_3))
2962 mk->voice_player.play(mk_voice(bank_angle_pause_bank_angle_3), VoicePlayer::PLAY_LOOPED);
2964 else if (select_voice_alerts(ALERT_MODE6_LOW_BANK_ANGLE_2))
2966 if (! has_old_alerts(ALERT_MODE6_LOW_BANK_ANGLE_2 | ALERT_MODE6_HIGH_BANK_ANGLE_2))
2967 mk->voice_player.play(mk_voice(bank_angle_bank_angle));
2969 else if (select_voice_alerts(ALERT_MODE6_HIGH_BANK_ANGLE_2))
2971 if (! has_old_alerts(ALERT_MODE6_LOW_BANK_ANGLE_2 | ALERT_MODE6_HIGH_BANK_ANGLE_2))
2972 mk->voice_player.play(mk_voice(bank_angle_pause_bank_angle));
2974 else if (select_voice_alerts(ALERT_MODE6_LOW_BANK_ANGLE_1))
2976 if (! has_old_alerts(ALERT_MODE6_LOW_BANK_ANGLE_1 | ALERT_MODE6_HIGH_BANK_ANGLE_1))
2977 mk->voice_player.play(mk_voice(bank_angle_bank_angle));
2979 else if (select_voice_alerts(ALERT_MODE6_HIGH_BANK_ANGLE_1))
2981 if (! has_old_alerts(ALERT_MODE6_LOW_BANK_ANGLE_1 | ALERT_MODE6_HIGH_BANK_ANGLE_1))
2982 mk->voice_player.play(mk_voice(bank_angle_pause_bank_angle));
2987 old_alerts = alerts;
2988 repeated_alerts = 0;
2989 altitude_callout_voice = NULL;
2993 MK_VIII::AlertHandler::set_alerts (unsigned int _alerts,
2995 VoicePlayer::Voice *_altitude_callout_voice)
2998 if (test_bits(flags, ALERT_FLAG_REPEAT))
2999 repeated_alerts |= _alerts;
3000 if (_altitude_callout_voice)
3001 altitude_callout_voice = _altitude_callout_voice;
3005 MK_VIII::AlertHandler::unset_alerts (unsigned int _alerts)
3008 repeated_alerts &= ~_alerts;
3011 ///////////////////////////////////////////////////////////////////////////////
3012 // StateHandler ///////////////////////////////////////////////////////////////
3013 ///////////////////////////////////////////////////////////////////////////////
3016 MK_VIII::StateHandler::update_ground ()
3020 if (! mk_ainput(computed_airspeed).ncd && mk_ainput(computed_airspeed).get() > 60
3021 && ! mk_data(radio_altitude).ncd && mk_data(radio_altitude).get() > 30)
3023 if (potentially_airborne_timer.start_or_elapsed() > 1)
3027 potentially_airborne_timer.stop();
3031 if (! mk_ainput(computed_airspeed).ncd && mk_ainput(computed_airspeed).get() < 40
3032 && ! mk_data(radio_altitude).ncd && mk_data(radio_altitude).get() < 30)
3038 MK_VIII::StateHandler::enter_ground ()
3041 mk->io_handler.enter_ground();
3043 // [SPEC] 6.0.1 does not specify this, but it seems to be an
3044 // omission; at this point, we must make sure that we are in takeoff
3045 // mode (otherwise the next takeoff might happen in approach mode).
3051 MK_VIII::StateHandler::leave_ground ()
3054 mk->mode2_handler.leave_ground();
3058 MK_VIII::StateHandler::update_takeoff ()
3062 // [SPEC] 6.0.2 is somewhat confusing: it mentions hardcoded
3063 // terrain clearance (500, 750) and airspeed (178, 200) values,
3064 // but it also mentions the mode 4A boundary, which varies as a
3065 // function of aircraft type. We consider that the mentioned
3066 // values are examples, and that we should use the mode 4A upper
3069 if (! mk_data(terrain_clearance).ncd
3070 && ! mk_ainput(computed_airspeed).ncd
3071 && mk_data(terrain_clearance).get() > mk->mode4_handler.get_upper_agl(mk->mode4_handler.conf.modes->ac))
3076 if (! mk_data(radio_altitude).ncd
3077 && mk_data(radio_altitude).get() < mk->mode4_handler.conf.modes->b->min_agl1
3078 && mk->io_handler.flaps_down()
3079 && mk_dinput(landing_gear))
3085 MK_VIII::StateHandler::enter_takeoff ()
3088 mk->io_handler.enter_takeoff();
3089 mk->mode2_handler.enter_takeoff();
3090 mk->mode3_handler.enter_takeoff();
3091 mk->mode6_handler.enter_takeoff();
3095 MK_VIII::StateHandler::leave_takeoff ()
3098 mk->mode6_handler.leave_takeoff();
3102 MK_VIII::StateHandler::post_reposition ()
3104 // Synchronize the state with the current situation.
3107 (! mk_ainput(computed_airspeed).ncd && mk_ainput(computed_airspeed).get() > 60
3108 && ! mk_data(radio_altitude).ncd && mk_data(radio_altitude).get() > 30);
3110 bool _takeoff = _ground;
3112 if (ground && ! _ground)
3114 else if (! ground && _ground)
3117 if (takeoff && ! _takeoff)
3119 else if (! takeoff && _takeoff)
3124 MK_VIII::StateHandler::update ()
3126 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
3133 ///////////////////////////////////////////////////////////////////////////////
3134 // Mode1Handler ///////////////////////////////////////////////////////////////
3135 ///////////////////////////////////////////////////////////////////////////////
3138 MK_VIII::Mode1Handler::get_pull_up_bias ()
3140 // [PILOT] page 54: "Steep Approach has priority over Flap Override
3141 // if selected simultaneously."
3143 if (mk->io_handler.steep_approach())
3145 else if (conf.envelopes->flap_override_bias && mk->io_handler.flap_override())
3152 MK_VIII::Mode1Handler::is_pull_up ()
3154 if (! mk->io_handler.gpws_inhibit()
3155 && ! mk->state_handler.ground // [1]
3156 && ! mk_data(radio_altitude).ncd
3157 && ! mk_data(barometric_altitude_rate).ncd
3158 && mk_data(radio_altitude).get() > conf.envelopes->min_agl)
3160 if (mk_data(radio_altitude).get() < conf.envelopes->pull_up_max_agl1)
3162 if (mk_data(radio_altitude).get() < conf.envelopes->pull_up_min_agl1(mk_data(barometric_altitude_rate).get() + get_pull_up_bias()))
3165 else if (mk_data(radio_altitude).get() < conf.envelopes->pull_up_max_agl2)
3167 if (mk_data(radio_altitude).get() < conf.envelopes->pull_up_min_agl2(mk_data(barometric_altitude_rate).get() + get_pull_up_bias()))
3176 MK_VIII::Mode1Handler::update_pull_up ()
3180 if (! mk_test_alert(MODE1_PULL_UP))
3182 // [SPEC] 6.2.1: at least one sink rate must be issued
3183 // before switching to pull up; if no sink rate has
3184 // occurred, a 0.2 second delay is used.
3185 if (mk->voice_player.voice == mk_voice(sink_rate_pause_sink_rate)
3186 || pull_up_timer.start_or_elapsed() >= 0.2)
3187 mk_set_alerts(mk_alert(MODE1_PULL_UP));
3192 pull_up_timer.stop();
3193 mk_unset_alerts(mk_alert(MODE1_PULL_UP));
3198 MK_VIII::Mode1Handler::get_sink_rate_bias ()
3200 // [PILOT] page 54: "Steep Approach has priority over Flap Override
3201 // if selected simultaneously."
3203 if (mk->io_handler.steep_approach())
3205 else if (conf.envelopes->flap_override_bias && mk->io_handler.flap_override())
3207 else if (! mk_data(glideslope_deviation_dots).ncd)
3211 if (mk_data(glideslope_deviation_dots).get() <= -2)
3213 else if (mk_data(glideslope_deviation_dots).get() < 0)
3214 bias = -150 * mk_data(glideslope_deviation_dots).get();
3216 if (mk_data(radio_altitude).get() < 100)
3217 bias *= 0.01 * mk_data(radio_altitude).get();
3226 MK_VIII::Mode1Handler::is_sink_rate ()
3228 return ! mk->io_handler.gpws_inhibit()
3229 && ! mk->state_handler.ground // [1]
3230 && ! mk_data(radio_altitude).ncd
3231 && ! mk_data(barometric_altitude_rate).ncd
3232 && mk_data(radio_altitude).get() > conf.envelopes->min_agl
3233 && mk_data(radio_altitude).get() < 2450
3234 && mk_data(radio_altitude).get() < -572 - 0.6035 * (mk_data(barometric_altitude_rate).get() + get_sink_rate_bias());
3238 MK_VIII::Mode1Handler::get_sink_rate_tti ()
3240 return mk_data(radio_altitude).get() / fabs(mk_data(barometric_altitude_rate).get());
3244 MK_VIII::Mode1Handler::update_sink_rate ()
3248 if (mk_test_alert(MODE1_SINK_RATE))
3250 double tti = get_sink_rate_tti();
3251 if (tti <= sink_rate_tti * 0.8)
3253 // ~20% degradation, warn again and store the new reference tti
3254 sink_rate_tti = tti;
3255 mk_repeat_alert(mk_alert(MODE1_SINK_RATE));
3260 if (sink_rate_timer.start_or_elapsed() >= 0.8)
3262 mk_set_alerts(mk_alert(MODE1_SINK_RATE));
3263 sink_rate_tti = get_sink_rate_tti();
3269 sink_rate_timer.stop();
3270 mk_unset_alerts(mk_alert(MODE1_SINK_RATE));
3275 MK_VIII::Mode1Handler::update ()
3277 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
3284 ///////////////////////////////////////////////////////////////////////////////
3285 // Mode2Handler ///////////////////////////////////////////////////////////////
3286 ///////////////////////////////////////////////////////////////////////////////
3289 MK_VIII::Mode2Handler::ClosureRateFilter::limit_radio_altitude_rate (double r)
3291 // Limit radio altitude rate according to aircraft configuration,
3292 // allowing maximum sensitivity during cruise while providing
3293 // progressively less sensitivity during the landing phases of
3296 if (! mk_data(glideslope_deviation_dots).ncd && fabs(mk_data(glideslope_deviation_dots).get()) <= 2)
3297 { // gs deviation <= +- 2 dots
3298 if (mk_dinput(landing_gear) && mk->io_handler.flaps_down())
3299 SG_CLAMP_RANGE(r, -1000.0, 3000.0);
3300 else if (mk_dinput(landing_gear) || mk->io_handler.flaps_down())
3301 SG_CLAMP_RANGE(r, 0.0, 4000.0);
3303 SG_CLAMP_RANGE(r, 1000.0, 5000.0);
3306 { // no ILS, or gs deviation > +- 2 dots
3307 if (mk_dinput(landing_gear) && mk->io_handler.flaps_down())
3308 SG_CLAMP_RANGE(r, 0.0, 4000.0);
3309 else if (mk_dinput(landing_gear) || mk->io_handler.flaps_down())
3310 SG_CLAMP_RANGE(r, 1000.0, 5000.0);
3318 MK_VIII::Mode2Handler::ClosureRateFilter::init ()
3321 last_ra.set(&mk_data(radio_altitude));
3322 last_ba.set(&mk_ainput(uncorrected_barometric_altitude));
3329 MK_VIII::Mode2Handler::ClosureRateFilter::update ()
3331 double elapsed = timer.start_or_elapsed();
3335 if (! mk_data(radio_altitude).ncd && ! mk_ainput(uncorrected_barometric_altitude).ncd)
3337 if (! last_ra.ncd && ! last_ba.ncd)
3339 // compute radio and barometric altitude rates (positive value = descent)
3340 double ra_rate = -(mk_data(radio_altitude).get() - last_ra.get()) / elapsed * 60;
3341 double ba_rate = -(mk_ainput(uncorrected_barometric_altitude).get() - last_ba.get()) / elapsed * 60;
3343 // limit radio altitude rate according to aircraft configuration
3344 ra_rate = limit_radio_altitude_rate(ra_rate);
3346 // apply a low-pass filter to the radio altitude rate
3347 ra_rate = ra_filter.filter(ra_rate);
3348 // apply a high-pass filter to the barometric altitude rate
3349 ba_rate = ba_filter.filter(ba_rate);
3351 // combine both rates to obtain a closure rate
3352 output.set(ra_rate + ba_rate);
3365 last_ra.set(&mk_data(radio_altitude));
3366 last_ba.set(&mk_ainput(uncorrected_barometric_altitude));
3370 MK_VIII::Mode2Handler::b_conditions ()
3372 return mk->io_handler.flaps_down()
3373 || (! mk_data(glideslope_deviation_dots).ncd && fabs(mk_data(glideslope_deviation_dots).get()) < 2)
3374 || takeoff_timer.running;
3378 MK_VIII::Mode2Handler::is_a ()
3380 if (! mk->io_handler.gpws_inhibit()
3381 && ! mk->state_handler.ground // [1]
3382 && ! mk_data(radio_altitude).ncd
3383 && ! mk_ainput(computed_airspeed).ncd
3384 && ! closure_rate_filter.output.ncd
3385 && ! b_conditions())
3387 if (mk_data(radio_altitude).get() < 1220)
3389 if (mk_data(radio_altitude).get() < -1579 + 0.7895 * closure_rate_filter.output.get())
3396 if (mk_ainput(computed_airspeed).get() <= conf->airspeed1)
3398 else if (mk_ainput(computed_airspeed).get() >= conf->airspeed2)
3401 upper_limit = 1650 + 8.9 * (mk_ainput(computed_airspeed).get() - conf->airspeed1);
3403 if (mk_data(radio_altitude).get() < upper_limit)
3405 if (mk_data(radio_altitude).get() < 522 + 0.1968 * closure_rate_filter.output.get())
3415 MK_VIII::Mode2Handler::is_b ()
3417 if (! mk->io_handler.gpws_inhibit()
3418 && ! mk->state_handler.ground // [1]
3419 && ! mk_data(radio_altitude).ncd
3420 && ! mk_data(barometric_altitude_rate).ncd
3421 && ! closure_rate_filter.output.ncd
3423 && mk_data(radio_altitude).get() < 789)
3427 if (mk->io_handler.flaps_down())
3429 if (mk_data(barometric_altitude_rate).get() > -400)
3431 else if (mk_data(barometric_altitude_rate).get() < -1000)
3434 lower_limit = -66.777 - 0.667 * mk_data(barometric_altitude_rate).get();
3439 if (mk_data(radio_altitude).get() > lower_limit)
3441 if (mk_data(radio_altitude).get() < -1579 + 0.7895 * closure_rate_filter.output.get())
3450 MK_VIII::Mode2Handler::check_pull_up (unsigned int preface_alert,
3453 if (pull_up_timer.running)
3455 if (pull_up_timer.elapsed() >= 1)
3457 mk_unset_alerts(preface_alert);
3458 mk_set_alerts(alert);
3463 if (! mk->voice_player.voice)
3464 pull_up_timer.start();
3469 MK_VIII::Mode2Handler::update_a ()
3473 if (mk_test_alert(MODE2A_PREFACE))
3474 check_pull_up(mk_alert(MODE2A_PREFACE), mk_alert(MODE2A));
3475 else if (! mk_test_alert(MODE2A))
3477 mk_unset_alerts(mk_alert(MODE2A_ALTITUDE_GAIN) | mk_alert(MODE2A_ALTITUDE_GAIN_TERRAIN_CLOSING));
3478 mk_set_alerts(mk_alert(MODE2A_PREFACE));
3479 a_start_time = globals->get_sim_time_sec();
3480 pull_up_timer.stop();
3485 if (mk_test_alert(MODE2A_ALTITUDE_GAIN))
3487 if (mk->io_handler.gpws_inhibit()
3488 || mk->state_handler.ground // [1]
3489 || a_altitude_gain_timer.elapsed() >= 45
3490 || mk_data(radio_altitude).ncd
3491 || mk_ainput(uncorrected_barometric_altitude).ncd
3492 || mk_ainput(uncorrected_barometric_altitude).get() - a_altitude_gain_alt >= 300
3493 // [PILOT] page 12: "the visual alert will remain on
3494 // until [...] landing flaps or the flap override switch
3496 || mk->io_handler.flaps_down())
3498 // exit altitude gain mode
3499 a_altitude_gain_timer.stop();
3500 mk_unset_alerts(mk_alert(MODE2A_ALTITUDE_GAIN) | mk_alert(MODE2A_ALTITUDE_GAIN_TERRAIN_CLOSING));
3504 // [SPEC] 6.2.2.2: "If the terrain starts to fall away
3505 // during this altitude gain time, the voice will shut
3507 if (closure_rate_filter.output.get() < 0)
3508 mk_unset_alerts(mk_alert(MODE2A_ALTITUDE_GAIN_TERRAIN_CLOSING));
3511 else if (mk_test_alerts(mk_alert(MODE2A_PREFACE) | mk_alert(MODE2A)))
3513 mk_unset_alerts(mk_alert(MODE2A_PREFACE) | mk_alert(MODE2A));
3515 if (! mk->io_handler.gpws_inhibit()
3516 && ! mk->state_handler.ground // [1]
3517 && globals->get_sim_time_sec() - a_start_time > 3
3518 && ! mk->io_handler.flaps_down()
3519 && ! mk_data(radio_altitude).ncd
3520 && ! mk_ainput(uncorrected_barometric_altitude).ncd)
3522 // [SPEC] 6.2.2.2: mode 2A envelope violated for more
3523 // than 3 seconds, enable altitude gain feature
3525 a_altitude_gain_timer.start();
3526 a_altitude_gain_alt = mk_ainput(uncorrected_barometric_altitude).get();
3528 unsigned int alerts = mk_alert(MODE2A_ALTITUDE_GAIN);
3529 if (closure_rate_filter.output.get() > 0)
3530 alerts |= mk_alert(MODE2A_ALTITUDE_GAIN_TERRAIN_CLOSING);
3532 mk_set_alerts(alerts);
3539 MK_VIII::Mode2Handler::update_b ()
3543 // handle normal mode
3545 if (b && (! mk_dinput(landing_gear) || ! mk->io_handler.flaps_down()))
3547 if (mk_test_alert(MODE2B_PREFACE))
3548 check_pull_up(mk_alert(MODE2B_PREFACE), mk_alert(MODE2B));
3549 else if (! mk_test_alert(MODE2B))
3551 mk_set_alerts(mk_alert(MODE2B_PREFACE));
3552 pull_up_timer.stop();
3556 mk_unset_alerts(mk_alert(MODE2B_PREFACE) | mk_alert(MODE2B));
3558 // handle landing mode ([SPEC] 6.2.2.3: "If both landing gear and
3559 // flaps are in the landing configuration, then the message will be
3562 if (b && mk_dinput(landing_gear) && mk->io_handler.flaps_down())
3563 mk_set_alerts(mk_alert(MODE2B_LANDING_MODE));
3565 mk_unset_alerts(mk_alert(MODE2B_LANDING_MODE));
3569 MK_VIII::Mode2Handler::boot ()
3571 closure_rate_filter.init();
3575 MK_VIII::Mode2Handler::power_off ()
3577 // [SPEC] 6.0.4: "This latching function is not power saved and a
3578 // system reset will force it false."
3579 takeoff_timer.stop();
3583 MK_VIII::Mode2Handler::leave_ground ()
3585 // takeoff, reset timer
3586 takeoff_timer.start();
3590 MK_VIII::Mode2Handler::enter_takeoff ()
3592 // reset timer, in case it's a go-around
3593 takeoff_timer.start();
3597 MK_VIII::Mode2Handler::update ()
3599 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
3602 closure_rate_filter.update();
3604 if (takeoff_timer.running && takeoff_timer.elapsed() >= 60)
3605 takeoff_timer.stop();
3611 ///////////////////////////////////////////////////////////////////////////////
3612 // Mode3Handler ///////////////////////////////////////////////////////////////
3613 ///////////////////////////////////////////////////////////////////////////////
3616 MK_VIII::Mode3Handler::max_alt_loss (double _bias)
3618 return conf->max_alt_loss(mk->io_handler.flap_override(), mk_data(radio_altitude).get()) + mk_data(radio_altitude).get() * _bias;
3622 MK_VIII::Mode3Handler::get_bias (double initial_bias, double alt_loss)
3624 if (mk_data(radio_altitude).get() > 0)
3625 while (alt_loss > max_alt_loss(initial_bias))
3626 initial_bias += 0.2;
3628 return initial_bias;
3632 MK_VIII::Mode3Handler::is (double *alt_loss)
3634 if (has_descent_alt)
3636 int max_agl = conf->max_agl(mk->io_handler.flap_override());
3638 if (mk_ainput(uncorrected_barometric_altitude).ncd
3639 || mk_ainput(uncorrected_barometric_altitude).get() > descent_alt
3640 || mk_data(radio_altitude).ncd
3641 || mk_data(radio_altitude).get() > max_agl)
3644 has_descent_alt = false;
3648 // gear/flaps: [SPEC] 1.3.1.3
3649 if (! mk->io_handler.gpws_inhibit()
3650 && ! mk->state_handler.ground // [1]
3651 && (! mk_dinput(landing_gear) || ! mk->io_handler.flaps_down())
3652 && ! mk_data(barometric_altitude_rate).ncd
3653 && ! mk_ainput(uncorrected_barometric_altitude).ncd
3654 && ! mk_data(radio_altitude).ncd
3655 && mk_data(barometric_altitude_rate).get() < 0)
3657 double _alt_loss = descent_alt - mk_ainput(uncorrected_barometric_altitude).get();
3658 if (armed || (mk_data(radio_altitude).get() > conf->min_agl
3659 && mk_data(radio_altitude).get() < max_agl
3660 && _alt_loss > max_alt_loss(0)))
3662 *alt_loss = _alt_loss;
3670 if (! mk_data(barometric_altitude_rate).ncd
3671 && ! mk_ainput(uncorrected_barometric_altitude).ncd
3672 && mk_data(barometric_altitude_rate).get() < 0)
3674 has_descent_alt = true;
3675 descent_alt = mk_ainput(uncorrected_barometric_altitude).get();
3683 MK_VIII::Mode3Handler::enter_takeoff ()
3686 has_descent_alt = false;
3690 MK_VIII::Mode3Handler::update ()
3692 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
3695 if (mk->state_handler.takeoff)
3699 if (! mk->state_handler.ground /* [1] */ && is(&alt_loss))
3701 if (mk_test_alert(MODE3))
3703 double new_bias = get_bias(bias, alt_loss);
3704 if (new_bias > bias)
3707 mk_repeat_alert(mk_alert(MODE3));
3713 bias = get_bias(0.2, alt_loss);
3714 mk_set_alerts(mk_alert(MODE3));
3721 mk_unset_alerts(mk_alert(MODE3));
3724 ///////////////////////////////////////////////////////////////////////////////
3725 // Mode4Handler ///////////////////////////////////////////////////////////////
3726 ///////////////////////////////////////////////////////////////////////////////
3728 // FIXME: for turbofans, the upper limit should be reduced from 1000
3729 // to 800 ft if a sudden change in radio altitude is detected, in
3730 // order to reduce nuisance alerts caused by overflying another
3731 // aircraft (see [PILOT] page 16).
3734 MK_VIII::Mode4Handler::get_upper_agl (const EnvelopesConfiguration *c)
3736 if (mk_ainput(computed_airspeed).get() >= c->airspeed2)
3738 else if (mk_ainput(computed_airspeed).get() >= c->airspeed1)
3739 return c->min_agl2(mk_ainput(computed_airspeed).get());
3744 const MK_VIII::Mode4Handler::EnvelopesConfiguration *
3745 MK_VIII::Mode4Handler::get_ab_envelope ()
3747 // [SPEC] 6.2.4.1: "The Mode 4B envelope can also be selected by
3748 // setting flaps to landing configuration or by selecting flap
3750 return mk_dinput(landing_gear) || mk->io_handler.flaps_down()
3756 MK_VIII::Mode4Handler::get_bias (double initial_bias, double min_agl)
3758 while (mk_data(radio_altitude).get() < min_agl - min_agl * initial_bias)
3759 initial_bias += 0.2;
3761 return initial_bias;
3765 MK_VIII::Mode4Handler::handle_alert (unsigned int alert,
3769 if (mk_test_alerts(alert))
3771 double new_bias = get_bias(*bias, min_agl);
3772 if (new_bias > *bias)
3775 mk_repeat_alert(alert);
3780 *bias = get_bias(0.2, min_agl);
3781 mk_set_alerts(alert);
3786 MK_VIII::Mode4Handler::update_ab ()
3788 if (! mk->io_handler.gpws_inhibit()
3789 && ! mk->state_handler.ground
3790 && ! mk->state_handler.takeoff
3791 && ! mk_data(radio_altitude).ncd
3792 && ! mk_ainput(computed_airspeed).ncd
3793 && mk_data(radio_altitude).get() > 30)
3795 const EnvelopesConfiguration *c = get_ab_envelope();
3796 if (mk_ainput(computed_airspeed).get() < c->airspeed1)
3798 if (mk_dinput(landing_gear))
3800 if (! mk->io_handler.flaps_down() && mk_data(radio_altitude).get() < c->min_agl1)
3802 handle_alert(mk_alert(MODE4_TOO_LOW_FLAPS), c->min_agl1, &ab_bias);
3808 if (mk_data(radio_altitude).get() < c->min_agl1)
3810 handle_alert(mk_alert(MODE4_TOO_LOW_GEAR), c->min_agl1, &ab_bias);
3817 mk_unset_alerts(mk_alert(MODE4_TOO_LOW_FLAPS) | mk_alert(MODE4_TOO_LOW_GEAR));
3821 MK_VIII::Mode4Handler::update_ab_expanded ()
3823 if (! mk->io_handler.gpws_inhibit()
3824 && ! mk->state_handler.ground
3825 && ! mk->state_handler.takeoff
3826 && ! mk_data(radio_altitude).ncd
3827 && ! mk_ainput(computed_airspeed).ncd
3828 && mk_data(radio_altitude).get() > 30)
3830 const EnvelopesConfiguration *c = get_ab_envelope();
3831 if (mk_ainput(computed_airspeed).get() >= c->airspeed1)
3833 double min_agl = get_upper_agl(c);
3834 if (mk_data(radio_altitude).get() < min_agl)
3836 handle_alert(mk_alert(MODE4AB_TOO_LOW_TERRAIN), min_agl, &ab_expanded_bias);
3842 mk_unset_alerts(mk_alert(MODE4AB_TOO_LOW_TERRAIN));
3846 MK_VIII::Mode4Handler::update_c ()
3848 if (! mk->io_handler.gpws_inhibit()
3849 && ! mk->state_handler.ground // [1]
3850 && mk->state_handler.takeoff
3851 && ! mk_data(radio_altitude).ncd
3852 && ! mk_data(terrain_clearance).ncd
3853 && mk_data(radio_altitude).get() > 30
3854 && (! mk_dinput(landing_gear) || ! mk->io_handler.flaps_down())
3855 && mk_data(radio_altitude).get() < get_upper_agl(conf.modes->ac)
3856 && mk_data(radio_altitude).get() < mk_data(terrain_clearance).get())
3857 handle_alert(mk_alert(MODE4C_TOO_LOW_TERRAIN), mk_data(terrain_clearance).get(), &c_bias);
3859 mk_unset_alerts(mk_alert(MODE4C_TOO_LOW_TERRAIN));
3863 MK_VIII::Mode4Handler::update ()
3865 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
3869 update_ab_expanded();
3873 ///////////////////////////////////////////////////////////////////////////////
3874 // Mode5Handler ///////////////////////////////////////////////////////////////
3875 ///////////////////////////////////////////////////////////////////////////////
3878 MK_VIII::Mode5Handler::is_hard ()
3880 if (mk_data(radio_altitude).get() > 30
3881 && mk_data(radio_altitude).get() < 300
3882 && mk_data(glideslope_deviation_dots).get() > 2)
3884 if (mk_data(radio_altitude).get() < 150)
3886 if (mk_data(radio_altitude).get() > 293 - 71.43 * mk_data(glideslope_deviation_dots).get())
3897 MK_VIII::Mode5Handler::is_soft (double bias)
3899 if (mk_data(radio_altitude).get() > 30)
3901 double bias_dots = 1.3 * bias;
3902 if (mk_data(glideslope_deviation_dots).get() > 1.3 + bias_dots)
3904 if (mk_data(radio_altitude).get() < 150)
3906 if (mk_data(radio_altitude).get() > 243 - 71.43 * (mk_data(glideslope_deviation_dots).get() - bias_dots))
3913 if (mk_data(barometric_altitude_rate).ncd)
3917 if (mk_data(barometric_altitude_rate).get() >= 0)
3919 else if (mk_data(barometric_altitude_rate).get() < -500)
3922 upper_limit = -mk_data(barometric_altitude_rate).get() + 500;
3925 if (mk_data(radio_altitude).get() < upper_limit)
3935 MK_VIII::Mode5Handler::get_soft_bias (double initial_bias)
3937 while (is_soft(initial_bias))
3938 initial_bias += 0.2;
3940 return initial_bias;
3944 MK_VIII::Mode5Handler::update_hard (bool is)
3948 if (! mk_test_alert(MODE5_HARD))
3950 if (hard_timer.start_or_elapsed() >= 0.8)
3951 mk_set_alerts(mk_alert(MODE5_HARD));
3957 mk_unset_alerts(mk_alert(MODE5_HARD));
3962 MK_VIII::Mode5Handler::update_soft (bool is)
3966 if (! mk_test_alert(MODE5_SOFT))
3968 double new_bias = get_soft_bias(soft_bias);
3969 if (new_bias > soft_bias)
3971 soft_bias = new_bias;
3972 mk_repeat_alert(mk_alert(MODE5_SOFT));
3977 if (soft_timer.start_or_elapsed() >= 0.8)
3979 soft_bias = get_soft_bias(0.2);
3980 mk_set_alerts(mk_alert(MODE5_SOFT));
3987 mk_unset_alerts(mk_alert(MODE5_SOFT));
3992 MK_VIII::Mode5Handler::update ()
3994 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
3997 if (! mk->io_handler.gpws_inhibit()
3998 && ! mk->state_handler.ground // [1]
3999 && ! mk_dinput(glideslope_inhibit) // not on backcourse
4000 && ! mk_data(radio_altitude).ncd
4001 && ! mk_data(glideslope_deviation_dots).ncd
4002 && (! mk->io_handler.conf.localizer_enabled
4003 || mk_data(localizer_deviation_dots).ncd
4004 || mk_data(radio_altitude).get() < 500
4005 || fabs(mk_data(localizer_deviation_dots).get()) < 2)
4006 && (! mk->state_handler.takeoff || mk->io_handler.flaps_down())
4007 && mk_dinput(landing_gear)
4008 && ! mk_doutput(glideslope_cancel))
4010 update_hard(is_hard());
4011 update_soft(is_soft(0));
4020 ///////////////////////////////////////////////////////////////////////////////
4021 // Mode6Handler ///////////////////////////////////////////////////////////////
4022 ///////////////////////////////////////////////////////////////////////////////
4024 // keep sorted in descending order
4025 const int MK_VIII::Mode6Handler::altitude_callout_definitions[] =
4026 { 1000, 500, 400, 300, 200, 100, 50, 40, 30, 20, 10 };
4029 MK_VIII::Mode6Handler::reset_minimums ()
4031 minimums_issued = false;
4035 MK_VIII::Mode6Handler::reset_altitude_callouts ()
4037 for (int i = 0; i < n_altitude_callouts; i++)
4038 altitude_callouts_issued[i] = false;
4042 MK_VIII::Mode6Handler::is_playing_altitude_callout ()
4044 for (int i = 0; i < n_altitude_callouts; i++)
4045 if (mk->voice_player.voice == mk_altitude_voice(i)
4046 || mk->voice_player.next_voice == mk_altitude_voice(i))
4053 MK_VIII::Mode6Handler::is_near_minimums (double callout)
4057 if (! mk_data(decision_height).ncd)
4059 double diff = callout - mk_data(decision_height).get();
4061 if (mk_data(radio_altitude).get() >= 200)
4063 if (fabs(diff) <= 30)
4068 if (diff >= -3 && diff <= 6)
4077 MK_VIII::Mode6Handler::is_outside_band (double elevation, double callout)
4080 return elevation < callout - (elevation > 150 ? 20 : 10);
4084 MK_VIII::Mode6Handler::inhibit_smart_500 ()
4088 if (! mk_data(glideslope_deviation_dots).ncd
4089 && ! mk_dinput(glideslope_inhibit) // backcourse
4090 && ! mk_doutput(glideslope_cancel))
4092 if (mk->io_handler.conf.localizer_enabled
4093 && ! mk_data(localizer_deviation_dots).ncd)
4095 if (fabs(mk_data(localizer_deviation_dots).get()) <= 2)
4100 if (fabs(mk_data(glideslope_deviation_dots).get()) <= 2)
4109 MK_VIII::Mode6Handler::boot ()
4111 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
4114 last_decision_height = mk_dinput(decision_height);
4115 last_radio_altitude.set(&mk_data(radio_altitude));
4118 for (int i = 0; i < n_altitude_callouts; i++)
4119 altitude_callouts_issued[i] = ! mk_data(radio_altitude).ncd
4120 && mk_data(radio_altitude).get() <= altitude_callout_definitions[i];
4122 // extrapolated from [SPEC] 6.4.2
4123 minimums_issued = mk_dinput(decision_height);
4125 if (conf.above_field_voice)
4128 get_altitude_above_field(&last_altitude_above_field);
4129 // extrapolated from [SPEC] 6.4.2
4130 above_field_issued = ! last_altitude_above_field.ncd
4131 && last_altitude_above_field.get() < 550;
4136 MK_VIII::Mode6Handler::power_off ()
4138 runway_timer.stop();
4142 MK_VIII::Mode6Handler::enter_takeoff ()
4144 reset_altitude_callouts(); // [SPEC] 6.4.2
4145 reset_minimums(); // omitted by [SPEC]; common sense
4149 MK_VIII::Mode6Handler::leave_takeoff ()
4151 reset_altitude_callouts(); // [SPEC] 6.0.2
4152 reset_minimums(); // [SPEC] 6.0.2
4156 MK_VIII::Mode6Handler::set_volume (double volume)
4158 mk_voice(minimums_minimums)->set_volume(volume);
4159 mk_voice(five_hundred_above)->set_volume(volume);
4160 for (int i = 0; i < n_altitude_callouts; i++)
4161 mk_altitude_voice(i)->set_volume(volume);
4165 MK_VIII::Mode6Handler::altitude_callouts_enabled ()
4167 if (conf.minimums_enabled || conf.smart_500_enabled || conf.above_field_voice)
4170 for (int i = 0; i < n_altitude_callouts; i++)
4171 if (conf.altitude_callouts_enabled[i])
4178 MK_VIII::Mode6Handler::update_minimums ()
4180 if (! mk->io_handler.gpws_inhibit()
4181 && (mk->voice_player.voice == mk_voice(minimums_minimums)
4182 || mk->voice_player.next_voice == mk_voice(minimums_minimums)))
4185 if (! mk->io_handler.gpws_inhibit()
4186 && ! mk->state_handler.ground // [1]
4187 && conf.minimums_enabled
4188 && ! minimums_issued
4189 && mk_dinput(landing_gear)
4190 && mk_dinput(decision_height)
4191 && ! last_decision_height)
4193 minimums_issued = true;
4195 // If an altitude callout is playing, stop it now so that the
4196 // minimums callout can be played (note that proper connection
4197 // and synchronization of the radio-altitude ARINC 529 input,
4198 // decision-height discrete and decision-height ARINC 529 input
4199 // will prevent an altitude callout from being played near the
4200 // decision height).
4202 if (is_playing_altitude_callout())
4203 mk->voice_player.stop(VoicePlayer::STOP_NOW);
4205 mk_set_alerts(mk_alert(MODE6_MINIMUMS));
4208 mk_unset_alerts(mk_alert(MODE6_MINIMUMS));
4211 last_decision_height = mk_dinput(decision_height);
4215 MK_VIII::Mode6Handler::update_altitude_callouts ()
4217 if (! mk->io_handler.gpws_inhibit() && is_playing_altitude_callout())
4220 if (! mk->io_handler.gpws_inhibit()
4221 && ! mk->state_handler.ground // [1]
4222 && ! mk_data(radio_altitude).ncd)
4223 for (int i = 0; i < n_altitude_callouts && mk_data(radio_altitude).get() <= altitude_callout_definitions[i]; i++)
4224 if ((conf.altitude_callouts_enabled[i]
4225 || (altitude_callout_definitions[i] == 500
4226 && conf.smart_500_enabled))
4227 && ! altitude_callouts_issued[i]
4228 && (last_radio_altitude.ncd
4229 || last_radio_altitude.get() > altitude_callout_definitions[i]))
4231 // lock out all callouts superior or equal to this one
4232 for (int j = 0; j <= i; j++)
4233 altitude_callouts_issued[j] = true;
4235 altitude_callouts_issued[i] = true;
4236 if (! is_near_minimums(altitude_callout_definitions[i])
4237 && ! is_outside_band(mk_data(radio_altitude).get(), altitude_callout_definitions[i])
4238 && (! conf.smart_500_enabled
4239 || altitude_callout_definitions[i] != 500
4240 || ! inhibit_smart_500()))
4242 mk->alert_handler.set_altitude_callout_alert(mk_altitude_voice(i));
4247 mk_unset_alerts(mk_alert(MODE6_ALTITUDE_CALLOUT));
4250 last_radio_altitude.set(&mk_data(radio_altitude));
4254 MK_VIII::Mode6Handler::test_runway (const FGRunway *_runway)
4256 if (_runway->_length < mk->conf.runway_database)
4259 // get position of threshold
4260 double latitude, longitude, az;
4261 geo_direct_wgs_84(0,
4264 get_reciprocal_heading(_runway->_heading),
4265 _runway->_length / 2 * SG_FEET_TO_METER,
4270 // get distance to threshold
4271 double distance, az1, az2;
4272 geo_inverse_wgs_84(0,
4273 mk_data(gps_latitude).get(),
4274 mk_data(gps_longitude).get(),
4277 &az1, &az2, &distance);
4279 return distance * SG_METER_TO_NM <= 5;
4283 MK_VIII::Mode6Handler::test_airport (const FGAirport *airport)
4286 if (globals->get_runways()->search(airport->getId(), &r))
4289 if (test_runway(&r))
4292 // reciprocal runway
4293 r._heading = get_reciprocal_heading(r._heading);
4294 if (test_runway(&r))
4297 while (globals->get_runways()->next(&r) && r._id == airport->getId());
4303 MK_VIII::Mode6Handler::update_runway ()
4305 if (! mk_data(gps_latitude).ncd && ! mk_data(gps_longitude).ncd)
4307 // Search for the closest runway threshold in range 5
4308 // nm. Passing 0.5 degrees (approximatively 30 nm) to
4309 // get_closest_airport() provides enough margin for large
4310 // airports, which may have a runway located far away from the
4311 // airport's reference point.
4313 const FGAirport *airport = get_closest_airport(mk_data(gps_latitude).get(),
4314 mk_data(gps_longitude).get(),
4317 &MK_VIII::Mode6Handler::test_airport);
4320 runway.elevation = airport->getElevation();
4322 has_runway.set(airport != NULL);
4329 MK_VIII::Mode6Handler::get_altitude_above_field (Parameter<double> *parameter)
4331 if (! has_runway.ncd && has_runway.get() && ! mk_data(geometric_altitude).ncd)
4332 parameter->set(mk_data(geometric_altitude).get() - runway.elevation);
4338 MK_VIII::Mode6Handler::update_above_field_callout ()
4340 if (! conf.above_field_voice)
4343 // update_runway() has to iterate over the whole runway database
4344 // (which contains thousands of runways), so it would be unwise to
4345 // run it every frame. Run it every 3 seconds. Note that the first
4346 // iteration is run in boot().
4347 if (runway_timer.start_or_elapsed() >= 3)
4350 runway_timer.start();
4353 Parameter<double> altitude_above_field;
4354 get_altitude_above_field(&altitude_above_field);
4356 if (! mk->io_handler.gpws_inhibit()
4357 && (mk->voice_player.voice == conf.above_field_voice
4358 || mk->voice_player.next_voice == conf.above_field_voice))
4361 // handle reset term
4362 if (above_field_issued)
4364 if ((! has_runway.ncd && ! has_runway.get())
4365 || (! altitude_above_field.ncd && altitude_above_field.get() > 700))
4366 above_field_issued = false;
4369 if (! mk->io_handler.gpws_inhibit()
4370 && ! mk->state_handler.ground // [1]
4371 && ! above_field_issued
4372 && ! altitude_above_field.ncd
4373 && altitude_above_field.get() < 550
4374 && (last_altitude_above_field.ncd
4375 || last_altitude_above_field.get() >= 550))
4377 above_field_issued = true;
4379 if (! is_outside_band(altitude_above_field.get(), 500))
4381 mk->alert_handler.set_altitude_callout_alert(conf.above_field_voice);
4386 mk_unset_alerts(mk_alert(MODE6_ALTITUDE_CALLOUT));
4389 last_altitude_above_field.set(&altitude_above_field);
4393 MK_VIII::Mode6Handler::is_bank_angle (double abs_roll_angle, double bias)
4395 return conf.is_bank_angle(&mk_data(radio_altitude),
4396 abs_roll_angle - abs_roll_angle * bias,
4397 mk_dinput(autopilot_engaged));
4401 MK_VIII::Mode6Handler::is_high_bank_angle ()
4403 return mk_data(radio_altitude).ncd || mk_data(radio_altitude).get() >= 210;
4407 MK_VIII::Mode6Handler::get_bank_angle_alerts ()
4409 if (! mk->io_handler.gpws_inhibit()
4410 && ! mk->state_handler.ground // [1]
4411 && ! mk_data(roll_angle).ncd)
4413 double abs_roll_angle = fabs(mk_data(roll_angle).get());
4415 if (is_bank_angle(abs_roll_angle, 0.4))
4416 return is_high_bank_angle()
4417 ? (mk_alert(MODE6_HIGH_BANK_ANGLE_1) | mk_alert(MODE6_HIGH_BANK_ANGLE_2) | mk_alert(MODE6_HIGH_BANK_ANGLE_3))
4418 : (mk_alert(MODE6_LOW_BANK_ANGLE_1) | mk_alert(MODE6_LOW_BANK_ANGLE_2) | mk_alert(MODE6_LOW_BANK_ANGLE_3));
4419 else if (is_bank_angle(abs_roll_angle, 0.2))
4420 return is_high_bank_angle()
4421 ? (mk_alert(MODE6_HIGH_BANK_ANGLE_1) | mk_alert(MODE6_HIGH_BANK_ANGLE_2))
4422 : (mk_alert(MODE6_LOW_BANK_ANGLE_1) | mk_alert(MODE6_LOW_BANK_ANGLE_2));
4423 else if (is_bank_angle(abs_roll_angle, 0))
4424 return is_high_bank_angle()
4425 ? mk_alert(MODE6_HIGH_BANK_ANGLE_1)
4426 : mk_alert(MODE6_LOW_BANK_ANGLE_1);
4433 MK_VIII::Mode6Handler::update_bank_angle ()
4435 if (! conf.bank_angle_enabled)
4438 unsigned int alerts = get_bank_angle_alerts();
4440 // [SPEC] 6.4.4 specifies that the non-continuous alerts
4441 // (MODE6_*_BANK_ANGLE_1 and MODE6_*_BANK_ANGLE_2) are locked out
4442 // until the initial envelope is exited.
4444 if (! test_bits(alerts, mk_alert(MODE6_LOW_BANK_ANGLE_3)))
4445 mk_unset_alerts(mk_alert(MODE6_LOW_BANK_ANGLE_3));
4446 if (! test_bits(alerts, mk_alert(MODE6_HIGH_BANK_ANGLE_3)))
4447 mk_unset_alerts(mk_alert(MODE6_HIGH_BANK_ANGLE_3));
4450 mk_set_alerts(alerts);
4452 mk_unset_alerts(mk_alert(MODE6_LOW_BANK_ANGLE_1)
4453 | mk_alert(MODE6_HIGH_BANK_ANGLE_1)
4454 | mk_alert(MODE6_LOW_BANK_ANGLE_2)
4455 | mk_alert(MODE6_HIGH_BANK_ANGLE_2));
4459 MK_VIII::Mode6Handler::update ()
4461 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
4464 if (! mk->state_handler.takeoff
4465 && ! mk_data(radio_altitude).ncd
4466 && mk_data(radio_altitude).get() > 1000)
4468 reset_altitude_callouts(); // [SPEC] 6.4.2
4469 reset_minimums(); // common sense
4473 update_altitude_callouts();
4474 update_above_field_callout();
4475 update_bank_angle();
4478 ///////////////////////////////////////////////////////////////////////////////
4479 // TCFHandler /////////////////////////////////////////////////////////////////
4480 ///////////////////////////////////////////////////////////////////////////////
4482 // Gets the difference between the azimuth from @from_lat,@from_lon to
4483 // @to_lat,@to_lon, and @to_heading, in degrees.
4485 MK_VIII::TCFHandler::get_azimuth_difference (double from_lat,
4491 double az1, az2, distance;
4492 geo_inverse_wgs_84(0, from_lat, from_lon, to_lat, to_lon, &az1, &az2, &distance);
4493 return get_heading_difference(az1, to_heading);
4496 // Gets the difference between the azimuth from the current GPS
4497 // position to the center of @_runway, and the heading of @_runway, in
4500 MK_VIII::TCFHandler::get_azimuth_difference (const FGRunway *_runway)
4502 return get_azimuth_difference(mk_data(gps_latitude).get(),
4503 mk_data(gps_longitude).get(),
4509 // Selects the most likely intended destination runway of @airport,
4510 // and returns it in @_runway. For each runway, the difference between
4511 // the azimuth from the current GPS position to the center of the
4512 // runway and its heading is computed. The runway having the smallest
4515 // This selection algorithm is not specified in [SPEC], but
4516 // http://www.egpws.com/general_information/description/runway_select.htm
4517 // talks about automatic runway selection.
4519 MK_VIII::TCFHandler::select_runway (const FGAirport *airport,
4523 bool status = globals->get_runways()->search(airport->getId(), &r);
4526 double min_diff = 360;
4531 diff = get_azimuth_difference(&r);
4532 if (diff < min_diff)
4538 // reciprocal runway
4539 r._heading = get_reciprocal_heading(r._heading);
4540 diff = get_azimuth_difference(&r);
4541 if (diff < min_diff)
4547 while (globals->get_runways()->next(&r) && r._id == airport->getId());
4551 MK_VIII::TCFHandler::test_airport (const FGAirport *airport)
4554 if (globals->get_runways()->search(airport->getId(), &r))
4557 if (r._length >= mk->conf.runway_database)
4560 while (globals->get_runways()->next(&r) && r._id == airport->getId());
4566 MK_VIII::TCFHandler::update_runway ()
4569 if (! mk_data(gps_latitude).ncd && ! mk_data(gps_longitude).ncd)
4571 // Search for the intended destination runway of the closest
4572 // airport in range 15 nm. Passing 0.5 degrees (approximatively
4573 // 30 nm) to get_closest_airport() provides enough margin for
4574 // large airports, which may have a runway located far away from
4575 // the airport's reference point.
4577 const FGAirport *airport = get_closest_airport(mk_data(gps_latitude).get(),
4578 mk_data(gps_longitude).get(),
4581 &MK_VIII::TCFHandler::test_airport);
4588 select_runway(airport, &_runway);
4590 runway.center.latitude = _runway._lat;
4591 runway.center.longitude = _runway._lon;
4593 runway.elevation = airport->getElevation();
4595 double half_length_m = _runway._length / 2 * SG_FEET_TO_METER;
4596 runway.half_length = half_length_m * SG_METER_TO_NM;
4598 // b3 ________________ b0
4600 // h1>>> | e1<<<<<<<<e0 | <<<h0
4601 // |________________|
4604 // get heading to runway threshold (h0) and end (h1)
4605 runway.edges[0].heading = _runway._heading;
4606 runway.edges[1].heading = get_reciprocal_heading(_runway._heading);
4610 // get position of runway threshold (e0)
4611 geo_direct_wgs_84(0,
4612 runway.center.latitude,
4613 runway.center.longitude,
4614 runway.edges[1].heading,
4616 &runway.edges[0].position.latitude,
4617 &runway.edges[0].position.longitude,
4620 // get position of runway end (e1)
4621 geo_direct_wgs_84(0,
4622 runway.center.latitude,
4623 runway.center.longitude,
4624 runway.edges[0].heading,
4626 &runway.edges[1].position.latitude,
4627 &runway.edges[1].position.longitude,
4630 double half_width_m = _runway._width / 2 * SG_FEET_TO_METER;
4632 // get position of threshold bias area edges (b0 and b1)
4633 get_bias_area_edges(&runway.edges[0].position,
4634 runway.edges[1].heading,
4636 &runway.bias_area[0],
4637 &runway.bias_area[1]);
4639 // get position of end bias area edges (b2 and b3)
4640 get_bias_area_edges(&runway.edges[1].position,
4641 runway.edges[0].heading,
4643 &runway.bias_area[2],
4644 &runway.bias_area[3]);
4650 MK_VIII::TCFHandler::get_bias_area_edges (Position *edge,
4652 double half_width_m,
4653 Position *bias_edge1,
4654 Position *bias_edge2)
4656 double half_bias_width_m = k * SG_NM_TO_METER + half_width_m;
4657 double tmp_latitude, tmp_longitude, az;
4659 geo_direct_wgs_84(0,
4667 geo_direct_wgs_84(0,
4670 heading_substract(reciprocal, 90),
4672 &bias_edge1->latitude,
4673 &bias_edge1->longitude,
4675 geo_direct_wgs_84(0,
4678 heading_add(reciprocal, 90),
4680 &bias_edge2->latitude,
4681 &bias_edge2->longitude,
4685 // Returns true if the current GPS position is inside the edge
4686 // triangle of @edge. The edge triangle, which is specified and
4687 // represented in [SPEC] 6.3.1, is defined as an isocele right
4688 // triangle of infinite height, whose right angle is located at the
4689 // position of @edge, and whose height is parallel to the heading of
4692 MK_VIII::TCFHandler::is_inside_edge_triangle (RunwayEdge *edge)
4694 return get_azimuth_difference(mk_data(gps_latitude).get(),
4695 mk_data(gps_longitude).get(),
4696 edge->position.latitude,
4697 edge->position.longitude,
4698 edge->heading) <= 45;
4701 // Returns true if the current GPS position is inside the bias area of
4702 // the currently selected runway.
4704 MK_VIII::TCFHandler::is_inside_bias_area ()
4707 double angles_sum = 0;
4709 for (int i = 0; i < 4; i++)
4711 double az2, distance;
4712 geo_inverse_wgs_84(0,
4713 mk_data(gps_latitude).get(),
4714 mk_data(gps_longitude).get(),
4715 runway.bias_area[i].latitude,
4716 runway.bias_area[i].longitude,
4717 &az1[i], &az2, &distance);
4720 for (int i = 0; i < 4; i++)
4722 double angle = az1[i == 3 ? 0 : i + 1] - az1[i];
4726 angles_sum += angle;
4729 return angles_sum > 180;
4733 MK_VIII::TCFHandler::is_tcf ()
4735 if (mk_data(radio_altitude).get() > 10)
4739 double distance, az1, az2;
4741 geo_inverse_wgs_84(0,
4742 mk_data(gps_latitude).get(),
4743 mk_data(gps_longitude).get(),
4744 runway.center.latitude,
4745 runway.center.longitude,
4746 &az1, &az2, &distance);
4748 distance *= SG_METER_TO_NM;
4750 // distance to the inner envelope edge
4751 double edge_distance = distance - runway.half_length - k;
4753 if (edge_distance >= 15)
4755 if (mk_data(radio_altitude).get() < 700)
4758 else if (edge_distance >= 12)
4760 if (mk_data(radio_altitude).get() < 100 * edge_distance - 800)
4763 else if (edge_distance >= 4)
4765 if (mk_data(radio_altitude).get() < 400)
4768 else if (edge_distance >= 2.45)
4770 if (mk_data(radio_altitude).get() < 66.666667 * edge_distance + 133.33333)
4775 if (is_inside_edge_triangle(&runway.edges[0]) || is_inside_edge_triangle(&runway.edges[1]))
4777 if (edge_distance >= 1)
4779 if (mk_data(radio_altitude).get() < 66.666667 * edge_distance + 133.33333)
4782 else if (edge_distance >= 0.05)
4784 if (mk_data(radio_altitude).get() < 200 * edge_distance)
4790 if (! is_inside_bias_area())
4792 if (mk_data(radio_altitude).get() < 245)
4800 if (mk_data(radio_altitude).get() < 700)
4809 MK_VIII::TCFHandler::is_rfcf ()
4813 double distance, az1, az2;
4814 geo_inverse_wgs_84(0,
4815 mk_data(gps_latitude).get(),
4816 mk_data(gps_longitude).get(),
4817 runway.center.latitude,
4818 runway.center.longitude,
4819 &az1, &az2, &distance);
4821 double krf = k + mk_data(gps_vertical_figure_of_merit).get() / 200;
4822 distance = distance * SG_METER_TO_NM - runway.half_length - krf;
4826 double altitude_above_field = mk_data(geometric_altitude).get() - runway.elevation;
4828 if (distance >= 1.5)
4830 if (altitude_above_field < 300)
4833 else if (distance >= 0)
4835 if (altitude_above_field < 200 * distance)
4845 MK_VIII::TCFHandler::update ()
4847 if (mk->configuration_module.state != ConfigurationModule::STATE_OK || ! conf.enabled)
4850 // update_runway() has to iterate over the whole runway database
4851 // (which contains thousands of runways), so it would be unwise to
4852 // run it every frame. Run it every 3 seconds.
4853 if (! runway_timer.running || runway_timer.elapsed() >= 3)
4856 runway_timer.start();
4859 if (! mk_dinput(ta_tcf_inhibit)
4860 && ! mk->state_handler.ground // [1]
4861 && ! mk_data(gps_latitude).ncd
4862 && ! mk_data(gps_longitude).ncd
4863 && ! mk_data(radio_altitude).ncd
4864 && ! mk_data(geometric_altitude).ncd
4865 && ! mk_data(gps_vertical_figure_of_merit).ncd)
4870 _reference = mk_data(radio_altitude).get_pointer();
4872 _reference = mk_data(geometric_altitude).get_pointer();
4878 if (mk_test_alert(TCF_TOO_LOW_TERRAIN))
4880 double new_bias = bias;
4881 while (*reference < initial_value - initial_value * new_bias)
4884 if (new_bias > bias)
4887 mk_repeat_alert(mk_alert(TCF_TOO_LOW_TERRAIN));
4893 reference = _reference;
4894 initial_value = *reference;
4895 mk_set_alerts(mk_alert(TCF_TOO_LOW_TERRAIN));
4902 mk_unset_alerts(mk_alert(TCF_TOO_LOW_TERRAIN));
4905 ///////////////////////////////////////////////////////////////////////////////
4906 // MK_VIII ////////////////////////////////////////////////////////////////////
4907 ///////////////////////////////////////////////////////////////////////////////
4909 MK_VIII::MK_VIII (SGPropertyNode *node)
4913 properties_handler(this),
4914 power_handler(this),
4915 system_handler(this),
4916 configuration_module(this),
4917 fault_handler(this),
4920 self_test_handler(this),
4921 alert_handler(this),
4922 state_handler(this),
4923 mode1_handler(this),
4924 mode2_handler(this),
4925 mode3_handler(this),
4926 mode4_handler(this),
4927 mode5_handler(this),
4928 mode6_handler(this),
4931 for (int i = 0; i < node->nChildren(); ++i)
4933 SGPropertyNode *child = node->getChild(i);
4934 string cname = child->getName();
4935 string cval = child->getStringValue();
4937 if (cname == "name")
4939 else if (cname == "number")
4940 num = child->getIntValue();
4943 SG_LOG(SG_INSTR, SG_WARN, "Error in mk-viii config logic");
4945 SG_LOG(SG_INSTR, SG_WARN, "Section = " << name);
4953 properties_handler.init();
4954 voice_player.init();
4960 SGPropertyNode *node = fgGetNode(("/instrumentation/" + name).c_str(), num, true);
4962 configuration_module.bind(node);
4963 power_handler.bind(node);
4964 io_handler.bind(node);
4965 voice_player.bind(node);
4971 properties_handler.unbind();
4975 MK_VIII::update (double dt)
4977 power_handler.update();
4978 system_handler.update();
4980 if (system_handler.state != SystemHandler::STATE_ON)
4983 io_handler.update_inputs();
4984 io_handler.update_input_faults();
4986 voice_player.update();
4987 state_handler.update();
4989 if (self_test_handler.update())
4992 io_handler.update_internal_latches();
4993 io_handler.update_lamps();
4995 mode1_handler.update();
4996 mode2_handler.update();
4997 mode3_handler.update();
4998 mode4_handler.update();
4999 mode5_handler.update();
5000 mode6_handler.update();
5001 tcf_handler.update();
5003 alert_handler.update();
5004 io_handler.update_outputs();