From 0494eb5b182d07b3a27eeed08cedeaa720d29457 Mon Sep 17 00:00:00 2001 From: Nick Foster Date: Wed, 20 Jun 2012 18:57:01 -0700 Subject: [PATCH 01/24] Extended test case for CPR calculator to use different odd/even locations --- python/cpr.py | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/python/cpr.py b/python/cpr.py index 460c69b..5639d2b 100755 --- a/python/cpr.py +++ b/python/cpr.py @@ -274,12 +274,14 @@ if __name__ == '__main__': for i in range(0, rounds): decoder = cpr_decoder(None) - ac_lat = random.uniform(-85, 85) - ac_lon = random.uniform(-180,180) + even_lat = random.uniform(-85, 85) + even_lon = random.uniform(-180,180) + odd_lat = even_lat + 1e-2 + odd_lon = min(even_lon + 1e-2, 180) #encode that position - (evenenclat, evenenclon) = cpr_encode(ac_lat, ac_lon, False, False) - (oddenclat, oddenclon) = cpr_encode(ac_lat, ac_lon, True, False) + (evenenclat, evenenclon) = cpr_encode(even_lat, even_lon, False, False) + (oddenclat, oddenclon) = cpr_encode(odd_lat, odd_lon, True, False) #perform a global decode icao = random.randint(0, 0xffffff) @@ -300,17 +302,22 @@ if __name__ == '__main__': #print "Lat: %f Lon: %f" % (ac_lat, ac_lon) - if abs(odddeclat - ac_lat) > threshold or abs(odddeclon - ac_lon) > threshold: - #print "odddeclat: %f ac_lat: %f" % (odddeclat, ac_lat) - #print "odddeclon: %f ac_lon: %f" % (odddeclon, ac_lon) + if abs(odddeclat - odd_lat) > threshold or abs(odddeclon - odd_lon) > threshold: + print "odddeclat: %f odd_lat: %f" % (odddeclat, odd_lat) + print "odddeclon: %f odd_lon: %f" % (odddeclon, odd_lon) raise Exception("CPR test failure: global decode error greater than threshold") + nexteven_lat = odd_lat + 1e-2 + nexteven_lon = min(odd_lon + 1e-2, 180) + + (nexteven_enclat, nexteven_enclon) = cpr_encode(nexteven_lat, nexteven_lon, False, False) + try: - (evendeclat, evendeclon) = cpr_resolve_local([ac_lat, ac_lon], [evenenclat, evenenclon], False, False) + (evendeclat, evendeclon) = cpr_resolve_local([even_lat, even_lon], [nexteven_enclat, nexteven_enclon], False, False) except CPRNoPositionError: raise Exception("CPR test failure: local decode failure to resolve") - if abs(evendeclat - ac_lat) > threshold or abs(evendeclon - ac_lon) > threshold: + if abs(evendeclat - nexteven_lat) > threshold or abs(evendeclon - nexteven_lon) > threshold: raise Exception("CPR test failure: local decode error greater than threshold") print "CPR test successful. There were %i boundary straddles over %i rounds." % (bs, rounds) From ba153e52edc149b00ae44065234aad761a4d793f Mon Sep 17 00:00:00 2001 From: Nick Foster Date: Thu, 21 Jun 2012 09:42:58 -0700 Subject: [PATCH 02/24] Completely rewrote parser, everything is broken --- lib/air_modes_slicer.cc | 31 ++---------- python/modes_exceptions.py | 5 ++ python/modes_parse.py | 99 ++++++++++++++++++++++++++++++++++++-- python/modes_print.py | 24 ++++----- 4 files changed, 115 insertions(+), 44 deletions(-) diff --git a/lib/air_modes_slicer.cc b/lib/air_modes_slicer.cc index 00e8f35..618efc7 100644 --- a/lib/air_modes_slicer.cc +++ b/lib/air_modes_slicer.cc @@ -167,7 +167,7 @@ int air_modes_slicer::work(int noutput_items, } if(zeroes) {continue;} //toss it - rx_packet.message_type = (rx_packet.data[0] >> 3) & 0x1F; //get the message type for the parser to conveniently use, and to make decisions on ECC methods + rx_packet.message_type = (rx_packet.data[0] >> 3) & 0x1F; //get the message type to make decisions on ECC methods if(rx_packet.type == Short_Packet && rx_packet.message_type != 11 && rx_packet.numlowconf > 0) {continue;} if(rx_packet.message_type == 11 && rx_packet.numlowconf >= 10) {continue;} @@ -179,36 +179,11 @@ int air_modes_slicer::work(int noutput_items, //crc for the other short packets is usually nonzero, so they can't really be trusted that far if(rx_packet.crc && (rx_packet.message_type == 11 || rx_packet.message_type == 17)) {continue;} - //we no longer attempt to brute force error correct via syndrome. it really only gets you 1% additional returns, - //at the expense of a lot of CPU time and complexity - - //we'll replicate some data by sending the message type as the first field, followed by the first 8+24=32 bits of the packet, followed by - //56 long packet data bits if applicable (zero-padded if not), followed by crc - d_payload.str(""); - d_payload << std::dec << std::setw(2) << std::setfill('0') << rx_packet.message_type << std::hex << " "; - for(int m = 0; m < 4; m++) { + for(int m = 0; m < 14; m++) { d_payload << std::setw(2) << std::setfill('0') << unsigned(rx_packet.data[m]); } - d_payload << " "; - if(packet_length == 112) { - for(int m = 4; m < 11; m++) { - d_payload << std::setw(2) << std::setfill('0') << unsigned(rx_packet.data[m]); - } - d_payload << " "; - for(int m = 11; m < 14; m++) { - d_payload << std::setw(2) << std::setfill('0') << unsigned(rx_packet.data[m]); - } - } else { - for(int m = 4; m < 11; m++) { - d_payload << std::setw(2) << std::setfill('0') << unsigned(0); - } - d_payload << " "; - for(int m = 4; m < 7; m++) { - d_payload << std::setw(2) << std::setfill('0') << unsigned(rx_packet.data[m]); - } - } - + d_payload << " " << std::setw(6) << rx_packet.crc << " " << std::dec << rx_packet.reference_level << " " << std::setprecision(10) << std::setw(10) << rx_packet.timestamp; gr_message_sptr msg = gr_make_message_from_string(std::string(d_payload.str())); diff --git a/python/modes_exceptions.py b/python/modes_exceptions.py index 6606e5d..c7179a9 100644 --- a/python/modes_exceptions.py +++ b/python/modes_exceptions.py @@ -40,3 +40,8 @@ class CPRNoPositionError(ADSBError): class CPRBoundaryStraddleError(CPRNoPositionError): pass + +class FieldNotInPacket(ParserError): + def __init__(self, item): + self.item = item + diff --git a/python/modes_parse.py b/python/modes_parse.py index a58da45..70327bc 100644 --- a/python/modes_parse.py +++ b/python/modes_parse.py @@ -17,8 +17,7 @@ # along with gr-air-modes; see the file COPYING. If not, write to # the Free Software Foundation, Inc., 51 Franklin Street, # Boston, MA 02110-1301, USA. -# - +# import time, os, sys from string import split, join @@ -27,6 +26,73 @@ import cpr import math from modes_exceptions import * +#this implements a packet class which can retrieve its own fields. +class modes_data: + def __init__(self, data): + self.data = data + #this depends on packet type so we initialize it here + #there's probably a cleaner way to do it. + self.parity_fields = { + "ap": (33+(self.get_numbits()-56),24), + "pi": (33+(self.get_numbits()-56),24) + } + + #bitfield definitions according to Mode S spec + #(start bit, num bits) + fields = { "df": (1,5), "vs": (6,1), "fs": (6,3), "cc": (7,1), + "sl": (9,3), "ri": (14,4), "ac": (20,13), "dr": (9,5), + "um": (14,6), "id": (20,13), "ca": (6,3), "aa": (9,24), + "mv": (33,56), "me": (33,56), "mb": (33,56), "ke": (6,1), + "nd": (7,4), "md": (11,80) + } + + #fields in each packet type (DF value) + types = { 0: ["df", "vs", "cc", "sl", "ri", "ac", "ap"], + 4: ["df", "fs", "dr", "um", "ac", "ap"], + 5: ["df", "fs", "dr", "um", "id", "ap"], + 11: ["df", "ca", "aa", "pi"], + 16: ["df", "vs", "sl", "ri", "ac", "mv", "ap"], + 17: ["df", "ca", "aa", "me", "pi"], + 20: ["df", "fs", "dr", "um", "ac", "mb", "ap"], + 21: ["df", "fs", "dr", "um", "id", "mb", "ap"], + 24: ["df", "ke", "nd", "md", "ap"] + } + + #packets which are 112 bits long + long_types = [16, 17, 20, 21, 24] + + #get a particular field from the data + def __getitem__(self, fieldname): + if fieldname in modes_data.types[self.get_bits(modes_data.fields["df"])]: #verify it exists in this packet type + if fieldname in self.parity_fields: + return self.get_bits(self.parity_fields[fieldname]) + else: + return self.get_bits(modes_data.fields[fieldname]) + else: + raise FieldNotInPacket(fieldname) + + #grab all the fields in the packet as a dict + def get_fields(self): + return {field: self[field] for field in modes_data.types[self["df"]]} + + #tell me if i'm an extended squitter or a normal Mode S reply + def is_long(self): + #can't use packet type because we use is_long() to get items, and + #this causes endless recursion + #return self["df"] in modes_data.long_types + return self.data > (1 << 56) + + #how many bits are in the packet? + def get_numbits(self): + return 112 if self.is_long() else 56 + + #retrieve bits from data given the offset and number of bits. + #the offset is both left-justified (LSB) and starts at 1, to + #correspond to the Mode S spec. Blame them. + def get_bits(self, arg): + (offset, num) = arg + return (self.data >> (self.get_numbits() - offset - num + 1)) & ((1 << num) - 1) + class modes_parse: def __init__(self, mypos): self.my_location = mypos @@ -54,14 +120,14 @@ class modes_parse: return [fs, dr, um, altitude] - def parse5(self, shortdata): fs = shortdata >> 24 & 0x07 #flight status: 0 is airborne normal, 1 is ground normal, 2 is airborne alert, 3 is ground alert, 4 is alert SPI, 5 is normal SPI dr = shortdata >> 19 & 0x1F #downlink request: 0 means no req, bit 0 is Comm-B msg rdy bit, bit 1 is TCAS info msg rdy, bit 2 is Comm-B bcast #1 msg rdy, bit2+bit0 is Comm-B bcast #2 msg rdy, #bit2+bit1 is TCAS info and Comm-B bcast #1 msg rdy, bit2+bit1+bit0 is TCAS info and Comm-B bcast #2 msg rdy, 8-15 N/A, 16-31 req to send N-15 segments um = shortdata >> 13 & 0x3F #transponder status readouts, no decoding information available + ident = shortdata & 0x1FFF - return [fs, dr, um] + return [fs, dr, um, ident] def parse11(self, shortdata, ecc): interrogator = ecc & 0x0F @@ -222,3 +288,28 @@ class modes_parse: return [velocity, heading, vert_spd] + def parse20(self, shortdata, longdata): + [fs, dr, um, alt] = self.parse4(shortdata) + #BDS defines TCAS reply type and is the first 8 bits + #BDS1 is first four, BDS2 is bits 5-8 + bds1 = longdata_bits(longdata, 33, 4) + bds2 = longdata_bits(longdata, 37, 4) + #bds2 != 0 defines extended TCAS capabilities, not in spec + return [fs, dr, um, alt, bds1, bds2] + + def parseMB_commB(self, longdata): #bds1, bds2 == 0 + raise NoHandlerError + + def parseMB_caps(self, longdata): #bds1 == 1, bds2 == 0 + #cfs, acs, bcs + raise NoHandlerError + + def parseMB_id(self, longdata): #bds1 == 2, bds2 == 0 + msg = "" + for i in range(0, 8): + msg += self.charmap( longdata >> (42-6*i) & 0x3F) + return (msg) + + def parseMB_TCASRA(self, longdata): #bds1 == 3, bds2 == 0 + #ara[41-54],rac[55-58],rat[59],mte[60],tti[61-62],tida[63-75],tidr[76-82],tidb[83-88] + raise NoHandlerError diff --git a/python/modes_print.py b/python/modes_print.py index fd60f8c..904ce6d 100644 --- a/python/modes_print.py +++ b/python/modes_print.py @@ -30,15 +30,15 @@ class modes_output_print(modes_parse.modes_parse): modes_parse.modes_parse.__init__(self, mypos) def parse(self, message): - [msgtype, shortdata, longdata, parity, ecc, reference, timestamp] = message.split() - shortdata = long(shortdata, 16) - longdata = long(longdata, 16) - parity = long(parity, 16) + [data, ecc, reference, timestamp] = message.split() + + #TODO FIXME HALP + data = modes_data(long(data, 16)) ecc = long(ecc, 16) reference = float(reference) timestamp = float(timestamp) - msgtype = int(msgtype) + msgtype = data["df"] #TODO this is suspect if reference == 0.0: @@ -49,15 +49,15 @@ class modes_output_print(modes_parse.modes_parse): try: if msgtype == 0: - output += self.print0(shortdata, ecc) + output += self.print0(data, ecc) elif msgtype == 4: - output += self.print4(shortdata, ecc) + output += self.print4(data, ecc) elif msgtype == 5: - output += self.print5(shortdata, ecc) + output += self.print5(data, ecc) elif msgtype == 11: - output += self.print11(shortdata, ecc) + output += self.print11(data, ecc) elif msgtype == 17: - output += self.print17(shortdata, longdata) + output += self.print17(data) print output except NoHandlerError as e: output += "No handler for message type " + str(e.msgtype) + " from %x" % ecc @@ -102,9 +102,9 @@ class modes_output_print(modes_parse.modes_parse): return retstr def print5(self, shortdata, ecc): - [fs, dr, um] = self.parse5(shortdata) + [fs, dr, um, ident] = self.parse5(shortdata) - retstr = "Type 5 (short surveillance ident reply) from " + "%x" % ecc + " with ident " + str(shortdata & 0x1FFF) + retstr = "Type 5 (short surveillance ident reply) from " + "%x" % ecc + " with ident " + str(ident) if fs == 1: retstr = retstr + " (aircraft is on the ground)" From a45531b15c6059846d69a7d054621dd944009353 Mon Sep 17 00:00:00 2001 From: Nick Foster Date: Fri, 22 Jun 2012 17:28:55 -0700 Subject: [PATCH 03/24] Additional work toward new parser. Added fields for extended squitter. --- python/modes_parse.py | 146 +++++++++++++++++++++++++++++++----------- 1 file changed, 110 insertions(+), 36 deletions(-) diff --git a/python/modes_parse.py b/python/modes_parse.py index 70327bc..b2eac10 100644 --- a/python/modes_parse.py +++ b/python/modes_parse.py @@ -27,11 +27,102 @@ import math from modes_exceptions import * #this implements a packet class which can retrieve its own fields. -class modes_data: +class data_field: def __init__(self, data): self.data = data - #this depends on packet type so we initialize it here - #there's probably a cleaner way to do it. + + fields = { } + types = { } + subfields = { } #fields to return objects instead of just returning bits + startbit = 0 #field offset applied to all fields. used for offsetting subtypes to reconcile with spec. + + #get a particular field from the data + def __getitem__(self, fieldname): + if fieldname in self.types[self.get_type()]: #verify it exists in this packet type + if fieldname in self.subfields: + #create a new subfield object and return it + return self.subfields[fieldname](self.get_bits(self.fields[fieldname])) + return self.get_bits(self.fields[fieldname]) + else: + raise FieldNotInPacket(fieldname) + + #grab all the fields in the packet as a dict + def get_fields(self): + return {field: self[field] for field in self.types[self.get_type()]} + + def get_type(self): + raise NotImplementedError + + def get_numbits(self): + raise NotImplementedError + + #retrieve bits from data given the offset and number of bits. + #the offset is both left-justified (LSB) and starts at 1, to + #correspond to the Mode S spec. Blame them. + def get_bits(self, arg): + (offset, num) = arg + return (self.data \ + >> (self.get_numbits() - offset - num + self.startbit + 1)) \ + & ((1 << num) - 1) + +#type MB (extended squitter types 20,21) subfields +class mb_reply(data_field): + fields = { "acs": (45,20), "ais": (41,48), "ara": (41,14), "bcs": (65,16), + "bds": (33,8), "bds1": (33,4), "bds2": (37,4), "cfs": (41,4), + "ecs": (81,8), "mte": (60,1), "rac": (55,4), "rat": (59,1), + "tid": (33,26), "tida": (63,13), "tidb": (83,6), "tidr": (76,7), + "tti": (61,2) + } + startbit = 32 #fields offset by 32 to match documentation + + #types are based on bds1 subfield + types = { 0: ["bds", "bds1", "bds2"], #TODO + 1: ["bds", "bds1", "bds2", "cfs", "acs", "bcs"], + 2: ["bds", "bds1", "bds2", "ais"], + 3: ["bds", "bds1", "bds2", "ara", "rac", "rat", + "mte", "tti", "tida", "tidr", "tidb"] + } + + def get_type(self): + bds1 = self.get_bits(self.fields["bds1"]) + bds2 = self.get_bits(self.fields["bds2"]) + if bds1 not in (0,1,2,3) or bds2 not in (0,): + raise NoHandlerError + return bds1 + + def get_numbits(self): + return 56 + +#type 17 extended squitter data +class me_reply(data_field): + #TODO: add comments explaining these fields + fields = { "ftc": (1,5), "ss": (6,2), "saf": (8,1), "alt": (9, 12), + "time": (21,1), "cpr": (22,1), "lat": (23, 17), "lon": (40, 17), + "mvt": (6,7), "gts": (13,1), "gtk": (14,7), "trs": (1,2), + "ats": (3,1), "cat": (6,3), "ident": (9,48), "sub": (6,3), + "dew": (10,1), "vew": (11,11), "dns": (22,1), "vns": (23,11), + "str": (34,1), "tr": (35,6), "svr": (41,1), "vr": (42,9), + "icf": (9,1), "ifr": (10,1), "nuc": (11,3), "gdew": (14,1), + "gvew": (15,10), "gdns": (25,1), "gvns": (26,10), "vrs": (36,1), + "gsvr": (37,1), "gvr": (38,9), "ghds": (49,1), "ghd": (50,6), + "mhs": (14,1), "hdg": (15,10), "ast": (25,1), "spd": (26,10), + "eps": (9,3) + #TODO: TCP, TCP+1/BDS 6,2 + } + startbit = 0 + types = { } + + def get_type(self): + pass + + def get_numbits(self): + return 56 + + +class modes_reply(data_field): + def __init__(self, data): + data_field.__init__(self, data) +#TODO FIX PARITY FIELDS self.parity_fields = { "ap": (33+(self.get_numbits()-56),24), "pi": (33+(self.get_numbits()-56),24) @@ -50,48 +141,31 @@ class modes_data: types = { 0: ["df", "vs", "cc", "sl", "ri", "ac", "ap"], 4: ["df", "fs", "dr", "um", "ac", "ap"], 5: ["df", "fs", "dr", "um", "id", "ap"], - 11: ["df", "ca", "aa", "pi"], - 16: ["df", "vs", "sl", "ri", "ac", "mv", "ap"], - 17: ["df", "ca", "aa", "me", "pi"], - 20: ["df", "fs", "dr", "um", "ac", "mb", "ap"], - 21: ["df", "fs", "dr", "um", "id", "mb", "ap"], - 24: ["df", "ke", "nd", "md", "ap"] + 11: ["df", "ca", "aa", "pi"], + 16: ["df", "vs", "sl", "ri", "ac", "mv", "ap"], + 17: ["df", "ca", "aa", "me", "pi"], + 20: ["df", "fs", "dr", "um", "ac", "mb", "ap"], + 21: ["df", "fs", "dr", "um", "id", "mb", "ap"], + 24: ["df", "ke", "nd", "md", "ap"] } - #packets which are 112 bits long - long_types = [16, 17, 20, 21, 24] + subfields = { "mb": mb_reply, "me": me_reply } #TODO MV, ME - #get a particular field from the data - def __getitem__(self, fieldname): - if fieldname in modes_data.types[self.get_bits(modes_data.fields["df"])]: #verify it exists in this packet type - if fieldname in self.parity_fields: - return self.get_bits(self.parity_fields[fieldname]) - else: - return self.get_bits(modes_data.fields[fieldname]) - else: - raise FieldNotInPacket(fieldname) - - #grab all the fields in the packet as a dict - def get_fields(self): - return {field: self[field] for field in modes_data.types[self["df"]]} - - #tell me if i'm an extended squitter or a normal Mode S reply def is_long(self): - #can't use packet type because we use is_long() to get items, and - #this causes endless recursion - #return self["df"] in modes_data.long_types return self.data > (1 << 56) - #how many bits are in the packet? def get_numbits(self): return 112 if self.is_long() else 56 - #retrieve bits from data given the offset and number of bits. - #the offset is both left-justified (LSB) and starts at 1, to - #correspond to the Mode S spec. Blame them. - def get_bits(self, arg): - (offset, num) = arg - return (self.data >> (self.get_numbits() - offset - num + 1)) & ((1 << num) - 1) + def get_type(self): + return self.get_bits(self.fields["df"]) + +#TODO overload getitem to handle special parity fields + +# #type MV (extended squitter type 16) subfields +# mv_fields = { "ara": (41,14), "mte": (60,1), "rac": (55,4), "rat": (59,1), +# "vds": (33,8), "vds1": (33,4), "vds2": (37,4) +# } class modes_parse: def __init__(self, mypos): From b7cc18c41f59da3effd43065dded8555d71ed191 Mon Sep 17 00:00:00 2001 From: Nick Foster Date: Sat, 23 Jun 2012 14:25:26 -0700 Subject: [PATCH 04/24] More parser work, type 17 done except for BDS0,9 velocity subtypes --- python/modes_parse.py | 30 +++++++++++++++++++++++++++--- 1 file changed, 27 insertions(+), 3 deletions(-) diff --git a/python/modes_parse.py b/python/modes_parse.py index b2eac10..96927e1 100644 --- a/python/modes_parse.py +++ b/python/modes_parse.py @@ -110,10 +110,34 @@ class me_reply(data_field): #TODO: TCP, TCP+1/BDS 6,2 } startbit = 0 - types = { } + #types in this format are listed by BDS register + types = { 0x05: ["ftc", "ss", "saf", "alt", "time", "cpr", "lat", "lon"], #airborne position + 0x06: ["ftc", "mvt", "gts", "gtk", "time", "cpr", "lat", "lon"], #surface position + 0x07: ["ftc",], #TODO extended squitter status + 0x08: ["ftc", "cat", "ident"], #extended squitter identification and type + + #TODO: bds0,9 has 3 subtypes, needs to be subclassed + 0x09: ["ftc", "sub", "dew", "vew", "dns", "vns", "str", "tr", "svr", "vr"], #velocity type 0 + + #0x0A: data link capability report + #0x17: common usage capability report + #0x18-0x1F: Mode S specific services capability report + #0x20: aircraft identification + 0x61: ["ftc", "eps"] + } def get_type(self): - pass + ftc = self.get_bits(self.fields["ftc"]) + if 1 <= ftc <= 4: + return 0x08 + elif 5 <= ftc <= 8: + return 0x06 + elif 9 <= ftc <= 18: + return 0x05 + elif ftc == 19: + return 0x09 + else: + return NoHandlerError def get_numbits(self): return 56 @@ -149,7 +173,7 @@ class modes_reply(data_field): 24: ["df", "ke", "nd", "md", "ap"] } - subfields = { "mb": mb_reply, "me": me_reply } #TODO MV, ME + subfields = { "mb": mb_reply, "me": me_reply } #TODO MV def is_long(self): return self.data > (1 << 56) From 03b41f14bee43bdb7fbefc6e16064c86a6172d93 Mon Sep 17 00:00:00 2001 From: Nick Foster Date: Sat, 23 Jun 2012 17:26:47 -0700 Subject: [PATCH 05/24] It works, still some bugs. --- lib/air_modes_slicer.cc | 4 +- python/modes_parse.py | 147 +++++++++++++--------------------------- python/modes_print.py | 45 ++++++------ 3 files changed, 73 insertions(+), 123 deletions(-) diff --git a/lib/air_modes_slicer.cc b/lib/air_modes_slicer.cc index 618efc7..04c4dd2 100644 --- a/lib/air_modes_slicer.cc +++ b/lib/air_modes_slicer.cc @@ -180,8 +180,8 @@ int air_modes_slicer::work(int noutput_items, if(rx_packet.crc && (rx_packet.message_type == 11 || rx_packet.message_type == 17)) {continue;} d_payload.str(""); - for(int m = 0; m < 14; m++) { - d_payload << std::setw(2) << std::setfill('0') << unsigned(rx_packet.data[m]); + for(int m = 0; m < packet_length/8; m++) { + d_payload << std::hex << std::setw(2) << std::setfill('0') << unsigned(rx_packet.data[m]); } d_payload << " " << std::setw(6) << rx_packet.crc << " " << std::dec << rx_packet.reference_level diff --git a/python/modes_parse.py b/python/modes_parse.py index 96927e1..60c5b53 100644 --- a/python/modes_parse.py +++ b/python/modes_parse.py @@ -38,13 +38,17 @@ class data_field: #get a particular field from the data def __getitem__(self, fieldname): - if fieldname in self.types[self.get_type()]: #verify it exists in this packet type - if fieldname in self.subfields: - #create a new subfield object and return it - return self.subfields[fieldname](self.get_bits(self.fields[fieldname])) - return self.get_bits(self.fields[fieldname]) + if self.get_type() in self.types: + if fieldname in self.types[self.get_type()]: #verify it exists in this packet type + if fieldname in self.subfields: + #create a new subfield object and return it + return self.subfields[fieldname](self.get_bits(self.fields[fieldname])) + else: + return self.get_bits(self.fields[fieldname]) + else: + raise FieldNotInPacket(fieldname) else: - raise FieldNotInPacket(fieldname) + raise NoHandlerError(self.get_type()) #grab all the fields in the packet as a dict def get_fields(self): @@ -88,7 +92,7 @@ class mb_reply(data_field): bds2 = self.get_bits(self.fields["bds2"]) if bds1 not in (0,1,2,3) or bds2 not in (0,): raise NoHandlerError - return bds1 + return int(bds1) def get_numbits(self): return 56 @@ -146,11 +150,6 @@ class me_reply(data_field): class modes_reply(data_field): def __init__(self, data): data_field.__init__(self, data) -#TODO FIX PARITY FIELDS - self.parity_fields = { - "ap": (33+(self.get_numbits()-56),24), - "pi": (33+(self.get_numbits()-56),24) - } #bitfield definitions according to Mode S spec #(start bit, num bits) @@ -158,7 +157,8 @@ class modes_reply(data_field): "sl": (9,3), "ri": (14,4), "ac": (20,13), "dr": (9,5), "um": (14,6), "id": (20,13), "ca": (6,3), "aa": (9,24), "mv": (33,56), "me": (33,56), "mb": (33,56), "ke": (6,1), - "nd": (7,4), "md": (11,80) + "nd": (7,4), "md": (11,80), "ap": (33,24), "pi": (33,24), + "lap": (88,24), "lpi": (88,24) } #fields in each packet type (DF value) @@ -166,11 +166,11 @@ class modes_reply(data_field): 4: ["df", "fs", "dr", "um", "ac", "ap"], 5: ["df", "fs", "dr", "um", "id", "ap"], 11: ["df", "ca", "aa", "pi"], - 16: ["df", "vs", "sl", "ri", "ac", "mv", "ap"], - 17: ["df", "ca", "aa", "me", "pi"], - 20: ["df", "fs", "dr", "um", "ac", "mb", "ap"], - 21: ["df", "fs", "dr", "um", "id", "mb", "ap"], - 24: ["df", "ke", "nd", "md", "ap"] + 16: ["df", "vs", "sl", "ri", "ac", "mv", "lap"], + 17: ["df", "ca", "aa", "me", "lpi"], + 20: ["df", "fs", "dr", "um", "ac", "mb", "lap"], + 21: ["df", "fs", "dr", "um", "id", "mb", "lap"], + 24: ["df", "ke", "nd", "md", "lap"] } subfields = { "mb": mb_reply, "me": me_reply } #TODO MV @@ -196,83 +196,34 @@ class modes_parse: self.my_location = mypos self.cpr = cpr.cpr_decoder(self.my_location) - def parse0(self, shortdata): - vs = bool(shortdata >> 26 & 0x1) #ground sensor -- airborne when 0 - cc = bool(shortdata >> 25 & 0x1) #crosslink capability, binary - sl = shortdata >> 21 & 0x07 #operating sensitivity of onboard TCAS system. 0 means no TCAS sensitivity reported, 1-7 give TCAS sensitivity - ri = shortdata >> 15 & 0x0F #speed coding: 0 = no onboard TCAS, 1 = NA, 2 = TCAS w/inhib res, 3 = TCAS w/vert only, 4 = TCAS w/vert+horiz, 5-7 = NA, 8 = no max A/S avail, - #9 = A/S <= 75kt, 10 = A/S (75-150]kt, 11 = (150-300]kt, 12 = (300-600]kt, 13 = (600-1200]kt, 14 = >1200kt, 15 = NA + def parse0(self, data): + fields = data.get_fields() + altitude = decode_alt(data["ac"], True) + return [fields["vs"], fields["cc"], fields["sl"], fields["ri"], altitude] - altitude = decode_alt(shortdata & 0x1FFF, True) #bit 13 is set for type 0 + def parse4(self, data): + fields = data.get_fields() + altitude = decode_alt(data["ac"], True) + return [data["fs"], data["dr"], data["um"], altitude] - return [vs, cc, sl, ri, altitude] + def parse5(self, data): + return [data["fs"], data["dr"], data["um"], data["id"]] - def parse4(self, shortdata): - fs = shortdata >> 24 & 0x07 #flight status: 0 is airborne normal, 1 is ground normal, 2 is airborne alert, 3 is ground alert, 4 is alert SPI, 5 is normal SPI - dr = shortdata >> 19 & 0x1F #downlink request: 0 means no req, bit 0 is Comm-B msg rdy bit, bit 1 is TCAS info msg rdy, bit 2 is Comm-B bcast #1 msg rdy, bit2+bit0 is Comm-B bcast #2 msg rdy, - #bit2+bit1 is TCAS info and Comm-B bcast #1 msg rdy, bit2+bit1+bit0 is TCAS info and Comm-B bcast #2 msg rdy, 8-15 N/A, 16-31 req to send N-15 segments - um = shortdata >> 13 & 0x3F #transponder status readouts, no decoding information available - - altitude = decode_alt(shortdata & 0x1FFF, True) - - return [fs, dr, um, altitude] - - - def parse5(self, shortdata): - fs = shortdata >> 24 & 0x07 #flight status: 0 is airborne normal, 1 is ground normal, 2 is airborne alert, 3 is ground alert, 4 is alert SPI, 5 is normal SPI - dr = shortdata >> 19 & 0x1F #downlink request: 0 means no req, bit 0 is Comm-B msg rdy bit, bit 1 is TCAS info msg rdy, bit 2 is Comm-B bcast #1 msg rdy, bit2+bit0 is Comm-B bcast #2 msg rdy, - #bit2+bit1 is TCAS info and Comm-B bcast #1 msg rdy, bit2+bit1+bit0 is TCAS info and Comm-B bcast #2 msg rdy, 8-15 N/A, 16-31 req to send N-15 segments - um = shortdata >> 13 & 0x3F #transponder status readouts, no decoding information available - ident = shortdata & 0x1FFF - - return [fs, dr, um, ident] - - def parse11(self, shortdata, ecc): + def parse11(self, data, ecc): interrogator = ecc & 0x0F - - ca = shortdata >> 13 & 0x3F #capability - icao24 = shortdata & 0xFFFFFF - - return [icao24, interrogator, ca] - - #the Type 17 subtypes are: - #0: No position information - #1: Identification (Category set D) - #2: Identification (Category set C) - #3: "" (B) - #4: "" (A) - #5: Surface position accurate to 7.5m - #6: "" to 25m - #7: "" to 185.2m (0.1nm) - #8: "" above 185.2m - #9: Airborne position to 7.5m - #10-18: Same with less accuracy - #19: Airborne velocity - #20: Airborne position w/GNSS height above earth - #21: same to 25m - #22: same above 25m - #23: Reserved - #24: Reserved for surface system status - #25-27: Reserved - #28: Extended squitter aircraft status - #29: Current/next trajectory change point - #30: Aircraft operational coordination - #31: Aircraft operational status + return [data["aa"], interrogator, data["ca"]] categories = [["NO INFO", "RESERVED", "RESERVED", "RESERVED", "RESERVED", "RESERVED", "RESERVED", "RESERVED"],\ ["NO INFO", "SURFACE EMERGENCY VEHICLE", "SURFACE SERVICE VEHICLE", "FIXED OBSTRUCTION", "RESERVED", "RESERVED", "RESERVED"],\ ["NO INFO", "GLIDER", "BALLOON/BLIMP", "PARACHUTE", "ULTRALIGHT", "RESERVED", "UAV", "SPACECRAFT"],\ ["NO INFO", "LIGHT", "SMALL", "LARGE", "LARGE HIGH VORTEX", "HEAVY", "HIGH PERFORMANCE", "ROTORCRAFT"]] - def parseBDS08(self, shortdata, longdata): - icao24 = shortdata & 0xFFFFFF - subtype = (longdata >> 51) & 0x1F - category = (longdata >> 48) & 0x07 - catstring = self.categories[subtype-1][category] + def parseBDS08(self, data): + catstring = self.categories[data["me"]["ftc"]-1][data["me"]["cat"]] msg = "" for i in range(0, 8): - msg += self.charmap( longdata >> (42-6*i) & 0x3F) + msg += self.charmap( data["me"]["ident"] >> (42-6*i) & 0x3F) return (msg, catstring) def charmap(self, d): @@ -287,16 +238,13 @@ class modes_parse: return retval - def parseBDS05(self, shortdata, longdata): - icao24 = shortdata & 0xFFFFFF + def parseBDS05(self, data): + icao24 = data["aa"] - encoded_lon = longdata & 0x1FFFF - encoded_lat = (longdata >> 17) & 0x1FFFF - cpr_format = (longdata >> 34) & 1 - - enc_alt = (longdata >> 36) & 0x0FFF - - altitude = decode_alt(enc_alt, False) + encoded_lon = data["me"]["lon"] + encoded_lat = data["me"]["lat"] + cpr_format = data["me"]["cpr"] + altitude = decode_alt(data["me"]["alt"], False) [decoded_lat, decoded_lon, rnge, bearing] = self.cpr.decode(icao24, encoded_lat, encoded_lon, cpr_format, 0) @@ -304,17 +252,14 @@ class modes_parse: #welp turns out it looks like there's only 17 bits in the BDS0,6 ground packet after all. - def parseBDS06(self, shortdata, longdata): - icao24 = shortdata & 0xFFFFFF - - encoded_lon = longdata & 0x1FFFF - encoded_lat = (longdata >> 17) & 0x1FFFF - cpr_format = (longdata >> 34) & 1 - - altitude = 0 - + def parseBDS06(self, data): + icao24 = data["aa"] + + encoded_lon = data["me"]["lon"] + encoded_lat = data["me"]["lat"] + cpr_format = data["me"]["cpr"] + altitude = decode_alt(data["me"]["alt"], False) [decoded_lat, decoded_lon, rnge, bearing] = self.cpr.decode(icao24, encoded_lat, encoded_lon, cpr_format, 1) - return [altitude, decoded_lat, decoded_lon, rnge, bearing] def parseBDS09_0(self, shortdata, longdata): diff --git a/python/modes_print.py b/python/modes_print.py index 904ce6d..b43349d 100644 --- a/python/modes_print.py +++ b/python/modes_print.py @@ -33,12 +33,16 @@ class modes_output_print(modes_parse.modes_parse): [data, ecc, reference, timestamp] = message.split() #TODO FIXME HALP - data = modes_data(long(data, 16)) + data = modes_parse.modes_reply(long(data, 16)) ecc = long(ecc, 16) reference = float(reference) timestamp = float(timestamp) - msgtype = data["df"] + try: + msgtype = data["df"] + except NoHandlerError as err: + print "No handler for msgtype %s" % err.msgtype + return #TODO this is suspect if reference == 0.0: @@ -119,31 +123,31 @@ class modes_output_print(modes_parse.modes_parse): return retstr - def print11(self, shortdata, ecc): - [icao24, interrogator, ca] = self.parse11(shortdata, ecc) + def print11(self, data, ecc): + [icao24, interrogator, ca] = self.parse11(data, ecc) retstr = "Type 11 (all call reply) from " + "%x" % icao24 + " in reply to interrogator " + str(interrogator) return retstr - def print17(self, shortdata, longdata): - icao24 = shortdata & 0xFFFFFF - subtype = (longdata >> 51) & 0x1F; + def print17(self, data): + icao24 = data["aa"] + subtype = data["me"]["ftc"] - retstr = None + retstr = "" if 1 <= subtype <= 4: - (msg, typestring) = self.parseBDS08(shortdata, longdata) + (msg, typestring) = self.parseBDS08(data) retstr = "Type 17 subtype 04 (ident) from " + "%x" % icao24 + " of type " + typestring + " with ident " + msg elif subtype >= 5 and subtype <= 8: - [altitude, decoded_lat, decoded_lon, rnge, bearing] = self.parseBDS06(shortdata, longdata) + [altitude, decoded_lat, decoded_lon, rnge, bearing] = self.parseBDS06(data) if decoded_lat is not None: retstr = "Type 17 subtype 06 (surface report) from " + "%x" % icao24 + " at (" + "%.6f" % decoded_lat + ", " + "%.6f" % decoded_lon + ")" if rnge is not None and bearing is not None: retstr += " (" + "%.2f" % rnge + " @ " + "%.0f" % bearing + ")" elif subtype >= 9 and subtype <= 18: - [altitude, decoded_lat, decoded_lon, rnge, bearing] = self.parseBDS05(shortdata, longdata) + [altitude, decoded_lat, decoded_lon, rnge, bearing] = self.parseBDS05(data) if decoded_lat is not None: retstr = "Type 17 subtype 05 (position report) from " + "%x" % icao24 + " at (" + "%.6f" % decoded_lat + ", " + "%.6f" % decoded_lon + ")" if rnge is not None and bearing is not None: @@ -151,17 +155,18 @@ class modes_output_print(modes_parse.modes_parse): retstr += " at " + str(altitude) + "ft" elif subtype == 19: - subsubtype = (longdata >> 48) & 0x07 - if subsubtype == 0: - [velocity, heading, vert_spd] = self.parseBDS09_0(shortdata, longdata) - retstr = "Type 17 subtype 09-0 (track report) from " + "%x" % icao24 + " with velocity " + "%.0f" % velocity + "kt heading " + "%.0f" % heading + " VS " + "%.0f" % vert_spd + pass +# subsubtype = (longdata >> 48) & 0x07 +# if subsubtype == 0: +# [velocity, heading, vert_spd] = self.parseBDS09_0(shortdata, longdata) +# retstr = "Type 17 subtype 09-0 (track report) from " + "%x" % icao24 + " with velocity " + "%.0f" % velocity + "kt heading " + "%.0f" % heading + " VS " + "%.0f" % vert_spd - elif subsubtype == 1: - [velocity, heading, vert_spd] = self.parseBDS09_1(shortdata, longdata) - retstr = "Type 17 subtype 09-1 (track report) from " + "%x" % icao24 + " with velocity " + "%.0f" % velocity + "kt heading " + "%.0f" % heading + " VS " + "%.0f" % vert_spd +# elif subsubtype == 1: +# [velocity, heading, vert_spd] = self.parseBDS09_1(shortdata, longdata) +# retstr = "Type 17 subtype 09-1 (track report) from " + "%x" % icao24 + " with velocity " + "%.0f" % velocity + "kt heading " + "%.0f" % heading + " VS " + "%.0f" % vert_spd - else: - retstr = "Type 17 subtype 09-%i" % (subsubtype) + " not implemented" +# else: +# retstr = "Type 17 subtype 09-%i" % (subsubtype) + " not implemented" else: retstr = "Type 17 subtype " + str(subtype) + " not implemented" From f928669094ec85afbb120ea0184ca3d8715dbc1f Mon Sep 17 00:00:00 2001 From: Nick Foster Date: Sat, 23 Jun 2012 17:55:17 -0700 Subject: [PATCH 06/24] Found the no handler thing --- python/modes_print.py | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/python/modes_print.py b/python/modes_print.py index b43349d..6b03703 100644 --- a/python/modes_print.py +++ b/python/modes_print.py @@ -32,18 +32,11 @@ class modes_output_print(modes_parse.modes_parse): def parse(self, message): [data, ecc, reference, timestamp] = message.split() - #TODO FIXME HALP data = modes_parse.modes_reply(long(data, 16)) ecc = long(ecc, 16) reference = float(reference) timestamp = float(timestamp) - try: - msgtype = data["df"] - except NoHandlerError as err: - print "No handler for msgtype %s" % err.msgtype - return - #TODO this is suspect if reference == 0.0: refdb = -150.0 @@ -51,6 +44,13 @@ class modes_output_print(modes_parse.modes_parse): refdb = 10.0*math.log10(reference) output = "(%.0f %.10f) " % (refdb, timestamp); + try: + msgtype = data["df"] + except NoHandlerError as err: + output += "No handler for msgtype %s" % err.msgtype + print output + return + try: if msgtype == 0: output += self.print0(data, ecc) @@ -62,6 +62,8 @@ class modes_output_print(modes_parse.modes_parse): output += self.print11(data, ecc) elif msgtype == 17: output += self.print17(data) + else: + output += "No handler for message type " + str(msgtype) + " (but it's in modes_parse)" print output except NoHandlerError as e: output += "No handler for message type " + str(e.msgtype) + " from %x" % ecc @@ -133,7 +135,7 @@ class modes_output_print(modes_parse.modes_parse): icao24 = data["aa"] subtype = data["me"]["ftc"] - retstr = "" + retstr = None if 1 <= subtype <= 4: (msg, typestring) = self.parseBDS08(data) @@ -141,21 +143,19 @@ class modes_output_print(modes_parse.modes_parse): elif subtype >= 5 and subtype <= 8: [altitude, decoded_lat, decoded_lon, rnge, bearing] = self.parseBDS06(data) - if decoded_lat is not None: - retstr = "Type 17 subtype 06 (surface report) from " + "%x" % icao24 + " at (" + "%.6f" % decoded_lat + ", " + "%.6f" % decoded_lon + ")" - if rnge is not None and bearing is not None: - retstr += " (" + "%.2f" % rnge + " @ " + "%.0f" % bearing + ")" + retstr = "Type 17 subtype 06 (surface report) from " + "%x" % icao24 + " at (" + "%.6f" % decoded_lat + ", " + "%.6f" % decoded_lon + ")" + if rnge is not None and bearing is not None: + retstr += " (" + "%.2f" % rnge + " @ " + "%.0f" % bearing + ")" elif subtype >= 9 and subtype <= 18: [altitude, decoded_lat, decoded_lon, rnge, bearing] = self.parseBDS05(data) - if decoded_lat is not None: - retstr = "Type 17 subtype 05 (position report) from " + "%x" % icao24 + " at (" + "%.6f" % decoded_lat + ", " + "%.6f" % decoded_lon + ")" - if rnge is not None and bearing is not None: - retstr += " (" + "%.2f" % rnge + " @ " + "%.0f" % bearing + ")" - retstr += " at " + str(altitude) + "ft" + retstr = "Type 17 subtype 05 (position report) from " + "%x" % icao24 + " at (" + "%.6f" % decoded_lat + ", " + "%.6f" % decoded_lon + ")" + if rnge is not None and bearing is not None: + retstr += " (" + "%.2f" % rnge + " @ " + "%.0f" % bearing + ")" + retstr += " at " + str(altitude) + "ft" elif subtype == 19: - pass + retstr = "Fix BDS0,9, dickhead" # subsubtype = (longdata >> 48) & 0x07 # if subsubtype == 0: # [velocity, heading, vert_spd] = self.parseBDS09_0(shortdata, longdata) From 3d2920b56ae5efef0327227e1baa81b2211bc776 Mon Sep 17 00:00:00 2001 From: Nick Foster Date: Sat, 23 Jun 2012 19:29:51 -0700 Subject: [PATCH 07/24] Flightgear interface for new parser, minus BDS0,9 --- python/modes_flightgear.py | 36 ++++++++++++++++++------------------ python/modes_print.py | 8 +------- 2 files changed, 19 insertions(+), 25 deletions(-) diff --git a/python/modes_flightgear.py b/python/modes_flightgear.py index 7d967cf..ec01165 100755 --- a/python/modes_flightgear.py +++ b/python/modes_flightgear.py @@ -27,40 +27,40 @@ class modes_flightgear(modes_parse.modes_parse): self.sock.connect((self.hostname, self.port)) def output(self, message): - [msgtype, shortdata, longdata, parity, ecc, reference, timestamp] = message.split() - shortdata = long(shortdata, 16) - longdata = long(longdata, 16) - msgtype = int(msgtype) + [data, ecc, reference, timestamp] = message.split() + data = modes_parse.modes_reply(long(data, 16)) try: + msgtype = data["df"] if msgtype == 17: #ADS-B report - icao24 = shortdata & 0xFFFFFF - subtype = (longdata >> 51) & 0x1F + icao24 = data["aa"] + subtype = data["me"]["sub"] if subtype == 4: #ident packet - (ident, actype) = self.parseBDS08(shortdata, longdata) + (ident, actype) = self.parseBDS08(data) #select model based on actype self.callsigns[icao24] = [ident, actype] elif 5 <= subtype <= 8: #BDS0,6 pos - [altitude, decoded_lat, decoded_lon, rnge, bearing] = self.parseBDS06(shortdata, longdata) + [altitude, decoded_lat, decoded_lon, rnge, bearing] = self.parseBDS06(data) self.positions[icao24] = [decoded_lat, decoded_lon, altitude] self.update(icao24) elif 9 <= subtype <= 18: #BDS0,5 pos - [altitude, decoded_lat, decoded_lon, rnge, bearing] = self.parseBDS05(shortdata, longdata) + [altitude, decoded_lat, decoded_lon, rnge, bearing] = self.parseBDS05(data) self.positions[icao24] = [decoded_lat, decoded_lon, altitude] self.update(icao24) elif subtype == 19: #velocity - subsubtype = (longdata >> 48) & 0x07 - if subsubtype == 0: - [velocity, heading, vert_spd, turnrate] = self.parseBDS09_0(shortdata, longdata) - elif subsubtype == 1: - [velocity, heading, vert_spd] = self.parseBDS09_1(shortdata, longdata) - turnrate = 0 - else: - return - self.velocities[icao24] = [velocity, heading, vert_spd, turnrate] + pass #FIXME TODO BDS0,9 + # subsubtype = (longdata >> 48) & 0x07 + # if subsubtype == 0: + # [velocity, heading, vert_spd, turnrate] = self.parseBDS09_0(data) + # elif subsubtype == 1: + # [velocity, heading, vert_spd] = self.parseBDS09_1(data) + # turnrate = 0 + # else: + # return + # self.velocities[icao24] = [velocity, heading, vert_spd, turnrate] except ADSBError: pass diff --git a/python/modes_print.py b/python/modes_print.py index 6b03703..71b4c06 100644 --- a/python/modes_print.py +++ b/python/modes_print.py @@ -46,12 +46,6 @@ class modes_output_print(modes_parse.modes_parse): try: msgtype = data["df"] - except NoHandlerError as err: - output += "No handler for msgtype %s" % err.msgtype - print output - return - - try: if msgtype == 0: output += self.print0(data, ecc) elif msgtype == 4: @@ -63,7 +57,7 @@ class modes_output_print(modes_parse.modes_parse): elif msgtype == 17: output += self.print17(data) else: - output += "No handler for message type " + str(msgtype) + " (but it's in modes_parse)" + output += "No handler for message type " + str(msgtype) + (" from %x" % ecc) + " (but it's in modes_parse)" print output except NoHandlerError as e: output += "No handler for message type " + str(e.msgtype) + " from %x" % ecc From e17409176329980d1a69beddd213f3380ba152d4 Mon Sep 17 00:00:00 2001 From: Nick Foster Date: Mon, 25 Jun 2012 17:25:57 -0700 Subject: [PATCH 08/24] Temp commit --- python/modes_parse.py | 49 ++++++++++++++++++++++++------------------- 1 file changed, 27 insertions(+), 22 deletions(-) diff --git a/python/modes_parse.py b/python/modes_parse.py index 60c5b53..80a858a 100644 --- a/python/modes_parse.py +++ b/python/modes_parse.py @@ -85,7 +85,7 @@ class mb_reply(data_field): 2: ["bds", "bds1", "bds2", "ais"], 3: ["bds", "bds1", "bds2", "ara", "rac", "rat", "mte", "tti", "tida", "tidr", "tidb"] - } + } def get_type(self): bds1 = self.get_bits(self.fields["bds1"]) @@ -104,25 +104,18 @@ class me_reply(data_field): "time": (21,1), "cpr": (22,1), "lat": (23, 17), "lon": (40, 17), "mvt": (6,7), "gts": (13,1), "gtk": (14,7), "trs": (1,2), "ats": (3,1), "cat": (6,3), "ident": (9,48), "sub": (6,3), - "dew": (10,1), "vew": (11,11), "dns": (22,1), "vns": (23,11), - "str": (34,1), "tr": (35,6), "svr": (41,1), "vr": (42,9), - "icf": (9,1), "ifr": (10,1), "nuc": (11,3), "gdew": (14,1), - "gvew": (15,10), "gdns": (25,1), "gvns": (26,10), "vrs": (36,1), - "gsvr": (37,1), "gvr": (38,9), "ghds": (49,1), "ghd": (50,6), - "mhs": (14,1), "hdg": (15,10), "ast": (25,1), "spd": (26,10), - "eps": (9,3) + "bds09": (9,48), "eps": (9,3) #TODO: TCP, TCP+1/BDS 6,2 - } + } + + subfields = { "bds09": bds09_reply } startbit = 0 #types in this format are listed by BDS register types = { 0x05: ["ftc", "ss", "saf", "alt", "time", "cpr", "lat", "lon"], #airborne position 0x06: ["ftc", "mvt", "gts", "gtk", "time", "cpr", "lat", "lon"], #surface position 0x07: ["ftc",], #TODO extended squitter status 0x08: ["ftc", "cat", "ident"], #extended squitter identification and type - - #TODO: bds0,9 has 3 subtypes, needs to be subclassed - 0x09: ["ftc", "sub", "dew", "vew", "dns", "vns", "str", "tr", "svr", "vr"], #velocity type 0 - + 0x09: ["ftc", "sub", "bds09"], #0x0A: data link capability report #0x17: common usage capability report #0x18-0x1F: Mode S specific services capability report @@ -130,6 +123,7 @@ class me_reply(data_field): 0x61: ["ftc", "eps"] } + #maps ftc to BDS register def get_type(self): ftc = self.get_bits(self.fields["ftc"]) if 1 <= ftc <= 4: @@ -146,11 +140,22 @@ class me_reply(data_field): def get_numbits(self): return 56 +class bds09_reply(data_field): + fields = { "sub": (6,3), "dew": (10,1), "vew": (11,11), "dns": (22,1), + "vns": (23,11), "str": (34,1), "tr": (35,6), "svr": (41,1), + "vr": (42,9), "icf": (9,1), "ifr": (10,1), "nuc": (11,3), + "gdew": (14,1), "gvew": (15,10), "gdns": (25,1), "gvns": (26,10), + "vrs": (36,1), "gsvr": (37,1), "gvr": (38,9), "ghds": (49,1), + "ghd": (50,6), "mhs": (14,1), "hdg": (15,10), "ast": (25,1), + "spd": (26,10) + } + + types = { #TODO + + def get_type(self): + return self.get_bits(self.fields["sub"]) class modes_reply(data_field): - def __init__(self, data): - data_field.__init__(self, data) - #bitfield definitions according to Mode S spec #(start bit, num bits) fields = { "df": (1,5), "vs": (6,1), "fs": (6,3), "cc": (7,1), @@ -165,12 +170,12 @@ class modes_reply(data_field): types = { 0: ["df", "vs", "cc", "sl", "ri", "ac", "ap"], 4: ["df", "fs", "dr", "um", "ac", "ap"], 5: ["df", "fs", "dr", "um", "id", "ap"], - 11: ["df", "ca", "aa", "pi"], - 16: ["df", "vs", "sl", "ri", "ac", "mv", "lap"], - 17: ["df", "ca", "aa", "me", "lpi"], - 20: ["df", "fs", "dr", "um", "ac", "mb", "lap"], - 21: ["df", "fs", "dr", "um", "id", "mb", "lap"], - 24: ["df", "ke", "nd", "md", "lap"] + 11: ["df", "ca", "aa", "pi"], + 16: ["df", "vs", "sl", "ri", "ac", "mv", "lap"], + 17: ["df", "ca", "aa", "me", "lpi"], + 20: ["df", "fs", "dr", "um", "ac", "mb", "lap"], + 21: ["df", "fs", "dr", "um", "id", "mb", "lap"], + 24: ["df", "ke", "nd", "md", "lap"] } subfields = { "mb": mb_reply, "me": me_reply } #TODO MV From 6a76ec8250df483b8b651aba6e50464f3ad77dc0 Mon Sep 17 00:00:00 2001 From: Nick Foster Date: Mon, 25 Jun 2012 23:09:11 -0700 Subject: [PATCH 09/24] Temp commit before changing new parser to eliminate "fields" --- python/modes_parse.py | 116 ++++++++++++++++++++++++++---------------- python/modes_sbs1.py | 4 +- 2 files changed, 73 insertions(+), 47 deletions(-) diff --git a/python/modes_parse.py b/python/modes_parse.py index 80a858a..3041076 100644 --- a/python/modes_parse.py +++ b/python/modes_parse.py @@ -33,8 +33,10 @@ class data_field: fields = { } types = { } - subfields = { } #fields to return objects instead of just returning bits - startbit = 0 #field offset applied to all fields. used for offsetting subtypes to reconcile with spec. + subfields = { } #fields to return objects instead of just returning bits, + #used for fields which may have multiple meanings (like ME for Type 17) + offset = 1 #field offset applied to all fields. used for offsetting + #subtypes to reconcile with the spec. Really just for readability. #get a particular field from the data def __getitem__(self, fieldname): @@ -64,9 +66,9 @@ class data_field: #the offset is both left-justified (LSB) and starts at 1, to #correspond to the Mode S spec. Blame them. def get_bits(self, arg): - (offset, num) = arg + (startbit, num) = arg return (self.data \ - >> (self.get_numbits() - offset - num + self.startbit + 1)) \ + >> (self.get_numbits() - startbit - num + self.offset)) \ & ((1 << num) - 1) #type MB (extended squitter types 20,21) subfields @@ -77,7 +79,7 @@ class mb_reply(data_field): "tid": (33,26), "tida": (63,13), "tidb": (83,6), "tidr": (76,7), "tti": (61,2) } - startbit = 32 #fields offset by 32 to match documentation + offset = 33 #fields offset by 33 to match documentation #types are based on bds1 subfield types = { 0: ["bds", "bds1", "bds2"], #TODO @@ -97,25 +99,64 @@ class mb_reply(data_field): def get_numbits(self): return 56 +class bds09_reply(data_field): + fields = { + "sub": (6,3), "dew": (10,1), "vew": (11,11), "dns": (22,1), + "vns": (23,11), "str": (34,1), "tr": (35,6), "dvr": (41,1), + "vr": (42,9), "icf": (9,1), "ifr": (10,1), "nuc": (11,3), + "gdew": (14,1), "gvew": (15,10), "gdns": (25,1), "gvns": (26,10), + "vrsrc": (36,1), "gdvr": (37,1), "gvr": (38,9), "ghds": (49,1), + "ghd": (50,6), "mhs": (14,1), "hdg": (15,10), "ast": (25,1), + "spd": (26,10) + } + + offset = 6 + + types = { + #BDS0,9 subtype 0 + 0: ["sub", "dew", "vew", "dns", "vns", "str", "tr", "dvr", "vr"], + #BDS0,9 subtypes 1-2 (differ only in velocity encoding) + 1: ["sub", "icf", "ifr", "nuc", "gdew", "gvew", "gdns", "gvns", "vrsrc", "gdvr", "gvr", "ghds", "ghd"], + #BDS0,9 subtype 3-4 (airspeed and heading) + 3: ["sub", "icf", "ifr", "nuc", "mhs", "hdg", "ast", "spd", "vrs", "gsvr", "gvr", "ghds", "ghd"] + } + + def get_type(self): + sub = self.get_bits(self.fields["sub"]) + if sub == 0: + return 0 + if 1 <= sub <= 2: + return 1 + if 3 <= sub <= 4: + return 3 + + def get_numbits(self): + return 51 + #type 17 extended squitter data class me_reply(data_field): #TODO: add comments explaining these fields - fields = { "ftc": (1,5), "ss": (6,2), "saf": (8,1), "alt": (9, 12), + fields = { "ftc": (1,5), + #BDS0,5 and BDS0,6 position information fields + "ss": (6,2), "saf": (8,1), "alt": (9, 12), "time": (21,1), "cpr": (22,1), "lat": (23, 17), "lon": (40, 17), "mvt": (6,7), "gts": (13,1), "gtk": (14,7), "trs": (1,2), - "ats": (3,1), "cat": (6,3), "ident": (9,48), "sub": (6,3), - "bds09": (9,48), "eps": (9,3) - #TODO: TCP, TCP+1/BDS 6,2 + "ats": (3,1), "cat": (6,3), "ident": (9,48), + #subclassed BDS0,9 velocity information subfields + "bds09": (6,51), + #emergency type BDS6,1 + "eps": (9,3) + #TODO: TCP, TCP+1/BDS 6,2/FTC 29 } subfields = { "bds09": bds09_reply } - startbit = 0 + #types in this format are listed by BDS register types = { 0x05: ["ftc", "ss", "saf", "alt", "time", "cpr", "lat", "lon"], #airborne position 0x06: ["ftc", "mvt", "gts", "gtk", "time", "cpr", "lat", "lon"], #surface position 0x07: ["ftc",], #TODO extended squitter status 0x08: ["ftc", "cat", "ident"], #extended squitter identification and type - 0x09: ["ftc", "sub", "bds09"], + 0x09: ["ftc", "bds09"], #0x0A: data link capability report #0x17: common usage capability report #0x18-0x1F: Mode S specific services capability report @@ -140,21 +181,6 @@ class me_reply(data_field): def get_numbits(self): return 56 -class bds09_reply(data_field): - fields = { "sub": (6,3), "dew": (10,1), "vew": (11,11), "dns": (22,1), - "vns": (23,11), "str": (34,1), "tr": (35,6), "svr": (41,1), - "vr": (42,9), "icf": (9,1), "ifr": (10,1), "nuc": (11,3), - "gdew": (14,1), "gvew": (15,10), "gdns": (25,1), "gvns": (26,10), - "vrs": (36,1), "gsvr": (37,1), "gvr": (38,9), "ghds": (49,1), - "ghd": (50,6), "mhs": (14,1), "hdg": (15,10), "ast": (25,1), - "spd": (26,10) - } - - types = { #TODO - - def get_type(self): - return self.get_bits(self.fields["sub"]) - class modes_reply(data_field): #bitfield definitions according to Mode S spec #(start bit, num bits) @@ -267,23 +293,22 @@ class modes_parse: [decoded_lat, decoded_lon, rnge, bearing] = self.cpr.decode(icao24, encoded_lat, encoded_lon, cpr_format, 1) return [altitude, decoded_lat, decoded_lon, rnge, bearing] - def parseBDS09_0(self, shortdata, longdata): - icao24 = shortdata & 0xFFFFFF - vert_spd = ((longdata >> 6) & 0x1FF) * 32 - ud = bool((longdata >> 15) & 1) + def parseBDS09_0(self, data): + #0: ["sub", "dew", "vew", "dns", "vns", "str", "tr", "svr", "vr"], + bdsobj = data["me"]["bds09"] + vert_spd = bdsobj["vr"] * 32 + ud = bool(bdsobj["dvr"]) if ud: vert_spd = 0 - vert_spd - turn_rate = (longdata >> 16) & 0x3F - turn_rate = turn_rate * 15/62 - rl = bool((longdata >> 22) & 1) + turn_rate = bdsobj["tr"] * 15/62 + rl = bdsobj["str"] if rl: turn_rate = 0 - turn_rate - ns_vel = (longdata >> 23) & 0x7FF - 1 - ns = bool((longdata >> 34) & 1) - ew_vel = (longdata >> 35) & 0x7FF - 1 - ew = bool((longdata >> 46) & 1) - subtype = (longdata >> 48) & 0x07 - + ns_vel = bdsobj["vns"] - 1 + ns = bool(bdsobj["dns"]) + ew_vel = bdsobj["vew"] - 1 + ew = bool(bdsobj["dew"]) + velocity = math.hypot(ns_vel, ew_vel) if ew: ew_vel = 0 - ew_vel @@ -295,14 +320,15 @@ class modes_parse: return [velocity, heading, vert_spd, turn_rate] - def parseBDS09_1(self, shortdata, longdata): - icao24 = shortdata & 0xFFFFFF - alt_geo_diff = longdata & 0x7F - 1 - above_below = bool((longdata >> 7) & 1) + def parseBDS09_1(self, data): + #1: ["sub", "icf", "ifr", "nuc", "gdew", "gvew", "gdns", "gvns", "vrs", "gsvr", "gvr", "ghds", "ghd"], + bdsobj = data["me"]["bds09"] + alt_geo_diff = bdsobj["ghd"] + above_below = bool(bdsobj["ghds"]) if above_below: alt_geo_diff = 0 - alt_geo_diff; - vert_spd = float((longdata >> 10) & 0x1FF - 1) - ud = bool((longdata >> 19) & 1) + vert_spd = float(bdsobj["gvr"] - 1) + ud = bool(bdsobj["gdvr"]) if ud: vert_spd = 0 - vert_spd vert_src = bool((longdata >> 20) & 1) diff --git a/python/modes_sbs1.py b/python/modes_sbs1.py index 68fc146..0f97d36 100644 --- a/python/modes_sbs1.py +++ b/python/modes_sbs1.py @@ -123,7 +123,7 @@ class modes_output_sbs1(modes_parse.modes_parse): elif msgtype == 17: outmsg = self.pp17(shortdata, longdata) else: - raise NoHandlerError + raise NoHandlerError(msgtype) return outmsg def pp0(self, shortdata, ecc): @@ -147,7 +147,7 @@ class modes_output_sbs1(modes_parse.modes_parse): def pp5(self, shortdata, ecc): # I'm not sure what to do with the identiifcation shortdata & 0x1FFF [datestr, timestr] = self.current_time() - [fs, dr, um, ident] = self.parse5(shortdata) + [fs, dr, um] = self.parse5(shortdata) aircraft_id = self.get_aircraft_id(ecc) retstr = "MSG,6,0,%i,%06X,%i,%s,%s,%s,%s,,,,,,,,," % (aircraft_id, ecc, aircraft_id+100, datestr, timestr, datestr, timestr) return retstr + self.decode_fs(fs) + "\n" From 067556cbdd2060f8238046838b5ec12f39a42360 Mon Sep 17 00:00:00 2001 From: Nick Foster Date: Tue, 26 Jun 2012 09:46:21 -0700 Subject: [PATCH 10/24] Fields eliminated, type includes field data. Subfields also gone. --- python/modes_parse.py | 153 ++++++++++++++++-------------------------- 1 file changed, 57 insertions(+), 96 deletions(-) diff --git a/python/modes_parse.py b/python/modes_parse.py index 3041076..2cf1311 100644 --- a/python/modes_parse.py +++ b/python/modes_parse.py @@ -31,26 +31,20 @@ class data_field: def __init__(self, data): self.data = data - fields = { } types = { } - subfields = { } #fields to return objects instead of just returning bits, - #used for fields which may have multiple meanings (like ME for Type 17) offset = 1 #field offset applied to all fields. used for offsetting #subtypes to reconcile with the spec. Really just for readability. #get a particular field from the data def __getitem__(self, fieldname): - if self.get_type() in self.types: - if fieldname in self.types[self.get_type()]: #verify it exists in this packet type - if fieldname in self.subfields: - #create a new subfield object and return it - return self.subfields[fieldname](self.get_bits(self.fields[fieldname])) - else: - return self.get_bits(self.fields[fieldname]) + mytype = self.get_type() + if mytype in self.types: + if fieldname in self.types[mytype]: #verify it exists in this packet type + return self.get_bits(*self.types[mytype][fieldname]) else: raise FieldNotInPacket(fieldname) else: - raise NoHandlerError(self.get_type()) + raise NoHandlerError(mytype) #grab all the fields in the packet as a dict def get_fields(self): @@ -65,11 +59,18 @@ class data_field: #retrieve bits from data given the offset and number of bits. #the offset is both left-justified (LSB) and starts at 1, to #correspond to the Mode S spec. Blame them. - def get_bits(self, arg): - (startbit, num) = arg - return (self.data \ + def get_bits(self, *args): + startbit = args[0] + num = args[1] + bits = (self.data \ >> (self.get_numbits() - startbit - num + self.offset)) \ & ((1 << num) - 1) + if len(args) == 3: + #construct a subtype from the bit field + return args[2](bits) + else: + #return the bits as a number + return bits #type MB (extended squitter types 20,21) subfields class mb_reply(data_field): @@ -100,29 +101,24 @@ class mb_reply(data_field): return 56 class bds09_reply(data_field): - fields = { - "sub": (6,3), "dew": (10,1), "vew": (11,11), "dns": (22,1), - "vns": (23,11), "str": (34,1), "tr": (35,6), "dvr": (41,1), - "vr": (42,9), "icf": (9,1), "ifr": (10,1), "nuc": (11,3), - "gdew": (14,1), "gvew": (15,10), "gdns": (25,1), "gvns": (26,10), - "vrsrc": (36,1), "gdvr": (37,1), "gvr": (38,9), "ghds": (49,1), - "ghd": (50,6), "mhs": (14,1), "hdg": (15,10), "ast": (25,1), - "spd": (26,10) - } - offset = 6 - types = { #BDS0,9 subtype 0 - 0: ["sub", "dew", "vew", "dns", "vns", "str", "tr", "dvr", "vr"], + 0: {"sub": (6,3), "dew": (10,1), "vew": (11,11), "dns": (22,1), + "vns": (23,11), "str": (34,1), "tr": (35,6), "dvr": (41,1), + "vr": (42,9)}, #BDS0,9 subtypes 1-2 (differ only in velocity encoding) - 1: ["sub", "icf", "ifr", "nuc", "gdew", "gvew", "gdns", "gvns", "vrsrc", "gdvr", "gvr", "ghds", "ghd"], + 1: {"sub": (6,3), "icf": (9,1), "ifr": (10,1), "nuc": (11,3), + "dew": (14,1), "vew": (15,10), "dns": (25,1), "vns": (26,10), + "vrsrc": (36,1), "dvr": (37,1), "vr": (38,9), "dhd": (49,1), "hd": (50,6)}, #BDS0,9 subtype 3-4 (airspeed and heading) - 3: ["sub", "icf", "ifr", "nuc", "mhs", "hdg", "ast", "spd", "vrs", "gsvr", "gvr", "ghds", "ghd"] + 3: {"sub": (6,3), "icf": (9,1), "ifr": (10,1), "nuc": (11,3), "mhs": (14,1), + "hdg": (15,10), "ast": (25,1), "spd": (26,10), "vrsrc": (36,1), + "dvr": (37,1), "vr": (38,9), "dhd": (49,1), "hd": (50,6)} } def get_type(self): - sub = self.get_bits(self.fields["sub"]) + sub = self.get_bits(6,3) if sub == 0: return 0 if 1 <= sub <= 2: @@ -135,38 +131,23 @@ class bds09_reply(data_field): #type 17 extended squitter data class me_reply(data_field): - #TODO: add comments explaining these fields - fields = { "ftc": (1,5), - #BDS0,5 and BDS0,6 position information fields - "ss": (6,2), "saf": (8,1), "alt": (9, 12), - "time": (21,1), "cpr": (22,1), "lat": (23, 17), "lon": (40, 17), - "mvt": (6,7), "gts": (13,1), "gtk": (14,7), "trs": (1,2), - "ats": (3,1), "cat": (6,3), "ident": (9,48), - #subclassed BDS0,9 velocity information subfields - "bds09": (6,51), - #emergency type BDS6,1 - "eps": (9,3) - #TODO: TCP, TCP+1/BDS 6,2/FTC 29 - } - - subfields = { "bds09": bds09_reply } - #types in this format are listed by BDS register - types = { 0x05: ["ftc", "ss", "saf", "alt", "time", "cpr", "lat", "lon"], #airborne position - 0x06: ["ftc", "mvt", "gts", "gtk", "time", "cpr", "lat", "lon"], #surface position - 0x07: ["ftc",], #TODO extended squitter status - 0x08: ["ftc", "cat", "ident"], #extended squitter identification and type - 0x09: ["ftc", "bds09"], + #TODO: add comments explaining these fields + types = { 0x05: {"ftc": (1,5), "ss": (6,2), "saf": (8,1), "alt": (9,12), "time": (21,1), "cpr": (22,1), "lat": (23,17), "lon": (40,17)}, #airborne position + 0x06: {"ftc": (1,5), "mvt": (6,7), "gts": (13,1), "gtk": (14,7), "time": (21,1), "cpr": (22,1), "lat": (23,17), "lon": (40,17)}, #surface position + 0x07: {"ftc": (1,5),}, #TODO extended squitter status + 0x08: {"ftc": (1,5), "cat": (6,3), "ident": (9,48)}, #extended squitter identification and type + 0x09: {"ftc": (1,5), "bds09": (6,51, bds09_reply)}, #0x0A: data link capability report #0x17: common usage capability report #0x18-0x1F: Mode S specific services capability report #0x20: aircraft identification - 0x61: ["ftc", "eps"] + 0x61: {"ftc": (1,5), "eps": (9,3)} } #maps ftc to BDS register def get_type(self): - ftc = self.get_bits(self.fields["ftc"]) + ftc = self.get_bits(1,5) if 1 <= ftc <= 4: return 0x08 elif 5 <= ftc <= 8: @@ -182,30 +163,18 @@ class me_reply(data_field): return 56 class modes_reply(data_field): - #bitfield definitions according to Mode S spec - #(start bit, num bits) - fields = { "df": (1,5), "vs": (6,1), "fs": (6,3), "cc": (7,1), - "sl": (9,3), "ri": (14,4), "ac": (20,13), "dr": (9,5), - "um": (14,6), "id": (20,13), "ca": (6,3), "aa": (9,24), - "mv": (33,56), "me": (33,56), "mb": (33,56), "ke": (6,1), - "nd": (7,4), "md": (11,80), "ap": (33,24), "pi": (33,24), - "lap": (88,24), "lpi": (88,24) - } - - #fields in each packet type (DF value) - types = { 0: ["df", "vs", "cc", "sl", "ri", "ac", "ap"], - 4: ["df", "fs", "dr", "um", "ac", "ap"], - 5: ["df", "fs", "dr", "um", "id", "ap"], - 11: ["df", "ca", "aa", "pi"], - 16: ["df", "vs", "sl", "ri", "ac", "mv", "lap"], - 17: ["df", "ca", "aa", "me", "lpi"], - 20: ["df", "fs", "dr", "um", "ac", "mb", "lap"], - 21: ["df", "fs", "dr", "um", "id", "mb", "lap"], - 24: ["df", "ke", "nd", "md", "lap"] + #bitfield definitions according to Mode S spec for each packet type + types = { 0: {"df": (1,5), "vs": (6,1), "cc": (7,1), "sl": (9,3), "ri": (14,4), "ac": (20,13), "ap": (33,24)}, + 4: {"df": (1,5), "fs": (6,3), "dr": (9,5), "um": (14,6), "ac": (20,13), "ap": (33,24)}, + 5: {"df": (1,5), "fs": (6,3), "dr": (9,5), "um": (14,6), "id": (20,13), "ap": (33,24)}, + 11: {"df": (1,5), "ca": (6,3), "aa": (9,24), "pi": (33,24)}, + 16: {"df": (1,5), "vs": (6,1), "sl": (9,3), "ri": (14,4), "ac": (20,13), "mv": (33,56), "ap": (88,24)}, + 17: {"df": (1,5), "ca": (6,3), "aa": (9,24), "me": (33,56, me_reply), "pi": (88,24)}, + 20: {"df": (1,5), "fs": (6,3), "dr": (9,24), "um": (14,6), "ac": (20,13), "mb": (33,56, mb_reply), "ap": (88,24)}, + 21: {"df": (1,5), "fs": (6,3), "dr": (9,24), "um": (14,6), "id": (20,13), "mb": (33,56, mb_reply), "ap": (88,24)}, + 24: {"df": (1,5), "ke": (6,1), "nd": (7,4), "md": (11,80), "ap": (88,24)} } - subfields = { "mb": mb_reply, "me": me_reply } #TODO MV - def is_long(self): return self.data > (1 << 56) @@ -213,9 +182,7 @@ class modes_reply(data_field): return 112 if self.is_long() else 56 def get_type(self): - return self.get_bits(self.fields["df"]) - -#TODO overload getitem to handle special parity fields + return self.get_bits(1,5) # #type MV (extended squitter type 16) subfields # mv_fields = { "ara": (41,14), "mte": (60,1), "rac": (55,4), "rat": (59,1), @@ -321,32 +288,26 @@ class modes_parse: return [velocity, heading, vert_spd, turn_rate] def parseBDS09_1(self, data): - #1: ["sub", "icf", "ifr", "nuc", "gdew", "gvew", "gdns", "gvns", "vrs", "gsvr", "gvr", "ghds", "ghd"], + #1: ["sub", "icf", "ifr", "nuc", "dew", "vew", "dns", "vns", "vrsrc", "dvr", "vr", "dhd", "hd"], bdsobj = data["me"]["bds09"] - alt_geo_diff = bdsobj["ghd"] - above_below = bool(bdsobj["ghds"]) + alt_geo_diff = bdsobj["hd"] * 25 + above_below = bool(bdsobj["dhd"]) if above_below: alt_geo_diff = 0 - alt_geo_diff; - vert_spd = float(bdsobj["gvr"] - 1) - ud = bool(bdsobj["gdvr"]) + vert_spd = float(bdsobj["vr"] - 1) * 64 + ud = bool(bdsobj["dvr"]) if ud: vert_spd = 0 - vert_spd - vert_src = bool((longdata >> 20) & 1) - ns_vel = float((longdata >> 21) & 0x3FF - 1) - ns = bool((longdata >> 31) & 1) - ew_vel = float((longdata >> 32) & 0x3FF - 1) - ew = bool((longdata >> 42) & 1) - subtype = (longdata >> 48) & 0x07 - - + vert_src = bool(bdsobj["vrsrc"]) + ns_vel = float(bdsobj["vns"]) + ns = bool(bdsobj["dns"]) + ew_vel = float(bdsobj["vew"]) + ew = bool(bdsobj["dew"]) + subtype = bdsobj["sub"] if subtype == 0x02: - ns_vel *= 4 - ew_vel *= 4 + ns_vel <<= 2 + ew_vel <<= 2 - - vert_spd *= 64 - alt_geo_diff *= 25 - velocity = math.hypot(ns_vel, ew_vel) if ew: ew_vel = 0 - ew_vel From cf8f429900dcd2165c515f0f822fa44c0283d811 Mon Sep 17 00:00:00 2001 From: Nick Foster Date: Tue, 26 Jun 2012 15:37:20 -0700 Subject: [PATCH 11/24] Cleaned up the print module, minor changes to parse and FGFS module. Untested. --- python/modes_flightgear.py | 19 +++++---- python/modes_parse.py | 49 ++++++++++++---------- python/modes_print.py | 83 +++++++++++++++++++++----------------- 3 files changed, 83 insertions(+), 68 deletions(-) diff --git a/python/modes_flightgear.py b/python/modes_flightgear.py index ec01165..d938760 100755 --- a/python/modes_flightgear.py +++ b/python/modes_flightgear.py @@ -51,16 +51,15 @@ class modes_flightgear(modes_parse.modes_parse): self.update(icao24) elif subtype == 19: #velocity - pass #FIXME TODO BDS0,9 - # subsubtype = (longdata >> 48) & 0x07 - # if subsubtype == 0: - # [velocity, heading, vert_spd, turnrate] = self.parseBDS09_0(data) - # elif subsubtype == 1: - # [velocity, heading, vert_spd] = self.parseBDS09_1(data) - # turnrate = 0 - # else: - # return - # self.velocities[icao24] = [velocity, heading, vert_spd, turnrate] + subsubtype = data["me"]["bds09"]["sub"] + if subsubtype == 0: + [velocity, heading, vert_spd, turnrate] = self.parseBDS09_0(data) + elif 1 <= subsubtype <= 2: + [velocity, heading, vert_spd] = self.parseBDS09_1(data) + turnrate = 0 + else: + return + self.velocities[icao24] = [velocity, heading, vert_spd, turnrate] except ADSBError: pass diff --git a/python/modes_parse.py b/python/modes_parse.py index 2cf1311..f84f2ea 100644 --- a/python/modes_parse.py +++ b/python/modes_parse.py @@ -156,6 +156,8 @@ class me_reply(data_field): return 0x05 elif ftc == 19: return 0x09 + elif ftc == 28: + return 0x61 else: return NoHandlerError @@ -212,7 +214,7 @@ class modes_parse: return [data["aa"], interrogator, data["ca"]] categories = [["NO INFO", "RESERVED", "RESERVED", "RESERVED", "RESERVED", "RESERVED", "RESERVED", "RESERVED"],\ - ["NO INFO", "SURFACE EMERGENCY VEHICLE", "SURFACE SERVICE VEHICLE", "FIXED OBSTRUCTION", "RESERVED", "RESERVED", "RESERVED"],\ + ["NO INFO", "SURFACE EMERGENCY VEHICLE", "SURFACE SERVICE VEHICLE", "FIXED OBSTRUCTION", "CLUSTER OBSTRUCTION", "LINE OBSTRUCTION", "RESERVED"],\ ["NO INFO", "GLIDER", "BALLOON/BLIMP", "PARACHUTE", "ULTRALIGHT", "RESERVED", "UAV", "SPACECRAFT"],\ ["NO INFO", "LIGHT", "SMALL", "LARGE", "LARGE HIGH VORTEX", "HEAVY", "HIGH PERFORMANCE", "ROTORCRAFT"]] @@ -323,28 +325,33 @@ class modes_parse: return [velocity, heading, vert_spd] - def parse20(self, shortdata, longdata): - [fs, dr, um, alt] = self.parse4(shortdata) - #BDS defines TCAS reply type and is the first 8 bits - #BDS1 is first four, BDS2 is bits 5-8 - bds1 = longdata_bits(longdata, 33, 4) - bds2 = longdata_bits(longdata, 37, 4) - #bds2 != 0 defines extended TCAS capabilities, not in spec - return [fs, dr, um, alt, bds1, bds2] + def parseBDS62(self, data): + eps_strings = ["NO EMERGENCY", "GENERAL EMERGENCY", "LIFEGUARD/MEDICAL", "FUEL EMERGENCY", + "NO COMMUNICATIONS", "UNLAWFUL INTERFERENCE", "RESERVED", "RESERVED"] + return eps_strings[data["me"]["eps"]] - def parseMB_commB(self, longdata): #bds1, bds2 == 0 - raise NoHandlerError +# def parse20(self, shortdata, longdata): +# [fs, dr, um, alt] = self.parse4(shortdata) +# #BDS defines TCAS reply type and is the first 8 bits +# #BDS1 is first four, BDS2 is bits 5-8 +# bds1 = longdata_bits(longdata, 33, 4) +# bds2 = longdata_bits(longdata, 37, 4) +# #bds2 != 0 defines extended TCAS capabilities, not in spec +# return [fs, dr, um, alt, bds1, bds2] - def parseMB_caps(self, longdata): #bds1 == 1, bds2 == 0 - #cfs, acs, bcs - raise NoHandlerError +# def parseMB_commB(self, longdata): #bds1, bds2 == 0 +# raise NoHandlerError - def parseMB_id(self, longdata): #bds1 == 2, bds2 == 0 - msg = "" - for i in range(0, 8): - msg += self.charmap( longdata >> (42-6*i) & 0x3F) - return (msg) +# def parseMB_caps(self, longdata): #bds1 == 1, bds2 == 0 +# #cfs, acs, bcs +# raise NoHandlerError - def parseMB_TCASRA(self, longdata): #bds1 == 3, bds2 == 0 +# def parseMB_id(self, longdata): #bds1 == 2, bds2 == 0 +# msg = "" +# for i in range(0, 8): +# msg += self.charmap( longdata >> (42-6*i) & 0x3F) +# return (msg) + +# def parseMB_TCASRA(self, longdata): #bds1 == 3, bds2 == 0 #ara[41-54],rac[55-58],rat[59],mte[60],tti[61-62],tida[63-75],tidr[76-82],tidb[83-88] - raise NoHandlerError +# raise NoHandlerError diff --git a/python/modes_print.py b/python/modes_print.py index 71b4c06..612b447 100644 --- a/python/modes_print.py +++ b/python/modes_print.py @@ -57,10 +57,10 @@ class modes_output_print(modes_parse.modes_parse): elif msgtype == 17: output += self.print17(data) else: - output += "No handler for message type " + str(msgtype) + (" from %x" % ecc) + " (but it's in modes_parse)" + output += "No handler for message type %i from %x (but it's in modes_parse)" % (msgtype, ecc) print output except NoHandlerError as e: - output += "No handler for message type " + str(e.msgtype) + " from %x" % ecc + output += "No handler for message type %i from %x" % (msgtype, ecc) print output except MetricAltError: pass @@ -70,15 +70,23 @@ class modes_output_print(modes_parse.modes_parse): def print0(self, shortdata, ecc): [vs, cc, sl, ri, altitude] = self.parse0(shortdata) - retstr = "Type 0 (short A-A surveillance) from " + "%x" % ecc + " at " + str(altitude) + "ft" + retstr = "Type 0 (short A-A surveillance) from %x at %ift" % (ecc, altitude) # the ri values below 9 are used for other things. might want to print those someday. - if ri == 9: - retstr = retstr + " (speed <75kt)" + if ri == 0: + retstr += " (No TCAS)" + elif ri == 2: + retstr += " (TCAS resolution inhibited)" + elif ri == 3: + retstr += " (Vertical TCAS resolution only)" + elif ri == 4: + retstr += " (Full TCAS resolution)" + elif ri == 9: + retstr += " (speed <75kt)" elif ri > 9: - retstr = retstr + " (speed " + str(75 * (1 << (ri-10))) + "-" + str(75 * (1 << (ri-9))) + "kt)" + retstr += " (speed %i-%ikt)" % (75 * (1 << (ri-10)), 75 * (1 << (ri-9))) if vs is True: - retstr = retstr + " (aircraft is on the ground)" + retstr += " (aircraft is on the ground)" return retstr @@ -86,43 +94,42 @@ class modes_output_print(modes_parse.modes_parse): [fs, dr, um, altitude] = self.parse4(shortdata) - retstr = "Type 4 (short surveillance altitude reply) from " + "%x" % ecc + " at " + str(altitude) + "ft" + retstr = "Type 4 (short surveillance altitude reply) from %x at %ift" % (ecc, altitude) if fs == 1: - retstr = retstr + " (aircraft is on the ground)" + retstr += " (aircraft is on the ground)" elif fs == 2: - retstr = retstr + " (AIRBORNE ALERT)" + retstr += " (AIRBORNE ALERT)" elif fs == 3: - retstr = retstr + " (GROUND ALERT)" + retstr += " (GROUND ALERT)" elif fs == 4: - retstr = retstr + " (SPI ALERT)" + retstr += " (SPI ALERT)" elif fs == 5: - retstr = retstr + " (SPI)" + retstr += " (SPI)" return retstr def print5(self, shortdata, ecc): [fs, dr, um, ident] = self.parse5(shortdata) - retstr = "Type 5 (short surveillance ident reply) from " + "%x" % ecc + " with ident " + str(ident) + retstr = "Type 5 (short surveillance ident reply) from %x with ident %i" % (ecc, ident) if fs == 1: - retstr = retstr + " (aircraft is on the ground)" + retstr += " (aircraft is on the ground)" elif fs == 2: - retstr = retstr + " (AIRBORNE ALERT)" + retstr += " (AIRBORNE ALERT)" elif fs == 3: - retstr = retstr + " (GROUND ALERT)" + retstr += " (GROUND ALERT)" elif fs == 4: - retstr = retstr + " (SPI ALERT)" + retstr += " (SPI ALERT)" elif fs == 5: - retstr = retstr + " (SPI)" + retstr += " (SPI)" return retstr def print11(self, data, ecc): [icao24, interrogator, ca] = self.parse11(data, ecc) - - retstr = "Type 11 (all call reply) from " + "%x" % icao24 + " in reply to interrogator " + str(interrogator) + retstr = "Type 11 (all call reply) from %x in reply to interrogator %i" % (icao24, interrogator) return retstr def print17(self, data): @@ -133,35 +140,37 @@ class modes_output_print(modes_parse.modes_parse): if 1 <= subtype <= 4: (msg, typestring) = self.parseBDS08(data) - retstr = "Type 17 subtype 04 (ident) from " + "%x" % icao24 + " of type " + typestring + " with ident " + msg + retstr = "Type 17 subtype 04 (ident) from %x type %s ident %s" % (icao24, typestring, msg) elif subtype >= 5 and subtype <= 8: [altitude, decoded_lat, decoded_lon, rnge, bearing] = self.parseBDS06(data) - retstr = "Type 17 subtype 06 (surface report) from " + "%x" % icao24 + " at (" + "%.6f" % decoded_lat + ", " + "%.6f" % decoded_lon + ")" + retstr = "Type 17 subtype 06 (surface report) from %x at (%.6f, %.6f)" % (icao24, decoded_lat, decoded_lon) if rnge is not None and bearing is not None: - retstr += " (" + "%.2f" % rnge + " @ " + "%.0f" % bearing + ")" + retstr += " (%.2f @ %.0f)" % (rnge, bearing) elif subtype >= 9 and subtype <= 18: [altitude, decoded_lat, decoded_lon, rnge, bearing] = self.parseBDS05(data) - retstr = "Type 17 subtype 05 (position report) from " + "%x" % icao24 + " at (" + "%.6f" % decoded_lat + ", " + "%.6f" % decoded_lon + ")" + retstr = "Type 17 subtype 05 (position report) from %x at (%.6f, %.6f)" % (icao24, decoded_lat, decoded_lon) if rnge is not None and bearing is not None: retstr += " (" + "%.2f" % rnge + " @ " + "%.0f" % bearing + ")" retstr += " at " + str(altitude) + "ft" elif subtype == 19: - retstr = "Fix BDS0,9, dickhead" -# subsubtype = (longdata >> 48) & 0x07 -# if subsubtype == 0: -# [velocity, heading, vert_spd] = self.parseBDS09_0(shortdata, longdata) -# retstr = "Type 17 subtype 09-0 (track report) from " + "%x" % icao24 + " with velocity " + "%.0f" % velocity + "kt heading " + "%.0f" % heading + " VS " + "%.0f" % vert_spd + subsubtype = data["me"]["bds09"]["sub"] + if subsubtype == 0: + [velocity, heading, vert_spd] = self.parseBDS09_0(data) + retstr = "Type 17 subtype 09-0 (track report) from %x with velocity %.0fkt heading %.0f VS %.0f" % (icao24, velocity, heading, vert_spd) + elif 1 <= subsubtype <= 2: + [velocity, heading, vert_spd] = self.parseBDS09_1(data) + retstr = "Type 17 subtype 09-%i (track report) from %x with velocity %.0fkt heading %.0f VS %.0f" % (subsubtype, icao24, velocity, heading, vert_spd) + else: + retstr = "Type 17 subtype 09-%i from %x not implemented" % (subsubtype, icao24) -# elif subsubtype == 1: -# [velocity, heading, vert_spd] = self.parseBDS09_1(shortdata, longdata) -# retstr = "Type 17 subtype 09-1 (track report) from " + "%x" % icao24 + " with velocity " + "%.0f" % velocity + "kt heading " + "%.0f" % heading + " VS " + "%.0f" % vert_spd - -# else: -# retstr = "Type 17 subtype 09-%i" % (subsubtype) + " not implemented" + elif subtype == 28: + emerg_str = self.parseBDS62(data) + retstr = "Type 17 subtype 28 (emergency) from %x type %s" % (icao24, emerg_str) + else: - retstr = "Type 17 subtype " + str(subtype) + " not implemented" + retstr = "Type 17 subtype %i from %x not implemented" % (subtype, icao24) return retstr From b05bea9618869bf34cff79bf6b188edc73b9beb2 Mon Sep 17 00:00:00 2001 From: Nick Foster Date: Tue, 26 Jun 2012 19:25:31 -0700 Subject: [PATCH 12/24] Parser and printer for type 20 and TCAS info --- python/modes_flightgear.py | 6 +- python/modes_parse.py | 135 +++++++++++++++++++++---------------- python/modes_print.py | 36 ++++++++++ 3 files changed, 116 insertions(+), 61 deletions(-) diff --git a/python/modes_flightgear.py b/python/modes_flightgear.py index d938760..9feb6b6 100755 --- a/python/modes_flightgear.py +++ b/python/modes_flightgear.py @@ -72,7 +72,7 @@ class modes_flightgear(modes_parse.modes_parse): if complete: print "FG update: %s" % (self.callsigns[icao24][0]) msg = fg_posmsg(self.callsigns[icao24][0], - None,#TODO FIX should populate ac type; this isn't pressing bc it appears nobody populates this field + self.callsigns[icao24][1], self.positions[icao24][0], self.positions[icao24][1], self.positions[icao24][2], @@ -133,14 +133,14 @@ modelmap = { None: 'Aircraft/777-200/Models/777-200ER.xml' } class fg_posmsg(fg_header): - def __init__(self, callsign, model, lat, lon, alt, hdg, vel, vs, turnrate): + def __init__(self, callsign, modelname, lat, lon, alt, hdg, vel, vs, turnrate): #from the above, calculate valid FGFS mp vals #this is the translation layer between ADS-B and FGFS fg_header.__init__(self) self.callsign = callsign if self.callsign is None: self.callsign = "UNKNOWN" - self.modelname = model + self.modelname = modelname if self.modelname not in modelmap: #this should keep people on their toes when strange aircraft types are seen self.model = 'Aircraft/santa/Models/santa.xml' diff --git a/python/modes_parse.py b/python/modes_parse.py index f84f2ea..16046ea 100644 --- a/python/modes_parse.py +++ b/python/modes_parse.py @@ -72,38 +72,9 @@ class data_field: #return the bits as a number return bits -#type MB (extended squitter types 20,21) subfields -class mb_reply(data_field): - fields = { "acs": (45,20), "ais": (41,48), "ara": (41,14), "bcs": (65,16), - "bds": (33,8), "bds1": (33,4), "bds2": (37,4), "cfs": (41,4), - "ecs": (81,8), "mte": (60,1), "rac": (55,4), "rat": (59,1), - "tid": (33,26), "tida": (63,13), "tidb": (83,6), "tidr": (76,7), - "tti": (61,2) - } - offset = 33 #fields offset by 33 to match documentation - - #types are based on bds1 subfield - types = { 0: ["bds", "bds1", "bds2"], #TODO - 1: ["bds", "bds1", "bds2", "cfs", "acs", "bcs"], - 2: ["bds", "bds1", "bds2", "ais"], - 3: ["bds", "bds1", "bds2", "ara", "rac", "rat", - "mte", "tti", "tida", "tidr", "tidb"] - } - - def get_type(self): - bds1 = self.get_bits(self.fields["bds1"]) - bds2 = self.get_bits(self.fields["bds2"]) - if bds1 not in (0,1,2,3) or bds2 not in (0,): - raise NoHandlerError - return int(bds1) - - def get_numbits(self): - return 56 - class bds09_reply(data_field): offset = 6 - types = { - #BDS0,9 subtype 0 + types = { #BDS0,9 subtype 0 0: {"sub": (6,3), "dew": (10,1), "vew": (11,11), "dns": (22,1), "vns": (23,11), "str": (34,1), "tr": (35,6), "dvr": (41,1), "vr": (42,9)}, @@ -164,8 +135,47 @@ class me_reply(data_field): def get_numbits(self): return 56 +#resolves the TCAS reply types from TTI info +class tcas_reply(data_field): + offset = 61 + types = { 0: {"tti": (61,2)}, #UNKNOWN + 1: {"tti": (61,2), "tid": (63,26)}, + 2: {"tti": (61,2), "tida": (63,13), "tidr": (76,7), "tidb": (83,6)} + } + def get_type(self): + return self.get_bits(61,2) + + def get_numbits(self): + return 28 + +#extended squitter types 20,21 MB subfield +class mb_reply(data_field): + offset = 33 #fields offset by 33 to match documentation + #types are based on bds1 subfield + types = { 0: {"bds1": (33,4), "bds2": (37,4)}, #TODO + 1: {"bds1": (33,4), "bds2": (37,4), "cfs": (41,4), "acs": (45,20), "bcs": (65,16), "ecs": (81,8)}, + 2: {"bds1": (33,4), "bds2": (37,4), "ais": (41,48)}, + 3: {"bds1": (33,4), "bds2": (37,4), "ara": (41,14), "rac": (55,4), "rat": (59,1), + "mte": (60,1), "tcas": (61, 28, tcas_reply)} + } + + def get_type(self): + bds1 = self.get_bits(33,4) + bds2 = self.get_bits(37,4) + if bds1 not in (0,1,2,3) or bds2 not in (0,): + raise NoHandlerError + return int(bds1) + + def get_numbits(self): + return 56 + +# #type MV (extended squitter type 16) subfields +# mv_fields = { "ara": (41,14), "mte": (60,1), "rac": (55,4), "rat": (59,1), +# "vds": (33,8), "vds1": (33,4), "vds2": (37,4) +# } + +#the whole Mode S packet type class modes_reply(data_field): - #bitfield definitions according to Mode S spec for each packet type types = { 0: {"df": (1,5), "vs": (6,1), "cc": (7,1), "sl": (9,3), "ri": (14,4), "ac": (20,13), "ap": (33,24)}, 4: {"df": (1,5), "fs": (6,3), "dr": (9,5), "um": (14,6), "ac": (20,13), "ap": (33,24)}, 5: {"df": (1,5), "fs": (6,3), "dr": (9,5), "um": (14,6), "id": (20,13), "ap": (33,24)}, @@ -186,11 +196,6 @@ class modes_reply(data_field): def get_type(self): return self.get_bits(1,5) -# #type MV (extended squitter type 16) subfields -# mv_fields = { "ara": (41,14), "mte": (60,1), "rac": (55,4), "rat": (59,1), -# "vds": (33,8), "vds1": (33,4), "vds2": (37,4) -# } - class modes_parse: def __init__(self, mypos): self.my_location = mypos @@ -330,28 +335,42 @@ class modes_parse: "NO COMMUNICATIONS", "UNLAWFUL INTERFERENCE", "RESERVED", "RESERVED"] return eps_strings[data["me"]["eps"]] -# def parse20(self, shortdata, longdata): -# [fs, dr, um, alt] = self.parse4(shortdata) -# #BDS defines TCAS reply type and is the first 8 bits -# #BDS1 is first four, BDS2 is bits 5-8 -# bds1 = longdata_bits(longdata, 33, 4) -# bds2 = longdata_bits(longdata, 37, 4) -# #bds2 != 0 defines extended TCAS capabilities, not in spec -# return [fs, dr, um, alt, bds1, bds2] + def parseMB_id(self, data): #bds1 == 2, bds2 == 0 + msg = "" + for i in range(0, 8): + msg += self.charmap( data["mb"]["ais"] >> (42-6*i) & 0x3F) + return (msg) -# def parseMB_commB(self, longdata): #bds1, bds2 == 0 -# raise NoHandlerError + def parseMB_TCAS_resolutions(self, data): + #these are LSB because the ICAO are asshats + ara_bits = {41: "CLIMB", 42: "DON'T DESCEND", 43: "DON'T DESCEND >500FPM", 44: "DON'T DESCEND >1000FPM", + 45: "DON'T DESCEND >2000FPM", 46: "DESCEND", 47: "DON'T CLIMB", 48: "DON'T CLIMB >500FPM", + 49: "DON'T CLIMB >1000FPM", 50: "DON'T CLIMB >2000FPM", 51: "TURN LEFT", 52: "TURN RIGHT", + 53: "DON'T TURN LEFT", 54: "DON'T TURN RIGHT"} + rac_bits = {55: "DON'T DESCEND", 56: "DON'T CLIMB", 57: "DON'T TURN LEFT", 58: "DON'T TURN RIGHT"} + ara = data["mb"]["ara"] + #check to see which bits are set + resolutions = "" + for bit, name in ara_bits: + if ara & (1 << (54-bit)): + resolutions += " " + name + complements = "" + for bit, name in rac_bits: + if rac & (1 << (58-bit)): + complements += " " + name + return (resolutions, complements) -# def parseMB_caps(self, longdata): #bds1 == 1, bds2 == 0 -# #cfs, acs, bcs -# raise NoHandlerError + #rat is 1 if resolution advisory terminated <18s ago + #mte is 1 if multiple threats indicated + #tti is threat type: 1 if ID, 2 if range/brg/alt + #tida is threat altitude in Mode C format + def parseMB_TCAS_threatid(self, data): #bds1==3, bds2==0, TTI==1 + #3: {"bds1": (33,4), "bds2": (37,4), "ara": (41,14), "rac": (55,4), "rat": (59,1), + # "mte": (60,1), "tti": (61,2), "tida": (63,13), "tidr": (76,7), "tidb": (83,6)} + (resolutions, complements) = self.parseMB_TCAS_resolutions(data) + return (resolutions, complements, data["mb"]["rat"], data["mb"]["mte"], data["mb"]["tcas"]["tid"]) -# def parseMB_id(self, longdata): #bds1 == 2, bds2 == 0 -# msg = "" -# for i in range(0, 8): -# msg += self.charmap( longdata >> (42-6*i) & 0x3F) -# return (msg) - -# def parseMB_TCASRA(self, longdata): #bds1 == 3, bds2 == 0 - #ara[41-54],rac[55-58],rat[59],mte[60],tti[61-62],tida[63-75],tidr[76-82],tidb[83-88] -# raise NoHandlerError + def parseMB_TCAS_threatloc(self, data): #bds1==3, bds2==0, TTI==2 + (resolutions, complements) = self.parseMB_TCAS_resolutions(data) + threat_alt = decode_alt(data["mb"]["tcas"]["tida"], True) + return (resolutions, complements, data["mb"]["rat"], data["mb"]["mte"], threat_alt, data["mb"]["tcas"]["tidr"], data["mb"]["tcas"]["tidb"]) diff --git a/python/modes_print.py b/python/modes_print.py index 612b447..d620d47 100644 --- a/python/modes_print.py +++ b/python/modes_print.py @@ -174,3 +174,39 @@ class modes_output_print(modes_parse.modes_parse): retstr = "Type 17 subtype %i from %x not implemented" % (subtype, icao24) return retstr + + def print20(self, data, ecc): + [fs, dr, um, alt] = self.parse4(data) + mb_fields = data["mb"].get_fields() + bds1 = mb_fields["bds1"] + bds2 = mb_fields["bds2"] + + if bds2 != 0: + retstr = "No handler for BDS2 == %i from %x" % (bds2, ecc) + + elif bds1 == 0: + retstr = "No handler for BDS1 == 0 from %x" % ecc + elif bds1 == 1: + retstr = "Type 20 link capability report from %x: ACS: 0x%x, BCS: 0x%x, ECS: 0x%x, continues %i" \ + % (ecc, mb_fields["acs"], mb_fields["bcs"], mb_fields["ecs"], mb_fields["cfs"]) + elif bds1 == 2: + retstr = "Type 20 identification from %x with text %s" % (ecc, self.parseMB_id(data)) + elif bds2 == 3: + retstr = "TCAS report from %x: " % ecc + tti = mb_fields["tcas"].get_type() + if tti == 1: + (resolutions, complements, rat, mte, threat_id) = self.parseMB_TCAS_threatid(data) + retstr += "threat ID: %x advised: %s complement: %s" % (threat_id, resolutions, complements) + elif tti == 2: + (resolutions, complements, rat, mte, threat_alt, threat_range, threat_bearing) = self.parseMB_TCAS_threatloc(data) + retstr += "range: %i bearing: %i alt: %i advised: %s complement: %s" % (threat_range, threat_bearing, threat_alt, resolutions, complements) + else: + retstr += " (no handler for TTI=%i)" % tti + if mte == 1: + retstr += " (multiple threats)" + if rat == 1: + retstr += " (resolved)" + else: + retstr = "No handler for BDS1 == %i from %x" % (bds1, ecc) + + return retstr From 28824cb0b23db96cbae539a5dc0aa424adc1360c Mon Sep 17 00:00:00 2001 From: Nick Foster Date: Tue, 26 Jun 2012 23:27:58 -0700 Subject: [PATCH 13/24] New parser works. TCAS untested. Extra info in print. Fixed ground_track printing error. Surface reports suppressed due to possible CPR bug. Not all code paths tested. --- python/cpr.py | 53 ++++++++++++++++---------------------- python/modes_flightgear.py | 4 +-- python/modes_parse.py | 8 +++--- python/modes_print.py | 9 +++---- python/modes_sbs1.py | 45 +++++++++++++++----------------- python/modes_sql.py | 36 +++++++++++--------------- 6 files changed, 68 insertions(+), 87 deletions(-) diff --git a/python/cpr.py b/python/cpr.py index 5639d2b..7ee4f00 100755 --- a/python/cpr.py +++ b/python/cpr.py @@ -29,11 +29,10 @@ from modes_exceptions import * latz = 15 def nbits(surface): - return 17 -# if surface == 1: -# return 19 -# else: -# return 17 + if surface == 1: + return 19 + else: + return 17 def nz(ctype): return 4 * latz - ctype @@ -97,18 +96,14 @@ def cpr_resolve_local(my_location, encoded_location, ctype, surface): def cpr_resolve_global(evenpos, oddpos, mostrecent, surface): dlateven = dlat(0, surface) dlatodd = dlat(1, surface) - if surface is True: - scalar = float(2**19) - else: - scalar = float(2**17) evenpos = [float(evenpos[0]), float(evenpos[1])] oddpos = [float(oddpos[0]), float(oddpos[1])] - j = math.floor(((nz(1)*evenpos[0] - nz(0)*oddpos[0])/scalar) + 0.5) #latitude index + j = math.floor(((nz(1)*evenpos[0] - nz(0)*oddpos[0])/2**17) + 0.5) #latitude index - rlateven = dlateven * ((j % nz(0))+evenpos[0]/scalar) - rlatodd = dlatodd * ((j % nz(1))+ oddpos[0]/scalar) + rlateven = dlateven * ((j % nz(0))+evenpos[0]/2**17) + rlatodd = dlatodd * ((j % nz(1))+ oddpos[0]/2**17) #limit to -90, 90 if rlateven > 270.0: @@ -131,14 +126,14 @@ def cpr_resolve_global(evenpos, oddpos, mostrecent, surface): nlthing = nl(rlat) ni = max(nlthing - mostrecent, 1) - m = math.floor(((evenpos[1]*(nlthing-1)-oddpos[1]*(nlthing))/scalar)+0.5) #longitude index + m = math.floor(((evenpos[1]*(nlthing-1)-oddpos[1]*(nlthing))/2**17)+0.5) #longitude index if mostrecent == 0: enclon = evenpos[1] else: enclon = oddpos[1] - rlon = dl * (((ni+m) % ni)+enclon/2**nbits(surface)) + rlon = dl * (((ni+m) % ni)+enclon/2**17) if rlon > 180: rlon = rlon - 360.0 @@ -172,8 +167,6 @@ def range_bearing(loc_a, loc_b): bearing += 360.0 rnge = math.hypot(distance_East,distance_North) - - return [rnge, bearing] class cpr_decoder: @@ -203,32 +196,30 @@ class cpr_decoder: #okay, let's traverse the lists and weed out those entries that are older than 15 minutes, as they're unlikely to be useful. self.weed_poslists() - - if surface==1: - validrange = 45 - else: - validrange = 180 if icao24 in self.lkplist: #do emitter-centered local decoding [decoded_lat, decoded_lon] = cpr_resolve_local(self.lkplist[icao24][0:2], [encoded_lat, encoded_lon], cpr_format, surface) self.lkplist[icao24] = [decoded_lat, decoded_lon, time.time()] #update the local position for next time - elif ((icao24 in self.evenlist) and (icao24 in self.oddlist) and abs(self.evenlist[icao24][2] - self.oddlist[icao24][2]) < 10): + elif (icao24 in self.evenlist) \ + and (icao24 in self.oddlist) \ + and (abs(self.evenlist[icao24][2] - self.oddlist[icao24][2]) < 10) \ + and (surface == 0): newer = (self.oddlist[icao24][2] - self.evenlist[icao24][2]) > 0 #figure out which report is newer - [decoded_lat, decoded_lon] = cpr_resolve_global(self.evenlist[icao24][0:2], self.oddlist[icao24][0:2], newer, surface) #do a global decode + [decoded_lat, decoded_lon] = cpr_resolve_global(self.evenlist[icao24][0:2], self.oddlist[icao24][0:2], newer, surface) #do a global decode self.lkplist[icao24] = [decoded_lat, decoded_lon, time.time()] - else: - raise CPRNoPositionError #so we really can't guarantee that local decoding will work unless you are POSITIVE that you can't hear more than 180nm out. #this will USUALLY work, but you can't guarantee it! -# elif self.my_location is not None: #if we have a location, use it -# [local_lat, local_lon] = cpr_resolve_local(self.my_location, [encoded_lat, encoded_lon], cpr_format, surface) #try local decoding -# [rnge, bearing] = range_bearing(self.my_location, [local_lat, local_lon]) -# if rnge < validrange: #if the local decoding can be guaranteed valid -# self.lkplist[icao24] = [local_lat, local_lon, time.time()] #update the local position for next time -# [decoded_lat, decoded_lon] = [local_lat, local_lon] + #elif surface == 1 and self.my_location is not None: + # [local_lat, local_lon] = cpr_resolve_local(self.my_location, [encoded_lat, encoded_lon], cpr_format, surface) #try local decoding + # [rnge, bearing] = range_bearing(self.my_location, [local_lat, local_lon]) + # if rnge < validrange: #if the local decoding can be guaranteed valid + # self.lkplist[icao24] = [local_lat, local_lon, time.time()] #update the local position for next time + # [decoded_lat, decoded_lon] = [local_lat, local_lon] + else: + raise CPRNoPositionError if self.my_location is not None: [rnge, bearing] = range_bearing(self.my_location, [decoded_lat, decoded_lon]) diff --git a/python/modes_flightgear.py b/python/modes_flightgear.py index 9feb6b6..54a0551 100755 --- a/python/modes_flightgear.py +++ b/python/modes_flightgear.py @@ -41,8 +41,8 @@ class modes_flightgear(modes_parse.modes_parse): self.callsigns[icao24] = [ident, actype] elif 5 <= subtype <= 8: #BDS0,6 pos - [altitude, decoded_lat, decoded_lon, rnge, bearing] = self.parseBDS06(data) - self.positions[icao24] = [decoded_lat, decoded_lon, altitude] + [ground_track, decoded_lat, decoded_lon, rnge, bearing] = self.parseBDS06(data) + self.positions[icao24] = [decoded_lat, decoded_lon, 0] self.update(icao24) elif 9 <= subtype <= 18: #BDS0,5 pos diff --git a/python/modes_parse.py b/python/modes_parse.py index 16046ea..3fe6e25 100644 --- a/python/modes_parse.py +++ b/python/modes_parse.py @@ -130,7 +130,7 @@ class me_reply(data_field): elif ftc == 28: return 0x61 else: - return NoHandlerError + return NoHandlerError(ftc) def get_numbits(self): return 56 @@ -163,7 +163,7 @@ class mb_reply(data_field): bds1 = self.get_bits(33,4) bds2 = self.get_bits(37,4) if bds1 not in (0,1,2,3) or bds2 not in (0,): - raise NoHandlerError + raise NoHandlerError(bds1) return int(bds1) def get_numbits(self): @@ -263,9 +263,9 @@ class modes_parse: encoded_lon = data["me"]["lon"] encoded_lat = data["me"]["lat"] cpr_format = data["me"]["cpr"] - altitude = decode_alt(data["me"]["alt"], False) + ground_track = data["me"]["gtk"] * 360. / 128 [decoded_lat, decoded_lon, rnge, bearing] = self.cpr.decode(icao24, encoded_lat, encoded_lon, cpr_format, 1) - return [altitude, decoded_lat, decoded_lon, rnge, bearing] + return [ground_track, decoded_lat, decoded_lon, rnge, bearing] def parseBDS09_0(self, data): #0: ["sub", "dew", "vew", "dns", "vns", "str", "tr", "svr", "vr"], diff --git a/python/modes_print.py b/python/modes_print.py index d620d47..c755eef 100644 --- a/python/modes_print.py +++ b/python/modes_print.py @@ -60,7 +60,7 @@ class modes_output_print(modes_parse.modes_parse): output += "No handler for message type %i from %x (but it's in modes_parse)" % (msgtype, ecc) print output except NoHandlerError as e: - output += "No handler for message type %i from %x" % (msgtype, ecc) + output += "No handler for message type %s from %x" % (e.msgtype, ecc) print output except MetricAltError: pass @@ -71,7 +71,6 @@ class modes_output_print(modes_parse.modes_parse): [vs, cc, sl, ri, altitude] = self.parse0(shortdata) retstr = "Type 0 (short A-A surveillance) from %x at %ift" % (ecc, altitude) - # the ri values below 9 are used for other things. might want to print those someday. if ri == 0: retstr += " (No TCAS)" elif ri == 2: @@ -129,7 +128,7 @@ class modes_output_print(modes_parse.modes_parse): def print11(self, data, ecc): [icao24, interrogator, ca] = self.parse11(data, ecc) - retstr = "Type 11 (all call reply) from %x in reply to interrogator %i" % (icao24, interrogator) + retstr = "Type 11 (all call reply) from %x in reply to interrogator %i with capability level %i" % (icao24, interrogator, ca+1) return retstr def print17(self, data): @@ -143,8 +142,8 @@ class modes_output_print(modes_parse.modes_parse): retstr = "Type 17 subtype 04 (ident) from %x type %s ident %s" % (icao24, typestring, msg) elif subtype >= 5 and subtype <= 8: - [altitude, decoded_lat, decoded_lon, rnge, bearing] = self.parseBDS06(data) - retstr = "Type 17 subtype 06 (surface report) from %x at (%.6f, %.6f)" % (icao24, decoded_lat, decoded_lon) + [ground_track, decoded_lat, decoded_lon, rnge, bearing] = self.parseBDS06(data) + retstr = "Type 17 subtype 06 (surface report) from %x at (%.6f, %.6f) ground track %i" % (icao24, decoded_lat, decoded_lon, ground_track) if rnge is not None and bearing is not None: retstr += " (%.2f @ %.0f)" % (rnge, bearing) diff --git a/python/modes_sbs1.py b/python/modes_sbs1.py index 0f97d36..ca38407 100644 --- a/python/modes_sbs1.py +++ b/python/modes_sbs1.py @@ -103,25 +103,23 @@ class modes_output_sbs1(modes_parse.modes_parse): def parse(self, message): #assembles a SBS-1-style output string from the received message - [msgtype, shortdata, longdata, parity, ecc, reference, timestamp] = message.split() + [data, ecc, reference, timestamp] = message.split() - shortdata = long(shortdata, 16) - longdata = long(longdata, 16) - parity = long(parity, 16) + data = modes_parse.modes_reply(long(data, 16)) ecc = long(ecc, 16) - msgtype = int(msgtype) + msgtype = data["df"] outmsg = None if msgtype == 0: - outmsg = self.pp0(shortdata, ecc) + outmsg = self.pp0(data, ecc) elif msgtype == 4: - outmsg = self.pp4(shortdata, ecc) + outmsg = self.pp4(data, ecc) elif msgtype == 5: - outmsg = self.pp5(shortdata, ecc) + outmsg = self.pp5(data, ecc) elif msgtype == 11: - outmsg = self.pp11(shortdata, ecc) + outmsg = self.pp11(data, ecc) elif msgtype == 17: - outmsg = self.pp17(shortdata, longdata) + outmsg = self.pp17(data) else: raise NoHandlerError(msgtype) return outmsg @@ -147,7 +145,7 @@ class modes_output_sbs1(modes_parse.modes_parse): def pp5(self, shortdata, ecc): # I'm not sure what to do with the identiifcation shortdata & 0x1FFF [datestr, timestr] = self.current_time() - [fs, dr, um] = self.parse5(shortdata) + [fs, dr, um, ident] = self.parse5(shortdata) aircraft_id = self.get_aircraft_id(ecc) retstr = "MSG,6,0,%i,%06X,%i,%s,%s,%s,%s,,,,,,,,," % (aircraft_id, ecc, aircraft_id+100, datestr, timestr, datestr, timestr) return retstr + self.decode_fs(fs) + "\n" @@ -158,10 +156,10 @@ class modes_output_sbs1(modes_parse.modes_parse): aircraft_id = self.get_aircraft_id(icao24) return "MSG,8,0,%i,%06X,%i,%s,%s,%s,%s,,,,,,,,,,,,\n" % (aircraft_id, icao24, aircraft_id+100, datestr, timestr, datestr, timestr) - def pp17(self, shortdata, longdata): - icao24 = shortdata & 0xFFFFFF + def pp17(self, data): + icao24 = data["aa"] aircraft_id = self.get_aircraft_id(icao24) - subtype = (longdata >> 51) & 0x1F + subtype = data["me"]["ftc"] retstr = None #we'll get better timestamps later, hopefully with actual VRT time @@ -170,23 +168,22 @@ class modes_output_sbs1(modes_parse.modes_parse): if subtype >= 1 and subtype <= 4: # Aircraft Identification - (msg, typestring) = self.parseBDS08(shortdata, longdata) + (msg, typestring) = self.parseBDS08(data) retstr = "MSG,1,0,%i,%06X,%i,%s,%s,%s,%s,%s,,,,,,,,,,,\n" % (aircraft_id, icao24, aircraft_id+100, datestr, timestr, datestr, timestr, msg) elif subtype >= 5 and subtype <= 8: # Surface position measurement - [altitude, decoded_lat, decoded_lon, rnge, bearing] = self.parseBDS06(shortdata, longdata) + [ground_track, decoded_lat, decoded_lon, rnge, bearing] = self.parseBDS06(data) + altitude = 0 if decoded_lat is None: #no unambiguously valid position available retstr = None else: retstr = "MSG,2,0,%i,%06X,%i,%s,%s,%s,%s,,%i,,,%.5f,%.5f,,,,0,0,0\n" % (aircraft_id, icao24, aircraft_id+100, datestr, timestr, datestr, timestr, altitude, decoded_lat, decoded_lon) - elif subtype >= 9 and subtype <= 18 and subtype != 15: + elif subtype >= 9 and subtype <= 18: # Airborne position measurements # WRONG (rnge, bearing), is this still true? - # i'm eliminating type 15 records because they don't appear to be - # valid position reports. - [altitude, decoded_lat, decoded_lon, rnge, bearing] = self.parseBDS05(shortdata, longdata) + [altitude, decoded_lat, decoded_lon, rnge, bearing] = self.parseBDS05(data) if decoded_lat is None: #no unambiguously valid position available retstr = None else: @@ -195,13 +192,13 @@ class modes_output_sbs1(modes_parse.modes_parse): elif subtype == 19: # Airborne velocity measurements # WRONG (heading, vert_spd), Is this still true? - subsubtype = (longdata >> 48) & 0x07 + subsubtype = data["me"]["bds09"]["sub"] if subsubtype == 0: - [velocity, heading, vert_spd] = self.parseBDS09_0(shortdata, longdata) + [velocity, heading, vert_spd] = self.parseBDS09_0(data) retstr = "MSG,4,0,%i,%06X,%i,%s,%s,%s,%s,,,%.1f,%.1f,,,%i,,,,,\n" % (aircraft_id, icao24, aircraft_id+100, datestr, timestr, datestr, timestr, velocity, heading, vert_spd) - elif subsubtype == 1: - [velocity, heading, vert_spd] = self.parseBDS09_1(shortdata, longdata) + elif 1 <= subsubtype <= 2: + [velocity, heading, vert_spd] = self.parseBDS09_1(data) retstr = "MSG,4,0,%i,%06X,%i,%s,%s,%s,%s,,,%.1f,%.1f,,,%i,,,,,\n" % (aircraft_id, icao24, aircraft_id+100, datestr, timestr, datestr, timestr, velocity, heading, vert_spd) return retstr diff --git a/python/modes_sql.py b/python/modes_sql.py index 88d2033..2c42899 100644 --- a/python/modes_sql.py +++ b/python/modes_sql.py @@ -72,59 +72,53 @@ class modes_output_sql(modes_parse.modes_parse): def make_insert_query(self, message): #assembles a SQL query tailored to our database #this version ignores anything that isn't Type 17 for now, because we just don't care - [msgtype, shortdata, longdata, parity, ecc, reference, timestamp] = message.split() + [data, ecc, reference, timestamp] = message.split() - shortdata = long(shortdata, 16) - longdata = long(longdata, 16) - parity = long(parity, 16) + data = modes_parse.modes_reply(long(data, 16)) ecc = long(ecc, 16) # reference = float(reference) - msgtype = int(msgtype) query = None - + msgtype = data["df"] if msgtype == 17: - query = self.sql17(shortdata, longdata) + query = self.sql17(data) return query - def sql17(self, shortdata, longdata): - icao24 = shortdata & 0xFFFFFF - subtype = (longdata >> 51) & 0x1F + def sql17(self, data): + icao24 = data["aa"] + subtype = data["me"]["ftc"] retstr = None if subtype == 4: - (msg, typename) = self.parseBDS08(shortdata, longdata) + (msg, typename) = self.parseBDS08(data) retstr = "INSERT OR REPLACE INTO ident (icao, ident) VALUES (" + "%i" % icao24 + ", '" + msg + "')" elif subtype >= 5 and subtype <= 8: - [altitude, decoded_lat, decoded_lon, rnge, bearing] = self.parseBDS06(shortdata, longdata) + [ground_track, decoded_lat, decoded_lon, rnge, bearing] = self.parseBDS06(data) + altitude = 0 if decoded_lat is None: #no unambiguously valid position available retstr = None else: retstr = "INSERT INTO positions (icao, seen, alt, lat, lon) VALUES (" + "%i" % icao24 + ", datetime('now'), " + str(altitude) + ", " + "%.6f" % decoded_lat + ", " + "%.6f" % decoded_lon + ")" elif subtype >= 9 and subtype <= 18 and subtype != 15: #i'm eliminating type 15 records because they don't appear to be valid position reports. - [altitude, decoded_lat, decoded_lon, rnge, bearing] = self.parseBDS05(shortdata, longdata) + [altitude, decoded_lat, decoded_lon, rnge, bearing] = self.parseBDS05(data) if decoded_lat is None: #no unambiguously valid position available retstr = None else: retstr = "INSERT INTO positions (icao, seen, alt, lat, lon) VALUES (" + "%i" % icao24 + ", datetime('now'), " + str(altitude) + ", " + "%.6f" % decoded_lat + ", " + "%.6f" % decoded_lon + ")" elif subtype == 19: - subsubtype = (longdata >> 48) & 0x07 + subsubtype = data["me"]["bds09"]["sub"] if subsubtype == 0: - [velocity, heading, vert_spd] = self.parseBDS09_0(shortdata, longdata) + [velocity, heading, vert_spd] = self.parseBDS09_0(data) retstr = "INSERT INTO vectors (icao, seen, speed, heading, vertical) VALUES (" + "%i" % icao24 + ", datetime('now'), " + "%.0f" % velocity + ", " + "%.0f" % heading + ", " + "%.0f" % vert_spd + ")"; - elif subsubtype == 1: - [velocity, heading, vert_spd] = self.parseBDS09_1(shortdata, longdata) + elif 1 <= subsubtype <= 2: + [velocity, heading, vert_spd] = self.parseBDS09_1(data) retstr = "INSERT INTO vectors (icao, seen, speed, heading, vertical) VALUES (" + "%i" % icao24 + ", datetime('now'), " + "%.0f" % velocity + ", " + "%.0f" % heading + ", " + "%.0f" % vert_spd + ")"; return retstr - - - - From 57f7bc84fc31e9590a0175a1d370e425e328d6d7 Mon Sep 17 00:00:00 2001 From: Nick Foster Date: Fri, 29 Jun 2012 09:32:58 -0700 Subject: [PATCH 14/24] Fixing TCAS reports. Types 20 and 21 still bomb because apparently MB field is optional -- the only type 20s I see are short packets. --- apps/uhd_modes.py | 12 ++++++------ python/modes_parse.py | 4 ++-- python/modes_print.py | 13 ++++++++++++- 3 files changed, 20 insertions(+), 9 deletions(-) diff --git a/apps/uhd_modes.py b/apps/uhd_modes.py index 9e83310..c883614 100755 --- a/apps/uhd_modes.py +++ b/apps/uhd_modes.py @@ -182,6 +182,12 @@ if __name__ == '__main__': outputs = [] #registry of plugin output functions updates = [] #registry of plugin update functions + if options.raw is True: + rawport = air_modes.modes_raw_server() + outputs.append(rawport.output) + outputs.append(printraw) + updates.append(rawport.add_pending_conns) + if options.kml is not None: #we spawn a thread to run every 30 seconds (or whatever) to generate KML kmlgen = air_modes.modes_kml(options.kml, my_position) #create a KML generating thread @@ -195,12 +201,6 @@ if __name__ == '__main__': if options.no_print is not True: outputs.append(air_modes.modes_output_print(my_position).parse) - if options.raw is True: - rawport = air_modes.modes_raw_server() - outputs.append(rawport.output) - outputs.append(printraw) - updates.append(rawport.add_pending_conns) - if options.multiplayer is not None: [fghost, fgport] = options.multiplayer.split(':') fgout = air_modes.modes_flightgear(my_position, fghost, int(fgport)) diff --git a/python/modes_parse.py b/python/modes_parse.py index 3fe6e25..d88d0fc 100644 --- a/python/modes_parse.py +++ b/python/modes_parse.py @@ -182,8 +182,8 @@ class modes_reply(data_field): 11: {"df": (1,5), "ca": (6,3), "aa": (9,24), "pi": (33,24)}, 16: {"df": (1,5), "vs": (6,1), "sl": (9,3), "ri": (14,4), "ac": (20,13), "mv": (33,56), "ap": (88,24)}, 17: {"df": (1,5), "ca": (6,3), "aa": (9,24), "me": (33,56, me_reply), "pi": (88,24)}, - 20: {"df": (1,5), "fs": (6,3), "dr": (9,24), "um": (14,6), "ac": (20,13), "mb": (33,56, mb_reply), "ap": (88,24)}, - 21: {"df": (1,5), "fs": (6,3), "dr": (9,24), "um": (14,6), "id": (20,13), "mb": (33,56, mb_reply), "ap": (88,24)}, + 20: {"df": (1,5), "fs": (6,3), "dr": (9,5), "um": (14,6), "ac": (20,13), "mb": (33,56, mb_reply), "ap": (88,24)}, + 21: {"df": (1,5), "fs": (6,3), "dr": (9,5), "um": (14,6), "id": (20,13), "mb": (33,56, mb_reply), "ap": (88,24)}, 24: {"df": (1,5), "ke": (6,1), "nd": (7,4), "md": (11,80), "ap": (88,24)} } diff --git a/python/modes_print.py b/python/modes_print.py index c755eef..5aac968 100644 --- a/python/modes_print.py +++ b/python/modes_print.py @@ -56,6 +56,8 @@ class modes_output_print(modes_parse.modes_parse): output += self.print11(data, ecc) elif msgtype == 17: output += self.print17(data) + elif msgtype == 20 or msgtype == 21: + output += self.print20(data, ecc) else: output += "No handler for message type %i from %x (but it's in modes_parse)" % (msgtype, ecc) print output @@ -175,7 +177,11 @@ class modes_output_print(modes_parse.modes_parse): return retstr def print20(self, data, ecc): - [fs, dr, um, alt] = self.parse4(data) + msgtype = data["df"] + if(msgtype == 20): + [fs, dr, um, alt] = self.parse4(data) + else: + [fs, dr, um, ident] = self.parse5(data) mb_fields = data["mb"].get_fields() bds1 = mb_fields["bds1"] bds2 = mb_fields["bds2"] @@ -207,5 +213,10 @@ class modes_output_print(modes_parse.modes_parse): retstr += " (resolved)" else: retstr = "No handler for BDS1 == %i from %x" % (bds1, ecc) + + if(msgtype == 20): + retstr += " at %ift" % altitude + else: + retstr += " ident %x" % ident return retstr From 19436349dce722180e58ef51dd9e984673673e0d Mon Sep 17 00:00:00 2001 From: Nick Foster Date: Sat, 30 Jun 2012 14:31:20 -0700 Subject: [PATCH 15/24] Parser is even bitchin'er. Now flattens the dict on init recursively so fields are automatically populated. --- python/modes_flightgear.py | 4 +- python/modes_parse.py | 100 ++++++++++++++++++------------------- python/modes_print.py | 13 +++-- python/modes_sbs1.py | 4 +- python/modes_sql.py | 4 +- 5 files changed, 62 insertions(+), 63 deletions(-) diff --git a/python/modes_flightgear.py b/python/modes_flightgear.py index 54a0551..a1d51bc 100755 --- a/python/modes_flightgear.py +++ b/python/modes_flightgear.py @@ -34,7 +34,7 @@ class modes_flightgear(modes_parse.modes_parse): msgtype = data["df"] if msgtype == 17: #ADS-B report icao24 = data["aa"] - subtype = data["me"]["sub"] + subtype = data["sub"] if subtype == 4: #ident packet (ident, actype) = self.parseBDS08(data) #select model based on actype @@ -51,7 +51,7 @@ class modes_flightgear(modes_parse.modes_parse): self.update(icao24) elif subtype == 19: #velocity - subsubtype = data["me"]["bds09"]["sub"] + subsubtype = data["sub"] if subsubtype == 0: [velocity, heading, vert_spd, turnrate] = self.parseBDS09_0(data) elif 1 <= subsubtype <= 2: diff --git a/python/modes_parse.py b/python/modes_parse.py index d88d0fc..ba60f39 100644 --- a/python/modes_parse.py +++ b/python/modes_parse.py @@ -30,6 +30,7 @@ from modes_exceptions import * class data_field: def __init__(self, data): self.data = data + self.fields = self.parse() types = { } offset = 1 #field offset applied to all fields. used for offsetting @@ -39,16 +40,24 @@ class data_field: def __getitem__(self, fieldname): mytype = self.get_type() if mytype in self.types: - if fieldname in self.types[mytype]: #verify it exists in this packet type - return self.get_bits(*self.types[mytype][fieldname]) + if fieldname in self.fields: #verify it exists in this packet type + return self.fields[fieldname] else: raise FieldNotInPacket(fieldname) else: raise NoHandlerError(mytype) #grab all the fields in the packet as a dict - def get_fields(self): - return {field: self[field] for field in self.types[self.get_type()]} + #done once on init so you don't have to iterate down every time you grab a field + def parse(self): + fields = {} + for field in self.types[self.get_type()]: + bits = self.types[self.get_type()][field] + if len(bits) == 3: + obj = bits[2](self.get_bits(bits[0], bits[1])) + fields.update(obj.parse()) + fields.update({field: self.get_bits(bits[0], bits[1])}) + return fields def get_type(self): raise NotImplementedError @@ -65,12 +74,7 @@ class data_field: bits = (self.data \ >> (self.get_numbits() - startbit - num + self.offset)) \ & ((1 << num) - 1) - if len(args) == 3: - #construct a subtype from the bit field - return args[2](bits) - else: - #return the bits as a number - return bits + return bits class bds09_reply(data_field): offset = 6 @@ -99,7 +103,7 @@ class bds09_reply(data_field): def get_numbits(self): return 51 - + #type 17 extended squitter data class me_reply(data_field): #types in this format are listed by BDS register @@ -202,12 +206,10 @@ class modes_parse: self.cpr = cpr.cpr_decoder(self.my_location) def parse0(self, data): - fields = data.get_fields() altitude = decode_alt(data["ac"], True) - return [fields["vs"], fields["cc"], fields["sl"], fields["ri"], altitude] + return [data["vs"], data["cc"], data["sl"], data["ri"], altitude] def parse4(self, data): - fields = data.get_fields() altitude = decode_alt(data["ac"], True) return [data["fs"], data["dr"], data["um"], altitude] @@ -224,11 +226,11 @@ class modes_parse: ["NO INFO", "LIGHT", "SMALL", "LARGE", "LARGE HIGH VORTEX", "HEAVY", "HIGH PERFORMANCE", "ROTORCRAFT"]] def parseBDS08(self, data): - catstring = self.categories[data["me"]["ftc"]-1][data["me"]["cat"]] + catstring = self.categories[data["ftc"]-1][data["cat"]] msg = "" for i in range(0, 8): - msg += self.charmap( data["me"]["ident"] >> (42-6*i) & 0x3F) + msg += self.charmap(data["ident"] >> (42-6*i) & 0x3F) return (msg, catstring) def charmap(self, d): @@ -246,10 +248,10 @@ class modes_parse: def parseBDS05(self, data): icao24 = data["aa"] - encoded_lon = data["me"]["lon"] - encoded_lat = data["me"]["lat"] - cpr_format = data["me"]["cpr"] - altitude = decode_alt(data["me"]["alt"], False) + encoded_lon = data["lon"] + encoded_lat = data["lat"] + cpr_format = data["cpr"] + altitude = decode_alt(data["alt"], False) [decoded_lat, decoded_lon, rnge, bearing] = self.cpr.decode(icao24, encoded_lat, encoded_lon, cpr_format, 0) @@ -260,28 +262,27 @@ class modes_parse: def parseBDS06(self, data): icao24 = data["aa"] - encoded_lon = data["me"]["lon"] - encoded_lat = data["me"]["lat"] - cpr_format = data["me"]["cpr"] - ground_track = data["me"]["gtk"] * 360. / 128 + encoded_lon = data["lon"] + encoded_lat = data["lat"] + cpr_format = data["cpr"] + ground_track = data["gtk"] * 360. / 128 [decoded_lat, decoded_lon, rnge, bearing] = self.cpr.decode(icao24, encoded_lat, encoded_lon, cpr_format, 1) return [ground_track, decoded_lat, decoded_lon, rnge, bearing] def parseBDS09_0(self, data): #0: ["sub", "dew", "vew", "dns", "vns", "str", "tr", "svr", "vr"], - bdsobj = data["me"]["bds09"] - vert_spd = bdsobj["vr"] * 32 - ud = bool(bdsobj["dvr"]) + vert_spd = data["vr"] * 32 + ud = bool(data["dvr"]) if ud: vert_spd = 0 - vert_spd - turn_rate = bdsobj["tr"] * 15/62 - rl = bdsobj["str"] + turn_rate = data["tr"] * 15/62 + rl = data["str"] if rl: turn_rate = 0 - turn_rate - ns_vel = bdsobj["vns"] - 1 - ns = bool(bdsobj["dns"]) - ew_vel = bdsobj["vew"] - 1 - ew = bool(bdsobj["dew"]) + ns_vel = data["vns"] - 1 + ns = bool(data["dns"]) + ew_vel = data["vew"] - 1 + ew = bool(data["dew"]) velocity = math.hypot(ns_vel, ew_vel) if ew: @@ -296,21 +297,20 @@ class modes_parse: def parseBDS09_1(self, data): #1: ["sub", "icf", "ifr", "nuc", "dew", "vew", "dns", "vns", "vrsrc", "dvr", "vr", "dhd", "hd"], - bdsobj = data["me"]["bds09"] - alt_geo_diff = bdsobj["hd"] * 25 - above_below = bool(bdsobj["dhd"]) + alt_geo_diff = data["hd"] * 25 + above_below = bool(data["dhd"]) if above_below: alt_geo_diff = 0 - alt_geo_diff; - vert_spd = float(bdsobj["vr"] - 1) * 64 - ud = bool(bdsobj["dvr"]) + vert_spd = float(data["vr"] - 1) * 64 + ud = bool(data["dvr"]) if ud: vert_spd = 0 - vert_spd - vert_src = bool(bdsobj["vrsrc"]) - ns_vel = float(bdsobj["vns"]) - ns = bool(bdsobj["dns"]) - ew_vel = float(bdsobj["vew"]) - ew = bool(bdsobj["dew"]) - subtype = bdsobj["sub"] + vert_src = bool(data["vrsrc"]) + ns_vel = float(data["vns"]) + ns = bool(data["dns"]) + ew_vel = float(data["vew"]) + ew = bool(data["dew"]) + subtype = data["sub"] if subtype == 0x02: ns_vel <<= 2 ew_vel <<= 2 @@ -333,12 +333,12 @@ class modes_parse: def parseBDS62(self, data): eps_strings = ["NO EMERGENCY", "GENERAL EMERGENCY", "LIFEGUARD/MEDICAL", "FUEL EMERGENCY", "NO COMMUNICATIONS", "UNLAWFUL INTERFERENCE", "RESERVED", "RESERVED"] - return eps_strings[data["me"]["eps"]] + return eps_strings[data["eps"]] def parseMB_id(self, data): #bds1 == 2, bds2 == 0 msg = "" for i in range(0, 8): - msg += self.charmap( data["mb"]["ais"] >> (42-6*i) & 0x3F) + msg += self.charmap( data["ais"] >> (42-6*i) & 0x3F) return (msg) def parseMB_TCAS_resolutions(self, data): @@ -348,7 +348,7 @@ class modes_parse: 49: "DON'T CLIMB >1000FPM", 50: "DON'T CLIMB >2000FPM", 51: "TURN LEFT", 52: "TURN RIGHT", 53: "DON'T TURN LEFT", 54: "DON'T TURN RIGHT"} rac_bits = {55: "DON'T DESCEND", 56: "DON'T CLIMB", 57: "DON'T TURN LEFT", 58: "DON'T TURN RIGHT"} - ara = data["mb"]["ara"] + ara = data["ara"] #check to see which bits are set resolutions = "" for bit, name in ara_bits: @@ -368,9 +368,9 @@ class modes_parse: #3: {"bds1": (33,4), "bds2": (37,4), "ara": (41,14), "rac": (55,4), "rat": (59,1), # "mte": (60,1), "tti": (61,2), "tida": (63,13), "tidr": (76,7), "tidb": (83,6)} (resolutions, complements) = self.parseMB_TCAS_resolutions(data) - return (resolutions, complements, data["mb"]["rat"], data["mb"]["mte"], data["mb"]["tcas"]["tid"]) + return (resolutions, complements, data["rat"], data["mte"], data["tid"]) def parseMB_TCAS_threatloc(self, data): #bds1==3, bds2==0, TTI==2 (resolutions, complements) = self.parseMB_TCAS_resolutions(data) - threat_alt = decode_alt(data["mb"]["tcas"]["tida"], True) - return (resolutions, complements, data["mb"]["rat"], data["mb"]["mte"], threat_alt, data["mb"]["tcas"]["tidr"], data["mb"]["tcas"]["tidb"]) + threat_alt = decode_alt(data["tida"], True) + return (resolutions, complements, data["rat"], data["mte"], threat_alt, data["tidr"], data["tidb"]) diff --git a/python/modes_print.py b/python/modes_print.py index 5aac968..a194c51 100644 --- a/python/modes_print.py +++ b/python/modes_print.py @@ -135,7 +135,7 @@ class modes_output_print(modes_parse.modes_parse): def print17(self, data): icao24 = data["aa"] - subtype = data["me"]["ftc"] + subtype = data["ftc"] retstr = None @@ -157,7 +157,7 @@ class modes_output_print(modes_parse.modes_parse): retstr += " at " + str(altitude) + "ft" elif subtype == 19: - subsubtype = data["me"]["bds09"]["sub"] + subsubtype = data["sub"] if subsubtype == 0: [velocity, heading, vert_spd] = self.parseBDS09_0(data) retstr = "Type 17 subtype 09-0 (track report) from %x with velocity %.0fkt heading %.0f VS %.0f" % (icao24, velocity, heading, vert_spd) @@ -182,9 +182,8 @@ class modes_output_print(modes_parse.modes_parse): [fs, dr, um, alt] = self.parse4(data) else: [fs, dr, um, ident] = self.parse5(data) - mb_fields = data["mb"].get_fields() - bds1 = mb_fields["bds1"] - bds2 = mb_fields["bds2"] + bds1 = data["bds1"] + bds2 = data["bds2"] if bds2 != 0: retstr = "No handler for BDS2 == %i from %x" % (bds2, ecc) @@ -193,12 +192,12 @@ class modes_output_print(modes_parse.modes_parse): retstr = "No handler for BDS1 == 0 from %x" % ecc elif bds1 == 1: retstr = "Type 20 link capability report from %x: ACS: 0x%x, BCS: 0x%x, ECS: 0x%x, continues %i" \ - % (ecc, mb_fields["acs"], mb_fields["bcs"], mb_fields["ecs"], mb_fields["cfs"]) + % (ecc, data["acs"], data["bcs"], data["ecs"], data["cfs"]) elif bds1 == 2: retstr = "Type 20 identification from %x with text %s" % (ecc, self.parseMB_id(data)) elif bds2 == 3: retstr = "TCAS report from %x: " % ecc - tti = mb_fields["tcas"].get_type() + tti = data["tti"] if tti == 1: (resolutions, complements, rat, mte, threat_id) = self.parseMB_TCAS_threatid(data) retstr += "threat ID: %x advised: %s complement: %s" % (threat_id, resolutions, complements) diff --git a/python/modes_sbs1.py b/python/modes_sbs1.py index ca38407..28e1430 100644 --- a/python/modes_sbs1.py +++ b/python/modes_sbs1.py @@ -159,7 +159,7 @@ class modes_output_sbs1(modes_parse.modes_parse): def pp17(self, data): icao24 = data["aa"] aircraft_id = self.get_aircraft_id(icao24) - subtype = data["me"]["ftc"] + subtype = data["ftc"] retstr = None #we'll get better timestamps later, hopefully with actual VRT time @@ -192,7 +192,7 @@ class modes_output_sbs1(modes_parse.modes_parse): elif subtype == 19: # Airborne velocity measurements # WRONG (heading, vert_spd), Is this still true? - subsubtype = data["me"]["bds09"]["sub"] + subsubtype = data["sub"] if subsubtype == 0: [velocity, heading, vert_spd] = self.parseBDS09_0(data) retstr = "MSG,4,0,%i,%06X,%i,%s,%s,%s,%s,,,%.1f,%.1f,,,%i,,,,,\n" % (aircraft_id, icao24, aircraft_id+100, datestr, timestr, datestr, timestr, velocity, heading, vert_spd) diff --git a/python/modes_sql.py b/python/modes_sql.py index 2c42899..944bcbf 100644 --- a/python/modes_sql.py +++ b/python/modes_sql.py @@ -88,7 +88,7 @@ class modes_output_sql(modes_parse.modes_parse): def sql17(self, data): icao24 = data["aa"] - subtype = data["me"]["ftc"] + subtype = data["ftc"] retstr = None @@ -112,7 +112,7 @@ class modes_output_sql(modes_parse.modes_parse): retstr = "INSERT INTO positions (icao, seen, alt, lat, lon) VALUES (" + "%i" % icao24 + ", datetime('now'), " + str(altitude) + ", " + "%.6f" % decoded_lat + ", " + "%.6f" % decoded_lon + ")" elif subtype == 19: - subsubtype = data["me"]["bds09"]["sub"] + subsubtype = data["sub"] if subsubtype == 0: [velocity, heading, vert_spd] = self.parseBDS09_0(data) retstr = "INSERT INTO vectors (icao, seen, speed, heading, vertical) VALUES (" + "%i" % icao24 + ", datetime('now'), " + "%.0f" % velocity + ", " + "%.0f" % heading + ", " + "%.0f" % vert_spd + ")"; From fe6aa0c6de4b53cb8797f7dcccd888fb7f55b9ce Mon Sep 17 00:00:00 2001 From: Nick Foster Date: Mon, 2 Jul 2012 07:17:21 -0700 Subject: [PATCH 16/24] Fix error handling for data construct on init. --- apps/uhd_modes.py | 5 ++++- python/modes_parse.py | 16 ++++++++++------ python/modes_print.py | 2 +- 3 files changed, 15 insertions(+), 8 deletions(-) diff --git a/apps/uhd_modes.py b/apps/uhd_modes.py index c883614..6f271c7 100755 --- a/apps/uhd_modes.py +++ b/apps/uhd_modes.py @@ -223,7 +223,10 @@ if __name__ == '__main__': msg = queue.delete_head() #blocking read for out in outputs: - out(msg.to_string()) + try: + out(msg.to_string()) + except ADSBError: + pass elif runner.done: raise KeyboardInterrupt diff --git a/python/modes_parse.py b/python/modes_parse.py index ba60f39..f628af2 100644 --- a/python/modes_parse.py +++ b/python/modes_parse.py @@ -51,12 +51,16 @@ class data_field: #done once on init so you don't have to iterate down every time you grab a field def parse(self): fields = {} - for field in self.types[self.get_type()]: - bits = self.types[self.get_type()][field] - if len(bits) == 3: - obj = bits[2](self.get_bits(bits[0], bits[1])) - fields.update(obj.parse()) - fields.update({field: self.get_bits(bits[0], bits[1])}) + mytype = self.get_type() + if mytype in self.types: + for field in self.types[mytype]: + bits = self.types[self.get_type()][field] + if len(bits) == 3: + obj = bits[2](self.get_bits(bits[0], bits[1])) + fields.update(obj.parse()) + fields.update({field: self.get_bits(bits[0], bits[1])}) + else: + raise NoHandlerError(mytype) return fields def get_type(self): diff --git a/python/modes_print.py b/python/modes_print.py index a194c51..4af3b9b 100644 --- a/python/modes_print.py +++ b/python/modes_print.py @@ -32,7 +32,6 @@ class modes_output_print(modes_parse.modes_parse): def parse(self, message): [data, ecc, reference, timestamp] = message.split() - data = modes_parse.modes_reply(long(data, 16)) ecc = long(ecc, 16) reference = float(reference) timestamp = float(timestamp) @@ -45,6 +44,7 @@ class modes_output_print(modes_parse.modes_parse): output = "(%.0f %.10f) " % (refdb, timestamp); try: + data = modes_parse.modes_reply(long(data, 16)) msgtype = data["df"] if msgtype == 0: output += self.print0(data, ecc) From 4750d2004462b4907177913d27f1eb7a37e10d17 Mon Sep 17 00:00:00 2001 From: Nick Foster Date: Mon, 16 Jul 2012 09:16:48 -0700 Subject: [PATCH 17/24] Parser returns objects for subfields instead of flat data. Lets you use get_type to resolve BDS registers, etc. Printer takes advantage of this. Now printing BDS reg instead of "subtype". --- python/modes_flightgear.py | 23 ++++++++++++----------- python/modes_parse.py | 6 ++++-- python/modes_print.py | 34 ++++++++++++++++------------------ python/modes_sbs1.py | 21 +++++++++------------ python/modes_sql.py | 23 ++++++++++------------- 5 files changed, 51 insertions(+), 56 deletions(-) diff --git a/python/modes_flightgear.py b/python/modes_flightgear.py index a1d51bc..2a50e95 100755 --- a/python/modes_flightgear.py +++ b/python/modes_flightgear.py @@ -34,31 +34,32 @@ class modes_flightgear(modes_parse.modes_parse): msgtype = data["df"] if msgtype == 17: #ADS-B report icao24 = data["aa"] - subtype = data["sub"] - if subtype == 4: #ident packet + bdsreg = data["me"].get_type() + if bdsreg == 0x08: #ident packet (ident, actype) = self.parseBDS08(data) #select model based on actype self.callsigns[icao24] = [ident, actype] - elif 5 <= subtype <= 8: #BDS0,6 pos + elif bdsreg == 0x06: #BDS0,6 pos [ground_track, decoded_lat, decoded_lon, rnge, bearing] = self.parseBDS06(data) self.positions[icao24] = [decoded_lat, decoded_lon, 0] self.update(icao24) - elif 9 <= subtype <= 18: #BDS0,5 pos + elif bdsreg == 0x05: #BDS0,5 pos [altitude, decoded_lat, decoded_lon, rnge, bearing] = self.parseBDS05(data) self.positions[icao24] = [decoded_lat, decoded_lon, altitude] self.update(icao24) - elif subtype == 19: #velocity - subsubtype = data["sub"] - if subsubtype == 0: - [velocity, heading, vert_spd, turnrate] = self.parseBDS09_0(data) - elif 1 <= subsubtype <= 2: - [velocity, heading, vert_spd] = self.parseBDS09_1(data) - turnrate = 0 + elif bdsreg == 0x09: #velocity + subtype = data["bds09"].get_type() + if subtype == 0: + [velocity, heading, vert_spd, turnrate] = self.parseBDS09_0(data) + elif subtype == 1: + [velocity, heading, vert_spd] = self.parseBDS09_1(data) + turnrate = 0 else: return + self.velocities[icao24] = [velocity, heading, vert_spd, turnrate] except ADSBError: diff --git a/python/modes_parse.py b/python/modes_parse.py index f628af2..f1c3b61 100644 --- a/python/modes_parse.py +++ b/python/modes_parse.py @@ -58,7 +58,9 @@ class data_field: if len(bits) == 3: obj = bits[2](self.get_bits(bits[0], bits[1])) fields.update(obj.parse()) - fields.update({field: self.get_bits(bits[0], bits[1])}) + fields.update({field: obj}) + else: + fields.update({field: self.get_bits(bits[0], bits[1])}) else: raise NoHandlerError(mytype) return fields @@ -131,7 +133,7 @@ class me_reply(data_field): return 0x08 elif 5 <= ftc <= 8: return 0x06 - elif 9 <= ftc <= 18: + elif 9 <= ftc <= 18 and ftc != 15: #FTC 15 does not appear to be valid return 0x05 elif ftc == 19: return 0x09 diff --git a/python/modes_print.py b/python/modes_print.py index 4af3b9b..b8064e0 100644 --- a/python/modes_print.py +++ b/python/modes_print.py @@ -135,41 +135,39 @@ class modes_output_print(modes_parse.modes_parse): def print17(self, data): icao24 = data["aa"] - subtype = data["ftc"] + bdsreg = data["me"].get_type() retstr = None - if 1 <= subtype <= 4: + if bdsreg == 0x08: (msg, typestring) = self.parseBDS08(data) - retstr = "Type 17 subtype 04 (ident) from %x type %s ident %s" % (icao24, typestring, msg) + retstr = "Type 17 BDS0,8 (ident) from %x type %s ident %s" % (icao24, typestring, msg) - elif subtype >= 5 and subtype <= 8: + elif bdsreg == 0x06: [ground_track, decoded_lat, decoded_lon, rnge, bearing] = self.parseBDS06(data) - retstr = "Type 17 subtype 06 (surface report) from %x at (%.6f, %.6f) ground track %i" % (icao24, decoded_lat, decoded_lon, ground_track) + retstr = "Type 17 BDS0,6 (surface report) from %x at (%.6f, %.6f) ground track %i" % (icao24, decoded_lat, decoded_lon, ground_track) if rnge is not None and bearing is not None: retstr += " (%.2f @ %.0f)" % (rnge, bearing) - elif subtype >= 9 and subtype <= 18: + elif bdsreg == 0x05: [altitude, decoded_lat, decoded_lon, rnge, bearing] = self.parseBDS05(data) - retstr = "Type 17 subtype 05 (position report) from %x at (%.6f, %.6f)" % (icao24, decoded_lat, decoded_lon) + retstr = "Type 17 BDS0,5 (position report) from %x at (%.6f, %.6f)" % (icao24, decoded_lat, decoded_lon) if rnge is not None and bearing is not None: retstr += " (" + "%.2f" % rnge + " @ " + "%.0f" % bearing + ")" retstr += " at " + str(altitude) + "ft" - elif subtype == 19: - subsubtype = data["sub"] - if subsubtype == 0: - [velocity, heading, vert_spd] = self.parseBDS09_0(data) - retstr = "Type 17 subtype 09-0 (track report) from %x with velocity %.0fkt heading %.0f VS %.0f" % (icao24, velocity, heading, vert_spd) - elif 1 <= subsubtype <= 2: - [velocity, heading, vert_spd] = self.parseBDS09_1(data) - retstr = "Type 17 subtype 09-%i (track report) from %x with velocity %.0fkt heading %.0f VS %.0f" % (subsubtype, icao24, velocity, heading, vert_spd) + elif bdsreg == 0x09: + subtype = data["bds09"].get_type() + if subtype == 0 or subtype == 1: + parser = self.parseBDS09_0 if subtype == 0 else self.parseBDS09_1 + [velocity, heading, vert_spd] = parser(data) + retstr = "Type 17 BDS0,9-%i (track report) from %x with velocity %.0fkt heading %.0f VS %.0f" % (subtype, icao24, velocity, heading, vert_spd) else: - retstr = "Type 17 subtype 09-%i from %x not implemented" % (subsubtype, icao24) + retstr = "Type 17 BDS0,9-%i from %x not implemented" % (subtype, icao24) - elif subtype == 28: + elif bdsreg == 0x62: emerg_str = self.parseBDS62(data) - retstr = "Type 17 subtype 28 (emergency) from %x type %s" % (icao24, emerg_str) + retstr = "Type 17 BDS6,2 (emergency) from %x type %s" % (icao24, emerg_str) else: retstr = "Type 17 subtype %i from %x not implemented" % (subtype, icao24) diff --git a/python/modes_sbs1.py b/python/modes_sbs1.py index 28e1430..3f16b13 100644 --- a/python/modes_sbs1.py +++ b/python/modes_sbs1.py @@ -159,19 +159,19 @@ class modes_output_sbs1(modes_parse.modes_parse): def pp17(self, data): icao24 = data["aa"] aircraft_id = self.get_aircraft_id(icao24) - subtype = data["ftc"] + bdsreg = data["me"].get_type() retstr = None #we'll get better timestamps later, hopefully with actual VRT time #in them [datestr, timestr] = self.current_time() - if subtype >= 1 and subtype <= 4: + if bdsreg == 0x08: # Aircraft Identification (msg, typestring) = self.parseBDS08(data) retstr = "MSG,1,0,%i,%06X,%i,%s,%s,%s,%s,%s,,,,,,,,,,,\n" % (aircraft_id, icao24, aircraft_id+100, datestr, timestr, datestr, timestr, msg) - elif subtype >= 5 and subtype <= 8: + elif bdsreg == 0x06: # Surface position measurement [ground_track, decoded_lat, decoded_lon, rnge, bearing] = self.parseBDS06(data) altitude = 0 @@ -180,7 +180,7 @@ class modes_output_sbs1(modes_parse.modes_parse): else: retstr = "MSG,2,0,%i,%06X,%i,%s,%s,%s,%s,,%i,,,%.5f,%.5f,,,,0,0,0\n" % (aircraft_id, icao24, aircraft_id+100, datestr, timestr, datestr, timestr, altitude, decoded_lat, decoded_lon) - elif subtype >= 9 and subtype <= 18: + elif bdsreg == 0x05: # Airborne position measurements # WRONG (rnge, bearing), is this still true? [altitude, decoded_lat, decoded_lon, rnge, bearing] = self.parseBDS05(data) @@ -189,16 +189,13 @@ class modes_output_sbs1(modes_parse.modes_parse): else: retstr = "MSG,3,0,%i,%06X,%i,%s,%s,%s,%s,,%i,,,%.5f,%.5f,,,,0,0,0\n" % (aircraft_id, icao24, aircraft_id+100, datestr, timestr, datestr, timestr, altitude, decoded_lat, decoded_lon) - elif subtype == 19: + elif bdsreg == 0x09: # Airborne velocity measurements # WRONG (heading, vert_spd), Is this still true? - subsubtype = data["sub"] - if subsubtype == 0: - [velocity, heading, vert_spd] = self.parseBDS09_0(data) - retstr = "MSG,4,0,%i,%06X,%i,%s,%s,%s,%s,,,%.1f,%.1f,,,%i,,,,,\n" % (aircraft_id, icao24, aircraft_id+100, datestr, timestr, datestr, timestr, velocity, heading, vert_spd) - - elif 1 <= subsubtype <= 2: - [velocity, heading, vert_spd] = self.parseBDS09_1(data) + subtype = data["bds09"].get_type() + if subtype == 0 or subtype == 1: + parser = self.parseBDS09_0 if subtype == 0 else self.parseBDS09_1 + [velocity, heading, vert_spd] = parser(data) retstr = "MSG,4,0,%i,%06X,%i,%s,%s,%s,%s,,,%.1f,%.1f,,,%i,,,,,\n" % (aircraft_id, icao24, aircraft_id+100, datestr, timestr, datestr, timestr, velocity, heading, vert_spd) return retstr diff --git a/python/modes_sql.py b/python/modes_sql.py index 944bcbf..9cf272b 100644 --- a/python/modes_sql.py +++ b/python/modes_sql.py @@ -88,15 +88,15 @@ class modes_output_sql(modes_parse.modes_parse): def sql17(self, data): icao24 = data["aa"] - subtype = data["ftc"] + bdsreg = data["me"].get_type() retstr = None - if subtype == 4: + if bdsreg == 0x08: (msg, typename) = self.parseBDS08(data) retstr = "INSERT OR REPLACE INTO ident (icao, ident) VALUES (" + "%i" % icao24 + ", '" + msg + "')" - elif subtype >= 5 and subtype <= 8: + elif bdsreg == 0x06: [ground_track, decoded_lat, decoded_lon, rnge, bearing] = self.parseBDS06(data) altitude = 0 if decoded_lat is None: #no unambiguously valid position available @@ -104,21 +104,18 @@ class modes_output_sql(modes_parse.modes_parse): else: retstr = "INSERT INTO positions (icao, seen, alt, lat, lon) VALUES (" + "%i" % icao24 + ", datetime('now'), " + str(altitude) + ", " + "%.6f" % decoded_lat + ", " + "%.6f" % decoded_lon + ")" - elif subtype >= 9 and subtype <= 18 and subtype != 15: #i'm eliminating type 15 records because they don't appear to be valid position reports. + elif bdsreg == 0x05: [altitude, decoded_lat, decoded_lon, rnge, bearing] = self.parseBDS05(data) if decoded_lat is None: #no unambiguously valid position available retstr = None else: retstr = "INSERT INTO positions (icao, seen, alt, lat, lon) VALUES (" + "%i" % icao24 + ", datetime('now'), " + str(altitude) + ", " + "%.6f" % decoded_lat + ", " + "%.6f" % decoded_lon + ")" - elif subtype == 19: - subsubtype = data["sub"] - if subsubtype == 0: - [velocity, heading, vert_spd] = self.parseBDS09_0(data) - retstr = "INSERT INTO vectors (icao, seen, speed, heading, vertical) VALUES (" + "%i" % icao24 + ", datetime('now'), " + "%.0f" % velocity + ", " + "%.0f" % heading + ", " + "%.0f" % vert_spd + ")"; - - elif 1 <= subsubtype <= 2: - [velocity, heading, vert_spd] = self.parseBDS09_1(data) - retstr = "INSERT INTO vectors (icao, seen, speed, heading, vertical) VALUES (" + "%i" % icao24 + ", datetime('now'), " + "%.0f" % velocity + ", " + "%.0f" % heading + ", " + "%.0f" % vert_spd + ")"; + elif bdsreg == 0x09: + subtype = data["bds09"].get_type() + if subtype == 0 or subtype == 1: + parser = self.parseBDS09_0 if subtype == 0 else self.parseBDS09_1 + [velocity, heading, vert_spd] = parser(data) + retstr = "INSERT INTO vectors (icao, seen, speed, heading, vertical) VALUES (" + "%i" % icao24 + ", datetime('now'), " + "%.0f" % velocity + ", " + "%.0f" % heading + ", " + "%.0f" % vert_spd + ")" return retstr From c2129e0eab7af92cbc2e6177bcc25742412ed8ef Mon Sep 17 00:00:00 2001 From: Nick Foster Date: Mon, 16 Jul 2012 09:36:15 -0700 Subject: [PATCH 18/24] Default RTL gain of 35 and parser catches negative shifts generated by invalid packets. --- apps/uhd_modes.py | 3 ++- python/modes_parse.py | 9 ++++++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/apps/uhd_modes.py b/apps/uhd_modes.py index 6f271c7..2650bf1 100755 --- a/apps/uhd_modes.py +++ b/apps/uhd_modes.py @@ -29,6 +29,7 @@ from string import split, join import air_modes import gnuradio.gr.gr_threading as _threading import csv +from air_modes.modes_exceptions import * class top_block_runner(_threading.Thread): def __init__(self, tb): @@ -87,7 +88,7 @@ class adsb_rx_block (gr.top_block): self.u.set_gain_mode(0) #manual gain mode if options.gain is None: - options.gain = 49 + options.gain = 34 self.u.set_gain(options.gain) print "Gain is %i" % self.u.get_gain() diff --git a/python/modes_parse.py b/python/modes_parse.py index f1c3b61..3af9486 100644 --- a/python/modes_parse.py +++ b/python/modes_parse.py @@ -77,9 +77,16 @@ class data_field: def get_bits(self, *args): startbit = args[0] num = args[1] - bits = (self.data \ + try: + bits = (self.data \ >> (self.get_numbits() - startbit - num + self.offset)) \ & ((1 << num) - 1) + #the exception handler catches instances when you try to shift more than + #the number of bits. this can happen when a garbage packet gets through + #which reports itself as a short packet but of type long. + #TODO: should find more productive way to throw this out + except ValueError: + bits = 0 return bits class bds09_reply(data_field): From c866dc9d3178d7582966d3ce78be68afb742fed2 Mon Sep 17 00:00:00 2001 From: Nick Foster Date: Mon, 16 Jul 2012 14:35:43 -0700 Subject: [PATCH 19/24] How did this go so long without being fixed? Range and bearing were asymptotic due to -- get this -- degree/radian confusion. --- python/cpr.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/python/cpr.py b/python/cpr.py index 7ee4f00..66ed474 100755 --- a/python/cpr.py +++ b/python/cpr.py @@ -153,10 +153,10 @@ def range_bearing(loc_a, loc_b): delta_lat = b_lat - a_lat delta_lon = b_lon - a_lon - avg_lat = (a_lat + b_lat) / 2.0 + avg_lat = ((a_lat + b_lat) / 2.0) * math.pi / 180 R1 = earth_radius_mi*(1.0-esquared)/pow((1.0-esquared*pow(math.sin(avg_lat),2)),1.5) - + R2 = earth_radius_mi/math.sqrt(1.0-esquared*pow(math.sin(avg_lat),2)) distance_North = R1*delta_lat From 3c506c44ab0671bf2c04e3c3bead8e9c5ff5b828 Mon Sep 17 00:00:00 2001 From: Nick Foster Date: Fri, 20 Jul 2012 21:10:14 -0700 Subject: [PATCH 20/24] Don't print alt/ident on type 20 packets yet. --- python/modes_print.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/python/modes_print.py b/python/modes_print.py index b8064e0..c3fa342 100644 --- a/python/modes_print.py +++ b/python/modes_print.py @@ -211,9 +211,9 @@ class modes_output_print(modes_parse.modes_parse): else: retstr = "No handler for BDS1 == %i from %x" % (bds1, ecc) - if(msgtype == 20): - retstr += " at %ift" % altitude - else: - retstr += " ident %x" % ident +# if(msgtype == 20): +# retstr += " at %ift" % altitude +# else: +# retstr += " ident %x" % ident return retstr From 3c73be4d64802695074082f3ea387e7334426add Mon Sep 17 00:00:00 2001 From: Nick Foster Date: Fri, 20 Jul 2012 22:05:03 -0700 Subject: [PATCH 21/24] Modifications to TCAS printing and to error handling --- python/modes_parse.py | 3 ++- python/modes_print.py | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/python/modes_parse.py b/python/modes_parse.py index 3af9486..c381f58 100644 --- a/python/modes_parse.py +++ b/python/modes_parse.py @@ -77,6 +77,7 @@ class data_field: def get_bits(self, *args): startbit = args[0] num = args[1] + bits = 0 try: bits = (self.data \ >> (self.get_numbits() - startbit - num + self.offset)) \ @@ -86,7 +87,7 @@ class data_field: #which reports itself as a short packet but of type long. #TODO: should find more productive way to throw this out except ValueError: - bits = 0 + print "Short packet received for long packet type: %x" % self.data return bits class bds09_reply(data_field): diff --git a/python/modes_print.py b/python/modes_print.py index c3fa342..f10c83b 100644 --- a/python/modes_print.py +++ b/python/modes_print.py @@ -194,7 +194,7 @@ class modes_output_print(modes_parse.modes_parse): elif bds1 == 2: retstr = "Type 20 identification from %x with text %s" % (ecc, self.parseMB_id(data)) elif bds2 == 3: - retstr = "TCAS report from %x: " % ecc + retstr = "Type 20 TCAS report from %x: " % ecc tti = data["tti"] if tti == 1: (resolutions, complements, rat, mte, threat_id) = self.parseMB_TCAS_threatid(data) From e11600ccb435dba0a8dd404b3a6507fc13a1d1c8 Mon Sep 17 00:00:00 2001 From: Nick Foster Date: Fri, 20 Jul 2012 22:08:13 -0700 Subject: [PATCH 22/24] Take out annoying print, you can put it in for debug --- python/modes_parse.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/python/modes_parse.py b/python/modes_parse.py index c381f58..8e672b4 100644 --- a/python/modes_parse.py +++ b/python/modes_parse.py @@ -87,7 +87,8 @@ class data_field: #which reports itself as a short packet but of type long. #TODO: should find more productive way to throw this out except ValueError: - print "Short packet received for long packet type: %x" % self.data + pass + #print "Short packet received for long packet type: %x" % self.data return bits class bds09_reply(data_field): From ac8a01646b2c0a53f399593bce82c0b99b2527d1 Mon Sep 17 00:00:00 2001 From: Nick Foster Date: Sat, 21 Jul 2012 11:10:15 -0700 Subject: [PATCH 23/24] Add BDS0,9-3 (air course report) to parser/printer. --- python/modes_parse.py | 15 +++++++++++++++ python/modes_print.py | 14 +++++++++++--- python/modes_sbs1.py | 2 +- 3 files changed, 27 insertions(+), 4 deletions(-) diff --git a/python/modes_parse.py b/python/modes_parse.py index 8e672b4..f41c83e 100644 --- a/python/modes_parse.py +++ b/python/modes_parse.py @@ -345,6 +345,21 @@ class modes_parse: return [velocity, heading, vert_spd] + def parseBDS09_3(self, data): + #3: {"sub", "icf", "ifr", "nuc", "mhs", "hdg", "ast", "spd", "vrsrc", + # "dvr", "vr", "dhd", "hd"} + mag_hdg = data["mhs"] * 360. / 1024 + vel_src = "TAS" if data["ast"] == 1 else "IAS" + vel = data["spd"] + if data["sub"] == 4: + vel *= 4 + vert_spd = float(data["vr"] - 1) * 64 + if data["dvr"] == 1: + vert_spd = 0 - vert_spd + geo_diff = float(data["hd"] - 1) * 25 + return [mag_hdg, vel_src, vel, vert_spd, geo_diff] + + def parseBDS62(self, data): eps_strings = ["NO EMERGENCY", "GENERAL EMERGENCY", "LIFEGUARD/MEDICAL", "FUEL EMERGENCY", "NO COMMUNICATIONS", "UNLAWFUL INTERFERENCE", "RESERVED", "RESERVED"] diff --git a/python/modes_print.py b/python/modes_print.py index f10c83b..e90d4f0 100644 --- a/python/modes_print.py +++ b/python/modes_print.py @@ -158,10 +158,18 @@ class modes_output_print(modes_parse.modes_parse): elif bdsreg == 0x09: subtype = data["bds09"].get_type() - if subtype == 0 or subtype == 1: - parser = self.parseBDS09_0 if subtype == 0 else self.parseBDS09_1 - [velocity, heading, vert_spd] = parser(data) + if subtype == 0: + [velocity, heading, vert_spd, turnrate] = self.parseBDS09_0(data) + retstr = "Type 17 BDS0,9-%i (track report) from %x with velocity %.0fkt heading %.0f VS %.0f turn rate %.0f" \ + % (subtype, icao24, velocity, heading, vert_spd, turnrate) + elif subtype == 1: + [velocity, heading, vert_spd] = self.parseBDS09_1(data) retstr = "Type 17 BDS0,9-%i (track report) from %x with velocity %.0fkt heading %.0f VS %.0f" % (subtype, icao24, velocity, heading, vert_spd) + elif subtype == 3: + [mag_hdg, vel_src, vel, vert_spd, geo_diff] = self.parseBDS09_3(data) + retstr = "Type 17 BDS0,9-%i (air course report) from %x with %s %.0fkt magnetic heading %.0f VS %.0f geo. diff. from baro. alt. %.0fft" \ + % (subtype, icao24, vel_src, vel, mag_hdg, vert_spd, geo_diff) + else: retstr = "Type 17 BDS0,9-%i from %x not implemented" % (subtype, icao24) diff --git a/python/modes_sbs1.py b/python/modes_sbs1.py index 452d008..20d6457 100644 --- a/python/modes_sbs1.py +++ b/python/modes_sbs1.py @@ -146,7 +146,7 @@ class modes_output_sbs1(modes_parse.modes_parse): def pp5(self, shortdata, ecc): # I'm not sure what to do with the identiifcation shortdata & 0x1FFF [datestr, timestr] = self.current_time() - [fs, dr, um] = self.parse5(shortdata) + [fs, dr, um, ident] = self.parse5(shortdata) aircraft_id = self.get_aircraft_id(ecc) retstr = "MSG,6,0,%i,%06X,%i,%s,%s,%s,%s,,,,,,,,," % (aircraft_id, ecc, aircraft_id+100, datestr, timestr, datestr, timestr) return retstr + self.decode_fs(fs) + "\n" From 9b539f89428d689000476291aebe9434f881557a Mon Sep 17 00:00:00 2001 From: Nick Foster Date: Sat, 21 Jul 2012 11:12:19 -0700 Subject: [PATCH 24/24] Fix for BDS0,9-0/1 --- python/modes_sql.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/python/modes_sql.py b/python/modes_sql.py index 9cf272b..88e8117 100644 --- a/python/modes_sql.py +++ b/python/modes_sql.py @@ -113,9 +113,12 @@ class modes_output_sql(modes_parse.modes_parse): elif bdsreg == 0x09: subtype = data["bds09"].get_type() - if subtype == 0 or subtype == 1: - parser = self.parseBDS09_0 if subtype == 0 else self.parseBDS09_1 - [velocity, heading, vert_spd] = parser(data) + if subtype == 0: + [velocity, heading, vert_spd, turnrate] = self.parseBDS09_0(data) retstr = "INSERT INTO vectors (icao, seen, speed, heading, vertical) VALUES (" + "%i" % icao24 + ", datetime('now'), " + "%.0f" % velocity + ", " + "%.0f" % heading + ", " + "%.0f" % vert_spd + ")" - + elif subtype == 1: + [velocity, heading, vert_spd] = self.parseBDS09_1(data) + retstr = "INSERT INTO vectors (icao, seen, speed, heading, vertical) VALUES (" + "%i" % icao24 + ", datetime('now'), " + "%.0f" % velocity + ", " + "%.0f" % heading + ", " + "%.0f" % vert_spd + ")" + else: + retstr = None return retstr