FlightGear next
mk_viii.cxx
Go to the documentation of this file.
1// mk_viii.cxx -- Honeywell MK VIII EGPWS emulation
2//
3// Written by Jean-Yves Lefort, started September 2005.
4//
5// Copyright (C) 2005, 2006 Jean-Yves Lefort - jylefort@FreeBSD.org
6//
7// This program is free software; you can redistribute it and/or
8// modify it under the terms of the GNU General Public License as
9// published by the Free Software Foundation; either version 2 of the
10// License, or (at your option) any later version.
11//
12// This program is distributed in the hope that it will be useful, but
13// WITHOUT ANY WARRANTY; without even the implied warranty of
14// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15// General Public License for more details.
16//
17// You should have received a copy of the GNU General Public License
18// along with this program; if not, write to the Free Software
19// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
20//
22//
23// References:
24//
25// [PILOT] Honeywell International Inc., "MK VI and MK VIII,
26// Enhanced Ground Proximity Warning System (EGPWS),
27// Pilot's Guide", May 2004
28//
29// http://www.egpws.com/engineering_support/documents/pilot_guides/060-4314-000.pdf
30//
31// [SPEC] Honeywell International Inc., "Product Specification
32// for the MK VI and MK VIII Enhanced Ground Proximity
33// Warning System (EGPWS)", June 2004
34//
35// http://www.egpws.com/engineering_support/documents/specs/965-1180-601.pdf
36//
37// [INSTALL] Honeywell Inc., "MK VI, MK VIII, Enhanced Ground
38// Proximity Warning System (Class A TAWS), Installation
39// Design Guide", July 2003
40//
41// http://www.egpws.com/engineering_support/documents/install/060-4314-150.pdf
42//
43// Notes:
44//
45// [1] [SPEC] does not specify the "must be airborne"
46// condition; we use it to make sure that the alert
47// will not trigger when on the ground, since it would
48// make no sense.
49
50#ifdef _MSC_VER
51# pragma warning( disable: 4355 )
52#endif
53
54#include <config.h>
55
56#include <stdio.h>
57#include <string.h>
58#include <assert.h>
59#include <cmath>
60
61#include <string>
62#include <sstream>
63
64#include <simgear/constants.h>
65#include <simgear/sg_inlines.h>
66#include <simgear/debug/logstream.hxx>
67#include <simgear/math/SGMathFwd.hxx>
68#include <simgear/math/SGLimits.hxx>
69#include <simgear/math/SGGeometryFwd.hxx>
70#include <simgear/math/SGGeodesy.hxx>
71#include <simgear/math/sg_random.hxx>
72#include <simgear/math/SGLineSegment.hxx>
73#include <simgear/math/SGIntersect.hxx>
74#include <simgear/misc/sg_path.hxx>
75#include <simgear/sound/soundmgr.hxx>
76#include <simgear/sound/sample_group.hxx>
77#include <simgear/structure/exception.hxx>
78
79using std::string;
80
81#include <Airports/runways.hxx>
82#include <Airports/airport.hxx>
83#include <Main/fg_props.hxx>
84#include <Main/globals.hxx>
85#include "instrument_mgr.hxx"
86#include "mk_viii.hxx"
87
89// constants //////////////////////////////////////////////////////////////////
91
92#define GLIDESLOPE_DOTS_TO_DDM 0.0875 // [INSTALL] 6.2.30
93#define GLIDESLOPE_DDM_TO_DOTS (1 / GLIDESLOPE_DOTS_TO_DDM)
94
95#define LOCALIZER_DOTS_TO_DDM 0.0775 // [INSTALL] 6.2.33
96#define LOCALIZER_DDM_TO_DOTS (1 / LOCALIZER_DOTS_TO_DDM)
97
99// helpers ////////////////////////////////////////////////////////////////////
101
102#define assert_not_reached() assert(false)
103#define n_elements(_array) (sizeof(_array) / sizeof((_array)[0]))
104#define test_bits(_bits, _test) (((_bits) & (_test)) != 0)
105
106#define mk_node(_name) (mk->properties_handler.external_properties._name)
107
108#define mk_dinput_feed(_name) (mk->io_handler.input_feeders.discretes._name)
109#define mk_dinput(_name) (mk->io_handler.inputs.discretes._name)
110#define mk_ainput_feed(_name) (mk->io_handler.input_feeders.arinc429._name)
111#define mk_ainput(_name) (mk->io_handler.inputs.arinc429._name)
112#define mk_doutput(_name) (mk->io_handler.outputs.discretes._name)
113#define mk_aoutput(_name) (mk->io_handler.outputs.arinc429._name)
114#define mk_data(_name) (mk->io_handler.data._name)
115
116#define mk_voice(_name) (mk->voice_player.voices._name)
117#define mk_altitude_voice(_i) (mk->voice_player.voices.altitude_callouts[(_i)])
118
119#define mk_alert(_name) (AlertHandler::ALERT_ ## _name)
120#define mk_alert_flag(_name) (AlertHandler::ALERT_FLAG_ ## _name)
121#define mk_set_alerts (mk->alert_handler.set_alerts)
122#define mk_unset_alerts (mk->alert_handler.unset_alerts)
123#define mk_repeat_alert (mk->alert_handler.repeat_alert)
124#define mk_test_alert(_name) (mk_test_alerts(mk_alert(_name)))
125#define mk_test_alerts(_test) (test_bits(mk->alert_handler.alerts, (_test)))
126
127const double MK_VIII::TCFHandler::k = 0.25;
128
129static double
130modify_amplitude (double amplitude, double dB)
131{
132 return amplitude * pow(10.0, dB / 20.0);
133}
134
135static double
136get_heading_difference (double h1, double h2)
137{
138 double diff = h1 - h2;
139
140 if (diff < -180)
141 diff += 360;
142 else if (diff > 180)
143 diff -= 360;
144
145 return fabs(diff);
146}
147
149// PropertiesHandler //////////////////////////////////////////////////////////
151
152void
153MK_VIII::PropertiesHandler::init ()
154{
155 mk_node(ai_caged) = fgGetNode("/instrumentation/attitude-indicator/caged-flag", true);
156 mk_node(ai_roll) = fgGetNode("/instrumentation/attitude-indicator/indicated-roll-deg", true);
157 mk_node(ai_serviceable) = fgGetNode("/instrumentation/attitude-indicator/serviceable", true);
158 mk_node(altimeter_altitude) = fgGetNode("/instrumentation/altimeter/indicated-altitude-ft", true);
159 mk_node(altimeter_serviceable) = fgGetNode("/instrumentation/altimeter/serviceable", true);
160 mk_node(altitude) = fgGetNode("/position/altitude-ft", true);
161 mk_node(altitude_agl) = fgGetNode("/position/altitude-agl-ft", true);
162 mk_node(altitude_gear_agl) = fgGetNode("/position/gear-agl-ft", true);
163 mk_node(altitude_radar_agl) = fgGetNode("/instrumentation/radar-altimeter/radar-altitude-ft", true);
164 mk_node(orientation_roll) = fgGetNode("/orientation/roll-deg", true);
165 mk_node(asi_serviceable) = fgGetNode("/instrumentation/airspeed-indicator/serviceable", true);
166 mk_node(asi_speed) = fgGetNode("/instrumentation/airspeed-indicator/indicated-speed-kt", true);
167 mk_node(autopilot_heading_lock) = fgGetNode("/autopilot/locks/heading", true);
168 mk_node(flaps) = fgGetNode("/controls/flight/flaps", true);
169 mk_node(gear_down) = fgGetNode("/controls/gear/gear-down", true);
170 mk_node(throttle) = fgGetNode("/controls/engines/engine/throttle", true);
171 mk_node(latitude) = fgGetNode("/position/latitude-deg", true);
172 mk_node(longitude) = fgGetNode("/position/longitude-deg", true);
173 mk_node(nav0_cdi_serviceable) = fgGetNode("/instrumentation/nav/cdi/serviceable", true);
174 mk_node(nav0_gs_distance) = fgGetNode("/instrumentation/nav/gs-distance", true);
175 mk_node(nav0_gs_needle_deflection) = fgGetNode("/instrumentation/nav/gs-needle-deflection-deg", true);
176 mk_node(nav0_gs_serviceable) = fgGetNode("/instrumentation/nav/gs/serviceable", true);
177 mk_node(nav0_has_gs) = fgGetNode("/instrumentation/nav/has-gs", true);
178 mk_node(nav0_heading_needle_deflection) = fgGetNode("/instrumentation/nav/heading-needle-deflection", true);
179 mk_node(nav0_in_range) = fgGetNode("/instrumentation/nav/in-range", true);
180 mk_node(nav0_nav_loc) = fgGetNode("/instrumentation/nav/nav-loc", true);
181 mk_node(nav0_serviceable) = fgGetNode("/instrumentation/nav/serviceable", true);
182 mk_node(power) = fgGetNode(("/systems/electrical/outputs/" + mk->name), mk->num, true);
183 mk_node(replay_state) = fgGetNode("/sim/freeze/replay-state", true);
184 mk_node(vs) = fgGetNode("/velocities/vertical-speed-fps", true);
185}
186
188// PowerHandler ///////////////////////////////////////////////////////////////
190
191void
192MK_VIII::PowerHandler::bind (SGPropertyNode *node)
193{
194 mk->properties_handler.tie(node, "serviceable", SGRawValuePointer<bool>(&serviceable));
195}
196
197bool
198MK_VIII::PowerHandler::handle_abnormal_voltage (bool abnormal,
199 Timer *timer,
200 double max_duration)
201{
202 if (abnormal)
203 {
204 if (timer->start_or_elapsed() >= max_duration)
205 return true; // power loss
206 } else
207 timer->stop();
208
209 return false;
210}
211
212void
213MK_VIII::PowerHandler::update ()
214{
215 double voltage = mk_node(power)->getDoubleValue();
216 bool now_powered = true;
217
218 // [SPEC] 3.2.1
219
220 if (!serviceable)
221 now_powered = false;
222 if (voltage < 15)
223 now_powered = false;
224 if (handle_abnormal_voltage(voltage < 20.5, &low_surge_timer, 0.03))
225 now_powered = false;
226 if (handle_abnormal_voltage(voltage < 22.0 || voltage > 30.3,
227 &abnormal_timer, 300))
228 now_powered = false;
229 if (handle_abnormal_voltage(voltage > 32.2, &high_surge_timer, 1))
230 now_powered = false;
231 if (handle_abnormal_voltage(voltage > 37.8, &very_high_surge_timer, 0.1))
232 now_powered = false;
233 if (voltage > 46.3)
234 now_powered = false;
235
236 if (powered)
237 {
238 // [SPEC] 3.5.2
239
240 if (now_powered)
241 power_loss_timer.stop();
242 else {
243 if (power_loss_timer.start_or_elapsed() >= 0.2)
244 power_off();
245 }
246 } else
247 {
248 if (now_powered)
249 power_on();
250 }
251}
252
253void
254MK_VIII::PowerHandler::power_on ()
255{
256 powered = true;
257 mk->system_handler.power_on();
258}
259
260void
261MK_VIII::PowerHandler::power_off ()
262{
263 powered = false;
264 mk->system_handler.power_off();
265 mk->voice_player.stop(VoicePlayer::STOP_NOW);
266 mk->self_test_handler.power_off(); // run before IOHandler::power_off()
267 mk->io_handler.power_off();
268 mk->mode2_handler.power_off();
269 mk->mode6_handler.power_off();
270}
271
273// SystemHandler //////////////////////////////////////////////////////////////
275
276void
277MK_VIII::SystemHandler::power_on ()
278{
279 state = STATE_BOOTING;
280
281 // [SPEC] 3.5.2 mentions a 20 seconds maximum boot delay. We use a
282 // random delay between 3 and 5 seconds.
283
284 boot_delay = 3 + sg_random() * 2;
285 boot_timer.start();
286}
287
288void
289MK_VIII::SystemHandler::power_off ()
290{
291 state = STATE_OFF;
292
293 boot_timer.stop();
294}
295
296void
297MK_VIII::SystemHandler::update ()
298{
299 if (state == STATE_BOOTING)
300 {
301 if (boot_timer.elapsed() >= boot_delay)
302 {
303 last_replay_state = mk_node(replay_state)->getIntValue();
304
305 mk->configuration_module.boot();
306
307 mk->io_handler.boot();
308 mk->fault_handler.boot();
309 mk->alert_handler.boot();
310
311 // inputs are needed by the following boot() routines
312 mk->io_handler.update_inputs();
313
314 mk->mode2_handler.boot();
315 mk->mode6_handler.boot();
316
317 state = STATE_ON;
318
319 mk->io_handler.post_boot();
320 }
321 }
322 else if (state != STATE_OFF && mk->configuration_module.state == ConfigurationModule::STATE_OK)
323 {
324 // If the replay state changes, switch to reposition mode for 3
325 // seconds ([SPEC] 6.0.5) to avoid spurious alerts.
326
327 int replay_state = mk_node(replay_state)->getIntValue();
328 if (replay_state != last_replay_state)
329 {
330 mk->alert_handler.reposition();
331 mk->io_handler.reposition();
332
333 last_replay_state = replay_state;
334 state = STATE_REPOSITION;
335 reposition_timer.start();
336 }
337
338 if (state == STATE_REPOSITION && reposition_timer.elapsed() >= 3)
339 {
340 // inputs are needed by StateHandler::post_reposition()
341 mk->io_handler.update_inputs();
342
343 mk->state_handler.post_reposition();
344
345 state = STATE_ON;
346 }
347 }
348}
349
351// ConfigurationModule ////////////////////////////////////////////////////////
353
354MK_VIII::ConfigurationModule::ConfigurationModule (MK_VIII *device)
355 : state(STATE_OK), mk(device)
356{
357 // arbitrary defaults
358 categories[CATEGORY_AIRCRAFT_MODE_TYPE_SELECT] = 0;
359 categories[CATEGORY_AIR_DATA_INPUT_SELECT] = 1;
360 categories[CATEGORY_POSITION_INPUT_SELECT] = 2;
361 categories[CATEGORY_ALTITUDE_CALLOUTS] = 13;
362 categories[CATEGORY_AUDIO_MENU_SELECT] = 0;
363 categories[CATEGORY_TERRAIN_DISPLAY_SELECT] = 1;
364 categories[CATEGORY_OPTIONS_SELECT_GROUP_1] = 124;
365 categories[CATEGORY_RADIO_ALTITUDE_INPUT_SELECT] = 2;
366 categories[CATEGORY_NAVIGATION_INPUT_SELECT] = 3;
367 categories[CATEGORY_ATTITUDE_INPUT_SELECT] = 6;
368 categories[CATEGORY_HEADING_INPUT_SELECT] = 2;
369 categories[CATEGORY_WINDSHEAR_INPUT_SELECT] = 0;
370 categories[CATEGORY_INPUT_OUTPUT_DISCRETE_TYPE_SELECT] = 7;
371 categories[CATEGORY_AUDIO_OUTPUT_LEVEL] = 0;
372 categories[CATEGORY_UNDEFINED_INPUT_SELECT_1] = 0;
373 categories[CATEGORY_UNDEFINED_INPUT_SELECT_2] = 0;
374 categories[CATEGORY_UNDEFINED_INPUT_SELECT_3] = 0;
375}
376
377static double m1_t1_min_agl1 (double vs) { return -1620 - 1.1133 * vs; }
378static double m1_t1_min_agl2 (double vs) { return -400 - 0.4 * vs; }
379static double m1_t4_min_agl1 (double vs) { return -1625.47 - 1.1167 * vs; }
380static double m1_t4_min_agl2 (double vs) { return -0.1958 * vs; }
381
382static int m3_t1_max_agl (bool flap_override) { return 1500; }
383static double m3_t1_max_alt_loss (bool flap_override, double agl) { return 5.4 + 0.092 * agl; }
384static int m3_t2_max_agl (bool flap_override) { return flap_override ? 815 : 925; }
385static double m3_t2_max_alt_loss (bool flap_override, double agl)
386{
387 int bias = agl > 700 ? 5 : 0;
388
389 if (flap_override)
390 return (9.0 + 0.184 * agl) + bias;
391 else
392 return (5.4 + 0.092 * agl) + bias;
393}
394
395static double m4_t1_min_agl2 (double airspeed) { return -1083 + 8.333 * airspeed; }
396static double m4_t568_a_min_agl2 (double airspeed) { return -1523 + 11.36 * airspeed; }
397static double m4_t79_a_min_agl2 (double airspeed) { return -1182 + 11.36 * airspeed; }
398static double m4_t568_b_min_agl2 (double airspeed) { return -1570 + 11.6 * airspeed; }
399static double m4_t79_b_min_agl2 (double airspeed) { return -1222 + 11.6 * airspeed; }
400
401bool
402MK_VIII::ConfigurationModule::m6_t2_is_bank_angle (Parameter<double> *agl,
403 double abs_roll_deg,
404 bool ap_engaged)
405{
406 if (ap_engaged)
407 {
408 if (agl->ncd || agl->get() > 122)
409 return abs_roll_deg > 33;
410 }
411 else
412 {
413 if (agl->ncd || agl->get() > 2450)
414 return abs_roll_deg > 55;
415 else if (agl->get() > 150)
416 return agl->get() < 153.33333 * abs_roll_deg - 5983.3333;
417 }
418
419 if (agl->get() > 30)
420 return agl->get() < 4 * abs_roll_deg - 10;
421 else if (agl->get() > 5)
422 return abs_roll_deg > 10;
423
424 return false;
425}
426
427bool
428MK_VIII::ConfigurationModule::m6_t4_is_bank_angle (Parameter<double> *agl,
429 double abs_roll_deg,
430 bool ap_engaged)
431{
432 if (ap_engaged)
433 {
434 if (agl->ncd || agl->get() > 156)
435 return abs_roll_deg > 33;
436 }
437 else
438 {
439 if (agl->ncd || agl->get() > 210)
440 return abs_roll_deg > 50;
441 }
442
443 if (agl->get() > 10)
444 return agl->get() < 5.7142857 * abs_roll_deg - 75.714286;
445
446 return false;
447}
448
449bool
450MK_VIII::ConfigurationModule::read_aircraft_mode_type_select (int value)
451{
452 static const Mode1Handler::EnvelopesConfiguration m1_t1 =
453 { false, 10, m1_t1_min_agl1, 284, m1_t1_min_agl2, 2450 };
454 static const Mode1Handler::EnvelopesConfiguration m1_t4 =
455 { true, 50, m1_t4_min_agl1, 346, m1_t4_min_agl2, 1958 };
456
457 static const Mode2Handler::Configuration m2_t1 = { 190, 280 };
458 static const Mode2Handler::Configuration m2_t4 = { 220, 310 };
459 static const Mode2Handler::Configuration m2_t5 = { 220, 310 };
460
461 static const Mode3Handler::Configuration m3_t1 =
463 static const Mode3Handler::Configuration m3_t2 =
465
466 static const Mode4Handler::EnvelopesConfiguration m4_t1_ac =
467 { 190, 250, 500, m4_t1_min_agl2, 1000 };
468 static const Mode4Handler::EnvelopesConfiguration m4_t5_ac =
469 { 178, 200, 500, m4_t568_a_min_agl2, 1000 };
470 static const Mode4Handler::EnvelopesConfiguration m4_t68_ac =
471 { 178, 200, 500, m4_t568_a_min_agl2, 750 };
472 static const Mode4Handler::EnvelopesConfiguration m4_t79_ac =
473 { 148, 170, 500, m4_t79_a_min_agl2, 750 };
474
475 static const Mode4Handler::EnvelopesConfiguration m4_t1_b =
476 { 159, 250, 245, m4_t1_min_agl2, 1000 };
477 static const Mode4Handler::EnvelopesConfiguration m4_t5_b =
478 { 148, 200, 200, m4_t568_b_min_agl2, 1000 };
479 static const Mode4Handler::EnvelopesConfiguration m4_t6_b =
480 { 150, 200, 170, m4_t568_b_min_agl2, 750 };
481 static const Mode4Handler::EnvelopesConfiguration m4_t7_b =
482 { 120, 170, 170, m4_t79_b_min_agl2, 750 };
483 static const Mode4Handler::EnvelopesConfiguration m4_t8_b =
484 { 148, 200, 150, m4_t568_b_min_agl2, 750 };
485 static const Mode4Handler::EnvelopesConfiguration m4_t9_b =
486 { 118, 170, 150, m4_t79_b_min_agl2, 750 };
487
488 static const Mode4Handler::ModesConfiguration m4_t1 = { &m4_t1_ac, &m4_t1_b };
489 static const Mode4Handler::ModesConfiguration m4_t5 = { &m4_t5_ac, &m4_t5_b };
490 static const Mode4Handler::ModesConfiguration m4_t6 = { &m4_t68_ac, &m4_t6_b };
491 static const Mode4Handler::ModesConfiguration m4_t7 = { &m4_t79_ac, &m4_t7_b };
492 static const Mode4Handler::ModesConfiguration m4_t8 = { &m4_t68_ac, &m4_t8_b };
493 static const Mode4Handler::ModesConfiguration m4_t9 = { &m4_t79_ac, &m4_t9_b };
494
495 static Mode6Handler::BankAnglePredicate m6_t2 = m6_t2_is_bank_angle;
496 static Mode6Handler::BankAnglePredicate m6_t4 = m6_t4_is_bank_angle;
497
498 static const IOHandler::FaultsConfiguration f_slow = { 180, 200 };
499 static const IOHandler::FaultsConfiguration f_fast = { 250, 290 };
500
501 static const struct
502 {
503 int type;
508 Mode6Handler::BankAnglePredicate m6;
510 int runway_database;
511 } aircraft_types[] = {
512 { 0, &m1_t4, &m2_t4, &m3_t2, &m4_t6, m6_t4, &f_fast, 2000 },
513 { 1, &m1_t4, &m2_t4, &m3_t2, &m4_t8, m6_t4, &f_fast, 2000 },
514 { 2, &m1_t4, &m2_t4, &m3_t2, &m4_t5, m6_t4, &f_fast, 2000 },
515 { 3, &m1_t4, &m2_t5, &m3_t2, &m4_t7, m6_t4, &f_slow, 2000 },
516 { 4, &m1_t4, &m2_t5, &m3_t2, &m4_t9, m6_t4, &f_slow, 2000 },
517 { 254, &m1_t1, &m2_t1, &m3_t1, &m4_t1, m6_t2, &f_fast, 3500 },
518 { 255, &m1_t1, &m2_t1, &m3_t1, &m4_t1, m6_t2, &f_fast, 2000 }
519 };
520
521 for (size_t i = 0; i < n_elements(aircraft_types); i++)
522 {
523 if (aircraft_types[i].type == value)
524 {
525 mk->mode1_handler.conf.envelopes = aircraft_types[i].m1;
526 mk->mode2_handler.conf = aircraft_types[i].m2;
527 mk->mode3_handler.conf = aircraft_types[i].m3;
528 mk->mode4_handler.conf.modes = aircraft_types[i].m4;
529 mk->mode6_handler.conf.is_bank_angle = aircraft_types[i].m6;
530 mk->io_handler.conf.faults = aircraft_types[i].f;
531 mk->conf.runway_database = aircraft_types[i].runway_database;
532 return true;
533 }
534 }
535
536 state = STATE_INVALID_AIRCRAFT_TYPE;
537 return false;
538}
539
540bool
541MK_VIII::ConfigurationModule::read_air_data_input_select (int value)
542{
543 // unimplemented
544 return (value >= 0 && value <= 6) || (value >= 10 && value <= 13) || value == 255;
545}
546
547bool
548MK_VIII::ConfigurationModule::read_position_input_select (int value)
549{
550 if (value == 2)
551 mk->io_handler.conf.use_internal_gps = true;
552 else if ((value >= 0 && value <= 5)
553 || (value >= 10 && value <= 13)
554 || (value == 253)
555 || (value == 255))
556 mk->io_handler.conf.use_internal_gps = false;
557 else
558 return false;
559
560 return true;
561}
562
563bool
564MK_VIII::ConfigurationModule::read_altitude_callouts (int value)
565{
566 enum
567 {
568 MINIMUMS = -1,
569 SMART_500 = -2,
570 FIELD_500 = -3,
571 FIELD_500_ABOVE = -4,
572 MINIMUMS_ABOVE_100 = -5,
573 RETARD = -6
574 };
575
576 static const struct
577 {
578 int id;
579 int callouts[16];
580 } values[] = {
581 { 0, { MINIMUMS, SMART_500, 200, 100, 50, 40, 30, 20, 10, 0 } },
582 { 1, { MINIMUMS, SMART_500, 200, 0 } },
583 { 2, { MINIMUMS, SMART_500, 100, 50, 40, 30, 20, 10, 0 } },
584 { 3, { MINIMUMS, SMART_500, 0 } },
585 { 4, { MINIMUMS, 200, 100, 50, 40, 30, 20, 10, 0 } },
586 { 5, { MINIMUMS, 200, 0 } },
587 { 6, { MINIMUMS, 100, 50, 40, 30, 20, 10, 0 } },
588 { 7, { 0 } },
589 { 8, { MINIMUMS, 0 } },
590 { 9, { MINIMUMS, 500, 200, 100, 50, 40, 30, 20, 10, 0 } },
591 { 10, { MINIMUMS, 500, 200, 0 } },
592 { 11, { MINIMUMS, 500, 100, 50, 40, 30, 20, 10, 0 } },
593 { 12, { MINIMUMS, 500, 0 } },
594 { 13, { MINIMUMS, 1000, 500, 400, 300, 200, 100, 50, 40, 30, 20, 10, 0 } },
595 { 14, { MINIMUMS, 100, 0 } },
596 { 15, { MINIMUMS, 200, 100, 0 } },
597 { 100, { FIELD_500, 0 } },
598 { 101, { FIELD_500_ABOVE, 0 } },
599 {1000, { RETARD, MINIMUMS_ABOVE_100, MINIMUMS, 2500, 1000, 500, 400, 300, 200, 100, 50, 40, 30,20, 10, 0 } },
600 {1001, { RETARD, MINIMUMS, 2500, 1000, 500, 400, 300, 200, 100, 50, 40, 30,20, 10, 0 } },
601 {1002, { MINIMUMS, 2500, 1000, 500, 400, 300, 200, 100, 50, 40, 30,20, 10, 0 } },
602 {1010, { RETARD, MINIMUMS_ABOVE_100, MINIMUMS, 1000, 500, 400, 300, 200, 100, 50, 40, 30,20, 10, 0 } },
603 {1011, { RETARD, MINIMUMS, 1000, 500, 400, 300, 200, 100, 50, 40, 30,20, 10, 0 } },
604 };
605
606 mk->mode6_handler.conf.retard_enabled = false;
607 mk->mode6_handler.conf.minimums_above_100_enabled = false;
608 mk->mode6_handler.conf.minimums_enabled = false;
609 mk->mode6_handler.conf.smart_500_enabled = false;
610 mk->mode6_handler.conf.above_field_voice = NULL;
611 mk->mode6_handler.conf.altitude_callouts_enabled = 0;
612
613 for (unsigned int i = 0; i < n_elements(values); i++)
614 {
615 if (values[i].id == value)
616 {
617 for (int j = 0; values[i].callouts[j] != 0; j++)
618 {
619 switch (values[i].callouts[j])
620 {
621 case RETARD:
622 mk->mode6_handler.conf.retard_enabled = true;
623 break;
624
625 case MINIMUMS_ABOVE_100:
626 mk->mode6_handler.conf.minimums_above_100_enabled = true;
627 break;
628
629 case MINIMUMS:
630 mk->mode6_handler.conf.minimums_enabled = true;
631 break;
632
633 case SMART_500:
634 mk->mode6_handler.conf.smart_500_enabled = true;
635 break;
636
637 case FIELD_500:
638 mk->mode6_handler.conf.above_field_voice = mk_altitude_voice(Mode6Handler::ALTITUDE_CALLOUT_500);
639 break;
640
641 case FIELD_500_ABOVE:
642 mk->mode6_handler.conf.above_field_voice = mk_voice(five_hundred_above);
643 break;
644
645 default:
646 for (unsigned k = 0; k < n_altitude_callouts; k++)
647 if (mk->mode6_handler.altitude_callout_definitions[k] == values[i].callouts[j])
648 mk->mode6_handler.conf.altitude_callouts_enabled |= 1 << k;
649 break;
650 }
651 }
652
653 return true;
654 }
655 }
656 return false;
657}
658
659bool
660MK_VIII::ConfigurationModule::read_audio_menu_select (int value)
661{
662 if (value == 0 || value == 1)
663 mk->mode4_handler.conf.voice_too_low_gear = mk_voice(too_low_gear);
664 else if (value == 2 || value == 3)
665 mk->mode4_handler.conf.voice_too_low_gear = mk_voice(too_low_flaps);
666 else
667 return false;
668
669 return true;
670}
671
672bool
673MK_VIII::ConfigurationModule::read_terrain_display_select (int value)
674{
675 if (value == 2)
676 mk->tcf_handler.conf.enabled = false;
677 else if (value == 0 || value == 1 || (value >= 3 && value <= 15)
678 || (value >= 18 && value <= 20) || (value >= 235 && value <= 255))
679 mk->tcf_handler.conf.enabled = true;
680 else
681 return false;
682
683 return true;
684}
685
686bool
687MK_VIII::ConfigurationModule::read_options_select_group_1 (int value)
688{
689 if (value >= 0 && value < 128)
690 {
691 mk->io_handler.conf.flap_reversal = test_bits(value, 1 << 1);
692 mk->mode6_handler.conf.bank_angle_enabled = test_bits(value, 1 << 2);
693 mk->io_handler.conf.steep_approach_enabled = test_bits(value, 1 << 6);
694 return true;
695 }
696
697 return false;
698}
699
700bool
701MK_VIII::ConfigurationModule::read_radio_altitude_input_select (int value)
702{
703 mk->io_handler.conf.altitude_source = value;
704 return (value >= 0 && value <= 4) || (value >= 251 && value <= 255);
705}
706
707bool
708MK_VIII::ConfigurationModule::read_navigation_input_select (int value)
709{
710 if (value >= 0 && value <= 2)
711 mk->io_handler.conf.localizer_enabled = false;
712 else if ((value >= 3 && value <= 5) || (value >= 250 && value <= 255))
713 mk->io_handler.conf.localizer_enabled = true;
714 else
715 return false;
716
717 return true;
718}
719
720bool
721MK_VIII::ConfigurationModule::read_attitude_input_select (int value)
722{
723 if (value == 2)
724 mk->io_handler.conf.use_attitude_indicator=true;
725 else
726 mk->io_handler.conf.use_attitude_indicator=false;
727 return (value >= 0 && value <= 6) || value == 253 || value == 255;
728}
729
730bool
731MK_VIII::ConfigurationModule::read_heading_input_select (int value)
732{
733 // unimplemented
734 return (value >= 0 && value <= 3) || value == 254 || value == 255;
735}
736
737bool
738MK_VIII::ConfigurationModule::read_windshear_input_select (int value)
739{
740 // unimplemented
741 return value == 0 || (value >= 253 && value <= 255);
742}
743
744bool
745MK_VIII::ConfigurationModule::read_input_output_discrete_type_select (int value)
746{
747 static const struct
748 {
749 int type;
751 bool gpws_inhibit_enabled;
752 bool momentary_flap_override_enabled;
753 bool alternate_steep_approach;
754 } io_types[] = {
755 { 0, { false, false }, false, true, true },
756 { 1, { true, false }, false, true, true },
757 { 2, { false, false }, true, true, true },
758 { 3, { true, false }, true, true, true },
759 { 4, { false, true }, true, true, true },
760 { 5, { true, true }, true, true, true },
761 { 6, { false, false }, true, true, false },
762 { 7, { true, false }, true, true, false },
763 { 254, { false, false }, true, false, true },
764 { 255, { false, false }, true, false, true }
765 };
766
767 for (size_t i = 0; i < n_elements(io_types); i++)
768 {
769 if (io_types[i].type == value)
770 {
771 mk->io_handler.conf.lamp = &io_types[i].lamp_conf;
772 mk->io_handler.conf.gpws_inhibit_enabled = io_types[i].gpws_inhibit_enabled;
773 mk->io_handler.conf.momentary_flap_override_enabled = io_types[i].momentary_flap_override_enabled;
774 mk->io_handler.conf.alternate_steep_approach = io_types[i].alternate_steep_approach;
775 return true;
776 }
777 }
778
779 return false;
780}
781
782bool
783MK_VIII::ConfigurationModule::read_audio_output_level (int value)
784{
785 static const struct
786 {
787 int id;
788 int relative_dB;
789 } values[] = {
790 { 0, 0 },
791 { 1, -6 },
792 { 2, -12 },
793 { 3, -18 },
794 { 4, -24 }
795 };
796
797 for (size_t i = 0; i < n_elements(values); i++)
798 {
799 if (values[i].id == value)
800 {
801 mk->voice_player.set_volume(mk->voice_player.conf.volume = modify_amplitude(1.0, values[i].relative_dB));
802 return true;
803 }
804 }
805
806 // The self test needs the voice player even when the configuration
807 // is invalid, so set a default value.
808 mk->voice_player.set_volume(mk->voice_player.conf.volume = 1.0);
809 return false;
810}
811
812bool
813MK_VIII::ConfigurationModule::read_undefined_input_select (int value)
814{
815 // unimplemented
816 return value == 0;
817}
818
819void
820MK_VIII::ConfigurationModule::boot ()
821{
822 bool (MK_VIII::ConfigurationModule::*readers[N_CATEGORIES]) (int) = {
823 &MK_VIII::ConfigurationModule::read_aircraft_mode_type_select,
824 &MK_VIII::ConfigurationModule::read_air_data_input_select,
825 &MK_VIII::ConfigurationModule::read_position_input_select,
826 &MK_VIII::ConfigurationModule::read_altitude_callouts,
827 &MK_VIII::ConfigurationModule::read_audio_menu_select,
828 &MK_VIII::ConfigurationModule::read_terrain_display_select,
829 &MK_VIII::ConfigurationModule::read_options_select_group_1,
830 &MK_VIII::ConfigurationModule::read_radio_altitude_input_select,
831 &MK_VIII::ConfigurationModule::read_navigation_input_select,
832 &MK_VIII::ConfigurationModule::read_attitude_input_select,
833 &MK_VIII::ConfigurationModule::read_heading_input_select,
834 &MK_VIII::ConfigurationModule::read_windshear_input_select,
835 &MK_VIII::ConfigurationModule::read_input_output_discrete_type_select,
836 &MK_VIII::ConfigurationModule::read_audio_output_level,
837 &MK_VIII::ConfigurationModule::read_undefined_input_select,
838 &MK_VIII::ConfigurationModule::read_undefined_input_select,
839 &MK_VIII::ConfigurationModule::read_undefined_input_select
840 };
841
842 memcpy(effective_categories, categories, sizeof(categories));
843 state = STATE_OK;
844
845 for (int i = 0; i < N_CATEGORIES; i++)
846 {
847 if (! (this->*readers[i])(effective_categories[i]))
848 {
849 SG_LOG(SG_INSTR, SG_ALERT, "MK VIII EGPWS configuration category " << i + 1 << ": invalid value " << effective_categories[i]);
850
851 if (state == STATE_OK)
852 state = STATE_INVALID_DATABASE;
853
854 mk_doutput(gpws_inop) = true;
855 mk_doutput(tad_inop) = true;
856
857 return;
858 }
859 }
860
861 // handle conflicts
862
863 if (mk->mode6_handler.conf.above_field_voice && ! mk->tcf_handler.conf.enabled)
864 {
865 // [INSTALL] 3.6
866 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");
867 state = STATE_INVALID_DATABASE;
868 }
869}
870
871void
872MK_VIII::ConfigurationModule::bind (SGPropertyNode *node)
873{
874 for (int i = 0; i < N_CATEGORIES; i++)
875 {
876 std::ostringstream name;
877 name << "configuration-module/category-" << i + 1;
878 mk->properties_handler.tie(node, name.str().c_str(), SGRawValuePointer<int>(&categories[i]));
879 }
880}
881
883// FaultHandler ///////////////////////////////////////////////////////////////
885
886// [INSTALL] only specifies that the faults cause a GPWS INOP
887// indication. We arbitrarily set a TAD INOP when it makes sense.
888const unsigned int MK_VIII::FaultHandler::fault_inops[] = {
889 INOP_GPWS | INOP_TAD, // [INSTALL] 3.15.1.3
890 INOP_GPWS, // [INSTALL] appendix E 6.6.2
891 INOP_GPWS, // [INSTALL] appendix E 6.6.4
892 INOP_GPWS, // [INSTALL] appendix E 6.6.6
893 INOP_GPWS | INOP_TAD, // [INSTALL] appendix E 6.6.7
894 INOP_GPWS, // [INSTALL] appendix E 6.6.13
895 INOP_GPWS, // [INSTALL] appendix E 6.6.25
896 INOP_GPWS, // [INSTALL] appendix E 6.6.27
897 INOP_TAD, // unspecified
898 INOP_GPWS, // unspecified
899 INOP_GPWS, // unspecified
900 // [INSTALL] 2.3.10.1 specifies that GPWS INOP is "activated during
901 // any detected partial or total failure of the GPWS modes 1-5", so
902 // do not set any INOP for mode 6 and bank angle.
903 0, // unspecified
904 0, // unspecified
905 INOP_TAD // unspecified
906};
907
908bool
909MK_VIII::FaultHandler::has_faults () const
910{
911 return faults!=0;
912}
913
914bool
915MK_VIII::FaultHandler::has_faults (unsigned int inop)
916{
917 if (!faults)
918 return false;
919
920 for (int i = 0; i < N_FAULTS; i++)
921 {
922 if ((faults & (1<<i)) && test_bits(fault_inops[i], inop))
923 return true;
924 }
925
926 return false;
927}
928
929void
930MK_VIII::FaultHandler::boot ()
931{
932 faults = 0;
933}
934
935void
936MK_VIII::FaultHandler::set_fault (Fault fault)
937{
938 if (! (faults & (1<<fault)))
939 {
940 faults |= 1<<fault;
941
942 mk->self_test_handler.set_inop();
943
944 if (test_bits(fault_inops[fault], INOP_GPWS))
945 {
946 mk_unset_alerts(~mk_alert(TCF_TOO_LOW_TERRAIN));
947 mk_doutput(gpws_inop) = true;
948 }
949 if (test_bits(fault_inops[fault], INOP_TAD))
950 {
951 mk_unset_alerts(mk_alert(TCF_TOO_LOW_TERRAIN));
952 mk_doutput(tad_inop) = true;
953 }
954 }
955}
956
957void
958MK_VIII::FaultHandler::unset_fault (Fault fault)
959{
960 if (faults & (1<<fault))
961 {
962 faults &= ~(1<<fault);
963 if (! has_faults(INOP_GPWS))
964 mk_doutput(gpws_inop) = false;
965 if (! has_faults(INOP_TAD))
966 mk_doutput(tad_inop) = false;
967 }
968}
969
971// IOHandler //////////////////////////////////////////////////////////////////
973
974double
975MK_VIII::IOHandler::TerrainClearanceFilter::update (double agl)
976{
977 // [PILOT] page 20 specifies that the terrain clearance is equal to
978 // 75% of the radio altitude, averaged over the previous 15 seconds.
979
980 // no updates when simulation is paused (dt=0.0), and add 5 samples/second only
981 if (globals->get_sim_time_sec() - last_update < 0.2)
982 return value;
983 last_update = globals->get_sim_time_sec();
984
985 samples_type::iterator iter;
986
987 // remove samples older than 15 seconds
988 for (iter = samples.begin(); iter != samples.end() && globals->get_sim_time_sec() - (*iter).timestamp >= 15; iter = samples.begin())
989 samples.erase(iter);
990
991 // append new sample
992 samples.push_back(Sample<double>(agl));
993
994 // calculate average
995 double new_value = 0;
996 if (! samples.empty())
997 {
998 // time consuming loop => queue limited to 75 samples
999 // (= 15seconds * 5samples/second)
1000 for (iter = samples.begin(); iter != samples.end(); iter++)
1001 new_value += (*iter).value;
1002 new_value /= samples.size();
1003 }
1004 new_value *= 0.75;
1005
1006 if (new_value > value)
1007 value = new_value;
1008
1009 return value;
1010}
1011
1012void
1013MK_VIII::IOHandler::TerrainClearanceFilter::reset ()
1014{
1015 samples.clear();
1016 value = 0;
1017 last_update = -1.0;
1018}
1019
1021 : mk(device), _lamp(LAMP_NONE), last_landing_gear(false), last_real_flaps_down(false)
1022{
1023 memset(&input_feeders, 0, sizeof(input_feeders));
1024 memset(&inputs.discretes, 0, sizeof(inputs.discretes));
1025 memset(&outputs, 0, sizeof(outputs));
1026 memset(&power_saved, 0, sizeof(power_saved));
1027
1028 mk_dinput_feed(landing_gear) = true;
1029 mk_dinput_feed(landing_flaps) = true;
1030 mk_dinput_feed(glideslope_inhibit) = true;
1031 mk_dinput_feed(decision_height) = true;
1032 mk_dinput_feed(autopilot_engaged) = true;
1033 mk_ainput_feed(uncorrected_barometric_altitude) = true;
1034 mk_ainput_feed(barometric_altitude_rate) = true;
1035 mk_ainput_feed(radio_altitude) = true;
1036 mk_ainput_feed(glideslope_deviation) = true;
1037 mk_ainput_feed(roll_angle) = true;
1038 mk_ainput_feed(localizer_deviation) = true;
1039 mk_ainput_feed(computed_airspeed) = true;
1040
1041 // will be unset on power on
1042 mk_doutput(gpws_inop) = true;
1043 mk_doutput(tad_inop) = true;
1044}
1045
1046void
1048{
1049 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
1050 return;
1051
1052 mk_doutput(gpws_inop) = false;
1053 mk_doutput(tad_inop) = false;
1054
1055 mk_doutput(glideslope_cancel) = power_saved.glideslope_cancel;
1056
1057 altitude_samples.clear();
1058 reset_terrain_clearance();
1059}
1060
1061void
1063{
1065 {
1066 last_landing_gear = mk_dinput(landing_gear);
1067 last_real_flaps_down = real_flaps_down();
1068 }
1069
1070 // read externally-latching input discretes
1071 update_alternate_discrete_input(&mk_dinput(mode6_low_volume));
1073}
1074
1075void
1077{
1078 power_saved.glideslope_cancel = mk_doutput(glideslope_cancel); // [SPEC] 6.2.5
1079
1080 memset(&outputs, 0, sizeof(outputs));
1081
1082 audio_inhibit_fault_timer.stop();
1083 landing_gear_fault_timer.stop();
1084 flaps_down_fault_timer.stop();
1085 momentary_flap_override_fault_timer.stop();
1086 self_test_fault_timer.stop();
1087 glideslope_cancel_fault_timer.stop();
1088 steep_approach_fault_timer.stop();
1089 gpws_inhibit_fault_timer.stop();
1090 ta_tcf_inhibit_fault_timer.stop();
1091
1092 // [SPEC] 6.9.3.5
1093 mk_doutput(gpws_inop) = true;
1094 mk_doutput(tad_inop) = true;
1095}
1096
1097void
1099{
1100 reset_terrain_clearance();
1101
1102 if (conf.momentary_flap_override_enabled)
1103 mk_doutput(flap_override) = false; // [INSTALL] 3.15.1.6
1104}
1105
1106void
1108{
1109 reset_terrain_clearance();
1110
1111 // landing or go-around, disable steep approach as per [SPEC] 6.2.1
1113 mk_doutput(steep_approach) = false;
1114}
1115
1116void
1118{
1119 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
1120 return;
1121
1122 // 1. process input feeders
1123
1124 if (mk_dinput_feed(landing_gear))
1125 mk_dinput(landing_gear) = mk_node(gear_down)->getBoolValue();
1126 if (mk_dinput_feed(landing_flaps))
1127 mk_dinput(landing_flaps) = mk_node(flaps)->getDoubleValue() < 1;
1128 if (mk_dinput_feed(glideslope_inhibit))
1129 mk_dinput(glideslope_inhibit) = mk_node(nav0_gs_distance)->getDoubleValue() < 0;
1130 if (mk_dinput_feed(autopilot_engaged))
1131 {
1132 const auto mode = mk_node(autopilot_heading_lock)->getStringValue();
1133 mk_dinput(autopilot_engaged) = !mode.empty();
1134 }
1135
1136 if (mk_ainput_feed(uncorrected_barometric_altitude))
1137 {
1138 if (mk_node(altimeter_serviceable)->getBoolValue())
1139 mk_ainput(uncorrected_barometric_altitude).set(mk_node(altimeter_altitude)->getDoubleValue());
1140 else
1141 mk_ainput(uncorrected_barometric_altitude).unset();
1142 }
1143 if (mk_ainput_feed(barometric_altitude_rate))
1144 // Do not use the vsi, because it is pressure-based only, and
1145 // therefore too laggy for GPWS alerting purposes. I guess that
1146 // a real ADC combines pressure-based and inerta-based altitude
1147 // rates to provide a non-laggy rate. That non-laggy rate is
1148 // best emulated by /velocities/vertical-speed-fps * 60.
1149 mk_ainput(barometric_altitude_rate).set(mk_node(vs)->getDoubleValue() * 60);
1150 if (mk_ainput_feed(radio_altitude))
1151 {
1152 double agl;
1153 switch (conf.altitude_source)
1154 {
1155 case 3:
1156 agl = mk_node(altitude_gear_agl)->getDoubleValue();
1157 break;
1158 case 4:
1159 agl = mk_node(altitude_radar_agl)->getDoubleValue();
1160 break;
1161 default: // 0,1,2 (and any currently unsupported values)
1162 agl = mk_node(altitude_agl)->getDoubleValue();
1163 break;
1164 }
1165 // Some flight models may return negative values when on the
1166 // ground or after a crash; do not allow them.
1167 mk_ainput(radio_altitude).set(SG_MAX2(0.0, agl));
1168 }
1169 if (mk_ainput_feed(glideslope_deviation))
1170 {
1171 if (mk_node(nav0_serviceable)->getBoolValue()
1172 && mk_node(nav0_gs_serviceable)->getBoolValue()
1173 && mk_node(nav0_in_range)->getBoolValue()
1174 && mk_node(nav0_has_gs)->getBoolValue())
1175 // gs-needle-deflection is expressed in degrees, and 1 dot =
1176 // 0.32 degrees (according to
1177 // http://www.ntsb.gov/Recs/letters/2000/A00_41_45.pdf)
1178 mk_ainput(glideslope_deviation).set(mk_node(nav0_gs_needle_deflection)->getDoubleValue() / 0.32 * GLIDESLOPE_DOTS_TO_DDM);
1179 else
1180 mk_ainput(glideslope_deviation).unset();
1181 }
1182 if (mk_ainput_feed(roll_angle))
1183 {
1184 if (conf.use_attitude_indicator)
1185 {
1186 // read data from attitude indicator instrument (requires vacuum system to work)
1187 if (mk_node(ai_serviceable)->getBoolValue() && ! mk_node(ai_caged)->getBoolValue())
1188 mk_ainput(roll_angle).set(mk_node(ai_roll)->getDoubleValue());
1189 else
1190 mk_ainput(roll_angle).unset();
1191 }
1192 else
1193 {
1194 // use simulator source
1195 mk_ainput(roll_angle).set(mk_node(orientation_roll)->getDoubleValue());
1196 }
1197 }
1198 if (mk_ainput_feed(localizer_deviation))
1199 {
1200 if (mk_node(nav0_serviceable)->getBoolValue()
1201 && mk_node(nav0_cdi_serviceable)->getBoolValue()
1202 && mk_node(nav0_in_range)->getBoolValue()
1203 && mk_node(nav0_nav_loc)->getBoolValue())
1204 // heading-needle-deflection is expressed in degrees, and 1
1205 // dot = 2 degrees (0.5 degrees for ILS, but navradio.cxx
1206 // performs the conversion)
1207 mk_ainput(localizer_deviation).set(mk_node(nav0_heading_needle_deflection)->getDoubleValue() / 2 * LOCALIZER_DOTS_TO_DDM);
1208 else
1209 mk_ainput(localizer_deviation).unset();
1210 }
1211 if (mk_ainput_feed(computed_airspeed))
1212 {
1213 if (mk_node(asi_serviceable)->getBoolValue())
1214 mk_ainput(computed_airspeed).set(mk_node(asi_speed)->getDoubleValue());
1215 else
1216 mk_ainput(computed_airspeed).unset();
1217 }
1218
1219 // 2. compute data
1220
1221 mk_data(decision_height).set(&mk_ainput(decision_height));
1222 mk_data(radio_altitude).set(&mk_ainput(radio_altitude));
1223 mk_data(roll_angle).set(&mk_ainput(roll_angle));
1224
1225 // update barometric_altitude_rate as per [SPEC] 6.2.1: "During
1226 // normal conditions, the system will base Mode 1 computations upon
1227 // barometric rate from the ADC. If this computed data is not valid
1228 // or available then the system will use internally computed
1229 // barometric altitude rate."
1230
1231 if (! mk_ainput(barometric_altitude_rate).ncd)
1232 // the altitude rate input is valid, use it
1233 mk_data(barometric_altitude_rate).set(mk_ainput(barometric_altitude_rate).get());
1234 else
1235 {
1236 bool has = false;
1237
1238 // The altitude rate input is invalid. We can compute an
1239 // altitude rate if all the following conditions are true:
1240 //
1241 // - the altitude input is valid
1242 // - there is an altitude sample with age >= 1 second
1243 // - that altitude sample is valid
1244
1245 if (! mk_ainput(uncorrected_barometric_altitude).ncd)
1246 {
1247 if (! altitude_samples.empty() && ! altitude_samples.front().value.ncd)
1248 {
1249 double elapsed = globals->get_sim_time_sec() - altitude_samples.front().timestamp;
1250 if (elapsed >= 1)
1251 {
1252 has = true;
1253 mk_data(barometric_altitude_rate).set((mk_ainput(uncorrected_barometric_altitude).get() - altitude_samples.front().value.get()) / elapsed * 60);
1254 }
1255 }
1256 }
1257
1258 if (! has)
1259 mk_data(barometric_altitude_rate).unset();
1260 }
1261
1262 altitude_samples.push_back(Sample< Parameter<double> >(mk_ainput(uncorrected_barometric_altitude)));
1263
1264 // Erase everything from the beginning of the list up to the sample
1265 // preceding the most recent sample whose age is >= 1 second.
1266
1267 altitude_samples_type::iterator erase_last = altitude_samples.begin();
1268 altitude_samples_type::iterator iter;
1269
1270 for (iter = altitude_samples.begin(); iter != altitude_samples.end(); iter++)
1271 {
1272 if (globals->get_sim_time_sec() - (*iter).timestamp >= 1)
1273 erase_last = iter;
1274 else
1275 break;
1276 }
1277
1278 if (erase_last != altitude_samples.begin())
1279 altitude_samples.erase(altitude_samples.begin(), erase_last);
1280
1281 // update GPS data
1282
1283 if (conf.use_internal_gps)
1284 {
1285 mk_data(gps_altitude).set(mk_node(altitude)->getDoubleValue());
1286 mk_data(gps_latitude).set(mk_node(latitude)->getDoubleValue());
1287 mk_data(gps_longitude).set(mk_node(longitude)->getDoubleValue());
1288 mk_data(gps_vertical_figure_of_merit).set(0.0);
1289 }
1290 else
1291 {
1292 mk_data(gps_altitude).set(&mk_ainput(gps_altitude));
1293 mk_data(gps_latitude).set(&mk_ainput(gps_latitude));
1294 mk_data(gps_longitude).set(&mk_ainput(gps_longitude));
1295 mk_data(gps_vertical_figure_of_merit).set(&mk_ainput(gps_vertical_figure_of_merit));
1296 }
1297
1298 // update glideslope and localizer
1299 mk_data(glideslope_deviation_dots).set(&mk_ainput(glideslope_deviation), GLIDESLOPE_DDM_TO_DOTS);
1300 mk_data(localizer_deviation_dots).set(&mk_ainput(localizer_deviation), LOCALIZER_DDM_TO_DOTS);
1301
1302 // Update geometric altitude; [SPEC] 6.7.8 provides an overview of a
1303 // complex algorithm which combines several input sources to
1304 // calculate the geometric altitude. Since the exact algorithm is
1305 // not given, we fallback to a much simpler method.
1306 if (! mk_data(gps_altitude).ncd)
1307 mk_data(geometric_altitude).set(mk_data(gps_altitude).get());
1308 else if (! mk_ainput(uncorrected_barometric_altitude).ncd)
1309 mk_data(geometric_altitude).set(mk_ainput(uncorrected_barometric_altitude).get());
1310 else
1311 mk_data(geometric_altitude).unset();
1312
1313 // update terrain clearance
1314 update_terrain_clearance();
1315
1316 // 3. perform sanity checks
1317 if (! mk_data(radio_altitude).ncd && mk_data(radio_altitude).get() < 0)
1318 mk_data(radio_altitude).unset();
1319
1320 if (! mk_data(decision_height).ncd && mk_data(decision_height).get() < 0)
1321 mk_data(decision_height).unset();
1322
1323 if (! mk_data(gps_latitude).ncd
1324 && (mk_data(gps_latitude).get() < -90
1325 || mk_data(gps_latitude).get() > 90))
1326 mk_data(gps_latitude).unset();
1327
1328 if (! mk_data(gps_longitude).ncd
1329 && (mk_data(gps_longitude).get() < -180
1330 || mk_data(gps_longitude).get() > 180))
1331 mk_data(gps_longitude).unset();
1332
1333 if (! mk_data(roll_angle).ncd
1334 && ((mk_data(roll_angle).get() < -180)
1335 || (mk_data(roll_angle).get() > 180)))
1336 mk_data(roll_angle).unset();
1337
1338 // 4. process input feeders requiring data computed above
1339 if (mk_dinput_feed(decision_height))
1340 {
1341 mk_dinput(decision_height_100) = ! mk_data(radio_altitude).ncd
1342 && ! mk_data(decision_height).ncd
1343 && mk_data(radio_altitude).get() <= mk_data(decision_height).get()+100;
1344
1345 mk_dinput(decision_height) = ! mk_data(radio_altitude).ncd
1346 && ! mk_data(decision_height).ncd
1347 && mk_data(radio_altitude).get() <= mk_data(decision_height).get();
1348 }
1349}
1350
1351void
1352MK_VIII::IOHandler::update_terrain_clearance ()
1353{
1354 if (! mk_data(radio_altitude).ncd)
1355 mk_data(terrain_clearance).set(terrain_clearance_filter.update(mk_data(radio_altitude).get()));
1356 else
1357 mk_data(terrain_clearance).unset();
1358}
1359
1360void
1361MK_VIII::IOHandler::reset_terrain_clearance ()
1362{
1363 terrain_clearance_filter.reset();
1364 update_terrain_clearance();
1365}
1366
1367void
1369{
1370 reset_terrain_clearance();
1371}
1372
1373void
1374MK_VIII::IOHandler::handle_input_fault (bool test, FaultHandler::Fault fault)
1375{
1376 if (test)
1377 {
1378 mk->fault_handler.set_fault(fault);
1379 }
1380 else
1381 mk->fault_handler.unset_fault(fault);
1382}
1383
1384void
1385MK_VIII::IOHandler::handle_input_fault (bool test,
1386 Timer *timer,
1387 double max_duration,
1388 FaultHandler::Fault fault)
1389{
1390 if (test)
1391 {
1392 if (! (mk->fault_handler.faults & (1<<fault)))
1393 {
1394 if (timer->start_or_elapsed() >= max_duration)
1395 mk->fault_handler.set_fault(fault);
1396 }
1397 }
1398 else
1399 {
1400 mk->fault_handler.unset_fault(fault);
1401 timer->stop();
1402 }
1403}
1404
1405void
1407{
1408 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
1409 return;
1410
1411 // [INSTALL] 3.15.1.3
1412 handle_input_fault(mk_dinput(audio_inhibit),
1413 &audio_inhibit_fault_timer,
1414 60,
1415 FaultHandler::FAULT_ALL_MODES_INHIBIT);
1416
1417 // [INSTALL] appendix E 6.6.2
1418 handle_input_fault(mk_dinput(landing_gear)
1419 && ! mk_ainput(computed_airspeed).ncd
1420 && mk_ainput(computed_airspeed).get() > conf.faults->max_gear_down_airspeed,
1421 &landing_gear_fault_timer,
1422 60,
1423 FaultHandler::FAULT_GEAR_SWITCH);
1424
1425 // [INSTALL] appendix E 6.6.4
1426 handle_input_fault(flaps_down()
1427 && ! mk_ainput(computed_airspeed).ncd
1428 && mk_ainput(computed_airspeed).get() > conf.faults->max_flaps_down_airspeed,
1429 &flaps_down_fault_timer,
1430 60,
1431 FaultHandler::FAULT_FLAPS_SWITCH);
1432
1433 // [INSTALL] appendix E 6.6.6
1434 if (conf.momentary_flap_override_enabled)
1435 handle_input_fault(mk_dinput(momentary_flap_override),
1436 &momentary_flap_override_fault_timer,
1437 15,
1438 FaultHandler::FAULT_MOMENTARY_FLAP_OVERRIDE_INVALID);
1439
1440 // [INSTALL] appendix E 6.6.7
1441 handle_input_fault(mk_dinput(self_test),
1442 &self_test_fault_timer,
1443 60,
1444 FaultHandler::FAULT_SELF_TEST_INVALID);
1445
1446 // [INSTALL] appendix E 6.6.13
1447 handle_input_fault(mk_dinput(glideslope_cancel),
1448 &glideslope_cancel_fault_timer,
1449 15,
1450 FaultHandler::FAULT_GLIDESLOPE_CANCEL_INVALID);
1451
1452 // [INSTALL] appendix E 6.6.25
1454 handle_input_fault(mk_dinput(steep_approach),
1455 &steep_approach_fault_timer,
1456 15,
1457 FaultHandler::FAULT_STEEP_APPROACH_INVALID);
1458
1459 // [INSTALL] appendix E 6.6.27
1460 if (conf.gpws_inhibit_enabled)
1461 handle_input_fault(mk_dinput(gpws_inhibit),
1462 &gpws_inhibit_fault_timer,
1463 5,
1464 FaultHandler::FAULT_GPWS_INHIBIT);
1465
1466 // [INSTALL] does not specify a fault for this one, but it makes
1467 // sense to have it behave like GPWS inhibit
1468 handle_input_fault(mk_dinput(ta_tcf_inhibit),
1469 &ta_tcf_inhibit_fault_timer,
1470 5,
1471 FaultHandler::FAULT_TA_TCF_INHIBIT);
1472
1473 // [PILOT] page 48: "In the event that required data for a
1474 // particular function is not available, then that function is
1475 // automatically inhibited and annunciated"
1476
1477 handle_input_fault(mk_data(radio_altitude).ncd
1478 || mk_ainput(uncorrected_barometric_altitude).ncd
1479 || mk_data(barometric_altitude_rate).ncd
1480 || mk_ainput(computed_airspeed).ncd
1481 || mk_data(terrain_clearance).ncd,
1482 FaultHandler::FAULT_MODES14_INPUTS_INVALID);
1483
1484 if (! mk_dinput(glideslope_inhibit))
1485 handle_input_fault(mk_data(radio_altitude).ncd,
1486 FaultHandler::FAULT_MODE5_INPUTS_INVALID);
1487
1488 if (mk->mode6_handler.altitude_callouts_enabled())
1489 handle_input_fault(mk->mode6_handler.conf.above_field_voice
1490 ? (mk_data(gps_latitude).ncd
1491 || mk_data(gps_longitude).ncd
1492 || mk_data(geometric_altitude).ncd)
1493 : mk_data(radio_altitude).ncd,
1494 FaultHandler::FAULT_MODE6_INPUTS_INVALID);
1495
1496 if (mk->mode6_handler.conf.bank_angle_enabled)
1497 handle_input_fault(mk_data(roll_angle).ncd,
1498 FaultHandler::FAULT_BANK_ANGLE_INPUTS_INVALID);
1499
1500 if (mk->tcf_handler.conf.enabled)
1501 handle_input_fault(mk_data(radio_altitude).ncd
1502 || mk_data(geometric_altitude).ncd
1503 || mk_data(gps_latitude).ncd
1504 || mk_data(gps_longitude).ncd
1505 || mk_data(gps_vertical_figure_of_merit).ncd,
1506 FaultHandler::FAULT_TCF_INPUTS_INVALID);
1507}
1508
1509void
1511{
1512 assert(mk->system_handler.state == SystemHandler::STATE_ON);
1513
1514 if (ptr == &mk_dinput(mode6_low_volume))
1515 {
1516 if (mk->configuration_module.state == ConfigurationModule::STATE_OK
1517 && mk->self_test_handler.state == SelfTestHandler::STATE_NONE)
1518 mk->mode6_handler.set_volume(*ptr ? modify_amplitude(1.0, -6) : 1.0);
1519 }
1520 else if (ptr == &mk_dinput(audio_inhibit))
1521 {
1522 if (mk->configuration_module.state == ConfigurationModule::STATE_OK
1523 && mk->self_test_handler.state == SelfTestHandler::STATE_NONE)
1524 mk->voice_player.set_volume(*ptr ? 0.0 : mk->voice_player.conf.volume);
1525 }
1526}
1527
1528void
1530{
1531 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
1532 return;
1533
1534 // [SPEC] 6.2.1
1535 if (conf.momentary_flap_override_enabled
1537 && ! mk->state_handler.takeoff
1538 && (mk_data(radio_altitude).ncd || mk_data(radio_altitude).get() <= 50))
1539 mk_doutput(flap_override) = false;
1540
1541 // [SPEC] 6.2.1
1543 {
1545 && ! mk->state_handler.takeoff
1546 && ((last_landing_gear && ! mk_dinput(landing_gear))
1547 || (last_real_flaps_down && ! real_flaps_down())))
1548 {
1549 // gear or flaps raised during approach: it's a go-around
1550 mk_doutput(steep_approach) = false;
1551 }
1552
1553 last_landing_gear = mk_dinput(landing_gear);
1554 last_real_flaps_down = real_flaps_down();
1555 }
1556
1557 // [SPEC] 6.2.5
1559 && (mk_data(glideslope_deviation_dots).ncd
1560 || mk_data(radio_altitude).ncd
1561 || mk_data(radio_altitude).get() > 2000
1562 || mk_data(radio_altitude).get() < 30))
1564}
1565
1566void
1568{
1569 if (mk->voice_player.voice)
1570 {
1571 const struct
1572 {
1573 int bit;
1574 VoicePlayer::Voice *voice;
1575 } voices[] = {
1576 { 11, mk_voice(sink_rate_pause_sink_rate) },
1577 { 12, mk_voice(pull_up) },
1578 { 13, mk_voice(terrain) },
1579 { 13, mk_voice(terrain_pause_terrain) },
1580 { 14, mk_voice(dont_sink_pause_dont_sink) },
1581 { 15, mk_voice(too_low_gear) },
1582 { 16, mk_voice(too_low_flaps) },
1583 { 17, mk_voice(too_low_terrain) },
1584 { 18, mk_voice(soft_glideslope) },
1585 { 18, mk_voice(hard_glideslope) },
1586 { 19, mk_voice(minimums_minimums) }
1587 };
1588
1589 for (size_t i = 0; i < n_elements(voices); i++)
1590 {
1591 if (voices[i].voice == mk->voice_player.voice)
1592 {
1593 mk_aoutput(egpws_alert_discrete_1) = 1 << voices[i].bit;
1594 return;
1595 }
1596 }
1597 }
1598
1599 mk_aoutput(egpws_alert_discrete_1) = 0;
1600}
1601
1602void
1604{
1605 mk_aoutput(egpwc_logic_discretes) = 0;
1606
1607 if (mk->state_handler.takeoff)
1608 mk_aoutput(egpwc_logic_discretes) |= 1 << 18;
1609
1610 static const struct
1611 {
1612 int bit;
1613 unsigned int alerts;
1614 } logic[] = {
1615 { 13, mk_alert(TCF_TOO_LOW_TERRAIN) },
1616 { 19, mk_alert(MODE1_SINK_RATE) },
1617 { 20, mk_alert(MODE1_PULL_UP) },
1618 { 21, mk_alert(MODE2A_PREFACE) | mk_alert(MODE2B_PREFACE) | mk_alert(MODE2B_LANDING_MODE) | mk_alert(MODE2A_ALTITUDE_GAIN_TERRAIN_CLOSING) },
1619 { 22, mk_alert(MODE2A) | mk_alert(MODE2B) },
1620 { 23, mk_alert(MODE3) },
1621 { 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) },
1622 { 25, mk_alert(MODE5_SOFT) | mk_alert(MODE5_HARD) }
1623 };
1624
1625 for (size_t i = 0; i < n_elements(logic); i++)
1626 {
1627 if (mk_test_alerts(logic[i].alerts))
1628 mk_aoutput(egpwc_logic_discretes) |= 1 << logic[i].bit;
1629 }
1630}
1631
1632void
1634{
1635 if (mk->voice_player.voice)
1636 {
1637 const struct
1638 {
1639 int bit;
1640 VoicePlayer::Voice *voice;
1641 } voices[] = {
1642 { 11, mk_voice(minimums_minimums) },
1643 { 16, mk_altitude_voice(Mode6Handler::ALTITUDE_CALLOUT_10) },
1644 { 17, mk_altitude_voice(Mode6Handler::ALTITUDE_CALLOUT_20) },
1645 { 18, mk_altitude_voice(Mode6Handler::ALTITUDE_CALLOUT_30) },
1646 { 19, mk_altitude_voice(Mode6Handler::ALTITUDE_CALLOUT_40) },
1647 { 20, mk_altitude_voice(Mode6Handler::ALTITUDE_CALLOUT_50) },
1648 { 23, mk_altitude_voice(Mode6Handler::ALTITUDE_CALLOUT_100) },
1649 { 24, mk_altitude_voice(Mode6Handler::ALTITUDE_CALLOUT_200) },
1650 { 25, mk_altitude_voice(Mode6Handler::ALTITUDE_CALLOUT_300) }
1651 };
1652
1653 for (size_t i = 0; i < n_elements(voices); i++)
1654 {
1655 if (voices[i].voice == mk->voice_player.voice)
1656 {
1657 mk_aoutput(mode6_callouts_discrete_1) = 1 << voices[i].bit;
1658 return;
1659 }
1660 }
1661 }
1662 mk_aoutput(mode6_callouts_discrete_1) = 0;
1663}
1664
1665void
1667{
1668 if (mk->voice_player.voice)
1669 {
1670 const struct
1671 {
1672 int bit;
1673 VoicePlayer::Voice *voice;
1674 } voices[] = {
1675 { 11, mk_altitude_voice(Mode6Handler::ALTITUDE_CALLOUT_400) },
1676 { 12, mk_altitude_voice(Mode6Handler::ALTITUDE_CALLOUT_500) },
1677 { 13, mk_altitude_voice(Mode6Handler::ALTITUDE_CALLOUT_1000) },
1678 { 14, mk_altitude_voice(Mode6Handler::ALTITUDE_CALLOUT_2500) },
1679 { 18, mk_voice(bank_angle_pause_bank_angle) },
1680 { 18, mk_voice(bank_angle_pause_bank_angle_3) },
1681 { 23, mk_voice(five_hundred_above) }
1682 };
1683
1684 for (size_t i = 0; i < n_elements(voices); i++)
1685 {
1686 if (voices[i].voice == mk->voice_player.voice)
1687 {
1688 mk_aoutput(mode6_callouts_discrete_2) = 1 << voices[i].bit;
1689 return;
1690 }
1691 }
1692 }
1693
1694 mk_aoutput(mode6_callouts_discrete_2) = 0;
1695}
1696
1697void
1699{
1700 mk_aoutput(egpws_alert_discrete_2) = 1 << 27; // Terrain NA
1701
1703 mk_aoutput(egpws_alert_discrete_2) |= 1 << 11;
1704 if (mk_doutput(gpws_alert))
1705 mk_aoutput(egpws_alert_discrete_2) |= 1 << 12;
1706 if (mk_doutput(gpws_warning))
1707 mk_aoutput(egpws_alert_discrete_2) |= 1 << 13;
1708 if (mk_doutput(gpws_inop))
1709 mk_aoutput(egpws_alert_discrete_2) |= 1 << 14;
1710 if (mk_doutput(audio_on))
1711 mk_aoutput(egpws_alert_discrete_2) |= 1 << 16;
1712 if (mk_doutput(tad_inop))
1713 mk_aoutput(egpws_alert_discrete_2) |= 1 << 24;
1714 if (mk->fault_handler.has_faults())
1715 mk_aoutput(egpws_alert_discrete_2) |= 1 << 25;
1716}
1717
1718void
1720{
1721 mk_aoutput(egpwc_alert_discrete_3) = 1 << 17 | 1 << 18;
1722
1723 if (mk->fault_handler.faults & (1<<FaultHandler::FAULT_MODES14_INPUTS_INVALID))
1724 mk_aoutput(egpwc_alert_discrete_3) |= 1 << 11;
1725 if (mk->fault_handler.faults & (1<<FaultHandler::FAULT_MODE5_INPUTS_INVALID))
1726 mk_aoutput(egpwc_alert_discrete_3) |= 1 << 12;
1727 if (mk->fault_handler.faults & (1<<FaultHandler::FAULT_MODE6_INPUTS_INVALID))
1728 mk_aoutput(egpwc_alert_discrete_3) |= 1 << 13;
1729 if (mk->fault_handler.faults & (1<<FaultHandler::FAULT_BANK_ANGLE_INPUTS_INVALID))
1730 mk_aoutput(egpwc_alert_discrete_3) |= 1 << 14;
1731 if (mk_doutput(tad_inop))
1732 mk_aoutput(egpwc_alert_discrete_3) |= 1 << 16;
1733}
1734
1735void
1737{
1738 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
1739 return;
1740
1741 mk_doutput(audio_on) = ! mk_dinput(audio_inhibit)
1742 && mk->voice_player.voice
1743 && ! mk->voice_player.voice->element->silence;
1744
1751}
1752
1753bool *
1755{
1756 switch (lamp)
1757 {
1758 case LAMP_GLIDESLOPE:
1759 return &mk_doutput(gpws_alert);
1760
1761 case LAMP_CAUTION:
1762 return conf.lamp->format2 ? &mk_doutput(gpws_alert) : &mk_doutput(gpws_warning);
1763
1764 case LAMP_WARNING:
1765 return &mk_doutput(gpws_warning);
1766
1767 default:
1769 return NULL;
1770 }
1771}
1772
1773void
1775{
1776 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
1777 return;
1778
1779 if (_lamp != LAMP_NONE && conf.lamp->flashing)
1780 {
1781 // [SPEC] 6.9.3: 70 cycles per minute
1782 if (lamp_timer.elapsed() >= 60.0 / 70.0 / 2.0)
1783 {
1784 bool *output = get_lamp_output(_lamp);
1785 *output = ! *output;
1786 lamp_timer.start();
1787 }
1788 }
1789}
1790
1791void
1793{
1794 if (lamp == _lamp)
1795 return;
1796
1797 _lamp = lamp;
1798
1799 mk_doutput(gpws_warning) = false;
1800 mk_doutput(gpws_alert) = false;
1801
1802 if (lamp != LAMP_NONE)
1803 {
1804 *get_lamp_output(lamp) = true;
1805 lamp_timer.start();
1806 }
1807}
1808
1809bool
1811{
1812 return conf.gpws_inhibit_enabled && mk_dinput(gpws_inhibit);
1813}
1814
1815bool
1817{
1818 return conf.flap_reversal ? mk_dinput(landing_flaps) : ! mk_dinput(landing_flaps);
1819}
1820
1821bool
1823{
1824 return flap_override() ? true : real_flaps_down();
1825}
1826
1827bool
1829{
1830 return conf.momentary_flap_override_enabled ? mk_doutput(flap_override) : false;
1831}
1832
1833bool
1835{
1836 if (conf.steep_approach_enabled)
1837 // If alternate action was configured in category 13, we return
1838 // the value of the input discrete (which should be connected to a
1839 // latching steep approach cockpit switch). Otherwise, we return
1840 // the value of the output discrete.
1841 return conf.alternate_steep_approach ? mk_dinput(steep_approach) : mk_doutput(steep_approach);
1842 else
1843 return false;
1844}
1845
1846bool
1848{
1849 return conf.steep_approach_enabled && ! conf.alternate_steep_approach;
1850}
1851
1852void
1853MK_VIII::IOHandler::tie_input (SGPropertyNode *node,
1854 const char *name,
1855 bool *input,
1856 bool *feed)
1857{
1858 mk->properties_handler.tie(node, (string("inputs/discretes/") + name).c_str(),
1860 if (feed)
1861 mk->properties_handler.tie(node, (string("input-feeders/discretes/") + name).c_str(), SGRawValuePointer<bool>(feed));
1862}
1863
1864void
1865MK_VIII::IOHandler::tie_input (SGPropertyNode *node,
1866 const char *name,
1867 Parameter<double> *input,
1868 bool *feed)
1869{
1870 mk->properties_handler.tie(node, (string("inputs/arinc429/") + name).c_str(), SGRawValuePointer<double>(input->get_pointer()));
1871 mk->properties_handler.tie(node, (string("inputs/arinc429/") + name + "-ncd").c_str(), SGRawValuePointer<bool>(&input->ncd));
1872 if (feed)
1873 mk->properties_handler.tie(node, (string("input-feeders/arinc429/") + name).c_str(), SGRawValuePointer<bool>(feed));
1874}
1875
1876void
1877MK_VIII::IOHandler::tie_output (SGPropertyNode *node,
1878 const char *name,
1879 bool *output)
1880{
1881 SGPropertyNode *child = node->getNode((string("outputs/discretes/") + name), true);
1882
1883 mk->properties_handler.tie(child, SGRawValuePointer<bool>(output));
1884 child->setAttribute(SGPropertyNode::WRITE, false);
1885}
1886
1887void
1888MK_VIII::IOHandler::tie_output (SGPropertyNode *node,
1889 const char *name,
1890 int *output)
1891{
1892 SGPropertyNode *child = node->getNode((string("outputs/arinc429/") + name), true);
1893
1894 mk->properties_handler.tie(child, SGRawValuePointer<int>(output));
1895 child->setAttribute(SGPropertyNode::WRITE, false);
1896}
1897
1898void
1899MK_VIII::IOHandler::bind (SGPropertyNode *node)
1900{
1901 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));
1902
1903 tie_input(node, "landing-gear", &mk_dinput(landing_gear), &mk_dinput_feed(landing_gear));
1904 tie_input(node, "landing-flaps", &mk_dinput(landing_flaps), &mk_dinput_feed(landing_flaps));
1905 tie_input(node, "momentary-flap-override", &mk_dinput(momentary_flap_override));
1906 tie_input(node, "self-test", &mk_dinput(self_test));
1907 tie_input(node, "glideslope-inhibit", &mk_dinput(glideslope_inhibit), &mk_dinput_feed(glideslope_inhibit));
1908 tie_input(node, "glideslope-cancel", &mk_dinput(glideslope_cancel));
1909 tie_input(node, "decision-height", &mk_dinput(decision_height), &mk_dinput_feed(decision_height));
1910 tie_input(node, "mode6-low-volume", &mk_dinput(mode6_low_volume));
1911 tie_input(node, "audio-inhibit", &mk_dinput(audio_inhibit));
1912 tie_input(node, "ta-tcf-inhibit", &mk_dinput(ta_tcf_inhibit));
1913 tie_input(node, "autopilot-engaged", &mk_dinput(autopilot_engaged), &mk_dinput_feed(autopilot_engaged));
1914 tie_input(node, "steep-approach", &mk_dinput(steep_approach));
1915 tie_input(node, "gpws-inhibit", &mk_dinput(gpws_inhibit));
1916
1917 tie_input(node, "uncorrected-barometric-altitude", &mk_ainput(uncorrected_barometric_altitude), &mk_ainput_feed(uncorrected_barometric_altitude));
1918 tie_input(node, "barometric-altitude-rate", &mk_ainput(barometric_altitude_rate), &mk_ainput_feed(barometric_altitude_rate));
1919 tie_input(node, "gps-altitude", &mk_ainput(gps_altitude));
1920 tie_input(node, "gps-latitude", &mk_ainput(gps_latitude));
1921 tie_input(node, "gps-longitude", &mk_ainput(gps_longitude));
1922 tie_input(node, "gps-vertical-figure-of-merit", &mk_ainput(gps_vertical_figure_of_merit));
1923 tie_input(node, "radio-altitude", &mk_ainput(radio_altitude), &mk_ainput_feed(radio_altitude));
1924 tie_input(node, "glideslope-deviation", &mk_ainput(glideslope_deviation), &mk_ainput_feed(glideslope_deviation));
1925 tie_input(node, "roll-angle", &mk_ainput(roll_angle), &mk_ainput_feed(roll_angle));
1926 tie_input(node, "localizer-deviation", &mk_ainput(localizer_deviation), &mk_ainput_feed(localizer_deviation));
1927 tie_input(node, "computed-airspeed", &mk_ainput(computed_airspeed), &mk_ainput_feed(computed_airspeed));
1928 tie_input(node, "decision-height", &mk_ainput(decision_height), &mk_ainput_feed(decision_height));
1929
1930 tie_output(node, "gpws-warning", &mk_doutput(gpws_warning));
1931 tie_output(node, "gpws-alert", &mk_doutput(gpws_alert));
1932 tie_output(node, "audio-on", &mk_doutput(audio_on));
1933 tie_output(node, "gpws-inop", &mk_doutput(gpws_inop));
1934 tie_output(node, "tad-inop", &mk_doutput(tad_inop));
1935 tie_output(node, "flap-override", &mk_doutput(flap_override));
1936 tie_output(node, "glideslope-cancel", &mk_doutput(glideslope_cancel));
1937 tie_output(node, "steep-approach", &mk_doutput(steep_approach));
1938
1939 tie_output(node, "egpws-alert-discrete-1", &mk_aoutput(egpws_alert_discrete_1));
1940 tie_output(node, "egpwc-logic-discretes", &mk_aoutput(egpwc_logic_discretes));
1941 tie_output(node, "mode6-callouts-discrete-1", &mk_aoutput(mode6_callouts_discrete_1));
1942 tie_output(node, "mode6-callouts-discrete-2", &mk_aoutput(mode6_callouts_discrete_2));
1943 tie_output(node, "egpws-alert-discrete-2", &mk_aoutput(egpws_alert_discrete_2));
1944 tie_output(node, "egpwc-alert-discrete-3", &mk_aoutput(egpwc_alert_discrete_3));
1945}
1946
1947bool
1949{
1950 return *ptr;
1951}
1952
1953void
1955{
1956 if (value == *ptr)
1957 return;
1958
1959 if (mk->system_handler.state == SystemHandler::STATE_ON)
1960 {
1961 if (ptr == &mk_dinput(momentary_flap_override))
1962 {
1963 if (mk->configuration_module.state == ConfigurationModule::STATE_OK
1964 && mk->self_test_handler.state == SelfTestHandler::STATE_NONE
1965 && conf.momentary_flap_override_enabled
1966 && value)
1968 }
1969 else if (ptr == &mk_dinput(self_test))
1970 mk->self_test_handler.handle_button_event(value);
1971 else if (ptr == &mk_dinput(glideslope_cancel))
1972 {
1973 if (mk->configuration_module.state == ConfigurationModule::STATE_OK
1974 && mk->self_test_handler.state == SelfTestHandler::STATE_NONE
1975 && value)
1976 {
1978 {
1979 // [SPEC] 6.2.5
1980
1981 // Although we are not called from update(), we are
1982 // sure that the inputs we use here are defined,
1983 // since state is STATE_ON.
1984
1985 if (! mk_data(glideslope_deviation_dots).ncd
1986 && ! mk_data(radio_altitude).ncd
1987 && mk_data(radio_altitude).get() <= 2000
1988 && mk_data(radio_altitude).get() >= 30)
1990 }
1991 // else do nothing ([PILOT] page 22: "Glideslope Cancel
1992 // can not be deselected (reset) by again pressing the
1993 // Glideslope Cancel switch.")
1994 }
1995 }
1996 else if (ptr == &mk_dinput(steep_approach))
1997 {
1998 if (mk->configuration_module.state == ConfigurationModule::STATE_OK
1999 && mk->self_test_handler.state == SelfTestHandler::STATE_NONE
2001 && value)
2003 }
2004 }
2005
2006 *ptr = value;
2007
2008 if (mk->system_handler.state == SystemHandler::STATE_ON)
2010}
2011
2012void
2014{
2015 printf("%s\n", name);
2016}
2017
2018void
2019MK_VIII::IOHandler::present_status_item (const char *name, const char *value)
2020{
2021 if (value)
2022 printf("\t%-32s %s\n", name, value);
2023 else
2024 printf("\t%s\n", name);
2025}
2026
2027void
2029{
2030 printf("\t\t%s\n", name);
2031}
2032
2033void
2035{
2036 // [SPEC] 6.10.13
2037
2038 if (mk->system_handler.state != SystemHandler::STATE_ON)
2039 return;
2040
2041 present_status_section("EGPWC CONFIGURATION");
2042 present_status_item("PART NUMBER:", "965-1220-000"); // [SPEC] 1.1
2043 present_status_item("MOD STATUS:", "N/A");
2044 present_status_item("SERIAL NUMBER:", "N/A");
2045 printf("\n");
2046 present_status_item("APPLICATION S/W VERSION:", FLIGHTGEAR_VERSION);
2047 present_status_item("TERRAIN DATABASE VERSION:", FLIGHTGEAR_VERSION);
2048 present_status_item("ENVELOPE MOD DATABASE VERSION:", "N/A");
2049 present_status_item("BOOT CODE VERSION:", FLIGHTGEAR_VERSION);
2050 printf("\n");
2051
2052 present_status_section("CURRENT FAULTS");
2053 if (mk->configuration_module.state == ConfigurationModule::STATE_OK && ! mk->fault_handler.has_faults())
2054 present_status_item("NO FAULTS");
2055 else
2056 {
2057 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
2058 {
2059 present_status_item("GPWS COMPUTER FAULTS:");
2060 switch (mk->configuration_module.state)
2061 {
2062 case ConfigurationModule::STATE_INVALID_DATABASE:
2063 present_status_subitem("APPLICATION DATABASE FAILED");
2064 break;
2065
2066 case ConfigurationModule::STATE_INVALID_AIRCRAFT_TYPE:
2067 present_status_subitem("CONFIGURATION TYPE INVALID");
2068 break;
2069
2070 default:
2072 break;
2073 }
2074 }
2075 else
2076 {
2077 present_status_item("GPWS COMPUTER OK");
2078 present_status_item("GPWS EXTERNAL FAULTS:");
2079
2080 static const char *fault_names[] = {
2081 "ALL MODES INHIBIT",
2082 "GEAR SWITCH",
2083 "FLAPS SWITCH",
2084 "MOMENTARY FLAP OVERRIDE INVALID",
2085 "SELF TEST INVALID",
2086 "GLIDESLOPE CANCEL INVALID",
2087 "STEEP APPROACH INVALID",
2088 "GPWS INHIBIT",
2089 "TA & TCF INHIBIT",
2090 "MODES 1-4 INPUTS INVALID",
2091 "MODE 5 INPUTS INVALID",
2092 "MODE 6 INPUTS INVALID",
2093 "BANK ANGLE INPUTS INVALID",
2094 "TCF INPUTS INVALID"
2095 };
2096
2097 for (size_t i = 0; i < n_elements(fault_names); i++)
2098 {
2099 if (mk->fault_handler.faults & (1<<i))
2100 present_status_subitem(fault_names[i]);
2101 }
2102 }
2103 }
2104 printf("\n");
2105
2106 present_status_section("CONFIGURATION:");
2107
2108 static const char *category_names[] = {
2109 "AIRCRAFT TYPE",
2110 "AIR DATA TYPE",
2111 "POSITION INPUT TYPE",
2112 "CALLOUTS OPTION TYPE",
2113 "AUDIO MENU TYPE",
2114 "TERRAIN DISPLAY TYPE",
2115 "OPTIONS 1 TYPE",
2116 "RADIO ALTITUDE TYPE",
2117 "NAVIGATION INPUT TYPE",
2118 "ATTITUDE TYPE",
2119 "MAGNETIC HEADING TYPE",
2120 "WINDSHEAR INPUT TYPE",
2121 "IO DISCRETE TYPE",
2122 "VOLUME SELECT"
2123 };
2124
2125 for (size_t i = 0; i < n_elements(category_names); i++)
2126 {
2127 std::ostringstream value;
2128 value << "= " << mk->configuration_module.effective_categories[i];
2129 present_status_item(category_names[i], value.str().c_str());
2130 }
2131}
2132
2133bool
2135{
2136 return false;
2137}
2138
2139void
2141{
2142 if (value)
2144}
2145
2146
2148// MK_VIII::VoicePlayer ///////////////////////////////////////////////////////
2150
2151void
2153{
2155
2156#define STDPAUSE 0.75 // [SPEC] 6.4.4: "the standard 0.75 second delay"
2157 make_voice(&voices.application_data_base_failed, "application-data-base-failed");
2158 make_voice(&voices.bank_angle, "bank-angle");
2159 make_voice(&voices.bank_angle_bank_angle, "bank-angle", "bank-angle");
2160 make_voice(&voices.bank_angle_bank_angle_3, "bank-angle", "bank-angle", 3.0);
2161 make_voice(&voices.bank_angle_inop, "bank-angle-inop");
2162 make_voice(&voices.bank_angle_pause_bank_angle, "bank-angle", STDPAUSE, "bank-angle");
2163 make_voice(&voices.bank_angle_pause_bank_angle_3, "bank-angle", STDPAUSE, "bank-angle", 3.0);
2164 make_voice(&voices.callouts_inop, "callouts-inop");
2165 make_voice(&voices.configuration_type_invalid, "configuration-type-invalid");
2166 make_voice(&voices.dont_sink, "dont-sink");
2167 make_voice(&voices.dont_sink_pause_dont_sink, "dont-sink", STDPAUSE, "dont-sink");
2168 make_voice(&voices.five_hundred_above, "500-above");
2169 make_voice(&voices.glideslope, "glideslope");
2170 make_voice(&voices.glideslope_inop, "glideslope-inop");
2171 make_voice(&voices.gpws_inop, "gpws-inop");
2172 make_voice(&voices.hard_glideslope, "glideslope", "glideslope", 3.0);
2173 make_voice(&voices.retard, "retard");
2174 make_voice(&voices.minimums, "minimums");
2175 make_voice(&voices.minimums_100, "100-above");
2176 make_voice(&voices.minimums_minimums, "minimums", "minimums");
2177 make_voice(&voices.pull_up, "pull-up");
2178 make_voice(&voices.sink_rate, "sink-rate");
2179 make_voice(&voices.sink_rate_pause_sink_rate, "sink-rate", STDPAUSE, "sink-rate");
2180 make_voice(&voices.soft_glideslope, new Voice::SampleElement(get_sample("glideslope"), modify_amplitude(1.0, -6)));
2181 make_voice(&voices.terrain, "terrain");
2182 make_voice(&voices.terrain_pause_terrain, "terrain", STDPAUSE, "terrain");
2183 make_voice(&voices.too_low_flaps, "too-low-flaps");
2184 make_voice(&voices.too_low_gear, "too-low-gear");
2185 make_voice(&voices.too_low_terrain, "too-low-terrain");
2186
2187 for (unsigned i = 0; i < n_altitude_callouts; i++)
2188 {
2189 std::ostringstream name;
2190 name << "altitude-" << MK_VIII::Mode6Handler::altitude_callout_definitions[i];
2191 make_voice(&voices.altitude_callouts[i], name.str().c_str());
2192 }
2193 speaker.update_configuration();
2194}
2195
2197// SelfTestHandler ////////////////////////////////////////////////////////////
2199
2200bool
2201MK_VIII::SelfTestHandler::_was_here (int position)
2202{
2203 if (position > current)
2204 {
2205 current = position;
2206 return false;
2207 }
2208
2209 return true;
2210}
2211
2212MK_VIII::SelfTestHandler::Action
2213MK_VIII::SelfTestHandler::sleep (double duration)
2214{
2215 Action _action = { ACTION_SLEEP, duration, NULL };
2216 return _action;
2217}
2218
2219MK_VIII::SelfTestHandler::Action
2220MK_VIII::SelfTestHandler::play (VoicePlayer::Voice *voice)
2221{
2222 mk->voice_player.play(voice);
2223 Action _action = { ACTION_VOICE, 0, NULL };
2224 return _action;
2225}
2226
2227MK_VIII::SelfTestHandler::Action
2228MK_VIII::SelfTestHandler::discrete_on (bool *discrete, double duration)
2229{
2230 *discrete = true;
2231 return sleep(duration);
2232}
2233
2234MK_VIII::SelfTestHandler::Action
2235MK_VIII::SelfTestHandler::discrete_on_off (bool *discrete, double duration)
2236{
2237 *discrete = true;
2238 Action _action = { ACTION_SLEEP | ACTION_DISCRETE_ON_OFF, duration, discrete };
2239 return _action;
2240}
2241
2242MK_VIII::SelfTestHandler::Action
2243MK_VIII::SelfTestHandler::discrete_on_off (bool *discrete, VoicePlayer::Voice *voice)
2244{
2245 *discrete = true;
2246 mk->voice_player.play(voice);
2247 Action _action = { ACTION_VOICE | ACTION_DISCRETE_ON_OFF, 0, discrete };
2248 return _action;
2249}
2250
2251MK_VIII::SelfTestHandler::Action
2252MK_VIII::SelfTestHandler::done ()
2253{
2254 Action _action = { ACTION_DONE, 0, NULL };
2255 return _action;
2256}
2257
2258MK_VIII::SelfTestHandler::Action
2259MK_VIII::SelfTestHandler::run ()
2260{
2261 // Note that "Terrain INOP" and "Terrain NA" are or'ed to the same
2262 // output discrete (see [SPEC] 6.9.3.5).
2263
2264#define was_here() was_here_offset(0)
2265#define was_here_offset(offset) _was_here(__LINE__ * 20 + (offset))
2266
2267 if (! was_here())
2268 {
2269 if (mk->configuration_module.state == ConfigurationModule::STATE_INVALID_DATABASE)
2270 return play(mk_voice(application_data_base_failed));
2271 else if (mk->configuration_module.state == ConfigurationModule::STATE_INVALID_AIRCRAFT_TYPE)
2272 return play(mk_voice(configuration_type_invalid));
2273 }
2274 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
2275 return done();
2276
2277 if (! was_here())
2278 return discrete_on(&mk_doutput(gpws_inop), 0.7);
2279 if (! was_here())
2280 return sleep(0.7); // W/S INOP
2281 if (! was_here())
2282 return discrete_on(&mk_doutput(tad_inop), 0.7);
2283 if (! was_here())
2284 {
2285 mk_doutput(gpws_inop) = false;
2286 // do not disable tad_inop since we must enable Terrain NA
2287 // do not disable W/S INOP since we do not emulate it
2288 return sleep(0.7); // Terrain NA
2289 }
2290 if (! was_here())
2291 {
2292 mk_doutput(tad_inop) = false; // disable Terrain NA
2293 if (mk->io_handler.conf.momentary_flap_override_enabled)
2294 return discrete_on_off(&mk_doutput(flap_override), 1.0);
2295 }
2296 if (! was_here())
2297 return discrete_on_off(&mk_doutput(audio_on), 1.0);
2298 if (! was_here())
2299 {
2300 if (mk->io_handler.momentary_steep_approach_enabled())
2301 return discrete_on_off(&mk_doutput(steep_approach), 1.0);
2302 }
2303
2304 if (! was_here())
2305 {
2306 if (mk_dinput(glideslope_inhibit))
2307 goto glideslope_end;
2308 else
2309 {
2310 if (mk->fault_handler.faults & (1<<FaultHandler::FAULT_MODE5_INPUTS_INVALID))
2311 goto glideslope_inop;
2312 }
2313 }
2314 if (! was_here())
2315 return discrete_on_off(&mk_doutput(gpws_alert), mk_voice(glideslope));
2316 if (! was_here())
2317 return discrete_on_off(&mk_doutput(glideslope_cancel), 0.7);
2318 goto glideslope_end;
2319
2320glideslope_inop:
2321 if (! was_here())
2322 return play(mk_voice(glideslope_inop));
2323
2324glideslope_end:
2325 if (! was_here())
2326 {
2327 if (mk->fault_handler.faults & (1<<FaultHandler::FAULT_MODES14_INPUTS_INVALID))
2328 goto gpws_inop;
2329 }
2330 if (! was_here())
2331 return discrete_on_off(&mk_doutput(gpws_warning), mk_voice(pull_up));
2332 goto gpws_end;
2333
2334gpws_inop:
2335 if (! was_here())
2336 return play(mk_voice(gpws_inop));
2337
2338gpws_end:
2339 if (! was_here())
2340 {
2341 if (mk_dinput(self_test)) // proceed to long self test
2342 goto long_test;
2343 }
2344 if (! was_here())
2345 {
2346 if (mk->mode6_handler.conf.bank_angle_enabled
2347 && (mk->fault_handler.faults & (1<<FaultHandler::FAULT_BANK_ANGLE_INPUTS_INVALID)))
2348 return play(mk_voice(bank_angle_inop));
2349 }
2350 if (! was_here())
2351 {
2352 if (mk->mode6_handler.altitude_callouts_enabled()
2353 && (mk->fault_handler.faults & (1<<FaultHandler::FAULT_MODE6_INPUTS_INVALID)))
2354 return play(mk_voice(callouts_inop));
2355 }
2356 if (! was_here())
2357 return done();
2358
2359long_test:
2360 if (! was_here())
2361 {
2362 mk_doutput(gpws_inop) = true;
2363 // do not enable W/S INOP, since we do not emulate it
2364 mk_doutput(tad_inop) = true; // Terrain INOP, Terrain NA
2365
2366 return play(mk_voice(sink_rate));
2367 }
2368 if (! was_here())
2369 return play(mk_voice(pull_up));
2370 if (! was_here())
2371 return play(mk_voice(terrain));
2372 if (! was_here())
2373 return play(mk_voice(pull_up));
2374 if (! was_here())
2375 return play(mk_voice(dont_sink));
2376 if (! was_here())
2377 return play(mk_voice(too_low_terrain));
2378 if (! was_here())
2379 return play(mk->mode4_handler.conf.voice_too_low_gear);
2380 if (! was_here())
2381 return play(mk_voice(too_low_flaps));
2382 if (! was_here())
2383 return play(mk_voice(too_low_terrain));
2384 if (! was_here())
2385 return play(mk_voice(glideslope));
2386 if (! was_here())
2387 {
2388 if (mk->mode6_handler.conf.bank_angle_enabled)
2389 return play(mk_voice(bank_angle));
2390 }
2391
2392 if (! was_here())
2393 {
2394 if (! mk->mode6_handler.altitude_callouts_enabled())
2395 goto callouts_disabled;
2396 }
2397 if (! was_here())
2398 {
2399 if (mk->mode6_handler.conf.minimums_above_100_enabled)
2400 return play(mk_voice(minimums_100));
2401 }
2402 if (! was_here())
2403 {
2404 if (mk->mode6_handler.conf.minimums_enabled)
2405 return play(mk_voice(minimums));
2406 }
2407 if (! was_here())
2408 {
2409 if (mk->mode6_handler.conf.above_field_voice)
2410 return play(mk->mode6_handler.conf.above_field_voice);
2411 }
2412 for (unsigned i = 0; i < n_altitude_callouts; i++)
2413 {
2414 if (! was_here_offset(i))
2415 {
2416 if (mk->mode6_handler.conf.altitude_callouts_enabled & (1<<i))
2417 return play(mk_altitude_voice(i));
2418 }
2419 }
2420
2421 if (! was_here())
2422 {
2423 if (mk->mode6_handler.conf.smart_500_enabled)
2424 return play(mk_altitude_voice(Mode6Handler::ALTITUDE_CALLOUT_500));
2425 }
2426 goto callouts_end;
2427
2428callouts_disabled:
2429 if (! was_here())
2430 return play(mk_voice(minimums_minimums));
2431
2432callouts_end:
2433 if (! was_here())
2434 {
2435 if (mk->tcf_handler.conf.enabled)
2436 return play(mk_voice(too_low_terrain));
2437 }
2438
2439 return done();
2440}
2441
2442void
2443MK_VIII::SelfTestHandler::start ()
2444{
2445 assert(state == STATE_START);
2446
2447 memcpy(&saved_outputs, &mk->io_handler.outputs, sizeof(mk->io_handler.outputs));
2448 memset(&mk->io_handler.outputs, 0, sizeof(mk->io_handler.outputs));
2449
2450 // [SPEC] 6.10.6: "The self-test results are annunciated, at 6db
2451 // lower than the normal audio level selected for the aircraft"
2452 mk->voice_player.set_volume(modify_amplitude(mk->voice_player.conf.volume, -6));
2453
2454 if (mk_dinput(mode6_low_volume))
2455 mk->mode6_handler.set_volume(1.0);
2456
2457 state = STATE_RUNNING;
2458 cancel = CANCEL_NONE;
2459 memset(&action, 0, sizeof(action));
2460 current = 0;
2461}
2462
2463void
2464MK_VIII::SelfTestHandler::stop ()
2465{
2466 if (state != STATE_NONE)
2467 {
2468 if (state != STATE_START)
2469 {
2470 mk->voice_player.stop(VoicePlayer::STOP_NOW);
2471 mk->voice_player.set_volume(mk_dinput(audio_inhibit) ? 0.0 : mk->voice_player.conf.volume);
2472
2473 if (mk_dinput(mode6_low_volume))
2474 mk->mode6_handler.set_volume(modify_amplitude(1.0, -6));
2475
2476 memcpy(&mk->io_handler.outputs, &saved_outputs, sizeof(mk->io_handler.outputs));
2477 }
2478
2479 button_pressed = false;
2480 state = STATE_NONE;
2481 // reset self-test handler position
2482 current = 0;
2483 }
2484}
2485
2486void
2487MK_VIII::SelfTestHandler::handle_button_event (bool value)
2488{
2489 if (state == STATE_NONE)
2490 {
2491 if (value)
2492 state = STATE_START;
2493 }
2494 else if (state == STATE_START)
2495 {
2496 if (value)
2497 state = STATE_NONE; // cancel the not-yet-started test
2498 }
2499 else if (cancel == CANCEL_NONE)
2500 {
2501 if (value)
2502 {
2503 assert(! button_pressed);
2504 button_pressed = true;
2505 button_press_timestamp = globals->get_sim_time_sec();
2506 }
2507 else
2508 {
2509 if (button_pressed)
2510 {
2511 if (globals->get_sim_time_sec() - button_press_timestamp < 2)
2512 cancel = CANCEL_SHORT;
2513 else
2514 cancel = CANCEL_LONG;
2515 }
2516 }
2517 }
2518}
2519
2520bool
2521MK_VIII::SelfTestHandler::update ()
2522{
2523 if (state == STATE_NONE)
2524 return false;
2525 else if (state == STATE_START)
2526 {
2527 if (mk->state_handler.ground && ! mk->io_handler.steep_approach())
2528 start();
2529 else
2530 {
2531 state = STATE_NONE;
2532 return false;
2533 }
2534 }
2535 else
2536 {
2537 if (button_pressed && cancel == CANCEL_NONE)
2538 {
2539 if (globals->get_sim_time_sec() - button_press_timestamp >= 2)
2540 cancel = CANCEL_LONG;
2541 }
2542 }
2543
2544 if (! mk->state_handler.ground || cancel != CANCEL_NONE)
2545 {
2546 stop();
2547 return false;
2548 }
2549
2550 if (test_bits(action.flags, ACTION_SLEEP))
2551 {
2552 if (globals->get_sim_time_sec() - sleep_start < action.sleep_duration)
2553 return true;
2554 }
2555 if (test_bits(action.flags, ACTION_VOICE))
2556 {
2557 if (mk->voice_player.voice)
2558 return true;
2559 }
2560 if (test_bits(action.flags, ACTION_DISCRETE_ON_OFF))
2561 *action.discrete = false;
2562
2563 action = run();
2564
2565 if (test_bits(action.flags, ACTION_SLEEP))
2566 sleep_start = globals->get_sim_time_sec();
2567 if (test_bits(action.flags, ACTION_DONE))
2568 {
2569 stop();
2570 return false;
2571 }
2572
2573 return true;
2574}
2575
2577// AlertHandler ///////////////////////////////////////////////////////////////
2579
2580bool
2581MK_VIII::AlertHandler::select_voice_alerts (unsigned int test)
2582{
2583 if (has_alerts(test))
2584 {
2585 voice_alerts = alerts & test;
2586 return true;
2587 }
2588 else
2589 {
2590 voice_alerts &= ~test;
2591 if (voice_alerts == 0)
2592 mk->voice_player.stop();
2593
2594 return false;
2595 }
2596}
2597
2598void
2599MK_VIII::AlertHandler::boot ()
2600{
2601 reset();
2602}
2603
2604void
2605MK_VIII::AlertHandler::reposition ()
2606{
2607 reset();
2608
2609 mk->io_handler.set_lamp(IOHandler::LAMP_NONE);
2610 mk->voice_player.stop(VoicePlayer::STOP_NOW);
2611}
2612
2613void
2614MK_VIII::AlertHandler::reset ()
2615{
2616 alerts = 0;
2617 old_alerts = 0;
2618 voice_alerts = 0;
2619 repeated_alerts = 0;
2620 altitude_callout_voice = NULL;
2621}
2622
2623void
2624MK_VIII::AlertHandler::update ()
2625{
2626 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
2627 return;
2628
2629 // Lamps and voices are prioritized according to [SPEC] 6.9.2.
2630 //
2631 // Voices which can interrupt other voices (VoicePlayer::PLAY_NOW)
2632 // are specified by [INSTALL] appendix E 5.3.5.
2633 //
2634 // When a voice is overriden by a higher priority voice and the
2635 // overriding voice stops, we restore the previous voice if it was
2636 // looped (this is not specified by [SPEC] but seems to make sense).
2637
2638 // update lamp
2639
2640 if (has_alerts(ALERT_MODE1_PULL_UP | ALERT_MODE2A | ALERT_MODE2B))
2641 mk->io_handler.set_lamp(IOHandler::LAMP_WARNING);
2642 else if (has_alerts(ALERT_MODE1_SINK_RATE
2643 | ALERT_MODE2A_PREFACE
2644 | ALERT_MODE2B_PREFACE
2645 | ALERT_MODE2A_ALTITUDE_GAIN_TERRAIN_CLOSING
2646 | ALERT_MODE2A_ALTITUDE_GAIN
2647 | ALERT_MODE2B_LANDING_MODE
2648 | ALERT_MODE3
2649 | ALERT_MODE4_TOO_LOW_FLAPS
2650 | ALERT_MODE4_TOO_LOW_GEAR
2651 | ALERT_MODE4AB_TOO_LOW_TERRAIN
2652 | ALERT_MODE4C_TOO_LOW_TERRAIN
2653 | ALERT_TCF_TOO_LOW_TERRAIN))
2654 mk->io_handler.set_lamp(IOHandler::LAMP_CAUTION);
2655 else if (has_alerts(ALERT_MODE5_SOFT | ALERT_MODE5_HARD))
2656 mk->io_handler.set_lamp(IOHandler::LAMP_GLIDESLOPE);
2657 else
2658 mk->io_handler.set_lamp(IOHandler::LAMP_NONE);
2659
2660 // update voice
2661
2662 if (select_voice_alerts(ALERT_MODE1_PULL_UP))
2663 {
2664 if (! has_old_alerts(ALERT_MODE1_PULL_UP))
2665 {
2666 if (mk->voice_player.voice != mk_voice(sink_rate_pause_sink_rate))
2667 mk->voice_player.play(mk_voice(sink_rate), VoicePlayer::PLAY_NOW);
2668 mk->voice_player.play(mk_voice(pull_up), VoicePlayer::PLAY_LOOPED);
2669 }
2670 }
2671 else if (select_voice_alerts(ALERT_MODE2A_PREFACE | ALERT_MODE2B_PREFACE))
2672 {
2673 if (! has_old_alerts(ALERT_MODE2A_PREFACE | ALERT_MODE2B_PREFACE))
2674 mk->voice_player.play(mk_voice(terrain_pause_terrain), VoicePlayer::PLAY_NOW);
2675 }
2676 else if (select_voice_alerts(ALERT_MODE2A | ALERT_MODE2B))
2677 {
2678 if (mk->voice_player.voice != mk_voice(pull_up))
2679 mk->voice_player.play(mk_voice(pull_up), VoicePlayer::PLAY_NOW | VoicePlayer::PLAY_LOOPED);
2680 }
2681 else if (select_voice_alerts(ALERT_MODE2A_ALTITUDE_GAIN_TERRAIN_CLOSING | ALERT_MODE2B_LANDING_MODE))
2682 {
2683 if (mk->voice_player.voice != mk_voice(terrain))
2684 mk->voice_player.play(mk_voice(terrain), VoicePlayer::PLAY_LOOPED);
2685 }
2686 else if (select_voice_alerts(ALERT_MODE6_MINIMUMS))
2687 {
2688 if (! has_old_alerts(ALERT_MODE6_MINIMUMS))
2689 mk->voice_player.play(mk_voice(minimums_minimums));
2690 }
2691 else if (select_voice_alerts(ALERT_MODE6_MINIMUMS_100))
2692 {
2693 if (! has_old_alerts(ALERT_MODE6_MINIMUMS_100))
2694 mk->voice_player.play(mk_voice(minimums_100));
2695 }
2696 else if (select_voice_alerts(ALERT_MODE6_RETARD))
2697 {
2698 if (must_play_voice(ALERT_MODE6_RETARD))
2699 mk->voice_player.play(mk_voice(retard), VoicePlayer::PLAY_LOOPED);
2700 }
2701 else if (select_voice_alerts(ALERT_MODE4AB_TOO_LOW_TERRAIN | ALERT_MODE4C_TOO_LOW_TERRAIN))
2702 {
2703 if (must_play_voice(ALERT_MODE4AB_TOO_LOW_TERRAIN | ALERT_MODE4C_TOO_LOW_TERRAIN))
2704 mk->voice_player.play(mk_voice(too_low_terrain));
2705 }
2706 else if (select_voice_alerts(ALERT_TCF_TOO_LOW_TERRAIN))
2707 {
2708 if (must_play_voice(ALERT_TCF_TOO_LOW_TERRAIN))
2709 mk->voice_player.play(mk_voice(too_low_terrain));
2710 }
2711 else if (select_voice_alerts(ALERT_MODE6_ALTITUDE_CALLOUT))
2712 {
2713 if (! has_old_alerts(ALERT_MODE6_ALTITUDE_CALLOUT))
2714 {
2715 assert(altitude_callout_voice != NULL);
2716 mk->voice_player.play(altitude_callout_voice);
2717 altitude_callout_voice = NULL;
2718 }
2719 }
2720 else if (select_voice_alerts(ALERT_MODE4_TOO_LOW_GEAR))
2721 {
2722 if (must_play_voice(ALERT_MODE4_TOO_LOW_GEAR))
2723 mk->voice_player.play(mk->mode4_handler.conf.voice_too_low_gear);
2724 }
2725 else if (select_voice_alerts(ALERT_MODE4_TOO_LOW_FLAPS))
2726 {
2727 if (must_play_voice(ALERT_MODE4_TOO_LOW_FLAPS))
2728 mk->voice_player.play(mk_voice(too_low_flaps));
2729 }
2730 else if (select_voice_alerts(ALERT_MODE1_SINK_RATE))
2731 {
2732 if (must_play_voice(ALERT_MODE1_SINK_RATE))
2733 mk->voice_player.play(mk_voice(sink_rate_pause_sink_rate));
2734 // [SPEC] 6.2.1: "During the time that the voice message for the
2735 // outer envelope is inhibited and the caution/warning lamp is
2736 // on, the Mode 5 alert message is allowed to annunciate for
2737 // excessive glideslope deviation conditions."
2738 else if (mk->voice_player.voice != mk_voice(sink_rate_pause_sink_rate)
2739 && mk->voice_player.next_voice != mk_voice(sink_rate_pause_sink_rate))
2740 {
2741 if (has_alerts(ALERT_MODE5_HARD))
2742 {
2743 voice_alerts |= ALERT_MODE5_HARD;
2744 if (mk->voice_player.voice != mk_voice(hard_glideslope))
2745 mk->voice_player.play(mk_voice(hard_glideslope), VoicePlayer::PLAY_LOOPED);
2746 }
2747 else if (has_alerts(ALERT_MODE5_SOFT))
2748 {
2749 voice_alerts |= ALERT_MODE5_SOFT;
2750 if (must_play_voice(ALERT_MODE5_SOFT))
2751 mk->voice_player.play(mk_voice(soft_glideslope));
2752 }
2753 }
2754 }
2755 else if (select_voice_alerts(ALERT_MODE3))
2756 {
2757 if (must_play_voice(ALERT_MODE3))
2758 mk->voice_player.play(mk_voice(dont_sink_pause_dont_sink));
2759 }
2760 else if (select_voice_alerts(ALERT_MODE5_HARD))
2761 {
2762 if (mk->voice_player.voice != mk_voice(hard_glideslope))
2763 mk->voice_player.play(mk_voice(hard_glideslope), VoicePlayer::PLAY_LOOPED);
2764 }
2765 else if (select_voice_alerts(ALERT_MODE5_SOFT))
2766 {
2767 if (must_play_voice(ALERT_MODE5_SOFT))
2768 mk->voice_player.play(mk_voice(soft_glideslope));
2769 }
2770 else if (select_voice_alerts(ALERT_MODE6_LOW_BANK_ANGLE_3))
2771 {
2772 if (mk->voice_player.voice != mk_voice(bank_angle_bank_angle_3))
2773 mk->voice_player.play(mk_voice(bank_angle_bank_angle_3), VoicePlayer::PLAY_LOOPED);
2774 }
2775 else if (select_voice_alerts(ALERT_MODE6_HIGH_BANK_ANGLE_3))
2776 {
2777 if (mk->voice_player.voice != mk_voice(bank_angle_pause_bank_angle_3))
2778 mk->voice_player.play(mk_voice(bank_angle_pause_bank_angle_3), VoicePlayer::PLAY_LOOPED);
2779 }
2780 else if (select_voice_alerts(ALERT_MODE6_LOW_BANK_ANGLE_2))
2781 {
2782 if (! has_old_alerts(ALERT_MODE6_LOW_BANK_ANGLE_2 | ALERT_MODE6_HIGH_BANK_ANGLE_2))
2783 mk->voice_player.play(mk_voice(bank_angle_bank_angle));
2784 }
2785 else if (select_voice_alerts(ALERT_MODE6_HIGH_BANK_ANGLE_2))
2786 {
2787 if (! has_old_alerts(ALERT_MODE6_LOW_BANK_ANGLE_2 | ALERT_MODE6_HIGH_BANK_ANGLE_2))
2788 mk->voice_player.play(mk_voice(bank_angle_pause_bank_angle));
2789 }
2790 else if (select_voice_alerts(ALERT_MODE6_LOW_BANK_ANGLE_1))
2791 {
2792 if (! has_old_alerts(ALERT_MODE6_LOW_BANK_ANGLE_1 | ALERT_MODE6_HIGH_BANK_ANGLE_1))
2793 mk->voice_player.play(mk_voice(bank_angle_bank_angle));
2794 }
2795 else if (select_voice_alerts(ALERT_MODE6_HIGH_BANK_ANGLE_1))
2796 {
2797 if (! has_old_alerts(ALERT_MODE6_LOW_BANK_ANGLE_1 | ALERT_MODE6_HIGH_BANK_ANGLE_1))
2798 mk->voice_player.play(mk_voice(bank_angle_pause_bank_angle));
2799 }
2800
2801 // remember all alerts voiced so far...
2802 old_alerts |= voice_alerts;
2803 // ... forget those no longer active
2804 old_alerts &= alerts;
2805 repeated_alerts = 0;
2806}
2807
2808void
2809MK_VIII::AlertHandler::set_alerts (unsigned int _alerts,
2810 unsigned int flags,
2811 VoicePlayer::Voice *_altitude_callout_voice)
2812{
2813 alerts |= _alerts;
2814 if (test_bits(flags, ALERT_FLAG_REPEAT))
2815 repeated_alerts |= _alerts;
2816 if (_altitude_callout_voice)
2817 altitude_callout_voice = _altitude_callout_voice;
2818}
2819
2820void
2821MK_VIII::AlertHandler::unset_alerts (unsigned int _alerts)
2822{
2823 alerts &= ~_alerts;
2824 repeated_alerts &= ~_alerts;
2825 if (_alerts & ALERT_MODE6_ALTITUDE_CALLOUT)
2826 altitude_callout_voice = NULL;
2827}
2828
2830// StateHandler ///////////////////////////////////////////////////////////////
2832
2833void
2834MK_VIII::StateHandler::update_ground ()
2835{
2836 if (ground)
2837 {
2838 if (! mk_ainput(computed_airspeed).ncd && mk_ainput(computed_airspeed).get() > 60
2839 && ! mk_data(radio_altitude).ncd && mk_data(radio_altitude).get() > 30)
2840 {
2841 if (potentially_airborne_timer.start_or_elapsed() > 1)
2842 leave_ground();
2843 }
2844 else
2845 potentially_airborne_timer.stop();
2846 }
2847 else
2848 {
2849 if (! mk_ainput(computed_airspeed).ncd && mk_ainput(computed_airspeed).get() < 40
2850 && ! mk_data(radio_altitude).ncd && mk_data(radio_altitude).get() < 30)
2851 enter_ground();
2852 }
2853}
2854
2855void
2856MK_VIII::StateHandler::enter_ground ()
2857{
2858 ground = true;
2859 mk->io_handler.enter_ground();
2860
2861 // [SPEC] 6.0.1 does not specify this, but it seems to be an
2862 // omission; at this point, we must make sure that we are in takeoff
2863 // mode (otherwise the next takeoff might happen in approach mode).
2864 if (! takeoff)
2865 enter_takeoff();
2866}
2867
2868void
2869MK_VIII::StateHandler::leave_ground ()
2870{
2871 ground = false;
2872 mk->mode2_handler.leave_ground();
2873}
2874
2875void
2876MK_VIII::StateHandler::update_takeoff ()
2877{
2878 if (takeoff)
2879 {
2880 // [SPEC] 6.0.2 is somewhat confusing: it mentions hardcoded
2881 // terrain clearance (500, 750) and airspeed (178, 200) values,
2882 // but it also mentions the mode 4A boundary, which varies as a
2883 // function of aircraft type. We consider that the mentioned
2884 // values are examples, and that we should use the mode 4A upper
2885 // boundary.
2886
2887 if (! mk_data(terrain_clearance).ncd
2888 && ! mk_ainput(computed_airspeed).ncd
2889 && mk_data(terrain_clearance).get() > mk->mode4_handler.get_upper_agl(mk->mode4_handler.conf.modes->ac))
2890 leave_takeoff();
2891 }
2892 else
2893 {
2894 if (! mk_data(radio_altitude).ncd
2895 && mk_data(radio_altitude).get() < mk->mode4_handler.conf.modes->b->min_agl1
2896 && mk->io_handler.flaps_down()
2897 && mk_dinput(landing_gear))
2898 enter_takeoff();
2899 }
2900}
2901
2902void
2903MK_VIII::StateHandler::enter_takeoff ()
2904{
2905 takeoff = true;
2906 mk->io_handler.enter_takeoff();
2907 mk->mode2_handler.enter_takeoff();
2908 mk->mode3_handler.enter_takeoff();
2909 mk->mode6_handler.enter_takeoff();
2910}
2911
2912void
2913MK_VIII::StateHandler::leave_takeoff ()
2914{
2915 takeoff = false;
2916 mk->mode6_handler.leave_takeoff();
2917}
2918
2919void
2920MK_VIII::StateHandler::post_reposition ()
2921{
2922 // Synchronize the state with the current situation.
2923
2924 bool _ground = !
2925 (! mk_ainput(computed_airspeed).ncd && mk_ainput(computed_airspeed).get() > 60
2926 && ! mk_data(radio_altitude).ncd && mk_data(radio_altitude).get() > 30);
2927
2928 bool _takeoff = _ground;
2929
2930 if (ground && ! _ground)
2931 leave_ground();
2932 else if ((!ground) && _ground)
2933 enter_ground();
2934
2935 if (takeoff && (!_takeoff))
2936 leave_takeoff();
2937 else if ((!takeoff) && _takeoff)
2938 enter_takeoff();
2939}
2940
2941void
2942MK_VIII::StateHandler::update ()
2943{
2944 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
2945 return;
2946
2947 update_ground();
2948 update_takeoff();
2949}
2950
2952// Mode1Handler ///////////////////////////////////////////////////////////////
2954
2955double
2956MK_VIII::Mode1Handler::get_pull_up_bias ()
2957{
2958 // [PILOT] page 54: "Steep Approach has priority over Flap Override
2959 // if selected simultaneously."
2960
2961 if (mk->io_handler.steep_approach())
2962 return 200;
2963 else if (conf.envelopes->flap_override_bias && mk->io_handler.flap_override())
2964 return 300;
2965 else
2966 return 0;
2967}
2968
2969bool
2970MK_VIII::Mode1Handler::is_pull_up ()
2971{
2972 if (! mk->io_handler.gpws_inhibit()
2973 && ! mk->state_handler.ground // [1]
2974 && ! mk_data(radio_altitude).ncd
2975 && ! mk_data(barometric_altitude_rate).ncd
2976 && mk_data(radio_altitude).get() > conf.envelopes->min_agl)
2977 {
2978 if (mk_data(radio_altitude).get() < conf.envelopes->pull_up_max_agl1)
2979 {
2980 if (mk_data(radio_altitude).get() < conf.envelopes->pull_up_min_agl1(mk_data(barometric_altitude_rate).get() + get_pull_up_bias()))
2981 return true;
2982 }
2983 else if (mk_data(radio_altitude).get() < conf.envelopes->pull_up_max_agl2)
2984 {
2985 if (mk_data(radio_altitude).get() < conf.envelopes->pull_up_min_agl2(mk_data(barometric_altitude_rate).get() + get_pull_up_bias()))
2986 return true;
2987 }
2988 }
2989
2990 return false;
2991}
2992
2993void
2994MK_VIII::Mode1Handler::update_pull_up ()
2995{
2996 if (is_pull_up())
2997 {
2998 if (! mk_test_alert(MODE1_PULL_UP))
2999 {
3000 // [SPEC] 6.2.1: at least one sink rate must be issued
3001 // before switching to pull up; if no sink rate has
3002 // occurred, a 0.2 second delay is used.
3003 if (mk->voice_player.voice == mk_voice(sink_rate_pause_sink_rate)
3004 || pull_up_timer.start_or_elapsed() >= 0.2)
3005 mk_set_alerts(mk_alert(MODE1_PULL_UP));
3006 }
3007 }
3008 else
3009 {
3010 pull_up_timer.stop();
3011 mk_unset_alerts(mk_alert(MODE1_PULL_UP));
3012 }
3013}
3014
3015double
3016MK_VIII::Mode1Handler::get_sink_rate_bias ()
3017{
3018 // [PILOT] page 54: "Steep Approach has priority over Flap Override
3019 // if selected simultaneously."
3020
3021 if (mk->io_handler.steep_approach())
3022 return 500;
3023 else if (conf.envelopes->flap_override_bias && mk->io_handler.flap_override())
3024 return 300;
3025 else if (! mk_data(glideslope_deviation_dots).ncd)
3026 {
3027 double bias = 0;
3028
3029 if (mk_data(glideslope_deviation_dots).get() <= -2)
3030 bias = 300;
3031 else if (mk_data(glideslope_deviation_dots).get() < 0)
3032 bias = -150 * mk_data(glideslope_deviation_dots).get();
3033
3034 if (mk_data(radio_altitude).get() < 100)
3035 bias *= 0.01 * mk_data(radio_altitude).get();
3036
3037 return bias;
3038 }
3039 else
3040 return 0;
3041}
3042
3043bool
3044MK_VIII::Mode1Handler::is_sink_rate ()
3045{
3046 return ! mk->io_handler.gpws_inhibit()
3047 && ! mk->state_handler.ground // [1]
3048 && ! mk_data(radio_altitude).ncd
3049 && ! mk_data(barometric_altitude_rate).ncd
3050 && mk_data(radio_altitude).get() > conf.envelopes->min_agl
3051 && mk_data(radio_altitude).get() < 2450
3052 && mk_data(radio_altitude).get() < -572 - 0.6035 * (mk_data(barometric_altitude_rate).get() + get_sink_rate_bias());
3053}
3054
3055double
3056MK_VIII::Mode1Handler::get_sink_rate_tti ()
3057{
3058 return mk_data(radio_altitude).get() / fabs(mk_data(barometric_altitude_rate).get());
3059}
3060
3061void
3062MK_VIII::Mode1Handler::update_sink_rate ()
3063{
3064 if (is_sink_rate())
3065 {
3066 if (mk_test_alert(MODE1_SINK_RATE))
3067 {
3068 double tti = get_sink_rate_tti();
3069 if (tti <= sink_rate_tti * 0.8)
3070 {
3071 // ~20% degradation, warn again and store the new reference tti
3072 sink_rate_tti = tti;
3073 mk_repeat_alert(mk_alert(MODE1_SINK_RATE));
3074 }
3075 }
3076 else
3077 {
3078 if (sink_rate_timer.start_or_elapsed() >= 0.8)
3079 {
3080 mk_set_alerts(mk_alert(MODE1_SINK_RATE));
3081 sink_rate_tti = get_sink_rate_tti();
3082 }
3083 }
3084 }
3085 else
3086 {
3087 sink_rate_timer.stop();
3088 mk_unset_alerts(mk_alert(MODE1_SINK_RATE));
3089 }
3090}
3091
3092void
3093MK_VIII::Mode1Handler::update ()
3094{
3095 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
3096 return;
3097
3098 update_pull_up();
3099 update_sink_rate();
3100}
3101
3103// Mode2Handler ///////////////////////////////////////////////////////////////
3105
3106double
3107MK_VIII::Mode2Handler::ClosureRateFilter::limit_radio_altitude_rate (double r)
3108{
3109 // Limit radio altitude rate according to aircraft configuration,
3110 // allowing maximum sensitivity during cruise while providing
3111 // progressively less sensitivity during the landing phases of
3112 // flight.
3113
3114 if (! mk_data(glideslope_deviation_dots).ncd && fabs(mk_data(glideslope_deviation_dots).get()) <= 2)
3115 { // gs deviation <= +- 2 dots
3116 if (mk_dinput(landing_gear) && mk->io_handler.flaps_down())
3117 SG_CLAMP_RANGE(r, -1000.0, 3000.0);
3118 else if (mk_dinput(landing_gear) || mk->io_handler.flaps_down())
3119 SG_CLAMP_RANGE(r, 0.0, 4000.0);
3120 else
3121 SG_CLAMP_RANGE(r, 1000.0, 5000.0);
3122 }
3123 else
3124 { // no ILS, or gs deviation > +- 2 dots
3125 if (mk_dinput(landing_gear) && mk->io_handler.flaps_down())
3126 SG_CLAMP_RANGE(r, 0.0, 4000.0);
3127 else if (mk_dinput(landing_gear) || mk->io_handler.flaps_down())
3128 SG_CLAMP_RANGE(r, 1000.0, 5000.0);
3129 // else no clamp
3130 }
3131
3132 return r;
3133}
3134
3135void
3136MK_VIII::Mode2Handler::ClosureRateFilter::init ()
3137{
3138 timer.stop();
3139 last_ra.set(&mk_data(radio_altitude));
3140 last_ba.set(&mk_ainput(uncorrected_barometric_altitude));
3141 ra_filter.reset();
3142 ba_filter.reset();
3143 output.unset();
3144}
3145
3146void
3147MK_VIII::Mode2Handler::ClosureRateFilter::update ()
3148{
3149 double elapsed = timer.start_or_elapsed();
3150 if (elapsed < 1)
3151 return;
3152
3153 if (! mk_data(radio_altitude).ncd && ! mk_ainput(uncorrected_barometric_altitude).ncd)
3154 {
3155 if (! last_ra.ncd && ! last_ba.ncd)
3156 {
3157 // compute radio and barometric altitude rates (positive value = descent)
3158 double ra_rate = -(mk_data(radio_altitude).get() - last_ra.get()) / elapsed * 60;
3159 double ba_rate = -(mk_ainput(uncorrected_barometric_altitude).get() - last_ba.get()) / elapsed * 60;
3160
3161 // limit radio altitude rate according to aircraft configuration
3162 ra_rate = limit_radio_altitude_rate(ra_rate);
3163
3164 // apply a low-pass filter to the radio altitude rate
3165 ra_rate = ra_filter.filter(ra_rate);
3166 // apply a high-pass filter to the barometric altitude rate
3167 ba_rate = ba_filter.filter(ba_rate);
3168
3169 // combine both rates to obtain a closure rate
3170 output.set(ra_rate + ba_rate);
3171 }
3172 else
3173 output.unset();
3174 }
3175 else
3176 {
3177 ra_filter.reset();
3178 ba_filter.reset();
3179 output.unset();
3180 }
3181
3182 timer.start();
3183 last_ra.set(&mk_data(radio_altitude));
3184 last_ba.set(&mk_ainput(uncorrected_barometric_altitude));
3185}
3186
3187bool
3188MK_VIII::Mode2Handler::b_conditions ()
3189{
3190 return mk->io_handler.flaps_down()
3191 || (! mk_data(glideslope_deviation_dots).ncd && fabs(mk_data(glideslope_deviation_dots).get()) < 2)
3192 || takeoff_timer.running;
3193}
3194
3195bool
3196MK_VIII::Mode2Handler::is_a ()
3197{
3198 if (! mk->io_handler.gpws_inhibit()
3199 && ! mk->state_handler.ground // [1]
3200 && ! mk_data(radio_altitude).ncd
3201 && ! mk_ainput(computed_airspeed).ncd
3202 && ! closure_rate_filter.output.ncd
3203 && ! b_conditions())
3204 {
3205 if (mk_data(radio_altitude).get() < 1220)
3206 {
3207 if (mk_data(radio_altitude).get() < -1579 + 0.7895 * closure_rate_filter.output.get())
3208 return true;
3209 }
3210 else
3211 {
3212 double upper_limit;
3213
3214 if (mk_ainput(computed_airspeed).get() <= conf->airspeed1)
3215 upper_limit = 1650;
3216 else if (mk_ainput(computed_airspeed).get() >= conf->airspeed2)
3217 upper_limit = 2450;
3218 else
3219 upper_limit = 1650 + 8.9 * (mk_ainput(computed_airspeed).get() - conf->airspeed1);
3220
3221 if (mk_data(radio_altitude).get() < upper_limit)
3222 {
3223 if (mk_data(radio_altitude).get() < 522 + 0.1968 * closure_rate_filter.output.get())
3224 return true;
3225 }
3226 }
3227 }
3228
3229 return false;
3230}
3231
3232bool
3233MK_VIII::Mode2Handler::is_b ()
3234{
3235 if (! mk->io_handler.gpws_inhibit()
3236 && ! mk->state_handler.ground // [1]
3237 && ! mk_data(radio_altitude).ncd
3238 && ! mk_data(barometric_altitude_rate).ncd
3239 && ! closure_rate_filter.output.ncd
3240 && b_conditions()
3241 && mk_data(radio_altitude).get() < 789)
3242 {
3243 double lower_limit;
3244
3245 if (mk->io_handler.flaps_down())
3246 {
3247 if (mk_data(barometric_altitude_rate).get() > -400)
3248 lower_limit = 200;
3249 else if (mk_data(barometric_altitude_rate).get() < -1000)
3250 lower_limit = 600;
3251 else
3252 lower_limit = -66.777 - 0.667 * mk_data(barometric_altitude_rate).get();
3253 }
3254 else
3255 lower_limit = 30;
3256
3257 if (mk_data(radio_altitude).get() > lower_limit)
3258 {
3259 if (mk_data(radio_altitude).get() < -1579 + 0.7895 * closure_rate_filter.output.get())
3260 return true;
3261 }
3262 }
3263
3264 return false;
3265}
3266
3267void
3268MK_VIII::Mode2Handler::check_pull_up (unsigned int preface_alert,
3269 unsigned int alert)
3270{
3271 if (pull_up_timer.running)
3272 {
3273 if (pull_up_timer.elapsed() >= 1)
3274 {
3275 mk_unset_alerts(preface_alert);
3276 mk_set_alerts(alert);
3277 }
3278 }
3279 else
3280 {
3281 if (! mk->voice_player.voice)
3282 pull_up_timer.start();
3283 }
3284}
3285
3286void
3287MK_VIII::Mode2Handler::update_a ()
3288{
3289 if (is_a())
3290 {
3291 if (mk_test_alert(MODE2A_PREFACE))
3292 check_pull_up(mk_alert(MODE2A_PREFACE), mk_alert(MODE2A));
3293 else if (! mk_test_alert(MODE2A))
3294 {
3295 mk_unset_alerts(mk_alert(MODE2A_ALTITUDE_GAIN) | mk_alert(MODE2A_ALTITUDE_GAIN_TERRAIN_CLOSING));
3296 mk_set_alerts(mk_alert(MODE2A_PREFACE));
3297 a_start_time = globals->get_sim_time_sec();
3298 pull_up_timer.stop();
3299 }
3300 }
3301 else
3302 {
3303 if (mk_test_alert(MODE2A_ALTITUDE_GAIN))
3304 {
3305 if (mk->io_handler.gpws_inhibit()
3306 || mk->state_handler.ground // [1]
3307 || a_altitude_gain_timer.elapsed() >= 45
3308 || mk_data(radio_altitude).ncd
3309 || mk_ainput(uncorrected_barometric_altitude).ncd
3310 || mk_ainput(uncorrected_barometric_altitude).get() - a_altitude_gain_alt >= 300
3311 // [PILOT] page 12: "the visual alert will remain on
3312 // until [...] landing flaps or the flap override switch
3313 // is activated"
3314 || mk->io_handler.flaps_down())
3315 {
3316 // exit altitude gain mode
3317 a_altitude_gain_timer.stop();
3318 mk_unset_alerts(mk_alert(MODE2A_ALTITUDE_GAIN) | mk_alert(MODE2A_ALTITUDE_GAIN_TERRAIN_CLOSING));
3319 }
3320 else
3321 {
3322 // [SPEC] 6.2.2.2: "If the terrain starts to fall away
3323 // during this altitude gain time, the voice will shut
3324 // off"
3325 if (closure_rate_filter.output.get() < 0)
3326 mk_unset_alerts(mk_alert(MODE2A_ALTITUDE_GAIN_TERRAIN_CLOSING));
3327 }
3328 }
3329 else if (mk_test_alerts(mk_alert(MODE2A_PREFACE) | mk_alert(MODE2A)))
3330 {
3331 mk_unset_alerts(mk_alert(MODE2A_PREFACE) | mk_alert(MODE2A));
3332
3333 if (! mk->io_handler.gpws_inhibit()
3334 && ! mk->state_handler.ground // [1]
3335 && globals->get_sim_time_sec() - a_start_time > 3
3336 && ! mk->io_handler.flaps_down()
3337 && ! mk_data(radio_altitude).ncd
3338 && ! mk_ainput(uncorrected_barometric_altitude).ncd)
3339 {
3340 // [SPEC] 6.2.2.2: mode 2A envelope violated for more
3341 // than 3 seconds, enable altitude gain feature
3342
3343 a_altitude_gain_timer.start();
3344 a_altitude_gain_alt = mk_ainput(uncorrected_barometric_altitude).get();
3345
3346 unsigned int alerts = mk_alert(MODE2A_ALTITUDE_GAIN);
3347 if (closure_rate_filter.output.get() > 0)
3348 alerts |= mk_alert(MODE2A_ALTITUDE_GAIN_TERRAIN_CLOSING);
3349
3350 mk_set_alerts(alerts);
3351 }
3352 }
3353 }
3354}
3355
3356void
3357MK_VIII::Mode2Handler::update_b ()
3358{
3359 bool b = is_b();
3360
3361 // handle normal mode
3362
3363 if (b && (! mk_dinput(landing_gear) || ! mk->io_handler.flaps_down()))
3364 {
3365 if (mk_test_alert(MODE2B_PREFACE))
3366 check_pull_up(mk_alert(MODE2B_PREFACE), mk_alert(MODE2B));
3367 else if (! mk_test_alert(MODE2B))
3368 {
3369 mk_set_alerts(mk_alert(MODE2B_PREFACE));
3370 pull_up_timer.stop();
3371 }
3372 }
3373 else
3374 mk_unset_alerts(mk_alert(MODE2B_PREFACE) | mk_alert(MODE2B));
3375
3376 // handle landing mode ([SPEC] 6.2.2.3: "If both landing gear and
3377 // flaps are in the landing configuration, then the message will be
3378 // Terrain")
3379
3380 if (b && mk_dinput(landing_gear) && mk->io_handler.flaps_down())
3381 mk_set_alerts(mk_alert(MODE2B_LANDING_MODE));
3382 else
3383 mk_unset_alerts(mk_alert(MODE2B_LANDING_MODE));
3384}
3385
3386void
3387MK_VIII::Mode2Handler::boot ()
3388{
3389 closure_rate_filter.init();
3390}
3391
3392void
3393MK_VIII::Mode2Handler::power_off ()
3394{
3395 // [SPEC] 6.0.4: "This latching function is not power saved and a
3396 // system reset will force it false."
3397 takeoff_timer.stop();
3398}
3399
3400void
3401MK_VIII::Mode2Handler::leave_ground ()
3402{
3403 // takeoff, reset timer
3404 takeoff_timer.start();
3405}
3406
3407void
3408MK_VIII::Mode2Handler::enter_takeoff ()
3409{
3410 // reset timer, in case it's a go-around
3411 takeoff_timer.start();
3412}
3413
3414void
3415MK_VIII::Mode2Handler::update ()
3416{
3417 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
3418 return;
3419
3420 closure_rate_filter.update();
3421
3422 if (takeoff_timer.running && takeoff_timer.elapsed() >= 60)
3423 takeoff_timer.stop();
3424
3425 update_a();
3426 update_b();
3427}
3428
3430// Mode3Handler ///////////////////////////////////////////////////////////////
3432
3433double
3434MK_VIII::Mode3Handler::max_alt_loss (double _bias)
3435{
3436 return conf->max_alt_loss(mk->io_handler.flap_override(), mk_data(radio_altitude).get()) + mk_data(radio_altitude).get() * _bias;
3437}
3438
3439double
3440MK_VIII::Mode3Handler::get_bias (double initial_bias, double alt_loss)
3441{
3442 // do not repeat altitude-loss alerts below 30ft agl
3443 if (mk_data(radio_altitude).get() > 30)
3444 {
3445 if (initial_bias < 0.0) // sanity check
3446 initial_bias = 0.0;
3447 // mk-viii spec: repeat alerts whenever losing 20% of initial altitude
3448 while ((alt_loss > max_alt_loss(initial_bias))&&
3449 (initial_bias < 1.0))
3450 {
3451 initial_bias += 0.2;
3452 }
3453 }
3454
3455 return initial_bias;
3456}
3457
3458bool
3459MK_VIII::Mode3Handler::is (double *alt_loss)
3460{
3461 if (has_descent_alt)
3462 {
3463 int max_agl = conf->max_agl(mk->io_handler.flap_override());
3464
3465 if (mk_ainput(uncorrected_barometric_altitude).ncd
3466 || mk_ainput(uncorrected_barometric_altitude).get() > descent_alt
3467 || mk_data(radio_altitude).ncd
3468 || mk_data(radio_altitude).get() > max_agl)
3469 {
3470 armed = false;
3471 has_descent_alt = false;
3472 }
3473 else
3474 {
3475 // gear/flaps: [SPEC] 1.3.1.3
3476 if (! mk->io_handler.gpws_inhibit()
3477 && ! mk->state_handler.ground // [1]
3478 && (! mk_dinput(landing_gear) || ! mk->io_handler.flaps_down())
3479 && ! mk_data(barometric_altitude_rate).ncd
3480 && ! mk_ainput(uncorrected_barometric_altitude).ncd
3481 && ! mk_data(radio_altitude).ncd
3482 && mk_data(barometric_altitude_rate).get() < 0)
3483 {
3484 double _alt_loss = descent_alt - mk_ainput(uncorrected_barometric_altitude).get();
3485 if (armed || (mk_data(radio_altitude).get() > conf->min_agl
3486 && mk_data(radio_altitude).get() < max_agl
3487 && _alt_loss > max_alt_loss(0)))
3488 {
3489 *alt_loss = _alt_loss;
3490 return true;
3491 }
3492 }
3493 }
3494 }
3495 else
3496 {
3497 if (! mk_data(barometric_altitude_rate).ncd
3498 && ! mk_ainput(uncorrected_barometric_altitude).ncd
3499 && mk_data(barometric_altitude_rate).get() < 0)
3500 {
3501 has_descent_alt = true;
3502 descent_alt = mk_ainput(uncorrected_barometric_altitude).get();
3503 }
3504 }
3505
3506 return false;
3507}
3508
3509void
3510MK_VIII::Mode3Handler::enter_takeoff ()
3511{
3512 armed = false;
3513 has_descent_alt = false;
3514}
3515
3516void
3517MK_VIII::Mode3Handler::update ()
3518{
3519 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
3520 return;
3521
3522 if (mk->state_handler.takeoff)
3523 {
3524 double alt_loss;
3525
3526 if (! mk->state_handler.ground /* [1] */ && is(&alt_loss))
3527 {
3528 if (mk_test_alert(MODE3))
3529 {
3530 double new_bias = get_bias(bias, alt_loss);
3531 if (new_bias > bias)
3532 {
3533 bias = new_bias;
3534 mk_repeat_alert(mk_alert(MODE3));
3535 }
3536 }
3537 else
3538 {
3539 armed = true;
3540 bias = get_bias(0.2, alt_loss);
3541 mk_set_alerts(mk_alert(MODE3));
3542 }
3543
3544 return;
3545 }
3546 }
3547
3548 mk_unset_alerts(mk_alert(MODE3));
3549}
3550
3552// Mode4Handler ///////////////////////////////////////////////////////////////
3554
3555// FIXME: for turbofans, the upper limit should be reduced from 1000
3556// to 800 ft if a sudden change in radio altitude is detected, in
3557// order to reduce nuisance alerts caused by overflying another
3558// aircraft (see [PILOT] page 16).
3559
3560double
3561MK_VIII::Mode4Handler::get_upper_agl (const EnvelopesConfiguration *c)
3562{
3563 if (mk_ainput(computed_airspeed).get() >= c->airspeed2)
3564 return c->min_agl3;
3565 else if (mk_ainput(computed_airspeed).get() >= c->airspeed1)
3566 return c->min_agl2(mk_ainput(computed_airspeed).get());
3567 else
3568 return c->min_agl1;
3569}
3570
3571const MK_VIII::Mode4Handler::EnvelopesConfiguration *
3572MK_VIII::Mode4Handler::get_ab_envelope ()
3573{
3574 // [SPEC] 6.2.4.1: "The Mode 4B envelope can also be selected by
3575 // setting flaps to landing configuration or by selecting flap
3576 // override."
3577 return mk_dinput(landing_gear) || mk->io_handler.flaps_down()
3578 ? conf.modes->b
3579 : conf.modes->ac;
3580}
3581
3582double
3583MK_VIII::Mode4Handler::get_bias (double initial_bias, double min_agl)
3584{
3585 // do not repeat terrain/gear/flap alerts below 30ft agl
3586 if (mk_data(radio_altitude).get() > 30.0)
3587 {
3588 if (initial_bias < 0.0) // sanity check
3589 initial_bias = 0.0;
3590 while ((mk_data(radio_altitude).get() < min_agl - min_agl * initial_bias)&&
3591 (initial_bias < 1.0))
3592 initial_bias += 0.2;
3593 }
3594
3595 return initial_bias;
3596}
3597
3598void
3599MK_VIII::Mode4Handler::handle_alert (unsigned int alert,
3600 double min_agl,
3601 double *bias)
3602{
3603 if (mk_test_alerts(alert))
3604 {
3605 double new_bias = get_bias(*bias, min_agl);
3606 if (new_bias > *bias)
3607 {
3608 *bias = new_bias;
3609 mk_repeat_alert(alert);
3610 }
3611 }
3612 else
3613 {
3614 *bias = get_bias(0.2, min_agl);
3615 mk_set_alerts(alert);
3616 }
3617}
3618
3619void
3620MK_VIII::Mode4Handler::update_ab ()
3621{
3622 if (! mk->io_handler.gpws_inhibit()
3623 && ! mk->state_handler.ground
3624 && ! mk->state_handler.takeoff
3625 && ! mk_data(radio_altitude).ncd
3626 && ! mk_ainput(computed_airspeed).ncd
3627 && mk_data(radio_altitude).get() > 30)
3628 {
3629 const EnvelopesConfiguration *c = get_ab_envelope();
3630 if (mk_ainput(computed_airspeed).get() < c->airspeed1)
3631 {
3632 if (mk_dinput(landing_gear))
3633 {
3634 if (! mk->io_handler.flaps_down() && mk_data(radio_altitude).get() < c->min_agl1)
3635 {
3636 handle_alert(mk_alert(MODE4_TOO_LOW_FLAPS), c->min_agl1, &ab_bias);
3637 return;
3638 }
3639 }
3640 else
3641 {
3642 if (mk_data(radio_altitude).get() < c->min_agl1)
3643 {
3644 handle_alert(mk_alert(MODE4_TOO_LOW_GEAR), c->min_agl1, &ab_bias);
3645 return;
3646 }
3647 }
3648 }
3649 }
3650
3651 mk_unset_alerts(mk_alert(MODE4_TOO_LOW_FLAPS) | mk_alert(MODE4_TOO_LOW_GEAR));
3652 ab_bias=0.0;
3653}
3654
3655void
3656MK_VIII::Mode4Handler::update_ab_expanded ()
3657{
3658 if (! mk->io_handler.gpws_inhibit()
3659 && ! mk->state_handler.ground
3660 && ! mk->state_handler.takeoff
3661 && ! mk_data(radio_altitude).ncd
3662 && ! mk_ainput(computed_airspeed).ncd
3663 && mk_data(radio_altitude).get() > 30)
3664 {
3665 const EnvelopesConfiguration *c = get_ab_envelope();
3666 if (mk_ainput(computed_airspeed).get() >= c->airspeed1)
3667 {
3668 double min_agl = get_upper_agl(c);
3669 if (mk_data(radio_altitude).get() < min_agl)
3670 {
3671 handle_alert(mk_alert(MODE4AB_TOO_LOW_TERRAIN), min_agl, &ab_expanded_bias);
3672 return;
3673 }
3674 }
3675 }
3676
3677 mk_unset_alerts(mk_alert(MODE4AB_TOO_LOW_TERRAIN));
3678 ab_expanded_bias=0.0;
3679}
3680
3681void
3682MK_VIII::Mode4Handler::update_c ()
3683{
3684 if (! mk->io_handler.gpws_inhibit()
3685 && ! mk->state_handler.ground // [1]
3686 && mk->state_handler.takeoff
3687 && ! mk_data(radio_altitude).ncd
3688 && ! mk_data(terrain_clearance).ncd
3689 && mk_data(radio_altitude).get() > 30
3690 && (! mk_dinput(landing_gear) || ! mk->io_handler.flaps_down())
3691 && mk_data(radio_altitude).get() < get_upper_agl(conf.modes->ac)
3692 && mk_data(radio_altitude).get() < mk_data(terrain_clearance).get())
3693 {
3694 handle_alert(mk_alert(MODE4C_TOO_LOW_TERRAIN), mk_data(terrain_clearance).get(), &c_bias);
3695 }
3696 else
3697 {
3698 mk_unset_alerts(mk_alert(MODE4C_TOO_LOW_TERRAIN));
3699 c_bias = 0.0;
3700 }
3701}
3702
3703void
3704MK_VIII::Mode4Handler::update ()
3705{
3706 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
3707 return;
3708
3709 update_ab();
3710 update_ab_expanded();
3711 update_c();
3712}
3713
3715// Mode5Handler ///////////////////////////////////////////////////////////////
3717
3718bool
3719MK_VIII::Mode5Handler::is_hard ()
3720{
3721 if (mk_data(radio_altitude).get() > 30
3722 && mk_data(radio_altitude).get() < 300
3723 && mk_data(glideslope_deviation_dots).get() > 2)
3724 {
3725 if (mk_data(radio_altitude).get() < 150)
3726 {
3727 if (mk_data(radio_altitude).get() > 293 - 71.43 * mk_data(glideslope_deviation_dots).get())
3728 return true;
3729 }
3730 else
3731 return true;
3732 }
3733
3734 return false;
3735}
3736
3737bool
3738MK_VIII::Mode5Handler::is_soft (double bias)
3739{
3740 // do not repeat glide-slope alerts below 30ft agl
3741 if (mk_data(radio_altitude).get() > 30)
3742 {
3743 double bias_dots = 1.3 * bias;
3744 if (mk_data(glideslope_deviation_dots).get() > 1.3 + bias_dots)
3745 {
3746 if (mk_data(radio_altitude).get() < 150)
3747 {
3748 if (mk_data(radio_altitude).get() > 243 - 71.43 * (mk_data(glideslope_deviation_dots).get() - bias_dots))
3749 return true;
3750 }
3751 else
3752 {
3753 double upper_limit;
3754
3755 if (mk_data(barometric_altitude_rate).ncd)
3756 upper_limit = 1000;
3757 else
3758 {
3759 if (mk_data(barometric_altitude_rate).get() >= 0)
3760 upper_limit = 500;
3761 else if (mk_data(barometric_altitude_rate).get() < -500)
3762 upper_limit = 1000;
3763 else
3764 upper_limit = -mk_data(barometric_altitude_rate).get() + 500;
3765 }
3766
3767 if (mk_data(radio_altitude).get() < upper_limit)
3768 return true;
3769 }
3770 }
3771 }
3772
3773 return false;
3774}
3775
3776double
3777MK_VIII::Mode5Handler::get_soft_bias (double initial_bias)
3778{
3779 if (initial_bias < 0.0) // sanity check
3780 initial_bias = 0.0;
3781
3782 while ((is_soft(initial_bias))&&
3783 (initial_bias < 1.0))
3784 {
3785 initial_bias += 0.2;
3786 }
3787
3788 return initial_bias;
3789}
3790
3791void
3792MK_VIII::Mode5Handler::update_hard (bool is)
3793{
3794 if (is)
3795 {
3796 if (! mk_test_alert(MODE5_HARD))
3797 {
3798 if (hard_timer.start_or_elapsed() >= 0.8)
3799 mk_set_alerts(mk_alert(MODE5_HARD));
3800 }
3801 }
3802 else
3803 {
3804 hard_timer.stop();
3805 mk_unset_alerts(mk_alert(MODE5_HARD));
3806 }
3807}
3808
3809void
3810MK_VIII::Mode5Handler::update_soft (bool is)
3811{
3812 if (is)
3813 {
3814 if (! mk_test_alert(MODE5_SOFT))
3815 {
3816 double new_bias = get_soft_bias(soft_bias);
3817 if (new_bias > soft_bias)
3818 {
3819 soft_bias = new_bias;
3820 mk_repeat_alert(mk_alert(MODE5_SOFT));
3821 }
3822 }
3823 else
3824 {
3825 if (soft_timer.start_or_elapsed() >= 0.8)
3826 {
3827 soft_bias = get_soft_bias(0.2);
3828 mk_set_alerts(mk_alert(MODE5_SOFT));
3829 }
3830 }
3831 }
3832 else
3833 {
3834 soft_timer.stop();
3835 mk_unset_alerts(mk_alert(MODE5_SOFT));
3836 }
3837}
3838
3839void
3840MK_VIII::Mode5Handler::update ()
3841{
3842 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
3843 return;
3844
3845 if (! mk->io_handler.gpws_inhibit()
3846 && ! mk->state_handler.ground // [1]
3847 && ! mk_dinput(glideslope_inhibit) // not on backcourse
3848 && ! mk_data(radio_altitude).ncd
3849 && ! mk_data(glideslope_deviation_dots).ncd
3850 && (! mk->io_handler.conf.localizer_enabled
3851 || mk_data(localizer_deviation_dots).ncd
3852 || mk_data(radio_altitude).get() < 500
3853 || fabs(mk_data(localizer_deviation_dots).get()) < 2)
3854 && (! mk->state_handler.takeoff || mk->io_handler.flaps_down())
3855 && mk_dinput(landing_gear)
3856 && ! mk_doutput(glideslope_cancel))
3857 {
3858 update_hard(is_hard());
3859 update_soft(is_soft(0));
3860 }
3861 else
3862 {
3863 update_hard(false);
3864 update_soft(false);
3865 }
3866}
3867
3869// Mode6Handler ///////////////////////////////////////////////////////////////
3871
3872// keep sorted in descending order
3873const int MK_VIII::Mode6Handler::altitude_callout_definitions[] =
3874 { 2500, 1000, 500, 400, 300, 200, 100, 50, 40, 30, 20, 10 };
3875
3876void
3877MK_VIII::Mode6Handler::reset_minimums ()
3878{
3879 minimums_issued = false;
3880 minimums_above_100_issued = false;
3881}
3882
3883void
3884MK_VIII::Mode6Handler::reset_altitude_callouts ()
3885{
3886 for (unsigned i = 0; i < n_altitude_callouts; i++)
3887 altitude_callouts_issued[i] = false;
3888 throttle_retarded = false;
3889}
3890
3891bool
3892MK_VIII::Mode6Handler::is_playing_altitude_callout ()
3893{
3894 for (unsigned i = 0; i < n_altitude_callouts; i++)
3895 {
3896 if (mk->voice_player.voice == mk_altitude_voice(i)
3897 || mk->voice_player.next_voice == mk_altitude_voice(i))
3898 return true;
3899 }
3900 return false;
3901}
3902
3903bool
3904MK_VIII::Mode6Handler::is_near_minimums (double callout)
3905{
3906 // [SPEC] 6.4.2
3907
3908 if (! mk_data(decision_height).ncd)
3909 {
3910 double diff = callout - mk_data(decision_height).get();
3911
3912 if (mk_data(radio_altitude).get() >= 200)
3913 {
3914 if (fabs(diff) <= 30)
3915 return true;
3916 }
3917 else
3918 {
3919 if (diff >= -3 && diff <= 6)
3920 return true;
3921 }
3922 }
3923
3924 return false;
3925}
3926
3927bool
3928MK_VIII::Mode6Handler::is_outside_band (double elevation, double callout)
3929{
3930 // [SPEC] 6.4.2
3931 return elevation < callout - (elevation > 150 ? 20 : 10);
3932}
3933
3934bool
3935MK_VIII::Mode6Handler::inhibit_smart_500 ()
3936{
3937 // [SPEC] 6.4.3
3938
3939 if (! mk_data(glideslope_deviation_dots).ncd
3940 && ! mk_dinput(glideslope_inhibit) // backcourse
3941 && ! mk_doutput(glideslope_cancel))
3942 {
3943 if (mk->io_handler.conf.localizer_enabled
3944 && ! mk_data(localizer_deviation_dots).ncd)
3945 {
3946 if (fabs(mk_data(localizer_deviation_dots).get()) <= 2)
3947 return true;
3948 }
3949 else
3950 {
3951 if (fabs(mk_data(glideslope_deviation_dots).get()) <= 2)
3952 return true;
3953 }
3954 }
3955
3956 return false;
3957}
3958
3959void
3960MK_VIII::Mode6Handler::boot ()
3961{
3962 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
3963 return;
3964
3965 last_decision_height = mk_dinput(decision_height);
3966 last_radio_altitude.set(&mk_data(radio_altitude));
3967
3968 // [SPEC] 6.4.2
3969 for (unsigned i = 0; i < n_altitude_callouts; i++)
3970 altitude_callouts_issued[i] = ! mk_data(radio_altitude).ncd
3971 && mk_data(radio_altitude).get() <= altitude_callout_definitions[i];
3972
3973 // extrapolated from [SPEC] 6.4.2
3974 minimums_issued = mk_dinput(decision_height);
3975
3976 if (conf.above_field_voice)
3977 {
3978 update_runway();
3979 get_altitude_above_field(&last_altitude_above_field);
3980 // extrapolated from [SPEC] 6.4.2
3981 above_field_issued = ! last_altitude_above_field.ncd
3982 && last_altitude_above_field.get() < 550;
3983 }
3984}
3985
3986void
3987MK_VIII::Mode6Handler::power_off ()
3988{
3989 runway_timer.stop();
3990}
3991
3992void
3993MK_VIII::Mode6Handler::enter_takeoff ()
3994{
3995 reset_altitude_callouts(); // [SPEC] 6.4.2
3996 reset_minimums(); // omitted by [SPEC]; common sense
3997}
3998
3999void
4000MK_VIII::Mode6Handler::leave_takeoff ()
4001{
4002 reset_altitude_callouts(); // [SPEC] 6.0.2
4003 reset_minimums(); // [SPEC] 6.0.2
4004}
4005
4006void
4007MK_VIII::Mode6Handler::set_volume (float volume)
4008{
4009 mk_voice(minimums_minimums)->set_volume(volume);
4010 mk_voice(five_hundred_above)->set_volume(volume);
4011 for (unsigned i = 0; i < n_altitude_callouts; i++)
4012 mk_altitude_voice(i)->set_volume(volume);
4013}
4014
4015bool
4016MK_VIII::Mode6Handler::altitude_callouts_enabled ()
4017{
4018 if (conf.minimums_enabled || conf.smart_500_enabled || conf.above_field_voice)
4019 return true;
4020
4021 return (conf.altitude_callouts_enabled != 0);
4022}
4023
4024void
4025MK_VIII::Mode6Handler::update_minimums ()
4026{
4027 if (! mk->io_handler.gpws_inhibit()
4028 && (mk->voice_player.voice == mk_voice(minimums_minimums)
4029 || mk->voice_player.next_voice == mk_voice(minimums_minimums)))
4030 {
4031 goto end;
4032 }
4033
4034 if (! mk->io_handler.gpws_inhibit()
4035 && ! mk->state_handler.ground // [1]
4036 && conf.minimums_enabled
4037 && ! minimums_issued
4038 && mk_dinput(landing_gear)
4039 && ! last_decision_height)
4040 {
4041 if (mk_dinput(decision_height))
4042 {
4043 minimums_issued = true;
4044
4045 // If an altitude callout is playing, stop it now so that the
4046 // minimums callout can be played (note that proper connection
4047 // and synchronization of the radio-altitude ARINC 529 input,
4048 // decision-height discrete and decision-height ARINC 529 input
4049 // will prevent an altitude callout from being played near the
4050 // decision height).
4051
4052 if (is_playing_altitude_callout())
4053 mk->voice_player.stop(VoicePlayer::STOP_NOW);
4054
4055 mk_unset_alerts(mk_alert(MODE6_MINIMUMS_100));
4056 mk_set_alerts(mk_alert(MODE6_MINIMUMS));
4057 }
4058 else
4059 if (conf.minimums_above_100_enabled && (!minimums_above_100_issued) &&
4060 mk_dinput(decision_height_100))
4061 {
4062 minimums_above_100_issued = true;
4063
4064 if (is_playing_altitude_callout())
4065 mk->voice_player.stop(VoicePlayer::STOP_NOW);
4066
4067 mk_unset_alerts(mk_alert(MODE6_MINIMUMS));
4068 mk_set_alerts(mk_alert(MODE6_MINIMUMS_100));
4069 }
4070 }
4071 else
4072 {
4073 mk_unset_alerts(mk_alert(MODE6_MINIMUMS));
4074 mk_unset_alerts(mk_alert(MODE6_MINIMUMS_100));
4075 }
4076
4077end:
4078 last_decision_height = mk_dinput(decision_height);
4079 last_decision_height_100 = mk_dinput(decision_height_100);
4080}
4081
4082void
4083MK_VIII::Mode6Handler::update_altitude_callouts ()
4084{
4085 if (! mk->io_handler.gpws_inhibit() && is_playing_altitude_callout())
4086 goto end;
4087
4088 if (! mk->io_handler.gpws_inhibit())
4089 {
4090 if (mk->mode6_handler.conf.retard_enabled &&
4091 (!throttle_retarded)&&
4092 (mk_data(radio_altitude).get() < 25))
4093 {
4094 if (mk_node(throttle)->getDoubleValue() > 0.05)
4095 {
4096 mk_repeat_alert(mk_alert(MODE6_RETARD));
4097 goto end;
4098 }
4099 // throttle was closed
4100 throttle_retarded = true;
4101 }
4102 mk_unset_alerts(mk_alert(MODE6_RETARD));
4103
4104 if (! mk->state_handler.ground // [1]
4105 && ! mk_data(radio_altitude).ncd)
4106 {
4107 for (unsigned i = 0; i < n_altitude_callouts && mk_data(radio_altitude).get() <= altitude_callout_definitions[i]; i++)
4108 {
4109 if (((conf.altitude_callouts_enabled & (1<<i))
4110 || (altitude_callout_definitions[i] == 500
4111 && conf.smart_500_enabled))
4112 && ! altitude_callouts_issued[i]
4113 && (last_radio_altitude.ncd
4114 || last_radio_altitude.get() > altitude_callout_definitions[i]))
4115 {
4116 // lock out all callouts superior or equal to this one
4117 for (unsigned j = 0; j <= i; j++)
4118 altitude_callouts_issued[j] = true;
4119
4120 altitude_callouts_issued[i] = true;
4121 if (! is_near_minimums(altitude_callout_definitions[i])
4122 && ! is_outside_band(mk_data(radio_altitude).get(), altitude_callout_definitions[i])
4123 && (! conf.smart_500_enabled
4124 || altitude_callout_definitions[i] != 500
4125 || ! inhibit_smart_500()))
4126 {
4127 mk->alert_handler.set_altitude_callout_alert(mk_altitude_voice(i));
4128 goto end;
4129 }
4130 }
4131 }
4132 }
4133 }
4134
4135 mk_unset_alerts(mk_alert(MODE6_ALTITUDE_CALLOUT));
4136
4137end:
4138 last_radio_altitude.set(&mk_data(radio_altitude));
4139}
4140
4141bool
4142MK_VIII::Mode6Handler::test_runway (const FGRunway *_runway)
4143{
4144 if (_runway->lengthFt() < mk->conf.runway_database)
4145 return false;
4146
4147 SGGeod pos(
4148 SGGeod::fromDeg(mk_data(gps_longitude).get(), mk_data(gps_latitude).get()));
4149
4150 // get distance to threshold
4151 return SGGeodesy::distanceNm(pos, _runway->threshold()) <= 5;
4152}
4153
4154bool
4155MK_VIII::Mode6Handler::test_airport (const FGAirport *airport)
4156{
4157 for (unsigned int r=0; r<airport->numRunways(); ++r)
4158 {
4159 FGRunway* rwy(airport->getRunwayByIndex(r));
4160
4161 if (test_runway(rwy))
4162 return true;
4163 }
4164
4165 return false;
4166}
4167
4168bool MK_VIII::Mode6Handler::AirportFilter::passAirport(FGAirport* a) const
4169{
4170 bool ok = self->test_airport(a);
4171 return ok;
4172}
4173
4174void
4175MK_VIII::Mode6Handler::update_runway ()
4176{
4177 if (mk_data(gps_latitude).ncd || mk_data(gps_longitude).ncd)
4178 {
4179 has_runway.unset();
4180 return;
4181 }
4182
4183 // Search for the closest runway threshold in range 5
4184 // nm. Passing 30nm to
4185 // get_closest_airport() provides enough margin for large
4186 // airports, which may have a runway located far away from the
4187 // airport's reference point.
4188 AirportFilter filter(this);
4190 SGGeod::fromDeg(mk_data(gps_longitude).get(), mk_data(gps_latitude).get()),
4191 30.0, &filter);
4192 if (apt)
4193 {
4194 runway.elevation = apt->elevation();
4195 }
4196
4197 has_runway.set(apt != NULL);
4198}
4199
4200void
4201MK_VIII::Mode6Handler::get_altitude_above_field (Parameter<double> *parameter)
4202{
4203 if (! has_runway.ncd && has_runway.get() && ! mk_data(geometric_altitude).ncd)
4204 parameter->set(mk_data(geometric_altitude).get() - runway.elevation);
4205 else
4206 parameter->unset();
4207}
4208
4209void
4210MK_VIII::Mode6Handler::update_above_field_callout ()
4211{
4212 if (! conf.above_field_voice)
4213 return;
4214
4215 // update_runway() has to iterate over the whole runway database
4216 // (which contains thousands of runways), so it would be unwise to
4217 // run it every frame. Run it every 3 seconds. Note that the first
4218 // iteration is run in boot().
4219 if (runway_timer.start_or_elapsed() >= 3)
4220 {
4221 update_runway();
4222 runway_timer.start();
4223 }
4224
4225 Parameter<double> altitude_above_field;
4226 get_altitude_above_field(&altitude_above_field);
4227
4228 if (! mk->io_handler.gpws_inhibit()
4229 && (mk->voice_player.voice == conf.above_field_voice
4230 || mk->voice_player.next_voice == conf.above_field_voice))
4231 goto end;
4232
4233 // handle reset term
4234 if (above_field_issued)
4235 {
4236 if ((! has_runway.ncd && ! has_runway.get())
4237 || (! altitude_above_field.ncd && altitude_above_field.get() > 700))
4238 above_field_issued = false;
4239 }
4240
4241 if (! mk->io_handler.gpws_inhibit()
4242 && ! mk->state_handler.ground // [1]
4243 && ! above_field_issued
4244 && ! altitude_above_field.ncd
4245 && altitude_above_field.get() < 550
4246 && (last_altitude_above_field.ncd
4247 || last_altitude_above_field.get() >= 550))
4248 {
4249 above_field_issued = true;
4250
4251 if (! is_outside_band(altitude_above_field.get(), 500))
4252 {
4253 mk->alert_handler.set_altitude_callout_alert(conf.above_field_voice);
4254 goto end;
4255 }
4256 }
4257
4258 mk_unset_alerts(mk_alert(MODE6_ALTITUDE_CALLOUT));
4259
4260end:
4261 last_altitude_above_field.set(&altitude_above_field);
4262}
4263
4264bool
4265MK_VIII::Mode6Handler::is_bank_angle (double abs_roll_angle, double bias)
4266{
4267 return conf.is_bank_angle(&mk_data(radio_altitude),
4268 abs_roll_angle - abs_roll_angle * bias,
4269 mk_dinput(autopilot_engaged));
4270}
4271
4272bool
4273MK_VIII::Mode6Handler::is_high_bank_angle ()
4274{
4275 return mk_data(radio_altitude).ncd || mk_data(radio_altitude).get() >= 210;
4276}
4277
4278unsigned int
4279MK_VIII::Mode6Handler::get_bank_angle_alerts ()
4280{
4281 if (! mk->io_handler.gpws_inhibit()
4282 && ! mk->state_handler.ground // [1]
4283 && ! mk_data(roll_angle).ncd)
4284 {
4285 double abs_roll_angle = fabs(mk_data(roll_angle).get());
4286
4287 if (is_bank_angle(abs_roll_angle, 0.4))
4288 return is_high_bank_angle()
4289 ? (mk_alert(MODE6_HIGH_BANK_ANGLE_1) | mk_alert(MODE6_HIGH_BANK_ANGLE_2) | mk_alert(MODE6_HIGH_BANK_ANGLE_3))
4290 : (mk_alert(MODE6_LOW_BANK_ANGLE_1) | mk_alert(MODE6_LOW_BANK_ANGLE_2) | mk_alert(MODE6_LOW_BANK_ANGLE_3));
4291 else if (is_bank_angle(abs_roll_angle, 0.2))
4292 return is_high_bank_angle()
4293 ? (mk_alert(MODE6_HIGH_BANK_ANGLE_1) | mk_alert(MODE6_HIGH_BANK_ANGLE_2))
4294 : (mk_alert(MODE6_LOW_BANK_ANGLE_1) | mk_alert(MODE6_LOW_BANK_ANGLE_2));
4295 else if (is_bank_angle(abs_roll_angle, 0))
4296 return is_high_bank_angle()
4297 ? mk_alert(MODE6_HIGH_BANK_ANGLE_1)
4298 : mk_alert(MODE6_LOW_BANK_ANGLE_1);
4299 }
4300
4301 return 0;
4302}
4303
4304void
4305MK_VIII::Mode6Handler::update_bank_angle ()
4306{
4307 if (! conf.bank_angle_enabled)
4308 return;
4309
4310 unsigned int alerts = get_bank_angle_alerts();
4311
4312 // [SPEC] 6.4.4 specifies that the non-continuous alerts
4313 // (MODE6_*_BANK_ANGLE_1 and MODE6_*_BANK_ANGLE_2) are locked out
4314 // until the initial envelope is exited.
4315
4316 if (! test_bits(alerts, mk_alert(MODE6_LOW_BANK_ANGLE_3)))
4317 mk_unset_alerts(mk_alert(MODE6_LOW_BANK_ANGLE_3));
4318 if (! test_bits(alerts, mk_alert(MODE6_HIGH_BANK_ANGLE_3)))
4319 mk_unset_alerts(mk_alert(MODE6_HIGH_BANK_ANGLE_3));
4320
4321 if (alerts != 0)
4322 mk_set_alerts(alerts);
4323 else
4324 mk_unset_alerts(mk_alert(MODE6_LOW_BANK_ANGLE_1)
4325 | mk_alert(MODE6_HIGH_BANK_ANGLE_1)
4326 | mk_alert(MODE6_LOW_BANK_ANGLE_2)
4327 | mk_alert(MODE6_HIGH_BANK_ANGLE_2));
4328}
4329
4330void
4331MK_VIII::Mode6Handler::update ()
4332{
4333 if (mk->configuration_module.state != ConfigurationModule::STATE_OK)
4334 return;
4335
4336 if (! mk->state_handler.takeoff
4337 && ! mk_data(radio_altitude).ncd
4338 && mk_data(radio_altitude).get() > 1000)
4339 {
4340 reset_altitude_callouts(); // [SPEC] 6.4.2
4341 reset_minimums(); // common sense
4342 }
4343
4344 update_minimums();
4345 update_altitude_callouts();
4346 update_above_field_callout();
4347 update_bank_angle();
4348}
4349
4351// TCFHandler /////////////////////////////////////////////////////////////////
4353
4354bool MK_VIII::TCFHandler::AirportFilter::passAirport(FGAirport* aApt) const
4355{
4356 return aApt->hasHardRunwayOfLengthFt(mk->conf.runway_database);
4357}
4358
4359void
4360MK_VIII::TCFHandler::update_runway ()
4361{
4362 has_runway = false;
4363 if (mk_data(gps_latitude).ncd || mk_data(gps_longitude).ncd)
4364 {
4365 return;
4366 }
4367
4368 // Search for the intended destination runway of the closest
4369 // airport in range 15 nm. Passing 30nm to get_closest_airport()
4370 // provides enough margin for
4371 // large airports, which may have a runway located far away from
4372 // the airport's reference point.
4373 AirportFilter filter(mk);
4374 SGGeod apos = SGGeod::fromDeg(mk_data(gps_longitude).get(), mk_data(gps_latitude).get());
4375 FGAirport* apt = FGAirport::findClosest(apos, 30.0, &filter);
4376 if (!apt)
4377 {
4378 return;
4379 }
4380
4381 FGRunway* _runway = apt->findBestRunwayForPos(apos).get();
4382 if (!_runway)
4383 {
4384 return;
4385 }
4386
4387 has_runway = true;
4388
4389 runway.center = _runway->pointOnCenterline(_runway->lengthM() * 0.5);
4390
4391 runway.elevation = apt->elevation();
4392
4393 runway.half_width_m = _runway->widthM() * 0.5;
4394 double half_length_m = _runway->lengthM() * 0.5;
4395 runway.half_length = half_length_m * SG_METER_TO_NM;
4396
4397 // _________
4398 // |
4399 // <<<<e0 | <<<h0
4400 // _________|
4401
4402 // get heading of runway end (h0)
4403 runway.edge.heading = _runway->headingDeg();
4404
4405 // get position of runway threshold (e0)
4406 runway.edge.position = _runway->begin();
4407
4408 // get cartesian coordinates of both runway ends
4409 runway.bias_points[0] = _runway->cart();
4410 runway.bias_points[1] = _runway->reciprocalRunway()->cart();
4411}
4412
4413// Returns true if the current GPS position is inside the edge
4414// triangle of @edge. The edge triangle, which is specified and
4415// represented in [SPEC] 6.3.1, is defined as an isocele right
4416// triangle of infinite height, whose right angle is located at the
4417// position of @edge, and whose height is parallel to the heading of
4418// @edge.
4419bool
4420MK_VIII::TCFHandler::is_inside_edge_triangle (RunwayEdge *edge)
4421{
4422 double az = SGGeodesy::courseDeg( SGGeod::fromDeg(mk_data(gps_longitude).get(),
4423 mk_data(gps_latitude).get()),
4424 edge->position);
4425 return get_heading_difference(az, edge->heading) <= 45;
4426}
4427
4428// Returns true if the current GPS position is inside the bias area of
4429// the currently selected runway.
4430bool
4431MK_VIII::TCFHandler::is_inside_bias_area ()
4432{
4433 double half_bias_width_m = k * SG_NM_TO_METER + runway.half_width_m;
4434 SGVec3d cpos = SGVec3d::fromGeod( SGGeod::fromDegFt(mk_data(gps_longitude).get(),
4435 mk_data(gps_latitude).get(),
4436 runway.elevation) );
4437 SGLineSegmentd bias_line = SGLineSegmentd(runway.bias_points[0], runway.bias_points[1]);
4438 return dist(cpos, bias_line) < half_bias_width_m;
4439}
4440
4441bool
4442MK_VIII::TCFHandler::is_tcf ()
4443{
4444 if (mk_data(radio_altitude).get() > 10)
4445 {
4446 if (has_runway)
4447 {
4448 double distance = SGGeodesy::distanceNm( SGGeod::fromDegFt(mk_data(gps_longitude).get(),
4449 mk_data(gps_latitude).get(),
4450 runway.elevation),
4451 runway.center);
4452
4453 // distance to the inner envelope edge
4454 double edge_distance = distance - runway.half_length - k;
4455
4456 if (edge_distance > 15)
4457 {
4458 if (mk_data(radio_altitude).get() < 700)
4459 return true;
4460 }
4461 else
4462 if (edge_distance > 12)
4463 {
4464 if (mk_data(radio_altitude).get() < 100 * edge_distance - 800)
4465 return true;
4466 }
4467 else
4468 if (edge_distance > 4)
4469 {
4470 if (mk_data(radio_altitude).get() < 400)
4471 return true;
4472 }
4473 else
4474 if (edge_distance > 2.45)
4475 {
4476 if (mk_data(radio_altitude).get() < 100 * edge_distance)
4477 return true;
4478 }
4479 else
4480 if ( is_inside_edge_triangle(&runway.edge) && (edge_distance > 0.01) )
4481 {
4482 if (mk_data(radio_altitude).get() < 100 * edge_distance)
4483 return true;
4484 }
4485 else
4486 if (! is_inside_bias_area())
4487 {
4488 if (mk_data(radio_altitude).get() < 245)
4489 return true;
4490 }
4491 }
4492 else
4493 if (mk_data(radio_altitude).get() < 700)
4494 {
4495 return true;
4496 }
4497 }
4498 return false;
4499}
4500
4501bool
4502MK_VIII::TCFHandler::is_rfcf ()
4503{
4504 if (has_runway)
4505 {
4506 double distance = SGGeodesy::distanceNm( SGGeod::fromDegFt(mk_data(gps_longitude).get(),
4507 mk_data(gps_latitude).get(),
4508 runway.elevation),
4509 runway.center);
4510 distance -= runway.half_length;
4511
4512 if (distance < 5.0)
4513 {
4514 double krf = k + mk_data(gps_vertical_figure_of_merit).get() / 200;
4515 distance -= krf;
4516 double altitude_above_field = mk_data(geometric_altitude).get() - runway.elevation;
4517
4518 if ( (distance > 1.5) && (altitude_above_field < 300.0) )
4519 return true;
4520 else if ( (distance > 0.0) && (altitude_above_field < 200 * distance) )
4521 return true;
4522 }
4523 }
4524
4525 return false;
4526}
4527
4528void
4529MK_VIII::TCFHandler::update ()
4530{
4531 if (mk->configuration_module.state != ConfigurationModule::STATE_OK || ! conf.enabled)
4532 return;
4533
4534 // update_runway() has to iterate over the whole runway database
4535 // (which contains thousands of runways), so it would be unwise to
4536 // run it every frame. Run it every 3 seconds.
4537 if (! runway_timer.running || runway_timer.elapsed() >= 3)
4538 {
4539 update_runway();
4540 runway_timer.start();
4541 }
4542
4543 if (! mk_dinput(ta_tcf_inhibit)
4544 && ! mk->state_handler.ground // [1]
4545 && ! mk_data(gps_latitude).ncd
4546 && ! mk_data(gps_longitude).ncd
4547 && ! mk_data(radio_altitude).ncd
4548 && ! mk_data(geometric_altitude).ncd
4549 && ! mk_data(gps_vertical_figure_of_merit).ncd)
4550 {
4551 double *_reference;
4552
4553 if (is_tcf())
4554 _reference = mk_data(radio_altitude).get_pointer();
4555 else if (is_rfcf())
4556 _reference = mk_data(geometric_altitude).get_pointer();
4557 else
4558 _reference = NULL;
4559
4560 if (_reference)
4561 {
4562 if (mk_test_alert(TCF_TOO_LOW_TERRAIN))
4563 {
4564 double new_bias = bias;
4565 // do not repeat terrain alerts below 30ft agl
4566 if (mk_data(radio_altitude).get() > 30)
4567 {
4568 if (new_bias < 0.0) // sanity check
4569 new_bias = 0.0;
4570 while ((*reference < initial_value - initial_value * new_bias)&&
4571 (new_bias < 1.0))
4572 {
4573 new_bias += 0.2;
4574 }
4575 }
4576
4577 if (new_bias > bias)
4578 {
4579 bias = new_bias;
4580 mk_repeat_alert(mk_alert(TCF_TOO_LOW_TERRAIN));
4581 }
4582 }
4583 else
4584 {
4585 bias = 0.2;
4586 reference = _reference;
4587 initial_value = *reference;
4588 mk_set_alerts(mk_alert(TCF_TOO_LOW_TERRAIN));
4589 }
4590
4591 return;
4592 }
4593 }
4594
4595 mk_unset_alerts(mk_alert(TCF_TOO_LOW_TERRAIN));
4596}
4597
4599// MK_VIII ////////////////////////////////////////////////////////////////////
4601
4602MK_VIII::MK_VIII (SGPropertyNode *node)
4603 : properties_handler(this),
4604 name("mk-viii"),
4605 num(0),
4606 power_handler(this),
4607 system_handler(this),
4608 configuration_module(this),
4609 fault_handler(this),
4610 io_handler(this),
4611 voice_player(this),
4612 self_test_handler(this),
4613 alert_handler(this),
4614 state_handler(this),
4615 mode1_handler(this),
4616 mode2_handler(this),
4617 mode3_handler(this),
4618 mode4_handler(this),
4619 mode5_handler(this),
4620 mode6_handler(this),
4621 tcf_handler(this)
4622{
4623 for (int i = 0; i < node->nChildren(); ++i)
4624 {
4625 SGPropertyNode *child = node->getChild(i);
4626 string cname = child->getNameString();
4627 string cval = child->getStringValue();
4628
4629 if (cname == "name")
4630 name = cval;
4631 else if (cname == "number")
4632 num = child->getIntValue();
4633 else
4634 {
4635 SG_LOG(SG_INSTR, SG_WARN, "Error in mk-viii config logic");
4636 if (name.length())
4637 SG_LOG(SG_INSTR, SG_WARN, "Section = " << name);
4638 }
4639 }
4640}
4641
4642void
4644{
4645 properties_handler.init();
4646 voice_player.init();
4647}
4648
4649void
4651{
4652 SGPropertyNode *node = fgGetNode(("/instrumentation/" + name), num, true);
4653
4654 configuration_module.bind(node);
4655 power_handler.bind(node);
4656 io_handler.bind(node);
4657 voice_player.bind(node, "Sounds/mk-viii/");
4658}
4659
4660void
4662{
4663 properties_handler.unbind();
4664}
4665
4666void
4668{
4669 power_handler.update();
4670 system_handler.update();
4671
4672 if (system_handler.state != SystemHandler::STATE_ON)
4673 return;
4674
4675 io_handler.update_inputs();
4676 io_handler.update_input_faults();
4677
4678 voice_player.update();
4679 state_handler.update();
4680
4681 if (self_test_handler.update())
4682 return;
4683
4684 io_handler.update_internal_latches();
4685 io_handler.update_lamps();
4686
4687 mode1_handler.update();
4688 mode2_handler.update();
4689 mode3_handler.update();
4690 mode4_handler.update();
4691 mode5_handler.update();
4692 mode6_handler.update();
4693 tcf_handler.update();
4694
4695 alert_handler.update();
4696 io_handler.update_outputs();
4697}
4698
4699
4700// Register the subsystem.
4701#if 0
4702SGSubsystemMgr::InstancedRegistrant<MK_VIII> registrantMK_VIII(
4703 SGSubsystemMgr::FDM,
4704 {{"instrumentation", SGSubsystemMgr::Dependency::HARD}},
4705 0.2);
4706#endif
double altitude
Definition ADA.cxx:46
#define throttle
Definition ADA.cxx:130
double latitude
Definition ADA.cxx:53
double longitude
Definition ADA.cxx:54
#define i(x)
SGSharedPtr< FGPositioned > FGPositionedRef
Definition airways.hxx:30
unsigned int numRunways() const
Definition airport.cxx:102
FGRunwayRef findBestRunwayForPos(const SGGeod &aPos) const
return the most likely target runway based on a position.
Definition airport.cxx:248
FGRunwayRef getRunwayByIndex(unsigned int aIndex) const
Definition airport.cxx:116
bool hasHardRunwayOfLengthFt(double aLengthFt) const
Useful predicate for FMS/GPS/NAV displays and similar - check if this airport has a hard-surfaced run...
Definition airport.cxx:272
static FGAirportRef findClosest(const SGGeod &aPos, double aCuttofNm, Filter *filter=NULL)
Syntactic wrapper around FGPositioned::findClosest - find the closest match for filter,...
Definition airport.cxx:425
double get_sim_time_sec() const
Definition globals.hxx:185
virtual const SGVec3d & cart() const
The cartesian position associated with this object.
double elevation() const
static FGPositionedRef findClosest(const SGGeod &aPos, double aCutoffNm, Filter *aFilter=NULL)
Find the closest item to a position, which pass the specified filter A cutoff range in NM must be spe...
double headingDeg() const
Runway heading in degrees.
double lengthFt() const
SGGeod pointOnCenterline(double aOffset) const
Retrieve a position on the extended centerline.
double widthM() const
double lengthM() const
SGGeod threshold() const
Get the (possibly displaced) threshold point.
Definition runways.cxx:99
FGRunway * reciprocalRunway() const
Definition runways.cxx:115
SGGeod begin() const
Get the runway beginning point - this is syntatic sugar, equivalent to calling pointOnCenterline(0....
Definition runways.cxx:89
void make_voice(Voice **voice)
SGSoundSample * get_sample(const char *name)
void present_status_section(const char *name)
Definition mk_viii.cxx:2013
bool gpws_inhibit() const
Definition mk_viii.cxx:1810
void bind(SGPropertyNode *node)
Definition mk_viii.cxx:1899
void update_egpwc_alert_discrete_3()
Definition mk_viii.cxx:1719
void present_status_item(const char *name, const char *value=NULL)
Definition mk_viii.cxx:2019
bool get_present_status() const
Definition mk_viii.cxx:2134
void present_status_subitem(const char *name)
Definition mk_viii.cxx:2028
bool steep_approach() const
Definition mk_viii.cxx:1834
struct MK_VIII::IOHandler::_s_inputs inputs
IOHandler(MK_VIII *device)
Definition mk_viii.cxx:1020
bool real_flaps_down() const
Definition mk_viii.cxx:1816
void update_internal_latches()
Definition mk_viii.cxx:1529
struct MK_VIII::IOHandler::_s_Conf conf
void set_present_status(bool value)
Definition mk_viii.cxx:2140
void set_lamp(Lamp lamp)
Definition mk_viii.cxx:1792
void update_egpwc_logic_discretes()
Definition mk_viii.cxx:1603
bool momentary_steep_approach_enabled() const
Definition mk_viii.cxx:1847
bool flaps_down() const
Definition mk_viii.cxx:1822
struct MK_VIII::IOHandler::_s_input_feeders input_feeders
void update_egpws_alert_discrete_1()
Definition mk_viii.cxx:1567
void update_alternate_discrete_input(bool *ptr)
Definition mk_viii.cxx:1510
void set_discrete_input(bool *ptr, bool value)
Definition mk_viii.cxx:1954
void update_egpws_alert_discrete_2()
Definition mk_viii.cxx:1698
bool flap_override() const
Definition mk_viii.cxx:1828
void update_mode6_callouts_discrete_2()
Definition mk_viii.cxx:1666
void update_input_faults()
Definition mk_viii.cxx:1406
bool get_discrete_input(bool *ptr) const
Definition mk_viii.cxx:1948
bool * get_lamp_output(Lamp lamp)
Definition mk_viii.cxx:1754
void update_mode6_callouts_discrete_1()
Definition mk_viii.cxx:1633
struct MK_VIII::VoicePlayer::@324313271375217117055266177221205376013353212253 voices
MK_VIII(SGPropertyNode *node)
Definition mk_viii.cxx:4602
void bind() override
Definition mk_viii.cxx:4650
void update(double dt) override
Definition mk_viii.cxx:4667
PropertiesHandler properties_handler
Definition mk_viii.hxx:192
void unbind() override
Definition mk_viii.cxx:4661
void init() override
Definition mk_viii.cxx:4643
int runway_database
Definition mk_viii.hxx:1371
const char * name
FGGlobals * globals
Definition globals.cxx:142
#define STDPAUSE
#define mk_dinput_feed(_name)
Definition mk_viii.cxx:108
#define mk_dinput(_name)
Definition mk_viii.cxx:109
#define GLIDESLOPE_DDM_TO_DOTS
Definition mk_viii.cxx:93
#define mk_aoutput(_name)
Definition mk_viii.cxx:113
#define LOCALIZER_DOTS_TO_DDM
Definition mk_viii.cxx:95
#define mk_voice(_name)
Definition mk_viii.cxx:116
#define mk_node(_name)
Definition mk_viii.cxx:106
static double m3_t2_max_alt_loss(bool flap_override, double agl)
Definition mk_viii.cxx:385
#define mk_test_alerts(_test)
Definition mk_viii.cxx:125
static double get_heading_difference(double h1, double h2)
Definition mk_viii.cxx:136
#define was_here_offset(offset)
static double m4_t79_b_min_agl2(double airspeed)
Definition mk_viii.cxx:399
#define mk_repeat_alert
Definition mk_viii.cxx:123
#define n_elements(_array)
Definition mk_viii.cxx:103
#define GLIDESLOPE_DOTS_TO_DDM
Definition mk_viii.cxx:92
#define mk_test_alert(_name)
Definition mk_viii.cxx:124
#define mk_set_alerts
Definition mk_viii.cxx:121
#define mk_doutput(_name)
Definition mk_viii.cxx:112
static int m3_t1_max_agl(bool flap_override)
Definition mk_viii.cxx:382
static double m4_t1_min_agl2(double airspeed)
Definition mk_viii.cxx:395
#define test_bits(_bits, _test)
Definition mk_viii.cxx:104
static double m1_t1_min_agl2(double vs)
Definition mk_viii.cxx:378
#define assert_not_reached()
Definition mk_viii.cxx:102
#define LOCALIZER_DDM_TO_DOTS
Definition mk_viii.cxx:96
static double modify_amplitude(double amplitude, double dB)
Definition mk_viii.cxx:130
static double m4_t79_a_min_agl2(double airspeed)
Definition mk_viii.cxx:397
#define mk_ainput_feed(_name)
Definition mk_viii.cxx:110
static double m1_t4_min_agl2(double vs)
Definition mk_viii.cxx:380
#define mk_data(_name)
Definition mk_viii.cxx:114
static double m1_t4_min_agl1(double vs)
Definition mk_viii.cxx:379
#define mk_ainput(_name)
Definition mk_viii.cxx:111
#define mk_unset_alerts
Definition mk_viii.cxx:122
static double m4_t568_b_min_agl2(double airspeed)
Definition mk_viii.cxx:398
#define was_here()
static double m3_t1_max_alt_loss(bool flap_override, double agl)
Definition mk_viii.cxx:383
static double m1_t1_min_agl1(double vs)
Definition mk_viii.cxx:377
static double m4_t568_a_min_agl2(double airspeed)
Definition mk_viii.cxx:396
#define mk_altitude_voice(_i)
Definition mk_viii.cxx:117
static int m3_t2_max_agl(bool flap_override)
Definition mk_viii.cxx:384
#define mk_alert(_name)
Definition mk_viii.cxx:119
double getDoubleValue(const char *spec, double default_)
SGPropertyNode * fgGetNode(const char *path, bool create)
Get a property node.
Definition proptest.cpp:27