aboutsummaryrefslogtreecommitdiffstats
path: root/gui
diff options
context:
space:
mode:
authorMatthias P. Braendli <matthias.braendli@mpb.li>2018-11-28 09:38:05 +0100
committerMatthias P. Braendli <matthias.braendli@mpb.li>2018-11-28 09:38:05 +0100
commitee435c029eac59e0399dc3ae765cc74d66b9442e (patch)
treecdd01efdb5f137a924559dcfbe55833247fc1493 /gui
parent64898e72aacd26d1dfb3b925fab571d658ad5af4 (diff)
downloaddabmod-ee435c029eac59e0399dc3ae765cc74d66b9442e.tar.gz
dabmod-ee435c029eac59e0399dc3ae765cc74d66b9442e.tar.bz2
dabmod-ee435c029eac59e0399dc3ae765cc74d66b9442e.zip
GUI: Use cherry bus to communicate internally
Diffstat (limited to 'gui')
-rwxr-xr-xgui/api/__init__.py55
-rw-r--r--gui/dpd/Capture.py30
-rw-r--r--gui/dpd/__init__.py20
-rwxr-xr-xgui/run.py21
-rw-r--r--gui/static/js/odr-predistortion.js29
-rw-r--r--gui/templates/predistortion.html17
6 files changed, 134 insertions, 38 deletions
diff --git a/gui/api/__init__.py b/gui/api/__init__.py
index cef81c6..a535eb3 100755
--- a/gui/api/__init__.py
+++ b/gui/api/__init__.py
@@ -20,8 +20,8 @@
# 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
import cherrypy
+from cherrypy.process import wspbus, plugins
from cherrypy.lib.httputil import parse_query_string
import urllib
@@ -30,30 +30,47 @@ import os
import io
import datetime
-def send_ok(data):
- return json.dumps({'status' : 'ok', 'data': data}).encode()
+def send_ok(data=None):
+ if data is not None:
+ return {'status' : 'ok', 'data': data}
+ else:
+ return {'status': 'ok'}
def send_error(reason=""):
- return json.dumps({'status' : 'error', 'reason': reason}).encode()
+ if reason:
+ return {'status' : 'error', 'reason': reason}
+ else:
+ return {'status' : 'error'}
-class API:
- def __init__(self, mod_rc, dpd):
+class API(plugins.SimplePlugin):
+ def __init__(self, mod_rc, bus):
+ plugins.SimplePlugin.__init__(self, bus)
self.mod_rc = mod_rc
- self.dpd = dpd
+ self.dpd_state = None
+
+ def start(self):
+ self.bus.subscribe("dpd-state", self.dpd_state)
+
+ def stop(self):
+ self.bus.unsubscribe("dpd-state", self.dpd_state)
+
+ def dpd_state(self, new_state):
+ print("API got new dpd-state {}".format(new_state))
+ self.dpd_state = new_state
@cherrypy.expose
def index(self):
return """This is the api area."""
@cherrypy.expose
+ @cherrypy.tools.json_out()
def rc_parameters(self):
- cherrypy.response.headers["Content-Type"] = "application/json"
return send_ok(self.mod_rc.get_modules())
@cherrypy.expose
+ @cherrypy.tools.json_out()
def parameter(self, **kwargs):
if cherrypy.request.method == 'POST':
- cherrypy.response.headers["Content-Type"] = "application/json"
cl = cherrypy.request.headers['Content-Length']
rawbody = cherrypy.request.body.read(int(cl))
params = json.loads(rawbody)
@@ -62,26 +79,26 @@ class API:
except ValueError as e:
cherrypy.response.status = 400
return send_error(str(e))
- return send_ok(None)
+ return send_ok()
else:
- cherrypy.response.headers["Content-Type"] = "application/json"
cherrypy.response.status = 400
return send_error("POST only")
@cherrypy.expose
+ @cherrypy.tools.json_out()
def trigger_capture(self, **kwargs):
if cherrypy.request.method == 'POST':
- cherrypy.response.headers["Content-Type"] = "application/json"
- try:
- return send_ok(self.dpd.capture_samples())
- except ValueError as e:
- return send_error(str(e))
+ cherrypy.engine.publish('dpd-capture', None)
+ return send_ok()
else:
- cherrypy.response.headers["Content-Type"] = "application/json"
cherrypy.response.status = 400
return send_error("POST only")
@cherrypy.expose
+ @cherrypy.tools.json_out()
def dpd_status(self, **kwargs):
- cherrypy.response.headers["Content-Type"] = "application/json"
- return send_ok(self.dpd.status())
+ if self.dpd_state is not None:
+ return send_ok(self.dpd_state)
+ else:
+ return send_error("DPD state unknown")
+
diff --git a/gui/dpd/Capture.py b/gui/dpd/Capture.py
index de428cb..e2ac63d 100644
--- a/gui/dpd/Capture.py
+++ b/gui/dpd/Capture.py
@@ -29,6 +29,10 @@ import os
import logging
import numpy as np
from scipy import signal
+import matplotlib
+matplotlib.use('Agg')
+import matplotlib.pyplot as plt
+import io
from . import Align as sa
@@ -77,10 +81,11 @@ class Capture:
self.binning_n_bins = 64 # Number of bins between binning_start and binning_end
self.binning_n_per_bin = 128 # Number of measurements pre bin
- self.target_median = 0.01
+ self.target_median = 0.05
self.median_max = self.target_median * 1.4
self.median_min = self.target_median / 1.4
+ # axis 0: bins
# axis 1: 0=tx, 1=rx
self.accumulated_bins = [np.zeros((0, 2), dtype=np.complex64) for i in range(self.binning_n_bins)]
@@ -179,6 +184,29 @@ class Capture:
def bin_histogram(self):
return [b.shape[0] for b in self.accumulated_bins]
+ def pointcloud_png(self):
+ fig = plt.figure()
+ ax = plt.subplot(1, 1, 1)
+ for b in self.accumulated_bins:
+ if b:
+ ax.scatter(
+ np.abs(b[0]),
+ np.abs(b[1]),
+ s=0.1,
+ color="black")
+ ax.set_title("Captured and Binned Samples")
+ ax.set_xlabel("TX Amplitude")
+ ax.set_ylabel("RX Amplitude")
+ ax.set_ylim(0, 0.8)
+ ax.set_xlim(0, 1.1)
+ ax.legend(loc=4)
+ fig.tight_layout()
+ buf = io.BytesIO()
+ fig.savefig(buf)
+ plt.close(fig)
+
+ return buf.getvalue()
+
def _bin_and_accumulate(self, txframe, rxframe):
"""Bin the samples and extend the accumulated samples"""
diff --git a/gui/dpd/__init__.py b/gui/dpd/__init__.py
index 8dd0807..06d180d 100644
--- a/gui/dpd/__init__.py
+++ b/gui/dpd/__init__.py
@@ -47,19 +47,25 @@ class DPD:
r['capture'] = self.last_capture_info
return r
+ def pointcloud_png(self):
+ return self.capture.pointcloud_png()
+
def capture_samples(self):
"""Captures samples and store them in the accumulated samples,
returns a dict with some info"""
+ result = {}
try:
txframe_aligned, tx_ts, tx_median, rxframe_aligned, rx_ts, rx_median = self.capture.get_samples()
- self.last_capture_info['length'] = len(txframe_aligned)
- self.last_capture_info['tx_median'] = float(tx_median)
- self.last_capture_info['rx_median'] = float(rx_median)
- self.last_capture_info['tx_ts'] = tx_ts
- self.last_capture_info['rx_ts'] = rx_ts
- return self.last_capture_info
+ result['status'] = "ok"
+ result['length'] = len(txframe_aligned)
+ result['tx_median'] = float(tx_median)
+ result['rx_median'] = float(rx_median)
+ result['tx_ts'] = tx_ts
+ result['rx_ts'] = rx_ts
except ValueError as e:
- raise ValueError("Capture failed: {}".format(e))
+ result['status'] = "Capture failed: {}".format(e)
+
+ self.last_capture_info = result
# tx, rx, phase_diff, n_per_bin = extStat.extract(txframe_aligned, rxframe_aligned)
# off = SA.calc_offset(txframe_aligned)
diff --git a/gui/run.py b/gui/run.py
index 3646b2c..b4af742 100755
--- a/gui/run.py
+++ b/gui/run.py
@@ -24,6 +24,7 @@
import configuration
import os.path
import cherrypy
+from cherrypy.process import wspbus, plugins
import argparse
from jinja2 import Environment, FileSystemLoader
from api import API
@@ -39,8 +40,8 @@ class Root:
self.config_file = config_file
self.conf = configuration.Configuration(self.config_file)
self.mod_rc = zmqrc.ModRemoteControl("localhost")
- self.dpd = dpd.DPD()
- self.api = API(self.mod_rc, self.dpd)
+ self.api = API(self.mod_rc, cherrypy.engine)
+ self.api.subscribe()
@cherrypy.expose
def index(self):
@@ -74,6 +75,20 @@ class Root:
js = base_js + ["js/odr-predistortion.js"]
return tmpl.render(tab='predistortion', js=js, is_login=False)
+class DPDPlugin(plugins.SimplePlugin):
+ def __init__(self, bus):
+ plugins.SimplePlugin.__init__(self, bus)
+ self.dpd = dpd.DPD()
+
+ def start(self):
+ self.bus.subscribe("dpd-capture", self.trigger_capture)
+
+ def stop(self):
+ self.bus.unsubscribe("dpd-capture", self.trigger_capture)
+
+ def trigger_capture(self, param):
+ print("trigger_capture({})".format(param))
+
if __name__ == '__main__':
parser = argparse.ArgumentParser(description='ODR-DabMod Web GUI')
parser.add_argument('-c', '--config',
@@ -107,6 +122,8 @@ if __name__ == '__main__':
staticdir = os.path.realpath(config.config['global']['static_directory'])
+ DPDPlugin(cherrypy.engine).subscribe()
+
cherrypy.tree.mount(
Root(cli_args.config), config={
'/': { },
diff --git a/gui/static/js/odr-predistortion.js b/gui/static/js/odr-predistortion.js
index 6b09202..4e86f1c 100644
--- a/gui/static/js/odr-predistortion.js
+++ b/gui/static/js/odr-predistortion.js
@@ -22,9 +22,6 @@ $(function(){
$('#capturebutton').click(function() {
doApiRequestPOST("/api/trigger_capture", {}, function(data) {
console.log("trigger_capture succeeded: " + JSON.stringify(data));
- $('#capturelength').text(data.length);
- $('#tx_median').text(data.tx_median);
- $('#rx_median').text(data.rx_median);
});
});
@@ -32,7 +29,33 @@ $(function(){
doApiRequestGET("/api/dpd_status", function(data) {
console.log("dpd_status succeeded: " + JSON.stringify(data));
$('#histogram').text(data.histogram);
+ $('#capturestatus').text(data.capture.status);
+ $('#capturelength').text(data.capture.length);
+ $('#tx_median').text(data.capture.tx_median);
+ $('#rx_median').text(data.capture.rx_median);
});
+
+ $.ajax({
+ type: "GET",
+ url: "/api/dpd_capture_pointcloud",
+
+ error: function(data) {
+ if (data.status == 500) {
+ var errorWindow = window.open("", "_self");
+ errorWindow.document.write(data.responseText);
+ }
+ else {
+ $.gritter.add({ title: 'API',
+ text: "AJAX failed: " + data.statusText,
+ image: '/fonts/warning.png',
+ sticky: true,
+ });
+ }
+ },
+ success: function(data) {
+ $('#dpd_pointcloud').value(data)
+ }
+ })
});
});
diff --git a/gui/templates/predistortion.html b/gui/templates/predistortion.html
index 8d5f1a5..ed224d8 100644
--- a/gui/templates/predistortion.html
+++ b/gui/templates/predistortion.html
@@ -31,19 +31,24 @@ along with ODR-DabMod. If not, see <http://www.gnu.org/licenses/>.
<div class="panel panel-default">
<div class="panel-body">
<h3>Capture</h3>
- <div>Number of samples captured: <span id="capturelength">None</span></div>
- <div>TX median: <span id="tx_median">N/A</span></div>
- <div>RX median: <span id="rx_median">N/A</span></div>
-
<div>On pressing this button,
the DPDCE will trigger a capture and a quick data
analysis, without updating any DPD models.</div>
- <button type="button" class="btn btn-sm btn-info" id="capturebutton">Capture</button>
+ <button type="button" class="btn btn-sm btn-info" id="capturebutton">
+ Capture</button>
</div>
<div class="panel-body">
<h3>Status</h3>
- <button type="button" class="btn btn-sm btn-info" id="dpdstatusbutton">Update</button>
+ <button type="button" class="btn btn-sm btn-info" id="dpdstatusbutton">
+ Update</button>
<div>Histogram: <span id="histogram">N/A</span></div>
+ <div>Capture status
+ <span id="capturestatus">N/A</span></div>
+ <div>Number of samples captured:
+ <span id="capturelength">None</span></div>
+ <div>TX median: <span id="tx_median">N/A</span></div>
+ <div>RX median: <span id="rx_median">N/A</span></div>
+ <div>Point cloud: <img id="dpd_pointcloud" /></div>
</div>
</div>
</div>