diff --git a/Makefile b/Makefile index 4c77d21..b4f6aaa 100644 --- a/Makefile +++ b/Makefile @@ -40,13 +40,13 @@ all: dump1090 view1090 %.o: %.c *.h $(CC) $(CPPFLAGS) $(CFLAGS) -c $< -o $@ -dump1090: dump1090.o anet.o interactive.o mode_ac.o mode_s.o net_io.o crc.o demod_2400.o stats.o cpr.o icao_filter.o track.o util.o convert.o sdr_ifile.o sdr.o $(SDR_OBJ) $(COMPAT) +dump1090: dump1090.o anet.o interactive.o mode_ac.o mode_s.o comm_b.o net_io.o crc.o demod_2400.o stats.o cpr.o icao_filter.o track.o util.o convert.o sdr_ifile.o sdr.o $(SDR_OBJ) $(COMPAT) $(CC) -g -o $@ $^ $(LDFLAGS) $(LIBS) $(LIBS_SDR) -lncurses -view1090: view1090.o anet.o interactive.o mode_ac.o mode_s.o net_io.o crc.o stats.o cpr.o icao_filter.o track.o util.o $(COMPAT) +view1090: view1090.o anet.o interactive.o mode_ac.o mode_s.o comm_b.o net_io.o crc.o stats.o cpr.o icao_filter.o track.o util.o $(COMPAT) $(CC) -g -o $@ $^ $(LDFLAGS) $(LIBS) -lncurses -faup1090: faup1090.o anet.o mode_ac.o mode_s.o net_io.o crc.o stats.o cpr.o icao_filter.o track.o util.o $(COMPAT) +faup1090: faup1090.o anet.o mode_ac.o mode_s.o comm_b.o net_io.o crc.o stats.o cpr.o icao_filter.o track.o util.o $(COMPAT) $(CC) -g -o $@ $^ $(LDFLAGS) $(LIBS) clean: diff --git a/comm_b.c b/comm_b.c new file mode 100644 index 0000000..519bf84 --- /dev/null +++ b/comm_b.c @@ -0,0 +1,740 @@ +// Part of dump1090, a Mode S message decoder for RTLSDR devices. +// +// comm_b.c: Comm-B message decoding +// +// Copyright (c) 2017 FlightAware, LLC +// Copyright (c) 2017 Oliver Jowett +// +// 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 . + +#include "dump1090.h" + +typedef int (*CommBDecoderFn)(struct modesMessage *,bool); + +static int decodeEmptyResponse(struct modesMessage *mm, bool store); +static int decodeBDS10(struct modesMessage *mm, bool store); +static int decodeBDS17(struct modesMessage *mm, bool store); +static int decodeBDS20(struct modesMessage *mm, bool store); +static int decodeBDS30(struct modesMessage *mm, bool store); +static int decodeBDS40(struct modesMessage *mm, bool store); +static int decodeBDS50(struct modesMessage *mm, bool store); +static int decodeBDS60(struct modesMessage *mm, bool store); + +static CommBDecoderFn comm_b_decoders[] = { + &decodeEmptyResponse, + &decodeBDS10, + &decodeBDS20, + &decodeBDS30, + &decodeBDS17, + &decodeBDS40, + &decodeBDS50, + &decodeBDS60 +}; + +void decodeCommB(struct modesMessage *mm) +{ + mm->commb_format = COMMB_UNKNOWN; + + // If DR or UM are set, this message is _probably_ noise + // as nothing really seems to use the multisite broadcast stuff? + // Also skip anything that had errors corrected + if (mm->DR != 0 || mm->UM != 0 || mm->correctedbits > 0) { + return; + } + + // This is a bit hairy as we don't know what the requested register was + int bestScore = 0; + CommBDecoderFn bestDecoder = NULL; + + for (unsigned i = 0; i < (sizeof(comm_b_decoders) / sizeof(comm_b_decoders[0])); ++i) { + int score = comm_b_decoders[i](mm, false); + if (score > bestScore) { + bestScore = score; + bestDecoder = comm_b_decoders[i]; + } + } + + if (bestDecoder) { + // decode it + bestDecoder(mm, true); + } +} + +static int decodeEmptyResponse(struct modesMessage *mm, bool store) +{ + for (unsigned i = 0; i < 7; ++i) { + if (mm->MB[i] != 0) { + return 0; + } + } + + if (store) { + mm->commb_format = COMMB_EMPTY_RESPONSE; + } + + return 56; +} + +// BDS1,0 Datalink capabilities +static int decodeBDS10(struct modesMessage *mm, bool store) +{ + unsigned char *msg = mm->MB; + + // BDS identifier + if (msg[0] != 0x10) { + return 0; + } + + // Reserved bits + if (getbits(msg, 10, 14) != 0) { + return 0; + } + + // Looks plausible. + + if (store) { + mm->commb_format = COMMB_DATALINK_CAPS; + } + + return 56; +} + +// BDS1,7 Common usage GICB capability report +static int decodeBDS17(struct modesMessage *mm, bool store) +{ + unsigned char *msg = mm->MB; + + // reserved bits + if (getbits(msg, 25, 56) != 0) { + return 0; + } + + int score = 0; + if (getbit(msg, 7)) { + score += 1; // 2,0 aircraft identification + } else { + // BDS2,0 is on almost everything + score -= 2; + } + + // unlikely bits + if (getbit(msg, 10)) { // 4,1 next waypoint identifier + score -= 2; + } + if (getbit(msg, 11)) { // 4,2 next waypoint position + score -= 2; + } + if (getbit(msg, 12)) { // 4,3 next waypoint information + score -= 2; + } + if (getbit(msg, 13)) { // 4,4 meterological routine report + score -= 2; + } + if (getbit(msg, 14)) { // 4,4 meterological hazard report + score -= 2; + } + if (getbit(msg, 20)) { // 5,4 waypoint 1 + score -= 2; + } + if (getbit(msg, 21)) { // 5,5 waypoint 2 + score -= 2; + } + if (getbit(msg, 22)) { // 5,6 waypoint 3 + score -= 2; + } + + if (getbit(msg, 1) && getbit(msg, 2) && getbit(msg, 3) && getbit(msg, 4) && getbit(msg, 5)) { + // looks like ES capable + score += 5; + if (getbit(msg, 6)) { + // ES EDI + score += 1; + } + } else if (!getbit(msg, 1) && !getbit(msg, 2) && !getbit(msg, 3) && !getbit(msg, 4) && !getbit(msg, 5) && !getbit(msg, 6)) { + // not ES capable + score += 1; + } else { + // partial ES support, unlikely + score -= 12; + } + + if (getbit(msg, 16) && getbit(msg, 24)) { + // track/turn, heading/speed + score += 2; + if (getbit(msg, 9)) { + // vertical intent + score += 1; + } + } else if (!getbit(msg, 16) && !getbit(msg, 24) && !getbit(msg, 9)) { + // neither + score += 1; + } else { + // unlikely + score -= 6; + } + + if (store) { + mm->commb_format = COMMB_GICB_CAPS; + } + + return score; +} + +// BDS2,0 Aircraft identification +static int decodeBDS20(struct modesMessage *mm, bool store) +{ + char callsign[9]; + unsigned char *msg = mm->MB; + + // BDS identifier + if (msg[0] != 0x20) { + return 0; + } + + callsign[0] = ais_charset[getbits(msg, 9, 14)]; + callsign[1] = ais_charset[getbits(msg, 15, 20)]; + callsign[2] = ais_charset[getbits(msg, 21, 26)]; + callsign[3] = ais_charset[getbits(msg, 27, 32)]; + callsign[4] = ais_charset[getbits(msg, 33, 38)]; + callsign[5] = ais_charset[getbits(msg, 39, 44)]; + callsign[6] = ais_charset[getbits(msg, 45, 50)]; + callsign[7] = ais_charset[getbits(msg, 51, 56)]; + callsign[8] = 0; + + // score based on number of valid non-space characters + int score = 8; + for (unsigned i = 0; i < 8; ++i) { + if ((callsign[i] >= 'A' && callsign[i] <= 'Z') || (callsign[i] >= '0' && callsign[i] <= '9')) { + score += 6; + } else if (callsign[i] == ' ') { + // Valid, but not informative + } else { + // Invalid + return 0; + } + } + + if (store) { + mm->commb_format = COMMB_AIRCRAFT_IDENT; + memcpy(mm->callsign, callsign, sizeof(mm->callsign)); + mm->callsign_valid = 1; + } + + return score; +} + +// BDS3,0 ACAS RA +static int decodeBDS30(struct modesMessage *mm, bool store) +{ + unsigned char *msg = mm->MB; + + // BDS identifier + if (msg[0] != 0x30) { + return 0; + } + + if (store) { + mm->commb_format = COMMB_ACAS_RA; + } + + // just accept it. + return 56; +} + +// BDS4,0 Selected vertical intention +static int decodeBDS40(struct modesMessage *mm, bool store) +{ + unsigned char *msg = mm->MB; + + unsigned mcp_valid = getbit(msg, 1); + unsigned mcp_raw = getbits(msg, 2, 13); + unsigned fms_valid = getbit(msg, 14); + unsigned fms_raw = getbits(msg, 15, 26); + unsigned baro_valid = getbit(msg, 27); + unsigned baro_raw = getbits(msg, 28, 39); + unsigned reserved_1 = getbits(msg, 40, 47); + unsigned mode_valid = getbit(msg, 48); + unsigned mode_raw = getbits(msg, 49, 51); + unsigned reserved_2 = getbits(msg, 52, 53); + unsigned source_valid = getbit(msg, 54); + unsigned source_raw = getbits(msg, 55, 56); + + if (!mcp_valid && !fms_valid && !baro_valid && !mode_valid && !source_valid) { + return 0; + } + + int score = 0; + + unsigned mcp_alt = 0; + if (mcp_valid && mcp_raw != 0) { + mcp_alt = mcp_raw * 16; + if (mcp_alt >= 1000 && mcp_alt <= 50000) { + score += 13; + } else { + // unlikely altitude + score -= 2; + } + } else if (!mcp_valid && mcp_raw == 0) { + score += 1; + } else { + score -= 130; + } + + unsigned fms_alt = 0; + if (fms_valid && fms_raw != 0) { + fms_alt = fms_raw * 16; + if (fms_alt >= 1000 && fms_alt <= 50000) { + score += 13; + } else { + // unlikely altitude + score -= 2; + } + } else if (!fms_valid && fms_raw == 0) { + score += 1; + } else { + score -= 130; + } + + float baro_setting = 0; + if (baro_valid && baro_raw != 0) { + baro_setting = 800 + baro_raw * 0.1; + if (baro_setting >= 900 && baro_setting <= 1100) { + score += 13; + } else { + // unlikely pressure setting + score -= 2; + } + } else if (!baro_valid && baro_raw == 0) { + score += 1; + } else { + score -= 130; + } + + if (reserved_1 != 0) { + score -= 80; + } + + if (mode_valid) { + score += 4; + } else if (!mode_valid && mode_raw == 0) { + score += 1; + } else { + score -= 40; + } + + if (reserved_2 != 0) { + score -= 20; + } + + if (source_valid) { + score += 3; + } else if (!source_valid && source_raw == 0) { + score += 1; + } else { + score -= 30; + } + + // small bonuses for consistent data + if (mcp_valid && fms_valid && mcp_alt == fms_alt) { + score += 2; + } + + if (baro_valid && baro_raw == 2132) { + // 1013.2mb, standard pressure + score += 2; + } + + if (mcp_valid) { + unsigned remainder = mcp_alt % 500; + if (remainder < 16 || remainder > 484) { + // mcp altitude is a multiple of 500 + score += 2; + } + } + + if (fms_valid) { + unsigned remainder = fms_alt % 500; + if (remainder < 16 || remainder > 484) { + // fms altitude is a multiple of 500 + score += 2; + } + } + + if (store) { + mm->commb_format = COMMB_VERTICAL_INTENT; + + if (mcp_valid) { + mm->nav.mcp_altitude_valid = 1; + mm->nav.mcp_altitude = mcp_alt; + } + + if (fms_valid) { + mm->nav.fms_altitude_valid = 1; + mm->nav.fms_altitude = fms_alt; + } + + if (baro_valid) { + mm->nav.qnh_valid = 1; + mm->nav.qnh = baro_setting; + } + + if (mode_valid) { + mm->nav.modes_valid = 1; + mm->nav.modes = + ((mode_raw & 4) ? NAV_MODE_VNAV : 0) | + ((mode_raw & 2) ? NAV_MODE_ALT_HOLD : 0) | + ((mode_raw & 1) ? NAV_MODE_APPROACH : 0); + } + + if (source_valid) { + switch (source_raw) { + case 0: + mm->nav.altitude_source = NAV_ALT_UNKNOWN; + break; + case 1: + mm->nav.altitude_source = NAV_ALT_AIRCRAFT; + break; + case 2: + mm->nav.altitude_source = NAV_ALT_MCP; + break; + case 3: + mm->nav.altitude_source = NAV_ALT_FMS; + break; + default: + mm->nav.altitude_source = NAV_ALT_INVALID; + break; + } + } else { + mm->nav.altitude_source = NAV_ALT_INVALID; + } + } + + return score; +} + +// BDS5,0 Track and turn report +static int decodeBDS50(struct modesMessage *mm, bool store) +{ + unsigned char *msg = mm->MB; + + unsigned roll_valid = getbit(msg, 1); + unsigned roll_sign = getbit(msg, 2); + unsigned roll_raw = getbits(msg, 3, 11); + + unsigned track_valid = getbit(msg, 12); + unsigned track_sign = getbit(msg, 13); + unsigned track_raw = getbits(msg, 14, 23); + + unsigned gs_valid = getbit(msg, 24); + unsigned gs_raw = getbits(msg, 25, 34); + + unsigned track_rate_valid = getbit(msg, 35); + unsigned track_rate_sign = getbit(msg, 36); + unsigned track_rate_raw = getbits(msg, 37, 45); + + unsigned tas_valid = getbit(msg, 46); + unsigned tas_raw = getbits(msg, 47, 56); + + if (!roll_valid && !track_valid && !gs_valid && !track_rate_valid && !tas_valid) { + return 0; + } + + int score = 0; + + float roll = 0; + if (roll_valid) { + roll = roll_raw * 45.0 / 256.0; + if (roll_sign) { + roll -= 90.0; + } + + if (roll >= -40 && roll < 40) { + score += 11; + } else { + score -= 5; + } + } else if (!roll_valid && roll_raw == 0 && !roll_sign) { + score += 1; + } else { + score -= 110; + } + + float track = 0; + if (track_valid) { + score += 12; + track = track_raw * 90.0 / 512.0; + if (track_sign) { + track += 180.0; + } + } else if (!track_valid && track_raw == 0 && !track_sign) { + score += 1; + } else { + score -= 120; + } + + unsigned gs = 0; + if (gs_valid && gs_raw != 0) { + gs = gs_raw * 2; + + if (gs >= 50 && gs <= 700) { + score += 11; + } else { + score -= 5; + } + } else if (!gs_valid && gs_raw == 0) { + score += 1; + } else { + score -= 110; + } + + float track_rate = 0; + if (track_rate_valid) { + track_rate = track_rate_raw * 8.0 / 256.0; + if (track_rate_sign) { + track_rate -= 16; + } + + if (track_rate >= -10.0 && track_rate <= 10.0) { + score += 11; + } else { + score -= 5; + } + } else if (!track_rate_valid && track_rate_raw == 0 && !track_rate_sign) { + score += 1; + } else { + score -= 110; + } + + unsigned tas = 0; + if (tas_valid && tas_raw != 0) { + tas = tas_raw * 2; + + if (tas >= 50 && tas <= 700) { + score += 11; + } else { + score -= 5; + } + } else if (!tas_valid && tas_raw == 0) { + score += 1; + } else { + score -= 110; + } + + // small bonuses for consistent data + if (gs_valid && tas_valid) { + int delta = abs((int)gs_valid - (int)tas_valid); + if (delta < 50) { + score += 5; + } else if (delta > 150) { + score -= 5; + } + } + + // compute the theoretical turn rate and compare to track angle rate + if (roll_valid && tas_valid && tas > 0 && track_rate_valid) { + double turn_rate = 68625 * tan(roll * M_PI / 180.0) / (tas * 20 * M_PI); + double delta = fabs(turn_rate - track_rate); + if (delta < 0.5) { + score += 5; + } else if (delta > 2.0) { + score -= 5; + } + } + + if (store) { + mm->commb_format = COMMB_TRACK_TURN; + + if (roll_valid) { + mm->roll_valid = 1; + mm->roll = roll; + } + + if (track_valid) { + mm->heading_valid = 1; + mm->heading = track; + mm->heading_type = HEADING_GROUND_TRACK; + } + + if (gs_valid) { + mm->gs_valid = 1; + mm->gs.v0 = mm->gs.v2 = gs; + } + + if (track_rate_valid) { + mm->track_rate_valid = 1; + mm->track_rate = track_rate; + } + + if (tas_valid) { + mm->tas_valid = 1; + mm->tas = tas; + } + } + + return score; +} + +// BDS6,0 Heading and speed report +static int decodeBDS60(struct modesMessage *mm, bool store) +{ + unsigned char *msg = mm->MB; + + unsigned heading_valid = getbit(msg, 1); + unsigned heading_sign = getbit(msg, 2); + unsigned heading_raw = getbits(msg, 3, 12); + + unsigned ias_valid = getbit(msg, 13); + unsigned ias_raw = getbits(msg, 14, 23); + + unsigned mach_valid = getbit(msg, 24); + unsigned mach_raw = getbits(msg, 25, 34); + + unsigned baro_rate_valid = getbit(msg, 35); + unsigned baro_rate_sign = getbit(msg, 36); + unsigned baro_rate_raw = getbits(msg, 37, 45); + + unsigned inertial_rate_valid = getbit(msg, 46); + unsigned inertial_rate_sign = getbit(msg, 47); + unsigned inertial_rate_raw = getbits(msg, 48, 56); + + if (!heading_valid && !ias_valid && !mach_valid && !baro_rate_valid && !inertial_rate_valid) { + return 0; + } + + int score = 0; + + float heading = 0; + if (heading_valid) { + heading = heading_raw * 90.0 / 512.0; + if (heading_sign) { + heading += 180.0; + } + score += 12; + } else if (!heading_valid && heading_raw == 0 && !heading_sign) { + score += 1; + } else { + score -= 120; + } + + unsigned ias = 0; + if (ias_valid && ias_raw != 0) { + ias = ias_raw; + if (ias >= 50 && ias <= 700) { + score += 11; + } else { + score -= 5; + } + } else if (!ias_valid && ias_raw == 0) { + score += 1; + } else { + score -= 110; + } + + float mach = 0; + if (mach_valid && mach_raw != 0) { + mach = mach_raw * 2.048 / 512; + if (mach >= 0.1 && mach <= 0.9) { + score += 11; + } else { + score -= 5; + } + } else if (!mach_valid && mach_raw == 0) { + score += 1; + } else { + score -= 110; + } + + int baro_rate = 0; + if (baro_rate_valid) { + baro_rate = baro_rate_raw * 32; + if (baro_rate_sign) { + baro_rate -= 16384; + } + + if (baro_rate >= -6000 && baro_rate <= 6000) { + score += 11; + } else { + score -= 5; + } + } else if (!baro_rate_valid && baro_rate_raw == 0) { + score += 1; + } else { + score -= 110; + } + + int inertial_rate = 0; + if (inertial_rate_valid) { + inertial_rate = inertial_rate_raw * 32; + if (inertial_rate_sign) { + inertial_rate -= 16384; + } + + if (inertial_rate >= -6000 && inertial_rate <= 6000) { + score += 11; + } else { + score -= 5; + } + } else if (!inertial_rate_valid && inertial_rate_raw == 0) { + score += 1; + } else { + score -= 110; + } + + // small bonuses for consistent data + + // Should check IAS vs Mach at given altitude, but the maths is a little involved + + if (baro_rate_valid && inertial_rate_valid) { + int delta = abs(baro_rate - inertial_rate); + if (delta < 500) { + score += 5; + } else if (delta > 2000) { + score -= 5; + } + } + + if (store) { + mm->commb_format = COMMB_HEADING_SPEED; + + if (heading_valid) { + mm->heading_valid = 1; + mm->heading = heading; + mm->heading_type = HEADING_MAGNETIC; + } + + if (ias_valid) { + mm->ias_valid = 1; + mm->ias = ias; + } + + if (mach_valid) { + mm->mach_valid = 1; + mm->mach = mach; + } + + if (baro_rate_valid) { + mm->baro_rate_valid = 1; + mm->baro_rate = baro_rate; + } + + if (inertial_rate_valid) { + // INS-derived data is treated as a "geometric rate" / "geometric altitude" + // elsewhere, so do the same here. + mm->geom_rate_valid = 1; + mm->geom_rate = inertial_rate; + } + } + + return score; +} diff --git a/comm_b.h b/comm_b.h new file mode 100644 index 0000000..e7cc888 --- /dev/null +++ b/comm_b.h @@ -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 +// +// 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 . + +#ifndef COMM_B_H +#define COMM_B_H + +void decodeCommB(struct modesMessage *mm); + +#endif diff --git a/convert.c b/convert.c index fc2a7ae..3f34a38 100644 --- a/convert.c +++ b/convert.c @@ -4,17 +4,17 @@ // // Copyright (c) 2015 Oliver Jowett // -// This file is free software: you may copy, redistribute and/or modify it +// 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. +// 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 +// 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 +// You should have received a copy of the GNU General Public License // along with this program. If not, see . #include "dump1090.h" diff --git a/convert.h b/convert.h index 6412fc0..6b861b1 100644 --- a/convert.h +++ b/convert.h @@ -4,17 +4,17 @@ // // Copyright (c) 2015 Oliver Jowett // -// This file is free software: you may copy, redistribute and/or modify it +// 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. +// 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 +// 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 +// You should have received a copy of the GNU General Public License // along with this program. If not, see . #ifndef DUMP1090_CONVERT_H diff --git a/convert_benchmark.c b/convert_benchmark.c index 24b1bc1..96b691f 100644 --- a/convert_benchmark.c +++ b/convert_benchmark.c @@ -5,17 +5,17 @@ // Copyright (c) 2016-2017 Oliver Jowett // Copyright (c) 2017 FlightAware LLC // -// This file is free software: you may copy, redistribute and/or modify it +// 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. +// 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 +// 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 +// You should have received a copy of the GNU General Public License // along with this program. If not, see . #include "dump1090.h" diff --git a/cpr.c b/cpr.c index 3f8187b..f2e35c5 100644 --- a/cpr.c +++ b/cpr.c @@ -4,20 +4,20 @@ // // Copyright (c) 2014,2015 Oliver Jowett // -// This file is free software: you may copy, redistribute and/or modify it +// 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. +// 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 +// 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 +// You should have received a copy of the GNU General Public License // along with this program. If not, see . -// This file incorporates work covered by the following copyright and +// This file incorporates work covered by the following copyright and // permission notice: // // Copyright (C) 2012 by Salvatore Sanfilippo @@ -345,9 +345,9 @@ int decodeCPRrelative(double reflat, double reflon, return (-1); // Time to give up - Latitude error } - // Check to see that answer is reasonable - ie no more than 1/2 cell away + // Check to see that answer is reasonable - ie no more than 1/2 cell away if (fabs(rlat - reflat) > (AirDlat/2)) { - return (-1); // Time to give up - Latitude error + return (-1); // Time to give up - Latitude error } // Compute the Longitude Index "m" diff --git a/cpr.h b/cpr.h index 1cb413b..65f4fa4 100644 --- a/cpr.h +++ b/cpr.h @@ -4,17 +4,17 @@ // // Copyright (c) 2014,2015 Oliver Jowett // -// This file is free software: you may copy, redistribute and/or modify it +// 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. +// 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 +// 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 +// You should have received a copy of the GNU General Public License // along with this program. If not, see . #ifndef DUMP1090_CPR_H diff --git a/cprtests.c b/cprtests.c index abcdac8..875c959 100644 --- a/cprtests.c +++ b/cprtests.c @@ -4,17 +4,17 @@ // // Copyright (c) 2014,2015 Oliver Jowett // -// This file is free software: you may copy, redistribute and/or modify it +// 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. +// 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 +// 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 +// You should have received a copy of the GNU General Public License // along with this program. If not, see . diff --git a/crc.c b/crc.c index ad7d020..0f366ee 100644 --- a/crc.c +++ b/crc.c @@ -4,17 +4,17 @@ // // Copyright (c) 2014,2015 Oliver Jowett // -// This file is free software: you may copy, redistribute and/or modify it +// 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. +// 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 +// 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 +// You should have received a copy of the GNU General Public License // along with this program. If not, see . #include "dump1090.h" @@ -40,7 +40,7 @@ static void initLookupTables() { int i; uint8_t msg[112/8]; - + for (i = 0; i < 256; ++i) { uint32_t c = i << 16; int j; @@ -141,7 +141,7 @@ static int prepareSubtable(struct errorinfo *table, int n, int maxsize, int offs table[n].syndrome ^= single_bit_syndrome[i + offset]; table[n].errors = error_bit+1; table[n].bit[error_bit] = i; - + ++n; n = prepareSubtable(table, n, maxsize, offset, i + 1, endbit, &table[n-1], error_bit + 1, max_errors); } @@ -200,7 +200,7 @@ static struct errorinfo *prepareErrorTable(int bits, int max_correct, int max_de maxsize += combinations(bits - 5, i); // space needed for all i-bit errors } -#ifdef CRCDEBUG +#ifdef CRCDEBUG fprintf(stderr, "Preparing syndrome table to correct up to %d-bit errors (detecting %d-bit errors) in a %d-bit message (max %d entries)\n", max_correct, max_detect, bits, maxsize); #endif @@ -212,7 +212,7 @@ static struct errorinfo *prepareErrorTable(int bits, int max_correct, int max_de // ignore the first 5 bits (DF type) usedsize = prepareSubtable(table, 0, maxsize, 112 - bits, 5, bits, &base_entry, 0, max_correct); - + #ifdef CRCDEBUG fprintf(stderr, "%d syndromes (expected %d).\n", usedsize, maxsize); fprintf(stderr, "Sorting syndromes..\n"); @@ -220,15 +220,15 @@ static struct errorinfo *prepareErrorTable(int bits, int max_correct, int max_de qsort(table, usedsize, sizeof(struct errorinfo), syndrome_compare); -#ifdef CRCDEBUG +#ifdef CRCDEBUG { // Show the table stats fprintf(stderr, "Undetectable errors:\n"); for (i = 1; i <= max_correct; ++i) { int j, count; - + count = 0; - for (j = 0; j < usedsize; ++j) + for (j = 0; j < usedsize; ++j) if (table[j].errors == i && table[j].syndrome == 0) ++count; @@ -240,7 +240,7 @@ static struct errorinfo *prepareErrorTable(int bits, int max_correct, int max_de // Handle ambiguous cases, where there is more than one possible error pattern // that produces a given syndrome (this happens with >2 bit errors). -#ifdef CRCDEBUG +#ifdef CRCDEBUG fprintf(stderr, "Finding collisions..\n"); #endif for (i = 0, j = 0; i < usedsize; ++i) { @@ -264,7 +264,7 @@ static struct errorinfo *prepareErrorTable(int bits, int max_correct, int max_de #endif usedsize = j; } - + // Flag collisions we want to detect but not correct if (max_detect > max_correct) { int flagged; @@ -302,9 +302,9 @@ static struct errorinfo *prepareErrorTable(int bits, int max_correct, int max_de table = realloc(table, usedsize * sizeof(struct errorinfo)); #endif } - + *size_out = usedsize; - + #ifdef CRCDEBUG { // Check the table. @@ -335,9 +335,9 @@ static struct errorinfo *prepareErrorTable(int bits, int max_correct, int max_de fprintf(stderr, "Syndrome table summary:\n"); for (i = 1; i <= max_correct; ++i) { int j, count, possible; - + count = 0; - for (j = 0; j < usedsize; ++j) + for (j = 0; j < usedsize; ++j) if (table[j].errors == i) ++count; diff --git a/crc.h b/crc.h index 96abe35..c6d2902 100644 --- a/crc.h +++ b/crc.h @@ -4,17 +4,17 @@ // // Copyright (c) 2014,2015 Oliver Jowett // -// This file is free software: you may copy, redistribute and/or modify it +// 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. +// 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 +// 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 +// You should have received a copy of the GNU General Public License // along with this program. If not, see . #ifndef DUMP1090_CRC_H diff --git a/debian/changelog b/debian/changelog index bdd83ae..206c386 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,9 @@ +dump1090-fa (3.6.0~dev) UNRELEASED; urgency=medium + + * In development + + -- Oliver Jowett Mon, 19 Jun 2017 11:11:59 -0500 + dump1090-fa (3.5.3) stable; urgency=medium * Skip 3.5.2 to align with piaware versioning diff --git a/demod_2400.c b/demod_2400.c index 9d350b0..6c0fcbd 100644 --- a/demod_2400.c +++ b/demod_2400.c @@ -4,17 +4,17 @@ // // Copyright (c) 2014,2015 Oliver Jowett // -// This file is free software: you may copy, redistribute and/or modify it +// 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. +// 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 +// 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 +// You should have received a copy of the GNU General Public License // along with this program. If not, see . #include "dump1090.h" @@ -98,7 +98,7 @@ void demodulate2400(struct mag_buf *mag) // phase 6: 0/4\2 2/4\0 0 0 0 2/4\0/5\1 0 0 0 0 0 0 X2 // phase 7: 0/3 3\1/5\0 0 0 0 1/5\0/4\2 0 0 0 0 0 0 X3 // - + // quick check: we must have a rising edge 0->1 and a falling edge 12->13 if (! (preamble[0] < preamble[1] && preamble[12] > preamble[13]) ) continue; @@ -174,7 +174,7 @@ void demodulate2400(struct mag_buf *mag) // Decode all the next 112 bits, regardless of the actual message // size. We'll check the actual message type later - + pPtr = &m[j+19] + (try_phase/5); phase = try_phase % 5; @@ -184,7 +184,7 @@ void demodulate2400(struct mag_buf *mag) switch (phase) { case 0: - theByte = + theByte = (slice_phase0(pPtr) > 0 ? 0x80 : 0) | (slice_phase2(pPtr+2) > 0 ? 0x40 : 0) | (slice_phase4(pPtr+4) > 0 ? 0x20 : 0) | @@ -198,7 +198,7 @@ void demodulate2400(struct mag_buf *mag) phase = 1; pPtr += 19; break; - + case 1: theByte = (slice_phase1(pPtr) > 0 ? 0x80 : 0) | @@ -213,7 +213,7 @@ void demodulate2400(struct mag_buf *mag) phase = 2; pPtr += 19; break; - + case 2: theByte = (slice_phase2(pPtr) > 0 ? 0x80 : 0) | @@ -228,9 +228,9 @@ void demodulate2400(struct mag_buf *mag) phase = 3; pPtr += 19; break; - + case 3: - theByte = + theByte = (slice_phase3(pPtr) > 0 ? 0x80 : 0) | (slice_phase0(pPtr+3) > 0 ? 0x40 : 0) | (slice_phase2(pPtr+5) > 0 ? 0x20 : 0) | @@ -243,9 +243,9 @@ void demodulate2400(struct mag_buf *mag) phase = 4; pPtr += 19; break; - + case 4: - theByte = + theByte = (slice_phase4(pPtr) > 0 ? 0x80 : 0) | (slice_phase1(pPtr+3) > 0 ? 0x40 : 0) | (slice_phase3(pPtr+5) > 0 ? 0x20 : 0) | @@ -265,7 +265,7 @@ void demodulate2400(struct mag_buf *mag) switch (msg[0] >> 3) { case 0: case 4: case 5: case 11: bytelen = MODES_SHORT_MSG_BYTES; break; - + case 16: case 17: case 18: case 20: case 21: case 24: break; @@ -283,7 +283,7 @@ void demodulate2400(struct mag_buf *mag) bestmsg = msg; bestscore = score; bestphase = try_phase; - + // swap to using the other buffer so we don't clobber our demodulated data // (if we find a better result then we'll swap back, but that's OK because // we no longer need this copy if we found a better one) @@ -311,9 +311,7 @@ void demodulate2400(struct mag_buf *mag) mm.timestampMsg = mag->sampleTimestamp + j*5 + (8 + 56) * 12 + bestphase; // compute message receive time as block-start-time + difference in the 12MHz clock - mm.sysTimestampMsg = mag->sysTimestamp; // start of block time - mm.sysTimestampMsg.tv_nsec += receiveclock_ns_elapsed(mag->sampleTimestamp, mm.timestampMsg); - normalize_timespec(&mm.sysTimestampMsg); + mm.sysTimestampMsg = mag->sysTimestamp + receiveclock_ms_elapsed(mag->sampleTimestamp, mm.timestampMsg); mm.score = bestscore; @@ -362,7 +360,7 @@ void demodulate2400(struct mag_buf *mag) // few bits of the first message, but the message bits didn't // overlap) j += msglen*12/5; - + // Pass data to the next layer useModesMessage(&mm); } @@ -646,9 +644,7 @@ void demodulate2400AC(struct mag_buf *mag) mm.timestampMsg = mag->sampleTimestamp + f2_clock / 5; // 60MHz -> 12MHz // compute message receive time as block-start-time + difference in the 12MHz clock - mm.sysTimestampMsg = mag->sysTimestamp; // start of block time - mm.sysTimestampMsg.tv_nsec += receiveclock_ns_elapsed(mag->sampleTimestamp, mm.timestampMsg); - normalize_timespec(&mm.sysTimestampMsg); + mm.sysTimestampMsg = mag->sysTimestamp + receiveclock_ms_elapsed(mag->sampleTimestamp, mm.timestampMsg); decodeModeAMessage(&mm, modeac); diff --git a/demod_2400.h b/demod_2400.h index 907294f..c514ba4 100644 --- a/demod_2400.h +++ b/demod_2400.h @@ -4,17 +4,17 @@ // // Copyright (c) 2014,2015 Oliver Jowett // -// This file is free software: you may copy, redistribute and/or modify it +// 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. +// 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 +// 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 +// You should have received a copy of the GNU General Public License // along with this program. If not, see . #ifndef DUMP1090_DEMOD_2400_H diff --git a/dump1090.c b/dump1090.c index 234fcc4..4e55367 100644 --- a/dump1090.c +++ b/dump1090.c @@ -4,20 +4,20 @@ // // Copyright (c) 2014-2016 Oliver Jowett // -// This file is free software: you may copy, redistribute and/or modify it +// 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. +// 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 +// 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 +// You should have received a copy of the GNU General Public License // along with this program. If not, see . -// This file incorporates work covered by the following copyright and +// This file incorporates work covered by the following copyright and // permission notice: // // Copyright (C) 2012 by Salvatore Sanfilippo @@ -157,24 +157,24 @@ void modesInit(void) { // Validate the users Lat/Lon home location inputs if ( (Modes.fUserLat > 90.0) // Latitude must be -90 to +90 - || (Modes.fUserLat < -90.0) // and + || (Modes.fUserLat < -90.0) // and || (Modes.fUserLon > 360.0) // Longitude must be -180 to +360 || (Modes.fUserLon < -180.0) ) { Modes.fUserLat = Modes.fUserLon = 0.0; } else if (Modes.fUserLon > 180.0) { // If Longitude is +180 to +360, make it -180 to 0 Modes.fUserLon -= 360.0; } - // If both Lat and Lon are 0.0 then the users location is either invalid/not-set, or (s)he's in the - // Atlantic ocean off the west coast of Africa. This is unlikely to be correct. - // Set the user LatLon valid flag only if either Lat or Lon are non zero. Note the Greenwich meridian - // is at 0.0 Lon,so we must check for either fLat or fLon being non zero not both. + // If both Lat and Lon are 0.0 then the users location is either invalid/not-set, or (s)he's in the + // Atlantic ocean off the west coast of Africa. This is unlikely to be correct. + // Set the user LatLon valid flag only if either Lat or Lon are non zero. Note the Greenwich meridian + // is at 0.0 Lon,so we must check for either fLat or fLon being non zero not both. // Testing the flag at runtime will be much quicker than ((fLon != 0.0) || (fLat != 0.0)) Modes.bUserFlags &= ~MODES_USER_LATLON_VALID; if ((Modes.fUserLat != 0.0) || (Modes.fUserLon != 0.0)) { Modes.bUserFlags |= MODES_USER_LATLON_VALID; } - // Limit the maximum requested raw output size to less than one Ethernet Block + // Limit the maximum requested raw output size to less than one Ethernet Block if (Modes.net_output_flush_size > (MODES_OUT_FLUSH_SIZE)) {Modes.net_output_flush_size = MODES_OUT_FLUSH_SIZE;} if (Modes.net_output_flush_interval > (MODES_OUT_FLUSH_INTERVAL)) @@ -369,8 +369,8 @@ void backgroundTasks(void) { trackPeriodicUpdate(); if (Modes.net) { - modesNetPeriodicWork(); - } + modesNetPeriodicWork(); + } // Refresh screen when in interactive mode @@ -379,7 +379,7 @@ void backgroundTasks(void) { } // always update end time so it is current when requests arrive - Modes.stats_current.end = now; + Modes.stats_current.end = mstime(); if (now >= next_stats_update) { int i; @@ -389,21 +389,21 @@ void backgroundTasks(void) { } else { Modes.stats_latest_1min = (Modes.stats_latest_1min + 1) % 15; Modes.stats_1min[Modes.stats_latest_1min] = Modes.stats_current; - + add_stats(&Modes.stats_current, &Modes.stats_alltime, &Modes.stats_alltime); add_stats(&Modes.stats_current, &Modes.stats_periodic, &Modes.stats_periodic); - + reset_stats(&Modes.stats_5min); for (i = 0; i < 5; ++i) add_stats(&Modes.stats_1min[(Modes.stats_latest_1min - i + 15) % 15], &Modes.stats_5min, &Modes.stats_5min); - + reset_stats(&Modes.stats_15min); for (i = 0; i < 15; ++i) add_stats(&Modes.stats_1min[i], &Modes.stats_15min, &Modes.stats_15min); - + reset_stats(&Modes.stats_current); Modes.stats_current.start = Modes.stats_current.end = now; - + if (Modes.json_dir) writeJsonToFile("stats.json", generateStatsJson); diff --git a/dump1090.h b/dump1090.h index 005fbff..4e0340c 100644 --- a/dump1090.h +++ b/dump1090.h @@ -4,20 +4,20 @@ // // Copyright (c) 2014-2016 Oliver Jowett // -// This file is free software: you may copy, redistribute and/or modify it +// 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. +// 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 +// 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 +// You should have received a copy of the GNU General Public License // along with this program. If not, see . -// This file incorporates work covered by the following copyright and +// This file incorporates work covered by the following copyright and // permission notice: // // Copyright (C) 2012 by Salvatore Sanfilippo @@ -167,7 +167,7 @@ typedef enum { typedef enum { ALTITUDE_BARO, - ALTITUDE_GNSS + ALTITUDE_GEOM } altitude_source_t; typedef enum { @@ -178,24 +178,55 @@ typedef enum { } airground_t; typedef enum { - SPEED_GROUNDSPEED, - SPEED_IAS, - SPEED_TAS -} speed_source_t; - -typedef enum { - HEADING_TRUE, - HEADING_MAGNETIC -} heading_source_t; - -typedef enum { - SIL_PER_SAMPLE, SIL_PER_HOUR + SIL_INVALID, SIL_UNKNOWN, SIL_PER_SAMPLE, SIL_PER_HOUR } sil_type_t; typedef enum { CPR_SURFACE, CPR_AIRBORNE, CPR_COARSE } cpr_type_t; +typedef enum { + HEADING_INVALID, // Not set + HEADING_GROUND_TRACK, // Direction of track over ground, degrees clockwise from true north + HEADING_TRUE, // Heading, degrees clockwise from true north + HEADING_MAGNETIC, // Heading, degrees clockwise from magnetic north + HEADING_MAGNETIC_OR_TRUE, // HEADING_MAGNETIC or HEADING_TRUE depending on the HRD bit in opstatus + HEADING_TRACK_OR_HEADING // GROUND_TRACK / MAGNETIC / TRUE depending on the TAH bit in opstatus +} heading_type_t; + +typedef enum { + COMMB_UNKNOWN, + COMMB_EMPTY_RESPONSE, + COMMB_DATALINK_CAPS, + COMMB_GICB_CAPS, + COMMB_AIRCRAFT_IDENT, + COMMB_ACAS_RA, + COMMB_VERTICAL_INTENT, + COMMB_TRACK_TURN, + COMMB_HEADING_SPEED +} commb_format_t; + +typedef enum { + NAV_MODE_AUTOPILOT = 1, + NAV_MODE_VNAV = 2, + NAV_MODE_ALT_HOLD = 4, + NAV_MODE_APPROACH = 8, + NAV_MODE_LNAV = 16, + NAV_MODE_TCAS = 32 +} nav_modes_t; + +// Matches encoding of the ES type 28/1 emergency/priority status subfield +typedef enum { + EMERGENCY_NONE = 0, + EMERGENCY_GENERAL = 1, + EMERGENCY_LIFEGUARD = 2, + EMERGENCY_MINFUEL = 3, + EMERGENCY_NORDO = 4, + EMERGENCY_UNLAWFUL = 5, + EMERGENCY_DOWNED = 6, + EMERGENCY_RESERVED = 7 +} emergency_t; + #define MODES_NON_ICAO_ADDRESS (1<<24) // Set on addresses to indicate they are not ICAO addresses #define MODES_DEBUG_DEMOD (1<<0) @@ -251,7 +282,7 @@ struct mag_buf { uint16_t *data; // Magnitude data. Starts with Modes.trailing_samples worth of overlap from the previous block unsigned length; // Number of valid samples _after_ overlap. Total buffer length is buf->length + Modes.trailing_samples. uint64_t sampleTimestamp; // Clock timestamp of the start of this block, 12MHz clock - struct timespec sysTimestamp; // Estimated system time at start of block + uint64_t sysTimestamp; // Estimated system time at start of block uint32_t dropped; // Number of dropped samples preceding this buffer double mean_level; // Mean of normalized (0..1) signal level double mean_power; // Mean of normalized (0..1) power level @@ -363,14 +394,14 @@ struct modesMessage { // Generic fields unsigned char msg[MODES_LONG_MSG_BYTES]; // Binary message. unsigned char verbatim[MODES_LONG_MSG_BYTES]; // Binary message, as originally received before correction - int msgbits; // Number of bits in message + int msgbits; // Number of bits in message int msgtype; // Downlink format # uint32_t crc; // Message CRC - int correctedbits; // No. of bits corrected + int correctedbits; // No. of bits corrected uint32_t addr; // Address Announced addrtype_t addrtype; // address format / source uint64_t timestampMsg; // Timestamp of the message (12MHz clock) - struct timespec sysTimestampMsg; // Timestamp of the message (system time) + uint64_t sysTimestampMsg; // Timestamp of the message (system time) int remote; // If set this message is from a remote station double signalLevel; // RSSI, in the range [0..1], as a fraction of full-scale power int score; // Scoring from scoreModesMessage, if used @@ -400,62 +431,111 @@ struct modesMessage { unsigned char MV[7]; // Decoded data - unsigned altitude_valid : 1; + unsigned altitude_baro_valid : 1; + unsigned altitude_geom_valid : 1; + unsigned track_valid : 1; + unsigned track_rate_valid : 1; unsigned heading_valid : 1; - unsigned speed_valid : 1; - unsigned vert_rate_valid : 1; + unsigned roll_valid : 1; + unsigned gs_valid : 1; + unsigned ias_valid : 1; + unsigned tas_valid : 1; + unsigned mach_valid : 1; + unsigned baro_rate_valid : 1; + unsigned geom_rate_valid : 1; unsigned squawk_valid : 1; unsigned callsign_valid : 1; - unsigned ew_velocity_valid : 1; - unsigned ns_velocity_valid : 1; unsigned cpr_valid : 1; unsigned cpr_odd : 1; unsigned cpr_decoded : 1; unsigned cpr_relative : 1; unsigned category_valid : 1; - unsigned gnss_delta_valid : 1; + unsigned geom_delta_valid : 1; unsigned from_mlat : 1; unsigned from_tisb : 1; unsigned spi_valid : 1; unsigned spi : 1; unsigned alert_valid : 1; unsigned alert : 1; + unsigned emergency_valid : 1; unsigned metype; // DF17/18 ME type unsigned mesub; // DF17/18 ME subtype - // valid if altitude_valid: - int altitude; // Altitude in either feet or meters - altitude_unit_t altitude_unit; // the unit used for altitude - altitude_source_t altitude_source; // whether the altitude is a barometric altude or a GNSS height - // valid if gnss_delta_valid: - int gnss_delta; // difference between GNSS and baro alt - // valid if heading_valid: - unsigned heading; // Reported by aircraft, or computed from from EW and NS velocity - heading_source_t heading_source; // what "heading" is measuring (true or magnetic heading) - // valid if speed_valid: - unsigned speed; // in kts, reported by aircraft, or computed from from EW and NS velocity - speed_source_t speed_source; // what "speed" is measuring (groundspeed / IAS / TAS) - // valid if vert_rate_valid: - int vert_rate; // vertical rate in feet/minute - altitude_source_t vert_rate_source; // the altitude source used for vert_rate - // valid if squawk_valid: - unsigned squawk; // 13 bits identity (Squawk), encoded as 4 hex digits - // valid if callsign_valid - char callsign[9]; // 8 chars flight number - // valid if category_valid - unsigned category; // A0 - D7 encoded as a single hex byte - // valid if cpr_valid - cpr_type_t cpr_type; // The encoding type used (surface, airborne, coarse TIS-B) - unsigned cpr_lat; // Non decoded latitude. - unsigned cpr_lon; // Non decoded longitude. - unsigned cpr_nucp; // NUCp/NIC value implied by message type + commb_format_t commb_format; // Inferred format of a comm-b message - airground_t airground; // air/ground state + // valid if altitude_baro_valid: + int altitude_baro; // Altitude in either feet or meters + altitude_unit_t altitude_baro_unit; // the unit used for altitude + + // valid if altitude_geom_valid: + int altitude_geom; // Altitude in either feet or meters + altitude_unit_t altitude_geom_unit; // the unit used for altitude + + // following fields are valid if the corresponding _valid field is set: + int geom_delta; // Difference between geometric and baro alt + float heading; // ground track or heading, degrees (0-359). Reported directly or computed from from EW and NS velocity + heading_type_t heading_type;// how to interpret 'track_or_heading' + float track_rate; // Rate of change of track, degrees/second + float roll; // Roll, degrees, negative is left roll + struct { + // Groundspeed, kts, reported directly or computed from from EW and NS velocity + // For surface movement, this has different interpretations for v0 and v2; both + // fields are populated. The tracking layer will update "gs.selected". + float v0; + float v2; + float selected; + } gs; + unsigned ias; // Indicated airspeed, kts + unsigned tas; // True airspeed, kts + double mach; // Mach number + int baro_rate; // Rate of change of barometric altitude, feet/minute + int geom_rate; // Rate of change of geometric (GNSS / INS) altitude, feet/minute + unsigned squawk; // 13 bits identity (Squawk), encoded as 4 hex digits + char callsign[9]; // 8 chars flight number, NUL-terminated + unsigned category; // A0 - D7 encoded as a single hex byte + emergency_t emergency; // emergency/priority status + + // valid if cpr_valid + cpr_type_t cpr_type; // The encoding type used (surface, airborne, coarse TIS-B) + unsigned cpr_lat; // Non decoded latitude. + unsigned cpr_lon; // Non decoded longitude. + unsigned cpr_nucp; // NUCp/NIC value implied by message type + + airground_t airground; // air/ground state // valid if cpr_decoded: double decoded_lat; double decoded_lon; + unsigned decoded_nic; + unsigned decoded_rc; + + // various integrity/accuracy things + struct { + unsigned nic_a_valid : 1; + unsigned nic_b_valid : 1; + unsigned nic_c_valid : 1; + unsigned nic_baro_valid : 1; + unsigned nac_p_valid : 1; + unsigned nac_v_valid : 1; + unsigned gva_valid : 1; + unsigned sda_valid : 1; + + unsigned nic_a : 1; // if nic_a_valid + unsigned nic_b : 1; // if nic_b_valid + unsigned nic_c : 1; // if nic_c_valid + unsigned nic_baro : 1; // if nic_baro_valid + + unsigned nac_p : 4; // if nac_p_valid + unsigned nac_v : 3; // if nac_v_valid + + unsigned sil : 2; // if sil_type != SIL_INVALID + sil_type_t sil_type; + + unsigned gva : 2; // if gva_valid + + unsigned sda : 2; // if sda_valid + } accuracy; // Operational Status struct { @@ -466,7 +546,6 @@ struct modesMessage { unsigned om_ident : 1; unsigned om_atc : 1; unsigned om_saf : 1; - unsigned om_sda : 2; unsigned cc_acas : 1; unsigned cc_cdti : 1; @@ -477,50 +556,41 @@ struct modesMessage { unsigned cc_uat_in : 1; unsigned cc_poa : 1; unsigned cc_b2_low : 1; - unsigned cc_nac_v : 3; - unsigned cc_nic_supp_c : 1; unsigned cc_lw_valid : 1; - unsigned nic_supp_a : 1; - unsigned nac_p : 4; - unsigned gva : 2; - unsigned sil : 2; - unsigned nic_baro : 1; - - sil_type_t sil_type; - enum { ANGLE_HEADING, ANGLE_TRACK } track_angle; - heading_source_t hrd; + heading_type_t tah; + heading_type_t hrd; unsigned cc_lw; unsigned cc_antenna_offset; } opstatus; - // Target State & Status (ADS-B V2 only) + // combined: + // Target State & Status (ADS-B V2 only) + // Comm-B BDS4,0 Vertical Intent struct { - unsigned valid : 1; - unsigned altitude_valid : 1; - unsigned baro_valid : 1; unsigned heading_valid : 1; - unsigned mode_valid : 1; - unsigned mode_autopilot : 1; - unsigned mode_vnav : 1; - unsigned mode_alt_hold : 1; - unsigned mode_approach : 1; - unsigned acas_operational : 1; - unsigned nac_p : 4; - unsigned nic_baro : 1; - unsigned sil : 2; + unsigned fms_altitude_valid : 1; + unsigned mcp_altitude_valid : 1; + unsigned qnh_valid : 1; + unsigned modes_valid : 1; - sil_type_t sil_type; - enum { TSS_ALTITUDE_MCP, TSS_ALTITUDE_FMS } altitude_type; - unsigned altitude; - float baro; - unsigned heading; - } tss; + float heading; // heading, degrees (0-359) (could be magnetic or true heading; magnetic recommended) + heading_type_t heading_type; + unsigned fms_altitude; // FMS selected altitude + unsigned mcp_altitude; // MCP/FCU selected altitude + float qnh; // altimeter setting (QFE or QNH/QNE), millibars + + enum { NAV_ALT_INVALID, NAV_ALT_UNKNOWN, NAV_ALT_AIRCRAFT, NAV_ALT_MCP, NAV_ALT_FMS } altitude_source; + + nav_modes_t modes; + } nav; }; // This one needs modesMessage: #include "track.h" +#include "mode_s.h" +#include "comm_b.h" // ======================== function declarations ========================= @@ -537,14 +607,6 @@ void modeACInit(); int modeAToModeC (unsigned int modeA); unsigned modeCToModeA (int modeC); -// -// Functions exported from mode_s.c -// -int modesMessageLenByType(int type); -int scoreModesMessage(unsigned char *msg, int validbits); -int decodeModesMessage (struct modesMessage *mm, unsigned char *msg); -void displayModesMessage(struct modesMessage *mm); -void useModesMessage (struct modesMessage *mm); // // Functions exported from interactive.c // diff --git a/faup1090.c b/faup1090.c index 31eb0d8..425bc92 100644 --- a/faup1090.c +++ b/faup1090.c @@ -4,20 +4,20 @@ // // Copyright (c) 2014,2015 Oliver Jowett // -// This file is free software: you may copy, redistribute and/or modify it +// 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. +// 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 +// 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 +// You should have received a copy of the GNU General Public License // along with this program. If not, see . -// This file incorporates work covered by the following copyright and +// This file incorporates work covered by the following copyright and // permission notice: // // Copyright (C) 2012 by Salvatore Sanfilippo @@ -83,17 +83,17 @@ static void faupInitConfig(void) { static void faupInit(void) { // Validate the users Lat/Lon home location inputs if ( (Modes.fUserLat > 90.0) // Latitude must be -90 to +90 - || (Modes.fUserLat < -90.0) // and + || (Modes.fUserLat < -90.0) // and || (Modes.fUserLon > 360.0) // Longitude must be -180 to +360 || (Modes.fUserLon < -180.0) ) { Modes.fUserLat = Modes.fUserLon = 0.0; } else if (Modes.fUserLon > 180.0) { // If Longitude is +180 to +360, make it -180 to 0 Modes.fUserLon -= 360.0; } - // If both Lat and Lon are 0.0 then the users location is either invalid/not-set, or (s)he's in the - // Atlantic ocean off the west coast of Africa. This is unlikely to be correct. - // Set the user LatLon valid flag only if either Lat or Lon are non zero. Note the Greenwich meridian - // is at 0.0 Lon,so we must check for either fLat or fLon being non zero not both. + // If both Lat and Lon are 0.0 then the users location is either invalid/not-set, or (s)he's in the + // Atlantic ocean off the west coast of Africa. This is unlikely to be correct. + // Set the user LatLon valid flag only if either Lat or Lon are non zero. Note the Greenwich meridian + // is at 0.0 Lon,so we must check for either fLat or fLon being non zero not both. // Testing the flag at runtime will be much quicker than ((fLon != 0.0) || (fLat != 0.0)) Modes.bUserFlags &= ~MODES_USER_LATLON_VALID; if ((Modes.fUserLat != 0.0) || (Modes.fUserLon != 0.0)) { @@ -204,6 +204,7 @@ int main(int argc, char **argv) { // Set up output connection on stdout fatsv_output = makeFatsvOutputService(); createGenericClient(fatsv_output, STDOUT_FILENO); + writeFATSVHeader(); // Run it until we've lost either connection while (!Modes.exit && beast_input->connections && fatsv_output->connections) { diff --git a/icao_filter.c b/icao_filter.c index 0284de1..38f7b5c 100644 --- a/icao_filter.c +++ b/icao_filter.c @@ -4,17 +4,17 @@ // // Copyright (c) 2014,2015 Oliver Jowett // -// This file is free software: you may copy, redistribute and/or modify it +// 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. +// 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 +// 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 +// You should have received a copy of the GNU General Public License // along with this program. If not, see . #include "dump1090.h" @@ -51,7 +51,7 @@ static uint32_t icaoHash(uint32_t a) hash += (a >> 16) & 0xff; hash += (hash << 10); hash ^= (hash >> 6); - + hash += (hash << 3); hash ^= (hash >> 11); hash += (hash << 15); diff --git a/icao_filter.h b/icao_filter.h index 2c30f8d..397f7f3 100644 --- a/icao_filter.h +++ b/icao_filter.h @@ -4,17 +4,17 @@ // // Copyright (c) 2014,2015 Oliver Jowett // -// This file is free software: you may copy, redistribute and/or modify it +// 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. +// 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 +// 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 +// You should have received a copy of the GNU General Public License // along with this program. If not, see . #ifndef DUMP1090_ICAO_FILTER_H diff --git a/interactive.c b/interactive.c index 85554b2..580a5d8 100644 --- a/interactive.c +++ b/interactive.c @@ -4,20 +4,20 @@ // // Copyright (c) 2014,2015 Oliver Jowett // -// This file is free software: you may copy, redistribute and/or modify it +// 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. +// 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 +// 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 +// You should have received a copy of the GNU General Public License // along with this program. If not, see . -// This file incorporates work covered by the following copyright and +// This file incorporates work covered by the following copyright and // permission notice: // // Copyright (C) 2012 by Salvatore Sanfilippo @@ -130,12 +130,12 @@ void interactiveShowData(void) { snprintf(strSquawk,5,"%04x", a->squawk); } - if (trackDataValid(&a->speed_valid)) { - snprintf (strGs, 5,"%3d", convert_speed(a->speed)); + if (trackDataValid(&a->gs_valid)) { + snprintf (strGs, 5,"%3d", convert_speed(a->gs)); } - if (trackDataValid(&a->heading_valid)) { - snprintf (strTt, 5,"%03d", a->heading); + if (trackDataValid(&a->track_valid)) { + snprintf (strTt, 5,"%03.0f", a->track); } if (msgs > 99999) { @@ -160,10 +160,10 @@ void interactiveShowData(void) { if (trackDataValid(&a->airground_valid) && a->airground == AG_GROUND) { snprintf(strFl, 7," grnd"); - } else if (Modes.use_gnss && trackDataValid(&a->altitude_gnss_valid)) { - snprintf(strFl, 7, "%5dH", convert_altitude(a->altitude_gnss)); - } else if (trackDataValid(&a->altitude_valid)) { - snprintf(strFl, 7, "%5d ", convert_altitude(a->altitude)); + } else if (Modes.use_gnss && trackDataValid(&a->altitude_geom_valid)) { + snprintf(strFl, 7, "%5dH", convert_altitude(a->altitude_geom)); + } else if (trackDataValid(&a->altitude_baro_valid)) { + snprintf(strFl, 7, "%5d ", convert_altitude(a->altitude_baro)); } mvprintw(row, 0, "%s%06X %-4s %-4s %-8s %6s %3s %3s %7s %8s %5.1f %5d %2.0f", diff --git a/mode_ac.c b/mode_ac.c index c482fd7..194ff67 100644 --- a/mode_ac.c +++ b/mode_ac.c @@ -92,10 +92,10 @@ static int internalModeAToModeC(unsigned int ModeA) if (ModeA & 0x0020) {OneHundreds ^= 0x003;} // C2 if (ModeA & 0x0040) {OneHundreds ^= 0x001;} // C4 - // Remove 7s from OneHundreds (Make 7->5, snd 5->7). + // Remove 7s from OneHundreds (Make 7->5, snd 5->7). if ((OneHundreds & 5) == 5) {OneHundreds ^= 2;} - // Check for invalid codes, only 1 to 5 are valid + // Check for invalid codes, only 1 to 5 are valid if (OneHundreds > 5) { return INVALID_ALTITUDE; } @@ -108,14 +108,14 @@ static int internalModeAToModeC(unsigned int ModeA) if (ModeA & 0x2000) {FiveHundreds ^= 0x01F;} // A2 if (ModeA & 0x4000) {FiveHundreds ^= 0x00F;} // A4 - if (ModeA & 0x0100) {FiveHundreds ^= 0x007;} // B1 + if (ModeA & 0x0100) {FiveHundreds ^= 0x007;} // B1 if (ModeA & 0x0200) {FiveHundreds ^= 0x003;} // B2 if (ModeA & 0x0400) {FiveHundreds ^= 0x001;} // B4 - - // Correct order of OneHundreds. - if (FiveHundreds & 1) {OneHundreds = 6 - OneHundreds;} - return ((FiveHundreds * 5) + OneHundreds - 13); + // Correct order of OneHundreds. + if (FiveHundreds & 1) {OneHundreds = 6 - OneHundreds;} + + return ((FiveHundreds * 5) + OneHundreds - 13); } // //========================================================================= @@ -146,10 +146,9 @@ void decodeModeAMessage(struct modesMessage *mm, int ModeA) if (!mm->spi) { int modeC = modeAToModeC(ModeA); if (modeC != INVALID_ALTITUDE) { - mm->altitude = modeC * 100; - mm->altitude_unit = UNIT_FEET; - mm->altitude_source = ALTITUDE_BARO; - mm->altitude_valid = 1; + mm->altitude_baro = modeC * 100; + mm->altitude_baro_unit = UNIT_FEET; + mm->altitude_baro_valid = 1; } } diff --git a/mode_s.c b/mode_s.c index be41840..4093cc3 100644 --- a/mode_s.c +++ b/mode_s.c @@ -4,20 +4,20 @@ // // Copyright (c) 2014-2016 Oliver Jowett // -// This file is free software: you may copy, redistribute and/or modify it +// 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. +// 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 +// 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 +// You should have received a copy of the GNU General Public License // along with this program. If not, see . -// This file incorporates work covered by the following copyright and +// This file incorporates work covered by the following copyright and // permission notice: // // Copyright (C) 2012 by Salvatore Sanfilippo @@ -53,8 +53,6 @@ /* for PRIX64 */ #include -#include - // // ===================== Mode S detection and decoding =================== // @@ -70,8 +68,8 @@ // // Given the Downlink Format (DF) of the message, return the message length in bits. // -// All known DF's 16 or greater are long. All known DF's 15 or less are short. -// There are lots of unused codes in both category, so we can assume ICAO will stick to +// All known DF's 16 or greater are long. All known DF's 15 or less are short. +// There are lots of unused codes in both category, so we can assume ICAO will stick to // these rules, meaning that the most significant bit of the DF indicates the length. // int modesMessageLenByType(int type) { @@ -88,7 +86,7 @@ int modesMessageLenByType(int type) { // // So every group of three bits A, B, C, D represent an integer from 0 to 7. // -// The actual meaning is just 4 octal numbers, but we convert it into a hex +// The actual meaning is just 4 octal numbers, but we convert it into a hex // number tha happens to represent the four octal numbers. // // For more info: http://en.wikipedia.org/wiki/Gillham_code @@ -102,8 +100,8 @@ static int decodeID13Field(int ID13Field) { if (ID13Field & 0x0200) {hexGillham |= 0x2000;} // Bit 9 = A2 if (ID13Field & 0x0100) {hexGillham |= 0x0040;} // Bit 8 = C4 if (ID13Field & 0x0080) {hexGillham |= 0x4000;} // Bit 7 = A4 - //if (ID13Field & 0x0040) {hexGillham |= 0x0800;} // Bit 6 = X or M - if (ID13Field & 0x0020) {hexGillham |= 0x0100;} // Bit 5 = B1 + //if (ID13Field & 0x0040) {hexGillham |= 0x0800;} // Bit 6 = X or M + if (ID13Field & 0x0020) {hexGillham |= 0x0100;} // Bit 5 = B1 if (ID13Field & 0x0010) {hexGillham |= 0x0001;} // Bit 4 = D1 or Q if (ID13Field & 0x0008) {hexGillham |= 0x0200;} // Bit 3 = B2 if (ID13Field & 0x0004) {hexGillham |= 0x0002;} // Bit 2 = D2 @@ -159,13 +157,13 @@ static int decodeAC12Field(int AC12Field, altitude_unit_t *unit) { *unit = UNIT_FEET; if (q_bit) { /// N is the 11 bit integer resulting from the removal of bit Q at bit 4 - int n = ((AC12Field & 0x0FE0) >> 1) | + int n = ((AC12Field & 0x0FE0) >> 1) | (AC12Field & 0x000F); // The final altitude is the resulting number multiplied by 25, minus 1000. return ((n * 25) - 1000); } else { // Make N a 13 bit Gillham coded altitude by inserting M=0 at bit 6 - int n = ((AC12Field & 0x0FC0) << 1) | + int n = ((AC12Field & 0x0FC0) << 1) | (AC12Field & 0x003F); n = modeAToModeC(decodeID13Field(n)); if (n < -12) { @@ -179,30 +177,57 @@ static int decodeAC12Field(int AC12Field, altitude_unit_t *unit) { // //========================================================================= // -// Decode the 7 bit ground movement field PWL exponential style scale +// Decode the 7 bit ground movement field PWL exponential style scale (ADS-B v2) // -static unsigned decodeMovementField(unsigned movement) { - int gspeed; - - // Note : movement codes 0,125,126,127 are all invalid, but they are +static float decodeMovementFieldV2(unsigned movement) { + // Note : movement codes 0,125,126,127 are all invalid, but they are // trapped for before this function is called. - if (movement > 123) gspeed = 199; // > 175kt - else if (movement > 108) gspeed = ((movement - 108) * 5) + 100; - else if (movement > 93) gspeed = ((movement - 93) * 2) + 70; - else if (movement > 38) gspeed = ((movement - 38) ) + 15; - else if (movement > 12) gspeed = ((movement - 11) >> 1) + 2; - else if (movement > 8) gspeed = ((movement - 6) >> 2) + 1; - else gspeed = 0; + // Each movement value is a range of speeds; + // we return the midpoint of the range (rounded to the nearest integer) + if (movement >= 125) return 0; // invalid + else if (movement == 124) return 180; // gs > 175kt, pick a value.. + else if (movement >= 109) return 100 + (movement - 109 + 0.5) * 5; // 100 < gs <= 175 in 5kt steps + else if (movement >= 94) return 70 + (movement - 94 + 0.5) * 2; // 70 < gs <= 100 in 2kt steps + else if (movement >= 39) return 15 + (movement - 39 + 0.5) * 1; // 15 < gs <= 70 in 1kt steps + else if (movement >= 13) return 2 + (movement - 13 + 0.5) * 0.50; // 2 < gs <= 15 in 0.5kt steps + else if (movement >= 9) return 1 + (movement - 9 + 0.5) * 0.25; // 1 < gs <= 2 in 0.25kt steps + else if (movement >= 3) return 0.125 + (movement - 3 + 0.5) * 0.875 / 6; // 0.125 < gs <= 1 in 0.875/6 kt step + else if (movement >= 2) return 0.125 / 2; // 0 < gs <= 0.125 + // 1: stopped, gs = 0 + // 0: no data + else return 0; +} - return (gspeed); +// +//========================================================================= +// +// Decode the 7 bit ground movement field PWL exponential style scale (ADS-B v0) +// +static float decodeMovementFieldV0(unsigned movement) { + // Note : movement codes 0,125,126,127 are all invalid, but they are + // trapped for before this function is called. + + // Each movement value is a range of speeds; + // we return the midpoint of the range + if (movement >= 125) return 0; // invalid + else if (movement == 124) return 180; // gs >= 175kt, pick a value.. + else if (movement >= 109) return 100 + (movement - 109 + 0.5) * 5; // 100 < gs <= 175 in 5kt steps + else if (movement >= 94) return 70 + (movement - 94 + 0.5) * 2; // 70 < gs <= 100 in 2kt steps + else if (movement >= 39) return 15 + (movement - 39 + 0.5) * 1; // 15 < gs <= 70 in 1kt steps + else if (movement >= 13) return 2 + (movement - 13 + 0.5) * 0.50; // 2 < gs <= 15 in 0.5kt steps + else if (movement >= 9) return 1 + (movement - 9 + 0.5) * 0.25; // 1 < gs <= 2 in 0.25kt steps + else if (movement >= 2) return 0.125 + (movement - 2 + 0.5) * 0.125; // 0.125 < gs <= 1 in 0.125kt step + // 1: stopped, gs < 0.125kt + // 0: no data + else return 0; } // Correct a decoded native-endian Address Announced field // (from bits 8-31) if it is affected by the given error // syndrome. Updates *addr and returns >0 if changed, 0 if // it was unaffected. -static int correct_aa_field(uint32_t *addr, struct errorinfo *ei) +static int correct_aa_field(uint32_t *addr, struct errorinfo *ei) { int i; int addr_errors = 0; @@ -220,67 +245,6 @@ static int correct_aa_field(uint32_t *addr, struct errorinfo *ei) 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. // The more positive, the more reliable the message is @@ -373,7 +337,7 @@ int scoreModesMessage(unsigned char *msg, int validbits) else return -1; } - + case 17: // Extended squitter case 18: // Extended squitter/non-transponder ei = modesChecksumDiagnose(crc, msgbits); @@ -382,7 +346,7 @@ int scoreModesMessage(unsigned char *msg, int validbits) // fix any errors in the address field addr = getbits(msg, 9, 32); - correct_aa_field(&addr, ei); + correct_aa_field(&addr, ei); if (icaoFilterTest(addr)) return 1800 / (ei->errors+1); @@ -412,13 +376,13 @@ int scoreModesMessage(unsigned char *msg, int validbits) // //========================================================================= // -// Decode a raw Mode S message demodulated as a stream of bytes by detectModeS(), +// Decode a raw Mode S message demodulated as a stream of bytes by detectModeS(), // and split it into fields populating a modesMessage structure. // 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 // -1: message might be valid, but we couldn't validate the CRC against a known ICAO @@ -521,7 +485,7 @@ int decodeModesMessage(struct modesMessage *mm, unsigned char *msg) mm->correctedbits = ei->errors; modesChecksumFix(msg, ei); addr2 = getbits(msg, 9, 32); - + // we are conservative here: only accept corrected messages that // match an existing aircraft. if (addr1 != addr2 && !icaoFilterTest(addr2)) { @@ -555,7 +519,7 @@ int decodeModesMessage(struct modesMessage *mm, unsigned char *msg) default: // All other message types, we don't know how to handle their CRCs, give up return -2; - } + } // decode the bulk of the message @@ -568,10 +532,9 @@ int decodeModesMessage(struct modesMessage *mm, unsigned char *msg) if (mm->msgtype == 0 || mm->msgtype == 4 || mm->msgtype == 16 || mm->msgtype == 20) { mm->AC = getbits(msg, 20, 32); if (mm->AC) { // Only attempt to decode if a valid (non zero) altitude is present - mm->altitude = decodeAC13Field(mm->AC, &mm->altitude_unit); - if (mm->altitude != INVALID_ALTITUDE) - mm->altitude_valid = 1; - mm->altitude_source = ALTITUDE_BARO; + mm->altitude_baro = decodeAC13Field(mm->AC, &mm->altitude_baro_unit); + if (mm->altitude_baro != INVALID_ALTITUDE) + mm->altitude_baro_valid = 1; } } @@ -737,34 +700,6 @@ int decodeModesMessage(struct modesMessage *mm, unsigned char *msg) 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) { // Aircraft Identification and Category @@ -780,6 +715,7 @@ static void decodeESIdentAndCategory(struct modesMessage *mm) mm->callsign[5] = ais_charset[getbits(me, 39, 44)]; mm->callsign[6] = ais_charset[getbits(me, 45, 50)]; mm->callsign[7] = ais_charset[getbits(me, 51, 56)]; + mm->callsign[8] = 0; // A common failure mode seems to be to intermittently send // all zeros. Catch that here. @@ -819,25 +755,31 @@ static void decodeESAirborneVelocity(struct modesMessage *mm, int check_imf) // Airborne Velocity Message unsigned char *me = mm->ME; + // 1-5: ME type + // 6-8: ME subtype mm->mesub = getbits(me, 6, 8); - if (check_imf && getbit(me, 9)) - setIMF(mm); - if (mm->mesub < 1 || mm->mesub > 4) return; - unsigned vert_rate = getbits(me, 38, 46); - if (vert_rate) { - mm->vert_rate = (vert_rate - 1) * (getbit(me, 37) ? -64 : 64); - mm->vert_rate_valid = 1; - } + // 9: IMF or Intent Change + if (check_imf && getbit(me, 9)) + setIMF(mm); - mm->vert_rate_source = (getbit(me, 36) ? ALTITUDE_GNSS : ALTITUDE_BARO); + // 10: reserved + // 11-13: NACv (NUCr in v0, maps directly to NACv in v2) + mm->accuracy.nac_v_valid = 1; + mm->accuracy.nac_v = getbits(me, 11, 13); + + // 14-35: speed/velocity depending on subtype switch (mm->mesub) { case 1: case 2: { + // 14: E/W direction + // 15-24: E/W speed + // 25: N/S direction + // 26-35: N/S speed unsigned ew_raw = getbits(me, 15, 24); unsigned ns_raw = getbits(me, 26, 35); @@ -846,46 +788,74 @@ static void decodeESAirborneVelocity(struct modesMessage *mm, int check_imf) int ns_vel = (ns_raw - 1) * (getbit(me, 25) ? -1 : 1) * ((mm->mesub == 2) ? 4 : 1); // Compute velocity and angle from the two speed components - mm->speed = (unsigned) sqrt((ns_vel * ns_vel) + (ew_vel * ew_vel) + 0.5); - mm->speed_valid = 1; + mm->gs.v0 = mm->gs.v2 = mm->gs.selected = sqrtf((ns_vel * ns_vel) + (ew_vel * ew_vel) + 0.5); + mm->gs_valid = 1; - if (mm->speed) { - int heading = (int) (atan2(ew_vel, ns_vel) * 180.0 / M_PI + 0.5); + if (mm->gs.selected > 0) { + float ground_track = atan2(ew_vel, ns_vel) * 180.0 / M_PI; // We don't want negative values but a 0-360 scale - if (heading < 0) - heading += 360; - mm->heading = (unsigned) heading; - mm->heading_source = HEADING_TRUE; + if (ground_track < 0) + ground_track += 360; + mm->heading = ground_track; + mm->heading_type = HEADING_GROUND_TRACK; mm->heading_valid = 1; } - - mm->speed_source = SPEED_GROUNDSPEED; } break; } case 3: case 4: { - unsigned airspeed = getbits(me, 26, 35); - if (airspeed) { - mm->speed = (airspeed - 1) * (mm->mesub == 4 ? 4 : 1); - mm->speed_source = getbit(me, 25) ? SPEED_TAS : SPEED_IAS; - mm->speed_valid = 1; + // 14: heading status + // 15-24: heading + if (getbit(me, 14)) { + mm->heading_valid = 1; + mm->heading = getbits(me, 15, 24) * 360.0 / 1024.0; + mm->heading_type = HEADING_MAGNETIC_OR_TRUE; } - if (getbit(me, 14)) { - mm->heading = getbits(me, 15, 24); - mm->heading_source = HEADING_MAGNETIC; - mm->heading_valid = 1; + // 25: airspeed type + // 26-35: airspeed + unsigned airspeed = getbits(me, 26, 35); + if (airspeed) { + unsigned speed = (airspeed - 1) * (mm->mesub == 4 ? 4 : 1); + if (getbit(me, 25)) { + mm->tas_valid = 1; + mm->tas = speed; + } else { + mm->ias_valid = 1; + mm->ias = speed; + } } + break; } } + // 36: vert rate source + // 37: vert rate sign + // 38-46: vert rate magnitude + unsigned vert_rate = getbits(me, 38, 46); + unsigned vert_rate_is_baro = getbit(me, 36); + if (vert_rate) { + int rate = (vert_rate - 1) * (getbit(me, 37) ? -64 : 64); + if (vert_rate_is_baro) { + mm->baro_rate = rate; + mm->baro_rate_valid = 1; + } else { + mm->geom_rate = rate; + mm->geom_rate_valid = 1; + } + } + + // 47-48: reserved + + // 49: baro/geom delta sign + // 50-56: baro/geom delta magnitude unsigned raw_delta = getbits(me, 50, 56); if (raw_delta) { - mm->gnss_delta_valid = 1; - mm->gnss_delta = (raw_delta - 1) * (getbit(me, 49) ? -25 : 25); + mm->geom_delta_valid = 1; + mm->geom_delta = (raw_delta - 1) * (getbit(me, 49) ? -25 : 25); } } @@ -894,29 +864,37 @@ static void decodeESSurfacePosition(struct modesMessage *mm, int check_imf) // Surface position and movement unsigned char *me = mm->ME; - if (check_imf && getbit(me, 21)) - setIMF(mm); - mm->airground = AG_GROUND; // definitely. - mm->cpr_lat = getbits(me, 23, 39); - mm->cpr_lon = getbits(me, 40, 56); - mm->cpr_odd = getbit(me, 22); - mm->cpr_nucp = (14 - mm->metype); mm->cpr_valid = 1; mm->cpr_type = CPR_SURFACE; + // 6-12: Movement unsigned movement = getbits(me, 6, 12); if (movement > 0 && movement < 125) { - mm->speed_valid = 1; - mm->speed = decodeMovementField(movement); - mm->speed_source = SPEED_GROUNDSPEED; + mm->gs_valid = 1; + mm->gs.selected = mm->gs.v0 = decodeMovementFieldV0(movement); // assumed v0 until told otherwise + mm->gs.v2 = decodeMovementFieldV2(movement); } + // 13: Heading/track status + // 14-20: Heading/track if (getbit(me, 13)) { mm->heading_valid = 1; - mm->heading_source = HEADING_TRUE; - mm->heading = getbits(me, 14, 20) * 360 / 128; + mm->heading = getbits(me, 14, 20) * 360.0 / 128.0; + mm->heading_type = HEADING_TRACK_OR_HEADING; } + + // 21: IMF or T flag + if (check_imf && getbit(me, 21)) + setIMF(mm); + + // 22: F flag (odd/even) + mm->cpr_odd = getbit(me, 22); + + // 23-39: CPR encoded latitude + mm->cpr_lat = getbits(me, 23, 39); + // 40-56: CPR encoded longitude + mm->cpr_lon = getbits(me, 40, 56); } static void decodeESAirbornePosition(struct modesMessage *mm, int check_imf) @@ -924,20 +902,54 @@ static void decodeESAirbornePosition(struct modesMessage *mm, int check_imf) // Airborne position and altitude unsigned char *me = mm->ME; - if (check_imf && getbit(me, 8)) - setIMF(mm); + // 6-7: surveillance status + switch (getbits(me, 6, 7)) { + case 0: + // no status + mm->alert_valid = mm->spi_valid = 1; + mm->alert = mm->spi = 0; + break; + case 1: // permanent alert + case 2: // temporary alert + mm->alert_valid = 1; + mm->alert = 1; + // states 1/2 override state 3, so we don't know SPI status here. + break; + case 3: // SPI + // we know there's no alert in this case + mm->alert_valid = mm->spi_valid = 1; + mm->alert = 0; + mm->spi = 1; + break; + } + // 8: IMF or NIC supplement-B + + if (check_imf) { + if (getbit(me, 8)) + setIMF(mm); + } else { + // NIC-B (v2) or SAF (v0/v1) + mm->accuracy.nic_b_valid = 1; + mm->accuracy.nic_b = getbit(me, 8); + } + + // 9-20: altitude unsigned AC12Field = getbits(me, 9, 20); if (mm->metype == 0) { - mm->cpr_nucp = 0; + // no position information } else { - // Catch some common failure modes and don't mark them as valid - // (so they won't be used for positioning) + // 21: T flag (UTC sync or not) + // 22: F flag (odd or even) + // 23-39: CPR encoded latitude + // 40-56: CPR encoded longitude mm->cpr_lat = getbits(me, 23, 39); mm->cpr_lon = getbits(me, 40, 56); + // Catch some common failure modes and don't mark them as valid + // (so they won't be used for positioning) if (AC12Field == 0 && mm->cpr_lon == 0 && (mm->cpr_lat & 0x0fff) == 0 && mm->metype == 15) { // Seen from at least: // 400F3F (Eurocopter ECC155 B1) - Bristow Helicopters @@ -951,23 +963,23 @@ static void decodeESAirbornePosition(struct modesMessage *mm, int check_imf) mm->cpr_valid = 1; mm->cpr_type = CPR_AIRBORNE; mm->cpr_odd = getbit(me, 22); - - if (mm->metype == 18 || mm->metype == 22) - mm->cpr_nucp = 0; - else if (mm->metype < 18) - mm->cpr_nucp = (18 - mm->metype); - else - mm->cpr_nucp = (29 - mm->metype); } } if (AC12Field) {// Only attempt to decode if a valid (non zero) altitude is present - mm->altitude = decodeAC12Field(AC12Field, &mm->altitude_unit); - if (mm->altitude != INVALID_ALTITUDE) { - mm->altitude_valid = 1; + altitude_unit_t unit; + int alt = decodeAC12Field(AC12Field, &unit); + if (alt != INVALID_ALTITUDE) { + if (mm->metype == 20 || mm->metype == 21 || mm->metype == 22) { + mm->altitude_geom = alt; + mm->altitude_geom_unit = unit; + mm->altitude_geom_valid = 1; + } else { + mm->altitude_baro = alt; + mm->altitude_baro_unit = unit; + mm->altitude_baro_valid = 1; + } } - - mm->altitude_source = (mm->metype == 20 || mm->metype == 21 || mm->metype == 22) ? ALTITUDE_GNSS : ALTITUDE_BARO; } } @@ -994,7 +1006,10 @@ static void decodeESAircraftStatus(struct modesMessage *mm, int check_imf) mm->mesub = getbits(me, 6, 8); if (mm->mesub == 1) { // Emergency status squawk field - int ID13Field = getbits(me, 12, 24); + mm->emergency_valid = 1; + mm->emergency = (emergency_t) getbits(me, 9, 11); + + unsigned ID13Field = getbits(me, 12, 24); if (ID13Field) { mm->squawk_valid = 1; mm->squawk = decodeID13Field(ID13Field); @@ -1014,48 +1029,189 @@ static void decodeESTargetStatus(struct modesMessage *mm, int check_imf) if (check_imf && getbit(me, 51)) setIMF(mm); - if (mm->mesub == 0) { // Target state and status, V1 - // TODO: need RTCA/DO-260A + if (mm->mesub == 0 && getbit(me, 11) == 0) { // Target state and status, V1 + // 8-9: vertical source + switch (getbits(me, 8, 9)) { + case 1: + mm->nav.altitude_source = NAV_ALT_MCP; + break; + case 2: + mm->nav.altitude_source = NAV_ALT_AIRCRAFT; + break; + case 3: + mm->nav.altitude_source = NAV_ALT_FMS; + break; + default: + // nothing + break; + } + // 10: target altitude type (ignored) + // 11: backward compatibility bit, always 0 + // 12-13: target alt capabilities (ignored) + // 14-15: vertical mode + switch (getbits(me, 14, 15)) { + case 1: // "acquiring" + mm->nav.modes_valid = 1; + if (mm->nav.altitude_source == NAV_ALT_FMS) { + mm->nav.modes |= NAV_MODE_VNAV; + } else { + mm->nav.modes |= NAV_MODE_AUTOPILOT; + } + break; + case 2: // "maintaining" + mm->nav.modes_valid = 1; + if (mm->nav.altitude_source == NAV_ALT_FMS) { + mm->nav.modes |= NAV_MODE_VNAV; + } else if (mm->nav.altitude_source == NAV_ALT_AIRCRAFT) { + mm->nav.modes |= NAV_MODE_ALT_HOLD; + } else { + mm->nav.modes |= NAV_MODE_AUTOPILOT; + } + break; + default: + // nothing + break; + } + + // 16-25: altitude + int alt = -1000 + 100 * getbits(me, 16, 25); + switch (mm->nav.altitude_source) { + case NAV_ALT_MCP: + mm->nav.mcp_altitude_valid = 1; + mm->nav.mcp_altitude = alt; + break; + case NAV_ALT_FMS: + mm->nav.fms_altitude_valid = 1; + mm->nav.fms_altitude = alt; + break; + default: + // nothing + break; + } + // 26-27: horizontal data source + unsigned h_source = getbits(me, 26, 27); + if (h_source != 0) { + // 28-36: target heading/track + mm->nav.heading_valid = 1; + mm->nav.heading = getbits(me, 28, 36); + // 37: track vs heading + if (getbit(me, 37)) { + mm->nav.heading_type = HEADING_GROUND_TRACK; + } else { + mm->nav.heading_type = HEADING_MAGNETIC_OR_TRUE; + } + } + // 38-39: horiontal mode + switch (getbits(me, 38, 39)) { + case 1: // acquiring + case 2: // maintaining + mm->nav.modes_valid = 1; + if (h_source == 3) { // FMS + mm->nav.modes |= NAV_MODE_LNAV; + } else { + mm->nav.modes |= NAV_MODE_AUTOPILOT; + } + break; + default: + // nothing + break; + } + + // 40-43: NACp + mm->accuracy.nac_p_valid = 1; + mm->accuracy.nac_p = getbits(me, 40, 43); + + // 44: NICbaro + mm->accuracy.nic_baro_valid = 1; + mm->accuracy.nic_baro = getbit(me, 44); + + // 45-46: SIL + mm->accuracy.sil = getbits(me, 45, 46); + mm->accuracy.sil_type = SIL_UNKNOWN; + + // 47-51: reserved + + // 52-53: TCAS status + switch (getbits(me, 52, 53)) { + case 1: + mm->nav.modes_valid = 1; + // no tcas + break; + case 2: + case 3: + mm->nav.modes_valid = 1; + mm->nav.modes |= NAV_MODE_TCAS; + break; + case 0: + // assume TCAS if we had any other modes + // but don't enable modes just for this + mm->nav.modes |= NAV_MODE_TCAS; + break; + default: + // nothing + break; + } + + + // 54-56: emergency/priority + mm->emergency_valid = 1; + mm->emergency = (emergency_t) getbits(me, 54, 56); } else if (mm->mesub == 1) { // Target state and status, V2 - mm->tss.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); - if (alt_bits == 0) { - mm->tss.altitude_valid = 0; - } else { - mm->tss.altitude_valid = 1; - mm->tss.altitude = (alt_bits - 1) * 32; + if (alt_bits != 0) { + if (is_fms) { + mm->nav.fms_altitude_valid = 1; + mm->nav.fms_altitude = (alt_bits - 1) * 32; + } else { + mm->nav.mcp_altitude_valid = 1; + mm->nav.mcp_altitude = (alt_bits - 1) * 32; + } } unsigned baro_bits = getbits(me, 21, 29); - if (baro_bits == 0) { - mm->tss.baro_valid = 0; - } else { - mm->tss.baro_valid = 1; - mm->tss.baro = 800.0 + (baro_bits - 1) * 0.8; + if (baro_bits != 0) { + mm->nav.qnh_valid = 1; + mm->nav.qnh = 800.0 + (baro_bits - 1) * 0.8; } - mm->tss.heading_valid = getbit(me, 30); - if (mm->tss.heading_valid) { + if (getbit(me, 30)) { + mm->nav.heading_valid = 1; // two's complement -180..+180, which is conveniently // also the same as unsigned 0..360 - mm->tss.heading = getbits(me, 31, 39) * 180 / 256; + mm->nav.heading = getbits(me, 31, 39) * 180.0 / 256.0; + mm->nav.heading_type = HEADING_MAGNETIC_OR_TRUE; } - mm->tss.nac_p = getbits(me, 40, 43); - mm->tss.nic_baro = getbit(me, 44); - mm->tss.sil = getbits(me, 45, 46); - mm->tss.mode_valid = getbit(me, 47); - if (mm->tss.mode_valid) { - mm->tss.mode_autopilot = getbit(me, 48); - mm->tss.mode_vnav = getbit(me, 49); - mm->tss.mode_alt_hold = getbit(me, 50); - mm->tss.mode_approach = getbit(me, 52); + // 40-43: NACp + mm->accuracy.nac_p_valid = 1; + mm->accuracy.nac_p = getbits(me, 40, 43); + + // 44: NICbaro + mm->accuracy.nic_baro_valid = 1; + mm->accuracy.nic_baro = getbit(me, 44); + + // 45-46: SIL + mm->accuracy.sil = getbits(me, 45, 46); + mm->accuracy.sil_type = SIL_UNKNOWN; + + // 47: mode bits validity + if (getbit(me, 47)) { + // 48-54: mode bits + mm->nav.modes_valid = 1; + mm->nav.modes = + (getbit(me, 48) ? NAV_MODE_AUTOPILOT : 0) | + (getbit(me, 49) ? NAV_MODE_VNAV : 0) | + (getbit(me, 50) ? NAV_MODE_ALT_HOLD : 0) | + // 51: IMF + (getbit(me, 52) ? NAV_MODE_APPROACH : 0) | + (getbit(me, 53) ? NAV_MODE_TCAS : 0) | + (getbit(me, 54) ? NAV_MODE_LNAV : 0); } - mm->tss.acas_operational = getbit(me, 53); + // 55-56 reserved } } @@ -1075,6 +1231,10 @@ static void decodeESOperationalStatus(struct modesMessage *mm, int check_imf) switch (mm->opstatus.version) { case 0: + if (mm->mesub == 0 && getbits(me, 9, 10) == 0) { + mm->opstatus.cc_acas = !getbit(me, 12); + mm->opstatus.cc_cdti = getbit(me, 13); + } break; case 1: @@ -1100,59 +1260,71 @@ static void decodeESOperationalStatus(struct modesMessage *mm, int check_imf) mm->opstatus.cc_lw = getbits(me, 21, 24); } - mm->opstatus.nic_supp_a = getbit(me, 44); - mm->opstatus.nac_p = getbits(me, 45, 48); - mm->opstatus.sil = getbits(me, 51, 52); - if (mm->mesub == 0) { - mm->opstatus.nic_baro = getbit(me, 53); - } else { - mm->opstatus.track_angle = getbit(me, 53) ? ANGLE_TRACK : ANGLE_HEADING; - } + mm->accuracy.nic_a_valid = 1; + mm->accuracy.nic_a = getbit(me, 44); + mm->accuracy.nac_p_valid = 1; + mm->accuracy.nac_p = getbits(me, 45, 48); + mm->accuracy.sil_type = SIL_UNKNOWN; + mm->accuracy.sil = getbits(me, 51, 52); + mm->opstatus.hrd = getbit(me, 54) ? HEADING_MAGNETIC : HEADING_TRUE; + + if (mm->mesub == 0) { + mm->accuracy.nic_baro_valid = 1; + mm->accuracy.nic_baro = getbit(me, 53); + } else { + mm->opstatus.tah = getbit(me, 53) ? HEADING_GROUND_TRACK : mm->opstatus.hrd; + } break; case 2: - default: if (getbits(me, 25, 26) == 0) { mm->opstatus.om_acas_ra = getbit(me, 27); mm->opstatus.om_ident = getbit(me, 28); mm->opstatus.om_atc = getbit(me, 29); mm->opstatus.om_saf = getbit(me, 30); - mm->opstatus.om_sda = getbits(me, 31, 32); + mm->accuracy.sda_valid = 1; + mm->accuracy.sda = getbits(me, 31, 32); } - if (mm->mesub == 0 && getbits(me, 9, 10) == 0 && getbits(me, 13, 14) == 0) { + if (mm->mesub == 0 && getbits(me, 9, 10) == 0) { // airborne - mm->opstatus.cc_acas = getbit(me, 11); + mm->opstatus.cc_acas = getbit(me, 11); // nb inverted sense versus v0/v1 mm->opstatus.cc_1090_in = getbit(me, 12); mm->opstatus.cc_arv = getbit(me, 15); mm->opstatus.cc_ts = getbit(me, 16); mm->opstatus.cc_tc = getbits(me, 17, 18); mm->opstatus.cc_uat_in = getbit(me, 19); - } else if (mm->mesub == 1 && getbits(me, 9, 10) == 0 && getbits(me, 13, 14) == 0) { + } else if (mm->mesub == 1 && getbits(me, 9, 10) == 0) { // surface mm->opstatus.cc_poa = getbit(me, 11); mm->opstatus.cc_1090_in = getbit(me, 12); mm->opstatus.cc_b2_low = getbit(me, 15); mm->opstatus.cc_uat_in = getbit(me, 16); - mm->opstatus.cc_nac_v = getbits(me, 17, 19); - mm->opstatus.cc_nic_supp_c = getbit(me, 20); + mm->accuracy.nac_v_valid = 1; + mm->accuracy.nac_v = getbits(me, 17, 19); + mm->accuracy.nic_c_valid = 1; + mm->accuracy.nic_c = getbit(me, 20); mm->opstatus.cc_lw_valid = 1; mm->opstatus.cc_lw = getbits(me, 21, 24); mm->opstatus.cc_antenna_offset = getbits(me, 33, 40); } - mm->opstatus.nic_supp_a = getbit(me, 44); - mm->opstatus.nac_p = getbits(me, 45, 48); - mm->opstatus.sil = getbits(me, 51, 52); - if (mm->mesub == 0) { - mm->opstatus.gva = getbits(me, 49, 50); - mm->opstatus.nic_baro = getbit(me, 53); - } else { - mm->opstatus.track_angle = getbit(me, 53) ? ANGLE_TRACK : ANGLE_HEADING; - } + mm->accuracy.nic_a_valid = 1; + mm->accuracy.nic_a = getbit(me, 44); + mm->accuracy.nac_p_valid = 1; + mm->accuracy.nac_p = getbits(me, 45, 48); + mm->accuracy.sil = getbits(me, 51, 52); + mm->accuracy.sil_type = getbit(me, 55) ? SIL_PER_SAMPLE : SIL_PER_HOUR; mm->opstatus.hrd = getbit(me, 54) ? HEADING_MAGNETIC : HEADING_TRUE; - mm->opstatus.sil_type = getbit(me, 55) ? SIL_PER_SAMPLE : SIL_PER_HOUR; + if (mm->mesub == 0) { + mm->accuracy.gva_valid = 1; + mm->accuracy.gva = getbits(me, 49, 50); + mm->accuracy.nic_baro_valid = 1; + mm->accuracy.nic_baro = getbit(me, 53); + } else { + mm->opstatus.tah = getbit(me, 53) ? HEADING_GROUND_TRACK : mm->opstatus.hrd; + } break; } } @@ -1230,7 +1402,7 @@ static void decodeExtendedSquitter(struct modesMessage *mm) 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 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); break; @@ -1256,21 +1428,11 @@ static void decodeExtendedSquitter(struct modesMessage *mm) decodeESOperationalStatus(mm, check_imf); break; - default: + default: break; } } -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] = { /* 0 */ "Short Air-Air Surveillance", /* 1 */ NULL, @@ -1326,17 +1488,6 @@ static const char *altitude_unit_to_string(altitude_unit_t unit) { } } -static const char *altitude_source_to_string(altitude_source_t source) { - switch (source) { - case ALTITUDE_BARO: - return "barometric"; - case ALTITUDE_GNSS: - return "GNSS"; - default: - return "(unknown altitude source)"; - } -} - static const char *airground_to_string(airground_t airground) { switch (airground) { case AG_GROUND: @@ -1352,19 +1503,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) { switch (type) { case ADDR_ADSB_ICAO: @@ -1403,6 +1541,94 @@ static const char *cpr_type_to_string(cpr_type_t type) { } } +static const char *heading_type_to_string(heading_type_t type) { + switch (type) { + case HEADING_GROUND_TRACK: + return "Ground track"; + case HEADING_MAGNETIC: + return "Mag heading"; + case HEADING_TRUE: + return "True heading"; + case HEADING_MAGNETIC_OR_TRUE: + return "Heading"; + case HEADING_TRACK_OR_HEADING: + return "Track/Heading"; + default: + return "unknown heading 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 const char *nav_modes_to_string(nav_modes_t flags) +{ + static char buf[128]; + + buf[0] = 0; + if (flags & NAV_MODE_AUTOPILOT) + strcat(buf, "autopilot "); + if (flags & NAV_MODE_VNAV) + strcat(buf, "vnav "); + if (flags & NAV_MODE_ALT_HOLD) + strcat(buf, "althold "); + if (flags & NAV_MODE_APPROACH) + strcat(buf, "approach "); + if (flags & NAV_MODE_LNAV) + strcat(buf, "lnav "); + if (flags & NAV_MODE_TCAS) + strcat(buf, "tcas "); + + if (buf[0] != 0) + buf[strlen(buf)-1] = 0; + + return buf; +} + +static const char *sil_type_to_string(sil_type_t type) +{ + switch (type) { + case SIL_UNKNOWN: return "unknown type"; + case SIL_PER_HOUR: return "per flight hour"; + case SIL_PER_SAMPLE: return "per sample"; + default: return "invalid type"; + } +} + +static const char *emergency_to_string(emergency_t emergency) +{ + switch (emergency) { + case EMERGENCY_NONE: return "no emergency"; + case EMERGENCY_GENERAL: return "general emergency (7700)"; + case EMERGENCY_LIFEGUARD: return "lifeguard / medical emergency"; + case EMERGENCY_MINFUEL: return "minimum fuel"; + case EMERGENCY_NORDO: return "no communications (7600)"; + case EMERGENCY_UNLAWFUL: return "unlawful interference (7500)"; + case EMERGENCY_DOWNED: return "downed aircraft"; + default: return "reserved"; + } +} + static void print_hex_bytes(unsigned char *data, size_t len) { size_t i; for (i = 0; i < len; ++i) { @@ -1455,7 +1681,7 @@ static const char *esTypeName(unsigned metype, unsigned mesub) } case 20: case 21: case 22: - return "Airborne position (GNSS altitude)"; + return "Airborne position (geometric altitude)"; case 23: switch (mesub) { @@ -1639,6 +1865,10 @@ void displayModesMessage(struct modesMessage *mm) { } 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) { printf(" Other Address: %06X (%s)\n", mm->addr & 0xFFFFFF, addrtype_to_string(mm->addrtype)); } else { @@ -1650,32 +1880,64 @@ void displayModesMessage(struct modesMessage *mm) { airground_to_string(mm->airground)); } - if (mm->altitude_valid) { - printf(" Altitude: %d %s %s\n", - mm->altitude, - altitude_unit_to_string(mm->altitude_unit), - altitude_source_to_string(mm->altitude_source)); + if (mm->altitude_baro_valid) { + printf(" Baro altitude: %d %s\n", + mm->altitude_baro, + altitude_unit_to_string(mm->altitude_baro_unit)); } - if (mm->gnss_delta_valid) { - printf(" GNSS delta: %d ft\n", - mm->gnss_delta); + if (mm->altitude_geom_valid) { + printf(" Geom altitude: %d %s\n", + mm->altitude_geom, + altitude_unit_to_string(mm->altitude_geom_unit)); + } + + if (mm->geom_delta_valid) { + printf(" Geom - baro: %d ft\n", + mm->geom_delta); } if (mm->heading_valid) { - printf(" Heading: %u\n", mm->heading); + printf(" %-13s %.1f\n", heading_type_to_string(mm->heading_type), mm->heading); } - if (mm->speed_valid) { - printf(" Speed: %u kt %s\n", - mm->speed, - speed_source_to_string(mm->speed_source)); + if (mm->track_rate_valid) { + printf(" Track rate: %.2f deg/sec %s\n", mm->track_rate, mm->track_rate < 0 ? "left" : mm->track_rate > 0 ? "right" : ""); } - if (mm->vert_rate_valid) { - printf(" Vertical rate: %d ft/min %s\n", - 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: %.1f kt", mm->gs.selected); + if (mm->gs.v0 != mm->gs.selected) { + printf(" (v0: %.1f kt)", mm->gs.v0); + } + if (mm->gs.v2 != mm->gs.selected) { + printf(" (v2: %.1f kt)", mm->gs.v2); + } + printf("\n"); + } + + if (mm->ias_valid) { + 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) { @@ -1695,21 +1957,24 @@ void displayModesMessage(struct modesMessage *mm) { if (mm->cpr_valid) { printf(" CPR type: %s\n" - " CPR odd flag: %s\n" - " CPR NUCp/NIC: %u\n", + " CPR odd flag: %s\n", cpr_type_to_string(mm->cpr_type), - mm->cpr_odd ? "odd" : "even", - mm->cpr_nucp); + mm->cpr_odd ? "odd" : "even"); if (mm->cpr_decoded) { printf(" CPR latitude: %.5f (%u)\n" " CPR longitude: %.5f (%u)\n" - " CPR decoding: %s\n", + " CPR decoding: %s\n" + " NIC: %u\n" + " Rc: %.3f km / %.1f NM\n", mm->decoded_lat, mm->cpr_lat, mm->decoded_lon, mm->cpr_lon, - mm->cpr_relative ? "local" : "global"); + mm->cpr_relative ? "local" : "global", + mm->decoded_nic, + mm->decoded_rc / 1000.0, + mm->decoded_rc / 1852.0); } else { printf(" CPR latitude: (%u)\n" " CPR longitude: (%u)\n" @@ -1719,6 +1984,52 @@ void displayModesMessage(struct modesMessage *mm) { } } + if (mm->accuracy.nic_a_valid) { + printf(" NIC-A: %d\n", mm->accuracy.nic_a); + } + if (mm->accuracy.nic_b_valid) { + printf(" NIC-B: %d\n", mm->accuracy.nic_b); + } + if (mm->accuracy.nic_c_valid) { + printf(" NIC-C: %d\n", mm->accuracy.nic_c); + } + if (mm->accuracy.nic_baro_valid) { + printf(" NIC-baro: %d\n", mm->accuracy.nic_baro); + } + if (mm->accuracy.nac_p_valid) { + printf(" NACp: %d\n", mm->accuracy.nac_p); + } + if (mm->accuracy.nac_v_valid) { + printf(" NACv: %d\n", mm->accuracy.nac_v); + } + if (mm->accuracy.gva_valid) { + printf(" GVA: %d\n", mm->accuracy.gva); + } + if (mm->accuracy.sil_type != SIL_INVALID) { + const char *sil_description; + switch (mm->accuracy.sil) { + case 1: + sil_description = "p <= 0.1%"; + break; + case 2: + sil_description = "p <= 0.001%"; + break; + case 3: + sil_description = "p <= 0.00001%"; + break; + default: + sil_description = "p > 0.1%"; + break; + } + printf(" SIL: %d (%s, %s)\n", + mm->accuracy.sil, + sil_description, + sil_type_to_string(mm->accuracy.sil_type)); + } + if (mm->accuracy.sda_valid) { + printf(" SDA: %d\n", mm->accuracy.sda); + } + if (mm->opstatus.valid) { printf(" Aircraft Operational Status:\n"); printf(" Version: %d\n", mm->opstatus.version); @@ -1733,8 +2044,6 @@ void displayModesMessage(struct modesMessage *mm) { if (mm->opstatus.cc_uat_in) printf("UATIN "); if (mm->opstatus.cc_poa) printf("POA "); if (mm->opstatus.cc_b2_low) printf("B2-LOW "); - if (mm->opstatus.cc_nac_v) printf("NACv=%d ", mm->opstatus.cc_nac_v); - if (mm->opstatus.cc_nic_supp_c) printf("NIC-C=1 "); if (mm->opstatus.cc_lw_valid) printf("L/W=%d ", mm->opstatus.cc_lw); if (mm->opstatus.cc_antenna_offset) printf("GPS-OFFSET=%d ", mm->opstatus.cc_antenna_offset); printf("\n"); @@ -1744,40 +2053,44 @@ void displayModesMessage(struct modesMessage *mm) { if (mm->opstatus.om_ident) printf("IDENT "); if (mm->opstatus.om_atc) printf("ATC "); if (mm->opstatus.om_saf) printf("SAF "); - if (mm->opstatus.om_sda) printf("SDA=%d ", mm->opstatus.om_sda); printf("\n"); - if (mm->opstatus.nic_supp_a) printf(" NIC-A: %d\n", mm->opstatus.nic_supp_a); - if (mm->opstatus.nac_p) printf(" NACp: %d\n", mm->opstatus.nac_p); - if (mm->opstatus.gva) printf(" GVA: %d\n", mm->opstatus.gva); - if (mm->opstatus.sil) printf(" SIL: %d (%s)\n", mm->opstatus.sil, (mm->opstatus.sil_type == SIL_PER_HOUR ? "per hour" : "per sample")); - if (mm->opstatus.nic_baro) printf(" NICbaro: %d\n", mm->opstatus.nic_baro); - if (mm->mesub == 1) - printf(" Heading type: %s\n", (mm->opstatus.track_angle == ANGLE_HEADING ? "heading" : "track angle")); - printf(" Heading reference: %s\n", (mm->opstatus.hrd == HEADING_TRUE ? "true north" : "magnetic north")); + printf(" Track/heading: %s\n", heading_type_to_string(mm->opstatus.tah)); + printf(" Heading ref dir: %s\n", heading_type_to_string(mm->opstatus.hrd)); } - if (mm->tss.valid) { - printf(" Target State and Status:\n"); - if (mm->tss.altitude_valid) - printf(" Target altitude: %s, %d ft\n", (mm->tss.altitude_type == TSS_ALTITUDE_MCP ? "MCP" : "FMS"), mm->tss.altitude); - if (mm->tss.baro_valid) - printf(" Altimeter setting: %.1f millibars\n", mm->tss.baro); - if (mm->tss.heading_valid) - printf(" Target heading: %d\n", mm->tss.heading); - if (mm->tss.mode_valid) { - printf(" Active modes: "); - if (mm->tss.mode_autopilot) printf("autopilot "); - if (mm->tss.mode_vnav) printf("VNAV "); - if (mm->tss.mode_alt_hold) printf("altitude-hold "); - if (mm->tss.mode_approach) printf("approach "); - printf("\n"); + if (mm->nav.heading_valid) + printf(" Selected heading: %.1f\n", mm->nav.heading); + if (mm->nav.fms_altitude_valid) + printf(" FMS selected altitude: %u ft\n", mm->nav.fms_altitude); + if (mm->nav.mcp_altitude_valid) + printf(" MCP selected altitude: %u ft\n", mm->nav.mcp_altitude); + if (mm->nav.qnh_valid) + printf(" QNH: %.1f millibars\n", mm->nav.qnh); + if (mm->nav.altitude_source != NAV_ALT_INVALID) { + printf(" Target altitude source: "); + switch (mm->nav.altitude_source) { + case NAV_ALT_AIRCRAFT: + printf("aircraft altitude\n"); + break; + case NAV_ALT_MCP: + printf("MCP selected altitude\n"); + break; + case NAV_ALT_FMS: + printf("FMS selected altitude\n"); + break; + default: + printf("unknown\n"); } - 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")); + } + + if (mm->nav.modes_valid) { + printf(" Nav modes: %s\n", nav_modes_to_string(mm->nav.modes)); + } + + if (mm->emergency_valid) { + printf(" Emergency/priority: %s\n", emergency_to_string(mm->emergency)); } printf("\n"); @@ -1787,8 +2100,8 @@ void displayModesMessage(struct modesMessage *mm) { // //========================================================================= // -// When a new message is available, because it was decoded from the RTL device, -// file, or received in the TCP input port, or any other way we can receive a +// When a new message is available, because it was decoded from the RTL device, +// file, or received in the TCP input port, or any other way we can receive a // decoded message, we call this function in order to use the message. // // Basically this function passes a raw message to the upper layers for further @@ -1813,14 +2126,13 @@ void useModesMessage(struct modesMessage *mm) { // forward messages when we have seen two of them. if (Modes.net) { - if (Modes.net_verbatim || mm->msgtype == 32) { + if (Modes.net_verbatim || mm->msgtype == 32 || !a) { // Unconditionally send modesQueueOutput(mm, a); } else if (a->messages > 1) { - // If this is the second message, and we - // squelched the first message, then re-emit the - // first message now. - if (!Modes.net_verbatim && a && a->messages == 2) { + // Suppress the first message. When we receive a second message, + // emit the first two messages. + if (a->messages == 2) { modesQueueOutput(&a->first_message, a); } modesQueueOutput(mm, a); diff --git a/mode_s.h b/mode_s.h new file mode 100644 index 0000000..4fbc983 --- /dev/null +++ b/mode_s.h @@ -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 +// +// 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 . + +#ifndef MODE_S_H +#define MODE_S_H + +#include + +// +// 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 diff --git a/net_io.c b/net_io.c index 35e1e27..689e328 100644 --- a/net_io.c +++ b/net_io.c @@ -4,20 +4,20 @@ // // Copyright (c) 2014-2016 Oliver Jowett // -// This file is free software: you may copy, redistribute and/or modify it +// 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. +// 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 +// 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 +// You should have received a copy of the GNU General Public License // along with this program. If not, see . -// This file incorporates work covered by the following copyright and +// This file incorporates work covered by the following copyright and // permission notice: // // Copyright (C) 2012 by Salvatore Sanfilippo @@ -53,6 +53,7 @@ #include #include +#include // // ============================= Networking ============================= @@ -565,11 +566,12 @@ static void modesSendSBSOutput(struct modesMessage *mm, struct aircraft *a) { localtime_r(&now.tv_sec, &stTime_now); // Find message reception time - localtime_r(&mm->sysTimestampMsg.tv_sec, &stTime_receive); + time_t received = (time_t) (mm->sysTimestampMsg / 1000); + localtime_r(&received, &stTime_receive); // Fields 7 & 8 are the message reception time and date p += sprintf(p, "%04d/%02d/%02d,", (stTime_receive.tm_year+1900),(stTime_receive.tm_mon+1), stTime_receive.tm_mday); - p += sprintf(p, "%02d:%02d:%02d.%03u,", stTime_receive.tm_hour, stTime_receive.tm_min, stTime_receive.tm_sec, (unsigned) (mm->sysTimestampMsg.tv_nsec / 1000000U)); + p += sprintf(p, "%02d:%02d:%02d.%03u,", stTime_receive.tm_hour, stTime_receive.tm_min, stTime_receive.tm_sec, (unsigned) (mm->sysTimestampMsg / 1000)); // Fields 9 & 10 are the current time and date p += sprintf(p, "%04d/%02d/%02d,", (stTime_now.tm_year+1900),(stTime_now.tm_mon+1), stTime_now.tm_mday); @@ -580,38 +582,36 @@ static void modesSendSBSOutput(struct modesMessage *mm, struct aircraft *a) { else {p += sprintf(p, ",");} // Field 12 is the altitude (if we have it) - if (mm->altitude_valid) { - if (Modes.use_gnss) { - if (mm->altitude_source == ALTITUDE_GNSS) { - p += sprintf(p, ",%dH", mm->altitude); - } else if (trackDataValid(&a->gnss_delta_valid)) { - p += sprintf(p, ",%dH", mm->altitude + a->gnss_delta); - } else { - p += sprintf(p, ",%d", mm->altitude); - } + if (Modes.use_gnss) { + if (mm->altitude_geom_valid) { + p += sprintf(p, ",%dH", mm->altitude_geom); + } else if (mm->altitude_baro_valid && trackDataValid(&a->geom_delta_valid)) { + p += sprintf(p, ",%dH", mm->altitude_baro + a->geom_delta); + } else if (mm->altitude_baro_valid) { + p += sprintf(p, ",%d", mm->altitude_baro); } else { - if (mm->altitude_source == ALTITUDE_BARO) { - p += sprintf(p, ",%d", mm->altitude); - } else if (trackDataValid(&a->gnss_delta_valid)) { - p += sprintf(p, ",%d", mm->altitude - a->gnss_delta); - } else { - p += sprintf(p, ","); - } + p += sprintf(p, ","); } + } else { + if (mm->altitude_baro_valid) { + p += sprintf(p, ",%d", mm->altitude_baro); + } else if (mm->altitude_geom_valid && trackDataValid(&a->geom_delta_valid)) { + p += sprintf(p, ",%d", mm->altitude_geom - a->geom_delta); + } else { + p += sprintf(p, ","); + } + } + + // Field 13 is the ground Speed (if we have it) + if (mm->gs_valid) { + p += sprintf(p, ",%.0f", mm->gs.selected); } else { p += sprintf(p, ","); } - // Field 13 is the ground Speed (if we have it) - if (mm->speed_valid && mm->speed_source == SPEED_GROUNDSPEED) { - p += sprintf(p, ",%d", mm->speed); - } else { - p += sprintf(p, ","); - } - - // Field 14 is the ground Heading (if we have it) - if (mm->heading_valid && mm->heading_source == HEADING_TRUE) { - p += sprintf(p, ",%d", mm->heading); + // Field 14 is the ground Heading (if we have it) + if (mm->heading_valid && mm->heading_type == HEADING_GROUND_TRACK) { + p += sprintf(p, ",%.0f", mm->heading); } else { p += sprintf(p, ","); } @@ -624,10 +624,22 @@ static void modesSendSBSOutput(struct modesMessage *mm, struct aircraft *a) { } // Field 17 is the VerticalRate (if we have it) - if (mm->vert_rate_valid) { - p += sprintf(p, ",%d", mm->vert_rate); + if (Modes.use_gnss) { + if (mm->geom_rate_valid) { + p += sprintf(p, ",%dH", mm->geom_rate); + } else if (mm->baro_rate_valid) { + p += sprintf(p, ",%d", mm->baro_rate); + } else { + p += sprintf(p, ","); + } } else { - p += sprintf(p, ","); + if (mm->baro_rate_valid) { + p += sprintf(p, ",%d", mm->baro_rate); + } else if (mm->geom_rate_valid) { + p += sprintf(p, ",%d", mm->geom_rate); + } else { + p += sprintf(p, ","); + } } // Field 18 is the Squawk (if we have it) @@ -908,7 +920,7 @@ static int decodeBinMessage(struct client *c, char *p) { } // record reception time as the time we read it. - clock_gettime(CLOCK_REALTIME, &mm.sysTimestampMsg); + mm.sysTimestampMsg = mstime(); ch = *p++; // Grab the signal level mm.signalLevel = ((unsigned char)ch / 255.0); @@ -960,13 +972,13 @@ static int hexDigitVal(int c) { // // This function decodes a string representing message in raw hex format // like: *8D4B969699155600E87406F5B69F; The string is null-terminated. -// +// // The message is passed to the higher level layers, so it feeds // the selected screen output, the network output and so forth. -// +// // If the message looks invalid it is silently discarded. // -// The function always returns 0 (success) to the caller as there is no +// The function always returns 0 (success) to the caller as there is no // case where we want broken messages here to close the client connection. // static int decodeHexMessage(struct client *c, char *hex) { @@ -1018,13 +1030,13 @@ static int decodeHexMessage(struct client *c, char *hex) { break;} } - if ( (l != (MODEAC_MSG_BYTES * 2)) - && (l != (MODES_SHORT_MSG_BYTES * 2)) + if ( (l != (MODEAC_MSG_BYTES * 2)) + && (l != (MODES_SHORT_MSG_BYTES * 2)) && (l != (MODES_LONG_MSG_BYTES * 2)) ) {return (0);} // Too short or long message... broken - if ( (0 == Modes.mode_ac) - && (l == (MODEAC_MSG_BYTES * 2)) ) + if ( (0 == Modes.mode_ac) + && (l == (MODEAC_MSG_BYTES * 2)) ) {return (0);} // Right length for ModeA/C, but not enabled for (j = 0; j < l; j += 2) { @@ -1036,7 +1048,7 @@ static int decodeHexMessage(struct client *c, char *hex) { } // record reception time as the time we read it. - clock_gettime(CLOCK_REALTIME, &mm.sysTimestampMsg); + mm.sysTimestampMsg = mstime(); if (l == (MODEAC_MSG_BYTES * 2)) { // ModeA or ModeC Modes.stats_current.remote_received_modeac++; @@ -1060,6 +1072,22 @@ static int decodeHexMessage(struct client *c, char *hex) { useModesMessage(&mm); return (0); } + +__attribute__ ((format (printf,3,0))) static char *safe_vsnprintf(char *p, char *end, const char *format, va_list ap) +{ + p += vsnprintf(p < end ? p : NULL, p < end ? (size_t)(end - p) : 0, format, ap); + return p; +} + + __attribute__ ((format (printf,3,4))) static char *safe_snprintf(char *p, char *end, const char *format, ...) +{ + va_list ap; + va_start(ap, format); + p += vsnprintf(p < end ? p : NULL, p < end ? (size_t)(end - p) : 0, format, ap); + va_end(ap); + return p; +} + // //========================================================================= // @@ -1078,7 +1106,7 @@ static const char *jsonEscapeString(const char *str) { *out++ = '\\'; *out++ = ch; } else if (ch < 32 || ch > 127) { - out += snprintf(out, end - out, "\\u%04x", ch); + out = safe_snprintf(out, end, "\\u%04x", ch); } else { *out++ = ch; } @@ -1090,30 +1118,109 @@ static const char *jsonEscapeString(const char *str) { static char *append_flags(char *p, char *end, struct aircraft *a, datasource_t source) { - p += snprintf(p, end-p, "["); - if (a->squawk_valid.source == source) - p += snprintf(p, end-p, "\"squawk\","); + p = safe_snprintf(p, end, "["); + + char *start = p; if (a->callsign_valid.source == source) - p += snprintf(p, end-p, "\"callsign\","); + p = safe_snprintf(p, end, "\"callsign\","); + if (a->altitude_baro_valid.source == source) + p = safe_snprintf(p, end, "\"altitude\","); + if (a->altitude_geom_valid.source == source) + p = safe_snprintf(p, end, "\"alt_geom\","); + if (a->gs_valid.source == source) + p = safe_snprintf(p, end, "\"gs\","); + if (a->ias_valid.source == source) + p = safe_snprintf(p, end, "\"ias\","); + if (a->tas_valid.source == source) + p = safe_snprintf(p, end, "\"tas\","); + if (a->mach_valid.source == source) + p = safe_snprintf(p, end, "\"mach\","); + if (a->track_valid.source == source) + p = safe_snprintf(p, end, "\"track\","); + if (a->track_rate_valid.source == source) + p = safe_snprintf(p, end, "\"track_rate\","); + if (a->roll_valid.source == source) + p = safe_snprintf(p, end, "\"roll\","); + if (a->mag_heading_valid.source == source) + p = safe_snprintf(p, end, "\"mag_heading\","); + if (a->true_heading_valid.source == source) + p = safe_snprintf(p, end, "\"true_heading\","); + if (a->baro_rate_valid.source == source) + p = safe_snprintf(p, end, "\"baro_rate\","); + if (a->geom_rate_valid.source == source) + p = safe_snprintf(p, end, "\"geom_rate\","); + if (a->squawk_valid.source == source) + p = safe_snprintf(p, end, "\"squawk\","); + if (a->emergency_valid.source == source) + p = safe_snprintf(p, end, "\"emergency\","); + if (a->nav_qnh_valid.source == source) + p = safe_snprintf(p, end, "\"nav_qnh\","); + if (a->nav_altitude_valid.source == source) + p = safe_snprintf(p, end, "\"nav_altitude\","); + if (a->nav_heading_valid.source == source) + p = safe_snprintf(p, end, "\"nav_heading\","); + if (a->nav_modes_valid.source == source) + p = safe_snprintf(p, end, "\"nav_modes\","); if (a->position_valid.source == source) - p += snprintf(p, end-p, "\"lat\",\"lon\","); - if (a->altitude_valid.source == source) - p += snprintf(p, end-p, "\"altitude\","); - if (a->heading_valid.source == source) - p += snprintf(p, end-p, "\"track\","); - if (a->speed_valid.source == source) - p += snprintf(p, end-p, "\"speed\","); - if (a->vert_rate_valid.source == source) - p += snprintf(p, end-p, "\"vert_rate\","); - if (a->category_valid.source == source) - p += snprintf(p, end-p, "\"category\","); - if (p[-1] != '[') + p = safe_snprintf(p, end, "\"lat\",\"lon\",\"nic\",\"rc\","); + if (a->nic_baro_valid.source == source) + p = safe_snprintf(p, end, "\"nic_baro\","); + if (a->nac_p_valid.source == source) + p = safe_snprintf(p, end, "\"nac_p\","); + if (a->nac_v_valid.source == source) + p = safe_snprintf(p, end, "\"nac_v\","); + if (a->sil_valid.source == source) + p = safe_snprintf(p, end, "\"sil\",\"sil_type\","); + if (a->gva_valid.source == source) + p = safe_snprintf(p, end, "\"gva\","); + if (a->sda_valid.source == source) + p = safe_snprintf(p, end, "\"sda\","); + if (p != start) --p; - p += snprintf(p, end-p, "]"); + p = safe_snprintf(p, end, "]"); return p; } -static const char *addrtype_short_string(addrtype_t type) { +static struct { + nav_modes_t flag; + const char *name; +} nav_modes_names[] = { + { NAV_MODE_AUTOPILOT, "autopilot" }, + { NAV_MODE_VNAV, "vnav" }, + { NAV_MODE_ALT_HOLD, "althold" }, + { NAV_MODE_APPROACH, "approach" }, + { NAV_MODE_LNAV, "lnav" }, + { NAV_MODE_TCAS, "tcas" }, + { 0, NULL } +}; + +static char *append_nav_modes(char *p, char *end, nav_modes_t flags, const char *quote, const char *sep) +{ + int first = 1; + for (int i = 0; nav_modes_names[i].name; ++i) { + if (!(flags & nav_modes_names[i].flag)) { + continue; + } + + if (!first) { + p = safe_snprintf(p, end, "%s", sep); + } + + first = 0; + p = safe_snprintf(p, end, "%s%s%s", quote, nav_modes_names[i].name, quote); + } + + return p; +} + +static const char *nav_modes_flags_string(nav_modes_t flags) { + static char buf[256]; + buf[0] = 0; + append_nav_modes(buf, buf + sizeof(buf), flags, "", " "); + return buf; +} + +static const char *addrtype_enum_string(addrtype_t type) { switch (type) { case ADDR_ADSB_ICAO: return "adsb_icao"; @@ -1136,75 +1243,155 @@ static const char *addrtype_short_string(addrtype_t type) { } } +static const char *emergency_enum_string(emergency_t emergency) +{ + switch (emergency) { + case EMERGENCY_NONE: return "none"; + case EMERGENCY_GENERAL: return "general"; + case EMERGENCY_LIFEGUARD: return "lifeguard"; + case EMERGENCY_MINFUEL: return "minfuel"; + case EMERGENCY_NORDO: return "nordo"; + case EMERGENCY_UNLAWFUL: return "unlawful"; + case EMERGENCY_DOWNED: return "downed"; + default: return "reserved"; + } +} + +static const char *sil_type_enum_string(sil_type_t type) +{ + switch (type) { + case SIL_UNKNOWN: return "unknown"; + case SIL_PER_HOUR: return "perhour"; + case SIL_PER_SAMPLE: return "persample"; + default: return "invalid"; + } +} + char *generateAircraftJson(const char *url_path, int *len) { uint64_t now = mstime(); struct aircraft *a; - int buflen = 1024; // The initial buffer is incremented as needed + int buflen = 32768; // The initial buffer is resized as needed char *buf = (char *) malloc(buflen), *p = buf, *end = buf+buflen; + char *line_start; int first = 1; MODES_NOTUSED(url_path); - p += snprintf(p, end-p, - "{ \"now\" : %.1f,\n" - " \"messages\" : %u,\n" - " \"aircraft\" : [", - now / 1000.0, - Modes.stats_current.messages_total + Modes.stats_alltime.messages_total); + _messageNow = now; + + p = safe_snprintf(p, end, + "{ \"now\" : %.1f,\n" + " \"messages\" : %u,\n" + " \"aircraft\" : [", + now / 1000.0, + Modes.stats_current.messages_total + Modes.stats_alltime.messages_total); for (a = Modes.aircrafts; a; a = a->next) { if (a->messages < 2) { // basic filter for bad decodes continue; } - if (first) + if (first) first = 0; else *p++ = ','; - - p += snprintf(p, end-p, "\n {\"hex\":\"%s%06x\"", (a->addr & MODES_NON_ICAO_ADDRESS) ? "~" : "", a->addr & 0xFFFFFF); - if (a->addrtype != ADDR_ADSB_ICAO) - p += snprintf(p, end-p, ",\"type\":\"%s\"", addrtype_short_string(a->addrtype)); - if (trackDataValid(&a->squawk_valid)) - p += snprintf(p, end-p, ",\"squawk\":\"%04x\"", a->squawk); - if (trackDataValid(&a->callsign_valid)) - p += snprintf(p, end-p, ",\"flight\":\"%s\"", jsonEscapeString(a->callsign)); - if (trackDataValid(&a->position_valid)) - p += snprintf(p, end-p, ",\"lat\":%f,\"lon\":%f,\"nucp\":%u,\"seen_pos\":%.1f", a->lat, a->lon, a->pos_nuc, (now - a->position_valid.updated)/1000.0); - if (trackDataValid(&a->airground_valid) && a->airground_valid.source >= SOURCE_MODE_S_CHECKED && a->airground == AG_GROUND) - p += snprintf(p, end-p, ",\"altitude\":\"ground\""); - else if (trackDataValid(&a->altitude_valid)) - p += snprintf(p, end-p, ",\"altitude\":%d", a->altitude); - if (trackDataValid(&a->vert_rate_valid)) - p += snprintf(p, end-p, ",\"vert_rate\":%d", a->vert_rate); - if (trackDataValid(&a->heading_valid)) - p += snprintf(p, end-p, ",\"track\":%d", a->heading); - if (trackDataValid(&a->speed_valid)) - p += snprintf(p, end-p, ",\"speed\":%d", a->speed); - if (trackDataValid(&a->category_valid)) - p += snprintf(p, end-p, ",\"category\":\"%02X\"", a->category); - p += snprintf(p, end-p, ",\"mlat\":"); + retry: + line_start = p; + p = safe_snprintf(p, end, "\n {\"hex\":\"%s%06x\"", (a->addr & MODES_NON_ICAO_ADDRESS) ? "~" : "", a->addr & 0xFFFFFF); + if (a->addrtype != ADDR_ADSB_ICAO) + p = safe_snprintf(p, end, ",\"type\":\"%s\"", addrtype_enum_string(a->addrtype)); + if (trackDataValid(&a->callsign_valid)) + p = safe_snprintf(p, end, ",\"flight\":\"%s\"", jsonEscapeString(a->callsign)); + if (trackDataValid(&a->airground_valid) && a->airground_valid.source >= SOURCE_MODE_S_CHECKED && a->airground == AG_GROUND) + p = safe_snprintf(p, end, ",\"alt_baro\":\"ground\""); + else { + if (trackDataValid(&a->altitude_baro_valid)) + p = safe_snprintf(p, end, ",\"alt_baro\":%d", a->altitude_baro); + if (trackDataValid(&a->altitude_geom_valid)) + p = safe_snprintf(p, end, ",\"alt_geom\":%d", a->altitude_geom); + } + if (trackDataValid(&a->gs_valid)) + p = safe_snprintf(p, end, ",\"gs\":%.1f", a->gs); + if (trackDataValid(&a->ias_valid)) + p = safe_snprintf(p, end, ",\"ias\":%u", a->ias); + if (trackDataValid(&a->tas_valid)) + p = safe_snprintf(p, end, ",\"tas\":%u", a->tas); + if (trackDataValid(&a->mach_valid)) + p = safe_snprintf(p, end, ",\"mach\":%.3f", a->mach); + if (trackDataValid(&a->track_valid)) + p = safe_snprintf(p, end, ",\"track\":%.1f", a->track); + if (trackDataValid(&a->track_rate_valid)) + p = safe_snprintf(p, end, ",\"track_rate\":%.2f", a->track_rate); + if (trackDataValid(&a->roll_valid)) + p = safe_snprintf(p, end, ",\"roll\":%.1f", a->roll); + if (trackDataValid(&a->mag_heading_valid)) + p = safe_snprintf(p, end, ",\"mag_heading\":%.1f", a->mag_heading); + if (trackDataValid(&a->true_heading_valid)) + p = safe_snprintf(p, end, ",\"true_heading\":%.1f", a->true_heading); + if (trackDataValid(&a->baro_rate_valid)) + p = safe_snprintf(p, end, ",\"baro_rate\":%d", a->baro_rate); + if (trackDataValid(&a->geom_rate_valid)) + p = safe_snprintf(p, end, ",\"geom_rate\":%d", a->geom_rate); + if (trackDataValid(&a->squawk_valid)) + p = safe_snprintf(p, end, ",\"squawk\":\"%04x\"", a->squawk); + if (trackDataValid(&a->emergency_valid)) + p = safe_snprintf(p, end, ",\"emergency\":\"%s\"", emergency_enum_string(a->emergency)); + if (a->category != 0) + p = safe_snprintf(p, end, ",\"category\":\"%02X\"", a->category); + if (trackDataValid(&a->nav_qnh_valid)) + p = safe_snprintf(p, end, ",\"nav_qnh\":%.1f", a->nav_qnh); + if (trackDataValid(&a->nav_altitude_valid)) + p = safe_snprintf(p, end, ",\"nav_altitude\":%d", a->nav_altitude); + if (trackDataValid(&a->nav_heading_valid)) + p = safe_snprintf(p, end, ",\"nav_heading\":%.1f", a->nav_heading); + if (trackDataValid(&a->nav_modes_valid)) { + p = safe_snprintf(p, end, ",\"nav_modes\":["); + p = append_nav_modes(p, end, a->nav_modes, "\"", ","); + p = safe_snprintf(p, end, "]"); + } + if (trackDataValid(&a->position_valid)) + p = safe_snprintf(p, end, ",\"lat\":%f,\"lon\":%f,\"nic\":%u,\"rc\":%u,\"seen_pos\":%.1f", a->lat, a->lon, a->pos_nic, a->pos_rc, (now - a->position_valid.updated)/1000.0); + if (a->adsb_version >= 0) + p = safe_snprintf(p, end, ",\"version\":%d", a->adsb_version); + if (trackDataValid(&a->nic_baro_valid)) + p = safe_snprintf(p, end, ",\"nic_baro\":%u", a->nic_baro); + if (trackDataValid(&a->nac_p_valid)) + p = safe_snprintf(p, end, ",\"nac_p\":%u", a->nac_p); + if (trackDataValid(&a->nac_v_valid)) + p = safe_snprintf(p, end, ",\"nac_v\":%u", a->nac_v); + if (trackDataValid(&a->sil_valid)) + p = safe_snprintf(p, end, ",\"sil\":%u", a->sil); + if (a->sil_type != SIL_INVALID) + p = safe_snprintf(p, end, ",\"sil_type\":\"%s\"", sil_type_enum_string(a->sil_type)); + if (trackDataValid(&a->gva_valid)) + p = safe_snprintf(p, end, ",\"gva\":%u", a->gva); + if (trackDataValid(&a->sda_valid)) + p = safe_snprintf(p, end, ",\"sda\":%u", a->sda); + + + p = safe_snprintf(p, end, ",\"mlat\":"); p = append_flags(p, end, a, SOURCE_MLAT); - p += snprintf(p, end-p, ",\"tisb\":"); + p = safe_snprintf(p, end, ",\"tisb\":"); p = append_flags(p, end, a, SOURCE_TISB); - p += snprintf(p, end-p, ",\"messages\":%ld,\"seen\":%.1f,\"rssi\":%.1f}", + p = safe_snprintf(p, end, ",\"messages\":%ld,\"seen\":%.1f,\"rssi\":%.1f}", a->messages, (now - a->seen)/1000.0, 10 * log10((a->signalLevel[0] + a->signalLevel[1] + a->signalLevel[2] + a->signalLevel[3] + a->signalLevel[4] + a->signalLevel[5] + a->signalLevel[6] + a->signalLevel[7] + 1e-5) / 8)); - - // If we're getting near the end of the buffer, expand it. - if ((end - p) < 512) { - int used = p - buf; + + if (p >= end) { + // overran the buffer + int used = line_start - buf; buflen *= 2; buf = (char *) realloc(buf, buflen); p = buf+used; end = buf + buflen; + goto retry; } } - p += snprintf(p, end-p, "\n ]\n}\n"); + p = safe_snprintf(p, end, "\n ]\n}\n"); *len = p-buf; return buf; } @@ -1216,61 +1403,61 @@ static char * appendStatsJson(char *p, { int i; - p += snprintf(p, end-p, - "\"%s\":{\"start\":%.1f,\"end\":%.1f", - key, - st->start / 1000.0, - st->end / 1000.0); + p = safe_snprintf(p, end, + "\"%s\":{\"start\":%.1f,\"end\":%.1f", + key, + st->start / 1000.0, + st->end / 1000.0); if (!Modes.net_only) { - p += snprintf(p, end-p, - ",\"local\":{\"samples_processed\":%llu" - ",\"samples_dropped\":%llu" - ",\"modeac\":%u" - ",\"modes\":%u" - ",\"bad\":%u" - ",\"unknown_icao\":%u", - (unsigned long long)st->samples_processed, - (unsigned long long)st->samples_dropped, - st->demod_modeac, - st->demod_preambles, - st->demod_rejected_bad, - st->demod_rejected_unknown_icao); + p = safe_snprintf(p, end, + ",\"local\":{\"samples_processed\":%llu" + ",\"samples_dropped\":%llu" + ",\"modeac\":%u" + ",\"modes\":%u" + ",\"bad\":%u" + ",\"unknown_icao\":%u", + (unsigned long long)st->samples_processed, + (unsigned long long)st->samples_dropped, + st->demod_modeac, + st->demod_preambles, + st->demod_rejected_bad, + st->demod_rejected_unknown_icao); for (i=0; i <= Modes.nfix_crc; ++i) { - if (i == 0) p += snprintf(p, end-p, ",\"accepted\":[%u", st->demod_accepted[i]); - else p += snprintf(p, end-p, ",%u", st->demod_accepted[i]); + if (i == 0) p = safe_snprintf(p, end, ",\"accepted\":[%u", st->demod_accepted[i]); + else p = safe_snprintf(p, end, ",%u", st->demod_accepted[i]); } - p += snprintf(p, end-p, "]"); + p = safe_snprintf(p, end, "]"); if (st->signal_power_sum > 0 && st->signal_power_count > 0) - p += snprintf(p, end-p,",\"signal\":%.1f", 10 * log10(st->signal_power_sum / st->signal_power_count)); + p = safe_snprintf(p, end, ",\"signal\":%.1f", 10 * log10(st->signal_power_sum / st->signal_power_count)); if (st->noise_power_sum > 0 && st->noise_power_count > 0) - p += snprintf(p, end-p,",\"noise\":%.1f", 10 * log10(st->noise_power_sum / st->noise_power_count)); + p = safe_snprintf(p, end, ",\"noise\":%.1f", 10 * log10(st->noise_power_sum / st->noise_power_count)); if (st->peak_signal_power > 0) - p += snprintf(p, end-p,",\"peak_signal\":%.1f", 10 * log10(st->peak_signal_power)); + p = safe_snprintf(p, end, ",\"peak_signal\":%.1f", 10 * log10(st->peak_signal_power)); - p += snprintf(p, end-p,",\"strong_signals\":%d}", st->strong_signal_count); + p = safe_snprintf(p, end, ",\"strong_signals\":%d}", st->strong_signal_count); } if (Modes.net) { - p += snprintf(p, end-p, - ",\"remote\":{\"modeac\":%u" - ",\"modes\":%u" - ",\"bad\":%u" - ",\"unknown_icao\":%u", - st->remote_received_modeac, - st->remote_received_modes, - st->remote_rejected_bad, - st->remote_rejected_unknown_icao); + p = safe_snprintf(p, end, + ",\"remote\":{\"modeac\":%u" + ",\"modes\":%u" + ",\"bad\":%u" + ",\"unknown_icao\":%u", + st->remote_received_modeac, + st->remote_received_modes, + st->remote_rejected_bad, + st->remote_rejected_unknown_icao); for (i=0; i <= Modes.nfix_crc; ++i) { - if (i == 0) p += snprintf(p, end-p, ",\"accepted\":[%u", st->remote_accepted[i]); - else p += snprintf(p, end-p, ",%u", st->remote_accepted[i]); + if (i == 0) p = safe_snprintf(p, end, ",\"accepted\":[%u", st->remote_accepted[i]); + else p = safe_snprintf(p, end, ",%u", st->remote_accepted[i]); } - p += snprintf(p, end-p, "]}"); + p = safe_snprintf(p, end, "]}"); } { @@ -1278,74 +1465,74 @@ static char * appendStatsJson(char *p, uint64_t reader_cpu_millis = (uint64_t)st->reader_cpu.tv_sec*1000UL + st->reader_cpu.tv_nsec/1000000UL; uint64_t background_cpu_millis = (uint64_t)st->background_cpu.tv_sec*1000UL + st->background_cpu.tv_nsec/1000000UL; - p += snprintf(p, end-p, - ",\"cpr\":{\"surface\":%u" - ",\"airborne\":%u" - ",\"global_ok\":%u" - ",\"global_bad\":%u" - ",\"global_range\":%u" - ",\"global_speed\":%u" - ",\"global_skipped\":%u" - ",\"local_ok\":%u" - ",\"local_aircraft_relative\":%u" - ",\"local_receiver_relative\":%u" - ",\"local_skipped\":%u" - ",\"local_range\":%u" - ",\"local_speed\":%u" - ",\"filtered\":%u}" - ",\"altitude_suppressed\":%u" - ",\"cpu\":{\"demod\":%llu,\"reader\":%llu,\"background\":%llu}" - ",\"tracks\":{\"all\":%u" - ",\"single_message\":%u}" - ",\"messages\":%u}", - st->cpr_surface, - st->cpr_airborne, - st->cpr_global_ok, - st->cpr_global_bad, - st->cpr_global_range_checks, - st->cpr_global_speed_checks, - st->cpr_global_skipped, - st->cpr_local_ok, - st->cpr_local_aircraft_relative, - st->cpr_local_receiver_relative, - st->cpr_local_skipped, - st->cpr_local_range_checks, - st->cpr_local_speed_checks, - st->cpr_filtered, - st->suppressed_altitude_messages, - (unsigned long long)demod_cpu_millis, - (unsigned long long)reader_cpu_millis, - (unsigned long long)background_cpu_millis, - st->unique_aircraft, - st->single_message_aircraft, - st->messages_total); + p = safe_snprintf(p, end, + ",\"cpr\":{\"surface\":%u" + ",\"airborne\":%u" + ",\"global_ok\":%u" + ",\"global_bad\":%u" + ",\"global_range\":%u" + ",\"global_speed\":%u" + ",\"global_skipped\":%u" + ",\"local_ok\":%u" + ",\"local_aircraft_relative\":%u" + ",\"local_receiver_relative\":%u" + ",\"local_skipped\":%u" + ",\"local_range\":%u" + ",\"local_speed\":%u" + ",\"filtered\":%u}" + ",\"altitude_suppressed\":%u" + ",\"cpu\":{\"demod\":%llu,\"reader\":%llu,\"background\":%llu}" + ",\"tracks\":{\"all\":%u" + ",\"single_message\":%u}" + ",\"messages\":%u}", + st->cpr_surface, + st->cpr_airborne, + st->cpr_global_ok, + st->cpr_global_bad, + st->cpr_global_range_checks, + st->cpr_global_speed_checks, + st->cpr_global_skipped, + st->cpr_local_ok, + st->cpr_local_aircraft_relative, + st->cpr_local_receiver_relative, + st->cpr_local_skipped, + st->cpr_local_range_checks, + st->cpr_local_speed_checks, + st->cpr_filtered, + st->suppressed_altitude_messages, + (unsigned long long)demod_cpu_millis, + (unsigned long long)reader_cpu_millis, + (unsigned long long)background_cpu_millis, + st->unique_aircraft, + st->single_message_aircraft, + st->messages_total); } return p; } - + char *generateStatsJson(const char *url_path, int *len) { struct stats add; char *buf = (char *) malloc(4096), *p = buf, *end = buf + 4096; MODES_NOTUSED(url_path); - p += snprintf(p, end-p, "{\n"); + p = safe_snprintf(p, end, "{\n"); p = appendStatsJson(p, end, &Modes.stats_current, "latest"); - p += snprintf(p, end-p, ",\n"); + p = safe_snprintf(p, end, ",\n"); p = appendStatsJson(p, end, &Modes.stats_1min[Modes.stats_latest_1min], "last1min"); - p += snprintf(p, end-p, ",\n"); + p = safe_snprintf(p, end, ",\n"); p = appendStatsJson(p, end, &Modes.stats_5min, "last5min"); - p += snprintf(p, end-p, ",\n"); + p = safe_snprintf(p, end, ",\n"); p = appendStatsJson(p, end, &Modes.stats_15min, "last15min"); - p += snprintf(p, end-p, ",\n"); + p = safe_snprintf(p, end, ",\n"); add_stats(&Modes.stats_alltime, &Modes.stats_current, &add); p = appendStatsJson(p, end, &add, "total"); - p += snprintf(p, end-p, "\n}\n"); + p = safe_snprintf(p, end, "\n}\n"); assert(p <= end); @@ -1431,7 +1618,7 @@ void writeJsonToFile(const char *file, char * (*generator) (const char *,int*)) fd = mkstemp(tmppath); if (fd < 0) return; - + mask = umask(0); umask(mask); fchmod(fd, 0644 & ~mask); @@ -1662,7 +1849,40 @@ static void modesReadFromClient(struct client *c) { } } -#define TSV_MAX_PACKET_SIZE 275 +__attribute__ ((format (printf,4,5))) static char *appendFATSV(char *p, char *end, const char *field, const char *format, ...) +{ + va_list ap; + va_start(ap, format); + + p = safe_snprintf(p, end, "%s\t", field); + p = safe_vsnprintf(p, end, format, ap); + p = safe_snprintf(p, end, "\t"); + + va_end(ap); + return p; +} + +#define TSV_MAX_PACKET_SIZE 600 +#define TSV_VERSION 2 + +void writeFATSVHeader() +{ + char *p = prepareWrite(&Modes.fatsv_out, TSV_MAX_PACKET_SIZE); + if (!p) + return; + + char *end = p + TSV_MAX_PACKET_SIZE; + + p = appendFATSV(p, end, "clock", "%" PRIu64, mstime() / 1000); + p = appendFATSV(p, end, "tsv_version", "%u", TSV_VERSION); + --p; // remove last tab + p = safe_snprintf(p, end, "\n"); + + if (p <= end) + completeWrite(&Modes.fatsv_out, p); + else + fprintf(stderr, "fatsv: output too large (max %d, overran by %d)\n", TSV_MAX_PACKET_SIZE, (int) (p - end)); +} static void writeFATSVPositionUpdate(float lat, float lon, float alt) { @@ -1680,22 +1900,20 @@ static void writeFATSVPositionUpdate(float lat, float lon, float alt) return; char *end = p + TSV_MAX_PACKET_SIZE; -# define bufsize(_p,_e) ((_p) >= (_e) ? (size_t)0 : (size_t)((_e) - (_p))) - p += snprintf(p, bufsize(p, end), "clock\t%" PRIu64, mstime() / 1000); - p += snprintf(p, bufsize(p, end), "\ttype\t%s", "location_update"); - p += snprintf(p, bufsize(p, end), "\tlat\t%.5f", lat); - p += snprintf(p, bufsize(p, end), "\tlon\t%.5f", lon); - p += snprintf(p, bufsize(p, end), "\talt\t%.0f", alt); - p += snprintf(p, bufsize(p, end), "\taltref\t%s", "egm96_meters"); - p += snprintf(p, bufsize(p, end), "\n"); + p = appendFATSV(p, end, "clock", "%" PRIu64, messageNow() / 1000); + p = appendFATSV(p, end, "type", "%s", "location_update"); + p = appendFATSV(p, end, "lat", "%.5f", lat); + p = appendFATSV(p, end, "lon", "%.5f", lon); + p = appendFATSV(p, end, "alt", "%.0f", alt); + p = appendFATSV(p, end, "altref", "%s", "egm96_meters"); + --p; // remove last tab + p = safe_snprintf(p, end, "\n"); if (p <= end) completeWrite(&Modes.fatsv_out, p); else fprintf(stderr, "fatsv: output too large (max %d, overran by %d)\n", TSV_MAX_PACKET_SIZE, (int) (p - end)); - -# undef bufsize } static void writeFATSVEventMessage(struct modesMessage *mm, const char *datafield, unsigned char *data, size_t len) @@ -1705,26 +1923,18 @@ static void writeFATSVEventMessage(struct modesMessage *mm, const char *datafiel return; char *end = p + TSV_MAX_PACKET_SIZE; -# define bufsize(_p,_e) ((_p) >= (_e) ? (size_t)0 : (size_t)((_e) - (_p))) - - p += snprintf(p, bufsize(p, end), "clock\t%" PRIu64, mstime() / 1000); - - if (mm->addr & MODES_NON_ICAO_ADDRESS) { - p += snprintf(p, bufsize(p, end), "\totherid\t%06X", mm->addr & 0xFFFFFF); - } else { - p += snprintf(p, bufsize(p, end), "\thexid\t%06X", mm->addr); - } + p = appendFATSV(p, end, "clock", "%" PRIu64, messageNow() / 1000); + p = appendFATSV(p, end, (mm->addr & MODES_NON_ICAO_ADDRESS) ? "otherid" : "hexid", "%06X", mm->addr & 0xFFFFFF); if (mm->addrtype != ADDR_ADSB_ICAO) { - p += snprintf(p, bufsize(p, end), "\taddrtype\t%s", addrtype_short_string(mm->addrtype)); + p = appendFATSV(p, end, "addrtype", "%s", addrtype_enum_string(mm->addrtype)); } - p += snprintf(p, bufsize(p, end), "\t%s\t", datafield); + p = safe_snprintf(p, end, "%s\t", datafield); for (size_t i = 0; i < len; ++i) { - p += snprintf(p, bufsize(p, end), "%02X", data[i]); + p = safe_snprintf(p, end, "%02X", data[i]); } - - p += snprintf(p, bufsize(p, end), "\n"); + p = safe_snprintf(p, end, "\n"); if (p <= end) completeWrite(&Modes.fatsv_out, p); @@ -1747,23 +1957,28 @@ static void writeFATSVEvent(struct modesMessage *mm, struct aircraft *a) switch (mm->msgtype) { case 20: 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 - // - // BDS 1,0: data link capability report - // BDS 3,0: ACAS RA report - if (mm->MB[0] == 0x10 && memcmp(mm->MB, a->fatsv_emitted_bds_10, 7) != 0) { - memcpy(a->fatsv_emitted_bds_10, mm->MB, 7); - writeFATSVEventMessage(mm, "datalink_caps", mm->MB, 7); - } + switch (mm->commb_format) { + case COMMB_DATALINK_CAPS: + // BDS 1,0: data link capability report + if (memcmp(mm->MB, a->fatsv_emitted_bds_10, 7) != 0) { + memcpy(a->fatsv_emitted_bds_10, 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) { - memcpy(a->fatsv_emitted_bds_30, mm->MB, 7); - writeFATSVEventMessage(mm, "commb_acas_ra", mm->MB, 7); - } + 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); + writeFATSVEventMessage(mm, "commb_acas_ra", mm->MB, 7); + } + break; + default: + // nothing + break; + } break; case 17: @@ -1778,52 +1993,97 @@ static void writeFATSVEvent(struct modesMessage *mm, struct aircraft *a) // aircraft operational status memcpy(a->fatsv_emitted_es_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; } } -typedef enum { - TISB_IDENT = 1, - TISB_SQUAWK = 2, - TISB_ALTITUDE = 4, - TISB_ALTITUDE_GNSS = 8, - TISB_SPEED = 16, - TISB_SPEED_IAS = 32, - TISB_SPEED_TAS = 64, - TISB_POSITION = 128, - TISB_HEADING = 256, - TISB_HEADING_MAGNETIC = 512, - TISB_AIRGROUND = 1024, - TISB_CATEGORY = 2048 -} tisb_flags; - static inline unsigned unsigned_difference(unsigned v1, unsigned v2) { 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); } + __attribute__ ((format (printf,6,7))) static char *appendFATSVMeta(char *p, char *end, const char *field, struct aircraft *a, const data_validity *source, const char *format, ...) +{ + const char *sourcetype; + switch (source->source) { + case SOURCE_MODE_S: + sourcetype = "U"; + break; + case SOURCE_MODE_S_CHECKED: + sourcetype = "S"; + break; + case SOURCE_TISB: + sourcetype = "T"; + break; + case SOURCE_ADSB: + sourcetype = "A"; + break; + default: + // don't want to forward data sourced from these + return p; + } + + if (!trackDataValid(source)) { + // expired data + return p; + } + + if (source->updated > messageNow()) { + // data in the future + return p; + } + + if (source->updated < a->fatsv_last_emitted) { + // not updated since last time + return p; + } + + uint64_t age = (messageNow() - source->updated) / 1000; + if (age > 255) { + // too old + return p; + } + + p = safe_snprintf(p, end, "%s\t", field); + + va_list ap; + va_start(ap, format); + p = safe_vsnprintf(p, end, format, ap); + va_end(ap); + + p = safe_snprintf(p, end, " %" PRIu64 " %s\t", age, sourcetype); + return p; +} + +static const char *airground_enum_string(airground_t ag) +{ + switch (ag) { + case AG_AIRBORNE: + return "A+"; + case AG_GROUND: + return "G+"; + default: + return "?"; + } +} + static void writeFATSV() { struct aircraft *a; - uint64_t now; static uint64_t next_update; if (!Modes.fatsv_out.service || !Modes.fatsv_out.service->connections) { return; // not enabled or no active connections } - now = mstime(); + uint64_t now = mstime(); if (now < next_update) { return; } @@ -1832,25 +2092,6 @@ static void writeFATSV() next_update = now + 1000; 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; - - int useful = 0; - int changed = 0; - tisb_flags tisb = 0; - - char *p, *end; - if (a->messages < 2) // basic filter for bad decodes continue; @@ -1859,62 +2100,65 @@ static void writeFATSV() 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 - altGNSSValid = trackDataValidEx(&a->altitude_gnss_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 - positionValid = trackDataValidEx(&a->position_valid, now, 15000, SOURCE_MODE_S_CHECKED); - headingValid = trackDataValidEx(&a->heading_valid, now, 15000, SOURCE_MODE_S_CHECKED); - headingMagValid = trackDataValidEx(&a->heading_magnetic_valid, now, 15000, SOURCE_MODE_S_CHECKED); - speedValid = trackDataValidEx(&a->speed_valid, now, 15000, SOURCE_MODE_S_CHECKED); - speedIASValid = trackDataValidEx(&a->speed_ias_valid, now, 15000, SOURCE_MODE_S_CHECKED); - speedTASValid = trackDataValidEx(&a->speed_tas_valid, now, 15000, SOURCE_MODE_S_CHECKED); - categoryValid = trackDataValidEx(&a->category_valid, now, 15000, SOURCE_MODE_S_CHECKED); + // Pretend we are "processing a message" so the validity checks work as expected + _messageNow = a->seen; + + // some special cases: + int altValid = trackDataValid(&a->altitude_baro_valid); + int airgroundValid = trackDataValid(&a->airground_valid) && a->airground_valid.source >= SOURCE_MODE_S_CHECKED; // for non-ADS-B transponders, only trust DF11 CA field + int gsValid = trackDataValid(&a->gs_valid); + int squawkValid = trackDataValid(&a->squawk_valid); + int callsignValid = trackDataValid(&a->callsign_valid) && strcmp(a->callsign, " ") != 0; + int positionValid = trackDataValid(&a->position_valid); // 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 // altitude, so a corrupted Mode S altitude response from some other in-the-air AC // might be taken as the "best available altitude" and produce e.g. "airGround G+ alt 31000". - if (airgroundValid && a->airground == AG_GROUND && a->altitude_valid.source < SOURCE_MODE_S_CHECKED) + if (airgroundValid && a->airground == AG_GROUND && a->altitude_baro_valid.source < SOURCE_MODE_S_CHECKED) altValid = 0; // if it hasn't changed altitude, heading, or speed much, // don't update so often - changed = 0; - if (altValid && abs(a->altitude - a->fatsv_emitted_altitude) >= 50) { - changed = 1; - } - if (altGNSSValid && abs(a->altitude_gnss - a->fatsv_emitted_altitude_gnss) >= 50) { - changed = 1; - } - if (headingValid && heading_difference(a->heading, a->fatsv_emitted_heading) >= 2) { - changed = 1; - } - if (headingMagValid && heading_difference(a->heading_magnetic, a->fatsv_emitted_heading_magnetic) >= 2) { - changed = 1; - } - if (speedValid && unsigned_difference(a->speed, a->fatsv_emitted_speed) >= 25) { - changed = 1; - } - if (speedIASValid && unsigned_difference(a->speed_ias, a->fatsv_emitted_speed_ias) >= 25) { - changed = 1; - } - if (speedTASValid && unsigned_difference(a->speed_tas, a->fatsv_emitted_speed_tas) >= 25) { - changed = 1; - } + int changed = + (altValid && abs(a->altitude_baro - a->fatsv_emitted_altitude_baro) >= 50) || + (trackDataValid(&a->altitude_geom_valid) && abs(a->altitude_geom - a->fatsv_emitted_altitude_geom) >= 50) || + (trackDataValid(&a->baro_rate_valid) && abs(a->baro_rate - a->fatsv_emitted_baro_rate) > 500) || + (trackDataValid(&a->geom_rate_valid) && abs(a->geom_rate - a->fatsv_emitted_geom_rate) > 500) || + (trackDataValid(&a->track_valid) && heading_difference(a->track, a->fatsv_emitted_track) >= 2) || + (trackDataValid(&a->track_rate_valid) && fabs(a->track_rate - a->fatsv_emitted_track_rate) >= 0.5) || + (trackDataValid(&a->roll_valid) && fabs(a->roll - a->fatsv_emitted_roll) >= 5.0) || + (trackDataValid(&a->mag_heading_valid) && heading_difference(a->mag_heading, a->fatsv_emitted_mag_heading) >= 2) || + (trackDataValid(&a->true_heading_valid) && heading_difference(a->true_heading, a->fatsv_emitted_true_heading) >= 2) || + (gsValid && fabs(a->gs - a->fatsv_emitted_gs) >= 25) || + (trackDataValid(&a->ias_valid) && unsigned_difference(a->ias, a->fatsv_emitted_ias) >= 25) || + (trackDataValid(&a->tas_valid) && unsigned_difference(a->tas, a->fatsv_emitted_tas) >= 25) || + (trackDataValid(&a->mach_valid) && fabs(a->mach - a->fatsv_emitted_mach) >= 0.02); - if (airgroundValid && ((a->airground == AG_AIRBORNE && a->fatsv_emitted_airground == AG_GROUND) || - (a->airground == AG_GROUND && a->fatsv_emitted_airground == AG_AIRBORNE))) { - // Air-ground transition, handle it immediately. + int immediate = + (trackDataValid(&a->nav_altitude_valid) && unsigned_difference(a->nav_altitude, a->fatsv_emitted_nav_altitude) > 50) || + (trackDataValid(&a->nav_heading_valid) && heading_difference(a->nav_heading, a->fatsv_emitted_nav_heading) > 2) || + (trackDataValid(&a->nav_modes_valid) && a->nav_modes != a->fatsv_emitted_nav_modes) || + (trackDataValid(&a->nav_qnh_valid) && fabs(a->nav_qnh - a->fatsv_emitted_nav_qnh) > 0.8) || // 0.8 is the ES message resolution + (callsignValid && strcmp(a->callsign, a->fatsv_emitted_callsign) != 0) || + (airgroundValid && a->airground == AG_AIRBORNE && a->fatsv_emitted_airground == AG_GROUND) || + (airgroundValid && a->airground == AG_GROUND && a->fatsv_emitted_airground == AG_AIRBORNE) || + (squawkValid && a->squawk != a->fatsv_emitted_squawk) || + (trackDataValid(&a->emergency_valid) && a->emergency != a->fatsv_emitted_emergency); + + uint64_t minAge; + if (immediate) { + // a change we want to emit right away minAge = 0; } else if (!positionValid) { // don't send mode S very often minAge = 30000; } else if ((airgroundValid && a->airground == AG_GROUND) || - (altValid && a->altitude < 500 && (!speedValid || a->speed < 200)) || - (speedValid && a->speed < 100 && (!altValid || a->altitude < 1000))) { + (altValid && a->altitude_baro < 500 && (!gsValid || a->gs < 200)) || + (gsValid && a->gs < 100 && (!altValid || a->altitude_baro < 1000))) { // we are probably on the ground, increase the update rate minAge = 1000; - } else if (!altValid || a->altitude < 10000) { + } else if (!altValid || a->altitude_baro < 10000) { // Below 10000 feet, emit up to every 5s when changing, 10s otherwise minAge = (changed ? 5000 : 10000); } else { @@ -1925,143 +2169,127 @@ static void writeFATSV() if ((now - a->fatsv_last_emitted) < minAge) continue; - p = prepareWrite(&Modes.fatsv_out, TSV_MAX_PACKET_SIZE); + char *p = prepareWrite(&Modes.fatsv_out, TSV_MAX_PACKET_SIZE); if (!p) return; + char *end = p + TSV_MAX_PACKET_SIZE; - end = p + TSV_MAX_PACKET_SIZE; -# define bufsize(_p,_e) ((_p) >= (_e) ? (size_t)0 : (size_t)((_e) - (_p))) + p = appendFATSV(p, end, "clock", "%" PRIu64, messageNow() / 1000); + p = appendFATSV(p, end, (a->addr & MODES_NON_ICAO_ADDRESS) ? "otherid" : "hexid", "%06X", a->addr & 0xFFFFFF); - p += snprintf(p, bufsize(p, end), "clock\t%" PRIu64, (uint64_t)(a->seen / 1000)); + // for fields we only emit on change, + // occasionally re-emit them all + int forceEmit = (now - a->fatsv_last_force_emit) > 600000; - if (a->addr & MODES_NON_ICAO_ADDRESS) { - p += snprintf(p, bufsize(p, end), "\totherid\t%06X", a->addr & 0xFFFFFF); - } else { - p += snprintf(p, bufsize(p, end), "\thexid\t%06X", a->addr); + // these don't change often / at all, only emit when they change + if (forceEmit || a->addrtype != a->fatsv_emitted_addrtype) { + p = appendFATSV(p, end, "addrtype", "%s", addrtype_enum_string(a->addrtype)); + } + if (forceEmit || a->adsb_version != a->fatsv_emitted_adsb_version) { + p = appendFATSV(p, end, "adsb_version", "%d", a->adsb_version); + } + if (forceEmit || a->category != a->fatsv_emitted_category) { + p = appendFATSV(p, end, "category", "%02X", a->category); + } + if (trackDataValid(&a->nac_p_valid) && (forceEmit || a->nac_p != a->fatsv_emitted_nac_p)) { + p = appendFATSVMeta(p, end, "nac_p", a, &a->nac_p_valid, "%u", a->nac_p); + } + if (trackDataValid(&a->nac_v_valid) && (forceEmit || a->nac_v != a->fatsv_emitted_nac_v)) { + p = appendFATSVMeta(p, end, "nac_v", a, &a->nac_v_valid, "%u", a->nac_v); + } + if (trackDataValid(&a->sil_valid) && (forceEmit || a->sil != a->fatsv_emitted_sil)) { + p = appendFATSVMeta(p, end, "sil", a, &a->sil_valid, "%u", a->sil); + } + if (trackDataValid(&a->sil_valid) && (forceEmit || a->sil_type != a->fatsv_emitted_sil_type)) { + p = appendFATSVMeta(p, end, "sil_type", a, &a->sil_valid, "%s", sil_type_enum_string(a->sil_type)); + } + if (trackDataValid(&a->nic_baro_valid) && (forceEmit || a->nic_baro != a->fatsv_emitted_nic_baro)) { + p = appendFATSVMeta(p, end, "nic_baro", a, &a->nic_baro_valid, "%u", a->nic_baro); } - if (a->addrtype != ADDR_ADSB_ICAO) { - 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) { - p += snprintf(p, bufsize(p,end), "\tident\t%s", a->callsign); - switch (a->callsign_valid.source) { - case SOURCE_MODE_S: - p += snprintf(p, bufsize(p,end), "\tiSource\tmodes"); - break; - case SOURCE_ADSB: - p += snprintf(p, bufsize(p,end), "\tiSource\tadsb"); - break; - case SOURCE_TISB: - p += snprintf(p, bufsize(p,end), "\tiSource\ttisb"); - break; - default: - p += snprintf(p, bufsize(p,end), "\tiSource\tunknown"); - break; - } - - useful = 1; - tisb |= (a->callsign_valid.source == SOURCE_TISB) ? TISB_IDENT : 0; - } - - if (trackDataValidEx(&a->squawk_valid, now, 35000, SOURCE_MODE_S) && a->squawk_valid.updated > a->fatsv_last_emitted) { - p += snprintf(p, bufsize(p,end), "\tsquawk\t%04x", a->squawk); - useful = 1; - 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 - if (altValid && a->altitude_valid.updated > a->fatsv_last_emitted) { - p += snprintf(p, bufsize(p,end), "\talt\t%d", a->altitude); - a->fatsv_emitted_altitude = a->altitude; - useful = 1; - tisb |= (a->altitude_valid.source == SOURCE_TISB) ? TISB_ALTITUDE : 0; + char *dataStart = p; + + // special cases + if (airgroundValid) + p = appendFATSVMeta(p, end, "airGround", a, &a->airground_valid, "%s", airground_enum_string(a->airground)); + if (squawkValid) + p = appendFATSVMeta(p, end, "squawk", a, &a->squawk_valid, "%04x", a->squawk); + if (callsignValid) + p = appendFATSVMeta(p, end, "ident", a, &a->callsign_valid, "{%s}", a->callsign); + if (altValid) + p = appendFATSVMeta(p, end, "alt", a, &a->altitude_baro_valid, "%d", a->altitude_baro); + if (positionValid) { + p = appendFATSVMeta(p, end, "position", a, &a->position_valid, "{%.5f %.5f %u %u}", a->lat, a->lon, a->pos_nic, a->pos_rc); } - if (altGNSSValid && a->altitude_gnss_valid.updated > a->fatsv_last_emitted) { - p += snprintf(p, bufsize(p,end), "\talt_gnss\t%d", a->altitude_gnss); - a->fatsv_emitted_altitude_gnss = a->altitude_gnss; - useful = 1; - tisb |= (a->altitude_gnss_valid.source == SOURCE_TISB) ? TISB_ALTITUDE_GNSS : 0; - } - - if (speedValid && a->speed_valid.updated > a->fatsv_last_emitted) { - p += snprintf(p, bufsize(p,end), "\tspeed\t%d", a->speed); - a->fatsv_emitted_speed = a->speed; - useful = 1; - tisb |= (a->speed_valid.source == SOURCE_TISB) ? TISB_SPEED : 0; - } - - if (speedIASValid && a->speed_ias_valid.updated > a->fatsv_last_emitted) { - p += snprintf(p, bufsize(p,end), "\tspeed_ias\t%d", a->speed_ias); - a->fatsv_emitted_speed_ias = a->speed_ias; - 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) { - p += snprintf(p, bufsize(p,end), "\tspeed_tas\t%d", a->speed_tas); - a->fatsv_emitted_speed_tas = a->speed_tas; - useful = 1; - tisb |= (a->speed_tas_valid.source == SOURCE_TISB) ? TISB_SPEED_TAS : 0; - } - - if (positionValid && a->position_valid.updated > a->fatsv_last_emitted) { - p += snprintf(p, bufsize(p,end), "\tlat\t%.5f\tlon\t%.5f", a->lat, a->lon); - useful = 1; - tisb |= (a->position_valid.source == SOURCE_TISB) ? TISB_POSITION : 0; - } - - if (headingValid && a->heading_valid.updated > a->fatsv_last_emitted) { - p += snprintf(p, bufsize(p,end), "\theading\t%d", a->heading); - a->fatsv_emitted_heading = a->heading; - useful = 1; - tisb |= (a->heading_valid.source == SOURCE_TISB) ? TISB_HEADING : 0; - } - - if (headingMagValid && a->heading_magnetic_valid.updated > a->fatsv_last_emitted) { - p += snprintf(p, bufsize(p,end), "\theading_magnetic\t%d", a->heading_magnetic); - a->fatsv_emitted_heading_magnetic = a->heading_magnetic; - useful = 1; - tisb |= (a->heading_magnetic_valid.source == SOURCE_TISB) ? TISB_HEADING_MAGNETIC : 0; - } - - if (airgroundValid && (a->airground == AG_GROUND || a->airground == AG_AIRBORNE) && a->airground_valid.updated > a->fatsv_last_emitted) { - p += snprintf(p, bufsize(p,end), "\tairGround\t%s", a->airground == AG_GROUND ? "G+" : "A+"); - a->fatsv_emitted_airground = a->airground; - useful = 1; - tisb |= (a->airground_valid.source == SOURCE_TISB) ? TISB_AIRGROUND : 0; - } - - if (categoryValid && (a->category & 0xF0) != 0xA0 && a->category_valid.updated > a->fatsv_last_emitted) { - // interesting category, not a regular aircraft - p += snprintf(p, bufsize(p,end), "\tcategory\t%02X", a->category); - useful = 1; - tisb |= (a->category_valid.source == SOURCE_TISB) ? TISB_CATEGORY : 0; - } + p = appendFATSVMeta(p, end, "alt_gnss", a, &a->altitude_geom_valid, "%d", a->altitude_geom); + p = appendFATSVMeta(p, end, "vrate", a, &a->baro_rate_valid, "%d", a->baro_rate); + p = appendFATSVMeta(p, end, "vrate_geom", a, &a->geom_rate_valid, "%d", a->geom_rate); + p = appendFATSVMeta(p, end, "speed", a, &a->gs_valid, "%.1f", a->gs); + p = appendFATSVMeta(p, end, "speed_ias", a, &a->ias_valid, "%u", a->ias); + p = appendFATSVMeta(p, end, "speed_tas", a, &a->tas_valid, "%u", a->tas); + p = appendFATSVMeta(p, end, "mach", a, &a->mach_valid, "%.3f", a->mach); + p = appendFATSVMeta(p, end, "track", a, &a->track_valid, "%.1f", a->track); + p = appendFATSVMeta(p, end, "track_rate", a, &a->track_rate_valid, "%.2f", a->track_rate); + p = appendFATSVMeta(p, end, "roll", a, &a->roll_valid, "%.1f", a->roll); + p = appendFATSVMeta(p, end, "heading_magnetic", a, &a->mag_heading_valid, "%.1f", a->mag_heading); + p = appendFATSVMeta(p, end, "heading_true", a, &a->true_heading_valid, "%.1f", a->true_heading); + p = appendFATSVMeta(p, end, "nav_alt", a, &a->nav_altitude_valid, "%u", a->nav_altitude); + p = appendFATSVMeta(p, end, "nav_heading", a, &a->nav_heading_valid, "%.1f", a->nav_heading); + p = appendFATSVMeta(p, end, "nav_modes", a, &a->nav_modes_valid, "{%s}", nav_modes_flags_string(a->nav_modes)); + p = appendFATSVMeta(p, end, "nav_qnh", a, &a->nav_qnh_valid, "%.1f", a->nav_qnh); + p = appendFATSVMeta(p, end, "emergency", a, &a->emergency_valid, "%s", emergency_enum_string(a->emergency)); // if we didn't get anything interesting, bail out. // We don't need to do anything special to unwind prepareWrite(). - if (!useful) { + if (p == dataStart) { continue; } - if (tisb != 0) { - p += snprintf(p, bufsize(p,end), "\ttisb\t%d", (int)tisb); - } - - p += snprintf(p, bufsize(p,end), "\n"); + --p; // remove last tab + p = safe_snprintf(p, end, "\n"); if (p <= end) completeWrite(&Modes.fatsv_out, p); else fprintf(stderr, "fatsv: output too large (max %d, overran by %d)\n", TSV_MAX_PACKET_SIZE, (int) (p - end)); -# undef bufsize + a->fatsv_emitted_altitude_baro = a->altitude_baro; + a->fatsv_emitted_altitude_geom = a->altitude_geom; + a->fatsv_emitted_baro_rate = a->baro_rate; + a->fatsv_emitted_geom_rate = a->geom_rate; + a->fatsv_emitted_gs = a->gs; + a->fatsv_emitted_ias = a->ias; + a->fatsv_emitted_tas = a->tas; + a->fatsv_emitted_mach = a->mach; + a->fatsv_emitted_track = a->track; + a->fatsv_emitted_track_rate = a->track_rate; + a->fatsv_emitted_roll = a->roll; + a->fatsv_emitted_mag_heading = a->mag_heading; + a->fatsv_emitted_true_heading = a->true_heading; + a->fatsv_emitted_airground = a->airground; + a->fatsv_emitted_nav_altitude = a->nav_altitude; + a->fatsv_emitted_nav_heading = a->nav_heading; + a->fatsv_emitted_nav_modes = a->nav_modes; + a->fatsv_emitted_nav_qnh = a->nav_qnh; + memcpy(a->fatsv_emitted_callsign, a->callsign, sizeof(a->fatsv_emitted_callsign)); + a->fatsv_emitted_addrtype = a->addrtype; + a->fatsv_emitted_adsb_version = a->adsb_version; + a->fatsv_emitted_category = a->category; + a->fatsv_emitted_squawk = a->squawk; + a->fatsv_emitted_nac_p = a->nac_p; + a->fatsv_emitted_nac_v = a->nac_v; + a->fatsv_emitted_sil = a->sil; + a->fatsv_emitted_sil_type = a->sil_type; + a->fatsv_emitted_nic_baro = a->nic_baro; + a->fatsv_emitted_emergency = a->emergency; a->fatsv_last_emitted = now; + if (forceEmit) { + a->fatsv_last_force_emit = now; + } } } diff --git a/net_io.h b/net_io.h index 194792b..0dd3ea8 100644 --- a/net_io.h +++ b/net_io.h @@ -4,17 +4,17 @@ // // Copyright (c) 2014,2015 Oliver Jowett // -// This file is free software: you may copy, redistribute and/or modify it +// 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. +// 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 +// 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 +// You should have received a copy of the GNU General Public License // along with this program. If not, see . #ifndef DUMP1090_NETIO_H @@ -87,6 +87,8 @@ void modesInitNet(void); void modesQueueOutput(struct modesMessage *mm, struct aircraft *a); void modesNetPeriodicWork(void); +void writeFATSVHeader(); + // TODO: move these somewhere else char *generateAircraftJson(const char *url_path, int *len); char *generateStatsJson(const char *url_path, int *len); diff --git a/public_html/config.js b/public_html/config.js index 7c1afa7..7e3e060 100644 --- a/public_html/config.js +++ b/public_html/config.js @@ -119,11 +119,7 @@ ChartBundleLayers = false; // BingMapsAPIKey = null; -// Provide a Mapzen API key here to enable the Mapzen vector tile layer. -// You can obtain a free key at https://mapzen.com/developers/ -// (you need a "vector tiles" key) -// -// Be sure to quote your key: -// MapzenAPIKey = "your key here"; -// -MapzenAPIKey = null; +// Turn on display of extra Mode S EHS / ADS-B v1/v2 data +// This is not polished yet (and so is disabled by default), +// currently it's just a data dump of the new fields with no UX work. +ExtendedData = false; diff --git a/public_html/index.html b/public_html/index.html index 22095c4..48bb634 100644 --- a/public_html/index.html +++ b/public_html/index.html @@ -103,10 +103,10 @@
- Speed: + Groundspeed:
- n/a + n/a
+ diff --git a/public_html/layers.js b/public_html/layers.js index 1a04475..431e9af 100644 --- a/public_html/layers.js +++ b/public_html/layers.js @@ -37,15 +37,11 @@ function createBaseLayers() { })); } - if (MapzenAPIKey) { - world.push(createMapzenLayer()); - } - if (ChartBundleLayers) { var chartbundleTypes = { sec: "Sectional Charts", tac: "Terminal Area Charts", - wac: "World Aeronautical Charts", + hel: "Helicopter Charts", enrl: "IFR Enroute Low Charts", enra: "IFR Area Charts", enrh: "IFR Enroute High Charts" @@ -105,99 +101,3 @@ function createBaseLayers() { return layers; } - -function createMapzenLayer() { - // draw earth with a fat stroke; - // force water above earth - - var earthStyle = new ol.style.Style({ - fill: new ol.style.Fill({ - color: '#a06000' - }), - stroke: new ol.style.Stroke({ - color: '#a06000', - width: 5.0 - }), - zIndex: 0 - }); - - var waterStyle = new ol.style.Style({ - fill: new ol.style.Fill({ - color: '#0040a0' - }), - stroke: new ol.style.Stroke({ - color: '#0040a0', - width: 1.0 - }), - zIndex: 1 - }); - - var boundaryStyle = new ol.style.Style({ - stroke: new ol.style.Stroke({ - color: '#804000', - width: 2.0 - }), - zIndex: 2 - }); - - var dashedBoundaryStyle = new ol.style.Style({ - stroke: new ol.style.Stroke({ - color: '#804000', - width: 1.0, - lineDash: [4, 4], - }), - zIndex: 2 - }); - - var styleMap = { - earth: earthStyle, - - water: waterStyle, - basin: waterStyle, - dock: waterStyle, - lake: waterStyle, - ocean: waterStyle, - riverbank: waterStyle, - river: waterStyle, - - country: boundaryStyle, - disputed: dashedBoundaryStyle, - indefinite: dashedBoundaryStyle, - indeterminate: dashedBoundaryStyle, - line_of_control: dashedBoundaryStyle - }; - - return new ol.layer.VectorTile({ - name: 'mapzen_vector', - title: 'Mapzen coastlines and water', - type: 'base', - renderMode: 'image', - renderOrder: function(a,b) { - return a.get('sort_key') - b.get('sort_key'); - }, - source: new ol.source.VectorTile({ - url: '//vector.mapzen.com/osm/earth,water,boundaries/{z}/{x}/{y}.topojson?api_key=' + MapzenAPIKey, - format: new ol.format.TopoJSON(), - attributions: [ - new ol.Attribution({ - html: 'Tiles courtesy of Mapzen' - }), - new ol.Attribution({ - html: '© OpenStreetMap contributors' - }) - ], - - tileGrid: ol.tilegrid.createXYZ({ - preload: 3, - maxZoom: 14, - tileSize: [512, 512] - }), - - wrapX: true - }), - - style: function (feature) { - return (styleMap[feature.get('kind')]); - } - }); -} diff --git a/public_html/planeObject.js b/public_html/planeObject.js index a0df910..db9c56c 100644 --- a/public_html/planeObject.js +++ b/public_html/planeObject.js @@ -10,9 +10,32 @@ function PlaneObject(icao) { this.category = null; // Basic location information - this.altitude = null; - this.speed = null; - this.track = null; + this.altitude = null; + this.alt_baro = null; + this.alt_geom = null; + + this.speed = null; + this.gs = null; + this.ias = null; + this.tas = null; + + this.track = null; + this.track_rate = null; + this.mag_heading = null; + this.true_heading = null; + this.mach = null; + this.roll = null; + this.nav_altitude = null; + this.nav_heading = null; + this.nav_modes = null; + this.nav_qnh = null; + + this.baro_rate = null; + this.geom_rate = null; + this.vert_rate = null; + + this.version = null; + this.prev_position = null; this.position = null; this.position_from_mlat = false @@ -410,21 +433,35 @@ PlaneObject.prototype.updateData = function(receiver_timestamp, data) { this.messages = data.messages; this.rssi = data.rssi; this.last_message_time = receiver_timestamp - data.seen; + + // simple fields + + var fields = ["alt_baro", "alt_geom", "gs", "ias", "tas", "track", + "track_rate", "mag_heading", "true_heading", "mach", + "roll", "nav_altitude", "nav_heading", "nav_modes", + "nav_qnh", "baro_rate", "geom_rate", + "squawk", "category", "version"]; + + for (var i = 0; i < fields.length; ++i) { + if (fields[i] in data) { + this[fields[i]] = data[fields[i]]; + } else { + this[fields[i]] = null; + } + } + + // fields with more complex behaviour - if (typeof data.type !== "undefined") + if ('type' in data) this.addrtype = data.type; else this.addrtype = 'adsb_icao'; - if (typeof data.altitude !== "undefined") - this.altitude = data.altitude; - if (typeof data.vert_rate !== "undefined") - this.vert_rate = data.vert_rate; - if (typeof data.speed !== "undefined") - this.speed = data.speed; - if (typeof data.track !== "undefined") - this.track = data.track; - if (typeof data.lat !== "undefined") { + // don't expire callsigns + if ('flight' in data) + this.flight = data.flight; + + if ('lat' in data && 'lon' in data) { this.position = [data.lon, data.lat]; this.last_position_time = receiver_timestamp - data.seen_pos; @@ -443,12 +480,36 @@ PlaneObject.prototype.updateData = function(receiver_timestamp, data) { } } } - if (typeof data.flight !== "undefined") - this.flight = data.flight; - if (typeof data.squawk !== "undefined") - this.squawk = data.squawk; - if (typeof data.category !== "undefined") - this.category = data.category; + + // Pick an altitude + if ('alt_baro' in data) { + this.altitude = data.alt_baro; + } else if ('alt_geom' in data) { + this.altitude = data.alt_geom; + } else { + this.altitude = null; + } + + // Pick vertical rate from either baro or geom rate + // geometric rate is generally more reliable (smoothed etc) + if ('geom_rate' in data) { + this.vert_rate = data.geom_rate; + } else if ('baro_rate' in data) { + this.vert_rate = data.baro_rate; + } else { + this.vert_rate = null; + } + + // Pick a speed + if ('gs' in data) { + this.speed = data.gs; + } else if ('tas' in data) { + this.speed = data.tas; + } else if ('ias' in data) { + this.speed = data.ias; + } else { + this.speed = null; + } }; PlaneObject.prototype.updateTick = function(receiver_timestamp, last_timestamp) { diff --git a/public_html/script.js b/public_html/script.js index f96630e..8d58106 100644 --- a/public_html/script.js +++ b/public_html/script.js @@ -197,7 +197,11 @@ function initialize() { refreshClock(); $("#loader").removeClass("hidden"); - + + if (ExtendedData || window.location.hash == '#extended') { + $("#extendedData").removeClass("hidden"); + } + // Set up map/sidebar splitter $("#sidebar_container").resizable({handles: {w: '#splitter'}}); @@ -883,7 +887,7 @@ function refreshSelected() { $('#selected_squawk').text(selected.squawk); } - $('#selected_speed').text(format_speed_long(selected.speed, DisplayUnits)); + $('#selected_gs').text(format_speed_long(selected.gs, DisplayUnits)); $('#selected_vertical_rate').text(format_vert_rate_long(selected.vert_rate, DisplayUnits)); $('#selected_icao').text(selected.icao.toUpperCase()); $('#airframes_post_icao').attr('value',selected.icao); @@ -936,6 +940,52 @@ function refreshSelected() { $('#selected_rssi').text(selected.rssi.toFixed(1) + ' dBFS'); $('#selected_message_count').text(selected.messages); $('#selected_photo_link').html(getFlightAwarePhotoLink(selected.registration)); + + $('#selected_alt_geom').text(format_altitude_long(selected.alt_geom, selected.geom_rate, DisplayUnits)); + $('#selected_mag_heading').text(format_track_long(selected.mag_heading)); + $('#selected_true_heading').text(format_track_long(selected.true_heading)); + $('#selected_ias').text(format_speed_long(selected.ias, DisplayUnits)); + $('#selected_tas').text(format_speed_long(selected.tas, DisplayUnits)); + if (selected.mach == null) { + $('#selected_mach').text('n/a'); + } else { + $('#selected_mach').text(selected.mach.toFixed(3)); + } + if (selected.roll == null) { + $('#selected_roll').text('n/a'); + } else { + $('#selected_roll').text(selected.roll.toFixed(1)); + } + if (selected.track_rate == null) { + $('#selected_track_rate').text('n/a'); + } else { + $('#selected_track_rate').text(selected.track_rate.toFixed(2)); + } + $('#selected_geom_rate').text(format_vert_rate_long(selected.geom_rate, DisplayUnits)); + if (selected.nav_qnh == null) { + $('#selected_nav_qnh').text("n/a"); + } else { + $('#selected_nav_qnh').text(selected.nav_qnh.toFixed(1) + " hPa"); + } + $('#selected_nav_altitude').text(format_altitude_long(selected.nav_altitude, 0, DisplayUnits)); + $('#selected_nav_heading').text(format_track_long(selected.nav_heading)); + if (selected.nav_modes == null) { + $('#selected_nav_modes').text("n/a"); + } else { + $('#selected_nav_modes').text(selected.nav_modes.join()); + } + + if (selected.version == null) { + $('#selected_version').text('none'); + } else if (selected.version == 0) { + $('#selected_version').text('v0 (DO-260)'); + } else if (selected.version == 1) { + $('#selected_version').text('v1 (DO-260A)'); + } else if (selected.version == 2) { + $('#selected_version').text('v2 (DO-260B)'); + } else { + $('#selected_version').text('v' + selected.version); + } } function refreshHighlighted() { @@ -1039,7 +1089,7 @@ function refreshTableInfo() { tableplane.tr.cells[4].textContent = (tableplane.icaotype !== null ? tableplane.icaotype : ""); tableplane.tr.cells[5].textContent = (tableplane.squawk !== null ? tableplane.squawk : ""); tableplane.tr.cells[6].innerHTML = format_altitude_brief(tableplane.altitude, tableplane.vert_rate, DisplayUnits); - tableplane.tr.cells[7].textContent = format_speed_brief(tableplane.speed, DisplayUnits); + tableplane.tr.cells[7].textContent = format_speed_brief(tableplane.gs, DisplayUnits); tableplane.tr.cells[8].textContent = format_vert_rate_brief(tableplane.vert_rate, DisplayUnits); tableplane.tr.cells[9].textContent = format_distance_brief(tableplane.sitedist, DisplayUnits); tableplane.tr.cells[10].textContent = format_track_brief(tableplane.track); @@ -1090,7 +1140,7 @@ function sortByRegistration() { sortBy('registration', compareAlpha, func function sortByAircraftType() { sortBy('icaotype', compareAlpha, function(x) { return x.icaotype; }); } function sortBySquawk() { sortBy('squawk', compareAlpha, function(x) { return x.squawk; }); } function sortByAltitude() { sortBy('altitude',compareNumeric, function(x) { return (x.altitude == "ground" ? -1e9 : x.altitude); }); } -function sortBySpeed() { sortBy('speed', compareNumeric, function(x) { return x.speed; }); } +function sortBySpeed() { sortBy('speed', compareNumeric, function(x) { return x.gs; }); } function sortByVerticalRate() { sortBy('vert_rate', compareNumeric, function(x) { return x.vert_rate; }); } function sortByDistance() { sortBy('sitedist',compareNumeric, function(x) { return x.sitedist; }); } function sortByTrack() { sortBy('track', compareNumeric, function(x) { return x.track; }); } diff --git a/sdr.c b/sdr.c index f37d3e8..89fe8f4 100644 --- a/sdr.c +++ b/sdr.c @@ -5,17 +5,17 @@ // Copyright (c) 2016-2017 Oliver Jowett // Copyright (c) 2017 FlightAware LLC // -// This file is free software: you may copy, redistribute and/or modify it +// 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. +// 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 +// 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 +// You should have received a copy of the GNU General Public License // along with this program. If not, see . #include "dump1090.h" diff --git a/sdr.h b/sdr.h index 072da66..4bd20a6 100644 --- a/sdr.h +++ b/sdr.h @@ -5,17 +5,17 @@ // Copyright (c) 2016-2017 Oliver Jowett // Copyright (c) 2017 FlightAware LLC // -// This file is free software: you may copy, redistribute and/or modify it +// 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. +// 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 +// 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 +// You should have received a copy of the GNU General Public License // along with this program. If not, see . #ifndef SDR_H diff --git a/sdr_bladerf.c b/sdr_bladerf.c index 068da48..8577b9d 100644 --- a/sdr_bladerf.c +++ b/sdr_bladerf.c @@ -4,17 +4,17 @@ // // Copyright (c) 2017 FlightAware LLC // -// This file is free software: you may copy, redistribute and/or modify it +// 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. +// 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 +// 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 +// You should have received a copy of the GNU General Public License // along with this program. If not, see . #include "dump1090.h" @@ -311,8 +311,7 @@ static void *handle_bladerf_samples(struct bladerf *dev, MODES_NOTUSED(num_samples); // record initial time for later sys timestamp calculation - struct timespec entryTimestamp; - clock_gettime(CLOCK_REALTIME, &entryTimestamp); + uint64_t entryTimestamp = mstime(); pthread_mutex_lock(&Modes.data_mutex); if (Modes.exit) { @@ -413,10 +412,8 @@ static void *handle_bladerf_samples(struct bladerf *dev, if (blocks_processed) { // Get the approx system time for the start of this block - unsigned block_duration = 1e9 * outbuf->length / Modes.sample_rate; - outbuf->sysTimestamp = entryTimestamp; - outbuf->sysTimestamp.tv_nsec -= block_duration; - normalize_timespec(&outbuf->sysTimestamp); + unsigned block_duration = 1e3 * outbuf->length / Modes.sample_rate; + outbuf->sysTimestamp = entryTimestamp - block_duration; outbuf->mean_level /= blocks_processed; outbuf->mean_power /= blocks_processed; diff --git a/sdr_bladerf.h b/sdr_bladerf.h index 39de012..9b8bdab 100644 --- a/sdr_bladerf.h +++ b/sdr_bladerf.h @@ -4,17 +4,17 @@ // // Copyright (c) 2017 FlightAware LLC // -// This file is free software: you may copy, redistribute and/or modify it +// 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. +// 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 +// 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 +// You should have received a copy of the GNU General Public License // along with this program. If not, see . #ifndef BLADERF_H diff --git a/sdr_ifile.c b/sdr_ifile.c index 6f7750c..f615071 100644 --- a/sdr_ifile.c +++ b/sdr_ifile.c @@ -5,20 +5,20 @@ // Copyright (c) 2014-2017 Oliver Jowett // Copyright (c) 2017 FlightAware LLC // -// This file is free software: you may copy, redistribute and/or modify it +// 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. +// 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 +// 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 +// You should have received a copy of the GNU General Public License // along with this program. If not, see . -// This file incorporates work covered by the following copyright and +// This file incorporates work covered by the following copyright and // permission notice: // // Copyright (C) 2012 by Salvatore Sanfilippo @@ -217,7 +217,7 @@ void ifileRun() } // Get the system time for the start of this block - clock_gettime(CLOCK_REALTIME, &outbuf->sysTimestamp); + outbuf->sysTimestamp = mstime(); toread = MODES_MAG_BUF_SAMPLES * ifile.bytes_per_sample; r = ifile.readbuf; diff --git a/sdr_ifile.h b/sdr_ifile.h index 6652426..41b9e4b 100644 --- a/sdr_ifile.h +++ b/sdr_ifile.h @@ -5,17 +5,17 @@ // Copyright (c) 2016-2017 Oliver Jowett // Copyright (c) 2017 FlightAware LLC // -// This file is free software: you may copy, redistribute and/or modify it +// 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. +// 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 +// 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 +// You should have received a copy of the GNU General Public License // along with this program. If not, see . #ifndef SDR_IFILE_H diff --git a/sdr_rtlsdr.c b/sdr_rtlsdr.c index 4f71c17..979ba38 100644 --- a/sdr_rtlsdr.c +++ b/sdr_rtlsdr.c @@ -5,20 +5,20 @@ // Copyright (c) 2014-2017 Oliver Jowett // Copyright (c) 2017 FlightAware LLC // -// This file is free software: you may copy, redistribute and/or modify it +// 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. +// 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 +// 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 +// You should have received a copy of the GNU General Public License // along with this program. If not, see . -// This file incorporates work covered by the following copyright and +// This file incorporates work covered by the following copyright and // permission notice: // // Copyright (C) 2012 by Salvatore Sanfilippo @@ -223,7 +223,7 @@ bool rtlsdrOpen(void) { if (closest == -1 || abs(gains[i] - target) < abs(gains[closest] - target)) closest = i; } - + rtlsdr_set_tuner_gain(RTLSDR.dev, gains[closest]); free(gains); @@ -313,12 +313,10 @@ void rtlsdrCallback(unsigned char *buf, uint32_t len, void *ctx) { // Compute the sample timestamp and system timestamp for the start of the block outbuf->sampleTimestamp = sampleCounter * 12e6 / Modes.sample_rate; sampleCounter += slen; - block_duration = 1e9 * slen / Modes.sample_rate; // Get the approx system time for the start of this block - clock_gettime(CLOCK_REALTIME, &outbuf->sysTimestamp); - outbuf->sysTimestamp.tv_nsec -= block_duration; - normalize_timespec(&outbuf->sysTimestamp); + block_duration = 1e3 * slen / Modes.sample_rate; + outbuf->sysTimestamp = mstime() - block_duration; // Copy trailing data from last block (or reset if not valid) if (outbuf->dropped == 0) { diff --git a/sdr_rtlsdr.h b/sdr_rtlsdr.h index 317b2e4..54999e7 100644 --- a/sdr_rtlsdr.h +++ b/sdr_rtlsdr.h @@ -5,17 +5,17 @@ // Copyright (c) 2016-2017 Oliver Jowett // Copyright (c) 2017 FlightAware LLC // -// This file is free software: you may copy, redistribute and/or modify it +// 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. +// 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 +// 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 +// You should have received a copy of the GNU General Public License // along with this program. If not, see . #ifndef SDR_RTLSDR_H diff --git a/stats.c b/stats.c index 69cfcf5..62522f7 100644 --- a/stats.c +++ b/stats.c @@ -4,20 +4,20 @@ // // Copyright (c) 2015 Oliver Jowett // -// This file is free software: you may copy, redistribute and/or modify it +// 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. +// 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 +// 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 +// You should have received a copy of the GNU General Public License // along with this program. If not, see . -// This file incorporates work covered by the following copyright and +// This file incorporates work covered by the following copyright and // permission notice: // // Copyright (C) 2012 by Salvatore Sanfilippo @@ -261,7 +261,7 @@ void add_stats(const struct stats *st1, const struct stats *st2, struct stats *t target->start = st2->start; target->end = st1->end > st2->end ? st1->end : st2->end; - + target->demod_preambles = st1->demod_preambles + st2->demod_preambles; target->demod_rejected_bad = st1->demod_rejected_bad + st2->demod_rejected_bad; target->demod_rejected_unknown_icao = st1->demod_rejected_unknown_icao + st2->demod_rejected_unknown_icao; @@ -275,7 +275,7 @@ void add_stats(const struct stats *st1, const struct stats *st2, struct stats *t add_timespecs(&st1->demod_cpu, &st2->demod_cpu, &target->demod_cpu); add_timespecs(&st1->reader_cpu, &st2->reader_cpu, &target->reader_cpu); add_timespecs(&st1->background_cpu, &st2->background_cpu, &target->background_cpu); - + // noise power: target->noise_power_sum = st1->noise_power_sum + st2->noise_power_sum; target->noise_power_count = st1->noise_power_count + st2->noise_power_count; diff --git a/stats.h b/stats.h index 0e65167..36ab59e 100644 --- a/stats.h +++ b/stats.h @@ -4,20 +4,20 @@ // // Copyright (c) 2015 Oliver Jowett // -// This file is free software: you may copy, redistribute and/or modify it +// 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. +// 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 +// 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 +// You should have received a copy of the GNU General Public License // along with this program. If not, see . -// This file incorporates work covered by the following copyright and +// This file incorporates work covered by the following copyright and // permission notice: // // Copyright (C) 2012 by Salvatore Sanfilippo @@ -124,7 +124,7 @@ struct stats { // range histogram #define RANGE_BUCKET_COUNT 76 uint32_t range_histogram[RANGE_BUCKET_COUNT]; -}; +}; void add_stats(const struct stats *st1, const struct stats *st2, struct stats *target); void display_stats(struct stats *st); diff --git a/track.c b/track.c index 24e6348..83d37df 100644 --- a/track.c +++ b/track.c @@ -4,20 +4,20 @@ // // Copyright (c) 2014-2016 Oliver Jowett // -// This file is free software: you may copy, redistribute and/or modify it +// 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. +// 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 +// 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 +// You should have received a copy of the GNU General Public License // along with this program. If not, see . -// This file incorporates work covered by the following copyright and +// This file incorporates work covered by the following copyright and // permission notice: // // Copyright (C) 2012 by Salvatore Sanfilippo @@ -76,14 +76,62 @@ struct aircraft *trackCreateAircraft(struct modesMessage *mm) { a->signalLevel[i] = 1e-5; a->signalNext = 0; + // defaults until we see a message otherwise + a->adsb_version = -1; + a->adsb_hrd = HEADING_MAGNETIC; + a->adsb_tah = HEADING_GROUND_TRACK; + + // prime FATSV defaults we only emit on change + // start off with the "last emitted" ACAS RA being blank (just the BDS 3,0 // or ES type code) a->fatsv_emitted_bds_30[0] = 0x30; a->fatsv_emitted_es_acas_ra[0] = 0xE2; + a->fatsv_emitted_adsb_version = -1; + a->fatsv_emitted_addrtype = ADDR_UNKNOWN; + + // don't immediately emit, let some data build up + a->fatsv_last_emitted = a->fatsv_last_force_emit = messageNow(); // Copy the first message so we can emit it later when a second message arrives. a->first_message = *mm; + // initialize data validity ages +#define F(f,s,e) do { a->f##_valid.stale_interval = (s) * 1000; a->f##_valid.expire_interval = (e) * 1000; } while (0) + F(callsign, 60, 70); // ADS-B or Comm-B + F(altitude_baro, 15, 70); // ADS-B or Mode S + F(altitude_geom, 60, 70); // ADS-B only + F(geom_delta, 60, 70); // ADS-B only + F(gs, 60, 70); // ADS-B or Comm-B + F(ias, 60, 70); // ADS-B (rare) or Comm-B + F(tas, 60, 70); // ADS-B (rare) or Comm-B + F(mach, 60, 70); // Comm-B only + F(track, 60, 70); // ADS-B or Comm-B + F(track_rate, 60, 70); // Comm-B only + F(roll, 60, 70); // Comm-B only + F(mag_heading, 60, 70); // ADS-B (rare) or Comm-B + F(true_heading, 60, 70); // ADS-B only (rare) + F(baro_rate, 60, 70); // ADS-B or Comm-B + F(geom_rate, 60, 70); // ADS-B or Comm-B + F(squawk, 15, 70); // ADS-B or Mode S + F(airground, 15, 70); // ADS-B or Mode S + F(nav_qnh, 60, 70); // Comm-B only + F(nav_altitude, 60, 70); // ADS-B or Comm-B + F(nav_heading, 60, 70); // ADS-B or Comm-B + F(nav_modes, 60, 70); // ADS-B or Comm-B + F(cpr_odd, 60, 70); // ADS-B only + F(cpr_even, 60, 70); // ADS-B only + F(position, 60, 70); // ADS-B only + F(nic_a, 60, 70); // ADS-B only + F(nic_c, 60, 70); // ADS-B only + F(nic_baro, 60, 70); // ADS-B only + F(nac_p, 60, 70); // ADS-B only + F(nac_v, 60, 70); // ADS-B only + F(sil, 60, 70); // ADS-B only + F(gva, 60, 70); // ADS-B only + F(sda, 60, 70); // ADS-B only +#undef F + Modes.stats_current.unique_aircraft++; return (a); @@ -107,15 +155,18 @@ struct aircraft *trackFindAircraft(uint32_t addr) { // Should we accept some new data from the given source? // If so, update the validity and return 1 -static int accept_data(data_validity *d, datasource_t source, uint64_t now) +static int accept_data(data_validity *d, datasource_t source) { - if (source < d->source && now < d->stale) + if (messageNow() < d->updated) + return 0; + + if (source < d->source && messageNow() < d->stale) return 0; d->source = source; - d->updated = now; - d->stale = now + 60000; - d->expires = now + 70000; + d->updated = messageNow(); + d->stale = messageNow() + (d->stale_interval ? d->stale_interval : 60000); + d->expires = messageNow() + (d->expire_interval ? d->expire_interval : 70000); return 1; } @@ -137,10 +188,10 @@ static void combine_validity(data_validity *to, const data_validity *from1, cons to->expires = (from1->expires < from2->expires) ? from1->expires : from2->expires; // the earlier of the two expiry times } -static int compare_validity(const data_validity *lhs, const data_validity *rhs, uint64_t now) { - if (now < lhs->stale && lhs->source > rhs->source) +static int compare_validity(const data_validity *lhs, const data_validity *rhs) { + if (messageNow() < lhs->stale && lhs->source > rhs->source) return 1; - else if (now < rhs->stale && lhs->source < rhs->source) + else if (messageNow() < rhs->stale && lhs->source < rhs->source) return -1; else if (lhs->updated > rhs->updated) return 1; @@ -196,7 +247,7 @@ static void update_range_histogram(double lat, double lon) // return true if it's OK for the aircraft to have travelled from its last known position // to a new position at (lat,lon,surface) at a time of now. -static int speed_check(struct aircraft *a, double lat, double lon, uint64_t now, int surface) +static int speed_check(struct aircraft *a, double lat, double lon, int surface) { uint64_t elapsed; double distance; @@ -207,14 +258,14 @@ static int speed_check(struct aircraft *a, double lat, double lon, uint64_t now, if (!trackDataValid(&a->position_valid)) return 1; // no reference, assume OK - elapsed = trackDataAge(&a->position_valid, now); + elapsed = trackDataAge(&a->position_valid); - if (trackDataValid(&a->speed_valid)) - speed = a->speed; - else if (trackDataValid(&a->speed_ias_valid)) - speed = a->speed_ias * 4 / 3; - else if (trackDataValid(&a->speed_tas_valid)) - speed = a->speed_tas * 4 / 3; + if (trackDataValid(&a->gs_valid)) + speed = a->gs; + else if (trackDataValid(&a->tas_valid)) + speed = a->tas * 4 / 3; + else if (trackDataValid(&a->ias_valid)) + speed = a->ias * 2; else speed = surface ? 100 : 600; // guess @@ -251,24 +302,25 @@ static int speed_check(struct aircraft *a, double lat, double lon, uint64_t now, return inrange; } -static int doGlobalCPR(struct aircraft *a, struct modesMessage *mm, uint64_t now, double *lat, double *lon, unsigned *nuc) +static int doGlobalCPR(struct aircraft *a, struct modesMessage *mm, double *lat, double *lon, unsigned *nic, unsigned *rc) { int result; int fflag = mm->cpr_odd; int surface = (mm->cpr_type == CPR_SURFACE); - *nuc = (a->cpr_even_nuc < a->cpr_odd_nuc ? a->cpr_even_nuc : a->cpr_odd_nuc); // worst of the two positions + // derive NIC, Rc from the worse of the two position + // smaller NIC is worse; larger Rc is worse + *nic = (a->cpr_even_nic < a->cpr_odd_nic ? a->cpr_even_nic : a->cpr_odd_nic); + *rc = (a->cpr_even_rc > a->cpr_odd_rc ? a->cpr_even_rc : a->cpr_odd_rc); if (surface) { // surface global CPR // find reference location double reflat, reflon; - if (trackDataValidEx(&a->position_valid, now, 50000, SOURCE_INVALID)) { // Ok to try aircraft relative first + if (trackDataValid(&a->position_valid)) { // Ok to try aircraft relative first reflat = a->lat; reflon = a->lon; - if (a->pos_nuc < *nuc) - *nuc = a->pos_nuc; } else if (Modes.bUserFlags & MODES_USER_LATLON_VALID) { reflat = Modes.fUserLat; reflon = Modes.fUserLon; @@ -320,7 +372,7 @@ static int doGlobalCPR(struct aircraft *a, struct modesMessage *mm, uint64_t now return result; // check speed limit - if (trackDataValid(&a->position_valid) && a->pos_nuc >= *nuc && !speed_check(a, *lat, *lon, now, surface)) { + if (trackDataValid(&a->position_valid) && a->pos_nic >= *nic && a->pos_rc <= *rc && !speed_check(a, *lat, *lon, surface)) { Modes.stats_current.cpr_global_speed_checks++; return -2; } @@ -328,7 +380,7 @@ static int doGlobalCPR(struct aircraft *a, struct modesMessage *mm, uint64_t now return result; } -static int doLocalCPR(struct aircraft *a, struct modesMessage *mm, uint64_t now, double *lat, double *lon, unsigned *nuc) +static int doLocalCPR(struct aircraft *a, struct modesMessage *mm, double *lat, double *lon, unsigned *nic, unsigned *rc) { // relative CPR // find reference location @@ -338,20 +390,28 @@ static int doLocalCPR(struct aircraft *a, struct modesMessage *mm, uint64_t now, int fflag = mm->cpr_odd; int surface = (mm->cpr_type == CPR_SURFACE); - *nuc = mm->cpr_nucp; + if (fflag) { + *nic = a->cpr_odd_nic; + *rc = a->cpr_odd_rc; + } else { + *nic = a->cpr_even_nic; + *rc = a->cpr_even_rc; + } - if (trackDataValidEx(&a->position_valid, now, 50000, SOURCE_INVALID)) { + if (trackDataValid(&a->position_valid)) { reflat = a->lat; reflon = a->lon; - if (a->pos_nuc < *nuc) - *nuc = a->pos_nuc; + if (a->pos_nic < *nic) + *nic = a->pos_nic; + if (a->pos_rc < *rc) + *rc = a->pos_rc; range_limit = 50e3; } else if (!surface && (Modes.bUserFlags & MODES_USER_LATLON_VALID)) { reflat = Modes.fUserLat; reflon = Modes.fUserLon; - + // The cell size is at least 360NM, giving a nominal // max range of 180NM (half a cell). // @@ -394,7 +454,7 @@ static int doLocalCPR(struct aircraft *a, struct modesMessage *mm, uint64_t now, } // check speed limit - if (trackDataValid(&a->position_valid) && a->pos_nuc >= *nuc && !speed_check(a, *lat, *lon, now, surface)) { + if (trackDataValid(&a->position_valid) && a->pos_nic >= *nic && a->pos_rc <= *rc && !speed_check(a, *lat, *lon, surface)) { #ifdef DEBUG_CPR_CHECKS fprintf(stderr, "Speed check for %06X with local decoding failed\n", a->addr); #endif @@ -413,12 +473,13 @@ static uint64_t time_between(uint64_t t1, uint64_t t2) return t2 - t1; } -static void updatePosition(struct aircraft *a, struct modesMessage *mm, uint64_t now) +static void updatePosition(struct aircraft *a, struct modesMessage *mm) { int location_result = -1; uint64_t max_elapsed; double new_lat = 0, new_lon = 0; - unsigned new_nuc = 0; + unsigned new_nic = 0; + unsigned new_rc = 0; int surface; surface = (mm->cpr_type == CPR_SURFACE); @@ -427,7 +488,7 @@ static void updatePosition(struct aircraft *a, struct modesMessage *mm, uint64_t ++Modes.stats_current.cpr_surface; // Surface: 25 seconds if >25kt or speed unknown, 50 seconds otherwise - if (mm->speed_valid && mm->speed <= 25) + if (mm->gs_valid && mm->gs.selected <= 25) max_elapsed = 50000; else max_elapsed = 25000; @@ -444,7 +505,7 @@ static void updatePosition(struct aircraft *a, struct modesMessage *mm, uint64_t a->cpr_odd_type == a->cpr_even_type && time_between(a->cpr_odd_valid.updated, a->cpr_even_valid.updated) <= max_elapsed) { - location_result = doGlobalCPR(a, mm, now, &new_lat, &new_lon, &new_nuc); + location_result = doGlobalCPR(a, mm, &new_lat, &new_lon, &new_nic, &new_rc); if (location_result == -2) { #ifdef DEBUG_CPR_CHECKS @@ -475,7 +536,7 @@ static void updatePosition(struct aircraft *a, struct modesMessage *mm, uint64_t // Otherwise try relative CPR. if (location_result == -1) { - location_result = doLocalCPR(a, mm, now, &new_lat, &new_lon, &new_nuc); + location_result = doLocalCPR(a, mm, &new_lat, &new_lon, &new_nic, &new_rc); if (location_result < 0) { Modes.stats_current.cpr_local_skipped++; @@ -496,16 +557,247 @@ static void updatePosition(struct aircraft *a, struct modesMessage *mm, uint64_t mm->cpr_decoded = 1; mm->decoded_lat = new_lat; mm->decoded_lon = new_lon; + mm->decoded_nic = new_nic; + mm->decoded_rc = new_rc; // Update aircraft state a->lat = new_lat; a->lon = new_lon; - a->pos_nuc = new_nuc; + a->pos_nic = new_nic; + a->pos_rc = new_rc; update_range_histogram(new_lat, new_lon); } } +static unsigned compute_nic(unsigned metype, unsigned version, unsigned nic_a, unsigned nic_b, unsigned nic_c) +{ + switch (metype) { + case 5: // surface + case 9: // airborne + case 20: // airborne, GNSS altitude + return 11; + + case 6: // surface + case 10: // airborne + case 21: // airborne, GNSS altitude + return 10; + + case 7: // surface + if (version == 2) { + if (nic_a && !nic_c) { + return 9; + } else { + return 8; + } + } else if (version == 1) { + if (nic_a) { + return 9; + } else { + return 8; + } + } else { + return 8; + } + + case 8: // surface + if (version == 2) { + if (nic_a && nic_c) { + return 7; + } else if (nic_a && !nic_c) { + return 6; + } else if (!nic_a && nic_c) { + return 6; + } else { + return 0; + } + } else { + return 0; + } + + case 11: // airborne + if (version == 2) { + if (nic_a && nic_b) { + return 9; + } else { + return 8; + } + } else if (version == 1) { + if (nic_a) { + return 9; + } else { + return 8; + } + } else { + return 8; + } + + case 12: // airborne + return 7; + + case 13: // airborne + return 6; + + case 14: // airborne + return 5; + + case 15: // airborne + return 4; + + case 16: // airborne + if (nic_a && nic_b) { + return 3; + } else { + return 2; + } + + case 17: // airborne + return 1; + + default: + return 0; + } +} + +static unsigned compute_rc(unsigned metype, unsigned version, unsigned nic_a, unsigned nic_b, unsigned nic_c) +{ + switch (metype) { + case 5: // surface + case 9: // airborne + case 20: // airborne, GNSS altitude + return 8; // 7.5m + + case 6: // surface + case 10: // airborne + case 21: // airborne, GNSS altitude + return 25; + + case 7: // surface + if (version == 2) { + if (nic_a && !nic_c) { + return 75; + } else { + return 186; // 185.2m, 0.1NM + } + } else if (version == 1) { + if (nic_a) { + return 75; + } else { + return 186; // 185.2m, 0.1NM + } + } else { + return 186; // 185.2m, 0.1NM + } + + case 8: // surface + if (version == 2) { + if (nic_a && nic_c) { + return 371; // 370.4m, 0.2NM + } else if (nic_a && !nic_c) { + return 556; // 555.6m, 0.3NM + } else if (!nic_a && nic_c) { + return 926; // 926m, 0.5NM + } else { + return RC_UNKNOWN; + } + } else { + return RC_UNKNOWN; + } + + case 11: // airborne + if (version == 2) { + if (nic_a && nic_b) { + return 75; + } else { + return 186; // 370.4m, 0.2NM + } + } else if (version == 1) { + if (nic_a) { + return 75; + } else { + return 186; // 370.4m, 0.2NM + } + } else { + return 186; // 370.4m, 0.2NM + } + + case 12: // airborne + return 371; // 370.4m, 0.2NM + + case 13: // airborne + if (version == 2) { + if (!nic_a && nic_b) { + return 556; // 555.6m, 0.3NM + } else if (!nic_a && !nic_b) { + return 926; // 926m, 0.5NM + } else if (nic_a && nic_b) { + return 1112; // 1111.2m, 0.6NM + } else { + return RC_UNKNOWN; // bad combination + } + } else if (version == 1) { + if (nic_a) { + return 1112; // 1111.2m, 0.6NM + } else { + return 926; // 926m, 0.5NM + } + } else { + return 926; // 926m, 0.5NM + } + + case 14: // airborne + return 1852; // 1.0NM + + case 15: // airborne + return 3704; // 2NM + + case 16: // airborne + if (version == 2) { + if (nic_a && nic_b) { + return 7408; // 4NM + } else { + return 14816; // 8NM + } + } else if (version == 1) { + if (nic_a) { + return 7408; // 4NM + } else { + return 14816; // 8NM + } + } else { + return 18520; // 10NM + } + + case 17: // airborne + return 37040; // 20NM + + default: + return RC_UNKNOWN; + } +} + +static void compute_nic_rc_from_message(struct modesMessage *mm, struct aircraft *a, unsigned *nic, unsigned *rc) +{ + int nic_a = (trackDataValid(&a->nic_a_valid) && a->nic_a); + int nic_b = (mm->accuracy.nic_b_valid && mm->accuracy.nic_b); + int nic_c = (trackDataValid(&a->nic_c_valid) && a->nic_c); + + *nic = compute_nic(mm->metype, a->adsb_version, nic_a, nic_b, nic_c); + *rc = compute_rc(mm->metype, a->adsb_version, nic_a, nic_b, nic_c); +} + +static int altitude_to_feet(int raw, altitude_unit_t unit) +{ + switch (unit) { + case UNIT_METERS: + return raw / 0.3048; + case UNIT_FEET: + return raw; + default: + return 0; + } +} + // //========================================================================= // @@ -522,7 +814,12 @@ struct aircraft *trackUpdateFromMessage(struct modesMessage *mm) return NULL; } - uint64_t now = mstime(); + if (mm->addr == 0) { + // junk address, don't track it + return NULL; + } + + _messageNow = mm->sysTimestampMsg; // Lookup our aircraft or create a new one a = trackFindAircraft(mm->addr); @@ -536,106 +833,240 @@ struct aircraft *trackUpdateFromMessage(struct modesMessage *mm) a->signalLevel[a->signalNext] = mm->signalLevel; a->signalNext = (a->signalNext + 1) & 7; } - a->seen = now; + a->seen = messageNow(); a->messages++; // update addrtype, we only ever go towards "more direct" types if (mm->addrtype < a->addrtype) a->addrtype = mm->addrtype; - if (mm->altitude_valid && mm->altitude_source == ALTITUDE_BARO && accept_data(&a->altitude_valid, mm->source, now)) { + // if we saw some direct ADS-B for the first time, assume version 0 + if (mm->source == SOURCE_ADSB && a->adsb_version < 0) + a->adsb_version = 0; + + // category shouldn't change over time, don't bother with metadata + if (mm->category_valid) { + a->category = mm->category; + } + + // operational status message + // done early to update version / HRD / TAH + if (mm->opstatus.valid) { + a->adsb_version = mm->opstatus.version; + if (mm->opstatus.hrd != HEADING_INVALID) { + a->adsb_hrd = mm->opstatus.hrd; + } + if (mm->opstatus.tah != HEADING_INVALID) { + a->adsb_tah = mm->opstatus.tah; + } + } + + if (mm->altitude_baro_valid && accept_data(&a->altitude_baro_valid, mm->source)) { + int alt = altitude_to_feet(mm->altitude_baro, mm->altitude_baro_unit); if (a->modeC_hit) { - int new_modeC = (a->altitude + 49) / 100; - int old_modeC = (mm->altitude + 49) / 100; + int new_modeC = (a->altitude_baro + 49) / 100; + int old_modeC = (alt + 49) / 100; if (new_modeC != old_modeC) { a->modeC_hit = 0; } } - a->altitude = mm->altitude; + a->altitude_baro = alt; } - if (mm->squawk_valid && accept_data(&a->squawk_valid, mm->source, now)) { + if (mm->squawk_valid && accept_data(&a->squawk_valid, mm->source)) { if (mm->squawk != a->squawk) { a->modeA_hit = 0; } a->squawk = mm->squawk; + + // Handle 7x00 without a corresponding emergency status + if (!mm->emergency_valid) { + emergency_t squawk_emergency; + switch (mm->squawk) { + case 0x7500: + squawk_emergency = EMERGENCY_UNLAWFUL; + break; + case 0x7600: + squawk_emergency = EMERGENCY_NORDO; + break; + case 0x7700: + squawk_emergency = EMERGENCY_GENERAL; + break; + default: + squawk_emergency = EMERGENCY_NONE; + break; + } + + if (squawk_emergency != EMERGENCY_NONE && accept_data(&a->emergency_valid, mm->source)) { + a->emergency = squawk_emergency; + } + } } - if (mm->altitude_valid && mm->altitude_source == ALTITUDE_GNSS && accept_data(&a->altitude_gnss_valid, mm->source, now)) { - a->altitude_gnss = mm->altitude; + if (mm->emergency_valid && accept_data(&a->emergency_valid, mm->source)) { + a->emergency = mm->emergency; } - if (mm->gnss_delta_valid && accept_data(&a->gnss_delta_valid, mm->source, now)) { - a->gnss_delta = mm->gnss_delta; + if (mm->altitude_geom_valid && accept_data(&a->altitude_geom_valid, mm->source)) { + a->altitude_geom = altitude_to_feet(mm->altitude_geom, mm->altitude_geom_unit); } - if (mm->heading_valid && mm->heading_source == HEADING_TRUE && accept_data(&a->heading_valid, mm->source, now)) { - a->heading = mm->heading; + if (mm->geom_delta_valid && accept_data(&a->geom_delta_valid, mm->source)) { + a->geom_delta = mm->geom_delta; } - if (mm->heading_valid && mm->heading_source == HEADING_MAGNETIC && accept_data(&a->heading_magnetic_valid, mm->source, now)) { - a->heading_magnetic = mm->heading; + if (mm->heading_valid) { + heading_type_t htype = mm->heading_type; + if (htype == HEADING_MAGNETIC_OR_TRUE) { + htype = a->adsb_hrd; + } else if (htype == HEADING_TRACK_OR_HEADING) { + htype = a->adsb_tah; + } + + if (htype == HEADING_GROUND_TRACK && accept_data(&a->track_valid, mm->source)) { + a->track = mm->heading; + } else if (htype == HEADING_MAGNETIC && accept_data(&a->mag_heading_valid, mm->source)) { + a->mag_heading = mm->heading; + } else if (htype == HEADING_TRUE && accept_data(&a->true_heading_valid, mm->source)) { + a->true_heading = mm->heading; + } } - if (mm->speed_valid && mm->speed_source == SPEED_GROUNDSPEED && accept_data(&a->speed_valid, mm->source, now)) { - a->speed = mm->speed; + if (mm->track_rate_valid && accept_data(&a->track_rate_valid, mm->source)) { + a->track_rate = mm->track_rate; } - if (mm->speed_valid && mm->speed_source == SPEED_IAS && accept_data(&a->speed_ias_valid, mm->source, now)) { - a->speed_ias = mm->speed; + if (mm->roll_valid && accept_data(&a->roll_valid, mm->source)) { + a->roll = mm->roll; } - if (mm->speed_valid && mm->speed_source == SPEED_TAS && accept_data(&a->speed_tas_valid, mm->source, now)) { - a->speed_tas = mm->speed; + if (mm->gs_valid) { + mm->gs.selected = (a->adsb_version == 2 ? mm->gs.v2 : mm->gs.v0); + if (accept_data(&a->gs_valid, mm->source)) { + a->gs = mm->gs.selected; + } } - if (mm->vert_rate_valid && accept_data(&a->vert_rate_valid, mm->source, now)) { - a->vert_rate = mm->vert_rate; - a->vert_rate_source = mm->vert_rate_source; + if (mm->ias_valid && accept_data(&a->ias_valid, mm->source)) { + a->ias = mm->ias; } - if (mm->category_valid && accept_data(&a->category_valid, mm->source, now)) { - a->category = mm->category; + if (mm->tas_valid && accept_data(&a->tas_valid, mm->source)) { + a->tas = mm->tas; } - if (mm->airground != AG_INVALID && accept_data(&a->airground_valid, mm->source, now)) { - a->airground = mm->airground; + if (mm->mach_valid && accept_data(&a->mach_valid, mm->source)) { + a->mach = mm->mach; } - if (mm->callsign_valid && accept_data(&a->callsign_valid, mm->source, now)) { + if (mm->baro_rate_valid && accept_data(&a->baro_rate_valid, mm->source)) { + a->baro_rate = mm->baro_rate; + } + + if (mm->geom_rate_valid && accept_data(&a->geom_rate_valid, mm->source)) { + a->geom_rate = mm->geom_rate; + } + + if (mm->airground != AG_INVALID) { + // If our current state is UNCERTAIN, accept new data as normal + // If our current state is certain but new data is not, only accept the uncertain state if the certain data has gone stale + if (mm->airground != AG_UNCERTAIN || + (mm->airground == AG_UNCERTAIN && !trackDataFresh(&a->airground_valid))) { + if (accept_data(&a->airground_valid, mm->source)) { + a->airground = mm->airground; + } + } + } + + if (mm->callsign_valid && accept_data(&a->callsign_valid, mm->source)) { memcpy(a->callsign, mm->callsign, sizeof(a->callsign)); } + // prefer MCP over FMS + // unless the source says otherwise + if (mm->nav.mcp_altitude_valid && mm->nav.altitude_source != NAV_ALT_FMS && accept_data(&a->nav_altitude_valid, mm->source)) { + a->nav_altitude = mm->nav.mcp_altitude; + } else if (mm->nav.fms_altitude_valid && accept_data(&a->nav_altitude_valid, mm->source)) { + a->nav_altitude = mm->nav.fms_altitude; + } + + if (mm->nav.heading_valid && accept_data(&a->nav_heading_valid, mm->source)) { + a->nav_heading = mm->nav.heading; + } + + if (mm->nav.modes_valid && accept_data(&a->nav_modes_valid, mm->source)) { + a->nav_modes = mm->nav.modes; + } + + if (mm->nav.qnh_valid && accept_data(&a->nav_qnh_valid, mm->source)) { + a->nav_qnh = mm->nav.qnh; + } + // CPR, even - if (mm->cpr_valid && !mm->cpr_odd && accept_data(&a->cpr_even_valid, mm->source, now)) { + if (mm->cpr_valid && !mm->cpr_odd && accept_data(&a->cpr_even_valid, mm->source)) { a->cpr_even_type = mm->cpr_type; a->cpr_even_lat = mm->cpr_lat; a->cpr_even_lon = mm->cpr_lon; - a->cpr_even_nuc = mm->cpr_nucp; + compute_nic_rc_from_message(mm, a, &a->cpr_even_nic, &a->cpr_even_rc); } // CPR, odd - if (mm->cpr_valid && mm->cpr_odd && accept_data(&a->cpr_odd_valid, mm->source, now)) { + if (mm->cpr_valid && mm->cpr_odd && accept_data(&a->cpr_odd_valid, mm->source)) { a->cpr_odd_type = mm->cpr_type; a->cpr_odd_lat = mm->cpr_lat; a->cpr_odd_lon = mm->cpr_lon; - a->cpr_odd_nuc = mm->cpr_nucp; + compute_nic_rc_from_message(mm, a, &a->cpr_odd_nic, &a->cpr_odd_rc); + } + + if (mm->accuracy.sda_valid && accept_data(&a->sda_valid, mm->source)) { + a->sda = mm->accuracy.sda; + } + + if (mm->accuracy.nic_a_valid && accept_data(&a->nic_a_valid, mm->source)) { + a->nic_a = mm->accuracy.nic_a; + } + + if (mm->accuracy.nic_c_valid && accept_data(&a->nic_c_valid, mm->source)) { + a->nic_c = mm->accuracy.nic_c; + } + + if (mm->accuracy.nac_p_valid && accept_data(&a->nac_p_valid, mm->source)) { + a->nac_p = mm->accuracy.nac_p; + } + + if (mm->accuracy.nac_v_valid && accept_data(&a->nac_v_valid, mm->source)) { + a->nac_v = mm->accuracy.nac_v; + } + + if (mm->accuracy.sil_type != SIL_INVALID && accept_data(&a->sil_valid, mm->source)) { + a->sil = mm->accuracy.sil; + if (a->sil_type == SIL_INVALID || mm->accuracy.sil_type != SIL_UNKNOWN) { + a->sil_type = mm->accuracy.sil_type; + } + } + + if (mm->accuracy.gva_valid && accept_data(&a->gva_valid, mm->source)) { + a->gva = mm->accuracy.gva; + } + + if (mm->accuracy.sda_valid && accept_data(&a->sda_valid, mm->source)) { + a->sda = mm->accuracy.sda; } // Now handle derived data - // derive GNSS if we have baro + delta - if (compare_validity(&a->altitude_valid, &a->altitude_gnss_valid, now) > 0 && - compare_validity(&a->gnss_delta_valid, &a->altitude_gnss_valid, now) > 0) { - // Baro and delta are both more recent than GNSS, derive GNSS from baro + delta - a->altitude_gnss = a->altitude + a->gnss_delta; - combine_validity(&a->altitude_gnss_valid, &a->altitude_valid, &a->gnss_delta_valid); + // derive geometric altitude if we have baro + delta + if (compare_validity(&a->altitude_baro_valid, &a->altitude_geom_valid) > 0 && + compare_validity(&a->geom_delta_valid, &a->altitude_geom_valid) > 0) { + // Baro and delta are both more recent than geometric, derive geometric from baro + delta + a->altitude_geom = a->altitude_baro + a->geom_delta; + combine_validity(&a->altitude_geom_valid, &a->altitude_baro_valid, &a->geom_delta_valid); } // If we've got a new cprlat or cprlon if (mm->cpr_valid) { - updatePosition(a, mm, now); + updatePosition(a, mm); } return (a); @@ -669,8 +1100,8 @@ static void trackMatchAC(uint64_t now) } // match on Mode C (+/- 100ft) - if (trackDataValid(&a->altitude_valid)) { - int modeC = (a->altitude + 49) / 100; + if (trackDataValid(&a->altitude_baro_valid)) { + int modeC = (a->altitude_baro + 49) / 100; unsigned modeA = modeCToModeA(modeC); unsigned i = modeAToIndex(modeA); @@ -731,7 +1162,7 @@ static void trackRemoveStaleAircraft(uint64_t now) { struct aircraft *a = Modes.aircrafts; struct aircraft *prev = NULL; - + while(a) { if ((now - a->seen) > TRACK_AIRCRAFT_TTL || (a->messages == 1 && (now - a->seen) > TRACK_AIRCRAFT_ONEHIT_TTL)) { @@ -751,22 +1182,37 @@ static void trackRemoveStaleAircraft(uint64_t now) #define EXPIRE(_f) do { if (a->_f##_valid.source != SOURCE_INVALID && now >= a->_f##_valid.expires) { a->_f##_valid.source = SOURCE_INVALID; } } while (0) EXPIRE(callsign); - EXPIRE(altitude); - EXPIRE(altitude_gnss); - EXPIRE(gnss_delta); - EXPIRE(speed); - EXPIRE(speed_ias); - EXPIRE(speed_tas); - EXPIRE(heading); - EXPIRE(heading_magnetic); - EXPIRE(vert_rate); + EXPIRE(altitude_baro); + EXPIRE(altitude_geom); + EXPIRE(geom_delta); + EXPIRE(gs); + EXPIRE(ias); + EXPIRE(tas); + EXPIRE(mach); + EXPIRE(track); + EXPIRE(track_rate); + EXPIRE(roll); + EXPIRE(mag_heading); + EXPIRE(true_heading); + EXPIRE(baro_rate); + EXPIRE(geom_rate); EXPIRE(squawk); - EXPIRE(category); EXPIRE(airground); + EXPIRE(nav_qnh); + EXPIRE(nav_altitude); + EXPIRE(nav_heading); + EXPIRE(nav_modes); EXPIRE(cpr_odd); EXPIRE(cpr_even); EXPIRE(position); - + EXPIRE(nic_a); + EXPIRE(nic_c); + EXPIRE(nic_baro); + EXPIRE(nac_p); + EXPIRE(sil); + EXPIRE(gva); + EXPIRE(sda); +#undef EXPIRE prev = a; a = a->next; } } diff --git a/track.h b/track.h index 57be9f9..daac95a 100644 --- a/track.h +++ b/track.h @@ -4,20 +4,20 @@ // // Copyright (c) 2014-2016 Oliver Jowett // -// This file is free software: you may copy, redistribute and/or modify it +// 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. +// 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 +// 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 +// You should have received a copy of the GNU General Public License // along with this program. If not, see . -// This file incorporates work covered by the following copyright and +// This file incorporates work covered by the following copyright and // permission notice: // // Copyright (C) 2012 by Salvatore Sanfilippo @@ -64,11 +64,21 @@ */ #define TRACK_MODEAC_MIN_MESSAGES 4 +/* Special value for Rc unknown */ +#define RC_UNKNOWN 0 + +// data moves through three states: +// fresh: data is valid. Updates from a less reliable source are not accepted. +// stale: data is valid. Updates from a less reliable source are accepted. +// expired: data is not valid. typedef struct { + uint64_t stale_interval; /* how long after an update until the data is stale */ + uint64_t expire_interval; /* how long after an update until the data expires */ + datasource_t source; /* where the data came from */ - uint64_t updated; /* when it arrived */ - uint64_t stale; /* when it will become stale */ - uint64_t expires; /* when it will expire */ + uint64_t updated; /* when it arrived */ + uint64_t stale; /* when it goes stale */ + uint64_t expires; /* when it expires */ } data_validity; /* Structure used to describe the state of one tracked aircraft */ @@ -85,77 +95,153 @@ struct aircraft { data_validity callsign_valid; char callsign[9]; // Flight number - data_validity altitude_valid; - int altitude; // Altitude (Baro) + data_validity altitude_baro_valid; + int altitude_baro; // Altitude (Baro) - data_validity altitude_gnss_valid; - int altitude_gnss; // Altitude (GNSS) + data_validity altitude_geom_valid; + int altitude_geom; // Altitude (Geometric) - data_validity gnss_delta_valid; - int gnss_delta; // Difference between GNSS and Baro altitudes + data_validity geom_delta_valid; + int geom_delta; // Difference between Geometric and Baro altitudes - data_validity speed_valid; - unsigned speed; + data_validity gs_valid; + float gs; - data_validity speed_ias_valid; - unsigned speed_ias; + data_validity ias_valid; + unsigned ias; - data_validity speed_tas_valid; - unsigned speed_tas; + data_validity tas_valid; + unsigned tas; - data_validity heading_valid; - unsigned heading; // Heading (OK it's really the track) + data_validity mach_valid; + float mach; - data_validity heading_magnetic_valid; - unsigned heading_magnetic; // Heading + data_validity track_valid; + float track; // Ground track - data_validity vert_rate_valid; - int vert_rate; // Vertical rate - altitude_source_t vert_rate_source; + data_validity track_rate_valid; + float track_rate; // Rate of change of ground track, degrees/second + + data_validity roll_valid; + float roll; // Roll angle, degrees right + + data_validity mag_heading_valid; + float mag_heading; // Magnetic heading + + data_validity true_heading_valid; + float true_heading; // True heading + + data_validity baro_rate_valid; + int baro_rate; // Vertical rate (barometric) + + data_validity geom_rate_valid; + int geom_rate; // Vertical rate (geometric) data_validity squawk_valid; unsigned squawk; // Squawk - data_validity category_valid; - unsigned category; // Aircraft category A0 - D7 encoded as a single hex byte + data_validity emergency_valid; + emergency_t emergency; // Emergency/priority status + + unsigned category; // Aircraft category A0 - D7 encoded as a single hex byte. 00 = unset data_validity airground_valid; airground_t airground; // air/ground status + data_validity nav_qnh_valid; + float nav_qnh; // Altimeter setting (QNH/QFE), millibars + + data_validity nav_altitude_valid; + unsigned nav_altitude; // FMS or FCU selected altitude + + data_validity nav_heading_valid; + float nav_heading; // target heading, degrees (0-359) + + data_validity nav_modes_valid; + nav_modes_t nav_modes; // enabled modes (autopilot, vnav, etc) + data_validity cpr_odd_valid; // Last seen even CPR message cpr_type_t cpr_odd_type; unsigned cpr_odd_lat; unsigned cpr_odd_lon; - unsigned cpr_odd_nuc; + unsigned cpr_odd_nic; + unsigned cpr_odd_rc; data_validity cpr_even_valid; // Last seen odd CPR message cpr_type_t cpr_even_type; unsigned cpr_even_lat; unsigned cpr_even_lon; - unsigned cpr_even_nuc; + unsigned cpr_even_nic; + unsigned cpr_even_rc; data_validity position_valid; double lat, lon; // Coordinated obtained from CPR encoded data - unsigned pos_nuc; // NUCp of last computed position + unsigned pos_nic; // NIC of last computed position + unsigned pos_rc; // Rc of last computed position + + // data extracted from opstatus etc + int adsb_version; // ADS-B version (from ADS-B operational status); -1 means no ADS-B messages seen + heading_type_t adsb_hrd; // Heading Reference Direction setting (from ADS-B operational status) + heading_type_t adsb_tah; // Track Angle / Heading setting (from ADS-B operational status) + + data_validity nic_a_valid; + data_validity nic_c_valid; + data_validity nic_baro_valid; + data_validity nac_p_valid; + data_validity nac_v_valid; + data_validity sil_valid; + data_validity gva_valid; + data_validity sda_valid; + + unsigned nic_a : 1; // NIC supplement A from opstatus + unsigned nic_c : 1; // NIC supplement C from opstatus + unsigned nic_baro : 1; // NIC baro supplement from TSS or opstatus + unsigned nac_p : 4; // NACp from TSS or opstatus + unsigned nac_v : 3; // NACv from airborne velocity or opstatus + unsigned sil : 2; // SIL from TSS or opstatus + sil_type_t sil_type; // SIL supplement from TSS or opstatus + unsigned gva : 2; // GVA from opstatus + unsigned sda : 2; // SDA from opstatus int modeA_hit; // did our squawk match a possible mode A reply in the last check period? int modeC_hit; // did our altitude match a possible mode C reply in the last check period? - int fatsv_emitted_altitude; // last FA emitted altitude - int fatsv_emitted_altitude_gnss; // -"- GNSS altitude - int fatsv_emitted_heading; // -"- true track - int fatsv_emitted_heading_magnetic; // -"- magnetic heading - int fatsv_emitted_speed; // -"- groundspeed - int fatsv_emitted_speed_ias; // -"- IAS - int fatsv_emitted_speed_tas; // -"- TAS + int fatsv_emitted_altitude_baro; // last FA emitted altitude + int fatsv_emitted_altitude_geom; // -"- GNSS altitude + int fatsv_emitted_baro_rate; // -"- barometric rate + int fatsv_emitted_geom_rate; // -"- geometric rate + float fatsv_emitted_track; // -"- true track + float fatsv_emitted_track_rate; // -"- track rate of change + float fatsv_emitted_mag_heading; // -"- magnetic heading + float fatsv_emitted_true_heading; // -"- true heading + float fatsv_emitted_roll; // -"- roll angle + float fatsv_emitted_gs; // -"- groundspeed + unsigned fatsv_emitted_ias; // -"- IAS + unsigned fatsv_emitted_tas; // -"- TAS + float fatsv_emitted_mach; // -"- Mach number airground_t fatsv_emitted_airground; // -"- air/ground state + unsigned fatsv_emitted_nav_altitude; // -"- target altitude + float fatsv_emitted_nav_heading; // -"- target heading + nav_modes_t fatsv_emitted_nav_modes; // -"- enabled navigation modes + float fatsv_emitted_nav_qnh; // -"- altimeter setting unsigned char fatsv_emitted_bds_10[7]; // -"- BDS 1,0 message unsigned char fatsv_emitted_bds_30[7]; // -"- BDS 3,0 message unsigned char fatsv_emitted_es_status[7]; // -"- ES operational status message - unsigned char fatsv_emitted_es_target[7]; // -"- ES target status message unsigned char fatsv_emitted_es_acas_ra[7]; // -"- ES ACAS RA report message + char fatsv_emitted_callsign[9]; // -"- callsign + addrtype_t fatsv_emitted_addrtype; // -"- address type (assumed ADSB_ICAO initially) + int fatsv_emitted_adsb_version; // -"- ADS-B version (assumed non-ADS-B initially) + unsigned fatsv_emitted_category; // -"- ADS-B emitter category (assumed A0 initially) + unsigned fatsv_emitted_squawk; // -"- squawk + unsigned fatsv_emitted_nac_p; // -"- NACp + unsigned fatsv_emitted_nac_v; // -"- NACv + unsigned fatsv_emitted_sil; // -"- SIL + sil_type_t fatsv_emitted_sil_type; // -"- SIL supplement + unsigned fatsv_emitted_nic_baro; // -"- NICbaro + emergency_t fatsv_emitted_emergency; // -"- emergency/priority status uint64_t fatsv_last_emitted; // time (millis) aircraft was last FA emitted + uint64_t fatsv_last_force_emit; // time (millis) we last emitted only-on-change data struct aircraft *next; // Next aircraft in our linked list @@ -173,33 +259,23 @@ extern uint32_t modeAC_age[4096]; /* is this bit of data valid? */ static inline int trackDataValid(const data_validity *v) { - return (v->source != SOURCE_INVALID); + return (v->source != SOURCE_INVALID && messageNow() < v->expires); } -/* .. with these constraints? */ -static inline int trackDataValidEx(const data_validity *v, - uint64_t now, - uint64_t maxAge, - datasource_t minSource) +/* is this bit of data fresh? */ +static inline int trackDataFresh(const data_validity *v) { - if (v->source == SOURCE_INVALID) - return 0; - if (v->source < minSource) - return 0; - if (v->updated < now && (now - v->updated) > maxAge) - return 0; - return 1; + return (v->source != SOURCE_INVALID && messageNow() < v->stale); } -/* what's the age of this data? */ -static inline uint64_t trackDataAge(const data_validity *v, - uint64_t now) +/* what's the age of this data, in milliseconds? */ +static inline uint64_t trackDataAge(const data_validity *v) { if (v->source == SOURCE_INVALID) return ~(uint64_t)0; - if (v->updated >= now) + if (v->updated >= messageNow()) return 0; - return (now - v->updated); + return (messageNow() - v->updated); } /* Update aircraft state from data in the provided mesage. diff --git a/util.c b/util.c index 3711237..d5f101c 100644 --- a/util.c +++ b/util.c @@ -4,20 +4,20 @@ // // Copyright (c) 2015 Oliver Jowett // -// This file is free software: you may copy, redistribute and/or modify it +// 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. +// 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 +// 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 +// You should have received a copy of the GNU General Public License // along with this program. If not, see . -// This file incorporates work covered by the following copyright and +// This file incorporates work covered by the following copyright and // permission notice: // // Copyright (C) 2012 by Salvatore Sanfilippo @@ -52,6 +52,8 @@ #include #include +uint64_t _messageNow = 0; + uint64_t mstime(void) { struct timeval tv; @@ -68,6 +70,11 @@ int64_t receiveclock_ns_elapsed(uint64_t t1, uint64_t t2) return (t2 - t1) * 1000U / 12U; } +int64_t receiveclock_ms_elapsed(uint64_t t1, uint64_t t2) +{ + return (t2 - t1) / 12000U; +} + void normalize_timespec(struct timespec *ts) { if (ts->tv_nsec > 1000000000) { diff --git a/util.h b/util.h index 51405b4..5d32f05 100644 --- a/util.h +++ b/util.h @@ -4,17 +4,17 @@ // // Copyright (c) 2015 Oliver Jowett // -// This file is free software: you may copy, redistribute and/or modify it +// 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. +// 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 +// 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 +// You should have received a copy of the GNU General Public License // along with this program. If not, see . #ifndef DUMP1090_UTIL_H @@ -25,11 +25,20 @@ /* Returns system time in milliseconds */ uint64_t mstime(void); +/* Returns the time for the current message we're dealing with */ +extern uint64_t _messageNow; +static inline uint64_t messageNow() { + return _messageNow; +} + /* Returns the time elapsed, in nanoseconds, from t1 to t2, * where t1 and t2 are 12MHz counters. */ int64_t receiveclock_ns_elapsed(uint64_t t1, uint64_t t2); +/* Same, in milliseconds */ +int64_t receiveclock_ms_elapsed(uint64_t t1, uint64_t t2); + /* Normalize the value in ts so that ts->nsec lies in * [0,999999999] */ diff --git a/view1090.c b/view1090.c index c1e81f9..863cc83 100644 --- a/view1090.c +++ b/view1090.c @@ -67,10 +67,10 @@ void view1090Init(void) { pthread_cond_init(&Modes.data_cond,NULL); #ifdef _WIN32 - if ( (!Modes.wsaData.wVersion) + if ( (!Modes.wsaData.wVersion) && (!Modes.wsaData.wHighVersion) ) { // Try to start the windows socket support - if (WSAStartup(MAKEWORD(2,1),&Modes.wsaData) != 0) + if (WSAStartup(MAKEWORD(2,1),&Modes.wsaData) != 0) { fprintf(stderr, "WSAStartup returned Error\n"); } @@ -79,17 +79,17 @@ void view1090Init(void) { // Validate the users Lat/Lon home location inputs if ( (Modes.fUserLat > 90.0) // Latitude must be -90 to +90 - || (Modes.fUserLat < -90.0) // and + || (Modes.fUserLat < -90.0) // and || (Modes.fUserLon > 360.0) // Longitude must be -180 to +360 || (Modes.fUserLon < -180.0) ) { Modes.fUserLat = Modes.fUserLon = 0.0; } else if (Modes.fUserLon > 180.0) { // If Longitude is +180 to +360, make it -180 to 0 Modes.fUserLon -= 360.0; } - // If both Lat and Lon are 0.0 then the users location is either invalid/not-set, or (s)he's in the - // Atlantic ocean off the west coast of Africa. This is unlikely to be correct. - // Set the user LatLon valid flag only if either Lat or Lon are non zero. Note the Greenwich meridian - // is at 0.0 Lon,so we must check for either fLat or fLon being non zero not both. + // If both Lat and Lon are 0.0 then the users location is either invalid/not-set, or (s)he's in the + // Atlantic ocean off the west coast of Africa. This is unlikely to be correct. + // Set the user LatLon valid flag only if either Lat or Lon are non zero. Note the Greenwich meridian + // is at 0.0 Lon,so we must check for either fLat or fLon being non zero not both. // Testing the flag at runtime will be much quicker than ((fLon != 0.0) || (fLat != 0.0)) Modes.bUserFlags &= ~MODES_USER_LATLON_VALID; if ((Modes.fUserLat != 0.0) || (Modes.fUserLon != 0.0)) {