1
0
Fork 0
mirror of https://git.rwth-aachen.de/acs/public/villas/web/ synced 2025-03-09 00:00:01 +01:00

Merge branch '21-reverse-data-channel' into 'develop'

Resolve "Reverse data channel"

Closes #21

See merge request acs/public/villas/VILLASweb!25
This commit is contained in:
Steffen Vogel 2018-02-06 13:14:26 +01:00
commit e76fdac8e3
22 changed files with 367 additions and 137 deletions

2
.gitignore vendored
View file

@ -16,4 +16,4 @@ npm-debug.log*
yarn-debug.log*
yarn-error.log*
.vscode/
*.code-workspace

View file

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

View file

@ -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 {
))}
</FormControl>
</FormGroup>
<FormGroup controlId="length" validationState={this.validateForm('length')}>
<ControlLabel>Length</ControlLabel>
<FormControl type="number" placeholder="Enter length" min="1" value={this.state.length} onChange={(e) => this.handleChange(e)} />
<FormGroup controlId="outputLength" validationState={this.validateForm('outputLength')}>
<ControlLabel>Output Length</ControlLabel>
<FormControl type="number" placeholder="Enter length" min="1" value={this.state.outputLength} onChange={(e) => this.handleChange(e)} />
<FormControl.Feedback />
</FormGroup>
<FormGroup controlId="mapping">
<ControlLabel>Mapping</ControlLabel>
<Table data={this.state.mapping}>
<FormGroup controlId="outputMapping">
<ControlLabel>Output Mapping</ControlLabel>
<HelpBlock>Click Name or Type cell to edit</HelpBlock>
<Table data={this.state.outputMapping}>
<TableColumn title='ID' width='60' dataIndex />
<TableColumn title='Name' dataKey='name' inlineEditable onInlineChange={(event, row, column) => this.handleMappingChange(event, row, column)} />
<TableColumn title='Type' dataKey='type' inlineEditable onInlineChange={(event, row, column) => this.handleMappingChange(event, row, column)} />
<TableColumn title='Name' dataKey='name' inlineEditable onInlineChange={(event, row, column) => this.handleMappingChange('outputMapping', event, row, column)} />
<TableColumn title='Type' dataKey='type' inlineEditable onInlineChange={(event, row, column) => this.handleMappingChange('outputMapping', event, row, column)} />
</Table>
</FormGroup>
<FormGroup controlId="inputLength" validationState={this.validateForm('inputLength')}>
<ControlLabel>Input Length</ControlLabel>
<FormControl type="number" placeholder="Enter length" min="1" value={this.state.inputLength} onChange={(e) => this.handleChange(e)} />
<FormControl.Feedback />
</FormGroup>
<FormGroup controlId="inputMapping">
<ControlLabel>Input Mapping</ControlLabel>
<HelpBlock>Click Name or Type cell to edit</HelpBlock>
<Table data={this.state.inputMapping}>
<TableColumn title='ID' width='60' dataIndex />
<TableColumn title='Name' dataKey='name' inlineEditable onInlineChange={(event, row, column) => this.handleMappingChange('inputMapping', event, row, column)} />
<TableColumn title='Type' dataKey='type' inlineEditable onInlineChange={(event, row, column) => this.handleMappingChange('inputMapping', event, row, column)} />
</Table>
</FormGroup>
</form>

View file

@ -117,13 +117,17 @@ export default function createControls(widgetType = null, widget = null, session
break;
case 'Slider':
dialogControls.push(
<EditWidgetOrientation key={0} widget={widget} validate={(id) => validateForm(id)} simulation={simulation} handleChange={(e) => handleChange(e)} />
<EditWidgetOrientation key={0} widget={widget} validate={(id) => validateForm(id)} simulation={simulation} handleChange={(e) => handleChange(e)} />,
<EditWidgetSimulatorControl key={1} widget={widget} validate={(id) => validateForm(id)} simulation={simulation} handleChange={(e) => valueBoundOnChange(e)} />,
<EditWidgetSignalControl key={2} widget={widget} input validate={(id) => validateForm(id)} simulation={simulation} handleChange={(e) => handleChange(e)} />
);
break;
case 'Button':
dialogControls.push(
<EditWidgetColorControl key={0} widget={widget} controlId={'background_color'} label={'Background'} validate={(id) => validateForm(id)} handleChange={(e) => handleChange(e)} />,
<EditWidgetColorControl key={1} widget={widget} controlId={'font_color'} label={'Font color'} validate={(id) => validateForm(id)} handleChange={(e) => handleChange(e)} />
<EditWidgetColorControl key={1} widget={widget} controlId={'font_color'} label={'Font color'} validate={(id) => validateForm(id)} handleChange={(e) => handleChange(e)} />,
<EditWidgetSimulatorControl key={2} widget={widget} validate={(id) => validateForm(id)} simulation={simulation} handleChange={(e) => valueBoundOnChange(e)} />,
<EditWidgetSignalControl key={3} widget={widget} input validate={(id) => 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(
<EditWidgetTextControl key={0} widget={widget} controlId={'name'} label={'Text'} placeholder={'Enter text'} validate={id => validateForm(id)} handleChange={e => handleChange(e)} />,
<EditWidgetSimulatorControl key={1} widget={widget} validate={(id) => validateForm(id)} simulation={simulation} handleChange={(e) => valueBoundOnChange(e)} />,
<EditWidgetSignalControl key={2} widget={widget} input validate={(id) => validateForm(id)} simulation={simulation} handleChange={(e) => handleChange(e)} />
);
break;
default:
console.log('Non-valid widget type: ' + widgetType);
}

View file

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

View file

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

View file

@ -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 {
))}
</FormControl>
</FormGroup>
<FormGroup controlId="length" validationState={this.validateForm('length')}>
<ControlLabel>Length</ControlLabel>
<FormControl readOnly={!this.imported} type="number" placeholder="Enter length" min="1" value={this.state.length} onChange={(e) => this.handleChange(e)} />
<FormGroup controlId="outputLength" validationState={this.validateForm('outputLength')}>
<ControlLabel>Output Length</ControlLabel>
<FormControl type="number" placeholder="Enter length" min="1" value={this.state.outputLength} onChange={(e) => this.handleChange(e)} />
<FormControl.Feedback />
</FormGroup>
<FormGroup controlId="mapping">
<ControlLabel>Mapping</ControlLabel>
<Table data={this.state.mapping}>
<FormGroup controlId="outputMapping">
<ControlLabel>Output Mapping</ControlLabel>
<HelpBlock>Click Name or Type cell to edit</HelpBlock>
<Table data={this.state.outputMapping}>
<TableColumn title='ID' width='60' dataIndex />
<TableColumn title='Name' dataKey='name' inlineEditable onInlineChange={(event, row, column) => this.handleMappingChange(event, row, column)} />
<TableColumn title='Type' dataKey='type' inlineEditable onInlineChange={(event, row, column) => this.handleMappingChange(event, row, column)} />
<TableColumn title='Name' dataKey='name' inlineEditable onInlineChange={(event, row, column) => this.handleMappingChange('outputMapping', event, row, column)} />
<TableColumn title='Type' dataKey='type' inlineEditable onInlineChange={(event, row, column) => this.handleMappingChange('outputMapping', event, row, column)} />
</Table>
</FormGroup>
<FormGroup controlId="inputLength" validationState={this.validateForm('inputLength')}>
<ControlLabel>Input Length</ControlLabel>
<FormControl type="number" placeholder="Enter length" min="1" value={this.state.inputLength} onChange={(e) => this.handleChange(e)} />
<FormControl.Feedback />
</FormGroup>
<FormGroup controlId="inputMapping">
<ControlLabel>Input Mapping</ControlLabel>
<HelpBlock>Click Name or Type cell to edit</HelpBlock>
<Table data={this.state.inputMapping}>
<TableColumn title='ID' width='60' dataIndex />
<TableColumn title='Name' dataKey='name' inlineEditable onInlineChange={(event, row, column) => this.handleMappingChange('inputMapping', event, row, column)} />
<TableColumn title='Type' dataKey='type' inlineEditable onInlineChange={(event, row, column) => this.handleMappingChange('inputMapping', event, row, column)} />
</Table>
</FormGroup>
</form>

View file

@ -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 {
))}
</FormControl>
</FormGroup>
<FormGroup controlId="length" validationState={this.validateForm('length')}>
<ControlLabel>Length</ControlLabel>
<FormControl type="number" placeholder="Enter length" min="1" value={this.state.length} onChange={(e) => this.handleChange(e)} />
<FormGroup controlId="outputLength" validationState={this.validateForm('outputLength')}>
<ControlLabel>Output Length</ControlLabel>
<FormControl type="number" placeholder="Enter length" min="1" value={this.state.outputLength} onChange={(e) => this.handleChange(e)} />
<FormControl.Feedback />
</FormGroup>
<FormGroup controlId="mapping">
<ControlLabel>Mapping</ControlLabel>
<FormGroup controlId="outputMapping">
<ControlLabel>Output Mapping</ControlLabel>
<HelpBlock>Click Name or Type cell to edit</HelpBlock>
<Table data={this.state.mapping}>
<Table data={this.state.outputMapping}>
<TableColumn title='ID' width='60' dataIndex />
<TableColumn title='Name' dataKey='name' inlineEditable onInlineChange={(event, row, column) => this.handleMappingChange(event, row, column)} />
<TableColumn title='Type' dataKey='type' inlineEditable onInlineChange={(event, row, column) => this.handleMappingChange(event, row, column)} />
<TableColumn title='Name' dataKey='name' inlineEditable onInlineChange={(event, row, column) => this.handleMappingChange('outputMapping', event, row, column)} />
<TableColumn title='Type' dataKey='type' inlineEditable onInlineChange={(event, row, column) => this.handleMappingChange('outputMapping', event, row, column)} />
</Table>
</FormGroup>
<FormGroup controlId="inputLength" validationState={this.validateForm('inputLength')}>
<ControlLabel>Input Length</ControlLabel>
<FormControl type="number" placeholder="Enter length" min="1" value={this.state.inputLength} onChange={(e) => this.handleChange(e)} />
<FormControl.Feedback />
</FormGroup>
<FormGroup controlId="inputMapping">
<ControlLabel>Input Mapping</ControlLabel>
<HelpBlock>Click Name or Type cell to edit</HelpBlock>
<Table data={this.state.inputMapping}>
<TableColumn title='ID' width='60' dataIndex />
<TableColumn title='Name' dataKey='name' inlineEditable onInlineChange={(event, row, column) => this.handleMappingChange('inputMapping', event, row, column)} />
<TableColumn title='Type' dataKey='type' inlineEditable onInlineChange={(event, row, column) => this.handleMappingChange('inputMapping', event, row, column)} />
</Table>
</FormGroup>
</form>

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -480,9 +480,9 @@ class Visualization extends React.Component {
<ToolboxItem name="Label" type="widget" />
<ToolboxItem name="Image" type="widget" />
<ToolboxItem name="PlotTable" type="widget" />
<ToolboxItem name="Button" type="widget" disabled />
<ToolboxItem name="NumberInput" type="widget" disabled />
<ToolboxItem name="Slider" type="widget" disabled />
<ToolboxItem name="Button" type="widget" />
<ToolboxItem name="NumberInput" type="widget" />
<ToolboxItem name="Slider" type="widget" />
<ToolboxItem name="Gauge" type="widget" />
<ToolboxItem name="Box" type="widget" />
<ToolboxItem name="HTML" type="html" />

View file

@ -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 = <WidgetNumberInput widget={widget} editing={this.props.editing} />
} else if (widget.type === 'Slider') {
element = <WidgetSlider widget={widget} editing={this.props.editing} onWidgetChange={(w) => this.props.onWidgetStatusChange(w, this.props.index) } />
element = <WidgetSlider widget={widget} editing={this.props.editing} onWidgetChange={(w) => this.props.onWidgetStatusChange(w, this.props.index) } onInputChanged={(value) => this.inputDataChanged(widget, value)} />
} else if (widget.type === 'Gauge') {
element = <WidgetGauge widget={widget} data={this.state.simulatorData} editing={this.props.editing} simulation={this.props.simulation} />
} else if (widget.type === 'Box') {

View file

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

View file

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

View file

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

View file

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