This commit is contained in:
Oliver Jowett 2017-12-07 10:34:08 -06:00
parent 6986b3847f
commit 161d2d8ded
8 changed files with 1088 additions and 421 deletions

View file

@ -374,51 +374,49 @@ static int decodeBDS40(struct modesMessage *mm, bool store)
if (store) { if (store) {
mm->commb_format = COMMB_VERTICAL_INTENT; mm->commb_format = COMMB_VERTICAL_INTENT;
mm->intent.valid = 1;
if (mcp_valid) { if (mcp_valid) {
mm->intent.mcp_altitude_valid = 1; mm->nav.mcp_altitude_valid = 1;
mm->intent.mcp_altitude = mcp_alt; mm->nav.mcp_altitude = mcp_alt;
} }
if (fms_valid) { if (fms_valid) {
mm->intent.fms_altitude_valid = 1; mm->nav.fms_altitude_valid = 1;
mm->intent.fms_altitude = fms_alt; mm->nav.fms_altitude = fms_alt;
} }
if (baro_valid) { if (baro_valid) {
mm->intent.alt_setting_valid = 1; mm->nav.qnh_valid = 1;
mm->intent.alt_setting = baro_setting; mm->nav.qnh = baro_setting;
} }
if (mode_valid) { if (mode_valid) {
mm->intent.modes_valid = 1; mm->nav.modes_valid = 1;
mm->intent.modes = mm->nav.modes =
((mode_raw & 4) ? INTENT_MODE_VNAV : 0) | ((mode_raw & 4) ? NAV_MODE_VNAV : 0) |
((mode_raw & 2) ? INTENT_MODE_ALT_HOLD : 0) | ((mode_raw & 2) ? NAV_MODE_ALT_HOLD : 0) |
((mode_raw & 1) ? INTENT_MODE_APPROACH : 0); ((mode_raw & 1) ? NAV_MODE_APPROACH : 0);
} }
if (source_valid) { if (source_valid) {
switch (source_raw) { switch (source_raw) {
case 0: case 0:
mm->intent.altitude_source = INTENT_ALT_UNKNOWN; mm->nav.altitude_source = NAV_ALT_UNKNOWN;
break; break;
case 1: case 1:
mm->intent.altitude_source = INTENT_ALT_AIRCRAFT; mm->nav.altitude_source = NAV_ALT_AIRCRAFT;
break; break;
case 2: case 2:
mm->intent.altitude_source = INTENT_ALT_MCP; mm->nav.altitude_source = NAV_ALT_MCP;
break; break;
case 3: case 3:
mm->intent.altitude_source = INTENT_ALT_FMS; mm->nav.altitude_source = NAV_ALT_FMS;
break; break;
default: default:
mm->intent.altitude_source = INTENT_ALT_INVALID; mm->nav.altitude_source = NAV_ALT_INVALID;
break; break;
} }
} else { } else {
mm->intent.altitude_source = INTENT_ALT_INVALID; mm->nav.altitude_source = NAV_ALT_INVALID;
} }
} }
@ -570,7 +568,7 @@ static int decodeBDS50(struct modesMessage *mm, bool store)
if (gs_valid) { if (gs_valid) {
mm->gs_valid = 1; mm->gs_valid = 1;
mm->gs = gs; mm->gs.v0 = mm->gs.v2 = gs;
} }
if (track_rate_valid) { if (track_rate_valid) {

View file

@ -178,7 +178,7 @@ typedef enum {
} airground_t; } airground_t;
typedef enum { typedef enum {
SIL_PER_SAMPLE, SIL_PER_HOUR SIL_INVALID, SIL_PER_SAMPLE, SIL_PER_HOUR
} sil_type_t; } sil_type_t;
typedef enum { typedef enum {
@ -186,11 +186,12 @@ typedef enum {
} cpr_type_t; } cpr_type_t;
typedef enum { typedef enum {
HEADING_INVALID, // Not set
HEADING_GROUND_TRACK, // Direction of track over ground, degrees clockwise from true north HEADING_GROUND_TRACK, // Direction of track over ground, degrees clockwise from true north
HEADING_TRUE, // Heading, degrees clockwise from true north HEADING_TRUE, // Heading, degrees clockwise from true north
HEADING_MAGNETIC, // Heading, degrees clockwise from magnetic north HEADING_MAGNETIC, // Heading, degrees clockwise from magnetic north
HEADING_MAGNETIC_OR_TRUE, // HEADING_MAGNETIC or HEADING_TRUE depending on the HRD bit in opstatus HEADING_MAGNETIC_OR_TRUE, // HEADING_MAGNETIC or HEADING_TRUE depending on the HRD bit in opstatus
HEADING_TRACK_OR_HEADING // HEADING_GROUND_TRACK or HEADING_REF_DIR depending on the TAH bit in opstatus HEADING_TRACK_OR_HEADING // GROUND_TRACK / MAGNETIC / TRUE depending on the TAH bit in opstatus
} heading_type_t; } heading_type_t;
typedef enum { typedef enum {
@ -206,12 +207,13 @@ typedef enum {
} commb_format_t; } commb_format_t;
typedef enum { typedef enum {
INTENT_MODE_AUTOPILOT = 1, NAV_MODE_AUTOPILOT = 1,
INTENT_MODE_VNAV = 2, NAV_MODE_VNAV = 2,
INTENT_MODE_ALT_HOLD = 4, NAV_MODE_ALT_HOLD = 4,
INTENT_MODE_APPROACH = 8, NAV_MODE_APPROACH = 8,
INTENT_MODE_LNAV = 16 NAV_MODE_LNAV = 16,
} intent_modes_t; NAV_MODE_TCAS = 32
} nav_modes_t;
#define MODES_NON_ICAO_ADDRESS (1<<24) // Set on addresses to indicate they are not ICAO addresses #define MODES_NON_ICAO_ADDRESS (1<<24) // Set on addresses to indicate they are not ICAO addresses
@ -422,7 +424,8 @@ struct modesMessage {
unsigned char MV[7]; unsigned char MV[7];
// Decoded data // Decoded data
unsigned altitude_valid : 1; unsigned altitude_baro_valid : 1;
unsigned altitude_geom_valid : 1;
unsigned track_valid : 1; unsigned track_valid : 1;
unsigned track_rate_valid : 1; unsigned track_rate_valid : 1;
unsigned heading_valid : 1; unsigned heading_valid : 1;
@ -453,10 +456,13 @@ struct modesMessage {
commb_format_t commb_format; // Inferred format of a comm-b message commb_format_t commb_format; // Inferred format of a comm-b message
// valid if altitude_valid: // valid if altitude_baro_valid:
int altitude; // Altitude in either feet or meters int altitude_baro; // Altitude in either feet or meters
altitude_unit_t altitude_unit; // the unit used for altitude altitude_unit_t altitude_baro_unit; // the unit used for altitude
altitude_source_t altitude_source; // whether the altitude is a barometric altitude or a geometric height
// valid if altitude_geom_valid:
int altitude_geom; // Altitude in either feet or meters
altitude_unit_t altitude_geom_unit; // the unit used for altitude
// following fields are valid if the corresponding _valid field is set: // following fields are valid if the corresponding _valid field is set:
int geom_delta; // Difference between geometric and baro alt int geom_delta; // Difference between geometric and baro alt
@ -464,7 +470,14 @@ struct modesMessage {
heading_type_t heading_type;// how to interpret 'track_or_heading' heading_type_t heading_type;// how to interpret 'track_or_heading'
float track_rate; // Rate of change of track, degrees/second float track_rate; // Rate of change of track, degrees/second
float roll; // Roll, degrees, negative is left roll float roll; // Roll, degrees, negative is left roll
unsigned gs; // Groundspeed, kts, reported directly or computed from from EW and NS velocity struct {
// Groundspeed, kts, reported directly or computed from from EW and NS velocity
// For surface movement, this has different interpretations for v0 and v2; both
// fields are populated. The tracking layer will update "gs.selected".
float v0;
float v2;
float selected;
} gs;
unsigned ias; // Indicated airspeed, kts unsigned ias; // Indicated airspeed, kts
unsigned tas; // True airspeed, kts unsigned tas; // True airspeed, kts
double mach; // Mach number double mach; // Mach number
@ -485,6 +498,36 @@ struct modesMessage {
// valid if cpr_decoded: // valid if cpr_decoded:
double decoded_lat; double decoded_lat;
double decoded_lon; double decoded_lon;
unsigned decoded_nic;
unsigned decoded_rc;
// various integrity/accuracy things
struct {
unsigned nic_a_valid : 1;
unsigned nic_b_valid : 1;
unsigned nic_c_valid : 1;
unsigned nic_baro_valid : 1;
unsigned nac_p_valid : 1;
unsigned nac_v_valid : 1;
unsigned sil_valid : 1;
unsigned gva_valid : 1;
unsigned sda_valid : 1;
unsigned nic_a : 1; // if nic_a_valid
unsigned nic_b : 1; // if nic_b_valid
unsigned nic_c : 1; // if nic_c_valid
unsigned nic_baro : 1; // if nic_baro_valid
unsigned nac_p : 4; // if nac_p_valid
unsigned nac_v : 3; // if nac_v_valid
unsigned sil : 2; // if sil_valid
sil_type_t sil_type; // if sil_valid
unsigned gva : 2; // if gva_valid
unsigned sda : 2; // if sda_valid
} accuracy;
// Operational Status // Operational Status
struct { struct {
@ -495,7 +538,6 @@ struct modesMessage {
unsigned om_ident : 1; unsigned om_ident : 1;
unsigned om_atc : 1; unsigned om_atc : 1;
unsigned om_saf : 1; unsigned om_saf : 1;
unsigned om_sda : 2;
unsigned cc_acas : 1; unsigned cc_acas : 1;
unsigned cc_cdti : 1; unsigned cc_cdti : 1;
@ -506,17 +548,8 @@ struct modesMessage {
unsigned cc_uat_in : 1; unsigned cc_uat_in : 1;
unsigned cc_poa : 1; unsigned cc_poa : 1;
unsigned cc_b2_low : 1; unsigned cc_b2_low : 1;
unsigned cc_nac_v : 3;
unsigned cc_nic_supp_c : 1;
unsigned cc_lw_valid : 1; unsigned cc_lw_valid : 1;
unsigned nic_supp_a : 1;
unsigned nac_p : 4;
unsigned gva : 2;
unsigned sil : 2;
unsigned nic_baro : 1;
sil_type_t sil_type;
heading_type_t tah; heading_type_t tah;
heading_type_t hrd; heading_type_t hrd;
@ -528,23 +561,22 @@ struct modesMessage {
// Target State & Status (ADS-B V2 only) // Target State & Status (ADS-B V2 only)
// Comm-B BDS4,0 Vertical Intent // Comm-B BDS4,0 Vertical Intent
struct { struct {
unsigned valid : 1;
unsigned heading_valid : 1; unsigned heading_valid : 1;
unsigned fms_altitude_valid : 1; unsigned fms_altitude_valid : 1;
unsigned mcp_altitude_valid : 1; unsigned mcp_altitude_valid : 1;
unsigned alt_setting_valid : 1; unsigned qnh_valid : 1;
unsigned modes_valid : 1; unsigned modes_valid : 1;
float heading; // heading, degrees (0-359) (could be magnetic or true heading; magnetic recommended) float heading; // heading, degrees (0-359) (could be magnetic or true heading; magnetic recommended)
heading_type_t heading_type;
unsigned fms_altitude; // FMS selected altitude unsigned fms_altitude; // FMS selected altitude
unsigned mcp_altitude; // MCP/FCU selected altitude unsigned mcp_altitude; // MCP/FCU selected altitude
float alt_setting; // altimeter setting (QFE or QNH/QNE), millibars float qnh; // altimeter setting (QFE or QNH/QNE), millibars
enum { INTENT_ALT_INVALID, INTENT_ALT_UNKNOWN, INTENT_ALT_AIRCRAFT, INTENT_ALT_MCP, INTENT_ALT_FMS } altitude_source; enum { NAV_ALT_INVALID, NAV_ALT_UNKNOWN, NAV_ALT_AIRCRAFT, NAV_ALT_MCP, NAV_ALT_FMS } altitude_source;
intent_modes_t modes; nav_modes_t modes;
} intent; } nav;
}; };
// This one needs modesMessage: // This one needs modesMessage:

View file

@ -162,8 +162,8 @@ void interactiveShowData(void) {
snprintf(strFl, 7," grnd"); snprintf(strFl, 7," grnd");
} else if (Modes.use_gnss && trackDataValid(&a->altitude_geom_valid)) { } else if (Modes.use_gnss && trackDataValid(&a->altitude_geom_valid)) {
snprintf(strFl, 7, "%5dH", convert_altitude(a->altitude_geom)); snprintf(strFl, 7, "%5dH", convert_altitude(a->altitude_geom));
} else if (trackDataValid(&a->altitude_valid)) { } else if (trackDataValid(&a->altitude_baro_valid)) {
snprintf(strFl, 7, "%5d ", convert_altitude(a->altitude)); snprintf(strFl, 7, "%5d ", convert_altitude(a->altitude_baro));
} }
mvprintw(row, 0, "%s%06X %-4s %-4s %-8s %6s %3s %3s %7s %8s %5.1f %5d %2.0f", mvprintw(row, 0, "%s%06X %-4s %-4s %-8s %6s %3s %3s %7s %8s %5.1f %5d %2.0f",

View file

@ -146,10 +146,9 @@ void decodeModeAMessage(struct modesMessage *mm, int ModeA)
if (!mm->spi) { if (!mm->spi) {
int modeC = modeAToModeC(ModeA); int modeC = modeAToModeC(ModeA);
if (modeC != INVALID_ALTITUDE) { if (modeC != INVALID_ALTITUDE) {
mm->altitude = modeC * 100; mm->altitude_baro = modeC * 100;
mm->altitude_unit = UNIT_FEET; mm->altitude_baro_unit = UNIT_FEET;
mm->altitude_source = ALTITUDE_BARO; mm->altitude_baro_valid = 1;
mm->altitude_valid = 1;
} }
} }

568
mode_s.c
View file

@ -177,23 +177,50 @@ static int decodeAC12Field(int AC12Field, altitude_unit_t *unit) {
// //
//========================================================================= //=========================================================================
// //
// Decode the 7 bit ground movement field PWL exponential style scale // Decode the 7 bit ground movement field PWL exponential style scale (ADS-B v2)
// //
static unsigned decodeMovementField(unsigned movement) { static float decodeMovementFieldV2(unsigned movement) {
int gspeed;
// Note : movement codes 0,125,126,127 are all invalid, but they are // Note : movement codes 0,125,126,127 are all invalid, but they are
// trapped for before this function is called. // trapped for before this function is called.
if (movement > 123) gspeed = 199; // > 175kt // Each movement value is a range of speeds;
else if (movement > 108) gspeed = ((movement - 108) * 5) + 100; // we return the midpoint of the range (rounded to the nearest integer)
else if (movement > 93) gspeed = ((movement - 93) * 2) + 70; if (movement >= 125) return 0; // invalid
else if (movement > 38) gspeed = ((movement - 38) ) + 15; else if (movement == 124) return 180; // gs > 175kt, pick a value..
else if (movement > 12) gspeed = ((movement - 11) >> 1) + 2; else if (movement >= 109) return 100 + (movement - 109 + 0.5) * 5; // 100 < gs <= 175 in 5kt steps
else if (movement > 8) gspeed = ((movement - 6) >> 2) + 1; else if (movement >= 94) return 70 + (movement - 94 + 0.5) * 2; // 70 < gs <= 100 in 2kt steps
else gspeed = 0; else if (movement >= 39) return 15 + (movement - 39 + 0.5) * 1; // 15 < gs <= 70 in 1kt steps
else if (movement >= 13) return 2 + (movement - 13 + 0.5) * 0.50; // 2 < gs <= 15 in 0.5kt steps
else if (movement >= 9) return 1 + (movement - 9 + 0.5) * 0.25; // 1 < gs <= 2 in 0.25kt steps
else if (movement >= 3) return 0.125 + (movement - 3 + 0.5) * 0.875 / 6; // 0.125 < gs <= 1 in 0.875/6 kt step
else if (movement >= 2) return 0.125 / 2; // 0 < gs <= 0.125
// 1: stopped, gs = 0
// 0: no data
else return 0;
}
return (gspeed); //
//=========================================================================
//
// Decode the 7 bit ground movement field PWL exponential style scale (ADS-B v0)
//
static float decodeMovementFieldV0(unsigned movement) {
// Note : movement codes 0,125,126,127 are all invalid, but they are
// trapped for before this function is called.
// Each movement value is a range of speeds;
// we return the midpoint of the range
if (movement >= 125) return 0; // invalid
else if (movement == 124) return 180; // gs >= 175kt, pick a value..
else if (movement >= 109) return 100 + (movement - 109 + 0.5) * 5; // 100 < gs <= 175 in 5kt steps
else if (movement >= 94) return 70 + (movement - 94 + 0.5) * 2; // 70 < gs <= 100 in 2kt steps
else if (movement >= 39) return 15 + (movement - 39 + 0.5) * 1; // 15 < gs <= 70 in 1kt steps
else if (movement >= 13) return 2 + (movement - 13 + 0.5) * 0.50; // 2 < gs <= 15 in 0.5kt steps
else if (movement >= 9) return 1 + (movement - 9 + 0.5) * 0.25; // 1 < gs <= 2 in 0.25kt steps
else if (movement >= 2) return 0.125 + (movement - 3 + 0.5) * 0.125; // 0.125 < gs <= 1 in 0.125kt step
// 1: stopped, gs < 0.125kt
// 0: no data
else return 0;
} }
// Correct a decoded native-endian Address Announced field // Correct a decoded native-endian Address Announced field
@ -505,10 +532,9 @@ int decodeModesMessage(struct modesMessage *mm, unsigned char *msg)
if (mm->msgtype == 0 || mm->msgtype == 4 || mm->msgtype == 16 || mm->msgtype == 20) { if (mm->msgtype == 0 || mm->msgtype == 4 || mm->msgtype == 16 || mm->msgtype == 20) {
mm->AC = getbits(msg, 20, 32); mm->AC = getbits(msg, 20, 32);
if (mm->AC) { // Only attempt to decode if a valid (non zero) altitude is present if (mm->AC) { // Only attempt to decode if a valid (non zero) altitude is present
mm->altitude = decodeAC13Field(mm->AC, &mm->altitude_unit); mm->altitude_baro = decodeAC13Field(mm->AC, &mm->altitude_baro_unit);
if (mm->altitude != INVALID_ALTITUDE) if (mm->altitude_baro != INVALID_ALTITUDE)
mm->altitude_valid = 1; mm->altitude_baro_valid = 1;
mm->altitude_source = ALTITUDE_BARO;
} }
} }
@ -729,30 +755,31 @@ static void decodeESAirborneVelocity(struct modesMessage *mm, int check_imf)
// Airborne Velocity Message // Airborne Velocity Message
unsigned char *me = mm->ME; unsigned char *me = mm->ME;
// 1-5: ME type
// 6-8: ME subtype
mm->mesub = getbits(me, 6, 8); mm->mesub = getbits(me, 6, 8);
if (check_imf && getbit(me, 9))
setIMF(mm);
if (mm->mesub < 1 || mm->mesub > 4) if (mm->mesub < 1 || mm->mesub > 4)
return; return;
unsigned vert_rate = getbits(me, 38, 46); // 9: IMF or Intent Change
unsigned vert_rate_is_geom = getbit(me, 36); if (check_imf && getbit(me, 9))
if (vert_rate) { setIMF(mm);
int rate = (vert_rate - 1) * (getbit(me, 37) ? -64 : 64);
if (vert_rate_is_geom) {
mm->geom_rate = rate;
mm->geom_rate_valid = 1;
} else {
mm->baro_rate = rate;
mm->baro_rate_valid = 1;
}
}
// 10: reserved
// 11-13: NACv (NUCr in v0, maps directly to NACv in v2)
mm->accuracy.nac_v_valid = 1;
mm->accuracy.nac_v = getbits(me, 11, 13);
// 14-35: speed/velocity depending on subtype
switch (mm->mesub) { switch (mm->mesub) {
case 1: case 2: case 1: case 2:
{ {
// 14: E/W direction
// 15-24: E/W speed
// 25: N/S direction
// 26-35: N/S speed
unsigned ew_raw = getbits(me, 15, 24); unsigned ew_raw = getbits(me, 15, 24);
unsigned ns_raw = getbits(me, 26, 35); unsigned ns_raw = getbits(me, 26, 35);
@ -761,10 +788,10 @@ static void decodeESAirborneVelocity(struct modesMessage *mm, int check_imf)
int ns_vel = (ns_raw - 1) * (getbit(me, 25) ? -1 : 1) * ((mm->mesub == 2) ? 4 : 1); int ns_vel = (ns_raw - 1) * (getbit(me, 25) ? -1 : 1) * ((mm->mesub == 2) ? 4 : 1);
// Compute velocity and angle from the two speed components // Compute velocity and angle from the two speed components
mm->gs = (unsigned) sqrt((ns_vel * ns_vel) + (ew_vel * ew_vel) + 0.5); mm->gs.v0 = mm->gs.v2 = mm->gs.selected = sqrtf((ns_vel * ns_vel) + (ew_vel * ew_vel) + 0.5);
mm->gs_valid = 1; mm->gs_valid = 1;
if (mm->gs) { if (mm->gs.selected > 0) {
float ground_track = atan2(ew_vel, ns_vel) * 180.0 / M_PI; float ground_track = atan2(ew_vel, ns_vel) * 180.0 / M_PI;
// We don't want negative values but a 0-360 scale // We don't want negative values but a 0-360 scale
if (ground_track < 0) if (ground_track < 0)
@ -779,6 +806,16 @@ static void decodeESAirborneVelocity(struct modesMessage *mm, int check_imf)
case 3: case 4: case 3: case 4:
{ {
// 14: heading status
// 15-24: heading
if (getbit(me, 14)) {
mm->heading_valid = 1;
mm->heading = getbits(me, 15, 24) * 360.0 / 1024.0;
mm->heading_type = HEADING_MAGNETIC_OR_TRUE;
}
// 25: airspeed type
// 26-35: airspeed
unsigned airspeed = getbits(me, 26, 35); unsigned airspeed = getbits(me, 26, 35);
if (airspeed) { if (airspeed) {
unsigned speed = (airspeed - 1) * (mm->mesub == 4 ? 4 : 1); unsigned speed = (airspeed - 1) * (mm->mesub == 4 ? 4 : 1);
@ -791,15 +828,30 @@ static void decodeESAirborneVelocity(struct modesMessage *mm, int check_imf)
} }
} }
if (getbit(me, 14)) {
mm->heading_valid = 1;
mm->heading = getbits(me, 15, 24) * 360.0 / 1024.0;
mm->heading_type = HEADING_MAGNETIC_OR_TRUE;
}
break; break;
} }
} }
// 36: vert rate source
// 37: vert rate sign
// 38-46: vert rate magnitude
unsigned vert_rate = getbits(me, 38, 46);
unsigned vert_rate_is_geom = getbit(me, 36);
if (vert_rate) {
int rate = (vert_rate - 1) * (getbit(me, 37) ? -64 : 64);
if (vert_rate_is_geom) {
mm->geom_rate = rate;
mm->geom_rate_valid = 1;
} else {
mm->baro_rate = rate;
mm->baro_rate_valid = 1;
}
}
// 47-48: reserved
// 49: baro/geom delta sign
// 50-56: baro/geom delta magnitude
unsigned raw_delta = getbits(me, 50, 56); unsigned raw_delta = getbits(me, 50, 56);
if (raw_delta) { if (raw_delta) {
mm->geom_delta_valid = 1; mm->geom_delta_valid = 1;
@ -812,28 +864,37 @@ static void decodeESSurfacePosition(struct modesMessage *mm, int check_imf)
// Surface position and movement // Surface position and movement
unsigned char *me = mm->ME; unsigned char *me = mm->ME;
if (check_imf && getbit(me, 21))
setIMF(mm);
mm->airground = AG_GROUND; // definitely. mm->airground = AG_GROUND; // definitely.
mm->cpr_lat = getbits(me, 23, 39);
mm->cpr_lon = getbits(me, 40, 56);
mm->cpr_odd = getbit(me, 22);
mm->cpr_nucp = (14 - mm->metype);
mm->cpr_valid = 1; mm->cpr_valid = 1;
mm->cpr_type = CPR_SURFACE; mm->cpr_type = CPR_SURFACE;
// 6-12: Movement
unsigned movement = getbits(me, 6, 12); unsigned movement = getbits(me, 6, 12);
if (movement > 0 && movement < 125) { if (movement > 0 && movement < 125) {
mm->gs_valid = 1; mm->gs_valid = 1;
mm->gs = decodeMovementField(movement); mm->gs.selected = mm->gs.v0 = decodeMovementFieldV0(movement); // assumed v0 until told otherwise
mm->gs.v2 = decodeMovementFieldV2(movement);
} }
// 13: Heading/track status
// 14-20: Heading/track
if (getbit(me, 13)) { if (getbit(me, 13)) {
mm->heading_valid = 1; mm->heading_valid = 1;
mm->heading = getbits(me, 14, 20) * 360.0 / 128.0; mm->heading = getbits(me, 14, 20) * 360.0 / 128.0;
mm->heading_type = HEADING_TRACK_OR_HEADING; mm->heading_type = HEADING_TRACK_OR_HEADING;
} }
// 21: IMF or T flag
if (check_imf && getbit(me, 21))
setIMF(mm);
// 22: F flag (odd/even)
mm->cpr_odd = getbit(me, 22);
// 23-39: CPR encoded latitude
mm->cpr_lat = getbits(me, 23, 39);
// 40-56: CPR encoded longitude
mm->cpr_lon = getbits(me, 40, 56);
} }
static void decodeESAirbornePosition(struct modesMessage *mm, int check_imf) static void decodeESAirbornePosition(struct modesMessage *mm, int check_imf)
@ -841,20 +902,34 @@ static void decodeESAirbornePosition(struct modesMessage *mm, int check_imf)
// Airborne position and altitude // Airborne position and altitude
unsigned char *me = mm->ME; unsigned char *me = mm->ME;
if (check_imf && getbit(me, 8)) // 6-7: surveillance status
setIMF(mm); // 8: IMF or NIC supplement-B
if (check_imf) {
if (getbit(me, 8))
setIMF(mm);
} else {
// NIC-B (v2) or SAF (v0/v1)
mm->accuracy.nic_b_valid = 1;
mm->accuracy.nic_b = getbit(me, 8);
}
// 9-20: altitude
unsigned AC12Field = getbits(me, 9, 20); unsigned AC12Field = getbits(me, 9, 20);
if (mm->metype == 0) { if (mm->metype == 0) {
mm->cpr_nucp = 0; // no position information
} else { } else {
// Catch some common failure modes and don't mark them as valid // 21: T flag (UTC sync or not)
// (so they won't be used for positioning) // 22: F flag (odd or even)
// 23-39: CPR encoded latitude
// 40-56: CPR encoded longitude
mm->cpr_lat = getbits(me, 23, 39); mm->cpr_lat = getbits(me, 23, 39);
mm->cpr_lon = getbits(me, 40, 56); mm->cpr_lon = getbits(me, 40, 56);
// Catch some common failure modes and don't mark them as valid
// (so they won't be used for positioning)
if (AC12Field == 0 && mm->cpr_lon == 0 && (mm->cpr_lat & 0x0fff) == 0 && mm->metype == 15) { if (AC12Field == 0 && mm->cpr_lon == 0 && (mm->cpr_lat & 0x0fff) == 0 && mm->metype == 15) {
// Seen from at least: // Seen from at least:
// 400F3F (Eurocopter ECC155 B1) - Bristow Helicopters // 400F3F (Eurocopter ECC155 B1) - Bristow Helicopters
@ -868,23 +943,23 @@ static void decodeESAirbornePosition(struct modesMessage *mm, int check_imf)
mm->cpr_valid = 1; mm->cpr_valid = 1;
mm->cpr_type = CPR_AIRBORNE; mm->cpr_type = CPR_AIRBORNE;
mm->cpr_odd = getbit(me, 22); mm->cpr_odd = getbit(me, 22);
if (mm->metype == 18 || mm->metype == 22)
mm->cpr_nucp = 0;
else if (mm->metype < 18)
mm->cpr_nucp = (18 - mm->metype);
else
mm->cpr_nucp = (29 - mm->metype);
} }
} }
if (AC12Field) {// Only attempt to decode if a valid (non zero) altitude is present if (AC12Field) {// Only attempt to decode if a valid (non zero) altitude is present
mm->altitude = decodeAC12Field(AC12Field, &mm->altitude_unit); altitude_unit_t unit;
if (mm->altitude != INVALID_ALTITUDE) { int alt = decodeAC12Field(AC12Field, &unit);
mm->altitude_valid = 1; if (alt != INVALID_ALTITUDE) {
if (mm->metype == 20 || mm->metype == 21 || mm->metype == 22) {
mm->altitude_geom = alt;
mm->altitude_geom_unit = unit;
mm->altitude_geom_valid = 1;
} else {
mm->altitude_baro = alt;
mm->altitude_baro_unit = unit;
mm->altitude_baro_valid = 1;
}
} }
mm->altitude_source = (mm->metype == 20 || mm->metype == 21 || mm->metype == 22) ? ALTITUDE_GEOM : ALTITUDE_BARO;
} }
} }
@ -931,52 +1006,186 @@ static void decodeESTargetStatus(struct modesMessage *mm, int check_imf)
if (check_imf && getbit(me, 51)) if (check_imf && getbit(me, 51))
setIMF(mm); setIMF(mm);
if (mm->mesub == 0) { // Target state and status, V1 if (mm->mesub == 0 && getbit(me, 11) == 0) { // Target state and status, V1
// TODO: need RTCA/DO-260A // 8-9: vertical source
} else if (mm->mesub == 1) { // Target state and status, V2 switch (getbits(me, 8, 9)) {
mm->intent.valid = 1; case 1:
mm->nav.altitude_source = NAV_ALT_MCP;
break;
case 2:
mm->nav.altitude_source = NAV_ALT_AIRCRAFT;
break;
case 3:
mm->nav.altitude_source = NAV_ALT_FMS;
break;
default:
// nothing
break;
}
// 10: target altitude type (ignored)
// 11: backward compatibility bit, always 0
// 12-13: target alt capabilities (ignored)
// 14-15: vertical mode
switch (getbits(me, 14, 15)) {
case 1: // "acquiring"
mm->nav.modes_valid = 1;
if (mm->nav.altitude_source == NAV_ALT_FMS) {
mm->nav.modes |= NAV_MODE_VNAV;
} else {
mm->nav.modes |= NAV_MODE_AUTOPILOT;
}
break;
case 2: // "maintaining"
mm->nav.modes_valid = 1;
if (mm->nav.altitude_source == NAV_ALT_FMS) {
mm->nav.modes |= NAV_MODE_VNAV;
} else if (mm->nav.altitude_source == NAV_ALT_AIRCRAFT) {
mm->nav.modes |= NAV_MODE_ALT_HOLD;
} else {
mm->nav.modes |= NAV_MODE_AUTOPILOT;
}
break;
default:
// nothing
break;
}
// 16-25: altitude
int alt = -1000 + 100 * getbits(me, 16, 25);
switch (mm->nav.altitude_source) {
case NAV_ALT_MCP:
mm->nav.mcp_altitude_valid = 1;
mm->nav.mcp_altitude = alt;
break;
case NAV_ALT_FMS:
mm->nav.fms_altitude_valid = 1;
mm->nav.fms_altitude = alt;
break;
default:
// nothing
break;
}
// 26-27: horizontal data source
unsigned h_source = getbits(me, 26, 27);
if (h_source != 0) {
// 28-36: target heading/track
mm->nav.heading_valid = 1;
mm->nav.heading = getbits(me, 28, 36);
// 37: track vs heading
if (getbit(me, 37)) {
mm->nav.heading_type = HEADING_GROUND_TRACK;
} else {
mm->nav.heading_type = HEADING_MAGNETIC_OR_TRUE;
}
}
// 38-39: horiontal mode
switch (getbits(me, 38, 39)) {
case 1: // acquiring
case 2: // maintaining
mm->nav.modes_valid = 1;
if (h_source == 3) { // FMS
mm->nav.modes |= NAV_MODE_LNAV;
} else {
mm->nav.modes |= NAV_MODE_AUTOPILOT;
}
break;
default:
// nothing
break;
}
// 40-43: NACp
mm->accuracy.nac_p_valid = 1;
mm->accuracy.nac_p = getbits(me, 40, 43);
// 44: NICbaro
mm->accuracy.nic_baro_valid = 1;
mm->accuracy.nic_baro = getbit(me, 44);
// 45-46: SIL
mm->accuracy.sil_valid = 1;
mm->accuracy.sil = getbits(me, 45, 46);
mm->accuracy.sil_type = SIL_INVALID;
// 47-51: reserved
// 52-53: TCAS status
switch (getbits(me, 52, 53)) {
case 1:
mm->nav.modes_valid = 1;
// no tcas
break;
case 2:
case 3:
mm->nav.modes_valid = 1;
mm->nav.modes |= NAV_MODE_TCAS;
break;
case 0:
// assume TCAS if we had any other modes
// but don't enable modes just for this
mm->nav.modes |= NAV_MODE_TCAS;
break;
default:
// nothing
break;
}
// 54-56: emergency/priority (ignored)
} else if (mm->mesub == 1) { // Target state and status, V2
// 8: SIL // 8: SIL
unsigned is_fms = getbit(me, 9); unsigned is_fms = getbit(me, 9);
unsigned alt_bits = getbits(me, 10, 20); unsigned alt_bits = getbits(me, 10, 20);
if (alt_bits != 0) { if (alt_bits != 0) {
if (is_fms) { if (is_fms) {
mm->intent.fms_altitude_valid = 1; mm->nav.fms_altitude_valid = 1;
mm->intent.fms_altitude = (alt_bits - 1) * 32; mm->nav.fms_altitude = (alt_bits - 1) * 32;
} else { } else {
mm->intent.mcp_altitude_valid = 1; mm->nav.mcp_altitude_valid = 1;
mm->intent.mcp_altitude = (alt_bits - 1) * 32; mm->nav.mcp_altitude = (alt_bits - 1) * 32;
} }
} }
unsigned baro_bits = getbits(me, 21, 29); unsigned baro_bits = getbits(me, 21, 29);
if (baro_bits != 0) { if (baro_bits != 0) {
mm->intent.alt_setting_valid = 1; mm->nav.qnh_valid = 1;
mm->intent.alt_setting = 800.0 + (baro_bits - 1) * 0.8; mm->nav.qnh = 800.0 + (baro_bits - 1) * 0.8;
} }
if (getbit(me, 30)) { if (getbit(me, 30)) {
mm->intent.heading_valid = 1; mm->nav.heading_valid = 1;
// two's complement -180..+180, which is conveniently // two's complement -180..+180, which is conveniently
// also the same as unsigned 0..360 // also the same as unsigned 0..360
mm->intent.heading = getbits(me, 31, 39) * 180.0 / 256.0; mm->nav.heading = getbits(me, 31, 39) * 180.0 / 256.0;
mm->nav.heading_type = HEADING_MAGNETIC_OR_TRUE;
} }
// 40-43: NACp // 40-43: NACp
// 44: NICbaro mm->accuracy.nac_p_valid = 1;
// 45-46: SIL mm->accuracy.nac_p = getbits(me, 40, 43);
// 44: NICbaro
mm->accuracy.nic_baro_valid = 1;
mm->accuracy.nic_baro = getbit(me, 44);
// 45-46: SIL
mm->accuracy.sil_valid = 1;
mm->accuracy.sil = getbits(me, 45, 46);
mm->accuracy.sil_type = SIL_INVALID;
// 47: mode bits validity
if (getbit(me, 47)) { if (getbit(me, 47)) {
mm->intent.modes_valid = 1; // 48-54: mode bits
mm->intent.modes = mm->nav.modes_valid = 1;
(getbit(me, 48) ? INTENT_MODE_AUTOPILOT : 0) | mm->nav.modes =
(getbit(me, 49) ? INTENT_MODE_VNAV : 0) | (getbit(me, 48) ? NAV_MODE_AUTOPILOT : 0) |
(getbit(me, 50) ? INTENT_MODE_ALT_HOLD : 0) | (getbit(me, 49) ? NAV_MODE_VNAV : 0) |
(getbit(me, 50) ? NAV_MODE_ALT_HOLD : 0) |
// 51: IMF // 51: IMF
(getbit(me, 52) ? INTENT_MODE_APPROACH : 0) | (getbit(me, 52) ? NAV_MODE_APPROACH : 0) |
// 53: TCAS operational (getbit(me, 53) ? NAV_MODE_TCAS : 0) |
(getbit(me, 54) ? INTENT_MODE_LNAV : 0); (getbit(me, 54) ? NAV_MODE_LNAV : 0);
} }
// 55-56 reserved // 55-56 reserved
@ -999,6 +1208,10 @@ static void decodeESOperationalStatus(struct modesMessage *mm, int check_imf)
switch (mm->opstatus.version) { switch (mm->opstatus.version) {
case 0: case 0:
if (mm->mesub == 0 && getbits(me, 9, 10) == 0) {
mm->opstatus.cc_acas = !getbit(me, 12);
mm->opstatus.cc_cdti = getbit(me, 13);
}
break; break;
case 1: case 1:
@ -1024,59 +1237,72 @@ static void decodeESOperationalStatus(struct modesMessage *mm, int check_imf)
mm->opstatus.cc_lw = getbits(me, 21, 24); mm->opstatus.cc_lw = getbits(me, 21, 24);
} }
mm->opstatus.nic_supp_a = getbit(me, 44); mm->accuracy.nic_a_valid = 1;
mm->opstatus.nac_p = getbits(me, 45, 48); mm->accuracy.nic_a = getbit(me, 44);
mm->opstatus.sil = getbits(me, 51, 52); mm->accuracy.nac_p_valid = 1;
mm->accuracy.nac_p = getbits(me, 45, 48);
mm->accuracy.sil_valid = 1;
mm->accuracy.sil = getbits(me, 51, 52);
mm->opstatus.hrd = getbit(me, 54) ? HEADING_MAGNETIC : HEADING_TRUE; mm->opstatus.hrd = getbit(me, 54) ? HEADING_MAGNETIC : HEADING_TRUE;
if (mm->mesub == 0) { if (mm->mesub == 0) {
mm->opstatus.nic_baro = getbit(me, 53); mm->accuracy.nic_baro_valid = 1;
mm->accuracy.nic_baro = getbit(me, 53);
} else { } else {
mm->opstatus.tah = getbit(me, 53) ? HEADING_GROUND_TRACK : mm->opstatus.hrd; mm->opstatus.tah = getbit(me, 53) ? HEADING_GROUND_TRACK : mm->opstatus.hrd;
} }
break; break;
case 2: case 2:
default:
if (getbits(me, 25, 26) == 0) { if (getbits(me, 25, 26) == 0) {
mm->opstatus.om_acas_ra = getbit(me, 27); mm->opstatus.om_acas_ra = getbit(me, 27);
mm->opstatus.om_ident = getbit(me, 28); mm->opstatus.om_ident = getbit(me, 28);
mm->opstatus.om_atc = getbit(me, 29); mm->opstatus.om_atc = getbit(me, 29);
mm->opstatus.om_saf = getbit(me, 30); mm->opstatus.om_saf = getbit(me, 30);
mm->opstatus.om_sda = getbits(me, 31, 32); mm->accuracy.sda_valid = 1;
mm->accuracy.sda = getbits(me, 31, 32);
} }
if (mm->mesub == 0 && getbits(me, 9, 10) == 0 && getbits(me, 13, 14) == 0) { if (mm->mesub == 0 && getbits(me, 9, 10) == 0) {
// airborne // airborne
mm->opstatus.cc_acas = getbit(me, 11); mm->opstatus.cc_acas = getbit(me, 11); // nb inverted sense versus v0/v1
mm->opstatus.cc_1090_in = getbit(me, 12); mm->opstatus.cc_1090_in = getbit(me, 12);
mm->opstatus.cc_arv = getbit(me, 15); mm->opstatus.cc_arv = getbit(me, 15);
mm->opstatus.cc_ts = getbit(me, 16); mm->opstatus.cc_ts = getbit(me, 16);
mm->opstatus.cc_tc = getbits(me, 17, 18); mm->opstatus.cc_tc = getbits(me, 17, 18);
mm->opstatus.cc_uat_in = getbit(me, 19); mm->opstatus.cc_uat_in = getbit(me, 19);
} else if (mm->mesub == 1 && getbits(me, 9, 10) == 0 && getbits(me, 13, 14) == 0) { } else if (mm->mesub == 1 && getbits(me, 9, 10) == 0) {
// surface // surface
mm->opstatus.cc_poa = getbit(me, 11); mm->opstatus.cc_poa = getbit(me, 11);
mm->opstatus.cc_1090_in = getbit(me, 12); mm->opstatus.cc_1090_in = getbit(me, 12);
mm->opstatus.cc_b2_low = getbit(me, 15); mm->opstatus.cc_b2_low = getbit(me, 15);
mm->opstatus.cc_uat_in = getbit(me, 16); mm->opstatus.cc_uat_in = getbit(me, 16);
mm->opstatus.cc_nac_v = getbits(me, 17, 19); mm->accuracy.nac_v_valid = 1;
mm->opstatus.cc_nic_supp_c = getbit(me, 20); mm->accuracy.nac_v = getbits(me, 17, 19);
mm->accuracy.nic_c_valid = 1;
mm->accuracy.nic_c = getbit(me, 20);
mm->opstatus.cc_lw_valid = 1; mm->opstatus.cc_lw_valid = 1;
mm->opstatus.cc_lw = getbits(me, 21, 24); mm->opstatus.cc_lw = getbits(me, 21, 24);
mm->opstatus.cc_antenna_offset = getbits(me, 33, 40); mm->opstatus.cc_antenna_offset = getbits(me, 33, 40);
} }
mm->opstatus.nic_supp_a = getbit(me, 44); mm->accuracy.nic_a_valid = 1;
mm->opstatus.nac_p = getbits(me, 45, 48); mm->accuracy.nic_a = getbit(me, 44);
mm->opstatus.sil = getbits(me, 51, 52); mm->accuracy.nac_p_valid = 1;
mm->accuracy.nac_p = getbits(me, 45, 48);
mm->accuracy.sil_valid = 1;
mm->accuracy.sil = getbits(me, 51, 52);
mm->accuracy.sil_type = getbit(me, 55) ? SIL_PER_SAMPLE : SIL_PER_HOUR;
mm->opstatus.hrd = getbit(me, 54) ? HEADING_MAGNETIC : HEADING_TRUE; mm->opstatus.hrd = getbit(me, 54) ? HEADING_MAGNETIC : HEADING_TRUE;
if (mm->mesub == 0) { if (mm->mesub == 0) {
mm->opstatus.gva = getbits(me, 49, 50); mm->accuracy.gva_valid = 1;
mm->opstatus.nic_baro = getbit(me, 53); mm->accuracy.gva = getbits(me, 49, 50);
mm->accuracy.nic_baro_valid = 1;
mm->accuracy.nic_baro = getbit(me, 53);
} else { } else {
mm->opstatus.tah = getbit(me, 53) ? HEADING_GROUND_TRACK : mm->opstatus.hrd; mm->opstatus.tah = getbit(me, 53) ? HEADING_GROUND_TRACK : mm->opstatus.hrd;
} }
mm->opstatus.sil_type = getbit(me, 55) ? SIL_PER_SAMPLE : SIL_PER_HOUR;
break; break;
} }
} }
@ -1240,17 +1466,6 @@ static const char *altitude_unit_to_string(altitude_unit_t unit) {
} }
} }
static const char *altitude_source_to_string(altitude_source_t source) {
switch (source) {
case ALTITUDE_BARO:
return "barometric";
case ALTITUDE_GEOM:
return "geometric";
default:
return "(unknown altitude source)";
}
}
static const char *airground_to_string(airground_t airground) { static const char *airground_to_string(airground_t airground) {
switch (airground) { switch (airground) {
case AG_GROUND: case AG_GROUND:
@ -1344,21 +1559,23 @@ static const char *commb_format_to_string(commb_format_t format) {
} }
} }
static const char *intent_modes_to_string(intent_modes_t flags) static const char *nav_modes_to_string(nav_modes_t flags)
{ {
static char buf[128]; static char buf[128];
buf[0] = 0; buf[0] = 0;
if (flags & INTENT_MODE_AUTOPILOT) if (flags & NAV_MODE_AUTOPILOT)
strcat(buf, "autopilot "); strcat(buf, "autopilot ");
if (flags & INTENT_MODE_VNAV) if (flags & NAV_MODE_VNAV)
strcat(buf, "vnav "); strcat(buf, "vnav ");
if (flags & INTENT_MODE_ALT_HOLD) if (flags & NAV_MODE_ALT_HOLD)
strcat(buf, "althold "); strcat(buf, "althold ");
if (flags & INTENT_MODE_APPROACH) if (flags & NAV_MODE_APPROACH)
strcat(buf, "approach "); strcat(buf, "approach ");
if (flags & INTENT_MODE_LNAV) if (flags & NAV_MODE_LNAV)
strcat(buf, "lnav "); strcat(buf, "lnav ");
if (flags & NAV_MODE_TCAS)
strcat(buf, "tcas ");
if (buf[0] != 0) if (buf[0] != 0)
buf[strlen(buf)-1] = 0; buf[strlen(buf)-1] = 0;
@ -1617,11 +1834,16 @@ void displayModesMessage(struct modesMessage *mm) {
airground_to_string(mm->airground)); airground_to_string(mm->airground));
} }
if (mm->altitude_valid) { if (mm->altitude_baro_valid) {
printf(" Altitude: %d %s %s\n", printf(" Baro altitude: %d %s\n",
mm->altitude, mm->altitude_baro,
altitude_unit_to_string(mm->altitude_unit), altitude_unit_to_string(mm->altitude_baro_unit));
altitude_source_to_string(mm->altitude_source)); }
if (mm->altitude_geom_valid) {
printf(" Geom altitude: %d %s\n",
mm->altitude_geom,
altitude_unit_to_string(mm->altitude_geom_unit));
} }
if (mm->geom_delta_valid) { if (mm->geom_delta_valid) {
@ -1642,7 +1864,14 @@ void displayModesMessage(struct modesMessage *mm) {
} }
if (mm->gs_valid) { if (mm->gs_valid) {
printf(" Groundspeed: %u kt\n", mm->gs); printf(" Groundspeed: %.1f kt", mm->gs.selected);
if (mm->gs.v0 != mm->gs.selected) {
printf(" (v0: %.1f kt)", mm->gs.v0);
}
if (mm->gs.v2 != mm->gs.selected) {
printf(" (v2: %.1f kt)", mm->gs.v2);
}
printf("\n");
} }
if (mm->ias_valid) { if (mm->ias_valid) {
@ -1682,21 +1911,24 @@ void displayModesMessage(struct modesMessage *mm) {
if (mm->cpr_valid) { if (mm->cpr_valid) {
printf(" CPR type: %s\n" printf(" CPR type: %s\n"
" CPR odd flag: %s\n" " CPR odd flag: %s\n",
" CPR NUCp/NIC: %u\n",
cpr_type_to_string(mm->cpr_type), cpr_type_to_string(mm->cpr_type),
mm->cpr_odd ? "odd" : "even", mm->cpr_odd ? "odd" : "even");
mm->cpr_nucp);
if (mm->cpr_decoded) { if (mm->cpr_decoded) {
printf(" CPR latitude: %.5f (%u)\n" printf(" CPR latitude: %.5f (%u)\n"
" CPR longitude: %.5f (%u)\n" " CPR longitude: %.5f (%u)\n"
" CPR decoding: %s\n", " CPR decoding: %s\n"
" NIC: %u\n"
" Rc: %.3f km / %.1f NM\n",
mm->decoded_lat, mm->decoded_lat,
mm->cpr_lat, mm->cpr_lat,
mm->decoded_lon, mm->decoded_lon,
mm->cpr_lon, mm->cpr_lon,
mm->cpr_relative ? "local" : "global"); mm->cpr_relative ? "local" : "global",
mm->decoded_nic,
mm->decoded_rc / 1000.0,
mm->decoded_rc / 1852.0);
} else { } else {
printf(" CPR latitude: (%u)\n" printf(" CPR latitude: (%u)\n"
" CPR longitude: (%u)\n" " CPR longitude: (%u)\n"
@ -1706,6 +1938,36 @@ void displayModesMessage(struct modesMessage *mm) {
} }
} }
if (mm->accuracy.nic_a_valid) {
printf(" NIC-A: %d\n", mm->accuracy.nic_a);
}
if (mm->accuracy.nic_b_valid) {
printf(" NIC-B: %d\n", mm->accuracy.nic_b);
}
if (mm->accuracy.nic_c_valid) {
printf(" NIC-C: %d\n", mm->accuracy.nic_c);
}
if (mm->accuracy.nic_baro_valid) {
printf(" NIC-baro: %d\n", mm->accuracy.nic_baro);
}
if (mm->accuracy.nac_p_valid) {
printf(" NACp: %d\n", mm->accuracy.nac_p);
}
if (mm->accuracy.nac_v_valid) {
printf(" NACv: %d\n", mm->accuracy.nac_v);
}
if (mm->accuracy.gva_valid) {
printf(" GVA: %d\n", mm->accuracy.gva);
}
if (mm->accuracy.sil_valid) {
printf(" SIL: %d (%s)\n",
mm->accuracy.sil,
(mm->accuracy.sil_type == SIL_PER_HOUR ? "per hour" : "per sample"));
}
if (mm->accuracy.sda_valid) {
printf(" SDA: %d\n", mm->accuracy.sda);
}
if (mm->opstatus.valid) { if (mm->opstatus.valid) {
printf(" Aircraft Operational Status:\n"); printf(" Aircraft Operational Status:\n");
printf(" Version: %d\n", mm->opstatus.version); printf(" Version: %d\n", mm->opstatus.version);
@ -1720,8 +1982,6 @@ void displayModesMessage(struct modesMessage *mm) {
if (mm->opstatus.cc_uat_in) printf("UATIN "); if (mm->opstatus.cc_uat_in) printf("UATIN ");
if (mm->opstatus.cc_poa) printf("POA "); if (mm->opstatus.cc_poa) printf("POA ");
if (mm->opstatus.cc_b2_low) printf("B2-LOW "); if (mm->opstatus.cc_b2_low) printf("B2-LOW ");
if (mm->opstatus.cc_nac_v) printf("NACv=%d ", mm->opstatus.cc_nac_v);
if (mm->opstatus.cc_nic_supp_c) printf("NIC-C=1 ");
if (mm->opstatus.cc_lw_valid) printf("L/W=%d ", mm->opstatus.cc_lw); if (mm->opstatus.cc_lw_valid) printf("L/W=%d ", mm->opstatus.cc_lw);
if (mm->opstatus.cc_antenna_offset) printf("GPS-OFFSET=%d ", mm->opstatus.cc_antenna_offset); if (mm->opstatus.cc_antenna_offset) printf("GPS-OFFSET=%d ", mm->opstatus.cc_antenna_offset);
printf("\n"); printf("\n");
@ -1731,41 +1991,31 @@ void displayModesMessage(struct modesMessage *mm) {
if (mm->opstatus.om_ident) printf("IDENT "); if (mm->opstatus.om_ident) printf("IDENT ");
if (mm->opstatus.om_atc) printf("ATC "); if (mm->opstatus.om_atc) printf("ATC ");
if (mm->opstatus.om_saf) printf("SAF "); if (mm->opstatus.om_saf) printf("SAF ");
if (mm->opstatus.om_sda) printf("SDA=%d ", mm->opstatus.om_sda);
printf("\n"); printf("\n");
if (mm->opstatus.nic_supp_a) printf(" NIC-A: %d\n", mm->opstatus.nic_supp_a);
if (mm->opstatus.nac_p) printf(" NACp: %d\n", mm->opstatus.nac_p);
if (mm->opstatus.gva) printf(" GVA: %d\n", mm->opstatus.gva);
if (mm->opstatus.sil) printf(" SIL: %d (%s)\n", mm->opstatus.sil, (mm->opstatus.sil_type == SIL_PER_HOUR ? "per hour" : "per sample"));
if (mm->opstatus.nic_baro) printf(" NICbaro: %d\n", mm->opstatus.nic_baro);
if (mm->mesub == 1) if (mm->mesub == 1)
printf(" Track/heading: %s\n", heading_type_to_string(mm->opstatus.tah)); printf(" Track/heading: %s\n", heading_type_to_string(mm->opstatus.tah));
printf(" Heading ref dir: %s\n", heading_type_to_string(mm->opstatus.hrd)); printf(" Heading ref dir: %s\n", heading_type_to_string(mm->opstatus.hrd));
} }
if (mm->intent.valid) { if (mm->nav.heading_valid)
printf(" Intent:\n"); printf(" Selected heading: %.1f\n", mm->nav.heading);
if (mm->intent.heading_valid) if (mm->nav.fms_altitude_valid)
printf(" Selected heading: %.1f\n", mm->intent.heading); printf(" FMS selected altitude: %u ft\n", mm->nav.fms_altitude);
if (mm->intent.fms_altitude_valid) if (mm->nav.mcp_altitude_valid)
printf(" FMS selected altitude: %u ft\n", mm->intent.fms_altitude); printf(" MCP selected altitude: %u ft\n", mm->nav.mcp_altitude);
if (mm->intent.mcp_altitude_valid) if (mm->nav.qnh_valid)
printf(" MCP selected altitude: %u ft\n", mm->intent.mcp_altitude); printf(" QNH: %.1f millibars\n", mm->nav.qnh);
if (mm->intent.alt_setting_valid) if (mm->nav.altitude_source != NAV_ALT_INVALID) {
printf(" Altimeter setting: %.1f millibars\n", mm->intent.alt_setting);
if (mm->intent.altitude_source != INTENT_ALT_INVALID) {
printf(" Target altitude source: "); printf(" Target altitude source: ");
switch (mm->intent.altitude_source) { switch (mm->nav.altitude_source) {
case INTENT_ALT_AIRCRAFT: case NAV_ALT_AIRCRAFT:
printf("aircraft altitude\n"); printf("aircraft altitude\n");
break; break;
case INTENT_ALT_MCP: case NAV_ALT_MCP:
printf("MCP selected altitude\n"); printf("MCP selected altitude\n");
break; break;
case INTENT_ALT_FMS: case NAV_ALT_FMS:
printf("FMS selected altitude\n"); printf("FMS selected altitude\n");
break; break;
default: default:
@ -1773,11 +2023,11 @@ void displayModesMessage(struct modesMessage *mm) {
} }
} }
if (mm->intent.modes_valid) { if (mm->nav.modes_valid) {
printf(" Active modes: %s\n", intent_modes_to_string(mm->intent.modes)); printf(" Nav modes: %s\n", nav_modes_to_string(mm->nav.modes));
}
} }
printf("\n"); printf("\n");
fflush(stdout); fflush(stdout);
} }

249
net_io.c
View file

@ -582,31 +582,29 @@ static void modesSendSBSOutput(struct modesMessage *mm, struct aircraft *a) {
else {p += sprintf(p, ",");} else {p += sprintf(p, ",");}
// Field 12 is the altitude (if we have it) // Field 12 is the altitude (if we have it)
if (mm->altitude_valid) {
if (Modes.use_gnss) { if (Modes.use_gnss) {
if (mm->altitude_source == ALTITUDE_GEOM) { if (mm->altitude_geom_valid) {
p += sprintf(p, ",%dH", mm->altitude); p += sprintf(p, ",%dH", mm->altitude_geom);
} else if (trackDataValid(&a->geom_delta_valid)) { } else if (mm->altitude_baro_valid && trackDataValid(&a->geom_delta_valid)) {
p += sprintf(p, ",%dH", mm->altitude + a->geom_delta); p += sprintf(p, ",%dH", mm->altitude_baro + a->geom_delta);
} else { } else if (mm->altitude_baro_valid) {
p += sprintf(p, ",%d", mm->altitude); p += sprintf(p, ",%d", mm->altitude_baro);
}
} else {
if (mm->altitude_source == ALTITUDE_BARO) {
p += sprintf(p, ",%d", mm->altitude);
} else if (trackDataValid(&a->geom_delta_valid)) {
p += sprintf(p, ",%d", mm->altitude - a->geom_delta);
} else { } else {
p += sprintf(p, ","); p += sprintf(p, ",");
} }
} } else {
if (mm->altitude_baro_valid) {
p += sprintf(p, ",%d", mm->altitude_baro);
} else if (mm->altitude_geom_valid && trackDataValid(&a->geom_delta_valid)) {
p += sprintf(p, ",%d", mm->altitude_geom - a->geom_delta);
} else { } else {
p += sprintf(p, ","); p += sprintf(p, ",");
} }
}
// Field 13 is the ground Speed (if we have it) // Field 13 is the ground Speed (if we have it)
if (mm->gs_valid) { if (mm->gs_valid) {
p += sprintf(p, ",%u", mm->gs); p += sprintf(p, ",%.0f", mm->gs.selected);
} else { } else {
p += sprintf(p, ","); p += sprintf(p, ",");
} }
@ -1111,8 +1109,10 @@ static char *append_flags(char *p, char *end, struct aircraft *a, datasource_t s
p += snprintf(p, end-p, "\"callsign\","); p += snprintf(p, end-p, "\"callsign\",");
if (a->position_valid.source == source) if (a->position_valid.source == source)
p += snprintf(p, end-p, "\"lat\",\"lon\","); p += snprintf(p, end-p, "\"lat\",\"lon\",");
if (a->altitude_valid.source == source) if (a->altitude_baro_valid.source == source)
p += snprintf(p, end-p, "\"altitude\","); p += snprintf(p, end-p, "\"altitude\",");
if (a->altitude_geom_valid.source == source)
p += snprintf(p, end-p, "\"alt_geom\",");
if (a->track_valid.source == source) if (a->track_valid.source == source)
p += snprintf(p, end-p, "\"track\","); p += snprintf(p, end-p, "\"track\",");
if (a->mag_heading_valid.source == source) if (a->mag_heading_valid.source == source)
@ -1129,8 +1129,6 @@ static char *append_flags(char *p, char *end, struct aircraft *a, datasource_t s
p += snprintf(p, end-p, "\"baro_rate\","); p += snprintf(p, end-p, "\"baro_rate\",");
if (a->geom_rate_valid.source == source) if (a->geom_rate_valid.source == source)
p += snprintf(p, end-p, "\"geom_rate\","); p += snprintf(p, end-p, "\"geom_rate\",");
if (a->category_valid.source == source)
p += snprintf(p, end-p, "\"category\",");
if (p[-1] != '[') if (p[-1] != '[')
--p; --p;
p += snprintf(p, end-p, "]"); p += snprintf(p, end-p, "]");
@ -1138,22 +1136,23 @@ static char *append_flags(char *p, char *end, struct aircraft *a, datasource_t s
} }
static struct { static struct {
intent_modes_t flag; nav_modes_t flag;
const char *name; const char *name;
} intent_modes_names[] = { } nav_modes_names[] = {
{ INTENT_MODE_AUTOPILOT, "autopilot" }, { NAV_MODE_AUTOPILOT, "autopilot" },
{ INTENT_MODE_VNAV, "vnav" }, { NAV_MODE_VNAV, "vnav" },
{ INTENT_MODE_ALT_HOLD, "althold" }, { NAV_MODE_ALT_HOLD, "althold" },
{ INTENT_MODE_APPROACH, "approach" }, { NAV_MODE_APPROACH, "approach" },
{ INTENT_MODE_LNAV, "lnav" }, { NAV_MODE_LNAV, "lnav" },
{ NAV_MODE_TCAS, "tcas" },
{ 0, NULL } { 0, NULL }
}; };
static char *append_intent_modes(char *p, char *end, intent_modes_t flags, const char *quote, const char *sep) static char *append_nav_modes(char *p, char *end, nav_modes_t flags, const char *quote, const char *sep)
{ {
int first = 1; int first = 1;
for (int i = 0; intent_modes_names[i].name; ++i) { for (int i = 0; nav_modes_names[i].name; ++i) {
if (!(flags & intent_modes_names[i].flag)) { if (!(flags & nav_modes_names[i].flag)) {
continue; continue;
} }
@ -1162,16 +1161,16 @@ static char *append_intent_modes(char *p, char *end, intent_modes_t flags, const
} }
first = 0; first = 0;
p += snprintf(p, end-p, "%s%s%s", quote, intent_modes_names[i].name, quote); p += snprintf(p, end-p, "%s%s%s", quote, nav_modes_names[i].name, quote);
} }
return p; return p;
} }
static const char *intent_modes_string(intent_modes_t flags) { static const char *nav_modes_string(nav_modes_t flags) {
static char buf[256]; static char buf[256];
buf[0] = 0; buf[0] = 0;
append_intent_modes(buf, buf + sizeof(buf), flags, "", " "); append_nav_modes(buf, buf + sizeof(buf), flags, "", " ");
return buf; return buf;
} }
@ -1234,12 +1233,12 @@ char *generateAircraftJson(const char *url_path, int *len) {
if (trackDataValid(&a->callsign_valid)) if (trackDataValid(&a->callsign_valid))
p += snprintf(p, end-p, ",\"flight\":\"%s\"", jsonEscapeString(a->callsign)); p += snprintf(p, end-p, ",\"flight\":\"%s\"", jsonEscapeString(a->callsign));
if (trackDataValid(&a->position_valid)) if (trackDataValid(&a->position_valid))
p += snprintf(p, end-p, ",\"lat\":%f,\"lon\":%f,\"nucp\":%u,\"seen_pos\":%.1f", a->lat, a->lon, a->pos_nuc, (now - a->position_valid.updated)/1000.0); p += snprintf(p, end-p, ",\"lat\":%f,\"lon\":%f,\"nic\":%u,\"rc\":%u\"seen_pos\":%.1f", a->lat, a->lon, a->pos_nic, a->pos_rc, (now - a->position_valid.updated)/1000.0);
if (trackDataValid(&a->airground_valid) && a->airground_valid.source >= SOURCE_MODE_S_CHECKED && a->airground == AG_GROUND) if (trackDataValid(&a->airground_valid) && a->airground_valid.source >= SOURCE_MODE_S_CHECKED && a->airground == AG_GROUND)
p += snprintf(p, end-p, ",\"altitude\":\"ground\""); p += snprintf(p, end-p, ",\"altitude\":\"ground\"");
else { else {
if (trackDataValid(&a->altitude_valid)) if (trackDataValid(&a->altitude_baro_valid))
p += snprintf(p, end-p, ",\"altitude\":%d", a->altitude); p += snprintf(p, end-p, ",\"altitude\":%d", a->altitude_baro);
if (trackDataValid(&a->altitude_geom_valid)) if (trackDataValid(&a->altitude_geom_valid))
p += snprintf(p, end-p, ",\"alt_geom\":%d", a->altitude_geom); p += snprintf(p, end-p, ",\"alt_geom\":%d", a->altitude_geom);
} }
@ -1256,7 +1255,7 @@ char *generateAircraftJson(const char *url_path, int *len) {
if (trackDataValid(&a->true_heading_valid)) if (trackDataValid(&a->true_heading_valid))
p += snprintf(p, end-p, ",\"true_heading\":%.1f", a->true_heading); p += snprintf(p, end-p, ",\"true_heading\":%.1f", a->true_heading);
if (trackDataValid(&a->gs_valid)) if (trackDataValid(&a->gs_valid))
p += snprintf(p, end-p, ",\"gs\":%u", a->gs); p += snprintf(p, end-p, ",\"gs\":%.1f", a->gs);
if (trackDataValid(&a->ias_valid)) if (trackDataValid(&a->ias_valid))
p += snprintf(p, end-p, ",\"ias\":%u", a->ias); p += snprintf(p, end-p, ",\"ias\":%u", a->ias);
if (trackDataValid(&a->tas_valid)) if (trackDataValid(&a->tas_valid))
@ -1265,19 +1264,19 @@ char *generateAircraftJson(const char *url_path, int *len) {
p += snprintf(p, end-p, ",\"mach\":%.3f", a->mach); p += snprintf(p, end-p, ",\"mach\":%.3f", a->mach);
if (trackDataValid(&a->roll_valid)) if (trackDataValid(&a->roll_valid))
p += snprintf(p, end-p, ",\"roll\":%.1f", a->roll); p += snprintf(p, end-p, ",\"roll\":%.1f", a->roll);
if (trackDataValid(&a->category_valid)) if (a->category != 0)
p += snprintf(p, end-p, ",\"category\":\"%02X\"", a->category); p += snprintf(p, end-p, ",\"category\":\"%02X\"", a->category);
if (trackDataValid(&a->intent_altitude_valid)) if (trackDataValid(&a->nav_altitude_valid))
p += snprintf(p, end-p, ",\"intent_alt\":%d", a->intent_altitude); p += snprintf(p, end-p, ",\"nav_alt\":%d", a->nav_altitude);
if (trackDataValid(&a->intent_heading_valid)) if (trackDataValid(&a->nav_heading_valid))
p += snprintf(p, end-p, ",\"intent_heading\":%.1f", a->intent_heading); p += snprintf(p, end-p, ",\"nav_heading\":%.1f", a->nav_heading);
if (trackDataValid(&a->intent_modes_valid)) { if (trackDataValid(&a->nav_modes_valid)) {
p += snprintf(p, end-p, ",\"intent_modes\":["); p += snprintf(p, end-p, ",\"nav_modes\":[");
p = append_intent_modes(p, end, a->intent_modes, "\"", ","); p = append_nav_modes(p, end, a->nav_modes, "\"", ",");
p += snprintf(p, end-p, "]"); p += snprintf(p, end-p, "]");
} }
if (trackDataValid(&a->alt_setting_valid)) if (trackDataValid(&a->nav_qnh_valid))
p += snprintf(p, end-p, ",\"alt_setting\":%.1f", a->alt_setting); p += snprintf(p, end-p, ",\"nav_qnh\":%.1f", a->nav_qnh);
p += snprintf(p, end-p, ",\"mlat\":"); p += snprintf(p, end-p, ",\"mlat\":");
p = append_flags(p, end, a, SOURCE_MLAT); p = append_flags(p, end, a, SOURCE_MLAT);
@ -1757,13 +1756,13 @@ static void modesReadFromClient(struct client *c) {
} }
} }
static char *safe_vsnprintf(char *p, char *end, const char *format, va_list ap) __attribute__ ((format (printf,3,0))) static char *safe_vsnprintf(char *p, char *end, const char *format, va_list ap)
{ {
p += vsnprintf(p < end ? p : NULL, p < end ? (size_t)(end - p) : 0, format, ap); p += vsnprintf(p < end ? p : NULL, p < end ? (size_t)(end - p) : 0, format, ap);
return p; return p;
} }
static char *safe_snprintf(char *p, char *end, const char *format, ...) __attribute__ ((format (printf,3,4))) static char *safe_snprintf(char *p, char *end, const char *format, ...)
{ {
va_list ap; va_list ap;
va_start(ap, format); va_start(ap, format);
@ -1773,7 +1772,7 @@ static char *safe_snprintf(char *p, char *end, const char *format, ...)
} }
static char *appendFATSV(char *p, char *end, const char *field, const char *format, ...) __attribute__ ((format (printf,4,5))) static char *appendFATSV(char *p, char *end, const char *field, const char *format, ...)
{ {
va_list ap; va_list ap;
va_start(ap, format); va_start(ap, format);
@ -1913,7 +1912,7 @@ static inline float heading_difference(float h1, float h2)
return (d < 180) ? d : (360 - d); return (d < 180) ? d : (360 - d);
} }
static char *appendFATSVMeta(char *p, char *end, const char *field, struct aircraft *a, const data_validity *source, const char *format, ...) __attribute__ ((format (printf,6,7))) static char *appendFATSVMeta(char *p, char *end, const char *field, struct aircraft *a, const data_validity *source, const char *format, ...)
{ {
const char *sourcetype; const char *sourcetype;
switch (source->source) { switch (source->source) {
@ -1966,15 +1965,51 @@ static char *appendFATSVMeta(char *p, char *end, const char *field, struct aircr
return p; return p;
} }
static const char *airground_string(airground_t ag)
{
switch (ag) {
case AG_AIRBORNE:
return "A+";
case AG_GROUND:
return "G+";
default:
return "?";
}
}
static void writeFATSVBanner()
{
char *p = prepareWrite(&Modes.fatsv_out, TSV_MAX_PACKET_SIZE);
if (!p)
return;
char *end = p + TSV_MAX_PACKET_SIZE;
p = appendFATSV(p, end, "faup1090_format_version", "%s", "2");
--p; // remove last tab
p = safe_snprintf(p, end, "\n");
if (p <= end)
completeWrite(&Modes.fatsv_out, p);
else
fprintf(stderr, "fatsv: output too large (max %d, overran by %d)\n", TSV_MAX_PACKET_SIZE, (int) (p - end));
}
static void writeFATSV() static void writeFATSV()
{ {
struct aircraft *a; struct aircraft *a;
static uint64_t next_update; static uint64_t next_update;
static int first_run = 1;
if (!Modes.fatsv_out.service || !Modes.fatsv_out.service->connections) { if (!Modes.fatsv_out.service || !Modes.fatsv_out.service->connections) {
return; // not enabled or no active connections return; // not enabled or no active connections
} }
if (first_run) {
writeFATSVBanner();
first_run = 0;
}
uint64_t now = mstime(); uint64_t now = mstime();
if (now < next_update) { if (now < next_update) {
return; return;
@ -1995,9 +2030,10 @@ static void writeFATSV()
} }
// some special cases: // some special cases:
int altValid = trackDataValid(&a->altitude_valid); int altValid = trackDataValid(&a->altitude_baro_valid);
int airgroundValid = trackDataValid(&a->airground_valid) && a->airground_valid.source >= SOURCE_MODE_S_CHECKED; // for non-ADS-B transponders, only trust DF11 CA field int airgroundValid = trackDataValid(&a->airground_valid) && a->airground_valid.source >= SOURCE_MODE_S_CHECKED; // for non-ADS-B transponders, only trust DF11 CA field
int gsValid = trackDataValid(&a->gs_valid); int gsValid = trackDataValid(&a->gs_valid);
int squawkValid = trackDataValid(&a->squawk_valid);
int callsignValid = trackDataValid(&a->callsign_valid) && strcmp(a->callsign, " ") != 0; int callsignValid = trackDataValid(&a->callsign_valid) && strcmp(a->callsign, " ") != 0;
int positionValid = trackDataValid(&a->position_valid); int positionValid = trackDataValid(&a->position_valid);
@ -2005,14 +2041,14 @@ static void writeFATSV()
// When on the ground, ADS-B transponders don't emit an ADS-B message that includes // When on the ground, ADS-B transponders don't emit an ADS-B message that includes
// altitude, so a corrupted Mode S altitude response from some other in-the-air AC // altitude, so a corrupted Mode S altitude response from some other in-the-air AC
// might be taken as the "best available altitude" and produce e.g. "airGround G+ alt 31000". // might be taken as the "best available altitude" and produce e.g. "airGround G+ alt 31000".
if (airgroundValid && a->airground == AG_GROUND && a->altitude_valid.source < SOURCE_MODE_S_CHECKED) if (airgroundValid && a->airground == AG_GROUND && a->altitude_baro_valid.source < SOURCE_MODE_S_CHECKED)
altValid = 0; altValid = 0;
// if it hasn't changed altitude, heading, or speed much, // if it hasn't changed altitude, heading, or speed much,
// don't update so often // don't update so often
int changed = int changed =
(altValid && abs(a->altitude - a->fatsv_emitted_altitude) >= 50) || (altValid && abs(a->altitude_baro - a->fatsv_emitted_altitude_baro) >= 50) ||
(trackDataValid(&a->altitude_geom_valid) && abs(a->altitude_geom - a->fatsv_emitted_altitude_gnss) >= 50) || (trackDataValid(&a->altitude_geom_valid) && abs(a->altitude_geom - a->fatsv_emitted_altitude_geom) >= 50) ||
(trackDataValid(&a->baro_rate_valid) && abs(a->baro_rate - a->fatsv_emitted_baro_rate) > 500) || (trackDataValid(&a->baro_rate_valid) && abs(a->baro_rate - a->fatsv_emitted_baro_rate) > 500) ||
(trackDataValid(&a->geom_rate_valid) && abs(a->geom_rate - a->fatsv_emitted_geom_rate) > 500) || (trackDataValid(&a->geom_rate_valid) && abs(a->geom_rate - a->fatsv_emitted_geom_rate) > 500) ||
(trackDataValid(&a->track_valid) && heading_difference(a->track, a->fatsv_emitted_track) >= 2) || (trackDataValid(&a->track_valid) && heading_difference(a->track, a->fatsv_emitted_track) >= 2) ||
@ -2020,19 +2056,20 @@ static void writeFATSV()
(trackDataValid(&a->roll_valid) && fabs(a->roll - a->fatsv_emitted_roll) >= 5.0) || (trackDataValid(&a->roll_valid) && fabs(a->roll - a->fatsv_emitted_roll) >= 5.0) ||
(trackDataValid(&a->mag_heading_valid) && heading_difference(a->mag_heading, a->fatsv_emitted_mag_heading) >= 2) || (trackDataValid(&a->mag_heading_valid) && heading_difference(a->mag_heading, a->fatsv_emitted_mag_heading) >= 2) ||
(trackDataValid(&a->true_heading_valid) && heading_difference(a->true_heading, a->fatsv_emitted_true_heading) >= 2) || (trackDataValid(&a->true_heading_valid) && heading_difference(a->true_heading, a->fatsv_emitted_true_heading) >= 2) ||
(gsValid && unsigned_difference(a->gs, a->fatsv_emitted_speed) >= 25) || (gsValid && fabs(a->gs - a->fatsv_emitted_gs) >= 25) ||
(trackDataValid(&a->ias_valid) && unsigned_difference(a->ias, a->fatsv_emitted_speed_ias) >= 25) || (trackDataValid(&a->ias_valid) && unsigned_difference(a->ias, a->fatsv_emitted_ias) >= 25) ||
(trackDataValid(&a->tas_valid) && unsigned_difference(a->tas, a->fatsv_emitted_speed_tas) >= 25) || (trackDataValid(&a->tas_valid) && unsigned_difference(a->tas, a->fatsv_emitted_tas) >= 25) ||
(trackDataValid(&a->mach_valid) && fabs(a->mach - a->fatsv_emitted_mach) >= 0.02); (trackDataValid(&a->mach_valid) && fabs(a->mach - a->fatsv_emitted_mach) >= 0.02);
int immediate = int immediate =
(trackDataValid(&a->intent_altitude_valid) && unsigned_difference(a->intent_altitude, a->fatsv_emitted_intent_altitude) > 50) || (trackDataValid(&a->nav_altitude_valid) && unsigned_difference(a->nav_altitude, a->fatsv_emitted_nav_altitude) > 50) ||
(trackDataValid(&a->intent_heading_valid) && heading_difference(a->intent_heading, a->fatsv_emitted_intent_heading) > 2) || (trackDataValid(&a->nav_heading_valid) && heading_difference(a->nav_heading, a->fatsv_emitted_nav_heading) > 2) ||
(trackDataValid(&a->intent_modes_valid) && a->intent_modes != a->fatsv_emitted_intent_modes) || (trackDataValid(&a->nav_modes_valid) && a->nav_modes != a->fatsv_emitted_nav_modes) ||
(trackDataValid(&a->alt_setting_valid) && fabs(a->alt_setting - a->fatsv_emitted_alt_setting) > 0.8) || // 0.8 is the ES message resolution (trackDataValid(&a->nav_qnh_valid) && fabs(a->nav_qnh - a->fatsv_emitted_nav_qnh) > 0.8) || // 0.8 is the ES message resolution
(callsignValid && strcmp(a->callsign, a->fatsv_emitted_callsign) != 0) || (callsignValid && strcmp(a->callsign, a->fatsv_emitted_callsign) != 0) ||
(airgroundValid && a->airground == AG_AIRBORNE && a->fatsv_emitted_airground == AG_GROUND) || (airgroundValid && a->airground == AG_AIRBORNE && a->fatsv_emitted_airground == AG_GROUND) ||
(airgroundValid && a->airground == AG_GROUND && a->fatsv_emitted_airground == AG_AIRBORNE); (airgroundValid && a->airground == AG_GROUND && a->fatsv_emitted_airground == AG_AIRBORNE) ||
(squawkValid && a->squawk != a->fatsv_emitted_squawk);
uint64_t minAge; uint64_t minAge;
if (immediate) { if (immediate) {
@ -2042,11 +2079,11 @@ static void writeFATSV()
// don't send mode S very often // don't send mode S very often
minAge = 30000; minAge = 30000;
} else if ((airgroundValid && a->airground == AG_GROUND) || } else if ((airgroundValid && a->airground == AG_GROUND) ||
(altValid && a->altitude < 500 && (!gsValid || a->gs < 200)) || (altValid && a->altitude_baro < 500 && (!gsValid || a->gs < 200)) ||
(gsValid && a->gs < 100 && (!altValid || a->altitude < 1000))) { (gsValid && a->gs < 100 && (!altValid || a->altitude_baro < 1000))) {
// we are probably on the ground, increase the update rate // we are probably on the ground, increase the update rate
minAge = 1000; minAge = 1000;
} else if (!altValid || a->altitude < 10000) { } else if (!altValid || a->altitude_baro < 10000) {
// Below 10000 feet, emit up to every 5s when changing, 10s otherwise // Below 10000 feet, emit up to every 5s when changing, 10s otherwise
minAge = (changed ? 5000 : 10000); minAge = (changed ? 5000 : 10000);
} else { } else {
@ -2065,13 +2102,20 @@ static void writeFATSV()
p = appendFATSV(p, end, "clock", "%" PRIu64, messageNow() / 1000); p = appendFATSV(p, end, "clock", "%" PRIu64, messageNow() / 1000);
p = appendFATSV(p, end, (a->addr & MODES_NON_ICAO_ADDRESS) ? "otherid" : "hexid", "%06X", a->addr & 0xFFFFFF); p = appendFATSV(p, end, (a->addr & MODES_NON_ICAO_ADDRESS) ? "otherid" : "hexid", "%06X", a->addr & 0xFFFFFF);
if (a->addrtype != ADDR_ADSB_ICAO) { // for fields we only emit on change,
// occasionally re-emit them all
int forceEmit = (now - a->fatsv_last_force_emit) > 600000;
// these don't change often / at all, only emit when they change
if (forceEmit || a->addrtype != a->fatsv_emitted_addrtype) {
p = appendFATSV(p, end, "addrtype", "%s", addrtype_short_string(a->addrtype)); p = appendFATSV(p, end, "addrtype", "%s", addrtype_short_string(a->addrtype));
} }
if (forceEmit || a->adsb_version != a->fatsv_emitted_adsb_version) {
if (a->adsb_version >= 0) {
p = appendFATSV(p, end, "adsbVer", "%d", a->adsb_version); p = appendFATSV(p, end, "adsbVer", "%d", a->adsb_version);
} }
if (forceEmit || a->category != a->fatsv_emitted_category) {
p = appendFATSV(p, end, "category", "%02X", a->category);
}
// only emit alt, speed, latlon, track etc if they have been received since the last time // only emit alt, speed, latlon, track etc if they have been received since the last time
// and are not stale // and are not stale
@ -2079,34 +2123,35 @@ static void writeFATSV()
char *dataStart = p; char *dataStart = p;
// special cases // special cases
if (altValid)
p = appendFATSVMeta(p, end, "alt", a, &a->altitude_valid, "%d", a->altitude);
if (airgroundValid) if (airgroundValid)
p = appendFATSVMeta(p, end, "ag", a, &a->airground_valid, "%s", a->airground == AG_GROUND ? "G+" : "A+"); p = appendFATSVMeta(p, end, "airGround", a, &a->airground_valid, "%s", airground_string(a->airground));
if (strcmp(a->callsign, " ") != 0) if (squawkValid)
p = appendFATSVMeta(p, end, "ident", a, &a->callsign_valid, "{%s}", a->callsign);
if (positionValid)
p = appendFATSVMeta(p, end, "pos", a, &a->position_valid, "{%.5f %.5f}", a->lat, a->lon);
p = appendFATSVMeta(p, end, "squawk", a, &a->squawk_valid, "%04x", a->squawk); p = appendFATSVMeta(p, end, "squawk", a, &a->squawk_valid, "%04x", a->squawk);
p = appendFATSVMeta(p, end, "altGeo", a, &a->altitude_geom_valid, "%d", a->altitude_geom); if (callsignValid)
p = appendFATSVMeta(p, end, "ident", a, &a->callsign_valid, "{%s}", a->callsign);
if (altValid)
p = appendFATSVMeta(p, end, "alt", a, &a->altitude_baro_valid, "%d", a->altitude_baro);
if (positionValid) {
p = appendFATSVMeta(p, end, "position", a, &a->position_valid, "{%.5f %.5f %u %u}", a->lat, a->lon, a->pos_nic, a->pos_rc);
p = appendFATSVMeta(p, end, "nac_p", a, &a->nac_p_valid, "%u", a->nac_p);
}
p = appendFATSVMeta(p, end, "alt_gnss", a, &a->altitude_geom_valid, "%d", a->altitude_geom);
p = appendFATSVMeta(p, end, "vrate", a, &a->baro_rate_valid, "%d", a->baro_rate); p = appendFATSVMeta(p, end, "vrate", a, &a->baro_rate_valid, "%d", a->baro_rate);
p = appendFATSVMeta(p, end, "vrateGeo", a, &a->geom_rate_valid, "%d", a->geom_rate); p = appendFATSVMeta(p, end, "vrate_geom", a, &a->geom_rate_valid, "%d", a->geom_rate);
p = appendFATSVMeta(p, end, "gs", a, &a->gs_valid, "%u", a->gs); p = appendFATSVMeta(p, end, "speed", a, &a->gs_valid, "%.1f", a->gs);
p = appendFATSVMeta(p, end, "ias", a, &a->ias_valid, "%u", a->ias); p = appendFATSVMeta(p, end, "speed_ias", a, &a->ias_valid, "%u", a->ias);
p = appendFATSVMeta(p, end, "tas", a, &a->tas_valid, "%u", a->tas); p = appendFATSVMeta(p, end, "speed_tas", a, &a->tas_valid, "%u", a->tas);
p = appendFATSVMeta(p, end, "mach", a, &a->mach_valid, "%.3f", a->mach); p = appendFATSVMeta(p, end, "mach", a, &a->mach_valid, "%.3f", a->mach);
p = appendFATSVMeta(p, end, "trk", a, &a->track_valid, "%.0f", a->track); p = appendFATSVMeta(p, end, "track", a, &a->track_valid, "%.1f", a->track);
p = appendFATSVMeta(p, end, "trkRate", a, &a->track_rate_valid, "%.2f", a->track_rate); p = appendFATSVMeta(p, end, "track_rate", a, &a->track_rate_valid, "%.2f", a->track_rate);
p = appendFATSVMeta(p, end, "roll", a, &a->roll_valid, "%.1f", a->roll); p = appendFATSVMeta(p, end, "roll", a, &a->roll_valid, "%.1f", a->roll);
p = appendFATSVMeta(p, end, "hdgMag", a, &a->mag_heading_valid, "%.0f", a->mag_heading); p = appendFATSVMeta(p, end, "heading_magnetic", a, &a->mag_heading_valid, "%.1f", a->mag_heading);
p = appendFATSVMeta(p, end, "hdgTrue", a, &a->true_heading_valid, "%.0f", a->true_heading); p = appendFATSVMeta(p, end, "heading_true", a, &a->true_heading_valid, "%.1f", a->true_heading);
if (a->category != 0xA0) p = appendFATSVMeta(p, end, "nav_alt", a, &a->nav_altitude_valid, "%u", a->nav_altitude);
p = appendFATSVMeta(p, end, "category", a, &a->category_valid, "%02X", a->category); p = appendFATSVMeta(p, end, "nav_heading", a, &a->nav_heading_valid, "%.1f", a->nav_heading);
p = appendFATSVMeta(p, end, "selAlt", a, &a->intent_altitude_valid,"%u", a->intent_altitude); p = appendFATSVMeta(p, end, "nav_modes", a, &a->nav_modes_valid, "{%s}", nav_modes_string(a->nav_modes));
p = appendFATSVMeta(p, end, "selHdg", a, &a->intent_heading_valid, "%.0f", a->intent_heading); p = appendFATSVMeta(p, end, "nav_qnh", a, &a->nav_qnh_valid, "%.1f", a->nav_qnh);
p = appendFATSVMeta(p, end, "selModes", a, &a->intent_modes_valid, "{%s}", intent_modes_string(a->intent_modes));
p = appendFATSVMeta(p, end, "qnh", a, &a->alt_setting_valid, "%.1f", a->alt_setting);
// if we didn't get anything interesting, bail out. // if we didn't get anything interesting, bail out.
// We don't need to do anything special to unwind prepareWrite(). // We don't need to do anything special to unwind prepareWrite().
@ -2122,12 +2167,13 @@ static void writeFATSV()
else else
fprintf(stderr, "fatsv: output too large (max %d, overran by %d)\n", TSV_MAX_PACKET_SIZE, (int) (p - end)); fprintf(stderr, "fatsv: output too large (max %d, overran by %d)\n", TSV_MAX_PACKET_SIZE, (int) (p - end));
a->fatsv_emitted_altitude = a->altitude; a->fatsv_emitted_altitude_baro = a->altitude_baro;
a->fatsv_emitted_altitude_gnss = a->altitude_geom; a->fatsv_emitted_altitude_geom = a->altitude_geom;
a->fatsv_emitted_baro_rate = a->baro_rate; a->fatsv_emitted_baro_rate = a->baro_rate;
a->fatsv_emitted_geom_rate = a->geom_rate; a->fatsv_emitted_geom_rate = a->geom_rate;
a->fatsv_emitted_speed = a->gs; a->fatsv_emitted_gs = a->gs;
a->fatsv_emitted_speed_ias = a->ias; a->fatsv_emitted_ias = a->ias;
a->fatsv_emitted_tas = a->tas;
a->fatsv_emitted_mach = a->mach; a->fatsv_emitted_mach = a->mach;
a->fatsv_emitted_track = a->track; a->fatsv_emitted_track = a->track;
a->fatsv_emitted_track_rate = a->track_rate; a->fatsv_emitted_track_rate = a->track_rate;
@ -2135,12 +2181,19 @@ static void writeFATSV()
a->fatsv_emitted_mag_heading = a->mag_heading; a->fatsv_emitted_mag_heading = a->mag_heading;
a->fatsv_emitted_true_heading = a->true_heading; a->fatsv_emitted_true_heading = a->true_heading;
a->fatsv_emitted_airground = a->airground; a->fatsv_emitted_airground = a->airground;
a->fatsv_emitted_intent_altitude = a->intent_altitude; a->fatsv_emitted_nav_altitude = a->nav_altitude;
a->fatsv_emitted_intent_heading = a->intent_heading; a->fatsv_emitted_nav_heading = a->nav_heading;
a->fatsv_emitted_intent_modes = a->intent_modes; a->fatsv_emitted_nav_modes = a->nav_modes;
a->fatsv_emitted_alt_setting = a->alt_setting; a->fatsv_emitted_nav_qnh = a->nav_qnh;
memcpy(a->fatsv_emitted_callsign, a->callsign, sizeof(a->fatsv_emitted_callsign)); memcpy(a->fatsv_emitted_callsign, a->callsign, sizeof(a->fatsv_emitted_callsign));
a->fatsv_emitted_addrtype = a->addrtype;
a->fatsv_emitted_adsb_version = a->adsb_version;
a->fatsv_emitted_category = a->category;
a->fatsv_emitted_squawk = a->squawk;
a->fatsv_last_emitted = now; a->fatsv_last_emitted = now;
if (forceEmit) {
a->fatsv_last_force_emit = now;
}
} }
} }

439
track.c
View file

@ -76,15 +76,22 @@ struct aircraft *trackCreateAircraft(struct modesMessage *mm) {
a->signalLevel[i] = 1e-5; a->signalLevel[i] = 1e-5;
a->signalNext = 0; a->signalNext = 0;
// defaults until we see a message otherwise
a->adsb_version = -1;
a->adsb_hrd = HEADING_MAGNETIC;
a->adsb_tah = HEADING_GROUND_TRACK;
// prime FATSV defaults we only emit on change
// start off with the "last emitted" ACAS RA being blank (just the BDS 3,0 // start off with the "last emitted" ACAS RA being blank (just the BDS 3,0
// or ES type code) // or ES type code)
a->fatsv_emitted_bds_30[0] = 0x30; a->fatsv_emitted_bds_30[0] = 0x30;
a->fatsv_emitted_es_acas_ra[0] = 0xE2; a->fatsv_emitted_es_acas_ra[0] = 0xE2;
a->fatsv_emitted_adsb_version = -1;
a->fatsv_emitted_addrtype = ADDR_UNKNOWN;
// defaults until we see an op status message // don't immediately emit, let some data build up
a->adsb_version = -1; a->fatsv_last_emitted = a->fatsv_last_force_emit = messageNow();
a->adsb_hrd = HEADING_MAGNETIC;
a->adsb_tah = HEADING_GROUND_TRACK;
// Copy the first message so we can emit it later when a second message arrives. // Copy the first message so we can emit it later when a second message arrives.
a->first_message = *mm; a->first_message = *mm;
@ -92,7 +99,7 @@ struct aircraft *trackCreateAircraft(struct modesMessage *mm) {
// initialize data validity ages // initialize data validity ages
#define F(f,s,e) do { a->f##_valid.stale_interval = (s) * 1000; a->f##_valid.expire_interval = (e) * 1000; } while (0) #define F(f,s,e) do { a->f##_valid.stale_interval = (s) * 1000; a->f##_valid.expire_interval = (e) * 1000; } while (0)
F(callsign, 60, 70); // ADS-B or Comm-B F(callsign, 60, 70); // ADS-B or Comm-B
F(altitude, 15, 70); // ADS-B or Mode S F(altitude_baro, 15, 70); // ADS-B or Mode S
F(altitude_geom, 60, 70); // ADS-B only F(altitude_geom, 60, 70); // ADS-B only
F(geom_delta, 60, 70); // ADS-B only F(geom_delta, 60, 70); // ADS-B only
F(gs, 60, 70); // ADS-B or Comm-B F(gs, 60, 70); // ADS-B or Comm-B
@ -107,14 +114,22 @@ struct aircraft *trackCreateAircraft(struct modesMessage *mm) {
F(baro_rate, 60, 70); // ADS-B or Comm-B F(baro_rate, 60, 70); // ADS-B or Comm-B
F(geom_rate, 60, 70); // ADS-B or Comm-B F(geom_rate, 60, 70); // ADS-B or Comm-B
F(squawk, 15, 70); // ADS-B or Mode S F(squawk, 15, 70); // ADS-B or Mode S
F(category, 60, 70); // ADS-B only
F(airground, 15, 70); // ADS-B or Mode S F(airground, 15, 70); // ADS-B or Mode S
F(alt_setting, 60, 70); // Comm-B only F(nav_qnh, 60, 70); // Comm-B only
F(intent_altitude, 60, 70); // ADS-B or Comm-B F(nav_altitude, 60, 70); // ADS-B or Comm-B
F(intent_modes, 60, 70); // ADS-B or Comm-B F(nav_heading, 60, 70); // ADS-B or Comm-B
F(nav_modes, 60, 70); // ADS-B or Comm-B
F(cpr_odd, 60, 70); // ADS-B only F(cpr_odd, 60, 70); // ADS-B only
F(cpr_even, 60, 70); // ADS-B only F(cpr_even, 60, 70); // ADS-B only
F(position, 60, 70); // ADS-B only F(position, 60, 70); // ADS-B only
F(nic_a, 60, 70); // ADS-B only
F(nic_c, 60, 70); // ADS-B only
F(nic_baro, 60, 70); // ADS-B only
F(nac_p, 60, 70); // ADS-B only
F(nac_v, 60, 70); // ADS-B only
F(sil, 60, 70); // ADS-B only
F(gva, 60, 70); // ADS-B only
F(sda, 60, 70); // ADS-B only
#undef F #undef F
Modes.stats_current.unique_aircraft++; Modes.stats_current.unique_aircraft++;
@ -150,8 +165,8 @@ static int accept_data(data_validity *d, datasource_t source)
d->source = source; d->source = source;
d->updated = messageNow(); d->updated = messageNow();
d->stale = messageNow() + d->stale_interval; d->stale = messageNow() + (d->stale_interval ? d->stale_interval : 60000);
d->expires = messageNow() + d->expire_interval; d->expires = messageNow() + (d->expire_interval ? d->expire_interval : 70000);
return 1; return 1;
} }
@ -287,13 +302,16 @@ static int speed_check(struct aircraft *a, double lat, double lon, int surface)
return inrange; return inrange;
} }
static int doGlobalCPR(struct aircraft *a, struct modesMessage *mm, double *lat, double *lon, unsigned *nuc) static int doGlobalCPR(struct aircraft *a, struct modesMessage *mm, double *lat, double *lon, unsigned *nic, unsigned *rc)
{ {
int result; int result;
int fflag = mm->cpr_odd; int fflag = mm->cpr_odd;
int surface = (mm->cpr_type == CPR_SURFACE); int surface = (mm->cpr_type == CPR_SURFACE);
*nuc = (a->cpr_even_nuc < a->cpr_odd_nuc ? a->cpr_even_nuc : a->cpr_odd_nuc); // worst of the two positions // derive NIC, Rc from the worse of the two position
// smaller NIC is worse; larger Rc is worse
*nic = (a->cpr_even_nic < a->cpr_odd_nic ? a->cpr_even_nic : a->cpr_odd_nic);
*rc = (a->cpr_even_rc > a->cpr_odd_rc ? a->cpr_even_rc : a->cpr_odd_rc);
if (surface) { if (surface) {
// surface global CPR // surface global CPR
@ -303,8 +321,6 @@ static int doGlobalCPR(struct aircraft *a, struct modesMessage *mm, double *lat,
if (trackDataValid(&a->position_valid)) { // Ok to try aircraft relative first if (trackDataValid(&a->position_valid)) { // Ok to try aircraft relative first
reflat = a->lat; reflat = a->lat;
reflon = a->lon; reflon = a->lon;
if (a->pos_nuc < *nuc)
*nuc = a->pos_nuc;
} else if (Modes.bUserFlags & MODES_USER_LATLON_VALID) { } else if (Modes.bUserFlags & MODES_USER_LATLON_VALID) {
reflat = Modes.fUserLat; reflat = Modes.fUserLat;
reflon = Modes.fUserLon; reflon = Modes.fUserLon;
@ -356,7 +372,7 @@ static int doGlobalCPR(struct aircraft *a, struct modesMessage *mm, double *lat,
return result; return result;
// check speed limit // check speed limit
if (trackDataValid(&a->position_valid) && a->pos_nuc >= *nuc && !speed_check(a, *lat, *lon, surface)) { if (trackDataValid(&a->position_valid) && a->pos_nic >= *nic && a->pos_rc <= *rc && !speed_check(a, *lat, *lon, surface)) {
Modes.stats_current.cpr_global_speed_checks++; Modes.stats_current.cpr_global_speed_checks++;
return -2; return -2;
} }
@ -364,7 +380,7 @@ static int doGlobalCPR(struct aircraft *a, struct modesMessage *mm, double *lat,
return result; return result;
} }
static int doLocalCPR(struct aircraft *a, struct modesMessage *mm, double *lat, double *lon, unsigned *nuc) static int doLocalCPR(struct aircraft *a, struct modesMessage *mm, double *lat, double *lon, unsigned *nic, unsigned *rc)
{ {
// relative CPR // relative CPR
// find reference location // find reference location
@ -374,14 +390,22 @@ static int doLocalCPR(struct aircraft *a, struct modesMessage *mm, double *lat,
int fflag = mm->cpr_odd; int fflag = mm->cpr_odd;
int surface = (mm->cpr_type == CPR_SURFACE); int surface = (mm->cpr_type == CPR_SURFACE);
*nuc = mm->cpr_nucp; if (fflag) {
*nic = a->cpr_odd_nic;
*rc = a->cpr_odd_rc;
} else {
*nic = a->cpr_even_nic;
*rc = a->cpr_even_rc;
}
if (trackDataValid(&a->position_valid)) { if (trackDataValid(&a->position_valid)) {
reflat = a->lat; reflat = a->lat;
reflon = a->lon; reflon = a->lon;
if (a->pos_nuc < *nuc) if (a->pos_nic < *nic)
*nuc = a->pos_nuc; *nic = a->pos_nic;
if (a->pos_rc < *rc)
*rc = a->pos_rc;
range_limit = 50e3; range_limit = 50e3;
} else if (!surface && (Modes.bUserFlags & MODES_USER_LATLON_VALID)) { } else if (!surface && (Modes.bUserFlags & MODES_USER_LATLON_VALID)) {
@ -430,7 +454,7 @@ static int doLocalCPR(struct aircraft *a, struct modesMessage *mm, double *lat,
} }
// check speed limit // check speed limit
if (trackDataValid(&a->position_valid) && a->pos_nuc >= *nuc && !speed_check(a, *lat, *lon, surface)) { if (trackDataValid(&a->position_valid) && a->pos_nic >= *nic && a->pos_rc <= *rc && !speed_check(a, *lat, *lon, surface)) {
#ifdef DEBUG_CPR_CHECKS #ifdef DEBUG_CPR_CHECKS
fprintf(stderr, "Speed check for %06X with local decoding failed\n", a->addr); fprintf(stderr, "Speed check for %06X with local decoding failed\n", a->addr);
#endif #endif
@ -454,7 +478,8 @@ static void updatePosition(struct aircraft *a, struct modesMessage *mm)
int location_result = -1; int location_result = -1;
uint64_t max_elapsed; uint64_t max_elapsed;
double new_lat = 0, new_lon = 0; double new_lat = 0, new_lon = 0;
unsigned new_nuc = 0; unsigned new_nic = 0;
unsigned new_rc = 0;
int surface; int surface;
surface = (mm->cpr_type == CPR_SURFACE); surface = (mm->cpr_type == CPR_SURFACE);
@ -463,7 +488,7 @@ static void updatePosition(struct aircraft *a, struct modesMessage *mm)
++Modes.stats_current.cpr_surface; ++Modes.stats_current.cpr_surface;
// Surface: 25 seconds if >25kt or speed unknown, 50 seconds otherwise // Surface: 25 seconds if >25kt or speed unknown, 50 seconds otherwise
if (mm->gs_valid && mm->gs <= 25) if (mm->gs_valid && mm->gs.selected <= 25)
max_elapsed = 50000; max_elapsed = 50000;
else else
max_elapsed = 25000; max_elapsed = 25000;
@ -480,7 +505,7 @@ static void updatePosition(struct aircraft *a, struct modesMessage *mm)
a->cpr_odd_type == a->cpr_even_type && a->cpr_odd_type == a->cpr_even_type &&
time_between(a->cpr_odd_valid.updated, a->cpr_even_valid.updated) <= max_elapsed) { time_between(a->cpr_odd_valid.updated, a->cpr_even_valid.updated) <= max_elapsed) {
location_result = doGlobalCPR(a, mm, &new_lat, &new_lon, &new_nuc); location_result = doGlobalCPR(a, mm, &new_lat, &new_lon, &new_nic, &new_rc);
if (location_result == -2) { if (location_result == -2) {
#ifdef DEBUG_CPR_CHECKS #ifdef DEBUG_CPR_CHECKS
@ -511,7 +536,7 @@ static void updatePosition(struct aircraft *a, struct modesMessage *mm)
// Otherwise try relative CPR. // Otherwise try relative CPR.
if (location_result == -1) { if (location_result == -1) {
location_result = doLocalCPR(a, mm, &new_lat, &new_lon, &new_nuc); location_result = doLocalCPR(a, mm, &new_lat, &new_lon, &new_nic, &new_rc);
if (location_result < 0) { if (location_result < 0) {
Modes.stats_current.cpr_local_skipped++; Modes.stats_current.cpr_local_skipped++;
@ -532,16 +557,247 @@ static void updatePosition(struct aircraft *a, struct modesMessage *mm)
mm->cpr_decoded = 1; mm->cpr_decoded = 1;
mm->decoded_lat = new_lat; mm->decoded_lat = new_lat;
mm->decoded_lon = new_lon; mm->decoded_lon = new_lon;
mm->decoded_nic = new_nic;
mm->decoded_rc = new_rc;
// Update aircraft state // Update aircraft state
a->lat = new_lat; a->lat = new_lat;
a->lon = new_lon; a->lon = new_lon;
a->pos_nuc = new_nuc; a->pos_nic = new_nic;
a->pos_rc = new_rc;
update_range_histogram(new_lat, new_lon); update_range_histogram(new_lat, new_lon);
} }
} }
static unsigned compute_nic(unsigned metype, unsigned version, unsigned nic_a, unsigned nic_b, unsigned nic_c)
{
switch (metype) {
case 5: // surface
case 9: // airborne
case 20: // airborne, GNSS altitude
return 11;
case 6: // surface
case 10: // airborne
case 21: // airborne, GNSS altitude
return 10;
case 7: // surface
if (version == 2) {
if (nic_a && !nic_c) {
return 9;
} else {
return 8;
}
} else if (version == 1) {
if (nic_a) {
return 9;
} else {
return 8;
}
} else {
return 8;
}
case 8: // surface
if (version == 2) {
if (nic_a && nic_c) {
return 7;
} else if (nic_a && !nic_c) {
return 6;
} else if (!nic_a && nic_c) {
return 6;
} else {
return 0;
}
} else {
return 0;
}
case 11: // airborne
if (version == 2) {
if (nic_a && nic_b) {
return 9;
} else {
return 8;
}
} else if (version == 1) {
if (nic_a) {
return 9;
} else {
return 8;
}
} else {
return 8;
}
case 12: // airborne
return 7;
case 13: // airborne
return 6;
case 14: // airborne
return 5;
case 15: // airborne
return 4;
case 16: // airborne
if (nic_a && nic_b) {
return 3;
} else {
return 2;
}
case 17: // airborne
return 1;
default:
return 0;
}
}
static unsigned compute_rc(unsigned metype, unsigned version, unsigned nic_a, unsigned nic_b, unsigned nic_c)
{
switch (metype) {
case 5: // surface
case 9: // airborne
case 20: // airborne, GNSS altitude
return 8; // 7.5m
case 6: // surface
case 10: // airborne
case 21: // airborne, GNSS altitude
return 25;
case 7: // surface
if (version == 2) {
if (nic_a && !nic_c) {
return 75;
} else {
return 186; // 185.2m, 0.1NM
}
} else if (version == 1) {
if (nic_a) {
return 75;
} else {
return 186; // 185.2m, 0.1NM
}
} else {
return 186; // 185.2m, 0.1NM
}
case 8: // surface
if (version == 2) {
if (nic_a && nic_c) {
return 371; // 370.4m, 0.2NM
} else if (nic_a && !nic_c) {
return 556; // 555.6m, 0.3NM
} else if (!nic_a && nic_c) {
return 926; // 926m, 0.5NM
} else {
return RC_UNKNOWN;
}
} else {
return RC_UNKNOWN;
}
case 11: // airborne
if (version == 2) {
if (nic_a && nic_b) {
return 75;
} else {
return 186; // 370.4m, 0.2NM
}
} else if (version == 1) {
if (nic_a) {
return 75;
} else {
return 186; // 370.4m, 0.2NM
}
} else {
return 186; // 370.4m, 0.2NM
}
case 12: // airborne
return 371; // 370.4m, 0.2NM
case 13: // airborne
if (version == 2) {
if (!nic_a && nic_b) {
return 556; // 555.6m, 0.3NM
} else if (!nic_a && !nic_b) {
return 926; // 926m, 0.5NM
} else if (nic_a && nic_b) {
return 1112; // 1111.2m, 0.6NM
} else {
return 1112; // bad combination, assume worst Rc
}
} else if (version == 1) {
if (nic_a) {
return 1112; // 1111.2m, 0.6NM
} else {
return 926; // 926m, 0.5NM
}
} else {
return 926; // 926m, 0.5NM
}
case 14: // airborne
return 1852; // 1.0NM
case 15: // airborne
return 3704; // 2NM
case 16: // airborne
if (version == 2) {
if (nic_a && nic_b) {
return 7408; // 4NM
} else {
return 14816; // 8NM
}
} else if (version == 1) {
if (nic_a) {
return 7408; // 4NM
} else {
return 14816; // 8NM
}
} else {
return 18510; // 10NM
}
case 17: // airborne
return 37040; // 20NM
default:
return RC_UNKNOWN;
}
}
static void compute_nic_rc_from_message(struct modesMessage *mm, struct aircraft *a, unsigned *nic, unsigned *rc)
{
int nic_a = (trackDataValid(&a->nic_a_valid) && a->nic_a);
int nic_b = (mm->accuracy.nic_b_valid && mm->accuracy.nic_b);
int nic_c = (trackDataValid(&a->nic_c_valid) && a->nic_c);
*nic = compute_nic(mm->metype, a->adsb_version, nic_a, nic_b, nic_c);
*rc = compute_rc(mm->metype, a->adsb_version, nic_a, nic_b, nic_c);
}
static int altitude_to_feet(int raw, altitude_unit_t unit)
{
switch (unit) {
case UNIT_METERS:
return raw / 0.3048;
case UNIT_FEET:
return raw;
default:
return 0;
}
}
// //
//========================================================================= //=========================================================================
// //
@ -583,16 +839,34 @@ struct aircraft *trackUpdateFromMessage(struct modesMessage *mm)
if (mm->source == SOURCE_ADSB && a->adsb_version < 0) if (mm->source == SOURCE_ADSB && a->adsb_version < 0)
a->adsb_version = 0; a->adsb_version = 0;
if (mm->altitude_valid && mm->altitude_source == ALTITUDE_BARO && accept_data(&a->altitude_valid, mm->source)) { // category shouldn't change over time, don't bother with metadata
if (mm->category_valid) {
a->category = mm->category;
}
// operational status message
// done early to update version / HRD / TAH
if (mm->opstatus.valid) {
a->adsb_version = mm->opstatus.version;
if (mm->opstatus.hrd != HEADING_INVALID) {
a->adsb_hrd = mm->opstatus.hrd;
}
if (mm->opstatus.tah != HEADING_INVALID) {
a->adsb_tah = mm->opstatus.tah;
}
}
if (mm->altitude_baro_valid && accept_data(&a->altitude_baro_valid, mm->source)) {
int alt = altitude_to_feet(mm->altitude_baro, mm->altitude_baro_unit);
if (a->modeC_hit) { if (a->modeC_hit) {
int new_modeC = (a->altitude + 49) / 100; int new_modeC = (a->altitude_baro + 49) / 100;
int old_modeC = (mm->altitude + 49) / 100; int old_modeC = (alt + 49) / 100;
if (new_modeC != old_modeC) { if (new_modeC != old_modeC) {
a->modeC_hit = 0; a->modeC_hit = 0;
} }
} }
a->altitude = mm->altitude; a->altitude_baro = alt;
} }
if (mm->squawk_valid && accept_data(&a->squawk_valid, mm->source)) { if (mm->squawk_valid && accept_data(&a->squawk_valid, mm->source)) {
@ -602,8 +876,8 @@ struct aircraft *trackUpdateFromMessage(struct modesMessage *mm)
a->squawk = mm->squawk; a->squawk = mm->squawk;
} }
if (mm->altitude_valid && mm->altitude_source == ALTITUDE_GEOM && accept_data(&a->altitude_geom_valid, mm->source)) { if (mm->altitude_geom_valid && accept_data(&a->altitude_geom_valid, mm->source)) {
a->altitude_geom = mm->altitude; a->altitude_geom = altitude_to_feet(mm->altitude_geom, mm->altitude_geom_unit);
} }
if (mm->geom_delta_valid && accept_data(&a->geom_delta_valid, mm->source)) { if (mm->geom_delta_valid && accept_data(&a->geom_delta_valid, mm->source)) {
@ -635,8 +909,11 @@ struct aircraft *trackUpdateFromMessage(struct modesMessage *mm)
a->roll = mm->roll; a->roll = mm->roll;
} }
if (mm->gs_valid && accept_data(&a->gs_valid, mm->source)) { if (mm->gs_valid) {
a->gs = mm->gs; mm->gs.selected = (a->adsb_version == 2 ? mm->gs.v2 : mm->gs.v0);
if (accept_data(&a->gs_valid, mm->source)) {
a->gs = mm->gs.selected;
}
} }
if (mm->ias_valid && accept_data(&a->ias_valid, mm->source)) { if (mm->ias_valid && accept_data(&a->ias_valid, mm->source)) {
@ -659,10 +936,6 @@ struct aircraft *trackUpdateFromMessage(struct modesMessage *mm)
a->geom_rate = mm->geom_rate; a->geom_rate = mm->geom_rate;
} }
if (mm->category_valid && accept_data(&a->category_valid, mm->source)) {
a->category = mm->category;
}
if (mm->airground != AG_INVALID && accept_data(&a->airground_valid, mm->source)) { if (mm->airground != AG_INVALID && accept_data(&a->airground_valid, mm->source)) {
a->airground = mm->airground; a->airground = mm->airground;
} }
@ -673,22 +946,22 @@ struct aircraft *trackUpdateFromMessage(struct modesMessage *mm)
// prefer MCP over FMS // prefer MCP over FMS
// unless the source says otherwise // unless the source says otherwise
if (mm->intent.mcp_altitude_valid && mm->intent.altitude_source != INTENT_ALT_FMS && accept_data(&a->intent_altitude_valid, mm->source)) { if (mm->nav.mcp_altitude_valid && mm->nav.altitude_source != NAV_ALT_FMS && accept_data(&a->nav_altitude_valid, mm->source)) {
a->intent_altitude = mm->intent.mcp_altitude; a->nav_altitude = mm->nav.mcp_altitude;
} else if (mm->intent.fms_altitude_valid && accept_data(&a->intent_altitude_valid, mm->source)) { } else if (mm->nav.fms_altitude_valid && accept_data(&a->nav_altitude_valid, mm->source)) {
a->intent_altitude = mm->intent.fms_altitude; a->nav_altitude = mm->nav.fms_altitude;
} }
if (mm->intent.heading_valid && accept_data(&a->intent_heading_valid, mm->source)) { if (mm->nav.heading_valid && accept_data(&a->nav_heading_valid, mm->source)) {
a->intent_heading = mm->intent.heading; a->nav_heading = mm->nav.heading;
} }
if (mm->intent.modes_valid && accept_data(&a->intent_modes_valid, mm->source)) { if (mm->nav.modes_valid && accept_data(&a->nav_modes_valid, mm->source)) {
a->intent_modes = mm->intent.modes; a->nav_modes = mm->nav.modes;
} }
if (mm->intent.alt_setting_valid && accept_data(&a->alt_setting_valid, mm->source)) { if (mm->nav.qnh_valid && accept_data(&a->nav_qnh_valid, mm->source)) {
a->alt_setting = mm->intent.alt_setting; a->nav_qnh = mm->nav.qnh;
} }
// CPR, even // CPR, even
@ -696,7 +969,7 @@ struct aircraft *trackUpdateFromMessage(struct modesMessage *mm)
a->cpr_even_type = mm->cpr_type; a->cpr_even_type = mm->cpr_type;
a->cpr_even_lat = mm->cpr_lat; a->cpr_even_lat = mm->cpr_lat;
a->cpr_even_lon = mm->cpr_lon; a->cpr_even_lon = mm->cpr_lon;
a->cpr_even_nuc = mm->cpr_nucp; compute_nic_rc_from_message(mm, a, &a->cpr_even_nic, &a->cpr_even_rc);
} }
// CPR, odd // CPR, odd
@ -704,26 +977,52 @@ struct aircraft *trackUpdateFromMessage(struct modesMessage *mm)
a->cpr_odd_type = mm->cpr_type; a->cpr_odd_type = mm->cpr_type;
a->cpr_odd_lat = mm->cpr_lat; a->cpr_odd_lat = mm->cpr_lat;
a->cpr_odd_lon = mm->cpr_lon; a->cpr_odd_lon = mm->cpr_lon;
a->cpr_odd_nuc = mm->cpr_nucp; compute_nic_rc_from_message(mm, a, &a->cpr_odd_nic, &a->cpr_odd_rc);
} }
// operational status message if (mm->accuracy.sda_valid && accept_data(&a->sda_valid, mm->source)) {
if (mm->opstatus.valid) { a->sda = mm->accuracy.sda;
a->adsb_version = mm->opstatus.version;
if (mm->opstatus.version > 0) {
a->adsb_hrd = mm->opstatus.hrd;
a->adsb_tah = mm->opstatus.tah;
} }
if (mm->accuracy.nic_a_valid && accept_data(&a->nic_a_valid, mm->source)) {
a->nic_a = mm->accuracy.nic_a;
}
if (mm->accuracy.nic_c_valid && accept_data(&a->nic_c_valid, mm->source)) {
a->nic_c = mm->accuracy.nic_c;
}
if (mm->accuracy.nac_p_valid && accept_data(&a->nac_p_valid, mm->source)) {
a->nac_p = mm->accuracy.nac_p;
}
if (mm->accuracy.nac_v_valid && accept_data(&a->nac_v_valid, mm->source)) {
a->nac_v = mm->accuracy.nac_v;
}
if (mm->accuracy.sil_valid && accept_data(&a->sil_valid, mm->source)) {
a->sil = mm->accuracy.sil;
if (mm->accuracy.sil_type != SIL_INVALID) {
a->sil_type = mm->accuracy.sil_type;
}
}
if (mm->accuracy.gva_valid && accept_data(&a->gva_valid, mm->source)) {
a->gva = mm->accuracy.gva;
}
if (mm->accuracy.sda_valid && accept_data(&a->sda_valid, mm->source)) {
a->sda = mm->accuracy.sda;
} }
// Now handle derived data // Now handle derived data
// derive geometric altitude if we have baro + delta // derive geometric altitude if we have baro + delta
if (compare_validity(&a->altitude_valid, &a->altitude_geom_valid) > 0 && if (compare_validity(&a->altitude_baro_valid, &a->altitude_geom_valid) > 0 &&
compare_validity(&a->geom_delta_valid, &a->altitude_geom_valid) > 0) { compare_validity(&a->geom_delta_valid, &a->altitude_geom_valid) > 0) {
// Baro and delta are both more recent than geometric, derive geometric from baro + delta // Baro and delta are both more recent than geometric, derive geometric from baro + delta
a->altitude_geom = a->altitude + a->geom_delta; a->altitude_geom = a->altitude_baro + a->geom_delta;
combine_validity(&a->altitude_geom_valid, &a->altitude_valid, &a->geom_delta_valid); combine_validity(&a->altitude_geom_valid, &a->altitude_baro_valid, &a->geom_delta_valid);
} }
// If we've got a new cprlat or cprlon // If we've got a new cprlat or cprlon
@ -762,8 +1061,8 @@ static void trackMatchAC(uint64_t now)
} }
// match on Mode C (+/- 100ft) // match on Mode C (+/- 100ft)
if (trackDataValid(&a->altitude_valid)) { if (trackDataValid(&a->altitude_baro_valid)) {
int modeC = (a->altitude + 49) / 100; int modeC = (a->altitude_baro + 49) / 100;
unsigned modeA = modeCToModeA(modeC); unsigned modeA = modeCToModeA(modeC);
unsigned i = modeAToIndex(modeA); unsigned i = modeAToIndex(modeA);
@ -844,7 +1143,7 @@ static void trackRemoveStaleAircraft(uint64_t now)
#define EXPIRE(_f) do { if (a->_f##_valid.source != SOURCE_INVALID && now >= a->_f##_valid.expires) { a->_f##_valid.source = SOURCE_INVALID; } } while (0) #define EXPIRE(_f) do { if (a->_f##_valid.source != SOURCE_INVALID && now >= a->_f##_valid.expires) { a->_f##_valid.source = SOURCE_INVALID; } } while (0)
EXPIRE(callsign); EXPIRE(callsign);
EXPIRE(altitude); EXPIRE(altitude_baro);
EXPIRE(altitude_geom); EXPIRE(altitude_geom);
EXPIRE(geom_delta); EXPIRE(geom_delta);
EXPIRE(gs); EXPIRE(gs);
@ -859,15 +1158,21 @@ static void trackRemoveStaleAircraft(uint64_t now)
EXPIRE(baro_rate); EXPIRE(baro_rate);
EXPIRE(geom_rate); EXPIRE(geom_rate);
EXPIRE(squawk); EXPIRE(squawk);
EXPIRE(category);
EXPIRE(airground); EXPIRE(airground);
EXPIRE(alt_setting); EXPIRE(nav_qnh);
EXPIRE(intent_altitude); EXPIRE(nav_altitude);
EXPIRE(intent_heading); EXPIRE(nav_heading);
EXPIRE(intent_modes); EXPIRE(nav_modes);
EXPIRE(cpr_odd); EXPIRE(cpr_odd);
EXPIRE(cpr_even); EXPIRE(cpr_even);
EXPIRE(position); EXPIRE(position);
EXPIRE(nic_a);
EXPIRE(nic_c);
EXPIRE(nic_baro);
EXPIRE(nac_p);
EXPIRE(sil);
EXPIRE(gva);
EXPIRE(sda);
#undef EXPIRE #undef EXPIRE
prev = a; a = a->next; prev = a; a = a->next;
} }

78
track.h
View file

@ -64,6 +64,9 @@
*/ */
#define TRACK_MODEAC_MIN_MESSAGES 4 #define TRACK_MODEAC_MIN_MESSAGES 4
/* Special value for Rc unknown (100NM) */
#define RC_UNKNOWN 185200
// data moves through three states: // data moves through three states:
// fresh: data is valid. Updates from a less reliable source are not accepted. // fresh: data is valid. Updates from a less reliable source are not accepted.
// stale: data is valid. Updates from a less reliable source are accepted. // stale: data is valid. Updates from a less reliable source are accepted.
@ -92,8 +95,8 @@ struct aircraft {
data_validity callsign_valid; data_validity callsign_valid;
char callsign[9]; // Flight number char callsign[9]; // Flight number
data_validity altitude_valid; data_validity altitude_baro_valid;
int altitude; // Altitude (Baro) int altitude_baro; // Altitude (Baro)
data_validity altitude_geom_valid; data_validity altitude_geom_valid;
int altitude_geom; // Altitude (Geometric) int altitude_geom; // Altitude (Geometric)
@ -102,7 +105,7 @@ struct aircraft {
int geom_delta; // Difference between Geometric and Baro altitudes int geom_delta; // Difference between Geometric and Baro altitudes
data_validity gs_valid; data_validity gs_valid;
unsigned gs; float gs;
data_validity ias_valid; data_validity ias_valid;
unsigned ias; unsigned ias;
@ -137,49 +140,71 @@ struct aircraft {
data_validity squawk_valid; data_validity squawk_valid;
unsigned squawk; // Squawk unsigned squawk; // Squawk
data_validity category_valid;
unsigned category; // Aircraft category A0 - D7 encoded as a single hex byte unsigned category; // Aircraft category A0 - D7 encoded as a single hex byte
data_validity airground_valid; data_validity airground_valid;
airground_t airground; // air/ground status airground_t airground; // air/ground status
data_validity alt_setting_valid; data_validity nav_qnh_valid;
float alt_setting; // Altimeter setting (QNH/QFE), millibars float nav_qnh; // Altimeter setting (QNH/QFE), millibars
data_validity intent_altitude_valid; data_validity nav_altitude_valid;
unsigned intent_altitude; // intent altitude (FMS or FCU selected altitude) unsigned nav_altitude; // FMS or FCU selected altitude
data_validity intent_heading_valid; data_validity nav_heading_valid;
float intent_heading; // intent heading, degrees (0-359) float nav_heading; // target heading, degrees (0-359)
data_validity intent_modes_valid; data_validity nav_modes_valid;
intent_modes_t intent_modes; // enabled modes (autopilot, vnav, etc) nav_modes_t nav_modes; // enabled modes (autopilot, vnav, etc)
data_validity cpr_odd_valid; // Last seen even CPR message data_validity cpr_odd_valid; // Last seen even CPR message
cpr_type_t cpr_odd_type; cpr_type_t cpr_odd_type;
unsigned cpr_odd_lat; unsigned cpr_odd_lat;
unsigned cpr_odd_lon; unsigned cpr_odd_lon;
unsigned cpr_odd_nuc; unsigned cpr_odd_nic;
unsigned cpr_odd_rc;
data_validity cpr_even_valid; // Last seen odd CPR message data_validity cpr_even_valid; // Last seen odd CPR message
cpr_type_t cpr_even_type; cpr_type_t cpr_even_type;
unsigned cpr_even_lat; unsigned cpr_even_lat;
unsigned cpr_even_lon; unsigned cpr_even_lon;
unsigned cpr_even_nuc; unsigned cpr_even_nic;
unsigned cpr_even_rc;
data_validity position_valid; data_validity position_valid;
double lat, lon; // Coordinated obtained from CPR encoded data double lat, lon; // Coordinated obtained from CPR encoded data
unsigned pos_nuc; // NUCp of last computed position unsigned pos_nic; // NIC of last computed position
unsigned pos_rc; // Rc of last computed position
// data extracted from opstatus etc
int adsb_version; // ADS-B version (from ADS-B operational status); -1 means no ADS-B messages seen int adsb_version; // ADS-B version (from ADS-B operational status); -1 means no ADS-B messages seen
heading_type_t adsb_hrd; // Heading Reference Direction setting (from ADS-B operational status) heading_type_t adsb_hrd; // Heading Reference Direction setting (from ADS-B operational status)
heading_type_t adsb_tah; // Track Angle / Heading setting (from ADS-B operational status) heading_type_t adsb_tah; // Track Angle / Heading setting (from ADS-B operational status)
data_validity nic_a_valid;
data_validity nic_c_valid;
data_validity nic_baro_valid;
data_validity nac_p_valid;
data_validity nac_v_valid;
data_validity sil_valid;
data_validity gva_valid;
data_validity sda_valid;
unsigned nic_a : 1; // NIC supplement A from opstatus
unsigned nic_c : 1; // NIC supplement C from opstatus
unsigned nic_baro : 1; // NIC baro supplement from TSS or opstatus
unsigned nac_p : 4; // NACp from TSS or opstatus
unsigned nac_v : 3; // NACv from opstatus
unsigned sil : 2; // SIL from TS or opstatus
sil_type_t sil_type; // SIL supplement from TS or opstatus
unsigned gva : 2; // GVA from opstatus
unsigned sda : 2; // SDA from opstatus
int modeA_hit; // did our squawk match a possible mode A reply in the last check period? int modeA_hit; // did our squawk match a possible mode A reply in the last check period?
int modeC_hit; // did our altitude match a possible mode C reply in the last check period? int modeC_hit; // did our altitude match a possible mode C reply in the last check period?
int fatsv_emitted_altitude; // last FA emitted altitude int fatsv_emitted_altitude_baro; // last FA emitted altitude
int fatsv_emitted_altitude_gnss; // -"- GNSS altitude int fatsv_emitted_altitude_geom; // -"- GNSS altitude
int fatsv_emitted_baro_rate; // -"- barometric rate int fatsv_emitted_baro_rate; // -"- barometric rate
int fatsv_emitted_geom_rate; // -"- geometric rate int fatsv_emitted_geom_rate; // -"- geometric rate
float fatsv_emitted_track; // -"- true track float fatsv_emitted_track; // -"- true track
@ -187,22 +212,27 @@ struct aircraft {
float fatsv_emitted_mag_heading; // -"- magnetic heading float fatsv_emitted_mag_heading; // -"- magnetic heading
float fatsv_emitted_true_heading; // -"- true heading float fatsv_emitted_true_heading; // -"- true heading
float fatsv_emitted_roll; // -"- roll angle float fatsv_emitted_roll; // -"- roll angle
unsigned fatsv_emitted_speed; // -"- groundspeed float fatsv_emitted_gs; // -"- groundspeed
unsigned fatsv_emitted_speed_ias; // -"- IAS unsigned fatsv_emitted_ias; // -"- IAS
unsigned fatsv_emitted_speed_tas; // -"- TAS unsigned fatsv_emitted_tas; // -"- TAS
float fatsv_emitted_mach; // -"- Mach number float fatsv_emitted_mach; // -"- Mach number
airground_t fatsv_emitted_airground; // -"- air/ground state airground_t fatsv_emitted_airground; // -"- air/ground state
unsigned fatsv_emitted_intent_altitude; // -"- intent altitude unsigned fatsv_emitted_nav_altitude; // -"- target altitude
float fatsv_emitted_intent_heading; // -"- intent heading float fatsv_emitted_nav_heading; // -"- target heading
intent_modes_t fatsv_emitted_intent_modes; // -"- enabled modes nav_modes_t fatsv_emitted_nav_modes; // -"- enabled navigation modes
float fatsv_emitted_alt_setting; // -"- altimeter setting float fatsv_emitted_nav_qnh; // -"- altimeter setting
unsigned char fatsv_emitted_bds_10[7]; // -"- BDS 1,0 message unsigned char fatsv_emitted_bds_10[7]; // -"- BDS 1,0 message
unsigned char fatsv_emitted_bds_30[7]; // -"- BDS 3,0 message unsigned char fatsv_emitted_bds_30[7]; // -"- BDS 3,0 message
unsigned char fatsv_emitted_es_status[7]; // -"- ES operational status message unsigned char fatsv_emitted_es_status[7]; // -"- ES operational status message
unsigned char fatsv_emitted_es_acas_ra[7]; // -"- ES ACAS RA report message unsigned char fatsv_emitted_es_acas_ra[7]; // -"- ES ACAS RA report message
char fatsv_emitted_callsign[9]; // -"- callsign char fatsv_emitted_callsign[9]; // -"- callsign
addrtype_t fatsv_emitted_addrtype; // -"- address type (assumed ADSB_ICAO initially)
int fatsv_emitted_adsb_version; // -"- ADS-B version (assumed non-ADS-B initially)
unsigned fatsv_emitted_category; // -"- ADS-B emitter category (assumed A0 initially)
unsigned fatsv_emitted_squawk; // -"- squawk
uint64_t fatsv_last_emitted; // time (millis) aircraft was last FA emitted uint64_t fatsv_last_emitted; // time (millis) aircraft was last FA emitted
uint64_t fatsv_last_force_emit; // time (millis) we last emitted only-on-change data
struct aircraft *next; // Next aircraft in our linked list struct aircraft *next; // Next aircraft in our linked list