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