]> git.mxchange.org Git - flightgear.git/commitdiff
Introduce new TCAS instrument
authorThorstenB <brehmt@gmail.com>
Wed, 29 Dec 2010 21:04:19 +0000 (22:04 +0100)
committerThorstenB <brehmt@gmail.com>
Fri, 25 Feb 2011 20:12:35 +0000 (21:12 +0100)
- implements a TCAS II v7.0

src/Instrumentation/CMakeLists.txt
src/Instrumentation/Makefile.am
src/Instrumentation/instrument_mgr.cxx
src/Instrumentation/tcas.cxx [new file with mode: 0644]
src/Instrumentation/tcas.hxx [new file with mode: 0644]

index d1cc59e3ab696433d60118fc6312dab96a91ba40..60d9457d347c5de6f52d393c2dd2950fcef154db 100644 (file)
@@ -31,6 +31,7 @@ set(SOURCES
        rnav_waypt_controller.cxx
        slip_skid_ball.cxx
        tacan.cxx
+       tcas.cxx
        transponder.cxx
        turn_indicator.cxx
        vertical_speed_indicator.cxx
index f519f99a04fe0dcac65f71d0de7f5f08aa3fa6f6..0edb854339ecf59f48670a02d77e8fd03ae1a22a 100644 (file)
@@ -33,6 +33,7 @@ libInstrumentation_a_SOURCES = \
        render_area_2d.cxx render_area_2d.hxx        \
        groundradar.cxx groundradar.hxx \
        agradar.cxx agradar.hxx rad_alt.cxx rad_alt.hxx \
-       rnav_waypt_controller.cxx rnav_waypt_controller.hxx
+       rnav_waypt_controller.cxx rnav_waypt_controller.hxx \
+       tcas.cxx tcas.hxx
 
 INCLUDES = -I$(top_srcdir) -I$(top_srcdir)/src -I$(top_builddir)/src
index 19fcaeb066ea21386d1fb4a4e2ae932b3ba3c9e0..58fe89db1a4c5b5a5c7976cc9f133753b51573d3 100644 (file)
@@ -50,6 +50,7 @@
 #include "groundradar.hxx"
 #include "agradar.hxx"
 #include "rad_alt.hxx"
+#include "tcas.hxx"
 
 FGInstrumentMgr::FGInstrumentMgr () :
   _explicitGps(false)
@@ -223,6 +224,9 @@ bool FGInstrumentMgr::build (SGPropertyNode* config_props)
         } else if ( name == "radar-altimeter" ) {
             set_subsystem( id, new radAlt( node ),1);
 
+        } else if ( name == "tcas" ) {
+            set_subsystem( id, new TCAS( node ) );
+
         } else {
             SG_LOG( SG_ALL, SG_ALERT, "Unknown top level section: "
                     << name );
diff --git a/src/Instrumentation/tcas.cxx b/src/Instrumentation/tcas.cxx
new file mode 100644 (file)
index 0000000..6598dc0
--- /dev/null
@@ -0,0 +1,1303 @@
+// tcas.cxx -- Traffic Alert and Collision Avoidance System (TCAS) Emulation
+//
+// Written by Thorsten Brehm, started December 2010.
+//
+// Copyright (C) 2010 Thorsten Brehm - brehmt (at) gmail com
+//
+// This program is free software; you can redistribute it and/or
+// modify it under the terms of the GNU General Public License as
+// published by the Free Software Foundation; either version 2 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; if not, write to the Free Software
+// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+//
+///////////////////////////////////////////////////////////////////////////////
+
+/* References:
+ *
+ *  [TCASII] Introduction to TCAS II Version 7, Federal Aviation Administration, November 2000
+ *           http://www.arinc.com/downloads/tcas/tcas.pdf
+ *
+ *  [EUROACAS] Eurocontrol Airborne Collision Avoidance System (ACAS),
+ *           http://www.eurocontrol.int/msa/public/standard_page/ACAS_Startpage.html
+ *
+ * Glossary:
+ *
+ *  ALIM: Altitude Limit
+ *
+ *  CPA: Closest point of approach as computed from a threat's range and range rate.
+ *
+ *  DMOD: Distance MODification
+ *
+ *  Intruder: A target that has satisfied the traffic detection criteria.
+ *
+ *  Proximity target: Any target that is less than 6 nmi in range and within +/-1200ft
+ *      vertically, but that does not meet the intruder or threat criteria.
+ *
+ *  RA: Resolution advisory. An indication given by TCAS II to a flight crew that a
+ *      vertical maneuver should, or in some cases should not, be performed to attain or
+ *      maintain safe separation from a threat.
+ *
+ *  SL: Sensitivity Level. A value used in defining the size of the protected volume
+ *      around the own aircraft.
+ *
+ *  TA: Traffic Advisory. An indication given by TCAS to the pilot when an aircraft has
+ *      entered, or is projected to enter, the protected volume around the own aircraft.
+ *
+ *  Tau: Approximation of the time, in seconds, to CPA or to the aircraft being at the
+ *       same altitude.
+ *
+ *  TCAS: Traffic alert and Collision Avoidance System
+ */
+
+/* Module properties:
+ *
+ *   serviceable             enable/disable TCAS processing
+ *   
+ *   voice/file-prefix       path (and optional prefix) for sound sample files
+ *                           (only evaluated at start-up)  
+ *
+ *   inputs/mode             TCAS mode selection: 0=off,1=standby,2=TA only,3=auto(TA/RA)
+ *   inputs/self-test        trigger self-test sequence
+ *
+ *   outputs/traffic-alert   intruder detected (true=TA-threat is active, includes RA-threats)
+ *   outputs/advisory-alert  resolution advisory is issued (true=advisory is valid)
+ *   outputs/vertical-speed  vertical speed required by advisory (+/-2000/1500/500/0)
+ *
+ *   speaker/max-dist        Max. distance where speaker is heard
+ *   speaker/reference-dist  Distance to pilot
+ *   speaker/volume          Volume at reference distance
+ *
+ *   debug/threat-trigger    trigger debugging test (in debug mode only)
+ *   debug/threat-RA         debugging RA value (in debug mode only)
+ *   debug/threat-level      debugging threat level (in debug mode only)
+ */
+
+#ifdef _MSC_VER
+#  pragma warning( disable: 4355 )
+#endif
+
+#ifdef HAVE_CONFIG_H
+#  include <config.h>
+#endif
+
+#include <stdio.h>
+#include <string.h>
+#include <assert.h>
+#include <math.h>
+
+#include <string>
+#include <sstream>
+
+#include <simgear/constants.h>
+#include <simgear/sg_inlines.h>
+#include <simgear/debug/logstream.hxx>
+#include <simgear/math/sg_geodesy.hxx>
+#include <simgear/math/sg_random.h>
+#include <simgear/misc/sg_path.hxx>
+#include <simgear/sound/soundmgr_openal.hxx>
+#include <simgear/structure/exception.hxx>
+
+using std::string;
+
+#if defined( HAVE_VERSION_H ) && HAVE_VERSION_H
+#  include <Include/version.h>
+#else
+#  include <Include/no_version.h>
+#endif
+
+#include <Main/fg_props.hxx>
+#include <Main/globals.hxx>
+#include <AIModel/submodel.hxx>
+#include "instrument_mgr.hxx"
+#include "tcas.hxx"
+
+///////////////////////////////////////////////////////////////////////////////
+// debug switches /////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+//#define FEATURE_TCAS_DEBUG_ANNUNCIATOR
+//#define FEATURE_TCAS_DEBUG_COORDINATOR
+//#define FEATURE_TCAS_DEBUG_THREAT_DETECTOR
+//#define FEATURE_TCAS_DEBUG_ADV_GENERATOR
+//#define FEATURE_TCAS_DEBUG_PROPERTIES
+
+///////////////////////////////////////////////////////////////////////////////
+// constants //////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+/** Sensitivity Level Definition and Alarm Thresholds (TCASII, Version 7) 
+ *  Max Own        |    |TA-level                |RA-level
+ *  Altitude(ft)   | SL |Tau(s),DMOD(nm),ALIM(ft)|Tau(s),DMOD(nm),ALIM(ft) */
+const TCAS::SensitivityLevel
+TCAS::ThreatDetector::sensitivityLevels[] = {
+   { 1000,           2,  {20,   0.30,     850},  {0,    0,        0  }}, 
+   { 2350,           3,  {25,   0.33,     850},  {15,   0.20,     300}},
+   { 5000,           4,  {30,   0.48,     850},  {20,   0.35,     300}},
+   {10000,           5,  {40,   0.75,     850},  {25,   0.55,     350}},
+   {20000,           6,  {45,   1.00,     850},  {30,   0.80,     400}},
+   {42000,           7,  {48,   1.30,     850},  {35,   1.10,     600}},
+   {0,               8,  {48,   1.30,    1200},  {35,   1.10,     700}}
+};
+
+///////////////////////////////////////////////////////////////////////////////
+// helpers ////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+#define ADD_VOICE(Var, Sample, SayTwice)    \
+    { make_voice(&Var);                    \
+      append(Var, Sample);                  \
+      if (SayTwice) append(Var, Sample); }
+
+#define AVAILABLE_RA(Options, Advisory) (Advisory == (Advisory & Options))
+
+#ifdef FEATURE_TCAS_DEBUG_THREAT_DETECTOR
+/** calculate relative angle in between two headings */
+static float
+relAngle(float Heading1, float Heading2)
+{
+    Heading1 -= Heading2;
+
+    while (Heading1 >= 360.0)
+        Heading1 -= 360.0;
+
+    while (Heading1 < 0.0)
+        Heading1 += 360;
+
+    return Heading1;
+}
+#endif
+
+/** calculate range and bearing of lat2/lon2 relative to lat1/lon1 */ 
+static void
+calcRangeBearing(double lat1, double lon1, double lat2, double lon2,
+                 double &rangeNm, double &bearing)
+{
+    // calculate the bearing and range of the second pos from the first
+    double az2, distanceM;
+    geo_inverse_wgs_84(lat1, lon1, lat2, lon2, &bearing, &az2, &distanceM);
+    rangeNm = distanceM * SG_METER_TO_NM;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// VoicePlayer ////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+void
+TCAS::VoicePlayer::init(void)
+{
+    FGVoicePlayer::init();
+
+    ADD_VOICE(Voices.pTrafficTraffic, "traffic",                 true);
+    ADD_VOICE(Voices.pClear,          "clear",                   false);
+    ADD_VOICE(Voices.pClimb,          "climb",                   true);
+    ADD_VOICE(Voices.pClimbNow,       "climb_now",               true);
+    ADD_VOICE(Voices.pClimbCrossing,  "climb_crossing",          true);
+    ADD_VOICE(Voices.pClimbIncrease,  "increase_climb",          false);
+    ADD_VOICE(Voices.pDescend,        "descend",                 true);
+    ADD_VOICE(Voices.pDescendNow,     "descend_now",             true);
+    ADD_VOICE(Voices.pDescendCrossing,"descend_crossing",        true);
+    ADD_VOICE(Voices.pDescendIncrease,"increase_descent",        false);
+    ADD_VOICE(Voices.pAdjustVSpeed,   "adjust_vertical_speed",   false);
+    ADD_VOICE(Voices.pMaintVSpeed,    "maintain_vertical_speed", false);
+    ADD_VOICE(Voices.pMonitorVSpeed,  "monitor_vertical_speed",  false);
+    ADD_VOICE(Voices.pLevelOff,       "level_off",               false);
+    ADD_VOICE(Voices.pTestOk,         "test_ok",                 false);
+    ADD_VOICE(Voices.pTestFail,       "test_fail",               false);
+
+    speaker.update_configuration();
+}
+
+/////////////////////////////////////////////////////////////////////////////
+// TCAS::Annunciator ////////////////////////////////////////////////////////
+/////////////////////////////////////////////////////////////////////////////
+
+TCAS::Annunciator::Annunciator(TCAS* tcas) :
+    pLastVoice(NULL),
+    voicePlayer(tcas)
+{
+    clear();
+}
+
+void TCAS::Annunciator::clear(void)
+{
+    previous.threatLevel = ThreatNone;
+    previous.RA          = AdvisoryClear;
+    previous.RAOption    = OptionNone;
+    pLastVoice = NULL;
+}
+
+void
+TCAS::Annunciator::bind(SGPropertyNode* node)
+{
+    voicePlayer.bind(node, "Sounds/tcas/");
+}
+
+void
+TCAS::Annunciator::init(void)
+{
+    //TODO link to GPWS module/audio-on signal must be configurable
+    nodeGpwsAlertOn = fgGetNode("/instrumentation/mk-viii/outputs/discretes/audio-on", true);
+    voicePlayer.init();
+}
+
+void
+TCAS::Annunciator::update(void)
+{
+    voicePlayer.update();
+    
+    /* [TCASII]: "The priority scheme gives ground proximity warning systems (GPWS)
+     *   a higher annunciation priority than a TCAS alert. TCAS aural annunciation will
+     *   be inhibited during the time that a GPWS alert is active." */
+    if (nodeGpwsAlertOn->getBoolValue())
+        voicePlayer.pause();
+    else
+        voicePlayer.resume();
+}
+
+/** Trigger voice sample for current alert. */
+void
+TCAS::Annunciator::trigger(const ResolutionAdvisory& current, bool revertedRA)
+{
+    int RA = current.RA;
+    int RAOption = current.RAOption;
+
+    if (RA == AdvisoryClear)
+    {
+        if (previous.RA != AdvisoryClear)
+        {
+            voicePlayer.play(voicePlayer.Voices.pClear, VoicePlayer::PLAY_NOW);
+            previous = current;
+        }
+        pLastVoice = NULL;
+        return;
+    }
+
+    if (previous.RA == AdvisoryClear)
+    {
+        voicePlayer.play(voicePlayer.Voices.pTrafficTraffic, VoicePlayer::PLAY_NOW);
+    }
+
+    // pick voice sample
+    VoicePlayer::Voice* pVoice = NULL;
+    switch(RA)
+    {
+        case AdvisoryClimb:
+            if (revertedRA)
+                pVoice = voicePlayer.Voices.pClimbNow;
+            else
+            if (AVAILABLE_RA(RAOption, OptionIncreaseClimb))
+                pVoice = voicePlayer.Voices.pClimbIncrease;
+            else
+            if (AVAILABLE_RA(RAOption, OptionCrossingClimb))
+                pVoice = voicePlayer.Voices.pClimbCrossing;
+            else
+                pVoice = voicePlayer.Voices.pClimb;
+            break;
+
+        case AdvisoryDescend:
+            if (revertedRA)
+                pVoice = voicePlayer.Voices.pDescendNow;
+            else
+            if (AVAILABLE_RA(RAOption, OptionIncreaseDescend))
+                pVoice = voicePlayer.Voices.pDescendIncrease;
+            else
+            if (AVAILABLE_RA(RAOption, OptionCrossingDescent))
+                pVoice = voicePlayer.Voices.pDescendCrossing;
+            else
+                pVoice = voicePlayer.Voices.pDescend;
+            break;
+
+        case AdvisoryAdjustVSpeed:
+            pVoice = voicePlayer.Voices.pAdjustVSpeed;
+            break;
+
+        case AdvisoryMaintVSpeed:
+            pVoice = voicePlayer.Voices.pMaintVSpeed;
+            break;
+
+        case AdvisoryMonitorVSpeed:
+            pVoice = voicePlayer.Voices.pMonitorVSpeed;
+            break;
+
+        case AdvisoryLevelOff:
+            pVoice = voicePlayer.Voices.pLevelOff;
+            break;
+
+        case AdvisoryIntrusion:
+            break;
+
+        default:
+            RA = AdvisoryIntrusion;
+            break;
+    }
+
+    previous = current;
+
+    if (pLastVoice == pVoice)
+    {
+        // don't repeat annunciation
+        return;
+    }
+    pLastVoice = pVoice;
+    if (pVoice)
+        voicePlayer.play(pVoice);
+
+#ifdef FEATURE_TCAS_DEBUG_ANNUNCIATOR
+    cout << "Annunciating TCAS RA " << RA << endl;
+#endif
+}
+
+void
+TCAS::Annunciator::test(bool testOk)
+{
+    if (testOk)
+        voicePlayer.play(voicePlayer.Voices.pTestOk);
+    else
+        voicePlayer.play(voicePlayer.Voices.pTestFail);
+}
+
+/////////////////////////////////////////////////////////////////////////////
+// TCAS::AdvisoryCoordinator ////////////////////////////////////////////////
+/////////////////////////////////////////////////////////////////////////////
+
+TCAS::AdvisoryCoordinator::AdvisoryCoordinator(TCAS* _tcas) :
+  tcas(_tcas),
+  lastTATime(0)
+{
+    init();
+}
+
+void
+TCAS::AdvisoryCoordinator::init(void)
+{
+    clear();
+    previous = current;
+}
+
+void
+TCAS::AdvisoryCoordinator::bind(SGPropertyNode* node)
+{
+    nodeTAWarning = node->getNode("outputs/traffic-alert", true);
+    nodeTAWarning->setBoolValue(false);
+}
+
+void
+TCAS::AdvisoryCoordinator::clear(void)
+{
+    current.threatLevel = ThreatNone;
+    current.RA          = AdvisoryClear;
+    current.RAOption    = OptionNone;
+}
+
+/** Add all suitable resolution advisories for a single threat. */
+void
+TCAS::AdvisoryCoordinator::add(const ResolutionAdvisory& newAdvisory)
+{
+    if (newAdvisory.RA == AdvisoryClear)
+        return;
+
+    if ((current.RA == AdvisoryClear)||
+        (current.threatLevel == ThreatTA))
+    {
+        current = newAdvisory;
+    }
+    else
+    {
+        // combine with other advisories so far
+        current.RA &= newAdvisory.RA;
+        // remember any advisory modifier
+        current.RAOption |= newAdvisory.RAOption;
+    }
+}
+
+/** Pick and trigger suitable resolution advisory. */
+void
+TCAS::AdvisoryCoordinator::update(int mode)
+{
+    bool revertedRA = false; // has advisory changed?
+    double currentTime = globals->get_sim_time_sec();
+
+    if (current.RA == AdvisoryClear)
+    {
+        // filter: wait 5 seconds after last TA/RA before announcing TA clearance
+        if ((previous.RA != AdvisoryClear)&&
+             (currentTime - lastTATime < 5.0))
+            return;
+    }
+    else
+    {
+        // intruder detected
+
+#ifdef FEATURE_TCAS_DEBUG_COORDINATOR
+        cout << "TCAS::Annunciator::update: previous: " << previous.RA << ", new: " << current.RA << endl;
+#endif
+
+        lastTATime = currentTime;
+        if ((previous.RA == AdvisoryClear)||
+            (previous.RA == AdvisoryIntrusion)||
+            ((current.RA & previous.RA) != previous.RA))
+        {
+            // no RA yet, or we can't keep previous RA: pick one - in order of priority
+
+            if (AVAILABLE_RA(current.RA, AdvisoryMonitorVSpeed))
+            {
+                // prio 1: monitor vertical speed only
+                current.RA = AdvisoryMonitorVSpeed;
+            }
+            else
+            if (AVAILABLE_RA(current.RA, AdvisoryMaintVSpeed))
+            {
+                // prio 2: maintain vertical speed
+                current.RA = AdvisoryMaintVSpeed;
+            }
+            else
+            if (AVAILABLE_RA(current.RA, AdvisoryAdjustVSpeed))
+            {
+                // prio 3: adjust vertical speed (TCAS II 7.0 only)
+                current.RA = AdvisoryAdjustVSpeed;
+            }
+            else
+            if (AVAILABLE_RA(current.RA, AdvisoryLevelOff))
+            {
+                // prio 3: adjust vertical speed (TCAS II 7.1 only, [EUROACAS]: CP115)
+                current.RA = AdvisoryLevelOff;
+            }
+            else
+            if (AVAILABLE_RA(current.RA, AdvisoryClimb))
+            {
+                // prio 4: climb
+                current.RA = AdvisoryClimb;
+            }
+            else
+            if (AVAILABLE_RA(current.RA, AdvisoryDescend))
+            {
+                // prio 5: descend
+                current.RA = AdvisoryDescend;
+            }
+            else
+            {
+                // no RA, issue a TA only
+                current.RA = AdvisoryIntrusion;
+            }
+
+            // check if earlier advisory was reverted
+            revertedRA = ((previous.RA != current.RA)&&
+                          (previous.RA != 0)&&
+                          (previous.RA != AdvisoryIntrusion));
+        }
+        else
+        {
+            // keep earlier RA
+            current.RA = previous.RA;
+        }
+    }
+
+    /* [TCASII]: "Aural annunciations are inhibited below 500+/-100 feet AGL." */
+    if ((tcas->threatDetector.getAlt() > 500)&&
+        (mode >= SwitchTaOnly))
+        tcas->annunciator.trigger(current, revertedRA);
+    else
+    if (current.RA == AdvisoryClear)
+    {
+        /* explicitly clear traffic alert (since aural annunciation disabled) */
+        tcas->annunciator.clear();
+    }
+
+    previous = current;
+    
+    /* [TCASII] "[..] also performs the function of setting flags that control the displays.
+     *   The traffic display, the RA display, [..] use these flags to alert the pilot to
+     *   the presence of TAs and RAs." */
+    nodeTAWarning->setBoolValue(current.RA != AdvisoryClear);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// TCAS::ThreatDetector ///////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+TCAS::ThreatDetector::ThreatDetector(TCAS* _tcas) :
+    tcas(_tcas),
+    pAlarmThresholds(&sensitivityLevels[0])
+{
+    unitTest();
+}
+
+void
+TCAS::ThreatDetector::init(void)
+{
+    nodeLat         = fgGetNode("/position/latitude-deg",          true);
+    nodeLon         = fgGetNode("/position/longitude-deg",         true);
+    nodeAlt         = fgGetNode("/position/altitude-ft",           true);
+    nodeHeading     = fgGetNode("/orientation/heading-deg",        true);
+    nodeVelocity    = fgGetNode("/velocities/airspeed-kt",         true);
+    nodeVerticalFps = fgGetNode("/velocities/vertical-speed-fps",  true);
+    
+    tcas->advisoryGenerator.init(&self,&currentThreat);
+}
+
+/** Update local position and threat sensitivity levels. */
+void
+TCAS::ThreatDetector::update(void)
+{
+    // update local position
+    self.lat         = nodeLat->getDoubleValue();
+    self.lon         = nodeLon->getDoubleValue();
+    self.altFt       = nodeAlt->getDoubleValue();
+    self.heading     = nodeHeading->getDoubleValue();
+    self.velocityKt  = nodeVelocity->getDoubleValue();
+    self.verticalFps = nodeVerticalFps->getDoubleValue();
+
+    checkCount = 0;
+
+    // determine current altitude's "Sensitivity Level Definition and Alarm Thresholds" 
+    int sl=0;
+    for (sl=0;((self.altFt > sensitivityLevels[sl].maxAltitude)&&
+               (sensitivityLevels[sl].maxAltitude));sl++);
+    pAlarmThresholds = &sensitivityLevels[sl];
+    tcas->advisoryGenerator.setAlarmThresholds(pAlarmThresholds);
+}
+
+/** Check if plane's transponder is enabled. */
+bool
+TCAS::ThreatDetector::checkTransponder(const SGPropertyNode* pModel, float velocityKt)
+{
+    const string name = pModel->getName();
+    if (name != "multiplayer" && name != "aircraft")
+    {
+        // assume non-MP/non-AI planes (e.g. ships) have no transponder
+        return false;
+    }
+
+    if (velocityKt < 40)
+    {
+        /* assume all pilots have their transponder switched off while taxiing/parking
+         * (at low speed) */
+        return false;
+    }
+
+    return true;
+}
+
+/** Check if plane is a threat. */
+int
+TCAS::ThreatDetector::checkThreat(int mode, const SGPropertyNode* pModel)
+{
+    checkCount++;
+
+    float velocityKt  = pModel->getDoubleValue("velocities/true-airspeed-kt");
+
+    if (!checkTransponder(pModel, velocityKt))
+        return ThreatInvisible;
+
+    int threatLevel = ThreatNone;
+    float altFt = pModel->getDoubleValue("position/altitude-ft");
+    currentThreat.relativeAltitudeFt = altFt - self.altFt;
+
+    // save computation time: don't care when relative altitude is excessive
+    if (fabs(currentThreat.relativeAltitudeFt) > 10000)
+        return threatLevel;
+
+    // position data of current intruder
+    double lat        = pModel->getDoubleValue("position/latitude-deg");
+    double lon        = pModel->getDoubleValue("position/longitude-deg");
+    float heading     = pModel->getDoubleValue("orientation/true-heading-deg");
+
+    double distanceNm, bearing;
+    calcRangeBearing(self.lat, self.lon, lat, lon, distanceNm, bearing);
+
+    // save computation time: don't care for excessive distances (also captures NaNs...)
+    if ((distanceNm > 10)||(distanceNm < 0))
+        return threatLevel;
+
+    // first stage: vertical movement
+    currentThreat.verticalFps = pModel->getDoubleValue("velocities/vertical-speed-fps");
+    
+    /* Detect proximity targets
+     * [TCASII]: "Any target that is less than 6 nmi in range and within +/-1200ft
+     *  vertically, but that does not meet the intruder or threat criteria." */
+    if ((distanceNm < 6)&&
+        (fabs(currentThreat.relativeAltitudeFt) < 1200))
+    {
+        // at least a proximity target
+        threatLevel = ThreatProximity;
+    }
+
+    checkVerticalThreat();
+
+    // stop processing when no vertical threat
+    if (!currentThreat.verticalTA)
+        return threatLevel;
+
+    // second stage: horizontal movement
+    horizontalThreat(bearing, distanceNm, heading, velocityKt);
+
+    // no horizontal threat?
+    if (!currentThreat.horizontalTA)
+        return threatLevel;
+
+    if ((currentThreat.horizontalTau < 0)||
+        (currentThreat.verticalTau < 0))
+    {
+        // do not trigger new alerts when Tau is negative, but keep existing alerts
+        int previousThreatLevel = pModel->getIntValue("tcas/threat-level", 0);
+        if (previousThreatLevel == 0)
+            return threatLevel;
+    }
+
+#ifdef FEATURE_TCAS_DEBUG_THREAT_DETECTOR
+    cout << "#" << checkCount << ": " << pModel->getStringValue("callsign") << endl;
+#endif
+
+    threatLevel = ThreatTA;
+    /* at least a TA-level threat, may also have a RA-level threat...
+     * [TCASII]: "For either a TA or an RA to be issued, both the range and 
+     *    vertical criteria, in terms of tau or the fixed thresholds, must be
+     *    satisfied only one of the criteria is satisfied, TCAS will not issue
+     *    an advisory." */
+    if (currentThreat.horizontalRA && currentThreat.verticalRA)
+        threatLevel = ThreatRA;
+
+    // find all resolution options for this conflict
+    tcas->advisoryGenerator.resolution(mode, threatLevel, distanceNm, altFt, heading, velocityKt);
+
+#ifdef FEATURE_TCAS_DEBUG_THREAT_DETECTOR
+    printf("  threat: distance: %4.1f, bearing: %4.1f, alt: %5.1f, velocity: %4.1f, heading: %4.1f, vspeed: %4.1f, "
+           "own alt: %5.1f, own heading: %4.1f, own velocity: %4.1f, vertical tau: %3.2f"
+           //", closing speed: %f"
+           "\n",
+           distanceNm, relAngle(bearing, self.heading), altFt, velocityKt, heading, currentThreat.verticalFps,
+           self.altFt, self.heading, self.velocityKt
+           //, currentThreat.closingSpeedKt
+           ,currentThreat.verticalTau
+           );
+#endif
+
+    return threatLevel;
+}
+
+/** Check if plane is a vertical threat. */
+void
+TCAS::ThreatDetector::checkVerticalThreat(void)
+{
+    // calculate relative vertical speed and altitude
+    float dV = self.verticalFps - currentThreat.verticalFps;
+    float dA = currentThreat.relativeAltitudeFt;
+
+    currentThreat.verticalTA  = false;
+    currentThreat.verticalRA  = false;
+    currentThreat.verticalTau = 0;
+
+    /* [TCASII]: "The vertical tau is equal to the altitude separation (feet)
+     *   divided by the combined vertical speed of the two aircraft (feet/minute)
+     *   times 60." */
+    float tau = 0;
+    if (fabs(dV) > 0.1)
+        tau = dA/dV;
+
+    /* [TCASII]: "When the combined vertical speed of the TCAS and the intruder aircraft
+     *    is low, TCAS will use a fixed-altitude threshold to determine whether a TA or
+     *    an RA should be issued." */
+    if ((fabs(dV) < 3.0)||
+        ((tau < 0) && (tau > -5)))
+    {
+        /* vertical closing speed is low (below 180fpm/3fps), check
+         * fixed altitude range. */
+        float abs_dA = fabs(dA);
+        if (abs_dA < pAlarmThresholds->RA.ALIM)
+        {
+            // continuous intrusion at RA-level
+            currentThreat.verticalTA = true;
+            currentThreat.verticalRA = true;
+        }
+        else
+        if (abs_dA < pAlarmThresholds->TA.ALIM)
+        {
+            // continuous intrusion: with TA-level, but no RA-threat
+            currentThreat.verticalTA = true;
+        }
+        // else: no RA/TA threat
+    }
+    else
+    {
+        if ((tau < pAlarmThresholds->TA.Tau)&&
+            (tau >= -5))
+        {
+            currentThreat.verticalTA = true;
+            currentThreat.verticalRA = (tau<pAlarmThresholds->RA.Tau);
+        }
+    }
+    currentThreat.verticalTau = tau;
+
+#ifdef FEATURE_TCAS_DEBUG_THREAT_DETECTOR
+    if (currentThreat.verticalTA)
+        printf("  vertical dV=%f (%f-%f), dA=%f\n", dV, self.verticalFps, currentThreat.verticalFps, dA);
+#endif
+}
+
+/** Check if plane is a horizontal threat. */
+void
+TCAS::ThreatDetector::horizontalThreat(float bearing, float distanceNm, float heading, float velocityKt)
+{
+    // calculate speed
+    float vxKt = sin(heading*SGD_DEGREES_TO_RADIANS)*velocityKt - sin(self.heading*SGD_DEGREES_TO_RADIANS)*self.velocityKt;
+    float vyKt = cos(heading*SGD_DEGREES_TO_RADIANS)*velocityKt - cos(self.heading*SGD_DEGREES_TO_RADIANS)*self.velocityKt;
+
+    // calculate horizontal closing speed
+    float closingSpeedKt2 = vxKt*vxKt+vyKt*vyKt; 
+    float closingSpeedKt  = sqrt(closingSpeedKt2);
+
+    /* [TCASII]: "The range tau is equal to the slant range (nmi) divided by the closing speed
+     *    (knots) multiplied by 3600."
+     * => calculate allowed slant range (nmi) based on known maximum tau */
+    float TA_rangeNm = (pAlarmThresholds->TA.Tau*closingSpeedKt)/3600;
+    float RA_rangeNm = (pAlarmThresholds->RA.Tau*closingSpeedKt)/3600;
+
+    if (closingSpeedKt < 100)
+    {
+        /* [TCASII]: "In events where the rate of closure is very low, [..]
+         *    an intruder aircraft can come very close in range without crossing the
+         *    range tau boundaries [..]. To provide protection in these types of
+         *    advisories, the range tau boundaries are modified [..] to use
+         *    a fixed-range threshold to issue TAs and RAs in these slow closure
+         *    encounters." */
+        TA_rangeNm += (100.0-closingSpeedKt)*(pAlarmThresholds->TA.DMOD/100.0);
+        RA_rangeNm += (100.0-closingSpeedKt)*(pAlarmThresholds->RA.DMOD/100.0);
+    }
+    if (TA_rangeNm < pAlarmThresholds->TA.DMOD)
+        TA_rangeNm = pAlarmThresholds->TA.DMOD;
+    if (RA_rangeNm < pAlarmThresholds->RA.DMOD)
+        RA_rangeNm = pAlarmThresholds->RA.DMOD;
+
+    currentThreat.horizontalTA   = (distanceNm < TA_rangeNm);
+    currentThreat.horizontalRA   = (distanceNm < RA_rangeNm);
+    currentThreat.horizontalTau  = -1;
+    
+    if ((currentThreat.horizontalRA)&&
+        (currentThreat.verticalRA))
+    {
+        /* an RA will be issued. Prepare extra data for the
+         * traffic resolution stage, i.e. calculate
+         * exact time tau to horizontal CPA.
+         */
+
+        /* relative position of intruder is
+         *   Sx(t) = sx + vx*t
+         *   Sy(t) = sy + vy*t
+         * horizontal distance to intruder is r(t)
+         *   r(t) = sqrt( Sx(t)^2 + Sy(t)^2 )
+         * => horizontal CPA at time t=tau, where r(t) has minimum
+         * r2(t) := r^2(t) = Sx(t)^2 + Sy(t)^2
+         * since r(t)>0 for all t => minimum of r(t) is also minimum of r2(t)
+         * => (d/dt) r2(t) = r2'(t) is 0 for t=tau
+         *    r2(t) = ((Sx(t)^2 + Sy(t))^2) = c + b*t + a*t^2
+         * => r2'(t) = b + a*2*t
+         * at t=tau:
+         *    r2'(tau) = 0 = b + 2*a*tau
+         * => tau = -b/(2*a) 
+         */
+        float sx = sin(bearing*SGD_DEGREES_TO_RADIANS)*distanceNm;
+        float sy = cos(bearing*SGD_DEGREES_TO_RADIANS)*distanceNm;
+        float vx = vxKt * (SG_KT_TO_MPS*SG_METER_TO_NM);
+        float vy = vyKt * (SG_KT_TO_MPS*SG_METER_TO_NM);
+        float a  = vx*vx + vy*vy;
+        float b  = 2*(sx*vx + sy*vy);
+        float tau = 0;
+        if (a > 0.0001)
+            tau = -b/(2*a);
+#ifdef FEATURE_TCAS_DEBUG_THREAT_DETECTOR
+        printf("  Time to horizontal CPA: %4.2f\n",tau);
+#endif
+        if (tau > pAlarmThresholds->RA.Tau)
+            tau = pAlarmThresholds->RA.Tau;
+        
+        // remember time to horizontal CPA
+        currentThreat.horizontalTau = tau;
+    }
+}
+
+/** Test threat detection logic. */
+void
+TCAS::ThreatDetector::unitTest(void)
+{
+    pAlarmThresholds = &sensitivityLevels[1];
+#if 0
+    // vertical tests
+    self.verticalFps = 0;
+    self.altFt = 1000;
+    cout << "identical altitude and vspeed " << endl;
+    checkVerticalThreat(self.altFt, self.verticalFps);
+    cout << "1000ft alt offset, dV=100 " << endl;
+    checkVerticalThreat(self.altFt+1000, 100);
+    cout << "-1000ft alt offset, dV=100 " << endl;
+    checkVerticalThreat(self.altFt-1000, 100);
+    cout << "3000ft alt offset, dV=10 " << endl;
+    checkVerticalThreat(self.altFt+3000, 10);
+    cout << "500ft alt offset, dV=100 " << endl;
+    checkVerticalThreat(self.altFt+500, 100);
+    cout << "500ft alt offset, dV=-100 " << endl;
+    checkVerticalThreat(self.altFt+500, -100);
+
+    // horizontal tests
+    self.heading = 0;
+    self.velocityKt = 0;
+    cout << "10nm behind, overtaking with 1Nm/s" << endl;
+    horizontalThreat(-180, 10, 0, 1/(SG_KT_TO_MPS*SG_METER_TO_NM));
+
+    cout << "10nm ahead, departing with 1Nm/s" << endl;
+    horizontalThreat(0, 20, 0, 1/(SG_KT_TO_MPS*SG_METER_TO_NM));
+
+    self.heading = 90;
+    self.velocityKt = 1/(SG_KT_TO_MPS*SG_METER_TO_NM);
+    cout << "10nm behind, overtaking with 1Nm/s at 90 degrees" << endl;
+    horizontalThreat(-90, 20, 90, 2/(SG_KT_TO_MPS*SG_METER_TO_NM));
+
+    self.heading = 20;
+    self.velocityKt = 1/(SG_KT_TO_MPS*SG_METER_TO_NM);
+    cout << "10nm behind, overtaking with 1Nm/s at 20 degrees" << endl;
+    horizontalThreat(200, 20, 20, 2/(SG_KT_TO_MPS*SG_METER_TO_NM));
+#endif
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// TCAS::AdvisoryGenerator ////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+TCAS::AdvisoryGenerator::AdvisoryGenerator(TCAS* _tcas) :
+    tcas(_tcas),
+    pSelf(NULL),
+    pCurrentThreat(NULL),
+    pAlarmThresholds(NULL)
+{
+}
+
+void
+TCAS::AdvisoryGenerator::init(const LocalInfo* _pSelf, const ThreatInfo* _pCurrentThreat)
+{
+    pCurrentThreat = _pCurrentThreat;
+    pSelf = _pSelf;
+}
+
+void
+TCAS::AdvisoryGenerator::setAlarmThresholds(const SensitivityLevel* _pAlarmThresholds)
+{
+    pAlarmThresholds = _pAlarmThresholds;
+}
+
+/** Calculate projected vertical separation at horizontal CPA. */
+float
+TCAS::AdvisoryGenerator::verticalSeparation(float newVerticalFps)
+{
+    // calculate relative vertical speed and altitude
+    float dV = pCurrentThreat->verticalFps - newVerticalFps;
+    float tau = pCurrentThreat->horizontalTau;
+    // don't use negative tau to project future separation...
+    if (tau < 0.5)
+        tau = 0.5;
+    return pCurrentThreat->relativeAltitudeFt + tau * dV;
+}
+
+/** Determine RA sense. */
+void
+TCAS::AdvisoryGenerator::determineRAsense(int& RASense, bool& isCrossing)
+{
+    /* [TCASII]: "[..] a two step process is used to select the appropriate RA for the encounter
+             *   geometry. The first step in the process is to select the RA sense, i.e., upward or downward." */
+    RASense = 0;
+    isCrossing = false;
+    
+    /* [TCASII]: "Based on the range and altitude tracks of the intruder, the CAS logic models the
+     *   intruder's flight path from its present position to CPA. The CAS logic then models upward
+     *   and downward sense RAs for own aircraft [..] to determine which sense provides the most
+     *   vertical separation at CPA." */
+    float upSenseRelAltFt   = verticalSeparation(+2000/60.0);
+    float downSenseRelAltFt = verticalSeparation(-2000/60.0);
+    if (fabs(upSenseRelAltFt) >= fabs(downSenseRelAltFt))
+        RASense = +1;  // upward
+    else
+        RASense = -1; // downward
+
+    /* [TCASII]: "In encounters where either of the senses results in the TCAS aircraft crossing through
+     *   the intruder's altitude, TCAS is designed to select the nonaltitude crossing sense if the
+     *   noncrossing sense provides the desired vertical separation, known as ALIM, at CPA." */
+    /* [TCASII]: "If ALIM cannot be obtained in the nonaltitude crossing sense, an altitude
+     *   crossing RA will be issued." */
+    if ((RASense > 0)&&
+        (pCurrentThreat->relativeAltitudeFt > 200))
+    {
+        // threat is above and RA is crossing
+        if (fabs(downSenseRelAltFt) > pAlarmThresholds->TA.ALIM)
+        {
+            // non-crossing descend is sufficient 
+            RASense = -1;
+        }
+        else
+        {
+            // keep crossing climb RA
+            isCrossing = true;
+        }
+    }
+    else
+    if ((RASense < 0)&&
+        (pCurrentThreat->relativeAltitudeFt < -200))
+    {
+        // threat is below and RA is crossing
+        if (fabs(upSenseRelAltFt) > pAlarmThresholds->TA.ALIM)
+        {
+            // non-crossing climb is sufficient 
+            RASense = 1;
+        }
+        else
+        {
+            // keep crossing descent RA
+            isCrossing = true;
+        }
+    }
+    // else: threat is at same altitude, keep optimal RA sense (non-crossing)
+
+#ifdef FEATURE_TCAS_DEBUG_ADV_GENERATOR
+        printf("  RASense: %i, crossing: %u, relAlt: %4.1f, upward separation: %4.1f, downward separation: %4.1f\n",
+               RASense,isCrossing,
+               pCurrentThreat->relativeAltitudeFt,
+               upSenseRelAltFt,downSenseRelAltFt);
+#endif
+}
+
+/** Determine suitable resolution advisories. */
+int
+TCAS::AdvisoryGenerator::resolution(int mode, int threatLevel, float rangeNm, float altFt,
+                                 float heading, float velocityKt)
+{
+    int RAOption = OptionNone;
+    int RA       = AdvisoryIntrusion;
+
+    // RAs are disabled under certain conditions
+    if (threatLevel == ThreatRA)
+    {
+        /* [TCASII]: "... less than 360 feet, TCAS considers the reporting aircraft
+         *   to be on the ground. If TCAS determines the intruder to be on the ground, it
+         *   inhibits the generation of advisories against this aircraft."*/
+        if (altFt < 360)
+            threatLevel = ThreatTA;
+
+        /* [EUROACAS]: "Certain RAs are inhibited at altitudes based on inputs from the radio altimeter:
+         *   [..] (c)1000ft (+/- 100ft) and below, all RAs are inhibited;" */
+        if (pSelf->altFt < 1000)
+            threatLevel = ThreatTA;
+        
+        // RAs only issued in mode "Auto" (= "TA/RA" mode)
+        if (mode != SwitchAuto)
+            threatLevel = ThreatTA;
+    }
+
+    bool isCrossing = false; 
+    int RASense = 0;
+    // determine suitable RAs
+    if (threatLevel == ThreatRA)
+    {
+        /* [TCASII]: "[..] a two step process is used to select the appropriate RA for the encounter
+         *   geometry. The first step in the process is to select the RA sense, i.e., upward or downward." */
+        determineRAsense(RASense, isCrossing);
+
+        /* second step: determine required strength */
+        if (RASense > 0)
+        {
+            // upward
+
+            if ((pSelf->verticalFps < -1000/60.0)&&
+                (!isCrossing))
+            {
+                // currently descending, see if reducing current descent is sufficient 
+                float relAltFt = verticalSeparation(-500/60.0);
+                if (relAltFt > pAlarmThresholds->TA.ALIM)
+                    RA |= AdvisoryAdjustVSpeed;
+            }
+            RA |= AdvisoryClimb;
+            if (isCrossing)
+                RAOption |= OptionCrossingClimb;
+        }
+
+        if (RASense < 0)
+        {
+            // downward
+
+            if ((pSelf->verticalFps > 1000/60.0)&&
+                (!isCrossing))
+            {
+                // currently climbing, see if reducing current climb is sufficient 
+                float relAltFt = verticalSeparation(500/60.0);
+                if (relAltFt < -pAlarmThresholds->TA.ALIM)
+                    RA |= AdvisoryAdjustVSpeed;
+            }
+            RA |= AdvisoryDescend;
+            if (isCrossing)
+                RAOption |= OptionCrossingDescent;
+        }
+
+        //TODO
+        /* [TCASII]: "When two TCAS-equipped aircraft are converging vertically with opposite rates
+         *   and are currently well separated in altitude, TCAS will first issue a vertical speed
+         *   limit (Negative) RA to reinforce the pilots' likely intention to level off at adjacent
+         *   flight levels." */
+        
+        //TODO
+        /* [TCASII]: "[..] if the CAS logic determines that the response to a Positive RA has provided
+         *   ALIM feet of vertical separation before CPA, the initial RA will be weakened to either a
+         *   Do Not Descend RA (after an initial Climb RA) or a Do Not Climb RA (after an initial
+         *   Descend RA)." */
+        
+        //TODO
+        /* [TCASII]: "TCAS is designed to inhibit Increase Descent RAs below 1450 feet AGL; */
+        
+        /* [TCASII]: "Descend RAs below 1100 feet AGL;" (inhibited) */
+        if (pSelf->altFt < 1100)
+        {
+            RA &= ~AdvisoryDescend;
+            //TODO Support "Do not descend" RA
+            RA |= AdvisoryIntrusion;
+        }
+    }
+
+#ifdef FEATURE_TCAS_DEBUG_ADV_GENERATOR
+    cout << "  resolution advisory: " << RA << endl;
+#endif
+
+    ResolutionAdvisory newAdvisory;
+    newAdvisory.RAOption    = RAOption;
+    newAdvisory.RA          = RA;
+    newAdvisory.threatLevel = threatLevel;
+    tcas->advisoryCoordinator.add(newAdvisory);
+    
+    return RA;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// TCAS ///////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+TCAS::TCAS(SGPropertyNode* pNode) :
+    name("tcas"),
+    num(0),
+    nextUpdateTime(0),
+    selfTestStep(0),
+    properties_handler(this),
+    threatDetector(this),
+    advisoryCoordinator(this),
+    advisoryGenerator(this),
+    annunciator(this)
+{
+    for (int i = 0; i < pNode->nChildren(); ++i)
+    {
+        SGPropertyNode* pChild = pNode->getChild(i);
+        string cname = pChild->getName();
+        string cval = pChild->getStringValue();
+
+        if (cname == "name")
+            name = cval;
+        else if (cname == "number")
+            num = pChild->getIntValue();
+        else
+        {
+            SG_LOG(SG_INSTR, SG_WARN, "Error in TCAS config logic");
+            if (name.length())
+                SG_LOG(SG_INSTR, SG_WARN, "Section = " << name);
+        }
+    }
+}
+
+void
+TCAS::init(void)
+{
+    annunciator.init();
+    advisoryCoordinator.init();
+    threatDetector.init();
+}
+
+void
+TCAS::bind(void)
+{
+    SGPropertyNode* node = fgGetNode(("/instrumentation/" + name).c_str(), num, true);
+
+    nodeServiceable  = node->getNode("serviceable", true);
+
+    // TCAS mode selection (0=off, 1=standby, 2=TA only, 3=auto(TA/RA) ) 
+    nodeModeSwitch   = node->getNode("inputs/mode", true);
+    // self-test button
+    nodeSelfTest     = node->getNode("inputs/self-test", true);
+    // default value
+    nodeSelfTest->setBoolValue(false);
+
+#ifdef FEATURE_TCAS_DEBUG_PROPERTIES
+    SGPropertyNode* nodeDebug = node->getNode("debug", true);
+    // debug triggers
+    nodeDebugTrigger = nodeDebug->getNode("threat-trigger", true);
+    nodeDebugRA      = nodeDebug->getNode("threat-RA",      true);
+    nodeDebugThreat  = nodeDebug->getNode("threat-level",   true);
+    // default values
+    nodeDebugTrigger->setBoolValue(false);
+    nodeDebugRA->setIntValue(3);
+    nodeDebugThreat->setIntValue(1);
+#endif
+
+    annunciator.bind(node);
+    advisoryCoordinator.bind(node);
+}
+
+void
+TCAS::unbind(void)
+{
+    properties_handler.unbind();
+}
+
+/** Monitor traffic for safety threats. */
+void
+TCAS::update(double dt)
+{
+    if (!nodeServiceable->getBoolValue())
+        return;
+    int mode = nodeModeSwitch->getIntValue();
+    if (mode == SwitchOff)
+        return;
+
+    nextUpdateTime -= dt;
+    if (nextUpdateTime <= 0.0 )
+    {
+        nextUpdateTime = 1.0;
+
+        // get aircrafts current position/speed/heading
+        threatDetector.update();
+
+        // clear old threats
+        advisoryCoordinator.clear();
+
+        if (nodeSelfTest->getBoolValue())
+        {
+            if (threatDetector.getVelocityKt() >= 40)
+            {
+                // disable self-test when plane moves above taxiing speed
+                nodeSelfTest->setBoolValue(false);
+                selfTestStep = 0;
+            }
+            else
+            {
+                selfTest();
+                // speed-up self test
+                nextUpdateTime = 0;
+                // no further TCAS processing during self-test
+                return;
+            }
+        }
+
+#ifdef FEATURE_TCAS_DEBUG_PROPERTIES
+        if (nodeDebugTrigger->getBoolValue())
+        {
+            // debugging test
+            ResolutionAdvisory debugAdvisory;
+            debugAdvisory.RAOption    = OptionNone;
+            debugAdvisory.RA          = nodeDebugRA->getIntValue();
+            debugAdvisory.threatLevel = nodeDebugThreat->getIntValue();
+            advisoryCoordinator.add(debugAdvisory);
+        }
+        else
+#endif
+        {
+            SGPropertyNode* pAi = fgGetNode("/ai/models", true);
+
+            // check all aircraft
+            for (int i = pAi->nChildren() - 1; i >= -1; i--)
+            {
+                SGPropertyNode* pModel = pAi->getChild(i);
+                if ((pModel)&&(pModel->nChildren()))
+                {
+                    int threatLevel = threatDetector.checkThreat(mode, pModel);
+                    /* expose aircraft threat-level (to be used by other instruments,
+                     * i.e. TCAS display) */
+                    pModel->setIntValue("tcas/threat-level", threatLevel);
+                }
+            }
+        }
+        advisoryCoordinator.update(mode);
+    }
+    annunciator.update();
+}
+
+/** Run a single self-test iteration. */
+void
+TCAS::selfTest(void)
+{
+    annunciator.update();
+    if (annunciator.isPlaying())
+    {
+        return;
+    }
+
+    ResolutionAdvisory newAdvisory;
+    newAdvisory.threatLevel = ThreatRA;
+    newAdvisory.RA          = AdvisoryClear;
+    newAdvisory.RAOption    = OptionNone;
+    // TCAS audio is disabled below 500ft
+    threatDetector.setAlt(501);
+
+    // trigger various advisories
+    switch(selfTestStep)
+    {
+        case 0:
+            newAdvisory.RA = AdvisoryIntrusion;
+            newAdvisory.threatLevel = ThreatTA;
+            break;
+        case 1:
+            newAdvisory.RA = AdvisoryClimb;
+            break;
+        case 2:
+            newAdvisory.RA = AdvisoryClimb;
+            newAdvisory.RAOption = OptionIncreaseClimb;
+            break;
+        case 3:
+            newAdvisory.RA = AdvisoryClimb;
+            newAdvisory.RAOption = OptionCrossingClimb;
+            break;
+        case 4:
+            newAdvisory.RA = AdvisoryDescend;
+            break;
+        case 5:
+            newAdvisory.RA = AdvisoryDescend;
+            newAdvisory.RAOption = OptionIncreaseDescend;
+            break;
+        case 6:
+            newAdvisory.RA = AdvisoryDescend;
+            newAdvisory.RAOption = OptionCrossingDescent;
+            break;
+        case 7:
+            newAdvisory.RA = AdvisoryAdjustVSpeed;
+            break;
+        case 8:
+            newAdvisory.RA = AdvisoryMaintVSpeed;
+            break;
+        case 9:
+            newAdvisory.RA = AdvisoryMonitorVSpeed;
+            break;
+        case 10:
+            newAdvisory.threatLevel = ThreatNone;
+            newAdvisory.RA = AdvisoryClear;
+            break;
+        case 11:
+            annunciator.test(true);
+            selfTestStep+=2;
+            return;
+        default:
+            nodeSelfTest->setBoolValue(false);
+            selfTestStep = 0;
+            return;
+    }
+
+    advisoryCoordinator.add(newAdvisory);
+    advisoryCoordinator.update(SwitchAuto);
+
+    selfTestStep++;
+}
diff --git a/src/Instrumentation/tcas.hxx b/src/Instrumentation/tcas.hxx
new file mode 100644 (file)
index 0000000..6882c83
--- /dev/null
@@ -0,0 +1,377 @@
+// tcas.hxx -- Traffic Alert and Collision Avoidance System (TCAS)
+//
+// Written by Thorsten Brehm, started December 2010.
+//
+// Copyright (C) 2010 Thorsten Brehm - brehmt (at) gmail com
+//
+// This program is free software; you can redistribute it and/or
+// modify it under the terms of the GNU General Public License as
+// published by the Free Software Foundation; either version 2 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; if not, write to the Free Software
+// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+#ifndef __INSTRUMENTS_TCAS_HXX
+#define __INSTRUMENTS_TCAS_HXX
+
+#include <assert.h>
+
+#include <vector>
+#include <deque>
+#include <map>
+
+#include <simgear/props/props.hxx>
+#include <simgear/sound/sample_openal.hxx>
+#include <simgear/structure/subsystem_mgr.hxx>
+#include "mk_viii.hxx" // voiceplayer only
+
+using std::vector;
+using std::deque;
+using std::map;
+
+class SGSampleGroup;
+
+#include <Main/globals.hxx>
+
+#ifdef _MSC_VER
+#  pragma warning( push )
+#  pragma warning( disable: 4355 )
+#endif
+
+///////////////////////////////////////////////////////////////////////////////
+// TCAS  //////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+class TCAS : public SGSubsystem
+{
+
+    typedef enum
+    {
+        AdvisoryClear         = 0,                          /*< Clear of traffic */
+        AdvisoryIntrusion     = 1,                          /*< Intrusion flag */
+        AdvisoryClimb         = AdvisoryIntrusion|(1 << 1), /*< RA climb */
+        AdvisoryDescend       = AdvisoryIntrusion|(1 << 2), /*< RA descend */
+        AdvisoryAdjustVSpeed  = AdvisoryIntrusion|(1 << 3), /*< RA adjust vertical speed (TCAS II 7.0 only) */
+        AdvisoryMaintVSpeed   = AdvisoryIntrusion|(1 << 4), /*< RA maintain vertical speed */
+        AdvisoryMonitorVSpeed = AdvisoryIntrusion|(1 << 5), /*< RA monitor vertical speed */
+        AdvisoryLevelOff      = AdvisoryIntrusion|(1 << 6)  /*< RA level off (TCAS II 7.1 only) */
+    } EnumAdvisory;
+
+    typedef enum
+    {
+        OptionNone            = 0,        /*< no option modifier */
+        OptionIncreaseClimb   = (1 << 0), /*< increase climb */
+        OptionIncreaseDescend = (1 << 1), /*< increase descend */
+        OptionCrossingClimb   = (1 << 2), /*< crossing climb */
+        OptionCrossingDescent = (1 << 3)  /*< crossing descent */
+    } EnumAdvisoryOption;
+
+    typedef enum
+    {
+        SwitchOff        = 0, /*< TCAS switched off */
+        SwitchStandby    = 1, /*< TCAS standby (no TA/RA) */
+        SwitchTaOnly     = 2, /*< TCAS in TA-only mode (no RA) */
+        SwitchAuto       = 3  /*< TCAS in TA/RA mode */
+    } EnumModeSwitch;
+
+    typedef enum
+    {
+        ThreatInvisible    = -1,/*< Traffic is invisible to TCAS (i.e. no transponder) */
+        ThreatNone         = 0, /*< Traffic is visible but no threat. */
+        ThreatProximity    = 1, /*< Proximity intruder traffic (no threat). */
+        ThreatTA           = 2, /*< TA-level threat traffic. */
+        ThreatRA           = 3  /*< RA-level threat traffic. */
+    } EnumThreatLevel;
+
+    typedef struct
+    {
+      int  threatLevel;  /*< intruder threat level: 0=clear, 1=proximity,
+                             2=intruder, 3=proximity intruder */
+      int  RA;           /*< resolution advisory */
+      int  RAOption;     /*< option flags for advisory */
+    } ResolutionAdvisory;
+
+    typedef struct
+    {
+        float Tau;       /*< vertical/horizontal protection range in seconds */ 
+        float DMOD;      /*< horizontal protection range in nm */
+        float ALIM;      /*< vertical protection range in ft */
+    } Thresholds;
+
+    typedef struct
+    {
+        double    maxAltitude; /*< max altitude for this sensitivity level */
+        int        sl;         /*< sensitivity level */
+        Thresholds TA;         /*< thresholds for TA-level threats */
+        Thresholds RA;         /*< thresholds for RA-level threats */
+    } SensitivityLevel;
+
+    typedef struct
+    {
+        bool  verticalTA;
+        bool  verticalRA;
+        bool  horizontalTA;
+        bool  horizontalRA;
+        float horizontalTau;
+        float verticalTau;
+        float relativeAltitudeFt;
+        float verticalFps;
+    } ThreatInfo;
+
+    typedef struct
+    {
+        double lat;
+        double lon;
+        float  altFt;
+        float  heading;
+        float  velocityKt;
+        float  verticalFps;
+    } LocalInfo; /*< info structure for local aircraft */
+
+//    /////////////////////////////////////////////////////////////////////////////
+//    // TCAS::Timer //////////////////////////////////////////////////////////////
+//    /////////////////////////////////////////////////////////////////////////////
+//
+//    class Timer
+//    {
+//        double start_time;
+//
+//    public:
+//        bool   running;
+//
+//        inline Timer ()
+//          : running(false) {}
+//
+//        inline void start () { running = true; start_time = globals->get_sim_time_sec(); }
+//        inline void stop ()  { running = false; }
+//        inline double elapsed () const { assert(running); return globals->get_sim_time_sec() - start_time; }
+//        inline double start_or_elapsed ()
+//        {
+//            if (running)
+//                return elapsed();
+//            else
+//            {
+//                start();
+//                return 0;
+//            }
+//        }
+//    };
+
+    /////////////////////////////////////////////////////////////////////////////
+    // TCAS::PropertiesHandler ///////////////////////////////////////////////
+    /////////////////////////////////////////////////////////////////////////////
+
+    class PropertiesHandler : public FGVoicePlayer::PropertiesHandler
+    {
+      TCAS *tcas;
+
+    public:
+      PropertiesHandler (TCAS *device) :
+        FGVoicePlayer::PropertiesHandler(), tcas(device) {}
+
+      PropertiesHandler (void) : FGVoicePlayer::PropertiesHandler() {}
+    };
+
+    /////////////////////////////////////////////////////////////////////////////
+    // TCAS::VoicePlayer ////////////////////////////////////////////////////////
+    /////////////////////////////////////////////////////////////////////////////
+
+    class VoicePlayer :
+        public FGVoicePlayer
+    {
+    public:
+        VoicePlayer (TCAS* tcas) :
+          FGVoicePlayer(&tcas->properties_handler, "tcas") {}
+
+        ~VoicePlayer (void) {}
+
+        void init    (void);
+
+        struct
+        {
+          Voice* pTrafficTraffic;
+          Voice* pClimb;
+          Voice* pClimbNow;
+          Voice* pClimbCrossing;
+          Voice* pClimbIncrease;
+          Voice* pDescend;
+          Voice* pDescendNow;
+          Voice* pDescendCrossing;
+          Voice* pDescendIncrease;
+          Voice* pClear;
+          Voice* pAdjustVSpeed;
+          Voice* pMaintVSpeed;
+          Voice* pMonitorVSpeed;
+          Voice* pLevelOff;
+          Voice* pTestOk;
+          Voice* pTestFail;
+        } Voices;
+    private:
+        SGPropertyNode_ptr nodeSoundFilePrefix;
+    };
+
+    /////////////////////////////////////////////////////////////////////////////
+    // TCAS::Annunciator ////////////////////////////////////////////////////////
+    /////////////////////////////////////////////////////////////////////////////
+
+    class Annunciator
+    {
+    public:
+        Annunciator    (TCAS* tcas);
+        ~Annunciator   (void) {}
+        void bind      (SGPropertyNode* node);
+        void init      (void);
+        void update    (void);
+
+        void trigger   (const ResolutionAdvisory& newAdvisory, bool revertedRA);
+        void test      (bool testOk);
+        void clear     (void);
+        bool isPlaying (void) { return voicePlayer.is_playing();}
+
+    private:
+        ResolutionAdvisory previous;
+        FGVoicePlayer::Voice* pLastVoice;
+        VoicePlayer voicePlayer;
+        SGPropertyNode_ptr nodeGpwsAlertOn;
+    };
+
+    /////////////////////////////////////////////////////////////////////////////
+    // TCAS::AdvisoryCoordinator ////////////////////////////////////////////////
+    /////////////////////////////////////////////////////////////////////////////
+
+    class AdvisoryCoordinator
+    {
+    public:
+        AdvisoryCoordinator  (TCAS* _tcas);
+        ~AdvisoryCoordinator (void) {}
+
+        void bind            (SGPropertyNode* node);
+        void init            (void);
+        void update          (int mode);
+
+        void clear           (void);
+        void add             (const ResolutionAdvisory& newAdvisory);
+
+    private:
+        TCAS* tcas;
+        double lastTATime;
+        ResolutionAdvisory current;
+        ResolutionAdvisory previous;
+        SGPropertyNode_ptr nodeTAWarning;
+    };
+
+    /////////////////////////////////////////////////////////////////////////////
+    // TCAS::AdvisoryGenerator //////////////////////////////////////////////////
+    /////////////////////////////////////////////////////////////////////////////
+
+    class AdvisoryGenerator
+    {
+    public:
+        AdvisoryGenerator        (TCAS* _tcas);
+        ~AdvisoryGenerator       (void) {}
+
+        void init                (const LocalInfo* _pSelf, const ThreatInfo* _pCurrentThreat);
+
+        void setAlarmThresholds  (const SensitivityLevel* _pAlarmThresholds);
+
+        int   resolution         (int mode, int threatLevel, float distanceNm,
+                                  float altFt, float heading, float velocityKt);
+
+    private:
+        float verticalSeparation (float newVerticalFps);
+        void  determineRAsense   (int& RASense, bool& isCrossing);
+
+    private:
+        TCAS*             tcas;
+        const LocalInfo*  pSelf;          /*< info structure for local aircraft */
+        const ThreatInfo* pCurrentThreat; /*< info structure on current intruder/threat */
+        const SensitivityLevel* pAlarmThresholds;
+    };
+
+    /////////////////////////////////////////////////////////////////////////////
+    // TCAS::ThreatDetector /////////////////////////////////////////////////////
+    /////////////////////////////////////////////////////////////////////////////
+
+    class ThreatDetector
+    {
+    public:
+        ThreatDetector            (TCAS* _tcas);
+        ~ThreatDetector           (void) {}
+
+        void  init                (void);
+        void  update              (void);
+
+        bool  checkTransponder    (const SGPropertyNode* pModel, float velocityKt);
+        int   checkThreat         (int mode, const SGPropertyNode* pModel);
+        void  checkVerticalThreat (void);
+        void  horizontalThreat    (float bearing, float distanceNm, float heading,
+                                   float velocityKt);
+
+        void  setAlt              (float altFt) { self.altFt = altFt;}
+        float getAlt              (void)        { return self.altFt;}
+        float getVelocityKt       (void)        { return self.velocityKt;}
+
+    private:
+        void  unitTest            (void);
+
+    private:
+        static const SensitivityLevel sensitivityLevels[];
+
+        TCAS*              tcas;
+        int                checkCount;
+
+        SGPropertyNode_ptr nodeLat;
+        SGPropertyNode_ptr nodeLon;
+        SGPropertyNode_ptr nodeAlt;
+        SGPropertyNode_ptr nodeHeading;
+        SGPropertyNode_ptr nodeVelocity;
+        SGPropertyNode_ptr nodeVerticalFps;
+
+        LocalInfo          self;          /*< info structure for local aircraft */
+        ThreatInfo         currentThreat; /*< info structure on current intruder/threat */
+        const SensitivityLevel* pAlarmThresholds;
+    };
+
+private:
+    string              name;
+    int                 num;
+    double              nextUpdateTime;
+    int                 selfTestStep;
+
+    SGPropertyNode_ptr  nodeModeSwitch;
+    SGPropertyNode_ptr  nodeServiceable;
+    SGPropertyNode_ptr  nodeSelfTest;
+    SGPropertyNode_ptr  nodeDebugTrigger;
+    SGPropertyNode_ptr  nodeDebugRA;
+    SGPropertyNode_ptr  nodeDebugThreat;
+
+    PropertiesHandler   properties_handler;
+    ThreatDetector      threatDetector;
+    AdvisoryCoordinator advisoryCoordinator;
+    AdvisoryGenerator   advisoryGenerator;
+    Annunciator         annunciator;
+
+private:
+    void selfTest       (void);
+
+public:
+    TCAS (SGPropertyNode* node);
+
+    virtual void bind   (void);
+    virtual void unbind (void);
+    virtual void init   (void);
+    virtual void update (double dt);
+};
+
+#ifdef _MSC_VER
+#  pragma warning( pop )
+#endif
+
+#endif // __INSTRUMENTS_TCAS_HXX