1
0
Fork 0
flightgear/src/Instrumentation/mk_viii.cxx
mfranz b07ad149ae turn a few #include paths from the "foo" form to <foo>
The quotes form is normally only used for headers with path relative
to the including file's path, though the standard doesn't strictly
mandate this. This is consistent with the rest of sg/fg, it makes the
code's intent clearer and helps to find headers. (And it's a few
milliseconds faster, too.)
2009-05-18 12:24:17 +02:00

4938 lines
139 KiB
C++
Executable file

// 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>
using std::string;
#include <Airports/runways.hxx>
#include <Airports/simple.hxx>
#ifndef _MSC_VER
# include <Include/version.h>
#endif
#include <Main/fg_props.hxx>
#include <Main/globals.hxx>
#include "instrument_mgr.hxx"
#include "mk_viii.hxx"
///////////////////////////////////////////////////////////////////////////////
// constants //////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
#define GLIDESLOPE_DOTS_TO_DDM 0.0875 // [INSTALL] 6.2.30
#define GLIDESLOPE_DDM_TO_DOTS (1 / GLIDESLOPE_DOTS_TO_DDM)
#define LOCALIZER_DOTS_TO_DDM 0.0775 // [INSTALL] 6.2.33
#define LOCALIZER_DDM_TO_DOTS (1 / LOCALIZER_DOTS_TO_DDM)
///////////////////////////////////////////////////////////////////////////////
// helpers ////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
#define assert_not_reached() assert(false)
#define n_elements(_array) (sizeof(_array) / sizeof((_array)[0]))
#define test_bits(_bits, _test) (((_bits) & (_test)) != 0)
#define mk_node(_name) (mk->properties_handler.external_properties._name)
#define mk_dinput_feed(_name) (mk->io_handler.input_feeders.discretes._name)
#define mk_dinput(_name) (mk->io_handler.inputs.discretes._name)
#define mk_ainput_feed(_name) (mk->io_handler.input_feeders.arinc429._name)
#define mk_ainput(_name) (mk->io_handler.inputs.arinc429._name)
#define mk_doutput(_name) (mk->io_handler.outputs.discretes._name)
#define mk_aoutput(_name) (mk->io_handler.outputs.arinc429._name)
#define mk_data(_name) (mk->io_handler.data._name)
#define mk_voice(_name) (mk->voice_player.voices._name)
#define mk_altitude_voice(_i) (mk->voice_player.voices.altitude_callouts[(_i)])
#define mk_alert(_name) (AlertHandler::ALERT_ ## _name)
#define mk_alert_flag(_name) (AlertHandler::ALERT_FLAG_ ## _name)
#define mk_set_alerts (mk->alert_handler.set_alerts)
#define mk_unset_alerts (mk->alert_handler.unset_alerts)
#define mk_repeat_alert (mk->alert_handler.repeat_alert)
#define mk_test_alert(_name) (mk_test_alerts(mk_alert(_name)))
#define mk_test_alerts(_test) (test_bits(mk->alert_handler.alerts, (_test)))
const double MK_VIII::TCFHandler::k = 0.25;
static double
modify_amplitude (double amplitude, double dB)
{
return amplitude * pow(10.0, dB / 20.0);
}
static double
heading_add (double h1, double h2)
{
double result = h1 + h2;
if (result >= 360)
result -= 360;
return result;
}
static double
heading_substract (double h1, double h2)
{
double result = h1 - h2;
if (result < 0)
result += 360;
return result;
}
static double
get_heading_difference (double h1, double h2)
{
double diff = h1 - h2;
if (diff < -180)
diff += 360;
else if (diff > 180)
diff -= 360;
return fabs(diff);
}
static double
get_reciprocal_heading (double h)
{
return heading_add(h, 180);
}
///////////////////////////////////////////////////////////////////////////////
// PropertiesHandler //////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
void
MK_VIII::PropertiesHandler::init ()
{
mk_node(ai_caged) = fgGetNode("/instrumentation/attitude-indicator/caged-flag", true);
mk_node(ai_roll) = fgGetNode("/instrumentation/attitude-indicator/indicated-roll-deg", true);
mk_node(ai_serviceable) = fgGetNode("/instrumentation/attitude-indicator/serviceable", true);
mk_node(altimeter_altitude) = fgGetNode("/instrumentation/altimeter/indicated-altitude-ft", true);
mk_node(altimeter_serviceable) = fgGetNode("/instrumentation/altimeter/serviceable", true);
mk_node(altitude) = fgGetNode("/position/altitude-ft", true);
mk_node(altitude_agl) = fgGetNode("/position/altitude-agl-ft", true);
mk_node(asi_serviceable) = fgGetNode("/instrumentation/airspeed-indicator/serviceable", true);
mk_node(asi_speed) = fgGetNode("/instrumentation/airspeed-indicator/indicated-speed-kt", true);
mk_node(autopilot_heading_lock) = fgGetNode("/autopilot/locks/heading", true);
mk_node(flaps) = fgGetNode("/controls/flight/flaps", true);
mk_node(gear_down) = fgGetNode("/controls/gear/gear-down", true);
mk_node(latitude) = fgGetNode("/position/latitude-deg", true);
mk_node(longitude) = fgGetNode("/position/longitude-deg", true);
mk_node(nav0_cdi_serviceable) = fgGetNode("/instrumentation/nav/cdi/serviceable", true);
mk_node(nav0_gs_distance) = fgGetNode("/instrumentation/nav/gs-distance", true);
mk_node(nav0_gs_needle_deflection) = fgGetNode("/instrumentation/nav/gs-needle-deflection", true);
mk_node(nav0_gs_serviceable) = fgGetNode("/instrumentation/nav/gs/serviceable", true);
mk_node(nav0_has_gs) = fgGetNode("/instrumentation/nav/has-gs", true);
mk_node(nav0_heading_needle_deflection) = fgGetNode("/instrumentation/nav/heading-needle-deflection", true);
mk_node(nav0_in_range) = fgGetNode("/instrumentation/nav/in-range", true);
mk_node(nav0_nav_loc) = fgGetNode("/instrumentation/nav/nav-loc", true);
mk_node(nav0_serviceable) = fgGetNode("/instrumentation/nav/serviceable", true);
mk_node(power) = fgGetNode(("/systems/electrical/outputs/" + mk->name).c_str(), mk->num, true);
mk_node(replay_state) = fgGetNode("/sim/freeze/replay-state", true);
mk_node(vs) = fgGetNode("/velocities/vertical-speed-fps", true);
}
void
MK_VIII::PropertiesHandler::unbind ()
{
vector<SGPropertyNode_ptr>::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.
samples_type::iterator iter;
// remove samples older than 15 seconds
for (iter = samples.begin(); iter != samples.end() && globals->get_sim_time_sec() - (*iter).timestamp >= 15; iter = samples.begin())
samples.erase(iter);
// append new sample
samples.push_back(Sample<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.
altitude_samples_type::iterator erase_last = altitude_samples.begin();
altitude_samples_type::iterator iter;
for (iter = altitude_samples.begin(); iter != altitude_samples.end(); iter++)
if (globals->get_sim_time_sec() - (*iter).timestamp >= 1)
erase_last = iter;
else
break;
if (erase_last != altitude_samples.begin())
altitude_samples.erase(altitude_samples.begin(), erase_last);
// update GPS data
if (conf.use_internal_gps)
{
mk_data(gps_altitude).set(mk_node(altitude)->getDoubleValue());
mk_data(gps_latitude).set(mk_node(latitude)->getDoubleValue());
mk_data(gps_longitude).set(mk_node(longitude)->getDoubleValue());
mk_data(gps_vertical_figure_of_merit).set(0.0);
}
else
{
mk_data(gps_altitude).set(&mk_ainput(gps_altitude));
mk_data(gps_latitude).set(&mk_ainput(gps_latitude));
mk_data(gps_longitude).set(&mk_ainput(gps_longitude));
mk_data(gps_vertical_figure_of_merit).set(&mk_ainput(gps_vertical_figure_of_merit));
}
// update glideslope and localizer
mk_data(glideslope_deviation_dots).set(&mk_ainput(glideslope_deviation), GLIDESLOPE_DDM_TO_DOTS);
mk_data(localizer_deviation_dots).set(&mk_ainput(localizer_deviation), LOCALIZER_DDM_TO_DOTS);
// Update geometric altitude; [SPEC] 6.7.8 provides an overview of a
// complex algorithm which combines several input sources to
// calculate the geometric altitude. Since the exact algorithm is
// not given, we fallback to a much simpler method.
if (! mk_data(gps_altitude).ncd)
mk_data(geometric_altitude).set(mk_data(gps_altitude).get());
else if (! mk_ainput(uncorrected_barometric_altitude).ncd)
mk_data(geometric_altitude).set(mk_ainput(uncorrected_barometric_altitude).get());
else
mk_data(geometric_altitude).unset();
// update terrain clearance
update_terrain_clearance();
// 3. perform sanity checks
if (! mk_data(radio_altitude).ncd && mk_data(radio_altitude).get() < 0)
mk_data(radio_altitude).unset();
if (! mk_data(decision_height).ncd && mk_data(decision_height).get() < 0)
mk_data(decision_height).unset();
if (! mk_data(gps_latitude).ncd
&& (mk_data(gps_latitude).get() < -90
|| mk_data(gps_latitude).get() > 90))
mk_data(gps_latitude).unset();
if (! mk_data(gps_longitude).ncd
&& (mk_data(gps_longitude).get() < -180
|| mk_data(gps_longitude).get() > 180))
mk_data(gps_longitude).unset();
if (! mk_data(roll_angle).ncd
&& ((mk_data(roll_angle).get() < -180)
|| (mk_data(roll_angle).get() > 180)))
mk_data(roll_angle).unset();
// 4. process input feeders requiring data computed above
if (mk_dinput_feed(decision_height))
mk_dinput(decision_height) = ! mk_data(radio_altitude).ncd
&& ! mk_data(decision_height).ncd
&& mk_data(radio_altitude).get() <= mk_data(decision_height).get();
}
void
MK_VIII::IOHandler::update_terrain_clearance ()
{
if (! mk_data(radio_altitude).ncd)
mk_data(terrain_clearance).set(terrain_clearance_filter.update(mk_data(radio_altitude).get()));
else
mk_data(terrain_clearance).unset();
}
void
MK_VIII::IOHandler::reset_terrain_clearance ()
{
terrain_clearance_filter.reset();
update_terrain_clearance();
}
void
MK_VIII::IOHandler::handle_input_fault (bool test, FaultHandler::Fault fault)
{
if (test)
{
if (! mk->fault_handler.faults[fault])
mk->fault_handler.set_fault(fault);
}
else
mk->fault_handler.unset_fault(fault);
}
void
MK_VIII::IOHandler::handle_input_fault (bool test,
Timer *timer,
double max_duration,
FaultHandler::Fault fault)
{
if (test)
{
if (! mk->fault_handler.faults[fault])
{
if (timer->start_or_elapsed() >= max_duration)
mk->fault_handler.set_fault(fault);
}
}
else
{
mk->fault_handler.unset_fault(fault);
timer->stop();
}
}
void
MK_VIII::IOHandler::update_input_faults ()
{
if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
return;
// [INSTALL] 3.15.1.3
handle_input_fault(mk_dinput(audio_inhibit),
&audio_inhibit_fault_timer,
60,
FaultHandler::FAULT_ALL_MODES_INHIBIT);
// [INSTALL] appendix E 6.6.2
handle_input_fault(mk_dinput(landing_gear)
&& ! mk_ainput(computed_airspeed).ncd
&& mk_ainput(computed_airspeed).get() > conf.faults->max_gear_down_airspeed,
&landing_gear_fault_timer,
60,
FaultHandler::FAULT_GEAR_SWITCH);
// [INSTALL] appendix E 6.6.4
handle_input_fault(flaps_down()
&& ! mk_ainput(computed_airspeed).ncd
&& mk_ainput(computed_airspeed).get() > conf.faults->max_flaps_down_airspeed,
&flaps_down_fault_timer,
60,
FaultHandler::FAULT_FLAPS_SWITCH);
// [INSTALL] appendix E 6.6.6
if (conf.momentary_flap_override_enabled)
handle_input_fault(mk_dinput(momentary_flap_override),
&momentary_flap_override_fault_timer,
15,
FaultHandler::FAULT_MOMENTARY_FLAP_OVERRIDE_INVALID);
// [INSTALL] appendix E 6.6.7
handle_input_fault(mk_dinput(self_test),
&self_test_fault_timer,
60,
FaultHandler::FAULT_SELF_TEST_INVALID);
// [INSTALL] appendix E 6.6.13
handle_input_fault(mk_dinput(glideslope_cancel),
&glideslope_cancel_fault_timer,
15,
FaultHandler::FAULT_GLIDESLOPE_CANCEL_INVALID);
// [INSTALL] appendix E 6.6.25
if (momentary_steep_approach_enabled())
handle_input_fault(mk_dinput(steep_approach),
&steep_approach_fault_timer,
15,
FaultHandler::FAULT_STEEP_APPROACH_INVALID);
// [INSTALL] appendix E 6.6.27
if (conf.gpws_inhibit_enabled)
handle_input_fault(mk_dinput(gpws_inhibit),
&gpws_inhibit_fault_timer,
5,
FaultHandler::FAULT_GPWS_INHIBIT);
// [INSTALL] does not specify a fault for this one, but it makes
// sense to have it behave like GPWS inhibit
handle_input_fault(mk_dinput(ta_tcf_inhibit),
&ta_tcf_inhibit_fault_timer,
5,
FaultHandler::FAULT_TA_TCF_INHIBIT);
// [PILOT] page 48: "In the event that required data for a
// particular function is not available, then that function is
// automatically inhibited and annunciated"
handle_input_fault(mk_data(radio_altitude).ncd
|| mk_ainput(uncorrected_barometric_altitude).ncd
|| mk_data(barometric_altitude_rate).ncd
|| mk_ainput(computed_airspeed).ncd
|| mk_data(terrain_clearance).ncd,
FaultHandler::FAULT_MODES14_INPUTS_INVALID);
if (! mk_dinput(glideslope_inhibit))
handle_input_fault(mk_data(radio_altitude).ncd,
FaultHandler::FAULT_MODE5_INPUTS_INVALID);
if (mk->mode6_handler.altitude_callouts_enabled())
handle_input_fault(mk->mode6_handler.conf.above_field_voice
? (mk_data(gps_latitude).ncd
|| mk_data(gps_longitude).ncd
|| mk_data(geometric_altitude).ncd)
: mk_data(radio_altitude).ncd,
FaultHandler::FAULT_MODE6_INPUTS_INVALID);
if (mk->mode6_handler.conf.bank_angle_enabled)
handle_input_fault(mk_data(roll_angle).ncd,
FaultHandler::FAULT_BANK_ANGLE_INPUTS_INVALID);
if (mk->tcf_handler.conf.enabled)
handle_input_fault(mk_data(radio_altitude).ncd
|| mk_data(geometric_altitude).ncd
|| mk_data(gps_latitude).ncd
|| mk_data(gps_longitude).ncd
|| mk_data(gps_vertical_figure_of_merit).ncd,
FaultHandler::FAULT_TCF_INPUTS_INVALID);
}
void
MK_VIII::IOHandler::update_alternate_discrete_input (bool *ptr)
{
assert(mk->system_handler.state == SystemHandler::STATE_ON);
if (ptr == &mk_dinput(mode6_low_volume))
{
if (mk->configuration_module.state == ConfigurationModule::STATE_OK
&& mk->self_test_handler.state == SelfTestHandler::STATE_NONE)
mk->mode6_handler.set_volume(*ptr ? modify_amplitude(1.0, -6) : 1.0);
}
else if (ptr == &mk_dinput(audio_inhibit))
{
if (mk->configuration_module.state == ConfigurationModule::STATE_OK
&& mk->self_test_handler.state == SelfTestHandler::STATE_NONE)
mk->voice_player.set_volume(*ptr ? 0.0 : mk->voice_player.conf.volume);
}
}
void
MK_VIII::IOHandler::update_internal_latches ()
{
if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
return;
// [SPEC] 6.2.1
if (conf.momentary_flap_override_enabled
&& mk_doutput(flap_override)
&& ! mk->state_handler.takeoff
&& (mk_data(radio_altitude).ncd || mk_data(radio_altitude).get() <= 50))
mk_doutput(flap_override) = false;
// [SPEC] 6.2.1
if (momentary_steep_approach_enabled())
{
if (mk_doutput(steep_approach)
&& ! mk->state_handler.takeoff
&& ((last_landing_gear && ! mk_dinput(landing_gear))
|| (last_real_flaps_down && ! real_flaps_down())))
// gear or flaps raised during approach: it's a go-around
mk_doutput(steep_approach) = false;
last_landing_gear = mk_dinput(landing_gear);
last_real_flaps_down = real_flaps_down();
}
// [SPEC] 6.2.5
if (mk_doutput(glideslope_cancel)
&& (mk_data(glideslope_deviation_dots).ncd
|| mk_data(radio_altitude).ncd
|| mk_data(radio_altitude).get() > 2000
|| mk_data(radio_altitude).get() < 30))
mk_doutput(glideslope_cancel) = false;
}
void
MK_VIII::IOHandler::update_egpws_alert_discrete_1 ()
{
if (mk->voice_player.voice)
{
const struct
{
int bit;
VoicePlayer::Voice *voice;
} voices[] = {
{ 11, mk_voice(sink_rate_pause_sink_rate) },
{ 12, mk_voice(pull_up) },
{ 13, mk_voice(terrain) },
{ 13, mk_voice(terrain_pause_terrain) },
{ 14, mk_voice(dont_sink_pause_dont_sink) },
{ 15, mk_voice(too_low_gear) },
{ 16, mk_voice(too_low_flaps) },
{ 17, mk_voice(too_low_terrain) },
{ 18, mk_voice(soft_glideslope) },
{ 18, mk_voice(hard_glideslope) },
{ 19, mk_voice(minimums_minimums) }
};
for (int i = 0; i < n_elements(voices); i++)
if (voices[i].voice == mk->voice_player.voice)
{
mk_aoutput(egpws_alert_discrete_1) = 1 << voices[i].bit;
return;
}
}
mk_aoutput(egpws_alert_discrete_1) = 0;
}
void
MK_VIII::IOHandler::update_egpwc_logic_discretes ()
{
mk_aoutput(egpwc_logic_discretes) = 0;
if (mk->state_handler.takeoff)
mk_aoutput(egpwc_logic_discretes) |= 1 << 18;
static const struct
{
int bit;
unsigned int alerts;
} logic[] = {
{ 13, mk_alert(TCF_TOO_LOW_TERRAIN) },
{ 19, mk_alert(MODE1_SINK_RATE) },
{ 20, mk_alert(MODE1_PULL_UP) },
{ 21, mk_alert(MODE2A_PREFACE) | mk_alert(MODE2B_PREFACE) | mk_alert(MODE2B_LANDING_MODE) | mk_alert(MODE2A_ALTITUDE_GAIN_TERRAIN_CLOSING) },
{ 22, mk_alert(MODE2A) | mk_alert(MODE2B) },
{ 23, mk_alert(MODE3) },
{ 24, mk_alert(MODE4_TOO_LOW_FLAPS) | mk_alert(MODE4_TOO_LOW_GEAR) | mk_alert(MODE4AB_TOO_LOW_TERRAIN) | mk_alert(MODE4C_TOO_LOW_TERRAIN) },
{ 25, mk_alert(MODE5_SOFT) | mk_alert(MODE5_HARD) }
};
for (int i = 0; i < n_elements(logic); i++)
if (mk_test_alerts(logic[i].alerts))
mk_aoutput(egpwc_logic_discretes) |= 1 << logic[i].bit;
}
void
MK_VIII::IOHandler::update_mode6_callouts_discrete_1 ()
{
if (mk->voice_player.voice)
{
const struct
{
int bit;
VoicePlayer::Voice *voice;
} voices[] = {
{ 11, mk_voice(minimums_minimums) },
{ 16, mk_altitude_voice(Mode6Handler::ALTITUDE_CALLOUT_10) },
{ 17, mk_altitude_voice(Mode6Handler::ALTITUDE_CALLOUT_20) },
{ 18, mk_altitude_voice(Mode6Handler::ALTITUDE_CALLOUT_30) },
{ 19, mk_altitude_voice(Mode6Handler::ALTITUDE_CALLOUT_40) },
{ 20, mk_altitude_voice(Mode6Handler::ALTITUDE_CALLOUT_50) },
{ 23, mk_altitude_voice(Mode6Handler::ALTITUDE_CALLOUT_100) },
{ 24, mk_altitude_voice(Mode6Handler::ALTITUDE_CALLOUT_200) },
{ 25, mk_altitude_voice(Mode6Handler::ALTITUDE_CALLOUT_300) }
};
for (int i = 0; i < n_elements(voices); i++)
if (voices[i].voice == mk->voice_player.voice)
{
mk_aoutput(mode6_callouts_discrete_1) = 1 << voices[i].bit;
return;
}
}
mk_aoutput(mode6_callouts_discrete_1) = 0;
}
void
MK_VIII::IOHandler::update_mode6_callouts_discrete_2 ()
{
if (mk->voice_player.voice)
{
const struct
{
int bit;
VoicePlayer::Voice *voice;
} voices[] = {
{ 11, mk_altitude_voice(Mode6Handler::ALTITUDE_CALLOUT_400) },
{ 12, mk_altitude_voice(Mode6Handler::ALTITUDE_CALLOUT_500) },
{ 13, mk_altitude_voice(Mode6Handler::ALTITUDE_CALLOUT_1000) },
{ 18, mk_voice(bank_angle_pause_bank_angle) },
{ 18, mk_voice(bank_angle_pause_bank_angle_3) },
{ 23, mk_voice(five_hundred_above) }
};
for (int i = 0; i < n_elements(voices); i++)
if (voices[i].voice == mk->voice_player.voice)
{
mk_aoutput(mode6_callouts_discrete_2) = 1 << voices[i].bit;
return;
}
}
mk_aoutput(mode6_callouts_discrete_2) = 0;
}
void
MK_VIII::IOHandler::update_egpws_alert_discrete_2 ()
{
mk_aoutput(egpws_alert_discrete_2) = 1 << 27; // Terrain NA
if (mk_doutput(glideslope_cancel))
mk_aoutput(egpws_alert_discrete_2) |= 1 << 11;
if (mk_doutput(gpws_alert))
mk_aoutput(egpws_alert_discrete_2) |= 1 << 12;
if (mk_doutput(gpws_warning))
mk_aoutput(egpws_alert_discrete_2) |= 1 << 13;
if (mk_doutput(gpws_inop))
mk_aoutput(egpws_alert_discrete_2) |= 1 << 14;
if (mk_doutput(audio_on))
mk_aoutput(egpws_alert_discrete_2) |= 1 << 16;
if (mk_doutput(tad_inop))
mk_aoutput(egpws_alert_discrete_2) |= 1 << 24;
if (mk->fault_handler.has_faults())
mk_aoutput(egpws_alert_discrete_2) |= 1 << 25;
}
void
MK_VIII::IOHandler::update_egpwc_alert_discrete_3 ()
{
mk_aoutput(egpwc_alert_discrete_3) = 1 << 17 | 1 << 18;
if (mk->fault_handler.faults[FaultHandler::FAULT_MODES14_INPUTS_INVALID])
mk_aoutput(egpwc_alert_discrete_3) |= 1 << 11;
if (mk->fault_handler.faults[FaultHandler::FAULT_MODE5_INPUTS_INVALID])
mk_aoutput(egpwc_alert_discrete_3) |= 1 << 12;
if (mk->fault_handler.faults[FaultHandler::FAULT_MODE6_INPUTS_INVALID])
mk_aoutput(egpwc_alert_discrete_3) |= 1 << 13;
if (mk->fault_handler.faults[FaultHandler::FAULT_BANK_ANGLE_INPUTS_INVALID])
mk_aoutput(egpwc_alert_discrete_3) |= 1 << 14;
if (mk_doutput(tad_inop))
mk_aoutput(egpwc_alert_discrete_3) |= 1 << 16;
}
void
MK_VIII::IOHandler::update_outputs ()
{
if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
return;
mk_doutput(audio_on) = ! mk_dinput(audio_inhibit)
&& mk->voice_player.voice
&& ! mk->voice_player.voice->element->silence;
update_egpws_alert_discrete_1();
update_egpwc_logic_discretes();
update_mode6_callouts_discrete_1();
update_mode6_callouts_discrete_2();
update_egpws_alert_discrete_2();
update_egpwc_alert_discrete_3();
}
bool *
MK_VIII::IOHandler::get_lamp_output (Lamp lamp)
{
switch (lamp)
{
case LAMP_GLIDESLOPE:
return &mk_doutput(gpws_alert);
case LAMP_CAUTION:
return conf.lamp->format2 ? &mk_doutput(gpws_alert) : &mk_doutput(gpws_warning);
case LAMP_WARNING:
return &mk_doutput(gpws_warning);
default:
assert_not_reached();
return NULL;
}
}
void
MK_VIII::IOHandler::update_lamps ()
{
if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
return;
if (_lamp != LAMP_NONE && conf.lamp->flashing)
{
// [SPEC] 6.9.3: 70 cycles per minute
if (lamp_timer.elapsed() >= 60.0 / 70.0 / 2.0)
{
bool *output = get_lamp_output(_lamp);
*output = ! *output;
lamp_timer.start();
}
}
}
void
MK_VIII::IOHandler::set_lamp (Lamp lamp)
{
if (lamp == _lamp)
return;
_lamp = lamp;
mk_doutput(gpws_warning) = false;
mk_doutput(gpws_alert) = false;
if (lamp != LAMP_NONE)
{
*get_lamp_output(lamp) = true;
lamp_timer.start();
}
}
bool
MK_VIII::IOHandler::gpws_inhibit () const
{
return conf.gpws_inhibit_enabled && mk_dinput(gpws_inhibit);
}
bool
MK_VIII::IOHandler::real_flaps_down () const
{
return conf.flap_reversal ? mk_dinput(landing_flaps) : ! mk_dinput(landing_flaps);
}
bool
MK_VIII::IOHandler::flaps_down () const
{
return flap_override() ? true : real_flaps_down();
}
bool
MK_VIII::IOHandler::flap_override () const
{
return conf.momentary_flap_override_enabled ? mk_doutput(flap_override) : false;
}
bool
MK_VIII::IOHandler::steep_approach () const
{
if (conf.steep_approach_enabled)
// If alternate action was configured in category 13, we return
// the value of the input discrete (which should be connected to a
// latching steep approach cockpit switch). Otherwise, we return
// the value of the output discrete.
return conf.alternate_steep_approach ? mk_dinput(steep_approach) : mk_doutput(steep_approach);
else
return false;
}
bool
MK_VIII::IOHandler::momentary_steep_approach_enabled () const
{
return conf.steep_approach_enabled && ! conf.alternate_steep_approach;
}
void
MK_VIII::IOHandler::tie_input (SGPropertyNode *node,
const char *name,
bool *input,
bool *feed)
{
mk->properties_handler.tie(node, (string("inputs/discretes/") + name).c_str(), RawValueMethodsData<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;
SGSoundMgr *soundmgr = globals->get_soundmgr();
if (soundmgr->is_working() == false)
{
return NULL;
}
SGSoundSample *sample = soundmgr->find(refname.str());
if (! sample)
{
SGPath sample_path(globals->get_fg_root());
sample_path.append("Sounds/mk-viii");
string filename = string(name) + ".wav";
try
{
sample = new SGSoundSample(sample_path.c_str(), filename.c_str());
}
catch (const sg_exception &e)
{
SG_LOG(SG_INSTR, SG_ALERT, "Error loading MK VIII sound sample \"" + filename + "\": " + e.getFormattedMessage());
exit(1);
}
soundmgr->add(sample, refname.str());
samples[refname.str()] = sample;
}
return sample;
}
void
MK_VIII::VoicePlayer::play (Voice *_voice, unsigned int flags)
{
if (test_bits(flags, PLAY_NOW) || ! voice || voice->element->silence)
{
if (voice)
voice->stop(true);
voice = _voice;
looped = test_bits(flags, PLAY_LOOPED);
next_voice = NULL;
next_looped = false;
voice->play();
}
else
{
next_voice = _voice;
next_looped = test_bits(flags, PLAY_LOOPED);
}
}
void
MK_VIII::VoicePlayer::stop (unsigned int flags)
{
if (voice)
{
voice->stop(test_bits(flags, STOP_NOW));
if (voice->element)
looped = false;
else
voice = NULL;
next_voice = NULL;
}
}
void
MK_VIII::VoicePlayer::set_volume (double _volume)
{
volume = _volume;
if (voice)
voice->volume_changed();
}
void
MK_VIII::VoicePlayer::update ()
{
if (voice)
{
voice->update();
if (next_voice)
{
if (! voice->element || voice->element->silence)
{
voice = next_voice;
looped = next_looped;
next_voice = NULL;
next_looped = false;
voice->play();
}
}
else
{
if (! voice->element)
{
if (looped)
voice->play();
else
voice = NULL;
}
}
}
}
///////////////////////////////////////////////////////////////////////////////
// SelfTestHandler ////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
bool
MK_VIII::SelfTestHandler::_was_here (int position)
{
if (position > current)
{
current = position;
return false;
}
else
return true;
}
MK_VIII::SelfTestHandler::Action
MK_VIII::SelfTestHandler::sleep (double duration)
{
Action _action = { ACTION_SLEEP, duration, NULL };
return _action;
}
MK_VIII::SelfTestHandler::Action
MK_VIII::SelfTestHandler::play (VoicePlayer::Voice *voice)
{
mk->voice_player.play(voice);
Action _action = { ACTION_VOICE, 0, NULL };
return _action;
}
MK_VIII::SelfTestHandler::Action
MK_VIII::SelfTestHandler::discrete_on (bool *discrete, double duration)
{
*discrete = true;
return sleep(duration);
}
MK_VIII::SelfTestHandler::Action
MK_VIII::SelfTestHandler::discrete_on_off (bool *discrete, double duration)
{
*discrete = true;
Action _action = { ACTION_SLEEP | ACTION_DISCRETE_ON_OFF, duration, discrete };
return _action;
}
MK_VIII::SelfTestHandler::Action
MK_VIII::SelfTestHandler::discrete_on_off (bool *discrete, VoicePlayer::Voice *voice)
{
*discrete = true;
mk->voice_player.play(voice);
Action _action = { ACTION_VOICE | ACTION_DISCRETE_ON_OFF, 0, discrete };
return _action;
}
MK_VIII::SelfTestHandler::Action
MK_VIII::SelfTestHandler::done ()
{
Action _action = { ACTION_DONE, 0, NULL };
return _action;
}
MK_VIII::SelfTestHandler::Action
MK_VIII::SelfTestHandler::run ()
{
// Note that "Terrain INOP" and "Terrain NA" are or'ed to the same
// output discrete (see [SPEC] 6.9.3.5).
#define was_here() was_here_offset(0)
#define was_here_offset(offset) _was_here(__LINE__ * 20 + (offset))
if (! was_here())
{
if (mk->configuration_module.state == ConfigurationModule::STATE_INVALID_DATABASE)
return play(mk_voice(application_data_base_failed));
else if (mk->configuration_module.state == ConfigurationModule::STATE_INVALID_AIRCRAFT_TYPE)
return play(mk_voice(configuration_type_invalid));
}
if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
return done();
if (! was_here())
return discrete_on(&mk_doutput(gpws_inop), 0.7);
if (! was_here())
return sleep(0.7); // W/S INOP
if (! was_here())
return discrete_on(&mk_doutput(tad_inop), 0.7);
if (! was_here())
{
mk_doutput(gpws_inop) = false;
// do not disable tad_inop since we must enable Terrain NA
// do not disable W/S INOP since we do not emulate it
return sleep(0.7); // Terrain NA
}
if (! was_here())
{
mk_doutput(tad_inop) = false; // disable Terrain NA
if (mk->io_handler.conf.momentary_flap_override_enabled)
return discrete_on_off(&mk_doutput(flap_override), 1.0);
}
if (! was_here())
return discrete_on_off(&mk_doutput(audio_on), 1.0);
if (! was_here())
{
if (mk->io_handler.momentary_steep_approach_enabled())
return discrete_on_off(&mk_doutput(steep_approach), 1.0);
}
if (! was_here())
{
if (mk_dinput(glideslope_inhibit))
goto glideslope_end;
else
{
if (mk->fault_handler.faults[FaultHandler::FAULT_MODE5_INPUTS_INVALID])
goto glideslope_inop;
}
}
if (! was_here())
return discrete_on_off(&mk_doutput(gpws_alert), mk_voice(glideslope));
if (! was_here())
return discrete_on_off(&mk_doutput(glideslope_cancel), 0.7);
goto glideslope_end;
glideslope_inop:
if (! was_here())
return play(mk_voice(glideslope_inop));
glideslope_end:
if (! was_here())
{
if (mk->fault_handler.faults[FaultHandler::FAULT_MODES14_INPUTS_INVALID])
goto gpws_inop;
}
if (! was_here())
return discrete_on_off(&mk_doutput(gpws_warning), mk_voice(pull_up));
goto gpws_end;
gpws_inop:
if (! was_here())
return play(mk_voice(gpws_inop));
gpws_end:
if (! was_here())
{
if (mk_dinput(self_test)) // proceed to long self test
goto long_test;
}
if (! was_here())
{
if (mk->mode6_handler.conf.bank_angle_enabled
&& mk->fault_handler.faults[FaultHandler::FAULT_BANK_ANGLE_INPUTS_INVALID])
return play(mk_voice(bank_angle_inop));
}
if (! was_here())
{
if (mk->mode6_handler.altitude_callouts_enabled()
&& mk->fault_handler.faults[FaultHandler::FAULT_MODE6_INPUTS_INVALID])
return play(mk_voice(callouts_inop));
}
if (! was_here())
return done();
long_test:
if (! was_here())
{
mk_doutput(gpws_inop) = true;
// do not enable W/S INOP, since we do not emulate it
mk_doutput(tad_inop) = true; // Terrain INOP, Terrain NA
return play(mk_voice(sink_rate));
}
if (! was_here())
return play(mk_voice(pull_up));
if (! was_here())
return play(mk_voice(terrain));
if (! was_here())
return play(mk_voice(pull_up));
if (! was_here())
return play(mk_voice(dont_sink));
if (! was_here())
return play(mk_voice(too_low_terrain));
if (! was_here())
return play(mk->mode4_handler.conf.voice_too_low_gear);
if (! was_here())
return play(mk_voice(too_low_flaps));
if (! was_here())
return play(mk_voice(too_low_terrain));
if (! was_here())
return play(mk_voice(glideslope));
if (! was_here())
{
if (mk->mode6_handler.conf.bank_angle_enabled)
return play(mk_voice(bank_angle));
}
if (! was_here())
{
if (! mk->mode6_handler.altitude_callouts_enabled())
goto callouts_disabled;
}
if (! was_here())
{
if (mk->mode6_handler.conf.minimums_enabled)
return play(mk_voice(minimums));
}
if (! was_here())
{
if (mk->mode6_handler.conf.above_field_voice)
return play(mk->mode6_handler.conf.above_field_voice);
}
for (int i = 0; i < n_altitude_callouts; i++)
if (! was_here_offset(i))
{
if (mk->mode6_handler.conf.altitude_callouts_enabled[i])
return play(mk_altitude_voice(i));
}
if (! was_here())
{
if (mk->mode6_handler.conf.smart_500_enabled)
return play(mk_altitude_voice(Mode6Handler::ALTITUDE_CALLOUT_500));
}
goto callouts_end;
callouts_disabled:
if (! was_here())
return play(mk_voice(minimums_minimums));
callouts_end:
if (! was_here())
{
if (mk->tcf_handler.conf.enabled)
return play(mk_voice(too_low_terrain));
}
return done();
}
void
MK_VIII::SelfTestHandler::start ()
{
assert(state == STATE_START);
memcpy(&saved_outputs, &mk->io_handler.outputs, sizeof(mk->io_handler.outputs));
memset(&mk->io_handler.outputs, 0, sizeof(mk->io_handler.outputs));
// [SPEC] 6.10.6: "The self-test results are annunciated, at 6db
// lower than the normal audio level selected for the aircraft"
mk->voice_player.set_volume(modify_amplitude(mk->voice_player.conf.volume, -6));
if (mk_dinput(mode6_low_volume))
mk->mode6_handler.set_volume(1.0);
state = STATE_RUNNING;
cancel = CANCEL_NONE;
memset(&action, 0, sizeof(action));
current = 0;
}
void
MK_VIII::SelfTestHandler::stop ()
{
if (state != STATE_NONE)
{
if (state != STATE_START)
{
mk->voice_player.stop(VoicePlayer::STOP_NOW);
mk->voice_player.set_volume(mk_dinput(audio_inhibit) ? 0.0 : mk->voice_player.conf.volume);
if (mk_dinput(mode6_low_volume))
mk->mode6_handler.set_volume(modify_amplitude(1.0, -6));
memcpy(&mk->io_handler.outputs, &saved_outputs, sizeof(mk->io_handler.outputs));
}
button_pressed = false;
state = STATE_NONE;
}
}
void
MK_VIII::SelfTestHandler::handle_button_event (bool value)
{
if (state == STATE_NONE)
{
if (value)
state = STATE_START;
}
else if (state == STATE_START)
{
if (value)
state = STATE_NONE; // cancel the not-yet-started test
}
else if (cancel == CANCEL_NONE)
{
if (value)
{
assert(! button_pressed);
button_pressed = true;
button_press_timestamp = globals->get_sim_time_sec();
}
else
{
if (button_pressed)
{
if (globals->get_sim_time_sec() - button_press_timestamp < 2)
cancel = CANCEL_SHORT;
else
cancel = CANCEL_LONG;
}
}
}
}
bool
MK_VIII::SelfTestHandler::update ()
{
if (state == STATE_NONE)
return false;
else if (state == STATE_START)
{
if (mk->state_handler.ground && ! mk->io_handler.steep_approach())
start();
else
{
state = STATE_NONE;
return false;
}
}
else
{
if (button_pressed && cancel == CANCEL_NONE)
{
if (globals->get_sim_time_sec() - button_press_timestamp >= 2)
cancel = CANCEL_LONG;
}
}
if (! mk->state_handler.ground || cancel != CANCEL_NONE)
{
stop();
return false;
}
if (test_bits(action.flags, ACTION_SLEEP))
{
if (globals->get_sim_time_sec() - sleep_start < action.sleep_duration)
return true;
}
if (test_bits(action.flags, ACTION_VOICE))
{
if (mk->voice_player.voice)
return true;
}
if (test_bits(action.flags, ACTION_DISCRETE_ON_OFF))
*action.discrete = false;
action = run();
if (test_bits(action.flags, ACTION_SLEEP))
sleep_start = globals->get_sim_time_sec();
if (test_bits(action.flags, ACTION_DONE))
{
stop();
return false;
}
return true;
}
///////////////////////////////////////////////////////////////////////////////
// AlertHandler ///////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
bool
MK_VIII::AlertHandler::select_voice_alerts (unsigned int test)
{
if (has_alerts(test))
{
voice_alerts = alerts & test;
return true;
}
else
{
voice_alerts &= ~test;
if (voice_alerts == 0)
mk->voice_player.stop();
return false;
}
}
void
MK_VIII::AlertHandler::boot ()
{
reset();
}
void
MK_VIII::AlertHandler::reposition ()
{
reset();
mk->io_handler.set_lamp(IOHandler::LAMP_NONE);
mk->voice_player.stop(VoicePlayer::STOP_NOW);
}
void
MK_VIII::AlertHandler::reset ()
{
alerts = 0;
old_alerts = 0;
voice_alerts = 0;
repeated_alerts = 0;
altitude_callout_voice = NULL;
}
void
MK_VIII::AlertHandler::update ()
{
if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
return;
// Lamps and voices are prioritized according to [SPEC] 6.9.2.
//
// Voices which can interrupt other voices (VoicePlayer::PLAY_NOW)
// are specified by [INSTALL] appendix E 5.3.5.
//
// When a voice is overriden by a higher priority voice and the
// overriding voice stops, we restore the previous voice if it was
// looped (this is not specified by [SPEC] but seems to make sense).
// update lamp
if (has_alerts(ALERT_MODE1_PULL_UP | ALERT_MODE2A | ALERT_MODE2B))
mk->io_handler.set_lamp(IOHandler::LAMP_WARNING);
else if (has_alerts(ALERT_MODE1_SINK_RATE
| ALERT_MODE2A_PREFACE
| ALERT_MODE2B_PREFACE
| ALERT_MODE2A_ALTITUDE_GAIN_TERRAIN_CLOSING
| ALERT_MODE2A_ALTITUDE_GAIN
| ALERT_MODE2B_LANDING_MODE
| ALERT_MODE3
| ALERT_MODE4_TOO_LOW_FLAPS
| ALERT_MODE4_TOO_LOW_GEAR
| ALERT_MODE4AB_TOO_LOW_TERRAIN
| ALERT_MODE4C_TOO_LOW_TERRAIN
| ALERT_TCF_TOO_LOW_TERRAIN))
mk->io_handler.set_lamp(IOHandler::LAMP_CAUTION);
else if (has_alerts(ALERT_MODE5_SOFT | ALERT_MODE5_HARD))
mk->io_handler.set_lamp(IOHandler::LAMP_GLIDESLOPE);
else
mk->io_handler.set_lamp(IOHandler::LAMP_NONE);
// update voice
if (select_voice_alerts(ALERT_MODE1_PULL_UP))
{
if (! has_old_alerts(ALERT_MODE1_PULL_UP))
{
if (mk->voice_player.voice != mk_voice(sink_rate_pause_sink_rate))
mk->voice_player.play(mk_voice(sink_rate), VoicePlayer::PLAY_NOW);
mk->voice_player.play(mk_voice(pull_up), VoicePlayer::PLAY_LOOPED);
}
}
else if (select_voice_alerts(ALERT_MODE2A_PREFACE | ALERT_MODE2B_PREFACE))
{
if (! has_old_alerts(ALERT_MODE2A_PREFACE | ALERT_MODE2B_PREFACE))
mk->voice_player.play(mk_voice(terrain_pause_terrain), VoicePlayer::PLAY_NOW);
}
else if (select_voice_alerts(ALERT_MODE2A | ALERT_MODE2B))
{
if (mk->voice_player.voice != mk_voice(pull_up))
mk->voice_player.play(mk_voice(pull_up), VoicePlayer::PLAY_NOW | VoicePlayer::PLAY_LOOPED);
}
else if (select_voice_alerts(ALERT_MODE2A_ALTITUDE_GAIN_TERRAIN_CLOSING | ALERT_MODE2B_LANDING_MODE))
{
if (mk->voice_player.voice != mk_voice(terrain))
mk->voice_player.play(mk_voice(terrain), VoicePlayer::PLAY_LOOPED);
}
else if (select_voice_alerts(ALERT_MODE6_MINIMUMS))
{
if (! has_old_alerts(ALERT_MODE6_MINIMUMS))
mk->voice_player.play(mk_voice(minimums_minimums));
}
else if (select_voice_alerts(ALERT_MODE4AB_TOO_LOW_TERRAIN | ALERT_MODE4C_TOO_LOW_TERRAIN))
{
if (must_play_voice(ALERT_MODE4AB_TOO_LOW_TERRAIN | ALERT_MODE4C_TOO_LOW_TERRAIN))
mk->voice_player.play(mk_voice(too_low_terrain));
}
else if (select_voice_alerts(ALERT_TCF_TOO_LOW_TERRAIN))
{
if (must_play_voice(ALERT_TCF_TOO_LOW_TERRAIN))
mk->voice_player.play(mk_voice(too_low_terrain));
}
else if (select_voice_alerts(ALERT_MODE6_ALTITUDE_CALLOUT))
{
if (! has_old_alerts(ALERT_MODE6_ALTITUDE_CALLOUT))
{
assert(altitude_callout_voice != NULL);
mk->voice_player.play(altitude_callout_voice);
}
}
else if (select_voice_alerts(ALERT_MODE4_TOO_LOW_GEAR))
{
if (must_play_voice(ALERT_MODE4_TOO_LOW_GEAR))
mk->voice_player.play(mk->mode4_handler.conf.voice_too_low_gear);
}
else if (select_voice_alerts(ALERT_MODE4_TOO_LOW_FLAPS))
{
if (must_play_voice(ALERT_MODE4_TOO_LOW_FLAPS))
mk->voice_player.play(mk_voice(too_low_flaps));
}
else if (select_voice_alerts(ALERT_MODE1_SINK_RATE))
{
if (must_play_voice(ALERT_MODE1_SINK_RATE))
mk->voice_player.play(mk_voice(sink_rate_pause_sink_rate));
// [SPEC] 6.2.1: "During the time that the voice message for the
// outer envelope is inhibited and the caution/warning lamp is
// on, the Mode 5 alert message is allowed to annunciate for
// excessive glideslope deviation conditions."
else if (mk->voice_player.voice != mk_voice(sink_rate_pause_sink_rate)
&& mk->voice_player.next_voice != mk_voice(sink_rate_pause_sink_rate))
{
if (has_alerts(ALERT_MODE5_HARD))
{
voice_alerts |= ALERT_MODE5_HARD;
if (mk->voice_player.voice != mk_voice(hard_glideslope))
mk->voice_player.play(mk_voice(hard_glideslope), VoicePlayer::PLAY_LOOPED);
}
else if (has_alerts(ALERT_MODE5_SOFT))
{
voice_alerts |= ALERT_MODE5_SOFT;
if (must_play_voice(ALERT_MODE5_SOFT))
mk->voice_player.play(mk_voice(soft_glideslope));
}
}
}
else if (select_voice_alerts(ALERT_MODE3))
{
if (must_play_voice(ALERT_MODE3))
mk->voice_player.play(mk_voice(dont_sink_pause_dont_sink));
}
else if (select_voice_alerts(ALERT_MODE5_HARD))
{
if (mk->voice_player.voice != mk_voice(hard_glideslope))
mk->voice_player.play(mk_voice(hard_glideslope), VoicePlayer::PLAY_LOOPED);
}
else if (select_voice_alerts(ALERT_MODE5_SOFT))
{
if (must_play_voice(ALERT_MODE5_SOFT))
mk->voice_player.play(mk_voice(soft_glideslope));
}
else if (select_voice_alerts(ALERT_MODE6_LOW_BANK_ANGLE_3))
{
if (mk->voice_player.voice != mk_voice(bank_angle_bank_angle_3))
mk->voice_player.play(mk_voice(bank_angle_bank_angle_3), VoicePlayer::PLAY_LOOPED);
}
else if (select_voice_alerts(ALERT_MODE6_HIGH_BANK_ANGLE_3))
{
if (mk->voice_player.voice != mk_voice(bank_angle_pause_bank_angle_3))
mk->voice_player.play(mk_voice(bank_angle_pause_bank_angle_3), VoicePlayer::PLAY_LOOPED);
}
else if (select_voice_alerts(ALERT_MODE6_LOW_BANK_ANGLE_2))
{
if (! has_old_alerts(ALERT_MODE6_LOW_BANK_ANGLE_2 | ALERT_MODE6_HIGH_BANK_ANGLE_2))
mk->voice_player.play(mk_voice(bank_angle_bank_angle));
}
else if (select_voice_alerts(ALERT_MODE6_HIGH_BANK_ANGLE_2))
{
if (! has_old_alerts(ALERT_MODE6_LOW_BANK_ANGLE_2 | ALERT_MODE6_HIGH_BANK_ANGLE_2))
mk->voice_player.play(mk_voice(bank_angle_pause_bank_angle));
}
else if (select_voice_alerts(ALERT_MODE6_LOW_BANK_ANGLE_1))
{
if (! has_old_alerts(ALERT_MODE6_LOW_BANK_ANGLE_1 | ALERT_MODE6_HIGH_BANK_ANGLE_1))
mk->voice_player.play(mk_voice(bank_angle_bank_angle));
}
else if (select_voice_alerts(ALERT_MODE6_HIGH_BANK_ANGLE_1))
{
if (! has_old_alerts(ALERT_MODE6_LOW_BANK_ANGLE_1 | ALERT_MODE6_HIGH_BANK_ANGLE_1))
mk->voice_player.play(mk_voice(bank_angle_pause_bank_angle));
}
// set new state
old_alerts = alerts;
repeated_alerts = 0;
altitude_callout_voice = NULL;
}
void
MK_VIII::AlertHandler::set_alerts (unsigned int _alerts,
unsigned int flags,
VoicePlayer::Voice *_altitude_callout_voice)
{
alerts |= _alerts;
if (test_bits(flags, ALERT_FLAG_REPEAT))
repeated_alerts |= _alerts;
if (_altitude_callout_voice)
altitude_callout_voice = _altitude_callout_voice;
}
void
MK_VIII::AlertHandler::unset_alerts (unsigned int _alerts)
{
alerts &= ~_alerts;
repeated_alerts &= ~_alerts;
}
///////////////////////////////////////////////////////////////////////////////
// StateHandler ///////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
void
MK_VIII::StateHandler::update_ground ()
{
if (ground)
{
if (! mk_ainput(computed_airspeed).ncd && mk_ainput(computed_airspeed).get() > 60
&& ! mk_data(radio_altitude).ncd && mk_data(radio_altitude).get() > 30)
{
if (potentially_airborne_timer.start_or_elapsed() > 1)
leave_ground();
}
else
potentially_airborne_timer.stop();
}
else
{
if (! mk_ainput(computed_airspeed).ncd && mk_ainput(computed_airspeed).get() < 40
&& ! mk_data(radio_altitude).ncd && mk_data(radio_altitude).get() < 30)
enter_ground();
}
}
void
MK_VIII::StateHandler::enter_ground ()
{
ground = true;
mk->io_handler.enter_ground();
// [SPEC] 6.0.1 does not specify this, but it seems to be an
// omission; at this point, we must make sure that we are in takeoff
// mode (otherwise the next takeoff might happen in approach mode).
if (! takeoff)
enter_takeoff();
}
void
MK_VIII::StateHandler::leave_ground ()
{
ground = false;
mk->mode2_handler.leave_ground();
}
void
MK_VIII::StateHandler::update_takeoff ()
{
if (takeoff)
{
// [SPEC] 6.0.2 is somewhat confusing: it mentions hardcoded
// terrain clearance (500, 750) and airspeed (178, 200) values,
// but it also mentions the mode 4A boundary, which varies as a
// function of aircraft type. We consider that the mentioned
// values are examples, and that we should use the mode 4A upper
// boundary.
if (! mk_data(terrain_clearance).ncd
&& ! mk_ainput(computed_airspeed).ncd
&& mk_data(terrain_clearance).get() > mk->mode4_handler.get_upper_agl(mk->mode4_handler.conf.modes->ac))
leave_takeoff();
}
else
{
if (! mk_data(radio_altitude).ncd
&& mk_data(radio_altitude).get() < mk->mode4_handler.conf.modes->b->min_agl1
&& mk->io_handler.flaps_down()
&& mk_dinput(landing_gear))
enter_takeoff();
}
}
void
MK_VIII::StateHandler::enter_takeoff ()
{
takeoff = true;
mk->io_handler.enter_takeoff();
mk->mode2_handler.enter_takeoff();
mk->mode3_handler.enter_takeoff();
mk->mode6_handler.enter_takeoff();
}
void
MK_VIII::StateHandler::leave_takeoff ()
{
takeoff = false;
mk->mode6_handler.leave_takeoff();
}
void
MK_VIII::StateHandler::post_reposition ()
{
// Synchronize the state with the current situation.
bool _ground = !
(! mk_ainput(computed_airspeed).ncd && mk_ainput(computed_airspeed).get() > 60
&& ! mk_data(radio_altitude).ncd && mk_data(radio_altitude).get() > 30);
bool _takeoff = _ground;
if (ground && ! _ground)
leave_ground();
else if (! ground && _ground)
enter_ground();
if (takeoff && ! _takeoff)
leave_takeoff();
else if (! takeoff && _takeoff)
enter_takeoff();
}
void
MK_VIII::StateHandler::update ()
{
if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
return;
update_ground();
update_takeoff();
}
///////////////////////////////////////////////////////////////////////////////
// Mode1Handler ///////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
double
MK_VIII::Mode1Handler::get_pull_up_bias ()
{
// [PILOT] page 54: "Steep Approach has priority over Flap Override
// if selected simultaneously."
if (mk->io_handler.steep_approach())
return 200;
else if (conf.envelopes->flap_override_bias && mk->io_handler.flap_override())
return 300;
else
return 0;
}
bool
MK_VIII::Mode1Handler::is_pull_up ()
{
if (! mk->io_handler.gpws_inhibit()
&& ! mk->state_handler.ground // [1]
&& ! mk_data(radio_altitude).ncd
&& ! mk_data(barometric_altitude_rate).ncd
&& mk_data(radio_altitude).get() > conf.envelopes->min_agl)
{
if (mk_data(radio_altitude).get() < conf.envelopes->pull_up_max_agl1)
{
if (mk_data(radio_altitude).get() < conf.envelopes->pull_up_min_agl1(mk_data(barometric_altitude_rate).get() + get_pull_up_bias()))
return true;
}
else if (mk_data(radio_altitude).get() < conf.envelopes->pull_up_max_agl2)
{
if (mk_data(radio_altitude).get() < conf.envelopes->pull_up_min_agl2(mk_data(barometric_altitude_rate).get() + get_pull_up_bias()))
return true;
}
}
return false;
}
void
MK_VIII::Mode1Handler::update_pull_up ()
{
if (is_pull_up())
{
if (! mk_test_alert(MODE1_PULL_UP))
{
// [SPEC] 6.2.1: at least one sink rate must be issued
// before switching to pull up; if no sink rate has
// occurred, a 0.2 second delay is used.
if (mk->voice_player.voice == mk_voice(sink_rate_pause_sink_rate)
|| pull_up_timer.start_or_elapsed() >= 0.2)
mk_set_alerts(mk_alert(MODE1_PULL_UP));
}
}
else
{
pull_up_timer.stop();
mk_unset_alerts(mk_alert(MODE1_PULL_UP));
}
}
double
MK_VIII::Mode1Handler::get_sink_rate_bias ()
{
// [PILOT] page 54: "Steep Approach has priority over Flap Override
// if selected simultaneously."
if (mk->io_handler.steep_approach())
return 500;
else if (conf.envelopes->flap_override_bias && mk->io_handler.flap_override())
return 300;
else if (! mk_data(glideslope_deviation_dots).ncd)
{
double bias = 0;
if (mk_data(glideslope_deviation_dots).get() <= -2)
bias = 300;
else if (mk_data(glideslope_deviation_dots).get() < 0)
bias = -150 * mk_data(glideslope_deviation_dots).get();
if (mk_data(radio_altitude).get() < 100)
bias *= 0.01 * mk_data(radio_altitude).get();
return bias;
}
else
return 0;
}
bool
MK_VIII::Mode1Handler::is_sink_rate ()
{
return ! mk->io_handler.gpws_inhibit()
&& ! mk->state_handler.ground // [1]
&& ! mk_data(radio_altitude).ncd
&& ! mk_data(barometric_altitude_rate).ncd
&& mk_data(radio_altitude).get() > conf.envelopes->min_agl
&& mk_data(radio_altitude).get() < 2450
&& mk_data(radio_altitude).get() < -572 - 0.6035 * (mk_data(barometric_altitude_rate).get() + get_sink_rate_bias());
}
double
MK_VIII::Mode1Handler::get_sink_rate_tti ()
{
return mk_data(radio_altitude).get() / fabs(mk_data(barometric_altitude_rate).get());
}
void
MK_VIII::Mode1Handler::update_sink_rate ()
{
if (is_sink_rate())
{
if (mk_test_alert(MODE1_SINK_RATE))
{
double tti = get_sink_rate_tti();
if (tti <= sink_rate_tti * 0.8)
{
// ~20% degradation, warn again and store the new reference tti
sink_rate_tti = tti;
mk_repeat_alert(mk_alert(MODE1_SINK_RATE));
}
}
else
{
if (sink_rate_timer.start_or_elapsed() >= 0.8)
{
mk_set_alerts(mk_alert(MODE1_SINK_RATE));
sink_rate_tti = get_sink_rate_tti();
}
}
}
else
{
sink_rate_timer.stop();
mk_unset_alerts(mk_alert(MODE1_SINK_RATE));
}
}
void
MK_VIII::Mode1Handler::update ()
{
if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
return;
update_pull_up();
update_sink_rate();
}
///////////////////////////////////////////////////////////////////////////////
// Mode2Handler ///////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
double
MK_VIII::Mode2Handler::ClosureRateFilter::limit_radio_altitude_rate (double r)
{
// Limit radio altitude rate according to aircraft configuration,
// allowing maximum sensitivity during cruise while providing
// progressively less sensitivity during the landing phases of
// flight.
if (! mk_data(glideslope_deviation_dots).ncd && fabs(mk_data(glideslope_deviation_dots).get()) <= 2)
{ // gs deviation <= +- 2 dots
if (mk_dinput(landing_gear) && mk->io_handler.flaps_down())
SG_CLAMP_RANGE(r, -1000.0, 3000.0);
else if (mk_dinput(landing_gear) || mk->io_handler.flaps_down())
SG_CLAMP_RANGE(r, 0.0, 4000.0);
else
SG_CLAMP_RANGE(r, 1000.0, 5000.0);
}
else
{ // no ILS, or gs deviation > +- 2 dots
if (mk_dinput(landing_gear) && mk->io_handler.flaps_down())
SG_CLAMP_RANGE(r, 0.0, 4000.0);
else if (mk_dinput(landing_gear) || mk->io_handler.flaps_down())
SG_CLAMP_RANGE(r, 1000.0, 5000.0);
// else no clamp
}
return r;
}
void
MK_VIII::Mode2Handler::ClosureRateFilter::init ()
{
timer.stop();
last_ra.set(&mk_data(radio_altitude));
last_ba.set(&mk_ainput(uncorrected_barometric_altitude));
ra_filter.reset();
ba_filter.reset();
output.unset();
}
void
MK_VIII::Mode2Handler::ClosureRateFilter::update ()
{
double elapsed = timer.start_or_elapsed();
if (elapsed < 1)
return;
if (! mk_data(radio_altitude).ncd && ! mk_ainput(uncorrected_barometric_altitude).ncd)
{
if (! last_ra.ncd && ! last_ba.ncd)
{
// compute radio and barometric altitude rates (positive value = descent)
double ra_rate = -(mk_data(radio_altitude).get() - last_ra.get()) / elapsed * 60;
double ba_rate = -(mk_ainput(uncorrected_barometric_altitude).get() - last_ba.get()) / elapsed * 60;
// limit radio altitude rate according to aircraft configuration
ra_rate = limit_radio_altitude_rate(ra_rate);
// apply a low-pass filter to the radio altitude rate
ra_rate = ra_filter.filter(ra_rate);
// apply a high-pass filter to the barometric altitude rate
ba_rate = ba_filter.filter(ba_rate);
// combine both rates to obtain a closure rate
output.set(ra_rate + ba_rate);
}
else
output.unset();
}
else
{
ra_filter.reset();
ba_filter.reset();
output.unset();
}
timer.start();
last_ra.set(&mk_data(radio_altitude));
last_ba.set(&mk_ainput(uncorrected_barometric_altitude));
}
bool
MK_VIII::Mode2Handler::b_conditions ()
{
return mk->io_handler.flaps_down()
|| (! mk_data(glideslope_deviation_dots).ncd && fabs(mk_data(glideslope_deviation_dots).get()) < 2)
|| takeoff_timer.running;
}
bool
MK_VIII::Mode2Handler::is_a ()
{
if (! mk->io_handler.gpws_inhibit()
&& ! mk->state_handler.ground // [1]
&& ! mk_data(radio_altitude).ncd
&& ! mk_ainput(computed_airspeed).ncd
&& ! closure_rate_filter.output.ncd
&& ! b_conditions())
{
if (mk_data(radio_altitude).get() < 1220)
{
if (mk_data(radio_altitude).get() < -1579 + 0.7895 * closure_rate_filter.output.get())
return true;
}
else
{
double upper_limit;
if (mk_ainput(computed_airspeed).get() <= conf->airspeed1)
upper_limit = 1650;
else if (mk_ainput(computed_airspeed).get() >= conf->airspeed2)
upper_limit = 2450;
else
upper_limit = 1650 + 8.9 * (mk_ainput(computed_airspeed).get() - conf->airspeed1);
if (mk_data(radio_altitude).get() < upper_limit)
{
if (mk_data(radio_altitude).get() < 522 + 0.1968 * closure_rate_filter.output.get())
return true;
}
}
}
return false;
}
bool
MK_VIII::Mode2Handler::is_b ()
{
if (! mk->io_handler.gpws_inhibit()
&& ! mk->state_handler.ground // [1]
&& ! mk_data(radio_altitude).ncd
&& ! mk_data(barometric_altitude_rate).ncd
&& ! closure_rate_filter.output.ncd
&& b_conditions()
&& mk_data(radio_altitude).get() < 789)
{
double lower_limit;
if (mk->io_handler.flaps_down())
{
if (mk_data(barometric_altitude_rate).get() > -400)
lower_limit = 200;
else if (mk_data(barometric_altitude_rate).get() < -1000)
lower_limit = 600;
else
lower_limit = -66.777 - 0.667 * mk_data(barometric_altitude_rate).get();
}
else
lower_limit = 30;
if (mk_data(radio_altitude).get() > lower_limit)
{
if (mk_data(radio_altitude).get() < -1579 + 0.7895 * closure_rate_filter.output.get())
return true;
}
}
return false;
}
void
MK_VIII::Mode2Handler::check_pull_up (unsigned int preface_alert,
unsigned int alert)
{
if (pull_up_timer.running)
{
if (pull_up_timer.elapsed() >= 1)
{
mk_unset_alerts(preface_alert);
mk_set_alerts(alert);
}
}
else
{
if (! mk->voice_player.voice)
pull_up_timer.start();
}
}
void
MK_VIII::Mode2Handler::update_a ()
{
if (is_a())
{
if (mk_test_alert(MODE2A_PREFACE))
check_pull_up(mk_alert(MODE2A_PREFACE), mk_alert(MODE2A));
else if (! mk_test_alert(MODE2A))
{
mk_unset_alerts(mk_alert(MODE2A_ALTITUDE_GAIN) | mk_alert(MODE2A_ALTITUDE_GAIN_TERRAIN_CLOSING));
mk_set_alerts(mk_alert(MODE2A_PREFACE));
a_start_time = globals->get_sim_time_sec();
pull_up_timer.stop();
}
}
else
{
if (mk_test_alert(MODE2A_ALTITUDE_GAIN))
{
if (mk->io_handler.gpws_inhibit()
|| mk->state_handler.ground // [1]
|| a_altitude_gain_timer.elapsed() >= 45
|| mk_data(radio_altitude).ncd
|| mk_ainput(uncorrected_barometric_altitude).ncd
|| mk_ainput(uncorrected_barometric_altitude).get() - a_altitude_gain_alt >= 300
// [PILOT] page 12: "the visual alert will remain on
// until [...] landing flaps or the flap override switch
// is activated"
|| mk->io_handler.flaps_down())
{
// exit altitude gain mode
a_altitude_gain_timer.stop();
mk_unset_alerts(mk_alert(MODE2A_ALTITUDE_GAIN) | mk_alert(MODE2A_ALTITUDE_GAIN_TERRAIN_CLOSING));
}
else
{
// [SPEC] 6.2.2.2: "If the terrain starts to fall away
// during this altitude gain time, the voice will shut
// off"
if (closure_rate_filter.output.get() < 0)
mk_unset_alerts(mk_alert(MODE2A_ALTITUDE_GAIN_TERRAIN_CLOSING));
}
}
else if (mk_test_alerts(mk_alert(MODE2A_PREFACE) | mk_alert(MODE2A)))
{
mk_unset_alerts(mk_alert(MODE2A_PREFACE) | mk_alert(MODE2A));
if (! mk->io_handler.gpws_inhibit()
&& ! mk->state_handler.ground // [1]
&& globals->get_sim_time_sec() - a_start_time > 3
&& ! mk->io_handler.flaps_down()
&& ! mk_data(radio_altitude).ncd
&& ! mk_ainput(uncorrected_barometric_altitude).ncd)
{
// [SPEC] 6.2.2.2: mode 2A envelope violated for more
// than 3 seconds, enable altitude gain feature
a_altitude_gain_timer.start();
a_altitude_gain_alt = mk_ainput(uncorrected_barometric_altitude).get();
unsigned int alerts = mk_alert(MODE2A_ALTITUDE_GAIN);
if (closure_rate_filter.output.get() > 0)
alerts |= mk_alert(MODE2A_ALTITUDE_GAIN_TERRAIN_CLOSING);
mk_set_alerts(alerts);
}
}
}
}
void
MK_VIII::Mode2Handler::update_b ()
{
bool b = is_b();
// handle normal mode
if (b && (! mk_dinput(landing_gear) || ! mk->io_handler.flaps_down()))
{
if (mk_test_alert(MODE2B_PREFACE))
check_pull_up(mk_alert(MODE2B_PREFACE), mk_alert(MODE2B));
else if (! mk_test_alert(MODE2B))
{
mk_set_alerts(mk_alert(MODE2B_PREFACE));
pull_up_timer.stop();
}
}
else
mk_unset_alerts(mk_alert(MODE2B_PREFACE) | mk_alert(MODE2B));
// handle landing mode ([SPEC] 6.2.2.3: "If both landing gear and
// flaps are in the landing configuration, then the message will be
// Terrain")
if (b && mk_dinput(landing_gear) && mk->io_handler.flaps_down())
mk_set_alerts(mk_alert(MODE2B_LANDING_MODE));
else
mk_unset_alerts(mk_alert(MODE2B_LANDING_MODE));
}
void
MK_VIII::Mode2Handler::boot ()
{
closure_rate_filter.init();
}
void
MK_VIII::Mode2Handler::power_off ()
{
// [SPEC] 6.0.4: "This latching function is not power saved and a
// system reset will force it false."
takeoff_timer.stop();
}
void
MK_VIII::Mode2Handler::leave_ground ()
{
// takeoff, reset timer
takeoff_timer.start();
}
void
MK_VIII::Mode2Handler::enter_takeoff ()
{
// reset timer, in case it's a go-around
takeoff_timer.start();
}
void
MK_VIII::Mode2Handler::update ()
{
if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
return;
closure_rate_filter.update();
if (takeoff_timer.running && takeoff_timer.elapsed() >= 60)
takeoff_timer.stop();
update_a();
update_b();
}
///////////////////////////////////////////////////////////////////////////////
// Mode3Handler ///////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
double
MK_VIII::Mode3Handler::max_alt_loss (double _bias)
{
return conf->max_alt_loss(mk->io_handler.flap_override(), mk_data(radio_altitude).get()) + mk_data(radio_altitude).get() * _bias;
}
double
MK_VIII::Mode3Handler::get_bias (double initial_bias, double alt_loss)
{
if (mk_data(radio_altitude).get() > 0)
while (alt_loss > max_alt_loss(initial_bias))
initial_bias += 0.2;
return initial_bias;
}
bool
MK_VIII::Mode3Handler::is (double *alt_loss)
{
if (has_descent_alt)
{
int max_agl = conf->max_agl(mk->io_handler.flap_override());
if (mk_ainput(uncorrected_barometric_altitude).ncd
|| mk_ainput(uncorrected_barometric_altitude).get() > descent_alt
|| mk_data(radio_altitude).ncd
|| mk_data(radio_altitude).get() > max_agl)
{
armed = false;
has_descent_alt = false;
}
else
{
// gear/flaps: [SPEC] 1.3.1.3
if (! mk->io_handler.gpws_inhibit()
&& ! mk->state_handler.ground // [1]
&& (! mk_dinput(landing_gear) || ! mk->io_handler.flaps_down())
&& ! mk_data(barometric_altitude_rate).ncd
&& ! mk_ainput(uncorrected_barometric_altitude).ncd
&& ! mk_data(radio_altitude).ncd
&& mk_data(barometric_altitude_rate).get() < 0)
{
double _alt_loss = descent_alt - mk_ainput(uncorrected_barometric_altitude).get();
if (armed || (mk_data(radio_altitude).get() > conf->min_agl
&& mk_data(radio_altitude).get() < max_agl
&& _alt_loss > max_alt_loss(0)))
{
*alt_loss = _alt_loss;
return true;
}
}
}
}
else
{
if (! mk_data(barometric_altitude_rate).ncd
&& ! mk_ainput(uncorrected_barometric_altitude).ncd
&& mk_data(barometric_altitude_rate).get() < 0)
{
has_descent_alt = true;
descent_alt = mk_ainput(uncorrected_barometric_altitude).get();
}
}
return false;
}
void
MK_VIII::Mode3Handler::enter_takeoff ()
{
armed = false;
has_descent_alt = false;
}
void
MK_VIII::Mode3Handler::update ()
{
if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
return;
if (mk->state_handler.takeoff)
{
double alt_loss;
if (! mk->state_handler.ground /* [1] */ && is(&alt_loss))
{
if (mk_test_alert(MODE3))
{
double new_bias = get_bias(bias, alt_loss);
if (new_bias > bias)
{
bias = new_bias;
mk_repeat_alert(mk_alert(MODE3));
}
}
else
{
armed = true;
bias = get_bias(0.2, alt_loss);
mk_set_alerts(mk_alert(MODE3));
}
return;
}
}
mk_unset_alerts(mk_alert(MODE3));
}
///////////////////////////////////////////////////////////////////////////////
// Mode4Handler ///////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
// FIXME: for turbofans, the upper limit should be reduced from 1000
// to 800 ft if a sudden change in radio altitude is detected, in
// order to reduce nuisance alerts caused by overflying another
// aircraft (see [PILOT] page 16).
double
MK_VIII::Mode4Handler::get_upper_agl (const EnvelopesConfiguration *c)
{
if (mk_ainput(computed_airspeed).get() >= c->airspeed2)
return c->min_agl3;
else if (mk_ainput(computed_airspeed).get() >= c->airspeed1)
return c->min_agl2(mk_ainput(computed_airspeed).get());
else
return c->min_agl1;
}
const MK_VIII::Mode4Handler::EnvelopesConfiguration *
MK_VIII::Mode4Handler::get_ab_envelope ()
{
// [SPEC] 6.2.4.1: "The Mode 4B envelope can also be selected by
// setting flaps to landing configuration or by selecting flap
// override."
return mk_dinput(landing_gear) || mk->io_handler.flaps_down()
? conf.modes->b
: conf.modes->ac;
}
double
MK_VIII::Mode4Handler::get_bias (double initial_bias, double min_agl)
{
while (mk_data(radio_altitude).get() < min_agl - min_agl * initial_bias)
initial_bias += 0.2;
return initial_bias;
}
void
MK_VIII::Mode4Handler::handle_alert (unsigned int alert,
double min_agl,
double *bias)
{
if (mk_test_alerts(alert))
{
double new_bias = get_bias(*bias, min_agl);
if (new_bias > *bias)
{
*bias = new_bias;
mk_repeat_alert(alert);
}
}
else
{
*bias = get_bias(0.2, min_agl);
mk_set_alerts(alert);
}
}
void
MK_VIII::Mode4Handler::update_ab ()
{
if (! mk->io_handler.gpws_inhibit()
&& ! mk->state_handler.ground
&& ! mk->state_handler.takeoff
&& ! mk_data(radio_altitude).ncd
&& ! mk_ainput(computed_airspeed).ncd
&& mk_data(radio_altitude).get() > 30)
{
const EnvelopesConfiguration *c = get_ab_envelope();
if (mk_ainput(computed_airspeed).get() < c->airspeed1)
{
if (mk_dinput(landing_gear))
{
if (! mk->io_handler.flaps_down() && mk_data(radio_altitude).get() < c->min_agl1)
{
handle_alert(mk_alert(MODE4_TOO_LOW_FLAPS), c->min_agl1, &ab_bias);
return;
}
}
else
{
if (mk_data(radio_altitude).get() < c->min_agl1)
{
handle_alert(mk_alert(MODE4_TOO_LOW_GEAR), c->min_agl1, &ab_bias);
return;
}
}
}
}
mk_unset_alerts(mk_alert(MODE4_TOO_LOW_FLAPS) | mk_alert(MODE4_TOO_LOW_GEAR));
}
void
MK_VIII::Mode4Handler::update_ab_expanded ()
{
if (! mk->io_handler.gpws_inhibit()
&& ! mk->state_handler.ground
&& ! mk->state_handler.takeoff
&& ! mk_data(radio_altitude).ncd
&& ! mk_ainput(computed_airspeed).ncd
&& mk_data(radio_altitude).get() > 30)
{
const EnvelopesConfiguration *c = get_ab_envelope();
if (mk_ainput(computed_airspeed).get() >= c->airspeed1)
{
double min_agl = get_upper_agl(c);
if (mk_data(radio_altitude).get() < min_agl)
{
handle_alert(mk_alert(MODE4AB_TOO_LOW_TERRAIN), min_agl, &ab_expanded_bias);
return;
}
}
}
mk_unset_alerts(mk_alert(MODE4AB_TOO_LOW_TERRAIN));
}
void
MK_VIII::Mode4Handler::update_c ()
{
if (! mk->io_handler.gpws_inhibit()
&& ! mk->state_handler.ground // [1]
&& mk->state_handler.takeoff
&& ! mk_data(radio_altitude).ncd
&& ! mk_data(terrain_clearance).ncd
&& mk_data(radio_altitude).get() > 30
&& (! mk_dinput(landing_gear) || ! mk->io_handler.flaps_down())
&& mk_data(radio_altitude).get() < get_upper_agl(conf.modes->ac)
&& mk_data(radio_altitude).get() < mk_data(terrain_clearance).get())
handle_alert(mk_alert(MODE4C_TOO_LOW_TERRAIN), mk_data(terrain_clearance).get(), &c_bias);
else
mk_unset_alerts(mk_alert(MODE4C_TOO_LOW_TERRAIN));
}
void
MK_VIII::Mode4Handler::update ()
{
if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
return;
update_ab();
update_ab_expanded();
update_c();
}
///////////////////////////////////////////////////////////////////////////////
// Mode5Handler ///////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
bool
MK_VIII::Mode5Handler::is_hard ()
{
if (mk_data(radio_altitude).get() > 30
&& mk_data(radio_altitude).get() < 300
&& mk_data(glideslope_deviation_dots).get() > 2)
{
if (mk_data(radio_altitude).get() < 150)
{
if (mk_data(radio_altitude).get() > 293 - 71.43 * mk_data(glideslope_deviation_dots).get())
return true;
}
else
return true;
}
return false;
}
bool
MK_VIII::Mode5Handler::is_soft (double bias)
{
if (mk_data(radio_altitude).get() > 30)
{
double bias_dots = 1.3 * bias;
if (mk_data(glideslope_deviation_dots).get() > 1.3 + bias_dots)
{
if (mk_data(radio_altitude).get() < 150)
{
if (mk_data(radio_altitude).get() > 243 - 71.43 * (mk_data(glideslope_deviation_dots).get() - bias_dots))
return true;
}
else
{
double upper_limit;
if (mk_data(barometric_altitude_rate).ncd)
upper_limit = 1000;
else
{
if (mk_data(barometric_altitude_rate).get() >= 0)
upper_limit = 500;
else if (mk_data(barometric_altitude_rate).get() < -500)
upper_limit = 1000;
else
upper_limit = -mk_data(barometric_altitude_rate).get() + 500;
}
if (mk_data(radio_altitude).get() < upper_limit)
return true;
}
}
}
return false;
}
double
MK_VIII::Mode5Handler::get_soft_bias (double initial_bias)
{
while (is_soft(initial_bias))
initial_bias += 0.2;
return initial_bias;
}
void
MK_VIII::Mode5Handler::update_hard (bool is)
{
if (is)
{
if (! mk_test_alert(MODE5_HARD))
{
if (hard_timer.start_or_elapsed() >= 0.8)
mk_set_alerts(mk_alert(MODE5_HARD));
}
}
else
{
hard_timer.stop();
mk_unset_alerts(mk_alert(MODE5_HARD));
}
}
void
MK_VIII::Mode5Handler::update_soft (bool is)
{
if (is)
{
if (! mk_test_alert(MODE5_SOFT))
{
double new_bias = get_soft_bias(soft_bias);
if (new_bias > soft_bias)
{
soft_bias = new_bias;
mk_repeat_alert(mk_alert(MODE5_SOFT));
}
}
else
{
if (soft_timer.start_or_elapsed() >= 0.8)
{
soft_bias = get_soft_bias(0.2);
mk_set_alerts(mk_alert(MODE5_SOFT));
}
}
}
else
{
soft_timer.stop();
mk_unset_alerts(mk_alert(MODE5_SOFT));
}
}
void
MK_VIII::Mode5Handler::update ()
{
if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
return;
if (! mk->io_handler.gpws_inhibit()
&& ! mk->state_handler.ground // [1]
&& ! mk_dinput(glideslope_inhibit) // not on backcourse
&& ! mk_data(radio_altitude).ncd
&& ! mk_data(glideslope_deviation_dots).ncd
&& (! mk->io_handler.conf.localizer_enabled
|| mk_data(localizer_deviation_dots).ncd
|| mk_data(radio_altitude).get() < 500
|| fabs(mk_data(localizer_deviation_dots).get()) < 2)
&& (! mk->state_handler.takeoff || mk->io_handler.flaps_down())
&& mk_dinput(landing_gear)
&& ! mk_doutput(glideslope_cancel))
{
update_hard(is_hard());
update_soft(is_soft(0));
}
else
{
update_hard(false);
update_soft(false);
}
}
///////////////////////////////////////////////////////////////////////////////
// Mode6Handler ///////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
// keep sorted in descending order
const int MK_VIII::Mode6Handler::altitude_callout_definitions[] =
{ 1000, 500, 400, 300, 200, 100, 50, 40, 30, 20, 10 };
void
MK_VIII::Mode6Handler::reset_minimums ()
{
minimums_issued = false;
}
void
MK_VIII::Mode6Handler::reset_altitude_callouts ()
{
for (int i = 0; i < n_altitude_callouts; i++)
altitude_callouts_issued[i] = false;
}
bool
MK_VIII::Mode6Handler::is_playing_altitude_callout ()
{
for (int i = 0; i < n_altitude_callouts; i++)
if (mk->voice_player.voice == mk_altitude_voice(i)
|| mk->voice_player.next_voice == mk_altitude_voice(i))
return true;
return false;
}
bool
MK_VIII::Mode6Handler::is_near_minimums (double callout)
{
// [SPEC] 6.4.2
if (! mk_data(decision_height).ncd)
{
double diff = callout - mk_data(decision_height).get();
if (mk_data(radio_altitude).get() >= 200)
{
if (fabs(diff) <= 30)
return true;
}
else
{
if (diff >= -3 && diff <= 6)
return true;
}
}
return false;
}
bool
MK_VIII::Mode6Handler::is_outside_band (double elevation, double callout)
{
// [SPEC] 6.4.2
return elevation < callout - (elevation > 150 ? 20 : 10);
}
bool
MK_VIII::Mode6Handler::inhibit_smart_500 ()
{
// [SPEC] 6.4.3
if (! mk_data(glideslope_deviation_dots).ncd
&& ! mk_dinput(glideslope_inhibit) // backcourse
&& ! mk_doutput(glideslope_cancel))
{
if (mk->io_handler.conf.localizer_enabled
&& ! mk_data(localizer_deviation_dots).ncd)
{
if (fabs(mk_data(localizer_deviation_dots).get()) <= 2)
return true;
}
else
{
if (fabs(mk_data(glideslope_deviation_dots).get()) <= 2)
return true;
}
}
return false;
}
void
MK_VIII::Mode6Handler::boot ()
{
if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
return;
last_decision_height = mk_dinput(decision_height);
last_radio_altitude.set(&mk_data(radio_altitude));
// [SPEC] 6.4.2
for (int i = 0; i < n_altitude_callouts; i++)
altitude_callouts_issued[i] = ! mk_data(radio_altitude).ncd
&& mk_data(radio_altitude).get() <= altitude_callout_definitions[i];
// extrapolated from [SPEC] 6.4.2
minimums_issued = mk_dinput(decision_height);
if (conf.above_field_voice)
{
update_runway();
get_altitude_above_field(&last_altitude_above_field);
// extrapolated from [SPEC] 6.4.2
above_field_issued = ! last_altitude_above_field.ncd
&& last_altitude_above_field.get() < 550;
}
}
void
MK_VIII::Mode6Handler::power_off ()
{
runway_timer.stop();
}
void
MK_VIII::Mode6Handler::enter_takeoff ()
{
reset_altitude_callouts(); // [SPEC] 6.4.2
reset_minimums(); // omitted by [SPEC]; common sense
}
void
MK_VIII::Mode6Handler::leave_takeoff ()
{
reset_altitude_callouts(); // [SPEC] 6.0.2
reset_minimums(); // [SPEC] 6.0.2
}
void
MK_VIII::Mode6Handler::set_volume (double volume)
{
mk_voice(minimums_minimums)->set_volume(volume);
mk_voice(five_hundred_above)->set_volume(volume);
for (int i = 0; i < n_altitude_callouts; i++)
mk_altitude_voice(i)->set_volume(volume);
}
bool
MK_VIII::Mode6Handler::altitude_callouts_enabled ()
{
if (conf.minimums_enabled || conf.smart_500_enabled || conf.above_field_voice)
return true;
for (int i = 0; i < n_altitude_callouts; i++)
if (conf.altitude_callouts_enabled[i])
return true;
return false;
}
void
MK_VIII::Mode6Handler::update_minimums ()
{
if (! mk->io_handler.gpws_inhibit()
&& (mk->voice_player.voice == mk_voice(minimums_minimums)
|| mk->voice_player.next_voice == mk_voice(minimums_minimums)))
goto end;
if (! mk->io_handler.gpws_inhibit()
&& ! mk->state_handler.ground // [1]
&& conf.minimums_enabled
&& ! minimums_issued
&& mk_dinput(landing_gear)
&& mk_dinput(decision_height)
&& ! last_decision_height)
{
minimums_issued = true;
// If an altitude callout is playing, stop it now so that the
// minimums callout can be played (note that proper connection
// and synchronization of the radio-altitude ARINC 529 input,
// decision-height discrete and decision-height ARINC 529 input
// will prevent an altitude callout from being played near the
// decision height).
if (is_playing_altitude_callout())
mk->voice_player.stop(VoicePlayer::STOP_NOW);
mk_set_alerts(mk_alert(MODE6_MINIMUMS));
}
else
mk_unset_alerts(mk_alert(MODE6_MINIMUMS));
end:
last_decision_height = mk_dinput(decision_height);
}
void
MK_VIII::Mode6Handler::update_altitude_callouts ()
{
if (! mk->io_handler.gpws_inhibit() && is_playing_altitude_callout())
goto end;
if (! mk->io_handler.gpws_inhibit()
&& ! mk->state_handler.ground // [1]
&& ! mk_data(radio_altitude).ncd)
for (int i = 0; i < n_altitude_callouts && mk_data(radio_altitude).get() <= altitude_callout_definitions[i]; i++)
if ((conf.altitude_callouts_enabled[i]
|| (altitude_callout_definitions[i] == 500
&& conf.smart_500_enabled))
&& ! altitude_callouts_issued[i]
&& (last_radio_altitude.ncd
|| last_radio_altitude.get() > altitude_callout_definitions[i]))
{
// lock out all callouts superior or equal to this one
for (int j = 0; j <= i; j++)
altitude_callouts_issued[j] = true;
altitude_callouts_issued[i] = true;
if (! is_near_minimums(altitude_callout_definitions[i])
&& ! is_outside_band(mk_data(radio_altitude).get(), altitude_callout_definitions[i])
&& (! conf.smart_500_enabled
|| altitude_callout_definitions[i] != 500
|| ! inhibit_smart_500()))
{
mk->alert_handler.set_altitude_callout_alert(mk_altitude_voice(i));
goto end;
}
}
mk_unset_alerts(mk_alert(MODE6_ALTITUDE_CALLOUT));
end:
last_radio_altitude.set(&mk_data(radio_altitude));
}
bool
MK_VIII::Mode6Handler::test_runway (const FGRunway *_runway)
{
if (_runway->lengthFt() < mk->conf.runway_database)
return false;
SGGeod pos(
SGGeod::fromDeg(mk_data(gps_longitude).get(), mk_data(gps_latitude).get()));
// get distance to threshold
double distance, az1, az2;
SGGeodesy::inverse(pos, _runway->threshold(), az1, az2, distance);
return distance * SG_METER_TO_NM <= 5;
}
bool
MK_VIII::Mode6Handler::test_airport (const FGAirport *airport)
{
for (unsigned int r=0; r<airport->numRunways(); ++r) {
FGRunway* rwy(airport->getRunwayByIndex(r));
if (test_runway(rwy)) return true;
}
return false;
}
bool MK_VIII::Mode6Handler::AirportFilter::passAirport(FGAirport* a) const
{
bool ok = self->test_airport(a);
return ok;
}
void
MK_VIII::Mode6Handler::update_runway ()
{
if (mk_data(gps_latitude).ncd || mk_data(gps_longitude).ncd) {
has_runway.unset();
return;
}
// Search for the closest runway threshold in range 5
// nm. Passing 30nm to
// get_closest_airport() provides enough margin for large
// airports, which may have a runway located far away from the
// airport's reference point.
AirportFilter filter(this);
FGPositionedRef apt = FGPositioned::findClosest(
SGGeod::fromDeg(mk_data(gps_longitude).get(), mk_data(gps_latitude).get()),
30.0, &filter);
if (apt) {
runway.elevation = apt->elevation();
}
has_runway.set(apt != NULL);
}
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->latitude(),
_runway->longitude(),
_runway->headingDeg());
}
// 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.
FGRunway*
MK_VIII::TCFHandler::select_runway (const FGAirport *airport)
{
FGRunway* _runway = 0;
double min_diff = 360;
for (unsigned int r=0; r<airport->numRunways(); ++r) {
FGRunway* rwy(airport->getRunwayByIndex(r));
double diff = get_azimuth_difference(rwy);
if (diff < min_diff)
{
min_diff = diff;
_runway = rwy;
}
} // of airport runways iteration
return _runway;
}
bool MK_VIII::TCFHandler::AirportFilter::passAirport(FGAirport* aApt) const
{
return aApt->hasHardRunwayOfLengthFt(mk->conf.runway_database);
}
void
MK_VIII::TCFHandler::update_runway ()
{
has_runway = false;
if (mk_data(gps_latitude).ncd || mk_data(gps_longitude).ncd) {
return;
}
// Search for the intended destination runway of the closest
// airport in range 15 nm. Passing 30nm to get_closest_airport()
// provides enough margin for
// large airports, which may have a runway located far away from
// the airport's reference point.
AirportFilter filter(mk);
FGAirport* apt = FGAirport::findClosest(
SGGeod::fromDeg(mk_data(gps_longitude).get(), mk_data(gps_latitude).get()),
30.0, &filter);
if (!apt) return;
has_runway = true;
FGRunway* _runway = select_runway(apt);
runway.center.latitude = _runway->latitude();
runway.center.longitude = _runway->longitude();
runway.elevation = apt->elevation();
double half_length_m = _runway->lengthM() * 0.5;
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->headingDeg();
runway.edges[1].heading = get_reciprocal_heading(_runway->headingDeg());
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->widthM() * 0.5;
// 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();
}