Compare commits
58 Commits
mlat_serve
...
gui_model
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c2d0ca6051 | ||
|
|
33349efd7f | ||
|
|
dd3e1fe629 | ||
|
|
a09c5add43 | ||
|
|
498cea34b2 | ||
|
|
4174658f0d | ||
|
|
b594fe2799 | ||
|
|
7fef37d34d | ||
|
|
2deefdf310 | ||
|
|
37aa74fbe0 | ||
|
|
d71e6bc1e7 | ||
|
|
0ce6374656 | ||
|
|
2766107a76 | ||
|
|
55cd17de67 | ||
|
|
fbe3c464fb | ||
|
|
12c09ba1df | ||
|
|
a7af518653 | ||
|
|
302fa7203d | ||
|
|
e18a2e460c | ||
|
|
f8f08ecd37 | ||
|
|
9563972591 | ||
|
|
72ae3abf12 | ||
|
|
d84c0c3204 | ||
|
|
a1e2297134 | ||
|
|
230356bcaa | ||
|
|
29f8a2c1b4 | ||
|
|
d508b39b31 | ||
|
|
bed2aa499e | ||
|
|
244c9105f2 | ||
|
|
1880126100 | ||
|
|
34939bba52 | ||
|
|
fd6ee2ce89 | ||
|
|
c0543923f6 | ||
|
|
1cb8c726ed | ||
|
|
b5e3964d12 | ||
|
|
94af9fac48 | ||
|
|
d2a6f40bbd | ||
|
|
51cb2bdf46 | ||
|
|
798d5e15c9 | ||
|
|
4bbe250f39 | ||
|
|
cfab7123cc | ||
|
|
a847f5f875 | ||
|
|
79aee53a52 | ||
|
|
f62813f039 | ||
|
|
2ace332b89 | ||
|
|
9dad60303a | ||
|
|
4fe2334b28 | ||
|
|
ba55d24e92 | ||
|
|
b71c978e27 | ||
|
|
33874893b7 | ||
|
|
4216b96262 | ||
|
|
841e2aaa04 | ||
|
|
4d569f9112 | ||
|
|
db34eca30e | ||
|
|
3e9854f337 | ||
|
|
4e5fb40531 | ||
|
|
f25d21f505 | ||
|
|
f4fbd25bb0 |
@@ -1,4 +1,4 @@
|
||||
# Copyright 2011 Free Software Foundation, Inc.
|
||||
# Copyright 2011,2013 Free Software Foundation, Inc.
|
||||
#
|
||||
# This file is part of GNU Radio
|
||||
#
|
||||
@@ -47,23 +47,7 @@ endif()
|
||||
########################################################################
|
||||
# Find boost
|
||||
########################################################################
|
||||
if(UNIX AND EXISTS "/usr/lib64")
|
||||
list(APPEND BOOST_LIBRARYDIR "/usr/lib64") #fedora 64-bit fix
|
||||
endif(UNIX AND EXISTS "/usr/lib64")
|
||||
set(Boost_ADDITIONAL_VERSIONS
|
||||
"1.35.0" "1.35" "1.36.0" "1.36" "1.37.0" "1.37" "1.38.0" "1.38" "1.39.0" "1.39"
|
||||
"1.40.0" "1.40" "1.41.0" "1.41" "1.42.0" "1.42" "1.43.0" "1.43" "1.44.0" "1.44"
|
||||
"1.45.0" "1.45" "1.46.0" "1.46" "1.47.0" "1.47" "1.48.0" "1.48" "1.49.0" "1.49"
|
||||
"1.50.0" "1.50" "1.51.0" "1.51" "1.52.0" "1.52" "1.53.0" "1.53" "1.54.0" "1.54"
|
||||
"1.55.0" "1.55" "1.56.0" "1.56" "1.57.0" "1.57" "1.58.0" "1.58" "1.59.0" "1.59"
|
||||
"1.60.0" "1.60" "1.61.0" "1.61" "1.62.0" "1.62" "1.63.0" "1.63" "1.64.0" "1.64"
|
||||
"1.65.0" "1.65" "1.66.0" "1.66" "1.67.0" "1.67" "1.68.0" "1.68" "1.69.0" "1.69"
|
||||
)
|
||||
find_package(Boost "1.35")
|
||||
|
||||
if(NOT Boost_FOUND)
|
||||
message(FATAL_ERROR "Boost required to compile gr-air-modes")
|
||||
endif()
|
||||
include(GrBoost)
|
||||
|
||||
########################################################################
|
||||
# Install directories
|
||||
@@ -85,15 +69,10 @@ set(GRC_BLOCKS_DIR ${GR_PKG_DATA_DIR}/grc/blocks)
|
||||
########################################################################
|
||||
# Find gnuradio build dependencies
|
||||
########################################################################
|
||||
find_package(Gruel)
|
||||
find_package(GnuradioCore)
|
||||
find_package(GnuradioRuntime)
|
||||
|
||||
if(NOT GRUEL_FOUND)
|
||||
message(FATAL_ERROR "Gruel required to compile gr-air-modes")
|
||||
endif()
|
||||
|
||||
if(NOT GNURADIO_CORE_FOUND)
|
||||
message(FATAL_ERROR "GnuRadio Core required to compile gr-air-modes")
|
||||
if(NOT GNURADIO_RUNTIME_FOUND)
|
||||
message(FATAL_ERROR "GnuRadio Runtime required to compile gr-air-modes")
|
||||
endif()
|
||||
|
||||
########################################################################
|
||||
@@ -102,14 +81,12 @@ endif()
|
||||
include_directories(
|
||||
${CMAKE_SOURCE_DIR}/include
|
||||
${Boost_INCLUDE_DIRS}
|
||||
${GRUEL_INCLUDE_DIRS}
|
||||
${GNURADIO_CORE_INCLUDE_DIRS}
|
||||
${GNURADIO_RUNTIME_INCLUDE_DIRS}
|
||||
)
|
||||
|
||||
link_directories(
|
||||
${Boost_LIBRARY_DIRS}
|
||||
${GRUEL_LIBRARY_DIRS}
|
||||
${GNURADIO_CORE_LIBRARY_DIRS}
|
||||
${GNURADIO_RUNTIME_LIBRARY_DIRS}
|
||||
)
|
||||
|
||||
# Set component parameters
|
||||
|
||||
340
apps/modes_gui
340
apps/modes_gui
@@ -19,16 +19,19 @@
|
||||
# Boston, MA 02110-1301, USA.
|
||||
#
|
||||
|
||||
import os, sys, time, threading, datetime, math, csv
|
||||
from PyQt4 import QtCore,QtGui
|
||||
import os, sys, time, threading, datetime, math, csv, tempfile
|
||||
from optparse import OptionParser
|
||||
from PyQt4 import QtCore,QtGui,QtWebKit
|
||||
from PyQt4.Qwt5 import Qwt
|
||||
from gnuradio import gr, gru, optfir, eng_notation, blks2
|
||||
import gnuradio.gr.gr_threading as _threading
|
||||
from gnuradio import gr, eng_notation
|
||||
from gnuradio.eng_option import eng_option
|
||||
from gnuradio.gr.pubsub import pubsub
|
||||
import air_modes
|
||||
from air_modes.exceptions import *
|
||||
from air_modes.modes_rx_ui import Ui_MainWindow
|
||||
from air_modes.gui_model import *
|
||||
import sqlite3
|
||||
import zmq
|
||||
|
||||
class mainwindow(QtGui.QMainWindow):
|
||||
live_data_changed_signal = QtCore.pyqtSignal(QtCore.QString, name='liveDataChanged')
|
||||
@@ -39,7 +42,7 @@ class mainwindow(QtGui.QMainWindow):
|
||||
|
||||
#set defaults
|
||||
#add file, RTL, UHD sources
|
||||
self.ui.combo_source.addItems(["UHD device", "RTL-SDR", "File"])
|
||||
self.ui.combo_source.addItems(["UHD", "Osmocom", "File/UDP"])
|
||||
self.ui.combo_source.setCurrentIndex(0)
|
||||
|
||||
#populate antenna, rate combo boxes based on source
|
||||
@@ -51,8 +54,8 @@ class mainwindow(QtGui.QMainWindow):
|
||||
#default to 5dB
|
||||
self.ui.line_threshold.insert("5")
|
||||
|
||||
self.ui.prog_rssi.setMinimum(-40)
|
||||
self.ui.prog_rssi.setMaximum(0)
|
||||
self.ui.prog_rssi.setMinimum(0)
|
||||
self.ui.prog_rssi.setMaximum(40)
|
||||
|
||||
self.ui.combo_ant.setCurrentIndex(self.ui.combo_ant.findText("RX2"))
|
||||
|
||||
@@ -70,18 +73,16 @@ class mainwindow(QtGui.QMainWindow):
|
||||
#disable by default
|
||||
self.ui.check_adsbonly.setCheckState(QtCore.Qt.Unchecked)
|
||||
|
||||
#set up the radio stuff
|
||||
self.queue = gr.msg_queue(10)
|
||||
self.runner = None
|
||||
self.fg = None
|
||||
self.outputs = []
|
||||
self.updates = []
|
||||
self.output_handler = None
|
||||
self.running = False
|
||||
self.kmlgen = None #necessary bc we stop its thread in shutdown
|
||||
self.dbname = "air_modes.db"
|
||||
self.dbname = "adsb.db"
|
||||
self.num_reports = 0
|
||||
self.last_report = 0
|
||||
self.context = zmq.Context(1)
|
||||
|
||||
self.datamodel = dashboard_data_model(None)
|
||||
self.datamodel = dashboard_sql_model(None)
|
||||
self.ui.list_aircraft.setModel(self.datamodel)
|
||||
self.ui.list_aircraft.setModelColumn(0)
|
||||
|
||||
@@ -95,15 +96,16 @@ class mainwindow(QtGui.QMainWindow):
|
||||
self.dashboard_mapper.setModel(self.datamodel)
|
||||
self.dashboard_mapper.addMapping(self.ui.line_icao, 0)
|
||||
#self.dashboard_mapper.addMapping(self.ui.prog_rssi, 2)
|
||||
self.dashboard_mapper.addMapping(self.ui.line_latitude, 3)
|
||||
self.dashboard_mapper.addMapping(self.ui.line_longitude, 4)
|
||||
self.dashboard_mapper.addMapping(self.ui.line_alt, 5)
|
||||
self.dashboard_mapper.addMapping(self.ui.line_speed, 6)
|
||||
#self.dashboard_mapper.addMapping(self.ui.compass_heading, 7)
|
||||
self.dashboard_mapper.addMapping(self.ui.line_climb, 8)
|
||||
self.dashboard_mapper.addMapping(self.ui.line_ident, 9)
|
||||
self.dashboard_mapper.addMapping(self.ui.line_type, 10)
|
||||
self.dashboard_mapper.addMapping(self.ui.line_range, 11)
|
||||
self.dashboard_mapper.addMapping(self.ui.line_latitude, 2)
|
||||
self.dashboard_mapper.addMapping(self.ui.line_longitude, 3)
|
||||
self.dashboard_mapper.addMapping(self.ui.line_alt, 4)
|
||||
self.dashboard_mapper.addMapping(self.ui.line_speed, 5)
|
||||
#self.dashboard_mapper.addMapping(self.ui.compass_heading, 6)
|
||||
self.dashboard_mapper.addMapping(self.ui.line_climb, 7)
|
||||
self.dashboard_mapper.addMapping(self.ui.line_ident, 8)
|
||||
self.dashboard_mapper.addMapping(self.ui.line_type, 9)
|
||||
#self.dashboard_mapper.addMapping(self.ui.line_range, 11)
|
||||
#self.dashboard_mapper.addMapping(self.ui.compass_bearing, 12)
|
||||
|
||||
compass_palette = QtGui.QPalette()
|
||||
compass_palette.setColor(QtGui.QPalette.Foreground, QtCore.Qt.white)
|
||||
@@ -113,12 +115,14 @@ class mainwindow(QtGui.QMainWindow):
|
||||
self.ui.compass_heading.setNeedle(Qwt.QwtDialSimpleNeedle(Qwt.QwtDialSimpleNeedle.Ray, False, QtCore.Qt.black))
|
||||
self.ui.compass_bearing.setNeedle(Qwt.QwtDialSimpleNeedle(Qwt.QwtDialSimpleNeedle.Ray, False, QtCore.Qt.black))
|
||||
|
||||
#hook up the update signal
|
||||
#hook up the update signals which are explicitly necessary
|
||||
#most of the dashboard_mapper and list_aircraft stuff is implicitly done already
|
||||
self.ui.list_aircraft.selectionModel().currentRowChanged.connect(self.dashboard_mapper.setCurrentModelIndex)
|
||||
self.ui.list_aircraft.selectionModel().currentRowChanged.connect(self.update_heading_widget)
|
||||
self.ui.list_aircraft.selectionModel().currentRowChanged.connect(self.update_bearing_widget)
|
||||
self.datamodel.dataChanged.connect(self.unmapped_widgets_dataChanged)
|
||||
self.ui.list_aircraft.selectionModel().currentRowChanged.connect(self.update_rssi_widget)
|
||||
self.ui.list_aircraft.selectionModel().currentRowChanged.connect(self.update_map_highlight)
|
||||
self.datamodel.dataChanged.connect(self.unmapped_widgets_dataChanged)
|
||||
|
||||
#hook up live data text box update signal
|
||||
self.live_data_changed_signal.connect(self.on_append_live_data)
|
||||
@@ -126,29 +130,29 @@ class mainwindow(QtGui.QMainWindow):
|
||||
############ widget update functions for non-mapped widgets ############
|
||||
def update_heading_widget(self, index):
|
||||
if index.model() is not None:
|
||||
heading = index.model().data(index.model().index(index.row(), self.datamodel._colnames.index("heading"))).toDouble()[0]
|
||||
heading = index.model().data(index.model().index(index.row(), 6)).toDouble()[0]
|
||||
self.ui.compass_heading.setValue(heading)
|
||||
|
||||
def update_bearing_widget(self, index):
|
||||
if index.model() is not None:
|
||||
bearing = index.model().data(index.model().index(index.row(), self.datamodel._colnames.index("bearing"))).toDouble()[0]
|
||||
bearing = 0#index.model().data(index.model().index(index.row(), 12)).toDouble()[0]
|
||||
self.ui.compass_bearing.setValue(bearing)
|
||||
|
||||
def unmapped_widgets_dataChanged(self, startIndex, endIndex):
|
||||
index = self.ui.list_aircraft.selectionModel().currentIndex()
|
||||
if index.row() in range(startIndex.row(), endIndex.row()+1): #the current aircraft was affected
|
||||
if self.datamodel._colnames.index("heading") in range(startIndex.column(), endIndex.column()+1):
|
||||
if 6 in range(startIndex.column(), endIndex.column()+1):
|
||||
self.update_heading_widget(index)
|
||||
if self.datamodel._colnames.index("bearing") in range(startIndex.column(), endIndex.column()+1):
|
||||
if 12 in range(startIndex.column(), endIndex.column()+1):
|
||||
self.update_bearing_widget(index)
|
||||
if self.datamodel._colnames.index("rssi") in range(startIndex.column(), endIndex.column()+1):
|
||||
if 2 in range(startIndex.column(), endIndex.column()+1):
|
||||
self.update_rssi_widget(index)
|
||||
|
||||
def update_rssi_widget(self, index):
|
||||
if index.model() is not None:
|
||||
rssi = index.model().data(index.model().index(index.row(), 2)).toDouble()[0]
|
||||
rssi = 0#index.model().data(index.model().index(index.row(), 2)).toDouble()[0]
|
||||
self.ui.prog_rssi.setValue(rssi)
|
||||
|
||||
|
||||
def increment_reportspersec(self, msg):
|
||||
self.num_reports += 1
|
||||
|
||||
@@ -159,6 +163,12 @@ class mainwindow(QtGui.QMainWindow):
|
||||
self.ui.line_reports.setText("%i" % self.num_reports)
|
||||
self.num_reports = 0
|
||||
|
||||
def update_map_highlight(self, index):
|
||||
if index.model() is not None:
|
||||
icaostr = index.model().data(index.model().index(index.row(), self.datamodel._colnames.index("icao"))).toString()
|
||||
icao = int(str(icaostr), 16)
|
||||
self.jsonpgen.set_highlight(icao)
|
||||
|
||||
##################### dynamic option population ########################
|
||||
#goes and gets valid antenna, sample rate options from the device and grays out appropriate things
|
||||
def populate_source_options(self):
|
||||
@@ -166,12 +176,13 @@ class mainwindow(QtGui.QMainWindow):
|
||||
self.rates = []
|
||||
self.ratetext = []
|
||||
self.antennas = []
|
||||
|
||||
if sourceid == "UHD device":
|
||||
|
||||
if sourceid == "UHD":
|
||||
try:
|
||||
from gnuradio import uhd
|
||||
self.src = uhd.single_usrp_source("", uhd.io_type_t.COMPLEX_FLOAT32, 1)
|
||||
self.rates = [rate.start() for rate in self.src.get_samp_rates()]
|
||||
self.rates = [rate.start() for rate in self.src.get_samp_rates()
|
||||
if (rate.start() % 2.e6) == 0 and rate >= 4e6]
|
||||
self.antennas = self.src.get_antennas()
|
||||
self.src = None #deconstruct UHD source for now
|
||||
self.ui.combo_ant.setEnabled(True)
|
||||
@@ -183,16 +194,26 @@ class mainwindow(QtGui.QMainWindow):
|
||||
self.ui.combo_ant.setEnabled(False)
|
||||
self.ui.combo_rate.setEnabled(False)
|
||||
self.ui.stack_source.setCurrentIndex(0)
|
||||
|
||||
elif sourceid == "RTL-SDR":
|
||||
self.rates = [3.2e6]
|
||||
self.antennas = ["RX"]
|
||||
self.ui.combo_ant.setEnabled(False)
|
||||
self.ui.combo_rate.setEnabled(False)
|
||||
self.ui.stack_source.setCurrentIndex(0)
|
||||
|
||||
elif sourceid == "File":
|
||||
self.rates = [2e6, 4e6, 6e6, 8e6, 10e6]
|
||||
elif sourceid == "Osmocom":
|
||||
try:
|
||||
import osmosdr
|
||||
self.src = osmosdr.source_c("")
|
||||
self.rates = [rate.start() for rate in self.src.get_sample_rates() if (rate.start() % 2.e6) == 0]
|
||||
self.antennas = ["RX"]
|
||||
self.src = None
|
||||
self.ui.combo_ant.setEnabled(False)
|
||||
self.ui.combo_rate.setEnabled(True)
|
||||
self.ui.stack_source.setCurrentIndex(0)
|
||||
except:
|
||||
self.rates = []
|
||||
self.antennas = []
|
||||
self.ui.combo_ant.setEnabled(False)
|
||||
self.ui.combo_rate.setEnabled(False)
|
||||
self.ui.stack_source.setCurrentIndex(0)
|
||||
|
||||
elif sourceid == "File/UDP":
|
||||
self.rates = [2e6*i for i in range(2,13)]
|
||||
self.antennas = ["None"]
|
||||
self.ui.combo_ant.setEnabled(False)
|
||||
self.ui.combo_rate.setEnabled(True)
|
||||
@@ -206,8 +227,12 @@ class mainwindow(QtGui.QMainWindow):
|
||||
self.ui.combo_ant.clear()
|
||||
self.ui.combo_ant.addItems(self.antennas)
|
||||
|
||||
if 4e6 in self.rates:
|
||||
self.ui.combo_rate.setCurrentIndex(self.rates.index(4e6))
|
||||
#set up recommended sample rate
|
||||
recommended_rate = min(x for x in self.rates if x >= 4e6 and
|
||||
max(self.rates) % x == 0)
|
||||
if recommended_rate >= 8.e6:
|
||||
self.ui.check_pmf.setChecked(True)
|
||||
self.ui.combo_rate.setCurrentIndex(self.rates.index(recommended_rate))
|
||||
|
||||
################ action handlers ####################
|
||||
def on_combo_source_currentIndexChanged(self, index):
|
||||
@@ -215,47 +240,47 @@ class mainwindow(QtGui.QMainWindow):
|
||||
|
||||
def on_button_start_released(self):
|
||||
#if we're already running, kill it!
|
||||
if self.runner is not None:
|
||||
self.output_handler.done = True
|
||||
self.output_handler = None
|
||||
self.outputs = []
|
||||
self.updates = []
|
||||
self.fg.stop()
|
||||
self.runner = None
|
||||
self.fg = None
|
||||
if self.kmlgen is not None:
|
||||
self.kmlgen.done = True
|
||||
#TODO FIXME need a way to kill kmlgen safely without delay
|
||||
#self.kmlgen.join()
|
||||
#self.kmlgen = None
|
||||
if self.running is True:
|
||||
self.on_quit()
|
||||
|
||||
self.num_reports = 0
|
||||
self.ui.line_reports.setText("0")
|
||||
|
||||
self.ui.button_start.setText("Start")
|
||||
self.running = False
|
||||
|
||||
else: #we aren't already running, let's get this party started
|
||||
options = {}
|
||||
options["source"] = str(self.ui.combo_source.currentText())
|
||||
options["rate"] = float(self.ui.combo_rate.currentText()) * 1e6
|
||||
options["antenna"] = str(self.ui.combo_ant.currentText())
|
||||
options["gain"] = float(self.ui.line_gain.text())
|
||||
options["threshold"] = float(self.ui.line_threshold.text())
|
||||
options["filename"] = str(self.ui.line_inputfile.text())
|
||||
options["pmf"] = self.ui.check_pmf.checkState()
|
||||
parser = OptionParser(option_class=eng_option)
|
||||
air_modes.modes_radio.add_radio_options(parser)
|
||||
(options, args) = parser.parse_args() #sets defaults nicely
|
||||
if str(self.ui.combo_source.currentText()) != "File/UDP":
|
||||
options.source = str(self.ui.combo_source.currentText()).lower()
|
||||
else:
|
||||
options.source = str(self.ui.line_inputfile.text())
|
||||
options.rate = float(self.ui.combo_rate.currentText()) * 1e6
|
||||
options.antenna = str(self.ui.combo_ant.currentText())
|
||||
options.gain = float(self.ui.line_gain.text())
|
||||
options.threshold = float(self.ui.line_threshold.text())
|
||||
options.pmf = self.ui.check_pmf.checkState()
|
||||
|
||||
self.fg = adsb_rx_block(options, self.queue) #create top RX block
|
||||
self.runner = top_block_runner(self.fg) #spawn new thread to do RX
|
||||
self._servers = ["inproc://modes-radio-pub"] #TODO ADD REMOTES
|
||||
self._relay = air_modes.zmq_pubsub_iface(self.context, subaddr=self._servers, pubaddr=None)
|
||||
|
||||
if self.ui.check_raw.checkState():
|
||||
options.tcp = int(self.ui.line_rawport.text())
|
||||
|
||||
self._radio = air_modes.modes_radio(options, self.context)
|
||||
self._publisher = pubsub()
|
||||
self._relay.subscribe("dl_data", air_modes.make_parser(self._publisher))
|
||||
|
||||
try:
|
||||
my_position = [float(self.ui.line_my_lat.text()), float(self.ui.line_my_lon.text())]
|
||||
except:
|
||||
my_position = None
|
||||
|
||||
self.datamodelout = dashboard_output(my_position, self.datamodel)
|
||||
self._cpr_dec = air_modes.cpr_decoder(my_position)
|
||||
# self.datamodelout = dashboard_output(self._cpr_dec, self.datamodel, self._publisher)
|
||||
|
||||
self.outputs = [self.datamodelout.output]
|
||||
self.updates = []
|
||||
self.lock = threading.Lock() #grab a lock to ensure sql and kml don't step on each other
|
||||
|
||||
#output options to populate outputs, updates
|
||||
@@ -265,55 +290,61 @@ class mainwindow(QtGui.QMainWindow):
|
||||
|
||||
if self.ui.check_sbs1.checkState():
|
||||
sbs1port = int(self.ui.line_sbs1port.text())
|
||||
sbs1out = air_modes.output_sbs1(my_position, sbs1port)
|
||||
self.outputs.append(sbs1out.output)
|
||||
self.updates.append(sbs1out.add_pending_conns)
|
||||
sbs1out = air_modes.output_sbs1(self._cpr_dec, sbs1port, self._publisher)
|
||||
|
||||
if self.ui.check_fgfs.checkState():
|
||||
fghost = "127.0.0.1" #TODO FIXME
|
||||
fgport = self.ui.line_fgfsport.text()
|
||||
fgout = air_modes.output_flightgear(my_position, fghost, int(fgport))
|
||||
self.outputs.append(fgout.output)
|
||||
|
||||
if self.ui.check_raw.checkState():
|
||||
rawport = air_modes.raw_server(int(self.ui.line_rawport.text()))
|
||||
self.outputs.append(rawport.output)
|
||||
self.updates.append(rawport.add_pending_conns)
|
||||
fgout = air_modes.output_flightgear(self._cpr_dec, fghost, int(fgport), self._publisher)
|
||||
|
||||
#add azimuth map output and hook it up
|
||||
if my_position is not None:
|
||||
self.az_map_output = air_modes.az_map_output(my_position, self.az_model)
|
||||
self.outputs.append(self.az_map_output.output)
|
||||
self.az_map_output = air_modes.az_map_output(self._cpr_dec, self.az_model, self._publisher)
|
||||
#self._relay.subscribe("dl_data", self.az_map_output.output)
|
||||
|
||||
self.livedata = air_modes.output_print(my_position)
|
||||
#add output for live data box
|
||||
self.outputs.append(self.output_live_data)
|
||||
#set up map
|
||||
#NOTE this is busted on windows. WebKit requires .htm[l] extensions to render,
|
||||
#so using a temp file doesn't work.
|
||||
self._htmlfile = open("/tmp/mode_s.html", 'wb+')#tempfile.NamedTemporaryFile()
|
||||
self._jsonfile = tempfile.NamedTemporaryFile()
|
||||
|
||||
self.livedata = air_modes.output_print(self._cpr_dec,
|
||||
self._publisher,
|
||||
self.live_data_changed_signal.emit)
|
||||
|
||||
#create SQL database for KML and dashboard displays
|
||||
self.dbwriter = air_modes.output_sql(my_position, self.dbname, self.lock)
|
||||
self.outputs.append(self.dbwriter.output) #now the db will update itself
|
||||
self.dbwriter = air_modes.output_sql(self._cpr_dec, self.dbname, self.lock, self._publisher)
|
||||
self.jsonpgen = air_modes.output_jsonp(self._jsonfile.name, self.dbname, my_position, self.lock, timeout=1)
|
||||
htmlstring = air_modes.html_template(my_position, self._jsonfile.name)
|
||||
self._htmlfile.write(htmlstring)
|
||||
self._htmlfile.flush()
|
||||
class WebPage(QtWebKit.QWebPage):
|
||||
def javaScriptConsoleMessage(self, msg, line, source):
|
||||
print '%s line %d: %s' % (source, line, msg)
|
||||
page = WebPage()
|
||||
self.ui.mapView.setPage(page)
|
||||
self.ui.mapView.load( QtCore.QUrl( QtCore.QUrl.fromLocalFile("/tmp/mode_s.html") ) )
|
||||
self.ui.mapView.show()
|
||||
|
||||
#output to update reports/sec widget
|
||||
self.outputs.append(self.increment_reportspersec)
|
||||
self.updates.append(self.update_reportspersec)
|
||||
self._relay.subscribe("dl_data", self.increment_reportspersec)
|
||||
self._rps_timer = QtCore.QTimer()
|
||||
self._rps_timer.timeout.connect(self.update_reportspersec)
|
||||
self._rps_timer.start(1000)
|
||||
|
||||
#create output handler thread
|
||||
self.output_handler = output_handler(self.outputs, self.updates, self.queue)
|
||||
#start the flowgraph
|
||||
self._radio.start()
|
||||
|
||||
self.ui.button_start.setText("Stop")
|
||||
self.running = True
|
||||
|
||||
def on_quit(self):
|
||||
if self.runner is not None:
|
||||
try:
|
||||
self.output_handler.done = True
|
||||
except:
|
||||
pass
|
||||
self.output_handler = None
|
||||
self.outputs = []
|
||||
self.updates = []
|
||||
self.fg.stop()
|
||||
self.runner = None
|
||||
self.fg = None
|
||||
if self.running is True:
|
||||
self._relay.close()
|
||||
self._radio.close()
|
||||
self._relay = None
|
||||
self._radio = None
|
||||
self._rps_timer = None
|
||||
try:
|
||||
self.kmlgen.done = True
|
||||
#TODO FIXME need a way to kill kmlgen safely without delay
|
||||
@@ -331,109 +362,10 @@ class mainwindow(QtGui.QMainWindow):
|
||||
cursor.movePosition(QtGui.QTextCursor.Start)
|
||||
cursor.select(QtGui.QTextCursor.LineUnderCursor)
|
||||
cursor.removeSelectedText()
|
||||
|
||||
|
||||
self.ui.text_livedata.append(msgstr)
|
||||
self.ui.text_livedata.verticalScrollBar().setSliderPosition(self.ui.text_livedata.verticalScrollBar().maximum())
|
||||
|
||||
def output_live_data(self, msg):
|
||||
msgstr = self.livedata.parse(msg)
|
||||
if msgstr is not None:
|
||||
self.live_data_changed_signal.emit(msgstr)
|
||||
|
||||
|
||||
|
||||
#the output handler is a thread which runs the various registered output functions when there's a received message.
|
||||
#it also executes registered updates at the sleep rate -- currently 10Hz.
|
||||
class output_handler(threading.Thread):
|
||||
def __init__(self, outputs, updates, queue):
|
||||
threading.Thread.__init__(self)
|
||||
self.setDaemon(1)
|
||||
self.outputs = outputs
|
||||
self.updates = updates
|
||||
self.queue = queue
|
||||
self.done = False
|
||||
self.start()
|
||||
|
||||
def run(self):
|
||||
while self.done is False:
|
||||
for update in self.updates:
|
||||
update()
|
||||
while not self.queue.empty_p():
|
||||
msg = self.queue.delete_head()
|
||||
for output in self.outputs:
|
||||
try:
|
||||
output(msg.to_string())
|
||||
except ADSBError:
|
||||
pass
|
||||
|
||||
time.sleep(0.1)
|
||||
|
||||
self.done = True
|
||||
self.outputs = None
|
||||
self.updates = None
|
||||
self.queue = None
|
||||
|
||||
|
||||
class top_block_runner(_threading.Thread):
|
||||
def __init__(self, tb):
|
||||
_threading.Thread.__init__(self)
|
||||
self.setDaemon(1)
|
||||
self.tb = tb
|
||||
self.done = False
|
||||
self.start()
|
||||
|
||||
def run(self):
|
||||
self.tb.run()
|
||||
self.done = True
|
||||
|
||||
#Top block for ADSB receiver. If you define a standard interface you
|
||||
#can make this common code between the GUI app and the cmdline app
|
||||
class adsb_rx_block (gr.top_block):
|
||||
def __init__(self, options, queue):
|
||||
gr.top_block.__init__(self)
|
||||
|
||||
self.options = options
|
||||
rate = options["rate"]
|
||||
print "Rate: %f" % rate
|
||||
use_resampler = False
|
||||
freq = 1090e6
|
||||
|
||||
if options["source"] == "UHD device":
|
||||
from gnuradio import uhd
|
||||
self.u = uhd.single_usrp_source("", uhd.io_type_t.COMPLEX_FLOAT32, 1)
|
||||
time_spec = uhd.time_spec(0.0)
|
||||
self.u.set_time_now(time_spec)
|
||||
self.u.set_antenna(options["antenna"])
|
||||
self.u.set_samp_rate(rate)
|
||||
rate = self.u.get_samp_rate()
|
||||
self.u.set_gain(int(options["gain"]))
|
||||
self.u.set_center_freq(freq, 0)
|
||||
|
||||
elif options["source"] == "RTL-SDR":
|
||||
import osmosdr
|
||||
self.u = osmosdr.source_c()
|
||||
self.u.set_sample_rate(3.2e6) #fixed for RTL dongles
|
||||
rate = int(4e6)
|
||||
self.u.set_gain_mode(0) #manual gain mode
|
||||
self.u.set_gain(int(options["gain"]))
|
||||
self.u.set_center_freq(freq, 0)
|
||||
use_resampler = True
|
||||
|
||||
elif options["source"] == "File":
|
||||
self.u = gr.file_source(gr.sizeof_gr_complex, options["filename"])
|
||||
else:
|
||||
raise NotImplementedError
|
||||
|
||||
self.rx_path = air_modes.rx_path(rate, options["threshold"], queue, options["pmf"])
|
||||
|
||||
if use_resampler:
|
||||
self.lpfiltcoeffs = gr.firdes.low_pass(1, 5*3.2e6, 1.6e6, 300e3)
|
||||
self.resample = blks2.rational_resampler_ccf(interpolation=5, decimation=4, taps=self.lpfiltcoeffs)
|
||||
self.connect(self.u, self.resample, self.rx_path)
|
||||
else:
|
||||
self.connect(self.u, self.rx_path)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
app = QtGui.QApplication(sys.argv)
|
||||
window = mainwindow()
|
||||
|
||||
243
apps/modes_rx
243
apps/modes_rx
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env python
|
||||
# Copyright 2010 Nick Foster
|
||||
# Copyright 2010, 2013 Nick Foster
|
||||
#
|
||||
# This file is part of gr-air-modes
|
||||
#
|
||||
@@ -19,221 +19,80 @@
|
||||
# Boston, MA 02110-1301, USA.
|
||||
#
|
||||
|
||||
my_position = None
|
||||
|
||||
from gnuradio import gr, gru, optfir, eng_notation, blks2
|
||||
from gnuradio.eng_option import eng_option
|
||||
from gnuradio.gr.pubsub import pubsub
|
||||
from optparse import OptionParser
|
||||
import time, os, sys, threading
|
||||
import time, os, sys, threading, math
|
||||
from string import split, join
|
||||
import air_modes
|
||||
import gnuradio.gr.gr_threading as _threading
|
||||
import csv
|
||||
from air_modes.types import *
|
||||
from air_modes.exceptions import *
|
||||
import zmq
|
||||
|
||||
class top_block_runner(_threading.Thread):
|
||||
def __init__(self, tb):
|
||||
_threading.Thread.__init__(self)
|
||||
self.setDaemon(1)
|
||||
self.tb = tb
|
||||
self.done = False
|
||||
self.start()
|
||||
#todo: maybe move plugins to separate programs (flightgear, SBS1, etc.)
|
||||
def main():
|
||||
my_position = None
|
||||
usage = "%prog: [options]"
|
||||
optparser = OptionParser(option_class=eng_option, usage=usage)
|
||||
air_modes.modes_radio.add_radio_options(optparser)
|
||||
|
||||
def run(self):
|
||||
self.tb.run()
|
||||
self.done = True
|
||||
optparser.add_option("-l","--location", type="string", default=None,
|
||||
help="GPS coordinates of receiving station in format xx.xxxxx,xx.xxxxx")
|
||||
#data source options
|
||||
optparser.add_option("-a","--remote", type="string", default=None,
|
||||
help="specify additional servers from which to take data in format tcp://x.x.x.x:y,tcp://....")
|
||||
optparser.add_option("-n","--no-print", action="store_true", default=False,
|
||||
help="disable printing decoded packets to stdout")
|
||||
#output plugins
|
||||
optparser.add_option("-K","--kml", type="string", default=None,
|
||||
help="filename for Google Earth KML output")
|
||||
optparser.add_option("-P","--sbs1", action="store_true", default=False,
|
||||
help="open an SBS-1-compatible server on port 30003")
|
||||
optparser.add_option("-m","--multiplayer", type="string", default=None,
|
||||
help="FlightGear server to send aircraft data, in format host:port")
|
||||
|
||||
class adsb_rx_block (gr.top_block):
|
||||
def __init__(self, options, args, queue):
|
||||
gr.top_block.__init__(self)
|
||||
(options, args) = optparser.parse_args()
|
||||
|
||||
self.options = options
|
||||
self.args = args
|
||||
rate = int(options.rate)
|
||||
use_resampler = False
|
||||
|
||||
if options.filename is None and options.udp is None and not options.rtlsdr:
|
||||
#UHD source by default
|
||||
from gnuradio import uhd
|
||||
self.u = uhd.single_usrp_source(options.args, uhd.io_type_t.COMPLEX_FLOAT32, 1)
|
||||
time_spec = uhd.time_spec(0.0)
|
||||
self.u.set_time_now(time_spec)
|
||||
|
||||
#if(options.rx_subdev_spec is None):
|
||||
# options.rx_subdev_spec = ""
|
||||
#self.u.set_subdev_spec(options.rx_subdev_spec)
|
||||
if not options.antenna is None:
|
||||
self.u.set_antenna(options.antenna)
|
||||
|
||||
self.u.set_samp_rate(rate)
|
||||
rate = int(self.u.get_samp_rate()) #retrieve actual
|
||||
|
||||
if options.gain is None: #set to halfway
|
||||
g = self.u.get_gain_range()
|
||||
options.gain = (g.start()+g.stop()) / 2.0
|
||||
|
||||
if not(self.tune(options.freq)):
|
||||
print "Failed to set initial frequency"
|
||||
|
||||
print "Setting gain to %i" % options.gain
|
||||
self.u.set_gain(options.gain)
|
||||
print "Gain is %i" % self.u.get_gain()
|
||||
|
||||
elif options.rtlsdr: #RTLSDR dongle
|
||||
import osmosdr
|
||||
self.u = osmosdr.source_c(options.args)
|
||||
self.u.set_sample_rate(3.2e6) #fixed for RTL dongles
|
||||
if not self.u.set_center_freq(options.freq):
|
||||
print "Failed to set initial frequency"
|
||||
|
||||
self.u.set_gain_mode(0) #manual gain mode
|
||||
if options.gain is None:
|
||||
options.gain = 34
|
||||
|
||||
self.u.set_gain(options.gain)
|
||||
print "Gain is %i" % self.u.get_gain()
|
||||
|
||||
use_resampler = True
|
||||
|
||||
else:
|
||||
if options.filename is not None:
|
||||
self.u = gr.file_source(gr.sizeof_gr_complex, options.filename)
|
||||
elif options.udp is not None:
|
||||
self.u = gr.udp_source(gr.sizeof_gr_complex, "localhost", options.udp)
|
||||
else:
|
||||
raise Exception("No valid source selected")
|
||||
|
||||
|
||||
print "Rate is %i" % (rate,)
|
||||
|
||||
pass_all = 0
|
||||
if options.output_all :
|
||||
pass_all = 1
|
||||
|
||||
self.rx_path = air_modes.rx_path(rate, options.threshold, queue, options.pmf)
|
||||
|
||||
if use_resampler:
|
||||
self.lpfiltcoeffs = gr.firdes.low_pass(1, 5*3.2e6, 1.6e6, 300e3)
|
||||
self.resample = blks2.rational_resampler_ccf(interpolation=5, decimation=4, taps=self.lpfiltcoeffs)
|
||||
self.connect(self.u, self.resample, self.rx_path)
|
||||
else:
|
||||
self.connect(self.u, self.rx_path)
|
||||
|
||||
def tune(self, freq):
|
||||
result = self.u.set_center_freq(freq, 0)
|
||||
return result
|
||||
|
||||
def printraw(msg):
|
||||
print msg
|
||||
|
||||
if __name__ == '__main__':
|
||||
usage = "%prog: [options] output filename"
|
||||
parser = OptionParser(option_class=eng_option, usage=usage)
|
||||
parser.add_option("-R", "--rx-subdev-spec", type="string",
|
||||
help="select USRP Rx side A or B", metavar="SUBDEV")
|
||||
parser.add_option("-A", "--antenna", type="string",
|
||||
help="select which antenna to use on daughterboard")
|
||||
parser.add_option("-D", "--args", type="string",
|
||||
help="arguments to pass to UHD/RTL constructor", default="")
|
||||
parser.add_option("-f", "--freq", type="eng_float", default=1090e6,
|
||||
help="set receive frequency in Hz [default=%default]", metavar="FREQ")
|
||||
parser.add_option("-g", "--gain", type="int", default=None,
|
||||
help="set RF gain", metavar="dB")
|
||||
parser.add_option("-r", "--rate", type="eng_float", default=4000000,
|
||||
help="set ADC sample rate [default=%default]")
|
||||
parser.add_option("-T", "--threshold", type="eng_float", default=5.0,
|
||||
help="set pulse detection threshold above noise in dB [default=%default]")
|
||||
parser.add_option("-a","--output-all", action="store_true", default=False,
|
||||
help="output all frames")
|
||||
parser.add_option("-F","--filename", type="string", default=None,
|
||||
help="read data from file instead of USRP")
|
||||
parser.add_option("-K","--kml", type="string", default=None,
|
||||
help="filename for Google Earth KML output")
|
||||
parser.add_option("-P","--sbs1", action="store_true", default=False,
|
||||
help="open an SBS-1-compatible server on port 30003")
|
||||
parser.add_option("-w","--raw", action="store_true", default=False,
|
||||
help="open a server outputting raw timestamped data on port 9988")
|
||||
parser.add_option("-n","--no-print", action="store_true", default=False,
|
||||
help="disable printing decoded packets to stdout")
|
||||
parser.add_option("-l","--location", type="string", default=None,
|
||||
help="GPS coordinates of receiving station in format xx.xxxxx,xx.xxxxx")
|
||||
parser.add_option("-u","--udp", type="int", default=None,
|
||||
help="Use UDP source on specified port")
|
||||
parser.add_option("-m","--multiplayer", type="string", default=None,
|
||||
help="FlightGear server to send aircraft data, in format host:port")
|
||||
parser.add_option("-d","--rtlsdr", action="store_true", default=False,
|
||||
help="Use RTLSDR dongle instead of UHD source")
|
||||
parser.add_option("-p","--pmf", action="store_true", default=False,
|
||||
help="Use pulse matched filtering")
|
||||
|
||||
(options, args) = parser.parse_args()
|
||||
#construct the radio
|
||||
context = zmq.Context(1)
|
||||
tb = air_modes.modes_radio(options, context)
|
||||
servers = ["inproc://modes-radio-pub"]
|
||||
if options.remote is not None:
|
||||
servers += options.remote.split(",")
|
||||
relay = air_modes.zmq_pubsub_iface(context, subaddr=servers, pubaddr=None)
|
||||
publisher = pubsub()
|
||||
relay.subscribe("dl_data", air_modes.make_parser(publisher))
|
||||
|
||||
if options.location is not None:
|
||||
reader = csv.reader([options.location], quoting=csv.QUOTE_NONNUMERIC)
|
||||
my_position = reader.next()
|
||||
my_position = [float(n) for n in options.location.split(",")]
|
||||
|
||||
queue = gr.msg_queue()
|
||||
|
||||
outputs = [] #registry of plugin output functions
|
||||
updates = [] #registry of plugin update functions
|
||||
|
||||
if options.raw is True:
|
||||
rawport = air_modes.raw_server(9988) #port
|
||||
outputs.append(rawport.output)
|
||||
outputs.append(printraw)
|
||||
updates.append(rawport.add_pending_conns)
|
||||
#CPR decoder obj to handle getting position from BDS0,5 and BDS0,6 pkts
|
||||
cpr_dec = air_modes.cpr_decoder(my_position)
|
||||
|
||||
if options.kml is not None:
|
||||
#we spawn a thread to run every 30 seconds (or whatever) to generate KML
|
||||
dbname = 'adsb.db'
|
||||
lock = threading.Lock()
|
||||
sqldb = air_modes.output_sql(my_position, dbname, lock) #input into the db
|
||||
sqldb = air_modes.output_sql(cpr_dec, dbname, lock, publisher) #input into the db
|
||||
kmlgen = air_modes.output_kml(options.kml, dbname, my_position, lock) #create a KML generating thread to read from the db
|
||||
outputs.append(sqldb.output)
|
||||
|
||||
if options.sbs1 is True:
|
||||
sbs1port = air_modes.output_sbs1(my_position, 30003)
|
||||
outputs.append(sbs1port.output)
|
||||
updates.append(sbs1port.add_pending_conns)
|
||||
|
||||
if options.no_print is not True:
|
||||
outputs.append(air_modes.output_print(my_position).output)
|
||||
printer = air_modes.output_print(cpr_dec, publisher)
|
||||
|
||||
if options.multiplayer is not None:
|
||||
[fghost, fgport] = options.multiplayer.split(':')
|
||||
fgout = air_modes.output_flightgear(my_position, fghost, int(fgport))
|
||||
outputs.append(fgout.output)
|
||||
fgout = air_modes.output_flightgear(cpr_dec, fghost, int(fgport), publisher)
|
||||
|
||||
fg = adsb_rx_block(options, args, queue)
|
||||
runner = top_block_runner(fg)
|
||||
if options.sbs1 is True:
|
||||
sbs1port = air_modes.output_sbs1(cpr_dec, 30003, publisher)
|
||||
|
||||
while 1:
|
||||
try:
|
||||
#the update registry is really for the SBS1 and raw server plugins -- we're looking for new TCP connections.
|
||||
#i think we have to do this here rather than in the output handler because otherwise connections will stack up
|
||||
#until the next output arrives
|
||||
for update in updates:
|
||||
update()
|
||||
|
||||
#main message handler
|
||||
if not queue.empty_p() :
|
||||
while not queue.empty_p() :
|
||||
msg = queue.delete_head() #blocking read
|
||||
tb.run()
|
||||
tb.close()
|
||||
|
||||
for out in outputs:
|
||||
try:
|
||||
out(msg.to_string())
|
||||
except air_modes.ADSBError:
|
||||
pass
|
||||
relay.close()
|
||||
|
||||
elif runner.done:
|
||||
raise KeyboardInterrupt
|
||||
else:
|
||||
time.sleep(0.1)
|
||||
if options.kml is not None:
|
||||
kmlgen.close()
|
||||
|
||||
|
||||
except KeyboardInterrupt:
|
||||
fg.stop()
|
||||
runner = None
|
||||
if options.kml is not None:
|
||||
kmlgen.done = True
|
||||
break
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
|
||||
@@ -1,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)
|
||||
6
cmake/Modules/FindGnuradioRuntime.cmake
Normal file
6
cmake/Modules/FindGnuradioRuntime.cmake
Normal 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)
|
||||
@@ -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)
|
||||
@@ -1,6 +1,7 @@
|
||||
# Find PyQt4
|
||||
# ~~~~~~~~~~
|
||||
# Copyright (c) 2007-2008, Simon Edwards <simon@simonzone.com>
|
||||
# Copyright (c) 2012, Nicholas Corgan <nick.corgan@ettus.com>
|
||||
# Redistribution and use is allowed according to the terms of the BSD license.
|
||||
# For details see the accompanying COPYING-CMAKE-SCRIPTS file.
|
||||
#
|
||||
@@ -22,10 +23,11 @@
|
||||
#
|
||||
# PYQT4_SIP_FLAGS - The SIP flags used to build PyQt.
|
||||
|
||||
IF(EXISTS PYQT4_VERSION)
|
||||
IF(EXISTS PYQT4_VERSION AND EXISTS PYUIC4_EXECUTABLE)
|
||||
# Already in cache, be silent
|
||||
SET(PYQT4_FOUND TRUE)
|
||||
ELSE(EXISTS PYQT4_VERSION)
|
||||
SET(PYUIC4_FOUND TRUE)
|
||||
ELSE(EXISTS PYQT4_VERSION AND EXISTS PYUIC4_EXECUTABLE)
|
||||
|
||||
FIND_FILE(_find_pyqt_py FindPyQt.py PATHS ${CMAKE_MODULE_PATH})
|
||||
|
||||
@@ -40,14 +42,20 @@ ELSE(EXISTS PYQT4_VERSION)
|
||||
SET(PYQT4_FOUND TRUE)
|
||||
ENDIF(pyqt_config)
|
||||
|
||||
IF(PYQT4_FOUND)
|
||||
FIND_PROGRAM(PYUIC4_EXECUTABLE NAMES pyuic4)
|
||||
IF(PYUIC4_EXECUTABLE)
|
||||
SET(PYUIC4_FOUND TRUE)
|
||||
ENDIF(PYUIC4_EXECUTABLE)
|
||||
|
||||
IF(PYQT4_FOUND AND PYUIC4_FOUND)
|
||||
IF(NOT PYQT4_FIND_QUIETLY)
|
||||
MESSAGE(STATUS "Found PyQt4 version: ${PYQT4_VERSION_STR}")
|
||||
MESSAGE(STATUS "Found pyuic4: ${PYUIC4_EXECUTABLE}")
|
||||
ENDIF(NOT PYQT4_FIND_QUIETLY)
|
||||
ELSE(PYQT4_FOUND)
|
||||
ELSE(PYQT4_FOUND AND PYUIC4_FOUND)
|
||||
IF(PYQT4_FIND_REQUIRED)
|
||||
MESSAGE(FATAL_ERROR "Could not find Python")
|
||||
ENDIF(PYQT4_FIND_REQUIRED)
|
||||
ENDIF(PYQT4_FOUND)
|
||||
ENDIF(PYQT4_FOUND AND PYUIC4_FOUND)
|
||||
|
||||
ENDIF(EXISTS PYQT4_VERSION)
|
||||
ENDIF(EXISTS PYQT4_VERSION AND EXISTS PYUIC4_EXECUTABLE)
|
||||
|
||||
56
cmake/Modules/FindZeroMQ.cmake
Normal file
56
cmake/Modules/FindZeroMQ.cmake
Normal file
@@ -0,0 +1,56 @@
|
||||
# - Find zeromq libraries
|
||||
# This module finds zeromq if it is installed and determines where the
|
||||
# include files and libraries are. It also determines what the name of
|
||||
# the library is. This code sets the following variables:
|
||||
#
|
||||
# ZEROMQ_FOUND - have the zeromq libs been found
|
||||
# ZEROMQ_LIBRARIES - path to the zeromq library
|
||||
# ZEROMQ_INCLUDE_DIRS - path to where zmq.h is found
|
||||
# ZEROMQ_DEBUG_LIBRARIES - path to the debug library
|
||||
|
||||
#INCLUDE(CMakeFindFrameworks)
|
||||
# Search for the zeromq framework on Apple.
|
||||
#CMAKE_FIND_FRAMEWORKS(ZeroMQ)
|
||||
|
||||
IF(WIN32)
|
||||
FIND_LIBRARY(ZEROMQ_DEBUG_LIBRARY
|
||||
NAMES libzmq_d zmq_d
|
||||
PATHS
|
||||
${ZEROMQ_LIBRARIES}
|
||||
)
|
||||
ENDIF(WIN32)
|
||||
|
||||
FIND_LIBRARY(ZEROMQ_LIBRARY
|
||||
NAMES libzmq zmq
|
||||
PATHS
|
||||
${ZEROMQ_LIBRARIES}
|
||||
${NSCP_LIBRARYDIR}
|
||||
)
|
||||
|
||||
# IF(ZeroMQ_FRAMEWORKS AND NOT ZEROMQ_INCLUDE_DIR)
|
||||
# FOREACH(dir ${ZeroMQ_FRAMEWORKS})
|
||||
# SET(ZEROMQ_FRAMEWORK_INCLUDES ${ZEROMQ_FRAMEWORK_INCLUDES}
|
||||
# ${dir}/Versions/${_CURRENT_VERSION}/include/zeromq${_CURRENT_VERSION})
|
||||
# ENDFOREACH(dir)
|
||||
# ENDIF(ZeroMQ_FRAMEWORKS AND NOT ZEROMQ_INCLUDE_DIR)
|
||||
|
||||
FIND_PATH(ZEROMQ_INCLUDE_DIR
|
||||
NAMES zmq.hpp
|
||||
PATHS
|
||||
# ${ZEROMQ_FRAMEWORK_INCLUDES}
|
||||
${ZEROMQ_INCLUDE_DIRS}
|
||||
${NSCP_INCLUDEDIR}
|
||||
${ZEROMQ_INCLUDE_DIR}
|
||||
)
|
||||
|
||||
MARK_AS_ADVANCED(
|
||||
ZEROMQ_DEBUG_LIBRARY
|
||||
ZEROMQ_LIBRARY
|
||||
ZEROMQ_INCLUDE_DIR
|
||||
)
|
||||
SET(ZEROMQ_INCLUDE_DIRS "${ZEROMQ_INCLUDE_DIR}")
|
||||
SET(ZEROMQ_LIBRARIES "${ZEROMQ_LIBRARY}")
|
||||
SET(ZEROMQ_DEBUG_LIBRARIES "${ZEROMQ_DEBUG_LIBRARY}")
|
||||
|
||||
INCLUDE(FindPackageHandleStandardArgs)
|
||||
FIND_PACKAGE_HANDLE_STANDARD_ARGS(ZeroMQ DEFAULT_MSG ZEROMQ_LIBRARIES ZEROMQ_INCLUDE_DIRS)
|
||||
99
cmake/Modules/GrBoost.cmake
Normal file
99
cmake/Modules/GrBoost.cmake
Normal file
@@ -0,0 +1,99 @@
|
||||
# Copyright 2010-2011 Free Software Foundation, Inc.
|
||||
#
|
||||
# This file is part of GNU Radio
|
||||
#
|
||||
# GNU Radio is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; either version 3, or (at your option)
|
||||
# any later version.
|
||||
#
|
||||
# GNU Radio is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with GNU Radio; see the file COPYING. If not, write to
|
||||
# the Free Software Foundation, Inc., 51 Franklin Street,
|
||||
# Boston, MA 02110-1301, USA.
|
||||
|
||||
if(DEFINED __INCLUDED_GR_BOOST_CMAKE)
|
||||
return()
|
||||
endif()
|
||||
set(__INCLUDED_GR_BOOST_CMAKE TRUE)
|
||||
|
||||
########################################################################
|
||||
# Setup Boost and handle some system specific things
|
||||
########################################################################
|
||||
|
||||
set(BOOST_REQUIRED_COMPONENTS
|
||||
date_time
|
||||
program_options
|
||||
filesystem
|
||||
system
|
||||
thread
|
||||
)
|
||||
|
||||
if(UNIX AND NOT BOOST_ROOT AND EXISTS "/usr/lib64")
|
||||
list(APPEND BOOST_LIBRARYDIR "/usr/lib64") #fedora 64-bit fix
|
||||
endif(UNIX AND NOT BOOST_ROOT AND EXISTS "/usr/lib64")
|
||||
|
||||
if(MSVC)
|
||||
set(BOOST_REQUIRED_COMPONENTS ${BOOST_REQUIRED_COMPONENTS} chrono)
|
||||
|
||||
if (NOT DEFINED BOOST_ALL_DYN_LINK)
|
||||
set(BOOST_ALL_DYN_LINK TRUE)
|
||||
endif()
|
||||
set(BOOST_ALL_DYN_LINK "${BOOST_ALL_DYN_LINK}" CACHE BOOL "boost enable dynamic linking")
|
||||
if(BOOST_ALL_DYN_LINK)
|
||||
add_definitions(-DBOOST_ALL_DYN_LINK) #setup boost auto-linking in msvc
|
||||
else(BOOST_ALL_DYN_LINK)
|
||||
unset(BOOST_REQUIRED_COMPONENTS) #empty components list for static link
|
||||
endif(BOOST_ALL_DYN_LINK)
|
||||
endif(MSVC)
|
||||
|
||||
find_package(Boost "1.35" COMPONENTS ${BOOST_REQUIRED_COMPONENTS})
|
||||
|
||||
# This does not allow us to disable specific versions. It is used
|
||||
# internally by cmake to know the formation newer versions. As newer
|
||||
# Boost version beyond what is shown here are produced, we must extend
|
||||
# this list. To disable Boost versions, see below.
|
||||
set(Boost_ADDITIONAL_VERSIONS
|
||||
"1.35.0" "1.35" "1.36.0" "1.36" "1.37.0" "1.37" "1.38.0" "1.38" "1.39.0" "1.39"
|
||||
"1.40.0" "1.40" "1.41.0" "1.41" "1.42.0" "1.42" "1.43.0" "1.43" "1.44.0" "1.44"
|
||||
"1.45.0" "1.45" "1.46.0" "1.46" "1.47.0" "1.47" "1.48.0" "1.48" "1.49.0" "1.49"
|
||||
"1.50.0" "1.50" "1.51.0" "1.51" "1.52.0" "1.52" "1.53.0" "1.53" "1.54.0" "1.54"
|
||||
"1.55.0" "1.55" "1.56.0" "1.56" "1.57.0" "1.57" "1.58.0" "1.58" "1.59.0" "1.59"
|
||||
"1.60.0" "1.60" "1.61.0" "1.61" "1.62.0" "1.62" "1.63.0" "1.63" "1.64.0" "1.64"
|
||||
"1.65.0" "1.65" "1.66.0" "1.66" "1.67.0" "1.67" "1.68.0" "1.68" "1.69.0" "1.69"
|
||||
)
|
||||
|
||||
# Boost 1.52 disabled, see https://svn.boost.org/trac/boost/ticket/7669
|
||||
# Similar problems with Boost 1.46 and 1.47.
|
||||
|
||||
OPTION(ENABLE_BAD_BOOST "Enable known bad versions of Boost" OFF)
|
||||
if(ENABLE_BAD_BOOST)
|
||||
MESSAGE(STATUS "Enabling use of known bad versions of Boost.")
|
||||
endif(ENABLE_BAD_BOOST)
|
||||
|
||||
# For any unsuitable Boost version, add the version number below in
|
||||
# the following format: XXYYZZ
|
||||
# Where:
|
||||
# XX is the major version ('10' for version 1)
|
||||
# YY is the minor version number ('46' for 1.46)
|
||||
# ZZ is the patcher version number (typically just '00')
|
||||
set(Boost_NOGO_VERSIONS
|
||||
104600 104601 104700 105200
|
||||
)
|
||||
|
||||
foreach(ver ${Boost_NOGO_VERSIONS})
|
||||
if(${Boost_VERSION} EQUAL ${ver})
|
||||
if(NOT ENABLE_BAD_BOOST)
|
||||
MESSAGE(STATUS "WARNING: Found a known bad version of Boost (v${Boost_VERSION}). Disabling.")
|
||||
set(Boost_FOUND FALSE)
|
||||
else(NOT ENABLE_BAD_BOOST)
|
||||
MESSAGE(STATUS "WARNING: Found a known bad version of Boost (v${Boost_VERSION}). Continuing anyway.")
|
||||
set(Boost_FOUND TRUE)
|
||||
endif(NOT ENABLE_BAD_BOOST)
|
||||
endif(${Boost_VERSION} EQUAL ${ver})
|
||||
endforeach(ver)
|
||||
@@ -22,7 +22,7 @@
|
||||
#ifndef INCLUDED_AIR_MODES_API_H
|
||||
#define INCLUDED_AIR_MODES_API_H
|
||||
|
||||
#include <gruel/attributes.h>
|
||||
#include <gnuradio/attributes.h>
|
||||
|
||||
#ifdef AIR_MODES_EXPORTS
|
||||
# define AIR_MODES_API __GR_ATTR_EXPORT
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
#ifndef INCLUDED_AIR_MODES_PREAMBLE_H
|
||||
#define INCLUDED_AIR_MODES_PREAMBLE_H
|
||||
|
||||
#include <gr_block.h>
|
||||
#include <gnuradio/block.h>
|
||||
#include <air_modes_api.h>
|
||||
|
||||
class air_modes_preamble;
|
||||
@@ -35,7 +35,7 @@ AIR_MODES_API air_modes_preamble_sptr air_make_modes_preamble(int channel_rate,
|
||||
* \brief mode select preamble detection
|
||||
* \ingroup block
|
||||
*/
|
||||
class AIR_MODES_API air_modes_preamble : public gr_block
|
||||
class AIR_MODES_API air_modes_preamble : public gr::block
|
||||
{
|
||||
private:
|
||||
friend air_modes_preamble_sptr air_make_modes_preamble(int channel_rate, float threshold_db);
|
||||
@@ -49,7 +49,7 @@ private:
|
||||
float d_threshold_db;
|
||||
float d_threshold;
|
||||
pmt::pmt_t d_me, d_key;
|
||||
gr_tag_t d_timestamp;
|
||||
gr::tag_t d_timestamp;
|
||||
double d_secs_per_sample;
|
||||
|
||||
public:
|
||||
@@ -57,6 +57,10 @@ public:
|
||||
gr_vector_int &ninput_items,
|
||||
gr_vector_const_void_star &input_items,
|
||||
gr_vector_void_star &output_items);
|
||||
|
||||
void set_rate(int channel_rate);
|
||||
void set_threshold(float threshold_db);
|
||||
float get_threshold(void);
|
||||
};
|
||||
|
||||
#endif /* INCLUDED_AIR_MODES_PREAMBLE_H */
|
||||
|
||||
@@ -23,30 +23,30 @@
|
||||
#ifndef INCLUDED_AIR_MODES_slicer_H
|
||||
#define INCLUDED_AIR_MODES_slicer_H
|
||||
|
||||
#include <gr_sync_block.h>
|
||||
#include <gr_msg_queue.h>
|
||||
#include <gnuradio/sync_block.h>
|
||||
#include <gnuradio/msg_queue.h>
|
||||
#include <air_modes_api.h>
|
||||
|
||||
class air_modes_slicer;
|
||||
typedef boost::shared_ptr<air_modes_slicer> air_modes_slicer_sptr;
|
||||
|
||||
AIR_MODES_API air_modes_slicer_sptr air_make_modes_slicer(int channel_rate, gr_msg_queue_sptr queue);
|
||||
AIR_MODES_API air_modes_slicer_sptr air_make_modes_slicer(int channel_rate, gr::msg_queue::sptr queue);
|
||||
|
||||
/*!
|
||||
* \brief mode select slicer detection
|
||||
* \ingroup block
|
||||
*/
|
||||
class AIR_MODES_API air_modes_slicer : public gr_sync_block
|
||||
class AIR_MODES_API air_modes_slicer : public gr::sync_block
|
||||
{
|
||||
private:
|
||||
friend air_modes_slicer_sptr air_make_modes_slicer(int channel_rate, gr_msg_queue_sptr queue);
|
||||
air_modes_slicer(int channel_rate, gr_msg_queue_sptr queue);
|
||||
friend air_modes_slicer_sptr air_make_modes_slicer(int channel_rate, gr::msg_queue::sptr queue);
|
||||
air_modes_slicer(int channel_rate, gr::msg_queue::sptr queue);
|
||||
|
||||
int d_check_width;
|
||||
int d_chip_rate;
|
||||
int d_samples_per_chip;
|
||||
int d_samples_per_symbol;
|
||||
gr_msg_queue_sptr d_queue;
|
||||
gr::msg_queue::sptr d_queue;
|
||||
std::ostringstream d_payload;
|
||||
|
||||
public:
|
||||
|
||||
@@ -27,7 +27,7 @@ add_library(air_modes SHARED
|
||||
air_modes_slicer.cc
|
||||
modes_crc.cc
|
||||
)
|
||||
target_link_libraries(air_modes ${Boost_LIBRARIES} ${GRUEL_LIBRARIES} ${GNURADIO_CORE_LIBRARIES})
|
||||
target_link_libraries(air_modes ${Boost_LIBRARIES} ${GNURADIO_RUNTIME_LIBRARIES})
|
||||
set_target_properties(air_modes PROPERTIES DEFINE_SYMBOL "AIR_MODES_EXPORTS")
|
||||
set_target_properties(air_modes PROPERTIES SOVERSION "${gr-gr-air-modes_VERSION_MAJOR}")
|
||||
set_target_properties(air_modes PROPERTIES VERSION "${gr-gr-air-modes_VERSION_MAJOR}.${gr-gr-air-modes_VERSION_MINOR}")
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
/*
|
||||
# Copyright 2010 Nick Foster
|
||||
# Copyright 2013 Nicholas Corgan
|
||||
#
|
||||
# This file is part of gr-air-modes
|
||||
#
|
||||
@@ -24,11 +25,12 @@
|
||||
#include "config.h"
|
||||
#endif
|
||||
|
||||
#include <ciso646>
|
||||
#include <air_modes_preamble.h>
|
||||
#include <gr_io_signature.h>
|
||||
#include <gnuradio/io_signature.h>
|
||||
#include <string.h>
|
||||
#include <iostream>
|
||||
#include <gr_tags.h>
|
||||
#include <gnuradio/tags.h>
|
||||
|
||||
air_modes_preamble_sptr air_make_modes_preamble(int channel_rate, float threshold_db)
|
||||
{
|
||||
@@ -36,9 +38,9 @@ air_modes_preamble_sptr air_make_modes_preamble(int channel_rate, float threshol
|
||||
}
|
||||
|
||||
air_modes_preamble::air_modes_preamble(int channel_rate, float threshold_db) :
|
||||
gr_block ("modes_preamble",
|
||||
gr_make_io_signature2 (2, 2, sizeof(float), sizeof(float)), //stream 0 is received data, stream 1 is moving average for reference
|
||||
gr_make_io_signature (1, 1, sizeof(float))) //the output packets
|
||||
gr::block ("modes_preamble",
|
||||
gr::io_signature::make2 (2, 2, sizeof(float), sizeof(float)), //stream 0 is received data, stream 1 is moving average for reference
|
||||
gr::io_signature::make (1, 1, sizeof(float))) //the output packets
|
||||
{
|
||||
d_chip_rate = 2000000; //2Mchips per second
|
||||
d_samples_per_chip = channel_rate / d_chip_rate; //must be integer number of samples per chip to work
|
||||
@@ -51,8 +53,8 @@ air_modes_preamble::air_modes_preamble(int channel_rate, float threshold_db) :
|
||||
|
||||
std::stringstream str;
|
||||
str << name() << unique_id();
|
||||
d_me = pmt::pmt_string_to_symbol(str.str());
|
||||
d_key = pmt::pmt_string_to_symbol("preamble_found");
|
||||
d_me = pmt::string_to_symbol(str.str());
|
||||
d_key = pmt::string_to_symbol("preamble_found");
|
||||
set_history(d_samples_per_symbol);
|
||||
}
|
||||
|
||||
@@ -79,14 +81,14 @@ static double correlate_preamble(const float *in, int samples_per_chip) {
|
||||
}
|
||||
|
||||
//todo: make it return a pair of some kind, otherwise you can lose precision
|
||||
static double tag_to_timestamp(gr_tag_t tstamp, uint64_t abs_sample_cnt, double secs_per_sample) {
|
||||
static double tag_to_timestamp(gr::tag_t tstamp, uint64_t abs_sample_cnt, double secs_per_sample) {
|
||||
uint64_t ts_sample, last_whole_stamp;
|
||||
double last_frac_stamp;
|
||||
|
||||
if(tstamp.key == NULL || pmt::pmt_symbol_to_string(tstamp.key) != "rx_time") return 0;
|
||||
if(tstamp.key == NULL || pmt::symbol_to_string(tstamp.key) != "rx_time") return 0;
|
||||
|
||||
last_whole_stamp = pmt::pmt_to_uint64(pmt::pmt_tuple_ref(tstamp.value, 0));
|
||||
last_frac_stamp = pmt::pmt_to_double(pmt::pmt_tuple_ref(tstamp.value, 1));
|
||||
last_whole_stamp = pmt::to_uint64(pmt::tuple_ref(tstamp.value, 0));
|
||||
last_frac_stamp = pmt::to_double(pmt::tuple_ref(tstamp.value, 1));
|
||||
ts_sample = tstamp.offset;
|
||||
|
||||
double tstime = double(abs_sample_cnt * secs_per_sample) + last_whole_stamp + last_frac_stamp;
|
||||
@@ -120,8 +122,8 @@ int air_modes_preamble::general_work(int noutput_items,
|
||||
};
|
||||
|
||||
uint64_t abs_sample_cnt = nitems_read(0);
|
||||
std::vector<gr_tag_t> tstamp_tags;
|
||||
get_tags_in_range(tstamp_tags, 0, abs_sample_cnt, abs_sample_cnt + ninputs, pmt::pmt_string_to_symbol("rx_time"));
|
||||
std::vector<gr::tag_t> tstamp_tags;
|
||||
get_tags_in_range(tstamp_tags, 0, abs_sample_cnt, abs_sample_cnt + ninputs, pmt::string_to_symbol("rx_time"));
|
||||
//tags.back() is the most recent timestamp, then.
|
||||
if(tstamp_tags.size() > 0) {
|
||||
d_timestamp = tstamp_tags.back();
|
||||
@@ -193,7 +195,7 @@ int air_modes_preamble::general_work(int noutput_items,
|
||||
add_item_tag(0, //stream ID
|
||||
nitems_written(0), //sample
|
||||
d_key, //frame_info
|
||||
pmt::pmt_from_double(tstamp),
|
||||
pmt::from_double(tstamp),
|
||||
d_me //block src id
|
||||
);
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
/*
|
||||
# Copyright 2010 Nick Foster
|
||||
# Copyright 2013 Nicholas Corgan
|
||||
#
|
||||
# This file is part of gr-air-modes
|
||||
#
|
||||
@@ -24,14 +25,15 @@
|
||||
#include "config.h"
|
||||
#endif
|
||||
|
||||
#include <ciso646>
|
||||
#include <air_modes_slicer.h>
|
||||
#include <gr_io_signature.h>
|
||||
#include <gnuradio/io_signature.h>
|
||||
#include <air_modes_types.h>
|
||||
#include <sstream>
|
||||
#include <iomanip>
|
||||
#include <modes_crc.h>
|
||||
#include <iostream>
|
||||
#include <gr_tags.h>
|
||||
#include <gnuradio/tags.h>
|
||||
|
||||
extern "C"
|
||||
{
|
||||
@@ -39,15 +41,15 @@ extern "C"
|
||||
#include <string.h>
|
||||
}
|
||||
|
||||
air_modes_slicer_sptr air_make_modes_slicer(int channel_rate, gr_msg_queue_sptr queue)
|
||||
air_modes_slicer_sptr air_make_modes_slicer(int channel_rate, gr::msg_queue::sptr queue)
|
||||
{
|
||||
return air_modes_slicer_sptr (new air_modes_slicer(channel_rate, queue));
|
||||
}
|
||||
|
||||
air_modes_slicer::air_modes_slicer(int channel_rate, gr_msg_queue_sptr queue) :
|
||||
gr_sync_block ("modes_slicer",
|
||||
gr_make_io_signature (1, 1, sizeof(float)), //stream 0 is received data, stream 1 is binary preamble detector output
|
||||
gr_make_io_signature (0, 0, 0) )
|
||||
air_modes_slicer::air_modes_slicer(int channel_rate, gr::msg_queue::sptr queue) :
|
||||
gr::sync_block ("modes_slicer",
|
||||
gr::io_signature::make (1, 1, sizeof(float)), //stream 0 is received data, stream 1 is binary preamble detector output
|
||||
gr::io_signature::make (0, 0, 0) )
|
||||
{
|
||||
//initialize private data here
|
||||
d_chip_rate = 2000000; //2Mchips per second
|
||||
@@ -105,10 +107,10 @@ int air_modes_slicer::work(int noutput_items,
|
||||
|
||||
if(0) std::cout << "Slicer called with " << size << " samples" << std::endl;
|
||||
|
||||
std::vector<gr_tag_t> tags;
|
||||
std::vector<gr::tag_t> tags;
|
||||
uint64_t abs_sample_cnt = nitems_read(0);
|
||||
get_tags_in_range(tags, 0, abs_sample_cnt, abs_sample_cnt + size, pmt::pmt_string_to_symbol("preamble_found"));
|
||||
std::vector<gr_tag_t>::iterator tag_iter;
|
||||
get_tags_in_range(tags, 0, abs_sample_cnt, abs_sample_cnt + size, pmt::string_to_symbol("preamble_found"));
|
||||
std::vector<gr::tag_t>::iterator tag_iter;
|
||||
|
||||
for(tag_iter = tags.begin(); tag_iter != tags.end(); tag_iter++) {
|
||||
uint64_t i = tag_iter->offset - abs_sample_cnt;
|
||||
@@ -156,7 +158,7 @@ int air_modes_slicer::work(int noutput_items,
|
||||
}
|
||||
|
||||
/******************** BEGIN TIMESTAMP BS ******************/
|
||||
rx_packet.timestamp = pmt_to_double(tag_iter->value);
|
||||
rx_packet.timestamp = pmt::to_double(tag_iter->value);
|
||||
/******************* END TIMESTAMP BS *********************/
|
||||
|
||||
//increment for the next round
|
||||
@@ -187,7 +189,7 @@ int air_modes_slicer::work(int noutput_items,
|
||||
|
||||
d_payload << " " << std::setw(6) << rx_packet.crc << " " << std::dec << rx_packet.reference_level
|
||||
<< " " << std::setprecision(10) << std::setw(10) << rx_packet.timestamp;
|
||||
gr_message_sptr msg = gr_make_message_from_string(std::string(d_payload.str()));
|
||||
gr::message::sptr msg = gr::message::make_from_string(std::string(d_payload.str()));
|
||||
d_queue->handle(msg);
|
||||
}
|
||||
if(0) std::cout << "Slicer consumed " << size << ", returned " << size << std::endl;
|
||||
|
||||
@@ -34,6 +34,7 @@ GR_PYTHON_INSTALL(
|
||||
altitude.py
|
||||
az_map.py
|
||||
cpr.py
|
||||
html_template.py
|
||||
mlat.py
|
||||
exceptions.py
|
||||
flightgear.py
|
||||
@@ -41,10 +42,13 @@ GR_PYTHON_INSTALL(
|
||||
kml.py
|
||||
parse.py
|
||||
msprint.py
|
||||
radio.py
|
||||
raw_server.py
|
||||
rx_path.py
|
||||
sbs1.py
|
||||
sql.py
|
||||
types.py
|
||||
zmq_socket.py
|
||||
Quaternion.py
|
||||
DESTINATION ${GR_PYTHON_DIR}/air_modes
|
||||
)
|
||||
|
||||
@@ -52,14 +52,20 @@ from air_modes_swig import *
|
||||
# import any pure python here
|
||||
#
|
||||
from rx_path import rx_path
|
||||
from parse import parse,modes_reply
|
||||
from zmq_socket import zmq_pubsub_iface
|
||||
from parse import *
|
||||
from msprint import output_print
|
||||
from sql import output_sql
|
||||
from sbs1 import output_sbs1
|
||||
from kml import output_kml
|
||||
from kml import output_kml, output_jsonp
|
||||
from raw_server import raw_server
|
||||
from radio import modes_radio
|
||||
from exceptions import *
|
||||
from az_map import *
|
||||
from types import *
|
||||
from altitude import *
|
||||
from cpr import cpr_decoder
|
||||
from html_template import html_template
|
||||
#this is try/excepted in case the user doesn't have numpy installed
|
||||
try:
|
||||
from flightgear import output_flightgear
|
||||
|
||||
@@ -26,6 +26,7 @@ from PyQt4 import QtCore, QtGui
|
||||
import threading
|
||||
import math
|
||||
import air_modes
|
||||
from air_modes.exceptions import *
|
||||
|
||||
|
||||
# model has max range vs. azimuth in n-degree increments
|
||||
@@ -168,30 +169,30 @@ class az_map(QtGui.QWidget):
|
||||
self.ringsize = ringsize
|
||||
self.drawPath()
|
||||
|
||||
class az_map_output(air_modes.parse):
|
||||
def __init__(self, mypos, model):
|
||||
air_modes.parse.__init__(self, mypos)
|
||||
class az_map_output:
|
||||
def __init__(self, cprdec, model, pub):
|
||||
self._cpr = cprdec
|
||||
self.model = model
|
||||
pub.subscribe("type17_dl", self.output)
|
||||
|
||||
def output(self, msg):
|
||||
[data, ecc, reference, timestamp] = msg.split()
|
||||
data = air_modes.modes_reply(long(data, 16))
|
||||
ecc = long(ecc, 16)
|
||||
rssi = 10.*math.log10(float(reference))
|
||||
msgtype = data["df"]
|
||||
now = time.time()
|
||||
try:
|
||||
msgtype = msg.data["df"]
|
||||
now = time.time()
|
||||
|
||||
if msgtype == 17:
|
||||
icao = data["aa"]
|
||||
subtype = data["ftc"]
|
||||
distance, altitude, bearing = [0,0,0]
|
||||
if 5 <= subtype <= 8:
|
||||
(ground_track, decoded_lat, decoded_lon, distance, bearing) = self.parseBDS06(data)
|
||||
altitude = 0
|
||||
elif 9 <= subtype <= 18:
|
||||
(altitude, decoded_lat, decoded_lon, distance, bearing) = self.parseBDS05(data)
|
||||
if msgtype == 17:
|
||||
icao = msg.data["aa"]
|
||||
subtype = msg.data["ftc"]
|
||||
distance, altitude, bearing = [0,0,0]
|
||||
if 5 <= subtype <= 8:
|
||||
(ground_track, decoded_lat, decoded_lon, distance, bearing) = air_modes.parseBDS06(msg.data, self._cpr)
|
||||
altitude = 0
|
||||
elif 9 <= subtype <= 18:
|
||||
(altitude, decoded_lat, decoded_lon, distance, bearing) = air_modes.parseBDS05(msg.data, self._cpr)
|
||||
|
||||
self.model.addRecord(bearing, altitude, distance)
|
||||
self.model.addRecord(bearing, altitude, distance)
|
||||
except ADSBError:
|
||||
pass
|
||||
|
||||
|
||||
##############################
|
||||
|
||||
@@ -188,7 +188,7 @@ class cpr_decoder:
|
||||
self.evenlist_sfc = {}
|
||||
self.oddlist_sfc = {}
|
||||
|
||||
def set_location(new_location):
|
||||
def set_location(self, new_location):
|
||||
self.my_location = new_location
|
||||
|
||||
def weed_poslists(self):
|
||||
|
||||
@@ -24,12 +24,12 @@ class ADSBError(Exception):
|
||||
|
||||
class MetricAltError(ADSBError):
|
||||
pass
|
||||
|
||||
|
||||
class ParserError(ADSBError):
|
||||
pass
|
||||
|
||||
class NoHandlerError(ADSBError):
|
||||
def __init__(self, msgtype):
|
||||
def __init__(self, msgtype=None):
|
||||
self.msgtype = msgtype
|
||||
|
||||
class MlatNonConvergeError(ADSBError):
|
||||
|
||||
@@ -14,49 +14,48 @@ from Quaternion import Quat
|
||||
import numpy
|
||||
from air_modes.exceptions import *
|
||||
|
||||
class output_flightgear(air_modes.parse):
|
||||
def __init__(self, localpos, hostname, port):
|
||||
air_modes.parse.__init__(self, localpos)
|
||||
class output_flightgear:
|
||||
def __init__(self, cprdec, hostname, port, pub):
|
||||
self.hostname = hostname
|
||||
self.port = port
|
||||
self.localpos = localpos
|
||||
self.positions = {}
|
||||
self.velocities = {}
|
||||
self.callsigns = {}
|
||||
self._cpr = cprdec
|
||||
|
||||
self.sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||
self.sock.connect((self.hostname, self.port))
|
||||
pub.subscribe("type17_dl", output)
|
||||
|
||||
def output(self, message):
|
||||
[data, ecc, reference, timestamp] = message.split()
|
||||
data = air_modes.modes_reply(long(data, 16))
|
||||
def output(self, msg):
|
||||
|
||||
try:
|
||||
msgtype = data["df"]
|
||||
msgtype = msg.data["df"]
|
||||
if msgtype == 17: #ADS-B report
|
||||
icao24 = data["aa"]
|
||||
bdsreg = data["me"].get_type()
|
||||
icao24 = msg.data["aa"]
|
||||
bdsreg = msg.data["me"].get_type()
|
||||
if bdsreg == 0x08: #ident packet
|
||||
(ident, actype) = self.parseBDS08(data)
|
||||
(ident, actype) = air_modes.parseBDS08(data)
|
||||
#select model based on actype
|
||||
self.callsigns[icao24] = [ident, actype]
|
||||
|
||||
elif bdsreg == 0x06: #BDS0,6 pos
|
||||
[ground_track, decoded_lat, decoded_lon, rnge, bearing] = self.parseBDS06(data)
|
||||
[ground_track, decoded_lat, decoded_lon, rnge, bearing] = air_modes.parseBDS06(data, self._cpr)
|
||||
self.positions[icao24] = [decoded_lat, decoded_lon, 0]
|
||||
self.update(icao24)
|
||||
|
||||
elif bdsreg == 0x05: #BDS0,5 pos
|
||||
[altitude, decoded_lat, decoded_lon, rnge, bearing] = self.parseBDS05(data)
|
||||
[altitude, decoded_lat, decoded_lon, rnge, bearing] = air_modes.parseBDS05(data, self._cpr)
|
||||
self.positions[icao24] = [decoded_lat, decoded_lon, altitude]
|
||||
self.update(icao24)
|
||||
|
||||
elif bdsreg == 0x09: #velocity
|
||||
subtype = data["bds09"].get_type()
|
||||
if subtype == 0:
|
||||
[velocity, heading, vert_spd, turnrate] = self.parseBDS09_0(data)
|
||||
[velocity, heading, vert_spd, turnrate] = air_modes.parseBDS09_0(data)
|
||||
elif subtype == 1:
|
||||
[velocity, heading, vert_spd] = self.parseBDS09_1(data)
|
||||
[velocity, heading, vert_spd] = air_modes.parseBDS09_1(data)
|
||||
turnrate = 0
|
||||
else:
|
||||
return
|
||||
|
||||
@@ -22,9 +22,11 @@
|
||||
# This file contains data models, view delegates, and associated classes
|
||||
# for handling the GUI back end data model.
|
||||
|
||||
from PyQt4 import QtCore, QtGui
|
||||
from PyQt4 import QtCore, QtGui, QtSql
|
||||
import air_modes
|
||||
import threading, math, time
|
||||
from air_modes.exceptions import *
|
||||
from gnuradio.gr.pubsub import pubsub
|
||||
|
||||
#fades the ICAOs out as their last report gets older,
|
||||
#and display ident if available, ICAO otherwise
|
||||
@@ -36,12 +38,16 @@ class ICAOViewDelegate(QtGui.QStyledItemDelegate):
|
||||
painter.drawRect(option.rect)
|
||||
|
||||
#if there's an ident available, use it. otherwise print the ICAO
|
||||
if index.model().data(index.model().index(index.row(), 9)) != QtCore.QVariant():
|
||||
paintstr = index.model().data(index.model().index(index.row(), 9)).toString()
|
||||
if index.model().data(index.model().index(index.row(), 8)) != QtCore.QVariant():
|
||||
paintstr = index.model().data(index.model().index(index.row(), 8)).toString()
|
||||
else:
|
||||
paintstr = index.model().data(index.model().index(index.row(), 0)).toString()
|
||||
last_report = index.model().data(index.model().index(index.row(), 1)).toDouble()[0]
|
||||
age = (time.time() - last_report)
|
||||
|
||||
#FIXME this is kind of heinous, find out how you got int data out of it last time
|
||||
last_report = time.strptime(str(index.model().data(index.model().index(index.row(), 1)).toString()), "%Y-%m-%d %H:%M:%S")
|
||||
age = (time.mktime(time.gmtime()) - time.mktime(last_report)) - 3600.*time.daylight
|
||||
print age
|
||||
|
||||
max_age = 60. #age at which it grays out
|
||||
#minimum alpha is 0x40 (oldest), max is 0xFF (newest)
|
||||
age = min(age, max_age)
|
||||
@@ -49,150 +55,36 @@ class ICAOViewDelegate(QtGui.QStyledItemDelegate):
|
||||
painter.setPen(QtGui.QColor(0, 0, 0, alpha))
|
||||
painter.drawText(option.rect.left()+3, option.rect.top(), option.rect.width(), option.rect.height(), option.displayAlignment, paintstr)
|
||||
|
||||
#the data model used to display dashboard data.
|
||||
class dashboard_data_model(QtCore.QAbstractTableModel):
|
||||
#class dashboard_sql_model(QtCore.QAbstractTableModel):
|
||||
# def __init__(self, parent):
|
||||
# QtCore.QAbstractTableModel.__init__(self, parent)
|
||||
|
||||
# def update(self, icao):
|
||||
|
||||
#TODO must add libqt4-sql, libqt4-sql-sqlite, python-qt4-sql to dependencies
|
||||
#TODO looks like you're going to have to either integrate this into sql.py (ugh!) or find a way to keep it in sync
|
||||
#seems like it wants to have control over maintaining data currency
|
||||
#worst case is you make your own damn SQL query model based on abstracttablemodel.
|
||||
class dashboard_sql_model(QtSql.QSqlQueryModel):
|
||||
def __init__(self, parent):
|
||||
QtCore.QAbstractTableModel.__init__(self, parent)
|
||||
self._data = []
|
||||
self.lock = threading.Lock()
|
||||
self._colnames = ["icao", "seen", "rssi", "latitude", "longitude", "altitude", "speed", "heading", "vertical", "ident", "type", "range", "bearing"]
|
||||
#custom precision limits for display
|
||||
self._precisions = [None, None, None, 6, 6, 0, 0, 0, 0, None, None, 2, 0]
|
||||
for field in self._colnames:
|
||||
self.setHeaderData(self._colnames.index(field), QtCore.Qt.Horizontal, field)
|
||||
def rowCount(self, parent=QtCore.QVariant()):
|
||||
return len(self._data)
|
||||
def columnCount(self, parent=QtCore.QVariant()):
|
||||
return len(self._colnames)
|
||||
def data(self, index, role=QtCore.Qt.DisplayRole):
|
||||
if not index.isValid():
|
||||
return QtCore.QVariant()
|
||||
if index.row() >= self.rowCount():
|
||||
return QtCore.QVariant()
|
||||
if index.column() >= self.columnCount():
|
||||
return QtCore.QVariant()
|
||||
if (role != QtCore.Qt.DisplayRole) and (role != QtCore.Qt.EditRole):
|
||||
return QtCore.QVariant()
|
||||
if self._data[index.row()][index.column()] is None:
|
||||
return QtCore.QVariant()
|
||||
else:
|
||||
#if there's a dedicated precision for that column, print it out with the specified precision.
|
||||
#this only works well if you DON'T have other views/widgets that depend on numeric data coming out.
|
||||
#i don't like this, but it works for now. unfortunately it seems like Qt doesn't give you a
|
||||
#good alternative.
|
||||
if self._precisions[index.column()] is not None:
|
||||
return QtCore.QVariant("%.*f" % (self._precisions[index.column()], self._data[index.row()][index.column()]))
|
||||
else:
|
||||
if self._colnames[index.column()] == "icao":
|
||||
return QtCore.QVariant("%06x" % self._data[index.row()][index.column()]) #return as hex string
|
||||
else:
|
||||
return QtCore.QVariant(self._data[index.row()][index.column()])
|
||||
QtSql.QSqlQueryModel.__init__(self, parent)
|
||||
self._query = """select tab1.icao, tab1.seen, tab1.lat, tab1.lon, tab1.alt, speed, heading, vertical, ident, type
|
||||
from (select * from (select * from positions order by seen desc) group by icao) tab1
|
||||
left join (select * from (select * from vectors order by seen desc) group by icao) tab2
|
||||
on tab1.icao=tab2.icao
|
||||
left join (select * from (select * from ident)) tab3
|
||||
on tab1.icao=tab3.icao
|
||||
where tab1.seen > datetime('now', '-1 minute')"""
|
||||
self._sql = None
|
||||
self._db = QtSql.QSqlDatabase("QSQLITE")
|
||||
self._db.setDatabaseName("adsb.db") #TODO specify this elsewhere
|
||||
self._db.open()
|
||||
#what is this i don't even
|
||||
#fetches the combined data of all three tables for all ICAOs seen in the last minute.
|
||||
#FIXME PyQt's SQLite gives you different results than the SQLite browser
|
||||
self.setQuery(self._query, self._db)
|
||||
|
||||
def setData(self, index, value, role=QtCore.Qt.EditRole):
|
||||
self.lock.acquire()
|
||||
if not index.isValid():
|
||||
return False
|
||||
if index.row() >= self.rowCount():
|
||||
return False
|
||||
if index.column >= self.columnCount():
|
||||
return False
|
||||
if role != QtCore.Qt.EditRole:
|
||||
return False
|
||||
self._data[index.row()][index.column()] = value
|
||||
self.lock.release()
|
||||
|
||||
#addRecord implements an upsert on self._data; that is,
|
||||
#it updates the row if the ICAO exists, or else it creates a new row.
|
||||
def addRecord(self, record):
|
||||
self.lock.acquire()
|
||||
icaos = [x[0] for x in self._data]
|
||||
if record["icao"] in icaos:
|
||||
row = icaos.index(record["icao"])
|
||||
for column in record:
|
||||
self._data[row][self._colnames.index(column)] = record[column]
|
||||
#create index to existing row and tell the model everything's changed in this row
|
||||
#or inside the for loop, use dataChanged on each changed field (might be better)
|
||||
self.dataChanged.emit(self.createIndex(row, 0), self.createIndex(row, len(self._colnames)-1))
|
||||
|
||||
#only create records for ICAOs with ADS-B reports
|
||||
elif ("latitude" or "speed" or "ident") in record:
|
||||
#find new inserted row number
|
||||
icaos.append(record["icao"])
|
||||
newrowoffset = sorted(icaos).index(record["icao"])
|
||||
self.beginInsertRows(QtCore.QModelIndex(), newrowoffset, newrowoffset)
|
||||
newrecord = [None for x in xrange(len(self._colnames))]
|
||||
for col in xrange(0, len(self._colnames)):
|
||||
if self._colnames[col] in record:
|
||||
newrecord[col] = record[self._colnames[col]]
|
||||
self._data.append(newrecord)
|
||||
self._data = sorted(self._data, key = lambda x: x[0]) #sort by icao
|
||||
self.endInsertRows()
|
||||
self.lock.release()
|
||||
self.prune()
|
||||
|
||||
#weeds out ICAOs older than 5 minutes
|
||||
def prune(self):
|
||||
self.lock.acquire()
|
||||
for (index,row) in enumerate(self._data):
|
||||
if time.time() - row[1] >= 60:
|
||||
self.beginRemoveRows(QtCore.QModelIndex(), index, index)
|
||||
self._data.pop(index)
|
||||
self.endRemoveRows()
|
||||
self.lock.release()
|
||||
|
||||
class dashboard_output(air_modes.parse):
|
||||
def __init__(self, mypos, model):
|
||||
air_modes.parse.__init__(self, mypos)
|
||||
self.model = model
|
||||
def output(self, msg):
|
||||
[data, ecc, reference, timestamp] = msg.split()
|
||||
data = air_modes.modes_reply(long(data, 16))
|
||||
ecc = long(ecc, 16)
|
||||
rssi = 10.*math.log10(float(reference))
|
||||
msgtype = data["df"]
|
||||
now = time.time()
|
||||
newrow = {"rssi": rssi, "seen": now}
|
||||
if msgtype in [0, 4, 20]:
|
||||
newrow["altitude"] = air_modes.altitude.decode_alt(data["ac"], True)
|
||||
newrow["icao"] = ecc
|
||||
self.model.addRecord(newrow)
|
||||
|
||||
elif msgtype == 17:
|
||||
icao = data["aa"]
|
||||
newrow["icao"] = icao
|
||||
subtype = data["ftc"]
|
||||
if subtype == 4:
|
||||
(ident, actype) = self.parseBDS08(data)
|
||||
newrow["ident"] = ident
|
||||
newrow["type"] = actype
|
||||
elif 5 <= subtype <= 8:
|
||||
(ground_track, decoded_lat, decoded_lon, rnge, bearing) = self.parseBDS06(data)
|
||||
newrow["heading"] = ground_track
|
||||
newrow["latitude"] = decoded_lat
|
||||
newrow["longitude"] = decoded_lon
|
||||
newrow["altitude"] = 0
|
||||
if rnge is not None:
|
||||
newrow["range"] = rnge
|
||||
newrow["bearing"] = bearing
|
||||
elif 9 <= subtype <= 18:
|
||||
(altitude, decoded_lat, decoded_lon, rnge, bearing) = self.parseBDS05(data)
|
||||
newrow["altitude"] = altitude
|
||||
newrow["latitude"] = decoded_lat
|
||||
newrow["longitude"] = decoded_lon
|
||||
if rnge is not None:
|
||||
newrow["range"] = rnge
|
||||
newrow["bearing"] = bearing
|
||||
elif subtype == 19:
|
||||
subsubtype = data["sub"]
|
||||
velocity = None
|
||||
heading = None
|
||||
vert_spd = None
|
||||
if subsubtype == 0:
|
||||
(velocity, heading, vert_spd) = self.parseBDS09_0(data)
|
||||
elif 1 <= subsubtype <= 2:
|
||||
(velocity, heading, vert_spd) = self.parseBDS09_1(data)
|
||||
newrow["speed"] = velocity
|
||||
newrow["heading"] = heading
|
||||
newrow["vertical"] = vert_spd
|
||||
|
||||
self.model.addRecord(newrow)
|
||||
#the big club
|
||||
def update_all(self, icao):
|
||||
self.setQuery(self._query, self._db)
|
||||
#self.dataChanged.emit(self.createIndex(0, 0), self.createIndex(self.rowCount(), self.columnCount()))
|
||||
|
||||
127
python/html_template.py
Normal file
127
python/html_template.py
Normal 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)
|
||||
113
python/kml.py
113
python/kml.py
@@ -30,21 +30,30 @@ class output_kml(threading.Thread):
|
||||
self.my_coords = localpos
|
||||
self._timeout = timeout
|
||||
self._lock = lock
|
||||
|
||||
self.done = False
|
||||
|
||||
self.shutdown = threading.Event()
|
||||
self.finished = threading.Event()
|
||||
self.setDaemon(1)
|
||||
self.start()
|
||||
|
||||
def run(self):
|
||||
self._db = sqlite3.connect(self._dbname) #read from the db
|
||||
while self.done is False:
|
||||
while self.shutdown.is_set() is False:
|
||||
self.writekml()
|
||||
time.sleep(self._timeout)
|
||||
|
||||
self.done = True
|
||||
time.sleep(self._timeout)
|
||||
|
||||
self._db.close()
|
||||
self._db = None
|
||||
|
||||
self.finished.set()
|
||||
|
||||
def close(self):
|
||||
self.shutdown.set()
|
||||
self.finished.wait(0.2)
|
||||
#there's a bug here where self._timeout is long and close() has
|
||||
#to wait for the sleep to expire before closing. we just bail
|
||||
#instead with the 0.2 param above.
|
||||
|
||||
|
||||
def writekml(self):
|
||||
kmlstr = self.genkml()
|
||||
if kmlstr is not None:
|
||||
@@ -55,7 +64,7 @@ class output_kml(threading.Thread):
|
||||
def locked_execute(self, c, query):
|
||||
with self._lock:
|
||||
c.execute(query)
|
||||
|
||||
|
||||
def draw_circle(self, center, rng):
|
||||
retstr = ""
|
||||
steps=30
|
||||
@@ -82,7 +91,7 @@ class output_kml(threading.Thread):
|
||||
|
||||
retstr = string.lstrip(retstr)
|
||||
return retstr
|
||||
|
||||
|
||||
def genkml(self):
|
||||
#first let's draw the static content
|
||||
retstr="""<?xml version="1.0" encoding="UTF-8"?>\n<kml xmlns="http://www.opengis.net/kml/2.2">\n<Document>\n\t<Style id="airplane">\n\t\t<IconStyle>\n\t\t\t<Icon><href>airports.png</href></Icon>\n\t\t</IconStyle>\n\t</Style>\n\t<Style id="rangering">\n\t<LineStyle>\n\t\t<color>9f4f4faf</color>\n\t\t<width>2</width>\n\t</LineStyle>\n\t</Style>\n\t<Style id="track">\n\t<LineStyle>\n\t\t<color>5fff8f8f</color>\n\t\t<width>4</width>\n\t</LineStyle>\n\t</Style>"""
|
||||
@@ -92,7 +101,7 @@ class output_kml(threading.Thread):
|
||||
for rng in [100, 200, 300]:
|
||||
retstr += """\n\t\t<Placemark>\n\t\t\t<name>%inm</name>\n\t\t\t<styleUrl>#rangering</styleUrl>\n\t\t\t<LinearRing>\n\t\t\t\t<coordinates>%s</coordinates>\n\t\t\t</LinearRing>\n\t\t</Placemark>""" % (rng, self.draw_circle(self.my_coords, rng),)
|
||||
retstr += """\t</Folder>\n"""
|
||||
|
||||
|
||||
retstr += """\t<Folder>\n\t\t<name>Aircraft locations</name>\n\t\t<open>0</open>"""
|
||||
|
||||
#read the database and add KML
|
||||
@@ -115,11 +124,11 @@ class output_kml(threading.Thread):
|
||||
if lon is None: lon = 0
|
||||
alt = track[0][2]
|
||||
if alt is None: alt = 0
|
||||
|
||||
|
||||
metric_alt = alt * 0.3048 #google earth takes meters, the commie bastards
|
||||
|
||||
|
||||
trackstr = ""
|
||||
|
||||
|
||||
for pos in track:
|
||||
trackstr += " %f,%f,%f" % (pos[4], pos[3], pos[2]*0.3048)
|
||||
|
||||
@@ -153,7 +162,7 @@ class output_kml(threading.Thread):
|
||||
seen = 0
|
||||
speed = 0
|
||||
heading = 0
|
||||
vertical = 0
|
||||
vertical = 0
|
||||
#now generate some KML
|
||||
retstr+= "\n\t\t<Placemark>\n\t\t\t<name>%s</name>\n\t\t\t<Style><IconStyle><heading>%i</heading></IconStyle></Style>\n\t\t\t<styleUrl>#airplane</styleUrl>\n\t\t\t<description>\n\t\t\t\t<![CDATA[Altitude: %s<br/>Heading: %i<br/>Speed: %i<br/>Vertical speed: %i<br/>ICAO: %x<br/>Last seen: %s]]>\n\t\t\t</description>\n\t\t\t<Point>\n\t\t\t\t<altitudeMode>absolute</altitudeMode>\n\t\t\t\t<extrude>1</extrude>\n\t\t\t\t<coordinates>%s,%s,%i</coordinates>\n\t\t\t</Point>\n\t\t</Placemark>" % (ident, heading, alt, heading, speed, vertical, icao[0], seen, lon, lat, metric_alt, )
|
||||
|
||||
@@ -161,3 +170,79 @@ class output_kml(threading.Thread):
|
||||
|
||||
retstr+= '\n\t</Folder>\n</Document>\n</kml>'
|
||||
return retstr
|
||||
|
||||
#we just inherit from output_kml because we're doing the same thing, only in a different format.
|
||||
class output_jsonp(output_kml):
|
||||
def set_highlight(self, icao):
|
||||
self.highlight = icao
|
||||
|
||||
def genkml(self):
|
||||
retstr="""jsonp_callback(["""
|
||||
|
||||
# if self.my_coords is not None:
|
||||
# retstr += """\n\t<Folder>\n\t\t<name>Range rings</name>\n\t\t<open>0</open>"""
|
||||
# for rng in [100, 200, 300]:
|
||||
# retstr += """\n\t\t<Placemark>\n\t\t\t<name>%inm</name>\n\t\t\t<styleUrl>#rangering</styleUrl>\n\t\t\t<LinearRing>\n\t\t\t\t<coordinates>%s</coordinates>\n\t\t\t</LinearRing>\n\t\t</Placemark>""" % (rng, self.draw_circle(self.my_coords, rng),)
|
||||
# retstr += """\t</Folder>\n"""
|
||||
|
||||
# retstr += """\t<Folder>\n\t\t<name>Aircraft locations</name>\n\t\t<open>0</open>"""
|
||||
|
||||
#read the database and add KML
|
||||
q = "select distinct icao from positions where seen > datetime('now', '-5 minute')"
|
||||
c = self._db.cursor()
|
||||
self.locked_execute(c, q)
|
||||
icaolist = c.fetchall()
|
||||
#now we have a list icaolist of all ICAOs seen in the last 5 minutes
|
||||
|
||||
for icao in icaolist:
|
||||
icao = icao[0]
|
||||
|
||||
#now get metadata
|
||||
q = "select ident, type from ident where icao=%i" % icao
|
||||
self.locked_execute(c, q)
|
||||
r = c.fetchall()
|
||||
if len(r) != 0:
|
||||
ident = r[0][0]
|
||||
actype = r[0][1]
|
||||
else:
|
||||
ident=""
|
||||
actype = ""
|
||||
if ident is None: ident = ""
|
||||
#get most recent speed/heading/vertical
|
||||
q = "select seen, speed, heading, vertical from vectors where icao=%i order by seen desc limit 1" % icao
|
||||
self.locked_execute(c, q)
|
||||
r = c.fetchall()
|
||||
if len(r) != 0:
|
||||
seen = r[0][0]
|
||||
speed = r[0][1]
|
||||
heading = r[0][2]
|
||||
vertical = r[0][3]
|
||||
|
||||
else:
|
||||
seen = 0
|
||||
speed = 0
|
||||
heading = 0
|
||||
vertical = 0
|
||||
|
||||
q = "select lat, lon, alt from positions where icao=%i order by seen desc limit 1" % icao
|
||||
self.locked_execute(c, q)
|
||||
r = c.fetchall()
|
||||
if len(r) != 0:
|
||||
lat = r[0][0]
|
||||
lon = r[0][1]
|
||||
alt = r[0][2]
|
||||
else:
|
||||
lat = 0
|
||||
lon = 0
|
||||
alt = 0
|
||||
|
||||
highlight = 0
|
||||
if hasattr(self, 'highlight'):
|
||||
if self.highlight == icao:
|
||||
highlight = 1
|
||||
|
||||
#now generate some JSONP
|
||||
retstr+= """{"icao": "%.6x", "lat": %f, "lon": %f, "alt": %i, "hdg": %i, "speed": %i, "vertical": %i, "ident": "%s", "type": "%s", "highlight": %i},""" % (icao, lat, lon, alt, heading, speed, vertical, ident, actype, highlight)
|
||||
|
||||
retstr+= """]);"""
|
||||
return retstr
|
||||
|
||||
@@ -25,204 +25,194 @@ import air_modes
|
||||
from air_modes.exceptions import *
|
||||
import math
|
||||
|
||||
class output_print(air_modes.parse):
|
||||
def __init__(self, mypos):
|
||||
air_modes.parse.__init__(self, mypos)
|
||||
|
||||
def parse(self, message):
|
||||
[data, ecc, reference, timestamp] = message.split()
|
||||
|
||||
ecc = long(ecc, 16)
|
||||
reference = float(reference)
|
||||
timestamp = float(timestamp)
|
||||
|
||||
if reference == 0.0:
|
||||
refdb = -150.0
|
||||
else:
|
||||
refdb = 20.0*math.log10(reference)
|
||||
output = "(%.0f %.10f) " % (refdb, timestamp);
|
||||
|
||||
try:
|
||||
data = air_modes.modes_reply(long(data, 16))
|
||||
msgtype = data["df"]
|
||||
if msgtype == 0:
|
||||
output += self.print0(data, ecc)
|
||||
elif msgtype == 4:
|
||||
output += self.print4(data, ecc)
|
||||
elif msgtype == 5:
|
||||
output += self.print5(data, ecc)
|
||||
elif msgtype == 11:
|
||||
output += self.print11(data, ecc)
|
||||
elif msgtype == 17:
|
||||
output += self.print17(data)
|
||||
elif msgtype == 20 or msgtype == 21 or msgtype == 16:
|
||||
output += self.printTCAS(data, ecc)
|
||||
else:
|
||||
output += "No handler for message type %i from %x (but it's in modes_parse)" % (msgtype, ecc)
|
||||
return output
|
||||
except NoHandlerError as e:
|
||||
output += "No handler for message type %s from %x" % (e.msgtype, ecc)
|
||||
return output
|
||||
except MetricAltError:
|
||||
pass
|
||||
except CPRNoPositionError:
|
||||
pass
|
||||
|
||||
def output(self, msg):
|
||||
parsed = self.parse(msg)
|
||||
if parsed is not None:
|
||||
print self.parse(msg)
|
||||
|
||||
def print0(self, shortdata, ecc):
|
||||
[vs, cc, sl, ri, altitude] = self.parse0(shortdata)
|
||||
|
||||
retstr = "Type 0 (short A-A surveillance) from %x at %ift" % (ecc, altitude)
|
||||
if ri == 0:
|
||||
retstr += " (No TCAS)"
|
||||
elif ri == 2:
|
||||
retstr += " (TCAS resolution inhibited)"
|
||||
elif ri == 3:
|
||||
retstr += " (Vertical TCAS resolution only)"
|
||||
elif ri == 4:
|
||||
retstr += " (Full TCAS resolution)"
|
||||
elif ri == 9:
|
||||
retstr += " (speed <75kt)"
|
||||
elif ri > 9:
|
||||
retstr += " (speed %i-%ikt)" % (75 * (1 << (ri-10)), 75 * (1 << (ri-9)))
|
||||
|
||||
if vs is True:
|
||||
retstr += " (aircraft is on the ground)"
|
||||
|
||||
return retstr
|
||||
|
||||
def print4(self, shortdata, ecc):
|
||||
|
||||
[fs, dr, um, altitude] = self.parse4(shortdata)
|
||||
|
||||
retstr = "Type 4 (short surveillance altitude reply) from %x at %ift" % (ecc, altitude)
|
||||
|
||||
if fs == 1:
|
||||
retstr += " (aircraft is on the ground)"
|
||||
elif fs == 2:
|
||||
retstr += " (AIRBORNE ALERT)"
|
||||
elif fs == 3:
|
||||
retstr += " (GROUND ALERT)"
|
||||
elif fs == 4:
|
||||
retstr += " (SPI ALERT)"
|
||||
elif fs == 5:
|
||||
retstr += " (SPI)"
|
||||
|
||||
return retstr
|
||||
|
||||
def print5(self, shortdata, ecc):
|
||||
[fs, dr, um, ident] = self.parse5(shortdata)
|
||||
|
||||
retstr = "Type 5 (short surveillance ident reply) from %x with ident %i" % (ecc, ident)
|
||||
|
||||
if fs == 1:
|
||||
retstr += " (aircraft is on the ground)"
|
||||
elif fs == 2:
|
||||
retstr += " (AIRBORNE ALERT)"
|
||||
elif fs == 3:
|
||||
retstr += " (GROUND ALERT)"
|
||||
elif fs == 4:
|
||||
retstr += " (SPI ALERT)"
|
||||
elif fs == 5:
|
||||
retstr += " (SPI)"
|
||||
|
||||
return retstr
|
||||
|
||||
def print11(self, data, ecc):
|
||||
[icao24, interrogator, ca] = self.parse11(data, ecc)
|
||||
retstr = "Type 11 (all call reply) from %x in reply to interrogator %i with capability level %i" % (icao24, interrogator, ca+1)
|
||||
return retstr
|
||||
|
||||
def print17(self, data):
|
||||
icao24 = data["aa"]
|
||||
bdsreg = data["me"].get_type()
|
||||
|
||||
retstr = None
|
||||
|
||||
if bdsreg == 0x08:
|
||||
(msg, typestring) = self.parseBDS08(data)
|
||||
retstr = "Type 17 BDS0,8 (ident) from %x type %s ident %s" % (icao24, typestring, msg)
|
||||
|
||||
elif bdsreg == 0x06:
|
||||
[ground_track, decoded_lat, decoded_lon, rnge, bearing] = self.parseBDS06(data)
|
||||
retstr = "Type 17 BDS0,6 (surface report) from %x at (%.6f, %.6f) ground track %i" % (icao24, decoded_lat, decoded_lon, ground_track)
|
||||
if rnge is not None and bearing is not None:
|
||||
retstr += " (%.2f @ %.0f)" % (rnge, bearing)
|
||||
|
||||
elif bdsreg == 0x05:
|
||||
[altitude, decoded_lat, decoded_lon, rnge, bearing] = self.parseBDS05(data)
|
||||
retstr = "Type 17 BDS0,5 (position report) from %x at (%.6f, %.6f)" % (icao24, decoded_lat, decoded_lon)
|
||||
if rnge is not None and bearing is not None:
|
||||
retstr += " (" + "%.2f" % rnge + " @ " + "%.0f" % bearing + ")"
|
||||
retstr += " at " + str(altitude) + "ft"
|
||||
|
||||
elif bdsreg == 0x09:
|
||||
subtype = data["bds09"].get_type()
|
||||
if subtype == 0:
|
||||
[velocity, heading, vert_spd, turnrate] = self.parseBDS09_0(data)
|
||||
retstr = "Type 17 BDS0,9-%i (track report) from %x with velocity %.0fkt heading %.0f VS %.0f turn rate %.0f" \
|
||||
% (subtype, icao24, velocity, heading, vert_spd, turnrate)
|
||||
elif subtype == 1:
|
||||
[velocity, heading, vert_spd] = self.parseBDS09_1(data)
|
||||
retstr = "Type 17 BDS0,9-%i (track report) from %x with velocity %.0fkt heading %.0f VS %.0f" % (subtype, icao24, velocity, heading, vert_spd)
|
||||
elif subtype == 3:
|
||||
[mag_hdg, vel_src, vel, vert_spd, geo_diff] = self.parseBDS09_3(data)
|
||||
retstr = "Type 17 BDS0,9-%i (air course report) from %x with %s %.0fkt magnetic heading %.0f VS %.0f geo. diff. from baro. alt. %.0fft" \
|
||||
% (subtype, icao24, vel_src, vel, mag_hdg, vert_spd, geo_diff)
|
||||
#TODO get rid of class and convert to functions
|
||||
#no need for class here
|
||||
class output_print:
|
||||
def __init__(self, cpr, publisher, callback=None):
|
||||
self._cpr = cpr
|
||||
self._callback = callback
|
||||
#sub to every function that starts with "handle"
|
||||
self._fns = [int(l[6:]) for l in dir(self) if l.startswith("handle")]
|
||||
for i in self._fns:
|
||||
publisher.subscribe("type%i_dl" % i, getattr(self, "handle%i" % i))
|
||||
|
||||
else:
|
||||
retstr = "Type 17 BDS0,9-%i from %x not implemented" % (subtype, icao24)
|
||||
publisher.subscribe("modes_dl", self.catch_nohandler)
|
||||
|
||||
elif bdsreg == 0x62:
|
||||
emerg_str = self.parseBDS62(data)
|
||||
retstr = "Type 17 BDS6,2 (emergency) from %x type %s" % (icao24, emerg_str)
|
||||
|
||||
@staticmethod
|
||||
def prefix(msg):
|
||||
return "(%i %.8f) " % (msg.rssi, msg.timestamp)
|
||||
|
||||
def _print(self, msg):
|
||||
if self._callback is None:
|
||||
print msg
|
||||
else:
|
||||
retstr = "Type 17 with FTC=%i from %x not implemented" % (data["ftc"], icao24)
|
||||
self._callback(msg)
|
||||
|
||||
return retstr
|
||||
def catch_nohandler(self, msg):
|
||||
if msg.data.get_type() not in self._fns:
|
||||
retstr = output_print.prefix(msg)
|
||||
retstr += "No handler for message type %i" % msg.data.get_type()
|
||||
if "aa" not in msg.data.fields:
|
||||
retstr += " from %.6x" % msg.ecc
|
||||
else:
|
||||
retstr += " from %.6x" % msg.data["aa"]
|
||||
self._print(retstr)
|
||||
|
||||
def handle0(self, msg):
|
||||
try:
|
||||
retstr = output_print.prefix(msg)
|
||||
retstr += "Type 0 (short A-A surveillance) from %x at %ift" % (msg.ecc, air_modes.decode_alt(msg.data["ac"], True))
|
||||
ri = msg.data["ri"]
|
||||
if ri == 0:
|
||||
retstr += " (No TCAS)"
|
||||
elif ri == 2:
|
||||
retstr += " (TCAS resolution inhibited)"
|
||||
elif ri == 3:
|
||||
retstr += " (Vertical TCAS resolution only)"
|
||||
elif ri == 4:
|
||||
retstr += " (Full TCAS resolution)"
|
||||
elif ri == 9:
|
||||
retstr += " (speed <75kt)"
|
||||
elif ri > 9:
|
||||
retstr += " (speed %i-%ikt)" % (75 * (1 << (ri-10)), 75 * (1 << (ri-9)))
|
||||
else:
|
||||
raise ADSBError
|
||||
|
||||
def printTCAS(self, data, ecc):
|
||||
msgtype = data["df"]
|
||||
if msgtype == 20 or msgtype == 16:
|
||||
#type 16 does not have fs, dr, um but we get alt here
|
||||
[fs, dr, um, alt] = self.parse4(data)
|
||||
elif msgtype == 21:
|
||||
[fs, dr, um, ident] = self.parse5(data)
|
||||
except ADSBError:
|
||||
return
|
||||
|
||||
if msg.data["vs"] is 1:
|
||||
retstr += " (aircraft is on the ground)"
|
||||
|
||||
self._print(retstr)
|
||||
|
||||
@staticmethod
|
||||
def fs_text(fs):
|
||||
if fs == 1:
|
||||
return " (aircraft is on the ground)"
|
||||
elif fs == 2:
|
||||
return " (AIRBORNE ALERT)"
|
||||
elif fs == 3:
|
||||
return " (GROUND ALERT)"
|
||||
elif fs == 4:
|
||||
return " (SPI ALERT)"
|
||||
elif fs == 5:
|
||||
return " (SPI)"
|
||||
else:
|
||||
raise ADSBError
|
||||
|
||||
def handle4(self, msg):
|
||||
try:
|
||||
retstr = output_print.prefix(msg)
|
||||
retstr += "Type 4 (short surveillance altitude reply) from %x at %ift" % (msg.ecc, air_modes.decode_alt(msg.data["ac"], True))
|
||||
retstr += output_print.fs_text(msg.data["fs"])
|
||||
except ADSBError:
|
||||
return
|
||||
self._print(retstr)
|
||||
|
||||
def handle5(self, msg):
|
||||
try:
|
||||
retstr = output_print.prefix(msg)
|
||||
retstr += "Type 5 (short surveillance ident reply) from %x with ident %i" % (msg.ecc, air_modes.decode_id(msg.data["id"]))
|
||||
retstr += output_print.fs_text(msg.data["fs"])
|
||||
except ADSBError:
|
||||
return
|
||||
self._print(retstr)
|
||||
|
||||
def handle11(self, msg):
|
||||
try:
|
||||
retstr = output_print.prefix(msg)
|
||||
retstr += "Type 11 (all call reply) from %x in reply to interrogator %i with capability level %i" % (msg.data["aa"], msg.ecc & 0xF, msg.data["ca"]+1)
|
||||
except ADSBError:
|
||||
return
|
||||
self._print(retstr)
|
||||
|
||||
#the only one which requires state
|
||||
def handle17(self, msg):
|
||||
icao24 = msg.data["aa"]
|
||||
bdsreg = msg.data["me"].get_type()
|
||||
|
||||
retstr = output_print.prefix(msg)
|
||||
try:
|
||||
if bdsreg == 0x08:
|
||||
(ident, typestring) = air_modes.parseBDS08(msg.data)
|
||||
retstr += "Type 17 BDS0,8 (ident) from %x type %s ident %s" % (icao24, typestring, ident)
|
||||
|
||||
elif bdsreg == 0x06:
|
||||
[ground_track, decoded_lat, decoded_lon, rnge, bearing] = air_modes.parseBDS06(msg.data, self._cpr)
|
||||
retstr += "Type 17 BDS0,6 (surface report) from %x at (%.6f, %.6f) ground track %i" % (icao24, decoded_lat, decoded_lon, ground_track)
|
||||
if rnge is not None and bearing is not None:
|
||||
retstr += " (%.2f @ %.0f)" % (rnge, bearing)
|
||||
|
||||
elif bdsreg == 0x05:
|
||||
[altitude, decoded_lat, decoded_lon, rnge, bearing] = air_modes.parseBDS05(msg.data, self._cpr)
|
||||
retstr += "Type 17 BDS0,5 (position report) from %x at (%.6f, %.6f)" % (icao24, decoded_lat, decoded_lon)
|
||||
if rnge is not None and bearing is not None:
|
||||
retstr += " (" + "%.2f" % rnge + " @ " + "%.0f" % bearing + ")"
|
||||
retstr += " at " + str(altitude) + "ft"
|
||||
|
||||
elif bdsreg == 0x09:
|
||||
subtype = msg.data["bds09"].get_type()
|
||||
if subtype == 0:
|
||||
[velocity, heading, vert_spd, turnrate] = air_modes.parseBDS09_0(msg.data)
|
||||
retstr += "Type 17 BDS0,9-%i (track report) from %x with velocity %.0fkt heading %.0f VS %.0f turn rate %.0f" \
|
||||
% (subtype, icao24, velocity, heading, vert_spd, turnrate)
|
||||
elif subtype == 1:
|
||||
[velocity, heading, vert_spd] = air_modes.parseBDS09_1(msg.data)
|
||||
retstr += "Type 17 BDS0,9-%i (track report) from %x with velocity %.0fkt heading %.0f VS %.0f" % (subtype, icao24, velocity, heading, vert_spd)
|
||||
elif subtype == 3:
|
||||
[mag_hdg, vel_src, vel, vert_spd, geo_diff] = air_modes.parseBDS09_3(msg.data)
|
||||
retstr += "Type 17 BDS0,9-%i (air course report) from %x with %s %.0fkt magnetic heading %.0f VS %.0f geo. diff. from baro. alt. %.0fft" \
|
||||
% (subtype, icao24, vel_src, vel, mag_hdg, vert_spd, geo_diff)
|
||||
|
||||
else:
|
||||
retstr += "Type 17 BDS0,9-%i from %x not implemented" % (subtype, icao24)
|
||||
|
||||
elif bdsreg == 0x62:
|
||||
emerg_str = air_modes.parseBDS62(data)
|
||||
retstr += "Type 17 BDS6,2 (emergency) from %x type %s" % (icao24, emerg_str)
|
||||
|
||||
else:
|
||||
retstr += "Type 17 with FTC=%i from %x not implemented" % (msg.data["ftc"], icao24)
|
||||
except ADSBError:
|
||||
return
|
||||
|
||||
self._print(retstr)
|
||||
|
||||
def printTCAS(self, msg):
|
||||
msgtype = msg.data["df"]
|
||||
|
||||
if msgtype == 16:
|
||||
bds1 = data["vds1"]
|
||||
bds2 = data["vds2"]
|
||||
bds1 = msg.data["vds1"]
|
||||
bds2 = msg.data["vds2"]
|
||||
else:
|
||||
bds1 = data["bds1"]
|
||||
bds2 = data["bds2"]
|
||||
bds1 = msg.data["bds1"]
|
||||
bds2 = msg.data["bds2"]
|
||||
|
||||
retstr = output_print.prefix(msg)
|
||||
|
||||
if bds2 != 0:
|
||||
retstr = "No handler in type %i for BDS2 == %i from %x" % (msgtype, bds2, ecc)
|
||||
retstr += "No handler in type %i for BDS2 == %i from %x" % (msgtype, bds2, msg.ecc)
|
||||
|
||||
elif bds1 == 0:
|
||||
retstr = "No handler in type %i for BDS1 == 0 from %x" % (msgtype, ecc)
|
||||
retstr += "No handler in type %i for BDS1 == 0 from %x" % (msgtype, msg.ecc)
|
||||
elif bds1 == 1:
|
||||
retstr = "Type %i link capability report from %x: ACS: 0x%x, BCS: 0x%x, ECS: 0x%x, continues %i" \
|
||||
% (msgtype, ecc, data["acs"], data["bcs"], data["ecs"], data["cfs"])
|
||||
retstr += "Type %i link capability report from %x: ACS: 0x%x, BCS: 0x%x, ECS: 0x%x, continues %i" \
|
||||
% (msgtype, msg.ecc, msg.data["acs"], msg.data["bcs"], msg.data["ecs"], msg.data["cfs"])
|
||||
elif bds1 == 2:
|
||||
retstr = "Type %i identification from %x with text %s" % (msgtype, ecc, self.parseMB_id(data))
|
||||
retstr += "Type %i identification from %x with text %s" % (msgtype, msg.ecc, air_modes.parseMB_id(msg.data))
|
||||
elif bds1 == 3:
|
||||
retstr = "Type %i TCAS report from %x: " % (msgtype, ecc)
|
||||
tti = data["tti"]
|
||||
retstr += "Type %i TCAS report from %x: " % (msgtype, msg.ecc)
|
||||
tti = msg.data["tti"]
|
||||
if msgtype == 16:
|
||||
(resolutions, complements, rat, mte) = self.parse_TCAS_CRM(data)
|
||||
(resolutions, complements, rat, mte) = air_modes.parse_TCAS_CRM(msg.data)
|
||||
retstr += "advised: %s complement: %s" % (resolutions, complements)
|
||||
else:
|
||||
if tti == 1:
|
||||
(resolutions, complements, rat, mte, threat_id) = self.parseMB_TCAS_threatid(data)
|
||||
(resolutions, complements, rat, mte, threat_id) = air_modes.parseMB_TCAS_threatid(msg.data)
|
||||
retstr += "threat ID: %x advised: %s complement: %s" % (threat_id, resolutions, complements)
|
||||
elif tti == 2:
|
||||
(resolutions, complements, rat, mte, threat_alt, threat_range, threat_bearing) = self.parseMB_TCAS_threatloc(data)
|
||||
(resolutions, complements, rat, mte, threat_alt, threat_range, threat_bearing) = air_modes.parseMB_TCAS_threatloc(msg.data)
|
||||
retstr += "range: %i bearing: %i alt: %i advised: %s complement: %s" % (threat_range, threat_bearing, threat_alt, resolutions, complements)
|
||||
else:
|
||||
rat = 0
|
||||
@@ -233,11 +223,15 @@ class output_print(air_modes.parse):
|
||||
if rat == 1:
|
||||
retstr += " (resolved)"
|
||||
else:
|
||||
retstr = "No handler for BDS1 == %i from %x" % (bds1, ecc)
|
||||
retstr += "No handler for type %i, BDS1 == %i from %x" % (msgtype, bds1, msg.ecc)
|
||||
|
||||
if(msgtype == 20 or msgtype == 16):
|
||||
retstr += " at %ift" % alt
|
||||
retstr += " at %ift" % air_modes.decode_alt(msg.data["ac"], True)
|
||||
else:
|
||||
retstr += " ident %x" % ident
|
||||
|
||||
return retstr
|
||||
retstr += " ident %x" % air_modes.decode_id(msg.data["id"])
|
||||
|
||||
self._print(retstr)
|
||||
|
||||
handle16 = printTCAS
|
||||
handle20 = printTCAS
|
||||
handle21 = printTCAS
|
||||
|
||||
345
python/parse.py
345
python/parse.py
@@ -23,8 +23,8 @@ import time, os, sys
|
||||
from string import split, join
|
||||
from altitude import decode_alt
|
||||
import math
|
||||
import air_modes
|
||||
from air_modes.exceptions import *
|
||||
from air_modes import cpr
|
||||
|
||||
#this implements a packet class which can retrieve its own fields.
|
||||
class data_field:
|
||||
@@ -231,198 +231,207 @@ class modes_reply(data_field):
|
||||
def get_type(self):
|
||||
return self.get_bits(1,5)
|
||||
|
||||
class parse:
|
||||
def __init__(self, mypos):
|
||||
self.my_location = mypos
|
||||
self.cpr = cpr.cpr_decoder(self.my_location)
|
||||
|
||||
def parse0(self, data):
|
||||
altitude = decode_alt(data["ac"], True)
|
||||
return [data["vs"], data["cc"], data["sl"], data["ri"], altitude]
|
||||
#unscramble mode A/C-style squawk codes for type 5 replies below
|
||||
def decode_id(id):
|
||||
|
||||
C1 = 0x1000
|
||||
A1 = 0x0800
|
||||
C2 = 0x0400
|
||||
A2 = 0x0200 #this represents the order in which the bits come
|
||||
C4 = 0x0100
|
||||
A4 = 0x0080
|
||||
B1 = 0x0020
|
||||
D1 = 0x0010
|
||||
B2 = 0x0008
|
||||
D2 = 0x0004
|
||||
B4 = 0x0002
|
||||
D4 = 0x0001
|
||||
|
||||
a = ((id & A1) >> 11) + ((id & A2) >> 8) + ((id & A4) >> 5)
|
||||
b = ((id & B1) >> 5) + ((id & B2) >> 2) + ((id & B4) << 1)
|
||||
c = ((id & C1) >> 12) + ((id & C2) >> 9) + ((id & C4) >> 6)
|
||||
d = ((id & D1) >> 2) + ((id & D2) >> 1) + ((id & D4) << 2)
|
||||
|
||||
return (a * 1000) + (b * 100) + (c * 10) + d
|
||||
|
||||
def parse4(self, data):
|
||||
altitude = decode_alt(data["ac"], True)
|
||||
return [data["fs"], data["dr"], data["um"], altitude]
|
||||
#decode ident squawks
|
||||
def charmap(d):
|
||||
if d > 0 and d < 27:
|
||||
retval = chr(ord("A")+d-1)
|
||||
elif d == 32:
|
||||
retval = " "
|
||||
elif d > 47 and d < 58:
|
||||
retval = chr(ord("0")+d-48)
|
||||
else:
|
||||
retval = " "
|
||||
|
||||
def parse5(self, data):
|
||||
return [data["fs"], data["dr"], data["um"], data["id"]]
|
||||
|
||||
def parse11(self, data, ecc):
|
||||
interrogator = ecc & 0x0F
|
||||
return [data["aa"], interrogator, data["ca"]]
|
||||
return retval
|
||||
|
||||
def parseBDS08(data):
|
||||
categories = [["NO INFO", "RESERVED", "RESERVED", "RESERVED", "RESERVED", "RESERVED", "RESERVED", "RESERVED"],\
|
||||
["NO INFO", "SURFACE EMERGENCY VEHICLE", "SURFACE SERVICE VEHICLE", "FIXED OBSTRUCTION", "CLUSTER OBSTRUCTION", "LINE OBSTRUCTION", "RESERVED"],\
|
||||
["NO INFO", "GLIDER", "BALLOON/BLIMP", "PARACHUTE", "ULTRALIGHT", "RESERVED", "UAV", "SPACECRAFT"],\
|
||||
["NO INFO", "LIGHT", "SMALL", "LARGE", "LARGE HIGH VORTEX", "HEAVY", "HIGH PERFORMANCE", "ROTORCRAFT"]]
|
||||
|
||||
def parseBDS08(self, data):
|
||||
catstring = self.categories[data["ftc"]-1][data["cat"]]
|
||||
catstring = categories[data["ftc"]-1][data["cat"]]
|
||||
|
||||
msg = ""
|
||||
for i in range(0, 8):
|
||||
msg += self.charmap(data["ident"] >> (42-6*i) & 0x3F)
|
||||
return (msg, catstring)
|
||||
msg = ""
|
||||
for i in range(0, 8):
|
||||
msg += charmap(data["ident"] >> (42-6*i) & 0x3F)
|
||||
return (msg, catstring)
|
||||
|
||||
def charmap(self, d):
|
||||
if d > 0 and d < 27:
|
||||
retval = chr(ord("A")+d-1)
|
||||
elif d == 32:
|
||||
retval = " "
|
||||
elif d > 47 and d < 58:
|
||||
retval = chr(ord("0")+d-48)
|
||||
else:
|
||||
retval = " "
|
||||
#NOTE: this is stateful -- requires CPR decoder
|
||||
def parseBDS05(data, cprdec):
|
||||
altitude = decode_alt(data["alt"], False)
|
||||
[decoded_lat, decoded_lon, rnge, bearing] = cprdec.decode(data["aa"], data["lat"], data["lon"], data["cpr"], 0)
|
||||
return [altitude, decoded_lat, decoded_lon, rnge, bearing]
|
||||
|
||||
return retval
|
||||
#NOTE: this is stateful -- requires CPR decoder
|
||||
def parseBDS06(data, cprdec):
|
||||
ground_track = data["gtk"] * 360. / 128
|
||||
[decoded_lat, decoded_lon, rnge, bearing] = cprdec.decode(data["aa"], data["lat"], data["lon"], data["cpr"], 1)
|
||||
return [ground_track, decoded_lat, decoded_lon, rnge, bearing]
|
||||
|
||||
def parseBDS05(self, data):
|
||||
icao24 = data["aa"]
|
||||
|
||||
encoded_lon = data["lon"]
|
||||
encoded_lat = data["lat"]
|
||||
cpr_format = data["cpr"]
|
||||
altitude = decode_alt(data["alt"], False)
|
||||
|
||||
[decoded_lat, decoded_lon, rnge, bearing] = self.cpr.decode(icao24, encoded_lat, encoded_lon, cpr_format, 0)
|
||||
|
||||
return [altitude, decoded_lat, decoded_lon, rnge, bearing]
|
||||
|
||||
|
||||
#welp turns out it looks like there's only 17 bits in the BDS0,6 ground packet after all.
|
||||
def parseBDS06(self, data):
|
||||
icao24 = data["aa"]
|
||||
|
||||
encoded_lon = data["lon"]
|
||||
encoded_lat = data["lat"]
|
||||
cpr_format = data["cpr"]
|
||||
ground_track = data["gtk"] * 360. / 128
|
||||
[decoded_lat, decoded_lon, rnge, bearing] = self.cpr.decode(icao24, encoded_lat, encoded_lon, cpr_format, 1)
|
||||
return [ground_track, decoded_lat, decoded_lon, rnge, bearing]
|
||||
|
||||
def parseBDS09_0(self, data):
|
||||
#0: ["sub", "dew", "vew", "dns", "vns", "str", "tr", "svr", "vr"],
|
||||
vert_spd = data["vr"] * 32
|
||||
ud = bool(data["dvr"])
|
||||
if ud:
|
||||
vert_spd = 0 - vert_spd
|
||||
turn_rate = data["tr"] * 15/62
|
||||
rl = data["str"]
|
||||
if rl:
|
||||
turn_rate = 0 - turn_rate
|
||||
ns_vel = data["vns"] - 1
|
||||
ns = bool(data["dns"])
|
||||
ew_vel = data["vew"] - 1
|
||||
ew = bool(data["dew"])
|
||||
def parseBDS09_0(data):
|
||||
#0: ["sub", "dew", "vew", "dns", "vns", "str", "tr", "svr", "vr"],
|
||||
vert_spd = data["vr"] * 32
|
||||
ud = bool(data["dvr"])
|
||||
if ud:
|
||||
vert_spd = 0 - vert_spd
|
||||
turn_rate = data["tr"] * 15/62
|
||||
rl = data["str"]
|
||||
if rl:
|
||||
turn_rate = 0 - turn_rate
|
||||
ns_vel = data["vns"] - 1
|
||||
ns = bool(data["dns"])
|
||||
ew_vel = data["vew"] - 1
|
||||
ew = bool(data["dew"])
|
||||
|
||||
velocity = math.hypot(ns_vel, ew_vel)
|
||||
if ew:
|
||||
ew_vel = 0 - ew_vel
|
||||
if ns:
|
||||
ns_vel = 0 - ns_vel
|
||||
heading = math.atan2(ew_vel, ns_vel) * (180.0 / math.pi)
|
||||
if heading < 0:
|
||||
heading += 360
|
||||
velocity = math.hypot(ns_vel, ew_vel)
|
||||
if ew:
|
||||
ew_vel = 0 - ew_vel
|
||||
if ns:
|
||||
ns_vel = 0 - ns_vel
|
||||
heading = math.atan2(ew_vel, ns_vel) * (180.0 / math.pi)
|
||||
if heading < 0:
|
||||
heading += 360
|
||||
|
||||
return [velocity, heading, vert_spd, turn_rate]
|
||||
return [velocity, heading, vert_spd, turn_rate]
|
||||
|
||||
def parseBDS09_1(self, data):
|
||||
#1: ["sub", "icf", "ifr", "nuc", "dew", "vew", "dns", "vns", "vrsrc", "dvr", "vr", "dhd", "hd"],
|
||||
alt_geo_diff = data["hd"] * 25
|
||||
above_below = bool(data["dhd"])
|
||||
if above_below:
|
||||
alt_geo_diff = 0 - alt_geo_diff;
|
||||
vert_spd = float(data["vr"] - 1) * 64
|
||||
ud = bool(data["dvr"])
|
||||
if ud:
|
||||
vert_spd = 0 - vert_spd
|
||||
vert_src = bool(data["vrsrc"])
|
||||
ns_vel = float(data["vns"])
|
||||
ns = bool(data["dns"])
|
||||
ew_vel = float(data["vew"])
|
||||
ew = bool(data["dew"])
|
||||
subtype = data["sub"]
|
||||
if subtype == 0x02:
|
||||
ns_vel <<= 2
|
||||
ew_vel <<= 2
|
||||
def parseBDS09_1(data):
|
||||
#1: ["sub", "icf", "ifr", "nuc", "dew", "vew", "dns", "vns", "vrsrc", "dvr", "vr", "dhd", "hd"],
|
||||
alt_geo_diff = data["hd"] * 25
|
||||
above_below = bool(data["dhd"])
|
||||
if above_below:
|
||||
alt_geo_diff = 0 - alt_geo_diff;
|
||||
vert_spd = float(data["vr"] - 1) * 64
|
||||
ud = bool(data["dvr"])
|
||||
if ud:
|
||||
vert_spd = 0 - vert_spd
|
||||
vert_src = bool(data["vrsrc"])
|
||||
ns_vel = float(data["vns"])
|
||||
ns = bool(data["dns"])
|
||||
ew_vel = float(data["vew"])
|
||||
ew = bool(data["dew"])
|
||||
subtype = data["sub"]
|
||||
if subtype == 0x02:
|
||||
ns_vel <<= 2
|
||||
ew_vel <<= 2
|
||||
|
||||
velocity = math.hypot(ns_vel, ew_vel)
|
||||
if ew:
|
||||
ew_vel = 0 - ew_vel
|
||||
velocity = math.hypot(ns_vel, ew_vel)
|
||||
if ew:
|
||||
ew_vel = 0 - ew_vel
|
||||
|
||||
if ns_vel == 0:
|
||||
heading = 0
|
||||
else:
|
||||
heading = math.atan(float(ew_vel) / float(ns_vel)) * (180.0 / math.pi)
|
||||
if ns:
|
||||
heading = 180 - heading
|
||||
if heading < 0:
|
||||
heading += 360
|
||||
if ns_vel == 0:
|
||||
heading = 0
|
||||
else:
|
||||
heading = math.atan(float(ew_vel) / float(ns_vel)) * (180.0 / math.pi)
|
||||
if ns:
|
||||
heading = 180 - heading
|
||||
if heading < 0:
|
||||
heading += 360
|
||||
|
||||
return [velocity, heading, vert_spd]
|
||||
return [velocity, heading, vert_spd]
|
||||
|
||||
def parseBDS09_3(self, data):
|
||||
def parseBDS09_3(data):
|
||||
#3: {"sub", "icf", "ifr", "nuc", "mhs", "hdg", "ast", "spd", "vrsrc",
|
||||
# "dvr", "vr", "dhd", "hd"}
|
||||
mag_hdg = data["mhs"] * 360. / 1024
|
||||
vel_src = "TAS" if data["ast"] == 1 else "IAS"
|
||||
vel = data["spd"]
|
||||
if data["sub"] == 4:
|
||||
vel *= 4
|
||||
vert_spd = float(data["vr"] - 1) * 64
|
||||
if data["dvr"] == 1:
|
||||
vert_spd = 0 - vert_spd
|
||||
geo_diff = float(data["hd"] - 1) * 25
|
||||
return [mag_hdg, vel_src, vel, vert_spd, geo_diff]
|
||||
mag_hdg = data["mhs"] * 360. / 1024
|
||||
vel_src = "TAS" if data["ast"] == 1 else "IAS"
|
||||
vel = data["spd"]
|
||||
if data["sub"] == 4:
|
||||
vel *= 4
|
||||
vert_spd = float(data["vr"] - 1) * 64
|
||||
if data["dvr"] == 1:
|
||||
vert_spd = 0 - vert_spd
|
||||
geo_diff = float(data["hd"] - 1) * 25
|
||||
return [mag_hdg, vel_src, vel, vert_spd, geo_diff]
|
||||
|
||||
|
||||
def parseBDS62(self, data):
|
||||
eps_strings = ["NO EMERGENCY", "GENERAL EMERGENCY", "LIFEGUARD/MEDICAL", "FUEL EMERGENCY",
|
||||
"NO COMMUNICATIONS", "UNLAWFUL INTERFERENCE", "RESERVED", "RESERVED"]
|
||||
return eps_strings[data["eps"]]
|
||||
def parseBDS62(data):
|
||||
eps_strings = ["NO EMERGENCY", "GENERAL EMERGENCY", "LIFEGUARD/MEDICAL", "FUEL EMERGENCY",
|
||||
"NO COMMUNICATIONS", "UNLAWFUL INTERFERENCE", "RESERVED", "RESERVED"]
|
||||
return eps_strings[data["eps"]]
|
||||
|
||||
def parseMB_id(self, data): #bds1 == 2, bds2 == 0
|
||||
msg = ""
|
||||
for i in range(0, 8):
|
||||
msg += self.charmap( data["ais"] >> (42-6*i) & 0x3F)
|
||||
return (msg)
|
||||
def parseMB_id(data): #bds1 == 2, bds2 == 0
|
||||
msg = ""
|
||||
for i in range(0, 8):
|
||||
msg += charmap( data["ais"] >> (42-6*i) & 0x3F)
|
||||
return (msg)
|
||||
|
||||
def parseMB_TCAS_resolutions(self, data):
|
||||
#these are LSB because the ICAO are asshats
|
||||
ara_bits = {41: "CLIMB", 42: "DON'T DESCEND", 43: "DON'T DESCEND >500FPM", 44: "DON'T DESCEND >1000FPM",
|
||||
45: "DON'T DESCEND >2000FPM", 46: "DESCEND", 47: "DON'T CLIMB", 48: "DON'T CLIMB >500FPM",
|
||||
49: "DON'T CLIMB >1000FPM", 50: "DON'T CLIMB >2000FPM", 51: "TURN LEFT", 52: "TURN RIGHT",
|
||||
53: "DON'T TURN LEFT", 54: "DON'T TURN RIGHT"}
|
||||
rac_bits = {55: "DON'T DESCEND", 56: "DON'T CLIMB", 57: "DON'T TURN LEFT", 58: "DON'T TURN RIGHT"}
|
||||
ara = data["ara"]
|
||||
rac = data["rac"]
|
||||
#check to see which bits are set
|
||||
resolutions = ""
|
||||
for bit in ara_bits:
|
||||
if ara & (1 << (54-bit)):
|
||||
resolutions += " " + ara_bits[bit]
|
||||
complements = ""
|
||||
for bit in rac_bits:
|
||||
if rac & (1 << (58-bit)):
|
||||
complements += " " + rac_bits[bit]
|
||||
return (resolutions, complements)
|
||||
def parseMB_TCAS_resolutions(data):
|
||||
#these are LSB because the ICAO are asshats
|
||||
ara_bits = {41: "CLIMB", 42: "DON'T DESCEND", 43: "DON'T DESCEND >500FPM", 44: "DON'T DESCEND >1000FPM",
|
||||
45: "DON'T DESCEND >2000FPM", 46: "DESCEND", 47: "DON'T CLIMB", 48: "DON'T CLIMB >500FPM",
|
||||
49: "DON'T CLIMB >1000FPM", 50: "DON'T CLIMB >2000FPM", 51: "TURN LEFT", 52: "TURN RIGHT",
|
||||
53: "DON'T TURN LEFT", 54: "DON'T TURN RIGHT"}
|
||||
rac_bits = {55: "DON'T DESCEND", 56: "DON'T CLIMB", 57: "DON'T TURN LEFT", 58: "DON'T TURN RIGHT"}
|
||||
ara = data["ara"]
|
||||
rac = data["rac"]
|
||||
#check to see which bits are set
|
||||
resolutions = ""
|
||||
for bit in ara_bits:
|
||||
if ara & (1 << (54-bit)):
|
||||
resolutions += " " + ara_bits[bit]
|
||||
complements = ""
|
||||
for bit in rac_bits:
|
||||
if rac & (1 << (58-bit)):
|
||||
complements += " " + rac_bits[bit]
|
||||
return (resolutions, complements)
|
||||
|
||||
#rat is 1 if resolution advisory terminated <18s ago
|
||||
#mte is 1 if multiple threats indicated
|
||||
#tti is threat type: 1 if ID, 2 if range/brg/alt
|
||||
#tida is threat altitude in Mode C format
|
||||
def parseMB_TCAS_threatid(self, data): #bds1==3, bds2==0, TTI==1
|
||||
#3: {"bds1": (33,4), "bds2": (37,4), "ara": (41,14), "rac": (55,4), "rat": (59,1),
|
||||
# "mte": (60,1), "tti": (61,2), "tida": (63,13), "tidr": (76,7), "tidb": (83,6)}
|
||||
(resolutions, complements) = self.parseMB_TCAS_resolutions(data)
|
||||
return (resolutions, complements, data["rat"], data["mte"], data["tid"])
|
||||
#rat is 1 if resolution advisory terminated <18s ago
|
||||
#mte is 1 if multiple threats indicated
|
||||
#tti is threat type: 1 if ID, 2 if range/brg/alt
|
||||
#tida is threat altitude in Mode C format
|
||||
def parseMB_TCAS_threatid(data): #bds1==3, bds2==0, TTI==1
|
||||
#3: {"bds1": (33,4), "bds2": (37,4), "ara": (41,14), "rac": (55,4), "rat": (59,1),
|
||||
# "mte": (60,1), "tti": (61,2), "tida": (63,13), "tidr": (76,7), "tidb": (83,6)}
|
||||
(resolutions, complements) = parseMB_TCAS_resolutions(data)
|
||||
return (resolutions, complements, data["rat"], data["mte"], data["tid"])
|
||||
|
||||
def parseMB_TCAS_threatloc(self, data): #bds1==3, bds2==0, TTI==2
|
||||
(resolutions, complements) = self.parseMB_TCAS_resolutions(data)
|
||||
threat_alt = decode_alt(data["tida"], True)
|
||||
return (resolutions, complements, data["rat"], data["mte"], threat_alt, data["tidr"], data["tidb"])
|
||||
def parseMB_TCAS_threatloc(data): #bds1==3, bds2==0, TTI==2
|
||||
(resolutions, complements) = parseMB_TCAS_resolutions(data)
|
||||
threat_alt = decode_alt(data["tida"], True)
|
||||
return (resolutions, complements, data["rat"], data["mte"], threat_alt, data["tidr"], data["tidb"])
|
||||
|
||||
#type 16 Coordination Reply Message
|
||||
def parse_TCAS_CRM(self, data):
|
||||
(resolutions, complements) = self.parseMB_TCAS_resolutions(data)
|
||||
return (resolutions, complements, data["rat"], data["mte"])
|
||||
#type 16 Coordination Reply Message
|
||||
def parse_TCAS_CRM(data):
|
||||
(resolutions, complements) = parseMB_TCAS_resolutions(data)
|
||||
return (resolutions, complements, data["rat"], data["mte"])
|
||||
|
||||
#this decorator takes a pubsub and returns a function which parses and publishes messages
|
||||
def make_parser(pub):
|
||||
publisher = pub
|
||||
def publish(message):
|
||||
[data, ecc, reference, timestamp] = message.split()
|
||||
try:
|
||||
ret = air_modes.modes_report(modes_reply(int(data, 16)),
|
||||
int(ecc, 16),
|
||||
20.0*math.log10(float(reference)),
|
||||
air_modes.stamp(0, float(timestamp)))
|
||||
pub["modes_dl"] = ret
|
||||
pub["type%i_dl" % ret.data.get_type()] = ret
|
||||
except ADSBError:
|
||||
pass
|
||||
|
||||
return publish
|
||||
|
||||
208
python/radio.py
Normal file
208
python/radio.py
Normal 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()
|
||||
@@ -1,5 +1,5 @@
|
||||
#
|
||||
# Copyright 2012 Corgan Labs
|
||||
# Copyright 2012, 2013 Corgan Labs, Nick Foster
|
||||
#
|
||||
# This file is part of gr-air-modes
|
||||
#
|
||||
@@ -19,7 +19,7 @@
|
||||
# Boston, MA 02110-1301, USA.
|
||||
#
|
||||
|
||||
from gnuradio import gr
|
||||
from gnuradio import gr, blocks
|
||||
import air_modes_swig
|
||||
|
||||
class rx_path(gr.hier_block2):
|
||||
@@ -35,17 +35,17 @@ class rx_path(gr.hier_block2):
|
||||
self._spc = int(rate/2e6)
|
||||
|
||||
# Convert incoming I/Q baseband to amplitude
|
||||
self._demod = gr.complex_to_mag()
|
||||
self._demod = blocks.complex_to_mag()
|
||||
self._bb = self._demod
|
||||
|
||||
# Pulse matched filter for 0.5us pulses
|
||||
if use_pmf:
|
||||
self._pmf = gr.moving_average_ff(self._spc, 1.0/self._spc, self._rate)
|
||||
self._pmf = blocks.moving_average_ff(self._spc, 1.0/self._spc)#, self._rate)
|
||||
self.connect(self._demod, self._pmf)
|
||||
self._bb = self._pmf
|
||||
|
||||
# Establish baseline amplitude (noise, interference)
|
||||
self._avg = gr.moving_average_ff(48*self._spc, 1.0/(48*self._spc), self._rate) # 3 preambles
|
||||
self._avg = blocks.moving_average_ff(48*self._spc, 1.0/(48*self._spc))#, self._rate) # 3 preambles
|
||||
|
||||
# Synchronize to Mode-S preamble
|
||||
self._sync = air_modes_swig.modes_preamble(self._rate, self._threshold)
|
||||
@@ -58,3 +58,25 @@ class rx_path(gr.hier_block2):
|
||||
self.connect(self._bb, (self._sync, 0))
|
||||
self.connect(self._bb, self._avg, (self._sync, 1))
|
||||
self.connect(self._sync, self._slicer)
|
||||
|
||||
def set_rate(self, rate):
|
||||
self._sync.set_rate(rate)
|
||||
self._slicer.set_rate(rate)
|
||||
self._spc = int(rate/2e6)
|
||||
self._avg.set_length_and_scale(48*self._spc, 1.0/(48*self._spc))
|
||||
if self._bb != self._demod:
|
||||
self._pmf.set_length_and_scale(self._spc, 1.0/self._spc)
|
||||
|
||||
def set_threshold(self, threshold):
|
||||
self._sync.set_threshold(threshold)
|
||||
|
||||
def set_pmf(self, pmf):
|
||||
#TODO must be done when top block is stopped
|
||||
pass
|
||||
|
||||
def get_pmf(self, pmf):
|
||||
return not (self._bb == self._demod)
|
||||
|
||||
def get_threshold(self, threshold):
|
||||
return self._sync.get_threshold()
|
||||
|
||||
|
||||
@@ -25,10 +25,30 @@ from string import split, join
|
||||
import air_modes
|
||||
from datetime import *
|
||||
from air_modes.exceptions import *
|
||||
import threading
|
||||
|
||||
class output_sbs1(air_modes.parse):
|
||||
def __init__(self, mypos, port):
|
||||
air_modes.parse.__init__(self, mypos)
|
||||
class dumb_task_runner(threading.Thread):
|
||||
def __init__(self, task, interval):
|
||||
threading.Thread.__init__(self)
|
||||
self._task = task
|
||||
self._interval = interval
|
||||
self.shutdown = threading.Event()
|
||||
self.finished = threading.Event()
|
||||
self.setDaemon(True)
|
||||
self.start()
|
||||
|
||||
def run(self):
|
||||
while not self.shutdown.is_set():
|
||||
self._task()
|
||||
time.sleep(self._interval)
|
||||
self.finished.set()
|
||||
|
||||
def close(self):
|
||||
self.shutdown.set()
|
||||
self.finished.wait(self._interval)
|
||||
|
||||
class output_sbs1:
|
||||
def __init__(self, cprdec, port, pub):
|
||||
self._s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
self._s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||
self._s.bind(('', port))
|
||||
@@ -38,6 +58,16 @@ class output_sbs1(air_modes.parse):
|
||||
self._aircraft_id_map = {} # dictionary of icao24 to aircraft IDs
|
||||
self._aircraft_id_count = 0 # Current Aircraft ID count
|
||||
|
||||
self._cpr = cprdec
|
||||
|
||||
#it could be cleaner if there were separate output_* fns
|
||||
#but this works
|
||||
for i in (0, 4, 5, 11, 17):
|
||||
pub.subscribe("type%i_dl" % i, output)
|
||||
|
||||
#spawn thread to add new connections as they come in
|
||||
self._runner = dumb_task_runner(self.add_pending_conns, 0.1)
|
||||
|
||||
def __del__(self):
|
||||
self._s.close()
|
||||
|
||||
@@ -94,41 +124,36 @@ class output_sbs1(air_modes.parse):
|
||||
elif fs == 3:
|
||||
return "1,0,0,1"
|
||||
elif fs == 4:
|
||||
return "1,0,0,"
|
||||
return "1,0,1,"
|
||||
elif fs == 5:
|
||||
return "0,0,0,"
|
||||
return "0,0,1,"
|
||||
else:
|
||||
return ",,,"
|
||||
|
||||
def parse(self, message):
|
||||
def parse(self, msg):
|
||||
#assembles a SBS-1-style output string from the received message
|
||||
|
||||
[data, ecc, reference, timestamp] = message.split()
|
||||
|
||||
data = air_modes.modes_reply(long(data, 16))
|
||||
ecc = long(ecc, 16)
|
||||
msgtype = data["df"]
|
||||
msgtype = msg.data["df"]
|
||||
outmsg = None
|
||||
|
||||
if msgtype == 0:
|
||||
outmsg = self.pp0(data, ecc)
|
||||
outmsg = self.pp0(msg.data, msg.ecc)
|
||||
elif msgtype == 4:
|
||||
outmsg = self.pp4(data, ecc)
|
||||
outmsg = self.pp4(msg.data, msg.ecc)
|
||||
elif msgtype == 5:
|
||||
outmsg = self.pp5(data, ecc)
|
||||
outmsg = self.pp5(msg.data, msg.ecc)
|
||||
elif msgtype == 11:
|
||||
outmsg = self.pp11(data, ecc)
|
||||
outmsg = self.pp11(msg.data, msg.ecc)
|
||||
elif msgtype == 17:
|
||||
outmsg = self.pp17(data)
|
||||
outmsg = self.pp17(msg.data)
|
||||
else:
|
||||
raise NoHandlerError(msgtype)
|
||||
return outmsg
|
||||
|
||||
def pp0(self, shortdata, ecc):
|
||||
[datestr, timestr] = self.current_time()
|
||||
[vs, cc, sl, ri, altitude] = self.parse0(shortdata)
|
||||
aircraft_id = self.get_aircraft_id(ecc)
|
||||
retstr = "MSG,7,0,%i,%06X,%i,%s,%s,%s,%s,,%s,,,,,,,,,," % (aircraft_id, ecc, aircraft_id+100, datestr, timestr, datestr, timestr, altitude)
|
||||
retstr = "MSG,7,0,%i,%06X,%i,%s,%s,%s,%s,,%s,,,,,,,,,," % (aircraft_id, ecc, aircraft_id+100, datestr, timestr, datestr, timestr, air_modes.decode_alt(shortdata["ac"], True))
|
||||
if vs:
|
||||
retstr += "1\r\n"
|
||||
else:
|
||||
@@ -137,24 +162,20 @@ class output_sbs1(air_modes.parse):
|
||||
|
||||
def pp4(self, shortdata, ecc):
|
||||
[datestr, timestr] = self.current_time()
|
||||
[fs, dr, um, altitude] = self.parse4(shortdata)
|
||||
aircraft_id = self.get_aircraft_id(ecc)
|
||||
retstr = "MSG,5,0,%i,%06X,%i,%s,%s,%s,%s,,%s,,,,,,," % (aircraft_id, ecc, aircraft_id+100, datestr, timestr, datestr, timestr, altitude)
|
||||
return retstr + self.decode_fs(fs) + "\r\n"
|
||||
retstr = "MSG,5,0,%i,%06X,%i,%s,%s,%s,%s,,%s,,,,,,," % (aircraft_id, ecc, aircraft_id+100, datestr, timestr, datestr, timestr, air_modes.decode_alt(shortdata["ac"], True))
|
||||
return retstr + self.decode_fs(shortdata["fs"]) + "\r\n"
|
||||
|
||||
def pp5(self, shortdata, ecc):
|
||||
# I'm not sure what to do with the identiifcation shortdata & 0x1FFF
|
||||
[datestr, timestr] = self.current_time()
|
||||
[fs, dr, um, ident] = self.parse5(shortdata)
|
||||
aircraft_id = self.get_aircraft_id(ecc)
|
||||
retstr = "MSG,6,0,%i,%06X,%i,%s,%s,%s,%s,,,,,,,,," % (aircraft_id, ecc, aircraft_id+100, datestr, timestr, datestr, timestr)
|
||||
return retstr + self.decode_fs(fs) + "\r\n"
|
||||
retstr = "MSG,6,0,%i,%06X,%i,%s,%s,%s,%s,,,,,,,,%04i," % (aircraft_id, ecc, aircraft_id+100, datestr, timestr, datestr, timestr, air_modes.decode_id(shortdata["id"]))
|
||||
return retstr + self.decode_fs(shortdata["fs"]) + "\r\n"
|
||||
|
||||
def pp11(self, shortdata, ecc):
|
||||
[datestr, timestr] = self.current_time()
|
||||
[icao24, interrogator, ca] = self.parse11(shortdata, ecc)
|
||||
aircraft_id = self.get_aircraft_id(icao24)
|
||||
return "MSG,8,0,%i,%06X,%i,%s,%s,%s,%s,,,,,,,,,,,,\r\n" % (aircraft_id, icao24, aircraft_id+100, datestr, timestr, datestr, timestr)
|
||||
return "MSG,8,0,%i,%06X,%i,%s,%s,%s,%s,,,,,,,,,,,,\r\n" % (aircraft_id, shortdata["aa"], aircraft_id+100, datestr, timestr, datestr, timestr)
|
||||
|
||||
def pp17(self, data):
|
||||
icao24 = data["aa"]
|
||||
@@ -168,12 +189,12 @@ class output_sbs1(air_modes.parse):
|
||||
|
||||
if bdsreg == 0x08:
|
||||
# Aircraft Identification
|
||||
(msg, typestring) = self.parseBDS08(data)
|
||||
(msg, typestring) = air_modes.parseBDS08(data)
|
||||
retstr = "MSG,1,0,%i,%06X,%i,%s,%s,%s,%s,%s,,,,,,,,,,,\r\n" % (aircraft_id, icao24, aircraft_id+100, datestr, timestr, datestr, timestr, msg)
|
||||
|
||||
elif bdsreg == 0x06:
|
||||
# Surface position measurement
|
||||
[ground_track, decoded_lat, decoded_lon, rnge, bearing] = self.parseBDS06(data)
|
||||
[ground_track, decoded_lat, decoded_lon, rnge, bearing] = air_modes.parseBDS06(data, self._cpr)
|
||||
altitude = 0
|
||||
if decoded_lat is None: #no unambiguously valid position available
|
||||
retstr = None
|
||||
@@ -183,7 +204,7 @@ class output_sbs1(air_modes.parse):
|
||||
elif bdsreg == 0x05:
|
||||
# Airborne position measurements
|
||||
# WRONG (rnge, bearing), is this still true?
|
||||
[altitude, decoded_lat, decoded_lon, rnge, bearing] = self.parseBDS05(data)
|
||||
[altitude, decoded_lat, decoded_lon, rnge, bearing] = air_modes.parseBDS05(data, self._cpr)
|
||||
if decoded_lat is None: #no unambiguously valid position available
|
||||
retstr = None
|
||||
else:
|
||||
@@ -194,7 +215,7 @@ class output_sbs1(air_modes.parse):
|
||||
# WRONG (heading, vert_spd), Is this still true?
|
||||
subtype = data["bds09"].get_type()
|
||||
if subtype == 0 or subtype == 1:
|
||||
parser = self.parseBDS09_0 if subtype == 0 else self.parseBDS09_1
|
||||
parser = air_modes.parseBDS09_0 if subtype == 0 else air_modes.parseBDS09_1
|
||||
[velocity, heading, vert_spd] = parser(data)
|
||||
retstr = "MSG,4,0,%i,%06X,%i,%s,%s,%s,%s,,,%.1f,%.1f,,,%i,,,,,\r\n" % (aircraft_id, icao24, aircraft_id+100, datestr, timestr, datestr, timestr, velocity, heading, vert_spd)
|
||||
|
||||
|
||||
137
python/sql.py
137
python/sql.py
@@ -24,119 +24,106 @@ from string import split, join
|
||||
import air_modes
|
||||
import sqlite3
|
||||
from air_modes.exceptions import *
|
||||
from gnuradio.gr.pubsub import pubsub
|
||||
|
||||
class output_sql(air_modes.parse):
|
||||
def __init__(self, mypos, filename, lock):
|
||||
air_modes.parse.__init__(self, mypos)
|
||||
class output_sql:
|
||||
def __init__(self, cpr, filename, lock, publisher):
|
||||
#pubsub.__init__(self)
|
||||
self._cpr = cpr
|
||||
self._lock = lock;
|
||||
#create the database
|
||||
self.filename = filename
|
||||
self._db = sqlite3.connect(filename)
|
||||
#now execute a schema to create the tables you need
|
||||
c = self._db.cursor()
|
||||
query = """CREATE TABLE IF NOT EXISTS "positions" (
|
||||
"icao" INTEGER KEY NOT NULL,
|
||||
"seen" DATETIME NOT NULL,
|
||||
"alt" INTEGER,
|
||||
"lat" REAL,
|
||||
"lon" REAL
|
||||
);"""
|
||||
c.execute(query)
|
||||
query = """CREATE TABLE IF NOT EXISTS "vectors" (
|
||||
"icao" INTEGER KEY NOT NULL,
|
||||
"seen" DATETIME NOT NULL,
|
||||
"speed" REAL,
|
||||
"heading" REAL,
|
||||
"vertical" REAL
|
||||
);"""
|
||||
c.execute(query)
|
||||
query = """CREATE TABLE IF NOT EXISTS "ident" (
|
||||
"icao" INTEGER PRIMARY KEY NOT NULL,
|
||||
"ident" TEXT NOT NULL,
|
||||
"type" TEXT NOT NULL
|
||||
);"""
|
||||
c.execute(query)
|
||||
c.close()
|
||||
self._db.commit()
|
||||
#we close the db conn now to reopen it in the output() thread context.
|
||||
self._db.close()
|
||||
self._db = None
|
||||
publisher.subscribe("type17_dl", self.insert)
|
||||
|
||||
self._lock = lock
|
||||
with self._lock:
|
||||
#create the database
|
||||
self.filename = filename
|
||||
self.db = sqlite3.connect(filename)
|
||||
#now execute a schema to create the tables you need
|
||||
c = self.db.cursor()
|
||||
query = """CREATE TABLE IF NOT EXISTS "positions" (
|
||||
"icao" INTEGER KEY NOT NULL,
|
||||
"seen" TEXT NOT NULL,
|
||||
"alt" INTEGER,
|
||||
"lat" REAL,
|
||||
"lon" REAL
|
||||
);"""
|
||||
c.execute(query)
|
||||
query = """CREATE TABLE IF NOT EXISTS "vectors" (
|
||||
"icao" INTEGER KEY NOT NULL,
|
||||
"seen" TEXT NOT NULL,
|
||||
"speed" REAL,
|
||||
"heading" REAL,
|
||||
"vertical" REAL
|
||||
);"""
|
||||
c.execute(query)
|
||||
query = """CREATE TABLE IF NOT EXISTS "ident" (
|
||||
"icao" INTEGER PRIMARY KEY NOT NULL,
|
||||
"ident" TEXT NOT NULL
|
||||
);"""
|
||||
c.execute(query)
|
||||
c.close()
|
||||
self.db.commit()
|
||||
#we close the db conn now to reopen it in the output() thread context.
|
||||
self.db.close()
|
||||
self.db = None
|
||||
|
||||
def __del__(self):
|
||||
self.db = None
|
||||
|
||||
def output(self, message):
|
||||
def insert(self, message):
|
||||
with self._lock:
|
||||
try:
|
||||
#we're checking to see if the db is empty, and creating the db object
|
||||
#if it is. the reason for this is so that the db writing is done within
|
||||
#the thread context of output(), rather than the thread context of the
|
||||
#constructor. that way you can spawn a thread to do output().
|
||||
if self.db is None:
|
||||
self.db = sqlite3.connect(self.filename)
|
||||
|
||||
#constructor.
|
||||
if self._db is None:
|
||||
self._db = sqlite3.connect(self.filename)
|
||||
|
||||
query = self.make_insert_query(message)
|
||||
if query is not None:
|
||||
c = self.db.cursor()
|
||||
c = self._db.cursor()
|
||||
c.execute(query)
|
||||
c.close()
|
||||
self.db.commit()
|
||||
self._db.commit()
|
||||
|
||||
except ADSBError:
|
||||
pass
|
||||
|
||||
def make_insert_query(self, message):
|
||||
def make_insert_query(self, msg):
|
||||
#assembles a SQL query tailored to our database
|
||||
#this version ignores anything that isn't Type 17 for now, because we just don't care
|
||||
[data, ecc, reference, timestamp] = message.split()
|
||||
|
||||
data = air_modes.modes_reply(long(data, 16))
|
||||
ecc = long(ecc, 16)
|
||||
# reference = float(reference)
|
||||
|
||||
|
||||
query = None
|
||||
msgtype = data["df"]
|
||||
msgtype = msg.data["df"]
|
||||
if msgtype == 17:
|
||||
query = self.sql17(data)
|
||||
query = self.sql17(msg.data)
|
||||
#self["new_adsb"] = data["aa"] #publish change notification
|
||||
|
||||
return query
|
||||
|
||||
def sql17(self, data):
|
||||
icao24 = data["aa"]
|
||||
bdsreg = data["me"].get_type()
|
||||
|
||||
retstr = None
|
||||
#self["bds%.2i" % bdsreg] = icao24 #publish under "bds08", "bds06", etc.
|
||||
|
||||
if bdsreg == 0x08:
|
||||
(msg, typename) = self.parseBDS08(data)
|
||||
retstr = "INSERT OR REPLACE INTO ident (icao, ident) VALUES (" + "%i" % icao24 + ", '" + msg + "')"
|
||||
|
||||
return "INSERT OR REPLACE INTO ident (icao, ident, type) VALUES (%i, '%s', '%s')" % (icao24, msg, typename)
|
||||
elif bdsreg == 0x06:
|
||||
[ground_track, decoded_lat, decoded_lon, rnge, bearing] = self.parseBDS06(data)
|
||||
[ground_track, decoded_lat, decoded_lon, rnge, bearing] = air_modes.parseBDS06(data, self._cpr)
|
||||
altitude = 0
|
||||
if decoded_lat is None: #no unambiguously valid position available
|
||||
retstr = None
|
||||
raise CPRNoPositionError
|
||||
else:
|
||||
retstr = "INSERT INTO positions (icao, seen, alt, lat, lon) VALUES (" + "%i" % icao24 + ", datetime('now'), " + str(altitude) + ", " + "%.6f" % decoded_lat + ", " + "%.6f" % decoded_lon + ")"
|
||||
|
||||
return "INSERT INTO positions (icao, seen, alt, lat, lon) VALUES (%i, datetime('now'), %i, %.6f, %.6f)" % (icao24, int(altitude), decoded_lat, decoded_lon)
|
||||
elif bdsreg == 0x05:
|
||||
[altitude, decoded_lat, decoded_lon, rnge, bearing] = self.parseBDS05(data)
|
||||
[altitude, decoded_lat, decoded_lon, rnge, bearing] = air_modes.parseBDS05(data, self._cpr)
|
||||
if decoded_lat is None: #no unambiguously valid position available
|
||||
retstr = None
|
||||
raise CPRNoPositionError
|
||||
else:
|
||||
retstr = "INSERT INTO positions (icao, seen, alt, lat, lon) VALUES (" + "%i" % icao24 + ", datetime('now'), " + str(altitude) + ", " + "%.6f" % decoded_lat + ", " + "%.6f" % decoded_lon + ")"
|
||||
|
||||
return "INSERT INTO positions (icao, seen, alt, lat, lon) VALUES (%i, datetime('now'), %i, %.6f, %.6f)" % (icao24, int(altitude), decoded_lat, decoded_lon)
|
||||
elif bdsreg == 0x09:
|
||||
subtype = data["bds09"].get_type()
|
||||
if subtype == 0:
|
||||
[velocity, heading, vert_spd, turnrate] = self.parseBDS09_0(data)
|
||||
retstr = "INSERT INTO vectors (icao, seen, speed, heading, vertical) VALUES (" + "%i" % icao24 + ", datetime('now'), " + "%.0f" % velocity + ", " + "%.0f" % heading + ", " + "%.0f" % vert_spd + ")"
|
||||
return "INSERT INTO vectors (icao, seen, speed, heading, vertical) VALUES (%i, datetime('now'), %.0f, %.0f, %.0f)" % (icao24, velocity, heading, vert_spd)
|
||||
elif subtype == 1:
|
||||
[velocity, heading, vert_spd] = self.parseBDS09_1(data)
|
||||
retstr = "INSERT INTO vectors (icao, seen, speed, heading, vertical) VALUES (" + "%i" % icao24 + ", datetime('now'), " + "%.0f" % velocity + ", " + "%.0f" % heading + ", " + "%.0f" % vert_spd + ")"
|
||||
[velocity, heading, vert_spd] = self.parseBDS09_1(data)
|
||||
return "INSERT INTO vectors (icao, seen, speed, heading, vertical) VALUES (%i, datetime('now'), %.0f, %.0f, %.0f)" % (icao24, velocity, heading, vert_spd)
|
||||
else:
|
||||
retstr = None
|
||||
|
||||
return retstr
|
||||
raise NoHandlerError
|
||||
|
||||
107
python/types.py
Normal file
107
python/types.py
Normal file
@@ -0,0 +1,107 @@
|
||||
#
|
||||
# Copyright 2013 Nick Foster
|
||||
#
|
||||
# This file is part of gr-air-modes
|
||||
#
|
||||
# gr-air-modes is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; either version 3, or (at your option)
|
||||
# any later version.
|
||||
#
|
||||
# gr-air-modes is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with gr-air-modes; see the file COPYING. If not, write to
|
||||
# the Free Software Foundation, Inc., 51 Franklin Street,
|
||||
# Boston, MA 02110-1301, USA.
|
||||
#
|
||||
|
||||
from collections import namedtuple
|
||||
|
||||
#this is a timestamp that preserves precision when used with UTC timestamps.
|
||||
#ordinary double-precision timestamps lose significant fractional precision
|
||||
#when the exponent is as large as necessary for UTC.
|
||||
class stamp:
|
||||
def __init__(self, secs, frac_secs):
|
||||
self.secs = secs
|
||||
self.frac_secs = frac_secs
|
||||
self.secs += int(self.frac_secs)
|
||||
self.frac_secs -= int(self.frac_secs)
|
||||
def __lt__(self, other):
|
||||
if isinstance(other, self.__class__):
|
||||
if self.secs == other.secs:
|
||||
return self.frac_secs < other.frac_secs
|
||||
else:
|
||||
return self.secs < other.secs
|
||||
elif isinstance(other, float):
|
||||
return float(self) > other
|
||||
else:
|
||||
raise TypeError
|
||||
def __gt__(self, other):
|
||||
if type(other) is type(self):
|
||||
if self.secs == other.secs:
|
||||
return self.frac_secs > other.frac_secs
|
||||
else:
|
||||
return self.secs > other.secs
|
||||
elif type(other) is type(float):
|
||||
return float(self) > other
|
||||
else:
|
||||
raise TypeError
|
||||
def __eq__(self, other):
|
||||
if isinstance(other, self.__class__):
|
||||
return self.secs == other.secs and self.frac_secs == other.frac_secs
|
||||
elif isinstance(other, float):
|
||||
return float(self) == other
|
||||
else:
|
||||
raise TypeError
|
||||
def __ne__(self, other):
|
||||
return not (self == other)
|
||||
def __le__(self, other):
|
||||
return (self == other) or (self < other)
|
||||
def __ge__(self, other):
|
||||
return (self == other) or (self > other)
|
||||
|
||||
def __add__(self, other):
|
||||
if isinstance(other, self.__class__):
|
||||
ipart = self.secs + other.secs
|
||||
fpart = self.frac_secs + other.frac_secs
|
||||
return stamp(ipart, fpart)
|
||||
elif isinstance(other, float):
|
||||
return self + stamp(0, other)
|
||||
elif isinstance(other, int):
|
||||
return self + stamp(other, 0)
|
||||
else:
|
||||
raise TypeError
|
||||
|
||||
def __sub__(self, other):
|
||||
if isinstance(other, self.__class__):
|
||||
ipart = self.secs - other.secs
|
||||
fpart = self.frac_secs - other.frac_secs
|
||||
return stamp(ipart, fpart)
|
||||
elif isinstance(other, float):
|
||||
return self - stamp(0, other)
|
||||
elif isinstance(other, int):
|
||||
return self - stamp(other, 0)
|
||||
else:
|
||||
raise TypeError
|
||||
|
||||
#to ensure we don't hash by stamp
|
||||
#TODO fixme with a reasonable hash in case you feel like you'd hash by stamp
|
||||
__hash__ = None
|
||||
|
||||
#good to within ms for comparison
|
||||
def __float__(self):
|
||||
return self.secs + self.frac_secs
|
||||
|
||||
def __str__(self):
|
||||
return "%f" % float(self)
|
||||
|
||||
#a Mode S report including the modes_reply data object
|
||||
modes_report = namedtuple('modes_report', ['data', 'ecc', 'rssi', 'timestamp'])
|
||||
#lat, lon, alt
|
||||
#TODO: a position class internally represented as ECEF XYZ which can easily be used for multilateration and distance calculation
|
||||
llh = namedtuple('llh', ['lat', 'lon', 'alt'])
|
||||
mlat_report = namedtuple('mlat_report', ['data', 'nreps', 'timestamp', 'llh', 'hdop', 'vdop'])
|
||||
140
python/zmq_socket.py
Normal file
140
python/zmq_socket.py
Normal file
@@ -0,0 +1,140 @@
|
||||
# Copyright 2013 Nick Foster
|
||||
#
|
||||
# This file is part of gr-air-modes
|
||||
#
|
||||
# gr-air-modes is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; either version 3, or (at your option)
|
||||
# any later version.
|
||||
#
|
||||
# gr-air-modes is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with gr-air-modes; see the file COPYING. If not, write to
|
||||
# the Free Software Foundation, Inc., 51 Franklin Street,
|
||||
# Boston, MA 02110-1301, USA.
|
||||
|
||||
#this serves as a bridge between ZMQ subscriber and the GR pubsub callbacks interface
|
||||
#creates a thread, publishes socket data to pubsub subscribers
|
||||
#just a convenient way to create an aggregating socket with callbacks on receive
|
||||
#can use this for inproc:// signalling with minimal overhead
|
||||
#not sure if it's a good idea to use this yet
|
||||
|
||||
import time
|
||||
import threading
|
||||
import zmq
|
||||
from gnuradio.gr.pubsub import pubsub
|
||||
import Queue
|
||||
|
||||
class zmq_pubsub_iface(threading.Thread):
|
||||
def __init__(self, context, subaddr=None, pubaddr=None):
|
||||
threading.Thread.__init__(self)
|
||||
#private data
|
||||
self._queue = Queue.Queue()
|
||||
self._subsocket = context.socket(zmq.SUB)
|
||||
self._pubsocket = context.socket(zmq.PUB)
|
||||
self._subaddr = subaddr
|
||||
self._pubaddr = pubaddr
|
||||
if type(self._subaddr) is str:
|
||||
self._subaddr = [self._subaddr]
|
||||
if type(self._pubaddr) is str:
|
||||
self._pubaddr = [self._pubaddr]
|
||||
self._sub_connected = False
|
||||
self._pubsub = pubsub()
|
||||
if self._pubaddr is not None:
|
||||
for addr in self._pubaddr:
|
||||
self._pubsocket.bind(addr)
|
||||
|
||||
self._poller = zmq.Poller()
|
||||
self._poller.register(self._subsocket, zmq.POLLIN)
|
||||
|
||||
#public data
|
||||
self.shutdown = threading.Event()
|
||||
self.finished = threading.Event()
|
||||
#init
|
||||
self.setDaemon(True)
|
||||
self.start()
|
||||
|
||||
def subscribe(self, key, subscriber):
|
||||
if not self._sub_connected:
|
||||
if not self._subaddr:
|
||||
raise Exception("No subscriber address set")
|
||||
for addr in self._subaddr:
|
||||
self._subsocket.connect(addr)
|
||||
self._sub_connected = True
|
||||
self._subsocket.setsockopt(zmq.SUBSCRIBE, key)
|
||||
self._pubsub.subscribe(key, subscriber)
|
||||
|
||||
def unsubscribe(self, key, subscriber):
|
||||
self._subsocket.setsockopt(zmq.UNSUBSCRIBE, key)
|
||||
self._pubsub.unsubscribe(key, subscriber)
|
||||
|
||||
#executed from the thread context(s) of the caller(s)
|
||||
#so we use a queue to push sending into the run loop
|
||||
#since sockets must be used in the thread they were created in
|
||||
def __setitem__(self, key, val):
|
||||
if not self._pubaddr:
|
||||
raise Exception("No publisher address set")
|
||||
if not self.shutdown.is_set():
|
||||
self._queue.put([key, val])
|
||||
|
||||
def __getitem__(self, key):
|
||||
return self._pubsub[key]
|
||||
|
||||
def run(self):
|
||||
done = False
|
||||
while not self.shutdown.is_set() and not done:
|
||||
if self.shutdown.is_set():
|
||||
done = True
|
||||
#send
|
||||
while not self._queue.empty():
|
||||
self._pubsocket.send_multipart(self._queue.get())
|
||||
#receive
|
||||
if self._sub_connected:
|
||||
socks = dict(self._poller.poll(timeout=0))
|
||||
while self._subsocket in socks \
|
||||
and socks[self._subsocket] == zmq.POLLIN:
|
||||
[address, msg] = self._subsocket.recv_multipart()
|
||||
self._pubsub[address] = msg
|
||||
socks = dict(self._poller.poll(timeout=0))
|
||||
#snooze
|
||||
if not done:
|
||||
time.sleep(0.1)
|
||||
|
||||
self._subsocket.close()
|
||||
self._pubsocket.close()
|
||||
self.finished.set()
|
||||
|
||||
def close(self):
|
||||
self.shutdown.set()
|
||||
#self._queue.join() #why does this block forever
|
||||
self.finished.wait(0.2)
|
||||
|
||||
def pr(x):
|
||||
print x
|
||||
|
||||
if __name__ == "__main__":
|
||||
#create socket pair
|
||||
context = zmq.Context(1)
|
||||
sock1 = zmq_pubsub_iface(context, subaddr="inproc://sock2-pub", pubaddr="inproc://sock1-pub")
|
||||
sock2 = zmq_pubsub_iface(context, subaddr="inproc://sock1-pub", pubaddr=["inproc://sock2-pub", "tcp://*:5433"])
|
||||
sock3 = zmq_pubsub_iface(context, subaddr="tcp://localhost:5433", pubaddr=None)
|
||||
|
||||
sock1.subscribe("data1", pr)
|
||||
sock2.subscribe("data2", pr)
|
||||
sock3.subscribe("data3", pr)
|
||||
|
||||
for i in range(10):
|
||||
sock1["data2"] = "HOWDY"
|
||||
sock2["data3"] = "DRAW"
|
||||
sock2["data1"] = "PARDNER"
|
||||
time.sleep(0.1)
|
||||
|
||||
time.sleep(0.1)
|
||||
|
||||
sock1.close()
|
||||
sock2.close()
|
||||
sock3.close()
|
||||
@@ -1,4 +1,4 @@
|
||||
# Copyright 2011 Free Software Foundation, Inc.
|
||||
# Copyright 2011,2013 Free Software Foundation, Inc.
|
||||
#
|
||||
# This file is part of GNU Radio
|
||||
#
|
||||
@@ -31,14 +31,17 @@ if(NOT PYQT4_FOUND)
|
||||
return()
|
||||
endif()
|
||||
|
||||
if(NOT PYUIC4_FOUND)
|
||||
message(STATUS "pyuic4 not found, not installing GUI application")
|
||||
return()
|
||||
endif()
|
||||
|
||||
find_package(Qwt)
|
||||
if(NOT QWT_FOUND)
|
||||
message(STATUS "Qwt not found, not installing GUI application")
|
||||
return()
|
||||
endif()
|
||||
|
||||
|
||||
set(PYUIC4_COMPILE pyuic4)
|
||||
set(RX_UI_SRC ${CMAKE_CURRENT_SOURCE_DIR}/modes_rx.ui)
|
||||
set(RX_UI_PY_PRE_MASSAGE ${CMAKE_CURRENT_BINARY_DIR}/modes_rx_ui_borked.py)
|
||||
set(RX_UI_PY ${CMAKE_CURRENT_BINARY_DIR}/modes_rx_ui.py)
|
||||
@@ -49,7 +52,7 @@ add_custom_target(rx_ui ALL
|
||||
)
|
||||
|
||||
add_custom_command(OUTPUT ${RX_UI_PY_PRE_MASSAGE}
|
||||
COMMAND ${PYUIC4_COMPILE} ${RX_UI_SRC} > ${RX_UI_PY_PRE_MASSAGE}
|
||||
COMMAND ${PYUIC4_EXECUTABLE} ${RX_UI_SRC} > ${RX_UI_PY_PRE_MASSAGE}
|
||||
MAIN_DEPENDENCY ${RX_UI_SRC}
|
||||
)
|
||||
|
||||
|
||||
@@ -365,7 +365,7 @@
|
||||
</rect>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Raw</string>
|
||||
<string>TCP</string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QLineEdit" name="line_kmlfilename">
|
||||
@@ -856,7 +856,7 @@
|
||||
<string>Climb</string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QwtCompass" name="compass_heading">
|
||||
<widget class="QwtCompass" name="compass_heading" native="true">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>200</x>
|
||||
@@ -865,10 +865,10 @@
|
||||
<height>91</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="readOnly">
|
||||
<property name="readOnly" stdset="0">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="lineWidth">
|
||||
<property name="lineWidth" stdset="0">
|
||||
<number>4</number>
|
||||
</property>
|
||||
</widget>
|
||||
@@ -901,7 +901,7 @@
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QwtCompass" name="compass_bearing">
|
||||
<widget class="QwtCompass" name="compass_bearing" native="true">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>-20</x>
|
||||
@@ -910,10 +910,10 @@
|
||||
<height>91</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="readOnly">
|
||||
<property name="readOnly" stdset="0">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="lineWidth">
|
||||
<property name="lineWidth" stdset="0">
|
||||
<number>4</number>
|
||||
</property>
|
||||
</widget>
|
||||
@@ -928,6 +928,16 @@
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QWidget" name="map_tab">
|
||||
<attribute name="title">
|
||||
<string>Map</string>
|
||||
</attribute>
|
||||
<layout class="QGridLayout" name="gridLayout_8">
|
||||
<item row="0" column="0">
|
||||
<widget class="QWebView" name="mapView" native="true"/>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QWidget" name="livedatatab">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
|
||||
@@ -1016,11 +1026,6 @@
|
||||
<widget class="QStatusBar" name="statusbar"/>
|
||||
</widget>
|
||||
<customwidgets>
|
||||
<customwidget>
|
||||
<class>QwtCompass</class>
|
||||
<extends>QwtDial</extends>
|
||||
<header>qwt_compass.h</header>
|
||||
</customwidget>
|
||||
<customwidget>
|
||||
<class>QwtDial</class>
|
||||
<extends>QWidget</extends>
|
||||
@@ -1032,6 +1037,17 @@
|
||||
<header location="global">air_modes/az_map</header>
|
||||
<container>1</container>
|
||||
</customwidget>
|
||||
<customwidget>
|
||||
<class>QwtCompass</class>
|
||||
<extends>QwtDial</extends>
|
||||
<header>qwt_compass.h</header>
|
||||
</customwidget>
|
||||
<customwidget>
|
||||
<class>QWebView</class>
|
||||
<extends>QWidget</extends>
|
||||
<header location="global">QtWebKit/qwebview.h</header>
|
||||
<container>1</container>
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<resources/>
|
||||
<connections/>
|
||||
|
||||
@@ -31,11 +31,8 @@ include(GrPython)
|
||||
########################################################################
|
||||
# Setup swig generation
|
||||
########################################################################
|
||||
foreach(incdir ${GNURADIO_CORE_INCLUDE_DIRS})
|
||||
list(APPEND GR_SWIG_INCLUDE_DIRS ${incdir}/swig)
|
||||
endforeach(incdir)
|
||||
foreach(incdir ${GRUEL_INCLUDE_DIRS})
|
||||
list(APPEND GR_SWIG_INCLUDE_DIRS ${incdir}/gruel/swig)
|
||||
foreach(incdir ${GNURADIO_RUNTIME_INCLUDE_DIRS})
|
||||
list(APPEND GR_SWIG_INCLUDE_DIRS ${incdir}/gnuradio/swig)
|
||||
endforeach(incdir)
|
||||
|
||||
set(GR_SWIG_LIBRARIES air_modes)
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
%{
|
||||
#include "air_modes_preamble.h"
|
||||
#include "air_modes_slicer.h"
|
||||
#include <gr_msg_queue.h>
|
||||
#include <gnuradio/msg_queue.h>
|
||||
%}
|
||||
|
||||
// ----------------------------------------------------------------
|
||||
@@ -21,20 +21,24 @@ GR_SWIG_BLOCK_MAGIC(air,modes_preamble);
|
||||
|
||||
air_modes_preamble_sptr air_make_modes_preamble (int channel_rate, float threshold_db);
|
||||
|
||||
class air_modes_preamble : public gr_sync_block
|
||||
class air_modes_preamble : public gr::sync_block
|
||||
{
|
||||
set_rate(int channel_rate);
|
||||
set_threshold(float threshold_db);
|
||||
int get_threshold(void);
|
||||
private:
|
||||
air_modes_preamble (int channel_rate, float threshold_db);
|
||||
};
|
||||
|
||||
GR_SWIG_BLOCK_MAGIC(air,modes_slicer);
|
||||
|
||||
air_modes_slicer_sptr air_make_modes_slicer (int channel_rate, gr_msg_queue_sptr queue);
|
||||
air_modes_slicer_sptr air_make_modes_slicer (int channel_rate, gr::msg_queue::sptr queue);
|
||||
|
||||
class air_modes_slicer : public gr_block
|
||||
class air_modes_slicer : public gr::block
|
||||
{
|
||||
set_rate(int channel_rate);
|
||||
private:
|
||||
air_modes_slicer (int channel_rate, gr_msg_queue_sptr queue);
|
||||
air_modes_slicer (int channel_rate, gr::msg_queue::sptr queue);
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user