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
33 changed files with 1001 additions and 1205 deletions

View File

@@ -108,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

@@ -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
@@ -48,45 +48,27 @@ 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")
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"])
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")
#disable by default
self.ui.check_adsbonly.setCheckState(QtCore.Qt.Unchecked)
@@ -95,12 +77,12 @@ 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)
@@ -114,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)
@@ -132,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)
@@ -140,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):
@@ -232,7 +198,7 @@ class mainwindow(QtGui.QMainWindow):
elif sourceid == "Osmocom":
try:
import osmosdr
self.src = osmosdr.source("")
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
@@ -262,12 +228,11 @@ class mainwindow(QtGui.QMainWindow):
self.ui.combo_ant.addItems(self.antennas)
#set up recommended sample rate
if len(self.rates) > 1:
recommended_rate = min(x for x in self.rates if x >= 4e6 and
max(self.rates) % x == 0)
if recommended_rate >= 8.e6:
self.ui.check_pmf.setChecked(True)
self.ui.combo_rate.setCurrentIndex(self.rates.index(recommended_rate))
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):
@@ -296,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)
@@ -315,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
@@ -375,29 +338,6 @@ class mainwindow(QtGui.QMainWindow):
self.ui.button_start.setText("Stop")
self.running = True
#grab prefs and save them
self.prefs = {}
self.prefs["samplerate"] = options.rate
self.prefs["antenna"] = options.antenna
self.prefs["gain"] = options.gain
self.prefs["pmf"] = "1" if options.pmf else "0"
self.prefs["dcblock"] = "1" if options.dcblock else "0"
self.prefs["source"] = self.ui.combo_source.currentText()
self.prefs["threshold"] = options.threshold
self.prefs["sbs1"] = "1" if self.ui.check_sbs1.isChecked() else "0"
self.prefs["sbs1port"] = int(self.ui.line_sbs1port.text())
self.prefs["fgfs"] = "1" if self.ui.check_fgfs.isChecked() else "0"
self.prefs["fgfsport"] = int(self.ui.line_fgfsport.text())
self.prefs["raw"] = "1" if self.ui.check_raw.isChecked() else "0"
self.prefs["rawport"] = int(self.ui.line_rawport.text())
self.prefs["kml"] = "1" if self.ui.check_kml.isChecked() else "0"
self.prefs["kmlfile"] = self.ui.line_kmlfilename.text()
try:
self.prefs["latitude"] = float(self.ui.line_my_lat.text())
self.prefs["longitude"] = float(self.ui.line_my_lon.text())
except:
pass
def on_quit(self):
if self.running is True:
self._relay.close()
@@ -413,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()
@@ -436,56 +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
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

@@ -86,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

@@ -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,20 +20,28 @@
#
*/
#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;
@@ -42,14 +50,9 @@ private:
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(int channel_rate, float threshold_db);
virtual void set_rate(int channel_rate) = 0;
virtual void set_threshold(float threshold_db) = 0;
virtual int get_rate(void) = 0;
virtual float get_threshold(void) = 0;
};
} // namespace air_modes
} // namespace gr
#endif /* INCLUDED_AIR_MODES_PREAMBLE_H */

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,8 +23,8 @@
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 ${Boost_LIBRARIES} ${GNURADIO_RUNTIME_LIBRARIES})

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,227 +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(int channel_rate, float threshold_db) {
return gnuradio::get_initial_sptr(new air_modes::preamble_impl(channel_rate, threshold_db));
}
air_modes::preamble_impl::preamble_impl(int channel_rate, float threshold_db) :
gr::block ("preamble",
gr::io_signature::make2 (2, 2, sizeof(float), sizeof(float)), //stream 0 is received data, stream 1 is moving average for reference
gr::io_signature::make (1, 1, sizeof(float))) //the output soft symbols
{
d_chip_rate = 2000000; //2Mchips per second
set_rate(channel_rate);
set_threshold(threshold_db);
std::stringstream str;
str << name() << unique_id();
d_me = pmt::string_to_symbol(str.str());
d_key = pmt::string_to_symbol("preamble_found");
}
void air_modes::preamble_impl::set_rate(int channel_rate) {
d_samples_per_chip = channel_rate / d_chip_rate;
d_samples_per_symbol = d_samples_per_chip * 2;
d_check_width = 120 * d_samples_per_symbol;
d_secs_per_sample = 1.0/channel_rate;
set_output_multiple(1+d_check_width*2);
set_history(d_samples_per_symbol);
}
void air_modes::preamble_impl::set_threshold(float threshold_db) {
d_threshold_db = threshold_db;
d_threshold = powf(10., threshold_db/20.); //the level that the sample must be above the moving average in order to qualify as a pulse
}
float air_modes::preamble_impl::get_threshold(void) {
return d_threshold_db;
}
int air_modes::preamble_impl::get_rate(void) {
return d_samples_per_chip * d_chip_rate;
}
static void integrate_and_dump(float *out, const float *in, int chips, int samps_per_chip) {
for(int i=0; i<chips; i++) {
float acc = 0;
for(int j=0; j<samps_per_chip; j++) {
acc += in[i*samps_per_chip+j];
}
out[i] = acc;
}
}
//the preamble pattern in bits
//fixme goes in .h
static const bool preamble_bits[] = {1, 0, 1, 0, 0, 0, 0, 1, 0, 1};
static double correlate_preamble(const float *in, int samples_per_chip) {
double corr = 0.0;
for(int i=0; i<10; i++) {
for(int j=0; j<samples_per_chip;j++)
if(preamble_bits[i]) corr += in[i*samples_per_chip+j];
}
return corr;
}
//todo: make it return a pair of some kind, otherwise you can lose precision
static double tag_to_timestamp(gr::tag_t tstamp, uint64_t abs_sample_cnt, double secs_per_sample) {
uint64_t ts_sample, last_whole_stamp;
double last_frac_stamp;
if(tstamp.key == NULL || pmt::symbol_to_string(tstamp.key) != "rx_time") return 0;
last_whole_stamp = pmt::to_uint64(pmt::tuple_ref(tstamp.value, 0));
last_frac_stamp = pmt::to_double(pmt::tuple_ref(tstamp.value, 1));
ts_sample = tstamp.offset;
double tstime = double(abs_sample_cnt * secs_per_sample) + last_whole_stamp + last_frac_stamp;
if(0) std::cout << "HEY WE GOT A STAMP AT " << tstime << " TICKS AT SAMPLE " << ts_sample << " ABS SAMPLE CNT IS " << abs_sample_cnt << std::endl;
return tstime;
}
int air_modes::preamble_impl::general_work(int noutput_items,
gr_vector_int &ninput_items,
gr_vector_const_void_star &input_items,
gr_vector_void_star &output_items)
{
const float *in = (const float *) input_items[0];
const float *inavg = (const float *) input_items[1];
int mininputs = std::min(ninput_items[0], ninput_items[1]); //they should be matched but let's be safe
//round number of input samples down to nearest d_samples_per_chip
//we also subtract off d_samples_per_chip to allow the bit center finder some leeway
const int ninputs = std::max(mininputs - (mininputs % d_samples_per_chip) - d_samples_per_chip, 0);
if (ninputs <= 0) { consume_each(0); return 0; }
float *out = (float *) output_items[0];
if(0) std::cout << "Preamble called with " << ninputs << " samples" << std::endl;
//fixme move into .h
const int pulse_offsets[4] = { 0,
int(2 * d_samples_per_chip),
int(7 * d_samples_per_chip),
int(9 * d_samples_per_chip)
};
uint64_t abs_sample_cnt = nitems_read(0);
std::vector<gr::tag_t> tstamp_tags;
get_tags_in_range(tstamp_tags, 0, abs_sample_cnt, abs_sample_cnt + ninputs, pmt::string_to_symbol("rx_time"));
//tags.back() is the most recent timestamp, then.
if(tstamp_tags.size() > 0) {
d_timestamp = tstamp_tags.back();
}
for(int i=0; i < ninputs; i++) {
float pulse_threshold = inavg[i] * d_threshold;
if(in[i] > pulse_threshold) { //hey we got a candidate
if(in[i+1] > in[i]) continue; //wait for the peak
//check to see the rest of the pulses are there
if( in[i+pulse_offsets[1]] < pulse_threshold ) continue;
if( in[i+pulse_offsets[2]] < pulse_threshold ) continue;
if( in[i+pulse_offsets[3]] < pulse_threshold ) continue;
//get a more accurate bit center by finding the correlation peak across all four preamble bits
bool late, early;
int how_late = 0;
do {
double now_corr = correlate_preamble(in+i, d_samples_per_chip);
double late_corr = correlate_preamble(in+i+1, d_samples_per_chip);
double early_corr = correlate_preamble(in+i-1, d_samples_per_chip);
late = (late_corr > now_corr);
//early = (early_corr > now_corr);
if(late) { i++; how_late++; }
//if(early && i>0) { std::cout << "EARLY " << i << std::endl; i--; }
} while(late and how_late < d_samples_per_chip);// xor early);
if(0) std::cout << "We were " << how_late << " samples late" << std::endl;
//now check to see that the non-peak symbols in the preamble
//are below the peaks by threshold dB
float avgpeak = ( in[i+pulse_offsets[0]]
+ in[i+pulse_offsets[1]]
+ in[i+pulse_offsets[2]]
+ in[i+pulse_offsets[3]]) / 4.0;
float space_threshold = inavg[i] + (avgpeak - inavg[i])/d_threshold;
bool valid_preamble = true; //f'in c++
for( int j=1.5*d_samples_per_symbol; j<=3*d_samples_per_symbol; j++)
if(in[i+j] > space_threshold) valid_preamble = false;
for( int j=5*d_samples_per_symbol; j<=7.5*d_samples_per_symbol; j++)
if(in[i+j] > space_threshold) valid_preamble = false;
if(!valid_preamble) continue;
//be sure we've got enough room in the input buffer to copy out a whole packet
if(ninputs-i < 240*d_samples_per_chip) {
consume_each(std::max(i-1,0));
if(0) std::cout << "Preamble consumed " << std::max(i-1,0) << ", returned 0 (no room)" << std::endl;
return 0;
}
//all right i'm prepared to call this a preamble
for(int j=0; j<240; j++) {
out[j] = in[i+j*d_samples_per_chip] - inavg[i];
}
//get the timestamp of the preamble
double tstamp = tag_to_timestamp(d_timestamp, abs_sample_cnt + i, d_secs_per_sample);
//now tag the preamble
add_item_tag(0, //stream ID
nitems_written(0), //sample
d_key, //frame_info
pmt::from_double(tstamp),
d_me //block src id
);
//std::cout << "PREAMBLE" << std::endl;
//produce only one output per work call -- TODO this should probably change
if(0) std::cout << "Preamble consumed " << i+240*d_samples_per_chip << "with i=" << i << ", returned 240" << std::endl;
consume_each(i+240*d_samples_per_chip);
return 240;
}
}
//didn't get anything this time
if(0) std::cout << "Preamble consumed " << ninputs << ", returned 0" << std::endl;
consume_each(ninputs);
return 0;
}
} //namespace gr

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;
int d_samples_per_chip;
int d_samples_per_symbol;
float d_threshold_db;
float d_threshold;
pmt::pmt_t d_me, d_key;
gr::tag_t d_timestamp;
double d_secs_per_sample;
public:
preamble_impl(int channel_rate, float threshold_db);
int general_work (int noutput_items,
gr_vector_int &ninput_items,
gr_vector_const_void_star &input_items,
gr_vector_void_star &output_items);
void set_rate(int channel_rate);
void set_threshold(float threshold_db);
float get_threshold(void);
int get_rate(void);
};
} //namespace air_modes
} //namespace gr
#endif //_AIR_MODES_PREAMBLE_IMPL_H_

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;
}
}
rx_packet.timestamp = pmt::to_double(tag_iter->value);
//here you might want to traverse the whole packet and if you find all 0's, just toss it. don't know why these packets turn up, but they pass ECC.
bool zeroes = 1;
for(int m = 0; m < 14; m++) {
if(rx_packet.data[m]) zeroes = 0;
}
if(zeroes) {continue;} //toss it
rx_packet.message_type = (rx_packet.data[0] >> 3) & 0x1F; //get the message type to make decisions on ECC methods
if(rx_packet.type == Short_Packet && rx_packet.message_type != 11 && rx_packet.numlowconf > 0) {continue;}
if(rx_packet.message_type == 11 && rx_packet.numlowconf >= 10) {continue;}
rx_packet.crc = modes_check_crc(rx_packet.data, (packet_length/8)-3);
unsigned int ap = rx_packet.data[packet_length/8-3] << 16
| rx_packet.data[packet_length/8-2] << 8
| rx_packet.data[packet_length/8-1] << 0;
rx_packet.crc ^= ap;
//crc for packets that aren't type 11 or type 17 is encoded with the transponder ID, which we don't know
//therefore we toss 'em if there's syndrome
//crc for the other short packets is usually nonzero, so they can't really be trusted that far
if(rx_packet.crc && (rx_packet.message_type == 11 || rx_packet.message_type == 17)) {continue;}
d_payload.str("");
for(int m = 0; m < packet_length/8; m++) {
d_payload << std::hex << std::setw(2) << std::setfill('0') << unsigned(rx_packet.data[m]);
}
d_payload << " " << std::setw(6) << rx_packet.crc << " " << std::dec << rx_packet.reference_level
<< " " << std::setprecision(10) << std::setw(10) << rx_packet.timestamp;
gr::message::sptr msg = gr::message::make_from_string(std::string(d_payload.str()));
d_queue->handle(msg);
}
if(0) std::cout << "Slicer consumed " << size << ", returned " << size << std::endl;
return size;
}
} //namespace gr

View File

@@ -27,7 +27,7 @@ import threading
import math
import air_modes
from air_modes.exceptions import *
import numpy as np
# model has max range vs. azimuth in n-degree increments
# contains separate max range for a variety of altitudes so
@@ -53,7 +53,7 @@ class az_map_model(QtCore.QObject):
def data(self, row, col):
return self._data[row][col]
def addRecord(self, bearing, altitude, distance):
with self.lock:
#round up to nearest altitude in altitudes list
@@ -85,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)
@@ -115,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)):
@@ -133,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))
@@ -150,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

@@ -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

@@ -14,21 +14,19 @@ def html_template(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?sensor=false">
</script>
<script type="text/javascript" src="http://google-maps-utility-library-v3.googlecode.com/svn/tags/markerwithlabel/1.1.9/src/markerwithlabel.js">
</script>
<script type="text/javascript">
var map;
var markers = [];
@@ -85,22 +83,22 @@ def html_template(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,
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}
};
planeMarker = new MarkerWithLabel(planeOptions);
planeMarker = new google.maps.Marker(planeOptions);
planes.push(planeMarker);
};
};

View File

@@ -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
@@ -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

@@ -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

@@ -1 +0,0 @@

View File

@@ -427,7 +427,7 @@ def make_parser(pub):
try:
ret = air_modes.modes_report(modes_reply(int(data, 16)),
int(ecc, 16),
10.0*math.log10(max(1e-8,float(reference))),
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

View File

@@ -23,7 +23,7 @@
# 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
@@ -33,7 +33,6 @@ import zmq
import threading
import time
import re
import fractions
class modes_radio (gr.top_block, pubsub):
def __init__(self, options, context):
@@ -45,11 +44,8 @@ class modes_radio (gr.top_block, pubsub):
self._resample = None
self._setup_source(options)
if self._resample is not None:
self._rate = 4.0e6 #fixed rate we resample to in RTL case
self._rx_path = air_modes.rx_path(self._rate, options.threshold,
self._queue, options.pmf, 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)
@@ -84,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]")
@@ -106,42 +102,33 @@ 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=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):
self._rx_path.set_rate(rate)
return self._u.set_rate(rate) if self.live_source() else 0
def set_threshold(self, threshold):
self._rx_path.set_threshold(threshold)
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":
@@ -175,32 +162,33 @@ class modes_radio (gr.top_block, pubsub):
self._u.set_gain(options.gain)
print "Gain is %i" % self._u.get_gain()
#TODO: detect if you're using an RTLSDR or Jawbreaker
#and set up accordingly.
#ALSO TODO: Actually set gain appropriately using gain bins in HackRF driver.
#osmocom doesn't have gain bucket distribution like UHD does
elif options.source == "osmocom": #RTLSDR dongle or HackRF Jawbreaker
import osmosdr
self._u = osmosdr.source(options.args)
rates = self._u.get_sample_rates()
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)
actual_rate = int(self._u.get_sample_rate())
if not self._u.set_center_freq(options.freq):
print "Failed to set initial frequency"
# self._u.set_gain_mode(0) #manual gain mode
self._u.set_gain_mode(0) #manual gain mode
if options.gain is None:
options.gain = 34
self._u.set_gain(options.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()
if actual_rate < 4.0e6:
gcd = fractions.gcd(4.0e6, actual_rate)
interp = 4.0e6 / gcd
decim = actual_rate / gcd
lpfiltcoeffs = filter.firdes.low_pass(1, interp*actual_rate, 1.6e6, 300e3)
self._resample = filter.rational_resampler_ccf(interpolation=interp,
decimation=decim,
taps=lpfiltcoeffs)
#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:
@@ -211,11 +199,10 @@ class modes_radio (gr.top_block, pubsub):
self._u = gr.udp_source(gr.sizeof_gr_complex, ip, int(port))
print "Using UDP source %s:%s" % (ip, port)
else:
self._u = blocks.file_source(gr.sizeof_gr_complex, options.source)
self._u = gr.file_source(gr.sizeof_gr_complex, options.source)
print "Using file source %s" % options.source
print "Rate is %i" % actual_rate
print "Rate is %i" % (options.rate,)
def close(self):
self._sender.close()
self._u = None

View File

@@ -19,12 +19,12 @@
# Boston, MA 02110-1301, USA.
#
from gnuradio import gr, blocks, filter
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,True)
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_swig.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_swig.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(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)
@@ -85,4 +79,4 @@ class rx_path(gr.hier_block2):
def get_threshold(self, threshold):
return self._sync.get_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
@@ -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

@@ -316,19 +316,6 @@
<string>Use Pulse Matched Filtering</string>
</property>
</widget>
<widget class="QCheckBox" name="check_dcblock">
<property name="geometry">
<rect>
<x>10</x>
<y>220</y>
<width>221</width>
<height>22</height>
</rect>
</property>
<property name="text">
<string>Use DC blocking filter</string>
</property>
</widget>
</widget>
<widget class="QGroupBox" name="group_output">
<property name="geometry">
@@ -848,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">

View File

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