aboutsummaryrefslogtreecommitdiffstats
path: root/python
diff options
context:
space:
mode:
authorMatthias P. Braendli <matthias.braendli@mpb.li>2018-12-05 11:19:07 +0100
committerMatthias P. Braendli <matthias.braendli@mpb.li>2018-12-05 11:19:07 +0100
commit31b65e41043900c0cadd80961f4b22cdfc171e7d (patch)
treecdeceac026a2d1e0fe8c00af5d0f867767d17ef4 /python
parent5cf52c74e9eb6bf8a82af4509ff3eb5106f928f9 (diff)
downloaddabmod-31b65e41043900c0cadd80961f4b22cdfc171e7d.tar.gz
dabmod-31b65e41043900c0cadd80961f4b22cdfc171e7d.tar.bz2
dabmod-31b65e41043900c0cadd80961f4b22cdfc171e7d.zip
Get GUI to communicate with DPDCE
Diffstat (limited to 'python')
-rw-r--r--python/dpd/GlobalConfig.py9
-rw-r--r--python/dpd/RX_Agc.py45
-rwxr-xr-xpython/dpdce.py48
-rwxr-xr-xpython/gui.py7
-rwxr-xr-xpython/gui/api.py56
-rw-r--r--python/gui/static/js/odr-predistortion.js17
-rw-r--r--python/gui/static/js/odr.js16
-rw-r--r--python/gui/templates/predistortion.html10
-rw-r--r--python/lib/yamlrpc.py58
-rw-r--r--python/poly.coef12
10 files changed, 220 insertions, 58 deletions
diff --git a/python/dpd/GlobalConfig.py b/python/dpd/GlobalConfig.py
index abd7442..873b6ac 100644
--- a/python/dpd/GlobalConfig.py
+++ b/python/dpd/GlobalConfig.py
@@ -10,9 +10,9 @@
import numpy as np
class GlobalConfig:
- def __init__(self, args, plot_location: str):
- self.sample_rate = args['samplerate']
- assert self.sample_rate == 8192000 # By now only constants for 8192000
+ def __init__(self, samplerate, plot_location: str):
+ self.sample_rate = samplerate
+ assert self.sample_rate == 8192000, "We only support constants for 8192000 sample rate: {}".format(self.sample_rate)
self.plot_location = plot_location
plot = len(plot_location) > 0
@@ -77,12 +77,13 @@ class GlobalConfig:
# Constants for RX_AGC
self.RAGC_min_rxgain = 25 # USRP B200 specific
+ self.RAGC_max_rxgain = 65 # USRP B200 specific
self.RAGC_rx_median_target = 0.05
# The MIT License (MIT)
#
# Copyright (c) 2017 Andreas Steger
-# Copyright (c) 2017 Matthias P. Braendli
+# Copyright (c) 2018 Matthias P. Braendli
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
diff --git a/python/dpd/RX_Agc.py b/python/dpd/RX_Agc.py
index 0cc18b8..2a2f548 100644
--- a/python/dpd/RX_Agc.py
+++ b/python/dpd/RX_Agc.py
@@ -13,6 +13,7 @@ import numpy as np
import matplotlib
matplotlib.use('agg')
import matplotlib.pyplot as plt
+from typing import Tuple
import dpd.Adapt as Adapt
import dpd.Measure as Measure
@@ -39,31 +40,39 @@ class Agc:
self.measure = measure
self.adapt = adapt
self.min_rxgain = c.RAGC_min_rxgain
+ self.max_rxgain = c.RAGC_max_rxgain
self.rxgain = self.min_rxgain
self.peak_to_median = 1./c.RAGC_rx_median_target
- def run(self):
+ def run(self) -> Tuple[bool, str]:
self.adapt.set_rxgain(self.rxgain)
- for i in range(2):
- # Measure
- txframe_aligned, tx_ts, rxframe_aligned, rx_ts, rx_median= \
- self.measure.get_samples()
-
- # Estimate Maximum
- rx_peak = self.peak_to_median * rx_median
- correction_factor = 20*np.log10(1/rx_peak)
- self.rxgain = self.rxgain + correction_factor
-
- assert self.min_rxgain <= self.rxgain, ("Desired RX Gain is {} which is smaller than the minimum of {}".format(
- self.rxgain, self.min_rxgain))
-
- logging.info("RX Median {:1.4f}, estimated peak {:1.4f}, correction factor {:1.4f}, new RX gain {:1.4f}".format(
- rx_median, rx_peak, correction_factor, self.rxgain
- ))
-
+ # Measure
+ txframe_aligned, tx_ts, rxframe_aligned, rx_ts, rx_median=self.measure.get_samples()
+
+ # Estimate Maximum
+ rx_peak = self.peak_to_median * rx_median
+ correction_factor = 20*np.log10(1/rx_peak)
+ self.rxgain = self.rxgain + correction_factor
+
+ measurements = "RX Median {:1.4f}, estimated peak {:1.4f}, correction factor {:1.4f}, new RX gain {:1.4f}".format(
+ rx_median, rx_peak, correction_factor, self.rxgain)
+ logging.info(measurements)
+
+ if self.rxgain < self.min_rxgain:
+ w = "Warning: calculated RX Gain={} is lower than minimum={}. RX feedback power is too high!".format(
+ self.rxgain, self.min_rxgain)
+ logging.warning(w)
+ return (False, "\n".join([measurements, w]))
+ elif self.rxgain > self.max_rxgain:
+ w = "Warning: calculated RX Gain={} is higher than maximum={}. RX feedback power should be increased.".format(
+ self.rxgain, self.max_rxgain)
+ logging.warning(w)
+ return (False, "\n".join([measurements, w]))
+ else:
self.adapt.set_rxgain(self.rxgain)
time.sleep(0.5)
+ return (True, measurements)
def plot_estimates(self):
"""Plots the estimate of for Max, Median, Mean for different
diff --git a/python/dpdce.py b/python/dpdce.py
index da1b6fb..efc69ef 100755
--- a/python/dpdce.py
+++ b/python/dpdce.py
@@ -37,11 +37,11 @@ config = allconfig['dpdce']
# removed options:
# txgain, rxgain, digital_gain, target_median, iterations, lut, enable-txgain-agc, plot, measure
-control_port = config['control_port']
-dpd_port = config['dpd_port']
-rc_port = config['rc_port']
-samplerate = config['samplerate']
-samps = config['samps']
+control_port = config.getint('control_port')
+dpd_port = config.getint('dpd_port')
+rc_port = config.getint('rc_port')
+samplerate = config.getint('samplerate')
+samps = config.getint('samps')
coef_file = config['coef_file']
log_folder = config['log_folder']
@@ -96,7 +96,7 @@ from dpd.GlobalConfig import GlobalConfig
from dpd.MER import MER
from dpd.Measure_Shoulders import Measure_Shoulders
-c = GlobalConfig(config, logging_path)
+c = GlobalConfig(samplerate, logging_path)
symbol_align = Symbol_align(c)
mer = MER(c)
meas_shoulders = Measure_Shoulders(c)
@@ -125,7 +125,7 @@ if cli_args.reset:
logging.info("DPD Settings were reset to default values.")
sys.exit(0)
-cmd_socket = yamlrpc.Socket(bind_port=config.getint(control_port))
+cmd_socket = yamlrpc.Socket(bind_port=control_port)
# The following is accessed by both threads and need to be locked
settings = {
@@ -138,6 +138,7 @@ results = {
'tx_median': 0,
'rx_median': 0,
'state': 'idle',
+ 'summary': 'DPD has not been calibrated yet',
}
lock = Lock()
command_queue = Queue(maxsize=1)
@@ -156,7 +157,11 @@ def engine_worker():
with lock:
results['state'] = 'rx agc'
- agc.run()
+ agc_success, agc_summary = agc.run()
+ summary = ["First calibration run: " + agc_summary]
+ if agc_success:
+ agc_success, agc_summary = agc.run()
+ summary.append("Second calibration run: " + agc_summary)
txframe_aligned, tx_ts, rxframe_aligned, rx_ts, rx_median = self.measure.get_samples()
@@ -166,6 +171,7 @@ def engine_worker():
results['tx_median'] = tx_median
results['rx_median'] = rx_median
results['state'] = 'idle'
+ results['summary'] = "Calibration was done:\n" + "\n".join(agc_summary)
finally:
with lock:
@@ -177,9 +183,27 @@ engine.start()
try:
while True:
- addr, msg_id, method, params = cmd_socket.receive_request()
-
- if method == 'get_settings':
+ try:
+ addr, msg_id, method, params = cmd_socket.receive_request()
+ except ValueError as e:
+ logging.warning('YAML-RPC request error: {}'.format(e))
+ continue
+ except TimeoutError:
+ continue
+ except:
+ logging.error('YAML-RPC unknown error')
+ break
+
+ logging.info('YAML-RPC request : {}'.format(method))
+
+ if method == 'trigger_run':
+ command_queue.put('trigger_run')
+ elif method == 'reset':
+ command_queue.put('reset')
+ elif method == 'set_setting':
+ # params == {'setting': ..., 'value': ...}
+ pass
+ elif method == 'get_settings':
with lock:
cmd_socket.send_success_response(addr, msg_id, settings)
elif method == 'get_results':
@@ -187,6 +211,8 @@ try:
cmd_socket.send_success_response(addr, msg_id, results)
elif method == 'calibrate':
command_queue.put('calibrate')
+ elif method == "get_calibration_result":
+ pass
else:
cmd_socket.send_error_response(addr, msg_id, "request not understood")
finally:
diff --git a/python/gui.py b/python/gui.py
index 512afef..ce7948c 100755
--- a/python/gui.py
+++ b/python/gui.py
@@ -35,9 +35,9 @@ env = Environment(loader=FileSystemLoader('gui/templates'))
base_js = ["js/odr.js"]
class Root:
- def __init__(self):
+ def __init__(self, dpd_port):
self.mod_rc = zmqrc.ModRemoteControl("localhost")
- self.api = API(self.mod_rc)
+ self.api = API(self.mod_rc, dpd_port)
@cherrypy.expose
def index(self):
@@ -81,6 +81,7 @@ if __name__ == '__main__':
allconfig = configparser.ConfigParser()
allconfig.read(cli_args.config)
config = allconfig['gui']
+ dpd_port = allconfig['dpdce'].getint('control_port')
daemon = False
if daemon:
@@ -105,7 +106,7 @@ if __name__ == '__main__':
staticdir = os.path.realpath(config['static_directory'])
cherrypy.tree.mount(
- Root(), config={
+ Root(dpd_port), config={
'/': { },
'/dpd': {
'tools.staticdir.on': True,
diff --git a/python/gui/api.py b/python/gui/api.py
index ae54e8b..0d24bac 100755
--- a/python/gui/api.py
+++ b/python/gui/api.py
@@ -46,8 +46,10 @@ def send_error(reason=""):
return {'status' : 'error'}
class API:
- def __init__(self, mod_rc):
+ def __init__(self, mod_rc, dpd_port):
self.mod_rc = mod_rc
+ self.dpd_port = dpd_port
+ self.dpd_rpc = yamlrpc.Socket(bind_port=0)
@cherrypy.expose
def index(self):
@@ -71,38 +73,66 @@ class API:
try:
self.mod_rc.set_param_value(params['controllable'], params['param'], params['value'])
except IOError as e:
+ cherrypy.response.status = 503
return send_error(str(e))
except ValueError as e:
- cherrypy.response.status = 400
+ cherrypy.response.status = 503
return send_error(str(e))
return send_ok()
else:
cherrypy.response.status = 400
return send_error("POST only")
+ def _wrap_dpd(self, method, data=None):
+ try:
+ reply = self.dpd_rpc.call_rpc_method(self.dpd_port, method, data)
+ return send_ok(reply)
+ except ValueError as e:
+ cherrypy.response.status = 503
+ return send_error("YAML-RPC call error: {}".format(e))
+ except TimeoutError as e:
+ cherrypy.response.status = 503
+ return send_error("YAML-RPC timeout: {}".format(e))
+ cherrypy.response.status = 500
+ return send_error("YAML-RPC unknown error")
+
@cherrypy.expose
@cherrypy.tools.json_out()
- def trigger_capture(self, **kwargs):
+ def dpd_trigger_run(self, **kwargs):
if cherrypy.request.method == 'POST':
- # TODO dpd send capture
- return send_ok()
+ return self._wrap_dpd("trigger_run")
else:
cherrypy.response.status = 400
return send_error("POST only")
@cherrypy.expose
@cherrypy.tools.json_out()
- def dpd_status(self, **kwargs):
- # TODO Request DPD state
- return send_error("DPD state unknown")
+ def dpd_reset(self, **kwargs):
+ if cherrypy.request.method == 'POST':
+ return self._wrap_dpd("reset")
+ else:
+ cherrypy.response.status = 400
+ return send_error("POST only")
@cherrypy.expose
@cherrypy.tools.json_out()
- def calibrate(self, **kwargs):
+ def dpd_settings(self, setting: str, value: str, **kwargs):
if cherrypy.request.method == 'POST':
- # TODO dpd send capture
- return send_ok()
+ data = {'setting': setting, 'value': value}
+ return self._wrap_dpd("set_setting", data)
+ else:
+ return self._wrap_dpd("get_settings")
+
+ @cherrypy.expose
+ @cherrypy.tools.json_out()
+ def dpd_results(self, **kwargs):
+ return self._wrap_dpd("get_results")
+
+ @cherrypy.expose
+ @cherrypy.tools.json_out()
+ def dpd_calibrate(self, **kwargs):
+ if cherrypy.request.method == 'POST':
+ return self._wrap_dpd("calibrate")
else:
- # Fetch dpd status
- return send_error("DPD calibration result unknown")
+ return self._wrap_dpd("get_calibration_result")
diff --git a/python/gui/static/js/odr-predistortion.js b/python/gui/static/js/odr-predistortion.js
index 7a93b2b..b2f1d22 100644
--- a/python/gui/static/js/odr-predistortion.js
+++ b/python/gui/static/js/odr-predistortion.js
@@ -18,6 +18,21 @@
// You should have received a copy of the GNU General Public License
// along with ODR-DabMod. If not, see <http://www.gnu.org/licenses/>.
+function resultrefresh() {
+ var jqxhr = doApiRequestGET("/api/dpd_results", function(data) {
+ $('#dpdresults').text(data['summary']);
+ });
+
+ jqxhr.always(function() {
+ setTimeout(resultrefresh, 2000);
+ });
+}
+
+$(function(){
+ setTimeout(resultrefresh, 2000);
+});
+
+/*
function calibraterefresh() {
doApiRequestGET("/api/calibrate", function(data) {
var text = "Captured TX signal and feedback." +
@@ -92,6 +107,8 @@ $(function(){
});
});
+*/
+
// ToolTip init
$(function(){
diff --git a/python/gui/static/js/odr.js b/python/gui/static/js/odr.js
index ecb02c5..0bf7729 100644
--- a/python/gui/static/js/odr.js
+++ b/python/gui/static/js/odr.js
@@ -20,7 +20,7 @@
function doApiRequestGET(uri, callback) {
- $.ajax({
+ return $.ajax({
type: "GET",
url: uri,
contentType: 'application/json',
@@ -32,6 +32,8 @@ function doApiRequestGET(uri, callback) {
errorWindow.document.write(data.responseText);
}
else {
+ console.log(data.responseText);
+
$.gritter.add({ title: 'API',
text: "AJAX failed: " + data.statusText,
image: '/fonts/warning.png',
@@ -56,7 +58,7 @@ function doApiRequestGET(uri, callback) {
}
function doApiRequestPOST(uri, data, callback) {
- $.ajax({
+ return $.ajax({
type: "POST",
url: uri,
contentType: 'application/json',
@@ -65,10 +67,14 @@ function doApiRequestPOST(uri, data, callback) {
error: function(data) {
if (data.status == 500) {
- var errorWindow = window.open("", "_self");
+ var windowObjectReference;
+ var winFeatures = "menubar=yes,location=yes,resizable=yes,scrollbars=yes,status=yes";
+ var errorWindow = window.open("", "Error 500", winFeatures);
errorWindow.document.write(data.responseText);
}
else {
+ console.log(data.responseText);
+
$.gritter.add({
title: 'API',
text: "AJAX failed: " + data.statusText,
@@ -100,10 +106,10 @@ function setRc(controllable, param, value, callback) {
param: param,
value: value
};
- doApiRequestPOST("/api/parameter/", data, callback);
+ return doApiRequestPOST("/api/parameter/", data, callback);
}
function getRc(callback) {
- doApiRequestGET("/api/rc_parameters", callback);
+ return doApiRequestGET("/api/rc_parameters", callback);
}
diff --git a/python/gui/templates/predistortion.html b/python/gui/templates/predistortion.html
index 2ebf7ea..ac68537 100644
--- a/python/gui/templates/predistortion.html
+++ b/python/gui/templates/predistortion.html
@@ -29,6 +29,15 @@ along with ODR-DabMod. If not, see <http://www.gnu.org/licenses/>.
<div class="container-fluid">
<div class="panel-group">
<div class="panel panel-default">
+ <div class="panel-heading">Status</div>
+ <div class="panel-body">
+ <div>Current DPDCE status:
+ <div class="well well-sm" id="dpdresults">N/A<div>
+ </div>
+ </div>
+ </div>
+ <!--
+ <div class="panel panel-default">
<div class="panel-heading">Calibration</div>
<div class="panel-body">
<div>Calibration needs to be done once before the PA model
@@ -55,7 +64,6 @@ along with ODR-DabMod. If not, see <http://www.gnu.org/licenses/>.
</div>
</div>
- <!--
<div class="panel panel-default">
<div class="panel-heading">Capture</div>
<div class="panel-body">
diff --git a/python/lib/yamlrpc.py b/python/lib/yamlrpc.py
index bd61569..d963601 100644
--- a/python/lib/yamlrpc.py
+++ b/python/lib/yamlrpc.py
@@ -30,6 +30,11 @@ import yaml
import socket
import struct
+class ResponseError(Exception):
+ """The response contains an error"""
+ def __init__(self, message):
+ self.message = message
+
def request(request_id: int, method: str, params) -> bytes:
r = {
'yamlrpc': YAMLRPC_VERSION,
@@ -42,14 +47,12 @@ def response_success(request_id: int, result) -> bytes:
r = {
'yamlrpc': YAMLRPC_VERSION,
'result': result,
- 'error': None,
'id': request_id}
return yaml.dump(r).encode()
def response_error(request_id: int, error) -> bytes:
r = {
'yamlrpc': YAMLRPC_VERSION,
- 'result': None,
'error': error,
'id': request_id}
return yaml.dump(r).encode()
@@ -66,9 +69,58 @@ class Socket:
self.socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
if bind_port > 0:
self.socket.bind(('127.0.0.1', bind_port))
+ self.socket.settimeout(3)
+ self._last_request_id = 0
+
+ def send_request(self, dest_port: int, method: str, params) -> int:
+ addr = ("127.0.0.1", dest_port)
+ self._last_request_id += 1
+ self.socket.sendto(request(self._last_request_id, method, params), addr)
+ return self._last_request_id
+
+ def receive_response(self, expected_msg_id: int):
+ try:
+ data, addr = self.socket.recvfrom(512)
+ except socket.timeout as to:
+ raise TimeoutError("Timeout: " + str(to))
+
+ y = yaml.load(data.decode())
+
+ if 'yamlrpc' not in y:
+ raise ValueError("Message is not yamlrpc")
+ if y['yamlrpc'] != YAMLRPC_VERSION:
+ raise ValueError("Invalid yamlrpc version")
+
+ # expect a response, with either 'error' or 'result' non-null
+ try:
+ msg_id = y['id']
+ except KeyError:
+ raise ValueError("Response is missing id")
+
+ if msg_id != expected_msg_id:
+ raise ValueError("Response id does not match request")
+
+ try:
+ result = y['result']
+ except KeyError:
+ try:
+ error = y['error']
+ raise ResponseError(error)
+ except KeyError:
+ raise ValueError("response is null")
+ return result
+
+ def call_rpc_method(self, dest_port: int, method: str, params):
+ msg_id = self.send_request(dest_port, method, params)
+ return self.receive_response(msg_id)
+
def receive_request(self):
- data, addr = self.socket.recvfrom(512)
+ try:
+ data, addr = self.socket.recvfrom(512)
+ except socket.timeout as to:
+ raise TimeoutError("Timeout: " + str(to))
+
y = yaml.load(data.decode())
if 'yamlrpc' not in y:
diff --git a/python/poly.coef b/python/poly.coef
new file mode 100644
index 0000000..248d316
--- /dev/null
+++ b/python/poly.coef
@@ -0,0 +1,12 @@
+1
+5
+1.0
+0.0
+0.0
+0.0
+0.0
+0.0
+0.0
+0.0
+0.0
+0.0