From e7904d2bd727bc81eaf62a9daf2c686897517ba6 Mon Sep 17 00:00:00 2001 From: Markus Grigull Date: Tue, 6 Feb 2018 00:38:32 +0100 Subject: [PATCH] Add reverse channel for simulation data --- .gitignore | 2 +- src/api/websocket-api.js | 8 +- .../dialog/edit-simulation-model.js | 85 ++++++++++++------ .../dialog/edit-widget-control-creator.js | 16 +++- .../dialog/edit-widget-signal-control.js | 7 +- .../dialog/edit-widget-signals-control.js | 2 +- .../dialog/import-simulation-model.js | 85 ++++++++++++------ src/components/dialog/new-simulation-model.js | 86 +++++++++++++------ src/components/widget-factory.js | 6 ++ src/components/widget-gauge.js | 9 +- src/components/widget-lamp.js | 4 +- src/components/widget-plot-table.js | 4 +- src/components/widget-plot.js | 4 +- src/components/widget-slider.js | 8 +- src/components/widget-table.js | 14 +-- src/components/widget-value.js | 8 +- src/containers/visualization.js | 6 +- src/containers/widget.js | 11 ++- src/data-managers/rest-data-manager.js | 9 ++ src/data-managers/simulations-data-manager.js | 22 ++++- .../simulator-data-data-manager.js | 52 +++++++++-- src/stores/simulator-data-store.js | 56 +++++++++--- 22 files changed, 367 insertions(+), 137 deletions(-) diff --git a/.gitignore b/.gitignore index 0ca9d3f..801e1dd 100644 --- a/.gitignore +++ b/.gitignore @@ -16,4 +16,4 @@ npm-debug.log* yarn-debug.log* yarn-error.log* .vscode/ - +*.code-workspace diff --git a/src/api/websocket-api.js b/src/api/websocket-api.js index 00d2f60..73b2dd4 100644 --- a/src/api/websocket-api.js +++ b/src/api/websocket-api.js @@ -26,10 +26,10 @@ class WebsocketAPI { 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)); + if (callbacks.onOpen) socket.onopen = callbacks.onOpen; + if (callbacks.onClose) socket.onclose = callbacks.onClose; + if (callbacks.onMessage) socket.onmessage = callbacks.onMessage; + if (callbacks.onError) socket.onerror = callbacks.onError; return socket; } diff --git a/src/components/dialog/edit-simulation-model.js b/src/components/dialog/edit-simulation-model.js index 37cf0d7..9aacadd 100644 --- a/src/components/dialog/edit-simulation-model.js +++ b/src/components/dialog/edit-simulation-model.js @@ -20,7 +20,7 @@ ******************************************************************************/ import React from 'react'; -import { FormGroup, FormControl, ControlLabel } from 'react-bootstrap'; +import { FormGroup, FormControl, ControlLabel, HelpBlock } from 'react-bootstrap'; import Table from '../table'; import TableColumn from '../table-column'; @@ -35,8 +35,10 @@ class EditSimulationModelDialog extends React.Component { this.state = { name: '', simulator: { node: '', simulator: '' }, - length: 1, - mapping: [{ name: 'Signal', type: 'Type' }] + outputLength: 1, + inputLength: 1, + outputMapping: [{ name: 'Signal', type: 'Type' }], + inputMapping: [{ name: 'Signal', type: 'Type' }] } } @@ -51,16 +53,24 @@ class EditSimulationModelDialog extends React.Component { } handleChange(e) { - if (e.target.id === 'length') { + let mapping = null; + + if (e.target.id === 'outputLength') { + mapping = this.state.outputMapping; + } else if (e.target.id === 'inputLength') { + mapping = this.state.inputMapping; + } + + if (mapping != null) { // change mapping size - if (e.target.value > this.state.mapping.length) { + if (e.target.value > mapping.length) { // add missing signals - while (this.state.mapping.length < e.target.value) { - this.state.mapping.push({ name: 'Signal', type: 'Type' }); + while (mapping.length < e.target.value) { + mapping.push({ name: 'Signal', type: 'Type' }); } } else { // remove signals - this.state.mapping.splice(e.target.value, this.state.mapping.length - e.target.value); + mapping.splice(e.target.value, mapping.length - e.target.value); } } @@ -72,8 +82,8 @@ class EditSimulationModelDialog extends React.Component { } } - handleMappingChange(event, row, column) { - var mapping = this.state.mapping; + handleMappingChange(key, event, row, column) { + const mapping = this.state[key]; if (column === 1) { mapping[row].name = event.target.value; @@ -81,37 +91,45 @@ class EditSimulationModelDialog extends React.Component { mapping[row].type = event.target.value; } - this.setState({ mapping: mapping }); + this.setState({ [key]: mapping }); } resetState() { this.setState({ name: this.props.data.name, simulator: this.props.data.simulator, - length: this.props.data.length, - mapping: this.props.data.mapping + outputLength: this.props.data.outputLength, + inputLength: this.props.data.inputLength, + outputMapping: this.props.data.outputMapping, + inputMapping: this.props.data.inputMapping }); } validateForm(target) { // check all controls var name = true; - var length = true; + let inputLength = true; + let outputLength = true; if (this.state.name === '') { name = false; } // test if simulatorid is a number (in a string, not type of number) - if (!/^\d+$/.test(this.state.length)) { - length = false; + if (!/^\d+$/.test(this.state.outputLength)) { + outputLength = false; } - this.valid = name && length; + if (!/^\d+$/.test(this.state.inputLength)) { + inputLength = false; + } + + this.valid = name && inputLength && outputLength; // return state to control if (target === 'name') return name ? "success" : "error"; - else if (target === 'length') return length ? "success" : "error"; + else if (target === 'outputLength') return outputLength ? "success" : "error"; + else if (target === 'inputLength') return inputLength ? "success" : "error"; } render() { @@ -133,17 +151,32 @@ class EditSimulationModelDialog extends React.Component { ))} - - Length - this.handleChange(e)} /> + + Output Length + this.handleChange(e)} /> - - Mapping - + + Output Mapping + Click Name or Type cell to edit +
- this.handleMappingChange(event, row, column)} /> - this.handleMappingChange(event, row, column)} /> + this.handleMappingChange('outputMapping', event, row, column)} /> + this.handleMappingChange('outputMapping', event, row, column)} /> +
+
+ + Input Length + this.handleChange(e)} /> + + + + Input Mapping + Click Name or Type cell to edit + + + this.handleMappingChange('inputMapping', event, row, column)} /> + this.handleMappingChange('inputMapping', event, row, column)} />
diff --git a/src/components/dialog/edit-widget-control-creator.js b/src/components/dialog/edit-widget-control-creator.js index 2c503ba..99ef3ba 100644 --- a/src/components/dialog/edit-widget-control-creator.js +++ b/src/components/dialog/edit-widget-control-creator.js @@ -117,13 +117,17 @@ export default function createControls(widgetType = null, widget = null, session break; case 'Slider': dialogControls.push( - validateForm(id)} simulation={simulation} handleChange={(e) => handleChange(e)} /> + validateForm(id)} simulation={simulation} handleChange={(e) => handleChange(e)} />, + validateForm(id)} simulation={simulation} handleChange={(e) => valueBoundOnChange(e)} />, + validateForm(id)} simulation={simulation} handleChange={(e) => handleChange(e)} /> ); break; case 'Button': dialogControls.push( validateForm(id)} handleChange={(e) => handleChange(e)} />, - validateForm(id)} handleChange={(e) => handleChange(e)} /> + validateForm(id)} handleChange={(e) => handleChange(e)} />, + validateForm(id)} simulation={simulation} handleChange={(e) => valueBoundOnChange(e)} />, + validateForm(id)} simulation={simulation} handleChange={(e) => handleChange(e)} /> ); break; case 'Box': @@ -151,6 +155,14 @@ export default function createControls(widgetType = null, widget = null, session ); break; + case 'NumberInput': + dialogControls.push( + validateForm(id)} handleChange={e => handleChange(e)} />, + validateForm(id)} simulation={simulation} handleChange={(e) => valueBoundOnChange(e)} />, + validateForm(id)} simulation={simulation} handleChange={(e) => handleChange(e)} /> + ); + break; + default: console.log('Non-valid widget type: ' + widgetType); } diff --git a/src/components/dialog/edit-widget-signal-control.js b/src/components/dialog/edit-widget-signal-control.js index 98d9b06..b0deda1 100644 --- a/src/components/dialog/edit-widget-signal-control.js +++ b/src/components/dialog/edit-widget-signal-control.js @@ -46,7 +46,12 @@ class EditWidgetSignalControl extends Component { const simulationModel = this.props.simulation.models.find( model => model.simulator.node === this.state.widget.simulator.node && model.simulator.simulator === this.state.widget.simulator.simulator ); // If simulation model update the signals to render - signalsToRender = simulationModel ? simulationModel.mapping : []; + if (this.props.input) { + signalsToRender = simulationModel ? simulationModel.inputMapping : []; + } else { + signalsToRender = simulationModel ? simulationModel.outputMapping : []; + } + } return ( diff --git a/src/components/dialog/edit-widget-signals-control.js b/src/components/dialog/edit-widget-signals-control.js index c52b928..7a0a595 100644 --- a/src/components/dialog/edit-widget-signals-control.js +++ b/src/components/dialog/edit-widget-signals-control.js @@ -61,7 +61,7 @@ class EditWidgetSignalsControl extends Component { const simulationModel = this.props.simulation.models.find( model => model.simulator.node === this.state.widget.simulator.node && model.simulator.simulator === this.state.widget.simulator.simulator ); // If simulation model update the signals to render - signalsToRender = simulationModel? simulationModel.mapping : []; + signalsToRender = simulationModel? simulationModel.outputMapping : []; } return ( diff --git a/src/components/dialog/import-simulation-model.js b/src/components/dialog/import-simulation-model.js index c2ce502..196e885 100644 --- a/src/components/dialog/import-simulation-model.js +++ b/src/components/dialog/import-simulation-model.js @@ -20,7 +20,7 @@ ******************************************************************************/ import React from 'react'; -import { FormGroup, FormControl, ControlLabel } from 'react-bootstrap'; +import { FormGroup, FormControl, ControlLabel, HelpBlock } from 'react-bootstrap'; import Table from '../table'; import TableColumn from '../table-column'; @@ -36,8 +36,10 @@ class ImportSimulationModelDialog extends React.Component { this.state = { name: '', simulator: { node: '', simulator: '' }, - length: '1', - mapping: [ { name: 'Signal', type: 'Type' } ] + outputLength: '1', + inputLength: '1', + outputMapping: [ { name: 'Signal', type: 'Type' } ], + inputMapping: [{ name: 'Signal', type: 'Type' }] }; } @@ -53,24 +55,34 @@ class ImportSimulationModelDialog extends React.Component { this.setState({ name: '', simulator: { node: this.props.nodes[0] ? this.props.nodes[0]._id : '', simulator: this.props.nodes[0].simulators[0] ? 0 : '' }, - length: '1', - mapping: [ { name: 'Signal', type: 'Type' } ] + outputLength: '1', + inputLength: '1', + outputMapping: [{ name: 'Signal', type: 'Type' }], + inputMapping: [{ name: 'Signal', type: 'Type' }] }); this.imported = false; } handleChange(e) { - if (e.target.id === 'length') { + let mapping = null; + + if (e.target.id === 'outputLength') { + mapping = this.state.outputMapping; + } else if (e.target.id === 'inputLength') { + mapping = this.state.inputMapping; + } + + if (mapping != null) { // change mapping size - if (e.target.value > this.state.mapping.length) { + if (e.target.value > mapping.length) { // add missing signals - while (this.state.mapping.length < e.target.value) { - this.state.mapping.push({ name: 'Signal', type: 'Type' }); + while (mapping.length < e.target.value) { + mapping.push({ name: 'Signal', type: 'Type' }); } } else { // remove signals - this.state.mapping.splice(e.target.value, this.state.mapping.length - e.target.value); + mapping.splice(e.target.value, mapping.length - e.target.value); } } @@ -81,8 +93,8 @@ class ImportSimulationModelDialog extends React.Component { } } - handleMappingChange(event, row, column) { - var mapping = this.state.mapping; + handleMappingChange(key, event, row, column) { + const mapping = this.state[key]; if (column === 1) { mapping[row].name = event.target.value; @@ -90,7 +102,7 @@ class ImportSimulationModelDialog extends React.Component { mapping[row].type = event.target.value; } - this.setState({ mapping: mapping }); + this.setState({ [key]: mapping }); } loadFile(fileList) { @@ -119,7 +131,8 @@ class ImportSimulationModelDialog extends React.Component { validateForm(target) { // check all controls var name = true; - var length = true; + let inputLength = true; + let outputLength = true; var simulator = true; if (this.state.name === '') { @@ -131,15 +144,20 @@ class ImportSimulationModelDialog extends React.Component { } // test if simulatorid is a number (in a string, not type of number) - if (!/^\d+$/.test(this.state.length)) { - length = false; + if (!/^\d+$/.test(this.state.outputLength)) { + outputLength = false; } - this.valid = name && length && simulator; + if (!/^\d+$/.test(this.state.inputLength)) { + inputLength = false; + } + + this.valid = name && inputLength && outputLength && simulator; // return state to control if (target === 'name') return name ? "success" : "error"; - else if (target === 'length') return length ? "success" : "error"; + else if (target === 'outputLength') return outputLength ? "success" : "error"; + else if (target === 'inputLength') return inputLength ? "success" : "error"; else if (target === 'simulator') return simulator ? "success" : "error"; } @@ -167,17 +185,32 @@ class ImportSimulationModelDialog extends React.Component { ))}
- - Length - this.handleChange(e)} /> + + Output Length + this.handleChange(e)} /> - - Mapping - + + Output Mapping + Click Name or Type cell to edit +
- this.handleMappingChange(event, row, column)} /> - this.handleMappingChange(event, row, column)} /> + this.handleMappingChange('outputMapping', event, row, column)} /> + this.handleMappingChange('outputMapping', event, row, column)} /> +
+
+ + Input Length + this.handleChange(e)} /> + + + + Input Mapping + Click Name or Type cell to edit + + + this.handleMappingChange('inputMapping', event, row, column)} /> + this.handleMappingChange('inputMapping', event, row, column)} />
diff --git a/src/components/dialog/new-simulation-model.js b/src/components/dialog/new-simulation-model.js index 9538b30..e94f562 100644 --- a/src/components/dialog/new-simulation-model.js +++ b/src/components/dialog/new-simulation-model.js @@ -35,8 +35,10 @@ class NewSimulationModelDialog extends React.Component { this.state = { name: '', simulator: { node: '', simulator: '' }, - length: '1', - mapping: [ { name: 'Signal', type: 'Type' } ] + outputLength: '1', + inputLength: '1', + outputMapping: [ { name: 'Signal', type: 'Type' } ], + inputMapping: [ { name: 'Signal', type: 'Type' } ] }; } @@ -51,16 +53,24 @@ class NewSimulationModelDialog extends React.Component { } handleChange(e) { - if (e.target.id === 'length') { + let mapping = null; + + if (e.target.id === 'outputLength') { + mapping = this.state.outputMapping; + } else if (e.target.id === 'inputLength') { + mapping = this.state.inputMapping; + } + + if (mapping != null) { // change mapping size - if (e.target.value > this.state.mapping.length) { + if (e.target.value > mapping.length) { // add missing signals - while (this.state.mapping.length < e.target.value) { - this.state.mapping.push({ name: 'Signal', type: 'Type' }); + while (mapping.length < e.target.value) { + mapping.push({ name: 'Signal', type: 'Type' }); } } else { // remove signals - this.state.mapping.splice(e.target.value, this.state.mapping.length - e.target.value); + mapping.splice(e.target.value, mapping.length - e.target.value); } } @@ -71,8 +81,8 @@ class NewSimulationModelDialog extends React.Component { } } - handleMappingChange(event, row, column) { - var mapping = this.state.mapping; + handleMappingChange(key, event, row, column) { + const mapping = this.state[key]; if (column === 1) { mapping[row].name = event.target.value; @@ -80,23 +90,26 @@ class NewSimulationModelDialog extends React.Component { mapping[row].type = event.target.value; } - this.setState({ mapping: mapping }); + this.setState({ [key]: mapping }); } resetState() { this.setState({ name: '', simulator: { node: this.props.nodes[0] ? this.props.nodes[0]._id : '', simulator: this.props.nodes[0].simulators[0] ? 0 : '' }, - length: '1', - mapping: [ { name: 'Signal', type: 'Type' } ] + outputLength: '1', + inputLength: '1', + outputMapping: [{ name: 'Signal', type: 'Type' }], + inputMapping: [{ name: 'Signal', type: 'Type' }] }); } validateForm(target) { // check all controls - var name = true; - var length = true; - var simulator = true; + let name = true; + let inputLength = true; + let outputLength = true; + let simulator = true; if (this.state.name === '') { name = false; @@ -107,15 +120,20 @@ class NewSimulationModelDialog extends React.Component { } // test if simulatorid is a number (in a string, not type of number) - if (!/^\d+$/.test(this.state.length)) { - length = false; + if (!/^\d+$/.test(this.state.outputLength)) { + outputLength = false; } - this.valid = name && length && simulator; + if (!/^\d+$/.test(this.state.inputLength)) { + inputLength = false; + } + + this.valid = name && inputLength && outputLength && simulator; // return state to control if (target === 'name') return name ? "success" : "error"; - else if (target === 'length') return length ? "success" : "error"; + else if (target === 'outputLength') return outputLength ? "success" : "error"; + else if (target === 'inputLength') return inputLength ? "success" : "error"; else if (target === 'simulator') return simulator ? "success" : "error"; } @@ -138,18 +156,32 @@ class NewSimulationModelDialog extends React.Component { ))}
- - Length - this.handleChange(e)} /> + + Output Length + this.handleChange(e)} /> - - Mapping + + Output Mapping Click Name or Type cell to edit - +
- this.handleMappingChange(event, row, column)} /> - this.handleMappingChange(event, row, column)} /> + this.handleMappingChange('outputMapping', event, row, column)} /> + this.handleMappingChange('outputMapping', event, row, column)} /> +
+
+ + Input Length + this.handleChange(e)} /> + + + + Input Mapping + Click Name or Type cell to edit + + + this.handleMappingChange('inputMapping', event, row, column)} /> + this.handleMappingChange('inputMapping', event, row, column)} />
diff --git a/src/components/widget-factory.js b/src/components/widget-factory.js index 9cae22a..87f31dd 100644 --- a/src/components/widget-factory.js +++ b/src/components/widget-factory.js @@ -105,12 +105,16 @@ class WidgetFactory { widget.height = 100; widget.background_color = 1; widget.font_color = 0; + widget.simulator = defaultSimulator; + widget.signal = 0; break; case 'NumberInput': widget.minWidth = 200; widget.minHeight = 50; widget.width = 200; widget.height = 50; + widget.simulator = defaultSimulator; + widget.signal = 0; break; case 'Slider': widget.minWidth = 380; @@ -118,6 +122,8 @@ class WidgetFactory { widget.width = 400; widget.height = 50; widget.orientation = WidgetSlider.OrientationTypes.HORIZONTAL.value; // Assign default orientation + widget.simulator = defaultSimulator; + widget.signal = 0; break; case 'Gauge': widget.simulator = defaultSimulator; diff --git a/src/components/widget-gauge.js b/src/components/widget-gauge.js index 5fc4153..cb23d31 100644 --- a/src/components/widget-gauge.js +++ b/src/components/widget-gauge.js @@ -40,15 +40,14 @@ class WidgetGauge extends Component { if (nextProps.data == null || nextProps.data[simulator.node] == null || nextProps.data[simulator.node][simulator.simulator] == null - || nextProps.data[simulator.node][simulator.simulator].length === 0 - || nextProps.data[simulator.node][simulator.simulator].values.length === 0 - || nextProps.data[simulator.node][simulator.simulator].values[0].length === 0) { + || nextProps.data[simulator.node][simulator.simulator].output.values.length === 0 + || nextProps.data[simulator.node][simulator.simulator].output.values[0].length === 0) { this.setState({ value: 0 }); return; } // check if value has changed - const signal = nextProps.data[simulator.node][simulator.simulator].values[nextProps.widget.signal]; + const signal = nextProps.data[simulator.node][simulator.simulator].output.values[nextProps.widget.signal]; // Take just 3 decimal positions // Note: Favor this method over Number.toFixed(n) in order to avoid a type conversion, since it returns a String if (signal != null) { @@ -180,7 +179,7 @@ class WidgetGauge extends Component { if (this.props.simulation) { const simulationModel = this.props.simulation.models.filter((model) => model.simulator.node === this.props.widget.simulator.node && model.simulator.simulator === this.props.widget.simulator.simulator)[0]; - signalType = (simulationModel != null && simulationModel.length > 0 && this.props.widget.signal < simulationModel.length) ? simulationModel.mapping[this.props.widget.signal].type : ''; + signalType = (simulationModel != null && simulationModel.length > 0 && this.props.widget.signal < simulationModel.length) ? simulationModel.outputMapping[this.props.widget.signal].type : ''; } return ( diff --git a/src/components/widget-lamp.js b/src/components/widget-lamp.js index 83d228c..db2fa06 100644 --- a/src/components/widget-lamp.js +++ b/src/components/widget-lamp.js @@ -38,13 +38,13 @@ class WidgetLamp extends Component { const simulator = nextProps.widget.simulator.simulator; const node = nextProps.widget.simulator.node; - if (nextProps.data == null || nextProps.data[node] == null || nextProps.data[node][simulator] == null || nextProps.data[node][simulator].values == null) { + if (nextProps.data == null || nextProps.data[node] == null || nextProps.data[node][simulator] == null || nextProps.data[node][simulator].output.values == null) { this.setState({ value: '' }); return; } // check if value has changed - const signal = nextProps.data[node][simulator].values[nextProps.widget.signal]; + const signal = nextProps.data[node][simulator].output.values[nextProps.widget.signal]; if (signal != null && this.state.value !== signal[signal.length - 1].y) { this.setState({ value: signal[signal.length - 1].y }); } diff --git a/src/components/widget-plot-table.js b/src/components/widget-plot-table.js index 3d32284..3cba4c7 100644 --- a/src/components/widget-plot-table.js +++ b/src/components/widget-plot-table.js @@ -71,7 +71,7 @@ class WidgetPlotTable extends Component { // Proceed if a simulation model is available if (simulationModel) { // Create checkboxes using the signal indices from simulation model - preselectedSignals = simulationModel.mapping.reduce( + preselectedSignals = simulationModel.outputMapping.reduce( // Loop through simulation model signals (accum, model_signal, signal_index) => { // Append them if they belong to the current selected type @@ -107,7 +107,7 @@ class WidgetPlotTable extends Component { let simulatorData = []; if (this.props.data[simulator.node] != null && this.props.data[simulator.node][simulator.simulator] != null) { - simulatorData = this.props.data[simulator.node][simulator.simulator].values.filter((values, index) => ( + simulatorData = this.props.data[simulator.node][simulator.simulator].output.values.filter((values, index) => ( this.props.widget.signals.findIndex(value => value === index) !== -1 )); } diff --git a/src/components/widget-plot.js b/src/components/widget-plot.js index 4aaad39..c492f3f 100644 --- a/src/components/widget-plot.js +++ b/src/components/widget-plot.js @@ -43,12 +43,12 @@ class WidgetPlot extends React.Component { const model = simulation.models.find(model => model.simulator.node === simulator.node && model.simulator.simulator === simulator.simulator); const chosenSignals = nextProps.widget.signals; - const data = nextProps.data[simulator.node][simulator.simulator].values.filter((values, index) => ( + const data = nextProps.data[simulator.node][simulator.simulator].output.values.filter((values, index) => ( nextProps.widget.signals.findIndex(value => value === index) !== -1 )); // Query the signals that will be displayed in the legend - const legend = model.mapping.reduce( (accum, model_signal, signal_index) => { + const legend = model.outputMapping.reduce( (accum, model_signal, signal_index) => { if (chosenSignals.includes(signal_index)) { accum.push({ index: signal_index, name: model_signal.name, type: model_signal.type }); } diff --git a/src/components/widget-slider.js b/src/components/widget-slider.js index 2ea720c..d242de9 100644 --- a/src/components/widget-slider.js +++ b/src/components/widget-slider.js @@ -55,11 +55,9 @@ class WidgetSlider extends Component { } valueChanged(newValue) { - // Enable to propagate action - // let newWidget = Object.assign({}, this.props.widget, { - // value: newValue - // }); - // this.props.onWidgetChange(newWidget); + if (this.props.onInputChanged) { + this.props.onInputChanged(newValue); + } } render() { diff --git a/src/components/widget-table.js b/src/components/widget-table.js index e72bca7..625b84b 100644 --- a/src/components/widget-table.js +++ b/src/components/widget-table.js @@ -40,9 +40,9 @@ class WidgetTable extends Component { if (nextProps.simulation == null || nextProps.data == null || nextProps.data[simulator.node] == null || nextProps.data[simulator.node][simulator.simulator] == null - || nextProps.data[simulator.node][simulator.simulator].length === 0 - || nextProps.data[simulator.node][simulator.simulator].values.length === 0 - || nextProps.data[simulator.node][simulator.simulator].values[0].length === 0) { + || nextProps.data[simulator.node][simulator.simulator].output.length === 0 + || nextProps.data[simulator.node][simulator.simulator].output.values.length === 0 + || nextProps.data[simulator.node][simulator.simulator].output.values[0].length === 0) { // clear values this.setState({ rows: [], sequence: null }); return; @@ -61,16 +61,16 @@ class WidgetTable extends Component { // get rows var rows = []; - nextProps.data[simulator.node][simulator.simulator].values.forEach((signal, index) => { - if (index < simulationModel.mapping.length) { + nextProps.data[simulator.node][simulator.simulator].output.values.forEach((signal, index) => { + if (index < simulationModel.outputMapping.length) { rows.push({ - name: simulationModel.mapping[index].name, + name: simulationModel.outputMapping[index].name, value: signal[signal.length - 1].y.toFixed(3) }); } }); - this.setState({ rows: rows, sequence: nextProps.data[simulator.node][simulator.simulator].sequence }); + this.setState({ rows: rows, sequence: nextProps.data[simulator.node][simulator.simulator].output.sequence }); } render() { diff --git a/src/components/widget-value.js b/src/components/widget-value.js index 4744d22..fc8b6d0 100644 --- a/src/components/widget-value.js +++ b/src/components/widget-value.js @@ -36,7 +36,7 @@ class WidgetValue extends Component { const simulator = nextProps.widget.simulator.simulator; const node = nextProps.widget.simulator.node; - if (nextProps.data == null || nextProps.data[node] == null || nextProps.data[node][simulator] == null || nextProps.data[node][simulator].values == null) { + if (nextProps.data == null || nextProps.data[node] == null || nextProps.data[node][simulator] == null || nextProps.data[node][simulator].output.values == null) { this.setState({ value: '' }); return; } @@ -47,13 +47,13 @@ class WidgetValue extends Component { if (nextProps.simulation) { const simulationModel = nextProps.simulation.models.find(model => model.simulator.node === node && model.simulator.simulator === simulator); - if (nextProps.widget.signal < simulationModel.mapping.length) { - unit = simulationModel.mapping[nextProps.widget.signal].type; + if (nextProps.widget.signal < simulationModel.outputMapping.length) { + unit = simulationModel.outputMapping[nextProps.widget.signal].type; } } // check if value has changed - const signal = nextProps.data[node][simulator].values[nextProps.widget.signal]; + const signal = nextProps.data[node][simulator].output.values[nextProps.widget.signal]; if (signal != null && this.state.value !== signal[signal.length - 1].y) { this.setState({ value: signal[signal.length - 1].y, unit }); } diff --git a/src/containers/visualization.js b/src/containers/visualization.js index 1f30c13..b42720f 100644 --- a/src/containers/visualization.js +++ b/src/containers/visualization.js @@ -480,9 +480,9 @@ class Visualization extends React.Component { - - - + + + diff --git a/src/containers/widget.js b/src/containers/widget.js index 7ebd0aa..bab9d79 100644 --- a/src/containers/widget.js +++ b/src/containers/widget.js @@ -153,6 +153,15 @@ class Widget extends React.Component { } } + inputDataChanged(widget, data) { + AppDispatcher.dispatch({ + type: 'simulatorData/inputChanged', + simulator: widget.simulator, + signal: widget.signal, + data + }); + } + render() { // configure grid const grid = [this.props.grid, this.props.grid]; @@ -182,7 +191,7 @@ class Widget extends React.Component { } else if (widget.type === 'NumberInput') { element = } else if (widget.type === 'Slider') { - element = this.props.onWidgetStatusChange(w, this.props.index) } /> + element = this.props.onWidgetStatusChange(w, this.props.index) } onInputChanged={(value) => this.inputDataChanged(widget, value)} /> } else if (widget.type === 'Gauge') { element = } else if (widget.type === 'Box') { diff --git a/src/data-managers/rest-data-manager.js b/src/data-managers/rest-data-manager.js index 2ae0a46..dbd278e 100644 --- a/src/data-managers/rest-data-manager.js +++ b/src/data-managers/rest-data-manager.js @@ -29,6 +29,7 @@ class RestDataManager { this.url = url; this.type = type; this.keyFilter = keyFilter; + this.onLoad = null; } makeURL(part) { @@ -61,6 +62,10 @@ class RestDataManager { type: this.type + 's/loaded', data: data }); + + if (this.onLoad != null) { + this.onLoad(data); + } }).catch(error => { AppDispatcher.dispatch({ type: this.type + 's/load-error', @@ -78,6 +83,10 @@ class RestDataManager { type: this.type + 's/loaded', data: data }); + + if (this.onLoad != null) { + this.onLoad(data); + } }).catch(error => { AppDispatcher.dispatch({ type: this.type + 's/load-error', diff --git a/src/data-managers/simulations-data-manager.js b/src/data-managers/simulations-data-manager.js index d5fdfde..bb9a6ba 100644 --- a/src/data-managers/simulations-data-manager.js +++ b/src/data-managers/simulations-data-manager.js @@ -20,5 +20,25 @@ ******************************************************************************/ import RestDataManager from './rest-data-manager'; +import AppDispatcher from '../app-dispatcher'; -export default new RestDataManager('simulation', '/simulations', [ '_id', 'name', 'projects', 'models' ]); +class SimulationsDataManager extends RestDataManager { + constructor() { + super('simulation', '/simulations', [ '_id', 'name', 'projects', 'models' ]); + + this.onLoad = this.onSimulationsLoad; + } + + onSimulationsLoad(simulation) { + for (let model of simulation.models) { + AppDispatcher.dispatch({ + type: 'simulatorData/prepare', + inputLength: parseInt(model.inputLength, 10), + outputLength: parseInt(model.outputLength, 10), + node: model.simulator + }); + } + } +} + +export default new SimulationsDataManager(); diff --git a/src/data-managers/simulator-data-data-manager.js b/src/data-managers/simulator-data-data-manager.js index 2f88c02..eee8e03 100644 --- a/src/data-managers/simulator-data-data-manager.js +++ b/src/data-managers/simulator-data-data-manager.js @@ -22,6 +22,9 @@ import WebsocketAPI from '../api/websocket-api'; import AppDispatcher from '../app-dispatcher'; +const OFFSET_TYPE = 2; +const OFFSET_VERSION = 4; + class SimulatorDataDataManager { constructor() { this._sockets = {}; @@ -34,14 +37,14 @@ class SimulatorDataDataManager { // replace connection, since endpoint changed this._sockets.close(); - this._sockets[node._id] = WebsocketAPI.addSocket(node, { onOpen: (event) => this.onOpen(event, node), onClose: (event) => this.onClose(event, node), onMessage: (event) => this.onMessage(event, node) }); + this._sockets[node._id] = WebsocketAPI.addSocket(node, { onOpen: (event) => this.onOpen(event, node), onClose: (event) => this.onClose(event, node), onMessage: (event) => this.onMessage(event, node), onError: (error) => this.onError(error, node) }); } } else { // set flag if a socket to this simulator was already create before if (this._sockets[node._id] === null) { - this._sockets[node._id] = WebsocketAPI.addSocket(node, { onOpen: (event) => this.onOpen(event, node, false), onClose: (event) => this.onClose(event, node), onMessage: (event) => this.onMessage(event, node) }); + this._sockets[node._id] = WebsocketAPI.addSocket(node, { onOpen: (event) => this.onOpen(event, node, false), onClose: (event) => this.onClose(event, node), onMessage: (event) => this.onMessage(event, node), onError: (error) => this.onError(error, node) }); } else { - this._sockets[node._id] = WebsocketAPI.addSocket(node, { onOpen: (event) => this.onOpen(event, node, true), onClose: (event) => this.onClose(event, node), onMessage: (event) => this.onMessage(event, node) }); + this._sockets[node._id] = WebsocketAPI.addSocket(node, { onOpen: (event) => this.onOpen(event, node, true), onClose: (event) => this.onClose(event, node), onMessage: (event) => this.onMessage(event, node), onError: (error) => this.onError(error, node) }); } } } @@ -56,6 +59,18 @@ class SimulatorDataDataManager { } } + send(message, nodeId) { + const socket = this._sockets[nodeId]; + if (socket == null) { + return false; + } + + const data = this.messageToBuffer(message); + socket.send(data); + + return true; + } + onOpen(event, node, firstOpen) { AppDispatcher.dispatch({ type: 'simulatorData/opened', @@ -75,6 +90,10 @@ class SimulatorDataDataManager { delete this._sockets[node._id]; } + onError(error, node) { + console.error('Error on ' + node._id + ':' + error); + } + onMessage(event, node) { var msgs = this.bufferToMessageArray(event.data); @@ -93,9 +112,6 @@ class SimulatorDataDataManager { return null; } - const OFFSET_TYPE = 2; - const OFFSET_VERSION = 4; - const id = data.getUint8(1); const bits = data.getUint8(0); const length = data.getUint16(0x02, 1); @@ -130,6 +146,30 @@ class SimulatorDataDataManager { return msgs; } + + messageToBuffer(message) { + const buffer = new ArrayBuffer(16 + 4 * message.length); + const view = new DataView(buffer); + + let bits = 0; + bits |= (message.version & 0xF) << OFFSET_VERSION; + bits |= (message.type & 0x3) << OFFSET_TYPE; + + const sec = Math.floor(message.timestamp / 1e3); + const nsec = (message.timestamp - sec * 1e3) * 1e6; + + view.setUint8(0x00, bits, true); + view.setUint8(0x01, message.id, true); + view.setUint16(0x02, message.length, true); + view.setUint32(0x04, message.sequence, true); + view.setUint32(0x08, sec, true); + view.setUint32(0x0C, nsec, true); + + const data = new Float32Array(buffer, 0x10, message.length); + data.set(message.values); + + return buffer; + } } export default new SimulatorDataDataManager(); diff --git a/src/stores/simulator-data-store.js b/src/stores/simulator-data-store.js index 25a24d1..61864bf 100644 --- a/src/stores/simulator-data-store.js +++ b/src/stores/simulator-data-store.js @@ -46,11 +46,30 @@ class SimulationDataStore extends ReduceStore { case 'simulatorData/opened': // create entry for simulator state[action.node._id] = {}; + return state; - action.node.simulators.forEach((simulator, index) => { - state[action.node._id][index] = { sequence: -1, values: [] }; - }); + case 'simulatorData/prepare': + if (state[action.node.node] == null) { + return state; + } + state[action.node.node][action.node.simulator] = { + output: { + sequence: -1, + length: action.outputLength, + values: [] + }, + input: { + sequence: -1, + length: action.inputLength, + version: 2, + type: 0, + id: action.node.simulator, + timestamp: Date.now(), + values: new Array(action.inputLength).fill(0) + } + }; + return state; case 'simulatorData/data-changed': @@ -70,22 +89,22 @@ class SimulationDataStore extends ReduceStore { // add data to simulator for (i = 0; i < smp.length; i++) { - while (state[action.node._id][index].values.length < i + 1) { - state[action.node._id][index].values.push([]); + while (state[action.node._id][index].output.values.length < i + 1) { + state[action.node._id][index].output.values.push([]); } - state[action.node._id][index].values[i].push({ x: smp.timestamp, y: smp.values[i] }); + state[action.node._id][index].output.values[i].push({ x: smp.timestamp, y: smp.values[i] }); // erase old values - if (state[action.node._id][index].values[i].length > MAX_VALUES) { - const pos = state[action.node._id][index].values[i].length - MAX_VALUES; - state[action.node._id][index].values[i].splice(0, pos); + if (state[action.node._id][index].output.values[i].length > MAX_VALUES) { + const pos = state[action.node._id][index].output.values[i].length - MAX_VALUES; + state[action.node._id][index].output.values[i].splice(0, pos); } } // update metadata - state[action.node._id][index].timestamp = smp.timestamp; - state[action.node._id][index].sequence = smp.sequence; + state[action.node._id][index].output.timestamp = smp.timestamp; + state[action.node._id][index].output.sequence = smp.sequence; } // explicit call to prevent array copy @@ -93,6 +112,21 @@ class SimulationDataStore extends ReduceStore { return state; + case 'simulatorData/inputChanged': + // find simulator in node array + if (state[action.simulator.node] == null || state[action.simulator.node][action.simulator.simulator] == null) { + return state; + } + + // update message properties + state[action.simulator.node][action.simulator.simulator].input.timestamp = Date.now(); + state[action.simulator.node][action.simulator.simulator].input.sequence++; + state[action.simulator.node][action.simulator.simulator].input.values[action.signal] = action.data; + + SimulatorDataDataManager.send(state[action.simulator.node][action.simulator.simulator].input, action.simulator.node); + + return state; + case 'simulatorData/closed': // close and delete socket if (state[action.node] != null) {