diff --git a/python/CMakeLists.txt b/python/CMakeLists.txt index 0056be0..cbc76da 100644 --- a/python/CMakeLists.txt +++ b/python/CMakeLists.txt @@ -34,6 +34,7 @@ GR_PYTHON_INSTALL( altitude.py cpr.py mlat.py + modes_exceptions.py modes_flightgear.py modes_kml.py modes_parse.py diff --git a/python/__init__.py b/python/__init__.py index 979049b..d292494 100644 --- a/python/__init__.py +++ b/python/__init__.py @@ -56,6 +56,7 @@ from modes_sql import modes_output_sql from modes_sbs1 import modes_output_sbs1 from modes_kml import modes_kml from modes_raw_server import modes_raw_server +from modes_exceptions import * #this is try/excepted in case the user doesn't have numpy installed try: from modes_flightgear import modes_flightgear diff --git a/python/altitude.py b/python/altitude.py index 3d55d3f..bf79943 100755 --- a/python/altitude.py +++ b/python/altitude.py @@ -23,6 +23,8 @@ # For reference into the methodology used to decode altitude, # see RTCA DO-181D p.42 +from modes_exceptions import * + def decode_alt(alt, bit13): mbit = alt & 0x0040 qbit = alt & 0x0010 @@ -30,7 +32,7 @@ def decode_alt(alt, bit13): if mbit and bit13: #TBD: bits 20-25, 27-31 encode alt in meters #remember that bits are left justified (bit 20 is MSB) - return "METRIC ERROR" + raise MetricAltError if qbit: #a mode S-style reply #bit13 is false for BDS0,5 ADS-B squitters, and is true otherwise @@ -122,12 +124,14 @@ def encode_alt_modes(alt, bit13): return (encalt & 0x0F) | tmp1 | tmp2 | (mbit << 6) | (qbit << 4) if __name__ == "__main__": - for alt in range(-1000, 101400, 25): - dec = decode_alt(encode_alt_modes(alt, False), False) - if dec != alt: - print "Failure at %i with bit13 clear (got %s)" % (alt, dec) - for alt in range(-1000, 101400, 25): - dec = decode_alt(encode_alt_modes(alt, True), True) - if dec != alt: - print "Failure at %i with bit13 set (got %s)" % (alt, dec) - + try: + for alt in range(-1000, 101400, 25): + dec = decode_alt(encode_alt_modes(alt, False), False) + if dec != alt: + print "Failure at %i with bit13 clear (got %s)" % (alt, dec) + for alt in range(-1000, 101400, 25): + dec = decode_alt(encode_alt_modes(alt, True), True) + if dec != alt: + print "Failure at %i with bit13 set (got %s)" % (alt, dec) + except MetricAltError: + print "Failure at %i due to metric alt bit" % alt diff --git a/python/cpr.py b/python/cpr.py index c06cc5c..460c69b 100755 --- a/python/cpr.py +++ b/python/cpr.py @@ -20,9 +20,8 @@ # Boston, MA 02110-1301, USA. # -#from string import split, join -#from math import pi, floor, cos, acos import math, time +from modes_exceptions import * #this implements CPR position decoding and encoding. #the decoder is implemented as a class, cpr_decoder, which keeps state for local decoding. #the encoder is cpr_encode([lat, lon], type (even=0, odd=1), and surface (0 for surface, 1 for airborne)) @@ -121,7 +120,7 @@ def cpr_resolve_global(evenpos, oddpos, mostrecent, surface): #If so, you can't get a globally-resolvable location. if nl(rlateven) != nl(rlatodd): #print "Boundary straddle!" - return (None, None,) + raise CPRBoundaryStraddleError if mostrecent == 0: rlat = rlateven @@ -218,8 +217,9 @@ class cpr_decoder: elif ((icao24 in self.evenlist) and (icao24 in self.oddlist) and abs(self.evenlist[icao24][2] - self.oddlist[icao24][2]) < 10): 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 - if decoded_lat is not None: - self.lkplist[icao24] = [decoded_lat, decoded_lon, time.time()] + 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! @@ -230,7 +230,7 @@ class cpr_decoder: # self.lkplist[icao24] = [local_lat, local_lon, time.time()] #update the local position for next time # [decoded_lat, decoded_lon] = [local_lat, local_lon] - if decoded_lat is not None and self.my_location is not None: + if self.my_location is not None: [rnge, bearing] = range_bearing(self.my_location, [decoded_lat, decoded_lon]) else: rnge = None @@ -283,15 +283,21 @@ if __name__ == '__main__': #perform a global decode icao = random.randint(0, 0xffffff) - evenpos = decoder.decode(icao, evenenclat, evenenclon, False, False) - if evenpos != [None, None, None, None]: + try: + evenpos = decoder.decode(icao, evenenclat, evenenclon, False, False) #print "CPR global decode with only one report: %f %f" % (evenpos[0], evenpos[1]) raise Exception("CPR test failure: global decode with only one report") - (odddeclat, odddeclon, rng, brg) = decoder.decode(icao, oddenclat, oddenclon, True, False) + except CPRNoPositionError: + pass - if odddeclat is None: #boundary straddle, just move on, it's normal + try: + (odddeclat, odddeclon, rng, brg) = decoder.decode(icao, oddenclat, oddenclon, True, False) + except CPRBoundaryStraddleError: bs += 1 continue + except CPRNoPositionError: + raise Exception("CPR test failure: no decode after even/odd inputs") + #print "Lat: %f Lon: %f" % (ac_lat, ac_lon) if abs(odddeclat - ac_lat) > threshold or abs(odddeclon - ac_lon) > threshold: @@ -299,7 +305,10 @@ if __name__ == '__main__': #print "odddeclon: %f ac_lon: %f" % (odddeclon, ac_lon) raise Exception("CPR test failure: global decode error greater than threshold") - (evendeclat, evendeclon) = cpr_resolve_local([ac_lat, ac_lon], [evenenclat, evenenclon], False, False) + try: + (evendeclat, evendeclon) = cpr_resolve_local([ac_lat, ac_lon], [evenenclat, evenenclon], 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: raise Exception("CPR test failure: local decode error greater than threshold") diff --git a/python/modes_exceptions.py b/python/modes_exceptions.py new file mode 100644 index 0000000..6606e5d --- /dev/null +++ b/python/modes_exceptions.py @@ -0,0 +1,42 @@ +# +# Copyright 2012 Nick Foster +# +# This file is part of gr-air-modes +# +# gr-air-modes is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3, or (at your option) +# any later version. +# +# gr-air-modes is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# 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. +# + +class ADSBError(Exception): + pass + +class MetricAltError(ADSBError): + pass + +class ParserError(ADSBError): + pass + +class NoHandlerError(ADSBError): + def __init__(self, msgtype): + self.msgtype = msgtype + +class MlatNonConvergeError(ADSBError): + pass + +class CPRNoPositionError(ADSBError): + pass + +class CPRBoundaryStraddleError(CPRNoPositionError): + pass diff --git a/python/modes_flightgear.py b/python/modes_flightgear.py index 9d2ec2b..7d967cf 100755 --- a/python/modes_flightgear.py +++ b/python/modes_flightgear.py @@ -11,6 +11,7 @@ import string, threading, math, time from air_modes.modes_sql import modes_output_sql from Quaternion import Quat import numpy +from modes_exceptions import * class modes_flightgear(modes_parse.modes_parse): def __init__(self, localpos, hostname, port): @@ -31,36 +32,38 @@ class modes_flightgear(modes_parse.modes_parse): longdata = long(longdata, 16) msgtype = int(msgtype) - if msgtype == 17: #ADS-B report - icao24 = shortdata & 0xFFFFFF - subtype = (longdata >> 51) & 0x1F - if subtype == 4: #ident packet - (ident, actype) = self.parseBDS08(shortdata, longdata) - #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) - if(decoded_lat is not None): + try: + if msgtype == 17: #ADS-B report + icao24 = shortdata & 0xFFFFFF + subtype = (longdata >> 51) & 0x1F + if subtype == 4: #ident packet + (ident, actype) = self.parseBDS08(shortdata, longdata) + #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) 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) - if(decoded_lat is not None): + 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 - else: - return - self.velocities[icao24] = [velocity, heading, vert_spd, turnrate] + 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] + + except ADSBError: + pass def update(self, icao24): #check to see that ICAO24 appears in all three records and that the data looks valid diff --git a/python/modes_parse.py b/python/modes_parse.py index 4350e67..a58da45 100644 --- a/python/modes_parse.py +++ b/python/modes_parse.py @@ -25,6 +25,7 @@ from string import split, join from altitude import decode_alt import cpr import math +from modes_exceptions import * class modes_parse: def __init__(self, mypos): @@ -32,9 +33,6 @@ class modes_parse: self.cpr = cpr.cpr_decoder(self.my_location) def parse0(self, shortdata): -# shortdata = long(shortdata, 16) - #parity = long(parity) - 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 @@ -46,7 +44,6 @@ class modes_parse: return [vs, cc, sl, ri, altitude] def parse4(self, shortdata): -# shortdata = long(shortdata, 16) 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 @@ -59,7 +56,6 @@ class modes_parse: def parse5(self, shortdata): -# shortdata = long(shortdata, 16) 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 @@ -68,7 +64,6 @@ class modes_parse: return [fs, dr, um] def parse11(self, shortdata, ecc): -# shortdata = long(shortdata, 16) interrogator = ecc & 0x0F ca = shortdata >> 13 & 0x3F #capability @@ -114,9 +109,6 @@ class modes_parse: msg = "" for i in range(0, 8): msg += self.charmap( longdata >> (42-6*i) & 0x3F) - - #retstr = "Type 17 subtype 04 (ident) from " + "%x" % icao24 + " with data " + msg - return (msg, catstring) def charmap(self, d): @@ -155,8 +147,6 @@ class modes_parse: encoded_lat = (longdata >> 17) & 0x1FFFF cpr_format = (longdata >> 34) & 1 -# enc_alt = (longdata >> 36) & 0x0FFF - altitude = 0 [decoded_lat, decoded_lon, rnge, bearing] = self.cpr.decode(icao24, encoded_lat, encoded_lon, cpr_format, 1) @@ -189,8 +179,6 @@ class modes_parse: if heading < 0: heading += 360 - #retstr = "Type 17 subtype 09-0 (track report) from " + "%x" % icao24 + " with velocity " + "%.0f" % velocity + "kt heading " + "%.0f" % heading + " VS " + "%.0f" % vert_spd - return [velocity, heading, vert_spd, turn_rate] def parseBDS09_1(self, shortdata, longdata): @@ -232,7 +220,5 @@ class modes_parse: if heading < 0: heading += 360 - #retstr = "Type 17 subtype 09-1 (track report) from " + "%x" % icao24 + " with velocity " + "%.0f" % velocity + "kt heading " + "%.0f" % heading + " VS " + "%.0f" % vert_spd - return [velocity, heading, vert_spd] diff --git a/python/modes_print.py b/python/modes_print.py index 3b1698f..fd60f8c 100644 --- a/python/modes_print.py +++ b/python/modes_print.py @@ -1,5 +1,5 @@ # -# Copyright 2010 Nick Foster +# Copyright 2010, 2012 Nick Foster # # This file is part of gr-air-modes # @@ -19,10 +19,10 @@ # Boston, MA 02110-1301, USA. # - import time, os, sys from string import split, join import modes_parse +from modes_exceptions import * import math class modes_output_print(modes_parse.modes_parse): @@ -39,30 +39,33 @@ class modes_output_print(modes_parse.modes_parse): timestamp = float(timestamp) msgtype = int(msgtype) - - output = None; - - if msgtype == 0: - output = self.print0(shortdata, ecc) - elif msgtype == 4: - output = self.print4(shortdata, ecc) - elif msgtype == 5: - output = self.print5(shortdata, ecc) - elif msgtype == 11: - output = self.print11(shortdata, ecc) - elif msgtype == 17: - output = self.print17(shortdata, longdata) - else: - output = "No handler for message type " + str(msgtype) + " from %x" % ecc + #TODO this is suspect if reference == 0.0: - refdb = -150.0 + refdb = -150.0 else: - refdb = 10.0*math.log10(reference) - - if output is not None: - output = "(%.0f %.10f) " % (refdb, timestamp) + output - print output + refdb = 10.0*math.log10(reference) + output = "(%.0f %.10f) " % (refdb, timestamp); + + try: + if msgtype == 0: + output += self.print0(shortdata, ecc) + elif msgtype == 4: + output += self.print4(shortdata, ecc) + elif msgtype == 5: + output += self.print5(shortdata, ecc) + elif msgtype == 11: + output += self.print11(shortdata, ecc) + elif msgtype == 17: + output += self.print17(shortdata, longdata) + print output + except NoHandlerError as e: + output += "No handler for message type " + str(e.msgtype) + " from %x" % ecc + print output + except MetricAltError: + pass + except CPRNoPositionError: + pass def print0(self, shortdata, ecc): [vs, cc, sl, ri, altitude] = self.parse0(shortdata) diff --git a/python/modes_sbs1.py b/python/modes_sbs1.py index ffa9a5d..9f0aeb6 100644 --- a/python/modes_sbs1.py +++ b/python/modes_sbs1.py @@ -24,6 +24,7 @@ import time, os, sys, socket from string import split, join import modes_parse from datetime import * +from modes_exceptions import * class modes_output_sbs1(modes_parse.modes_parse): def __init__(self, mypos): @@ -61,14 +62,15 @@ class modes_output_sbs1(modes_parse.modes_parse): return self._aircraft_id_count def output(self, msg): - sbs1_msg = self.parse(msg) - if sbs1_msg is not None: + try: + sbs1_msg = self.parse(msg) for conn in self._conns[:]: #iterate over a copy of the list - try: - conn.send(sbs1_msg) - except socket.error: - self._conns.remove(conn) - print "Connections: ", len(self._conns) + conn.send(sbs1_msg) + except socket.error: + self._conns.remove(conn) + print "Connections: ", len(self._conns) + except ADSBError: + pass def add_pending_conns(self): try: diff --git a/python/modes_sql.py b/python/modes_sql.py index bafde26..7e27ef6 100644 --- a/python/modes_sql.py +++ b/python/modes_sql.py @@ -23,6 +23,7 @@ import time, os, sys from string import split, join import modes_parse import sqlite3 +from modes_exceptions import * class modes_output_sql(modes_parse.modes_parse): def __init__(self, mypos, filename): @@ -58,13 +59,15 @@ class modes_output_sql(modes_parse.modes_parse): self.db.close() def output(self, message): - query = self.make_insert_query(message) - if query is not None: + try: + query = self.make_insert_query(message) c = self.db.cursor() c.execute(query) c.close() - self.db.commit() #not sure if i have to do this - + self.db.commit() + except ADSBError: + pass + 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