Merge branch 'more_mode_s' into dev
This commit is contained in:
commit
f4fa94f842
6
Makefile
6
Makefile
|
@ -40,13 +40,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:
|
||||||
|
|
740
comm_b.c
Normal file
740
comm_b.c
Normal 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
26
comm_b.h
Normal 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
6
debian/changelog
vendored
|
@ -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
|
dump1090-fa (3.5.3) stable; urgency=medium
|
||||||
|
|
||||||
* Skip 3.5.2 to align with piaware versioning
|
* Skip 3.5.2 to align with piaware versioning
|
||||||
|
|
|
@ -311,9 +311,7 @@ void demodulate2400(struct mag_buf *mag)
|
||||||
mm.timestampMsg = mag->sampleTimestamp + j*5 + (8 + 56) * 12 + bestphase;
|
mm.timestampMsg = mag->sampleTimestamp + j*5 + (8 + 56) * 12 + bestphase;
|
||||||
|
|
||||||
// compute message receive time as block-start-time + difference in the 12MHz clock
|
// compute message receive time as block-start-time + difference in the 12MHz clock
|
||||||
mm.sysTimestampMsg = mag->sysTimestamp; // start of block time
|
mm.sysTimestampMsg = mag->sysTimestamp + receiveclock_ms_elapsed(mag->sampleTimestamp, mm.timestampMsg);
|
||||||
mm.sysTimestampMsg.tv_nsec += receiveclock_ns_elapsed(mag->sampleTimestamp, mm.timestampMsg);
|
|
||||||
normalize_timespec(&mm.sysTimestampMsg);
|
|
||||||
|
|
||||||
mm.score = bestscore;
|
mm.score = bestscore;
|
||||||
|
|
||||||
|
@ -646,9 +644,7 @@ void demodulate2400AC(struct mag_buf *mag)
|
||||||
mm.timestampMsg = mag->sampleTimestamp + f2_clock / 5; // 60MHz -> 12MHz
|
mm.timestampMsg = mag->sampleTimestamp + f2_clock / 5; // 60MHz -> 12MHz
|
||||||
|
|
||||||
// compute message receive time as block-start-time + difference in the 12MHz clock
|
// compute message receive time as block-start-time + difference in the 12MHz clock
|
||||||
mm.sysTimestampMsg = mag->sysTimestamp; // start of block time
|
mm.sysTimestampMsg = mag->sysTimestamp + receiveclock_ms_elapsed(mag->sampleTimestamp, mm.timestampMsg);
|
||||||
mm.sysTimestampMsg.tv_nsec += receiveclock_ns_elapsed(mag->sampleTimestamp, mm.timestampMsg);
|
|
||||||
normalize_timespec(&mm.sysTimestampMsg);
|
|
||||||
|
|
||||||
decodeModeAMessage(&mm, modeac);
|
decodeModeAMessage(&mm, modeac);
|
||||||
|
|
||||||
|
|
|
@ -369,7 +369,7 @@ void backgroundTasks(void) {
|
||||||
trackPeriodicUpdate();
|
trackPeriodicUpdate();
|
||||||
|
|
||||||
if (Modes.net) {
|
if (Modes.net) {
|
||||||
modesNetPeriodicWork();
|
modesNetPeriodicWork();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -379,7 +379,7 @@ void backgroundTasks(void) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// always update end time so it is current when requests arrive
|
// 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) {
|
if (now >= next_stats_update) {
|
||||||
int i;
|
int i;
|
||||||
|
|
236
dump1090.h
236
dump1090.h
|
@ -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 {
|
||||||
|
@ -178,24 +178,55 @@ typedef enum {
|
||||||
} airground_t;
|
} airground_t;
|
||||||
|
|
||||||
typedef enum {
|
typedef enum {
|
||||||
SPEED_GROUNDSPEED,
|
SIL_INVALID, SIL_UNKNOWN, SIL_PER_SAMPLE, SIL_PER_HOUR
|
||||||
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_type_t;
|
} sil_type_t;
|
||||||
|
|
||||||
typedef enum {
|
typedef enum {
|
||||||
CPR_SURFACE, CPR_AIRBORNE, CPR_COARSE
|
CPR_SURFACE, CPR_AIRBORNE, CPR_COARSE
|
||||||
} cpr_type_t;
|
} 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_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)
|
||||||
|
@ -251,7 +282,7 @@ struct mag_buf {
|
||||||
uint16_t *data; // Magnitude data. Starts with Modes.trailing_samples worth of overlap from the previous block
|
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.
|
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
|
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
|
uint32_t dropped; // Number of dropped samples preceding this buffer
|
||||||
double mean_level; // Mean of normalized (0..1) signal level
|
double mean_level; // Mean of normalized (0..1) signal level
|
||||||
double mean_power; // Mean of normalized (0..1) power level
|
double mean_power; // Mean of normalized (0..1) power level
|
||||||
|
@ -370,7 +401,7 @@ struct modesMessage {
|
||||||
uint32_t addr; // Address Announced
|
uint32_t addr; // Address Announced
|
||||||
addrtype_t addrtype; // address format / source
|
addrtype_t addrtype; // address format / source
|
||||||
uint64_t timestampMsg; // Timestamp of the message (12MHz clock)
|
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
|
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
|
double signalLevel; // RSSI, in the range [0..1], as a fraction of full-scale power
|
||||||
int score; // Scoring from scoreModesMessage, if used
|
int score; // Scoring from scoreModesMessage, if used
|
||||||
|
@ -400,62 +431,111 @@ struct modesMessage {
|
||||||
unsigned char MV[7];
|
unsigned char MV[7];
|
||||||
|
|
||||||
// Decoded data
|
// Decoded data
|
||||||
unsigned altitude_valid : 1;
|
unsigned altitude_baro_valid : 1;
|
||||||
|
unsigned altitude_geom_valid : 1;
|
||||||
|
unsigned track_valid : 1;
|
||||||
|
unsigned track_rate_valid : 1;
|
||||||
unsigned heading_valid : 1;
|
unsigned heading_valid : 1;
|
||||||
unsigned speed_valid : 1;
|
unsigned roll_valid : 1;
|
||||||
unsigned vert_rate_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;
|
||||||
unsigned spi : 1;
|
unsigned spi : 1;
|
||||||
unsigned alert_valid : 1;
|
unsigned alert_valid : 1;
|
||||||
unsigned alert : 1;
|
unsigned alert : 1;
|
||||||
|
unsigned emergency_valid : 1;
|
||||||
|
|
||||||
unsigned metype; // DF17/18 ME type
|
unsigned metype; // DF17/18 ME type
|
||||||
unsigned mesub; // DF17/18 ME subtype
|
unsigned mesub; // DF17/18 ME subtype
|
||||||
|
|
||||||
// valid if altitude_valid:
|
commb_format_t commb_format; // Inferred format of a comm-b message
|
||||||
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
|
|
||||||
|
|
||||||
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:
|
// valid if cpr_decoded:
|
||||||
double decoded_lat;
|
double decoded_lat;
|
||||||
double decoded_lon;
|
double decoded_lon;
|
||||||
|
unsigned decoded_nic;
|
||||||
|
unsigned decoded_rc;
|
||||||
|
|
||||||
|
// various integrity/accuracy things
|
||||||
|
struct {
|
||||||
|
unsigned nic_a_valid : 1;
|
||||||
|
unsigned nic_b_valid : 1;
|
||||||
|
unsigned nic_c_valid : 1;
|
||||||
|
unsigned nic_baro_valid : 1;
|
||||||
|
unsigned nac_p_valid : 1;
|
||||||
|
unsigned nac_v_valid : 1;
|
||||||
|
unsigned 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
|
// Operational Status
|
||||||
struct {
|
struct {
|
||||||
|
@ -466,7 +546,6 @@ struct modesMessage {
|
||||||
unsigned om_ident : 1;
|
unsigned om_ident : 1;
|
||||||
unsigned om_atc : 1;
|
unsigned om_atc : 1;
|
||||||
unsigned om_saf : 1;
|
unsigned om_saf : 1;
|
||||||
unsigned om_sda : 2;
|
|
||||||
|
|
||||||
unsigned cc_acas : 1;
|
unsigned cc_acas : 1;
|
||||||
unsigned cc_cdti : 1;
|
unsigned cc_cdti : 1;
|
||||||
|
@ -477,50 +556,41 @@ struct modesMessage {
|
||||||
unsigned cc_uat_in : 1;
|
unsigned cc_uat_in : 1;
|
||||||
unsigned cc_poa : 1;
|
unsigned cc_poa : 1;
|
||||||
unsigned cc_b2_low : 1;
|
unsigned cc_b2_low : 1;
|
||||||
unsigned cc_nac_v : 3;
|
|
||||||
unsigned cc_nic_supp_c : 1;
|
|
||||||
unsigned cc_lw_valid : 1;
|
unsigned cc_lw_valid : 1;
|
||||||
|
|
||||||
unsigned nic_supp_a : 1;
|
heading_type_t tah;
|
||||||
unsigned nac_p : 4;
|
heading_type_t hrd;
|
||||||
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;
|
|
||||||
|
|
||||||
unsigned cc_lw;
|
unsigned cc_lw;
|
||||||
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 altitude_valid : 1;
|
|
||||||
unsigned baro_valid : 1;
|
|
||||||
unsigned heading_valid : 1;
|
unsigned heading_valid : 1;
|
||||||
unsigned mode_valid : 1;
|
unsigned fms_altitude_valid : 1;
|
||||||
unsigned mode_autopilot : 1;
|
unsigned mcp_altitude_valid : 1;
|
||||||
unsigned mode_vnav : 1;
|
unsigned qnh_valid : 1;
|
||||||
unsigned mode_alt_hold : 1;
|
unsigned modes_valid : 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;
|
float heading; // heading, degrees (0-359) (could be magnetic or true heading; magnetic recommended)
|
||||||
enum { TSS_ALTITUDE_MCP, TSS_ALTITUDE_FMS } altitude_type;
|
heading_type_t heading_type;
|
||||||
unsigned altitude;
|
unsigned fms_altitude; // FMS selected altitude
|
||||||
float baro;
|
unsigned mcp_altitude; // MCP/FCU selected altitude
|
||||||
unsigned heading;
|
float qnh; // altimeter setting (QFE or QNH/QNE), millibars
|
||||||
} tss;
|
|
||||||
|
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:
|
// This one needs modesMessage:
|
||||||
#include "track.h"
|
#include "track.h"
|
||||||
|
#include "mode_s.h"
|
||||||
|
#include "comm_b.h"
|
||||||
|
|
||||||
// ======================== function declarations =========================
|
// ======================== function declarations =========================
|
||||||
|
|
||||||
|
@ -537,14 +607,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
|
||||||
//
|
//
|
||||||
|
|
|
@ -204,6 +204,7 @@ int main(int argc, char **argv) {
|
||||||
// Set up output connection on stdout
|
// Set up output connection on stdout
|
||||||
fatsv_output = makeFatsvOutputService();
|
fatsv_output = makeFatsvOutputService();
|
||||||
createGenericClient(fatsv_output, STDOUT_FILENO);
|
createGenericClient(fatsv_output, STDOUT_FILENO);
|
||||||
|
writeFATSVHeader();
|
||||||
|
|
||||||
// Run it until we've lost either connection
|
// Run it until we've lost either connection
|
||||||
while (!Modes.exit && beast_input->connections && fatsv_output->connections) {
|
while (!Modes.exit && beast_input->connections && fatsv_output->connections) {
|
||||||
|
|
|
@ -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,10 +160,10 @@ 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_baro_valid)) {
|
||||||
snprintf(strFl, 7, "%5d ", convert_altitude(a->altitude));
|
snprintf(strFl, 7, "%5d ", convert_altitude(a->altitude_baro));
|
||||||
}
|
}
|
||||||
|
|
||||||
mvprintw(row, 0, "%s%06X %-4s %-4s %-8s %6s %3s %3s %7s %8s %5.1f %5d %2.0f",
|
mvprintw(row, 0, "%s%06X %-4s %-4s %-8s %6s %3s %3s %7s %8s %5.1f %5d %2.0f",
|
||||||
|
|
|
@ -146,10 +146,9 @@ void decodeModeAMessage(struct modesMessage *mm, int ModeA)
|
||||||
if (!mm->spi) {
|
if (!mm->spi) {
|
||||||
int modeC = modeAToModeC(ModeA);
|
int modeC = modeAToModeC(ModeA);
|
||||||
if (modeC != INVALID_ALTITUDE) {
|
if (modeC != INVALID_ALTITUDE) {
|
||||||
mm->altitude = modeC * 100;
|
mm->altitude_baro = modeC * 100;
|
||||||
mm->altitude_unit = UNIT_FEET;
|
mm->altitude_baro_unit = UNIT_FEET;
|
||||||
mm->altitude_source = ALTITUDE_BARO;
|
mm->altitude_baro_valid = 1;
|
||||||
mm->altitude_valid = 1;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
100
mode_s.h
Normal file
100
mode_s.h
Normal 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
|
2
net_io.h
2
net_io.h
|
@ -87,6 +87,8 @@ void modesInitNet(void);
|
||||||
void modesQueueOutput(struct modesMessage *mm, struct aircraft *a);
|
void modesQueueOutput(struct modesMessage *mm, struct aircraft *a);
|
||||||
void modesNetPeriodicWork(void);
|
void modesNetPeriodicWork(void);
|
||||||
|
|
||||||
|
void writeFATSVHeader();
|
||||||
|
|
||||||
// TODO: move these somewhere else
|
// TODO: move these somewhere else
|
||||||
char *generateAircraftJson(const char *url_path, int *len);
|
char *generateAircraftJson(const char *url_path, int *len);
|
||||||
char *generateStatsJson(const char *url_path, int *len);
|
char *generateStatsJson(const char *url_path, int *len);
|
||||||
|
|
|
@ -119,11 +119,7 @@ ChartBundleLayers = false;
|
||||||
//
|
//
|
||||||
BingMapsAPIKey = null;
|
BingMapsAPIKey = null;
|
||||||
|
|
||||||
// Provide a Mapzen API key here to enable the Mapzen vector tile layer.
|
// Turn on display of extra Mode S EHS / ADS-B v1/v2 data
|
||||||
// You can obtain a free key at https://mapzen.com/developers/
|
// This is not polished yet (and so is disabled by default),
|
||||||
// (you need a "vector tiles" key)
|
// currently it's just a data dump of the new fields with no UX work.
|
||||||
//
|
ExtendedData = false;
|
||||||
// Be sure to quote your key:
|
|
||||||
// MapzenAPIKey = "your key here";
|
|
||||||
//
|
|
||||||
MapzenAPIKey = null;
|
|
||||||
|
|
|
@ -103,10 +103,10 @@
|
||||||
<span id="selected_squawk"></span>
|
<span id="selected_squawk"></span>
|
||||||
</div>
|
</div>
|
||||||
<div class="infoHeading infoRowFluid fourColumnSection3">
|
<div class="infoHeading infoRowFluid fourColumnSection3">
|
||||||
Speed:
|
Groundspeed:
|
||||||
</div>
|
</div>
|
||||||
<div class="infoData infoRowFluid fourColumnSection4">
|
<div class="infoData infoRowFluid fourColumnSection4">
|
||||||
<span id="selected_speed">n/a</span>
|
<span id="selected_gs">n/a</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
|
@ -127,7 +127,7 @@
|
||||||
<div class="infoBlockSection lightGreyBackground">
|
<div class="infoBlockSection lightGreyBackground">
|
||||||
<div>
|
<div>
|
||||||
<div class="infoHeading infoRowFluid fourColumnSection1">
|
<div class="infoHeading infoRowFluid fourColumnSection1">
|
||||||
Heading:
|
Ground track:
|
||||||
</div>
|
</div>
|
||||||
<div class="infoData infoRowFluid fourColumnSection2">
|
<div class="infoData infoRowFluid fourColumnSection2">
|
||||||
<span id="selected_track">n/a</span>
|
<span id="selected_track">n/a</span>
|
||||||
|
@ -170,11 +170,73 @@
|
||||||
</div>
|
</div>
|
||||||
</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">
|
|
||||||
|
|
||||||
|
<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
|
AirFrames.org
|
||||||
</a>
|
</a>
|
||||||
<span id="selected_photo_link" class="link"></span>
|
<span id="selected_photo_link" class="link"></span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div> <!-- selected_infoblock -->
|
</div> <!-- selected_infoblock -->
|
||||||
|
|
||||||
|
|
|
@ -37,15 +37,11 @@ function createBaseLayers() {
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (MapzenAPIKey) {
|
|
||||||
world.push(createMapzenLayer());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ChartBundleLayers) {
|
if (ChartBundleLayers) {
|
||||||
var chartbundleTypes = {
|
var chartbundleTypes = {
|
||||||
sec: "Sectional Charts",
|
sec: "Sectional Charts",
|
||||||
tac: "Terminal Area Charts",
|
tac: "Terminal Area Charts",
|
||||||
wac: "World Aeronautical Charts",
|
hel: "Helicopter Charts",
|
||||||
enrl: "IFR Enroute Low Charts",
|
enrl: "IFR Enroute Low Charts",
|
||||||
enra: "IFR Area Charts",
|
enra: "IFR Area Charts",
|
||||||
enrh: "IFR Enroute High Charts"
|
enrh: "IFR Enroute High Charts"
|
||||||
|
@ -105,99 +101,3 @@ function createBaseLayers() {
|
||||||
|
|
||||||
return layers;
|
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: '© <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')]);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
|
@ -10,9 +10,32 @@ function PlaneObject(icao) {
|
||||||
this.category = null;
|
this.category = null;
|
||||||
|
|
||||||
// Basic location information
|
// Basic location information
|
||||||
this.altitude = null;
|
this.altitude = null;
|
||||||
this.speed = null;
|
this.alt_baro = null;
|
||||||
this.track = 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.prev_position = null;
|
||||||
this.position = null;
|
this.position = null;
|
||||||
this.position_from_mlat = false
|
this.position_from_mlat = false
|
||||||
|
@ -411,20 +434,34 @@ PlaneObject.prototype.updateData = function(receiver_timestamp, data) {
|
||||||
this.rssi = data.rssi;
|
this.rssi = data.rssi;
|
||||||
this.last_message_time = receiver_timestamp - data.seen;
|
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;
|
this.addrtype = data.type;
|
||||||
else
|
else
|
||||||
this.addrtype = 'adsb_icao';
|
this.addrtype = 'adsb_icao';
|
||||||
|
|
||||||
if (typeof data.altitude !== "undefined")
|
// don't expire callsigns
|
||||||
this.altitude = data.altitude;
|
if ('flight' in data)
|
||||||
if (typeof data.vert_rate !== "undefined")
|
this.flight = data.flight;
|
||||||
this.vert_rate = data.vert_rate;
|
|
||||||
if (typeof data.speed !== "undefined")
|
if ('lat' in data && 'lon' in data) {
|
||||||
this.speed = data.speed;
|
|
||||||
if (typeof data.track !== "undefined")
|
|
||||||
this.track = data.track;
|
|
||||||
if (typeof data.lat !== "undefined") {
|
|
||||||
this.position = [data.lon, data.lat];
|
this.position = [data.lon, data.lat];
|
||||||
this.last_position_time = receiver_timestamp - data.seen_pos;
|
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;
|
// Pick an altitude
|
||||||
if (typeof data.squawk !== "undefined")
|
if ('alt_baro' in data) {
|
||||||
this.squawk = data.squawk;
|
this.altitude = data.alt_baro;
|
||||||
if (typeof data.category !== "undefined")
|
} else if ('alt_geom' in data) {
|
||||||
this.category = data.category;
|
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) {
|
PlaneObject.prototype.updateTick = function(receiver_timestamp, last_timestamp) {
|
||||||
|
|
|
@ -198,6 +198,10 @@ function initialize() {
|
||||||
|
|
||||||
$("#loader").removeClass("hidden");
|
$("#loader").removeClass("hidden");
|
||||||
|
|
||||||
|
if (ExtendedData || window.location.hash == '#extended') {
|
||||||
|
$("#extendedData").removeClass("hidden");
|
||||||
|
}
|
||||||
|
|
||||||
// Set up map/sidebar splitter
|
// Set up map/sidebar splitter
|
||||||
$("#sidebar_container").resizable({handles: {w: '#splitter'}});
|
$("#sidebar_container").resizable({handles: {w: '#splitter'}});
|
||||||
|
|
||||||
|
@ -883,7 +887,7 @@ function refreshSelected() {
|
||||||
$('#selected_squawk').text(selected.squawk);
|
$('#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_vertical_rate').text(format_vert_rate_long(selected.vert_rate, DisplayUnits));
|
||||||
$('#selected_icao').text(selected.icao.toUpperCase());
|
$('#selected_icao').text(selected.icao.toUpperCase());
|
||||||
$('#airframes_post_icao').attr('value',selected.icao);
|
$('#airframes_post_icao').attr('value',selected.icao);
|
||||||
|
@ -936,6 +940,52 @@ function refreshSelected() {
|
||||||
$('#selected_rssi').text(selected.rssi.toFixed(1) + ' dBFS');
|
$('#selected_rssi').text(selected.rssi.toFixed(1) + ' dBFS');
|
||||||
$('#selected_message_count').text(selected.messages);
|
$('#selected_message_count').text(selected.messages);
|
||||||
$('#selected_photo_link').html(getFlightAwarePhotoLink(selected.registration));
|
$('#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() {
|
function refreshHighlighted() {
|
||||||
|
@ -1039,7 +1089,7 @@ function refreshTableInfo() {
|
||||||
tableplane.tr.cells[4].textContent = (tableplane.icaotype !== null ? tableplane.icaotype : "");
|
tableplane.tr.cells[4].textContent = (tableplane.icaotype !== null ? tableplane.icaotype : "");
|
||||||
tableplane.tr.cells[5].textContent = (tableplane.squawk !== null ? tableplane.squawk : "");
|
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[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[8].textContent = format_vert_rate_brief(tableplane.vert_rate, DisplayUnits);
|
||||||
tableplane.tr.cells[9].textContent = format_distance_brief(tableplane.sitedist, DisplayUnits);
|
tableplane.tr.cells[9].textContent = format_distance_brief(tableplane.sitedist, DisplayUnits);
|
||||||
tableplane.tr.cells[10].textContent = format_track_brief(tableplane.track);
|
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 sortByAircraftType() { sortBy('icaotype', compareAlpha, function(x) { return x.icaotype; }); }
|
||||||
function sortBySquawk() { sortBy('squawk', compareAlpha, function(x) { return x.squawk; }); }
|
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 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 sortByVerticalRate() { sortBy('vert_rate', compareNumeric, function(x) { return x.vert_rate; }); }
|
||||||
function sortByDistance() { sortBy('sitedist',compareNumeric, function(x) { return x.sitedist; }); }
|
function sortByDistance() { sortBy('sitedist',compareNumeric, function(x) { return x.sitedist; }); }
|
||||||
function sortByTrack() { sortBy('track', compareNumeric, function(x) { return x.track; }); }
|
function sortByTrack() { sortBy('track', compareNumeric, function(x) { return x.track; }); }
|
||||||
|
|
|
@ -311,8 +311,7 @@ static void *handle_bladerf_samples(struct bladerf *dev,
|
||||||
MODES_NOTUSED(num_samples);
|
MODES_NOTUSED(num_samples);
|
||||||
|
|
||||||
// record initial time for later sys timestamp calculation
|
// record initial time for later sys timestamp calculation
|
||||||
struct timespec entryTimestamp;
|
uint64_t entryTimestamp = mstime();
|
||||||
clock_gettime(CLOCK_REALTIME, &entryTimestamp);
|
|
||||||
|
|
||||||
pthread_mutex_lock(&Modes.data_mutex);
|
pthread_mutex_lock(&Modes.data_mutex);
|
||||||
if (Modes.exit) {
|
if (Modes.exit) {
|
||||||
|
@ -413,10 +412,8 @@ static void *handle_bladerf_samples(struct bladerf *dev,
|
||||||
|
|
||||||
if (blocks_processed) {
|
if (blocks_processed) {
|
||||||
// Get the approx system time for the start of this block
|
// Get the approx system time for the start of this block
|
||||||
unsigned block_duration = 1e9 * outbuf->length / Modes.sample_rate;
|
unsigned block_duration = 1e3 * outbuf->length / Modes.sample_rate;
|
||||||
outbuf->sysTimestamp = entryTimestamp;
|
outbuf->sysTimestamp = entryTimestamp - block_duration;
|
||||||
outbuf->sysTimestamp.tv_nsec -= block_duration;
|
|
||||||
normalize_timespec(&outbuf->sysTimestamp);
|
|
||||||
|
|
||||||
outbuf->mean_level /= blocks_processed;
|
outbuf->mean_level /= blocks_processed;
|
||||||
outbuf->mean_power /= blocks_processed;
|
outbuf->mean_power /= blocks_processed;
|
||||||
|
|
|
@ -217,7 +217,7 @@ void ifileRun()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the system time for the start of this block
|
// 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;
|
toread = MODES_MAG_BUF_SAMPLES * ifile.bytes_per_sample;
|
||||||
r = ifile.readbuf;
|
r = ifile.readbuf;
|
||||||
|
|
|
@ -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
|
// Compute the sample timestamp and system timestamp for the start of the block
|
||||||
outbuf->sampleTimestamp = sampleCounter * 12e6 / Modes.sample_rate;
|
outbuf->sampleTimestamp = sampleCounter * 12e6 / Modes.sample_rate;
|
||||||
sampleCounter += slen;
|
sampleCounter += slen;
|
||||||
block_duration = 1e9 * slen / Modes.sample_rate;
|
|
||||||
|
|
||||||
// Get the approx system time for the start of this block
|
// Get the approx system time for the start of this block
|
||||||
clock_gettime(CLOCK_REALTIME, &outbuf->sysTimestamp);
|
block_duration = 1e3 * slen / Modes.sample_rate;
|
||||||
outbuf->sysTimestamp.tv_nsec -= block_duration;
|
outbuf->sysTimestamp = mstime() - block_duration;
|
||||||
normalize_timespec(&outbuf->sysTimestamp);
|
|
||||||
|
|
||||||
// Copy trailing data from last block (or reset if not valid)
|
// Copy trailing data from last block (or reset if not valid)
|
||||||
if (outbuf->dropped == 0) {
|
if (outbuf->dropped == 0) {
|
||||||
|
|
620
track.c
620
track.c
|
@ -76,14 +76,62 @@ struct aircraft *trackCreateAircraft(struct modesMessage *mm) {
|
||||||
a->signalLevel[i] = 1e-5;
|
a->signalLevel[i] = 1e-5;
|
||||||
a->signalNext = 0;
|
a->signalNext = 0;
|
||||||
|
|
||||||
|
// defaults until we see a message otherwise
|
||||||
|
a->adsb_version = -1;
|
||||||
|
a->adsb_hrd = HEADING_MAGNETIC;
|
||||||
|
a->adsb_tah = HEADING_GROUND_TRACK;
|
||||||
|
|
||||||
|
// prime FATSV defaults we only emit on change
|
||||||
|
|
||||||
// start off with the "last emitted" ACAS RA being blank (just the BDS 3,0
|
// start off with the "last emitted" ACAS RA being blank (just the BDS 3,0
|
||||||
// or ES type code)
|
// or ES type code)
|
||||||
a->fatsv_emitted_bds_30[0] = 0x30;
|
a->fatsv_emitted_bds_30[0] = 0x30;
|
||||||
a->fatsv_emitted_es_acas_ra[0] = 0xE2;
|
a->fatsv_emitted_es_acas_ra[0] = 0xE2;
|
||||||
|
a->fatsv_emitted_adsb_version = -1;
|
||||||
|
a->fatsv_emitted_addrtype = ADDR_UNKNOWN;
|
||||||
|
|
||||||
|
// 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.
|
// Copy the first message so we can emit it later when a second message arrives.
|
||||||
a->first_message = *mm;
|
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++;
|
Modes.stats_current.unique_aircraft++;
|
||||||
|
|
||||||
return (a);
|
return (a);
|
||||||
|
@ -107,15 +155,18 @@ struct aircraft *trackFindAircraft(uint32_t addr) {
|
||||||
|
|
||||||
// Should we accept some new data from the given source?
|
// Should we accept some new data from the given source?
|
||||||
// If so, update the validity and return 1
|
// 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;
|
return 0;
|
||||||
|
|
||||||
d->source = source;
|
d->source = source;
|
||||||
d->updated = now;
|
d->updated = messageNow();
|
||||||
d->stale = now + 60000;
|
d->stale = messageNow() + (d->stale_interval ? d->stale_interval : 60000);
|
||||||
d->expires = now + 70000;
|
d->expires = messageNow() + (d->expire_interval ? d->expire_interval : 70000);
|
||||||
return 1;
|
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
|
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) {
|
static int compare_validity(const data_validity *lhs, const data_validity *rhs) {
|
||||||
if (now < lhs->stale && lhs->source > rhs->source)
|
if (messageNow() < lhs->stale && lhs->source > rhs->source)
|
||||||
return 1;
|
return 1;
|
||||||
else if (now < rhs->stale && lhs->source < rhs->source)
|
else if (messageNow() < rhs->stale && lhs->source < rhs->source)
|
||||||
return -1;
|
return -1;
|
||||||
else if (lhs->updated > rhs->updated)
|
else if (lhs->updated > rhs->updated)
|
||||||
return 1;
|
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
|
// 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.
|
// 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;
|
uint64_t elapsed;
|
||||||
double distance;
|
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))
|
if (!trackDataValid(&a->position_valid))
|
||||||
return 1; // no reference, assume OK
|
return 1; // no reference, assume OK
|
||||||
|
|
||||||
elapsed = trackDataAge(&a->position_valid, now);
|
elapsed = trackDataAge(&a->position_valid);
|
||||||
|
|
||||||
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
|
||||||
|
|
||||||
|
@ -251,24 +302,25 @@ static int speed_check(struct aircraft *a, double lat, double lon, uint64_t now,
|
||||||
return inrange;
|
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 result;
|
||||||
int fflag = mm->cpr_odd;
|
int fflag = mm->cpr_odd;
|
||||||
int surface = (mm->cpr_type == CPR_SURFACE);
|
int surface = (mm->cpr_type == CPR_SURFACE);
|
||||||
|
|
||||||
*nuc = (a->cpr_even_nuc < a->cpr_odd_nuc ? a->cpr_even_nuc : a->cpr_odd_nuc); // worst of the two positions
|
// derive NIC, Rc from the worse of the two position
|
||||||
|
// smaller NIC is worse; larger Rc is worse
|
||||||
|
*nic = (a->cpr_even_nic < a->cpr_odd_nic ? a->cpr_even_nic : a->cpr_odd_nic);
|
||||||
|
*rc = (a->cpr_even_rc > a->cpr_odd_rc ? a->cpr_even_rc : a->cpr_odd_rc);
|
||||||
|
|
||||||
if (surface) {
|
if (surface) {
|
||||||
// surface global CPR
|
// surface global CPR
|
||||||
// find reference location
|
// find reference location
|
||||||
double reflat, reflon;
|
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;
|
reflat = a->lat;
|
||||||
reflon = a->lon;
|
reflon = a->lon;
|
||||||
if (a->pos_nuc < *nuc)
|
|
||||||
*nuc = a->pos_nuc;
|
|
||||||
} else if (Modes.bUserFlags & MODES_USER_LATLON_VALID) {
|
} else if (Modes.bUserFlags & MODES_USER_LATLON_VALID) {
|
||||||
reflat = Modes.fUserLat;
|
reflat = Modes.fUserLat;
|
||||||
reflon = Modes.fUserLon;
|
reflon = Modes.fUserLon;
|
||||||
|
@ -320,7 +372,7 @@ static int doGlobalCPR(struct aircraft *a, struct modesMessage *mm, uint64_t now
|
||||||
return result;
|
return result;
|
||||||
|
|
||||||
// check speed limit
|
// 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++;
|
Modes.stats_current.cpr_global_speed_checks++;
|
||||||
return -2;
|
return -2;
|
||||||
}
|
}
|
||||||
|
@ -328,7 +380,7 @@ static int doGlobalCPR(struct aircraft *a, struct modesMessage *mm, uint64_t now
|
||||||
return result;
|
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
|
// relative CPR
|
||||||
// find reference location
|
// 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 fflag = mm->cpr_odd;
|
||||||
int surface = (mm->cpr_type == CPR_SURFACE);
|
int surface = (mm->cpr_type == CPR_SURFACE);
|
||||||
|
|
||||||
*nuc = mm->cpr_nucp;
|
if (fflag) {
|
||||||
|
*nic = a->cpr_odd_nic;
|
||||||
|
*rc = a->cpr_odd_rc;
|
||||||
|
} else {
|
||||||
|
*nic = a->cpr_even_nic;
|
||||||
|
*rc = a->cpr_even_rc;
|
||||||
|
}
|
||||||
|
|
||||||
if (trackDataValidEx(&a->position_valid, now, 50000, SOURCE_INVALID)) {
|
if (trackDataValid(&a->position_valid)) {
|
||||||
reflat = a->lat;
|
reflat = a->lat;
|
||||||
reflon = a->lon;
|
reflon = a->lon;
|
||||||
|
|
||||||
if (a->pos_nuc < *nuc)
|
if (a->pos_nic < *nic)
|
||||||
*nuc = a->pos_nuc;
|
*nic = a->pos_nic;
|
||||||
|
if (a->pos_rc < *rc)
|
||||||
|
*rc = a->pos_rc;
|
||||||
|
|
||||||
range_limit = 50e3;
|
range_limit = 50e3;
|
||||||
} else if (!surface && (Modes.bUserFlags & MODES_USER_LATLON_VALID)) {
|
} else if (!surface && (Modes.bUserFlags & MODES_USER_LATLON_VALID)) {
|
||||||
|
@ -394,7 +454,7 @@ static int doLocalCPR(struct aircraft *a, struct modesMessage *mm, uint64_t now,
|
||||||
}
|
}
|
||||||
|
|
||||||
// check speed limit
|
// 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
|
#ifdef DEBUG_CPR_CHECKS
|
||||||
fprintf(stderr, "Speed check for %06X with local decoding failed\n", a->addr);
|
fprintf(stderr, "Speed check for %06X with local decoding failed\n", a->addr);
|
||||||
#endif
|
#endif
|
||||||
|
@ -413,12 +473,13 @@ static uint64_t time_between(uint64_t t1, uint64_t t2)
|
||||||
return t2 - t1;
|
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;
|
int location_result = -1;
|
||||||
uint64_t max_elapsed;
|
uint64_t max_elapsed;
|
||||||
double new_lat = 0, new_lon = 0;
|
double new_lat = 0, new_lon = 0;
|
||||||
unsigned new_nuc = 0;
|
unsigned new_nic = 0;
|
||||||
|
unsigned new_rc = 0;
|
||||||
int surface;
|
int surface;
|
||||||
|
|
||||||
surface = (mm->cpr_type == CPR_SURFACE);
|
surface = (mm->cpr_type == CPR_SURFACE);
|
||||||
|
@ -427,7 +488,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.selected <= 25)
|
||||||
max_elapsed = 50000;
|
max_elapsed = 50000;
|
||||||
else
|
else
|
||||||
max_elapsed = 25000;
|
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 &&
|
a->cpr_odd_type == a->cpr_even_type &&
|
||||||
time_between(a->cpr_odd_valid.updated, a->cpr_even_valid.updated) <= max_elapsed) {
|
time_between(a->cpr_odd_valid.updated, a->cpr_even_valid.updated) <= max_elapsed) {
|
||||||
|
|
||||||
location_result = doGlobalCPR(a, mm, now, &new_lat, &new_lon, &new_nuc);
|
location_result = doGlobalCPR(a, mm, &new_lat, &new_lon, &new_nic, &new_rc);
|
||||||
|
|
||||||
if (location_result == -2) {
|
if (location_result == -2) {
|
||||||
#ifdef DEBUG_CPR_CHECKS
|
#ifdef DEBUG_CPR_CHECKS
|
||||||
|
@ -475,7 +536,7 @@ static void updatePosition(struct aircraft *a, struct modesMessage *mm, uint64_t
|
||||||
|
|
||||||
// Otherwise try relative CPR.
|
// Otherwise try relative CPR.
|
||||||
if (location_result == -1) {
|
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) {
|
if (location_result < 0) {
|
||||||
Modes.stats_current.cpr_local_skipped++;
|
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->cpr_decoded = 1;
|
||||||
mm->decoded_lat = new_lat;
|
mm->decoded_lat = new_lat;
|
||||||
mm->decoded_lon = new_lon;
|
mm->decoded_lon = new_lon;
|
||||||
|
mm->decoded_nic = new_nic;
|
||||||
|
mm->decoded_rc = new_rc;
|
||||||
|
|
||||||
// Update aircraft state
|
// Update aircraft state
|
||||||
a->lat = new_lat;
|
a->lat = new_lat;
|
||||||
a->lon = new_lon;
|
a->lon = new_lon;
|
||||||
a->pos_nuc = new_nuc;
|
a->pos_nic = new_nic;
|
||||||
|
a->pos_rc = new_rc;
|
||||||
|
|
||||||
update_range_histogram(new_lat, new_lon);
|
update_range_histogram(new_lat, new_lon);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static unsigned compute_nic(unsigned metype, unsigned version, unsigned nic_a, unsigned nic_b, unsigned nic_c)
|
||||||
|
{
|
||||||
|
switch (metype) {
|
||||||
|
case 5: // surface
|
||||||
|
case 9: // airborne
|
||||||
|
case 20: // airborne, GNSS altitude
|
||||||
|
return 11;
|
||||||
|
|
||||||
|
case 6: // surface
|
||||||
|
case 10: // airborne
|
||||||
|
case 21: // airborne, GNSS altitude
|
||||||
|
return 10;
|
||||||
|
|
||||||
|
case 7: // surface
|
||||||
|
if (version == 2) {
|
||||||
|
if (nic_a && !nic_c) {
|
||||||
|
return 9;
|
||||||
|
} else {
|
||||||
|
return 8;
|
||||||
|
}
|
||||||
|
} else if (version == 1) {
|
||||||
|
if (nic_a) {
|
||||||
|
return 9;
|
||||||
|
} else {
|
||||||
|
return 8;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return 8;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 8: // surface
|
||||||
|
if (version == 2) {
|
||||||
|
if (nic_a && nic_c) {
|
||||||
|
return 7;
|
||||||
|
} else if (nic_a && !nic_c) {
|
||||||
|
return 6;
|
||||||
|
} else if (!nic_a && nic_c) {
|
||||||
|
return 6;
|
||||||
|
} else {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 11: // airborne
|
||||||
|
if (version == 2) {
|
||||||
|
if (nic_a && nic_b) {
|
||||||
|
return 9;
|
||||||
|
} else {
|
||||||
|
return 8;
|
||||||
|
}
|
||||||
|
} else if (version == 1) {
|
||||||
|
if (nic_a) {
|
||||||
|
return 9;
|
||||||
|
} else {
|
||||||
|
return 8;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return 8;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 12: // airborne
|
||||||
|
return 7;
|
||||||
|
|
||||||
|
case 13: // airborne
|
||||||
|
return 6;
|
||||||
|
|
||||||
|
case 14: // airborne
|
||||||
|
return 5;
|
||||||
|
|
||||||
|
case 15: // airborne
|
||||||
|
return 4;
|
||||||
|
|
||||||
|
case 16: // airborne
|
||||||
|
if (nic_a && nic_b) {
|
||||||
|
return 3;
|
||||||
|
} else {
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 17: // airborne
|
||||||
|
return 1;
|
||||||
|
|
||||||
|
default:
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static unsigned compute_rc(unsigned metype, unsigned version, unsigned nic_a, unsigned nic_b, unsigned nic_c)
|
||||||
|
{
|
||||||
|
switch (metype) {
|
||||||
|
case 5: // surface
|
||||||
|
case 9: // airborne
|
||||||
|
case 20: // airborne, GNSS altitude
|
||||||
|
return 8; // 7.5m
|
||||||
|
|
||||||
|
case 6: // surface
|
||||||
|
case 10: // airborne
|
||||||
|
case 21: // airborne, GNSS altitude
|
||||||
|
return 25;
|
||||||
|
|
||||||
|
case 7: // surface
|
||||||
|
if (version == 2) {
|
||||||
|
if (nic_a && !nic_c) {
|
||||||
|
return 75;
|
||||||
|
} else {
|
||||||
|
return 186; // 185.2m, 0.1NM
|
||||||
|
}
|
||||||
|
} else if (version == 1) {
|
||||||
|
if (nic_a) {
|
||||||
|
return 75;
|
||||||
|
} else {
|
||||||
|
return 186; // 185.2m, 0.1NM
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return 186; // 185.2m, 0.1NM
|
||||||
|
}
|
||||||
|
|
||||||
|
case 8: // surface
|
||||||
|
if (version == 2) {
|
||||||
|
if (nic_a && nic_c) {
|
||||||
|
return 371; // 370.4m, 0.2NM
|
||||||
|
} else if (nic_a && !nic_c) {
|
||||||
|
return 556; // 555.6m, 0.3NM
|
||||||
|
} else if (!nic_a && nic_c) {
|
||||||
|
return 926; // 926m, 0.5NM
|
||||||
|
} else {
|
||||||
|
return RC_UNKNOWN;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return RC_UNKNOWN;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 11: // airborne
|
||||||
|
if (version == 2) {
|
||||||
|
if (nic_a && nic_b) {
|
||||||
|
return 75;
|
||||||
|
} else {
|
||||||
|
return 186; // 370.4m, 0.2NM
|
||||||
|
}
|
||||||
|
} else if (version == 1) {
|
||||||
|
if (nic_a) {
|
||||||
|
return 75;
|
||||||
|
} else {
|
||||||
|
return 186; // 370.4m, 0.2NM
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return 186; // 370.4m, 0.2NM
|
||||||
|
}
|
||||||
|
|
||||||
|
case 12: // airborne
|
||||||
|
return 371; // 370.4m, 0.2NM
|
||||||
|
|
||||||
|
case 13: // airborne
|
||||||
|
if (version == 2) {
|
||||||
|
if (!nic_a && nic_b) {
|
||||||
|
return 556; // 555.6m, 0.3NM
|
||||||
|
} else if (!nic_a && !nic_b) {
|
||||||
|
return 926; // 926m, 0.5NM
|
||||||
|
} else if (nic_a && nic_b) {
|
||||||
|
return 1112; // 1111.2m, 0.6NM
|
||||||
|
} else {
|
||||||
|
return 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;
|
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
|
// Lookup our aircraft or create a new one
|
||||||
a = trackFindAircraft(mm->addr);
|
a = trackFindAircraft(mm->addr);
|
||||||
|
@ -536,106 +833,240 @@ struct aircraft *trackUpdateFromMessage(struct modesMessage *mm)
|
||||||
a->signalLevel[a->signalNext] = mm->signalLevel;
|
a->signalLevel[a->signalNext] = mm->signalLevel;
|
||||||
a->signalNext = (a->signalNext + 1) & 7;
|
a->signalNext = (a->signalNext + 1) & 7;
|
||||||
}
|
}
|
||||||
a->seen = now;
|
a->seen = messageNow();
|
||||||
a->messages++;
|
a->messages++;
|
||||||
|
|
||||||
// update addrtype, we only ever go towards "more direct" types
|
// update addrtype, we only ever go towards "more direct" types
|
||||||
if (mm->addrtype < a->addrtype)
|
if (mm->addrtype < a->addrtype)
|
||||||
a->addrtype = mm->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) {
|
if (a->modeC_hit) {
|
||||||
int new_modeC = (a->altitude + 49) / 100;
|
int new_modeC = (a->altitude_baro + 49) / 100;
|
||||||
int old_modeC = (mm->altitude + 49) / 100;
|
int old_modeC = (alt + 49) / 100;
|
||||||
if (new_modeC != old_modeC) {
|
if (new_modeC != old_modeC) {
|
||||||
a->modeC_hit = 0;
|
a->modeC_hit = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
a->altitude = mm->altitude;
|
a->altitude_baro = alt;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mm->squawk_valid && accept_data(&a->squawk_valid, mm->source, now)) {
|
if (mm->squawk_valid && accept_data(&a->squawk_valid, mm->source)) {
|
||||||
if (mm->squawk != a->squawk) {
|
if (mm->squawk != a->squawk) {
|
||||||
a->modeA_hit = 0;
|
a->modeA_hit = 0;
|
||||||
}
|
}
|
||||||
a->squawk = mm->squawk;
|
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)) {
|
if (mm->emergency_valid && accept_data(&a->emergency_valid, mm->source)) {
|
||||||
a->altitude_gnss = mm->altitude;
|
a->emergency = mm->emergency;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mm->gnss_delta_valid && accept_data(&a->gnss_delta_valid, mm->source, now)) {
|
if (mm->altitude_geom_valid && accept_data(&a->altitude_geom_valid, mm->source)) {
|
||||||
a->gnss_delta = mm->gnss_delta;
|
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)) {
|
if (mm->geom_delta_valid && accept_data(&a->geom_delta_valid, mm->source)) {
|
||||||
a->heading = mm->heading;
|
a->geom_delta = mm->geom_delta;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mm->heading_valid && mm->heading_source == HEADING_MAGNETIC && accept_data(&a->heading_magnetic_valid, mm->source, now)) {
|
if (mm->heading_valid) {
|
||||||
a->heading_magnetic = mm->heading;
|
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)) {
|
if (mm->track_rate_valid && accept_data(&a->track_rate_valid, mm->source)) {
|
||||||
a->speed = mm->speed;
|
a->track_rate = mm->track_rate;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mm->speed_valid && mm->speed_source == SPEED_IAS && accept_data(&a->speed_ias_valid, mm->source, now)) {
|
if (mm->roll_valid && accept_data(&a->roll_valid, mm->source)) {
|
||||||
a->speed_ias = mm->speed;
|
a->roll = mm->roll;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mm->speed_valid && mm->speed_source == SPEED_TAS && accept_data(&a->speed_tas_valid, mm->source, now)) {
|
if (mm->gs_valid) {
|
||||||
a->speed_tas = mm->speed;
|
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)) {
|
if (mm->ias_valid && accept_data(&a->ias_valid, mm->source)) {
|
||||||
a->vert_rate = mm->vert_rate;
|
a->ias = mm->ias;
|
||||||
a->vert_rate_source = mm->vert_rate_source;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mm->category_valid && accept_data(&a->category_valid, mm->source, now)) {
|
if (mm->tas_valid && accept_data(&a->tas_valid, mm->source)) {
|
||||||
a->category = mm->category;
|
a->tas = mm->tas;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mm->airground != AG_INVALID && accept_data(&a->airground_valid, mm->source, now)) {
|
if (mm->mach_valid && accept_data(&a->mach_valid, mm->source)) {
|
||||||
a->airground = mm->airground;
|
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));
|
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
|
// 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_type = mm->cpr_type;
|
||||||
a->cpr_even_lat = mm->cpr_lat;
|
a->cpr_even_lat = mm->cpr_lat;
|
||||||
a->cpr_even_lon = mm->cpr_lon;
|
a->cpr_even_lon = mm->cpr_lon;
|
||||||
a->cpr_even_nuc = mm->cpr_nucp;
|
compute_nic_rc_from_message(mm, a, &a->cpr_even_nic, &a->cpr_even_rc);
|
||||||
}
|
}
|
||||||
|
|
||||||
// CPR, odd
|
// CPR, odd
|
||||||
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_type = mm->cpr_type;
|
||||||
a->cpr_odd_lat = mm->cpr_lat;
|
a->cpr_odd_lat = mm->cpr_lat;
|
||||||
a->cpr_odd_lon = mm->cpr_lon;
|
a->cpr_odd_lon = mm->cpr_lon;
|
||||||
a->cpr_odd_nuc = mm->cpr_nucp;
|
compute_nic_rc_from_message(mm, a, &a->cpr_odd_nic, &a->cpr_odd_rc);
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
// 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_baro_valid, &a->altitude_geom_valid) > 0 &&
|
||||||
compare_validity(&a->gnss_delta_valid, &a->altitude_gnss_valid, now) > 0) {
|
compare_validity(&a->geom_delta_valid, &a->altitude_geom_valid) > 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_baro + a->geom_delta;
|
||||||
combine_validity(&a->altitude_gnss_valid, &a->altitude_valid, &a->gnss_delta_valid);
|
combine_validity(&a->altitude_geom_valid, &a->altitude_baro_valid, &a->geom_delta_valid);
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we've got a new cprlat or cprlon
|
// If we've got a new cprlat or cprlon
|
||||||
if (mm->cpr_valid) {
|
if (mm->cpr_valid) {
|
||||||
updatePosition(a, mm, now);
|
updatePosition(a, mm);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (a);
|
return (a);
|
||||||
|
@ -669,8 +1100,8 @@ static void trackMatchAC(uint64_t now)
|
||||||
}
|
}
|
||||||
|
|
||||||
// match on Mode C (+/- 100ft)
|
// match on Mode C (+/- 100ft)
|
||||||
if (trackDataValid(&a->altitude_valid)) {
|
if (trackDataValid(&a->altitude_baro_valid)) {
|
||||||
int modeC = (a->altitude + 49) / 100;
|
int modeC = (a->altitude_baro + 49) / 100;
|
||||||
|
|
||||||
unsigned modeA = modeCToModeA(modeC);
|
unsigned modeA = modeCToModeA(modeC);
|
||||||
unsigned i = modeAToIndex(modeA);
|
unsigned i = modeAToIndex(modeA);
|
||||||
|
@ -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)
|
#define EXPIRE(_f) do { if (a->_f##_valid.source != SOURCE_INVALID && now >= a->_f##_valid.expires) { a->_f##_valid.source = SOURCE_INVALID; } } while (0)
|
||||||
EXPIRE(callsign);
|
EXPIRE(callsign);
|
||||||
EXPIRE(altitude);
|
EXPIRE(altitude_baro);
|
||||||
EXPIRE(altitude_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(true_heading);
|
||||||
|
EXPIRE(baro_rate);
|
||||||
|
EXPIRE(geom_rate);
|
||||||
EXPIRE(squawk);
|
EXPIRE(squawk);
|
||||||
EXPIRE(category);
|
|
||||||
EXPIRE(airground);
|
EXPIRE(airground);
|
||||||
|
EXPIRE(nav_qnh);
|
||||||
|
EXPIRE(nav_altitude);
|
||||||
|
EXPIRE(nav_heading);
|
||||||
|
EXPIRE(nav_modes);
|
||||||
EXPIRE(cpr_odd);
|
EXPIRE(cpr_odd);
|
||||||
EXPIRE(cpr_even);
|
EXPIRE(cpr_even);
|
||||||
EXPIRE(position);
|
EXPIRE(position);
|
||||||
|
EXPIRE(nic_a);
|
||||||
|
EXPIRE(nic_c);
|
||||||
|
EXPIRE(nic_baro);
|
||||||
|
EXPIRE(nac_p);
|
||||||
|
EXPIRE(sil);
|
||||||
|
EXPIRE(gva);
|
||||||
|
EXPIRE(sda);
|
||||||
|
#undef EXPIRE
|
||||||
prev = a; a = a->next;
|
prev = a; a = a->next;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
182
track.h
182
track.h
|
@ -64,11 +64,21 @@
|
||||||
*/
|
*/
|
||||||
#define TRACK_MODEAC_MIN_MESSAGES 4
|
#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 {
|
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 */
|
datasource_t source; /* where the data came from */
|
||||||
uint64_t updated; /* when it arrived */
|
uint64_t updated; /* when it arrived */
|
||||||
uint64_t stale; /* when it will become stale */
|
uint64_t stale; /* when it goes stale */
|
||||||
uint64_t expires; /* when it will expire */
|
uint64_t expires; /* when it expires */
|
||||||
} data_validity;
|
} data_validity;
|
||||||
|
|
||||||
/* Structure used to describe the state of one tracked aircraft */
|
/* Structure used to describe the state of one tracked aircraft */
|
||||||
|
@ -85,77 +95,153 @@ struct aircraft {
|
||||||
data_validity callsign_valid;
|
data_validity callsign_valid;
|
||||||
char callsign[9]; // Flight number
|
char callsign[9]; // Flight number
|
||||||
|
|
||||||
data_validity altitude_valid;
|
data_validity altitude_baro_valid;
|
||||||
int altitude; // Altitude (Baro)
|
int altitude_baro; // Altitude (Baro)
|
||||||
|
|
||||||
data_validity altitude_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;
|
float 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 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;
|
data_validity squawk_valid;
|
||||||
unsigned squawk; // Squawk
|
unsigned squawk; // Squawk
|
||||||
|
|
||||||
data_validity category_valid;
|
data_validity emergency_valid;
|
||||||
unsigned category; // Aircraft category A0 - D7 encoded as a single hex byte
|
emergency_t emergency; // Emergency/priority status
|
||||||
|
|
||||||
|
unsigned category; // Aircraft category A0 - D7 encoded as a single hex byte. 00 = unset
|
||||||
|
|
||||||
data_validity airground_valid;
|
data_validity airground_valid;
|
||||||
airground_t airground; // air/ground status
|
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
|
data_validity cpr_odd_valid; // Last seen even CPR message
|
||||||
cpr_type_t cpr_odd_type;
|
cpr_type_t cpr_odd_type;
|
||||||
unsigned cpr_odd_lat;
|
unsigned cpr_odd_lat;
|
||||||
unsigned cpr_odd_lon;
|
unsigned cpr_odd_lon;
|
||||||
unsigned cpr_odd_nuc;
|
unsigned cpr_odd_nic;
|
||||||
|
unsigned cpr_odd_rc;
|
||||||
|
|
||||||
data_validity cpr_even_valid; // Last seen odd CPR message
|
data_validity cpr_even_valid; // Last seen odd CPR message
|
||||||
cpr_type_t cpr_even_type;
|
cpr_type_t cpr_even_type;
|
||||||
unsigned cpr_even_lat;
|
unsigned cpr_even_lat;
|
||||||
unsigned cpr_even_lon;
|
unsigned cpr_even_lon;
|
||||||
unsigned cpr_even_nuc;
|
unsigned cpr_even_nic;
|
||||||
|
unsigned cpr_even_rc;
|
||||||
|
|
||||||
data_validity position_valid;
|
data_validity position_valid;
|
||||||
double lat, lon; // Coordinated obtained from CPR encoded data
|
double lat, lon; // Coordinated obtained from CPR encoded data
|
||||||
unsigned pos_nuc; // NUCp of last computed position
|
unsigned pos_nic; // NIC of last computed position
|
||||||
|
unsigned pos_rc; // Rc of last computed position
|
||||||
|
|
||||||
|
// data extracted from opstatus etc
|
||||||
|
int adsb_version; // ADS-B version (from ADS-B operational status); -1 means no ADS-B messages seen
|
||||||
|
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 modeA_hit; // did our squawk match a possible mode A reply in the last check period?
|
||||||
int modeC_hit; // did our altitude match a possible mode C reply in the last check period?
|
int modeC_hit; // did our altitude match a possible mode C reply in the last check period?
|
||||||
|
|
||||||
int fatsv_emitted_altitude; // last FA emitted altitude
|
int fatsv_emitted_altitude_baro; // last FA emitted altitude
|
||||||
int fatsv_emitted_altitude_gnss; // -"- GNSS altitude
|
int fatsv_emitted_altitude_geom; // -"- GNSS altitude
|
||||||
int fatsv_emitted_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_track; // -"- true track
|
||||||
int fatsv_emitted_speed_ias; // -"- IAS
|
float fatsv_emitted_track_rate; // -"- track rate of change
|
||||||
int fatsv_emitted_speed_tas; // -"- TAS
|
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
|
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_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
|
||||||
|
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_emitted; // time (millis) aircraft was last FA emitted
|
||||||
|
uint64_t fatsv_last_force_emit; // time (millis) we last emitted only-on-change data
|
||||||
|
|
||||||
struct aircraft *next; // Next aircraft in our linked list
|
struct aircraft *next; // Next aircraft in our linked list
|
||||||
|
|
||||||
|
@ -173,33 +259,23 @@ extern uint32_t modeAC_age[4096];
|
||||||
/* is this bit of data valid? */
|
/* is this bit of data valid? */
|
||||||
static inline int trackDataValid(const data_validity *v)
|
static inline int trackDataValid(const data_validity *v)
|
||||||
{
|
{
|
||||||
return (v->source != SOURCE_INVALID);
|
return (v->source != SOURCE_INVALID && messageNow() < v->expires);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* .. with these constraints? */
|
/* is this bit of data fresh? */
|
||||||
static inline int trackDataValidEx(const data_validity *v,
|
static inline int trackDataFresh(const data_validity *v)
|
||||||
uint64_t now,
|
|
||||||
uint64_t maxAge,
|
|
||||||
datasource_t minSource)
|
|
||||||
{
|
{
|
||||||
if (v->source == SOURCE_INVALID)
|
return (v->source != SOURCE_INVALID && messageNow() < v->stale);
|
||||||
return 0;
|
|
||||||
if (v->source < minSource)
|
|
||||||
return 0;
|
|
||||||
if (v->updated < now && (now - v->updated) > maxAge)
|
|
||||||
return 0;
|
|
||||||
return 1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* what's the age of this data? */
|
/* what's the age of this data, in milliseconds? */
|
||||||
static inline uint64_t trackDataAge(const data_validity *v,
|
static inline uint64_t trackDataAge(const data_validity *v)
|
||||||
uint64_t now)
|
|
||||||
{
|
{
|
||||||
if (v->source == SOURCE_INVALID)
|
if (v->source == SOURCE_INVALID)
|
||||||
return ~(uint64_t)0;
|
return ~(uint64_t)0;
|
||||||
if (v->updated >= now)
|
if (v->updated >= messageNow())
|
||||||
return 0;
|
return 0;
|
||||||
return (now - v->updated);
|
return (messageNow() - v->updated);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Update aircraft state from data in the provided mesage.
|
/* Update aircraft state from data in the provided mesage.
|
||||||
|
|
7
util.c
7
util.c
|
@ -52,6 +52,8 @@
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <sys/time.h>
|
#include <sys/time.h>
|
||||||
|
|
||||||
|
uint64_t _messageNow = 0;
|
||||||
|
|
||||||
uint64_t mstime(void)
|
uint64_t mstime(void)
|
||||||
{
|
{
|
||||||
struct timeval tv;
|
struct timeval tv;
|
||||||
|
@ -68,6 +70,11 @@ int64_t receiveclock_ns_elapsed(uint64_t t1, uint64_t t2)
|
||||||
return (t2 - t1) * 1000U / 12U;
|
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)
|
void normalize_timespec(struct timespec *ts)
|
||||||
{
|
{
|
||||||
if (ts->tv_nsec > 1000000000) {
|
if (ts->tv_nsec > 1000000000) {
|
||||||
|
|
9
util.h
9
util.h
|
@ -25,11 +25,20 @@
|
||||||
/* Returns system time in milliseconds */
|
/* Returns system time in milliseconds */
|
||||||
uint64_t mstime(void);
|
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,
|
/* Returns the time elapsed, in nanoseconds, from t1 to t2,
|
||||||
* where t1 and t2 are 12MHz counters.
|
* where t1 and t2 are 12MHz counters.
|
||||||
*/
|
*/
|
||||||
int64_t receiveclock_ns_elapsed(uint64_t t1, uint64_t t2);
|
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
|
/* Normalize the value in ts so that ts->nsec lies in
|
||||||
* [0,999999999]
|
* [0,999999999]
|
||||||
*/
|
*/
|
||||||
|
|
Loading…
Reference in a new issue