From e3a8e004128d4824959c0ac593ae4640347a12c3 Mon Sep 17 00:00:00 2001 From: Oliver Jowett Date: Thu, 29 Jul 2021 19:04:49 +0800 Subject: [PATCH 01/11] WIP on MRAR decoding --- comm_b.c | 174 +++++++++++++++++++++++++++++++++++++++++++++++++++-- dump1090.h | 36 ++++++++++- mode_s.c | 40 ++++++++++++ 3 files changed, 245 insertions(+), 5 deletions(-) diff --git a/comm_b.c b/comm_b.c index 196ec14..19373c3 100644 --- a/comm_b.c +++ b/comm_b.c @@ -29,6 +29,7 @@ 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 decodeBDS44(struct modesMessage *mm, bool store); static int decodeBDS50(struct modesMessage *mm, bool store); static int decodeBDS60(struct modesMessage *mm, bool store); @@ -40,7 +41,8 @@ static CommBDecoderFn comm_b_decoders[] = { &decodeBDS17, &decodeBDS40, &decodeBDS50, - &decodeBDS60 + &decodeBDS60, + &decodeBDS44 }; void decodeCommB(struct modesMessage *mm) @@ -148,10 +150,10 @@ static int decodeBDS17(struct modesMessage *mm, bool store) score -= 2; } if (getbit(msg, 13)) { // 4,4 meterological routine report - score -= 2; + score -= 1; } if (getbit(msg, 14)) { // 4,4 meterological hazard report - score -= 2; + score -= 1; } if (getbit(msg, 20)) { // 5,4 waypoint 1 score -= 2; @@ -173,8 +175,11 @@ static int decodeBDS17(struct modesMessage *mm, bool store) } 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 if (!getbit(msg, 1) && !getbit(msg, 2) && getbit(msg, 3) && getbit(msg, 4) && getbit(msg, 5)) { + // ES with no position data + score += 3; } else { - // partial ES support, unlikely + // other combinations, unlikely score -= 12; } @@ -740,3 +745,164 @@ static int decodeBDS60(struct modesMessage *mm, bool store) return score; } + +// BDS4,4 Meterological routine air report +static int decodeBDS44(struct modesMessage *mm, bool store) +{ + unsigned char *msg = mm->MB; + + unsigned source = getbits(msg, 1, 4); + + unsigned wind_valid = getbit(msg, 5); + unsigned windspeed_raw = getbits(msg, 6, 14); + unsigned winddir_raw = getbits(msg, 15, 23); + + // ICAO 9871 is inconsistent, it claims: + // bit 24 sign + // bits 25..34 static air temperature, MSB = 64C, LSB=0.25C, range -128C..+128C + // + // .. but this does not actually work, there is one too many bits in the bitfield + // for the claimed values. + // + // Based on observed data, the most plausible actual layout is: + // + // bit 24 status + // bit 25 sign + // bits 26..34 static air temperature, MSB=64C, LSB=0.25C, range -128C..+128C + + unsigned sat_valid = getbit(msg, 24); + unsigned sat_sign = getbit(msg, 25); + unsigned sat_raw = getbits(msg, 26, 34); + + unsigned asp_valid = getbit(msg, 35); + unsigned asp_raw = getbits(msg, 36, 46); + + unsigned turbulence_valid = getbit(msg, 47); + unsigned turbulence_raw = getbits(msg, 48, 49); + + unsigned humidity_valid = getbit(msg, 50); + unsigned humidity_raw = getbits(msg, 51, 56); + + if (source == MRAR_SOURCE_INVALID || source > 5) + return 0; // invalid or reserved source + + if (!wind_valid || !sat_valid) + return 0; // all valid messages seen in the wild have at least temp + wind + + if (!asp_valid && asp_raw != 0) + return 0; // ASP not valid, but non-zero values in the ASP field + + if (!turbulence_valid && turbulence_raw != 0) + return 0; // turbulence not valid, but non-zero values in the turbulence field + + if (!humidity_valid && humidity_raw != 0) + return 0; // humidity not valid, but non-zero values in the humidity field + + int score = 0; + + float wind_speed = 0; + float wind_dir = 0; + if (wind_valid) { + wind_dir = winddir_raw * (180.0 / 256.0); + wind_speed = windspeed_raw; + + if (windspeed_raw == 0) // possible but uncommon + score += 2; + else if (wind_speed <= 250) + score += 19; + else + return 0; + } else { + score += 1; + } + + float sat = 0; + if (sat_valid) { + sat = sat_raw * 0.25; + if (sat_sign) + sat -= 128; + if (sat == 0) // possible but uncommon + score += 2; + else if (sat >= -80 && sat <= 60) + score += 11; + else + return 0; + } else { + score += 1; + } + + float asp = 0; + if (asp_valid) { + asp = asp_raw; + if (asp >= 25 && asp <= 1100) + score += 12; + else + return 0; + } else { + score += 1; + } + + hazard_t turbulence = HAZARD_NIL; + if (turbulence_valid) { + turbulence = (hazard_t) turbulence_raw; + score += 3; + } else { + score += 1; + } + + float humidity = 0; + if (humidity_valid) { + humidity = humidity_raw * (100.0 / 64.0); + score += 7; + } else { + score += 1; + } + + if (source == MRAR_SOURCE_DMEDME && wind_valid && sat_valid && score > 0) { + // Some GICB messages can be easily mistaken for a MRAR: + // + // GICB bit 1: BDS 0,5 ES airborne position = 0 ] + // GICB bit 2: BDS 0,6 ES surface position = 0 ] + // GICB bit 3: BDS 0,7 ES status = 1 ] + // GICB bit 4: BDS 0,8 ES type & identification = 1 ] -> MRAR source = 3 + // GICB bit 5: BDS 0,9 ES airborne velocity = 1 -> MRAR wind valid bit + // GICB bit 24: BDS 6,0 heading and speed report = 1 -> MRAR temp valid bit + // most trailing bits = 0 + // + // so only treat this as MRAR as a last resort + score = 1; + } + + if (store) { + mm->commb_format = COMMB_MRAR; + mm->mrar_source = (mrar_source_t) source; + + if (wind_valid) { + mm->wind_valid = 1; + mm->wind_speed = wind_speed; + mm->wind_dir = wind_dir; + } + + if (sat_valid) { + mm->temperature_valid = 1; + mm->temperature = sat; + } + + if (asp_valid) { + mm->pressure_valid = 1; + mm->pressure = asp; + } + + if (turbulence_valid) { + mm->turbulence_valid = 1; + mm->turbulence = turbulence; + } + + if (humidity_valid) { + mm->humidity_valid = 1; + mm->humidity = humidity; + } + } + + return score; +} diff --git a/dump1090.h b/dump1090.h index 2beeea7..96262c4 100644 --- a/dump1090.h +++ b/dump1090.h @@ -209,7 +209,8 @@ typedef enum { COMMB_ACAS_RA, COMMB_VERTICAL_INTENT, COMMB_TRACK_TURN, - COMMB_HEADING_SPEED + COMMB_HEADING_SPEED, + COMMB_MRAR } commb_format_t; typedef enum { @@ -237,6 +238,23 @@ typedef enum { NAV_ALT_INVALID, NAV_ALT_UNKNOWN, NAV_ALT_AIRCRAFT, NAV_ALT_MCP, NAV_ALT_FMS } nav_altitude_source_t; +// BDS4,4 MRAR - FOM/Source values +typedef enum { + MRAR_SOURCE_INVALID = 0, + MRAR_SOURCE_INS = 1, + MRAR_SOURCE_GNSS = 2, + MRAR_SOURCE_DMEDME = 3, + MRAR_SOURCE_VORDME = 4 +} mrar_source_t; + +// BDS4,4 and BDS4,5 hazard reports +typedef enum { + HAZARD_NIL = 0, + HAZARD_LIGHT = 1, + HAZARD_MODERATE = 2, + HAZARD_SEVERE = 3 +} hazard_t; + #define MODES_NON_ICAO_ADDRESS (1<<24) // Set on addresses to indicate they are not ICAO addresses #define MODES_INTERACTIVE_REFRESH_TIME 250 // Milliseconds @@ -611,6 +629,22 @@ struct modesMessage { nav_modes_t modes; } nav; + + // BDS 4,4 MRAR + unsigned mrar_source_valid : 1; + unsigned wind_valid : 1; + unsigned temperature_valid : 1; + unsigned pressure_valid : 1; + unsigned turbulence_valid : 1; + unsigned humidity_valid : 1; + + mrar_source_t mrar_source; + float wind_speed; // kts + float wind_dir; // degrees + float temperature; // degrees C + float pressure; // hPa + hazard_t turbulence; // NIL/LIGHT/MODERATE/SEVERE + float humidity; // 0-100 % }; // This one needs modesMessage: diff --git a/mode_s.c b/mode_s.c index 617a23c..f1c9ce0 100644 --- a/mode_s.c +++ b/mode_s.c @@ -1695,6 +1695,8 @@ static const char *commb_format_to_string(commb_format_t format) { return "BDS5,0 Track and turn report"; case COMMB_HEADING_SPEED: return "BDS6,0 Heading and speed report"; + case COMMB_MRAR: + return "BDS4,4 Meterological routine air report"; default: return "unknown format"; } @@ -1748,6 +1750,29 @@ static const char *emergency_to_string(emergency_t emergency) } } +static const char *mrar_source_to_string(mrar_source_t source) +{ + switch (source) { + case MRAR_SOURCE_INVALID: return "invalid"; + case MRAR_SOURCE_INS: return "INS"; + case MRAR_SOURCE_GNSS: return "GNSS"; + case MRAR_SOURCE_DMEDME: return "DME/DME"; + case MRAR_SOURCE_VORDME: return "VOR/DME"; + default: return "reserved"; + } +} + +static const char *hazard_to_string(hazard_t hazard) +{ + switch (hazard) { + case HAZARD_NIL: return "nil"; + case HAZARD_LIGHT: return "light"; + case HAZARD_MODERATE: return "moderate"; + case HAZARD_SEVERE: return "severe"; + default: return "invalid hazard severity"; + } +} + static void print_hex_bytes(unsigned char *data, size_t len) { size_t i; for (i = 0; i < len; ++i) { @@ -2213,6 +2238,21 @@ void displayModesMessage(struct modesMessage *mm) { printf(" Emergency/priority: %s\n", emergency_to_string(mm->emergency)); } + if (mm->mrar_source_valid) + printf(" MRAR FOM/Source: %s\n", mrar_source_to_string(mm->mrar_source)); + if (mm->wind_valid) { + printf(" Wind speed: %.0f kt\n", mm->wind_speed); + printf(" Wind direction: %.1f degrees\n", mm->wind_dir); + } + if (mm->temperature_valid) + printf(" Air temperature: %.1f degrees C\n", mm->temperature); + if (mm->pressure_valid) + printf(" Static pressure: %.0f hPa\n", mm->pressure); + if (mm->turbulence_valid) + printf(" Turbulence: %s\n", hazard_to_string(mm->turbulence)); + if (mm->humidity_valid) + printf(" Humidity: %.0f%%\n", mm->humidity); + printf("\n"); fflush(stdout); } From cdc818a9f3d0a952e0d6f913f11413dffcf459ea Mon Sep 17 00:00:00 2001 From: Oliver Jowett Date: Thu, 29 Jul 2021 19:04:50 +0800 Subject: [PATCH 02/11] MRAR source=5 is bogus, don't decode that. --- comm_b.c | 2 +- dump1090.h | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/comm_b.c b/comm_b.c index 19373c3..2cc2656 100644 --- a/comm_b.c +++ b/comm_b.c @@ -783,7 +783,7 @@ static int decodeBDS44(struct modesMessage *mm, bool store) unsigned humidity_valid = getbit(msg, 50); unsigned humidity_raw = getbits(msg, 51, 56); - if (source == MRAR_SOURCE_INVALID || source > 5) + if (source == MRAR_SOURCE_INVALID || source >= MRAR_SOURCE_RESERVED) return 0; // invalid or reserved source if (!wind_valid || !sat_valid) diff --git a/dump1090.h b/dump1090.h index 96262c4..31d48e2 100644 --- a/dump1090.h +++ b/dump1090.h @@ -244,7 +244,8 @@ typedef enum { MRAR_SOURCE_INS = 1, MRAR_SOURCE_GNSS = 2, MRAR_SOURCE_DMEDME = 3, - MRAR_SOURCE_VORDME = 4 + MRAR_SOURCE_VORDME = 4, + MRAR_SOURCE_RESERVED = 5 } mrar_source_t; // BDS4,4 and BDS4,5 hazard reports From 9f146fcb714930e6a0e2a318d66b0a72bdff7bed Mon Sep 17 00:00:00 2001 From: Oliver Jowett Date: Thu, 29 Jul 2021 19:04:51 +0800 Subject: [PATCH 03/11] Set mrar_source_valid correctly --- comm_b.c | 1 + 1 file changed, 1 insertion(+) diff --git a/comm_b.c b/comm_b.c index 2cc2656..e7e57a7 100644 --- a/comm_b.c +++ b/comm_b.c @@ -875,6 +875,7 @@ static int decodeBDS44(struct modesMessage *mm, bool store) if (store) { mm->commb_format = COMMB_MRAR; + mm->mrar_source_valid = 1; mm->mrar_source = (mrar_source_t) source; if (wind_valid) { From c148fdca846d4f5e465104ab0773bb4ab5a612c0 Mon Sep 17 00:00:00 2001 From: Oliver Jowett Date: Thu, 29 Jul 2021 19:04:51 +0800 Subject: [PATCH 04/11] Distinguish "unknown format" from "didn't even try to decode" when decoding Comm-B messages --- comm_b.c | 5 +++-- dump1090.h | 1 + mode_s.c | 2 ++ 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/comm_b.c b/comm_b.c index e7e57a7..1da2397 100644 --- a/comm_b.c +++ b/comm_b.c @@ -47,12 +47,11 @@ static CommBDecoderFn comm_b_decoders[] = { 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) { + mm->commb_format = COMMB_NOT_DECODED; return; } @@ -79,6 +78,8 @@ void decodeCommB(struct modesMessage *mm) // decode it bestDecoder(mm, true); } + } else { + mm->commb_format = COMMB_UNKNOWN; } } diff --git a/dump1090.h b/dump1090.h index 31d48e2..3864f43 100644 --- a/dump1090.h +++ b/dump1090.h @@ -202,6 +202,7 @@ typedef enum { typedef enum { COMMB_UNKNOWN, COMMB_AMBIGUOUS, + COMMB_NOT_DECODED, COMMB_EMPTY_RESPONSE, COMMB_DATALINK_CAPS, COMMB_GICB_CAPS, diff --git a/mode_s.c b/mode_s.c index f1c9ce0..5f7b666 100644 --- a/mode_s.c +++ b/mode_s.c @@ -1681,6 +1681,8 @@ static const char *commb_format_to_string(commb_format_t format) { return "empty response"; case COMMB_AMBIGUOUS: return "ambiguous format"; + case COMMB_NOT_DECODED: + return "not decoded"; case COMMB_DATALINK_CAPS: return "BDS1,0 Datalink capabilities"; case COMMB_GICB_CAPS: From 825f959e4da5548d0ae2cbf0537709cd27a687c4 Mon Sep 17 00:00:00 2001 From: Oliver Jowett Date: Thu, 29 Jul 2021 19:04:52 +0800 Subject: [PATCH 05/11] Add skeletal BDS0,5 decoding. These messages duplicate ADS-B messages so it's not clear why they are being interrogated for, and we don't really trust them enough to do anything with the position information. But recognizing/decoding them where possible does let us exclude other Comm-B message types, avoiding some false positives. --- comm_b.c | 52 +++++++++++++++++++++++++++++++++++++++++++++++++++- dump1090.h | 3 ++- mode_s.c | 2 ++ 3 files changed, 55 insertions(+), 2 deletions(-) diff --git a/comm_b.c b/comm_b.c index 1da2397..3afc5c3 100644 --- a/comm_b.c +++ b/comm_b.c @@ -32,6 +32,7 @@ static int decodeBDS40(struct modesMessage *mm, bool store); static int decodeBDS44(struct modesMessage *mm, bool store); static int decodeBDS50(struct modesMessage *mm, bool store); static int decodeBDS60(struct modesMessage *mm, bool store); +static int decodeBDS05(struct modesMessage *mm, bool store); static CommBDecoderFn comm_b_decoders[] = { &decodeEmptyResponse, @@ -42,7 +43,8 @@ static CommBDecoderFn comm_b_decoders[] = { &decodeBDS40, &decodeBDS50, &decodeBDS60, - &decodeBDS44 + &decodeBDS44, + &decodeBDS05 }; void decodeCommB(struct modesMessage *mm) @@ -908,3 +910,51 @@ static int decodeBDS44(struct modesMessage *mm, bool store) return score; } + +// BDS0,5 extended squitter airborne position +// (apparently this gets queried via comm-b sometimes??) +// We don't try to _use_ this as a position, but we can +// at least try to recognize it, to exclude other +// comm-b types (in particular they can be mistaken for MRAR) +static int decodeBDS05(struct modesMessage *mm, bool store) +{ + // We recognize these by matching the position altitude against + // the altitude in the surrounding message, so we need a + // DF20 not a DF21 + if (mm->msgtype != 20) + return 0; + + unsigned char *msg = mm->MB; + + unsigned typecode = getbits(msg, 1, 5); + if (typecode < 9 || typecode > 18) + return 0; // only consider typecodes that could be an airborne position with baro altitude + + unsigned t_bit = getbit(msg, 21); + if (t_bit) // unlikely + return 0; + + unsigned ac12 = getbits(msg, 9, 20); + if (!ac12) + return 0; + + // Insert M=0 to make an AC13 value, match against the + // AC13 value in the surrounding message + unsigned ac13 = ((ac12 & 0x0FC0) << 1) | (ac12 & 0x003F); + if (mm->AC != ac13) + return 0; // no altitude match + + unsigned lat = getbits(msg, 23, 39); + unsigned lon = getbits(msg, 40, 56); + if (lat == 0 || lon == 0) // unlikely position + return 0; + + if (store) { + mm->commb_format = COMMB_AIRBORNE_POSITION; + // No further decoding done, we don't really trust this + // enough to use as real input to CPR + } + + // Score this high enough to override everything else + return 100; +} diff --git a/dump1090.h b/dump1090.h index 3864f43..edcd43b 100644 --- a/dump1090.h +++ b/dump1090.h @@ -211,7 +211,8 @@ typedef enum { COMMB_VERTICAL_INTENT, COMMB_TRACK_TURN, COMMB_HEADING_SPEED, - COMMB_MRAR + COMMB_MRAR, + COMMB_AIRBORNE_POSITION } commb_format_t; typedef enum { diff --git a/mode_s.c b/mode_s.c index 5f7b666..6509abc 100644 --- a/mode_s.c +++ b/mode_s.c @@ -1699,6 +1699,8 @@ static const char *commb_format_to_string(commb_format_t format) { return "BDS6,0 Heading and speed report"; case COMMB_MRAR: return "BDS4,4 Meterological routine air report"; + case COMMB_AIRBORNE_POSITION: + return "BDS0,5 Extended squitter airborne position"; default: return "unknown format"; } From 9d4e2230d2f762bf0251b6d12c0066f409b54483 Mon Sep 17 00:00:00 2001 From: Oliver Jowett Date: Thu, 29 Jul 2021 19:04:52 +0800 Subject: [PATCH 06/11] Add a few more Comm-B EMPTY_RESPONSE cases --- comm_b.c | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/comm_b.c b/comm_b.c index 3afc5c3..a9b08fb 100644 --- a/comm_b.c +++ b/comm_b.c @@ -87,7 +87,32 @@ void decodeCommB(struct modesMessage *mm) static int decodeEmptyResponse(struct modesMessage *mm, bool store) { - for (unsigned i = 0; i < 7; ++i) { + // 00000000000000 is a common response. Ignore it. + // + // Also, it's common to see responses that look like this: + // 40000000000000 + // 50000000000000 + // 60000000000000 + // typically in grouped bursts (one of each message) from + // the same aircraft. + // + // I speculate that these are response to interrogations for + // BDS 4,0 5,0 and 6,0 respectively where the transponder + // doesn't support the register or has no data loaded for it. + // Treat them like empty responses. + + switch (mm->MB[0]) { + case 0x00: + case 0x40: + case 0x50: + case 0x60: + break; + + default: + return 0; + } + + for (unsigned i = 1; i < 7; ++i) { if (mm->MB[i] != 0) { return 0; } From fab8081322ddf75de39a26a514ee3195d14909d1 Mon Sep 17 00:00:00 2001 From: Oliver Jowett Date: Thu, 29 Jul 2021 19:04:53 +0800 Subject: [PATCH 07/11] Upload Comm-B GICB messages --- net_io.c | 8 ++++++++ track.h | 1 + 2 files changed, 9 insertions(+) diff --git a/net_io.c b/net_io.c index a257d94..61b7f65 100644 --- a/net_io.c +++ b/net_io.c @@ -2352,6 +2352,14 @@ static void writeFATSVEvent(struct modesMessage *mm, struct aircraft *a) } break; + case COMMB_GICB_CAPS: + // BDS 1,7: common usage GICB capability report + if (memcmp(mm->MB, a->fatsv_emitted_bds_17, 7) != 0) { + memcpy(a->fatsv_emitted_bds_17, mm->MB, 7); + writeFATSVEventMessage(mm, "gicb_caps", mm->MB, 7); + } + break; + default: // nothing break; diff --git a/track.h b/track.h index ade1267..4bad096 100644 --- a/track.h +++ b/track.h @@ -250,6 +250,7 @@ struct aircraft { 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_17[7]; // -"- BDS 1,7 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_acas_ra[7]; // -"- ES ACAS RA report message From 95ab1c0faa2a76e9e5cccaf7b60b9cbabcf6892b Mon Sep 17 00:00:00 2001 From: Oliver Jowett Date: Thu, 29 Jul 2021 19:04:53 +0800 Subject: [PATCH 08/11] faup1090: upload unknown-format Comm-B messages on demand --- dump1090.h | 1 + net_io.c | 19 ++++++++++++++++++- track.h | 1 + 3 files changed, 20 insertions(+), 1 deletion(-) diff --git a/dump1090.h b/dump1090.h index edcd43b..0ec6def 100644 --- a/dump1090.h +++ b/dump1090.h @@ -385,6 +385,7 @@ struct _Modes { // Internal state uint64_t json_stats_interval; // Interval between rewriting the json stats file, in milliseconds int json_location_accuracy; // Accuracy of location metadata: 0=none, 1=approx, 2=exact double faup_rate_multiplier; // Multiplier to adjust rate of faup1090 messages emitted + bool faup_upload_unknown_commb; // faup1090: should we upload Comm-B messages that weren't in a recognized format? int json_aircraft_history_next; struct { diff --git a/net_io.c b/net_io.c index 61b7f65..eb56672 100644 --- a/net_io.c +++ b/net_io.c @@ -1126,7 +1126,7 @@ static int handleFaupCommand(struct client *c, char *p) { // Traverse through message for commands while (msg_field != NULL) { - if (strcmp(msg_field, "upload_rate_multiplier") == 0) { + if (!strcmp(msg_field, "upload_rate_multiplier")) { msg_field = strtok (NULL, "\t"); multiplier = atof(msg_field); @@ -1140,6 +1140,14 @@ static int handleFaupCommand(struct client *c, char *p) { Modes.faup_rate_multiplier = multiplier; break; } + + if (!strcmp(msg_field, "upload_unknown_commb")) { + msg_field = strtok (NULL, "\t"); + unsigned enable = atoi(msg_field); + fprintf(stderr, "handleFaupCommand(): %s upload of unknown Comm-B messages\n", enable ? "Enabling" : "Disabling"); + Modes.faup_upload_unknown_commb = enable; + break; + } msg_field = strtok (NULL, "\t"); } @@ -2360,6 +2368,15 @@ static void writeFATSVEvent(struct modesMessage *mm, struct aircraft *a) } break; + case COMMB_UNKNOWN: + // If enabled, upload raw unrecognized Comm-B messages + // for server-side analysis + if (Modes.faup_upload_unknown_commb && memcmp(mm->MB, a->fatsv_emitted_unknown_commb, 7) != 0) { + memcpy(a->fatsv_emitted_unknown_commb, mm->MB, 7); + writeFATSVEventMessage(mm, "unknown_commb", mm->MB, 7); + } + break; + default: // nothing break; diff --git a/track.h b/track.h index 4bad096..5fc04f4 100644 --- a/track.h +++ b/track.h @@ -252,6 +252,7 @@ struct aircraft { unsigned char fatsv_emitted_bds_10[7]; // -"- BDS 1,0 message unsigned char fatsv_emitted_bds_17[7]; // -"- BDS 1,7 message unsigned char fatsv_emitted_bds_30[7]; // -"- BDS 3,0 message + unsigned char fatsv_emitted_unknown_commb[7]; // -"- unrecognized Comm-B message unsigned char fatsv_emitted_es_status[7]; // -"- ES operational status message unsigned char fatsv_emitted_es_acas_ra[7]; // -"- ES ACAS RA report message char fatsv_emitted_callsign[9]; // -"- callsign From e0f794b1a5bdba914883a53ea837d8aeec4401e3 Mon Sep 17 00:00:00 2001 From: Oliver Jowett Date: Thu, 29 Jul 2021 19:04:54 +0800 Subject: [PATCH 09/11] Track MRAR data per-aircraft and emit in aircraft.json --- net_io.c | 36 +++++++++++++++++++++++++++++++++++- track.c | 37 +++++++++++++++++++++++++++++++++++++ track.h | 16 ++++++++++++++++ 3 files changed, 88 insertions(+), 1 deletion(-) diff --git a/net_io.c b/net_io.c index eb56672..e4d7882 100644 --- a/net_io.c +++ b/net_io.c @@ -1612,6 +1612,29 @@ static const char *nav_altitude_source_enum_string(nav_altitude_source_t src) } } +static const char *mrar_source_enum_string(mrar_source_t src) +{ + switch (src) { + case MRAR_SOURCE_INVALID: return "invalid"; + case MRAR_SOURCE_INS: return "ins"; + case MRAR_SOURCE_GNSS: return "gnss"; + case MRAR_SOURCE_DMEDME: return "dmedme"; + case MRAR_SOURCE_VORDME: return "vordme"; + default: return "reserved"; + } +} + +static const char *hazard_enum_string(hazard_t hazard) +{ + switch (hazard) { + case HAZARD_NIL: return "nil"; + case HAZARD_LIGHT: return "light"; + case HAZARD_MODERATE: return "moderate"; + case HAZARD_SEVERE: return "severe"; + default: return "invalid"; + } +} + char *generateAircraftJson(const char *url_path, int *len) { uint64_t now = mstime(); struct aircraft *a; @@ -1715,12 +1738,23 @@ char *generateAircraftJson(const char *url_path, int *len) { p = safe_snprintf(p, end, ",\"gva\":%u", a->gva); if (trackDataValid(&a->sda_valid)) p = safe_snprintf(p, end, ",\"sda\":%u", a->sda); + if (trackDataValid(&a->mrar_source_valid)) + p = safe_snprintf(p, end, ",\"mrar_source\":\"%s\"", mrar_source_enum_string(a->mrar_source)); + if (trackDataValid(&a->wind_valid)) + p = safe_snprintf(p, end, ",\"wind_speed\":%.0f,\"wind_dir\":%.1f", a->wind_speed, a->wind_dir); + if (trackDataValid(&a->temperature_valid)) + p = safe_snprintf(p, end, ",\"temperature\":%.2f", a->temperature); + if (trackDataValid(&a->pressure_valid)) + p = safe_snprintf(p, end, ",\"pressure\":%.0f", a->pressure); + if (trackDataValid(&a->turbulence_valid)) + p = safe_snprintf(p, end, ",\"turbulence\":\"%s\"", hazard_enum_string(a->turbulence)); + if (trackDataValid(&a->humidity_valid)) + p = safe_snprintf(p, end, ",\"humidity\":%.1f", a->humidity); if (a->modeA_hit) p = safe_snprintf(p, end, ",\"modea\":true"); if (a->modeC_hit) p = safe_snprintf(p, end, ",\"modec\":true"); - p = safe_snprintf(p, end, ",\"mlat\":"); p = append_flags(p, end, a, SOURCE_MLAT); p = safe_snprintf(p, end, ",\"tisb\":"); diff --git a/track.c b/track.c index aab634e..ac9590d 100644 --- a/track.c +++ b/track.c @@ -130,6 +130,12 @@ static struct aircraft *trackCreateAircraft(struct modesMessage *mm) { F(sil, 60, 70); // ADS-B only F(gva, 60, 70); // ADS-B only F(sda, 60, 70); // ADS-B only + F(mrar_source, 60, 70); // Comm-B only + F(wind, 60, 70); // Comm-B only + F(temperature, 60, 70); // Comm-B only + F(pressure, 60, 70); // Comm-B only + F(turbulence, 60, 70); // Comm-B only + F(humidity, 60, 70); // Comm-B only #undef F Modes.stats_current.unique_aircraft++; @@ -1237,6 +1243,31 @@ struct aircraft *trackUpdateFromMessage(struct modesMessage *mm) a->sda = mm->accuracy.sda; } + if (mm->mrar_source_valid && accept_data(&a->mrar_source_valid, mm->source)) { + a->mrar_source = mm->mrar_source; + } + + if (mm->wind_valid && accept_data(&a->wind_valid, mm->source)) { + a->wind_speed = mm->wind_speed; + a->wind_dir = mm->wind_dir; + } + + if (mm->temperature_valid && accept_data(&a->temperature_valid, mm->source)) { + a->temperature = mm->temperature; + } + + if (mm->pressure_valid && accept_data(&a->pressure_valid, mm->source)) { + a->pressure = mm->pressure; + } + + if (mm->turbulence_valid && accept_data(&a->turbulence_valid, mm->source)) { + a->turbulence = mm->turbulence; + } + + if (mm->humidity_valid && accept_data(&a->humidity_valid, mm->source)) { + a->humidity = mm->humidity; + } + // Now handle derived data // derive geometric altitude if we have baro + delta @@ -1400,6 +1431,12 @@ static void trackRemoveStaleAircraft(uint64_t now) EXPIRE(sil); EXPIRE(gva); EXPIRE(sda); + EXPIRE(mrar_source); + EXPIRE(wind); + EXPIRE(temperature); + EXPIRE(pressure); + EXPIRE(turbulence); + EXPIRE(humidity); #undef EXPIRE prev = a; a = a->next; } diff --git a/track.h b/track.h index 5fc04f4..8968833 100644 --- a/track.h +++ b/track.h @@ -226,6 +226,22 @@ struct aircraft { unsigned gva : 2; // GVA from opstatus unsigned sda : 2; // SDA from opstatus + // data extracted from MRAR + data_validity mrar_source_valid; + data_validity wind_valid; // speed and direction + data_validity pressure_valid; + data_validity temperature_valid; + data_validity turbulence_valid; + data_validity humidity_valid; + + mrar_source_t mrar_source; + float wind_speed; + float wind_dir; + float pressure; + float temperature; + hazard_t turbulence; + float humidity; + 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? From fe36349083b41e1a6a2513089c11b07608fbf794 Mon Sep 17 00:00:00 2001 From: Oliver Jowett Date: Thu, 29 Jul 2021 19:04:54 +0800 Subject: [PATCH 10/11] faup1090: upload MRAR data --- net_io.c | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/net_io.c b/net_io.c index e4d7882..b551e4e 100644 --- a/net_io.c +++ b/net_io.c @@ -2585,7 +2585,13 @@ static void writeFATSV() (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); + (trackDataValid(&a->emergency_valid) && a->emergency != a->fatsv_emitted_emergency) || + (trackDataValid(&a->mrar_source_valid) && a->mrar_source_valid.updated > a->fatsv_last_emitted) || + (trackDataValid(&a->wind_valid) && a->wind_valid.updated > a->fatsv_last_emitted) || + (trackDataValid(&a->pressure_valid) && a->pressure_valid.updated > a->fatsv_last_emitted) || + (trackDataValid(&a->temperature_valid) && a->temperature_valid.updated > a->fatsv_last_emitted) || + (trackDataValid(&a->turbulence_valid) && a->turbulence_valid.updated > a->fatsv_last_emitted) || + (trackDataValid(&a->humidity_valid) && a->humidity_valid.updated > a->fatsv_last_emitted); uint64_t minAge; double adjustedMinAge; @@ -2687,10 +2693,17 @@ static void writeFATSV() p = appendFATSVMeta(p, end, "nav_alt_mcp", a, &a->nav_altitude_mcp_valid, "%u", a->nav_altitude_mcp); p = appendFATSVMeta(p, end, "nav_alt_fms", a, &a->nav_altitude_fms_valid, "%u", a->nav_altitude_fms); p = appendFATSVMeta(p, end, "nav_alt_src", a, &a->nav_altitude_src_valid, "%s", nav_altitude_source_enum_string(a->nav_altitude_src)); - 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)); + 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)); + p = appendFATSVMeta(p, end, "mrar_source", a, &a->mrar_source_valid, "%s", mrar_source_enum_string(a->mrar_source)); + p = appendFATSVMeta(p, end, "wind_speed", a, &a->wind_valid, "%.0f", a->wind_speed); + p = appendFATSVMeta(p, end, "wind_dir", a, &a->wind_valid, "%.1f", a->wind_dir); + p = appendFATSVMeta(p, end, "temperature", a, &a->temperature_valid, "%.2f", a->temperature); + p = appendFATSVMeta(p, end, "pressure", a, &a->pressure_valid, "%.0f", a->pressure); + p = appendFATSVMeta(p, end, "turbulence", a, &a->turbulence_valid, "%s", hazard_enum_string(a->turbulence)); + p = appendFATSVMeta(p, end, "humidity", a, &a->humidity_valid, "%.0f", a->humidity); // if we didn't get anything interesting, bail out. // We don't need to do anything special to unwind prepareWrite(). From 3367cc5a82bb5d34a59b146e4e45b589ac9e7519 Mon Sep 17 00:00:00 2001 From: Oliver Jowett Date: Thu, 29 Jul 2021 19:04:54 +0800 Subject: [PATCH 11/11] faup1090: bump TSV_VERSION to 9E for MRAR, Comm-B changes --- net_io.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/net_io.c b/net_io.c index b551e4e..037411e 100644 --- a/net_io.c +++ b/net_io.c @@ -2299,7 +2299,7 @@ __attribute__ ((format (printf,4,5))) static char *appendFATSV(char *p, char *en } #define TSV_MAX_PACKET_SIZE 800 -#define TSV_VERSION "8E" +#define TSV_VERSION "9E" static void writeFATSVPositionUpdate(float lat, float lon, float alt) {