Refactored modes_rx to use a more modular radio interface (radio.py) using a pubsub pattern to formalize the old "outputs" interface I was using. Should make it easier to reuse the radio interface.

This commit is contained in:
Nick Foster 2013-05-27 19:50:42 -07:00
parent 3e9854f337
commit db34eca30e
4 changed files with 171 additions and 133 deletions

View File

@ -1,5 +1,5 @@
#!/usr/bin/env python #!/usr/bin/env python
# Copyright 2010 Nick Foster # Copyright 2010, 2013 Nick Foster
# #
# This file is part of gr-air-modes # This file is part of gr-air-modes
# #
@ -21,120 +21,26 @@
my_position = None my_position = None
from gnuradio import gr, gru, optfir, eng_notation, blks2
from gnuradio.eng_option import eng_option from gnuradio.eng_option import eng_option
from optparse import OptionParser from optparse import OptionParser
import time, os, sys, threading import time, os, sys, threading
from string import split, join from string import split, join
import air_modes import air_modes
import gnuradio.gr.gr_threading as _threading
import csv import csv
from air_modes.exceptions import * from air_modes.exceptions import *
class top_block_runner(_threading.Thread):
def __init__(self, tb):
_threading.Thread.__init__(self)
self.setDaemon(1)
self.tb = tb
self.done = False
self.start()
def run(self):
self.tb.run()
self.done = True
class adsb_rx_block (gr.top_block):
def __init__(self, options, args, queue):
gr.top_block.__init__(self)
self.options = options
self.args = args
rate = int(options.rate)
use_resampler = False
if options.filename is None and options.udp is None and not options.rtlsdr:
#UHD source by default
from gnuradio import uhd
self.u = uhd.single_usrp_source(options.args, uhd.io_type_t.COMPLEX_FLOAT32, 1)
time_spec = uhd.time_spec(0.0)
self.u.set_time_now(time_spec)
#if(options.rx_subdev_spec is None):
# options.rx_subdev_spec = ""
#self.u.set_subdev_spec(options.rx_subdev_spec)
if not options.antenna is None:
self.u.set_antenna(options.antenna)
self.u.set_samp_rate(rate)
rate = int(self.u.get_samp_rate()) #retrieve actual
if options.gain is None: #set to halfway
g = self.u.get_gain_range()
options.gain = (g.start()+g.stop()) / 2.0
if not(self.tune(options.freq)):
print "Failed to set initial frequency"
print "Setting gain to %i" % options.gain
self.u.set_gain(options.gain)
print "Gain is %i" % self.u.get_gain()
elif options.rtlsdr: #RTLSDR dongle
import osmosdr
self.u = osmosdr.source_c(options.args)
self.u.set_sample_rate(3.2e6) #fixed for RTL dongles
if not self.u.set_center_freq(options.freq):
print "Failed to set initial frequency"
self.u.set_gain_mode(0) #manual gain mode
if options.gain is None:
options.gain = 34
self.u.set_gain(options.gain)
print "Gain is %i" % self.u.get_gain()
use_resampler = True
else:
if options.filename is not None:
self.u = gr.file_source(gr.sizeof_gr_complex, options.filename)
elif options.udp is not None:
self.u = gr.udp_source(gr.sizeof_gr_complex, "localhost", options.udp)
else:
raise Exception("No valid source selected")
print "Rate is %i" % (rate,)
pass_all = 0
if options.output_all :
pass_all = 1
self.rx_path = air_modes.rx_path(rate, options.threshold, queue, options.pmf)
if use_resampler:
self.lpfiltcoeffs = gr.firdes.low_pass(1, 5*3.2e6, 1.6e6, 300e3)
self.resample = blks2.rational_resampler_ccf(interpolation=5, decimation=4, taps=self.lpfiltcoeffs)
self.connect(self.u, self.resample, self.rx_path)
else:
self.connect(self.u, self.rx_path)
def tune(self, freq):
result = self.u.set_center_freq(freq, 0)
return result
def printraw(msg): def printraw(msg):
print msg print msg
if __name__ == '__main__': if __name__ == '__main__':
usage = "%prog: [options] output filename" usage = "%prog: [options] output filename"
parser = OptionParser(option_class=eng_option, usage=usage) parser = OptionParser(option_class=eng_option, usage=usage)
parser.add_option("-R", "--rx-subdev-spec", type="string", parser.add_option("-R", "--subdev", type="string",
help="select USRP Rx side A or B", metavar="SUBDEV") help="select USRP Rx side A or B", metavar="SUBDEV")
parser.add_option("-A", "--antenna", type="string", parser.add_option("-A", "--antenna", type="string",
help="select which antenna to use on daughterboard") help="select which antenna to use on daughterboard")
parser.add_option("-D", "--args", type="string", parser.add_option("-D", "--args", type="string",
help="arguments to pass to UHD/RTL constructor", default="") help="arguments to pass to radio constructor", default="")
parser.add_option("-f", "--freq", type="eng_float", default=1090e6, parser.add_option("-f", "--freq", type="eng_float", default=1090e6,
help="set receive frequency in Hz [default=%default]", metavar="FREQ") help="set receive frequency in Hz [default=%default]", metavar="FREQ")
parser.add_option("-g", "--gain", type="int", default=None, parser.add_option("-g", "--gain", type="int", default=None,
@ -146,7 +52,7 @@ if __name__ == '__main__':
parser.add_option("-a","--output-all", action="store_true", default=False, parser.add_option("-a","--output-all", action="store_true", default=False,
help="output all frames") help="output all frames")
parser.add_option("-F","--filename", type="string", default=None, parser.add_option("-F","--filename", type="string", default=None,
help="read data from file instead of USRP") help="read data from file instead of USRP")
parser.add_option("-K","--kml", type="string", default=None, parser.add_option("-K","--kml", type="string", default=None,
help="filename for Google Earth KML output") help="filename for Google Earth KML output")
parser.add_option("-P","--sbs1", action="store_true", default=False, parser.add_option("-P","--sbs1", action="store_true", default=False,
@ -168,19 +74,18 @@ if __name__ == '__main__':
(options, args) = parser.parse_args() (options, args) = parser.parse_args()
tb = air_modes.modes_radio(options)
updates = []
if options.location is not None: if options.location is not None:
reader = csv.reader([options.location], quoting=csv.QUOTE_NONNUMERIC) reader = csv.reader([options.location], quoting=csv.QUOTE_NONNUMERIC)
my_position = reader.next() my_position = reader.next()
queue = gr.msg_queue()
outputs = [] #registry of plugin output functions
updates = [] #registry of plugin update functions
if options.raw is True: if options.raw is True:
rawport = air_modes.raw_server(9988) #port rawport = air_modes.raw_server(9988) #port
outputs.append(rawport.output) tb.subscribe('dl_data', rawport.output)
outputs.append(printraw) tb.subscribe('dl_data', printraw)
updates.append(rawport.add_pending_conns) updates.append(rawport.add_pending_conns)
if options.kml is not None: if options.kml is not None:
@ -189,23 +94,22 @@ if __name__ == '__main__':
lock = threading.Lock() lock = threading.Lock()
sqldb = air_modes.output_sql(my_position, dbname, lock) #input into the db sqldb = air_modes.output_sql(my_position, dbname, lock) #input into the db
kmlgen = air_modes.output_kml(options.kml, dbname, my_position, lock) #create a KML generating thread to read from the db kmlgen = air_modes.output_kml(options.kml, dbname, my_position, lock) #create a KML generating thread to read from the db
outputs.append(sqldb.output) tb.subscribe('dl_data', sqldb.output)
if options.sbs1 is True: if options.sbs1 is True:
sbs1port = air_modes.output_sbs1(my_position, 30003) sbs1port = air_modes.output_sbs1(my_position, 30003)
outputs.append(sbs1port.output) tb.subscribe('dl_data', sbs1port.output)
updates.append(sbs1port.add_pending_conns) updates.append(sbs1port.add_pending_conns)
if options.no_print is not True: if options.no_print is not True:
outputs.append(air_modes.output_print(my_position).output) tb.subscribe('dl_data', air_modes.output_print(my_position).output)
if options.multiplayer is not None: if options.multiplayer is not None:
[fghost, fgport] = options.multiplayer.split(':') [fghost, fgport] = options.multiplayer.split(':')
fgout = air_modes.output_flightgear(my_position, fghost, int(fgport)) fgout = air_modes.output_flightgear(my_position, fghost, int(fgport))
outputs.append(fgout.output) tb.subscribe('dl_data', fgout.output)
fg = adsb_rx_block(options, args, queue) tb.start()
runner = top_block_runner(fg)
while 1: while 1:
try: try:
@ -214,26 +118,14 @@ if __name__ == '__main__':
#until the next output arrives #until the next output arrives
for update in updates: for update in updates:
update() update()
time.sleep(0.1)
#main message handler
if not queue.empty_p() :
while not queue.empty_p() :
msg = queue.delete_head() #blocking read
for out in outputs:
try:
out(msg.to_string())
except air_modes.ADSBError:
pass
elif runner.done:
raise KeyboardInterrupt
else:
time.sleep(0.1)
except KeyboardInterrupt: except KeyboardInterrupt:
fg.stop()
runner = None
if options.kml is not None:
kmlgen.done = True
break break
tb.stop()
tb.wait()
if options.kml is not None:
kmlgen.done = True

View File

@ -41,6 +41,7 @@ GR_PYTHON_INSTALL(
kml.py kml.py
parse.py parse.py
msprint.py msprint.py
radio.py
raw_server.py raw_server.py
rx_path.py rx_path.py
sbs1.py sbs1.py

View File

@ -58,6 +58,7 @@ from sql import output_sql
from sbs1 import output_sbs1 from sbs1 import output_sbs1
from kml import output_kml from kml import output_kml
from raw_server import raw_server from raw_server import raw_server
from radio import modes_radio
from exceptions import * from exceptions import *
from az_map import * from az_map import *
#this is try/excepted in case the user doesn't have numpy installed #this is try/excepted in case the user doesn't have numpy installed

144
python/radio.py Normal file
View File

@ -0,0 +1,144 @@
# Copyright 2013 Nick Foster
#
# This file is part of gr-air-modes
#
# gr-air-modes is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3, or (at your option)
# any later version.
#
# gr-air-modes is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with gr-air-modes; see the file COPYING. If not, write to
# the Free Software Foundation, Inc., 51 Franklin Street,
# Boston, MA 02110-1301, USA.
#
# Radio interface for Mode S RX.
# Handles all Gnuradio-related functionality.
# You pass it options, it gives you data.
# It uses the pubsub interface to allow clients to subscribe to its data feeds.
from gnuradio import gr, gru, optfir, eng_notation, blks2
from gnuradio.eng_option import eng_option
from gnuradio.gr.pubsub import pubsub
import air_modes
DOWNLINK_DATA_TYPE = 'dl_data'
class modes_radio (gr.top_block, pubsub):
def __init__(self, options):
gr.top_block.__init__(self)
pubsub.__init__(self)
self._options = options
self._queue = gr.msg_queue()
rate = int(options.rate)
use_resampler = False
self.time_source = None
self._setup_source(options)
self.subscribe('gain', self.set_gain)
self.subscribe('freq', self.set_freq)
self['gain'] = options.gain
self['freq'] = options.freq
self['rate'] = options.rate
self['filename'] = options.filename
#TODO allow setting rate, threshold (drill down into slicer & preamble)
self.rx_path = air_modes.rx_path(rate, options.threshold, self._queue, options.pmf)
if use_resampler:
self.lpfiltcoeffs = gr.firdes.low_pass(1, 5*3.2e6, 1.6e6, 300e3)
self.resample = blks2.rational_resampler_ccf(interpolation=5, decimation=4, taps=self.lpfiltcoeffs)
self.connect(self._u, self.resample, self.rx_path)
else:
self.connect(self._u, self.rx_path)
#Publish messages when they come back off the queue
self._async_rcv = gru.msgq_runner(self._queue, self.async_callback)
def async_callback(self, msg):
self[DOWNLINK_DATA_TYPE] = msg.to_string()
#these are wrapped with try/except because file sources and udp sources
#don't have set_center_freq/set_gain functions. this should check to see
#the type of self._u.
def set_freq(self, freq):
try:
result = self._u.set_center_freq(freq, 0)
return result
except:
pass
def set_gain(self, gain):
try:
self._u.set_gain(gain)
except:
pass
def _setup_source(self, options):
if options.filename is None and options.udp is None and options.rtlsdr is None:
#UHD source by default
from gnuradio import uhd
self._u = uhd.single_usrp_source(options.args, uhd.io_type_t.COMPLEX_FLOAT32, 1)
if(options.subdev):
self._u.set_subdev_spec(options.subdev, 0)
#check for GPSDO
#if you have a GPSDO, UHD will automatically set the timestamp to UTC time
#as well as automatically set the clock to lock to GPSDO.
if self._u.get_time_source(0) == 'gpsdo':
self._time_source = 'gpsdo'
else:
self._time_source = None
self._u.set_time_now(uhd.time_spec(0.0))
if options.antenna is not None:
self._u.set_antenna(options.antenna)
self._u.set_samp_rate(rate)
rate = int(self._u.get_samp_rate()) #retrieve actual
if options.gain is None: #set to halfway
g = self._u.get_gain_range()
options.gain = (g.start()+g.stop()) / 2.0
print "Setting gain to %i" % options.gain
self._u.set_gain(options.gain)
print "Gain is %i" % self._u.get_gain()
elif options.rtlsdr: #RTLSDR dongle
import osmosdr
self._u = osmosdr.source_c(options.args)
self._u.set_sample_rate(3.2e6) #fixed for RTL dongles
if not self._u.set_center_freq(options.freq):
print "Failed to set initial frequency"
self._u.set_gain_mode(0) #manual gain mode
if options.gain is None:
options.gain = 34
self._u.set_gain(options.gain)
print "Gain is %i" % self._u.get_gain()
use_resampler = True
else:
if options.filename is not None:
self._u = gr.file_source(gr.sizeof_gr_complex, options.filename)
elif options.udp is not None:
self._u = gr.udp_source(gr.sizeof_gr_complex, "localhost", options.udp)
else:
raise Exception("No valid source selected")
print "Rate is %i" % (options.rate,)