FlightGear next
tcas.cxx
Go to the documentation of this file.
1// tcas.cxx -- Traffic Alert and Collision Avoidance System (TCAS) Emulation
2//
3// Written by Thorsten Brehm, started December 2010.
4//
5// Copyright (C) 2010 Thorsten Brehm - brehmt (at) gmail com
6//
7// This program is free software; you can redistribute it and/or
8// modify it under the terms of the GNU General Public License as
9// published by the Free Software Foundation; either version 2 of the
10// License, or (at your option) any later version.
11//
12// This program is distributed in the hope that it will be useful, but
13// WITHOUT ANY WARRANTY; without even the implied warranty of
14// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15// General Public License for more details.
16//
17// You should have received a copy of the GNU General Public License
18// along with this program; if not, write to the Free Software
19// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
20//
22
23/* References:
24 *
25 * [TCASII] Introduction to TCAS II Version 7, Federal Aviation Administration, November 2000
26 * http://www.arinc.com/downloads/tcas/tcas.pdf
27 *
28 * [EUROACAS] Eurocontrol Airborne Collision Avoidance System (ACAS),
29 * http://www.eurocontrol.int/msa/public/standard_page/ACAS_Startpage.html
30 *
31 * Glossary:
32 *
33 * ALIM: Altitude Limit
34 *
35 * CPA: Closest point of approach as computed from a threat's range and range rate.
36 *
37 * DMOD: Distance MODification
38 *
39 * Intruder: A target that has satisfied the traffic detection criteria.
40 *
41 * Proximity target: Any target that is less than 6 nmi in range and within +/-1200ft
42 * vertically, but that does not meet the intruder or threat criteria.
43 *
44 * RA: Resolution advisory. An indication given by TCAS II to a flight crew that a
45 * vertical maneuver should, or in some cases should not, be performed to attain or
46 * maintain safe separation from a threat.
47 *
48 * SL: Sensitivity Level. A value used in defining the size of the protected volume
49 * around the own aircraft.
50 *
51 * TA: Traffic Advisory. An indication given by TCAS to the pilot when an aircraft has
52 * entered, or is projected to enter, the protected volume around the own aircraft.
53 *
54 * Tau: Approximation of the time, in seconds, to CPA or to the aircraft being at the
55 * same altitude.
56 *
57 * TCAS: Traffic alert and Collision Avoidance System
58 */
59
60/* Module properties:
61 *
62 * serviceable enable/disable TCAS processing
63 *
64 * voice/file-prefix path (and optional prefix) for sound sample files
65 * (only evaluated at start-up)
66 *
67 * inputs/mode TCAS mode selection: 0=off,1=standby,2=TA only,3=auto(TA/RA)
68 * inputs/self-test trigger self-test sequence
69 *
70 * outputs/traffic-alert intruder detected (true=TA-threat is active, includes RA-threats)
71 * outputs/advisory-alert resolution advisory is issued (true=advisory is valid)
72 * outputs/vertical-speed vertical speed required by advisory (+/-2000/1500/500/0)
73 *
74 * speaker/max-dist Max. distance where speaker is heard
75 * speaker/reference-dist Distance to pilot
76 * speaker/volume Volume at reference distance
77 *
78 * debug/threat-trigger trigger debugging test (in debug mode only)
79 * debug/threat-RA debugging RA value (in debug mode only)
80 * debug/threat-level debugging threat level (in debug mode only)
81 */
82
83#ifdef _MSC_VER
84# pragma warning( disable: 4355 )
85#endif
86
87#include <config.h>
88
89#include <stdio.h>
90#include <string.h>
91#include <assert.h>
92#include <cmath>
93
94#include <string>
95#include <sstream>
96
97#include <simgear/constants.h>
98#include <simgear/sg_inlines.h>
99#include <simgear/debug/logstream.hxx>
100#include <simgear/math/sg_geodesy.hxx>
101#include <simgear/sound/soundmgr.hxx>
102#include <simgear/sound/sample_group.hxx>
103#include <simgear/structure/exception.hxx>
104
105using std::string;
106
108// debug switches /////////////////////////////////////////////////////////////
110//#define FEATURE_TCAS_DEBUG_ANNUNCIATOR
111//#define FEATURE_TCAS_DEBUG_COORDINATOR
112//#define FEATURE_TCAS_DEBUG_THREAT_DETECTOR
113//#define FEATURE_TCAS_DEBUG_TRACKER
114//#define FEATURE_TCAS_DEBUG_ADV_GENERATOR
115//#define FEATURE_TCAS_DEBUG_PROPERTIES
116
117#include <Main/fg_props.hxx>
118#include <Main/globals.hxx>
119#include "instrument_mgr.hxx"
120#include "tcas.hxx"
121
123// constants //////////////////////////////////////////////////////////////////
125
129const TCAS::SensitivityLevel
130TCAS::ThreatDetector::sensitivityLevels[] = {
131 { 1000, 2, {20, 0.30, 850}, {0, 0, 0 }},
132 { 2350, 3, {25, 0.33, 850}, {15, 0.20, 300}},
133 { 5000, 4, {30, 0.48, 850}, {20, 0.35, 300}},
134 {10000, 5, {40, 0.75, 850}, {25, 0.55, 350}},
135 {20000, 6, {45, 1.00, 850}, {30, 0.80, 400}},
136 {42000, 7, {48, 1.30, 850}, {35, 1.10, 600}},
137 {0, 8, {48, 1.30, 1200}, {35, 1.10, 700}}
138};
139
141// helpers ////////////////////////////////////////////////////////////////////
143
144#define ADD_VOICE(Var, Sample, SayTwice) \
145 { make_voice(&Var); \
146 append(Var, Sample); \
147 if (SayTwice) append(Var, Sample); }
148
149#define AVAILABLE_RA(Options, Advisory) (Advisory == (Advisory & Options))
150
151#ifdef FEATURE_TCAS_DEBUG_THREAT_DETECTOR
153static float
154relAngle(float Heading1, float Heading2)
155{
156 Heading1 -= Heading2;
157
158 while (Heading1 >= 360.0)
159 Heading1 -= 360.0;
160
161 while (Heading1 < 0.0)
162 Heading1 += 360;
163
164 return Heading1;
165}
166#endif
167
169static void
170calcRangeBearing(double lat1, double lon1, double lat2, double lon2,
171 double &rangeNm, double &bearing)
172{
173 // calculate the bearing and range of the second pos from the first
174 double az2, distanceM;
175 geo_inverse_wgs_84(lat1, lon1, lat2, lon2, &bearing, &az2, &distanceM);
176 rangeNm = distanceM * SG_METER_TO_NM;
177}
178
180// VoicePlayer ////////////////////////////////////////////////////////////////
182
183void
184TCAS::VoicePlayer::init(void)
185{
187
188 ADD_VOICE(Voices.pTrafficTraffic, "traffic", true);
189 ADD_VOICE(Voices.pClear, "clear", false);
190 ADD_VOICE(Voices.pClimb, "climb", true);
191 ADD_VOICE(Voices.pClimbNow, "climb_now", true);
192 ADD_VOICE(Voices.pClimbCrossing, "climb_crossing", true);
193 ADD_VOICE(Voices.pClimbIncrease, "increase_climb", false);
194 ADD_VOICE(Voices.pDescend, "descend", true);
195 ADD_VOICE(Voices.pDescendNow, "descend_now", true);
196 ADD_VOICE(Voices.pDescendCrossing,"descend_crossing", true);
197 ADD_VOICE(Voices.pDescendIncrease,"increase_descent", false);
198 ADD_VOICE(Voices.pAdjustVSpeed, "adjust_vertical_speed", false);
199 ADD_VOICE(Voices.pMaintVSpeed, "maintain_vertical_speed", false);
200 ADD_VOICE(Voices.pMonitorVSpeed, "monitor_vertical_speed", false);
201 ADD_VOICE(Voices.pLevelOff, "level_off", false);
202 ADD_VOICE(Voices.pTestOk, "test_ok", false);
203 ADD_VOICE(Voices.pTestFail, "test_fail", false);
204
205 speaker.update_configuration();
206}
207
209// TCAS::Annunciator ////////////////////////////////////////////////////////
211
212TCAS::Annunciator::Annunciator(TCAS* _tcas) :
213 tcas(_tcas),
214 pLastVoice(NULL),
215 voicePlayer(_tcas)
216{
217 clear();
218}
219
220void TCAS::Annunciator::clear(void)
221{
222 previous.threatLevel = ThreatNone;
223 previous.RA = AdvisoryClear;
224 previous.RAOption = OptionNone;
225 pLastVoice = NULL;
226}
227
228void
229TCAS::Annunciator::bind(SGPropertyNode* node)
230{
231 voicePlayer.bind(node, "Sounds/tcas/female/");
232}
233
234void
235TCAS::Annunciator::init(void)
236{
237 //TODO link to GPWS module/audio-on signal must be configurable
238 nodeGpwsAlertOn = fgGetNode("/instrumentation/mk-viii/outputs/discretes/audio-on", true);
239 voicePlayer.init();
240}
241
242void
243TCAS::Annunciator::update(void)
244{
245 voicePlayer.update();
246
247 /* [TCASII]: "The priority scheme gives ground proximity warning systems (GPWS)
248 * a higher annunciation priority than a TCAS alert. TCAS aural annunciation will
249 * be inhibited during the time that a GPWS alert is active." */
250 if (nodeGpwsAlertOn->getBoolValue())
251 voicePlayer.pause();
252 else
253 voicePlayer.resume();
254}
255
257void
258TCAS::Annunciator::trigger(const ResolutionAdvisory& current, bool revertedRA)
259{
260 int RA = current.RA;
261 int RAOption = current.RAOption;
262
263 if (RA == AdvisoryClear)
264 {
265 if (previous.RA != AdvisoryClear)
266 {
267 voicePlayer.play(voicePlayer.Voices.pClear, VoicePlayer::PLAY_NOW);
268 previous = current;
269 }
270 pLastVoice = NULL;
271 return;
272 }
273
274 if ((previous.RA == AdvisoryClear)||
275 (tcas->tracker.newTraffic()))
276 {
277 voicePlayer.play(voicePlayer.Voices.pTrafficTraffic, VoicePlayer::PLAY_NOW);
278 }
279
280 // pick voice sample
281 VoicePlayer::Voice* pVoice = NULL;
282 switch(RA)
283 {
284 case AdvisoryClimb:
285 if (revertedRA)
286 pVoice = voicePlayer.Voices.pClimbNow;
287 else
288 if (AVAILABLE_RA(RAOption, OptionIncreaseClimb))
289 pVoice = voicePlayer.Voices.pClimbIncrease;
290 else
291 if (AVAILABLE_RA(RAOption, OptionCrossingClimb))
292 pVoice = voicePlayer.Voices.pClimbCrossing;
293 else
294 pVoice = voicePlayer.Voices.pClimb;
295 break;
296
297 case AdvisoryDescend:
298 if (revertedRA)
299 pVoice = voicePlayer.Voices.pDescendNow;
300 else
301 if (AVAILABLE_RA(RAOption, OptionIncreaseDescend))
302 pVoice = voicePlayer.Voices.pDescendIncrease;
303 else
304 if (AVAILABLE_RA(RAOption, OptionCrossingDescent))
305 pVoice = voicePlayer.Voices.pDescendCrossing;
306 else
307 pVoice = voicePlayer.Voices.pDescend;
308 break;
309
310 case AdvisoryAdjustVSpeed:
311 pVoice = voicePlayer.Voices.pAdjustVSpeed;
312 break;
313
314 case AdvisoryMaintVSpeed:
315 pVoice = voicePlayer.Voices.pMaintVSpeed;
316 break;
317
318 case AdvisoryMonitorVSpeed:
319 pVoice = voicePlayer.Voices.pMonitorVSpeed;
320 break;
321
322 case AdvisoryLevelOff:
323 pVoice = voicePlayer.Voices.pLevelOff;
324 break;
325
326 case AdvisoryIntrusion:
327 break;
328
329 default:
330 RA = AdvisoryIntrusion;
331 break;
332 }
333
334 previous = current;
335
336 if ((pLastVoice == pVoice)&&
337 (!tcas->tracker.newTraffic()))
338 {
339 // don't repeat annunciation
340 return;
341 }
342 pLastVoice = pVoice;
343 if (pVoice)
344 voicePlayer.play(pVoice);
345
346#ifdef FEATURE_TCAS_DEBUG_ANNUNCIATOR
347 cout << "Annunciating TCAS RA " << RA << endl;
348#endif
349}
350
351void
352TCAS::Annunciator::test(bool testOk)
353{
354 if (testOk)
355 voicePlayer.play(voicePlayer.Voices.pTestOk);
356 else
357 voicePlayer.play(voicePlayer.Voices.pTestFail);
358}
359
361// TCAS::AdvisoryCoordinator ////////////////////////////////////////////////
363
364TCAS::AdvisoryCoordinator::AdvisoryCoordinator(TCAS* _tcas) :
365 tcas(_tcas),
366 lastTATime(0)
367{
368 init();
369}
370
371void
372TCAS::AdvisoryCoordinator::init(void)
373{
374 reinit();
375}
376
377void
378TCAS::AdvisoryCoordinator::reinit(void)
379{
380 clear();
381 previous = current;
382}
383
384void
385TCAS::AdvisoryCoordinator::bind(SGPropertyNode* node)
386{
387 nodeTAWarning = node->getNode("outputs/traffic-alert", true);
388 nodeTAWarning->setBoolValue(false);
389}
390
391void
392TCAS::AdvisoryCoordinator::clear(void)
393{
394 current.threatLevel = ThreatNone;
395 current.RA = AdvisoryClear;
396 current.RAOption = OptionNone;
397}
398
400void
401TCAS::AdvisoryCoordinator::add(const ResolutionAdvisory& newAdvisory)
402{
403 if ((newAdvisory.RA == AdvisoryClear)||
404 (newAdvisory.threatLevel < current.threatLevel))
405 return;
406
407 if (current.threatLevel == newAdvisory.threatLevel)
408 {
409 // combine with other advisories so far
410 current.RA &= newAdvisory.RA;
411 // remember any advisory modifier
412 current.RAOption |= newAdvisory.RAOption;
413 }
414 else
415 {
416 current = newAdvisory;
417 }
418}
419
421void
422TCAS::AdvisoryCoordinator::update(int mode)
423{
424 bool revertedRA = false; // has advisory changed?
425 double currentTime = globals->get_sim_time_sec();
426
427 if (current.RA == AdvisoryClear)
428 {
429 // filter: wait 5 seconds after last TA/RA before announcing TA clearance
430 if ((previous.RA != AdvisoryClear)&&
431 (currentTime - lastTATime < 5.0))
432 return;
433 }
434 else
435 {
436 // intruder detected
437
438#ifdef FEATURE_TCAS_DEBUG_COORDINATOR
439 cout << "TCAS::Annunciator::update: previous: " << previous.RA << ", new: " << current.RA << endl;
440#endif
441
442 lastTATime = currentTime;
443 if ((previous.RA == AdvisoryClear)||
444 (previous.RA == AdvisoryIntrusion)||
445 ((current.RA & previous.RA) != previous.RA))
446 {
447 // no RA yet, or we can't keep previous RA: pick one - in order of priority
448
449 if (AVAILABLE_RA(current.RA, AdvisoryMonitorVSpeed))
450 {
451 // prio 1: monitor vertical speed only
452 current.RA = AdvisoryMonitorVSpeed;
453 }
454 else
455 if (AVAILABLE_RA(current.RA, AdvisoryMaintVSpeed))
456 {
457 // prio 2: maintain vertical speed
458 current.RA = AdvisoryMaintVSpeed;
459 }
460 else
461 if (AVAILABLE_RA(current.RA, AdvisoryAdjustVSpeed))
462 {
463 // prio 3: adjust vertical speed (TCAS II 7.0 only)
464 current.RA = AdvisoryAdjustVSpeed;
465 }
466 else
467 if (AVAILABLE_RA(current.RA, AdvisoryLevelOff))
468 {
469 // prio 3: adjust vertical speed (TCAS II 7.1 only, [EUROACAS]: CP115)
470 current.RA = AdvisoryLevelOff;
471 }
472 else
473 if (AVAILABLE_RA(current.RA, AdvisoryClimb))
474 {
475 // prio 4: climb
476 current.RA = AdvisoryClimb;
477 }
478 else
479 if (AVAILABLE_RA(current.RA, AdvisoryDescend))
480 {
481 // prio 5: descend
482 current.RA = AdvisoryDescend;
483 }
484 else
485 {
486 // no RA, issue a TA only
487 current.RA = AdvisoryIntrusion;
488 }
489
490 // check if earlier advisory was reverted
491 revertedRA = ((previous.RA != current.RA)&&
492 (previous.RA != 0)&&
493 (previous.RA != AdvisoryIntrusion));
494 }
495 else
496 {
497 // keep earlier RA
498 current.RA = previous.RA;
499 }
500 }
501
502 /* [TCASII]: "Aural annunciations are inhibited below 500+/-100 feet AGL." */
503 if ((tcas->threatDetector.getRadarAlt() > tcas->_TAInhbAlt) &&
504 (mode >= SwitchTaOnly))
505 tcas->annunciator.trigger(current, revertedRA);
506 else
507 if (current.RA == AdvisoryClear)
508 {
509 /* explicitly clear traffic alert (since aural annunciation disabled) */
510 tcas->annunciator.clear();
511 }
512
513 previous = current;
514
515 /* [TCASII] "[..] also performs the function of setting flags that control the displays.
516 * The traffic display, the RA display, [..] use these flags to alert the pilot to
517 * the presence of TAs and RAs." */
518 nodeTAWarning->setBoolValue(current.RA != AdvisoryClear);
519}
520
522// TCAS::ThreatDetector ///////////////////////////////////////////////////////
524
525TCAS::ThreatDetector::ThreatDetector(TCAS* _tcas) :
526 tcas(_tcas),
527 pAlarmThresholds(&sensitivityLevels[0])
528{
529#ifdef FEATURE_TCAS_DEBUG_THREAT_DETECTOR
530 checkCount = 0;
531#endif
532 self.radarAltFt = 0.0;
533 unitTest();
534}
535
536void
537TCAS::ThreatDetector::init(void)
538{
539 nodeLat = fgGetNode("/position/latitude-deg", true);
540 nodeLon = fgGetNode("/position/longitude-deg", true);
541 nodePressureAlt = fgGetNode("/position/altitude-ft", true);
542 nodeRadarAlt = fgGetNode("/position/altitude-agl-ft", true);
543 nodeHeading = fgGetNode("/orientation/heading-deg", true);
544 nodeVelocity = fgGetNode("/velocities/airspeed-kt", true);
545 nodeVerticalFps = fgGetNode("/velocities/vertical-speed-fps", true);
546
547 tcas->advisoryGenerator.init(&self,&currentThreat);
548}
549
551void
552TCAS::ThreatDetector::update(void)
553{
554 // update local position
555 self.lat = nodeLat->getDoubleValue();
556 self.lon = nodeLon->getDoubleValue();
557 self.pressureAltFt = nodePressureAlt->getDoubleValue();
558 self.heading = nodeHeading->getDoubleValue();
559 self.velocityKt = nodeVelocity->getDoubleValue();
560 self.verticalFps = nodeVerticalFps->getDoubleValue();
561
562 /* radar altimeter provides a lot of spikes due to uneven terrain
563 * MK-VIII GPWS-spec requires smoothing the radar altitude with a
564 * 10second moving average. Likely the TCAS spec requires the same.
565 * => We use a cheap 10 second exponential average method.
566 */
567 const double SmoothingFactor = 0.3;
568 self.radarAltFt = nodeRadarAlt->getDoubleValue()*SmoothingFactor +
569 (1-SmoothingFactor)*self.radarAltFt;
570
571#ifdef FEATURE_TCAS_DEBUG_THREAT_DETECTOR
572 printf("TCAS::ThreatDetector::update: radarAlt = %f\n",self.radarAltFt);
573 checkCount = 0;
574#endif
575
576 // determine current altitude's "Sensitivity Level Definition and Alarm Thresholds"
577 int sl=0;
578 for (sl=0;((self.radarAltFt > sensitivityLevels[sl].maxAltitude)&&
579 (sensitivityLevels[sl].maxAltitude));sl++);
580 pAlarmThresholds = &sensitivityLevels[sl];
581 tcas->advisoryGenerator.setAlarmThresholds(pAlarmThresholds);
582}
583
584// If plane's transponder is enabled, return true with o_altFt set to
585// altitude. Otherwise return false.
586//
587static bool checkTransponderLocal(const SGPropertyNode* pModel, float velocityKt, float& o_altFt)
588{
589 if (pModel->getBoolValue("controls/invisible", false /*default*/))
590 {
591 // For MP aircraft (name='multiplayer') that are being ignored.
592 return false;
593 }
594 if (pModel->getNameString() == "swift")
595 {
596 /* Transponder info is in ./swift/transponder/ but altitude needs to
597 come from ./position/altitude-ft. */
598 bool xpdr_on = pModel->getBoolValue("swift/transponder/c-mode", false);
599 if (!xpdr_on) return false;
600 o_altFt = pModel->getDoubleValue("position/altitude-ft");
601 return true;
602 }
603 else if (pModel->getNameString() == "aircraft")
604 {
605 /* assume all non-MP and non-Swift (i.e. AI) aircraft have their transponder switched off while taxiing/parking
606 * (at low speed) */
607 if (velocityKt < 40.0) return false;
608 o_altFt = pModel->getDoubleValue("position/altitude-ft");
609 return true;
610 }
611 o_altFt = pModel->getIntValue("instrumentation/transponder/altitude", -9999);
612 // must have Mode C (altitude) transponder to be visible.
613 // "-9999" is a special value used by src/Instrumentation/transponder.cxx to indicate the non-transmission of a value.
614 return (o_altFt != -9999);
615}
616
618bool
619TCAS::ThreatDetector::checkTransponder(const SGPropertyNode* pModel, float velocityKt)
620{
621 float altFt;
622 return checkTransponderLocal(pModel, velocityKt, altFt);
623}
624
626int
627TCAS::ThreatDetector::checkThreat(int mode, const SGPropertyNode* pModel)
628{
629#ifdef FEATURE_TCAS_DEBUG_THREAT_DETECTOR
630 checkCount++;
631#endif
632 if (!pModel->getBoolValue("valid", true))
633 return ThreatInvisible;
634
635 float velocityKt = pModel->getDoubleValue("velocities/true-airspeed-kt");
636
637 float altFt;
638 if (!checkTransponderLocal(pModel, velocityKt, altFt))
639 return ThreatInvisible;
640
641 int threatLevel = ThreatNone;
642 currentThreat.relativeAltitudeFt = altFt - self.pressureAltFt;
643
644 // save computation time: don't care when relative altitude is excessive
645 if (fabs(currentThreat.relativeAltitudeFt) > tcas->_verticalRange)
646 return threatLevel;
647
648 // position data of current intruder
649 double lat = pModel->getDoubleValue("position/latitude-deg");
650 double lon = pModel->getDoubleValue("position/longitude-deg");
651 float heading = pModel->getDoubleValue("orientation/true-heading-deg");
652
653 double distanceNm, bearing;
654 calcRangeBearing(self.lat, self.lon, lat, lon, distanceNm, bearing);
655
656 // save computation time: don't care for excessive distances (also captures NaNs...)
657 if ((distanceNm > tcas->_lateralRange) || (distanceNm < 0))
658 return threatLevel;
659
660 currentThreat.verticalFps = pModel->getDoubleValue("velocities/vertical-speed-fps");
661
662 /* Detect proximity targets
663 * [TCASII]: "Any target that is less than 6 nmi in range and within +/-1200ft
664 * vertically, but that does not meet the intruder or threat criteria." */
665 if ((distanceNm < tcas->_proxLatRange) &&
666 (fabs(currentThreat.relativeAltitudeFt) < tcas->_proxVertRange))
667 {
668 // at least a proximity target
669 threatLevel = ThreatProximity;
670 }
671
672 /* do not detect any threats when in standby or on ground and taxiing */
673 if ((mode <= SwitchStandby)||
674 ((self.radarAltFt < 360)&&(self.velocityKt < 40)))
675 {
676 return threatLevel;
677 }
678
679 if (tcas->tracker.active())
680 {
681 currentThreat.callsign = pModel->getStringValue("callsign");
682 currentThreat.isTracked = tcas->tracker.isTracked(currentThreat.callsign);
683 }
684 else
685 currentThreat.isTracked = false;
686
687 // first stage: vertical movement
688 checkVerticalThreat();
689
690 // stop processing when no vertical threat
691 if ((!currentThreat.verticalTA)&&
692 (!currentThreat.isTracked))
693 return threatLevel;
694
695 // second stage: horizontal movement
696 horizontalThreat(bearing, distanceNm, heading, velocityKt);
697
698 if (!currentThreat.isTracked)
699 {
700 // no horizontal threat?
701 if (!currentThreat.horizontalTA)
702 return threatLevel;
703
704 if ((currentThreat.horizontalTau < 0)||
705 (currentThreat.verticalTau < 0))
706 {
707 // do not trigger new alerts when Tau is negative, but keep existing alerts
708 int previousThreatLevel = pModel->getIntValue("tcas/threat-level", 0);
709 if (previousThreatLevel == 0)
710 return threatLevel;
711 }
712 }
713
714#ifdef FEATURE_TCAS_DEBUG_THREAT_DETECTOR
715 cout << "#" << checkCount << ": " << pModel->getStringValue("callsign") << endl;
716#endif
717
718
719 /* [TCASII]: "For either a TA or an RA to be issued, both the range and
720 * vertical criteria, in terms of tau or the fixed thresholds, must be
721 * satisfied only one of the criteria is satisfied, TCAS will not issue
722 * an advisory." */
723 if (currentThreat.horizontalTA && currentThreat.verticalTA)
724 threatLevel = ThreatTA;
725 if (currentThreat.horizontalRA && currentThreat.verticalRA)
726 threatLevel = ThreatRA;
727
728 if (!tcas->tracker.active())
729 currentThreat.callsign = pModel->getStringValue("callsign");
730
731 tcas->tracker.add(currentThreat.callsign, threatLevel);
732
733 // check existing threat level
734 if (currentThreat.isTracked)
735 {
736 int oldLevel = tcas->tracker.getThreatLevel(currentThreat.callsign);
737 if (oldLevel > threatLevel)
738 threatLevel = oldLevel;
739 }
740
741 // find all resolution options for this conflict
742 threatLevel = tcas->advisoryGenerator.resolution(mode, threatLevel, distanceNm, altFt, heading, velocityKt);
743
744
745#ifdef FEATURE_TCAS_DEBUG_THREAT_DETECTOR
746 printf(" threat: distance: %4.1f, bearing: %4.1f, alt: %5.1f, velocity: %4.1f, heading: %4.1f, vspeed: %4.1f, "
747 "own alt: %5.1f, own heading: %4.1f, own velocity: %4.1f, vertical tau: %3.2f"
748 //", closing speed: %f"
749 "\n",
750 distanceNm, relAngle(bearing, self.heading), altFt, velocityKt, heading, currentThreat.verticalFps,
751 self.altFt, self.heading, self.velocityKt
752 //, currentThreat.closingSpeedKt
753 ,currentThreat.verticalTau
754 );
755#endif
756
757 return threatLevel;
758}
759
761void
762TCAS::ThreatDetector::checkVerticalThreat(void)
763{
764 // calculate relative vertical speed and altitude
765 float dV = self.verticalFps - currentThreat.verticalFps;
766 float dA = currentThreat.relativeAltitudeFt;
767
768 currentThreat.verticalTA = false;
769 currentThreat.verticalRA = false;
770 currentThreat.verticalTau = 0;
771
772 /* [TCASII]: "The vertical tau is equal to the altitude separation (feet)
773 * divided by the combined vertical speed of the two aircraft (feet/minute)
774 * times 60." */
775 float tau = 0;
776 if (fabs(dV) > 0.1)
777 tau = dA/dV;
778
779 /* [TCASII]: "When the combined vertical speed of the TCAS and the intruder aircraft
780 * is low, TCAS will use a fixed-altitude threshold to determine whether a TA or
781 * an RA should be issued." */
782 if ((fabs(dV) < 3.0)||
783 ((tau < 0) && (tau > -5)))
784 {
785 /* vertical closing speed is low (below 180fpm/3fps), check
786 * fixed altitude range. */
787 float abs_dA = fabs(dA);
788 if (abs_dA < pAlarmThresholds->RA.ALIM)
789 {
790 // continuous intrusion at RA-level
791 currentThreat.verticalTA = true;
792 currentThreat.verticalRA = true;
793 }
794 else
795 if (abs_dA < pAlarmThresholds->TA.ALIM)
796 {
797 // continuous intrusion: with TA-level, but no RA-threat
798 currentThreat.verticalTA = true;
799 }
800 // else: no RA/TA threat
801 }
802 else
803 {
804 if ((tau < pAlarmThresholds->TA.Tau)&&
805 (tau >= -5))
806 {
807 currentThreat.verticalTA = true;
808 currentThreat.verticalRA = (tau < pAlarmThresholds->RA.Tau);
809 }
810 }
811 currentThreat.verticalTau = tau;
812
813#ifdef FEATURE_TCAS_DEBUG_THREAT_DETECTOR
814 if (currentThreat.verticalTA)
815 printf(" vertical dV=%f (%f-%f), dA=%f\n", dV, self.verticalFps, currentThreat.verticalFps, dA);
816#endif
817}
818
820void
821TCAS::ThreatDetector::horizontalThreat(float bearing, float distanceNm, float heading, float velocityKt)
822{
823 // calculate speed
824 float vxKt = sin(heading*SGD_DEGREES_TO_RADIANS)*velocityKt - sin(self.heading*SGD_DEGREES_TO_RADIANS)*self.velocityKt;
825 float vyKt = cos(heading*SGD_DEGREES_TO_RADIANS)*velocityKt - cos(self.heading*SGD_DEGREES_TO_RADIANS)*self.velocityKt;
826
827 // calculate horizontal closing speed
828 float closingSpeedKt2 = vxKt*vxKt+vyKt*vyKt;
829 float closingSpeedKt = sqrt(closingSpeedKt2);
830
831 /* [TCASII]: "The range tau is equal to the slant range (nmi) divided by the closing speed
832 * (knots) multiplied by 3600."
833 * => calculate allowed slant range (nmi) based on known maximum tau */
834 float TA_rangeNm = (pAlarmThresholds->TA.Tau*closingSpeedKt)/3600;
835 float RA_rangeNm = (pAlarmThresholds->RA.Tau*closingSpeedKt)/3600;
836
837 if (closingSpeedKt < 100)
838 {
839 /* [TCASII]: "In events where the rate of closure is very low, [..]
840 * an intruder aircraft can come very close in range without crossing the
841 * range tau boundaries [..]. To provide protection in these types of
842 * advisories, the range tau boundaries are modified [..] to use
843 * a fixed-range threshold to issue TAs and RAs in these slow closure
844 * encounters." */
845 TA_rangeNm += (100.0-closingSpeedKt)*(pAlarmThresholds->TA.DMOD/100.0);
846 RA_rangeNm += (100.0-closingSpeedKt)*(pAlarmThresholds->RA.DMOD/100.0);
847 }
848 if (TA_rangeNm < pAlarmThresholds->TA.DMOD)
849 TA_rangeNm = pAlarmThresholds->TA.DMOD;
850 if (RA_rangeNm < pAlarmThresholds->RA.DMOD)
851 RA_rangeNm = pAlarmThresholds->RA.DMOD;
852
853 currentThreat.horizontalTA = (distanceNm < TA_rangeNm);
854 currentThreat.horizontalRA = (distanceNm < RA_rangeNm);
855 currentThreat.horizontalTau = -1;
856
857 if ((currentThreat.horizontalRA)&&
858 (currentThreat.verticalRA))
859 {
860 /* an RA will be issued. Prepare extra data for the
861 * traffic resolution stage, i.e. calculate
862 * exact time tau to horizontal CPA.
863 */
864
865 /* relative position of intruder is
866 * Sx(t) = sx + vx*t
867 * Sy(t) = sy + vy*t
868 * horizontal distance to intruder is r(t)
869 * r(t) = sqrt( Sx(t)^2 + Sy(t)^2 )
870 * => horizontal CPA at time t=tau, where r(t) has minimum
871 * r2(t) := r^2(t) = Sx(t)^2 + Sy(t)^2
872 * since r(t)>0 for all t => minimum of r(t) is also minimum of r2(t)
873 * => (d/dt) r2(t) = r2'(t) is 0 for t=tau
874 * r2(t) = ((Sx(t)^2 + Sy(t))^2) = c + b*t + a*t^2
875 * => r2'(t) = b + a*2*t
876 * at t=tau:
877 * r2'(tau) = 0 = b + 2*a*tau
878 * => tau = -b/(2*a)
879 */
880 float sx = sin(bearing*SGD_DEGREES_TO_RADIANS)*distanceNm;
881 float sy = cos(bearing*SGD_DEGREES_TO_RADIANS)*distanceNm;
882 float vx = vxKt * (SG_KT_TO_MPS*SG_METER_TO_NM);
883 float vy = vyKt * (SG_KT_TO_MPS*SG_METER_TO_NM);
884 float a = vx*vx + vy*vy;
885 float b = 2*(sx*vx + sy*vy);
886 float tau = 0;
887 if (a > 0.0001)
888 tau = -b/(2*a);
889#ifdef FEATURE_TCAS_DEBUG_THREAT_DETECTOR
890 printf(" Time to horizontal CPA: %4.2f\n",tau);
891#endif
892 if (tau > pAlarmThresholds->RA.Tau)
893 tau = pAlarmThresholds->RA.Tau;
894
895 // remember time to horizontal CPA
896 currentThreat.horizontalTau = tau;
897 }
898}
899
901void
902TCAS::ThreatDetector::unitTest(void)
903{
904 pAlarmThresholds = &sensitivityLevels[1];
905#if 0
906 // vertical tests
907 self.verticalFps = 0;
908 self.altFt = 1000;
909 cout << "identical altitude and vspeed " << endl;
910 checkVerticalThreat(self.altFt, self.verticalFps);
911 cout << "1000ft alt offset, dV=100 " << endl;
912 checkVerticalThreat(self.altFt+1000, 100);
913 cout << "-1000ft alt offset, dV=100 " << endl;
914 checkVerticalThreat(self.altFt-1000, 100);
915 cout << "3000ft alt offset, dV=10 " << endl;
916 checkVerticalThreat(self.altFt+3000, 10);
917 cout << "500ft alt offset, dV=100 " << endl;
918 checkVerticalThreat(self.altFt+500, 100);
919 cout << "500ft alt offset, dV=-100 " << endl;
920 checkVerticalThreat(self.altFt+500, -100);
921
922 // horizontal tests
923 self.heading = 0;
924 self.velocityKt = 0;
925 cout << "10nm behind, overtaking with 1Nm/s" << endl;
926 horizontalThreat(-180, 10, 0, 1/(SG_KT_TO_MPS*SG_METER_TO_NM));
927
928 cout << "10nm ahead, departing with 1Nm/s" << endl;
929 horizontalThreat(0, 20, 0, 1/(SG_KT_TO_MPS*SG_METER_TO_NM));
930
931 self.heading = 90;
932 self.velocityKt = 1/(SG_KT_TO_MPS*SG_METER_TO_NM);
933 cout << "10nm behind, overtaking with 1Nm/s at 90 degrees" << endl;
934 horizontalThreat(-90, 20, 90, 2/(SG_KT_TO_MPS*SG_METER_TO_NM));
935
936 self.heading = 20;
937 self.velocityKt = 1/(SG_KT_TO_MPS*SG_METER_TO_NM);
938 cout << "10nm behind, overtaking with 1Nm/s at 20 degrees" << endl;
939 horizontalThreat(200, 20, 20, 2/(SG_KT_TO_MPS*SG_METER_TO_NM));
940#endif
941}
942
944// TCAS::AdvisoryGenerator ////////////////////////////////////////////////////
946
947TCAS::AdvisoryGenerator::AdvisoryGenerator(TCAS* _tcas) :
948 tcas(_tcas),
949 pSelf(NULL),
950 pCurrentThreat(NULL),
951 pAlarmThresholds(NULL)
952{
953}
954
955void
956TCAS::AdvisoryGenerator::init(const LocalInfo* _pSelf, ThreatInfo* _pCurrentThreat)
957{
958 pCurrentThreat = _pCurrentThreat;
959 pSelf = _pSelf;
960}
961
962void
963TCAS::AdvisoryGenerator::setAlarmThresholds(const SensitivityLevel* _pAlarmThresholds)
964{
965 pAlarmThresholds = _pAlarmThresholds;
966}
967
969float
970TCAS::AdvisoryGenerator::verticalSeparation(float newVerticalFps)
971{
972 // calculate relative vertical speed and altitude
973 float dV = pCurrentThreat->verticalFps - newVerticalFps;
974 float tau = pCurrentThreat->horizontalTau;
975 // don't use negative tau to project future separation...
976 if (tau < 0.5)
977 tau = 0.5;
978 return pCurrentThreat->relativeAltitudeFt + tau * dV;
979}
980
982void
983TCAS::AdvisoryGenerator::determineRAsense(int& RASense, bool& isCrossing)
984{
985 /* [TCASII]: "[..] a two step process is used to select the appropriate RA for the encounter
986 * geometry. The first step in the process is to select the RA sense, i.e., upward or downward." */
987 RASense = 0;
988 isCrossing = false;
989
990 /* [TCASII]: "Based on the range and altitude tracks of the intruder, the CAS logic models the
991 * intruder's flight path from its present position to CPA. The CAS logic then models upward
992 * and downward sense RAs for own aircraft [..] to determine which sense provides the most
993 * vertical separation at CPA." */
994 float upSenseRelAltFt = verticalSeparation(+2000/60.0);
995 float downSenseRelAltFt = verticalSeparation(-2000/60.0);
996 if (fabs(upSenseRelAltFt) >= fabs(downSenseRelAltFt))
997 RASense = +1; // upward
998 else
999 RASense = -1; // downward
1000
1001 /* [TCASII]: "In encounters where either of the senses results in the TCAS aircraft crossing through
1002 * the intruder's altitude, TCAS is designed to select the nonaltitude crossing sense if the
1003 * noncrossing sense provides the desired vertical separation, known as ALIM, at CPA." */
1004 /* [TCASII]: "If ALIM cannot be obtained in the nonaltitude crossing sense, an altitude
1005 * crossing RA will be issued." */
1006 if ((RASense > 0)&&
1007 (pCurrentThreat->relativeAltitudeFt > 200))
1008 {
1009 // threat is above and RA is crossing
1010 if (fabs(downSenseRelAltFt) > pAlarmThresholds->TA.ALIM)
1011 {
1012 // non-crossing descend is sufficient
1013 RASense = -1;
1014 }
1015 else
1016 {
1017 // keep crossing climb RA
1018 isCrossing = true;
1019 }
1020 }
1021 else
1022 if ((RASense < 0)&&
1023 (pCurrentThreat->relativeAltitudeFt < -200))
1024 {
1025 // threat is below and RA is crossing
1026 if (fabs(upSenseRelAltFt) > pAlarmThresholds->TA.ALIM)
1027 {
1028 // non-crossing climb is sufficient
1029 RASense = 1;
1030 }
1031 else
1032 {
1033 // keep crossing descent RA
1034 isCrossing = true;
1035 }
1036 }
1037 // else: threat is at same altitude, keep optimal RA sense (non-crossing)
1038
1039 pCurrentThreat->RASense = RASense;
1040
1041#ifdef FEATURE_TCAS_DEBUG_ADV_GENERATOR
1042 printf(" RASense: %i, crossing: %u, relAlt: %4.1f, upward separation: %4.1f, downward separation: %4.1f\n",
1043 RASense,isCrossing,
1044 pCurrentThreat->relativeAltitudeFt,
1045 upSenseRelAltFt,downSenseRelAltFt);
1046#endif
1047}
1048
1050int
1051TCAS::AdvisoryGenerator::resolution(int mode, int threatLevel, float rangeNm, float altFt,
1052 float heading, float velocityKt)
1053{
1054 int RAOption = OptionNone;
1055 int RA = AdvisoryIntrusion;
1056
1057 // RAs are disabled under certain conditions
1058 if (threatLevel == ThreatRA)
1059 {
1060 /* [TCASII]: "... less than 360 feet, TCAS considers the reporting aircraft
1061 * to be on the ground. If TCAS determines the intruder to be on the ground, it
1062 * New custom logic for the A320 - if enabled explicitly this will only occur if your altitude is less than 1700 */
1063 if ((altFt < tcas->_intruderInhbAlt) && (!tcas->_intruderInhbSelfAltToggle || pSelf->radarAltFt < tcas->_intruderInhbSelfAlt))
1064 threatLevel = ThreatTA;
1065
1066 /* [EUROACAS]: "Certain RAs are inhibited at altitudes based on inputs from the radio altimeter:
1067 * [..] (c)1000ft (+/- 100ft) and below, all RAs are inhibited;" */
1068 if (pSelf->radarAltFt < tcas->_RAInhbAlt)
1069 threatLevel = ThreatTA;
1070
1071 // RAs only issued in mode "Auto" (= "TA/RA" mode)
1072 if (mode != SwitchAuto)
1073 threatLevel = ThreatTA;
1074 }
1075
1076 bool isCrossing = false;
1077 int RASense = 0;
1078 // determine suitable RAs
1079 if (threatLevel == ThreatRA)
1080 {
1081 /* [TCASII]: "[..] a two step process is used to select the appropriate RA for the encounter
1082 * geometry. The first step in the process is to select the RA sense, i.e., upward or downward." */
1083 determineRAsense(RASense, isCrossing);
1084
1085 /* second step: determine required strength */
1086 if (RASense > 0)
1087 {
1088 // upward
1089
1090 if ((pSelf->verticalFps < -1000/60.0)&&
1091 (!isCrossing))
1092 {
1093 // currently descending, see if reducing current descent is sufficient
1094 float relAltFt = verticalSeparation(-500/60.0);
1095 if (relAltFt > pAlarmThresholds->TA.ALIM)
1096 RA |= AdvisoryAdjustVSpeed;
1097 }
1098 RA |= AdvisoryClimb;
1099 if (isCrossing)
1100 RAOption |= OptionCrossingClimb;
1101 }
1102
1103 if (RASense < 0)
1104 {
1105 // downward
1106
1107 if ((pSelf->verticalFps > 1000/60.0)&&
1108 (!isCrossing))
1109 {
1110 // currently climbing, see if reducing current climb is sufficient
1111 float relAltFt = verticalSeparation(500/60.0);
1112 if (relAltFt < -pAlarmThresholds->TA.ALIM)
1113 RA |= AdvisoryAdjustVSpeed;
1114 }
1115 RA |= AdvisoryDescend;
1116 if (isCrossing)
1117 RAOption |= OptionCrossingDescent;
1118 }
1119
1120 //TODO
1121 /* [TCASII]: "When two TCAS-equipped aircraft are converging vertically with opposite rates
1122 * and are currently well separated in altitude, TCAS will first issue a vertical speed
1123 * limit (Negative) RA to reinforce the pilots' likely intention to level off at adjacent
1124 * flight levels." */
1125
1126 //TODO
1127 /* [TCASII]: "[..] if the CAS logic determines that the response to a Positive RA has provided
1128 * ALIM feet of vertical separation before CPA, the initial RA will be weakened to either a
1129 * Do Not Descend RA (after an initial Climb RA) or a Do Not Climb RA (after an initial
1130 * Descend RA)." */
1131
1132 //TODO
1133 /* [TCASII]: "TCAS is designed to inhibit Increase Descent RAs below 1450 feet AGL; */
1134
1135 /* [TCASII]: "Descend RAs below 1100 feet AGL;" (inhibited) */
1136 if (pSelf->radarAltFt < tcas->_DesInhbAlt)
1137 {
1138 RA &= ~AdvisoryDescend;
1139 //TODO Support "Do not descend" RA
1140 RA |= AdvisoryIntrusion;
1141 }
1142 }
1143
1144#ifdef FEATURE_TCAS_DEBUG_ADV_GENERATOR
1145 cout << " resolution advisory: " << RA << endl;
1146#endif
1147
1148 ResolutionAdvisory newAdvisory;
1149 newAdvisory.RAOption = RAOption;
1150 newAdvisory.RA = RA;
1151 newAdvisory.threatLevel = threatLevel;
1152 tcas->advisoryCoordinator.add(newAdvisory);
1153
1154 return threatLevel;
1155}
1156
1158// TCAS ///////////////////////////////////////////////////////////////////////
1160
1161TCAS::TCAS(SGPropertyNode* pNode) :
1162 name("tcas"),
1163 num(0),
1164 nextUpdateTime(0),
1165 selfTestStep(0),
1166 properties_handler(this),
1167 threatDetector(this),
1168 tracker(this),
1169 advisoryCoordinator(this),
1170 advisoryGenerator(this),
1171 annunciator(this),
1172 _verticalRange(10000),
1173 _lateralRange(10),
1174 _proxVertRange(1200),
1175 _proxLatRange(6),
1176 _incDesInhbAlt(1450),
1177 _DesInhbAlt(1100),
1178 _RAInhbAlt(1000),
1179 _TAInhbAlt(500),
1180 _intruderInhbAlt(360),
1183{
1184 for (int i = 0; i < pNode->nChildren(); ++i)
1185 {
1186 SGPropertyNode* pChild = pNode->getChild(i);
1187 string cname = pChild->getNameString();
1188 string cval = pChild->getStringValue();
1189 int cintval = pChild->getIntValue();
1190
1191 if (cintval < 0 ) {
1192 SG_LOG(SG_INSTR, SG_WARN, "Error in TCAS config logic: value less than zero. Skipping!");
1193 }
1194
1195 if (cname == "name")
1196 name = cval;
1197 else if (cname == "number")
1198 num = cintval;
1199 else if (cname == "vertical-range-ft" && cintval > 0)
1200 _verticalRange = cintval;
1201 else if (cname == "lateral-range-nm" && cintval > 0)
1202 _lateralRange = cintval;
1203 else if (cname == "proximate-vertical-range-ft" && cintval > 0)
1204 _proxVertRange = cintval;
1205 else if (cname == "proximate-lateral-range-nm" && cintval > 0)
1206 _proxLatRange = cintval;
1207 else if (cname == "increase-descent-inhibit-alt-ft" && cintval > 0)
1208 _incDesInhbAlt = cintval;
1209 else if (cname == "descend-inhibit-alt-ft" && cintval > 0)
1210 _DesInhbAlt = cintval;
1211 else if (cname == "ra-inhibit-alt-ft" && cintval > 0)
1212 _RAInhbAlt = cintval;
1213 else if (cname == "ta-inhibit-alt-ft" && cintval > 0)
1214 _TAInhbAlt = cintval;
1215 else if (cname == "intruder-inhibit-alt-ft" && cintval > 0)
1216 _intruderInhbAlt = cintval;
1217 else if (cname == "intruder-use-own-alt")
1218 _intruderInhbSelfAltToggle = pChild->getBoolValue();
1219 else if (cname == "intruder-minimum-own-alt-ft" && cintval > 0)
1220 _intruderInhbSelfAlt = cintval;
1221 else
1222 {
1223 SG_LOG(SG_INSTR, SG_WARN, "Error in TCAS config logic");
1224 if (name.length())
1225 SG_LOG(SG_INSTR, SG_WARN, "Section = " << name);
1226 }
1227 }
1228}
1229
1230void
1232{
1233 annunciator.init();
1234 advisoryCoordinator.init();
1235 threatDetector.init();
1236}
1237
1238void
1240{
1241 nextUpdateTime = 0;
1242 advisoryCoordinator.reinit();
1243}
1244
1245void
1247{
1248 SGPropertyNode* node = fgGetNode(("/instrumentation/" + name), num, true);
1249
1250 nodeServiceable = node->getNode("serviceable", true);
1251
1252 // TCAS mode selection (0=off, 1=standby, 2=TA only, 3=auto(TA/RA) )
1253 nodeModeSwitch = node->getNode("inputs/mode", true);
1254 // self-test button
1255 nodeSelfTest = node->getNode("inputs/self-test", true);
1256 // default value
1257 nodeSelfTest->setBoolValue(false);
1258
1259#ifdef FEATURE_TCAS_DEBUG_PROPERTIES
1260 SGPropertyNode* nodeDebug = node->getNode("debug", true);
1261 // debug triggers
1262 nodeDebugTrigger = nodeDebug->getNode("threat-trigger", true);
1263 nodeDebugRA = nodeDebug->getNode("threat-RA", true);
1264 nodeDebugThreat = nodeDebug->getNode("threat-level", true);
1265 // default values
1266 nodeDebugTrigger->setBoolValue(false);
1267 nodeDebugRA->setIntValue(3);
1268 nodeDebugThreat->setIntValue(1);
1269#endif
1270
1271 annunciator.bind(node);
1272 advisoryCoordinator.bind(node);
1273}
1274
1275void
1277{
1278 properties_handler.unbind();
1279}
1280
1282void
1284{
1285 if (!nodeServiceable->getBoolValue())
1286 return;
1287 int mode = nodeModeSwitch->getIntValue();
1288 if (mode == SwitchOff)
1289 return;
1290
1291 nextUpdateTime -= dt;
1292 if (nextUpdateTime <= 0.0 )
1293 {
1294 nextUpdateTime = 1.0;
1295
1296 // remove obsolete targets
1297 tracker.update();
1298
1299 // get aircrafts current position/speed/heading
1300 threatDetector.update();
1301
1302 // clear old threats
1303 advisoryCoordinator.clear();
1304
1305 if (nodeSelfTest->getBoolValue())
1306 {
1307 if (threatDetector.getVelocityKt() >= 40)
1308 {
1309 // disable self-test when plane moves above taxiing speed
1310 nodeSelfTest->setBoolValue(false);
1311 selfTestStep = 0;
1312 }
1313 else
1314 {
1315 selfTest();
1316 // speed-up self test
1317 nextUpdateTime = 0;
1318 // no further TCAS processing during self-test
1319 return;
1320 }
1321 }
1322
1323#ifdef FEATURE_TCAS_DEBUG_PROPERTIES
1324 if (nodeDebugTrigger->getBoolValue())
1325 {
1326 // debugging test
1327 ResolutionAdvisory debugAdvisory;
1328 debugAdvisory.RAOption = OptionNone;
1329 debugAdvisory.RA = nodeDebugRA->getIntValue();
1330 debugAdvisory.threatLevel = nodeDebugThreat->getIntValue();
1331 advisoryCoordinator.add(debugAdvisory);
1332 }
1333 else
1334#endif
1335 {
1336 SGPropertyNode* pAi = fgGetNode("/ai/models", true);
1337
1338 // check all aircraft
1339 for (int i = pAi->nChildren() - 1; i >= -1; i--)
1340 {
1341 SGPropertyNode* pModel = pAi->getChild(i);
1342 if ((pModel)&&(pModel->nChildren()))
1343 {
1344 int threatLevel = threatDetector.checkThreat(mode, pModel);
1345 /* expose aircraft threat-level (to be used by other instruments,
1346 * i.e. TCAS display) */
1347 if (threatLevel==ThreatRA)
1348 pModel->setIntValue("tcas/ra-sense", -threatDetector.getRASense());
1349 pModel->setIntValue("tcas/threat-level", threatLevel);
1350 }
1351 }
1352 }
1353 advisoryCoordinator.update(mode);
1354 }
1355 annunciator.update();
1356}
1357
1359void
1360TCAS::selfTest(void)
1361{
1362 annunciator.update();
1363 if (annunciator.isPlaying())
1364 {
1365 return;
1366 }
1367
1368 ResolutionAdvisory newAdvisory;
1369 newAdvisory.threatLevel = ThreatRA;
1370 newAdvisory.RA = AdvisoryClear;
1371 newAdvisory.RAOption = OptionNone;
1372 // TCAS audio is disabled below 500ft AGL
1373 threatDetector.setRadarAlt(501);
1374
1375 // trigger various advisories
1376 switch(selfTestStep)
1377 {
1378 case 0:
1379 newAdvisory.RA = AdvisoryIntrusion;
1380 newAdvisory.threatLevel = ThreatTA;
1381 break;
1382 case 1:
1383 newAdvisory.RA = AdvisoryClimb;
1384 break;
1385 case 2:
1386 newAdvisory.RA = AdvisoryClimb;
1387 newAdvisory.RAOption = OptionIncreaseClimb;
1388 break;
1389 case 3:
1390 newAdvisory.RA = AdvisoryClimb;
1391 newAdvisory.RAOption = OptionCrossingClimb;
1392 break;
1393 case 4:
1394 newAdvisory.RA = AdvisoryDescend;
1395 break;
1396 case 5:
1397 newAdvisory.RA = AdvisoryDescend;
1398 newAdvisory.RAOption = OptionIncreaseDescend;
1399 break;
1400 case 6:
1401 newAdvisory.RA = AdvisoryDescend;
1402 newAdvisory.RAOption = OptionCrossingDescent;
1403 break;
1404 case 7:
1405 newAdvisory.RA = AdvisoryAdjustVSpeed;
1406 break;
1407 case 8:
1408 newAdvisory.RA = AdvisoryMaintVSpeed;
1409 break;
1410 case 9:
1411 newAdvisory.RA = AdvisoryMonitorVSpeed;
1412 break;
1413 case 10:
1414 newAdvisory.threatLevel = ThreatNone;
1415 newAdvisory.RA = AdvisoryClear;
1416 break;
1417 case 11:
1418 annunciator.test(true);
1419 selfTestStep+=2;
1420 return;
1421 default:
1422 nodeSelfTest->setBoolValue(false);
1423 selfTestStep = 0;
1424 return;
1425 }
1426
1427 advisoryCoordinator.add(newAdvisory);
1428 advisoryCoordinator.update(SwitchAuto);
1429
1430 selfTestStep++;
1431}
1432
1434// TCAS::Tracker //////////////////////////////////////////////////////////////
1436
1437TCAS::Tracker::Tracker(TCAS*) :
1438 currentTime(0),
1439 haveTargets(false),
1440 newTargets(false)
1441{
1442 targets.clear();
1443}
1444
1445void
1446TCAS::Tracker::update(void)
1447{
1448 currentTime = globals->get_sim_time_sec();
1449 newTargets = false;
1450
1451 if (haveTargets)
1452 {
1453 // remove outdated targets
1454 TrackerTargets::iterator it = targets.begin();
1455 while (it != targets.end())
1456 {
1457 TrackerTarget* pTarget = it->second;
1458 if (currentTime - pTarget->TAtimestamp > 10.0)
1459 {
1460 TrackerTargets::iterator temp = it;
1461 ++it;
1462#ifdef FEATURE_TCAS_DEBUG_TRACKER
1463 printf("target %s no longer a TA threat.\n",temp->first.c_str());
1464#endif
1465 targets.erase(temp->first);
1466 delete pTarget;
1467 pTarget = NULL;
1468 }
1469 else
1470 {
1471 if ((pTarget->threatLevel == ThreatRA)&&
1472 (currentTime - pTarget->RAtimestamp > 7.0))
1473 {
1474 pTarget->threatLevel = ThreatTA;
1475#ifdef FEATURE_TCAS_DEBUG_TRACKER
1476 printf("target %s no longer an RA threat.\n",it->first.c_str());
1477#endif
1478 }
1479 ++it;
1480 }
1481 }
1482 haveTargets = !targets.empty();
1483 }
1484}
1485
1486void
1487TCAS::Tracker::add(const string callsign, int detectedLevel)
1488{
1489 TrackerTarget* pTarget = NULL;
1490 if (haveTargets)
1491 {
1492 TrackerTargets::iterator it = targets.find(callsign);
1493 if (it != targets.end())
1494 {
1495 pTarget = it->second;
1496 }
1497 }
1498
1499 if (!pTarget)
1500 {
1501 pTarget = new TrackerTarget();
1502 pTarget->TAtimestamp = 0;
1503 pTarget->RAtimestamp = 0;
1504 pTarget->threatLevel = 0;
1505 newTargets = true;
1506 targets[callsign] = pTarget;
1507#ifdef FEATURE_TCAS_DEBUG_TRACKER
1508 printf("new target: %s, level: %i\n",callsign.c_str(),detectedLevel);
1509#endif
1510 }
1511
1512 if (detectedLevel > pTarget->threatLevel)
1513 pTarget->threatLevel = detectedLevel;
1514
1515 if (detectedLevel >= ThreatTA)
1516 pTarget->TAtimestamp = currentTime;
1517
1518 if (detectedLevel >= ThreatRA)
1519 pTarget->RAtimestamp = currentTime;
1520
1521 haveTargets = true;
1522}
1523
1524bool
1525TCAS::Tracker::_isTracked(string callsign)
1526{
1527 return targets.find(callsign) != targets.end();
1528}
1529
1530int
1531TCAS::Tracker::getThreatLevel(string callsign)
1532{
1533 TrackerTargets::iterator it = targets.find(callsign);
1534 if (it != targets.end())
1535 return it->second->threatLevel;
1536 else
1537 return 0;
1538}
1539
1540
1541// Register the subsystem.
1542#if 0
1543SGSubsystemMgr::InstancedRegistrant<TCAS> registrantTCAS(
1544 SGSubsystemMgr::FDM,
1545 {{"instrumentation", SGSubsystemMgr::Dependency::HARD}},
1546 0.2);
1547#endif
#define i(x)
double get_sim_time_sec() const
Definition globals.hxx:185
Definition tcas.hxx:51
int _proxLatRange
Definition tcas.hxx:397
int _intruderInhbSelfAlt
Definition tcas.hxx:404
void update(double dt) override
Monitor traffic for safety threats.
Definition tcas.cxx:1283
int _RAInhbAlt
Definition tcas.hxx:400
int _intruderInhbAlt
Definition tcas.hxx:402
int _incDesInhbAlt
Definition tcas.hxx:398
void unbind() override
Definition tcas.cxx:1276
int _verticalRange
Definition tcas.hxx:394
void reinit() override
Definition tcas.cxx:1239
TCAS(SGPropertyNode *node)
Definition tcas.cxx:1161
void bind() override
Definition tcas.cxx:1246
int _proxVertRange
Definition tcas.hxx:396
void init() override
Definition tcas.cxx:1231
int _TAInhbAlt
Definition tcas.hxx:401
int _lateralRange
Definition tcas.hxx:395
bool _intruderInhbSelfAltToggle
Definition tcas.hxx:403
int _DesInhbAlt
Definition tcas.hxx:399
FGGlobals * globals
Definition globals.cxx:142
SGPropertyNode * fgGetNode(const char *path, bool create)
Get a property node.
Definition proptest.cpp:27
static void clear(FGReplayInternal &self)
static bool checkTransponderLocal(const SGPropertyNode *pModel, float velocityKt, float &o_altFt)
Definition tcas.cxx:587
static void calcRangeBearing(double lat1, double lon1, double lat2, double lon2, double &rangeNm, double &bearing)
calculate range and bearing of lat2/lon2 relative to lat1/lon1
Definition tcas.cxx:170
#define AVAILABLE_RA(Options, Advisory)
Definition tcas.cxx:149
#define ADD_VOICE(Var, Sample, SayTwice)
Definition tcas.cxx:144