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