From 5441bd46f6fd5e0869fa418d626dcb7ab6b63179 Mon Sep 17 00:00:00 2001 From: Markus Grigull Date: Sat, 4 Mar 2017 10:31:13 +0100 Subject: [PATCH] Add websocket api with simulator data Simulator data is passed to each widget to process the data --- src/api/websocket-api.js | 36 +++++++++++ src/containers/visualization.js | 15 +++-- src/{components => containers}/widget.js | 44 +++++++++---- src/data-managers/rest-data-manager.js | 2 +- src/data-managers/simulator-data-manager.js | 72 +++++++++++++++++++++ src/stores/simulator-data-store.js | 66 +++++++++++++++++++ 6 files changed, 218 insertions(+), 17 deletions(-) create mode 100644 src/api/websocket-api.js rename src/{components => containers}/widget.js (76%) create mode 100644 src/data-managers/simulator-data-manager.js create mode 100644 src/stores/simulator-data-store.js diff --git a/src/api/websocket-api.js b/src/api/websocket-api.js new file mode 100644 index 0000000..33f96b8 --- /dev/null +++ b/src/api/websocket-api.js @@ -0,0 +1,36 @@ +/** + * File: websocket-api.js + * Author: Markus Grigull + * 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(); diff --git a/src/containers/visualization.js b/src/containers/visualization.js index 5257c9b..f2c33f2 100644 --- a/src/containers/visualization.js +++ b/src/containers/visualization.js @@ -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 (
diff --git a/src/components/widget.js b/src/containers/widget.js similarity index 76% rename from src/components/widget.js rename to src/containers/widget.js index 424d799..949cb4a 100644 --- a/src/components/widget.js +++ b/src/containers/widget.js @@ -1,26 +1,32 @@ /** * File: widget.js * Author: Markus Grigull - * 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 ( this.dragStop(event, ui)} > -
{widget.name}
+
{value}
); } else { return (
- {widget.name} + {value}
); } } } -export default Widget; +export default Container.create(Widget); diff --git a/src/data-managers/rest-data-manager.js b/src/data-managers/rest-data-manager.js index 87ef406..cf5a47b 100644 --- a/src/data-managers/rest-data-manager.js +++ b/src/data-managers/rest-data-manager.js @@ -1,5 +1,5 @@ /** - * File: data-manager.js + * File: rest-data-manager.js * Author: Markus Grigull * Date: 03.03.2017 * Copyright: 2017, Institute for Automation of Complex Power Systems, EONERC diff --git a/src/data-managers/simulator-data-manager.js b/src/data-managers/simulator-data-manager.js new file mode 100644 index 0000000..049eda2 --- /dev/null +++ b/src/data-managers/simulator-data-manager.js @@ -0,0 +1,72 @@ +/** + * File: simulator-data-manager.js + * Author: Markus Grigull + * 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(); diff --git a/src/stores/simulator-data-store.js b/src/stores/simulator-data-store.js new file mode 100644 index 0000000..14b426b --- /dev/null +++ b/src/stores/simulator-data-store.js @@ -0,0 +1,66 @@ +/** + * File: simulator-data-store.js + * Author: Markus Grigull + * 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();