#!/usr/bin/env python # Copyright 2008-2011 Steve Glass # # Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Max H. Parke 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 math import numpy import time import re import json import traceback try: import Hamlib except: pass try: import Numeric except: pass from gnuradio import audio, eng_notation, gr, filter, blocks, fft, analog, digital from gnuradio.eng_option import eng_option from math import pi from optparse import OptionParser import op25 import op25_repeater import trunking import p25_demodulator import p25_decoder sys.path.append('tdma') import lfsr from gr_gnuplot import constellation_sink_c from gr_gnuplot import fft_sink_c from gr_gnuplot import symbol_sink_f from gr_gnuplot import eye_sink_f from gr_gnuplot import mixer_sink_c from gr_gnuplot import setup_correlation from gr_gnuplot import sync_plot from terminal import op25_terminal from sockaudio import audio_thread from sql_dbi import sql_dbi #speeds = [300, 600, 900, 1200, 1440, 1800, 1920, 2400, 2880, 3200, 3600, 3840, 4000, 4800, 6000, 6400, 7200, 8000, 9600, 14400, 19200] speeds = [4800, 6000] os.environ['IMBE'] = 'soft' WIRESHARK_PORT = 23456 _def_interval = 3.0 # sec _def_file_dir = '../www/images' _def_error_update_time = 3.0 # freq error tracking interval _def_lo_adj_limit = 2500 # Hz class udp_source_c(gr.hier_block2): def __init__(self, hostname, udp_port): gr.hier_block2.__init__(self, "udp_source_c", gr.io_signature(0, 0, 0), # Input signature gr.io_signature(1, 1, gr.sizeof_gr_complex)) # Output signature bufsize = 32000 self.src = blocks.udp_source(gr.sizeof_gr_complex, hostname, udp_port, payload_size = bufsize) self.sample_rate = 0 self.connect(self.src, self) def set_center_freq(self, f): sys.stderr.write('udp source: set_center_freq(%s) ignored\n' % f) def set_bandwidth(self, f): sys.stderr.write('udp source: set_bandwidth(%s) ignored\n' % f) def set_sample_rate(self, f): self.sample_rate = f return f def set_antenna(self, a): sys.stderr.write('udp source: set_antenna(%s) ignored\n' % a) # The P25 receiver # class p25_rx_block (gr.top_block): # Initialize the P25 receiver # def __init__(self, options): self.trunk_rx = None self.plot_sinks = [] gr.top_block.__init__(self) self.channel_rate = 0 self.baseband_input = False self.rtl_found = False self.channel_rate = options.sample_rate self.fft_sink = None self.last_error_update = 0 self.error_band = 0 self.tuning_error = 0 self.freq_correction = 0 self.last_set_freq = 0 self.last_set_freq_at = time.time() self.last_change_freq = 0 self.last_change_freq_at = time.time() self.last_freq_params = {'freq' : 0.0, 'tgid' : None, 'tag' : "", 'tdma' : None} self.next_status_png = time.time() self.last_process_update = 0 self.sql_db = sql_dbi() self.last_center_freq = 0 self.last_relative_freq = 0 self.src = None if (not options.input) and (not options.audio) and (not options.audio_if) and (not options.args.startswith('udp:')): # check if osmocom is accessible try: import osmosdr self.src = osmosdr.source(options.args) except Exception: print ("osmosdr source_c creation failure") ignore = True if any(x in options.args.lower() for x in ['rtl', 'airspy', 'hackrf', 'uhd']): #print ("'rtl' has been found in options.args (%s)" % (options.args)) self.rtl_found = True gain_names = self.src.get_gain_names() for name in gain_names: range1 = self.src.get_gain_range(name) print ("gain: name: %s range: start %d stop %d step %d" % (name, range1[0].start(), range1[0].stop(), range1[0].step())) if options.gains: for tup in options.gains.split(","): name, gain = tup.split(":") gain = int(gain) print ("setting gain %s to %d" % (name, gain)) self.src.set_gain(gain, name) rates = self.src.get_sample_rates() try: print ('supported sample rates %d-%d step %d' % (rates.start(), rates.stop(), rates.step())) except: pass # ignore if options.freq_corr: self.src.set_freq_corr(options.freq_corr) elif (not options.input) and (not options.audio) and (not options.audio_if) and options.args.startswith('udp:'): hostinfo = options.args.split(':') hostname = hostinfo[1] udp_port = int(hostinfo[2]) self.src = udp_source_c(hostname, udp_port) sys.stderr.write('started udp listener: %s %s\n' % (hostname, udp_port)) 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 = 24000 _default_speed = 4800 self.options = options # keep track of flow graph connections self.cnxns = [] self.datascope_raw_input = False self.data_scope_connected = False self.constellation_scope_connected = False for i in range(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) # wait for gdb if options.pause: print ('Ready for GDB to attach (pid = %d)' % (os.getpid(),)) raw_input("Press 'Enter' to continue...") self.input_q = gr.msg_queue(20) self.output_q = gr.msg_queue(10) # configure specified data source if options.input: self.open_file(options.input) elif options.audio_if: self.open_audio_c(self.channel_rate, options.gain, options.audio_input) elif options.audio: self.open_audio(self.channel_rate, options.gain, options.audio_input) elif options.ifile: self.open_ifile(self.channel_rate, options.gain, options.ifile, options.seek) elif (self.rtl_found or options.frequency): self.open_usrp() else: pass # attach terminal thread and make sure currently tuned frequency is displayed self.terminal = op25_terminal(self.input_q, self.output_q, self.options.terminal_type) if self.terminal is None: sys.stderr.write('warning: no terminal attached\n') # attach audio thread if self.options.udp_player: self.audio = audio_thread("127.0.0.1", self.options.wireshark_port, self.options.audio_output, False, self.options.audio_gain) else: self.audio = None # setup common flow graph elements # def __build_graph(self, source, capture_rate): global speeds global WIRESHARK_PORT sps = 5 # samples / symbol if_rate = sps * 4800 self.rx_q = gr.msg_queue(100) udp_port = 0 vocoder = self.options.vocoder wireshark = self.options.wireshark wireshark_host = self.options.wireshark_host if self.options.udp_player: vocoder = True wireshark = True wireshark_host = "127.0.0.1" if wireshark or (wireshark_host != "127.0.0.1"): udp_port = self.options.wireshark_port self.tdma_state = False self.xor_cache = {} if self.baseband_input: self.demod = p25_demodulator.p25_demod_fb(input_rate=capture_rate, excess_bw=self.options.excess_bw,if_rate=if_rate) else: # complex input # local osc self.lo_freq = self.options.offset if self.options.ifile or self.options.input: self.lo_freq += self.options.calibration self.demod = p25_demodulator.p25_demod_cb( input_rate = capture_rate, demod_type = self.options.demod_type, relative_freq = self.lo_freq, offset = self.options.offset, if_rate = if_rate, gain_mu = self.options.gain_mu, costas_alpha = self.options.costas_alpha, excess_bw = self.options.excess_bw, symbol_rate = self.symbol_rate, use_old_decim = True if self.options.audio_if else False) num_ambe = 0 if self.options.phase2_tdma: num_ambe = 1 self.decoder = p25_decoder.p25_decoder_sink_b(dest='audio', do_imbe=vocoder, num_ambe=num_ambe, wireshark_host=wireshark_host, udp_port=udp_port, do_msgq = True, msgq=self.rx_q, audio_output=self.options.audio_output, debug=self.options.verbosity) # connect it all up self.connect(source, self.demod, self.decoder) if self.baseband_input: sps = if_rate // 4800 plot_modes = [] if self.options.plot_mode is not None: plot_modes = self.options.plot_mode.split(',') for plot_mode in plot_modes: if plot_mode == 'constellation': assert self.options.demod_type == 'cqpsk' ## constellation requires cqpsk demod-type sink = constellation_sink_c() self.plot_sinks.append(sink) self.demod.connect_complex('diffdec', sink) elif plot_mode == 'symbol': sink = symbol_sink_f() self.plot_sinks.append(sink) self.demod.connect_float(sink) elif plot_mode == 'fft': sink = fft_sink_c() self.plot_sinks.append(sink) self.spectrum_decim = filter.rational_resampler_ccf(1, self.options.decim_amt) self.connect(self.spectrum_decim, sink) self.demod.connect_complex('src', self.spectrum_decim) elif plot_mode == 'mixer': sink = mixer_sink_c() self.plot_sinks.append(sink) self.demod.connect_complex('mixer', sink) elif plot_mode == 'datascope': assert self.options.demod_type == 'fsk4' ## datascope requires fsk4 demod-type sink = eye_sink_f(sps=sps) self.plot_sinks.append(sink) self.demod.connect_bb('symbol_filter', sink) elif plot_mode == 'correlation': assert self.options.demod_type == 'fsk4' ## correlation plot requires fsk4 demod type self.plot_sinks += setup_correlation(sps, "", self.demod.connect_bb) elif plot_mode == 'sync': assert self.options.demod_type == 'cqpsk' ## sync plot requires cqpsk demod-type sink = sync_plot(block = self.demod.clock) self.plot_sinks.append(sink) # no flowgraph connection in this plot else: raise ValueError('unsupported plot type: %s' % plot_mode) if self.is_http_term(): for sink in self.plot_sinks: sink.gnuplot.set_interval(_def_interval) sink.gnuplot.set_output_dir(_def_file_dir) if self.options.raw_symbols: self.sink_sf = blocks.file_sink(gr.sizeof_char, self.options.raw_symbols) self.connect(self.demod, self.sink_sf) logfile_workers = [] if self.options.phase2_tdma: num_ambe = 2 if self.options.logfile_workers: for i in range(self.options.logfile_workers): demod = p25_demodulator.p25_demod_cb(input_rate=capture_rate, demod_type=self.options.demod_type, offset=self.options.offset) decoder = p25_decoder.p25_decoder_sink_b(debug = self.options.verbosity, do_imbe = vocoder, num_ambe=num_ambe) logfile_workers.append({'demod': demod, 'decoder': decoder, 'active': False}) self.connect(source, demod, decoder) self.trunk_rx = trunking.rx_ctl(frequency_set = self.change_freq, debug = self.options.verbosity, conf_file = self.options.trunk_conf_file, logfile_workers=logfile_workers, send_event=self.send_event) self.du_watcher = du_queue_watcher(self.rx_q, self.preprocess_qmsg) def preprocess_qmsg(self, msg): self.trunk_rx.process_qmsg(msg) if self.options.freq_error_tracking: if self.last_error_update + _def_error_update_time > time.time() \ or self.last_change_freq_at + _def_error_update_time > time.time(): pass else: self.error_tracking() # 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 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 configure_tdma(self, params): if params['tdma'] is not None and not self.options.phase2_tdma: sys.stderr.write("***TDMA request for frequency %d failed- phase2_tdma option not enabled\n" % params['freq']) return set_tdma = False if params['tdma'] is not None: set_tdma = True self.decoder.set_slotid(params['tdma']) self.demod.clock.set_tdma(set_tdma) if set_tdma == self.tdma_state: return # already in desired state self.tdma_state = set_tdma if set_tdma: hash = '%x%x%x' % (params['nac'], params['sysid'], params['wacn']) if hash not in self.xor_cache: self.xor_cache[hash] = lfsr.p25p2_lfsr(params['nac'], params['sysid'], params['wacn']).xor_chars self.decoder.set_xormask(self.xor_cache[hash], hash) self.decoder.set_nac(params['nac']) rate = 6000 else: rate = 4800 sps = self.basic_rate / rate self.demod.set_symbol_rate(rate) # this and the foll. call should be merged? self.demod.clock.set_omega(float(sps)) def error_tracking(self): if self.last_error_update + _def_error_update_time > time.time() \ or self.last_change_freq_at + _def_error_update_time > time.time(): return self.last_error_update = time.time() band = self.demod.get_error_band() freq_error = self.demod.get_freq_error() if band: self.error_band += band self.freq_correction += freq_error * 0.15 self.freq_correction = int(self.freq_correction) if self.freq_correction > 600: self.freq_correction -= 1200 self.error_band += 1 elif self.freq_correction < -600: self.freq_correction += 1200 self.error_band -= 1 self.tuning_error = int(self.error_band * 1200 + self.freq_correction) e = 0 if self.last_change_freq > 0: e = (self.tuning_error*1e6) / float(self.last_change_freq) if self.options.verbosity >= 10: sys.stderr.write('frequency_tracking\t%d\t%d\t%d\t%d\t%f\n' % (freq_error, self.error_band, self.tuning_error, self.freq_correction, e)) self.adjust_lo_freq() # adjust lo based on updated tuning error d = {'time': time.time(), 'json_type': 'freq_error_tracking', 'name': 'rx.py', 'device': self.options.args, 'freq_error': freq_error, 'band': band, 'error_band': self.error_band, 'tuning_error': self.tuning_error, 'freq_correction': self.freq_correction} js = json.dumps(d) if self.options.verbosity > 0: sys.stderr.write('%f error tracking: %s\n' % (time.time(), js)) if self.input_q.full_p(): if self.options.verbosity > 0: sys.stderr.write('%f error_tracking: qfull\n' % (time.time())) return msg = gr.message().make_from_string(js, -4, 0, 0) self.input_q.insert_tail(msg) def adjust_lo_freq(self): # apply tuning error updates to adjust lo frequency center_freq = self.last_center_freq offset = self.options.offset if not center_freq: new_lo_freq = offset + self.tuning_error else: new_lo_freq = self.last_relative_freq + offset + self.tuning_error if abs(self.lo_freq - new_lo_freq) > _def_lo_adj_limit: sys.stderr.write('adjust_lo_freq: adjustment exceeds limit: previous %d requested %d\n' % (self.lo_freq, new_lo_freq)) return self.lo_freq = new_lo_freq self.demod.set_relative_frequency(self.lo_freq) def change_freq(self, params): if self.options.verbosity > 1: sys.stderr.write('%f change_freq: %s\n' % (time.time(), params)) self.last_freq_params = params freq = params['freq'] offset = self.options.offset center_freq = params['center_frequency'] self.last_center_freq = center_freq if self.options.freq_error_tracking: self.error_tracking() if self.last_change_freq != freq: self.last_change_freq = freq self.last_change_freq_at = time.time() self.configure_tdma(params) if self.options.hamlib_model: self.hamlib.set_freq(freq) return if not center_freq: self.lo_freq = offset + self.tuning_error self.demod.set_relative_frequency(self.lo_freq) self.set_freq(freq) return relative_freq = center_freq - freq self.last_relative_freq = relative_freq if abs(relative_freq + offset + self.tuning_error) > self.channel_rate / 2: self.lo_freq = offset + self.tuning_error # relative tune not possible self.demod.set_relative_frequency(self.lo_freq) # reset demod relative freq self.set_freq(freq) # direct tune instead return self.lo_freq = relative_freq + offset + self.tuning_error if self.demod.set_relative_frequency(self.lo_freq): # relative tune successful self.set_freq(center_freq) if self.fft_sink: self.fft_sink.set_relative_freq(self.lo_freq) return self.lo_freq = offset + self.tuning_error # relative tune unsuccessful self.demod.set_relative_frequency(self.lo_freq) # reset demod relative freq self.set_freq(freq + offset) # direct tune instead self.configure_tdma(params) self.freq_update() def freq_update(self): if self.input_q.full_p(): return params = self.last_freq_params params['json_type'] = 'change_freq' params['fine_tune'] = self.options.fine_tune params['current_time'] = time.time() js = json.dumps(params) msg = gr.message().make_from_string(js, -4, 0, 0) self.input_q.insert_tail(msg) 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 set_gain(self, gain): if self.rtl_found: self.src.set_gain(gain, 'LNA') if self.options.verbosity: print ('RTL Gain of %d set to: %.1f' % (gain, self.src.get_gain('LNA'))) else: if self.baseband_input: f = 1.0 else: f = 0.1 self.demod.set_baseband_gain(float(gain) * f) def set_audio_scaler(self, vol): #print ('audio scaler: %f' % ((1 / 32768.0) * (vol * 0.1))) if hasattr(self.decoder, 'set_scaler_k'): self.decoder.set_scaler_k((1 / 32768.0) * (vol * 0.1)) def set_rtl_ppm(self, ppm): self.src.set_freq_corr(ppm) def set_freq_tune(self, val): self.demod.set_relative_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. """ if not self.src: return False self.target_freq = target_freq tune_freq = target_freq + self.options.calibration + self.options.offset + self.options.fine_tune r = self.src.set_center_freq(tune_freq) if self.fft_sink: self.fft_sink.set_center_freq(target_freq) self.fft_sink.set_width(self.options.sample_rate) 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) if self.last_set_freq != tune_freq: self.last_set_freq = tune_freq self.last_set_freq_at = time.time() return True return False # 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) gain = blocks.multiply_const_cc(self.options.gain) throttle = blocks.throttle(gr.sizeof_gr_complex, capture_rate) self.__connect([[file, gain, 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 + self.options.fine_tune) if not r: sys.stderr.write("__set_rx_from_osmosdr(): failed to set frequency\n") # 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) # Write capture file properties # def __write_file_properties(self, filename): f = open(filename, "w") pickle.dump(self.info, f) f.close() 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)) throttle = blocks.throttle(gr.sizeof_gr_complex, speed) self.source = blocks.multiply_const_cc(gain) self.connect(ifile, throttle, self.source) self.__set_rx_from_audio(speed) 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.null_source = blocks.null_source (gr.sizeof_float) self.audio_cvt = blocks.float_to_complex() self.connect(self.audio_source, (self.audio_cvt, 0)) self.connect(self.null_source, (self.audio_cvt, 1)) self.source = blocks.multiply_const_cc(gain) self.connect(self.audio_cvt, self.source) self.__set_rx_from_audio(capture_rate) rc = self.demod.set_relative_frequency(self.options.calibration) sys.stderr.write('open_audio_c: set_relative_frequency %d: %s\n' % (self.options.calibration, rc)) 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.audio_source = audio.source(capture_rate, audio_input_filename) self.source = blocks.multiply_const_ff(gain) self.connect(self.audio_source, self.source) self.__set_rx_from_audio(capture_rate) # 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() if self.options.frequency: self.last_freq_params['freq'] = self.options.frequency self.set_freq(self.options.frequency) # except Exception, x: # wx.MessageBox("Cannot open USRP: " + x.message, "USRP Error", wx.CANCEL | wx.ICON_EXCLAMATION) def is_http_term(self): if self.options.terminal_type.startswith('http:'): return True elif self.options.terminal_type.startswith('zmq:'): return True else: return False def process_ajax(self): if not self.is_http_term(): return if self.input_q.full_p(): return filenames = [sink.gnuplot.filename for sink in self.plot_sinks if sink.gnuplot.filename] error = None if self.options.demod_type == 'cqpsk': error = self.demod.get_freq_error() d = {'json_type': 'rx_update', 'error': error, 'fine_tune': self.options.fine_tune, 'files': filenames, 'time': time.time()} msg = gr.message().make_from_string(json.dumps(d), -4, 0, 0) self.input_q.insert_tail(msg) def process_update(self): UPDATE_INTERVAL = 1.0 # sec. now = time.time() if self.options.freq_error_tracking: self.error_tracking() if self.input_q.full_p(): return if now < self.last_process_update + UPDATE_INTERVAL: return self.last_process_update = now self.freq_update() if self.trunk_rx is None: return ## possible race cond - just ignore if self.input_q.full_p(): return js = self.trunk_rx.to_json() msg = gr.message().make_from_string(js, -4, 0, 0) self.input_q.insert_tail(msg) self.process_ajax() def send_event(self, d): ## called from trunking module to send json msgs / updates to client if d is not None: self.sql_db.event(d) if d and not self.input_q.full_p(): msg = gr.message().make_from_string(json.dumps(d), -4, 0, 0) self.input_q.insert_tail(msg) self.process_update() def process_qmsg(self, msg): # return true = end top block RX_COMMANDS = 'skip lockout hold'.split() s = msg.to_string() t = msg.type() if t == -4: d = json.loads(s) s = d['command'] if type(s) is not str and isinstance(s, bytes): # should only get here if python3 s = s.decode() if s == 'quit': return True elif s == 'update': ## deprecated here: to be removed pass # self.process_update() elif s == 'set_freq': freq = msg.arg1() self.last_freq_params['freq'] = freq self.set_freq(freq) elif s == 'adj_tune': freq = msg.arg1() elif s == 'dump_tgids': self.trunk_rx.dump_tgids() elif s == 'reload_tags': nac = msg.arg1() self.trunk_rx.reload_tags(int(nac)) elif s == 'add_default_config': nac = msg.arg1() self.trunk_rx.add_default_config(int(nac)) elif s in RX_COMMANDS: self.rx_q.insert_tail(msg) elif s == 'settings-enable' and self.trunk_rx is not None: self.trunk_rx.enable_status(d['data']) return False ############################################################################ # 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() if not self.keep_running: break self.callback(msg) class rx_main(object): def __init__(self): self.keep_running = True self.cli_options() self.tb = p25_rx_block(self.options) self.q_watcher = du_queue_watcher(self.tb.output_q, self.process_qmsg) sys.stderr.write('python version detected: %s\n' % sys.version) def process_qmsg(self, msg): if self.tb.process_qmsg(msg): self.keep_running = False def run(self): try: self.tb.start() while self.keep_running: time.sleep(1) except: sys.stderr.write('main: exception occurred\n') sys.stderr.write('main: exception:\n%s\n' % traceback.format_exc()) if self.tb.terminal: self.tb.terminal.end_terminal() if self.tb.audio: self.tb.audio.stop() self.tb.stop() for sink in self.tb.plot_sinks: sink.kill() def cli_options(self): # 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", type="string", 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("-D", "--demod-type", type="choice", default="cqpsk", choices=('cqpsk', 'fsk4'), help="cqpsk | fsk4") parser.add_option("-P", "--plot-mode", type="string", default=None, help="one or more of constellation, fft, symbol, datascope (comma-separated)") 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("-l", "--terminal-type", type="string", default='curses', help="'curses' or udp port or 'http:host:port'") parser.add_option("-L", "--logfile-workers", type="int", default=None, help="number of demodulators to instantiate") 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=0, help="message debug level") parser.add_option("-V", "--vocoder", action="store_true", default=False, help="voice codec") parser.add_option("-n", "--nocrypt", action="store_true", default=False, help="silence encrypted traffic") 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("-u", "--wireshark-port", type="int", default=23456, help="Wireshark udp port") parser.add_option("-r", "--raw-symbols", type="string", default=None, help="dump decoded symbols to file") 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="default", help="audio output device name") parser.add_option("-x", "--audio-gain", type="eng_float", default="1.0", help="audio gain (default = 1.0)") parser.add_option("-X", "--freq-error-tracking", action="store_true", default=False, help="enable experimental frequency error tracking") parser.add_option("-U", "--udp-player", action="store_true", default=False, help="enable built-in udp audio player") parser.add_option("-q", "--freq-corr", type="eng_float", default=0.0, help="frequency correction") parser.add_option("-d", "--fine-tune", type="eng_float", default=0.0, help="fine tuning") parser.add_option("-2", "--phase2-tdma", action="store_true", default=False, help="enable phase2 tdma decode") parser.add_option("-Z", "--decim-amt", type="int", default=1, help="spectrum decimation") (options, args) = parser.parse_args() if len(args) != 0: parser.print_help() sys.exit(1) self.options = options # Start the receiver # if __name__ == "__main__": rx = rx_main() rx.run()