Files
IoTManagerWeb/src/WebSocketManager.js

537 lines
15 KiB
JavaScript
Raw Normal View History

2025-03-15 15:21:42 +01:00
import { eventEmitter } from "../eventEmitter";
2024-08-24 23:00:03 +02:00
class WebSocketManager {
constructor(deviceList, debug = true) {
this.deviceList = deviceList;
this.debug = debug;
this.socket = [];
this.selectedWs = 0;
this.currentPageName = undefined;
this.reconnectTimeout = 60;
this.remainingTimeout = this.reconnectTimeout;
this.preventReconnect = false;
this.waitingAckTimeout = 18000;
this.rebootOrUpdateProcess = false;
this.showAwaitingCircle = false;
this.socketConnected = false;
this.ackTimeoutsArr = [];
this.startMillis = [];
this.ping = [];
this.layoutJson = [];
this.paramsJson = {};
this.itemsJson = {};
this.widgetsJson = {};
this.configJson = {};
this.scenarioTxt = "";
this.settingsJson = {};
this.ssidJson = {};
this.errorsJson = {};
this.incDeviceList = [];
this.flashProfileJson = {};
this.otaJson = {};
this.firstDevListRequest = true;
this.parsed = this.initializeParsedFlags();
this.pageReady = this.initializePageReadyFlags();
this.pages = [];
}
initializeParsedFlags() {
return {
itemsJson: false,
widgetsJson: false,
configJson: false,
settingsJson: false,
ssidJson: false,
errorsJson: false,
incDeviceList: false,
flashProfileJson: false,
otaJson: false,
};
}
initializePageReadyFlags() {
return {
dash: false,
config: false,
connection: false,
list: false,
system: false,
dev: false,
};
}
connectToAllDevices() {
this.deviceList.forEach((device, i) => {
device.ws = i;
if (!device.status) {
this.wsConnect(i);
this.wsEventAdd(i);
}
});
}
async initDevList() {
if (this.firstDevListRequest) {
this.devListOverride();
} else {
this.devListCombine();
}
this.firstDevListRequest = false;
this.parsed.deviceListJson = true;
this.debug && console.log("[✔]", "deviceList parsed");
this.onParsed();
this.selectedDeviceDataRefresh();
this.connectToAllDevices();
}
wsConnect(ws) {
const ip = this.getIP(ws);
if (ip === "error") {
this.debug && console.log("[e]", "device list wrong");
} else {
this.socket[ws] = new WebSocket(`ws://${ip}:81`);
this.socket[ws].binaryType = "blob";
this.debug && console.log("[i]", ip, ws, "started connecting...");
}
}
getIP(ws) {
const device = this.deviceList.find((device) => device.ws === ws);
return device ? device.ip : "error";
}
wsEventAdd(ws) {
if (!this.socket[ws]) {
this.debug && console.log("[e]", "socket not exist");
return;
}
const ip = this.getIP(ws);
this.socket[ws].addEventListener("open", () => this.handleOpen(ws, ip));
this.socket[ws].addEventListener("message", (event) => this.handleMessage(event, ws));
this.socket[ws].addEventListener("close", () => this.handleClose(ws, ip));
this.socket[ws].addEventListener("error", () => this.handleError(ws, ip));
}
handleOpen(ws, ip) {
this.markDeviceStatus(ws, true);
if (this.firstDevListRequest && ws === 0) this.wsSendMsg(ws, "/devlist|");
if (this.currentPageName === "/|") {
this.wsSendMsg(ws, this.currentPageName);
} else if (ws === this.selectedWs) {
this.sendCurrentPageNameToSelectedWs();
}
}
handleMessage(event, ws) {
if (typeof event.data === "string" && event.data === "/tstr|") {
this.ack(ws, true);
} else if (event.data instanceof Blob) {
if (ws === this.selectedWs) {
this.parseBlob(event.data, ws);
}
if (this.currentPageName === "/|") {
this.parseAllBlob(event.data, ws);
}
}
}
handleClose(ws, ip) {
this.debug && console.log("[e]", ip, "connection closed");
this.markDeviceStatus(ws, false);
}
handleError(ws, ip) {
this.debug && console.log("[e]", ip, "connection error");
this.markDeviceStatus(ws, false);
}
markDeviceStatus(ws, status) {
this.deviceList.forEach((device) => {
if (device.ws === ws) {
device.status = status;
device.ping = 0;
this.debug && console.log("[i]", device.ip, ws, `status ${status ? "online" : "offline"}`);
if (!status) {
this.deleteWidget(ws);
this.sortingLayout(ws);
}
}
});
this.selectedDeviceDataRefresh();
}
deleteWidget(ws) {
this.layoutJson = this.layoutJson.filter((item) => item.ws !== ws);
}
async parseBlob(blob, ws) {
const header = await blob.slice(0, 6).text();
const size = await blob.slice(7, 11).text();
const handlers = {
itemsj: this.handleItemsJson,
widget: this.handleWidgetsJson,
config: this.handleConfigJson,
scenar: this.handleScenarioTxt,
settin: this.handleSettingsJson,
ssidli: this.handleSsidJson,
errors: this.handleErrorsJson,
devlis: this.handleDeviceList,
prfile: this.handleFlashProfileJson,
otaupd: this.handleOtaJson,
corelg: this.handleCoreLog,
};
if (handlers[header]) {
await handlers[header].call(this, blob, size, ws);
}
await this.onParsed();
}
async handleItemsJson(blob, size) {
await this.parseJsonPayload(blob, size, "itemsJson");
}
async handleWidgetsJson(blob, size) {
await this.parseJsonPayload(blob, size, "widgetsJson");
}
async handleConfigJson(blob, size) {
await this.parseJsonPayload(blob, size, "configJson");
}
async handleScenarioTxt(blob, size) {
this.scenarioTxt = await this.getPayloadAsTxt(blob, size);
this.debug && console.log("[i]", "scenarioTxt: ", this.scenarioTxt);
}
async handleSettingsJson(blob, size) {
await this.parseJsonPayload(blob, size, "settingsJson");
}
async handleSsidJson(blob, size) {
await this.parseJsonPayload(blob, size, "ssidJson");
}
async handleErrorsJson(blob, size) {
await this.parseJsonPayload(blob, size, "errorsJson");
}
async handleDeviceList(blob, size, ws) {
await this.parseJsonPayload(blob, size, "incDeviceList");
await this.initDevList();
}
async handleFlashProfileJson(blob, size) {
await this.parseJsonPayload(blob, size, "flashProfileJson");
}
async handleOtaJson(blob, size) {
await this.parseJsonPayload(blob, size, "otaJson");
}
async handleCoreLog(blob, size) {
const txt = await this.getPayloadAsTxt(blob, size);
this.addCoreMsg(txt);
}
async parseJsonPayload(blob, size, jsonKey) {
const out = {};
if (await this.getPayloadAsJson(blob, size, out)) {
this[jsonKey] = out.json;
this.parsed[jsonKey] = true;
this.debug && console.log("[✔]", `${jsonKey}: `, this[jsonKey]);
} else {
this.parsed[jsonKey] = false;
this.debug && console.log("[e]", `${jsonKey} parse error`);
}
}
async parseAllBlob(blob, ws) {
const header = await blob.slice(0, 6).text();
const size = await blob.slice(7, 11).text();
const handlers = {
status: this.handleStatusJson,
layout: this.handleLayoutJson,
params: this.handleParamsJson,
charta: this.handleChartAJson,
chartb: this.handleChartBJson,
};
if (handlers[header]) {
await handlers[header].call(this, blob, size, ws);
}
}
async handleStatusJson(blob, size) {
await this.parseJsonPayload(blob, size, "statusJson");
}
async handleLayoutJson(blob, size, ws) {
const out = {};
if (await this.getPayloadAsJson(blob, size, out)) {
this.combineLayoutsInOne(ws, out.json);
this.debug && console.log("[✔]", "devLayout: ", out.json);
} else {
this.debug && console.log("[e]", "devLayout parse error");
}
}
async handleParamsJson(blob, size, ws) {
const out = {};
if (await this.getPayloadAsJson(blob, size, out)) {
this.paramsJson = { ...this.paramsJson, ...out.json };
this.updateAllStatuses(ws);
this.onParsed();
this.debug && console.log("[✔]", "devParams: ", out.json);
} else {
this.debug && console.log("[e]", "devParams parse error");
}
}
async handleChartAJson(blob, size) {
const txt = await this.getPayloadAsTxt(blob, size);
const chartJson = JSON.parse(`[${txt.substring(0, txt.length - 1)}]`);
const out = {};
if (await this.getJsonAsJson(blob, size, out)) {
const finalDataJson = { status: chartJson, ...out.json };
this.updateWidgetByArray(finalDataJson);
this.debug && console.log("[✔]", "chartJson: ", finalDataJson);
} else {
this.debug && console.log("[e]", "chart json add-ns parse error");
}
}
async handleChartBJson(blob, size) {
await this.parseJsonPayload(blob, size, "chartStatus");
}
sendCurrentPageNameToSelectedWs() {
if (this.selectedWs !== undefined) {
this.wsSendMsg(this.selectedWs, this.currentPageName);
}
}
wsSendMsg(ws, msg) {
if (this.socket[ws]?.readyState === 1) {
this.socket[ws].send(msg);
this.debug && console.log("[i]", this.getIP(ws), ws, "msg send success", msg);
} else {
this.debug && console.log("[e]", this.getIP(ws), ws, "msg not send");
}
}
ack(ws, st) {
if (!st) {
this.startMillis[ws] = Date.now();
this.ackTimeoutsArr[ws] = setTimeout(() => {
this.markDeviceStatus(ws, false);
}, this.waitingAckTimeout);
} else {
clearTimeout(this.ackTimeoutsArr[ws]);
this.ping[ws] = Date.now() - this.startMillis[ws];
this.deviceList.forEach((device) => {
if (device.ws === ws) {
device.ping = this.ping[ws];
}
});
}
}
selectedDeviceDataRefresh() {
const selectedDevice = this.deviceList.find((device) => device.ws === this.selectedWs);
this.socketConnected = selectedDevice?.status || false;
}
sortingLayout(ws) {
this.layoutJson.sort((a, b) => a.descr.localeCompare(b.descr));
2024-08-25 01:26:34 +02:00
this.pages = Array.from(new Set(this.layoutJson.map(({ page }) => page)))
.map((page) => ({ page }))
.sort((a, b) => a.page.localeCompare(b.page));
2025-03-15 15:21:42 +01:00
eventEmitter.emit("layoutJsonUpdated", this.layoutJson);
2024-08-25 01:26:34 +02:00
console.log("[3]", ws, "layout sort, requested params...");
2024-08-24 23:00:03 +02:00
this.wsSendMsg(ws, "/params|");
}
wsTestMsgTask() {
setTimeout(() => this.wsTestMsgTask(), 1000);
if (!this.preventReconnect) {
this.remainingTimeout--;
if (this.rebootOrUpdateProcess && this.socketConnected) {
this.rebootOrUpdateProcess = false;
this.showAwaitingCircle = false;
this.reconnectTimeout = 60;
this.remainingTimeout = this.reconnectTimeout;
}
if (this.remainingTimeout <= 0) {
this.debug && console.log("[i]", "----timer tick----");
this.remainingTimeout = this.reconnectTimeout;
this.deviceList.forEach((device) => {
if (!device.status) {
this.wsConnect(device.ws);
this.wsEventAdd(device.ws);
} else {
this.wsSendMsg(device.ws, "/tst|");
this.ack(device.ws, false);
}
});
}
}
}
async getPayloadAsJson(blob, size, out) {
const partBlob = blob.slice(size, blob.length);
const txt = await partBlob.text();
try {
out.json = JSON.parse(txt);
out.parse = true;
} catch (e) {
this.debug && console.log("[e]", "json parse error: ", txt);
out.parse = false;
}
return out.parse;
}
async getPayloadAsTxt(blob, size) {
const partBlob = blob.slice(size, blob.length);
return await partBlob.text();
}
async getJsonAsJson(blob, size, out) {
const partBlob = blob.slice(size, blob.length);
const txt = await partBlob.text();
try {
out.json = JSON.parse(txt);
out.parse = true;
} catch (e) {
this.debug && console.log("[e]", "json parse error: ", txt);
out.parse = false;
}
return out.parse;
}
async devListOverride() {
this.deviceList = this.incDeviceList;
this.sortList(this.deviceList);
this.deviceList[0].status = true;
this.debug && console.log("[i]", "[devlist]", "devlist overridden");
}
async onParsed() {
const pageHandlers = {
"/|": () => (this.pageReady.dash = true),
"/config|": () => this.handleConfigPage(),
"/connection|": () => this.handleConnectionPage(),
"/list|": () => this.handleListPage(),
"/system|": () => this.handleSystemPage(),
"/profile|": () => this.handleProfilePage(),
};
if (pageHandlers[this.currentPageName]) {
await pageHandlers[this.currentPageName].call(this);
}
}
handleConfigPage() {
if (this.parsed.itemsJson && this.parsed.widgetsJson && this.parsed.configJson && this.parsed.settingsJson) {
this.clearParsedFlags();
this.pageReady.config = true;
this.debug && console.log("✔✔", "config page parsed");
}
}
handleConnectionPage() {
if (this.parsed.ssidJson && this.parsed.settingsJson && this.parsed.errorsJson) {
this.clearParsedFlags();
this.pageReady.connection = true;
this.debug && console.log("✔✔", "connection page parsed");
}
}
handleListPage() {
if (this.parsed.settingsJson) {
this.clearParsedFlags();
this.pageReady.list = true;
this.debug && console.log("✔✔", "list page parsed");
}
}
handleSystemPage() {
if (this.parsed.errorsJson && this.parsed.settingsJson) {
this.clearParsedFlags();
this.getVersionsList();
this.pageReady.system = true;
this.debug && console.log("✔✔", "system page parsed");
}
}
async handleProfilePage() {
if (this.parsed.flashProfileJson) {
this.clearParsedFlags();
this.pageReady.profile = true;
this.debug && console.log("✔✔", "profile page parsed");
await this.getModInfo();
await this.getProfile();
}
}
async devListCombine() {
this.deviceList = this.combineArrays(this.deviceList, this.incDeviceList);
this.sortList(this.deviceList);
this.debug && console.log("[i]", "[devlist]", "devlist combined");
}
combineArrays(A, B) {
const ids = new Set(A.map((d) => d.ip));
return [...A, ...B.filter((d) => !ids.has(d.ip))];
}
getSelectedDeviceData(ws) {
this.selectedDeviceData = this.deviceList.find((device) => device.ws === ws);
}
sortList(list) {
const firstDev = list.shift();
list.sort((a, b) => a.name.localeCompare(b.name));
list.unshift(firstDev);
}
sendToAllDevices(msg) {
this.deviceList.forEach((device) => {
if (device.status === true) {
this.wsSendMsg(device.ws, msg);
}
});
}
async combineLayoutsInOne(ws, devLayout) {
2024-08-25 01:26:34 +02:00
devLayout.forEach((item) => (item.ws = ws));
2024-08-24 23:00:03 +02:00
this.layoutJson = this.layoutJson.concat(devLayout);
console.log("[2]", ws, "devLayout pushed to layout");
this.sortingLayout(ws);
}
updateAllStatuses(ws) {
for (const [key, value] of Object.entries(this.paramsJson)) {
2024-08-25 01:26:34 +02:00
this.layoutJson.forEach((item) => {
let topic = item.topic;
2024-08-24 23:00:03 +02:00
if (topic) {
2024-08-25 01:26:34 +02:00
topic = topic.substring(topic.lastIndexOf("/") + 1);
2024-08-24 23:00:03 +02:00
if (key === topic) {
console.log("[i]", "updated =>" + topic, value);
2024-08-25 01:26:34 +02:00
item.status = value;
2024-08-24 23:00:03 +02:00
}
}
2024-08-25 01:26:34 +02:00
});
2024-08-24 23:00:03 +02:00
}
this.wsSendMsg(ws, "/charts|");
}
}
export default WebSocketManager;