Compare commits
44 Commits
mlat_serve
...
3.6
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fbe3c464fb | ||
|
|
12c09ba1df | ||
|
|
a7af518653 | ||
|
|
302fa7203d | ||
|
|
e18a2e460c | ||
|
|
f8f08ecd37 | ||
|
|
9563972591 | ||
|
|
72ae3abf12 | ||
|
|
d84c0c3204 | ||
|
|
a1e2297134 | ||
|
|
230356bcaa | ||
|
|
29f8a2c1b4 | ||
|
|
d508b39b31 | ||
|
|
bed2aa499e | ||
|
|
244c9105f2 | ||
|
|
1880126100 | ||
|
|
34939bba52 | ||
|
|
fd6ee2ce89 | ||
|
|
c0543923f6 | ||
|
|
1cb8c726ed | ||
|
|
b5e3964d12 | ||
|
|
94af9fac48 | ||
|
|
d2a6f40bbd | ||
|
|
51cb2bdf46 | ||
|
|
798d5e15c9 | ||
|
|
4bbe250f39 | ||
|
|
cfab7123cc | ||
|
|
a847f5f875 | ||
|
|
79aee53a52 | ||
|
|
f62813f039 | ||
|
|
2ace332b89 | ||
|
|
9dad60303a | ||
|
|
4fe2334b28 | ||
|
|
ba55d24e92 | ||
|
|
b71c978e27 | ||
|
|
33874893b7 | ||
|
|
4216b96262 | ||
|
|
841e2aaa04 | ||
|
|
4d569f9112 | ||
|
|
db34eca30e | ||
|
|
3e9854f337 | ||
|
|
4e5fb40531 | ||
|
|
f25d21f505 | ||
|
|
f4fbd25bb0 |
@@ -1,4 +1,4 @@
|
||||
# Copyright 2011 Free Software Foundation, Inc.
|
||||
# Copyright 2011,2013 Free Software Foundation, Inc.
|
||||
#
|
||||
# This file is part of GNU Radio
|
||||
#
|
||||
@@ -47,23 +47,7 @@ endif()
|
||||
########################################################################
|
||||
# Find boost
|
||||
########################################################################
|
||||
if(UNIX AND EXISTS "/usr/lib64")
|
||||
list(APPEND BOOST_LIBRARYDIR "/usr/lib64") #fedora 64-bit fix
|
||||
endif(UNIX AND EXISTS "/usr/lib64")
|
||||
set(Boost_ADDITIONAL_VERSIONS
|
||||
"1.35.0" "1.35" "1.36.0" "1.36" "1.37.0" "1.37" "1.38.0" "1.38" "1.39.0" "1.39"
|
||||
"1.40.0" "1.40" "1.41.0" "1.41" "1.42.0" "1.42" "1.43.0" "1.43" "1.44.0" "1.44"
|
||||
"1.45.0" "1.45" "1.46.0" "1.46" "1.47.0" "1.47" "1.48.0" "1.48" "1.49.0" "1.49"
|
||||
"1.50.0" "1.50" "1.51.0" "1.51" "1.52.0" "1.52" "1.53.0" "1.53" "1.54.0" "1.54"
|
||||
"1.55.0" "1.55" "1.56.0" "1.56" "1.57.0" "1.57" "1.58.0" "1.58" "1.59.0" "1.59"
|
||||
"1.60.0" "1.60" "1.61.0" "1.61" "1.62.0" "1.62" "1.63.0" "1.63" "1.64.0" "1.64"
|
||||
"1.65.0" "1.65" "1.66.0" "1.66" "1.67.0" "1.67" "1.68.0" "1.68" "1.69.0" "1.69"
|
||||
)
|
||||
find_package(Boost "1.35")
|
||||
|
||||
if(NOT Boost_FOUND)
|
||||
message(FATAL_ERROR "Boost required to compile gr-air-modes")
|
||||
endif()
|
||||
include(GrBoost)
|
||||
|
||||
########################################################################
|
||||
# Install directories
|
||||
@@ -96,6 +80,14 @@ if(NOT GNURADIO_CORE_FOUND)
|
||||
message(FATAL_ERROR "GnuRadio Core required to compile gr-air-modes")
|
||||
endif()
|
||||
|
||||
########################################################################
|
||||
# Find 0MQ networking library
|
||||
########################################################################
|
||||
find_package(ZeroMQ)
|
||||
if(NOT ZEROMQ_FOUND)
|
||||
message(FATAL_ERROR "Could not find required library libzmq (ZeroMQ).")
|
||||
endif()
|
||||
|
||||
########################################################################
|
||||
# Setup the include and linker paths
|
||||
########################################################################
|
||||
|
||||
252
apps/modes_gui
252
apps/modes_gui
@@ -20,15 +20,18 @@
|
||||
#
|
||||
|
||||
import os, sys, time, threading, datetime, math, csv
|
||||
from optparse import OptionParser
|
||||
from PyQt4 import QtCore,QtGui
|
||||
from PyQt4.Qwt5 import Qwt
|
||||
from gnuradio import gr, gru, optfir, eng_notation, blks2
|
||||
import gnuradio.gr.gr_threading as _threading
|
||||
from gnuradio.eng_option import eng_option
|
||||
from gnuradio.gr.pubsub import pubsub
|
||||
import air_modes
|
||||
from air_modes.exceptions import *
|
||||
from air_modes.modes_rx_ui import Ui_MainWindow
|
||||
from air_modes.gui_model import *
|
||||
import sqlite3
|
||||
import zmq
|
||||
|
||||
class mainwindow(QtGui.QMainWindow):
|
||||
live_data_changed_signal = QtCore.pyqtSignal(QtCore.QString, name='liveDataChanged')
|
||||
@@ -39,7 +42,7 @@ class mainwindow(QtGui.QMainWindow):
|
||||
|
||||
#set defaults
|
||||
#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)
|
||||
|
||||
#populate antenna, rate combo boxes based on source
|
||||
@@ -51,8 +54,8 @@ class mainwindow(QtGui.QMainWindow):
|
||||
#default to 5dB
|
||||
self.ui.line_threshold.insert("5")
|
||||
|
||||
self.ui.prog_rssi.setMinimum(-40)
|
||||
self.ui.prog_rssi.setMaximum(0)
|
||||
self.ui.prog_rssi.setMinimum(0)
|
||||
self.ui.prog_rssi.setMaximum(40)
|
||||
|
||||
self.ui.combo_ant.setCurrentIndex(self.ui.combo_ant.findText("RX2"))
|
||||
|
||||
@@ -71,15 +74,12 @@ class mainwindow(QtGui.QMainWindow):
|
||||
self.ui.check_adsbonly.setCheckState(QtCore.Qt.Unchecked)
|
||||
|
||||
self.queue = gr.msg_queue(10)
|
||||
self.runner = None
|
||||
self.fg = None
|
||||
self.outputs = []
|
||||
self.updates = []
|
||||
self.output_handler = None
|
||||
self.running = False
|
||||
self.kmlgen = None #necessary bc we stop its thread in shutdown
|
||||
self.dbname = "air_modes.db"
|
||||
self.num_reports = 0
|
||||
self.last_report = 0
|
||||
self.context = zmq.Context(1)
|
||||
|
||||
self.datamodel = dashboard_data_model(None)
|
||||
self.ui.list_aircraft.setModel(self.datamodel)
|
||||
@@ -167,11 +167,11 @@ class mainwindow(QtGui.QMainWindow):
|
||||
self.ratetext = []
|
||||
self.antennas = []
|
||||
|
||||
if sourceid == "UHD device":
|
||||
if sourceid == "UHD":
|
||||
try:
|
||||
from gnuradio import uhd
|
||||
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.src = None #deconstruct UHD source for now
|
||||
self.ui.combo_ant.setEnabled(True)
|
||||
@@ -184,15 +184,25 @@ class mainwindow(QtGui.QMainWindow):
|
||||
self.ui.combo_rate.setEnabled(False)
|
||||
self.ui.stack_source.setCurrentIndex(0)
|
||||
|
||||
elif sourceid == "RTL-SDR":
|
||||
self.rates = [3.2e6]
|
||||
self.antennas = ["RX"]
|
||||
self.ui.combo_ant.setEnabled(False)
|
||||
self.ui.combo_rate.setEnabled(False)
|
||||
self.ui.stack_source.setCurrentIndex(0)
|
||||
elif sourceid == "Osmocom":
|
||||
try:
|
||||
import osmosdr
|
||||
self.src = osmosdr.source_c("")
|
||||
self.rates = [rate.start() for rate in self.src.get_sample_rates() if (rate.start() % 2.e6) == 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":
|
||||
self.rates = [2e6, 4e6, 6e6, 8e6, 10e6]
|
||||
elif sourceid == "File/UDP":
|
||||
self.rates = [2e6*i for i in range(2,13)]
|
||||
self.antennas = ["None"]
|
||||
self.ui.combo_ant.setEnabled(False)
|
||||
self.ui.combo_rate.setEnabled(True)
|
||||
@@ -215,47 +225,48 @@ class mainwindow(QtGui.QMainWindow):
|
||||
|
||||
def on_button_start_released(self):
|
||||
#if we're already running, kill it!
|
||||
if self.runner is not None:
|
||||
self.output_handler.done = True
|
||||
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
|
||||
if self.running is True:
|
||||
self.on_quit()
|
||||
|
||||
self.num_reports = 0
|
||||
self.ui.line_reports.setText("0")
|
||||
|
||||
self.ui.button_start.setText("Start")
|
||||
self.running = False
|
||||
|
||||
else: #we aren't already running, let's get this party started
|
||||
options = {}
|
||||
options["source"] = str(self.ui.combo_source.currentText())
|
||||
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["filename"] = str(self.ui.line_inputfile.text())
|
||||
options["pmf"] = self.ui.check_pmf.checkState()
|
||||
parser = OptionParser(option_class=eng_option)
|
||||
air_modes.modes_radio.add_radio_options(parser)
|
||||
(options, args) = parser.parse_args() #sets defaults nicely
|
||||
if str(self.ui.combo_source.currentText()) != "File/UDP":
|
||||
options.source = str(self.ui.combo_source.currentText()).lower()
|
||||
else:
|
||||
options.source = str(self.ui.line_inputfile.text())
|
||||
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.runner = top_block_runner(self.fg) #spawn new thread to do RX
|
||||
self._servers = ["inproc://modes-radio-pub"] #TODO ADD REMOTES
|
||||
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)
|
||||
self._publisher = pubsub()
|
||||
self._relay.subscribe("dl_data", air_modes.make_parser(self._publisher))
|
||||
|
||||
try:
|
||||
my_position = [float(self.ui.line_my_lat.text()), float(self.ui.line_my_lon.text())]
|
||||
except:
|
||||
my_position = None
|
||||
|
||||
self.datamodelout = dashboard_output(my_position, self.datamodel)
|
||||
self._cpr_dec = air_modes.cpr_decoder(my_position)
|
||||
|
||||
self.datamodelout = dashboard_output(self._cpr_dec, self.datamodel, self._publisher)
|
||||
|
||||
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
|
||||
|
||||
#output options to populate outputs, updates
|
||||
@@ -265,55 +276,41 @@ class mainwindow(QtGui.QMainWindow):
|
||||
|
||||
if self.ui.check_sbs1.checkState():
|
||||
sbs1port = int(self.ui.line_sbs1port.text())
|
||||
sbs1out = air_modes.output_sbs1(my_position, sbs1port)
|
||||
self.outputs.append(sbs1out.output)
|
||||
self.updates.append(sbs1out.add_pending_conns)
|
||||
sbs1out = air_modes.output_sbs1(self._cpr_dec, sbs1port, self._publisher)
|
||||
|
||||
if self.ui.check_fgfs.checkState():
|
||||
fghost = "127.0.0.1" #TODO FIXME
|
||||
fgport = self.ui.line_fgfsport.text()
|
||||
fgout = air_modes.output_flightgear(my_position, fghost, int(fgport))
|
||||
self.outputs.append(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)
|
||||
fgout = air_modes.output_flightgear(self._cpr_dec, fghost, int(fgport), self._publisher)
|
||||
|
||||
#add azimuth map output and hook it up
|
||||
if my_position is not None:
|
||||
self.az_map_output = air_modes.az_map_output(my_position, self.az_model)
|
||||
self.outputs.append(self.az_map_output.output)
|
||||
self.az_map_output = air_modes.az_map_output(self._cpr_dec, self.az_model, self._publisher)
|
||||
#self._relay.subscribe("dl_data", self.az_map_output.output)
|
||||
|
||||
self.livedata = air_modes.output_print(my_position)
|
||||
self.livedata = air_modes.output_print(self._cpr_dec, self._publisher)
|
||||
#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
|
||||
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.dbwriter = air_modes.output_sql(self._cpr_dec, self.dbname, self.lock, self._publisher)
|
||||
|
||||
#output to update reports/sec widget
|
||||
self.outputs.append(self.increment_reportspersec)
|
||||
self.updates.append(self.update_reportspersec)
|
||||
self._relay.subscribe("dl_data", self.increment_reportspersec)
|
||||
#self.updates.append(self.update_reportspersec) #TODO FIXME
|
||||
|
||||
#create output handler thread
|
||||
self.output_handler = output_handler(self.outputs, self.updates, self.queue)
|
||||
#start the flowgraph
|
||||
self._radio.start()
|
||||
|
||||
self.ui.button_start.setText("Stop")
|
||||
self.running = True
|
||||
|
||||
def on_quit(self):
|
||||
if self.runner is not None:
|
||||
try:
|
||||
self.output_handler.done = True
|
||||
except:
|
||||
pass
|
||||
self.output_handler = None
|
||||
self.outputs = []
|
||||
self.updates = []
|
||||
self.fg.stop()
|
||||
self.runner = None
|
||||
self.fg = None
|
||||
if self.running is True:
|
||||
self._relay.close()
|
||||
self._radio.close()
|
||||
self._relay = None
|
||||
self._radio = None
|
||||
try:
|
||||
self.kmlgen.done = True
|
||||
#TODO FIXME need a way to kill kmlgen safely without delay
|
||||
@@ -336,103 +333,12 @@ class mainwindow(QtGui.QMainWindow):
|
||||
self.ui.text_livedata.verticalScrollBar().setSliderPosition(self.ui.text_livedata.verticalScrollBar().maximum())
|
||||
|
||||
def output_live_data(self, msg):
|
||||
msgstr = self.livedata.parse(msg)
|
||||
if msgstr is not None:
|
||||
self.live_data_changed_signal.emit(msgstr)
|
||||
|
||||
|
||||
|
||||
#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)
|
||||
|
||||
try:
|
||||
msgstr = self.livedata.parse(msg)
|
||||
if msgstr is not None:
|
||||
self.live_data_changed_signal.emit(msgstr)
|
||||
except ADSBError:
|
||||
pass
|
||||
|
||||
if __name__ == '__main__':
|
||||
app = QtGui.QApplication(sys.argv)
|
||||
|
||||
243
apps/modes_rx
243
apps/modes_rx
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env python
|
||||
# Copyright 2010 Nick Foster
|
||||
# Copyright 2010, 2013 Nick Foster
|
||||
#
|
||||
# This file is part of gr-air-modes
|
||||
#
|
||||
@@ -19,221 +19,80 @@
|
||||
# Boston, MA 02110-1301, USA.
|
||||
#
|
||||
|
||||
my_position = None
|
||||
|
||||
from gnuradio import gr, gru, optfir, eng_notation, blks2
|
||||
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
|
||||
import gnuradio.gr.gr_threading as _threading
|
||||
import csv
|
||||
from air_modes.types import *
|
||||
from air_modes.exceptions import *
|
||||
import zmq
|
||||
|
||||
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()
|
||||
#todo: maybe move plugins to separate programs (flightgear, SBS1, etc.)
|
||||
def main():
|
||||
my_position = None
|
||||
usage = "%prog: [options]"
|
||||
optparser = OptionParser(option_class=eng_option, usage=usage)
|
||||
air_modes.modes_radio.add_radio_options(optparser)
|
||||
|
||||
def run(self):
|
||||
self.tb.run()
|
||||
self.done = True
|
||||
optparser.add_option("-l","--location", type="string", default=None,
|
||||
help="GPS coordinates of receiving station in format xx.xxxxx,xx.xxxxx")
|
||||
#data source options
|
||||
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://....")
|
||||
optparser.add_option("-n","--no-print", action="store_true", default=False,
|
||||
help="disable printing decoded packets to stdout")
|
||||
#output plugins
|
||||
optparser.add_option("-K","--kml", type="string", default=None,
|
||||
help="filename for Google Earth KML output")
|
||||
optparser.add_option("-P","--sbs1", action="store_true", default=False,
|
||||
help="open an SBS-1-compatible server on port 30003")
|
||||
optparser.add_option("-m","--multiplayer", type="string", default=None,
|
||||
help="FlightGear server to send aircraft data, in format host:port")
|
||||
|
||||
class adsb_rx_block (gr.top_block):
|
||||
def __init__(self, options, args, queue):
|
||||
gr.top_block.__init__(self)
|
||||
(options, args) = optparser.parse_args()
|
||||
|
||||
self.options = options
|
||||
self.args = args
|
||||
rate = int(options.rate)
|
||||
use_resampler = False
|
||||
|
||||
if options.filename is None and options.udp is None and not options.rtlsdr:
|
||||
#UHD source by default
|
||||
from gnuradio import uhd
|
||||
self.u = uhd.single_usrp_source(options.args, uhd.io_type_t.COMPLEX_FLOAT32, 1)
|
||||
time_spec = uhd.time_spec(0.0)
|
||||
self.u.set_time_now(time_spec)
|
||||
|
||||
#if(options.rx_subdev_spec is None):
|
||||
# options.rx_subdev_spec = ""
|
||||
#self.u.set_subdev_spec(options.rx_subdev_spec)
|
||||
if not options.antenna is None:
|
||||
self.u.set_antenna(options.antenna)
|
||||
|
||||
self.u.set_samp_rate(rate)
|
||||
rate = int(self.u.get_samp_rate()) #retrieve actual
|
||||
|
||||
if options.gain is None: #set to halfway
|
||||
g = self.u.get_gain_range()
|
||||
options.gain = (g.start()+g.stop()) / 2.0
|
||||
|
||||
if not(self.tune(options.freq)):
|
||||
print "Failed to set initial frequency"
|
||||
|
||||
print "Setting gain to %i" % options.gain
|
||||
self.u.set_gain(options.gain)
|
||||
print "Gain is %i" % self.u.get_gain()
|
||||
|
||||
elif options.rtlsdr: #RTLSDR dongle
|
||||
import osmosdr
|
||||
self.u = osmosdr.source_c(options.args)
|
||||
self.u.set_sample_rate(3.2e6) #fixed for RTL dongles
|
||||
if not self.u.set_center_freq(options.freq):
|
||||
print "Failed to set initial frequency"
|
||||
|
||||
self.u.set_gain_mode(0) #manual gain mode
|
||||
if options.gain is None:
|
||||
options.gain = 34
|
||||
|
||||
self.u.set_gain(options.gain)
|
||||
print "Gain is %i" % self.u.get_gain()
|
||||
|
||||
use_resampler = True
|
||||
|
||||
else:
|
||||
if options.filename is not None:
|
||||
self.u = gr.file_source(gr.sizeof_gr_complex, options.filename)
|
||||
elif options.udp is not None:
|
||||
self.u = gr.udp_source(gr.sizeof_gr_complex, "localhost", options.udp)
|
||||
else:
|
||||
raise Exception("No valid source selected")
|
||||
|
||||
|
||||
print "Rate is %i" % (rate,)
|
||||
|
||||
pass_all = 0
|
||||
if options.output_all :
|
||||
pass_all = 1
|
||||
|
||||
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)
|
||||
|
||||
def tune(self, freq):
|
||||
result = self.u.set_center_freq(freq, 0)
|
||||
return result
|
||||
|
||||
def printraw(msg):
|
||||
print msg
|
||||
|
||||
if __name__ == '__main__':
|
||||
usage = "%prog: [options] output filename"
|
||||
parser = OptionParser(option_class=eng_option, usage=usage)
|
||||
parser.add_option("-R", "--rx-subdev-spec", type="string",
|
||||
help="select USRP Rx side A or B", metavar="SUBDEV")
|
||||
parser.add_option("-A", "--antenna", type="string",
|
||||
help="select which antenna to use on daughterboard")
|
||||
parser.add_option("-D", "--args", type="string",
|
||||
help="arguments to pass to UHD/RTL constructor", default="")
|
||||
parser.add_option("-f", "--freq", type="eng_float", default=1090e6,
|
||||
help="set receive frequency in Hz [default=%default]", metavar="FREQ")
|
||||
parser.add_option("-g", "--gain", type="int", default=None,
|
||||
help="set RF gain", metavar="dB")
|
||||
parser.add_option("-r", "--rate", type="eng_float", default=4000000,
|
||||
help="set ADC sample rate [default=%default]")
|
||||
parser.add_option("-T", "--threshold", type="eng_float", default=5.0,
|
||||
help="set pulse detection threshold above noise in dB [default=%default]")
|
||||
parser.add_option("-a","--output-all", action="store_true", default=False,
|
||||
help="output all frames")
|
||||
parser.add_option("-F","--filename", type="string", default=None,
|
||||
help="read data from file instead of USRP")
|
||||
parser.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,
|
||||
help="open an SBS-1-compatible server on port 30003")
|
||||
parser.add_option("-w","--raw", action="store_true", default=False,
|
||||
help="open a server outputting raw timestamped data on port 9988")
|
||||
parser.add_option("-n","--no-print", action="store_true", default=False,
|
||||
help="disable printing decoded packets to stdout")
|
||||
parser.add_option("-l","--location", type="string", default=None,
|
||||
help="GPS coordinates of receiving station in format xx.xxxxx,xx.xxxxx")
|
||||
parser.add_option("-u","--udp", type="int", default=None,
|
||||
help="Use UDP source on specified port")
|
||||
parser.add_option("-m","--multiplayer", type="string", default=None,
|
||||
help="FlightGear server to send aircraft data, in format host:port")
|
||||
parser.add_option("-d","--rtlsdr", action="store_true", default=False,
|
||||
help="Use RTLSDR dongle instead of UHD source")
|
||||
parser.add_option("-p","--pmf", action="store_true", default=False,
|
||||
help="Use pulse matched filtering")
|
||||
|
||||
(options, args) = parser.parse_args()
|
||||
#construct the radio
|
||||
context = zmq.Context(1)
|
||||
tb = air_modes.modes_radio(options, context)
|
||||
servers = ["inproc://modes-radio-pub"]
|
||||
if options.remote is not None:
|
||||
servers += options.remote.split(",")
|
||||
relay = air_modes.zmq_pubsub_iface(context, subaddr=servers, pubaddr=None)
|
||||
publisher = pubsub()
|
||||
relay.subscribe("dl_data", air_modes.make_parser(publisher))
|
||||
|
||||
if options.location is not None:
|
||||
reader = csv.reader([options.location], quoting=csv.QUOTE_NONNUMERIC)
|
||||
my_position = reader.next()
|
||||
my_position = [float(n) for n in options.location.split(",")]
|
||||
|
||||
queue = gr.msg_queue()
|
||||
|
||||
outputs = [] #registry of plugin output functions
|
||||
updates = [] #registry of plugin update functions
|
||||
|
||||
if options.raw is True:
|
||||
rawport = air_modes.raw_server(9988) #port
|
||||
outputs.append(rawport.output)
|
||||
outputs.append(printraw)
|
||||
updates.append(rawport.add_pending_conns)
|
||||
#CPR decoder obj to handle getting position from BDS0,5 and BDS0,6 pkts
|
||||
cpr_dec = air_modes.cpr_decoder(my_position)
|
||||
|
||||
if options.kml is not None:
|
||||
#we spawn a thread to run every 30 seconds (or whatever) to generate KML
|
||||
dbname = 'adsb.db'
|
||||
lock = threading.Lock()
|
||||
sqldb = air_modes.output_sql(my_position, dbname, lock) #input into the db
|
||||
sqldb = air_modes.output_sql(cpr_dec, dbname, lock, publisher) #input into the db
|
||||
kmlgen = air_modes.output_kml(options.kml, dbname, my_position, lock) #create a KML generating thread to read from the db
|
||||
outputs.append(sqldb.output)
|
||||
|
||||
if options.sbs1 is True:
|
||||
sbs1port = air_modes.output_sbs1(my_position, 30003)
|
||||
outputs.append(sbs1port.output)
|
||||
updates.append(sbs1port.add_pending_conns)
|
||||
|
||||
if options.no_print is not True:
|
||||
outputs.append(air_modes.output_print(my_position).output)
|
||||
printer = air_modes.output_print(cpr_dec, publisher)
|
||||
|
||||
if options.multiplayer is not None:
|
||||
[fghost, fgport] = options.multiplayer.split(':')
|
||||
fgout = air_modes.output_flightgear(my_position, fghost, int(fgport))
|
||||
outputs.append(fgout.output)
|
||||
fgout = air_modes.output_flightgear(cpr_dec, fghost, int(fgport), publisher)
|
||||
|
||||
fg = adsb_rx_block(options, args, queue)
|
||||
runner = top_block_runner(fg)
|
||||
if options.sbs1 is True:
|
||||
sbs1port = air_modes.output_sbs1(cpr_dec, 30003, publisher)
|
||||
|
||||
while 1:
|
||||
try:
|
||||
#the update registry is really for the SBS1 and raw server plugins -- we're looking for new TCP connections.
|
||||
#i think we have to do this here rather than in the output handler because otherwise connections will stack up
|
||||
#until the next output arrives
|
||||
for update in updates:
|
||||
update()
|
||||
|
||||
#main message handler
|
||||
if not queue.empty_p() :
|
||||
while not queue.empty_p() :
|
||||
msg = queue.delete_head() #blocking read
|
||||
tb.run()
|
||||
tb.close()
|
||||
|
||||
for out in outputs:
|
||||
try:
|
||||
out(msg.to_string())
|
||||
except air_modes.ADSBError:
|
||||
pass
|
||||
relay.close()
|
||||
|
||||
elif runner.done:
|
||||
raise KeyboardInterrupt
|
||||
else:
|
||||
time.sleep(0.1)
|
||||
if options.kml is not None:
|
||||
kmlgen.close()
|
||||
|
||||
|
||||
except KeyboardInterrupt:
|
||||
fg.stop()
|
||||
runner = None
|
||||
if options.kml is not None:
|
||||
kmlgen.done = True
|
||||
break
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
# Find PyQt4
|
||||
# ~~~~~~~~~~
|
||||
# Copyright (c) 2007-2008, Simon Edwards <simon@simonzone.com>
|
||||
# Copyright (c) 2012, Nicholas Corgan <nick.corgan@ettus.com>
|
||||
# Redistribution and use is allowed according to the terms of the BSD license.
|
||||
# For details see the accompanying COPYING-CMAKE-SCRIPTS file.
|
||||
#
|
||||
@@ -22,10 +23,11 @@
|
||||
#
|
||||
# PYQT4_SIP_FLAGS - The SIP flags used to build PyQt.
|
||||
|
||||
IF(EXISTS PYQT4_VERSION)
|
||||
IF(EXISTS PYQT4_VERSION AND EXISTS PYUIC4_EXECUTABLE)
|
||||
# Already in cache, be silent
|
||||
SET(PYQT4_FOUND TRUE)
|
||||
ELSE(EXISTS PYQT4_VERSION)
|
||||
SET(PYUIC4_FOUND TRUE)
|
||||
ELSE(EXISTS PYQT4_VERSION AND EXISTS PYUIC4_EXECUTABLE)
|
||||
|
||||
FIND_FILE(_find_pyqt_py FindPyQt.py PATHS ${CMAKE_MODULE_PATH})
|
||||
|
||||
@@ -40,14 +42,20 @@ ELSE(EXISTS PYQT4_VERSION)
|
||||
SET(PYQT4_FOUND TRUE)
|
||||
ENDIF(pyqt_config)
|
||||
|
||||
IF(PYQT4_FOUND)
|
||||
FIND_PROGRAM(PYUIC4_EXECUTABLE NAMES pyuic4)
|
||||
IF(PYUIC4_EXECUTABLE)
|
||||
SET(PYUIC4_FOUND TRUE)
|
||||
ENDIF(PYUIC4_EXECUTABLE)
|
||||
|
||||
IF(PYQT4_FOUND AND PYUIC4_FOUND)
|
||||
IF(NOT PYQT4_FIND_QUIETLY)
|
||||
MESSAGE(STATUS "Found PyQt4 version: ${PYQT4_VERSION_STR}")
|
||||
MESSAGE(STATUS "Found pyuic4: ${PYUIC4_EXECUTABLE}")
|
||||
ENDIF(NOT PYQT4_FIND_QUIETLY)
|
||||
ELSE(PYQT4_FOUND)
|
||||
ELSE(PYQT4_FOUND AND PYUIC4_FOUND)
|
||||
IF(PYQT4_FIND_REQUIRED)
|
||||
MESSAGE(FATAL_ERROR "Could not find Python")
|
||||
ENDIF(PYQT4_FIND_REQUIRED)
|
||||
ENDIF(PYQT4_FOUND)
|
||||
ENDIF(PYQT4_FOUND AND PYUIC4_FOUND)
|
||||
|
||||
ENDIF(EXISTS PYQT4_VERSION)
|
||||
ENDIF(EXISTS PYQT4_VERSION AND EXISTS PYUIC4_EXECUTABLE)
|
||||
|
||||
56
cmake/Modules/FindZeroMQ.cmake
Normal file
56
cmake/Modules/FindZeroMQ.cmake
Normal file
@@ -0,0 +1,56 @@
|
||||
# - Find zeromq libraries
|
||||
# This module finds zeromq if it is installed and determines where the
|
||||
# include files and libraries are. It also determines what the name of
|
||||
# the library is. This code sets the following variables:
|
||||
#
|
||||
# ZEROMQ_FOUND - have the zeromq libs been found
|
||||
# ZEROMQ_LIBRARIES - path to the zeromq library
|
||||
# ZEROMQ_INCLUDE_DIRS - path to where zmq.h is found
|
||||
# ZEROMQ_DEBUG_LIBRARIES - path to the debug library
|
||||
|
||||
#INCLUDE(CMakeFindFrameworks)
|
||||
# Search for the zeromq framework on Apple.
|
||||
#CMAKE_FIND_FRAMEWORKS(ZeroMQ)
|
||||
|
||||
IF(WIN32)
|
||||
FIND_LIBRARY(ZEROMQ_DEBUG_LIBRARY
|
||||
NAMES libzmq_d zmq_d
|
||||
PATHS
|
||||
${ZEROMQ_LIBRARIES}
|
||||
)
|
||||
ENDIF(WIN32)
|
||||
|
||||
FIND_LIBRARY(ZEROMQ_LIBRARY
|
||||
NAMES libzmq zmq
|
||||
PATHS
|
||||
${ZEROMQ_LIBRARIES}
|
||||
${NSCP_LIBRARYDIR}
|
||||
)
|
||||
|
||||
# IF(ZeroMQ_FRAMEWORKS AND NOT ZEROMQ_INCLUDE_DIR)
|
||||
# FOREACH(dir ${ZeroMQ_FRAMEWORKS})
|
||||
# SET(ZEROMQ_FRAMEWORK_INCLUDES ${ZEROMQ_FRAMEWORK_INCLUDES}
|
||||
# ${dir}/Versions/${_CURRENT_VERSION}/include/zeromq${_CURRENT_VERSION})
|
||||
# ENDFOREACH(dir)
|
||||
# ENDIF(ZeroMQ_FRAMEWORKS AND NOT ZEROMQ_INCLUDE_DIR)
|
||||
|
||||
FIND_PATH(ZEROMQ_INCLUDE_DIR
|
||||
NAMES zmq.hpp
|
||||
PATHS
|
||||
# ${ZEROMQ_FRAMEWORK_INCLUDES}
|
||||
${ZEROMQ_INCLUDE_DIRS}
|
||||
${NSCP_INCLUDEDIR}
|
||||
${ZEROMQ_INCLUDE_DIR}
|
||||
)
|
||||
|
||||
MARK_AS_ADVANCED(
|
||||
ZEROMQ_DEBUG_LIBRARY
|
||||
ZEROMQ_LIBRARY
|
||||
ZEROMQ_INCLUDE_DIR
|
||||
)
|
||||
SET(ZEROMQ_INCLUDE_DIRS "${ZEROMQ_INCLUDE_DIR}")
|
||||
SET(ZEROMQ_LIBRARIES "${ZEROMQ_LIBRARY}")
|
||||
SET(ZEROMQ_DEBUG_LIBRARIES "${ZEROMQ_DEBUG_LIBRARY}")
|
||||
|
||||
INCLUDE(FindPackageHandleStandardArgs)
|
||||
FIND_PACKAGE_HANDLE_STANDARD_ARGS(ZeroMQ DEFAULT_MSG ZEROMQ_LIBRARIES ZEROMQ_INCLUDE_DIRS)
|
||||
99
cmake/Modules/GrBoost.cmake
Normal file
99
cmake/Modules/GrBoost.cmake
Normal file
@@ -0,0 +1,99 @@
|
||||
# Copyright 2010-2011 Free Software Foundation, Inc.
|
||||
#
|
||||
# This file is part of GNU Radio
|
||||
#
|
||||
# GNU Radio 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.
|
||||
#
|
||||
# GNU Radio 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 GNU Radio; see the file COPYING. If not, write to
|
||||
# the Free Software Foundation, Inc., 51 Franklin Street,
|
||||
# Boston, MA 02110-1301, USA.
|
||||
|
||||
if(DEFINED __INCLUDED_GR_BOOST_CMAKE)
|
||||
return()
|
||||
endif()
|
||||
set(__INCLUDED_GR_BOOST_CMAKE TRUE)
|
||||
|
||||
########################################################################
|
||||
# Setup Boost and handle some system specific things
|
||||
########################################################################
|
||||
|
||||
set(BOOST_REQUIRED_COMPONENTS
|
||||
date_time
|
||||
program_options
|
||||
filesystem
|
||||
system
|
||||
thread
|
||||
)
|
||||
|
||||
if(UNIX AND NOT BOOST_ROOT AND EXISTS "/usr/lib64")
|
||||
list(APPEND BOOST_LIBRARYDIR "/usr/lib64") #fedora 64-bit fix
|
||||
endif(UNIX AND NOT BOOST_ROOT AND EXISTS "/usr/lib64")
|
||||
|
||||
if(MSVC)
|
||||
set(BOOST_REQUIRED_COMPONENTS ${BOOST_REQUIRED_COMPONENTS} chrono)
|
||||
|
||||
if (NOT DEFINED BOOST_ALL_DYN_LINK)
|
||||
set(BOOST_ALL_DYN_LINK TRUE)
|
||||
endif()
|
||||
set(BOOST_ALL_DYN_LINK "${BOOST_ALL_DYN_LINK}" CACHE BOOL "boost enable dynamic linking")
|
||||
if(BOOST_ALL_DYN_LINK)
|
||||
add_definitions(-DBOOST_ALL_DYN_LINK) #setup boost auto-linking in msvc
|
||||
else(BOOST_ALL_DYN_LINK)
|
||||
unset(BOOST_REQUIRED_COMPONENTS) #empty components list for static link
|
||||
endif(BOOST_ALL_DYN_LINK)
|
||||
endif(MSVC)
|
||||
|
||||
find_package(Boost "1.35" COMPONENTS ${BOOST_REQUIRED_COMPONENTS})
|
||||
|
||||
# This does not allow us to disable specific versions. It is used
|
||||
# internally by cmake to know the formation newer versions. As newer
|
||||
# Boost version beyond what is shown here are produced, we must extend
|
||||
# this list. To disable Boost versions, see below.
|
||||
set(Boost_ADDITIONAL_VERSIONS
|
||||
"1.35.0" "1.35" "1.36.0" "1.36" "1.37.0" "1.37" "1.38.0" "1.38" "1.39.0" "1.39"
|
||||
"1.40.0" "1.40" "1.41.0" "1.41" "1.42.0" "1.42" "1.43.0" "1.43" "1.44.0" "1.44"
|
||||
"1.45.0" "1.45" "1.46.0" "1.46" "1.47.0" "1.47" "1.48.0" "1.48" "1.49.0" "1.49"
|
||||
"1.50.0" "1.50" "1.51.0" "1.51" "1.52.0" "1.52" "1.53.0" "1.53" "1.54.0" "1.54"
|
||||
"1.55.0" "1.55" "1.56.0" "1.56" "1.57.0" "1.57" "1.58.0" "1.58" "1.59.0" "1.59"
|
||||
"1.60.0" "1.60" "1.61.0" "1.61" "1.62.0" "1.62" "1.63.0" "1.63" "1.64.0" "1.64"
|
||||
"1.65.0" "1.65" "1.66.0" "1.66" "1.67.0" "1.67" "1.68.0" "1.68" "1.69.0" "1.69"
|
||||
)
|
||||
|
||||
# Boost 1.52 disabled, see https://svn.boost.org/trac/boost/ticket/7669
|
||||
# Similar problems with Boost 1.46 and 1.47.
|
||||
|
||||
OPTION(ENABLE_BAD_BOOST "Enable known bad versions of Boost" OFF)
|
||||
if(ENABLE_BAD_BOOST)
|
||||
MESSAGE(STATUS "Enabling use of known bad versions of Boost.")
|
||||
endif(ENABLE_BAD_BOOST)
|
||||
|
||||
# For any unsuitable Boost version, add the version number below in
|
||||
# the following format: XXYYZZ
|
||||
# Where:
|
||||
# XX is the major version ('10' for version 1)
|
||||
# YY is the minor version number ('46' for 1.46)
|
||||
# ZZ is the patcher version number (typically just '00')
|
||||
set(Boost_NOGO_VERSIONS
|
||||
104600 104601 104700 105200
|
||||
)
|
||||
|
||||
foreach(ver ${Boost_NOGO_VERSIONS})
|
||||
if(${Boost_VERSION} EQUAL ${ver})
|
||||
if(NOT ENABLE_BAD_BOOST)
|
||||
MESSAGE(STATUS "WARNING: Found a known bad version of Boost (v${Boost_VERSION}). Disabling.")
|
||||
set(Boost_FOUND FALSE)
|
||||
else(NOT ENABLE_BAD_BOOST)
|
||||
MESSAGE(STATUS "WARNING: Found a known bad version of Boost (v${Boost_VERSION}). Continuing anyway.")
|
||||
set(Boost_FOUND TRUE)
|
||||
endif(NOT ENABLE_BAD_BOOST)
|
||||
endif(${Boost_VERSION} EQUAL ${ver})
|
||||
endforeach(ver)
|
||||
@@ -57,6 +57,10 @@ public:
|
||||
gr_vector_int &ninput_items,
|
||||
gr_vector_const_void_star &input_items,
|
||||
gr_vector_void_star &output_items);
|
||||
|
||||
void set_rate(int channel_rate);
|
||||
void set_threshold(float threshold_db);
|
||||
float get_threshold(void);
|
||||
};
|
||||
|
||||
#endif /* INCLUDED_AIR_MODES_PREAMBLE_H */
|
||||
|
||||
@@ -53,6 +53,8 @@ public:
|
||||
int work (int noutput_items,
|
||||
gr_vector_const_void_star &input_items,
|
||||
gr_vector_void_star &output_items);
|
||||
|
||||
void set_rate(int channel_rate);
|
||||
};
|
||||
|
||||
#endif /* INCLUDED_AIR_MODES_slicer_H */
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
/*
|
||||
# Copyright 2010 Nick Foster
|
||||
# Copyright 2013 Nicholas Corgan
|
||||
#
|
||||
# This file is part of gr-air-modes
|
||||
#
|
||||
@@ -24,6 +25,7 @@
|
||||
#include "config.h"
|
||||
#endif
|
||||
|
||||
#include <ciso646>
|
||||
#include <air_modes_preamble.h>
|
||||
#include <gr_io_signature.h>
|
||||
#include <string.h>
|
||||
@@ -40,14 +42,8 @@ air_modes_preamble::air_modes_preamble(int channel_rate, float threshold_db) :
|
||||
gr_make_io_signature2 (2, 2, sizeof(float), sizeof(float)), //stream 0 is received data, stream 1 is moving average for reference
|
||||
gr_make_io_signature (1, 1, sizeof(float))) //the output packets
|
||||
{
|
||||
d_chip_rate = 2000000; //2Mchips per second
|
||||
d_samples_per_chip = channel_rate / d_chip_rate; //must be integer number of samples per chip to work
|
||||
d_samples_per_symbol = d_samples_per_chip * 2;
|
||||
d_check_width = 120 * d_samples_per_symbol; //only search to this far from the end of the stream buffer
|
||||
d_threshold_db = threshold_db;
|
||||
d_threshold = powf(10., threshold_db/20.); //the level that the sample must be above the moving average in order to qualify as a pulse
|
||||
d_secs_per_sample = 1.0 / channel_rate;
|
||||
set_output_multiple(1+d_check_width*2);
|
||||
set_rate(channel_rate);
|
||||
set_threshold(threshold_db);
|
||||
|
||||
std::stringstream str;
|
||||
str << name() << unique_id();
|
||||
@@ -56,6 +52,27 @@ air_modes_preamble::air_modes_preamble(int channel_rate, float threshold_db) :
|
||||
set_history(d_samples_per_symbol);
|
||||
}
|
||||
|
||||
void air_modes_preamble::set_rate(int channel_rate)
|
||||
{
|
||||
d_chip_rate = 2000000; //2Mchips per second
|
||||
d_samples_per_chip = channel_rate / d_chip_rate; //must be integer number of samples per chip to work
|
||||
d_samples_per_symbol = d_samples_per_chip * 2;
|
||||
d_secs_per_sample = 1.0 / channel_rate;
|
||||
d_check_width = 240 * d_samples_per_symbol; //only search to this far from the end of the stream buffer
|
||||
set_output_multiple(1+d_check_width);
|
||||
}
|
||||
|
||||
void air_modes_preamble::set_threshold(float threshold_db)
|
||||
{
|
||||
d_threshold_db = threshold_db;
|
||||
d_threshold = powf(10., threshold_db/20.); //the level that the sample must be above the moving average in order to qualify as a pulse
|
||||
}
|
||||
|
||||
float air_modes_preamble::get_threshold(void)
|
||||
{
|
||||
return d_threshold_db;
|
||||
}
|
||||
|
||||
static void integrate_and_dump(float *out, const float *in, int chips, int samps_per_chip) {
|
||||
for(int i=0; i<chips; i++) {
|
||||
float acc = 0;
|
||||
@@ -193,10 +210,10 @@ int air_modes_preamble::general_work(int noutput_items,
|
||||
add_item_tag(0, //stream ID
|
||||
nitems_written(0), //sample
|
||||
d_key, //frame_info
|
||||
pmt::pmt_from_double(tstamp),
|
||||
pmt::pmt_make_tuple(pmt::pmt_from_double(tstamp), pmt::pmt_from_double(inavg[i])),
|
||||
d_me //block src id
|
||||
);
|
||||
|
||||
|
||||
//std::cout << "PREAMBLE" << std::endl;
|
||||
|
||||
//produce only one output per work call -- TODO this should probably change
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
/*
|
||||
# Copyright 2010 Nick Foster
|
||||
# Copyright 2013 Nicholas Corgan
|
||||
#
|
||||
# This file is part of gr-air-modes
|
||||
#
|
||||
@@ -24,6 +25,7 @@
|
||||
#include "config.h"
|
||||
#endif
|
||||
|
||||
#include <ciso646>
|
||||
#include <air_modes_slicer.h>
|
||||
#include <gr_io_signature.h>
|
||||
#include <air_modes_types.h>
|
||||
@@ -49,13 +51,16 @@ air_modes_slicer::air_modes_slicer(int channel_rate, gr_msg_queue_sptr queue) :
|
||||
gr_make_io_signature (1, 1, sizeof(float)), //stream 0 is received data, stream 1 is binary preamble detector output
|
||||
gr_make_io_signature (0, 0, 0) )
|
||||
{
|
||||
//initialize private data here
|
||||
set_rate(channel_rate);
|
||||
d_queue = queue;
|
||||
}
|
||||
|
||||
void air_modes_slicer::set_rate(int channel_rate)
|
||||
{
|
||||
d_chip_rate = 2000000; //2Mchips per second
|
||||
d_samples_per_chip = 2;//FIXME this is constant now channel_rate / d_chip_rate;
|
||||
d_samples_per_symbol = d_samples_per_chip * 2;
|
||||
d_check_width = 120 * d_samples_per_symbol; //how far you will have to look ahead
|
||||
d_queue = queue;
|
||||
|
||||
set_output_multiple(d_check_width*2); //how do you specify buffer size for sinks?
|
||||
}
|
||||
|
||||
@@ -154,17 +159,14 @@ int air_modes_slicer::work(int noutput_items,
|
||||
if(rx_packet.numlowconf < 24) rx_packet.lowconfbits[rx_packet.numlowconf++] = j;
|
||||
}
|
||||
}
|
||||
|
||||
rx_packet.timestamp = pmt_to_double(pmt_tuple_ref(tag_iter->value, 0));
|
||||
double ref = pmt_to_double(pmt_tuple_ref(tag_iter->value, 1));
|
||||
|
||||
/******************** BEGIN TIMESTAMP BS ******************/
|
||||
rx_packet.timestamp = pmt_to_double(tag_iter->value);
|
||||
/******************* END TIMESTAMP BS *********************/
|
||||
|
||||
//increment for the next round
|
||||
|
||||
//here you might want to traverse the whole packet and if you find all 0's, just toss it. don't know why these packets turn up, but they pass ECC.
|
||||
//traverse the whole packet and if you find all 0's, just toss it. don't know why these packets turn up, but they pass ECC.
|
||||
bool zeroes = 1;
|
||||
for(int m = 0; m < 14; m++) {
|
||||
if(rx_packet.data[m]) zeroes = 0;
|
||||
if(rx_packet.data[m]) { zeroes = 0; break; }
|
||||
}
|
||||
if(zeroes) {continue;} //toss it
|
||||
|
||||
@@ -185,7 +187,7 @@ int air_modes_slicer::work(int noutput_items,
|
||||
d_payload << std::hex << std::setw(2) << std::setfill('0') << unsigned(rx_packet.data[m]);
|
||||
}
|
||||
|
||||
d_payload << " " << std::setw(6) << rx_packet.crc << " " << std::dec << rx_packet.reference_level
|
||||
d_payload << " " << std::setw(6) << rx_packet.crc << " " << std::dec << rx_packet.reference_level / ref
|
||||
<< " " << std::setprecision(10) << std::setw(10) << rx_packet.timestamp;
|
||||
gr_message_sptr msg = gr_make_message_from_string(std::string(d_payload.str()));
|
||||
d_queue->handle(msg);
|
||||
|
||||
@@ -41,10 +41,13 @@ GR_PYTHON_INSTALL(
|
||||
kml.py
|
||||
parse.py
|
||||
msprint.py
|
||||
radio.py
|
||||
raw_server.py
|
||||
rx_path.py
|
||||
sbs1.py
|
||||
sql.py
|
||||
types.py
|
||||
zmq_socket.py
|
||||
Quaternion.py
|
||||
DESTINATION ${GR_PYTHON_DIR}/air_modes
|
||||
)
|
||||
|
||||
@@ -52,14 +52,19 @@ from air_modes_swig import *
|
||||
# import any pure python here
|
||||
#
|
||||
from rx_path import rx_path
|
||||
from parse import parse,modes_reply
|
||||
from zmq_socket import zmq_pubsub_iface
|
||||
from parse import *
|
||||
from msprint import output_print
|
||||
from sql import output_sql
|
||||
from sbs1 import output_sbs1
|
||||
from kml import output_kml
|
||||
from raw_server import raw_server
|
||||
from radio import modes_radio
|
||||
from exceptions import *
|
||||
from az_map import *
|
||||
from types import *
|
||||
from altitude import *
|
||||
from cpr import cpr_decoder
|
||||
#this is try/excepted in case the user doesn't have numpy installed
|
||||
try:
|
||||
from flightgear import output_flightgear
|
||||
|
||||
@@ -26,6 +26,7 @@ from PyQt4 import QtCore, QtGui
|
||||
import threading
|
||||
import math
|
||||
import air_modes
|
||||
from air_modes.exceptions import *
|
||||
|
||||
|
||||
# model has max range vs. azimuth in n-degree increments
|
||||
@@ -168,30 +169,30 @@ class az_map(QtGui.QWidget):
|
||||
self.ringsize = ringsize
|
||||
self.drawPath()
|
||||
|
||||
class az_map_output(air_modes.parse):
|
||||
def __init__(self, mypos, model):
|
||||
air_modes.parse.__init__(self, mypos)
|
||||
class az_map_output:
|
||||
def __init__(self, cprdec, model, pub):
|
||||
self._cpr = cprdec
|
||||
self.model = model
|
||||
pub.subscribe("type17_dl", self.output)
|
||||
|
||||
def output(self, msg):
|
||||
[data, ecc, reference, timestamp] = msg.split()
|
||||
data = air_modes.modes_reply(long(data, 16))
|
||||
ecc = long(ecc, 16)
|
||||
rssi = 10.*math.log10(float(reference))
|
||||
msgtype = data["df"]
|
||||
now = time.time()
|
||||
try:
|
||||
msgtype = msg.data["df"]
|
||||
now = time.time()
|
||||
|
||||
if msgtype == 17:
|
||||
icao = data["aa"]
|
||||
subtype = data["ftc"]
|
||||
distance, altitude, bearing = [0,0,0]
|
||||
if 5 <= subtype <= 8:
|
||||
(ground_track, decoded_lat, decoded_lon, distance, bearing) = self.parseBDS06(data)
|
||||
altitude = 0
|
||||
elif 9 <= subtype <= 18:
|
||||
(altitude, decoded_lat, decoded_lon, distance, bearing) = self.parseBDS05(data)
|
||||
if msgtype == 17:
|
||||
icao = msg.data["aa"]
|
||||
subtype = msg.data["ftc"]
|
||||
distance, altitude, bearing = [0,0,0]
|
||||
if 5 <= subtype <= 8:
|
||||
(ground_track, decoded_lat, decoded_lon, distance, bearing) = air_modes.parseBDS06(msg.data, self._cpr)
|
||||
altitude = 0
|
||||
elif 9 <= subtype <= 18:
|
||||
(altitude, decoded_lat, decoded_lon, distance, bearing) = air_modes.parseBDS05(msg.data, self._cpr)
|
||||
|
||||
self.model.addRecord(bearing, altitude, distance)
|
||||
self.model.addRecord(bearing, altitude, distance)
|
||||
except ADSBError:
|
||||
pass
|
||||
|
||||
|
||||
##############################
|
||||
|
||||
@@ -188,7 +188,7 @@ class cpr_decoder:
|
||||
self.evenlist_sfc = {}
|
||||
self.oddlist_sfc = {}
|
||||
|
||||
def set_location(new_location):
|
||||
def set_location(self, new_location):
|
||||
self.my_location = new_location
|
||||
|
||||
def weed_poslists(self):
|
||||
|
||||
@@ -24,12 +24,12 @@ class ADSBError(Exception):
|
||||
|
||||
class MetricAltError(ADSBError):
|
||||
pass
|
||||
|
||||
|
||||
class ParserError(ADSBError):
|
||||
pass
|
||||
|
||||
class NoHandlerError(ADSBError):
|
||||
def __init__(self, msgtype):
|
||||
def __init__(self, msgtype=None):
|
||||
self.msgtype = msgtype
|
||||
|
||||
class MlatNonConvergeError(ADSBError):
|
||||
|
||||
@@ -14,49 +14,48 @@ from Quaternion import Quat
|
||||
import numpy
|
||||
from air_modes.exceptions import *
|
||||
|
||||
class output_flightgear(air_modes.parse):
|
||||
def __init__(self, localpos, hostname, port):
|
||||
air_modes.parse.__init__(self, localpos)
|
||||
class output_flightgear:
|
||||
def __init__(self, cprdec, hostname, port, pub):
|
||||
self.hostname = hostname
|
||||
self.port = port
|
||||
self.localpos = localpos
|
||||
self.positions = {}
|
||||
self.velocities = {}
|
||||
self.callsigns = {}
|
||||
self._cpr = cprdec
|
||||
|
||||
self.sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||
self.sock.connect((self.hostname, self.port))
|
||||
pub.subscribe("type17_dl", output)
|
||||
|
||||
def output(self, message):
|
||||
[data, ecc, reference, timestamp] = message.split()
|
||||
data = air_modes.modes_reply(long(data, 16))
|
||||
def output(self, msg):
|
||||
|
||||
try:
|
||||
msgtype = data["df"]
|
||||
msgtype = msg.data["df"]
|
||||
if msgtype == 17: #ADS-B report
|
||||
icao24 = data["aa"]
|
||||
bdsreg = data["me"].get_type()
|
||||
icao24 = msg.data["aa"]
|
||||
bdsreg = msg.data["me"].get_type()
|
||||
if bdsreg == 0x08: #ident packet
|
||||
(ident, actype) = self.parseBDS08(data)
|
||||
(ident, actype) = air_modes.parseBDS08(data)
|
||||
#select model based on actype
|
||||
self.callsigns[icao24] = [ident, actype]
|
||||
|
||||
elif bdsreg == 0x06: #BDS0,6 pos
|
||||
[ground_track, decoded_lat, decoded_lon, rnge, bearing] = self.parseBDS06(data)
|
||||
[ground_track, decoded_lat, decoded_lon, rnge, bearing] = air_modes.parseBDS06(data, self._cpr)
|
||||
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)
|
||||
[altitude, decoded_lat, decoded_lon, rnge, bearing] = air_modes.parseBDS05(data, self._cpr)
|
||||
self.positions[icao24] = [decoded_lat, decoded_lon, altitude]
|
||||
self.update(icao24)
|
||||
|
||||
elif bdsreg == 0x09: #velocity
|
||||
subtype = data["bds09"].get_type()
|
||||
if subtype == 0:
|
||||
[velocity, heading, vert_spd, turnrate] = self.parseBDS09_0(data)
|
||||
[velocity, heading, vert_spd, turnrate] = air_modes.parseBDS09_0(data)
|
||||
elif subtype == 1:
|
||||
[velocity, heading, vert_spd] = self.parseBDS09_1(data)
|
||||
[velocity, heading, vert_spd] = air_modes.parseBDS09_1(data)
|
||||
turnrate = 0
|
||||
else:
|
||||
return
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
from PyQt4 import QtCore, QtGui
|
||||
import air_modes
|
||||
import threading, math, time
|
||||
from air_modes.exceptions import *
|
||||
|
||||
#fades the ICAOs out as their last report gets older,
|
||||
#and display ident if available, ICAO otherwise
|
||||
@@ -140,59 +141,61 @@ class dashboard_data_model(QtCore.QAbstractTableModel):
|
||||
self.endRemoveRows()
|
||||
self.lock.release()
|
||||
|
||||
class dashboard_output(air_modes.parse):
|
||||
def __init__(self, mypos, model):
|
||||
air_modes.parse.__init__(self, mypos)
|
||||
class dashboard_output:
|
||||
def __init__(self, cprdec, model, pub):
|
||||
self.model = model
|
||||
self._cpr = cprdec
|
||||
pub.subscribe("modes_dl", self.output)
|
||||
def output(self, msg):
|
||||
[data, ecc, reference, timestamp] = msg.split()
|
||||
data = air_modes.modes_reply(long(data, 16))
|
||||
ecc = long(ecc, 16)
|
||||
rssi = 10.*math.log10(float(reference))
|
||||
msgtype = data["df"]
|
||||
now = time.time()
|
||||
newrow = {"rssi": rssi, "seen": now}
|
||||
if msgtype in [0, 4, 20]:
|
||||
newrow["altitude"] = air_modes.altitude.decode_alt(data["ac"], True)
|
||||
newrow["icao"] = ecc
|
||||
self.model.addRecord(newrow)
|
||||
|
||||
elif msgtype == 17:
|
||||
icao = data["aa"]
|
||||
newrow["icao"] = icao
|
||||
subtype = data["ftc"]
|
||||
if subtype == 4:
|
||||
(ident, actype) = self.parseBDS08(data)
|
||||
newrow["ident"] = ident
|
||||
newrow["type"] = actype
|
||||
elif 5 <= subtype <= 8:
|
||||
(ground_track, decoded_lat, decoded_lon, rnge, bearing) = self.parseBDS06(data)
|
||||
newrow["heading"] = ground_track
|
||||
newrow["latitude"] = decoded_lat
|
||||
newrow["longitude"] = decoded_lon
|
||||
newrow["altitude"] = 0
|
||||
if rnge is not None:
|
||||
newrow["range"] = rnge
|
||||
newrow["bearing"] = bearing
|
||||
elif 9 <= subtype <= 18:
|
||||
(altitude, decoded_lat, decoded_lon, rnge, bearing) = self.parseBDS05(data)
|
||||
newrow["altitude"] = altitude
|
||||
newrow["latitude"] = decoded_lat
|
||||
newrow["longitude"] = decoded_lon
|
||||
if rnge is not None:
|
||||
newrow["range"] = rnge
|
||||
newrow["bearing"] = bearing
|
||||
elif subtype == 19:
|
||||
subsubtype = data["sub"]
|
||||
velocity = None
|
||||
heading = None
|
||||
vert_spd = None
|
||||
if subsubtype == 0:
|
||||
(velocity, heading, vert_spd) = self.parseBDS09_0(data)
|
||||
elif 1 <= subsubtype <= 2:
|
||||
(velocity, heading, vert_spd) = self.parseBDS09_1(data)
|
||||
newrow["speed"] = velocity
|
||||
newrow["heading"] = heading
|
||||
newrow["vertical"] = vert_spd
|
||||
try:
|
||||
msgtype = msg.data["df"]
|
||||
now = time.time()
|
||||
newrow = {"rssi": msg.rssi, "seen": now}
|
||||
if msgtype in [0, 4, 20]:
|
||||
newrow["altitude"] = air_modes.altitude.decode_alt(msg.data["ac"], True)
|
||||
newrow["icao"] = msg.ecc
|
||||
self.model.addRecord(newrow)
|
||||
|
||||
elif msgtype == 17:
|
||||
icao = msg.data["aa"]
|
||||
newrow["icao"] = icao
|
||||
subtype = msg.data["ftc"]
|
||||
if subtype == 4:
|
||||
(ident, actype) = air_modes.parseBDS08(msg.data)
|
||||
newrow["ident"] = ident
|
||||
newrow["type"] = actype
|
||||
elif 5 <= subtype <= 8:
|
||||
(ground_track, decoded_lat, decoded_lon, rnge, bearing) = air_modes.parseBDS06(msg.data, self._cpr)
|
||||
newrow["heading"] = ground_track
|
||||
newrow["latitude"] = decoded_lat
|
||||
newrow["longitude"] = decoded_lon
|
||||
newrow["altitude"] = 0
|
||||
if rnge is not None:
|
||||
newrow["range"] = rnge
|
||||
newrow["bearing"] = bearing
|
||||
elif 9 <= subtype <= 18:
|
||||
(altitude, decoded_lat, decoded_lon, rnge, bearing) = air_modes.parseBDS05(msg.data, self._cpr)
|
||||
newrow["altitude"] = altitude
|
||||
newrow["latitude"] = decoded_lat
|
||||
newrow["longitude"] = decoded_lon
|
||||
if rnge is not None:
|
||||
newrow["range"] = rnge
|
||||
newrow["bearing"] = bearing
|
||||
elif subtype == 19:
|
||||
subsubtype = msg.data["sub"]
|
||||
velocity = None
|
||||
heading = None
|
||||
vert_spd = None
|
||||
if subsubtype == 0:
|
||||
(velocity, heading, vert_spd) = air_modes.parseBDS09_0(msg.data)
|
||||
elif 1 <= subsubtype <= 2:
|
||||
(velocity, heading, vert_spd) = air_modes.parseBDS09_1(msg.data)
|
||||
newrow["speed"] = velocity
|
||||
newrow["heading"] = heading
|
||||
newrow["vertical"] = vert_spd
|
||||
|
||||
self.model.addRecord(newrow)
|
||||
|
||||
except ADSBError:
|
||||
return
|
||||
|
||||
self.model.addRecord(newrow)
|
||||
|
||||
@@ -31,19 +31,28 @@ class output_kml(threading.Thread):
|
||||
self._timeout = timeout
|
||||
self._lock = lock
|
||||
|
||||
self.done = False
|
||||
self.shutdown = threading.Event()
|
||||
self.finished = threading.Event()
|
||||
self.setDaemon(1)
|
||||
self.start()
|
||||
|
||||
def run(self):
|
||||
self._db = sqlite3.connect(self._dbname) #read from the db
|
||||
while self.done is False:
|
||||
while self.shutdown.is_set() is False:
|
||||
self.writekml()
|
||||
time.sleep(self._timeout)
|
||||
time.sleep(self._timeout)
|
||||
|
||||
self.done = True
|
||||
self._db.close()
|
||||
self._db = None
|
||||
self.finished.set()
|
||||
|
||||
def close(self):
|
||||
self.shutdown.set()
|
||||
self.finished.wait(0.2)
|
||||
#there's a bug here where self._timeout is long and close() has
|
||||
#to wait for the sleep to expire before closing. we just bail
|
||||
#instead with the 0.2 param above.
|
||||
|
||||
|
||||
def writekml(self):
|
||||
kmlstr = self.genkml()
|
||||
|
||||
@@ -25,204 +25,187 @@ 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)
|
||||
|
||||
def parse(self, message):
|
||||
[data, ecc, reference, timestamp] = message.split()
|
||||
|
||||
ecc = long(ecc, 16)
|
||||
reference = float(reference)
|
||||
timestamp = float(timestamp)
|
||||
|
||||
if reference == 0.0:
|
||||
refdb = -150.0
|
||||
else:
|
||||
refdb = 20.0*math.log10(reference)
|
||||
output = "(%.0f %.10f) " % (refdb, timestamp);
|
||||
|
||||
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):
|
||||
parsed = self.parse(msg)
|
||||
if parsed is not None:
|
||||
print self.parse(msg)
|
||||
|
||||
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)
|
||||
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 += " (speed %i-%ikt)" % (75 * (1 << (ri-10)), 75 * (1 << (ri-9)))
|
||||
|
||||
if vs is True:
|
||||
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)
|
||||
|
||||
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 print5(self, shortdata, ecc):
|
||||
[fs, dr, um, ident] = self.parse5(shortdata)
|
||||
|
||||
retstr = "Type 5 (short surveillance ident reply) from %x with ident %i" % (ecc, ident)
|
||||
|
||||
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):
|
||||
icao24 = data["aa"]
|
||||
bdsreg = data["me"].get_type()
|
||||
|
||||
retstr = None
|
||||
|
||||
if bdsreg == 0x08:
|
||||
(msg, typestring) = self.parseBDS08(data)
|
||||
retstr = "Type 17 BDS0,8 (ident) from %x type %s ident %s" % (icao24, typestring, msg)
|
||||
|
||||
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 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)
|
||||
#TODO get rid of class and convert to functions
|
||||
#no need for class here
|
||||
class output_print:
|
||||
def __init__(self, cpr, publisher):
|
||||
self._cpr = cpr
|
||||
#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))
|
||||
|
||||
publisher.subscribe("modes_dl", self.catch_nohandler)
|
||||
|
||||
@staticmethod
|
||||
def prefix(msg):
|
||||
return "(%i %.8f) " % (msg.rssi, msg.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 "aa" not in msg.data.fields:
|
||||
retstr += " from %.6x" % msg.ecc
|
||||
else:
|
||||
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)
|
||||
retstr += " from %.6x" % msg.data["aa"]
|
||||
print retstr
|
||||
|
||||
def handle0(self, msg):
|
||||
try:
|
||||
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:
|
||||
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 += " (speed %i-%ikt)" % (75 * (1 << (ri-10)), 75 * (1 << (ri-9)))
|
||||
else:
|
||||
raise ADSBError
|
||||
|
||||
except ADSBError:
|
||||
return
|
||||
|
||||
if msg.data["vs"] is 1:
|
||||
retstr += " (aircraft is on the ground)"
|
||||
|
||||
print retstr
|
||||
|
||||
@staticmethod
|
||||
def fs_text(fs):
|
||||
if fs == 1:
|
||||
return " (aircraft is on the ground)"
|
||||
elif fs == 2:
|
||||
return " (AIRBORNE ALERT)"
|
||||
elif fs == 3:
|
||||
return " (GROUND ALERT)"
|
||||
elif fs == 4:
|
||||
return " (SPI ALERT)"
|
||||
elif fs == 5:
|
||||
return " (SPI)"
|
||||
else:
|
||||
retstr = "Type 17 with FTC=%i from %x not implemented" % (data["ftc"], icao24)
|
||||
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 printTCAS(self, data, ecc):
|
||||
msgtype = data["df"]
|
||||
if msgtype == 20 or msgtype == 16:
|
||||
#type 16 does not have fs, dr, um but we get alt here
|
||||
[fs, dr, um, alt] = self.parse4(data)
|
||||
elif msgtype == 21:
|
||||
[fs, dr, um, ident] = self.parse5(data)
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
#the only one which requires state
|
||||
def handle17(self, msg):
|
||||
icao24 = msg.data["aa"]
|
||||
bdsreg = msg.data["me"].get_type()
|
||||
|
||||
retstr = output_print.prefix(msg)
|
||||
try:
|
||||
if bdsreg == 0x08:
|
||||
(ident, typestring) = air_modes.parseBDS08(msg.data)
|
||||
retstr += "Type 17 BDS0,8 (ident) from %x type %s ident %s" % (icao24, typestring, ident)
|
||||
|
||||
elif bdsreg == 0x06:
|
||||
[ground_track, decoded_lat, decoded_lon, rnge, bearing] = air_modes.parseBDS06(msg.data, self._cpr)
|
||||
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 bdsreg == 0x05:
|
||||
[altitude, decoded_lat, decoded_lon, rnge, bearing] = air_modes.parseBDS05(msg.data, self._cpr)
|
||||
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 = msg.data["bds09"].get_type()
|
||||
if subtype == 0:
|
||||
[velocity, heading, vert_spd, turnrate] = air_modes.parseBDS09_0(msg.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] = air_modes.parseBDS09_1(msg.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] = air_modes.parseBDS09_3(msg.data)
|
||||
retstr += "Type 17 BDS0,9-%i (air course report) from %x with %s %.0fkt magnetic heading %.0f VS %.0f geo. diff. from baro. alt. %.0fft" \
|
||||
% (subtype, icao24, vel_src, vel, mag_hdg, vert_spd, geo_diff)
|
||||
|
||||
else:
|
||||
retstr += "Type 17 BDS0,9-%i from %x not implemented" % (subtype, icao24)
|
||||
|
||||
elif bdsreg == 0x62:
|
||||
emerg_str = air_modes.parseBDS62(data)
|
||||
retstr += "Type 17 BDS6,2 (emergency) from %x type %s" % (icao24, emerg_str)
|
||||
|
||||
else:
|
||||
retstr += "Type 17 with FTC=%i from %x not implemented" % (msg.data["ftc"], icao24)
|
||||
except ADSBError:
|
||||
return
|
||||
|
||||
print retstr
|
||||
|
||||
def printTCAS(self, msg):
|
||||
msgtype = msg.data["df"]
|
||||
|
||||
if msgtype == 16:
|
||||
bds1 = data["vds1"]
|
||||
bds2 = data["vds2"]
|
||||
bds1 = msg.data["vds1"]
|
||||
bds2 = msg.data["vds2"]
|
||||
else:
|
||||
bds1 = data["bds1"]
|
||||
bds2 = data["bds2"]
|
||||
bds1 = msg.data["bds1"]
|
||||
bds2 = msg.data["bds2"]
|
||||
|
||||
retstr = output_print.prefix(msg)
|
||||
|
||||
if bds2 != 0:
|
||||
retstr = "No handler in type %i for BDS2 == %i from %x" % (msgtype, bds2, ecc)
|
||||
retstr += "No handler in type %i for BDS2 == %i from %x" % (msgtype, bds2, msg.ecc)
|
||||
|
||||
elif bds1 == 0:
|
||||
retstr = "No handler in type %i for BDS1 == 0 from %x" % (msgtype, ecc)
|
||||
retstr += "No handler in type %i for BDS1 == 0 from %x" % (msgtype, msg.ecc)
|
||||
elif bds1 == 1:
|
||||
retstr = "Type %i link capability report from %x: ACS: 0x%x, BCS: 0x%x, ECS: 0x%x, continues %i" \
|
||||
% (msgtype, ecc, data["acs"], data["bcs"], data["ecs"], data["cfs"])
|
||||
retstr += "Type %i link capability report from %x: ACS: 0x%x, BCS: 0x%x, ECS: 0x%x, continues %i" \
|
||||
% (msgtype, msg.ecc, msg.data["acs"], msg.data["bcs"], msg.data["ecs"], msg.data["cfs"])
|
||||
elif bds1 == 2:
|
||||
retstr = "Type %i identification from %x with text %s" % (msgtype, ecc, self.parseMB_id(data))
|
||||
retstr += "Type %i identification from %x with text %s" % (msgtype, msg.ecc, air_modes.parseMB_id(msg.data))
|
||||
elif bds1 == 3:
|
||||
retstr = "Type %i TCAS report from %x: " % (msgtype, ecc)
|
||||
tti = data["tti"]
|
||||
retstr += "Type %i TCAS report from %x: " % (msgtype, msg.ecc)
|
||||
tti = msg.data["tti"]
|
||||
if msgtype == 16:
|
||||
(resolutions, complements, rat, mte) = self.parse_TCAS_CRM(data)
|
||||
(resolutions, complements, rat, mte) = air_modes.parse_TCAS_CRM(msg.data)
|
||||
retstr += "advised: %s complement: %s" % (resolutions, complements)
|
||||
else:
|
||||
if tti == 1:
|
||||
(resolutions, complements, rat, mte, threat_id) = self.parseMB_TCAS_threatid(data)
|
||||
(resolutions, complements, rat, mte, threat_id) = air_modes.parseMB_TCAS_threatid(msg.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)
|
||||
(resolutions, complements, rat, mte, threat_alt, threat_range, threat_bearing) = air_modes.parseMB_TCAS_threatloc(msg.data)
|
||||
retstr += "range: %i bearing: %i alt: %i advised: %s complement: %s" % (threat_range, threat_bearing, threat_alt, resolutions, complements)
|
||||
else:
|
||||
rat = 0
|
||||
@@ -233,11 +216,15 @@ class output_print(air_modes.parse):
|
||||
if rat == 1:
|
||||
retstr += " (resolved)"
|
||||
else:
|
||||
retstr = "No handler for BDS1 == %i from %x" % (bds1, ecc)
|
||||
retstr += "No handler for type %i, BDS1 == %i from %x" % (msgtype, bds1, msg.ecc)
|
||||
|
||||
if(msgtype == 20 or msgtype == 16):
|
||||
retstr += " at %ift" % alt
|
||||
retstr += " at %ift" % air_modes.decode_alt(msg.data["ac"], True)
|
||||
else:
|
||||
retstr += " ident %x" % ident
|
||||
retstr += " ident %x" % air_modes.decode_id(msg.data["id"])
|
||||
|
||||
return retstr
|
||||
print retstr
|
||||
|
||||
handle16 = printTCAS
|
||||
handle20 = printTCAS
|
||||
handle21 = printTCAS
|
||||
|
||||
345
python/parse.py
345
python/parse.py
@@ -23,8 +23,8 @@ import time, os, sys
|
||||
from string import split, join
|
||||
from altitude import decode_alt
|
||||
import math
|
||||
import air_modes
|
||||
from air_modes.exceptions import *
|
||||
from air_modes import cpr
|
||||
|
||||
#this implements a packet class which can retrieve its own fields.
|
||||
class data_field:
|
||||
@@ -231,198 +231,207 @@ class modes_reply(data_field):
|
||||
def get_type(self):
|
||||
return self.get_bits(1,5)
|
||||
|
||||
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]
|
||||
#unscramble mode A/C-style squawk codes for type 5 replies below
|
||||
def decode_id(id):
|
||||
|
||||
C1 = 0x1000
|
||||
A1 = 0x0800
|
||||
C2 = 0x0400
|
||||
A2 = 0x0200 #this represents the order in which the bits come
|
||||
C4 = 0x0100
|
||||
A4 = 0x0080
|
||||
B1 = 0x0020
|
||||
D1 = 0x0010
|
||||
B2 = 0x0008
|
||||
D2 = 0x0004
|
||||
B4 = 0x0002
|
||||
D4 = 0x0001
|
||||
|
||||
a = ((id & A1) >> 11) + ((id & A2) >> 8) + ((id & A4) >> 5)
|
||||
b = ((id & B1) >> 5) + ((id & B2) >> 2) + ((id & B4) << 1)
|
||||
c = ((id & C1) >> 12) + ((id & C2) >> 9) + ((id & C4) >> 6)
|
||||
d = ((id & D1) >> 2) + ((id & D2) >> 1) + ((id & D4) << 2)
|
||||
|
||||
return (a * 1000) + (b * 100) + (c * 10) + d
|
||||
|
||||
def parse4(self, data):
|
||||
altitude = decode_alt(data["ac"], True)
|
||||
return [data["fs"], data["dr"], data["um"], altitude]
|
||||
#decode ident squawks
|
||||
def charmap(d):
|
||||
if d > 0 and d < 27:
|
||||
retval = chr(ord("A")+d-1)
|
||||
elif d == 32:
|
||||
retval = " "
|
||||
elif d > 47 and d < 58:
|
||||
retval = chr(ord("0")+d-48)
|
||||
else:
|
||||
retval = " "
|
||||
|
||||
def parse5(self, data):
|
||||
return [data["fs"], data["dr"], data["um"], data["id"]]
|
||||
|
||||
def parse11(self, data, ecc):
|
||||
interrogator = ecc & 0x0F
|
||||
return [data["aa"], interrogator, data["ca"]]
|
||||
return retval
|
||||
|
||||
def parseBDS08(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"]]
|
||||
|
||||
def parseBDS08(self, data):
|
||||
catstring = self.categories[data["ftc"]-1][data["cat"]]
|
||||
catstring = 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)
|
||||
msg = ""
|
||||
for i in range(0, 8):
|
||||
msg += charmap(data["ident"] >> (42-6*i) & 0x3F)
|
||||
return (msg, catstring)
|
||||
|
||||
def charmap(self, d):
|
||||
if d > 0 and d < 27:
|
||||
retval = chr(ord("A")+d-1)
|
||||
elif d == 32:
|
||||
retval = " "
|
||||
elif d > 47 and d < 58:
|
||||
retval = chr(ord("0")+d-48)
|
||||
else:
|
||||
retval = " "
|
||||
#NOTE: this is stateful -- requires CPR decoder
|
||||
def parseBDS05(data, cprdec):
|
||||
altitude = decode_alt(data["alt"], False)
|
||||
[decoded_lat, decoded_lon, rnge, bearing] = cprdec.decode(data["aa"], data["lat"], data["lon"], data["cpr"], 0)
|
||||
return [altitude, decoded_lat, decoded_lon, rnge, bearing]
|
||||
|
||||
return retval
|
||||
#NOTE: this is stateful -- requires CPR decoder
|
||||
def parseBDS06(data, cprdec):
|
||||
ground_track = data["gtk"] * 360. / 128
|
||||
[decoded_lat, decoded_lon, rnge, bearing] = cprdec.decode(data["aa"], data["lat"], data["lon"], data["cpr"], 1)
|
||||
return [ground_track, decoded_lat, decoded_lon, rnge, bearing]
|
||||
|
||||
def parseBDS05(self, data):
|
||||
icao24 = data["aa"]
|
||||
|
||||
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)
|
||||
|
||||
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"]
|
||||
ground_track = data["gtk"] * 360. / 128
|
||||
[decoded_lat, decoded_lon, rnge, bearing] = self.cpr.decode(icao24, encoded_lat, encoded_lon, cpr_format, 1)
|
||||
return [ground_track, decoded_lat, decoded_lon, rnge, bearing]
|
||||
|
||||
def parseBDS09_0(self, data):
|
||||
#0: ["sub", "dew", "vew", "dns", "vns", "str", "tr", "svr", "vr"],
|
||||
vert_spd = data["vr"] * 32
|
||||
ud = bool(data["dvr"])
|
||||
if ud:
|
||||
vert_spd = 0 - vert_spd
|
||||
turn_rate = data["tr"] * 15/62
|
||||
rl = data["str"]
|
||||
if rl:
|
||||
turn_rate = 0 - turn_rate
|
||||
ns_vel = data["vns"] - 1
|
||||
ns = bool(data["dns"])
|
||||
ew_vel = data["vew"] - 1
|
||||
ew = bool(data["dew"])
|
||||
def parseBDS09_0(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 = data["tr"] * 15/62
|
||||
rl = data["str"]
|
||||
if rl:
|
||||
turn_rate = 0 - turn_rate
|
||||
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
|
||||
if ns:
|
||||
ns_vel = 0 - ns_vel
|
||||
heading = math.atan2(ew_vel, ns_vel) * (180.0 / math.pi)
|
||||
if heading < 0:
|
||||
heading += 360
|
||||
velocity = math.hypot(ns_vel, ew_vel)
|
||||
if ew:
|
||||
ew_vel = 0 - ew_vel
|
||||
if ns:
|
||||
ns_vel = 0 - ns_vel
|
||||
heading = math.atan2(ew_vel, ns_vel) * (180.0 / math.pi)
|
||||
if heading < 0:
|
||||
heading += 360
|
||||
|
||||
return [velocity, heading, vert_spd, turn_rate]
|
||||
return [velocity, heading, vert_spd, turn_rate]
|
||||
|
||||
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(data["vr"] - 1) * 64
|
||||
ud = bool(data["dvr"])
|
||||
if ud:
|
||||
vert_spd = 0 - vert_spd
|
||||
vert_src = bool(data["vrsrc"])
|
||||
ns_vel = float(data["vns"])
|
||||
ns = bool(data["dns"])
|
||||
ew_vel = float(data["vew"])
|
||||
ew = bool(data["dew"])
|
||||
subtype = data["sub"]
|
||||
if subtype == 0x02:
|
||||
ns_vel <<= 2
|
||||
ew_vel <<= 2
|
||||
def parseBDS09_1(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(data["vr"] - 1) * 64
|
||||
ud = bool(data["dvr"])
|
||||
if ud:
|
||||
vert_spd = 0 - vert_spd
|
||||
vert_src = bool(data["vrsrc"])
|
||||
ns_vel = float(data["vns"])
|
||||
ns = bool(data["dns"])
|
||||
ew_vel = float(data["vew"])
|
||||
ew = bool(data["dew"])
|
||||
subtype = data["sub"]
|
||||
if subtype == 0x02:
|
||||
ns_vel <<= 2
|
||||
ew_vel <<= 2
|
||||
|
||||
velocity = math.hypot(ns_vel, ew_vel)
|
||||
if ew:
|
||||
ew_vel = 0 - ew_vel
|
||||
velocity = math.hypot(ns_vel, ew_vel)
|
||||
if ew:
|
||||
ew_vel = 0 - ew_vel
|
||||
|
||||
if ns_vel == 0:
|
||||
heading = 0
|
||||
else:
|
||||
heading = math.atan(float(ew_vel) / float(ns_vel)) * (180.0 / math.pi)
|
||||
if ns:
|
||||
heading = 180 - heading
|
||||
if heading < 0:
|
||||
heading += 360
|
||||
if ns_vel == 0:
|
||||
heading = 0
|
||||
else:
|
||||
heading = math.atan(float(ew_vel) / float(ns_vel)) * (180.0 / math.pi)
|
||||
if ns:
|
||||
heading = 180 - heading
|
||||
if heading < 0:
|
||||
heading += 360
|
||||
|
||||
return [velocity, heading, vert_spd]
|
||||
return [velocity, heading, vert_spd]
|
||||
|
||||
def parseBDS09_3(self, data):
|
||||
def parseBDS09_3(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]
|
||||
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 parseBDS62(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_id(data): #bds1 == 2, bds2 == 0
|
||||
msg = ""
|
||||
for i in range(0, 8):
|
||||
msg += 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"]
|
||||
rac = data["rac"]
|
||||
#check to see which bits are set
|
||||
resolutions = ""
|
||||
for bit in ara_bits:
|
||||
if ara & (1 << (54-bit)):
|
||||
resolutions += " " + ara_bits[bit]
|
||||
complements = ""
|
||||
for bit in rac_bits:
|
||||
if rac & (1 << (58-bit)):
|
||||
complements += " " + rac_bits[bit]
|
||||
return (resolutions, complements)
|
||||
def parseMB_TCAS_resolutions(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"]
|
||||
rac = data["rac"]
|
||||
#check to see which bits are set
|
||||
resolutions = ""
|
||||
for bit in ara_bits:
|
||||
if ara & (1 << (54-bit)):
|
||||
resolutions += " " + ara_bits[bit]
|
||||
complements = ""
|
||||
for bit in rac_bits:
|
||||
if rac & (1 << (58-bit)):
|
||||
complements += " " + rac_bits[bit]
|
||||
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"])
|
||||
#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(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) = 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"])
|
||||
def parseMB_TCAS_threatloc(data): #bds1==3, bds2==0, TTI==2
|
||||
(resolutions, complements) = parseMB_TCAS_resolutions(data)
|
||||
threat_alt = decode_alt(data["tida"], True)
|
||||
return (resolutions, complements, data["rat"], data["mte"], threat_alt, data["tidr"], data["tidb"])
|
||||
|
||||
#type 16 Coordination Reply Message
|
||||
def parse_TCAS_CRM(self, data):
|
||||
(resolutions, complements) = self.parseMB_TCAS_resolutions(data)
|
||||
return (resolutions, complements, data["rat"], data["mte"])
|
||||
#type 16 Coordination Reply Message
|
||||
def parse_TCAS_CRM(data):
|
||||
(resolutions, complements) = parseMB_TCAS_resolutions(data)
|
||||
return (resolutions, complements, data["rat"], data["mte"])
|
||||
|
||||
#this decorator takes a pubsub and returns a function which parses and publishes messages
|
||||
def make_parser(pub):
|
||||
publisher = pub
|
||||
def publish(message):
|
||||
[data, ecc, reference, timestamp] = message.split()
|
||||
try:
|
||||
ret = air_modes.modes_report(modes_reply(int(data, 16)),
|
||||
int(ecc, 16),
|
||||
20.0*math.log10(float(reference)),
|
||||
air_modes.stamp(0, float(timestamp)))
|
||||
pub["modes_dl"] = ret
|
||||
pub["type%i_dl" % ret.data.get_type()] = ret
|
||||
except ADSBError:
|
||||
pass
|
||||
|
||||
return publish
|
||||
|
||||
207
python/radio.py
Normal file
207
python/radio.py
Normal file
@@ -0,0 +1,207 @@
|
||||
# Copyright 2013 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.
|
||||
#
|
||||
|
||||
# Radio interface for Mode S RX.
|
||||
# Handles all hardware- and source-related functionality
|
||||
# You pass it options, it gives you data.
|
||||
# It uses the pubsub interface to allow clients to subscribe to its data feeds.
|
||||
|
||||
from gnuradio import gr, gru, optfir, eng_notation, blks2
|
||||
from gnuradio.eng_option import eng_option
|
||||
from gnuradio.gr.pubsub import pubsub
|
||||
from optparse import OptionParser, OptionGroup
|
||||
import air_modes
|
||||
import zmq
|
||||
import threading
|
||||
import time
|
||||
import re
|
||||
|
||||
class modes_radio (gr.top_block, pubsub):
|
||||
def __init__(self, options, context):
|
||||
gr.top_block.__init__(self)
|
||||
pubsub.__init__(self)
|
||||
self._options = options
|
||||
self._queue = gr.msg_queue()
|
||||
self._rate = int(options.rate)
|
||||
|
||||
self._resample = None
|
||||
self._setup_source(options)
|
||||
|
||||
self._rx_path = air_modes.rx_path(self._rate, options.threshold, self._queue, options.pmf)
|
||||
|
||||
#now subscribe to set various options via pubsub
|
||||
self.subscribe("freq", self.set_freq)
|
||||
self.subscribe("gain", self.set_gain)
|
||||
self.subscribe("rate", self.set_rate)
|
||||
self.subscribe("rate", self._rx_path.set_rate)
|
||||
self.subscribe("threshold", self._rx_path.set_threshold)
|
||||
self.subscribe("pmf", self._rx_path.set_pmf)
|
||||
|
||||
self.publish("freq", self.get_freq)
|
||||
self.publish("gain", self.get_gain)
|
||||
self.publish("rate", self.get_rate)
|
||||
self.publish("threshold", self._rx_path.get_threshold)
|
||||
self.publish("pmf", self._rx_path.get_pmf)
|
||||
|
||||
if self._resample is not None:
|
||||
self.connect(self._u, self._resample, self._rx_path)
|
||||
else:
|
||||
self.connect(self._u, self._rx_path)
|
||||
|
||||
#Publish messages when they come back off the queue
|
||||
server_addr = ["inproc://modes-radio-pub"]
|
||||
if options.tcp is not None:
|
||||
server_addr += ["tcp://*:%i" % options.tcp]
|
||||
|
||||
self._sender = air_modes.zmq_pubsub_iface(context, subaddr=None, pubaddr=server_addr)
|
||||
self._async_sender = gru.msgq_runner(self._queue, self.send)
|
||||
|
||||
def send(self, msg):
|
||||
self._sender["dl_data"] = msg.to_string()
|
||||
|
||||
@staticmethod
|
||||
def add_radio_options(parser):
|
||||
group = OptionGroup(parser, "Receiver setup options")
|
||||
|
||||
#Choose source
|
||||
group.add_option("-s","--source", type="string", default="uhd",
|
||||
help="Choose source: uhd, osmocom, <filename>, or <ip:port> [default=%default]")
|
||||
group.add_option("-t","--tcp", type="int", default=None, metavar="PORT",
|
||||
help="Open a TCP server on this port to publish reports")
|
||||
|
||||
#UHD/Osmocom args
|
||||
group.add_option("-R", "--subdev", type="string",
|
||||
help="select USRP Rx side A or B", metavar="SUBDEV")
|
||||
group.add_option("-A", "--antenna", type="string",
|
||||
help="select which antenna to use on daughterboard")
|
||||
group.add_option("-D", "--args", type="string",
|
||||
help="arguments to pass to radio constructor", default="")
|
||||
group.add_option("-f", "--freq", type="eng_float", default=1090e6,
|
||||
help="set receive frequency in Hz [default=%default]", metavar="FREQ")
|
||||
group.add_option("-g", "--gain", type="int", default=None,
|
||||
help="set RF gain", metavar="dB")
|
||||
|
||||
#RX path args
|
||||
group.add_option("-r", "--rate", type="eng_float", default=4e6,
|
||||
help="set sample rate [default=%default]")
|
||||
group.add_option("-T", "--threshold", type="eng_float", default=5.0,
|
||||
help="set pulse detection threshold above noise in dB [default=%default]")
|
||||
group.add_option("-p","--pmf", action="store_true", default=False,
|
||||
help="Use pulse matched filtering [default=%default]")
|
||||
|
||||
parser.add_option_group(group)
|
||||
|
||||
def live_source(self):
|
||||
return self._options.source is 'uhd' or self._options.source is 'osmocom'
|
||||
|
||||
def set_freq(self, freq):
|
||||
return self._u.set_center_freq(freq, 0) if live_source() else 0
|
||||
|
||||
def set_gain(self, gain):
|
||||
return self._u.set_gain(gain) if live_source() else 0
|
||||
|
||||
def set_rate(self, rate):
|
||||
return self._u.set_rate(rate) if live_source() else 0
|
||||
|
||||
def get_freq(self, freq):
|
||||
return self._u.get_center_freq(freq, 0) if live_source() else 1090e6
|
||||
|
||||
def get_gain(self, gain):
|
||||
return self._u.get_gain() if live_source() else 0
|
||||
|
||||
def get_rate(self, rate):
|
||||
return self._u.get_rate() if live_source() else self._rate
|
||||
|
||||
def _setup_source(self, options):
|
||||
if options.source == "uhd":
|
||||
#UHD source by default
|
||||
from gnuradio import uhd
|
||||
self._u = uhd.single_usrp_source(options.args, uhd.io_type_t.COMPLEX_FLOAT32, 1)
|
||||
|
||||
if(options.subdev):
|
||||
self._u.set_subdev_spec(options.subdev, 0)
|
||||
|
||||
if not self._u.set_center_freq(options.freq):
|
||||
print "Failed to set initial frequency"
|
||||
|
||||
#check for GPSDO
|
||||
#if you have a GPSDO, UHD will automatically set the timestamp to UTC time
|
||||
#as well as automatically set the clock to lock to GPSDO.
|
||||
if self._u.get_time_source(0) != 'gpsdo':
|
||||
self._u.set_time_now(uhd.time_spec(0.0))
|
||||
|
||||
if options.antenna is not None:
|
||||
self._u.set_antenna(options.antenna)
|
||||
|
||||
self._u.set_samp_rate(options.rate)
|
||||
options.rate = int(self._u.get_samp_rate()) #retrieve actual
|
||||
|
||||
if options.gain is None: #set to halfway
|
||||
g = self._u.get_gain_range()
|
||||
options.gain = (g.start()+g.stop()) / 2.0
|
||||
|
||||
print "Setting gain to %i" % options.gain
|
||||
self._u.set_gain(options.gain)
|
||||
print "Gain is %i" % self._u.get_gain()
|
||||
|
||||
#TODO: detect if you're using an RTLSDR or Jawbreaker
|
||||
#and set up accordingly.
|
||||
#ALSO TODO: Actually set gain appropriately using gain bins in HackRF driver.
|
||||
#osmocom doesn't have gain bucket distribution like UHD does
|
||||
elif options.source == "osmocom": #RTLSDR dongle or HackRF Jawbreaker
|
||||
import osmosdr
|
||||
self._u = osmosdr.source_c(options.args)
|
||||
# self._u.set_sample_rate(3.2e6) #fixed for RTL dongles
|
||||
self._u.set_sample_rate(options.rate)
|
||||
if not self._u.set_center_freq(options.freq):
|
||||
print "Failed to set initial frequency"
|
||||
|
||||
self._u.set_gain_mode(0) #manual gain mode
|
||||
if options.gain is None:
|
||||
options.gain = 34
|
||||
###DO NOT COMMIT
|
||||
self._u.set_gain(14, "RF", 0)
|
||||
self._u.set_gain(40, "IF", 0)
|
||||
#self._u.set_gain(14, "BB", 0)
|
||||
###DO NOT COMMIT
|
||||
# self._u.set_gain(options.gain)
|
||||
print "Gain is %i" % self._u.get_gain()
|
||||
|
||||
#Note: this should only come into play if using an RTLSDR.
|
||||
# lpfiltcoeffs = gr.firdes.low_pass(1, 5*3.2e6, 1.6e6, 300e3)
|
||||
# self._resample = blks2.rational_resampler_ccf(interpolation=5, decimation=4, taps=lpfiltcoeffs)
|
||||
|
||||
else:
|
||||
#semantically detect whether it's ip.ip.ip.ip:port or filename
|
||||
if ':' in options.source:
|
||||
try:
|
||||
ip, port = re.search("(.*)\:(\d{1,5})", options.source).groups()
|
||||
except:
|
||||
raise Exception("Please input UDP source e.g. 192.168.10.1:12345")
|
||||
self._u = gr.udp_source(gr.sizeof_gr_complex, ip, int(port))
|
||||
print "Using UDP source %s:%s" % (ip, port)
|
||||
else:
|
||||
self._u = gr.file_source(gr.sizeof_gr_complex, options.source)
|
||||
print "Using file source %s" % options.source
|
||||
|
||||
print "Rate is %i" % (options.rate,)
|
||||
|
||||
def close(self):
|
||||
self._sender.close()
|
||||
@@ -1,5 +1,5 @@
|
||||
#
|
||||
# Copyright 2012 Corgan Labs
|
||||
# Copyright 2012, 2013 Corgan Labs, Nick Foster
|
||||
#
|
||||
# This file is part of gr-air-modes
|
||||
#
|
||||
@@ -40,12 +40,12 @@ class rx_path(gr.hier_block2):
|
||||
|
||||
# Pulse matched filter for 0.5us pulses
|
||||
if use_pmf:
|
||||
self._pmf = gr.moving_average_ff(self._spc, 1.0/self._spc, self._rate)
|
||||
self._pmf = gr.moving_average_ff(self._spc, 1.0/self._spc)#, self._rate)
|
||||
self.connect(self._demod, self._pmf)
|
||||
self._bb = self._pmf
|
||||
|
||||
# Establish baseline amplitude (noise, interference)
|
||||
self._avg = gr.moving_average_ff(48*self._spc, 1.0/(48*self._spc), self._rate) # 3 preambles
|
||||
self._avg = gr.moving_average_ff(48*self._spc, 1.0/(48*self._spc))#, self._rate) # 3 preambles
|
||||
|
||||
# Synchronize to Mode-S preamble
|
||||
self._sync = air_modes_swig.modes_preamble(self._rate, self._threshold)
|
||||
@@ -58,3 +58,25 @@ class rx_path(gr.hier_block2):
|
||||
self.connect(self._bb, (self._sync, 0))
|
||||
self.connect(self._bb, self._avg, (self._sync, 1))
|
||||
self.connect(self._sync, self._slicer)
|
||||
|
||||
def set_rate(self, rate):
|
||||
self._sync.set_rate(rate)
|
||||
self._slicer.set_rate(rate)
|
||||
self._spc = int(rate/2e6)
|
||||
self._avg.set_length_and_scale(48*self._spc, 1.0/(48*self._spc))
|
||||
if self._bb != self._demod:
|
||||
self._pmf.set_length_and_scale(self._spc, 1.0/self._spc)
|
||||
|
||||
def set_threshold(self, threshold):
|
||||
self._sync.set_threshold(threshold)
|
||||
|
||||
def set_pmf(self, pmf):
|
||||
#TODO must be done when top block is stopped
|
||||
pass
|
||||
|
||||
def get_pmf(self, pmf):
|
||||
return not (self._bb == self._demod)
|
||||
|
||||
def get_threshold(self, threshold):
|
||||
return self._sync.get_threshold()
|
||||
|
||||
|
||||
@@ -25,10 +25,30 @@ from string import split, join
|
||||
import air_modes
|
||||
from datetime import *
|
||||
from air_modes.exceptions import *
|
||||
import threading
|
||||
|
||||
class output_sbs1(air_modes.parse):
|
||||
def __init__(self, mypos, port):
|
||||
air_modes.parse.__init__(self, mypos)
|
||||
class dumb_task_runner(threading.Thread):
|
||||
def __init__(self, task, interval):
|
||||
threading.Thread.__init__(self)
|
||||
self._task = task
|
||||
self._interval = interval
|
||||
self.shutdown = threading.Event()
|
||||
self.finished = threading.Event()
|
||||
self.setDaemon(True)
|
||||
self.start()
|
||||
|
||||
def run(self):
|
||||
while not self.shutdown.is_set():
|
||||
self._task()
|
||||
time.sleep(self._interval)
|
||||
self.finished.set()
|
||||
|
||||
def close(self):
|
||||
self.shutdown.set()
|
||||
self.finished.wait(self._interval)
|
||||
|
||||
class output_sbs1:
|
||||
def __init__(self, cprdec, port, pub):
|
||||
self._s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
self._s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||
self._s.bind(('', port))
|
||||
@@ -38,6 +58,16 @@ class output_sbs1(air_modes.parse):
|
||||
self._aircraft_id_map = {} # dictionary of icao24 to aircraft IDs
|
||||
self._aircraft_id_count = 0 # Current Aircraft ID count
|
||||
|
||||
self._cpr = cprdec
|
||||
|
||||
#it could be cleaner if there were separate output_* fns
|
||||
#but this works
|
||||
for i in (0, 4, 5, 11, 17):
|
||||
pub.subscribe("type%i_dl" % i, output)
|
||||
|
||||
#spawn thread to add new connections as they come in
|
||||
self._runner = dumb_task_runner(self.add_pending_conns, 0.1)
|
||||
|
||||
def __del__(self):
|
||||
self._s.close()
|
||||
|
||||
@@ -94,41 +124,36 @@ class output_sbs1(air_modes.parse):
|
||||
elif fs == 3:
|
||||
return "1,0,0,1"
|
||||
elif fs == 4:
|
||||
return "1,0,0,"
|
||||
return "1,0,1,"
|
||||
elif fs == 5:
|
||||
return "0,0,0,"
|
||||
return "0,0,1,"
|
||||
else:
|
||||
return ",,,"
|
||||
|
||||
def parse(self, message):
|
||||
def parse(self, msg):
|
||||
#assembles a SBS-1-style output string from the received message
|
||||
|
||||
[data, ecc, reference, timestamp] = message.split()
|
||||
|
||||
data = air_modes.modes_reply(long(data, 16))
|
||||
ecc = long(ecc, 16)
|
||||
msgtype = data["df"]
|
||||
msgtype = msg.data["df"]
|
||||
outmsg = None
|
||||
|
||||
if msgtype == 0:
|
||||
outmsg = self.pp0(data, ecc)
|
||||
outmsg = self.pp0(msg.data, msg.ecc)
|
||||
elif msgtype == 4:
|
||||
outmsg = self.pp4(data, ecc)
|
||||
outmsg = self.pp4(msg.data, msg.ecc)
|
||||
elif msgtype == 5:
|
||||
outmsg = self.pp5(data, ecc)
|
||||
outmsg = self.pp5(msg.data, msg.ecc)
|
||||
elif msgtype == 11:
|
||||
outmsg = self.pp11(data, ecc)
|
||||
outmsg = self.pp11(msg.data, msg.ecc)
|
||||
elif msgtype == 17:
|
||||
outmsg = self.pp17(data)
|
||||
outmsg = self.pp17(msg.data)
|
||||
else:
|
||||
raise NoHandlerError(msgtype)
|
||||
return outmsg
|
||||
|
||||
def pp0(self, shortdata, ecc):
|
||||
[datestr, timestr] = self.current_time()
|
||||
[vs, cc, sl, ri, altitude] = self.parse0(shortdata)
|
||||
aircraft_id = self.get_aircraft_id(ecc)
|
||||
retstr = "MSG,7,0,%i,%06X,%i,%s,%s,%s,%s,,%s,,,,,,,,,," % (aircraft_id, ecc, aircraft_id+100, datestr, timestr, datestr, timestr, altitude)
|
||||
retstr = "MSG,7,0,%i,%06X,%i,%s,%s,%s,%s,,%s,,,,,,,,,," % (aircraft_id, ecc, aircraft_id+100, datestr, timestr, datestr, timestr, air_modes.decode_alt(shortdata["ac"], True))
|
||||
if vs:
|
||||
retstr += "1\r\n"
|
||||
else:
|
||||
@@ -137,24 +162,20 @@ class output_sbs1(air_modes.parse):
|
||||
|
||||
def pp4(self, shortdata, ecc):
|
||||
[datestr, timestr] = self.current_time()
|
||||
[fs, dr, um, altitude] = self.parse4(shortdata)
|
||||
aircraft_id = self.get_aircraft_id(ecc)
|
||||
retstr = "MSG,5,0,%i,%06X,%i,%s,%s,%s,%s,,%s,,,,,,," % (aircraft_id, ecc, aircraft_id+100, datestr, timestr, datestr, timestr, altitude)
|
||||
return retstr + self.decode_fs(fs) + "\r\n"
|
||||
retstr = "MSG,5,0,%i,%06X,%i,%s,%s,%s,%s,,%s,,,,,,," % (aircraft_id, ecc, aircraft_id+100, datestr, timestr, datestr, timestr, air_modes.decode_alt(shortdata["ac"], True))
|
||||
return retstr + self.decode_fs(shortdata["fs"]) + "\r\n"
|
||||
|
||||
def pp5(self, shortdata, ecc):
|
||||
# I'm not sure what to do with the identiifcation shortdata & 0x1FFF
|
||||
[datestr, timestr] = self.current_time()
|
||||
[fs, dr, um, ident] = self.parse5(shortdata)
|
||||
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) + "\r\n"
|
||||
retstr = "MSG,6,0,%i,%06X,%i,%s,%s,%s,%s,,,,,,,,%04i," % (aircraft_id, ecc, aircraft_id+100, datestr, timestr, datestr, timestr, air_modes.decode_id(shortdata["id"]))
|
||||
return retstr + self.decode_fs(shortdata["fs"]) + "\r\n"
|
||||
|
||||
def pp11(self, shortdata, ecc):
|
||||
[datestr, timestr] = self.current_time()
|
||||
[icao24, interrogator, ca] = self.parse11(shortdata, ecc)
|
||||
aircraft_id = self.get_aircraft_id(icao24)
|
||||
return "MSG,8,0,%i,%06X,%i,%s,%s,%s,%s,,,,,,,,,,,,\r\n" % (aircraft_id, icao24, aircraft_id+100, datestr, timestr, datestr, timestr)
|
||||
return "MSG,8,0,%i,%06X,%i,%s,%s,%s,%s,,,,,,,,,,,,\r\n" % (aircraft_id, shortdata["aa"], aircraft_id+100, datestr, timestr, datestr, timestr)
|
||||
|
||||
def pp17(self, data):
|
||||
icao24 = data["aa"]
|
||||
@@ -168,12 +189,12 @@ class output_sbs1(air_modes.parse):
|
||||
|
||||
if bdsreg == 0x08:
|
||||
# Aircraft Identification
|
||||
(msg, typestring) = self.parseBDS08(data)
|
||||
(msg, typestring) = air_modes.parseBDS08(data)
|
||||
retstr = "MSG,1,0,%i,%06X,%i,%s,%s,%s,%s,%s,,,,,,,,,,,\r\n" % (aircraft_id, icao24, aircraft_id+100, datestr, timestr, datestr, timestr, msg)
|
||||
|
||||
elif bdsreg == 0x06:
|
||||
# Surface position measurement
|
||||
[ground_track, decoded_lat, decoded_lon, rnge, bearing] = self.parseBDS06(data)
|
||||
[ground_track, decoded_lat, decoded_lon, rnge, bearing] = air_modes.parseBDS06(data, self._cpr)
|
||||
altitude = 0
|
||||
if decoded_lat is None: #no unambiguously valid position available
|
||||
retstr = None
|
||||
@@ -183,7 +204,7 @@ class output_sbs1(air_modes.parse):
|
||||
elif bdsreg == 0x05:
|
||||
# Airborne position measurements
|
||||
# WRONG (rnge, bearing), is this still true?
|
||||
[altitude, decoded_lat, decoded_lon, rnge, bearing] = self.parseBDS05(data)
|
||||
[altitude, decoded_lat, decoded_lon, rnge, bearing] = air_modes.parseBDS05(data, self._cpr)
|
||||
if decoded_lat is None: #no unambiguously valid position available
|
||||
retstr = None
|
||||
else:
|
||||
@@ -194,7 +215,7 @@ class output_sbs1(air_modes.parse):
|
||||
# WRONG (heading, vert_spd), Is this still true?
|
||||
subtype = data["bds09"].get_type()
|
||||
if subtype == 0 or subtype == 1:
|
||||
parser = self.parseBDS09_0 if subtype == 0 else self.parseBDS09_1
|
||||
parser = air_modes.parseBDS09_0 if subtype == 0 else air_modes.parseBDS09_1
|
||||
[velocity, heading, vert_spd] = parser(data)
|
||||
retstr = "MSG,4,0,%i,%06X,%i,%s,%s,%s,%s,,,%.1f,%.1f,,,%i,,,,,\r\n" % (aircraft_id, icao24, aircraft_id+100, datestr, timestr, datestr, timestr, velocity, heading, vert_spd)
|
||||
|
||||
|
||||
147
python/sql.py
147
python/sql.py
@@ -24,119 +24,114 @@ from string import split, join
|
||||
import air_modes
|
||||
import sqlite3
|
||||
from air_modes.exceptions import *
|
||||
from gnuradio.gr.pubsub import pubsub
|
||||
|
||||
class output_sql(air_modes.parse):
|
||||
def __init__(self, mypos, filename, lock):
|
||||
air_modes.parse.__init__(self, mypos)
|
||||
class output_sql:
|
||||
def __init__(self, cpr, filename, lock, publisher):
|
||||
#pubsub.__init__(self)
|
||||
self._cpr = cpr
|
||||
self._lock = lock;
|
||||
#create the database
|
||||
self.filename = filename
|
||||
self._db = sqlite3.connect(filename)
|
||||
#now execute a schema to create the tables you need
|
||||
c = self._db.cursor()
|
||||
query = """CREATE TABLE IF NOT EXISTS "positions" (
|
||||
"icao" INTEGER KEY NOT NULL,
|
||||
"seen" TEXT NOT NULL,
|
||||
"alt" INTEGER,
|
||||
"lat" REAL,
|
||||
"lon" REAL
|
||||
);"""
|
||||
c.execute(query)
|
||||
query = """CREATE TABLE IF NOT EXISTS "vectors" (
|
||||
"icao" INTEGER KEY NOT NULL,
|
||||
"seen" TEXT NOT NULL,
|
||||
"speed" REAL,
|
||||
"heading" REAL,
|
||||
"vertical" REAL
|
||||
);"""
|
||||
c.execute(query)
|
||||
query = """CREATE TABLE IF NOT EXISTS "ident" (
|
||||
"icao" INTEGER PRIMARY KEY NOT NULL,
|
||||
"ident" TEXT NOT NULL,
|
||||
"type" TEXT NOT NULL
|
||||
);"""
|
||||
c.execute(query)
|
||||
c.close()
|
||||
self._db.commit()
|
||||
#we close the db conn now to reopen it in the output() thread context.
|
||||
self._db.close()
|
||||
self._db = None
|
||||
publisher.subscribe("type17_dl", self.insert)
|
||||
|
||||
self._lock = lock
|
||||
with self._lock:
|
||||
#create the database
|
||||
self.filename = filename
|
||||
self.db = sqlite3.connect(filename)
|
||||
#now execute a schema to create the tables you need
|
||||
c = self.db.cursor()
|
||||
query = """CREATE TABLE IF NOT EXISTS "positions" (
|
||||
"icao" INTEGER KEY NOT NULL,
|
||||
"seen" TEXT NOT NULL,
|
||||
"alt" INTEGER,
|
||||
"lat" REAL,
|
||||
"lon" REAL
|
||||
);"""
|
||||
c.execute(query)
|
||||
query = """CREATE TABLE IF NOT EXISTS "vectors" (
|
||||
"icao" INTEGER KEY NOT NULL,
|
||||
"seen" TEXT NOT NULL,
|
||||
"speed" REAL,
|
||||
"heading" REAL,
|
||||
"vertical" REAL
|
||||
);"""
|
||||
c.execute(query)
|
||||
query = """CREATE TABLE IF NOT EXISTS "ident" (
|
||||
"icao" INTEGER PRIMARY KEY NOT NULL,
|
||||
"ident" TEXT NOT NULL
|
||||
);"""
|
||||
c.execute(query)
|
||||
c.close()
|
||||
self.db.commit()
|
||||
#we close the db conn now to reopen it in the output() thread context.
|
||||
self.db.close()
|
||||
self.db = None
|
||||
|
||||
def __del__(self):
|
||||
self.db = None
|
||||
|
||||
def output(self, message):
|
||||
def insert(self, message):
|
||||
with self._lock:
|
||||
try:
|
||||
#we're checking to see if the db is empty, and creating the db object
|
||||
#if it is. the reason for this is so that the db writing is done within
|
||||
#the thread context of output(), rather than the thread context of the
|
||||
#constructor. that way you can spawn a thread to do output().
|
||||
if self.db is None:
|
||||
self.db = sqlite3.connect(self.filename)
|
||||
#constructor.
|
||||
if self._db is None:
|
||||
self._db = sqlite3.connect(self.filename)
|
||||
|
||||
query = self.make_insert_query(message)
|
||||
if query is not None:
|
||||
c = self.db.cursor()
|
||||
c = self._db.cursor()
|
||||
c.execute(query)
|
||||
c.close()
|
||||
self.db.commit()
|
||||
# self._db.commit()
|
||||
|
||||
except ADSBError:
|
||||
pass
|
||||
|
||||
def make_insert_query(self, message):
|
||||
def make_insert_query(self, msg):
|
||||
#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
|
||||
[data, ecc, reference, timestamp] = message.split()
|
||||
|
||||
data = air_modes.modes_reply(long(data, 16))
|
||||
ecc = long(ecc, 16)
|
||||
# reference = float(reference)
|
||||
|
||||
|
||||
query = None
|
||||
msgtype = data["df"]
|
||||
msgtype = msg.data["df"]
|
||||
if msgtype == 17:
|
||||
query = self.sql17(data)
|
||||
query = self.sql17(msg.data)
|
||||
#self["new_adsb"] = data["aa"] #publish change notification
|
||||
|
||||
return query
|
||||
|
||||
#TODO: if there's a way to publish selective reports on upsert to distinguish,
|
||||
#for instance, between a new ICAO that's just been heard, and a refresh of an
|
||||
#existing ICAO, both of those would be useful publishers for the GUI model.
|
||||
#otherwise, worst-case you can just refresh everything every time a report
|
||||
#comes in, but it's going to use more CPU. Not likely a problem if you're only
|
||||
#looking at ADS-B (no mode S) data.
|
||||
#It's probably time to look back at the Qt SQL table model and see if it can be
|
||||
#bent into shape for you.
|
||||
def sql17(self, data):
|
||||
icao24 = data["aa"]
|
||||
bdsreg = data["me"].get_type()
|
||||
|
||||
retstr = None
|
||||
#self["bds%.2i" % bdsreg] = icao24 #publish under "bds08", "bds06", etc.
|
||||
|
||||
if bdsreg == 0x08:
|
||||
(msg, typename) = self.parseBDS08(data)
|
||||
retstr = "INSERT OR REPLACE INTO ident (icao, ident) VALUES (" + "%i" % icao24 + ", '" + msg + "')"
|
||||
|
||||
(msg, typename) = air_modes.parseBDS08(data)
|
||||
return "INSERT OR REPLACE INTO ident (icao, ident, type) VALUES (" + "%i" % icao24 + ", '" + msg + "', '" + typename + "')"
|
||||
elif bdsreg == 0x06:
|
||||
[ground_track, decoded_lat, decoded_lon, rnge, bearing] = self.parseBDS06(data)
|
||||
[ground_track, decoded_lat, decoded_lon, rnge, bearing] = air_modes.parseBDS06(data, self._cpr)
|
||||
altitude = 0
|
||||
if decoded_lat is None: #no unambiguously valid position available
|
||||
retstr = None
|
||||
raise CPRNoPositionError
|
||||
else:
|
||||
retstr = "INSERT INTO positions (icao, seen, alt, lat, lon) VALUES (" + "%i" % icao24 + ", datetime('now'), " + str(altitude) + ", " + "%.6f" % decoded_lat + ", " + "%.6f" % decoded_lon + ")"
|
||||
|
||||
return "INSERT INTO positions (icao, seen, alt, lat, lon) VALUES (" + "%i" % icao24 + ", datetime('now'), " + str(altitude) + ", " + "%.6f" % decoded_lat + ", " + "%.6f" % decoded_lon + ")"
|
||||
elif bdsreg == 0x05:
|
||||
[altitude, decoded_lat, decoded_lon, rnge, bearing] = self.parseBDS05(data)
|
||||
[altitude, decoded_lat, decoded_lon, rnge, bearing] = air_modes.parseBDS05(data, self._cpr)
|
||||
if decoded_lat is None: #no unambiguously valid position available
|
||||
retstr = None
|
||||
raise CPRNoPositionError
|
||||
else:
|
||||
retstr = "INSERT INTO positions (icao, seen, alt, lat, lon) VALUES (" + "%i" % icao24 + ", datetime('now'), " + str(altitude) + ", " + "%.6f" % decoded_lat + ", " + "%.6f" % decoded_lon + ")"
|
||||
|
||||
return "INSERT INTO positions (icao, seen, alt, lat, lon) VALUES (" + "%i" % icao24 + ", datetime('now'), " + str(altitude) + ", " + "%.6f" % decoded_lat + ", " + "%.6f" % decoded_lon + ")"
|
||||
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 + ")"
|
||||
[velocity, heading, vert_spd, turnrate] = air_modes.parseBDS09_0(data)
|
||||
return "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 + ")"
|
||||
[velocity, heading, vert_spd] = air_modes.parseBDS09_1(data)
|
||||
return "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
|
||||
raise NoHandlerError
|
||||
|
||||
107
python/types.py
Normal file
107
python/types.py
Normal file
@@ -0,0 +1,107 @@
|
||||
#
|
||||
# Copyright 2013 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.
|
||||
#
|
||||
|
||||
from collections import namedtuple
|
||||
|
||||
#this is a timestamp that preserves precision when used with UTC timestamps.
|
||||
#ordinary double-precision timestamps lose significant fractional precision
|
||||
#when the exponent is as large as necessary for UTC.
|
||||
class stamp:
|
||||
def __init__(self, secs, frac_secs):
|
||||
self.secs = secs
|
||||
self.frac_secs = frac_secs
|
||||
self.secs += int(self.frac_secs)
|
||||
self.frac_secs -= int(self.frac_secs)
|
||||
def __lt__(self, other):
|
||||
if isinstance(other, self.__class__):
|
||||
if self.secs == other.secs:
|
||||
return self.frac_secs < other.frac_secs
|
||||
else:
|
||||
return self.secs < other.secs
|
||||
elif isinstance(other, float):
|
||||
return float(self) > other
|
||||
else:
|
||||
raise TypeError
|
||||
def __gt__(self, other):
|
||||
if type(other) is type(self):
|
||||
if self.secs == other.secs:
|
||||
return self.frac_secs > other.frac_secs
|
||||
else:
|
||||
return self.secs > other.secs
|
||||
elif type(other) is type(float):
|
||||
return float(self) > other
|
||||
else:
|
||||
raise TypeError
|
||||
def __eq__(self, other):
|
||||
if isinstance(other, self.__class__):
|
||||
return self.secs == other.secs and self.frac_secs == other.frac_secs
|
||||
elif isinstance(other, float):
|
||||
return float(self) == other
|
||||
else:
|
||||
raise TypeError
|
||||
def __ne__(self, other):
|
||||
return not (self == other)
|
||||
def __le__(self, other):
|
||||
return (self == other) or (self < other)
|
||||
def __ge__(self, other):
|
||||
return (self == other) or (self > other)
|
||||
|
||||
def __add__(self, other):
|
||||
if isinstance(other, self.__class__):
|
||||
ipart = self.secs + other.secs
|
||||
fpart = self.frac_secs + other.frac_secs
|
||||
return stamp(ipart, fpart)
|
||||
elif isinstance(other, float):
|
||||
return self + stamp(0, other)
|
||||
elif isinstance(other, int):
|
||||
return self + stamp(other, 0)
|
||||
else:
|
||||
raise TypeError
|
||||
|
||||
def __sub__(self, other):
|
||||
if isinstance(other, self.__class__):
|
||||
ipart = self.secs - other.secs
|
||||
fpart = self.frac_secs - other.frac_secs
|
||||
return stamp(ipart, fpart)
|
||||
elif isinstance(other, float):
|
||||
return self - stamp(0, other)
|
||||
elif isinstance(other, int):
|
||||
return self - stamp(other, 0)
|
||||
else:
|
||||
raise TypeError
|
||||
|
||||
#to ensure we don't hash by stamp
|
||||
#TODO fixme with a reasonable hash in case you feel like you'd hash by stamp
|
||||
__hash__ = None
|
||||
|
||||
#good to within ms for comparison
|
||||
def __float__(self):
|
||||
return self.secs + self.frac_secs
|
||||
|
||||
def __str__(self):
|
||||
return "%f" % float(self)
|
||||
|
||||
#a Mode S report including the modes_reply data object
|
||||
modes_report = namedtuple('modes_report', ['data', 'ecc', 'rssi', 'timestamp'])
|
||||
#lat, lon, alt
|
||||
#TODO: a position class internally represented as ECEF XYZ which can easily be used for multilateration and distance calculation
|
||||
llh = namedtuple('llh', ['lat', 'lon', 'alt'])
|
||||
mlat_report = namedtuple('mlat_report', ['data', 'nreps', 'timestamp', 'llh', 'hdop', 'vdop'])
|
||||
140
python/zmq_socket.py
Normal file
140
python/zmq_socket.py
Normal file
@@ -0,0 +1,140 @@
|
||||
# Copyright 2013 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.
|
||||
|
||||
#this serves as a bridge between ZMQ subscriber and the GR pubsub callbacks interface
|
||||
#creates a thread, publishes socket data to pubsub subscribers
|
||||
#just a convenient way to create an aggregating socket with callbacks on receive
|
||||
#can use this for inproc:// signalling with minimal overhead
|
||||
#not sure if it's a good idea to use this yet
|
||||
|
||||
import time
|
||||
import threading
|
||||
import zmq
|
||||
from gnuradio.gr.pubsub import pubsub
|
||||
import Queue
|
||||
|
||||
class zmq_pubsub_iface(threading.Thread):
|
||||
def __init__(self, context, subaddr=None, pubaddr=None):
|
||||
threading.Thread.__init__(self)
|
||||
#private data
|
||||
self._queue = Queue.Queue()
|
||||
self._subsocket = context.socket(zmq.SUB)
|
||||
self._pubsocket = context.socket(zmq.PUB)
|
||||
self._subaddr = subaddr
|
||||
self._pubaddr = pubaddr
|
||||
if type(self._subaddr) is str:
|
||||
self._subaddr = [self._subaddr]
|
||||
if type(self._pubaddr) is str:
|
||||
self._pubaddr = [self._pubaddr]
|
||||
self._sub_connected = False
|
||||
self._pubsub = pubsub()
|
||||
if self._pubaddr is not None:
|
||||
for addr in self._pubaddr:
|
||||
self._pubsocket.bind(addr)
|
||||
|
||||
self._poller = zmq.Poller()
|
||||
self._poller.register(self._subsocket, zmq.POLLIN)
|
||||
|
||||
#public data
|
||||
self.shutdown = threading.Event()
|
||||
self.finished = threading.Event()
|
||||
#init
|
||||
self.setDaemon(True)
|
||||
self.start()
|
||||
|
||||
def subscribe(self, key, subscriber):
|
||||
if not self._sub_connected:
|
||||
if not self._subaddr:
|
||||
raise Exception("No subscriber address set")
|
||||
for addr in self._subaddr:
|
||||
self._subsocket.connect(addr)
|
||||
self._sub_connected = True
|
||||
self._subsocket.setsockopt(zmq.SUBSCRIBE, key)
|
||||
self._pubsub.subscribe(key, subscriber)
|
||||
|
||||
def unsubscribe(self, key, subscriber):
|
||||
self._subsocket.setsockopt(zmq.UNSUBSCRIBE, key)
|
||||
self._pubsub.unsubscribe(key, subscriber)
|
||||
|
||||
#executed from the thread context(s) of the caller(s)
|
||||
#so we use a queue to push sending into the run loop
|
||||
#since sockets must be used in the thread they were created in
|
||||
def __setitem__(self, key, val):
|
||||
if not self._pubaddr:
|
||||
raise Exception("No publisher address set")
|
||||
if not self.shutdown.is_set():
|
||||
self._queue.put([key, val])
|
||||
|
||||
def __getitem__(self, key):
|
||||
return self._pubsub[key]
|
||||
|
||||
def run(self):
|
||||
done = False
|
||||
while not self.shutdown.is_set() and not done:
|
||||
if self.shutdown.is_set():
|
||||
done = True
|
||||
#send
|
||||
while not self._queue.empty():
|
||||
self._pubsocket.send_multipart(self._queue.get())
|
||||
#receive
|
||||
if self._sub_connected:
|
||||
socks = dict(self._poller.poll(timeout=0))
|
||||
while self._subsocket in socks \
|
||||
and socks[self._subsocket] == zmq.POLLIN:
|
||||
[address, msg] = self._subsocket.recv_multipart()
|
||||
self._pubsub[address] = msg
|
||||
socks = dict(self._poller.poll(timeout=0))
|
||||
#snooze
|
||||
if not done:
|
||||
time.sleep(0.1)
|
||||
|
||||
self._subsocket.close()
|
||||
self._pubsocket.close()
|
||||
self.finished.set()
|
||||
|
||||
def close(self):
|
||||
self.shutdown.set()
|
||||
#self._queue.join() #why does this block forever
|
||||
self.finished.wait(0.2)
|
||||
|
||||
def pr(x):
|
||||
print x
|
||||
|
||||
if __name__ == "__main__":
|
||||
#create socket pair
|
||||
context = zmq.Context(1)
|
||||
sock1 = zmq_pubsub_iface(context, subaddr="inproc://sock2-pub", pubaddr="inproc://sock1-pub")
|
||||
sock2 = zmq_pubsub_iface(context, subaddr="inproc://sock1-pub", pubaddr=["inproc://sock2-pub", "tcp://*:5433"])
|
||||
sock3 = zmq_pubsub_iface(context, subaddr="tcp://localhost:5433", pubaddr=None)
|
||||
|
||||
sock1.subscribe("data1", pr)
|
||||
sock2.subscribe("data2", pr)
|
||||
sock3.subscribe("data3", pr)
|
||||
|
||||
for i in range(10):
|
||||
sock1["data2"] = "HOWDY"
|
||||
sock2["data3"] = "DRAW"
|
||||
sock2["data1"] = "PARDNER"
|
||||
time.sleep(0.1)
|
||||
|
||||
time.sleep(0.1)
|
||||
|
||||
sock1.close()
|
||||
sock2.close()
|
||||
sock3.close()
|
||||
@@ -1,4 +1,4 @@
|
||||
# Copyright 2011 Free Software Foundation, Inc.
|
||||
# Copyright 2011,2013 Free Software Foundation, Inc.
|
||||
#
|
||||
# This file is part of GNU Radio
|
||||
#
|
||||
@@ -31,14 +31,17 @@ if(NOT PYQT4_FOUND)
|
||||
return()
|
||||
endif()
|
||||
|
||||
if(NOT PYUIC4_FOUND)
|
||||
message(STATUS "pyuic4 not found, not installing GUI application")
|
||||
return()
|
||||
endif()
|
||||
|
||||
find_package(Qwt)
|
||||
if(NOT QWT_FOUND)
|
||||
message(STATUS "Qwt not found, not installing GUI application")
|
||||
return()
|
||||
endif()
|
||||
|
||||
|
||||
set(PYUIC4_COMPILE pyuic4)
|
||||
set(RX_UI_SRC ${CMAKE_CURRENT_SOURCE_DIR}/modes_rx.ui)
|
||||
set(RX_UI_PY_PRE_MASSAGE ${CMAKE_CURRENT_BINARY_DIR}/modes_rx_ui_borked.py)
|
||||
set(RX_UI_PY ${CMAKE_CURRENT_BINARY_DIR}/modes_rx_ui.py)
|
||||
@@ -49,7 +52,7 @@ add_custom_target(rx_ui ALL
|
||||
)
|
||||
|
||||
add_custom_command(OUTPUT ${RX_UI_PY_PRE_MASSAGE}
|
||||
COMMAND ${PYUIC4_COMPILE} ${RX_UI_SRC} > ${RX_UI_PY_PRE_MASSAGE}
|
||||
COMMAND ${PYUIC4_EXECUTABLE} ${RX_UI_SRC} > ${RX_UI_PY_PRE_MASSAGE}
|
||||
MAIN_DEPENDENCY ${RX_UI_SRC}
|
||||
)
|
||||
|
||||
|
||||
@@ -365,7 +365,7 @@
|
||||
</rect>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Raw</string>
|
||||
<string>TCP</string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QLineEdit" name="line_kmlfilename">
|
||||
|
||||
@@ -23,6 +23,9 @@ air_modes_preamble_sptr air_make_modes_preamble (int channel_rate, float thresho
|
||||
|
||||
class air_modes_preamble : public gr_sync_block
|
||||
{
|
||||
set_rate(int channel_rate);
|
||||
set_threshold(float threshold_db);
|
||||
int get_threshold(void);
|
||||
private:
|
||||
air_modes_preamble (int channel_rate, float threshold_db);
|
||||
};
|
||||
@@ -33,8 +36,9 @@ air_modes_slicer_sptr air_make_modes_slicer (int channel_rate, gr_msg_queue_sptr
|
||||
|
||||
class air_modes_slicer : public gr_block
|
||||
{
|
||||
set_rate(int channel_rate);
|
||||
private:
|
||||
air_modes_slicer (int channel_rate, gr_msg_queue_sptr queue);
|
||||
air_modes_slicer (int channel_rate, gr_msg_queue_sptr queue);
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user