mirror of
https://git.rwth-aachen.de/acs/public/villas/web/
synced 2025-03-16 00:00:03 +01:00
Add websocket api with simulator data
Simulator data is passed to each widget to process the data
This commit is contained in:
parent
d9a2ae55a9
commit
5441bd46f6
6 changed files with 218 additions and 17 deletions
src
api
containers
data-managers
stores
36
src/api/websocket-api.js
Normal file
36
src/api/websocket-api.js
Normal file
|
@ -0,0 +1,36 @@
|
|||
/**
|
||||
* File: websocket-api.js
|
||||
* Author: Markus Grigull <mgrigull@eonerc.rwth-aachen.de>
|
||||
* Date: 03.03.2017
|
||||
* Copyright: 2017, Institute for Automation of Complex Power Systems, EONERC
|
||||
* This file is part of VILLASweb. All Rights Reserved. Proprietary and confidential.
|
||||
* Unauthorized copying of this file, via any medium is strictly prohibited.
|
||||
**********************************************************************************/
|
||||
|
||||
class WebsocketAPI {
|
||||
constructor() {
|
||||
this.sockets = {};
|
||||
}
|
||||
|
||||
addSocket(endpoint, identifier, callbacks) {
|
||||
// create web socket client
|
||||
var socket = new WebSocket('ws://' + endpoint, 'live');
|
||||
socket.binaryType = 'arraybuffer';
|
||||
|
||||
// register callbacks
|
||||
if (callbacks.onOpen) socket.addEventListener('open', event => callbacks.onOpen(event));
|
||||
if (callbacks.onClose) socket.addEventListener('close', event => callbacks.onClose(event));
|
||||
if (callbacks.onMessage) socket.addEventListener('message', event => callbacks.onMessage(event));
|
||||
if (callbacks.onError) socket.addEventListener('error', event => callbacks.onError(event));
|
||||
|
||||
// save socket
|
||||
this.sockets[identifier] = socket;
|
||||
}
|
||||
|
||||
removeSocket(identifier) {
|
||||
this.sockets[identifier].close();
|
||||
delete this.sockets[identifier];
|
||||
}
|
||||
}
|
||||
|
||||
export default new WebsocketAPI();
|
|
@ -14,7 +14,7 @@ import { ContextMenu, MenuItem } from 'react-contextmenu';
|
|||
|
||||
import ToolboxItem from '../components/toolbox-item';
|
||||
import Dropzone from '../components/dropzone';
|
||||
import Widget from '../components/widget';
|
||||
import Widget from './widget';
|
||||
import VisualizationStore from '../stores/visualization-store';
|
||||
import AppDispatcher from '../app-dispatcher';
|
||||
|
||||
|
@ -27,8 +27,7 @@ class Visualization extends Component {
|
|||
return {
|
||||
visualizations: VisualizationStore.getState(),
|
||||
|
||||
visualization: {},
|
||||
editing: false
|
||||
visualization: {}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -92,6 +91,14 @@ class Visualization extends Component {
|
|||
AppDispatcher.dispatch({
|
||||
type: 'visualizations/start-load'
|
||||
});
|
||||
|
||||
AppDispatcher.dispatch({
|
||||
type: 'simulatorData/open',
|
||||
endpoint: 'localhost:5000',
|
||||
identifier: 'RTDS'
|
||||
});
|
||||
|
||||
this.setState({ editing: false });
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
|
@ -111,8 +118,6 @@ class Visualization extends Component {
|
|||
}
|
||||
|
||||
render() {
|
||||
console.log(this.state.visualization.widgets);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div>
|
||||
|
|
|
@ -1,26 +1,32 @@
|
|||
/**
|
||||
* File: widget.js
|
||||
* Author: Markus Grigull <mgrigull@eonerc.rwth-aachen.de>
|
||||
* Date: 02.03.2017
|
||||
* Date: 04.03.2017
|
||||
* Copyright: 2017, Institute for Automation of Complex Power Systems, EONERC
|
||||
* This file is part of VILLASweb. All Rights Reserved. Proprietary and confidential.
|
||||
* Unauthorized copying of this file, via any medium is strictly prohibited.
|
||||
**********************************************************************************/
|
||||
|
||||
import React, { Component } from 'react';
|
||||
import Rnd from 'react-rnd';
|
||||
import { Container } from 'flux/utils';
|
||||
import { ContextMenuTrigger } from 'react-contextmenu';
|
||||
import Rnd from 'react-rnd';
|
||||
|
||||
import SimulatorDataStore from '../stores/simulator-data-store';
|
||||
|
||||
import '../styles/widgets.css';
|
||||
|
||||
class Widget extends Component {
|
||||
resizeStop(direction, styleSize, clientSize, delta) {
|
||||
// update widget
|
||||
var widget = this.props.data;
|
||||
widget.width = styleSize.width;
|
||||
widget.height = styleSize.height;
|
||||
static getStores() {
|
||||
return [ SimulatorDataStore ];
|
||||
}
|
||||
|
||||
this.props.onWidgetChange(widget, this.props.index);
|
||||
static calculateState() {
|
||||
return {
|
||||
simulatorData: SimulatorDataStore.getState(),
|
||||
|
||||
widget: {}
|
||||
};
|
||||
}
|
||||
|
||||
dragStop(event, ui) {
|
||||
|
@ -32,9 +38,25 @@ class Widget extends Component {
|
|||
this.props.onWidgetChange(widget, this.props.index);
|
||||
}
|
||||
|
||||
resizeStop(direction, styleSize, clientSize, delta) {
|
||||
// update widget
|
||||
var widget = this.props.data;
|
||||
widget.width = styleSize.width;
|
||||
widget.height = styleSize.height;
|
||||
|
||||
this.props.onWidgetChange(widget, this.props.index);
|
||||
}
|
||||
|
||||
render() {
|
||||
const widget = this.props.data;
|
||||
|
||||
var value = '';
|
||||
|
||||
if (this.state.simulatorData.RTDS && this.state.simulatorData.RTDS.values) {
|
||||
const arr = this.state.simulatorData.RTDS.values[this.props.index];
|
||||
value = arr[arr.length - 1].y;
|
||||
}
|
||||
|
||||
if (this.props.editing) {
|
||||
return (
|
||||
<Rnd
|
||||
|
@ -46,18 +68,18 @@ class Widget extends Component {
|
|||
onDragStop={(event, ui) => this.dragStop(event, ui)}
|
||||
>
|
||||
<ContextMenuTrigger id={'widgetMenu' + this.props.index} attributes={{ style: { width: '100%', height: '100%' } }}>
|
||||
<div>{widget.name}</div>
|
||||
<div>{value}</div>
|
||||
</ContextMenuTrigger>
|
||||
</Rnd>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<div className="widget" style={{ width: Number(widget.width), height: Number(widget.height), left: Number(widget.x), top: Number(widget.y), position: 'absolute' }}>
|
||||
{widget.name}
|
||||
{value}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default Widget;
|
||||
export default Container.create(Widget);
|
|
@ -1,5 +1,5 @@
|
|||
/**
|
||||
* File: data-manager.js
|
||||
* File: rest-data-manager.js
|
||||
* Author: Markus Grigull <mgrigull@eonerc.rwth-aachen.de>
|
||||
* Date: 03.03.2017
|
||||
* Copyright: 2017, Institute for Automation of Complex Power Systems, EONERC
|
||||
|
|
72
src/data-managers/simulator-data-manager.js
Normal file
72
src/data-managers/simulator-data-manager.js
Normal file
|
@ -0,0 +1,72 @@
|
|||
/**
|
||||
* File: simulator-data-manager.js
|
||||
* Author: Markus Grigull <mgrigull@eonerc.rwth-aachen.de>
|
||||
* Date: 03.03.2017
|
||||
* Copyright: 2017, Institute for Automation of Complex Power Systems, EONERC
|
||||
* This file is part of VILLASweb. All Rights Reserved. Proprietary and confidential.
|
||||
* Unauthorized copying of this file, via any medium is strictly prohibited.
|
||||
**********************************************************************************/
|
||||
|
||||
import WebsocketAPI from '../api/websocket-api';
|
||||
import AppDispatcher from '../app-dispatcher';
|
||||
|
||||
class SimulationDataManager {
|
||||
open(endpoint, identifier) {
|
||||
WebsocketAPI.addSocket(endpoint, identifier, { onOpen: event => this.onOpen(event), onClose: event => this.onClose(event), onMessage: event => this.onMessage(event) });
|
||||
}
|
||||
|
||||
onOpen(event) {
|
||||
// TODO: Add identifiers to callbacks
|
||||
|
||||
AppDispatcher.dispatch({
|
||||
type: 'simulatorData/opened',
|
||||
identifier: 'RTDS',
|
||||
signals: 8
|
||||
});
|
||||
}
|
||||
|
||||
onClose(event) {
|
||||
AppDispatcher.dispatch({
|
||||
type: 'simulatorData/closed'
|
||||
});
|
||||
}
|
||||
|
||||
onMessage(event) {
|
||||
var message = this.bufferToMessage(event.data);
|
||||
|
||||
AppDispatcher.dispatch({
|
||||
type: 'simulatorData/data-changed',
|
||||
data: message,
|
||||
identifier: 'RTDS'
|
||||
});
|
||||
}
|
||||
|
||||
bufferToMessage(blob) {
|
||||
// parse incoming message into usable data
|
||||
var data = new DataView(blob);
|
||||
|
||||
let OFFSET_ENDIAN = 1;
|
||||
let OFFSET_TYPE = 2;
|
||||
let OFFSET_VERSION = 4;
|
||||
|
||||
var bits = data.getUint8(0);
|
||||
var simulator = data.getUint8(0x01);
|
||||
var endian = (bits >> OFFSET_ENDIAN) & 0x1 ? 0 : 1;
|
||||
var length = data.getUint16(0x02, endian);
|
||||
|
||||
var values = new Float32Array(data.buffer, data.byteOffset + 0x10, length);
|
||||
|
||||
return {
|
||||
endian: endian,
|
||||
version: (bits >> OFFSET_VERSION) & 0xF,
|
||||
type: (bits >> OFFSET_TYPE) & 0x3,
|
||||
length: length,
|
||||
sequence: data.getUint32(0x04, endian),
|
||||
timestamp: data.getUint32(0x08, endian) * 1e3 + data.getUint32(0x0C, endian) * 1e-6,
|
||||
values: values,
|
||||
simulator: simulator
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export default new SimulationDataManager();
|
66
src/stores/simulator-data-store.js
Normal file
66
src/stores/simulator-data-store.js
Normal file
|
@ -0,0 +1,66 @@
|
|||
/**
|
||||
* File: simulator-data-store.js
|
||||
* Author: Markus Grigull <mgrigull@eonerc.rwth-aachen.de>
|
||||
* Date: 03.03.2017
|
||||
* Copyright: 2017, Institute for Automation of Complex Power Systems, EONERC
|
||||
* This file is part of VILLASweb. All Rights Reserved. Proprietary and confidential.
|
||||
* Unauthorized copying of this file, via any medium is strictly prohibited.
|
||||
**********************************************************************************/
|
||||
|
||||
import { ReduceStore } from 'flux/utils';
|
||||
|
||||
import AppDispatcher from '../app-dispatcher';
|
||||
import SimulatorDataManager from '../data-managers/simulator-data-manager';
|
||||
|
||||
class SimulationDataStore extends ReduceStore {
|
||||
constructor() {
|
||||
super(AppDispatcher);
|
||||
}
|
||||
|
||||
getInitialState() {
|
||||
return {};
|
||||
}
|
||||
|
||||
reduce(state, action) {
|
||||
var i;
|
||||
|
||||
switch (action.type) {
|
||||
case 'simulatorData/open':
|
||||
SimulatorDataManager.open(action.endpoint, action.identifier);
|
||||
return state;
|
||||
|
||||
case 'simulatorData/opened':
|
||||
// create entry for simulator
|
||||
state[action.identifier] = { signals: action.signals, values: [], sequence: null, timestamp: null };
|
||||
|
||||
for (i = 0; i < action.signals; i++) {
|
||||
state[action.identifier].values.push([]);
|
||||
}
|
||||
|
||||
return state;
|
||||
|
||||
case 'simulatorData/data-changed':
|
||||
// add data to simulator
|
||||
for (i = 0; i < state[action.identifier].signals; i++) {
|
||||
state[action.identifier].values[i].push({ x: action.data.timestamp, y: action.data.values[i] });
|
||||
}
|
||||
|
||||
// update metadata
|
||||
state[action.identifier].timestamp = action.data.timestamp;
|
||||
state[action.identifier].sequence = action.data.sequence;
|
||||
|
||||
// explicit call to prevent array copy
|
||||
this.__emitChange();
|
||||
|
||||
return state;
|
||||
|
||||
case 'simulatorData/closed':
|
||||
return state;
|
||||
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default new SimulationDataStore();
|
Loading…
Add table
Reference in a new issue