// mk_viii.cxx -- Honeywell MK VIII EGPWS emulation // // Written by Jean-Yves Lefort, started September 2005. // // Copyright (C) 2005, 2006 Jean-Yves Lefort - jylefort@FreeBSD.org // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License as // published by the Free Software Foundation; either version 2 of the // License, or (at your option) any later version. // // This program is distributed in the hope that it will be useful, but // WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software // Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. // /////////////////////////////////////////////////////////////////////////////// // // References: // // [PILOT] Honeywell International Inc., "MK VI and MK VIII, // Enhanced Ground Proximity Warning System (EGPWS), // Pilot's Guide", May 2004 // // http://www.egpws.com/engineering_support/documents/pilot_guides/060-4314-000.pdf // // [SPEC] Honeywell International Inc., "Product Specification // for the MK VI and MK VIII Enhanced Ground Proximity // Warning System (EGPWS)", June 2004 // // http://www.egpws.com/engineering_support/documents/specs/965-1180-601.pdf // // [INSTALL] Honeywell Inc., "MK VI, MK VIII, Enhanced Ground // Proximity Warning System (Class A TAWS), Installation // Design Guide", July 2003 // // http://www.egpws.com/engineering_support/documents/install/060-4314-150.pdf // // Notes: // // [1] [SPEC] does not specify the "must be airborne" // condition; we use it to make sure that the alert // will not trigger when on the ground, since it would // make no sense. #ifdef _MSC_VER # pragma warning( disable: 4355 ) #endif #ifdef HAVE_CONFIG_H # include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include using std::string; #include "Airports/runways.hxx" #include "Airports/simple.hxx" #ifndef _MSC_VER # include "Include/version.h" #endif #include "Main/fg_props.hxx" #include "Main/globals.hxx" #include "instrument_mgr.hxx" #include "mk_viii.hxx" /////////////////////////////////////////////////////////////////////////////// // constants ////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////// #define GLIDESLOPE_DOTS_TO_DDM 0.0875 // [INSTALL] 6.2.30 #define GLIDESLOPE_DDM_TO_DOTS (1 / GLIDESLOPE_DOTS_TO_DDM) #define LOCALIZER_DOTS_TO_DDM 0.0775 // [INSTALL] 6.2.33 #define LOCALIZER_DDM_TO_DOTS (1 / LOCALIZER_DOTS_TO_DDM) /////////////////////////////////////////////////////////////////////////////// // helpers //////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////// #define assert_not_reached() assert(false) #define n_elements(_array) (sizeof(_array) / sizeof((_array)[0])) #define test_bits(_bits, _test) (((_bits) & (_test)) != 0) #define mk_node(_name) (mk->properties_handler.external_properties._name) #define mk_dinput_feed(_name) (mk->io_handler.input_feeders.discretes._name) #define mk_dinput(_name) (mk->io_handler.inputs.discretes._name) #define mk_ainput_feed(_name) (mk->io_handler.input_feeders.arinc429._name) #define mk_ainput(_name) (mk->io_handler.inputs.arinc429._name) #define mk_doutput(_name) (mk->io_handler.outputs.discretes._name) #define mk_aoutput(_name) (mk->io_handler.outputs.arinc429._name) #define mk_data(_name) (mk->io_handler.data._name) #define mk_voice(_name) (mk->voice_player.voices._name) #define mk_altitude_voice(_i) (mk->voice_player.voices.altitude_callouts[(_i)]) #define mk_alert(_name) (AlertHandler::ALERT_ ## _name) #define mk_alert_flag(_name) (AlertHandler::ALERT_FLAG_ ## _name) #define mk_set_alerts (mk->alert_handler.set_alerts) #define mk_unset_alerts (mk->alert_handler.unset_alerts) #define mk_repeat_alert (mk->alert_handler.repeat_alert) #define mk_test_alert(_name) (mk_test_alerts(mk_alert(_name))) #define mk_test_alerts(_test) (test_bits(mk->alert_handler.alerts, (_test))) const double MK_VIII::TCFHandler::k = 0.25; static double modify_amplitude (double amplitude, double dB) { return amplitude * pow(10.0, dB / 20.0); } static double heading_add (double h1, double h2) { double result = h1 + h2; if (result >= 360) result -= 360; return result; } static double heading_substract (double h1, double h2) { double result = h1 - h2; if (result < 0) result += 360; return result; } static double get_heading_difference (double h1, double h2) { double diff = h1 - h2; if (diff < -180) diff += 360; else if (diff > 180) diff -= 360; return fabs(diff); } static double get_reciprocal_heading (double h) { return heading_add(h, 180); } /////////////////////////////////////////////////////////////////////////////// // PropertiesHandler ////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////// void MK_VIII::PropertiesHandler::init () { mk_node(ai_caged) = fgGetNode("/instrumentation/attitude-indicator/caged-flag", true); mk_node(ai_roll) = fgGetNode("/instrumentation/attitude-indicator/indicated-roll-deg", true); mk_node(ai_serviceable) = fgGetNode("/instrumentation/attitude-indicator/serviceable", true); mk_node(altimeter_altitude) = fgGetNode("/instrumentation/altimeter/indicated-altitude-ft", true); mk_node(altimeter_serviceable) = fgGetNode("/instrumentation/altimeter/serviceable", true); mk_node(altitude) = fgGetNode("/position/altitude-ft", true); mk_node(altitude_agl) = fgGetNode("/position/altitude-agl-ft", true); mk_node(asi_serviceable) = fgGetNode("/instrumentation/airspeed-indicator/serviceable", true); mk_node(asi_speed) = fgGetNode("/instrumentation/airspeed-indicator/indicated-speed-kt", true); mk_node(autopilot_heading_lock) = fgGetNode("/autopilot/locks/heading", true); mk_node(flaps) = fgGetNode("/controls/flight/flaps", true); mk_node(gear_down) = fgGetNode("/controls/gear/gear-down", true); mk_node(latitude) = fgGetNode("/position/latitude-deg", true); mk_node(longitude) = fgGetNode("/position/longitude-deg", true); mk_node(nav0_cdi_serviceable) = fgGetNode("/instrumentation/nav/cdi/serviceable", true); mk_node(nav0_gs_distance) = fgGetNode("/instrumentation/nav/gs-distance", true); mk_node(nav0_gs_needle_deflection) = fgGetNode("/instrumentation/nav/gs-needle-deflection", true); mk_node(nav0_gs_serviceable) = fgGetNode("/instrumentation/nav/gs/serviceable", true); mk_node(nav0_has_gs) = fgGetNode("/instrumentation/nav/has-gs", true); mk_node(nav0_heading_needle_deflection) = fgGetNode("/instrumentation/nav/heading-needle-deflection", true); mk_node(nav0_in_range) = fgGetNode("/instrumentation/nav/in-range", true); mk_node(nav0_nav_loc) = fgGetNode("/instrumentation/nav/nav-loc", true); mk_node(nav0_serviceable) = fgGetNode("/instrumentation/nav/serviceable", true); mk_node(power) = fgGetNode(("/systems/electrical/outputs/" + mk->name).c_str(), mk->num, true); mk_node(replay_state) = fgGetNode("/sim/freeze/replay-state", true); mk_node(vs) = fgGetNode("/velocities/vertical-speed-fps", true); } void MK_VIII::PropertiesHandler::unbind () { vector::iterator iter; for (iter = tied_properties.begin(); iter != tied_properties.end(); iter++) (*iter)->untie(); tied_properties.clear(); } /////////////////////////////////////////////////////////////////////////////// // PowerHandler /////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////// void MK_VIII::PowerHandler::bind (SGPropertyNode *node) { mk->properties_handler.tie(node, "serviceable", SGRawValuePointer(&serviceable)); } bool MK_VIII::PowerHandler::handle_abnormal_voltage (bool abnormal, Timer *timer, double max_duration) { if (abnormal) { if (timer->start_or_elapsed() >= max_duration) return true; // power loss } else timer->stop(); return false; } void MK_VIII::PowerHandler::update () { double voltage = mk_node(power)->getDoubleValue(); bool now_powered = true; // [SPEC] 3.2.1 if (! serviceable) now_powered = false; if (voltage < 15) now_powered = false; if (handle_abnormal_voltage(voltage < 20.5, &low_surge_timer, 0.03)) now_powered = false; if (handle_abnormal_voltage(voltage < 22.0 || voltage > 30.3, &abnormal_timer, 300)) now_powered = false; if (handle_abnormal_voltage(voltage > 32.2, &high_surge_timer, 1)) now_powered = false; if (handle_abnormal_voltage(voltage > 37.8, &very_high_surge_timer, 0.1)) now_powered = false; if (voltage > 46.3) now_powered = false; if (powered) { // [SPEC] 3.5.2 if (now_powered) power_loss_timer.stop(); else { if (power_loss_timer.start_or_elapsed() >= 0.2) power_off(); } } else { if (now_powered) power_on(); } } void MK_VIII::PowerHandler::power_on () { powered = true; mk->system_handler.power_on(); } void MK_VIII::PowerHandler::power_off () { powered = false; mk->system_handler.power_off(); mk->voice_player.stop(VoicePlayer::STOP_NOW); mk->self_test_handler.power_off(); // run before IOHandler::power_off() mk->io_handler.power_off(); mk->mode2_handler.power_off(); mk->mode6_handler.power_off(); } /////////////////////////////////////////////////////////////////////////////// // SystemHandler ////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////// void MK_VIII::SystemHandler::power_on () { state = STATE_BOOTING; // [SPEC] 3.5.2 mentions a 20 seconds maximum boot delay. We use a // random delay between 3 and 5 seconds. boot_delay = 3 + sg_random() * 2; boot_timer.start(); } void MK_VIII::SystemHandler::power_off () { state = STATE_OFF; boot_timer.stop(); } void MK_VIII::SystemHandler::update () { if (state == STATE_BOOTING) { if (boot_timer.elapsed() >= boot_delay) { last_replay_state = mk_node(replay_state)->getIntValue(); mk->configuration_module.boot(); mk->io_handler.boot(); mk->fault_handler.boot(); mk->alert_handler.boot(); // inputs are needed by the following boot() routines mk->io_handler.update_inputs(); mk->mode2_handler.boot(); mk->mode6_handler.boot(); state = STATE_ON; mk->io_handler.post_boot(); } } else if (state != STATE_OFF && mk->configuration_module.state == ConfigurationModule::STATE_OK) { // If the replay state changes, switch to reposition mode for 3 // seconds ([SPEC] 6.0.5) to avoid spurious alerts. int replay_state = mk_node(replay_state)->getIntValue(); if (replay_state != last_replay_state) { mk->alert_handler.reposition(); last_replay_state = replay_state; state = STATE_REPOSITION; reposition_timer.start(); } if (state == STATE_REPOSITION && reposition_timer.elapsed() >= 3) { // inputs are needed by StateHandler::post_reposition() mk->io_handler.update_inputs(); mk->state_handler.post_reposition(); state = STATE_ON; } } } /////////////////////////////////////////////////////////////////////////////// // ConfigurationModule //////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////// MK_VIII::ConfigurationModule::ConfigurationModule (MK_VIII *device) : mk(device) { // arbitrary defaults categories[CATEGORY_AIRCRAFT_MODE_TYPE_SELECT] = 0; categories[CATEGORY_AIR_DATA_INPUT_SELECT] = 1; categories[CATEGORY_POSITION_INPUT_SELECT] = 2; categories[CATEGORY_ALTITUDE_CALLOUTS] = 13; categories[CATEGORY_AUDIO_MENU_SELECT] = 0; categories[CATEGORY_TERRAIN_DISPLAY_SELECT] = 1; categories[CATEGORY_OPTIONS_SELECT_GROUP_1] = 124; categories[CATEGORY_RADIO_ALTITUDE_INPUT_SELECT] = 2; categories[CATEGORY_NAVIGATION_INPUT_SELECT] = 3; categories[CATEGORY_ATTITUDE_INPUT_SELECT] = 6; categories[CATEGORY_HEADING_INPUT_SELECT] = 2; categories[CATEGORY_WINDSHEAR_INPUT_SELECT] = 0; categories[CATEGORY_INPUT_OUTPUT_DISCRETE_TYPE_SELECT] = 7; categories[CATEGORY_AUDIO_OUTPUT_LEVEL] = 0; categories[CATEGORY_UNDEFINED_INPUT_SELECT_1] = 0; categories[CATEGORY_UNDEFINED_INPUT_SELECT_2] = 0; categories[CATEGORY_UNDEFINED_INPUT_SELECT_3] = 0; } static double m1_t1_min_agl1 (double vs) { return -1620 - 1.1133 * vs; } static double m1_t1_min_agl2 (double vs) { return -400 - 0.4 * vs; } static double m1_t4_min_agl1 (double vs) { return -1625.47 - 1.1167 * vs; } static double m1_t4_min_agl2 (double vs) { return -0.1958 * vs; } static int m3_t1_max_agl (bool flap_override) { return 1500; } static double m3_t1_max_alt_loss (bool flap_override, double agl) { return 5.4 + 0.092 * agl; } static int m3_t2_max_agl (bool flap_override) { return flap_override ? 815 : 925; } static double m3_t2_max_alt_loss (bool flap_override, double agl) { int bias = agl > 700 ? 5 : 0; if (flap_override) return (9.0 + 0.184 * agl) + bias; else return (5.4 + 0.092 * agl) + bias; } static double m4_t1_min_agl2 (double airspeed) { return -1083 + 8.333 * airspeed; } static double m4_t568_a_min_agl2 (double airspeed) { return -1523 + 11.36 * airspeed; } static double m4_t79_a_min_agl2 (double airspeed) { return -1182 + 11.36 * airspeed; } static double m4_t568_b_min_agl2 (double airspeed) { return -1570 + 11.6 * airspeed; } static double m4_t79_b_min_agl2 (double airspeed) { return -1222 + 11.6 * airspeed; } bool MK_VIII::ConfigurationModule::m6_t2_is_bank_angle (Parameter *agl, double abs_roll_deg, bool ap_engaged) { if (ap_engaged) { if (agl->ncd || agl->get() > 122) return abs_roll_deg > 33; } else { if (agl->ncd || agl->get() > 2450) return abs_roll_deg > 55; else if (agl->get() > 150) return agl->get() < 153.33333 * abs_roll_deg - 5983.3333; } if (agl->get() > 30) return agl->get() < 4 * abs_roll_deg - 10; else if (agl->get() > 5) return abs_roll_deg > 10; return false; } bool MK_VIII::ConfigurationModule::m6_t4_is_bank_angle (Parameter *agl, double abs_roll_deg, bool ap_engaged) { if (ap_engaged) { if (agl->ncd || agl->get() > 156) return abs_roll_deg > 33; } else { if (agl->ncd || agl->get() > 210) return abs_roll_deg > 50; } if (agl->get() > 10) return agl->get() < 5.7142857 * abs_roll_deg - 75.714286; return false; } bool MK_VIII::ConfigurationModule::read_aircraft_mode_type_select (int value) { static const Mode1Handler::EnvelopesConfiguration m1_t1 = { false, 10, m1_t1_min_agl1, 284, m1_t1_min_agl2, 2450 }; static const Mode1Handler::EnvelopesConfiguration m1_t4 = { true, 50, m1_t4_min_agl1, 346, m1_t4_min_agl2, 1958 }; static const Mode2Handler::Configuration m2_t1 = { 190, 280 }; static const Mode2Handler::Configuration m2_t4 = { 220, 310 }; static const Mode2Handler::Configuration m2_t5 = { 220, 310 }; static const Mode3Handler::Configuration m3_t1 = { 30, m3_t1_max_agl, m3_t1_max_alt_loss }; static const Mode3Handler::Configuration m3_t2 = { 50, m3_t2_max_agl, m3_t2_max_alt_loss }; static const Mode4Handler::EnvelopesConfiguration m4_t1_ac = { 190, 250, 500, m4_t1_min_agl2, 1000 }; static const Mode4Handler::EnvelopesConfiguration m4_t5_ac = { 178, 200, 500, m4_t568_a_min_agl2, 1000 }; static const Mode4Handler::EnvelopesConfiguration m4_t68_ac = { 178, 200, 500, m4_t568_a_min_agl2, 750 }; static const Mode4Handler::EnvelopesConfiguration m4_t79_ac = { 148, 170, 500, m4_t79_a_min_agl2, 750 }; static const Mode4Handler::EnvelopesConfiguration m4_t1_b = { 159, 250, 245, m4_t1_min_agl2, 1000 }; static const Mode4Handler::EnvelopesConfiguration m4_t5_b = { 148, 200, 200, m4_t568_b_min_agl2, 1000 }; static const Mode4Handler::EnvelopesConfiguration m4_t6_b = { 150, 200, 170, m4_t568_b_min_agl2, 750 }; static const Mode4Handler::EnvelopesConfiguration m4_t7_b = { 120, 170, 170, m4_t79_b_min_agl2, 750 }; static const Mode4Handler::EnvelopesConfiguration m4_t8_b = { 148, 200, 150, m4_t568_b_min_agl2, 750 }; static const Mode4Handler::EnvelopesConfiguration m4_t9_b = { 118, 170, 150, m4_t79_b_min_agl2, 750 }; static const Mode4Handler::ModesConfiguration m4_t1 = { &m4_t1_ac, &m4_t1_b }; static const Mode4Handler::ModesConfiguration m4_t5 = { &m4_t5_ac, &m4_t5_b }; static const Mode4Handler::ModesConfiguration m4_t6 = { &m4_t68_ac, &m4_t6_b }; static const Mode4Handler::ModesConfiguration m4_t7 = { &m4_t79_ac, &m4_t7_b }; static const Mode4Handler::ModesConfiguration m4_t8 = { &m4_t68_ac, &m4_t8_b }; static const Mode4Handler::ModesConfiguration m4_t9 = { &m4_t79_ac, &m4_t9_b }; static Mode6Handler::BankAnglePredicate m6_t2 = m6_t2_is_bank_angle; static Mode6Handler::BankAnglePredicate m6_t4 = m6_t4_is_bank_angle; static const IOHandler::FaultsConfiguration f_slow = { 180, 200 }; static const IOHandler::FaultsConfiguration f_fast = { 250, 290 }; static const struct { int type; const Mode1Handler::EnvelopesConfiguration *m1; const Mode2Handler::Configuration *m2; const Mode3Handler::Configuration *m3; const Mode4Handler::ModesConfiguration *m4; Mode6Handler::BankAnglePredicate m6; const IOHandler::FaultsConfiguration *f; int runway_database; } aircraft_types[] = { { 0, &m1_t4, &m2_t4, &m3_t2, &m4_t6, m6_t4, &f_fast, 2000 }, { 1, &m1_t4, &m2_t4, &m3_t2, &m4_t8, m6_t4, &f_fast, 2000 }, { 2, &m1_t4, &m2_t4, &m3_t2, &m4_t5, m6_t4, &f_fast, 2000 }, { 3, &m1_t4, &m2_t5, &m3_t2, &m4_t7, m6_t4, &f_slow, 2000 }, { 4, &m1_t4, &m2_t5, &m3_t2, &m4_t9, m6_t4, &f_slow, 2000 }, { 254, &m1_t1, &m2_t1, &m3_t1, &m4_t1, m6_t2, &f_fast, 3500 }, { 255, &m1_t1, &m2_t1, &m3_t1, &m4_t1, m6_t2, &f_fast, 2000 } }; for (int i = 0; i < n_elements(aircraft_types); i++) if (aircraft_types[i].type == value) { mk->mode1_handler.conf.envelopes = aircraft_types[i].m1; mk->mode2_handler.conf = aircraft_types[i].m2; mk->mode3_handler.conf = aircraft_types[i].m3; mk->mode4_handler.conf.modes = aircraft_types[i].m4; mk->mode6_handler.conf.is_bank_angle = aircraft_types[i].m6; mk->io_handler.conf.faults = aircraft_types[i].f; mk->conf.runway_database = aircraft_types[i].runway_database; return true; } state = STATE_INVALID_AIRCRAFT_TYPE; return false; } bool MK_VIII::ConfigurationModule::read_air_data_input_select (int value) { // unimplemented return (value >= 0 && value <= 6) || (value >= 10 && value <= 13) || value == 255; } bool MK_VIII::ConfigurationModule::read_position_input_select (int value) { if (value == 2) mk->io_handler.conf.use_internal_gps = true; else if ((value >= 0 && value <= 5) || (value >= 10 && value <= 13) || (value == 253) || (value == 255)) mk->io_handler.conf.use_internal_gps = false; else return false; return true; } bool MK_VIII::ConfigurationModule::read_altitude_callouts (int value) { enum { MINIMUMS = -1, SMART_500 = -2, FIELD_500 = -3, FIELD_500_ABOVE = -4 }; static const struct { int id; int callouts[13]; } values[] = { { 0, { MINIMUMS, SMART_500, 200, 100, 50, 40, 30, 20, 10, 0 } }, { 1, { MINIMUMS, SMART_500, 200, 0 } }, { 2, { MINIMUMS, SMART_500, 100, 50, 40, 30, 20, 10, 0 } }, { 3, { MINIMUMS, SMART_500, 0 } }, { 4, { MINIMUMS, 200, 100, 50, 40, 30, 20, 10, 0 } }, { 5, { MINIMUMS, 200, 0 } }, { 6, { MINIMUMS, 100, 50, 40, 30, 20, 10, 0 } }, { 7, { 0 } }, { 8, { MINIMUMS, 0 } }, { 9, { MINIMUMS, 500, 200, 100, 50, 40, 30, 20, 10, 0 } }, { 10, { MINIMUMS, 500, 200, 0 } }, { 11, { MINIMUMS, 500, 100, 50, 40, 30, 20, 10, 0 } }, { 12, { MINIMUMS, 500, 0 } }, { 13, { MINIMUMS, 1000, 500, 400, 300, 200, 100, 50, 40, 30, 20, 10, 0 } }, { 14, { MINIMUMS, 100, 0 } }, { 15, { MINIMUMS, 200, 100, 0 } }, { 100, { FIELD_500, 0 } }, { 101, { FIELD_500_ABOVE, 0 } } }; int i; mk->mode6_handler.conf.minimums_enabled = false; mk->mode6_handler.conf.smart_500_enabled = false; mk->mode6_handler.conf.above_field_voice = NULL; for (i = 0; i < n_altitude_callouts; i++) mk->mode6_handler.conf.altitude_callouts_enabled[i] = false; for (i = 0; i < n_elements(values); i++) if (values[i].id == value) { for (int j = 0; values[i].callouts[j] != 0; j++) switch (values[i].callouts[j]) { case MINIMUMS: mk->mode6_handler.conf.minimums_enabled = true; break; case SMART_500: mk->mode6_handler.conf.smart_500_enabled = true; break; case FIELD_500: mk->mode6_handler.conf.above_field_voice = mk_altitude_voice(Mode6Handler::ALTITUDE_CALLOUT_500); break; case FIELD_500_ABOVE: mk->mode6_handler.conf.above_field_voice = mk_voice(five_hundred_above); break; default: for (int k = 0; k < n_altitude_callouts; k++) if (mk->mode6_handler.altitude_callout_definitions[k] == values[i].callouts[j]) mk->mode6_handler.conf.altitude_callouts_enabled[k] = true; break; } return true; } return false; } bool MK_VIII::ConfigurationModule::read_audio_menu_select (int value) { if (value == 0 || value == 1) mk->mode4_handler.conf.voice_too_low_gear = mk_voice(too_low_gear); else if (value == 2 || value == 3) mk->mode4_handler.conf.voice_too_low_gear = mk_voice(too_low_flaps); else return false; return true; } bool MK_VIII::ConfigurationModule::read_terrain_display_select (int value) { if (value == 2) mk->tcf_handler.conf.enabled = false; else if (value == 0 || value == 1 || (value >= 3 && value <= 15) || (value >= 18 && value <= 20) || (value >= 235 && value <= 255)) mk->tcf_handler.conf.enabled = true; else return false; return true; } bool MK_VIII::ConfigurationModule::read_options_select_group_1 (int value) { if (value >= 0 && value < 128) { mk->io_handler.conf.flap_reversal = test_bits(value, 1 << 1); mk->mode6_handler.conf.bank_angle_enabled = test_bits(value, 1 << 2); mk->io_handler.conf.steep_approach_enabled = test_bits(value, 1 << 6); return true; } else return false; } bool MK_VIII::ConfigurationModule::read_radio_altitude_input_select (int value) { // unimplemented return (value >= 0 && value <= 4) || (value >= 251 && value <= 255); } bool MK_VIII::ConfigurationModule::read_navigation_input_select (int value) { if (value >= 0 && value <= 2) mk->io_handler.conf.localizer_enabled = false; else if ((value >= 3 && value <= 5) || (value >= 250 && value <= 255)) mk->io_handler.conf.localizer_enabled = true; else return false; return true; } bool MK_VIII::ConfigurationModule::read_attitude_input_select (int value) { // unimplemented return (value >= 0 && value <= 6) || value == 253 || value == 255; } bool MK_VIII::ConfigurationModule::read_heading_input_select (int value) { // unimplemented return (value >= 0 && value <= 3) || value == 254 || value == 255; } bool MK_VIII::ConfigurationModule::read_windshear_input_select (int value) { // unimplemented return value == 0 || (value >= 253 && value <= 255); } bool MK_VIII::ConfigurationModule::read_input_output_discrete_type_select (int value) { static const struct { int type; IOHandler::LampConfiguration lamp_conf; bool gpws_inhibit_enabled; bool momentary_flap_override_enabled; bool alternate_steep_approach; } io_types[] = { { 0, { false, false }, false, true, true }, { 1, { true, false }, false, true, true }, { 2, { false, false }, true, true, true }, { 3, { true, false }, true, true, true }, { 4, { false, true }, true, true, true }, { 5, { true, true }, true, true, true }, { 6, { false, false }, true, true, false }, { 7, { true, false }, true, true, false }, { 254, { false, false }, true, false, true }, { 255, { false, false }, true, false, true } }; for (int i = 0; i < n_elements(io_types); i++) if (io_types[i].type == value) { mk->io_handler.conf.lamp = &io_types[i].lamp_conf; mk->io_handler.conf.gpws_inhibit_enabled = io_types[i].gpws_inhibit_enabled; mk->io_handler.conf.momentary_flap_override_enabled = io_types[i].momentary_flap_override_enabled; mk->io_handler.conf.alternate_steep_approach = io_types[i].alternate_steep_approach; return true; } return false; } bool MK_VIII::ConfigurationModule::read_audio_output_level (int value) { static const struct { int id; int relative_dB; } values[] = { { 0, 0 }, { 1, -6 }, { 2, -12 }, { 3, -18 }, { 4, -24 } }; for (int i = 0; i < n_elements(values); i++) if (values[i].id == value) { mk->voice_player.set_volume(mk->voice_player.conf.volume = modify_amplitude(1.0, values[i].relative_dB)); return true; } // The self test needs the voice player even when the configuration // is invalid, so set a default value. mk->voice_player.set_volume(mk->voice_player.conf.volume = 1.0); return false; } bool MK_VIII::ConfigurationModule::read_undefined_input_select (int value) { // unimplemented return value == 0; } void MK_VIII::ConfigurationModule::boot () { bool (MK_VIII::ConfigurationModule::*readers[N_CATEGORIES]) (int) = { &MK_VIII::ConfigurationModule::read_aircraft_mode_type_select, &MK_VIII::ConfigurationModule::read_air_data_input_select, &MK_VIII::ConfigurationModule::read_position_input_select, &MK_VIII::ConfigurationModule::read_altitude_callouts, &MK_VIII::ConfigurationModule::read_audio_menu_select, &MK_VIII::ConfigurationModule::read_terrain_display_select, &MK_VIII::ConfigurationModule::read_options_select_group_1, &MK_VIII::ConfigurationModule::read_radio_altitude_input_select, &MK_VIII::ConfigurationModule::read_navigation_input_select, &MK_VIII::ConfigurationModule::read_attitude_input_select, &MK_VIII::ConfigurationModule::read_heading_input_select, &MK_VIII::ConfigurationModule::read_windshear_input_select, &MK_VIII::ConfigurationModule::read_input_output_discrete_type_select, &MK_VIII::ConfigurationModule::read_audio_output_level, &MK_VIII::ConfigurationModule::read_undefined_input_select, &MK_VIII::ConfigurationModule::read_undefined_input_select, &MK_VIII::ConfigurationModule::read_undefined_input_select }; memcpy(effective_categories, categories, sizeof(categories)); state = STATE_OK; for (int i = 0; i < N_CATEGORIES; i++) if (! (this->*readers[i])(effective_categories[i])) { SG_LOG(SG_INSTR, SG_ALERT, "MK VIII EGPWS configuration category " << i + 1 << ": invalid value " << effective_categories[i]); if (state == STATE_OK) state = STATE_INVALID_DATABASE; mk_doutput(gpws_inop) = true; mk_doutput(tad_inop) = true; return; } // handle conflicts if (mk->mode6_handler.conf.above_field_voice && ! mk->tcf_handler.conf.enabled) { // [INSTALL] 3.6 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"); state = STATE_INVALID_DATABASE; } } void MK_VIII::ConfigurationModule::bind (SGPropertyNode *node) { for (int i = 0; i < N_CATEGORIES; i++) { std::ostringstream name; name << "configuration-module/category-" << i + 1; mk->properties_handler.tie(node, name.str().c_str(), SGRawValuePointer(&categories[i])); } } /////////////////////////////////////////////////////////////////////////////// // FaultHandler /////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////// // [INSTALL] only specifies that the faults cause a GPWS INOP // indication. We arbitrarily set a TAD INOP when it makes sense. const unsigned int MK_VIII::FaultHandler::fault_inops[] = { INOP_GPWS | INOP_TAD, // [INSTALL] 3.15.1.3 INOP_GPWS, // [INSTALL] appendix E 6.6.2 INOP_GPWS, // [INSTALL] appendix E 6.6.4 INOP_GPWS, // [INSTALL] appendix E 6.6.6 INOP_GPWS | INOP_TAD, // [INSTALL] appendix E 6.6.7 INOP_GPWS, // [INSTALL] appendix E 6.6.13 INOP_GPWS, // [INSTALL] appendix E 6.6.25 INOP_GPWS, // [INSTALL] appendix E 6.6.27 INOP_TAD, // unspecified INOP_GPWS, // unspecified INOP_GPWS, // unspecified // [INSTALL] 2.3.10.1 specifies that GPWS INOP is "activated during // any detected partial or total failure of the GPWS modes 1-5", so // do not set any INOP for mode 6 and bank angle. 0, // unspecified 0, // unspecified INOP_TAD // unspecified }; bool MK_VIII::FaultHandler::has_faults () const { for (int i = 0; i < N_FAULTS; i++) if (faults[i]) return true; return false; } bool MK_VIII::FaultHandler::has_faults (unsigned int inop) { for (int i = 0; i < N_FAULTS; i++) if (faults[i] && test_bits(fault_inops[i], inop)) return true; return false; } void MK_VIII::FaultHandler::boot () { memset(faults, 0, sizeof(faults)); } void MK_VIII::FaultHandler::set_fault (Fault fault) { if (! faults[fault]) { faults[fault] = true; mk->self_test_handler.set_inop(); if (test_bits(fault_inops[fault], INOP_GPWS)) { mk_unset_alerts(~mk_alert(TCF_TOO_LOW_TERRAIN)); mk_doutput(gpws_inop) = true; } if (test_bits(fault_inops[fault], INOP_TAD)) { mk_unset_alerts(mk_alert(TCF_TOO_LOW_TERRAIN)); mk_doutput(tad_inop) = true; } } } void MK_VIII::FaultHandler::unset_fault (Fault fault) { if (faults[fault]) { faults[fault] = false; if (! has_faults(INOP_GPWS)) mk_doutput(gpws_inop) = false; if (! has_faults(INOP_TAD)) mk_doutput(tad_inop) = false; } } /////////////////////////////////////////////////////////////////////////////// // IOHandler ////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////// double MK_VIII::IOHandler::TerrainClearanceFilter::update (double agl) { // [PILOT] page 20 specifies that the terrain clearance is equal to // 75% of the radio altitude, averaged over the previous 15 seconds. samples_type::iterator iter; // remove samples older than 15 seconds for (iter = samples.begin(); iter != samples.end() && globals->get_sim_time_sec() - (*iter).timestamp >= 15; iter = samples.begin()) samples.erase(iter); // append new sample samples.push_back(Sample(agl)); // calculate average double new_value = 0; if (samples.size() > 0) { for (iter = samples.begin(); iter != samples.end(); iter++) new_value += (*iter).value; new_value /= samples.size(); } new_value *= 0.75; if (new_value > value) value = new_value; return value; } void MK_VIII::IOHandler::TerrainClearanceFilter::reset () { samples.clear(); value = 0; } MK_VIII::IOHandler::IOHandler (MK_VIII *device) : mk(device), _lamp(LAMP_NONE) { memset(&input_feeders, 0, sizeof(input_feeders)); memset(&inputs.discretes, 0, sizeof(inputs.discretes)); memset(&outputs, 0, sizeof(outputs)); memset(&power_saved, 0, sizeof(power_saved)); mk_dinput_feed(landing_gear) = true; mk_dinput_feed(landing_flaps) = true; mk_dinput_feed(glideslope_inhibit) = true; mk_dinput_feed(decision_height) = true; mk_dinput_feed(autopilot_engaged) = true; mk_ainput_feed(uncorrected_barometric_altitude) = true; mk_ainput_feed(barometric_altitude_rate) = true; mk_ainput_feed(radio_altitude) = true; mk_ainput_feed(glideslope_deviation) = true; mk_ainput_feed(roll_angle) = true; mk_ainput_feed(localizer_deviation) = true; mk_ainput_feed(computed_airspeed) = true; // will be unset on power on mk_doutput(gpws_inop) = true; mk_doutput(tad_inop) = true; } void MK_VIII::IOHandler::boot () { if (mk->configuration_module.state != ConfigurationModule::STATE_OK) return; mk_doutput(gpws_inop) = false; mk_doutput(tad_inop) = false; mk_doutput(glideslope_cancel) = power_saved.glideslope_cancel; altitude_samples.clear(); } void MK_VIII::IOHandler::post_boot () { if (momentary_steep_approach_enabled()) { last_landing_gear = mk_dinput(landing_gear); last_real_flaps_down = real_flaps_down(); } // read externally-latching input discretes update_alternate_discrete_input(&mk_dinput(mode6_low_volume)); update_alternate_discrete_input(&mk_dinput(audio_inhibit)); } void MK_VIII::IOHandler::power_off () { power_saved.glideslope_cancel = mk_doutput(glideslope_cancel); // [SPEC] 6.2.5 memset(&outputs, 0, sizeof(outputs)); audio_inhibit_fault_timer.stop(); landing_gear_fault_timer.stop(); flaps_down_fault_timer.stop(); momentary_flap_override_fault_timer.stop(); self_test_fault_timer.stop(); glideslope_cancel_fault_timer.stop(); steep_approach_fault_timer.stop(); gpws_inhibit_fault_timer.stop(); ta_tcf_inhibit_fault_timer.stop(); // [SPEC] 6.9.3.5 mk_doutput(gpws_inop) = true; mk_doutput(tad_inop) = true; } void MK_VIII::IOHandler::enter_ground () { reset_terrain_clearance(); if (conf.momentary_flap_override_enabled) mk_doutput(flap_override) = false; // [INSTALL] 3.15.1.6 } void MK_VIII::IOHandler::enter_takeoff () { reset_terrain_clearance(); if (momentary_steep_approach_enabled()) // landing or go-around, disable steep approach as per [SPEC] 6.2.1 mk_doutput(steep_approach) = false; } void MK_VIII::IOHandler::update_inputs () { if (mk->configuration_module.state != ConfigurationModule::STATE_OK) return; // 1. process input feeders if (mk_dinput_feed(landing_gear)) mk_dinput(landing_gear) = mk_node(gear_down)->getBoolValue(); if (mk_dinput_feed(landing_flaps)) mk_dinput(landing_flaps) = mk_node(flaps)->getDoubleValue() < 1; if (mk_dinput_feed(glideslope_inhibit)) mk_dinput(glideslope_inhibit) = mk_node(nav0_gs_distance)->getDoubleValue() < 0; if (mk_dinput_feed(autopilot_engaged)) { const char *mode; mode = mk_node(autopilot_heading_lock)->getStringValue(); mk_dinput(autopilot_engaged) = mode && *mode; } if (mk_ainput_feed(uncorrected_barometric_altitude)) { if (mk_node(altimeter_serviceable)->getBoolValue()) mk_ainput(uncorrected_barometric_altitude).set(mk_node(altimeter_altitude)->getDoubleValue()); else mk_ainput(uncorrected_barometric_altitude).unset(); } if (mk_ainput_feed(barometric_altitude_rate)) // Do not use the vsi, because it is pressure-based only, and // therefore too laggy for GPWS alerting purposes. I guess that // a real ADC combines pressure-based and inerta-based altitude // rates to provide a non-laggy rate. That non-laggy rate is // best emulated by /velocities/vertical-speed-fps * 60. mk_ainput(barometric_altitude_rate).set(mk_node(vs)->getDoubleValue() * 60); if (mk_ainput_feed(radio_altitude)) { // Some flight models may return negative values when on the // ground or after a crash; do not allow them. double agl = mk_node(altitude_agl)->getDoubleValue(); mk_ainput(radio_altitude).set(SG_MAX2(0.0, agl)); } if (mk_ainput_feed(glideslope_deviation)) { if (mk_node(nav0_serviceable)->getBoolValue() && mk_node(nav0_gs_serviceable)->getBoolValue() && mk_node(nav0_in_range)->getBoolValue() && mk_node(nav0_has_gs)->getBoolValue()) // gs-needle-deflection is expressed in degrees, and 1 dot = // 0.32 degrees (according to // http://www.ntsb.gov/Recs/letters/2000/A00_41_45.pdf) mk_ainput(glideslope_deviation).set(mk_node(nav0_gs_needle_deflection)->getDoubleValue() / 0.32 * GLIDESLOPE_DOTS_TO_DDM); else mk_ainput(glideslope_deviation).unset(); } if (mk_ainput_feed(roll_angle)) { if (mk_node(ai_serviceable)->getBoolValue() && ! mk_node(ai_caged)->getBoolValue()) mk_ainput(roll_angle).set(mk_node(ai_roll)->getDoubleValue()); else mk_ainput(roll_angle).unset(); } if (mk_ainput_feed(localizer_deviation)) { if (mk_node(nav0_serviceable)->getBoolValue() && mk_node(nav0_cdi_serviceable)->getBoolValue() && mk_node(nav0_in_range)->getBoolValue() && mk_node(nav0_nav_loc)->getBoolValue()) // heading-needle-deflection is expressed in degrees, and 1 // dot = 2 degrees (0.5 degrees for ILS, but navradio.cxx // performs the conversion) mk_ainput(localizer_deviation).set(mk_node(nav0_heading_needle_deflection)->getDoubleValue() / 2 * LOCALIZER_DOTS_TO_DDM); else mk_ainput(localizer_deviation).unset(); } if (mk_ainput_feed(computed_airspeed)) { if (mk_node(asi_serviceable)->getBoolValue()) mk_ainput(computed_airspeed).set(mk_node(asi_speed)->getDoubleValue()); else mk_ainput(computed_airspeed).unset(); } // 2. compute data mk_data(decision_height).set(&mk_ainput(decision_height)); mk_data(radio_altitude).set(&mk_ainput(radio_altitude)); mk_data(roll_angle).set(&mk_ainput(roll_angle)); // update barometric_altitude_rate as per [SPEC] 6.2.1: "During // normal conditions, the system will base Mode 1 computations upon // barometric rate from the ADC. If this computed data is not valid // or available then the system will use internally computed // barometric altitude rate." if (! mk_ainput(barometric_altitude_rate).ncd) // the altitude rate input is valid, use it mk_data(barometric_altitude_rate).set(mk_ainput(barometric_altitude_rate).get()); else { bool has = false; // The altitude rate input is invalid. We can compute an // altitude rate if all the following conditions are true: // // - the altitude input is valid // - there is an altitude sample with age >= 1 second // - that altitude sample is valid if (! mk_ainput(uncorrected_barometric_altitude).ncd) { if (! altitude_samples.empty() && ! altitude_samples.front().value.ncd) { double elapsed = globals->get_sim_time_sec() - altitude_samples.front().timestamp; if (elapsed >= 1) { has = true; mk_data(barometric_altitude_rate).set((mk_ainput(uncorrected_barometric_altitude).get() - altitude_samples.front().value.get()) / elapsed * 60); } } } if (! has) mk_data(barometric_altitude_rate).unset(); } altitude_samples.push_back(Sample< Parameter >(mk_ainput(uncorrected_barometric_altitude))); // Erase everything from the beginning of the list up to the sample // preceding the most recent sample whose age is >= 1 second. altitude_samples_type::iterator erase_last = altitude_samples.begin(); altitude_samples_type::iterator iter; for (iter = altitude_samples.begin(); iter != altitude_samples.end(); iter++) if (globals->get_sim_time_sec() - (*iter).timestamp >= 1) erase_last = iter; else break; if (erase_last != altitude_samples.begin()) altitude_samples.erase(altitude_samples.begin(), erase_last); // update GPS data if (conf.use_internal_gps) { mk_data(gps_altitude).set(mk_node(altitude)->getDoubleValue()); mk_data(gps_latitude).set(mk_node(latitude)->getDoubleValue()); mk_data(gps_longitude).set(mk_node(longitude)->getDoubleValue()); mk_data(gps_vertical_figure_of_merit).set(0.0); } else { mk_data(gps_altitude).set(&mk_ainput(gps_altitude)); mk_data(gps_latitude).set(&mk_ainput(gps_latitude)); mk_data(gps_longitude).set(&mk_ainput(gps_longitude)); mk_data(gps_vertical_figure_of_merit).set(&mk_ainput(gps_vertical_figure_of_merit)); } // update glideslope and localizer mk_data(glideslope_deviation_dots).set(&mk_ainput(glideslope_deviation), GLIDESLOPE_DDM_TO_DOTS); mk_data(localizer_deviation_dots).set(&mk_ainput(localizer_deviation), LOCALIZER_DDM_TO_DOTS); // Update geometric altitude; [SPEC] 6.7.8 provides an overview of a // complex algorithm which combines several input sources to // calculate the geometric altitude. Since the exact algorithm is // not given, we fallback to a much simpler method. if (! mk_data(gps_altitude).ncd) mk_data(geometric_altitude).set(mk_data(gps_altitude).get()); else if (! mk_ainput(uncorrected_barometric_altitude).ncd) mk_data(geometric_altitude).set(mk_ainput(uncorrected_barometric_altitude).get()); else mk_data(geometric_altitude).unset(); // update terrain clearance update_terrain_clearance(); // 3. perform sanity checks if (! mk_data(radio_altitude).ncd && mk_data(radio_altitude).get() < 0) mk_data(radio_altitude).unset(); if (! mk_data(decision_height).ncd && mk_data(decision_height).get() < 0) mk_data(decision_height).unset(); if (! mk_data(gps_latitude).ncd && (mk_data(gps_latitude).get() < -90 || mk_data(gps_latitude).get() > 90)) mk_data(gps_latitude).unset(); if (! mk_data(gps_longitude).ncd && (mk_data(gps_longitude).get() < -180 || mk_data(gps_longitude).get() > 180)) mk_data(gps_longitude).unset(); if (! mk_data(roll_angle).ncd && ((mk_data(roll_angle).get() < -180) || (mk_data(roll_angle).get() > 180))) mk_data(roll_angle).unset(); // 4. process input feeders requiring data computed above if (mk_dinput_feed(decision_height)) mk_dinput(decision_height) = ! mk_data(radio_altitude).ncd && ! mk_data(decision_height).ncd && mk_data(radio_altitude).get() <= mk_data(decision_height).get(); } void MK_VIII::IOHandler::update_terrain_clearance () { if (! mk_data(radio_altitude).ncd) mk_data(terrain_clearance).set(terrain_clearance_filter.update(mk_data(radio_altitude).get())); else mk_data(terrain_clearance).unset(); } void MK_VIII::IOHandler::reset_terrain_clearance () { terrain_clearance_filter.reset(); update_terrain_clearance(); } void MK_VIII::IOHandler::handle_input_fault (bool test, FaultHandler::Fault fault) { if (test) { if (! mk->fault_handler.faults[fault]) mk->fault_handler.set_fault(fault); } else mk->fault_handler.unset_fault(fault); } void MK_VIII::IOHandler::handle_input_fault (bool test, Timer *timer, double max_duration, FaultHandler::Fault fault) { if (test) { if (! mk->fault_handler.faults[fault]) { if (timer->start_or_elapsed() >= max_duration) mk->fault_handler.set_fault(fault); } } else { mk->fault_handler.unset_fault(fault); timer->stop(); } } void MK_VIII::IOHandler::update_input_faults () { if (mk->configuration_module.state != ConfigurationModule::STATE_OK) return; // [INSTALL] 3.15.1.3 handle_input_fault(mk_dinput(audio_inhibit), &audio_inhibit_fault_timer, 60, FaultHandler::FAULT_ALL_MODES_INHIBIT); // [INSTALL] appendix E 6.6.2 handle_input_fault(mk_dinput(landing_gear) && ! mk_ainput(computed_airspeed).ncd && mk_ainput(computed_airspeed).get() > conf.faults->max_gear_down_airspeed, &landing_gear_fault_timer, 60, FaultHandler::FAULT_GEAR_SWITCH); // [INSTALL] appendix E 6.6.4 handle_input_fault(flaps_down() && ! mk_ainput(computed_airspeed).ncd && mk_ainput(computed_airspeed).get() > conf.faults->max_flaps_down_airspeed, &flaps_down_fault_timer, 60, FaultHandler::FAULT_FLAPS_SWITCH); // [INSTALL] appendix E 6.6.6 if (conf.momentary_flap_override_enabled) handle_input_fault(mk_dinput(momentary_flap_override), &momentary_flap_override_fault_timer, 15, FaultHandler::FAULT_MOMENTARY_FLAP_OVERRIDE_INVALID); // [INSTALL] appendix E 6.6.7 handle_input_fault(mk_dinput(self_test), &self_test_fault_timer, 60, FaultHandler::FAULT_SELF_TEST_INVALID); // [INSTALL] appendix E 6.6.13 handle_input_fault(mk_dinput(glideslope_cancel), &glideslope_cancel_fault_timer, 15, FaultHandler::FAULT_GLIDESLOPE_CANCEL_INVALID); // [INSTALL] appendix E 6.6.25 if (momentary_steep_approach_enabled()) handle_input_fault(mk_dinput(steep_approach), &steep_approach_fault_timer, 15, FaultHandler::FAULT_STEEP_APPROACH_INVALID); // [INSTALL] appendix E 6.6.27 if (conf.gpws_inhibit_enabled) handle_input_fault(mk_dinput(gpws_inhibit), &gpws_inhibit_fault_timer, 5, FaultHandler::FAULT_GPWS_INHIBIT); // [INSTALL] does not specify a fault for this one, but it makes // sense to have it behave like GPWS inhibit handle_input_fault(mk_dinput(ta_tcf_inhibit), &ta_tcf_inhibit_fault_timer, 5, FaultHandler::FAULT_TA_TCF_INHIBIT); // [PILOT] page 48: "In the event that required data for a // particular function is not available, then that function is // automatically inhibited and annunciated" handle_input_fault(mk_data(radio_altitude).ncd || mk_ainput(uncorrected_barometric_altitude).ncd || mk_data(barometric_altitude_rate).ncd || mk_ainput(computed_airspeed).ncd || mk_data(terrain_clearance).ncd, FaultHandler::FAULT_MODES14_INPUTS_INVALID); if (! mk_dinput(glideslope_inhibit)) handle_input_fault(mk_data(radio_altitude).ncd, FaultHandler::FAULT_MODE5_INPUTS_INVALID); if (mk->mode6_handler.altitude_callouts_enabled()) handle_input_fault(mk->mode6_handler.conf.above_field_voice ? (mk_data(gps_latitude).ncd || mk_data(gps_longitude).ncd || mk_data(geometric_altitude).ncd) : mk_data(radio_altitude).ncd, FaultHandler::FAULT_MODE6_INPUTS_INVALID); if (mk->mode6_handler.conf.bank_angle_enabled) handle_input_fault(mk_data(roll_angle).ncd, FaultHandler::FAULT_BANK_ANGLE_INPUTS_INVALID); if (mk->tcf_handler.conf.enabled) handle_input_fault(mk_data(radio_altitude).ncd || mk_data(geometric_altitude).ncd || mk_data(gps_latitude).ncd || mk_data(gps_longitude).ncd || mk_data(gps_vertical_figure_of_merit).ncd, FaultHandler::FAULT_TCF_INPUTS_INVALID); } void MK_VIII::IOHandler::update_alternate_discrete_input (bool *ptr) { assert(mk->system_handler.state == SystemHandler::STATE_ON); if (ptr == &mk_dinput(mode6_low_volume)) { if (mk->configuration_module.state == ConfigurationModule::STATE_OK && mk->self_test_handler.state == SelfTestHandler::STATE_NONE) mk->mode6_handler.set_volume(*ptr ? modify_amplitude(1.0, -6) : 1.0); } else if (ptr == &mk_dinput(audio_inhibit)) { if (mk->configuration_module.state == ConfigurationModule::STATE_OK && mk->self_test_handler.state == SelfTestHandler::STATE_NONE) mk->voice_player.set_volume(*ptr ? 0.0 : mk->voice_player.conf.volume); } } void MK_VIII::IOHandler::update_internal_latches () { if (mk->configuration_module.state != ConfigurationModule::STATE_OK) return; // [SPEC] 6.2.1 if (conf.momentary_flap_override_enabled && mk_doutput(flap_override) && ! mk->state_handler.takeoff && (mk_data(radio_altitude).ncd || mk_data(radio_altitude).get() <= 50)) mk_doutput(flap_override) = false; // [SPEC] 6.2.1 if (momentary_steep_approach_enabled()) { if (mk_doutput(steep_approach) && ! mk->state_handler.takeoff && ((last_landing_gear && ! mk_dinput(landing_gear)) || (last_real_flaps_down && ! real_flaps_down()))) // gear or flaps raised during approach: it's a go-around mk_doutput(steep_approach) = false; last_landing_gear = mk_dinput(landing_gear); last_real_flaps_down = real_flaps_down(); } // [SPEC] 6.2.5 if (mk_doutput(glideslope_cancel) && (mk_data(glideslope_deviation_dots).ncd || mk_data(radio_altitude).ncd || mk_data(radio_altitude).get() > 2000 || mk_data(radio_altitude).get() < 30)) mk_doutput(glideslope_cancel) = false; } void MK_VIII::IOHandler::update_egpws_alert_discrete_1 () { if (mk->voice_player.voice) { const struct { int bit; VoicePlayer::Voice *voice; } voices[] = { { 11, mk_voice(sink_rate_pause_sink_rate) }, { 12, mk_voice(pull_up) }, { 13, mk_voice(terrain) }, { 13, mk_voice(terrain_pause_terrain) }, { 14, mk_voice(dont_sink_pause_dont_sink) }, { 15, mk_voice(too_low_gear) }, { 16, mk_voice(too_low_flaps) }, { 17, mk_voice(too_low_terrain) }, { 18, mk_voice(soft_glideslope) }, { 18, mk_voice(hard_glideslope) }, { 19, mk_voice(minimums_minimums) } }; for (int i = 0; i < n_elements(voices); i++) if (voices[i].voice == mk->voice_player.voice) { mk_aoutput(egpws_alert_discrete_1) = 1 << voices[i].bit; return; } } mk_aoutput(egpws_alert_discrete_1) = 0; } void MK_VIII::IOHandler::update_egpwc_logic_discretes () { mk_aoutput(egpwc_logic_discretes) = 0; if (mk->state_handler.takeoff) mk_aoutput(egpwc_logic_discretes) |= 1 << 18; static const struct { int bit; unsigned int alerts; } logic[] = { { 13, mk_alert(TCF_TOO_LOW_TERRAIN) }, { 19, mk_alert(MODE1_SINK_RATE) }, { 20, mk_alert(MODE1_PULL_UP) }, { 21, mk_alert(MODE2A_PREFACE) | mk_alert(MODE2B_PREFACE) | mk_alert(MODE2B_LANDING_MODE) | mk_alert(MODE2A_ALTITUDE_GAIN_TERRAIN_CLOSING) }, { 22, mk_alert(MODE2A) | mk_alert(MODE2B) }, { 23, mk_alert(MODE3) }, { 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) }, { 25, mk_alert(MODE5_SOFT) | mk_alert(MODE5_HARD) } }; for (int i = 0; i < n_elements(logic); i++) if (mk_test_alerts(logic[i].alerts)) mk_aoutput(egpwc_logic_discretes) |= 1 << logic[i].bit; } void MK_VIII::IOHandler::update_mode6_callouts_discrete_1 () { if (mk->voice_player.voice) { const struct { int bit; VoicePlayer::Voice *voice; } voices[] = { { 11, mk_voice(minimums_minimums) }, { 16, mk_altitude_voice(Mode6Handler::ALTITUDE_CALLOUT_10) }, { 17, mk_altitude_voice(Mode6Handler::ALTITUDE_CALLOUT_20) }, { 18, mk_altitude_voice(Mode6Handler::ALTITUDE_CALLOUT_30) }, { 19, mk_altitude_voice(Mode6Handler::ALTITUDE_CALLOUT_40) }, { 20, mk_altitude_voice(Mode6Handler::ALTITUDE_CALLOUT_50) }, { 23, mk_altitude_voice(Mode6Handler::ALTITUDE_CALLOUT_100) }, { 24, mk_altitude_voice(Mode6Handler::ALTITUDE_CALLOUT_200) }, { 25, mk_altitude_voice(Mode6Handler::ALTITUDE_CALLOUT_300) } }; for (int i = 0; i < n_elements(voices); i++) if (voices[i].voice == mk->voice_player.voice) { mk_aoutput(mode6_callouts_discrete_1) = 1 << voices[i].bit; return; } } mk_aoutput(mode6_callouts_discrete_1) = 0; } void MK_VIII::IOHandler::update_mode6_callouts_discrete_2 () { if (mk->voice_player.voice) { const struct { int bit; VoicePlayer::Voice *voice; } voices[] = { { 11, mk_altitude_voice(Mode6Handler::ALTITUDE_CALLOUT_400) }, { 12, mk_altitude_voice(Mode6Handler::ALTITUDE_CALLOUT_500) }, { 13, mk_altitude_voice(Mode6Handler::ALTITUDE_CALLOUT_1000) }, { 18, mk_voice(bank_angle_pause_bank_angle) }, { 18, mk_voice(bank_angle_pause_bank_angle_3) }, { 23, mk_voice(five_hundred_above) } }; for (int i = 0; i < n_elements(voices); i++) if (voices[i].voice == mk->voice_player.voice) { mk_aoutput(mode6_callouts_discrete_2) = 1 << voices[i].bit; return; } } mk_aoutput(mode6_callouts_discrete_2) = 0; } void MK_VIII::IOHandler::update_egpws_alert_discrete_2 () { mk_aoutput(egpws_alert_discrete_2) = 1 << 27; // Terrain NA if (mk_doutput(glideslope_cancel)) mk_aoutput(egpws_alert_discrete_2) |= 1 << 11; if (mk_doutput(gpws_alert)) mk_aoutput(egpws_alert_discrete_2) |= 1 << 12; if (mk_doutput(gpws_warning)) mk_aoutput(egpws_alert_discrete_2) |= 1 << 13; if (mk_doutput(gpws_inop)) mk_aoutput(egpws_alert_discrete_2) |= 1 << 14; if (mk_doutput(audio_on)) mk_aoutput(egpws_alert_discrete_2) |= 1 << 16; if (mk_doutput(tad_inop)) mk_aoutput(egpws_alert_discrete_2) |= 1 << 24; if (mk->fault_handler.has_faults()) mk_aoutput(egpws_alert_discrete_2) |= 1 << 25; } void MK_VIII::IOHandler::update_egpwc_alert_discrete_3 () { mk_aoutput(egpwc_alert_discrete_3) = 1 << 17 | 1 << 18; if (mk->fault_handler.faults[FaultHandler::FAULT_MODES14_INPUTS_INVALID]) mk_aoutput(egpwc_alert_discrete_3) |= 1 << 11; if (mk->fault_handler.faults[FaultHandler::FAULT_MODE5_INPUTS_INVALID]) mk_aoutput(egpwc_alert_discrete_3) |= 1 << 12; if (mk->fault_handler.faults[FaultHandler::FAULT_MODE6_INPUTS_INVALID]) mk_aoutput(egpwc_alert_discrete_3) |= 1 << 13; if (mk->fault_handler.faults[FaultHandler::FAULT_BANK_ANGLE_INPUTS_INVALID]) mk_aoutput(egpwc_alert_discrete_3) |= 1 << 14; if (mk_doutput(tad_inop)) mk_aoutput(egpwc_alert_discrete_3) |= 1 << 16; } void MK_VIII::IOHandler::update_outputs () { if (mk->configuration_module.state != ConfigurationModule::STATE_OK) return; mk_doutput(audio_on) = ! mk_dinput(audio_inhibit) && mk->voice_player.voice && ! mk->voice_player.voice->element->silence; update_egpws_alert_discrete_1(); update_egpwc_logic_discretes(); update_mode6_callouts_discrete_1(); update_mode6_callouts_discrete_2(); update_egpws_alert_discrete_2(); update_egpwc_alert_discrete_3(); } bool * MK_VIII::IOHandler::get_lamp_output (Lamp lamp) { switch (lamp) { case LAMP_GLIDESLOPE: return &mk_doutput(gpws_alert); case LAMP_CAUTION: return conf.lamp->format2 ? &mk_doutput(gpws_alert) : &mk_doutput(gpws_warning); case LAMP_WARNING: return &mk_doutput(gpws_warning); default: assert_not_reached(); return NULL; } } void MK_VIII::IOHandler::update_lamps () { if (mk->configuration_module.state != ConfigurationModule::STATE_OK) return; if (_lamp != LAMP_NONE && conf.lamp->flashing) { // [SPEC] 6.9.3: 70 cycles per minute if (lamp_timer.elapsed() >= 60.0 / 70.0 / 2.0) { bool *output = get_lamp_output(_lamp); *output = ! *output; lamp_timer.start(); } } } void MK_VIII::IOHandler::set_lamp (Lamp lamp) { if (lamp == _lamp) return; _lamp = lamp; mk_doutput(gpws_warning) = false; mk_doutput(gpws_alert) = false; if (lamp != LAMP_NONE) { *get_lamp_output(lamp) = true; lamp_timer.start(); } } bool MK_VIII::IOHandler::gpws_inhibit () const { return conf.gpws_inhibit_enabled && mk_dinput(gpws_inhibit); } bool MK_VIII::IOHandler::real_flaps_down () const { return conf.flap_reversal ? mk_dinput(landing_flaps) : ! mk_dinput(landing_flaps); } bool MK_VIII::IOHandler::flaps_down () const { return flap_override() ? true : real_flaps_down(); } bool MK_VIII::IOHandler::flap_override () const { return conf.momentary_flap_override_enabled ? mk_doutput(flap_override) : false; } bool MK_VIII::IOHandler::steep_approach () const { if (conf.steep_approach_enabled) // If alternate action was configured in category 13, we return // the value of the input discrete (which should be connected to a // latching steep approach cockpit switch). Otherwise, we return // the value of the output discrete. return conf.alternate_steep_approach ? mk_dinput(steep_approach) : mk_doutput(steep_approach); else return false; } bool MK_VIII::IOHandler::momentary_steep_approach_enabled () const { return conf.steep_approach_enabled && ! conf.alternate_steep_approach; } void MK_VIII::IOHandler::tie_input (SGPropertyNode *node, const char *name, bool *input, bool *feed) { mk->properties_handler.tie(node, (string("inputs/discretes/") + name).c_str(), RawValueMethodsData(*this, input, &MK_VIII::IOHandler::get_discrete_input, &MK_VIII::IOHandler::set_discrete_input)); if (feed) mk->properties_handler.tie(node, (string("input-feeders/discretes/") + name).c_str(), SGRawValuePointer(feed)); } void MK_VIII::IOHandler::tie_input (SGPropertyNode *node, const char *name, Parameter *input, bool *feed) { mk->properties_handler.tie(node, (string("inputs/arinc429/") + name).c_str(), SGRawValuePointer(input->get_pointer())); mk->properties_handler.tie(node, (string("inputs/arinc429/") + name + "-ncd").c_str(), SGRawValuePointer(&input->ncd)); if (feed) mk->properties_handler.tie(node, (string("input-feeders/arinc429/") + name).c_str(), SGRawValuePointer(feed)); } void MK_VIII::IOHandler::tie_output (SGPropertyNode *node, const char *name, bool *output) { SGPropertyNode *child = node->getNode((string("outputs/discretes/") + name).c_str(), true); mk->properties_handler.tie(child, SGRawValuePointer(output)); child->setAttribute(SGPropertyNode::WRITE, false); } void MK_VIII::IOHandler::tie_output (SGPropertyNode *node, const char *name, int *output) { SGPropertyNode *child = node->getNode((string("outputs/arinc429/") + name).c_str(), true); mk->properties_handler.tie(child, SGRawValuePointer(output)); child->setAttribute(SGPropertyNode::WRITE, false); } void MK_VIII::IOHandler::bind (SGPropertyNode *node) { mk->properties_handler.tie(node, "inputs/rs-232/present-status", SGRawValueMethods(*this, &MK_VIII::IOHandler::get_present_status, &MK_VIII::IOHandler::set_present_status)); tie_input(node, "landing-gear", &mk_dinput(landing_gear), &mk_dinput_feed(landing_gear)); tie_input(node, "landing-flaps", &mk_dinput(landing_flaps), &mk_dinput_feed(landing_flaps)); tie_input(node, "momentary-flap-override", &mk_dinput(momentary_flap_override)); tie_input(node, "self-test", &mk_dinput(self_test)); tie_input(node, "glideslope-inhibit", &mk_dinput(glideslope_inhibit), &mk_dinput_feed(glideslope_inhibit)); tie_input(node, "glideslope-cancel", &mk_dinput(glideslope_cancel)); tie_input(node, "decision-height", &mk_dinput(decision_height), &mk_dinput_feed(decision_height)); tie_input(node, "mode6-low-volume", &mk_dinput(mode6_low_volume)); tie_input(node, "audio-inhibit", &mk_dinput(audio_inhibit)); tie_input(node, "ta-tcf-inhibit", &mk_dinput(ta_tcf_inhibit)); tie_input(node, "autopilot-engaged", &mk_dinput(autopilot_engaged), &mk_dinput_feed(autopilot_engaged)); tie_input(node, "steep-approach", &mk_dinput(steep_approach)); tie_input(node, "gpws-inhibit", &mk_dinput(gpws_inhibit)); tie_input(node, "uncorrected-barometric-altitude", &mk_ainput(uncorrected_barometric_altitude), &mk_ainput_feed(uncorrected_barometric_altitude)); tie_input(node, "barometric-altitude-rate", &mk_ainput(barometric_altitude_rate), &mk_ainput_feed(barometric_altitude_rate)); tie_input(node, "gps-altitude", &mk_ainput(gps_altitude)); tie_input(node, "gps-latitude", &mk_ainput(gps_latitude)); tie_input(node, "gps-longitude", &mk_ainput(gps_longitude)); tie_input(node, "gps-vertical-figure-of-merit", &mk_ainput(gps_vertical_figure_of_merit)); tie_input(node, "radio-altitude", &mk_ainput(radio_altitude), &mk_ainput_feed(radio_altitude)); tie_input(node, "glideslope-deviation", &mk_ainput(glideslope_deviation), &mk_ainput_feed(glideslope_deviation)); tie_input(node, "roll-angle", &mk_ainput(roll_angle), &mk_ainput_feed(roll_angle)); tie_input(node, "localizer-deviation", &mk_ainput(localizer_deviation), &mk_ainput_feed(localizer_deviation)); tie_input(node, "computed-airspeed", &mk_ainput(computed_airspeed), &mk_ainput_feed(computed_airspeed)); tie_input(node, "decision-height", &mk_ainput(decision_height), &mk_ainput_feed(decision_height)); tie_output(node, "gpws-warning", &mk_doutput(gpws_warning)); tie_output(node, "gpws-alert", &mk_doutput(gpws_alert)); tie_output(node, "audio-on", &mk_doutput(audio_on)); tie_output(node, "gpws-inop", &mk_doutput(gpws_inop)); tie_output(node, "tad-inop", &mk_doutput(tad_inop)); tie_output(node, "flap-override", &mk_doutput(flap_override)); tie_output(node, "glideslope-cancel", &mk_doutput(glideslope_cancel)); tie_output(node, "steep-approach", &mk_doutput(steep_approach)); tie_output(node, "egpws-alert-discrete-1", &mk_aoutput(egpws_alert_discrete_1)); tie_output(node, "egpwc-logic-discretes", &mk_aoutput(egpwc_logic_discretes)); tie_output(node, "mode6-callouts-discrete-1", &mk_aoutput(mode6_callouts_discrete_1)); tie_output(node, "mode6-callouts-discrete-2", &mk_aoutput(mode6_callouts_discrete_2)); tie_output(node, "egpws-alert-discrete-2", &mk_aoutput(egpws_alert_discrete_2)); tie_output(node, "egpwc-alert-discrete-3", &mk_aoutput(egpwc_alert_discrete_3)); } bool MK_VIII::IOHandler::get_discrete_input (bool *ptr) const { return *ptr; } void MK_VIII::IOHandler::set_discrete_input (bool *ptr, bool value) { if (value == *ptr) return; if (mk->system_handler.state == SystemHandler::STATE_ON) { if (ptr == &mk_dinput(momentary_flap_override)) { if (mk->configuration_module.state == ConfigurationModule::STATE_OK && mk->self_test_handler.state == SelfTestHandler::STATE_NONE && conf.momentary_flap_override_enabled && value) mk_doutput(flap_override) = ! mk_doutput(flap_override); } else if (ptr == &mk_dinput(self_test)) mk->self_test_handler.handle_button_event(value); else if (ptr == &mk_dinput(glideslope_cancel)) { if (mk->configuration_module.state == ConfigurationModule::STATE_OK && mk->self_test_handler.state == SelfTestHandler::STATE_NONE && value) { if (! mk_doutput(glideslope_cancel)) { // [SPEC] 6.2.5 // Although we are not called from update(), we are // sure that the inputs we use here are defined, // since state is STATE_ON. if (! mk_data(glideslope_deviation_dots).ncd && ! mk_data(radio_altitude).ncd && mk_data(radio_altitude).get() <= 2000 && mk_data(radio_altitude).get() >= 30) mk_doutput(glideslope_cancel) = true; } // else do nothing ([PILOT] page 22: "Glideslope Cancel // can not be deselected (reset) by again pressing the // Glideslope Cancel switch.") } } else if (ptr == &mk_dinput(steep_approach)) { if (mk->configuration_module.state == ConfigurationModule::STATE_OK && mk->self_test_handler.state == SelfTestHandler::STATE_NONE && momentary_steep_approach_enabled() && value) mk_doutput(steep_approach) = ! mk_doutput(steep_approach); } } *ptr = value; if (mk->system_handler.state == SystemHandler::STATE_ON) update_alternate_discrete_input(ptr); } void MK_VIII::IOHandler::present_status_section (const char *name) { printf("%s\n", name); } void MK_VIII::IOHandler::present_status_item (const char *name, const char *value) { if (value) printf("\t%-32s %s\n", name, value); else printf("\t%s\n", name); } void MK_VIII::IOHandler::present_status_subitem (const char *name) { printf("\t\t%s\n", name); } void MK_VIII::IOHandler::present_status () { // [SPEC] 6.10.13 if (mk->system_handler.state != SystemHandler::STATE_ON) return; present_status_section("EGPWC CONFIGURATION"); present_status_item("PART NUMBER:", "965-1220-000"); // [SPEC] 1.1 present_status_item("MOD STATUS:", "N/A"); present_status_item("SERIAL NUMBER:", "N/A"); printf("\n"); present_status_item("APPLICATION S/W VERSION:", FLIGHTGEAR_VERSION); present_status_item("TERRAIN DATABASE VERSION:", FLIGHTGEAR_VERSION); present_status_item("ENVELOPE MOD DATABASE VERSION:", "N/A"); present_status_item("BOOT CODE VERSION:", FLIGHTGEAR_VERSION); printf("\n"); present_status_section("CURRENT FAULTS"); if (mk->configuration_module.state == ConfigurationModule::STATE_OK && ! mk->fault_handler.has_faults()) present_status_item("NO FAULTS"); else { if (mk->configuration_module.state != ConfigurationModule::STATE_OK) { present_status_item("GPWS COMPUTER FAULTS:"); switch (mk->configuration_module.state) { case ConfigurationModule::STATE_INVALID_DATABASE: present_status_subitem("APPLICATION DATABASE FAILED"); break; case ConfigurationModule::STATE_INVALID_AIRCRAFT_TYPE: present_status_subitem("CONFIGURATION TYPE INVALID"); break; default: assert_not_reached(); break; } } else { present_status_item("GPWS COMPUTER OK"); present_status_item("GPWS EXTERNAL FAULTS:"); static const char *fault_names[] = { "ALL MODES INHIBIT", "GEAR SWITCH", "FLAPS SWITCH", "MOMENTARY FLAP OVERRIDE INVALID", "SELF TEST INVALID", "GLIDESLOPE CANCEL INVALID", "STEEP APPROACH INVALID", "GPWS INHIBIT", "TA & TCF INHIBIT", "MODES 1-4 INPUTS INVALID", "MODE 5 INPUTS INVALID", "MODE 6 INPUTS INVALID", "BANK ANGLE INPUTS INVALID", "TCF INPUTS INVALID" }; for (int i = 0; i < n_elements(fault_names); i++) if (mk->fault_handler.faults[i]) present_status_subitem(fault_names[i]); } } printf("\n"); present_status_section("CONFIGURATION:"); static const char *category_names[] = { "AIRCRAFT TYPE", "AIR DATA TYPE", "POSITION INPUT TYPE", "CALLOUTS OPTION TYPE", "AUDIO MENU TYPE", "TERRAIN DISPLAY TYPE", "OPTIONS 1 TYPE", "RADIO ALTITUDE TYPE", "NAVIGATION INPUT TYPE", "ATTITUDE TYPE", "MAGNETIC HEADING TYPE", "WINDSHEAR INPUT TYPE", "IO DISCRETE TYPE", "VOLUME SELECT" }; for (int i = 0; i < n_elements(category_names); i++) { std::ostringstream value; value << "= " << mk->configuration_module.effective_categories[i]; present_status_item(category_names[i], value.str().c_str()); } } bool MK_VIII::IOHandler::get_present_status () const { return false; } void MK_VIII::IOHandler::set_present_status (bool value) { if (value) present_status(); } /////////////////////////////////////////////////////////////////////////////// // VoicePlayer //////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////// void MK_VIII::VoicePlayer::Speaker::bind (SGPropertyNode *node) { // uses xmlsound property names tie(node, "volume", &volume); tie(node, "pitch", &pitch); tie(node, "position/x", &position[0]); tie(node, "position/y", &position[1]); tie(node, "position/z", &position[2]); tie(node, "orientation/x", &orientation[0]); tie(node, "orientation/y", &orientation[1]); tie(node, "orientation/z", &orientation[2]); tie(node, "orientation/inner-cone", &inner_cone); tie(node, "orientation/outer-cone", &outer_cone); tie(node, "reference-dist", &reference_dist); tie(node, "max-dist", &max_dist); } void MK_VIII::VoicePlayer::Speaker::update_configuration () { map::iterator iter; for (iter = player->samples.begin(); iter != player->samples.end(); iter++) { SGSoundSample *sample = (*iter).second; sample->set_pitch(pitch); sample->set_offset_pos(position); sample->set_orientation(orientation, inner_cone, outer_cone, outer_gain); sample->set_reference_dist(reference_dist); sample->set_max_dist(max_dist); } if (player->voice) player->voice->volume_changed(); } MK_VIII::VoicePlayer::Voice::~Voice () { for (iter = elements.begin(); iter != elements.end(); iter++) delete *iter; // we owned the element elements.clear(); } void MK_VIII::VoicePlayer::Voice::play () { iter = elements.begin(); element = *iter; element->play(get_volume()); } void MK_VIII::VoicePlayer::Voice::stop (bool now) { if (element) { if (now || element->silence) { element->stop(); element = NULL; } else iter = elements.end() - 1; // stop after the current element finishes } } void MK_VIII::VoicePlayer::Voice::set_volume (double _volume) { volume = _volume; volume_changed(); } void MK_VIII::VoicePlayer::Voice::volume_changed () { if (element) element->set_volume(get_volume()); } void MK_VIII::VoicePlayer::Voice::update () { if (element) { if (! element->is_playing()) { if (++iter == elements.end()) element = NULL; else { element = *iter; element->play(get_volume()); } } } } MK_VIII::VoicePlayer::~VoicePlayer () { vector::iterator iter1; for (iter1 = _voices.begin(); iter1 != _voices.end(); iter1++) delete *iter1; _voices.clear(); /* sound mgr already destroyed - samples already deleted map::iterator iter2; for (iter2 = samples.begin(); iter2 != samples.end(); iter2++) { bool status = globals->get_soundmgr()->remove((*iter2).first); assert(status); } */ samples.clear(); } void MK_VIII::VoicePlayer::init () { #define STDPAUSE 0.75 // [SPEC] 6.4.4: "the standard 0.75 second delay" make_voice(&voices.application_data_base_failed, "application-data-base-failed"); make_voice(&voices.bank_angle, "bank-angle"); make_voice(&voices.bank_angle_bank_angle, "bank-angle", "bank-angle"); make_voice(&voices.bank_angle_bank_angle_3, "bank-angle", "bank-angle", 3.0); make_voice(&voices.bank_angle_inop, "bank-angle-inop"); make_voice(&voices.bank_angle_pause_bank_angle, "bank-angle", STDPAUSE, "bank-angle"); make_voice(&voices.bank_angle_pause_bank_angle_3, "bank-angle", STDPAUSE, "bank-angle", 3.0); make_voice(&voices.callouts_inop, "callouts-inop"); make_voice(&voices.configuration_type_invalid, "configuration-type-invalid"); make_voice(&voices.dont_sink, "dont-sink"); make_voice(&voices.dont_sink_pause_dont_sink, "dont-sink", STDPAUSE, "dont-sink"); make_voice(&voices.five_hundred_above, "500-above"); make_voice(&voices.glideslope, "glideslope"); make_voice(&voices.glideslope_inop, "glideslope-inop"); make_voice(&voices.gpws_inop, "gpws-inop"); make_voice(&voices.hard_glideslope, "glideslope", "glideslope", 3.0); make_voice(&voices.minimums, "minimums"); make_voice(&voices.minimums_minimums, "minimums", "minimums"); make_voice(&voices.pull_up, "pull-up"); make_voice(&voices.sink_rate, "sink-rate"); make_voice(&voices.sink_rate_pause_sink_rate, "sink-rate", STDPAUSE, "sink-rate"); make_voice(&voices.soft_glideslope, new Voice::SampleElement(get_sample("glideslope"), modify_amplitude(1.0, -6))); make_voice(&voices.terrain, "terrain"); make_voice(&voices.terrain_pause_terrain, "terrain", STDPAUSE, "terrain"); make_voice(&voices.too_low_flaps, "too-low-flaps"); make_voice(&voices.too_low_gear, "too-low-gear"); make_voice(&voices.too_low_terrain, "too-low-terrain"); for (int i = 0; i < n_altitude_callouts; i++) { std::ostringstream name; name << "altitude-" << mk->mode6_handler.altitude_callout_definitions[i]; make_voice(&voices.altitude_callouts[i], name.str().c_str()); } speaker.update_configuration(); } SGSoundSample * MK_VIII::VoicePlayer::get_sample (const char *name) { std::ostringstream refname; refname << mk->name << "[" << mk->num << "]" << "/" << name; SGSoundMgr *soundmgr = globals->get_soundmgr(); if (soundmgr->is_working() == false) { return NULL; } SGSoundSample *sample = soundmgr->find(refname.str()); if (! sample) { SGPath sample_path(globals->get_fg_root()); sample_path.append("Sounds/mk-viii"); string filename = string(name) + ".wav"; try { sample = new SGSoundSample(sample_path.c_str(), filename.c_str()); } catch (const sg_exception &e) { SG_LOG(SG_INSTR, SG_ALERT, "Error loading MK VIII sound sample \"" + filename + "\": " + e.getFormattedMessage()); exit(1); } soundmgr->add(sample, refname.str()); samples[refname.str()] = sample; } return sample; } void MK_VIII::VoicePlayer::play (Voice *_voice, unsigned int flags) { if (test_bits(flags, PLAY_NOW) || ! voice || voice->element->silence) { if (voice) voice->stop(true); voice = _voice; looped = test_bits(flags, PLAY_LOOPED); next_voice = NULL; next_looped = false; voice->play(); } else { next_voice = _voice; next_looped = test_bits(flags, PLAY_LOOPED); } } void MK_VIII::VoicePlayer::stop (unsigned int flags) { if (voice) { voice->stop(test_bits(flags, STOP_NOW)); if (voice->element) looped = false; else voice = NULL; next_voice = NULL; } } void MK_VIII::VoicePlayer::set_volume (double _volume) { volume = _volume; if (voice) voice->volume_changed(); } void MK_VIII::VoicePlayer::update () { if (voice) { voice->update(); if (next_voice) { if (! voice->element || voice->element->silence) { voice = next_voice; looped = next_looped; next_voice = NULL; next_looped = false; voice->play(); } } else { if (! voice->element) { if (looped) voice->play(); else voice = NULL; } } } } /////////////////////////////////////////////////////////////////////////////// // SelfTestHandler //////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////// bool MK_VIII::SelfTestHandler::_was_here (int position) { if (position > current) { current = position; return false; } else return true; } MK_VIII::SelfTestHandler::Action MK_VIII::SelfTestHandler::sleep (double duration) { Action _action = { ACTION_SLEEP, duration, NULL }; return _action; } MK_VIII::SelfTestHandler::Action MK_VIII::SelfTestHandler::play (VoicePlayer::Voice *voice) { mk->voice_player.play(voice); Action _action = { ACTION_VOICE, 0, NULL }; return _action; } MK_VIII::SelfTestHandler::Action MK_VIII::SelfTestHandler::discrete_on (bool *discrete, double duration) { *discrete = true; return sleep(duration); } MK_VIII::SelfTestHandler::Action MK_VIII::SelfTestHandler::discrete_on_off (bool *discrete, double duration) { *discrete = true; Action _action = { ACTION_SLEEP | ACTION_DISCRETE_ON_OFF, duration, discrete }; return _action; } MK_VIII::SelfTestHandler::Action MK_VIII::SelfTestHandler::discrete_on_off (bool *discrete, VoicePlayer::Voice *voice) { *discrete = true; mk->voice_player.play(voice); Action _action = { ACTION_VOICE | ACTION_DISCRETE_ON_OFF, 0, discrete }; return _action; } MK_VIII::SelfTestHandler::Action MK_VIII::SelfTestHandler::done () { Action _action = { ACTION_DONE, 0, NULL }; return _action; } MK_VIII::SelfTestHandler::Action MK_VIII::SelfTestHandler::run () { // Note that "Terrain INOP" and "Terrain NA" are or'ed to the same // output discrete (see [SPEC] 6.9.3.5). #define was_here() was_here_offset(0) #define was_here_offset(offset) _was_here(__LINE__ * 20 + (offset)) if (! was_here()) { if (mk->configuration_module.state == ConfigurationModule::STATE_INVALID_DATABASE) return play(mk_voice(application_data_base_failed)); else if (mk->configuration_module.state == ConfigurationModule::STATE_INVALID_AIRCRAFT_TYPE) return play(mk_voice(configuration_type_invalid)); } if (mk->configuration_module.state != ConfigurationModule::STATE_OK) return done(); if (! was_here()) return discrete_on(&mk_doutput(gpws_inop), 0.7); if (! was_here()) return sleep(0.7); // W/S INOP if (! was_here()) return discrete_on(&mk_doutput(tad_inop), 0.7); if (! was_here()) { mk_doutput(gpws_inop) = false; // do not disable tad_inop since we must enable Terrain NA // do not disable W/S INOP since we do not emulate it return sleep(0.7); // Terrain NA } if (! was_here()) { mk_doutput(tad_inop) = false; // disable Terrain NA if (mk->io_handler.conf.momentary_flap_override_enabled) return discrete_on_off(&mk_doutput(flap_override), 1.0); } if (! was_here()) return discrete_on_off(&mk_doutput(audio_on), 1.0); if (! was_here()) { if (mk->io_handler.momentary_steep_approach_enabled()) return discrete_on_off(&mk_doutput(steep_approach), 1.0); } if (! was_here()) { if (mk_dinput(glideslope_inhibit)) goto glideslope_end; else { if (mk->fault_handler.faults[FaultHandler::FAULT_MODE5_INPUTS_INVALID]) goto glideslope_inop; } } if (! was_here()) return discrete_on_off(&mk_doutput(gpws_alert), mk_voice(glideslope)); if (! was_here()) return discrete_on_off(&mk_doutput(glideslope_cancel), 0.7); goto glideslope_end; glideslope_inop: if (! was_here()) return play(mk_voice(glideslope_inop)); glideslope_end: if (! was_here()) { if (mk->fault_handler.faults[FaultHandler::FAULT_MODES14_INPUTS_INVALID]) goto gpws_inop; } if (! was_here()) return discrete_on_off(&mk_doutput(gpws_warning), mk_voice(pull_up)); goto gpws_end; gpws_inop: if (! was_here()) return play(mk_voice(gpws_inop)); gpws_end: if (! was_here()) { if (mk_dinput(self_test)) // proceed to long self test goto long_test; } if (! was_here()) { if (mk->mode6_handler.conf.bank_angle_enabled && mk->fault_handler.faults[FaultHandler::FAULT_BANK_ANGLE_INPUTS_INVALID]) return play(mk_voice(bank_angle_inop)); } if (! was_here()) { if (mk->mode6_handler.altitude_callouts_enabled() && mk->fault_handler.faults[FaultHandler::FAULT_MODE6_INPUTS_INVALID]) return play(mk_voice(callouts_inop)); } if (! was_here()) return done(); long_test: if (! was_here()) { mk_doutput(gpws_inop) = true; // do not enable W/S INOP, since we do not emulate it mk_doutput(tad_inop) = true; // Terrain INOP, Terrain NA return play(mk_voice(sink_rate)); } if (! was_here()) return play(mk_voice(pull_up)); if (! was_here()) return play(mk_voice(terrain)); if (! was_here()) return play(mk_voice(pull_up)); if (! was_here()) return play(mk_voice(dont_sink)); if (! was_here()) return play(mk_voice(too_low_terrain)); if (! was_here()) return play(mk->mode4_handler.conf.voice_too_low_gear); if (! was_here()) return play(mk_voice(too_low_flaps)); if (! was_here()) return play(mk_voice(too_low_terrain)); if (! was_here()) return play(mk_voice(glideslope)); if (! was_here()) { if (mk->mode6_handler.conf.bank_angle_enabled) return play(mk_voice(bank_angle)); } if (! was_here()) { if (! mk->mode6_handler.altitude_callouts_enabled()) goto callouts_disabled; } if (! was_here()) { if (mk->mode6_handler.conf.minimums_enabled) return play(mk_voice(minimums)); } if (! was_here()) { if (mk->mode6_handler.conf.above_field_voice) return play(mk->mode6_handler.conf.above_field_voice); } for (int i = 0; i < n_altitude_callouts; i++) if (! was_here_offset(i)) { if (mk->mode6_handler.conf.altitude_callouts_enabled[i]) return play(mk_altitude_voice(i)); } if (! was_here()) { if (mk->mode6_handler.conf.smart_500_enabled) return play(mk_altitude_voice(Mode6Handler::ALTITUDE_CALLOUT_500)); } goto callouts_end; callouts_disabled: if (! was_here()) return play(mk_voice(minimums_minimums)); callouts_end: if (! was_here()) { if (mk->tcf_handler.conf.enabled) return play(mk_voice(too_low_terrain)); } return done(); } void MK_VIII::SelfTestHandler::start () { assert(state == STATE_START); memcpy(&saved_outputs, &mk->io_handler.outputs, sizeof(mk->io_handler.outputs)); memset(&mk->io_handler.outputs, 0, sizeof(mk->io_handler.outputs)); // [SPEC] 6.10.6: "The self-test results are annunciated, at 6db // lower than the normal audio level selected for the aircraft" mk->voice_player.set_volume(modify_amplitude(mk->voice_player.conf.volume, -6)); if (mk_dinput(mode6_low_volume)) mk->mode6_handler.set_volume(1.0); state = STATE_RUNNING; cancel = CANCEL_NONE; memset(&action, 0, sizeof(action)); current = 0; } void MK_VIII::SelfTestHandler::stop () { if (state != STATE_NONE) { if (state != STATE_START) { mk->voice_player.stop(VoicePlayer::STOP_NOW); mk->voice_player.set_volume(mk_dinput(audio_inhibit) ? 0.0 : mk->voice_player.conf.volume); if (mk_dinput(mode6_low_volume)) mk->mode6_handler.set_volume(modify_amplitude(1.0, -6)); memcpy(&mk->io_handler.outputs, &saved_outputs, sizeof(mk->io_handler.outputs)); } button_pressed = false; state = STATE_NONE; } } void MK_VIII::SelfTestHandler::handle_button_event (bool value) { if (state == STATE_NONE) { if (value) state = STATE_START; } else if (state == STATE_START) { if (value) state = STATE_NONE; // cancel the not-yet-started test } else if (cancel == CANCEL_NONE) { if (value) { assert(! button_pressed); button_pressed = true; button_press_timestamp = globals->get_sim_time_sec(); } else { if (button_pressed) { if (globals->get_sim_time_sec() - button_press_timestamp < 2) cancel = CANCEL_SHORT; else cancel = CANCEL_LONG; } } } } bool MK_VIII::SelfTestHandler::update () { if (state == STATE_NONE) return false; else if (state == STATE_START) { if (mk->state_handler.ground && ! mk->io_handler.steep_approach()) start(); else { state = STATE_NONE; return false; } } else { if (button_pressed && cancel == CANCEL_NONE) { if (globals->get_sim_time_sec() - button_press_timestamp >= 2) cancel = CANCEL_LONG; } } if (! mk->state_handler.ground || cancel != CANCEL_NONE) { stop(); return false; } if (test_bits(action.flags, ACTION_SLEEP)) { if (globals->get_sim_time_sec() - sleep_start < action.sleep_duration) return true; } if (test_bits(action.flags, ACTION_VOICE)) { if (mk->voice_player.voice) return true; } if (test_bits(action.flags, ACTION_DISCRETE_ON_OFF)) *action.discrete = false; action = run(); if (test_bits(action.flags, ACTION_SLEEP)) sleep_start = globals->get_sim_time_sec(); if (test_bits(action.flags, ACTION_DONE)) { stop(); return false; } return true; } /////////////////////////////////////////////////////////////////////////////// // AlertHandler /////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////// bool MK_VIII::AlertHandler::select_voice_alerts (unsigned int test) { if (has_alerts(test)) { voice_alerts = alerts & test; return true; } else { voice_alerts &= ~test; if (voice_alerts == 0) mk->voice_player.stop(); return false; } } void MK_VIII::AlertHandler::boot () { reset(); } void MK_VIII::AlertHandler::reposition () { reset(); mk->io_handler.set_lamp(IOHandler::LAMP_NONE); mk->voice_player.stop(VoicePlayer::STOP_NOW); } void MK_VIII::AlertHandler::reset () { alerts = 0; old_alerts = 0; voice_alerts = 0; repeated_alerts = 0; altitude_callout_voice = NULL; } void MK_VIII::AlertHandler::update () { if (mk->configuration_module.state != ConfigurationModule::STATE_OK) return; // Lamps and voices are prioritized according to [SPEC] 6.9.2. // // Voices which can interrupt other voices (VoicePlayer::PLAY_NOW) // are specified by [INSTALL] appendix E 5.3.5. // // When a voice is overriden by a higher priority voice and the // overriding voice stops, we restore the previous voice if it was // looped (this is not specified by [SPEC] but seems to make sense). // update lamp if (has_alerts(ALERT_MODE1_PULL_UP | ALERT_MODE2A | ALERT_MODE2B)) mk->io_handler.set_lamp(IOHandler::LAMP_WARNING); else if (has_alerts(ALERT_MODE1_SINK_RATE | ALERT_MODE2A_PREFACE | ALERT_MODE2B_PREFACE | ALERT_MODE2A_ALTITUDE_GAIN_TERRAIN_CLOSING | ALERT_MODE2A_ALTITUDE_GAIN | ALERT_MODE2B_LANDING_MODE | ALERT_MODE3 | ALERT_MODE4_TOO_LOW_FLAPS | ALERT_MODE4_TOO_LOW_GEAR | ALERT_MODE4AB_TOO_LOW_TERRAIN | ALERT_MODE4C_TOO_LOW_TERRAIN | ALERT_TCF_TOO_LOW_TERRAIN)) mk->io_handler.set_lamp(IOHandler::LAMP_CAUTION); else if (has_alerts(ALERT_MODE5_SOFT | ALERT_MODE5_HARD)) mk->io_handler.set_lamp(IOHandler::LAMP_GLIDESLOPE); else mk->io_handler.set_lamp(IOHandler::LAMP_NONE); // update voice if (select_voice_alerts(ALERT_MODE1_PULL_UP)) { if (! has_old_alerts(ALERT_MODE1_PULL_UP)) { if (mk->voice_player.voice != mk_voice(sink_rate_pause_sink_rate)) mk->voice_player.play(mk_voice(sink_rate), VoicePlayer::PLAY_NOW); mk->voice_player.play(mk_voice(pull_up), VoicePlayer::PLAY_LOOPED); } } else if (select_voice_alerts(ALERT_MODE2A_PREFACE | ALERT_MODE2B_PREFACE)) { if (! has_old_alerts(ALERT_MODE2A_PREFACE | ALERT_MODE2B_PREFACE)) mk->voice_player.play(mk_voice(terrain_pause_terrain), VoicePlayer::PLAY_NOW); } else if (select_voice_alerts(ALERT_MODE2A | ALERT_MODE2B)) { if (mk->voice_player.voice != mk_voice(pull_up)) mk->voice_player.play(mk_voice(pull_up), VoicePlayer::PLAY_NOW | VoicePlayer::PLAY_LOOPED); } else if (select_voice_alerts(ALERT_MODE2A_ALTITUDE_GAIN_TERRAIN_CLOSING | ALERT_MODE2B_LANDING_MODE)) { if (mk->voice_player.voice != mk_voice(terrain)) mk->voice_player.play(mk_voice(terrain), VoicePlayer::PLAY_LOOPED); } else if (select_voice_alerts(ALERT_MODE6_MINIMUMS)) { if (! has_old_alerts(ALERT_MODE6_MINIMUMS)) mk->voice_player.play(mk_voice(minimums_minimums)); } else if (select_voice_alerts(ALERT_MODE4AB_TOO_LOW_TERRAIN | ALERT_MODE4C_TOO_LOW_TERRAIN)) { if (must_play_voice(ALERT_MODE4AB_TOO_LOW_TERRAIN | ALERT_MODE4C_TOO_LOW_TERRAIN)) mk->voice_player.play(mk_voice(too_low_terrain)); } else if (select_voice_alerts(ALERT_TCF_TOO_LOW_TERRAIN)) { if (must_play_voice(ALERT_TCF_TOO_LOW_TERRAIN)) mk->voice_player.play(mk_voice(too_low_terrain)); } else if (select_voice_alerts(ALERT_MODE6_ALTITUDE_CALLOUT)) { if (! has_old_alerts(ALERT_MODE6_ALTITUDE_CALLOUT)) { assert(altitude_callout_voice != NULL); mk->voice_player.play(altitude_callout_voice); } } else if (select_voice_alerts(ALERT_MODE4_TOO_LOW_GEAR)) { if (must_play_voice(ALERT_MODE4_TOO_LOW_GEAR)) mk->voice_player.play(mk->mode4_handler.conf.voice_too_low_gear); } else if (select_voice_alerts(ALERT_MODE4_TOO_LOW_FLAPS)) { if (must_play_voice(ALERT_MODE4_TOO_LOW_FLAPS)) mk->voice_player.play(mk_voice(too_low_flaps)); } else if (select_voice_alerts(ALERT_MODE1_SINK_RATE)) { if (must_play_voice(ALERT_MODE1_SINK_RATE)) mk->voice_player.play(mk_voice(sink_rate_pause_sink_rate)); // [SPEC] 6.2.1: "During the time that the voice message for the // outer envelope is inhibited and the caution/warning lamp is // on, the Mode 5 alert message is allowed to annunciate for // excessive glideslope deviation conditions." else if (mk->voice_player.voice != mk_voice(sink_rate_pause_sink_rate) && mk->voice_player.next_voice != mk_voice(sink_rate_pause_sink_rate)) { if (has_alerts(ALERT_MODE5_HARD)) { voice_alerts |= ALERT_MODE5_HARD; if (mk->voice_player.voice != mk_voice(hard_glideslope)) mk->voice_player.play(mk_voice(hard_glideslope), VoicePlayer::PLAY_LOOPED); } else if (has_alerts(ALERT_MODE5_SOFT)) { voice_alerts |= ALERT_MODE5_SOFT; if (must_play_voice(ALERT_MODE5_SOFT)) mk->voice_player.play(mk_voice(soft_glideslope)); } } } else if (select_voice_alerts(ALERT_MODE3)) { if (must_play_voice(ALERT_MODE3)) mk->voice_player.play(mk_voice(dont_sink_pause_dont_sink)); } else if (select_voice_alerts(ALERT_MODE5_HARD)) { if (mk->voice_player.voice != mk_voice(hard_glideslope)) mk->voice_player.play(mk_voice(hard_glideslope), VoicePlayer::PLAY_LOOPED); } else if (select_voice_alerts(ALERT_MODE5_SOFT)) { if (must_play_voice(ALERT_MODE5_SOFT)) mk->voice_player.play(mk_voice(soft_glideslope)); } else if (select_voice_alerts(ALERT_MODE6_LOW_BANK_ANGLE_3)) { if (mk->voice_player.voice != mk_voice(bank_angle_bank_angle_3)) mk->voice_player.play(mk_voice(bank_angle_bank_angle_3), VoicePlayer::PLAY_LOOPED); } else if (select_voice_alerts(ALERT_MODE6_HIGH_BANK_ANGLE_3)) { if (mk->voice_player.voice != mk_voice(bank_angle_pause_bank_angle_3)) mk->voice_player.play(mk_voice(bank_angle_pause_bank_angle_3), VoicePlayer::PLAY_LOOPED); } else if (select_voice_alerts(ALERT_MODE6_LOW_BANK_ANGLE_2)) { if (! has_old_alerts(ALERT_MODE6_LOW_BANK_ANGLE_2 | ALERT_MODE6_HIGH_BANK_ANGLE_2)) mk->voice_player.play(mk_voice(bank_angle_bank_angle)); } else if (select_voice_alerts(ALERT_MODE6_HIGH_BANK_ANGLE_2)) { if (! has_old_alerts(ALERT_MODE6_LOW_BANK_ANGLE_2 | ALERT_MODE6_HIGH_BANK_ANGLE_2)) mk->voice_player.play(mk_voice(bank_angle_pause_bank_angle)); } else if (select_voice_alerts(ALERT_MODE6_LOW_BANK_ANGLE_1)) { if (! has_old_alerts(ALERT_MODE6_LOW_BANK_ANGLE_1 | ALERT_MODE6_HIGH_BANK_ANGLE_1)) mk->voice_player.play(mk_voice(bank_angle_bank_angle)); } else if (select_voice_alerts(ALERT_MODE6_HIGH_BANK_ANGLE_1)) { if (! has_old_alerts(ALERT_MODE6_LOW_BANK_ANGLE_1 | ALERT_MODE6_HIGH_BANK_ANGLE_1)) mk->voice_player.play(mk_voice(bank_angle_pause_bank_angle)); } // set new state old_alerts = alerts; repeated_alerts = 0; altitude_callout_voice = NULL; } void MK_VIII::AlertHandler::set_alerts (unsigned int _alerts, unsigned int flags, VoicePlayer::Voice *_altitude_callout_voice) { alerts |= _alerts; if (test_bits(flags, ALERT_FLAG_REPEAT)) repeated_alerts |= _alerts; if (_altitude_callout_voice) altitude_callout_voice = _altitude_callout_voice; } void MK_VIII::AlertHandler::unset_alerts (unsigned int _alerts) { alerts &= ~_alerts; repeated_alerts &= ~_alerts; } /////////////////////////////////////////////////////////////////////////////// // StateHandler /////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////// void MK_VIII::StateHandler::update_ground () { if (ground) { if (! mk_ainput(computed_airspeed).ncd && mk_ainput(computed_airspeed).get() > 60 && ! mk_data(radio_altitude).ncd && mk_data(radio_altitude).get() > 30) { if (potentially_airborne_timer.start_or_elapsed() > 1) leave_ground(); } else potentially_airborne_timer.stop(); } else { if (! mk_ainput(computed_airspeed).ncd && mk_ainput(computed_airspeed).get() < 40 && ! mk_data(radio_altitude).ncd && mk_data(radio_altitude).get() < 30) enter_ground(); } } void MK_VIII::StateHandler::enter_ground () { ground = true; mk->io_handler.enter_ground(); // [SPEC] 6.0.1 does not specify this, but it seems to be an // omission; at this point, we must make sure that we are in takeoff // mode (otherwise the next takeoff might happen in approach mode). if (! takeoff) enter_takeoff(); } void MK_VIII::StateHandler::leave_ground () { ground = false; mk->mode2_handler.leave_ground(); } void MK_VIII::StateHandler::update_takeoff () { if (takeoff) { // [SPEC] 6.0.2 is somewhat confusing: it mentions hardcoded // terrain clearance (500, 750) and airspeed (178, 200) values, // but it also mentions the mode 4A boundary, which varies as a // function of aircraft type. We consider that the mentioned // values are examples, and that we should use the mode 4A upper // boundary. if (! mk_data(terrain_clearance).ncd && ! mk_ainput(computed_airspeed).ncd && mk_data(terrain_clearance).get() > mk->mode4_handler.get_upper_agl(mk->mode4_handler.conf.modes->ac)) leave_takeoff(); } else { if (! mk_data(radio_altitude).ncd && mk_data(radio_altitude).get() < mk->mode4_handler.conf.modes->b->min_agl1 && mk->io_handler.flaps_down() && mk_dinput(landing_gear)) enter_takeoff(); } } void MK_VIII::StateHandler::enter_takeoff () { takeoff = true; mk->io_handler.enter_takeoff(); mk->mode2_handler.enter_takeoff(); mk->mode3_handler.enter_takeoff(); mk->mode6_handler.enter_takeoff(); } void MK_VIII::StateHandler::leave_takeoff () { takeoff = false; mk->mode6_handler.leave_takeoff(); } void MK_VIII::StateHandler::post_reposition () { // Synchronize the state with the current situation. bool _ground = ! (! mk_ainput(computed_airspeed).ncd && mk_ainput(computed_airspeed).get() > 60 && ! mk_data(radio_altitude).ncd && mk_data(radio_altitude).get() > 30); bool _takeoff = _ground; if (ground && ! _ground) leave_ground(); else if (! ground && _ground) enter_ground(); if (takeoff && ! _takeoff) leave_takeoff(); else if (! takeoff && _takeoff) enter_takeoff(); } void MK_VIII::StateHandler::update () { if (mk->configuration_module.state != ConfigurationModule::STATE_OK) return; update_ground(); update_takeoff(); } /////////////////////////////////////////////////////////////////////////////// // Mode1Handler /////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////// double MK_VIII::Mode1Handler::get_pull_up_bias () { // [PILOT] page 54: "Steep Approach has priority over Flap Override // if selected simultaneously." if (mk->io_handler.steep_approach()) return 200; else if (conf.envelopes->flap_override_bias && mk->io_handler.flap_override()) return 300; else return 0; } bool MK_VIII::Mode1Handler::is_pull_up () { if (! mk->io_handler.gpws_inhibit() && ! mk->state_handler.ground // [1] && ! mk_data(radio_altitude).ncd && ! mk_data(barometric_altitude_rate).ncd && mk_data(radio_altitude).get() > conf.envelopes->min_agl) { if (mk_data(radio_altitude).get() < conf.envelopes->pull_up_max_agl1) { if (mk_data(radio_altitude).get() < conf.envelopes->pull_up_min_agl1(mk_data(barometric_altitude_rate).get() + get_pull_up_bias())) return true; } else if (mk_data(radio_altitude).get() < conf.envelopes->pull_up_max_agl2) { if (mk_data(radio_altitude).get() < conf.envelopes->pull_up_min_agl2(mk_data(barometric_altitude_rate).get() + get_pull_up_bias())) return true; } } return false; } void MK_VIII::Mode1Handler::update_pull_up () { if (is_pull_up()) { if (! mk_test_alert(MODE1_PULL_UP)) { // [SPEC] 6.2.1: at least one sink rate must be issued // before switching to pull up; if no sink rate has // occurred, a 0.2 second delay is used. if (mk->voice_player.voice == mk_voice(sink_rate_pause_sink_rate) || pull_up_timer.start_or_elapsed() >= 0.2) mk_set_alerts(mk_alert(MODE1_PULL_UP)); } } else { pull_up_timer.stop(); mk_unset_alerts(mk_alert(MODE1_PULL_UP)); } } double MK_VIII::Mode1Handler::get_sink_rate_bias () { // [PILOT] page 54: "Steep Approach has priority over Flap Override // if selected simultaneously." if (mk->io_handler.steep_approach()) return 500; else if (conf.envelopes->flap_override_bias && mk->io_handler.flap_override()) return 300; else if (! mk_data(glideslope_deviation_dots).ncd) { double bias = 0; if (mk_data(glideslope_deviation_dots).get() <= -2) bias = 300; else if (mk_data(glideslope_deviation_dots).get() < 0) bias = -150 * mk_data(glideslope_deviation_dots).get(); if (mk_data(radio_altitude).get() < 100) bias *= 0.01 * mk_data(radio_altitude).get(); return bias; } else return 0; } bool MK_VIII::Mode1Handler::is_sink_rate () { return ! mk->io_handler.gpws_inhibit() && ! mk->state_handler.ground // [1] && ! mk_data(radio_altitude).ncd && ! mk_data(barometric_altitude_rate).ncd && mk_data(radio_altitude).get() > conf.envelopes->min_agl && mk_data(radio_altitude).get() < 2450 && mk_data(radio_altitude).get() < -572 - 0.6035 * (mk_data(barometric_altitude_rate).get() + get_sink_rate_bias()); } double MK_VIII::Mode1Handler::get_sink_rate_tti () { return mk_data(radio_altitude).get() / fabs(mk_data(barometric_altitude_rate).get()); } void MK_VIII::Mode1Handler::update_sink_rate () { if (is_sink_rate()) { if (mk_test_alert(MODE1_SINK_RATE)) { double tti = get_sink_rate_tti(); if (tti <= sink_rate_tti * 0.8) { // ~20% degradation, warn again and store the new reference tti sink_rate_tti = tti; mk_repeat_alert(mk_alert(MODE1_SINK_RATE)); } } else { if (sink_rate_timer.start_or_elapsed() >= 0.8) { mk_set_alerts(mk_alert(MODE1_SINK_RATE)); sink_rate_tti = get_sink_rate_tti(); } } } else { sink_rate_timer.stop(); mk_unset_alerts(mk_alert(MODE1_SINK_RATE)); } } void MK_VIII::Mode1Handler::update () { if (mk->configuration_module.state != ConfigurationModule::STATE_OK) return; update_pull_up(); update_sink_rate(); } /////////////////////////////////////////////////////////////////////////////// // Mode2Handler /////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////// double MK_VIII::Mode2Handler::ClosureRateFilter::limit_radio_altitude_rate (double r) { // Limit radio altitude rate according to aircraft configuration, // allowing maximum sensitivity during cruise while providing // progressively less sensitivity during the landing phases of // flight. if (! mk_data(glideslope_deviation_dots).ncd && fabs(mk_data(glideslope_deviation_dots).get()) <= 2) { // gs deviation <= +- 2 dots if (mk_dinput(landing_gear) && mk->io_handler.flaps_down()) SG_CLAMP_RANGE(r, -1000.0, 3000.0); else if (mk_dinput(landing_gear) || mk->io_handler.flaps_down()) SG_CLAMP_RANGE(r, 0.0, 4000.0); else SG_CLAMP_RANGE(r, 1000.0, 5000.0); } else { // no ILS, or gs deviation > +- 2 dots if (mk_dinput(landing_gear) && mk->io_handler.flaps_down()) SG_CLAMP_RANGE(r, 0.0, 4000.0); else if (mk_dinput(landing_gear) || mk->io_handler.flaps_down()) SG_CLAMP_RANGE(r, 1000.0, 5000.0); // else no clamp } return r; } void MK_VIII::Mode2Handler::ClosureRateFilter::init () { timer.stop(); last_ra.set(&mk_data(radio_altitude)); last_ba.set(&mk_ainput(uncorrected_barometric_altitude)); ra_filter.reset(); ba_filter.reset(); output.unset(); } void MK_VIII::Mode2Handler::ClosureRateFilter::update () { double elapsed = timer.start_or_elapsed(); if (elapsed < 1) return; if (! mk_data(radio_altitude).ncd && ! mk_ainput(uncorrected_barometric_altitude).ncd) { if (! last_ra.ncd && ! last_ba.ncd) { // compute radio and barometric altitude rates (positive value = descent) double ra_rate = -(mk_data(radio_altitude).get() - last_ra.get()) / elapsed * 60; double ba_rate = -(mk_ainput(uncorrected_barometric_altitude).get() - last_ba.get()) / elapsed * 60; // limit radio altitude rate according to aircraft configuration ra_rate = limit_radio_altitude_rate(ra_rate); // apply a low-pass filter to the radio altitude rate ra_rate = ra_filter.filter(ra_rate); // apply a high-pass filter to the barometric altitude rate ba_rate = ba_filter.filter(ba_rate); // combine both rates to obtain a closure rate output.set(ra_rate + ba_rate); } else output.unset(); } else { ra_filter.reset(); ba_filter.reset(); output.unset(); } timer.start(); last_ra.set(&mk_data(radio_altitude)); last_ba.set(&mk_ainput(uncorrected_barometric_altitude)); } bool MK_VIII::Mode2Handler::b_conditions () { return mk->io_handler.flaps_down() || (! mk_data(glideslope_deviation_dots).ncd && fabs(mk_data(glideslope_deviation_dots).get()) < 2) || takeoff_timer.running; } bool MK_VIII::Mode2Handler::is_a () { if (! mk->io_handler.gpws_inhibit() && ! mk->state_handler.ground // [1] && ! mk_data(radio_altitude).ncd && ! mk_ainput(computed_airspeed).ncd && ! closure_rate_filter.output.ncd && ! b_conditions()) { if (mk_data(radio_altitude).get() < 1220) { if (mk_data(radio_altitude).get() < -1579 + 0.7895 * closure_rate_filter.output.get()) return true; } else { double upper_limit; if (mk_ainput(computed_airspeed).get() <= conf->airspeed1) upper_limit = 1650; else if (mk_ainput(computed_airspeed).get() >= conf->airspeed2) upper_limit = 2450; else upper_limit = 1650 + 8.9 * (mk_ainput(computed_airspeed).get() - conf->airspeed1); if (mk_data(radio_altitude).get() < upper_limit) { if (mk_data(radio_altitude).get() < 522 + 0.1968 * closure_rate_filter.output.get()) return true; } } } return false; } bool MK_VIII::Mode2Handler::is_b () { if (! mk->io_handler.gpws_inhibit() && ! mk->state_handler.ground // [1] && ! mk_data(radio_altitude).ncd && ! mk_data(barometric_altitude_rate).ncd && ! closure_rate_filter.output.ncd && b_conditions() && mk_data(radio_altitude).get() < 789) { double lower_limit; if (mk->io_handler.flaps_down()) { if (mk_data(barometric_altitude_rate).get() > -400) lower_limit = 200; else if (mk_data(barometric_altitude_rate).get() < -1000) lower_limit = 600; else lower_limit = -66.777 - 0.667 * mk_data(barometric_altitude_rate).get(); } else lower_limit = 30; if (mk_data(radio_altitude).get() > lower_limit) { if (mk_data(radio_altitude).get() < -1579 + 0.7895 * closure_rate_filter.output.get()) return true; } } return false; } void MK_VIII::Mode2Handler::check_pull_up (unsigned int preface_alert, unsigned int alert) { if (pull_up_timer.running) { if (pull_up_timer.elapsed() >= 1) { mk_unset_alerts(preface_alert); mk_set_alerts(alert); } } else { if (! mk->voice_player.voice) pull_up_timer.start(); } } void MK_VIII::Mode2Handler::update_a () { if (is_a()) { if (mk_test_alert(MODE2A_PREFACE)) check_pull_up(mk_alert(MODE2A_PREFACE), mk_alert(MODE2A)); else if (! mk_test_alert(MODE2A)) { mk_unset_alerts(mk_alert(MODE2A_ALTITUDE_GAIN) | mk_alert(MODE2A_ALTITUDE_GAIN_TERRAIN_CLOSING)); mk_set_alerts(mk_alert(MODE2A_PREFACE)); a_start_time = globals->get_sim_time_sec(); pull_up_timer.stop(); } } else { if (mk_test_alert(MODE2A_ALTITUDE_GAIN)) { if (mk->io_handler.gpws_inhibit() || mk->state_handler.ground // [1] || a_altitude_gain_timer.elapsed() >= 45 || mk_data(radio_altitude).ncd || mk_ainput(uncorrected_barometric_altitude).ncd || mk_ainput(uncorrected_barometric_altitude).get() - a_altitude_gain_alt >= 300 // [PILOT] page 12: "the visual alert will remain on // until [...] landing flaps or the flap override switch // is activated" || mk->io_handler.flaps_down()) { // exit altitude gain mode a_altitude_gain_timer.stop(); mk_unset_alerts(mk_alert(MODE2A_ALTITUDE_GAIN) | mk_alert(MODE2A_ALTITUDE_GAIN_TERRAIN_CLOSING)); } else { // [SPEC] 6.2.2.2: "If the terrain starts to fall away // during this altitude gain time, the voice will shut // off" if (closure_rate_filter.output.get() < 0) mk_unset_alerts(mk_alert(MODE2A_ALTITUDE_GAIN_TERRAIN_CLOSING)); } } else if (mk_test_alerts(mk_alert(MODE2A_PREFACE) | mk_alert(MODE2A))) { mk_unset_alerts(mk_alert(MODE2A_PREFACE) | mk_alert(MODE2A)); if (! mk->io_handler.gpws_inhibit() && ! mk->state_handler.ground // [1] && globals->get_sim_time_sec() - a_start_time > 3 && ! mk->io_handler.flaps_down() && ! mk_data(radio_altitude).ncd && ! mk_ainput(uncorrected_barometric_altitude).ncd) { // [SPEC] 6.2.2.2: mode 2A envelope violated for more // than 3 seconds, enable altitude gain feature a_altitude_gain_timer.start(); a_altitude_gain_alt = mk_ainput(uncorrected_barometric_altitude).get(); unsigned int alerts = mk_alert(MODE2A_ALTITUDE_GAIN); if (closure_rate_filter.output.get() > 0) alerts |= mk_alert(MODE2A_ALTITUDE_GAIN_TERRAIN_CLOSING); mk_set_alerts(alerts); } } } } void MK_VIII::Mode2Handler::update_b () { bool b = is_b(); // handle normal mode if (b && (! mk_dinput(landing_gear) || ! mk->io_handler.flaps_down())) { if (mk_test_alert(MODE2B_PREFACE)) check_pull_up(mk_alert(MODE2B_PREFACE), mk_alert(MODE2B)); else if (! mk_test_alert(MODE2B)) { mk_set_alerts(mk_alert(MODE2B_PREFACE)); pull_up_timer.stop(); } } else mk_unset_alerts(mk_alert(MODE2B_PREFACE) | mk_alert(MODE2B)); // handle landing mode ([SPEC] 6.2.2.3: "If both landing gear and // flaps are in the landing configuration, then the message will be // Terrain") if (b && mk_dinput(landing_gear) && mk->io_handler.flaps_down()) mk_set_alerts(mk_alert(MODE2B_LANDING_MODE)); else mk_unset_alerts(mk_alert(MODE2B_LANDING_MODE)); } void MK_VIII::Mode2Handler::boot () { closure_rate_filter.init(); } void MK_VIII::Mode2Handler::power_off () { // [SPEC] 6.0.4: "This latching function is not power saved and a // system reset will force it false." takeoff_timer.stop(); } void MK_VIII::Mode2Handler::leave_ground () { // takeoff, reset timer takeoff_timer.start(); } void MK_VIII::Mode2Handler::enter_takeoff () { // reset timer, in case it's a go-around takeoff_timer.start(); } void MK_VIII::Mode2Handler::update () { if (mk->configuration_module.state != ConfigurationModule::STATE_OK) return; closure_rate_filter.update(); if (takeoff_timer.running && takeoff_timer.elapsed() >= 60) takeoff_timer.stop(); update_a(); update_b(); } /////////////////////////////////////////////////////////////////////////////// // Mode3Handler /////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////// double MK_VIII::Mode3Handler::max_alt_loss (double _bias) { return conf->max_alt_loss(mk->io_handler.flap_override(), mk_data(radio_altitude).get()) + mk_data(radio_altitude).get() * _bias; } double MK_VIII::Mode3Handler::get_bias (double initial_bias, double alt_loss) { if (mk_data(radio_altitude).get() > 0) while (alt_loss > max_alt_loss(initial_bias)) initial_bias += 0.2; return initial_bias; } bool MK_VIII::Mode3Handler::is (double *alt_loss) { if (has_descent_alt) { int max_agl = conf->max_agl(mk->io_handler.flap_override()); if (mk_ainput(uncorrected_barometric_altitude).ncd || mk_ainput(uncorrected_barometric_altitude).get() > descent_alt || mk_data(radio_altitude).ncd || mk_data(radio_altitude).get() > max_agl) { armed = false; has_descent_alt = false; } else { // gear/flaps: [SPEC] 1.3.1.3 if (! mk->io_handler.gpws_inhibit() && ! mk->state_handler.ground // [1] && (! mk_dinput(landing_gear) || ! mk->io_handler.flaps_down()) && ! mk_data(barometric_altitude_rate).ncd && ! mk_ainput(uncorrected_barometric_altitude).ncd && ! mk_data(radio_altitude).ncd && mk_data(barometric_altitude_rate).get() < 0) { double _alt_loss = descent_alt - mk_ainput(uncorrected_barometric_altitude).get(); if (armed || (mk_data(radio_altitude).get() > conf->min_agl && mk_data(radio_altitude).get() < max_agl && _alt_loss > max_alt_loss(0))) { *alt_loss = _alt_loss; return true; } } } } else { if (! mk_data(barometric_altitude_rate).ncd && ! mk_ainput(uncorrected_barometric_altitude).ncd && mk_data(barometric_altitude_rate).get() < 0) { has_descent_alt = true; descent_alt = mk_ainput(uncorrected_barometric_altitude).get(); } } return false; } void MK_VIII::Mode3Handler::enter_takeoff () { armed = false; has_descent_alt = false; } void MK_VIII::Mode3Handler::update () { if (mk->configuration_module.state != ConfigurationModule::STATE_OK) return; if (mk->state_handler.takeoff) { double alt_loss; if (! mk->state_handler.ground /* [1] */ && is(&alt_loss)) { if (mk_test_alert(MODE3)) { double new_bias = get_bias(bias, alt_loss); if (new_bias > bias) { bias = new_bias; mk_repeat_alert(mk_alert(MODE3)); } } else { armed = true; bias = get_bias(0.2, alt_loss); mk_set_alerts(mk_alert(MODE3)); } return; } } mk_unset_alerts(mk_alert(MODE3)); } /////////////////////////////////////////////////////////////////////////////// // Mode4Handler /////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////// // FIXME: for turbofans, the upper limit should be reduced from 1000 // to 800 ft if a sudden change in radio altitude is detected, in // order to reduce nuisance alerts caused by overflying another // aircraft (see [PILOT] page 16). double MK_VIII::Mode4Handler::get_upper_agl (const EnvelopesConfiguration *c) { if (mk_ainput(computed_airspeed).get() >= c->airspeed2) return c->min_agl3; else if (mk_ainput(computed_airspeed).get() >= c->airspeed1) return c->min_agl2(mk_ainput(computed_airspeed).get()); else return c->min_agl1; } const MK_VIII::Mode4Handler::EnvelopesConfiguration * MK_VIII::Mode4Handler::get_ab_envelope () { // [SPEC] 6.2.4.1: "The Mode 4B envelope can also be selected by // setting flaps to landing configuration or by selecting flap // override." return mk_dinput(landing_gear) || mk->io_handler.flaps_down() ? conf.modes->b : conf.modes->ac; } double MK_VIII::Mode4Handler::get_bias (double initial_bias, double min_agl) { while (mk_data(radio_altitude).get() < min_agl - min_agl * initial_bias) initial_bias += 0.2; return initial_bias; } void MK_VIII::Mode4Handler::handle_alert (unsigned int alert, double min_agl, double *bias) { if (mk_test_alerts(alert)) { double new_bias = get_bias(*bias, min_agl); if (new_bias > *bias) { *bias = new_bias; mk_repeat_alert(alert); } } else { *bias = get_bias(0.2, min_agl); mk_set_alerts(alert); } } void MK_VIII::Mode4Handler::update_ab () { if (! mk->io_handler.gpws_inhibit() && ! mk->state_handler.ground && ! mk->state_handler.takeoff && ! mk_data(radio_altitude).ncd && ! mk_ainput(computed_airspeed).ncd && mk_data(radio_altitude).get() > 30) { const EnvelopesConfiguration *c = get_ab_envelope(); if (mk_ainput(computed_airspeed).get() < c->airspeed1) { if (mk_dinput(landing_gear)) { if (! mk->io_handler.flaps_down() && mk_data(radio_altitude).get() < c->min_agl1) { handle_alert(mk_alert(MODE4_TOO_LOW_FLAPS), c->min_agl1, &ab_bias); return; } } else { if (mk_data(radio_altitude).get() < c->min_agl1) { handle_alert(mk_alert(MODE4_TOO_LOW_GEAR), c->min_agl1, &ab_bias); return; } } } } mk_unset_alerts(mk_alert(MODE4_TOO_LOW_FLAPS) | mk_alert(MODE4_TOO_LOW_GEAR)); } void MK_VIII::Mode4Handler::update_ab_expanded () { if (! mk->io_handler.gpws_inhibit() && ! mk->state_handler.ground && ! mk->state_handler.takeoff && ! mk_data(radio_altitude).ncd && ! mk_ainput(computed_airspeed).ncd && mk_data(radio_altitude).get() > 30) { const EnvelopesConfiguration *c = get_ab_envelope(); if (mk_ainput(computed_airspeed).get() >= c->airspeed1) { double min_agl = get_upper_agl(c); if (mk_data(radio_altitude).get() < min_agl) { handle_alert(mk_alert(MODE4AB_TOO_LOW_TERRAIN), min_agl, &ab_expanded_bias); return; } } } mk_unset_alerts(mk_alert(MODE4AB_TOO_LOW_TERRAIN)); } void MK_VIII::Mode4Handler::update_c () { if (! mk->io_handler.gpws_inhibit() && ! mk->state_handler.ground // [1] && mk->state_handler.takeoff && ! mk_data(radio_altitude).ncd && ! mk_data(terrain_clearance).ncd && mk_data(radio_altitude).get() > 30 && (! mk_dinput(landing_gear) || ! mk->io_handler.flaps_down()) && mk_data(radio_altitude).get() < get_upper_agl(conf.modes->ac) && mk_data(radio_altitude).get() < mk_data(terrain_clearance).get()) handle_alert(mk_alert(MODE4C_TOO_LOW_TERRAIN), mk_data(terrain_clearance).get(), &c_bias); else mk_unset_alerts(mk_alert(MODE4C_TOO_LOW_TERRAIN)); } void MK_VIII::Mode4Handler::update () { if (mk->configuration_module.state != ConfigurationModule::STATE_OK) return; update_ab(); update_ab_expanded(); update_c(); } /////////////////////////////////////////////////////////////////////////////// // Mode5Handler /////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////// bool MK_VIII::Mode5Handler::is_hard () { if (mk_data(radio_altitude).get() > 30 && mk_data(radio_altitude).get() < 300 && mk_data(glideslope_deviation_dots).get() > 2) { if (mk_data(radio_altitude).get() < 150) { if (mk_data(radio_altitude).get() > 293 - 71.43 * mk_data(glideslope_deviation_dots).get()) return true; } else return true; } return false; } bool MK_VIII::Mode5Handler::is_soft (double bias) { if (mk_data(radio_altitude).get() > 30) { double bias_dots = 1.3 * bias; if (mk_data(glideslope_deviation_dots).get() > 1.3 + bias_dots) { if (mk_data(radio_altitude).get() < 150) { if (mk_data(radio_altitude).get() > 243 - 71.43 * (mk_data(glideslope_deviation_dots).get() - bias_dots)) return true; } else { double upper_limit; if (mk_data(barometric_altitude_rate).ncd) upper_limit = 1000; else { if (mk_data(barometric_altitude_rate).get() >= 0) upper_limit = 500; else if (mk_data(barometric_altitude_rate).get() < -500) upper_limit = 1000; else upper_limit = -mk_data(barometric_altitude_rate).get() + 500; } if (mk_data(radio_altitude).get() < upper_limit) return true; } } } return false; } double MK_VIII::Mode5Handler::get_soft_bias (double initial_bias) { while (is_soft(initial_bias)) initial_bias += 0.2; return initial_bias; } void MK_VIII::Mode5Handler::update_hard (bool is) { if (is) { if (! mk_test_alert(MODE5_HARD)) { if (hard_timer.start_or_elapsed() >= 0.8) mk_set_alerts(mk_alert(MODE5_HARD)); } } else { hard_timer.stop(); mk_unset_alerts(mk_alert(MODE5_HARD)); } } void MK_VIII::Mode5Handler::update_soft (bool is) { if (is) { if (! mk_test_alert(MODE5_SOFT)) { double new_bias = get_soft_bias(soft_bias); if (new_bias > soft_bias) { soft_bias = new_bias; mk_repeat_alert(mk_alert(MODE5_SOFT)); } } else { if (soft_timer.start_or_elapsed() >= 0.8) { soft_bias = get_soft_bias(0.2); mk_set_alerts(mk_alert(MODE5_SOFT)); } } } else { soft_timer.stop(); mk_unset_alerts(mk_alert(MODE5_SOFT)); } } void MK_VIII::Mode5Handler::update () { if (mk->configuration_module.state != ConfigurationModule::STATE_OK) return; if (! mk->io_handler.gpws_inhibit() && ! mk->state_handler.ground // [1] && ! mk_dinput(glideslope_inhibit) // not on backcourse && ! mk_data(radio_altitude).ncd && ! mk_data(glideslope_deviation_dots).ncd && (! mk->io_handler.conf.localizer_enabled || mk_data(localizer_deviation_dots).ncd || mk_data(radio_altitude).get() < 500 || fabs(mk_data(localizer_deviation_dots).get()) < 2) && (! mk->state_handler.takeoff || mk->io_handler.flaps_down()) && mk_dinput(landing_gear) && ! mk_doutput(glideslope_cancel)) { update_hard(is_hard()); update_soft(is_soft(0)); } else { update_hard(false); update_soft(false); } } /////////////////////////////////////////////////////////////////////////////// // Mode6Handler /////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////// // keep sorted in descending order const int MK_VIII::Mode6Handler::altitude_callout_definitions[] = { 1000, 500, 400, 300, 200, 100, 50, 40, 30, 20, 10 }; void MK_VIII::Mode6Handler::reset_minimums () { minimums_issued = false; } void MK_VIII::Mode6Handler::reset_altitude_callouts () { for (int i = 0; i < n_altitude_callouts; i++) altitude_callouts_issued[i] = false; } bool MK_VIII::Mode6Handler::is_playing_altitude_callout () { for (int i = 0; i < n_altitude_callouts; i++) if (mk->voice_player.voice == mk_altitude_voice(i) || mk->voice_player.next_voice == mk_altitude_voice(i)) return true; return false; } bool MK_VIII::Mode6Handler::is_near_minimums (double callout) { // [SPEC] 6.4.2 if (! mk_data(decision_height).ncd) { double diff = callout - mk_data(decision_height).get(); if (mk_data(radio_altitude).get() >= 200) { if (fabs(diff) <= 30) return true; } else { if (diff >= -3 && diff <= 6) return true; } } return false; } bool MK_VIII::Mode6Handler::is_outside_band (double elevation, double callout) { // [SPEC] 6.4.2 return elevation < callout - (elevation > 150 ? 20 : 10); } bool MK_VIII::Mode6Handler::inhibit_smart_500 () { // [SPEC] 6.4.3 if (! mk_data(glideslope_deviation_dots).ncd && ! mk_dinput(glideslope_inhibit) // backcourse && ! mk_doutput(glideslope_cancel)) { if (mk->io_handler.conf.localizer_enabled && ! mk_data(localizer_deviation_dots).ncd) { if (fabs(mk_data(localizer_deviation_dots).get()) <= 2) return true; } else { if (fabs(mk_data(glideslope_deviation_dots).get()) <= 2) return true; } } return false; } void MK_VIII::Mode6Handler::boot () { if (mk->configuration_module.state != ConfigurationModule::STATE_OK) return; last_decision_height = mk_dinput(decision_height); last_radio_altitude.set(&mk_data(radio_altitude)); // [SPEC] 6.4.2 for (int i = 0; i < n_altitude_callouts; i++) altitude_callouts_issued[i] = ! mk_data(radio_altitude).ncd && mk_data(radio_altitude).get() <= altitude_callout_definitions[i]; // extrapolated from [SPEC] 6.4.2 minimums_issued = mk_dinput(decision_height); if (conf.above_field_voice) { update_runway(); get_altitude_above_field(&last_altitude_above_field); // extrapolated from [SPEC] 6.4.2 above_field_issued = ! last_altitude_above_field.ncd && last_altitude_above_field.get() < 550; } } void MK_VIII::Mode6Handler::power_off () { runway_timer.stop(); } void MK_VIII::Mode6Handler::enter_takeoff () { reset_altitude_callouts(); // [SPEC] 6.4.2 reset_minimums(); // omitted by [SPEC]; common sense } void MK_VIII::Mode6Handler::leave_takeoff () { reset_altitude_callouts(); // [SPEC] 6.0.2 reset_minimums(); // [SPEC] 6.0.2 } void MK_VIII::Mode6Handler::set_volume (double volume) { mk_voice(minimums_minimums)->set_volume(volume); mk_voice(five_hundred_above)->set_volume(volume); for (int i = 0; i < n_altitude_callouts; i++) mk_altitude_voice(i)->set_volume(volume); } bool MK_VIII::Mode6Handler::altitude_callouts_enabled () { if (conf.minimums_enabled || conf.smart_500_enabled || conf.above_field_voice) return true; for (int i = 0; i < n_altitude_callouts; i++) if (conf.altitude_callouts_enabled[i]) return true; return false; } void MK_VIII::Mode6Handler::update_minimums () { if (! mk->io_handler.gpws_inhibit() && (mk->voice_player.voice == mk_voice(minimums_minimums) || mk->voice_player.next_voice == mk_voice(minimums_minimums))) goto end; if (! mk->io_handler.gpws_inhibit() && ! mk->state_handler.ground // [1] && conf.minimums_enabled && ! minimums_issued && mk_dinput(landing_gear) && mk_dinput(decision_height) && ! last_decision_height) { minimums_issued = true; // If an altitude callout is playing, stop it now so that the // minimums callout can be played (note that proper connection // and synchronization of the radio-altitude ARINC 529 input, // decision-height discrete and decision-height ARINC 529 input // will prevent an altitude callout from being played near the // decision height). if (is_playing_altitude_callout()) mk->voice_player.stop(VoicePlayer::STOP_NOW); mk_set_alerts(mk_alert(MODE6_MINIMUMS)); } else mk_unset_alerts(mk_alert(MODE6_MINIMUMS)); end: last_decision_height = mk_dinput(decision_height); } void MK_VIII::Mode6Handler::update_altitude_callouts () { if (! mk->io_handler.gpws_inhibit() && is_playing_altitude_callout()) goto end; if (! mk->io_handler.gpws_inhibit() && ! mk->state_handler.ground // [1] && ! mk_data(radio_altitude).ncd) for (int i = 0; i < n_altitude_callouts && mk_data(radio_altitude).get() <= altitude_callout_definitions[i]; i++) if ((conf.altitude_callouts_enabled[i] || (altitude_callout_definitions[i] == 500 && conf.smart_500_enabled)) && ! altitude_callouts_issued[i] && (last_radio_altitude.ncd || last_radio_altitude.get() > altitude_callout_definitions[i])) { // lock out all callouts superior or equal to this one for (int j = 0; j <= i; j++) altitude_callouts_issued[j] = true; altitude_callouts_issued[i] = true; if (! is_near_minimums(altitude_callout_definitions[i]) && ! is_outside_band(mk_data(radio_altitude).get(), altitude_callout_definitions[i]) && (! conf.smart_500_enabled || altitude_callout_definitions[i] != 500 || ! inhibit_smart_500())) { mk->alert_handler.set_altitude_callout_alert(mk_altitude_voice(i)); goto end; } } mk_unset_alerts(mk_alert(MODE6_ALTITUDE_CALLOUT)); end: last_radio_altitude.set(&mk_data(radio_altitude)); } bool MK_VIII::Mode6Handler::test_runway (const FGRunway *_runway) { if (_runway->_length < mk->conf.runway_database) return false; // get position of threshold double latitude, longitude, az; geo_direct_wgs_84(0, _runway->_lat, _runway->_lon, get_reciprocal_heading(_runway->_heading), _runway->_length / 2 * SG_FEET_TO_METER, &latitude, &longitude, &az); // get distance to threshold double distance, az1, az2; geo_inverse_wgs_84(0, mk_data(gps_latitude).get(), mk_data(gps_longitude).get(), latitude, longitude, &az1, &az2, &distance); return distance * SG_METER_TO_NM <= 5; } bool MK_VIII::Mode6Handler::test_airport (const FGAirport *airport) { for (unsigned int r=0; rnumRunways(); ++r) { FGRunway rwy(airport->getRunwayByIndex(r)); if (test_runway(&rwy)) return true; // reciprocal runway rwy._heading = get_reciprocal_heading(rwy._heading); if (test_runway(&rwy)) return true; } return false; } bool MK_VIII::Mode6Handler::AirportFilter::pass(FGAirport* a) { return self->test_airport(a); } void MK_VIII::Mode6Handler::update_runway () { if (mk_data(gps_latitude).ncd || mk_data(gps_longitude).ncd) { has_runway.unset(); return; } // Search for the closest runway threshold in range 5 // nm. Passing 0.5 degrees (approximatively 30 nm) to // get_closest_airport() provides enough margin for large // airports, which may have a runway located far away from the // airport's reference point. AirportFilter filter(this); const FGAirport *airport = globals->get_airports()->search( mk_data(gps_latitude).get(), mk_data(gps_longitude).get(), 0.5, filter); if (airport) { runway.elevation = airport->getElevation(); } has_runway.set(airport != NULL); } void MK_VIII::Mode6Handler::get_altitude_above_field (Parameter *parameter) { if (! has_runway.ncd && has_runway.get() && ! mk_data(geometric_altitude).ncd) parameter->set(mk_data(geometric_altitude).get() - runway.elevation); else parameter->unset(); } void MK_VIII::Mode6Handler::update_above_field_callout () { if (! conf.above_field_voice) return; // update_runway() has to iterate over the whole runway database // (which contains thousands of runways), so it would be unwise to // run it every frame. Run it every 3 seconds. Note that the first // iteration is run in boot(). if (runway_timer.start_or_elapsed() >= 3) { update_runway(); runway_timer.start(); } Parameter altitude_above_field; get_altitude_above_field(&altitude_above_field); if (! mk->io_handler.gpws_inhibit() && (mk->voice_player.voice == conf.above_field_voice || mk->voice_player.next_voice == conf.above_field_voice)) goto end; // handle reset term if (above_field_issued) { if ((! has_runway.ncd && ! has_runway.get()) || (! altitude_above_field.ncd && altitude_above_field.get() > 700)) above_field_issued = false; } if (! mk->io_handler.gpws_inhibit() && ! mk->state_handler.ground // [1] && ! above_field_issued && ! altitude_above_field.ncd && altitude_above_field.get() < 550 && (last_altitude_above_field.ncd || last_altitude_above_field.get() >= 550)) { above_field_issued = true; if (! is_outside_band(altitude_above_field.get(), 500)) { mk->alert_handler.set_altitude_callout_alert(conf.above_field_voice); goto end; } } mk_unset_alerts(mk_alert(MODE6_ALTITUDE_CALLOUT)); end: last_altitude_above_field.set(&altitude_above_field); } bool MK_VIII::Mode6Handler::is_bank_angle (double abs_roll_angle, double bias) { return conf.is_bank_angle(&mk_data(radio_altitude), abs_roll_angle - abs_roll_angle * bias, mk_dinput(autopilot_engaged)); } bool MK_VIII::Mode6Handler::is_high_bank_angle () { return mk_data(radio_altitude).ncd || mk_data(radio_altitude).get() >= 210; } unsigned int MK_VIII::Mode6Handler::get_bank_angle_alerts () { if (! mk->io_handler.gpws_inhibit() && ! mk->state_handler.ground // [1] && ! mk_data(roll_angle).ncd) { double abs_roll_angle = fabs(mk_data(roll_angle).get()); if (is_bank_angle(abs_roll_angle, 0.4)) return is_high_bank_angle() ? (mk_alert(MODE6_HIGH_BANK_ANGLE_1) | mk_alert(MODE6_HIGH_BANK_ANGLE_2) | mk_alert(MODE6_HIGH_BANK_ANGLE_3)) : (mk_alert(MODE6_LOW_BANK_ANGLE_1) | mk_alert(MODE6_LOW_BANK_ANGLE_2) | mk_alert(MODE6_LOW_BANK_ANGLE_3)); else if (is_bank_angle(abs_roll_angle, 0.2)) return is_high_bank_angle() ? (mk_alert(MODE6_HIGH_BANK_ANGLE_1) | mk_alert(MODE6_HIGH_BANK_ANGLE_2)) : (mk_alert(MODE6_LOW_BANK_ANGLE_1) | mk_alert(MODE6_LOW_BANK_ANGLE_2)); else if (is_bank_angle(abs_roll_angle, 0)) return is_high_bank_angle() ? mk_alert(MODE6_HIGH_BANK_ANGLE_1) : mk_alert(MODE6_LOW_BANK_ANGLE_1); } return 0; } void MK_VIII::Mode6Handler::update_bank_angle () { if (! conf.bank_angle_enabled) return; unsigned int alerts = get_bank_angle_alerts(); // [SPEC] 6.4.4 specifies that the non-continuous alerts // (MODE6_*_BANK_ANGLE_1 and MODE6_*_BANK_ANGLE_2) are locked out // until the initial envelope is exited. if (! test_bits(alerts, mk_alert(MODE6_LOW_BANK_ANGLE_3))) mk_unset_alerts(mk_alert(MODE6_LOW_BANK_ANGLE_3)); if (! test_bits(alerts, mk_alert(MODE6_HIGH_BANK_ANGLE_3))) mk_unset_alerts(mk_alert(MODE6_HIGH_BANK_ANGLE_3)); if (alerts != 0) mk_set_alerts(alerts); else mk_unset_alerts(mk_alert(MODE6_LOW_BANK_ANGLE_1) | mk_alert(MODE6_HIGH_BANK_ANGLE_1) | mk_alert(MODE6_LOW_BANK_ANGLE_2) | mk_alert(MODE6_HIGH_BANK_ANGLE_2)); } void MK_VIII::Mode6Handler::update () { if (mk->configuration_module.state != ConfigurationModule::STATE_OK) return; if (! mk->state_handler.takeoff && ! mk_data(radio_altitude).ncd && mk_data(radio_altitude).get() > 1000) { reset_altitude_callouts(); // [SPEC] 6.4.2 reset_minimums(); // common sense } update_minimums(); update_altitude_callouts(); update_above_field_callout(); update_bank_angle(); } /////////////////////////////////////////////////////////////////////////////// // TCFHandler ///////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////// // Gets the difference between the azimuth from @from_lat,@from_lon to // @to_lat,@to_lon, and @to_heading, in degrees. double MK_VIII::TCFHandler::get_azimuth_difference (double from_lat, double from_lon, double to_lat, double to_lon, double to_heading) { double az1, az2, distance; geo_inverse_wgs_84(0, from_lat, from_lon, to_lat, to_lon, &az1, &az2, &distance); return get_heading_difference(az1, to_heading); } // Gets the difference between the azimuth from the current GPS // position to the center of @_runway, and the heading of @_runway, in // degrees. double MK_VIII::TCFHandler::get_azimuth_difference (const FGRunway *_runway) { return get_azimuth_difference(mk_data(gps_latitude).get(), mk_data(gps_longitude).get(), _runway->_lat, _runway->_lon, _runway->_heading); } // Selects the most likely intended destination runway of @airport, // and returns it in @_runway. For each runway, the difference between // the azimuth from the current GPS position to the center of the // runway and its heading is computed. The runway having the smallest // difference wins. // // This selection algorithm is not specified in [SPEC], but // http://www.egpws.com/general_information/description/runway_select.htm // talks about automatic runway selection. void MK_VIII::TCFHandler::select_runway (const FGAirport *airport, FGRunway *_runway) { /* FGRunway r; bool status = globals->get_runways()->search(airport->getId(), &r); assert(status); } while (globals->get_runways()->next(&r) && r._id == airport->getId()); */ double min_diff = 360; for (unsigned int r=0; rnumRunways(); ++r) { FGRunway rwy(airport->getRunwayByIndex(r)); double diff = get_azimuth_difference(&rwy); if (diff < min_diff) { min_diff = diff; *_runway = rwy; } // reciprocal runway rwy._heading = get_reciprocal_heading(rwy._heading); diff = get_azimuth_difference(&rwy); if (diff < min_diff) { min_diff = diff; *_runway = rwy; } } // of airport runways iteration } bool MK_VIII::TCFHandler::AirportFilter::pass(FGAirport *a) { for (unsigned int r=0; rnumRunways(); ++r) { if (a->getRunwayByIndex(r)._length >= mk->conf.runway_database) { return true; } } return false; } void MK_VIII::TCFHandler::update_runway () { has_runway = false; if (mk_data(gps_latitude).ncd || mk_data(gps_longitude).ncd) { return; } // Search for the intended destination runway of the closest // airport in range 15 nm. Passing 0.5 degrees (approximatively // 30 nm) to get_closest_airport() provides enough margin for // large airports, which may have a runway located far away from // the airport's reference point. AirportFilter filter(mk); const FGAirport *airport = globals->get_airports()->search( mk_data(gps_latitude).get(), mk_data(gps_longitude).get(), 0.5, filter); if (!airport) return; has_runway = true; FGRunway _runway; select_runway(airport, &_runway); runway.center.latitude = _runway._lat; runway.center.longitude = _runway._lon; runway.elevation = airport->getElevation(); double half_length_m = _runway._length / 2 * SG_FEET_TO_METER; runway.half_length = half_length_m * SG_METER_TO_NM; // b3 ________________ b0 // | | // h1>>> | e1<<<<<<<latitude, edge->longitude, reciprocal, k * SG_NM_TO_METER, &tmp_latitude, &tmp_longitude, &az); geo_direct_wgs_84(0, tmp_latitude, tmp_longitude, heading_substract(reciprocal, 90), half_bias_width_m, &bias_edge1->latitude, &bias_edge1->longitude, &az); geo_direct_wgs_84(0, tmp_latitude, tmp_longitude, heading_add(reciprocal, 90), half_bias_width_m, &bias_edge2->latitude, &bias_edge2->longitude, &az); } // Returns true if the current GPS position is inside the edge // triangle of @edge. The edge triangle, which is specified and // represented in [SPEC] 6.3.1, is defined as an isocele right // triangle of infinite height, whose right angle is located at the // position of @edge, and whose height is parallel to the heading of // @edge. bool MK_VIII::TCFHandler::is_inside_edge_triangle (RunwayEdge *edge) { return get_azimuth_difference(mk_data(gps_latitude).get(), mk_data(gps_longitude).get(), edge->position.latitude, edge->position.longitude, edge->heading) <= 45; } // Returns true if the current GPS position is inside the bias area of // the currently selected runway. bool MK_VIII::TCFHandler::is_inside_bias_area () { double az1[4]; double angles_sum = 0; for (int i = 0; i < 4; i++) { double az2, distance; geo_inverse_wgs_84(0, mk_data(gps_latitude).get(), mk_data(gps_longitude).get(), runway.bias_area[i].latitude, runway.bias_area[i].longitude, &az1[i], &az2, &distance); } for (int i = 0; i < 4; i++) { double angle = az1[i == 3 ? 0 : i + 1] - az1[i]; if (angle < -180) angle += 360; angles_sum += angle; } return angles_sum > 180; } bool MK_VIII::TCFHandler::is_tcf () { if (mk_data(radio_altitude).get() > 10) { if (has_runway) { double distance, az1, az2; geo_inverse_wgs_84(0, mk_data(gps_latitude).get(), mk_data(gps_longitude).get(), runway.center.latitude, runway.center.longitude, &az1, &az2, &distance); distance *= SG_METER_TO_NM; // distance to the inner envelope edge double edge_distance = distance - runway.half_length - k; if (edge_distance >= 15) { if (mk_data(radio_altitude).get() < 700) return true; } else if (edge_distance >= 12) { if (mk_data(radio_altitude).get() < 100 * edge_distance - 800) return true; } else if (edge_distance >= 4) { if (mk_data(radio_altitude).get() < 400) return true; } else if (edge_distance >= 2.45) { if (mk_data(radio_altitude).get() < 66.666667 * edge_distance + 133.33333) return true; } else { if (is_inside_edge_triangle(&runway.edges[0]) || is_inside_edge_triangle(&runway.edges[1])) { if (edge_distance >= 1) { if (mk_data(radio_altitude).get() < 66.666667 * edge_distance + 133.33333) return true; } else if (edge_distance >= 0.05) { if (mk_data(radio_altitude).get() < 200 * edge_distance) return true; } } else { if (! is_inside_bias_area()) { if (mk_data(radio_altitude).get() < 245) return true; } } } } else { if (mk_data(radio_altitude).get() < 700) return true; } } return false; } bool MK_VIII::TCFHandler::is_rfcf () { if (has_runway) { double distance, az1, az2; geo_inverse_wgs_84(0, mk_data(gps_latitude).get(), mk_data(gps_longitude).get(), runway.center.latitude, runway.center.longitude, &az1, &az2, &distance); double krf = k + mk_data(gps_vertical_figure_of_merit).get() / 200; distance = distance * SG_METER_TO_NM - runway.half_length - krf; if (distance <= 5) { double altitude_above_field = mk_data(geometric_altitude).get() - runway.elevation; if (distance >= 1.5) { if (altitude_above_field < 300) return true; } else if (distance >= 0) { if (altitude_above_field < 200 * distance) return true; } } } return false; } void MK_VIII::TCFHandler::update () { if (mk->configuration_module.state != ConfigurationModule::STATE_OK || ! conf.enabled) return; // update_runway() has to iterate over the whole runway database // (which contains thousands of runways), so it would be unwise to // run it every frame. Run it every 3 seconds. if (! runway_timer.running || runway_timer.elapsed() >= 3) { update_runway(); runway_timer.start(); } if (! mk_dinput(ta_tcf_inhibit) && ! mk->state_handler.ground // [1] && ! mk_data(gps_latitude).ncd && ! mk_data(gps_longitude).ncd && ! mk_data(radio_altitude).ncd && ! mk_data(geometric_altitude).ncd && ! mk_data(gps_vertical_figure_of_merit).ncd) { double *_reference; if (is_tcf()) _reference = mk_data(radio_altitude).get_pointer(); else if (is_rfcf()) _reference = mk_data(geometric_altitude).get_pointer(); else _reference = NULL; if (_reference) { if (mk_test_alert(TCF_TOO_LOW_TERRAIN)) { double new_bias = bias; while (*reference < initial_value - initial_value * new_bias) new_bias += 0.2; if (new_bias > bias) { bias = new_bias; mk_repeat_alert(mk_alert(TCF_TOO_LOW_TERRAIN)); } } else { bias = 0.2; reference = _reference; initial_value = *reference; mk_set_alerts(mk_alert(TCF_TOO_LOW_TERRAIN)); } return; } } mk_unset_alerts(mk_alert(TCF_TOO_LOW_TERRAIN)); } /////////////////////////////////////////////////////////////////////////////// // MK_VIII //////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////// MK_VIII::MK_VIII (SGPropertyNode *node) : name("mk-viii"), num(0), properties_handler(this), power_handler(this), system_handler(this), configuration_module(this), fault_handler(this), io_handler(this), voice_player(this), self_test_handler(this), alert_handler(this), state_handler(this), mode1_handler(this), mode2_handler(this), mode3_handler(this), mode4_handler(this), mode5_handler(this), mode6_handler(this), tcf_handler(this) { for (int i = 0; i < node->nChildren(); ++i) { SGPropertyNode *child = node->getChild(i); string cname = child->getName(); string cval = child->getStringValue(); if (cname == "name") name = cval; else if (cname == "number") num = child->getIntValue(); else { SG_LOG(SG_INSTR, SG_WARN, "Error in mk-viii config logic"); if (name.length()) SG_LOG(SG_INSTR, SG_WARN, "Section = " << name); } } } void MK_VIII::init () { properties_handler.init(); voice_player.init(); } void MK_VIII::bind () { SGPropertyNode *node = fgGetNode(("/instrumentation/" + name).c_str(), num, true); configuration_module.bind(node); power_handler.bind(node); io_handler.bind(node); voice_player.bind(node); } void MK_VIII::unbind () { properties_handler.unbind(); } void MK_VIII::update (double dt) { power_handler.update(); system_handler.update(); if (system_handler.state != SystemHandler::STATE_ON) return; io_handler.update_inputs(); io_handler.update_input_faults(); voice_player.update(); state_handler.update(); if (self_test_handler.update()) return; io_handler.update_internal_latches(); io_handler.update_lamps(); mode1_handler.update(); mode2_handler.update(); mode3_handler.update(); mode4_handler.update(); mode5_handler.update(); mode6_handler.update(); tcf_handler.update(); alert_handler.update(); io_handler.update_outputs(); }