From 68e95c06cc46937deeb0776c0a9bd1da8db6ccdc Mon Sep 17 00:00:00 2001 From: George Joseph Date: Mon, 21 Sep 2020 16:00:44 -0600 Subject: [PATCH] Interactive mode updates MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit These updates were designed to assist those using interactive mode to tune antennas and SDR gain. * Add options to display distance and bearing in interactive mode Distance and bearing instead of latitude and longitude can now be displayed in interactive mode using the following options to dump1090-fa and view1090-fa. --interactive-show-distance Show aircraft distance and bearing instead of aircraft lat/lon (requires --lat and --lon) --interactive-distance-units Distance units ('km', 'sm', 'nm') (default: 'nm')" You have to specify a reference --lat and --lon for this to work of course. * A new line now shows at the top of the interactive display that has for the current sample: Total valid aircraft count Vidible aircraft count Will be less than total if the screen hasn't enough lines to show them all. Max RSSI Min RSSI Mean RSSI Max Distance Tot: 47 Vis: 47 RSSI: Max 25.4+ Mean -29.5 Min -36.9- MaxD: 197.3nm+ * Add max distance and min/max RSSI indicators A '+' after the distance in a row indicates it's the row with the maximum distance. A '+' after the RSSI in a row indicates it's the row with the highest RSSI. A '-' after the RSSI in a row indicates it's the row with the lowest RSSI. The summary line at the top of the screen always shows the values for ALL aircraft, even those not visible. The row indicators only mark visible rows though. In this example, the first aircraft is both the farthest away and has the weakest RSSI. The second aircraft has the strongest RSSI. Tot: 47 Vis: 47 RSSI: Max 25.4+ Mean -29.5 Min -36.9- MaxD: 197.3nm+ - Hex Mode Sqwk Flight Alt Spd Hdg Dist(nm) Bearing RSSI Msgs Ti ──────────────────────────────────────────────────────────────────────────────── A8D5A4 S2 34000 438 252 197.3+ 85 -36.1- 26 2 A39A13 S2ac 5740 FFT525 30750 439 256 98.8 68 -25.4+ 123 0 A70B23 S2ac 2744 LXJ553 43000 419 258 136.1 39 -33.6 174 0 * Finally, a new option '--interactive-callsign-filter' has been added to allow filtering interactive by callsign. The value can be a simple string, in which case aircraft with that string anywhere in its callsign will be displayed, or a regular expression should you want a more precise match. Examples: --interactive-callsign-filter UAL will match all aircraft with UAL anywhere in its callsign. --interactive-callsign-filter "^UAL" will match only those callsigns that start with UAL. --interactive-callsign-filter "^(UAL|AAL)" will match only those callsigns that start with UAL or AAL. --interactive-callsign-filter "^N[0-9]" or "^N[[:digit:]]" will match only those callsigns that start with "N" and a number If you need more info on regular expressions, see this link: https://en.wikipedia.org/wiki/Regular_expression --- dump1090.c | 19 +++++- dump1090.h | 9 +++ interactive.c | 159 +++++++++++++++++++++++++++++++++++++++++++------- track.c | 26 ++++++++- track.h | 8 +++ view1090.c | 23 +++++++- 6 files changed, 218 insertions(+), 26 deletions(-) diff --git a/dump1090.c b/dump1090.c index aa23b73..2df4041 100644 --- a/dump1090.c +++ b/dump1090.c @@ -284,6 +284,10 @@ static void showHelp(void) "--freq Set frequency (default: 1090 Mhz)\n" "--interactive Interactive mode refreshing data on screen. Implies --throttle\n" "--interactive-ttl Remove from list if idle for (default: 60)\n" +"--interactive-show-distance Show aircraft distance and bearing instead of lat/lon\n" +" (requires --lat and --lon)\n" +"--interactive-distance-units Distance units ('km', 'sm', 'nm') (default: 'nm')\n" +"--interactive-callsign-filter Only callsigns that match the prefix or regex will be displayed\n" "--raw Show only messages hex values\n" "--net Enable networking with default ports unless overridden\n" "--modeac Enable decoding of SSR Modes 3/A & 3/C\n" @@ -571,7 +575,20 @@ int main(int argc, char **argv) { Modes.interactive = 1; } else if (!strcmp(argv[j],"--interactive-ttl") && more) { Modes.interactive_display_ttl = (uint64_t)(1000 * atof(argv[++j])); - } else if (!strcmp(argv[j],"--lat") && more) { + } else if (!strcmp(argv[j],"--interactive-show-distance")) { + Modes.interactive_show_distance = 1; + } else if (!strcmp(argv[j], "--interactive-distance-units") && more) { + char *units = argv[++j]; + if (!strcmp(units, "km")) { + Modes.interactive_distance_units = UNIT_KILOMETERS; + } else if (!strcmp(units, "sm")) { + Modes.interactive_distance_units = UNIT_STATUTE_MILES; + } else { + Modes.interactive_distance_units = UNIT_NAUTICAL_MILES; + } + } else if (!strcmp(argv[j], "--interactive-callsign-filter") && more) { + Modes.interactive_callsign_filter = strdup(argv[++j]); + } else if (!strcmp(argv[j], "--lat") && more) { Modes.fUserLat = atof(argv[++j]); } else if (!strcmp(argv[j],"--lon") && more) { Modes.fUserLon = atof(argv[++j]); diff --git a/dump1090.h b/dump1090.h index 088d3f6..3068ea0 100644 --- a/dump1090.h +++ b/dump1090.h @@ -163,6 +163,12 @@ typedef enum { UNIT_METERS } altitude_unit_t; +typedef enum { + UNIT_NAUTICAL_MILES, + UNIT_STATUTE_MILES, + UNIT_KILOMETERS, +} interactive_distance_unit_t; + typedef enum { ALTITUDE_BARO, ALTITUDE_GEOM @@ -336,6 +342,9 @@ struct _Modes { // Internal state uint32_t show_only; // Only show messages from this ICAO int interactive; // Interactive mode uint64_t interactive_display_ttl;// Interactive mode: TTL display + int interactive_show_distance; // Show aircraft distance and bearing instead of lat/lon + interactive_distance_unit_t interactive_distance_units; // Units for interactive distance display + char *interactive_callsign_filter; // Filter for interactive display callsigns uint64_t stats; // Interval (millis) between stats dumps, int stats_range_histo; // Collect/show a range histogram? int onlyaddr; // Print only ICAO addresses diff --git a/interactive.c b/interactive.c index 3e36448..9b55ff3 100644 --- a/interactive.c +++ b/interactive.c @@ -50,6 +50,8 @@ #include "dump1090.h" #include +#include +#include // //========================= Interactive mode =============================== @@ -77,9 +79,40 @@ static int convert_speed(int kts) // Show the currently captured interactive data on screen. // +double distance_units_conversion; +char *distance_units_suffix; +regex_t callsign_filter_regex; + void interactiveInit() { - if (!Modes.interactive) + if (!Modes.interactive) { return; + } + + switch(Modes.interactive_distance_units) { + case UNIT_NAUTICAL_MILES: + distance_units_conversion = 0.53996; + distance_units_suffix = "nm"; + break; + case UNIT_STATUTE_MILES: + distance_units_conversion = 0.621371; + distance_units_suffix = "sm"; + break; + case UNIT_KILOMETERS: + distance_units_conversion = 1.0; + distance_units_suffix = "km"; + break; + } + + if (Modes.interactive_callsign_filter) { + int rc = regcomp(&callsign_filter_regex, Modes.interactive_callsign_filter, + REG_EXTENDED | REG_NOSUB | REG_ICASE); + if (rc != 0) { + char msg[256]; + regerror(rc, &callsign_filter_regex, msg, sizeof(msg)); + fprintf(stderr, "Unable to parse filter '%s': %s\n", Modes.interactive_callsign_filter, msg); + exit(1); + } + } initscr(); clear(); @@ -88,6 +121,7 @@ void interactiveInit() { void interactiveCleanup(void) { if (Modes.interactive) { + regfree(&callsign_filter_regex); endwin(); } } @@ -106,6 +140,11 @@ void interactiveShowData(void) { uint64_t now = mstime(); char progress; char spinner[4] = "|/-\\"; + int valid = 0; + double signalMax = -100.0; + double signalMin = +100.0; + double signalMean = 0.0; + double distanceMax = 0.0; if (!Modes.interactive) return; @@ -116,39 +155,58 @@ void interactiveShowData(void) { next_update = now + MODES_INTERACTIVE_REFRESH_TIME; - mvprintw(0, 0, " Hex Mode Sqwk Flight Alt Spd Hdg Lat Long RSSI Msgs Ti"); - mvhline(1, 0, ACS_HLINE, 80); + if (Modes.interactive_show_distance) { + mvprintw(1, 0, " Hex Mode Sqwk Flight Alt Spd Hdg Dist(%s) Bearing RSSI Msgs Ti", + distance_units_suffix); + } else { + mvprintw(1, 0, " Hex Mode Sqwk Flight Alt Spd Hdg Lat Long RSSI Msgs Ti"); + } + + mvhline(2, 0, ACS_HLINE, 80); progress = spinner[(now/1000)%4]; mvaddch(0, 79, progress); int rows = getmaxy(stdscr); - int row = 2; + int row = 3; + int rowMaxd = 0; + int rowMaxRSSI = 0; + int rowMinRSSI = 0; - while (a && row < rows) { - if (a->reliable && (now - a->seen) < Modes.interactive_display_ttl) { + while (a) { + + if (a->reliable && (now - a->seen) < Modes.interactive_display_ttl + && (Modes.interactive_callsign_filter == NULL || a->callsign_matched + || regexec(&callsign_filter_regex, a->callsign, 0, NULL, 0) == 0)) { char strSquawk[5] = " "; char strFl[7] = " "; char strTt[5] = " "; char strGs[5] = " "; int msgs = a->messages; + a->callsign_matched = 1; + valid++; + if (trackDataValid(&a->squawk_valid)) { - snprintf(strSquawk,5,"%04x", a->squawk); + snprintf(strSquawk, sizeof(strSquawk), "%04x", a->squawk); } if (trackDataValid(&a->gs_valid)) { - snprintf (strGs, 5,"%3d", convert_speed(a->gs)); + snprintf (strGs, sizeof(strGs), "%3d", convert_speed(a->gs)); } if (trackDataValid(&a->track_valid)) { - snprintf (strTt, 5,"%03.0f", a->track); + snprintf (strTt, sizeof(strTt), "%03.0f", a->track); } if (msgs > 99999) { msgs = 99999; } + double distance = 0.0; + char strDistance[8] = " "; + double bearing = 0.0; + char strBearing[9] = " "; char strMode[5] = " "; char strLat[8] = " "; char strLon[9] = " "; @@ -168,28 +226,70 @@ void interactiveShowData(void) { } if (trackDataValid(&a->position_valid)) { - snprintf(strLat, 8,"%7.03f", a->lat); - snprintf(strLon, 9,"%8.03f", a->lon); + snprintf(strLat, sizeof(strLat), "%7.03f", a->lat); + snprintf(strLon, sizeof(strLon), "%8.03f", a->lon); } if (trackDataValid(&a->airground_valid) && a->airground == AG_GROUND) { - snprintf(strFl, 7," grnd"); + snprintf(strFl, sizeof(strFl), "grnd "); } else if (Modes.use_gnss && trackDataValid(&a->altitude_geom_valid)) { - snprintf(strFl, 7, "%5dH", convert_altitude(a->altitude_geom)); + snprintf(strFl, sizeof(strFl), "%5dH", convert_altitude(a->altitude_geom)); } else if (trackDataValid(&a->altitude_baro_valid)) { - snprintf(strFl, 7, "%5d ", convert_altitude(a->altitude_baro)); + snprintf(strFl, sizeof(strFl), "%5d ", convert_altitude(a->altitude_baro)); + } + double signalDisplay = 10.0 * log10(signalAverage); + + + if ((Modes.bUserFlags & MODES_USER_LATLON_VALID) && trackDataValid(&a->position_valid)) { + distance = greatcircle(Modes.fUserLat, Modes.fUserLon, + a->lat, a->lon); + + distance /= 1000.0; + + distance *= distance_units_conversion; + if (distance > distanceMax) { + distanceMax = distance; + } + snprintf(strDistance, sizeof(strDistance), "%5.1f ", distance); + bearing = get_bearing(Modes.fUserLat, Modes.fUserLon, a->lat, a->lon); + snprintf(strBearing, sizeof(strBearing), "%5.0f ", bearing); } - mvprintw(row, 0, "%s%06X %-4s %-4s %-8s %6s %3s %3s %7s %8s %5.1f %5d %2.0f", + if (signalDisplay > signalMax) { + signalMax = signalDisplay; + } + if (signalDisplay < signalMin) { + signalMin = signalDisplay; + } + signalMean += signalDisplay; + + if (row < rows) { + mvprintw(row, 0, "%s%06X %-4s %-4s %-8s %6s %3s %3s %7s %8s %5.1f %5d %2.0f", (a->addr & MODES_NON_ICAO_ADDRESS) ? "~" : " ", (a->addr & 0xffffff), strMode, strSquawk, a->callsign, strFl, strGs, strTt, - strLat, strLon, 10 * log10(signalAverage), msgs, (now - a->seen)/1000.0); - ++row; + Modes.interactive_show_distance ? strDistance : strLat, + Modes.interactive_show_distance ? strBearing : strLon, + signalDisplay, msgs, (now - a->seen)/1000.0); + + if (signalDisplay >= signalMax) { + rowMaxRSSI = row; + } + if (signalDisplay <= signalMin) { + rowMinRSSI = row; + } + if (distance >= distanceMax) { + rowMaxd = row; + } + + ++row; + } + } a = a->next; } - if (Modes.mode_ac) { + + if (Modes.mode_ac && !Modes.interactive_callsign_filter) { for (unsigned i = 1; i < 4096 && row < rows; ++i) { if (modeAC_match[i] || modeAC_count[i] < 50 || modeAC_age[i] > 5) continue; @@ -200,8 +300,9 @@ void interactiveShowData(void) { int modeC = modeAToModeC(modeA); if (modeC != INVALID_ALTITUDE) { strMode[3] = 'C'; - snprintf(strFl, 7, "%5d ", convert_altitude(modeC * 100)); + snprintf(strFl, sizeof(strFl), "%5d ", convert_altitude(modeC * 100)); } + valid++; mvprintw(row, 0, "%7s %-4s %04x %-8s %6s %3s %3s %7s %8s %5s %5d %2d\n", @@ -221,8 +322,24 @@ void interactiveShowData(void) { } } - move(row, 0); - clrtobot(); + if (rowMaxd > 3 && Modes.interactive_show_distance) { + mvprintw(rowMaxd, 52, "^"); + } + + mvprintw(rowMaxd, 52, "+"); + mvprintw(rowMaxRSSI, 68, "+"); + mvprintw(rowMinRSSI, 68, "-"); + + mvprintw(0, 0, " Tot: %3d Vis: %3d RSSI: Max %5.1f+ Mean %5.1f Min %5.1f- MaxD: %6.1f%s+ ", + valid, (rows-3) < valid ? (rows-3) : valid, signalMax, signalMean / valid, signalMin, distanceMax, + distance_units_suffix); + + if (row < rows) { + move(row, 0); + clrtobot(); + } + move(0, 0); + refresh(); } diff --git a/track.c b/track.c index d18b11d..b5419b8 100644 --- a/track.c +++ b/track.c @@ -207,7 +207,7 @@ static int compare_validity(const data_validity *lhs, const data_validity *rhs) // Distance between points on a spherical earth. // This has up to 0.5% error because the earth isn't actually spherical // (but we don't use it in situations where that matters) -static double greatcircle(double lat0, double lon0, double lat1, double lon1) +double greatcircle(double lat0, double lon0, double lat1, double lon1) { double dlat, dlon; @@ -229,6 +229,25 @@ static double greatcircle(double lat0, double lon0, double lat1, double lon1) return 6371e3 * acos(sin(lat0) * sin(lat1) + cos(lat0) * cos(lat1) * cos(dlon)); } +double get_bearing(double lat0, double lon0, double lat1, double lon1) +{ + double dlon; + + lat0 = lat0 * M_PI / 180.0; + lon0 = lon0 * M_PI / 180.0; + lat1 = lat1 * M_PI / 180.0; + lon1 = lon1 * M_PI / 180.0; + + dlon = (lon1 - lon0); + + double x = (cos(lat0) * sin(lat1)) - + (sin(lat0) * cos(lat1) * cos(dlon)); + double y = sin(dlon) * cos(lat1); + double degree = atan2(y, x) * 180 / M_PI; + + return (degree >= 0)? degree : (degree + 360); +} + static void update_range_histogram(double lat, double lon) { if (Modes.stats_range_histo && (Modes.bUserFlags & MODES_USER_LATLON_VALID)) { @@ -1128,6 +1147,11 @@ struct aircraft *trackUpdateFromMessage(struct modesMessage *mm) } if (mm->callsign_valid && accept_data(&a->callsign_valid, mm->source)) { + if (strcmp(a->callsign, mm->callsign) != 0) { + // The callsign changed so tell interactive to + // re-evaluate its callsign filter regex if it has one + a->callsign_matched = 0; + } memcpy(a->callsign, mm->callsign, sizeof(a->callsign)); } diff --git a/track.h b/track.h index dc4f851..ade1267 100644 --- a/track.h +++ b/track.h @@ -108,6 +108,7 @@ struct aircraft { data_validity callsign_valid; char callsign[9]; // Flight number + int callsign_matched; // Interactive callsign filter matched data_validity altitude_baro_valid; int altitude_baro; // Altitude (Baro) @@ -321,4 +322,11 @@ static inline unsigned indexToModeA(unsigned index) return (index & 0007) | ((index & 0070) << 1) | ((index & 0700) << 2) | ((index & 07000) << 3); } +/* Great Circle distance in m */ +double greatcircle(double lat0, double lon0, double lat1, double lon1); + +/* Get bearing from 2 points */ +double get_bearing(double lat0, double lon0, double lat1, double lon1); + + #endif diff --git a/view1090.c b/view1090.c index c27a47f..1912590 100644 --- a/view1090.c +++ b/view1090.c @@ -107,11 +107,15 @@ static void view1090Init(void) { // static void showHelp(void) { printf( -"-----------------------------------------------------------------------------\n" +"-------------------------------------------------------------------------------------\n" "| view1090 ModeS Viewer %45s |\n" -"-----------------------------------------------------------------------------\n" +"-------------------------------------------------------------------------------------\n" "--no-interactive Disable interactive mode, print messages to stdout\n" "--interactive-ttl Remove from list if idle for (default: 60)\n" + "--interactive-show-distance Show aircraft distance and bearing instead of lat/lon\n" + " (requires --lat and --lon)\n" + "--interactive-distance-units Distance units ('km', 'sm', 'nm') (default: 'nm')\n" + "--interactive-callsign-filter Only callsigns that match the prefix or regex will be displayed\n" "--modeac Enable decoding of SSR modes 3/A & 3/C\n" "--net-bo-ipaddr TCP Beast output listen IPv4 (default: 127.0.0.1)\n" "--net-bo-port TCP Beast output listen port (default: 30005)\n" @@ -168,7 +172,20 @@ int main(int argc, char **argv) { Modes.interactive = 0; } else if (!strcmp(argv[j],"--interactive-ttl") && more) { Modes.interactive_display_ttl = (uint64_t)(1000 * atof(argv[++j])); - } else if (!strcmp(argv[j],"--lat") && more) { + } else if (!strcmp(argv[j], "--interactive-show-distance")) { + Modes.interactive_show_distance = 1; + } else if (!strcmp(argv[j], "--interactive-distance-units") && more) { + char *units = argv[++j]; + if (!strcmp(units, "km")) { + Modes.interactive_distance_units = UNIT_KILOMETERS; + } else if (!strcmp(units, "sm")) { + Modes.interactive_distance_units = UNIT_STATUTE_MILES; + } else { + Modes.interactive_distance_units = UNIT_NAUTICAL_MILES; + } + } else if (!strcmp(argv[j], "--interactive-callsign-filter") && more) { + Modes.interactive_callsign_filter = strdup(argv[++j]); + } else if (!strcmp(argv[j], "--lat") && more) { Modes.fUserLat = atof(argv[++j]); } else if (!strcmp(argv[j],"--lon") && more) { Modes.fUserLon = atof(argv[++j]);