From abf9b4f06c95f864187c7a2f01141bfadfe46191 Mon Sep 17 00:00:00 2001 From: Max Date: Fri, 16 Mar 2018 16:26:42 -0400 Subject: [PATCH] configuration additions --- op25/gr-op25_repeater/apps/http.py | 158 +++++++++++++++++++++++++++-- 1 file changed, 150 insertions(+), 8 deletions(-) mode change 100644 => 100755 op25/gr-op25_repeater/apps/http.py diff --git a/op25/gr-op25_repeater/apps/http.py b/op25/gr-op25_repeater/apps/http.py old mode 100644 new mode 100755 index 9196005..2af7e96 --- a/op25/gr-op25_repeater/apps/http.py +++ b/op25/gr-op25_repeater/apps/http.py @@ -1,3 +1,4 @@ +#! /usr/bin/env python # Copyright 2017, 2018 Max H. Parke KA1RBI # @@ -26,14 +27,20 @@ import json import socket import traceback import threading +import glob from gnuradio import gr from waitress.server import create_server +from optparse import OptionParser +from multi_rx import byteify +from rx import p25_rx_block my_input_q = None my_output_q = None my_recv_q = None my_port = None +my_backend = None +CFG_DIR = '../www/config/' """ fake http and ajax server module @@ -63,20 +70,70 @@ def static_file(environ, start_response): 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 do_request(d): + global my_backend + TSV_DIR = './' + 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': + filename = '%s%s.json' % (CFG_DIR, d['data']) + if not os.access(filename, os.R_OK): + return + 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) - for d in data: - msg = gr.message().make_from_string(str(d['command']), -2, d['data'], 0) - my_output_q.insert_tail(msg) - time.sleep(0.2) 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 + 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) - resp_msg = [] while not my_recv_q.empty_p(): msg = my_recv_q.delete_head() if msg.type() == -4: @@ -124,9 +181,12 @@ def process_qmsg(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) + if endpoint == 'internal': + return + else: + 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) @@ -152,3 +212,85 @@ class queue_watcher(threading.Thread): while(self.keep_running): msg = self.msgq.delete_head() self.callback(msg) + +class Backend(threading.Thread): + def __init__(self, options, input_q, output_q, **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.start() + + def process_msg(self, msg): + msg = json.loads(msg.to_string()) + if msg['command'] == 'rx-start': + options = rx_options(msg['data']) + options.verbosity = self.verbosity + options._js_config['config-rx-data'] = {'input_q': self.input_q, 'output_q': self.output_q} + self.tb = p25_rx_block(options) + + def run(self): + while self.keep_running: + msg = self.input_q.delete_head() + self.process_msg(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): + 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]) + for k in 'demod_type filter_type'.split(): + setattr(self, k, chan[k]) + 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 = None + self.terminal_type = None + self._js_config = config + +def http_main(): + global my_backend + # command line argument parsing + parser = OptionParser() + parser.add_option("-c", "--config-file", type="string", default=None, help="specify config file name") + 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") + (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) + server = http_server(input_q, output_q, options.endpoint) + + server.run() + +if __name__ == '__main__': + http_main()