luci-mod-dsl: initial commit
authorturboproc <roland@treslong.com>
Wed, 3 Aug 2022 21:47:23 +0000 (23:47 +0200)
committerFlorian Eckert <fe@dev.tdt.de>
Fri, 4 Nov 2022 10:25:05 +0000 (11:25 +0100)
Signed-off-by: Roland Barenbrug <roland@treslong.com>
[replace spcaes with tabs]
[remove not used dsl_cpe_pipe.sh from acl]
[cleanup file naming]
Signed-off-by: Florian Eckert <fe@dev.tdt.de>
modules/luci-mod-dsl/Makefile [new file with mode: 0644]
modules/luci-mod-dsl/htdocs/luci-static/resources/view/status/dsl/graph.js [new file with mode: 0644]
modules/luci-mod-dsl/htdocs/luci-static/resources/view/status/dsl/spectrum.js [new file with mode: 0644]
modules/luci-mod-dsl/root/usr/share/luci/menu.d/luci-mod-dsl.json [new file with mode: 0644]
modules/luci-mod-dsl/root/usr/share/rpcd/acl.d/luci-mod-dsl.json [new file with mode: 0644]

diff --git a/modules/luci-mod-dsl/Makefile b/modules/luci-mod-dsl/Makefile
new file mode 100644 (file)
index 0000000..3d2b0c6
--- /dev/null
@@ -0,0 +1,16 @@
+#
+# Copyright (C) 2022 Roland Barenbrug <roland@treslong.com>
+#
+# This is free software, licensed under the Apache License, Version 2.0
+#
+
+include $(TOPDIR)/rules.mk
+
+LUCI_TITLE:=LUCI DSL spectrum graph
+LUCI_DEPENDS:=+luci-base +ltq-dsl-base
+
+PKG_LICENSE:=Apache-2.0
+
+include ../../luci.mk
+
+# call BuildPackage - OpenWrt buildroot signature
diff --git a/modules/luci-mod-dsl/htdocs/luci-static/resources/view/status/dsl/graph.js b/modules/luci-mod-dsl/htdocs/luci-static/resources/view/status/dsl/graph.js
new file mode 100644 (file)
index 0000000..26b3f60
--- /dev/null
@@ -0,0 +1,368 @@
+//
+// Rendering of DSL spectrum graphs  showing
+// US/DS SNR and US/DS bits/tone
+//
+// This version does depend on an ubus version that support DSL line stattiscis  but 
+// does not depend on chart.js or any other package
+
+class DataSet {
+       constructor (input, extractFunction) {
+               this.groupSize = input.groupsize;
+               this.numData = input.groups;
+               // needs to be validated with various input
+               this.maxX = this.numData * this.groupSize;
+               this.data = input.data.map(extractFunction,
+                       {groupSize: this.groupSize}
+               );
+       }
+}
+
+function myBitsFunction(value, index, array) {
+       return({x: index, y: value, error: false});
+}
+
+function mySnrFunction(value, index, array) {
+       let result;
+
+       if (value == null) {
+               result = {
+                       x: index * this.groupSize,
+                       y: -40 ,
+                       error: true
+               }
+       } else {
+               result = {
+                       x: index * this.groupSize,
+                       y: value,
+                       error: false
+               }
+       }
+
+       return(result);
+}
+
+function myQLNFunction(value, index, array) {
+       let result;
+
+       if (value == null) {
+               result = {
+                       x: index * this.groupSize,
+                       y:  - 150,
+                       error: true
+               }
+       } else {
+               result = {
+                       x: index * this.groupSize,
+                       y:  value,
+                       error: false
+               }
+       }
+
+       return(result);
+}
+
+function myHLOGFunction(value, index, array) {
+       let result;
+
+       if (value == null) {
+               result = {
+                       x: index * this.groupSize,
+                       y: -100,
+                       error: true
+               }
+       } else {
+               result = {
+                       x: index * this.groupSize,
+                       y: value,
+                       error: false
+               }
+       }
+
+       return(result);
+}
+
+const usSnrData  = new DataSet(window.json['snr']['upstream'], mySnrFunction);
+const dsSnrData  = new DataSet(window.json['snr']['downstream'], mySnrFunction);
+const usBitsData = new DataSet(window.json['bits']['upstream'], myBitsFunction);
+const dsBitsData = new DataSet(window.json['bits']['downstream'], myBitsFunction);
+const usQLNData  = new DataSet(window.json['qln']['upstream'], myQLNFunction);
+const dsQLNData  = new DataSet(window.json['qln']['downstream'], myQLNFunction);
+const usHLOGData = new DataSet(window.json['hlog']['upstream'], myHLOGFunction);
+const dsHLOGData = new DataSet(window.json['hlog']['downstream'], myHLOGFunction);
+
+const marginX = 50;
+const marginY = 80;
+let darkMode = document.getElementsByTagName("body")[0].parentNode.dataset.darkmode;
+
+let bitsChart = {
+       "config": {
+               "canvas": document.getElementById("bitsChart"),
+               "ctx" : document.getElementById("bitsChart").getContext("2d"),
+               "minX" : 0,
+               "maxX" : Math.max(dsBitsData.maxX, usBitsData.maxX),
+               "stepX": Math.max(dsBitsData.maxX, usBitsData.maxX) / 16,
+               "graphWidth" : document.getElementById("bitsChart").width - 2 * marginX,
+               "lineWidth" : 1,
+               "titleX" : "Sub-carrier",
+               "minY" : 0,
+               "maxY" : 16,
+               "stepY": 2,
+               "graphHeight" : document.getElementById("bitsChart").height - 2 * marginY,
+               "titleY" : "bits",
+       },
+       "dataSet" : [
+               {
+                       "data" :usBitsData.data,
+                       "color":"YellowGreen",
+                       "title": "Upstream bits allocation"
+               },
+               {
+                       "data" : dsBitsData.data,
+                       "color": "navy",
+               "title": "Downstream bits allocation"
+               }
+       ]
+};
+
+let dBChart = {
+       "config": {
+               "canvas": document.getElementById("dbChart"),
+               "ctx" : document.getElementById("dbChart").getContext("2d"),
+               "minX" : 0,
+               "maxX" : Math.max(dsSnrData.maxX, usSnrData.maxX),
+               "stepX": Math.max(dsSnrData.maxX, usSnrData.maxX) / 16,
+               "graphWidth" : document.getElementById("dbChart").width - 2 * marginX,
+               "lineWidth": 4,
+               "titleX" : "Sub-carrier",
+               "minY" : -40,
+               "maxY" : 100,
+               "stepY": 10,
+               "graphHeight" : document.getElementById("dbChart").height - 2 * marginY,
+               "titleY" : "dB"
+       },
+       "dataSet" : [
+               {
+                       "data" :usSnrData.data,
+                       "color":"Turquoise",
+                       "title": "Upstream SNR"
+               },
+               {
+                       "data" : dsSnrData.data,
+                       "color": "Coral",
+                       "title" : "Downstream SNR"
+               }
+       ]
+};
+
+let qLNChart = {
+       "config": {
+               "canvas": document.getElementById("qlnChart"),
+               "ctx" : document.getElementById("qlnChart").getContext("2d"),
+               "minX" : 0,
+               "maxX" : Math.max(dsQLNData.maxX, usQLNData.maxX),
+               "stepX": Math.max(dsQLNData.maxX, usQLNData.maxX) / 16,
+               "graphWidth" : document.getElementById("qlnChart").width - 2 * marginX,
+               "lineWidth": 4,
+               "titleX" : "Sub-carrier",
+               "minY" : -150,
+               "maxY" : -20,
+               "stepY": 10,
+               "graphHeight" : document.getElementById("qlnChart").height - 2 * marginY,
+               "titleY" : "dBm/Hz"
+       },
+       "dataSet" : [
+               {
+                       "data" :usQLNData.data,
+                       "color":"brown",
+                       "title": "Upstream QLN"
+               },
+               {
+                       "data" : dsQLNData.data,
+                       "color": "teal",
+                       "title" : "Downstream QLN"
+               }
+       ]
+};
+
+let hLogChart = {
+       "config": {
+               "canvas": document.getElementById("hlogChart"),
+               "ctx" : document.getElementById("hlogChart").getContext("2d"),
+               "minX" : 0,
+               "maxX" : Math.max(dsHLOGData.maxX, usHLOGData.maxX),
+               "stepX": Math.max(dsHLOGData.maxX, usHLOGData.maxX) / 16,
+               "graphWidth" : document.getElementById("hlogChart").width - 2 * marginX,
+               "lineWidth": 4,
+               "titleX" : "Sub-carrier",
+               "minY" : -100,
+               "maxY" : 14,
+               "stepY": 10,
+               "graphHeight" : document.getElementById("hlogChart").height - 2 * marginY,
+               "titleY" : "dB"
+       },
+       "dataSet" : [
+               {
+                       "data" :usHLOGData.data,
+                       "color":"#E8E800",
+                       "title": "Upstream HLOG"
+               },
+               {
+                       "data" : dsHLOGData.data,
+                       "color": "darkmagenta",
+                       "title" : "Downstream HLOG"
+               }
+       ]
+};
+
+function drawChart (info) {
+       drawAxisX(info.config, info.config.minX, info.config.maxX, info.config.stepX, info.config.titleX);
+       drawAxisY(info.config, info.config.minY, info.config.maxY, info.config.stepY, info.config.titleY);
+
+       drawLegend(info.config, info.dataSet);
+
+       drawData(info.config, info.dataSet[0].data, info.dataSet[0].color);
+       drawData(info.config, info.dataSet[1].data, info.dataSet[1].color);
+}
+
+function drawBlocks(config, dataPoints, color, borders) {
+       borders.map(drawBlock, {config, dataPoints, color, borders});
+}
+
+function drawData(config, dataPoints, color) {
+       let ctx = config.ctx;
+       let len = dataPoints.length;
+       let minX =config.minX;
+       let maxX = config.maxX;
+       let minY = config.minY;
+       let maxY = config.maxY;
+       let startX = (dataPoints[0].x  - config.minX) / (config.maxX - config.minX)
+       let startY = (config.minY - config.minY) / (config.maxY - config.minY)
+
+       ctx.fillStyle = color;
+       ctx.beginPath();
+       ctx.moveTo(startX * config.graphWidth + marginX, marginY + config.graphHeight - startY * config.graphHeight);
+
+       for (let i  = 1 ;  i < len  ; i++) {
+               let relX = (dataPoints[i].x - minX) / (maxX - minX);
+               let relY = (dataPoints[i].y - minY) / (maxY - minY);
+               ctx.lineTo(relX * config.graphWidth + marginX, marginY + config.graphHeight - relY * config.graphHeight);
+       }
+
+       let endX = (dataPoints[len-1].x - minX) / (maxX - minX)
+       let endY = (config.minY - minY) / (maxY - minY)
+
+       ctx.lineTo(endX * config.graphWidth + marginX, marginY + config.graphHeight - endY * config.graphHeight);
+       ctx.lineTo(startX * config.graphWidth + marginX, marginY + config.graphHeight - startY * config.graphHeight);
+       ctx.closePath();
+       ctx.fill();
+}
+
+function drawLegend(config, dataSet){
+       let ctx = config.ctx;
+       let graphWidth = config.graphWidth;
+       let graphHeight = config.graphHeight;
+
+       ctx.font = "12px Arial";
+       ctx.fillStyle = dataSet[0].color;
+       ctx.fillRect(0.5 * graphWidth + marginX - ctx.measureText(dataSet[0].title).width - 50, config.canvas.height - marginY*1/4 - 8, 30, 10);
+       ctx.strokeStyle = "#C0C0C0";
+       ctx.strokeRect(0.5 * graphWidth + marginX - ctx.measureText(dataSet[0].title).width - 50, config.canvas.height - marginY*1/4 - 8, 30, 10);
+
+       if (darkMode == "true") {
+               ctx.strokeStyle = "#505050";
+               ctx.fillStyle = "#A0A0A0";
+       } else {
+               ctx.strokeStyle = "#303030";
+               ctx.fillStyle = "#303030";
+       }
+
+       ctx.textAlign = "right"
+       ctx.fillText(dataSet[0].title, 0.5 * graphWidth + marginX - 10, config.canvas.height - marginY*1/4);
+
+       ctx.fillStyle = dataSet[1].color;
+       ctx.fillRect(0.5 * graphWidth + marginX, config.canvas.height - marginY*1/4 - 8, 30, 10);
+       ctx.strokeStyle = "#C0C0C0";
+       ctx.strokeRect(0.5 * graphWidth + marginX, config.canvas.height - marginY*1/4 - 8, 30, 10);
+
+       if (darkMode == "true") {
+               ctx.fillStyle = "#A0A0A0";
+       } else {
+               ctx.fillStyle = "#303030";
+       }
+
+       ctx.textAlign = "left"
+       ctx.fillText(dataSet[1].title, 0.5 * graphWidth + marginX + 40, config.canvas.height - marginY*1/4);
+}
+
+function drawAxisX(config, minValue, maxValue, step, title) {
+       let ctx = config.ctx;
+       let graphWidth = config.graphWidth;
+       let graphHeight = config.graphHeight;
+
+       ctx.font = "12px Arial";
+       ctx.textAlign = "center";
+
+       if (darkMode == "true") {
+               ctx.strokeStyle = "#505050";
+               ctx.fillStyle = "#A0A0A0";
+       } else {
+               ctx.strokeStyle = "#E0E0E0";
+               ctx.fillStyle = "#303030";
+       }
+
+       for (let x = minValue ; x <= maxValue ; x=x+step) {
+               let relX = (x - config.minX) / (config.maxX - config.minX);
+
+               ctx.fillText(x , relX * graphWidth + marginX,  config.canvas.height - marginY*3/4);
+
+               ctx.beginPath();
+               ctx.moveTo(relX * graphWidth + marginX, marginY);
+               ctx.lineTo(relX * graphWidth + marginX, config.canvas.height - marginY);
+               ctx.stroke();
+       }
+
+       ctx.font = "12px Arial";
+       ctx.textAlign = "center";
+       ctx.fillText(title, config.canvas.width/2, config.canvas.height - marginY*2/4);
+}
+
+function drawAxisY(config, minValue, maxValue, step, title) {
+       let ctx = config.ctx
+       let graphWidth = config.graphWidth;
+       let graphHeight = config.graphHeight;
+
+       ctx.font = "12px Arial";
+       ctx.textAlign = "center";
+
+       if (darkMode == "true") {
+               ctx.strokeStyle = "#505050";
+               ctx.fillStyle = "#A0A0A0";
+       } else {
+               ctx.strokeStyle = "#E0E0E0";
+               ctx.fillStyle = "#303030";
+       }
+
+       for (let y = minValue ; y <= maxValue ; y=y+step) {
+               let relY = (y - config.minY) / (config.maxY - config.minY);
+
+               ctx.fillText(y , marginX *2 / 3,  marginY + graphHeight - relY * graphHeight + 4);
+
+               ctx.beginPath();
+               ctx.moveTo(marginX, marginY + graphHeight - relY * graphHeight );
+               ctx.lineTo(config.canvas.width - marginX, marginY + graphHeight - relY * graphHeight);
+               ctx.stroke();
+       }
+
+       ctx.font = "12px Arial";
+       ctx.textAlign = "center";
+       ctx.translate(marginX/3, marginY + graphHeight / 2);
+       ctx.rotate(-3.14 /2);
+       ctx.fillText(title, 0, 0);
+       ctx.rotate(3.14 /2)
+       ctx.translate(-marginX/3,-(marginY + graphHeight / 2));
+}
+
+drawChart(dBChart);
+drawChart(bitsChart);
+drawChart(qLNChart);
+drawChart(hLogChart);
diff --git a/modules/luci-mod-dsl/htdocs/luci-static/resources/view/status/dsl/spectrum.js b/modules/luci-mod-dsl/htdocs/luci-static/resources/view/status/dsl/spectrum.js
new file mode 100644 (file)
index 0000000..7c8a7ca
--- /dev/null
@@ -0,0 +1,63 @@
+'use strict';
+'require view';
+'require fs';
+'require ui';
+'require rpc';
+
+var callDSLStatistics = rpc.declare({
+       object: 'dsl',
+       method: 'statistics',
+       expect: { '': {} }
+});
+
+return view.extend({
+       load: function() {
+               return Promise.all([
+                       callDSLStatistics()
+               ]);
+       },
+
+       render: function(data) {
+               window.json = data[0];
+
+               var v = E([], [
+                       E('h2', {'style': "height: 40px"}, [ _('DSL line spectrum') ]),
+                       E('p', {}, 'Graphs below show Signal-to-noise ratio, Bit allocation, Quiet line noise and Channel characteristics function (HLOG) per sub-carrier.'),
+                       E('div', {'style': "height: 360px; width: 1024px"},
+                               E('canvas', {
+                                       'id': 'dbChart',
+                                       'height': 360,
+                                       'width': 1024},
+                                       ["chart"])
+                       ),
+                       E('div', {'style': "height: 360px; width:1024px"},
+                               E('canvas', {
+                                       'id': 'bitsChart',
+                                       'height': 360,
+                                       'width': 1024},
+                                       ["chart2"])
+                       ),
+                       E('div', {'style': "height: 360px; width:1024px"},
+                               E('canvas', {
+                                       'id': 'qlnChart',
+                                       'height': 360,
+                                       'width': 1024},
+                                       ["chart2"])
+                       ),
+                       E('div', {'style': "height: 360px; width:1024px"},
+                               E('canvas', {
+                                       'id': 'hlogChart',
+                                       'height': 360,
+                                       'width': 1024},
+                                       ["chart2"])
+                               ),
+                       E('script', {'src':'/luci-static/resources/view/status/dsl/graph.js'}, {})
+               ]);
+
+               return v;
+       },
+
+       handleSaveApply: null,
+       handleSave: null,
+       handleReset: null
+});
diff --git a/modules/luci-mod-dsl/root/usr/share/luci/menu.d/luci-mod-dsl.json b/modules/luci-mod-dsl/root/usr/share/luci/menu.d/luci-mod-dsl.json
new file mode 100644 (file)
index 0000000..76d56fd
--- /dev/null
@@ -0,0 +1,13 @@
+{
+       "admin/status/dsl": {
+               "title": "DSL line spectrum",
+               "order": 7,
+               "action": {
+                       "type": "view",
+                       "path": "status/dsl/spectrum"
+               },
+               "depends": {
+                       "acl": [ "luci-mod-dsl-spectrum" ]
+               }
+       }
+}
diff --git a/modules/luci-mod-dsl/root/usr/share/rpcd/acl.d/luci-mod-dsl.json b/modules/luci-mod-dsl/root/usr/share/rpcd/acl.d/luci-mod-dsl.json
new file mode 100644 (file)
index 0000000..7a5b6fc
--- /dev/null
@@ -0,0 +1,10 @@
+{
+       "luci-mod-dsl-spectrum": {
+               "description": "Grant access to luci-mod-dsl spectrum",
+               "read": {
+                       "ubus": {
+                               "dsl": [ "statistics" ]
+                       }
+               }
+       }
+}