Fix modes_gui. Only thing which should be nonfunc. is the reports/sec box (no thread to run it).

This commit is contained in:
Nick Foster 2013-06-10 11:57:12 -04:00
parent bed2aa499e
commit d508b39b31
5 changed files with 131 additions and 220 deletions

View File

@ -20,15 +20,17 @@
# #
import os, sys, time, threading, datetime, math, csv import os, sys, time, threading, datetime, math, csv
from optparse import OptionParser
from PyQt4 import QtCore,QtGui from PyQt4 import QtCore,QtGui
from PyQt4.Qwt5 import Qwt from PyQt4.Qwt5 import Qwt
from gnuradio import gr, gru, optfir, eng_notation, blks2 from gnuradio import gr, gru, optfir, eng_notation, blks2
import gnuradio.gr.gr_threading as _threading from gnuradio.eng_option import eng_option
import air_modes import air_modes
from air_modes.exceptions import * from air_modes.exceptions import *
from air_modes.modes_rx_ui import Ui_MainWindow from air_modes.modes_rx_ui import Ui_MainWindow
from air_modes.gui_model import * from air_modes.gui_model import *
import sqlite3 import sqlite3
import zmq
class mainwindow(QtGui.QMainWindow): class mainwindow(QtGui.QMainWindow):
live_data_changed_signal = QtCore.pyqtSignal(QtCore.QString, name='liveDataChanged') live_data_changed_signal = QtCore.pyqtSignal(QtCore.QString, name='liveDataChanged')
@ -39,7 +41,7 @@ class mainwindow(QtGui.QMainWindow):
#set defaults #set defaults
#add file, RTL, UHD sources #add file, RTL, UHD sources
self.ui.combo_source.addItems(["UHD device", "RTL-SDR", "File"]) self.ui.combo_source.addItems(["UHD", "Osmocom", "File/UDP"])
self.ui.combo_source.setCurrentIndex(0) self.ui.combo_source.setCurrentIndex(0)
#populate antenna, rate combo boxes based on source #populate antenna, rate combo boxes based on source
@ -51,8 +53,8 @@ class mainwindow(QtGui.QMainWindow):
#default to 5dB #default to 5dB
self.ui.line_threshold.insert("5") self.ui.line_threshold.insert("5")
self.ui.prog_rssi.setMinimum(-40) self.ui.prog_rssi.setMinimum(0)
self.ui.prog_rssi.setMaximum(0) self.ui.prog_rssi.setMaximum(40)
self.ui.combo_ant.setCurrentIndex(self.ui.combo_ant.findText("RX2")) self.ui.combo_ant.setCurrentIndex(self.ui.combo_ant.findText("RX2"))
@ -71,15 +73,12 @@ class mainwindow(QtGui.QMainWindow):
self.ui.check_adsbonly.setCheckState(QtCore.Qt.Unchecked) self.ui.check_adsbonly.setCheckState(QtCore.Qt.Unchecked)
self.queue = gr.msg_queue(10) self.queue = gr.msg_queue(10)
self.runner = None self.running = False
self.fg = None
self.outputs = []
self.updates = []
self.output_handler = None
self.kmlgen = None #necessary bc we stop its thread in shutdown self.kmlgen = None #necessary bc we stop its thread in shutdown
self.dbname = "air_modes.db" self.dbname = "air_modes.db"
self.num_reports = 0 self.num_reports = 0
self.last_report = 0 self.last_report = 0
self.context = zmq.Context(1)
self.datamodel = dashboard_data_model(None) self.datamodel = dashboard_data_model(None)
self.ui.list_aircraft.setModel(self.datamodel) self.ui.list_aircraft.setModel(self.datamodel)
@ -167,11 +166,11 @@ class mainwindow(QtGui.QMainWindow):
self.ratetext = [] self.ratetext = []
self.antennas = [] self.antennas = []
if sourceid == "UHD device": if sourceid == "UHD":
try: try:
from gnuradio import uhd from gnuradio import uhd
self.src = uhd.single_usrp_source("", uhd.io_type_t.COMPLEX_FLOAT32, 1) self.src = uhd.single_usrp_source("", uhd.io_type_t.COMPLEX_FLOAT32, 1)
self.rates = [rate.start() for rate in self.src.get_samp_rates()] self.rates = [rate.start() for rate in self.src.get_samp_rates() if (rate.start() % 2.e6) == 0]
self.antennas = self.src.get_antennas() self.antennas = self.src.get_antennas()
self.src = None #deconstruct UHD source for now self.src = None #deconstruct UHD source for now
self.ui.combo_ant.setEnabled(True) self.ui.combo_ant.setEnabled(True)
@ -184,15 +183,25 @@ class mainwindow(QtGui.QMainWindow):
self.ui.combo_rate.setEnabled(False) self.ui.combo_rate.setEnabled(False)
self.ui.stack_source.setCurrentIndex(0) self.ui.stack_source.setCurrentIndex(0)
elif sourceid == "RTL-SDR": elif sourceid == "Osmocom":
self.rates = [3.2e6] try:
self.antennas = ["RX"] import osmosdr
self.ui.combo_ant.setEnabled(False) self.src = osmosdr.source_c("")
self.ui.combo_rate.setEnabled(False) self.rates = [rate.start() for rate in self.src.get_sample_rates() if (rate.start() % 2.e6) == 0]
self.ui.stack_source.setCurrentIndex(0) self.antennas = ["RX"]
self.src = None
self.ui.combo_ant.setEnabled(False)
self.ui.combo_rate.setEnabled(True)
self.ui.stack_source.setCurrentIndex(0)
except:
self.rates = []
self.antennas = []
self.ui.combo_ant.setEnabled(False)
self.ui.combo_rate.setEnabled(False)
self.ui.stack_source.setCurrentIndex(0)
elif sourceid == "File": elif sourceid == "File/UDP":
self.rates = [2e6, 4e6, 6e6, 8e6, 10e6] self.rates = [2e6*i for i in range(2,13)]
self.antennas = ["None"] self.antennas = ["None"]
self.ui.combo_ant.setEnabled(False) self.ui.combo_ant.setEnabled(False)
self.ui.combo_rate.setEnabled(True) self.ui.combo_rate.setEnabled(True)
@ -215,37 +224,36 @@ class mainwindow(QtGui.QMainWindow):
def on_button_start_released(self): def on_button_start_released(self):
#if we're already running, kill it! #if we're already running, kill it!
if self.runner is not None: if self.running is True:
self.output_handler.done = True self.on_quit()
self.output_handler = None
self.outputs = []
self.updates = []
self.fg.stop()
self.runner = None
self.fg = None
if self.kmlgen is not None:
self.kmlgen.done = True
#TODO FIXME need a way to kill kmlgen safely without delay
#self.kmlgen.join()
#self.kmlgen = None
self.num_reports = 0 self.num_reports = 0
self.ui.line_reports.setText("0") self.ui.line_reports.setText("0")
self.ui.button_start.setText("Start") self.ui.button_start.setText("Start")
self.running = False
else: #we aren't already running, let's get this party started else: #we aren't already running, let's get this party started
options = {} parser = OptionParser(option_class=eng_option)
options["source"] = str(self.ui.combo_source.currentText()) air_modes.modes_radio.add_radio_options(parser)
options["rate"] = float(self.ui.combo_rate.currentText()) * 1e6 (options, args) = parser.parse_args() #sets defaults nicely
options["antenna"] = str(self.ui.combo_ant.currentText()) if str(self.ui.combo_source.currentText()) != "File/UDP":
options["gain"] = float(self.ui.line_gain.text()) options.source = str(self.ui.combo_source.currentText()).lower()
options["threshold"] = float(self.ui.line_threshold.text()) else:
options["filename"] = str(self.ui.line_inputfile.text()) options.source = str(self.ui.line_inputfile.text())
options["pmf"] = self.ui.check_pmf.checkState() options.rate = float(self.ui.combo_rate.currentText()) * 1e6
options.antenna = str(self.ui.combo_ant.currentText())
options.gain = float(self.ui.line_gain.text())
options.threshold = float(self.ui.line_threshold.text())
options.pmf = self.ui.check_pmf.checkState()
self.fg = adsb_rx_block(options, self.queue) #create top RX block self._servers = ["inproc://modes-radio-pub"] #TODO ADD REMOTES
self.runner = top_block_runner(self.fg) #spawn new thread to do RX self._relay = air_modes.zmq_pubsub_iface(self.context, subaddr=self._servers, pubaddr=None)
if self.ui.check_raw.checkState():
options.tcp = int(self.ui.line_rawport.text())
self._radio = air_modes.modes_radio(options, self.context)
try: try:
my_position = [float(self.ui.line_my_lat.text()), float(self.ui.line_my_lon.text())] my_position = [float(self.ui.line_my_lat.text()), float(self.ui.line_my_lon.text())]
@ -253,9 +261,8 @@ class mainwindow(QtGui.QMainWindow):
my_position = None my_position = None
self.datamodelout = dashboard_output(my_position, self.datamodel) self.datamodelout = dashboard_output(my_position, self.datamodel)
self._relay.subscribe("dl_data", self.datamodelout.output)
self.outputs = [self.datamodelout.output]
self.updates = []
self.lock = threading.Lock() #grab a lock to ensure sql and kml don't step on each other self.lock = threading.Lock() #grab a lock to ensure sql and kml don't step on each other
#output options to populate outputs, updates #output options to populate outputs, updates
@ -266,54 +273,43 @@ class mainwindow(QtGui.QMainWindow):
if self.ui.check_sbs1.checkState(): if self.ui.check_sbs1.checkState():
sbs1port = int(self.ui.line_sbs1port.text()) sbs1port = int(self.ui.line_sbs1port.text())
sbs1out = air_modes.output_sbs1(my_position, sbs1port) sbs1out = air_modes.output_sbs1(my_position, sbs1port)
self.outputs.append(sbs1out.output) self._relay.subscribe("dl_data", sbs1.output)
self.updates.append(sbs1out.add_pending_conns)
if self.ui.check_fgfs.checkState(): if self.ui.check_fgfs.checkState():
fghost = "127.0.0.1" #TODO FIXME fghost = "127.0.0.1" #TODO FIXME
fgport = self.ui.line_fgfsport.text() fgport = self.ui.line_fgfsport.text()
fgout = air_modes.output_flightgear(my_position, fghost, int(fgport)) fgout = air_modes.output_flightgear(my_position, fghost, int(fgport))
self.outputs.append(fgout.output) self._relay.subscribe("dl_data", fgout.output)
if self.ui.check_raw.checkState():
rawport = air_modes.raw_server(int(self.ui.line_rawport.text()))
self.outputs.append(rawport.output)
self.updates.append(rawport.add_pending_conns)
#add azimuth map output and hook it up #add azimuth map output and hook it up
if my_position is not None: if my_position is not None:
self.az_map_output = air_modes.az_map_output(my_position, self.az_model) self.az_map_output = air_modes.az_map_output(my_position, self.az_model)
self.outputs.append(self.az_map_output.output) self._relay.subscribe("dl_data", self.az_map_output.output)
self.livedata = air_modes.output_print(my_position) self.livedata = air_modes.output_print(my_position)
#add output for live data box #add output for live data box
self.outputs.append(self.output_live_data) self._relay.subscribe("dl_data", self.output_live_data)
#create SQL database for KML and dashboard displays #create SQL database for KML and dashboard displays
self.dbwriter = air_modes.output_sql(my_position, self.dbname, self.lock) self.dbwriter = air_modes.output_sql(my_position, self.dbname, self.lock)
self.outputs.append(self.dbwriter.output) #now the db will update itself self._relay.subscribe("dl_data", self.dbwriter.insert) #now the db will update itself
#output to update reports/sec widget #output to update reports/sec widget
self.outputs.append(self.increment_reportspersec) self._relay.subscribe("dl_data", self.increment_reportspersec)
self.updates.append(self.update_reportspersec) #self.updates.append(self.update_reportspersec) #TODO FIXME
#create output handler thread #start the flowgraph
self.output_handler = output_handler(self.outputs, self.updates, self.queue) self._radio.start()
self.ui.button_start.setText("Stop") self.ui.button_start.setText("Stop")
self.running = True
def on_quit(self): def on_quit(self):
if self.runner is not None: if self.running is True:
try: self._relay.close()
self.output_handler.done = True self._radio.close()
except: self._relay = None
pass self._radio = None
self.output_handler = None
self.outputs = []
self.updates = []
self.fg.stop()
self.runner = None
self.fg = None
try: try:
self.kmlgen.done = True self.kmlgen.done = True
#TODO FIXME need a way to kill kmlgen safely without delay #TODO FIXME need a way to kill kmlgen safely without delay
@ -336,103 +332,12 @@ class mainwindow(QtGui.QMainWindow):
self.ui.text_livedata.verticalScrollBar().setSliderPosition(self.ui.text_livedata.verticalScrollBar().maximum()) self.ui.text_livedata.verticalScrollBar().setSliderPosition(self.ui.text_livedata.verticalScrollBar().maximum())
def output_live_data(self, msg): def output_live_data(self, msg):
msgstr = self.livedata.parse(msg) try:
if msgstr is not None: msgstr = self.livedata.parse(msg)
self.live_data_changed_signal.emit(msgstr) if msgstr is not None:
self.live_data_changed_signal.emit(msgstr)
except ADSBError:
pass
#the output handler is a thread which runs the various registered output functions when there's a received message.
#it also executes registered updates at the sleep rate -- currently 10Hz.
class output_handler(threading.Thread):
def __init__(self, outputs, updates, queue):
threading.Thread.__init__(self)
self.setDaemon(1)
self.outputs = outputs
self.updates = updates
self.queue = queue
self.done = False
self.start()
def run(self):
while self.done is False:
for update in self.updates:
update()
while not self.queue.empty_p():
msg = self.queue.delete_head()
for output in self.outputs:
try:
output(msg.to_string())
except ADSBError:
pass
time.sleep(0.1)
self.done = True
self.outputs = None
self.updates = None
self.queue = None
class top_block_runner(_threading.Thread):
def __init__(self, tb):
_threading.Thread.__init__(self)
self.setDaemon(1)
self.tb = tb
self.done = False
self.start()
def run(self):
self.tb.run()
self.done = True
#Top block for ADSB receiver. If you define a standard interface you
#can make this common code between the GUI app and the cmdline app
class adsb_rx_block (gr.top_block):
def __init__(self, options, queue):
gr.top_block.__init__(self)
self.options = options
rate = options["rate"]
print "Rate: %f" % rate
use_resampler = False
freq = 1090e6
if options["source"] == "UHD device":
from gnuradio import uhd
self.u = uhd.single_usrp_source("", uhd.io_type_t.COMPLEX_FLOAT32, 1)
time_spec = uhd.time_spec(0.0)
self.u.set_time_now(time_spec)
self.u.set_antenna(options["antenna"])
self.u.set_samp_rate(rate)
rate = self.u.get_samp_rate()
self.u.set_gain(int(options["gain"]))
self.u.set_center_freq(freq, 0)
elif options["source"] == "RTL-SDR":
import osmosdr
self.u = osmosdr.source_c()
self.u.set_sample_rate(3.2e6) #fixed for RTL dongles
rate = int(4e6)
self.u.set_gain_mode(0) #manual gain mode
self.u.set_gain(int(options["gain"]))
self.u.set_center_freq(freq, 0)
use_resampler = True
elif options["source"] == "File":
self.u = gr.file_source(gr.sizeof_gr_complex, options["filename"])
else:
raise NotImplementedError
self.rx_path = air_modes.rx_path(rate, options["threshold"], queue, options["pmf"])
if use_resampler:
self.lpfiltcoeffs = gr.firdes.low_pass(1, 5*3.2e6, 1.6e6, 300e3)
self.resample = blks2.rational_resampler_ccf(interpolation=5, decimation=4, taps=self.lpfiltcoeffs)
self.connect(self.u, self.resample, self.rx_path)
else:
self.connect(self.u, self.rx_path)
if __name__ == '__main__': if __name__ == '__main__':
app = QtGui.QApplication(sys.argv) app = QtGui.QApplication(sys.argv)

View File

@ -25,6 +25,7 @@
from PyQt4 import QtCore, QtGui from PyQt4 import QtCore, QtGui
import air_modes import air_modes
import threading, math, time import threading, math, time
from air_modes.exceptions import *
#fades the ICAOs out as their last report gets older, #fades the ICAOs out as their last report gets older,
#and display ident if available, ICAO otherwise #and display ident if available, ICAO otherwise
@ -146,53 +147,58 @@ class dashboard_output(air_modes.parse):
self.model = model self.model = model
def output(self, msg): def output(self, msg):
[data, ecc, reference, timestamp] = msg.split() [data, ecc, reference, timestamp] = msg.split()
data = air_modes.modes_reply(long(data, 16)) try:
ecc = long(ecc, 16) data = air_modes.modes_reply(long(data, 16))
rssi = 10.*math.log10(float(reference)) ecc = long(ecc, 16)
msgtype = data["df"] rssi = 10.*math.log10(float(reference))
now = time.time() msgtype = data["df"]
newrow = {"rssi": rssi, "seen": now} now = time.time()
if msgtype in [0, 4, 20]: newrow = {"rssi": rssi, "seen": now}
newrow["altitude"] = air_modes.altitude.decode_alt(data["ac"], True) if msgtype in [0, 4, 20]:
newrow["icao"] = ecc newrow["altitude"] = air_modes.altitude.decode_alt(data["ac"], True)
self.model.addRecord(newrow) newrow["icao"] = ecc
self.model.addRecord(newrow)
elif msgtype == 17: elif msgtype == 17:
icao = data["aa"] icao = data["aa"]
newrow["icao"] = icao newrow["icao"] = icao
subtype = data["ftc"] subtype = data["ftc"]
if subtype == 4: if subtype == 4:
(ident, actype) = self.parseBDS08(data) (ident, actype) = self.parseBDS08(data)
newrow["ident"] = ident newrow["ident"] = ident
newrow["type"] = actype newrow["type"] = actype
elif 5 <= subtype <= 8: elif 5 <= subtype <= 8:
(ground_track, decoded_lat, decoded_lon, rnge, bearing) = self.parseBDS06(data) (ground_track, decoded_lat, decoded_lon, rnge, bearing) = self.parseBDS06(data)
newrow["heading"] = ground_track newrow["heading"] = ground_track
newrow["latitude"] = decoded_lat newrow["latitude"] = decoded_lat
newrow["longitude"] = decoded_lon newrow["longitude"] = decoded_lon
newrow["altitude"] = 0 newrow["altitude"] = 0
if rnge is not None: if rnge is not None:
newrow["range"] = rnge newrow["range"] = rnge
newrow["bearing"] = bearing newrow["bearing"] = bearing
elif 9 <= subtype <= 18: elif 9 <= subtype <= 18:
(altitude, decoded_lat, decoded_lon, rnge, bearing) = self.parseBDS05(data) (altitude, decoded_lat, decoded_lon, rnge, bearing) = self.parseBDS05(data)
newrow["altitude"] = altitude newrow["altitude"] = altitude
newrow["latitude"] = decoded_lat newrow["latitude"] = decoded_lat
newrow["longitude"] = decoded_lon newrow["longitude"] = decoded_lon
if rnge is not None: if rnge is not None:
newrow["range"] = rnge newrow["range"] = rnge
newrow["bearing"] = bearing newrow["bearing"] = bearing
elif subtype == 19: elif subtype == 19:
subsubtype = data["sub"] subsubtype = data["sub"]
velocity = None velocity = None
heading = None heading = None
vert_spd = None vert_spd = None
if subsubtype == 0: if subsubtype == 0:
(velocity, heading, vert_spd) = self.parseBDS09_0(data) (velocity, heading, vert_spd) = self.parseBDS09_0(data)
elif 1 <= subsubtype <= 2: elif 1 <= subsubtype <= 2:
(velocity, heading, vert_spd) = self.parseBDS09_1(data) (velocity, heading, vert_spd) = self.parseBDS09_1(data)
newrow["speed"] = velocity newrow["speed"] = velocity
newrow["heading"] = heading newrow["heading"] = heading
newrow["vertical"] = vert_spd newrow["vertical"] = vert_spd
self.model.addRecord(newrow)
except ADSBError:
return
self.model.addRecord(newrow)

View File

@ -68,7 +68,7 @@ class modes_radio (gr.top_block, pubsub):
#Publish messages when they come back off the queue #Publish messages when they come back off the queue
server_addr = ["inproc://modes-radio-pub"] server_addr = ["inproc://modes-radio-pub"]
if options.tcp is not None: if options.tcp is not None:
server_addr += ["tcp://*:%i"] % options.tcp server_addr += ["tcp://*:%i" % options.tcp]
self._sender = air_modes.zmq_pubsub_iface(context, subaddr=None, pubaddr=server_addr) self._sender = air_modes.zmq_pubsub_iface(context, subaddr=None, pubaddr=server_addr)
self._async_sender = gru.msgq_runner(self._queue, self.send) self._async_sender = gru.msgq_runner(self._queue, self.send)
@ -83,7 +83,7 @@ class modes_radio (gr.top_block, pubsub):
#Choose source #Choose source
group.add_option("-s","--source", type="string", default="uhd", group.add_option("-s","--source", type="string", default="uhd",
help="Choose source: uhd, osmocom, <filename>, or <ip:port> [default=%default]") help="Choose source: uhd, osmocom, <filename>, or <ip:port> [default=%default]")
group.add_option("-t","--tcp", type="int", default=None, group.add_option("-t","--tcp", type="int", default=None, metavar="PORT",
help="Open a TCP server on this port to publish reports") help="Open a TCP server on this port to publish reports")
#UHD/Osmocom args #UHD/Osmocom args

View File

@ -111,7 +111,7 @@ class output_sql(air_modes.parse, pubsub):
def sql17(self, data): def sql17(self, data):
icao24 = data["aa"] icao24 = data["aa"]
bdsreg = data["me"].get_type() bdsreg = data["me"].get_type()
self["bds%.2i" % bdsreg] = icao24 #publish self["bds%.2i" % bdsreg] = icao24 #publish under "bds08", "bds06", etc.
if bdsreg == 0x08: if bdsreg == 0x08:
(msg, typename) = self.parseBDS08(data) (msg, typename) = self.parseBDS08(data)

View File

@ -365,7 +365,7 @@
</rect> </rect>
</property> </property>
<property name="text"> <property name="text">
<string>Raw</string> <string>TCP</string>
</property> </property>
</widget> </widget>
<widget class="QLineEdit" name="line_kmlfilename"> <widget class="QLineEdit" name="line_kmlfilename">