14 Commits

Author SHA1 Message Date
Nick Foster
c2d0ca6051 Temp commit. Not yet working. 2013-07-21 17:57:37 -07:00
Nick Foster
33349efd7f GUI interface still not quite working. Two issues:
* PyQt4.QtSql's SQLite interface doesn't appear to return the same results as the SQLite browser. This is probably me depending on a bad data ordering assumption.
* QSqlQueryModel isn't set up to have the db change from underneath it, AFAIK -- have to add the ability to notify it there's new data.
2013-07-21 17:41:26 -07:00
Nick Foster
dd3e1fe629 GUI model ripped up and replaced with QSqlQueryModel. Not working but committing before I kill the child in row 15. 2013-07-21 17:41:26 -07:00
Nick Foster
a09c5add43 Set default UI tab to setup. 2013-07-18 18:43:29 -07:00
Nick Foster
498cea34b2 Better sample rate selection. 2013-07-18 18:31:10 -07:00
Nick Foster
4174658f0d Fixed GUI live print and reports/sec widget. 2013-07-18 17:46:50 -07:00
Nick Foster
b594fe2799 Mapview: added highlighting of selected aircraft 2013-07-18 09:45:09 -07:00
Nick Foster
7fef37d34d Merge branch 'gr3.7' into mapview 2013-07-17 21:46:55 -07:00
Nick Foster
2deefdf310 Remove unnecessary imports from modes_gui. 2013-07-17 21:46:25 -07:00
Nick Foster
37aa74fbe0 Merge branch 'gr3.7' into mapview
Conflicts:
	python/radio.py
2013-07-17 21:42:48 -07:00
Nick Foster
d71e6bc1e7 Interim commit. 2013-07-17 21:41:55 -07:00
Nick Foster
0ce6374656 Convert next branch to 3.7 API. Based on Johnathan Corgan's 3.7 conversion of
the master branch.
2013-07-17 18:03:45 -07:00
Nick Foster
2766107a76 Small changes to map view. WebKit won't render files w/o .htm[l] extension so using a named temp file is out. 2013-06-21 16:28:56 -07:00
Nick Foster
55cd17de67 Added support for integrated Google Maps interface via QWebView/JavaScript/JSONP. Broken due to something hairy wrt QWebView and /tmp. 2013-06-20 23:05:41 -07:00
23 changed files with 467 additions and 412 deletions

View File

@@ -69,23 +69,10 @@ set(GRC_BLOCKS_DIR ${GR_PKG_DATA_DIR}/grc/blocks)
########################################################################
# Find gnuradio build dependencies
########################################################################
find_package(Gruel)
find_package(GnuradioCore)
find_package(GnuradioRuntime)
if(NOT GRUEL_FOUND)
message(FATAL_ERROR "Gruel required to compile gr-air-modes")
endif()
if(NOT GNURADIO_CORE_FOUND)
message(FATAL_ERROR "GnuRadio Core required to compile gr-air-modes")
endif()
########################################################################
# Find 0MQ networking library
########################################################################
find_package(ZeroMQ)
if(NOT ZEROMQ_FOUND)
message(FATAL_ERROR "Could not find required library libzmq (ZeroMQ).")
if(NOT GNURADIO_RUNTIME_FOUND)
message(FATAL_ERROR "GnuRadio Runtime required to compile gr-air-modes")
endif()
########################################################################
@@ -94,14 +81,12 @@ endif()
include_directories(
${CMAKE_SOURCE_DIR}/include
${Boost_INCLUDE_DIRS}
${GRUEL_INCLUDE_DIRS}
${GNURADIO_CORE_INCLUDE_DIRS}
${GNURADIO_RUNTIME_INCLUDE_DIRS}
)
link_directories(
${Boost_LIBRARY_DIRS}
${GRUEL_LIBRARY_DIRS}
${GNURADIO_CORE_LIBRARY_DIRS}
${GNURADIO_RUNTIME_LIBRARY_DIRS}
)
# Set component parameters

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -27,10 +27,10 @@
#include <ciso646>
#include <air_modes_preamble.h>
#include <gr_io_signature.h>
#include <gnuradio/io_signature.h>
#include <string.h>
#include <iostream>
#include <gr_tags.h>
#include <gnuradio/tags.h>
air_modes_preamble_sptr air_make_modes_preamble(int channel_rate, float threshold_db)
{
@@ -38,39 +38,24 @@ air_modes_preamble_sptr air_make_modes_preamble(int channel_rate, float threshol
}
air_modes_preamble::air_modes_preamble(int channel_rate, float threshold_db) :
gr_block ("modes_preamble",
gr_make_io_signature2 (2, 2, sizeof(float), sizeof(float)), //stream 0 is received data, stream 1 is moving average for reference
gr_make_io_signature (1, 1, sizeof(float))) //the output packets
{
set_rate(channel_rate);
set_threshold(threshold_db);
std::stringstream str;
str << name() << unique_id();
d_me = pmt::pmt_string_to_symbol(str.str());
d_key = pmt::pmt_string_to_symbol("preamble_found");
set_history(d_samples_per_symbol);
}
void air_modes_preamble::set_rate(int channel_rate)
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_secs_per_sample = 1.0 / channel_rate;
d_check_width = 240 * d_samples_per_symbol; //only search to this far from the end of the stream buffer
set_output_multiple(1+d_check_width);
}
void air_modes_preamble::set_threshold(float threshold_db)
{
d_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
}
float air_modes_preamble::get_threshold(void)
{
return d_threshold_db;
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) {
@@ -96,14 +81,14 @@ static double correlate_preamble(const float *in, int samples_per_chip) {
}
//todo: make it return a pair of some kind, otherwise you can lose precision
static double tag_to_timestamp(gr_tag_t tstamp, uint64_t abs_sample_cnt, double secs_per_sample) {
static double tag_to_timestamp(gr::tag_t tstamp, uint64_t abs_sample_cnt, double secs_per_sample) {
uint64_t ts_sample, last_whole_stamp;
double last_frac_stamp;
if(tstamp.key == NULL || pmt::pmt_symbol_to_string(tstamp.key) != "rx_time") return 0;
if(tstamp.key == NULL || pmt::symbol_to_string(tstamp.key) != "rx_time") return 0;
last_whole_stamp = pmt::pmt_to_uint64(pmt::pmt_tuple_ref(tstamp.value, 0));
last_frac_stamp = pmt::pmt_to_double(pmt::pmt_tuple_ref(tstamp.value, 1));
last_whole_stamp = pmt::to_uint64(pmt::tuple_ref(tstamp.value, 0));
last_frac_stamp = pmt::to_double(pmt::tuple_ref(tstamp.value, 1));
ts_sample = tstamp.offset;
double tstime = double(abs_sample_cnt * secs_per_sample) + last_whole_stamp + last_frac_stamp;
@@ -137,8 +122,8 @@ int air_modes_preamble::general_work(int noutput_items,
};
uint64_t abs_sample_cnt = nitems_read(0);
std::vector<gr_tag_t> tstamp_tags;
get_tags_in_range(tstamp_tags, 0, abs_sample_cnt, abs_sample_cnt + ninputs, pmt::pmt_string_to_symbol("rx_time"));
std::vector<gr::tag_t> tstamp_tags;
get_tags_in_range(tstamp_tags, 0, abs_sample_cnt, abs_sample_cnt + ninputs, pmt::string_to_symbol("rx_time"));
//tags.back() is the most recent timestamp, then.
if(tstamp_tags.size() > 0) {
d_timestamp = tstamp_tags.back();
@@ -210,10 +195,10 @@ int air_modes_preamble::general_work(int noutput_items,
add_item_tag(0, //stream ID
nitems_written(0), //sample
d_key, //frame_info
pmt::pmt_make_tuple(pmt::pmt_from_double(tstamp), pmt::pmt_from_double(inavg[i])),
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

View File

@@ -27,13 +27,13 @@
#include <ciso646>
#include <air_modes_slicer.h>
#include <gr_io_signature.h>
#include <gnuradio/io_signature.h>
#include <air_modes_types.h>
#include <sstream>
#include <iomanip>
#include <modes_crc.h>
#include <iostream>
#include <gr_tags.h>
#include <gnuradio/tags.h>
extern "C"
{
@@ -41,26 +41,23 @@ extern "C"
#include <string.h>
}
air_modes_slicer_sptr air_make_modes_slicer(int channel_rate, gr_msg_queue_sptr queue)
air_modes_slicer_sptr air_make_modes_slicer(int channel_rate, gr::msg_queue::sptr queue)
{
return air_modes_slicer_sptr (new air_modes_slicer(channel_rate, queue));
}
air_modes_slicer::air_modes_slicer(int channel_rate, gr_msg_queue_sptr queue) :
gr_sync_block ("modes_slicer",
gr_make_io_signature (1, 1, sizeof(float)), //stream 0 is received data, stream 1 is binary preamble detector output
gr_make_io_signature (0, 0, 0) )
{
set_rate(channel_rate);
d_queue = queue;
}
void air_modes_slicer::set_rate(int channel_rate)
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?
}
@@ -110,10 +107,10 @@ int air_modes_slicer::work(int noutput_items,
if(0) std::cout << "Slicer called with " << size << " samples" << std::endl;
std::vector<gr_tag_t> tags;
std::vector<gr::tag_t> tags;
uint64_t abs_sample_cnt = nitems_read(0);
get_tags_in_range(tags, 0, abs_sample_cnt, abs_sample_cnt + size, pmt::pmt_string_to_symbol("preamble_found"));
std::vector<gr_tag_t>::iterator tag_iter;
get_tags_in_range(tags, 0, abs_sample_cnt, abs_sample_cnt + size, pmt::string_to_symbol("preamble_found"));
std::vector<gr::tag_t>::iterator tag_iter;
for(tag_iter = tags.begin(); tag_iter != tags.end(); tag_iter++) {
uint64_t i = tag_iter->offset - abs_sample_cnt;
@@ -159,14 +156,17 @@ int air_modes_slicer::work(int noutput_items,
if(rx_packet.numlowconf < 24) rx_packet.lowconfbits[rx_packet.numlowconf++] = j;
}
}
rx_packet.timestamp = pmt_to_double(pmt_tuple_ref(tag_iter->value, 0));
double ref = pmt_to_double(pmt_tuple_ref(tag_iter->value, 1));
//traverse the whole packet and if you find all 0's, just toss it. don't know why these packets turn up, but they pass ECC.
/******************** 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; break; }
if(rx_packet.data[m]) zeroes = 0;
}
if(zeroes) {continue;} //toss it
@@ -187,9 +187,9 @@ int air_modes_slicer::work(int noutput_items,
d_payload << std::hex << std::setw(2) << std::setfill('0') << unsigned(rx_packet.data[m]);
}
d_payload << " " << std::setw(6) << rx_packet.crc << " " << std::dec << rx_packet.reference_level / ref
d_payload << " " << std::setw(6) << rx_packet.crc << " " << std::dec << rx_packet.reference_level
<< " " << std::setprecision(10) << std::setw(10) << rx_packet.timestamp;
gr_message_sptr msg = gr_make_message_from_string(std::string(d_payload.str()));
gr::message::sptr msg = gr::message::make_from_string(std::string(d_payload.str()));
d_queue->handle(msg);
}
if(0) std::cout << "Slicer consumed " << size << ", returned " << size << std::endl;

View File

@@ -34,6 +34,7 @@ GR_PYTHON_INSTALL(
altitude.py
az_map.py
cpr.py
html_template.py
mlat.py
exceptions.py
flightgear.py

View File

@@ -57,7 +57,7 @@ from parse import *
from msprint import output_print
from sql import output_sql
from sbs1 import output_sbs1
from kml import output_kml
from kml import output_kml, output_jsonp
from raw_server import raw_server
from radio import modes_radio
from exceptions import *
@@ -65,6 +65,7 @@ from az_map import *
from types import *
from altitude import *
from cpr import cpr_decoder
from html_template import html_template
#this is try/excepted in case the user doesn't have numpy installed
try:
from flightgear import output_flightgear

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 5 minutes
def prune(self):
self.lock.acquire()
for (index,row) in enumerate(self._data):
if time.time() - row[1] >= 60:
self.beginRemoveRows(QtCore.QModelIndex(), index, index)
self._data.pop(index)
self.endRemoveRows()
self.lock.release()
class dashboard_output:
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()))

127
python/html_template.py Normal file
View File

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

View File

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

View File

@@ -28,9 +28,10 @@ import math
#TODO get rid of class and convert to functions
#no need for class here
class output_print:
def __init__(self, cpr, publisher):
def __init__(self, cpr, publisher, callback=None):
self._cpr = cpr
#sub to every function that starts with "print"
self._callback = callback
#sub to every function that starts with "handle"
self._fns = [int(l[6:]) for l in dir(self) if l.startswith("handle")]
for i in self._fns:
publisher.subscribe("type%i_dl" % i, getattr(self, "handle%i" % i))
@@ -41,6 +42,12 @@ class output_print:
def prefix(msg):
return "(%i %.8f) " % (msg.rssi, msg.timestamp)
def _print(self, msg):
if self._callback is None:
print msg
else:
self._callback(msg)
def catch_nohandler(self, msg):
if msg.data.get_type() not in self._fns:
retstr = output_print.prefix(msg)
@@ -49,7 +56,7 @@ class output_print:
retstr += " from %.6x" % msg.ecc
else:
retstr += " from %.6x" % msg.data["aa"]
print retstr
self._print(retstr)
def handle0(self, msg):
try:
@@ -77,7 +84,7 @@ class output_print:
if msg.data["vs"] is 1:
retstr += " (aircraft is on the ground)"
print retstr
self._print(retstr)
@staticmethod
def fs_text(fs):
@@ -101,7 +108,7 @@ class output_print:
retstr += output_print.fs_text(msg.data["fs"])
except ADSBError:
return
print retstr
self._print(retstr)
def handle5(self, msg):
try:
@@ -110,7 +117,7 @@ class output_print:
retstr += output_print.fs_text(msg.data["fs"])
except ADSBError:
return
print retstr
self._print(retstr)
def handle11(self, msg):
try:
@@ -118,7 +125,7 @@ class output_print:
retstr += "Type 11 (all call reply) from %x in reply to interrogator %i with capability level %i" % (msg.data["aa"], msg.ecc & 0xF, msg.data["ca"]+1)
except ADSBError:
return
print retstr
self._print(retstr)
#the only one which requires state
def handle17(self, msg):
@@ -157,20 +164,20 @@ class output_print:
[mag_hdg, vel_src, vel, vert_spd, geo_diff] = air_modes.parseBDS09_3(msg.data)
retstr += "Type 17 BDS0,9-%i (air course report) from %x with %s %.0fkt magnetic heading %.0f VS %.0f geo. diff. from baro. alt. %.0fft" \
% (subtype, icao24, vel_src, vel, mag_hdg, vert_spd, geo_diff)
else:
retstr += "Type 17 BDS0,9-%i from %x not implemented" % (subtype, icao24)
elif bdsreg == 0x62:
emerg_str = air_modes.parseBDS62(data)
retstr += "Type 17 BDS6,2 (emergency) from %x type %s" % (icao24, emerg_str)
else:
retstr += "Type 17 with FTC=%i from %x not implemented" % (msg.data["ftc"], icao24)
except ADSBError:
return
print retstr
self._print(retstr)
def printTCAS(self, msg):
msgtype = msg.data["df"]
@@ -222,8 +229,8 @@ class output_print:
retstr += " at %ift" % air_modes.decode_alt(msg.data["ac"], True)
else:
retstr += " ident %x" % air_modes.decode_id(msg.data["id"])
print retstr
self._print(retstr)
handle16 = printTCAS
handle20 = printTCAS

View File

@@ -23,7 +23,8 @@
# You pass it options, it gives you data.
# It uses the pubsub interface to allow clients to subscribe to its data feeds.
from gnuradio import gr, gru, optfir, eng_notation, blks2
from gnuradio import gr, gru, eng_notation, filter
from gnuradio.filter import optfir
from gnuradio.eng_option import eng_option
from gnuradio.gr.pubsub import pubsub
from optparse import OptionParser, OptionGroup
@@ -186,7 +187,7 @@ class modes_radio (gr.top_block, pubsub):
#Note: this should only come into play if using an RTLSDR.
# lpfiltcoeffs = gr.firdes.low_pass(1, 5*3.2e6, 1.6e6, 300e3)
# self._resample = blks2.rational_resampler_ccf(interpolation=5, decimation=4, taps=lpfiltcoeffs)
# 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

View File

@@ -19,7 +19,7 @@
# Boston, MA 02110-1301, USA.
#
from gnuradio import gr
from gnuradio import gr, blocks
import air_modes_swig
class rx_path(gr.hier_block2):
@@ -35,17 +35,17 @@ class rx_path(gr.hier_block2):
self._spc = int(rate/2e6)
# Convert incoming I/Q baseband to amplitude
self._demod = gr.complex_to_mag()
self._demod = blocks.complex_to_mag()
self._bb = self._demod
# Pulse matched filter for 0.5us pulses
if use_pmf:
self._pmf = gr.moving_average_ff(self._spc, 1.0/self._spc)#, self._rate)
self._pmf = blocks.moving_average_ff(self._spc, 1.0/self._spc)#, self._rate)
self.connect(self._demod, self._pmf)
self._bb = self._pmf
# Establish baseline amplitude (noise, interference)
self._avg = gr.moving_average_ff(48*self._spc, 1.0/(48*self._spc))#, self._rate) # 3 preambles
self._avg = blocks.moving_average_ff(48*self._spc, 1.0/(48*self._spc))#, self._rate) # 3 preambles
# Synchronize to Mode-S preamble
self._sync = air_modes_swig.modes_preamble(self._rate, self._threshold)

View File

@@ -38,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
@@ -46,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
@@ -74,13 +74,13 @@ class output_sql:
#constructor.
if self._db is None:
self._db = sqlite3.connect(self.filename)
query = self.make_insert_query(message)
if query is not None:
c = self._db.cursor()
c.execute(query)
c.close()
# self._db.commit()
self._db.commit()
except ADSBError:
pass
@@ -96,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

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

View File

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

View File

@@ -5,7 +5,7 @@
%{
#include "air_modes_preamble.h"
#include "air_modes_slicer.h"
#include <gr_msg_queue.h>
#include <gnuradio/msg_queue.h>
%}
// ----------------------------------------------------------------
@@ -21,7 +21,7 @@ GR_SWIG_BLOCK_MAGIC(air,modes_preamble);
air_modes_preamble_sptr air_make_modes_preamble (int channel_rate, float threshold_db);
class air_modes_preamble : public gr_sync_block
class air_modes_preamble : public gr::sync_block
{
set_rate(int channel_rate);
set_threshold(float threshold_db);
@@ -32,13 +32,13 @@ private:
GR_SWIG_BLOCK_MAGIC(air,modes_slicer);
air_modes_slicer_sptr air_make_modes_slicer (int channel_rate, gr_msg_queue_sptr queue);
air_modes_slicer_sptr air_make_modes_slicer (int channel_rate, gr::msg_queue::sptr queue);
class air_modes_slicer : public gr_block
class air_modes_slicer : public gr::block
{
set_rate(int channel_rate);
private:
air_modes_slicer (int channel_rate, gr_msg_queue_sptr queue);
air_modes_slicer (int channel_rate, gr::msg_queue::sptr queue);
};
// ----------------------------------------------------------------