aboutsummaryrefslogtreecommitdiffstats
path: root/python/gui
diff options
context:
space:
mode:
authorMatthias P. Braendli <matthias.braendli@mpb.li>2018-12-04 16:45:58 +0100
committerMatthias P. Braendli <matthias.braendli@mpb.li>2018-12-04 16:45:58 +0100
commit5cf52c74e9eb6bf8a82af4509ff3eb5106f928f9 (patch)
treea7edc1dfd2b2f4469f4dc4d760fdfa83a25fa710 /python/gui
parentd5cbe10c0e2298b0e40161607a3da158249bdb82 (diff)
downloaddabmod-5cf52c74e9eb6bf8a82af4509ff3eb5106f928f9.tar.gz
dabmod-5cf52c74e9eb6bf8a82af4509ff3eb5106f928f9.tar.bz2
dabmod-5cf52c74e9eb6bf8a82af4509ff3eb5106f928f9.zip
Rework GUI and DPDCE
Diffstat (limited to 'python/gui')
-rw-r--r--python/gui/README.md45
-rw-r--r--python/gui/__init__.py1
-rwxr-xr-xpython/gui/api.py (renamed from python/gui/api/__init__.py)54
-rw-r--r--python/gui/configuration.py44
-rwxr-xr-xpython/gui/run.py166
-rw-r--r--python/gui/ui-config.json9
-rw-r--r--python/gui/zmqrc.py84
7 files changed, 16 insertions, 387 deletions
diff --git a/python/gui/README.md b/python/gui/README.md
deleted file mode 100644
index ec55bc9..0000000
--- a/python/gui/README.md
+++ /dev/null
@@ -1,45 +0,0 @@
-ODR-DabMod Web UI
-=================
-
-Goals
------
-
-Enable users to play with digital predistortion settings, through a
-visualisation of the settings and the parameters.
-
-Make it easier to discover the tuning possibilities of the modulator.
-
-
-Install
--------
-
-Install dependencies: cherrypy, jinja2, scipy, matplotlib, zmq python modules
-
-Run
----
-
-1. Execute ODR-DabMod, configured with zmq rc on port 9400
-1. `cd gui`
-1. `./run.py`
-1. Connect your browser to `http://localhost:8099`
-
-Todo
-----
-
-* Integrate DPDCE
- * Show DPD settings and effect visually
- * Allow load/store of DPD settings
- * Make ports configurable
-* Use Feedback Server interface and make spectrum and constellation plots
-* Get authentication to work
-* Read and write config file, and add forms to change ODR-DabMod configuration
-* Connect to supervisord to be able to restart ODR-DabMod
-* Create a status page
- * Is process running?
- * Is modulator rate within bounds?
- * Are there underruns or late packets?
- * Is the GPSDO ok? (needs new RC params)
-* Think about how to show PA settings
- * Return loss is an important metric
- * Some PAs offer serial interfaces for supervision
-
diff --git a/python/gui/__init__.py b/python/gui/__init__.py
new file mode 100644
index 0000000..725f20d
--- /dev/null
+++ b/python/gui/__init__.py
@@ -0,0 +1 @@
+#gui module
diff --git a/python/gui/api/__init__.py b/python/gui/api.py
index 77faa10..ae54e8b 100755
--- a/python/gui/api/__init__.py
+++ b/python/gui/api.py
@@ -23,6 +23,8 @@
import cherrypy
from cherrypy.lib.httputil import parse_query_string
+from lib import yamlrpc
+
import json
import urllib
import os
@@ -43,36 +45,9 @@ def send_error(reason=""):
else:
return {'status' : 'error'}
-class RXThread(threading.Thread):
- def __init__(self, api):
- super(RXThread, self).__init__()
- self.api = api
- self.running = False
- self.daemon = True
-
- def cancel(self):
- self.running = False
-
- def run(self):
- self.running = True
- while self.running:
- if self.api.dpd_pipe.poll(1):
- rx = self.api.dpd_pipe.recv()
- if rx['cmd'] == "quit":
- break
- elif rx['cmd'] == "dpd-state":
- self.api.dpd_state = rx['data']
- elif rx['cmd'] == "dpd-calibration-result":
- self.api.calibration_result = rx['data']
-
class API:
- def __init__(self, mod_rc, dpd_pipe):
+ def __init__(self, mod_rc):
self.mod_rc = mod_rc
- self.dpd_pipe = dpd_pipe
- self.dpd_state = None
- self.calibration_result = None
- self.receive_thread = RXThread(self)
- self.receive_thread.start()
@cherrypy.expose
def index(self):
@@ -81,7 +56,10 @@ class API:
@cherrypy.expose
@cherrypy.tools.json_out()
def rc_parameters(self):
- return send_ok(self.mod_rc.get_modules())
+ try:
+ return send_ok(self.mod_rc.get_modules())
+ except IOError as e:
+ return send_error(str(e))
@cherrypy.expose
@cherrypy.tools.json_out()
@@ -92,6 +70,8 @@ class API:
params = json.loads(rawbody.decode())
try:
self.mod_rc.set_param_value(params['controllable'], params['param'], params['value'])
+ except IOError as e:
+ return send_error(str(e))
except ValueError as e:
cherrypy.response.status = 400
return send_error(str(e))
@@ -104,7 +84,7 @@ class API:
@cherrypy.tools.json_out()
def trigger_capture(self, **kwargs):
if cherrypy.request.method == 'POST':
- self.dpd_pipe.send({'cmd': "dpd-capture"})
+ # TODO dpd send capture
return send_ok()
else:
cherrypy.response.status = 400
@@ -113,20 +93,16 @@ class API:
@cherrypy.expose
@cherrypy.tools.json_out()
def dpd_status(self, **kwargs):
- if self.dpd_state is not None:
- return send_ok(self.dpd_state)
- else:
- return send_error("DPD state unknown")
+ # TODO Request DPD state
+ return send_error("DPD state unknown")
@cherrypy.expose
@cherrypy.tools.json_out()
def calibrate(self, **kwargs):
if cherrypy.request.method == 'POST':
- self.dpd_pipe.send({'cmd': "dpd-calibrate"})
+ # TODO dpd send capture
return send_ok()
else:
- if self.calibration_result is not None:
- return send_ok(self.calibration_result)
- else:
- return send_error("DPD calibration result unknown")
+ # Fetch dpd status
+ return send_error("DPD calibration result unknown")
diff --git a/python/gui/configuration.py b/python/gui/configuration.py
deleted file mode 100644
index 4af1abd..0000000
--- a/python/gui/configuration.py
+++ /dev/null
@@ -1,44 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-#
-# Copyright (C) 2018
-# Matthias P. Braendli, matthias.braendli@mpb.li
-#
-# http://www.opendigitalradio.org
-#
-# This file is part of ODR-DabMod.
-#
-# ODR-DabMod 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 of the
-# License, or (at your option) any later version.
-#
-# ODR-DabMod 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 ODR-DabMod. If not, see <http://www.gnu.org/licenses/>.
-import json
-
-class ConfigurationNotPresent:
- pass
-
-class Configuration:
- def __init__(self, configfilename="ui-config.json"):
- self.config = None
-
- try:
- fd = open(configfilename, "r")
- self.config = json.load(fd)
- except json.JSONDecodeError:
- pass
- except OSError:
- pass
-
- def get_key(self, key):
- if self.config is None:
- raise ConfigurationNotPresent()
- else:
- return self.config[key]
diff --git a/python/gui/run.py b/python/gui/run.py
deleted file mode 100755
index b83dd14..0000000
--- a/python/gui/run.py
+++ /dev/null
@@ -1,166 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-#
-# Copyright (C) 2018
-# Matthias P. Braendli, matthias.braendli@mpb.li
-#
-# http://www.opendigitalradio.org
-#
-# This file is part of ODR-DabMod.
-#
-# ODR-DabMod 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 of the
-# License, or (at your option) any later version.
-#
-# ODR-DabMod 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 ODR-DabMod. If not, see <http://www.gnu.org/licenses/>.
-
-import configuration
-from multiprocessing import Process, Pipe
-import os.path
-import cherrypy
-import argparse
-from jinja2 import Environment, FileSystemLoader
-from api import API
-import zmqrc
-import dpd
-
-env = Environment(loader=FileSystemLoader('templates'))
-
-base_js = ["js/odr.js"]
-
-class Root:
- def __init__(self, config_file, dpd_pipe):
- self.config_file = config_file
- self.conf = configuration.Configuration(self.config_file)
- self.mod_rc = zmqrc.ModRemoteControl("localhost")
- self.api = API(self.mod_rc, dpd_pipe)
-
- @cherrypy.expose
- def index(self):
- raise cherrypy.HTTPRedirect('/home')
-
- @cherrypy.expose
- def about(self):
- tmpl = env.get_template("about.html")
- return tmpl.render(tab='about', js=base_js, is_login=False)
-
- @cherrypy.expose
- def home(self):
- tmpl = env.get_template("home.html")
- return tmpl.render(tab='home', js=base_js, is_login=False)
-
- @cherrypy.expose
- def rcvalues(self):
- tmpl = env.get_template("rcvalues.html")
- js = base_js + ["js/odr-rcvalues.js"]
- return tmpl.render(tab='rcvalues', js=js, is_login=False)
-
- @cherrypy.expose
- def modulator(self):
- tmpl = env.get_template("modulator.html")
- js = base_js + ["js/odr-modulator.js"]
- return tmpl.render(tab='modulator', js=js, is_login=False)
-
- @cherrypy.expose
- def predistortion(self):
- tmpl = env.get_template("predistortion.html")
- js = base_js + ["js/odr-predistortion.js"]
- return tmpl.render(tab='predistortion', js=js, is_login=False)
-
-class DPDRunner:
- def __init__(self, static_dir):
- self.web_end, self.dpd_end = Pipe()
- self.dpd = dpd.DPD(static_dir)
-
- def __enter__(self):
- self.p = Process(target=self._handle_messages)
- self.p.start()
- return self.web_end
-
- def _handle_messages(self):
- while True:
- rx = self.dpd_end.recv()
- if rx['cmd'] == "quit":
- break
- elif rx['cmd'] == "dpd-capture":
- self.dpd.capture_samples()
- elif rx['cmd'] == "dpd-calibrate":
- self.dpd_end.send({'cmd': "dpd-calibration-result",
- 'data': self.dpd.capture_calibration()})
-
-
- def __exit__(self, exc_type, exc_value, traceback):
- self.web_end.send({'cmd': "quit"})
- self.p.join()
- return False
-
-if __name__ == '__main__':
- parser = argparse.ArgumentParser(description='ODR-DabMod Web GUI')
- parser.add_argument('-c', '--config',
- default="ui-config.json",
- help='configuration filename')
- cli_args = parser.parse_args()
-
- config = configuration.Configuration(cli_args.config)
- if config.config is None:
- print("Configuration file is missing or is not readable - {}".format(cli_args.config))
- sys.exit(1)
-
- if config.config['global']['daemon']:
- cherrypy.process.plugins.Daemonizer(cherrypy.engine).subscribe()
-
- accesslog = os.path.realpath(os.path.join(config.config['global']['logs_directory'], 'access.log'))
- errorlog = os.path.realpath(os.path.join(config.config['global']['logs_directory'], 'error.log'))
-
- cherrypy.config.update({
- 'engine.autoreload.on': True,
- 'server.socket_host': config.config['global']['host'],
- 'server.socket_port': int(config.config['global']['port']),
- 'request.show_tracebacks' : True,
- 'tools.sessions.on': False,
- 'tools.encode.on': True,
- 'tools.encode.encoding': "utf-8",
- 'log.access_file': accesslog,
- 'log.error_file': errorlog,
- 'log.screen': True,
- })
-
- staticdir = os.path.realpath(config.config['global']['static_directory'])
-
- with DPDRunner(os.path.join(staticdir, "dpd")) as dpd_pipe:
- cherrypy.tree.mount(
- Root(cli_args.config, dpd_pipe), config={
- '/': { },
- '/dpd': {
- 'tools.staticdir.on': True,
- 'tools.staticdir.dir': os.path.join(staticdir, u"dpd/")
- },
- '/css': {
- 'tools.staticdir.on': True,
- 'tools.staticdir.dir': os.path.join(staticdir, u"css/")
- },
- '/js': {
- 'tools.staticdir.on': True,
- 'tools.staticdir.dir': os.path.join(staticdir, u"js/")
- },
- '/fonts': {
- 'tools.staticdir.on': True,
- 'tools.staticdir.dir': os.path.join(staticdir, u"fonts/")
- },
- '/favicon.ico': {
- 'tools.staticfile.on': True,
- 'tools.staticfile.filename': os.path.join(staticdir, u"fonts/favicon.ico")
- },
- }
- )
-
- cherrypy.engine.start()
- cherrypy.engine.block()
-
diff --git a/python/gui/ui-config.json b/python/gui/ui-config.json
deleted file mode 100644
index 070290c..0000000
--- a/python/gui/ui-config.json
+++ /dev/null
@@ -1,9 +0,0 @@
-{
- "global": {
- "daemon": false,
- "host": "127.0.0.1",
- "port": "8099",
- "logs_directory": "logs",
- "static_directory": "static"
- }
-}
diff --git a/python/gui/zmqrc.py b/python/gui/zmqrc.py
deleted file mode 100644
index 3897d7a..0000000
--- a/python/gui/zmqrc.py
+++ /dev/null
@@ -1,84 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-#
-# Copyright (C) 2018
-# Matthias P. Braendli, matthias.braendli@mpb.li
-#
-# http://www.opendigitalradio.org
-#
-# This file is part of ODR-DabMod.
-#
-# ODR-DabMod 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 of the
-# License, or (at your option) any later version.
-#
-# ODR-DabMod 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 ODR-DabMod. If not, see <http://www.gnu.org/licenses/>.
-import zmq
-import json
-
-class ModRemoteControl(object):
- """Interact with ODR-DabMod using the ZMQ RC"""
- def __init__(self, mod_host, mod_port=9400):
- self._host = mod_host
- self._port = mod_port
- self._ctx = zmq.Context()
-
- def _read(self, message_parts):
- sock = zmq.Socket(self._ctx, zmq.REQ)
- sock.setsockopt(zmq.LINGER, 0)
- sock.connect("tcp://{}:{}".format(self._host, self._port))
-
- for i, part in enumerate(message_parts):
- if i == len(message_parts) - 1:
- f = 0
- else:
- f = zmq.SNDMORE
- sock.send(part.encode(), flags=f)
-
- # use poll for timeouts:
- poller = zmq.Poller()
- poller.register(sock, zmq.POLLIN)
- if poller.poll(5*1000): # 5s timeout in milliseconds
- recv = sock.recv_multipart()
- sock.close()
- return [r.decode() for r in recv]
- else:
- raise IOError("Timeout processing ZMQ request")
-
- def get_modules(self):
- modules = {}
-
- for mod in [json.loads(j) for j in self._read(['list'])]:
- params = {}
- pv_list = self._read(['show', mod['name']])
-
- for pv in pv_list:
- p, _, v = pv.partition(": ")
- params[p] = {"value": v.strip()}
-
- for p in mod['params']:
- if p in params:
- params[p]["help"] = mod['params'][p]
- modules[mod['name']] = params
-
- return modules
-
- def get_param_value(self, module, param):
- value = self._read(['get', module, param])
- if value[0] == 'fail':
- raise ValueError("Error getting param: {}".format(value[1]))
- else:
- return value[0]
-
- def set_param_value(self, module, param, value):
- ret = self._read(['set', module, param, value])
- if ret[0] == 'fail':
- raise ValueError("Error setting param: {}".format(ret[1]))
-