diff --git a/dump1090.h b/dump1090.h index ccfd389..9c4a79c 100644 --- a/dump1090.h +++ b/dump1090.h @@ -195,6 +195,10 @@ typedef enum { SIL_PER_SAMPLE, SIL_PER_HOUR } sil_type_t; +typedef enum { + CPR_SURFACE, CPR_AIRBORNE, CPR_COARSE +} cpr_type_t; + #define MODES_NON_ICAO_ADDRESS (1<<24) // Set on addresses to indicate they are not ICAO addresses #define MODES_DEBUG_DEMOD (1<<0) @@ -455,6 +459,7 @@ struct modesMessage { // 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 diff --git a/interactive.c b/interactive.c index 7be904a..85554b2 100644 --- a/interactive.c +++ b/interactive.c @@ -66,7 +66,7 @@ static int convert_altitude(int ft) static int convert_speed(int kts) { if (Modes.metric) - return (kts / 1.852); + return (kts * 1.852); else return kts; } diff --git a/mode_s.c b/mode_s.c index d8dcae1..be41840 100644 --- a/mode_s.c +++ b/mode_s.c @@ -903,6 +903,7 @@ static void decodeESSurfacePosition(struct modesMessage *mm, int check_imf) mm->cpr_odd = getbit(me, 22); mm->cpr_nucp = (14 - mm->metype); mm->cpr_valid = 1; + mm->cpr_type = CPR_SURFACE; unsigned movement = getbits(me, 6, 12); if (movement > 0 && movement < 125) { @@ -948,6 +949,7 @@ static void decodeESAirbornePosition(struct modesMessage *mm, int check_imf) } else { // Otherwise, assume it's valid. mm->cpr_valid = 1; + mm->cpr_type = CPR_AIRBORNE; mm->cpr_odd = getbit(me, 22); if (mm->metype == 18 || mm->metype == 22) @@ -1388,6 +1390,19 @@ static const char *addrtype_to_string(addrtype_t type) { } } +static const char *cpr_type_to_string(cpr_type_t type) { + switch (type) { + case CPR_SURFACE: + return "Surface"; + case CPR_AIRBORNE: + return "Airborne"; + case CPR_COARSE: + return "TIS-B Coarse"; + default: + return "unknown CPR type"; + } +} + static void print_hex_bytes(unsigned char *data, size_t len) { size_t i; for (i = 0; i < len; ++i) { @@ -1678,12 +1693,11 @@ void displayModesMessage(struct modesMessage *mm) { mm->category); } - if (mm->msgtype == 17 || mm->msgtype == 18) { - } - if (mm->cpr_valid) { - printf(" CPR odd flag: %s\n" + printf(" CPR type: %s\n" + " CPR odd flag: %s\n" " CPR NUCp/NIC: %u\n", + cpr_type_to_string(mm->cpr_type), mm->cpr_odd ? "odd" : "even", mm->cpr_nucp); diff --git a/tools/fuzzy-30003-matcher.py b/tools/fuzzy-30003-matcher.py new file mode 100755 index 0000000..255166a --- /dev/null +++ b/tools/fuzzy-30003-matcher.py @@ -0,0 +1,204 @@ +#!/usr/bin/env python3 + +# +# Regression testing helper: takes a 3.0.5 port-30003 output file +# and a 3.1.0 port-30003 output file and generates a diff, after +# dealing with the known formatting / data differences + +import csv +from contextlib import closing + +horizon=5 + +def fuzzy_match_details(l1, l2): + _, _, type1, _, _, addr1, _, _, _, _, _, cs1, alt1, gs1, hdg1, lat1, lon1, vr1, sq1, change1, emerg1, spi1, aog1 = l1 + _, _, type2, _, _, addr2, _, _, _, _, _, cs2, alt2, gs2, hdg2, lat2, lon2, vr2, sq2, change2, emerg2, spi2, aog2 = l2 + + if addr1 != addr2: + return (False, 'adr') + + if type1 != type2: + # 3.0.5: reports DF17 surface/airborne with no position as type 7 + # 3.1.0: reports DF17 surface/airborne with no position as type 2/3 + if type1 != '7': + return (False, 'typ') + if type2 != '2' and type2 != '3': + return (False, 'typ') + if lat1 != '' or lon1 != '': + return (False, 'typ') + + if alt1 != alt2: + # 3.0.5: omits altitude in DF17 if no position was decoded + # 3.1.0: includes it + if type1 != '7' or alt1 != '' or alt2 == '': + return (False, 'alt') + + if gs1 != gs2: + # 3.0.5: truncates computed GS + # 3.1.0: rounds computed GS + if gs1 == '' or gs2 == '' or abs(int(gs1) - int(gs2)) > 1: + return (False, 'gs ') + if hdg1 != hdg2: + # 3.0.5: truncates computed heading + # 3.1.0: rounds computed heading + if hdg1 == '' or hdg2 == '': + return (False, 'hdg') + delta = abs(int(hdg1) - int(hdg2)) + if delta > 180: + delta = 360 - delta + if delta > 1: + return False + + if lat1 != lat2: + return (False, 'lat') + if lon1 != lon2: + return (False, 'lon') + if vr1 != vr2: + return (False, 'vr ') + + if sq1 != sq2: + # 3.0.5: strips leading zeros + # 3.1.0: preserves leading zeros + if ('0' + sq1) != sq2 and ('00' + sq1) != sq2 and ('000' + sq1) != sq2: + return (False, 'sqk') + + # 3.1.0: only reports these when available + if change1 != change2: + if change1 != '0' or change2 != '': + return (False, 'chg') + if emerg1 != emerg2: + if emerg1 != '0' or emerg2 != '': + return (False, 'emg') + if spi1 != spi2: + if spi1 != '0' or spi2 != '': + return (False, 'spi') + + if aog1 != aog2: + # 3.1.0: different rules for when AOG is reported + if aog1 != '' and aog2 != '': + return (False, 'aog') + + return (True, None) + +def fuzzy_match(l1, l2): + return fuzzy_match_details(l1, l2)[0] + +def fuzzy_match_reason(l1, l2): + return fuzzy_match_details(l1, l2)[1] + +def next_line(reader, queue): + if queue: + return queue.pop() + line = next(reader, None) + if line is None or len(line) == 0: + return None + else: + return [reader.line_num] + line + +def unpush_line(queue, line): + queue.insert(0, line) + +def csv_diff(path1, path2): + diffs = [] + q1 = [] + q2 = [] + + with closing(open(path1, 'r')) as f1, closing(open(path2, 'r')) as f2: + r1 = csv.reader(f1) + r2 = csv.reader(f2) + + l1 = next_line(r1, q1) + l2 = next_line(r2, q2) + + while (l1 is not None) or (l2 is not None): + if l1 is None: + yield ('+', None, l2) + l2 = next_line(r2, q2) + continue + + if l2 is None: + yield ('-', l1, None) + l1 = next_line(r1, q1) + continue + + if fuzzy_match(l1, l2): + yield (' ', l1, l2) + l1 = next_line(r1, q1) + l2 = next_line(r2, q2) + continue + + #print('mismatch:', l1, l2) + + save_1 = [] + save_2 = [] + + found = False + for i in range(horizon): + next_l2 = next_line(r2, q2) + if next_l2 is not None: + if fuzzy_match(l1, next_l2): + # skip l2 and any lines in save_2 + # continue with l1 and next_l2 + yield('+', None, l2) + for l in save_2: + yield('+', None, l) + l2 = next_l2 + q1.extend(reversed(save_1)) + found = True + break + else: + save_2.append(next_l2) + + next_l1 = next_line(r1, q1) + if next_l1 is not None: + if fuzzy_match(next_l1, l2): + # skip l1 and any lines in save_1 + # continue with next_l1 and l2 + yield('-', l1, None) + for l in save_1: + yield('-', l, None) + l1 = next_l1 + q2.extend(reversed(save_2)) + found = True + break + else: + save_1.append(next_l1) + + if found: + #print('new l1:', l1) + #print('new l2:', l2) + #print('new q1:') + #for q in q1: print(q) + #print('new q2:') + #for q in q2: print(q) + continue + + #print('lookahead: nothing likely') + + q1.extend(reversed(save_1)) + q2.extend(reversed(save_2)) + yield ('*', l1, l2) + l1 = next_line(r1, q1) + l2 = next_line(r1, q2) + +def format_line(line): + line_num = line[0] + subrow = line[1:3] + line[5:6] + line[11:] + return str(line_num) + ': ' + ','.join(subrow) + +if __name__ == '__main__': + import sys + for action, old, new in csv_diff(sys.argv[1], sys.argv[2]): + if action == ' ': + if False: print (' ' + format_line(new)) + elif action == '*': + reason = fuzzy_match_reason(old, new) + print ('< ' + reason + ' ' + format_line(old)) + print ('> ' + reason + ' ' + format_line(new)) + elif action == '-': + # 3.0.5: emits lines for all-zero messages + # 3.1.0: doesn't + if old[5] != '000000': + print ('- ' + format_line(old)) + elif action == '+': + print ('+ ' + format_line(new)) diff --git a/track.c b/track.c index 1aded30..24e6348 100644 --- a/track.c +++ b/track.c @@ -255,7 +255,7 @@ static int doGlobalCPR(struct aircraft *a, struct modesMessage *mm, uint64_t now { int result; int fflag = mm->cpr_odd; - int surface = (mm->airground == AG_GROUND); + 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 @@ -336,7 +336,7 @@ static int doLocalCPR(struct aircraft *a, struct modesMessage *mm, uint64_t now, double range_limit = 0; int result; int fflag = mm->cpr_odd; - int surface = (mm->airground == AG_GROUND); + int surface = (mm->cpr_type == CPR_SURFACE); *nuc = mm->cpr_nucp; @@ -421,7 +421,7 @@ static void updatePosition(struct aircraft *a, struct modesMessage *mm, uint64_t unsigned new_nuc = 0; int surface; - surface = (mm->airground == AG_GROUND); + surface = (mm->cpr_type == CPR_SURFACE); if (surface) { ++Modes.stats_current.cpr_surface; @@ -441,7 +441,7 @@ static void updatePosition(struct aircraft *a, struct modesMessage *mm, uint64_t // If we have enough recent data, try global CPR if (trackDataValid(&a->cpr_odd_valid) && trackDataValid(&a->cpr_even_valid) && a->cpr_odd_valid.source == a->cpr_even_valid.source && - a->cpr_odd_airground == a->cpr_even_airground && + 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); @@ -609,7 +609,7 @@ struct aircraft *trackUpdateFromMessage(struct modesMessage *mm) // CPR, even if (mm->cpr_valid && !mm->cpr_odd && accept_data(&a->cpr_even_valid, mm->source, now)) { - a->cpr_even_airground = mm->airground; + 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; @@ -617,7 +617,7 @@ struct aircraft *trackUpdateFromMessage(struct modesMessage *mm) // CPR, odd if (mm->cpr_valid && mm->cpr_odd && accept_data(&a->cpr_odd_valid, mm->source, now)) { - a->cpr_odd_airground = mm->airground; + 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; diff --git a/track.h b/track.h index de26f68..57be9f9 100644 --- a/track.h +++ b/track.h @@ -123,13 +123,13 @@ struct aircraft { airground_t airground; // air/ground status data_validity cpr_odd_valid; // Last seen even CPR message - airground_t cpr_odd_airground; + cpr_type_t cpr_odd_type; unsigned cpr_odd_lat; unsigned cpr_odd_lon; unsigned cpr_odd_nuc; data_validity cpr_even_valid; // Last seen odd CPR message - airground_t cpr_even_airground; + cpr_type_t cpr_even_type; unsigned cpr_even_lat; unsigned cpr_even_lon; unsigned cpr_even_nuc;