aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMax <ikj1234i@yahoo.com>2014-12-19 19:16:15 -0500
committerMax <ikj1234i@yahoo.com>2014-12-19 19:16:15 -0500
commite319bdcd9f5848ad870a52ddd69cead8ec0af126 (patch)
tree72795153129f1589e91f00e877d6e1da774b2b07
parent2b3eda4b05f3aa0dd3f9eb662ced24b059cdeb94 (diff)
p2tdma tg logging, fdma trunk cc hunting, blacklist/whitelist files, bug fixes
-rw-r--r--op25/gr-op25_repeater/apps/p25_decoder.py68
-rw-r--r--op25/gr-op25_repeater/apps/p25_demodulator.py1
-rwxr-xr-xop25/gr-op25_repeater/apps/scope.py95
-rw-r--r--op25/gr-op25_repeater/apps/tdma/lfsr.py3
-rw-r--r--op25/gr-op25_repeater/apps/trunking.py232
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()