mirror of
https://git.rwth-aachen.de/acs/public/villas/node/
synced 2025-03-09 00:00:00 +01:00
327 lines
6.7 KiB
JavaScript
327 lines
6.7 KiB
JavaScript
/** VILLASnode WebMockup Javascript application.
|
||
*
|
||
* @author Steffen Vogel <stvogel@eonerc.rwth-aachen.de>
|
||
* @copyright 2017, Institute for Automation of Complex Power Systems, EONERC
|
||
* @license GNU General Public License (version 3)
|
||
*
|
||
* VILLASnode
|
||
*
|
||
* This program 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
|
||
* any later version.
|
||
*
|
||
* This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
|
||
*********************************************************************************/
|
||
|
||
// global variables
|
||
var api;
|
||
var connection;
|
||
var timer;
|
||
var plot;
|
||
|
||
var nodes = [];
|
||
var currentNode;
|
||
|
||
var paused = false;
|
||
var sequence = 0;
|
||
|
||
var plotData = [];
|
||
var plotOptions = {
|
||
xaxis: {
|
||
mode: 'time'
|
||
},
|
||
legend: {
|
||
show: true
|
||
},
|
||
selection: {
|
||
mode: "x"
|
||
}
|
||
};
|
||
|
||
var updateRate = 25;
|
||
var redrawPlot = true;
|
||
|
||
var xDelta = 5000;
|
||
var xPast = xDelta * 0.9;
|
||
var xFuture = xDelta * 0.1;
|
||
|
||
$(document).ready(function() {
|
||
api = new Api('v1', apiConnected);
|
||
|
||
$('#play').click(function(e, ui) {
|
||
connection = wsConnect(currentNode);
|
||
paused = false;
|
||
});
|
||
|
||
$('#pause').click(function(e, ui) {
|
||
connection.close();
|
||
paused = true;
|
||
});
|
||
|
||
$('#timespan').slider({
|
||
min : 100,
|
||
max : 10000,
|
||
value : xDelta,
|
||
slide : function(e, ui) {
|
||
updatePlotWindow(ui.value);
|
||
}
|
||
});
|
||
|
||
$('#updaterate').slider({
|
||
min : 1,
|
||
max : 100,
|
||
value : updateRate,
|
||
slide : function(e, ui) {
|
||
clearInterval(timer);
|
||
timer = setInterval(updatePlot, 1000.0 / updateRate);
|
||
updateRate = ui.value;
|
||
}
|
||
});
|
||
|
||
$('.inputs #slider').slider({
|
||
min : 0,
|
||
max : 10000,
|
||
slide : sendData
|
||
});
|
||
|
||
$('.inputs-checkboxes input').checkboxradio()
|
||
.each(function(idx, elm) {
|
||
$(elm).change(sendData);
|
||
});
|
||
|
||
timer = setInterval(updatePlot, 1000.0 / updateRate);
|
||
});
|
||
|
||
$(window).on('beforeunload', function() {
|
||
connection.close();
|
||
api.close();
|
||
});
|
||
|
||
function sendData()
|
||
{
|
||
var slider = $('.inputs #slider');
|
||
var checkboxes = $('.inputs-checkboxes input');
|
||
|
||
var data = [ $(slider).slider('value') / 100, 0 ];
|
||
|
||
for (var i = 0; i < checkboxes.length; i++)
|
||
data[1] += (checkboxes[i].checked ? 1 : 0) << i;
|
||
|
||
var msg = new Msg({
|
||
timestamp : Date.now(),
|
||
sequence : sequence++,
|
||
data : data
|
||
});
|
||
|
||
console.log('Sending message', msg);
|
||
|
||
connection.send(msg.toArrayBuffer());
|
||
}
|
||
|
||
function apiConnected()
|
||
{
|
||
api.request('nodes', {},
|
||
function(response) {
|
||
nodes = response;
|
||
|
||
console.log("Found " + nodes.length + " nodes:",);
|
||
|
||
nodes.forEach(function(node) {
|
||
console.log(node);
|
||
if (node.type == 'websocket' && node.name == getParameterByName('node'))
|
||
currentNode = node;
|
||
});
|
||
|
||
if (currentNode === undefined) {
|
||
nodes.forEach(function(node) {
|
||
if (node.type == 'websocket')
|
||
currentNode = node;
|
||
});
|
||
}
|
||
|
||
if (currentNode !== undefined) {
|
||
updateNodeList();
|
||
connection = wsConnect(currentNode);
|
||
}
|
||
}
|
||
);
|
||
}
|
||
|
||
function updateNodeList()
|
||
{
|
||
$('.node-selector').empty();
|
||
|
||
nodes.forEach(function(node, index) {
|
||
if (node.type == 'websocket') {
|
||
$('.node-selector').append(
|
||
$('<button>')
|
||
.addClass(node.name == currentNode.name ? 'ui-state-active' : '')
|
||
.text(node.description ? node.description : node.name)
|
||
.click(function() {
|
||
var url = node.name;
|
||
window.location = '?node=' + node.name;
|
||
})
|
||
);
|
||
}
|
||
});
|
||
|
||
$('.node-selector').buttonset();
|
||
}
|
||
|
||
function updatePlotWindow(delta)
|
||
{
|
||
xDelta = delta
|
||
xPast = xDelta * 0.9;
|
||
xFuture = xDelta * 0.1;
|
||
}
|
||
|
||
function updatePlot()
|
||
{
|
||
var data = [];
|
||
|
||
if (!redrawPlot)
|
||
return;
|
||
|
||
// add data to arrays
|
||
for (var i = 0; i < plotData.length; i++) {
|
||
var seriesOptions = nodes;
|
||
|
||
data[i] = {
|
||
data : plotData[i],
|
||
shadowSize : 0,
|
||
label : 'Index ' + String(i),
|
||
lines : {
|
||
lineWidth: 2
|
||
}
|
||
}
|
||
|
||
if (currentNode.series !== undefined && currentNode.series[i] !== undefined)
|
||
$.extend(true, data[i], currentNode.series[i]);
|
||
}
|
||
|
||
var options = {
|
||
xaxis: {
|
||
min: Date.now() - xPast,
|
||
max: Date.now() + xFuture
|
||
},
|
||
grid: {
|
||
markings: [
|
||
{ xaxis: { from: Date.now(), to: Date.now() }, color: '#ff0000' }
|
||
]
|
||
}
|
||
}
|
||
|
||
var placeholder = $('.plot-container div');
|
||
|
||
/* update plot */
|
||
plot = $.plot(placeholder, data, $.extend(true, options, plotOptions));
|
||
|
||
placeholder.bind("plotselected", function (event, ranges) {
|
||
$.each(plot.getXAxes(), function(_, axis) {
|
||
var opts = axis.options;
|
||
opts.min = ranges.xaxis.from;
|
||
opts.max = ranges.xaxis.to;
|
||
});
|
||
|
||
plot.setupGrid();
|
||
plot.draw();
|
||
plot.clearSelection();
|
||
});
|
||
|
||
redrawPlot = false;
|
||
}
|
||
|
||
function wsConnect(node)
|
||
{
|
||
var url = wsUrl(node.name);
|
||
var conn = new WebSocket(url, 'live');
|
||
|
||
conn.binaryType = 'arraybuffer';
|
||
|
||
conn.onopen = function() {
|
||
$('#status')
|
||
.text('Connected')
|
||
.css('color', 'green');
|
||
|
||
console.log('WebSocket connection established');
|
||
};
|
||
|
||
conn.onclose = function(error) {
|
||
console.log('WebSocket connection closed', error);
|
||
|
||
$('#status')
|
||
.text('Disconnected' + (error.reason != '' ? ' (' + error.reason + ')' : ''))
|
||
.css('color', 'red');
|
||
|
||
/* Try connect if close reason was CLOSE_NORMAL */
|
||
if (error.code == 1000 || error.code == 1001) {
|
||
setTimeout(function() {
|
||
wsConnect(currentNode);
|
||
}, 1000);
|
||
}
|
||
};
|
||
|
||
conn.onerror = function(error) {
|
||
console.log('WebSocket connection error', error);
|
||
|
||
$('#status').text('Status: Error: ' + error.message);
|
||
};
|
||
|
||
conn.onmessage = function(e) {
|
||
var msgs = Msg.fromArrayBufferVector(e.data);
|
||
|
||
for (var j = 0; j < plotData.length; j++) {
|
||
// remove old
|
||
while (plotData[j].length > 0 && plotData[j][0][0] < (Date.now() - xPast))
|
||
plotData[j].shift()
|
||
}
|
||
|
||
for (var j = 0; j < msgs.length; j++) {
|
||
var msg = msgs[j];
|
||
|
||
console.log('Received message with ' + msg.data.length + ' values from id ' + msg.id + ' with timestamp ' + new Date(msg.timestamp).toString() + ': '+ msg.data);
|
||
|
||
// add empty arrays for data series
|
||
while (plotData.length < msg.length)
|
||
plotData.push([]);
|
||
|
||
// add data to arrays
|
||
for (var i = 0; i < msg.length; i++)
|
||
plotData[i].push([msg.timestamp, msg.data[i]]);
|
||
}
|
||
|
||
redrawPlot = true;
|
||
};
|
||
|
||
return conn;
|
||
};
|
||
|
||
/* Helpers */
|
||
function wsUrl(endpoint)
|
||
{
|
||
var l = window.location;
|
||
var url = '';
|
||
|
||
if (l.protocol === 'https:')
|
||
url += 'wss://';
|
||
else
|
||
url += 'ws://';
|
||
|
||
url += l.hostname;
|
||
|
||
if ((l.port) && (l.port != 80) && (l.port != 443))
|
||
url += ':'+ l.port;
|
||
|
||
url += '/' + endpoint;
|
||
|
||
return url;
|
||
}
|
||
|
||
/* Some helpers */
|