Merge branch 'more_mode_s' into dev

This commit is contained in:
Oliver Jowett 2018-05-09 16:20:58 +01:00
commit f4fa94f842
45 changed files with 3533 additions and 1459 deletions

View file

@ -40,13 +40,13 @@ all: dump1090 view1090
%.o: %.c *.h
$(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
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
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)
clean:

740
comm_b.c Normal file
View file

@ -0,0 +1,740 @@
// 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, 14) != 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 {
// unlikely altitude
score -= 2;
}
} 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 {
// unlikely altitude
score -= 2;
}
} 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 {
// unlikely pressure setting
score -= 2;
}
} 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;
if (mcp_valid) {
mm->nav.mcp_altitude_valid = 1;
mm->nav.mcp_altitude = mcp_alt;
}
if (fms_valid) {
mm->nav.fms_altitude_valid = 1;
mm->nav.fms_altitude = fms_alt;
}
if (baro_valid) {
mm->nav.qnh_valid = 1;
mm->nav.qnh = baro_setting;
}
if (mode_valid) {
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->nav.altitude_source = NAV_ALT_UNKNOWN;
break;
case 1:
mm->nav.altitude_source = NAV_ALT_AIRCRAFT;
break;
case 2:
mm->nav.altitude_source = NAV_ALT_MCP;
break;
case 3:
mm->nav.altitude_source = NAV_ALT_FMS;
break;
default:
mm->nav.altitude_source = NAV_ALT_INVALID;
break;
}
} else {
mm->nav.altitude_source = NAV_ALT_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->heading_valid = 1;
mm->heading = track;
mm->heading_type = HEADING_GROUND_TRACK;
}
if (gs_valid) {
mm->gs_valid = 1;
mm->gs.v0 = mm->gs.v2 = 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
// Should check IAS vs Mach at given altitude, but the maths is a little involved
if (baro_rate_valid && inertial_rate_valid) {
int delta = abs(baro_rate - inertial_rate);
if (delta < 500) {
score += 5;
} else if (delta > 2000) {
score -= 5;
}
}
if (store) {
mm->commb_format = COMMB_HEADING_SPEED;
if (heading_valid) {
mm->heading_valid = 1;
mm->heading = heading;
mm->heading_type = HEADING_MAGNETIC;
}
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

6
debian/changelog vendored
View file

@ -1,3 +1,9 @@
dump1090-fa (3.6.0~dev) UNRELEASED; urgency=medium
* In development
-- Oliver Jowett <oliver@mutability.co.uk> Mon, 19 Jun 2017 11:11:59 -0500
dump1090-fa (3.5.3) stable; urgency=medium
* Skip 3.5.2 to align with piaware versioning

View file

@ -311,9 +311,7 @@ void demodulate2400(struct mag_buf *mag)
mm.timestampMsg = mag->sampleTimestamp + j*5 + (8 + 56) * 12 + bestphase;
// compute message receive time as block-start-time + difference in the 12MHz clock
mm.sysTimestampMsg = mag->sysTimestamp; // start of block time
mm.sysTimestampMsg.tv_nsec += receiveclock_ns_elapsed(mag->sampleTimestamp, mm.timestampMsg);
normalize_timespec(&mm.sysTimestampMsg);
mm.sysTimestampMsg = mag->sysTimestamp + receiveclock_ms_elapsed(mag->sampleTimestamp, mm.timestampMsg);
mm.score = bestscore;
@ -646,9 +644,7 @@ void demodulate2400AC(struct mag_buf *mag)
mm.timestampMsg = mag->sampleTimestamp + f2_clock / 5; // 60MHz -> 12MHz
// compute message receive time as block-start-time + difference in the 12MHz clock
mm.sysTimestampMsg = mag->sysTimestamp; // start of block time
mm.sysTimestampMsg.tv_nsec += receiveclock_ns_elapsed(mag->sampleTimestamp, mm.timestampMsg);
normalize_timespec(&mm.sysTimestampMsg);
mm.sysTimestampMsg = mag->sysTimestamp + receiveclock_ms_elapsed(mag->sampleTimestamp, mm.timestampMsg);
decodeModeAMessage(&mm, modeac);

View file

@ -369,7 +369,7 @@ void backgroundTasks(void) {
trackPeriodicUpdate();
if (Modes.net) {
modesNetPeriodicWork();
modesNetPeriodicWork();
}
@ -379,7 +379,7 @@ void backgroundTasks(void) {
}
// always update end time so it is current when requests arrive
Modes.stats_current.end = now;
Modes.stats_current.end = mstime();
if (now >= next_stats_update) {
int i;

View file

@ -167,7 +167,7 @@ typedef enum {
typedef enum {
ALTITUDE_BARO,
ALTITUDE_GNSS
ALTITUDE_GEOM
} altitude_source_t;
typedef enum {
@ -178,24 +178,55 @@ typedef enum {
} airground_t;
typedef enum {
SPEED_GROUNDSPEED,
SPEED_IAS,
SPEED_TAS
} speed_source_t;
typedef enum {
HEADING_TRUE,
HEADING_MAGNETIC
} heading_source_t;
typedef enum {
SIL_PER_SAMPLE, SIL_PER_HOUR
SIL_INVALID, SIL_UNKNOWN, SIL_PER_SAMPLE, SIL_PER_HOUR
} sil_type_t;
typedef enum {
CPR_SURFACE, CPR_AIRBORNE, CPR_COARSE
} 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 // GROUND_TRACK / MAGNETIC / TRUE depending on the TAH bit in opstatus
} heading_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;
typedef enum {
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;
// Matches encoding of the ES type 28/1 emergency/priority status subfield
typedef enum {
EMERGENCY_NONE = 0,
EMERGENCY_GENERAL = 1,
EMERGENCY_LIFEGUARD = 2,
EMERGENCY_MINFUEL = 3,
EMERGENCY_NORDO = 4,
EMERGENCY_UNLAWFUL = 5,
EMERGENCY_DOWNED = 6,
EMERGENCY_RESERVED = 7
} emergency_t;
#define MODES_NON_ICAO_ADDRESS (1<<24) // Set on addresses to indicate they are not ICAO addresses
#define MODES_DEBUG_DEMOD (1<<0)
@ -251,7 +282,7 @@ struct mag_buf {
uint16_t *data; // Magnitude data. Starts with Modes.trailing_samples worth of overlap from the previous block
unsigned length; // Number of valid samples _after_ overlap. Total buffer length is buf->length + Modes.trailing_samples.
uint64_t sampleTimestamp; // Clock timestamp of the start of this block, 12MHz clock
struct timespec sysTimestamp; // Estimated system time at start of block
uint64_t sysTimestamp; // Estimated system time at start of block
uint32_t dropped; // Number of dropped samples preceding this buffer
double mean_level; // Mean of normalized (0..1) signal level
double mean_power; // Mean of normalized (0..1) power level
@ -370,7 +401,7 @@ struct modesMessage {
uint32_t addr; // Address Announced
addrtype_t addrtype; // address format / source
uint64_t timestampMsg; // Timestamp of the message (12MHz clock)
struct timespec sysTimestampMsg; // Timestamp of the message (system time)
uint64_t sysTimestampMsg; // Timestamp of the message (system time)
int remote; // If set this message is from a remote station
double signalLevel; // RSSI, in the range [0..1], as a fraction of full-scale power
int score; // Scoring from scoreModesMessage, if used
@ -400,62 +431,111 @@ 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;
unsigned speed_valid : 1;
unsigned vert_rate_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 callsign_valid : 1;
unsigned ew_velocity_valid : 1;
unsigned ns_velocity_valid : 1;
unsigned cpr_valid : 1;
unsigned cpr_odd : 1;
unsigned cpr_decoded : 1;
unsigned cpr_relative : 1;
unsigned category_valid : 1;
unsigned gnss_delta_valid : 1;
unsigned geom_delta_valid : 1;
unsigned from_mlat : 1;
unsigned from_tisb : 1;
unsigned spi_valid : 1;
unsigned spi : 1;
unsigned alert_valid : 1;
unsigned alert : 1;
unsigned emergency_valid : 1;
unsigned metype; // DF17/18 ME type
unsigned mesub; // DF17/18 ME subtype
// 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 altude or a GNSS height
// valid if gnss_delta_valid:
int gnss_delta; // difference between GNSS and baro alt
// valid if heading_valid:
unsigned heading; // Reported by aircraft, or computed from from EW and NS velocity
heading_source_t heading_source; // what "heading" is measuring (true or magnetic heading)
// valid if speed_valid:
unsigned speed; // in kts, reported by aircraft, or computed from from EW and NS velocity
speed_source_t speed_source; // what "speed" is measuring (groundspeed / IAS / TAS)
// valid if vert_rate_valid:
int vert_rate; // vertical rate in feet/minute
altitude_source_t vert_rate_source; // the altitude source used for vert_rate
// valid if squawk_valid:
unsigned squawk; // 13 bits identity (Squawk), encoded as 4 hex digits
// valid if callsign_valid
char callsign[9]; // 8 chars flight number
// valid if category_valid
unsigned category; // A0 - D7 encoded as a single hex byte
// valid if cpr_valid
cpr_type_t cpr_type; // The encoding type used (surface, airborne, coarse TIS-B)
unsigned cpr_lat; // Non decoded latitude.
unsigned cpr_lon; // Non decoded longitude.
unsigned cpr_nucp; // NUCp/NIC value implied by message type
commb_format_t commb_format; // Inferred format of a comm-b message
airground_t airground; // air/ground state
// 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
float heading; // ground track or heading, degrees (0-359). Reported directly or computed from from EW and NS velocity
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
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
int baro_rate; // Rate of change of barometric altitude, feet/minute
int geom_rate; // Rate of change of geometric (GNSS / INS) altitude, feet/minute
unsigned squawk; // 13 bits identity (Squawk), encoded as 4 hex digits
char callsign[9]; // 8 chars flight number, NUL-terminated
unsigned category; // A0 - D7 encoded as a single hex byte
emergency_t emergency; // emergency/priority status
// valid if cpr_valid
cpr_type_t cpr_type; // The encoding type used (surface, airborne, coarse TIS-B)
unsigned cpr_lat; // Non decoded latitude.
unsigned cpr_lon; // Non decoded longitude.
unsigned cpr_nucp; // NUCp/NIC value implied by message type
airground_t airground; // air/ground state
// 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 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_type != SIL_INVALID
sil_type_t sil_type;
unsigned gva : 2; // if gva_valid
unsigned sda : 2; // if sda_valid
} accuracy;
// Operational Status
struct {
@ -466,7 +546,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;
@ -477,50 +556,41 @@ 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;
enum { ANGLE_HEADING, ANGLE_TRACK } track_angle;
heading_source_t hrd;
heading_type_t tah;
heading_type_t hrd;
unsigned cc_lw;
unsigned cc_antenna_offset;
} opstatus;
// Target State & Status (ADS-B V2 only)
// combined:
// Target State & Status (ADS-B V2 only)
// Comm-B BDS4,0 Vertical Intent
struct {
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;
unsigned fms_altitude_valid : 1;
unsigned mcp_altitude_valid : 1;
unsigned qnh_valid : 1;
unsigned modes_valid : 1;
sil_type_t sil_type;
enum { TSS_ALTITUDE_MCP, TSS_ALTITUDE_FMS } altitude_type;
unsigned altitude;
float baro;
unsigned heading;
} tss;
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 qnh; // altimeter setting (QFE or QNH/QNE), millibars
enum { NAV_ALT_INVALID, NAV_ALT_UNKNOWN, NAV_ALT_AIRCRAFT, NAV_ALT_MCP, NAV_ALT_FMS } altitude_source;
nav_modes_t modes;
} nav;
};
// This one needs modesMessage:
#include "track.h"
#include "mode_s.h"
#include "comm_b.h"
// ======================== function declarations =========================
@ -537,14 +607,6 @@ void modeACInit();
int modeAToModeC (unsigned int modeA);
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
//

View file

@ -204,6 +204,7 @@ int main(int argc, char **argv) {
// Set up output connection on stdout
fatsv_output = makeFatsvOutputService();
createGenericClient(fatsv_output, STDOUT_FILENO);
writeFATSVHeader();
// Run it until we've lost either connection
while (!Modes.exit && beast_input->connections && fatsv_output->connections) {

View file

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

View file

@ -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;
}
}

944
mode_s.c

File diff suppressed because it is too large Load diff

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

1020
net_io.c

File diff suppressed because it is too large Load diff

View file

@ -87,6 +87,8 @@ void modesInitNet(void);
void modesQueueOutput(struct modesMessage *mm, struct aircraft *a);
void modesNetPeriodicWork(void);
void writeFATSVHeader();
// TODO: move these somewhere else
char *generateAircraftJson(const char *url_path, int *len);
char *generateStatsJson(const char *url_path, int *len);

View file

@ -119,11 +119,7 @@ ChartBundleLayers = false;
//
BingMapsAPIKey = null;
// Provide a Mapzen API key here to enable the Mapzen vector tile layer.
// You can obtain a free key at https://mapzen.com/developers/
// (you need a "vector tiles" key)
//
// Be sure to quote your key:
// MapzenAPIKey = "your key here";
//
MapzenAPIKey = null;
// Turn on display of extra Mode S EHS / ADS-B v1/v2 data
// This is not polished yet (and so is disabled by default),
// currently it's just a data dump of the new fields with no UX work.
ExtendedData = false;

View file

@ -103,10 +103,10 @@
<span id="selected_squawk"></span>
</div>
<div class="infoHeading infoRowFluid fourColumnSection3">
Speed:
Groundspeed:
</div>
<div class="infoData infoRowFluid fourColumnSection4">
<span id="selected_speed">n/a</span>
<span id="selected_gs">n/a</span>
</div>
</div>
<div>
@ -127,7 +127,7 @@
<div class="infoBlockSection lightGreyBackground">
<div>
<div class="infoHeading infoRowFluid fourColumnSection1">
Heading:
Ground track:
</div>
<div class="infoData infoRowFluid fourColumnSection2">
<span id="selected_track">n/a</span>
@ -170,11 +170,73 @@
</div>
</div>
</div>
<a href="http://www.airframes.org/" onclick="document.getElementById('horrible_hack').submit.call(document.getElementById('airframes_post')); return false;" class="link rightLink">
<div id="extendedData" class="hidden">
<div class="infoBlockSection lightGreyBackground">
<div>
<div class="infoHeading infoRowFluid fourColumnSection1">Alt (geom):</div>
<div class="infoData infoRowFluid fourColumnSection2"><span id="selected_alt_geom"/></div>
<div class="infoHeading infoRowFluid fourColumnSection3">Geom rate:</div>
<div class="infoData infoRowFluid fourColumnSection4"><span id="selected_geom_rate"/></div>
</div>
</div>
<div class="infoBlockSection">
<div>
<div class="infoHeading infoRowFluid fourColumnSection1">Mag heading:</div>
<div class="infoData infoRowFluid fourColumnSection2"><span id="selected_mag_heading"/></div>
<div class="infoHeading infoRowFluid fourColumnSection3">True heading:</div>
<div class="infoData infoRowFluid fourColumnSection4"><span id="selected_true_heading"/></div>
</div>
<div>
<div class="infoHeading infoRowFluid fourColumnSection1">Roll:</div>
<div class="infoData infoRowFluid fourColumnSection2"><span id="selected_roll"/></div>
<div class="infoHeading infoRowFluid fourColumnSection3">Track rate:</div>
<div class="infoData infoRowFluid fourColumnSection4"><span id="selected_track_rate"/></div>
</div>
</div>
<div class="infoBlockSection lightGreyBackground">
<div>
<div class="infoHeading infoRowFluid fourColumnSection1">IAS:</div>
<div class="infoData infoRowFluid fourColumnSection2"><span id="selected_ias"/></div>
<div class="infoHeading infoRowFluid fourColumnSection3">TAS:</div>
<div class="infoData infoRowFluid fourColumnSection4"><span id="selected_tas"/></div>
</div>
<div>
<div class="infoHeading infoRowFluid fourColumnSection1">Mach:</div>
<div class="infoData infoRowFluid fourColumnSection2"><span id="selected_mach"/></div>
</div>
</div>
<div class="infoBlockSection">
<div>
<div class="infoHeading infoRowFluid fourColumnSection1">Nav alt:</div>
<div class="infoData infoRowFluid fourColumnSection2"><span id="selected_nav_altitude"/></div>
<div class="infoHeading infoRowFluid fourColumnSection3">Nav heading:</div>
<div class="infoData infoRowFluid fourColumnSection4"><span id="selected_nav_heading"/></div>
</div>
<div>
<div class="infoHeading infoRowFluid fourColumnSection1">Nav modes:</div>
<div class="infoData infoRowFluid fourColumnSection2"><span id="selected_nav_modes"/></div>
<div class="infoHeading infoRowFluid fourColumnSection3">Nav QNH:</div>
<div class="infoData infoRowFluid fourColumnSection4"><span id="selected_nav_qnh"/></div>
</div>
</div>
<div class="infoBlockSection lightGreyBackground">
<div>
<div class="infoHeading infoRowFluid fourColumnSection1">ADS-B:</div>
<div class="infoData infoRowFluid fourColumnSection2"><span id="selected_version"/></div>
</div>
</div>
</div>
<a href="http://www.airframes.org/" onclick="document.getElementById('horrible_hack').submit.call(document.getElementById('airframes_post')); return false;" class="link rightLink">
AirFrames.org
</a>
<span id="selected_photo_link" class="link"></span>
</div>
</div>
</div> <!-- selected_infoblock -->

View file

@ -37,15 +37,11 @@ function createBaseLayers() {
}));
}
if (MapzenAPIKey) {
world.push(createMapzenLayer());
}
if (ChartBundleLayers) {
var chartbundleTypes = {
sec: "Sectional Charts",
tac: "Terminal Area Charts",
wac: "World Aeronautical Charts",
hel: "Helicopter Charts",
enrl: "IFR Enroute Low Charts",
enra: "IFR Area Charts",
enrh: "IFR Enroute High Charts"
@ -105,99 +101,3 @@ function createBaseLayers() {
return layers;
}
function createMapzenLayer() {
// draw earth with a fat stroke;
// force water above earth
var earthStyle = new ol.style.Style({
fill: new ol.style.Fill({
color: '#a06000'
}),
stroke: new ol.style.Stroke({
color: '#a06000',
width: 5.0
}),
zIndex: 0
});
var waterStyle = new ol.style.Style({
fill: new ol.style.Fill({
color: '#0040a0'
}),
stroke: new ol.style.Stroke({
color: '#0040a0',
width: 1.0
}),
zIndex: 1
});
var boundaryStyle = new ol.style.Style({
stroke: new ol.style.Stroke({
color: '#804000',
width: 2.0
}),
zIndex: 2
});
var dashedBoundaryStyle = new ol.style.Style({
stroke: new ol.style.Stroke({
color: '#804000',
width: 1.0,
lineDash: [4, 4],
}),
zIndex: 2
});
var styleMap = {
earth: earthStyle,
water: waterStyle,
basin: waterStyle,
dock: waterStyle,
lake: waterStyle,
ocean: waterStyle,
riverbank: waterStyle,
river: waterStyle,
country: boundaryStyle,
disputed: dashedBoundaryStyle,
indefinite: dashedBoundaryStyle,
indeterminate: dashedBoundaryStyle,
line_of_control: dashedBoundaryStyle
};
return new ol.layer.VectorTile({
name: 'mapzen_vector',
title: 'Mapzen coastlines and water',
type: 'base',
renderMode: 'image',
renderOrder: function(a,b) {
return a.get('sort_key') - b.get('sort_key');
},
source: new ol.source.VectorTile({
url: '//vector.mapzen.com/osm/earth,water,boundaries/{z}/{x}/{y}.topojson?api_key=' + MapzenAPIKey,
format: new ol.format.TopoJSON(),
attributions: [
new ol.Attribution({
html: 'Tiles courtesy of <a href="http://mapzen.com">Mapzen</a>'
}),
new ol.Attribution({
html: '&copy; <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
})
],
tileGrid: ol.tilegrid.createXYZ({
preload: 3,
maxZoom: 14,
tileSize: [512, 512]
}),
wrapX: true
}),
style: function (feature) {
return (styleMap[feature.get('kind')]);
}
});
}

View file

@ -10,9 +10,32 @@ function PlaneObject(icao) {
this.category = null;
// Basic location information
this.altitude = null;
this.speed = null;
this.track = null;
this.altitude = null;
this.alt_baro = null;
this.alt_geom = null;
this.speed = null;
this.gs = null;
this.ias = null;
this.tas = null;
this.track = null;
this.track_rate = null;
this.mag_heading = null;
this.true_heading = null;
this.mach = null;
this.roll = null;
this.nav_altitude = null;
this.nav_heading = null;
this.nav_modes = null;
this.nav_qnh = null;
this.baro_rate = null;
this.geom_rate = null;
this.vert_rate = null;
this.version = null;
this.prev_position = null;
this.position = null;
this.position_from_mlat = false
@ -411,20 +434,34 @@ PlaneObject.prototype.updateData = function(receiver_timestamp, data) {
this.rssi = data.rssi;
this.last_message_time = receiver_timestamp - data.seen;
if (typeof data.type !== "undefined")
// simple fields
var fields = ["alt_baro", "alt_geom", "gs", "ias", "tas", "track",
"track_rate", "mag_heading", "true_heading", "mach",
"roll", "nav_altitude", "nav_heading", "nav_modes",
"nav_qnh", "baro_rate", "geom_rate",
"squawk", "category", "version"];
for (var i = 0; i < fields.length; ++i) {
if (fields[i] in data) {
this[fields[i]] = data[fields[i]];
} else {
this[fields[i]] = null;
}
}
// fields with more complex behaviour
if ('type' in data)
this.addrtype = data.type;
else
this.addrtype = 'adsb_icao';
if (typeof data.altitude !== "undefined")
this.altitude = data.altitude;
if (typeof data.vert_rate !== "undefined")
this.vert_rate = data.vert_rate;
if (typeof data.speed !== "undefined")
this.speed = data.speed;
if (typeof data.track !== "undefined")
this.track = data.track;
if (typeof data.lat !== "undefined") {
// don't expire callsigns
if ('flight' in data)
this.flight = data.flight;
if ('lat' in data && 'lon' in data) {
this.position = [data.lon, data.lat];
this.last_position_time = receiver_timestamp - data.seen_pos;
@ -443,12 +480,36 @@ PlaneObject.prototype.updateData = function(receiver_timestamp, data) {
}
}
}
if (typeof data.flight !== "undefined")
this.flight = data.flight;
if (typeof data.squawk !== "undefined")
this.squawk = data.squawk;
if (typeof data.category !== "undefined")
this.category = data.category;
// Pick an altitude
if ('alt_baro' in data) {
this.altitude = data.alt_baro;
} else if ('alt_geom' in data) {
this.altitude = data.alt_geom;
} else {
this.altitude = null;
}
// Pick vertical rate from either baro or geom rate
// geometric rate is generally more reliable (smoothed etc)
if ('geom_rate' in data) {
this.vert_rate = data.geom_rate;
} else if ('baro_rate' in data) {
this.vert_rate = data.baro_rate;
} else {
this.vert_rate = null;
}
// Pick a speed
if ('gs' in data) {
this.speed = data.gs;
} else if ('tas' in data) {
this.speed = data.tas;
} else if ('ias' in data) {
this.speed = data.ias;
} else {
this.speed = null;
}
};
PlaneObject.prototype.updateTick = function(receiver_timestamp, last_timestamp) {

View file

@ -198,6 +198,10 @@ function initialize() {
$("#loader").removeClass("hidden");
if (ExtendedData || window.location.hash == '#extended') {
$("#extendedData").removeClass("hidden");
}
// Set up map/sidebar splitter
$("#sidebar_container").resizable({handles: {w: '#splitter'}});
@ -883,7 +887,7 @@ function refreshSelected() {
$('#selected_squawk').text(selected.squawk);
}
$('#selected_speed').text(format_speed_long(selected.speed, DisplayUnits));
$('#selected_gs').text(format_speed_long(selected.gs, DisplayUnits));
$('#selected_vertical_rate').text(format_vert_rate_long(selected.vert_rate, DisplayUnits));
$('#selected_icao').text(selected.icao.toUpperCase());
$('#airframes_post_icao').attr('value',selected.icao);
@ -936,6 +940,52 @@ function refreshSelected() {
$('#selected_rssi').text(selected.rssi.toFixed(1) + ' dBFS');
$('#selected_message_count').text(selected.messages);
$('#selected_photo_link').html(getFlightAwarePhotoLink(selected.registration));
$('#selected_alt_geom').text(format_altitude_long(selected.alt_geom, selected.geom_rate, DisplayUnits));
$('#selected_mag_heading').text(format_track_long(selected.mag_heading));
$('#selected_true_heading').text(format_track_long(selected.true_heading));
$('#selected_ias').text(format_speed_long(selected.ias, DisplayUnits));
$('#selected_tas').text(format_speed_long(selected.tas, DisplayUnits));
if (selected.mach == null) {
$('#selected_mach').text('n/a');
} else {
$('#selected_mach').text(selected.mach.toFixed(3));
}
if (selected.roll == null) {
$('#selected_roll').text('n/a');
} else {
$('#selected_roll').text(selected.roll.toFixed(1));
}
if (selected.track_rate == null) {
$('#selected_track_rate').text('n/a');
} else {
$('#selected_track_rate').text(selected.track_rate.toFixed(2));
}
$('#selected_geom_rate').text(format_vert_rate_long(selected.geom_rate, DisplayUnits));
if (selected.nav_qnh == null) {
$('#selected_nav_qnh').text("n/a");
} else {
$('#selected_nav_qnh').text(selected.nav_qnh.toFixed(1) + " hPa");
}
$('#selected_nav_altitude').text(format_altitude_long(selected.nav_altitude, 0, DisplayUnits));
$('#selected_nav_heading').text(format_track_long(selected.nav_heading));
if (selected.nav_modes == null) {
$('#selected_nav_modes').text("n/a");
} else {
$('#selected_nav_modes').text(selected.nav_modes.join());
}
if (selected.version == null) {
$('#selected_version').text('none');
} else if (selected.version == 0) {
$('#selected_version').text('v0 (DO-260)');
} else if (selected.version == 1) {
$('#selected_version').text('v1 (DO-260A)');
} else if (selected.version == 2) {
$('#selected_version').text('v2 (DO-260B)');
} else {
$('#selected_version').text('v' + selected.version);
}
}
function refreshHighlighted() {
@ -1039,7 +1089,7 @@ function refreshTableInfo() {
tableplane.tr.cells[4].textContent = (tableplane.icaotype !== null ? tableplane.icaotype : "");
tableplane.tr.cells[5].textContent = (tableplane.squawk !== null ? tableplane.squawk : "");
tableplane.tr.cells[6].innerHTML = format_altitude_brief(tableplane.altitude, tableplane.vert_rate, DisplayUnits);
tableplane.tr.cells[7].textContent = format_speed_brief(tableplane.speed, DisplayUnits);
tableplane.tr.cells[7].textContent = format_speed_brief(tableplane.gs, DisplayUnits);
tableplane.tr.cells[8].textContent = format_vert_rate_brief(tableplane.vert_rate, DisplayUnits);
tableplane.tr.cells[9].textContent = format_distance_brief(tableplane.sitedist, DisplayUnits);
tableplane.tr.cells[10].textContent = format_track_brief(tableplane.track);
@ -1090,7 +1140,7 @@ function sortByRegistration() { sortBy('registration', compareAlpha, func
function sortByAircraftType() { sortBy('icaotype', compareAlpha, function(x) { return x.icaotype; }); }
function sortBySquawk() { sortBy('squawk', compareAlpha, function(x) { return x.squawk; }); }
function sortByAltitude() { sortBy('altitude',compareNumeric, function(x) { return (x.altitude == "ground" ? -1e9 : x.altitude); }); }
function sortBySpeed() { sortBy('speed', compareNumeric, function(x) { return x.speed; }); }
function sortBySpeed() { sortBy('speed', compareNumeric, function(x) { return x.gs; }); }
function sortByVerticalRate() { sortBy('vert_rate', compareNumeric, function(x) { return x.vert_rate; }); }
function sortByDistance() { sortBy('sitedist',compareNumeric, function(x) { return x.sitedist; }); }
function sortByTrack() { sortBy('track', compareNumeric, function(x) { return x.track; }); }

View file

@ -311,8 +311,7 @@ static void *handle_bladerf_samples(struct bladerf *dev,
MODES_NOTUSED(num_samples);
// record initial time for later sys timestamp calculation
struct timespec entryTimestamp;
clock_gettime(CLOCK_REALTIME, &entryTimestamp);
uint64_t entryTimestamp = mstime();
pthread_mutex_lock(&Modes.data_mutex);
if (Modes.exit) {
@ -413,10 +412,8 @@ static void *handle_bladerf_samples(struct bladerf *dev,
if (blocks_processed) {
// Get the approx system time for the start of this block
unsigned block_duration = 1e9 * outbuf->length / Modes.sample_rate;
outbuf->sysTimestamp = entryTimestamp;
outbuf->sysTimestamp.tv_nsec -= block_duration;
normalize_timespec(&outbuf->sysTimestamp);
unsigned block_duration = 1e3 * outbuf->length / Modes.sample_rate;
outbuf->sysTimestamp = entryTimestamp - block_duration;
outbuf->mean_level /= blocks_processed;
outbuf->mean_power /= blocks_processed;

View file

@ -217,7 +217,7 @@ void ifileRun()
}
// Get the system time for the start of this block
clock_gettime(CLOCK_REALTIME, &outbuf->sysTimestamp);
outbuf->sysTimestamp = mstime();
toread = MODES_MAG_BUF_SAMPLES * ifile.bytes_per_sample;
r = ifile.readbuf;

View file

@ -313,12 +313,10 @@ void rtlsdrCallback(unsigned char *buf, uint32_t len, void *ctx) {
// Compute the sample timestamp and system timestamp for the start of the block
outbuf->sampleTimestamp = sampleCounter * 12e6 / Modes.sample_rate;
sampleCounter += slen;
block_duration = 1e9 * slen / Modes.sample_rate;
// Get the approx system time for the start of this block
clock_gettime(CLOCK_REALTIME, &outbuf->sysTimestamp);
outbuf->sysTimestamp.tv_nsec -= block_duration;
normalize_timespec(&outbuf->sysTimestamp);
block_duration = 1e3 * slen / Modes.sample_rate;
outbuf->sysTimestamp = mstime() - block_duration;
// Copy trailing data from last block (or reset if not valid)
if (outbuf->dropped == 0) {

620
track.c
View file

@ -76,14 +76,62 @@ 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;
// 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;
// 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_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
F(ias, 60, 70); // ADS-B (rare) or Comm-B
F(tas, 60, 70); // ADS-B (rare) or Comm-B
F(mach, 60, 70); // Comm-B only
F(track, 60, 70); // ADS-B or Comm-B
F(track_rate, 60, 70); // Comm-B only
F(roll, 60, 70); // Comm-B only
F(mag_heading, 60, 70); // ADS-B (rare) or Comm-B
F(true_heading, 60, 70); // ADS-B only (rare)
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(airground, 15, 70); // ADS-B or Mode S
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++;
return (a);
@ -107,15 +155,18 @@ struct aircraft *trackFindAircraft(uint32_t addr) {
// Should we accept some new data from the given source?
// If so, update the validity and return 1
static int accept_data(data_validity *d, datasource_t source, uint64_t now)
static int accept_data(data_validity *d, datasource_t source)
{
if (source < d->source && now < d->stale)
if (messageNow() < d->updated)
return 0;
if (source < d->source && messageNow() < d->stale)
return 0;
d->source = source;
d->updated = now;
d->stale = now + 60000;
d->expires = now + 70000;
d->updated = messageNow();
d->stale = messageNow() + (d->stale_interval ? d->stale_interval : 60000);
d->expires = messageNow() + (d->expire_interval ? d->expire_interval : 70000);
return 1;
}
@ -137,10 +188,10 @@ static void combine_validity(data_validity *to, const data_validity *from1, cons
to->expires = (from1->expires < from2->expires) ? from1->expires : from2->expires; // the earlier of the two expiry times
}
static int compare_validity(const data_validity *lhs, const data_validity *rhs, uint64_t now) {
if (now < lhs->stale && lhs->source > rhs->source)
static int compare_validity(const data_validity *lhs, const data_validity *rhs) {
if (messageNow() < lhs->stale && lhs->source > rhs->source)
return 1;
else if (now < rhs->stale && lhs->source < rhs->source)
else if (messageNow() < rhs->stale && lhs->source < rhs->source)
return -1;
else if (lhs->updated > rhs->updated)
return 1;
@ -196,7 +247,7 @@ static void update_range_histogram(double lat, double lon)
// return true if it's OK for the aircraft to have travelled from its last known position
// to a new position at (lat,lon,surface) at a time of now.
static int speed_check(struct aircraft *a, double lat, double lon, uint64_t now, int surface)
static int speed_check(struct aircraft *a, double lat, double lon, int surface)
{
uint64_t elapsed;
double distance;
@ -207,14 +258,14 @@ static int speed_check(struct aircraft *a, double lat, double lon, uint64_t now,
if (!trackDataValid(&a->position_valid))
return 1; // no reference, assume OK
elapsed = trackDataAge(&a->position_valid, now);
elapsed = trackDataAge(&a->position_valid);
if (trackDataValid(&a->speed_valid))
speed = a->speed;
else if (trackDataValid(&a->speed_ias_valid))
speed = a->speed_ias * 4 / 3;
else if (trackDataValid(&a->speed_tas_valid))
speed = a->speed_tas * 4 / 3;
if (trackDataValid(&a->gs_valid))
speed = a->gs;
else if (trackDataValid(&a->tas_valid))
speed = a->tas * 4 / 3;
else if (trackDataValid(&a->ias_valid))
speed = a->ias * 2;
else
speed = surface ? 100 : 600; // guess
@ -251,24 +302,25 @@ static int speed_check(struct aircraft *a, double lat, double lon, uint64_t now,
return inrange;
}
static int doGlobalCPR(struct aircraft *a, struct modesMessage *mm, uint64_t now, 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
// find reference location
double reflat, reflon;
if (trackDataValidEx(&a->position_valid, now, 50000, SOURCE_INVALID)) { // Ok to try aircraft relative first
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;
@ -320,7 +372,7 @@ static int doGlobalCPR(struct aircraft *a, struct modesMessage *mm, uint64_t now
return result;
// check speed limit
if (trackDataValid(&a->position_valid) && a->pos_nuc >= *nuc && !speed_check(a, *lat, *lon, now, 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;
}
@ -328,7 +380,7 @@ static int doGlobalCPR(struct aircraft *a, struct modesMessage *mm, uint64_t now
return result;
}
static int doLocalCPR(struct aircraft *a, struct modesMessage *mm, uint64_t now, 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
@ -338,14 +390,22 @@ static int doLocalCPR(struct aircraft *a, struct modesMessage *mm, uint64_t now,
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 (trackDataValidEx(&a->position_valid, now, 50000, SOURCE_INVALID)) {
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)) {
@ -394,7 +454,7 @@ static int doLocalCPR(struct aircraft *a, struct modesMessage *mm, uint64_t now,
}
// check speed limit
if (trackDataValid(&a->position_valid) && a->pos_nuc >= *nuc && !speed_check(a, *lat, *lon, now, 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
@ -413,12 +473,13 @@ static uint64_t time_between(uint64_t t1, uint64_t t2)
return t2 - t1;
}
static void updatePosition(struct aircraft *a, struct modesMessage *mm, uint64_t now)
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);
@ -427,7 +488,7 @@ static void updatePosition(struct aircraft *a, struct modesMessage *mm, uint64_t
++Modes.stats_current.cpr_surface;
// Surface: 25 seconds if >25kt or speed unknown, 50 seconds otherwise
if (mm->speed_valid && mm->speed <= 25)
if (mm->gs_valid && mm->gs.selected <= 25)
max_elapsed = 50000;
else
max_elapsed = 25000;
@ -444,7 +505,7 @@ static void updatePosition(struct aircraft *a, struct modesMessage *mm, uint64_t
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, now, &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
@ -475,7 +536,7 @@ static void updatePosition(struct aircraft *a, struct modesMessage *mm, uint64_t
// Otherwise try relative CPR.
if (location_result == -1) {
location_result = doLocalCPR(a, mm, now, &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++;
@ -496,16 +557,247 @@ static void updatePosition(struct aircraft *a, struct modesMessage *mm, uint64_t
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 RC_UNKNOWN; // bad combination
}
} 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 18520; // 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;
}
}
//
//=========================================================================
//
@ -522,7 +814,12 @@ struct aircraft *trackUpdateFromMessage(struct modesMessage *mm)
return NULL;
}
uint64_t now = mstime();
if (mm->addr == 0) {
// junk address, don't track it
return NULL;
}
_messageNow = mm->sysTimestampMsg;
// Lookup our aircraft or create a new one
a = trackFindAircraft(mm->addr);
@ -536,106 +833,240 @@ struct aircraft *trackUpdateFromMessage(struct modesMessage *mm)
a->signalLevel[a->signalNext] = mm->signalLevel;
a->signalNext = (a->signalNext + 1) & 7;
}
a->seen = now;
a->seen = messageNow();
a->messages++;
// update addrtype, we only ever go towards "more direct" types
if (mm->addrtype < a->addrtype)
a->addrtype = mm->addrtype;
if (mm->altitude_valid && mm->altitude_source == ALTITUDE_BARO && accept_data(&a->altitude_valid, mm->source, now)) {
// if we saw some direct ADS-B for the first time, assume version 0
if (mm->source == SOURCE_ADSB && a->adsb_version < 0)
a->adsb_version = 0;
// 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, now)) {
if (mm->squawk_valid && accept_data(&a->squawk_valid, mm->source)) {
if (mm->squawk != a->squawk) {
a->modeA_hit = 0;
}
a->squawk = mm->squawk;
// Handle 7x00 without a corresponding emergency status
if (!mm->emergency_valid) {
emergency_t squawk_emergency;
switch (mm->squawk) {
case 0x7500:
squawk_emergency = EMERGENCY_UNLAWFUL;
break;
case 0x7600:
squawk_emergency = EMERGENCY_NORDO;
break;
case 0x7700:
squawk_emergency = EMERGENCY_GENERAL;
break;
default:
squawk_emergency = EMERGENCY_NONE;
break;
}
if (squawk_emergency != EMERGENCY_NONE && accept_data(&a->emergency_valid, mm->source)) {
a->emergency = squawk_emergency;
}
}
}
if (mm->altitude_valid && mm->altitude_source == ALTITUDE_GNSS && accept_data(&a->altitude_gnss_valid, mm->source, now)) {
a->altitude_gnss = mm->altitude;
if (mm->emergency_valid && accept_data(&a->emergency_valid, mm->source)) {
a->emergency = mm->emergency;
}
if (mm->gnss_delta_valid && accept_data(&a->gnss_delta_valid, mm->source, now)) {
a->gnss_delta = mm->gnss_delta;
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->heading_valid && mm->heading_source == HEADING_TRUE && accept_data(&a->heading_valid, mm->source, now)) {
a->heading = mm->heading;
if (mm->geom_delta_valid && accept_data(&a->geom_delta_valid, mm->source)) {
a->geom_delta = mm->geom_delta;
}
if (mm->heading_valid && mm->heading_source == HEADING_MAGNETIC && accept_data(&a->heading_magnetic_valid, mm->source, now)) {
a->heading_magnetic = mm->heading;
if (mm->heading_valid) {
heading_type_t htype = mm->heading_type;
if (htype == HEADING_MAGNETIC_OR_TRUE) {
htype = a->adsb_hrd;
} else if (htype == HEADING_TRACK_OR_HEADING) {
htype = a->adsb_tah;
}
if (htype == HEADING_GROUND_TRACK && accept_data(&a->track_valid, mm->source)) {
a->track = mm->heading;
} else if (htype == HEADING_MAGNETIC && accept_data(&a->mag_heading_valid, mm->source)) {
a->mag_heading = mm->heading;
} else if (htype == HEADING_TRUE && accept_data(&a->true_heading_valid, mm->source)) {
a->true_heading = mm->heading;
}
}
if (mm->speed_valid && mm->speed_source == SPEED_GROUNDSPEED && accept_data(&a->speed_valid, mm->source, now)) {
a->speed = mm->speed;
if (mm->track_rate_valid && accept_data(&a->track_rate_valid, mm->source)) {
a->track_rate = mm->track_rate;
}
if (mm->speed_valid && mm->speed_source == SPEED_IAS && accept_data(&a->speed_ias_valid, mm->source, now)) {
a->speed_ias = mm->speed;
if (mm->roll_valid && accept_data(&a->roll_valid, mm->source)) {
a->roll = mm->roll;
}
if (mm->speed_valid && mm->speed_source == SPEED_TAS && accept_data(&a->speed_tas_valid, mm->source, now)) {
a->speed_tas = mm->speed;
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->vert_rate_valid && accept_data(&a->vert_rate_valid, mm->source, now)) {
a->vert_rate = mm->vert_rate;
a->vert_rate_source = mm->vert_rate_source;
if (mm->ias_valid && accept_data(&a->ias_valid, mm->source)) {
a->ias = mm->ias;
}
if (mm->category_valid && accept_data(&a->category_valid, mm->source, now)) {
a->category = mm->category;
if (mm->tas_valid && accept_data(&a->tas_valid, mm->source)) {
a->tas = mm->tas;
}
if (mm->airground != AG_INVALID && accept_data(&a->airground_valid, mm->source, now)) {
a->airground = mm->airground;
if (mm->mach_valid && accept_data(&a->mach_valid, mm->source)) {
a->mach = mm->mach;
}
if (mm->callsign_valid && accept_data(&a->callsign_valid, mm->source, now)) {
if (mm->baro_rate_valid && accept_data(&a->baro_rate_valid, mm->source)) {
a->baro_rate = mm->baro_rate;
}
if (mm->geom_rate_valid && accept_data(&a->geom_rate_valid, mm->source)) {
a->geom_rate = mm->geom_rate;
}
if (mm->airground != AG_INVALID) {
// If our current state is UNCERTAIN, accept new data as normal
// If our current state is certain but new data is not, only accept the uncertain state if the certain data has gone stale
if (mm->airground != AG_UNCERTAIN ||
(mm->airground == AG_UNCERTAIN && !trackDataFresh(&a->airground_valid))) {
if (accept_data(&a->airground_valid, mm->source)) {
a->airground = mm->airground;
}
}
}
if (mm->callsign_valid && accept_data(&a->callsign_valid, mm->source)) {
memcpy(a->callsign, mm->callsign, sizeof(a->callsign));
}
// prefer MCP over FMS
// unless the source says otherwise
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->nav.heading_valid && accept_data(&a->nav_heading_valid, mm->source)) {
a->nav_heading = mm->nav.heading;
}
if (mm->nav.modes_valid && accept_data(&a->nav_modes_valid, mm->source)) {
a->nav_modes = mm->nav.modes;
}
if (mm->nav.qnh_valid && accept_data(&a->nav_qnh_valid, mm->source)) {
a->nav_qnh = mm->nav.qnh;
}
// 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)) {
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
if (mm->cpr_valid && mm->cpr_odd && accept_data(&a->cpr_odd_valid, mm->source, now)) {
if (mm->cpr_valid && mm->cpr_odd && accept_data(&a->cpr_odd_valid, mm->source)) {
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);
}
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_type != SIL_INVALID && accept_data(&a->sil_valid, mm->source)) {
a->sil = mm->accuracy.sil;
if (a->sil_type == SIL_INVALID || mm->accuracy.sil_type != SIL_UNKNOWN) {
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 GNSS if we have baro + delta
if (compare_validity(&a->altitude_valid, &a->altitude_gnss_valid, now) > 0 &&
compare_validity(&a->gnss_delta_valid, &a->altitude_gnss_valid, now) > 0) {
// Baro and delta are both more recent than GNSS, derive GNSS from baro + delta
a->altitude_gnss = a->altitude + a->gnss_delta;
combine_validity(&a->altitude_gnss_valid, &a->altitude_valid, &a->gnss_delta_valid);
// derive geometric altitude if we have baro + delta
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_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
if (mm->cpr_valid) {
updatePosition(a, mm, now);
updatePosition(a, mm);
}
return (a);
@ -669,8 +1100,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);
@ -751,22 +1182,37 @@ 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_gnss);
EXPIRE(gnss_delta);
EXPIRE(speed);
EXPIRE(speed_ias);
EXPIRE(speed_tas);
EXPIRE(heading);
EXPIRE(heading_magnetic);
EXPIRE(vert_rate);
EXPIRE(altitude_baro);
EXPIRE(altitude_geom);
EXPIRE(geom_delta);
EXPIRE(gs);
EXPIRE(ias);
EXPIRE(tas);
EXPIRE(mach);
EXPIRE(track);
EXPIRE(track_rate);
EXPIRE(roll);
EXPIRE(mag_heading);
EXPIRE(true_heading);
EXPIRE(baro_rate);
EXPIRE(geom_rate);
EXPIRE(squawk);
EXPIRE(category);
EXPIRE(airground);
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;
}
}

182
track.h
View file

@ -64,11 +64,21 @@
*/
#define TRACK_MODEAC_MIN_MESSAGES 4
/* Special value for Rc unknown */
#define RC_UNKNOWN 0
// 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.
// expired: data is not valid.
typedef struct {
uint64_t stale_interval; /* how long after an update until the data is stale */
uint64_t expire_interval; /* how long after an update until the data expires */
datasource_t source; /* where the data came from */
uint64_t updated; /* when it arrived */
uint64_t stale; /* when it will become stale */
uint64_t expires; /* when it will expire */
uint64_t updated; /* when it arrived */
uint64_t stale; /* when it goes stale */
uint64_t expires; /* when it expires */
} data_validity;
/* Structure used to describe the state of one tracked aircraft */
@ -85,77 +95,153 @@ 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_gnss_valid;
int altitude_gnss; // Altitude (GNSS)
data_validity altitude_geom_valid;
int altitude_geom; // Altitude (Geometric)
data_validity gnss_delta_valid;
int gnss_delta; // Difference between GNSS and Baro altitudes
data_validity geom_delta_valid;
int geom_delta; // Difference between Geometric and Baro altitudes
data_validity speed_valid;
unsigned speed;
data_validity gs_valid;
float gs;
data_validity speed_ias_valid;
unsigned speed_ias;
data_validity ias_valid;
unsigned ias;
data_validity speed_tas_valid;
unsigned speed_tas;
data_validity tas_valid;
unsigned tas;
data_validity heading_valid;
unsigned heading; // Heading (OK it's really the track)
data_validity mach_valid;
float mach;
data_validity heading_magnetic_valid;
unsigned heading_magnetic; // Heading
data_validity track_valid;
float track; // Ground track
data_validity vert_rate_valid;
int vert_rate; // Vertical rate
altitude_source_t vert_rate_source;
data_validity track_rate_valid;
float track_rate; // Rate of change of ground track, degrees/second
data_validity roll_valid;
float roll; // Roll angle, degrees right
data_validity mag_heading_valid;
float mag_heading; // Magnetic heading
data_validity true_heading_valid;
float true_heading; // True 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;
unsigned squawk; // Squawk
data_validity category_valid;
unsigned category; // Aircraft category A0 - D7 encoded as a single hex byte
data_validity emergency_valid;
emergency_t emergency; // Emergency/priority status
unsigned category; // Aircraft category A0 - D7 encoded as a single hex byte. 00 = unset
data_validity airground_valid;
airground_t airground; // air/ground status
data_validity nav_qnh_valid;
float nav_qnh; // Altimeter setting (QNH/QFE), millibars
data_validity nav_altitude_valid;
unsigned nav_altitude; // FMS or FCU selected altitude
data_validity nav_heading_valid;
float nav_heading; // target heading, degrees (0-359)
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 airborne velocity or opstatus
unsigned sil : 2; // SIL from TSS or opstatus
sil_type_t sil_type; // SIL supplement from TSS 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_heading; // -"- true track
int fatsv_emitted_heading_magnetic; // -"- magnetic heading
int fatsv_emitted_speed; // -"- groundspeed
int fatsv_emitted_speed_ias; // -"- IAS
int fatsv_emitted_speed_tas; // -"- TAS
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
float fatsv_emitted_track_rate; // -"- track rate of change
float fatsv_emitted_mag_heading; // -"- magnetic heading
float fatsv_emitted_true_heading; // -"- true heading
float fatsv_emitted_roll; // -"- roll angle
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_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_target[7]; // -"- ES target 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
unsigned fatsv_emitted_nac_p; // -"- NACp
unsigned fatsv_emitted_nac_v; // -"- NACv
unsigned fatsv_emitted_sil; // -"- SIL
sil_type_t fatsv_emitted_sil_type; // -"- SIL supplement
unsigned fatsv_emitted_nic_baro; // -"- NICbaro
emergency_t fatsv_emitted_emergency; // -"- emergency/priority status
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
@ -173,33 +259,23 @@ extern uint32_t modeAC_age[4096];
/* is this bit of data valid? */
static inline int trackDataValid(const data_validity *v)
{
return (v->source != SOURCE_INVALID);
return (v->source != SOURCE_INVALID && messageNow() < v->expires);
}
/* .. with these constraints? */
static inline int trackDataValidEx(const data_validity *v,
uint64_t now,
uint64_t maxAge,
datasource_t minSource)
/* is this bit of data fresh? */
static inline int trackDataFresh(const data_validity *v)
{
if (v->source == SOURCE_INVALID)
return 0;
if (v->source < minSource)
return 0;
if (v->updated < now && (now - v->updated) > maxAge)
return 0;
return 1;
return (v->source != SOURCE_INVALID && messageNow() < v->stale);
}
/* what's the age of this data? */
static inline uint64_t trackDataAge(const data_validity *v,
uint64_t now)
/* what's the age of this data, in milliseconds? */
static inline uint64_t trackDataAge(const data_validity *v)
{
if (v->source == SOURCE_INVALID)
return ~(uint64_t)0;
if (v->updated >= now)
if (v->updated >= messageNow())
return 0;
return (now - v->updated);
return (messageNow() - v->updated);
}
/* Update aircraft state from data in the provided mesage.

7
util.c
View file

@ -52,6 +52,8 @@
#include <stdlib.h>
#include <sys/time.h>
uint64_t _messageNow = 0;
uint64_t mstime(void)
{
struct timeval tv;
@ -68,6 +70,11 @@ int64_t receiveclock_ns_elapsed(uint64_t t1, uint64_t t2)
return (t2 - t1) * 1000U / 12U;
}
int64_t receiveclock_ms_elapsed(uint64_t t1, uint64_t t2)
{
return (t2 - t1) / 12000U;
}
void normalize_timespec(struct timespec *ts)
{
if (ts->tv_nsec > 1000000000) {

9
util.h
View file

@ -25,11 +25,20 @@
/* Returns system time in milliseconds */
uint64_t mstime(void);
/* Returns the time for the current message we're dealing with */
extern uint64_t _messageNow;
static inline uint64_t messageNow() {
return _messageNow;
}
/* Returns the time elapsed, in nanoseconds, from t1 to t2,
* where t1 and t2 are 12MHz counters.
*/
int64_t receiveclock_ns_elapsed(uint64_t t1, uint64_t t2);
/* Same, in milliseconds */
int64_t receiveclock_ms_elapsed(uint64_t t1, uint64_t t2);
/* Normalize the value in ts so that ts->nsec lies in
* [0,999999999]
*/