From e3a8e004128d4824959c0ac593ae4640347a12c3 Mon Sep 17 00:00:00 2001 From: Oliver Jowett Date: Thu, 29 Jul 2021 19:04:49 +0800 Subject: [PATCH] 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); }