Compare commits
36 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
df35c92fe2 | ||
|
|
b3021720d6 | ||
|
|
b7627fa2d4 | ||
|
|
4d2587574a | ||
|
|
ca58874861 | ||
|
|
55559086ac | ||
|
|
c878d80e28 | ||
|
|
a528f375f7 | ||
|
|
1630e67c3b | ||
|
|
8e1bdafc51 | ||
|
|
e52e4039c0 | ||
|
|
640b13e62d | ||
|
|
2c92b15a34 | ||
|
|
797bef13d1 | ||
|
|
366e4d1736 | ||
|
|
96679fbd35 | ||
|
|
fb6143596d | ||
|
|
dfa62d0621 | ||
|
|
8d3b4d4da1 | ||
|
|
2869446a9a | ||
|
|
3540114c94 | ||
|
|
041305fd49 | ||
|
|
48c55fa7f8 | ||
|
|
1eca043bac | ||
|
|
6e230a7d9e | ||
|
|
a09c5add43 | ||
|
|
498cea34b2 | ||
|
|
4174658f0d | ||
|
|
b594fe2799 | ||
|
|
7fef37d34d | ||
|
|
2deefdf310 | ||
|
|
37aa74fbe0 | ||
|
|
d71e6bc1e7 | ||
|
|
0ce6374656 | ||
|
|
2766107a76 | ||
|
|
55cd17de67 |
@@ -69,23 +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")
|
||||
endif()
|
||||
|
||||
########################################################################
|
||||
# Find 0MQ networking library
|
||||
########################################################################
|
||||
find_package(ZeroMQ)
|
||||
if(NOT ZEROMQ_FOUND)
|
||||
message(FATAL_ERROR "Could not find required library libzmq (ZeroMQ).")
|
||||
if(NOT GNURADIO_RUNTIME_FOUND)
|
||||
message(FATAL_ERROR "GnuRadio Runtime required to compile gr-air-modes")
|
||||
endif()
|
||||
|
||||
########################################################################
|
||||
@@ -94,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
|
||||
@@ -123,7 +108,7 @@ add_custom_target(uninstall
|
||||
########################################################################
|
||||
# Add subdirectories
|
||||
########################################################################
|
||||
add_subdirectory(include)
|
||||
add_subdirectory(include/gr_air_modes)
|
||||
add_subdirectory(lib)
|
||||
add_subdirectory(swig)
|
||||
add_subdirectory(python)
|
||||
|
||||
218
apps/modes_gui
218
apps/modes_gui
@@ -19,11 +19,11 @@
|
||||
# Boston, MA 02110-1301, USA.
|
||||
#
|
||||
|
||||
import os, sys, time, threading, datetime, math, csv
|
||||
import os, sys, time, threading, datetime, math, csv, tempfile, ConfigParser
|
||||
from optparse import OptionParser
|
||||
from PyQt4 import QtCore,QtGui
|
||||
from PyQt4 import QtCore,QtGui,QtWebKit
|
||||
from PyQt4.Qwt5 import Qwt
|
||||
from gnuradio import gr, gru, optfir, eng_notation, blks2
|
||||
from gnuradio import gr, eng_notation
|
||||
from gnuradio.eng_option import eng_option
|
||||
from gnuradio.gr.pubsub import pubsub
|
||||
import air_modes
|
||||
@@ -48,31 +48,50 @@ class mainwindow(QtGui.QMainWindow):
|
||||
#populate antenna, rate combo boxes based on source
|
||||
self.populate_source_options()
|
||||
|
||||
defaults = self.get_defaults()
|
||||
|
||||
#should round to actual achieved gain
|
||||
self.ui.line_gain.insert("30")
|
||||
self.ui.line_gain.insert(defaults["gain"])
|
||||
|
||||
#default to 5dB
|
||||
self.ui.line_threshold.insert("5")
|
||||
self.ui.line_threshold.insert(defaults["threshold"])
|
||||
|
||||
self.ui.prog_rssi.setMinimum(0)
|
||||
self.ui.prog_rssi.setMaximum(40)
|
||||
if defaults["pmf"] is not None:
|
||||
self.ui.check_pmf.setChecked(bool(defaults["pmf"]))
|
||||
if defaults["dcblock"] is not None:
|
||||
self.ui.check_dcblock.setChecked(bool(defaults["dcblock"]))
|
||||
if defaults["samplerate"] is not None:
|
||||
if defaults["samplerate"] in self.rates:
|
||||
self.ui.combo_rate.setCurrentIndex(self.rates.index(int(defaults["samplerate"])))
|
||||
|
||||
self.ui.combo_ant.setCurrentIndex(self.ui.combo_ant.findText("RX2"))
|
||||
self.ui.prog_rssi.setMinimum(-60)
|
||||
self.ui.prog_rssi.setMaximum(0)
|
||||
|
||||
if defaults["antenna"] is None:
|
||||
self.ui.combo_ant.setCurrentIndex(self.ui.combo_ant.findText("RX2"))
|
||||
else:
|
||||
self.ui.combo_ant.setCurrentIndex(self.ui.combo_ant.findText(defaults["antenna"]))
|
||||
|
||||
#check KML by default, leave the rest unchecked.
|
||||
self.ui.check_sbs1.setCheckState(QtCore.Qt.Unchecked)
|
||||
self.ui.check_raw.setCheckState(QtCore.Qt.Unchecked)
|
||||
self.ui.check_fgfs.setCheckState(QtCore.Qt.Unchecked)
|
||||
self.ui.check_kml.setCheckState(QtCore.Qt.Checked)
|
||||
self.ui.check_sbs1.setChecked(bool(defaults["sbs1"] == "1"))
|
||||
self.ui.check_raw.setChecked(bool(defaults["raw"] == "1"))
|
||||
self.ui.check_fgfs.setChecked(bool(defaults["fgfs"] == "1"))
|
||||
self.ui.check_kml.setChecked(bool(defaults["kml"] == "1"))
|
||||
|
||||
self.ui.line_sbs1port.insert("30003")
|
||||
self.ui.line_rawport.insert("9988")
|
||||
self.ui.line_fgfsport.insert("5500")
|
||||
self.ui.line_kmlfilename.insert("modes.kml")
|
||||
self.ui.line_sbs1port.insert(defaults["sbs1port"])#"30003")
|
||||
self.ui.line_rawport.insert(defaults["rawport"])#"9988")
|
||||
self.ui.line_fgfsport.insert(defaults["fgfsport"])#"5500")
|
||||
self.ui.line_kmlfilename.insert(defaults["kmlfile"])#"modes.kml")
|
||||
|
||||
if defaults["latitude"] is not None:
|
||||
self.ui.line_my_lat.insert(defaults["latitude"])
|
||||
if defaults["longitude"] is not None:
|
||||
self.ui.line_my_lon.insert(defaults["longitude"])
|
||||
|
||||
#disable by default
|
||||
self.ui.check_adsbonly.setCheckState(QtCore.Qt.Unchecked)
|
||||
|
||||
#set up the radio stuff
|
||||
self.queue = gr.msg_queue(10)
|
||||
self.running = False
|
||||
self.kmlgen = None #necessary bc we stop its thread in shutdown
|
||||
@@ -117,12 +136,31 @@ class mainwindow(QtGui.QMainWindow):
|
||||
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 parameter-changed signals so we can change gain, rate, etc. while running
|
||||
self.ui.combo_rate.currentIndexChanged['QString'].connect(self.update_sample_rate)
|
||||
self.ui.line_gain.editingFinished.connect(self.update_gain)
|
||||
self.ui.combo_source.currentIndexChanged['QString'].connect(self.populate_source_options)
|
||||
|
||||
#hook up live data text box update signal
|
||||
self.live_data_changed_signal.connect(self.on_append_live_data)
|
||||
|
||||
self._last_live_data_update = time.time()
|
||||
self._pending_msgstr = ""
|
||||
|
||||
self.prefs = None
|
||||
|
||||
def update_sample_rate(self, rate):
|
||||
if self.running:
|
||||
self._radio.set_rate(int(float(rate)*1e6))
|
||||
|
||||
def update_gain(self):
|
||||
if self.running:
|
||||
self._radio.set_gain(float(self.ui.line_gain.text()))
|
||||
|
||||
############ widget update functions for non-mapped widgets ############
|
||||
def update_heading_widget(self, index):
|
||||
if index.model() is not None:
|
||||
@@ -148,7 +186,7 @@ class mainwindow(QtGui.QMainWindow):
|
||||
if index.model() is not None:
|
||||
rssi = 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 +197,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 +210,13 @@ class mainwindow(QtGui.QMainWindow):
|
||||
self.rates = []
|
||||
self.ratetext = []
|
||||
self.antennas = []
|
||||
|
||||
|
||||
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() if (rate.start() % 2.e6) == 0]
|
||||
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,11 +228,11 @@ class mainwindow(QtGui.QMainWindow):
|
||||
self.ui.combo_ant.setEnabled(False)
|
||||
self.ui.combo_rate.setEnabled(False)
|
||||
self.ui.stack_source.setCurrentIndex(0)
|
||||
|
||||
|
||||
elif sourceid == "Osmocom":
|
||||
try:
|
||||
import osmosdr
|
||||
self.src = osmosdr.source_c("")
|
||||
self.src = osmosdr.source("")
|
||||
self.rates = [rate.start() for rate in self.src.get_sample_rates() if (rate.start() % 2.e6) == 0]
|
||||
self.antennas = ["RX"]
|
||||
self.src = None
|
||||
@@ -216,8 +261,13 @@ 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
|
||||
if len(self.rates) > 1:
|
||||
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):
|
||||
@@ -246,7 +296,8 @@ class mainwindow(QtGui.QMainWindow):
|
||||
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()
|
||||
options.pmf = self.ui.check_pmf.isChecked()
|
||||
options.dcblock = self.ui.check_dcblock.isChecked()
|
||||
|
||||
self._servers = ["inproc://modes-radio-pub"] #TODO ADD REMOTES
|
||||
self._relay = air_modes.zmq_pubsub_iface(self.context, subaddr=self._servers, pubaddr=None)
|
||||
@@ -288,16 +339,35 @@ class mainwindow(QtGui.QMainWindow):
|
||||
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(self._cpr_dec, self._publisher)
|
||||
#add output for live data box
|
||||
#self._relay.subscribe("dl_data", 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(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._relay.subscribe("dl_data", self.increment_reportspersec)
|
||||
#self.updates.append(self.update_reportspersec) #TODO FIXME
|
||||
self._rps_timer = QtCore.QTimer()
|
||||
self._rps_timer.timeout.connect(self.update_reportspersec)
|
||||
self._rps_timer.start(1000)
|
||||
|
||||
#start the flowgraph
|
||||
self._radio.start()
|
||||
@@ -305,12 +375,36 @@ class mainwindow(QtGui.QMainWindow):
|
||||
self.ui.button_start.setText("Stop")
|
||||
self.running = True
|
||||
|
||||
#grab prefs and save them
|
||||
self.prefs = {}
|
||||
self.prefs["samplerate"] = options.rate
|
||||
self.prefs["antenna"] = options.antenna
|
||||
self.prefs["gain"] = options.gain
|
||||
self.prefs["pmf"] = "1" if options.pmf else "0"
|
||||
self.prefs["dcblock"] = "1" if options.dcblock else "0"
|
||||
self.prefs["source"] = self.ui.combo_source.currentText()
|
||||
self.prefs["threshold"] = options.threshold
|
||||
self.prefs["sbs1"] = "1" if self.ui.check_sbs1.isChecked() else "0"
|
||||
self.prefs["sbs1port"] = int(self.ui.line_sbs1port.text())
|
||||
self.prefs["fgfs"] = "1" if self.ui.check_fgfs.isChecked() else "0"
|
||||
self.prefs["fgfsport"] = int(self.ui.line_fgfsport.text())
|
||||
self.prefs["raw"] = "1" if self.ui.check_raw.isChecked() else "0"
|
||||
self.prefs["rawport"] = int(self.ui.line_rawport.text())
|
||||
self.prefs["kml"] = "1" if self.ui.check_kml.isChecked() else "0"
|
||||
self.prefs["kmlfile"] = self.ui.line_kmlfilename.text()
|
||||
try:
|
||||
self.prefs["latitude"] = float(self.ui.line_my_lat.text())
|
||||
self.prefs["longitude"] = float(self.ui.line_my_lon.text())
|
||||
except:
|
||||
pass
|
||||
|
||||
def on_quit(self):
|
||||
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
|
||||
@@ -319,26 +413,78 @@ class mainwindow(QtGui.QMainWindow):
|
||||
except:
|
||||
pass
|
||||
|
||||
if self.prefs is not None:
|
||||
self.write_defaults(self.prefs)
|
||||
|
||||
#slot to catch signal emitted by output_live_data (necessary for
|
||||
#thread safety since output_live_data is called by another thread)
|
||||
def on_append_live_data(self, msgstr):
|
||||
self._pending_msgstr += msgstr + "\n"
|
||||
if time.time() - self._last_live_data_update >= 0.1:
|
||||
self._last_live_data_update = time.time()
|
||||
self.update_live_data(self._pending_msgstr)
|
||||
self._pending_msgstr = ""
|
||||
|
||||
def update_live_data(self, msgstr):
|
||||
#limit scrollback buffer size -- is there a faster way?
|
||||
if(self.ui.text_livedata.document().lineCount() > 500):
|
||||
cursor = self.ui.text_livedata.textCursor()
|
||||
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):
|
||||
opt_file = "~/.gr-air-modes/prefs"
|
||||
def get_defaults(self):
|
||||
defaults = {}
|
||||
defaults["samplerate"] = None #let app pick it
|
||||
defaults["pmf"] = None
|
||||
defaults["dcblock"] = None
|
||||
defaults["antenna"] = None
|
||||
defaults["gain"] = "25"
|
||||
defaults["kml"] = "1"
|
||||
defaults["kmlfile"] = "modes.kml"
|
||||
defaults["sbs1"] = "0"
|
||||
defaults["sbs1port"] = "30003"
|
||||
defaults["raw"] = "0"
|
||||
defaults["rawport"] = "9988"
|
||||
defaults["fgfs"] = "0"
|
||||
defaults["fgfsport"] = "5500"
|
||||
defaults["source"] = "UHD"
|
||||
defaults["threshold"] = "5"
|
||||
defaults["latitude"] = None
|
||||
defaults["longitude"] = None
|
||||
|
||||
prefs = ConfigParser.ConfigParser(defaults)
|
||||
prefs.optionxform = str
|
||||
|
||||
try:
|
||||
msgstr = self.livedata.parse(msg)
|
||||
if msgstr is not None:
|
||||
self.live_data_changed_signal.emit(msgstr)
|
||||
except ADSBError:
|
||||
pass
|
||||
prefs.read(os.path.expanduser(self.opt_file))
|
||||
for item in prefs.items("GUI"):
|
||||
defaults[item[0]] = item[1]
|
||||
except (IOError, ConfigParser.NoSectionError):
|
||||
print "No preferences file %s found, creating..." % os.path.expanduser(self.opt_file)
|
||||
self.write_defaults(defaults)
|
||||
|
||||
return defaults
|
||||
|
||||
|
||||
def write_defaults(self, defaults):
|
||||
config = ConfigParser.RawConfigParser()
|
||||
config.add_section('GUI')
|
||||
|
||||
for item in defaults:
|
||||
config.set('GUI', item, str(defaults[item]))
|
||||
|
||||
dirname = os.path.dirname(os.path.expanduser(self.opt_file))
|
||||
if not os.path.exists(dirname):
|
||||
os.makedirs(dirname)
|
||||
|
||||
with open(os.path.expanduser(self.opt_file), 'wb') as prefsfile:
|
||||
config.write(prefsfile)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
app = QtGui.QApplication(sys.argv)
|
||||
|
||||
@@ -86,8 +86,9 @@ def main():
|
||||
sbs1port = air_modes.output_sbs1(cpr_dec, 30003, publisher)
|
||||
|
||||
tb.run()
|
||||
time.sleep(0.2)
|
||||
tb.close()
|
||||
|
||||
time.sleep(0.2)
|
||||
relay.close()
|
||||
|
||||
if options.kml is not None:
|
||||
|
||||
@@ -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,54 +0,0 @@
|
||||
/*
|
||||
# Copyright 2010 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.
|
||||
#
|
||||
*/
|
||||
|
||||
#ifndef INCLUDED_AIR_MODES_int_and_dump_H
|
||||
#define INCLUDED_AIR_MODES_int_and_dump_H
|
||||
|
||||
#include <gr_block.h>
|
||||
|
||||
class air_modes_int_and_dump;
|
||||
typedef boost::shared_ptr<air_modes_int_and_dump> air_modes_int_and_dump_sptr;
|
||||
|
||||
air_modes_int_and_dump_sptr air_make_modes_int_and_dump(int samples_per_chip);
|
||||
|
||||
/*!
|
||||
* \brief mode select int_and_dump filter
|
||||
* \ingroup block
|
||||
*/
|
||||
class air_modes_int_and_dump : public gr_block
|
||||
{
|
||||
private:
|
||||
friend air_modes_int_and_dump_sptr air_make_modes_int_and_dump(int samples_per_chip);
|
||||
air_modes_int_and_dump(int samples_per_chip);
|
||||
|
||||
int d_samples_per_chip;
|
||||
float d_acc;
|
||||
float d_pos;
|
||||
|
||||
public:
|
||||
int general_work (int noutput_items,
|
||||
gr_vector_int &ninput_items,
|
||||
gr_vector_const_void_star &input_items,
|
||||
gr_vector_void_star &output_items);
|
||||
};
|
||||
|
||||
#endif /* INCLUDED_AIR_MODES_int_and_dump_H */
|
||||
@@ -1,66 +0,0 @@
|
||||
/*
|
||||
# Copyright 2010 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.
|
||||
#
|
||||
*/
|
||||
|
||||
#ifndef INCLUDED_AIR_MODES_PREAMBLE_H
|
||||
#define INCLUDED_AIR_MODES_PREAMBLE_H
|
||||
|
||||
#include <gr_block.h>
|
||||
#include <air_modes_api.h>
|
||||
|
||||
class air_modes_preamble;
|
||||
typedef boost::shared_ptr<air_modes_preamble> air_modes_preamble_sptr;
|
||||
|
||||
AIR_MODES_API air_modes_preamble_sptr air_make_modes_preamble(int channel_rate, float threshold_db);
|
||||
|
||||
/*!
|
||||
* \brief mode select preamble detection
|
||||
* \ingroup 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);
|
||||
air_modes_preamble(int channel_rate, float threshold_db);
|
||||
|
||||
int d_check_width;
|
||||
int d_chip_rate;
|
||||
float d_preamble_length_us;
|
||||
int d_samples_per_chip;
|
||||
int d_samples_per_symbol;
|
||||
float d_threshold_db;
|
||||
float d_threshold;
|
||||
pmt::pmt_t d_me, d_key;
|
||||
gr_tag_t d_timestamp;
|
||||
double d_secs_per_sample;
|
||||
|
||||
public:
|
||||
int general_work (int noutput_items,
|
||||
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 */
|
||||
@@ -21,9 +21,9 @@
|
||||
# Install public header files
|
||||
########################################################################
|
||||
install(FILES
|
||||
air_modes_preamble.h
|
||||
air_modes_slicer.h
|
||||
air_modes_types.h
|
||||
air_modes_api.h
|
||||
DESTINATION include/gr-air-modes
|
||||
preamble.h
|
||||
slicer.h
|
||||
types.h
|
||||
api.h
|
||||
DESTINATION include/gr_air_modes
|
||||
)
|
||||
@@ -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_MODES_CRC_H
|
||||
#define INCLUDED_MODES_CRC_H
|
||||
extern const unsigned int modes_crc_table[112];
|
||||
int modes_check_crc(unsigned char data[], int length);
|
||||
unsigned int modes_check_crc(unsigned char data[], int length);
|
||||
bruteResultTypeDef modes_ec_brute(modes_packet &err_packet);
|
||||
unsigned next_set_of_n_elements(unsigned x);
|
||||
|
||||
51
include/gr_air_modes/preamble.h
Normal file
51
include/gr_air_modes/preamble.h
Normal file
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
# 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.
|
||||
#
|
||||
*/
|
||||
|
||||
#ifndef INCLUDED_AIR_MODES_PREAMBLE_H
|
||||
#define INCLUDED_AIR_MODES_PREAMBLE_H
|
||||
|
||||
#include <gnuradio/block.h>
|
||||
#include <gr_air_modes/api.h>
|
||||
|
||||
namespace gr {
|
||||
namespace air_modes {
|
||||
|
||||
/*!
|
||||
* \brief mode select preamble detection
|
||||
* \ingroup block
|
||||
*/
|
||||
class AIR_MODES_API preamble : virtual public gr::block
|
||||
{
|
||||
public:
|
||||
typedef boost::shared_ptr<preamble> sptr;
|
||||
static sptr make(int channel_rate, float threshold_db);
|
||||
|
||||
virtual void set_rate(int channel_rate) = 0;
|
||||
virtual void set_threshold(float threshold_db) = 0;
|
||||
virtual int get_rate(void) = 0;
|
||||
virtual float get_threshold(void) = 0;
|
||||
};
|
||||
|
||||
} // namespace air_modes
|
||||
} // namespace gr
|
||||
|
||||
#endif /* INCLUDED_AIR_MODES_PREAMBLE_H */
|
||||
47
include/gr_air_modes/slicer.h
Normal file
47
include/gr_air_modes/slicer.h
Normal file
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
# 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.
|
||||
#
|
||||
*/
|
||||
|
||||
#ifndef INCLUDED_AIR_MODES_SLICER_H
|
||||
#define INCLUDED_AIR_MODES_SLICER_H
|
||||
|
||||
#include <gnuradio/block.h>
|
||||
#include <gr_air_modes/api.h>
|
||||
#include <gnuradio/msg_queue.h>
|
||||
|
||||
namespace gr {
|
||||
namespace air_modes {
|
||||
|
||||
/*!
|
||||
* \brief mode select slicer
|
||||
* \ingroup block
|
||||
*/
|
||||
class AIR_MODES_API slicer : virtual public gr::sync_block
|
||||
{
|
||||
public:
|
||||
typedef boost::shared_ptr<slicer> sptr;
|
||||
static sptr make(gr::msg_queue::sptr queue);
|
||||
};
|
||||
|
||||
} //namespace air_modes
|
||||
} //namespace gr
|
||||
|
||||
#endif /* INCLUDED_AIR_MODES_SLICER_H */
|
||||
@@ -23,11 +23,11 @@
|
||||
include(GrPlatform) #define LIB_SUFFIX
|
||||
|
||||
add_library(air_modes SHARED
|
||||
air_modes_preamble.cc
|
||||
air_modes_slicer.cc
|
||||
preamble_impl.cc
|
||||
slicer_impl.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,89 +0,0 @@
|
||||
/*
|
||||
# Copyright 2010 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.
|
||||
#
|
||||
*/
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
#endif
|
||||
|
||||
#include <air_modes_int_and_dump.h>
|
||||
#include <gr_io_signature.h>
|
||||
#include <string.h>
|
||||
#include <iostream>
|
||||
|
||||
air_modes_int_and_dump_sptr air_make_modes_int_and_dump(int samples_per_symbol)
|
||||
{
|
||||
return air_modes_int_and_dump_sptr (new air_modes_int_and_dump(samples_per_symbol));
|
||||
}
|
||||
|
||||
air_modes_int_and_dump::air_modes_int_and_dump(int samps_per_chip) :
|
||||
gr_block ("modes_int_and_dump",
|
||||
gr_make_io_signature (1, 1, sizeof(float)),
|
||||
gr_make_io_signature (1, 1, sizeof(float)))
|
||||
{
|
||||
d_samples_per_symbol = samples_per_symbol;
|
||||
set_output_multiple(d_samples_per_symbol);
|
||||
set_history(d_samples_per_symbol);
|
||||
|
||||
d_acc = 0;
|
||||
d_pos = 0;
|
||||
}
|
||||
|
||||
int air_modes_int_and_dump::general_work(int noutput_items,
|
||||
gr_vector_int &ninput_items,
|
||||
gr_vector_const_void_star &input_items,
|
||||
gr_vector_void_star &output_items)
|
||||
{
|
||||
const float *in = (const float *) input_items[0];
|
||||
float *out = (float *) output_items[0];
|
||||
|
||||
int input_items = std::min(ninput_items[0], ninput_items[1]); //just in case
|
||||
|
||||
//ok first of all we look for "preamble_found" tags in our input range.
|
||||
//get a vector of these tags, then every time we hit one
|
||||
//reset the integrator position and accumulator
|
||||
std::vector<pmt::pmt_t> tags;
|
||||
uint64_t abs_sample_cnt = nitems_read(0);
|
||||
get_tags_in_range(tags, 0, abs_sample_cnt, abs_sample_cnt + ninput_items, pmt::pmt_string_to_symbol("preamble_found"));
|
||||
|
||||
int out_items = 0;
|
||||
|
||||
int offset = gr_tags::get_nitems(&tags[0]) - abs_sample_cnt;
|
||||
|
||||
for(int i=0; i<120*2; i++) { //for each symbol in a potential long packet
|
||||
out[out_items] = 0;
|
||||
for(int j=0; j<d_samples_per_symbol; j++) { //for each sample in the symbol
|
||||
out[out_items] += in[offset+i+j]; //integrate
|
||||
}
|
||||
out_items++;
|
||||
}
|
||||
|
||||
//insert tag here
|
||||
add_item_tag(0, //stream ID
|
||||
nitems_written(0), //sample number
|
||||
pmt::pmt_string_to_symbol("preamble_found");
|
||||
pmt::PMT_T,
|
||||
pmt::pmt_string_to_symbol(unique_id());
|
||||
);
|
||||
|
||||
consume_each(wat);
|
||||
return out_items;
|
||||
}
|
||||
@@ -1,231 +0,0 @@
|
||||
/*
|
||||
# Copyright 2010 Nick Foster
|
||||
# Copyright 2013 Nicholas Corgan
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
*/
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
#endif
|
||||
|
||||
#include <ciso646>
|
||||
#include <air_modes_preamble.h>
|
||||
#include <gr_io_signature.h>
|
||||
#include <string.h>
|
||||
#include <iostream>
|
||||
#include <gr_tags.h>
|
||||
|
||||
air_modes_preamble_sptr air_make_modes_preamble(int channel_rate, float threshold_db)
|
||||
{
|
||||
return air_modes_preamble_sptr (new air_modes_preamble(channel_rate, threshold_db));
|
||||
}
|
||||
|
||||
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
|
||||
{
|
||||
set_rate(channel_rate);
|
||||
set_threshold(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");
|
||||
set_history(d_samples_per_symbol);
|
||||
}
|
||||
|
||||
void air_modes_preamble::set_rate(int channel_rate)
|
||||
{
|
||||
d_chip_rate = 2000000; //2Mchips per second
|
||||
d_samples_per_chip = channel_rate / d_chip_rate; //must be integer number of samples per chip to work
|
||||
d_samples_per_symbol = d_samples_per_chip * 2;
|
||||
d_secs_per_sample = 1.0 / channel_rate;
|
||||
d_check_width = 240 * d_samples_per_symbol; //only search to this far from the end of the stream buffer
|
||||
set_output_multiple(1+d_check_width);
|
||||
}
|
||||
|
||||
void air_modes_preamble::set_threshold(float threshold_db)
|
||||
{
|
||||
d_threshold_db = threshold_db;
|
||||
d_threshold = powf(10., threshold_db/20.); //the level that the sample must be above the moving average in order to qualify as a pulse
|
||||
}
|
||||
|
||||
float air_modes_preamble::get_threshold(void)
|
||||
{
|
||||
return d_threshold_db;
|
||||
}
|
||||
|
||||
static void integrate_and_dump(float *out, const float *in, int chips, int samps_per_chip) {
|
||||
for(int i=0; i<chips; i++) {
|
||||
float acc = 0;
|
||||
for(int j=0; j<samps_per_chip; j++) {
|
||||
acc += in[i*samps_per_chip+j];
|
||||
}
|
||||
out[i] = acc;
|
||||
}
|
||||
}
|
||||
|
||||
//the preamble pattern in bits
|
||||
//fixme goes in .h
|
||||
static const bool preamble_bits[] = {1, 0, 1, 0, 0, 0, 0, 1, 0, 1};
|
||||
static double correlate_preamble(const float *in, int samples_per_chip) {
|
||||
double corr = 0.0;
|
||||
for(int i=0; i<10; i++) {
|
||||
for(int j=0; j<samples_per_chip;j++)
|
||||
if(preamble_bits[i]) corr += in[i*samples_per_chip+j];
|
||||
}
|
||||
return corr;
|
||||
}
|
||||
|
||||
//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) {
|
||||
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;
|
||||
|
||||
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));
|
||||
ts_sample = tstamp.offset;
|
||||
|
||||
double tstime = double(abs_sample_cnt * secs_per_sample) + last_whole_stamp + last_frac_stamp;
|
||||
if(0) std::cout << "HEY WE GOT A STAMP AT " << tstime << " TICKS AT SAMPLE " << ts_sample << " ABS SAMPLE CNT IS " << abs_sample_cnt << std::endl;
|
||||
return tstime;
|
||||
}
|
||||
|
||||
int air_modes_preamble::general_work(int noutput_items,
|
||||
gr_vector_int &ninput_items,
|
||||
gr_vector_const_void_star &input_items,
|
||||
gr_vector_void_star &output_items)
|
||||
{
|
||||
const float *in = (const float *) input_items[0];
|
||||
const float *inavg = (const float *) input_items[1];
|
||||
|
||||
int mininputs = std::min(ninput_items[0], ninput_items[1]); //they should be matched but let's be safe
|
||||
//round number of input samples down to nearest d_samples_per_chip
|
||||
//we also subtract off d_samples_per_chip to allow the bit center finder some leeway
|
||||
const int ninputs = std::max(mininputs - (mininputs % d_samples_per_chip) - d_samples_per_chip, 0);
|
||||
if (ninputs <= 0) { consume_each(0); return 0; }
|
||||
|
||||
float *out = (float *) output_items[0];
|
||||
|
||||
if(0) std::cout << "Preamble called with " << ninputs << " samples" << std::endl;
|
||||
|
||||
//fixme move into .h
|
||||
const int pulse_offsets[4] = { 0,
|
||||
int(2 * d_samples_per_chip),
|
||||
int(7 * d_samples_per_chip),
|
||||
int(9 * d_samples_per_chip)
|
||||
};
|
||||
|
||||
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"));
|
||||
//tags.back() is the most recent timestamp, then.
|
||||
if(tstamp_tags.size() > 0) {
|
||||
d_timestamp = tstamp_tags.back();
|
||||
}
|
||||
|
||||
for(int i=0; i < ninputs; i++) {
|
||||
float pulse_threshold = inavg[i] * d_threshold;
|
||||
if(in[i] > pulse_threshold) { //hey we got a candidate
|
||||
if(in[i+1] > in[i]) continue; //wait for the peak
|
||||
//check to see the rest of the pulses are there
|
||||
if( in[i+pulse_offsets[1]] < pulse_threshold ) continue;
|
||||
if( in[i+pulse_offsets[2]] < pulse_threshold ) continue;
|
||||
if( in[i+pulse_offsets[3]] < pulse_threshold ) continue;
|
||||
|
||||
//get a more accurate bit center by finding the correlation peak across all four preamble bits
|
||||
bool late, early;
|
||||
int how_late = 0;
|
||||
do {
|
||||
double now_corr = correlate_preamble(in+i, d_samples_per_chip);
|
||||
double late_corr = correlate_preamble(in+i+1, d_samples_per_chip);
|
||||
double early_corr = correlate_preamble(in+i-1, d_samples_per_chip);
|
||||
late = (late_corr > now_corr);
|
||||
//early = (early_corr > now_corr);
|
||||
if(late) { i++; how_late++; }
|
||||
//if(early && i>0) { std::cout << "EARLY " << i << std::endl; i--; }
|
||||
} while(late and how_late < d_samples_per_chip);// xor early);
|
||||
|
||||
if(0) std::cout << "We were " << how_late << " samples late" << std::endl;
|
||||
|
||||
//now check to see that the non-peak symbols in the preamble
|
||||
//are below the peaks by threshold dB
|
||||
float avgpeak = ( in[i+pulse_offsets[0]]
|
||||
+ in[i+pulse_offsets[1]]
|
||||
+ in[i+pulse_offsets[2]]
|
||||
+ in[i+pulse_offsets[3]]) / 4.0;
|
||||
|
||||
float space_threshold = inavg[i] + (avgpeak - inavg[i])/d_threshold;
|
||||
bool valid_preamble = true; //f'in c++
|
||||
for( int j=1.5*d_samples_per_symbol; j<=3*d_samples_per_symbol; j++)
|
||||
if(in[i+j] > space_threshold) valid_preamble = false;
|
||||
for( int j=5*d_samples_per_symbol; j<=7.5*d_samples_per_symbol; j++)
|
||||
if(in[i+j] > space_threshold) valid_preamble = false;
|
||||
if(!valid_preamble) continue;
|
||||
|
||||
//be sure we've got enough room in the input buffer to copy out a whole packet
|
||||
if(ninputs-i < 240*d_samples_per_chip) {
|
||||
consume_each(std::max(i-1,0));
|
||||
if(0) std::cout << "Preamble consumed " << std::max(i-1,0) << ", returned 0 (no room)" << std::endl;
|
||||
return 0;
|
||||
}
|
||||
|
||||
//all right i'm prepared to call this a preamble
|
||||
//let's integrate and dump the output
|
||||
//FIXME: disable and use center sample
|
||||
bool life_sucks = true;
|
||||
if(life_sucks) {
|
||||
for(int j=0; j<240; j++) {
|
||||
out[j] = in[i+j*d_samples_per_chip];
|
||||
}
|
||||
} else {
|
||||
i -= d_samples_per_chip-1;
|
||||
integrate_and_dump(out, &in[i], 240, d_samples_per_chip);
|
||||
}
|
||||
|
||||
//get the timestamp of the preamble
|
||||
double tstamp = tag_to_timestamp(d_timestamp, abs_sample_cnt + i, d_secs_per_sample);
|
||||
|
||||
//now tag the preamble
|
||||
add_item_tag(0, //stream ID
|
||||
nitems_written(0), //sample
|
||||
d_key, //frame_info
|
||||
pmt::pmt_make_tuple(pmt::pmt_from_double(tstamp), pmt::pmt_from_double(inavg[i])),
|
||||
d_me //block src id
|
||||
);
|
||||
|
||||
//std::cout << "PREAMBLE" << std::endl;
|
||||
|
||||
//produce only one output per work call -- TODO this should probably change
|
||||
if(0) std::cout << "Preamble consumed " << i+240*d_samples_per_chip << "with i=" << i << ", returned 240" << std::endl;
|
||||
|
||||
consume_each(i+240*d_samples_per_chip);
|
||||
return 240;
|
||||
}
|
||||
}
|
||||
|
||||
//didn't get anything this time
|
||||
if(0) std::cout << "Preamble consumed " << ninputs << ", returned 0" << std::endl;
|
||||
consume_each(ninputs);
|
||||
return 0;
|
||||
}
|
||||
@@ -1,197 +0,0 @@
|
||||
/*
|
||||
# Copyright 2010 Nick Foster
|
||||
# Copyright 2013 Nicholas Corgan
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
*/
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
#endif
|
||||
|
||||
#include <ciso646>
|
||||
#include <air_modes_slicer.h>
|
||||
#include <gr_io_signature.h>
|
||||
#include <air_modes_types.h>
|
||||
#include <sstream>
|
||||
#include <iomanip>
|
||||
#include <modes_crc.h>
|
||||
#include <iostream>
|
||||
#include <gr_tags.h>
|
||||
|
||||
extern "C"
|
||||
{
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
}
|
||||
|
||||
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) )
|
||||
{
|
||||
set_rate(channel_rate);
|
||||
d_queue = queue;
|
||||
}
|
||||
|
||||
void air_modes_slicer::set_rate(int channel_rate)
|
||||
{
|
||||
d_chip_rate = 2000000; //2Mchips per second
|
||||
d_samples_per_chip = 2;//FIXME this is constant now channel_rate / d_chip_rate;
|
||||
d_samples_per_symbol = d_samples_per_chip * 2;
|
||||
d_check_width = 120 * d_samples_per_symbol; //how far you will have to look ahead
|
||||
set_output_multiple(d_check_width*2); //how do you specify buffer size for sinks?
|
||||
}
|
||||
|
||||
//this slicer is courtesy of Lincoln Labs. supposedly it is more resistant to mode A/C FRUIT.
|
||||
//see http://adsb.tc.faa.gov/WG3_Meetings/Meeting8/Squitter-Lon.pdf
|
||||
static slice_result_t slicer(const float bit0, const float bit1, const float ref) {
|
||||
slice_result_t result;
|
||||
|
||||
//3dB limits for bit slicing and confidence measurement
|
||||
float highlimit=ref*1.414;
|
||||
float lowlimit=ref*0.707;
|
||||
|
||||
bool firstchip_inref = ((bit0 > lowlimit) && (bit0 < highlimit));
|
||||
bool secondchip_inref = ((bit1 > lowlimit) && (bit1 < highlimit));
|
||||
|
||||
if(firstchip_inref && !secondchip_inref) {
|
||||
result.decision = 1;
|
||||
result.confidence = 1;
|
||||
}
|
||||
else if(secondchip_inref && !firstchip_inref) {
|
||||
result.decision = 0;
|
||||
result.confidence = 1;
|
||||
}
|
||||
else if(firstchip_inref && secondchip_inref) {
|
||||
result.decision = bit0 > bit1;
|
||||
result.confidence = 0;
|
||||
}
|
||||
else {//if(!firstchip_inref && !secondchip_inref) {
|
||||
result.decision = bit0 > bit1;
|
||||
if(result.decision) {
|
||||
if(bit1 < lowlimit * 0.5) result.confidence = 1;
|
||||
else result.confidence = 0;
|
||||
} else {
|
||||
if(bit0 < lowlimit * 0.5) result.confidence = 1;
|
||||
else result.confidence = 0;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
int air_modes_slicer::work(int noutput_items,
|
||||
gr_vector_const_void_star &input_items,
|
||||
gr_vector_void_star &output_items)
|
||||
{
|
||||
const float *in = (const float *) input_items[0];
|
||||
int size = noutput_items - d_check_width; //since it's a sync block, i assume that it runs with ninput_items = noutput_items
|
||||
|
||||
if(0) std::cout << "Slicer called with " << size << " samples" << std::endl;
|
||||
|
||||
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;
|
||||
|
||||
for(tag_iter = tags.begin(); tag_iter != tags.end(); tag_iter++) {
|
||||
uint64_t i = tag_iter->offset - abs_sample_cnt;
|
||||
modes_packet rx_packet;
|
||||
|
||||
memset(&rx_packet.data, 0x00, 14 * sizeof(unsigned char));
|
||||
memset(&rx_packet.lowconfbits, 0x00, 24 * sizeof(unsigned char));
|
||||
rx_packet.numlowconf = 0;
|
||||
|
||||
//let's use the preamble to get a reference level for the packet
|
||||
//fixme: a better thing to do is create a bi-level avg 1 and avg 0
|
||||
//through simple statistics, then take the median for your slice level
|
||||
//this won't improve decoding but will improve confidence
|
||||
rx_packet.reference_level = (in[i]
|
||||
+ in[i+2]
|
||||
+ in[i+7]
|
||||
+ in[i+9]) / 4.0;
|
||||
|
||||
i += 16; //move on up to the first bit of the packet data
|
||||
//now let's slice the header so we can determine if it's a short pkt or a long pkt
|
||||
unsigned char pkt_hdr = 0;
|
||||
for(int j=0; j < 5; j++) {
|
||||
slice_result_t slice_result = slicer(in[i+j*2], in[i+j*2+1], rx_packet.reference_level);
|
||||
if(slice_result.decision) pkt_hdr += 1 << (4-j);
|
||||
}
|
||||
if(pkt_hdr == 16 or pkt_hdr == 17 or pkt_hdr == 20 or pkt_hdr == 21) rx_packet.type = Long_Packet;
|
||||
else rx_packet.type = Short_Packet;
|
||||
int packet_length = (rx_packet.type == framer_packet_type(Short_Packet)) ? 56 : 112;
|
||||
|
||||
//it's slice time!
|
||||
//TODO: don't repeat your work here, you already have the first 5 bits
|
||||
for(int j = 0; j < packet_length; j++) {
|
||||
slice_result_t slice_result = slicer(in[i+j*2], in[i+j*2+1], rx_packet.reference_level);
|
||||
|
||||
//put the data into the packet
|
||||
if(slice_result.decision) {
|
||||
rx_packet.data[j/8] += 1 << (7-(j%8));
|
||||
}
|
||||
//put the confidence decision into the packet
|
||||
if(slice_result.confidence) {
|
||||
//rx_packet.confidence[j/8] += 1 << (7-(j%8));
|
||||
} else {
|
||||
if(rx_packet.numlowconf < 24) rx_packet.lowconfbits[rx_packet.numlowconf++] = j;
|
||||
}
|
||||
}
|
||||
|
||||
rx_packet.timestamp = pmt_to_double(pmt_tuple_ref(tag_iter->value, 0));
|
||||
double ref = pmt_to_double(pmt_tuple_ref(tag_iter->value, 1));
|
||||
|
||||
//traverse the whole packet and if you find all 0's, just toss it. don't know why these packets turn up, but they pass ECC.
|
||||
bool zeroes = 1;
|
||||
for(int m = 0; m < 14; m++) {
|
||||
if(rx_packet.data[m]) { zeroes = 0; break; }
|
||||
}
|
||||
if(zeroes) {continue;} //toss it
|
||||
|
||||
rx_packet.message_type = (rx_packet.data[0] >> 3) & 0x1F; //get the message type to make decisions on ECC methods
|
||||
|
||||
if(rx_packet.type == Short_Packet && rx_packet.message_type != 11 && rx_packet.numlowconf > 0) {continue;}
|
||||
if(rx_packet.message_type == 11 && rx_packet.numlowconf >= 10) {continue;}
|
||||
|
||||
rx_packet.crc = modes_check_crc(rx_packet.data, packet_length);
|
||||
|
||||
//crc for packets that aren't type 11 or type 17 is encoded with the transponder ID, which we don't know
|
||||
//therefore we toss 'em if there's syndrome
|
||||
//crc for the other short packets is usually nonzero, so they can't really be trusted that far
|
||||
if(rx_packet.crc && (rx_packet.message_type == 11 || rx_packet.message_type == 17)) {continue;}
|
||||
|
||||
d_payload.str("");
|
||||
for(int m = 0; m < packet_length/8; m++) {
|
||||
d_payload << std::hex << std::setw(2) << std::setfill('0') << unsigned(rx_packet.data[m]);
|
||||
}
|
||||
|
||||
d_payload << " " << std::setw(6) << rx_packet.crc << " " << std::dec << rx_packet.reference_level / ref
|
||||
<< " " << std::setprecision(10) << std::setw(10) << rx_packet.timestamp;
|
||||
gr_message_sptr msg = gr_make_message_from_string(std::string(d_payload.str()));
|
||||
d_queue->handle(msg);
|
||||
}
|
||||
if(0) std::cout << "Slicer consumed " << size << ", returned " << size << std::endl;
|
||||
return size;
|
||||
}
|
||||
172
lib/modes_crc.cc
172
lib/modes_crc.cc
@@ -1,165 +1,63 @@
|
||||
/*
|
||||
* Copyright 2007 Free Software Foundation, Inc.
|
||||
* Copyright 2013 Nick Foster
|
||||
*
|
||||
* This file is part of GNU Radio
|
||||
* This file is part of gr-air-modes
|
||||
*
|
||||
* GNU Radio is free software; you can redistribute it and/or modify
|
||||
* 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 2, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* GNU Radio is distributed in the hope that it will be useful,
|
||||
* 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 GNU Radio; see the file COPYING. If not, write to
|
||||
* 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 is copied almost verbatim from Eric Cottrell's gr-air platform.
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
#endif
|
||||
|
||||
#include <stdio.h>
|
||||
#include <air_modes_types.h>
|
||||
#include <modes_crc.h>
|
||||
#include <gr_air_modes/types.h>
|
||||
#include <gr_air_modes/modes_crc.h>
|
||||
#include <math.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
/* Mode S Parity Table
|
||||
* Index is bit position with bit 0 being the first bit after preamble
|
||||
* On short frames an offset of 56 is used.
|
||||
*/
|
||||
const unsigned int modes_crc_table[112] =
|
||||
{
|
||||
0x3935ea, // Start of Long Frame CRC
|
||||
0x1c9af5,
|
||||
0xf1b77e,
|
||||
0x78dbbf,
|
||||
0xc397db,
|
||||
0x9e31e9,
|
||||
0xb0e2f0,
|
||||
0x587178,
|
||||
0x2c38bc,
|
||||
0x161c5e,
|
||||
0x0b0e2f,
|
||||
0xfa7d13,
|
||||
0x82c48d,
|
||||
0xbe9842,
|
||||
0x5f4c21,
|
||||
0xd05c14,
|
||||
0x682e0a,
|
||||
0x341705,
|
||||
0xe5f186,
|
||||
0x72f8c3,
|
||||
0xc68665,
|
||||
0x9cb936,
|
||||
0x4e5c9b,
|
||||
0xd8d449,
|
||||
0x939020,
|
||||
0x49c810,
|
||||
0x24e408,
|
||||
0x127204,
|
||||
0x093902,
|
||||
0x049c81,
|
||||
0xfdb444,
|
||||
0x7eda22,
|
||||
0x3f6d11, // Extended 56 bit field
|
||||
0xe04c8c,
|
||||
0x702646,
|
||||
0x381323,
|
||||
0xe3f395,
|
||||
0x8e03ce,
|
||||
0x4701e7,
|
||||
0xdc7af7,
|
||||
0x91c77f,
|
||||
0xb719bb,
|
||||
0xa476d9,
|
||||
0xadc168,
|
||||
0x56e0b4,
|
||||
0x2b705a,
|
||||
0x15b82d,
|
||||
0xf52612,
|
||||
0x7a9309,
|
||||
0xc2b380,
|
||||
0x6159c0,
|
||||
0x30ace0,
|
||||
0x185670,
|
||||
0x0c2b38,
|
||||
0x06159c,
|
||||
0x030ace,
|
||||
0x018567,
|
||||
0xff38b7, // Start of Short Frame CRC
|
||||
0x80665f,
|
||||
0xbfc92b,
|
||||
0xa01e91,
|
||||
0xaff54c,
|
||||
0x57faa6,
|
||||
0x2bfd53,
|
||||
0xea04ad,
|
||||
0x8af852,
|
||||
0x457c29,
|
||||
0xdd4410,
|
||||
0x6ea208,
|
||||
0x375104,
|
||||
0x1ba882,
|
||||
0x0dd441,
|
||||
0xf91024,
|
||||
0x7c8812,
|
||||
0x3e4409,
|
||||
0xe0d800,
|
||||
0x706c00,
|
||||
0x383600,
|
||||
0x1c1b00,
|
||||
0x0e0d80,
|
||||
0x0706c0,
|
||||
0x038360,
|
||||
0x01c1b0,
|
||||
0x00e0d8,
|
||||
0x00706c,
|
||||
0x003836,
|
||||
0x001c1b,
|
||||
0xfff409,
|
||||
0x800000, // 24 PI or PA bits
|
||||
0x400000,
|
||||
0x200000,
|
||||
0x100000,
|
||||
0x080000,
|
||||
0x040000,
|
||||
0x020000,
|
||||
0x010000,
|
||||
0x008000,
|
||||
0x004000,
|
||||
0x002000,
|
||||
0x001000,
|
||||
0x000800,
|
||||
0x000400,
|
||||
0x000200,
|
||||
0x000100,
|
||||
0x000080,
|
||||
0x000040,
|
||||
0x000020,
|
||||
0x000010,
|
||||
0x000008,
|
||||
0x000004,
|
||||
0x000002,
|
||||
0x000001,
|
||||
};
|
||||
unsigned int crc_table[256];
|
||||
const unsigned int POLY=0xFFF409;
|
||||
|
||||
int modes_check_crc(unsigned char data[], int length)
|
||||
//generate a bytewise lookup CRC table
|
||||
|
||||
void generate_crc_table(void)
|
||||
{
|
||||
int crc=0, i;
|
||||
for(i = 0; i < length; i++)
|
||||
{
|
||||
if(data[i/8] & (1 << (7-(i%8))))
|
||||
{
|
||||
crc ^= modes_crc_table[i+(112-length)];
|
||||
}
|
||||
}
|
||||
return crc;
|
||||
unsigned int crc = 0;
|
||||
for(int n=0; n<256; n++) {
|
||||
crc = n<<16;
|
||||
for(int k=0; k<8; k++) {
|
||||
if(crc & 0x800000) {
|
||||
crc = ((crc<<1) ^ POLY) & 0xFFFFFF;
|
||||
} else {
|
||||
crc = (crc<<1) & 0xFFFFFF;
|
||||
}
|
||||
}
|
||||
crc_table[n] = crc & 0xFFFFFF;
|
||||
}
|
||||
}
|
||||
|
||||
//Perform a bytewise CRC check
|
||||
unsigned int modes_check_crc(unsigned char data[], int length)
|
||||
{
|
||||
if(crc_table[1] != POLY) generate_crc_table();
|
||||
unsigned int crc=0;
|
||||
for(int i=0; i<length; i++) {
|
||||
crc = crc_table[((crc>>16) ^ data[i]) & 0xff] ^ (crc << 8);
|
||||
}
|
||||
return crc & 0xFFFFFF;
|
||||
}
|
||||
|
||||
227
lib/preamble_impl.cc
Normal file
227
lib/preamble_impl.cc
Normal file
@@ -0,0 +1,227 @@
|
||||
/*
|
||||
# Copyright 2010 Nick Foster
|
||||
# Copyright 2013 Nicholas Corgan
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
*/
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
#endif
|
||||
|
||||
#include <ciso646>
|
||||
#include "preamble_impl.h"
|
||||
#include <gnuradio/io_signature.h>
|
||||
#include <string.h>
|
||||
#include <iostream>
|
||||
#include <gnuradio/tags.h>
|
||||
|
||||
namespace gr {
|
||||
|
||||
air_modes::preamble::sptr air_modes::preamble::make(int channel_rate, float threshold_db) {
|
||||
return gnuradio::get_initial_sptr(new air_modes::preamble_impl(channel_rate, threshold_db));
|
||||
}
|
||||
|
||||
air_modes::preamble_impl::preamble_impl(int channel_rate, float threshold_db) :
|
||||
gr::block ("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 soft symbols
|
||||
{
|
||||
d_chip_rate = 2000000; //2Mchips per second
|
||||
set_rate(channel_rate);
|
||||
set_threshold(threshold_db);
|
||||
|
||||
std::stringstream str;
|
||||
str << name() << unique_id();
|
||||
d_me = pmt::string_to_symbol(str.str());
|
||||
d_key = pmt::string_to_symbol("preamble_found");
|
||||
}
|
||||
|
||||
void air_modes::preamble_impl::set_rate(int channel_rate) {
|
||||
d_samples_per_chip = channel_rate / d_chip_rate;
|
||||
d_samples_per_symbol = d_samples_per_chip * 2;
|
||||
d_check_width = 120 * d_samples_per_symbol;
|
||||
d_secs_per_sample = 1.0/channel_rate;
|
||||
set_output_multiple(1+d_check_width*2);
|
||||
set_history(d_samples_per_symbol);
|
||||
}
|
||||
|
||||
void air_modes::preamble_impl::set_threshold(float threshold_db) {
|
||||
d_threshold_db = threshold_db;
|
||||
d_threshold = powf(10., threshold_db/20.); //the level that the sample must be above the moving average in order to qualify as a pulse
|
||||
}
|
||||
|
||||
float air_modes::preamble_impl::get_threshold(void) {
|
||||
return d_threshold_db;
|
||||
}
|
||||
|
||||
int air_modes::preamble_impl::get_rate(void) {
|
||||
return d_samples_per_chip * d_chip_rate;
|
||||
}
|
||||
|
||||
static void integrate_and_dump(float *out, const float *in, int chips, int samps_per_chip) {
|
||||
for(int i=0; i<chips; i++) {
|
||||
float acc = 0;
|
||||
for(int j=0; j<samps_per_chip; j++) {
|
||||
acc += in[i*samps_per_chip+j];
|
||||
}
|
||||
out[i] = acc;
|
||||
}
|
||||
}
|
||||
|
||||
//the preamble pattern in bits
|
||||
//fixme goes in .h
|
||||
static const bool preamble_bits[] = {1, 0, 1, 0, 0, 0, 0, 1, 0, 1};
|
||||
static double correlate_preamble(const float *in, int samples_per_chip) {
|
||||
double corr = 0.0;
|
||||
for(int i=0; i<10; i++) {
|
||||
for(int j=0; j<samples_per_chip;j++)
|
||||
if(preamble_bits[i]) corr += in[i*samples_per_chip+j];
|
||||
}
|
||||
return corr;
|
||||
}
|
||||
|
||||
//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) {
|
||||
uint64_t ts_sample, last_whole_stamp;
|
||||
double last_frac_stamp;
|
||||
|
||||
if(tstamp.key == NULL || pmt::symbol_to_string(tstamp.key) != "rx_time") return 0;
|
||||
|
||||
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;
|
||||
if(0) std::cout << "HEY WE GOT A STAMP AT " << tstime << " TICKS AT SAMPLE " << ts_sample << " ABS SAMPLE CNT IS " << abs_sample_cnt << std::endl;
|
||||
return tstime;
|
||||
}
|
||||
|
||||
int air_modes::preamble_impl::general_work(int noutput_items,
|
||||
gr_vector_int &ninput_items,
|
||||
gr_vector_const_void_star &input_items,
|
||||
gr_vector_void_star &output_items)
|
||||
{
|
||||
const float *in = (const float *) input_items[0];
|
||||
const float *inavg = (const float *) input_items[1];
|
||||
|
||||
int mininputs = std::min(ninput_items[0], ninput_items[1]); //they should be matched but let's be safe
|
||||
//round number of input samples down to nearest d_samples_per_chip
|
||||
//we also subtract off d_samples_per_chip to allow the bit center finder some leeway
|
||||
const int ninputs = std::max(mininputs - (mininputs % d_samples_per_chip) - d_samples_per_chip, 0);
|
||||
if (ninputs <= 0) { consume_each(0); return 0; }
|
||||
|
||||
float *out = (float *) output_items[0];
|
||||
|
||||
if(0) std::cout << "Preamble called with " << ninputs << " samples" << std::endl;
|
||||
|
||||
//fixme move into .h
|
||||
const int pulse_offsets[4] = { 0,
|
||||
int(2 * d_samples_per_chip),
|
||||
int(7 * d_samples_per_chip),
|
||||
int(9 * d_samples_per_chip)
|
||||
};
|
||||
|
||||
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::string_to_symbol("rx_time"));
|
||||
//tags.back() is the most recent timestamp, then.
|
||||
if(tstamp_tags.size() > 0) {
|
||||
d_timestamp = tstamp_tags.back();
|
||||
}
|
||||
|
||||
for(int i=0; i < ninputs; i++) {
|
||||
float pulse_threshold = inavg[i] * d_threshold;
|
||||
if(in[i] > pulse_threshold) { //hey we got a candidate
|
||||
if(in[i+1] > in[i]) continue; //wait for the peak
|
||||
//check to see the rest of the pulses are there
|
||||
if( in[i+pulse_offsets[1]] < pulse_threshold ) continue;
|
||||
if( in[i+pulse_offsets[2]] < pulse_threshold ) continue;
|
||||
if( in[i+pulse_offsets[3]] < pulse_threshold ) continue;
|
||||
|
||||
//get a more accurate bit center by finding the correlation peak across all four preamble bits
|
||||
bool late, early;
|
||||
int how_late = 0;
|
||||
do {
|
||||
double now_corr = correlate_preamble(in+i, d_samples_per_chip);
|
||||
double late_corr = correlate_preamble(in+i+1, d_samples_per_chip);
|
||||
double early_corr = correlate_preamble(in+i-1, d_samples_per_chip);
|
||||
late = (late_corr > now_corr);
|
||||
//early = (early_corr > now_corr);
|
||||
if(late) { i++; how_late++; }
|
||||
//if(early && i>0) { std::cout << "EARLY " << i << std::endl; i--; }
|
||||
} while(late and how_late < d_samples_per_chip);// xor early);
|
||||
|
||||
if(0) std::cout << "We were " << how_late << " samples late" << std::endl;
|
||||
|
||||
//now check to see that the non-peak symbols in the preamble
|
||||
//are below the peaks by threshold dB
|
||||
float avgpeak = ( in[i+pulse_offsets[0]]
|
||||
+ in[i+pulse_offsets[1]]
|
||||
+ in[i+pulse_offsets[2]]
|
||||
+ in[i+pulse_offsets[3]]) / 4.0;
|
||||
|
||||
float space_threshold = inavg[i] + (avgpeak - inavg[i])/d_threshold;
|
||||
bool valid_preamble = true; //f'in c++
|
||||
for( int j=1.5*d_samples_per_symbol; j<=3*d_samples_per_symbol; j++)
|
||||
if(in[i+j] > space_threshold) valid_preamble = false;
|
||||
for( int j=5*d_samples_per_symbol; j<=7.5*d_samples_per_symbol; j++)
|
||||
if(in[i+j] > space_threshold) valid_preamble = false;
|
||||
if(!valid_preamble) continue;
|
||||
|
||||
//be sure we've got enough room in the input buffer to copy out a whole packet
|
||||
if(ninputs-i < 240*d_samples_per_chip) {
|
||||
consume_each(std::max(i-1,0));
|
||||
if(0) std::cout << "Preamble consumed " << std::max(i-1,0) << ", returned 0 (no room)" << std::endl;
|
||||
return 0;
|
||||
}
|
||||
|
||||
//all right i'm prepared to call this a preamble
|
||||
for(int j=0; j<240; j++) {
|
||||
out[j] = in[i+j*d_samples_per_chip] - inavg[i];
|
||||
}
|
||||
|
||||
//get the timestamp of the preamble
|
||||
double tstamp = tag_to_timestamp(d_timestamp, abs_sample_cnt + i, d_secs_per_sample);
|
||||
|
||||
//now tag the preamble
|
||||
add_item_tag(0, //stream ID
|
||||
nitems_written(0), //sample
|
||||
d_key, //frame_info
|
||||
pmt::from_double(tstamp),
|
||||
d_me //block src id
|
||||
);
|
||||
|
||||
//std::cout << "PREAMBLE" << std::endl;
|
||||
|
||||
//produce only one output per work call -- TODO this should probably change
|
||||
if(0) std::cout << "Preamble consumed " << i+240*d_samples_per_chip << "with i=" << i << ", returned 240" << std::endl;
|
||||
|
||||
consume_each(i+240*d_samples_per_chip);
|
||||
return 240;
|
||||
}
|
||||
}
|
||||
|
||||
//didn't get anything this time
|
||||
if(0) std::cout << "Preamble consumed " << ninputs << ", returned 0" << std::endl;
|
||||
consume_each(ninputs);
|
||||
return 0;
|
||||
}
|
||||
|
||||
} //namespace gr
|
||||
43
lib/preamble_impl.h
Normal file
43
lib/preamble_impl.h
Normal file
@@ -0,0 +1,43 @@
|
||||
|
||||
#ifndef _AIR_MODES_PREAMBLE_IMPL_H_
|
||||
#define _AIR_MODES_PREAMBLE_IMPL_H_
|
||||
|
||||
#include <gnuradio/block.h>
|
||||
#include <gr_air_modes/api.h>
|
||||
#include <gr_air_modes/preamble.h>
|
||||
|
||||
namespace gr {
|
||||
namespace air_modes {
|
||||
|
||||
class AIR_MODES_API preamble_impl : public preamble
|
||||
{
|
||||
private:
|
||||
int d_check_width;
|
||||
int d_chip_rate;
|
||||
float d_preamble_length_us;
|
||||
int d_samples_per_chip;
|
||||
int d_samples_per_symbol;
|
||||
float d_threshold_db;
|
||||
float d_threshold;
|
||||
pmt::pmt_t d_me, d_key;
|
||||
gr::tag_t d_timestamp;
|
||||
double d_secs_per_sample;
|
||||
|
||||
public:
|
||||
preamble_impl(int channel_rate, float threshold_db);
|
||||
|
||||
int general_work (int noutput_items,
|
||||
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);
|
||||
int get_rate(void);
|
||||
};
|
||||
|
||||
} //namespace air_modes
|
||||
} //namespace gr
|
||||
|
||||
#endif //_AIR_MODES_PREAMBLE_IMPL_H_
|
||||
200
lib/slicer_impl.cc
Normal file
200
lib/slicer_impl.cc
Normal file
@@ -0,0 +1,200 @@
|
||||
/*
|
||||
# Copyright 2010 Nick Foster
|
||||
# Copyright 2013 Nicholas Corgan
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
*/
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
#endif
|
||||
|
||||
#include <ciso646>
|
||||
#include "slicer_impl.h"
|
||||
#include <gnuradio/io_signature.h>
|
||||
#include <gr_air_modes/types.h>
|
||||
#include <sstream>
|
||||
#include <iomanip>
|
||||
#include <gr_air_modes/modes_crc.h>
|
||||
#include <iostream>
|
||||
#include <gnuradio/tags.h>
|
||||
|
||||
extern "C"
|
||||
{
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
}
|
||||
|
||||
namespace gr {
|
||||
|
||||
air_modes::slicer::sptr air_modes::slicer::make(gr::msg_queue::sptr queue) {
|
||||
return gnuradio::get_initial_sptr(new air_modes::slicer_impl(queue));
|
||||
}
|
||||
|
||||
air_modes::slicer_impl::slicer_impl(gr::msg_queue::sptr queue) :
|
||||
gr::sync_block ("slicer",
|
||||
gr::io_signature::make (1, 1, sizeof(float)),
|
||||
gr::io_signature::make (0, 0, 0) )
|
||||
{
|
||||
//initialize private data here
|
||||
d_chip_rate = 2000000; //2Mchips per second
|
||||
d_samples_per_chip = 2;//FIXME this is constant now channel_rate / d_chip_rate;
|
||||
d_samples_per_symbol = d_samples_per_chip * 2;
|
||||
d_check_width = 120 * d_samples_per_symbol; //how far you will have to look ahead
|
||||
d_queue = queue;
|
||||
|
||||
set_output_multiple(d_check_width*2); //how do you specify buffer size for sinks?
|
||||
}
|
||||
|
||||
//this slicer is courtesy of Lincoln Labs. supposedly it is more resistant to mode A/C FRUIT.
|
||||
//see http://adsb.tc.faa.gov/WG3_Meetings/Meeting8/Squitter-Lon.pdf
|
||||
static slice_result_t llslicer(const float bit0, const float bit1, const float ref) {
|
||||
slice_result_t result;
|
||||
|
||||
//3dB limits for bit slicing and confidence measurement
|
||||
float highlimit=ref*1.414;
|
||||
float lowlimit=ref*0.707;
|
||||
|
||||
bool firstchip_inref = ((bit0 > lowlimit) && (bit0 < highlimit));
|
||||
bool secondchip_inref = ((bit1 > lowlimit) && (bit1 < highlimit));
|
||||
|
||||
if(firstchip_inref && !secondchip_inref) {
|
||||
result.decision = 1;
|
||||
result.confidence = 1;
|
||||
}
|
||||
else if(secondchip_inref && !firstchip_inref) {
|
||||
result.decision = 0;
|
||||
result.confidence = 1;
|
||||
}
|
||||
else if(firstchip_inref && secondchip_inref) {
|
||||
result.decision = bit0 > bit1;
|
||||
result.confidence = 0;
|
||||
}
|
||||
else {//if(!firstchip_inref && !secondchip_inref) {
|
||||
result.decision = bit0 > bit1;
|
||||
if(result.decision) {
|
||||
if(bit1 < lowlimit * 0.5) result.confidence = 1;
|
||||
else result.confidence = 0;
|
||||
} else {
|
||||
if(bit0 < lowlimit * 0.5) result.confidence = 1;
|
||||
else result.confidence = 0;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
int air_modes::slicer_impl::work(int noutput_items,
|
||||
gr_vector_const_void_star &input_items,
|
||||
gr_vector_void_star &output_items)
|
||||
{
|
||||
const float *in = (const float *) input_items[0];
|
||||
int size = noutput_items - d_check_width; //since it's a sync block, i assume that it runs with ninput_items = noutput_items
|
||||
|
||||
if(0) std::cout << "Slicer called with " << size << " samples" << std::endl;
|
||||
|
||||
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::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;
|
||||
modes_packet rx_packet;
|
||||
|
||||
memset(&rx_packet.data, 0x00, 14 * sizeof(unsigned char));
|
||||
memset(&rx_packet.lowconfbits, 0x00, 24 * sizeof(unsigned char));
|
||||
rx_packet.numlowconf = 0;
|
||||
|
||||
//let's use the preamble to get a reference level for the packet
|
||||
//fixme: a better thing to do is create a bi-level avg 1 and avg 0
|
||||
//through simple statistics, then take the median for your slice level
|
||||
//this won't improve decoding but will improve confidence
|
||||
rx_packet.reference_level = (in[i]
|
||||
+ in[i+2]
|
||||
+ in[i+7]
|
||||
+ in[i+9]) / 4.0;
|
||||
|
||||
i += 16; //move on up to the first bit of the packet data
|
||||
//now let's slice the header so we can determine if it's a short pkt or a long pkt
|
||||
unsigned char pkt_hdr = 0;
|
||||
for(int j=0; j < 5; j++) {
|
||||
slice_result_t slice_result = llslicer(in[i+j*2], in[i+j*2+1], rx_packet.reference_level);
|
||||
if(slice_result.decision) pkt_hdr += 1 << (4-j);
|
||||
}
|
||||
if(pkt_hdr == 16 or pkt_hdr == 17 or pkt_hdr == 20 or pkt_hdr == 21) rx_packet.type = Long_Packet;
|
||||
else rx_packet.type = Short_Packet;
|
||||
int packet_length = (rx_packet.type == framer_packet_type(Short_Packet)) ? 56 : 112;
|
||||
|
||||
//it's slice time!
|
||||
//TODO: don't repeat your work here, you already have the first 5 bits
|
||||
for(int j = 0; j < packet_length; j++) {
|
||||
slice_result_t slice_result = llslicer(in[i+j*2], in[i+j*2+1], rx_packet.reference_level);
|
||||
|
||||
//put the data into the packet
|
||||
if(slice_result.decision) {
|
||||
rx_packet.data[j/8] += 1 << (7-(j%8));
|
||||
}
|
||||
//put the confidence decision into the packet
|
||||
if(slice_result.confidence) {
|
||||
//rx_packet.confidence[j/8] += 1 << (7-(j%8));
|
||||
} else {
|
||||
if(rx_packet.numlowconf < 24) rx_packet.lowconfbits[rx_packet.numlowconf++] = j;
|
||||
}
|
||||
}
|
||||
|
||||
rx_packet.timestamp = pmt::to_double(tag_iter->value);
|
||||
|
||||
//here you might want to traverse the whole packet and if you find all 0's, just toss it. don't know why these packets turn up, but they pass ECC.
|
||||
bool zeroes = 1;
|
||||
for(int m = 0; m < 14; m++) {
|
||||
if(rx_packet.data[m]) zeroes = 0;
|
||||
}
|
||||
if(zeroes) {continue;} //toss it
|
||||
|
||||
rx_packet.message_type = (rx_packet.data[0] >> 3) & 0x1F; //get the message type to make decisions on ECC methods
|
||||
|
||||
if(rx_packet.type == Short_Packet && rx_packet.message_type != 11 && rx_packet.numlowconf > 0) {continue;}
|
||||
if(rx_packet.message_type == 11 && rx_packet.numlowconf >= 10) {continue;}
|
||||
|
||||
rx_packet.crc = modes_check_crc(rx_packet.data, (packet_length/8)-3);
|
||||
unsigned int ap = rx_packet.data[packet_length/8-3] << 16
|
||||
| rx_packet.data[packet_length/8-2] << 8
|
||||
| rx_packet.data[packet_length/8-1] << 0;
|
||||
rx_packet.crc ^= ap;
|
||||
|
||||
//crc for packets that aren't type 11 or type 17 is encoded with the transponder ID, which we don't know
|
||||
//therefore we toss 'em if there's syndrome
|
||||
//crc for the other short packets is usually nonzero, so they can't really be trusted that far
|
||||
if(rx_packet.crc && (rx_packet.message_type == 11 || rx_packet.message_type == 17)) {continue;}
|
||||
|
||||
d_payload.str("");
|
||||
for(int m = 0; m < packet_length/8; m++) {
|
||||
d_payload << std::hex << std::setw(2) << std::setfill('0') << unsigned(rx_packet.data[m]);
|
||||
}
|
||||
|
||||
d_payload << " " << std::setw(6) << rx_packet.crc << " " << std::dec << rx_packet.reference_level
|
||||
<< " " << std::setprecision(10) << std::setw(10) << rx_packet.timestamp;
|
||||
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;
|
||||
return size;
|
||||
}
|
||||
|
||||
} //namespace gr
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
# Copyright 2010 Nick Foster
|
||||
# Copyright 2013 Nick Foster
|
||||
#
|
||||
# This file is part of gr-air-modes
|
||||
#
|
||||
@@ -20,41 +20,36 @@
|
||||
#
|
||||
*/
|
||||
|
||||
#ifndef INCLUDED_AIR_MODES_slicer_H
|
||||
#define INCLUDED_AIR_MODES_slicer_H
|
||||
#ifndef INCLUDED_AIR_MODES_SLICER_IMPL_H
|
||||
#define INCLUDED_AIR_MODES_SLICER_IMPL_H
|
||||
|
||||
#include <gr_sync_block.h>
|
||||
#include <gr_msg_queue.h>
|
||||
#include <air_modes_api.h>
|
||||
#include <gnuradio/sync_block.h>
|
||||
#include <gnuradio/msg_queue.h>
|
||||
#include <gr_air_modes/api.h>
|
||||
#include <gr_air_modes/slicer.h>
|
||||
|
||||
class air_modes_slicer;
|
||||
typedef boost::shared_ptr<air_modes_slicer> air_modes_slicer_sptr;
|
||||
namespace gr {
|
||||
namespace air_modes {
|
||||
|
||||
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 slicer_impl : public slicer
|
||||
{
|
||||
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);
|
||||
|
||||
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:
|
||||
slicer_impl(gr::msg_queue::sptr queue);
|
||||
|
||||
int work (int noutput_items,
|
||||
gr_vector_const_void_star &input_items,
|
||||
gr_vector_void_star &output_items);
|
||||
|
||||
void set_rate(int channel_rate);
|
||||
};
|
||||
|
||||
#endif /* INCLUDED_AIR_MODES_slicer_H */
|
||||
} //namespace air_modes
|
||||
} //namespace gr
|
||||
|
||||
#endif /* INCLUDED_AIR_MODES_SLICER_IMPL_H */
|
||||
@@ -34,6 +34,7 @@ GR_PYTHON_INSTALL(
|
||||
altitude.py
|
||||
az_map.py
|
||||
cpr.py
|
||||
html_template.py
|
||||
mlat.py
|
||||
exceptions.py
|
||||
flightgear.py
|
||||
|
||||
@@ -57,7 +57,7 @@ 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 *
|
||||
@@ -65,6 +65,7 @@ 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
|
||||
|
||||
@@ -27,7 +27,7 @@ import threading
|
||||
import math
|
||||
import air_modes
|
||||
from air_modes.exceptions import *
|
||||
|
||||
import numpy as np
|
||||
|
||||
# model has max range vs. azimuth in n-degree increments
|
||||
# contains separate max range for a variety of altitudes so
|
||||
@@ -53,7 +53,7 @@ class az_map_model(QtCore.QObject):
|
||||
|
||||
def data(self, row, col):
|
||||
return self._data[row][col]
|
||||
|
||||
|
||||
def addRecord(self, bearing, altitude, distance):
|
||||
with self.lock:
|
||||
#round up to nearest altitude in altitudes list
|
||||
@@ -85,18 +85,15 @@ class az_map_model(QtCore.QObject):
|
||||
|
||||
# the azimuth map widget
|
||||
class az_map(QtGui.QWidget):
|
||||
maxrange = 450
|
||||
ringsize = 100
|
||||
maxrange = 200
|
||||
bgcolor = QtCore.Qt.black
|
||||
ringpen = QtGui.QPen(QtGui.QColor(0, 96, 127, 255), 1.3)
|
||||
#rangepen = QtGui.QPen(QtGui.QColor(255, 255, 0, 255), 1.0)
|
||||
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super(az_map, self).__init__(parent)
|
||||
self._model = None
|
||||
self._paths = []
|
||||
self.maxrange = az_map.maxrange
|
||||
self.ringsize = az_map.ringsize
|
||||
|
||||
def minimumSizeHint(self):
|
||||
return QtCore.QSize(50, 50)
|
||||
@@ -118,7 +115,7 @@ class az_map(QtGui.QWidget):
|
||||
|
||||
#set background
|
||||
painter.fillRect(event.rect(), QtGui.QBrush(az_map.bgcolor))
|
||||
|
||||
|
||||
#draw the range rings
|
||||
self.drawRangeRings(painter)
|
||||
for i in range(len(self._paths)):
|
||||
@@ -136,12 +133,12 @@ class az_map(QtGui.QWidget):
|
||||
bearing = (i+0.5) * 360./az_map_model.npoints
|
||||
distance = self._model._data[i][alt]
|
||||
radius = min(self.width(), self.height()) / 2.0
|
||||
scale = radius * distance / self.maxrange
|
||||
scale = radius * distance / self.get_range()
|
||||
#convert bearing,distance to x,y
|
||||
xpts = scale * math.sin(bearing * math.pi / 180)
|
||||
ypts = scale * math.cos(bearing * math.pi / 180)
|
||||
#get the bounding rectangle of the arc
|
||||
|
||||
|
||||
arcrect = QtCore.QRectF(QtCore.QPointF(0-scale, 0-scale),
|
||||
QtCore.QPointF(scale, scale))
|
||||
|
||||
@@ -153,44 +150,55 @@ class az_map(QtGui.QWidget):
|
||||
|
||||
self._paths.append(path)
|
||||
|
||||
#this is just to add a little buffer space for showing the ring & range
|
||||
def get_range(self):
|
||||
return int(self.maxrange * 1.1)
|
||||
|
||||
def drawRangeRings(self, painter):
|
||||
painter.translate(self.width()/2, self.height()/2)
|
||||
painter.setPen(az_map.ringpen)
|
||||
for i in range(0, self.maxrange, self.ringsize):
|
||||
diameter = (float(i) / az_map.maxrange) * min(self.width(), self.height())
|
||||
#choose intelligent range step -- keep it between 3-5 rings
|
||||
rangestep = 100
|
||||
while self.get_range() / rangestep < 3:
|
||||
rangestep /= 2.0
|
||||
for i in np.arange(rangestep, self.get_range(), rangestep):
|
||||
diameter = (float(i) / self.get_range()) * min(self.width(), self.height())
|
||||
painter.setPen(az_map.ringpen)
|
||||
painter.drawEllipse(QtCore.QRectF(-diameter / 2.0,
|
||||
-diameter / 2.0, diameter, diameter))
|
||||
painter.setPen(QtGui.QColor(255,127,0,255))
|
||||
|
||||
painter.drawText(0-70/2.0, diameter/2.0, 70, 30, QtCore.Qt.AlignHCenter,
|
||||
"%.1fnm" % i)
|
||||
|
||||
def setMaxRange(self, maxrange):
|
||||
maxrange = max(3.25, maxrange)
|
||||
maxrange = min(500., maxrange)
|
||||
self.maxrange = maxrange
|
||||
self.drawPath()
|
||||
self.repaint()
|
||||
|
||||
def setRingSize(self, ringsize):
|
||||
self.ringsize = ringsize
|
||||
self.drawPath()
|
||||
def wheelEvent(self, event):
|
||||
self.setMaxRange(self.maxrange + (event.delta()/120.)*self.maxrange/4.)
|
||||
|
||||
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):
|
||||
try:
|
||||
msgtype = msg.data["df"]
|
||||
now = time.time()
|
||||
|
||||
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:
|
||||
|
||||
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
|
||||
|
||||
|
||||
21
python/get_uniq.py
Executable file
21
python/get_uniq.py
Executable file
@@ -0,0 +1,21 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
import sys, re
|
||||
|
||||
if __name__== '__main__':
|
||||
data = sys.stdin.readlines()
|
||||
icaos = []
|
||||
num_icaos = 0
|
||||
for line in data:
|
||||
match = re.match(".*from (\w+)", line)
|
||||
if match is not None:
|
||||
icao = int(match.group(1), 16)
|
||||
icaos.append(icao)
|
||||
|
||||
#get dupes
|
||||
dupes = sorted([icao for icao in set(icaos) if icaos.count(icao) > 1])
|
||||
for icao in dupes:
|
||||
print "%x" % icao
|
||||
print "Found non-unique replies from %i aircraft" % len(dupes)
|
||||
|
||||
|
||||
@@ -131,7 +131,7 @@ class dashboard_data_model(QtCore.QAbstractTableModel):
|
||||
self.lock.release()
|
||||
self.prune()
|
||||
|
||||
#weeds out ICAOs older than 5 minutes
|
||||
#weeds out ICAOs older than 1 minute
|
||||
def prune(self):
|
||||
self.lock.acquire()
|
||||
for (index,row) in enumerate(self._data):
|
||||
|
||||
129
python/html_template.py
Normal file
129
python/html_template.py
Normal file
@@ -0,0 +1,129 @@
|
||||
#!/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: blue;
|
||||
background-color: white;
|
||||
font-family: "Lucida Grande", "Arial", sans-serif;
|
||||
font-size: 13px;
|
||||
font-weight: bold;
|
||||
text-align: center;
|
||||
width: 70px;
|
||||
border: none;
|
||||
white-space: nowrap;
|
||||
}
|
||||
</style>
|
||||
<script type="text/javascript" src="http://maps.google.com/maps/api/js?sensor=false">
|
||||
</script>
|
||||
<script type="text/javascript" src="http://google-maps-utility-library-v3.googlecode.com/svn/tags/markerwithlabel/1.1.9/src/markerwithlabel.js">
|
||||
</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)
|
||||
};
|
||||
|
||||
if (airplanes[airplane].ident.length != 8) {
|
||||
identstr = airplane;
|
||||
} else {
|
||||
identstr = airplanes[airplane].ident;
|
||||
};
|
||||
|
||||
var planeOptions = {
|
||||
map: map,
|
||||
position: airplanes[airplane].center,
|
||||
icon: plane_icon,
|
||||
labelContent: identstr,
|
||||
labelAnchor: new google.maps.Point(35, -32),
|
||||
labelClass: "labels",
|
||||
labelStyle: {opacity: 0.75}
|
||||
};
|
||||
planeMarker = new MarkerWithLabel(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)
|
||||
100
python/kml.py
100
python/kml.py
@@ -30,7 +30,7 @@ class output_kml(threading.Thread):
|
||||
self.my_coords = localpos
|
||||
self._timeout = timeout
|
||||
self._lock = lock
|
||||
|
||||
|
||||
self.shutdown = threading.Event()
|
||||
self.finished = threading.Event()
|
||||
self.setDaemon(1)
|
||||
@@ -39,9 +39,9 @@ class output_kml(threading.Thread):
|
||||
def run(self):
|
||||
self._db = sqlite3.connect(self._dbname) #read from the db
|
||||
while self.shutdown.is_set() is False:
|
||||
self.writekml()
|
||||
time.sleep(self._timeout)
|
||||
|
||||
self.writekml()
|
||||
|
||||
self._db.close()
|
||||
self._db = None
|
||||
self.finished.set()
|
||||
@@ -52,8 +52,8 @@ class output_kml(threading.Thread):
|
||||
#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:
|
||||
@@ -64,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
|
||||
@@ -91,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>"""
|
||||
@@ -101,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
|
||||
@@ -124,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)
|
||||
|
||||
@@ -162,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, )
|
||||
|
||||
@@ -170,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', '-1 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
|
||||
|
||||
87
python/mlat_client.py
Normal file
87
python/mlat_client.py
Normal file
@@ -0,0 +1,87 @@
|
||||
#!/usr/bin/env python
|
||||
#
|
||||
# Copyright 2012 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.
|
||||
#
|
||||
|
||||
#multilateration client
|
||||
#outputs stamps to server, receives multilaterated outputs back
|
||||
|
||||
import socket, pickle, time, sys
|
||||
import air_modes
|
||||
from gnuradio import gr
|
||||
|
||||
pickle_prot = 0
|
||||
#pickle_prot = pickle.HIGHEST_PROTOCOL
|
||||
|
||||
class client_info:
|
||||
def __init__(self):
|
||||
self.name = ""
|
||||
self.position = []
|
||||
self.offset_secs = 0
|
||||
self.offset_frac_secs = 0.0
|
||||
self.time_source = None
|
||||
|
||||
class mlat_client:
|
||||
def __init__(self, queue, position, server_addr, time_source):
|
||||
self._queue = queue
|
||||
self._pos = position
|
||||
self._name = socket.gethostname()
|
||||
#connect to server
|
||||
self._sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
self._sock.setblocking(1)
|
||||
self._sock.connect((server_addr, 19005))
|
||||
info = client_info()
|
||||
info.name = self._name
|
||||
info.position = self._pos
|
||||
info.time_source = time_source #"gpsdo" or None
|
||||
self._sock.send(pickle.dumps(info))
|
||||
reply = self._sock.recv(1024)
|
||||
if reply != "HELO": #i know, shut up
|
||||
raise Exception("Invalid reply from server: %s" % reply)
|
||||
self._sock.setblocking(0)
|
||||
self._remnant = None
|
||||
|
||||
def __del__(self):
|
||||
self._sock.close()
|
||||
|
||||
#send a stamped report to the server
|
||||
def output(self, message):
|
||||
self._sock.send(message+"\n")
|
||||
|
||||
#this is called from the update() method list of the main app thread
|
||||
def get_mlat_positions(self):
|
||||
msg = None
|
||||
try:
|
||||
msg = self._sock.recv(1024)
|
||||
except socket.error:
|
||||
pass
|
||||
if msg:
|
||||
for line in msg.splitlines(True):
|
||||
if line.endswith("\n"):
|
||||
if self._remnant:
|
||||
line = self._remnant + line
|
||||
self._remnant = None
|
||||
self._queue.insert_tail(gr.message_from_string(line))
|
||||
|
||||
else:
|
||||
if self._remnant is not None:
|
||||
raise Exception("Malformed data: " + line)
|
||||
else:
|
||||
self._remnant = line
|
||||
1
python/mlat_types.py
Normal file
1
python/mlat_types.py
Normal file
@@ -0,0 +1 @@
|
||||
|
||||
@@ -28,9 +28,10 @@ import math
|
||||
#TODO get rid of class and convert to functions
|
||||
#no need for class here
|
||||
class output_print:
|
||||
def __init__(self, cpr, publisher):
|
||||
def __init__(self, cpr, publisher, callback=None):
|
||||
self._cpr = cpr
|
||||
#sub to every function that starts with "print"
|
||||
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))
|
||||
@@ -41,6 +42,12 @@ class output_print:
|
||||
def prefix(msg):
|
||||
return "(%i %.8f) " % (msg.rssi, msg.timestamp)
|
||||
|
||||
def _print(self, msg):
|
||||
if self._callback is None:
|
||||
print msg
|
||||
else:
|
||||
self._callback(msg)
|
||||
|
||||
def catch_nohandler(self, msg):
|
||||
if msg.data.get_type() not in self._fns:
|
||||
retstr = output_print.prefix(msg)
|
||||
@@ -49,7 +56,7 @@ class output_print:
|
||||
retstr += " from %.6x" % msg.ecc
|
||||
else:
|
||||
retstr += " from %.6x" % msg.data["aa"]
|
||||
print retstr
|
||||
self._print(retstr)
|
||||
|
||||
def handle0(self, msg):
|
||||
try:
|
||||
@@ -77,7 +84,7 @@ class output_print:
|
||||
if msg.data["vs"] is 1:
|
||||
retstr += " (aircraft is on the ground)"
|
||||
|
||||
print retstr
|
||||
self._print(retstr)
|
||||
|
||||
@staticmethod
|
||||
def fs_text(fs):
|
||||
@@ -101,7 +108,7 @@ class output_print:
|
||||
retstr += output_print.fs_text(msg.data["fs"])
|
||||
except ADSBError:
|
||||
return
|
||||
print retstr
|
||||
self._print(retstr)
|
||||
|
||||
def handle5(self, msg):
|
||||
try:
|
||||
@@ -110,7 +117,7 @@ class output_print:
|
||||
retstr += output_print.fs_text(msg.data["fs"])
|
||||
except ADSBError:
|
||||
return
|
||||
print retstr
|
||||
self._print(retstr)
|
||||
|
||||
def handle11(self, msg):
|
||||
try:
|
||||
@@ -118,7 +125,7 @@ class output_print:
|
||||
retstr += "Type 11 (all call reply) from %x in reply to interrogator %i with capability level %i" % (msg.data["aa"], msg.ecc & 0xF, msg.data["ca"]+1)
|
||||
except ADSBError:
|
||||
return
|
||||
print retstr
|
||||
self._print(retstr)
|
||||
|
||||
#the only one which requires state
|
||||
def handle17(self, msg):
|
||||
@@ -157,20 +164,20 @@ class output_print:
|
||||
[mag_hdg, vel_src, vel, vert_spd, geo_diff] = air_modes.parseBDS09_3(msg.data)
|
||||
retstr += "Type 17 BDS0,9-%i (air course report) from %x with %s %.0fkt magnetic heading %.0f VS %.0f geo. diff. from baro. alt. %.0fft" \
|
||||
% (subtype, icao24, vel_src, vel, mag_hdg, vert_spd, geo_diff)
|
||||
|
||||
|
||||
else:
|
||||
retstr += "Type 17 BDS0,9-%i from %x not implemented" % (subtype, icao24)
|
||||
|
||||
elif bdsreg == 0x62:
|
||||
emerg_str = air_modes.parseBDS62(data)
|
||||
retstr += "Type 17 BDS6,2 (emergency) from %x type %s" % (icao24, emerg_str)
|
||||
|
||||
|
||||
else:
|
||||
retstr += "Type 17 with FTC=%i from %x not implemented" % (msg.data["ftc"], icao24)
|
||||
except ADSBError:
|
||||
return
|
||||
|
||||
print retstr
|
||||
self._print(retstr)
|
||||
|
||||
def printTCAS(self, msg):
|
||||
msgtype = msg.data["df"]
|
||||
@@ -222,8 +229,8 @@ class output_print:
|
||||
retstr += " at %ift" % air_modes.decode_alt(msg.data["ac"], True)
|
||||
else:
|
||||
retstr += " ident %x" % air_modes.decode_id(msg.data["id"])
|
||||
|
||||
print retstr
|
||||
|
||||
self._print(retstr)
|
||||
|
||||
handle16 = printTCAS
|
||||
handle20 = printTCAS
|
||||
|
||||
@@ -427,7 +427,7 @@ def make_parser(pub):
|
||||
try:
|
||||
ret = air_modes.modes_report(modes_reply(int(data, 16)),
|
||||
int(ecc, 16),
|
||||
20.0*math.log10(float(reference)),
|
||||
10.0*math.log10(max(1e-8,float(reference))),
|
||||
air_modes.stamp(0, float(timestamp)))
|
||||
pub["modes_dl"] = ret
|
||||
pub["type%i_dl" % ret.data.get_type()] = ret
|
||||
|
||||
@@ -23,7 +23,8 @@
|
||||
# You pass it options, it gives you data.
|
||||
# It uses the pubsub interface to allow clients to subscribe to its data feeds.
|
||||
|
||||
from gnuradio import gr, gru, optfir, eng_notation, blks2
|
||||
from gnuradio import gr, gru, eng_notation, filter, blocks
|
||||
from gnuradio.filter import optfir
|
||||
from gnuradio.eng_option import eng_option
|
||||
from gnuradio.gr.pubsub import pubsub
|
||||
from optparse import OptionParser, OptionGroup
|
||||
@@ -32,6 +33,7 @@ import zmq
|
||||
import threading
|
||||
import time
|
||||
import re
|
||||
import fractions
|
||||
|
||||
class modes_radio (gr.top_block, pubsub):
|
||||
def __init__(self, options, context):
|
||||
@@ -43,8 +45,11 @@ class modes_radio (gr.top_block, pubsub):
|
||||
|
||||
self._resample = None
|
||||
self._setup_source(options)
|
||||
if self._resample is not None:
|
||||
self._rate = 4.0e6 #fixed rate we resample to in RTL case
|
||||
|
||||
self._rx_path = air_modes.rx_path(self._rate, options.threshold, self._queue, options.pmf)
|
||||
self._rx_path = air_modes.rx_path(self._rate, options.threshold,
|
||||
self._queue, options.pmf, options.dcblock)
|
||||
|
||||
#now subscribe to set various options via pubsub
|
||||
self.subscribe("freq", self.set_freq)
|
||||
@@ -79,7 +84,7 @@ class modes_radio (gr.top_block, pubsub):
|
||||
@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]")
|
||||
@@ -101,33 +106,42 @@ class modes_radio (gr.top_block, pubsub):
|
||||
#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,
|
||||
group.add_option("-T", "--threshold", type="eng_float", default=7.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]")
|
||||
group.add_option("-d","--dcblock", action="store_true", default=False,
|
||||
help="Use a DC blocking filter (best for HackRF Jawbreaker) [default=%default]")
|
||||
|
||||
parser.add_option_group(group)
|
||||
|
||||
def live_source(self):
|
||||
return self._options.source is 'uhd' or self._options.source is 'osmocom'
|
||||
return self._options.source=="uhd" or self._options.source=="osmocom"
|
||||
|
||||
def set_freq(self, freq):
|
||||
return self._u.set_center_freq(freq, 0) if live_source() else 0
|
||||
return self._u.set_center_freq(freq, 0) if self.live_source() else 0
|
||||
|
||||
def set_gain(self, gain):
|
||||
return self._u.set_gain(gain) if live_source() else 0
|
||||
if self.live_source():
|
||||
self._u.set_gain(gain)
|
||||
print "Gain is %f" % self.get_gain()
|
||||
return self.get_gain()
|
||||
|
||||
def set_rate(self, rate):
|
||||
return self._u.set_rate(rate) if live_source() else 0
|
||||
self._rx_path.set_rate(rate)
|
||||
return self._u.set_rate(rate) if self.live_source() else 0
|
||||
|
||||
def set_threshold(self, threshold):
|
||||
self._rx_path.set_threshold(threshold)
|
||||
|
||||
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
|
||||
return self._u.get_center_freq(freq, 0) if self.live_source() else 1090e6
|
||||
|
||||
def get_rate(self, rate):
|
||||
return self._u.get_rate() if live_source() else self._rate
|
||||
def get_gain(self):
|
||||
return self._u.get_gain() if self.live_source() else 0
|
||||
|
||||
def get_rate(self):
|
||||
return self._u.get_rate() if self.live_source() else self._rate
|
||||
|
||||
def _setup_source(self, options):
|
||||
if options.source == "uhd":
|
||||
@@ -161,33 +175,32 @@ class modes_radio (gr.top_block, pubsub):
|
||||
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 = osmosdr.source(options.args)
|
||||
rates = self._u.get_sample_rates()
|
||||
|
||||
self._u.set_sample_rate(options.rate)
|
||||
actual_rate = int(self._u.get_sample_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
|
||||
# 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)
|
||||
self._u.set_gain(options.gain)
|
||||
print "Gain is %i" % self._u.get_gain()
|
||||
|
||||
#Note: this should only come into play if using an RTLSDR.
|
||||
# lpfiltcoeffs = gr.firdes.low_pass(1, 5*3.2e6, 1.6e6, 300e3)
|
||||
# self._resample = blks2.rational_resampler_ccf(interpolation=5, decimation=4, taps=lpfiltcoeffs)
|
||||
|
||||
if actual_rate < 4.0e6:
|
||||
gcd = fractions.gcd(4.0e6, actual_rate)
|
||||
interp = 4.0e6 / gcd
|
||||
decim = actual_rate / gcd
|
||||
lpfiltcoeffs = filter.firdes.low_pass(1, interp*actual_rate, 1.6e6, 300e3)
|
||||
self._resample = filter.rational_resampler_ccf(interpolation=interp,
|
||||
decimation=decim,
|
||||
taps=lpfiltcoeffs)
|
||||
|
||||
else:
|
||||
#semantically detect whether it's ip.ip.ip.ip:port or filename
|
||||
if ':' in options.source:
|
||||
@@ -198,10 +211,11 @@ class modes_radio (gr.top_block, pubsub):
|
||||
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)
|
||||
self._u = blocks.file_source(gr.sizeof_gr_complex, options.source)
|
||||
print "Using file source %s" % options.source
|
||||
|
||||
print "Rate is %i" % (options.rate,)
|
||||
print "Rate is %i" % actual_rate
|
||||
|
||||
def close(self):
|
||||
self._sender.close()
|
||||
self._u = None
|
||||
|
||||
@@ -19,12 +19,12 @@
|
||||
# Boston, MA 02110-1301, USA.
|
||||
#
|
||||
|
||||
from gnuradio import gr
|
||||
from gnuradio import gr, blocks, filter
|
||||
import air_modes_swig
|
||||
|
||||
class rx_path(gr.hier_block2):
|
||||
|
||||
def __init__(self, rate, threshold, queue, use_pmf=False):
|
||||
def __init__(self, rate, threshold, queue, use_pmf=False, use_dcblock=False):
|
||||
gr.hier_block2.__init__(self, "modes_rx_path",
|
||||
gr.io_signature(1, 1, gr.sizeof_gr_complex),
|
||||
gr.io_signature(0,0,0))
|
||||
@@ -35,37 +35,43 @@ 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._bb = self._demod
|
||||
self._demod = blocks.complex_to_mag_squared()
|
||||
if use_dcblock:
|
||||
self._dcblock = filter.dc_blocker_cc(100*self._spc,True)
|
||||
self.connect(self, self._dcblock, self._demod)
|
||||
else:
|
||||
self.connect(self, self._demod)
|
||||
self._dcblock = None
|
||||
|
||||
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)
|
||||
self._sync = air_modes_swig.preamble(self._rate, self._threshold)
|
||||
|
||||
# Slice Mode-S bits and send to message queue
|
||||
self._slicer = air_modes_swig.modes_slicer(self._rate, self._queue)
|
||||
self._slicer = air_modes_swig.slicer(self._queue)
|
||||
|
||||
# Wire up the flowgraph
|
||||
self.connect(self, self._demod)
|
||||
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)
|
||||
if self._dcblock is not None:
|
||||
self._dcblock.set_length(100*self._spc)
|
||||
|
||||
def set_threshold(self, threshold):
|
||||
self._sync.set_threshold(threshold)
|
||||
@@ -79,4 +85,4 @@ class rx_path(gr.hier_block2):
|
||||
|
||||
def get_threshold(self, threshold):
|
||||
return self._sync.get_threshold()
|
||||
|
||||
|
||||
|
||||
@@ -74,13 +74,13 @@ class output_sql:
|
||||
#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.execute(query)
|
||||
c.close()
|
||||
# self._db.commit()
|
||||
self._db.commit()
|
||||
|
||||
except ADSBError:
|
||||
pass
|
||||
|
||||
@@ -316,6 +316,19 @@
|
||||
<string>Use Pulse Matched Filtering</string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QCheckBox" name="check_dcblock">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>10</x>
|
||||
<y>220</y>
|
||||
<width>221</width>
|
||||
<height>22</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Use DC blocking filter</string>
|
||||
</property>
|
||||
</widget>
|
||||
</widget>
|
||||
<widget class="QGroupBox" name="group_output">
|
||||
<property name="geometry">
|
||||
@@ -835,12 +848,12 @@
|
||||
<rect>
|
||||
<x>483</x>
|
||||
<y>125</y>
|
||||
<width>31</width>
|
||||
<width>51</width>
|
||||
<height>17</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>ft/s</string>
|
||||
<string>ft/min</string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QLabel" name="label_31">
|
||||
@@ -856,7 +869,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 +878,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 +914,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 +923,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 +941,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 +1039,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 +1050,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)
|
||||
|
||||
@@ -1,45 +1,17 @@
|
||||
/* -*- c++ -*- */
|
||||
|
||||
%include "gnuradio.i" // the common stuff
|
||||
#define AIR_MODES_API
|
||||
|
||||
%include "gnuradio.i"
|
||||
|
||||
%{
|
||||
#include "air_modes_preamble.h"
|
||||
#include "air_modes_slicer.h"
|
||||
#include <gr_msg_queue.h>
|
||||
#include "gr_air_modes/preamble.h"
|
||||
#include "gr_air_modes/slicer.h"
|
||||
%}
|
||||
|
||||
// ----------------------------------------------------------------
|
||||
%include "gr_air_modes/preamble.h"
|
||||
%include "gr_air_modes/slicer.h"
|
||||
|
||||
/*
|
||||
* First arg is the package prefix.
|
||||
* Second arg is the name of the class minus the prefix.
|
||||
*
|
||||
* This does some behind-the-scenes magic so we can
|
||||
* access howto_square_ff from python as howto.square_ff
|
||||
*/
|
||||
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
|
||||
{
|
||||
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);
|
||||
|
||||
class air_modes_slicer : public gr_block
|
||||
{
|
||||
set_rate(int channel_rate);
|
||||
private:
|
||||
air_modes_slicer (int channel_rate, gr_msg_queue_sptr queue);
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------
|
||||
GR_SWIG_BLOCK_MAGIC2(air_modes,preamble);
|
||||
GR_SWIG_BLOCK_MAGIC2(air_modes,slicer);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user