diff --git a/op25/gr-op25_repeater/apps/scope.py b/op25/gr-op25_repeater/apps/scope.py index 1c10c0f..802bb6a 100755 --- a/op25/gr-op25_repeater/apps/scope.py +++ b/op25/gr-op25_repeater/apps/scope.py @@ -58,6 +58,9 @@ import gnuradio.wxgui.plot as plot import trunking +import p25_demodulator +import p25_decoder + sys.path.append('tdma') import lfsr @@ -107,6 +110,7 @@ class p25_rx_block (stdgui2.std_top_block): 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", "--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") @@ -258,8 +262,6 @@ class p25_rx_block (stdgui2.std_top_block): 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 @@ -368,7 +370,7 @@ class p25_rx_block (stdgui2.std_top_block): alpha = self.options.costas_alpha beta = 0.125 * alpha * alpha - fmax = 1200 # Hz + fmax = 2400 # Hz fmax = 2*pi * fmax / self.basic_rate self.clock = op25_repeater.gardner_costas_cc(omega, gain_mu, gain_omega, alpha, beta, fmax, -fmax) @@ -391,6 +393,18 @@ class p25_rx_block (stdgui2.std_top_block): self.set_connection(fft=1) self.connect_demods() + logfile_workers = [] + if self.options.logfile_workers: + for i in xrange(self.options.logfile_workers): + demod = p25_demodulator.p25_demod_cb(input_rate=capture_rate, demod_type='cqpsk', offset=self.options.offset) + decoder = p25_decoder.p25_decoder_c(debug = self.options.verbosity, do_imbe = self.options.vocoder, do_ambe=self.options.phase2_tdma) + 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) + + self.du_watcher = du_queue_watcher(self.rx_q, self.trunk_rx.process_qmsg) + # Connect up the flow graph # def __connect(self, cnxns): @@ -432,6 +446,10 @@ class p25_rx_block (stdgui2.std_top_block): nac = params['nac'] system = params['system'] self.myform['system'].set_value('%X:%s' % (nac, system)) + if 'tdma' in params and params['tdma'] is not None: + self.myform['tdma'].set_value('TDMA Slot %d' % (params['tdma'])) + else: + self.myform['tdma'].set_value('') def set_speed(self, new_speed): # assumes that lock is held, or that we are in init @@ -661,7 +679,14 @@ class p25_rx_block (stdgui2.std_top_block): parent=self.panel, sizer=vbox_form, label="Talkgroup", weight=0) myform['talkgroup'].set_value("........................................") - if not self.baseband_input: + if self.baseband_input: + min_gain = 0 + max_gain = 200 + initial_val = 50 + else: + min_gain = 0 + max_gain = 25 + initial_val = 10 if self.options.trunk_conf_file: myform['freq'] = form.static_text_field( parent=self.panel, sizer=vbox_form, label="Frequency", weight=0) @@ -671,14 +696,9 @@ class p25_rx_block (stdgui2.std_top_block): 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 + myform['tdma'] = form.static_text_field( + parent=self.panel, sizer=vbox_form, label=None, weight=0) + myform['tdma'].set_value("") hbox.Add(vbox_form, 0, 0) @@ -749,7 +769,7 @@ class p25_rx_block (stdgui2.std_top_block): self.hamlib.set_freq(freq) elif params['center_frequency']: relative_freq = center_freq - freq - if relative_freq + self.options.offset > self.channel_rate / 2: + if abs(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 @@ -1126,10 +1146,10 @@ class p25_rx_block (stdgui2.std_top_block): "source-dev": "AUDIO", "source-decim": 1 } self.audio_source = audio.source(capture_rate, audio_input_filename) - self.audio_cvt = gr.float_to_complex() + self.audio_cvt = blocks.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.source = blocks.multiply_const_cc(gain) self.connect(self.audio_cvt, self.source) self.__set_rx_from_audio(capture_rate) self._set_titlebar("Capturing") diff --git a/op25/gr-op25_repeater/apps/trunking.py b/op25/gr-op25_repeater/apps/trunking.py index 7fa9ddf..99e035c 100644 --- a/op25/gr-op25_repeater/apps/trunking.py +++ b/op25/gr-op25_repeater/apps/trunking.py @@ -140,6 +140,19 @@ class trunked_system (object): self.voice_frequencies[frequency]['time'] = time.time() self.voice_frequencies[frequency]['slot'] = tdma_slot + def get_updated_talkgroup_frequencies(self, start_time): + updated_talkgroup_frequencies = [] + for frequency in self.voice_frequencies: + if self.voice_frequencies[frequency]['time'] < start_time: + continue + active_tgid = self.voice_frequencies[frequency]['tgid'] + if active_tgid in self.blacklist: + continue + if self.whitelist and active_tgid not in self.whitelist: + continue + updated_talkgroup_frequencies.append(frequency) + return updated_talkgroup_frequencies + def find_voice_frequency(self, start_time, tgid=None): for frequency in self.voice_frequencies: if self.voice_frequencies[frequency]['time'] < start_time: @@ -376,7 +389,7 @@ class trunked_system (object): class rx_ctl (object): - def __init__(self, debug=0, frequency_set=None, conf_file=None): + def __init__(self, debug=0, frequency_set=None, conf_file=None, logfile_workers=None): class _states(object): ACQ = 0 CC = 1 @@ -398,6 +411,8 @@ class rx_ctl (object): self.configs = {} self.last_tdma_vf = 0 self.P2_GRACE_TIME = 1.0 # TODO: make more configurable + self.logfile_workers = logfile_workers + self.active_talkgroups = {} if conf_file: if conf_file.endswith('.tsv'): @@ -408,6 +423,19 @@ class rx_ctl (object): self.current_nac = self.nacs[0] self.current_state = self.states.CC + tsys = self.trunked_systems[self.current_nac] + self.set_frequency({ + 'freq': tsys.trunk_cc, + 'tgid': None, + 'offset': tsys.offset, + 'tag': "", + 'nac': self.current_nac, + 'system': tsys.sysname, + 'center_frequency': tsys.center_frequency, + 'tdma': None, + 'wacn': None, + 'sysid': None}) + def set_frequency(self, params): frequency = params['freq'] if frequency and self.frequency_set: @@ -558,11 +586,68 @@ class rx_ctl (object): if nac != self.current_nac: return + if self.logfile_workers: + self.update_logging_state(curr_time) + return + if updated: self.update_state('update', curr_time) else: self.update_state('duid%d' % type, curr_time) + def find_available_worker(self): + for worker in self.logfile_workers: + if not worker['active']: + worker['active'] = True + return worker + return None + + def update_logging_state(self, curr_time): + tsys = self.trunked_systems[self.current_nac] + freqs = tsys.get_updated_talkgroup_frequencies(curr_time) + for frequency in freqs: + tgid = tsys.voice_frequencies[frequency]['tgid'] + relative_freq = tsys.center_frequency - frequency + + if tgid in self.active_talkgroups: + self.active_talkgroups[tgid]['updated'] = curr_time + self.active_talkgroups[tgid]['worker']['demod'].set_relative_frequency(relative_freq) + continue + + worker = self.find_available_worker() + if worker is None: + print '*** error, no free demodulators, freq %d tgid %d' % (frequency, tgid) + continue + + self.active_talkgroups[tgid] = {} + self.active_talkgroups[tgid]['updated'] = curr_time + self.active_talkgroups[tgid]['worker'] = worker + demod = worker['demod'] + decoder = worker['decoder'] + demod.set_relative_frequency(relative_freq) + symbol_rate = 4800 + + if tsys.voice_frequencies[frequency]['slot'] is not None: + symbol_rate = 6000 + demod.set_tdma_slotid(tsys.voice_frequencies[frequency]['slot']) + demod.set_tdma_parameters(self.current_nac, tsys.ns_syid, tsys.ns_wacn) + + demod.set_omega(symbol_rate) + + filename = 'tgid-%d-%f.wav' % (tgid, curr_time) + decoder.set_output(tsys.voice_frequencies[frequency]['slot'], filename) + + # look for inactive talkgroups + timeout_groups = [] + for tgid in self.active_talkgroups: + if self.active_talkgroups[tgid]['updated'] + self.TGID_HOLD_TIME < curr_time: + timeout_groups.append(tgid) + for tgid in timeout_groups: + print '%f release %d' % (time.time(), tgid) + self.active_talkgroups[tgid]['worker']['decoder'].close_file() + self.active_talkgroups[tgid]['worker']['active'] = False + self.active_talkgroups.pop(tgid) + def update_state(self, command, curr_time): if not self.configs: return # run in "manual mode" if no conf