From 1ec32903bae3f080a3e9313614e0fa13007b97ca Mon Sep 17 00:00:00 2001 From: Oliver Jowett Date: Thu, 15 Jun 2017 21:07:53 +0100 Subject: [PATCH] Rework heading/track to include HRD/TAH. Clean up TIS-B flag output. --- comm_b.c | 10 ++++--- dump1090.h | 23 +++++++------- mode_s.c | 58 ++++++++++++++++++++++------------- net_io.c | 88 +++++++++++++++++++++++++++++++++++++++++++----------- track.c | 39 ++++++++++++++++++++---- track.h | 12 ++++++-- 6 files changed, 169 insertions(+), 61 deletions(-) diff --git a/comm_b.c b/comm_b.c index 085454b..80f9022 100644 --- a/comm_b.c +++ b/comm_b.c @@ -552,8 +552,9 @@ static int decodeBDS50(struct modesMessage *mm, bool store) } if (track_valid) { - mm->track_valid = 1; - mm->track = track; + mm->heading_valid = 1; + mm->heading = track; + mm->heading_type = HEADING_GROUND_TRACK; } if (gs_valid) { @@ -704,8 +705,9 @@ static int decodeBDS60(struct modesMessage *mm, bool store) mm->commb_format = COMMB_HEADING_SPEED; if (heading_valid) { - mm->mag_heading_valid = 1; - mm->mag_heading = heading; + mm->heading_valid = 1; + mm->heading = heading; + mm->heading_type = HEADING_MAGNETIC; } if (ias_valid) { diff --git a/dump1090.h b/dump1090.h index 2e4408b..7da9819 100644 --- a/dump1090.h +++ b/dump1090.h @@ -177,11 +177,6 @@ typedef enum { AG_UNCERTAIN } airground_t; -typedef enum { - HEADING_TRUE, - HEADING_MAGNETIC -} heading_source_t; - typedef enum { SIL_PER_SAMPLE, SIL_PER_HOUR } sil_type_t; @@ -190,6 +185,14 @@ typedef enum { CPR_SURFACE, CPR_AIRBORNE, CPR_COARSE } cpr_type_t; +typedef enum { + 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 // HEADING_GROUND_TRACK or HEADING_REF_DIR depending on the TAH bit in opstatus +} heading_type_t; + typedef enum { COMMB_UNKNOWN, COMMB_EMPTY_RESPONSE, @@ -414,7 +417,7 @@ struct modesMessage { unsigned altitude_valid : 1; unsigned track_valid : 1; unsigned track_rate_valid : 1; - unsigned mag_heading_valid : 1; + unsigned heading_valid : 1; unsigned roll_valid : 1; unsigned gs_valid : 1; unsigned ias_valid : 1; @@ -449,9 +452,9 @@ struct modesMessage { // following fields are valid if the corresponding _valid field is set: int geom_delta; // Difference between geometric and baro alt - float track; // True ground track, degrees (0-359). Reported directly or computed from from EW and NS velocity + 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 mag_heading; // Magnetic heading, degrees (0-359) float roll; // Roll, degrees, negative is left roll unsigned gs; // Groundspeed, kts, reported directly or computed from from EW and NS velocity unsigned ias; // Indicated airspeed, kts @@ -506,8 +509,8 @@ struct modesMessage { 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; diff --git a/mode_s.c b/mode_s.c index 7f087ce..f5c1405 100644 --- a/mode_s.c +++ b/mode_s.c @@ -765,12 +765,13 @@ static void decodeESAirborneVelocity(struct modesMessage *mm, int check_imf) mm->gs_valid = 1; if (mm->gs) { - float heading = atan2(ew_vel, ns_vel) * 180.0 / M_PI; + 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->track = heading; - mm->track_valid = 1; + if (ground_track < 0) + ground_track += 360; + mm->heading = ground_track; + mm->heading_type = HEADING_GROUND_TRACK; + mm->heading_valid = 1; } } break; @@ -791,8 +792,9 @@ static void decodeESAirborneVelocity(struct modesMessage *mm, int check_imf) } if (getbit(me, 14)) { - mm->mag_heading_valid = 1; - mm->mag_heading = getbits(me, 15, 24) * 360.0 / 1024.0; + mm->heading_valid = 1; + mm->heading = getbits(me, 15, 24) * 360.0 / 1024.0; + mm->heading_type = HEADING_MAGNETIC_OR_TRUE; } break; } @@ -828,8 +830,9 @@ static void decodeESSurfacePosition(struct modesMessage *mm, int check_imf) } if (getbit(me, 13)) { - mm->track_valid = 1; - mm->track = getbits(me, 14, 20) * 360.0 / 128.0; + mm->heading_valid = 1; + mm->heading = getbits(me, 14, 20) * 360.0 / 128.0; + mm->heading_type = HEADING_TRACK_OR_HEADING; } } @@ -1022,12 +1025,12 @@ static void decodeESOperationalStatus(struct modesMessage *mm, int check_imf) mm->opstatus.nic_supp_a = getbit(me, 44); mm->opstatus.nac_p = getbits(me, 45, 48); mm->opstatus.sil = getbits(me, 51, 52); + mm->opstatus.hrd = getbit(me, 54) ? HEADING_MAGNETIC : HEADING_TRUE; if (mm->mesub == 0) { mm->opstatus.nic_baro = getbit(me, 53); } else { - mm->opstatus.track_angle = getbit(me, 53) ? ANGLE_TRACK : ANGLE_HEADING; + mm->opstatus.tah = getbit(me, 53) ? HEADING_GROUND_TRACK : mm->opstatus.hrd; } - mm->opstatus.hrd = getbit(me, 54) ? HEADING_MAGNETIC : HEADING_TRUE; break; case 2: @@ -1064,13 +1067,13 @@ static void decodeESOperationalStatus(struct modesMessage *mm, int check_imf) mm->opstatus.nic_supp_a = getbit(me, 44); mm->opstatus.nac_p = getbits(me, 45, 48); mm->opstatus.sil = getbits(me, 51, 52); + mm->opstatus.hrd = getbit(me, 54) ? HEADING_MAGNETIC : HEADING_TRUE; 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->opstatus.tah = getbit(me, 53) ? HEADING_GROUND_TRACK : mm->opstatus.hrd; } - mm->opstatus.hrd = getbit(me, 54) ? HEADING_MAGNETIC : HEADING_TRUE; mm->opstatus.sil_type = getbit(me, 55) ? SIL_PER_SAMPLE : SIL_PER_HOUR; break; } @@ -1299,6 +1302,23 @@ 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: @@ -1585,12 +1605,8 @@ void displayModesMessage(struct modesMessage *mm) { mm->geom_delta); } - if (mm->track_valid) { - printf(" Track: %.1f\n", mm->track); - } - - if (mm->mag_heading_valid) { - printf(" Mag heading: %.1f\n", mm->mag_heading); + if (mm->heading_valid) { + printf(" %-13s %.1f\n", heading_type_to_string(mm->heading_type), mm->heading); } if (mm->track_rate_valid) { @@ -1701,8 +1717,8 @@ void displayModesMessage(struct modesMessage *mm) { 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->intent.valid) { diff --git a/net_io.c b/net_io.c index b0b9a36..59ea606 100644 --- a/net_io.c +++ b/net_io.c @@ -604,14 +604,14 @@ static void modesSendSBSOutput(struct modesMessage *mm, struct aircraft *a) { // Field 13 is the ground Speed (if we have it) if (mm->gs_valid) { - p += sprintf(p, ",%d", mm->gs); + p += sprintf(p, ",%u", mm->gs); } else { p += sprintf(p, ","); } // Field 14 is the ground Heading (if we have it) - if (mm->track_valid) { - p += sprintf(p, ",%.0f", mm->track); + if (mm->heading_valid && mm->heading_type == HEADING_GROUND_TRACK) { + p += sprintf(p, ",%.0f", mm->heading); } else { p += sprintf(p, ","); } @@ -1201,6 +1201,10 @@ char *generateAircraftJson(const char *url_path, int *len) { p += snprintf(p, end-p, ",\"track\":%.1f", a->track); if (trackDataValid(&a->track_rate_valid)) p += snprintf(p, end-p, ",\"track_rate\":%.2f", a->track_rate); + if (trackDataValid(&a->mag_heading_valid)) + p += snprintf(p, end-p, ",\"mag_heading\":%.1f", a->mag_heading); + if (trackDataValid(&a->true_heading_valid)) + p += snprintf(p, end-p, ",\"true_heading\":%.1f", a->true_heading); if (trackDataValid(&a->gs_valid)) p += snprintf(p, end-p, ",\"gs\":%u", a->gs); if (trackDataValid(&a->ias_valid)) @@ -1821,18 +1825,47 @@ static void writeFATSVEvent(struct modesMessage *mm, struct aircraft *a) typedef enum { TISB_IDENT = 1, TISB_SQUAWK = 2, - TISB_ALTITUDE = 4, - TISB_ALTITUDE_GEOM = 8, + TISB_ALT = 4, + TISB_ALT_GEOM = 8, TISB_GS = 16, TISB_IAS = 32, TISB_TAS = 64, - TISB_POSITION = 128, - TISB_TRACK = 256, - TISB_MAG_HEADING = 512, - TISB_AIRGROUND = 1024, - TISB_CATEGORY = 2048 + TISB_LAT = 128, + TISB_LON = 256, + TISB_TRACK = 512, + TISB_MAG_HEADING = 1024, + TISB_TRUE_HEADING = 2048, + TISB_AIRGROUND = 4096, + TISB_CATEGORY = 8192, + TISB_INTENT_ALT = 16384, + TISB_INTENT_HEADING = 32768, + TISB_ALT_SETTING = 65536 } tisb_flags; +struct { + tisb_flags flag; + const char *name; +} tisb_flag_names[] = { + { TISB_IDENT, "ident" }, + { TISB_SQUAWK, "squawk" }, + { TISB_ALT, "alt" }, + { TISB_ALT_GEOM, "alt_geom" }, + { TISB_GS, "gs" }, + { TISB_IAS, "ias" }, + { TISB_TAS, "tas" }, + { TISB_LAT, "lat" }, + { TISB_LON, "lat" }, + { TISB_TRACK, "track" }, + { TISB_MAG_HEADING, "mag_heading" }, + { TISB_TRUE_HEADING, "true_heading" }, + { TISB_AIRGROUND, "airGround" }, + { TISB_CATEGORY, "category" }, + { TISB_INTENT_ALT, "intent_alt" }, + { TISB_INTENT_HEADING, "intent_heading" }, + { TISB_ALT_SETTING, "alt_setting" }, + { 0, NULL } +}; + static inline unsigned unsigned_difference(unsigned v1, unsigned v2) { return (v1 > v2) ? (v1 - v2) : (v2 - v1); @@ -1887,6 +1920,7 @@ static void writeFATSV() int trackValid = trackDataValidEx(&a->track_valid, now, 15000, SOURCE_MODE_S); // Comm-B or ES int trackRateValid = trackDataValidEx(&a->track_rate_valid, now, 15000, SOURCE_MODE_S); // Comm-B int rollValid = trackDataValidEx(&a->roll_valid, now, 15000, SOURCE_MODE_S); // Comm-B + int trueHeadingValid = trackDataValidEx(&a->true_heading_valid, now, 15000, SOURCE_MODE_S); // Comm-B or ES int magHeadingValid = trackDataValidEx(&a->mag_heading_valid, now, 15000, SOURCE_MODE_S); // Comm-B or ES int gsValid = trackDataValidEx(&a->gs_valid, now, 15000, SOURCE_MODE_S); // Comm-B or ES int iasValid = trackDataValidEx(&a->ias_valid, now, 15000, SOURCE_MODE_S); // Comm-B or ES @@ -1921,7 +1955,7 @@ static void writeFATSV() if (geomRateValid && abs(a->geom_rate - a->fatsv_emitted_geom_rate) > 500) { changed = 1; } - if (trackValid && heading_difference(a->track, a->fatsv_emitted_heading) >= 2) { + if (trackValid && heading_difference(a->track, a->fatsv_emitted_track) >= 2) { changed = 1; } if (trackRateValid && fabs(a->track_rate - a->fatsv_emitted_track_rate) >= 0.5) { @@ -1930,7 +1964,10 @@ static void writeFATSV() if (rollValid && fabs(a->roll - a->fatsv_emitted_roll) >= 5.0) { changed = 1; } - if (magHeadingValid && heading_difference(a->mag_heading, a->fatsv_emitted_heading_magnetic) >= 2) { + if (magHeadingValid && heading_difference(a->mag_heading, a->fatsv_emitted_mag_heading) >= 2) { + changed = 1; + } + if (trueHeadingValid && heading_difference(a->true_heading, a->fatsv_emitted_true_heading) >= 2) { changed = 1; } if (gsValid && unsigned_difference(a->gs, a->fatsv_emitted_speed) >= 25) { @@ -2039,14 +2076,14 @@ static void writeFATSV() 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; + tisb |= (a->altitude_valid.source == SOURCE_TISB) ? TISB_ALT : 0; } if (altGeomValid && a->altitude_geom_valid.updated > a->fatsv_last_emitted) { p += snprintf(p, bufsize(p,end), "\talt_geom\t%d", a->altitude_geom); a->fatsv_emitted_altitude_gnss = a->altitude_geom; useful = 1; - tisb |= (a->altitude_geom_valid.source == SOURCE_TISB) ? TISB_ALTITUDE_GEOM : 0; + tisb |= (a->altitude_geom_valid.source == SOURCE_TISB) ? TISB_ALT_GEOM : 0; } if (baroRateValid && a->baro_rate_valid.updated > a->fatsv_last_emitted) { @@ -2091,12 +2128,12 @@ static void writeFATSV() 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; + tisb |= (a->position_valid.source == SOURCE_TISB) ? (TISB_LAT | TISB_LON) : 0; } if (trackValid && a->track_valid.updated > a->fatsv_last_emitted) { p += snprintf(p, bufsize(p,end), "\ttrack\t%.0f", a->track); - a->fatsv_emitted_heading = a->track; + a->fatsv_emitted_track = a->track; useful = 1; tisb |= (a->track_valid.source == SOURCE_TISB) ? TISB_TRACK : 0; } @@ -2115,11 +2152,18 @@ static void writeFATSV() if (magHeadingValid && a->mag_heading_valid.updated > a->fatsv_last_emitted) { p += snprintf(p, bufsize(p,end), "\tmag_heading\t%.0f", a->mag_heading); - a->fatsv_emitted_heading_magnetic = a->mag_heading; + a->fatsv_emitted_mag_heading = a->mag_heading; useful = 1; tisb |= (a->mag_heading_valid.source == SOURCE_TISB) ? TISB_MAG_HEADING : 0; } + if (trueHeadingValid && a->true_heading_valid.updated > a->fatsv_last_emitted) { + p += snprintf(p, bufsize(p,end), "\true_heading\t%.0f", a->true_heading); + a->fatsv_emitted_true_heading = a->true_heading; + useful = 1; + tisb |= (a->true_heading_valid.source == SOURCE_TISB) ? TISB_TRUE_HEADING : 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; @@ -2138,18 +2182,21 @@ static void writeFATSV() p += snprintf(p, bufsize(p,end), "\tintent_alt\t%u", a->intent_altitude); a->fatsv_emitted_intent_altitude = a->intent_altitude; useful = 1; + tisb |= (a->category_valid.source == SOURCE_TISB) ? TISB_INTENT_ALT : 0; } if (intentHeadingValid && a->intent_heading_valid.updated > a->fatsv_last_emitted) { p += snprintf(p, bufsize(p,end), "\tintent_heading\t%.0f", a->intent_heading); a->fatsv_emitted_intent_heading = a->intent_heading; useful = 1; + tisb |= (a->category_valid.source == SOURCE_TISB) ? TISB_INTENT_HEADING : 0; } if (altSettingValid && a->alt_setting_valid.updated > a->fatsv_last_emitted) { p += snprintf(p, bufsize(p,end), "\talt_setting\t%.1f", a->alt_setting); a->fatsv_emitted_alt_setting = a->alt_setting; useful = 1; + tisb |= (a->category_valid.source == SOURCE_TISB) ? TISB_ALT_SETTING : 0; } @@ -2160,7 +2207,12 @@ static void writeFATSV() } if (tisb != 0) { - p += snprintf(p, bufsize(p,end), "\ttisb\t%d", (int)tisb); + p += snprintf(p, bufsize(p,end), "\ttisb\t"); + for (int i = 0; tisb_flag_names[i].name; ++i) { + if (tisb & tisb_flag_names[i].flag) { + p += snprintf(p, bufsize(p,end), "%s ", tisb_flag_names[i].name); + } + } } p += snprintf(p, bufsize(p,end), "\n"); diff --git a/track.c b/track.c index b2ae5dd..f1b9020 100644 --- a/track.c +++ b/track.c @@ -81,6 +81,11 @@ struct aircraft *trackCreateAircraft(struct modesMessage *mm) { a->fatsv_emitted_bds_30[0] = 0x30; a->fatsv_emitted_es_acas_ra[0] = 0xE2; + // defaults until we see an op status message + a->adsb_version = -1; + a->adsb_hrd = HEADING_MAGNETIC; + a->adsb_tah = HEADING_GROUND_TRACK; + // Copy the first message so we can emit it later when a second message arrives. a->first_message = *mm; @@ -543,6 +548,10 @@ struct aircraft *trackUpdateFromMessage(struct modesMessage *mm) if (mm->addrtype < a->addrtype) a->addrtype = mm->addrtype; + // 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; + if (mm->altitude_valid && mm->altitude_source == ALTITUDE_BARO && accept_data(&a->altitude_valid, mm->source, now)) { if (a->modeC_hit) { int new_modeC = (a->altitude + 49) / 100; @@ -570,8 +579,21 @@ struct aircraft *trackUpdateFromMessage(struct modesMessage *mm) a->geom_delta = mm->geom_delta; } - if (mm->track_valid && accept_data(&a->track_valid, mm->source, now)) { - a->track = mm->track; + 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, now)) { + a->track = mm->heading; + } else if (htype == HEADING_MAGNETIC && accept_data(&a->mag_heading_valid, mm->source, now)) { + a->mag_heading = mm->heading; + } else if (htype == HEADING_TRUE && accept_data(&a->true_heading_valid, mm->source, now)) { + a->true_heading = mm->heading; + } } if (mm->track_rate_valid && accept_data(&a->track_rate_valid, mm->source, now)) { @@ -582,10 +604,6 @@ struct aircraft *trackUpdateFromMessage(struct modesMessage *mm) a->roll = mm->roll; } - if (mm->mag_heading_valid && accept_data(&a->mag_heading_valid, mm->source, now)) { - a->mag_heading = mm->mag_heading; - } - if (mm->gs_valid && accept_data(&a->gs_valid, mm->source, now)) { a->gs = mm->gs; } @@ -654,6 +672,15 @@ struct aircraft *trackUpdateFromMessage(struct modesMessage *mm) a->cpr_odd_nuc = mm->cpr_nucp; } + // operational status message + if (mm->opstatus.valid) { + a->adsb_version = mm->opstatus.version; + if (mm->opstatus.version > 0) { + a->adsb_hrd = mm->opstatus.hrd; + a->adsb_tah = mm->opstatus.tah; + } + } + // Now handle derived data // derive geometric altitude if we have baro + delta diff --git a/track.h b/track.h index 82a619c..dadebcf 100644 --- a/track.h +++ b/track.h @@ -118,6 +118,9 @@ struct aircraft { 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) @@ -158,6 +161,10 @@ struct aircraft { double lat, lon; // Coordinated obtained from CPR encoded data unsigned pos_nuc; // NUCp of last computed position + 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) + 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? @@ -165,9 +172,10 @@ struct aircraft { int fatsv_emitted_altitude_gnss; // -"- GNSS altitude int fatsv_emitted_baro_rate; // -"- barometric rate int fatsv_emitted_geom_rate; // -"- geometric rate - float fatsv_emitted_heading; // -"- true track - float fatsv_emitted_heading_magnetic; // -"- magnetic heading + 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 unsigned fatsv_emitted_speed; // -"- groundspeed unsigned fatsv_emitted_speed_ias; // -"- IAS