2011-01-18 07:49:48 +00:00
|
|
|
#!/usr/bin/env python
|
|
|
|
|
|
|
|
# -*- mode: Python -*-
|
|
|
|
|
|
|
|
# Copyright 2011 Steve Glass
|
|
|
|
#
|
|
|
|
# This file is part of OP25.
|
|
|
|
#
|
|
|
|
# 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 math
|
|
|
|
import os
|
|
|
|
import sys
|
|
|
|
import threading
|
|
|
|
|
2011-01-19 01:38:27 +00:00
|
|
|
from gnuradio import audio, fsk4, gr, gru, op25, usrp2
|
2011-01-18 07:49:48 +00:00
|
|
|
from gnuradio.eng_option import eng_option
|
|
|
|
from math import pi
|
|
|
|
|
|
|
|
# The P25 receiver
|
|
|
|
#
|
2011-01-19 01:38:27 +00:00
|
|
|
class usrp2_c4fm_rx (gr.top_block):
|
2011-01-18 07:49:48 +00:00
|
|
|
|
|
|
|
# Initialize the P25 receiver
|
|
|
|
#
|
2011-01-19 01:38:27 +00:00
|
|
|
def __init__(self, interface, address, center_freq, offset_freq, decim, squelch, gain):
|
2011-01-18 07:49:48 +00:00
|
|
|
|
|
|
|
gr.top_block.__init__(self)
|
|
|
|
|
2011-01-19 01:38:27 +00:00
|
|
|
# setup USRP2
|
|
|
|
u = usrp2.source_32fc(interface, address)
|
|
|
|
u.set_decim(decim)
|
2011-01-23 00:31:33 +00:00
|
|
|
capture_rate = u.adc_rate() / decim
|
2011-01-19 01:38:27 +00:00
|
|
|
u.set_center_freq(center_freq)
|
2011-01-18 07:49:48 +00:00
|
|
|
if gain is None:
|
2011-01-19 01:38:27 +00:00
|
|
|
g = u.gain_range()
|
2011-01-18 07:49:48 +00:00
|
|
|
gain = float(g[0] + g[1]) / 2
|
2011-01-19 01:38:27 +00:00
|
|
|
u.set_gain(gain)
|
|
|
|
|
|
|
|
# Setup receiver attributes
|
2011-01-18 07:49:48 +00:00
|
|
|
channel_rate = 125000
|
|
|
|
symbol_rate = 4800
|
2011-01-19 01:38:27 +00:00
|
|
|
|
2011-01-18 07:49:48 +00:00
|
|
|
# channel filter
|
|
|
|
self.channel_offset = offset_freq
|
|
|
|
channel_decim = capture_rate // channel_rate
|
|
|
|
channel_rate = capture_rate // channel_decim
|
|
|
|
trans_width = 12.5e3 / 2;
|
|
|
|
trans_centre = trans_width + (trans_width / 2)
|
|
|
|
coeffs = gr.firdes.low_pass(1.0, capture_rate, trans_centre, trans_width, gr.firdes.WIN_HANN)
|
|
|
|
self.channel_filter = gr.freq_xlating_fir_filter_ccf(channel_decim, coeffs, self.channel_offset, capture_rate)
|
|
|
|
self.connect(u, self.channel_filter)
|
|
|
|
|
|
|
|
# power squelch
|
|
|
|
power_squelch = gr.pwr_squelch_cc(squelch, 1e-3, 0, True)
|
|
|
|
self.connect(self.channel_filter, power_squelch)
|
|
|
|
|
|
|
|
# FM demodulator
|
|
|
|
self.symbol_deviation = 600.0
|
|
|
|
fm_demod_gain = channel_rate / (2.0 * pi * self.symbol_deviation)
|
|
|
|
fm_demod = gr.quadrature_demod_cf(fm_demod_gain)
|
|
|
|
self.connect(power_squelch, fm_demod)
|
|
|
|
|
|
|
|
# symbol filter
|
|
|
|
symbol_decim = 1
|
|
|
|
samples_per_symbol = channel_rate // symbol_rate
|
|
|
|
symbol_coeffs = (1.0/samples_per_symbol,) * samples_per_symbol
|
|
|
|
symbol_filter = gr.fir_filter_fff(symbol_decim, symbol_coeffs)
|
|
|
|
self.connect(fm_demod, symbol_filter)
|
|
|
|
|
|
|
|
# C4FM demodulator
|
|
|
|
autotuneq = gr.msg_queue(2)
|
|
|
|
self.demod_watcher = demod_watcher(autotuneq, self.adjust_channel_offset)
|
|
|
|
demod_fsk4 = fsk4.demod_ff(autotuneq, channel_rate, symbol_rate)
|
|
|
|
self.connect(symbol_filter, demod_fsk4)
|
|
|
|
|
2011-01-23 00:31:33 +00:00
|
|
|
# symbol slicer
|
|
|
|
levels = [ -2.0, 0.0, 2.0, 4.0 ]
|
|
|
|
slicer = op25.fsk4_slicer_fb(levels)
|
|
|
|
self.connect(demod_fsk4, slicer)
|
2011-01-18 07:49:48 +00:00
|
|
|
|
2011-01-23 00:31:33 +00:00
|
|
|
# frame decoder
|
|
|
|
decoder = op25.decoder_bf()
|
|
|
|
self.connect(slicer, decoder)
|
|
|
|
|
|
|
|
# try to connect audio output device
|
2011-01-18 07:49:48 +00:00
|
|
|
try:
|
|
|
|
audio_sink = audio.sink(8000, "plughw:0,0", True)
|
|
|
|
self.connect(decoder, audio_sink)
|
|
|
|
except Exception:
|
|
|
|
sink = gr.null_sink(gr.sizeof_float)
|
|
|
|
self.connect(decoder, null);
|
|
|
|
|
|
|
|
|
|
|
|
# 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)
|
|
|
|
|
|
|
|
|
|
|
|
# Demodulator 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)
|
|
|
|
|
|
|
|
|
|
|
|
# Run the receiver
|
|
|
|
#
|
|
|
|
if '__main__' == __name__:
|
|
|
|
|
|
|
|
from optparse import OptionParser
|
|
|
|
parser = OptionParser(option_class=eng_option)
|
2011-01-19 01:38:27 +00:00
|
|
|
parser.add_option("-i", "--interface", type="string", default="eth0",
|
|
|
|
help="select Ethernet interface, default is eth0")
|
|
|
|
parser.add_option("-a", "--address", type="string", default="",
|
|
|
|
help="select USRP by MAC address, default is auto-select")
|
|
|
|
parser.add_option("-f", "--freq", type="eng_float", default=434.075e6,
|
|
|
|
help="rx frequency [=%default]", metavar="Hz")
|
|
|
|
parser.add_option("-c", "--calibration", type="eng_float", default=0.0,
|
|
|
|
help="frequency offset [=%default]", metavar="Hz")
|
|
|
|
parser.add_option("-d", "--decim", type="int", default=400,
|
|
|
|
help="decimation factor [=%default]")
|
|
|
|
parser.add_option("-s", "--squelch", type="eng_float", default=15.0,
|
|
|
|
help="squelch threshold [=%default]", metavar="dB")
|
|
|
|
parser.add_option("-g", "--gain", type="eng_float", default=None, help="gain", metavar="dB")
|
2011-01-18 07:49:48 +00:00
|
|
|
(options, args) = parser.parse_args()
|
|
|
|
if len(args) != 0:
|
|
|
|
parser.print_help()
|
|
|
|
sys.exit(1)
|
|
|
|
self.options = options
|
|
|
|
|
|
|
|
try:
|
2011-01-19 01:38:27 +00:00
|
|
|
rx = usrp2_c4fm_rx(options.interface, options.address, options.freq, options.calibration, options.decim, options.squelch, options.gain)
|
2011-01-18 07:49:48 +00:00
|
|
|
rx.run()
|
|
|
|
except KeyboardInterrupt:
|
|
|
|
pass
|