58 Commits

Author SHA1 Message Date
Nick Foster
c2d0ca6051 Temp commit. Not yet working. 2013-07-21 17:57:37 -07:00
Nick Foster
33349efd7f GUI interface still not quite working. Two issues:
* PyQt4.QtSql's SQLite interface doesn't appear to return the same results as the SQLite browser. This is probably me depending on a bad data ordering assumption.
* QSqlQueryModel isn't set up to have the db change from underneath it, AFAIK -- have to add the ability to notify it there's new data.
2013-07-21 17:41:26 -07:00
Nick Foster
dd3e1fe629 GUI model ripped up and replaced with QSqlQueryModel. Not working but committing before I kill the child in row 15. 2013-07-21 17:41:26 -07:00
Nick Foster
a09c5add43 Set default UI tab to setup. 2013-07-18 18:43:29 -07:00
Nick Foster
498cea34b2 Better sample rate selection. 2013-07-18 18:31:10 -07:00
Nick Foster
4174658f0d Fixed GUI live print and reports/sec widget. 2013-07-18 17:46:50 -07:00
Nick Foster
b594fe2799 Mapview: added highlighting of selected aircraft 2013-07-18 09:45:09 -07:00
Nick Foster
7fef37d34d Merge branch 'gr3.7' into mapview 2013-07-17 21:46:55 -07:00
Nick Foster
2deefdf310 Remove unnecessary imports from modes_gui. 2013-07-17 21:46:25 -07:00
Nick Foster
37aa74fbe0 Merge branch 'gr3.7' into mapview
Conflicts:
	python/radio.py
2013-07-17 21:42:48 -07:00
Nick Foster
d71e6bc1e7 Interim commit. 2013-07-17 21:41:55 -07:00
Nick Foster
0ce6374656 Convert next branch to 3.7 API. Based on Johnathan Corgan's 3.7 conversion of
the master branch.
2013-07-17 18:03:45 -07:00
Nick Foster
2766107a76 Small changes to map view. WebKit won't render files w/o .htm[l] extension so using a named temp file is out. 2013-06-21 16:28:56 -07:00
Nick Foster
55cd17de67 Added support for integrated Google Maps interface via QWebView/JavaScript/JSONP. Broken due to something hairy wrt QWebView and /tmp. 2013-06-20 23:05:41 -07:00
Nick Foster
fbe3c464fb GUI working again w/new parser setup. Live print isn't working due to use of print instead of return. 2013-06-19 11:24:11 -07:00
Nick Foster
12c09ba1df Fix TCAS printing. 2013-06-18 22:03:37 -07:00
Nick Foster
a7af518653 Move parser factory decorator into parse.py. Fix multiple bugs in parse. 2013-06-18 21:49:07 -07:00
Nick Foster
302fa7203d Left a debug print in there 2013-06-18 19:09:49 -07:00
Nick Foster
e18a2e460c Fix SQL bug introduced with AC type addition 2013-06-18 19:09:09 -07:00
Nick Foster
f8f08ecd37 Flightgear plugin modified for new parser interface. 2013-06-18 19:06:30 -07:00
Nick Foster
9563972591 Moved SBS1, az_map, and SQL modules to new parser interface. Not tested. 2013-06-18 19:02:22 -07:00
Nick Foster
72ae3abf12 Forgot to add types.py (from the mlat_server branch). 2013-06-18 18:11:13 -07:00
Nick Foster
d84c0c3204 Parser works for the print case. Not quite sure this is the best way to do it, but it's better. 2013-06-18 17:47:13 -07:00
Nick Foster
a1e2297134 Progress toward rewriting the parser to be less insane. 2013-06-18 17:34:11 -07:00
Nick Foster
230356bcaa Derp bug in hacked Jawbreaker gain. 2013-06-18 15:50:06 -07:00
Nick Foster
29f8a2c1b4 Add try/catch around az_map's parsing. 2013-06-18 11:45:58 -07:00
Nick Foster
d508b39b31 Fix modes_gui. Only thing which should be nonfunc. is the reports/sec box (no thread to run it). 2013-06-10 13:37:50 -04:00
Nick Foster
bed2aa499e Re-remove the RTLSDR interpolating filter. Still need to distinguish HackRF vs. RTL-SDR sources. 2013-06-10 11:24:42 -04:00
Nick Foster
244c9105f2 Remove leftover arg from sql constructor 2013-06-10 08:52:28 -04:00
Nick Foster
1880126100 Move --tcp to radio. 2013-06-10 08:52:11 -04:00
Nick Foster
34939bba52 Whoops 2013-06-10 08:37:28 -04:00
Nick Foster
fd6ee2ce89 Change reference output so it now outputs SNR, which is much more useful. 2013-06-10 07:47:42 -04:00
Nick Foster
c0543923f6 Used wrong gain bin for HackRF source -- this is still a TODO 2013-06-08 16:53:06 -04:00
Nick Foster
1cb8c726ed Add default for --source option 2013-06-08 16:48:14 -04:00
Nick Foster
b5e3964d12 Add radio options to separate option group 2013-06-08 16:12:38 -04:00
Nick Foster
94af9fac48 Add back in the SBS1 interface 2013-06-08 16:06:42 -04:00
Nick Foster
d2a6f40bbd Clean up logic in zmq_socket and don't repeat code 2013-06-08 16:06:16 -04:00
Nick Foster
51cb2bdf46 Cleanup and remove dead/obsolete code. 2013-06-08 15:42:22 -04:00
Nick Foster
798d5e15c9 Rework options in radio.py for cleaner cmdline interface 2013-06-08 15:25:28 -04:00
Nick Foster
4bbe250f39 Publish some SQL notifications. Still TODO: issue list of new ICAOs. Might use a separate thread/publisher for that. 2013-06-05 18:30:45 -04:00
Nick Foster
cfab7123cc Non-functional cosmetic changes. 2013-06-05 18:18:18 -04:00
Nick Foster
a847f5f875 Fix introduced CPU consumption bug. 2013-06-05 17:50:25 -04:00
Nick Foster
79aee53a52 Subscribe to *all* the servers. 2013-06-05 16:05:14 -04:00
Nick Foster
f62813f039 Add getters/setters in preamble/slicer, bring them out to radio.py via pubsub. 2013-06-05 15:56:09 -04:00
Nick Foster
2ace332b89 Fix broken msgq assumption 2013-06-03 09:29:08 -04:00
Nick Foster
9dad60303a update socks in receive loop 2013-06-03 09:19:49 -04:00
Nick Foster
4fe2334b28 Don't need locks for queue inserts 2013-06-03 09:17:17 -04:00
Nick Foster
ba55d24e92 Don't use commit() on each SQL insert, it makes things terrislow. 2013-06-03 09:07:36 -04:00
Nick Foster
b71c978e27 New universal pubsub interface in zmq_socket.py. Needs more work. 2013-06-03 08:38:26 -04:00
Nick Foster
33874893b7 Better exception handling in sql.py 2013-05-30 17:17:15 -04:00
Nick Foster
4216b96262 Threading fixes for ZMQ work. Also moved radio optparse options into radio.py. 2013-05-30 00:58:03 -07:00
Nick Foster
841e2aaa04 Add checking for 0MQ library to CMake 2013-05-29 14:57:51 -07:00
Nick Foster
4d569f9112 In progress, temp commit. Have removed pubsub interface in favor of 0MQ sockets -- this gets us free message-passing across network or local host. 2013-05-29 14:18:15 -07:00
Nick Foster
db34eca30e Refactored modes_rx to use a more modular radio interface (radio.py) using a pubsub pattern to formalize the old "outputs" interface I was using. Should make it easier to reuse the radio interface. 2013-05-27 19:50:42 -07:00
Nick Foster
3e9854f337 Move Boost finding into module from Gnuradio 2013-05-07 13:05:52 -07:00
Nicholas Corgan
4e5fb40531 Some Windows fixes 2013-04-01 15:51:00 -07:00
Mattias Schäfer
f25d21f505 cpr.set_location lacked self arg 2013-01-22 08:07:46 -08:00
Stephan Ruloff
f4fbd25bb0 Add type 5 squawk ID decoding to parser, fix some SBS-1 outputs. 2013-01-08 17:25:28 -08:00
36 changed files with 1726 additions and 1211 deletions

View File

@@ -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
@@ -85,15 +69,10 @@ set(GRC_BLOCKS_DIR ${GR_PKG_DATA_DIR}/grc/blocks)
########################################################################
# Find gnuradio build dependencies
########################################################################
find_package(Gruel)
find_package(GnuradioCore)
find_package(GnuradioRuntime)
if(NOT GRUEL_FOUND)
message(FATAL_ERROR "Gruel required to compile gr-air-modes")
endif()
if(NOT GNURADIO_CORE_FOUND)
message(FATAL_ERROR "GnuRadio Core required to compile gr-air-modes")
if(NOT GNURADIO_RUNTIME_FOUND)
message(FATAL_ERROR "GnuRadio Runtime required to compile gr-air-modes")
endif()
########################################################################
@@ -102,14 +81,12 @@ endif()
include_directories(
${CMAKE_SOURCE_DIR}/include
${Boost_INCLUDE_DIRS}
${GRUEL_INCLUDE_DIRS}
${GNURADIO_CORE_INCLUDE_DIRS}
${GNURADIO_RUNTIME_INCLUDE_DIRS}
)
link_directories(
${Boost_LIBRARY_DIRS}
${GRUEL_LIBRARY_DIRS}
${GNURADIO_CORE_LIBRARY_DIRS}
${GNURADIO_RUNTIME_LIBRARY_DIRS}
)
# Set component parameters

View File

@@ -19,16 +19,19 @@
# Boston, MA 02110-1301, USA.
#
import os, sys, time, threading, datetime, math, csv
from PyQt4 import QtCore,QtGui
import os, sys, time, threading, datetime, math, csv, tempfile
from optparse import OptionParser
from PyQt4 import QtCore,QtGui,QtWebKit
from PyQt4.Qwt5 import Qwt
from gnuradio import gr, gru, optfir, eng_notation, blks2
import gnuradio.gr.gr_threading as _threading
from gnuradio import gr, eng_notation
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"))
@@ -70,18 +73,16 @@ class mainwindow(QtGui.QMainWindow):
#disable by default
self.ui.check_adsbonly.setCheckState(QtCore.Qt.Unchecked)
#set up the radio stuff
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.dbname = "adsb.db"
self.num_reports = 0
self.last_report = 0
self.context = zmq.Context(1)
self.datamodel = dashboard_data_model(None)
self.datamodel = dashboard_sql_model(None)
self.ui.list_aircraft.setModel(self.datamodel)
self.ui.list_aircraft.setModelColumn(0)
@@ -95,15 +96,16 @@ class mainwindow(QtGui.QMainWindow):
self.dashboard_mapper.setModel(self.datamodel)
self.dashboard_mapper.addMapping(self.ui.line_icao, 0)
#self.dashboard_mapper.addMapping(self.ui.prog_rssi, 2)
self.dashboard_mapper.addMapping(self.ui.line_latitude, 3)
self.dashboard_mapper.addMapping(self.ui.line_longitude, 4)
self.dashboard_mapper.addMapping(self.ui.line_alt, 5)
self.dashboard_mapper.addMapping(self.ui.line_speed, 6)
#self.dashboard_mapper.addMapping(self.ui.compass_heading, 7)
self.dashboard_mapper.addMapping(self.ui.line_climb, 8)
self.dashboard_mapper.addMapping(self.ui.line_ident, 9)
self.dashboard_mapper.addMapping(self.ui.line_type, 10)
self.dashboard_mapper.addMapping(self.ui.line_range, 11)
self.dashboard_mapper.addMapping(self.ui.line_latitude, 2)
self.dashboard_mapper.addMapping(self.ui.line_longitude, 3)
self.dashboard_mapper.addMapping(self.ui.line_alt, 4)
self.dashboard_mapper.addMapping(self.ui.line_speed, 5)
#self.dashboard_mapper.addMapping(self.ui.compass_heading, 6)
self.dashboard_mapper.addMapping(self.ui.line_climb, 7)
self.dashboard_mapper.addMapping(self.ui.line_ident, 8)
self.dashboard_mapper.addMapping(self.ui.line_type, 9)
#self.dashboard_mapper.addMapping(self.ui.line_range, 11)
#self.dashboard_mapper.addMapping(self.ui.compass_bearing, 12)
compass_palette = QtGui.QPalette()
compass_palette.setColor(QtGui.QPalette.Foreground, QtCore.Qt.white)
@@ -113,12 +115,14 @@ class mainwindow(QtGui.QMainWindow):
self.ui.compass_heading.setNeedle(Qwt.QwtDialSimpleNeedle(Qwt.QwtDialSimpleNeedle.Ray, False, QtCore.Qt.black))
self.ui.compass_bearing.setNeedle(Qwt.QwtDialSimpleNeedle(Qwt.QwtDialSimpleNeedle.Ray, False, QtCore.Qt.black))
#hook up the update signal
#hook up the update signals which are explicitly necessary
#most of the dashboard_mapper and list_aircraft stuff is implicitly done already
self.ui.list_aircraft.selectionModel().currentRowChanged.connect(self.dashboard_mapper.setCurrentModelIndex)
self.ui.list_aircraft.selectionModel().currentRowChanged.connect(self.update_heading_widget)
self.ui.list_aircraft.selectionModel().currentRowChanged.connect(self.update_bearing_widget)
self.datamodel.dataChanged.connect(self.unmapped_widgets_dataChanged)
self.ui.list_aircraft.selectionModel().currentRowChanged.connect(self.update_rssi_widget)
self.ui.list_aircraft.selectionModel().currentRowChanged.connect(self.update_map_highlight)
self.datamodel.dataChanged.connect(self.unmapped_widgets_dataChanged)
#hook up live data text box update signal
self.live_data_changed_signal.connect(self.on_append_live_data)
@@ -126,29 +130,29 @@ class mainwindow(QtGui.QMainWindow):
############ widget update functions for non-mapped widgets ############
def update_heading_widget(self, index):
if index.model() is not None:
heading = index.model().data(index.model().index(index.row(), self.datamodel._colnames.index("heading"))).toDouble()[0]
heading = index.model().data(index.model().index(index.row(), 6)).toDouble()[0]
self.ui.compass_heading.setValue(heading)
def update_bearing_widget(self, index):
if index.model() is not None:
bearing = index.model().data(index.model().index(index.row(), self.datamodel._colnames.index("bearing"))).toDouble()[0]
bearing = 0#index.model().data(index.model().index(index.row(), 12)).toDouble()[0]
self.ui.compass_bearing.setValue(bearing)
def unmapped_widgets_dataChanged(self, startIndex, endIndex):
index = self.ui.list_aircraft.selectionModel().currentIndex()
if index.row() in range(startIndex.row(), endIndex.row()+1): #the current aircraft was affected
if self.datamodel._colnames.index("heading") in range(startIndex.column(), endIndex.column()+1):
if 6 in range(startIndex.column(), endIndex.column()+1):
self.update_heading_widget(index)
if self.datamodel._colnames.index("bearing") in range(startIndex.column(), endIndex.column()+1):
if 12 in range(startIndex.column(), endIndex.column()+1):
self.update_bearing_widget(index)
if self.datamodel._colnames.index("rssi") in range(startIndex.column(), endIndex.column()+1):
if 2 in range(startIndex.column(), endIndex.column()+1):
self.update_rssi_widget(index)
def update_rssi_widget(self, index):
if index.model() is not None:
rssi = index.model().data(index.model().index(index.row(), 2)).toDouble()[0]
rssi = 0#index.model().data(index.model().index(index.row(), 2)).toDouble()[0]
self.ui.prog_rssi.setValue(rssi)
def increment_reportspersec(self, msg):
self.num_reports += 1
@@ -159,6 +163,12 @@ class mainwindow(QtGui.QMainWindow):
self.ui.line_reports.setText("%i" % self.num_reports)
self.num_reports = 0
def update_map_highlight(self, index):
if index.model() is not None:
icaostr = index.model().data(index.model().index(index.row(), self.datamodel._colnames.index("icao"))).toString()
icao = int(str(icaostr), 16)
self.jsonpgen.set_highlight(icao)
##################### dynamic option population ########################
#goes and gets valid antenna, sample rate options from the device and grays out appropriate things
def populate_source_options(self):
@@ -166,12 +176,13 @@ class mainwindow(QtGui.QMainWindow):
self.rates = []
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 and rate >= 4e6]
self.antennas = self.src.get_antennas()
self.src = None #deconstruct UHD source for now
self.ui.combo_ant.setEnabled(True)
@@ -183,16 +194,26 @@ class mainwindow(QtGui.QMainWindow):
self.ui.combo_ant.setEnabled(False)
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 == "File":
self.rates = [2e6, 4e6, 6e6, 8e6, 10e6]
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/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)
@@ -206,8 +227,12 @@ class mainwindow(QtGui.QMainWindow):
self.ui.combo_ant.clear()
self.ui.combo_ant.addItems(self.antennas)
if 4e6 in self.rates:
self.ui.combo_rate.setCurrentIndex(self.rates.index(4e6))
#set up recommended sample rate
recommended_rate = min(x for x in self.rates if x >= 4e6 and
max(self.rates) % x == 0)
if recommended_rate >= 8.e6:
self.ui.check_pmf.setChecked(True)
self.ui.combo_rate.setCurrentIndex(self.rates.index(recommended_rate))
################ action handlers ####################
def on_combo_source_currentIndexChanged(self, index):
@@ -215,47 +240,47 @@ 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 +290,61 @@ 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)
#add output for live data box
self.outputs.append(self.output_live_data)
#set up map
#NOTE this is busted on windows. WebKit requires .htm[l] extensions to render,
#so using a temp file doesn't work.
self._htmlfile = open("/tmp/mode_s.html", 'wb+')#tempfile.NamedTemporaryFile()
self._jsonfile = tempfile.NamedTemporaryFile()
self.livedata = air_modes.output_print(self._cpr_dec,
self._publisher,
self.live_data_changed_signal.emit)
#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)
self.jsonpgen = air_modes.output_jsonp(self._jsonfile.name, self.dbname, my_position, self.lock, timeout=1)
htmlstring = air_modes.html_template(my_position, self._jsonfile.name)
self._htmlfile.write(htmlstring)
self._htmlfile.flush()
class WebPage(QtWebKit.QWebPage):
def javaScriptConsoleMessage(self, msg, line, source):
print '%s line %d: %s' % (source, line, msg)
page = WebPage()
self.ui.mapView.setPage(page)
self.ui.mapView.load( QtCore.QUrl( QtCore.QUrl.fromLocalFile("/tmp/mode_s.html") ) )
self.ui.mapView.show()
#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._rps_timer = QtCore.QTimer()
self._rps_timer.timeout.connect(self.update_reportspersec)
self._rps_timer.start(1000)
#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
self._rps_timer = None
try:
self.kmlgen.done = True
#TODO FIXME need a way to kill kmlgen safely without delay
@@ -331,109 +362,10 @@ class mainwindow(QtGui.QMainWindow):
cursor.movePosition(QtGui.QTextCursor.Start)
cursor.select(QtGui.QTextCursor.LineUnderCursor)
cursor.removeSelectedText()
self.ui.text_livedata.append(msgstr)
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)
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
window = mainwindow()

View File

@@ -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()

View File

@@ -1,26 +0,0 @@
INCLUDE(FindPkgConfig)
PKG_CHECK_MODULES(PC_GNURADIO_CORE gnuradio-core)
FIND_PATH(
GNURADIO_CORE_INCLUDE_DIRS
NAMES gr_random.h
HINTS $ENV{GNURADIO_CORE_DIR}/include/gnuradio
${PC_GNURADIO_CORE_INCLUDEDIR}
PATHS /usr/local/include/gnuradio
/usr/include/gnuradio
)
FIND_LIBRARY(
GNURADIO_CORE_LIBRARIES
NAMES gnuradio-core
HINTS $ENV{GNURADIO_CORE_DIR}/lib
${PC_GNURADIO_CORE_LIBDIR}
PATHS /usr/local/lib
/usr/local/lib64
/usr/lib
/usr/lib64
)
INCLUDE(FindPackageHandleStandardArgs)
FIND_PACKAGE_HANDLE_STANDARD_ARGS(GNURADIO_CORE DEFAULT_MSG GNURADIO_CORE_LIBRARIES GNURADIO_CORE_INCLUDE_DIRS)
MARK_AS_ADVANCED(GNURADIO_CORE_LIBRARIES GNURADIO_CORE_INCLUDE_DIRS)

View File

@@ -0,0 +1,6 @@
INCLUDE(FindPkgConfig)
PKG_CHECK_MODULES(GNURADIO_RUNTIME gnuradio-runtime)
INCLUDE(FindPackageHandleStandardArgs)
FIND_PACKAGE_HANDLE_STANDARD_ARGS(GNURADIO_RUNTIME DEFAULT_MSG GNURADIO_RUNTIME_LIBRARIES GNURADIO_RUNTIME_INCLUDE_DIRS)
MARK_AS_ADVANCED(GNURADIO_RUNTIME_LIBRARIES GNURADIO_RUNTIME_INCLUDE_DIRS)

View File

@@ -1,26 +0,0 @@
INCLUDE(FindPkgConfig)
PKG_CHECK_MODULES(PC_GRUEL gnuradio-core)
FIND_PATH(
GRUEL_INCLUDE_DIRS
NAMES gruel/attributes.h
HINTS $ENV{GRUEL_DIR}/include
${PC_GRUEL_INCLUDEDIR}
PATHS /usr/local/include
/usr/include
)
FIND_LIBRARY(
GRUEL_LIBRARIES
NAMES gruel
HINTS $ENV{GRUEL_DIR}/lib
${PC_GRUEL_LIBDIR}
PATHS /usr/local/lib
/usr/local/lib64
/usr/lib
/usr/lib64
)
INCLUDE(FindPackageHandleStandardArgs)
FIND_PACKAGE_HANDLE_STANDARD_ARGS(GRUEL DEFAULT_MSG GRUEL_LIBRARIES GRUEL_INCLUDE_DIRS)
MARK_AS_ADVANCED(GRUEL_LIBRARIES GRUEL_INCLUDE_DIRS)

View File

@@ -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)

View 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)

View 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)

View File

@@ -22,7 +22,7 @@
#ifndef INCLUDED_AIR_MODES_API_H
#define INCLUDED_AIR_MODES_API_H
#include <gruel/attributes.h>
#include <gnuradio/attributes.h>
#ifdef AIR_MODES_EXPORTS
# define AIR_MODES_API __GR_ATTR_EXPORT

View File

@@ -23,7 +23,7 @@
#ifndef INCLUDED_AIR_MODES_PREAMBLE_H
#define INCLUDED_AIR_MODES_PREAMBLE_H
#include <gr_block.h>
#include <gnuradio/block.h>
#include <air_modes_api.h>
class air_modes_preamble;
@@ -35,7 +35,7 @@ AIR_MODES_API air_modes_preamble_sptr air_make_modes_preamble(int channel_rate,
* \brief mode select preamble detection
* \ingroup block
*/
class AIR_MODES_API air_modes_preamble : public gr_block
class AIR_MODES_API air_modes_preamble : public gr::block
{
private:
friend air_modes_preamble_sptr air_make_modes_preamble(int channel_rate, float threshold_db);
@@ -49,7 +49,7 @@ private:
float d_threshold_db;
float d_threshold;
pmt::pmt_t d_me, d_key;
gr_tag_t d_timestamp;
gr::tag_t d_timestamp;
double d_secs_per_sample;
public:
@@ -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 */

View File

@@ -23,30 +23,30 @@
#ifndef INCLUDED_AIR_MODES_slicer_H
#define INCLUDED_AIR_MODES_slicer_H
#include <gr_sync_block.h>
#include <gr_msg_queue.h>
#include <gnuradio/sync_block.h>
#include <gnuradio/msg_queue.h>
#include <air_modes_api.h>
class air_modes_slicer;
typedef boost::shared_ptr<air_modes_slicer> air_modes_slicer_sptr;
AIR_MODES_API air_modes_slicer_sptr air_make_modes_slicer(int channel_rate, gr_msg_queue_sptr queue);
AIR_MODES_API air_modes_slicer_sptr air_make_modes_slicer(int channel_rate, gr::msg_queue::sptr queue);
/*!
* \brief mode select slicer detection
* \ingroup block
*/
class AIR_MODES_API air_modes_slicer : public gr_sync_block
class AIR_MODES_API air_modes_slicer : public gr::sync_block
{
private:
friend air_modes_slicer_sptr air_make_modes_slicer(int channel_rate, gr_msg_queue_sptr queue);
air_modes_slicer(int channel_rate, gr_msg_queue_sptr queue);
friend air_modes_slicer_sptr air_make_modes_slicer(int channel_rate, gr::msg_queue::sptr queue);
air_modes_slicer(int channel_rate, gr::msg_queue::sptr queue);
int d_check_width;
int d_chip_rate;
int d_samples_per_chip;
int d_samples_per_symbol;
gr_msg_queue_sptr d_queue;
gr::msg_queue::sptr d_queue;
std::ostringstream d_payload;
public:

View File

@@ -27,7 +27,7 @@ add_library(air_modes SHARED
air_modes_slicer.cc
modes_crc.cc
)
target_link_libraries(air_modes ${Boost_LIBRARIES} ${GRUEL_LIBRARIES} ${GNURADIO_CORE_LIBRARIES})
target_link_libraries(air_modes ${Boost_LIBRARIES} ${GNURADIO_RUNTIME_LIBRARIES})
set_target_properties(air_modes PROPERTIES DEFINE_SYMBOL "AIR_MODES_EXPORTS")
set_target_properties(air_modes PROPERTIES SOVERSION "${gr-gr-air-modes_VERSION_MAJOR}")
set_target_properties(air_modes PROPERTIES VERSION "${gr-gr-air-modes_VERSION_MAJOR}.${gr-gr-air-modes_VERSION_MINOR}")

View File

@@ -1,5 +1,6 @@
/*
# Copyright 2010 Nick Foster
# Copyright 2013 Nicholas Corgan
#
# This file is part of gr-air-modes
#
@@ -24,11 +25,12 @@
#include "config.h"
#endif
#include <ciso646>
#include <air_modes_preamble.h>
#include <gr_io_signature.h>
#include <gnuradio/io_signature.h>
#include <string.h>
#include <iostream>
#include <gr_tags.h>
#include <gnuradio/tags.h>
air_modes_preamble_sptr air_make_modes_preamble(int channel_rate, float threshold_db)
{
@@ -36,9 +38,9 @@ air_modes_preamble_sptr air_make_modes_preamble(int channel_rate, float threshol
}
air_modes_preamble::air_modes_preamble(int channel_rate, float threshold_db) :
gr_block ("modes_preamble",
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
gr::block ("modes_preamble",
gr::io_signature::make2 (2, 2, sizeof(float), sizeof(float)), //stream 0 is received data, stream 1 is moving average for reference
gr::io_signature::make (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
@@ -51,8 +53,8 @@ air_modes_preamble::air_modes_preamble(int channel_rate, float threshold_db) :
std::stringstream str;
str << name() << unique_id();
d_me = pmt::pmt_string_to_symbol(str.str());
d_key = pmt::pmt_string_to_symbol("preamble_found");
d_me = pmt::string_to_symbol(str.str());
d_key = pmt::string_to_symbol("preamble_found");
set_history(d_samples_per_symbol);
}
@@ -79,14 +81,14 @@ static double correlate_preamble(const float *in, int samples_per_chip) {
}
//todo: make it return a pair of some kind, otherwise you can lose precision
static double tag_to_timestamp(gr_tag_t tstamp, uint64_t abs_sample_cnt, double secs_per_sample) {
static double tag_to_timestamp(gr::tag_t tstamp, uint64_t abs_sample_cnt, double secs_per_sample) {
uint64_t ts_sample, last_whole_stamp;
double last_frac_stamp;
if(tstamp.key == NULL || pmt::pmt_symbol_to_string(tstamp.key) != "rx_time") return 0;
if(tstamp.key == NULL || pmt::symbol_to_string(tstamp.key) != "rx_time") return 0;
last_whole_stamp = pmt::pmt_to_uint64(pmt::pmt_tuple_ref(tstamp.value, 0));
last_frac_stamp = pmt::pmt_to_double(pmt::pmt_tuple_ref(tstamp.value, 1));
last_whole_stamp = pmt::to_uint64(pmt::tuple_ref(tstamp.value, 0));
last_frac_stamp = pmt::to_double(pmt::tuple_ref(tstamp.value, 1));
ts_sample = tstamp.offset;
double tstime = double(abs_sample_cnt * secs_per_sample) + last_whole_stamp + last_frac_stamp;
@@ -120,8 +122,8 @@ int air_modes_preamble::general_work(int noutput_items,
};
uint64_t abs_sample_cnt = nitems_read(0);
std::vector<gr_tag_t> tstamp_tags;
get_tags_in_range(tstamp_tags, 0, abs_sample_cnt, abs_sample_cnt + ninputs, pmt::pmt_string_to_symbol("rx_time"));
std::vector<gr::tag_t> tstamp_tags;
get_tags_in_range(tstamp_tags, 0, abs_sample_cnt, abs_sample_cnt + ninputs, pmt::string_to_symbol("rx_time"));
//tags.back() is the most recent timestamp, then.
if(tstamp_tags.size() > 0) {
d_timestamp = tstamp_tags.back();
@@ -193,7 +195,7 @@ 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::from_double(tstamp),
d_me //block src id
);

View File

@@ -1,5 +1,6 @@
/*
# Copyright 2010 Nick Foster
# Copyright 2013 Nicholas Corgan
#
# This file is part of gr-air-modes
#
@@ -24,14 +25,15 @@
#include "config.h"
#endif
#include <ciso646>
#include <air_modes_slicer.h>
#include <gr_io_signature.h>
#include <gnuradio/io_signature.h>
#include <air_modes_types.h>
#include <sstream>
#include <iomanip>
#include <modes_crc.h>
#include <iostream>
#include <gr_tags.h>
#include <gnuradio/tags.h>
extern "C"
{
@@ -39,15 +41,15 @@ extern "C"
#include <string.h>
}
air_modes_slicer_sptr air_make_modes_slicer(int channel_rate, gr_msg_queue_sptr queue)
air_modes_slicer_sptr air_make_modes_slicer(int channel_rate, gr::msg_queue::sptr queue)
{
return air_modes_slicer_sptr (new air_modes_slicer(channel_rate, queue));
}
air_modes_slicer::air_modes_slicer(int channel_rate, gr_msg_queue_sptr queue) :
gr_sync_block ("modes_slicer",
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) )
air_modes_slicer::air_modes_slicer(int channel_rate, gr::msg_queue::sptr queue) :
gr::sync_block ("modes_slicer",
gr::io_signature::make (1, 1, sizeof(float)), //stream 0 is received data, stream 1 is binary preamble detector output
gr::io_signature::make (0, 0, 0) )
{
//initialize private data here
d_chip_rate = 2000000; //2Mchips per second
@@ -105,10 +107,10 @@ int air_modes_slicer::work(int noutput_items,
if(0) std::cout << "Slicer called with " << size << " samples" << std::endl;
std::vector<gr_tag_t> tags;
std::vector<gr::tag_t> tags;
uint64_t abs_sample_cnt = nitems_read(0);
get_tags_in_range(tags, 0, abs_sample_cnt, abs_sample_cnt + size, pmt::pmt_string_to_symbol("preamble_found"));
std::vector<gr_tag_t>::iterator tag_iter;
get_tags_in_range(tags, 0, abs_sample_cnt, abs_sample_cnt + size, pmt::string_to_symbol("preamble_found"));
std::vector<gr::tag_t>::iterator tag_iter;
for(tag_iter = tags.begin(); tag_iter != tags.end(); tag_iter++) {
uint64_t i = tag_iter->offset - abs_sample_cnt;
@@ -156,7 +158,7 @@ int air_modes_slicer::work(int noutput_items,
}
/******************** BEGIN TIMESTAMP BS ******************/
rx_packet.timestamp = pmt_to_double(tag_iter->value);
rx_packet.timestamp = pmt::to_double(tag_iter->value);
/******************* END TIMESTAMP BS *********************/
//increment for the next round
@@ -187,7 +189,7 @@ int air_modes_slicer::work(int noutput_items,
d_payload << " " << std::setw(6) << rx_packet.crc << " " << std::dec << rx_packet.reference_level
<< " " << std::setprecision(10) << std::setw(10) << rx_packet.timestamp;
gr_message_sptr msg = gr_make_message_from_string(std::string(d_payload.str()));
gr::message::sptr msg = gr::message::make_from_string(std::string(d_payload.str()));
d_queue->handle(msg);
}
if(0) std::cout << "Slicer consumed " << size << ", returned " << size << std::endl;

View File

@@ -34,6 +34,7 @@ GR_PYTHON_INSTALL(
altitude.py
az_map.py
cpr.py
html_template.py
mlat.py
exceptions.py
flightgear.py
@@ -41,10 +42,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
)

View File

@@ -52,14 +52,20 @@ 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 kml import output_kml, output_jsonp
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
from html_template import html_template
#this is try/excepted in case the user doesn't have numpy installed
try:
from flightgear import output_flightgear

View File

@@ -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
##############################

View File

@@ -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):

View File

@@ -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):

View File

@@ -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

View File

@@ -22,9 +22,11 @@
# This file contains data models, view delegates, and associated classes
# for handling the GUI back end data model.
from PyQt4 import QtCore, QtGui
from PyQt4 import QtCore, QtGui, QtSql
import air_modes
import threading, math, time
from air_modes.exceptions import *
from gnuradio.gr.pubsub import pubsub
#fades the ICAOs out as their last report gets older,
#and display ident if available, ICAO otherwise
@@ -36,12 +38,16 @@ class ICAOViewDelegate(QtGui.QStyledItemDelegate):
painter.drawRect(option.rect)
#if there's an ident available, use it. otherwise print the ICAO
if index.model().data(index.model().index(index.row(), 9)) != QtCore.QVariant():
paintstr = index.model().data(index.model().index(index.row(), 9)).toString()
if index.model().data(index.model().index(index.row(), 8)) != QtCore.QVariant():
paintstr = index.model().data(index.model().index(index.row(), 8)).toString()
else:
paintstr = index.model().data(index.model().index(index.row(), 0)).toString()
last_report = index.model().data(index.model().index(index.row(), 1)).toDouble()[0]
age = (time.time() - last_report)
#FIXME this is kind of heinous, find out how you got int data out of it last time
last_report = time.strptime(str(index.model().data(index.model().index(index.row(), 1)).toString()), "%Y-%m-%d %H:%M:%S")
age = (time.mktime(time.gmtime()) - time.mktime(last_report)) - 3600.*time.daylight
print age
max_age = 60. #age at which it grays out
#minimum alpha is 0x40 (oldest), max is 0xFF (newest)
age = min(age, max_age)
@@ -49,150 +55,36 @@ class ICAOViewDelegate(QtGui.QStyledItemDelegate):
painter.setPen(QtGui.QColor(0, 0, 0, alpha))
painter.drawText(option.rect.left()+3, option.rect.top(), option.rect.width(), option.rect.height(), option.displayAlignment, paintstr)
#the data model used to display dashboard data.
class dashboard_data_model(QtCore.QAbstractTableModel):
#class dashboard_sql_model(QtCore.QAbstractTableModel):
# def __init__(self, parent):
# QtCore.QAbstractTableModel.__init__(self, parent)
# def update(self, icao):
#TODO must add libqt4-sql, libqt4-sql-sqlite, python-qt4-sql to dependencies
#TODO looks like you're going to have to either integrate this into sql.py (ugh!) or find a way to keep it in sync
#seems like it wants to have control over maintaining data currency
#worst case is you make your own damn SQL query model based on abstracttablemodel.
class dashboard_sql_model(QtSql.QSqlQueryModel):
def __init__(self, parent):
QtCore.QAbstractTableModel.__init__(self, parent)
self._data = []
self.lock = threading.Lock()
self._colnames = ["icao", "seen", "rssi", "latitude", "longitude", "altitude", "speed", "heading", "vertical", "ident", "type", "range", "bearing"]
#custom precision limits for display
self._precisions = [None, None, None, 6, 6, 0, 0, 0, 0, None, None, 2, 0]
for field in self._colnames:
self.setHeaderData(self._colnames.index(field), QtCore.Qt.Horizontal, field)
def rowCount(self, parent=QtCore.QVariant()):
return len(self._data)
def columnCount(self, parent=QtCore.QVariant()):
return len(self._colnames)
def data(self, index, role=QtCore.Qt.DisplayRole):
if not index.isValid():
return QtCore.QVariant()
if index.row() >= self.rowCount():
return QtCore.QVariant()
if index.column() >= self.columnCount():
return QtCore.QVariant()
if (role != QtCore.Qt.DisplayRole) and (role != QtCore.Qt.EditRole):
return QtCore.QVariant()
if self._data[index.row()][index.column()] is None:
return QtCore.QVariant()
else:
#if there's a dedicated precision for that column, print it out with the specified precision.
#this only works well if you DON'T have other views/widgets that depend on numeric data coming out.
#i don't like this, but it works for now. unfortunately it seems like Qt doesn't give you a
#good alternative.
if self._precisions[index.column()] is not None:
return QtCore.QVariant("%.*f" % (self._precisions[index.column()], self._data[index.row()][index.column()]))
else:
if self._colnames[index.column()] == "icao":
return QtCore.QVariant("%06x" % self._data[index.row()][index.column()]) #return as hex string
else:
return QtCore.QVariant(self._data[index.row()][index.column()])
QtSql.QSqlQueryModel.__init__(self, parent)
self._query = """select tab1.icao, tab1.seen, tab1.lat, tab1.lon, tab1.alt, speed, heading, vertical, ident, type
from (select * from (select * from positions order by seen desc) group by icao) tab1
left join (select * from (select * from vectors order by seen desc) group by icao) tab2
on tab1.icao=tab2.icao
left join (select * from (select * from ident)) tab3
on tab1.icao=tab3.icao
where tab1.seen > datetime('now', '-1 minute')"""
self._sql = None
self._db = QtSql.QSqlDatabase("QSQLITE")
self._db.setDatabaseName("adsb.db") #TODO specify this elsewhere
self._db.open()
#what is this i don't even
#fetches the combined data of all three tables for all ICAOs seen in the last minute.
#FIXME PyQt's SQLite gives you different results than the SQLite browser
self.setQuery(self._query, self._db)
def setData(self, index, value, role=QtCore.Qt.EditRole):
self.lock.acquire()
if not index.isValid():
return False
if index.row() >= self.rowCount():
return False
if index.column >= self.columnCount():
return False
if role != QtCore.Qt.EditRole:
return False
self._data[index.row()][index.column()] = value
self.lock.release()
#addRecord implements an upsert on self._data; that is,
#it updates the row if the ICAO exists, or else it creates a new row.
def addRecord(self, record):
self.lock.acquire()
icaos = [x[0] for x in self._data]
if record["icao"] in icaos:
row = icaos.index(record["icao"])
for column in record:
self._data[row][self._colnames.index(column)] = record[column]
#create index to existing row and tell the model everything's changed in this row
#or inside the for loop, use dataChanged on each changed field (might be better)
self.dataChanged.emit(self.createIndex(row, 0), self.createIndex(row, len(self._colnames)-1))
#only create records for ICAOs with ADS-B reports
elif ("latitude" or "speed" or "ident") in record:
#find new inserted row number
icaos.append(record["icao"])
newrowoffset = sorted(icaos).index(record["icao"])
self.beginInsertRows(QtCore.QModelIndex(), newrowoffset, newrowoffset)
newrecord = [None for x in xrange(len(self._colnames))]
for col in xrange(0, len(self._colnames)):
if self._colnames[col] in record:
newrecord[col] = record[self._colnames[col]]
self._data.append(newrecord)
self._data = sorted(self._data, key = lambda x: x[0]) #sort by icao
self.endInsertRows()
self.lock.release()
self.prune()
#weeds out ICAOs older than 5 minutes
def prune(self):
self.lock.acquire()
for (index,row) in enumerate(self._data):
if time.time() - row[1] >= 60:
self.beginRemoveRows(QtCore.QModelIndex(), index, index)
self._data.pop(index)
self.endRemoveRows()
self.lock.release()
class dashboard_output(air_modes.parse):
def __init__(self, mypos, model):
air_modes.parse.__init__(self, mypos)
self.model = model
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
self.model.addRecord(newrow)
#the big club
def update_all(self, icao):
self.setQuery(self._query, self._db)
#self.dataChanged.emit(self.createIndex(0, 0), self.createIndex(self.rowCount(), self.columnCount()))

127
python/html_template.py Normal file
View File

@@ -0,0 +1,127 @@
#!/usr/bin/env python
#HTML template for Mode S map display
#Nick Foster, 2013
def html_template(my_position, json_file):
if my_position is None:
my_position = [37, -122]
return """
<html>
<head>
<title>ADS-B Aircraft Map</title>
<meta name="viewport" content="initial-scale=1.0, user-scalable=no" />
<meta http-equiv="content-type" content="text/html;charset=utf-8" />
<style type="text/css">
.labels {
color: red;
background-color: white;
font-family: "Lucida Grande", "Arial", sans-serif;
font-size: 10px;
font-weight: bold;
text-align: center;
width: 40px;
border: 2px solid black;
white-space: nowrap;
}
</style>
<script type="text/javascript" src="http://maps.google.com/maps/api/js?sensor=false">
</script>
<script type="text/javascript">
var map;
var markers = [];
var defaultLocation = new google.maps.LatLng(%f, %f);
var defaultZoomLevel = 9;
function requestJSONP() {
var script = document.createElement("script");
script.src = "%s?" + Math.random();
script.params = Math.random();
document.getElementsByTagName('head')[0].appendChild(script);
};
var planeMarker;
var planes = [];
function clearMarkers() {
for (var i = 0; i < planes.length; i++) {
planes[i].setMap(null);
}
planes = [];
};
function jsonp_callback(results) { // from JSONP
clearMarkers();
airplanes = {};
for (var i = 0; i < results.length; i++) {
airplanes[results[i].icao] = {
center: new google.maps.LatLng(results[i].lat, results[i].lon),
heading: results[i].hdg,
altitude: results[i].alt,
type: results[i].type,
ident: results[i].ident,
speed: results[i].speed,
vertical: results[i].vertical,
highlight: results[i].highlight
};
}
refreshIcons();
}
function refreshIcons() {
for (var airplane in airplanes) {
if (airplanes[airplane].highlight != 0) {
icon_file = "http://www.nerdnetworks.org/~bistromath/airplane_sprite_highlight.png";
} else {
icon_file = "http://www.nerdnetworks.org/~bistromath/airplane_sprite.png";
};
var plane_icon = {
url: icon_file,
size: new google.maps.Size(128,128),
origin: new google.maps.Point(parseInt(airplanes[airplane].heading/10)*128,0),
anchor: new google.maps.Point(64,64),
//scaledSize: new google.maps.Size(4608,126)
};
identstr = airplanes[airplane].ident;
if (identstr === "" || !identstr) {
identstr = airplanes[airplane].icao;
};
var planeOptions = {
map: map,
position: airplanes[airplane].center,
icon: plane_icon,
//label content meaningless unless you use the MarkerWithLabel class from the Maps Utility Library
labelContent: identstr,
labelAnchor: new google.maps.Point(64, 0),
labelClass: "labels",
labelStyle: {opacity: 0.75}
};
planeMarker = new google.maps.Marker(planeOptions);
planes.push(planeMarker);
};
};
function initialize()
{
var myOptions =
{
zoom: defaultZoomLevel,
center: defaultLocation,
disableDefaultUI: true,
mapTypeId: google.maps.MapTypeId.TERRAIN
};
map = new google.maps.Map(document.getElementById("map_canvas"), myOptions);
requestJSONP();
setInterval("requestJSONP()", 1000);
};
</script>
</head>
<body onload="initialize()">
<div id="map_canvas" style="width:100%%; height:100%%">
</div>
</body>
</html>""" % (my_position[0], my_position[1], json_file)

View File

@@ -30,21 +30,30 @@ class output_kml(threading.Thread):
self.my_coords = localpos
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)
self.done = True
time.sleep(self._timeout)
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()
if kmlstr is not None:
@@ -55,7 +64,7 @@ class output_kml(threading.Thread):
def locked_execute(self, c, query):
with self._lock:
c.execute(query)
def draw_circle(self, center, rng):
retstr = ""
steps=30
@@ -82,7 +91,7 @@ class output_kml(threading.Thread):
retstr = string.lstrip(retstr)
return retstr
def genkml(self):
#first let's draw the static content
retstr="""<?xml version="1.0" encoding="UTF-8"?>\n<kml xmlns="http://www.opengis.net/kml/2.2">\n<Document>\n\t<Style id="airplane">\n\t\t<IconStyle>\n\t\t\t<Icon><href>airports.png</href></Icon>\n\t\t</IconStyle>\n\t</Style>\n\t<Style id="rangering">\n\t<LineStyle>\n\t\t<color>9f4f4faf</color>\n\t\t<width>2</width>\n\t</LineStyle>\n\t</Style>\n\t<Style id="track">\n\t<LineStyle>\n\t\t<color>5fff8f8f</color>\n\t\t<width>4</width>\n\t</LineStyle>\n\t</Style>"""
@@ -92,7 +101,7 @@ class output_kml(threading.Thread):
for rng in [100, 200, 300]:
retstr += """\n\t\t<Placemark>\n\t\t\t<name>%inm</name>\n\t\t\t<styleUrl>#rangering</styleUrl>\n\t\t\t<LinearRing>\n\t\t\t\t<coordinates>%s</coordinates>\n\t\t\t</LinearRing>\n\t\t</Placemark>""" % (rng, self.draw_circle(self.my_coords, rng),)
retstr += """\t</Folder>\n"""
retstr += """\t<Folder>\n\t\t<name>Aircraft locations</name>\n\t\t<open>0</open>"""
#read the database and add KML
@@ -115,11 +124,11 @@ class output_kml(threading.Thread):
if lon is None: lon = 0
alt = track[0][2]
if alt is None: alt = 0
metric_alt = alt * 0.3048 #google earth takes meters, the commie bastards
trackstr = ""
for pos in track:
trackstr += " %f,%f,%f" % (pos[4], pos[3], pos[2]*0.3048)
@@ -153,7 +162,7 @@ class output_kml(threading.Thread):
seen = 0
speed = 0
heading = 0
vertical = 0
vertical = 0
#now generate some KML
retstr+= "\n\t\t<Placemark>\n\t\t\t<name>%s</name>\n\t\t\t<Style><IconStyle><heading>%i</heading></IconStyle></Style>\n\t\t\t<styleUrl>#airplane</styleUrl>\n\t\t\t<description>\n\t\t\t\t<![CDATA[Altitude: %s<br/>Heading: %i<br/>Speed: %i<br/>Vertical speed: %i<br/>ICAO: %x<br/>Last seen: %s]]>\n\t\t\t</description>\n\t\t\t<Point>\n\t\t\t\t<altitudeMode>absolute</altitudeMode>\n\t\t\t\t<extrude>1</extrude>\n\t\t\t\t<coordinates>%s,%s,%i</coordinates>\n\t\t\t</Point>\n\t\t</Placemark>" % (ident, heading, alt, heading, speed, vertical, icao[0], seen, lon, lat, metric_alt, )
@@ -161,3 +170,79 @@ class output_kml(threading.Thread):
retstr+= '\n\t</Folder>\n</Document>\n</kml>'
return retstr
#we just inherit from output_kml because we're doing the same thing, only in a different format.
class output_jsonp(output_kml):
def set_highlight(self, icao):
self.highlight = icao
def genkml(self):
retstr="""jsonp_callback(["""
# if self.my_coords is not None:
# retstr += """\n\t<Folder>\n\t\t<name>Range rings</name>\n\t\t<open>0</open>"""
# for rng in [100, 200, 300]:
# retstr += """\n\t\t<Placemark>\n\t\t\t<name>%inm</name>\n\t\t\t<styleUrl>#rangering</styleUrl>\n\t\t\t<LinearRing>\n\t\t\t\t<coordinates>%s</coordinates>\n\t\t\t</LinearRing>\n\t\t</Placemark>""" % (rng, self.draw_circle(self.my_coords, rng),)
# retstr += """\t</Folder>\n"""
# retstr += """\t<Folder>\n\t\t<name>Aircraft locations</name>\n\t\t<open>0</open>"""
#read the database and add KML
q = "select distinct icao from positions where seen > datetime('now', '-5 minute')"
c = self._db.cursor()
self.locked_execute(c, q)
icaolist = c.fetchall()
#now we have a list icaolist of all ICAOs seen in the last 5 minutes
for icao in icaolist:
icao = icao[0]
#now get metadata
q = "select ident, type from ident where icao=%i" % icao
self.locked_execute(c, q)
r = c.fetchall()
if len(r) != 0:
ident = r[0][0]
actype = r[0][1]
else:
ident=""
actype = ""
if ident is None: ident = ""
#get most recent speed/heading/vertical
q = "select seen, speed, heading, vertical from vectors where icao=%i order by seen desc limit 1" % icao
self.locked_execute(c, q)
r = c.fetchall()
if len(r) != 0:
seen = r[0][0]
speed = r[0][1]
heading = r[0][2]
vertical = r[0][3]
else:
seen = 0
speed = 0
heading = 0
vertical = 0
q = "select lat, lon, alt from positions where icao=%i order by seen desc limit 1" % icao
self.locked_execute(c, q)
r = c.fetchall()
if len(r) != 0:
lat = r[0][0]
lon = r[0][1]
alt = r[0][2]
else:
lat = 0
lon = 0
alt = 0
highlight = 0
if hasattr(self, 'highlight'):
if self.highlight == icao:
highlight = 1
#now generate some JSONP
retstr+= """{"icao": "%.6x", "lat": %f, "lon": %f, "alt": %i, "hdg": %i, "speed": %i, "vertical": %i, "ident": "%s", "type": "%s", "highlight": %i},""" % (icao, lat, lon, alt, heading, speed, vertical, ident, actype, highlight)
retstr+= """]);"""
return retstr

View File

@@ -25,204 +25,194 @@ 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, callback=None):
self._cpr = cpr
self._callback = callback
#sub to every function that starts with "handle"
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))
else:
retstr = "Type 17 BDS0,9-%i from %x not implemented" % (subtype, icao24)
publisher.subscribe("modes_dl", self.catch_nohandler)
elif bdsreg == 0x62:
emerg_str = self.parseBDS62(data)
retstr = "Type 17 BDS6,2 (emergency) from %x type %s" % (icao24, emerg_str)
@staticmethod
def prefix(msg):
return "(%i %.8f) " % (msg.rssi, msg.timestamp)
def _print(self, msg):
if self._callback is None:
print msg
else:
retstr = "Type 17 with FTC=%i from %x not implemented" % (data["ftc"], icao24)
self._callback(msg)
return retstr
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 += " from %.6x" % msg.data["aa"]
self._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
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)
except ADSBError:
return
if msg.data["vs"] is 1:
retstr += " (aircraft is on the ground)"
self._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:
raise ADSBError
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
self._print(retstr)
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
self._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
self._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
self._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 +223,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
return retstr
retstr += " ident %x" % air_modes.decode_id(msg.data["id"])
self._print(retstr)
handle16 = printTCAS
handle20 = printTCAS
handle21 = printTCAS

View File

@@ -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

208
python/radio.py Normal file
View File

@@ -0,0 +1,208 @@
# 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, eng_notation, filter
from gnuradio.filter import optfir
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 = filter.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()

View File

@@ -1,5 +1,5 @@
#
# Copyright 2012 Corgan Labs
# Copyright 2012, 2013 Corgan Labs, Nick Foster
#
# This file is part of gr-air-modes
#
@@ -19,7 +19,7 @@
# Boston, MA 02110-1301, USA.
#
from gnuradio import gr
from gnuradio import gr, blocks
import air_modes_swig
class rx_path(gr.hier_block2):
@@ -35,17 +35,17 @@ class rx_path(gr.hier_block2):
self._spc = int(rate/2e6)
# Convert incoming I/Q baseband to amplitude
self._demod = gr.complex_to_mag()
self._demod = blocks.complex_to_mag()
self._bb = self._demod
# 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 = blocks.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 = blocks.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()

View File

@@ -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)

View File

@@ -24,119 +24,106 @@ 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" DATETIME NOT NULL,
"alt" INTEGER,
"lat" REAL,
"lon" REAL
);"""
c.execute(query)
query = """CREATE TABLE IF NOT EXISTS "vectors" (
"icao" INTEGER KEY NOT NULL,
"seen" DATETIME 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
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 + "')"
return "INSERT OR REPLACE INTO ident (icao, ident, type) VALUES (%i, '%s', '%s')" % (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, datetime('now'), %i, %.6f, %.6f)" % (icao24, int(altitude), decoded_lat, 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, datetime('now'), %i, %.6f, %.6f)" % (icao24, int(altitude), decoded_lat, 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 + ")"
return "INSERT INTO vectors (icao, seen, speed, heading, vertical) VALUES (%i, datetime('now'), %.0f, %.0f, %.0f)" % (icao24, velocity, heading, 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] = self.parseBDS09_1(data)
return "INSERT INTO vectors (icao, seen, speed, heading, vertical) VALUES (%i, datetime('now'), %.0f, %.0f, %.0f)" % (icao24, velocity, heading, vert_spd)
else:
retstr = None
return retstr
raise NoHandlerError

107
python/types.py Normal file
View 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
View 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()

View File

@@ -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}
)

View File

@@ -365,7 +365,7 @@
</rect>
</property>
<property name="text">
<string>Raw</string>
<string>TCP</string>
</property>
</widget>
<widget class="QLineEdit" name="line_kmlfilename">
@@ -856,7 +856,7 @@
<string>Climb</string>
</property>
</widget>
<widget class="QwtCompass" name="compass_heading">
<widget class="QwtCompass" name="compass_heading" native="true">
<property name="geometry">
<rect>
<x>200</x>
@@ -865,10 +865,10 @@
<height>91</height>
</rect>
</property>
<property name="readOnly">
<property name="readOnly" stdset="0">
<bool>true</bool>
</property>
<property name="lineWidth">
<property name="lineWidth" stdset="0">
<number>4</number>
</property>
</widget>
@@ -901,7 +901,7 @@
<bool>true</bool>
</property>
</widget>
<widget class="QwtCompass" name="compass_bearing">
<widget class="QwtCompass" name="compass_bearing" native="true">
<property name="geometry">
<rect>
<x>-20</x>
@@ -910,10 +910,10 @@
<height>91</height>
</rect>
</property>
<property name="readOnly">
<property name="readOnly" stdset="0">
<bool>true</bool>
</property>
<property name="lineWidth">
<property name="lineWidth" stdset="0">
<number>4</number>
</property>
</widget>
@@ -928,6 +928,16 @@
</item>
</layout>
</widget>
<widget class="QWidget" name="map_tab">
<attribute name="title">
<string>Map</string>
</attribute>
<layout class="QGridLayout" name="gridLayout_8">
<item row="0" column="0">
<widget class="QWebView" name="mapView" native="true"/>
</item>
</layout>
</widget>
<widget class="QWidget" name="livedatatab">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
@@ -1016,11 +1026,6 @@
<widget class="QStatusBar" name="statusbar"/>
</widget>
<customwidgets>
<customwidget>
<class>QwtCompass</class>
<extends>QwtDial</extends>
<header>qwt_compass.h</header>
</customwidget>
<customwidget>
<class>QwtDial</class>
<extends>QWidget</extends>
@@ -1032,6 +1037,17 @@
<header location="global">air_modes/az_map</header>
<container>1</container>
</customwidget>
<customwidget>
<class>QwtCompass</class>
<extends>QwtDial</extends>
<header>qwt_compass.h</header>
</customwidget>
<customwidget>
<class>QWebView</class>
<extends>QWidget</extends>
<header location="global">QtWebKit/qwebview.h</header>
<container>1</container>
</customwidget>
</customwidgets>
<resources/>
<connections/>

View File

@@ -31,11 +31,8 @@ include(GrPython)
########################################################################
# Setup swig generation
########################################################################
foreach(incdir ${GNURADIO_CORE_INCLUDE_DIRS})
list(APPEND GR_SWIG_INCLUDE_DIRS ${incdir}/swig)
endforeach(incdir)
foreach(incdir ${GRUEL_INCLUDE_DIRS})
list(APPEND GR_SWIG_INCLUDE_DIRS ${incdir}/gruel/swig)
foreach(incdir ${GNURADIO_RUNTIME_INCLUDE_DIRS})
list(APPEND GR_SWIG_INCLUDE_DIRS ${incdir}/gnuradio/swig)
endforeach(incdir)
set(GR_SWIG_LIBRARIES air_modes)

View File

@@ -5,7 +5,7 @@
%{
#include "air_modes_preamble.h"
#include "air_modes_slicer.h"
#include <gr_msg_queue.h>
#include <gnuradio/msg_queue.h>
%}
// ----------------------------------------------------------------
@@ -21,20 +21,24 @@ GR_SWIG_BLOCK_MAGIC(air,modes_preamble);
air_modes_preamble_sptr air_make_modes_preamble (int channel_rate, float threshold_db);
class air_modes_preamble : public gr_sync_block
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);
};
GR_SWIG_BLOCK_MAGIC(air,modes_slicer);
air_modes_slicer_sptr air_make_modes_slicer (int channel_rate, gr_msg_queue_sptr queue);
air_modes_slicer_sptr air_make_modes_slicer (int channel_rate, gr::msg_queue::sptr queue);
class air_modes_slicer : public gr_block
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);
};
// ----------------------------------------------------------------