1 // mk_viii.cxx -- Honeywell MK VIII EGPWS emulation
3 // Written by Jean-Yves Lefort, started September 2005.
5 // Copyright (C) 2005, 2006 Jean-Yves Lefort - jylefort@FreeBSD.org
7 // This program is free software; you can redistribute it and/or
8 // modify it under the terms of the GNU General Public License as
9 // published by the Free Software Foundation; either version 2 of the
10 // License, or (at your option) any later version.
12 // This program is distributed in the hope that it will be useful, but
13 // WITHOUT ANY WARRANTY; without even the implied warranty of
14 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 // General Public License for more details.
17 // You should have received a copy of the GNU General Public License
18 // along with this program; if not, write to the Free Software
19 // Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
21 ///////////////////////////////////////////////////////////////////////////////
25 // [PILOT] Honeywell International Inc., "MK VI and MK VIII,
26 // Enhanced Ground Proximity Warning System (EGPWS),
27 // Pilot's Guide", May 2004
29 // http://www.egpws.com/engineering_support/documents/pilot_guides/060-4314-000.pdf
31 // [SPEC] Honeywell International Inc., "Product Specification
32 // for the MK VI and MK VIII Enhanced Ground Proximity
33 // Warning System (EGPWS)", June 2004
35 // http://www.egpws.com/engineering_support/documents/specs/965-1180-601.pdf
37 // [INSTALL] Honeywell Inc., "MK VI, MK VIII, Enhanced Ground
38 // Proximity Warning System (Class A TAWS), Installation
39 // Design Guide", July 2003
41 // http://www.egpws.com/engineering_support/documents/install/060-4314-150.pdf
45 // [1] [SPEC] does not specify the "must be airborne"
46 // condition; we use it to make sure that the alert
47 // will not trigger when on the ground, since it would
51 # pragma warning( disable: 4355 )
66 #include <simgear/constants.h>
67 #include <simgear/sg_inlines.h>
68 #include <simgear/debug/logstream.hxx>
69 #include <simgear/math/sg_geodesy.hxx>
70 #include <simgear/math/sg_random.h>
71 #include <simgear/misc/sg_path.hxx>
72 #include <simgear/sound/soundmgr_openal.hxx>
73 #include <simgear/structure/exception.hxx>
77 #include "Airports/runways.hxx"
78 #include "Airports/simple.hxx"
79 #include "Include/version.h"
80 #include "Main/fg_props.hxx"
81 #include "Main/globals.hxx"
82 #include "instrument_mgr.hxx"
83 #include "mk_viii.hxx"
85 ///////////////////////////////////////////////////////////////////////////////
86 // constants //////////////////////////////////////////////////////////////////
87 ///////////////////////////////////////////////////////////////////////////////
89 #define GLIDESLOPE_DOTS_TO_DDM 0.0875 // [INSTALL] 6.2.30
90 #define GLIDESLOPE_DDM_TO_DOTS (1 / GLIDESLOPE_DOTS_TO_DDM)
92 #define LOCALIZER_DOTS_TO_DDM 0.0775 // [INSTALL] 6.2.33
93 #define LOCALIZER_DDM_TO_DOTS (1 / LOCALIZER_DOTS_TO_DDM)
95 ///////////////////////////////////////////////////////////////////////////////
96 // helpers ////////////////////////////////////////////////////////////////////
97 ///////////////////////////////////////////////////////////////////////////////
99 #define assert_not_reached() assert(false)
100 #define n_elements(_array) (sizeof(_array) / sizeof((_array)[0]))
101 #define test_bits(_bits, _test) (((_bits) & (_test)) != 0)
103 #define mk_node(_name) (mk->properties_handler.external_properties._name)
105 #define mk_dinput_feed(_name) (mk->io_handler.input_feeders.discretes._name)
106 #define mk_dinput(_name) (mk->io_handler.inputs.discretes._name)
107 #define mk_ainput_feed(_name) (mk->io_handler.input_feeders.arinc429._name)
108 #define mk_ainput(_name) (mk->io_handler.inputs.arinc429._name)
109 #define mk_doutput(_name) (mk->io_handler.outputs.discretes._name)
110 #define mk_aoutput(_name) (mk->io_handler.outputs.arinc429._name)
111 #define mk_data(_name) (mk->io_handler.data._name)
113 #define mk_voice(_name) (mk->voice_player.voices._name)
114 #define mk_altitude_voice(_i) (mk->voice_player.voices.altitude_callouts[(_i)])
116 #define mk_alert(_name) (AlertHandler::ALERT_ ## _name)
117 #define mk_alert_flag(_name) (AlertHandler::ALERT_FLAG_ ## _name)
118 #define mk_set_alerts (mk->alert_handler.set_alerts)
119 #define mk_unset_alerts (mk->alert_handler.unset_alerts)
120 #define mk_repeat_alert (mk->alert_handler.repeat_alert)
121 #define mk_test_alert(_name) (mk_test_alerts(mk_alert(_name)))
122 #define mk_test_alerts(_test) (test_bits(mk->alert_handler.alerts, (_test)))
124 const double MK_VIII::TCFHandler::k = 0.25;
127 modify_amplitude (double amplitude, double dB)
129 return amplitude * pow(10.0, dB / 20.0);
133 heading_add (double h1, double h2)
135 double result = h1 + h2;
142 heading_substract (double h1, double h2)
144 double result = h1 - h2;
151 get_heading_difference (double h1, double h2)
153 double diff = h1 - h2;
164 get_reciprocal_heading (double h)
166 return heading_add(h, 180);
169 // Searches for the closest airport whose Manhattan distance to
170 // @lat,@lon is inferior to @min_manhattan_distance (expressed in
171 // degrees) and for which @test_airport returns true. Returns NULL if
172 // no airport was found.
174 static const FGAirport *
175 get_closest_airport (double lat,
177 double min_manhattan_distance,
179 bool (C::*test_airport) (const FGAirport *))
181 const FGAirport *airport = NULL;
182 const airport_list *airport_list = globals->get_airports()->getAirportList();
184 for (size_t i = 0; i < airport_list->size(); i++)
186 const FGAirport *a = (*airport_list)[i];
187 double dist = fabs(lat - a->getLatitude()) + fabs(lon - a->getLongitude());
188 if (dist < min_manhattan_distance && (obj.*test_airport)(a))
191 min_manhattan_distance = dist;
198 ///////////////////////////////////////////////////////////////////////////////
199 // PropertiesHandler //////////////////////////////////////////////////////////
200 ///////////////////////////////////////////////////////////////////////////////
203 MK_VIII::PropertiesHandler::init ()
205 mk_node(ai_caged) = fgGetNode("/instrumentation/attitude-indicator/caged-flag", true);
206 mk_node(ai_roll) = fgGetNode("/instrumentation/attitude-indicator/indicated-roll-deg", true);
207 mk_node(ai_serviceable) = fgGetNode("/instrumentation/attitude-indicator/serviceable", true);
208 mk_node(altimeter_altitude) = fgGetNode("/instrumentation/altimeter/indicated-altitude-ft", true);
209 mk_node(altimeter_serviceable) = fgGetNode("/instrumentation/altimeter/serviceable", true);
210 mk_node(altitude) = fgGetNode("/position/altitude-ft", true);
211 mk_node(altitude_agl) = fgGetNode("/position/altitude-agl-ft", true);
212 mk_node(asi_serviceable) = fgGetNode("/instrumentation/airspeed-indicator/serviceable", true);
213 mk_node(asi_speed) = fgGetNode("/instrumentation/airspeed-indicator/indicated-speed-kt", true);
214 mk_node(autopilot_heading_lock) = fgGetNode("/autopilot/locks/heading", true);
215 mk_node(flaps) = fgGetNode("/controls/flight/flaps", true);
216 mk_node(gear_down) = fgGetNode("/controls/gear/gear-down", true);
217 mk_node(latitude) = fgGetNode("/position/latitude-deg", true);
218 mk_node(longitude) = fgGetNode("/position/longitude-deg", true);
219 mk_node(nav0_cdi_serviceable) = fgGetNode("/instrumentation/nav/cdi/serviceable", true);
220 mk_node(nav0_gs_distance) = fgGetNode("/instrumentation/nav/gs-distance", true);
221 mk_node(nav0_gs_needle_deflection) = fgGetNode("/instrumentation/nav/gs-needle-deflection", true);
222 mk_node(nav0_gs_serviceable) = fgGetNode("/instrumentation/nav/gs/serviceable", true);
223 mk_node(nav0_has_gs) = fgGetNode("/instrumentation/nav/has-gs", true);
224 mk_node(nav0_heading_needle_deflection) = fgGetNode("/instrumentation/nav/heading-needle-deflection", true);
225 mk_node(nav0_in_range) = fgGetNode("/instrumentation/nav/in-range", true);
226 mk_node(nav0_nav_loc) = fgGetNode("/instrumentation/nav/nav-loc", true);
227 mk_node(nav0_serviceable) = fgGetNode("/instrumentation/nav/serviceable", true);
228 mk_node(power) = fgGetNode(("/systems/electrical/outputs/" + mk->name).c_str(), mk->num, true);
229 mk_node(replay_state) = fgGetNode("/sim/freeze/replay-state", true);
230 mk_node(vs) = fgGetNode("/velocities/vertical-speed-fps", true);
234 MK_VIII::PropertiesHandler::unbind ()
236 vector<SGPropertyNode *>::iterator iter;
238 for (iter = tied_properties.begin(); iter != tied_properties.end(); iter++)
241 tied_properties.clear();
244 ///////////////////////////////////////////////////////////////////////////////
245 // PowerHandler ///////////////////////////////////////////////////////////////
246 ///////////////////////////////////////////////////////////////////////////////
249 MK_VIII::PowerHandler::bind (SGPropertyNode *node)
251 mk->properties_handler.tie(node, "serviceable", SGRawValuePointer<bool>(&serviceable));
255 MK_VIII::PowerHandler::handle_abnormal_voltage (bool abnormal,
261 if (timer->start_or_elapsed() >= max_duration)
262 return true; // power loss
271 MK_VIII::PowerHandler::update ()
273 double voltage = mk_node(power)->getDoubleValue();
274 bool now_powered = true;
282 if (handle_abnormal_voltage(voltage < 20.5, &low_surge_timer, 0.03))
284 if (handle_abnormal_voltage(voltage < 22.0 || voltage > 30.3, &abnormal_timer, 300))
286 if (handle_abnormal_voltage(voltage > 32.2, &high_surge_timer, 1))
288 if (handle_abnormal_voltage(voltage > 37.8, &very_high_surge_timer, 0.1))
298 power_loss_timer.stop();
301 if (power_loss_timer.start_or_elapsed() >= 0.2)
313 MK_VIII::PowerHandler::power_on ()
316 mk->system_handler.power_on();
320 MK_VIII::PowerHandler::power_off ()
323 mk->system_handler.power_off();
324 mk->voice_player.stop(VoicePlayer::STOP_NOW);
325 mk->self_test_handler.power_off(); // run before IOHandler::power_off()
326 mk->io_handler.power_off();
327 mk->mode2_handler.power_off();
328 mk->mode6_handler.power_off();
331 ///////////////////////////////////////////////////////////////////////////////
332 // SystemHandler //////////////////////////////////////////////////////////////
333 ///////////////////////////////////////////////////////////////////////////////
336 MK_VIII::SystemHandler::power_on ()
338 state = STATE_BOOTING;
340 // [SPEC] 3.5.2 mentions a 20 seconds maximum boot delay. We use a
341 // random delay between 3 and 5 seconds.
343 boot_delay = 3 + sg_random() * 2;
348 MK_VIII::SystemHandler::power_off ()
356 MK_VIII::SystemHandler::update ()
358 if (state == STATE_BOOTING)
360 if (boot_timer.elapsed() >= boot_delay)
362 last_replay_state = mk_node(replay_state)->getIntValue();
364 mk->configuration_module.boot();
366 mk->io_handler.boot();
367 mk->fault_handler.boot();
368 mk->alert_handler.boot();
370 // inputs are needed by the following boot() routines
371 mk->io_handler.update_inputs();
373 mk->mode2_handler.boot();
374 mk->mode6_handler.boot();
378 mk->io_handler.post_boot();
381 else if (state != STATE_OFF && mk->configuration_module.state == ConfigurationModule::STATE_OK)
383 // If the replay state changes, switch to reposition mode for 3
384 // seconds ([SPEC] 6.0.5) to avoid spurious alerts.
386 int replay_state = mk_node(replay_state)->getIntValue();
387 if (replay_state != last_replay_state)
389 mk->alert_handler.reposition();
391 last_replay_state = replay_state;
392 state = STATE_REPOSITION;
393 reposition_timer.start();
396 if (state == STATE_REPOSITION && reposition_timer.elapsed() >= 3)
398 // inputs are needed by StateHandler::post_reposition()
399 mk->io_handler.update_inputs();
401 mk->state_handler.post_reposition();
408 ///////////////////////////////////////////////////////////////////////////////
409 // ConfigurationModule ////////////////////////////////////////////////////////
410 ///////////////////////////////////////////////////////////////////////////////
412 MK_VIII::ConfigurationModule::ConfigurationModule (MK_VIII *device)
415 // arbitrary defaults
416 categories[CATEGORY_AIRCRAFT_MODE_TYPE_SELECT] = 0;
417 categories[CATEGORY_AIR_DATA_INPUT_SELECT] = 1;
418 categories[CATEGORY_POSITION_INPUT_SELECT] = 2;
419 categories[CATEGORY_ALTITUDE_CALLOUTS] = 13;
420 categories[CATEGORY_AUDIO_MENU_SELECT] = 0;
421 categories[CATEGORY_TERRAIN_DISPLAY_SELECT] = 1;
422 categories[CATEGORY_OPTIONS_SELECT_GROUP_1] = 124;
423 categories[CATEGORY_RADIO_ALTITUDE_INPUT_SELECT] = 2;
424 categories[CATEGORY_NAVIGATION_INPUT_SELECT] = 3;
425 categories[CATEGORY_ATTITUDE_INPUT_SELECT] = 6;
426 categories[CATEGORY_HEADING_INPUT_SELECT] = 2;
427 categories[CATEGORY_WINDSHEAR_INPUT_SELECT] = 0;
428 categories[CATEGORY_INPUT_OUTPUT_DISCRETE_TYPE_SELECT] = 7;
429 categories[CATEGORY_AUDIO_OUTPUT_LEVEL] = 0;
430 categories[CATEGORY_UNDEFINED_INPUT_SELECT_1] = 0;
431 categories[CATEGORY_UNDEFINED_INPUT_SELECT_2] = 0;
432 categories[CATEGORY_UNDEFINED_INPUT_SELECT_3] = 0;
435 static double m1_t1_min_agl1 (double vs) { return -1620 - 1.1133 * vs; }
436 static double m1_t1_min_agl2 (double vs) { return -400 - 0.4 * vs; }
437 static double m1_t4_min_agl1 (double vs) { return -1625.47 - 1.1167 * vs; }
438 static double m1_t4_min_agl2 (double vs) { return -0.1958 * vs; }
440 static int m3_t1_max_agl (bool flap_override) { return 1500; }
441 static double m3_t1_max_alt_loss (bool flap_override, double agl) { return 5.4 + 0.092 * agl; }
442 static int m3_t2_max_agl (bool flap_override) { return flap_override ? 815 : 925; }
443 static double m3_t2_max_alt_loss (bool flap_override, double agl)
445 int bias = agl > 700 ? 5 : 0;
448 return (9.0 + 0.184 * agl) + bias;
450 return (5.4 + 0.092 * agl) + bias;
453 static double m4_t1_min_agl2 (double airspeed) { return -1083 + 8.333 * airspeed; }
454 static double m4_t568_a_min_agl2 (double airspeed) { return -1523 + 11.36 * airspeed; }
455 static double m4_t79_a_min_agl2 (double airspeed) { return -1182 + 11.36 * airspeed; }
456 static double m4_t568_b_min_agl2 (double airspeed) { return -1570 + 11.6 * airspeed; }
457 static double m4_t79_b_min_agl2 (double airspeed) { return -1222 + 11.6 * airspeed; }
460 MK_VIII::ConfigurationModule::m6_t2_is_bank_angle (Parameter<double> *agl,
466 if (agl->ncd || agl->get() > 122)
467 return abs_roll_deg > 33;
471 if (agl->ncd || agl->get() > 2450)
472 return abs_roll_deg > 55;
473 else if (agl->get() > 150)
474 return agl->get() < 153.33333 * abs_roll_deg - 5983.3333;
478 return agl->get() < 4 * abs_roll_deg - 10;
479 else if (agl->get() > 5)
480 return abs_roll_deg > 10;
486 MK_VIII::ConfigurationModule::m6_t4_is_bank_angle (Parameter<double> *agl,
492 if (agl->ncd || agl->get() > 156)
493 return abs_roll_deg > 33;
497 if (agl->ncd || agl->get() > 210)
498 return abs_roll_deg > 50;
502 return agl->get() < 5.7142857 * abs_roll_deg - 75.714286;
508 MK_VIII::ConfigurationModule::read_aircraft_mode_type_select (int value)
510 static const Mode1Handler::EnvelopesConfiguration m1_t1 =
511 { false, 10, m1_t1_min_agl1, 284, m1_t1_min_agl2, 2450 };
512 static const Mode1Handler::EnvelopesConfiguration m1_t4 =
513 { true, 50, m1_t4_min_agl1, 346, m1_t4_min_agl2, 1958 };
515 static const Mode2Handler::Configuration m2_t1 = { 190, 280 };
516 static const Mode2Handler::Configuration m2_t4 = { 220, 310 };
517 static const Mode2Handler::Configuration m2_t5 = { 220, 310 };
519 static const Mode3Handler::Configuration m3_t1 =
520 { 30, m3_t1_max_agl, m3_t1_max_alt_loss };
521 static const Mode3Handler::Configuration m3_t2 =
522 { 50, m3_t2_max_agl, m3_t2_max_alt_loss };
524 static const Mode4Handler::EnvelopesConfiguration m4_t1_ac =
525 { 190, 250, 500, m4_t1_min_agl2, 1000 };
526 static const Mode4Handler::EnvelopesConfiguration m4_t5_ac =
527 { 178, 200, 500, m4_t568_a_min_agl2, 1000 };
528 static const Mode4Handler::EnvelopesConfiguration m4_t68_ac =
529 { 178, 200, 500, m4_t568_a_min_agl2, 750 };
530 static const Mode4Handler::EnvelopesConfiguration m4_t79_ac =
531 { 148, 170, 500, m4_t79_a_min_agl2, 750 };
533 static const Mode4Handler::EnvelopesConfiguration m4_t1_b =
534 { 159, 250, 245, m4_t1_min_agl2, 1000 };
535 static const Mode4Handler::EnvelopesConfiguration m4_t5_b =
536 { 148, 200, 200, m4_t568_b_min_agl2, 1000 };
537 static const Mode4Handler::EnvelopesConfiguration m4_t6_b =
538 { 150, 200, 170, m4_t568_b_min_agl2, 750 };
539 static const Mode4Handler::EnvelopesConfiguration m4_t7_b =
540 { 120, 170, 170, m4_t79_b_min_agl2, 750 };
541 static const Mode4Handler::EnvelopesConfiguration m4_t8_b =
542 { 148, 200, 150, m4_t568_b_min_agl2, 750 };
543 static const Mode4Handler::EnvelopesConfiguration m4_t9_b =
544 { 118, 170, 150, m4_t79_b_min_agl2, 750 };
546 static const Mode4Handler::ModesConfiguration m4_t1 = { &m4_t1_ac, &m4_t1_b };
547 static const Mode4Handler::ModesConfiguration m4_t5 = { &m4_t5_ac, &m4_t5_b };
548 static const Mode4Handler::ModesConfiguration m4_t6 = { &m4_t68_ac, &m4_t6_b };
549 static const Mode4Handler::ModesConfiguration m4_t7 = { &m4_t79_ac, &m4_t7_b };
550 static const Mode4Handler::ModesConfiguration m4_t8 = { &m4_t68_ac, &m4_t8_b };
551 static const Mode4Handler::ModesConfiguration m4_t9 = { &m4_t79_ac, &m4_t9_b };
553 static Mode6Handler::BankAnglePredicate m6_t2 = m6_t2_is_bank_angle;
554 static Mode6Handler::BankAnglePredicate m6_t4 = m6_t4_is_bank_angle;
556 static const IOHandler::FaultsConfiguration f_slow = { 180, 200 };
557 static const IOHandler::FaultsConfiguration f_fast = { 250, 290 };
562 const Mode1Handler::EnvelopesConfiguration *m1;
563 const Mode2Handler::Configuration *m2;
564 const Mode3Handler::Configuration *m3;
565 const Mode4Handler::ModesConfiguration *m4;
566 Mode6Handler::BankAnglePredicate m6;
567 const IOHandler::FaultsConfiguration *f;
569 } aircraft_types[] = {
570 { 0, &m1_t4, &m2_t4, &m3_t2, &m4_t6, m6_t4, &f_fast, 2000 },
571 { 1, &m1_t4, &m2_t4, &m3_t2, &m4_t8, m6_t4, &f_fast, 2000 },
572 { 2, &m1_t4, &m2_t4, &m3_t2, &m4_t5, m6_t4, &f_fast, 2000 },
573 { 3, &m1_t4, &m2_t5, &m3_t2, &m4_t7, m6_t4, &f_slow, 2000 },
574 { 4, &m1_t4, &m2_t5, &m3_t2, &m4_t9, m6_t4, &f_slow, 2000 },
575 { 254, &m1_t1, &m2_t1, &m3_t1, &m4_t1, m6_t2, &f_fast, 3500 },
576 { 255, &m1_t1, &m2_t1, &m3_t1, &m4_t1, m6_t2, &f_fast, 2000 }
579 for (int i = 0; i < n_elements(aircraft_types); i++)
580 if (aircraft_types[i].type == value)
582 mk->mode1_handler.conf.envelopes = aircraft_types[i].m1;
583 mk->mode2_handler.conf = aircraft_types[i].m2;
584 mk->mode3_handler.conf = aircraft_types[i].m3;
585 mk->mode4_handler.conf.modes = aircraft_types[i].m4;
586 mk->mode6_handler.conf.is_bank_angle = aircraft_types[i].m6;
587 mk->io_handler.conf.faults = aircraft_types[i].f;
588 mk->conf.runway_database = aircraft_types[i].runway_database;
592 state = STATE_INVALID_AIRCRAFT_TYPE;
597 MK_VIII::ConfigurationModule::read_air_data_input_select (int value)
600 return (value >= 0 && value <= 6) || (value >= 10 && value <= 13) || value == 255;
604 MK_VIII::ConfigurationModule::read_position_input_select (int value)
607 mk->io_handler.conf.use_internal_gps = true;
608 else if ((value >= 0 && value <= 5)
609 || (value >= 10 && value <= 13)
612 mk->io_handler.conf.use_internal_gps = false;
620 MK_VIII::ConfigurationModule::read_altitude_callouts (int value)
635 { 0, { MINIMUMS, SMART_500, 200, 100, 50, 40, 30, 20, 10, 0 } },
636 { 1, { MINIMUMS, SMART_500, 200, 0 } },
637 { 2, { MINIMUMS, SMART_500, 100, 50, 40, 30, 20, 10, 0 } },
638 { 3, { MINIMUMS, SMART_500, 0 } },
639 { 4, { MINIMUMS, 200, 100, 50, 40, 30, 20, 10, 0 } },
640 { 5, { MINIMUMS, 200, 0 } },
641 { 6, { MINIMUMS, 100, 50, 40, 30, 20, 10, 0 } },
643 { 8, { MINIMUMS, 0 } },
644 { 9, { MINIMUMS, 500, 200, 100, 50, 40, 30, 20, 10, 0 } },
645 { 10, { MINIMUMS, 500, 200, 0 } },
646 { 11, { MINIMUMS, 500, 100, 50, 40, 30, 20, 10, 0 } },
647 { 12, { MINIMUMS, 500, 0 } },
648 { 13, { MINIMUMS, 1000, 500, 400, 300, 200, 100, 50, 40, 30, 20, 10, 0 } },
649 { 14, { MINIMUMS, 100, 0 } },
650 { 15, { MINIMUMS, 200, 100, 0 } },
651 { 100, { FIELD_500, 0 } },
652 { 101, { FIELD_500_ABOVE, 0 } }
657 mk->mode6_handler.conf.minimums_enabled = false;
658 mk->mode6_handler.conf.smart_500_enabled = false;
659 mk->mode6_handler.conf.above_field_voice = NULL;
660 for (i = 0; i < n_altitude_callouts; i++)
661 mk->mode6_handler.conf.altitude_callouts_enabled[i] = false;
663 for (i = 0; i < n_elements(values); i++)
664 if (values[i].id == value)
666 for (int j = 0; values[i].callouts[j] != 0; j++)
667 switch (values[i].callouts[j])
670 mk->mode6_handler.conf.minimums_enabled = true;
674 mk->mode6_handler.conf.smart_500_enabled = true;
678 mk->mode6_handler.conf.above_field_voice = mk_altitude_voice(Mode6Handler::ALTITUDE_CALLOUT_500);
681 case FIELD_500_ABOVE:
682 mk->mode6_handler.conf.above_field_voice = mk_voice(five_hundred_above);
686 for (int k = 0; k < n_altitude_callouts; k++)
687 if (mk->mode6_handler.altitude_callout_definitions[k] == values[i].callouts[j])
688 mk->mode6_handler.conf.altitude_callouts_enabled[k] = true;
699 MK_VIII::ConfigurationModule::read_audio_menu_select (int value)
701 if (value == 0 || value == 1)
702 mk->mode4_handler.conf.voice_too_low_gear = mk_voice(too_low_gear);
703 else if (value == 2 || value == 3)
704 mk->mode4_handler.conf.voice_too_low_gear = mk_voice(too_low_flaps);
712 MK_VIII::ConfigurationModule::read_terrain_display_select (int value)
715 mk->tcf_handler.conf.enabled = false;
716 else if (value == 0 || value == 1 || (value >= 3 && value <= 15)
717 || (value >= 18 && value <= 20) || (value >= 235 && value <= 255))
718 mk->tcf_handler.conf.enabled = true;
726 MK_VIII::ConfigurationModule::read_options_select_group_1 (int value)
728 if (value >= 0 && value < 128)
730 mk->io_handler.conf.flap_reversal = test_bits(value, 1 << 1);
731 mk->mode6_handler.conf.bank_angle_enabled = test_bits(value, 1 << 2);
732 mk->io_handler.conf.steep_approach_enabled = test_bits(value, 1 << 6);
740 MK_VIII::ConfigurationModule::read_radio_altitude_input_select (int value)
743 return (value >= 0 && value <= 4) || (value >= 251 && value <= 255);
747 MK_VIII::ConfigurationModule::read_navigation_input_select (int value)
749 if (value >= 0 && value <= 2)
750 mk->io_handler.conf.localizer_enabled = false;
751 else if ((value >= 3 && value <= 5) || (value >= 250 && value <= 255))
752 mk->io_handler.conf.localizer_enabled = true;
760 MK_VIII::ConfigurationModule::read_attitude_input_select (int value)
763 return (value >= 0 && value <= 6) || value == 253 || value == 255;
767 MK_VIII::ConfigurationModule::read_heading_input_select (int value)
770 return (value >= 0 && value <= 3) || value == 254 || value == 255;
774 MK_VIII::ConfigurationModule::read_windshear_input_select (int value)
777 return value == 0 || (value >= 253 && value <= 255);
781 MK_VIII::ConfigurationModule::read_input_output_discrete_type_select (int value)
786 IOHandler::LampConfiguration lamp_conf;
787 bool gpws_inhibit_enabled;
788 bool momentary_flap_override_enabled;
789 bool alternate_steep_approach;
791 { 0, { false, false }, false, true, true },
792 { 1, { true, false }, false, true, true },
793 { 2, { false, false }, true, true, true },
794 { 3, { true, false }, true, true, true },
795 { 4, { false, true }, true, true, true },
796 { 5, { true, true }, true, true, true },
797 { 6, { false, false }, true, true, false },
798 { 7, { true, false }, true, true, false },
799 { 254, { false, false }, true, false, true },
800 { 255, { false, false }, true, false, true }
803 for (int i = 0; i < n_elements(io_types); i++)
804 if (io_types[i].type == value)
806 mk->io_handler.conf.lamp = &io_types[i].lamp_conf;
807 mk->io_handler.conf.gpws_inhibit_enabled = io_types[i].gpws_inhibit_enabled;
808 mk->io_handler.conf.momentary_flap_override_enabled = io_types[i].momentary_flap_override_enabled;
809 mk->io_handler.conf.alternate_steep_approach = io_types[i].alternate_steep_approach;
817 MK_VIII::ConfigurationModule::read_audio_output_level (int value)
831 for (int i = 0; i < n_elements(values); i++)
832 if (values[i].id == value)
834 mk->voice_player.set_volume(mk->voice_player.conf.volume = modify_amplitude(1.0, values[i].relative_dB));
838 // The self test needs the voice player even when the configuration
839 // is invalid, so set a default value.
840 mk->voice_player.set_volume(mk->voice_player.conf.volume = 1.0);
845 MK_VIII::ConfigurationModule::read_undefined_input_select (int value)
852 MK_VIII::ConfigurationModule::boot ()
854 bool (MK_VIII::ConfigurationModule::*readers[N_CATEGORIES]) (int) = {
855 &MK_VIII::ConfigurationModule::read_aircraft_mode_type_select,
856 &MK_VIII::ConfigurationModule::read_air_data_input_select,
857 &MK_VIII::ConfigurationModule::read_position_input_select,
858 &MK_VIII::ConfigurationModule::read_altitude_callouts,
859 &MK_VIII::ConfigurationModule::read_audio_menu_select,
860 &MK_VIII::ConfigurationModule::read_terrain_display_select,
861 &MK_VIII::ConfigurationModule::read_options_select_group_1,
862 &MK_VIII::ConfigurationModule::read_radio_altitude_input_select,
863 &MK_VIII::ConfigurationModule::read_navigation_input_select,
864 &MK_VIII::ConfigurationModule::read_attitude_input_select,
865 &MK_VIII::ConfigurationModule::read_heading_input_select,
866 &MK_VIII::ConfigurationModule::read_windshear_input_select,
867 &MK_VIII::ConfigurationModule::read_input_output_discrete_type_select,
868 &MK_VIII::ConfigurationModule::read_audio_output_level,
869 &MK_VIII::ConfigurationModule::read_undefined_input_select,
870 &MK_VIII::ConfigurationModule::read_undefined_input_select,
871 &MK_VIII::ConfigurationModule::read_undefined_input_select
874 memcpy(effective_categories, categories, sizeof(categories));
877 for (int i = 0; i < N_CATEGORIES; i++)
878 if (! (this->*readers[i])(effective_categories[i]))
880 SG_LOG(SG_INSTR, SG_ALERT, "MK VIII EGPWS configuration category " << i + 1 << ": invalid value " << effective_categories[i]);
882 if (state == STATE_OK)
883 state = STATE_INVALID_DATABASE;
885 mk_doutput(gpws_inop) = true;
886 mk_doutput(tad_inop) = true;
893 if (mk->mode6_handler.conf.above_field_voice && ! mk->tcf_handler.conf.enabled)
896 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");
897 state = STATE_INVALID_DATABASE;
902 MK_VIII::ConfigurationModule::bind (SGPropertyNode *node)
904 for (int i = 0; i < N_CATEGORIES; i++)
906 std::ostringstream name;
907 name << "configuration-module/category-" << i + 1;
908 mk->properties_handler.tie(node, name.str().c_str(), SGRawValuePointer<int>(&categories[i]));
912 ///////////////////////////////////////////////////////////////////////////////
913 // FaultHandler ///////////////////////////////////////////////////////////////
914 ///////////////////////////////////////////////////////////////////////////////
916 // [INSTALL] only specifies that the faults cause a GPWS INOP
917 // indication. We arbitrarily set a TAD INOP when it makes sense.
918 const unsigned int MK_VIII::FaultHandler::fault_inops[] = {
919 INOP_GPWS | INOP_TAD, // [INSTALL] 3.15.1.3
920 INOP_GPWS, // [INSTALL] appendix E 6.6.2
921 INOP_GPWS, // [INSTALL] appendix E 6.6.4
922 INOP_GPWS, // [INSTALL] appendix E 6.6.6
923 INOP_GPWS | INOP_TAD, // [INSTALL] appendix E 6.6.7
924 INOP_GPWS, // [INSTALL] appendix E 6.6.13
925 INOP_GPWS, // [INSTALL] appendix E 6.6.25
926 INOP_GPWS, // [INSTALL] appendix E 6.6.27
927 INOP_TAD, // unspecified
928 INOP_GPWS, // unspecified
929 INOP_GPWS, // unspecified
930 // [INSTALL] 2.3.10.1 specifies that GPWS INOP is "activated during
931 // any detected partial or total failure of the GPWS modes 1-5", so
932 // do not set any INOP for mode 6 and bank angle.
935 INOP_TAD // unspecified
939 MK_VIII::FaultHandler::has_faults () const
941 for (int i = 0; i < N_FAULTS; i++)
949 MK_VIII::FaultHandler::has_faults (unsigned int inop)
951 for (int i = 0; i < N_FAULTS; i++)
952 if (faults[i] && test_bits(fault_inops[i], inop))
959 MK_VIII::FaultHandler::boot ()
961 memset(faults, 0, sizeof(faults));
965 MK_VIII::FaultHandler::set_fault (Fault fault)
969 faults[fault] = true;
971 mk->self_test_handler.set_inop();
973 if (test_bits(fault_inops[fault], INOP_GPWS))
975 mk_unset_alerts(~mk_alert(TCF_TOO_LOW_TERRAIN));
976 mk_doutput(gpws_inop) = true;
978 if (test_bits(fault_inops[fault], INOP_TAD))
980 mk_unset_alerts(mk_alert(TCF_TOO_LOW_TERRAIN));
981 mk_doutput(tad_inop) = true;
987 MK_VIII::FaultHandler::unset_fault (Fault fault)
991 faults[fault] = false;
992 if (! has_faults(INOP_GPWS))
993 mk_doutput(gpws_inop) = false;
994 if (! has_faults(INOP_TAD))
995 mk_doutput(tad_inop) = false;
999 ///////////////////////////////////////////////////////////////////////////////
1000 // IOHandler //////////////////////////////////////////////////////////////////
1001 ///////////////////////////////////////////////////////////////////////////////
1004 MK_VIII::IOHandler::TerrainClearanceFilter::update (double agl)
1006 // [PILOT] page 20 specifies that the terrain clearance is equal to
1007 // 75% of the radio altitude, averaged over the previous 15 seconds.
1009 deque< Sample<double> >::iterator iter;
1011 // remove samples older than 15 seconds
1012 for (iter = samples.begin(); iter != samples.end() && globals->get_sim_time_sec() - (*iter).timestamp >= 15; iter = samples.begin())
1013 samples.erase(iter);
1015 // append new sample
1016 samples.push_back(Sample<double>(agl));
1018 // calculate average
1019 double new_value = 0;
1020 if (samples.size() > 0)
1022 for (iter = samples.begin(); iter != samples.end(); iter++)
1023 new_value += (*iter).value;
1024 new_value /= samples.size();
1028 if (new_value > value)
1035 MK_VIII::IOHandler::TerrainClearanceFilter::reset ()
1041 MK_VIII::IOHandler::IOHandler (MK_VIII *device)
1042 : mk(device), _lamp(LAMP_NONE)
1044 memset(&input_feeders, 0, sizeof(input_feeders));
1045 memset(&inputs.discretes, 0, sizeof(inputs.discretes));
1046 memset(&outputs, 0, sizeof(outputs));
1047 memset(&power_saved, 0, sizeof(power_saved));
1049 mk_dinput_feed(landing_gear) = true;
1050 mk_dinput_feed(landing_flaps) = true;
1051 mk_dinput_feed(glideslope_inhibit) = true;
1052 mk_dinput_feed(decision_height) = true;
1053 mk_dinput_feed(autopilot_engaged) = true;
1054 mk_ainput_feed(uncorrected_barometric_altitude) = true;
1055 mk_ainput_feed(barometric_altitude_rate) = true;
1056 mk_ainput_feed(radio_altitude) = true;
1057 mk_ainput_feed(glideslope_deviation) = true;
1058 mk_ainput_feed(roll_angle) = true;
1059 mk_ainput_feed(localizer_deviation) = true;
1060 mk_ainput_feed(computed_airspeed) = true;
1062 // will be unset on power on
1063 mk_doutput(gpws_inop) = true;
1064 mk_doutput(tad_inop) = true;
1068 MK_VIII::IOHandler::boot ()
1070 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
1073 mk_doutput(gpws_inop) = false;
1074 mk_doutput(tad_inop) = false;
1076 mk_doutput(glideslope_cancel) = power_saved.glideslope_cancel;
1078 altitude_samples.clear();
1082 MK_VIII::IOHandler::post_boot ()
1084 if (momentary_steep_approach_enabled())
1086 last_landing_gear = mk_dinput(landing_gear);
1087 last_real_flaps_down = real_flaps_down();
1090 // read externally-latching input discretes
1091 update_alternate_discrete_input(&mk_dinput(mode6_low_volume));
1092 update_alternate_discrete_input(&mk_dinput(audio_inhibit));
1096 MK_VIII::IOHandler::power_off ()
1098 power_saved.glideslope_cancel = mk_doutput(glideslope_cancel); // [SPEC] 6.2.5
1100 memset(&outputs, 0, sizeof(outputs));
1102 audio_inhibit_fault_timer.stop();
1103 landing_gear_fault_timer.stop();
1104 flaps_down_fault_timer.stop();
1105 momentary_flap_override_fault_timer.stop();
1106 self_test_fault_timer.stop();
1107 glideslope_cancel_fault_timer.stop();
1108 steep_approach_fault_timer.stop();
1109 gpws_inhibit_fault_timer.stop();
1110 ta_tcf_inhibit_fault_timer.stop();
1113 mk_doutput(gpws_inop) = true;
1114 mk_doutput(tad_inop) = true;
1118 MK_VIII::IOHandler::enter_ground ()
1120 reset_terrain_clearance();
1122 if (conf.momentary_flap_override_enabled)
1123 mk_doutput(flap_override) = false; // [INSTALL] 3.15.1.6
1127 MK_VIII::IOHandler::enter_takeoff ()
1129 reset_terrain_clearance();
1131 if (momentary_steep_approach_enabled())
1132 // landing or go-around, disable steep approach as per [SPEC] 6.2.1
1133 mk_doutput(steep_approach) = false;
1137 MK_VIII::IOHandler::update_inputs ()
1139 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
1142 // 1. process input feeders
1144 if (mk_dinput_feed(landing_gear))
1145 mk_dinput(landing_gear) = mk_node(gear_down)->getBoolValue();
1146 if (mk_dinput_feed(landing_flaps))
1147 mk_dinput(landing_flaps) = mk_node(flaps)->getDoubleValue() < 1;
1148 if (mk_dinput_feed(glideslope_inhibit))
1149 mk_dinput(glideslope_inhibit) = mk_node(nav0_gs_distance)->getDoubleValue() < 0;
1150 if (mk_dinput_feed(autopilot_engaged))
1154 mode = mk_node(autopilot_heading_lock)->getStringValue();
1155 mk_dinput(autopilot_engaged) = mode && *mode;
1158 if (mk_ainput_feed(uncorrected_barometric_altitude))
1160 if (mk_node(altimeter_serviceable)->getBoolValue())
1161 mk_ainput(uncorrected_barometric_altitude).set(mk_node(altimeter_altitude)->getDoubleValue());
1163 mk_ainput(uncorrected_barometric_altitude).unset();
1165 if (mk_ainput_feed(barometric_altitude_rate))
1166 // Do not use the vsi, because it is pressure-based only, and
1167 // therefore too laggy for GPWS alerting purposes. I guess that
1168 // a real ADC combines pressure-based and inerta-based altitude
1169 // rates to provide a non-laggy rate. That non-laggy rate is
1170 // best emulated by /velocities/vertical-speed-fps * 60.
1171 mk_ainput(barometric_altitude_rate).set(mk_node(vs)->getDoubleValue() * 60);
1172 if (mk_ainput_feed(radio_altitude))
1174 // Some flight models may return negative values when on the
1175 // ground or after a crash; do not allow them.
1176 double agl = mk_node(altitude_agl)->getDoubleValue();
1177 mk_ainput(radio_altitude).set(SG_MAX2(0.0, agl));
1179 if (mk_ainput_feed(glideslope_deviation))
1181 if (mk_node(nav0_serviceable)->getBoolValue()
1182 && mk_node(nav0_gs_serviceable)->getBoolValue()
1183 && mk_node(nav0_in_range)->getBoolValue()
1184 && mk_node(nav0_has_gs)->getBoolValue())
1185 // gs-needle-deflection is expressed in degrees, and 1 dot =
1186 // 0.32 degrees (according to
1187 // http://www.ntsb.gov/Recs/letters/2000/A00_41_45.pdf)
1188 mk_ainput(glideslope_deviation).set(mk_node(nav0_gs_needle_deflection)->getDoubleValue() / 0.32 * GLIDESLOPE_DOTS_TO_DDM);
1190 mk_ainput(glideslope_deviation).unset();
1192 if (mk_ainput_feed(roll_angle))
1194 if (mk_node(ai_serviceable)->getBoolValue() && ! mk_node(ai_caged)->getBoolValue())
1195 mk_ainput(roll_angle).set(mk_node(ai_roll)->getDoubleValue());
1197 mk_ainput(roll_angle).unset();
1199 if (mk_ainput_feed(localizer_deviation))
1201 if (mk_node(nav0_serviceable)->getBoolValue()
1202 && mk_node(nav0_cdi_serviceable)->getBoolValue()
1203 && mk_node(nav0_in_range)->getBoolValue()
1204 && mk_node(nav0_nav_loc)->getBoolValue())
1205 // heading-needle-deflection is expressed in degrees, and 1
1206 // dot = 2 degrees (0.5 degrees for ILS, but navradio.cxx
1207 // performs the conversion)
1208 mk_ainput(localizer_deviation).set(mk_node(nav0_heading_needle_deflection)->getDoubleValue() / 2 * LOCALIZER_DOTS_TO_DDM);
1210 mk_ainput(localizer_deviation).unset();
1212 if (mk_ainput_feed(computed_airspeed))
1214 if (mk_node(asi_serviceable)->getBoolValue())
1215 mk_ainput(computed_airspeed).set(mk_node(asi_speed)->getDoubleValue());
1217 mk_ainput(computed_airspeed).unset();
1222 mk_data(decision_height).set(&mk_ainput(decision_height));
1223 mk_data(radio_altitude).set(&mk_ainput(radio_altitude));
1224 mk_data(roll_angle).set(&mk_ainput(roll_angle));
1226 // update barometric_altitude_rate as per [SPEC] 6.2.1: "During
1227 // normal conditions, the system will base Mode 1 computations upon
1228 // barometric rate from the ADC. If this computed data is not valid
1229 // or available then the system will use internally computed
1230 // barometric altitude rate."
1232 if (! mk_ainput(barometric_altitude_rate).ncd)
1233 // the altitude rate input is valid, use it
1234 mk_data(barometric_altitude_rate).set(mk_ainput(barometric_altitude_rate).get());
1239 // The altitude rate input is invalid. We can compute an
1240 // altitude rate if all the following conditions are true:
1242 // - the altitude input is valid
1243 // - there is an altitude sample with age >= 1 second
1244 // - that altitude sample is valid
1246 if (! mk_ainput(uncorrected_barometric_altitude).ncd)
1248 if (! altitude_samples.empty() && ! altitude_samples.front().value.ncd)
1250 double elapsed = globals->get_sim_time_sec() - altitude_samples.front().timestamp;
1254 mk_data(barometric_altitude_rate).set((mk_ainput(uncorrected_barometric_altitude).get() - altitude_samples.front().value.get()) / elapsed * 60);
1260 mk_data(barometric_altitude_rate).unset();
1263 altitude_samples.push_back(Sample< Parameter<double> >(mk_ainput(uncorrected_barometric_altitude)));
1265 // Erase everything from the beginning of the list up to the sample
1266 // preceding the most recent sample whose age is >= 1 second.
1268 deque< Sample< Parameter<double> > >::iterator erase_last = altitude_samples.begin();
1269 deque< Sample< Parameter<double> > >::iterator iter;
1271 for (iter = altitude_samples.begin(); iter != altitude_samples.end(); iter++)
1272 if (globals->get_sim_time_sec() - (*iter).timestamp >= 1)
1277 if (erase_last != altitude_samples.begin())
1278 altitude_samples.erase(altitude_samples.begin(), erase_last);
1282 if (conf.use_internal_gps)
1284 mk_data(gps_altitude).set(mk_node(altitude)->getDoubleValue());
1285 mk_data(gps_latitude).set(mk_node(latitude)->getDoubleValue());
1286 mk_data(gps_longitude).set(mk_node(longitude)->getDoubleValue());
1287 mk_data(gps_vertical_figure_of_merit).set(0.0);
1291 mk_data(gps_altitude).set(&mk_ainput(gps_altitude));
1292 mk_data(gps_latitude).set(&mk_ainput(gps_latitude));
1293 mk_data(gps_longitude).set(&mk_ainput(gps_longitude));
1294 mk_data(gps_vertical_figure_of_merit).set(&mk_ainput(gps_vertical_figure_of_merit));
1297 // update glideslope and localizer
1299 mk_data(glideslope_deviation_dots).set(&mk_ainput(glideslope_deviation), GLIDESLOPE_DDM_TO_DOTS);
1300 mk_data(localizer_deviation_dots).set(&mk_ainput(localizer_deviation), LOCALIZER_DDM_TO_DOTS);
1302 // Update geometric altitude; [SPEC] 6.7.8 provides an overview of a
1303 // complex algorithm which combines several input sources to
1304 // calculate the geometric altitude. Since the exact algorithm is
1305 // not given, we fallback to a much simpler method.
1307 if (! mk_data(gps_altitude).ncd)
1308 mk_data(geometric_altitude).set(mk_data(gps_altitude).get());
1309 else if (! mk_ainput(uncorrected_barometric_altitude).ncd)
1310 mk_data(geometric_altitude).set(mk_ainput(uncorrected_barometric_altitude).get());
1312 mk_data(geometric_altitude).unset();
1314 // update terrain clearance
1316 update_terrain_clearance();
1318 // 3. perform sanity checks
1320 if (! mk_data(radio_altitude).ncd && mk_data(radio_altitude).get() < 0)
1321 mk_data(radio_altitude).unset();
1323 if (! mk_data(decision_height).ncd && mk_data(decision_height).get() < 0)
1324 mk_data(decision_height).unset();
1326 if (! mk_data(gps_latitude).ncd
1327 && (mk_data(gps_latitude).get() < -90
1328 || mk_data(gps_latitude).get() > 90))
1329 mk_data(gps_latitude).unset();
1331 if (! mk_data(gps_longitude).ncd
1332 && (mk_data(gps_longitude).get() < -180
1333 || mk_data(gps_longitude).get() > 180))
1334 mk_data(gps_longitude).unset();
1336 if (! mk_data(roll_angle).ncd
1337 && ((mk_data(roll_angle).get() < -180)
1338 || (mk_data(roll_angle).get() > 180)))
1339 mk_data(roll_angle).unset();
1341 // 4. process input feeders requiring data computed above
1343 if (mk_dinput_feed(decision_height))
1344 mk_dinput(decision_height) = ! mk_data(radio_altitude).ncd
1345 && ! mk_data(decision_height).ncd
1346 && mk_data(radio_altitude).get() <= mk_data(decision_height).get();
1350 MK_VIII::IOHandler::update_terrain_clearance ()
1352 if (! mk_data(radio_altitude).ncd)
1353 mk_data(terrain_clearance).set(terrain_clearance_filter.update(mk_data(radio_altitude).get()));
1355 mk_data(terrain_clearance).unset();
1359 MK_VIII::IOHandler::reset_terrain_clearance ()
1361 terrain_clearance_filter.reset();
1362 update_terrain_clearance();
1366 MK_VIII::IOHandler::handle_input_fault (bool test, FaultHandler::Fault fault)
1370 if (! mk->fault_handler.faults[fault])
1371 mk->fault_handler.set_fault(fault);
1374 mk->fault_handler.unset_fault(fault);
1378 MK_VIII::IOHandler::handle_input_fault (bool test,
1380 double max_duration,
1381 FaultHandler::Fault fault)
1385 if (! mk->fault_handler.faults[fault])
1387 if (timer->start_or_elapsed() >= max_duration)
1388 mk->fault_handler.set_fault(fault);
1393 mk->fault_handler.unset_fault(fault);
1399 MK_VIII::IOHandler::update_input_faults ()
1401 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
1404 // [INSTALL] 3.15.1.3
1405 handle_input_fault(mk_dinput(audio_inhibit),
1406 &audio_inhibit_fault_timer,
1408 FaultHandler::FAULT_ALL_MODES_INHIBIT);
1410 // [INSTALL] appendix E 6.6.2
1411 handle_input_fault(mk_dinput(landing_gear)
1412 && ! mk_ainput(computed_airspeed).ncd
1413 && mk_ainput(computed_airspeed).get() > conf.faults->max_gear_down_airspeed,
1414 &landing_gear_fault_timer,
1416 FaultHandler::FAULT_GEAR_SWITCH);
1418 // [INSTALL] appendix E 6.6.4
1419 handle_input_fault(flaps_down()
1420 && ! mk_ainput(computed_airspeed).ncd
1421 && mk_ainput(computed_airspeed).get() > conf.faults->max_flaps_down_airspeed,
1422 &flaps_down_fault_timer,
1424 FaultHandler::FAULT_FLAPS_SWITCH);
1426 // [INSTALL] appendix E 6.6.6
1427 if (conf.momentary_flap_override_enabled)
1428 handle_input_fault(mk_dinput(momentary_flap_override),
1429 &momentary_flap_override_fault_timer,
1431 FaultHandler::FAULT_MOMENTARY_FLAP_OVERRIDE_INVALID);
1433 // [INSTALL] appendix E 6.6.7
1434 handle_input_fault(mk_dinput(self_test),
1435 &self_test_fault_timer,
1437 FaultHandler::FAULT_SELF_TEST_INVALID);
1439 // [INSTALL] appendix E 6.6.13
1440 handle_input_fault(mk_dinput(glideslope_cancel),
1441 &glideslope_cancel_fault_timer,
1443 FaultHandler::FAULT_GLIDESLOPE_CANCEL_INVALID);
1445 // [INSTALL] appendix E 6.6.25
1446 if (momentary_steep_approach_enabled())
1447 handle_input_fault(mk_dinput(steep_approach),
1448 &steep_approach_fault_timer,
1450 FaultHandler::FAULT_STEEP_APPROACH_INVALID);
1452 // [INSTALL] appendix E 6.6.27
1453 if (conf.gpws_inhibit_enabled)
1454 handle_input_fault(mk_dinput(gpws_inhibit),
1455 &gpws_inhibit_fault_timer,
1457 FaultHandler::FAULT_GPWS_INHIBIT);
1459 // [INSTALL] does not specify a fault for this one, but it makes
1460 // sense to have it behave like GPWS inhibit
1461 handle_input_fault(mk_dinput(ta_tcf_inhibit),
1462 &ta_tcf_inhibit_fault_timer,
1464 FaultHandler::FAULT_TA_TCF_INHIBIT);
1466 // [PILOT] page 48: "In the event that required data for a
1467 // particular function is not available, then that function is
1468 // automatically inhibited and annunciated"
1470 handle_input_fault(mk_data(radio_altitude).ncd
1471 || mk_ainput(uncorrected_barometric_altitude).ncd
1472 || mk_data(barometric_altitude_rate).ncd
1473 || mk_ainput(computed_airspeed).ncd
1474 || mk_data(terrain_clearance).ncd,
1475 FaultHandler::FAULT_MODES14_INPUTS_INVALID);
1477 if (! mk_dinput(glideslope_inhibit))
1478 handle_input_fault(mk_data(radio_altitude).ncd,
1479 FaultHandler::FAULT_MODE5_INPUTS_INVALID);
1481 if (mk->mode6_handler.altitude_callouts_enabled())
1482 handle_input_fault(mk->mode6_handler.conf.above_field_voice
1483 ? (mk_data(gps_latitude).ncd
1484 || mk_data(gps_longitude).ncd
1485 || mk_data(geometric_altitude).ncd)
1486 : mk_data(radio_altitude).ncd,
1487 FaultHandler::FAULT_MODE6_INPUTS_INVALID);
1489 if (mk->mode6_handler.conf.bank_angle_enabled)
1490 handle_input_fault(mk_data(roll_angle).ncd,
1491 FaultHandler::FAULT_BANK_ANGLE_INPUTS_INVALID);
1493 if (mk->tcf_handler.conf.enabled)
1494 handle_input_fault(mk_data(radio_altitude).ncd
1495 || mk_data(geometric_altitude).ncd
1496 || mk_data(gps_latitude).ncd
1497 || mk_data(gps_longitude).ncd
1498 || mk_data(gps_vertical_figure_of_merit).ncd,
1499 FaultHandler::FAULT_TCF_INPUTS_INVALID);
1503 MK_VIII::IOHandler::update_alternate_discrete_input (bool *ptr)
1505 assert(mk->system_handler.state == SystemHandler::STATE_ON);
1507 if (ptr == &mk_dinput(mode6_low_volume))
1509 if (mk->configuration_module.state == ConfigurationModule::STATE_OK
1510 && mk->self_test_handler.state == SelfTestHandler::STATE_NONE)
1511 mk->mode6_handler.set_volume(*ptr ? modify_amplitude(1.0, -6) : 1.0);
1513 else if (ptr == &mk_dinput(audio_inhibit))
1515 if (mk->configuration_module.state == ConfigurationModule::STATE_OK
1516 && mk->self_test_handler.state == SelfTestHandler::STATE_NONE)
1517 mk->voice_player.set_volume(*ptr ? 0.0 : mk->voice_player.conf.volume);
1522 MK_VIII::IOHandler::update_internal_latches ()
1524 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
1528 if (conf.momentary_flap_override_enabled
1529 && mk_doutput(flap_override)
1530 && ! mk->state_handler.takeoff
1531 && (mk_data(radio_altitude).ncd || mk_data(radio_altitude).get() <= 50))
1532 mk_doutput(flap_override) = false;
1535 if (momentary_steep_approach_enabled())
1537 if (mk_doutput(steep_approach)
1538 && ! mk->state_handler.takeoff
1539 && ((last_landing_gear && ! mk_dinput(landing_gear))
1540 || (last_real_flaps_down && ! real_flaps_down())))
1541 // gear or flaps raised during approach: it's a go-around
1542 mk_doutput(steep_approach) = false;
1544 last_landing_gear = mk_dinput(landing_gear);
1545 last_real_flaps_down = real_flaps_down();
1549 if (mk_doutput(glideslope_cancel)
1550 && (mk_data(glideslope_deviation_dots).ncd
1551 || mk_data(radio_altitude).ncd
1552 || mk_data(radio_altitude).get() > 2000
1553 || mk_data(radio_altitude).get() < 30))
1554 mk_doutput(glideslope_cancel) = false;
1558 MK_VIII::IOHandler::update_egpws_alert_discrete_1 ()
1560 if (mk->voice_player.voice)
1565 VoicePlayer::Voice *voice;
1567 { 11, mk_voice(sink_rate_pause_sink_rate) },
1568 { 12, mk_voice(pull_up) },
1569 { 13, mk_voice(terrain) },
1570 { 13, mk_voice(terrain_pause_terrain) },
1571 { 14, mk_voice(dont_sink_pause_dont_sink) },
1572 { 15, mk_voice(too_low_gear) },
1573 { 16, mk_voice(too_low_flaps) },
1574 { 17, mk_voice(too_low_terrain) },
1575 { 18, mk_voice(soft_glideslope) },
1576 { 18, mk_voice(hard_glideslope) },
1577 { 19, mk_voice(minimums_minimums) }
1580 for (int i = 0; i < n_elements(voices); i++)
1581 if (voices[i].voice == mk->voice_player.voice)
1583 mk_aoutput(egpws_alert_discrete_1) = 1 << voices[i].bit;
1588 mk_aoutput(egpws_alert_discrete_1) = 0;
1592 MK_VIII::IOHandler::update_egpwc_logic_discretes ()
1594 mk_aoutput(egpwc_logic_discretes) = 0;
1596 if (mk->state_handler.takeoff)
1597 mk_aoutput(egpwc_logic_discretes) |= 1 << 18;
1602 unsigned int alerts;
1604 { 13, mk_alert(TCF_TOO_LOW_TERRAIN) },
1605 { 19, mk_alert(MODE1_SINK_RATE) },
1606 { 20, mk_alert(MODE1_PULL_UP) },
1607 { 21, mk_alert(MODE2A_PREFACE) | mk_alert(MODE2B_PREFACE) | mk_alert(MODE2B_LANDING_MODE) | mk_alert(MODE2A_ALTITUDE_GAIN_TERRAIN_CLOSING) },
1608 { 22, mk_alert(MODE2A) | mk_alert(MODE2B) },
1609 { 23, mk_alert(MODE3) },
1610 { 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) },
1611 { 25, mk_alert(MODE5_SOFT) | mk_alert(MODE5_HARD) }
1614 for (int i = 0; i < n_elements(logic); i++)
1615 if (mk_test_alerts(logic[i].alerts))
1616 mk_aoutput(egpwc_logic_discretes) |= 1 << logic[i].bit;
1620 MK_VIII::IOHandler::update_mode6_callouts_discrete_1 ()
1622 if (mk->voice_player.voice)
1627 VoicePlayer::Voice *voice;
1629 { 11, mk_voice(minimums_minimums) },
1630 { 16, mk_altitude_voice(Mode6Handler::ALTITUDE_CALLOUT_10) },
1631 { 17, mk_altitude_voice(Mode6Handler::ALTITUDE_CALLOUT_20) },
1632 { 18, mk_altitude_voice(Mode6Handler::ALTITUDE_CALLOUT_30) },
1633 { 19, mk_altitude_voice(Mode6Handler::ALTITUDE_CALLOUT_40) },
1634 { 20, mk_altitude_voice(Mode6Handler::ALTITUDE_CALLOUT_50) },
1635 { 23, mk_altitude_voice(Mode6Handler::ALTITUDE_CALLOUT_100) },
1636 { 24, mk_altitude_voice(Mode6Handler::ALTITUDE_CALLOUT_200) },
1637 { 25, mk_altitude_voice(Mode6Handler::ALTITUDE_CALLOUT_300) }
1640 for (int i = 0; i < n_elements(voices); i++)
1641 if (voices[i].voice == mk->voice_player.voice)
1643 mk_aoutput(mode6_callouts_discrete_1) = 1 << voices[i].bit;
1648 mk_aoutput(mode6_callouts_discrete_1) = 0;
1652 MK_VIII::IOHandler::update_mode6_callouts_discrete_2 ()
1654 if (mk->voice_player.voice)
1659 VoicePlayer::Voice *voice;
1661 { 11, mk_altitude_voice(Mode6Handler::ALTITUDE_CALLOUT_400) },
1662 { 12, mk_altitude_voice(Mode6Handler::ALTITUDE_CALLOUT_500) },
1663 { 13, mk_altitude_voice(Mode6Handler::ALTITUDE_CALLOUT_1000) },
1664 { 18, mk_voice(bank_angle_pause_bank_angle) },
1665 { 18, mk_voice(bank_angle_pause_bank_angle_3) },
1666 { 23, mk_voice(five_hundred_above) }
1669 for (int i = 0; i < n_elements(voices); i++)
1670 if (voices[i].voice == mk->voice_player.voice)
1672 mk_aoutput(mode6_callouts_discrete_2) = 1 << voices[i].bit;
1677 mk_aoutput(mode6_callouts_discrete_2) = 0;
1681 MK_VIII::IOHandler::update_egpws_alert_discrete_2 ()
1683 mk_aoutput(egpws_alert_discrete_2) = 1 << 27; // Terrain NA
1685 if (mk_doutput(glideslope_cancel))
1686 mk_aoutput(egpws_alert_discrete_2) |= 1 << 11;
1687 if (mk_doutput(gpws_alert))
1688 mk_aoutput(egpws_alert_discrete_2) |= 1 << 12;
1689 if (mk_doutput(gpws_warning))
1690 mk_aoutput(egpws_alert_discrete_2) |= 1 << 13;
1691 if (mk_doutput(gpws_inop))
1692 mk_aoutput(egpws_alert_discrete_2) |= 1 << 14;
1693 if (mk_doutput(audio_on))
1694 mk_aoutput(egpws_alert_discrete_2) |= 1 << 16;
1695 if (mk_doutput(tad_inop))
1696 mk_aoutput(egpws_alert_discrete_2) |= 1 << 24;
1697 if (mk->fault_handler.has_faults())
1698 mk_aoutput(egpws_alert_discrete_2) |= 1 << 25;
1702 MK_VIII::IOHandler::update_egpwc_alert_discrete_3 ()
1704 mk_aoutput(egpwc_alert_discrete_3) = 1 << 17 | 1 << 18;
1706 if (mk->fault_handler.faults[FaultHandler::FAULT_MODES14_INPUTS_INVALID])
1707 mk_aoutput(egpwc_alert_discrete_3) |= 1 << 11;
1708 if (mk->fault_handler.faults[FaultHandler::FAULT_MODE5_INPUTS_INVALID])
1709 mk_aoutput(egpwc_alert_discrete_3) |= 1 << 12;
1710 if (mk->fault_handler.faults[FaultHandler::FAULT_MODE6_INPUTS_INVALID])
1711 mk_aoutput(egpwc_alert_discrete_3) |= 1 << 13;
1712 if (mk->fault_handler.faults[FaultHandler::FAULT_BANK_ANGLE_INPUTS_INVALID])
1713 mk_aoutput(egpwc_alert_discrete_3) |= 1 << 14;
1714 if (mk_doutput(tad_inop))
1715 mk_aoutput(egpwc_alert_discrete_3) |= 1 << 16;
1719 MK_VIII::IOHandler::update_outputs ()
1721 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
1724 mk_doutput(audio_on) = ! mk_dinput(audio_inhibit)
1725 && mk->voice_player.voice
1726 && ! mk->voice_player.voice->element->silence;
1728 update_egpws_alert_discrete_1();
1729 update_egpwc_logic_discretes();
1730 update_mode6_callouts_discrete_1();
1731 update_mode6_callouts_discrete_2();
1732 update_egpws_alert_discrete_2();
1733 update_egpwc_alert_discrete_3();
1737 MK_VIII::IOHandler::get_lamp_output (Lamp lamp)
1741 case LAMP_GLIDESLOPE:
1742 return &mk_doutput(gpws_alert);
1745 return conf.lamp->format2 ? &mk_doutput(gpws_alert) : &mk_doutput(gpws_warning);
1748 return &mk_doutput(gpws_warning);
1751 assert_not_reached();
1757 MK_VIII::IOHandler::update_lamps ()
1759 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
1762 if (_lamp != LAMP_NONE && conf.lamp->flashing)
1764 // [SPEC] 6.9.3: 70 cycles per minute
1765 if (lamp_timer.elapsed() >= 60.0 / 70.0 / 2.0)
1767 bool *output = get_lamp_output(_lamp);
1768 *output = ! *output;
1775 MK_VIII::IOHandler::set_lamp (Lamp lamp)
1782 mk_doutput(gpws_warning) = false;
1783 mk_doutput(gpws_alert) = false;
1785 if (lamp != LAMP_NONE)
1787 *get_lamp_output(lamp) = true;
1793 MK_VIII::IOHandler::gpws_inhibit () const
1795 return conf.gpws_inhibit_enabled && mk_dinput(gpws_inhibit);
1799 MK_VIII::IOHandler::real_flaps_down () const
1801 return conf.flap_reversal ? mk_dinput(landing_flaps) : ! mk_dinput(landing_flaps);
1805 MK_VIII::IOHandler::flaps_down () const
1807 return flap_override() ? true : real_flaps_down();
1811 MK_VIII::IOHandler::flap_override () const
1813 return conf.momentary_flap_override_enabled ? mk_doutput(flap_override) : false;
1817 MK_VIII::IOHandler::steep_approach () const
1819 if (conf.steep_approach_enabled)
1820 // If alternate action was configured in category 13, we return
1821 // the value of the input discrete (which should be connected to a
1822 // latching steep approach cockpit switch). Otherwise, we return
1823 // the value of the output discrete.
1824 return conf.alternate_steep_approach ? mk_dinput(steep_approach) : mk_doutput(steep_approach);
1830 MK_VIII::IOHandler::momentary_steep_approach_enabled () const
1832 return conf.steep_approach_enabled && ! conf.alternate_steep_approach;
1836 MK_VIII::IOHandler::tie_input (SGPropertyNode *node,
1841 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));
1843 mk->properties_handler.tie(node, (string("input-feeders/discretes/") + name).c_str(), SGRawValuePointer<bool>(feed));
1847 MK_VIII::IOHandler::tie_input (SGPropertyNode *node,
1849 Parameter<double> *input,
1852 mk->properties_handler.tie(node, (string("inputs/arinc429/") + name).c_str(), SGRawValuePointer<double>(input->get_pointer()));
1853 mk->properties_handler.tie(node, (string("inputs/arinc429/") + name + "-ncd").c_str(), SGRawValuePointer<bool>(&input->ncd));
1855 mk->properties_handler.tie(node, (string("input-feeders/arinc429/") + name).c_str(), SGRawValuePointer<bool>(feed));
1859 MK_VIII::IOHandler::tie_output (SGPropertyNode *node,
1863 SGPropertyNode *child = node->getNode((string("outputs/discretes/") + name).c_str(), true);
1865 mk->properties_handler.tie(child, SGRawValuePointer<bool>(output));
1866 child->setAttribute(SGPropertyNode::WRITE, false);
1870 MK_VIII::IOHandler::tie_output (SGPropertyNode *node,
1874 SGPropertyNode *child = node->getNode((string("outputs/arinc429/") + name).c_str(), true);
1876 mk->properties_handler.tie(child, SGRawValuePointer<int>(output));
1877 child->setAttribute(SGPropertyNode::WRITE, false);
1881 MK_VIII::IOHandler::bind (SGPropertyNode *node)
1883 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));
1885 tie_input(node, "landing-gear", &mk_dinput(landing_gear), &mk_dinput_feed(landing_gear));
1886 tie_input(node, "landing-flaps", &mk_dinput(landing_flaps), &mk_dinput_feed(landing_flaps));
1887 tie_input(node, "momentary-flap-override", &mk_dinput(momentary_flap_override));
1888 tie_input(node, "self-test", &mk_dinput(self_test));
1889 tie_input(node, "glideslope-inhibit", &mk_dinput(glideslope_inhibit), &mk_dinput_feed(glideslope_inhibit));
1890 tie_input(node, "glideslope-cancel", &mk_dinput(glideslope_cancel));
1891 tie_input(node, "decision-height", &mk_dinput(decision_height), &mk_dinput_feed(decision_height));
1892 tie_input(node, "mode6-low-volume", &mk_dinput(mode6_low_volume));
1893 tie_input(node, "audio-inhibit", &mk_dinput(audio_inhibit));
1894 tie_input(node, "ta-tcf-inhibit", &mk_dinput(ta_tcf_inhibit));
1895 tie_input(node, "autopilot-engaged", &mk_dinput(autopilot_engaged), &mk_dinput_feed(autopilot_engaged));
1896 tie_input(node, "steep-approach", &mk_dinput(steep_approach));
1897 tie_input(node, "gpws-inhibit", &mk_dinput(gpws_inhibit));
1899 tie_input(node, "uncorrected-barometric-altitude", &mk_ainput(uncorrected_barometric_altitude), &mk_ainput_feed(uncorrected_barometric_altitude));
1900 tie_input(node, "barometric-altitude-rate", &mk_ainput(barometric_altitude_rate), &mk_ainput_feed(barometric_altitude_rate));
1901 tie_input(node, "gps-altitude", &mk_ainput(gps_altitude));
1902 tie_input(node, "gps-latitude", &mk_ainput(gps_latitude));
1903 tie_input(node, "gps-longitude", &mk_ainput(gps_longitude));
1904 tie_input(node, "gps-vertical-figure-of-merit", &mk_ainput(gps_vertical_figure_of_merit));
1905 tie_input(node, "radio-altitude", &mk_ainput(radio_altitude), &mk_ainput_feed(radio_altitude));
1906 tie_input(node, "glideslope-deviation", &mk_ainput(glideslope_deviation), &mk_ainput_feed(glideslope_deviation));
1907 tie_input(node, "roll-angle", &mk_ainput(roll_angle), &mk_ainput_feed(roll_angle));
1908 tie_input(node, "localizer-deviation", &mk_ainput(localizer_deviation), &mk_ainput_feed(localizer_deviation));
1909 tie_input(node, "computed-airspeed", &mk_ainput(computed_airspeed), &mk_ainput_feed(computed_airspeed));
1910 tie_input(node, "decision-height", &mk_ainput(decision_height), &mk_ainput_feed(decision_height));
1912 tie_output(node, "gpws-warning", &mk_doutput(gpws_warning));
1913 tie_output(node, "gpws-alert", &mk_doutput(gpws_alert));
1914 tie_output(node, "audio-on", &mk_doutput(audio_on));
1915 tie_output(node, "gpws-inop", &mk_doutput(gpws_inop));
1916 tie_output(node, "tad-inop", &mk_doutput(tad_inop));
1917 tie_output(node, "flap-override", &mk_doutput(flap_override));
1918 tie_output(node, "glideslope-cancel", &mk_doutput(glideslope_cancel));
1919 tie_output(node, "steep-approach", &mk_doutput(steep_approach));
1921 tie_output(node, "egpws-alert-discrete-1", &mk_aoutput(egpws_alert_discrete_1));
1922 tie_output(node, "egpwc-logic-discretes", &mk_aoutput(egpwc_logic_discretes));
1923 tie_output(node, "mode6-callouts-discrete-1", &mk_aoutput(mode6_callouts_discrete_1));
1924 tie_output(node, "mode6-callouts-discrete-2", &mk_aoutput(mode6_callouts_discrete_2));
1925 tie_output(node, "egpws-alert-discrete-2", &mk_aoutput(egpws_alert_discrete_2));
1926 tie_output(node, "egpwc-alert-discrete-3", &mk_aoutput(egpwc_alert_discrete_3));
1930 MK_VIII::IOHandler::get_discrete_input (bool *ptr) const
1936 MK_VIII::IOHandler::set_discrete_input (bool *ptr, bool value)
1941 if (mk->system_handler.state == SystemHandler::STATE_ON)
1943 if (ptr == &mk_dinput(momentary_flap_override))
1945 if (mk->configuration_module.state == ConfigurationModule::STATE_OK
1946 && mk->self_test_handler.state == SelfTestHandler::STATE_NONE
1947 && conf.momentary_flap_override_enabled
1949 mk_doutput(flap_override) = ! mk_doutput(flap_override);
1951 else if (ptr == &mk_dinput(self_test))
1952 mk->self_test_handler.handle_button_event(value);
1953 else if (ptr == &mk_dinput(glideslope_cancel))
1955 if (mk->configuration_module.state == ConfigurationModule::STATE_OK
1956 && mk->self_test_handler.state == SelfTestHandler::STATE_NONE
1959 if (! mk_doutput(glideslope_cancel))
1963 // Although we are not called from update(), we are
1964 // sure that the inputs we use here are defined,
1965 // since state is STATE_ON.
1967 if (! mk_data(glideslope_deviation_dots).ncd
1968 && ! mk_data(radio_altitude).ncd
1969 && mk_data(radio_altitude).get() <= 2000
1970 && mk_data(radio_altitude).get() >= 30)
1971 mk_doutput(glideslope_cancel) = true;
1973 // else do nothing ([PILOT] page 22: "Glideslope Cancel
1974 // can not be deselected (reset) by again pressing the
1975 // Glideslope Cancel switch.")
1978 else if (ptr == &mk_dinput(steep_approach))
1980 if (mk->configuration_module.state == ConfigurationModule::STATE_OK
1981 && mk->self_test_handler.state == SelfTestHandler::STATE_NONE
1982 && momentary_steep_approach_enabled()
1984 mk_doutput(steep_approach) = ! mk_doutput(steep_approach);
1990 if (mk->system_handler.state == SystemHandler::STATE_ON)
1991 update_alternate_discrete_input(ptr);
1995 MK_VIII::IOHandler::present_status_section (const char *name)
1997 printf("%s\n", name);
2001 MK_VIII::IOHandler::present_status_item (const char *name, const char *value)
2004 printf("\t%-32s %s\n", name, value);
2006 printf("\t%s\n", name);
2010 MK_VIII::IOHandler::present_status_subitem (const char *name)
2012 printf("\t\t%s\n", name);
2016 MK_VIII::IOHandler::present_status ()
2020 if (mk->system_handler.state != SystemHandler::STATE_ON)
2023 present_status_section("EGPWC CONFIGURATION");
2024 present_status_item("PART NUMBER:", "965-1220-000"); // [SPEC] 1.1
2025 present_status_item("MOD STATUS:", "N/A");
2026 present_status_item("SERIAL NUMBER:", "N/A");
2028 present_status_item("APPLICATION S/W VERSION:", FLIGHTGEAR_VERSION);
2029 present_status_item("TERRAIN DATABASE VERSION:", FLIGHTGEAR_VERSION);
2030 present_status_item("ENVELOPE MOD DATABASE VERSION:", "N/A");
2031 present_status_item("BOOT CODE VERSION:", FLIGHTGEAR_VERSION);
2034 present_status_section("CURRENT FAULTS");
2035 if (mk->configuration_module.state == ConfigurationModule::STATE_OK && ! mk->fault_handler.has_faults())
2036 present_status_item("NO FAULTS");
2039 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
2041 present_status_item("GPWS COMPUTER FAULTS:");
2042 switch (mk->configuration_module.state)
2044 case ConfigurationModule::STATE_INVALID_DATABASE:
2045 present_status_subitem("APPLICATION DATABASE FAILED");
2048 case ConfigurationModule::STATE_INVALID_AIRCRAFT_TYPE:
2049 present_status_subitem("CONFIGURATION TYPE INVALID");
2053 assert_not_reached();
2059 present_status_item("GPWS COMPUTER OK");
2060 present_status_item("GPWS EXTERNAL FAULTS:");
2062 static const char *fault_names[] = {
2063 "ALL MODES INHIBIT",
2066 "MOMENTARY FLAP OVERRIDE INVALID",
2067 "SELF TEST INVALID",
2068 "GLIDESLOPE CANCEL INVALID",
2069 "STEEP APPROACH INVALID",
2072 "MODES 1-4 INPUTS INVALID",
2073 "MODE 5 INPUTS INVALID",
2074 "MODE 6 INPUTS INVALID",
2075 "BANK ANGLE INPUTS INVALID",
2076 "TCF INPUTS INVALID"
2079 for (int i = 0; i < n_elements(fault_names); i++)
2080 if (mk->fault_handler.faults[i])
2081 present_status_subitem(fault_names[i]);
2086 present_status_section("CONFIGURATION:");
2088 static const char *category_names[] = {
2091 "POSITION INPUT TYPE",
2092 "CALLOUTS OPTION TYPE",
2094 "TERRAIN DISPLAY TYPE",
2096 "RADIO ALTITUDE TYPE",
2097 "NAVIGATION INPUT TYPE",
2099 "MAGNETIC HEADING TYPE",
2100 "WINDSHEAR INPUT TYPE",
2105 for (int i = 0; i < n_elements(category_names); i++)
2107 std::ostringstream value;
2108 value << "= " << mk->configuration_module.effective_categories[i];
2109 present_status_item(category_names[i], value.str().c_str());
2114 MK_VIII::IOHandler::get_present_status () const
2120 MK_VIII::IOHandler::set_present_status (bool value)
2122 if (value) present_status();
2126 ///////////////////////////////////////////////////////////////////////////////
2127 // VoicePlayer ////////////////////////////////////////////////////////////////
2128 ///////////////////////////////////////////////////////////////////////////////
2131 MK_VIII::VoicePlayer::Speaker::bind (SGPropertyNode *node)
2133 // uses xmlsound property names
2134 tie(node, "volume", &volume);
2135 tie(node, "pitch", &pitch);
2136 tie(node, "position/x", &position[0]);
2137 tie(node, "position/y", &position[1]);
2138 tie(node, "position/z", &position[2]);
2139 tie(node, "orientation/x", &orientation[0]);
2140 tie(node, "orientation/y", &orientation[1]);
2141 tie(node, "orientation/z", &orientation[2]);
2142 tie(node, "orientation/inner-cone", &inner_cone);
2143 tie(node, "orientation/outer-cone", &outer_cone);
2144 tie(node, "reference-dist", &reference_dist);
2145 tie(node, "max-dist", &max_dist);
2149 MK_VIII::VoicePlayer::Speaker::update_configuration ()
2151 map<string, SGSoundSample *>::iterator iter;
2152 for (iter = player->samples.begin(); iter != player->samples.end(); iter++)
2154 SGSoundSample *sample = (*iter).second;
2156 sample->set_pitch(pitch);
2157 sample->set_offset_pos(position);
2158 sample->set_orientation(orientation,
2162 sample->set_reference_dist(reference_dist);
2163 sample->set_max_dist(max_dist);
2167 player->voice->volume_changed();
2170 MK_VIII::VoicePlayer::Voice::~Voice ()
2172 for (iter = elements.begin(); iter != elements.end(); iter++)
2173 delete *iter; // we owned the element
2178 MK_VIII::VoicePlayer::Voice::play ()
2180 iter = elements.begin();
2183 element->play(get_volume());
2187 MK_VIII::VoicePlayer::Voice::stop (bool now)
2191 if (now || element->silence)
2197 iter = elements.end() - 1; // stop after the current element finishes
2202 MK_VIII::VoicePlayer::Voice::set_volume (double _volume)
2209 MK_VIII::VoicePlayer::Voice::volume_changed ()
2212 element->set_volume(get_volume());
2216 MK_VIII::VoicePlayer::Voice::update ()
2220 if (! element->is_playing())
2222 if (++iter == elements.end())
2227 element->play(get_volume());
2233 MK_VIII::VoicePlayer::~VoicePlayer ()
2235 vector<Voice *>::iterator iter1;
2236 for (iter1 = _voices.begin(); iter1 != _voices.end(); iter1++)
2240 /* sound mgr already destroyed - samples already deleted
2241 map<string, SGSoundSample *>::iterator iter2;
2242 for (iter2 = samples.begin(); iter2 != samples.end(); iter2++)
2244 bool status = globals->get_soundmgr()->remove((*iter2).first);
2252 MK_VIII::VoicePlayer::init ()
2254 #define STDPAUSE 0.75 // [SPEC] 6.4.4: "the standard 0.75 second delay"
2256 make_voice(&voices.application_data_base_failed, "application-data-base-failed");
2257 make_voice(&voices.bank_angle, "bank-angle");
2258 make_voice(&voices.bank_angle_bank_angle, "bank-angle", "bank-angle");
2259 make_voice(&voices.bank_angle_bank_angle_3, "bank-angle", "bank-angle", 3.0);
2260 make_voice(&voices.bank_angle_inop, "bank-angle-inop");
2261 make_voice(&voices.bank_angle_pause_bank_angle, "bank-angle", STDPAUSE, "bank-angle");
2262 make_voice(&voices.bank_angle_pause_bank_angle_3, "bank-angle", STDPAUSE, "bank-angle", 3.0);
2263 make_voice(&voices.callouts_inop, "callouts-inop");
2264 make_voice(&voices.configuration_type_invalid, "configuration-type-invalid");
2265 make_voice(&voices.dont_sink, "dont-sink");
2266 make_voice(&voices.dont_sink_pause_dont_sink, "dont-sink", STDPAUSE, "dont-sink");
2267 make_voice(&voices.five_hundred_above, "500-above");
2268 make_voice(&voices.glideslope, "glideslope");
2269 make_voice(&voices.glideslope_inop, "glideslope-inop");
2270 make_voice(&voices.gpws_inop, "gpws-inop");
2271 make_voice(&voices.hard_glideslope, "glideslope", "glideslope", 3.0);
2272 make_voice(&voices.minimums, "minimums");
2273 make_voice(&voices.minimums_minimums, "minimums", "minimums");
2274 make_voice(&voices.pull_up, "pull-up");
2275 make_voice(&voices.sink_rate, "sink-rate");
2276 make_voice(&voices.sink_rate_pause_sink_rate, "sink-rate", STDPAUSE, "sink-rate");
2277 make_voice(&voices.soft_glideslope, new Voice::SampleElement(get_sample("glideslope"), modify_amplitude(1.0, -6)));
2278 make_voice(&voices.terrain, "terrain");
2279 make_voice(&voices.terrain_pause_terrain, "terrain", STDPAUSE, "terrain");
2280 make_voice(&voices.too_low_flaps, "too-low-flaps");
2281 make_voice(&voices.too_low_gear, "too-low-gear");
2282 make_voice(&voices.too_low_terrain, "too-low-terrain");
2284 for (int i = 0; i < n_altitude_callouts; i++)
2286 std::ostringstream name;
2287 name << "altitude-" << mk->mode6_handler.altitude_callout_definitions[i];
2288 make_voice(&voices.altitude_callouts[i], name.str().c_str());
2291 speaker.update_configuration();
2295 MK_VIII::VoicePlayer::get_sample (const char *name)
2297 std::ostringstream refname;
2298 refname << mk->name << "[" << mk->num << "]" << "/" << name;
2300 SGSoundSample *sample = globals->get_soundmgr()->find(refname.str());
2303 SGPath sample_path(globals->get_fg_root());
2304 sample_path.append("Sounds/mk-viii");
2306 string filename = string(name) + ".wav";
2309 sample = new SGSoundSample(sample_path.c_str(), filename.c_str());
2311 catch (const sg_exception &e)
2313 SG_LOG(SG_INSTR, SG_ALERT, "Error loading MK VIII sound sample \"" + filename + "\": " + e.getFormattedMessage());
2317 globals->get_soundmgr()->add(sample, refname.str());
2318 samples[refname.str()] = sample;
2325 MK_VIII::VoicePlayer::play (Voice *_voice, unsigned int flags)
2327 if (test_bits(flags, PLAY_NOW) || ! voice || voice->element->silence)
2333 looped = test_bits(flags, PLAY_LOOPED);
2336 next_looped = false;
2342 next_voice = _voice;
2343 next_looped = test_bits(flags, PLAY_LOOPED);
2348 MK_VIII::VoicePlayer::stop (unsigned int flags)
2352 voice->stop(test_bits(flags, STOP_NOW));
2362 MK_VIII::VoicePlayer::set_volume (double _volume)
2366 voice->volume_changed();
2370 MK_VIII::VoicePlayer::update ()
2378 if (! voice->element || voice->element->silence)
2381 looped = next_looped;
2384 next_looped = false;
2391 if (! voice->element)
2402 ///////////////////////////////////////////////////////////////////////////////
2403 // SelfTestHandler ////////////////////////////////////////////////////////////
2404 ///////////////////////////////////////////////////////////////////////////////
2407 MK_VIII::SelfTestHandler::_was_here (int position)
2409 if (position > current)
2418 MK_VIII::SelfTestHandler::Action
2419 MK_VIII::SelfTestHandler::sleep (double duration)
2421 Action _action = { ACTION_SLEEP, duration, NULL };
2425 MK_VIII::SelfTestHandler::Action
2426 MK_VIII::SelfTestHandler::play (VoicePlayer::Voice *voice)
2428 mk->voice_player.play(voice);
2429 Action _action = { ACTION_VOICE, 0, NULL };
2433 MK_VIII::SelfTestHandler::Action
2434 MK_VIII::SelfTestHandler::discrete_on (bool *discrete, double duration)
2437 return sleep(duration);
2440 MK_VIII::SelfTestHandler::Action
2441 MK_VIII::SelfTestHandler::discrete_on_off (bool *discrete, double duration)
2444 Action _action = { ACTION_SLEEP | ACTION_DISCRETE_ON_OFF, duration, discrete };
2448 MK_VIII::SelfTestHandler::Action
2449 MK_VIII::SelfTestHandler::discrete_on_off (bool *discrete, VoicePlayer::Voice *voice)
2452 mk->voice_player.play(voice);
2453 Action _action = { ACTION_VOICE | ACTION_DISCRETE_ON_OFF, 0, discrete };
2457 MK_VIII::SelfTestHandler::Action
2458 MK_VIII::SelfTestHandler::done ()
2460 Action _action = { ACTION_DONE, 0, NULL };
2464 MK_VIII::SelfTestHandler::Action
2465 MK_VIII::SelfTestHandler::run ()
2467 // Note that "Terrain INOP" and "Terrain NA" are or'ed to the same
2468 // output discrete (see [SPEC] 6.9.3.5).
2470 #define was_here() was_here_offset(0)
2471 #define was_here_offset(offset) _was_here(__LINE__ * 20 + (offset))
2475 if (mk->configuration_module.state == ConfigurationModule::STATE_INVALID_DATABASE)
2476 return play(mk_voice(application_data_base_failed));
2477 else if (mk->configuration_module.state == ConfigurationModule::STATE_INVALID_AIRCRAFT_TYPE)
2478 return play(mk_voice(configuration_type_invalid));
2480 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
2484 return discrete_on(&mk_doutput(gpws_inop), 0.7);
2486 return sleep(0.7); // W/S INOP
2488 return discrete_on(&mk_doutput(tad_inop), 0.7);
2491 mk_doutput(gpws_inop) = false;
2492 // do not disable tad_inop since we must enable Terrain NA
2493 // do not disable W/S INOP since we do not emulate it
2494 return sleep(0.7); // Terrain NA
2498 mk_doutput(tad_inop) = false; // disable Terrain NA
2499 if (mk->io_handler.conf.momentary_flap_override_enabled)
2500 return discrete_on_off(&mk_doutput(flap_override), 1.0);
2503 return discrete_on_off(&mk_doutput(audio_on), 1.0);
2506 if (mk->io_handler.momentary_steep_approach_enabled())
2507 return discrete_on_off(&mk_doutput(steep_approach), 1.0);
2512 if (mk_dinput(glideslope_inhibit))
2513 goto glideslope_end;
2516 if (mk->fault_handler.faults[FaultHandler::FAULT_MODE5_INPUTS_INVALID])
2517 goto glideslope_inop;
2521 return discrete_on_off(&mk_doutput(gpws_alert), mk_voice(glideslope));
2523 return discrete_on_off(&mk_doutput(glideslope_cancel), 0.7);
2524 goto glideslope_end;
2527 return play(mk_voice(glideslope_inop));
2532 if (mk->fault_handler.faults[FaultHandler::FAULT_MODES14_INPUTS_INVALID])
2536 return discrete_on_off(&mk_doutput(gpws_warning), mk_voice(pull_up));
2540 return play(mk_voice(gpws_inop));
2545 if (mk_dinput(self_test)) // proceed to long self test
2550 if (mk->mode6_handler.conf.bank_angle_enabled
2551 && mk->fault_handler.faults[FaultHandler::FAULT_BANK_ANGLE_INPUTS_INVALID])
2552 return play(mk_voice(bank_angle_inop));
2556 if (mk->mode6_handler.altitude_callouts_enabled()
2557 && mk->fault_handler.faults[FaultHandler::FAULT_MODE6_INPUTS_INVALID])
2558 return play(mk_voice(callouts_inop));
2566 mk_doutput(gpws_inop) = true;
2567 // do not enable W/S INOP, since we do not emulate it
2568 mk_doutput(tad_inop) = true; // Terrain INOP, Terrain NA
2570 return play(mk_voice(sink_rate));
2573 return play(mk_voice(pull_up));
2575 return play(mk_voice(terrain));
2577 return play(mk_voice(pull_up));
2579 return play(mk_voice(dont_sink));
2581 return play(mk_voice(too_low_terrain));
2583 return play(mk->mode4_handler.conf.voice_too_low_gear);
2585 return play(mk_voice(too_low_flaps));
2587 return play(mk_voice(too_low_terrain));
2589 return play(mk_voice(glideslope));
2592 if (mk->mode6_handler.conf.bank_angle_enabled)
2593 return play(mk_voice(bank_angle));
2598 if (! mk->mode6_handler.altitude_callouts_enabled())
2599 goto callouts_disabled;
2603 if (mk->mode6_handler.conf.minimums_enabled)
2604 return play(mk_voice(minimums));
2608 if (mk->mode6_handler.conf.above_field_voice)
2609 return play(mk->mode6_handler.conf.above_field_voice);
2611 for (int i = 0; i < n_altitude_callouts; i++)
2612 if (! was_here_offset(i))
2614 if (mk->mode6_handler.conf.altitude_callouts_enabled[i])
2615 return play(mk_altitude_voice(i));
2619 if (mk->mode6_handler.conf.smart_500_enabled)
2620 return play(mk_altitude_voice(Mode6Handler::ALTITUDE_CALLOUT_500));
2625 return play(mk_voice(minimums_minimums));
2630 if (mk->tcf_handler.conf.enabled)
2631 return play(mk_voice(too_low_terrain));
2638 MK_VIII::SelfTestHandler::start ()
2640 assert(state == STATE_START);
2642 memcpy(&saved_outputs, &mk->io_handler.outputs, sizeof(mk->io_handler.outputs));
2643 memset(&mk->io_handler.outputs, 0, sizeof(mk->io_handler.outputs));
2645 // [SPEC] 6.10.6: "The self-test results are annunciated, at 6db
2646 // lower than the normal audio level selected for the aircraft"
2647 mk->voice_player.set_volume(modify_amplitude(mk->voice_player.conf.volume, -6));
2649 if (mk_dinput(mode6_low_volume))
2650 mk->mode6_handler.set_volume(1.0);
2652 state = STATE_RUNNING;
2653 cancel = CANCEL_NONE;
2654 memset(&action, 0, sizeof(action));
2659 MK_VIII::SelfTestHandler::stop ()
2661 if (state != STATE_NONE)
2663 if (state != STATE_START)
2665 mk->voice_player.stop(VoicePlayer::STOP_NOW);
2666 mk->voice_player.set_volume(mk_dinput(audio_inhibit) ? 0.0 : mk->voice_player.conf.volume);
2668 if (mk_dinput(mode6_low_volume))
2669 mk->mode6_handler.set_volume(modify_amplitude(1.0, -6));
2671 memcpy(&mk->io_handler.outputs, &saved_outputs, sizeof(mk->io_handler.outputs));
2674 button_pressed = false;
2680 MK_VIII::SelfTestHandler::handle_button_event (bool value)
2682 if (state == STATE_NONE)
2685 state = STATE_START;
2687 else if (state == STATE_START)
2690 state = STATE_NONE; // cancel the not-yet-started test
2692 else if (cancel == CANCEL_NONE)
2696 assert(! button_pressed);
2697 button_pressed = true;
2698 button_press_timestamp = globals->get_sim_time_sec();
2704 if (globals->get_sim_time_sec() - button_press_timestamp < 2)
2705 cancel = CANCEL_SHORT;
2707 cancel = CANCEL_LONG;
2714 MK_VIII::SelfTestHandler::update ()
2716 if (state == STATE_NONE)
2718 else if (state == STATE_START)
2720 if (mk->state_handler.ground && ! mk->io_handler.steep_approach())
2730 if (button_pressed && cancel == CANCEL_NONE)
2732 if (globals->get_sim_time_sec() - button_press_timestamp >= 2)
2733 cancel = CANCEL_LONG;
2737 if (! mk->state_handler.ground || cancel != CANCEL_NONE)
2743 if (test_bits(action.flags, ACTION_SLEEP))
2745 if (globals->get_sim_time_sec() - sleep_start < action.sleep_duration)
2748 if (test_bits(action.flags, ACTION_VOICE))
2750 if (mk->voice_player.voice)
2753 if (test_bits(action.flags, ACTION_DISCRETE_ON_OFF))
2754 *action.discrete = false;
2758 if (test_bits(action.flags, ACTION_SLEEP))
2759 sleep_start = globals->get_sim_time_sec();
2760 if (test_bits(action.flags, ACTION_DONE))
2769 ///////////////////////////////////////////////////////////////////////////////
2770 // AlertHandler ///////////////////////////////////////////////////////////////
2771 ///////////////////////////////////////////////////////////////////////////////
2774 MK_VIII::AlertHandler::select_voice_alerts (unsigned int test)
2776 if (has_alerts(test))
2778 voice_alerts = alerts & test;
2783 voice_alerts &= ~test;
2784 if (voice_alerts == 0)
2785 mk->voice_player.stop();
2792 MK_VIII::AlertHandler::boot ()
2798 MK_VIII::AlertHandler::reposition ()
2802 mk->io_handler.set_lamp(IOHandler::LAMP_NONE);
2803 mk->voice_player.stop(VoicePlayer::STOP_NOW);
2807 MK_VIII::AlertHandler::reset ()
2812 repeated_alerts = 0;
2813 altitude_callout_voice = NULL;
2817 MK_VIII::AlertHandler::update ()
2819 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
2822 // Lamps and voices are prioritized according to [SPEC] 6.9.2.
2824 // Voices which can interrupt other voices (VoicePlayer::PLAY_NOW)
2825 // are specified by [INSTALL] appendix E 5.3.5.
2827 // When a voice is overriden by a higher priority voice and the
2828 // overriding voice stops, we restore the previous voice if it was
2829 // looped (this is not specified by [SPEC] but seems to make sense).
2833 if (has_alerts(ALERT_MODE1_PULL_UP | ALERT_MODE2A | ALERT_MODE2B))
2834 mk->io_handler.set_lamp(IOHandler::LAMP_WARNING);
2835 else if (has_alerts(ALERT_MODE1_SINK_RATE
2836 | ALERT_MODE2A_PREFACE
2837 | ALERT_MODE2B_PREFACE
2838 | ALERT_MODE2A_ALTITUDE_GAIN_TERRAIN_CLOSING
2839 | ALERT_MODE2A_ALTITUDE_GAIN
2840 | ALERT_MODE2B_LANDING_MODE
2842 | ALERT_MODE4_TOO_LOW_FLAPS
2843 | ALERT_MODE4_TOO_LOW_GEAR
2844 | ALERT_MODE4AB_TOO_LOW_TERRAIN
2845 | ALERT_MODE4C_TOO_LOW_TERRAIN
2846 | ALERT_TCF_TOO_LOW_TERRAIN))
2847 mk->io_handler.set_lamp(IOHandler::LAMP_CAUTION);
2848 else if (has_alerts(ALERT_MODE5_SOFT | ALERT_MODE5_HARD))
2849 mk->io_handler.set_lamp(IOHandler::LAMP_GLIDESLOPE);
2851 mk->io_handler.set_lamp(IOHandler::LAMP_NONE);
2855 if (select_voice_alerts(ALERT_MODE1_PULL_UP))
2857 if (! has_old_alerts(ALERT_MODE1_PULL_UP))
2859 if (mk->voice_player.voice != mk_voice(sink_rate_pause_sink_rate))
2860 mk->voice_player.play(mk_voice(sink_rate), VoicePlayer::PLAY_NOW);
2861 mk->voice_player.play(mk_voice(pull_up), VoicePlayer::PLAY_LOOPED);
2864 else if (select_voice_alerts(ALERT_MODE2A_PREFACE | ALERT_MODE2B_PREFACE))
2866 if (! has_old_alerts(ALERT_MODE2A_PREFACE | ALERT_MODE2B_PREFACE))
2867 mk->voice_player.play(mk_voice(terrain_pause_terrain), VoicePlayer::PLAY_NOW);
2869 else if (select_voice_alerts(ALERT_MODE2A | ALERT_MODE2B))
2871 if (mk->voice_player.voice != mk_voice(pull_up))
2872 mk->voice_player.play(mk_voice(pull_up), VoicePlayer::PLAY_NOW | VoicePlayer::PLAY_LOOPED);
2874 else if (select_voice_alerts(ALERT_MODE2A_ALTITUDE_GAIN_TERRAIN_CLOSING | ALERT_MODE2B_LANDING_MODE))
2876 if (mk->voice_player.voice != mk_voice(terrain))
2877 mk->voice_player.play(mk_voice(terrain), VoicePlayer::PLAY_LOOPED);
2879 else if (select_voice_alerts(ALERT_MODE6_MINIMUMS))
2881 if (! has_old_alerts(ALERT_MODE6_MINIMUMS))
2882 mk->voice_player.play(mk_voice(minimums_minimums));
2884 else if (select_voice_alerts(ALERT_MODE4AB_TOO_LOW_TERRAIN | ALERT_MODE4C_TOO_LOW_TERRAIN))
2886 if (must_play_voice(ALERT_MODE4AB_TOO_LOW_TERRAIN | ALERT_MODE4C_TOO_LOW_TERRAIN))
2887 mk->voice_player.play(mk_voice(too_low_terrain));
2889 else if (select_voice_alerts(ALERT_TCF_TOO_LOW_TERRAIN))
2891 if (must_play_voice(ALERT_TCF_TOO_LOW_TERRAIN))
2892 mk->voice_player.play(mk_voice(too_low_terrain));
2894 else if (select_voice_alerts(ALERT_MODE6_ALTITUDE_CALLOUT))
2896 if (! has_old_alerts(ALERT_MODE6_ALTITUDE_CALLOUT))
2898 assert(altitude_callout_voice != NULL);
2899 mk->voice_player.play(altitude_callout_voice);
2902 else if (select_voice_alerts(ALERT_MODE4_TOO_LOW_GEAR))
2904 if (must_play_voice(ALERT_MODE4_TOO_LOW_GEAR))
2905 mk->voice_player.play(mk->mode4_handler.conf.voice_too_low_gear);
2907 else if (select_voice_alerts(ALERT_MODE4_TOO_LOW_FLAPS))
2909 if (must_play_voice(ALERT_MODE4_TOO_LOW_FLAPS))
2910 mk->voice_player.play(mk_voice(too_low_flaps));
2912 else if (select_voice_alerts(ALERT_MODE1_SINK_RATE))
2914 if (must_play_voice(ALERT_MODE1_SINK_RATE))
2915 mk->voice_player.play(mk_voice(sink_rate_pause_sink_rate));
2916 // [SPEC] 6.2.1: "During the time that the voice message for the
2917 // outer envelope is inhibited and the caution/warning lamp is
2918 // on, the Mode 5 alert message is allowed to annunciate for
2919 // excessive glideslope deviation conditions."
2920 else if (mk->voice_player.voice != mk_voice(sink_rate_pause_sink_rate)
2921 && mk->voice_player.next_voice != mk_voice(sink_rate_pause_sink_rate))
2923 if (has_alerts(ALERT_MODE5_HARD))
2925 voice_alerts |= ALERT_MODE5_HARD;
2926 if (mk->voice_player.voice != mk_voice(hard_glideslope))
2927 mk->voice_player.play(mk_voice(hard_glideslope), VoicePlayer::PLAY_LOOPED);
2929 else if (has_alerts(ALERT_MODE5_SOFT))
2931 voice_alerts |= ALERT_MODE5_SOFT;
2932 if (must_play_voice(ALERT_MODE5_SOFT))
2933 mk->voice_player.play(mk_voice(soft_glideslope));
2937 else if (select_voice_alerts(ALERT_MODE3))
2939 if (must_play_voice(ALERT_MODE3))
2940 mk->voice_player.play(mk_voice(dont_sink_pause_dont_sink));
2942 else if (select_voice_alerts(ALERT_MODE5_HARD))
2944 if (mk->voice_player.voice != mk_voice(hard_glideslope))
2945 mk->voice_player.play(mk_voice(hard_glideslope), VoicePlayer::PLAY_LOOPED);
2947 else if (select_voice_alerts(ALERT_MODE5_SOFT))
2949 if (must_play_voice(ALERT_MODE5_SOFT))
2950 mk->voice_player.play(mk_voice(soft_glideslope));
2952 else if (select_voice_alerts(ALERT_MODE6_LOW_BANK_ANGLE_3))
2954 if (mk->voice_player.voice != mk_voice(bank_angle_bank_angle_3))
2955 mk->voice_player.play(mk_voice(bank_angle_bank_angle_3), VoicePlayer::PLAY_LOOPED);
2957 else if (select_voice_alerts(ALERT_MODE6_HIGH_BANK_ANGLE_3))
2959 if (mk->voice_player.voice != mk_voice(bank_angle_pause_bank_angle_3))
2960 mk->voice_player.play(mk_voice(bank_angle_pause_bank_angle_3), VoicePlayer::PLAY_LOOPED);
2962 else if (select_voice_alerts(ALERT_MODE6_LOW_BANK_ANGLE_2))
2964 if (! has_old_alerts(ALERT_MODE6_LOW_BANK_ANGLE_2 | ALERT_MODE6_HIGH_BANK_ANGLE_2))
2965 mk->voice_player.play(mk_voice(bank_angle_bank_angle));
2967 else if (select_voice_alerts(ALERT_MODE6_HIGH_BANK_ANGLE_2))
2969 if (! has_old_alerts(ALERT_MODE6_LOW_BANK_ANGLE_2 | ALERT_MODE6_HIGH_BANK_ANGLE_2))
2970 mk->voice_player.play(mk_voice(bank_angle_pause_bank_angle));
2972 else if (select_voice_alerts(ALERT_MODE6_LOW_BANK_ANGLE_1))
2974 if (! has_old_alerts(ALERT_MODE6_LOW_BANK_ANGLE_1 | ALERT_MODE6_HIGH_BANK_ANGLE_1))
2975 mk->voice_player.play(mk_voice(bank_angle_bank_angle));
2977 else if (select_voice_alerts(ALERT_MODE6_HIGH_BANK_ANGLE_1))
2979 if (! has_old_alerts(ALERT_MODE6_LOW_BANK_ANGLE_1 | ALERT_MODE6_HIGH_BANK_ANGLE_1))
2980 mk->voice_player.play(mk_voice(bank_angle_pause_bank_angle));
2985 old_alerts = alerts;
2986 repeated_alerts = 0;
2987 altitude_callout_voice = NULL;
2991 MK_VIII::AlertHandler::set_alerts (unsigned int _alerts,
2993 VoicePlayer::Voice *_altitude_callout_voice)
2996 if (test_bits(flags, ALERT_FLAG_REPEAT))
2997 repeated_alerts |= _alerts;
2998 if (_altitude_callout_voice)
2999 altitude_callout_voice = _altitude_callout_voice;
3003 MK_VIII::AlertHandler::unset_alerts (unsigned int _alerts)
3006 repeated_alerts &= ~_alerts;
3009 ///////////////////////////////////////////////////////////////////////////////
3010 // StateHandler ///////////////////////////////////////////////////////////////
3011 ///////////////////////////////////////////////////////////////////////////////
3014 MK_VIII::StateHandler::update_ground ()
3018 if (! mk_ainput(computed_airspeed).ncd && mk_ainput(computed_airspeed).get() > 60
3019 && ! mk_data(radio_altitude).ncd && mk_data(radio_altitude).get() > 30)
3021 if (potentially_airborne_timer.start_or_elapsed() > 1)
3025 potentially_airborne_timer.stop();
3029 if (! mk_ainput(computed_airspeed).ncd && mk_ainput(computed_airspeed).get() < 40
3030 && ! mk_data(radio_altitude).ncd && mk_data(radio_altitude).get() < 30)
3036 MK_VIII::StateHandler::enter_ground ()
3039 mk->io_handler.enter_ground();
3041 // [SPEC] 6.0.1 does not specify this, but it seems to be an
3042 // omission; at this point, we must make sure that we are in takeoff
3043 // mode (otherwise the next takeoff might happen in approach mode).
3049 MK_VIII::StateHandler::leave_ground ()
3052 mk->mode2_handler.leave_ground();
3056 MK_VIII::StateHandler::update_takeoff ()
3060 // [SPEC] 6.0.2 is somewhat confusing: it mentions hardcoded
3061 // terrain clearance (500, 750) and airspeed (178, 200) values,
3062 // but it also mentions the mode 4A boundary, which varies as a
3063 // function of aircraft type. We consider that the mentioned
3064 // values are examples, and that we should use the mode 4A upper
3067 if (! mk_data(terrain_clearance).ncd
3068 && ! mk_ainput(computed_airspeed).ncd
3069 && mk_data(terrain_clearance).get() > mk->mode4_handler.get_upper_agl(mk->mode4_handler.conf.modes->ac))
3074 if (! mk_data(radio_altitude).ncd
3075 && mk_data(radio_altitude).get() < mk->mode4_handler.conf.modes->b->min_agl1
3076 && mk->io_handler.flaps_down()
3077 && mk_dinput(landing_gear))
3083 MK_VIII::StateHandler::enter_takeoff ()
3086 mk->io_handler.enter_takeoff();
3087 mk->mode2_handler.enter_takeoff();
3088 mk->mode3_handler.enter_takeoff();
3089 mk->mode6_handler.enter_takeoff();
3093 MK_VIII::StateHandler::leave_takeoff ()
3096 mk->mode6_handler.leave_takeoff();
3100 MK_VIII::StateHandler::post_reposition ()
3102 // Synchronize the state with the current situation.
3105 (! mk_ainput(computed_airspeed).ncd && mk_ainput(computed_airspeed).get() > 60
3106 && ! mk_data(radio_altitude).ncd && mk_data(radio_altitude).get() > 30);
3108 bool _takeoff = _ground;
3110 if (ground && ! _ground)
3112 else if (! ground && _ground)
3115 if (takeoff && ! _takeoff)
3117 else if (! takeoff && _takeoff)
3122 MK_VIII::StateHandler::update ()
3124 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
3131 ///////////////////////////////////////////////////////////////////////////////
3132 // Mode1Handler ///////////////////////////////////////////////////////////////
3133 ///////////////////////////////////////////////////////////////////////////////
3136 MK_VIII::Mode1Handler::get_pull_up_bias ()
3138 // [PILOT] page 54: "Steep Approach has priority over Flap Override
3139 // if selected simultaneously."
3141 if (mk->io_handler.steep_approach())
3143 else if (conf.envelopes->flap_override_bias && mk->io_handler.flap_override())
3150 MK_VIII::Mode1Handler::is_pull_up ()
3152 if (! mk->io_handler.gpws_inhibit()
3153 && ! mk->state_handler.ground // [1]
3154 && ! mk_data(radio_altitude).ncd
3155 && ! mk_data(barometric_altitude_rate).ncd
3156 && mk_data(radio_altitude).get() > conf.envelopes->min_agl)
3158 if (mk_data(radio_altitude).get() < conf.envelopes->pull_up_max_agl1)
3160 if (mk_data(radio_altitude).get() < conf.envelopes->pull_up_min_agl1(mk_data(barometric_altitude_rate).get() + get_pull_up_bias()))
3163 else if (mk_data(radio_altitude).get() < conf.envelopes->pull_up_max_agl2)
3165 if (mk_data(radio_altitude).get() < conf.envelopes->pull_up_min_agl2(mk_data(barometric_altitude_rate).get() + get_pull_up_bias()))
3174 MK_VIII::Mode1Handler::update_pull_up ()
3178 if (! mk_test_alert(MODE1_PULL_UP))
3180 // [SPEC] 6.2.1: at least one sink rate must be issued
3181 // before switching to pull up; if no sink rate has
3182 // occurred, a 0.2 second delay is used.
3183 if (mk->voice_player.voice == mk_voice(sink_rate_pause_sink_rate)
3184 || pull_up_timer.start_or_elapsed() >= 0.2)
3185 mk_set_alerts(mk_alert(MODE1_PULL_UP));
3190 pull_up_timer.stop();
3191 mk_unset_alerts(mk_alert(MODE1_PULL_UP));
3196 MK_VIII::Mode1Handler::get_sink_rate_bias ()
3198 // [PILOT] page 54: "Steep Approach has priority over Flap Override
3199 // if selected simultaneously."
3201 if (mk->io_handler.steep_approach())
3203 else if (conf.envelopes->flap_override_bias && mk->io_handler.flap_override())
3205 else if (! mk_data(glideslope_deviation_dots).ncd)
3209 if (mk_data(glideslope_deviation_dots).get() <= -2)
3211 else if (mk_data(glideslope_deviation_dots).get() < 0)
3212 bias = -150 * mk_data(glideslope_deviation_dots).get();
3214 if (mk_data(radio_altitude).get() < 100)
3215 bias *= 0.01 * mk_data(radio_altitude).get();
3224 MK_VIII::Mode1Handler::is_sink_rate ()
3226 return ! mk->io_handler.gpws_inhibit()
3227 && ! mk->state_handler.ground // [1]
3228 && ! mk_data(radio_altitude).ncd
3229 && ! mk_data(barometric_altitude_rate).ncd
3230 && mk_data(radio_altitude).get() > conf.envelopes->min_agl
3231 && mk_data(radio_altitude).get() < 2450
3232 && mk_data(radio_altitude).get() < -572 - 0.6035 * (mk_data(barometric_altitude_rate).get() + get_sink_rate_bias());
3236 MK_VIII::Mode1Handler::get_sink_rate_tti ()
3238 return mk_data(radio_altitude).get() / fabs(mk_data(barometric_altitude_rate).get());
3242 MK_VIII::Mode1Handler::update_sink_rate ()
3246 if (mk_test_alert(MODE1_SINK_RATE))
3248 double tti = get_sink_rate_tti();
3249 if (tti <= sink_rate_tti * 0.8)
3251 // ~20% degradation, warn again and store the new reference tti
3252 sink_rate_tti = tti;
3253 mk_repeat_alert(mk_alert(MODE1_SINK_RATE));
3258 if (sink_rate_timer.start_or_elapsed() >= 0.8)
3260 mk_set_alerts(mk_alert(MODE1_SINK_RATE));
3261 sink_rate_tti = get_sink_rate_tti();
3267 sink_rate_timer.stop();
3268 mk_unset_alerts(mk_alert(MODE1_SINK_RATE));
3273 MK_VIII::Mode1Handler::update ()
3275 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
3282 ///////////////////////////////////////////////////////////////////////////////
3283 // Mode2Handler ///////////////////////////////////////////////////////////////
3284 ///////////////////////////////////////////////////////////////////////////////
3287 MK_VIII::Mode2Handler::ClosureRateFilter::limit_radio_altitude_rate (double r)
3289 // Limit radio altitude rate according to aircraft configuration,
3290 // allowing maximum sensitivity during cruise while providing
3291 // progressively less sensitivity during the landing phases of
3294 if (! mk_data(glideslope_deviation_dots).ncd && fabs(mk_data(glideslope_deviation_dots).get()) <= 2)
3295 { // gs deviation <= +- 2 dots
3296 if (mk_dinput(landing_gear) && mk->io_handler.flaps_down())
3297 SG_CLAMP_RANGE(r, -1000.0, 3000.0);
3298 else if (mk_dinput(landing_gear) || mk->io_handler.flaps_down())
3299 SG_CLAMP_RANGE(r, 0.0, 4000.0);
3301 SG_CLAMP_RANGE(r, 1000.0, 5000.0);
3304 { // no ILS, or gs deviation > +- 2 dots
3305 if (mk_dinput(landing_gear) && mk->io_handler.flaps_down())
3306 SG_CLAMP_RANGE(r, 0.0, 4000.0);
3307 else if (mk_dinput(landing_gear) || mk->io_handler.flaps_down())
3308 SG_CLAMP_RANGE(r, 1000.0, 5000.0);
3316 MK_VIII::Mode2Handler::ClosureRateFilter::init ()
3319 last_ra.set(&mk_data(radio_altitude));
3320 last_ba.set(&mk_ainput(uncorrected_barometric_altitude));
3327 MK_VIII::Mode2Handler::ClosureRateFilter::update ()
3329 double elapsed = timer.start_or_elapsed();
3333 if (! mk_data(radio_altitude).ncd && ! mk_ainput(uncorrected_barometric_altitude).ncd)
3335 if (! last_ra.ncd && ! last_ba.ncd)
3337 // compute radio and barometric altitude rates (positive value = descent)
3338 double ra_rate = -(mk_data(radio_altitude).get() - last_ra.get()) / elapsed * 60;
3339 double ba_rate = -(mk_ainput(uncorrected_barometric_altitude).get() - last_ba.get()) / elapsed * 60;
3341 // limit radio altitude rate according to aircraft configuration
3342 ra_rate = limit_radio_altitude_rate(ra_rate);
3344 // apply a low-pass filter to the radio altitude rate
3345 ra_rate = ra_filter.filter(ra_rate);
3346 // apply a high-pass filter to the barometric altitude rate
3347 ba_rate = ba_filter.filter(ba_rate);
3349 // combine both rates to obtain a closure rate
3350 output.set(ra_rate + ba_rate);
3363 last_ra.set(&mk_data(radio_altitude));
3364 last_ba.set(&mk_ainput(uncorrected_barometric_altitude));
3368 MK_VIII::Mode2Handler::b_conditions ()
3370 return mk->io_handler.flaps_down()
3371 || (! mk_data(glideslope_deviation_dots).ncd && fabs(mk_data(glideslope_deviation_dots).get()) < 2)
3372 || takeoff_timer.running;
3376 MK_VIII::Mode2Handler::is_a ()
3378 if (! mk->io_handler.gpws_inhibit()
3379 && ! mk->state_handler.ground // [1]
3380 && ! mk_data(radio_altitude).ncd
3381 && ! mk_ainput(computed_airspeed).ncd
3382 && ! closure_rate_filter.output.ncd
3383 && ! b_conditions())
3385 if (mk_data(radio_altitude).get() < 1220)
3387 if (mk_data(radio_altitude).get() < -1579 + 0.7895 * closure_rate_filter.output.get())
3394 if (mk_ainput(computed_airspeed).get() <= conf->airspeed1)
3396 else if (mk_ainput(computed_airspeed).get() >= conf->airspeed2)
3399 upper_limit = 1650 + 8.9 * (mk_ainput(computed_airspeed).get() - conf->airspeed1);
3401 if (mk_data(radio_altitude).get() < upper_limit)
3403 if (mk_data(radio_altitude).get() < 522 + 0.1968 * closure_rate_filter.output.get())
3413 MK_VIII::Mode2Handler::is_b ()
3415 if (! mk->io_handler.gpws_inhibit()
3416 && ! mk->state_handler.ground // [1]
3417 && ! mk_data(radio_altitude).ncd
3418 && ! mk_data(barometric_altitude_rate).ncd
3419 && ! closure_rate_filter.output.ncd
3421 && mk_data(radio_altitude).get() < 789)
3425 if (mk->io_handler.flaps_down())
3427 if (mk_data(barometric_altitude_rate).get() > -400)
3429 else if (mk_data(barometric_altitude_rate).get() < -1000)
3432 lower_limit = -66.777 - 0.667 * mk_data(barometric_altitude_rate).get();
3437 if (mk_data(radio_altitude).get() > lower_limit)
3439 if (mk_data(radio_altitude).get() < -1579 + 0.7895 * closure_rate_filter.output.get())
3448 MK_VIII::Mode2Handler::check_pull_up (unsigned int preface_alert,
3451 if (pull_up_timer.running)
3453 if (pull_up_timer.elapsed() >= 1)
3455 mk_unset_alerts(preface_alert);
3456 mk_set_alerts(alert);
3461 if (! mk->voice_player.voice)
3462 pull_up_timer.start();
3467 MK_VIII::Mode2Handler::update_a ()
3471 if (mk_test_alert(MODE2A_PREFACE))
3472 check_pull_up(mk_alert(MODE2A_PREFACE), mk_alert(MODE2A));
3473 else if (! mk_test_alert(MODE2A))
3475 mk_unset_alerts(mk_alert(MODE2A_ALTITUDE_GAIN) | mk_alert(MODE2A_ALTITUDE_GAIN_TERRAIN_CLOSING));
3476 mk_set_alerts(mk_alert(MODE2A_PREFACE));
3477 a_start_time = globals->get_sim_time_sec();
3478 pull_up_timer.stop();
3483 if (mk_test_alert(MODE2A_ALTITUDE_GAIN))
3485 if (mk->io_handler.gpws_inhibit()
3486 || mk->state_handler.ground // [1]
3487 || a_altitude_gain_timer.elapsed() >= 45
3488 || mk_data(radio_altitude).ncd
3489 || mk_ainput(uncorrected_barometric_altitude).ncd
3490 || mk_ainput(uncorrected_barometric_altitude).get() - a_altitude_gain_alt >= 300
3491 // [PILOT] page 12: "the visual alert will remain on
3492 // until [...] landing flaps or the flap override switch
3494 || mk->io_handler.flaps_down())
3496 // exit altitude gain mode
3497 a_altitude_gain_timer.stop();
3498 mk_unset_alerts(mk_alert(MODE2A_ALTITUDE_GAIN) | mk_alert(MODE2A_ALTITUDE_GAIN_TERRAIN_CLOSING));
3502 // [SPEC] 6.2.2.2: "If the terrain starts to fall away
3503 // during this altitude gain time, the voice will shut
3505 if (closure_rate_filter.output.get() < 0)
3506 mk_unset_alerts(mk_alert(MODE2A_ALTITUDE_GAIN_TERRAIN_CLOSING));
3509 else if (mk_test_alerts(mk_alert(MODE2A_PREFACE) | mk_alert(MODE2A)))
3511 mk_unset_alerts(mk_alert(MODE2A_PREFACE) | mk_alert(MODE2A));
3513 if (! mk->io_handler.gpws_inhibit()
3514 && ! mk->state_handler.ground // [1]
3515 && globals->get_sim_time_sec() - a_start_time > 3
3516 && ! mk->io_handler.flaps_down()
3517 && ! mk_data(radio_altitude).ncd
3518 && ! mk_ainput(uncorrected_barometric_altitude).ncd)
3520 // [SPEC] 6.2.2.2: mode 2A envelope violated for more
3521 // than 3 seconds, enable altitude gain feature
3523 a_altitude_gain_timer.start();
3524 a_altitude_gain_alt = mk_ainput(uncorrected_barometric_altitude).get();
3526 unsigned int alerts = mk_alert(MODE2A_ALTITUDE_GAIN);
3527 if (closure_rate_filter.output.get() > 0)
3528 alerts |= mk_alert(MODE2A_ALTITUDE_GAIN_TERRAIN_CLOSING);
3530 mk_set_alerts(alerts);
3537 MK_VIII::Mode2Handler::update_b ()
3541 // handle normal mode
3543 if (b && (! mk_dinput(landing_gear) || ! mk->io_handler.flaps_down()))
3545 if (mk_test_alert(MODE2B_PREFACE))
3546 check_pull_up(mk_alert(MODE2B_PREFACE), mk_alert(MODE2B));
3547 else if (! mk_test_alert(MODE2B))
3549 mk_set_alerts(mk_alert(MODE2B_PREFACE));
3550 pull_up_timer.stop();
3554 mk_unset_alerts(mk_alert(MODE2B_PREFACE) | mk_alert(MODE2B));
3556 // handle landing mode ([SPEC] 6.2.2.3: "If both landing gear and
3557 // flaps are in the landing configuration, then the message will be
3560 if (b && mk_dinput(landing_gear) && mk->io_handler.flaps_down())
3561 mk_set_alerts(mk_alert(MODE2B_LANDING_MODE));
3563 mk_unset_alerts(mk_alert(MODE2B_LANDING_MODE));
3567 MK_VIII::Mode2Handler::boot ()
3569 closure_rate_filter.init();
3573 MK_VIII::Mode2Handler::power_off ()
3575 // [SPEC] 6.0.4: "This latching function is not power saved and a
3576 // system reset will force it false."
3577 takeoff_timer.stop();
3581 MK_VIII::Mode2Handler::leave_ground ()
3583 // takeoff, reset timer
3584 takeoff_timer.start();
3588 MK_VIII::Mode2Handler::enter_takeoff ()
3590 // reset timer, in case it's a go-around
3591 takeoff_timer.start();
3595 MK_VIII::Mode2Handler::update ()
3597 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
3600 closure_rate_filter.update();
3602 if (takeoff_timer.running && takeoff_timer.elapsed() >= 60)
3603 takeoff_timer.stop();
3609 ///////////////////////////////////////////////////////////////////////////////
3610 // Mode3Handler ///////////////////////////////////////////////////////////////
3611 ///////////////////////////////////////////////////////////////////////////////
3614 MK_VIII::Mode3Handler::max_alt_loss (double _bias)
3616 return conf->max_alt_loss(mk->io_handler.flap_override(), mk_data(radio_altitude).get()) + mk_data(radio_altitude).get() * _bias;
3620 MK_VIII::Mode3Handler::get_bias (double initial_bias, double alt_loss)
3622 if (mk_data(radio_altitude).get() > 0)
3623 while (alt_loss > max_alt_loss(initial_bias))
3624 initial_bias += 0.2;
3626 return initial_bias;
3630 MK_VIII::Mode3Handler::is (double *alt_loss)
3632 if (has_descent_alt)
3634 int max_agl = conf->max_agl(mk->io_handler.flap_override());
3636 if (mk_ainput(uncorrected_barometric_altitude).ncd
3637 || mk_ainput(uncorrected_barometric_altitude).get() > descent_alt
3638 || mk_data(radio_altitude).ncd
3639 || mk_data(radio_altitude).get() > max_agl)
3642 has_descent_alt = false;
3646 // gear/flaps: [SPEC] 1.3.1.3
3647 if (! mk->io_handler.gpws_inhibit()
3648 && ! mk->state_handler.ground // [1]
3649 && (! mk_dinput(landing_gear) || ! mk->io_handler.flaps_down())
3650 && ! mk_data(barometric_altitude_rate).ncd
3651 && ! mk_ainput(uncorrected_barometric_altitude).ncd
3652 && ! mk_data(radio_altitude).ncd
3653 && mk_data(barometric_altitude_rate).get() < 0)
3655 double _alt_loss = descent_alt - mk_ainput(uncorrected_barometric_altitude).get();
3656 if (armed || (mk_data(radio_altitude).get() > conf->min_agl
3657 && mk_data(radio_altitude).get() < max_agl
3658 && _alt_loss > max_alt_loss(0)))
3660 *alt_loss = _alt_loss;
3668 if (! mk_data(barometric_altitude_rate).ncd
3669 && ! mk_ainput(uncorrected_barometric_altitude).ncd
3670 && mk_data(barometric_altitude_rate).get() < 0)
3672 has_descent_alt = true;
3673 descent_alt = mk_ainput(uncorrected_barometric_altitude).get();
3681 MK_VIII::Mode3Handler::enter_takeoff ()
3684 has_descent_alt = false;
3688 MK_VIII::Mode3Handler::update ()
3690 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
3693 if (mk->state_handler.takeoff)
3697 if (! mk->state_handler.ground /* [1] */ && is(&alt_loss))
3699 if (mk_test_alert(MODE3))
3701 double new_bias = get_bias(bias, alt_loss);
3702 if (new_bias > bias)
3705 mk_repeat_alert(mk_alert(MODE3));
3711 bias = get_bias(0.2, alt_loss);
3712 mk_set_alerts(mk_alert(MODE3));
3719 mk_unset_alerts(mk_alert(MODE3));
3722 ///////////////////////////////////////////////////////////////////////////////
3723 // Mode4Handler ///////////////////////////////////////////////////////////////
3724 ///////////////////////////////////////////////////////////////////////////////
3726 // FIXME: for turbofans, the upper limit should be reduced from 1000
3727 // to 800 ft if a sudden change in radio altitude is detected, in
3728 // order to reduce nuisance alerts caused by overflying another
3729 // aircraft (see [PILOT] page 16).
3732 MK_VIII::Mode4Handler::get_upper_agl (const EnvelopesConfiguration *c)
3734 if (mk_ainput(computed_airspeed).get() >= c->airspeed2)
3736 else if (mk_ainput(computed_airspeed).get() >= c->airspeed1)
3737 return c->min_agl2(mk_ainput(computed_airspeed).get());
3742 const MK_VIII::Mode4Handler::EnvelopesConfiguration *
3743 MK_VIII::Mode4Handler::get_ab_envelope ()
3745 // [SPEC] 6.2.4.1: "The Mode 4B envelope can also be selected by
3746 // setting flaps to landing configuration or by selecting flap
3748 return mk_dinput(landing_gear) || mk->io_handler.flaps_down()
3754 MK_VIII::Mode4Handler::get_bias (double initial_bias, double min_agl)
3756 while (mk_data(radio_altitude).get() < min_agl - min_agl * initial_bias)
3757 initial_bias += 0.2;
3759 return initial_bias;
3763 MK_VIII::Mode4Handler::handle_alert (unsigned int alert,
3767 if (mk_test_alerts(alert))
3769 double new_bias = get_bias(*bias, min_agl);
3770 if (new_bias > *bias)
3773 mk_repeat_alert(alert);
3778 *bias = get_bias(0.2, min_agl);
3779 mk_set_alerts(alert);
3784 MK_VIII::Mode4Handler::update_ab ()
3786 if (! mk->io_handler.gpws_inhibit()
3787 && ! mk->state_handler.ground
3788 && ! mk->state_handler.takeoff
3789 && ! mk_data(radio_altitude).ncd
3790 && ! mk_ainput(computed_airspeed).ncd
3791 && mk_data(radio_altitude).get() > 30)
3793 const EnvelopesConfiguration *c = get_ab_envelope();
3794 if (mk_ainput(computed_airspeed).get() < c->airspeed1)
3796 if (mk_dinput(landing_gear))
3798 if (! mk->io_handler.flaps_down() && mk_data(radio_altitude).get() < c->min_agl1)
3800 handle_alert(mk_alert(MODE4_TOO_LOW_FLAPS), c->min_agl1, &ab_bias);
3806 if (mk_data(radio_altitude).get() < c->min_agl1)
3808 handle_alert(mk_alert(MODE4_TOO_LOW_GEAR), c->min_agl1, &ab_bias);
3815 mk_unset_alerts(mk_alert(MODE4_TOO_LOW_FLAPS) | mk_alert(MODE4_TOO_LOW_GEAR));
3819 MK_VIII::Mode4Handler::update_ab_expanded ()
3821 if (! mk->io_handler.gpws_inhibit()
3822 && ! mk->state_handler.ground
3823 && ! mk->state_handler.takeoff
3824 && ! mk_data(radio_altitude).ncd
3825 && ! mk_ainput(computed_airspeed).ncd
3826 && mk_data(radio_altitude).get() > 30)
3828 const EnvelopesConfiguration *c = get_ab_envelope();
3829 if (mk_ainput(computed_airspeed).get() >= c->airspeed1)
3831 double min_agl = get_upper_agl(c);
3832 if (mk_data(radio_altitude).get() < min_agl)
3834 handle_alert(mk_alert(MODE4AB_TOO_LOW_TERRAIN), min_agl, &ab_expanded_bias);
3840 mk_unset_alerts(mk_alert(MODE4AB_TOO_LOW_TERRAIN));
3844 MK_VIII::Mode4Handler::update_c ()
3846 if (! mk->io_handler.gpws_inhibit()
3847 && ! mk->state_handler.ground // [1]
3848 && mk->state_handler.takeoff
3849 && ! mk_data(radio_altitude).ncd
3850 && ! mk_data(terrain_clearance).ncd
3851 && mk_data(radio_altitude).get() > 30
3852 && (! mk_dinput(landing_gear) || ! mk->io_handler.flaps_down())
3853 && mk_data(radio_altitude).get() < get_upper_agl(conf.modes->ac)
3854 && mk_data(radio_altitude).get() < mk_data(terrain_clearance).get())
3855 handle_alert(mk_alert(MODE4C_TOO_LOW_TERRAIN), mk_data(terrain_clearance).get(), &c_bias);
3857 mk_unset_alerts(mk_alert(MODE4C_TOO_LOW_TERRAIN));
3861 MK_VIII::Mode4Handler::update ()
3863 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
3867 update_ab_expanded();
3871 ///////////////////////////////////////////////////////////////////////////////
3872 // Mode5Handler ///////////////////////////////////////////////////////////////
3873 ///////////////////////////////////////////////////////////////////////////////
3876 MK_VIII::Mode5Handler::is_hard ()
3878 if (mk_data(radio_altitude).get() > 30
3879 && mk_data(radio_altitude).get() < 300
3880 && mk_data(glideslope_deviation_dots).get() > 2)
3882 if (mk_data(radio_altitude).get() < 150)
3884 if (mk_data(radio_altitude).get() > 293 - 71.43 * mk_data(glideslope_deviation_dots).get())
3895 MK_VIII::Mode5Handler::is_soft (double bias)
3897 if (mk_data(radio_altitude).get() > 30)
3899 double bias_dots = 1.3 * bias;
3900 if (mk_data(glideslope_deviation_dots).get() > 1.3 + bias_dots)
3902 if (mk_data(radio_altitude).get() < 150)
3904 if (mk_data(radio_altitude).get() > 243 - 71.43 * (mk_data(glideslope_deviation_dots).get() - bias_dots))
3911 if (mk_data(barometric_altitude_rate).ncd)
3915 if (mk_data(barometric_altitude_rate).get() >= 0)
3917 else if (mk_data(barometric_altitude_rate).get() < -500)
3920 upper_limit = -mk_data(barometric_altitude_rate).get() + 500;
3923 if (mk_data(radio_altitude).get() < upper_limit)
3933 MK_VIII::Mode5Handler::get_soft_bias (double initial_bias)
3935 while (is_soft(initial_bias))
3936 initial_bias += 0.2;
3938 return initial_bias;
3942 MK_VIII::Mode5Handler::update_hard (bool is)
3946 if (! mk_test_alert(MODE5_HARD))
3948 if (hard_timer.start_or_elapsed() >= 0.8)
3949 mk_set_alerts(mk_alert(MODE5_HARD));
3955 mk_unset_alerts(mk_alert(MODE5_HARD));
3960 MK_VIII::Mode5Handler::update_soft (bool is)
3964 if (! mk_test_alert(MODE5_SOFT))
3966 double new_bias = get_soft_bias(soft_bias);
3967 if (new_bias > soft_bias)
3969 soft_bias = new_bias;
3970 mk_repeat_alert(mk_alert(MODE5_SOFT));
3975 if (soft_timer.start_or_elapsed() >= 0.8)
3977 soft_bias = get_soft_bias(0.2);
3978 mk_set_alerts(mk_alert(MODE5_SOFT));
3985 mk_unset_alerts(mk_alert(MODE5_SOFT));
3990 MK_VIII::Mode5Handler::update ()
3992 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
3995 if (! mk->io_handler.gpws_inhibit()
3996 && ! mk->state_handler.ground // [1]
3997 && ! mk_dinput(glideslope_inhibit) // not on backcourse
3998 && ! mk_data(radio_altitude).ncd
3999 && ! mk_data(glideslope_deviation_dots).ncd
4000 && (! mk->io_handler.conf.localizer_enabled
4001 || mk_data(localizer_deviation_dots).ncd
4002 || mk_data(radio_altitude).get() < 500
4003 || fabs(mk_data(localizer_deviation_dots).get()) < 2)
4004 && (! mk->state_handler.takeoff || mk->io_handler.flaps_down())
4005 && mk_dinput(landing_gear)
4006 && ! mk_doutput(glideslope_cancel))
4008 update_hard(is_hard());
4009 update_soft(is_soft(0));
4018 ///////////////////////////////////////////////////////////////////////////////
4019 // Mode6Handler ///////////////////////////////////////////////////////////////
4020 ///////////////////////////////////////////////////////////////////////////////
4022 // keep sorted in descending order
4023 const int MK_VIII::Mode6Handler::altitude_callout_definitions[] =
4024 { 1000, 500, 400, 300, 200, 100, 50, 40, 30, 20, 10 };
4027 MK_VIII::Mode6Handler::reset_minimums ()
4029 minimums_issued = false;
4033 MK_VIII::Mode6Handler::reset_altitude_callouts ()
4035 for (int i = 0; i < n_altitude_callouts; i++)
4036 altitude_callouts_issued[i] = false;
4040 MK_VIII::Mode6Handler::is_playing_altitude_callout ()
4042 for (int i = 0; i < n_altitude_callouts; i++)
4043 if (mk->voice_player.voice == mk_altitude_voice(i)
4044 || mk->voice_player.next_voice == mk_altitude_voice(i))
4051 MK_VIII::Mode6Handler::is_near_minimums (double callout)
4055 if (! mk_data(decision_height).ncd)
4057 double diff = callout - mk_data(decision_height).get();
4059 if (mk_data(radio_altitude).get() >= 200)
4061 if (fabs(diff) <= 30)
4066 if (diff >= -3 && diff <= 6)
4075 MK_VIII::Mode6Handler::is_outside_band (double elevation, double callout)
4078 return elevation < callout - (elevation > 150 ? 20 : 10);
4082 MK_VIII::Mode6Handler::inhibit_smart_500 ()
4086 if (! mk_data(glideslope_deviation_dots).ncd
4087 && ! mk_dinput(glideslope_inhibit) // backcourse
4088 && ! mk_doutput(glideslope_cancel))
4090 if (mk->io_handler.conf.localizer_enabled
4091 && ! mk_data(localizer_deviation_dots).ncd)
4093 if (fabs(mk_data(localizer_deviation_dots).get()) <= 2)
4098 if (fabs(mk_data(glideslope_deviation_dots).get()) <= 2)
4107 MK_VIII::Mode6Handler::boot ()
4109 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
4112 last_decision_height = mk_dinput(decision_height);
4113 last_radio_altitude.set(&mk_data(radio_altitude));
4116 for (int i = 0; i < n_altitude_callouts; i++)
4117 altitude_callouts_issued[i] = ! mk_data(radio_altitude).ncd
4118 && mk_data(radio_altitude).get() <= altitude_callout_definitions[i];
4120 // extrapolated from [SPEC] 6.4.2
4121 minimums_issued = mk_dinput(decision_height);
4123 if (conf.above_field_voice)
4126 get_altitude_above_field(&last_altitude_above_field);
4127 // extrapolated from [SPEC] 6.4.2
4128 above_field_issued = ! last_altitude_above_field.ncd
4129 && last_altitude_above_field.get() < 550;
4134 MK_VIII::Mode6Handler::power_off ()
4136 runway_timer.stop();
4140 MK_VIII::Mode6Handler::enter_takeoff ()
4142 reset_altitude_callouts(); // [SPEC] 6.4.2
4143 reset_minimums(); // omitted by [SPEC]; common sense
4147 MK_VIII::Mode6Handler::leave_takeoff ()
4149 reset_altitude_callouts(); // [SPEC] 6.0.2
4150 reset_minimums(); // [SPEC] 6.0.2
4154 MK_VIII::Mode6Handler::set_volume (double volume)
4156 mk_voice(minimums_minimums)->set_volume(volume);
4157 mk_voice(five_hundred_above)->set_volume(volume);
4158 for (int i = 0; i < n_altitude_callouts; i++)
4159 mk_altitude_voice(i)->set_volume(volume);
4163 MK_VIII::Mode6Handler::altitude_callouts_enabled ()
4165 if (conf.minimums_enabled || conf.smart_500_enabled || conf.above_field_voice)
4168 for (int i = 0; i < n_altitude_callouts; i++)
4169 if (conf.altitude_callouts_enabled[i])
4176 MK_VIII::Mode6Handler::update_minimums ()
4178 if (! mk->io_handler.gpws_inhibit()
4179 && (mk->voice_player.voice == mk_voice(minimums_minimums)
4180 || mk->voice_player.next_voice == mk_voice(minimums_minimums)))
4183 if (! mk->io_handler.gpws_inhibit()
4184 && ! mk->state_handler.ground // [1]
4185 && conf.minimums_enabled
4186 && ! minimums_issued
4187 && mk_dinput(landing_gear)
4188 && mk_dinput(decision_height)
4189 && ! last_decision_height)
4191 minimums_issued = true;
4193 // If an altitude callout is playing, stop it now so that the
4194 // minimums callout can be played (note that proper connection
4195 // and synchronization of the radio-altitude ARINC 529 input,
4196 // decision-height discrete and decision-height ARINC 529 input
4197 // will prevent an altitude callout from being played near the
4198 // decision height).
4200 if (is_playing_altitude_callout())
4201 mk->voice_player.stop(VoicePlayer::STOP_NOW);
4203 mk_set_alerts(mk_alert(MODE6_MINIMUMS));
4206 mk_unset_alerts(mk_alert(MODE6_MINIMUMS));
4209 last_decision_height = mk_dinput(decision_height);
4213 MK_VIII::Mode6Handler::update_altitude_callouts ()
4215 if (! mk->io_handler.gpws_inhibit() && is_playing_altitude_callout())
4218 if (! mk->io_handler.gpws_inhibit()
4219 && ! mk->state_handler.ground // [1]
4220 && ! mk_data(radio_altitude).ncd)
4221 for (int i = 0; i < n_altitude_callouts && mk_data(radio_altitude).get() <= altitude_callout_definitions[i]; i++)
4222 if ((conf.altitude_callouts_enabled[i]
4223 || (altitude_callout_definitions[i] == 500
4224 && conf.smart_500_enabled))
4225 && ! altitude_callouts_issued[i]
4226 && (last_radio_altitude.ncd
4227 || last_radio_altitude.get() > altitude_callout_definitions[i]))
4229 // lock out all callouts superior or equal to this one
4230 for (int j = 0; j <= i; j++)
4231 altitude_callouts_issued[j] = true;
4233 altitude_callouts_issued[i] = true;
4234 if (! is_near_minimums(altitude_callout_definitions[i])
4235 && ! is_outside_band(mk_data(radio_altitude).get(), altitude_callout_definitions[i])
4236 && (! conf.smart_500_enabled
4237 || altitude_callout_definitions[i] != 500
4238 || ! inhibit_smart_500()))
4240 mk->alert_handler.set_altitude_callout_alert(mk_altitude_voice(i));
4245 mk_unset_alerts(mk_alert(MODE6_ALTITUDE_CALLOUT));
4248 last_radio_altitude.set(&mk_data(radio_altitude));
4252 MK_VIII::Mode6Handler::test_runway (const FGRunway *_runway)
4254 if (_runway->_length < mk->conf.runway_database)
4257 // get position of threshold
4258 double latitude, longitude, az;
4259 geo_direct_wgs_84(0,
4262 get_reciprocal_heading(_runway->_heading),
4263 _runway->_length / 2 * SG_FEET_TO_METER,
4268 // get distance to threshold
4269 double distance, az1, az2;
4270 geo_inverse_wgs_84(0,
4271 mk_data(gps_latitude).get(),
4272 mk_data(gps_longitude).get(),
4275 &az1, &az2, &distance);
4277 return distance * SG_METER_TO_NM <= 5;
4281 MK_VIII::Mode6Handler::test_airport (const FGAirport *airport)
4284 if (globals->get_runways()->search(airport->getId(), &r))
4287 if (test_runway(&r))
4290 // reciprocal runway
4291 r._heading = get_reciprocal_heading(r._heading);
4292 if (test_runway(&r))
4295 while (globals->get_runways()->next(&r) && r._id == airport->getId());
4301 MK_VIII::Mode6Handler::update_runway ()
4303 if (! mk_data(gps_latitude).ncd && ! mk_data(gps_longitude).ncd)
4305 // Search for the closest runway threshold in range 5
4306 // nm. Passing 0.5 degrees (approximatively 30 nm) to
4307 // get_closest_airport() provides enough margin for large
4308 // airports, which may have a runway located far away from the
4309 // airport's reference point.
4311 const FGAirport *airport = get_closest_airport(mk_data(gps_latitude).get(),
4312 mk_data(gps_longitude).get(),
4315 &MK_VIII::Mode6Handler::test_airport);
4318 runway.elevation = airport->getElevation();
4320 has_runway.set(airport != NULL);
4327 MK_VIII::Mode6Handler::get_altitude_above_field (Parameter<double> *parameter)
4329 if (! has_runway.ncd && has_runway.get() && ! mk_data(geometric_altitude).ncd)
4330 parameter->set(mk_data(geometric_altitude).get() - runway.elevation);
4336 MK_VIII::Mode6Handler::update_above_field_callout ()
4338 if (! conf.above_field_voice)
4341 // update_runway() has to iterate over the whole runway database
4342 // (which contains thousands of runways), so it would be unwise to
4343 // run it every frame. Run it every 3 seconds. Note that the first
4344 // iteration is run in boot().
4345 if (runway_timer.start_or_elapsed() >= 3)
4348 runway_timer.start();
4351 Parameter<double> altitude_above_field;
4352 get_altitude_above_field(&altitude_above_field);
4354 if (! mk->io_handler.gpws_inhibit()
4355 && (mk->voice_player.voice == conf.above_field_voice
4356 || mk->voice_player.next_voice == conf.above_field_voice))
4359 // handle reset term
4360 if (above_field_issued)
4362 if ((! has_runway.ncd && ! has_runway.get())
4363 || (! altitude_above_field.ncd && altitude_above_field.get() > 700))
4364 above_field_issued = false;
4367 if (! mk->io_handler.gpws_inhibit()
4368 && ! mk->state_handler.ground // [1]
4369 && ! above_field_issued
4370 && ! altitude_above_field.ncd
4371 && altitude_above_field.get() < 550
4372 && (last_altitude_above_field.ncd
4373 || last_altitude_above_field.get() >= 550))
4375 above_field_issued = true;
4377 if (! is_outside_band(altitude_above_field.get(), 500))
4379 mk->alert_handler.set_altitude_callout_alert(conf.above_field_voice);
4384 mk_unset_alerts(mk_alert(MODE6_ALTITUDE_CALLOUT));
4387 last_altitude_above_field.set(&altitude_above_field);
4391 MK_VIII::Mode6Handler::is_bank_angle (double abs_roll_angle, double bias)
4393 return conf.is_bank_angle(&mk_data(radio_altitude),
4394 abs_roll_angle - abs_roll_angle * bias,
4395 mk_dinput(autopilot_engaged));
4399 MK_VIII::Mode6Handler::is_high_bank_angle ()
4401 return mk_data(radio_altitude).ncd || mk_data(radio_altitude).get() >= 210;
4405 MK_VIII::Mode6Handler::get_bank_angle_alerts ()
4407 if (! mk->io_handler.gpws_inhibit()
4408 && ! mk->state_handler.ground // [1]
4409 && ! mk_data(roll_angle).ncd)
4411 double abs_roll_angle = fabs(mk_data(roll_angle).get());
4413 if (is_bank_angle(abs_roll_angle, 0.4))
4414 return is_high_bank_angle()
4415 ? (mk_alert(MODE6_HIGH_BANK_ANGLE_1) | mk_alert(MODE6_HIGH_BANK_ANGLE_2) | mk_alert(MODE6_HIGH_BANK_ANGLE_3))
4416 : (mk_alert(MODE6_LOW_BANK_ANGLE_1) | mk_alert(MODE6_LOW_BANK_ANGLE_2) | mk_alert(MODE6_LOW_BANK_ANGLE_3));
4417 else if (is_bank_angle(abs_roll_angle, 0.2))
4418 return is_high_bank_angle()
4419 ? (mk_alert(MODE6_HIGH_BANK_ANGLE_1) | mk_alert(MODE6_HIGH_BANK_ANGLE_2))
4420 : (mk_alert(MODE6_LOW_BANK_ANGLE_1) | mk_alert(MODE6_LOW_BANK_ANGLE_2));
4421 else if (is_bank_angle(abs_roll_angle, 0))
4422 return is_high_bank_angle()
4423 ? mk_alert(MODE6_HIGH_BANK_ANGLE_1)
4424 : mk_alert(MODE6_LOW_BANK_ANGLE_1);
4431 MK_VIII::Mode6Handler::update_bank_angle ()
4433 if (! conf.bank_angle_enabled)
4436 unsigned int alerts = get_bank_angle_alerts();
4438 // [SPEC] 6.4.4 specifies that the non-continuous alerts
4439 // (MODE6_*_BANK_ANGLE_1 and MODE6_*_BANK_ANGLE_2) are locked out
4440 // until the initial envelope is exited.
4442 if (! test_bits(alerts, mk_alert(MODE6_LOW_BANK_ANGLE_3)))
4443 mk_unset_alerts(mk_alert(MODE6_LOW_BANK_ANGLE_3));
4444 if (! test_bits(alerts, mk_alert(MODE6_HIGH_BANK_ANGLE_3)))
4445 mk_unset_alerts(mk_alert(MODE6_HIGH_BANK_ANGLE_3));
4448 mk_set_alerts(alerts);
4450 mk_unset_alerts(mk_alert(MODE6_LOW_BANK_ANGLE_1)
4451 | mk_alert(MODE6_HIGH_BANK_ANGLE_1)
4452 | mk_alert(MODE6_LOW_BANK_ANGLE_2)
4453 | mk_alert(MODE6_HIGH_BANK_ANGLE_2));
4457 MK_VIII::Mode6Handler::update ()
4459 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
4462 if (! mk->state_handler.takeoff
4463 && ! mk_data(radio_altitude).ncd
4464 && mk_data(radio_altitude).get() > 1000)
4466 reset_altitude_callouts(); // [SPEC] 6.4.2
4467 reset_minimums(); // common sense
4471 update_altitude_callouts();
4472 update_above_field_callout();
4473 update_bank_angle();
4476 ///////////////////////////////////////////////////////////////////////////////
4477 // TCFHandler /////////////////////////////////////////////////////////////////
4478 ///////////////////////////////////////////////////////////////////////////////
4480 // Gets the difference between the azimuth from @from_lat,@from_lon to
4481 // @to_lat,@to_lon, and @to_heading, in degrees.
4483 MK_VIII::TCFHandler::get_azimuth_difference (double from_lat,
4489 double az1, az2, distance;
4490 geo_inverse_wgs_84(0, from_lat, from_lon, to_lat, to_lon, &az1, &az2, &distance);
4491 return get_heading_difference(az1, to_heading);
4494 // Gets the difference between the azimuth from the current GPS
4495 // position to the center of @_runway, and the heading of @_runway, in
4498 MK_VIII::TCFHandler::get_azimuth_difference (const FGRunway *_runway)
4500 return get_azimuth_difference(mk_data(gps_latitude).get(),
4501 mk_data(gps_longitude).get(),
4507 // Selects the most likely intended destination runway of @airport,
4508 // and returns it in @_runway. For each runway, the difference between
4509 // the azimuth from the current GPS position to the center of the
4510 // runway and its heading is computed. The runway having the smallest
4513 // This selection algorithm is not specified in [SPEC], but
4514 // http://www.egpws.com/general_information/description/runway_select.htm
4515 // talks about automatic runway selection.
4517 MK_VIII::TCFHandler::select_runway (const FGAirport *airport,
4521 bool status = globals->get_runways()->search(airport->getId(), &r);
4524 double min_diff = 360;
4529 diff = get_azimuth_difference(&r);
4530 if (diff < min_diff)
4536 // reciprocal runway
4537 r._heading = get_reciprocal_heading(r._heading);
4538 diff = get_azimuth_difference(&r);
4539 if (diff < min_diff)
4545 while (globals->get_runways()->next(&r) && r._id == airport->getId());
4549 MK_VIII::TCFHandler::test_airport (const FGAirport *airport)
4552 if (globals->get_runways()->search(airport->getId(), &r))
4555 if (r._length >= mk->conf.runway_database)
4558 while (globals->get_runways()->next(&r) && r._id == airport->getId());
4564 MK_VIII::TCFHandler::update_runway ()
4567 if (! mk_data(gps_latitude).ncd && ! mk_data(gps_longitude).ncd)
4569 // Search for the intended destination runway of the closest
4570 // airport in range 15 nm. Passing 0.5 degrees (approximatively
4571 // 30 nm) to get_closest_airport() provides enough margin for
4572 // large airports, which may have a runway located far away from
4573 // the airport's reference point.
4575 const FGAirport *airport = get_closest_airport(mk_data(gps_latitude).get(),
4576 mk_data(gps_longitude).get(),
4579 &MK_VIII::TCFHandler::test_airport);
4586 select_runway(airport, &_runway);
4588 runway.center.latitude = _runway._lat;
4589 runway.center.longitude = _runway._lon;
4591 runway.elevation = airport->getElevation();
4593 double half_length_m = _runway._length / 2 * SG_FEET_TO_METER;
4594 runway.half_length = half_length_m * SG_METER_TO_NM;
4596 // b3 ________________ b0
4598 // h1>>> | e1<<<<<<<<e0 | <<<h0
4599 // |________________|
4602 // get heading to runway threshold (h0) and end (h1)
4603 runway.edges[0].heading = _runway._heading;
4604 runway.edges[1].heading = get_reciprocal_heading(_runway._heading);
4608 // get position of runway threshold (e0)
4609 geo_direct_wgs_84(0,
4610 runway.center.latitude,
4611 runway.center.longitude,
4612 runway.edges[1].heading,
4614 &runway.edges[0].position.latitude,
4615 &runway.edges[0].position.longitude,
4618 // get position of runway end (e1)
4619 geo_direct_wgs_84(0,
4620 runway.center.latitude,
4621 runway.center.longitude,
4622 runway.edges[0].heading,
4624 &runway.edges[1].position.latitude,
4625 &runway.edges[1].position.longitude,
4628 double half_width_m = _runway._width / 2 * SG_FEET_TO_METER;
4630 // get position of threshold bias area edges (b0 and b1)
4631 get_bias_area_edges(&runway.edges[0].position,
4632 runway.edges[1].heading,
4634 &runway.bias_area[0],
4635 &runway.bias_area[1]);
4637 // get position of end bias area edges (b2 and b3)
4638 get_bias_area_edges(&runway.edges[1].position,
4639 runway.edges[0].heading,
4641 &runway.bias_area[2],
4642 &runway.bias_area[3]);
4648 MK_VIII::TCFHandler::get_bias_area_edges (Position *edge,
4650 double half_width_m,
4651 Position *bias_edge1,
4652 Position *bias_edge2)
4654 double half_bias_width_m = k * SG_NM_TO_METER + half_width_m;
4655 double tmp_latitude, tmp_longitude, az;
4657 geo_direct_wgs_84(0,
4665 geo_direct_wgs_84(0,
4668 heading_substract(reciprocal, 90),
4670 &bias_edge1->latitude,
4671 &bias_edge1->longitude,
4673 geo_direct_wgs_84(0,
4676 heading_add(reciprocal, 90),
4678 &bias_edge2->latitude,
4679 &bias_edge2->longitude,
4683 // Returns true if the current GPS position is inside the edge
4684 // triangle of @edge. The edge triangle, which is specified and
4685 // represented in [SPEC] 6.3.1, is defined as an isocele right
4686 // triangle of infinite height, whose right angle is located at the
4687 // position of @edge, and whose height is parallel to the heading of
4690 MK_VIII::TCFHandler::is_inside_edge_triangle (RunwayEdge *edge)
4692 return get_azimuth_difference(mk_data(gps_latitude).get(),
4693 mk_data(gps_longitude).get(),
4694 edge->position.latitude,
4695 edge->position.longitude,
4696 edge->heading) <= 45;
4699 // Returns true if the current GPS position is inside the bias area of
4700 // the currently selected runway.
4702 MK_VIII::TCFHandler::is_inside_bias_area ()
4705 double angles_sum = 0;
4707 for (int i = 0; i < 4; i++)
4709 double az2, distance;
4710 geo_inverse_wgs_84(0,
4711 mk_data(gps_latitude).get(),
4712 mk_data(gps_longitude).get(),
4713 runway.bias_area[i].latitude,
4714 runway.bias_area[i].longitude,
4715 &az1[i], &az2, &distance);
4718 for (int i = 0; i < 4; i++)
4720 double angle = az1[i == 3 ? 0 : i + 1] - az1[i];
4724 angles_sum += angle;
4727 return angles_sum > 180;
4731 MK_VIII::TCFHandler::is_tcf ()
4733 if (mk_data(radio_altitude).get() > 10)
4737 double distance, az1, az2;
4739 geo_inverse_wgs_84(0,
4740 mk_data(gps_latitude).get(),
4741 mk_data(gps_longitude).get(),
4742 runway.center.latitude,
4743 runway.center.longitude,
4744 &az1, &az2, &distance);
4746 distance *= SG_METER_TO_NM;
4748 // distance to the inner envelope edge
4749 double edge_distance = distance - runway.half_length - k;
4751 if (edge_distance >= 15)
4753 if (mk_data(radio_altitude).get() < 700)
4756 else if (edge_distance >= 12)
4758 if (mk_data(radio_altitude).get() < 100 * edge_distance - 800)
4761 else if (edge_distance >= 4)
4763 if (mk_data(radio_altitude).get() < 400)
4766 else if (edge_distance >= 2.45)
4768 if (mk_data(radio_altitude).get() < 66.666667 * edge_distance + 133.33333)
4773 if (is_inside_edge_triangle(&runway.edges[0]) || is_inside_edge_triangle(&runway.edges[1]))
4775 if (edge_distance >= 1)
4777 if (mk_data(radio_altitude).get() < 66.666667 * edge_distance + 133.33333)
4780 else if (edge_distance >= 0.05)
4782 if (mk_data(radio_altitude).get() < 200 * edge_distance)
4788 if (! is_inside_bias_area())
4790 if (mk_data(radio_altitude).get() < 245)
4798 if (mk_data(radio_altitude).get() < 700)
4807 MK_VIII::TCFHandler::is_rfcf ()
4811 double distance, az1, az2;
4812 geo_inverse_wgs_84(0,
4813 mk_data(gps_latitude).get(),
4814 mk_data(gps_longitude).get(),
4815 runway.center.latitude,
4816 runway.center.longitude,
4817 &az1, &az2, &distance);
4819 double krf = k + mk_data(gps_vertical_figure_of_merit).get() / 200;
4820 distance = distance * SG_METER_TO_NM - runway.half_length - krf;
4824 double altitude_above_field = mk_data(geometric_altitude).get() - runway.elevation;
4826 if (distance >= 1.5)
4828 if (altitude_above_field < 300)
4831 else if (distance >= 0)
4833 if (altitude_above_field < 200 * distance)
4843 MK_VIII::TCFHandler::update ()
4845 if (mk->configuration_module.state != ConfigurationModule::STATE_OK || ! conf.enabled)
4848 // update_runway() has to iterate over the whole runway database
4849 // (which contains thousands of runways), so it would be unwise to
4850 // run it every frame. Run it every 3 seconds.
4851 if (! runway_timer.running || runway_timer.elapsed() >= 3)
4854 runway_timer.start();
4857 if (! mk_dinput(ta_tcf_inhibit)
4858 && ! mk->state_handler.ground // [1]
4859 && ! mk_data(gps_latitude).ncd
4860 && ! mk_data(gps_longitude).ncd
4861 && ! mk_data(radio_altitude).ncd
4862 && ! mk_data(geometric_altitude).ncd
4863 && ! mk_data(gps_vertical_figure_of_merit).ncd)
4868 _reference = mk_data(radio_altitude).get_pointer();
4870 _reference = mk_data(geometric_altitude).get_pointer();
4876 if (mk_test_alert(TCF_TOO_LOW_TERRAIN))
4878 double new_bias = bias;
4879 while (*reference < initial_value - initial_value * new_bias)
4882 if (new_bias > bias)
4885 mk_repeat_alert(mk_alert(TCF_TOO_LOW_TERRAIN));
4891 reference = _reference;
4892 initial_value = *reference;
4893 mk_set_alerts(mk_alert(TCF_TOO_LOW_TERRAIN));
4900 mk_unset_alerts(mk_alert(TCF_TOO_LOW_TERRAIN));
4903 ///////////////////////////////////////////////////////////////////////////////
4904 // MK_VIII ////////////////////////////////////////////////////////////////////
4905 ///////////////////////////////////////////////////////////////////////////////
4907 MK_VIII::MK_VIII (SGPropertyNode *node)
4911 properties_handler(this),
4912 power_handler(this),
4913 system_handler(this),
4914 configuration_module(this),
4915 fault_handler(this),
4918 self_test_handler(this),
4919 alert_handler(this),
4920 state_handler(this),
4921 mode1_handler(this),
4922 mode2_handler(this),
4923 mode3_handler(this),
4924 mode4_handler(this),
4925 mode5_handler(this),
4926 mode6_handler(this),
4929 for (int i = 0; i < node->nChildren(); ++i)
4931 SGPropertyNode *child = node->getChild(i);
4932 string cname = child->getName();
4933 string cval = child->getStringValue();
4935 if (cname == "name")
4937 else if (cname == "number")
4938 num = child->getIntValue();
4941 SG_LOG(SG_INSTR, SG_WARN, "Error in mk-viii config logic");
4943 SG_LOG(SG_INSTR, SG_WARN, "Section = " << name);
4951 properties_handler.init();
4952 voice_player.init();
4958 SGPropertyNode *node = fgGetNode(("/instrumentation/" + name).c_str(), num, true);
4960 configuration_module.bind(node);
4961 power_handler.bind(node);
4962 io_handler.bind(node);
4963 voice_player.bind(node);
4969 properties_handler.unbind();
4973 MK_VIII::update (double dt)
4975 power_handler.update();
4976 system_handler.update();
4978 if (system_handler.state != SystemHandler::STATE_ON)
4981 io_handler.update_inputs();
4982 io_handler.update_input_faults();
4984 voice_player.update();
4985 state_handler.update();
4987 if (self_test_handler.update())
4990 io_handler.update_internal_latches();
4991 io_handler.update_lamps();
4993 mode1_handler.update();
4994 mode2_handler.update();
4995 mode3_handler.update();
4996 mode4_handler.update();
4997 mode5_handler.update();
4998 mode6_handler.update();
4999 tcf_handler.update();
5001 alert_handler.update();
5002 io_handler.update_outputs();