summaryrefslogtreecommitdiff
path: root/gsm-tvoid/src/python
diff options
context:
space:
mode:
Diffstat (limited to 'gsm-tvoid/src/python')
-rwxr-xr-xgsm-tvoid/src/python/gsm_scan.py205
1 files changed, 151 insertions, 54 deletions
diff --git a/gsm-tvoid/src/python/gsm_scan.py b/gsm-tvoid/src/python/gsm_scan.py
index 5d48ce5..6d25fd3 100755
--- a/gsm-tvoid/src/python/gsm_scan.py
+++ b/gsm-tvoid/src/python/gsm_scan.py
@@ -1,13 +1,15 @@
#!/usr/bin/env python
# TODO:
-# * Adjust offset by PPM
-# * Auto-tune offset (add option to enable)
# * Add status info to window (frequency, offset, etc)
# * Put direct frequency tuning back
# * Add rate-limited file reads (throttle?)
# * Make console only version
# * Reset burst_stats on retune
# * Add better option checking
+# * Wideband (multi-channel) processing (usrp and/or file input)
+# * Automatic beacon scan (quick scan RSSI, then check for BCCH)
+# * AGC
+# * Refactor, this is too messy
import sys
@@ -26,6 +28,62 @@ from math import pi
import wx
import gsm
+
+class burst_callback(gr.feval_ll):
+ def __init__(self, fg):
+ gr.feval_ll.__init__(self)
+ self.fg = fg
+ self.offset_mean_num = 10 #number of FCCH offsets to average
+ self.offset_vals = []
+
+ def eval(self, x):
+ #print "burst_callback: eval(",x,")\n";
+ try:
+ #TODO: rework so this will work on file input
+ if gsm.BURST_CB_SYNC_OFFSET == x:
+ #print "burst_callback: SYNC_OFFSET\n";
+ if self.fg.options.tuning.count("o"):
+ last_offset = self.fg.burst.last_freq_offset()
+ self.fg.offset -= last_offset
+ #print "burst_callback: SYNC_OFFSET:", last_offset, " ARFCN: ", self.fg.channel, "\n";
+ self.fg.set_channel(self.fg.channel)
+
+ elif gsm.BURST_CB_ADJ_OFFSET == x:
+ last_offset = self.fg.burst.last_freq_offset()
+
+ self.offset_vals.append(last_offset)
+ count = len(self.offset_vals)
+ #print "burst_callback: ADJ_OFFSET:", last_offset, ", count=",count,"\n";
+
+ if count >= self.offset_mean_num:
+ sum = 0.0
+ while len(self.offset_vals):
+ sum += self.offset_vals.pop(0)
+
+ self.fg.mean_offset = sum / self.offset_mean_num
+
+ #print "burst_callback: mean offset:", self.fg.mean_offset, "\n";
+
+ #retune if greater than 100 Hz
+ if abs(self.fg.mean_offset) > 100.0:
+ #print "burst_callback: mean offset adjust:", self.fg.mean_offset, "\n";
+ if self.fg.options.tuning.count("o"):
+ #print "burst_callback: tuning.\n";
+ self.fg.offset -= self.fg.mean_offset
+ self.fg.set_channel(self.fg.channel)
+
+ elif gsm.BURST_CB_TUNE == x and self.fg.options.tuning.count("h"):
+ #print "burst_callback: BURST_CB_TUNE: ARFCN: ", self.fg.burst.next_arfcn, "\n";
+ if self.fg.options.tuning.count("h"):
+ #print "burst_callback: tuning.\n";
+ self.fg.set_channel(self.fg.burst.next_arfcn)
+
+ return 0
+
+ except Exception, e:
+ print "burst_callback: Exception: ", e
+
+
def pick_subdevice(u):
if u.db[0][0].dbid() >= 0:
return (0, 0)
@@ -73,6 +131,9 @@ class app_flow_graph(stdgui.gui_flow_graph):
def __init__(self, frame, panel, vbox, argv):
stdgui.gui_flow_graph.__init__(self)
+ #testing
+ self.status_msg = "Started."
+
self.frame = frame
self.panel = panel
@@ -100,10 +161,15 @@ class app_flow_graph(stdgui.gui_flow_graph):
help="Sample clock offset frequency")
parser.add_option("-E", "--equalizer", type="string", default="none",
help="Type of equalizer to use. none, fixed-dfe [default=%default]")
- parser.add_option("-t", "--timing", type="string", default="cq",
+ parser.add_option("-t", "--timing", type="string", default="cn",
help="Type of timing techniques to use. [default=%default] \n" +
"(n)one, (c)orrelation track, (q)uarter bit, (f)ull04 ")
+ #tuning options
+ parser.add_option("-T", "--tuning", type="string", default="oh",
+ help="Type of tuning to perform. [default=%default] \n" +
+ "(n)one, (o)ffset adjustment, (h)opping ")
+
#file options
parser.add_option("-I", "--inputfile", type="string", default=None,
help="Select a capture file to read")
@@ -115,10 +181,17 @@ class app_flow_graph(stdgui.gui_flow_graph):
#usrp options
parser.add_option("-R", "--rx-subdev-spec", type="subdev", default=None,
help="Select USRP Rx side A or B (default=first one with a daughterboard)")
+ parser.add_option("--fusb-block-size", type="int", default=0,
+ help="Set USRP blocksize")
+ parser.add_option("--fusb-nblocks", type="int", default=0,
+ help="Set USRP block buffers")
+ parser.add_option("--realtime",action="store_true", dest="realtime",
+ help="Use realtime scheduling.")
+
#FIXME: gain not working?
parser.add_option("-g", "--gain", type="eng_float", default=None,
help="Set gain in dB (default is midpoint)")
- parser.add_option("-c", "--channel", type="int", default=None,
+ parser.add_option("-c", "--channel", type="int", default=1,
help="Tune to GSM ARFCN. Overrides --freq")
parser.add_option("-r", "--region", type="string", default="u",
help="Frequency bands to use for channels. (u)s or (e)urope [default=%default]")
@@ -134,6 +207,8 @@ class app_flow_graph(stdgui.gui_flow_graph):
self.region = options.region
self.channel = options.channel
self.offset = options.offset
+
+ self.mean_offset = 0.0 #this is relative averaged freq offset
self.print_status = options.print_console.count('s')
@@ -164,6 +239,31 @@ class app_flow_graph(stdgui.gui_flow_graph):
gsm_symb_rate = 1625000.0 / 6.0
sps = input_rate/gsm_symb_rate
+ # Attempt to enable realtime scheduling
+ if options.realtime:
+ r = gr.enable_realtime_scheduling()
+ if r == gr.RT_OK:
+ realtime = True
+ print "Realtime scheduling ENABLED"
+ else:
+ realtime = False
+ print "Realtime scheduling FAILED"
+
+ #set resonable defaults if no user prefs set
+ if options.realtime: # be more aggressive
+ if options.fusb_block_size == 0:
+ options.fusb_block_size = gr.prefs().get_long('fusb', 'rt_block_size', 1024)
+ if options.fusb_nblocks == 0:
+ options.fusb_nblocks = gr.prefs().get_long('fusb', 'rt_nblocks', 16)
+ else:
+ if options.fusb_block_size == 0:
+ options.fusb_block_size = gr.prefs().get_long('fusb', 'block_size', 4096)
+ if options.fusb_nblocks == 0:
+ options.fusb_nblocks = gr.prefs().get_long('fusb', 'nblocks', 16)
+
+ print "fusb_block_size =", options.fusb_block_size
+ print "fusb_nblocks =", options.fusb_nblocks
+
# Build the flowgraph
# Setup our input source
if options.inputfile:
@@ -172,7 +272,8 @@ class app_flow_graph(stdgui.gui_flow_graph):
self.source = gr.file_source(gr.sizeof_gr_complex, options.inputfile, options.fileloop)
else:
self.using_usrp = True
- self.u = usrp.source_c(decim_rate=options.decim)
+ self.u = usrp.source_c(decim_rate=options.decim,fusb_block_size=options.fusb_block_size,fusb_nblocks=options.fusb_nblocks)
+
if options.rx_subdev_spec is None:
options.rx_subdev_spec = pick_subdevice(self.u)
self.u.set_mux(usrp.determine_rx_mux_value(self.u, options.rx_subdev_spec))
@@ -201,6 +302,7 @@ class app_flow_graph(stdgui.gui_flow_graph):
else:
offset = 0.0
+ #TODO: try a differnet filter for latency (and CPU)
filter_taps = gr.firdes.low_pass(1.0, input_rate, filter_cutoff, filter_t_width, gr.firdes.WIN_HAMMING)
self.filter = gr.freq_xlating_fir_filter_ccf(1, filter_taps, offset, input_rate)
@@ -217,9 +319,17 @@ class app_flow_graph(stdgui.gui_flow_graph):
if self.scopes.count("I"):
self.connect(self.u, self.input_fft_scope)
+ #create a tuner callback
+ self.burst_cb = burst_callback(self)
+
# Setup flow based on decoder selection
if options.decoder.count("c"):
- self.burst = gsm.burst_cf(input_rate)
+ #use the sink version if burst scope not selected
+ if self.scopes.count("b"):
+ self.burst = gsm.burst_cf(self.burst_cb,input_rate)
+ else:
+ self.burst = gsm.burst_sink_c(self.burst_cb,input_rate)
+
self.connect(self.filter, self.burst)
elif options.decoder.count("f"):
@@ -236,7 +346,7 @@ class app_flow_graph(stdgui.gui_flow_graph):
gain_mu,
0.3) #omega_relative_limit,
- self.burst = gsm.burst_ff()
+ self.burst = gsm.burst_ff(self.burst_cb)
self.connect(self.filter, self.demod, self.clocker, self.burst)
if self.scopes.count("d"):
@@ -247,31 +357,6 @@ class app_flow_graph(stdgui.gui_flow_graph):
self.clocked_scope = scopesink.scope_sink_f(self, panel, sample_rate=gsm_symb_rate,v_scale=1)
self.connect(self.clocker, self.clocked_scope)
- elif options.decoder.count("F"):
- #configure clock recovery
- gain_mu = 0.01
- gain_omega = .25 * gain_mu * gain_mu # critically damped
- self.clocker = gr.clock_recovery_mm_cc( sps,
- gain_omega,
- 0.5, #mu
- gain_mu,
- 0.3) #omega_relative_limit,
-
-
- # configure demodulator
- self.demod = gr.quadrature_demod_cf(1);
-
- self.burst = gsm.burst_ff()
- self.connect(self.filter, self.clocker, self.demod, self.burst)
-
- if self.scopes.count("d"):
- self.demod_scope = scopesink.scope_sink_f(self, panel, sample_rate=input_rate)
- self.connect(self.demod, self.demod_scope)
-
- if self.scopes.count("c"):
- self.clocked_scope = scopesink.scope_sink_f(self, panel, sample_rate=gsm_symb_rate,v_scale=1)
- self.connect(self.clocker, self.clocked_scope)
-
# setup decoder parameters
# equalizer
@@ -344,30 +429,26 @@ class app_flow_graph(stdgui.gui_flow_graph):
popts |= gsm.PRINT_GSM_DECODE
#TODO: should warn if PRINT_GSM_DECODE is combined with other flags (will corrupt output for gsmdecode)
-
self.burst.d_print_options = popts
- ##########################
- #set burst tuning callback
- #self.tuner = gsm_tuner()
- #self.burst.set_tuner_callback(self.tuner)
-
- # connect the primary path after source
- self.v2s = gr.vector_to_stream(gr.sizeof_float,142) #burst output is 142 (USEFUL_BITS)
- self.connect(self.burst, self.v2s)
# create and connect the scopes that apply to all decoders
if self.scopes.count("F"):
self.filter_fft_scope = fftsink.fft_sink_c (self, panel, fft_size=1024, sample_rate=input_rate)
self.connect(self.filter, self.filter_fft_scope)
+ # connect the burst output
+ if self.scopes.count("b") or options.outputfile:
+ self.v2s = gr.vector_to_stream(gr.sizeof_float,142) #burst output is 142 (USEFUL_BITS)
+ self.connect(self.burst, self.v2s)
+# else:
+# self.burst_sink = gr.null_sink(gr.sizeof_float)
+# self.connect(self.v2s, self.burst_sink)
+
#Connect output sinks
if self.scopes.count("b"):
self.burst_scope = scopesink.scope_sink_f(self, panel, sample_rate=gsm_symb_rate,v_scale=1)
self.connect(self.v2s, self.burst_scope)
- elif not options.outputfile:
- self.burst_sink = gr.null_sink(gr.sizeof_float)
- self.connect(self.v2s, self.burst_sink)
# setup & connect output file
if options.outputfile:
@@ -383,6 +464,9 @@ class app_flow_graph(stdgui.gui_flow_graph):
self.t1.Start(5000,0)
self.frame.Bind(wx.EVT_TIMER, self.on_tick)
+ #bind the idle routing for message_queue processing
+ self.frame.Bind(wx.EVT_IDLE, self.on_idle)
+
def _set_status_msg(self, msg):
self.frame.GetStatusBar().SetStatusText(msg, 0)
@@ -428,22 +512,28 @@ class app_flow_graph(stdgui.gui_flow_graph):
callback=self.set_channel)
vbox.Add(hbox, 0, wx.EXPAND)
-
+
def set_freq(self, freq):
-
+ #TODO: for wideband processing, determine if the desired freq is within our current sample range.
+ # If so, use the frequency translator to tune. Tune the USRP otherwise.
+ # Maybe have a flag to force tuning the USRP?
+
if not self.using_usrp:
- return False
+ #if reading from file just adjust for offset in the freq translator
+ print "Setting filter center freq to offset: ", self.offset, "\n"
+ self.filter.set_center_freq(self.offset)
+ return True
freq = freq - self.offset
r = self.u.tune(0, self.subdev, freq)
if r:
- self._set_status_msg('%f' % (freq/1e6))
+ self.status_msg = '%f' % (freq/1e6)
return True
else:
- self._set_status_msg("Failed to set frequency (%f)" % (freq/1e6))
+ self.status_msg = "Failed to set frequency (%f)" % (freq/1e6)
return False
def set_gain(self, gain):
@@ -455,23 +545,24 @@ class app_flow_graph(stdgui.gui_flow_graph):
def set_channel(self, chan):
- if not self.using_usrp:
- return False
+ self.chan = chan
freq = get_freq_from_arfcn(chan,self.region)
if freq:
self.set_freq(freq)
else:
- self._set_status_msg("Invalid Channel")
+ self.status_msg = "Invalid Channel"
def print_stats(self):
+
n_total = self.burst.d_total_count
n_unknown = self.burst.d_unknown_count
n_known = n_total - n_unknown
print "======== STATS ========="
- print 'freq_offset: ',self.burst.mean_freq_offset()
+ print 'freq_offset: ',self.offset
+ print 'mean_offset: ',self.mean_offset
print 'sync_loss_count:',self.burst.d_sync_loss_count
print 'total_bursts: ',n_total
print 'fcch_count: ',self.burst.d_fcch_count
@@ -491,7 +582,13 @@ class app_flow_graph(stdgui.gui_flow_graph):
if self.print_status:
self.print_stats()
-
+
+ def on_idle(self, event):
+ #We can't update this while in the tune functions since they can be invoked by callbaks and the GUI croaks...
+ #FIXME: This is icky.
+ self._set_status_msg(self.status_msg)
+ #print "Idle.\n";
+
def main ():
app = stdgui.stdapp(app_flow_graph, "GSM Scanner", nstatus=1)
app.MainLoop()
personal git repositories of Harald Welte. Your mileage may vary