mirror of
https://git.rwth-aachen.de/acs/public/villas/web/
synced 2025-03-09 00:00:01 +01:00
implemented websockets and finished dashboard migration
Signed-off-by: Andrii Podriez <andrey5577990@gmail.com>
This commit is contained in:
parent
ab81e45ce4
commit
6d2cabb268
11 changed files with 401 additions and 503 deletions
|
@ -20,46 +20,82 @@ const OFFSET_VERSION = 4;
|
|||
|
||||
class WebSocketManager {
|
||||
constructor() {
|
||||
this.socket = null;
|
||||
this.sockets = []; // Array to store multiple socket objects
|
||||
}
|
||||
|
||||
id = null;
|
||||
|
||||
connect(url, onMessage, onOpen, onClose) {
|
||||
if (this.socket && this.socket.readyState === WebSocket.OPEN) {
|
||||
connect(id, url, onMessage, onOpen, onClose) {
|
||||
const existingSocket = this.sockets.find(s => s.id === id);
|
||||
if (existingSocket && existingSocket.socket.readyState === WebSocket.OPEN) {
|
||||
console.log('Already connected to:', url);
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.socket) {
|
||||
this.socket.close();
|
||||
}
|
||||
const socket = new WebSocket(url, 'live');
|
||||
socket.binaryType = 'arraybuffer';
|
||||
|
||||
this.socket = new WebSocket(url, 'live');
|
||||
this.socket.binaryType = 'arraybuffer';
|
||||
|
||||
this.socket.onopen = onOpen;
|
||||
this.socket.onmessage = (event) => {
|
||||
socket.onopen = onOpen;
|
||||
socket.onmessage = (event) => {
|
||||
const msgs = this.bufferToMessageArray(event.data);
|
||||
onMessage(msgs);
|
||||
onMessage(msgs, id);
|
||||
};
|
||||
this.socket.onclose = onClose;
|
||||
socket.onclose = onClose;
|
||||
|
||||
// Store the new socket along with its id
|
||||
this.sockets.push({ id, socket });
|
||||
}
|
||||
|
||||
disconnect() {
|
||||
if (this.socket) {
|
||||
this.socket.close();
|
||||
this.socket = null;
|
||||
console.log('WebSocket connection closed');
|
||||
disconnect(id) {
|
||||
const socket = this.sockets.find(s => s.id === id);
|
||||
if (socket) {
|
||||
socket.socket.close();
|
||||
this.sockets = this.sockets.filter(s => s.id !== id);
|
||||
console.log('WebSocket connection closed for id:', id);
|
||||
}
|
||||
}
|
||||
|
||||
send(id, message) {
|
||||
const socket = this.sockets.find(s => s.id === id);
|
||||
if (socket == null) {
|
||||
return false;
|
||||
}
|
||||
console.log("Sending to IC", id, "message: ", message);
|
||||
const data = this.messageToBuffer(message);
|
||||
socket.socket.send(data);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
let source_index = 0;
|
||||
source_index |= (message.source_index & 0xFF);
|
||||
|
||||
const sec = Math.floor(message.timestamp / 1e3);
|
||||
const nsec = (message.timestamp - sec * 1e3) * 1e6;
|
||||
|
||||
view.setUint8(0x00, bits, true);
|
||||
view.setUint8(0x01, source_index, 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;
|
||||
}
|
||||
|
||||
bufferToMessageArray(blob) {
|
||||
/* some local variables for parsing */
|
||||
let offset = 0;
|
||||
const msgs = [];
|
||||
|
||||
/* for every msg in vector */
|
||||
while (offset < blob.byteLength) {
|
||||
const msg = this.bufferToMessage(new DataView(blob, offset));
|
||||
|
||||
|
@ -70,31 +106,39 @@ class WebSocketManager {
|
|||
}
|
||||
|
||||
return msgs;
|
||||
}
|
||||
}
|
||||
|
||||
bufferToMessage(data) {
|
||||
// parse incoming message into usable data
|
||||
if (data.byteLength === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const source_index = data.getUint8(1);
|
||||
const bits = data.getUint8(0);
|
||||
const length = data.getUint16(0x02, 1);
|
||||
const bytes = length * 4 + 16;
|
||||
|
||||
return {
|
||||
version: (bits >> OFFSET_VERSION) & 0xF,
|
||||
type: (bits >> OFFSET_TYPE) & 0x3,
|
||||
source_index: source_index,
|
||||
length: length,
|
||||
sequence: data.getUint32(0x04, 1),
|
||||
timestamp: data.getUint32(0x08, 1) * 1e3 + data.getUint32(0x0C, 1) * 1e-6,
|
||||
values: new Float32Array(data.buffer, data.byteOffset + 0x10, length),
|
||||
blob: new DataView(data.buffer, data.byteOffset + 0x00, bytes),
|
||||
// id: id
|
||||
};
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
const source_index = data.getUint8(1);
|
||||
const bits = data.getUint8(0);
|
||||
const length = data.getUint16(0x02, 1);
|
||||
const bytes = length * 4 + 16;
|
||||
|
||||
return {
|
||||
version: (bits >> OFFSET_VERSION) & 0xF,
|
||||
type: (bits >> OFFSET_TYPE) & 0x3,
|
||||
source_index: source_index,
|
||||
length: length,
|
||||
sequence: data.getUint32(0x04, 1),
|
||||
timestamp: data.getUint32(0x08, 1) * 1e3 + data.getUint32(0x0C, 1) * 1e-6,
|
||||
values: new Float32Array(data.buffer, data.byteOffset + 0x10, length),
|
||||
blob: new DataView(data.buffer, data.byteOffset + 0x00, bytes),
|
||||
};
|
||||
}
|
||||
|
||||
getSocketById(id) {
|
||||
const socketEntry = this.sockets.find(s => s.id === id);
|
||||
return socketEntry ? socketEntry.socket : null;
|
||||
}
|
||||
|
||||
isConnected(id) {
|
||||
const socket = this.getSocketById(id);
|
||||
return socket && socket.readyState === WebSocket.OPEN;
|
||||
}
|
||||
}
|
||||
|
||||
export const wsManager = new WebSocketManager();
|
||||
|
|
|
@ -1,4 +1,21 @@
|
|||
import React, { useState, useEffect, useCallback, useRef } from 'react';
|
||||
/**
|
||||
* This file is part of VILLASweb.
|
||||
*
|
||||
* VILLASweb is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* VILLASweb is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with VILLASweb. If not, see <http://www.gnu.org/licenses/>.
|
||||
******************************************************************************/
|
||||
|
||||
import React, { useState, useEffect, useCallback, useRef, act } from 'react';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import Fullscreenable from 'react-fullscreenable';
|
||||
|
@ -71,24 +88,46 @@ const Dashboard = ({ isFullscreen, toggleFullscreen }) => {
|
|||
const [grid, setGrid] = useState(50);
|
||||
const [newHeightValue, setNewHeightValue] = useState(0);
|
||||
|
||||
//ics that are included in configurations
|
||||
const [activeICS, setActiveICS] = useState([]);
|
||||
|
||||
useEffect(() => {
|
||||
const wsUrl = 'wss://villas.k8s.eonerc.rwth-aachen.de/ws/ws_sig';
|
||||
dispatch(connectWebSocket({ url: wsUrl, id: 547627 }));
|
||||
let usedICS = [];
|
||||
for(const config of configs){
|
||||
usedICS.push(config.icID);
|
||||
}
|
||||
setActiveICS(ics.filter((i) => usedICS.includes(i.id)));
|
||||
}, [configs])
|
||||
|
||||
const activeSocketURLs = useSelector((state) => state.websocket.activeSocketURLs);
|
||||
|
||||
//connect to websockets
|
||||
useEffect(() => {
|
||||
activeICS.forEach((i) => {
|
||||
if(i.websocketurl){
|
||||
if(!activeSocketURLs.includes(i.websocketurl))
|
||||
dispatch(connectWebSocket({ url: i.websocketurl, id: i.id }));
|
||||
}
|
||||
})
|
||||
|
||||
return () => {
|
||||
dispatch(disconnect());
|
||||
activeICS.forEach((i) => {
|
||||
dispatch(disconnect({ id: i.id }));
|
||||
});
|
||||
};
|
||||
}, [dispatch]);
|
||||
|
||||
}, [activeICS]);
|
||||
|
||||
|
||||
//as soon as dashboard is loaded, load widgets, configs, signals and files for this dashboard
|
||||
useEffect(() => {
|
||||
if (dashboard.id) {
|
||||
//as soon as dashboard is loaded, load widgets, configs, signals and files for this dashboard
|
||||
fetchWidgets(dashboard.id);
|
||||
fetchWidgetData(dashboard.scenarioID);
|
||||
setHeight(dashboard.height);
|
||||
setGrid(dashboard.grid);
|
||||
|
||||
console.log('widgets', widgets);
|
||||
}
|
||||
}, [dashboard]);
|
||||
|
||||
|
@ -471,8 +510,9 @@ const Dashboard = ({ isFullscreen, toggleFullscreen }) => {
|
|||
configs={configs}
|
||||
signals={signals}
|
||||
paused={paused}
|
||||
ics={ics}
|
||||
icData={[]}
|
||||
ics={activeICS}
|
||||
scenarioID={dashboard.scenarioID}
|
||||
onSimulationStarted={() => onSimulationStarted()}
|
||||
/>
|
||||
</WidgetContainer>
|
||||
</div>
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
|
||||
import React from 'react';
|
||||
import { useState, useEffect } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { useSelector, useDispatch } from 'react-redux';
|
||||
import WidgetLabel from './widgets/label';
|
||||
import WidgetLine from './widgets/line';
|
||||
import WidgetBox from './widgets/box';
|
||||
|
@ -31,23 +31,40 @@ import WidgetTimeOffset from './widgets/time-offset';
|
|||
import WidgetICstatus from './widgets/icstatus';
|
||||
// import WidgetCustomAction from './widgets/custom-action';
|
||||
// import WidgetAction from './widgets/action';
|
||||
// import WidgetButton from './widgets/button';
|
||||
// import WidgetInput from './widgets/input';
|
||||
// import WidgetSlider from './widgets/slider';
|
||||
import WidgetButton from './widgets/button';
|
||||
import WidgetInput from './widgets/input';
|
||||
import WidgetSlider from './widgets/slider';
|
||||
// import WidgetTopology from './widgets/topology';
|
||||
// import WidgetPlayer from './widgets/player';
|
||||
import WidgetPlayer from './widgets/player';
|
||||
//import WidgetHTML from './widgets/html';
|
||||
import '../../../styles/widgets.css';
|
||||
import { useGetICSQuery, useGetSignalsQuery, useGetConfigsQuery } from '../../../store/apiSlice';
|
||||
import { sessionToken } from '../../../localStorage';
|
||||
import { useUpdateWidgetMutation } from '../../../store/apiSlice';
|
||||
import { sendMessageToWebSocket } from '../../../store/websocketSlice';
|
||||
import { useGetResultsQuery } from '../../../store/apiSlice';
|
||||
|
||||
const Widget = ({widget, editing, files, configs, signals, paused, ics, icData}) => {
|
||||
|
||||
const Widget = ({widget, editing, files, configs, signals, paused, ics, scenarioID, onSimulationStarted}) => {
|
||||
const dispatch = useDispatch();
|
||||
const { token: sessionToken } = useSelector((state) => state.auth);
|
||||
|
||||
const {data, refetch: refetchResults } = useGetResultsQuery(scenarioID);
|
||||
const results = data ? data.results : [];
|
||||
const [icIDs, setICIDs] = useState([]);
|
||||
|
||||
const icdata = useSelector((state) => state.websocket.icdata);
|
||||
|
||||
const [websockets, setWebsockets] = useState([]);
|
||||
const activeSocketURLs = useSelector((state) => state.websocket.activeSocketURLs);
|
||||
const [update] = useUpdateWidgetMutation();
|
||||
|
||||
useEffect(() => {
|
||||
if(activeSocketURLs.length > 0){
|
||||
activeSocketURLs.forEach(url => {
|
||||
setWebsockets(prevState=>([...prevState, { url: url.replace(/^wss:\/\//, "https://"), connected:true}]))
|
||||
})
|
||||
}
|
||||
}, [activeSocketURLs])
|
||||
|
||||
useEffect(() => {
|
||||
if(signals.length > 0){
|
||||
let ids = [];
|
||||
|
@ -66,387 +83,110 @@ const Widget = ({widget, editing, files, configs, signals, paused, ics, icData})
|
|||
}
|
||||
}, [signals])
|
||||
|
||||
// const {data: signals, isLoading: signalsLoading} = useGetSignalsQuery({})
|
||||
const inputDataChanged = (widget, data, controlID, controlValue, isFinalChange) => {
|
||||
if (controlID !== '' && isFinalChange) {
|
||||
let updatedWidget = JSON.parse(JSON.stringify(widget));
|
||||
updatedWidget.customProperties[controlID] = controlValue;
|
||||
|
||||
updateWidget(updatedWidget);
|
||||
}
|
||||
|
||||
switch(widget.type){
|
||||
//Cosmetic widgets
|
||||
case 'Line':
|
||||
return <WidgetLine widget={widget} editing={editing} />
|
||||
case 'Box':
|
||||
return <WidgetBox widget={widget} editing={editing} />
|
||||
case 'Label':
|
||||
return <WidgetLabel widget={widget}/>;
|
||||
case 'Image':
|
||||
return <WidgetImage widget={widget} files={files} token={sessionToken} />
|
||||
//Displaying widgets
|
||||
case 'Plot':
|
||||
return <WidgetPlot widget={widget} data={icdata} signals={signals} icIDs={icIDs} paused={paused} />
|
||||
case 'Table':
|
||||
return <WidgetTable widget={widget} data={icdata} signals={signals} icIDs={icIDs} />
|
||||
case 'Value':
|
||||
return <WidgetValue widget={widget} data={icdata} signals={signals} icIDs={icIDs} />
|
||||
case 'Lamp':
|
||||
return <WidgetLamp widget={widget} data={icdata} signals={signals} icIDs={icIDs} />
|
||||
case 'Gauge':
|
||||
return <WidgetGauge widget={widget} data={icdata} signals={signals} icIDs={icIDs} editing={editing} />
|
||||
case 'TimeOffset':
|
||||
return <WidgetTimeOffset widget={widget} data={icdata} signals={signals} icIDs={icIDs} editing={editing} />
|
||||
case 'ICstatus':
|
||||
return <WidgetICstatus widget={widget} ics={ics} />
|
||||
//Manipulation widgets
|
||||
default:
|
||||
return <div>Error: Widget not found!</div>
|
||||
let signalID = widget.signalIDs[0];
|
||||
let signal = signals.filter(s => s.id === signalID)
|
||||
if (signal.length === 0){
|
||||
console.warn("Unable to send signal for signal ID", signalID, ". Signal not found.");
|
||||
return;
|
||||
}
|
||||
// determine ID of infrastructure component related to signal[0]
|
||||
// Remark: there is only one selected signal for an input type widget
|
||||
let icID = icIDs[signal[0].id];
|
||||
dispatch(sendMessageToWebSocket({message: {ic: icID, signalID: signal[0].id, signalIndex: signal[0].index, data: signal[0].scalingFactor * data}}));
|
||||
}
|
||||
|
||||
|
||||
// if (widget.type === 'CustomAction') {
|
||||
// return <WidgetCustomAction
|
||||
// widget={widget}
|
||||
// data={this.state.icData}
|
||||
// signals={this.state.signals}
|
||||
// icIDs={this.state.icIDs}
|
||||
// />
|
||||
// } else if (widget.type === 'Action') {
|
||||
// return <WidgetAction
|
||||
// widget={widget}
|
||||
// data={this.state.icData}
|
||||
// />
|
||||
// } else if (widget.type === 'Lamp') {
|
||||
// return <WidgetLamp
|
||||
// widget={widget}
|
||||
// data={this.state.icData}
|
||||
// signals={this.state.signals}
|
||||
// icIDs={this.state.icIDs}
|
||||
// />
|
||||
// } else if (widget.type === 'Value') {
|
||||
// return <WidgetValue
|
||||
// widget={widget}
|
||||
// data={this.state.icData}
|
||||
// signals={this.state.signals}
|
||||
// icIDs={this.state.icIDs}
|
||||
// />
|
||||
// } else if (widget.type === 'Plot') {
|
||||
// return <WidgetPlot
|
||||
// widget={widget}
|
||||
// data={this.state.icData}
|
||||
// signals={this.state.signals}
|
||||
// icIDs={this.state.icIDs}
|
||||
// paused={this.props.paused}
|
||||
// />
|
||||
// } else if (widget.type === 'Table') {
|
||||
// return <WidgetTable
|
||||
// widget={widget}
|
||||
// data={this.state.icData}
|
||||
// signals={this.state.signals}
|
||||
// icIDs={this.state.icIDs}
|
||||
// />
|
||||
// } else if (widget.type === 'Label') {
|
||||
// return <WidgetLabel
|
||||
// widget={widget}
|
||||
// />
|
||||
// } else if (widget.type === 'Image') {
|
||||
// return <WidgetImage
|
||||
// widget={widget}
|
||||
// files={this.state.files}
|
||||
// token={this.state.sessionToken}
|
||||
// />
|
||||
// } else if (widget.type === 'Button') {
|
||||
// return <WidgetButton
|
||||
// widget={widget}
|
||||
// editing={this.props.editing}
|
||||
// onInputChanged={(value, controlID, controlValue, isFinalChange) => this.inputDataChanged(widget, value, controlID, controlValue, isFinalChange)}
|
||||
// signals={this.state.signals}
|
||||
// token={this.state.sessionToken}
|
||||
// />
|
||||
// } else if (widget.type === 'NumberInput') {
|
||||
// return <WidgetInput
|
||||
// widget={widget}
|
||||
// editing={this.props.editing}
|
||||
// onInputChanged={(value, controlID, controlValue, isFinalChange) => this.inputDataChanged(widget, value, controlID, controlValue, isFinalChange)}
|
||||
// signals={this.state.signals}
|
||||
// token={this.state.sessionToken}
|
||||
// />
|
||||
// } else if (widget.type === 'Slider') {
|
||||
// return <WidgetSlider
|
||||
// widget={widget}
|
||||
// editing={this.props.editing}
|
||||
// onInputChanged={(value, controlID, controlValue, isFinalChange) => this.inputDataChanged(widget, value, controlID, controlValue, isFinalChange)}
|
||||
// signals={this.state.signals}
|
||||
// token={this.state.sessionToken}
|
||||
// />
|
||||
// } else if (widget.type === 'Gauge') {
|
||||
// return <WidgetGauge
|
||||
// widget={widget}
|
||||
// data={this.state.icData}
|
||||
// editing={this.props.editing}
|
||||
// signals={this.state.signals}
|
||||
// icIDs={this.state.icIDs}
|
||||
// />
|
||||
// //} else if (widget.type === 'HTML') {
|
||||
// //return <WidgetHTML
|
||||
// // widget={widget}
|
||||
// // editing={this.props.editing}
|
||||
// ///>
|
||||
// } else if (widget.type === 'Topology') {
|
||||
// return <WidgetTopology
|
||||
// widget={widget}
|
||||
// files={this.state.files}
|
||||
// token={this.state.sessionToken}
|
||||
// />
|
||||
// } else if (widget.type === 'TimeOffset') {
|
||||
// return <WidgetTimeOffset
|
||||
// widget={widget}
|
||||
// data={this.state.icData}
|
||||
// websockets={this.state.websockets}
|
||||
// ics={this.props.ics}
|
||||
// />
|
||||
// } else if (widget.type === 'ICstatus') {
|
||||
// return <WidgetICstatus
|
||||
// widget={widget}
|
||||
// ics={this.props.ics}
|
||||
// />
|
||||
// } else if (widget.type === 'Player') {
|
||||
// return <WidgetPlayer
|
||||
// widget={widget}
|
||||
// editing={this.props.editing}
|
||||
// configs={this.props.configs}
|
||||
// onStarted={this.props.onSimulationStarted}
|
||||
// ics={this.props.ics}
|
||||
// results={this.state.results}
|
||||
// files={this.state.files}
|
||||
// scenarioID={this.props.scenarioID}
|
||||
// />
|
||||
// }
|
||||
const updateWidget = async (updatedWidget) => {
|
||||
try {
|
||||
await update({ widgetID: widget.id, updatedWidget: { widget: updatedWidget } }).unwrap();
|
||||
} catch (err) {
|
||||
console.log('error', err);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
|
||||
if (widget.type === 'Line') {
|
||||
return <WidgetLine widget={widget} editing={editing} />;
|
||||
} else if (widget.type === 'Box') {
|
||||
return <WidgetBox widget={widget} editing={editing} />;
|
||||
} else if (widget.type === 'Label') {
|
||||
return <WidgetLabel widget={widget} />;
|
||||
} else if (widget.type === 'Image') {
|
||||
return <WidgetImage widget={widget} files={files} token={sessionToken} />;
|
||||
} else if (widget.type === 'Plot') {
|
||||
return <WidgetPlot widget={widget} data={icdata} signals={signals} icIDs={icIDs} paused={paused} />;
|
||||
} else if (widget.type === 'Table') {
|
||||
return <WidgetTable widget={widget} data={icdata} signals={signals} icIDs={icIDs} />;
|
||||
} else if (widget.type === 'Value') {
|
||||
return <WidgetValue widget={widget} data={icdata} signals={signals} icIDs={icIDs} />;
|
||||
} else if (widget.type === 'Lamp') {
|
||||
return <WidgetLamp widget={widget} data={icdata} signals={signals} icIDs={icIDs} />;
|
||||
} else if (widget.type === 'Gauge') {
|
||||
return <WidgetGauge widget={widget} data={icdata} signals={signals} icIDs={icIDs} editing={editing} />;
|
||||
} else if (widget.type === 'TimeOffset') {
|
||||
return <WidgetTimeOffset widget={widget} data={icdata} signals={signals} ics={ics} editing={editing} websockets={websockets} />;
|
||||
} else if (widget.type === 'ICstatus') {
|
||||
return <WidgetICstatus widget={widget} ics={ics} />;
|
||||
} else if (widget.type === 'Button') {
|
||||
return (
|
||||
<WidgetButton
|
||||
widget={widget}
|
||||
editing={editing}
|
||||
onInputChanged={(value, controlID, controlValue, isFinalChange) =>
|
||||
inputDataChanged(widget, value, controlID, controlValue, isFinalChange)
|
||||
}
|
||||
signals={signals}
|
||||
token={sessionToken}
|
||||
/>
|
||||
);
|
||||
} else if (widget.type === 'NumberInput') {
|
||||
return (
|
||||
<WidgetInput
|
||||
widget={widget}
|
||||
editing={editing}
|
||||
onInputChanged={(value, controlID, controlValue, isFinalChange) =>
|
||||
inputDataChanged(widget, value, controlID, controlValue, isFinalChange)
|
||||
}
|
||||
signals={signals}
|
||||
token={sessionToken}
|
||||
/>
|
||||
);
|
||||
} else if (widget.type === 'Slider') {
|
||||
return (
|
||||
<WidgetSlider
|
||||
widget={widget}
|
||||
editing={editing}
|
||||
onInputChanged={(value, controlID, controlValue, isFinalChange) =>
|
||||
inputDataChanged(widget, value, controlID, controlValue, isFinalChange)
|
||||
}
|
||||
signals={signals}
|
||||
token={sessionToken}
|
||||
/>
|
||||
);
|
||||
} else if (widget.type === 'Player') {
|
||||
return (
|
||||
<WidgetPlayer
|
||||
widget={widget}
|
||||
editing={editing}
|
||||
configs={configs}
|
||||
onStarted={onSimulationStarted}
|
||||
ics={ics}
|
||||
results={results}
|
||||
files={files}
|
||||
scenarioID={scenarioID}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
console.log('Unknown widget type', widget.type);
|
||||
return <div>Error: Widget not found!</div>;
|
||||
}
|
||||
}
|
||||
|
||||
// class Widget extends React.Component {
|
||||
// static getStores() {
|
||||
// return [ ICDataStore, ConfigsStore, FileStore, SignalStore, WebsocketStore, ResultStore];
|
||||
// }
|
||||
|
||||
// static calculateState(prevState, props) {
|
||||
|
||||
// let websockets = WebsocketStore.getState();
|
||||
|
||||
// let icData = {};
|
||||
|
||||
// if (props.paused) {
|
||||
// if (prevState && prevState.icData) {
|
||||
// icData = JSON.parse(JSON.stringify(prevState.icData));
|
||||
// }
|
||||
// } else {
|
||||
// icData = ICDataStore.getState();
|
||||
// }
|
||||
|
||||
// // Get the IC IDs and signal indexes for all signals of the widget
|
||||
// let configs = ConfigsStore.getState().filter(c => c.scenarioID === parseInt(props.scenarioID, 10));
|
||||
// // TODO make sure that the signals are only the signals that belong to the scenario at hand
|
||||
// let signals = SignalStore.getState();
|
||||
// let icIDs = [];
|
||||
|
||||
// for (let id of props.data.signalIDs){
|
||||
// let signal = signals.find(s => s.id === id);
|
||||
// if (signal !== undefined) {
|
||||
// let config = configs.find(m => m.id === signal.configID);
|
||||
// if (config !== undefined){
|
||||
// icIDs[signal.id] = config.icID;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
// let results = ResultStore.getState().filter(r => r.scenarioID === parseInt(props.scenarioID, 10));
|
||||
// let files = FileStore.getState().filter(f => f.scenarioID === parseInt(props.scenarioID, 10));
|
||||
|
||||
// return {
|
||||
// websockets: websockets,
|
||||
// icData: icData,
|
||||
// signals: signals,
|
||||
// icIDs: icIDs,
|
||||
// files: files,
|
||||
// sessionToken: localStorage.getItem("token"),
|
||||
// results: results,
|
||||
// };
|
||||
// }
|
||||
|
||||
// inputDataChanged(widget, data, controlID, controlValue, isFinalChange) {
|
||||
// // controlID is the path to the widget customProperty that is changed (for example 'value')
|
||||
|
||||
// // modify the widget customProperty
|
||||
// if (controlID !== '' && isFinalChange) {
|
||||
// let updatedWidget = JSON.parse(JSON.stringify(widget));
|
||||
// updatedWidget.customProperties[controlID] = controlValue;
|
||||
|
||||
// AppDispatcher.dispatch({
|
||||
// type: 'widgets/start-edit',
|
||||
// token: this.state.sessionToken,
|
||||
// data: updatedWidget
|
||||
// });
|
||||
// }
|
||||
|
||||
// // The following assumes that a widget modifies/ uses exactly one signal
|
||||
|
||||
// // get the signal with the selected signal ID
|
||||
// let signalID = widget.signalIDs[0];
|
||||
// let signal = this.state.signals.filter(s => s.id === signalID)
|
||||
// if (signal.length === 0){
|
||||
// console.warn("Unable to send signal for signal ID", signalID, ". Signal not found.");
|
||||
// return;
|
||||
// }
|
||||
// // determine ID of infrastructure component related to signal[0]
|
||||
// // Remark: there is only one selected signal for an input type widget
|
||||
// let icID = this.state.icIDs[signal[0].id];
|
||||
// AppDispatcher.dispatch({
|
||||
// type: 'icData/inputChanged',
|
||||
// ic: icID,
|
||||
// signalID: signal[0].id,
|
||||
// signalIndex: signal[0].index,
|
||||
// data: signal[0].scalingFactor * data
|
||||
// });
|
||||
// }
|
||||
|
||||
// createWidget(widget) {
|
||||
|
||||
// if (widget.type === 'CustomAction') {
|
||||
// return <WidgetCustomAction
|
||||
// widget={widget}
|
||||
// data={this.state.icData}
|
||||
// signals={this.state.signals}
|
||||
// icIDs={this.state.icIDs}
|
||||
// />
|
||||
// } else if (widget.type === 'Action') {
|
||||
// return <WidgetAction
|
||||
// widget={widget}
|
||||
// data={this.state.icData}
|
||||
// />
|
||||
// } else if (widget.type === 'Lamp') {
|
||||
// return <WidgetLamp
|
||||
// widget={widget}
|
||||
// data={this.state.icData}
|
||||
// signals={this.state.signals}
|
||||
// icIDs={this.state.icIDs}
|
||||
// />
|
||||
// } else if (widget.type === 'Value') {
|
||||
// return <WidgetValue
|
||||
// widget={widget}
|
||||
// data={this.state.icData}
|
||||
// signals={this.state.signals}
|
||||
// icIDs={this.state.icIDs}
|
||||
// />
|
||||
// } else if (widget.type === 'Plot') {
|
||||
// return <WidgetPlot
|
||||
// widget={widget}
|
||||
// data={this.state.icData}
|
||||
// signals={this.state.signals}
|
||||
// icIDs={this.state.icIDs}
|
||||
// paused={this.props.paused}
|
||||
// />
|
||||
// } else if (widget.type === 'Table') {
|
||||
// return <WidgetTable
|
||||
// widget={widget}
|
||||
// data={this.state.icData}
|
||||
// signals={this.state.signals}
|
||||
// icIDs={this.state.icIDs}
|
||||
// />
|
||||
// } else if (widget.type === 'Label') {
|
||||
// return <WidgetLabel
|
||||
// widget={widget}
|
||||
// />
|
||||
// } else if (widget.type === 'Image') {
|
||||
// return <WidgetImage
|
||||
// widget={widget}
|
||||
// files={this.state.files}
|
||||
// token={this.state.sessionToken}
|
||||
// />
|
||||
// } else if (widget.type === 'Button') {
|
||||
// return <WidgetButton
|
||||
// widget={widget}
|
||||
// editing={this.props.editing}
|
||||
// onInputChanged={(value, controlID, controlValue, isFinalChange) => this.inputDataChanged(widget, value, controlID, controlValue, isFinalChange)}
|
||||
// signals={this.state.signals}
|
||||
// token={this.state.sessionToken}
|
||||
// />
|
||||
// } else if (widget.type === 'NumberInput') {
|
||||
// return <WidgetInput
|
||||
// widget={widget}
|
||||
// editing={this.props.editing}
|
||||
// onInputChanged={(value, controlID, controlValue, isFinalChange) => this.inputDataChanged(widget, value, controlID, controlValue, isFinalChange)}
|
||||
// signals={this.state.signals}
|
||||
// token={this.state.sessionToken}
|
||||
// />
|
||||
// } else if (widget.type === 'Slider') {
|
||||
// return <WidgetSlider
|
||||
// widget={widget}
|
||||
// editing={this.props.editing}
|
||||
// onInputChanged={(value, controlID, controlValue, isFinalChange) => this.inputDataChanged(widget, value, controlID, controlValue, isFinalChange)}
|
||||
// signals={this.state.signals}
|
||||
// token={this.state.sessionToken}
|
||||
// />
|
||||
// } else if (widget.type === 'Gauge') {
|
||||
// return <WidgetGauge
|
||||
// widget={widget}
|
||||
// data={this.state.icData}
|
||||
// editing={this.props.editing}
|
||||
// signals={this.state.signals}
|
||||
// icIDs={this.state.icIDs}
|
||||
// />
|
||||
// } else if (widget.type === 'Box') {
|
||||
// return <WidgetBox
|
||||
// widget={widget}
|
||||
// editing={this.props.editing}
|
||||
// />
|
||||
// //} else if (widget.type === 'HTML') {
|
||||
// //return <WidgetHTML
|
||||
// // widget={widget}
|
||||
// // editing={this.props.editing}
|
||||
// ///>
|
||||
// } else if (widget.type === 'Topology') {
|
||||
// return <WidgetTopology
|
||||
// widget={widget}
|
||||
// files={this.state.files}
|
||||
// token={this.state.sessionToken}
|
||||
// />
|
||||
// } else if (widget.type === 'Line') {
|
||||
// return <WidgetLine
|
||||
// widget={widget}
|
||||
// editing={this.props.editing}
|
||||
// />
|
||||
// } else if (widget.type === 'TimeOffset') {
|
||||
// return <WidgetTimeOffset
|
||||
// widget={widget}
|
||||
// data={this.state.icData}
|
||||
// websockets={this.state.websockets}
|
||||
// ics={this.props.ics}
|
||||
// />
|
||||
// } else if (widget.type === 'ICstatus') {
|
||||
// return <WidgetICstatus
|
||||
// widget={widget}
|
||||
// ics={this.props.ics}
|
||||
// />
|
||||
// } else if (widget.type === 'Player') {
|
||||
// return <WidgetPlayer
|
||||
// widget={widget}
|
||||
// editing={this.props.editing}
|
||||
// configs={this.props.configs}
|
||||
// onStarted={this.props.onSimulationStarted}
|
||||
// ics={this.props.ics}
|
||||
// results={this.state.results}
|
||||
// files={this.state.files}
|
||||
// scenarioID={this.props.scenarioID}
|
||||
// />
|
||||
// }
|
||||
|
||||
// return null;
|
||||
// }
|
||||
|
||||
// render() {
|
||||
// return this.createWidget(this.props.data);
|
||||
// }
|
||||
// }
|
||||
|
||||
// let fluxContainerConverter = require('../common/FluxContainerConverter');
|
||||
// export default Container.create(fluxContainerConverter.convert(Widget), { withProps: true });
|
||||
|
||||
export default Widget;
|
||||
|
|
|
@ -17,7 +17,6 @@
|
|||
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { Button } from 'react-bootstrap';
|
||||
import AppDispatcher from '../../common/app-dispatcher';
|
||||
|
||||
const WidgetButton = (props) => {
|
||||
const [pressed, setPressed] = useState(props.widget.customProperties.pressed);
|
||||
|
@ -27,11 +26,11 @@ const WidgetButton = (props) => {
|
|||
widget.customProperties.simStartedSendValue = false;
|
||||
widget.customProperties.pressed = false;
|
||||
|
||||
AppDispatcher.dispatch({
|
||||
type: 'widgets/start-edit',
|
||||
token: props.token,
|
||||
data: widget
|
||||
});
|
||||
// AppDispatcher.dispatch({
|
||||
// type: 'widgets/start-edit',
|
||||
// token: props.token,
|
||||
// data: widget
|
||||
// });
|
||||
|
||||
// Effect cleanup
|
||||
return () => {
|
||||
|
|
|
@ -16,20 +16,36 @@
|
|||
******************************************************************************/
|
||||
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { useLazyDownloadImageQuery } from "../../../../store/apiSlice";
|
||||
import FileSaver from "file-saver";
|
||||
|
||||
const WidgetImage = (props) => {
|
||||
const [file, setFile] = useState(null);
|
||||
const [objectURL, setObjectURL] = useState("");
|
||||
|
||||
const widget = JSON.parse(JSON.stringify(props.widget));
|
||||
|
||||
const [triggerDownloadImage] = useLazyDownloadImageQuery();
|
||||
|
||||
const handleDownloadFile = async (fileID) => {
|
||||
try {
|
||||
const res = await triggerDownloadImage(fileID);
|
||||
const blob = await res.data; // This is where you get the blob directly
|
||||
setObjectURL(URL.createObjectURL(blob))
|
||||
} catch (error) {
|
||||
console.error(`Failed to download file with ID ${fileID}`, error);
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if(file !== null){
|
||||
handleDownloadFile(file.id);
|
||||
}
|
||||
}, [file])
|
||||
|
||||
useEffect(() => {
|
||||
let widgetFile = widget.customProperties.file;
|
||||
if (widgetFile !== -1 && file === null) {
|
||||
// AppDispatcher.dispatch({
|
||||
// type: "files/start-download",
|
||||
// data: widgetFile,
|
||||
// token: props.token,
|
||||
// });
|
||||
}
|
||||
}, [file, props.token, widget.customProperties.file]);
|
||||
|
||||
|
@ -38,17 +54,11 @@ const WidgetImage = (props) => {
|
|||
widget.customProperties.update = false;
|
||||
if (file !== null) setFile(null);
|
||||
} else {
|
||||
console.log("looking in", props.files)
|
||||
let foundFile = props.files.find(
|
||||
(f) => f.id === parseInt(widget.customProperties.file, 10)
|
||||
);
|
||||
if (foundFile && widget.customProperties.update) {
|
||||
widget.customProperties.update = false;
|
||||
// AppDispatcher.dispatch({
|
||||
// type: "files/start-download",
|
||||
// data: foundFile.id,
|
||||
// token: props.token,
|
||||
// });
|
||||
setFile(foundFile);
|
||||
}
|
||||
}
|
||||
|
@ -58,7 +68,14 @@ const WidgetImage = (props) => {
|
|||
console.error("Image error:", e);
|
||||
};
|
||||
|
||||
let objectURL = file && file.objectURL ? file.objectURL : "";
|
||||
//revoke object url when component unmounts
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
if (objectURL) {
|
||||
URL.revokeObjectURL(objectURL);
|
||||
}
|
||||
};
|
||||
}, [objectURL]);
|
||||
|
||||
return (
|
||||
<div className="full">
|
||||
|
|
|
@ -16,7 +16,6 @@
|
|||
******************************************************************************/
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { Form, Col, InputGroup } from "react-bootstrap";
|
||||
import AppDispatcher from "../../common/app-dispatcher";
|
||||
|
||||
function WidgetInput(props) {
|
||||
const [value, setValue] = useState("");
|
||||
|
@ -26,11 +25,11 @@ function WidgetInput(props) {
|
|||
const widget = { ...props.widget };
|
||||
widget.customProperties.simStartedSendValue = false;
|
||||
|
||||
AppDispatcher.dispatch({
|
||||
type: "widgets/start-edit",
|
||||
token: props.token,
|
||||
data: widget,
|
||||
});
|
||||
// AppDispatcher.dispatch({
|
||||
// type: "widgets/start-edit",
|
||||
// token: props.token,
|
||||
// data: widget,
|
||||
// });
|
||||
}, [props.token, props.widget]);
|
||||
|
||||
useEffect(() => {
|
||||
|
|
|
@ -18,12 +18,10 @@
|
|||
import React, { Component } from 'react';
|
||||
import { Container, Row, Col } from 'react-bootstrap';
|
||||
import JSZip from 'jszip';
|
||||
import IconButton from '../../common/buttons/icon-button';
|
||||
import IconTextButton from '../../common/buttons/icon-text-button';
|
||||
import ParametersEditor from '../../common/parameters-editor';
|
||||
import ICAction from '../../ic/ic-action';
|
||||
import ResultPythonDialog from "../../pages/scenarios/dialogs/result-python-dialog";
|
||||
import AppDispatcher from "../../common/app-dispatcher";
|
||||
import IconButton from '../../../../common/buttons/icon-button';
|
||||
import IconTextButton from '../../../../common/buttons/icon-text-button';
|
||||
import ParametersEditor from '../../../../common/parameters-editor';
|
||||
import ResultPythonDialog from '../../../scenarios/dialogs/result-python-dialog';
|
||||
import { playerMachine } from '../widget-player/player-machine';
|
||||
import { interpret } from 'xstate';
|
||||
|
||||
|
@ -33,7 +31,6 @@ function transitionState(currentState, playerEvent) {
|
|||
return playerMachine.transition(currentState, { type: playerEvent })
|
||||
}
|
||||
|
||||
|
||||
class WidgetPlayer extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
@ -131,17 +128,17 @@ class WidgetPlayer extends Component {
|
|||
switch (state.ic.state) {
|
||||
case 'stopping': // if configured, show results
|
||||
if (state.uploadResults) {
|
||||
AppDispatcher.dispatch({
|
||||
type: 'results/start-load',
|
||||
param: '?scenarioID=' + props.scenarioID,
|
||||
token: state.sessionToken,
|
||||
})
|
||||
// AppDispatcher.dispatch({
|
||||
// type: 'results/start-load',
|
||||
// param: '?scenarioID=' + props.scenarioID,
|
||||
// token: state.sessionToken,
|
||||
// })
|
||||
|
||||
AppDispatcher.dispatch({
|
||||
type: 'files/start-load',
|
||||
token: state.sessionToken,
|
||||
param: '?scenarioID=' + props.scenarioID,
|
||||
});
|
||||
// AppDispatcher.dispatch({
|
||||
// type: 'files/start-load',
|
||||
// token: state.sessionToken,
|
||||
// param: '?scenarioID=' + props.scenarioID,
|
||||
// });
|
||||
}
|
||||
newState = transitionState(state.playerState, 'FINISH')
|
||||
return { playerState: newState, icState: state.ic.state }
|
||||
|
@ -163,14 +160,14 @@ class WidgetPlayer extends Component {
|
|||
clickStart() {
|
||||
let config = this.state.config
|
||||
config.startParameters = this.state.startParameters
|
||||
ICAction.start([config], '{}', [this.state.ic], new Date(), this.state.sessionToken, this.state.uploadResults)
|
||||
// dispatch(sendActionToIC({token: sessionToken, id: id, actions: newAction}));
|
||||
|
||||
let newState = transitionState(this.state.playerState, 'START')
|
||||
this.setState({ playerState: newState })
|
||||
}
|
||||
|
||||
clickReset() {
|
||||
ICAction.reset(this.state.ic.id, new Date(), this.state.sessionToken)
|
||||
//dispatch(sendActionToIC({token: sessionToken, id: id, actions: newAction}));
|
||||
}
|
||||
|
||||
openPythonDialog() {
|
||||
|
@ -197,11 +194,11 @@ class WidgetPlayer extends Component {
|
|||
}
|
||||
|
||||
toDownload.forEach(fileid => {
|
||||
AppDispatcher.dispatch({
|
||||
type: 'files/start-download',
|
||||
data: fileid,
|
||||
token: this.state.sessionToken
|
||||
});
|
||||
// AppDispatcher.dispatch({
|
||||
// type: 'files/start-download',
|
||||
// data: fileid,
|
||||
// token: this.state.sessionToken
|
||||
// });
|
||||
});
|
||||
|
||||
this.setState({ filesToDownload: toDownload });
|
||||
|
|
|
@ -40,11 +40,11 @@ const WidgetSlider = (props) => {
|
|||
if (props.widget.customProperties.simStartedSendValue) {
|
||||
let widget = { ...props.widget };
|
||||
widget.customProperties.simStartedSendValue = false;
|
||||
AppDispatcher.dispatch({
|
||||
type: "widgets/start-edit",
|
||||
token: props.token,
|
||||
data: widget,
|
||||
});
|
||||
// AppDispatcher.dispatch({
|
||||
// type: "widgets/start-edit",
|
||||
// token: props.token,
|
||||
// data: widget,
|
||||
// });
|
||||
|
||||
// Send value without changing widget
|
||||
props.onInputChanged(widget.customProperties.value, "", "", false);
|
||||
|
|
|
@ -84,5 +84,7 @@ export const {
|
|||
useAuthenticateUserMutation,
|
||||
useLazyGetFilesQuery,
|
||||
useUpdateSignalMutation,
|
||||
useGetIcDataQuery
|
||||
useGetIcDataQuery,
|
||||
useLazyDownloadImageQuery,
|
||||
useUpdateComponentConfigMutation
|
||||
} = apiSlice;
|
||||
|
|
|
@ -40,6 +40,13 @@ export const fileEndpoints = (builder) => ({
|
|||
responseType: 'blob',
|
||||
}),
|
||||
}),
|
||||
downloadImage: builder.query({
|
||||
query: (fileID) => ({
|
||||
url: `files/${fileID}`,
|
||||
method: 'GET',
|
||||
responseHandler: (response) => response.blob(),
|
||||
}),
|
||||
}),
|
||||
updateFile: builder.mutation({
|
||||
query: ({ fileID, file }) => {
|
||||
const formData = new FormData();
|
||||
|
|
|
@ -1,14 +1,39 @@
|
|||
/**
|
||||
* This file is part of VILLASweb.
|
||||
*
|
||||
* VILLASweb is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* VILLASweb is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with VILLASweb. If not, see <http://www.gnu.org/licenses/>.
|
||||
******************************************************************************/
|
||||
|
||||
import { createSlice, createAsyncThunk } from "@reduxjs/toolkit";
|
||||
import { wsManager } from "../common/api/websocket-api";
|
||||
import { current } from "@reduxjs/toolkit";
|
||||
|
||||
export const connectWebSocket = createAsyncThunk(
|
||||
'websocket/connect',
|
||||
async ({ url, id, length }, { dispatch }) => {
|
||||
async ({ url, id, length }, { dispatch, getState }) => {
|
||||
|
||||
console.log('Want to connect to', url);
|
||||
|
||||
//check if we are already connected to this socket
|
||||
if(getState().websocket.activeSocketURLs.includes(url)) return;
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
dispatch(addActiveSocket({parameters: {id: id, url: url, length: length}}));
|
||||
wsManager.connect(
|
||||
id,
|
||||
url,
|
||||
(msgs) => {
|
||||
(msgs, id) => {
|
||||
const icdata = {
|
||||
input: {
|
||||
sequence: -1,
|
||||
|
@ -63,14 +88,13 @@ export const connectWebSocket = createAsyncThunk(
|
|||
icdata.output.sequence = smp.sequence;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Dispatch the action to update the Redux state
|
||||
dispatch(updateIcData({ id, newIcData: icdata }));
|
||||
}
|
||||
},
|
||||
() => {
|
||||
console.log('WebSocket connected to:', url);
|
||||
dispatch(setConnectedUrl({ url })); // Optional: Track the connected URL
|
||||
resolve(); // Resolve the promise on successful connection
|
||||
},
|
||||
() => {
|
||||
|
@ -86,21 +110,30 @@ export const connectWebSocket = createAsyncThunk(
|
|||
const websocketSlice = createSlice({
|
||||
name: 'websocket',
|
||||
initialState: {
|
||||
connectedUrl: null,
|
||||
icdata: {},
|
||||
activeSocketURLs: []
|
||||
},
|
||||
reducers: {
|
||||
setConnectedUrl: (state, action) => {
|
||||
state.connectedUrl = action.payload.url;
|
||||
addActiveSocket: (state, action) => {
|
||||
const {url, id, length} = action.payload.parameters;
|
||||
const currentSockets = current(state.activeSocketURLs);
|
||||
state.activeSocketURLs = [...currentSockets, url];
|
||||
state.icdata[id] = {input: {
|
||||
sequence: -1,
|
||||
length: length,
|
||||
version: 2,
|
||||
type: 0,
|
||||
timestamp: Date.now(),
|
||||
values: new Array(length).fill(0)
|
||||
}, output: {}};
|
||||
},
|
||||
disconnect: (state) => {
|
||||
wsManager.disconnect(); // Ensure the WebSocket is disconnected
|
||||
state.connectedUrl = null;
|
||||
disconnect: (state, action) => {
|
||||
wsManager.disconnect(action.payload.id); // Ensure the WebSocket is disconnected
|
||||
},
|
||||
updateIcData: (state, action) => {
|
||||
const { id, newIcData } = action.payload;
|
||||
const currentICdata = current(state.icdata);
|
||||
if(currentICdata[id]){
|
||||
if(currentICdata[id].output.values){
|
||||
const {values, ...rest} = newIcData.output;
|
||||
let oldValues = [...currentICdata[id].output.values];
|
||||
for(let i = 0; i < newIcData.output.values.length; i++){
|
||||
|
@ -119,16 +152,36 @@ const websocketSlice = createSlice({
|
|||
};
|
||||
}
|
||||
},
|
||||
sendMessageToWebSocket: (state, action) => {
|
||||
const { ic, signalID, signalIndex, data} = action.payload.message;
|
||||
const currentICdata = current(state.icdata);
|
||||
|
||||
if (!(ic == null || currentICdata[ic].input == null)) {
|
||||
const inputAction = JSON.parse(JSON.stringify(currentICdata[ic].input));
|
||||
// update message properties
|
||||
inputAction.timestamp = Date.now();
|
||||
inputAction.sequence++;
|
||||
inputAction.values[signalIndex] = data;
|
||||
inputAction.length = inputAction.values.length;
|
||||
inputAction.source_index = signalID;
|
||||
// The previous line sets the source_index field of the message to the ID of the signal
|
||||
// so that upon loopback through VILLASrelay the value can be mapped to correct signal
|
||||
|
||||
state.icdata[ic].input = inputAction;
|
||||
let input = JSON.parse(JSON.stringify(inputAction));
|
||||
wsManager.send(ic, input);
|
||||
}
|
||||
}
|
||||
},
|
||||
extraReducers: (builder) => {
|
||||
builder.addCase(connectWebSocket.fulfilled, (state, action) => {
|
||||
// Handle the fulfilled state if needed
|
||||
});
|
||||
builder.addCase(connectWebSocket.rejected, (state, action) => {
|
||||
// Handle the rejected state if needed
|
||||
console.log('error', action);
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
export const { setConnectedUrl, disconnect, updateIcData } = websocketSlice.actions;
|
||||
export const { disconnect, updateIcData, addActiveSocket, sendMessageToWebSocket } = websocketSlice.actions;
|
||||
export default websocketSlice.reducer;
|
||||
|
|
Loading…
Add table
Reference in a new issue