2013-12-10 00:47:21 +00:00
|
|
|
#!/usr/bin/env python
|
|
|
|
|
|
|
|
# Copyright 2008-2011 Steve Glass
|
|
|
|
#
|
|
|
|
# Copyright 2011, 2012, 2013 KA1RBI
|
|
|
|
#
|
|
|
|
# Copyright 2003,2004,2005,2006 Free Software Foundation, Inc.
|
|
|
|
# (from radiorausch)
|
|
|
|
#
|
|
|
|
# This file is part of OP25 and part of GNU Radio
|
|
|
|
#
|
|
|
|
# OP25 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.
|
|
|
|
#
|
|
|
|
# OP25 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 OP25; see the file COPYING. If not, write to the Free
|
|
|
|
# Software Foundation, Inc., 51 Franklin Street, Boston, MA
|
|
|
|
# 02110-1301, USA.
|
|
|
|
|
|
|
|
import os
|
|
|
|
import pickle
|
|
|
|
import sys
|
|
|
|
import threading
|
|
|
|
import wx
|
|
|
|
import wx.html
|
|
|
|
import wx.wizard
|
|
|
|
import math
|
|
|
|
import numpy
|
|
|
|
import time
|
|
|
|
import re
|
|
|
|
try:
|
|
|
|
import Hamlib
|
|
|
|
except:
|
|
|
|
pass
|
|
|
|
|
|
|
|
try:
|
|
|
|
import Numeric
|
|
|
|
except:
|
|
|
|
pass
|
|
|
|
|
|
|
|
from gnuradio import audio, eng_notation, gr, gru, filter, blocks, fft, analog, digital
|
|
|
|
from gnuradio.eng_option import eng_option
|
|
|
|
from gnuradio.wxgui import stdgui2, fftsink2, scopesink2, form
|
|
|
|
from math import pi
|
|
|
|
from optparse import OptionParser
|
|
|
|
|
|
|
|
import op25
|
|
|
|
import op25_repeater
|
|
|
|
|
|
|
|
import gnuradio.wxgui.plot as plot
|
|
|
|
|
|
|
|
import trunking
|
|
|
|
|
|
|
|
#speeds = [300, 600, 900, 1200, 1440, 1800, 1920, 2400, 2880, 3200, 3600, 3840, 4000, 4800, 6000, 6400, 7200, 8000, 9600, 14400, 19200]
|
|
|
|
speeds = [4800]
|
|
|
|
|
|
|
|
os.environ['IMBE'] = 'soft'
|
|
|
|
|
|
|
|
WIRESHARK_PORT = 23456
|
|
|
|
|
|
|
|
msg_wxDATA_EVENT = wx.NewEventType()
|
|
|
|
|
|
|
|
def msg_EVT_DATA_EVENT(win, func):
|
|
|
|
win.Connect(-1, -1, msg_wxDATA_EVENT, func)
|
|
|
|
|
|
|
|
class msg_DataEvent(wx.PyEvent):
|
|
|
|
def __init__(self, data):
|
|
|
|
wx.PyEvent.__init__(self)
|
|
|
|
self.SetEventType (msg_wxDATA_EVENT)
|
|
|
|
self.data = data
|
|
|
|
|
|
|
|
def Clone (self):
|
|
|
|
self.__class__ (self.GetId())
|
|
|
|
|
|
|
|
# The P25 receiver
|
|
|
|
#
|
|
|
|
class p25_rx_block (stdgui2.std_top_block):
|
|
|
|
|
|
|
|
# Initialize the P25 receiver
|
|
|
|
#
|
|
|
|
def __init__(self, frame, panel, vbox, argv):
|
|
|
|
|
|
|
|
stdgui2.std_top_block.__init__(self, frame, panel, vbox, argv)
|
|
|
|
|
|
|
|
# command line argument parsing
|
|
|
|
parser = OptionParser(option_class=eng_option)
|
|
|
|
parser.add_option("--args", type="string", default="", help="device args")
|
|
|
|
parser.add_option("--antenna", type="string", default="", help="select antenna")
|
|
|
|
parser.add_option("-a", "--audio", action="store_true", default=False, help="use direct audio input")
|
|
|
|
parser.add_option("-A", "--audio-if", action="store_true", default=False, help="soundcard IF mode (use --calibration to set IF freq)")
|
|
|
|
parser.add_option("-I", "--audio-input", type="string", default="", help="pcm input device name. E.g., hw:0,0 or /dev/dsp")
|
|
|
|
parser.add_option("-i", "--input", default=None, help="input file name")
|
|
|
|
parser.add_option("-b", "--excess-bw", type="eng_float", default=0.2, help="for RRC filter", metavar="Hz")
|
|
|
|
parser.add_option("-c", "--calibration", type="eng_float", default=0.0, help="USRP offset or audio IF frequency", metavar="Hz")
|
|
|
|
parser.add_option("-C", "--costas-alpha", type="eng_float", default=0.04, help="value of alpha for Costas loop", metavar="Hz")
|
|
|
|
parser.add_option("-f", "--frequency", type="eng_float", default=0.0, help="USRP center frequency", metavar="Hz")
|
|
|
|
parser.add_option("-F", "--ifile", type="string", default=None, help="read input from complex capture file")
|
|
|
|
parser.add_option("-H", "--hamlib-model", type="int", default=None, help="specify model for hamlib")
|
|
|
|
parser.add_option("-s", "--seek", type="int", default=0, help="ifile seek in K")
|
|
|
|
parser.add_option("-S", "--sample-rate", type="int", default=320e3, help="source samp rate")
|
|
|
|
parser.add_option("-t", "--tone-detect", action="store_true", default=False, help="use experimental tone detect algorithm")
|
|
|
|
parser.add_option("-T", "--trunk-conf-file", type="string", default=None, help="trunking config file name")
|
|
|
|
parser.add_option("-v", "--verbosity", type="int", default=10, help="message debug level")
|
|
|
|
parser.add_option("-V", "--vocoder", action="store_true", default=False, help="voice codec")
|
|
|
|
parser.add_option("-o", "--offset", type="eng_float", default=0.0, help="tuning offset frequency [to circumvent DC offset]", metavar="Hz")
|
|
|
|
parser.add_option("-p", "--pause", action="store_true", default=False, help="block on startup")
|
|
|
|
parser.add_option("-w", "--wireshark", action="store_true", default=False, help="output data to Wireshark")
|
|
|
|
parser.add_option("-W", "--wireshark-host", type="string", default="127.0.0.1", help="Wireshark host")
|
|
|
|
parser.add_option("-r", "--raw-symbols", type="string", default=None, help="dump decoded symbols to file")
|
|
|
|
parser.add_option("-R", "--rx-subdev-spec", type="subdev", default=(0, 0), help="select USRP Rx side A or B (default=A)")
|
|
|
|
parser.add_option("-g", "--gain", type="eng_float", default=None, help="set USRP gain in dB (default is midpoint) or set audio gain")
|
|
|
|
parser.add_option("-G", "--gain-mu", type="eng_float", default=0.025, help="gardner gain")
|
|
|
|
parser.add_option("-N", "--gains", type="string", default=None, help="gain settings")
|
|
|
|
parser.add_option("-O", "--audio-output", type="string", default="plughw:0,0", help="audio output device name")
|
2014-02-07 02:54:59 +00:00
|
|
|
parser.add_option("-q", "--freq-corr", type="eng_float", default=0.0, help="frequency correction")
|
2013-12-10 00:47:21 +00:00
|
|
|
(options, args) = parser.parse_args()
|
|
|
|
if len(args) != 0:
|
|
|
|
parser.print_help()
|
|
|
|
sys.exit(1)
|
|
|
|
|
|
|
|
self.channel_rate = 0
|
|
|
|
self.baseband_input = False
|
|
|
|
|
|
|
|
# check if osmocom is accessible
|
|
|
|
try:
|
|
|
|
self.src = None
|
|
|
|
import osmosdr
|
|
|
|
self.src = osmosdr.source(options.args)
|
|
|
|
self.channel_rate = options.sample_rate
|
|
|
|
except Exception:
|
|
|
|
print "osmosdr source_c creation failure"
|
|
|
|
ignore = True
|
|
|
|
|
|
|
|
gain_names = self.src.get_gain_names()
|
|
|
|
for name in gain_names:
|
|
|
|
range = self.src.get_gain_range(name)
|
|
|
|
print "gain: name: %s range: start %d stop %d step %d" % (name, range[0].start(), range[0].stop(), range[0].step())
|
|
|
|
if options.gains:
|
|
|
|
for tuple in options.gains.split(","):
|
|
|
|
name, gain = tuple.split(":")
|
|
|
|
gain = int(gain)
|
|
|
|
print "setting gain %s to %d" % (name, gain)
|
|
|
|
self.src.set_gain(gain, name)
|
|
|
|
|
|
|
|
rates = self.src.get_sample_rates()
|
2014-08-03 21:27:16 +00:00
|
|
|
try:
|
|
|
|
print 'supported sample rates %d-%d step %d' % (rates.start(), rates.stop(), rates.step())
|
|
|
|
except:
|
|
|
|
pass # ignore
|
2013-12-10 00:47:21 +00:00
|
|
|
|
2014-02-07 02:54:59 +00:00
|
|
|
if options.freq_corr:
|
|
|
|
self.src.set_freq_corr(options.freq_corr)
|
|
|
|
|
2013-12-10 00:47:21 +00:00
|
|
|
if options.audio:
|
|
|
|
self.channel_rate = 48000
|
|
|
|
self.baseband_input = True
|
|
|
|
|
|
|
|
if options.audio_if:
|
|
|
|
self.channel_rate = 96000
|
|
|
|
|
|
|
|
if options.ifile:
|
|
|
|
self.channel_rate = 96000 # TODO: fixme
|
|
|
|
|
|
|
|
# setup (read-only) attributes
|
|
|
|
self.symbol_rate = 4800
|
|
|
|
self.symbol_deviation = 600.0
|
|
|
|
self.basic_rate = 48000
|
|
|
|
_default_speed = 4800
|
|
|
|
|
|
|
|
# keep track of flow graph connections
|
|
|
|
self.cnxns = []
|
|
|
|
|
|
|
|
self.datascope_raw_input = False
|
|
|
|
self.data_scope_connected = False
|
|
|
|
|
|
|
|
self.constellation_scope_connected = False
|
|
|
|
|
|
|
|
self.options = options
|
|
|
|
|
|
|
|
for i in xrange(len(speeds)):
|
|
|
|
if speeds[i] == _default_speed:
|
|
|
|
self.current_speed = i
|
|
|
|
self.default_speed_idx = i
|
|
|
|
|
|
|
|
if options.hamlib_model:
|
|
|
|
self.hamlib_attach(options.hamlib_model)
|
|
|
|
|
|
|
|
# initialize the UI
|
|
|
|
#
|
|
|
|
self.__init_gui(frame, panel, vbox)
|
|
|
|
|
|
|
|
# wait for gdb
|
|
|
|
if options.pause:
|
|
|
|
print 'Ready for GDB to attach (pid = %d)' % (os.getpid(),)
|
|
|
|
raw_input("Press 'Enter' to continue...")
|
|
|
|
|
|
|
|
# configure specified data source
|
|
|
|
if options.input:
|
|
|
|
self.open_file(options.input)
|
|
|
|
elif options.frequency:
|
|
|
|
self._set_state("CAPTURING")
|
|
|
|
self.open_usrp()
|
|
|
|
elif options.audio_if:
|
|
|
|
self._set_state("CAPTURING")
|
|
|
|
self.open_audio_c(self.channel_rate, options.gain, options.audio_input)
|
|
|
|
elif options.audio:
|
|
|
|
self._set_state("CAPTURING")
|
|
|
|
self.open_audio(self.channel_rate, options.gain, options.audio_input)
|
|
|
|
# skip past unused FFT spectrum plot
|
|
|
|
self.notebook.AdvanceSelection()
|
|
|
|
elif options.ifile:
|
|
|
|
self._set_state("CAPTURING")
|
|
|
|
self.open_ifile(self.channel_rate, options.gain, options.ifile, options.seek)
|
|
|
|
else:
|
|
|
|
self._set_state("STOPPED")
|
|
|
|
|
|
|
|
# setup common flow graph elements
|
|
|
|
#
|
|
|
|
def __build_graph(self, source, capture_rate):
|
|
|
|
global speeds
|
|
|
|
global WIRESHARK_PORT
|
|
|
|
# tell the scope the source rate
|
|
|
|
self.spectrum.set_sample_rate(capture_rate)
|
|
|
|
# channel filter
|
|
|
|
self.channel_offset = 0.0
|
|
|
|
channel_decim = int(capture_rate // self.channel_rate)
|
|
|
|
channel_rate = capture_rate // channel_decim
|
|
|
|
trans_width = 12.5e3 / 2;
|
|
|
|
trans_centre = trans_width + (trans_width / 2)
|
|
|
|
sps = int(self.basic_rate // self.symbol_rate)
|
|
|
|
|
|
|
|
symbol_decim = 1
|
|
|
|
ntaps = 11 * sps
|
|
|
|
# rrc_coeffs = gr.firdes.root_raised_cosine(1.0, self.basic_rate, self.basic_rate * 0.1, 0.2, ntaps)
|
|
|
|
rrc_coeffs = (1.0/sps,)*sps
|
|
|
|
self.symbol_filter = filter.fir_filter_fff(symbol_decim, rrc_coeffs)
|
|
|
|
|
|
|
|
autotuneq = gr.msg_queue(2)
|
|
|
|
self.fsk4_demod = op25.fsk4_demod_ff(autotuneq, self.basic_rate, self.symbol_rate)
|
|
|
|
|
|
|
|
self.null_sym = blocks.null_sink(gr.sizeof_float)
|
|
|
|
|
|
|
|
levels = [ -2.0, 0.0, 2.0, 4.0 ]
|
|
|
|
self.slicer = op25_repeater.fsk4_slicer_fb(levels)
|
|
|
|
|
|
|
|
self.buffer = blocks.copy(gr.sizeof_float)
|
|
|
|
|
|
|
|
self.rx_q = gr.msg_queue(100)
|
|
|
|
msg_EVT_DATA_EVENT(self.frame, self.msg_data)
|
|
|
|
self.trunk_rx = trunking.rx_ctl(frequency_set = self.change_freq, debug = self.options.verbosity, conf_file = self.options.trunk_conf_file)
|
|
|
|
self.du_watcher = du_queue_watcher(self.rx_q, self.trunk_rx.process_qmsg)
|
|
|
|
udp_port = 0
|
|
|
|
if self.options.wireshark:
|
|
|
|
udp_port = WIRESHARK_PORT
|
|
|
|
if self.options.raw_symbols:
|
|
|
|
self.sink_sf = blocks.file_sink(gr.sizeof_char, self.options.raw_symbols)
|
|
|
|
do_imbe = 1
|
|
|
|
do_output = 1
|
|
|
|
do_msgq = 1
|
|
|
|
self.sink_s = op25_repeater.p25_frame_assembler(self.options.wireshark_host, udp_port, self.options.verbosity, do_imbe, do_output, do_msgq, self.rx_q)
|
|
|
|
if self.options.vocoder:
|
|
|
|
self.sink_imbe = op25_repeater.vocoder(0, 0, 0, '', 0, 0)
|
|
|
|
self.audio_s2f = blocks.short_to_float()
|
|
|
|
self.audio_scaler = blocks.multiply_const_ff(1 / 32768.0)
|
|
|
|
self.audio_output = audio.sink(8000, self.options.audio_output, True)
|
|
|
|
self.connect(self.sink_imbe, self.audio_s2f, self.audio_scaler, self.audio_output)
|
|
|
|
else:
|
|
|
|
self.sink_imbe = blocks.null_sink(gr.sizeof_char)
|
|
|
|
if self.baseband_input:
|
|
|
|
gain = self.options.gain
|
|
|
|
else:
|
|
|
|
gain = 1.0
|
|
|
|
self.baseband_amp = blocks.multiply_const_ff(gain)
|
|
|
|
self.real_amp = blocks.multiply_const_ff(0.2)
|
|
|
|
|
|
|
|
# self.connect_data_scope(True)
|
|
|
|
|
|
|
|
self.fft_state = False
|
|
|
|
self.c4fm_state = False
|
|
|
|
self.fscope_state = False
|
|
|
|
self.corr_state = False
|
|
|
|
self.fac_state = False
|
|
|
|
self.fsk4_demod_connected = False
|
|
|
|
self.psk_demod_connected = False
|
|
|
|
self.fsk4_demod_mode = False
|
|
|
|
self.corr_i_chan = False
|
|
|
|
|
|
|
|
if self.baseband_input:
|
|
|
|
self.rescale = blocks.multiply_const_ff( 1.0 ) # dummy for compat
|
|
|
|
self.float_resamplers = []
|
|
|
|
for i in xrange(len(speeds)):
|
|
|
|
speed = speeds[i] * sps
|
|
|
|
lcm = gru.lcm(speed, capture_rate)
|
|
|
|
interp = int(lcm // capture_rate)
|
|
|
|
decim = int(lcm // speed)
|
|
|
|
resamp = filter.rational_resampler_fff(interp,decim)
|
|
|
|
self.float_resamplers.append(resamp)
|
|
|
|
self.set_connection(c4fm=1)
|
|
|
|
else: # complex input
|
|
|
|
coeffs = filter.firdes.low_pass(1.0, capture_rate, trans_centre, trans_width, filter.firdes.WIN_HANN)
|
|
|
|
self.channel_filter = filter.freq_xlating_fir_filter_ccf(channel_decim, coeffs, 0.0, capture_rate)
|
|
|
|
self.set_channel_offset(0.0, 0, "")
|
|
|
|
# local osc
|
|
|
|
self.lo_freq = self.options.offset
|
|
|
|
if self.options.audio_if or self.options.ifile:
|
|
|
|
self.lo_freq += self.options.calibration
|
|
|
|
self.lo = analog.sig_source_c (channel_rate, analog.GR_SIN_WAVE, self.lo_freq, 1.0, 0)
|
|
|
|
self.mixer = blocks.multiply_cc()
|
2014-02-07 02:54:59 +00:00
|
|
|
lpf_coeffs = filter.firdes.low_pass(1.0, self.channel_rate, 15000, 1500, filter.firdes.WIN_HANN)
|
2013-12-10 00:47:21 +00:00
|
|
|
self.lpf = filter.fir_filter_ccf(1, lpf_coeffs)
|
|
|
|
|
|
|
|
self.to_real = blocks.complex_to_real()
|
|
|
|
|
|
|
|
nphases = 64
|
|
|
|
frac_bw = 0.25
|
|
|
|
rs_taps = filter.firdes.low_pass(nphases, nphases, frac_bw, 0.5-frac_bw)
|
|
|
|
|
|
|
|
self.arb_resampler = filter.pfb.arb_resampler_ccf(
|
|
|
|
float(self.basic_rate)/float(capture_rate))
|
|
|
|
|
|
|
|
fm_demod_gain = self.basic_rate / (2.0 * pi * self.symbol_deviation)
|
|
|
|
self.fm_demod = analog.quadrature_demod_cf(fm_demod_gain)
|
|
|
|
|
|
|
|
rrc_taps = filter.firdes.root_raised_cosine(
|
|
|
|
1.0, # gain (sps since we're interpolating by
|
|
|
|
sps, # sampling rate
|
|
|
|
1.0, # symbol rate
|
|
|
|
self.options.excess_bw, # excess bandwidth (roll-off factor)
|
|
|
|
ntaps)
|
|
|
|
|
|
|
|
#self.symbol_filter_c = gr.interp_fir_filter_ccf(1, rrc_taps)
|
|
|
|
self.symbol_filter_c = blocks.multiply_const_cc(1.0)
|
|
|
|
self.resamplers = []
|
|
|
|
|
|
|
|
for speed in speeds:
|
|
|
|
resampler = filter.pfb.arb_resampler_ccf(
|
|
|
|
float(speed)/float(self.symbol_rate))
|
|
|
|
self.resamplers.append(resampler)
|
|
|
|
|
|
|
|
if self.options.tone_detect:
|
|
|
|
step_size = 7.5e-8
|
|
|
|
theta = -2
|
|
|
|
cic_length = 48
|
|
|
|
self.clock = repeater.tdetect_cc(sps, step_size, theta, cic_length)
|
|
|
|
else:
|
|
|
|
gain_mu= self.options.gain_mu
|
|
|
|
omega = float(sps)
|
|
|
|
gain_omega = 0.1 * gain_mu * gain_mu
|
|
|
|
|
|
|
|
alpha = self.options.costas_alpha
|
|
|
|
beta = 0.125 * alpha * alpha
|
|
|
|
fmax = 1200 # Hz
|
|
|
|
fmax = 2*pi * fmax / self.basic_rate
|
|
|
|
|
|
|
|
self.clock = op25_repeater.gardner_costas_cc(omega, gain_mu, gain_omega, alpha, beta, fmax, -fmax)
|
|
|
|
|
|
|
|
self.agc = analog.feedforward_agc_cc(16, 1.0)
|
|
|
|
|
|
|
|
# Perform Differential decoding on the constellation
|
|
|
|
self.diffdec = digital.diff_phasor_cc()
|
|
|
|
|
|
|
|
# take angle of the difference (in radians)
|
|
|
|
self.to_float = blocks.complex_to_arg()
|
|
|
|
|
|
|
|
# convert from radians such that signal is in -3/-1/+1/+3
|
|
|
|
self.rescale = blocks.multiply_const_ff( (1 / (pi / 4)) )
|
|
|
|
|
|
|
|
# connect it all up
|
|
|
|
self.__connect([[source, (self.mixer, 0)],
|
|
|
|
[self.lo, (self.mixer, 1)]])
|
|
|
|
self.set_connection(fft=1)
|
|
|
|
self.connect_demods()
|
|
|
|
|
|
|
|
# Connect up the flow graph
|
|
|
|
#
|
|
|
|
def __connect(self, cnxns):
|
|
|
|
for l in cnxns:
|
|
|
|
for b in l:
|
|
|
|
if b == l[0]:
|
|
|
|
p = l[0]
|
|
|
|
else:
|
|
|
|
self.connect(p, b)
|
|
|
|
p = b
|
|
|
|
self.cnxns.extend(cnxns)
|
|
|
|
|
|
|
|
# Disconnect the flow graph
|
|
|
|
#
|
|
|
|
def __disconnect(self):
|
|
|
|
for l in self.cnxns:
|
|
|
|
for b in l:
|
|
|
|
if b == l[0]:
|
|
|
|
p = l[0]
|
|
|
|
else:
|
|
|
|
self.disconnect(p, b)
|
|
|
|
p = b
|
|
|
|
self.cnxns = []
|
|
|
|
|
|
|
|
def msg_data(self, evt):
|
|
|
|
params = evt.data
|
|
|
|
freq = params['freq']
|
|
|
|
self.myform['freq'].set_value('%s' % (freq / 1000000.0))
|
|
|
|
talkgroup = params['tgid']
|
|
|
|
tag = params['tag']
|
|
|
|
if not talkgroup:
|
|
|
|
talkgroup = 0
|
|
|
|
if not tag:
|
|
|
|
tag = ''
|
|
|
|
tg = '%s (%d)' % (tag, talkgroup)
|
|
|
|
if talkgroup == 0 and tag == '':
|
|
|
|
tg = ''
|
|
|
|
self.myform['talkgroup'].set_value(tg)
|
|
|
|
nac = params['nac']
|
|
|
|
system = params['system']
|
|
|
|
self.myform['system'].set_value('%X:%s' % (nac, system))
|
|
|
|
|
|
|
|
def set_speed(self, new_speed):
|
|
|
|
# assumes that lock is held, or that we are in init
|
|
|
|
self.disconnect_demods()
|
|
|
|
self.current_speed = new_speed
|
|
|
|
self.connect_fsk4_demod()
|
|
|
|
|
|
|
|
def set_connection(self,
|
|
|
|
fscope=False,
|
|
|
|
fft=False,
|
|
|
|
corr=False,
|
|
|
|
fac=False,
|
|
|
|
c4fm=False):
|
|
|
|
# assumes that lock is held, or that we are in init
|
|
|
|
if fac != self.fac_state:
|
|
|
|
self.fac_state = fac
|
|
|
|
if fac:
|
|
|
|
self.connect(self.mixer, self.fac_scope)
|
|
|
|
else:
|
|
|
|
self.disconnect(self.mixer, self.fac_scope)
|
|
|
|
if corr != self.corr_state:
|
|
|
|
self.corr_state = corr
|
|
|
|
if corr:
|
|
|
|
if self.corr_i_chan:
|
|
|
|
self.connect(self.arb_resampler, self.to_real, self.real_amp, self.correlation_scope)
|
|
|
|
else:
|
|
|
|
self.connect(self.symbol_filter, self.correlation_scope)
|
|
|
|
else:
|
|
|
|
if self.corr_i_chan:
|
|
|
|
self.disconnect(self.arb_resampler, self.to_real, self.real_amp, self.correlation_scope)
|
|
|
|
else:
|
|
|
|
self.disconnect(self.symbol_filter, self.correlation_scope)
|
|
|
|
|
|
|
|
if fscope != self.fscope_state:
|
|
|
|
self.fscope_state = fscope
|
|
|
|
if fscope == 0:
|
|
|
|
self.disconnect(self.buffer, self.float_scope)
|
|
|
|
else:
|
|
|
|
self.connect(self.buffer, self.float_scope)
|
|
|
|
|
|
|
|
if fft != self.fft_state:
|
|
|
|
self.fft_state = fft
|
|
|
|
if fft == 0:
|
|
|
|
self.disconnect(self.mixer, self.spectrum)
|
|
|
|
else:
|
|
|
|
self.connect(self.mixer, self.spectrum)
|
|
|
|
|
|
|
|
if c4fm != self.c4fm_state:
|
|
|
|
self.c4fm_state = c4fm
|
|
|
|
if c4fm == 0:
|
|
|
|
self.disconnect(self.symbol_filter, self.signal_scope)
|
|
|
|
else:
|
|
|
|
self.connect(self.symbol_filter, self.signal_scope)
|
|
|
|
|
|
|
|
def notebook_changed(self, evt):
|
|
|
|
sel = self.notebook.GetSelection()
|
|
|
|
self.lock()
|
|
|
|
self.disconnect_data_scope()
|
|
|
|
self.disconnect_constellation_scope()
|
|
|
|
if sel == 0: # spectrum
|
|
|
|
if not self.baseband_input:
|
|
|
|
self.set_connection(fft=1)
|
|
|
|
self.connect_demods()
|
|
|
|
elif sel == 1: # c4fm
|
|
|
|
self.set_connection(c4fm=1)
|
|
|
|
self.connect_fsk4_demod()
|
|
|
|
elif sel == 2: # datascope
|
|
|
|
self.set_connection()
|
|
|
|
self.connect_fsk4_demod()
|
|
|
|
self.connect_data_scope()
|
|
|
|
elif sel == 3: # constellation (complex)
|
|
|
|
if not self.baseband_input:
|
|
|
|
self.set_connection()
|
|
|
|
self.connect_psk_demod()
|
|
|
|
self.connect_constellation_scope()
|
|
|
|
elif sel == 4: # demodulated symbols
|
|
|
|
self.connect_demods()
|
|
|
|
self.set_connection(fscope=1)
|
|
|
|
elif sel == 5: # traffic pane
|
|
|
|
self.connect_demods()
|
|
|
|
self.set_connection(fscope=1)
|
|
|
|
self.update_traffic(None)
|
|
|
|
elif sel == 6: # correlation
|
|
|
|
self.disconnect_demods()
|
|
|
|
self.current_speed = self.default_speed_idx # reset speed for corr
|
|
|
|
self.data_scope.win.radio_box_speed.SetSelection(self.current_speed)
|
|
|
|
self.connect_fsk4_demod()
|
|
|
|
self.set_connection(corr=1)
|
|
|
|
elif sel == 7: # fac - fast auto correlation
|
|
|
|
if not self.baseband_input:
|
|
|
|
self.set_connection(fac=1)
|
|
|
|
self.connect_demods()
|
|
|
|
self.unlock()
|
|
|
|
|
|
|
|
# initialize the UI
|
|
|
|
#
|
|
|
|
def __init_gui(self, frame, panel, vbox):
|
|
|
|
|
|
|
|
def _form_set_freq(kv):
|
|
|
|
return self.set_freq(kv['freq'])
|
|
|
|
|
|
|
|
self.frame = frame
|
|
|
|
self.frame.CreateStatusBar()
|
|
|
|
self.panel = panel
|
|
|
|
self.vbox = vbox
|
|
|
|
|
|
|
|
# setup the menu bar
|
|
|
|
menubar = self.frame.GetMenuBar()
|
|
|
|
|
|
|
|
# setup the "File" menu
|
|
|
|
file_menu = menubar.GetMenu(0)
|
|
|
|
self.file_new = file_menu.Insert(0, wx.ID_NEW)
|
|
|
|
self.frame.Bind(wx.EVT_MENU, self._on_file_new, self.file_new)
|
|
|
|
self.file_open = file_menu.Insert(1, wx.ID_OPEN)
|
|
|
|
self.frame.Bind(wx.EVT_MENU, self._on_file_open, self.file_open)
|
|
|
|
file_menu.InsertSeparator(2)
|
|
|
|
self.file_properties = file_menu.Insert(3, wx.ID_PROPERTIES)
|
|
|
|
self.frame.Bind(wx.EVT_MENU, self._on_file_properties, self.file_properties)
|
|
|
|
file_menu.InsertSeparator(4)
|
|
|
|
self.file_close = file_menu.Insert(5, wx.ID_CLOSE)
|
|
|
|
self.frame.Bind(wx.EVT_MENU, self._on_file_close, self.file_close)
|
|
|
|
|
|
|
|
# setup the "Edit" menu
|
|
|
|
edit_menu = wx.Menu()
|
|
|
|
self.edit_undo = edit_menu.Insert(0, wx.ID_UNDO)
|
|
|
|
self.frame.Bind(wx.EVT_MENU, self._on_edit_undo, self.edit_undo)
|
|
|
|
self.edit_redo = edit_menu.Insert(1, wx.ID_REDO)
|
|
|
|
self.frame.Bind(wx.EVT_MENU, self._on_edit_redo, self.edit_redo)
|
|
|
|
edit_menu.InsertSeparator(2)
|
|
|
|
self.edit_cut = edit_menu.Insert(3, wx.ID_CUT)
|
|
|
|
self.frame.Bind(wx.EVT_MENU, self._on_edit_cut, self.edit_cut)
|
|
|
|
self.edit_copy = edit_menu.Insert(4, wx.ID_COPY)
|
|
|
|
self.frame.Bind(wx.EVT_MENU, self._on_edit_copy, self.edit_copy)
|
|
|
|
self.edit_paste = edit_menu.Insert(5, wx.ID_PASTE)
|
|
|
|
self.frame.Bind(wx.EVT_MENU, self._on_edit_paste, self.edit_paste)
|
|
|
|
self.edit_delete = edit_menu.Insert(6, wx.ID_DELETE)
|
|
|
|
self.frame.Bind(wx.EVT_MENU, self._on_edit_delete, self.edit_delete)
|
|
|
|
edit_menu.InsertSeparator(7)
|
|
|
|
self.edit_select_all = edit_menu.Insert(8, wx.ID_SELECTALL)
|
|
|
|
self.frame.Bind(wx.EVT_MENU, self._on_edit_select_all, self.edit_select_all)
|
|
|
|
edit_menu.InsertSeparator(9)
|
|
|
|
self.edit_prefs = edit_menu.Insert(10, wx.ID_PREFERENCES)
|
|
|
|
self.frame.Bind(wx.EVT_MENU, self._on_edit_prefs, self.edit_prefs)
|
|
|
|
menubar.Append(edit_menu, "&Edit"); # ToDo use wx.ID_EDIT stuff
|
|
|
|
|
|
|
|
# setup the toolbar
|
|
|
|
if True:
|
|
|
|
self.toolbar = wx.ToolBar(frame, -1, style = wx.TB_DOCKABLE | wx.TB_HORIZONTAL)
|
|
|
|
frame.SetToolBar(self.toolbar)
|
|
|
|
icon_size = wx.Size(24, 24)
|
|
|
|
# new_icon = wx.ArtProvider.GetBitmap(wx.ART_NEW, wx.ART_TOOLBAR, icon_size)
|
|
|
|
# toolbar_new = self.toolbar.AddSimpleTool(wx.ID_NEW, new_icon, "New Capture")
|
|
|
|
# open_icon = wx.ArtProvider.GetBitmap(wx.ART_FILE_OPEN, wx.ART_TOOLBAR, icon_size)
|
|
|
|
# toolbar_open = self.toolbar.AddSimpleTool(wx.ID_OPEN, open_icon, "Open")
|
|
|
|
|
|
|
|
self.toolbar.Realize()
|
|
|
|
else:
|
|
|
|
self.toolbar = None
|
|
|
|
|
|
|
|
# setup the notebook
|
|
|
|
self.notebook = wx.Notebook(self.panel)
|
|
|
|
self.vbox.Add(self.notebook, 1, wx.EXPAND)
|
|
|
|
# add spectrum scope
|
|
|
|
self.spectrum = fftsink2.fft_sink_c(self.notebook, sample_rate = self.channel_rate, fft_size=512, fft_rate=2, average=False, peak_hold=False)
|
|
|
|
try:
|
|
|
|
self.spectrum_plotter = self.spectrum.win.plotter
|
|
|
|
except:
|
|
|
|
self.spectrum_plotter = self.spectrum.win.plot
|
|
|
|
#self.spectrum_plotter.enable_point_label(False)
|
|
|
|
self.spectrum_plotter.Bind(wx.EVT_LEFT_DOWN, self._on_spectrum_left_click)
|
|
|
|
self.notebook.AddPage(self.spectrum.win, "Spectrum")
|
|
|
|
# add C4FM scope
|
|
|
|
self.signal_scope = scopesink2.scope_sink_f(self.notebook, sample_rate = self.basic_rate, v_scale=5, t_scale=0.001)
|
|
|
|
try:
|
|
|
|
self.signal_plotter = self.signal_scope.win.plotter
|
|
|
|
except:
|
|
|
|
self.signal_plotter = self.signal_scope.win.graph
|
|
|
|
self.notebook.AddPage(self.signal_scope.win, "C4FM")
|
|
|
|
# add datascope
|
|
|
|
self.data_scope = datascope_sink_f(self.notebook, samples_per_symbol = 10, num_plots = 100)
|
|
|
|
self.data_plotter = self.data_scope.win.graph
|
|
|
|
wx.EVT_RADIOBOX(self.data_scope.win.radio_box, 11103, self.filter_select)
|
|
|
|
wx.EVT_RADIOBOX(self.data_scope.win.radio_box_speed, 11104, self.speed_select)
|
|
|
|
self.data_scope.win.radio_box_speed.SetSelection(self.current_speed)
|
|
|
|
self.notebook.AddPage(self.data_scope.win, "Datascope")
|
|
|
|
# add complex scope
|
|
|
|
self.complex_scope = constellation_plot_c(self.notebook, title="Constellation", num_plots=250)
|
|
|
|
self.notebook.AddPage(self.complex_scope.win, "Constellation")
|
|
|
|
wx.EVT_RADIOBOX(self.complex_scope.win.radio_box_source, 11108, self.source_select)
|
|
|
|
# add float scope
|
|
|
|
self.float_scope = scopesink2.scope_sink_f(self.notebook, frame_decim=1, sample_rate=self.symbol_rate, v_scale=1, t_scale=0.05)
|
|
|
|
try: #gl
|
|
|
|
self.float_plotter = self.float_scope.win.plotter
|
|
|
|
self.float_scope.win['marker_1'] = 3.0 # set type = large dots
|
|
|
|
except: #nongl
|
|
|
|
self.float_plotter = self.float_scope.win.graph
|
|
|
|
self.float_scope.win.set_format_plus()
|
|
|
|
self.notebook.AddPage(self.float_scope.win, "Symbols")
|
|
|
|
# Traffic snapshot
|
|
|
|
self.traffic = TrafficPane(self.notebook, trunk_traffic=True)
|
|
|
|
self.notebook.AddPage(self.traffic, "Traffic")
|
|
|
|
wx.EVT_BUTTON (self.traffic, 11109, self.update_traffic)
|
|
|
|
# add corr scope
|
|
|
|
self.correlation_scope = correlation_plot_f(self.notebook, frame_decim=4, sps=10, v_scale=1, t_scale=0.05)
|
|
|
|
# self.correlation_plotter = self.correlation_scope.win.plotter
|
|
|
|
wx.EVT_RADIOBOX(self.correlation_scope.win.radio_box_corr, 11105, self.corr_select)
|
|
|
|
self.notebook.AddPage(self.correlation_scope.win, "Correlation")
|
|
|
|
# add fac scope
|
|
|
|
self.fac_scope = fac_sink_c(self.notebook, fac_size=32768, sample_rate=self.channel_rate, title="Auto Correlation")
|
|
|
|
self.notebook.AddPage(self.fac_scope.win, "FAC")
|
|
|
|
# Setup the decoder and report the TUN/TAP device name
|
|
|
|
msgq = gr.msg_queue(2)
|
|
|
|
# self.decode_watcher = decode_watcher(msgq, self.traffic)
|
|
|
|
# self.p25_decoder = op25.decoder_ff(msgq)
|
|
|
|
# self.frame.SetStatusText("TUN/TAP: " + self.p25_decoder.device_name())
|
|
|
|
|
|
|
|
self.notebook.Bind(wx.EVT_NOTEBOOK_PAGE_CHANGED, self.notebook_changed)
|
|
|
|
|
|
|
|
self.myform = myform = form.form()
|
|
|
|
hbox = wx.BoxSizer(wx.HORIZONTAL)
|
|
|
|
|
|
|
|
vbox_form = wx.BoxSizer(wx.VERTICAL)
|
|
|
|
myform['system'] = form.static_text_field(
|
|
|
|
parent=self.panel, sizer=vbox_form, label="System", weight=0)
|
|
|
|
myform['system'].set_value("........................................")
|
|
|
|
myform['talkgroup'] = form.static_text_field(
|
|
|
|
parent=self.panel, sizer=vbox_form, label="Talkgroup", weight=0)
|
|
|
|
myform['talkgroup'].set_value("........................................")
|
|
|
|
|
|
|
|
if not self.baseband_input:
|
|
|
|
if self.options.trunk_conf_file:
|
|
|
|
myform['freq'] = form.static_text_field(
|
|
|
|
parent=self.panel, sizer=vbox_form, label="Frequency", weight=0)
|
|
|
|
myform['freq'].set_value('%s' % self.options.frequency)
|
|
|
|
else:
|
|
|
|
myform['freq'] = form.float_field(
|
|
|
|
parent=self.panel, sizer=vbox_form, label="Frequency", weight=0,
|
|
|
|
callback=myform.check_input_and_call(_form_set_freq, self._set_status_msg))
|
|
|
|
myform['freq'].set_value(self.options.frequency)
|
|
|
|
if self.baseband_input:
|
|
|
|
min_gain = 0
|
|
|
|
max_gain = 200
|
|
|
|
initial_val = 50
|
|
|
|
else:
|
|
|
|
min_gain = 0
|
|
|
|
max_gain = 25
|
|
|
|
initial_val = 10
|
|
|
|
|
|
|
|
hbox.Add(vbox_form, 0, 0)
|
|
|
|
|
|
|
|
vbox_buttons = wx.BoxSizer(wx.VERTICAL)
|
|
|
|
# skip_button = form.button_with_callback(
|
|
|
|
# parent=self.panel, label="Skip",
|
|
|
|
# callback=self.form_skip)
|
|
|
|
# vbox_buttons.Add(skip_button, 0, 0)
|
|
|
|
lockout_button = form.button_with_callback(
|
|
|
|
parent=self.panel, label="Lockout",
|
|
|
|
callback=self.form_lockout)
|
|
|
|
vbox_buttons.Add(lockout_button, 0, 0)
|
|
|
|
myform['hold'] = form.checkbox_field(
|
|
|
|
parent=self.panel, sizer=vbox_buttons, label="Hold", weight=0,
|
|
|
|
callback=myform.check_input_and_call(self.form_hold))
|
|
|
|
hbox.Add(vbox_buttons, 0, 0)
|
|
|
|
|
|
|
|
vbox_sliders = wx.BoxSizer(wx.VERTICAL)
|
|
|
|
myform['signal_gain'] = form.slider_field(parent=self.panel, sizer=vbox_sliders, label="Signal Gain",
|
|
|
|
weight=0,
|
|
|
|
min=min_gain, max=max_gain,
|
|
|
|
callback=self.set_gain)
|
|
|
|
self.myform['signal_gain'].set_value(initial_val)
|
|
|
|
if not self.baseband_input:
|
|
|
|
myform['freq_tune'] = form.slider_field(parent=self.panel, sizer=vbox_sliders, label="Fine Tune",
|
|
|
|
weight=0,
|
|
|
|
min=-3000, max=3000,
|
|
|
|
callback=self.set_freq_tune)
|
|
|
|
if not self.options.trunk_conf_file:
|
|
|
|
myform['demod_type'] = form.radiobox_field(parent=self.panel, sizer=hbox, label="Demod",
|
|
|
|
weight=0, choices=['FSK4','PSK'], specify_rows=True,
|
|
|
|
callback=self.demod_type_chg)
|
|
|
|
|
|
|
|
hbox.Add(vbox_sliders, 0, 0)
|
|
|
|
|
|
|
|
vbox.Add(hbox, 0, 0)
|
|
|
|
|
|
|
|
def change_freq(self, params):
|
|
|
|
freq = params['freq']
|
|
|
|
offset = params['offset']
|
2014-02-07 02:54:59 +00:00
|
|
|
center_freq = params['center_frequency']
|
2013-12-10 00:47:21 +00:00
|
|
|
|
|
|
|
if self.options.hamlib_model:
|
|
|
|
self.hamlib.set_freq(freq)
|
2014-02-07 02:54:59 +00:00
|
|
|
elif params['center_frequency']:
|
|
|
|
relative_freq = center_freq - freq
|
|
|
|
if relative_freq + self.options.offset > self.channel_rate / 2:
|
|
|
|
print '***unable to tune Local Oscillator to offset %d Hz' % (relative_freq + self.options.offset)
|
|
|
|
print '***limit is one half of sample-rate %d = %d' % (self.channel_rate, self.channel_rate / 2)
|
|
|
|
print '***request for frequency %d rejected' % freq
|
|
|
|
|
|
|
|
self.lo_freq = self.options.offset + relative_freq
|
|
|
|
self.lo.set_frequency(self.lo_freq + self.myform['freq_tune'].get_value())
|
|
|
|
self.set_freq(center_freq + offset)
|
|
|
|
#self.spectrum.set_baseband_freq(center_freq)
|
2013-12-10 00:47:21 +00:00
|
|
|
else:
|
|
|
|
self.set_freq(freq + offset)
|
|
|
|
|
|
|
|
# send msg as event to avoid thread safety problems updating form
|
|
|
|
evt = msg_DataEvent(params)
|
|
|
|
wx.PostEvent(self.frame, evt)
|
|
|
|
|
|
|
|
def hamlib_attach(self, model):
|
|
|
|
Hamlib.rig_set_debug (Hamlib.RIG_DEBUG_NONE) # RIG_DEBUG_TRACE
|
|
|
|
|
|
|
|
self.hamlib = Hamlib.Rig (model)
|
|
|
|
self.hamlib.set_conf ("serial_speed","9600")
|
|
|
|
self.hamlib.set_conf ("retry","5")
|
|
|
|
|
|
|
|
self.hamlib.open ()
|
|
|
|
|
|
|
|
def q_action(self, action):
|
|
|
|
msg = gr.message().make_from_string(action, -2, 0, 0)
|
|
|
|
self.rx_q.insert_tail(msg)
|
|
|
|
|
|
|
|
def form_hold(self, kv):
|
|
|
|
if kv['hold']:
|
|
|
|
cmd = 'set_hold'
|
|
|
|
else:
|
|
|
|
cmd = 'unset_hold'
|
|
|
|
self.q_action(cmd)
|
|
|
|
|
|
|
|
def form_skip(self):
|
|
|
|
self.q_action('skip')
|
|
|
|
|
|
|
|
def form_lockout(self):
|
|
|
|
self.q_action('lockout')
|
|
|
|
|
|
|
|
def update_traffic(self, evt):
|
|
|
|
s = self.trunk_rx.to_string()
|
|
|
|
t = {}
|
|
|
|
t['string'] = s
|
|
|
|
self.traffic.update(t)
|
|
|
|
|
|
|
|
def set_gain(self, gain):
|
|
|
|
if self.baseband_input:
|
|
|
|
f = 1.0
|
|
|
|
else:
|
|
|
|
f = 0.1
|
|
|
|
self.baseband_amp.set_k(float(gain) * f)
|
|
|
|
|
|
|
|
def set_freq_tune(self, val):
|
|
|
|
self.myform['freq_tune'].set_value(val)
|
|
|
|
self.lo.set_frequency(val + self.lo_freq)
|
|
|
|
|
|
|
|
def set_freq(self, target_freq):
|
|
|
|
"""
|
|
|
|
Set the center frequency we're interested in.
|
|
|
|
|
|
|
|
@param target_freq: frequency in Hz
|
|
|
|
@rypte: bool
|
|
|
|
|
|
|
|
Tuning is a two step process. First we ask the front-end to
|
|
|
|
tune as close to the desired frequency as it can. Then we use
|
|
|
|
the result of that operation and our target_frequency to
|
|
|
|
determine the value for the digital down converter.
|
|
|
|
"""
|
2014-02-07 02:54:59 +00:00
|
|
|
tune_freq = target_freq + self.options.calibration + self.options.offset
|
|
|
|
r = self.src.set_center_freq(tune_freq)
|
2013-12-10 00:47:21 +00:00
|
|
|
|
|
|
|
if r:
|
|
|
|
#self.myform['freq'].set_value(target_freq) # update displayed va
|
|
|
|
#if self.show_debug_info:
|
|
|
|
# self.myform['baseband'].set_value(r.baseband_freq)
|
|
|
|
# self.myform['ddc'].set_value(r.dxc_freq)
|
|
|
|
return True
|
|
|
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
def demod_type_chg(self, val):
|
|
|
|
if val == 'FSK4':
|
|
|
|
self.fsk4_demod_mode = True
|
|
|
|
else:
|
|
|
|
self.fsk4_demod_mode = False
|
|
|
|
self.lock()
|
|
|
|
self.disconnect_demods()
|
|
|
|
notebook_sel = self.notebook.GetSelection()
|
|
|
|
if notebook_sel == 4: # demod symbols
|
|
|
|
self.connect_demods()
|
|
|
|
elif notebook_sel == 1 or notebook_sel == 2 or notebook_sel == 5:
|
|
|
|
self.connect_fsk4_demod()
|
|
|
|
elif notebook_sel == 3: # constellation
|
|
|
|
self.connect_psk_demod()
|
|
|
|
|
|
|
|
self.unlock()
|
|
|
|
|
|
|
|
def _set_status_msg(self, msg):
|
|
|
|
self.frame.GetStatusBar().SetStatusText(msg, 0)
|
|
|
|
|
|
|
|
# read capture file properties (decimation etc.)
|
|
|
|
#
|
|
|
|
def __read_file_properties(self, filename):
|
|
|
|
f = open(filename, "r")
|
|
|
|
self.info = pickle.load(f)
|
|
|
|
ToDo = True
|
|
|
|
f.close()
|
|
|
|
|
|
|
|
# setup to rx from file
|
|
|
|
#
|
|
|
|
def __set_rx_from_file(self, filename, capture_rate):
|
|
|
|
file = blocks.file_source(gr.sizeof_gr_complex, filename, True)
|
|
|
|
throttle = gr.throttle(gr.sizeof_gr_complex, capture_rate)
|
|
|
|
self.__connect([[file, throttle]])
|
|
|
|
self.__build_graph(throttle, capture_rate)
|
|
|
|
|
|
|
|
# setup to rx from Audio
|
|
|
|
#
|
|
|
|
def __set_rx_from_audio(self, capture_rate):
|
|
|
|
self.__build_graph(self.source, capture_rate)
|
|
|
|
|
|
|
|
# setup to rx from USRP
|
|
|
|
#
|
|
|
|
def __set_rx_from_osmosdr(self):
|
|
|
|
# setup osmosdr
|
|
|
|
capture_rate = self.src.set_sample_rate(self.options.sample_rate)
|
|
|
|
if self.options.antenna:
|
|
|
|
self.src.set_antenna(self.options.antenna)
|
|
|
|
self.info["capture-rate"] = capture_rate
|
|
|
|
self.src.set_bandwidth(capture_rate)
|
|
|
|
r = self.src.set_center_freq(self.options.frequency + self.options.calibration+ self.options.offset)
|
|
|
|
print 'set_center_freq: %d' % r
|
|
|
|
if not r:
|
|
|
|
raise RuntimeError("failed to set USRP frequency")
|
|
|
|
# capture file
|
|
|
|
# if preserve:
|
|
|
|
if 0:
|
|
|
|
try:
|
|
|
|
self.capture_filename = os.tmpnam()
|
|
|
|
except RuntimeWarning:
|
|
|
|
ignore = True
|
|
|
|
capture_file = blocks.file_sink(gr.sizeof_gr_complex, self.capture_filename)
|
|
|
|
self.__connect([[self.usrp, capture_file]])
|
|
|
|
else:
|
|
|
|
self.capture_filename = None
|
|
|
|
# everything else
|
|
|
|
self.__build_graph(self.src, capture_rate)
|
|
|
|
|
|
|
|
# Change the UI state
|
|
|
|
#
|
|
|
|
def _set_state(self, new_state):
|
|
|
|
self.state = new_state
|
|
|
|
if "STOPPED" == self.state:
|
|
|
|
# menu items
|
|
|
|
can_capture = self.usrp is not None
|
|
|
|
self.file_new.Enable(can_capture)
|
|
|
|
self.file_open.Enable(True)
|
|
|
|
self.file_properties.Enable(False)
|
|
|
|
self.file_close.Enable(False)
|
|
|
|
# toolbar
|
|
|
|
if self.toolbar:
|
|
|
|
self.toolbar.EnableTool(wx.ID_NEW, can_capture)
|
|
|
|
self.toolbar.EnableTool(wx.ID_OPEN, True)
|
|
|
|
# Visually reflect "no file"
|
|
|
|
self.frame.SetStatusText("", 1)
|
|
|
|
self.frame.SetStatusText("", 2)
|
|
|
|
self.spectrum_plotter.ClearBackground()
|
|
|
|
self.signal_plotter.ClearBackground()
|
|
|
|
# self.symbol_plotter.ClearBackground()
|
|
|
|
# self.traffic.clear()
|
|
|
|
elif "RUNNING" == self.state:
|
|
|
|
# menu items
|
|
|
|
self.file_new.Enable(False)
|
|
|
|
self.file_open.Enable(False)
|
|
|
|
self.file_properties.Enable(True)
|
|
|
|
self.file_close.Enable(True)
|
|
|
|
# toolbar
|
|
|
|
if self.toolbar:
|
|
|
|
self.toolbar.EnableTool(wx.ID_NEW, False)
|
|
|
|
self.toolbar.EnableTool(wx.ID_OPEN, False)
|
|
|
|
elif "CAPTURING" == self.state:
|
|
|
|
# menu items
|
|
|
|
self.file_new.Enable(False)
|
|
|
|
self.file_open.Enable(False)
|
|
|
|
self.file_properties.Enable(True)
|
|
|
|
self.file_close.Enable(True)
|
|
|
|
# toolbar
|
|
|
|
if self.toolbar:
|
|
|
|
self.toolbar.EnableTool(wx.ID_NEW, False)
|
|
|
|
self.toolbar.EnableTool(wx.ID_OPEN, False)
|
|
|
|
|
|
|
|
|
|
|
|
# Append filename to default title bar
|
|
|
|
#
|
|
|
|
def _set_titlebar(self, filename):
|
|
|
|
ToDo = True
|
|
|
|
|
|
|
|
# Write capture file properties
|
|
|
|
#
|
|
|
|
def __write_file_properties(self, filename):
|
|
|
|
f = open(filename, "w")
|
|
|
|
pickle.dump(self.info, f)
|
|
|
|
f.close()
|
|
|
|
|
|
|
|
# Adjust the channel offset
|
|
|
|
#
|
|
|
|
def adjust_channel_offset(self, delta_hz):
|
|
|
|
max_delta_hz = 12000.0
|
|
|
|
delta_hz *= self.symbol_deviation
|
|
|
|
delta_hz = max(delta_hz, -max_delta_hz)
|
|
|
|
delta_hz = min(delta_hz, max_delta_hz)
|
|
|
|
self.channel_filter.set_center_freq(self.channel_offset - delta_hz+ self.options.offset)
|
|
|
|
|
|
|
|
# Close an open file
|
|
|
|
#
|
|
|
|
def _on_file_close(self, event):
|
|
|
|
self.stop()
|
|
|
|
self.wait()
|
|
|
|
self.__disconnect()
|
|
|
|
if "CAPTURING" == self.state and self.capture_filename:
|
|
|
|
dialog = wx.MessageDialog(self.frame, "Save capture file before closing?", style=wx.YES_NO | wx.YES_DEFAULT | wx.ICON_QUESTION)
|
|
|
|
if wx.ID_YES == dialog.ShowModal():
|
|
|
|
save_dialog = wx.FileDialog(self.frame, "Save capture file as:", wildcard="*.dat", style=wx.SAVE|wx.OVERWRITE_PROMPT)
|
|
|
|
if save_dialog.ShowModal() == wx.ID_OK:
|
|
|
|
path = str(save_dialog.GetPath())
|
|
|
|
save_dialog.Destroy()
|
|
|
|
os.rename(self.capture_filename, path)
|
|
|
|
self.__write_file_properties(path + ".info")
|
|
|
|
else:
|
|
|
|
os.remove(self.capture_filename)
|
|
|
|
self.capture_filename = None
|
|
|
|
self._set_state("STOPPED")
|
|
|
|
|
|
|
|
# New capture from USRP
|
|
|
|
#
|
|
|
|
def _on_file_new(self, event):
|
|
|
|
# wizard = wx.wizard.Wizard(self.frame, -1, "New Capture from USRP")
|
|
|
|
# page1 = wizard_intro_page(wizard)
|
|
|
|
# page2 = wizard_details_page(wizard)
|
|
|
|
# page3 = wizard_preserve_page(wizard)
|
|
|
|
# page4 = wizard_finish_page(wizard)
|
|
|
|
# wx.wizard.WizardPageSimple_Chain(page1, page2)
|
|
|
|
# wx.wizard.WizardPageSimple_Chain(page2, page3)
|
|
|
|
# wx.wizard.WizardPageSimple_Chain(page3, page4)
|
|
|
|
# wizard.FitToPage(page1)
|
|
|
|
# if wizard.RunWizard(page1):
|
|
|
|
self.stop()
|
|
|
|
self.wait()
|
|
|
|
# ToDo: get open_usrp() arguments from wizard
|
|
|
|
self.open_usrp((0,0), 200, None, 434.08e06, True) # Test freq
|
|
|
|
self.start()
|
|
|
|
|
|
|
|
# Open an existing capture
|
|
|
|
#
|
|
|
|
def _on_file_open(self, event):
|
|
|
|
dialog = wx.FileDialog(self.frame, "Choose a capture file:", wildcard="*.dat", style=wx.OPEN)
|
|
|
|
if dialog.ShowModal() == wx.ID_OK:
|
|
|
|
file = str(dialog.GetPath())
|
|
|
|
dialog.Destroy()
|
|
|
|
self.stop()
|
|
|
|
self.wait()
|
|
|
|
self.open_file(file)
|
|
|
|
self.start()
|
|
|
|
|
|
|
|
# Present file properties dialog
|
|
|
|
#
|
|
|
|
def _on_file_properties(self, event):
|
|
|
|
# ToDo: show what info we have about the capture file (name,
|
|
|
|
# capture source, capture rate, date(?), size(?),)
|
|
|
|
todo = True
|
|
|
|
|
|
|
|
# Undo the last edit
|
|
|
|
#
|
|
|
|
def _on_edit_undo(self, event):
|
|
|
|
todo = True
|
|
|
|
|
|
|
|
# Redo the edit
|
|
|
|
#
|
|
|
|
def _on_edit_redo(self, event):
|
|
|
|
todo = True
|
|
|
|
|
|
|
|
# Cut the current selection
|
|
|
|
#
|
|
|
|
def _on_edit_cut(self, event):
|
|
|
|
todo = True
|
|
|
|
|
|
|
|
# Copy the current selection
|
|
|
|
#
|
|
|
|
def _on_edit_copy(self, event):
|
|
|
|
todo = True
|
|
|
|
|
|
|
|
# Paste into the current sample
|
|
|
|
#
|
|
|
|
def _on_edit_paste(self, event):
|
|
|
|
todo = True
|
|
|
|
|
|
|
|
# Delete the current selection
|
|
|
|
#
|
|
|
|
def _on_edit_delete(self, event):
|
|
|
|
todo = True
|
|
|
|
|
|
|
|
# Select all
|
|
|
|
#
|
|
|
|
def _on_edit_select_all(self, event):
|
|
|
|
todo = True
|
|
|
|
|
|
|
|
# Open the preferences dialog
|
|
|
|
#
|
|
|
|
def _on_edit_prefs(self, event):
|
|
|
|
todo = True
|
|
|
|
|
|
|
|
|
|
|
|
# Set channel offset and RF squelch threshold
|
|
|
|
#
|
|
|
|
def _on_spectrum_left_click(self, event):
|
|
|
|
if "STOPPED" != self.state:
|
|
|
|
# set frequency
|
|
|
|
x,y = self.spectrum_plotter.GetXY(event)
|
|
|
|
xmin, xmax = self.spectrum_plotter.GetXCurrentRange()
|
|
|
|
x = min(x, xmax)
|
|
|
|
x = max(x, xmin)
|
|
|
|
scale_factor = self.spectrum.win._scale_factor
|
|
|
|
chan_width = 6.25e3
|
|
|
|
x /= scale_factor
|
|
|
|
x += chan_width / 2
|
|
|
|
x = (x // chan_width) * chan_width
|
|
|
|
self.set_channel_offset(x, scale_factor, self.spectrum.win._units)
|
|
|
|
# set squelch threshold
|
|
|
|
ymin, ymax = self.spectrum_plotter.GetYCurrentRange()
|
|
|
|
y = min(y, ymax)
|
|
|
|
y = max(y, ymin)
|
|
|
|
squelch_increment = 5
|
|
|
|
y += squelch_increment / 2
|
|
|
|
y = (y // squelch_increment) * squelch_increment
|
|
|
|
self.set_squelch_threshold(int(y))
|
|
|
|
|
|
|
|
# Open an existing capture file
|
|
|
|
#
|
|
|
|
def open_file(self, capture_file):
|
|
|
|
try:
|
|
|
|
self.__read_file_properties(capture_file + ".info")
|
|
|
|
capture_rate = self.info["capture-rate"]
|
|
|
|
self.__set_rx_from_file(capture_file, capture_rate)
|
|
|
|
self._set_titlebar(capture_file)
|
|
|
|
self._set_state("RUNNING")
|
|
|
|
except Exception, x:
|
|
|
|
wx.MessageBox("Cannot open capture file: " + x.message, "File Error", wx.CANCEL | wx.ICON_EXCLAMATION)
|
|
|
|
|
|
|
|
def open_ifile(self, capture_rate, gain, input_filename, file_seek):
|
|
|
|
speed = 96000 # TODO: fixme
|
|
|
|
ifile = blocks.file_source(gr.sizeof_gr_complex, input_filename, 1)
|
|
|
|
if file_seek > 0:
|
|
|
|
rc = ifile.seek(file_seek*1024, gr.SEEK_SET)
|
|
|
|
assert rc == True
|
|
|
|
#print "seek: %d, rc = %d" % (file_seek, rc)
|
2014-08-03 21:27:16 +00:00
|
|
|
throttle = blocks.throttle(gr.sizeof_gr_complex, speed)
|
|
|
|
self.source = blocks.multiply_const_cc(gain)
|
2013-12-10 00:47:21 +00:00
|
|
|
self.connect(ifile, throttle, self.source)
|
|
|
|
self.__set_rx_from_audio(speed)
|
|
|
|
self._set_titlebar("Playing")
|
|
|
|
self._set_state("PLAYING")
|
|
|
|
|
|
|
|
def open_audio_c(self, capture_rate, gain, audio_input_filename):
|
|
|
|
self.info = {
|
|
|
|
"capture-rate": capture_rate,
|
|
|
|
"center-freq": 0,
|
|
|
|
"source-dev": "AUDIO",
|
|
|
|
"source-decim": 1 }
|
|
|
|
self.audio_source = audio.source(capture_rate, audio_input_filename)
|
|
|
|
self.audio_cvt = gr.float_to_complex()
|
|
|
|
self.connect((self.audio_source, 0), (self.audio_cvt, 0))
|
|
|
|
self.connect((self.audio_source, 1), (self.audio_cvt, 1))
|
|
|
|
self.source = gr.multiply_const_cc(gain)
|
|
|
|
self.connect(self.audio_cvt, self.source)
|
|
|
|
self.__set_rx_from_audio(capture_rate)
|
|
|
|
self._set_titlebar("Capturing")
|
|
|
|
self._set_state("CAPTURING")
|
|
|
|
|
|
|
|
def open_audio(self, capture_rate, gain, audio_input_filename):
|
|
|
|
self.info = {
|
|
|
|
"capture-rate": capture_rate,
|
|
|
|
"center-freq": 0,
|
|
|
|
"source-dev": "AUDIO",
|
|
|
|
"source-decim": 1 }
|
|
|
|
self.source = audio.source(capture_rate, audio_input_filename)
|
|
|
|
self.__set_rx_from_audio(capture_rate)
|
|
|
|
self._set_titlebar("Capturing")
|
|
|
|
self._set_state("CAPTURING")
|
|
|
|
|
|
|
|
# Open the USRP
|
|
|
|
#
|
|
|
|
def open_usrp(self):
|
|
|
|
# try:
|
|
|
|
self.info = {
|
|
|
|
"capture-rate": "unknown",
|
|
|
|
"center-freq": self.options.frequency,
|
|
|
|
"source-dev": "USRP",
|
|
|
|
"source-decim": 1 }
|
|
|
|
self.__set_rx_from_osmosdr()
|
|
|
|
self._set_titlebar("Capturing")
|
|
|
|
self._set_state("CAPTURING")
|
|
|
|
# except Exception, x:
|
|
|
|
# wx.MessageBox("Cannot open USRP: " + x.message, "USRP Error", wx.CANCEL | wx.ICON_EXCLAMATION)
|
|
|
|
|
|
|
|
# Set the channel offset
|
|
|
|
#
|
|
|
|
def set_channel_offset(self, offset_hz, scale, units):
|
|
|
|
self.channel_offset = -offset_hz
|
|
|
|
self.channel_filter.set_center_freq(self.channel_offset+ self.options.offset)
|
|
|
|
self.frame.SetStatusText("Channel offset: " + str(offset_hz * scale) + units, 1)
|
|
|
|
|
|
|
|
# Set the RF squelch threshold level
|
|
|
|
#
|
|
|
|
def set_squelch_threshold(self, squelch_db):
|
|
|
|
self.squelch.set_threshold(squelch_db)
|
|
|
|
self.frame.SetStatusText("Squelch: " + str(squelch_db) + "dB", 2)
|
|
|
|
|
|
|
|
def disconnect_demods(self):
|
|
|
|
# assumes lock held or init
|
|
|
|
idx = self.current_speed
|
|
|
|
if self.fsk4_demod_connected:
|
|
|
|
# self.disconnect(self.mixer, self.lpf, self.arb_resampler, self.resamplers[idx], self.fm_demod, self.baseband_amp, self.symbol_filter, self.fsk4_demod, self.buffer, self.slicer, self.sink_s)
|
|
|
|
if self.baseband_input:
|
|
|
|
self.disconnect(self.source, self.baseband_amp, self.float_resamplers[self.current_speed], self.symbol_filter)
|
|
|
|
else:
|
|
|
|
self.disconnect(self.mixer, self.lpf, self.arb_resampler, self.resamplers[idx], self.fm_demod, self.baseband_amp, self.symbol_filter)
|
|
|
|
self.disconnect(self.symbol_filter, self.fsk4_demod, self.buffer, self.slicer, self.sink_s, self.sink_imbe)
|
|
|
|
if self.options.raw_symbols:
|
|
|
|
self.disconnect(self.slicer, self.sink_sf)
|
|
|
|
self.fsk4_demod_connected = False
|
|
|
|
if self.psk_demod_connected:
|
|
|
|
self.disconnect(self.mixer, self.lpf, self.arb_resampler, self.resamplers[idx], self.agc, self.symbol_filter_c, self.clock, self.diffdec, self.to_float, self.rescale, self.buffer, self.slicer, self.sink_s, self.sink_imbe)
|
|
|
|
if self.options.raw_symbols:
|
|
|
|
self.disconnect(self.slicer, self.sink_sf)
|
|
|
|
self.psk_demod_connected = False
|
|
|
|
|
|
|
|
def connect_psk_demod(self):
|
|
|
|
# assumes lock held or init
|
|
|
|
self.disconnect_demods()
|
|
|
|
idx = self.current_speed
|
|
|
|
self.connect(self.mixer, self.lpf, self.arb_resampler, self.resamplers[idx], self.agc, self.symbol_filter_c, self.clock, self.diffdec, self.to_float, self.rescale, self.buffer, self.slicer, self.sink_s, self.sink_imbe)
|
|
|
|
if self.options.raw_symbols:
|
|
|
|
self.connect(self.slicer, self.sink_sf)
|
|
|
|
self.psk_demod_connected = True
|
|
|
|
|
|
|
|
def connect_fsk4_demod(self):
|
|
|
|
# assumes lock held or init
|
|
|
|
self.disconnect_demods()
|
|
|
|
idx = self.current_speed
|
|
|
|
if self.baseband_input:
|
|
|
|
self.connect(self.source, self.baseband_amp, self.float_resamplers[self.current_speed], self.symbol_filter)
|
|
|
|
else:
|
|
|
|
self.connect(self.mixer, self.lpf, self.arb_resampler, self.resamplers[idx], self.fm_demod, self.baseband_amp, self.symbol_filter)
|
|
|
|
self.connect(self.symbol_filter, self.fsk4_demod, self.buffer, self.slicer, self.sink_s, self.sink_imbe)
|
|
|
|
if self.options.raw_symbols:
|
|
|
|
self.connect(self.slicer, self.sink_sf)
|
|
|
|
self.fsk4_demod_connected = True
|
|
|
|
|
|
|
|
def connect_demods(self):
|
|
|
|
if self.baseband_input:
|
|
|
|
self.connect_fsk4_demod()
|
|
|
|
else:
|
|
|
|
if self.fsk4_demod_mode:
|
|
|
|
self.connect_fsk4_demod()
|
|
|
|
else:
|
|
|
|
self.connect_psk_demod()
|
|
|
|
|
|
|
|
def disconnect_constellation_scope(self):
|
|
|
|
if self.constellation_scope_connected:
|
|
|
|
self.disconnect(self.constellation_scope_input, self.complex_scope)
|
|
|
|
self.constellation_scope_connected = False
|
|
|
|
self.constellation_scope_input = None
|
|
|
|
|
|
|
|
def connect_constellation_scope(self):
|
|
|
|
self.disconnect_constellation_scope()
|
|
|
|
sel = self.complex_scope.win.radio_box_source.GetSelection()
|
|
|
|
if sel:
|
|
|
|
self.constellation_scope_input = self.diffdec
|
|
|
|
else:
|
|
|
|
self.constellation_scope_input = self.clock
|
|
|
|
self.constellation_scope_connected = True
|
|
|
|
self.connect(self.constellation_scope_input, self.complex_scope)
|
|
|
|
|
|
|
|
def disconnect_data_scope(self):
|
|
|
|
if self.data_scope_connected:
|
|
|
|
self.disconnect(self.data_scope_input, self.data_scope)
|
|
|
|
self.data_scope_connected = False
|
|
|
|
self.data_scope_input = None
|
|
|
|
|
|
|
|
def connect_data_scope(self):
|
|
|
|
self.disconnect_data_scope()
|
|
|
|
sel = self.data_scope.win.radio_box.GetSelection()
|
|
|
|
if sel:
|
|
|
|
self.data_scope_input = self.symbol_filter
|
|
|
|
else:
|
|
|
|
self.data_scope_input = self.baseband_amp
|
|
|
|
self.data_scope_connected = True
|
|
|
|
self.connect(self.data_scope_input, self.data_scope)
|
|
|
|
|
|
|
|
# for datascope, choose monitor viewpoint
|
|
|
|
def filter_select(self, evt):
|
|
|
|
self.lock()
|
|
|
|
self.connect_data_scope()
|
|
|
|
self.unlock()
|
|
|
|
|
|
|
|
def corr_select(self, evt):
|
|
|
|
new_corr = self.correlation_scope.win.radio_box_corr.GetSelection()
|
|
|
|
self.correlation_scope.win.correlation = self.correlation_scope.win.signatures[new_corr]
|
|
|
|
if self.baseband_input:
|
|
|
|
return
|
|
|
|
self.lock()
|
|
|
|
self.set_connection()
|
|
|
|
if new_corr == len(self.correlation_scope.win.signatures) - 1:
|
|
|
|
# special iden mode
|
|
|
|
self.corr_i_chan = True
|
|
|
|
else:
|
|
|
|
self.corr_i_chan = False
|
|
|
|
self.set_connection(corr=True)
|
|
|
|
self.unlock()
|
|
|
|
|
|
|
|
def source_select(self, evt):
|
|
|
|
self.lock()
|
|
|
|
self.connect_constellation_scope()
|
|
|
|
self.unlock()
|
|
|
|
|
|
|
|
def speed_select(self, evt):
|
|
|
|
new_speed = self.data_scope.win.radio_box_speed.GetSelection()
|
|
|
|
self.lock()
|
|
|
|
self.set_speed(new_speed)
|
|
|
|
self.unlock()
|
|
|
|
|
|
|
|
class window_with_ctlbox(wx.Panel):
|
|
|
|
def __init__(self, parent, id = -1):
|
|
|
|
wx.Panel.__init__(self, parent, id)
|
|
|
|
|
|
|
|
def make_control_box (self):
|
|
|
|
global speeds
|
|
|
|
ctrlbox = wx.BoxSizer (wx.HORIZONTAL)
|
|
|
|
|
|
|
|
ctrlbox.Add ((5,0) ,0)
|
|
|
|
|
|
|
|
run_stop = wx.Button (self, 11102, "Run/Stop")
|
|
|
|
run_stop.SetToolTipString ("Toggle Run/Stop mode")
|
|
|
|
wx.EVT_BUTTON (self, 11102, self.run_stop)
|
|
|
|
ctrlbox.Add (run_stop, 0, wx.EXPAND)
|
|
|
|
|
|
|
|
self.radio_box = wx.RadioBox(self, 11103, "Viewpoint", style=wx.RA_SPECIFY_ROWS,
|
|
|
|
choices = ["Raw", "Filtered"] )
|
|
|
|
self.radio_box.SetToolTipString("Viewpoint Before Or After Symbol Filter")
|
|
|
|
self.radio_box.SetSelection(1)
|
|
|
|
ctrlbox.Add (self.radio_box, 0, wx.EXPAND)
|
|
|
|
|
|
|
|
ctrlbox.Add ((5, 0) ,0) # stretchy space
|
|
|
|
|
|
|
|
speed_str = []
|
|
|
|
for speed in speeds:
|
|
|
|
speed_str.append("%d" % speed)
|
|
|
|
|
|
|
|
self.radio_box_speed = wx.RadioBox(self, 11104, "Symbol Rate", style=wx.RA_SPECIFY_ROWS, majorDimension=2, choices = speed_str)
|
|
|
|
self.radio_box_speed.SetToolTipString("Symbol Rate")
|
|
|
|
ctrlbox.Add (self.radio_box_speed, 0, wx.EXPAND)
|
|
|
|
ctrlbox.Add ((10, 0) ,1) # stretchy space
|
|
|
|
|
|
|
|
return ctrlbox
|
|
|
|
|
|
|
|
# A snapshot of important fields in current traffic
|
|
|
|
#
|
|
|
|
class TrafficPane(wx.Panel):
|
|
|
|
|
|
|
|
# Initializer
|
|
|
|
#
|
|
|
|
def __init__(self, parent, trunk_traffic=False, voice_traffic=False, update_callback=None):
|
|
|
|
|
|
|
|
wx.Panel.__init__(self, parent)
|
|
|
|
sizer = wx.GridBagSizer(hgap=10, vgap=10)
|
|
|
|
self.fields = {}
|
|
|
|
|
|
|
|
if trunk_traffic:
|
|
|
|
#label = wx.StaticText(self, -1, "DUID:")
|
|
|
|
#sizer.Add(label, pos=(1,1))
|
|
|
|
self.update_b = wx.Button (self, 11109, "Update")
|
|
|
|
sizer.Add(self.update_b, pos=(1,1))
|
|
|
|
field = wx.TextCtrl(self, -1, "", size=(500,400), style=wx.TE_MULTILINE+wx.TE_READONLY)
|
|
|
|
sizer.Add(field, pos=(1,2))
|
|
|
|
self.fields["string"] = field;
|
|
|
|
|
|
|
|
if voice_traffic:
|
|
|
|
label = wx.StaticText(self, -1, "DUID:")
|
|
|
|
sizer.Add(label, pos=(1,1))
|
|
|
|
field = wx.TextCtrl(self, -1, "", size=(72, -1), style=wx.TE_READONLY)
|
|
|
|
sizer.Add(field, pos=(1,2))
|
|
|
|
self.fields["duid"] = field;
|
|
|
|
|
|
|
|
label = wx.StaticText(self, -1, "NAC:")
|
|
|
|
sizer.Add(label, pos=(2,1))
|
|
|
|
field = wx.TextCtrl(self, -1, "", size=(175, -1), style=wx.TE_READONLY)
|
|
|
|
sizer.Add(field, pos=(2,2))
|
|
|
|
self.fields["nac"] = field;
|
|
|
|
|
|
|
|
label = wx.StaticText(self, -1, "Source:")
|
|
|
|
sizer.Add(label, pos=(3,1))
|
|
|
|
field = wx.TextCtrl(self, -1, "", size=(175, -1), style=wx.TE_READONLY)
|
|
|
|
sizer.Add(field, pos=(3,2))
|
|
|
|
self.fields["source"] = field;
|
|
|
|
|
|
|
|
label = wx.StaticText(self, -1, "Destination:")
|
|
|
|
sizer.Add(label, pos=(4,1))
|
|
|
|
field = wx.TextCtrl(self, -1, "", size=(175, -1), style=wx.TE_READONLY)
|
|
|
|
sizer.Add(field, pos=(4,2))
|
|
|
|
self.fields["dest"] = field;
|
|
|
|
|
|
|
|
# label = wx.StaticText(self, -1, "ToDo:")
|
|
|
|
# sizer.Add(label, pos=(5,1))
|
|
|
|
# field = wx.TextCtrl(self, -1, "", size=(175, -1), style=wx.TE_READONLY)
|
|
|
|
# sizer.Add(field, pos=(5,2))
|
|
|
|
# self.fields["nid"] = field;
|
|
|
|
|
|
|
|
label = wx.StaticText(self, -1, "MFID:")
|
|
|
|
sizer.Add(label, pos=(1,4))
|
|
|
|
field = wx.TextCtrl(self, -1, "", size=(175, -1), style=wx.TE_READONLY)
|
|
|
|
sizer.Add(field, pos=(1,5))
|
|
|
|
self.fields["mfid"] = field;
|
|
|
|
|
|
|
|
label = wx.StaticText(self, -1, "ALGID:")
|
|
|
|
sizer.Add(label, pos=(2,4))
|
|
|
|
field = wx.TextCtrl(self, -1, "", size=(175, -1), style=wx.TE_READONLY)
|
|
|
|
sizer.Add(field, pos=(2,5))
|
|
|
|
self.fields["algid"] = field;
|
|
|
|
|
|
|
|
label = wx.StaticText(self, -1, "KID:")
|
|
|
|
sizer.Add(label, pos=(3,4))
|
|
|
|
field = wx.TextCtrl(self, -1, "", size=(72, -1), style=wx.TE_READONLY)
|
|
|
|
sizer.Add(field, pos=(3,5))
|
|
|
|
self.fields["kid"] = field;
|
|
|
|
|
|
|
|
label = wx.StaticText(self, -1, "MI:")
|
|
|
|
sizer.Add(label, pos=(4,4))
|
|
|
|
field = wx.TextCtrl(self, -1, "", size=(216, -1), style=wx.TE_READONLY)
|
|
|
|
sizer.Add(field, pos=(4,5))
|
|
|
|
self.fields["mi"] = field;
|
|
|
|
|
|
|
|
label = wx.StaticText(self, -1, "TGID:")
|
|
|
|
sizer.Add(label, pos=(5,4))
|
|
|
|
field = wx.TextCtrl(self, -1, "", size=(72, -1), style=wx.TE_READONLY)
|
|
|
|
sizer.Add(field, pos=(5,5))
|
|
|
|
self.fields["tgid"] = field;
|
|
|
|
|
|
|
|
self.SetSizer(sizer)
|
|
|
|
self.Fit()
|
|
|
|
|
|
|
|
# Clear the field values
|
|
|
|
#
|
|
|
|
def clear(self):
|
|
|
|
for v in self.fields.values():
|
|
|
|
v.Clear()
|
|
|
|
|
|
|
|
# Update the field values
|
|
|
|
#
|
|
|
|
def update(self, field_values):
|
|
|
|
for k,v in self.fields.items():
|
|
|
|
f = field_values.get(k, None)
|
|
|
|
if f:
|
|
|
|
v.SetValue(f)
|
|
|
|
else:
|
|
|
|
v.SetValue("")
|
|
|
|
|
|
|
|
|
|
|
|
# Introduction page for USRP capture wizard
|
|
|
|
#
|
|
|
|
class wizard_intro_page(wx.wizard.WizardPageSimple):
|
|
|
|
|
|
|
|
# Initializer
|
|
|
|
#
|
|
|
|
def __init__(self, parent):
|
|
|
|
wx.wizard.WizardPageSimple.__init__(self, parent)
|
|
|
|
html = wx.html.HtmlWindow(self)
|
|
|
|
html.SetPage('''
|
|
|
|
<html>
|
|
|
|
<body>
|
|
|
|
<h1>Capture from USRP</h1>
|
|
|
|
<p>
|
|
|
|
We will guide you through the process of capturing a sample from the USRP.
|
|
|
|
Please ensure that the USRP is switched on and connected to this computer.
|
|
|
|
</p>
|
|
|
|
</body>
|
|
|
|
</html>
|
|
|
|
''')
|
|
|
|
sizer = wx.BoxSizer(wx.VERTICAL)
|
|
|
|
self.SetSizer(sizer)
|
|
|
|
sizer.Add(html, 1, wx.ALIGN_CENTER | wx.EXPAND | wx.FIXED_MINSIZE)
|
|
|
|
|
|
|
|
|
|
|
|
# USRP wizard details page
|
|
|
|
#
|
|
|
|
class wizard_details_page(wx.wizard.WizardPageSimple):
|
|
|
|
|
|
|
|
# Initializer
|
|
|
|
#
|
|
|
|
def __init__(self, parent):
|
|
|
|
wx.wizard.WizardPageSimple.__init__(self, parent)
|
|
|
|
sizer = wx.BoxSizer(wx.VERTICAL)
|
|
|
|
self.SetSizer(sizer)
|
|
|
|
|
|
|
|
# Return a tuple containing the subdev_spec, gain, frequency, decimation factor
|
|
|
|
#
|
|
|
|
def get_details(self):
|
|
|
|
ToDo = True
|
|
|
|
|
|
|
|
|
|
|
|
# data unit receive queue
|
|
|
|
#
|
|
|
|
class du_queue_watcher(threading.Thread):
|
|
|
|
|
|
|
|
def __init__(self, msgq, callback, **kwds):
|
|
|
|
threading.Thread.__init__ (self, **kwds)
|
|
|
|
self.setDaemon(1)
|
|
|
|
self.msgq = msgq
|
|
|
|
self.callback = callback
|
|
|
|
self.keep_running = True
|
|
|
|
self.start()
|
|
|
|
|
|
|
|
def run(self):
|
|
|
|
while(self.keep_running):
|
|
|
|
msg = self.msgq.delete_head()
|
|
|
|
self.callback(msg)
|
|
|
|
|
|
|
|
# Frequency tracker
|
|
|
|
#
|
|
|
|
class demod_watcher(threading.Thread):
|
|
|
|
|
|
|
|
def __init__(self, msgq, callback, **kwds):
|
|
|
|
threading.Thread.__init__ (self, **kwds)
|
|
|
|
self.setDaemon(1)
|
|
|
|
self.msgq = msgq
|
|
|
|
self.callback = callback
|
|
|
|
self.keep_running = True
|
|
|
|
self.start()
|
|
|
|
|
|
|
|
def run(self):
|
|
|
|
while(self.keep_running):
|
|
|
|
msg = self.msgq.delete_head()
|
|
|
|
frequency_correction = msg.arg1()
|
|
|
|
self.callback(frequency_correction)
|
|
|
|
|
|
|
|
|
|
|
|
# Decoder watcher
|
|
|
|
#
|
|
|
|
class decode_watcher(threading.Thread):
|
|
|
|
|
|
|
|
def __init__(self, msgq, traffic_pane, **kwds):
|
|
|
|
threading.Thread.__init__ (self, **kwds)
|
|
|
|
self.setDaemon(1)
|
|
|
|
self.msgq = msgq
|
|
|
|
self.traffic_pane = traffic_pane
|
|
|
|
self.keep_running = True
|
|
|
|
self.start()
|
|
|
|
|
|
|
|
def run(self):
|
|
|
|
while(self.keep_running):
|
|
|
|
msg = self.msgq.delete_head()
|
|
|
|
pickled_dict = msg.to_string()
|
|
|
|
attrs = pickle.loads(pickled_dict)
|
|
|
|
self.traffic_pane.update(attrs)
|
|
|
|
|
|
|
|
############################################################################
|
|
|
|
# following code modified from GNURadio sources
|
|
|
|
|
|
|
|
default_scopesink_size = (640, 240)
|
|
|
|
default_v_scale = 1000
|
|
|
|
default_frame_decim = gr.prefs().get_long('wxgui', 'frame_decim', 1)
|
|
|
|
|
|
|
|
class datascope_sink_f(gr.hier_block2):
|
|
|
|
def __init__(self, parent, title='', sample_rate=1,
|
|
|
|
size=default_scopesink_size, frame_decim=default_frame_decim,
|
|
|
|
samples_per_symbol=10, num_plots=100,
|
|
|
|
v_scale=default_v_scale, t_scale=None, num_inputs=1, **kwargs):
|
|
|
|
|
|
|
|
gr.hier_block2.__init__(self, "datascope_sink_f",
|
|
|
|
gr.io_signature(num_inputs, num_inputs, gr.sizeof_float),
|
|
|
|
gr.io_signature(0,0,0))
|
|
|
|
|
|
|
|
msgq = gr.msg_queue(2) # message queue that holds at most 2 messages
|
|
|
|
self.st = blocks.message_sink(gr.sizeof_float, msgq, 1)
|
|
|
|
self.connect((self, 0), self.st)
|
|
|
|
|
|
|
|
self.win = datascope_window(datascope_win_info (msgq, sample_rate, frame_decim,
|
|
|
|
v_scale, t_scale, None, title), parent, samples_per_symbol=samples_per_symbol, num_plots=num_plots)
|
|
|
|
|
|
|
|
def set_sample_rate(self, sample_rate):
|
|
|
|
self.guts.set_sample_rate(sample_rate)
|
|
|
|
self.win.info.set_sample_rate(sample_rate)
|
|
|
|
|
|
|
|
# ========================================================================
|
|
|
|
|
|
|
|
wxDATA_EVENT = wx.NewEventType()
|
|
|
|
|
|
|
|
def EVT_DATA_EVENT(win, func):
|
|
|
|
win.Connect(-1, -1, wxDATA_EVENT, func)
|
|
|
|
|
|
|
|
class datascope_DataEvent(wx.PyEvent):
|
|
|
|
def __init__(self, data):
|
|
|
|
wx.PyEvent.__init__(self)
|
|
|
|
self.SetEventType (wxDATA_EVENT)
|
|
|
|
self.data = data
|
|
|
|
|
|
|
|
def Clone (self):
|
|
|
|
self.__class__ (self.GetId())
|
|
|
|
|
|
|
|
class datascope_win_info (object):
|
|
|
|
__slots__ = ['msgq', 'sample_rate', 'frame_decim', 'v_scale',
|
|
|
|
'scopesink', 'title',
|
|
|
|
'time_scale_cursor', 'v_scale_cursor', 'marker', 'xy',
|
|
|
|
'autorange', 'running']
|
|
|
|
|
|
|
|
def __init__ (self, msgq, sample_rate, frame_decim, v_scale, t_scale,
|
|
|
|
scopesink, title = "Oscilloscope", xy=False):
|
|
|
|
self.msgq = msgq
|
|
|
|
self.sample_rate = sample_rate
|
|
|
|
self.frame_decim = frame_decim
|
|
|
|
self.scopesink = scopesink
|
|
|
|
self.title = title;
|
|
|
|
|
|
|
|
self.marker = 'line'
|
|
|
|
self.xy = xy
|
|
|
|
self.autorange = not v_scale
|
|
|
|
self.running = True
|
|
|
|
|
|
|
|
def set_sample_rate(self, sample_rate):
|
|
|
|
self.sample_rate = sample_rate
|
|
|
|
|
|
|
|
def get_sample_rate (self):
|
|
|
|
return self.sample_rate
|
|
|
|
|
|
|
|
def get_decimation_rate (self):
|
|
|
|
return 1.0
|
|
|
|
|
|
|
|
def set_marker (self, s):
|
|
|
|
self.marker = s
|
|
|
|
|
|
|
|
def get_marker (self):
|
|
|
|
return self.marker
|
|
|
|
|
|
|
|
|
|
|
|
class datascope_input_watcher (threading.Thread):
|
|
|
|
def __init__ (self, msgq, event_receiver, frame_decim, num_plots, samples_per_symbol, **kwds):
|
|
|
|
threading.Thread.__init__ (self, **kwds)
|
|
|
|
self.setDaemon (1)
|
|
|
|
self.msgq = msgq
|
|
|
|
self.event_receiver = event_receiver
|
|
|
|
self.frame_decim = frame_decim
|
|
|
|
self.samples_per_symbol = samples_per_symbol
|
|
|
|
self.num_plots = num_plots
|
|
|
|
self.iscan = 0
|
|
|
|
self.keep_running = True
|
|
|
|
self.skip = 0
|
|
|
|
self.totsamp = 0
|
|
|
|
self.skip_samples = 0
|
|
|
|
self.start ()
|
|
|
|
self.msg_string = ""
|
|
|
|
|
|
|
|
def run (self):
|
|
|
|
# print "datascope_input_watcher: pid = ", os.getpid ()
|
|
|
|
while (self.keep_running):
|
|
|
|
msg = self.msgq.delete_head() # blocking read of message queue
|
|
|
|
nchan = int(msg.arg1()) # number of channels of data in msg
|
|
|
|
nsamples = int(msg.arg2()) # number of samples in each channel
|
|
|
|
self.totsamp += nsamples
|
|
|
|
if self.skip_samples >= nsamples:
|
|
|
|
self.skip_samples -= nsamples
|
|
|
|
continue
|
|
|
|
|
|
|
|
self.msg_string += msg.to_string() # body of the msg as a string
|
|
|
|
|
|
|
|
bytes_needed = (self.num_plots*self.samples_per_symbol) * gr.sizeof_float
|
|
|
|
if (len(self.msg_string) < bytes_needed):
|
|
|
|
continue
|
|
|
|
|
|
|
|
records = []
|
|
|
|
# start = self.skip * gr.sizeof_float
|
|
|
|
start = 0
|
|
|
|
chan_data = self.msg_string[start:start+bytes_needed]
|
|
|
|
rec = numpy.fromstring (chan_data, numpy.float32)
|
|
|
|
records.append (rec)
|
|
|
|
self.msg_string = ""
|
|
|
|
|
|
|
|
unused = nsamples - (self.num_plots*self.samples_per_symbol)
|
|
|
|
unused -= (start/gr.sizeof_float)
|
|
|
|
self.skip = self.samples_per_symbol - (unused % self.samples_per_symbol)
|
|
|
|
# print "reclen = %d totsamp %d appended %d skip %d start %d unused %d" % (nsamples, self.totsamp, len(rec), self.skip, start/gr.sizeof_float, unused)
|
|
|
|
|
|
|
|
de = datascope_DataEvent (records)
|
|
|
|
wx.PostEvent (self.event_receiver, de)
|
|
|
|
records = []
|
|
|
|
del de
|
|
|
|
|
|
|
|
# lower values = more frequent plots, but higher CPU usage
|
|
|
|
self.skip_samples = self.num_plots * self.samples_per_symbol * 20
|
|
|
|
|
|
|
|
class datascope_window (window_with_ctlbox):
|
|
|
|
|
|
|
|
def __init__ (self, info, parent, id = -1,
|
|
|
|
samples_per_symbol=10, num_plots=100,
|
|
|
|
pos = wx.DefaultPosition, size = wx.DefaultSize, name = ""):
|
|
|
|
window_with_ctlbox.__init__ (self, parent, -1)
|
|
|
|
self.info = info
|
|
|
|
|
|
|
|
vbox = wx.BoxSizer (wx.VERTICAL)
|
|
|
|
|
|
|
|
self.graph = datascope_graph_window (info, self, -1, samples_per_symbol=samples_per_symbol, num_plots=num_plots)
|
|
|
|
|
|
|
|
vbox.Add (self.graph, 1, wx.EXPAND)
|
|
|
|
vbox.Add (self.make_control_box(), 0, wx.EXPAND)
|
|
|
|
vbox.Add (self.make_control2_box(), 0, wx.EXPAND)
|
|
|
|
|
|
|
|
self.sizer = vbox
|
|
|
|
self.SetSizer (self.sizer)
|
|
|
|
self.SetAutoLayout (True)
|
|
|
|
self.sizer.Fit (self)
|
|
|
|
|
|
|
|
|
|
|
|
# second row of control buttons etc. appears BELOW control_box
|
|
|
|
def make_control2_box (self):
|
|
|
|
ctrlbox = wx.BoxSizer (wx.HORIZONTAL)
|
|
|
|
|
|
|
|
ctrlbox.Add ((5,0) ,0) # left margin space
|
|
|
|
|
|
|
|
return ctrlbox
|
|
|
|
|
|
|
|
def run_stop (self, evt):
|
|
|
|
self.info.running = not self.info.running
|
|
|
|
|
|
|
|
class datascope_graph_window (plot.PlotCanvas):
|
|
|
|
|
|
|
|
def __init__ (self, info, parent, id = -1,
|
|
|
|
pos = wx.DefaultPosition, size = (140, 140),
|
|
|
|
samples_per_symbol=10, num_plots=100,
|
|
|
|
style = wx.DEFAULT_FRAME_STYLE, name = ""):
|
|
|
|
plot.PlotCanvas.__init__ (self, parent, id, pos, size, style, name)
|
|
|
|
|
|
|
|
self.SetXUseScopeTicks (True)
|
|
|
|
self.SetEnableGrid (False)
|
|
|
|
self.SetEnableZoom (True)
|
|
|
|
self.SetEnableLegend(True)
|
|
|
|
# self.SetBackgroundColour ('black')
|
|
|
|
|
|
|
|
self.info = info;
|
|
|
|
|
|
|
|
self.total_points = 0
|
|
|
|
|
|
|
|
self.samples_per_symbol = samples_per_symbol
|
|
|
|
self.num_plots = num_plots
|
|
|
|
|
|
|
|
EVT_DATA_EVENT (self, self.format_data)
|
|
|
|
|
|
|
|
self.input_watcher = datascope_input_watcher (info.msgq, self, info.frame_decim, self.samples_per_symbol, self.num_plots)
|
|
|
|
|
|
|
|
def format_data (self, evt):
|
|
|
|
if not self.info.running:
|
|
|
|
return
|
|
|
|
|
|
|
|
info = self.info
|
|
|
|
records = evt.data
|
|
|
|
nchannels = len (records)
|
|
|
|
npoints = len (records[0])
|
|
|
|
self.total_points += npoints
|
|
|
|
|
|
|
|
x_vals = numpy.arange (0, self.samples_per_symbol)
|
|
|
|
|
|
|
|
self.SetXUseScopeTicks (True) # use 10 divisions, no labels
|
|
|
|
|
|
|
|
objects = []
|
|
|
|
colors = ['red','orange','yellow','green','blue','violet','cyan','magenta','brown','black']
|
|
|
|
|
|
|
|
r = records[0] # input data
|
|
|
|
for i in range(self.num_plots):
|
|
|
|
points = []
|
|
|
|
for j in range(self.samples_per_symbol):
|
|
|
|
p = [ j, r[ i*self.samples_per_symbol + j ] ]
|
|
|
|
points.append(p)
|
|
|
|
objects.append (plot.PolyLine (points, colour=colors[i % len(colors)], legend=('')))
|
|
|
|
|
|
|
|
graphics = plot.PlotGraphics (objects,
|
|
|
|
title='Data Scope',
|
|
|
|
xLabel = 'Time', yLabel = 'Amplitude')
|
|
|
|
|
|
|
|
x_range = (0., 0. + (self.samples_per_symbol-1)) # ranges are tuples!
|
|
|
|
self.y_range = (-4., 4.) # for standard -3/-1/+1/+3
|
|
|
|
# self.y_range = (-10., 10.) # for standard -3/-1/+1/+3
|
|
|
|
self.Draw (graphics, xAxis=x_range, yAxis=self.y_range)
|
|
|
|
############################################################################
|
|
|
|
class constellation_plot_c(gr.hier_block2):
|
|
|
|
def __init__(self, parent, title='', sample_rate=1,
|
|
|
|
frame_decim=10,
|
|
|
|
num_plots=100,
|
|
|
|
num_inputs=1, **kwargs):
|
|
|
|
|
|
|
|
gr.hier_block2.__init__(self, "constellation_plot_c",
|
|
|
|
gr.io_signature(num_inputs, num_inputs, gr.sizeof_gr_complex),
|
|
|
|
gr.io_signature(0,0,0))
|
|
|
|
|
|
|
|
msgq = gr.msg_queue(2) # message queue that holds at most 2 messages
|
|
|
|
self.st = blocks.message_sink(gr.sizeof_gr_complex, msgq, 1)
|
|
|
|
self.connect((self, 0), self.st)
|
|
|
|
|
|
|
|
self.win = constellation_plot_window(constellation_plot_win_info (msgq, sample_rate, frame_decim, None, title), parent, num_plots=num_plots)
|
|
|
|
|
|
|
|
def set_sample_rate(self, sample_rate):
|
|
|
|
self.guts.set_sample_rate(sample_rate)
|
|
|
|
self.win.info.set_sample_rate(sample_rate)
|
|
|
|
|
|
|
|
# ========================================================================
|
|
|
|
|
|
|
|
wxDATA_EVENT = wx.NewEventType()
|
|
|
|
|
|
|
|
def EVT_DATA_EVENT(win, func):
|
|
|
|
win.Connect(-1, -1, wxDATA_EVENT, func)
|
|
|
|
|
|
|
|
class constellation_plot_DataEvent(wx.PyEvent):
|
|
|
|
def __init__(self, data):
|
|
|
|
wx.PyEvent.__init__(self)
|
|
|
|
self.SetEventType (wxDATA_EVENT)
|
|
|
|
self.data = data
|
|
|
|
|
|
|
|
def Clone (self):
|
|
|
|
self.__class__ (self.GetId())
|
|
|
|
|
|
|
|
class constellation_plot_win_info (object):
|
|
|
|
__slots__ = ['msgq', 'sample_rate', 'frame_decim',
|
|
|
|
'scopesink', 'title',
|
|
|
|
'time_scale_cursor', 'marker', 'xy',
|
|
|
|
'autorange', 'running']
|
|
|
|
|
|
|
|
def __init__ (self, msgq, sample_rate, frame_decim,
|
|
|
|
scopesink, title = "Oscilloscope", xy=True):
|
|
|
|
self.msgq = msgq
|
|
|
|
self.sample_rate = sample_rate
|
|
|
|
self.frame_decim = frame_decim
|
|
|
|
self.scopesink = scopesink
|
|
|
|
self.title = title;
|
|
|
|
|
|
|
|
self.marker = 'line'
|
|
|
|
self.xy = xy
|
|
|
|
self.autorange = False
|
|
|
|
self.running = True
|
|
|
|
|
|
|
|
def set_sample_rate(self, sample_rate):
|
|
|
|
self.sample_rate = sample_rate
|
|
|
|
|
|
|
|
def get_sample_rate (self):
|
|
|
|
return self.sample_rate
|
|
|
|
|
|
|
|
def get_decimation_rate (self):
|
|
|
|
return 1.0
|
|
|
|
|
|
|
|
def set_marker (self, s):
|
|
|
|
self.marker = s
|
|
|
|
|
|
|
|
def get_marker (self):
|
|
|
|
return self.marker
|
|
|
|
|
|
|
|
|
|
|
|
class constellation_plot_input_watcher (threading.Thread):
|
|
|
|
def __init__ (self, msgq, event_receiver, frame_decim, num_plots, **kwds):
|
|
|
|
threading.Thread.__init__ (self, **kwds)
|
|
|
|
self.setDaemon (1)
|
|
|
|
self.msgq = msgq
|
|
|
|
self.event_receiver = event_receiver
|
|
|
|
self.frame_decim = frame_decim
|
|
|
|
self.num_plots = num_plots
|
|
|
|
self.iscan = 0
|
|
|
|
self.keep_running = True
|
|
|
|
self.skip = 0
|
|
|
|
self.totsamp = 0
|
|
|
|
self.skip_samples = 0
|
|
|
|
self.start ()
|
|
|
|
self.msg_string = ""
|
|
|
|
self.skip_mode = False
|
|
|
|
|
|
|
|
def run (self):
|
|
|
|
# print "constellation_plot_input_watcher: pid = ", os.getpid ()
|
|
|
|
time.sleep(1)
|
|
|
|
while (self.keep_running):
|
|
|
|
bytes_needed = self.num_plots * gr.sizeof_float * 2
|
|
|
|
if self.skip_mode:
|
|
|
|
bytes_needed = 500 * gr.sizeof_float * 2
|
|
|
|
|
|
|
|
if len(self.msg_string) < bytes_needed:
|
|
|
|
msg = self.msgq.delete_head() # blocking read of message queue
|
|
|
|
nchan = int(msg.arg1()) # number of channels of data in msg
|
|
|
|
nsamples = int(msg.arg2()) # number of samples in each channel
|
|
|
|
self.totsamp += nsamples
|
|
|
|
|
|
|
|
self.msg_string += msg.to_string() # body of the msg as a string
|
|
|
|
continue
|
|
|
|
|
|
|
|
chan_data = self.msg_string[:bytes_needed]
|
|
|
|
self.msg_string = self.msg_string[bytes_needed:]
|
|
|
|
|
|
|
|
if self.skip_mode:
|
|
|
|
self.skip_mode = False
|
|
|
|
continue
|
|
|
|
|
|
|
|
records = []
|
|
|
|
# start = self.skip * gr.sizeof_gr_complex
|
|
|
|
# start = 0
|
|
|
|
# chan_data = self.msg_string[start:start+bytes_needed]
|
|
|
|
rec = numpy.fromstring (chan_data, numpy.float32)
|
|
|
|
records.append (rec)
|
|
|
|
# self.msg_string = ""
|
|
|
|
|
|
|
|
# unused = nsamples - self.num_plots
|
|
|
|
# unused -= (start/gr.sizeof_gr_complex)
|
|
|
|
# print "reclen = %d totsamp %d appended %d skip %d start %d unused %d" % (nsamples, self.totsamp, len(rec), self.skip, start/gr.sizeof_float, unused)
|
|
|
|
|
|
|
|
de = constellation_plot_DataEvent (records)
|
|
|
|
wx.PostEvent (self.event_receiver, de)
|
|
|
|
records = []
|
|
|
|
del de
|
|
|
|
|
|
|
|
# lower values = more frequent plots, but higher CPU usage
|
|
|
|
# self.skip_samples = 5000
|
|
|
|
self.skip_mode = True
|
|
|
|
|
|
|
|
class constellation_plot_window (wx.Panel):
|
|
|
|
|
|
|
|
constellation_window_size = wx.DefaultSize
|
|
|
|
def __init__ (self, info, parent, id = -1,
|
|
|
|
num_plots=100,
|
|
|
|
pos = wx.DefaultPosition, size = constellation_window_size, name = ""):
|
|
|
|
wx.Panel.__init__ (self, parent, -1)
|
|
|
|
self.info = info
|
|
|
|
|
|
|
|
hbox = wx.BoxSizer (wx.HORIZONTAL)
|
|
|
|
|
|
|
|
self.graph = constellation_plot_graph_window (info, self, -1, num_plots=num_plots)
|
|
|
|
|
|
|
|
hbox.Add (self.graph, 1, wx.SHAPED)
|
|
|
|
hbox.Add (self.make_control_box(), 0, wx.EXPAND)
|
|
|
|
hbox.Add (self.make_control2_box(), 0, wx.EXPAND)
|
|
|
|
|
|
|
|
self.sizer = hbox
|
|
|
|
self.SetSizer (self.sizer)
|
|
|
|
self.SetAutoLayout (True)
|
|
|
|
self.sizer.Fit (self)
|
|
|
|
|
|
|
|
|
|
|
|
# second row of control buttons etc. appears BELOW control_box
|
|
|
|
def make_control2_box (self):
|
|
|
|
ctrlbox = wx.BoxSizer (wx.HORIZONTAL)
|
|
|
|
|
|
|
|
ctrlbox.Add ((5,0) ,0) # left margin space
|
|
|
|
|
|
|
|
return ctrlbox
|
|
|
|
|
|
|
|
def make_control_box (self):
|
|
|
|
ctrlbox = wx.BoxSizer (wx.HORIZONTAL)
|
|
|
|
|
|
|
|
ctrlbox.Add ((5,0) ,0)
|
|
|
|
|
|
|
|
run_stop = wx.Button (self, 11102, "Run/Stop")
|
|
|
|
run_stop.SetToolTipString ("Toggle Run/Stop mode")
|
|
|
|
wx.EVT_BUTTON (self, 11102, self.run_stop)
|
|
|
|
ctrlbox.Add (run_stop, 0, wx.EXPAND)
|
|
|
|
|
|
|
|
# self.radio_box.SetToolTipString("Viewpoint Before Or After Symbol Filter")
|
|
|
|
|
|
|
|
self.radio_box_mode = wx.RadioBox(self, 11106, "Mode", style=wx.RA_SPECIFY_ROWS,
|
|
|
|
choices = ["Standard", "Population"] )
|
|
|
|
ctrlbox.Add (self.radio_box_mode, 0, wx.EXPAND)
|
|
|
|
|
|
|
|
self.radio_box_color = wx.RadioBox(self, 11107, "Color", style=wx.RA_SPECIFY_ROWS,
|
|
|
|
choices = ["Mono", "2 Color"] )
|
|
|
|
ctrlbox.Add (self.radio_box_color, 0, wx.EXPAND)
|
|
|
|
wx.EVT_RADIOBOX(self.radio_box_color, 11107, self.color_select)
|
|
|
|
|
|
|
|
self.radio_box_source = wx.RadioBox(self, 11108, "Source", style=wx.RA_SPECIFY_ROWS,
|
|
|
|
choices = ["Direct", "Differential"] )
|
|
|
|
ctrlbox.Add (self.radio_box_source, 0, wx.EXPAND)
|
|
|
|
|
|
|
|
ctrlbox.Add ((10, 0) ,1) # stretchy space
|
|
|
|
|
|
|
|
return ctrlbox
|
|
|
|
|
|
|
|
def run_stop (self, evt):
|
|
|
|
self.info.running = not self.info.running
|
|
|
|
|
|
|
|
def color_select(self, evt):
|
|
|
|
sel = self.radio_box_color.GetSelection()
|
|
|
|
if sel:
|
|
|
|
self.graph.color1 = 'red'
|
|
|
|
self.graph.color2 = 'green'
|
|
|
|
else:
|
|
|
|
self.graph.color1 = 'blue'
|
|
|
|
self.graph.color2 = 'blue'
|
|
|
|
|
|
|
|
class constellation_plot_graph_window (plot.PlotCanvas):
|
|
|
|
|
|
|
|
def __init__ (self, info, parent, id = -1,
|
|
|
|
pos = wx.DefaultPosition, size = (140, 140),
|
|
|
|
num_plots=100,
|
|
|
|
style = wx.DEFAULT_FRAME_STYLE, name = ""):
|
|
|
|
plot.PlotCanvas.__init__ (self, parent, id, pos, size, style, name)
|
|
|
|
|
|
|
|
self.SetXUseScopeTicks (True)
|
|
|
|
self.SetEnableGrid (False)
|
|
|
|
self.SetEnableZoom (True)
|
|
|
|
self.SetEnableLegend(True)
|
|
|
|
# self.SetBackgroundColour ('black')
|
|
|
|
|
|
|
|
self.info = info;
|
|
|
|
self.plot_window = parent
|
|
|
|
|
|
|
|
self.total_points = 0
|
|
|
|
|
|
|
|
self.num_plots = num_plots
|
|
|
|
|
|
|
|
EVT_DATA_EVENT (self, self.format_data)
|
|
|
|
|
|
|
|
self.input_watcher = constellation_plot_input_watcher (info.msgq, self, info.frame_decim, self.num_plots)
|
|
|
|
|
|
|
|
self.flag = False
|
|
|
|
|
|
|
|
self.color1 = 'blue'
|
|
|
|
self.color2 = 'blue'
|
|
|
|
|
|
|
|
def format_data (self, evt):
|
|
|
|
if not self.info.running:
|
|
|
|
return
|
|
|
|
if self.plot_window.radio_box_mode.GetSelection():
|
|
|
|
self.format_data_pop(evt)
|
|
|
|
else:
|
|
|
|
self.format_data_std(evt)
|
|
|
|
|
|
|
|
def format_data_std (self, evt):
|
|
|
|
info = self.info
|
|
|
|
records = evt.data
|
|
|
|
nchannels = len (records)
|
|
|
|
npoints = len (records[0])
|
|
|
|
self.total_points += npoints
|
|
|
|
|
|
|
|
self.SetXUseScopeTicks (True) # use 10 divisions, no labels
|
|
|
|
|
|
|
|
objects = []
|
|
|
|
|
|
|
|
r = records[0] # input data
|
|
|
|
l = len(r) / 2
|
|
|
|
p0 = []
|
|
|
|
p1 = []
|
|
|
|
for i in range(l):
|
|
|
|
p = [ r[ i*2 ], r[ i*2+1 ] ]
|
|
|
|
if self.flag:
|
|
|
|
p1.append(p)
|
|
|
|
else:
|
|
|
|
p0.append(p)
|
|
|
|
self.flag = not self.flag
|
|
|
|
|
|
|
|
objects.append (plot.PolyMarker (p0, marker='plus', colour=self.color1))
|
|
|
|
objects.append (plot.PolyMarker (p1, marker='plus', colour=self.color2))
|
|
|
|
|
|
|
|
graphics = plot.PlotGraphics (objects,
|
|
|
|
title='Constellation',
|
|
|
|
xLabel = 'I', yLabel = 'Q')
|
|
|
|
|
|
|
|
x_range = (-1.0, 1.0)
|
|
|
|
y_range = (-1.0, 1.0)
|
|
|
|
self.Draw (graphics, xAxis=x_range, yAxis=y_range)
|
|
|
|
|
|
|
|
def format_data_pop (self, evt):
|
|
|
|
if not self.info.running:
|
|
|
|
return
|
|
|
|
|
|
|
|
info = self.info
|
|
|
|
records = evt.data
|
|
|
|
nchannels = len (records)
|
|
|
|
npoints = len (records[0])
|
|
|
|
self.total_points += npoints
|
|
|
|
|
|
|
|
self.SetXUseScopeTicks (True) # use 10 divisions, no labels
|
|
|
|
|
|
|
|
objects = []
|
|
|
|
|
|
|
|
r = records[0] # input data
|
|
|
|
l = len(r) / 2
|
|
|
|
b0 = []
|
|
|
|
b1 = []
|
|
|
|
max_buckets = 6.0
|
|
|
|
m = int(2 * pi * max_buckets)
|
|
|
|
for i in range(m+1):
|
|
|
|
b0.append(0)
|
|
|
|
b1.append(0)
|
|
|
|
for i in range(l):
|
|
|
|
# p = [ r[ i*2 ], r[ i*2+1 ] ]
|
|
|
|
# if self.flag:
|
|
|
|
# p1.append(p)
|
|
|
|
# else:
|
|
|
|
# p0.append(p)
|
|
|
|
theta = math.atan2 ( r[ i*2 ], r[ i*2+1 ] )
|
|
|
|
bucket = int((theta + pi) * max_buckets)
|
|
|
|
if 1:
|
|
|
|
if self.flag:
|
|
|
|
b0[bucket] += 1
|
|
|
|
else:
|
|
|
|
b1[bucket] += 1
|
|
|
|
self.flag = not self.flag
|
|
|
|
|
|
|
|
# determine avg. "power" - for later rescaling of the values
|
|
|
|
tot = ct = 0
|
|
|
|
for b in b0+b1:
|
|
|
|
tot += b
|
|
|
|
ct += 1
|
|
|
|
avg = float(tot) / float(ct)
|
|
|
|
|
|
|
|
p0 = []
|
|
|
|
p1 = []
|
|
|
|
r = len(b0)
|
|
|
|
for i in range(r):
|
|
|
|
theta = ((float(i)/ r) * 2 * pi) - pi
|
|
|
|
abs = 0.5 * b0[i] / avg
|
|
|
|
p = [ abs * math.cos(theta), abs * math.sin(theta) ]
|
|
|
|
if i == 0:
|
|
|
|
sp = p
|
|
|
|
p0.append(p)
|
|
|
|
p0.append(sp)
|
|
|
|
r = len(b1)
|
|
|
|
for i in range(r):
|
|
|
|
theta = ((float(i) / r) * 2 * pi) - pi
|
|
|
|
abs = 0.5 * b1[i] / avg
|
|
|
|
p = [ abs * math.cos(theta), abs * math.sin(theta) ]
|
|
|
|
if i == 0:
|
|
|
|
sp = p
|
|
|
|
p1.append(p)
|
|
|
|
p1.append(sp)
|
|
|
|
objects.append (plot.PolyLine (p0, colour=self.color1, legend=''))
|
|
|
|
objects.append (plot.PolyLine (p1, colour=self.color2, legend=''))
|
|
|
|
|
|
|
|
graphics = plot.PlotGraphics (objects,
|
|
|
|
title='Constellation',
|
|
|
|
xLabel = 'I', yLabel = 'Q')
|
|
|
|
|
|
|
|
x_range = (-2.5, 2.5)
|
|
|
|
y_range = (-2.5, 2.5)
|
|
|
|
self.Draw (graphics, xAxis=x_range, yAxis=y_range)
|
|
|
|
############################################################################
|
|
|
|
class correlation_plot_f(gr.hier_block2):
|
|
|
|
def __init__(self, parent, title='', sps=10,
|
|
|
|
frame_decim=4,
|
|
|
|
num_inputs=1, **kwargs):
|
|
|
|
|
|
|
|
gr.hier_block2.__init__(self, "correlation_plot_f",
|
|
|
|
gr.io_signature(num_inputs, num_inputs, gr.sizeof_float),
|
|
|
|
gr.io_signature(0,0,0))
|
|
|
|
|
|
|
|
msgq = gr.msg_queue(2) # message queue that holds at most 2 messages
|
|
|
|
self.st = blocks.message_sink(gr.sizeof_float, msgq, 1)
|
|
|
|
self.connect((self, 0), self.st)
|
|
|
|
|
|
|
|
self.win = correlation_plot_window(correlation_plot_win_info (msgq, sps, frame_decim, None, title), parent, sps=sps)
|
|
|
|
|
|
|
|
# ========================================================================
|
|
|
|
|
|
|
|
wxDATA_EVENT = wx.NewEventType()
|
|
|
|
|
|
|
|
def EVT_DATA_EVENT(win, func):
|
|
|
|
win.Connect(-1, -1, wxDATA_EVENT, func)
|
|
|
|
|
|
|
|
class correlation_plot_DataEvent(wx.PyEvent):
|
|
|
|
def __init__(self, data):
|
|
|
|
wx.PyEvent.__init__(self)
|
|
|
|
self.SetEventType (wxDATA_EVENT)
|
|
|
|
self.data = data
|
|
|
|
|
|
|
|
def Clone (self):
|
|
|
|
self.__class__ (self.GetId())
|
|
|
|
|
|
|
|
class correlation_plot_win_info (object):
|
|
|
|
__slots__ = ['msgq', 'sps', 'frame_decim',
|
|
|
|
'scopesink', 'title',
|
|
|
|
'time_scale_cursor', 'marker', 'xy',
|
|
|
|
'autorange', 'running']
|
|
|
|
|
|
|
|
def __init__ (self, msgq, sps, frame_decim,
|
|
|
|
scopesink, title = "Oscilloscope", xy=True):
|
|
|
|
self.msgq = msgq
|
|
|
|
self.sps = sps
|
|
|
|
self.frame_decim = frame_decim
|
|
|
|
self.scopesink = scopesink
|
|
|
|
self.title = title;
|
|
|
|
|
|
|
|
self.marker = 'line'
|
|
|
|
self.xy = xy
|
|
|
|
self.autorange = False
|
|
|
|
self.running = True
|
|
|
|
|
|
|
|
def get_decimation_rate (self):
|
|
|
|
return 1.0
|
|
|
|
|
|
|
|
def set_marker (self, s):
|
|
|
|
self.marker = s
|
|
|
|
|
|
|
|
def get_marker (self):
|
|
|
|
return self.marker
|
|
|
|
|
|
|
|
class correlation_plot_input_watcher (threading.Thread):
|
|
|
|
def __init__ (self, msgq, event_receiver, frame_decim, **kwds):
|
|
|
|
threading.Thread.__init__ (self, **kwds)
|
|
|
|
self.setDaemon (1)
|
|
|
|
self.msgq = msgq
|
|
|
|
self.event_receiver = event_receiver
|
|
|
|
self.frame_decim = frame_decim
|
|
|
|
self.iscan = 0
|
|
|
|
self.keep_running = True
|
|
|
|
self.skip = 0
|
|
|
|
self.totsamp = 0
|
|
|
|
self.skip_samples = 0
|
|
|
|
self.start ()
|
|
|
|
self.msg_string = ""
|
|
|
|
self.skip_mode = False
|
|
|
|
|
|
|
|
def run (self):
|
|
|
|
# print "correlation_plot_input_watcher: pid = ", os.getpid ()
|
|
|
|
time.sleep(1)
|
|
|
|
while (self.keep_running):
|
|
|
|
bytes_needed = 24000 * gr.sizeof_float
|
|
|
|
|
|
|
|
if len(self.msg_string) < bytes_needed:
|
|
|
|
msg = self.msgq.delete_head() # blocking read of message queue
|
|
|
|
nchan = int(msg.arg1()) # number of channels of data in msg
|
|
|
|
nsamples = int(msg.arg2()) # number of samples in each channel
|
|
|
|
self.totsamp += nsamples
|
|
|
|
|
|
|
|
self.msg_string += msg.to_string() # body of the msg as a string
|
|
|
|
continue
|
|
|
|
|
|
|
|
chan_data = self.msg_string[:bytes_needed]
|
|
|
|
self.msg_string = self.msg_string[bytes_needed:]
|
|
|
|
|
|
|
|
# if self.skip_mode:
|
|
|
|
# self.skip_mode = False
|
|
|
|
# continue
|
|
|
|
|
|
|
|
records = []
|
|
|
|
# start = self.skip * gr.sizeof_gr_complex
|
|
|
|
# start = 0
|
|
|
|
# chan_data = self.msg_string[start:start+bytes_needed]
|
|
|
|
rec = numpy.fromstring (chan_data, numpy.float32)
|
|
|
|
records.append (rec)
|
|
|
|
# self.msg_string = ""
|
|
|
|
|
|
|
|
# unused = nsamples - self.num_plots
|
|
|
|
# unused -= (start/gr.sizeof_gr_complex)
|
|
|
|
# print "reclen = %d totsamp %d appended %d skip %d start %d unused %d" % (nsamples, self.totsamp, len(rec), self.skip, start/gr.sizeof_float, unused)
|
|
|
|
|
|
|
|
de = correlation_plot_DataEvent (records)
|
|
|
|
wx.PostEvent (self.event_receiver, de)
|
|
|
|
records = []
|
|
|
|
del de
|
|
|
|
|
|
|
|
# lower values = more frequent plots, but higher CPU usage
|
|
|
|
# self.skip_samples = 5000
|
|
|
|
# self.skip_mode = True
|
|
|
|
|
|
|
|
class correlation_plot_window (wx.Panel):
|
|
|
|
|
|
|
|
def __init__ (self, info, parent, id = -1,
|
|
|
|
sps=10,
|
|
|
|
pos = wx.DefaultPosition, size = wx.DefaultSize, name = ""):
|
|
|
|
wx.Panel.__init__ (self, parent, -1)
|
|
|
|
self.info = info
|
|
|
|
|
|
|
|
vbox = wx.BoxSizer (wx.HORIZONTAL)
|
|
|
|
|
|
|
|
self.graph = correlation_plot_graph_window (info, self, -1, sps=sps)
|
|
|
|
|
|
|
|
vbox.Add (self.graph, 1, wx.EXPAND)
|
|
|
|
vbox.Add (self.make_control_box(), 0, wx.EXPAND)
|
|
|
|
# vbox.Add (self.make_control2_box(), 0, wx.EXPAND)
|
|
|
|
|
|
|
|
self.sizer = vbox
|
|
|
|
self.SetSizer (self.sizer)
|
|
|
|
self.SetAutoLayout (True)
|
|
|
|
self.sizer.Fit (self)
|
|
|
|
|
|
|
|
|
|
|
|
# second row of control buttons etc. appears BELOW control_box
|
|
|
|
def make_control2_box (self):
|
|
|
|
ctrlbox = wx.BoxSizer (wx.HORIZONTAL)
|
|
|
|
|
|
|
|
ctrlbox.Add ((5,0) ,0) # left margin space
|
|
|
|
|
|
|
|
return ctrlbox
|
|
|
|
|
|
|
|
def make_control_box (self):
|
|
|
|
# 48k iden sync sig
|
|
|
|
iden_frame_sync = [0.131053, 0.762875, 0.985880, 0.692932, 0.021247, -0.509172, -0.436476, 0.121728, 0.574703, 0.545912, 0.008813, -0.676659, -0.920639, -0.490609, 0.182287, 0.632788, 0.737212, 0.681760, 0.737237, 0.937172, 1.009479, 0.794382, 0.339788, 0.026356, 0.178487, 0.627079, 0.902744, 0.742624, 0.165377, -0.442614, -0.691702, -0.454418, -0.135002]
|
|
|
|
ctrlbox = wx.BoxSizer (wx.HORIZONTAL)
|
|
|
|
|
|
|
|
ctrlbox.Add ((5,0) ,0)
|
|
|
|
|
|
|
|
# read directory of correlation signatures
|
|
|
|
ents = []
|
|
|
|
self.signatures = []
|
|
|
|
r = re.compile(r'^[13]+$')
|
|
|
|
path = "corr"
|
|
|
|
# another hack, add support for 6000 symbol rate in correlation sigs
|
|
|
|
sps_6k = int((self.info.sps * 4800) / 6000)
|
|
|
|
for fn in os.listdir(path):
|
|
|
|
sps = self.info.sps
|
|
|
|
fn_check = fn
|
|
|
|
if fn.endswith("-6k"):
|
|
|
|
sps = sps_6k
|
|
|
|
fn_check = fn_check.replace("-6k", "")
|
|
|
|
if not r.match(fn_check):
|
|
|
|
continue
|
|
|
|
f = open("%s/%s" % (path, fn))
|
|
|
|
line = f.readline()
|
|
|
|
f.close()
|
|
|
|
ents.append(line.strip())
|
|
|
|
|
|
|
|
frame_sync = []
|
|
|
|
for c in fn:
|
|
|
|
if c == '1':
|
|
|
|
frame_sync.append(1)
|
|
|
|
else: # 3
|
|
|
|
frame_sync.append(-1)
|
|
|
|
correlation = []
|
|
|
|
for symbol in frame_sync:
|
|
|
|
for i in xrange(sps):
|
|
|
|
correlation.append(symbol)
|
|
|
|
correlation.reverse() # reverse order for convolve()
|
|
|
|
self.signatures.append(correlation)
|
|
|
|
|
|
|
|
#special final entry for iden
|
|
|
|
ents.append('iDEN')
|
|
|
|
correlation = iden_frame_sync
|
|
|
|
correlation.reverse() # reverse order for convolve()
|
|
|
|
self.signatures.append(correlation)
|
|
|
|
|
|
|
|
self.radio_box_corr = wx.RadioBox(self, 11105, "Sync Signature", style=wx.RA_SPECIFY_COLS,
|
|
|
|
majorDimension=2, choices = ents )
|
|
|
|
self.radio_box_corr.SetToolTipString("Signatures of Known Signal Types")
|
|
|
|
|
|
|
|
ctrlbox.Add (self.radio_box_corr, 0, wx.EXPAND)
|
|
|
|
|
|
|
|
ctrlbox.Add ((10, 0) ,1) # stretchy space
|
|
|
|
|
|
|
|
return ctrlbox
|
|
|
|
|
|
|
|
def run_stop (self, evt):
|
|
|
|
self.info.running = not self.info.running
|
|
|
|
|
|
|
|
class correlation_plot_graph_window (plot.PlotCanvas):
|
|
|
|
|
|
|
|
def __init__ (self, info, parent, id = -1,
|
|
|
|
pos = wx.DefaultPosition, size = (140, 140),
|
|
|
|
sps=10,
|
|
|
|
style = wx.DEFAULT_FRAME_STYLE, name = ""):
|
|
|
|
plot.PlotCanvas.__init__ (self, parent, id, pos, size, style, name)
|
|
|
|
|
|
|
|
self.SetXUseScopeTicks (True)
|
|
|
|
self.SetEnableGrid (False)
|
|
|
|
self.SetEnableZoom (True)
|
|
|
|
self.SetEnableLegend(True)
|
|
|
|
# self.SetBackgroundColour ('black')
|
|
|
|
# self.Zoom([0, 0], [1.0, 1.0])
|
|
|
|
|
|
|
|
self.info = info;
|
|
|
|
self.parent = parent;
|
|
|
|
|
|
|
|
self.total_points = 0
|
|
|
|
|
|
|
|
EVT_DATA_EVENT (self, self.format_data)
|
|
|
|
|
|
|
|
self.input_watcher = correlation_plot_input_watcher (info.msgq, self, info.frame_decim)
|
|
|
|
|
|
|
|
def format_data (self, evt):
|
|
|
|
if not self.info.running:
|
|
|
|
return
|
|
|
|
|
|
|
|
info = self.info
|
|
|
|
records = evt.data
|
|
|
|
nchannels = len (records)
|
|
|
|
npoints = len (records[0])
|
|
|
|
self.total_points += npoints
|
|
|
|
|
|
|
|
self.SetXUseScopeTicks (True) # use 10 divisions, no labels
|
|
|
|
|
|
|
|
objects = []
|
|
|
|
|
|
|
|
r = records[0] # input data
|
|
|
|
|
|
|
|
sig = self.parent.signatures[self.parent.radio_box_corr.GetSelection()]
|
|
|
|
res = numpy.convolve(r, sig, mode='valid')
|
|
|
|
p0 = []
|
|
|
|
i = 0
|
|
|
|
for p in res:
|
|
|
|
p0.append([i, p])
|
|
|
|
i += 1
|
|
|
|
|
|
|
|
objects.append (plot.PolyLine (p0, colour='blue'))
|
|
|
|
|
|
|
|
graphics = plot.PlotGraphics (objects,
|
|
|
|
title='Correlation',
|
|
|
|
xLabel = '', yLabel = '')
|
|
|
|
|
|
|
|
x_range = (0, len(res))
|
|
|
|
y_range = (-800.0, 800.0)
|
|
|
|
self.Draw (graphics, xAxis=x_range, yAxis=y_range)
|
|
|
|
|
|
|
|
#
|
|
|
|
# following code copied from radiorausch file facsink.py
|
|
|
|
# source: http://sites.google.com/site/radiorausch/
|
|
|
|
#
|
|
|
|
# KA1RBI modified Jul. 2011 to current GR (to fix error messages)
|
|
|
|
#
|
|
|
|
# Copyright 2003,2004,2005,2006 Free Software Foundation, Inc.
|
|
|
|
#
|
|
|
|
# This file is part of GNU Radio
|
|
|
|
#
|
|
|
|
# GNU Radio is free software; you can redistribute it and/or modify
|
|
|
|
# it under the terms of the GNU General Public License as published by
|
|
|
|
# the Free Software Foundation; either version 3, or (at your option)
|
|
|
|
# any later version.
|
|
|
|
#
|
|
|
|
# GNU Radio is distributed in the hope that it will be useful,
|
|
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
# GNU General Public License for more details.
|
|
|
|
#
|
|
|
|
# You should have received a copy of the GNU General Public License
|
|
|
|
# along with GNU Radio; see the file COPYING. If not, write to
|
|
|
|
# the Free Software Foundation, Inc., 51 Franklin Street,
|
|
|
|
# Boston, MA 02110-1301, USA.
|
|
|
|
#
|
|
|
|
|
|
|
|
# default_facsink_size = (640,240)
|
|
|
|
default_facsink_size = wx.DefaultSize
|
|
|
|
default_fac_rate = gr.prefs().get_long('wxgui', 'fac_rate', 3) # was 15
|
|
|
|
|
|
|
|
class fac_sink_base(object):
|
|
|
|
def __init__(self, input_is_real=False, baseband_freq=0, y_per_div=10, ref_level=50,
|
|
|
|
sample_rate=1, fac_size=512,
|
|
|
|
fac_rate=default_fac_rate,
|
|
|
|
average=False, avg_alpha=None, title='', peak_hold=False):
|
|
|
|
|
|
|
|
# initialize common attributes
|
|
|
|
self.baseband_freq = baseband_freq
|
|
|
|
self.y_divs = 8
|
|
|
|
self.y_per_div=y_per_div
|
|
|
|
self.ref_level = ref_level
|
|
|
|
self.sample_rate = sample_rate
|
|
|
|
self.fac_size = fac_size
|
|
|
|
self.fac_rate = fac_rate
|
|
|
|
self.average = average
|
|
|
|
if avg_alpha is None:
|
|
|
|
self.avg_alpha = 0.20 / fac_rate # averaging needed to be slowed down for very slow rates
|
|
|
|
else:
|
|
|
|
self.avg_alpha = avg_alpha
|
|
|
|
self.title = title
|
|
|
|
self.peak_hold = peak_hold
|
|
|
|
self.input_is_real = input_is_real
|
|
|
|
self.msgq = gr.msg_queue(2) # queue that holds a maximum of 2 messages
|
|
|
|
|
|
|
|
def set_y_per_div(self, y_per_div):
|
|
|
|
self.y_per_div = y_per_div
|
|
|
|
|
|
|
|
def set_ref_level(self, ref_level):
|
|
|
|
self.ref_level = ref_level
|
|
|
|
|
|
|
|
def set_average(self, average):
|
|
|
|
self.average = average
|
|
|
|
if average:
|
|
|
|
self.avg.set_taps(self.avg_alpha)
|
|
|
|
self.set_peak_hold(False)
|
|
|
|
else:
|
|
|
|
self.avg.set_taps(1.0)
|
|
|
|
|
|
|
|
def set_peak_hold(self, enable):
|
|
|
|
self.peak_hold = enable
|
|
|
|
if enable:
|
|
|
|
self.set_average(False)
|
|
|
|
self.win.set_peak_hold(enable)
|
|
|
|
|
|
|
|
def set_avg_alpha(self, avg_alpha):
|
|
|
|
self.avg_alpha = avg_alpha
|
|
|
|
|
|
|
|
def set_baseband_freq(self, baseband_freq):
|
|
|
|
self.baseband_freq = baseband_freq
|
|
|
|
|
|
|
|
def set_sample_rate(self, sample_rate):
|
|
|
|
self.sample_rate = sample_rate
|
|
|
|
self._set_n()
|
|
|
|
|
|
|
|
def _set_n(self):
|
|
|
|
self.one_in_n.set_n(max(1, int(self.sample_rate/self.fac_size/self.fac_rate)))
|
|
|
|
|
|
|
|
|
|
|
|
class fac_sink_f(gr.hier_block2, fac_sink_base):
|
|
|
|
def __init__(self, parent, baseband_freq=0,
|
|
|
|
y_per_div=10, ref_level=50, sample_rate=1, fac_size=512,
|
|
|
|
fac_rate=default_fac_rate,
|
|
|
|
average=False, avg_alpha=None,
|
|
|
|
title='', size=default_facsink_size, peak_hold=False):
|
|
|
|
|
|
|
|
fac_sink_base.__init__(self, input_is_real=True, baseband_freq=baseband_freq,
|
|
|
|
y_per_div=y_per_div, ref_level=ref_level,
|
|
|
|
sample_rate=sample_rate, fac_size=fac_size,
|
|
|
|
fac_rate=fac_rate,
|
|
|
|
average=average, avg_alpha=avg_alpha, title=title,
|
|
|
|
peak_hold=peak_hold)
|
|
|
|
|
|
|
|
s2p = gr.stream_to_vector(gr.sizeof_float, self.fac_size)
|
|
|
|
self.one_in_n = gr.keep_one_in_n(gr.sizeof_float * self.fac_size,
|
|
|
|
max(1, int(self.sample_rate/self.fac_size/self.fac_rate)))
|
|
|
|
|
|
|
|
|
|
|
|
# windowing removed...
|
|
|
|
|
|
|
|
fac = gr.fft_vfc(self.fac_size, True, ())
|
|
|
|
|
|
|
|
c2mag = gr.complex_to_mag(self.fac_size)
|
|
|
|
self.avg = gr.single_pole_iir_filter_ff(1.0, self.fac_size)
|
|
|
|
|
|
|
|
#
|
|
|
|
fac_fac = gr.fft_vfc(self.fac_size, True, ())
|
|
|
|
fac_c2mag = gr.complex_to_mag(fac_size)
|
|
|
|
|
|
|
|
|
|
|
|
# FIXME We need to add 3dB to all bins but the DC bin
|
|
|
|
log = gr.nlog10_ff(20, self.fac_size,
|
|
|
|
-20*math.log10(self.fac_size) )
|
|
|
|
sink = gr.message_sink(gr.sizeof_float * self.fac_size, self.msgq, True)
|
|
|
|
|
|
|
|
self.connect(self, s2p, self.one_in_n, fac, c2mag, fac_fac, fac_c2mag, self.avg, log, sink)
|
|
|
|
# gr.hier_block.__init__(self, fg, s2p, sink)
|
|
|
|
gr.hier_block2.__init__(self, "fac_sink_f",
|
|
|
|
gr.io_signature(1, 1, gr.sizeof_float),
|
|
|
|
gr.io_signature(0, 0, 0))
|
|
|
|
|
|
|
|
self.win = fac_window(self, parent, size=size)
|
|
|
|
self.set_average(self.average)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class fac_sink_c(gr.hier_block2, fac_sink_base):
|
|
|
|
def __init__(self, parent, baseband_freq=0,
|
|
|
|
y_per_div=10, ref_level=90, sample_rate=1, fac_size=512,
|
|
|
|
fac_rate=default_fac_rate,
|
|
|
|
average=False, avg_alpha=None,
|
|
|
|
title='', size=default_facsink_size, peak_hold=False):
|
|
|
|
|
|
|
|
fac_sink_base.__init__(self, input_is_real=False, baseband_freq=baseband_freq,
|
|
|
|
y_per_div=y_per_div, ref_level=ref_level,
|
|
|
|
sample_rate=sample_rate, fac_size=fac_size,
|
|
|
|
fac_rate=fac_rate,
|
|
|
|
average=average, avg_alpha=avg_alpha, title=title,
|
|
|
|
peak_hold=peak_hold)
|
|
|
|
gr.hier_block2.__init__(self, "fac_sink_c",
|
|
|
|
gr.io_signature(1, 1, gr.sizeof_gr_complex),
|
|
|
|
gr.io_signature(0, 0, 0))
|
|
|
|
|
|
|
|
s2p = blocks.stream_to_vector(gr.sizeof_gr_complex, self.fac_size)
|
|
|
|
#s2p = repeater.s2v(gr.sizeof_gr_complex, self.fac_size)
|
|
|
|
self.one_in_n = blocks.keep_one_in_n(gr.sizeof_gr_complex * self.fac_size,
|
|
|
|
max(1, int(self.sample_rate/self.fac_size/self.fac_rate)))
|
|
|
|
|
|
|
|
|
|
|
|
# windowing removed ...
|
|
|
|
|
|
|
|
fac = fft.fft_vcc(self.fac_size, True, ())
|
|
|
|
c2mag = blocks.complex_to_mag(fac_size)
|
|
|
|
|
|
|
|
# Things go off into the weeds if we try for an inverse FFT so a forward FFT will have to do...
|
|
|
|
fac_fac = fft.fft_vfc(self.fac_size, True, ())
|
|
|
|
fac_c2mag = blocks.complex_to_mag(fac_size)
|
|
|
|
|
|
|
|
|
|
|
|
self.avg = filter.single_pole_iir_filter_ff(1.0, fac_size)
|
|
|
|
|
|
|
|
log = blocks.nlog10_ff(20, self.fac_size,
|
|
|
|
-20*math.log10(self.fac_size) ) # - 20*math.log10(norm) ) # - self.avg[0] )
|
|
|
|
sink = blocks.message_sink(gr.sizeof_float * fac_size, self.msgq, True)
|
|
|
|
|
|
|
|
self.connect(self, s2p, self.one_in_n, fac, c2mag, fac_fac, fac_c2mag, self.avg)
|
|
|
|
self.connect(self.avg, log, sink)
|
|
|
|
|
|
|
|
# gr.hier_block.__init__(self, fg, s2p, sink)
|
|
|
|
|
|
|
|
self.win = fac_window(self, parent, size=size)
|
|
|
|
self.set_average(self.average)
|
|
|
|
|
|
|
|
|
|
|
|
# ------------------------------------------------------------------------
|
|
|
|
|
|
|
|
fac_myDATA_EVENT = wx.NewEventType()
|
|
|
|
fac_EVT_DATA_EVENT = wx.PyEventBinder (fac_myDATA_EVENT, 0)
|
|
|
|
|
|
|
|
|
|
|
|
class fac_DataEvent(wx.PyEvent):
|
|
|
|
def __init__(self, data):
|
|
|
|
wx.PyEvent.__init__(self)
|
|
|
|
self.SetEventType (fac_myDATA_EVENT)
|
|
|
|
self.data = data
|
|
|
|
|
|
|
|
def Clone (self):
|
|
|
|
self.__class__ (self.GetId())
|
|
|
|
|
|
|
|
|
|
|
|
class fac_input_watcher (threading.Thread):
|
|
|
|
def __init__ (self, msgq, fac_size, event_receiver, **kwds):
|
|
|
|
threading.Thread.__init__ (self, **kwds)
|
|
|
|
self.setDaemon (1)
|
|
|
|
self.msgq = msgq
|
|
|
|
self.fac_size = fac_size
|
|
|
|
self.event_receiver = event_receiver
|
|
|
|
self.keep_running = True
|
|
|
|
self.start ()
|
|
|
|
|
|
|
|
def run (self):
|
|
|
|
while (self.keep_running):
|
|
|
|
msg = self.msgq.delete_head() # blocking read of message queue
|
|
|
|
itemsize = int(msg.arg1())
|
|
|
|
nitems = int(msg.arg2())
|
|
|
|
|
|
|
|
s = msg.to_string() # get the body of the msg as a string
|
|
|
|
|
|
|
|
# There may be more than one fac frame in the message.
|
|
|
|
# If so, we take only the last one
|
|
|
|
if nitems > 1:
|
|
|
|
start = itemsize * (nitems - 1)
|
|
|
|
s = s[start:start+itemsize]
|
|
|
|
|
|
|
|
complex_data = Numeric.fromstring (s, Numeric.Float32)
|
|
|
|
de = fac_DataEvent (complex_data)
|
|
|
|
wx.PostEvent (self.event_receiver, de)
|
|
|
|
del de
|
|
|
|
|
|
|
|
|
|
|
|
class fac_window (plot.PlotCanvas):
|
|
|
|
def __init__ (self, facsink, parent, id = -1,
|
|
|
|
pos = wx.DefaultPosition, size = wx.DefaultSize,
|
|
|
|
style = wx.DEFAULT_FRAME_STYLE, name = ""):
|
|
|
|
plot.PlotCanvas.__init__ (self, parent, id, pos, size, style, name)
|
|
|
|
|
|
|
|
self.y_range = None
|
|
|
|
self.facsink = facsink
|
|
|
|
self.peak_hold = False
|
|
|
|
self.peak_vals = None
|
|
|
|
|
|
|
|
self.SetEnableGrid (True)
|
|
|
|
# self.SetEnableZoom (True)
|
|
|
|
# self.SetBackgroundColour ('black')
|
|
|
|
|
|
|
|
self.build_popup_menu()
|
|
|
|
|
|
|
|
fac_EVT_DATA_EVENT (self, self.set_data)
|
|
|
|
wx.EVT_CLOSE (self, self.on_close_window)
|
|
|
|
self.Bind(wx.EVT_RIGHT_UP, self.on_right_click)
|
|
|
|
|
|
|
|
self.input_watcher = fac_input_watcher(facsink.msgq, facsink.fac_size, self)
|
|
|
|
|
|
|
|
|
|
|
|
def on_close_window (self, event):
|
|
|
|
print "fac_window:on_close_window"
|
|
|
|
self.keep_running = False
|
|
|
|
|
|
|
|
|
|
|
|
def set_data (self, evt):
|
|
|
|
dB = evt.data
|
|
|
|
L = len (dB)
|
|
|
|
|
|
|
|
if self.peak_hold:
|
|
|
|
if self.peak_vals is None:
|
|
|
|
self.peak_vals = dB
|
|
|
|
else:
|
|
|
|
self.peak_vals = Numeric.maximum(dB, self.peak_vals)
|
|
|
|
dB = self.peak_vals
|
|
|
|
|
|
|
|
x = max(abs(self.facsink.sample_rate), abs(self.facsink.baseband_freq))
|
|
|
|
sf = 1000.0
|
|
|
|
units = "ms"
|
|
|
|
|
|
|
|
x_vals = ((Numeric.arrayrange (L/2)
|
|
|
|
* ( (sf / self.facsink.sample_rate ) )) )
|
|
|
|
points = Numeric.zeros((len(x_vals), 2), Numeric.Float64)
|
|
|
|
points[:,0] = x_vals
|
|
|
|
points[:,1] = dB[0:L/2]
|
|
|
|
|
|
|
|
|
|
|
|
lines = plot.PolyLine (points, colour='DARKRED')
|
|
|
|
|
|
|
|
|
|
|
|
graphics = plot.PlotGraphics ([lines],
|
|
|
|
title=self.facsink.title,
|
|
|
|
xLabel = units, yLabel = "dB")
|
|
|
|
|
|
|
|
self.Draw (graphics, xAxis=None, yAxis=self.y_range)
|
|
|
|
self.update_y_range ()
|
|
|
|
|
|
|
|
def set_peak_hold(self, enable):
|
|
|
|
self.peak_hold = enable
|
|
|
|
self.peak_vals = None
|
|
|
|
|
|
|
|
def update_y_range (self):
|
|
|
|
ymax = self.facsink.ref_level
|
|
|
|
ymin = self.facsink.ref_level - self.facsink.y_per_div * self.facsink.y_divs
|
|
|
|
self.y_range = self._axisInterval ('min', ymin, ymax)
|
|
|
|
|
|
|
|
def on_average(self, evt):
|
|
|
|
# print "on_average"
|
|
|
|
self.facsink.set_average(evt.IsChecked())
|
|
|
|
|
|
|
|
def on_peak_hold(self, evt):
|
|
|
|
# print "on_peak_hold"
|
|
|
|
self.facsink.set_peak_hold(evt.IsChecked())
|
|
|
|
|
|
|
|
def on_incr_ref_level(self, evt):
|
|
|
|
# print "on_incr_ref_level"
|
|
|
|
self.facsink.set_ref_level(self.facsink.ref_level
|
|
|
|
+ self.facsink.y_per_div)
|
|
|
|
|
|
|
|
def on_decr_ref_level(self, evt):
|
|
|
|
# print "on_decr_ref_level"
|
|
|
|
self.facsink.set_ref_level(self.facsink.ref_level
|
|
|
|
- self.facsink.y_per_div)
|
|
|
|
|
|
|
|
def on_incr_y_per_div(self, evt):
|
|
|
|
# print "on_incr_y_per_div"
|
|
|
|
self.facsink.set_y_per_div(next_up(self.facsink.y_per_div, (1,2,5,10,20)))
|
|
|
|
|
|
|
|
def on_decr_y_per_div(self, evt):
|
|
|
|
# print "on_decr_y_per_div"
|
|
|
|
self.facsink.set_y_per_div(next_down(self.facsink.y_per_div, (1,2,5,10,20)))
|
|
|
|
|
|
|
|
def on_y_per_div(self, evt):
|
|
|
|
# print "on_y_per_div"
|
|
|
|
Id = evt.GetId()
|
|
|
|
if Id == self.id_y_per_div_1:
|
|
|
|
self.facsink.set_y_per_div(1)
|
|
|
|
elif Id == self.id_y_per_div_2:
|
|
|
|
self.facsink.set_y_per_div(2)
|
|
|
|
elif Id == self.id_y_per_div_5:
|
|
|
|
self.facsink.set_y_per_div(5)
|
|
|
|
elif Id == self.id_y_per_div_10:
|
|
|
|
self.facsink.set_y_per_div(10)
|
|
|
|
elif Id == self.id_y_per_div_20:
|
|
|
|
self.facsink.set_y_per_div(20)
|
|
|
|
|
|
|
|
|
|
|
|
def on_right_click(self, event):
|
|
|
|
menu = self.popup_menu
|
|
|
|
for id, pred in self.checkmarks.items():
|
|
|
|
item = menu.FindItemById(id)
|
|
|
|
item.Check(pred())
|
|
|
|
self.PopupMenu(menu, event.GetPosition())
|
|
|
|
|
|
|
|
|
|
|
|
def build_popup_menu(self):
|
|
|
|
self.id_incr_ref_level = wx.NewId()
|
|
|
|
self.id_decr_ref_level = wx.NewId()
|
|
|
|
self.id_incr_y_per_div = wx.NewId()
|
|
|
|
self.id_decr_y_per_div = wx.NewId()
|
|
|
|
self.id_y_per_div_1 = wx.NewId()
|
|
|
|
self.id_y_per_div_2 = wx.NewId()
|
|
|
|
self.id_y_per_div_5 = wx.NewId()
|
|
|
|
self.id_y_per_div_10 = wx.NewId()
|
|
|
|
self.id_y_per_div_20 = wx.NewId()
|
|
|
|
self.id_average = wx.NewId()
|
|
|
|
self.id_peak_hold = wx.NewId()
|
|
|
|
|
|
|
|
self.Bind(wx.EVT_MENU, self.on_average, id=self.id_average)
|
|
|
|
self.Bind(wx.EVT_MENU, self.on_peak_hold, id=self.id_peak_hold)
|
|
|
|
self.Bind(wx.EVT_MENU, self.on_incr_ref_level, id=self.id_incr_ref_level)
|
|
|
|
self.Bind(wx.EVT_MENU, self.on_decr_ref_level, id=self.id_decr_ref_level)
|
|
|
|
self.Bind(wx.EVT_MENU, self.on_incr_y_per_div, id=self.id_incr_y_per_div)
|
|
|
|
self.Bind(wx.EVT_MENU, self.on_decr_y_per_div, id=self.id_decr_y_per_div)
|
|
|
|
self.Bind(wx.EVT_MENU, self.on_y_per_div, id=self.id_y_per_div_1)
|
|
|
|
self.Bind(wx.EVT_MENU, self.on_y_per_div, id=self.id_y_per_div_2)
|
|
|
|
self.Bind(wx.EVT_MENU, self.on_y_per_div, id=self.id_y_per_div_5)
|
|
|
|
self.Bind(wx.EVT_MENU, self.on_y_per_div, id=self.id_y_per_div_10)
|
|
|
|
self.Bind(wx.EVT_MENU, self.on_y_per_div, id=self.id_y_per_div_20)
|
|
|
|
|
|
|
|
|
|
|
|
# make a menu
|
|
|
|
menu = wx.Menu()
|
|
|
|
self.popup_menu = menu
|
|
|
|
menu.AppendCheckItem(self.id_average, "Average")
|
|
|
|
menu.AppendCheckItem(self.id_peak_hold, "Peak Hold")
|
|
|
|
menu.Append(self.id_incr_ref_level, "Incr Ref Level")
|
|
|
|
menu.Append(self.id_decr_ref_level, "Decr Ref Level")
|
|
|
|
# menu.Append(self.id_incr_y_per_div, "Incr dB/div")
|
|
|
|
# menu.Append(self.id_decr_y_per_div, "Decr dB/div")
|
|
|
|
menu.AppendSeparator()
|
|
|
|
# we'd use RadioItems for these, but they're not supported on Mac
|
|
|
|
menu.AppendCheckItem(self.id_y_per_div_1, "1 dB/div")
|
|
|
|
menu.AppendCheckItem(self.id_y_per_div_2, "2 dB/div")
|
|
|
|
menu.AppendCheckItem(self.id_y_per_div_5, "5 dB/div")
|
|
|
|
menu.AppendCheckItem(self.id_y_per_div_10, "10 dB/div")
|
|
|
|
menu.AppendCheckItem(self.id_y_per_div_20, "20 dB/div")
|
|
|
|
|
|
|
|
self.checkmarks = {
|
|
|
|
self.id_average : lambda : self.facsink.average,
|
|
|
|
self.id_peak_hold : lambda : self.facsink.peak_hold,
|
|
|
|
self.id_y_per_div_1 : lambda : self.facsink.y_per_div == 1,
|
|
|
|
self.id_y_per_div_2 : lambda : self.facsink.y_per_div == 2,
|
|
|
|
self.id_y_per_div_5 : lambda : self.facsink.y_per_div == 5,
|
|
|
|
self.id_y_per_div_10 : lambda : self.facsink.y_per_div == 10,
|
|
|
|
self.id_y_per_div_20 : lambda : self.facsink.y_per_div == 20,
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
def next_up(v, seq):
|
|
|
|
"""
|
|
|
|
Return the first item in seq that is > v.
|
|
|
|
"""
|
|
|
|
for s in seq:
|
|
|
|
if s > v:
|
|
|
|
return s
|
|
|
|
return v
|
|
|
|
|
|
|
|
def next_down(v, seq):
|
|
|
|
"""
|
|
|
|
Return the last item in seq that is < v.
|
|
|
|
"""
|
|
|
|
rseq = list(seq[:])
|
|
|
|
rseq.reverse()
|
|
|
|
|
|
|
|
for s in rseq:
|
|
|
|
if s < v:
|
|
|
|
return s
|
|
|
|
return v
|
|
|
|
|
|
|
|
|
|
|
|
# ----------------------------------------------------------------
|
|
|
|
# Deprecated interfaces
|
|
|
|
# ----------------------------------------------------------------
|
|
|
|
|
|
|
|
# returns (block, win).
|
|
|
|
# block requires a single input stream of float
|
|
|
|
# win is a subclass of wxWindow
|
|
|
|
|
|
|
|
def make_fac_sink_f(fg, parent, title, fac_size, input_rate, ymin = 0, ymax=50):
|
|
|
|
|
|
|
|
block = fac_sink_f(fg, parent, title=title, fac_size=fac_size, sample_rate=input_rate,
|
|
|
|
y_per_div=(ymax - ymin)/8, ref_level=ymax)
|
|
|
|
return (block, block.win)
|
|
|
|
|
|
|
|
# returns (block, win).
|
|
|
|
# block requires a single input stream of gr_complex
|
|
|
|
# win is a subclass of wxWindow
|
|
|
|
|
|
|
|
def make_fac_sink_c(fg, parent, title, fac_size, input_rate, ymin=0, ymax=50):
|
|
|
|
block = fac_sink_c(fg, parent, title=title, fac_size=fac_size, sample_rate=input_rate,
|
|
|
|
y_per_div=(ymax - ymin)/8, ref_level=ymax)
|
|
|
|
return (block, block.win)
|
|
|
|
|
|
|
|
|
|
|
|
# ----------------------------------------------------------------
|
|
|
|
# Standalone test app - deleted
|
|
|
|
# ----------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
|
|
############################################################################
|
|
|
|
|
|
|
|
# Start the receiver
|
|
|
|
#
|
|
|
|
if '__main__' == __name__:
|
|
|
|
app = stdgui2.stdapp(p25_rx_block, "APCO P25 Receiver", 3)
|
|
|
|
app.MainLoop()
|