This code example demonstrates a way to provide traffic information about airplanes to X-Plane and third-party traffic consumers like aircraft instruments or an EFB connected to ADSB-out.

A provider of traffic would use XPLMInstance to place 3d object of aircraft in the world, and then use TCAS override to tell the user about the other airplanes in this airspace. A maximum of 64 planes, so one user and 63 targets, can be supplied at any time, regardless of the user’s setting for AI plane count.

#include "XPLMProcessing.h"
#include "XPLMDataAccess.h"
#include "XPLMPlanes.h"
#include "XPLMUtilities.h"
#include "XPLMPlugin.h"
#include <cassert>
#include <cstring>

/* This plugin creates four traffic targets that will fly circles around the users' plane. These traffic
 * targets exist purely as TCAS targets, not as 3D objects, as such would usually be placed by XPLMInstance
 */


// for convenience, I'm going to define the altitude in feet and the distance in nm and convert later
static const float fttomtr = 0.3048f;
static const float nmtomtr = 1852.f;

// how many targets this plugin generates.
static const std::size_t TARGETS = 4;

// datarefs we are going to write to
static XPLMDataRef id = NULL;
static XPLMDataRef flt_id = NULL;
static XPLMDataRef brg = NULL;
static XPLMDataRef dis = NULL;
static XPLMDataRef alt = NULL;
static XPLMDataRef override = NULL;

// datarefs for our own plane
static XPLMDataRef psi = NULL;
static XPLMDataRef ele = NULL;

// whether our plugin is in charge
static bool plugin_owns_tcas = false;


static int      ids[TARGETS]    = { 0XA51B64, 0XAB90C2, 0XADCB98, 0XA08DB8 };    // Required: unique ID for the target. Must be 24bit number. 
static char tailnum[TARGETS][8] = {"N428X", "N844X", "N98825", "N1349Z"};       // Optional: Flight ID is item 7 of the ICAO flightplan. So it can be the tailnumber OR the flightnumber! Cannot be longer than 7 chars+nullbyte!

// the initial position of our four targets. We'll place them directly north, east, etc. of us
// at various altitudes and distances between 3 and 6 nautical miles
static float absbrgs[] = { 0, 90, 180, 270 };
static float absalts[] = { 1000, 1500, 2000, 4000 };
static float   dists[] = { 6*nmtomtr, 5*nmtomtr, 4*nmtomtr, 3*nmtomtr };

// this flightloop callback will be called every frame to update the targets
float floop_cb(float elapsed1, float elapsed2, int ctr, void* refcon)
{
    static float relbrgs[TARGETS];
    static float relalts[TARGETS];

    // make some targets change altitude
    absalts[0] += 400 * elapsed1 / 60.f;                            // target is climbing 400fpm
    absalts[3] -= 600 * elapsed1 / 60.f;                            // target descending 600fpm

    for (int i = 0; i < TARGETS; ++i)
    {
        // targets are just flying perfect circles around the user.
        absbrgs[i] += (i+1) * elapsed1;                             // this just makes the targets fly circles of varying speed
        relbrgs[i] = absbrgs[i] - XPLMGetDataf(psi);                // convert to relative position for TCAS dataref. Use true_psi, not hpath or something else

        relalts[i] = absalts[i] * fttomtr - XPLMGetDatad(ele);      // convert to relative position for TCAS dataref. Use elevation, not local_y!
    }

    // if we are in charge, we can write four targets to the four TCAS datarefs, starting at index 1
    // Note this dataref write would do nothing if we hadn't acquired the planes and set override_TCAS
    if (plugin_owns_tcas)
    {
        // These relative coordinates, or the absolute x/y/z double coordinates must be updated to keep the target flying, obviously.
        // X-Plane will forget about your target if you don't update it for 10 consecutive frames.
        XPLMSetDatavf(brg, relbrgs, 1, TARGETS);
        XPLMSetDatavf(dis, dists, 1, TARGETS);
        XPLMSetDatavf(alt, relalts, 1, TARGETS);
        // You could also update sim/cockpit2/tcas/targets/position/double/plane1_x, plane1_y, etc.. In which case X-Plane would update the relative bearings for you
        // So for one target, you can write either absolute coorindates or relative bearings, but not both!
        // For mulitple targets, you can update some targets in relative mode, and others in absolute mode. 
    }

    // be sure to be called every frame. A target not updated for 10 successive frames will be dropped.
    return -1;
}


// A simple reset we will call every minute to reset the targets to their initial position and altitude
float reset_cb(float elapsed1, float elapsed2, int ctr, void* refcon)
{
    absbrgs[0] = 0;
    absbrgs[1] = 90;
    absbrgs[2] = 180;
    absbrgs[3] = 270;

    absalts[0] = 1000;
    absalts[3] = 4000;
    return 60;                      // call me again in a minute
}


// we call this function when we succesfully acquired the AI planes.
// Arthur says: BRILLIANT! My Jets Now!
void my_planes_now()
{
    XPLMDebugString("TCAS test plugin now has the AI planes!\n");
    XPLMSetDatai(override, 1);      // If you try to set this dataref when not owning the planes, it will fail!
    
    // query the array size. This might change with X-Plane updates.
    std::size_t max_targets = XPLMGetDatavi(id, NULL, 0, 0);
    assert(TARGETS < max_targets);

    XPLMSetActiveAircraftCount(TARGETS);  // This will give you four targets, even if the user's AI plane count is set to 0. This can be as high as 63!
    plugin_owns_tcas = true;


    // As long as you keep updating the positions, X-Plane will remember the ID. 
    // These IDs can be used by other plugins to keep track of your aircraft if you shuffle slots. 
    // Note that the ID cannot be left 0! X-Plane will not update your target's dependent datarefs if it has no ID!!
    // If you haven't updated a target for 10 frames, X-Plane will forget it and reset the ID of the slot to 0.
    XPLMSetDatavi(id, ids, 1, TARGETS);


    // Each target can have a 7 ASCII character flight ID, usually the tailnumber or flightnumber
    // it consists of an 8 byte character array, which is null terminated.
    // The array is 64*8 bytes long, and the first 8 bytes are the user's tailnumber obviously.
    // Note that this is, unlike the Mode-S ID, totally optional.
    // But it is nice to see the tainumber on the map obviously!
    for (int i = 1; i <= TARGETS; ++i)
        XPLMSetDatab(flt_id, tailnum[i - 1], i * 8, strnlen(tailnum[i-1], 8));  // copy at most 8 characters, but not more than we actually have.


    // start updating
    XPLMRegisterFlightLoopCallback(floop_cb, 1, NULL);
    XPLMRegisterFlightLoopCallback(reset_cb, 60, NULL);
}

// we call this function when we want to give up on controlling the AI planes
// For example, a network plugin would do this as soon as you disconnect from your multiplayer session!
void someone_elses_planes_now()
{
    // stop updating
    XPLMUnregisterFlightLoopCallback(floop_cb, NULL);
    XPLMUnregisterFlightLoopCallback(reset_cb, NULL);
    // relinquish control
    plugin_owns_tcas = false;
    XPLMSetDatai(override, 0);      // order is important! Relinquish the override first
    XPLMReleasePlanes();            // Then release the AI planes to another plugin! Note that another plugins AcquirePlanes callback function might be called here synchronously
}

// this is a callback that will be called by X-Plane, if we asked for planes, but another plugin had the planes at the time
// but now this other plugin has given up the planes. They essentially yielded control to us. So the planes are up for grabs again!!
void retry_acquiring_planes(void*)
{
    if (!XPLMAcquirePlanes(NULL, &retry_acquiring_planes, NULL))
    {
        // Damn, someone else cut in the queue before us!
        // this can happen if more than two plugins are all competing for AI. 
        XPLMDebugString("TCAS test plugin could not get the AI planes, even after the previous plugin gave them up. We are waiting for the next chance\n");
    }
    else
    {
        // Acquisition succeded.
        my_planes_now();
    }
}

PLUGIN_API int XPluginStart(char* name, char* sig, char* desc)
{
    strcpy(name, "TCAS override test");
    strcpy(sig, "com.laminarresearch.test.tcas");
    strcpy(desc, "Test plugin for TCAS override datarefs");
    return 1;
}

PLUGIN_API void XPluginStop(void)
{
}

PLUGIN_API int XPluginEnable(void)
{
    psi = XPLMFindDataRef("sim/flightmodel/position/true_psi");
    ele = XPLMFindDataRef("sim/flightmodel/position/elevation");

    // these datarefs were read-only until 11.50
    brg = XPLMFindDataRef("sim/cockpit2/tcas/indicators/relative_bearing_degs");
    dis = XPLMFindDataRef("sim/cockpit2/tcas/indicators/relative_distance_mtrs");
    alt = XPLMFindDataRef("sim/cockpit2/tcas/indicators/relative_altitude_mtrs");
    // these datarefs are new to 11.50
    id = XPLMFindDataRef("sim/cockpit2/tcas/targets/modeS_id");         // array of 64 integers
    flt_id = XPLMFindDataRef("sim/cockpit2/tcas/targets/flight_id");    // array of 64*8 bytes

    // this dataref can only be set if we own the AI planes!
    override = XPLMFindDataRef("sim/operation/override/override_TCAS");


    /// STOP
    /// DROP AND LISTEN
    // In a real application, you would only do the next step if you are immediately ready to supply traffic.
    // I.e. if you are connected to a session if you are a multiplayer plugin. Don't preemptively acquire the traffic
    // just because you might connect to a session some time later!
    // So this piecce of code is probably not going to be in XPluginEnable for you. 
    // It is going to be wherever you are actually done establishing your traffic source!

    // try to acquire planes. If a different plugin has them, this will fail.
    // If the other plugin releases them, our callback will be called.
    if (!XPLMAcquirePlanes(NULL, &retry_acquiring_planes, NULL))
    {
        // If acquisition has failed, gather some intelligence on who currently owns the planes
        int total, active;
        XPLMPluginID controller;
        char who[256];
        XPLMCountAircraft(&total, &active, &controller);
        XPLMGetPluginInfo(controller, who, NULL, NULL, NULL);
        XPLMDebugString("TCAS test plugin could not get the AI planes, because ");
        XPLMDebugString(who);
        XPLMDebugString(" owns the AI planes now. We'll get them when he relinquishes control.\n");
        // Note that the retry callback will be called when this other guy gives up the planes.
    }
    else
    {
        // Acquisition succeded.
        my_planes_now();
    }

    return 1;
}

PLUGIN_API void XPluginDisable(void)
{
    someone_elses_planes_now();
}

PLUGIN_API void XPluginReceiveMessage(XPLMPluginID from, int msg, void* param)
{
    if (msg == XPLM_MSG_RELEASE_PLANES)
    {
        // Some other plugin wants the AI planes. Since this is just a dummy traffic provider, we yield
        // to a real traffic provider. Deactivate myself now!
        // If this was send to a VATSIM plugin while the user is connected, of course it would just ignore this message.
        someone_elses_planes_now();

        char name[256];
        XPLMGetPluginInfo(from, name, NULL, NULL, NULL);
        XPLMDebugString("TCAS test plugin has given up control of the AI planes to ");
        XPLMDebugString(name);
        XPLMDebugString("\n");
    }
}

2 comments on “Overriding TCAS

Leave a Reply

Your email address will not be published. Required fields are marked *

Please do not report bugs in the blog comments.
Only bugs reported via the X-Plane Bug Reporter are tracked.