mirror of
https://git.rwth-aachen.de/acs/public/villas/web/
synced 2025-03-09 00:00:01 +01:00
Add unique simulator sockets
Add automatic simulator-data load Add missing widget-value component
This commit is contained in:
parent
bcb92422ff
commit
7161ea7adc
9 changed files with 178 additions and 78 deletions
|
@ -8,13 +8,9 @@
|
|||
**********************************************************************************/
|
||||
|
||||
class WebsocketAPI {
|
||||
constructor() {
|
||||
this.sockets = {};
|
||||
}
|
||||
|
||||
addSocket(endpoint, identifier, callbacks) {
|
||||
addSocket(endpoint, callbacks) {
|
||||
// create web socket client
|
||||
var socket = new WebSocket('ws://' + endpoint, 'live');
|
||||
var socket = new WebSocket(this.getURL(endpoint), 'live');
|
||||
socket.binaryType = 'arraybuffer';
|
||||
|
||||
// register callbacks
|
||||
|
@ -23,13 +19,11 @@ class WebsocketAPI {
|
|||
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;
|
||||
return socket;
|
||||
}
|
||||
|
||||
removeSocket(identifier) {
|
||||
this.sockets[identifier].close();
|
||||
delete this.sockets[identifier];
|
||||
getURL(endpoint) {
|
||||
return 'ws://' + endpoint;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -9,6 +9,20 @@
|
|||
|
||||
import { Dispatcher } from 'flux';
|
||||
|
||||
const dispatcher: Dispatcher = new Dispatcher();
|
||||
class AppDispatcher extends Dispatcher {
|
||||
dispatch(payload) {
|
||||
if (this.isDispatching()) {
|
||||
// try again later
|
||||
var self = this;
|
||||
|
||||
export default dispatcher;
|
||||
setTimeout(function() {
|
||||
self.dispatch(payload);
|
||||
}, 1);
|
||||
} else {
|
||||
// do actual dispatch
|
||||
super.dispatch(payload);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default new AppDispatcher();
|
||||
|
|
34
src/components/widget-value.js
Normal file
34
src/components/widget-value.js
Normal file
|
@ -0,0 +1,34 @@
|
|||
/**
|
||||
* File: widget-value.js
|
||||
* Author: Markus Grigull <mgrigull@eonerc.rwth-aachen.de>
|
||||
* 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 EditWidgetValueDialog from './dialog-edit-widget-value';
|
||||
|
||||
class WidgetValue extends Component {
|
||||
render() {
|
||||
// calculate value
|
||||
var value = null;
|
||||
const identifier = '58bfd9facd76830327c8b6d4';
|
||||
const signal = 2;
|
||||
|
||||
if (this.props.data && this.props.data[identifier] && this.props.data[identifier].values) {
|
||||
const signalArray = this.props.data[identifier].values[signal];
|
||||
value = signalArray[signalArray.length - 1].y;
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
{this.props.widget.name}: {value}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default WidgetValue;
|
|
@ -12,8 +12,9 @@ import { Container } from 'flux/utils';
|
|||
import { DragDropContext } from 'react-dnd';
|
||||
import HTML5Backend from 'react-dnd-html5-backend';
|
||||
|
||||
// import AppDispatcher from '../app-dispatcher';
|
||||
import VillasStore from '../stores/villas-store';
|
||||
import AppDispatcher from '../app-dispatcher';
|
||||
import SimulationStore from '../stores/simulation-store';
|
||||
import SimulatorStore from '../stores/simulator-store';
|
||||
|
||||
import Header from '../components/header';
|
||||
import Footer from '../components/footer';
|
||||
|
@ -23,15 +24,68 @@ import '../styles/app.css';
|
|||
|
||||
class App extends Component {
|
||||
static getStores() {
|
||||
return [ VillasStore ];
|
||||
return [ SimulationStore, SimulatorStore ];
|
||||
}
|
||||
|
||||
static calculateState() {
|
||||
return {
|
||||
villas: VillasStore.getState()
|
||||
simulators: SimulatorStore.getState(),
|
||||
simulations: SimulationStore.getState()
|
||||
};
|
||||
}
|
||||
|
||||
componentWillMount() {
|
||||
// load all simulators and simulations to fetch simulation data
|
||||
AppDispatcher.dispatch({
|
||||
type: 'simulators/start-load'
|
||||
});
|
||||
|
||||
AppDispatcher.dispatch({
|
||||
type: 'simulations/start-load'
|
||||
});
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
if (this.state.simulators && this.state.simulations && this.state.simulations.length > 0) {
|
||||
// get list of used simulators
|
||||
var simulators = [];
|
||||
|
||||
this.state.simulations.forEach((simulation) => {
|
||||
// open connection to each simulator running a simulation model
|
||||
simulation.models.forEach((simulationModel) => {
|
||||
// add simulator to list if not already part of
|
||||
const index = simulators.findIndex((element) => {
|
||||
return element.simulator === simulationModel.simulator;
|
||||
});
|
||||
|
||||
if (index === -1) {
|
||||
simulators.push({ simulator: simulationModel.simulator, signals: simulationModel.length });
|
||||
} else {
|
||||
if (simulators[index].length < simulationModel.length) {
|
||||
simulators[index].length = simulationModel.length;
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// open connection to each simulator
|
||||
this.state.simulators.forEach((simulator) => {
|
||||
const index = simulators.findIndex((element) => {
|
||||
return element.simulator === simulator._id;
|
||||
});
|
||||
|
||||
if (index !== -1) {
|
||||
AppDispatcher.dispatch({
|
||||
type: 'simulatorData/open',
|
||||
identifier: simulator._id,
|
||||
endpoint: simulator.endpoint,
|
||||
signals: simulators[index].signals
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
// get children
|
||||
var children = this.props.children;
|
||||
|
|
|
@ -71,21 +71,6 @@ class Visualizations extends Component {
|
|||
}
|
||||
}
|
||||
|
||||
loadVisualizations(ids) {
|
||||
if (AppDispatcher.isDispatching()) {
|
||||
// try again later
|
||||
var self = this;
|
||||
setTimeout(function() {
|
||||
self.loadVisualizations(ids);
|
||||
}, 1);
|
||||
} else {
|
||||
AppDispatcher.dispatch({
|
||||
type: 'visualizations/start-load',
|
||||
data: ids
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
reloadProject() {
|
||||
// select project by param id
|
||||
this.state.projects.forEach((project) => {
|
||||
|
@ -94,7 +79,10 @@ class Visualizations extends Component {
|
|||
this.setState({ project: JSON.parse(JSON.stringify(project)) });
|
||||
|
||||
// load visualizations
|
||||
this.loadVisualizations(project.visualizations);
|
||||
AppDispatcher.dispatch({
|
||||
type: 'visualizations/start-load',
|
||||
data: project.visualizations
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
@ -15,12 +15,15 @@ import { ContextMenu, MenuItem } from 'react-contextmenu';
|
|||
import ToolboxItem from '../components/toolbox-item';
|
||||
import Dropzone from '../components/dropzone';
|
||||
import Widget from './widget';
|
||||
|
||||
import VisualizationStore from '../stores/visualization-store';
|
||||
import ProjectStore from '../stores/project-store';
|
||||
import SimulationStore from '../stores/simulation-store';
|
||||
import AppDispatcher from '../app-dispatcher';
|
||||
|
||||
class Visualization extends Component {
|
||||
static getStores() {
|
||||
return [ VisualizationStore ];
|
||||
return [ VisualizationStore, ProjectStore, SimulationStore ];
|
||||
}
|
||||
|
||||
static calculateState(prevState) {
|
||||
|
@ -38,11 +41,34 @@ class Visualization extends Component {
|
|||
visualizations: VisualizationStore.getState(),
|
||||
|
||||
visualization: {},
|
||||
simulation: null,
|
||||
editing: false,
|
||||
grid: false
|
||||
}
|
||||
}
|
||||
|
||||
componentWillMount() {
|
||||
AppDispatcher.dispatch({
|
||||
type: 'visualizations/start-load'
|
||||
});
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
if (this.state.visualization._id !== this.props.params.visualization) {
|
||||
this.reloadVisualization();
|
||||
}
|
||||
}
|
||||
|
||||
reloadVisualization() {
|
||||
// select visualization by param id
|
||||
this.state.visualizations.forEach((visualization) => {
|
||||
if (visualization._id === this.props.params.visualization) {
|
||||
// JSON.parse(JSON.stringify(obj)) = deep clone to make also copy of widget objects inside
|
||||
this.setState({ visualization: JSON.parse(JSON.stringify(visualization)) });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
handleDrop(item) {
|
||||
// add new widget
|
||||
var widget = {
|
||||
|
@ -99,34 +125,6 @@ class Visualization extends Component {
|
|||
this.forceUpdate();
|
||||
}
|
||||
|
||||
componentWillMount() {
|
||||
AppDispatcher.dispatch({
|
||||
type: 'visualizations/start-load'
|
||||
});
|
||||
|
||||
AppDispatcher.dispatch({
|
||||
type: 'simulatorData/open',
|
||||
endpoint: 'localhost:5000',
|
||||
identifier: 'RTDS'
|
||||
});
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
if (this.state.visualization._id !== this.props.params.visualization) {
|
||||
this.reloadVisualization();
|
||||
}
|
||||
}
|
||||
|
||||
reloadVisualization() {
|
||||
// select visualization by param id
|
||||
this.state.visualizations.forEach((visualization) => {
|
||||
if (visualization._id === this.props.params.visualization) {
|
||||
// JSON.parse(JSON.stringify(obj)) = deep clone to make also copy of widget objects inside
|
||||
this.setState({ visualization: JSON.parse(JSON.stringify(visualization)) });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
|
|
|
@ -84,7 +84,7 @@ class Widget extends Component {
|
|||
} else {
|
||||
return (
|
||||
<div className="widget" style={{ width: Number(widget.width), height: Number(widget.height), left: Number(widget.x), top: Number(widget.y), position: 'absolute' }}>
|
||||
<WidgetValue widget={widget} data={this.state.simulatorData} />
|
||||
<WidgetValue widget={widget} data={this.state.simulatorData} sequence={this.state.sequence} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/**
|
||||
* File: simulator-data-manager.js
|
||||
* File: simulator-data-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
|
||||
|
@ -10,34 +10,47 @@
|
|||
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) });
|
||||
class SimulatorDataDataManager {
|
||||
constructor() {
|
||||
this._sockets = {};
|
||||
}
|
||||
|
||||
onOpen(event) {
|
||||
// TODO: Add identifiers to callbacks
|
||||
open(endpoint, identifier, signals) {
|
||||
// pass signals to onOpen callback
|
||||
if (this._sockets[identifier] != null) {
|
||||
if (this._sockets[identifier].url !== WebsocketAPI.getURL(endpoint)) {
|
||||
// replace connection, since endpoint changed
|
||||
this._sockets.close();
|
||||
|
||||
this._sockets[identifier] = WebsocketAPI.addSocket(endpoint, { onOpen: (event) => this.onOpen(event, identifier, signals), onClose: (event) => this.onClose(event, identifier), onMessage: (event) => this.onMessage(event, identifier) });
|
||||
}
|
||||
} else {
|
||||
this._sockets[identifier] = WebsocketAPI.addSocket(endpoint, { onOpen: (event) => this.onOpen(event, identifier, signals), onClose: (event) => this.onClose(event, identifier), onMessage: (event) => this.onMessage(event, identifier) });
|
||||
}
|
||||
}
|
||||
|
||||
onOpen(event, identifier, signals) {
|
||||
AppDispatcher.dispatch({
|
||||
type: 'simulatorData/opened',
|
||||
identifier: 'RTDS',
|
||||
signals: 8
|
||||
identifier: identifier,
|
||||
signals: signals
|
||||
});
|
||||
}
|
||||
|
||||
onClose(event) {
|
||||
onClose(event, identifier) {
|
||||
AppDispatcher.dispatch({
|
||||
type: 'simulatorData/closed'
|
||||
type: 'simulatorData/closed',
|
||||
identifier: identifier
|
||||
});
|
||||
}
|
||||
|
||||
onMessage(event) {
|
||||
onMessage(event, identifier) {
|
||||
var message = this.bufferToMessage(event.data);
|
||||
|
||||
AppDispatcher.dispatch({
|
||||
type: 'simulatorData/data-changed',
|
||||
data: message,
|
||||
identifier: 'RTDS'
|
||||
identifier: identifier
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -69,4 +82,4 @@ class SimulationDataManager {
|
|||
}
|
||||
}
|
||||
|
||||
export default new SimulationDataManager();
|
||||
export default new SimulatorDataDataManager();
|
|
@ -10,7 +10,7 @@
|
|||
import { ReduceStore } from 'flux/utils';
|
||||
|
||||
import AppDispatcher from '../app-dispatcher';
|
||||
import SimulatorDataManager from '../data-managers/simulator-data-manager';
|
||||
import SimulatorDataDataManager from '../data-managers/simulator-data-data-manager';
|
||||
|
||||
const MAX_VALUES = 10000;
|
||||
|
||||
|
@ -28,7 +28,7 @@ class SimulationDataStore extends ReduceStore {
|
|||
|
||||
switch (action.type) {
|
||||
case 'simulatorData/open':
|
||||
SimulatorDataManager.open(action.endpoint, action.identifier);
|
||||
SimulatorDataDataManager.open(action.endpoint, action.identifier, action.signals);
|
||||
return state;
|
||||
|
||||
case 'simulatorData/opened':
|
||||
|
@ -63,6 +63,11 @@ class SimulationDataStore extends ReduceStore {
|
|||
return state;
|
||||
|
||||
case 'simulatorData/closed':
|
||||
// close and delete socket
|
||||
state[action.identifier].close();
|
||||
state[action.identifier] = null;
|
||||
|
||||
this.__emitChange();
|
||||
return state;
|
||||
|
||||
default:
|
||||
|
|
Loading…
Add table
Reference in a new issue