1
0
Fork 0
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:
Markus Grigull 2017-03-08 15:41:53 +01:00
parent bcb92422ff
commit 7161ea7adc
9 changed files with 178 additions and 78 deletions

View file

@ -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;
}
}

View file

@ -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();

View 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;

View file

@ -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;

View file

@ -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
});
}
});
}

View file

@ -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>

View file

@ -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>
);
}

View file

@ -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();

View file

@ -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: