initial hacks for python 2/3 agnosticism

master
Max 3 years ago
parent 3de2d3e440
commit b91039ecd0
  1. 46
      op25/gr-op25_repeater/apps/gr_gnuplot.py
  2. 449
      op25/gr-op25_repeater/apps/http_server.py
  3. 6
      op25/gr-op25_repeater/apps/multi_rx.py
  4. 4
      op25/gr-op25_repeater/apps/p25_decoder.py
  5. 10
      op25/gr-op25_repeater/apps/p25_demodulator.py
  6. 29
      op25/gr-op25_repeater/apps/rx.py
  7. 14
      op25/gr-op25_repeater/apps/tdma/bit_utils.py
  8. 66
      op25/gr-op25_repeater/apps/tdma/lfsr.py
  9. 8
      op25/gr-op25_repeater/apps/terminal.py
  10. 67
      op25/gr-op25_repeater/apps/trunking.py
  11. 24
      op25/gr-op25_repeater/apps/tsvfile.py
  12. 20
      op25/gr-op25_repeater/apps/tx/op25_c4fm_mod.py

@ -131,13 +131,13 @@ class wrap_gp(object):
s += 'e\n'
self.buf=self.buf[self.sps:]
plots.append('"-" with lines')
elif mode == 'constellation':
elif mode == 'constellation':
plot_size = (240,240)
self.buf = self.buf[:100]
for b in self.buf:
for b in self.buf:
s += '%f\t%f\n' % (degrees(np.angle(b)), limit(np.abs(b),1.0))
s += 'e\n'
plots.append('"-" with points')
s += 'e\n'
plots.append('"-" with points')
for b in self.buf:
#s += '%f\t%f\n' % (b.real, b.imag)
s += '%f\t%f\n' % (degrees(np.angle(b)), limit(np.abs(b),1.0))
@ -159,7 +159,7 @@ class wrap_gp(object):
tune_freq = (self.center_freq - self.relative_freq) / 1e6
if self.center_freq and self.width:
self.freqs = ((self.freqs * self.width) + self.center_freq + self.offset_freq) / 1e6
for i in xrange(len(self.ffts)):
for i in range(len(self.ffts)):
if mode == 'fft':
self.avg_pwr[i] = ((1.0 - FFT_AVG) * self.avg_pwr[i]) + (FFT_AVG * np.abs(self.ffts[i]))
else:
@ -223,37 +223,39 @@ class wrap_gp(object):
h += 'set format ""\n'
h += 'set style line 11 lt 1 lw 2 pt 2 ps 2\n'
h+= 'set title "Constellation"\n'
h+= 'set title "Constellation"\n'
elif mode == 'eye':
h+= background
h+= 'set yrange [-4:4]\n'
h+= 'set title "Datascope"\n'
h+= 'set title "Datascope"\n'
elif mode == 'symbol':
h+= background
h+= 'set yrange [-4:4]\n'
h+= 'set title "Symbol"\n'
h+= 'set title "Symbol"\n'
elif mode == 'fft' or mode == 'mixer':
h+= 'unset arrow; unset title\n'
h+= 'set xrange [%f:%f]\n' % (self.freqs[0], self.freqs[len(self.freqs)-1])
h+= 'set xlabel "Frequency"\n'
h+= 'set ylabel "Power(dB)"\n'
h+= 'set grid\n'
h+= 'set xlabel "Frequency"\n'
h+= 'set ylabel "Power(dB)"\n'
h+= 'set grid\n'
h+= 'set yrange [-100:0]\n'
if mode == 'mixer': # mixer
h+= 'set title "Mixer: balance %3.0f (smaller is better)"\n' % (np.abs(self.avg_sum_pwr * 1000))
h+= 'set title "Mixer: balance %3.0f (smaller is better)"\n' % (np.abs(self.avg_sum_pwr * 1000))
else: # fft
h+= 'set title "Spectrum"\n'
h+= 'set title "Spectrum"\n'
if self.center_freq:
arrow_pos = (self.center_freq - self.relative_freq) / 1e6
h+= 'set arrow from %f, graph 0 to %f, graph 1 nohead\n' % (arrow_pos, arrow_pos)
h+= 'set title "Spectrum: tuned to %f Mhz"\n' % arrow_pos
elif mode == 'float':
h+= 'set yrange [-2:2]\n'
h+= 'set title "Oscilloscope"\n'
h+= 'set title "Oscilloscope"\n'
dat = '%s%splot %s\n%s' % (h0, h, ','.join(plots), s)
if self.logfile is not None:
with open(self.logfile, 'a') as fd:
fd.write(dat)
if sys.version[0] != '2':
dat = bytes(dat, 'utf8')
if self.logfile is not None:
with open(self.logfile, 'a') as fd:
fd.write(dat)
self.gp.poll()
if self.gp.returncode is None: # make sure gnuplot is still running
try:
@ -293,7 +295,7 @@ class eye_sink_f(gr.sync_block):
def work(self, input_items, output_items):
in0 = input_items[0]
consumed = self.gnuplot.plot(in0, 100 * self.sps, mode='eye')
consumed = self.gnuplot.plot(in0, 100 * self.sps, mode='eye')
return consumed ### len(input_items[0])
def kill(self):
@ -312,7 +314,7 @@ class constellation_sink_c(gr.sync_block):
def work(self, input_items, output_items):
in0 = input_items[0]
self.gnuplot.plot(in0, 1000, mode='constellation')
self.gnuplot.plot(in0, 1000, mode='constellation')
return len(input_items[0])
def kill(self):
@ -335,7 +337,7 @@ class fft_sink_c(gr.sync_block):
if self.skip >= 50:
self.skip = 0
in0 = input_items[0]
self.gnuplot.plot(in0, FFT_BINS, mode='fft')
self.gnuplot.plot(in0, FFT_BINS, mode='fft')
return len(input_items[0])
def kill(self):
@ -343,7 +345,7 @@ class fft_sink_c(gr.sync_block):
def set_center_freq(self, f):
self.gnuplot.set_center_freq(f)
self.gnuplot.set_relative_freq(0.0)
self.gnuplot.set_relative_freq(0.0)
def set_relative_freq(self, f):
self.gnuplot.set_relative_freq(f)
@ -390,7 +392,7 @@ class symbol_sink_f(gr.sync_block):
def work(self, input_items, output_items):
in0 = input_items[0]
self.gnuplot.plot(in0, 2400, mode='symbol')
self.gnuplot.plot(in0, 2400, mode='symbol')
return len(input_items[0])
def kill(self):

@ -0,0 +1,449 @@
#!/usr/bin/env python
# Copyright 2017, 2018 Max H. Parke KA1RBI
#
# This file is part of OP25
#
# 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 sys
import os
import time
import re
import json
import socket
import traceback
import threading
import glob
import subprocess
import zmq
from gnuradio import gr
from waitress.server import create_server
from optparse import OptionParser
from multi_rx import byteify
from tsvfile import load_tsv, make_config
my_input_q = None
my_output_q = None
my_recv_q = None
my_port = None
my_backend = None
CFG_DIR = '../www/config/'
TSV_DIR = './'
"""
fake http and ajax server module
TODO: make less fake
"""
def static_file(environ, start_response):
content_types = { 'png': 'image/png', 'jpeg': 'image/jpeg', 'jpg': 'image/jpeg', 'gif': 'image/gif', 'css': 'text/css', 'js': 'application/javascript', 'html': 'text/html'}
img_types = 'png jpg jpeg gif'.split()
if environ['PATH_INFO'] == '/':
filename = 'index.html'
else:
filename = re.sub(r'[^a-zA-Z0-9_.\-]', '', environ['PATH_INFO'])
suf = filename.split('.')[-1]
pathname = '../www/www-static'
if suf in img_types:
pathname = '../www/images'
pathname = '%s/%s' % (pathname, filename)
if suf not in content_types.keys() or '..' in filename or not os.access(pathname, os.R_OK):
sys.stderr.write('404 %s\n' % pathname)
status = '404 NOT FOUND'
content_type = 'text/plain'
output = status
else:
output = open(pathname, 'rb').read()
content_type = content_types[suf]
status = '200 OK'
return status, content_type, output
def valid_tsv(filename):
if not os.access(filename, os.R_OK):
return False
line = open(filename).readline()
for word in 'Sysname Offset NAC Modulation TGID Whitelist Blacklist'.split():
if word not in line:
return False
return True
def tsv_config(filename):
DEFAULT_CFG = '../www/config/default.json'
filename = '%s%s' % (TSV_DIR, filename)
filename = filename.replace('[TSV]', '.tsv')
if not valid_tsv(filename):
return None
cfg = make_config(load_tsv(filename))
default_cfg = json.loads(open(DEFAULT_CFG).read())
result = default_cfg
channels = [ {'active': True,
'blacklist': cfg[nac]['blacklist'],
'whitelist': cfg[nac]['whitelist'],
'cclist': cfg[nac]['cclist'],
'demod_type': 'cqpsk',
'destination': 'udp://127.0.0.1:23456',
'filter_type': 'rc',
'frequency': 500000000,
'if_rate': 24000,
'nac': nac,
'name': cfg[nac]['sysname'],
'phase2_tdma': False,
'plot': "",
'tgids': cfg[nac]['tgid_map'],
'trunked': True
}
for nac in cfg.keys() ]
result['channels'] = channels
return {'json_type':'config_data', 'data': result}
def do_request(d):
global my_backend
if d['command'].startswith('rx-'):
msg = gr.message().make_from_string(json.dumps(d), -2, 0, 0)
if not my_backend.input_q.full_p():
my_backend.input_q.insert_tail(msg)
return None
elif d['command'] == 'config-load':
if '[TSV]' in d['data']:
return tsv_config(d['data'])
filename = '%s%s.json' % (CFG_DIR, d['data'])
if not os.access(filename, os.R_OK):
return None
js_msg = json.loads(open(filename).read())
return {'json_type':'config_data', 'data': js_msg}
elif d['command'] == 'config-list':
files = glob.glob('%s*.json' % CFG_DIR)
files = [x.replace('.json', '') for x in files]
files = [x.replace(CFG_DIR, '') for x in files]
if d['data'] == 'tsv':
tsvfiles = glob.glob('%s*.tsv' % TSV_DIR)
tsvfiles = [x for x in tsvfiles if valid_tsv(x)]
tsvfiles = [x.replace('.tsv', '[TSV]') for x in tsvfiles]
tsvfiles = [x.replace(TSV_DIR, '') for x in tsvfiles]
files += tsvfiles
return {'json_type':'config_list', 'data': files}
elif d['command'] == 'config-save':
name = d['data']['name']
if '..' in name or '.json' in name or '/' in name:
return None
filename = '%s%s.json' % (CFG_DIR, d['data']['name'])
open(filename, 'w').write(json.dumps(d['data']['value'], indent=4, separators=[',',':'], sort_keys=True))
return None
def post_req(environ, start_response, postdata):
global my_input_q, my_output_q, my_recv_q, my_port
resp_msg = []
try:
data = json.loads(postdata)
except:
sys.stderr.write('post_req: error processing input: %s:\n' % (postdata))
traceback.print_exc(limit=None, file=sys.stderr)
sys.stderr.write('*** end traceback ***\n')
for d in data:
if d['command'].startswith('config-') or d['command'].startswith('rx-'):
resp = do_request(d)
if resp:
resp_msg.append(resp)
continue
if d['command'].startswith('settings-'):
msg = gr.message().make_from_string(json.dumps(d), -4, 0, 0)
else:
msg = gr.message().make_from_string(str(d['command']), -2, d['data'], 0)
if my_output_q.full_p():
my_output_q.delete_head_nowait() # ignores result
if not my_output_q.full_p():
my_output_q.insert_tail(msg)
time.sleep(0.2)
while not my_recv_q.empty_p():
msg = my_recv_q.delete_head()
if msg.type() == -4:
resp_msg.append(json.loads(msg.to_string()))
status = '200 OK'
content_type = 'application/json'
output = json.dumps(resp_msg)
return status, content_type, output
def http_request(environ, start_response):
if environ['REQUEST_METHOD'] == 'GET':
status, content_type, output = static_file(environ, start_response)
elif environ['REQUEST_METHOD'] == 'POST':
postdata = environ['wsgi.input'].read()
status, content_type, output = post_req(environ, start_response, postdata)
else:
status = '200 OK'
content_type = 'text/plain'
output = status
sys.stderr.write('http_request: unexpected input %s\n' % environ['PATH_INFO'])
response_headers = [('Content-type', content_type),
('Content-Length', str(len(output)))]
start_response(status, response_headers)
if sys.version[0] != '2':
if isinstance(output, str):
output = output.encode()
return [output]
def application(environ, start_response):
failed = False
try:
result = http_request(environ, start_response)
except:
failed = True
sys.stderr.write('application: request failed:\n%s\n' % traceback.format_exc())
sys.exit(1)
return result
def process_qmsg(msg):
if my_recv_q.full_p():
my_recv_q.delete_head_nowait() # ignores result
if my_recv_q.full_p():
return
my_recv_q.insert_tail(msg)
class http_server(object):
def __init__(self, input_q, output_q, endpoint, **kwds):
global my_input_q, my_output_q, my_recv_q, my_port
host, port = endpoint.split(':')
if my_port is not None:
raise AssertionError('this server is already active on port %s' % my_port)
my_input_q = input_q
my_output_q = output_q
my_port = int(port)
my_recv_q = gr.msg_queue(10)
self.q_watcher = queue_watcher(my_input_q, process_qmsg)
self.server = create_server(application, host=host, port=my_port)
def run(self):
self.server.run()
class 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)
class Backend(threading.Thread):
def __init__(self, options, input_q, output_q, init_config=None, **kwds):
threading.Thread.__init__ (self, **kwds)
self.setDaemon(1)
self.keep_running = True
self.rx_options = None
self.input_q = input_q
self.output_q = output_q
self.verbosity = options.verbosity
self.zmq_context = zmq.Context()
self.zmq_port = options.zmq_port
self.zmq_sub = self.zmq_context.socket(zmq.SUB)
self.zmq_sub.connect('tcp://localhost:%d' % self.zmq_port)
self.zmq_sub.setsockopt(zmq.SUBSCRIBE, '')
self.zmq_pub = self.zmq_context.socket(zmq.PUB)
self.zmq_pub.sndhwm = 5
self.zmq_pub.bind('tcp://*:%d' % (self.zmq_port+1))
self.start()
self.subproc = None
self.msg = None
self.q_watcher = queue_watcher(self.input_q, self.process_msg)
if init_config:
d = {'command': 'rx-start', 'data': init_config}
msg = gr.message().make_from_string(json.dumps(d), -4, 0, 0)
self.input_q.insert_tail(msg)
def publish(self, msg):
t = msg.type()
s = msg.to_string()
a = msg.arg1()
self.zmq_pub.send(json.dumps({'command': s, 'data': a, 'msgtype': t}))
def check_subproc(self): # return True if subprocess is active
if not self.subproc:
return False
rc = self.subproc.poll()
if rc is None:
return True
else:
self.subproc.wait()
self.subproc = None
return False
def process_msg(self, msg):
def make_command(options, config_file):
trunked_ct = [True for x in options._js_config['channels'] if x['trunked']]
total_ct = [True for x in options._js_config['channels']]
if trunked_ct and len(trunked_ct) != len(total_ct):
self.msg = 'no suitable backend found for this configuration'
return None
if not trunked_ct:
self.backend = '%s/%s' % (os.getcwd(), 'multi_rx.py')
opts = [self.backend]
filename = '%s%s.json' % (CFG_DIR, config_file)
opts.append('--config-file')
opts.append(filename)
return opts
types = {'costas-alpha': 'float', 'trunk-conf-file': 'str', 'demod-type': 'str', 'logfile-workers': 'int', 'decim-amt': 'int', 'wireshark-host': 'str', 'gain-mu': 'float', 'phase2-tdma': 'bool', 'seek': 'int', 'ifile': 'str', 'pause': 'bool', 'antenna': 'str', 'calibration': 'float', 'fine-tune': 'float', 'raw-symbols': 'str', 'audio-output': 'str', 'vocoder': 'bool', 'input': 'str', 'wireshark': 'bool', 'gains': 'str', 'args': 'str', 'sample-rate': 'int', 'terminal-type': 'str', 'gain': 'float', 'excess-bw': 'float', 'offset': 'float', 'audio-input': 'str', 'audio': 'bool', 'plot-mode': 'str', 'audio-if': 'bool', 'tone-detect': 'bool', 'frequency': 'int', 'freq-corr': 'float', 'hamlib-model': 'int', 'udp-player': 'bool', 'verbosity': 'int'}
self.backend = '%s/%s' % (os.getcwd(), 'rx.py')
opts = [self.backend]
for k in [ x for x in dir(options) if not x.startswith('_') ]:
kw = k.replace('_', '-')
val = getattr(options, k)
if kw not in types.keys():
self.msg = 'make_command: unknown option: %s %s type %s' % (k, val, type(val))
return None
elif types[kw] == 'str':
if val:
opts.append('--%s' % kw)
opts.append('%s' % (val))
elif types[kw] == 'float':
opts.append('--%s' % kw)
if val:
opts.append('%f' % (val))
else:
opts.append('%f' % (0))
elif types[kw] == 'int':
opts.append('--%s' % kw)
if val:
opts.append('%d' % (val))
else:
opts.append('%d' % (0))
elif types[kw] == 'bool':
if val:
opts.append('--%s' % kw)
else:
self.msg = 'make_command: unknown2 option: %s %s type %s' % (k, val, type(val))
return None
return opts
msg = json.loads(msg.to_string())
if msg['command'] == 'rx-start':
if self.check_subproc():
self.msg = 'start command failed: subprocess pid %d already active' % self.subproc.pid
return
options = rx_options(msg['data'])
if getattr(options, '_js_config', None) is None:
self.msg = 'start command failed: rx_options: unable to initialize config=%s' % (msg['data'])
return
options.verbosity = self.verbosity
options.terminal_type = 'zmq:tcp:%d' % (self.zmq_port)
cmd = make_command(options, msg['data'])
if cmd:
self.subproc = subprocess.Popen(cmd)
elif msg['command'] == 'rx-stop':
if not self.check_subproc():
self.msg = 'stop command failed: subprocess not active'
return
if msg['data'] == 'kill':
self.subproc.kill()
else:
self.subproc.terminate()
elif msg['command'] == 'rx-state':
d = {}
if self.check_subproc():
d['rx-state'] = 'subprocess pid %d active' % self.subproc.pid
else:
d['rx-state'] = 'subprocess not active, last msg: %s' % self.msg
msg = gr.message().make_from_string(json.dumps(d), -4, 0, 0)
if not self.output_q.full_p():
self.output_q.insert_tail(msg)
def run(self):
while self.keep_running:
js = self.zmq_sub.recv()
if not self.keep_running:
break
msg = gr.message().make_from_string(js, -4, 0, 0)
if not self.output_q.full_p():
self.output_q.insert_tail(msg)
class rx_options(object):
def __init__(self, name):
def map_name(k):
return k.replace('-', '_')
filename = '%s%s.json' % (CFG_DIR, name)
if not os.access(filename, os.R_OK):
sys.stderr.write('unable to access config file %s\n' % (filename))
return
config = byteify(json.loads(open(filename).read()))
dev = [x for x in config['devices'] if x['active']][0]
if not dev:
return
chan = [x for x in config['channels'] if x['active']][0]
if not chan:
return
options = object()
for k in config['backend-rx'].keys():
setattr(self, map_name(k), config['backend-rx'][k])
for k in 'args frequency gains offset'.split():
setattr(self, k, dev[k])
self.demod_type = chan['demod_type']
self.freq_corr = dev['ppm']
self.sample_rate = dev['rate']
self.plot_mode = chan['plot']
self.phase2_tdma = chan['phase2_tdma']
self.trunk_conf_file = filename
self._js_config = config
def http_main():
global my_backend
# command line argument parsing
parser = OptionParser()
parser.add_option("-c", "--config", type="string", default=None, help="config json name, without prefix/suffix")
parser.add_option("-e", "--endpoint", type="string", default="127.0.0.1:8080", help="address:port to listen on (use addr 0.0.0.0 to enable external clients)")
parser.add_option("-v", "--verbosity", type="int", default=0, help="message debug level")
parser.add_option("-p", "--pause", action="store_true", default=False, help="block on startup")
parser.add_option("-z", "--zmq-port", type="int", default=25000, help="backend sub port")
(options, args) = parser.parse_args()
# wait for gdb
if options.pause:
print ('Ready for GDB to attach (pid = %d)' % (os.getpid(),))
raw_input("Press 'Enter' to continue...")
input_q = gr.msg_queue(20)
output_q = gr.msg_queue(20)
backend_input_q = gr.msg_queue(20)
backend_output_q = gr.msg_queue(20)
my_backend = Backend(options, backend_input_q, backend_output_q, init_config=options.config)
server = http_server(input_q, output_q, options.endpoint)
q_watcher = queue_watcher(output_q, lambda msg : my_backend.publish(msg))
backend_q_watcher = queue_watcher(backend_output_q, lambda msg : process_qmsg(msg))
server.run()
if __name__ == '__main__':
http_main()

@ -1,6 +1,6 @@
#!/usr/bin/env python
# Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017 Max H. Parke KA1RBI
# Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Max H. Parke KA1RBI
#
# This file is part of OP25
#
@ -51,6 +51,8 @@ _def_symbol_rate = 4800
#
def byteify(input): # thx so
if sys.version[0] != '2': # hack, must be a better way
return input
if isinstance(input, dict):
return {byteify(key): byteify(value)
for key, value in input.iteritems()}
@ -237,7 +239,7 @@ class rx_main(object):
# wait for gdb
if options.pause:
print 'Ready for GDB to attach (pid = %d)' % (os.getpid(),)
print ('Ready for GDB to attach (pid = %d)' % (os.getpid(),))
raw_input("Press 'Enter' to continue...")
if options.config_file == '-':

@ -67,7 +67,7 @@ class p25_decoder_sink_b(gr.hier_block2):
@type debug: int
"""
gr.hier_block2.__init__(self, "p25_demod_c",
gr.hier_block2.__init__(self, "p25_demod_c",
gr.io_signature(1, 1, gr.sizeof_char), # Input signature
gr.io_signature(0, 0, 0)) # Output signature
@ -99,7 +99,7 @@ class p25_decoder_sink_b(gr.hier_block2):
num_decoders = 1
if num_ambe > 1:
num_decoders += num_ambe - 1
for slot in xrange(num_decoders):
for slot in range(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, do_phase2_tdma))
self.p25_decoders[slot].set_slotid(slot)

@ -62,16 +62,16 @@ def get_decim(speed):
for i_f in if_freqs:
if s % i_f != 0:
continue
q = int(s / i_f)
q = s // i_f
if q & 1:
continue
if q >= 40 and q & 3 == 0:
decim = q/4
decim = q//4
decim2 = 4
else:
decim = q/2
decim = q//2
decim2 = 2
return int(decim), int(decim2)
return decim, decim2
return None
class p25_demod_base(gr.hier_block2):
@ -92,7 +92,7 @@ class p25_demod_base(gr.hier_block2):
self.baseband_amp = blocks.multiply_const_ff(_def_bb_gain)
coeffs = op25_c4fm_mod.c4fm_taps(sample_rate=self.if_rate, span=9, generator=op25_c4fm_mod.transfer_function_rx).generate()
sps = self.if_rate / self.symbol_rate
sps = self.if_rate // self.symbol_rate
if filter_type == 'rrc':
ntaps = 7 * sps
if ntaps & 1 == 0:

@ -2,7 +2,7 @@
# Copyright 2008-2011 Steve Glass
#
# Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019 Max H. Parke KA1RBI
# 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)
@ -114,27 +114,27 @@ class p25_rx_block (gr.top_block):
import osmosdr
self.src = osmosdr.source(options.args)
except Exception:
print "osmosdr source_c creation failure"
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)
#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())
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)
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())
print ('supported sample rates %d-%d step %d' % (rates.start(), rates.stop(), rates.step()))
except:
pass # ignore
@ -166,7 +166,7 @@ class p25_rx_block (gr.top_block):
self.constellation_scope_connected = False
for i in xrange(len(speeds)):
for i in range(len(speeds)):
if speeds[i] == _default_speed:
self.current_speed = i
self.default_speed_idx = i
@ -176,7 +176,7 @@ class p25_rx_block (gr.top_block):
# wait for gdb
if options.pause:
print 'Ready for GDB to attach (pid = %d)' % (os.getpid(),)
print ('Ready for GDB to attach (pid = %d)' % (os.getpid(),))
raw_input("Press 'Enter' to continue...")
self.input_q = gr.msg_queue(10)
@ -298,7 +298,7 @@ class p25_rx_block (gr.top_block):
if self.options.phase2_tdma:
num_ambe = 2
if self.options.logfile_workers:
for i in xrange(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)
@ -458,7 +458,7 @@ class p25_rx_block (gr.top_block):
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'))
print ('RTL Gain of %d set to: %.1f' % (gain, self.src.get_gain('LNA')))
else:
if self.baseband_input:
f = 1.0
@ -467,7 +467,7 @@ class p25_rx_block (gr.top_block):
self.demod.set_baseband_gain(float(gain) * f)
def set_audio_scaler(self, vol):
#print 'audio scaler: %f' % ((1 / 32768.0) * (vol * 0.1))
#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))
@ -571,7 +571,7 @@ class p25_rx_block (gr.top_block):
if file_seek > 0:
rc = ifile.seek(file_seek*1024, gr.SEEK_SET)
assert rc == True
#print "seek: %d, rc = %d" % (file_seek, rc)
#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)
@ -645,6 +645,9 @@ class p25_rx_block (gr.top_block):
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':
self.freq_update()

@ -22,7 +22,7 @@ import numpy as np
def rev_int(n,l):
j=0
for i in xrange(l):
for i in range(l):
b=n&1
n=n>>1
j = (j << 1) | b
@ -30,7 +30,7 @@ def rev_int(n,l):
def bits_to_dibits(bits):
d = []
for i in xrange(len(bits)>>1):
for i in range(len(bits)>>1):
d.append((bits[i*2]<<1) + bits[i*2+1])
return d
@ -43,14 +43,14 @@ def dibits_to_bits(dibits):
def mk_array(n, l):
a = []
for i in xrange(0,l):
for i in range(0,l):
a.insert(0, n & 1)
n = n >> 1
return np.array(a)
def mk_int(a):
res= 0L
for i in xrange(0, len(a)):
res= 0
for i in range(0, len(a)):
res = res * 2
res = res + (a[i] & 1)
return res
@ -61,7 +61,7 @@ def mk_str(a):
def check_l(a,b):
ans = 0
assert len(a) == len(b)
for i in xrange(len(a)):
for i in range(len(a)):
if (a[i] == b[i]):
ans += 1
return ans
@ -77,7 +77,7 @@ def fixup(a):
return res
def find_sym(pattern, symbols):
for i in xrange(0, len(symbols)-len(pattern)):
for i in range(0, len(symbols)-len(pattern)):
chunk = symbols[i : i + len(pattern)]
if chunk == pattern:
return i

@ -25,54 +25,54 @@ class p25p2_lfsr(object):
def __init__(self,nac,sysid,wacn):
xorbits = self.mk_xor_bits(nac,sysid,wacn)
#self.xorsyms = np.zeros(len(xorbits)/2)
self.xorsyms = [0] * (len(xorbits)/2)
for i in xrange(len(self.xorsyms)):
self.xorsyms = [0] * (len(xorbits)//2)
for i in range(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
s3 = s3 & 0x3fL
s4 = s4 & 0x1fL
s5 = s5 & 0x3fffL
s6 = s6 & 0x3ffL
s1 = s1 & 0xf
s2 = s2 & 0x1f
s3 = s3 & 0x3f
s4 = s4 & 0x1f
s5 = s5 & 0x3fff
s6 = s6 & 0x3ff
return (s1<<40)+(s2<<35)+(s3<<29)+(s4<<24)+(s5<<10)+s6
def disasm_reg(self,r):
s1 = (r>>40) & 0xfL
s2 = (r>>35) & 0x1fL
s3 = (r>>29) & 0x3fL
s4 = (r>>24) & 0x1fL
s5 = (r>>10) & 0x3fffL
s6 = r & 0x3ffL
s1 = (r>>40) & 0xf
s2 = (r>>35) & 0x1f
s3 = (r>>29) & 0x3f
s4 = (r>>24) & 0x1f
s5 = (r>>10) & 0x3fff
s6 = r & 0x3ff
return s1,s2,s3,s4,s5,s6
def cyc_reg(self, reg):
s1,s2,s3,s4,s5,s6 = self.disasm_reg(reg)
cy1 = (s1 >> 3) & 1L
cy2 = (s2 >> 4) & 1L
cy3 = (s3 >> 5) & 1L
cy4 = (s4 >> 4) & 1L
cy5 = (s5 >> 13) & 1L
cy6 = (s6 >> 9) & 1L
cy1 = (s1 >> 3) & 1
cy2 = (s2 >> 4) & 1
cy3 = (s3 >> 5) & 1
cy4 = (s4 >> 4) & 1
cy5 = (s5 >> 13) & 1
cy6 = (s6 >> 9) & 1
x1 = cy1 ^ cy2
x2 = cy1 ^ cy3
x3 = cy1 ^ cy4
x4 = cy1 ^ cy5
x5 = cy1 ^ cy6
s1 = (s1 << 1) & 0xfL
s2 = (s2 << 1) & 0x1fL
s3 = (s3 << 1) & 0x3fL
s4 = (s4 << 1) & 0x1fL
s5 = (s5 << 1) & 0x3fffL
s6 = (s6 << 1) & 0x3ffL
s1 = s1 | (x1 & 1L)
s2 = s2 | (x2 & 1L)
s3 = s3 | (x3 & 1L)
s4 = s4 | (x4 & 1L)
s5 = s5 | (x5 & 1L)
s6 = s6 | (cy1 & 1L)
s1 = (s1 << 1) & 0xf
s2 = (s2 << 1) & 0x1f
s3 = (s3 << 1) & 0x3f
s4 = (s4 << 1) & 0x1f
s5 = (s5 << 1) & 0x3fff
s6 = (s6 << 1) & 0x3ff
s1 = s1 | (x1 & 1)
s2 = s2 | (x2 & 1)
s3 = s3 | (x3 & 1)
s4 = s4 | (x4 & 1)
s5 = s5 | (x5 & 1)
s6 = s6 | (cy1 & 1)
return self.asm_reg(s1,s2,s3,s4,s5,s6)
def mk_xor_bits(self, nac,sysid,wacn):
@ -82,7 +82,7 @@ class p25p2_lfsr(object):
reg = mk_int(np.dot(reg,M))
s = []
for i in xrange(4320):
for i in range(4320):
s.append((reg >> 43) & 1)
reg = self.cyc_reg(reg)

@ -68,7 +68,7 @@ class curses_terminal(threading.Thread):
self.maxy, self.maxx = self.stdscr.getmaxyx()
if (self.maxy < 6) or (self.maxx < 60):
sys.stderr.write("Terminal window too small! Minimum size [70 x 6], actual [%d x %d]\n" % (self.maxx, self.maxy))
print "Terminal window too small! Minimum size [70 x 6], actual [%d x %d]\n" % (self.maxx, self.maxy)
print ("Terminal window too small! Minimum size [70 x 6], actual [%d x %d]\n" % (self.maxx, self.maxy))
self.keep_running = False
return
@ -157,7 +157,7 @@ class curses_terminal(threading.Thread):
if c in COMMANDS.keys():
self.send_command(COMMANDS[c], 0)
elif c == ord('q'):
return True
return True
elif c == ord('t'):
if self.current_nac:
self.send_command('add_default_config', int(self.current_nac))
@ -220,7 +220,7 @@ class curses_terminal(threading.Thread):
self.top_bar.addstr(0, 0, s)
self.top_bar.refresh()
self.freq_list.erase()
for i in xrange(len(freqs)):
for i in range(len(freqs)):
if i > (self.maxy - 6):
break
s=msg[current_nac]['frequencies'][freqs[i]]
@ -346,7 +346,7 @@ class zeromq_terminal(threading.Thread):
class http_terminal(threading.Thread):
def __init__(self, input_q, output_q, endpoint, **kwds):
from http import http_server
from http_server import http_server
threading.Thread.__init__ (self, **kwds)
self.setDaemon(1)

@ -41,6 +41,16 @@ def crc16(dat,len): # slow version
crc = crc ^ 0xffff
return crc
def get_ordinals(s):
t = 0
if type(s) is not str and isinstance(s, bytes):
for c in s:
t = (t << 8) + c
else:
for c in s:
t = (t << 8) + ord(c)
return t
class trunked_system (object):
def __init__(self, debug=0, config=None):
self.debug = debug
@ -104,7 +114,7 @@ class trunked_system (object):
d['rxchan'] = self.rfss_chan
d['txchan'] = self.rfss_txchan
d['wacn'] = self.ns_wacn
d['secondary'] = self.secondary.keys()
d['secondary'] = [k for k in self.secondary.keys()]
d['tsbks'] = self.stats['tsbks']
d['frequencies'] = {}
d['frequency_data'] = {}
@ -120,7 +130,7 @@ class trunked_system (object):
d['frequencies'][f] = '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'])
d['frequency_data'][f] = {'tgids': self.voice_frequencies[f]['tgid'], 'last_activity': '%7.1f' % (t - self.voice_frequencies[f]['time']), 'counter': self.voice_frequencies[f]['counter']}
d['adjacent_data'] = self.adjacent_data
d['adjacent_data'] = self.adjacent_data
return json.dumps(d)
def to_string(self):
@ -608,7 +618,7 @@ class rx_ctl (object):
self.build_config_json(conf_file)
else:
self.build_config(conf_file)
self.nacs = self.configs.keys()
self.nacs = [int (x) for x in self.configs.keys()]
self.current_nac = self.find_next_tsys()
self.current_state = self.states.CC
@ -755,10 +765,10 @@ class rx_ctl (object):
return s
def process_qmsg(self, msg):
type = msg.type()
mtype = msg.type()
updated = 0
curr_time = time.time()
if type == -3: # P25 call signalling data
if mtype == -3: # P25 call signalling data
if self.debug > 10:
sys.stderr.write("%f process_qmsg: P25 info: %s\n" % (time.time(), msg.to_string()))
js = json.loads(msg.to_string())
@ -777,55 +787,50 @@ class rx_ctl (object):
if 'keyid' in js.keys():
tsys.current_keyid = js['keyid']
return
elif type == -2: # request from gui
elif mtype == -2: # request from gui
cmd = msg.to_string()
if self.debug > 10:
sys.stderr.write('process_qmsg: command: %s\n' % cmd)
self.update_state(cmd, curr_time)
return
elif type == -1: # timeout
elif mtype == -1: # timeout
if self.debug > 10:
sys.stderr.write('%f process_data_unit timeout\n' % time.time())
self.update_state('timeout', curr_time)
if self.logfile_workers:
self.logging_scheduler(curr_time)
return
elif type < 0:
sys.stderr.write('unknown message type %d\n' % (type))
elif mtype < 0:
sys.stderr.write('unknown message type %d\n' % (mtype))
return
s = msg.to_string()
# nac is always 1st two bytes
nac = (ord(s[0]) << 8) + ord(s[1])
nac = get_ordinals(s[:2])
#assert nac != 0xffff # FIXME: uncomment this after any remaining usages of 0xffff removed
if nac == 0xffff:
if (type != 7) and (type != 12): # TDMA duid (end of call etc)
self.update_state('tdma_duid%d' % type, curr_time)
if (mtype != 7) and (mtype != 12): # TDMA duid (end of call etc)
self.update_state('tdma_duid%d' % mtype, curr_time)
return
else: # voice channel derived TSBK or MBT PDU
nac = self.current_nac
s = s[2:]
if self.debug > 10:
sys.stderr.write('nac %x type %d at %f state %d len %d\n' %(nac, type, time.time(), self.current_state, len(s)))
if (type == 7 or type == 12) and nac not in self.trunked_systems:
sys.stderr.write('nac %x type %d at %f state %d len %d\n' %(nac, mtype, time.time(), self.current_state, len(s)))
if (mtype == 7 or mtype == 12) and nac not in self.trunked_systems:
if not self.configs:
# TODO: allow whitelist/blacklist rather than blind automatic-add
self.add_trunked_system(nac)
else:
sys.stderr.write("%f NAC %x not configured\n" % (time.time(), nac))
return
if type == 7: # trunk: TSBK
t = 0
for c in s:
t = (t << 8) + ord(c)
if mtype == 7: # trunk: TSBK
t = get_ordinals(s)
updated += self.trunked_systems[nac].decode_tsbk(t)
elif type == 12: # trunk: MBT
elif mtype == 12: # trunk: MBT
s1 = s[:10] # header without crc
s2 = s[12:]
header = mbt_data = 0
for c in s1:
header = (header << 8) + ord(c)
for c in s2:
mbt_data = (mbt_data << 8) + ord(c)
header = get_ordinals(s1)
mbt_data = get_ordinals(s2)
fmt = (header >> 72) & 0x1f
sap = (header >> 64) & 0x3f
@ -835,12 +840,15 @@ class rx_ctl (object):
opcode = (header >> 16) & 0x3f
if self.debug > 10:
sys.stderr.write('type %d at %f state %d len %d/%d opcode %x [%x/%x]\n' %(type, time.time(), self.current_state, len(s1), len(s2), opcode, header,mbt_data))
sys.stderr.write('type %d at %f state %d len %d/%d opcode %x [%x/%x]\n' %(mtype, time.time(), self.current_state, len(s1), len(s2), opcode, header,mbt_data))
updated += self.trunked_systems[nac].decode_mbt_data(opcode, src, header << 16, mbt_data << 32)
if nac != self.current_nac:
if self.debug > 10: # this is occasionally expected if cycling between different tsys
sys.stderr.write("%f received NAC %x does not match expected NAC %x\n" % (time.time(), nac, self.current_nac))
cnac = self.current_nac
if cnac is None:
cnac = 0
sys.stderr.write("%f received NAC %x does not match expected NAC %x\n" % (time.time(), nac, cnac))
return
if self.logfile_workers:
@ -850,7 +858,7 @@ class rx_ctl (object):
if updated:
self.update_state('update', curr_time)
else:
self.update_state('duid%d' % type, curr_time)
self.update_state('duid%d' % mtype, curr_time)
def find_available_worker(self):
for worker in self.logfile_workers:
@ -962,6 +970,9 @@ class rx_ctl (object):
if not self.configs:
return # run in "manual mode" if no conf
if type(command) is not str and isinstance(command, bytes):
command = command.decode()
nac = self.current_nac
tsys = self.trunked_systems[nac]
@ -1064,7 +1075,7 @@ class rx_ctl (object):
self.tgid_hold = self.current_tgid
self.tgid_hold_until = curr_time + 86400 * 10000
self.hold_mode = True
print 'set hold until %f' % self.tgid_hold_until
print ('set hold until %f' % self.tgid_hold_until)
elif command == 'unset_hold':
self.last_command = {'command': command, 'time': curr_time}
if self.current_tgid:

@ -42,14 +42,14 @@ def get_int_dict(s):
v = v.split("\t",1) # split on tab
try:
v0 = int(v[0]) # first parameter is tgid or start of tgid range
v1 = v0