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

View File

@@ -19,16 +19,19 @@
# Boston, MA 02110-1301, USA. # Boston, MA 02110-1301, USA.
# #
import os, sys, time, threading, datetime, math, csv import os, sys, time, threading, datetime, math, csv, tempfile
from PyQt4 import QtCore,QtGui from optparse import OptionParser
from PyQt4 import QtCore,QtGui,QtWebKit
from PyQt4.Qwt5 import Qwt from PyQt4.Qwt5 import Qwt
from gnuradio import gr, gru, optfir, eng_notation, blks2 from gnuradio import gr, eng_notation
import gnuradio.gr.gr_threading as _threading from gnuradio.eng_option import eng_option
from gnuradio.gr.pubsub import pubsub
import air_modes import air_modes
from air_modes.exceptions import * from air_modes.exceptions import *
from air_modes.modes_rx_ui import Ui_MainWindow from air_modes.modes_rx_ui import Ui_MainWindow
from air_modes.gui_model import * from air_modes.gui_model import *
import sqlite3 import sqlite3
import zmq
class mainwindow(QtGui.QMainWindow): class mainwindow(QtGui.QMainWindow):
live_data_changed_signal = QtCore.pyqtSignal(QtCore.QString, name='liveDataChanged') live_data_changed_signal = QtCore.pyqtSignal(QtCore.QString, name='liveDataChanged')
@@ -39,7 +42,7 @@ class mainwindow(QtGui.QMainWindow):
#set defaults #set defaults
#add file, RTL, UHD sources #add file, RTL, UHD sources
self.ui.combo_source.addItems(["UHD device", "RTL-SDR", "File"]) self.ui.combo_source.addItems(["UHD", "Osmocom", "File/UDP"])
self.ui.combo_source.setCurrentIndex(0) self.ui.combo_source.setCurrentIndex(0)
#populate antenna, rate combo boxes based on source #populate antenna, rate combo boxes based on source
@@ -51,8 +54,8 @@ class mainwindow(QtGui.QMainWindow):
#default to 5dB #default to 5dB
self.ui.line_threshold.insert("5") self.ui.line_threshold.insert("5")
self.ui.prog_rssi.setMinimum(-40) self.ui.prog_rssi.setMinimum(0)
self.ui.prog_rssi.setMaximum(0) self.ui.prog_rssi.setMaximum(40)
self.ui.combo_ant.setCurrentIndex(self.ui.combo_ant.findText("RX2")) self.ui.combo_ant.setCurrentIndex(self.ui.combo_ant.findText("RX2"))
@@ -70,18 +73,16 @@ class mainwindow(QtGui.QMainWindow):
#disable by default #disable by default
self.ui.check_adsbonly.setCheckState(QtCore.Qt.Unchecked) self.ui.check_adsbonly.setCheckState(QtCore.Qt.Unchecked)
#set up the radio stuff
self.queue = gr.msg_queue(10) self.queue = gr.msg_queue(10)
self.runner = None self.running = False
self.fg = None
self.outputs = []
self.updates = []
self.output_handler = None
self.kmlgen = None #necessary bc we stop its thread in shutdown self.kmlgen = None #necessary bc we stop its thread in shutdown
self.dbname = "air_modes.db" self.dbname = "adsb.db"
self.num_reports = 0 self.num_reports = 0
self.last_report = 0 self.last_report = 0
self.context = zmq.Context(1)
self.datamodel = dashboard_data_model(None) self.datamodel = dashboard_sql_model(None)
self.ui.list_aircraft.setModel(self.datamodel) self.ui.list_aircraft.setModel(self.datamodel)
self.ui.list_aircraft.setModelColumn(0) self.ui.list_aircraft.setModelColumn(0)
@@ -95,15 +96,16 @@ class mainwindow(QtGui.QMainWindow):
self.dashboard_mapper.setModel(self.datamodel) self.dashboard_mapper.setModel(self.datamodel)
self.dashboard_mapper.addMapping(self.ui.line_icao, 0) self.dashboard_mapper.addMapping(self.ui.line_icao, 0)
#self.dashboard_mapper.addMapping(self.ui.prog_rssi, 2) #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_latitude, 2)
self.dashboard_mapper.addMapping(self.ui.line_longitude, 4) self.dashboard_mapper.addMapping(self.ui.line_longitude, 3)
self.dashboard_mapper.addMapping(self.ui.line_alt, 5) self.dashboard_mapper.addMapping(self.ui.line_alt, 4)
self.dashboard_mapper.addMapping(self.ui.line_speed, 6) self.dashboard_mapper.addMapping(self.ui.line_speed, 5)
#self.dashboard_mapper.addMapping(self.ui.compass_heading, 7) #self.dashboard_mapper.addMapping(self.ui.compass_heading, 6)
self.dashboard_mapper.addMapping(self.ui.line_climb, 8) self.dashboard_mapper.addMapping(self.ui.line_climb, 7)
self.dashboard_mapper.addMapping(self.ui.line_ident, 9) self.dashboard_mapper.addMapping(self.ui.line_ident, 8)
self.dashboard_mapper.addMapping(self.ui.line_type, 10) self.dashboard_mapper.addMapping(self.ui.line_type, 9)
self.dashboard_mapper.addMapping(self.ui.line_range, 11) #self.dashboard_mapper.addMapping(self.ui.line_range, 11)
#self.dashboard_mapper.addMapping(self.ui.compass_bearing, 12)
compass_palette = QtGui.QPalette() compass_palette = QtGui.QPalette()
compass_palette.setColor(QtGui.QPalette.Foreground, QtCore.Qt.white) 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_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)) 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.dashboard_mapper.setCurrentModelIndex)
self.ui.list_aircraft.selectionModel().currentRowChanged.connect(self.update_heading_widget) self.ui.list_aircraft.selectionModel().currentRowChanged.connect(self.update_heading_widget)
self.ui.list_aircraft.selectionModel().currentRowChanged.connect(self.update_bearing_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_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 #hook up live data text box update signal
self.live_data_changed_signal.connect(self.on_append_live_data) 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 ############ ############ widget update functions for non-mapped widgets ############
def update_heading_widget(self, index): def update_heading_widget(self, index):
if index.model() is not None: 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) self.ui.compass_heading.setValue(heading)
def update_bearing_widget(self, index): def update_bearing_widget(self, index):
if index.model() is not None: 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) self.ui.compass_bearing.setValue(bearing)
def unmapped_widgets_dataChanged(self, startIndex, endIndex): def unmapped_widgets_dataChanged(self, startIndex, endIndex):
index = self.ui.list_aircraft.selectionModel().currentIndex() index = self.ui.list_aircraft.selectionModel().currentIndex()
if index.row() in range(startIndex.row(), endIndex.row()+1): #the current aircraft was affected 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) 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) 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) self.update_rssi_widget(index)
def update_rssi_widget(self, index): def update_rssi_widget(self, index):
if index.model() is not None: 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) self.ui.prog_rssi.setValue(rssi)
def increment_reportspersec(self, msg): def increment_reportspersec(self, msg):
self.num_reports += 1 self.num_reports += 1
@@ -159,6 +163,12 @@ class mainwindow(QtGui.QMainWindow):
self.ui.line_reports.setText("%i" % self.num_reports) self.ui.line_reports.setText("%i" % self.num_reports)
self.num_reports = 0 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 ######################## ##################### dynamic option population ########################
#goes and gets valid antenna, sample rate options from the device and grays out appropriate things #goes and gets valid antenna, sample rate options from the device and grays out appropriate things
def populate_source_options(self): def populate_source_options(self):
@@ -166,12 +176,13 @@ class mainwindow(QtGui.QMainWindow):
self.rates = [] self.rates = []
self.ratetext = [] self.ratetext = []
self.antennas = [] self.antennas = []
if sourceid == "UHD device": if sourceid == "UHD":
try: try:
from gnuradio import uhd from gnuradio import uhd
self.src = uhd.single_usrp_source("", uhd.io_type_t.COMPLEX_FLOAT32, 1) self.src = uhd.single_usrp_source("", uhd.io_type_t.COMPLEX_FLOAT32, 1)
self.rates = [rate.start() for rate in self.src.get_samp_rates()] self.rates = [rate.start() for rate in self.src.get_samp_rates()
if (rate.start() % 2.e6) == 0 and rate >= 4e6]
self.antennas = self.src.get_antennas() self.antennas = self.src.get_antennas()
self.src = None #deconstruct UHD source for now self.src = None #deconstruct UHD source for now
self.ui.combo_ant.setEnabled(True) self.ui.combo_ant.setEnabled(True)
@@ -183,16 +194,26 @@ class mainwindow(QtGui.QMainWindow):
self.ui.combo_ant.setEnabled(False) self.ui.combo_ant.setEnabled(False)
self.ui.combo_rate.setEnabled(False) self.ui.combo_rate.setEnabled(False)
self.ui.stack_source.setCurrentIndex(0) self.ui.stack_source.setCurrentIndex(0)
elif sourceid == "RTL-SDR":
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": elif sourceid == "Osmocom":
self.rates = [2e6, 4e6, 6e6, 8e6, 10e6] 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.antennas = ["None"]
self.ui.combo_ant.setEnabled(False) self.ui.combo_ant.setEnabled(False)
self.ui.combo_rate.setEnabled(True) self.ui.combo_rate.setEnabled(True)
@@ -206,8 +227,12 @@ class mainwindow(QtGui.QMainWindow):
self.ui.combo_ant.clear() self.ui.combo_ant.clear()
self.ui.combo_ant.addItems(self.antennas) self.ui.combo_ant.addItems(self.antennas)
if 4e6 in self.rates: #set up recommended sample rate
self.ui.combo_rate.setCurrentIndex(self.rates.index(4e6)) 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 #################### ################ action handlers ####################
def on_combo_source_currentIndexChanged(self, index): def on_combo_source_currentIndexChanged(self, index):
@@ -215,47 +240,47 @@ class mainwindow(QtGui.QMainWindow):
def on_button_start_released(self): def on_button_start_released(self):
#if we're already running, kill it! #if we're already running, kill it!
if self.runner is not None: if self.running is True:
self.output_handler.done = True self.on_quit()
self.output_handler = None
self.outputs = []
self.updates = []
self.fg.stop()
self.runner = None
self.fg = None
if self.kmlgen is not None:
self.kmlgen.done = True
#TODO FIXME need a way to kill kmlgen safely without delay
#self.kmlgen.join()
#self.kmlgen = None
self.num_reports = 0 self.num_reports = 0
self.ui.line_reports.setText("0") self.ui.line_reports.setText("0")
self.ui.button_start.setText("Start") self.ui.button_start.setText("Start")
self.running = False
else: #we aren't already running, let's get this party started else: #we aren't already running, let's get this party started
options = {} parser = OptionParser(option_class=eng_option)
options["source"] = str(self.ui.combo_source.currentText()) air_modes.modes_radio.add_radio_options(parser)
options["rate"] = float(self.ui.combo_rate.currentText()) * 1e6 (options, args) = parser.parse_args() #sets defaults nicely
options["antenna"] = str(self.ui.combo_ant.currentText()) if str(self.ui.combo_source.currentText()) != "File/UDP":
options["gain"] = float(self.ui.line_gain.text()) options.source = str(self.ui.combo_source.currentText()).lower()
options["threshold"] = float(self.ui.line_threshold.text()) else:
options["filename"] = str(self.ui.line_inputfile.text()) options.source = str(self.ui.line_inputfile.text())
options["pmf"] = self.ui.check_pmf.checkState() options.rate = float(self.ui.combo_rate.currentText()) * 1e6
options.antenna = str(self.ui.combo_ant.currentText())
options.gain = float(self.ui.line_gain.text())
options.threshold = float(self.ui.line_threshold.text())
options.pmf = self.ui.check_pmf.checkState()
self.fg = adsb_rx_block(options, self.queue) #create top RX block self._servers = ["inproc://modes-radio-pub"] #TODO ADD REMOTES
self.runner = top_block_runner(self.fg) #spawn new thread to do RX self._relay = air_modes.zmq_pubsub_iface(self.context, subaddr=self._servers, pubaddr=None)
if self.ui.check_raw.checkState():
options.tcp = int(self.ui.line_rawport.text())
self._radio = air_modes.modes_radio(options, self.context)
self._publisher = pubsub()
self._relay.subscribe("dl_data", air_modes.make_parser(self._publisher))
try: try:
my_position = [float(self.ui.line_my_lat.text()), float(self.ui.line_my_lon.text())] my_position = [float(self.ui.line_my_lat.text()), float(self.ui.line_my_lon.text())]
except: except:
my_position = None 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 self.lock = threading.Lock() #grab a lock to ensure sql and kml don't step on each other
#output options to populate outputs, updates #output options to populate outputs, updates
@@ -265,55 +290,61 @@ class mainwindow(QtGui.QMainWindow):
if self.ui.check_sbs1.checkState(): if self.ui.check_sbs1.checkState():
sbs1port = int(self.ui.line_sbs1port.text()) sbs1port = int(self.ui.line_sbs1port.text())
sbs1out = air_modes.output_sbs1(my_position, sbs1port) sbs1out = air_modes.output_sbs1(self._cpr_dec, sbs1port, self._publisher)
self.outputs.append(sbs1out.output)
self.updates.append(sbs1out.add_pending_conns)
if self.ui.check_fgfs.checkState(): if self.ui.check_fgfs.checkState():
fghost = "127.0.0.1" #TODO FIXME fghost = "127.0.0.1" #TODO FIXME
fgport = self.ui.line_fgfsport.text() fgport = self.ui.line_fgfsport.text()
fgout = air_modes.output_flightgear(my_position, fghost, int(fgport)) fgout = air_modes.output_flightgear(self._cpr_dec, fghost, int(fgport), self._publisher)
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)
#add azimuth map output and hook it up #add azimuth map output and hook it up
if my_position is not None: if my_position is not None:
self.az_map_output = air_modes.az_map_output(my_position, self.az_model) self.az_map_output = air_modes.az_map_output(self._cpr_dec, self.az_model, self._publisher)
self.outputs.append(self.az_map_output.output) #self._relay.subscribe("dl_data", self.az_map_output.output)
self.livedata = air_modes.output_print(my_position) #set up map
#add output for live data box #NOTE this is busted on windows. WebKit requires .htm[l] extensions to render,
self.outputs.append(self.output_live_data) #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 #create SQL database for KML and dashboard displays
self.dbwriter = air_modes.output_sql(my_position, self.dbname, self.lock) self.dbwriter = air_modes.output_sql(self._cpr_dec, self.dbname, self.lock, self._publisher)
self.outputs.append(self.dbwriter.output) #now the db will update itself 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 #output to update reports/sec widget
self.outputs.append(self.increment_reportspersec) self._relay.subscribe("dl_data", self.increment_reportspersec)
self.updates.append(self.update_reportspersec) self._rps_timer = QtCore.QTimer()
self._rps_timer.timeout.connect(self.update_reportspersec)
self._rps_timer.start(1000)
#create output handler thread #start the flowgraph
self.output_handler = output_handler(self.outputs, self.updates, self.queue) self._radio.start()
self.ui.button_start.setText("Stop") self.ui.button_start.setText("Stop")
self.running = True
def on_quit(self): def on_quit(self):
if self.runner is not None: if self.running is True:
try: self._relay.close()
self.output_handler.done = True self._radio.close()
except: self._relay = None
pass self._radio = None
self.output_handler = None self._rps_timer = None
self.outputs = []
self.updates = []
self.fg.stop()
self.runner = None
self.fg = None
try: try:
self.kmlgen.done = True self.kmlgen.done = True
#TODO FIXME need a way to kill kmlgen safely without delay #TODO FIXME need a way to kill kmlgen safely without delay
@@ -331,109 +362,10 @@ class mainwindow(QtGui.QMainWindow):
cursor.movePosition(QtGui.QTextCursor.Start) cursor.movePosition(QtGui.QTextCursor.Start)
cursor.select(QtGui.QTextCursor.LineUnderCursor) cursor.select(QtGui.QTextCursor.LineUnderCursor)
cursor.removeSelectedText() cursor.removeSelectedText()
self.ui.text_livedata.append(msgstr) self.ui.text_livedata.append(msgstr)
self.ui.text_livedata.verticalScrollBar().setSliderPosition(self.ui.text_livedata.verticalScrollBar().maximum()) self.ui.text_livedata.verticalScrollBar().setSliderPosition(self.ui.text_livedata.verticalScrollBar().maximum())
def output_live_data(self, msg):
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__': if __name__ == '__main__':
app = QtGui.QApplication(sys.argv) app = QtGui.QApplication(sys.argv)
window = mainwindow() window = mainwindow()

View File

@@ -1,5 +1,5 @@
#!/usr/bin/env python #!/usr/bin/env python
# Copyright 2010 Nick Foster # Copyright 2010, 2013 Nick Foster
# #
# This file is part of gr-air-modes # This file is part of gr-air-modes
# #
@@ -19,221 +19,80 @@
# Boston, MA 02110-1301, USA. # 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.eng_option import eng_option
from gnuradio.gr.pubsub import pubsub
from optparse import OptionParser from optparse import OptionParser
import time, os, sys, threading import time, os, sys, threading, math
from string import split, join from string import split, join
import air_modes import air_modes
import gnuradio.gr.gr_threading as _threading from air_modes.types import *
import csv
from air_modes.exceptions import * from air_modes.exceptions import *
import zmq
class top_block_runner(_threading.Thread): #todo: maybe move plugins to separate programs (flightgear, SBS1, etc.)
def __init__(self, tb): def main():
_threading.Thread.__init__(self) my_position = None
self.setDaemon(1) usage = "%prog: [options]"
self.tb = tb optparser = OptionParser(option_class=eng_option, usage=usage)
self.done = False air_modes.modes_radio.add_radio_options(optparser)
self.start()
def run(self): optparser.add_option("-l","--location", type="string", default=None,
self.tb.run() help="GPS coordinates of receiving station in format xx.xxxxx,xx.xxxxx")
self.done = True #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): (options, args) = optparser.parse_args()
def __init__(self, options, args, queue):
gr.top_block.__init__(self)
self.options = options #construct the radio
self.args = args context = zmq.Context(1)
rate = int(options.rate) tb = air_modes.modes_radio(options, context)
use_resampler = False servers = ["inproc://modes-radio-pub"]
if options.remote is not None:
if options.filename is None and options.udp is None and not options.rtlsdr: servers += options.remote.split(",")
#UHD source by default relay = air_modes.zmq_pubsub_iface(context, subaddr=servers, pubaddr=None)
from gnuradio import uhd publisher = pubsub()
self.u = uhd.single_usrp_source(options.args, uhd.io_type_t.COMPLEX_FLOAT32, 1) relay.subscribe("dl_data", air_modes.make_parser(publisher))
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()
if options.location is not None: if options.location is not None:
reader = csv.reader([options.location], quoting=csv.QUOTE_NONNUMERIC) my_position = [float(n) for n in options.location.split(",")]
my_position = reader.next()
queue = gr.msg_queue() #CPR decoder obj to handle getting position from BDS0,5 and BDS0,6 pkts
cpr_dec = air_modes.cpr_decoder(my_position)
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)
if options.kml is not None: if options.kml is not None:
#we spawn a thread to run every 30 seconds (or whatever) to generate KML
dbname = 'adsb.db' dbname = 'adsb.db'
lock = threading.Lock() 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 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: 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: if options.multiplayer is not None:
[fghost, fgport] = options.multiplayer.split(':') [fghost, fgport] = options.multiplayer.split(':')
fgout = air_modes.output_flightgear(my_position, fghost, int(fgport)) fgout = air_modes.output_flightgear(cpr_dec, fghost, int(fgport), publisher)
outputs.append(fgout.output)
fg = adsb_rx_block(options, args, queue) if options.sbs1 is True:
runner = top_block_runner(fg) sbs1port = air_modes.output_sbs1(cpr_dec, 30003, publisher)
while 1: tb.run()
try: tb.close()
#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
for out in outputs: relay.close()
try:
out(msg.to_string())
except air_modes.ADSBError:
pass
elif runner.done: if options.kml is not None:
raise KeyboardInterrupt kmlgen.close()
else:
time.sleep(0.1)
except KeyboardInterrupt: if __name__ == '__main__':
fg.stop() main()
runner = None
if options.kml is not None:
kmlgen.done = True
break

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 # Find PyQt4
# ~~~~~~~~~~ # ~~~~~~~~~~
# Copyright (c) 2007-2008, Simon Edwards <simon@simonzone.com> # 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. # Redistribution and use is allowed according to the terms of the BSD license.
# For details see the accompanying COPYING-CMAKE-SCRIPTS file. # For details see the accompanying COPYING-CMAKE-SCRIPTS file.
# #
@@ -22,10 +23,11 @@
# #
# PYQT4_SIP_FLAGS - The SIP flags used to build PyQt. # 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 # Already in cache, be silent
SET(PYQT4_FOUND TRUE) 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}) FIND_FILE(_find_pyqt_py FindPyQt.py PATHS ${CMAKE_MODULE_PATH})
@@ -40,14 +42,20 @@ ELSE(EXISTS PYQT4_VERSION)
SET(PYQT4_FOUND TRUE) SET(PYQT4_FOUND TRUE)
ENDIF(pyqt_config) 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) IF(NOT PYQT4_FIND_QUIETLY)
MESSAGE(STATUS "Found PyQt4 version: ${PYQT4_VERSION_STR}") MESSAGE(STATUS "Found PyQt4 version: ${PYQT4_VERSION_STR}")
MESSAGE(STATUS "Found pyuic4: ${PYUIC4_EXECUTABLE}")
ENDIF(NOT PYQT4_FIND_QUIETLY) ENDIF(NOT PYQT4_FIND_QUIETLY)
ELSE(PYQT4_FOUND) ELSE(PYQT4_FOUND AND PYUIC4_FOUND)
IF(PYQT4_FIND_REQUIRED) IF(PYQT4_FIND_REQUIRED)
MESSAGE(FATAL_ERROR "Could not find Python") MESSAGE(FATAL_ERROR "Could not find Python")
ENDIF(PYQT4_FIND_REQUIRED) 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 #ifndef INCLUDED_AIR_MODES_API_H
#define INCLUDED_AIR_MODES_API_H #define INCLUDED_AIR_MODES_API_H
#include <gruel/attributes.h> #include <gnuradio/attributes.h>
#ifdef AIR_MODES_EXPORTS #ifdef AIR_MODES_EXPORTS
# define AIR_MODES_API __GR_ATTR_EXPORT # define AIR_MODES_API __GR_ATTR_EXPORT

View File

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

View File

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

View File

@@ -27,7 +27,7 @@ add_library(air_modes SHARED
air_modes_slicer.cc air_modes_slicer.cc
modes_crc.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 DEFINE_SYMBOL "AIR_MODES_EXPORTS")
set_target_properties(air_modes PROPERTIES SOVERSION "${gr-gr-air-modes_VERSION_MAJOR}") 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}") 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 2010 Nick Foster
# Copyright 2013 Nicholas Corgan
# #
# This file is part of gr-air-modes # This file is part of gr-air-modes
# #
@@ -24,11 +25,12 @@
#include "config.h" #include "config.h"
#endif #endif
#include <ciso646>
#include <air_modes_preamble.h> #include <air_modes_preamble.h>
#include <gr_io_signature.h> #include <gnuradio/io_signature.h>
#include <string.h> #include <string.h>
#include <iostream> #include <iostream>
#include <gr_tags.h> #include <gnuradio/tags.h>
air_modes_preamble_sptr air_make_modes_preamble(int channel_rate, float threshold_db) 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) : air_modes_preamble::air_modes_preamble(int channel_rate, float threshold_db) :
gr_block ("modes_preamble", 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::io_signature::make2 (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::io_signature::make (1, 1, sizeof(float))) //the output packets
{ {
d_chip_rate = 2000000; //2Mchips per second d_chip_rate = 2000000; //2Mchips per second
d_samples_per_chip = channel_rate / d_chip_rate; //must be integer number of samples per chip to work d_samples_per_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; std::stringstream str;
str << name() << unique_id(); str << name() << unique_id();
d_me = pmt::pmt_string_to_symbol(str.str()); d_me = pmt::string_to_symbol(str.str());
d_key = pmt::pmt_string_to_symbol("preamble_found"); d_key = pmt::string_to_symbol("preamble_found");
set_history(d_samples_per_symbol); 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 //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; uint64_t ts_sample, last_whole_stamp;
double last_frac_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_whole_stamp = pmt::to_uint64(pmt::tuple_ref(tstamp.value, 0));
last_frac_stamp = pmt::pmt_to_double(pmt::pmt_tuple_ref(tstamp.value, 1)); last_frac_stamp = pmt::to_double(pmt::tuple_ref(tstamp.value, 1));
ts_sample = tstamp.offset; ts_sample = tstamp.offset;
double tstime = double(abs_sample_cnt * secs_per_sample) + last_whole_stamp + last_frac_stamp; 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); uint64_t abs_sample_cnt = nitems_read(0);
std::vector<gr_tag_t> tstamp_tags; 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")); 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. //tags.back() is the most recent timestamp, then.
if(tstamp_tags.size() > 0) { if(tstamp_tags.size() > 0) {
d_timestamp = tstamp_tags.back(); d_timestamp = tstamp_tags.back();
@@ -193,7 +195,7 @@ int air_modes_preamble::general_work(int noutput_items,
add_item_tag(0, //stream ID add_item_tag(0, //stream ID
nitems_written(0), //sample nitems_written(0), //sample
d_key, //frame_info d_key, //frame_info
pmt::pmt_from_double(tstamp), pmt::from_double(tstamp),
d_me //block src id d_me //block src id
); );

View File

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

View File

@@ -34,6 +34,7 @@ GR_PYTHON_INSTALL(
altitude.py altitude.py
az_map.py az_map.py
cpr.py cpr.py
html_template.py
mlat.py mlat.py
exceptions.py exceptions.py
flightgear.py flightgear.py
@@ -41,10 +42,13 @@ GR_PYTHON_INSTALL(
kml.py kml.py
parse.py parse.py
msprint.py msprint.py
radio.py
raw_server.py raw_server.py
rx_path.py rx_path.py
sbs1.py sbs1.py
sql.py sql.py
types.py
zmq_socket.py
Quaternion.py Quaternion.py
DESTINATION ${GR_PYTHON_DIR}/air_modes DESTINATION ${GR_PYTHON_DIR}/air_modes
) )

View File

@@ -52,14 +52,20 @@ from air_modes_swig import *
# import any pure python here # import any pure python here
# #
from rx_path import rx_path 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 msprint import output_print
from sql import output_sql from sql import output_sql
from sbs1 import output_sbs1 from sbs1 import output_sbs1
from kml import output_kml from kml import output_kml, output_jsonp
from raw_server import raw_server from raw_server import raw_server
from radio import modes_radio
from exceptions import * from exceptions import *
from az_map 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 #this is try/excepted in case the user doesn't have numpy installed
try: try:
from flightgear import output_flightgear from flightgear import output_flightgear

View File

@@ -26,6 +26,7 @@ from PyQt4 import QtCore, QtGui
import threading import threading
import math import math
import air_modes import air_modes
from air_modes.exceptions import *
# model has max range vs. azimuth in n-degree increments # model has max range vs. azimuth in n-degree increments
@@ -168,30 +169,30 @@ class az_map(QtGui.QWidget):
self.ringsize = ringsize self.ringsize = ringsize
self.drawPath() self.drawPath()
class az_map_output(air_modes.parse): class az_map_output:
def __init__(self, mypos, model): def __init__(self, cprdec, model, pub):
air_modes.parse.__init__(self, mypos) self._cpr = cprdec
self.model = model self.model = model
pub.subscribe("type17_dl", self.output)
def output(self, msg): def output(self, msg):
[data, ecc, reference, timestamp] = msg.split() try:
data = air_modes.modes_reply(long(data, 16)) msgtype = msg.data["df"]
ecc = long(ecc, 16) now = time.time()
rssi = 10.*math.log10(float(reference))
msgtype = data["df"]
now = time.time()
if msgtype == 17: if msgtype == 17:
icao = data["aa"] icao = msg.data["aa"]
subtype = data["ftc"] subtype = msg.data["ftc"]
distance, altitude, bearing = [0,0,0] distance, altitude, bearing = [0,0,0]
if 5 <= subtype <= 8: if 5 <= subtype <= 8:
(ground_track, decoded_lat, decoded_lon, distance, bearing) = self.parseBDS06(data) (ground_track, decoded_lat, decoded_lon, distance, bearing) = air_modes.parseBDS06(msg.data, self._cpr)
altitude = 0 altitude = 0
elif 9 <= subtype <= 18: elif 9 <= subtype <= 18:
(altitude, decoded_lat, decoded_lon, distance, bearing) = self.parseBDS05(data) (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.evenlist_sfc = {}
self.oddlist_sfc = {} self.oddlist_sfc = {}
def set_location(new_location): def set_location(self, new_location):
self.my_location = new_location self.my_location = new_location
def weed_poslists(self): def weed_poslists(self):

View File

@@ -24,12 +24,12 @@ class ADSBError(Exception):
class MetricAltError(ADSBError): class MetricAltError(ADSBError):
pass pass
class ParserError(ADSBError): class ParserError(ADSBError):
pass pass
class NoHandlerError(ADSBError): class NoHandlerError(ADSBError):
def __init__(self, msgtype): def __init__(self, msgtype=None):
self.msgtype = msgtype self.msgtype = msgtype
class MlatNonConvergeError(ADSBError): class MlatNonConvergeError(ADSBError):

View File

@@ -14,49 +14,48 @@ from Quaternion import Quat
import numpy import numpy
from air_modes.exceptions import * from air_modes.exceptions import *
class output_flightgear(air_modes.parse): class output_flightgear:
def __init__(self, localpos, hostname, port): def __init__(self, cprdec, hostname, port, pub):
air_modes.parse.__init__(self, localpos)
self.hostname = hostname self.hostname = hostname
self.port = port self.port = port
self.localpos = localpos self.localpos = localpos
self.positions = {} self.positions = {}
self.velocities = {} self.velocities = {}
self.callsigns = {} self.callsigns = {}
self._cpr = cprdec
self.sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) self.sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
self.sock.connect((self.hostname, self.port)) self.sock.connect((self.hostname, self.port))
pub.subscribe("type17_dl", output)
def output(self, message): def output(self, msg):
[data, ecc, reference, timestamp] = message.split()
data = air_modes.modes_reply(long(data, 16))
try: try:
msgtype = data["df"] msgtype = msg.data["df"]
if msgtype == 17: #ADS-B report if msgtype == 17: #ADS-B report
icao24 = data["aa"] icao24 = msg.data["aa"]
bdsreg = data["me"].get_type() bdsreg = msg.data["me"].get_type()
if bdsreg == 0x08: #ident packet if bdsreg == 0x08: #ident packet
(ident, actype) = self.parseBDS08(data) (ident, actype) = air_modes.parseBDS08(data)
#select model based on actype #select model based on actype
self.callsigns[icao24] = [ident, actype] self.callsigns[icao24] = [ident, actype]
elif bdsreg == 0x06: #BDS0,6 pos 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.positions[icao24] = [decoded_lat, decoded_lon, 0]
self.update(icao24) self.update(icao24)
elif bdsreg == 0x05: #BDS0,5 pos 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.positions[icao24] = [decoded_lat, decoded_lon, altitude]
self.update(icao24) self.update(icao24)
elif bdsreg == 0x09: #velocity elif bdsreg == 0x09: #velocity
subtype = data["bds09"].get_type() subtype = data["bds09"].get_type()
if subtype == 0: 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: elif subtype == 1:
[velocity, heading, vert_spd] = self.parseBDS09_1(data) [velocity, heading, vert_spd] = air_modes.parseBDS09_1(data)
turnrate = 0 turnrate = 0
else: else:
return return

View File

@@ -22,9 +22,11 @@
# This file contains data models, view delegates, and associated classes # This file contains data models, view delegates, and associated classes
# for handling the GUI back end data model. # for handling the GUI back end data model.
from PyQt4 import QtCore, QtGui from PyQt4 import QtCore, QtGui, QtSql
import air_modes import air_modes
import threading, math, time 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, #fades the ICAOs out as their last report gets older,
#and display ident if available, ICAO otherwise #and display ident if available, ICAO otherwise
@@ -36,12 +38,16 @@ class ICAOViewDelegate(QtGui.QStyledItemDelegate):
painter.drawRect(option.rect) painter.drawRect(option.rect)
#if there's an ident available, use it. otherwise print the ICAO #if there's an ident available, use it. otherwise print the ICAO
if index.model().data(index.model().index(index.row(), 9)) != QtCore.QVariant(): if index.model().data(index.model().index(index.row(), 8)) != QtCore.QVariant():
paintstr = index.model().data(index.model().index(index.row(), 9)).toString() paintstr = index.model().data(index.model().index(index.row(), 8)).toString()
else: else:
paintstr = index.model().data(index.model().index(index.row(), 0)).toString() 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 max_age = 60. #age at which it grays out
#minimum alpha is 0x40 (oldest), max is 0xFF (newest) #minimum alpha is 0x40 (oldest), max is 0xFF (newest)
age = min(age, max_age) age = min(age, max_age)
@@ -49,150 +55,36 @@ class ICAOViewDelegate(QtGui.QStyledItemDelegate):
painter.setPen(QtGui.QColor(0, 0, 0, alpha)) 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) 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_sql_model(QtCore.QAbstractTableModel):
class dashboard_data_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): def __init__(self, parent):
QtCore.QAbstractTableModel.__init__(self, parent) QtSql.QSqlQueryModel.__init__(self, parent)
self._data = [] self._query = """select tab1.icao, tab1.seen, tab1.lat, tab1.lon, tab1.alt, speed, heading, vertical, ident, type
self.lock = threading.Lock() from (select * from (select * from positions order by seen desc) group by icao) tab1
self._colnames = ["icao", "seen", "rssi", "latitude", "longitude", "altitude", "speed", "heading", "vertical", "ident", "type", "range", "bearing"] left join (select * from (select * from vectors order by seen desc) group by icao) tab2
#custom precision limits for display on tab1.icao=tab2.icao
self._precisions = [None, None, None, 6, 6, 0, 0, 0, 0, None, None, 2, 0] left join (select * from (select * from ident)) tab3
for field in self._colnames: on tab1.icao=tab3.icao
self.setHeaderData(self._colnames.index(field), QtCore.Qt.Horizontal, field) where tab1.seen > datetime('now', '-1 minute')"""
def rowCount(self, parent=QtCore.QVariant()): self._sql = None
return len(self._data) self._db = QtSql.QSqlDatabase("QSQLITE")
def columnCount(self, parent=QtCore.QVariant()): self._db.setDatabaseName("adsb.db") #TODO specify this elsewhere
return len(self._colnames) self._db.open()
def data(self, index, role=QtCore.Qt.DisplayRole): #what is this i don't even
if not index.isValid(): #fetches the combined data of all three tables for all ICAOs seen in the last minute.
return QtCore.QVariant() #FIXME PyQt's SQLite gives you different results than the SQLite browser
if index.row() >= self.rowCount(): self.setQuery(self._query, self._db)
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()])
def setData(self, index, value, role=QtCore.Qt.EditRole): #the big club
self.lock.acquire() def update_all(self, icao):
if not index.isValid(): self.setQuery(self._query, self._db)
return False #self.dataChanged.emit(self.createIndex(0, 0), self.createIndex(self.rowCount(), self.columnCount()))
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)

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.my_coords = localpos
self._timeout = timeout self._timeout = timeout
self._lock = lock self._lock = lock
self.done = False self.shutdown = threading.Event()
self.finished = threading.Event()
self.setDaemon(1) self.setDaemon(1)
self.start() self.start()
def run(self): def run(self):
self._db = sqlite3.connect(self._dbname) #read from the db self._db = sqlite3.connect(self._dbname) #read from the db
while self.done is False: while self.shutdown.is_set() is False:
self.writekml() self.writekml()
time.sleep(self._timeout) time.sleep(self._timeout)
self.done = True
self._db.close() self._db.close()
self._db = None 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): def writekml(self):
kmlstr = self.genkml() kmlstr = self.genkml()
if kmlstr is not None: if kmlstr is not None:
@@ -55,7 +64,7 @@ class output_kml(threading.Thread):
def locked_execute(self, c, query): def locked_execute(self, c, query):
with self._lock: with self._lock:
c.execute(query) c.execute(query)
def draw_circle(self, center, rng): def draw_circle(self, center, rng):
retstr = "" retstr = ""
steps=30 steps=30
@@ -82,7 +91,7 @@ class output_kml(threading.Thread):
retstr = string.lstrip(retstr) retstr = string.lstrip(retstr)
return retstr return retstr
def genkml(self): def genkml(self):
#first let's draw the static content #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>""" 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]: 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 += """\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"""
retstr += """\t<Folder>\n\t\t<name>Aircraft locations</name>\n\t\t<open>0</open>""" retstr += """\t<Folder>\n\t\t<name>Aircraft locations</name>\n\t\t<open>0</open>"""
#read the database and add KML #read the database and add KML
@@ -115,11 +124,11 @@ class output_kml(threading.Thread):
if lon is None: lon = 0 if lon is None: lon = 0
alt = track[0][2] alt = track[0][2]
if alt is None: alt = 0 if alt is None: alt = 0
metric_alt = alt * 0.3048 #google earth takes meters, the commie bastards metric_alt = alt * 0.3048 #google earth takes meters, the commie bastards
trackstr = "" trackstr = ""
for pos in track: for pos in track:
trackstr += " %f,%f,%f" % (pos[4], pos[3], pos[2]*0.3048) trackstr += " %f,%f,%f" % (pos[4], pos[3], pos[2]*0.3048)
@@ -153,7 +162,7 @@ class output_kml(threading.Thread):
seen = 0 seen = 0
speed = 0 speed = 0
heading = 0 heading = 0
vertical = 0 vertical = 0
#now generate some KML #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, ) 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>' retstr+= '\n\t</Folder>\n</Document>\n</kml>'
return retstr 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 * from air_modes.exceptions import *
import math import math
class output_print(air_modes.parse): #TODO get rid of class and convert to functions
def __init__(self, mypos): #no need for class here
air_modes.parse.__init__(self, mypos) class output_print:
def __init__(self, cpr, publisher, callback=None):
def parse(self, message): self._cpr = cpr
[data, ecc, reference, timestamp] = message.split() self._callback = callback
#sub to every function that starts with "handle"
ecc = long(ecc, 16) self._fns = [int(l[6:]) for l in dir(self) if l.startswith("handle")]
reference = float(reference) for i in self._fns:
timestamp = float(timestamp) publisher.subscribe("type%i_dl" % i, getattr(self, "handle%i" % i))
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)
else: publisher.subscribe("modes_dl", self.catch_nohandler)
retstr = "Type 17 BDS0,9-%i from %x not implemented" % (subtype, icao24)
elif bdsreg == 0x62: @staticmethod
emerg_str = self.parseBDS62(data) def prefix(msg):
retstr = "Type 17 BDS6,2 (emergency) from %x type %s" % (icao24, emerg_str) return "(%i %.8f) " % (msg.rssi, msg.timestamp)
def _print(self, msg):
if self._callback is None:
print msg
else: 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): except ADSBError:
msgtype = data["df"] return
if msgtype == 20 or msgtype == 16:
#type 16 does not have fs, dr, um but we get alt here if msg.data["vs"] is 1:
[fs, dr, um, alt] = self.parse4(data) retstr += " (aircraft is on the ground)"
elif msgtype == 21:
[fs, dr, um, ident] = self.parse5(data) 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: if msgtype == 16:
bds1 = data["vds1"] bds1 = msg.data["vds1"]
bds2 = data["vds2"] bds2 = msg.data["vds2"]
else: else:
bds1 = data["bds1"] bds1 = msg.data["bds1"]
bds2 = data["bds2"] bds2 = msg.data["bds2"]
retstr = output_print.prefix(msg)
if bds2 != 0: 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: 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: elif bds1 == 1:
retstr = "Type %i link capability report from %x: ACS: 0x%x, BCS: 0x%x, ECS: 0x%x, continues %i" \ 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"]) % (msgtype, msg.ecc, msg.data["acs"], msg.data["bcs"], msg.data["ecs"], msg.data["cfs"])
elif bds1 == 2: 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: elif bds1 == 3:
retstr = "Type %i TCAS report from %x: " % (msgtype, ecc) retstr += "Type %i TCAS report from %x: " % (msgtype, msg.ecc)
tti = data["tti"] tti = msg.data["tti"]
if msgtype == 16: 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) retstr += "advised: %s complement: %s" % (resolutions, complements)
else: else:
if tti == 1: 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) retstr += "threat ID: %x advised: %s complement: %s" % (threat_id, resolutions, complements)
elif tti == 2: 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) retstr += "range: %i bearing: %i alt: %i advised: %s complement: %s" % (threat_range, threat_bearing, threat_alt, resolutions, complements)
else: else:
rat = 0 rat = 0
@@ -233,11 +223,15 @@ class output_print(air_modes.parse):
if rat == 1: if rat == 1:
retstr += " (resolved)" retstr += " (resolved)"
else: 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): if(msgtype == 20 or msgtype == 16):
retstr += " at %ift" % alt retstr += " at %ift" % air_modes.decode_alt(msg.data["ac"], True)
else: else:
retstr += " ident %x" % ident retstr += " ident %x" % air_modes.decode_id(msg.data["id"])
return retstr 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 string import split, join
from altitude import decode_alt from altitude import decode_alt
import math import math
import air_modes
from air_modes.exceptions import * from air_modes.exceptions import *
from air_modes import cpr
#this implements a packet class which can retrieve its own fields. #this implements a packet class which can retrieve its own fields.
class data_field: class data_field:
@@ -231,198 +231,207 @@ class modes_reply(data_field):
def get_type(self): def get_type(self):
return self.get_bits(1,5) return self.get_bits(1,5)
class parse: #unscramble mode A/C-style squawk codes for type 5 replies below
def __init__(self, mypos): def decode_id(id):
self.my_location = mypos
self.cpr = cpr.cpr_decoder(self.my_location) C1 = 0x1000
A1 = 0x0800
def parse0(self, data): C2 = 0x0400
altitude = decode_alt(data["ac"], True) A2 = 0x0200 #this represents the order in which the bits come
return [data["vs"], data["cc"], data["sl"], data["ri"], altitude] 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): #decode ident squawks
altitude = decode_alt(data["ac"], True) def charmap(d):
return [data["fs"], data["dr"], data["um"], altitude] 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 retval
return [data["fs"], data["dr"], data["um"], data["id"]]
def parse11(self, data, ecc):
interrogator = ecc & 0x0F
return [data["aa"], interrogator, data["ca"]]
def parseBDS08(data):
categories = [["NO INFO", "RESERVED", "RESERVED", "RESERVED", "RESERVED", "RESERVED", "RESERVED", "RESERVED"],\ 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", "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", "GLIDER", "BALLOON/BLIMP", "PARACHUTE", "ULTRALIGHT", "RESERVED", "UAV", "SPACECRAFT"],\
["NO INFO", "LIGHT", "SMALL", "LARGE", "LARGE HIGH VORTEX", "HEAVY", "HIGH PERFORMANCE", "ROTORCRAFT"]] ["NO INFO", "LIGHT", "SMALL", "LARGE", "LARGE HIGH VORTEX", "HEAVY", "HIGH PERFORMANCE", "ROTORCRAFT"]]
def parseBDS08(self, data): catstring = categories[data["ftc"]-1][data["cat"]]
catstring = self.categories[data["ftc"]-1][data["cat"]]
msg = "" msg = ""
for i in range(0, 8): for i in range(0, 8):
msg += self.charmap(data["ident"] >> (42-6*i) & 0x3F) msg += charmap(data["ident"] >> (42-6*i) & 0x3F)
return (msg, catstring) return (msg, catstring)
def charmap(self, d): #NOTE: this is stateful -- requires CPR decoder
if d > 0 and d < 27: def parseBDS05(data, cprdec):
retval = chr(ord("A")+d-1) altitude = decode_alt(data["alt"], False)
elif d == 32: [decoded_lat, decoded_lon, rnge, bearing] = cprdec.decode(data["aa"], data["lat"], data["lon"], data["cpr"], 0)
retval = " " return [altitude, decoded_lat, decoded_lon, rnge, bearing]
elif d > 47 and d < 58:
retval = chr(ord("0")+d-48)
else:
retval = " "
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): def parseBDS09_0(data):
icao24 = data["aa"] #0: ["sub", "dew", "vew", "dns", "vns", "str", "tr", "svr", "vr"],
vert_spd = data["vr"] * 32
encoded_lon = data["lon"] ud = bool(data["dvr"])
encoded_lat = data["lat"] if ud:
cpr_format = data["cpr"] vert_spd = 0 - vert_spd
altitude = decode_alt(data["alt"], False) turn_rate = data["tr"] * 15/62
rl = data["str"]
[decoded_lat, decoded_lon, rnge, bearing] = self.cpr.decode(icao24, encoded_lat, encoded_lon, cpr_format, 0) if rl:
turn_rate = 0 - turn_rate
return [altitude, decoded_lat, decoded_lon, rnge, bearing] ns_vel = data["vns"] - 1
ns = bool(data["dns"])
ew_vel = data["vew"] - 1
#welp turns out it looks like there's only 17 bits in the BDS0,6 ground packet after all. ew = bool(data["dew"])
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"])
velocity = math.hypot(ns_vel, ew_vel) velocity = math.hypot(ns_vel, ew_vel)
if ew: if ew:
ew_vel = 0 - ew_vel ew_vel = 0 - ew_vel
if ns: if ns:
ns_vel = 0 - ns_vel ns_vel = 0 - ns_vel
heading = math.atan2(ew_vel, ns_vel) * (180.0 / math.pi) heading = math.atan2(ew_vel, ns_vel) * (180.0 / math.pi)
if heading < 0: if heading < 0:
heading += 360 heading += 360
return [velocity, heading, vert_spd, turn_rate] return [velocity, heading, vert_spd, turn_rate]
def parseBDS09_1(self, data): def parseBDS09_1(data):
#1: ["sub", "icf", "ifr", "nuc", "dew", "vew", "dns", "vns", "vrsrc", "dvr", "vr", "dhd", "hd"], #1: ["sub", "icf", "ifr", "nuc", "dew", "vew", "dns", "vns", "vrsrc", "dvr", "vr", "dhd", "hd"],
alt_geo_diff = data["hd"] * 25 alt_geo_diff = data["hd"] * 25
above_below = bool(data["dhd"]) above_below = bool(data["dhd"])
if above_below: if above_below:
alt_geo_diff = 0 - alt_geo_diff; alt_geo_diff = 0 - alt_geo_diff;
vert_spd = float(data["vr"] - 1) * 64 vert_spd = float(data["vr"] - 1) * 64
ud = bool(data["dvr"]) ud = bool(data["dvr"])
if ud: if ud:
vert_spd = 0 - vert_spd vert_spd = 0 - vert_spd
vert_src = bool(data["vrsrc"]) vert_src = bool(data["vrsrc"])
ns_vel = float(data["vns"]) ns_vel = float(data["vns"])
ns = bool(data["dns"]) ns = bool(data["dns"])
ew_vel = float(data["vew"]) ew_vel = float(data["vew"])
ew = bool(data["dew"]) ew = bool(data["dew"])
subtype = data["sub"] subtype = data["sub"]
if subtype == 0x02: if subtype == 0x02:
ns_vel <<= 2 ns_vel <<= 2
ew_vel <<= 2 ew_vel <<= 2
velocity = math.hypot(ns_vel, ew_vel) velocity = math.hypot(ns_vel, ew_vel)
if ew: if ew:
ew_vel = 0 - ew_vel ew_vel = 0 - ew_vel
if ns_vel == 0: if ns_vel == 0:
heading = 0 heading = 0
else: else:
heading = math.atan(float(ew_vel) / float(ns_vel)) * (180.0 / math.pi) heading = math.atan(float(ew_vel) / float(ns_vel)) * (180.0 / math.pi)
if ns: if ns:
heading = 180 - heading heading = 180 - heading
if heading < 0: if heading < 0:
heading += 360 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", #3: {"sub", "icf", "ifr", "nuc", "mhs", "hdg", "ast", "spd", "vrsrc",
# "dvr", "vr", "dhd", "hd"} # "dvr", "vr", "dhd", "hd"}
mag_hdg = data["mhs"] * 360. / 1024 mag_hdg = data["mhs"] * 360. / 1024
vel_src = "TAS" if data["ast"] == 1 else "IAS" vel_src = "TAS" if data["ast"] == 1 else "IAS"
vel = data["spd"] vel = data["spd"]
if data["sub"] == 4: if data["sub"] == 4:
vel *= 4 vel *= 4
vert_spd = float(data["vr"] - 1) * 64 vert_spd = float(data["vr"] - 1) * 64
if data["dvr"] == 1: if data["dvr"] == 1:
vert_spd = 0 - vert_spd vert_spd = 0 - vert_spd
geo_diff = float(data["hd"] - 1) * 25 geo_diff = float(data["hd"] - 1) * 25
return [mag_hdg, vel_src, vel, vert_spd, geo_diff] return [mag_hdg, vel_src, vel, vert_spd, geo_diff]
def parseBDS62(self, data): def parseBDS62(data):
eps_strings = ["NO EMERGENCY", "GENERAL EMERGENCY", "LIFEGUARD/MEDICAL", "FUEL EMERGENCY", eps_strings = ["NO EMERGENCY", "GENERAL EMERGENCY", "LIFEGUARD/MEDICAL", "FUEL EMERGENCY",
"NO COMMUNICATIONS", "UNLAWFUL INTERFERENCE", "RESERVED", "RESERVED"] "NO COMMUNICATIONS", "UNLAWFUL INTERFERENCE", "RESERVED", "RESERVED"]
return eps_strings[data["eps"]] return eps_strings[data["eps"]]
def parseMB_id(self, data): #bds1 == 2, bds2 == 0 def parseMB_id(data): #bds1 == 2, bds2 == 0
msg = "" msg = ""
for i in range(0, 8): for i in range(0, 8):
msg += self.charmap( data["ais"] >> (42-6*i) & 0x3F) msg += charmap( data["ais"] >> (42-6*i) & 0x3F)
return (msg) return (msg)
def parseMB_TCAS_resolutions(self, data): def parseMB_TCAS_resolutions(data):
#these are LSB because the ICAO are asshats #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", 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", 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", 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"} 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"} rac_bits = {55: "DON'T DESCEND", 56: "DON'T CLIMB", 57: "DON'T TURN LEFT", 58: "DON'T TURN RIGHT"}
ara = data["ara"] ara = data["ara"]
rac = data["rac"] rac = data["rac"]
#check to see which bits are set #check to see which bits are set
resolutions = "" resolutions = ""
for bit in ara_bits: for bit in ara_bits:
if ara & (1 << (54-bit)): if ara & (1 << (54-bit)):
resolutions += " " + ara_bits[bit] resolutions += " " + ara_bits[bit]
complements = "" complements = ""
for bit in rac_bits: for bit in rac_bits:
if rac & (1 << (58-bit)): if rac & (1 << (58-bit)):
complements += " " + rac_bits[bit] complements += " " + rac_bits[bit]
return (resolutions, complements) return (resolutions, complements)
#rat is 1 if resolution advisory terminated <18s ago #rat is 1 if resolution advisory terminated <18s ago
#mte is 1 if multiple threats indicated #mte is 1 if multiple threats indicated
#tti is threat type: 1 if ID, 2 if range/brg/alt #tti is threat type: 1 if ID, 2 if range/brg/alt
#tida is threat altitude in Mode C format #tida is threat altitude in Mode C format
def parseMB_TCAS_threatid(self, data): #bds1==3, bds2==0, TTI==1 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), #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)} # "mte": (60,1), "tti": (61,2), "tida": (63,13), "tidr": (76,7), "tidb": (83,6)}
(resolutions, complements) = self.parseMB_TCAS_resolutions(data) (resolutions, complements) = parseMB_TCAS_resolutions(data)
return (resolutions, complements, data["rat"], data["mte"], data["tid"]) return (resolutions, complements, data["rat"], data["mte"], data["tid"])
def parseMB_TCAS_threatloc(self, data): #bds1==3, bds2==0, TTI==2 def parseMB_TCAS_threatloc(data): #bds1==3, bds2==0, TTI==2
(resolutions, complements) = self.parseMB_TCAS_resolutions(data) (resolutions, complements) = parseMB_TCAS_resolutions(data)
threat_alt = decode_alt(data["tida"], True) threat_alt = decode_alt(data["tida"], True)
return (resolutions, complements, data["rat"], data["mte"], threat_alt, data["tidr"], data["tidb"]) return (resolutions, complements, data["rat"], data["mte"], threat_alt, data["tidr"], data["tidb"])
#type 16 Coordination Reply Message #type 16 Coordination Reply Message
def parse_TCAS_CRM(self, data): def parse_TCAS_CRM(data):
(resolutions, complements) = self.parseMB_TCAS_resolutions(data) (resolutions, complements) = parseMB_TCAS_resolutions(data)
return (resolutions, complements, data["rat"], data["mte"]) 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 # This file is part of gr-air-modes
# #
@@ -19,7 +19,7 @@
# Boston, MA 02110-1301, USA. # Boston, MA 02110-1301, USA.
# #
from gnuradio import gr from gnuradio import gr, blocks
import air_modes_swig import air_modes_swig
class rx_path(gr.hier_block2): class rx_path(gr.hier_block2):
@@ -35,17 +35,17 @@ class rx_path(gr.hier_block2):
self._spc = int(rate/2e6) self._spc = int(rate/2e6)
# Convert incoming I/Q baseband to amplitude # Convert incoming I/Q baseband to amplitude
self._demod = gr.complex_to_mag() self._demod = blocks.complex_to_mag()
self._bb = self._demod self._bb = self._demod
# Pulse matched filter for 0.5us pulses # Pulse matched filter for 0.5us pulses
if use_pmf: 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.connect(self._demod, self._pmf)
self._bb = self._pmf self._bb = self._pmf
# Establish baseline amplitude (noise, interference) # 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 # Synchronize to Mode-S preamble
self._sync = air_modes_swig.modes_preamble(self._rate, self._threshold) 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._sync, 0))
self.connect(self._bb, self._avg, (self._sync, 1)) self.connect(self._bb, self._avg, (self._sync, 1))
self.connect(self._sync, self._slicer) 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 import air_modes
from datetime import * from datetime import *
from air_modes.exceptions import * from air_modes.exceptions import *
import threading
class output_sbs1(air_modes.parse): class dumb_task_runner(threading.Thread):
def __init__(self, mypos, port): def __init__(self, task, interval):
air_modes.parse.__init__(self, mypos) 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 = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self._s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) self._s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
self._s.bind(('', port)) 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_map = {} # dictionary of icao24 to aircraft IDs
self._aircraft_id_count = 0 # Current Aircraft ID count 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): def __del__(self):
self._s.close() self._s.close()
@@ -94,41 +124,36 @@ class output_sbs1(air_modes.parse):
elif fs == 3: elif fs == 3:
return "1,0,0,1" return "1,0,0,1"
elif fs == 4: elif fs == 4:
return "1,0,0," return "1,0,1,"
elif fs == 5: elif fs == 5:
return "0,0,0," return "0,0,1,"
else: else:
return ",,," return ",,,"
def parse(self, message): def parse(self, msg):
#assembles a SBS-1-style output string from the received message #assembles a SBS-1-style output string from the received message
[data, ecc, reference, timestamp] = message.split() msgtype = msg.data["df"]
data = air_modes.modes_reply(long(data, 16))
ecc = long(ecc, 16)
msgtype = data["df"]
outmsg = None outmsg = None
if msgtype == 0: if msgtype == 0:
outmsg = self.pp0(data, ecc) outmsg = self.pp0(msg.data, msg.ecc)
elif msgtype == 4: elif msgtype == 4:
outmsg = self.pp4(data, ecc) outmsg = self.pp4(msg.data, msg.ecc)
elif msgtype == 5: elif msgtype == 5:
outmsg = self.pp5(data, ecc) outmsg = self.pp5(msg.data, msg.ecc)
elif msgtype == 11: elif msgtype == 11:
outmsg = self.pp11(data, ecc) outmsg = self.pp11(msg.data, msg.ecc)
elif msgtype == 17: elif msgtype == 17:
outmsg = self.pp17(data) outmsg = self.pp17(msg.data)
else: else:
raise NoHandlerError(msgtype) raise NoHandlerError(msgtype)
return outmsg return outmsg
def pp0(self, shortdata, ecc): def pp0(self, shortdata, ecc):
[datestr, timestr] = self.current_time() [datestr, timestr] = self.current_time()
[vs, cc, sl, ri, altitude] = self.parse0(shortdata)
aircraft_id = self.get_aircraft_id(ecc) 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: if vs:
retstr += "1\r\n" retstr += "1\r\n"
else: else:
@@ -137,24 +162,20 @@ class output_sbs1(air_modes.parse):
def pp4(self, shortdata, ecc): def pp4(self, shortdata, ecc):
[datestr, timestr] = self.current_time() [datestr, timestr] = self.current_time()
[fs, dr, um, altitude] = self.parse4(shortdata)
aircraft_id = self.get_aircraft_id(ecc) 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) 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(fs) + "\r\n" return retstr + self.decode_fs(shortdata["fs"]) + "\r\n"
def pp5(self, shortdata, ecc): def pp5(self, shortdata, ecc):
# I'm not sure what to do with the identiifcation shortdata & 0x1FFF
[datestr, timestr] = self.current_time() [datestr, timestr] = self.current_time()
[fs, dr, um, ident] = self.parse5(shortdata)
aircraft_id = self.get_aircraft_id(ecc) 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) 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(fs) + "\r\n" return retstr + self.decode_fs(shortdata["fs"]) + "\r\n"
def pp11(self, shortdata, ecc): def pp11(self, shortdata, ecc):
[datestr, timestr] = self.current_time() [datestr, timestr] = self.current_time()
[icao24, interrogator, ca] = self.parse11(shortdata, ecc)
aircraft_id = self.get_aircraft_id(icao24) 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): def pp17(self, data):
icao24 = data["aa"] icao24 = data["aa"]
@@ -168,12 +189,12 @@ class output_sbs1(air_modes.parse):
if bdsreg == 0x08: if bdsreg == 0x08:
# Aircraft Identification # 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) 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: elif bdsreg == 0x06:
# Surface position measurement # 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 altitude = 0
if decoded_lat is None: #no unambiguously valid position available if decoded_lat is None: #no unambiguously valid position available
retstr = None retstr = None
@@ -183,7 +204,7 @@ class output_sbs1(air_modes.parse):
elif bdsreg == 0x05: elif bdsreg == 0x05:
# Airborne position measurements # Airborne position measurements
# WRONG (rnge, bearing), is this still true? # 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 if decoded_lat is None: #no unambiguously valid position available
retstr = None retstr = None
else: else:
@@ -194,7 +215,7 @@ class output_sbs1(air_modes.parse):
# WRONG (heading, vert_spd), Is this still true? # WRONG (heading, vert_spd), Is this still true?
subtype = data["bds09"].get_type() subtype = data["bds09"].get_type()
if subtype == 0 or subtype == 1: 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) [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) 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 air_modes
import sqlite3 import sqlite3
from air_modes.exceptions import * from air_modes.exceptions import *
from gnuradio.gr.pubsub import pubsub
class output_sql(air_modes.parse): class output_sql:
def __init__(self, mypos, filename, lock): def __init__(self, cpr, filename, lock, publisher):
air_modes.parse.__init__(self, mypos) #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 def insert(self, message):
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):
with self._lock: with self._lock:
try: try:
#we're checking to see if the db is empty, and creating the db object #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 #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 #the thread context of output(), rather than the thread context of the
#constructor. that way you can spawn a thread to do output(). #constructor.
if self.db is None: if self._db is None:
self.db = sqlite3.connect(self.filename) self._db = sqlite3.connect(self.filename)
query = self.make_insert_query(message) query = self.make_insert_query(message)
if query is not None: if query is not None:
c = self.db.cursor() c = self._db.cursor()
c.execute(query) c.execute(query)
c.close() c.close()
self.db.commit() self._db.commit()
except ADSBError: except ADSBError:
pass pass
def make_insert_query(self, message): def make_insert_query(self, msg):
#assembles a SQL query tailored to our database #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 #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 query = None
msgtype = data["df"] msgtype = msg.data["df"]
if msgtype == 17: if msgtype == 17:
query = self.sql17(data) query = self.sql17(msg.data)
#self["new_adsb"] = data["aa"] #publish change notification
return query return query
def sql17(self, data): def sql17(self, data):
icao24 = data["aa"] icao24 = data["aa"]
bdsreg = data["me"].get_type() bdsreg = data["me"].get_type()
#self["bds%.2i" % bdsreg] = icao24 #publish under "bds08", "bds06", etc.
retstr = None
if bdsreg == 0x08: if bdsreg == 0x08:
(msg, typename) = self.parseBDS08(data) (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: 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 altitude = 0
if decoded_lat is None: #no unambiguously valid position available if decoded_lat is None: #no unambiguously valid position available
retstr = None raise CPRNoPositionError
else: 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: 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 if decoded_lat is None: #no unambiguously valid position available
retstr = None raise CPRNoPositionError
else: 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: elif bdsreg == 0x09:
subtype = data["bds09"].get_type() subtype = data["bds09"].get_type()
if subtype == 0: if subtype == 0:
[velocity, heading, vert_spd, turnrate] = self.parseBDS09_0(data) [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: elif subtype == 1:
[velocity, heading, vert_spd] = self.parseBDS09_1(data) [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 + ")" return "INSERT INTO vectors (icao, seen, speed, heading, vertical) VALUES (%i, datetime('now'), %.0f, %.0f, %.0f)" % (icao24, velocity, heading, vert_spd)
else: else:
retstr = None raise NoHandlerError
return retstr

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 # This file is part of GNU Radio
# #
@@ -31,14 +31,17 @@ if(NOT PYQT4_FOUND)
return() return()
endif() endif()
if(NOT PYUIC4_FOUND)
message(STATUS "pyuic4 not found, not installing GUI application")
return()
endif()
find_package(Qwt) find_package(Qwt)
if(NOT QWT_FOUND) if(NOT QWT_FOUND)
message(STATUS "Qwt not found, not installing GUI application") message(STATUS "Qwt not found, not installing GUI application")
return() return()
endif() endif()
set(PYUIC4_COMPILE pyuic4)
set(RX_UI_SRC ${CMAKE_CURRENT_SOURCE_DIR}/modes_rx.ui) 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_PRE_MASSAGE ${CMAKE_CURRENT_BINARY_DIR}/modes_rx_ui_borked.py)
set(RX_UI_PY ${CMAKE_CURRENT_BINARY_DIR}/modes_rx_ui.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} 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} MAIN_DEPENDENCY ${RX_UI_SRC}
) )

View File

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

View File

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

View File

@@ -5,7 +5,7 @@
%{ %{
#include "air_modes_preamble.h" #include "air_modes_preamble.h"
#include "air_modes_slicer.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); 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: private:
air_modes_preamble (int channel_rate, float threshold_db); air_modes_preamble (int channel_rate, float threshold_db);
}; };
GR_SWIG_BLOCK_MAGIC(air,modes_slicer); 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: private:
air_modes_slicer (int channel_rate, gr_msg_queue_sptr queue); air_modes_slicer (int channel_rate, gr::msg_queue::sptr queue);
}; };
// ---------------------------------------------------------------- // ----------------------------------------------------------------