--- /dev/null
+// 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 <config.h>
+#endif
+
+#include <stdio.h>
+#include <string.h>
+#include <assert.h>
+#include <math.h>
+
+#include <string>
+#include <sstream>
+
+#include <simgear/constants.h>
+#include <simgear/sg_inlines.h>
+#include <simgear/debug/logstream.hxx>
+#include <simgear/math/sg_geodesy.hxx>
+#include <simgear/math/sg_random.h>
+#include <simgear/misc/sg_path.hxx>
+#include <simgear/sound/soundmgr_openal.hxx>
+#include <simgear/structure/exception.hxx>
+
+SG_USING_STD(string);
+
+#include "Airports/runways.hxx"
+#include "Airports/simple.hxx"
+#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);
+}
+
+// Searches for the closest airport whose Manhattan distance to
+// @lat,@lon is inferior to @min_manhattan_distance (expressed in
+// degrees) and for which @test_airport returns true. Returns NULL if
+// no airport was found.
+template <class C>
+static const FGAirport *
+get_closest_airport (double lat,
+ double lon,
+ double min_manhattan_distance,
+ C &obj,
+ bool (C::*test_airport) (const FGAirport *))
+{
+ const FGAirport *airport = NULL;
+ const airport_list *airport_list = globals->get_airports()->getAirportList();
+
+ for (size_t i = 0; i < airport_list->size(); i++)
+ {
+ const FGAirport *a = (*airport_list)[i];
+ double dist = fabs(lat - a->getLatitude()) + fabs(lon - a->getLongitude());
+ if (dist < min_manhattan_distance && (obj.*test_airport)(a))
+ {
+ airport = a;
+ min_manhattan_distance = dist;
+ }
+ }
+
+ return airport;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// 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<SGPropertyNode *>::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<bool>(&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<double> *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<double> *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<int>(&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.
+
+ deque< Sample<double> >::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<double>(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<double> >(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.
+
+ deque< Sample< Parameter<double> > >::iterator erase_last = altitude_samples.begin();
+ deque< Sample< Parameter<double> > >::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<MK_VIII::IOHandler, bool, bool *>(*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<bool>(feed));
+}
+
+void
+MK_VIII::IOHandler::tie_input (SGPropertyNode *node,
+ const char *name,
+ Parameter<double> *input,
+ bool *feed)
+{
+ mk->properties_handler.tie(node, (string("inputs/arinc429/") + name).c_str(), SGRawValuePointer<double>(input->get_pointer()));
+ mk->properties_handler.tie(node, (string("inputs/arinc429/") + name + "-ncd").c_str(), SGRawValuePointer<bool>(&input->ncd));
+ if (feed)
+ mk->properties_handler.tie(node, (string("input-feeders/arinc429/") + name).c_str(), SGRawValuePointer<bool>(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<bool>(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<int>(output));
+ child->setAttribute(SGPropertyNode::WRITE, false);
+}
+
+void
+MK_VIII::IOHandler::bind (SGPropertyNode *node)
+{
+ mk->properties_handler.tie(node, "inputs/rs-232/present-status", SGRawValueMethods<MK_VIII::IOHandler, bool>(*this, &MK_VIII::IOHandler::get_present_status, &MK_VIII::IOHandler::set_present_status));
+
+ 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<string, SGSoundSample *>::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<Voice *>::iterator iter1;
+ for (iter1 = _voices.begin(); iter1 != _voices.end(); iter1++)
+ delete *iter1;
+ _voices.clear();
+
+/* sound mgr already destroyed - samples already deleted
+ map<string, SGSoundSample *>::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;
+
+ SGSoundSample *sample = globals->get_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);
+ }
+
+ globals->get_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)
+{
+ FGRunway r;
+ if (globals->get_runways()->search(airport->getId(), &r))
+ do
+ {
+ if (test_runway(&r))
+ return true;
+
+ // reciprocal runway
+ r._heading = get_reciprocal_heading(r._heading);
+ if (test_runway(&r))
+ return true;
+ }
+ while (globals->get_runways()->next(&r) && r._id == airport->getId());
+
+ return false;
+}
+
+void
+MK_VIII::Mode6Handler::update_runway ()
+{
+ if (! mk_data(gps_latitude).ncd && ! mk_data(gps_longitude).ncd)
+ {
+ // 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.
+
+ const FGAirport *airport = get_closest_airport(mk_data(gps_latitude).get(),
+ mk_data(gps_longitude).get(),
+ 0.5,
+ *this,
+ &MK_VIII::Mode6Handler::test_airport);
+
+ if (airport)
+ runway.elevation = airport->getElevation();
+
+ has_runway.set(airport != NULL);
+ }
+ else
+ has_runway.unset();
+}
+
+void
+MK_VIII::Mode6Handler::get_altitude_above_field (Parameter<double> *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<double> 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);
+
+ double min_diff = 360;
+ do
+ {
+ double diff;
+
+ diff = get_azimuth_difference(&r);
+ if (diff < min_diff)
+ {
+ min_diff = diff;
+ *_runway = r;
+ }
+
+ // reciprocal runway
+ r._heading = get_reciprocal_heading(r._heading);
+ diff = get_azimuth_difference(&r);
+ if (diff < min_diff)
+ {
+ min_diff = diff;
+ *_runway = r;
+ }
+ }
+ while (globals->get_runways()->next(&r) && r._id == airport->getId());
+}
+
+bool
+MK_VIII::TCFHandler::test_airport (const FGAirport *airport)
+{
+ FGRunway r;
+ if (globals->get_runways()->search(airport->getId(), &r))
+ do
+ {
+ if (r._length >= mk->conf.runway_database)
+ return true;
+ }
+ while (globals->get_runways()->next(&r) && r._id == airport->getId());
+
+ return false;
+}
+
+void
+MK_VIII::TCFHandler::update_runway ()
+{
+ has_runway = false;
+ if (! mk_data(gps_latitude).ncd && ! mk_data(gps_longitude).ncd)
+ {
+ // 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.
+
+ const FGAirport *airport = get_closest_airport(mk_data(gps_latitude).get(),
+ mk_data(gps_longitude).get(),
+ 0.5,
+ *this,
+ &MK_VIII::TCFHandler::test_airport);
+
+ if (airport)
+ {
+ 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<<<<<<<<e0 | <<<h0
+ // |________________|
+ // b2 b1
+
+ // get heading to runway threshold (h0) and end (h1)
+ runway.edges[0].heading = _runway._heading;
+ runway.edges[1].heading = get_reciprocal_heading(_runway._heading);
+
+ double az;
+
+ // get position of runway threshold (e0)
+ geo_direct_wgs_84(0,
+ runway.center.latitude,
+ runway.center.longitude,
+ runway.edges[1].heading,
+ half_length_m,
+ &runway.edges[0].position.latitude,
+ &runway.edges[0].position.longitude,
+ &az);
+
+ // get position of runway end (e1)
+ geo_direct_wgs_84(0,
+ runway.center.latitude,
+ runway.center.longitude,
+ runway.edges[0].heading,
+ half_length_m,
+ &runway.edges[1].position.latitude,
+ &runway.edges[1].position.longitude,
+ &az);
+
+ double half_width_m = _runway._width / 2 * SG_FEET_TO_METER;
+
+ // get position of threshold bias area edges (b0 and b1)
+ get_bias_area_edges(&runway.edges[0].position,
+ runway.edges[1].heading,
+ half_width_m,
+ &runway.bias_area[0],
+ &runway.bias_area[1]);
+
+ // get position of end bias area edges (b2 and b3)
+ get_bias_area_edges(&runway.edges[1].position,
+ runway.edges[0].heading,
+ half_width_m,
+ &runway.bias_area[2],
+ &runway.bias_area[3]);
+ }
+ }
+}
+
+void
+MK_VIII::TCFHandler::get_bias_area_edges (Position *edge,
+ double reciprocal,
+ double half_width_m,
+ Position *bias_edge1,
+ Position *bias_edge2)
+{
+ double half_bias_width_m = k * SG_NM_TO_METER + half_width_m;
+ double tmp_latitude, tmp_longitude, az;
+
+ geo_direct_wgs_84(0,
+ edge->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();
+}
--- /dev/null
+// 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.
+
+
+#ifndef __INSTRUMENTS_MK_VIII_HXX
+#define __INSTRUMENTS_MK_VIII_HXX
+
+#include <assert.h>
+
+#include <vector>
+#include <deque>
+#include <map>
+
+#include <simgear/props/props.hxx>
+#include <simgear/sound/sample_openal.hxx>
+#include <simgear/structure/subsystem_mgr.hxx>
+
+SG_USING_STD(vector);
+SG_USING_STD(deque);
+SG_USING_STD(map);
+
+#include "Airports/runways.hxx"
+#include "Airports/simple.hxx"
+#include "Main/globals.hxx"
+
+///////////////////////////////////////////////////////////////////////////////
+// MK_VIII ////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+class MK_VIII : public SGSubsystem
+{
+ // keep in sync with Mode6Handler::altitude_callout_definitions[]
+ static const int n_altitude_callouts = 11;
+
+ /////////////////////////////////////////////////////////////////////////////
+ // MK_VIII::RawValueMethodsData /////////////////////////////////////////////
+ /////////////////////////////////////////////////////////////////////////////
+
+ template <class C, class VT, class DT>
+ class RawValueMethodsData : public SGRawValue<VT>
+ {
+ public:
+ typedef VT (C::*getter_t) (DT) const;
+ typedef void (C::*setter_t) (DT, VT);
+
+ RawValueMethodsData (C &obj, DT data, getter_t getter = 0, setter_t setter = 0)
+ : _obj(obj), _data(data), _getter(getter), _setter(setter) {}
+
+ virtual VT getValue () const
+ {
+ if (_getter)
+ return (_obj.*_getter)(_data);
+ else
+ return SGRawValue<VT>::DefaultValue;
+ }
+ virtual bool setValue (VT value)
+ {
+ if (_setter)
+ {
+ (_obj.*_setter)(_data, value);
+ return true;
+ }
+ else
+ return false;
+ }
+ virtual SGRawValue<VT> *clone () const
+ {
+ return new RawValueMethodsData<C,VT,DT>(_obj, _data, _getter, _setter);
+ }
+
+ private:
+ C &_obj;
+ DT _data;
+ getter_t _getter;
+ setter_t _setter;
+ };
+
+ /////////////////////////////////////////////////////////////////////////////
+ // MK_VIII::Parameter ///////////////////////////////////////////////////////
+ /////////////////////////////////////////////////////////////////////////////
+
+ template <class T>
+ class Parameter
+ {
+ T _value;
+
+ public:
+ bool ncd;
+
+ inline Parameter ()
+ : _value(0), ncd(true) {}
+
+ inline T get () const { assert(! ncd); return _value; }
+ inline T *get_pointer () { return &_value; }
+ inline void set (T value) { ncd = false; _value = value; }
+ inline void unset () { ncd = true; }
+
+ inline void set (const Parameter<T> *parameter)
+ {
+ if (parameter->ncd)
+ unset();
+ else
+ set(parameter->get());
+ }
+
+ inline void set (const Parameter<double> *parameter, double factor)
+ {
+ if (parameter->ncd)
+ unset();
+ else
+ set(parameter->get() * factor);
+ }
+ };
+
+ /////////////////////////////////////////////////////////////////////////////
+ // MK_VIII::Sample //////////////////////////////////////////////////////////
+ /////////////////////////////////////////////////////////////////////////////
+
+ template <class T>
+ class Sample
+ {
+ public:
+ double timestamp;
+ T value;
+
+ inline Sample (T _value)
+ : timestamp(globals->get_sim_time_sec()), value(_value) {}
+ };
+
+ /////////////////////////////////////////////////////////////////////////////
+ // MK_VIII::Timer ///////////////////////////////////////////////////////////
+ /////////////////////////////////////////////////////////////////////////////
+
+ class Timer
+ {
+ double start_time;
+
+ public:
+ bool running;
+
+ inline Timer ()
+ : running(false) {}
+
+ inline void start () { running = true; start_time = globals->get_sim_time_sec(); }
+ inline void stop () { running = false; }
+ inline double elapsed () const { assert(running); return globals->get_sim_time_sec() - start_time; }
+ inline double start_or_elapsed ()
+ {
+ if (running)
+ return elapsed();
+ else
+ {
+ start();
+ return 0;
+ }
+ }
+ };
+
+ /////////////////////////////////////////////////////////////////////////////
+ // MK_VIII::PropertiesHandler ///////////////////////////////////////////////
+ /////////////////////////////////////////////////////////////////////////////
+
+ class PropertiesHandler
+ {
+ MK_VIII *mk;
+
+ vector<SGPropertyNode *> tied_properties;
+
+ public:
+ struct
+ {
+ SGPropertyNode *ai_caged;
+ SGPropertyNode *ai_roll;
+ SGPropertyNode *ai_serviceable;
+ SGPropertyNode *altimeter_altitude;
+ SGPropertyNode *altimeter_serviceable;
+ SGPropertyNode *altitude;
+ SGPropertyNode *altitude_agl;
+ SGPropertyNode *asi_serviceable;
+ SGPropertyNode *asi_speed;
+ SGPropertyNode *autopilot_heading_lock;
+ SGPropertyNode *flaps;
+ SGPropertyNode *gear_down;
+ SGPropertyNode *latitude;
+ SGPropertyNode *longitude;
+ SGPropertyNode *nav0_cdi_serviceable;
+ SGPropertyNode *nav0_gs_distance;
+ SGPropertyNode *nav0_gs_needle_deflection;
+ SGPropertyNode *nav0_gs_serviceable;
+ SGPropertyNode *nav0_has_gs;
+ SGPropertyNode *nav0_heading_needle_deflection;
+ SGPropertyNode *nav0_in_range;
+ SGPropertyNode *nav0_nav_loc;
+ SGPropertyNode *nav0_serviceable;
+ SGPropertyNode *power;
+ SGPropertyNode *replay_state;
+ SGPropertyNode *vs;
+ } external_properties;
+
+ inline PropertiesHandler (MK_VIII *device)
+ : mk(device) {}
+
+ template <class T>
+ inline void tie (SGPropertyNode *node, const SGRawValue<T> &raw_value)
+ {
+ node->tie(raw_value);
+ tied_properties.push_back(node);
+ }
+
+ template <class T>
+ inline void tie (SGPropertyNode *node,
+ const char *relative_path,
+ const SGRawValue<T> &raw_value)
+ {
+ tie(node->getNode(relative_path, true), raw_value);
+ }
+
+ void init ();
+ void unbind ();
+ };
+
+ /////////////////////////////////////////////////////////////////////////////
+ // MK_VIII::PowerHandler ////////////////////////////////////////////////////
+ /////////////////////////////////////////////////////////////////////////////
+
+ class PowerHandler
+ {
+ MK_VIII *mk;
+
+ bool serviceable;
+ bool powered;
+
+ Timer power_loss_timer;
+ Timer abnormal_timer;
+ Timer low_surge_timer;
+ Timer high_surge_timer;
+ Timer very_high_surge_timer;
+
+ bool handle_abnormal_voltage (bool abnormal,
+ Timer *timer,
+ double max_duration);
+
+ void power_on ();
+ void power_off ();
+
+ public:
+ inline PowerHandler (MK_VIII *device)
+ : mk(device), serviceable(false), powered(false) {}
+
+ void bind (SGPropertyNode *node);
+ void update ();
+ };
+
+ /////////////////////////////////////////////////////////////////////////////
+ // MK_VIII::SystemHandler ///////////////////////////////////////////////////
+ /////////////////////////////////////////////////////////////////////////////
+
+ class SystemHandler
+ {
+ MK_VIII *mk;
+
+ double boot_delay;
+ Timer boot_timer;
+
+ int last_replay_state;
+ Timer reposition_timer;
+
+ public:
+ typedef enum
+ {
+ STATE_OFF,
+ STATE_BOOTING,
+ STATE_ON,
+ STATE_REPOSITION
+ } State;
+
+ State state;
+
+ inline SystemHandler (MK_VIII *device)
+ : mk(device), state(STATE_OFF) {}
+
+ void power_on ();
+ void power_off ();
+ void update ();
+ };
+
+ /////////////////////////////////////////////////////////////////////////////
+ // MK_VIII::ConfigurationModule /////////////////////////////////////////////
+ /////////////////////////////////////////////////////////////////////////////
+
+ class ConfigurationModule
+ {
+ public:
+ // keep in sync with IOHandler::present_status()
+ typedef enum
+ {
+ CATEGORY_AIRCRAFT_MODE_TYPE_SELECT,
+ CATEGORY_AIR_DATA_INPUT_SELECT,
+ CATEGORY_POSITION_INPUT_SELECT,
+ CATEGORY_ALTITUDE_CALLOUTS,
+ CATEGORY_AUDIO_MENU_SELECT,
+ CATEGORY_TERRAIN_DISPLAY_SELECT,
+ CATEGORY_OPTIONS_SELECT_GROUP_1,
+ CATEGORY_RADIO_ALTITUDE_INPUT_SELECT,
+ CATEGORY_NAVIGATION_INPUT_SELECT,
+ CATEGORY_ATTITUDE_INPUT_SELECT,
+ CATEGORY_HEADING_INPUT_SELECT,
+ CATEGORY_WINDSHEAR_INPUT_SELECT,
+ CATEGORY_INPUT_OUTPUT_DISCRETE_TYPE_SELECT,
+ CATEGORY_AUDIO_OUTPUT_LEVEL,
+ CATEGORY_UNDEFINED_INPUT_SELECT_1,
+ CATEGORY_UNDEFINED_INPUT_SELECT_2,
+ CATEGORY_UNDEFINED_INPUT_SELECT_3,
+ N_CATEGORIES
+ } Category;
+
+ typedef enum
+ {
+ STATE_OK,
+ STATE_INVALID_DATABASE,
+ STATE_INVALID_AIRCRAFT_TYPE
+ } State;
+ State state;
+
+ int effective_categories[N_CATEGORIES];
+
+ ConfigurationModule (MK_VIII *device);
+
+ void boot ();
+ void bind (SGPropertyNode *node);
+
+ private:
+ MK_VIII *mk;
+
+ int categories[N_CATEGORIES];
+
+ bool read_aircraft_mode_type_select (int value);
+ bool read_air_data_input_select (int value);
+ bool read_position_input_select (int value);
+ bool read_altitude_callouts (int value);
+ bool read_audio_menu_select (int value);
+ bool read_terrain_display_select (int value);
+ bool read_options_select_group_1 (int value);
+ bool read_radio_altitude_input_select (int value);
+ bool read_navigation_input_select (int value);
+ bool read_attitude_input_select (int value);
+ bool read_heading_input_select (int value);
+ bool read_windshear_input_select (int value);
+ bool read_input_output_discrete_type_select (int value);
+ bool read_audio_output_level (int value);
+ bool read_undefined_input_select (int value);
+
+ static bool m6_t2_is_bank_angle (Parameter<double> *agl,
+ double abs_roll_deg,
+ bool ap_engaged);
+ static bool m6_t4_is_bank_angle (Parameter<double> *agl,
+ double abs_roll_deg,
+ bool ap_engaged);
+ };
+
+ /////////////////////////////////////////////////////////////////////////////
+ // MK_VIII::FaultHandler ////////////////////////////////////////////////////
+ /////////////////////////////////////////////////////////////////////////////
+
+ class FaultHandler
+ {
+ enum
+ {
+ INOP_GPWS = 1 << 0,
+ INOP_TAD = 1 << 1
+ };
+
+ MK_VIII *mk;
+
+ static const unsigned int fault_inops[];
+
+ bool has_faults (unsigned int inop);
+
+ public:
+ // keep in sync with IOHandler::present_status()
+ typedef enum
+ {
+ FAULT_ALL_MODES_INHIBIT,
+ FAULT_GEAR_SWITCH,
+ FAULT_FLAPS_SWITCH,
+ FAULT_MOMENTARY_FLAP_OVERRIDE_INVALID,
+ FAULT_SELF_TEST_INVALID,
+ FAULT_GLIDESLOPE_CANCEL_INVALID,
+ FAULT_STEEP_APPROACH_INVALID,
+ FAULT_GPWS_INHIBIT,
+ FAULT_TA_TCF_INHIBIT,
+ FAULT_MODES14_INPUTS_INVALID,
+ FAULT_MODE5_INPUTS_INVALID,
+ FAULT_MODE6_INPUTS_INVALID,
+ FAULT_BANK_ANGLE_INPUTS_INVALID,
+ FAULT_TCF_INPUTS_INVALID,
+ N_FAULTS
+ } Fault;
+
+ bool faults[N_FAULTS];
+
+ inline FaultHandler (MK_VIII *device)
+ : mk(device) {}
+
+ void boot ();
+
+ void set_fault (Fault fault);
+ void unset_fault (Fault fault);
+
+ bool has_faults () const;
+ };
+
+ /////////////////////////////////////////////////////////////////////////////
+ // MK_VIII::IOHandler ///////////////////////////////////////////////////////
+ /////////////////////////////////////////////////////////////////////////////
+
+ class IOHandler
+ {
+ public:
+ enum Lamp
+ {
+ LAMP_NONE,
+ LAMP_GLIDESLOPE,
+ LAMP_CAUTION,
+ LAMP_WARNING
+ };
+
+ struct LampConfiguration
+ {
+ bool format2;
+ bool flashing;
+ };
+
+ struct FaultsConfiguration
+ {
+ double max_flaps_down_airspeed;
+ double max_gear_down_airspeed;
+ };
+
+ struct _s_Conf
+ {
+ const LampConfiguration *lamp;
+ const FaultsConfiguration *faults;
+ bool flap_reversal;
+ bool steep_approach_enabled;
+ bool gpws_inhibit_enabled;
+ bool momentary_flap_override_enabled;
+ bool alternate_steep_approach;
+ bool use_internal_gps;
+ bool localizer_enabled;
+ } conf;
+
+ struct _s_input_feeders
+ {
+ struct _s_discretes
+ {
+ bool landing_gear;
+ bool landing_flaps;
+ bool glideslope_inhibit;
+ bool decision_height;
+ bool autopilot_engaged;
+ } discretes;
+
+ struct _s_arinc429
+ {
+ bool uncorrected_barometric_altitude;
+ bool barometric_altitude_rate;
+ bool radio_altitude;
+ bool glideslope_deviation;
+ bool roll_angle;
+ bool localizer_deviation;
+ bool computed_airspeed;
+ bool decision_height;
+ } arinc429;
+ } input_feeders;
+
+ struct _s_inputs
+ {
+ struct _s_discretes
+ {
+ bool landing_gear; // appendix E 6.6.2, 3.15.1.4
+ bool landing_flaps; // appendix E 6.6.4, 3.15.1.2
+ bool momentary_flap_override; // appendix E 6.6.6, 3.15.1.6
+ bool self_test; // appendix E 6.6.7, 3.15.1.10
+ bool glideslope_inhibit; // appendix E 6.6.11, 3.15.1.1
+ bool glideslope_cancel; // appendix E 6.6.13, 3.15.1.5
+ bool decision_height; // appendix E 6.6.14, 3.10.2
+ bool mode6_low_volume; // appendix E 6.6.15, 3.15.1.7
+ bool audio_inhibit; // appendix E 6.6.16, 3.15.1.3
+ bool ta_tcf_inhibit; // appendix E 6.6.20, 3.15.1.9
+ bool autopilot_engaged; // appendix E 6.6.21, 3.15.1.8
+ bool steep_approach; // appendix E 6.6.25, 3.15.1.11
+ bool gpws_inhibit; // appendix E 6.6.27, 3.15.1.12
+ } discretes;
+
+ struct _s_arinc429
+ {
+ Parameter<double> uncorrected_barometric_altitude; // appendix E 6.2.1
+ Parameter<double> barometric_altitude_rate; // appendix E 6.2.2
+ Parameter<double> gps_altitude; // appendix E 6.2.4
+ Parameter<double> gps_latitude; // appendix E 6.2.7
+ Parameter<double> gps_longitude; // appendix E 6.2.8
+ Parameter<double> gps_vertical_figure_of_merit; // appendix E 6.2.13
+ Parameter<double> radio_altitude; // appendix E 6.2.29
+ Parameter<double> glideslope_deviation; // appendix E 6.2.30
+ Parameter<double> roll_angle; // appendix E 6.2.31
+ Parameter<double> localizer_deviation; // appendix E 6.2.33
+ Parameter<double> computed_airspeed; // appendix E 6.2.39
+ Parameter<double> decision_height; // appendix E 6.2.41
+ } arinc429;
+ } inputs;
+
+ struct Outputs
+ {
+ struct _s_discretes
+ {
+ bool gpws_warning; // appendix E 7.4.1, 3.15.2.5
+ bool gpws_alert; // appendix E 7.4.1, 3.15.2.6
+ bool audio_on; // appendix E 7.4.2, 3.15.2.10
+ bool gpws_inop; // appendix E 7.4.3, 3.15.2.3
+ bool tad_inop; // appendix E 7.4.3, 3.15.2.4
+ bool flap_override; // appendix E 7.4.5, 3.15.2.8
+ bool glideslope_cancel; // appendix E 7.4.6, 3.15.2.7
+ bool steep_approach; // appendix E 7.4.12, 3.15.2.9
+ } discretes;
+
+ struct _s_arinc429
+ {
+ int egpws_alert_discrete_1; // appendix E 7.1.1.1
+ int egpwc_logic_discretes; // appendix E 7.1.1.2
+ int mode6_callouts_discrete_1; // appendix E 7.1.1.3
+ int mode6_callouts_discrete_2; // appendix E 7.1.1.4
+ int egpws_alert_discrete_2; // appendix E 7.1.1.5
+ int egpwc_alert_discrete_3; // appendix E 7.1.1.6
+ } arinc429;
+ };
+
+ Outputs outputs;
+
+ struct _s_data
+ {
+ Parameter<double> barometric_altitude_rate;
+ Parameter<double> decision_height;
+ Parameter<double> geometric_altitude;
+ Parameter<double> glideslope_deviation_dots;
+ Parameter<double> gps_altitude;
+ Parameter<double> gps_latitude;
+ Parameter<double> gps_longitude;
+ Parameter<double> gps_vertical_figure_of_merit;
+ Parameter<double> localizer_deviation_dots;
+ Parameter<double> radio_altitude;
+ Parameter<double> roll_angle;
+ Parameter<double> terrain_clearance;
+ } data;
+
+ IOHandler (MK_VIII *device);
+
+ void boot ();
+ void post_boot ();
+ void power_off ();
+
+ void enter_ground ();
+ void enter_takeoff ();
+
+ void update_inputs ();
+ void update_input_faults ();
+ void update_alternate_discrete_input (bool *ptr);
+ void update_internal_latches ();
+
+ void update_egpws_alert_discrete_1 ();
+ void update_egpwc_logic_discretes ();
+ void update_mode6_callouts_discrete_1 ();
+ void update_mode6_callouts_discrete_2 ();
+ void update_egpws_alert_discrete_2 ();
+ void update_egpwc_alert_discrete_3 ();
+ void update_outputs ();
+
+ void update_lamps ();
+ void set_lamp (Lamp lamp);
+
+ bool gpws_inhibit () const;
+ bool real_flaps_down () const;
+ bool flaps_down () const;
+ bool flap_override () const;
+ bool steep_approach () const;
+ bool momentary_steep_approach_enabled () const;
+
+ void bind (SGPropertyNode *node);
+
+ private:
+
+ ///////////////////////////////////////////////////////////////////////////
+ // MK_VIII::IOHandler::TerrainClearanceFilter /////////////////////////////
+ ///////////////////////////////////////////////////////////////////////////
+
+ class TerrainClearanceFilter
+ {
+ deque< Sample<double> > samples;
+ double value;
+
+ public:
+ inline TerrainClearanceFilter ()
+ : value(0) {}
+
+ double update (double agl);
+ void reset ();
+ };
+
+ ///////////////////////////////////////////////////////////////////////////
+ // MK_VIII::IOHandler (continued) /////////////////////////////////////////
+ ///////////////////////////////////////////////////////////////////////////
+
+ MK_VIII *mk;
+
+ TerrainClearanceFilter terrain_clearance_filter;
+
+ Lamp _lamp;
+ Timer lamp_timer;
+
+ Timer audio_inhibit_fault_timer;
+ Timer landing_gear_fault_timer;
+ Timer flaps_down_fault_timer;
+ Timer momentary_flap_override_fault_timer;
+ Timer self_test_fault_timer;
+ Timer glideslope_cancel_fault_timer;
+ Timer steep_approach_fault_timer;
+ Timer gpws_inhibit_fault_timer;
+ Timer ta_tcf_inhibit_fault_timer;
+
+ bool last_landing_gear;
+ bool last_real_flaps_down;
+
+ deque< Sample< Parameter<double> > > altitude_samples;
+
+ struct
+ {
+ bool glideslope_cancel;
+ } power_saved;
+
+ void update_terrain_clearance ();
+ void reset_terrain_clearance ();
+
+ void handle_input_fault (bool test, FaultHandler::Fault fault);
+ void handle_input_fault (bool test,
+ Timer *timer,
+ double max_duration,
+ FaultHandler::Fault fault);
+
+ void tie_input (SGPropertyNode *node,
+ const char *name,
+ bool *input,
+ bool *feed = NULL);
+ void tie_input (SGPropertyNode *node,
+ const char *name,
+ Parameter<double> *input,
+ bool *feed = NULL);
+ void tie_output (SGPropertyNode *node,
+ const char *name,
+ bool *output);
+ void tie_output (SGPropertyNode *node,
+ const char *name,
+ int *output);
+
+ bool get_discrete_input (bool *ptr) const;
+ void set_discrete_input (bool *ptr, bool value);
+
+ void present_status ();
+ void present_status_section (const char *name);
+ void present_status_item (const char *name, const char *value = NULL);
+ void present_status_subitem (const char *name);
+
+ bool get_present_status () const;
+ void set_present_status (bool value);
+
+ bool *get_lamp_output (Lamp lamp);
+ };
+
+ /////////////////////////////////////////////////////////////////////////////
+ // MK_VIII::VoicePlayer /////////////////////////////////////////////////////
+ /////////////////////////////////////////////////////////////////////////////
+
+ class VoicePlayer
+ {
+ public:
+
+ ///////////////////////////////////////////////////////////////////////////
+ // MK_VIII::VoicePlayer::Voice ////////////////////////////////////////////
+ ///////////////////////////////////////////////////////////////////////////
+
+ class Voice
+ {
+ public:
+
+ /////////////////////////////////////////////////////////////////////////
+ // MK_VIII::VoicePlayer::Voice::Element ////////////////////////////////////////
+ /////////////////////////////////////////////////////////////////////////
+
+ class Element
+ {
+ public:
+ bool silence;
+
+ virtual inline void play (double volume) {}
+ virtual inline void stop () {}
+ virtual bool is_playing () = 0;
+ virtual inline void set_volume (double volume) {}
+ };
+
+ /////////////////////////////////////////////////////////////////////////
+ // MK_VIII::VoicePlayer::Voice::SampleElement ///////////////////////////
+ /////////////////////////////////////////////////////////////////////////
+
+ class SampleElement : public Element
+ {
+ SGSoundSample *_sample;
+ double _volume;
+
+ public:
+ inline SampleElement (SGSoundSample *sample, double volume = 1.0)
+ : _sample(sample), _volume(volume) { silence = false; }
+
+ virtual inline void play (double volume) { set_volume(volume); _sample->play_once(); }
+ virtual inline void stop () { _sample->stop(); }
+ virtual inline bool is_playing () { return _sample->is_playing(); }
+ virtual inline void set_volume (double volume) { _sample->set_volume(volume * _volume); }
+ };
+
+ /////////////////////////////////////////////////////////////////////////
+ // MK_VIII::VoicePlayer::Voice::SilenceElement //////////////////////////
+ /////////////////////////////////////////////////////////////////////////
+
+ class SilenceElement : public Element
+ {
+ double _duration;
+ double start_time;
+
+ public:
+ inline SilenceElement (double duration)
+ : _duration(duration) { silence = true; }
+
+ virtual inline void play (double volume) { start_time = globals->get_sim_time_sec(); }
+ virtual inline bool is_playing () { return globals->get_sim_time_sec() - start_time < _duration; }
+ };
+
+ /////////////////////////////////////////////////////////////////////////
+ // MK_VIII::VoicePlayer::Voice (continued) //////////////////////////////
+ /////////////////////////////////////////////////////////////////////////
+
+ Element *element;
+
+ inline Voice (VoicePlayer *_player)
+ : player(_player), volume(1.0), element(NULL) {}
+
+ ~Voice ();
+
+ inline void append (Element *_element) { elements.push_back(_element); }
+
+ void play ();
+ void stop (bool now);
+ void set_volume (double _volume);
+ void volume_changed ();
+ void update ();
+
+ private:
+ VoicePlayer *player;
+
+ double volume;
+
+ vector<Element *> elements;
+ vector<Element *>::iterator iter;
+
+ inline double get_volume () const { return player->volume * player->speaker.volume * volume; }
+ };
+
+ ///////////////////////////////////////////////////////////////////////////
+ // MK_VIII::VoicePlayer (continued) ///////////////////////////////////////
+ ///////////////////////////////////////////////////////////////////////////
+
+ struct
+ {
+ double volume;
+ } conf;
+
+ double volume;
+
+ Voice *voice;
+ Voice *next_voice;
+
+ struct
+ {
+ Voice *application_data_base_failed;
+ Voice *bank_angle;
+ Voice *bank_angle_bank_angle;
+ Voice *bank_angle_bank_angle_3;
+ Voice *bank_angle_inop;
+ Voice *bank_angle_pause_bank_angle;
+ Voice *bank_angle_pause_bank_angle_3;
+ Voice *callouts_inop;
+ Voice *configuration_type_invalid;
+ Voice *dont_sink;
+ Voice *dont_sink_pause_dont_sink;
+ Voice *five_hundred_above;
+ Voice *glideslope;
+ Voice *glideslope_inop;
+ Voice *gpws_inop;
+ Voice *hard_glideslope;
+ Voice *minimums;
+ Voice *minimums_minimums;
+ Voice *pull_up;
+ Voice *sink_rate;
+ Voice *sink_rate_pause_sink_rate;
+ Voice *soft_glideslope;
+ Voice *terrain;
+ Voice *terrain_pause_terrain;
+ Voice *too_low_flaps;
+ Voice *too_low_gear;
+ Voice *too_low_terrain;
+ Voice *altitude_callouts[n_altitude_callouts];
+ } voices;
+
+ inline VoicePlayer (MK_VIII *device)
+ : mk(device), speaker(this), voice(NULL), next_voice(NULL) {}
+
+ ~VoicePlayer ();
+
+ void init ();
+
+ enum
+ {
+ PLAY_NOW = 1 << 0,
+ PLAY_LOOPED = 1 << 1
+ };
+ void play (Voice *_voice, unsigned int flags = 0);
+
+ enum
+ {
+ STOP_NOW = 1 << 0
+ };
+ void stop (unsigned int flags = 0);
+
+ void set_volume (double _volume);
+ void update ();
+
+ inline void bind (SGPropertyNode *node) { speaker.bind(node); }
+
+ private:
+
+ ///////////////////////////////////////////////////////////////////////////
+ // MK_VIII::VoicePlayer::Speaker //////////////////////////////////////////
+ ///////////////////////////////////////////////////////////////////////////
+
+ class Speaker
+ {
+ VoicePlayer *player;
+
+ double pitch;
+ float position[3];
+ float orientation[3];
+ float inner_cone;
+ float outer_cone;
+ float outer_gain;
+ float reference_dist;
+ float max_dist;
+
+ template <class T>
+ inline void tie (SGPropertyNode *node, const char *name, T *ptr)
+ {
+ player->mk->properties_handler.tie
+ (node, (string("speaker/") + name).c_str(),
+ RawValueMethodsData<MK_VIII::VoicePlayer::Speaker,T,T*>
+ (*this, ptr,
+ &MK_VIII::VoicePlayer::Speaker::get_property,
+ &MK_VIII::VoicePlayer::Speaker::set_property));
+ }
+
+ template <class T>
+ inline void set_property (T *ptr, T value) { *ptr = value; update_configuration(); }
+
+ template <class T>
+ inline T get_property (T *ptr) const { return *ptr; }
+
+ public:
+ double volume;
+
+ inline Speaker (VoicePlayer *_player)
+ : player(_player),
+ volume(1),
+ pitch(1),
+ inner_cone(360),
+ outer_cone(360),
+ outer_gain(0),
+ reference_dist(3),
+ max_dist(10)
+ {
+ position[0] = 0; position[1] = 0; position[2] = 0;
+ orientation[0] = 0; orientation[1] = 0; orientation[2] = 0;
+ }
+
+ void bind (SGPropertyNode *node);
+ void update_configuration ();
+ };
+
+ ///////////////////////////////////////////////////////////////////////////
+ // MK_VIII::VoicePlayer (continued) ///////////////////////////////////////
+ ///////////////////////////////////////////////////////////////////////////
+
+ MK_VIII *mk;
+
+ Speaker speaker;
+
+ map<string, SGSoundSample *> samples;
+ vector<Voice *> _voices;
+
+ bool looped;
+ bool next_looped;
+
+ SGSoundSample *get_sample (const char *name);
+
+ inline void append (Voice *voice, Voice::Element *element) { voice->append(element); }
+ inline void append (Voice *voice, const char *sample_name) { voice->append(new Voice::SampleElement(get_sample(sample_name))); }
+ inline void append (Voice *voice, double silence) { voice->append(new Voice::SilenceElement(silence)); }
+
+ inline void make_voice (Voice **voice) { *voice = new Voice(this); _voices.push_back(*voice); }
+
+ template <class T1>
+ inline void make_voice (Voice **voice, T1 e1) { make_voice(voice); append(*voice, e1); }
+ template <class T1, class T2>
+ inline void make_voice (Voice **voice, T1 e1, T2 e2) { make_voice(voice, e1); append(*voice, e2); }
+ template <class T1, class T2, class T3>
+ inline void make_voice (Voice **voice, T1 e1, T2 e2, T3 e3) { make_voice(voice, e1, e2); append(*voice, e3); }
+ template <class T1, class T2, class T3, class T4>
+ inline void make_voice (Voice **voice, T1 e1, T2 e2, T3 e3, T4 e4) { make_voice(voice, e1, e2, e3); append(*voice, e4); }
+ };
+
+ /////////////////////////////////////////////////////////////////////////////
+ // MK_VIII::SelfTestHandler /////////////////////////////////////////////////
+ /////////////////////////////////////////////////////////////////////////////
+
+ class SelfTestHandler
+ {
+ MK_VIII *mk;
+
+ typedef enum
+ {
+ CANCEL_NONE,
+ CANCEL_SHORT,
+ CANCEL_LONG
+ } Cancel;
+
+ enum
+ {
+ ACTION_SLEEP = 1 << 0,
+ ACTION_VOICE = 1 << 1,
+ ACTION_DISCRETE_ON_OFF = 1 << 2,
+ ACTION_DONE = 1 << 3
+ };
+
+ typedef struct
+ {
+ unsigned int flags;
+ double sleep_duration;
+ bool *discrete;
+ } Action;
+
+ Cancel cancel;
+ Action action;
+ int current;
+ bool button_pressed;
+ double button_press_timestamp;
+ IOHandler::Outputs saved_outputs;
+ double sleep_start;
+
+ bool _was_here (int position);
+
+ Action sleep (double duration);
+ Action play (VoicePlayer::Voice *voice);
+ Action discrete_on (bool *discrete, double duration);
+ Action discrete_on_off (bool *discrete, double duration);
+ Action discrete_on_off (bool *discrete, VoicePlayer::Voice *voice);
+ Action done ();
+
+ Action run ();
+
+ void start ();
+ void stop ();
+ void shutdown ();
+
+ public:
+ typedef enum
+ {
+ STATE_NONE,
+ STATE_START,
+ STATE_RUNNING
+ } State;
+
+ State state;
+
+ inline SelfTestHandler (MK_VIII *device)
+ : mk(device), state(STATE_NONE), button_pressed(false) {}
+
+ inline void power_off () { stop(); }
+ inline void set_inop () { stop(); }
+ void handle_button_event (bool value);
+ bool update ();
+ };
+
+ /////////////////////////////////////////////////////////////////////////////
+ // MK_VIII::AlertHandler ////////////////////////////////////////////////////
+ /////////////////////////////////////////////////////////////////////////////
+
+ class AlertHandler
+ {
+ MK_VIII *mk;
+
+ unsigned int old_alerts;
+ unsigned int voice_alerts;
+ unsigned int repeated_alerts;
+ VoicePlayer::Voice *altitude_callout_voice;
+
+ void reset ();
+ inline bool has_alerts (unsigned int test) const { return (alerts & test) != 0; }
+ inline bool has_old_alerts (unsigned int test) const { return (old_alerts & test) != 0; }
+ inline bool must_play_voice (unsigned int test) const { return ! has_old_alerts(test) || (repeated_alerts & test) != 0; }
+ bool select_voice_alerts (unsigned int test);
+
+ public:
+ enum
+ {
+ ALERT_MODE1_PULL_UP = 1 << 0,
+ ALERT_MODE1_SINK_RATE = 1 << 1,
+
+ ALERT_MODE2A_PREFACE = 1 << 2,
+ ALERT_MODE2B_PREFACE = 1 << 3,
+ ALERT_MODE2A = 1 << 4,
+ ALERT_MODE2B = 1 << 5,
+ ALERT_MODE2B_LANDING_MODE = 1 << 6,
+ ALERT_MODE2A_ALTITUDE_GAIN = 1 << 7,
+ ALERT_MODE2A_ALTITUDE_GAIN_TERRAIN_CLOSING = 1 << 8,
+
+ ALERT_MODE3 = 1 << 9,
+
+ ALERT_MODE4_TOO_LOW_FLAPS = 1 << 10,
+ ALERT_MODE4_TOO_LOW_GEAR = 1 << 11,
+ ALERT_MODE4AB_TOO_LOW_TERRAIN = 1 << 12,
+ ALERT_MODE4C_TOO_LOW_TERRAIN = 1 << 13,
+
+ ALERT_MODE5_SOFT = 1 << 14,
+ ALERT_MODE5_HARD = 1 << 15,
+
+ ALERT_MODE6_MINIMUMS = 1 << 16,
+ ALERT_MODE6_ALTITUDE_CALLOUT = 1 << 17,
+ ALERT_MODE6_LOW_BANK_ANGLE_1 = 1 << 18,
+ ALERT_MODE6_HIGH_BANK_ANGLE_1 = 1 << 19,
+ ALERT_MODE6_LOW_BANK_ANGLE_2 = 1 << 20,
+ ALERT_MODE6_HIGH_BANK_ANGLE_2 = 1 << 21,
+ ALERT_MODE6_LOW_BANK_ANGLE_3 = 1 << 22,
+ ALERT_MODE6_HIGH_BANK_ANGLE_3 = 1 << 23,
+
+ ALERT_TCF_TOO_LOW_TERRAIN = 1 << 24
+ };
+
+ enum
+ {
+ ALERT_FLAG_REPEAT = 1 << 0
+ };
+
+ unsigned int alerts;
+
+ inline AlertHandler (MK_VIII *device)
+ : mk(device) {}
+
+ void boot ();
+ void reposition ();
+ void update ();
+
+ void set_alerts (unsigned int _alerts,
+ unsigned int flags = 0,
+ VoicePlayer::Voice *_altitude_callout_voice = NULL);
+ void unset_alerts (unsigned int _alerts);
+
+ inline void repeat_alert (unsigned int alert) { set_alerts(alert, ALERT_FLAG_REPEAT); }
+ inline void set_altitude_callout_alert (VoicePlayer::Voice *voice) { set_alerts(ALERT_MODE6_ALTITUDE_CALLOUT, 0, voice); }
+ };
+
+ /////////////////////////////////////////////////////////////////////////////
+ // MK_VIII::StateHandler ////////////////////////////////////////////////////
+ /////////////////////////////////////////////////////////////////////////////
+
+ class StateHandler
+ {
+ MK_VIII *mk;
+
+ Timer potentially_airborne_timer;
+
+ void update_ground ();
+ void enter_ground ();
+ void leave_ground ();
+
+ void update_takeoff ();
+ void enter_takeoff ();
+ void leave_takeoff ();
+
+ public:
+ bool ground;
+ bool takeoff;
+
+ inline StateHandler (MK_VIII *device)
+ : mk(device), ground(true), takeoff(true) {}
+
+ void post_reposition ();
+ void update ();
+ };
+
+ /////////////////////////////////////////////////////////////////////////////
+ // MK_VIII::Mode1Handler ////////////////////////////////////////////////////
+ /////////////////////////////////////////////////////////////////////////////
+
+ class Mode1Handler
+ {
+ MK_VIII *mk;
+
+ Timer pull_up_timer;
+ Timer sink_rate_timer;
+
+ double sink_rate_tti; // time-to-impact in minutes
+
+ double get_pull_up_bias ();
+ bool is_pull_up ();
+
+ double get_sink_rate_bias ();
+ bool is_sink_rate ();
+ double get_sink_rate_tti ();
+
+ void update_pull_up ();
+ void update_sink_rate ();
+
+ public:
+ typedef struct
+ {
+ bool flap_override_bias;
+ int min_agl;
+ double (*pull_up_min_agl1) (double vs);
+ int pull_up_max_agl1;
+ double (*pull_up_min_agl2) (double vs);
+ int pull_up_max_agl2;
+ } EnvelopesConfiguration;
+
+ struct
+ {
+ const EnvelopesConfiguration *envelopes;
+ } conf;
+
+ inline Mode1Handler (MK_VIII *device)
+ : mk(device) {}
+
+ void update ();
+ };
+
+ /////////////////////////////////////////////////////////////////////////////
+ // MK_VIII::Mode2Handler ////////////////////////////////////////////////////
+ /////////////////////////////////////////////////////////////////////////////
+
+ class Mode2Handler
+ {
+
+ ///////////////////////////////////////////////////////////////////////////
+ // MK_VIII::Mode2Handler::ClosureRateFilter ///////////////////////////////
+ ///////////////////////////////////////////////////////////////////////////
+
+ class ClosureRateFilter
+ {
+ /////////////////////////////////////////////////////////////////////////
+ // MK_VIII::Mode2Handler::ClosureRateFilter::PassFilter /////////////////
+ /////////////////////////////////////////////////////////////////////////
+
+ class PassFilter
+ {
+ double a0;
+ double a1;
+ double b1;
+
+ double last_input;
+ double last_output;
+
+ public:
+ inline PassFilter (double _a0, double _a1, double _b1)
+ : a0(_a0), a1(_a1), b1(_b1) {}
+
+ inline double filter (double input)
+ {
+ last_output = a0 * input + a1 * last_input + b1 * last_output;
+ last_input = input;
+
+ return last_output;
+ }
+
+ inline void reset ()
+ {
+ last_input = 0;
+ last_output = 0;
+ }
+ };
+
+ /////////////////////////////////////////////////////////////////////////
+ // MK_VIII::Mode2Handler::ClosureRateFilter (continued) /////////////////
+ /////////////////////////////////////////////////////////////////////////
+
+ MK_VIII *mk;
+
+ Timer timer;
+ Parameter<double> last_ra; // last radio altitude
+ Parameter<double> last_ba; // last barometric altitude
+ PassFilter ra_filter; // radio altitude rate filter
+ PassFilter ba_filter; // barometric altitude rate filter
+
+ double limit_radio_altitude_rate (double r);
+
+ public:
+ Parameter<double> output;
+
+ inline ClosureRateFilter (MK_VIII *device)
+ : mk(device),
+ ra_filter(0.05, 0, 0.95), // low-pass filter
+ ba_filter(0.93, -0.93, 0.86) {} // high-pass-filter
+
+ void init ();
+ void update ();
+ };
+
+ ///////////////////////////////////////////////////////////////////////////
+ // MK_VIII::Mode2Handler (continued) //////////////////////////////////////
+ ///////////////////////////////////////////////////////////////////////////
+
+ MK_VIII *mk;
+
+ ClosureRateFilter closure_rate_filter;
+
+ Timer takeoff_timer;
+ Timer pull_up_timer;
+
+ double a_start_time;
+ Timer a_altitude_gain_timer;
+ double a_altitude_gain_alt;
+
+ void check_pull_up (unsigned int preface_alert, unsigned int alert);
+
+ bool b_conditions ();
+
+ bool is_a ();
+ bool is_b ();
+
+ void update_a ();
+ void update_b ();
+
+ public:
+ typedef struct
+ {
+ int airspeed1;
+ int airspeed2;
+ } Configuration;
+
+ const Configuration *conf;
+
+ inline Mode2Handler (MK_VIII *device)
+ : mk(device), closure_rate_filter(device) {}
+
+ void boot ();
+ void power_off ();
+ void leave_ground ();
+ void enter_takeoff ();
+ void update ();
+ };
+
+ /////////////////////////////////////////////////////////////////////////////
+ // MK_VIII::Mode3Handler ////////////////////////////////////////////////////
+ /////////////////////////////////////////////////////////////////////////////
+
+ class Mode3Handler
+ {
+ MK_VIII *mk;
+
+ bool armed;
+ bool has_descent_alt;
+ double descent_alt;
+ double bias;
+
+ double max_alt_loss (double _bias);
+ double get_bias (double initial_bias, double alt_loss);
+ bool is (double *alt_loss);
+
+ public:
+ typedef struct
+ {
+ int min_agl;
+ int (*max_agl) (bool flap_override);
+ double (*max_alt_loss) (bool flap_override, double agl);
+ } Configuration;
+
+ const Configuration *conf;
+
+ inline Mode3Handler (MK_VIII *device)
+ : mk(device), armed(false), has_descent_alt(false) {}
+
+ void enter_takeoff ();
+ void update ();
+ };
+
+ /////////////////////////////////////////////////////////////////////////////
+ // MK_VIII::Mode4Handler ////////////////////////////////////////////////////
+ /////////////////////////////////////////////////////////////////////////////
+
+ class Mode4Handler
+ {
+ public:
+ typedef struct
+ {
+ int airspeed1;
+ int airspeed2;
+ int min_agl1;
+ double (*min_agl2) (double airspeed);
+ int min_agl3;
+ } EnvelopesConfiguration;
+
+ typedef struct
+ {
+ const EnvelopesConfiguration *ac;
+ const EnvelopesConfiguration *b;
+ } ModesConfiguration;
+
+ struct
+ {
+ VoicePlayer::Voice *voice_too_low_gear;
+ const ModesConfiguration *modes;
+ } conf;
+
+ inline Mode4Handler (MK_VIII *device)
+ : mk(device) {}
+
+ double get_upper_agl (const EnvelopesConfiguration *c);
+ void update ();
+
+ private:
+ MK_VIII *mk;
+
+ double ab_bias;
+ double ab_expanded_bias;
+ double c_bias;
+
+ const EnvelopesConfiguration *get_ab_envelope ();
+ double get_bias (double initial_bias, double min_agl);
+ void handle_alert (unsigned int alert, double min_agl, double *bias);
+
+ void update_ab ();
+ void update_ab_expanded ();
+ void update_c ();
+ };
+
+ /////////////////////////////////////////////////////////////////////////////
+ // MK_VIII::Mode5Handler ////////////////////////////////////////////////////
+ /////////////////////////////////////////////////////////////////////////////
+
+ class Mode5Handler
+ {
+ MK_VIII *mk;
+
+ Timer hard_timer;
+ Timer soft_timer;
+
+ double soft_bias;
+
+ bool is_hard ();
+ bool is_soft (double bias);
+
+ double get_soft_bias (double initial_bias);
+
+ void update_hard (bool is);
+ void update_soft (bool is);
+
+ public:
+ inline Mode5Handler (MK_VIII *device)
+ : mk(device) {}
+
+ void update ();
+ };
+
+ /////////////////////////////////////////////////////////////////////////////
+ // MK_VIII::Mode6Handler ////////////////////////////////////////////////////
+ /////////////////////////////////////////////////////////////////////////////
+
+ class Mode6Handler
+ {
+ public:
+ // keep in sync with altitude_callout_definitions[]
+ typedef enum
+ {
+ ALTITUDE_CALLOUT_1000,
+ ALTITUDE_CALLOUT_500,
+ ALTITUDE_CALLOUT_400,
+ ALTITUDE_CALLOUT_300,
+ ALTITUDE_CALLOUT_200,
+ ALTITUDE_CALLOUT_100,
+ ALTITUDE_CALLOUT_50,
+ ALTITUDE_CALLOUT_40,
+ ALTITUDE_CALLOUT_30,
+ ALTITUDE_CALLOUT_20,
+ ALTITUDE_CALLOUT_10
+ } AltitudeCallout;
+
+ typedef bool (*BankAnglePredicate) (Parameter<double> *agl,
+ double abs_roll_deg,
+ bool ap_engaged);
+
+ struct
+ {
+ bool minimums_enabled;
+ bool smart_500_enabled;
+ VoicePlayer::Voice *above_field_voice;
+
+ bool altitude_callouts_enabled[n_altitude_callouts];
+ bool bank_angle_enabled;
+ BankAnglePredicate is_bank_angle;
+ } conf;
+
+ static const int altitude_callout_definitions[];
+
+ inline Mode6Handler (MK_VIII *device)
+ : mk(device) {}
+
+ void boot ();
+ void power_off ();
+ void enter_takeoff ();
+ void leave_takeoff ();
+ void set_volume (double volume);
+ bool altitude_callouts_enabled ();
+ void update ();
+
+ private:
+ MK_VIII *mk;
+
+ bool last_decision_height;
+ Parameter<double> last_radio_altitude;
+ Parameter<double> last_altitude_above_field;
+
+ bool altitude_callouts_issued[n_altitude_callouts];
+ bool minimums_issued;
+ bool above_field_issued;
+
+ Timer runway_timer;
+ Parameter<bool> has_runway;
+
+ struct
+ {
+ double elevation; // elevation in feet
+ } runway;
+
+ void reset_minimums ();
+ void reset_altitude_callouts ();
+ bool is_playing_altitude_callout ();
+ bool is_near_minimums (double callout);
+ bool is_outside_band (double elevation, double callout);
+ bool inhibit_smart_500 ();
+
+ void update_minimums ();
+ void update_altitude_callouts ();
+
+ bool test_runway (const FGRunway *_runway);
+ bool test_airport (const FGAirport *airport);
+ void update_runway ();
+
+ void get_altitude_above_field (Parameter<double> *parameter);
+ void update_above_field_callout ();
+
+ bool is_bank_angle (double abs_roll_angle, double bias);
+ bool is_high_bank_angle ();
+ unsigned int get_bank_angle_alerts ();
+ void update_bank_angle ();
+ };
+
+ /////////////////////////////////////////////////////////////////////////////
+ // MK_VIII::TCFHandler //////////////////////////////////////////////////////
+ /////////////////////////////////////////////////////////////////////////////
+
+ class TCFHandler
+ {
+ typedef struct
+ {
+ double latitude; // latitude in degrees
+ double longitude; // longitude in degrees
+ } Position;
+
+ typedef struct
+ {
+ Position position; // position of threshold
+ double heading; // runway heading
+ } RunwayEdge;
+
+ MK_VIII *mk;
+
+ static const double k;
+
+ Timer runway_timer;
+ bool has_runway;
+
+ struct
+ {
+ Position center; // center point
+ double elevation; // elevation in feet
+ double half_length; // runway half length, in nautical miles
+ RunwayEdge edges[2]; // runway threshold and end
+ Position bias_area[4]; // vertices of the bias area
+ } runway;
+
+ double bias;
+ double *reference;
+ double initial_value;
+
+ double get_azimuth_difference (double from_lat,
+ double from_lon,
+ double to_lat,
+ double to_lon,
+ double to_heading);
+ double get_azimuth_difference (const FGRunway *_runway);
+
+ void select_runway (const FGAirport *airport, FGRunway *_runway);
+ bool test_airport (const FGAirport *airport);
+ void update_runway ();
+
+ void get_bias_area_edges (Position *edge,
+ double reciprocal,
+ double half_width_m,
+ Position *bias_edge1,
+ Position *bias_edge2);
+
+ bool is_inside_edge_triangle (RunwayEdge *edge);
+ bool is_inside_bias_area ();
+
+ bool is_tcf ();
+ bool is_rfcf ();
+
+ public:
+ struct
+ {
+ bool enabled;
+ } conf;
+
+ inline TCFHandler (MK_VIII *device)
+ : mk(device) {}
+
+ void update ();
+ };
+
+ /////////////////////////////////////////////////////////////////////////////
+ // MK_VIII (continued) //////////////////////////////////////////////////////
+ /////////////////////////////////////////////////////////////////////////////
+
+ string name;
+ int num;
+
+ PropertiesHandler properties_handler;
+ PowerHandler power_handler;
+ SystemHandler system_handler;
+ ConfigurationModule configuration_module;
+ FaultHandler fault_handler;
+ IOHandler io_handler;
+ VoicePlayer voice_player;
+ SelfTestHandler self_test_handler;
+ AlertHandler alert_handler;
+ StateHandler state_handler;
+ Mode1Handler mode1_handler;
+ Mode2Handler mode2_handler;
+ Mode3Handler mode3_handler;
+ Mode4Handler mode4_handler;
+ Mode5Handler mode5_handler;
+ Mode6Handler mode6_handler;
+ TCFHandler tcf_handler;
+
+ struct
+ {
+ int runway_database;
+ } conf;
+
+public:
+ MK_VIII (SGPropertyNode *node);
+
+ virtual void init ();
+ virtual void bind ();
+ virtual void unbind ();
+ virtual void update (double dt);
+};
+
+#endif // __INSTRUMENTS_MK_VIII_HXX