diff options
author | Max <ikj1234i@yahoo.com> | 2014-12-19 19:16:15 -0500 |
---|---|---|
committer | Max <ikj1234i@yahoo.com> | 2014-12-19 19:16:15 -0500 |
commit | e319bdcd9f5848ad870a52ddd69cead8ec0af126 (patch) | |
tree | 72795153129f1589e91f00e877d6e1da774b2b07 | |
parent | 2b3eda4b05f3aa0dd3f9eb662ced24b059cdeb94 (diff) |
p2tdma tg logging, fdma trunk cc hunting, blacklist/whitelist files, bug fixes
-rw-r--r-- | op25/gr-op25_repeater/apps/p25_decoder.py | 68 | ||||
-rw-r--r-- | op25/gr-op25_repeater/apps/p25_demodulator.py | 1 | ||||
-rwxr-xr-x | op25/gr-op25_repeater/apps/scope.py | 95 | ||||
-rw-r--r-- | op25/gr-op25_repeater/apps/tdma/lfsr.py | 3 | ||||
-rw-r--r-- | op25/gr-op25_repeater/apps/trunking.py | 232 |
5 files changed, 268 insertions, 131 deletions
diff --git a/op25/gr-op25_repeater/apps/p25_decoder.py b/op25/gr-op25_repeater/apps/p25_decoder.py index 19cbd75..ecde7f1 100644 --- a/op25/gr-op25_repeater/apps/p25_decoder.py +++ b/op25/gr-op25_repeater/apps/p25_decoder.py @@ -35,24 +35,25 @@ import op25_repeater # default values (used in __init__ and add_options) _def_debug = 0 -_def_do_ambe = False +_def_num_ambe = False _def_do_imbe = True _def_wireshark_host = '' _def_udp_port = 0 _def_dest = 'wav' _def_audio_rate = 8000 _def_audio_output = 'plughw:0,0' +_def_max_tdma_timeslots = 2 # ///////////////////////////////////////////////////////////////////////////// # decoder # ///////////////////////////////////////////////////////////////////////////// -class p25_decoder_c(gr.hier_block2): +class p25_decoder_sink_b(gr.hier_block2): def __init__(self, dest = _def_dest, do_imbe = _def_do_imbe, - do_ambe = _def_do_ambe, + num_ambe = _def_num_ambe, wireshark_host = _def_wireshark_host, udp_port = _def_udp_port, do_msgq = False, @@ -70,6 +71,9 @@ class p25_decoder_c(gr.hier_block2): gr.io_signature(1, 1, gr.sizeof_char), # Input signature gr.io_signature(0, 0, 0)) # Output signature + assert 0 <= num_ambe <= _def_max_tdma_timeslots + assert not (num_ambe > 1 and dest != 'wav') + self.debug = debug self.dest = dest do_output = 1 @@ -77,30 +81,52 @@ class p25_decoder_c(gr.hier_block2): if msgq is None: msgq = gr.msg_queue(1) - - self.p25decoder = op25_repeater.p25_frame_assembler(wireshark_host, udp_port, debug, do_imbe, do_output, do_msgq, msgq, do_audio_output, do_ambe) - if dest == 'wav': - filename = 'default-%f.wav' % (time.time()) - n_channels = 1 - sample_rate = 8000 - bits_per_sample = 16 - self.audio_sink = blocks.wavfile_sink(filename, n_channels, sample_rate, bits_per_sample) - elif dest == 'audio': - self.audio_sink = audio.sink(_def_audio_rate, audio_output, True) + self.p25_decoders = [] + self.audio_s2f = [] + self.scaler = [] + self.audio_sink = [] + self.xorhash = [] + num_decoders = 1 + if num_ambe > 1: + num_decoders += num_ambe - 1 + for slot in xrange(num_decoders): + self.p25_decoders.append(op25_repeater.p25_frame_assembler(wireshark_host, udp_port, debug, do_imbe, do_output, do_msgq, msgq, do_audio_output, True)) + self.p25_decoders[slot].set_slotid(slot) + + self.audio_s2f.append(blocks.short_to_float()) # another ridiculous conversion + self.scaler.append(blocks.multiply_const_ff(1 / 32768.0)) + self.xorhash.append('') - self.audio_s2f = blocks.short_to_float() # another ridiculous conversion - self.scaler = blocks.multiply_const_ff(1 / 32768.0) + if dest == 'wav': + filename = 'default-%f-%d.wav' % (time.time(), slot) + n_channels = 1 + sample_rate = 8000 + bits_per_sample = 16 + self.audio_sink.append(blocks.wavfile_sink(filename, n_channels, sample_rate, bits_per_sample)) + elif dest == 'audio': + self.audio_sink.append(audio.sink(_def_audio_rate, audio_output, True)) - self.connect(self, self.p25decoder, self.audio_s2f, self.scaler, self.audio_sink) + self.connect(self, self.p25_decoders[slot], self.audio_s2f[slot], self.scaler[slot], self.audio_sink[slot]) - def close_file(self): + def close_file(self, index=0): if self.dest != 'wav': return - self.audio_sink.close() + self.audio_sink[index].close() + + def set_slotid(self, slot, index=0): + self.p25_decoders[index].set_slotid(slot) - def set_output(self, slot, filename): + def set_output(self, filename, index=0): if self.dest != 'wav': return - self.audio_sink.open(filename) - print 'set_output', slot, filename + self.audio_sink[index].open(filename) + + def set_xormask(self, xormask, xorhash, index=0): + if self.xorhash[index] == xorhash: + return + self.xorhash[index] = xorhash + self.p25_decoders[index].set_xormask(xormask) + + def set_scaler_k(self, k, index=0): + self.scaler[index].set_k(k) diff --git a/op25/gr-op25_repeater/apps/p25_demodulator.py b/op25/gr-op25_repeater/apps/p25_demodulator.py index a5b5faa..4936348 100644 --- a/op25/gr-op25_repeater/apps/p25_demodulator.py +++ b/op25/gr-op25_repeater/apps/p25_demodulator.py @@ -112,6 +112,7 @@ class p25_demod_fb(p25_demod_base): p25_demod_base.__init__(self, if_rate=input_rate, symbol_rate=symbol_rate) + self.input_rate = input_rate self.float_sink = None self.connect(self, self.baseband_amp, self.symbol_filter, self.fsk4_demod, self.slicer, self) diff --git a/op25/gr-op25_repeater/apps/scope.py b/op25/gr-op25_repeater/apps/scope.py index f9f2156..4a56425 100755 --- a/op25/gr-op25_repeater/apps/scope.py +++ b/op25/gr-op25_repeater/apps/scope.py @@ -137,40 +137,41 @@ class p25_rx_block (stdgui2.std_top_block): self.channel_rate = 0 self.baseband_input = False self.rtl_found = False + self.channel_rate = options.sample_rate - # 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 + self.src = None + if not options.input: + # 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 "rtl" in options.args.lower(): - #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: - 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() - try: - print 'supported sample rates %d-%d step %d' % (rates.start(), rates.stop(), rates.step()) - except: - pass # ignore + if "rtl" in options.args.lower(): + #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: + 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() + 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) + if options.freq_corr: + self.src.set_freq_corr(options.freq_corr) if options.audio: self.channel_rate = 48000 @@ -248,9 +249,6 @@ class p25_rx_block (stdgui2.std_top_block): 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) - self.connect(self.slicer, self.sink_sf) self.tdma_state = False self.xor_cache = {} @@ -288,18 +286,28 @@ class p25_rx_block (stdgui2.std_top_block): if self.options.wireshark: udp_port = WIRESHARK_PORT - self.decoder = p25_decoder.p25_decoder_c(dest='audio', do_imbe=True, do_ambe=self.options.phase2_tdma, wireshark_host=self.options.wireshark_host, udp_port=udp_port, do_msgq = True, msgq=self.rx_q, audio_output=self.options.audio_output, debug=self.options.verbosity) + num_ambe = 0 + if self.options.phase2_tdma: + num_ambe = 1 + + self.decoder = p25_decoder.p25_decoder_sink_b(dest='audio', do_imbe=True, num_ambe=num_ambe, wireshark_host=self.options.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.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 xrange(self.options.logfile_workers): demod = p25_demodulator.p25_demod_cb(input_rate=capture_rate, demod_type='cqpsk', ### FIXME 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) + decoder = p25_decoder.p25_decoder_sink_b(debug = self.options.verbosity, do_imbe = self.options.vocoder, num_ambe=num_ambe) logfile_workers.append({'demod': demod, 'decoder': decoder, 'active': False}) self.connect(source, demod, decoder) @@ -667,18 +675,15 @@ class p25_rx_block (stdgui2.std_top_block): return # already in desired state self.tdma_state = set_tdma if set_tdma: - self.p25decoder.set_slotid(params['tdma']) + self.decoder.set_slotid(params['tdma']) hash = '%x%x%x' % (params['nac'], params['sysid'], params['wacn']) if hash not in self.xor_cache: - sreg = lfsr.p25p2_lfsr(params['nac'], params['sysid'], params['wacn']) - self.xor_cache[hash] = '' - for c in sreg.xorsyms: - self.xor_cache[hash] += chr(c) - self.p25decoder.set_xormask(self.xor_cache[hash]) + self.xor_cache[hash] = lfsr.p25p2_lfsr(params['nac'], params['sysid'], params['wacn']).xor_chars + self.decoder.set_xormask(self.xor_cache[hash], hash) sps = self.basic_rate / 6000 else: sps = self.basic_rate / 4800 - self.clock.set_omega(float(sps)) + self.demod.clock.set_omega(float(sps)) def change_freq(self, params): freq = params['freq'] @@ -753,7 +758,7 @@ class p25_rx_block (stdgui2.std_top_block): def set_audio_scaler(self, vol): #print 'audio scaler: %f' % ((1 / 32768.0) * (vol * 0.1)) - self.decoder.scaler.set_k((1 / 32768.0) * (vol * 0.1)) + self.decoder.set_scaler_k((1 / 32768.0) * (vol * 0.1)) def set_rtl_ppm(self, ppm): self.src.set_freq_corr(ppm) @@ -774,6 +779,8 @@ class p25_rx_block (stdgui2.std_top_block): the result of that operation and our target_frequency to determine the value for the digital down converter. """ + if not self.src: + return False tune_freq = target_freq + self.options.calibration + self.options.offset r = self.src.set_center_freq(tune_freq) diff --git a/op25/gr-op25_repeater/apps/tdma/lfsr.py b/op25/gr-op25_repeater/apps/tdma/lfsr.py index 18557b5..6a7d8ac 100644 --- a/op25/gr-op25_repeater/apps/tdma/lfsr.py +++ b/op25/gr-op25_repeater/apps/tdma/lfsr.py @@ -28,7 +28,8 @@ class p25p2_lfsr(object): self.xorsyms = [0] * (len(xorbits)/2) for i in xrange(len(self.xorsyms)): self.xorsyms[i] = (xorbits[i*2] << 1) + xorbits[i*2+1] - + self.xor_chars = ''.join([chr(c) for c in self.xorsyms]) + def asm_reg(self,s1,s2,s3,s4,s5,s6): s1 = s1 & 0xfL s2 = s2 & 0x1fL diff --git a/op25/gr-op25_repeater/apps/trunking.py b/op25/gr-op25_repeater/apps/trunking.py index 99e035c..0274a94 100644 --- a/op25/gr-op25_repeater/apps/trunking.py +++ b/op25/gr-op25_repeater/apps/trunking.py @@ -21,7 +21,10 @@ # FIXME: hideously mixes indentation, some is tabs and some is spaces # +import sys import time +sys.path.append('tdma') +import lfsr def crc16(dat,len): # slow version poly = (1<<12) + (1<<5) + (1<<0) @@ -66,8 +69,16 @@ class trunked_system (object): self.tgid_map = {} self.offset = 0 self.sysname = 0 + self.trunk_cc = 0 + self.cc_list = [] + self.cc_list_index = 0 + self.CC_HUNT_TIME = 5.0 self.center_frequency = 0 + self.last_tsbk = 0 + self.cc_timeouts = 0 + + self.talkgroups = {} if config: self.blacklist = config['blacklist'] self.whitelist = config['whitelist'] @@ -75,7 +86,9 @@ class trunked_system (object): self.offset = config['offset'] self.sysname = config['sysname'] self.trunk_cc = config['cclist'][0] # TODO: scan thru list + self.cc_list = config['cclist'] self.center_frequency = config['center_frequency'] + self.modulation = config['modulation'] def to_string(self): s = [] @@ -86,7 +99,8 @@ class trunked_system (object): s.append('') t = time.time() for f in self.voice_frequencies: - s.append('voice frequency %f tgid %d %4.1fs ago count %d' % (f / 1000000.0, self.voice_frequencies[f]['tgid'], t - self.voice_frequencies[f]['time'], self.voice_frequencies[f]['counter'])) + tgs = '%s %s' % (self.voice_frequencies[f]['tgid'][0], self.voice_frequencies[f]['tgid'][1]) + s.append('voice frequency %f tgid(s) %s %4.1fs ago count %d' % (f / 1000000.0, tgs, t - self.voice_frequencies[f]['time'], self.voice_frequencies[f]['counter'])) s.append('') for table in self.freq_table: a = self.freq_table[table]['frequency'] / 1000000.0 @@ -130,42 +144,47 @@ class trunked_system (object): return "Talkgroup ID %d [0x%x]" % (tgid, tgid) return self.tgid_map[tgid] + def update_talkgroup(self, frequency, tgid, tdma_slot): + if tgid not in self.talkgroups: + self.talkgroups[tgid] = {'counter':0} + self.talkgroups[tgid]['time'] = time.time() + self.talkgroups[tgid]['frequency'] = frequency + self.talkgroups[tgid]['tdma_slot'] = tdma_slot + def update_voice_frequency(self, frequency, tgid=None, tdma_slot=None): - if frequency is None: + if not frequency: # e.g., channel identifier not yet known return + self.update_talkgroup(frequency, tgid, tdma_slot) if frequency not in self.voice_frequencies: self.voice_frequencies[frequency] = {'counter':0} - self.voice_frequencies[frequency]['tgid'] = tgid + if tdma_slot is None: + tdma_slot = 0 + if 'tgid' not in self.voice_frequencies[frequency]: + self.voice_frequencies[frequency]['tgid'] = [None, None] + self.voice_frequencies[frequency]['tgid'][tdma_slot] = tgid self.voice_frequencies[frequency]['counter'] += 1 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: + def get_updated_talkgroups(self, start_time): + return [tgid for tgid in self.talkgroups if ( + self.talkgroups[tgid]['time'] >= start_time and + tgid not in self.blacklist and + not (self.whitelist and tgid not in self.whitelist))] + + def find_talkgroup(self, start_time, tgid=None): + if tgid is not None and tgid in self.talkgroups and self.talkgroups[tgid]['time'] >= start_time: + return self.talkgroups[tgid]['frequency'], tgid, self.talkgroups[tgid]['tdma_slot'] + for active_tgid in self.talkgroups: + if self.talkgroups[active_tgid]['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 - if self.voice_frequencies[frequency]['slot'] is not None and (self.ns_syid < 0 or self.ns_wacn < 0): + if self.talkgroups[active_tgid]['tdma_slot'] is not None and (self.ns_syid < 0 or self.ns_wacn < 0): continue - if tgid is None or tgid == active_tgid: - return frequency, active_tgid, self.voice_frequencies[frequency]['slot'] + if tgid is None: + return self.talkgroups[active_tgid]['frequency'], active_tgid, self.talkgroups[active_tgid]['tdma_slot'] return None, None, None def add_blacklist(self, tgid): @@ -174,6 +193,8 @@ class trunked_system (object): self.blacklist[tgid] = 1 def decode_mbt_data(self, opcode, header, mbt_data): + self.cc_timeouts = 0 + self.last_tsbk = time.time() if self.debug > 10: print "decode_mbt_data: %x %x" %(opcode, mbt_data) if opcode == 0x0: # grp voice channel grant @@ -227,6 +248,7 @@ class trunked_system (object): # print "mbt other %x" % opcode def decode_tsbk(self, tsbk): + self.cc_timeouts = 0 self.stats['tsbks'] += 1 updated = 0 # if crc16(tsbk, 12) != 0: @@ -387,6 +409,20 @@ class trunked_system (object): # print "tsbk other %x" % opcode return updated + def hunt_cc(self, curr_time): + if self.cc_timeouts < 6: + return + self.cc_timeouts = 0 + self.cc_list_index += 1 + if self.cc_list_index >= len(self.cc_list): + self.cc_list_index = 0 + self.trunk_cc = self.cc_list[self.cc_list_index] + print '%f set trunk_cc to %s' % (curr_time, self.trunk_cc) + +def get_int_dict(s): + if s[0].isdigit(): + return dict.fromkeys([int(d) for d in s.split(',')]) + return dict.fromkeys([int(d) for d in open(s).readlines()]) class rx_ctl (object): def __init__(self, debug=0, frequency_set=None, conf_file=None, logfile_workers=None): @@ -413,6 +449,11 @@ class rx_ctl (object): self.P2_GRACE_TIME = 1.0 # TODO: make more configurable self.logfile_workers = logfile_workers self.active_talkgroups = {} + self.working_frequencies = {} + self.xor_cache = {} + self.last_garbage_collect = 0 + if self.logfile_workers: + self.input_rate = self.logfile_workers[0]['demod'].input_rate if conf_file: if conf_file.endswith('.tsv'): @@ -424,6 +465,11 @@ class rx_ctl (object): self.current_state = self.states.CC tsys = self.trunked_systems[self.current_nac] + + if self.logfile_workers and tsys.modulation == 'c4fm': + for worker in self.logfile_workers: + worker['demod'].connect_chain('fsk4') + self.set_frequency({ 'freq': tsys.trunk_cc, 'tgid': None, @@ -502,10 +548,9 @@ class rx_ctl (object): self.configs[nac]['modulation'] = configs[nac]['modulation'] else: self.configs[nac]['modulation'] = 'cqpsk' - if 'whitelist' in configs[nac]: - self.configs[nac]['whitelist'] = dict.fromkeys([int(d) for d in configs[nac]['whitelist'].split(',')]) - if 'blacklist' in configs[nac]: - self.configs[nac]['blacklist'] = dict.fromkeys([int(d) for d in configs[nac]['blacklist'].split(',')]) + for k in ['whitelist', 'blacklist']: + if k in configs[nac]: + self.configs[nac][k] = get_int_dict(configs[nac][k]) if 'tgid_tags_file' in configs[nac]: import csv with open(configs[nac]['tgid_tags_file'], 'rb') as csvfile: @@ -545,6 +590,8 @@ class rx_ctl (object): elif type == -1: # timeout print "process_data_unit timeout" self.update_state('timeout', curr_time) + if self.logfile_workers: + self.logging_scheduler(curr_time) return elif type < 0: print 'unknown message type %d' % (type) @@ -587,7 +634,7 @@ class rx_ctl (object): return if self.logfile_workers: - self.update_logging_state(curr_time) + self.logging_scheduler(curr_time) return if updated: @@ -602,51 +649,102 @@ class rx_ctl (object): return worker return None - def update_logging_state(self, curr_time): + def free_frequency(self, frequency, curr_time): + assert not self.working_frequencies[frequency]['tgids'] + self.working_frequencies[frequency]['worker']['demod'].set_relative_frequency(0) + self.working_frequencies[frequency]['worker']['active'] = False + self.working_frequencies.pop(frequency) + print '%f release worker frequency %d' % (curr_time, frequency) + + def free_talkgroup(self, frequency, tgid, curr_time): + decoder = self.working_frequencies[frequency]['worker']['decoder'] + tdma_slot = self.working_frequencies[frequency]['tgids'][tgid]['tdma_slot'] + index = tdma_slot + if tdma_slot is None: + index = 0 + filename = 'idle-channel-%d-%d-%f.wav' % (frequency, index, curr_time) + decoder.set_output(filename, index=index) + self.working_frequencies[frequency]['tgids'].pop(tgid) + print '%f release tgid %d frequency %d' % (curr_time, tgid, frequency) + + def logging_scheduler(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) + for tgid in tsys.get_updated_talkgroups(curr_time): + frequency = tsys.talkgroups[tgid]['frequency'] + tdma_slot = tsys.talkgroups[tgid]['tdma_slot'] + # see if this tgid active on any other freq(s) + other_freqs = [f for f in self.working_frequencies if f != frequency and tgid in self.working_frequencies[f]['tgids']] + if other_freqs: + print '%f tgid %d slot %s frequency %d found on other frequencies %s' % (curr_time, tgid, tdma_slot, frequency, ','.join(['%s' % f for f in other_freqs])) + for f in other_freqs: + self.free_talkgroup(f, tgid, curr_time) + if not self.working_frequencies[f]['tgids']: + self.free_frequency(f, curr_time) + diff = abs(tsys.center_frequency - frequency) + if diff > self.input_rate/2: + #print '%f request for frequency %d tgid %d failed, offset %d exceeds maximum %d' % (curr_time, frequency, tgid, diff, self.input_rate/2) continue - worker = self.find_available_worker() - if worker is None: - print '*** error, no free demodulators, freq %d tgid %d' % (frequency, tgid) + update = True + if frequency in self.working_frequencies: + tgids = self.working_frequencies[frequency]['tgids'] + if tgid in tgids: + if tgids[tgid]['tdma_slot'] == tdma_slot: + update = False + else: + print '%f slot switch %s was %s tgid %d frequency %d' % (curr_time, tdma_slot, tgids[tgid]['tdma_slot'], tgid, frequency) + worker = self.working_frequencies[frequency]['worker'] + else: + #active_tdma_slots = [tgids[tg]['tdma_slot'] for tg in tgids] + print '%f new tgid %d slot %s arriving on already active frequency %d' % (curr_time, tgid, tdma_slot, frequency) + worker = self.working_frequencies[frequency]['worker'] + else: + worker = self.find_available_worker() + if worker is None: + print '*** error, no free demodulators, freq %d tgid %d' % (frequency, tgid) + continue + self.working_frequencies[frequency] = {'tgids' : {}, 'worker': worker} + worker['demod'].set_relative_frequency(tsys.center_frequency - frequency) + print '%f starting worker frequency %d tg %d slot %s' % (curr_time, frequency, tgid, tdma_slot) + self.working_frequencies[frequency]['tgids'][tgid] = {'updated': curr_time, 'tdma_slot': tdma_slot} + if not update: continue - - self.active_talkgroups[tgid] = {} - self.active_talkgroups[tgid]['updated'] = curr_time - self.active_talkgroups[tgid]['worker'] = worker + filename = 'tgid-%d-%f.wav' % (tgid, curr_time) + print '%f update frequency %d tg %d slot %s file %s' % (curr_time, frequency, tgid, tdma_slot, filename) + # set demod speed, decoder slot, output file name demod = worker['demod'] decoder = worker['decoder'] - demod.set_relative_frequency(relative_freq) symbol_rate = 4800 - if tsys.voice_frequencies[frequency]['slot'] is not None: + if tdma_slot is None: + index = 0 + else: + index = tdma_slot 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) - + xorhash = '%x%x%x' % (self.current_nac, tsys.ns_syid, tsys.ns_wacn) + if xorhash not in self.xor_cache: + self.xor_cache[xorhash] = lfsr.p25p2_lfsr(self.current_nac, tsys.ns_syid, tsys.ns_wacn).xor_chars + decoder.set_xormask(self.xor_cache[xorhash], xorhash, index=index) demod.set_omega(symbol_rate) + decoder.set_output(filename, index=index) - 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) + # garbage collection + if self.last_garbage_collect + 1 > curr_time: + return + self.last_garbage_collect = curr_time + + gc_frequencies = [] + gc_tgids = [] + for frequency in self.working_frequencies: + tgids = self.working_frequencies[frequency]['tgids'] + inactive_tgids = [[frequency, tgid] for tgid in tgids if tgids[tgid]['updated'] + self.TGID_HOLD_TIME < curr_time] + if len(inactive_tgids) == len(tgids): + gc_frequencies += [frequency] + gc_tgids += inactive_tgids + for frequency, tgid in gc_tgids: # expire talkgroups + self.free_talkgroup(frequency, tgid, curr_time) + for frequency in gc_frequencies: # expire working frequencies + self.free_frequency(frequency, curr_time) def update_state(self, command, curr_time): if not self.configs: @@ -662,7 +760,9 @@ class rx_ctl (object): new_slot = None if command == 'timeout' or command == 'duid15': - if self.current_state != self.states.CC and curr_time - self.last_tdma_vf > self.P2_GRACE_TIME: + if self.current_state == self.states.CC: + tsys.cc_timeouts += 1 + elif self.current_state != self.states.CC and curr_time - self.last_tdma_vf > self.P2_GRACE_TIME: new_state = self.states.CC new_frequency = tsys.trunk_cc elif command == 'update': @@ -670,7 +770,7 @@ class rx_ctl (object): desired_tgid = None if self.tgid_hold_until > curr_time: desired_tgid = self.tgid_hold - new_frequency, new_tgid, tdma_slot = tsys.find_voice_frequency(curr_time, tgid=desired_tgid) + new_frequency, new_tgid, tdma_slot = tsys.find_talkgroup(curr_time, tgid=desired_tgid) if new_frequency: new_state = self.states.TO_VC self.current_tgid = new_tgid @@ -714,6 +814,8 @@ class rx_ctl (object): print 'update_state: unknown command: %s\n' % command assert 0 == 1 + tsys.hunt_cc(curr_time) + if self.wait_until <= curr_time and self.tgid_hold_until <= curr_time and new_state is None: self.wait_until = curr_time + self.TSYS_HOLD_TIME new_nac = self.find_next_tsys() |