trunking
git-svn-id: http://op25.osmocom.org/svn/trunk@327 65a5c917-d112-43f1-993d-58c26a4786be
This commit is contained in:
parent
54f1a19a17
commit
ef927d63ec
|
@ -35,6 +35,11 @@ import math
|
|||
import numpy
|
||||
import time
|
||||
import re
|
||||
try:
|
||||
import Hamlib
|
||||
except:
|
||||
pass
|
||||
|
||||
try:
|
||||
import Numeric
|
||||
except:
|
||||
|
@ -49,6 +54,8 @@ from optparse import OptionParser
|
|||
|
||||
import gnuradio.wxgui.plot as plot
|
||||
|
||||
import trunking
|
||||
|
||||
speeds = [300, 600, 900, 1200, 1440, 1800, 1920, 2400, 2880, 3200, 3600, 3840, 4000, 4800, 6000, 6400, 7200, 8000, 9600, 14400, 19200]
|
||||
|
||||
WIRESHARK_PORT = 23456
|
||||
|
@ -76,9 +83,11 @@ class p25_rx_block (stdgui2.std_top_block):
|
|||
parser.add_option("-C", "--costas-alpha", type="eng_float", default=0.125, help="value of alpha for Costas loop", metavar="Hz")
|
||||
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("-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-cc-freq", type="eng_float", default=None, help="trunk control channel frequency", metavar="Hz")
|
||||
parser.add_option("-v", "--verbosity", type="int", default=10, help="message debug level")
|
||||
parser.add_option("-V", "--vocoder", action="store_true", default=False, help="voice codec")
|
||||
parser.add_option("-o", "--offset", type="eng_float", default=0.0, help="tuning offset frequency [to circumvent DC offset]", metavar="Hz")
|
||||
|
@ -89,6 +98,8 @@ class p25_rx_block (stdgui2.std_top_block):
|
|||
parser.add_option("-R", "--rx-subdev-spec", type="subdev", default=(0, 0), help="select USRP Rx side A or B (default=A)")
|
||||
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="plughw:0,0", help="audio output device name")
|
||||
(options, args) = parser.parse_args()
|
||||
if len(args) != 0:
|
||||
parser.print_help()
|
||||
|
@ -107,7 +118,16 @@ class p25_rx_block (stdgui2.std_top_block):
|
|||
print "osmosdr source_c creation failure"
|
||||
ignore = True
|
||||
|
||||
print 'gain names ', self.src.get_gain_names()
|
||||
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)
|
||||
|
||||
if options.audio:
|
||||
self.channel_rate = 48000
|
||||
|
@ -140,6 +160,24 @@ class p25_rx_block (stdgui2.std_top_block):
|
|||
self.current_speed = i
|
||||
self.default_speed_idx = i
|
||||
|
||||
if options.hamlib_model:
|
||||
self.hamlib_attach(options.hamlib_model)
|
||||
|
||||
class _trunked_states(object):
|
||||
ACQ = 0
|
||||
CC = 1
|
||||
TO_VC = 2
|
||||
VC = 3
|
||||
TO_CC = 4
|
||||
self.trunked_states = _trunked_states
|
||||
|
||||
self.trunked_state = self.trunked_states.ACQ
|
||||
if options.trunk_cc_freq:
|
||||
if options.hamlib_model:
|
||||
self.hamlib.set_freq(int(options.trunk_cc_freq))
|
||||
# acquire trunk CC based on list of primary and alternates
|
||||
self.trunked_state = self.trunked_states.CC
|
||||
|
||||
# initialize the UI
|
||||
#
|
||||
self.__init_gui(frame, panel, vbox)
|
||||
|
@ -200,7 +238,8 @@ class p25_rx_block (stdgui2.std_top_block):
|
|||
|
||||
self.buffer = gr.copy(gr.sizeof_float)
|
||||
|
||||
msgq = gr.msg_queue(2)
|
||||
msgq = gr.msg_queue(100)
|
||||
self.du_watcher = du_queue_watcher(msgq, self.process_data_unit)
|
||||
udp_port = 0
|
||||
if self.options.wireshark:
|
||||
udp_port = WIRESHARK_PORT
|
||||
|
@ -208,12 +247,14 @@ class p25_rx_block (stdgui2.std_top_block):
|
|||
self.sink_sf = gr.file_sink(gr.sizeof_char, self.options.raw_symbols)
|
||||
do_imbe = 1
|
||||
do_output = 1
|
||||
self.sink_s = repeater.p25_frame_assembler(self.options.wireshark_host, udp_port, self.options.verbosity, do_imbe, do_output, 0, msgq)
|
||||
do_msgq = 1
|
||||
self.sink_s = repeater.p25_frame_assembler(self.options.wireshark_host, udp_port, self.options.verbosity, do_imbe, do_output, do_msgq, msgq)
|
||||
self.trunk_ctl = trunking.trunked_system(frequency_set = self.change_freq)
|
||||
if self.options.vocoder:
|
||||
self.sink_imbe = repeater.vocoder(0, 0, 0, '', 0, 0)
|
||||
self.audio_s2f = gr.short_to_float()
|
||||
self.audio_scaler = gr.multiply_const_ff(1 / 32768.0)
|
||||
self.audio_output = audio.sink(8000, "pulse", True)
|
||||
self.audio_output = audio.sink(8000, self.options.audio_output, True)
|
||||
self.connect(self.sink_imbe, self.audio_s2f, self.audio_scaler, self.audio_output)
|
||||
else:
|
||||
self.sink_imbe = gr.null_sink(gr.sizeof_char)
|
||||
|
@ -233,7 +274,7 @@ class p25_rx_block (stdgui2.std_top_block):
|
|||
self.fac_state = False
|
||||
self.fsk4_demod_connected = False
|
||||
self.psk_demod_connected = False
|
||||
self.fsk4_demod_mode = True
|
||||
self.fsk4_demod_mode = False
|
||||
self.corr_i_chan = False
|
||||
|
||||
if self.baseband_input:
|
||||
|
@ -320,7 +361,7 @@ class p25_rx_block (stdgui2.std_top_block):
|
|||
self.__connect([[source, (self.mixer, 0)],
|
||||
[self.lo, (self.mixer, 1)]])
|
||||
self.set_connection(fft=1)
|
||||
self.connect_fsk4_demod()
|
||||
self.connect_demods()
|
||||
|
||||
# Connect up the flow graph
|
||||
#
|
||||
|
@ -589,6 +630,49 @@ class p25_rx_block (stdgui2.std_top_block):
|
|||
|
||||
vbox.Add(hbox, 0, 0)
|
||||
|
||||
def change_freq(self, freq):
|
||||
if self.trunked_state == self.trunked_states.CC:
|
||||
print "%d: change frequency to: %f" % (self.trunked_state, float(freq) / 1000000.0)
|
||||
self.trunked_state = self.trunked_states.TO_VC
|
||||
if self.options.hamlib_model:
|
||||
self.hamlib.set_freq(freq)
|
||||
else:
|
||||
self.set_freq(freq)
|
||||
|
||||
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 process_data_unit(self, msg):
|
||||
type = msg.type()
|
||||
#print "type %d at %f state %d" %(type, time.time(), self.trunked_state)
|
||||
s = msg.to_string()
|
||||
t = 0
|
||||
for c in s:
|
||||
t = (t << 8) + ord(c)
|
||||
nac = t & 0xffff
|
||||
t = t >> 16
|
||||
if type == 7:
|
||||
self.trunk_ctl.decode_tsbk(t)
|
||||
if self.trunked_state == self.trunked_states.TO_CC:
|
||||
self.trunked_state = self.trunked_states.CC
|
||||
#print self.trunk_ctl.to_string()
|
||||
else:
|
||||
if self.trunked_state == self.trunked_states.TO_VC:
|
||||
self.trunked_state = self.trunked_states.VC
|
||||
if self.trunked_state == self.trunked_states.VC and (type == 3 or type == 15):
|
||||
self.trunked_state = self.trunked_states.TO_CC
|
||||
print "%d: change frequency to: %f" % (self.trunked_state, float(self.options.trunk_cc_freq) / 1000000.0)
|
||||
if self.options.hamlib_model:
|
||||
self.hamlib.set_freq(int(self.options.trunk_cc_freq))
|
||||
else:
|
||||
self.set_freq(int(self.options.trunk_cc_freq))
|
||||
|
||||
def set_gain(self, gain):
|
||||
if self.baseband_input:
|
||||
f = 1.0
|
||||
|
@ -1242,6 +1326,23 @@ class wizard_details_page(wx.wizard.WizardPageSimple):
|
|||
ToDo = True
|
||||
|
||||
|
||||
# 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()
|
||||
self.callback(msg)
|
||||
|
||||
# Frequency tracker
|
||||
#
|
||||
class demod_watcher(threading.Thread):
|
||||
|
|
|
@ -0,0 +1,220 @@
|
|||
|
||||
def crc16(dat,len): # slow version
|
||||
poly = (1<<12) + (1<<5) + (1<<0)
|
||||
crc = 0
|
||||
for i in range(len):
|
||||
bits = (dat >> (((len-1)-i)*8)) & 0xff
|
||||
for j in range(8):
|
||||
bit = (bits >> (7-j)) & 1
|
||||
crc = ((crc << 1) | bit) & 0x1ffff
|
||||
if crc & 0x10000:
|
||||
crc = (crc & 0xffff) ^ poly
|
||||
crc = crc ^ 0xffff
|
||||
return crc
|
||||
|
||||
class trunked_system (object):
|
||||
def __init__(self, debug=0, frequency_set=None):
|
||||
self.debug = debug
|
||||
self.frequency_set = frequency_set
|
||||
self.freq_table = {}
|
||||
self.stats = {}
|
||||
self.stats['tsbks'] = 0
|
||||
self.stats['crc'] = 0
|
||||
self.tsbk_cache = {}
|
||||
self.secondary = {}
|
||||
self.adjacent = {}
|
||||
self.rfss_syid = 0
|
||||
self.rfss_rfid = 0
|
||||
self.rfss_stid = 0
|
||||
self.rfss_chan = 0
|
||||
self.rfss_txchan = 0
|
||||
self.ns_syid = 0
|
||||
self.ns_wacn = 0
|
||||
self.ns_chan = 0
|
||||
|
||||
def to_string(self):
|
||||
s = []
|
||||
for table in self.freq_table:
|
||||
a = self.freq_table[table]['frequency'] / 1000000.0
|
||||
b = self.freq_table[table]['step'] / 1000000.0
|
||||
c = self.freq_table[table]['offset'] / 1000000.0
|
||||
s.append('tbl-id: %x frequency: %f step %f offset %f' % ( table, a,b,c))
|
||||
#self.freq_table[table]['frequency'] / 1000000.0, self.freq_table[table]['step'] / 1000000.0, self.freq_table[table]['offset']) / 1000000.0)
|
||||
s.append('stats: tsbks %d crc %d' % (self.stats['tsbks'], self.stats['crc']))
|
||||
s.append('secondary control channel(s): %s' % ','.join(['%f' % (float(k) / 1000000.0) for k in self.secondary.keys()]))
|
||||
for f in self.adjacent:
|
||||
s.append('adjacent %f: %s' % (float(f) / 1000000.0, self.adjacent[f]))
|
||||
s.append('rf: sysid %x rfid %x stid %x frequency %f uplink %f' % ( self.rfss_syid, self.rfss_rfid, self.rfss_stid, float(self.rfss_chan) / 1000000.0, float(self.rfss_txchan) / 1000000.0))
|
||||
s.append('net: sysid %x wacn %x frequency %f' % ( self.ns_syid, self.ns_wacn, float(self.ns_chan) / 1000000.0))
|
||||
return '\n'.join(s)
|
||||
|
||||
# return frequency in Hz
|
||||
def channel_id_to_frequency(self, id):
|
||||
table = (id >> 12) & 0xf
|
||||
channel = id & 0xfff
|
||||
if table not in self.freq_table:
|
||||
return None
|
||||
return self.freq_table[table]['frequency'] + self.freq_table[table]['step'] * channel
|
||||
|
||||
def channel_id_to_string(self, id):
|
||||
table = (id >> 12) & 0xf
|
||||
channel = id & 0xfff
|
||||
if table not in self.freq_table:
|
||||
return "%x-%d" % (table, channel)
|
||||
return "%f" % (self.freq_table[table]['frequency'] + self.freq_table[table]['step'] * channel) / 1000000.0
|
||||
|
||||
def decode_mbt_data(self, opcode, header, mbt_data):
|
||||
# print "decode_mbt_data: %x %x" %(opcode, mbt_data)
|
||||
if opcode == 0x0: # grp voice channel grant
|
||||
ch1 = (mbt_data >> 32) & 0xffff
|
||||
ch2 = (mbt_data >> 16) & 0xffff
|
||||
ga = (mbt_data ) & 0xffff
|
||||
if self.debug > 10:
|
||||
print "mbt00 voice grant ch1 %x ch2 %x addr 0x%x" %(ch1, ch2, ga)
|
||||
elif opcode == 0x3c: # adjacent status
|
||||
rfid = (header >> 24) & 0xff
|
||||
stid = (header >> 16) & 0xff
|
||||
ch1 = (mbt_data >> 48) & 0xffff
|
||||
ch2 = (mbt_data >> 32) & 0xffff
|
||||
if self.debug > 10:
|
||||
print "mbt3c adjacent rfid %x stid %x ch1 %x ch2 %x" %(rfid, stid, ch1, ch2)
|
||||
elif opcode == 0x3b: # network status
|
||||
wacn = (mbt_data >> 44) & 0xfffff
|
||||
ch1 = (mbt_data >> 24) & 0xffff
|
||||
ch2 = (mbt_data >> 8) & 0xffff
|
||||
if self.debug > 10:
|
||||
print "mbt3b net stat wacn %x ch1 %x ch2 %x" %(wacn, ch1, ch2)
|
||||
elif opcode == 0x3a: # rfss status
|
||||
rfid = (mbt_data >> 56) & 0xff
|
||||
stid = (mbt_data >> 48) & 0xff
|
||||
ch1 = (mbt_data >> 32) & 0xffff
|
||||
ch2 = (mbt_data >> 16) & 0xffff
|
||||
if self.debug > 10:
|
||||
print "mbt3a rfss stat rfid %x stid %x ch1 %x ch2 %x" %(rfid, stid, ch1, ch2)
|
||||
#else:
|
||||
# print "mbt other %x" % opcode
|
||||
|
||||
def decode_tsbk(self, tsbk):
|
||||
self.stats['tsbks'] += 1
|
||||
# if crc16(tsbk, 12) != 0:
|
||||
# self.stats['crc'] += 1
|
||||
# return # crc check failed
|
||||
tsbk = tsbk << 16 # for missing crc
|
||||
opcode = (tsbk >> 88) & 0x3f
|
||||
if self.debug > 10:
|
||||
print "TSBK: 0x%02x 0x%024x" % (opcode, tsbk)
|
||||
if opcode == 0x02: # group voice chan grant update
|
||||
ch1 = (tsbk >> 64) & 0xffff
|
||||
ga1 = (tsbk >> 48) & 0xffff
|
||||
ch2 = (tsbk >> 32) & 0xffff
|
||||
ga2 = (tsbk >> 16) & 0xffff
|
||||
if self.frequency_set:
|
||||
frequency = self.channel_id_to_frequency(ch1)
|
||||
if frequency:
|
||||
self.frequency_set(frequency)
|
||||
if self.debug > 10:
|
||||
print "tsbk02 grant update: chan %s %d %s %d" %(self.channel_id_to_string(ch1), ga1, self.channel_id_to_string(ch2), ga2)
|
||||
elif opcode == 0x16: # sndcp data ch
|
||||
ch1 = (tsbk >> 48) & 0xffff
|
||||
ch2 = (tsbk >> 32) & 0xffff
|
||||
if self.debug > 10:
|
||||
print "tsbk16 sndcp data ch: chan %x %x" %(ch1, ch2)
|
||||
elif opcode == 0x34: # iden_up vhf uhf
|
||||
iden = (tsbk >> 76) & 0xf
|
||||
bwvu = (tsbk >> 72) & 0xf
|
||||
toff0 = (tsbk >> 58) & 0x3fff
|
||||
spac = (tsbk >> 48) & 0x3ff
|
||||
freq = (tsbk >> 16) & 0xffffffff
|
||||
toff_sign = (toff0 >> 13) & 1
|
||||
toff = toff0 & 0x1fff
|
||||
if toff_sign == 0:
|
||||
toff = 0 - toff
|
||||
txt = ["mob Tx-", "mob Tx+"]
|
||||
self.freq_table[iden] = {}
|
||||
self.freq_table[iden]['offset'] = toff * spac * 125
|
||||
self.freq_table[iden]['step'] = spac * 125
|
||||
self.freq_table[iden]['frequency'] = freq * 5
|
||||
if self.debug > 10:
|
||||
print "tsbk34 iden vhf/uhf id %d toff %f spac %f freq %f [%s]" % (iden, toff * spac * 0.125 * 1e-3, spac * 0.125, freq * 0.000005, txt[toff_sign])
|
||||
elif opcode == 0x3d: # iden_up
|
||||
iden = (tsbk >> 76) & 0xf
|
||||
bw = (tsbk >> 67) & 0x1ff
|
||||
toff0 = (tsbk >> 58) & 0x1ff
|
||||
spac = (tsbk >> 48) & 0x3ff
|
||||
freq = (tsbk >> 16) & 0xffffffff
|
||||
toff_sign = (toff0 >> 8) & 1
|
||||
toff = toff0 & 0xff
|
||||
if toff_sign == 0:
|
||||
toff = 0 - toff
|
||||
txt = ["mob xmit < recv", "mob xmit > recv"]
|
||||
self.freq_table[iden] = {}
|
||||
self.freq_table[iden]['offset'] = toff * 250000
|
||||
self.freq_table[iden]['step'] = spac * 125
|
||||
self.freq_table[iden]['frequency'] = freq * 5
|
||||
if self.debug > 10:
|
||||
print "tsbk3d iden id %d toff %f spac %f freq %f" % (iden, toff * 0.25, spac * 0.125, freq * 0.000005)
|
||||
elif opcode == 0x3a: # rfss status
|
||||
syid = (tsbk >> 56) & 0xfff
|
||||
rfid = (tsbk >> 48) & 0xff
|
||||
stid = (tsbk >> 40) & 0xff
|
||||
chan = (tsbk >> 24) & 0xffff
|
||||
f1 = self.channel_id_to_frequency(chan)
|
||||
if f1:
|
||||
self.rfss_syid = syid
|
||||
self.rfss_rfid = rfid
|
||||
self.rfss_stid = stid
|
||||
self.rfss_chan = f1
|
||||
self.rfss_txchan = f1 + self.freq_table[chan >> 12]['offset']
|
||||
if self.debug > 10:
|
||||
print "tsbk3a rfss status: syid: %x rfid %x stid %d ch1 %x(%s)" %(syid, rfid, stid, chan, self.channel_id_to_string(chan))
|
||||
elif opcode == 0x39: # secondary cc
|
||||
rfid = (tsbk >> 72) & 0xff
|
||||
stid = (tsbk >> 64) & 0xff
|
||||
ch1 = (tsbk >> 48) & 0xffff
|
||||
ch2 = (tsbk >> 24) & 0xffff
|
||||
f1 = self.channel_id_to_frequency(ch1)
|
||||
f2 = self.channel_id_to_frequency(ch2)
|
||||
if f1 and f2:
|
||||
self.secondary[ f1 ] = 1
|
||||
self.secondary[ f2 ] = 1
|
||||
if self.debug > 10:
|
||||
print "tsbk39 secondary cc: rfid %x stid %d ch1 %x(%s) ch2 %x(%s)" %(rfid, stid, ch1, self.channel_id_to_string(ch1), ch2, self.channel_id_to_string(ch2))
|
||||
elif opcode == 0x3b: # network status
|
||||
wacn = (tsbk >> 52) & 0xfffff
|
||||
syid = (tsbk >> 40) & 0xfff
|
||||
ch1 = (tsbk >> 24) & 0xffff
|
||||
f1 = self.channel_id_to_frequency(ch1)
|
||||
if f1:
|
||||
self.ns_syid = syid
|
||||
self.ns_wacn = wacn
|
||||
self.ns_chan = f1
|
||||
if self.debug > 10:
|
||||
print "tsbk3b net stat: wacn %x syid %x ch1 %x(%s)" %(wacn, syid, ch1, self.channel_id_to_string(ch1))
|
||||
elif opcode == 0x3c: # adjacent status
|
||||
rfid = (tsbk >> 48) & 0xff
|
||||
stid = (tsbk >> 40) & 0xff
|
||||
ch1 = (tsbk >> 24) & 0xffff
|
||||
f1 = self.channel_id_to_frequency(ch1)
|
||||
if f1:
|
||||
self.adjacent[f1] = 'rfid: %x stid:%x' % (rfid, stid)
|
||||
if self.debug > 10:
|
||||
print "tsbk3c adjacent: rfid %x stid %d ch1 %x(%s)" %(rfid, stid, ch1, self.channel_id_to_string(ch1))
|
||||
#else:
|
||||
# print "tsbk other %x" % opcode
|
||||
|
||||
def main():
|
||||
q = 0x3a000012ae01013348704a54
|
||||
rc = crc16(q,12)
|
||||
print "should be zero: %x" % rc
|
||||
|
||||
q = 0x3a001012ae01013348704a54
|
||||
rc = crc16(q,12)
|
||||
print "should be nonzero: %x" % rc
|
||||
|
||||
t = trunked_system(debug=255)
|
||||
q = 0x3a000012ae0101334870
|
||||
t.decode_tsbk(q)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
Reference in New Issue