Compare commits

..

3 Commits

Author SHA1 Message Date
Nick Foster
c2d0ca6051 Temp commit. Not yet working. 2013-07-21 17:57:37 -07:00
Nick Foster
33349efd7f GUI interface still not quite working. Two issues:
* PyQt4.QtSql's SQLite interface doesn't appear to return the same results as the SQLite browser. This is probably me depending on a bad data ordering assumption.
* QSqlQueryModel isn't set up to have the db change from underneath it, AFAIK -- have to add the ability to notify it there's new data.
2013-07-21 17:41:26 -07:00
Nick Foster
dd3e1fe629 GUI model ripped up and replaced with QSqlQueryModel. Not working but committing before I kill the child in row 15. 2013-07-21 17:41:26 -07:00
62 changed files with 2751 additions and 1938 deletions

View File

@ -21,16 +21,12 @@
########################################################################
# Project setup
########################################################################
cmake_minimum_required(VERSION 3.8)
project(gr-air-modes CXX C)
cmake_minimum_required(VERSION 2.6)
project(gr-gr-air-modes CXX)
set(gr-gr-air-modes_VERSION_MAJOR 0)
set(gr-gr-air-modes_VERSION_MINOR 0)
enable_testing()
#install to PyBOMBS target prefix if defined
if(DEFINED ENV{PYBOMBS_PREFIX})
set(CMAKE_INSTALL_PREFIX $ENV{PYBOMBS_PREFIX})
message(STATUS "PyBOMBS installed GNU Radio. Setting CMAKE_INSTALL_PREFIX to $ENV{PYBOMBS_PREFIX}")
endif()
#select the release build type by default to get optimization flags
if(NOT CMAKE_BUILD_TYPE)
set(CMAKE_BUILD_TYPE "Release")
@ -38,91 +34,64 @@ if(NOT CMAKE_BUILD_TYPE)
endif(NOT CMAKE_BUILD_TYPE)
set(CMAKE_BUILD_TYPE ${CMAKE_BUILD_TYPE} CACHE STRING "")
#make sure our local CMake Modules path comes first
list(INSERT CMAKE_MODULE_PATH 0 ${CMAKE_SOURCE_DIR}/cmake/Modules)
# Set the version information here
set(VERSION_MAJOR 1)
set(VERSION_API 0)
set(VERSION_ABI 0)
set(VERSION_PATCH git)
# Set cmake policies.
# This will suppress developer warnings during the cmake process that can occur
# if a newer cmake version than the minimum is used.
cmake_policy(SET CMP0011 NEW)
# Enable generation of compile_commands.json for code completion engines
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
list(APPEND CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/cmake/Modules)
########################################################################
# Compiler specific setup
########################################################################
if((CMAKE_CXX_COMPILER_ID MATCHES "Clang" OR
CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
AND NOT WIN32)
if(CMAKE_COMPILER_IS_GNUCXX AND NOT WIN32)
#http://gcc.gnu.org/wiki/Visibility
add_definitions(-fvisibility=hidden)
endif()
IF(CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
SET(CMAKE_CXX_STANDARD 11)
ELSEIF(CMAKE_CXX_COMPILER_ID MATCHES "Clang")
SET(CMAKE_CXX_STANDARD 11)
ELSEIF(CMAKE_CXX_COMPILER_ID STREQUAL "MSVC")
SET(CMAKE_CXX_STANDARD 11)
ELSE()
message(WARNING "C++ standard could not be set because compiler is not GNU, Clang or MSVC.")
ENDIF()
########################################################################
# Find boost
########################################################################
include(GrBoost)
IF(CMAKE_C_COMPILER_ID STREQUAL "GNU")
SET(CMAKE_C_STANDARD 11)
ELSEIF(CMAKE_C_COMPILER_ID MATCHES "Clang")
SET(CMAKE_C_STANDARD 11)
ELSEIF(CMAKE_C_COMPILER_ID STREQUAL "MSVC")
SET(CMAKE_C_STANDARD 11)
ELSE()
message(WARNING "C standard could not be set because compiler is not GNU, Clang or MSVC.")
ENDIF()
########################################################################
# Install directories
########################################################################
include(GrPlatform) #define LIB_SUFFIX
set(GR_RUNTIME_DIR bin)
set(GR_LIBRARY_DIR lib${LIB_SUFFIX})
set(GR_INCLUDE_DIR include)
set(GR_DATA_DIR share)
set(GR_PKG_DATA_DIR ${GR_DATA_DIR}/${CMAKE_PROJECT_NAME})
set(GR_DOC_DIR ${GR_DATA_DIR}/doc)
set(GR_PKG_DOC_DIR ${GR_DOC_DIR}/${CMAKE_PROJECT_NAME})
set(GR_CONF_DIR etc)
set(GR_PKG_CONF_DIR ${GR_CONF_DIR}/${CMAKE_PROJECT_NAME}/conf.d)
set(GR_LIBEXEC_DIR libexec)
set(GR_PKG_LIBEXEC_DIR ${GR_LIBEXEC_DIR}/${CMAKE_PROJECT_NAME})
set(GRC_BLOCKS_DIR ${GR_PKG_DATA_DIR}/grc/blocks)
########################################################################
# Find gnuradio build dependencies
########################################################################
find_package(Gnuradio "3.8" REQUIRED)
include(GrVersion)
find_package(GnuradioRuntime)
include(GrPlatform) #define LIB_SUFFIX
if(NOT CMAKE_MODULES_DIR)
set(CMAKE_MODULES_DIR lib${LIB_SUFFIX}/cmake)
endif(NOT CMAKE_MODULES_DIR)
set(GR_INCLUDE_DIR include/gr_air_modes)
set(GR_CMAKE_DIR ${CMAKE_MODULES_DIR}/${CMAKE_PROJECT_NAME})
set(GR_PKG_DATA_DIR ${GR_DATA_DIR}/${CMAKE_PROJECT_NAME})
set(GR_PKG_DOC_DIR ${GR_DOC_DIR}/${CMAKE_PROJECT_NAME})
set(GR_PKG_CONF_DIR ${GR_CONF_DIR}/${CMAKE_PROJECT_NAME}/conf.d)
set(GR_PKG_LIBEXEC_DIR ${GR_LIBEXEC_DIR}/${CMAKE_PROJECT_NAME})
if(NOT GNURADIO_RUNTIME_FOUND)
message(FATAL_ERROR "GnuRadio Runtime required to compile gr-air-modes")
endif()
########################################################################
# On Apple only, set install name and use rpath correctly, if not already set
# Setup the include and linker paths
########################################################################
if(APPLE)
if(NOT CMAKE_INSTALL_NAME_DIR)
set(CMAKE_INSTALL_NAME_DIR
${CMAKE_INSTALL_PREFIX}/${GR_LIBRARY_DIR} CACHE
PATH "Library Install Name Destination Directory" FORCE)
endif(NOT CMAKE_INSTALL_NAME_DIR)
if(NOT CMAKE_INSTALL_RPATH)
set(CMAKE_INSTALL_RPATH
${CMAKE_INSTALL_PREFIX}/${GR_LIBRARY_DIR} CACHE
PATH "Library Install RPath" FORCE)
endif(NOT CMAKE_INSTALL_RPATH)
if(NOT CMAKE_BUILD_WITH_INSTALL_RPATH)
set(CMAKE_BUILD_WITH_INSTALL_RPATH ON CACHE
BOOL "Do Build Using Library Install RPath" FORCE)
endif(NOT CMAKE_BUILD_WITH_INSTALL_RPATH)
endif(APPLE)
include_directories(
${CMAKE_SOURCE_DIR}/include
${Boost_INCLUDE_DIRS}
${GNURADIO_RUNTIME_INCLUDE_DIRS}
)
link_directories(
${Boost_LIBRARY_DIRS}
${GNURADIO_RUNTIME_LIBRARY_DIRS}
)
# Set component parameters
set(GR_GR-AIR-MODES_INCLUDE_DIRS ${CMAKE_CURRENT_SOURCE_DIR}/include CACHE INTERNAL "" FORCE)
set(GR_GR-AIR-MODES_SWIG_INCLUDE_DIRS ${CMAKE_CURRENT_SOURCE_DIR}/swig CACHE INTERNAL "" FORCE)
########################################################################
# Create uninstall target
@ -139,7 +108,7 @@ add_custom_target(uninstall
########################################################################
# Add subdirectories
########################################################################
add_subdirectory(include/gr_air_modes)
add_subdirectory(include)
add_subdirectory(lib)
add_subdirectory(swig)
add_subdirectory(python)

View File

@ -1,13 +0,0 @@
FROM bistromath/gnuradio:v3.8
ENV num_threads 10
MAINTAINER bistromath@gmail.com version: 0.1
WORKDIR /opt
RUN apt install -y python3-zmq python3-scipy
RUN mkdir gr-air-modes
COPY . gr-air-modes/
WORKDIR /opt/gr-air-modes
RUN mkdir build && cd build && cmake ../ && make -j${num_threads} && make install && ldconfig

1
README
View File

@ -97,7 +97,6 @@ gr-air-modes requires:
* Python >= 2.5 (written for Python 2.7, Python 3.0 might work)
** NumPy and SciPy are required for the FlightGear output plugin.
* PyZMQ
* Gnuradio >= 3.5.0
* Ettus UHD >= 3.4.0 for use with USRPs
* osmosdr (any version) for use with RTLSDR dongles

View File

@ -19,7 +19,7 @@
# Boston, MA 02110-1301, USA.
#
import os, sys, time, threading, datetime, math, csv, tempfile, ConfigParser
import os, sys, time, threading, datetime, math, csv, tempfile
from optparse import OptionParser
from PyQt4 import QtCore,QtGui,QtWebKit
from PyQt4.Qwt5 import Qwt
@ -30,7 +30,6 @@ import air_modes
from air_modes.exceptions import *
from air_modes.modes_rx_ui import Ui_MainWindow
from air_modes.gui_model import *
from air_modes.az_map import *
import sqlite3
import zmq
@ -49,48 +48,28 @@ 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(defaults["gain"])
self.ui.line_gain.insert("30")
#default to 5dB
self.ui.line_threshold.insert(defaults["threshold"])
self.ui.line_threshold.insert("5")
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.prog_rssi.setMinimum(0)
self.ui.prog_rssi.setMaximum(40)
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"]))
self.ui.combo_ant.setCurrentIndex(self.ui.combo_ant.findText("RX2"))
#check KML by default, leave the rest unchecked.
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.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.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")
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")
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"])
if defaults["apikey"] is not None:
self.ui.line_my_api_key.insert(defaults["apikey"])
#disable by default
self.ui.check_adsbonly.setCheckState(QtCore.Qt.Unchecked)
@ -98,16 +77,16 @@ class mainwindow(QtGui.QMainWindow):
self.queue = gr.msg_queue(10)
self.running = False
self.kmlgen = None #necessary bc we stop its thread in shutdown
self.dbname = "air_modes.db"
self.dbname = "adsb.db"
self.num_reports = 0
self.last_report = 0
self.context = zmq.Context(1)
self.datamodel = dashboard_data_model(None)
self.datamodel = dashboard_sql_model(None)
self.ui.list_aircraft.setModel(self.datamodel)
self.ui.list_aircraft.setModelColumn(0)
self.az_model = air_modes.az_map.az_map_model(None)
self.az_model = air_modes.az_map_model(None)
self.ui.azimuth_map.setModel(self.az_model)
#set up dashboard views
@ -117,15 +96,16 @@ class mainwindow(QtGui.QMainWindow):
self.dashboard_mapper.setModel(self.datamodel)
self.dashboard_mapper.addMapping(self.ui.line_icao, 0)
#self.dashboard_mapper.addMapping(self.ui.prog_rssi, 2)
self.dashboard_mapper.addMapping(self.ui.line_latitude, 3)
self.dashboard_mapper.addMapping(self.ui.line_longitude, 4)
self.dashboard_mapper.addMapping(self.ui.line_alt, 5)
self.dashboard_mapper.addMapping(self.ui.line_speed, 6)
#self.dashboard_mapper.addMapping(self.ui.compass_heading, 7)
self.dashboard_mapper.addMapping(self.ui.line_climb, 8)
self.dashboard_mapper.addMapping(self.ui.line_ident, 9)
self.dashboard_mapper.addMapping(self.ui.line_type, 10)
self.dashboard_mapper.addMapping(self.ui.line_range, 11)
self.dashboard_mapper.addMapping(self.ui.line_latitude, 2)
self.dashboard_mapper.addMapping(self.ui.line_longitude, 3)
self.dashboard_mapper.addMapping(self.ui.line_alt, 4)
self.dashboard_mapper.addMapping(self.ui.line_speed, 5)
#self.dashboard_mapper.addMapping(self.ui.compass_heading, 6)
self.dashboard_mapper.addMapping(self.ui.line_climb, 7)
self.dashboard_mapper.addMapping(self.ui.line_ident, 8)
self.dashboard_mapper.addMapping(self.ui.line_type, 9)
#self.dashboard_mapper.addMapping(self.ui.line_range, 11)
#self.dashboard_mapper.addMapping(self.ui.compass_bearing, 12)
compass_palette = QtGui.QPalette()
compass_palette.setColor(QtGui.QPalette.Foreground, QtCore.Qt.white)
@ -135,7 +115,8 @@ class mainwindow(QtGui.QMainWindow):
self.ui.compass_heading.setNeedle(Qwt.QwtDialSimpleNeedle(Qwt.QwtDialSimpleNeedle.Ray, False, QtCore.Qt.black))
self.ui.compass_bearing.setNeedle(Qwt.QwtDialSimpleNeedle(Qwt.QwtDialSimpleNeedle.Ray, False, QtCore.Qt.black))
#hook up the update signal
#hook up the update signals which are explicitly necessary
#most of the dashboard_mapper and list_aircraft stuff is implicitly done already
self.ui.list_aircraft.selectionModel().currentRowChanged.connect(self.dashboard_mapper.setCurrentModelIndex)
self.ui.list_aircraft.selectionModel().currentRowChanged.connect(self.update_heading_widget)
self.ui.list_aircraft.selectionModel().currentRowChanged.connect(self.update_bearing_widget)
@ -143,51 +124,33 @@ class mainwindow(QtGui.QMainWindow):
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:
heading = index.model().data(index.model().index(index.row(), self.datamodel._colnames.index("heading"))).toDouble()[0]
heading = index.model().data(index.model().index(index.row(), 6)).toDouble()[0]
self.ui.compass_heading.setValue(heading)
def update_bearing_widget(self, index):
if index.model() is not None:
bearing = index.model().data(index.model().index(index.row(), self.datamodel._colnames.index("bearing"))).toDouble()[0]
bearing = 0#index.model().data(index.model().index(index.row(), 12)).toDouble()[0]
self.ui.compass_bearing.setValue(bearing)
def unmapped_widgets_dataChanged(self, startIndex, endIndex):
index = self.ui.list_aircraft.selectionModel().currentIndex()
if index.row() in range(startIndex.row(), endIndex.row()+1): #the current aircraft was affected
if self.datamodel._colnames.index("heading") in range(startIndex.column(), endIndex.column()+1):
if 6 in range(startIndex.column(), endIndex.column()+1):
self.update_heading_widget(index)
if self.datamodel._colnames.index("bearing") in range(startIndex.column(), endIndex.column()+1):
if 12 in range(startIndex.column(), endIndex.column()+1):
self.update_bearing_widget(index)
if self.datamodel._colnames.index("rssi") in range(startIndex.column(), endIndex.column()+1):
if 2 in range(startIndex.column(), endIndex.column()+1):
self.update_rssi_widget(index)
def update_rssi_widget(self, index):
if index.model() is not None:
rssi = index.model().data(index.model().index(index.row(), 2)).toDouble()[0]
rssi = 0#index.model().data(index.model().index(index.row(), 2)).toDouble()[0]
self.ui.prog_rssi.setValue(rssi)
def increment_reportspersec(self, msg):
@ -235,10 +198,8 @@ class mainwindow(QtGui.QMainWindow):
elif sourceid == "Osmocom":
try:
import osmosdr
self.src = osmosdr.source("")
self.rates = [rate.start() for rate in self.src.get_sample_rates()
if ((rate.start() % 2.e6) == 0)
or (rate.start() < 4.e6 and ((rate.start()%0.2e6) == 0))]
self.src = osmosdr.source_c("")
self.rates = [rate.start() for rate in self.src.get_sample_rates() if (rate.start() % 2.e6) == 0]
self.antennas = ["RX"]
self.src = None
self.ui.combo_ant.setEnabled(False)
@ -267,17 +228,11 @@ class mainwindow(QtGui.QMainWindow):
self.ui.combo_ant.addItems(self.antennas)
#set up recommended sample rate
if len(self.rates) > 1:
if max(self.rates) > 4.e6:
recommended_rate = min(x for x in self.rates if x >= 4e6 and
max(self.rates) % x == 0)
else:
recommended_rate = max(self.rates)
if recommended_rate >= 8.e6:
self.ui.check_pmf.setChecked(True)
else:
self.ui.check_pmf.setChecked(False)
self.ui.combo_rate.setCurrentIndex(self.rates.index(recommended_rate))
recommended_rate = min(x for x in self.rates if x >= 4e6 and
max(self.rates) % x == 0)
if recommended_rate >= 8.e6:
self.ui.check_pmf.setChecked(True)
self.ui.combo_rate.setCurrentIndex(self.rates.index(recommended_rate))
################ action handlers ####################
def on_combo_source_currentIndexChanged(self, index):
@ -306,8 +261,7 @@ 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.isChecked()
options.dcblock = self.ui.check_dcblock.isChecked()
options.pmf = self.ui.check_pmf.checkState()
self._servers = ["inproc://modes-radio-pub"] #TODO ADD REMOTES
self._relay = air_modes.zmq_pubsub_iface(self.context, subaddr=self._servers, pubaddr=None)
@ -324,14 +278,8 @@ class mainwindow(QtGui.QMainWindow):
except:
my_position = None
try:
my_apikey = str(self.ui.line_my_api_key.text())
except:
my_apikey = None
self._cpr_dec = air_modes.cpr_decoder(my_position)
self.datamodelout = dashboard_output(self._cpr_dec, self.datamodel, self._publisher)
# self.datamodelout = dashboard_output(self._cpr_dec, self.datamodel, self._publisher)
self.lock = threading.Lock() #grab a lock to ensure sql and kml don't step on each other
@ -351,7 +299,7 @@ class mainwindow(QtGui.QMainWindow):
#add azimuth map output and hook it up
if my_position is not None:
self.az_map_output = air_modes.az_map.az_map_output(self._cpr_dec, self.az_model, self._publisher)
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)
#set up map
@ -367,12 +315,12 @@ class mainwindow(QtGui.QMainWindow):
#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_apikey, my_position, self._jsonfile.name)
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))
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") ) )
@ -390,39 +338,12 @@ 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
try:
self.prefs["apikey"] = self.ui.line_my_api_key.text()
except:
pass
def on_quit(self):
if self.running is True:
self._radio.close()
self._radio = None
self._relay.close()
self._radio.close()
self._relay = None
self._radio = None
self._rps_timer = None
try:
self.kmlgen.done = True
@ -432,19 +353,9 @@ 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()
@ -455,57 +366,6 @@ class mainwindow(QtGui.QMainWindow):
self.ui.text_livedata.append(msgstr)
self.ui.text_livedata.verticalScrollBar().setSliderPosition(self.ui.text_livedata.verticalScrollBar().maximum())
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
defaults["apikey"] = None
prefs = ConfigParser.ConfigParser(defaults)
prefs.optionxform = str
try:
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)
window = mainwindow()

View File

@ -1,4 +1,4 @@
#!/usr/bin/env python3
#!/usr/bin/env python
# Copyright 2010, 2013 Nick Foster
#
# This file is part of gr-air-modes
@ -23,8 +23,9 @@ from gnuradio.eng_option import eng_option
from gnuradio.gr.pubsub import pubsub
from optparse import OptionParser
import time, os, sys, threading, math
from string import split, join
import air_modes
from air_modes.modes_types import *
from air_modes.types import *
from air_modes.exceptions import *
import zmq
@ -85,9 +86,8 @@ 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:

View File

@ -58,7 +58,7 @@
# the new option.
# E.g. my_install(TARGETS foo DESTINATION OPTIONAL) would result in
# MY_INSTALL_DESTINATION set to "OPTIONAL", but MY_INSTALL_DESTINATION would
# be empty and MY_INSTALL_OPTIONAL would be set to TRUE therefore.
# be empty and MY_INSTALL_OPTIONAL would be set to TRUE therefor.
#=============================================================================
# Copyright 2010 Alexander Neundorf <neundorf@kde.org>

View File

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

24
cmake/Modules/FindPyQt.py Normal file
View File

@ -0,0 +1,24 @@
# Copyright (c) 2007, Simon Edwards <simon@simonzone.com>
# Redistribution and use is allowed according to the terms of the BSD license.
# For details see the accompanying COPYING-CMAKE-SCRIPTS file.
import PyQt4.pyqtconfig
pyqtcfg = PyQt4.pyqtconfig.Configuration()
print("pyqt_version:%06.0x" % pyqtcfg.pyqt_version)
print("pyqt_version_str:%s" % pyqtcfg.pyqt_version_str)
pyqt_version_tag = ""
in_t = False
for item in pyqtcfg.pyqt_sip_flags.split(' '):
if item=="-t":
in_t = True
elif in_t:
if item.startswith("Qt_4"):
pyqt_version_tag = item
else:
in_t = False
print("pyqt_version_tag:%s" % pyqt_version_tag)
print("pyqt_sip_dir:%s" % pyqtcfg.pyqt_sip_dir)
print("pyqt_sip_flags:%s" % pyqtcfg.pyqt_sip_flags)

View File

@ -0,0 +1,61 @@
# Find PyQt4
# ~~~~~~~~~~
# Copyright (c) 2007-2008, Simon Edwards <simon@simonzone.com>
# Copyright (c) 2012, Nicholas Corgan <nick.corgan@ettus.com>
# Redistribution and use is allowed according to the terms of the BSD license.
# For details see the accompanying COPYING-CMAKE-SCRIPTS file.
#
# PyQt4 website: http://www.riverbankcomputing.co.uk/pyqt/index.php
#
# Find the installed version of PyQt4. FindPyQt4 should only be called after
# Python has been found.
#
# This file defines the following variables:
#
# PYQT4_VERSION - The version of PyQt4 found expressed as a 6 digit hex number
# suitable for comparision as a string
#
# PYQT4_VERSION_STR - The version of PyQt4 as a human readable string.
#
# PYQT4_VERSION_TAG - The PyQt version tag using by PyQt's sip files.
#
# PYQT4_SIP_DIR - The directory holding the PyQt4 .sip files.
#
# PYQT4_SIP_FLAGS - The SIP flags used to build PyQt.
IF(EXISTS PYQT4_VERSION AND EXISTS PYUIC4_EXECUTABLE)
# Already in cache, be silent
SET(PYQT4_FOUND TRUE)
SET(PYUIC4_FOUND TRUE)
ELSE(EXISTS PYQT4_VERSION AND EXISTS PYUIC4_EXECUTABLE)
FIND_FILE(_find_pyqt_py FindPyQt.py PATHS ${CMAKE_MODULE_PATH})
EXECUTE_PROCESS(COMMAND ${PYTHON_EXECUTABLE} ${_find_pyqt_py} OUTPUT_VARIABLE pyqt_config)
IF(pyqt_config)
STRING(REGEX REPLACE "^pyqt_version:([^\n]+).*$" "\\1" PYQT4_VERSION ${pyqt_config})
STRING(REGEX REPLACE ".*\npyqt_version_str:([^\n]+).*$" "\\1" PYQT4_VERSION_STR ${pyqt_config})
STRING(REGEX REPLACE ".*\npyqt_version_tag:([^\n]+).*$" "\\1" PYQT4_VERSION_TAG ${pyqt_config})
STRING(REGEX REPLACE ".*\npyqt_sip_dir:([^\n]+).*$" "\\1" PYQT4_SIP_DIR ${pyqt_config})
STRING(REGEX REPLACE ".*\npyqt_sip_flags:([^\n]+).*$" "\\1" PYQT4_SIP_FLAGS ${pyqt_config})
SET(PYQT4_FOUND TRUE)
ENDIF(pyqt_config)
FIND_PROGRAM(PYUIC4_EXECUTABLE NAMES pyuic4)
IF(PYUIC4_EXECUTABLE)
SET(PYUIC4_FOUND TRUE)
ENDIF(PYUIC4_EXECUTABLE)
IF(PYQT4_FOUND AND PYUIC4_FOUND)
IF(NOT PYQT4_FIND_QUIETLY)
MESSAGE(STATUS "Found PyQt4 version: ${PYQT4_VERSION_STR}")
MESSAGE(STATUS "Found pyuic4: ${PYUIC4_EXECUTABLE}")
ENDIF(NOT PYQT4_FIND_QUIETLY)
ELSE(PYQT4_FOUND AND PYUIC4_FOUND)
IF(PYQT4_FIND_REQUIRED)
MESSAGE(FATAL_ERROR "Could not find Python")
ENDIF(PYQT4_FIND_REQUIRED)
ENDIF(PYQT4_FOUND AND PYUIC4_FOUND)
ENDIF(EXISTS PYQT4_VERSION AND EXISTS PYUIC4_EXECUTABLE)

View File

@ -0,0 +1,30 @@
# - try to find Qwt libraries and include files
# QWT_INCLUDE_DIR where to find qwt_plot.h, etc.
# QWT_LIBRARIES libraries to link against
# QWT_FOUND If false, do not try to use Qwt
find_path (QWT_INCLUDE_DIRS
NAMES qwt_plot.h
PATHS
/usr/local/include/qwt-qt4
/usr/local/include/qwt
/usr/include/qwt-qt4
/usr/include/qwt
/opt/local/include/qwt
/sw/include/qwt
)
find_library (QWT_LIBRARIES
NAMES qwt-qt4 qwt
PATHS
/usr/local/lib
/usr/lib
/opt/local/lib
/sw/lib
)
# handle the QUIETLY and REQUIRED arguments and set QWT_FOUND to TRUE if
# all listed variables are TRUE
include ( FindPackageHandleStandardArgs )
find_package_handle_standard_args( Qwt DEFAULT_MSG QWT_LIBRARIES QWT_INCLUDE_DIRS )
MARK_AS_ADVANCED(QWT_LIBRARIES QWT_INCLUDE_DIRS)

View File

@ -0,0 +1,56 @@
# - Find zeromq libraries
# This module finds zeromq if it is installed and determines where the
# include files and libraries are. It also determines what the name of
# the library is. This code sets the following variables:
#
# ZEROMQ_FOUND - have the zeromq libs been found
# ZEROMQ_LIBRARIES - path to the zeromq library
# ZEROMQ_INCLUDE_DIRS - path to where zmq.h is found
# ZEROMQ_DEBUG_LIBRARIES - path to the debug library
#INCLUDE(CMakeFindFrameworks)
# Search for the zeromq framework on Apple.
#CMAKE_FIND_FRAMEWORKS(ZeroMQ)
IF(WIN32)
FIND_LIBRARY(ZEROMQ_DEBUG_LIBRARY
NAMES libzmq_d zmq_d
PATHS
${ZEROMQ_LIBRARIES}
)
ENDIF(WIN32)
FIND_LIBRARY(ZEROMQ_LIBRARY
NAMES libzmq zmq
PATHS
${ZEROMQ_LIBRARIES}
${NSCP_LIBRARYDIR}
)
# IF(ZeroMQ_FRAMEWORKS AND NOT ZEROMQ_INCLUDE_DIR)
# FOREACH(dir ${ZeroMQ_FRAMEWORKS})
# SET(ZEROMQ_FRAMEWORK_INCLUDES ${ZEROMQ_FRAMEWORK_INCLUDES}
# ${dir}/Versions/${_CURRENT_VERSION}/include/zeromq${_CURRENT_VERSION})
# ENDFOREACH(dir)
# ENDIF(ZeroMQ_FRAMEWORKS AND NOT ZEROMQ_INCLUDE_DIR)
FIND_PATH(ZEROMQ_INCLUDE_DIR
NAMES zmq.hpp
PATHS
# ${ZEROMQ_FRAMEWORK_INCLUDES}
${ZEROMQ_INCLUDE_DIRS}
${NSCP_INCLUDEDIR}
${ZEROMQ_INCLUDE_DIR}
)
MARK_AS_ADVANCED(
ZEROMQ_DEBUG_LIBRARY
ZEROMQ_LIBRARY
ZEROMQ_INCLUDE_DIR
)
SET(ZEROMQ_INCLUDE_DIRS "${ZEROMQ_INCLUDE_DIR}")
SET(ZEROMQ_LIBRARIES "${ZEROMQ_LIBRARY}")
SET(ZEROMQ_DEBUG_LIBRARIES "${ZEROMQ_DEBUG_LIBRARY}")
INCLUDE(FindPackageHandleStandardArgs)
FIND_PACKAGE_HANDLE_STANDARD_ARGS(ZeroMQ DEFAULT_MSG ZEROMQ_LIBRARIES ZEROMQ_INCLUDE_DIRS)

View File

@ -0,0 +1,99 @@
# Copyright 2010-2011 Free Software Foundation, Inc.
#
# This file is part of GNU Radio
#
# GNU Radio is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3, or (at your option)
# any later version.
#
# GNU Radio is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with GNU Radio; see the file COPYING. If not, write to
# the Free Software Foundation, Inc., 51 Franklin Street,
# Boston, MA 02110-1301, USA.
if(DEFINED __INCLUDED_GR_BOOST_CMAKE)
return()
endif()
set(__INCLUDED_GR_BOOST_CMAKE TRUE)
########################################################################
# Setup Boost and handle some system specific things
########################################################################
set(BOOST_REQUIRED_COMPONENTS
date_time
program_options
filesystem
system
thread
)
if(UNIX AND NOT BOOST_ROOT AND EXISTS "/usr/lib64")
list(APPEND BOOST_LIBRARYDIR "/usr/lib64") #fedora 64-bit fix
endif(UNIX AND NOT BOOST_ROOT AND EXISTS "/usr/lib64")
if(MSVC)
set(BOOST_REQUIRED_COMPONENTS ${BOOST_REQUIRED_COMPONENTS} chrono)
if (NOT DEFINED BOOST_ALL_DYN_LINK)
set(BOOST_ALL_DYN_LINK TRUE)
endif()
set(BOOST_ALL_DYN_LINK "${BOOST_ALL_DYN_LINK}" CACHE BOOL "boost enable dynamic linking")
if(BOOST_ALL_DYN_LINK)
add_definitions(-DBOOST_ALL_DYN_LINK) #setup boost auto-linking in msvc
else(BOOST_ALL_DYN_LINK)
unset(BOOST_REQUIRED_COMPONENTS) #empty components list for static link
endif(BOOST_ALL_DYN_LINK)
endif(MSVC)
find_package(Boost "1.35" COMPONENTS ${BOOST_REQUIRED_COMPONENTS})
# This does not allow us to disable specific versions. It is used
# internally by cmake to know the formation newer versions. As newer
# Boost version beyond what is shown here are produced, we must extend
# this list. To disable Boost versions, see below.
set(Boost_ADDITIONAL_VERSIONS
"1.35.0" "1.35" "1.36.0" "1.36" "1.37.0" "1.37" "1.38.0" "1.38" "1.39.0" "1.39"
"1.40.0" "1.40" "1.41.0" "1.41" "1.42.0" "1.42" "1.43.0" "1.43" "1.44.0" "1.44"
"1.45.0" "1.45" "1.46.0" "1.46" "1.47.0" "1.47" "1.48.0" "1.48" "1.49.0" "1.49"
"1.50.0" "1.50" "1.51.0" "1.51" "1.52.0" "1.52" "1.53.0" "1.53" "1.54.0" "1.54"
"1.55.0" "1.55" "1.56.0" "1.56" "1.57.0" "1.57" "1.58.0" "1.58" "1.59.0" "1.59"
"1.60.0" "1.60" "1.61.0" "1.61" "1.62.0" "1.62" "1.63.0" "1.63" "1.64.0" "1.64"
"1.65.0" "1.65" "1.66.0" "1.66" "1.67.0" "1.67" "1.68.0" "1.68" "1.69.0" "1.69"
)
# Boost 1.52 disabled, see https://svn.boost.org/trac/boost/ticket/7669
# Similar problems with Boost 1.46 and 1.47.
OPTION(ENABLE_BAD_BOOST "Enable known bad versions of Boost" OFF)
if(ENABLE_BAD_BOOST)
MESSAGE(STATUS "Enabling use of known bad versions of Boost.")
endif(ENABLE_BAD_BOOST)
# For any unsuitable Boost version, add the version number below in
# the following format: XXYYZZ
# Where:
# XX is the major version ('10' for version 1)
# YY is the minor version number ('46' for 1.46)
# ZZ is the patcher version number (typically just '00')
set(Boost_NOGO_VERSIONS
104600 104601 104700 105200
)
foreach(ver ${Boost_NOGO_VERSIONS})
if(${Boost_VERSION} EQUAL ${ver})
if(NOT ENABLE_BAD_BOOST)
MESSAGE(STATUS "WARNING: Found a known bad version of Boost (v${Boost_VERSION}). Disabling.")
set(Boost_FOUND FALSE)
else(NOT ENABLE_BAD_BOOST)
MESSAGE(STATUS "WARNING: Found a known bad version of Boost (v${Boost_VERSION}). Continuing anyway.")
set(Boost_FOUND TRUE)
endif(NOT ENABLE_BAD_BOOST)
endif(${Boost_VERSION} EQUAL ${ver})
endforeach(ver)

View File

@ -0,0 +1,210 @@
# Copyright 2010-2011 Free Software Foundation, Inc.
#
# This file is part of GNU Radio
#
# GNU Radio is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3, or (at your option)
# any later version.
#
# GNU Radio is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with GNU Radio; see the file COPYING. If not, write to
# the Free Software Foundation, Inc., 51 Franklin Street,
# Boston, MA 02110-1301, USA.
if(DEFINED __INCLUDED_GR_MISC_UTILS_CMAKE)
return()
endif()
set(__INCLUDED_GR_MISC_UTILS_CMAKE TRUE)
########################################################################
# Set global variable macro.
# Used for subdirectories to export settings.
# Example: include and library paths.
########################################################################
function(GR_SET_GLOBAL var)
set(${var} ${ARGN} CACHE INTERNAL "" FORCE)
endfunction(GR_SET_GLOBAL)
########################################################################
# Set the pre-processor definition if the condition is true.
# - def the pre-processor definition to set and condition name
########################################################################
function(GR_ADD_COND_DEF def)
if(${def})
add_definitions(-D${def})
endif(${def})
endfunction(GR_ADD_COND_DEF)
########################################################################
# Check for a header and conditionally set a compile define.
# - hdr the relative path to the header file
# - def the pre-processor definition to set
########################################################################
function(GR_CHECK_HDR_N_DEF hdr def)
include(CheckIncludeFileCXX)
CHECK_INCLUDE_FILE_CXX(${hdr} ${def})
GR_ADD_COND_DEF(${def})
endfunction(GR_CHECK_HDR_N_DEF)
########################################################################
# Include subdirectory macro.
# Sets the CMake directory variables,
# includes the subdirectory CMakeLists.txt,
# resets the CMake directory variables.
#
# This macro includes subdirectories rather than adding them
# so that the subdirectory can affect variables in the level above.
# This provides a work-around for the lack of convenience libraries.
# This way a subdirectory can append to the list of library sources.
########################################################################
macro(GR_INCLUDE_SUBDIRECTORY subdir)
#insert the current directories on the front of the list
list(INSERT _cmake_source_dirs 0 ${CMAKE_CURRENT_SOURCE_DIR})
list(INSERT _cmake_binary_dirs 0 ${CMAKE_CURRENT_BINARY_DIR})
#set the current directories to the names of the subdirs
set(CMAKE_CURRENT_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/${subdir})
set(CMAKE_CURRENT_BINARY_DIR ${CMAKE_CURRENT_BINARY_DIR}/${subdir})
#include the subdirectory CMakeLists to run it
file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
include(${CMAKE_CURRENT_SOURCE_DIR}/CMakeLists.txt)
#reset the value of the current directories
list(GET _cmake_source_dirs 0 CMAKE_CURRENT_SOURCE_DIR)
list(GET _cmake_binary_dirs 0 CMAKE_CURRENT_BINARY_DIR)
#pop the subdir names of the front of the list
list(REMOVE_AT _cmake_source_dirs 0)
list(REMOVE_AT _cmake_binary_dirs 0)
endmacro(GR_INCLUDE_SUBDIRECTORY)
########################################################################
# Check if a compiler flag works and conditionally set a compile define.
# - flag the compiler flag to check for
# - have the variable to set with result
########################################################################
macro(GR_ADD_CXX_COMPILER_FLAG_IF_AVAILABLE flag have)
include(CheckCXXCompilerFlag)
CHECK_CXX_COMPILER_FLAG(${flag} ${have})
if(${have})
add_definitions(${flag})
endif(${have})
endmacro(GR_ADD_CXX_COMPILER_FLAG_IF_AVAILABLE)
########################################################################
# Generates the .la libtool file
# This appears to generate libtool files that cannot be used by auto*.
# Usage GR_LIBTOOL(TARGET [target] DESTINATION [dest])
# Notice: there is not COMPONENT option, these will not get distributed.
########################################################################
function(GR_LIBTOOL)
if(NOT DEFINED GENERATE_LIBTOOL)
set(GENERATE_LIBTOOL OFF) #disabled by default
endif()
if(GENERATE_LIBTOOL)
include(CMakeParseArgumentsCopy)
CMAKE_PARSE_ARGUMENTS(GR_LIBTOOL "" "TARGET;DESTINATION" "" ${ARGN})
find_program(LIBTOOL libtool)
if(LIBTOOL)
include(CMakeMacroLibtoolFile)
CREATE_LIBTOOL_FILE(${GR_LIBTOOL_TARGET} /${GR_LIBTOOL_DESTINATION})
endif(LIBTOOL)
endif(GENERATE_LIBTOOL)
endfunction(GR_LIBTOOL)
########################################################################
# Do standard things to the library target
# - set target properties
# - make install rules
# Also handle gnuradio custom naming conventions w/ extras mode.
########################################################################
function(GR_LIBRARY_FOO target)
#parse the arguments for component names
include(CMakeParseArgumentsCopy)
CMAKE_PARSE_ARGUMENTS(GR_LIBRARY "" "RUNTIME_COMPONENT;DEVEL_COMPONENT" "" ${ARGN})
#set additional target properties
set_target_properties(${target} PROPERTIES SOVERSION ${LIBVER})
#install the generated files like so...
install(TARGETS ${target}
LIBRARY DESTINATION ${GR_LIBRARY_DIR} COMPONENT ${GR_LIBRARY_RUNTIME_COMPONENT} # .so/.dylib file
ARCHIVE DESTINATION ${GR_LIBRARY_DIR} COMPONENT ${GR_LIBRARY_DEVEL_COMPONENT} # .lib file
RUNTIME DESTINATION ${GR_RUNTIME_DIR} COMPONENT ${GR_LIBRARY_RUNTIME_COMPONENT} # .dll file
)
#extras mode enabled automatically on linux
if(NOT DEFINED LIBRARY_EXTRAS)
set(LIBRARY_EXTRAS ${LINUX})
endif()
#special extras mode to enable alternative naming conventions
if(LIBRARY_EXTRAS)
#create .la file before changing props
GR_LIBTOOL(TARGET ${target} DESTINATION ${GR_LIBRARY_DIR})
#give the library a special name with ultra-zero soversion
set_target_properties(${target} PROPERTIES OUTPUT_NAME ${target}-${LIBVER} SOVERSION "0.0.0")
set(target_name lib${target}-${LIBVER}.so.0.0.0)
#custom command to generate symlinks
add_custom_command(
TARGET ${target}
POST_BUILD
COMMAND ${CMAKE_COMMAND} -E create_symlink ${target_name} ${CMAKE_CURRENT_BINARY_DIR}/lib${target}.so
COMMAND ${CMAKE_COMMAND} -E create_symlink ${target_name} ${CMAKE_CURRENT_BINARY_DIR}/lib${target}-${LIBVER}.so.0
COMMAND ${CMAKE_COMMAND} -E touch ${target_name} #so the symlinks point to something valid so cmake 2.6 will install
)
#and install the extra symlinks
install(
FILES
${CMAKE_CURRENT_BINARY_DIR}/lib${target}.so
${CMAKE_CURRENT_BINARY_DIR}/lib${target}-${LIBVER}.so.0
DESTINATION ${GR_LIBRARY_DIR} COMPONENT ${GR_LIBRARY_RUNTIME_COMPONENT}
)
endif(LIBRARY_EXTRAS)
endfunction(GR_LIBRARY_FOO)
########################################################################
# Create a dummy custom command that depends on other targets.
# Usage:
# GR_GEN_TARGET_DEPS(unique_name target_deps <target1> <target2> ...)
# ADD_CUSTOM_COMMAND(<the usual args> ${target_deps})
#
# Custom command cant depend on targets, but can depend on executables,
# and executables can depend on targets. So this is the process:
########################################################################
function(GR_GEN_TARGET_DEPS name var)
file(
WRITE ${CMAKE_CURRENT_BINARY_DIR}/${name}.cpp.in
"int main(void){return 0;}\n"
)
execute_process(
COMMAND ${CMAKE_COMMAND} -E copy_if_different
${CMAKE_CURRENT_BINARY_DIR}/${name}.cpp.in
${CMAKE_CURRENT_BINARY_DIR}/${name}.cpp
)
add_executable(${name} ${CMAKE_CURRENT_BINARY_DIR}/${name}.cpp)
if(ARGN)
add_dependencies(${name} ${ARGN})
endif(ARGN)
if(CMAKE_CROSSCOMPILING)
set(${var} "DEPENDS;${name}" PARENT_SCOPE) #cant call command when cross
else()
set(${var} "DEPENDS;${name};COMMAND;${name}" PARENT_SCOPE)
endif()
endfunction(GR_GEN_TARGET_DEPS)

View File

@ -0,0 +1,54 @@
# Copyright 2011 Free Software Foundation, Inc.
#
# This file is part of GNU Radio
#
# GNU Radio is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3, or (at your option)
# any later version.
#
# GNU Radio is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with GNU Radio; see the file COPYING. If not, write to
# the Free Software Foundation, Inc., 51 Franklin Street,
# Boston, MA 02110-1301, USA.
if(DEFINED __INCLUDED_GR_PLATFORM_CMAKE)
return()
endif()
set(__INCLUDED_GR_PLATFORM_CMAKE TRUE)
########################################################################
# Setup additional defines for OS types
########################################################################
if(CMAKE_SYSTEM_NAME STREQUAL "Linux")
set(LINUX TRUE)
endif()
if(LINUX AND EXISTS "/etc/debian_version")
set(DEBIAN TRUE)
endif()
if(LINUX AND EXISTS "/etc/redhat-release")
set(REDHAT TRUE)
endif()
if(LINUX AND EXISTS "/etc/slackware-version")
set(SLACKWARE TRUE)
endif()
########################################################################
# when the library suffix should be 64 (applies to redhat linux family)
########################################################################
if (REDHAT OR SLACKWARE)
set(LIB64_CONVENTION TRUE)
endif()
if(NOT DEFINED LIB_SUFFIX AND LIB64_CONVENTION AND CMAKE_SYSTEM_PROCESSOR MATCHES "64$")
set(LIB_SUFFIX 64)
endif()
set(LIB_SUFFIX ${LIB_SUFFIX} CACHE STRING "lib directory suffix")

View File

@ -0,0 +1,232 @@
# Copyright 2010-2011 Free Software Foundation, Inc.
#
# This file is part of GNU Radio
#
# GNU Radio is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3, or (at your option)
# any later version.
#
# GNU Radio is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with GNU Radio; see the file COPYING. If not, write to
# the Free Software Foundation, Inc., 51 Franklin Street,
# Boston, MA 02110-1301, USA.
if(DEFINED __INCLUDED_GR_PYTHON_CMAKE)
return()
endif()
set(__INCLUDED_GR_PYTHON_CMAKE TRUE)
########################################################################
# Setup the python interpreter:
# This allows the user to specify a specific interpreter,
# or finds the interpreter via the built-in cmake module.
########################################################################
#this allows the user to override PYTHON_EXECUTABLE
if(PYTHON_EXECUTABLE)
set(PYTHONINTERP_FOUND TRUE)
#otherwise if not set, try to automatically find it
else(PYTHON_EXECUTABLE)
#use the built-in find script
find_package(PythonInterp)
#and if that fails use the find program routine
if(NOT PYTHONINTERP_FOUND)
find_program(PYTHON_EXECUTABLE NAMES python python2.7 python2.6 python2.5)
if(PYTHON_EXECUTABLE)
set(PYTHONINTERP_FOUND TRUE)
endif(PYTHON_EXECUTABLE)
endif(NOT PYTHONINTERP_FOUND)
endif(PYTHON_EXECUTABLE)
#make the path to the executable appear in the cmake gui
set(PYTHON_EXECUTABLE ${PYTHON_EXECUTABLE} CACHE FILEPATH "python interpreter")
#make sure we can use -B with python (introduced in 2.6)
if(PYTHON_EXECUTABLE)
execute_process(
COMMAND ${PYTHON_EXECUTABLE} -B -c ""
OUTPUT_QUIET ERROR_QUIET
RESULT_VARIABLE PYTHON_HAS_DASH_B_RESULT
)
if(PYTHON_HAS_DASH_B_RESULT EQUAL 0)
set(PYTHON_DASH_B "-B")
endif()
endif(PYTHON_EXECUTABLE)
########################################################################
# Check for the existence of a python module:
# - desc a string description of the check
# - mod the name of the module to import
# - cmd an additional command to run
# - have the result variable to set
########################################################################
macro(GR_PYTHON_CHECK_MODULE desc mod cmd have)
message(STATUS "")
message(STATUS "Python checking for ${desc}")
execute_process(
COMMAND ${PYTHON_EXECUTABLE} -c "
#########################################
try:
import ${mod}
assert ${cmd}
except ImportError, AssertionError: exit(-1)
except: pass
#########################################"
RESULT_VARIABLE ${have}
)
if(${have} EQUAL 0)
message(STATUS "Python checking for ${desc} - found")
set(${have} TRUE)
else(${have} EQUAL 0)
message(STATUS "Python checking for ${desc} - not found")
set(${have} FALSE)
endif(${have} EQUAL 0)
endmacro(GR_PYTHON_CHECK_MODULE)
########################################################################
# Sets the python installation directory GR_PYTHON_DIR
########################################################################
execute_process(COMMAND ${PYTHON_EXECUTABLE} -c "
from distutils import sysconfig
print sysconfig.get_python_lib(plat_specific=True, prefix='')
" OUTPUT_VARIABLE GR_PYTHON_DIR OUTPUT_STRIP_TRAILING_WHITESPACE
)
file(TO_CMAKE_PATH ${GR_PYTHON_DIR} GR_PYTHON_DIR)
########################################################################
# Create an always-built target with a unique name
# Usage: GR_UNIQUE_TARGET(<description> <dependencies list>)
########################################################################
function(GR_UNIQUE_TARGET desc)
file(RELATIVE_PATH reldir ${CMAKE_BINARY_DIR} ${CMAKE_CURRENT_BINARY_DIR})
execute_process(COMMAND ${PYTHON_EXECUTABLE} -c "import re, hashlib
unique = hashlib.md5('${reldir}${ARGN}').hexdigest()[:5]
print(re.sub('\\W', '_', '${desc} ${reldir} ' + unique))"
OUTPUT_VARIABLE _target OUTPUT_STRIP_TRAILING_WHITESPACE)
add_custom_target(${_target} ALL DEPENDS ${ARGN})
endfunction(GR_UNIQUE_TARGET)
########################################################################
# Install python sources (also builds and installs byte-compiled python)
########################################################################
function(GR_PYTHON_INSTALL)
include(CMakeParseArgumentsCopy)
CMAKE_PARSE_ARGUMENTS(GR_PYTHON_INSTALL "" "DESTINATION;COMPONENT" "FILES;PROGRAMS" ${ARGN})
####################################################################
if(GR_PYTHON_INSTALL_FILES)
####################################################################
install(${ARGN}) #installs regular python files
#create a list of all generated files
unset(pysrcfiles)
unset(pycfiles)
unset(pyofiles)
foreach(pyfile ${GR_PYTHON_INSTALL_FILES})
get_filename_component(pyfile ${pyfile} ABSOLUTE)
list(APPEND pysrcfiles ${pyfile})
#determine if this file is in the source or binary directory
file(RELATIVE_PATH source_rel_path ${CMAKE_CURRENT_SOURCE_DIR} ${pyfile})
string(LENGTH "${source_rel_path}" source_rel_path_len)
file(RELATIVE_PATH binary_rel_path ${CMAKE_CURRENT_BINARY_DIR} ${pyfile})
string(LENGTH "${binary_rel_path}" binary_rel_path_len)
#and set the generated path appropriately
if(${source_rel_path_len} GREATER ${binary_rel_path_len})
set(pygenfile ${CMAKE_CURRENT_BINARY_DIR}/${binary_rel_path})
else()
set(pygenfile ${CMAKE_CURRENT_BINARY_DIR}/${source_rel_path})
endif()
list(APPEND pycfiles ${pygenfile}c)
list(APPEND pyofiles ${pygenfile}o)
#ensure generation path exists
get_filename_component(pygen_path ${pygenfile} PATH)
file(MAKE_DIRECTORY ${pygen_path})
endforeach(pyfile)
#the command to generate the pyc files
add_custom_command(
DEPENDS ${pysrcfiles} OUTPUT ${pycfiles}
COMMAND ${PYTHON_EXECUTABLE} ${CMAKE_BINARY_DIR}/python_compile_helper.py ${pysrcfiles} ${pycfiles}
)
#the command to generate the pyo files
add_custom_command(
DEPENDS ${pysrcfiles} OUTPUT ${pyofiles}
COMMAND ${PYTHON_EXECUTABLE} -O ${CMAKE_BINARY_DIR}/python_compile_helper.py ${pysrcfiles} ${pyofiles}
)
#create install rule and add generated files to target list
set(python_install_gen_targets ${pycfiles} ${pyofiles})
install(FILES ${python_install_gen_targets}
DESTINATION ${GR_PYTHON_INSTALL_DESTINATION}
COMPONENT ${GR_PYTHON_INSTALL_COMPONENT}
)
####################################################################
elseif(GR_PYTHON_INSTALL_PROGRAMS)
####################################################################
file(TO_NATIVE_PATH ${PYTHON_EXECUTABLE} pyexe_native)
if (CMAKE_CROSSCOMPILING)
set(pyexe_native /usr/bin/env python)
endif()
foreach(pyfile ${GR_PYTHON_INSTALL_PROGRAMS})
get_filename_component(pyfile_name ${pyfile} NAME)
get_filename_component(pyfile ${pyfile} ABSOLUTE)
string(REPLACE "${CMAKE_SOURCE_DIR}" "${CMAKE_BINARY_DIR}" pyexefile "${pyfile}.exe")
list(APPEND python_install_gen_targets ${pyexefile})
get_filename_component(pyexefile_path ${pyexefile} PATH)
file(MAKE_DIRECTORY ${pyexefile_path})
add_custom_command(
OUTPUT ${pyexefile} DEPENDS ${pyfile}
COMMAND ${PYTHON_EXECUTABLE} -c
\"open('${pyexefile}', 'w').write('\#!${pyexe_native}\\n'+open('${pyfile}').read())\"
COMMENT "Shebangin ${pyfile_name}"
)
#on windows, python files need an extension to execute
get_filename_component(pyfile_ext ${pyfile} EXT)
if(WIN32 AND NOT pyfile_ext)
set(pyfile_name "${pyfile_name}.py")
endif()
install(PROGRAMS ${pyexefile} RENAME ${pyfile_name}
DESTINATION ${GR_PYTHON_INSTALL_DESTINATION}
COMPONENT ${GR_PYTHON_INSTALL_COMPONENT}
)
endforeach(pyfile)
endif()
GR_UNIQUE_TARGET("pygen" ${python_install_gen_targets})
endfunction(GR_PYTHON_INSTALL)
########################################################################
# Write the python helper script that generates byte code files
########################################################################
file(WRITE ${CMAKE_BINARY_DIR}/python_compile_helper.py "
import sys, py_compile
files = sys.argv[1:]
srcs, gens = files[:len(files)/2], files[len(files)/2:]
for src, gen in zip(srcs, gens):
py_compile.compile(file=src, cfile=gen, doraise=True)
")

232
cmake/Modules/GrSwig.cmake Normal file
View File

@ -0,0 +1,232 @@
# Copyright 2010-2011 Free Software Foundation, Inc.
#
# This file is part of GNU Radio
#
# GNU Radio is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3, or (at your option)
# any later version.
#
# GNU Radio is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with GNU Radio; see the file COPYING. If not, write to
# the Free Software Foundation, Inc., 51 Franklin Street,
# Boston, MA 02110-1301, USA.
if(DEFINED __INCLUDED_GR_SWIG_CMAKE)
return()
endif()
set(__INCLUDED_GR_SWIG_CMAKE TRUE)
include(GrPython)
########################################################################
# Builds a swig documentation file to be generated into python docstrings
# Usage: GR_SWIG_MAKE_DOCS(output_file input_path input_path....)
#
# Set the following variable to specify extra dependent targets:
# - GR_SWIG_DOCS_SOURCE_DEPS
# - GR_SWIG_DOCS_TARGET_DEPS
########################################################################
function(GR_SWIG_MAKE_DOCS output_file)
if(ENABLE_DOXYGEN)
#setup the input files variable list, quote formated
set(input_files)
unset(INPUT_PATHS)
foreach(input_path ${ARGN})
if (IS_DIRECTORY ${input_path}) #when input path is a directory
file(GLOB input_path_h_files ${input_path}/*.h)
else() #otherwise its just a file, no glob
set(input_path_h_files ${input_path})
endif()
list(APPEND input_files ${input_path_h_files})
set(INPUT_PATHS "${INPUT_PATHS} \"${input_path}\"")
endforeach(input_path)
#determine the output directory
get_filename_component(name ${output_file} NAME_WE)
get_filename_component(OUTPUT_DIRECTORY ${output_file} PATH)
set(OUTPUT_DIRECTORY ${OUTPUT_DIRECTORY}/${name}_swig_docs)
make_directory(${OUTPUT_DIRECTORY})
#generate the Doxyfile used by doxygen
configure_file(
${CMAKE_SOURCE_DIR}/docs/doxygen/Doxyfile.swig_doc.in
${OUTPUT_DIRECTORY}/Doxyfile
@ONLY)
#Create a dummy custom command that depends on other targets
include(GrMiscUtils)
GR_GEN_TARGET_DEPS(_${name}_tag tag_deps ${GR_SWIG_DOCS_TARGET_DEPS})
#call doxygen on the Doxyfile + input headers
add_custom_command(
OUTPUT ${OUTPUT_DIRECTORY}/xml/index.xml
DEPENDS ${input_files} ${GR_SWIG_DOCS_SOURCE_DEPS} ${tag_deps}
COMMAND ${DOXYGEN_EXECUTABLE} ${OUTPUT_DIRECTORY}/Doxyfile
COMMENT "Generating doxygen xml for ${name} docs"
)
#call the swig_doc script on the xml files
add_custom_command(
OUTPUT ${output_file}
DEPENDS ${input_files} ${stamp-file} ${OUTPUT_DIRECTORY}/xml/index.xml
COMMAND ${PYTHON_EXECUTABLE} ${PYTHON_DASH_B}
${CMAKE_SOURCE_DIR}/docs/doxygen/swig_doc.py
${OUTPUT_DIRECTORY}/xml
${output_file}
COMMENT "Generating python docstrings for ${name}"
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/docs/doxygen
)
else(ENABLE_DOXYGEN)
file(WRITE ${output_file} "\n") #no doxygen -> empty file
endif(ENABLE_DOXYGEN)
endfunction(GR_SWIG_MAKE_DOCS)
########################################################################
# Build a swig target for the common gnuradio use case. Usage:
# GR_SWIG_MAKE(target ifile ifile ifile...)
#
# Set the following variables before calling:
# - GR_SWIG_FLAGS
# - GR_SWIG_INCLUDE_DIRS
# - GR_SWIG_LIBRARIES
# - GR_SWIG_SOURCE_DEPS
# - GR_SWIG_TARGET_DEPS
# - GR_SWIG_DOC_FILE
# - GR_SWIG_DOC_DIRS
########################################################################
macro(GR_SWIG_MAKE name)
set(ifiles ${ARGN})
list(APPEND GR_SWIG_TARGET_DEPS ${GR_SWIG_LIBRARIES})
#do swig doc generation if specified
if (GR_SWIG_DOC_FILE)
set(GR_SWIG_DOCS_SOURCE_DEPS ${GR_SWIG_SOURCE_DEPS})
set(GR_SWIG_DOCS_TAREGT_DEPS ${GR_SWIG_TARGET_DEPS})
GR_SWIG_MAKE_DOCS(${GR_SWIG_DOC_FILE} ${GR_SWIG_DOC_DIRS})
add_custom_target(${name}_swig_doc DEPENDS ${GR_SWIG_DOC_FILE})
list(APPEND GR_SWIG_TARGET_DEPS ${name}_swig_doc)
endif()
#append additional include directories
find_package(PythonLibs)
list(APPEND GR_SWIG_INCLUDE_DIRS ${PYTHON_INCLUDE_PATH}) #deprecated name (now dirs)
list(APPEND GR_SWIG_INCLUDE_DIRS ${PYTHON_INCLUDE_DIRS})
list(APPEND GR_SWIG_INCLUDE_DIRS ${CMAKE_CURRENT_SOURCE_DIR})
list(APPEND GR_SWIG_INCLUDE_DIRS ${CMAKE_CURRENT_BINARY_DIR})
#determine include dependencies for swig file
execute_process(
COMMAND ${PYTHON_EXECUTABLE}
${CMAKE_BINARY_DIR}/get_swig_deps.py
"${ifiles}" "${GR_SWIG_INCLUDE_DIRS}"
OUTPUT_STRIP_TRAILING_WHITESPACE
OUTPUT_VARIABLE SWIG_MODULE_${name}_EXTRA_DEPS
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
)
#Create a dummy custom command that depends on other targets
include(GrMiscUtils)
GR_GEN_TARGET_DEPS(_${name}_swig_tag tag_deps ${GR_SWIG_TARGET_DEPS})
set(tag_file ${CMAKE_CURRENT_BINARY_DIR}/${name}.tag)
add_custom_command(
OUTPUT ${tag_file}
DEPENDS ${GR_SWIG_SOURCE_DEPS} ${tag_deps}
COMMAND ${CMAKE_COMMAND} -E touch ${tag_file}
)
#append the specified include directories
include_directories(${GR_SWIG_INCLUDE_DIRS})
list(APPEND SWIG_MODULE_${name}_EXTRA_DEPS ${tag_file})
#setup the swig flags with flags and include directories
set(CMAKE_SWIG_FLAGS -fvirtual -modern -keyword -w511 -module ${name} ${GR_SWIG_FLAGS})
foreach(dir ${GR_SWIG_INCLUDE_DIRS})
list(APPEND CMAKE_SWIG_FLAGS "-I${dir}")
endforeach(dir)
#set the C++ property on the swig .i file so it builds
set_source_files_properties(${ifiles} PROPERTIES CPLUSPLUS ON)
#setup the actual swig library target to be built
include(UseSWIG)
SWIG_ADD_MODULE(${name} python ${ifiles})
SWIG_LINK_LIBRARIES(${name} ${PYTHON_LIBRARIES} ${GR_SWIG_LIBRARIES})
endmacro(GR_SWIG_MAKE)
########################################################################
# Install swig targets generated by GR_SWIG_MAKE. Usage:
# GR_SWIG_INSTALL(
# TARGETS target target target...
# [DESTINATION destination]
# [COMPONENT component]
# )
########################################################################
macro(GR_SWIG_INSTALL)
include(CMakeParseArgumentsCopy)
CMAKE_PARSE_ARGUMENTS(GR_SWIG_INSTALL "" "DESTINATION;COMPONENT" "TARGETS" ${ARGN})
foreach(name ${GR_SWIG_INSTALL_TARGETS})
install(TARGETS ${SWIG_MODULE_${name}_REAL_NAME}
DESTINATION ${GR_SWIG_INSTALL_DESTINATION}
COMPONENT ${GR_SWIG_INSTALL_COMPONENT}
)
include(GrPython)
GR_PYTHON_INSTALL(FILES ${CMAKE_CURRENT_BINARY_DIR}/${name}.py
DESTINATION ${GR_SWIG_INSTALL_DESTINATION}
COMPONENT ${GR_SWIG_INSTALL_COMPONENT}
)
GR_LIBTOOL(
TARGET ${SWIG_MODULE_${name}_REAL_NAME}
DESTINATION ${GR_SWIG_INSTALL_DESTINATION}
)
endforeach(name)
endmacro(GR_SWIG_INSTALL)
########################################################################
# Generate a python file that can determine swig dependencies.
# Used by the make macro above to determine extra dependencies.
# When you build C++, CMake figures out the header dependencies.
# This code essentially performs that logic for swig includes.
########################################################################
file(WRITE ${CMAKE_BINARY_DIR}/get_swig_deps.py "
import os, sys, re
include_matcher = re.compile('[#|%]include\\s*[<|\"](.*)[>|\"]')
include_dirs = sys.argv[2].split(';')
def get_swig_incs(file_path):
file_contents = open(file_path, 'r').read()
return include_matcher.findall(file_contents, re.MULTILINE)
def get_swig_deps(file_path, level):
deps = [file_path]
if level == 0: return deps
for inc_file in get_swig_incs(file_path):
for inc_dir in include_dirs:
inc_path = os.path.join(inc_dir, inc_file)
if not os.path.exists(inc_path): continue
deps.extend(get_swig_deps(inc_path, level-1))
return deps
if __name__ == '__main__':
ifiles = sys.argv[1].split(';')
deps = sum([get_swig_deps(ifile, 3) for ifile in ifiles], [])
#sys.stderr.write(';'.join(set(deps)) + '\\n\\n')
print(';'.join(set(deps)))
")

137
cmake/Modules/GrTest.cmake Normal file
View File

@ -0,0 +1,137 @@
# Copyright 2010-2011 Free Software Foundation, Inc.
#
# This file is part of GNU Radio
#
# GNU Radio is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3, or (at your option)
# any later version.
#
# GNU Radio is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with GNU Radio; see the file COPYING. If not, write to
# the Free Software Foundation, Inc., 51 Franklin Street,
# Boston, MA 02110-1301, USA.
if(DEFINED __INCLUDED_GR_TEST_CMAKE)
return()
endif()
set(__INCLUDED_GR_TEST_CMAKE TRUE)
########################################################################
# Add a unit test and setup the environment for a unit test.
# Takes the same arguments as the ADD_TEST function.
#
# Before calling set the following variables:
# GR_TEST_TARGET_DEPS - built targets for the library path
# GR_TEST_LIBRARY_DIRS - directories for the library path
# GR_TEST_PYTHON_DIRS - directories for the python path
########################################################################
function(GR_ADD_TEST test_name)
#Ensure that the build exe also appears in the PATH.
list(APPEND GR_TEST_TARGET_DEPS ${ARGN})
#In the land of windows, all libraries must be in the PATH.
#Since the dependent libraries are not yet installed,
#we must manually set them in the PATH to run tests.
#The following appends the path of a target dependency.
foreach(target ${GR_TEST_TARGET_DEPS})
get_target_property(location ${target} LOCATION)
if(location)
get_filename_component(path ${location} PATH)
string(REGEX REPLACE "\\$\\(.*\\)" ${CMAKE_BUILD_TYPE} path ${path})
list(APPEND GR_TEST_LIBRARY_DIRS ${path})
endif(location)
endforeach(target)
if(WIN32)
#SWIG generates the python library files into a subdirectory.
#Therefore, we must append this subdirectory into PYTHONPATH.
#Only do this for the python directories matching the following:
foreach(pydir ${GR_TEST_PYTHON_DIRS})
get_filename_component(name ${pydir} NAME)
if(name MATCHES "^(swig|lib|src)$")
list(APPEND GR_TEST_PYTHON_DIRS ${pydir}/${CMAKE_BUILD_TYPE})
endif()
endforeach(pydir)
endif(WIN32)
file(TO_NATIVE_PATH ${CMAKE_CURRENT_SOURCE_DIR} srcdir)
file(TO_NATIVE_PATH "${GR_TEST_LIBRARY_DIRS}" libpath) #ok to use on dir list?
file(TO_NATIVE_PATH "${GR_TEST_PYTHON_DIRS}" pypath) #ok to use on dir list?
set(environs "GR_DONT_LOAD_PREFS=1" "srcdir=${srcdir}")
#http://www.cmake.org/pipermail/cmake/2009-May/029464.html
#Replaced this add test + set environs code with the shell script generation.
#Its nicer to be able to manually run the shell script to diagnose problems.
#ADD_TEST(${ARGV})
#SET_TESTS_PROPERTIES(${test_name} PROPERTIES ENVIRONMENT "${environs}")
if(UNIX)
set(LD_PATH_VAR "LD_LIBRARY_PATH")
if(APPLE)
set(LD_PATH_VAR "DYLD_LIBRARY_PATH")
endif()
set(binpath "${CMAKE_CURRENT_BINARY_DIR}:$PATH")
list(APPEND libpath "$${LD_PATH_VAR}")
list(APPEND pypath "$PYTHONPATH")
#replace list separator with the path separator
string(REPLACE ";" ":" libpath "${libpath}")
string(REPLACE ";" ":" pypath "${pypath}")
list(APPEND environs "PATH=${binpath}" "${LD_PATH_VAR}=${libpath}" "PYTHONPATH=${pypath}")
#generate a bat file that sets the environment and runs the test
find_program(SHELL sh)
set(sh_file ${CMAKE_CURRENT_BINARY_DIR}/${test_name}_test.sh)
file(WRITE ${sh_file} "#!${SHELL}\n")
#each line sets an environment variable
foreach(environ ${environs})
file(APPEND ${sh_file} "export ${environ}\n")
endforeach(environ)
#load the command to run with its arguments
foreach(arg ${ARGN})
file(APPEND ${sh_file} "${arg} ")
endforeach(arg)
file(APPEND ${sh_file} "\n")
#make the shell file executable
execute_process(COMMAND chmod +x ${sh_file})
add_test(${test_name} ${SHELL} ${sh_file})
endif(UNIX)
if(WIN32)
list(APPEND libpath ${DLL_PATHS} "%PATH%")
list(APPEND pypath "%PYTHONPATH%")
#replace list separator with the path separator (escaped)
string(REPLACE ";" "\\;" libpath "${libpath}")
string(REPLACE ";" "\\;" pypath "${pypath}")
list(APPEND environs "PATH=${libpath}" "PYTHONPATH=${pypath}")
#generate a bat file that sets the environment and runs the test
set(bat_file ${CMAKE_CURRENT_BINARY_DIR}/${test_name}_test.bat)
file(WRITE ${bat_file} "@echo off\n")
#each line sets an environment variable
foreach(environ ${environs})
file(APPEND ${bat_file} "SET ${environ}\n")
endforeach(environ)
#load the command to run with its arguments
foreach(arg ${ARGN})
file(APPEND ${bat_file} "${arg} ")
endforeach(arg)
file(APPEND ${bat_file} "\n")
add_test(${test_name} ${bat_file})
endif(WIN32)
endfunction(GR_ADD_TEST)

View File

@ -1,31 +0,0 @@
INCLUDE(FindPkgConfig)
PKG_CHECK_MODULES(PC_AIR_MODES air_modes)
FIND_PATH(
AIR_MODES_INCLUDE_DIRS
NAMES air_modes/api.h
HINTS $ENV{AIR_MODES_DIR}/include
${PC_AIR_MODES_INCLUDEDIR}
PATHS ${CMAKE_INSTALL_PREFIX}/include
/usr/local/include
/usr/include
)
FIND_LIBRARY(
AIR_MODES_LIBRARIES
NAMES gnuradio-air-modes
HINTS $ENV{AIR_MODES_DIR}/lib
${PC_AIR_MODES_LIBDIR}
PATHS ${CMAKE_INSTALL_PREFIX}/lib
${CMAKE_INSTALL_PREFIX}/lib64
/usr/local/lib
/usr/local/lib64
/usr/lib
/usr/lib64
)
include("${CMAKE_CURRENT_LIST_DIR}/air_modesTarget.cmake")
INCLUDE(FindPackageHandleStandardArgs)
FIND_PACKAGE_HANDLE_STANDARD_ARGS(AIR_MODES DEFAULT_MSG AIR_MODES_LIBRARIES AIR_MODES_INCLUDE_DIRS)
MARK_AS_ADVANCED(AIR_MODES_LIBRARIES AIR_MODES_INCLUDE_DIRS)

View File

@ -1,26 +0,0 @@
# Copyright 2018 Free Software Foundation, Inc.
#
# This file is part of GNU Radio
#
# GNU Radio is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3, or (at your option)
# any later version.
#
# GNU Radio is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with GNU Radio; see the file COPYING. If not, write to
# the Free Software Foundation, Inc., 51 Franklin Street,
# Boston, MA 02110-1301, USA.
include(CMakeFindDependencyMacro)
set(target_deps "@TARGET_DEPENDENCIES@")
foreach(dep IN LISTS target_deps)
find_dependency(${dep})
endforeach()
include("${CMAKE_CURRENT_LIST_DIR}/@TARGET@Targets.cmake")

View File

@ -21,9 +21,9 @@
# Install public header files
########################################################################
install(FILES
preamble.h
slicer.h
types.h
api.h
DESTINATION include/gr_air_modes
air_modes_preamble.h
air_modes_slicer.h
air_modes_types.h
air_modes_api.h
DESTINATION include/gr-air-modes
)

View File

@ -0,0 +1,54 @@
/*
# 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 */

View File

@ -0,0 +1,66 @@
/*
# 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 <gnuradio/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 */

View File

@ -1,5 +1,5 @@
/*
# Copyright 2013 Nick Foster
# Copyright 2010 Nick Foster
#
# This file is part of gr-air-modes
#
@ -20,37 +20,39 @@
#
*/
#ifndef INCLUDED_AIR_MODES_SLICER_IMPL_H
#define INCLUDED_AIR_MODES_SLICER_IMPL_H
#ifndef INCLUDED_AIR_MODES_slicer_H
#define INCLUDED_AIR_MODES_slicer_H
#include <gnuradio/sync_block.h>
#include <gnuradio/msg_queue.h>
#include <gr_air_modes/api.h>
#include <gr_air_modes/slicer.h>
#include <air_modes_api.h>
namespace gr {
namespace air_modes {
class air_modes_slicer;
typedef boost::shared_ptr<air_modes_slicer> air_modes_slicer_sptr;
class AIR_MODES_API slicer_impl : public slicer
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
{
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::tag_t d_timestamp;
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);
};
} //namespace air_modes
} //namespace gr
#endif /* INCLUDED_AIR_MODES_SLICER_IMPL_H */
#endif /* INCLUDED_AIR_MODES_slicer_H */

View File

@ -1,51 +0,0 @@
/*
# 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(float channel_rate, float threshold_db);
virtual void set_rate(float channel_rate) = 0;
virtual void set_threshold(float threshold_db) = 0;
virtual float get_rate(void) = 0;
virtual float get_threshold(void) = 0;
};
} // namespace air_modes
} // namespace gr
#endif /* INCLUDED_AIR_MODES_PREAMBLE_H */

View File

@ -1,47 +0,0 @@
/*
# 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 */

View File

@ -23,7 +23,7 @@
#ifndef INCLUDED_MODES_CRC_H
#define INCLUDED_MODES_CRC_H
extern const unsigned int modes_crc_table[112];
unsigned int modes_check_crc(unsigned char data[], int length);
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);

View File

@ -23,33 +23,38 @@
include(GrPlatform) #define LIB_SUFFIX
add_library(air_modes SHARED
preamble_impl.cc
slicer_impl.cc
air_modes_preamble.cc
air_modes_slicer.cc
modes_crc.cc
)
target_link_libraries(air_modes gnuradio::gnuradio-runtime)
target_include_directories(air_modes
PUBLIC $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/../include>
PUBLIC $<INSTALL_INTERFACE:include>
)
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 "${VERSION_MAJOR}")
set_target_properties(air_modes PROPERTIES VERSION "${VERSION_MAJOR}.${VERSION_MINOR}")
if(APPLE)
set_target_properties(air_modes PROPERTIES
INSTALL_NAME_DIR "${CMAKE_INSTALL_PREFIX}/lib"
)
endif(APPLE)
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}")
########################################################################
# Install built library files
########################################################################
include(GrMiscUtils)
GR_LIBRARY_FOO(air_modes)
install(TARGETS air_modes
LIBRARY DESTINATION lib${LIB_SUFFIX} # .so/.dylib file
ARCHIVE DESTINATION lib${LIB_SUFFIX} # .lib file
RUNTIME DESTINATION bin # .dll file
)
########################################################################
# Print summary
# Build and register unit test
########################################################################
message(STATUS "Using install prefix: ${CMAKE_INSTALL_PREFIX}")
message(STATUS "Building for version: ${VERSION} / ${LIBVER}")
#find_package(Boost COMPONENTS unit_test_framework)
#include(GrTest)
#set(GR_TEST_TARGET_DEPS gnuradio-gr-air-modes)
#turn each test cpp file into an executable with an int main() function
#add_definitions(-DBOOST_TEST_DYN_LINK -DBOOST_TEST_MAIN)
#add_executable(qa_gr-air-modes_square_ff qa_gr-air-modes_square_ff.cc)
#target_link_libraries(qa_gr-air-modes_square_ff gnuradio-gr-air-modes ${Boost_LIBRARIES})
#GR_ADD_TEST(qa_gr-air-modes_square_ff qa_gr-air-modes_square_ff)
#add_executable(qa_gr-air-modes_square2_ff qa_gr-air-modes_square2_ff.cc)
#target_link_libraries(qa_gr-air-modes_square2_ff gnuradio-gr-air-modes ${Boost_LIBRARIES})
#GR_ADD_TEST(qa_gr-air-modes_square2_ff qa_gr-air-modes_square2_ff)

View File

@ -0,0 +1,89 @@
/*
# 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;
}

216
lib/air_modes_preamble.cc Normal file
View File

@ -0,0 +1,216 @@
/*
# 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 <gnuradio/io_signature.h>
#include <string.h>
#include <iostream>
#include <gnuradio/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::io_signature::make2 (2, 2, sizeof(float), sizeof(float)), //stream 0 is received data, stream 1 is moving average for reference
gr::io_signature::make (1, 1, sizeof(float))) //the output packets
{
d_chip_rate = 2000000; //2Mchips per second
d_samples_per_chip = channel_rate / d_chip_rate; //must be integer number of samples per chip to work
d_samples_per_symbol = d_samples_per_chip * 2;
d_check_width = 120 * d_samples_per_symbol; //only search to this far from the end of the stream buffer
d_threshold_db = threshold_db;
d_threshold = powf(10., threshold_db/20.); //the level that the sample must be above the moving average in order to qualify as a pulse
d_secs_per_sample = 1.0 / channel_rate;
set_output_multiple(1+d_check_width*2);
std::stringstream str;
str << name() << unique_id();
d_me = pmt::string_to_symbol(str.str());
d_key = pmt::string_to_symbol("preamble_found");
set_history(d_samples_per_symbol);
}
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::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
//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::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;
}

197
lib/air_modes_slicer.cc Normal file
View File

@ -0,0 +1,197 @@
/*
# 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 <gnuradio/io_signature.h>
#include <air_modes_types.h>
#include <sstream>
#include <iomanip>
#include <modes_crc.h>
#include <iostream>
#include <gnuradio/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::io_signature::make (1, 1, sizeof(float)), //stream 0 is received data, stream 1 is binary preamble detector output
gr::io_signature::make (0, 0, 0) )
{
//initialize private data here
d_chip_rate = 2000000; //2Mchips per second
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 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::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;
}
}
/******************** BEGIN TIMESTAMP BS ******************/
rx_packet.timestamp = pmt::to_double(tag_iter->value);
/******************* END TIMESTAMP BS *********************/
//increment for the next round
//here you might want to traverse the whole packet and if you find all 0's, just toss it. don't know why these packets turn up, but they pass ECC.
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);
//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;
}

View File

@ -1,63 +1,165 @@
/*
* Copyright 2013 Nick Foster
* Copyright 2007 Free Software Foundation, Inc.
*
* This file is part of gr-air-modes
* This file is part of GNU Radio
*
* gr-air-modes is free software; you can redistribute it and/or modify
* GNU Radio is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* gr-air-modes is distributed in the hope that it will be useful,
* GNU Radio is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with gr-air-modes; see the file COPYING. If not, write to
* along with GNU Radio; see the file COPYING. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street,
* Boston, MA 02110-1301, USA.
*/
//this is copied almost verbatim from Eric Cottrell's gr-air platform.
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <stdio.h>
#include <gr_air_modes/types.h>
#include <gr_air_modes/modes_crc.h>
#include <air_modes_types.h>
#include <modes_crc.h>
#include <math.h>
#include <stdlib.h>
unsigned int crc_table[256];
const unsigned int POLY=0xFFF409;
//generate a bytewise lookup CRC table
void generate_crc_table(void)
/* 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] =
{
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;
}
}
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,
};
//Perform a bytewise CRC check
unsigned int modes_check_crc(unsigned char data[], int length)
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;
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;
}

View File

@ -1,248 +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 "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(float 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(float 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(float 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_sample_rate = 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;
}
float air_modes::preamble_impl::get_rate(void) {
return d_sample_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;
}
static pmt::pmt_t tag_to_timestamp(gr::tag_t tstamp, uint64_t abs_sample_cnt, int rate) {
uint64_t last_whole_stamp;
double last_frac_stamp;
pmt::pmt_t tstime = pmt::make_tuple(pmt::from_uint64(0), pmt::from_double(0));
if(tstamp.key == NULL
|| !pmt::is_symbol(tstamp.key)
|| pmt::symbol_to_string(tstamp.key) != "rx_time") {
last_whole_stamp = 0;
last_frac_stamp = 0;
} else {
last_whole_stamp = pmt::to_uint64(pmt::tuple_ref(tstamp.value, 0));
last_frac_stamp = pmt::to_double(pmt::tuple_ref(tstamp.value, 1));
}
//the timestamp tag has tstamp.offset, the sample index of the timestamp tag
//also tstamp.value, a pmt pair with (uint64, double) representing int and
//fractional timestamp, respectively.
//this function also gets an abs_sample_cnt which represents the sample count to
//find a timestamp for. sps is obviously samples per second.
//
//so (abs_sample_cnt - tstamp.offset) is the delay we apply to the tag
// int((abs_sample_cnt - tstamp.offset)/sps) is the integer offset
// (abs_sample_cnt - tstamp.offset)/sps is the fractional offset
uint64_t int_offset = (abs_sample_cnt - tstamp.offset)/rate;
double frac_offset = ((abs_sample_cnt - tstamp.offset) % rate) / double(rate);
uint64_t abs_whole = last_whole_stamp + int_offset;
double abs_frac = last_frac_stamp + frac_offset;
if(abs_frac > 1.0f) {
abs_frac -= 1.0f;
abs_whole += 1;
}
tstime = pmt::make_tuple(pmt::from_uint64(abs_whole), pmt::from_double(abs_frac));
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 % int(d_samples_per_chip)) - int(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+int(j*d_samples_per_chip)] - inavg[i];
}
//get the timestamp of the preamble
pmt::pmt_t tstamp = tag_to_timestamp(d_timestamp, abs_sample_cnt + i, d_sample_rate);
//now tag the preamble
add_item_tag(0, //stream ID
nitems_written(0), //sample
d_key, //frame_info
tstamp,
d_me //block src id
);
//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

View File

@ -1,43 +0,0 @@
#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;
float d_samples_per_chip;
float d_samples_per_symbol;
float d_threshold_db;
float d_threshold;
gr::tag_t d_timestamp;
pmt::pmt_t d_me, d_key;
int d_sample_rate;
public:
preamble_impl(float 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(float channel_rate);
void set_threshold(float threshold_db);
float get_threshold(void);
float get_rate(void);
};
} //namespace air_modes
} //namespace gr
#endif //_AIR_MODES_PREAMBLE_IMPL_H_

View File

@ -1,200 +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 "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;
}
}
//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;}
pmt::pmt_t tstamp = tag_iter->value;
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
<< " " << pmt::to_uint64(pmt::tuple_ref(tstamp, 0)) << " " << std::setprecision(10) << pmt::to_double(pmt::tuple_ref(tstamp, 1));
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

View File

@ -40,7 +40,6 @@ GR_PYTHON_INSTALL(
flightgear.py
gui_model.py
kml.py
modes_types.py
parse.py
msprint.py
radio.py
@ -48,6 +47,7 @@ GR_PYTHON_INSTALL(
rx_path.py
sbs1.py
sql.py
types.py
zmq_socket.py
Quaternion.py
DESTINATION ${GR_PYTHON_DIR}/air_modes

View File

@ -28,37 +28,54 @@ KML, and PlanePlotter-compliant SBS-1 emulation output. mlat.py provides
an experimental implementation of a multilateration solver.
'''
from __future__ import unicode_literals
# ----------------------------------------------------------------
# Temporary workaround for ticket:181 (swig+python problem)
import sys
_RTLD_GLOBAL = 0
try:
from dl import RTLD_GLOBAL as _RTLD_GLOBAL
except ImportError:
try:
from DLFCN import RTLD_GLOBAL as _RTLD_GLOBAL
except ImportError:
pass
if _RTLD_GLOBAL != 0:
_dlopenflags = sys.getdlopenflags()
sys.setdlopenflags(_dlopenflags|_RTLD_GLOBAL)
# ----------------------------------------------------------------
# import swig generated symbols into the gr-air-modes namespace
from .air_modes_swig import *
from air_modes_swig import *
# import any pure python here
#
try:
import zmq
except ImportError:
raise RuntimeError("PyZMQ not found! Please install libzmq and PyZMQ to run gr-air-modes")
from .rx_path import rx_path
from .zmq_socket import zmq_pubsub_iface
from .parse import *
from .msprint import output_print
from .sql import output_sql
from .sbs1 import output_sbs1
from .kml import output_kml, output_jsonp
from .raw_server import raw_server
from .radio import modes_radio
from .exceptions import *
from .modes_types import *
from .altitude import *
from .cpr import cpr_decoder
from .html_template import html_template
from rx_path import rx_path
from zmq_socket import zmq_pubsub_iface
from parse import *
from msprint import output_print
from sql import output_sql
from sbs1 import output_sbs1
from kml import output_kml, output_jsonp
from raw_server import raw_server
from radio import modes_radio
from exceptions import *
from az_map import *
from types import *
from altitude import *
from cpr import cpr_decoder
from html_template import html_template
#this is try/excepted in case the user doesn't have numpy installed
try:
from .flightgear import output_flightgear
from .Quaternion import *
from flightgear import output_flightgear
from Quaternion import *
except ImportError:
print("gr-air-modes warning: numpy+scipy not installed, FlightGear interface not supported")
print "gr-air-modes warning: numpy+scipy not installed, FlightGear interface not supported"
pass
# ----------------------------------------------------------------
# Tail of workaround
if _RTLD_GLOBAL != 0:
sys.setdlopenflags(_dlopenflags) # Restore original flags
# ----------------------------------------------------------------

View File

@ -26,119 +26,120 @@
from air_modes.exceptions import *
def decode_alt(alt, bit13):
mbit = alt & 0x0040
qbit = alt & 0x0010
if mbit and bit13:
#nobody uses metric altitude: AFAIK, it's an orphaned part of
#the spec. haven't seen it in three years. as a result, replies
#with mbit set can be considered spurious, and so we discard them here.
#bits 20-25, 27-31 encode alt in meters
#remember that bits are LSB (bit 20 is MSB)
#meters_alt = 0
#for (shift, bit) in enumerate(range(31,26,-1)+range(25,19,-1)):
# meters_alt += ((alt & (1<<bit)) != 0) << shift
#decoded_alt = meters_alt / 0.3048
raise MetricAltError
mbit = alt & 0x0040
qbit = alt & 0x0010
if mbit and bit13:
#nobody uses metric altitude: AFAIK, it's an orphaned part of
#the spec. haven't seen it in three years. as a result, replies
#with mbit set can be considered spurious, and so we discard them here.
#bits 20-25, 27-31 encode alt in meters
#remember that bits are LSB (bit 20 is MSB)
#meters_alt = 0
#for (shift, bit) in enumerate(range(31,26,-1)+range(25,19,-1)):
# meters_alt += ((alt & (1<<bit)) != 0) << shift
#decoded_alt = meters_alt / 0.3048
raise MetricAltError
if qbit: #a mode S-style reply
#bit13 is false for BDS0,5 ADS-B squitters, and is true otherwise
if bit13:
#in this representation, the altitude bits are as follows:
# 12 11 10 9 8 7 (6) 5 (4) 3 2 1 0
# so bits 6 and 4 are the M and Q bits, respectively.
tmp1 = (alt & 0x3F80) >> 2
tmp2 = (alt & 0x0020) >> 1
else:
tmp1 = (alt & 0x1FE0) >> 1
tmp2 = 0
if qbit: #a mode S-style reply
#bit13 is false for BDS0,5 ADS-B squitters, and is true otherwise
if bit13:
#in this representation, the altitude bits are as follows:
# 12 11 10 9 8 7 (6) 5 (4) 3 2 1 0
# so bits 6 and 4 are the M and Q bits, respectively.
tmp1 = (alt & 0x3F80) >> 2
tmp2 = (alt & 0x0020) >> 1
else:
tmp1 = (alt & 0x1FE0) >> 1
tmp2 = 0
decoded_alt = ((alt & 0x0F) | tmp1 | tmp2) * 25 - 1000
decoded_alt = ((alt & 0x0F) | tmp1 | tmp2) * 25 - 1000
else: #a mode C-style reply
#okay, the order they come in is:
#C1 A1 C2 A2 C4 A4 X B1 D1 B2 D2 B4 D4
#the order we want them in is:
#D2 D4 A1 A2 A4 B1 B2 B4
#so we'll reassemble into a Gray-coded representation
else: #a mode C-style reply
#okay, the order they come in is:
#C1 A1 C2 A2 C4 A4 X B1 D1 B2 D2 B4 D4
#the order we want them in is:
#D2 D4 A1 A2 A4 B1 B2 B4
#so we'll reassemble into a Gray-coded representation
if bit13 is False:
alt = (alt & 0x003F) | (alt & 0x0FC0 << 1)
if bit13 is False:
alt = (alt & 0x003F) | (alt & 0x0FC0 << 1)
C1 = 0x1000
A1 = 0x0800
C2 = 0x0400
A2 = 0x0200 #this represents the order in which the bits come
C4 = 0x0100
A4 = 0x0080
B1 = 0x0020
D1 = 0x0010
B2 = 0x0008
D2 = 0x0004
B4 = 0x0002
D4 = 0x0001
C1 = 0x1000
A1 = 0x0800
C2 = 0x0400
A2 = 0x0200 #this represents the order in which the bits come
C4 = 0x0100
A4 = 0x0080
B1 = 0x0020
D1 = 0x0010
B2 = 0x0008
D2 = 0x0004
B4 = 0x0002
D4 = 0x0001
bigpart = ((alt & B4) >> 1) \
+ ((alt & B2) >> 2) \
+ ((alt & B1) >> 3) \
+ ((alt & A4) >> 4) \
+ ((alt & A2) >> 5) \
+ ((alt & A1) >> 6) \
+ ((alt & D4) << 6) \
+ ((alt & D2) << 5)
bigpart = ((alt & B4) >> 1) \
+ ((alt & B2) >> 2) \
+ ((alt & B1) >> 3) \
+ ((alt & A4) >> 4) \
+ ((alt & A2) >> 5) \
+ ((alt & A1) >> 6) \
+ ((alt & D4) << 6) \
+ ((alt & D2) << 5)
#bigpart is now the 500-foot-resolution Gray-coded binary part
decoded_alt = gray2bin(bigpart)
#real_alt is now the 500-foot-per-tick altitude
#bigpart is now the 500-foot-resolution Gray-coded binary part
decoded_alt = gray2bin(bigpart)
#real_alt is now the 500-foot-per-tick altitude
cbits = ((alt & C4) >> 8) + ((alt & C2) >> 9) + ((alt & C1) >> 10)
cval = gray2bin(cbits) #turn them into a real number
cbits = ((alt & C4) >> 8) + ((alt & C2) >> 9) + ((alt & C1) >> 10)
cval = gray2bin(cbits) #turn them into a real number
if cval == 7:
cval = 5 #not a real gray code after all
if cval == 7:
cval = 5 #not a real gray code after all
if decoded_alt % 2:
cval = 6 - cval #since the code is symmetric this unwraps it to see whether to subtract the C bits or add them
if decoded_alt % 2:
cval = 6 - cval #since the code is symmetric this unwraps it to see whether to subtract the C bits or add them
decoded_alt *= 500 #take care of the A,B,D data
decoded_alt += cval * 100 #factor in the C data
decoded_alt -= 1300 #subtract the offset
decoded_alt *= 500 #take care of the A,B,D data
decoded_alt += cval * 100 #factor in the C data
decoded_alt -= 1300 #subtract the offset
return decoded_alt
return decoded_alt
def gray2bin(gray):
i = gray >> 1
i = gray >> 1
while i != 0:
gray ^= i
i >>= 1
while i != 0:
gray ^= i
i >>= 1
return gray
return gray
def encode_alt_modes(alt, bit13):
mbit = False
qbit = True
encalt = (int(alt) + 1000) / 25
mbit = False
qbit = True
encalt = (int(alt) + 1000) / 25
if bit13 is True:
tmp1 = (encalt & 0xfe0) << 2
tmp2 = (encalt & 0x010) << 1
else:
tmp1 = (encalt & 0xff8) << 1
tmp2 = 0
if bit13 is True:
tmp1 = (encalt & 0xfe0) << 2
tmp2 = (encalt & 0x010) << 1
else:
tmp1 = (encalt & 0xff8) << 1
tmp2 = 0
return (encalt & 0x0F) | tmp1 | tmp2 | (mbit << 6) | (qbit << 4)
return (encalt & 0x0F) | tmp1 | tmp2 | (mbit << 6) | (qbit << 4)
if __name__ == "__main__":
try:
for alt in range(-1000, 101400, 25):
dec = decode_alt(encode_alt_modes(alt, False), False)
if dec != alt:
print("Failure at %i with bit13 clear (got %s)" % (alt, dec))
for alt in range(-1000, 101400, 25):
dec = decode_alt(encode_alt_modes(alt, True), True)
if dec != alt:
print("Failure at %i with bit13 set (got %s)" % (alt, dec))
except MetricAltError:
print("Failure at %i due to metric alt bit" % alt)
try:
for alt in range(-1000, 101400, 25):
dec = decode_alt(encode_alt_modes(alt, False), False)
if dec != alt:
print "Failure at %i with bit13 clear (got %s)" % (alt, dec)
for alt in range(-1000, 101400, 25):
dec = decode_alt(encode_alt_modes(alt, True), True)
if dec != alt:
print "Failure at %i with bit13 set (got %s)" % (alt, dec)
except MetricAltError:
print "Failure at %i due to metric alt bit" % alt

View File

@ -25,8 +25,9 @@
from PyQt4 import QtCore, QtGui
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
@ -52,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
@ -84,15 +85,18 @@ class az_map_model(QtCore.QObject):
# the azimuth map widget
class az_map(QtGui.QWidget):
maxrange = 200
maxrange = 450
ringsize = 100
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)
@ -114,7 +118,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)):
@ -132,12 +136,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.get_range()
scale = radius * distance / self.maxrange
#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))
@ -149,55 +153,44 @@ 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)
#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.setPen(az_map.ringpen)
for i in range(0, self.maxrange, self.ringsize):
diameter = (float(i) / az_map.maxrange) * min(self.width(), self.height())
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.repaint()
self.drawPath()
def wheelEvent(self, event):
self.setMaxRange(self.maxrange + (event.delta()/120.)*self.maxrange/4.)
def setRingSize(self, ringsize):
self.ringsize = ringsize
self.drawPath()
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()
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:
if msgtype == 17:
icao = msg.data["aa"]
subtype = msg.data["ftc"]
distance, altitude, bearing = [0,0,0]
if 5 <= subtype <= 8:
(ground_track, decoded_lat, decoded_lon, distance, bearing) = air_modes.parseBDS06(msg.data, self._cpr)
altitude = 0
elif 9 <= subtype <= 18:
(altitude, decoded_lat, decoded_lon, distance, bearing) = air_modes.parseBDS05(msg.data, self._cpr)
self.model.addRecord(bearing, altitude, distance)
self.model.addRecord(bearing, altitude, distance)
except ADSBError:
pass

View File

@ -31,302 +31,302 @@ from air_modes.exceptions import *
latz = 15
def nz(ctype):
return 4 * latz - ctype
return 4 * latz - ctype
def dlat(ctype, surface):
if surface == 1:
tmp = 90.0
else:
tmp = 360.0
if surface == 1:
tmp = 90.0
else:
tmp = 360.0
nzcalc = nz(ctype)
if nzcalc == 0:
return tmp
else:
return tmp / nzcalc
nzcalc = nz(ctype)
if nzcalc == 0:
return tmp
else:
return tmp / nzcalc
def nl(declat_in):
if abs(declat_in) >= 87.0:
return 1.0
return math.floor( (2.0*math.pi) * math.acos(1.0- (1.0-math.cos(math.pi/(2.0*latz))) / math.cos( (math.pi/180.0)*abs(declat_in) )**2 )**-1)
if abs(declat_in) >= 87.0:
return 1.0
return math.floor( (2.0*math.pi) * math.acos(1.0- (1.0-math.cos(math.pi/(2.0*latz))) / math.cos( (math.pi/180.0)*abs(declat_in) )**2 )**-1)
def dlon(declat_in, ctype, surface):
if surface:
tmp = 90.0
else:
tmp = 360.0
nlcalc = max(nl(declat_in)-ctype, 1)
return tmp / nlcalc
if surface:
tmp = 90.0
else:
tmp = 360.0
nlcalc = max(nl(declat_in)-ctype, 1)
return tmp / nlcalc
def decode_lat(enclat, ctype, my_lat, surface):
tmp1 = dlat(ctype, surface)
tmp2 = float(enclat) / (2**17)
j = math.floor(my_lat/tmp1) + math.floor(0.5 + ((my_lat % tmp1) / tmp1) - tmp2)
tmp1 = dlat(ctype, surface)
tmp2 = float(enclat) / (2**17)
j = math.floor(my_lat/tmp1) + math.floor(0.5 + ((my_lat % tmp1) / tmp1) - tmp2)
return tmp1 * (j + tmp2)
return tmp1 * (j + tmp2)
def decode_lon(declat, enclon, ctype, my_lon, surface):
tmp1 = dlon(declat, ctype, surface)
tmp2 = float(enclon) / (2**17)
m = math.floor(my_lon / tmp1) + math.floor(0.5 + ((my_lon % tmp1) / tmp1) - tmp2)
tmp1 = dlon(declat, ctype, surface)
tmp2 = float(enclon) / (2**17)
m = math.floor(my_lon / tmp1) + math.floor(0.5 + ((my_lon % tmp1) / tmp1) - tmp2)
return tmp1 * (m + tmp2)
return tmp1 * (m + tmp2)
def cpr_resolve_local(my_location, encoded_location, ctype, surface):
[my_lat, my_lon] = my_location
[enclat, enclon] = encoded_location
[my_lat, my_lon] = my_location
[enclat, enclon] = encoded_location
decoded_lat = decode_lat(enclat, ctype, my_lat, surface)
decoded_lon = decode_lon(decoded_lat, enclon, ctype, my_lon, surface)
decoded_lat = decode_lat(enclat, ctype, my_lat, surface)
decoded_lon = decode_lon(decoded_lat, enclon, ctype, my_lon, surface)
return [decoded_lat, decoded_lon]
return [decoded_lat, decoded_lon]
def cpr_resolve_global(evenpos, oddpos, mypos, mostrecent, surface):
#cannot resolve surface positions unambiguously without knowing receiver position
if surface and mypos is None:
raise CPRNoPositionError
dlateven = dlat(0, surface)
dlatodd = dlat(1, surface)
#cannot resolve surface positions unambiguously without knowing receiver position
if surface and mypos is None:
raise CPRNoPositionError
dlateven = dlat(0, surface)
dlatodd = dlat(1, surface)
evenpos = [float(evenpos[0]), float(evenpos[1])]
oddpos = [float(oddpos[0]), float(oddpos[1])]
j = math.floor(((nz(1)*evenpos[0] - nz(0)*oddpos[0])/2**17) + 0.5) #latitude index
evenpos = [float(evenpos[0]), float(evenpos[1])]
oddpos = [float(oddpos[0]), float(oddpos[1])]
j = math.floor(((nz(1)*evenpos[0] - nz(0)*oddpos[0])/2**17) + 0.5) #latitude index
rlateven = dlateven * ((j % nz(0))+evenpos[0]/2**17)
rlatodd = dlatodd * ((j % nz(1))+ oddpos[0]/2**17)
rlateven = dlateven * ((j % nz(0))+evenpos[0]/2**17)
rlatodd = dlatodd * ((j % nz(1))+ oddpos[0]/2**17)
#limit to -90, 90
if rlateven > 270.0:
rlateven -= 360.0
if rlatodd > 270.0:
rlatodd -= 360.0
#limit to -90, 90
if rlateven > 270.0:
rlateven -= 360.0
if rlatodd > 270.0:
rlatodd -= 360.0
#This checks to see if the latitudes of the reports straddle a transition boundary
#If so, you can't get a globally-resolvable location.
if nl(rlateven) != nl(rlatodd):
raise CPRBoundaryStraddleError
#This checks to see if the latitudes of the reports straddle a transition boundary
#If so, you can't get a globally-resolvable location.
if nl(rlateven) != nl(rlatodd):
raise CPRBoundaryStraddleError
if mostrecent == 0:
rlat = rlateven
else:
rlat = rlatodd
if mostrecent == 0:
rlat = rlateven
else:
rlat = rlatodd
#disambiguate latitude
if surface:
if mypos[0] < 0:
rlat -= 90
#disambiguate latitude
if surface:
if mypos[0] < 0:
rlat -= 90
dl = dlon(rlat, mostrecent, surface)
nl_rlat = nl(rlat)
dl = dlon(rlat, mostrecent, surface)
nl_rlat = nl(rlat)
m = math.floor(((evenpos[1]*(nl_rlat-1)-oddpos[1]*nl_rlat)/2**17)+0.5) #longitude index
#when surface positions straddle a disambiguation boundary (90 degrees),
#surface decoding will fail. this might never be a problem in real life, but it'll fail in the
#test case. the documentation doesn't mention it.
m = math.floor(((evenpos[1]*(nl_rlat-1)-oddpos[1]*nl_rlat)/2**17)+0.5) #longitude index
#when surface positions straddle a disambiguation boundary (90 degrees),
#surface decoding will fail. this might never be a problem in real life, but it'll fail in the
#test case. the documentation doesn't mention it.
if mostrecent == 0:
enclon = evenpos[1]
else:
enclon = oddpos[1]
if mostrecent == 0:
enclon = evenpos[1]
else:
enclon = oddpos[1]
rlon = dl * ((m % max(nl_rlat-mostrecent,1)) + enclon/2.**17)
rlon = dl * ((m % max(nl_rlat-mostrecent,1)) + enclon/2.**17)
#print "DL: %f nl: %f m: %f rlon: %f" % (dl, nl_rlat, m, rlon)
#print "evenpos: %x, oddpos: %x, mostrecent: %i" % (evenpos[1], oddpos[1], mostrecent)
#print "DL: %f nl: %f m: %f rlon: %f" % (dl, nl_rlat, m, rlon)
#print "evenpos: %x, oddpos: %x, mostrecent: %i" % (evenpos[1], oddpos[1], mostrecent)
if surface:
#longitudes need to be resolved to the nearest 90 degree segment to the receiver.
wat = mypos[1]
if wat < 0:
wat += 360
zone = lambda lon: 90 * (int(lon) / 90)
rlon += (zone(wat) - zone(rlon))
if surface:
#longitudes need to be resolved to the nearest 90 degree segment to the receiver.
wat = mypos[1]
if wat < 0:
wat += 360
zone = lambda lon: 90 * (int(lon) / 90)
rlon += (zone(wat) - zone(rlon))
#limit to (-180, 180)
if rlon > 180:
rlon -= 360.0
#limit to (-180, 180)
if rlon > 180:
rlon -= 360.0
return [rlat, rlon]
return [rlat, rlon]
#calculate range and bearing between two lat/lon points
#should probably throw this in the mlat py somewhere or make another lib
def range_bearing(loc_a, loc_b):
[a_lat, a_lon] = loc_a
[b_lat, b_lon] = loc_b
[a_lat, a_lon] = loc_a
[b_lat, b_lon] = loc_b
esquared = (1/298.257223563)*(2-(1/298.257223563))
earth_radius_mi = 3963.19059 * (math.pi / 180)
esquared = (1/298.257223563)*(2-(1/298.257223563))
earth_radius_mi = 3963.19059 * (math.pi / 180)
delta_lat = b_lat - a_lat
delta_lon = b_lon - a_lon
delta_lat = b_lat - a_lat
delta_lon = b_lon - a_lon
avg_lat = ((a_lat + b_lat) / 2.0) * math.pi / 180
avg_lat = ((a_lat + b_lat) / 2.0) * math.pi / 180
R1 = earth_radius_mi*(1.0-esquared)/pow((1.0-esquared*pow(math.sin(avg_lat),2)),1.5)
R1 = earth_radius_mi*(1.0-esquared)/pow((1.0-esquared*pow(math.sin(avg_lat),2)),1.5)
R2 = earth_radius_mi/math.sqrt(1.0-esquared*pow(math.sin(avg_lat),2))
R2 = earth_radius_mi/math.sqrt(1.0-esquared*pow(math.sin(avg_lat),2))
distance_North = R1*delta_lat
distance_East = R2*math.cos(avg_lat)*delta_lon
distance_North = R1*delta_lat
distance_East = R2*math.cos(avg_lat)*delta_lon
bearing = math.atan2(distance_East,distance_North) * (180.0 / math.pi)
if bearing < 0.0:
bearing += 360.0
bearing = math.atan2(distance_East,distance_North) * (180.0 / math.pi)
if bearing < 0.0:
bearing += 360.0
rnge = math.hypot(distance_East,distance_North)
return [rnge, bearing]
rnge = math.hypot(distance_East,distance_North)
return [rnge, bearing]
class cpr_decoder:
def __init__(self, my_location):
self.my_location = my_location
self.evenlist = {}
self.oddlist = {}
self.evenlist_sfc = {}
self.oddlist_sfc = {}
def __init__(self, my_location):
self.my_location = my_location
self.evenlist = {}
self.oddlist = {}
self.evenlist_sfc = {}
self.oddlist_sfc = {}
def set_location(self, new_location):
self.my_location = new_location
def set_location(self, new_location):
self.my_location = new_location
def weed_poslists(self):
for poslist in [self.evenlist, self.oddlist]:
for key, item in tuple(poslist.items()):
if time.time() - item[2] > 10:
del poslist[key]
for poslist in [self.evenlist_sfc, self.oddlist_sfc]:
for key, item in tuple(poslist.items()):
if time.time() - item[2] > 25:
del poslist[key]
def weed_poslists(self):
for poslist in [self.evenlist, self.oddlist]:
for key, item in poslist.items():
if time.time() - item[2] > 10:
del poslist[key]
for poslist in [self.evenlist_sfc, self.oddlist_sfc]:
for key, item in poslist.items():
if time.time() - item[2] > 25:
del poslist[key]
def decode(self, icao24, encoded_lat, encoded_lon, cpr_format, surface):
if surface:
oddlist = self.oddlist_sfc
evenlist = self.evenlist_sfc
else:
oddlist = self.oddlist
evenlist = self.evenlist
def decode(self, icao24, encoded_lat, encoded_lon, cpr_format, surface):
if surface:
oddlist = self.oddlist_sfc
evenlist = self.evenlist_sfc
else:
oddlist = self.oddlist
evenlist = self.evenlist
#add the info to the position reports list for global decoding
if cpr_format==1:
oddlist[icao24] = [encoded_lat, encoded_lon, time.time()]
else:
evenlist[icao24] = [encoded_lat, encoded_lon, time.time()]
#add the info to the position reports list for global decoding
if cpr_format==1:
oddlist[icao24] = [encoded_lat, encoded_lon, time.time()]
else:
evenlist[icao24] = [encoded_lat, encoded_lon, time.time()]
[decoded_lat, decoded_lon] = [None, None]
[decoded_lat, decoded_lon] = [None, None]
#okay, let's traverse the lists and weed out those entries that are older than 10 seconds
self.weed_poslists()
#okay, let's traverse the lists and weed out those entries that are older than 10 seconds
self.weed_poslists()
if (icao24 in evenlist) \
and (icao24 in oddlist):
newer = (oddlist[icao24][2] - evenlist[icao24][2]) > 0 #figure out which report is newer
[decoded_lat, decoded_lon] = cpr_resolve_global(evenlist[icao24][0:2], oddlist[icao24][0:2], self.my_location, newer, surface) #do a global decode
else:
raise CPRNoPositionError
if (icao24 in evenlist) \
and (icao24 in oddlist):
newer = (oddlist[icao24][2] - evenlist[icao24][2]) > 0 #figure out which report is newer
[decoded_lat, decoded_lon] = cpr_resolve_global(evenlist[icao24][0:2], oddlist[icao24][0:2], self.my_location, newer, surface) #do a global decode
else:
raise CPRNoPositionError
if self.my_location is not None:
[rnge, bearing] = range_bearing(self.my_location, [decoded_lat, decoded_lon])
else:
rnge = None
bearing = None
if self.my_location is not None:
[rnge, bearing] = range_bearing(self.my_location, [decoded_lat, decoded_lon])
else:
rnge = None
bearing = None
return [decoded_lat, decoded_lon, rnge, bearing]
return [decoded_lat, decoded_lon, rnge, bearing]
#encode CPR position
def cpr_encode(lat, lon, ctype, surface):
if surface is True:
scalar = 2.**19
else:
scalar = 2.**17
if surface is True:
scalar = 2.**19
else:
scalar = 2.**17
#encode using 360 constant for segment size.
dlati = dlat(ctype, False)
yz = math.floor(scalar * ((lat % dlati)/dlati) + 0.5)
rlat = dlati * ((yz / scalar) + math.floor(lat / dlati))
#encode using 360 constant for segment size.
dlati = dlat(ctype, False)
yz = math.floor(scalar * ((lat % dlati)/dlati) + 0.5)
rlat = dlati * ((yz / scalar) + math.floor(lat / dlati))
#encode using 360 constant for segment size.
dloni = dlon(lat, ctype, False)
xz = math.floor(scalar * ((lon % dloni)/dloni) + 0.5)
#encode using 360 constant for segment size.
dloni = dlon(lat, ctype, False)
xz = math.floor(scalar * ((lon % dloni)/dloni) + 0.5)
yz = int(yz) & (2**17-1)
xz = int(xz) & (2**17-1)
yz = int(yz) & (2**17-1)
xz = int(xz) & (2**17-1)
return (yz, xz) #lat, lon
return (yz, xz) #lat, lon
if __name__ == '__main__':
import sys, random
rounds = 10001
threshold = 1e-3 #0.001 deg lat/lon
#this accuracy is highly dependent on latitude, since at high
#latitudes the corresponding error in longitude is greater
import sys, random
rounds = 10001
threshold = 1e-3 #0.001 deg lat/lon
#this accuracy is highly dependent on latitude, since at high
#latitudes the corresponding error in longitude is greater
bs = 0
surface = False
bs = 0
surface = False
lats = [i/(rounds/170.)-85 for i in range(0,rounds)]
lons = [i/(rounds/360.)-180 for i in range(0,rounds)]
lats = [i/(rounds/170.)-85 for i in range(0,rounds)]
lons = [i/(rounds/360.)-180 for i in range(0,rounds)]
for i in range(0, rounds):
even_lat = lats[i]
#even_lat = random.uniform(-85, 85)
even_lon = lons[i]
#even_lon = random.uniform(-180, 180)
odd_lat = even_lat + 1e-3
odd_lon = min(even_lon + 1e-3, 180)
decoder = cpr_decoder([odd_lat, odd_lon])
for i in range(0, rounds):
even_lat = lats[i]
#even_lat = random.uniform(-85, 85)
even_lon = lons[i]
#even_lon = random.uniform(-180, 180)
odd_lat = even_lat + 1e-3
odd_lon = min(even_lon + 1e-3, 180)
decoder = cpr_decoder([odd_lat, odd_lon])
#encode that position
(evenenclat, evenenclon) = cpr_encode(even_lat, even_lon, False, surface)
(oddenclat, oddenclon) = cpr_encode(odd_lat, odd_lon, True, surface)
#encode that position
(evenenclat, evenenclon) = cpr_encode(even_lat, even_lon, False, surface)
(oddenclat, oddenclon) = cpr_encode(odd_lat, odd_lon, True, surface)
#try to perform a global decode -- this should fail since the decoder
#only has heard one position. need two for global decoding.
icao = random.randint(0, 0xffffff)
try:
evenpos = decoder.decode(icao, evenenclat, evenenclon, False, surface)
raise Exception("CPR test failure: global decode with only one report")
except CPRNoPositionError:
pass
#try to perform a global decode -- this should fail since the decoder
#only has heard one position. need two for global decoding.
icao = random.randint(0, 0xffffff)
try:
evenpos = decoder.decode(icao, evenenclat, evenenclon, False, surface)
raise Exception("CPR test failure: global decode with only one report")
except CPRNoPositionError:
pass
#now try to do a real decode with the last packet's odd complement
#watch for a boundary straddle -- this isn't fatal, it just indicates
#that the even and odd reports lie on either side of a longitudinal boundary
#and so you can't get a position
try:
(odddeclat, odddeclon, rng, brg) = decoder.decode(icao, oddenclat, oddenclon, True, surface)
except CPRBoundaryStraddleError:
bs += 1
continue
except CPRNoPositionError:
raise Exception("CPR test failure: no decode after even/odd inputs")
#now try to do a real decode with the last packet's odd complement
#watch for a boundary straddle -- this isn't fatal, it just indicates
#that the even and odd reports lie on either side of a longitudinal boundary
#and so you can't get a position
try:
(odddeclat, odddeclon, rng, brg) = decoder.decode(icao, oddenclat, oddenclon, True, surface)
except CPRBoundaryStraddleError:
bs += 1
continue
except CPRNoPositionError:
raise Exception("CPR test failure: no decode after even/odd inputs")
if abs(odddeclat - odd_lat) > threshold or abs(odddeclon - odd_lon) > threshold:
print("F odddeclat: %f odd_lat: %f" % (odddeclat, odd_lat))
print( "F odddeclon: %f odd_lon: %f" % (odddeclon, odd_lon))
raise Exception("CPR test failure: global decode error greater than threshold")
# else:
# print("S odddeclat: %f odd_lat: %f" % (odddeclat, odd_lat))
# print("S odddeclon: %f odd_lon: %f" % (odddeclon, odd_lon))
if abs(odddeclat - odd_lat) > threshold or abs(odddeclon - odd_lon) > threshold:
print "F odddeclat: %f odd_lat: %f" % (odddeclat, odd_lat)
print "F odddeclon: %f odd_lon: %f" % (odddeclon, odd_lon)
raise Exception("CPR test failure: global decode error greater than threshold")
# else:
# print "S odddeclat: %f odd_lat: %f" % (odddeclat, odd_lat)
# print "S odddeclon: %f odd_lon: %f" % (odddeclon, odd_lon)
nexteven_lat = odd_lat + 1e-3
nexteven_lon = min(odd_lon + 1e-3, 180)
nexteven_lat = odd_lat + 1e-3
nexteven_lon = min(odd_lon + 1e-3, 180)
(nexteven_enclat, nexteven_enclon) = cpr_encode(nexteven_lat, nexteven_lon, False, surface)
(nexteven_enclat, nexteven_enclon) = cpr_encode(nexteven_lat, nexteven_lon, False, surface)
#try a locally-referenced decode
try:
(evendeclat, evendeclon) = cpr_resolve_local([even_lat, even_lon], [nexteven_enclat, nexteven_enclon], False, surface)
except CPRNoPositionError:
raise Exception("CPR test failure: local decode failure to resolve")
#try a locally-referenced decode
try:
(evendeclat, evendeclon) = cpr_resolve_local([even_lat, even_lon], [nexteven_enclat, nexteven_enclon], False, surface)
except CPRNoPositionError:
raise Exception("CPR test failure: local decode failure to resolve")
#check to see if the positions were valid
if abs(evendeclat - nexteven_lat) > threshold or abs(evendeclon - nexteven_lon) > threshold:
print("F evendeclat: %f nexteven_lat: %f evenlat: %f" % (evendeclat, nexteven_lat, even_lat))
print("F evendeclon: %f nexteven_lon: %f evenlon: %f" % (evendeclon, nexteven_lon, even_lon))
raise Exception("CPR test failure: local decode error greater than threshold")
#check to see if the positions were valid
if abs(evendeclat - nexteven_lat) > threshold or abs(evendeclon - nexteven_lon) > threshold:
print "F evendeclat: %f nexteven_lat: %f evenlat: %f" % (evendeclat, nexteven_lat, even_lat)
print "F evendeclon: %f nexteven_lon: %f evenlon: %f" % (evendeclon, nexteven_lon, even_lon)
raise Exception("CPR test failure: local decode error greater than threshold")
print("CPR test successful. There were %i boundary straddles over %i rounds." % (bs, rounds))
print "CPR test successful. There were %i boundary straddles over %i rounds." % (bs, rounds)

View File

@ -18,48 +18,50 @@ class output_flightgear:
def __init__(self, cprdec, hostname, port, pub):
self.hostname = hostname
self.port = port
self.localpos = localpos
self.positions = {}
self.velocities = {}
self.callsigns = {}
self._cpr = cprdec
self.sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
# self.sock.connect((self.hostname, self.port))
pub.subscribe("type17_dl", self.output)
self.sock.connect((self.hostname, self.port))
pub.subscribe("type17_dl", output)
def output(self, msg):
try:
msgtype = msg.data["df"]
if msgtype == 17: #ADS-B report
icao24 = msg.data["aa"]
bdsreg = msg.data["me"].get_type()
if bdsreg == 0x08: #ident packet
(ident, actype) = air_modes.parseBDS08(msg.data)
(ident, actype) = air_modes.parseBDS08(data)
#select model based on actype
self.callsigns[icao24] = [ident, actype]
elif bdsreg == 0x06: #BDS0,6 pos
[ground_track, decoded_lat, decoded_lon, rnge, bearing] = air_modes.parseBDS06(msg.data, self._cpr)
[ground_track, decoded_lat, decoded_lon, rnge, bearing] = air_modes.parseBDS06(data, self._cpr)
self.positions[icao24] = [decoded_lat, decoded_lon, 0]
self.update(icao24)
elif bdsreg == 0x05: #BDS0,5 pos
[altitude, decoded_lat, decoded_lon, rnge, bearing] = air_modes.parseBDS05(msg.data, self._cpr)
[altitude, decoded_lat, decoded_lon, rnge, bearing] = air_modes.parseBDS05(data, self._cpr)
self.positions[icao24] = [decoded_lat, decoded_lon, altitude]
self.update(icao24)
elif bdsreg == 0x09: #velocity
subtype = msg.data["bds09"].get_type()
subtype = data["bds09"].get_type()
if subtype == 0:
[velocity, heading, vert_spd, turnrate] = air_modes.parseBDS09_0(msg.data)
[velocity, heading, vert_spd, turnrate] = air_modes.parseBDS09_0(data)
elif subtype == 1:
[velocity, heading, vert_spd] = air_modes.parseBDS09_1(msg.data)
[velocity, heading, vert_spd] = air_modes.parseBDS09_1(data)
turnrate = 0
else:
return
self.velocities[icao24] = [velocity, heading, vert_spd, turnrate]
except ADSBError:
pass
@ -69,7 +71,7 @@ class output_flightgear:
and (icao24 in self.velocities)\
and (icao24 in self.callsigns)
if complete:
print("FG update: %s" % (self.callsigns[icao24][0]))
print "FG update: %s" % (self.callsigns[icao24][0])
msg = fg_posmsg(self.callsigns[icao24][0],
self.callsigns[icao24][1],
self.positions[icao24][0],
@ -80,7 +82,7 @@ class output_flightgear:
self.velocities[icao24][2],
self.velocities[icao24][3]).pack()
self.sock.sendto(msg, (self.hostname, self.port))
self.sock.send(msg)
class fg_header:
def __init__(self):
@ -119,7 +121,7 @@ modelmap = { None: 'Aircraft/777-200/Models/777-200ER.xml'
"SMALL": 'Aircraft/CitationX/Models/Citation-X.xml',
"LARGE": 'Aircraft/CRJ700-family/Models/CRJ700.xml',
"LARGE HIGH VORTEX": 'Aircraft/757-200/Models/757-200.xml',
"HEAVY": 'Aircraft/IDG-A32X/Models/A320neo-CFM.xml',
"HEAVY": 'Aircraft/747-200/Models/boeing747-200.xml',
"HIGH PERFORMANCE": 'Aircraft/SR71-BlackBird/Models/Blackbird-SR71B.xml', #yeah i know
"ROTORCRAFT": 'Aircraft/ec130/Models/ec130b4.xml',
"GLIDER": 'Aircraft/ASK21-MI/Models/ask21mi.xml',

View File

@ -1,21 +0,0 @@
#!/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)

View File

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

View File

@ -2,7 +2,7 @@
#HTML template for Mode S map display
#Nick Foster, 2013
def html_template(my_apikey, my_position, json_file):
def html_template(my_position, json_file):
if my_position is None:
my_position = [37, -122]
@ -14,20 +14,18 @@ def html_template(my_apikey, my_position, json_file):
<meta http-equiv="content-type" content="text/html;charset=utf-8" />
<style type="text/css">
.labels {
color: blue;
color: red;
background-color: white;
font-family: "Lucida Grande", "Arial", sans-serif;
font-size: 13px;
font-size: 10px;
font-weight: bold;
text-align: center;
width: 70px;
border: none;
width: 40px;
border: 2px solid black;
white-space: nowrap;
}
</style>
<script type="text/javascript" src="http://maps.google.com/maps/api/js?key=%s">
</script>
<script type="text/javascript" src="https://raw.githubusercontent.com/googlemaps/v3-utility-library/master/markerwithlabel/src/markerwithlabel.js">
<script type="text/javascript" src="http://maps.google.com/maps/api/js?sensor=false">
</script>
<script type="text/javascript">
var map;
@ -53,6 +51,7 @@ def html_template(my_apikey, my_position, json_file):
};
function jsonp_callback(results) { // from JSONP
clearMarkers();
airplanes = {};
for (var i = 0; i < results.length; i++) {
airplanes[results[i].icao] = {
@ -66,20 +65,10 @@ def html_template(my_apikey, my_position, json_file):
highlight: results[i].highlight
};
}
// clearMarkers();
refreshIcons();
}
function refreshIcons() {
//prune the list
for(var i = 0; i < planes.length; i++) {
icao = planes[i].get("icao")
if(!(icao in airplanes)) {
planes[i].setMap(null)
planes.splice(i, 1);
};
};
for (var airplane in airplanes) {
if (airplanes[airplane].highlight != 0) {
icon_file = "http://www.nerdnetworks.org/~bistromath/airplane_sprite_highlight.png";
@ -94,40 +83,23 @@ def html_template(my_apikey, my_position, json_file):
//scaledSize: new google.maps.Size(4608,126)
};
if (airplanes[airplane].ident.length != 8) {
identstr = airplane;
} else {
identstr = airplanes[airplane].ident;
identstr = airplanes[airplane].ident;
if (identstr === "" || !identstr) {
identstr = airplanes[airplane].icao;
};
var planeOptions = {
map: map,
position: airplanes[airplane].center,
icao: airplane,
icon: plane_icon,
//label content meaningless unless you use the MarkerWithLabel class from the Maps Utility Library
labelContent: identstr,
labelAnchor: new google.maps.Point(35, -32),
labelAnchor: new google.maps.Point(64, 0),
labelClass: "labels",
labelStyle: {opacity: 0.75}
};
var i = 0;
for(i; i<planes.length; i++) {
if(planes[i].get("icao") == airplane) {
planes[i].setPosition(airplanes[airplane].center);
if(planes[i].get("icon") != plane_icon) {
planes[i].setIcon(plane_icon); //handles highlight and heading
};
if(planes[i].get("labelContent") != identstr) {
planes[i].set("labelContent", identstr);
};
break;
};
};
if(i == planes.length) {
planeMarker = new MarkerWithLabel(planeOptions);
planes.push(planeMarker);
};
planeMarker = new google.maps.Marker(planeOptions);
planes.push(planeMarker);
};
};
@ -152,4 +124,4 @@ def html_template(my_apikey, my_position, json_file):
<div id="map_canvas" style="width:100%%; height:100%%">
</div>
</body>
</html>""" % (my_apikey, my_position[0], my_position[1], json_file)
</html>""" % (my_position[0], my_position[1], json_file)

View File

@ -20,7 +20,7 @@
#
import sqlite3
import math, threading, time
import string, math, threading, time
class output_kml(threading.Thread):
def __init__(self, filename, dbname, localpos, lock, timeout=5):
@ -39,8 +39,8 @@ 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:
time.sleep(self._timeout)
self.writekml()
time.sleep(self._timeout)
self._db.close()
self._db = None
@ -89,7 +89,7 @@ class output_kml(threading.Thread):
lon_out = center_lon + math.degrees(math.atan2(math.sin(bearing)*math.sin(tmp0)*math.cos(lat_rad), math.cos(tmp0)-math.sin(lat_rad)*math.sin(math.radians(lat_out))))
retstr += " %.8f,%.8f, 0" % (lon_out, lat_out,)
retstr = retstr.lstrip()
retstr = string.lstrip(retstr)
return retstr
def genkml(self):
@ -132,7 +132,7 @@ class output_kml(threading.Thread):
for pos in track:
trackstr += " %f,%f,%f" % (pos[4], pos[3], pos[2]*0.3048)
trackstr = trackstr.lstrip()
trackstr = string.lstrip(trackstr)
else:
alt = 0
metric_alt = 0
@ -188,7 +188,7 @@ class output_jsonp(output_kml):
# 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')"
q = "select distinct icao from positions where seen > datetime('now', '-5 minute')"
c = self._db.cursor()
self.locked_execute(c, q)
icaolist = c.fetchall()

View File

@ -55,8 +55,7 @@ wgs84_b2 = wgs84_b**2
#convert ECEF to lat/lon/alt without geoid correction
#returns alt in meters
def ecef2llh(ecef):
x, y, z = ecef
def ecef2llh((x,y,z)):
ep = math.sqrt((wgs84_a2 - wgs84_b2) / wgs84_b2)
p = math.sqrt(x**2+y**2)
th = math.atan2(wgs84_a*z, wgs84_b*p)
@ -72,8 +71,7 @@ def ecef2llh(ecef):
#convert lat/lon/alt coords to ECEF without geoid correction, WGS84 model
#remember that alt is in meters
def llh2ecef(lla):
lat, lon, alt = lla
def llh2ecef((lat, lon, alt)):
lat *= (math.pi / 180.0)
lon *= (math.pi / 180.0)
@ -86,8 +84,7 @@ def llh2ecef(lla):
return [x,y,z]
#do both of the above to get a geoid-corrected x,y,z position
def llh2geoid(lla):
lat, lon, alt = lla
def llh2geoid((lat, lon, alt)):
(x,y,z) = llh2ecef((lat, lon, alt + wgs84_height(lat, lon)))
return [x,y,z]
@ -188,7 +185,7 @@ if __name__ == '__main__':
10 + numpy.linalg.norm(testplane-numpy.array(llh2geoid(teststations[3]))) / c,
]
print(teststamps)
print teststamps
replies = []
for i in range(0, len(teststations)):
@ -196,7 +193,7 @@ if __name__ == '__main__':
ans = mlat(replies, testalt)
error = numpy.linalg.norm(numpy.array(llh2ecef(ans))-numpy.array(testplane))
range = numpy.linalg.norm(llh2geoid(ans)-numpy.array(testme))
print(testplane-testme)
print(ans)
print("Error: %.2fm" % (error))
print("Range: %.2fkm (from first station in list)" % (range/1000))
print testplane-testme
print ans
print "Error: %.2fm" % (error)
print "Range: %.2fkm (from first station in list)" % (range/1000)

View File

@ -1,87 +0,0 @@
#!/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

View File

@ -20,6 +20,7 @@
#
import time, os, sys
from string import split, join
import air_modes
from air_modes.exceptions import *
import math
@ -43,7 +44,7 @@ class output_print:
def _print(self, msg):
if self._callback is None:
print(msg)
print msg
else:
self._callback(msg)
@ -80,7 +81,7 @@ class output_print:
except ADSBError:
return
if msg.data["vs"] == 1:
if msg.data["vs"] is 1:
retstr += " (aircraft is on the ground)"
self._print(retstr)

View File

@ -20,7 +20,8 @@
#
import time, os, sys
from air_modes.altitude import decode_alt
from string import split, join
from altitude import decode_alt
import math
import air_modes
from air_modes.exceptions import *
@ -31,14 +32,14 @@ class data_field:
self.data = data
self.fields = self.parse()
dtypes = { }
types = { }
offset = 1 #field offset applied to all fields. used for offsetting
#subtypes to reconcile with the spec. Really just for readability.
#get a particular field from the data
def __getitem__(self, fieldname):
mytype = self.get_type()
if mytype in self.dtypes:
if mytype in self.types:
if fieldname in self.fields: #verify it exists in this packet type
return self.fields[fieldname]
else:
@ -51,9 +52,9 @@ class data_field:
def parse(self):
fields = {}
mytype = self.get_type()
if mytype in self.dtypes:
for field in self.dtypes[mytype]:
bits = self.dtypes[self.get_type()][field]
if mytype in self.types:
for field in self.types[mytype]:
bits = self.types[self.get_type()][field]
if len(bits) == 3:
obj = bits[2](self.get_bits(bits[0], bits[1]))
fields.update(obj.parse())
@ -92,7 +93,7 @@ class data_field:
class bds09_reply(data_field):
offset = 6
dtypes = { #BDS0,9 subtype 0
types = { #BDS0,9 subtype 0
0: {"sub": (6,3), "dew": (10,1), "vew": (11,11), "dns": (22,1),
"vns": (23,11), "str": (34,1), "tr": (35,6), "dvr": (41,1),
"vr": (42,9)},
@ -122,7 +123,7 @@ class bds09_reply(data_field):
class me_reply(data_field):
#types in this format are listed by BDS register
#TODO: add comments explaining these fields
dtypes = { 0x05: {"ftc": (1,5), "ss": (6,2), "saf": (8,1), "alt": (9,12), "time": (21,1), "cpr": (22,1), "lat": (23,17), "lon": (40,17)}, #airborne position
types = { 0x05: {"ftc": (1,5), "ss": (6,2), "saf": (8,1), "alt": (9,12), "time": (21,1), "cpr": (22,1), "lat": (23,17), "lon": (40,17)}, #airborne position
0x06: {"ftc": (1,5), "mvt": (6,7), "gts": (13,1), "gtk": (14,7), "time": (21,1), "cpr": (22,1), "lat": (23,17), "lon": (40,17)}, #surface position
0x07: {"ftc": (1,5),}, #TODO extended squitter status
0x08: {"ftc": (1,5), "cat": (6,3), "ident": (9,48)}, #extended squitter identification and type
@ -156,7 +157,7 @@ class me_reply(data_field):
#resolves the TCAS reply types from TTI info
class tcas_reply(data_field):
offset = 61
dtypes = { 0: {"tti": (61,2)}, #UNKNOWN
types = { 0: {"tti": (61,2)}, #UNKNOWN
1: {"tti": (61,2), "tid": (63,26)},
2: {"tti": (61,2), "tida": (63,13), "tidr": (76,7), "tidb": (83,6)}
}
@ -170,7 +171,7 @@ class tcas_reply(data_field):
class mb_reply(data_field):
offset = 33 #fields offset by 33 to match documentation
#types are based on bds1 subfield
dtypes = { 0: {"bds1": (33,4), "bds2": (37,4)}, #TODO
types = { 0: {"bds1": (33,4), "bds2": (37,4)}, #TODO
1: {"bds1": (33,4), "bds2": (37,4), "cfs": (41,4), "acs": (45,20), "bcs": (65,16), "ecs": (81,8)},
2: {"bds1": (33,4), "bds2": (37,4), "ais": (41,48)},
3: {"bds1": (33,4), "bds2": (37,4), "ara": (41,14), "rac": (55,4), "rat": (59,1),
@ -194,7 +195,7 @@ class mb_reply(data_field):
class mv_reply(data_field):
offset = 33
dtypes = { "ara": (41,14), "mte": (60,1), "rac": (55,4), "rat": (59,1),
types = { "ara": (41,14), "mte": (60,1), "rac": (55,4), "rat": (59,1),
"vds": (33,8), "vds1": (33,4), "vds2": (37,4)
}
@ -210,7 +211,7 @@ class mv_reply(data_field):
#the whole Mode S packet type
class modes_reply(data_field):
dtypes = { 0: {"df": (1,5), "vs": (6,1), "cc": (7,1), "sl": (9,3), "ri": (14,4), "ac": (20,13), "ap": (33,24)},
types = { 0: {"df": (1,5), "vs": (6,1), "cc": (7,1), "sl": (9,3), "ri": (14,4), "ac": (20,13), "ap": (33,24)},
4: {"df": (1,5), "fs": (6,3), "dr": (9,5), "um": (14,6), "ac": (20,13), "ap": (33,24)},
5: {"df": (1,5), "fs": (6,3), "dr": (9,5), "um": (14,6), "id": (20,13), "ap": (33,24)},
11: {"df": (1,5), "ca": (6,3), "aa": (9,24), "pi": (33,24)},
@ -334,13 +335,13 @@ def parseBDS09_1(data):
ew = bool(data["dew"])
subtype = data["sub"]
if subtype == 0x02:
ns_vel *= 4
ew_vel *= 4
ns_vel <<= 2
ew_vel <<= 2
velocity = math.hypot(ns_vel, ew_vel)
if ew:
ew_vel = 0 - ew_vel
if ns_vel == 0:
heading = 0
else:
@ -422,12 +423,12 @@ def parse_TCAS_CRM(data):
def make_parser(pub):
publisher = pub
def publish(message):
[data, ecc, reference, int_timestamp, frac_timestamp] = message.split()
[data, ecc, reference, timestamp] = message.split()
try:
ret = air_modes.modes_report(modes_reply(int(data, 16)),
int(ecc, 16),
10.0*math.log10(max(1e-8,float(reference))),
air_modes.stamp(int(int_timestamp), float(frac_timestamp)))
20.0*math.log10(float(reference)),
air_modes.stamp(0, float(timestamp)))
pub["modes_dl"] = ret
pub["type%i_dl" % ret.data.get_type()] = ret
except ADSBError:

59
python/qa_gr-air-modes.py Executable file
View File

@ -0,0 +1,59 @@
#!/usr/bin/env python
#
# Copyright 2004,2007 Free Software Foundation, Inc.
#
# This file is part of GNU Radio
#
# GNU Radio is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3, or (at your option)
# any later version.
#
# GNU Radio is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with GNU Radio; see the file COPYING. If not, write to
# the Free Software Foundation, Inc., 51 Franklin Street,
# Boston, MA 02110-1301, USA.
#
from gnuradio import gr, gr_unittest
import gr-air-modes_swig
class qa_gr-air-modes (gr_unittest.TestCase):
def setUp (self):
self.tb = gr.top_block ()
def tearDown (self):
self.tb = None
def test_001_square_ff (self):
src_data = (-3, 4, -5.5, 2, 3)
expected_result = (9, 16, 30.25, 4, 9)
src = gr.vector_source_f (src_data)
sqr = gr-air-modes_swig.square_ff ()
dst = gr.vector_sink_f ()
self.tb.connect (src, sqr)
self.tb.connect (sqr, dst)
self.tb.run ()
result_data = dst.data ()
self.assertFloatTuplesAlmostEqual (expected_result, result_data, 6)
def test_002_square2_ff (self):
src_data = (-3, 4, -5.5, 2, 3)
expected_result = (9, 16, 30.25, 4, 9)
src = gr.vector_source_f (src_data)
sqr = gr-air-modes_swig.square2_ff ()
dst = gr.vector_sink_f ()
self.tb.connect (src, sqr)
self.tb.connect (sqr, dst)
self.tb.run ()
result_data = dst.data ()
self.assertFloatTuplesAlmostEqual (expected_result, result_data, 6)
if __name__ == '__main__':
gr_unittest.main ()

View File

@ -23,11 +23,10 @@
# You pass it options, it gives you data.
# It uses the pubsub interface to allow clients to subscribe to its data feeds.
from gnuradio import gr, gru, eng_notation, filter, blocks
from gnuradio import gr, gru, eng_notation, filter
from gnuradio.filter import optfir
from gnuradio.eng_option import eng_option
from gnuradio.gr.pubsub import pubsub
from gnuradio.filter import pfb
from optparse import OptionParser, OptionGroup
import air_modes
import zmq
@ -46,15 +45,7 @@ class modes_radio (gr.top_block, pubsub):
self._resample = None
self._setup_source(options)
if self._rate < 4e6:
self._resample = pfb.arb_resampler_ccf(4.e6/self._rate)
self._rx_rate = 4e6
else:
self._rx_rate = self._rate
self._rx_path = air_modes.rx_path(self._rx_rate, options.threshold,
self._queue, options.pmf, options.dcblock)
self._rx_path = air_modes.rx_path(self._rate, options.threshold, self._queue, options.pmf)
#now subscribe to set various options via pubsub
self.subscribe("freq", self.set_freq)
@ -89,7 +80,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]")
@ -111,72 +102,45 @@ 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=7.0,
group.add_option("-T", "--threshold", type="eng_float", default=5.0,
help="set pulse detection threshold above noise in dB [default=%default]")
group.add_option("-p","--pmf", action="store_true", default=True,
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=="uhd" or self._options.source=="osmocom"
return self._options.source is 'uhd' or self._options.source is 'osmocom'
def set_freq(self, freq):
return self._u.set_center_freq(freq, 0) if self.live_source() else 0
return self._u.set_center_freq(freq, 0) if live_source() else 0
def set_gain(self, gain):
if self.live_source():
self._u.set_gain(gain)
print("Gain is %f" % self.get_gain())
return self.get_gain()
return self._u.set_gain(gain) if live_source() else 0
def set_rate(self, rate):
if(rate < 4e6 and self._rate > 4e6):
raise NotImplementedError("Lowering rate <4e6Msps not currently supported.")
if(rate < 4e6):
self._resample.set_rate(4e6/rate)
self._rx_rate = 4e6
else:
self._rx_rate = rate
self._rx_path.set_rate(self._rx_rate)
if self._options.source in ("osmocom"):
return self._u.set_sample_rate(rate)
if self._options.source in ("uhd"):
return self._u.set_rate(rate)
else:
return 0
def set_threshold(self, threshold):
self._rx_path.set_threshold(threshold)
return self._u.set_rate(rate) if live_source() else 0
def get_freq(self, freq):
return self._u.get_center_freq(freq, 0) if self.live_source() else 1090e6
return self._u.get_center_freq(freq, 0) if live_source() else 1090e6
def get_gain(self, gain):
return self._u.get_gain() if live_source() else 0
def get_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 get_rate(self, rate):
return self._u.get_rate() if live_source() else self._rate
def _setup_source(self, options):
if options.source == "uhd":
#UHD source by default
from gnuradio import uhd
self._u = uhd.usrp_source(
options.args,
uhd.stream_args(
cpu_format="fc32",
channels=range(1),
),
)
self._u = uhd.single_usrp_source(options.args, uhd.io_type_t.COMPLEX_FLOAT32, 1)
if(options.subdev):
self._u.set_subdev_spec(options.subdev, 0)
if not self._u.set_center_freq(options.freq):
print("Failed to set initial frequency")
print "Failed to set initial frequency"
#check for GPSDO
#if you have a GPSDO, UHD will automatically set the timestamp to UTC time
@ -194,30 +158,37 @@ class modes_radio (gr.top_block, pubsub):
g = self._u.get_gain_range()
options.gain = (g.start()+g.stop()) / 2.0
print("Setting gain to %i" % options.gain)
print "Setting gain to %i" % options.gain
self._u.set_gain(options.gain)
print("Gain is %i" % self._u.get_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(options.args)
self._u = osmosdr.source_c(options.args)
# self._u.set_sample_rate(3.2e6) #fixed for RTL dongles
self._u.set_sample_rate(options.rate)
if not self._u.set_center_freq(options.freq):
print("Failed to set initial frequency")
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
self._u.set_gain(options.gain)
print("Gain is %i" % self._u.get_gain())
###DO NOT COMMIT
self._u.set_gain(14, "RF", 0)
self._u.set_gain(40, "IF", 0)
#self._u.set_gain(14, "BB", 0)
###DO NOT COMMIT
# self._u.set_gain(options.gain)
print "Gain is %i" % self._u.get_gain()
#Note: this should only come into play if using an RTLSDR.
# lpfiltcoeffs = gr.firdes.low_pass(1, 5*3.2e6, 1.6e6, 300e3)
# self._resample = filter.rational_resampler_ccf(interpolation=5, decimation=4, taps=lpfiltcoeffs)
else:
#semantically detect whether it's ip.ip.ip.ip:port or filename
if ':' in options.source:
@ -225,16 +196,13 @@ class modes_radio (gr.top_block, pubsub):
ip, port = re.search("(.*)\:(\d{1,5})", options.source).groups()
except:
raise Exception("Please input UDP source e.g. 192.168.10.1:12345")
self._u = blocks.udp_source(gr.sizeof_gr_complex, ip, int(port))
print("Using UDP source %s:%s" % (ip, port))
self._u = gr.udp_source(gr.sizeof_gr_complex, ip, int(port))
print "Using UDP source %s:%s" % (ip, port)
else:
self._u = blocks.file_source(gr.sizeof_gr_complex, options.source)
print("Using file source %s" % options.source)
self._u = gr.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" % (options.rate,)
def close(self):
self.stop()
self.wait()
self._sender.close()
self._u = None

View File

@ -21,6 +21,7 @@
import time, os, sys, socket
from string import split, join
from datetime import *
class raw_server:
@ -40,12 +41,12 @@ class raw_server:
conn.send(msg)
except socket.error:
self._conns.remove(conn)
print("Connections: ", len(self._conns))
print "Connections: ", len(self._conns)
def add_pending_conns(self):
try:
conn, addr = self._s.accept()
self._conns.append(conn)
print("Connections: ", len(self._conns))
print "Connections: ", len(self._conns)
except socket.error:
pass

View File

@ -19,12 +19,12 @@
# Boston, MA 02110-1301, USA.
#
from gnuradio import gr, blocks, filter
import air_modes
from gnuradio import gr, blocks
import air_modes_swig
class rx_path(gr.hier_block2):
def __init__(self, rate, threshold, queue, use_pmf=False, use_dcblock=False):
def __init__(self, rate, threshold, queue, use_pmf=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,15 +35,9 @@ class rx_path(gr.hier_block2):
self._spc = int(rate/2e6)
# Convert incoming I/Q baseband to amplitude
self._demod = blocks.complex_to_mag_squared()
if use_dcblock:
self._dcblock = filter.dc_blocker_cc(100*self._spc,False)
self.connect(self, self._dcblock, self._demod)
else:
self.connect(self, self._demod)
self._dcblock = None
self._demod = blocks.complex_to_mag()
self._bb = self._demod
# Pulse matched filter for 0.5us pulses
if use_pmf:
self._pmf = blocks.moving_average_ff(self._spc, 1.0/self._spc)#, self._rate)
@ -54,24 +48,24 @@ class rx_path(gr.hier_block2):
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.preamble(self._rate, self._threshold)
self._sync = air_modes_swig.modes_preamble(self._rate, self._threshold)
# Slice Mode-S bits and send to message queue
self._slicer = air_modes.slicer(self._queue)
self._slicer = air_modes_swig.modes_slicer(self._rate, 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(int(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)
@ -83,6 +77,6 @@ class rx_path(gr.hier_block2):
def get_pmf(self, pmf):
return not (self._bb == self._demod)
def get_threshold(self):
def get_threshold(self, threshold):
return self._sync.get_threshold()

View File

@ -21,8 +21,9 @@
import time, os, sys, socket
from string import split, join
import air_modes
import datetime
from datetime import *
from air_modes.exceptions import *
import threading
@ -62,7 +63,7 @@ class output_sbs1:
#it could be cleaner if there were separate output_* fns
#but this works
for i in (0, 4, 5, 11, 17):
pub.subscribe("type%i_dl" % i, self.output)
pub.subscribe("type%i_dl" % i, output)
#spawn thread to add new connections as they come in
self._runner = dumb_task_runner(self.add_pending_conns, 0.1)
@ -82,7 +83,7 @@ class output_sbs1:
# dictionary is getting too large.
if len(self._aircraft_id_map) > 1e4:
minimum = min(self._aircraft_id_map.values()) + (len(self._aircraft_id_map) - 1e4)
for icao, _id in dict(self._aircraft_id_map).iteritems():
for icao, _id in self._aircraft_id_map:
if _id < minimum:
del self._aircraft_id_map[icao]
@ -93,12 +94,11 @@ class output_sbs1:
try:
sbs1_msg = self.parse(msg)
if sbs1_msg is not None:
sbs1_bytes = sbs1_msg.encode('utf-8')
for conn in self._conns[:]: #iterate over a copy of the list
conn.send(sbs1_bytes)
conn.send(sbs1_msg)
except socket.error:
self._conns.remove(conn)
print("Connections: ", len(self._conns))
print "Connections: ", len(self._conns)
except ADSBError:
pass
@ -106,12 +106,12 @@ class output_sbs1:
try:
conn, addr = self._s.accept()
self._conns.append(conn)
print("Connections: ", len(self._conns))
print "Connections: ", len(self._conns)
except socket.error:
pass
def current_time(self):
timenow = datetime.datetime.now()
timenow = datetime.now()
return [timenow.strftime("%Y/%m/%d"), timenow.strftime("%H:%M:%S.%f")[0:-3]]
def decode_fs(self, fs):
@ -154,7 +154,7 @@ class output_sbs1:
[datestr, timestr] = self.current_time()
aircraft_id = self.get_aircraft_id(ecc)
retstr = "MSG,7,0,%i,%06X,%i,%s,%s,%s,%s,,%s,,,,,,,,,," % (aircraft_id, ecc, aircraft_id+100, datestr, timestr, datestr, timestr, air_modes.decode_alt(shortdata["ac"], True))
if shortdata["vs"]:
if vs:
retstr += "1\r\n"
else:
retstr += "0\r\n"
@ -174,7 +174,7 @@ class output_sbs1:
def pp11(self, shortdata, ecc):
[datestr, timestr] = self.current_time()
aircraft_id = self.get_aircraft_id(shortdata["aa"])
aircraft_id = self.get_aircraft_id(icao24)
return "MSG,8,0,%i,%06X,%i,%s,%s,%s,%s,,,,,,,,,,,,\r\n" % (aircraft_id, shortdata["aa"], aircraft_id+100, datestr, timestr, datestr, timestr)
def pp17(self, data):

View File

@ -20,6 +20,7 @@
#
import time, os, sys, threading
from string import split, join
import air_modes
import sqlite3
from air_modes.exceptions import *
@ -37,7 +38,7 @@ class output_sql:
c = self._db.cursor()
query = """CREATE TABLE IF NOT EXISTS "positions" (
"icao" INTEGER KEY NOT NULL,
"seen" TEXT NOT NULL,
"seen" DATETIME NOT NULL,
"alt" INTEGER,
"lat" REAL,
"lon" REAL
@ -45,7 +46,7 @@ class output_sql:
c.execute(query)
query = """CREATE TABLE IF NOT EXISTS "vectors" (
"icao" INTEGER KEY NOT NULL,
"seen" TEXT NOT NULL,
"seen" DATETIME NOT NULL,
"speed" REAL,
"heading" REAL,
"vertical" REAL
@ -95,42 +96,34 @@ class output_sql:
return query
#TODO: if there's a way to publish selective reports on upsert to distinguish,
#for instance, between a new ICAO that's just been heard, and a refresh of an
#existing ICAO, both of those would be useful publishers for the GUI model.
#otherwise, worst-case you can just refresh everything every time a report
#comes in, but it's going to use more CPU. Not likely a problem if you're only
#looking at ADS-B (no mode S) data.
#It's probably time to look back at the Qt SQL table model and see if it can be
#bent into shape for you.
def sql17(self, data):
icao24 = data["aa"]
bdsreg = data["me"].get_type()
#self["bds%.2i" % bdsreg] = icao24 #publish under "bds08", "bds06", etc.
if bdsreg == 0x08:
(msg, typename) = air_modes.parseBDS08(data)
return "INSERT OR REPLACE INTO ident (icao, ident, type) VALUES (" + "%i" % icao24 + ", '" + msg + "', '" + typename + "')"
(msg, typename) = self.parseBDS08(data)
return "INSERT OR REPLACE INTO ident (icao, ident, type) VALUES (%i, '%s', '%s')" % (icao24, msg, typename)
elif bdsreg == 0x06:
[ground_track, decoded_lat, decoded_lon, rnge, bearing] = air_modes.parseBDS06(data, self._cpr)
altitude = 0
if decoded_lat is None: #no unambiguously valid position available
raise CPRNoPositionError
else:
return "INSERT INTO positions (icao, seen, alt, lat, lon) VALUES (" + "%i" % icao24 + ", datetime('now'), " + str(altitude) + ", " + "%.6f" % decoded_lat + ", " + "%.6f" % decoded_lon + ")"
return "INSERT INTO positions (icao, seen, alt, lat, lon) VALUES (%i, datetime('now'), %i, %.6f, %.6f)" % (icao24, int(altitude), decoded_lat, decoded_lon)
elif bdsreg == 0x05:
[altitude, decoded_lat, decoded_lon, rnge, bearing] = air_modes.parseBDS05(data, self._cpr)
if decoded_lat is None: #no unambiguously valid position available
raise CPRNoPositionError
else:
return "INSERT INTO positions (icao, seen, alt, lat, lon) VALUES (" + "%i" % icao24 + ", datetime('now'), " + str(altitude) + ", " + "%.6f" % decoded_lat + ", " + "%.6f" % decoded_lon + ")"
return "INSERT INTO positions (icao, seen, alt, lat, lon) VALUES (%i, datetime('now'), %i, %.6f, %.6f)" % (icao24, int(altitude), decoded_lat, decoded_lon)
elif bdsreg == 0x09:
subtype = data["bds09"].get_type()
if subtype == 0:
[velocity, heading, vert_spd, turnrate] = air_modes.parseBDS09_0(data)
return "INSERT INTO vectors (icao, seen, speed, heading, vertical) VALUES (" + "%i" % icao24 + ", datetime('now'), " + "%.0f" % velocity + ", " + "%.0f" % heading + ", " + "%.0f" % vert_spd + ")"
[velocity, heading, vert_spd, turnrate] = self.parseBDS09_0(data)
return "INSERT INTO vectors (icao, seen, speed, heading, vertical) VALUES (%i, datetime('now'), %.0f, %.0f, %.0f)" % (icao24, velocity, heading, vert_spd)
elif subtype == 1:
[velocity, heading, vert_spd] = air_modes.parseBDS09_1(data)
return "INSERT INTO vectors (icao, seen, speed, heading, vertical) VALUES (" + "%i" % icao24 + ", datetime('now'), " + "%.0f" % velocity + ", " + "%.0f" % heading + ", " + "%.0f" % vert_spd + ")"
[velocity, heading, vert_spd] = self.parseBDS09_1(data)
return "INSERT INTO vectors (icao, seen, speed, heading, vertical) VALUES (%i, datetime('now'), %.0f, %.0f, %.0f)" % (icao24, velocity, heading, vert_spd)
else:
raise NoHandlerError

View File

@ -27,13 +27,13 @@ import time
import threading
import zmq
from gnuradio.gr.pubsub import pubsub
import queue
import Queue
class zmq_pubsub_iface(threading.Thread):
def __init__(self, context, subaddr=None, pubaddr=None):
threading.Thread.__init__(self)
#private data
self._queue = queue.Queue()
self._queue = Queue.Queue()
self._subsocket = context.socket(zmq.SUB)
self._pubsocket = context.socket(zmq.PUB)
self._subaddr = subaddr
@ -46,11 +46,11 @@ class zmq_pubsub_iface(threading.Thread):
self._pubsub = pubsub()
if self._pubaddr is not None:
for addr in self._pubaddr:
self._pubsocket.bind(addr.encode('ascii'))
self._pubsocket.bind(addr)
self._poller = zmq.Poller()
self._poller.register(self._subsocket, zmq.POLLIN)
#public data
self.shutdown = threading.Event()
self.finished = threading.Event()
@ -63,14 +63,14 @@ class zmq_pubsub_iface(threading.Thread):
if not self._subaddr:
raise Exception("No subscriber address set")
for addr in self._subaddr:
self._subsocket.connect(addr.encode('ascii'))
self._subsocket.connect(addr)
self._sub_connected = True
self._subsocket.setsockopt(zmq.SUBSCRIBE, key.encode('ascii'))
self._pubsub.subscribe(key.encode('ascii'), subscriber)
self._subsocket.setsockopt(zmq.SUBSCRIBE, key)
self._pubsub.subscribe(key, subscriber)
def unsubscribe(self, key, subscriber):
self._subsocket.setsockopt(zmq.UNSUBSCRIBE, key.encode('ascii'))
self._pubsub.unsubscribe(key.encode('ascii'), subscriber)
self._subsocket.setsockopt(zmq.UNSUBSCRIBE, key)
self._pubsub.unsubscribe(key, subscriber)
#executed from the thread context(s) of the caller(s)
#so we use a queue to push sending into the run loop
@ -79,10 +79,10 @@ class zmq_pubsub_iface(threading.Thread):
if not self._pubaddr:
raise Exception("No publisher address set")
if not self.shutdown.is_set():
self._queue.put([key.encode('ascii'), val])
self._queue.put([key, val])
def __getitem__(self, key):
return self._pubsub[key.encode('ascii')]
return self._pubsub[key]
def run(self):
done = False
@ -90,19 +90,16 @@ class zmq_pubsub_iface(threading.Thread):
if self.shutdown.is_set():
done = True
#send
while True:
try:
msg = self._queue.get(block=False)
self._pubsocket.send_multipart(msg)
except queue.Empty:
break
while not self._queue.empty():
self._pubsocket.send_multipart(self._queue.get())
#receive
if self._sub_connected:
socks = [s[0].underlying for s in self._poller.poll(timeout=0) if s[1] == zmq.POLLIN]
while self._subsocket.underlying in socks:
socks = dict(self._poller.poll(timeout=0))
while self._subsocket in socks \
and socks[self._subsocket] == zmq.POLLIN:
[address, msg] = self._subsocket.recv_multipart()
self._pubsub[address] = msg
socks = [s[0].underlying for s in self._poller.poll(timeout=0) if s[1] == zmq.POLLIN]
socks = dict(self._poller.poll(timeout=0))
#snooze
if not done:
time.sleep(0.1)
@ -117,7 +114,7 @@ class zmq_pubsub_iface(threading.Thread):
self.finished.wait(0.2)
def pr(x):
print(x)
print x
if __name__ == "__main__":
#create socket pair

View File

@ -6,8 +6,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>719</width>
<height>454</height>
<width>687</width>
<height>422</height>
</rect>
</property>
<property name="sizePolicy">
@ -102,8 +102,8 @@
<rect>
<x>10</x>
<y>20</y>
<width>241</width>
<height>281</height>
<width>236</width>
<height>251</height>
</rect>
</property>
<property name="title">
@ -307,7 +307,7 @@
<property name="geometry">
<rect>
<x>10</x>
<y>190</y>
<y>200</y>
<width>221</width>
<height>22</height>
</rect>
@ -316,29 +316,6 @@
<string>Use Pulse Matched Filtering</string>
</property>
</widget>
<widget class="QCheckBox" name="check_dcblock">
<property name="geometry">
<rect>
<x>10</x>
<y>210</y>
<width>221</width>
<height>22</height>
</rect>
</property>
<property name="text">
<string>Use DC blocking filter</string>
</property>
</widget>
<widget class="QLineEdit" name="line_my_api_key">
<property name="geometry">
<rect>
<x>90</x>
<y>250</y>
<width>121</width>
<height>27</height>
</rect>
</property>
</widget>
</widget>
<widget class="QGroupBox" name="group_output">
<property name="geometry">
@ -559,19 +536,6 @@
</property>
</widget>
</widget>
<widget class="QLabel" name="label_34">
<property name="geometry">
<rect>
<x>20</x>
<y>270</y>
<width>67</width>
<height>17</height>
</rect>
</property>
<property name="text">
<string>API Key</string>
</property>
</widget>
</widget>
<widget class="QWidget" name="dashboard">
<attribute name="title">
@ -871,12 +835,12 @@
<rect>
<x>483</x>
<y>125</y>
<width>51</width>
<width>31</width>
<height>17</height>
</rect>
</property>
<property name="text">
<string>ft/min</string>
<string>ft/s</string>
</property>
</widget>
<widget class="QLabel" name="label_31">
@ -1054,7 +1018,7 @@
<rect>
<x>0</x>
<y>0</y>
<width>719</width>
<width>687</width>
<height>25</height>
</rect>
</property>

View File

@ -31,12 +31,15 @@ include(GrPython)
########################################################################
# Setup swig generation
########################################################################
set(GR_SWIG_INCLUDE_DIRS $<TARGET_PROPERTY:gnuradio::runtime_swig,INTERFACE_INCLUDE_DIRECTORIES>)
set(GR_SWIG_TARGET_DEPS gnuradio::runtime_swig)
foreach(incdir ${GNURADIO_RUNTIME_INCLUDE_DIRS})
list(APPEND GR_SWIG_INCLUDE_DIRS ${incdir}/gnuradio/swig)
endforeach(incdir)
set(GR_SWIG_LIBRARIES air_modes)
#set(GR_SWIG_DOC_FILE ${CMAKE_CURRENT_BINARY_DIR}/gr-air-modes_swig_doc.i)
#set(GR_SWIG_DOC_DIRS ${CMAKE_CURRENT_SOURCE_DIR}/../include)
GR_SWIG_MAKE(air_modes_swig air_modes_swig.i)
GR_SWIG_MAKE(air_modes_swig air_modes.i)
########################################################################
# Install the build swig module

45
swig/air_modes.i Normal file
View File

@ -0,0 +1,45 @@
/* -*- c++ -*- */
%include "gnuradio.i" // the common stuff
%{
#include "air_modes_preamble.h"
#include "air_modes_slicer.h"
#include <gnuradio/msg_queue.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);
};
// ----------------------------------------------------------------

View File

@ -1,17 +0,0 @@
/* -*- c++ -*- */
#define AIR_MODES_API
%include "gnuradio.i"
%{
#include "gr_air_modes/preamble.h"
#include "gr_air_modes/slicer.h"
%}
%include "gr_air_modes/preamble.h"
%include "gr_air_modes/slicer.h"
GR_SWIG_BLOCK_MAGIC2(air_modes,preamble);
GR_SWIG_BLOCK_MAGIC2(air_modes,slicer);