diff --git a/apps/uhd_modes.py b/apps/uhd_modes.py index 3258452..288274e 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() @@ -184,6 +185,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 @@ -197,12 +204,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)) @@ -225,7 +226,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/lib/air_modes_slicer.cc b/lib/air_modes_slicer.cc index b29346c..1426d56 100644 --- a/lib/air_modes_slicer.cc +++ b/lib/air_modes_slicer.cc @@ -166,7 +166,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;} @@ -178,36 +178,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++) { - 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 << " "; - 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/cpr.py b/python/cpr.py index 5a0e0b9..66ed474 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]) @@ -274,12 +265,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 +293,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) 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_flightgear.py b/python/modes_flightgear.py index 7d967cf..2a50e95 100755 --- a/python/modes_flightgear.py +++ b/python/modes_flightgear.py @@ -27,39 +27,39 @@ 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 - if subtype == 4: #ident packet - (ident, actype) = self.parseBDS08(shortdata, longdata) + icao24 = data["aa"] + 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 - [altitude, decoded_lat, decoded_lon, rnge, bearing] = self.parseBDS06(shortdata, longdata) + 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 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 9 <= subtype <= 18: #BDS0,5 pos - [altitude, decoded_lat, decoded_lon, rnge, bearing] = self.parseBDS05(shortdata, longdata) - 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 + 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: @@ -73,7 +73,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], @@ -134,14 +134,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 a58da45..f41c83e 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,88 +26,226 @@ import cpr import math from modes_exceptions import * +#this implements a packet class which can retrieve its own fields. +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 + #subtypes to reconcile with the spec. Really just for readability. + + #get a particular field from the data + def __getitem__(self, fieldname): + mytype = self.get_type() + if mytype in self.types: + 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 + #done once on init so you don't have to iterate down every time you grab a field + def parse(self): + fields = {} + 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: obj}) + else: + fields.update({field: self.get_bits(bits[0], bits[1])}) + else: + raise NoHandlerError(mytype) + return fields + + 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, *args): + startbit = args[0] + num = args[1] + bits = 0 + 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: + pass + #print "Short packet received for long packet type: %x" % self.data + return bits + +class bds09_reply(data_field): + offset = 6 + 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)}, + #BDS0,9 subtypes 1-2 (differ only in velocity encoding) + 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": (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(6,3) + 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): + #types in this format are listed by BDS register + #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": (1,5), "eps": (9,3)} + } + + #maps ftc to BDS register + def get_type(self): + ftc = self.get_bits(1,5) + if 1 <= ftc <= 4: + return 0x08 + elif 5 <= ftc <= 8: + return 0x06 + elif 9 <= ftc <= 18 and ftc != 15: #FTC 15 does not appear to be valid + return 0x05 + elif ftc == 19: + return 0x09 + elif ftc == 28: + return 0x61 + else: + return NoHandlerError(ftc) + + 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(bds1) + 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): + 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,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)} + } + + def is_long(self): + return self.data > (1 << 56) + + def get_numbits(self): + return 112 if self.is_long() else 56 + + def get_type(self): + return self.get_bits(1,5) + class modes_parse: def __init__(self, mypos): 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): + altitude = decode_alt(data["ac"], True) + return [data["vs"], data["cc"], data["sl"], data["ri"], altitude] - altitude = decode_alt(shortdata & 0x1FFF, True) #bit 13 is set for type 0 + def parse4(self, data): + 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 - - return [fs, dr, um] - - 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", "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"]] - 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["ftc"]-1][data["cat"]] msg = "" for i in range(0, 8): - msg += self.charmap( longdata >> (42-6*i) & 0x3F) + msg += self.charmap(data["ident"] >> (42-6*i) & 0x3F) return (msg, catstring) def charmap(self, d): @@ -123,16 +260,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["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) @@ -140,36 +274,31 @@ 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["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] - 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"], + vert_spd = data["vr"] * 32 + ud = bool(data["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 = data["tr"] * 15/62 + rl = data["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 = 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: ew_vel = 0 - ew_vel @@ -181,32 +310,26 @@ 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", "dew", "vew", "dns", "vns", "vrsrc", "dvr", "vr", "dhd", "hd"], + alt_geo_diff = data["hd"] * 25 + above_below = bool(data["dhd"]) 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(data["vr"] - 1) * 64 + ud = bool(data["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(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 *= 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 @@ -222,3 +345,62 @@ 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"] + 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["ais"] >> (42-6*i) & 0x3F) + return (msg) + + 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["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) + + #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["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["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 fd60f8c..e90d4f0 100644 --- a/python/modes_print.py +++ b/python/modes_print.py @@ -30,16 +30,12 @@ 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() + ecc = long(ecc, 16) reference = float(reference) timestamp = float(timestamp) - msgtype = int(msgtype) - #TODO this is suspect if reference == 0.0: refdb = -150.0 @@ -48,19 +44,25 @@ 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(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) + 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 except NoHandlerError as e: - output += "No handler for message type " + str(e.msgtype) + " from %x" % ecc + output += "No handler for message type %s from %x" % (e.msgtype, ecc) print output except MetricAltError: pass @@ -70,15 +72,22 @@ 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" - # the ri values below 9 are used for other things. might want to print those someday. - if ri == 9: - retstr = retstr + " (speed <75kt)" + retstr = "Type 0 (short A-A surveillance) from %x at %ift" % (ecc, altitude) + 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,83 +95,133 @@ 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] = 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 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, shortdata, ecc): - [icao24, interrogator, ca] = self.parse11(shortdata, ecc) - - retstr = "Type 11 (all call reply) from " + "%x" % icao24 + " in reply to interrogator " + str(interrogator) + 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 with capability level %i" % (icao24, interrogator, ca+1) return retstr - def print17(self, shortdata, longdata): - icao24 = shortdata & 0xFFFFFF - subtype = (longdata >> 51) & 0x1F; + def print17(self, data): + icao24 = data["aa"] + bdsreg = data["me"].get_type() retstr = None - if 1 <= subtype <= 4: - (msg, typestring) = self.parseBDS08(shortdata, longdata) - retstr = "Type 17 subtype 04 (ident) from " + "%x" % icao24 + " of type " + typestring + " with ident " + msg + if bdsreg == 0x08: + (msg, typestring) = self.parseBDS08(data) + retstr = "Type 17 BDS0,8 (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(shortdata, longdata) - 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 bdsreg == 0x06: + [ground_track, decoded_lat, decoded_lon, rnge, bearing] = self.parseBDS06(data) + 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: - [altitude, decoded_lat, decoded_lon, rnge, bearing] = self.parseBDS05(shortdata, longdata) - 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" - - 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 - - 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 bdsreg == 0x05: + [altitude, decoded_lat, decoded_lon, rnge, bearing] = self.parseBDS05(data) + 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 bdsreg == 0x09: + subtype = data["bds09"].get_type() + 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 subtype 09-%i" % (subsubtype) + " not implemented" + retstr = "Type 17 BDS0,9-%i from %x not implemented" % (subtype, icao24) + + elif bdsreg == 0x62: + emerg_str = self.parseBDS62(data) + retstr = "Type 17 BDS6,2 (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 + + def print20(self, data, ecc): + msgtype = data["df"] + if(msgtype == 20): + [fs, dr, um, alt] = self.parse4(data) + else: + [fs, dr, um, ident] = self.parse5(data) + bds1 = data["bds1"] + bds2 = data["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, 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 = "Type 20 TCAS report from %x: " % ecc + 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) + 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) + +# if(msgtype == 20): +# retstr += " at %ift" % altitude +# else: +# retstr += " ident %x" % ident + + return retstr diff --git a/python/modes_sbs1.py b/python/modes_sbs1.py index bce8a0f..20d6457 100644 --- a/python/modes_sbs1.py +++ b/python/modes_sbs1.py @@ -104,25 +104,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 @@ -148,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" @@ -159,50 +157,46 @@ 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 + 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(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: + elif bdsreg == 0x06: # 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 bdsreg == 0x05: # 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: 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 = (longdata >> 48) & 0x07 - if subsubtype == 0: - [velocity, heading, vert_spd] = self.parseBDS09_0(shortdata, longdata) - 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) + 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 ca9dbb6..54385d2 100644 --- a/python/modes_sql.py +++ b/python/modes_sql.py @@ -73,59 +73,54 @@ 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"] + bdsreg = data["me"].get_type() retstr = None - if subtype == 4: - (msg, typename) = self.parseBDS08(shortdata, longdata) + 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: - [altitude, decoded_lat, decoded_lon, rnge, bearing] = self.parseBDS06(shortdata, longdata) + 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 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) + 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 = (longdata >> 48) & 0x07 - if subsubtype == 0: - [velocity, heading, vert_spd, turnrate] = self.parseBDS09_0(shortdata, longdata) - 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) - 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: + [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 - - - -