WIP: More Comm-B & ADS-B decoding.
This commit is contained in:
parent
2e0aba4f1f
commit
98d64483d6
6
Makefile
6
Makefile
|
@ -45,13 +45,13 @@ all: dump1090 view1090
|
||||||
%.o: %.c *.h
|
%.o: %.c *.h
|
||||||
$(CC) $(CPPFLAGS) $(CFLAGS) -c $< -o $@
|
$(CC) $(CPPFLAGS) $(CFLAGS) -c $< -o $@
|
||||||
|
|
||||||
dump1090: dump1090.o anet.o interactive.o mode_ac.o mode_s.o net_io.o crc.o demod_2400.o stats.o cpr.o icao_filter.o track.o util.o convert.o sdr_ifile.o sdr.o $(SDR_OBJ) $(COMPAT)
|
dump1090: dump1090.o anet.o interactive.o mode_ac.o mode_s.o comm_b.o net_io.o crc.o demod_2400.o stats.o cpr.o icao_filter.o track.o util.o convert.o sdr_ifile.o sdr.o $(SDR_OBJ) $(COMPAT)
|
||||||
$(CC) -g -o $@ $^ $(LDFLAGS) $(LIBS) $(LIBS_SDR) -lncurses
|
$(CC) -g -o $@ $^ $(LDFLAGS) $(LIBS) $(LIBS_SDR) -lncurses
|
||||||
|
|
||||||
view1090: view1090.o anet.o interactive.o mode_ac.o mode_s.o net_io.o crc.o stats.o cpr.o icao_filter.o track.o util.o $(COMPAT)
|
view1090: view1090.o anet.o interactive.o mode_ac.o mode_s.o comm_b.o net_io.o crc.o stats.o cpr.o icao_filter.o track.o util.o $(COMPAT)
|
||||||
$(CC) -g -o $@ $^ $(LDFLAGS) $(LIBS) -lncurses
|
$(CC) -g -o $@ $^ $(LDFLAGS) $(LIBS) -lncurses
|
||||||
|
|
||||||
faup1090: faup1090.o anet.o mode_ac.o mode_s.o net_io.o crc.o stats.o cpr.o icao_filter.o track.o util.o $(COMPAT)
|
faup1090: faup1090.o anet.o mode_ac.o mode_s.o comm_b.o net_io.o crc.o stats.o cpr.o icao_filter.o track.o util.o $(COMPAT)
|
||||||
$(CC) -g -o $@ $^ $(LDFLAGS) $(LIBS)
|
$(CC) -g -o $@ $^ $(LDFLAGS) $(LIBS)
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
|
|
735
comm_b.c
Normal file
735
comm_b.c
Normal file
|
@ -0,0 +1,735 @@
|
||||||
|
// Part of dump1090, a Mode S message decoder for RTLSDR devices.
|
||||||
|
//
|
||||||
|
// comm_b.c: Comm-B message decoding
|
||||||
|
//
|
||||||
|
// Copyright (c) 2017 FlightAware, LLC
|
||||||
|
// Copyright (c) 2017 Oliver Jowett <oliver@mutability.co.uk>
|
||||||
|
//
|
||||||
|
// This file is free software: you may copy, redistribute and/or modify it
|
||||||
|
// under the terms of the GNU General Public License as published by the
|
||||||
|
// Free Software Foundation, either version 2 of the License, or (at your
|
||||||
|
// option) any later version.
|
||||||
|
//
|
||||||
|
// This file is distributed in the hope that it will be useful, but
|
||||||
|
// WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
|
// General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
#include "dump1090.h"
|
||||||
|
|
||||||
|
typedef int (*CommBDecoderFn)(struct modesMessage *,bool);
|
||||||
|
|
||||||
|
static int decodeEmptyResponse(struct modesMessage *mm, bool store);
|
||||||
|
static int decodeBDS10(struct modesMessage *mm, bool store);
|
||||||
|
static int decodeBDS17(struct modesMessage *mm, bool store);
|
||||||
|
static int decodeBDS20(struct modesMessage *mm, bool store);
|
||||||
|
static int decodeBDS30(struct modesMessage *mm, bool store);
|
||||||
|
static int decodeBDS40(struct modesMessage *mm, bool store);
|
||||||
|
static int decodeBDS50(struct modesMessage *mm, bool store);
|
||||||
|
static int decodeBDS60(struct modesMessage *mm, bool store);
|
||||||
|
|
||||||
|
static CommBDecoderFn comm_b_decoders[] = {
|
||||||
|
&decodeEmptyResponse,
|
||||||
|
&decodeBDS10,
|
||||||
|
&decodeBDS20,
|
||||||
|
&decodeBDS30,
|
||||||
|
&decodeBDS17,
|
||||||
|
&decodeBDS40,
|
||||||
|
&decodeBDS50,
|
||||||
|
&decodeBDS60
|
||||||
|
};
|
||||||
|
|
||||||
|
void decodeCommB(struct modesMessage *mm)
|
||||||
|
{
|
||||||
|
mm->commb_format = COMMB_UNKNOWN;
|
||||||
|
|
||||||
|
// If DR or UM are set, this message is _probably_ noise
|
||||||
|
// as nothing really seems to use the multisite broadcast stuff?
|
||||||
|
// Also skip anything that had errors corrected
|
||||||
|
if (mm->DR != 0 || mm->UM != 0 || mm->correctedbits > 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is a bit hairy as we don't know what the requested register was
|
||||||
|
int bestScore = 0;
|
||||||
|
CommBDecoderFn bestDecoder = NULL;
|
||||||
|
|
||||||
|
for (unsigned i = 0; i < (sizeof(comm_b_decoders) / sizeof(comm_b_decoders[0])); ++i) {
|
||||||
|
int score = comm_b_decoders[i](mm, false);
|
||||||
|
if (score > bestScore) {
|
||||||
|
bestScore = score;
|
||||||
|
bestDecoder = comm_b_decoders[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bestDecoder) {
|
||||||
|
// decode it
|
||||||
|
bestDecoder(mm, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static int decodeEmptyResponse(struct modesMessage *mm, bool store)
|
||||||
|
{
|
||||||
|
for (unsigned i = 0; i < 7; ++i) {
|
||||||
|
if (mm->MB[i] != 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (store) {
|
||||||
|
mm->commb_format = COMMB_EMPTY_RESPONSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 56;
|
||||||
|
}
|
||||||
|
|
||||||
|
// BDS1,0 Datalink capabilities
|
||||||
|
static int decodeBDS10(struct modesMessage *mm, bool store)
|
||||||
|
{
|
||||||
|
unsigned char *msg = mm->MB;
|
||||||
|
|
||||||
|
// BDS identifier
|
||||||
|
if (msg[0] != 0x10) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reserved bits
|
||||||
|
if (getbits(msg, 10, 15) != 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Looks plausible.
|
||||||
|
|
||||||
|
if (store) {
|
||||||
|
mm->commb_format = COMMB_DATALINK_CAPS;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 56;
|
||||||
|
}
|
||||||
|
|
||||||
|
// BDS1,7 Common usage GICB capability report
|
||||||
|
static int decodeBDS17(struct modesMessage *mm, bool store)
|
||||||
|
{
|
||||||
|
unsigned char *msg = mm->MB;
|
||||||
|
|
||||||
|
// reserved bits
|
||||||
|
if (getbits(msg, 25, 56) != 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int score = 0;
|
||||||
|
if (getbit(msg, 7)) {
|
||||||
|
score += 1; // 2,0 aircraft identification
|
||||||
|
} else {
|
||||||
|
// BDS2,0 is on almost everything
|
||||||
|
score -= 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
// unlikely bits
|
||||||
|
if (getbit(msg, 10)) { // 4,1 next waypoint identifier
|
||||||
|
score -= 2;
|
||||||
|
}
|
||||||
|
if (getbit(msg, 11)) { // 4,2 next waypoint position
|
||||||
|
score -= 2;
|
||||||
|
}
|
||||||
|
if (getbit(msg, 12)) { // 4,3 next waypoint information
|
||||||
|
score -= 2;
|
||||||
|
}
|
||||||
|
if (getbit(msg, 13)) { // 4,4 meterological routine report
|
||||||
|
score -= 2;
|
||||||
|
}
|
||||||
|
if (getbit(msg, 14)) { // 4,4 meterological hazard report
|
||||||
|
score -= 2;
|
||||||
|
}
|
||||||
|
if (getbit(msg, 20)) { // 5,4 waypoint 1
|
||||||
|
score -= 2;
|
||||||
|
}
|
||||||
|
if (getbit(msg, 21)) { // 5,5 waypoint 2
|
||||||
|
score -= 2;
|
||||||
|
}
|
||||||
|
if (getbit(msg, 22)) { // 5,6 waypoint 3
|
||||||
|
score -= 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (getbit(msg, 1) && getbit(msg, 2) && getbit(msg, 3) && getbit(msg, 4) && getbit(msg, 5)) {
|
||||||
|
// looks like ES capable
|
||||||
|
score += 5;
|
||||||
|
if (getbit(msg, 6)) {
|
||||||
|
// ES EDI
|
||||||
|
score += 1;
|
||||||
|
}
|
||||||
|
} else if (!getbit(msg, 1) && !getbit(msg, 2) && !getbit(msg, 3) && !getbit(msg, 4) && !getbit(msg, 5) && !getbit(msg, 6)) {
|
||||||
|
// not ES capable
|
||||||
|
score += 1;
|
||||||
|
} else {
|
||||||
|
// partial ES support, unlikely
|
||||||
|
score -= 12;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (getbit(msg, 16) && getbit(msg, 24)) {
|
||||||
|
// track/turn, heading/speed
|
||||||
|
score += 2;
|
||||||
|
if (getbit(msg, 9)) {
|
||||||
|
// vertical intent
|
||||||
|
score += 1;
|
||||||
|
}
|
||||||
|
} else if (!getbit(msg, 16) && !getbit(msg, 24) && !getbit(msg, 9)) {
|
||||||
|
// neither
|
||||||
|
score += 1;
|
||||||
|
} else {
|
||||||
|
// unlikely
|
||||||
|
score -= 6;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (store) {
|
||||||
|
mm->commb_format = COMMB_GICB_CAPS;
|
||||||
|
}
|
||||||
|
|
||||||
|
return score;
|
||||||
|
}
|
||||||
|
|
||||||
|
// BDS2,0 Aircraft identification
|
||||||
|
static int decodeBDS20(struct modesMessage *mm, bool store)
|
||||||
|
{
|
||||||
|
char callsign[9];
|
||||||
|
unsigned char *msg = mm->MB;
|
||||||
|
|
||||||
|
// BDS identifier
|
||||||
|
if (msg[0] != 0x20) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
callsign[0] = ais_charset[getbits(msg, 9, 14)];
|
||||||
|
callsign[1] = ais_charset[getbits(msg, 15, 20)];
|
||||||
|
callsign[2] = ais_charset[getbits(msg, 21, 26)];
|
||||||
|
callsign[3] = ais_charset[getbits(msg, 27, 32)];
|
||||||
|
callsign[4] = ais_charset[getbits(msg, 33, 38)];
|
||||||
|
callsign[5] = ais_charset[getbits(msg, 39, 44)];
|
||||||
|
callsign[6] = ais_charset[getbits(msg, 45, 50)];
|
||||||
|
callsign[7] = ais_charset[getbits(msg, 51, 56)];
|
||||||
|
callsign[8] = 0;
|
||||||
|
|
||||||
|
// score based on number of valid non-space characters
|
||||||
|
int score = 8;
|
||||||
|
for (unsigned i = 0; i < 8; ++i) {
|
||||||
|
if ((callsign[i] >= 'A' && callsign[i] <= 'Z') || (callsign[i] >= '0' && callsign[i] <= '9')) {
|
||||||
|
score += 6;
|
||||||
|
} else if (callsign[i] == ' ') {
|
||||||
|
// Valid, but not informative
|
||||||
|
} else {
|
||||||
|
// Invalid
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (store) {
|
||||||
|
mm->commb_format = COMMB_AIRCRAFT_IDENT;
|
||||||
|
memcpy(mm->callsign, callsign, sizeof(mm->callsign));
|
||||||
|
mm->callsign_valid = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return score;
|
||||||
|
}
|
||||||
|
|
||||||
|
// BDS3,0 ACAS RA
|
||||||
|
static int decodeBDS30(struct modesMessage *mm, bool store)
|
||||||
|
{
|
||||||
|
unsigned char *msg = mm->MB;
|
||||||
|
|
||||||
|
// BDS identifier
|
||||||
|
if (msg[0] != 0x30) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (store) {
|
||||||
|
mm->commb_format = COMMB_ACAS_RA;
|
||||||
|
}
|
||||||
|
|
||||||
|
// just accept it.
|
||||||
|
return 56;
|
||||||
|
}
|
||||||
|
|
||||||
|
// BDS4,0 Selected vertical intention
|
||||||
|
static int decodeBDS40(struct modesMessage *mm, bool store)
|
||||||
|
{
|
||||||
|
unsigned char *msg = mm->MB;
|
||||||
|
|
||||||
|
unsigned mcp_valid = getbit(msg, 1);
|
||||||
|
unsigned mcp_raw = getbits(msg, 2, 13);
|
||||||
|
unsigned fms_valid = getbit(msg, 14);
|
||||||
|
unsigned fms_raw = getbits(msg, 15, 26);
|
||||||
|
unsigned baro_valid = getbit(msg, 27);
|
||||||
|
unsigned baro_raw = getbits(msg, 28, 39);
|
||||||
|
unsigned reserved_1 = getbits(msg, 40, 47);
|
||||||
|
unsigned mode_valid = getbit(msg, 48);
|
||||||
|
unsigned mode_raw = getbits(msg, 49, 51);
|
||||||
|
unsigned reserved_2 = getbits(msg, 52, 53);
|
||||||
|
unsigned source_valid = getbit(msg, 54);
|
||||||
|
unsigned source_raw = getbits(msg, 55, 56);
|
||||||
|
|
||||||
|
if (!mcp_valid && !fms_valid && !baro_valid && !mode_valid && !source_valid) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int score = 0;
|
||||||
|
|
||||||
|
unsigned mcp_alt = 0;
|
||||||
|
if (mcp_valid && mcp_raw != 0) {
|
||||||
|
mcp_alt = mcp_raw * 16;
|
||||||
|
if (mcp_alt >= 1000 && mcp_alt <= 50000) {
|
||||||
|
score += 13;
|
||||||
|
}
|
||||||
|
} else if (!mcp_valid && mcp_raw == 0) {
|
||||||
|
score += 1;
|
||||||
|
} else {
|
||||||
|
score -= 130;
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned fms_alt = 0;
|
||||||
|
if (fms_valid && fms_raw != 0) {
|
||||||
|
fms_alt = fms_raw * 16;
|
||||||
|
if (fms_alt >= 1000 && fms_alt <= 50000) {
|
||||||
|
score += 13;
|
||||||
|
}
|
||||||
|
} else if (!fms_valid && fms_raw == 0) {
|
||||||
|
score += 1;
|
||||||
|
} else {
|
||||||
|
score -= 130;
|
||||||
|
}
|
||||||
|
|
||||||
|
float baro_setting = 0;
|
||||||
|
if (baro_valid && baro_raw != 0) {
|
||||||
|
baro_setting = 800 + baro_raw * 0.1;
|
||||||
|
if (baro_setting >= 900 && baro_setting <= 1100) {
|
||||||
|
score += 13;
|
||||||
|
}
|
||||||
|
} else if (!baro_valid && baro_raw == 0) {
|
||||||
|
score += 1;
|
||||||
|
} else {
|
||||||
|
score -= 130;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (reserved_1 != 0) {
|
||||||
|
score -= 80;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mode_valid) {
|
||||||
|
score += 4;
|
||||||
|
} else if (!mode_valid && mode_raw == 0) {
|
||||||
|
score += 1;
|
||||||
|
} else {
|
||||||
|
score -= 40;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (reserved_2 != 0) {
|
||||||
|
score -= 20;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (source_valid) {
|
||||||
|
score += 3;
|
||||||
|
} else if (!source_valid && source_raw == 0) {
|
||||||
|
score += 1;
|
||||||
|
} else {
|
||||||
|
score -= 30;
|
||||||
|
}
|
||||||
|
|
||||||
|
// small bonuses for consistent data
|
||||||
|
if (mcp_valid && fms_valid && mcp_alt == fms_alt) {
|
||||||
|
score += 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (baro_valid && baro_raw == 2132) {
|
||||||
|
// 1013.2mb, standard pressure
|
||||||
|
score += 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mcp_valid) {
|
||||||
|
unsigned remainder = mcp_alt % 500;
|
||||||
|
if (remainder < 16 || remainder > 484) {
|
||||||
|
// mcp altitude is a multiple of 500
|
||||||
|
score += 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fms_valid) {
|
||||||
|
unsigned remainder = fms_alt % 500;
|
||||||
|
if (remainder < 16 || remainder > 484) {
|
||||||
|
// fms altitude is a multiple of 500
|
||||||
|
score += 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (store) {
|
||||||
|
mm->commb_format = COMMB_VERTICAL_INTENT;
|
||||||
|
|
||||||
|
mm->intent.valid = 1;
|
||||||
|
|
||||||
|
if (mcp_valid) {
|
||||||
|
mm->intent.mcp_altitude_valid = 1;
|
||||||
|
mm->intent.mcp_altitude = mcp_alt;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fms_valid) {
|
||||||
|
mm->intent.fms_altitude_valid = 1;
|
||||||
|
mm->intent.fms_altitude = fms_alt;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (baro_valid) {
|
||||||
|
mm->intent.alt_setting_valid = 1;
|
||||||
|
mm->intent.alt_setting = baro_setting;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mode_valid) {
|
||||||
|
mm->intent.mode_vnav = (mode_raw & 4) ? 1 : 0;
|
||||||
|
mm->intent.mode_alt_hold = (mode_raw & 2) ? 1 : 0;
|
||||||
|
mm->intent.mode_approach = (mode_raw & 1) ? 1 : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (source_valid) {
|
||||||
|
switch (source_raw) {
|
||||||
|
case 0:
|
||||||
|
mm->intent.altitude_source = TARGET_UNKNOWN;
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
mm->intent.altitude_source = TARGET_AIRCRAFT;
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
mm->intent.altitude_source = TARGET_MCP;
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
mm->intent.altitude_source = TARGET_FMS;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
mm->intent.altitude_source = TARGET_INVALID;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
mm->intent.altitude_source = TARGET_INVALID;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return score;
|
||||||
|
}
|
||||||
|
|
||||||
|
// BDS5,0 Track and turn report
|
||||||
|
static int decodeBDS50(struct modesMessage *mm, bool store)
|
||||||
|
{
|
||||||
|
unsigned char *msg = mm->MB;
|
||||||
|
|
||||||
|
unsigned roll_valid = getbit(msg, 1);
|
||||||
|
unsigned roll_sign = getbit(msg, 2);
|
||||||
|
unsigned roll_raw = getbits(msg, 3, 11);
|
||||||
|
|
||||||
|
unsigned track_valid = getbit(msg, 12);
|
||||||
|
unsigned track_sign = getbit(msg, 13);
|
||||||
|
unsigned track_raw = getbits(msg, 14, 23);
|
||||||
|
|
||||||
|
unsigned gs_valid = getbit(msg, 24);
|
||||||
|
unsigned gs_raw = getbits(msg, 25, 34);
|
||||||
|
|
||||||
|
unsigned track_rate_valid = getbit(msg, 35);
|
||||||
|
unsigned track_rate_sign = getbit(msg, 36);
|
||||||
|
unsigned track_rate_raw = getbits(msg, 37, 45);
|
||||||
|
|
||||||
|
unsigned tas_valid = getbit(msg, 46);
|
||||||
|
unsigned tas_raw = getbits(msg, 47, 56);
|
||||||
|
|
||||||
|
if (!roll_valid && !track_valid && !gs_valid && !track_rate_valid && !tas_valid) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int score = 0;
|
||||||
|
|
||||||
|
float roll = 0;
|
||||||
|
if (roll_valid) {
|
||||||
|
roll = roll_raw * 45.0 / 256.0;
|
||||||
|
if (roll_sign) {
|
||||||
|
roll -= 90.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (roll >= -40 && roll < 40) {
|
||||||
|
score += 11;
|
||||||
|
} else {
|
||||||
|
score -= 5;
|
||||||
|
}
|
||||||
|
} else if (!roll_valid && roll_raw == 0 && !roll_sign) {
|
||||||
|
score += 1;
|
||||||
|
} else {
|
||||||
|
score -= 110;
|
||||||
|
}
|
||||||
|
|
||||||
|
float track = 0;
|
||||||
|
if (track_valid) {
|
||||||
|
score += 12;
|
||||||
|
track = track_raw * 90.0 / 512.0;
|
||||||
|
if (track_sign) {
|
||||||
|
track += 180.0;
|
||||||
|
}
|
||||||
|
} else if (!track_valid && track_raw == 0 && !track_sign) {
|
||||||
|
score += 1;
|
||||||
|
} else {
|
||||||
|
score -= 120;
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned gs = 0;
|
||||||
|
if (gs_valid && gs_raw != 0) {
|
||||||
|
gs = gs_raw * 2;
|
||||||
|
|
||||||
|
if (gs >= 50 && gs <= 700) {
|
||||||
|
score += 11;
|
||||||
|
} else {
|
||||||
|
score -= 5;
|
||||||
|
}
|
||||||
|
} else if (!gs_valid && gs_raw == 0) {
|
||||||
|
score += 1;
|
||||||
|
} else {
|
||||||
|
score -= 110;
|
||||||
|
}
|
||||||
|
|
||||||
|
float track_rate = 0;
|
||||||
|
if (track_rate_valid) {
|
||||||
|
track_rate = track_rate_raw * 8.0 / 256.0;
|
||||||
|
if (track_rate_sign) {
|
||||||
|
track_rate -= 16;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (track_rate >= -10.0 && track_rate <= 10.0) {
|
||||||
|
score += 11;
|
||||||
|
} else {
|
||||||
|
score -= 5;
|
||||||
|
}
|
||||||
|
} else if (!track_rate_valid && track_rate_raw == 0 && !track_rate_sign) {
|
||||||
|
score += 1;
|
||||||
|
} else {
|
||||||
|
score -= 110;
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned tas = 0;
|
||||||
|
if (tas_valid && tas_raw != 0) {
|
||||||
|
tas = tas_raw * 2;
|
||||||
|
|
||||||
|
if (tas >= 50 && tas <= 700) {
|
||||||
|
score += 11;
|
||||||
|
} else {
|
||||||
|
score -= 5;
|
||||||
|
}
|
||||||
|
} else if (!tas_valid && tas_raw == 0) {
|
||||||
|
score += 1;
|
||||||
|
} else {
|
||||||
|
score -= 110;
|
||||||
|
}
|
||||||
|
|
||||||
|
// small bonuses for consistent data
|
||||||
|
if (gs_valid && tas_valid) {
|
||||||
|
int delta = abs((int)gs_valid - (int)tas_valid);
|
||||||
|
if (delta < 50) {
|
||||||
|
score += 5;
|
||||||
|
} else if (delta > 150) {
|
||||||
|
score -= 5;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// compute the theoretical turn rate and compare to track angle rate
|
||||||
|
if (roll_valid && tas_valid && tas > 0 && track_rate_valid) {
|
||||||
|
double turn_rate = 68625 * tan(roll * M_PI / 180.0) / (tas * 20 * M_PI);
|
||||||
|
double delta = fabs(turn_rate - track_rate);
|
||||||
|
if (delta < 0.5) {
|
||||||
|
score += 5;
|
||||||
|
} else if (delta > 2.0) {
|
||||||
|
score -= 5;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (store) {
|
||||||
|
mm->commb_format = COMMB_TRACK_TURN;
|
||||||
|
|
||||||
|
if (roll_valid) {
|
||||||
|
mm->roll_valid = 1;
|
||||||
|
mm->roll = roll;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (track_valid) {
|
||||||
|
mm->track_valid = 1;
|
||||||
|
mm->track = track;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (gs_valid) {
|
||||||
|
mm->gs_valid = 1;
|
||||||
|
mm->gs = gs;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (track_rate_valid) {
|
||||||
|
mm->track_rate_valid = 1;
|
||||||
|
mm->track_rate = track_rate;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tas_valid) {
|
||||||
|
mm->tas_valid = 1;
|
||||||
|
mm->tas = tas;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return score;
|
||||||
|
}
|
||||||
|
|
||||||
|
// BDS6,0 Heading and speed report
|
||||||
|
static int decodeBDS60(struct modesMessage *mm, bool store)
|
||||||
|
{
|
||||||
|
unsigned char *msg = mm->MB;
|
||||||
|
|
||||||
|
unsigned heading_valid = getbit(msg, 1);
|
||||||
|
unsigned heading_sign = getbit(msg, 2);
|
||||||
|
unsigned heading_raw = getbits(msg, 3, 12);
|
||||||
|
|
||||||
|
unsigned ias_valid = getbit(msg, 13);
|
||||||
|
unsigned ias_raw = getbits(msg, 14, 23);
|
||||||
|
|
||||||
|
unsigned mach_valid = getbit(msg, 24);
|
||||||
|
unsigned mach_raw = getbits(msg, 25, 34);
|
||||||
|
|
||||||
|
unsigned baro_rate_valid = getbit(msg, 35);
|
||||||
|
unsigned baro_rate_sign = getbit(msg, 36);
|
||||||
|
unsigned baro_rate_raw = getbits(msg, 37, 45);
|
||||||
|
|
||||||
|
unsigned inertial_rate_valid = getbit(msg, 46);
|
||||||
|
unsigned inertial_rate_sign = getbit(msg, 47);
|
||||||
|
unsigned inertial_rate_raw = getbits(msg, 48, 56);
|
||||||
|
|
||||||
|
if (!heading_valid && !ias_valid && !mach_valid && !baro_rate_valid && !inertial_rate_valid) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int score = 0;
|
||||||
|
|
||||||
|
float heading = 0;
|
||||||
|
if (heading_valid) {
|
||||||
|
heading = heading_raw * 90.0 / 512.0;
|
||||||
|
if (heading_sign) {
|
||||||
|
heading += 180.0;
|
||||||
|
}
|
||||||
|
score += 12;
|
||||||
|
} else if (!heading_valid && heading_raw == 0 && !heading_sign) {
|
||||||
|
score += 1;
|
||||||
|
} else {
|
||||||
|
score -= 120;
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned ias = 0;
|
||||||
|
if (ias_valid && ias_raw != 0) {
|
||||||
|
ias = ias_raw;
|
||||||
|
if (ias >= 50 && ias <= 700) {
|
||||||
|
score += 11;
|
||||||
|
} else {
|
||||||
|
score -= 5;
|
||||||
|
}
|
||||||
|
} else if (!ias_valid && ias_raw == 0) {
|
||||||
|
score += 1;
|
||||||
|
} else {
|
||||||
|
score -= 110;
|
||||||
|
}
|
||||||
|
|
||||||
|
float mach = 0;
|
||||||
|
if (mach_valid && mach_raw != 0) {
|
||||||
|
mach = mach_raw * 2.048 / 512;
|
||||||
|
if (mach >= 0.1 && mach <= 0.9) {
|
||||||
|
score += 11;
|
||||||
|
} else {
|
||||||
|
score -= 5;
|
||||||
|
}
|
||||||
|
} else if (!mach_valid && mach_raw == 0) {
|
||||||
|
score += 1;
|
||||||
|
} else {
|
||||||
|
score -= 110;
|
||||||
|
}
|
||||||
|
|
||||||
|
int baro_rate = 0;
|
||||||
|
if (baro_rate_valid) {
|
||||||
|
baro_rate = baro_rate_raw * 32;
|
||||||
|
if (baro_rate_sign) {
|
||||||
|
baro_rate -= 16384;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (baro_rate >= -6000 && baro_rate <= 6000) {
|
||||||
|
score += 11;
|
||||||
|
} else {
|
||||||
|
score -= 5;
|
||||||
|
}
|
||||||
|
} else if (!baro_rate_valid && baro_rate_raw == 0) {
|
||||||
|
score += 1;
|
||||||
|
} else {
|
||||||
|
score -= 110;
|
||||||
|
}
|
||||||
|
|
||||||
|
int inertial_rate = 0;
|
||||||
|
if (inertial_rate_valid) {
|
||||||
|
inertial_rate = inertial_rate_raw * 32;
|
||||||
|
if (inertial_rate_sign) {
|
||||||
|
inertial_rate -= 16384;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (inertial_rate >= -6000 && inertial_rate <= 6000) {
|
||||||
|
score += 11;
|
||||||
|
} else {
|
||||||
|
score -= 5;
|
||||||
|
}
|
||||||
|
} else if (!inertial_rate_valid && inertial_rate_raw == 0) {
|
||||||
|
score += 1;
|
||||||
|
} else {
|
||||||
|
score -= 110;
|
||||||
|
}
|
||||||
|
|
||||||
|
// small bonuses for consistent data
|
||||||
|
if (ias_valid && mach_valid) {
|
||||||
|
double delta = fabs(ias / 666.0 - mach);
|
||||||
|
if (delta < 0.1) {
|
||||||
|
score += 5;
|
||||||
|
} else if (delta > 0.25) {
|
||||||
|
score -= 5;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (baro_rate_valid && inertial_rate_valid) {
|
||||||
|
int delta = abs(baro_rate - inertial_rate);
|
||||||
|
if (delta < 500) {
|
||||||
|
score += 5;
|
||||||
|
} else if (delta > 200) {
|
||||||
|
score -= 5;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (store) {
|
||||||
|
mm->commb_format = COMMB_HEADING_SPEED;
|
||||||
|
|
||||||
|
if (heading_valid) {
|
||||||
|
mm->mag_heading_valid = 1;
|
||||||
|
mm->mag_heading = heading;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ias_valid) {
|
||||||
|
mm->ias_valid = 1;
|
||||||
|
mm->ias = ias;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mach_valid) {
|
||||||
|
mm->mach_valid = 1;
|
||||||
|
mm->mach = mach;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (baro_rate_valid) {
|
||||||
|
mm->baro_rate_valid = 1;
|
||||||
|
mm->baro_rate = baro_rate;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (inertial_rate_valid) {
|
||||||
|
// INS-derived data is treated as a "geometric rate" / "geometric altitude"
|
||||||
|
// elsewhere, so do the same here.
|
||||||
|
mm->geom_rate_valid = 1;
|
||||||
|
mm->geom_rate = inertial_rate;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return score;
|
||||||
|
}
|
26
comm_b.h
Normal file
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
|
119
dump1090.h
119
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 {
|
||||||
|
@ -177,12 +177,6 @@ typedef enum {
|
||||||
AG_UNCERTAIN
|
AG_UNCERTAIN
|
||||||
} airground_t;
|
} airground_t;
|
||||||
|
|
||||||
typedef enum {
|
|
||||||
SPEED_GROUNDSPEED,
|
|
||||||
SPEED_IAS,
|
|
||||||
SPEED_TAS
|
|
||||||
} speed_source_t;
|
|
||||||
|
|
||||||
typedef enum {
|
typedef enum {
|
||||||
HEADING_TRUE,
|
HEADING_TRUE,
|
||||||
HEADING_MAGNETIC
|
HEADING_MAGNETIC
|
||||||
|
@ -196,6 +190,18 @@ typedef enum {
|
||||||
CPR_SURFACE, CPR_AIRBORNE, CPR_COARSE
|
CPR_SURFACE, CPR_AIRBORNE, CPR_COARSE
|
||||||
} cpr_type_t;
|
} cpr_type_t;
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
COMMB_UNKNOWN,
|
||||||
|
COMMB_EMPTY_RESPONSE,
|
||||||
|
COMMB_DATALINK_CAPS,
|
||||||
|
COMMB_GICB_CAPS,
|
||||||
|
COMMB_AIRCRAFT_IDENT,
|
||||||
|
COMMB_ACAS_RA,
|
||||||
|
COMMB_VERTICAL_INTENT,
|
||||||
|
COMMB_TRACK_TURN,
|
||||||
|
COMMB_HEADING_SPEED
|
||||||
|
} commb_format_t;
|
||||||
|
|
||||||
#define MODES_NON_ICAO_ADDRESS (1<<24) // Set on addresses to indicate they are not ICAO addresses
|
#define MODES_NON_ICAO_ADDRESS (1<<24) // Set on addresses to indicate they are not ICAO addresses
|
||||||
|
|
||||||
#define MODES_DEBUG_DEMOD (1<<0)
|
#define MODES_DEBUG_DEMOD (1<<0)
|
||||||
|
@ -406,19 +412,24 @@ struct modesMessage {
|
||||||
|
|
||||||
// Decoded data
|
// Decoded data
|
||||||
unsigned altitude_valid : 1;
|
unsigned altitude_valid : 1;
|
||||||
unsigned heading_valid : 1;
|
unsigned track_valid : 1;
|
||||||
unsigned speed_valid : 1;
|
unsigned track_rate_valid : 1;
|
||||||
unsigned vert_rate_valid : 1;
|
unsigned mag_heading_valid : 1;
|
||||||
|
unsigned roll_valid : 1;
|
||||||
|
unsigned gs_valid : 1;
|
||||||
|
unsigned ias_valid : 1;
|
||||||
|
unsigned tas_valid : 1;
|
||||||
|
unsigned mach_valid : 1;
|
||||||
|
unsigned baro_rate_valid : 1;
|
||||||
|
unsigned geom_rate_valid : 1;
|
||||||
unsigned squawk_valid : 1;
|
unsigned squawk_valid : 1;
|
||||||
unsigned callsign_valid : 1;
|
unsigned callsign_valid : 1;
|
||||||
unsigned ew_velocity_valid : 1;
|
|
||||||
unsigned ns_velocity_valid : 1;
|
|
||||||
unsigned cpr_valid : 1;
|
unsigned cpr_valid : 1;
|
||||||
unsigned cpr_odd : 1;
|
unsigned cpr_odd : 1;
|
||||||
unsigned cpr_decoded : 1;
|
unsigned cpr_decoded : 1;
|
||||||
unsigned cpr_relative : 1;
|
unsigned cpr_relative : 1;
|
||||||
unsigned category_valid : 1;
|
unsigned category_valid : 1;
|
||||||
unsigned gnss_delta_valid : 1;
|
unsigned geom_delta_valid : 1;
|
||||||
unsigned from_mlat : 1;
|
unsigned from_mlat : 1;
|
||||||
unsigned from_tisb : 1;
|
unsigned from_tisb : 1;
|
||||||
unsigned spi_valid : 1;
|
unsigned spi_valid : 1;
|
||||||
|
@ -429,26 +440,27 @@ struct modesMessage {
|
||||||
unsigned metype; // DF17/18 ME type
|
unsigned metype; // DF17/18 ME type
|
||||||
unsigned mesub; // DF17/18 ME subtype
|
unsigned mesub; // DF17/18 ME subtype
|
||||||
|
|
||||||
|
commb_format_t commb_format; // Inferred format of a comm-b message
|
||||||
|
|
||||||
// valid if altitude_valid:
|
// valid if altitude_valid:
|
||||||
int altitude; // Altitude in either feet or meters
|
int altitude; // Altitude in either feet or meters
|
||||||
altitude_unit_t altitude_unit; // the unit used for altitude
|
altitude_unit_t altitude_unit; // the unit used for altitude
|
||||||
altitude_source_t altitude_source; // whether the altitude is a barometric altude or a GNSS height
|
altitude_source_t altitude_source; // whether the altitude is a barometric altitude or a geometric height
|
||||||
// valid if gnss_delta_valid:
|
|
||||||
int gnss_delta; // difference between GNSS and baro alt
|
// following fields are valid if the corresponding _valid field is set:
|
||||||
// valid if heading_valid:
|
int geom_delta; // Difference between geometric and baro alt
|
||||||
unsigned heading; // Reported by aircraft, or computed from from EW and NS velocity
|
float track; // True ground track, degrees (0-359). Reported directly or computed from from EW and NS velocity
|
||||||
heading_source_t heading_source; // what "heading" is measuring (true or magnetic heading)
|
float track_rate; // Rate of change of track, degrees/second
|
||||||
// valid if speed_valid:
|
float mag_heading; // Magnetic heading, degrees (0-359)
|
||||||
unsigned speed; // in kts, reported by aircraft, or computed from from EW and NS velocity
|
float roll; // Roll, degrees, negative is left roll
|
||||||
speed_source_t speed_source; // what "speed" is measuring (groundspeed / IAS / TAS)
|
unsigned gs; // Groundspeed, kts, reported directly or computed from from EW and NS velocity
|
||||||
// valid if vert_rate_valid:
|
unsigned ias; // Indicated airspeed, kts
|
||||||
int vert_rate; // vertical rate in feet/minute
|
unsigned tas; // True airspeed, kts
|
||||||
altitude_source_t vert_rate_source; // the altitude source used for vert_rate
|
double mach; // Mach number
|
||||||
// valid if squawk_valid:
|
int baro_rate; // Rate of change of barometric altitude, feet/minute
|
||||||
|
int geom_rate; // Rate of change of geometric (GNSS / INS) altitude, feet/minute
|
||||||
unsigned squawk; // 13 bits identity (Squawk), encoded as 4 hex digits
|
unsigned squawk; // 13 bits identity (Squawk), encoded as 4 hex digits
|
||||||
// valid if callsign_valid
|
char callsign[9]; // 8 chars flight number, NUL-terminated
|
||||||
char callsign[9]; // 8 chars flight number
|
|
||||||
// valid if category_valid
|
|
||||||
unsigned category; // A0 - D7 encoded as a single hex byte
|
unsigned category; // A0 - D7 encoded as a single hex byte
|
||||||
// valid if cpr_valid
|
// valid if cpr_valid
|
||||||
cpr_type_t cpr_type; // The encoding type used (surface, airborne, coarse TIS-B)
|
cpr_type_t cpr_type; // The encoding type used (surface, airborne, coarse TIS-B)
|
||||||
|
@ -500,32 +512,37 @@ struct modesMessage {
|
||||||
unsigned cc_antenna_offset;
|
unsigned cc_antenna_offset;
|
||||||
} opstatus;
|
} opstatus;
|
||||||
|
|
||||||
|
// combined:
|
||||||
// Target State & Status (ADS-B V2 only)
|
// Target State & Status (ADS-B V2 only)
|
||||||
|
// Comm-B BDS4,0 Vertical Intent
|
||||||
struct {
|
struct {
|
||||||
unsigned valid : 1;
|
unsigned valid : 1;
|
||||||
unsigned altitude_valid : 1;
|
|
||||||
unsigned baro_valid : 1;
|
|
||||||
unsigned heading_valid : 1;
|
|
||||||
unsigned mode_valid : 1;
|
|
||||||
unsigned mode_autopilot : 1;
|
|
||||||
unsigned mode_vnav : 1;
|
|
||||||
unsigned mode_alt_hold : 1;
|
|
||||||
unsigned mode_approach : 1;
|
|
||||||
unsigned acas_operational : 1;
|
|
||||||
unsigned nac_p : 4;
|
|
||||||
unsigned nic_baro : 1;
|
|
||||||
unsigned sil : 2;
|
|
||||||
|
|
||||||
sil_type_t sil_type;
|
unsigned heading_valid : 1;
|
||||||
enum { TSS_ALTITUDE_MCP, TSS_ALTITUDE_FMS } altitude_type;
|
unsigned fms_altitude_valid : 1;
|
||||||
unsigned altitude;
|
unsigned mcp_altitude_valid : 1;
|
||||||
float baro;
|
unsigned alt_setting_valid : 1;
|
||||||
unsigned heading;
|
|
||||||
} tss;
|
float heading; // heading, degrees (0-359) (could be magnetic or true heading; magnetic recommended)
|
||||||
|
unsigned fms_altitude; // FMS selected altitude
|
||||||
|
unsigned mcp_altitude; // MCP/FCU selected altitude
|
||||||
|
float alt_setting; // altimeter setting (QFE or QNH/QNE), millibars
|
||||||
|
|
||||||
|
enum { TARGET_INVALID, TARGET_UNKNOWN, TARGET_AIRCRAFT, TARGET_MCP, TARGET_FMS } altitude_source;
|
||||||
|
|
||||||
|
unsigned mode_autopilot : 1; // Autopilot engaged
|
||||||
|
unsigned mode_vnav : 1; // Vertical Navigation Mode active
|
||||||
|
unsigned mode_alt_hold : 1; // Altitude Hold Mode active
|
||||||
|
unsigned mode_approach : 1; // Approach Mode active
|
||||||
|
unsigned mode_lnav : 1; // Lateral Navigation Mode active
|
||||||
|
|
||||||
|
} intent;
|
||||||
};
|
};
|
||||||
|
|
||||||
// This one needs modesMessage:
|
// This one needs modesMessage:
|
||||||
#include "track.h"
|
#include "track.h"
|
||||||
|
#include "mode_s.h"
|
||||||
|
#include "comm_b.h"
|
||||||
|
|
||||||
// ======================== function declarations =========================
|
// ======================== function declarations =========================
|
||||||
|
|
||||||
|
@ -542,14 +559,6 @@ void modeACInit();
|
||||||
int modeAToModeC (unsigned int modeA);
|
int modeAToModeC (unsigned int modeA);
|
||||||
unsigned modeCToModeA (int modeC);
|
unsigned modeCToModeA (int modeC);
|
||||||
|
|
||||||
//
|
|
||||||
// Functions exported from mode_s.c
|
|
||||||
//
|
|
||||||
int modesMessageLenByType(int type);
|
|
||||||
int scoreModesMessage(unsigned char *msg, int validbits);
|
|
||||||
int decodeModesMessage (struct modesMessage *mm, unsigned char *msg);
|
|
||||||
void displayModesMessage(struct modesMessage *mm);
|
|
||||||
void useModesMessage (struct modesMessage *mm);
|
|
||||||
//
|
//
|
||||||
// Functions exported from interactive.c
|
// Functions exported from interactive.c
|
||||||
//
|
//
|
||||||
|
|
|
@ -130,12 +130,12 @@ void interactiveShowData(void) {
|
||||||
snprintf(strSquawk,5,"%04x", a->squawk);
|
snprintf(strSquawk,5,"%04x", a->squawk);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (trackDataValid(&a->speed_valid)) {
|
if (trackDataValid(&a->gs_valid)) {
|
||||||
snprintf (strGs, 5,"%3d", convert_speed(a->speed));
|
snprintf (strGs, 5,"%3d", convert_speed(a->gs));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (trackDataValid(&a->heading_valid)) {
|
if (trackDataValid(&a->track_valid)) {
|
||||||
snprintf (strTt, 5,"%03d", a->heading);
|
snprintf (strTt, 5,"%03.0f", a->track);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (msgs > 99999) {
|
if (msgs > 99999) {
|
||||||
|
@ -160,8 +160,8 @@ void interactiveShowData(void) {
|
||||||
|
|
||||||
if (trackDataValid(&a->airground_valid) && a->airground == AG_GROUND) {
|
if (trackDataValid(&a->airground_valid) && a->airground == AG_GROUND) {
|
||||||
snprintf(strFl, 7," grnd");
|
snprintf(strFl, 7," grnd");
|
||||||
} else if (Modes.use_gnss && trackDataValid(&a->altitude_gnss_valid)) {
|
} else if (Modes.use_gnss && trackDataValid(&a->altitude_geom_valid)) {
|
||||||
snprintf(strFl, 7, "%5dH", convert_altitude(a->altitude_gnss));
|
snprintf(strFl, 7, "%5dH", convert_altitude(a->altitude_geom));
|
||||||
} else if (trackDataValid(&a->altitude_valid)) {
|
} else if (trackDataValid(&a->altitude_valid)) {
|
||||||
snprintf(strFl, 7, "%5d ", convert_altitude(a->altitude));
|
snprintf(strFl, 7, "%5d ", convert_altitude(a->altitude));
|
||||||
}
|
}
|
||||||
|
|
372
mode_s.c
372
mode_s.c
|
@ -53,8 +53,6 @@
|
||||||
/* for PRIX64 */
|
/* for PRIX64 */
|
||||||
#include <inttypes.h>
|
#include <inttypes.h>
|
||||||
|
|
||||||
#include <assert.h>
|
|
||||||
|
|
||||||
//
|
//
|
||||||
// ===================== Mode S detection and decoding ===================
|
// ===================== Mode S detection and decoding ===================
|
||||||
//
|
//
|
||||||
|
@ -220,67 +218,6 @@ static int correct_aa_field(uint32_t *addr, struct errorinfo *ei)
|
||||||
return addr_errors;
|
return addr_errors;
|
||||||
}
|
}
|
||||||
|
|
||||||
// The first bit (MSB of the first byte) is numbered 1, for consistency
|
|
||||||
// with how the specs number them.
|
|
||||||
|
|
||||||
// Extract one bit from a message.
|
|
||||||
static inline __attribute__((always_inline)) unsigned getbit(unsigned char *data, unsigned bitnum)
|
|
||||||
{
|
|
||||||
unsigned bi = bitnum - 1;
|
|
||||||
unsigned by = bi >> 3;
|
|
||||||
unsigned mask = 1 << (7 - (bi & 7));
|
|
||||||
|
|
||||||
return (data[by] & mask) != 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extract some bits (firstbit .. lastbit inclusive) from a message.
|
|
||||||
static inline __attribute__((always_inline)) unsigned getbits(unsigned char *data, unsigned firstbit, unsigned lastbit)
|
|
||||||
{
|
|
||||||
unsigned fbi = firstbit - 1;
|
|
||||||
unsigned lbi = lastbit - 1;
|
|
||||||
unsigned nbi = (lastbit - firstbit + 1);
|
|
||||||
|
|
||||||
unsigned fby = fbi >> 3;
|
|
||||||
unsigned lby = lbi >> 3;
|
|
||||||
unsigned nby = (lby - fby) + 1;
|
|
||||||
|
|
||||||
unsigned shift = 7 - (lbi & 7);
|
|
||||||
unsigned topmask = 0xFF >> (fbi & 7);
|
|
||||||
|
|
||||||
assert (fbi <= lbi);
|
|
||||||
assert (nbi <= 32);
|
|
||||||
assert (nby <= 5);
|
|
||||||
|
|
||||||
if (nby == 5) {
|
|
||||||
return
|
|
||||||
((data[fby] & topmask) << (32 - shift)) |
|
|
||||||
(data[fby + 1] << (24 - shift)) |
|
|
||||||
(data[fby + 2] << (16 - shift)) |
|
|
||||||
(data[fby + 3] << (8 - shift)) |
|
|
||||||
(data[fby + 4] >> shift);
|
|
||||||
} else if (nby == 4) {
|
|
||||||
return
|
|
||||||
((data[fby] & topmask) << (24 - shift)) |
|
|
||||||
(data[fby + 1] << (16 - shift)) |
|
|
||||||
(data[fby + 2] << (8 - shift)) |
|
|
||||||
(data[fby + 3] >> shift);
|
|
||||||
} else if (nby == 3) {
|
|
||||||
return
|
|
||||||
((data[fby] & topmask) << (16 - shift)) |
|
|
||||||
(data[fby + 1] << (8 - shift)) |
|
|
||||||
(data[fby + 2] >> shift);
|
|
||||||
} else if (nby == 2) {
|
|
||||||
return
|
|
||||||
((data[fby] & topmask) << (8 - shift)) |
|
|
||||||
(data[fby + 1] >> shift);
|
|
||||||
} else if (nby == 1) {
|
|
||||||
return
|
|
||||||
(data[fby] & topmask) >> shift;
|
|
||||||
} else {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Score how plausible this ModeS message looks.
|
// Score how plausible this ModeS message looks.
|
||||||
// The more positive, the more reliable the message is
|
// The more positive, the more reliable the message is
|
||||||
|
|
||||||
|
@ -417,8 +354,8 @@ int scoreModesMessage(unsigned char *msg, int validbits)
|
||||||
//
|
//
|
||||||
|
|
||||||
static void decodeExtendedSquitter(struct modesMessage *mm);
|
static void decodeExtendedSquitter(struct modesMessage *mm);
|
||||||
static void decodeCommB(struct modesMessage *mm);
|
|
||||||
static char *ais_charset = "@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_ !\"#$%&'()*+,-./0123456789:;<=>?";
|
char ais_charset[64] = "@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_ !\"#$%&'()*+,-./0123456789:;<=>?";
|
||||||
|
|
||||||
// return 0 if all OK
|
// return 0 if all OK
|
||||||
// -1: message might be valid, but we couldn't validate the CRC against a known ICAO
|
// -1: message might be valid, but we couldn't validate the CRC against a known ICAO
|
||||||
|
@ -737,33 +674,6 @@ int decodeModesMessage(struct modesMessage *mm, unsigned char *msg)
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Decode BDS2,0 carried in Comm-B or ES
|
|
||||||
static void decodeBDS20(struct modesMessage *mm)
|
|
||||||
{
|
|
||||||
unsigned char *msg = mm->msg;
|
|
||||||
|
|
||||||
mm->callsign[0] = ais_charset[getbits(msg, 41, 46)];
|
|
||||||
mm->callsign[1] = ais_charset[getbits(msg, 47, 52)];
|
|
||||||
mm->callsign[2] = ais_charset[getbits(msg, 53, 58)];
|
|
||||||
mm->callsign[3] = ais_charset[getbits(msg, 59, 64)];
|
|
||||||
mm->callsign[4] = ais_charset[getbits(msg, 65, 70)];
|
|
||||||
mm->callsign[5] = ais_charset[getbits(msg, 71, 76)];
|
|
||||||
mm->callsign[6] = ais_charset[getbits(msg, 77, 82)];
|
|
||||||
mm->callsign[7] = ais_charset[getbits(msg, 83, 88)];
|
|
||||||
mm->callsign[8] = 0;
|
|
||||||
|
|
||||||
// Catch possible bad decodings since BDS2,0 is not
|
|
||||||
// 100% reliable: accept only alphanumeric data
|
|
||||||
mm->callsign_valid = 1;
|
|
||||||
for (int i = 0; i < 8; ++i) {
|
|
||||||
if (! ((mm->callsign[i] >= 'A' && mm->callsign[i] <= 'Z') ||
|
|
||||||
(mm->callsign[i] >= '0' && mm->callsign[i] <= '9') ||
|
|
||||||
mm->callsign[i] == ' ') ) {
|
|
||||||
mm->callsign_valid = 0;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void decodeESIdentAndCategory(struct modesMessage *mm)
|
static void decodeESIdentAndCategory(struct modesMessage *mm)
|
||||||
{
|
{
|
||||||
|
@ -828,12 +738,17 @@ static void decodeESAirborneVelocity(struct modesMessage *mm, int check_imf)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
unsigned vert_rate = getbits(me, 38, 46);
|
unsigned vert_rate = getbits(me, 38, 46);
|
||||||
|
unsigned vert_rate_is_geom = getbit(me, 36);
|
||||||
if (vert_rate) {
|
if (vert_rate) {
|
||||||
mm->vert_rate = (vert_rate - 1) * (getbit(me, 37) ? -64 : 64);
|
int rate = (vert_rate - 1) * (getbit(me, 37) ? -64 : 64);
|
||||||
mm->vert_rate_valid = 1;
|
if (vert_rate_is_geom) {
|
||||||
|
mm->geom_rate = rate;
|
||||||
|
mm->geom_rate_valid = 1;
|
||||||
|
} else {
|
||||||
|
mm->baro_rate = rate;
|
||||||
|
mm->baro_rate_valid = 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
mm->vert_rate_source = (getbit(me, 36) ? ALTITUDE_GNSS : ALTITUDE_BARO);
|
|
||||||
|
|
||||||
switch (mm->mesub) {
|
switch (mm->mesub) {
|
||||||
case 1: case 2:
|
case 1: case 2:
|
||||||
|
@ -846,20 +761,17 @@ static void decodeESAirborneVelocity(struct modesMessage *mm, int check_imf)
|
||||||
int ns_vel = (ns_raw - 1) * (getbit(me, 25) ? -1 : 1) * ((mm->mesub == 2) ? 4 : 1);
|
int ns_vel = (ns_raw - 1) * (getbit(me, 25) ? -1 : 1) * ((mm->mesub == 2) ? 4 : 1);
|
||||||
|
|
||||||
// Compute velocity and angle from the two speed components
|
// Compute velocity and angle from the two speed components
|
||||||
mm->speed = (unsigned) sqrt((ns_vel * ns_vel) + (ew_vel * ew_vel) + 0.5);
|
mm->gs = (unsigned) sqrt((ns_vel * ns_vel) + (ew_vel * ew_vel) + 0.5);
|
||||||
mm->speed_valid = 1;
|
mm->gs_valid = 1;
|
||||||
|
|
||||||
if (mm->speed) {
|
if (mm->gs) {
|
||||||
int heading = (int) (atan2(ew_vel, ns_vel) * 180.0 / M_PI + 0.5);
|
float heading = atan2(ew_vel, ns_vel) * 180.0 / M_PI;
|
||||||
// We don't want negative values but a 0-360 scale
|
// We don't want negative values but a 0-360 scale
|
||||||
if (heading < 0)
|
if (heading < 0)
|
||||||
heading += 360;
|
heading += 360;
|
||||||
mm->heading = (unsigned) heading;
|
mm->track = heading;
|
||||||
mm->heading_source = HEADING_TRUE;
|
mm->track_valid = 1;
|
||||||
mm->heading_valid = 1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
mm->speed_source = SPEED_GROUNDSPEED;
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -868,15 +780,19 @@ static void decodeESAirborneVelocity(struct modesMessage *mm, int check_imf)
|
||||||
{
|
{
|
||||||
unsigned airspeed = getbits(me, 26, 35);
|
unsigned airspeed = getbits(me, 26, 35);
|
||||||
if (airspeed) {
|
if (airspeed) {
|
||||||
mm->speed = (airspeed - 1) * (mm->mesub == 4 ? 4 : 1);
|
unsigned speed = (airspeed - 1) * (mm->mesub == 4 ? 4 : 1);
|
||||||
mm->speed_source = getbit(me, 25) ? SPEED_TAS : SPEED_IAS;
|
if (getbit(me, 25)) {
|
||||||
mm->speed_valid = 1;
|
mm->tas_valid = 1;
|
||||||
|
mm->tas = speed;
|
||||||
|
} else {
|
||||||
|
mm->ias_valid = 1;
|
||||||
|
mm->ias = speed;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (getbit(me, 14)) {
|
if (getbit(me, 14)) {
|
||||||
mm->heading = getbits(me, 15, 24);
|
mm->mag_heading_valid = 1;
|
||||||
mm->heading_source = HEADING_MAGNETIC;
|
mm->mag_heading = getbits(me, 15, 24) * 360.0 / 1024.0;
|
||||||
mm->heading_valid = 1;
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -884,8 +800,8 @@ static void decodeESAirborneVelocity(struct modesMessage *mm, int check_imf)
|
||||||
|
|
||||||
unsigned raw_delta = getbits(me, 50, 56);
|
unsigned raw_delta = getbits(me, 50, 56);
|
||||||
if (raw_delta) {
|
if (raw_delta) {
|
||||||
mm->gnss_delta_valid = 1;
|
mm->geom_delta_valid = 1;
|
||||||
mm->gnss_delta = (raw_delta - 1) * (getbit(me, 49) ? -25 : 25);
|
mm->geom_delta = (raw_delta - 1) * (getbit(me, 49) ? -25 : 25);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -907,15 +823,13 @@ static void decodeESSurfacePosition(struct modesMessage *mm, int check_imf)
|
||||||
|
|
||||||
unsigned movement = getbits(me, 6, 12);
|
unsigned movement = getbits(me, 6, 12);
|
||||||
if (movement > 0 && movement < 125) {
|
if (movement > 0 && movement < 125) {
|
||||||
mm->speed_valid = 1;
|
mm->gs_valid = 1;
|
||||||
mm->speed = decodeMovementField(movement);
|
mm->gs = decodeMovementField(movement);
|
||||||
mm->speed_source = SPEED_GROUNDSPEED;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (getbit(me, 13)) {
|
if (getbit(me, 13)) {
|
||||||
mm->heading_valid = 1;
|
mm->track_valid = 1;
|
||||||
mm->heading_source = HEADING_TRUE;
|
mm->track = getbits(me, 14, 20) * 360.0 / 128.0;
|
||||||
mm->heading = getbits(me, 14, 20) * 360 / 128;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -967,7 +881,7 @@ static void decodeESAirbornePosition(struct modesMessage *mm, int check_imf)
|
||||||
mm->altitude_valid = 1;
|
mm->altitude_valid = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
mm->altitude_source = (mm->metype == 20 || mm->metype == 21 || mm->metype == 22) ? ALTITUDE_GNSS : ALTITUDE_BARO;
|
mm->altitude_source = (mm->metype == 20 || mm->metype == 21 || mm->metype == 22) ? ALTITUDE_GEOM : ALTITUDE_BARO;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1017,45 +931,50 @@ static void decodeESTargetStatus(struct modesMessage *mm, int check_imf)
|
||||||
if (mm->mesub == 0) { // Target state and status, V1
|
if (mm->mesub == 0) { // Target state and status, V1
|
||||||
// TODO: need RTCA/DO-260A
|
// TODO: need RTCA/DO-260A
|
||||||
} else if (mm->mesub == 1) { // Target state and status, V2
|
} else if (mm->mesub == 1) { // Target state and status, V2
|
||||||
mm->tss.valid = 1;
|
mm->intent.valid = 1;
|
||||||
mm->tss.sil_type = getbit(me, 8) ? SIL_PER_SAMPLE : SIL_PER_HOUR;
|
|
||||||
mm->tss.altitude_type = getbit(me, 9) ? TSS_ALTITUDE_FMS : TSS_ALTITUDE_MCP;
|
// 8: SIL
|
||||||
|
unsigned is_fms = getbit(me, 9);
|
||||||
|
|
||||||
unsigned alt_bits = getbits(me, 10, 20);
|
unsigned alt_bits = getbits(me, 10, 20);
|
||||||
if (alt_bits == 0) {
|
if (alt_bits != 0) {
|
||||||
mm->tss.altitude_valid = 0;
|
if (is_fms) {
|
||||||
|
mm->intent.fms_altitude_valid = 1;
|
||||||
|
mm->intent.fms_altitude = (alt_bits - 1) * 32;
|
||||||
} else {
|
} else {
|
||||||
mm->tss.altitude_valid = 1;
|
mm->intent.mcp_altitude_valid = 1;
|
||||||
mm->tss.altitude = (alt_bits - 1) * 32;
|
mm->intent.mcp_altitude = (alt_bits - 1) * 32;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
unsigned baro_bits = getbits(me, 21, 29);
|
unsigned baro_bits = getbits(me, 21, 29);
|
||||||
if (baro_bits == 0) {
|
if (baro_bits != 0) {
|
||||||
mm->tss.baro_valid = 0;
|
mm->intent.alt_setting_valid = 1;
|
||||||
} else {
|
mm->intent.alt_setting = 800.0 + (baro_bits - 1) * 0.8;
|
||||||
mm->tss.baro_valid = 1;
|
|
||||||
mm->tss.baro = 800.0 + (baro_bits - 1) * 0.8;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
mm->tss.heading_valid = getbit(me, 30);
|
if (getbit(me, 30)) {
|
||||||
if (mm->tss.heading_valid) {
|
mm->intent.heading_valid = 1;
|
||||||
// two's complement -180..+180, which is conveniently
|
// two's complement -180..+180, which is conveniently
|
||||||
// also the same as unsigned 0..360
|
// also the same as unsigned 0..360
|
||||||
mm->tss.heading = getbits(me, 31, 39) * 180 / 256;
|
mm->intent.heading = getbits(me, 31, 39) * 180.0 / 256.0;
|
||||||
}
|
}
|
||||||
|
|
||||||
mm->tss.nac_p = getbits(me, 40, 43);
|
// 40-43: NACp
|
||||||
mm->tss.nic_baro = getbit(me, 44);
|
// 44: NICbaro
|
||||||
mm->tss.sil = getbits(me, 45, 46);
|
// 45-46: SIL
|
||||||
mm->tss.mode_valid = getbit(me, 47);
|
|
||||||
if (mm->tss.mode_valid) {
|
if (getbit(me, 47)) {
|
||||||
mm->tss.mode_autopilot = getbit(me, 48);
|
mm->intent.mode_autopilot = getbit(me, 48);
|
||||||
mm->tss.mode_vnav = getbit(me, 49);
|
mm->intent.mode_vnav = getbit(me, 49);
|
||||||
mm->tss.mode_alt_hold = getbit(me, 50);
|
mm->intent.mode_alt_hold = getbit(me, 50);
|
||||||
mm->tss.mode_approach = getbit(me, 52);
|
// 51: IMF
|
||||||
|
mm->intent.mode_approach = getbit(me, 52);
|
||||||
|
// 53: TCAS operational
|
||||||
|
mm->intent.mode_lnav = getbit(me, 54);
|
||||||
}
|
}
|
||||||
|
|
||||||
mm->tss.acas_operational = getbit(me, 53);
|
// 55-56 reserved
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1230,7 +1149,7 @@ static void decodeExtendedSquitter(struct modesMessage *mm)
|
||||||
|
|
||||||
case 0: // Airborne position, baro altitude only
|
case 0: // Airborne position, baro altitude only
|
||||||
case 9: case 10: case 11: case 12: case 13: case 14: case 15: case 16: case 17: case 18: // Airborne position, baro
|
case 9: case 10: case 11: case 12: case 13: case 14: case 15: case 16: case 17: case 18: // Airborne position, baro
|
||||||
case 20: case 21: case 22: // Airborne position, GNSS altitude (HAE or MSL)
|
case 20: case 21: case 22: // Airborne position, geometric altitude (HAE or MSL)
|
||||||
decodeESAirbornePosition(mm, check_imf);
|
decodeESAirbornePosition(mm, check_imf);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
@ -1261,16 +1180,6 @@ static void decodeExtendedSquitter(struct modesMessage *mm)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void decodeCommB(struct modesMessage *mm)
|
|
||||||
{
|
|
||||||
unsigned char *msg = mm->msg;
|
|
||||||
|
|
||||||
// This is a bit hairy as we don't know what the requested register was
|
|
||||||
if (getbits(msg, 33, 40) == 0x20) { // BDS 2,0 Aircraft Identification
|
|
||||||
decodeBDS20(mm);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static const char *df_names[33] = {
|
static const char *df_names[33] = {
|
||||||
/* 0 */ "Short Air-Air Surveillance",
|
/* 0 */ "Short Air-Air Surveillance",
|
||||||
/* 1 */ NULL,
|
/* 1 */ NULL,
|
||||||
|
@ -1330,8 +1239,8 @@ static const char *altitude_source_to_string(altitude_source_t source) {
|
||||||
switch (source) {
|
switch (source) {
|
||||||
case ALTITUDE_BARO:
|
case ALTITUDE_BARO:
|
||||||
return "barometric";
|
return "barometric";
|
||||||
case ALTITUDE_GNSS:
|
case ALTITUDE_GEOM:
|
||||||
return "GNSS";
|
return "geometric";
|
||||||
default:
|
default:
|
||||||
return "(unknown altitude source)";
|
return "(unknown altitude source)";
|
||||||
}
|
}
|
||||||
|
@ -1352,19 +1261,6 @@ static const char *airground_to_string(airground_t airground) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static const char *speed_source_to_string(speed_source_t speed) {
|
|
||||||
switch (speed) {
|
|
||||||
case SPEED_GROUNDSPEED:
|
|
||||||
return "groundspeed";
|
|
||||||
case SPEED_IAS:
|
|
||||||
return "IAS";
|
|
||||||
case SPEED_TAS:
|
|
||||||
return "TAS";
|
|
||||||
default:
|
|
||||||
return "(unknown speed type)";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static const char *addrtype_to_string(addrtype_t type) {
|
static const char *addrtype_to_string(addrtype_t type) {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case ADDR_ADSB_ICAO:
|
case ADDR_ADSB_ICAO:
|
||||||
|
@ -1403,6 +1299,29 @@ static const char *cpr_type_to_string(cpr_type_t type) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static const char *commb_format_to_string(commb_format_t format) {
|
||||||
|
switch (format) {
|
||||||
|
case COMMB_EMPTY_RESPONSE:
|
||||||
|
return "empty response";
|
||||||
|
case COMMB_DATALINK_CAPS:
|
||||||
|
return "BDS1,0 Datalink capabilities";
|
||||||
|
case COMMB_GICB_CAPS:
|
||||||
|
return "BDS1,7 Common usage GICB capabilities";
|
||||||
|
case COMMB_AIRCRAFT_IDENT:
|
||||||
|
return "BDS2,0 Aircraft identification";
|
||||||
|
case COMMB_ACAS_RA:
|
||||||
|
return "BDS3,0 ACAS resolution advisory";
|
||||||
|
case COMMB_VERTICAL_INTENT:
|
||||||
|
return "BDS4,0 Selected vertical intention";
|
||||||
|
case COMMB_TRACK_TURN:
|
||||||
|
return "BDS5,0 Track and turn report";
|
||||||
|
case COMMB_HEADING_SPEED:
|
||||||
|
return "BDS6,0 Heading and speed report";
|
||||||
|
default:
|
||||||
|
return "unknown format";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static void print_hex_bytes(unsigned char *data, size_t len) {
|
static void print_hex_bytes(unsigned char *data, size_t len) {
|
||||||
size_t i;
|
size_t i;
|
||||||
for (i = 0; i < len; ++i) {
|
for (i = 0; i < len; ++i) {
|
||||||
|
@ -1455,7 +1374,7 @@ static const char *esTypeName(unsigned metype, unsigned mesub)
|
||||||
}
|
}
|
||||||
|
|
||||||
case 20: case 21: case 22:
|
case 20: case 21: case 22:
|
||||||
return "Airborne position (GNSS altitude)";
|
return "Airborne position (geometric altitude)";
|
||||||
|
|
||||||
case 23:
|
case 23:
|
||||||
switch (mesub) {
|
switch (mesub) {
|
||||||
|
@ -1639,6 +1558,10 @@ void displayModesMessage(struct modesMessage *mm) {
|
||||||
}
|
}
|
||||||
printf("\n");
|
printf("\n");
|
||||||
|
|
||||||
|
if (mm->msgtype == 20 || mm->msgtype == 21) {
|
||||||
|
printf(" Comm-B format: %s\n", commb_format_to_string(mm->commb_format));
|
||||||
|
}
|
||||||
|
|
||||||
if (mm->addr & MODES_NON_ICAO_ADDRESS) {
|
if (mm->addr & MODES_NON_ICAO_ADDRESS) {
|
||||||
printf(" Other Address: %06X (%s)\n", mm->addr & 0xFFFFFF, addrtype_to_string(mm->addrtype));
|
printf(" Other Address: %06X (%s)\n", mm->addr & 0xFFFFFF, addrtype_to_string(mm->addrtype));
|
||||||
} else {
|
} else {
|
||||||
|
@ -1657,25 +1580,49 @@ void displayModesMessage(struct modesMessage *mm) {
|
||||||
altitude_source_to_string(mm->altitude_source));
|
altitude_source_to_string(mm->altitude_source));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mm->gnss_delta_valid) {
|
if (mm->geom_delta_valid) {
|
||||||
printf(" GNSS delta: %d ft\n",
|
printf(" Geom - baro: %d ft\n",
|
||||||
mm->gnss_delta);
|
mm->geom_delta);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mm->heading_valid) {
|
if (mm->track_valid) {
|
||||||
printf(" Heading: %u\n", mm->heading);
|
printf(" Track: %.1f\n", mm->track);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mm->speed_valid) {
|
if (mm->mag_heading_valid) {
|
||||||
printf(" Speed: %u kt %s\n",
|
printf(" Mag heading: %.1f\n", mm->mag_heading);
|
||||||
mm->speed,
|
|
||||||
speed_source_to_string(mm->speed_source));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mm->vert_rate_valid) {
|
if (mm->track_rate_valid) {
|
||||||
printf(" Vertical rate: %d ft/min %s\n",
|
printf(" Track rate: %.2f deg/sec %s\n", mm->track_rate, mm->track_rate < 0 ? "left" : mm->track_rate > 0 ? "right" : "");
|
||||||
mm->vert_rate,
|
}
|
||||||
altitude_source_to_string(mm->vert_rate_source));
|
|
||||||
|
if (mm->roll_valid) {
|
||||||
|
printf(" Roll: %.1f degrees %s\n", mm->roll, mm->roll < -0.05 ? "left" : mm->roll > 0.05 ? "right" : "");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mm->gs_valid) {
|
||||||
|
printf(" Groundspeed: %u kt\n", mm->gs);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mm->ias_valid) {
|
||||||
|
printf(" IAS: %u kt\n", mm->ias);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mm->tas_valid) {
|
||||||
|
printf(" TAS: %u kt\n", mm->tas);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mm->mach_valid) {
|
||||||
|
printf(" Mach number: %.3f\n", mm->mach);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mm->baro_rate_valid) {
|
||||||
|
printf(" Baro rate: %d ft/min\n", mm->baro_rate);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mm->geom_rate_valid) {
|
||||||
|
printf(" Geom rate: %d ft/min\n", mm->geom_rate);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mm->squawk_valid) {
|
if (mm->squawk_valid) {
|
||||||
|
@ -1758,26 +1705,47 @@ void displayModesMessage(struct modesMessage *mm) {
|
||||||
printf(" Heading reference: %s\n", (mm->opstatus.hrd == HEADING_TRUE ? "true north" : "magnetic north"));
|
printf(" Heading reference: %s\n", (mm->opstatus.hrd == HEADING_TRUE ? "true north" : "magnetic north"));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mm->tss.valid) {
|
if (mm->intent.valid) {
|
||||||
printf(" Target State and Status:\n");
|
printf(" Intent:\n");
|
||||||
if (mm->tss.altitude_valid)
|
if (mm->intent.heading_valid)
|
||||||
printf(" Target altitude: %s, %d ft\n", (mm->tss.altitude_type == TSS_ALTITUDE_MCP ? "MCP" : "FMS"), mm->tss.altitude);
|
printf(" Selected heading: %.1f\n", mm->intent.heading);
|
||||||
if (mm->tss.baro_valid)
|
if (mm->intent.fms_altitude_valid)
|
||||||
printf(" Altimeter setting: %.1f millibars\n", mm->tss.baro);
|
printf(" FMS selected altitude: %u ft\n", mm->intent.fms_altitude);
|
||||||
if (mm->tss.heading_valid)
|
if (mm->intent.mcp_altitude_valid)
|
||||||
printf(" Target heading: %d\n", mm->tss.heading);
|
printf(" MCP selected altitude: %u ft\n", mm->intent.mcp_altitude);
|
||||||
if (mm->tss.mode_valid) {
|
if (mm->intent.alt_setting_valid)
|
||||||
|
printf(" Altimeter setting: %.1f millibars\n", mm->intent.alt_setting);
|
||||||
|
|
||||||
|
if (mm->intent.altitude_source != TARGET_INVALID) {
|
||||||
|
printf(" Target altitude source: ");
|
||||||
|
switch (mm->intent.altitude_source) {
|
||||||
|
case TARGET_AIRCRAFT:
|
||||||
|
printf("aircraft altitude\n");
|
||||||
|
break;
|
||||||
|
case TARGET_MCP:
|
||||||
|
printf("MCP selected altitude\n");
|
||||||
|
break;
|
||||||
|
case TARGET_FMS:
|
||||||
|
printf("FMS selected altitude\n");
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
printf("unknown\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mm->intent.mode_autopilot ||
|
||||||
|
mm->intent.mode_vnav ||
|
||||||
|
mm->intent.mode_alt_hold ||
|
||||||
|
mm->intent.mode_approach ||
|
||||||
|
mm->intent.mode_lnav) {
|
||||||
printf(" Active modes: ");
|
printf(" Active modes: ");
|
||||||
if (mm->tss.mode_autopilot) printf("autopilot ");
|
if (mm->intent.mode_autopilot) printf("autopilot ");
|
||||||
if (mm->tss.mode_vnav) printf("VNAV ");
|
if (mm->intent.mode_vnav) printf("VNAV ");
|
||||||
if (mm->tss.mode_alt_hold) printf("altitude-hold ");
|
if (mm->intent.mode_alt_hold) printf("altitude-hold ");
|
||||||
if (mm->tss.mode_approach) printf("approach ");
|
if (mm->intent.mode_approach) printf("approach ");
|
||||||
|
if (mm->intent.mode_lnav) printf("LNAV ");
|
||||||
printf("\n");
|
printf("\n");
|
||||||
}
|
}
|
||||||
printf(" ACAS: %s\n", mm->tss.acas_operational ? "operational" : "NOT operational");
|
|
||||||
printf(" NACp: %d\n", mm->tss.nac_p);
|
|
||||||
printf(" NICbaro: %d\n", mm->tss.nic_baro);
|
|
||||||
printf(" SIL: %d (%s)\n", mm->tss.sil, (mm->opstatus.sil_type == SIL_PER_HOUR ? "per hour" : "per sample"));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
printf("\n");
|
printf("\n");
|
||||||
|
|
100
mode_s.h
Normal file
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
|
299
net_io.c
299
net_io.c
|
@ -582,18 +582,18 @@ static void modesSendSBSOutput(struct modesMessage *mm, struct aircraft *a) {
|
||||||
// Field 12 is the altitude (if we have it)
|
// Field 12 is the altitude (if we have it)
|
||||||
if (mm->altitude_valid) {
|
if (mm->altitude_valid) {
|
||||||
if (Modes.use_gnss) {
|
if (Modes.use_gnss) {
|
||||||
if (mm->altitude_source == ALTITUDE_GNSS) {
|
if (mm->altitude_source == ALTITUDE_GEOM) {
|
||||||
p += sprintf(p, ",%dH", mm->altitude);
|
p += sprintf(p, ",%dH", mm->altitude);
|
||||||
} else if (trackDataValid(&a->gnss_delta_valid)) {
|
} else if (trackDataValid(&a->geom_delta_valid)) {
|
||||||
p += sprintf(p, ",%dH", mm->altitude + a->gnss_delta);
|
p += sprintf(p, ",%dH", mm->altitude + a->geom_delta);
|
||||||
} else {
|
} else {
|
||||||
p += sprintf(p, ",%d", mm->altitude);
|
p += sprintf(p, ",%d", mm->altitude);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (mm->altitude_source == ALTITUDE_BARO) {
|
if (mm->altitude_source == ALTITUDE_BARO) {
|
||||||
p += sprintf(p, ",%d", mm->altitude);
|
p += sprintf(p, ",%d", mm->altitude);
|
||||||
} else if (trackDataValid(&a->gnss_delta_valid)) {
|
} else if (trackDataValid(&a->geom_delta_valid)) {
|
||||||
p += sprintf(p, ",%d", mm->altitude - a->gnss_delta);
|
p += sprintf(p, ",%d", mm->altitude - a->geom_delta);
|
||||||
} else {
|
} else {
|
||||||
p += sprintf(p, ",");
|
p += sprintf(p, ",");
|
||||||
}
|
}
|
||||||
|
@ -603,15 +603,15 @@ static void modesSendSBSOutput(struct modesMessage *mm, struct aircraft *a) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Field 13 is the ground Speed (if we have it)
|
// Field 13 is the ground Speed (if we have it)
|
||||||
if (mm->speed_valid && mm->speed_source == SPEED_GROUNDSPEED) {
|
if (mm->gs_valid) {
|
||||||
p += sprintf(p, ",%d", mm->speed);
|
p += sprintf(p, ",%d", mm->gs);
|
||||||
} else {
|
} else {
|
||||||
p += sprintf(p, ",");
|
p += sprintf(p, ",");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Field 14 is the ground Heading (if we have it)
|
// Field 14 is the ground Heading (if we have it)
|
||||||
if (mm->heading_valid && mm->heading_source == HEADING_TRUE) {
|
if (mm->track_valid) {
|
||||||
p += sprintf(p, ",%d", mm->heading);
|
p += sprintf(p, ",%.0f", mm->track);
|
||||||
} else {
|
} else {
|
||||||
p += sprintf(p, ",");
|
p += sprintf(p, ",");
|
||||||
}
|
}
|
||||||
|
@ -624,11 +624,23 @@ static void modesSendSBSOutput(struct modesMessage *mm, struct aircraft *a) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Field 17 is the VerticalRate (if we have it)
|
// Field 17 is the VerticalRate (if we have it)
|
||||||
if (mm->vert_rate_valid) {
|
if (Modes.use_gnss) {
|
||||||
p += sprintf(p, ",%d", mm->vert_rate);
|
if (mm->geom_rate_valid) {
|
||||||
|
p += sprintf(p, ",%dH", mm->geom_rate);
|
||||||
|
} else if (mm->baro_rate_valid) {
|
||||||
|
p += sprintf(p, ",%d", mm->baro_rate);
|
||||||
} else {
|
} else {
|
||||||
p += sprintf(p, ",");
|
p += sprintf(p, ",");
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
if (mm->baro_rate_valid) {
|
||||||
|
p += sprintf(p, ",%d", mm->baro_rate);
|
||||||
|
} else if (mm->geom_rate_valid) {
|
||||||
|
p += sprintf(p, ",%d", mm->geom_rate);
|
||||||
|
} else {
|
||||||
|
p += sprintf(p, ",");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Field 18 is the Squawk (if we have it)
|
// Field 18 is the Squawk (if we have it)
|
||||||
if (mm->squawk_valid) {
|
if (mm->squawk_valid) {
|
||||||
|
@ -1099,12 +1111,14 @@ static char *append_flags(char *p, char *end, struct aircraft *a, datasource_t s
|
||||||
p += snprintf(p, end-p, "\"lat\",\"lon\",");
|
p += snprintf(p, end-p, "\"lat\",\"lon\",");
|
||||||
if (a->altitude_valid.source == source)
|
if (a->altitude_valid.source == source)
|
||||||
p += snprintf(p, end-p, "\"altitude\",");
|
p += snprintf(p, end-p, "\"altitude\",");
|
||||||
if (a->heading_valid.source == source)
|
if (a->track_valid.source == source)
|
||||||
p += snprintf(p, end-p, "\"track\",");
|
p += snprintf(p, end-p, "\"track\",");
|
||||||
if (a->speed_valid.source == source)
|
if (a->gs_valid.source == source)
|
||||||
p += snprintf(p, end-p, "\"speed\",");
|
p += snprintf(p, end-p, "\"speed\",");
|
||||||
if (a->vert_rate_valid.source == source)
|
if (a->baro_rate_valid.source == source)
|
||||||
p += snprintf(p, end-p, "\"vert_rate\",");
|
p += snprintf(p, end-p, "\"baro_rate\",");
|
||||||
|
if (a->geom_rate_valid.source == source)
|
||||||
|
p += snprintf(p, end-p, "\"geom_rate\",");
|
||||||
if (a->category_valid.source == source)
|
if (a->category_valid.source == source)
|
||||||
p += snprintf(p, end-p, "\"category\",");
|
p += snprintf(p, end-p, "\"category\",");
|
||||||
if (p[-1] != '[')
|
if (p[-1] != '[')
|
||||||
|
@ -1173,14 +1187,30 @@ char *generateAircraftJson(const char *url_path, int *len) {
|
||||||
p += snprintf(p, end-p, ",\"lat\":%f,\"lon\":%f,\"nucp\":%u,\"seen_pos\":%.1f", a->lat, a->lon, a->pos_nuc, (now - a->position_valid.updated)/1000.0);
|
p += snprintf(p, end-p, ",\"lat\":%f,\"lon\":%f,\"nucp\":%u,\"seen_pos\":%.1f", a->lat, a->lon, a->pos_nuc, (now - a->position_valid.updated)/1000.0);
|
||||||
if (trackDataValid(&a->airground_valid) && a->airground_valid.source >= SOURCE_MODE_S_CHECKED && a->airground == AG_GROUND)
|
if (trackDataValid(&a->airground_valid) && a->airground_valid.source >= SOURCE_MODE_S_CHECKED && a->airground == AG_GROUND)
|
||||||
p += snprintf(p, end-p, ",\"altitude\":\"ground\"");
|
p += snprintf(p, end-p, ",\"altitude\":\"ground\"");
|
||||||
else if (trackDataValid(&a->altitude_valid))
|
else {
|
||||||
|
if (trackDataValid(&a->altitude_valid))
|
||||||
p += snprintf(p, end-p, ",\"altitude\":%d", a->altitude);
|
p += snprintf(p, end-p, ",\"altitude\":%d", a->altitude);
|
||||||
if (trackDataValid(&a->vert_rate_valid))
|
if (trackDataValid(&a->altitude_geom_valid))
|
||||||
p += snprintf(p, end-p, ",\"vert_rate\":%d", a->vert_rate);
|
p += snprintf(p, end-p, ",\"altitude_geom\":%d", a->altitude_geom);
|
||||||
if (trackDataValid(&a->heading_valid))
|
}
|
||||||
p += snprintf(p, end-p, ",\"track\":%d", a->heading);
|
if (trackDataValid(&a->baro_rate_valid))
|
||||||
if (trackDataValid(&a->speed_valid))
|
p += snprintf(p, end-p, ",\"baro_rate\":%d", a->baro_rate);
|
||||||
p += snprintf(p, end-p, ",\"speed\":%d", a->speed);
|
if (trackDataValid(&a->geom_rate_valid))
|
||||||
|
p += snprintf(p, end-p, ",\"geom_rate\":%d", a->geom_rate);
|
||||||
|
if (trackDataValid(&a->track_valid))
|
||||||
|
p += snprintf(p, end-p, ",\"track\":%.1f", a->track);
|
||||||
|
if (trackDataValid(&a->track_rate_valid))
|
||||||
|
p += snprintf(p, end-p, ",\"track_rate\":%.2f", a->track_rate);
|
||||||
|
if (trackDataValid(&a->gs_valid))
|
||||||
|
p += snprintf(p, end-p, ",\"gs\":%u", a->gs);
|
||||||
|
if (trackDataValid(&a->ias_valid))
|
||||||
|
p += snprintf(p, end-p, ",\"ias\":%u", a->ias);
|
||||||
|
if (trackDataValid(&a->tas_valid))
|
||||||
|
p += snprintf(p, end-p, ",\"tas\":%u", a->tas);
|
||||||
|
if (trackDataValid(&a->mach_valid))
|
||||||
|
p += snprintf(p, end-p, ",\"mach\":%.3f", a->mach);
|
||||||
|
if (trackDataValid(&a->roll_valid))
|
||||||
|
p += snprintf(p, end-p, ",\"roll\":%.1f", a->roll);
|
||||||
if (trackDataValid(&a->category_valid))
|
if (trackDataValid(&a->category_valid))
|
||||||
p += snprintf(p, end-p, ",\"category\":\"%02X\"", a->category);
|
p += snprintf(p, end-p, ",\"category\":\"%02X\"", a->category);
|
||||||
|
|
||||||
|
@ -1662,7 +1692,7 @@ static void modesReadFromClient(struct client *c) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#define TSV_MAX_PACKET_SIZE 275
|
#define TSV_MAX_PACKET_SIZE 400
|
||||||
|
|
||||||
static void writeFATSVPositionUpdate(float lat, float lon, float alt)
|
static void writeFATSVPositionUpdate(float lat, float lon, float alt)
|
||||||
{
|
{
|
||||||
|
@ -1747,23 +1777,28 @@ static void writeFATSVEvent(struct modesMessage *mm, struct aircraft *a)
|
||||||
switch (mm->msgtype) {
|
switch (mm->msgtype) {
|
||||||
case 20:
|
case 20:
|
||||||
case 21:
|
case 21:
|
||||||
if (mm->correctedbits > 0)
|
|
||||||
break; // only messages we trust a little more
|
|
||||||
|
|
||||||
// DF 20/21: Comm-B: emit if they've changed since we last sent them
|
// DF 20/21: Comm-B: emit if they've changed since we last sent them
|
||||||
//
|
switch (mm->commb_format) {
|
||||||
|
case COMMB_DATALINK_CAPS:
|
||||||
// BDS 1,0: data link capability report
|
// BDS 1,0: data link capability report
|
||||||
// BDS 3,0: ACAS RA report
|
if (memcmp(mm->MB, a->fatsv_emitted_bds_10, 7) != 0) {
|
||||||
if (mm->MB[0] == 0x10 && memcmp(mm->MB, a->fatsv_emitted_bds_10, 7) != 0) {
|
|
||||||
memcpy(a->fatsv_emitted_bds_10, mm->MB, 7);
|
memcpy(a->fatsv_emitted_bds_10, mm->MB, 7);
|
||||||
writeFATSVEventMessage(mm, "datalink_caps", mm->MB, 7);
|
writeFATSVEventMessage(mm, "datalink_caps", mm->MB, 7);
|
||||||
}
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
else if (mm->MB[0] == 0x30 && memcmp(mm->MB, a->fatsv_emitted_bds_30, 7) != 0) {
|
case COMMB_ACAS_RA:
|
||||||
|
// BDS 3,0: ACAS RA report
|
||||||
|
if (memcmp(mm->MB, a->fatsv_emitted_bds_30, 7) != 0) {
|
||||||
memcpy(a->fatsv_emitted_bds_30, mm->MB, 7);
|
memcpy(a->fatsv_emitted_bds_30, mm->MB, 7);
|
||||||
writeFATSVEventMessage(mm, "commb_acas_ra", mm->MB, 7);
|
writeFATSVEventMessage(mm, "commb_acas_ra", mm->MB, 7);
|
||||||
}
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
// nothing
|
||||||
|
break;
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 17:
|
case 17:
|
||||||
|
@ -1778,10 +1813,6 @@ static void writeFATSVEvent(struct modesMessage *mm, struct aircraft *a)
|
||||||
// aircraft operational status
|
// aircraft operational status
|
||||||
memcpy(a->fatsv_emitted_es_status, mm->ME, 7);
|
memcpy(a->fatsv_emitted_es_status, mm->ME, 7);
|
||||||
writeFATSVEventMessage(mm, "es_op_status", mm->ME, 7);
|
writeFATSVEventMessage(mm, "es_op_status", mm->ME, 7);
|
||||||
} else if (mm->metype == 29 && (mm->mesub == 0 || mm->mesub == 1) && memcmp(mm->ME, a->fatsv_emitted_es_target, 7) != 0) {
|
|
||||||
// target state and status
|
|
||||||
memcpy(a->fatsv_emitted_es_target, mm->ME, 7);
|
|
||||||
writeFATSVEventMessage(mm, "es_target", mm->ME, 7);
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -1791,13 +1822,13 @@ typedef enum {
|
||||||
TISB_IDENT = 1,
|
TISB_IDENT = 1,
|
||||||
TISB_SQUAWK = 2,
|
TISB_SQUAWK = 2,
|
||||||
TISB_ALTITUDE = 4,
|
TISB_ALTITUDE = 4,
|
||||||
TISB_ALTITUDE_GNSS = 8,
|
TISB_ALTITUDE_GEOM = 8,
|
||||||
TISB_SPEED = 16,
|
TISB_GS = 16,
|
||||||
TISB_SPEED_IAS = 32,
|
TISB_IAS = 32,
|
||||||
TISB_SPEED_TAS = 64,
|
TISB_TAS = 64,
|
||||||
TISB_POSITION = 128,
|
TISB_POSITION = 128,
|
||||||
TISB_HEADING = 256,
|
TISB_TRACK = 256,
|
||||||
TISB_HEADING_MAGNETIC = 512,
|
TISB_MAG_HEADING = 512,
|
||||||
TISB_AIRGROUND = 1024,
|
TISB_AIRGROUND = 1024,
|
||||||
TISB_CATEGORY = 2048
|
TISB_CATEGORY = 2048
|
||||||
} tisb_flags;
|
} tisb_flags;
|
||||||
|
@ -1807,9 +1838,9 @@ static inline unsigned unsigned_difference(unsigned v1, unsigned v2)
|
||||||
return (v1 > v2) ? (v1 - v2) : (v2 - v1);
|
return (v1 > v2) ? (v1 - v2) : (v2 - v1);
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline unsigned heading_difference(unsigned h1, unsigned h2)
|
static inline float heading_difference(float h1, float h2)
|
||||||
{
|
{
|
||||||
unsigned d = unsigned_difference(h1, h2);
|
float d = fabs(h1 - h2);
|
||||||
return (d < 180) ? d : (360 - d);
|
return (d < 180) ? d : (360 - d);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1832,17 +1863,6 @@ static void writeFATSV()
|
||||||
next_update = now + 1000;
|
next_update = now + 1000;
|
||||||
|
|
||||||
for (a = Modes.aircrafts; a; a = a->next) {
|
for (a = Modes.aircrafts; a; a = a->next) {
|
||||||
int altValid = 0;
|
|
||||||
int altGNSSValid = 0;
|
|
||||||
int positionValid = 0;
|
|
||||||
int speedValid = 0;
|
|
||||||
int speedIASValid = 0;
|
|
||||||
int speedTASValid = 0;
|
|
||||||
int headingValid = 0;
|
|
||||||
int headingMagValid = 0;
|
|
||||||
int airgroundValid = 0;
|
|
||||||
int categoryValid = 0;
|
|
||||||
|
|
||||||
uint64_t minAge;
|
uint64_t minAge;
|
||||||
|
|
||||||
int useful = 0;
|
int useful = 0;
|
||||||
|
@ -1859,16 +1879,25 @@ static void writeFATSV()
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
altValid = trackDataValidEx(&a->altitude_valid, now, 15000, SOURCE_MODE_S); // for non-ADS-B transponders, DF0/4/16/20 are the only sources of altitude data
|
int altValid = trackDataValidEx(&a->altitude_valid, now, 15000, SOURCE_MODE_S); // for non-ADS-B transponders, DF0/4/16/20 are the only sources of altitude data
|
||||||
altGNSSValid = trackDataValidEx(&a->altitude_gnss_valid, now, 15000, SOURCE_MODE_S_CHECKED);
|
int altGeomValid = trackDataValidEx(&a->altitude_geom_valid, now, 15000, SOURCE_MODE_S_CHECKED);
|
||||||
airgroundValid = trackDataValidEx(&a->airground_valid, now, 15000, SOURCE_MODE_S_CHECKED); // for non-ADS-B transponders, only trust DF11 CA field
|
int airgroundValid = trackDataValidEx(&a->airground_valid, now, 15000, SOURCE_MODE_S_CHECKED); // for non-ADS-B transponders, only trust DF11 CA field
|
||||||
positionValid = trackDataValidEx(&a->position_valid, now, 15000, SOURCE_MODE_S_CHECKED);
|
int baroRateValid = trackDataValidEx(&a->baro_rate_valid, now, 15000, SOURCE_MODE_S); // Comm-B or ES
|
||||||
headingValid = trackDataValidEx(&a->heading_valid, now, 15000, SOURCE_MODE_S_CHECKED);
|
int geomRateValid = trackDataValidEx(&a->geom_rate_valid, now, 15000, SOURCE_MODE_S); // Comm-B or ES
|
||||||
headingMagValid = trackDataValidEx(&a->heading_magnetic_valid, now, 15000, SOURCE_MODE_S_CHECKED);
|
int positionValid = trackDataValidEx(&a->position_valid, now, 15000, SOURCE_MODE_S_CHECKED);
|
||||||
speedValid = trackDataValidEx(&a->speed_valid, now, 15000, SOURCE_MODE_S_CHECKED);
|
int trackValid = trackDataValidEx(&a->track_valid, now, 15000, SOURCE_MODE_S); // Comm-B or ES
|
||||||
speedIASValid = trackDataValidEx(&a->speed_ias_valid, now, 15000, SOURCE_MODE_S_CHECKED);
|
int trackRateValid = trackDataValidEx(&a->track_rate_valid, now, 15000, SOURCE_MODE_S); // Comm-B
|
||||||
speedTASValid = trackDataValidEx(&a->speed_tas_valid, now, 15000, SOURCE_MODE_S_CHECKED);
|
int rollValid = trackDataValidEx(&a->roll_valid, now, 15000, SOURCE_MODE_S); // Comm-B
|
||||||
categoryValid = trackDataValidEx(&a->category_valid, now, 15000, SOURCE_MODE_S_CHECKED);
|
int magHeadingValid = trackDataValidEx(&a->mag_heading_valid, now, 15000, SOURCE_MODE_S); // Comm-B or ES
|
||||||
|
int gsValid = trackDataValidEx(&a->gs_valid, now, 15000, SOURCE_MODE_S); // Comm-B or ES
|
||||||
|
int iasValid = trackDataValidEx(&a->ias_valid, now, 15000, SOURCE_MODE_S); // Comm-B or ES
|
||||||
|
int tasValid = trackDataValidEx(&a->tas_valid, now, 15000, SOURCE_MODE_S); // Comm-B or ES
|
||||||
|
int machValid = trackDataValidEx(&a->mach_valid, now, 15000, SOURCE_MODE_S); // Comm-B
|
||||||
|
int categoryValid = trackDataValidEx(&a->category_valid, now, 15000, SOURCE_MODE_S_CHECKED);
|
||||||
|
int intentAltValid = trackDataValidEx(&a->intent_altitude_valid, now, 15000, SOURCE_MODE_S); // Comm-B or ES
|
||||||
|
int intentHeadingValid = trackDataValidEx(&a->intent_heading_valid, now, 15000, SOURCE_MODE_S); // Comm-B or ES
|
||||||
|
int altSettingValid = trackDataValidEx(&a->alt_setting_valid, now, 15000, SOURCE_MODE_S); // Comm-B or ES
|
||||||
|
int callsignValid = trackDataValidEx(&a->callsign, now, 15000, SOURCE_MODE_S); // Comm-B or ES
|
||||||
|
|
||||||
// If we are definitely on the ground, suppress any unreliable altitude info.
|
// If we are definitely on the ground, suppress any unreliable altitude info.
|
||||||
// When on the ground, ADS-B transponders don't emit an ADS-B message that includes
|
// When on the ground, ADS-B transponders don't emit an ADS-B message that includes
|
||||||
|
@ -1883,22 +1912,49 @@ static void writeFATSV()
|
||||||
if (altValid && abs(a->altitude - a->fatsv_emitted_altitude) >= 50) {
|
if (altValid && abs(a->altitude - a->fatsv_emitted_altitude) >= 50) {
|
||||||
changed = 1;
|
changed = 1;
|
||||||
}
|
}
|
||||||
if (altGNSSValid && abs(a->altitude_gnss - a->fatsv_emitted_altitude_gnss) >= 50) {
|
if (altGeomValid && abs(a->altitude_geom - a->fatsv_emitted_altitude_gnss) >= 50) {
|
||||||
changed = 1;
|
changed = 1;
|
||||||
}
|
}
|
||||||
if (headingValid && heading_difference(a->heading, a->fatsv_emitted_heading) >= 2) {
|
if (baroRateValid && abs(a->baro_rate - a->fatsv_emitted_baro_rate) > 500) {
|
||||||
changed = 1;
|
changed = 1;
|
||||||
}
|
}
|
||||||
if (headingMagValid && heading_difference(a->heading_magnetic, a->fatsv_emitted_heading_magnetic) >= 2) {
|
if (geomRateValid && abs(a->geom_rate - a->fatsv_emitted_geom_rate) > 500) {
|
||||||
changed = 1;
|
changed = 1;
|
||||||
}
|
}
|
||||||
if (speedValid && unsigned_difference(a->speed, a->fatsv_emitted_speed) >= 25) {
|
if (trackValid && heading_difference(a->track, a->fatsv_emitted_heading) >= 2) {
|
||||||
changed = 1;
|
changed = 1;
|
||||||
}
|
}
|
||||||
if (speedIASValid && unsigned_difference(a->speed_ias, a->fatsv_emitted_speed_ias) >= 25) {
|
if (trackRateValid && fabs(a->track_rate - a->fatsv_emitted_track_rate) >= 0.5) {
|
||||||
changed = 1;
|
changed = 1;
|
||||||
}
|
}
|
||||||
if (speedTASValid && unsigned_difference(a->speed_tas, a->fatsv_emitted_speed_tas) >= 25) {
|
if (rollValid && fabs(a->roll - a->fatsv_emitted_roll) >= 5.0) {
|
||||||
|
changed = 1;
|
||||||
|
}
|
||||||
|
if (magHeadingValid && heading_difference(a->mag_heading, a->fatsv_emitted_heading_magnetic) >= 2) {
|
||||||
|
changed = 1;
|
||||||
|
}
|
||||||
|
if (gsValid && unsigned_difference(a->gs, a->fatsv_emitted_speed) >= 25) {
|
||||||
|
changed = 1;
|
||||||
|
}
|
||||||
|
if (iasValid && unsigned_difference(a->ias, a->fatsv_emitted_speed_ias) >= 25) {
|
||||||
|
changed = 1;
|
||||||
|
}
|
||||||
|
if (tasValid && unsigned_difference(a->tas, a->fatsv_emitted_speed_tas) >= 25) {
|
||||||
|
changed = 1;
|
||||||
|
}
|
||||||
|
if (machValid && fabs(a->mach - a->fatsv_emitted_mach) >= 0.02) {
|
||||||
|
changed = 1;
|
||||||
|
}
|
||||||
|
if (intentAltValid && unsigned_difference(a->intent_altitude, a->fatsv_emitted_intent_altitude) > 50) {
|
||||||
|
changed = 1;
|
||||||
|
}
|
||||||
|
if (intentHeadingValid && heading_difference(a->intent_heading, a->fatsv_emitted_intent_heading) > 2) {
|
||||||
|
changed = 1;
|
||||||
|
}
|
||||||
|
if (altSettingValid && fabs(a->alt_setting - a->fatsv_emitted_alt_setting) > 0.8) { // 0.8 is the ES message resolution
|
||||||
|
changed = 1;
|
||||||
|
}
|
||||||
|
if (callsignValid && strcmp(a->callsign, a->fatsv_emitted_callsign) != 0) {
|
||||||
changed = 1;
|
changed = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1910,8 +1966,8 @@ static void writeFATSV()
|
||||||
// don't send mode S very often
|
// don't send mode S very often
|
||||||
minAge = 30000;
|
minAge = 30000;
|
||||||
} else if ((airgroundValid && a->airground == AG_GROUND) ||
|
} else if ((airgroundValid && a->airground == AG_GROUND) ||
|
||||||
(altValid && a->altitude < 500 && (!speedValid || a->speed < 200)) ||
|
(altValid && a->altitude < 500 && (!gsValid || a->gs < 200)) ||
|
||||||
(speedValid && a->speed < 100 && (!altValid || a->altitude < 1000))) {
|
(gsValid && a->gs < 100 && (!altValid || a->altitude < 1000))) {
|
||||||
// we are probably on the ground, increase the update rate
|
// we are probably on the ground, increase the update rate
|
||||||
minAge = 1000;
|
minAge = 1000;
|
||||||
} else if (!altValid || a->altitude < 10000) {
|
} else if (!altValid || a->altitude < 10000) {
|
||||||
|
@ -1944,7 +2000,7 @@ static void writeFATSV()
|
||||||
p += snprintf(p, bufsize(p, end), "\taddrtype\t%s", addrtype_short_string(a->addrtype));
|
p += snprintf(p, bufsize(p, end), "\taddrtype\t%s", addrtype_short_string(a->addrtype));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (trackDataValidEx(&a->callsign_valid, now, 35000, SOURCE_MODE_S_CHECKED) && strcmp(a->callsign, " ") != 0 && a->callsign_valid.updated > a->fatsv_last_emitted) {
|
if (trackDataValidEx(&a->callsign_valid, now, 35000, SOURCE_MODE_S) && strcmp(a->callsign, " ") != 0 && a->callsign_valid.updated > a->fatsv_last_emitted) {
|
||||||
p += snprintf(p, bufsize(p,end), "\tident\t%s", a->callsign);
|
p += snprintf(p, bufsize(p,end), "\tident\t%s", a->callsign);
|
||||||
switch (a->callsign_valid.source) {
|
switch (a->callsign_valid.source) {
|
||||||
case SOURCE_MODE_S:
|
case SOURCE_MODE_S:
|
||||||
|
@ -1971,7 +2027,7 @@ static void writeFATSV()
|
||||||
tisb |= (a->squawk_valid.source == SOURCE_TISB) ? TISB_SQUAWK : 0;
|
tisb |= (a->squawk_valid.source == SOURCE_TISB) ? TISB_SQUAWK : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// only emit alt, speed, latlon, track if they have been received since the last time
|
// only emit alt, speed, latlon, track etc if they have been received since the last time
|
||||||
// and are not stale
|
// and are not stale
|
||||||
|
|
||||||
if (altValid && a->altitude_valid.updated > a->fatsv_last_emitted) {
|
if (altValid && a->altitude_valid.updated > a->fatsv_last_emitted) {
|
||||||
|
@ -1981,32 +2037,50 @@ static void writeFATSV()
|
||||||
tisb |= (a->altitude_valid.source == SOURCE_TISB) ? TISB_ALTITUDE : 0;
|
tisb |= (a->altitude_valid.source == SOURCE_TISB) ? TISB_ALTITUDE : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (altGNSSValid && a->altitude_gnss_valid.updated > a->fatsv_last_emitted) {
|
if (altGeomValid && a->altitude_geom_valid.updated > a->fatsv_last_emitted) {
|
||||||
p += snprintf(p, bufsize(p,end), "\talt_gnss\t%d", a->altitude_gnss);
|
p += snprintf(p, bufsize(p,end), "\talt_geom\t%d", a->altitude_geom);
|
||||||
a->fatsv_emitted_altitude_gnss = a->altitude_gnss;
|
a->fatsv_emitted_altitude_gnss = a->altitude_geom;
|
||||||
useful = 1;
|
useful = 1;
|
||||||
tisb |= (a->altitude_gnss_valid.source == SOURCE_TISB) ? TISB_ALTITUDE_GNSS : 0;
|
tisb |= (a->altitude_geom_valid.source == SOURCE_TISB) ? TISB_ALTITUDE_GEOM : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (speedValid && a->speed_valid.updated > a->fatsv_last_emitted) {
|
if (baroRateValid && a->baro_rate_valid.updated > a->fatsv_last_emitted) {
|
||||||
p += snprintf(p, bufsize(p,end), "\tspeed\t%d", a->speed);
|
p += snprintf(p, bufsize(p,end), "\tbaro_rate\t%d", a->baro_rate);
|
||||||
a->fatsv_emitted_speed = a->speed;
|
a->fatsv_emitted_baro_rate = a->baro_rate;
|
||||||
useful = 1;
|
useful = 1;
|
||||||
tisb |= (a->speed_valid.source == SOURCE_TISB) ? TISB_SPEED : 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (speedIASValid && a->speed_ias_valid.updated > a->fatsv_last_emitted) {
|
if (geomRateValid && a->geom_rate_valid.updated > a->fatsv_last_emitted) {
|
||||||
p += snprintf(p, bufsize(p,end), "\tspeed_ias\t%d", a->speed_ias);
|
p += snprintf(p, bufsize(p,end), "\tgeom_rate\t%d", a->geom_rate);
|
||||||
a->fatsv_emitted_speed_ias = a->speed_ias;
|
a->fatsv_emitted_geom_rate = a->geom_rate;
|
||||||
useful = 1;
|
useful = 1;
|
||||||
tisb |= (a->speed_ias_valid.source == SOURCE_TISB) ? TISB_SPEED_IAS : 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (speedTASValid && a->speed_tas_valid.updated > a->fatsv_last_emitted) {
|
if (gsValid && a->gs_valid.updated > a->fatsv_last_emitted) {
|
||||||
p += snprintf(p, bufsize(p,end), "\tspeed_tas\t%d", a->speed_tas);
|
p += snprintf(p, bufsize(p,end), "\tgs\t%u", a->gs);
|
||||||
a->fatsv_emitted_speed_tas = a->speed_tas;
|
a->fatsv_emitted_speed = a->gs;
|
||||||
|
useful = 1;
|
||||||
|
tisb |= (a->gs_valid.source == SOURCE_TISB) ? TISB_GS : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (iasValid && a->ias_valid.updated > a->fatsv_last_emitted) {
|
||||||
|
p += snprintf(p, bufsize(p,end), "\tias\t%u", a->ias);
|
||||||
|
a->fatsv_emitted_speed_ias = a->ias;
|
||||||
|
useful = 1;
|
||||||
|
tisb |= (a->ias_valid.source == SOURCE_TISB) ? TISB_IAS : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tasValid && a->tas_valid.updated > a->fatsv_last_emitted) {
|
||||||
|
p += snprintf(p, bufsize(p,end), "\ttas\t%u", a->tas);
|
||||||
|
a->fatsv_emitted_speed_tas = a->tas;
|
||||||
|
useful = 1;
|
||||||
|
tisb |= (a->tas_valid.source == SOURCE_TISB) ? TISB_TAS : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (machValid && a->mach_valid.updated > a->fatsv_last_emitted) {
|
||||||
|
p += snprintf(p, bufsize(p,end), "\tmach\t%.3f", a->mach);
|
||||||
|
a->fatsv_emitted_mach = a->mach;
|
||||||
useful = 1;
|
useful = 1;
|
||||||
tisb |= (a->speed_tas_valid.source == SOURCE_TISB) ? TISB_SPEED_TAS : 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (positionValid && a->position_valid.updated > a->fatsv_last_emitted) {
|
if (positionValid && a->position_valid.updated > a->fatsv_last_emitted) {
|
||||||
|
@ -2015,18 +2089,30 @@ static void writeFATSV()
|
||||||
tisb |= (a->position_valid.source == SOURCE_TISB) ? TISB_POSITION : 0;
|
tisb |= (a->position_valid.source == SOURCE_TISB) ? TISB_POSITION : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (headingValid && a->heading_valid.updated > a->fatsv_last_emitted) {
|
if (trackValid && a->track_valid.updated > a->fatsv_last_emitted) {
|
||||||
p += snprintf(p, bufsize(p,end), "\theading\t%d", a->heading);
|
p += snprintf(p, bufsize(p,end), "\ttrack\t%.0f", a->track);
|
||||||
a->fatsv_emitted_heading = a->heading;
|
a->fatsv_emitted_heading = a->track;
|
||||||
useful = 1;
|
useful = 1;
|
||||||
tisb |= (a->heading_valid.source == SOURCE_TISB) ? TISB_HEADING : 0;
|
tisb |= (a->track_valid.source == SOURCE_TISB) ? TISB_TRACK : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (headingMagValid && a->heading_magnetic_valid.updated > a->fatsv_last_emitted) {
|
if (trackRateValid && a->track_rate_valid.updated > a->fatsv_last_emitted) {
|
||||||
p += snprintf(p, bufsize(p,end), "\theading_magnetic\t%d", a->heading_magnetic);
|
p += snprintf(p, bufsize(p,end), "\ttrack_rate\t%.2f", a->track_rate);
|
||||||
a->fatsv_emitted_heading_magnetic = a->heading_magnetic;
|
a->fatsv_emitted_track_rate = a->track_rate;
|
||||||
useful = 1;
|
useful = 1;
|
||||||
tisb |= (a->heading_magnetic_valid.source == SOURCE_TISB) ? TISB_HEADING_MAGNETIC : 0;
|
}
|
||||||
|
|
||||||
|
if (rollValid && a->roll_valid.updated > a->fatsv_last_emitted) {
|
||||||
|
p += snprintf(p, bufsize(p,end), "\troll\t%.1f", a->roll);
|
||||||
|
a->fatsv_emitted_roll = a->roll;
|
||||||
|
useful = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (magHeadingValid && a->mag_heading_valid.updated > a->fatsv_last_emitted) {
|
||||||
|
p += snprintf(p, bufsize(p,end), "\tmag_heading\t%.0f", a->mag_heading);
|
||||||
|
a->fatsv_emitted_heading_magnetic = a->mag_heading;
|
||||||
|
useful = 1;
|
||||||
|
tisb |= (a->mag_heading_valid.source == SOURCE_TISB) ? TISB_MAG_HEADING : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (airgroundValid && (a->airground == AG_GROUND || a->airground == AG_AIRBORNE) && a->airground_valid.updated > a->fatsv_last_emitted) {
|
if (airgroundValid && (a->airground == AG_GROUND || a->airground == AG_AIRBORNE) && a->airground_valid.updated > a->fatsv_last_emitted) {
|
||||||
|
@ -2043,6 +2129,25 @@ static void writeFATSV()
|
||||||
tisb |= (a->category_valid.source == SOURCE_TISB) ? TISB_CATEGORY : 0;
|
tisb |= (a->category_valid.source == SOURCE_TISB) ? TISB_CATEGORY : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (intentAltValid && a->intent_altitude_valid.updated > a->fatsv_last_emitted) {
|
||||||
|
p += snprintf(p, bufsize(p,end), "\tintent_alt\t%u", a->intent_altitude);
|
||||||
|
a->fatsv_emitted_intent_altitude = a->intent_altitude;
|
||||||
|
useful = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (intentHeadingValid && a->intent_heading_valid.updated > a->fatsv_last_emitted) {
|
||||||
|
p += snprintf(p, bufsize(p,end), "\tintent_heading\t%.0f", a->intent_heading);
|
||||||
|
a->fatsv_emitted_intent_heading = a->intent_heading;
|
||||||
|
useful = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (altSettingValid && a->alt_setting_valid.updated > a->fatsv_last_emitted) {
|
||||||
|
p += snprintf(p, bufsize(p,end), "\talt_setting\t%.1f", a->alt_setting);
|
||||||
|
a->fatsv_emitted_alt_setting = a->alt_setting;
|
||||||
|
useful = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// if we didn't get anything interesting, bail out.
|
// if we didn't get anything interesting, bail out.
|
||||||
// We don't need to do anything special to unwind prepareWrite().
|
// We don't need to do anything special to unwind prepareWrite().
|
||||||
if (!useful) {
|
if (!useful) {
|
||||||
|
|
114
track.c
114
track.c
|
@ -209,12 +209,12 @@ static int speed_check(struct aircraft *a, double lat, double lon, uint64_t now,
|
||||||
|
|
||||||
elapsed = trackDataAge(&a->position_valid, now);
|
elapsed = trackDataAge(&a->position_valid, now);
|
||||||
|
|
||||||
if (trackDataValid(&a->speed_valid))
|
if (trackDataValid(&a->gs_valid))
|
||||||
speed = a->speed;
|
speed = a->gs;
|
||||||
else if (trackDataValid(&a->speed_ias_valid))
|
else if (trackDataValid(&a->tas_valid))
|
||||||
speed = a->speed_ias * 4 / 3;
|
speed = a->tas * 4 / 3;
|
||||||
else if (trackDataValid(&a->speed_tas_valid))
|
else if (trackDataValid(&a->ias_valid))
|
||||||
speed = a->speed_tas * 4 / 3;
|
speed = a->ias * 2;
|
||||||
else
|
else
|
||||||
speed = surface ? 100 : 600; // guess
|
speed = surface ? 100 : 600; // guess
|
||||||
|
|
||||||
|
@ -427,7 +427,7 @@ static void updatePosition(struct aircraft *a, struct modesMessage *mm, uint64_t
|
||||||
++Modes.stats_current.cpr_surface;
|
++Modes.stats_current.cpr_surface;
|
||||||
|
|
||||||
// Surface: 25 seconds if >25kt or speed unknown, 50 seconds otherwise
|
// Surface: 25 seconds if >25kt or speed unknown, 50 seconds otherwise
|
||||||
if (mm->speed_valid && mm->speed <= 25)
|
if (mm->gs_valid && mm->gs <= 25)
|
||||||
max_elapsed = 50000;
|
max_elapsed = 50000;
|
||||||
else
|
else
|
||||||
max_elapsed = 25000;
|
max_elapsed = 25000;
|
||||||
|
@ -562,37 +562,52 @@ struct aircraft *trackUpdateFromMessage(struct modesMessage *mm)
|
||||||
a->squawk = mm->squawk;
|
a->squawk = mm->squawk;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mm->altitude_valid && mm->altitude_source == ALTITUDE_GNSS && accept_data(&a->altitude_gnss_valid, mm->source, now)) {
|
if (mm->altitude_valid && mm->altitude_source == ALTITUDE_GEOM && accept_data(&a->altitude_geom_valid, mm->source, now)) {
|
||||||
a->altitude_gnss = mm->altitude;
|
a->altitude_geom = mm->altitude;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mm->gnss_delta_valid && accept_data(&a->gnss_delta_valid, mm->source, now)) {
|
if (mm->geom_delta_valid && accept_data(&a->geom_delta_valid, mm->source, now)) {
|
||||||
a->gnss_delta = mm->gnss_delta;
|
a->geom_delta = mm->geom_delta;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mm->heading_valid && mm->heading_source == HEADING_TRUE && accept_data(&a->heading_valid, mm->source, now)) {
|
if (mm->track_valid && accept_data(&a->track_valid, mm->source, now)) {
|
||||||
a->heading = mm->heading;
|
a->track = mm->track;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mm->heading_valid && mm->heading_source == HEADING_MAGNETIC && accept_data(&a->heading_magnetic_valid, mm->source, now)) {
|
if (mm->track_rate_valid && accept_data(&a->track_rate_valid, mm->source, now)) {
|
||||||
a->heading_magnetic = mm->heading;
|
a->track_rate = mm->track_rate;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mm->speed_valid && mm->speed_source == SPEED_GROUNDSPEED && accept_data(&a->speed_valid, mm->source, now)) {
|
if (mm->roll_valid && accept_data(&a->roll_valid, mm->source, now)) {
|
||||||
a->speed = mm->speed;
|
a->roll = mm->roll;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mm->speed_valid && mm->speed_source == SPEED_IAS && accept_data(&a->speed_ias_valid, mm->source, now)) {
|
if (mm->mag_heading_valid && accept_data(&a->mag_heading_valid, mm->source, now)) {
|
||||||
a->speed_ias = mm->speed;
|
a->mag_heading = mm->mag_heading;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mm->speed_valid && mm->speed_source == SPEED_TAS && accept_data(&a->speed_tas_valid, mm->source, now)) {
|
if (mm->gs_valid && accept_data(&a->gs_valid, mm->source, now)) {
|
||||||
a->speed_tas = mm->speed;
|
a->gs = mm->gs;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mm->vert_rate_valid && accept_data(&a->vert_rate_valid, mm->source, now)) {
|
if (mm->ias_valid && accept_data(&a->ias_valid, mm->source, now)) {
|
||||||
a->vert_rate = mm->vert_rate;
|
a->ias = mm->ias;
|
||||||
a->vert_rate_source = mm->vert_rate_source;
|
}
|
||||||
|
|
||||||
|
if (mm->tas_valid && accept_data(&a->tas_valid, mm->source, now)) {
|
||||||
|
a->tas = mm->tas;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mm->mach_valid && accept_data(&a->mach_valid, mm->source, now)) {
|
||||||
|
a->mach = mm->mach;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mm->baro_rate_valid && accept_data(&a->baro_rate_valid, mm->source, now)) {
|
||||||
|
a->baro_rate = mm->baro_rate;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mm->geom_rate_valid && accept_data(&a->geom_rate_valid, mm->source, now)) {
|
||||||
|
a->geom_rate = mm->geom_rate;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mm->category_valid && accept_data(&a->category_valid, mm->source, now)) {
|
if (mm->category_valid && accept_data(&a->category_valid, mm->source, now)) {
|
||||||
|
@ -607,6 +622,22 @@ struct aircraft *trackUpdateFromMessage(struct modesMessage *mm)
|
||||||
memcpy(a->callsign, mm->callsign, sizeof(a->callsign));
|
memcpy(a->callsign, mm->callsign, sizeof(a->callsign));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (mm->intent.valid) {
|
||||||
|
if (mm->intent.mcp_altitude_valid && accept_data(&a->intent_altitude_valid, mm->source, now)) {
|
||||||
|
a->intent_altitude = mm->intent.mcp_altitude;
|
||||||
|
} else if (mm->intent.fms_altitude_valid && accept_data(&a->intent_altitude_valid, mm->source, now)) {
|
||||||
|
a->intent_altitude = mm->intent.fms_altitude;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mm->intent.heading_valid && accept_data(&a->intent_heading_valid, mm->source, now)) {
|
||||||
|
a->intent_heading = mm->intent.heading;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mm->intent.alt_setting_valid && accept_data(&a->alt_setting_valid, mm->source, now)) {
|
||||||
|
a->alt_setting = mm->intent.alt_setting;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// CPR, even
|
// CPR, even
|
||||||
if (mm->cpr_valid && !mm->cpr_odd && accept_data(&a->cpr_even_valid, mm->source, now)) {
|
if (mm->cpr_valid && !mm->cpr_odd && accept_data(&a->cpr_even_valid, mm->source, now)) {
|
||||||
a->cpr_even_type = mm->cpr_type;
|
a->cpr_even_type = mm->cpr_type;
|
||||||
|
@ -625,12 +656,12 @@ struct aircraft *trackUpdateFromMessage(struct modesMessage *mm)
|
||||||
|
|
||||||
// Now handle derived data
|
// Now handle derived data
|
||||||
|
|
||||||
// derive GNSS if we have baro + delta
|
// derive geometric altitude if we have baro + delta
|
||||||
if (compare_validity(&a->altitude_valid, &a->altitude_gnss_valid, now) > 0 &&
|
if (compare_validity(&a->altitude_valid, &a->altitude_geom_valid, now) > 0 &&
|
||||||
compare_validity(&a->gnss_delta_valid, &a->altitude_gnss_valid, now) > 0) {
|
compare_validity(&a->geom_delta_valid, &a->altitude_geom_valid, now) > 0) {
|
||||||
// Baro and delta are both more recent than GNSS, derive GNSS from baro + delta
|
// Baro and delta are both more recent than geometric, derive geometric from baro + delta
|
||||||
a->altitude_gnss = a->altitude + a->gnss_delta;
|
a->altitude_geom = a->altitude + a->geom_delta;
|
||||||
combine_validity(&a->altitude_gnss_valid, &a->altitude_valid, &a->gnss_delta_valid);
|
combine_validity(&a->altitude_geom_valid, &a->altitude_valid, &a->geom_delta_valid);
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we've got a new cprlat or cprlon
|
// If we've got a new cprlat or cprlon
|
||||||
|
@ -752,17 +783,24 @@ static void trackRemoveStaleAircraft(uint64_t now)
|
||||||
#define EXPIRE(_f) do { if (a->_f##_valid.source != SOURCE_INVALID && now >= a->_f##_valid.expires) { a->_f##_valid.source = SOURCE_INVALID; } } while (0)
|
#define EXPIRE(_f) do { if (a->_f##_valid.source != SOURCE_INVALID && now >= a->_f##_valid.expires) { a->_f##_valid.source = SOURCE_INVALID; } } while (0)
|
||||||
EXPIRE(callsign);
|
EXPIRE(callsign);
|
||||||
EXPIRE(altitude);
|
EXPIRE(altitude);
|
||||||
EXPIRE(altitude_gnss);
|
EXPIRE(altitude_geom);
|
||||||
EXPIRE(gnss_delta);
|
EXPIRE(geom_delta);
|
||||||
EXPIRE(speed);
|
EXPIRE(gs);
|
||||||
EXPIRE(speed_ias);
|
EXPIRE(ias);
|
||||||
EXPIRE(speed_tas);
|
EXPIRE(tas);
|
||||||
EXPIRE(heading);
|
EXPIRE(mach);
|
||||||
EXPIRE(heading_magnetic);
|
EXPIRE(track);
|
||||||
EXPIRE(vert_rate);
|
EXPIRE(track_rate);
|
||||||
|
EXPIRE(roll);
|
||||||
|
EXPIRE(mag_heading);
|
||||||
|
EXPIRE(baro_rate);
|
||||||
|
EXPIRE(geom_rate);
|
||||||
EXPIRE(squawk);
|
EXPIRE(squawk);
|
||||||
EXPIRE(category);
|
EXPIRE(category);
|
||||||
EXPIRE(airground);
|
EXPIRE(airground);
|
||||||
|
EXPIRE(alt_setting);
|
||||||
|
EXPIRE(intent_altitude);
|
||||||
|
EXPIRE(intent_heading);
|
||||||
EXPIRE(cpr_odd);
|
EXPIRE(cpr_odd);
|
||||||
EXPIRE(cpr_even);
|
EXPIRE(cpr_even);
|
||||||
EXPIRE(position);
|
EXPIRE(position);
|
||||||
|
|
73
track.h
73
track.h
|
@ -88,30 +88,41 @@ struct aircraft {
|
||||||
data_validity altitude_valid;
|
data_validity altitude_valid;
|
||||||
int altitude; // Altitude (Baro)
|
int altitude; // Altitude (Baro)
|
||||||
|
|
||||||
data_validity altitude_gnss_valid;
|
data_validity altitude_geom_valid;
|
||||||
int altitude_gnss; // Altitude (GNSS)
|
int altitude_geom; // Altitude (Geometric)
|
||||||
|
|
||||||
data_validity gnss_delta_valid;
|
data_validity geom_delta_valid;
|
||||||
int gnss_delta; // Difference between GNSS and Baro altitudes
|
int geom_delta; // Difference between Geometric and Baro altitudes
|
||||||
|
|
||||||
data_validity speed_valid;
|
data_validity gs_valid;
|
||||||
unsigned speed;
|
unsigned gs;
|
||||||
|
|
||||||
data_validity speed_ias_valid;
|
data_validity ias_valid;
|
||||||
unsigned speed_ias;
|
unsigned ias;
|
||||||
|
|
||||||
data_validity speed_tas_valid;
|
data_validity tas_valid;
|
||||||
unsigned speed_tas;
|
unsigned tas;
|
||||||
|
|
||||||
data_validity heading_valid;
|
data_validity mach_valid;
|
||||||
unsigned heading; // Heading (OK it's really the track)
|
float mach;
|
||||||
|
|
||||||
data_validity heading_magnetic_valid;
|
data_validity track_valid;
|
||||||
unsigned heading_magnetic; // Heading
|
float track; // Ground track
|
||||||
|
|
||||||
data_validity vert_rate_valid;
|
data_validity track_rate_valid;
|
||||||
int vert_rate; // Vertical rate
|
float track_rate; // Rate of change of ground track, degrees/second
|
||||||
altitude_source_t vert_rate_source;
|
|
||||||
|
data_validity roll_valid;
|
||||||
|
float roll; // Roll angle, degrees right
|
||||||
|
|
||||||
|
data_validity mag_heading_valid;
|
||||||
|
float mag_heading; // Magnetic heading
|
||||||
|
|
||||||
|
data_validity baro_rate_valid;
|
||||||
|
int baro_rate; // Vertical rate (barometric)
|
||||||
|
|
||||||
|
data_validity geom_rate_valid;
|
||||||
|
int geom_rate; // Vertical rate (geometric)
|
||||||
|
|
||||||
data_validity squawk_valid;
|
data_validity squawk_valid;
|
||||||
unsigned squawk; // Squawk
|
unsigned squawk; // Squawk
|
||||||
|
@ -122,6 +133,15 @@ struct aircraft {
|
||||||
data_validity airground_valid;
|
data_validity airground_valid;
|
||||||
airground_t airground; // air/ground status
|
airground_t airground; // air/ground status
|
||||||
|
|
||||||
|
data_validity alt_setting_valid;
|
||||||
|
float alt_setting; // Altimeter setting (QNH/QFE), millibars
|
||||||
|
|
||||||
|
data_validity intent_altitude_valid;
|
||||||
|
unsigned intent_altitude; // intent altitude (FMS or FCU selected altitude)
|
||||||
|
|
||||||
|
data_validity intent_heading_valid;
|
||||||
|
float intent_heading; // intent heading, degrees (0-359)
|
||||||
|
|
||||||
data_validity cpr_odd_valid; // Last seen even CPR message
|
data_validity cpr_odd_valid; // Last seen even CPR message
|
||||||
cpr_type_t cpr_odd_type;
|
cpr_type_t cpr_odd_type;
|
||||||
unsigned cpr_odd_lat;
|
unsigned cpr_odd_lat;
|
||||||
|
@ -143,16 +163,23 @@ struct aircraft {
|
||||||
|
|
||||||
int fatsv_emitted_altitude; // last FA emitted altitude
|
int fatsv_emitted_altitude; // last FA emitted altitude
|
||||||
int fatsv_emitted_altitude_gnss; // -"- GNSS altitude
|
int fatsv_emitted_altitude_gnss; // -"- GNSS altitude
|
||||||
int fatsv_emitted_heading; // -"- true track
|
int fatsv_emitted_baro_rate; // -"- barometric rate
|
||||||
int fatsv_emitted_heading_magnetic; // -"- magnetic heading
|
int fatsv_emitted_geom_rate; // -"- geometric rate
|
||||||
int fatsv_emitted_speed; // -"- groundspeed
|
float fatsv_emitted_heading; // -"- true track
|
||||||
int fatsv_emitted_speed_ias; // -"- IAS
|
float fatsv_emitted_heading_magnetic; // -"- magnetic heading
|
||||||
int fatsv_emitted_speed_tas; // -"- TAS
|
float fatsv_emitted_track_rate; // -"- track rate of change
|
||||||
|
float fatsv_emitted_roll; // -"- roll angle
|
||||||
|
unsigned fatsv_emitted_speed; // -"- groundspeed
|
||||||
|
unsigned fatsv_emitted_speed_ias; // -"- IAS
|
||||||
|
unsigned fatsv_emitted_speed_tas; // -"- TAS
|
||||||
|
float fatsv_emitted_mach; // -"- Mach number
|
||||||
airground_t fatsv_emitted_airground; // -"- air/ground state
|
airground_t fatsv_emitted_airground; // -"- air/ground state
|
||||||
|
unsigned fatsv_emitted_intent_altitude; // -"- intent altitude
|
||||||
|
float fatsv_emitted_intent_heading; // -"- intent heading
|
||||||
|
float fatsv_emitted_alt_setting; // -"- altimeter setting
|
||||||
unsigned char fatsv_emitted_bds_10[7]; // -"- BDS 1,0 message
|
unsigned char fatsv_emitted_bds_10[7]; // -"- BDS 1,0 message
|
||||||
unsigned char fatsv_emitted_bds_30[7]; // -"- BDS 3,0 message
|
unsigned char fatsv_emitted_bds_30[7]; // -"- BDS 3,0 message
|
||||||
unsigned char fatsv_emitted_es_status[7]; // -"- ES operational status message
|
unsigned char fatsv_emitted_es_status[7]; // -"- ES operational status message
|
||||||
unsigned char fatsv_emitted_es_target[7]; // -"- ES target status message
|
|
||||||
unsigned char fatsv_emitted_es_acas_ra[7]; // -"- ES ACAS RA report message
|
unsigned char fatsv_emitted_es_acas_ra[7]; // -"- ES ACAS RA report message
|
||||||
|
|
||||||
uint64_t fatsv_last_emitted; // time (millis) aircraft was last FA emitted
|
uint64_t fatsv_last_emitted; // time (millis) aircraft was last FA emitted
|
||||||
|
|
Loading…
Reference in a new issue