WIP: More Comm-B & ADS-B decoding.

This commit is contained in:
Oliver Jowett 2017-06-15 18:07:40 +01:00
parent 2e0aba4f1f
commit 98d64483d6
10 changed files with 1446 additions and 438 deletions

View file

@ -45,13 +45,13 @@ all: dump1090 view1090
%.o: %.c *.h %.o: %.c *.h
$(CC) $(CPPFLAGS) $(CFLAGS) -c $< -o $@ $(CC) $(CPPFLAGS) $(CFLAGS) -c $< -o $@
dump1090: dump1090.o anet.o interactive.o mode_ac.o mode_s.o net_io.o crc.o demod_2400.o stats.o cpr.o icao_filter.o track.o util.o convert.o sdr_ifile.o sdr.o $(SDR_OBJ) $(COMPAT) dump1090: dump1090.o anet.o interactive.o mode_ac.o mode_s.o comm_b.o net_io.o crc.o demod_2400.o stats.o cpr.o icao_filter.o track.o util.o convert.o sdr_ifile.o sdr.o $(SDR_OBJ) $(COMPAT)
$(CC) -g -o $@ $^ $(LDFLAGS) $(LIBS) $(LIBS_SDR) -lncurses $(CC) -g -o $@ $^ $(LDFLAGS) $(LIBS) $(LIBS_SDR) -lncurses
view1090: view1090.o anet.o interactive.o mode_ac.o mode_s.o net_io.o crc.o stats.o cpr.o icao_filter.o track.o util.o $(COMPAT) view1090: view1090.o anet.o interactive.o mode_ac.o mode_s.o comm_b.o net_io.o crc.o stats.o cpr.o icao_filter.o track.o util.o $(COMPAT)
$(CC) -g -o $@ $^ $(LDFLAGS) $(LIBS) -lncurses $(CC) -g -o $@ $^ $(LDFLAGS) $(LIBS) -lncurses
faup1090: faup1090.o anet.o mode_ac.o mode_s.o net_io.o crc.o stats.o cpr.o icao_filter.o track.o util.o $(COMPAT) faup1090: faup1090.o anet.o mode_ac.o mode_s.o comm_b.o net_io.o crc.o stats.o cpr.o icao_filter.o track.o util.o $(COMPAT)
$(CC) -g -o $@ $^ $(LDFLAGS) $(LIBS) $(CC) -g -o $@ $^ $(LDFLAGS) $(LIBS)
clean: clean:

735
comm_b.c Normal file
View file

@ -0,0 +1,735 @@
// Part of dump1090, a Mode S message decoder for RTLSDR devices.
//
// comm_b.c: Comm-B message decoding
//
// Copyright (c) 2017 FlightAware, LLC
// Copyright (c) 2017 Oliver Jowett <oliver@mutability.co.uk>
//
// This file is free software: you may copy, redistribute and/or modify it
// under the terms of the GNU General Public License as published by the
// Free Software Foundation, either version 2 of the License, or (at your
// option) any later version.
//
// This file is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
#include "dump1090.h"
typedef int (*CommBDecoderFn)(struct modesMessage *,bool);
static int decodeEmptyResponse(struct modesMessage *mm, bool store);
static int decodeBDS10(struct modesMessage *mm, bool store);
static int decodeBDS17(struct modesMessage *mm, bool store);
static int decodeBDS20(struct modesMessage *mm, bool store);
static int decodeBDS30(struct modesMessage *mm, bool store);
static int decodeBDS40(struct modesMessage *mm, bool store);
static int decodeBDS50(struct modesMessage *mm, bool store);
static int decodeBDS60(struct modesMessage *mm, bool store);
static CommBDecoderFn comm_b_decoders[] = {
&decodeEmptyResponse,
&decodeBDS10,
&decodeBDS20,
&decodeBDS30,
&decodeBDS17,
&decodeBDS40,
&decodeBDS50,
&decodeBDS60
};
void decodeCommB(struct modesMessage *mm)
{
mm->commb_format = COMMB_UNKNOWN;
// If DR or UM are set, this message is _probably_ noise
// as nothing really seems to use the multisite broadcast stuff?
// Also skip anything that had errors corrected
if (mm->DR != 0 || mm->UM != 0 || mm->correctedbits > 0) {
return;
}
// This is a bit hairy as we don't know what the requested register was
int bestScore = 0;
CommBDecoderFn bestDecoder = NULL;
for (unsigned i = 0; i < (sizeof(comm_b_decoders) / sizeof(comm_b_decoders[0])); ++i) {
int score = comm_b_decoders[i](mm, false);
if (score > bestScore) {
bestScore = score;
bestDecoder = comm_b_decoders[i];
}
}
if (bestDecoder) {
// decode it
bestDecoder(mm, true);
}
}
static int decodeEmptyResponse(struct modesMessage *mm, bool store)
{
for (unsigned i = 0; i < 7; ++i) {
if (mm->MB[i] != 0) {
return 0;
}
}
if (store) {
mm->commb_format = COMMB_EMPTY_RESPONSE;
}
return 56;
}
// BDS1,0 Datalink capabilities
static int decodeBDS10(struct modesMessage *mm, bool store)
{
unsigned char *msg = mm->MB;
// BDS identifier
if (msg[0] != 0x10) {
return 0;
}
// Reserved bits
if (getbits(msg, 10, 15) != 0) {
return 0;
}
// Looks plausible.
if (store) {
mm->commb_format = COMMB_DATALINK_CAPS;
}
return 56;
}
// BDS1,7 Common usage GICB capability report
static int decodeBDS17(struct modesMessage *mm, bool store)
{
unsigned char *msg = mm->MB;
// reserved bits
if (getbits(msg, 25, 56) != 0) {
return 0;
}
int score = 0;
if (getbit(msg, 7)) {
score += 1; // 2,0 aircraft identification
} else {
// BDS2,0 is on almost everything
score -= 2;
}
// unlikely bits
if (getbit(msg, 10)) { // 4,1 next waypoint identifier
score -= 2;
}
if (getbit(msg, 11)) { // 4,2 next waypoint position
score -= 2;
}
if (getbit(msg, 12)) { // 4,3 next waypoint information
score -= 2;
}
if (getbit(msg, 13)) { // 4,4 meterological routine report
score -= 2;
}
if (getbit(msg, 14)) { // 4,4 meterological hazard report
score -= 2;
}
if (getbit(msg, 20)) { // 5,4 waypoint 1
score -= 2;
}
if (getbit(msg, 21)) { // 5,5 waypoint 2
score -= 2;
}
if (getbit(msg, 22)) { // 5,6 waypoint 3
score -= 2;
}
if (getbit(msg, 1) && getbit(msg, 2) && getbit(msg, 3) && getbit(msg, 4) && getbit(msg, 5)) {
// looks like ES capable
score += 5;
if (getbit(msg, 6)) {
// ES EDI
score += 1;
}
} else if (!getbit(msg, 1) && !getbit(msg, 2) && !getbit(msg, 3) && !getbit(msg, 4) && !getbit(msg, 5) && !getbit(msg, 6)) {
// not ES capable
score += 1;
} else {
// partial ES support, unlikely
score -= 12;
}
if (getbit(msg, 16) && getbit(msg, 24)) {
// track/turn, heading/speed
score += 2;
if (getbit(msg, 9)) {
// vertical intent
score += 1;
}
} else if (!getbit(msg, 16) && !getbit(msg, 24) && !getbit(msg, 9)) {
// neither
score += 1;
} else {
// unlikely
score -= 6;
}
if (store) {
mm->commb_format = COMMB_GICB_CAPS;
}
return score;
}
// BDS2,0 Aircraft identification
static int decodeBDS20(struct modesMessage *mm, bool store)
{
char callsign[9];
unsigned char *msg = mm->MB;
// BDS identifier
if (msg[0] != 0x20) {
return 0;
}
callsign[0] = ais_charset[getbits(msg, 9, 14)];
callsign[1] = ais_charset[getbits(msg, 15, 20)];
callsign[2] = ais_charset[getbits(msg, 21, 26)];
callsign[3] = ais_charset[getbits(msg, 27, 32)];
callsign[4] = ais_charset[getbits(msg, 33, 38)];
callsign[5] = ais_charset[getbits(msg, 39, 44)];
callsign[6] = ais_charset[getbits(msg, 45, 50)];
callsign[7] = ais_charset[getbits(msg, 51, 56)];
callsign[8] = 0;
// score based on number of valid non-space characters
int score = 8;
for (unsigned i = 0; i < 8; ++i) {
if ((callsign[i] >= 'A' && callsign[i] <= 'Z') || (callsign[i] >= '0' && callsign[i] <= '9')) {
score += 6;
} else if (callsign[i] == ' ') {
// Valid, but not informative
} else {
// Invalid
return 0;
}
}
if (store) {
mm->commb_format = COMMB_AIRCRAFT_IDENT;
memcpy(mm->callsign, callsign, sizeof(mm->callsign));
mm->callsign_valid = 1;
}
return score;
}
// BDS3,0 ACAS RA
static int decodeBDS30(struct modesMessage *mm, bool store)
{
unsigned char *msg = mm->MB;
// BDS identifier
if (msg[0] != 0x30) {
return 0;
}
if (store) {
mm->commb_format = COMMB_ACAS_RA;
}
// just accept it.
return 56;
}
// BDS4,0 Selected vertical intention
static int decodeBDS40(struct modesMessage *mm, bool store)
{
unsigned char *msg = mm->MB;
unsigned mcp_valid = getbit(msg, 1);
unsigned mcp_raw = getbits(msg, 2, 13);
unsigned fms_valid = getbit(msg, 14);
unsigned fms_raw = getbits(msg, 15, 26);
unsigned baro_valid = getbit(msg, 27);
unsigned baro_raw = getbits(msg, 28, 39);
unsigned reserved_1 = getbits(msg, 40, 47);
unsigned mode_valid = getbit(msg, 48);
unsigned mode_raw = getbits(msg, 49, 51);
unsigned reserved_2 = getbits(msg, 52, 53);
unsigned source_valid = getbit(msg, 54);
unsigned source_raw = getbits(msg, 55, 56);
if (!mcp_valid && !fms_valid && !baro_valid && !mode_valid && !source_valid) {
return 0;
}
int score = 0;
unsigned mcp_alt = 0;
if (mcp_valid && mcp_raw != 0) {
mcp_alt = mcp_raw * 16;
if (mcp_alt >= 1000 && mcp_alt <= 50000) {
score += 13;
}
} else if (!mcp_valid && mcp_raw == 0) {
score += 1;
} else {
score -= 130;
}
unsigned fms_alt = 0;
if (fms_valid && fms_raw != 0) {
fms_alt = fms_raw * 16;
if (fms_alt >= 1000 && fms_alt <= 50000) {
score += 13;
}
} else if (!fms_valid && fms_raw == 0) {
score += 1;
} else {
score -= 130;
}
float baro_setting = 0;
if (baro_valid && baro_raw != 0) {
baro_setting = 800 + baro_raw * 0.1;
if (baro_setting >= 900 && baro_setting <= 1100) {
score += 13;
}
} else if (!baro_valid && baro_raw == 0) {
score += 1;
} else {
score -= 130;
}
if (reserved_1 != 0) {
score -= 80;
}
if (mode_valid) {
score += 4;
} else if (!mode_valid && mode_raw == 0) {
score += 1;
} else {
score -= 40;
}
if (reserved_2 != 0) {
score -= 20;
}
if (source_valid) {
score += 3;
} else if (!source_valid && source_raw == 0) {
score += 1;
} else {
score -= 30;
}
// small bonuses for consistent data
if (mcp_valid && fms_valid && mcp_alt == fms_alt) {
score += 2;
}
if (baro_valid && baro_raw == 2132) {
// 1013.2mb, standard pressure
score += 2;
}
if (mcp_valid) {
unsigned remainder = mcp_alt % 500;
if (remainder < 16 || remainder > 484) {
// mcp altitude is a multiple of 500
score += 2;
}
}
if (fms_valid) {
unsigned remainder = fms_alt % 500;
if (remainder < 16 || remainder > 484) {
// fms altitude is a multiple of 500
score += 2;
}
}
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;
}
if (fms_valid) {
mm->intent.fms_altitude_valid = 1;
mm->intent.fms_altitude = fms_alt;
}
if (baro_valid) {
mm->intent.alt_setting_valid = 1;
mm->intent.alt_setting = baro_setting;
}
if (mode_valid) {
mm->intent.mode_vnav = (mode_raw & 4) ? 1 : 0;
mm->intent.mode_alt_hold = (mode_raw & 2) ? 1 : 0;
mm->intent.mode_approach = (mode_raw & 1) ? 1 : 0;
}
if (source_valid) {
switch (source_raw) {
case 0:
mm->intent.altitude_source = TARGET_UNKNOWN;
break;
case 1:
mm->intent.altitude_source = TARGET_AIRCRAFT;
break;
case 2:
mm->intent.altitude_source = TARGET_MCP;
break;
case 3:
mm->intent.altitude_source = TARGET_FMS;
break;
default:
mm->intent.altitude_source = TARGET_INVALID;
break;
}
} else {
mm->intent.altitude_source = TARGET_INVALID;
}
}
return score;
}
// BDS5,0 Track and turn report
static int decodeBDS50(struct modesMessage *mm, bool store)
{
unsigned char *msg = mm->MB;
unsigned roll_valid = getbit(msg, 1);
unsigned roll_sign = getbit(msg, 2);
unsigned roll_raw = getbits(msg, 3, 11);
unsigned track_valid = getbit(msg, 12);
unsigned track_sign = getbit(msg, 13);
unsigned track_raw = getbits(msg, 14, 23);
unsigned gs_valid = getbit(msg, 24);
unsigned gs_raw = getbits(msg, 25, 34);
unsigned track_rate_valid = getbit(msg, 35);
unsigned track_rate_sign = getbit(msg, 36);
unsigned track_rate_raw = getbits(msg, 37, 45);
unsigned tas_valid = getbit(msg, 46);
unsigned tas_raw = getbits(msg, 47, 56);
if (!roll_valid && !track_valid && !gs_valid && !track_rate_valid && !tas_valid) {
return 0;
}
int score = 0;
float roll = 0;
if (roll_valid) {
roll = roll_raw * 45.0 / 256.0;
if (roll_sign) {
roll -= 90.0;
}
if (roll >= -40 && roll < 40) {
score += 11;
} else {
score -= 5;
}
} else if (!roll_valid && roll_raw == 0 && !roll_sign) {
score += 1;
} else {
score -= 110;
}
float track = 0;
if (track_valid) {
score += 12;
track = track_raw * 90.0 / 512.0;
if (track_sign) {
track += 180.0;
}
} else if (!track_valid && track_raw == 0 && !track_sign) {
score += 1;
} else {
score -= 120;
}
unsigned gs = 0;
if (gs_valid && gs_raw != 0) {
gs = gs_raw * 2;
if (gs >= 50 && gs <= 700) {
score += 11;
} else {
score -= 5;
}
} else if (!gs_valid && gs_raw == 0) {
score += 1;
} else {
score -= 110;
}
float track_rate = 0;
if (track_rate_valid) {
track_rate = track_rate_raw * 8.0 / 256.0;
if (track_rate_sign) {
track_rate -= 16;
}
if (track_rate >= -10.0 && track_rate <= 10.0) {
score += 11;
} else {
score -= 5;
}
} else if (!track_rate_valid && track_rate_raw == 0 && !track_rate_sign) {
score += 1;
} else {
score -= 110;
}
unsigned tas = 0;
if (tas_valid && tas_raw != 0) {
tas = tas_raw * 2;
if (tas >= 50 && tas <= 700) {
score += 11;
} else {
score -= 5;
}
} else if (!tas_valid && tas_raw == 0) {
score += 1;
} else {
score -= 110;
}
// small bonuses for consistent data
if (gs_valid && tas_valid) {
int delta = abs((int)gs_valid - (int)tas_valid);
if (delta < 50) {
score += 5;
} else if (delta > 150) {
score -= 5;
}
}
// compute the theoretical turn rate and compare to track angle rate
if (roll_valid && tas_valid && tas > 0 && track_rate_valid) {
double turn_rate = 68625 * tan(roll * M_PI / 180.0) / (tas * 20 * M_PI);
double delta = fabs(turn_rate - track_rate);
if (delta < 0.5) {
score += 5;
} else if (delta > 2.0) {
score -= 5;
}
}
if (store) {
mm->commb_format = COMMB_TRACK_TURN;
if (roll_valid) {
mm->roll_valid = 1;
mm->roll = roll;
}
if (track_valid) {
mm->track_valid = 1;
mm->track = track;
}
if (gs_valid) {
mm->gs_valid = 1;
mm->gs = gs;
}
if (track_rate_valid) {
mm->track_rate_valid = 1;
mm->track_rate = track_rate;
}
if (tas_valid) {
mm->tas_valid = 1;
mm->tas = tas;
}
}
return score;
}
// BDS6,0 Heading and speed report
static int decodeBDS60(struct modesMessage *mm, bool store)
{
unsigned char *msg = mm->MB;
unsigned heading_valid = getbit(msg, 1);
unsigned heading_sign = getbit(msg, 2);
unsigned heading_raw = getbits(msg, 3, 12);
unsigned ias_valid = getbit(msg, 13);
unsigned ias_raw = getbits(msg, 14, 23);
unsigned mach_valid = getbit(msg, 24);
unsigned mach_raw = getbits(msg, 25, 34);
unsigned baro_rate_valid = getbit(msg, 35);
unsigned baro_rate_sign = getbit(msg, 36);
unsigned baro_rate_raw = getbits(msg, 37, 45);
unsigned inertial_rate_valid = getbit(msg, 46);
unsigned inertial_rate_sign = getbit(msg, 47);
unsigned inertial_rate_raw = getbits(msg, 48, 56);
if (!heading_valid && !ias_valid && !mach_valid && !baro_rate_valid && !inertial_rate_valid) {
return 0;
}
int score = 0;
float heading = 0;
if (heading_valid) {
heading = heading_raw * 90.0 / 512.0;
if (heading_sign) {
heading += 180.0;
}
score += 12;
} else if (!heading_valid && heading_raw == 0 && !heading_sign) {
score += 1;
} else {
score -= 120;
}
unsigned ias = 0;
if (ias_valid && ias_raw != 0) {
ias = ias_raw;
if (ias >= 50 && ias <= 700) {
score += 11;
} else {
score -= 5;
}
} else if (!ias_valid && ias_raw == 0) {
score += 1;
} else {
score -= 110;
}
float mach = 0;
if (mach_valid && mach_raw != 0) {
mach = mach_raw * 2.048 / 512;
if (mach >= 0.1 && mach <= 0.9) {
score += 11;
} else {
score -= 5;
}
} else if (!mach_valid && mach_raw == 0) {
score += 1;
} else {
score -= 110;
}
int baro_rate = 0;
if (baro_rate_valid) {
baro_rate = baro_rate_raw * 32;
if (baro_rate_sign) {
baro_rate -= 16384;
}
if (baro_rate >= -6000 && baro_rate <= 6000) {
score += 11;
} else {
score -= 5;
}
} else if (!baro_rate_valid && baro_rate_raw == 0) {
score += 1;
} else {
score -= 110;
}
int inertial_rate = 0;
if (inertial_rate_valid) {
inertial_rate = inertial_rate_raw * 32;
if (inertial_rate_sign) {
inertial_rate -= 16384;
}
if (inertial_rate >= -6000 && inertial_rate <= 6000) {
score += 11;
} else {
score -= 5;
}
} else if (!inertial_rate_valid && inertial_rate_raw == 0) {
score += 1;
} else {
score -= 110;
}
// small bonuses for consistent data
if (ias_valid && mach_valid) {
double delta = fabs(ias / 666.0 - mach);
if (delta < 0.1) {
score += 5;
} else if (delta > 0.25) {
score -= 5;
}
}
if (baro_rate_valid && inertial_rate_valid) {
int delta = abs(baro_rate - inertial_rate);
if (delta < 500) {
score += 5;
} else if (delta > 200) {
score -= 5;
}
}
if (store) {
mm->commb_format = COMMB_HEADING_SPEED;
if (heading_valid) {
mm->mag_heading_valid = 1;
mm->mag_heading = heading;
}
if (ias_valid) {
mm->ias_valid = 1;
mm->ias = ias;
}
if (mach_valid) {
mm->mach_valid = 1;
mm->mach = mach;
}
if (baro_rate_valid) {
mm->baro_rate_valid = 1;
mm->baro_rate = baro_rate;
}
if (inertial_rate_valid) {
// INS-derived data is treated as a "geometric rate" / "geometric altitude"
// elsewhere, so do the same here.
mm->geom_rate_valid = 1;
mm->geom_rate = inertial_rate;
}
}
return score;
}

26
comm_b.h Normal file
View file

@ -0,0 +1,26 @@
// Part of dump1090, a Mode S message decoder for RTLSDR devices.
//
// comm_b.h: Comm-B message decoding (prototypes)
//
// Copyright (c) 2017 FlightAware, LLC
// Copyright (c) 2017 Oliver Jowett <oliver@mutability.co.uk>
//
// This file is free software: you may copy, redistribute and/or modify it
// under the terms of the GNU General Public License as published by the
// Free Software Foundation, either version 2 of the License, or (at your
// option) any later version.
//
// This file is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
#ifndef COMM_B_H
#define COMM_B_H
void decodeCommB(struct modesMessage *mm);
#endif

View file

@ -167,7 +167,7 @@ typedef enum {
typedef enum { typedef enum {
ALTITUDE_BARO, ALTITUDE_BARO,
ALTITUDE_GNSS ALTITUDE_GEOM
} altitude_source_t; } altitude_source_t;
typedef enum { typedef enum {
@ -177,12 +177,6 @@ typedef enum {
AG_UNCERTAIN AG_UNCERTAIN
} airground_t; } airground_t;
typedef enum {
SPEED_GROUNDSPEED,
SPEED_IAS,
SPEED_TAS
} speed_source_t;
typedef enum { typedef enum {
HEADING_TRUE, HEADING_TRUE,
HEADING_MAGNETIC HEADING_MAGNETIC
@ -196,6 +190,18 @@ typedef enum {
CPR_SURFACE, CPR_AIRBORNE, CPR_COARSE CPR_SURFACE, CPR_AIRBORNE, CPR_COARSE
} cpr_type_t; } cpr_type_t;
typedef enum {
COMMB_UNKNOWN,
COMMB_EMPTY_RESPONSE,
COMMB_DATALINK_CAPS,
COMMB_GICB_CAPS,
COMMB_AIRCRAFT_IDENT,
COMMB_ACAS_RA,
COMMB_VERTICAL_INTENT,
COMMB_TRACK_TURN,
COMMB_HEADING_SPEED
} commb_format_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
#define MODES_DEBUG_DEMOD (1<<0) #define MODES_DEBUG_DEMOD (1<<0)
@ -406,19 +412,24 @@ struct modesMessage {
// Decoded data // Decoded data
unsigned altitude_valid : 1; unsigned altitude_valid : 1;
unsigned heading_valid : 1; unsigned track_valid : 1;
unsigned speed_valid : 1; unsigned track_rate_valid : 1;
unsigned vert_rate_valid : 1; unsigned mag_heading_valid : 1;
unsigned roll_valid : 1;
unsigned gs_valid : 1;
unsigned ias_valid : 1;
unsigned tas_valid : 1;
unsigned mach_valid : 1;
unsigned baro_rate_valid : 1;
unsigned geom_rate_valid : 1;
unsigned squawk_valid : 1; unsigned squawk_valid : 1;
unsigned callsign_valid : 1; unsigned callsign_valid : 1;
unsigned ew_velocity_valid : 1;
unsigned ns_velocity_valid : 1;
unsigned cpr_valid : 1; unsigned cpr_valid : 1;
unsigned cpr_odd : 1; unsigned cpr_odd : 1;
unsigned cpr_decoded : 1; unsigned cpr_decoded : 1;
unsigned cpr_relative : 1; unsigned cpr_relative : 1;
unsigned category_valid : 1; unsigned category_valid : 1;
unsigned gnss_delta_valid : 1; unsigned geom_delta_valid : 1;
unsigned from_mlat : 1; unsigned from_mlat : 1;
unsigned from_tisb : 1; unsigned from_tisb : 1;
unsigned spi_valid : 1; unsigned spi_valid : 1;
@ -429,26 +440,27 @@ struct modesMessage {
unsigned metype; // DF17/18 ME type unsigned metype; // DF17/18 ME type
unsigned mesub; // DF17/18 ME subtype unsigned mesub; // DF17/18 ME subtype
commb_format_t commb_format; // Inferred format of a comm-b message
// valid if altitude_valid: // valid if altitude_valid:
int altitude; // Altitude in either feet or meters int altitude; // Altitude in either feet or meters
altitude_unit_t altitude_unit; // the unit used for altitude altitude_unit_t altitude_unit; // the unit used for altitude
altitude_source_t altitude_source; // whether the altitude is a barometric altude or a GNSS height altitude_source_t altitude_source; // whether the altitude is a barometric altitude or a geometric height
// valid if gnss_delta_valid:
int gnss_delta; // difference between GNSS and baro alt // following fields are valid if the corresponding _valid field is set:
// valid if heading_valid: int geom_delta; // Difference between geometric and baro alt
unsigned heading; // Reported by aircraft, or computed from from EW and NS velocity float track; // True ground track, degrees (0-359). Reported directly or computed from from EW and NS velocity
heading_source_t heading_source; // what "heading" is measuring (true or magnetic heading) float track_rate; // Rate of change of track, degrees/second
// valid if speed_valid: float mag_heading; // Magnetic heading, degrees (0-359)
unsigned speed; // in kts, reported by aircraft, or computed from from EW and NS velocity float roll; // Roll, degrees, negative is left roll
speed_source_t speed_source; // what "speed" is measuring (groundspeed / IAS / TAS) unsigned gs; // Groundspeed, kts, reported directly or computed from from EW and NS velocity
// valid if vert_rate_valid: unsigned ias; // Indicated airspeed, kts
int vert_rate; // vertical rate in feet/minute unsigned tas; // True airspeed, kts
altitude_source_t vert_rate_source; // the altitude source used for vert_rate double mach; // Mach number
// valid if squawk_valid: int baro_rate; // Rate of change of barometric altitude, feet/minute
unsigned squawk; // 13 bits identity (Squawk), encoded as 4 hex digits int geom_rate; // Rate of change of geometric (GNSS / INS) altitude, feet/minute
// valid if callsign_valid unsigned squawk; // 13 bits identity (Squawk), encoded as 4 hex digits
char callsign[9]; // 8 chars flight number char callsign[9]; // 8 chars flight number, NUL-terminated
// valid if category_valid
unsigned category; // A0 - D7 encoded as a single hex byte unsigned category; // A0 - D7 encoded as a single hex byte
// valid if cpr_valid // valid if cpr_valid
cpr_type_t cpr_type; // The encoding type used (surface, airborne, coarse TIS-B) cpr_type_t cpr_type; // The encoding type used (surface, airborne, coarse TIS-B)
@ -500,32 +512,37 @@ struct modesMessage {
unsigned cc_antenna_offset; unsigned cc_antenna_offset;
} opstatus; } opstatus;
// Target State & Status (ADS-B V2 only) // combined:
// Target State & Status (ADS-B V2 only)
// Comm-B BDS4,0 Vertical Intent
struct { struct {
unsigned valid : 1; unsigned valid : 1;
unsigned altitude_valid : 1;
unsigned baro_valid : 1;
unsigned heading_valid : 1;
unsigned mode_valid : 1;
unsigned mode_autopilot : 1;
unsigned mode_vnav : 1;
unsigned mode_alt_hold : 1;
unsigned mode_approach : 1;
unsigned acas_operational : 1;
unsigned nac_p : 4;
unsigned nic_baro : 1;
unsigned sil : 2;
sil_type_t sil_type; unsigned heading_valid : 1;
enum { TSS_ALTITUDE_MCP, TSS_ALTITUDE_FMS } altitude_type; unsigned fms_altitude_valid : 1;
unsigned altitude; unsigned mcp_altitude_valid : 1;
float baro; unsigned alt_setting_valid : 1;
unsigned heading;
} tss; float heading; // heading, degrees (0-359) (could be magnetic or true heading; magnetic recommended)
unsigned fms_altitude; // FMS selected altitude
unsigned mcp_altitude; // MCP/FCU selected altitude
float alt_setting; // altimeter setting (QFE or QNH/QNE), millibars
enum { TARGET_INVALID, TARGET_UNKNOWN, TARGET_AIRCRAFT, TARGET_MCP, TARGET_FMS } altitude_source;
unsigned mode_autopilot : 1; // Autopilot engaged
unsigned mode_vnav : 1; // Vertical Navigation Mode active
unsigned mode_alt_hold : 1; // Altitude Hold Mode active
unsigned mode_approach : 1; // Approach Mode active
unsigned mode_lnav : 1; // Lateral Navigation Mode active
} intent;
}; };
// This one needs modesMessage: // This one needs modesMessage:
#include "track.h" #include "track.h"
#include "mode_s.h"
#include "comm_b.h"
// ======================== function declarations ========================= // ======================== function declarations =========================
@ -542,14 +559,6 @@ void modeACInit();
int modeAToModeC (unsigned int modeA); int modeAToModeC (unsigned int modeA);
unsigned modeCToModeA (int modeC); unsigned modeCToModeA (int modeC);
//
// Functions exported from mode_s.c
//
int modesMessageLenByType(int type);
int scoreModesMessage(unsigned char *msg, int validbits);
int decodeModesMessage (struct modesMessage *mm, unsigned char *msg);
void displayModesMessage(struct modesMessage *mm);
void useModesMessage (struct modesMessage *mm);
// //
// Functions exported from interactive.c // Functions exported from interactive.c
// //

View file

@ -130,12 +130,12 @@ void interactiveShowData(void) {
snprintf(strSquawk,5,"%04x", a->squawk); snprintf(strSquawk,5,"%04x", a->squawk);
} }
if (trackDataValid(&a->speed_valid)) { if (trackDataValid(&a->gs_valid)) {
snprintf (strGs, 5,"%3d", convert_speed(a->speed)); snprintf (strGs, 5,"%3d", convert_speed(a->gs));
} }
if (trackDataValid(&a->heading_valid)) { if (trackDataValid(&a->track_valid)) {
snprintf (strTt, 5,"%03d", a->heading); snprintf (strTt, 5,"%03.0f", a->track);
} }
if (msgs > 99999) { if (msgs > 99999) {
@ -160,8 +160,8 @@ void interactiveShowData(void) {
if (trackDataValid(&a->airground_valid) && a->airground == AG_GROUND) { if (trackDataValid(&a->airground_valid) && a->airground == AG_GROUND) {
snprintf(strFl, 7," grnd"); snprintf(strFl, 7," grnd");
} else if (Modes.use_gnss && trackDataValid(&a->altitude_gnss_valid)) { } else if (Modes.use_gnss && trackDataValid(&a->altitude_geom_valid)) {
snprintf(strFl, 7, "%5dH", convert_altitude(a->altitude_gnss)); snprintf(strFl, 7, "%5dH", convert_altitude(a->altitude_geom));
} else if (trackDataValid(&a->altitude_valid)) { } else if (trackDataValid(&a->altitude_valid)) {
snprintf(strFl, 7, "%5d ", convert_altitude(a->altitude)); snprintf(strFl, 7, "%5d ", convert_altitude(a->altitude));
} }

376
mode_s.c
View file

@ -53,8 +53,6 @@
/* for PRIX64 */ /* for PRIX64 */
#include <inttypes.h> #include <inttypes.h>
#include <assert.h>
// //
// ===================== Mode S detection and decoding =================== // ===================== Mode S detection and decoding ===================
// //
@ -220,67 +218,6 @@ static int correct_aa_field(uint32_t *addr, struct errorinfo *ei)
return addr_errors; return addr_errors;
} }
// The first bit (MSB of the first byte) is numbered 1, for consistency
// with how the specs number them.
// Extract one bit from a message.
static inline __attribute__((always_inline)) unsigned getbit(unsigned char *data, unsigned bitnum)
{
unsigned bi = bitnum - 1;
unsigned by = bi >> 3;
unsigned mask = 1 << (7 - (bi & 7));
return (data[by] & mask) != 0;
}
// Extract some bits (firstbit .. lastbit inclusive) from a message.
static inline __attribute__((always_inline)) unsigned getbits(unsigned char *data, unsigned firstbit, unsigned lastbit)
{
unsigned fbi = firstbit - 1;
unsigned lbi = lastbit - 1;
unsigned nbi = (lastbit - firstbit + 1);
unsigned fby = fbi >> 3;
unsigned lby = lbi >> 3;
unsigned nby = (lby - fby) + 1;
unsigned shift = 7 - (lbi & 7);
unsigned topmask = 0xFF >> (fbi & 7);
assert (fbi <= lbi);
assert (nbi <= 32);
assert (nby <= 5);
if (nby == 5) {
return
((data[fby] & topmask) << (32 - shift)) |
(data[fby + 1] << (24 - shift)) |
(data[fby + 2] << (16 - shift)) |
(data[fby + 3] << (8 - shift)) |
(data[fby + 4] >> shift);
} else if (nby == 4) {
return
((data[fby] & topmask) << (24 - shift)) |
(data[fby + 1] << (16 - shift)) |
(data[fby + 2] << (8 - shift)) |
(data[fby + 3] >> shift);
} else if (nby == 3) {
return
((data[fby] & topmask) << (16 - shift)) |
(data[fby + 1] << (8 - shift)) |
(data[fby + 2] >> shift);
} else if (nby == 2) {
return
((data[fby] & topmask) << (8 - shift)) |
(data[fby + 1] >> shift);
} else if (nby == 1) {
return
(data[fby] & topmask) >> shift;
} else {
return 0;
}
}
// Score how plausible this ModeS message looks. // Score how plausible this ModeS message looks.
// The more positive, the more reliable the message is // The more positive, the more reliable the message is
@ -417,8 +354,8 @@ int scoreModesMessage(unsigned char *msg, int validbits)
// //
static void decodeExtendedSquitter(struct modesMessage *mm); static void decodeExtendedSquitter(struct modesMessage *mm);
static void decodeCommB(struct modesMessage *mm);
static char *ais_charset = "@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_ !\"#$%&'()*+,-./0123456789:;<=>?"; char ais_charset[64] = "@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_ !\"#$%&'()*+,-./0123456789:;<=>?";
// return 0 if all OK // return 0 if all OK
// -1: message might be valid, but we couldn't validate the CRC against a known ICAO // -1: message might be valid, but we couldn't validate the CRC against a known ICAO
@ -737,33 +674,6 @@ int decodeModesMessage(struct modesMessage *mm, unsigned char *msg)
return 0; return 0;
} }
// Decode BDS2,0 carried in Comm-B or ES
static void decodeBDS20(struct modesMessage *mm)
{
unsigned char *msg = mm->msg;
mm->callsign[0] = ais_charset[getbits(msg, 41, 46)];
mm->callsign[1] = ais_charset[getbits(msg, 47, 52)];
mm->callsign[2] = ais_charset[getbits(msg, 53, 58)];
mm->callsign[3] = ais_charset[getbits(msg, 59, 64)];
mm->callsign[4] = ais_charset[getbits(msg, 65, 70)];
mm->callsign[5] = ais_charset[getbits(msg, 71, 76)];
mm->callsign[6] = ais_charset[getbits(msg, 77, 82)];
mm->callsign[7] = ais_charset[getbits(msg, 83, 88)];
mm->callsign[8] = 0;
// Catch possible bad decodings since BDS2,0 is not
// 100% reliable: accept only alphanumeric data
mm->callsign_valid = 1;
for (int i = 0; i < 8; ++i) {
if (! ((mm->callsign[i] >= 'A' && mm->callsign[i] <= 'Z') ||
(mm->callsign[i] >= '0' && mm->callsign[i] <= '9') ||
mm->callsign[i] == ' ') ) {
mm->callsign_valid = 0;
break;
}
}
}
static void decodeESIdentAndCategory(struct modesMessage *mm) static void decodeESIdentAndCategory(struct modesMessage *mm)
{ {
@ -828,13 +738,18 @@ static void decodeESAirborneVelocity(struct modesMessage *mm, int check_imf)
return; return;
unsigned vert_rate = getbits(me, 38, 46); unsigned vert_rate = getbits(me, 38, 46);
unsigned vert_rate_is_geom = getbit(me, 36);
if (vert_rate) { if (vert_rate) {
mm->vert_rate = (vert_rate - 1) * (getbit(me, 37) ? -64 : 64); int rate = (vert_rate - 1) * (getbit(me, 37) ? -64 : 64);
mm->vert_rate_valid = 1; if (vert_rate_is_geom) {
mm->geom_rate = rate;
mm->geom_rate_valid = 1;
} else {
mm->baro_rate = rate;
mm->baro_rate_valid = 1;
}
} }
mm->vert_rate_source = (getbit(me, 36) ? ALTITUDE_GNSS : ALTITUDE_BARO);
switch (mm->mesub) { switch (mm->mesub) {
case 1: case 2: case 1: case 2:
{ {
@ -846,20 +761,17 @@ 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->speed = (unsigned) sqrt((ns_vel * ns_vel) + (ew_vel * ew_vel) + 0.5); mm->gs = (unsigned) sqrt((ns_vel * ns_vel) + (ew_vel * ew_vel) + 0.5);
mm->speed_valid = 1; mm->gs_valid = 1;
if (mm->speed) { if (mm->gs) {
int heading = (int) (atan2(ew_vel, ns_vel) * 180.0 / M_PI + 0.5); float heading = 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 (heading < 0) if (heading < 0)
heading += 360; heading += 360;
mm->heading = (unsigned) heading; mm->track = heading;
mm->heading_source = HEADING_TRUE; mm->track_valid = 1;
mm->heading_valid = 1;
} }
mm->speed_source = SPEED_GROUNDSPEED;
} }
break; break;
} }
@ -868,15 +780,19 @@ static void decodeESAirborneVelocity(struct modesMessage *mm, int check_imf)
{ {
unsigned airspeed = getbits(me, 26, 35); unsigned airspeed = getbits(me, 26, 35);
if (airspeed) { if (airspeed) {
mm->speed = (airspeed - 1) * (mm->mesub == 4 ? 4 : 1); unsigned speed = (airspeed - 1) * (mm->mesub == 4 ? 4 : 1);
mm->speed_source = getbit(me, 25) ? SPEED_TAS : SPEED_IAS; if (getbit(me, 25)) {
mm->speed_valid = 1; mm->tas_valid = 1;
mm->tas = speed;
} else {
mm->ias_valid = 1;
mm->ias = speed;
}
} }
if (getbit(me, 14)) { if (getbit(me, 14)) {
mm->heading = getbits(me, 15, 24); mm->mag_heading_valid = 1;
mm->heading_source = HEADING_MAGNETIC; mm->mag_heading = getbits(me, 15, 24) * 360.0 / 1024.0;
mm->heading_valid = 1;
} }
break; break;
} }
@ -884,8 +800,8 @@ static void decodeESAirborneVelocity(struct modesMessage *mm, int check_imf)
unsigned raw_delta = getbits(me, 50, 56); unsigned raw_delta = getbits(me, 50, 56);
if (raw_delta) { if (raw_delta) {
mm->gnss_delta_valid = 1; mm->geom_delta_valid = 1;
mm->gnss_delta = (raw_delta - 1) * (getbit(me, 49) ? -25 : 25); mm->geom_delta = (raw_delta - 1) * (getbit(me, 49) ? -25 : 25);
} }
} }
@ -907,15 +823,13 @@ static void decodeESSurfacePosition(struct modesMessage *mm, int check_imf)
unsigned movement = getbits(me, 6, 12); unsigned movement = getbits(me, 6, 12);
if (movement > 0 && movement < 125) { if (movement > 0 && movement < 125) {
mm->speed_valid = 1; mm->gs_valid = 1;
mm->speed = decodeMovementField(movement); mm->gs = decodeMovementField(movement);
mm->speed_source = SPEED_GROUNDSPEED;
} }
if (getbit(me, 13)) { if (getbit(me, 13)) {
mm->heading_valid = 1; mm->track_valid = 1;
mm->heading_source = HEADING_TRUE; mm->track = getbits(me, 14, 20) * 360.0 / 128.0;
mm->heading = getbits(me, 14, 20) * 360 / 128;
} }
} }
@ -967,7 +881,7 @@ static void decodeESAirbornePosition(struct modesMessage *mm, int check_imf)
mm->altitude_valid = 1; mm->altitude_valid = 1;
} }
mm->altitude_source = (mm->metype == 20 || mm->metype == 21 || mm->metype == 22) ? ALTITUDE_GNSS : ALTITUDE_BARO; mm->altitude_source = (mm->metype == 20 || mm->metype == 21 || mm->metype == 22) ? ALTITUDE_GEOM : ALTITUDE_BARO;
} }
} }
@ -1017,45 +931,50 @@ static void decodeESTargetStatus(struct modesMessage *mm, int check_imf)
if (mm->mesub == 0) { // Target state and status, V1 if (mm->mesub == 0) { // Target state and status, V1
// TODO: need RTCA/DO-260A // TODO: need RTCA/DO-260A
} else if (mm->mesub == 1) { // Target state and status, V2 } else if (mm->mesub == 1) { // Target state and status, V2
mm->tss.valid = 1; mm->intent.valid = 1;
mm->tss.sil_type = getbit(me, 8) ? SIL_PER_SAMPLE : SIL_PER_HOUR;
mm->tss.altitude_type = getbit(me, 9) ? TSS_ALTITUDE_FMS : TSS_ALTITUDE_MCP; // 8: SIL
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) {
mm->tss.altitude_valid = 0; if (is_fms) {
} else { mm->intent.fms_altitude_valid = 1;
mm->tss.altitude_valid = 1; mm->intent.fms_altitude = (alt_bits - 1) * 32;
mm->tss.altitude = (alt_bits - 1) * 32; } else {
mm->intent.mcp_altitude_valid = 1;
mm->intent.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->tss.baro_valid = 0; mm->intent.alt_setting_valid = 1;
} else { mm->intent.alt_setting = 800.0 + (baro_bits - 1) * 0.8;
mm->tss.baro_valid = 1;
mm->tss.baro = 800.0 + (baro_bits - 1) * 0.8;
} }
mm->tss.heading_valid = getbit(me, 30); if (getbit(me, 30)) {
if (mm->tss.heading_valid) { mm->intent.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->tss.heading = getbits(me, 31, 39) * 180 / 256; mm->intent.heading = getbits(me, 31, 39) * 180.0 / 256.0;
} }
mm->tss.nac_p = getbits(me, 40, 43); // 40-43: NACp
mm->tss.nic_baro = getbit(me, 44); // 44: NICbaro
mm->tss.sil = getbits(me, 45, 46); // 45-46: SIL
mm->tss.mode_valid = getbit(me, 47);
if (mm->tss.mode_valid) { if (getbit(me, 47)) {
mm->tss.mode_autopilot = getbit(me, 48); mm->intent.mode_autopilot = getbit(me, 48);
mm->tss.mode_vnav = getbit(me, 49); mm->intent.mode_vnav = getbit(me, 49);
mm->tss.mode_alt_hold = getbit(me, 50); mm->intent.mode_alt_hold = getbit(me, 50);
mm->tss.mode_approach = getbit(me, 52); // 51: IMF
mm->intent.mode_approach = getbit(me, 52);
// 53: TCAS operational
mm->intent.mode_lnav = getbit(me, 54);
} }
mm->tss.acas_operational = getbit(me, 53); // 55-56 reserved
} }
} }
@ -1230,7 +1149,7 @@ static void decodeExtendedSquitter(struct modesMessage *mm)
case 0: // Airborne position, baro altitude only case 0: // Airborne position, baro altitude only
case 9: case 10: case 11: case 12: case 13: case 14: case 15: case 16: case 17: case 18: // Airborne position, baro case 9: case 10: case 11: case 12: case 13: case 14: case 15: case 16: case 17: case 18: // Airborne position, baro
case 20: case 21: case 22: // Airborne position, GNSS altitude (HAE or MSL) case 20: case 21: case 22: // Airborne position, geometric altitude (HAE or MSL)
decodeESAirbornePosition(mm, check_imf); decodeESAirbornePosition(mm, check_imf);
break; break;
@ -1261,16 +1180,6 @@ static void decodeExtendedSquitter(struct modesMessage *mm)
} }
} }
static void decodeCommB(struct modesMessage *mm)
{
unsigned char *msg = mm->msg;
// This is a bit hairy as we don't know what the requested register was
if (getbits(msg, 33, 40) == 0x20) { // BDS 2,0 Aircraft Identification
decodeBDS20(mm);
}
}
static const char *df_names[33] = { static const char *df_names[33] = {
/* 0 */ "Short Air-Air Surveillance", /* 0 */ "Short Air-Air Surveillance",
/* 1 */ NULL, /* 1 */ NULL,
@ -1330,8 +1239,8 @@ static const char *altitude_source_to_string(altitude_source_t source) {
switch (source) { switch (source) {
case ALTITUDE_BARO: case ALTITUDE_BARO:
return "barometric"; return "barometric";
case ALTITUDE_GNSS: case ALTITUDE_GEOM:
return "GNSS"; return "geometric";
default: default:
return "(unknown altitude source)"; return "(unknown altitude source)";
} }
@ -1352,19 +1261,6 @@ static const char *airground_to_string(airground_t airground) {
} }
} }
static const char *speed_source_to_string(speed_source_t speed) {
switch (speed) {
case SPEED_GROUNDSPEED:
return "groundspeed";
case SPEED_IAS:
return "IAS";
case SPEED_TAS:
return "TAS";
default:
return "(unknown speed type)";
}
}
static const char *addrtype_to_string(addrtype_t type) { static const char *addrtype_to_string(addrtype_t type) {
switch (type) { switch (type) {
case ADDR_ADSB_ICAO: case ADDR_ADSB_ICAO:
@ -1403,6 +1299,29 @@ static const char *cpr_type_to_string(cpr_type_t type) {
} }
} }
static const char *commb_format_to_string(commb_format_t format) {
switch (format) {
case COMMB_EMPTY_RESPONSE:
return "empty response";
case COMMB_DATALINK_CAPS:
return "BDS1,0 Datalink capabilities";
case COMMB_GICB_CAPS:
return "BDS1,7 Common usage GICB capabilities";
case COMMB_AIRCRAFT_IDENT:
return "BDS2,0 Aircraft identification";
case COMMB_ACAS_RA:
return "BDS3,0 ACAS resolution advisory";
case COMMB_VERTICAL_INTENT:
return "BDS4,0 Selected vertical intention";
case COMMB_TRACK_TURN:
return "BDS5,0 Track and turn report";
case COMMB_HEADING_SPEED:
return "BDS6,0 Heading and speed report";
default:
return "unknown format";
}
}
static void print_hex_bytes(unsigned char *data, size_t len) { static void print_hex_bytes(unsigned char *data, size_t len) {
size_t i; size_t i;
for (i = 0; i < len; ++i) { for (i = 0; i < len; ++i) {
@ -1455,7 +1374,7 @@ static const char *esTypeName(unsigned metype, unsigned mesub)
} }
case 20: case 21: case 22: case 20: case 21: case 22:
return "Airborne position (GNSS altitude)"; return "Airborne position (geometric altitude)";
case 23: case 23:
switch (mesub) { switch (mesub) {
@ -1639,6 +1558,10 @@ void displayModesMessage(struct modesMessage *mm) {
} }
printf("\n"); printf("\n");
if (mm->msgtype == 20 || mm->msgtype == 21) {
printf(" Comm-B format: %s\n", commb_format_to_string(mm->commb_format));
}
if (mm->addr & MODES_NON_ICAO_ADDRESS) { if (mm->addr & MODES_NON_ICAO_ADDRESS) {
printf(" Other Address: %06X (%s)\n", mm->addr & 0xFFFFFF, addrtype_to_string(mm->addrtype)); printf(" Other Address: %06X (%s)\n", mm->addr & 0xFFFFFF, addrtype_to_string(mm->addrtype));
} else { } else {
@ -1657,25 +1580,49 @@ void displayModesMessage(struct modesMessage *mm) {
altitude_source_to_string(mm->altitude_source)); altitude_source_to_string(mm->altitude_source));
} }
if (mm->gnss_delta_valid) { if (mm->geom_delta_valid) {
printf(" GNSS delta: %d ft\n", printf(" Geom - baro: %d ft\n",
mm->gnss_delta); mm->geom_delta);
} }
if (mm->heading_valid) { if (mm->track_valid) {
printf(" Heading: %u\n", mm->heading); printf(" Track: %.1f\n", mm->track);
} }
if (mm->speed_valid) { if (mm->mag_heading_valid) {
printf(" Speed: %u kt %s\n", printf(" Mag heading: %.1f\n", mm->mag_heading);
mm->speed,
speed_source_to_string(mm->speed_source));
} }
if (mm->vert_rate_valid) { if (mm->track_rate_valid) {
printf(" Vertical rate: %d ft/min %s\n", printf(" Track rate: %.2f deg/sec %s\n", mm->track_rate, mm->track_rate < 0 ? "left" : mm->track_rate > 0 ? "right" : "");
mm->vert_rate, }
altitude_source_to_string(mm->vert_rate_source));
if (mm->roll_valid) {
printf(" Roll: %.1f degrees %s\n", mm->roll, mm->roll < -0.05 ? "left" : mm->roll > 0.05 ? "right" : "");
}
if (mm->gs_valid) {
printf(" Groundspeed: %u kt\n", mm->gs);
}
if (mm->ias_valid) {
printf(" IAS: %u kt\n", mm->ias);
}
if (mm->tas_valid) {
printf(" TAS: %u kt\n", mm->tas);
}
if (mm->mach_valid) {
printf(" Mach number: %.3f\n", mm->mach);
}
if (mm->baro_rate_valid) {
printf(" Baro rate: %d ft/min\n", mm->baro_rate);
}
if (mm->geom_rate_valid) {
printf(" Geom rate: %d ft/min\n", mm->geom_rate);
} }
if (mm->squawk_valid) { if (mm->squawk_valid) {
@ -1758,26 +1705,47 @@ void displayModesMessage(struct modesMessage *mm) {
printf(" Heading reference: %s\n", (mm->opstatus.hrd == HEADING_TRUE ? "true north" : "magnetic north")); printf(" Heading reference: %s\n", (mm->opstatus.hrd == HEADING_TRUE ? "true north" : "magnetic north"));
} }
if (mm->tss.valid) { if (mm->intent.valid) {
printf(" Target State and Status:\n"); printf(" Intent:\n");
if (mm->tss.altitude_valid) if (mm->intent.heading_valid)
printf(" Target altitude: %s, %d ft\n", (mm->tss.altitude_type == TSS_ALTITUDE_MCP ? "MCP" : "FMS"), mm->tss.altitude); printf(" Selected heading: %.1f\n", mm->intent.heading);
if (mm->tss.baro_valid) if (mm->intent.fms_altitude_valid)
printf(" Altimeter setting: %.1f millibars\n", mm->tss.baro); printf(" FMS selected altitude: %u ft\n", mm->intent.fms_altitude);
if (mm->tss.heading_valid) if (mm->intent.mcp_altitude_valid)
printf(" Target heading: %d\n", mm->tss.heading); printf(" MCP selected altitude: %u ft\n", mm->intent.mcp_altitude);
if (mm->tss.mode_valid) { if (mm->intent.alt_setting_valid)
printf(" Active modes: "); printf(" Altimeter setting: %.1f millibars\n", mm->intent.alt_setting);
if (mm->tss.mode_autopilot) printf("autopilot ");
if (mm->tss.mode_vnav) printf("VNAV "); if (mm->intent.altitude_source != TARGET_INVALID) {
if (mm->tss.mode_alt_hold) printf("altitude-hold "); printf(" Target altitude source: ");
if (mm->tss.mode_approach) printf("approach "); switch (mm->intent.altitude_source) {
case TARGET_AIRCRAFT:
printf("aircraft altitude\n");
break;
case TARGET_MCP:
printf("MCP selected altitude\n");
break;
case TARGET_FMS:
printf("FMS selected altitude\n");
break;
default:
printf("unknown\n");
}
}
if (mm->intent.mode_autopilot ||
mm->intent.mode_vnav ||
mm->intent.mode_alt_hold ||
mm->intent.mode_approach ||
mm->intent.mode_lnav) {
printf(" Active modes: ");
if (mm->intent.mode_autopilot) printf("autopilot ");
if (mm->intent.mode_vnav) printf("VNAV ");
if (mm->intent.mode_alt_hold) printf("altitude-hold ");
if (mm->intent.mode_approach) printf("approach ");
if (mm->intent.mode_lnav) printf("LNAV ");
printf("\n"); printf("\n");
} }
printf(" ACAS: %s\n", mm->tss.acas_operational ? "operational" : "NOT operational");
printf(" NACp: %d\n", mm->tss.nac_p);
printf(" NICbaro: %d\n", mm->tss.nic_baro);
printf(" SIL: %d (%s)\n", mm->tss.sil, (mm->opstatus.sil_type == SIL_PER_HOUR ? "per hour" : "per sample"));
} }
printf("\n"); printf("\n");

100
mode_s.h Normal file
View file

@ -0,0 +1,100 @@
// Part of dump1090, a Mode S message decoder for RTLSDR devices.
//
// mode_s.h: Mode S message decoding (prototypes)
//
// Copyright (c) 2017 FlightAware, LLC
// Copyright (c) 2017 Oliver Jowett <oliver@mutability.co.uk>
//
// This file is free software: you may copy, redistribute and/or modify it
// under the terms of the GNU General Public License as published by the
// Free Software Foundation, either version 2 of the License, or (at your
// option) any later version.
//
// This file is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
#ifndef MODE_S_H
#define MODE_S_H
#include <assert.h>
//
// Functions exported from mode_s.c
//
int modesMessageLenByType(int type);
int scoreModesMessage(unsigned char *msg, int validbits);
int decodeModesMessage (struct modesMessage *mm, unsigned char *msg);
void displayModesMessage(struct modesMessage *mm);
void useModesMessage (struct modesMessage *mm);
// datafield extraction helpers
// The first bit (MSB of the first byte) is numbered 1, for consistency
// with how the specs number them.
// Extract one bit from a message.
static inline __attribute__((always_inline)) unsigned getbit(unsigned char *data, unsigned bitnum)
{
unsigned bi = bitnum - 1;
unsigned by = bi >> 3;
unsigned mask = 1 << (7 - (bi & 7));
return (data[by] & mask) != 0;
}
// Extract some bits (firstbit .. lastbit inclusive) from a message.
static inline __attribute__((always_inline)) unsigned getbits(unsigned char *data, unsigned firstbit, unsigned lastbit)
{
unsigned fbi = firstbit - 1;
unsigned lbi = lastbit - 1;
unsigned nbi = (lastbit - firstbit + 1);
unsigned fby = fbi >> 3;
unsigned lby = lbi >> 3;
unsigned nby = (lby - fby) + 1;
unsigned shift = 7 - (lbi & 7);
unsigned topmask = 0xFF >> (fbi & 7);
assert (fbi <= lbi);
assert (nbi <= 32);
assert (nby <= 5);
if (nby == 5) {
return
((data[fby] & topmask) << (32 - shift)) |
(data[fby + 1] << (24 - shift)) |
(data[fby + 2] << (16 - shift)) |
(data[fby + 3] << (8 - shift)) |
(data[fby + 4] >> shift);
} else if (nby == 4) {
return
((data[fby] & topmask) << (24 - shift)) |
(data[fby + 1] << (16 - shift)) |
(data[fby + 2] << (8 - shift)) |
(data[fby + 3] >> shift);
} else if (nby == 3) {
return
((data[fby] & topmask) << (16 - shift)) |
(data[fby + 1] << (8 - shift)) |
(data[fby + 2] >> shift);
} else if (nby == 2) {
return
((data[fby] & topmask) << (8 - shift)) |
(data[fby + 1] >> shift);
} else if (nby == 1) {
return
(data[fby] & topmask) >> shift;
} else {
return 0;
}
}
extern char ais_charset[64];
#endif

317
net_io.c
View file

@ -582,18 +582,18 @@ static void modesSendSBSOutput(struct modesMessage *mm, struct aircraft *a) {
// Field 12 is the altitude (if we have it) // Field 12 is the altitude (if we have it)
if (mm->altitude_valid) { if (mm->altitude_valid) {
if (Modes.use_gnss) { if (Modes.use_gnss) {
if (mm->altitude_source == ALTITUDE_GNSS) { if (mm->altitude_source == ALTITUDE_GEOM) {
p += sprintf(p, ",%dH", mm->altitude); p += sprintf(p, ",%dH", mm->altitude);
} else if (trackDataValid(&a->gnss_delta_valid)) { } else if (trackDataValid(&a->geom_delta_valid)) {
p += sprintf(p, ",%dH", mm->altitude + a->gnss_delta); p += sprintf(p, ",%dH", mm->altitude + a->geom_delta);
} else { } else {
p += sprintf(p, ",%d", mm->altitude); p += sprintf(p, ",%d", mm->altitude);
} }
} else { } else {
if (mm->altitude_source == ALTITUDE_BARO) { if (mm->altitude_source == ALTITUDE_BARO) {
p += sprintf(p, ",%d", mm->altitude); p += sprintf(p, ",%d", mm->altitude);
} else if (trackDataValid(&a->gnss_delta_valid)) { } else if (trackDataValid(&a->geom_delta_valid)) {
p += sprintf(p, ",%d", mm->altitude - a->gnss_delta); p += sprintf(p, ",%d", mm->altitude - a->geom_delta);
} else { } else {
p += sprintf(p, ","); p += sprintf(p, ",");
} }
@ -603,15 +603,15 @@ static void modesSendSBSOutput(struct modesMessage *mm, struct aircraft *a) {
} }
// Field 13 is the ground Speed (if we have it) // Field 13 is the ground Speed (if we have it)
if (mm->speed_valid && mm->speed_source == SPEED_GROUNDSPEED) { if (mm->gs_valid) {
p += sprintf(p, ",%d", mm->speed); p += sprintf(p, ",%d", mm->gs);
} else { } else {
p += sprintf(p, ","); p += sprintf(p, ",");
} }
// Field 14 is the ground Heading (if we have it) // Field 14 is the ground Heading (if we have it)
if (mm->heading_valid && mm->heading_source == HEADING_TRUE) { if (mm->track_valid) {
p += sprintf(p, ",%d", mm->heading); p += sprintf(p, ",%.0f", mm->track);
} else { } else {
p += sprintf(p, ","); p += sprintf(p, ",");
} }
@ -624,10 +624,22 @@ static void modesSendSBSOutput(struct modesMessage *mm, struct aircraft *a) {
} }
// Field 17 is the VerticalRate (if we have it) // Field 17 is the VerticalRate (if we have it)
if (mm->vert_rate_valid) { if (Modes.use_gnss) {
p += sprintf(p, ",%d", mm->vert_rate); if (mm->geom_rate_valid) {
p += sprintf(p, ",%dH", mm->geom_rate);
} else if (mm->baro_rate_valid) {
p += sprintf(p, ",%d", mm->baro_rate);
} else {
p += sprintf(p, ",");
}
} else { } else {
p += sprintf(p, ","); if (mm->baro_rate_valid) {
p += sprintf(p, ",%d", mm->baro_rate);
} else if (mm->geom_rate_valid) {
p += sprintf(p, ",%d", mm->geom_rate);
} else {
p += sprintf(p, ",");
}
} }
// Field 18 is the Squawk (if we have it) // Field 18 is the Squawk (if we have it)
@ -1099,12 +1111,14 @@ static char *append_flags(char *p, char *end, struct aircraft *a, datasource_t s
p += snprintf(p, end-p, "\"lat\",\"lon\","); p += snprintf(p, end-p, "\"lat\",\"lon\",");
if (a->altitude_valid.source == source) if (a->altitude_valid.source == source)
p += snprintf(p, end-p, "\"altitude\","); p += snprintf(p, end-p, "\"altitude\",");
if (a->heading_valid.source == source) if (a->track_valid.source == source)
p += snprintf(p, end-p, "\"track\","); p += snprintf(p, end-p, "\"track\",");
if (a->speed_valid.source == source) if (a->gs_valid.source == source)
p += snprintf(p, end-p, "\"speed\","); p += snprintf(p, end-p, "\"speed\",");
if (a->vert_rate_valid.source == source) if (a->baro_rate_valid.source == source)
p += snprintf(p, end-p, "\"vert_rate\","); 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) if (a->category_valid.source == source)
p += snprintf(p, end-p, "\"category\","); p += snprintf(p, end-p, "\"category\",");
if (p[-1] != '[') if (p[-1] != '[')
@ -1173,14 +1187,30 @@ char *generateAircraftJson(const char *url_path, int *len) {
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,\"nucp\":%u,\"seen_pos\":%.1f", a->lat, a->lon, a->pos_nuc, (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 if (trackDataValid(&a->altitude_valid)) else {
p += snprintf(p, end-p, ",\"altitude\":%d", a->altitude); if (trackDataValid(&a->altitude_valid))
if (trackDataValid(&a->vert_rate_valid)) p += snprintf(p, end-p, ",\"altitude\":%d", a->altitude);
p += snprintf(p, end-p, ",\"vert_rate\":%d", a->vert_rate); if (trackDataValid(&a->altitude_geom_valid))
if (trackDataValid(&a->heading_valid)) p += snprintf(p, end-p, ",\"altitude_geom\":%d", a->altitude_geom);
p += snprintf(p, end-p, ",\"track\":%d", a->heading); }
if (trackDataValid(&a->speed_valid)) if (trackDataValid(&a->baro_rate_valid))
p += snprintf(p, end-p, ",\"speed\":%d", a->speed); p += snprintf(p, end-p, ",\"baro_rate\":%d", a->baro_rate);
if (trackDataValid(&a->geom_rate_valid))
p += snprintf(p, end-p, ",\"geom_rate\":%d", a->geom_rate);
if (trackDataValid(&a->track_valid))
p += snprintf(p, end-p, ",\"track\":%.1f", a->track);
if (trackDataValid(&a->track_rate_valid))
p += snprintf(p, end-p, ",\"track_rate\":%.2f", a->track_rate);
if (trackDataValid(&a->gs_valid))
p += snprintf(p, end-p, ",\"gs\":%u", a->gs);
if (trackDataValid(&a->ias_valid))
p += snprintf(p, end-p, ",\"ias\":%u", a->ias);
if (trackDataValid(&a->tas_valid))
p += snprintf(p, end-p, ",\"tas\":%u", a->tas);
if (trackDataValid(&a->mach_valid))
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 (trackDataValid(&a->category_valid))
p += snprintf(p, end-p, ",\"category\":\"%02X\"", a->category); p += snprintf(p, end-p, ",\"category\":\"%02X\"", a->category);
@ -1662,7 +1692,7 @@ static void modesReadFromClient(struct client *c) {
} }
} }
#define TSV_MAX_PACKET_SIZE 275 #define TSV_MAX_PACKET_SIZE 400
static void writeFATSVPositionUpdate(float lat, float lon, float alt) static void writeFATSVPositionUpdate(float lat, float lon, float alt)
{ {
@ -1747,23 +1777,28 @@ static void writeFATSVEvent(struct modesMessage *mm, struct aircraft *a)
switch (mm->msgtype) { switch (mm->msgtype) {
case 20: case 20:
case 21: case 21:
if (mm->correctedbits > 0)
break; // only messages we trust a little more
// DF 20/21: Comm-B: emit if they've changed since we last sent them // DF 20/21: Comm-B: emit if they've changed since we last sent them
// switch (mm->commb_format) {
// BDS 1,0: data link capability report case COMMB_DATALINK_CAPS:
// BDS 3,0: ACAS RA report // BDS 1,0: data link capability report
if (mm->MB[0] == 0x10 && memcmp(mm->MB, a->fatsv_emitted_bds_10, 7) != 0) { if (memcmp(mm->MB, a->fatsv_emitted_bds_10, 7) != 0) {
memcpy(a->fatsv_emitted_bds_10, mm->MB, 7); memcpy(a->fatsv_emitted_bds_10, mm->MB, 7);
writeFATSVEventMessage(mm, "datalink_caps", mm->MB, 7); writeFATSVEventMessage(mm, "datalink_caps", mm->MB, 7);
} }
break;
else if (mm->MB[0] == 0x30 && memcmp(mm->MB, a->fatsv_emitted_bds_30, 7) != 0) { case COMMB_ACAS_RA:
memcpy(a->fatsv_emitted_bds_30, mm->MB, 7); // BDS 3,0: ACAS RA report
writeFATSVEventMessage(mm, "commb_acas_ra", mm->MB, 7); if (memcmp(mm->MB, a->fatsv_emitted_bds_30, 7) != 0) {
} memcpy(a->fatsv_emitted_bds_30, mm->MB, 7);
writeFATSVEventMessage(mm, "commb_acas_ra", mm->MB, 7);
}
break;
default:
// nothing
break;
}
break; break;
case 17: case 17:
@ -1778,10 +1813,6 @@ static void writeFATSVEvent(struct modesMessage *mm, struct aircraft *a)
// aircraft operational status // aircraft operational status
memcpy(a->fatsv_emitted_es_status, mm->ME, 7); memcpy(a->fatsv_emitted_es_status, mm->ME, 7);
writeFATSVEventMessage(mm, "es_op_status", mm->ME, 7); writeFATSVEventMessage(mm, "es_op_status", mm->ME, 7);
} else if (mm->metype == 29 && (mm->mesub == 0 || mm->mesub == 1) && memcmp(mm->ME, a->fatsv_emitted_es_target, 7) != 0) {
// target state and status
memcpy(a->fatsv_emitted_es_target, mm->ME, 7);
writeFATSVEventMessage(mm, "es_target", mm->ME, 7);
} }
break; break;
} }
@ -1791,13 +1822,13 @@ typedef enum {
TISB_IDENT = 1, TISB_IDENT = 1,
TISB_SQUAWK = 2, TISB_SQUAWK = 2,
TISB_ALTITUDE = 4, TISB_ALTITUDE = 4,
TISB_ALTITUDE_GNSS = 8, TISB_ALTITUDE_GEOM = 8,
TISB_SPEED = 16, TISB_GS = 16,
TISB_SPEED_IAS = 32, TISB_IAS = 32,
TISB_SPEED_TAS = 64, TISB_TAS = 64,
TISB_POSITION = 128, TISB_POSITION = 128,
TISB_HEADING = 256, TISB_TRACK = 256,
TISB_HEADING_MAGNETIC = 512, TISB_MAG_HEADING = 512,
TISB_AIRGROUND = 1024, TISB_AIRGROUND = 1024,
TISB_CATEGORY = 2048 TISB_CATEGORY = 2048
} tisb_flags; } tisb_flags;
@ -1807,9 +1838,9 @@ static inline unsigned unsigned_difference(unsigned v1, unsigned v2)
return (v1 > v2) ? (v1 - v2) : (v2 - v1); return (v1 > v2) ? (v1 - v2) : (v2 - v1);
} }
static inline unsigned heading_difference(unsigned h1, unsigned h2) static inline float heading_difference(float h1, float h2)
{ {
unsigned d = unsigned_difference(h1, h2); float d = fabs(h1 - h2);
return (d < 180) ? d : (360 - d); return (d < 180) ? d : (360 - d);
} }
@ -1832,17 +1863,6 @@ static void writeFATSV()
next_update = now + 1000; next_update = now + 1000;
for (a = Modes.aircrafts; a; a = a->next) { for (a = Modes.aircrafts; a; a = a->next) {
int altValid = 0;
int altGNSSValid = 0;
int positionValid = 0;
int speedValid = 0;
int speedIASValid = 0;
int speedTASValid = 0;
int headingValid = 0;
int headingMagValid = 0;
int airgroundValid = 0;
int categoryValid = 0;
uint64_t minAge; uint64_t minAge;
int useful = 0; int useful = 0;
@ -1859,16 +1879,25 @@ static void writeFATSV()
continue; continue;
} }
altValid = trackDataValidEx(&a->altitude_valid, now, 15000, SOURCE_MODE_S); // for non-ADS-B transponders, DF0/4/16/20 are the only sources of altitude data int altValid = trackDataValidEx(&a->altitude_valid, now, 15000, SOURCE_MODE_S); // for non-ADS-B transponders, DF0/4/16/20 are the only sources of altitude data
altGNSSValid = trackDataValidEx(&a->altitude_gnss_valid, now, 15000, SOURCE_MODE_S_CHECKED); int altGeomValid = trackDataValidEx(&a->altitude_geom_valid, now, 15000, SOURCE_MODE_S_CHECKED);
airgroundValid = trackDataValidEx(&a->airground_valid, now, 15000, SOURCE_MODE_S_CHECKED); // for non-ADS-B transponders, only trust DF11 CA field int airgroundValid = trackDataValidEx(&a->airground_valid, now, 15000, SOURCE_MODE_S_CHECKED); // for non-ADS-B transponders, only trust DF11 CA field
positionValid = trackDataValidEx(&a->position_valid, now, 15000, SOURCE_MODE_S_CHECKED); int baroRateValid = trackDataValidEx(&a->baro_rate_valid, now, 15000, SOURCE_MODE_S); // Comm-B or ES
headingValid = trackDataValidEx(&a->heading_valid, now, 15000, SOURCE_MODE_S_CHECKED); int geomRateValid = trackDataValidEx(&a->geom_rate_valid, now, 15000, SOURCE_MODE_S); // Comm-B or ES
headingMagValid = trackDataValidEx(&a->heading_magnetic_valid, now, 15000, SOURCE_MODE_S_CHECKED); int positionValid = trackDataValidEx(&a->position_valid, now, 15000, SOURCE_MODE_S_CHECKED);
speedValid = trackDataValidEx(&a->speed_valid, now, 15000, SOURCE_MODE_S_CHECKED); int trackValid = trackDataValidEx(&a->track_valid, now, 15000, SOURCE_MODE_S); // Comm-B or ES
speedIASValid = trackDataValidEx(&a->speed_ias_valid, now, 15000, SOURCE_MODE_S_CHECKED); int trackRateValid = trackDataValidEx(&a->track_rate_valid, now, 15000, SOURCE_MODE_S); // Comm-B
speedTASValid = trackDataValidEx(&a->speed_tas_valid, now, 15000, SOURCE_MODE_S_CHECKED); int rollValid = trackDataValidEx(&a->roll_valid, now, 15000, SOURCE_MODE_S); // Comm-B
categoryValid = trackDataValidEx(&a->category_valid, now, 15000, SOURCE_MODE_S_CHECKED); int magHeadingValid = trackDataValidEx(&a->mag_heading_valid, now, 15000, SOURCE_MODE_S); // Comm-B or ES
int gsValid = trackDataValidEx(&a->gs_valid, now, 15000, SOURCE_MODE_S); // Comm-B or ES
int iasValid = trackDataValidEx(&a->ias_valid, now, 15000, SOURCE_MODE_S); // Comm-B or ES
int tasValid = trackDataValidEx(&a->tas_valid, now, 15000, SOURCE_MODE_S); // Comm-B or ES
int machValid = trackDataValidEx(&a->mach_valid, now, 15000, SOURCE_MODE_S); // Comm-B
int categoryValid = trackDataValidEx(&a->category_valid, now, 15000, SOURCE_MODE_S_CHECKED);
int intentAltValid = trackDataValidEx(&a->intent_altitude_valid, now, 15000, SOURCE_MODE_S); // Comm-B or ES
int intentHeadingValid = trackDataValidEx(&a->intent_heading_valid, now, 15000, SOURCE_MODE_S); // Comm-B or ES
int altSettingValid = trackDataValidEx(&a->alt_setting_valid, now, 15000, SOURCE_MODE_S); // Comm-B or ES
int callsignValid = trackDataValidEx(&a->callsign, now, 15000, SOURCE_MODE_S); // Comm-B or ES
// If we are definitely on the ground, suppress any unreliable altitude info. // If we are definitely on the ground, suppress any unreliable altitude info.
// 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
@ -1883,22 +1912,49 @@ static void writeFATSV()
if (altValid && abs(a->altitude - a->fatsv_emitted_altitude) >= 50) { if (altValid && abs(a->altitude - a->fatsv_emitted_altitude) >= 50) {
changed = 1; changed = 1;
} }
if (altGNSSValid && abs(a->altitude_gnss - a->fatsv_emitted_altitude_gnss) >= 50) { if (altGeomValid && abs(a->altitude_geom - a->fatsv_emitted_altitude_gnss) >= 50) {
changed = 1; changed = 1;
} }
if (headingValid && heading_difference(a->heading, a->fatsv_emitted_heading) >= 2) { if (baroRateValid && abs(a->baro_rate - a->fatsv_emitted_baro_rate) > 500) {
changed = 1; changed = 1;
} }
if (headingMagValid && heading_difference(a->heading_magnetic, a->fatsv_emitted_heading_magnetic) >= 2) { if (geomRateValid && abs(a->geom_rate - a->fatsv_emitted_geom_rate) > 500) {
changed = 1; changed = 1;
} }
if (speedValid && unsigned_difference(a->speed, a->fatsv_emitted_speed) >= 25) { if (trackValid && heading_difference(a->track, a->fatsv_emitted_heading) >= 2) {
changed = 1; changed = 1;
} }
if (speedIASValid && unsigned_difference(a->speed_ias, a->fatsv_emitted_speed_ias) >= 25) { if (trackRateValid && fabs(a->track_rate - a->fatsv_emitted_track_rate) >= 0.5) {
changed = 1; changed = 1;
} }
if (speedTASValid && unsigned_difference(a->speed_tas, a->fatsv_emitted_speed_tas) >= 25) { if (rollValid && fabs(a->roll - a->fatsv_emitted_roll) >= 5.0) {
changed = 1;
}
if (magHeadingValid && heading_difference(a->mag_heading, a->fatsv_emitted_heading_magnetic) >= 2) {
changed = 1;
}
if (gsValid && unsigned_difference(a->gs, a->fatsv_emitted_speed) >= 25) {
changed = 1;
}
if (iasValid && unsigned_difference(a->ias, a->fatsv_emitted_speed_ias) >= 25) {
changed = 1;
}
if (tasValid && unsigned_difference(a->tas, a->fatsv_emitted_speed_tas) >= 25) {
changed = 1;
}
if (machValid && fabs(a->mach - a->fatsv_emitted_mach) >= 0.02) {
changed = 1;
}
if (intentAltValid && unsigned_difference(a->intent_altitude, a->fatsv_emitted_intent_altitude) > 50) {
changed = 1;
}
if (intentHeadingValid && heading_difference(a->intent_heading, a->fatsv_emitted_intent_heading) > 2) {
changed = 1;
}
if (altSettingValid && fabs(a->alt_setting - a->fatsv_emitted_alt_setting) > 0.8) { // 0.8 is the ES message resolution
changed = 1;
}
if (callsignValid && strcmp(a->callsign, a->fatsv_emitted_callsign) != 0) {
changed = 1; changed = 1;
} }
@ -1910,8 +1966,8 @@ 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 && (!speedValid || a->speed < 200)) || (altValid && a->altitude < 500 && (!gsValid || a->gs < 200)) ||
(speedValid && a->speed < 100 && (!altValid || a->altitude < 1000))) { (gsValid && a->gs < 100 && (!altValid || a->altitude < 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 < 10000) {
@ -1944,7 +2000,7 @@ static void writeFATSV()
p += snprintf(p, bufsize(p, end), "\taddrtype\t%s", addrtype_short_string(a->addrtype)); p += snprintf(p, bufsize(p, end), "\taddrtype\t%s", addrtype_short_string(a->addrtype));
} }
if (trackDataValidEx(&a->callsign_valid, now, 35000, SOURCE_MODE_S_CHECKED) && strcmp(a->callsign, " ") != 0 && a->callsign_valid.updated > a->fatsv_last_emitted) { if (trackDataValidEx(&a->callsign_valid, now, 35000, SOURCE_MODE_S) && strcmp(a->callsign, " ") != 0 && a->callsign_valid.updated > a->fatsv_last_emitted) {
p += snprintf(p, bufsize(p,end), "\tident\t%s", a->callsign); p += snprintf(p, bufsize(p,end), "\tident\t%s", a->callsign);
switch (a->callsign_valid.source) { switch (a->callsign_valid.source) {
case SOURCE_MODE_S: case SOURCE_MODE_S:
@ -1971,7 +2027,7 @@ static void writeFATSV()
tisb |= (a->squawk_valid.source == SOURCE_TISB) ? TISB_SQUAWK : 0; tisb |= (a->squawk_valid.source == SOURCE_TISB) ? TISB_SQUAWK : 0;
} }
// only emit alt, speed, latlon, track 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
if (altValid && a->altitude_valid.updated > a->fatsv_last_emitted) { if (altValid && a->altitude_valid.updated > a->fatsv_last_emitted) {
@ -1981,32 +2037,50 @@ static void writeFATSV()
tisb |= (a->altitude_valid.source == SOURCE_TISB) ? TISB_ALTITUDE : 0; tisb |= (a->altitude_valid.source == SOURCE_TISB) ? TISB_ALTITUDE : 0;
} }
if (altGNSSValid && a->altitude_gnss_valid.updated > a->fatsv_last_emitted) { if (altGeomValid && a->altitude_geom_valid.updated > a->fatsv_last_emitted) {
p += snprintf(p, bufsize(p,end), "\talt_gnss\t%d", a->altitude_gnss); p += snprintf(p, bufsize(p,end), "\talt_geom\t%d", a->altitude_geom);
a->fatsv_emitted_altitude_gnss = a->altitude_gnss; a->fatsv_emitted_altitude_gnss = a->altitude_geom;
useful = 1; useful = 1;
tisb |= (a->altitude_gnss_valid.source == SOURCE_TISB) ? TISB_ALTITUDE_GNSS : 0; tisb |= (a->altitude_geom_valid.source == SOURCE_TISB) ? TISB_ALTITUDE_GEOM : 0;
} }
if (speedValid && a->speed_valid.updated > a->fatsv_last_emitted) { if (baroRateValid && a->baro_rate_valid.updated > a->fatsv_last_emitted) {
p += snprintf(p, bufsize(p,end), "\tspeed\t%d", a->speed); p += snprintf(p, bufsize(p,end), "\tbaro_rate\t%d", a->baro_rate);
a->fatsv_emitted_speed = a->speed; a->fatsv_emitted_baro_rate = a->baro_rate;
useful = 1; useful = 1;
tisb |= (a->speed_valid.source == SOURCE_TISB) ? TISB_SPEED : 0;
} }
if (speedIASValid && a->speed_ias_valid.updated > a->fatsv_last_emitted) { if (geomRateValid && a->geom_rate_valid.updated > a->fatsv_last_emitted) {
p += snprintf(p, bufsize(p,end), "\tspeed_ias\t%d", a->speed_ias); p += snprintf(p, bufsize(p,end), "\tgeom_rate\t%d", a->geom_rate);
a->fatsv_emitted_speed_ias = a->speed_ias; a->fatsv_emitted_geom_rate = a->geom_rate;
useful = 1; useful = 1;
tisb |= (a->speed_ias_valid.source == SOURCE_TISB) ? TISB_SPEED_IAS : 0;
} }
if (speedTASValid && a->speed_tas_valid.updated > a->fatsv_last_emitted) { if (gsValid && a->gs_valid.updated > a->fatsv_last_emitted) {
p += snprintf(p, bufsize(p,end), "\tspeed_tas\t%d", a->speed_tas); p += snprintf(p, bufsize(p,end), "\tgs\t%u", a->gs);
a->fatsv_emitted_speed_tas = a->speed_tas; a->fatsv_emitted_speed = a->gs;
useful = 1;
tisb |= (a->gs_valid.source == SOURCE_TISB) ? TISB_GS : 0;
}
if (iasValid && a->ias_valid.updated > a->fatsv_last_emitted) {
p += snprintf(p, bufsize(p,end), "\tias\t%u", a->ias);
a->fatsv_emitted_speed_ias = a->ias;
useful = 1;
tisb |= (a->ias_valid.source == SOURCE_TISB) ? TISB_IAS : 0;
}
if (tasValid && a->tas_valid.updated > a->fatsv_last_emitted) {
p += snprintf(p, bufsize(p,end), "\ttas\t%u", a->tas);
a->fatsv_emitted_speed_tas = a->tas;
useful = 1;
tisb |= (a->tas_valid.source == SOURCE_TISB) ? TISB_TAS : 0;
}
if (machValid && a->mach_valid.updated > a->fatsv_last_emitted) {
p += snprintf(p, bufsize(p,end), "\tmach\t%.3f", a->mach);
a->fatsv_emitted_mach = a->mach;
useful = 1; useful = 1;
tisb |= (a->speed_tas_valid.source == SOURCE_TISB) ? TISB_SPEED_TAS : 0;
} }
if (positionValid && a->position_valid.updated > a->fatsv_last_emitted) { if (positionValid && a->position_valid.updated > a->fatsv_last_emitted) {
@ -2015,18 +2089,30 @@ static void writeFATSV()
tisb |= (a->position_valid.source == SOURCE_TISB) ? TISB_POSITION : 0; tisb |= (a->position_valid.source == SOURCE_TISB) ? TISB_POSITION : 0;
} }
if (headingValid && a->heading_valid.updated > a->fatsv_last_emitted) { if (trackValid && a->track_valid.updated > a->fatsv_last_emitted) {
p += snprintf(p, bufsize(p,end), "\theading\t%d", a->heading); p += snprintf(p, bufsize(p,end), "\ttrack\t%.0f", a->track);
a->fatsv_emitted_heading = a->heading; a->fatsv_emitted_heading = a->track;
useful = 1; useful = 1;
tisb |= (a->heading_valid.source == SOURCE_TISB) ? TISB_HEADING : 0; tisb |= (a->track_valid.source == SOURCE_TISB) ? TISB_TRACK : 0;
} }
if (headingMagValid && a->heading_magnetic_valid.updated > a->fatsv_last_emitted) { if (trackRateValid && a->track_rate_valid.updated > a->fatsv_last_emitted) {
p += snprintf(p, bufsize(p,end), "\theading_magnetic\t%d", a->heading_magnetic); p += snprintf(p, bufsize(p,end), "\ttrack_rate\t%.2f", a->track_rate);
a->fatsv_emitted_heading_magnetic = a->heading_magnetic; a->fatsv_emitted_track_rate = a->track_rate;
useful = 1; useful = 1;
tisb |= (a->heading_magnetic_valid.source == SOURCE_TISB) ? TISB_HEADING_MAGNETIC : 0; }
if (rollValid && a->roll_valid.updated > a->fatsv_last_emitted) {
p += snprintf(p, bufsize(p,end), "\troll\t%.1f", a->roll);
a->fatsv_emitted_roll = a->roll;
useful = 1;
}
if (magHeadingValid && a->mag_heading_valid.updated > a->fatsv_last_emitted) {
p += snprintf(p, bufsize(p,end), "\tmag_heading\t%.0f", a->mag_heading);
a->fatsv_emitted_heading_magnetic = a->mag_heading;
useful = 1;
tisb |= (a->mag_heading_valid.source == SOURCE_TISB) ? TISB_MAG_HEADING : 0;
} }
if (airgroundValid && (a->airground == AG_GROUND || a->airground == AG_AIRBORNE) && a->airground_valid.updated > a->fatsv_last_emitted) { if (airgroundValid && (a->airground == AG_GROUND || a->airground == AG_AIRBORNE) && a->airground_valid.updated > a->fatsv_last_emitted) {
@ -2043,6 +2129,25 @@ static void writeFATSV()
tisb |= (a->category_valid.source == SOURCE_TISB) ? TISB_CATEGORY : 0; tisb |= (a->category_valid.source == SOURCE_TISB) ? TISB_CATEGORY : 0;
} }
if (intentAltValid && a->intent_altitude_valid.updated > a->fatsv_last_emitted) {
p += snprintf(p, bufsize(p,end), "\tintent_alt\t%u", a->intent_altitude);
a->fatsv_emitted_intent_altitude = a->intent_altitude;
useful = 1;
}
if (intentHeadingValid && a->intent_heading_valid.updated > a->fatsv_last_emitted) {
p += snprintf(p, bufsize(p,end), "\tintent_heading\t%.0f", a->intent_heading);
a->fatsv_emitted_intent_heading = a->intent_heading;
useful = 1;
}
if (altSettingValid && a->alt_setting_valid.updated > a->fatsv_last_emitted) {
p += snprintf(p, bufsize(p,end), "\talt_setting\t%.1f", a->alt_setting);
a->fatsv_emitted_alt_setting = a->alt_setting;
useful = 1;
}
// 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().
if (!useful) { if (!useful) {

114
track.c
View file

@ -209,12 +209,12 @@ static int speed_check(struct aircraft *a, double lat, double lon, uint64_t now,
elapsed = trackDataAge(&a->position_valid, now); elapsed = trackDataAge(&a->position_valid, now);
if (trackDataValid(&a->speed_valid)) if (trackDataValid(&a->gs_valid))
speed = a->speed; speed = a->gs;
else if (trackDataValid(&a->speed_ias_valid)) else if (trackDataValid(&a->tas_valid))
speed = a->speed_ias * 4 / 3; speed = a->tas * 4 / 3;
else if (trackDataValid(&a->speed_tas_valid)) else if (trackDataValid(&a->ias_valid))
speed = a->speed_tas * 4 / 3; speed = a->ias * 2;
else else
speed = surface ? 100 : 600; // guess speed = surface ? 100 : 600; // guess
@ -427,7 +427,7 @@ static void updatePosition(struct aircraft *a, struct modesMessage *mm, uint64_t
++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->speed_valid && mm->speed <= 25) if (mm->gs_valid && mm->gs <= 25)
max_elapsed = 50000; max_elapsed = 50000;
else else
max_elapsed = 25000; max_elapsed = 25000;
@ -562,37 +562,52 @@ struct aircraft *trackUpdateFromMessage(struct modesMessage *mm)
a->squawk = mm->squawk; a->squawk = mm->squawk;
} }
if (mm->altitude_valid && mm->altitude_source == ALTITUDE_GNSS && accept_data(&a->altitude_gnss_valid, mm->source, now)) { if (mm->altitude_valid && mm->altitude_source == ALTITUDE_GEOM && accept_data(&a->altitude_geom_valid, mm->source, now)) {
a->altitude_gnss = mm->altitude; a->altitude_geom = mm->altitude;
} }
if (mm->gnss_delta_valid && accept_data(&a->gnss_delta_valid, mm->source, now)) { if (mm->geom_delta_valid && accept_data(&a->geom_delta_valid, mm->source, now)) {
a->gnss_delta = mm->gnss_delta; a->geom_delta = mm->geom_delta;
} }
if (mm->heading_valid && mm->heading_source == HEADING_TRUE && accept_data(&a->heading_valid, mm->source, now)) { if (mm->track_valid && accept_data(&a->track_valid, mm->source, now)) {
a->heading = mm->heading; a->track = mm->track;
} }
if (mm->heading_valid && mm->heading_source == HEADING_MAGNETIC && accept_data(&a->heading_magnetic_valid, mm->source, now)) { if (mm->track_rate_valid && accept_data(&a->track_rate_valid, mm->source, now)) {
a->heading_magnetic = mm->heading; a->track_rate = mm->track_rate;
} }
if (mm->speed_valid && mm->speed_source == SPEED_GROUNDSPEED && accept_data(&a->speed_valid, mm->source, now)) { if (mm->roll_valid && accept_data(&a->roll_valid, mm->source, now)) {
a->speed = mm->speed; a->roll = mm->roll;
} }
if (mm->speed_valid && mm->speed_source == SPEED_IAS && accept_data(&a->speed_ias_valid, mm->source, now)) { if (mm->mag_heading_valid && accept_data(&a->mag_heading_valid, mm->source, now)) {
a->speed_ias = mm->speed; a->mag_heading = mm->mag_heading;
} }
if (mm->speed_valid && mm->speed_source == SPEED_TAS && accept_data(&a->speed_tas_valid, mm->source, now)) { if (mm->gs_valid && accept_data(&a->gs_valid, mm->source, now)) {
a->speed_tas = mm->speed; a->gs = mm->gs;
} }
if (mm->vert_rate_valid && accept_data(&a->vert_rate_valid, mm->source, now)) { if (mm->ias_valid && accept_data(&a->ias_valid, mm->source, now)) {
a->vert_rate = mm->vert_rate; a->ias = mm->ias;
a->vert_rate_source = mm->vert_rate_source; }
if (mm->tas_valid && accept_data(&a->tas_valid, mm->source, now)) {
a->tas = mm->tas;
}
if (mm->mach_valid && accept_data(&a->mach_valid, mm->source, now)) {
a->mach = mm->mach;
}
if (mm->baro_rate_valid && accept_data(&a->baro_rate_valid, mm->source, now)) {
a->baro_rate = mm->baro_rate;
}
if (mm->geom_rate_valid && accept_data(&a->geom_rate_valid, mm->source, now)) {
a->geom_rate = mm->geom_rate;
} }
if (mm->category_valid && accept_data(&a->category_valid, mm->source, now)) { if (mm->category_valid && accept_data(&a->category_valid, mm->source, now)) {
@ -607,6 +622,22 @@ struct aircraft *trackUpdateFromMessage(struct modesMessage *mm)
memcpy(a->callsign, mm->callsign, sizeof(a->callsign)); memcpy(a->callsign, mm->callsign, sizeof(a->callsign));
} }
if (mm->intent.valid) {
if (mm->intent.mcp_altitude_valid && accept_data(&a->intent_altitude_valid, mm->source, now)) {
a->intent_altitude = mm->intent.mcp_altitude;
} else if (mm->intent.fms_altitude_valid && accept_data(&a->intent_altitude_valid, mm->source, now)) {
a->intent_altitude = mm->intent.fms_altitude;
}
if (mm->intent.heading_valid && accept_data(&a->intent_heading_valid, mm->source, now)) {
a->intent_heading = mm->intent.heading;
}
if (mm->intent.alt_setting_valid && accept_data(&a->alt_setting_valid, mm->source, now)) {
a->alt_setting = mm->intent.alt_setting;
}
}
// CPR, even // CPR, even
if (mm->cpr_valid && !mm->cpr_odd && accept_data(&a->cpr_even_valid, mm->source, now)) { if (mm->cpr_valid && !mm->cpr_odd && accept_data(&a->cpr_even_valid, mm->source, now)) {
a->cpr_even_type = mm->cpr_type; a->cpr_even_type = mm->cpr_type;
@ -625,12 +656,12 @@ struct aircraft *trackUpdateFromMessage(struct modesMessage *mm)
// Now handle derived data // Now handle derived data
// derive GNSS if we have baro + delta // derive geometric altitude if we have baro + delta
if (compare_validity(&a->altitude_valid, &a->altitude_gnss_valid, now) > 0 && if (compare_validity(&a->altitude_valid, &a->altitude_geom_valid, now) > 0 &&
compare_validity(&a->gnss_delta_valid, &a->altitude_gnss_valid, now) > 0) { compare_validity(&a->geom_delta_valid, &a->altitude_geom_valid, now) > 0) {
// Baro and delta are both more recent than GNSS, derive GNSS from baro + delta // Baro and delta are both more recent than geometric, derive geometric from baro + delta
a->altitude_gnss = a->altitude + a->gnss_delta; a->altitude_geom = a->altitude + a->geom_delta;
combine_validity(&a->altitude_gnss_valid, &a->altitude_valid, &a->gnss_delta_valid); combine_validity(&a->altitude_geom_valid, &a->altitude_valid, &a->geom_delta_valid);
} }
// If we've got a new cprlat or cprlon // If we've got a new cprlat or cprlon
@ -752,17 +783,24 @@ 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);
EXPIRE(altitude_gnss); EXPIRE(altitude_geom);
EXPIRE(gnss_delta); EXPIRE(geom_delta);
EXPIRE(speed); EXPIRE(gs);
EXPIRE(speed_ias); EXPIRE(ias);
EXPIRE(speed_tas); EXPIRE(tas);
EXPIRE(heading); EXPIRE(mach);
EXPIRE(heading_magnetic); EXPIRE(track);
EXPIRE(vert_rate); EXPIRE(track_rate);
EXPIRE(roll);
EXPIRE(mag_heading);
EXPIRE(baro_rate);
EXPIRE(geom_rate);
EXPIRE(squawk); EXPIRE(squawk);
EXPIRE(category); EXPIRE(category);
EXPIRE(airground); EXPIRE(airground);
EXPIRE(alt_setting);
EXPIRE(intent_altitude);
EXPIRE(intent_heading);
EXPIRE(cpr_odd); EXPIRE(cpr_odd);
EXPIRE(cpr_even); EXPIRE(cpr_even);
EXPIRE(position); EXPIRE(position);

73
track.h
View file

@ -88,30 +88,41 @@ struct aircraft {
data_validity altitude_valid; data_validity altitude_valid;
int altitude; // Altitude (Baro) int altitude; // Altitude (Baro)
data_validity altitude_gnss_valid; data_validity altitude_geom_valid;
int altitude_gnss; // Altitude (GNSS) int altitude_geom; // Altitude (Geometric)
data_validity gnss_delta_valid; data_validity geom_delta_valid;
int gnss_delta; // Difference between GNSS and Baro altitudes int geom_delta; // Difference between Geometric and Baro altitudes
data_validity speed_valid; data_validity gs_valid;
unsigned speed; unsigned gs;
data_validity speed_ias_valid; data_validity ias_valid;
unsigned speed_ias; unsigned ias;
data_validity speed_tas_valid; data_validity tas_valid;
unsigned speed_tas; unsigned tas;
data_validity heading_valid; data_validity mach_valid;
unsigned heading; // Heading (OK it's really the track) float mach;
data_validity heading_magnetic_valid; data_validity track_valid;
unsigned heading_magnetic; // Heading float track; // Ground track
data_validity vert_rate_valid; data_validity track_rate_valid;
int vert_rate; // Vertical rate float track_rate; // Rate of change of ground track, degrees/second
altitude_source_t vert_rate_source;
data_validity roll_valid;
float roll; // Roll angle, degrees right
data_validity mag_heading_valid;
float mag_heading; // Magnetic heading
data_validity baro_rate_valid;
int baro_rate; // Vertical rate (barometric)
data_validity geom_rate_valid;
int geom_rate; // Vertical rate (geometric)
data_validity squawk_valid; data_validity squawk_valid;
unsigned squawk; // Squawk unsigned squawk; // Squawk
@ -122,6 +133,15 @@ struct aircraft {
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;
float alt_setting; // Altimeter setting (QNH/QFE), millibars
data_validity intent_altitude_valid;
unsigned intent_altitude; // intent altitude (FMS or FCU selected altitude)
data_validity intent_heading_valid;
float intent_heading; // intent heading, degrees (0-359)
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;
@ -143,16 +163,23 @@ struct aircraft {
int fatsv_emitted_altitude; // last FA emitted altitude int fatsv_emitted_altitude; // last FA emitted altitude
int fatsv_emitted_altitude_gnss; // -"- GNSS altitude int fatsv_emitted_altitude_gnss; // -"- GNSS altitude
int fatsv_emitted_heading; // -"- true track int fatsv_emitted_baro_rate; // -"- barometric rate
int fatsv_emitted_heading_magnetic; // -"- magnetic heading int fatsv_emitted_geom_rate; // -"- geometric rate
int fatsv_emitted_speed; // -"- groundspeed float fatsv_emitted_heading; // -"- true track
int fatsv_emitted_speed_ias; // -"- IAS float fatsv_emitted_heading_magnetic; // -"- magnetic heading
int fatsv_emitted_speed_tas; // -"- TAS float fatsv_emitted_track_rate; // -"- track rate of change
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_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
float fatsv_emitted_intent_heading; // -"- intent heading
float fatsv_emitted_alt_setting; // -"- 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_target[7]; // -"- ES target 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
uint64_t fatsv_last_emitted; // time (millis) aircraft was last FA emitted uint64_t fatsv_last_emitted; // time (millis) aircraft was last FA emitted