Progress toward rewriting the parser to be less insane.

This commit is contained in:
Nick Foster 2013-06-18 17:34:11 -07:00
parent 230356bcaa
commit a1e2297134
5 changed files with 260 additions and 288 deletions

View File

@ -20,10 +20,12 @@
#
from gnuradio.eng_option import eng_option
from gnuradio.gr.pubsub import pubsub
from optparse import OptionParser
import time, os, sys, threading
import time, os, sys, threading, math
from string import split, join
import air_modes
from air_modes.types import *
from air_modes.exceptions import *
import zmq
@ -31,26 +33,25 @@ import zmq
def main():
my_position = None
usage = "%prog: [options]"
parser = OptionParser(option_class=eng_option, usage=usage)
air_modes.modes_radio.add_radio_options(parser)
optparser = OptionParser(option_class=eng_option, usage=usage)
air_modes.modes_radio.add_radio_options(optparser)
parser.add_option("-l","--location", type="string", default=None,
optparser.add_option("-l","--location", type="string", default=None,
help="GPS coordinates of receiving station in format xx.xxxxx,xx.xxxxx")
#data source options
parser.add_option("-a","--remote", type="string", default=None,
optparser.add_option("-a","--remote", type="string", default=None,
help="specify additional servers from which to take data in format tcp://x.x.x.x:y,tcp://....")
parser.add_option("-n","--no-print", action="store_true", default=False,
optparser.add_option("-n","--no-print", action="store_true", default=False,
help="disable printing decoded packets to stdout")
#output plugins
parser.add_option("-K","--kml", type="string", default=None,
optparser.add_option("-K","--kml", type="string", default=None,
help="filename for Google Earth KML output")
parser.add_option("-P","--sbs1", action="store_true", default=False,
optparser.add_option("-P","--sbs1", action="store_true", default=False,
help="open an SBS-1-compatible server on port 30003")
parser.add_option("-m","--multiplayer", type="string", default=None,
optparser.add_option("-m","--multiplayer", type="string", default=None,
help="FlightGear server to send aircraft data, in format host:port")
(options, args) = parser.parse_args()
(options, args) = optparser.parse_args()
#construct the radio
context = zmq.Context(1)
@ -60,6 +61,20 @@ def main():
servers += options.remote.split(",")
relay = air_modes.zmq_pubsub_iface(context, subaddr=servers, pubaddr=None)
#ok now relay is gonna get all those tasty strings
#internally we want to distribute parsed data instead, lighten the load
publisher = pubsub()
def send(message):
[data, ecc, reference, timestamp] = message.split()
try:
ret = air_modes.modes_report(air_modes.modes_reply(int(data, 16)), int(ecc, 16), 20.0*math.log10(float(reference)), air_modes.stamp(0, float(timestamp)))
publisher["modes_dl"] = ret
publisher["type%i_dl" % ret.data.get_type()] = ret
except ADSBError:
pass
relay.subscribe("dl_data", send)
if options.location is not None:
my_position = [float(n) for n in options.location.split(",")]
@ -71,7 +86,8 @@ def main():
relay.subscribe("dl_data", sqldb.insert)
if options.no_print is not True:
relay.subscribe("dl_data", air_modes.output_print(my_position).output)
#relay.subscribe("dl_data", air_modes.output_print(my_position).output)
printer = air_modes.output_print(my_position, publisher)
if options.multiplayer is not None:
[fghost, fgport] = options.multiplayer.split(':')

View File

@ -46,6 +46,7 @@ GR_PYTHON_INSTALL(
rx_path.py
sbs1.py
sql.py
types.py
zmq_socket.py
Quaternion.py
DESTINATION ${GR_PYTHON_DIR}/air_modes

View File

@ -53,7 +53,7 @@ from air_modes_swig import *
#
from rx_path import rx_path
from zmq_socket import zmq_pubsub_iface
from parse import parse,modes_reply
from parse import *
from msprint import output_print
from sql import output_sql
from sbs1 import output_sbs1
@ -62,6 +62,8 @@ from raw_server import raw_server
from radio import modes_radio
from exceptions import *
from az_map import *
from types import *
from altitude import *
#this is try/excepted in case the user doesn't have numpy installed
try:
from flightgear import output_flightgear

View File

@ -25,61 +25,35 @@ import air_modes
from air_modes.exceptions import *
import math
class output_print(air_modes.parse):
def __init__(self, mypos):
air_modes.parse.__init__(self, mypos)
#TODO get rid of class and convert to functions
#no need for class here
class output_print:
def __init__(self, mypos, publisher):
#self._cpr = air_modes.cpr_decoder(mypos)
#sub to every function that starts with "print"
self._fns = [int(l[6:]) for l in dir(self) if l.startswith("handle")]
for i in self._fns:
publisher.subscribe("type%i_dl" % i, getattr(self, "handle%i" % i))
def parse(self, message):
[data, ecc, reference, timestamp] = message.split()
publisher.subscribe("modes_dl", self.catch_nohandler)
ecc = long(ecc, 16)
reference = float(reference)
timestamp = float(timestamp)
@staticmethod
def prefix(msg):
return "(%i %.8f) " % (msg.rssi, msg.timestamp)
if reference == 0.0:
refdb = -150.0
else:
refdb = 20.0*math.log10(reference)
output = "(%.0f %.10f) " % (refdb, timestamp);
def catch_nohandler(self, msg):
if msg.data.get_type() not in self._fns:
retstr = output_print.prefix(msg)
retstr += "No handler for message type %i" % msg.data.get_type()
if "ap" in msg.data.fields:
retstr += " from %.6x" % msg.data["ap"]
print retstr
def handle0(self, msg):
try:
data = air_modes.modes_reply(long(data, 16))
msgtype = data["df"]
if msgtype == 0:
output += self.print0(data, ecc)
elif msgtype == 4:
output += self.print4(data, ecc)
elif msgtype == 5:
output += self.print5(data, ecc)
elif msgtype == 11:
output += self.print11(data, ecc)
elif msgtype == 17:
output += self.print17(data)
elif msgtype == 20 or msgtype == 21 or msgtype == 16:
output += self.printTCAS(data, ecc)
else:
output += "No handler for message type %i from %x (but it's in modes_parse)" % (msgtype, ecc)
return output
except NoHandlerError as e:
output += "No handler for message type %s from %x" % (e.msgtype, ecc)
return output
except MetricAltError:
pass
except CPRNoPositionError:
pass
def output(self, msg):
try:
parsed = self.parse(msg)
if parsed is not None:
print self.parse(msg)
except ADSBError:
pass
def print0(self, shortdata, ecc):
[vs, cc, sl, ri, altitude] = self.parse0(shortdata)
retstr = "Type 0 (short A-A surveillance) from %x at %ift" % (ecc, altitude)
retstr = output_print.prefix(msg)
retstr += "Type 0 (short A-A surveillance) from %x at %ift" % (msg.ecc, air_modes.decode_alt(msg.data["ac"], True))
ri = msg.data["ri"]
if ri == 0:
retstr += " (No TCAS)"
elif ri == 2:
@ -92,55 +66,61 @@ class output_print(air_modes.parse):
retstr += " (speed <75kt)"
elif ri > 9:
retstr += " (speed %i-%ikt)" % (75 * (1 << (ri-10)), 75 * (1 << (ri-9)))
else:
raise ADSBError
if vs is True:
except ADSBError:
return
if msg.data["vs"] is 1:
retstr += " (aircraft is on the ground)"
return retstr
def print4(self, shortdata, ecc):
[fs, dr, um, altitude] = self.parse4(shortdata)
retstr = "Type 4 (short surveillance altitude reply) from %x at %ift" % (ecc, altitude)
print retstr
@staticmethod
def fs_text(fs):
if fs == 1:
retstr += " (aircraft is on the ground)"
return " (aircraft is on the ground)"
elif fs == 2:
retstr += " (AIRBORNE ALERT)"
return " (AIRBORNE ALERT)"
elif fs == 3:
retstr += " (GROUND ALERT)"
return " (GROUND ALERT)"
elif fs == 4:
retstr += " (SPI ALERT)"
return " (SPI ALERT)"
elif fs == 5:
retstr += " (SPI)"
return " (SPI)"
else:
raise ADSBError
return retstr
def handle4(self, msg):
try:
retstr = output_print.prefix(msg)
retstr += "Type 4 (short surveillance altitude reply) from %x at %ift" % (msg.ecc, air_modes.decode_alt(msg.data["ac"], True))
retstr += output_print.fs_text(msg.data["fs"])
except ADSBError:
return
print retstr
def print5(self, shortdata, ecc):
[fs, dr, um, ident] = self.parse5(shortdata)
def handle5(self, msg):
try:
retstr = output_print.prefix(msg)
retstr += "Type 5 (short surveillance ident reply) from %x with ident %i" % (msg.ecc, air_modes.decode_id(msg.data["id"]))
retstr += output_print.fs_text(msg.data["fs"])
except ADSBError:
return
print retstr
retstr = "Type 5 (short surveillance ident reply) from %x with ident %i" % (ecc, ident)
def handle11(self, msg):
try:
retstr = output_print.prefix(msg)
retstr += "Type 11 (all call reply) from %x in reply to interrogator %i with capability level %i" % (msg.data["aa"], msg.ecc & 0xF, msg.data["ca"]+1)
except ADSBError:
return
print retstr
if fs == 1:
retstr += " (aircraft is on the ground)"
elif fs == 2:
retstr += " (AIRBORNE ALERT)"
elif fs == 3:
retstr += " (GROUND ALERT)"
elif fs == 4:
retstr += " (SPI ALERT)"
elif fs == 5:
retstr += " (SPI)"
return retstr
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, data):
#the only one which requires state
def handle17(self, data):
return
icao24 = data["aa"]
bdsreg = data["me"].get_type()
@ -189,7 +169,8 @@ class output_print(air_modes.parse):
return retstr
def printTCAS(self, data, ecc):
def printTCAS(self, msg):
return
msgtype = data["df"]
if msgtype == 20 or msgtype == 16:
#type 16 does not have fs, dr, um but we get alt here
@ -244,3 +225,7 @@ class output_print(air_modes.parse):
retstr += " ident %x" % ident
return retstr
handle16 = printTCAS
handle20 = printTCAS
handle21 = printTCAS

View File

@ -254,40 +254,7 @@ def decode_id(id):
return (a * 1000) + (b * 100) + (c * 10) + d
class parse:
def __init__(self, mypos):
self.my_location = mypos
self.cpr = cpr.cpr_decoder(self.my_location)
def parse0(self, data):
altitude = decode_alt(data["ac"], True)
return [data["vs"], data["cc"], data["sl"], data["ri"], altitude]
def parse4(self, data):
altitude = decode_alt(data["ac"], True)
return [data["fs"], data["dr"], data["um"], altitude]
def parse5(self, data):
squawk = decode_id(data["id"])
return [data["fs"], data["dr"], data["um"], squawk]
def parse11(self, data, ecc):
interrogator = ecc & 0x0F
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", "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, data):
catstring = self.categories[data["ftc"]-1][data["cat"]]
msg = ""
for i in range(0, 8):
msg += self.charmap(data["ident"] >> (42-6*i) & 0x3F)
return (msg, catstring)
#decode ident squawks
def charmap(self, d):
if d > 0 and d < 27:
retval = chr(ord("A")+d-1)
@ -300,28 +267,29 @@ class parse:
return retval
def parseBDS05(self, data):
icao24 = data["aa"]
def parseBDS08(self, data):
categories = [["NO INFO", "RESERVED", "RESERVED", "RESERVED", "RESERVED", "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"]]
encoded_lon = data["lon"]
encoded_lat = data["lat"]
cpr_format = data["cpr"]
catstring = categories[data["ftc"]-1][data["cat"]]
msg = ""
for i in range(0, 8):
msg += charmap(data["ident"] >> (42-6*i) & 0x3F)
return (msg, catstring)
#NOTE: this is stateful -- requires CPR decoder
def parseBDS05(self, 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)
[decoded_lat, decoded_lon, rnge, bearing] = cpr.decode(data["aa"], data["lat"], data["lon"], data["cpr"], 0)
return [altitude, decoded_lat, decoded_lon, rnge, bearing]
#welp turns out it looks like there's only 17 bits in the BDS0,6 ground packet after all.
def parseBDS06(self, data):
icao24 = data["aa"]
encoded_lon = data["lon"]
encoded_lat = data["lat"]
cpr_format = data["cpr"]
#NOTE: this is stateful -- requires CPR decoder
def parseBDS06(self, 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)
[decoded_lat, decoded_lon, rnge, bearing] = cpr.decode(data["aa"], data["lat"], data["lon"], data["cpr"], 1)
return [ground_track, decoded_lat, decoded_lon, rnge, bearing]
def parseBDS09_0(self, data):