diff --git a/comm_b.c b/comm_b.c index 9ac9676..519bf84 100644 --- a/comm_b.c +++ b/comm_b.c @@ -374,51 +374,49 @@ static int decodeBDS40(struct modesMessage *mm, bool store) if (store) { mm->commb_format = COMMB_VERTICAL_INTENT; - mm->intent.valid = 1; - if (mcp_valid) { - mm->intent.mcp_altitude_valid = 1; - mm->intent.mcp_altitude = mcp_alt; + mm->nav.mcp_altitude_valid = 1; + mm->nav.mcp_altitude = mcp_alt; } if (fms_valid) { - mm->intent.fms_altitude_valid = 1; - mm->intent.fms_altitude = fms_alt; + mm->nav.fms_altitude_valid = 1; + mm->nav.fms_altitude = fms_alt; } if (baro_valid) { - mm->intent.alt_setting_valid = 1; - mm->intent.alt_setting = baro_setting; + mm->nav.qnh_valid = 1; + mm->nav.qnh = baro_setting; } if (mode_valid) { - mm->intent.modes_valid = 1; - mm->intent.modes = - ((mode_raw & 4) ? INTENT_MODE_VNAV : 0) | - ((mode_raw & 2) ? INTENT_MODE_ALT_HOLD : 0) | - ((mode_raw & 1) ? INTENT_MODE_APPROACH : 0); + mm->nav.modes_valid = 1; + mm->nav.modes = + ((mode_raw & 4) ? NAV_MODE_VNAV : 0) | + ((mode_raw & 2) ? NAV_MODE_ALT_HOLD : 0) | + ((mode_raw & 1) ? NAV_MODE_APPROACH : 0); } if (source_valid) { switch (source_raw) { case 0: - mm->intent.altitude_source = INTENT_ALT_UNKNOWN; + mm->nav.altitude_source = NAV_ALT_UNKNOWN; break; case 1: - mm->intent.altitude_source = INTENT_ALT_AIRCRAFT; + mm->nav.altitude_source = NAV_ALT_AIRCRAFT; break; case 2: - mm->intent.altitude_source = INTENT_ALT_MCP; + mm->nav.altitude_source = NAV_ALT_MCP; break; case 3: - mm->intent.altitude_source = INTENT_ALT_FMS; + mm->nav.altitude_source = NAV_ALT_FMS; break; default: - mm->intent.altitude_source = INTENT_ALT_INVALID; + mm->nav.altitude_source = NAV_ALT_INVALID; break; } } 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) { mm->gs_valid = 1; - mm->gs = gs; + mm->gs.v0 = mm->gs.v2 = gs; } if (track_rate_valid) { diff --git a/dump1090.h b/dump1090.h index fe95ec2..55bcedd 100644 --- a/dump1090.h +++ b/dump1090.h @@ -178,7 +178,7 @@ typedef enum { } airground_t; typedef enum { - SIL_PER_SAMPLE, SIL_PER_HOUR + SIL_INVALID, SIL_PER_SAMPLE, SIL_PER_HOUR } sil_type_t; typedef enum { @@ -186,11 +186,12 @@ typedef enum { } cpr_type_t; typedef enum { + HEADING_INVALID, // Not set HEADING_GROUND_TRACK, // Direction of track over ground, degrees clockwise from true north HEADING_TRUE, // Heading, degrees clockwise from true 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_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; typedef enum { @@ -206,12 +207,13 @@ typedef enum { } commb_format_t; typedef enum { - INTENT_MODE_AUTOPILOT = 1, - INTENT_MODE_VNAV = 2, - INTENT_MODE_ALT_HOLD = 4, - INTENT_MODE_APPROACH = 8, - INTENT_MODE_LNAV = 16 -} intent_modes_t; + NAV_MODE_AUTOPILOT = 1, + NAV_MODE_VNAV = 2, + NAV_MODE_ALT_HOLD = 4, + NAV_MODE_APPROACH = 8, + NAV_MODE_LNAV = 16, + NAV_MODE_TCAS = 32 +} nav_modes_t; #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]; // Decoded data - unsigned altitude_valid : 1; + unsigned altitude_baro_valid : 1; + unsigned altitude_geom_valid : 1; unsigned track_valid : 1; unsigned track_rate_valid : 1; unsigned heading_valid : 1; @@ -453,10 +456,13 @@ struct modesMessage { commb_format_t commb_format; // Inferred format of a comm-b message - // valid if altitude_valid: - int altitude; // Altitude in either feet or meters - altitude_unit_t altitude_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_baro_valid: + int altitude_baro; // Altitude in either feet or meters + altitude_unit_t altitude_baro_unit; // the unit used for altitude + + // 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: 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' float track_rate; // Rate of change of track, degrees/second 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 tas; // True airspeed, kts double mach; // Mach number @@ -485,6 +498,36 @@ struct modesMessage { // valid if cpr_decoded: double decoded_lat; 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 struct { @@ -495,7 +538,6 @@ struct modesMessage { unsigned om_ident : 1; unsigned om_atc : 1; unsigned om_saf : 1; - unsigned om_sda : 2; unsigned cc_acas : 1; unsigned cc_cdti : 1; @@ -506,17 +548,8 @@ struct modesMessage { unsigned cc_uat_in : 1; unsigned cc_poa : 1; unsigned cc_b2_low : 1; - unsigned cc_nac_v : 3; - unsigned cc_nic_supp_c : 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 hrd; @@ -528,23 +561,22 @@ struct modesMessage { // Target State & Status (ADS-B V2 only) // Comm-B BDS4,0 Vertical Intent struct { - unsigned valid : 1; - unsigned heading_valid : 1; unsigned fms_altitude_valid : 1; unsigned mcp_altitude_valid : 1; - unsigned alt_setting_valid : 1; + unsigned qnh_valid : 1; unsigned modes_valid : 1; 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 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; - } intent; + nav_modes_t modes; + } nav; }; // This one needs modesMessage: diff --git a/interactive.c b/interactive.c index 66fdfb9..580a5d8 100644 --- a/interactive.c +++ b/interactive.c @@ -162,8 +162,8 @@ void interactiveShowData(void) { snprintf(strFl, 7," grnd"); } else if (Modes.use_gnss && trackDataValid(&a->altitude_geom_valid)) { snprintf(strFl, 7, "%5dH", convert_altitude(a->altitude_geom)); - } else if (trackDataValid(&a->altitude_valid)) { - snprintf(strFl, 7, "%5d ", convert_altitude(a->altitude)); + } else if (trackDataValid(&a->altitude_baro_valid)) { + 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", diff --git a/mode_ac.c b/mode_ac.c index 9af1243..194ff67 100644 --- a/mode_ac.c +++ b/mode_ac.c @@ -146,10 +146,9 @@ void decodeModeAMessage(struct modesMessage *mm, int ModeA) if (!mm->spi) { int modeC = modeAToModeC(ModeA); if (modeC != INVALID_ALTITUDE) { - mm->altitude = modeC * 100; - mm->altitude_unit = UNIT_FEET; - mm->altitude_source = ALTITUDE_BARO; - mm->altitude_valid = 1; + mm->altitude_baro = modeC * 100; + mm->altitude_baro_unit = UNIT_FEET; + mm->altitude_baro_valid = 1; } } diff --git a/mode_s.c b/mode_s.c index 2f13af4..3f30c59 100644 --- a/mode_s.c +++ b/mode_s.c @@ -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) { - int gspeed; - +static float decodeMovementFieldV2(unsigned movement) { // Note : movement codes 0,125,126,127 are all invalid, but they are // trapped for before this function is called. - if (movement > 123) gspeed = 199; // > 175kt - else if (movement > 108) gspeed = ((movement - 108) * 5) + 100; - else if (movement > 93) gspeed = ((movement - 93) * 2) + 70; - else if (movement > 38) gspeed = ((movement - 38) ) + 15; - else if (movement > 12) gspeed = ((movement - 11) >> 1) + 2; - else if (movement > 8) gspeed = ((movement - 6) >> 2) + 1; - else gspeed = 0; + // Each movement value is a range of speeds; + // we return the midpoint of the range (rounded to the nearest integer) + 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 >= 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 @@ -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) { mm->AC = getbits(msg, 20, 32); if (mm->AC) { // Only attempt to decode if a valid (non zero) altitude is present - mm->altitude = decodeAC13Field(mm->AC, &mm->altitude_unit); - if (mm->altitude != INVALID_ALTITUDE) - mm->altitude_valid = 1; - mm->altitude_source = ALTITUDE_BARO; + mm->altitude_baro = decodeAC13Field(mm->AC, &mm->altitude_baro_unit); + if (mm->altitude_baro != INVALID_ALTITUDE) + mm->altitude_baro_valid = 1; } } @@ -729,30 +755,31 @@ static void decodeESAirborneVelocity(struct modesMessage *mm, int check_imf) // Airborne Velocity Message unsigned char *me = mm->ME; + // 1-5: ME type + // 6-8: ME subtype mm->mesub = getbits(me, 6, 8); - if (check_imf && getbit(me, 9)) - setIMF(mm); - if (mm->mesub < 1 || mm->mesub > 4) return; - 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; - } - } + // 9: IMF or Intent Change + if (check_imf && getbit(me, 9)) + setIMF(mm); + // 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) { 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 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); // 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; - if (mm->gs) { + if (mm->gs.selected > 0) { float ground_track = atan2(ew_vel, ns_vel) * 180.0 / M_PI; // We don't want negative values but a 0-360 scale if (ground_track < 0) @@ -779,6 +806,16 @@ static void decodeESAirborneVelocity(struct modesMessage *mm, int check_imf) 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); if (airspeed) { 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; } } + // 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); if (raw_delta) { mm->geom_delta_valid = 1; @@ -812,28 +864,37 @@ static void decodeESSurfacePosition(struct modesMessage *mm, int check_imf) // Surface position and movement unsigned char *me = mm->ME; - if (check_imf && getbit(me, 21)) - setIMF(mm); - 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_type = CPR_SURFACE; + // 6-12: Movement unsigned movement = getbits(me, 6, 12); if (movement > 0 && movement < 125) { 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)) { mm->heading_valid = 1; mm->heading = getbits(me, 14, 20) * 360.0 / 128.0; 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) @@ -841,20 +902,34 @@ static void decodeESAirbornePosition(struct modesMessage *mm, int check_imf) // Airborne position and altitude unsigned char *me = mm->ME; - if (check_imf && getbit(me, 8)) - setIMF(mm); + // 6-7: surveillance status + // 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); if (mm->metype == 0) { - mm->cpr_nucp = 0; + // no position information } else { - // Catch some common failure modes and don't mark them as valid - // (so they won't be used for positioning) + // 21: T flag (UTC sync or not) + // 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_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) { // Seen from at least: // 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_type = CPR_AIRBORNE; 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 - mm->altitude = decodeAC12Field(AC12Field, &mm->altitude_unit); - if (mm->altitude != INVALID_ALTITUDE) { - mm->altitude_valid = 1; + altitude_unit_t unit; + int alt = decodeAC12Field(AC12Field, &unit); + 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)) setIMF(mm); - if (mm->mesub == 0) { // Target state and status, V1 - // TODO: need RTCA/DO-260A - } else if (mm->mesub == 1) { // Target state and status, V2 - mm->intent.valid = 1; + if (mm->mesub == 0 && getbit(me, 11) == 0) { // Target state and status, V1 + // 8-9: vertical source + switch (getbits(me, 8, 9)) { + 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 unsigned is_fms = getbit(me, 9); unsigned alt_bits = getbits(me, 10, 20); if (alt_bits != 0) { if (is_fms) { - mm->intent.fms_altitude_valid = 1; - mm->intent.fms_altitude = (alt_bits - 1) * 32; + mm->nav.fms_altitude_valid = 1; + mm->nav.fms_altitude = (alt_bits - 1) * 32; } else { - mm->intent.mcp_altitude_valid = 1; - mm->intent.mcp_altitude = (alt_bits - 1) * 32; + mm->nav.mcp_altitude_valid = 1; + mm->nav.mcp_altitude = (alt_bits - 1) * 32; } } unsigned baro_bits = getbits(me, 21, 29); if (baro_bits != 0) { - mm->intent.alt_setting_valid = 1; - mm->intent.alt_setting = 800.0 + (baro_bits - 1) * 0.8; + mm->nav.qnh_valid = 1; + mm->nav.qnh = 800.0 + (baro_bits - 1) * 0.8; } if (getbit(me, 30)) { - mm->intent.heading_valid = 1; + mm->nav.heading_valid = 1; // two's complement -180..+180, which is conveniently // 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 - // 44: NICbaro - // 45-46: SIL + 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: mode bits validity if (getbit(me, 47)) { - mm->intent.modes_valid = 1; - mm->intent.modes = - (getbit(me, 48) ? INTENT_MODE_AUTOPILOT : 0) | - (getbit(me, 49) ? INTENT_MODE_VNAV : 0) | - (getbit(me, 50) ? INTENT_MODE_ALT_HOLD : 0) | + // 48-54: mode bits + mm->nav.modes_valid = 1; + mm->nav.modes = + (getbit(me, 48) ? NAV_MODE_AUTOPILOT : 0) | + (getbit(me, 49) ? NAV_MODE_VNAV : 0) | + (getbit(me, 50) ? NAV_MODE_ALT_HOLD : 0) | // 51: IMF - (getbit(me, 52) ? INTENT_MODE_APPROACH : 0) | - // 53: TCAS operational - (getbit(me, 54) ? INTENT_MODE_LNAV : 0); + (getbit(me, 52) ? NAV_MODE_APPROACH : 0) | + (getbit(me, 53) ? NAV_MODE_TCAS : 0) | + (getbit(me, 54) ? NAV_MODE_LNAV : 0); } // 55-56 reserved @@ -999,6 +1208,10 @@ static void decodeESOperationalStatus(struct modesMessage *mm, int check_imf) switch (mm->opstatus.version) { 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; 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.nic_supp_a = getbit(me, 44); - mm->opstatus.nac_p = getbits(me, 45, 48); - mm->opstatus.sil = getbits(me, 51, 52); + mm->accuracy.nic_a_valid = 1; + mm->accuracy.nic_a = getbit(me, 44); + 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; + 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 { mm->opstatus.tah = getbit(me, 53) ? HEADING_GROUND_TRACK : mm->opstatus.hrd; } break; case 2: - default: if (getbits(me, 25, 26) == 0) { mm->opstatus.om_acas_ra = getbit(me, 27); mm->opstatus.om_ident = getbit(me, 28); mm->opstatus.om_atc = getbit(me, 29); 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 - 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_arv = getbit(me, 15); mm->opstatus.cc_ts = getbit(me, 16); mm->opstatus.cc_tc = getbits(me, 17, 18); 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 mm->opstatus.cc_poa = getbit(me, 11); mm->opstatus.cc_1090_in = getbit(me, 12); mm->opstatus.cc_b2_low = getbit(me, 15); mm->opstatus.cc_uat_in = getbit(me, 16); - mm->opstatus.cc_nac_v = getbits(me, 17, 19); - mm->opstatus.cc_nic_supp_c = getbit(me, 20); + mm->accuracy.nac_v_valid = 1; + 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 = getbits(me, 21, 24); mm->opstatus.cc_antenna_offset = getbits(me, 33, 40); } - mm->opstatus.nic_supp_a = getbit(me, 44); - mm->opstatus.nac_p = getbits(me, 45, 48); - mm->opstatus.sil = getbits(me, 51, 52); + mm->accuracy.nic_a_valid = 1; + mm->accuracy.nic_a = getbit(me, 44); + 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; if (mm->mesub == 0) { - mm->opstatus.gva = getbits(me, 49, 50); - mm->opstatus.nic_baro = getbit(me, 53); + mm->accuracy.gva_valid = 1; + mm->accuracy.gva = getbits(me, 49, 50); + mm->accuracy.nic_baro_valid = 1; + mm->accuracy.nic_baro = getbit(me, 53); } else { 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; } } @@ -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) { switch (airground) { 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]; buf[0] = 0; - if (flags & INTENT_MODE_AUTOPILOT) + if (flags & NAV_MODE_AUTOPILOT) strcat(buf, "autopilot "); - if (flags & INTENT_MODE_VNAV) + if (flags & NAV_MODE_VNAV) strcat(buf, "vnav "); - if (flags & INTENT_MODE_ALT_HOLD) + if (flags & NAV_MODE_ALT_HOLD) strcat(buf, "althold "); - if (flags & INTENT_MODE_APPROACH) + if (flags & NAV_MODE_APPROACH) strcat(buf, "approach "); - if (flags & INTENT_MODE_LNAV) + if (flags & NAV_MODE_LNAV) strcat(buf, "lnav "); + if (flags & NAV_MODE_TCAS) + strcat(buf, "tcas "); if (buf[0] != 0) buf[strlen(buf)-1] = 0; @@ -1617,11 +1834,16 @@ void displayModesMessage(struct modesMessage *mm) { airground_to_string(mm->airground)); } - if (mm->altitude_valid) { - printf(" Altitude: %d %s %s\n", - mm->altitude, - altitude_unit_to_string(mm->altitude_unit), - altitude_source_to_string(mm->altitude_source)); + if (mm->altitude_baro_valid) { + printf(" Baro altitude: %d %s\n", + mm->altitude_baro, + altitude_unit_to_string(mm->altitude_baro_unit)); + } + + 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) { @@ -1642,7 +1864,14 @@ void displayModesMessage(struct modesMessage *mm) { } 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) { @@ -1682,21 +1911,24 @@ void displayModesMessage(struct modesMessage *mm) { if (mm->cpr_valid) { printf(" CPR type: %s\n" - " CPR odd flag: %s\n" - " CPR NUCp/NIC: %u\n", + " CPR odd flag: %s\n", cpr_type_to_string(mm->cpr_type), - mm->cpr_odd ? "odd" : "even", - mm->cpr_nucp); + mm->cpr_odd ? "odd" : "even"); if (mm->cpr_decoded) { printf(" CPR latitude: %.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->cpr_lat, mm->decoded_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 { printf(" CPR latitude: (%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) { printf(" Aircraft Operational Status:\n"); 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_poa) printf("POA "); 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_antenna_offset) printf("GPS-OFFSET=%d ", mm->opstatus.cc_antenna_offset); printf("\n"); @@ -1731,53 +1991,43 @@ void displayModesMessage(struct modesMessage *mm) { if (mm->opstatus.om_ident) printf("IDENT "); if (mm->opstatus.om_atc) printf("ATC "); if (mm->opstatus.om_saf) printf("SAF "); - if (mm->opstatus.om_sda) printf("SDA=%d ", mm->opstatus.om_sda); 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) 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)); } - if (mm->intent.valid) { - printf(" Intent:\n"); - if (mm->intent.heading_valid) - printf(" Selected heading: %.1f\n", mm->intent.heading); - if (mm->intent.fms_altitude_valid) - printf(" FMS selected altitude: %u ft\n", mm->intent.fms_altitude); - if (mm->intent.mcp_altitude_valid) - printf(" MCP selected altitude: %u ft\n", mm->intent.mcp_altitude); - if (mm->intent.alt_setting_valid) - printf(" Altimeter setting: %.1f millibars\n", mm->intent.alt_setting); - - if (mm->intent.altitude_source != INTENT_ALT_INVALID) { - printf(" Target altitude source: "); - switch (mm->intent.altitude_source) { - case INTENT_ALT_AIRCRAFT: - printf("aircraft altitude\n"); - break; - case INTENT_ALT_MCP: - printf("MCP selected altitude\n"); - break; - case INTENT_ALT_FMS: - printf("FMS selected altitude\n"); - break; - default: - printf("unknown\n"); - } - } - - if (mm->intent.modes_valid) { - printf(" Active modes: %s\n", intent_modes_to_string(mm->intent.modes)); + if (mm->nav.heading_valid) + printf(" Selected heading: %.1f\n", mm->nav.heading); + if (mm->nav.fms_altitude_valid) + printf(" FMS selected altitude: %u ft\n", mm->nav.fms_altitude); + if (mm->nav.mcp_altitude_valid) + printf(" MCP selected altitude: %u ft\n", mm->nav.mcp_altitude); + if (mm->nav.qnh_valid) + printf(" QNH: %.1f millibars\n", mm->nav.qnh); + if (mm->nav.altitude_source != NAV_ALT_INVALID) { + printf(" Target altitude source: "); + switch (mm->nav.altitude_source) { + case NAV_ALT_AIRCRAFT: + printf("aircraft altitude\n"); + break; + case NAV_ALT_MCP: + printf("MCP selected altitude\n"); + break; + case NAV_ALT_FMS: + printf("FMS selected altitude\n"); + break; + default: + printf("unknown\n"); } } + if (mm->nav.modes_valid) { + printf(" Nav modes: %s\n", nav_modes_to_string(mm->nav.modes)); + } + + printf("\n"); fflush(stdout); } diff --git a/net_io.c b/net_io.c index 61d2f61..1ba7635 100644 --- a/net_io.c +++ b/net_io.c @@ -582,31 +582,29 @@ static void modesSendSBSOutput(struct modesMessage *mm, struct aircraft *a) { else {p += sprintf(p, ",");} // Field 12 is the altitude (if we have it) - if (mm->altitude_valid) { - if (Modes.use_gnss) { - if (mm->altitude_source == ALTITUDE_GEOM) { - p += sprintf(p, ",%dH", mm->altitude); - } else if (trackDataValid(&a->geom_delta_valid)) { - p += sprintf(p, ",%dH", mm->altitude + a->geom_delta); - } else { - p += sprintf(p, ",%d", mm->altitude); - } + if (Modes.use_gnss) { + if (mm->altitude_geom_valid) { + p += sprintf(p, ",%dH", mm->altitude_geom); + } else if (mm->altitude_baro_valid && trackDataValid(&a->geom_delta_valid)) { + p += sprintf(p, ",%dH", mm->altitude_baro + a->geom_delta); + } else if (mm->altitude_baro_valid) { + 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 { - p += sprintf(p, ","); - } + p += sprintf(p, ","); } } else { - p += sprintf(p, ","); + 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 { + p += sprintf(p, ","); + } } // Field 13 is the ground Speed (if we have it) if (mm->gs_valid) { - p += sprintf(p, ",%u", mm->gs); + p += sprintf(p, ",%.0f", mm->gs.selected); } else { 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\","); if (a->position_valid.source == source) 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\","); + if (a->altitude_geom_valid.source == source) + p += snprintf(p, end-p, "\"alt_geom\","); if (a->track_valid.source == source) p += snprintf(p, end-p, "\"track\","); 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\","); if (a->geom_rate_valid.source == source) p += snprintf(p, end-p, "\"geom_rate\","); - if (a->category_valid.source == source) - p += snprintf(p, end-p, "\"category\","); if (p[-1] != '[') --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 { - intent_modes_t flag; + nav_modes_t flag; const char *name; -} intent_modes_names[] = { - { INTENT_MODE_AUTOPILOT, "autopilot" }, - { INTENT_MODE_VNAV, "vnav" }, - { INTENT_MODE_ALT_HOLD, "althold" }, - { INTENT_MODE_APPROACH, "approach" }, - { INTENT_MODE_LNAV, "lnav" }, +} nav_modes_names[] = { + { NAV_MODE_AUTOPILOT, "autopilot" }, + { NAV_MODE_VNAV, "vnav" }, + { NAV_MODE_ALT_HOLD, "althold" }, + { NAV_MODE_APPROACH, "approach" }, + { NAV_MODE_LNAV, "lnav" }, + { NAV_MODE_TCAS, "tcas" }, { 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; - for (int i = 0; intent_modes_names[i].name; ++i) { - if (!(flags & intent_modes_names[i].flag)) { + for (int i = 0; nav_modes_names[i].name; ++i) { + if (!(flags & nav_modes_names[i].flag)) { continue; } @@ -1162,16 +1161,16 @@ static char *append_intent_modes(char *p, char *end, intent_modes_t flags, const } 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; } -static const char *intent_modes_string(intent_modes_t flags) { +static const char *nav_modes_string(nav_modes_t flags) { static char buf[256]; buf[0] = 0; - append_intent_modes(buf, buf + sizeof(buf), flags, "", " "); + append_nav_modes(buf, buf + sizeof(buf), flags, "", " "); return buf; } @@ -1234,12 +1233,12 @@ char *generateAircraftJson(const char *url_path, int *len) { if (trackDataValid(&a->callsign_valid)) p += snprintf(p, end-p, ",\"flight\":\"%s\"", jsonEscapeString(a->callsign)); 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) p += snprintf(p, end-p, ",\"altitude\":\"ground\""); else { - if (trackDataValid(&a->altitude_valid)) - p += snprintf(p, end-p, ",\"altitude\":%d", a->altitude); + if (trackDataValid(&a->altitude_baro_valid)) + p += snprintf(p, end-p, ",\"altitude\":%d", a->altitude_baro); if (trackDataValid(&a->altitude_geom_valid)) 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)) p += snprintf(p, end-p, ",\"true_heading\":%.1f", a->true_heading); 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)) p += snprintf(p, end-p, ",\"ias\":%u", a->ias); 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); if (trackDataValid(&a->roll_valid)) 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); - if (trackDataValid(&a->intent_altitude_valid)) - p += snprintf(p, end-p, ",\"intent_alt\":%d", a->intent_altitude); - if (trackDataValid(&a->intent_heading_valid)) - p += snprintf(p, end-p, ",\"intent_heading\":%.1f", a->intent_heading); - if (trackDataValid(&a->intent_modes_valid)) { - p += snprintf(p, end-p, ",\"intent_modes\":["); - p = append_intent_modes(p, end, a->intent_modes, "\"", ","); + if (trackDataValid(&a->nav_altitude_valid)) + p += snprintf(p, end-p, ",\"nav_alt\":%d", a->nav_altitude); + if (trackDataValid(&a->nav_heading_valid)) + p += snprintf(p, end-p, ",\"nav_heading\":%.1f", a->nav_heading); + if (trackDataValid(&a->nav_modes_valid)) { + p += snprintf(p, end-p, ",\"nav_modes\":["); + p = append_nav_modes(p, end, a->nav_modes, "\"", ","); p += snprintf(p, end-p, "]"); } - if (trackDataValid(&a->alt_setting_valid)) - p += snprintf(p, end-p, ",\"alt_setting\":%.1f", a->alt_setting); + if (trackDataValid(&a->nav_qnh_valid)) + p += snprintf(p, end-p, ",\"nav_qnh\":%.1f", a->nav_qnh); p += snprintf(p, end-p, ",\"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); 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_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_start(ap, format); @@ -1913,7 +1912,7 @@ static inline float heading_difference(float h1, float h2) 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; switch (source->source) { @@ -1966,15 +1965,51 @@ static char *appendFATSVMeta(char *p, char *end, const char *field, struct aircr 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() { struct aircraft *a; static uint64_t next_update; + static int first_run = 1; if (!Modes.fatsv_out.service || !Modes.fatsv_out.service->connections) { return; // not enabled or no active connections } + if (first_run) { + writeFATSVBanner(); + first_run = 0; + } + uint64_t now = mstime(); if (now < next_update) { return; @@ -1995,9 +2030,10 @@ static void writeFATSV() } // 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 gsValid = trackDataValid(&a->gs_valid); + int squawkValid = trackDataValid(&a->squawk_valid); int callsignValid = trackDataValid(&a->callsign_valid) && strcmp(a->callsign, " ") != 0; 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 // 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". - 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; // if it hasn't changed altitude, heading, or speed much, // don't update so often int changed = - (altValid && abs(a->altitude - a->fatsv_emitted_altitude) >= 50) || - (trackDataValid(&a->altitude_geom_valid) && abs(a->altitude_geom - a->fatsv_emitted_altitude_gnss) >= 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_geom) >= 50) || (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->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->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) || - (gsValid && unsigned_difference(a->gs, a->fatsv_emitted_speed) >= 25) || - (trackDataValid(&a->ias_valid) && unsigned_difference(a->ias, a->fatsv_emitted_speed_ias) >= 25) || - (trackDataValid(&a->tas_valid) && unsigned_difference(a->tas, a->fatsv_emitted_speed_tas) >= 25) || + (gsValid && fabs(a->gs - a->fatsv_emitted_gs) >= 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_tas) >= 25) || (trackDataValid(&a->mach_valid) && fabs(a->mach - a->fatsv_emitted_mach) >= 0.02); int immediate = - (trackDataValid(&a->intent_altitude_valid) && unsigned_difference(a->intent_altitude, a->fatsv_emitted_intent_altitude) > 50) || - (trackDataValid(&a->intent_heading_valid) && heading_difference(a->intent_heading, a->fatsv_emitted_intent_heading) > 2) || - (trackDataValid(&a->intent_modes_valid) && a->intent_modes != a->fatsv_emitted_intent_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_altitude_valid) && unsigned_difference(a->nav_altitude, a->fatsv_emitted_nav_altitude) > 50) || + (trackDataValid(&a->nav_heading_valid) && heading_difference(a->nav_heading, a->fatsv_emitted_nav_heading) > 2) || + (trackDataValid(&a->nav_modes_valid) && a->nav_modes != a->fatsv_emitted_nav_modes) || + (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) || (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; if (immediate) { @@ -2042,11 +2079,11 @@ static void writeFATSV() // don't send mode S very often minAge = 30000; } else if ((airgroundValid && a->airground == AG_GROUND) || - (altValid && a->altitude < 500 && (!gsValid || a->gs < 200)) || - (gsValid && a->gs < 100 && (!altValid || a->altitude < 1000))) { + (altValid && a->altitude_baro < 500 && (!gsValid || a->gs < 200)) || + (gsValid && a->gs < 100 && (!altValid || a->altitude_baro < 1000))) { // we are probably on the ground, increase the update rate 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 minAge = (changed ? 5000 : 10000); } else { @@ -2065,13 +2102,20 @@ static void writeFATSV() p = appendFATSV(p, end, "clock", "%" PRIu64, messageNow() / 1000); 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)); } - - if (a->adsb_version >= 0) { + if (forceEmit || a->adsb_version != a->fatsv_emitted_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 // and are not stale @@ -2079,34 +2123,35 @@ static void writeFATSV() char *dataStart = p; // special cases - if (altValid) - p = appendFATSVMeta(p, end, "alt", a, &a->altitude_valid, "%d", a->altitude); if (airgroundValid) - p = appendFATSVMeta(p, end, "ag", a, &a->airground_valid, "%s", a->airground == AG_GROUND ? "G+" : "A+"); - if (strcmp(a->callsign, " ") != 0) + p = appendFATSVMeta(p, end, "airGround", a, &a->airground_valid, "%s", airground_string(a->airground)); + if (squawkValid) + p = appendFATSVMeta(p, end, "squawk", a, &a->squawk_valid, "%04x", a->squawk); + if (callsignValid) 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); + 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, "squawk", a, &a->squawk_valid, "%04x", a->squawk); - p = appendFATSVMeta(p, end, "altGeo", 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, "vrateGeo", 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, "ias", a, &a->ias_valid, "%u", a->ias); - p = appendFATSVMeta(p, end, "tas", a, &a->tas_valid, "%u", a->tas); - 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, "trkRate", 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, "hdgMag", a, &a->mag_heading_valid, "%.0f", a->mag_heading); - p = appendFATSVMeta(p, end, "hdgTrue", a, &a->true_heading_valid, "%.0f", a->true_heading); - if (a->category != 0xA0) - p = appendFATSVMeta(p, end, "category", a, &a->category_valid, "%02X", a->category); - p = appendFATSVMeta(p, end, "selAlt", a, &a->intent_altitude_valid,"%u", a->intent_altitude); - p = appendFATSVMeta(p, end, "selHdg", a, &a->intent_heading_valid, "%.0f", a->intent_heading); - 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); + 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_geom", a, &a->geom_rate_valid, "%d", a->geom_rate); + p = appendFATSVMeta(p, end, "speed", a, &a->gs_valid, "%.1f", a->gs); + p = appendFATSVMeta(p, end, "speed_ias", a, &a->ias_valid, "%u", a->ias); + 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, "track", a, &a->track_valid, "%.1f", a->track); + 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, "heading_magnetic", a, &a->mag_heading_valid, "%.1f", a->mag_heading); + p = appendFATSVMeta(p, end, "heading_true", a, &a->true_heading_valid, "%.1f", a->true_heading); + p = appendFATSVMeta(p, end, "nav_alt", a, &a->nav_altitude_valid, "%u", a->nav_altitude); + p = appendFATSVMeta(p, end, "nav_heading", a, &a->nav_heading_valid, "%.1f", a->nav_heading); + p = appendFATSVMeta(p, end, "nav_modes", a, &a->nav_modes_valid, "{%s}", nav_modes_string(a->nav_modes)); + p = appendFATSVMeta(p, end, "nav_qnh", a, &a->nav_qnh_valid, "%.1f", a->nav_qnh); // if we didn't get anything interesting, bail out. // We don't need to do anything special to unwind prepareWrite(). @@ -2122,12 +2167,13 @@ static void writeFATSV() else 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_gnss = a->altitude_geom; + a->fatsv_emitted_altitude_baro = a->altitude_baro; + a->fatsv_emitted_altitude_geom = a->altitude_geom; a->fatsv_emitted_baro_rate = a->baro_rate; a->fatsv_emitted_geom_rate = a->geom_rate; - a->fatsv_emitted_speed = a->gs; - a->fatsv_emitted_speed_ias = a->ias; + a->fatsv_emitted_gs = a->gs; + a->fatsv_emitted_ias = a->ias; + a->fatsv_emitted_tas = a->tas; a->fatsv_emitted_mach = a->mach; a->fatsv_emitted_track = a->track; 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_true_heading = a->true_heading; a->fatsv_emitted_airground = a->airground; - a->fatsv_emitted_intent_altitude = a->intent_altitude; - a->fatsv_emitted_intent_heading = a->intent_heading; - a->fatsv_emitted_intent_modes = a->intent_modes; - a->fatsv_emitted_alt_setting = a->alt_setting; + a->fatsv_emitted_nav_altitude = a->nav_altitude; + a->fatsv_emitted_nav_heading = a->nav_heading; + a->fatsv_emitted_nav_modes = a->nav_modes; + a->fatsv_emitted_nav_qnh = a->nav_qnh; 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; + if (forceEmit) { + a->fatsv_last_force_emit = now; + } } } diff --git a/track.c b/track.c index 2e39a23..ecfd066 100644 --- a/track.c +++ b/track.c @@ -76,15 +76,22 @@ struct aircraft *trackCreateAircraft(struct modesMessage *mm) { a->signalLevel[i] = 1e-5; 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 // or ES type code) a->fatsv_emitted_bds_30[0] = 0x30; 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 - a->adsb_version = -1; - a->adsb_hrd = HEADING_MAGNETIC; - a->adsb_tah = HEADING_GROUND_TRACK; + // don't immediately emit, let some data build up + a->fatsv_last_emitted = a->fatsv_last_force_emit = messageNow(); // Copy the first message so we can emit it later when a second message arrives. a->first_message = *mm; @@ -92,7 +99,7 @@ struct aircraft *trackCreateAircraft(struct modesMessage *mm) { // 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) 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(geom_delta, 60, 70); // ADS-B only 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(geom_rate, 60, 70); // ADS-B or Comm-B 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(alt_setting, 60, 70); // Comm-B only - F(intent_altitude, 60, 70); // ADS-B or Comm-B - F(intent_modes, 60, 70); // ADS-B or Comm-B + F(nav_qnh, 60, 70); // Comm-B only + F(nav_altitude, 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_even, 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 Modes.stats_current.unique_aircraft++; @@ -150,8 +165,8 @@ static int accept_data(data_validity *d, datasource_t source) d->source = source; d->updated = messageNow(); - d->stale = messageNow() + d->stale_interval; - d->expires = messageNow() + d->expire_interval; + d->stale = messageNow() + (d->stale_interval ? d->stale_interval : 60000); + d->expires = messageNow() + (d->expire_interval ? d->expire_interval : 70000); return 1; } @@ -287,13 +302,16 @@ static int speed_check(struct aircraft *a, double lat, double lon, int surface) 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 fflag = mm->cpr_odd; 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) { // 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 reflat = a->lat; reflon = a->lon; - if (a->pos_nuc < *nuc) - *nuc = a->pos_nuc; } else if (Modes.bUserFlags & MODES_USER_LATLON_VALID) { reflat = Modes.fUserLat; reflon = Modes.fUserLon; @@ -356,7 +372,7 @@ static int doGlobalCPR(struct aircraft *a, struct modesMessage *mm, double *lat, return result; // 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++; return -2; } @@ -364,7 +380,7 @@ static int doGlobalCPR(struct aircraft *a, struct modesMessage *mm, double *lat, 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 // find reference location @@ -374,14 +390,22 @@ static int doLocalCPR(struct aircraft *a, struct modesMessage *mm, double *lat, int fflag = mm->cpr_odd; 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)) { reflat = a->lat; reflon = a->lon; - if (a->pos_nuc < *nuc) - *nuc = a->pos_nuc; + if (a->pos_nic < *nic) + *nic = a->pos_nic; + if (a->pos_rc < *rc) + *rc = a->pos_rc; range_limit = 50e3; } 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 - 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 fprintf(stderr, "Speed check for %06X with local decoding failed\n", a->addr); #endif @@ -454,7 +478,8 @@ static void updatePosition(struct aircraft *a, struct modesMessage *mm) int location_result = -1; uint64_t max_elapsed; double new_lat = 0, new_lon = 0; - unsigned new_nuc = 0; + unsigned new_nic = 0; + unsigned new_rc = 0; int 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; // 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; else max_elapsed = 25000; @@ -480,7 +505,7 @@ static void updatePosition(struct aircraft *a, struct modesMessage *mm) a->cpr_odd_type == a->cpr_even_type && 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) { #ifdef DEBUG_CPR_CHECKS @@ -511,7 +536,7 @@ static void updatePosition(struct aircraft *a, struct modesMessage *mm) // Otherwise try relative CPR. 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) { Modes.stats_current.cpr_local_skipped++; @@ -532,16 +557,247 @@ static void updatePosition(struct aircraft *a, struct modesMessage *mm) mm->cpr_decoded = 1; mm->decoded_lat = new_lat; mm->decoded_lon = new_lon; + mm->decoded_nic = new_nic; + mm->decoded_rc = new_rc; // Update aircraft state a->lat = new_lat; 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); } } +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) 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) { - int new_modeC = (a->altitude + 49) / 100; - int old_modeC = (mm->altitude + 49) / 100; + int new_modeC = (a->altitude_baro + 49) / 100; + int old_modeC = (alt + 49) / 100; if (new_modeC != old_modeC) { a->modeC_hit = 0; } } - a->altitude = mm->altitude; + a->altitude_baro = alt; } 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; } - if (mm->altitude_valid && mm->altitude_source == ALTITUDE_GEOM && accept_data(&a->altitude_geom_valid, mm->source)) { - a->altitude_geom = mm->altitude; + if (mm->altitude_geom_valid && accept_data(&a->altitude_geom_valid, mm->source)) { + 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)) { @@ -635,8 +909,11 @@ struct aircraft *trackUpdateFromMessage(struct modesMessage *mm) a->roll = mm->roll; } - if (mm->gs_valid && accept_data(&a->gs_valid, mm->source)) { - a->gs = mm->gs; + if (mm->gs_valid) { + 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)) { @@ -659,10 +936,6 @@ struct aircraft *trackUpdateFromMessage(struct modesMessage *mm) 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)) { a->airground = mm->airground; } @@ -673,22 +946,22 @@ struct aircraft *trackUpdateFromMessage(struct modesMessage *mm) // prefer MCP over FMS // 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)) { - a->intent_altitude = mm->intent.mcp_altitude; - } else if (mm->intent.fms_altitude_valid && accept_data(&a->intent_altitude_valid, mm->source)) { - a->intent_altitude = mm->intent.fms_altitude; + if (mm->nav.mcp_altitude_valid && mm->nav.altitude_source != NAV_ALT_FMS && accept_data(&a->nav_altitude_valid, mm->source)) { + a->nav_altitude = mm->nav.mcp_altitude; + } else if (mm->nav.fms_altitude_valid && accept_data(&a->nav_altitude_valid, mm->source)) { + a->nav_altitude = mm->nav.fms_altitude; } - if (mm->intent.heading_valid && accept_data(&a->intent_heading_valid, mm->source)) { - a->intent_heading = mm->intent.heading; + if (mm->nav.heading_valid && accept_data(&a->nav_heading_valid, mm->source)) { + a->nav_heading = mm->nav.heading; } - if (mm->intent.modes_valid && accept_data(&a->intent_modes_valid, mm->source)) { - a->intent_modes = mm->intent.modes; + if (mm->nav.modes_valid && accept_data(&a->nav_modes_valid, mm->source)) { + a->nav_modes = mm->nav.modes; } - if (mm->intent.alt_setting_valid && accept_data(&a->alt_setting_valid, mm->source)) { - a->alt_setting = mm->intent.alt_setting; + if (mm->nav.qnh_valid && accept_data(&a->nav_qnh_valid, mm->source)) { + a->nav_qnh = mm->nav.qnh; } // CPR, even @@ -696,7 +969,7 @@ struct aircraft *trackUpdateFromMessage(struct modesMessage *mm) a->cpr_even_type = mm->cpr_type; a->cpr_even_lat = mm->cpr_lat; 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 @@ -704,26 +977,52 @@ struct aircraft *trackUpdateFromMessage(struct modesMessage *mm) a->cpr_odd_type = mm->cpr_type; a->cpr_odd_lat = mm->cpr_lat; 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->opstatus.valid) { - 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.sda_valid && accept_data(&a->sda_valid, mm->source)) { + a->sda = mm->accuracy.sda; + } + + 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 // 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) { // Baro and delta are both more recent than geometric, derive geometric from baro + delta - a->altitude_geom = a->altitude + a->geom_delta; - combine_validity(&a->altitude_geom_valid, &a->altitude_valid, &a->geom_delta_valid); + a->altitude_geom = a->altitude_baro + a->geom_delta; + combine_validity(&a->altitude_geom_valid, &a->altitude_baro_valid, &a->geom_delta_valid); } // If we've got a new cprlat or cprlon @@ -762,8 +1061,8 @@ static void trackMatchAC(uint64_t now) } // match on Mode C (+/- 100ft) - if (trackDataValid(&a->altitude_valid)) { - int modeC = (a->altitude + 49) / 100; + if (trackDataValid(&a->altitude_baro_valid)) { + int modeC = (a->altitude_baro + 49) / 100; unsigned modeA = modeCToModeA(modeC); 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) EXPIRE(callsign); - EXPIRE(altitude); + EXPIRE(altitude_baro); EXPIRE(altitude_geom); EXPIRE(geom_delta); EXPIRE(gs); @@ -859,15 +1158,21 @@ static void trackRemoveStaleAircraft(uint64_t now) EXPIRE(baro_rate); EXPIRE(geom_rate); EXPIRE(squawk); - EXPIRE(category); EXPIRE(airground); - EXPIRE(alt_setting); - EXPIRE(intent_altitude); - EXPIRE(intent_heading); - EXPIRE(intent_modes); + EXPIRE(nav_qnh); + EXPIRE(nav_altitude); + EXPIRE(nav_heading); + EXPIRE(nav_modes); EXPIRE(cpr_odd); EXPIRE(cpr_even); EXPIRE(position); + EXPIRE(nic_a); + EXPIRE(nic_c); + EXPIRE(nic_baro); + EXPIRE(nac_p); + EXPIRE(sil); + EXPIRE(gva); + EXPIRE(sda); #undef EXPIRE prev = a; a = a->next; } diff --git a/track.h b/track.h index f8ab089..f78af1a 100644 --- a/track.h +++ b/track.h @@ -64,6 +64,9 @@ */ #define TRACK_MODEAC_MIN_MESSAGES 4 +/* Special value for Rc unknown (100NM) */ +#define RC_UNKNOWN 185200 + // data moves through three states: // 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. @@ -92,8 +95,8 @@ struct aircraft { data_validity callsign_valid; char callsign[9]; // Flight number - data_validity altitude_valid; - int altitude; // Altitude (Baro) + data_validity altitude_baro_valid; + int altitude_baro; // Altitude (Baro) data_validity altitude_geom_valid; int altitude_geom; // Altitude (Geometric) @@ -102,7 +105,7 @@ struct aircraft { int geom_delta; // Difference between Geometric and Baro altitudes data_validity gs_valid; - unsigned gs; + float gs; data_validity ias_valid; unsigned ias; @@ -137,49 +140,71 @@ struct aircraft { data_validity squawk_valid; unsigned squawk; // Squawk - data_validity category_valid; unsigned category; // Aircraft category A0 - D7 encoded as a single hex byte data_validity airground_valid; airground_t airground; // air/ground status - data_validity alt_setting_valid; - float alt_setting; // Altimeter setting (QNH/QFE), millibars + data_validity nav_qnh_valid; + float nav_qnh; // Altimeter setting (QNH/QFE), millibars - data_validity intent_altitude_valid; - unsigned intent_altitude; // intent altitude (FMS or FCU selected altitude) + data_validity nav_altitude_valid; + unsigned nav_altitude; // FMS or FCU selected altitude - data_validity intent_heading_valid; - float intent_heading; // intent heading, degrees (0-359) + data_validity nav_heading_valid; + float nav_heading; // target heading, degrees (0-359) - data_validity intent_modes_valid; - intent_modes_t intent_modes; // enabled modes (autopilot, vnav, etc) + data_validity nav_modes_valid; + nav_modes_t nav_modes; // enabled modes (autopilot, vnav, etc) data_validity cpr_odd_valid; // Last seen even CPR message cpr_type_t cpr_odd_type; unsigned cpr_odd_lat; 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 cpr_type_t cpr_even_type; unsigned cpr_even_lat; unsigned cpr_even_lon; - unsigned cpr_even_nuc; + unsigned cpr_even_nic; + unsigned cpr_even_rc; data_validity position_valid; 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 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) + 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 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_gnss; // -"- GNSS altitude + int fatsv_emitted_altitude_baro; // last FA emitted altitude + int fatsv_emitted_altitude_geom; // -"- GNSS altitude int fatsv_emitted_baro_rate; // -"- barometric rate int fatsv_emitted_geom_rate; // -"- geometric rate float fatsv_emitted_track; // -"- true track @@ -187,22 +212,27 @@ struct aircraft { float fatsv_emitted_mag_heading; // -"- magnetic heading float fatsv_emitted_true_heading; // -"- true heading float fatsv_emitted_roll; // -"- roll angle - unsigned fatsv_emitted_speed; // -"- groundspeed - unsigned fatsv_emitted_speed_ias; // -"- IAS - unsigned fatsv_emitted_speed_tas; // -"- TAS + float fatsv_emitted_gs; // -"- groundspeed + unsigned fatsv_emitted_ias; // -"- IAS + unsigned fatsv_emitted_tas; // -"- TAS float fatsv_emitted_mach; // -"- Mach number airground_t fatsv_emitted_airground; // -"- air/ground state - unsigned fatsv_emitted_intent_altitude; // -"- intent altitude - float fatsv_emitted_intent_heading; // -"- intent heading - intent_modes_t fatsv_emitted_intent_modes; // -"- enabled modes - float fatsv_emitted_alt_setting; // -"- altimeter setting + unsigned fatsv_emitted_nav_altitude; // -"- target altitude + float fatsv_emitted_nav_heading; // -"- target heading + nav_modes_t fatsv_emitted_nav_modes; // -"- enabled navigation modes + float fatsv_emitted_nav_qnh; // -"- altimeter setting 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_es_status[7]; // -"- ES operational status message unsigned char fatsv_emitted_es_acas_ra[7]; // -"- ES ACAS RA report message 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_force_emit; // time (millis) we last emitted only-on-change data struct aircraft *next; // Next aircraft in our linked list