From 37ad2654e4d0da7d8335212a917ecbc96d6ec76f Mon Sep 17 00:00:00 2001 From: unknown Date: Tue, 17 Dec 2024 14:40:21 +0100 Subject: [PATCH 1/8] Removed/Added unexpected/expected props for control element components for devices. No more unnecessary warning messages when adding virtual modules --- .../grid-layout/grid-modules/devices/EF44.svelte | 9 +-------- .../grid-layout/grid-modules/devices/PBF4.svelte | 9 +-------- .../grid-layout/grid-modules/devices/TEK1.svelte | 14 ++------------ .../grid-layout/grid-modules/devices/VSNX.svelte | 8 +++----- .../grid-layout/grid-modules/devices/XX16.svelte | 14 ++++---------- .../grid-modules/elements/LcdAndMenuButtons.svelte | 6 +----- 6 files changed, 12 insertions(+), 48 deletions(-) diff --git a/src/renderer/main/grid-layout/grid-modules/devices/EF44.svelte b/src/renderer/main/grid-layout/grid-modules/devices/EF44.svelte index 2fe375845..d41ad81ed 100644 --- a/src/renderer/main/grid-layout/grid-modules/devices/EF44.svelte +++ b/src/renderer/main/grid-layout/grid-modules/devices/EF44.svelte @@ -108,12 +108,7 @@
- +
@@ -138,10 +133,8 @@
diff --git a/src/renderer/main/grid-layout/grid-modules/devices/PBF4.svelte b/src/renderer/main/grid-layout/grid-modules/devices/PBF4.svelte index 63d70a806..24f9fd7e8 100644 --- a/src/renderer/main/grid-layout/grid-modules/devices/PBF4.svelte +++ b/src/renderer/main/grid-layout/grid-modules/devices/PBF4.svelte @@ -127,10 +127,8 @@ @@ -154,12 +152,7 @@
-
@@ -186,12 +181,7 @@
diff --git a/src/renderer/main/grid-layout/grid-modules/devices/VSNX.svelte b/src/renderer/main/grid-layout/grid-modules/devices/VSNX.svelte index cfe32c7e6..b30fc653c 100644 --- a/src/renderer/main/grid-layout/grid-modules/devices/VSNX.svelte +++ b/src/renderer/main/grid-layout/grid-modules/devices/VSNX.svelte @@ -11,6 +11,7 @@ export let moduleWidth; export let device = undefined; + export let id = device.type; let [dx, dy] = [device?.dx, device?.dy]; let moduleType = device?.type; @@ -225,6 +226,7 @@ {/each}
diff --git a/src/renderer/main/grid-layout/grid-modules/devices/XX16.svelte b/src/renderer/main/grid-layout/grid-modules/devices/XX16.svelte index a954d615c..c1ba67bf3 100644 --- a/src/renderer/main/grid-layout/grid-modules/devices/XX16.svelte +++ b/src/renderer/main/grid-layout/grid-modules/devices/XX16.svelte @@ -11,6 +11,7 @@ export let moduleWidth; export let device = undefined; + export let id = device.type; let [dx, dy] = [device?.dx, device?.dy]; let moduleType = device?.type; @@ -105,23 +106,16 @@
{#if moduleType === ModuleType.BU16} -
diff --git a/src/renderer/main/grid-layout/grid-modules/elements/LcdAndMenuButtons.svelte b/src/renderer/main/grid-layout/grid-modules/elements/LcdAndMenuButtons.svelte index 7e2acae68..6910b0d6b 100644 --- a/src/renderer/main/grid-layout/grid-modules/elements/LcdAndMenuButtons.svelte +++ b/src/renderer/main/grid-layout/grid-modules/elements/LcdAndMenuButtons.svelte @@ -40,11 +40,7 @@ {/if}
{#if elementNumber == Math.min(...elementNumberList)} From 060c7dabff4131bd1877bff1ceba2864f3541fd9 Mon Sep 17 00:00:00 2001 From: unknown Date: Tue, 17 Dec 2024 15:27:05 +0100 Subject: [PATCH 2/8] Reworked serialport and runtime to accept multiple runtime instances --- src/renderer/runtime/runtime.store.ts | 35 +++++++++++++++++++++++++-- src/renderer/serialport/serialport.ts | 16 ++++++------ 2 files changed, 41 insertions(+), 10 deletions(-) diff --git a/src/renderer/runtime/runtime.store.ts b/src/renderer/runtime/runtime.store.ts index 95951a4fc..38915edec 100644 --- a/src/renderer/runtime/runtime.store.ts +++ b/src/renderer/runtime/runtime.store.ts @@ -4,6 +4,32 @@ import { appSettings } from "./app-helper.store"; import { modal, Snap } from "../main/modals/modal.store"; import { ProtectedStore } from "./smart-store.store"; import { GridAction, GridRuntime, aliveModules } from "./runtime"; +import { connection_manager, GridPort } from "../serialport/serialport"; + +function create_runtime_store(): GridRuntime { + const _internal: Writable<{ data: GridRuntime; port: GridPort }[]> = writable( + [] + ); + let runtime = undefined; + + connection_manager.active.subscribe((port) => { + const active = get(_internal).find((e) => e.port.id === port.id); + if (active) { + runtime = active.data; + } else { + const incoming = { data: new GridRuntime(), port: port }; + _internal.update((store) => { + store.push(incoming); + return store; + }); + runtime = incoming.data; + } + }); + + return runtime; +} + +export let runtime: GridRuntime = create_runtime_store(); const setIntervalAsync = (fn, ms) => { fn().then(() => { @@ -320,8 +346,6 @@ function create_user_input() { }; } -export const runtime = new GridRuntime(); - export type UserInputValue = { dx: number; dy: number; @@ -388,6 +412,10 @@ const heartbeat_editor_ms = 300; const heartbeat_grid_ms = 250; const grid_heartbeat_interval_handler = async function () { + if (!runtime) { + return; + } + for (const device of runtime.modules) { if (device.architecture === "virtual") { return; @@ -420,6 +448,9 @@ const editor_heartbeat_interval_handler = async function () { type = 254; } */ + if (!runtime) { + return; + } if ( runtime.modules.length > 0 && diff --git a/src/renderer/serialport/serialport.ts b/src/renderer/serialport/serialport.ts index 08c98cd8a..158c2d9ac 100644 --- a/src/renderer/serialport/serialport.ts +++ b/src/renderer/serialport/serialport.ts @@ -61,11 +61,11 @@ const filter: SerialPortInfo[] = [ class GridConnectionManager { private _ports: Writable; - private _active: GridPort; + private _active: Writable; constructor() { this._ports = writable([]); - this._active = undefined; + this._active = writable(); } get ports() { @@ -85,7 +85,7 @@ class GridConnectionManager { const ports = get(this._ports); this._ports.set(ports.filter((e) => e.id !== current.id)); if (get(this._ports).length > 0) { - if (this._active.id === current.id) { + if (get(this._active).id === current.id) { this.fetchStream(get(this._ports)[0]); } } else { @@ -113,7 +113,7 @@ class GridConnectionManager { } isSerialWriteLocked() { - const port = this.active; + const port = get(this.active); if (port === undefined || port === null) { return true; } @@ -132,7 +132,7 @@ class GridConnectionManager { return Promise.reject("Serial Write Error 1."); } - const port = this.active; + const port = get(this.active); if (port === undefined || port === null) { return Promise.reject("Serial Write Error 2."); @@ -177,11 +177,11 @@ class GridConnectionManager { return; } - if (port.id === this.active?.id) { + if (port.id === get(this.active)?.id) { return; } - this._active = port; + this._active.set(port); const reader = port.readable.getReader(); let charsReceived = 0; let rxBuffer = []; @@ -190,7 +190,7 @@ class GridConnectionManager { while (true) { const { done, value } = await reader.read(); - if (done || this.active !== port) { + if (done || get(this.active) !== port) { console.log("Stream complete"); break; } From 3d929e83422877dd6cae167d052caf69e4d422d8 Mon Sep 17 00:00:00 2001 From: unknown Date: Wed, 18 Dec 2024 23:05:01 +0100 Subject: [PATCH 3/8] Daily push --- src/renderer/App.svelte | 19 +- src/renderer/main/MiddlePanelContainer.svelte | 3 +- .../panels/DebugMonitor/DebugMonitor.svelte | 4 +- .../panels/preferences/Preferences.svelte | 45 +- .../main/user-interface/ActiveChanges.svelte | 9 +- .../user-interface/ModuleHangingDialog.svelte | 5 +- .../main/user-interface/PortSelector.svelte | 9 +- src/renderer/runtime/engine.store.ts | 146 ++-- src/renderer/runtime/runtime.store.ts | 330 +++++--- src/renderer/runtime/runtime.ts | 127 +++- src/renderer/runtime/virtual-engine.ts | 388 +++++----- src/renderer/serialport/instructions.ts | 709 ++++++++++-------- ...tream.store.js => message-stream.store.ts} | 30 +- src/renderer/serialport/serialport.ts | 200 ++--- 14 files changed, 1115 insertions(+), 909 deletions(-) rename src/renderer/serialport/{message-stream.store.js => message-stream.store.ts} (92%) diff --git a/src/renderer/App.svelte b/src/renderer/App.svelte index b6de28685..915c8073b 100644 --- a/src/renderer/App.svelte +++ b/src/renderer/App.svelte @@ -1,4 +1,4 @@ -
import { runtime, + runtime_manager, user_input, UserInputValue, } from "./../../../runtime/runtime.store"; @@ -23,7 +24,6 @@ import { incoming_messages } from "../../../serialport/message-stream.store"; import { Pane, Splitpanes } from "svelte-splitpanes"; import { MoltenPushButton, MoltenInput } from "@intechstudio/grid-uikit"; - import { instructions } from "../../../serialport/instructions"; let event: GridEvent; @@ -196,7 +196,7 @@ { - instructions.sendImmediateToGrid( + runtime_manager.LUAExecImmediate( 0, 0, "" diff --git a/src/renderer/main/panels/preferences/Preferences.svelte b/src/renderer/main/panels/preferences/Preferences.svelte index 166e72dbb..f05b761b7 100644 --- a/src/renderer/main/panels/preferences/Preferences.svelte +++ b/src/renderer/main/panels/preferences/Preferences.svelte @@ -1,9 +1,8 @@ diff --git a/src/renderer/runtime/engine.store.ts b/src/renderer/runtime/engine.store.ts index 5010a9ec1..bc696b79d 100644 --- a/src/renderer/runtime/engine.store.ts +++ b/src/renderer/runtime/engine.store.ts @@ -1,13 +1,9 @@ -import { get, writable } from "svelte/store"; +import { get, Readable, Unsubscriber, Updater, writable } from "svelte/store"; import { grid } from "@intechstudio/grid-protocol"; -import { connection_manager } from "../serialport/serialport"; +import { connection_manager, GridPort } from "../serialport/serialport"; import { appSettings } from "./app-helper.store"; - -import { instructions } from "../serialport/instructions"; +import { Subscriber } from "svelte/motion"; import { simulateProcess } from "./virtual-engine"; -import { runtime } from "./runtime.store"; -import { virtual_runtime } from "./virtual-engine"; -import { buffer } from "stream/consumers"; export enum InstructionClassName { HEARTBEAT = "HEARTBEAT", @@ -31,8 +27,9 @@ export enum InstructionClass { export type BufferElement = { id: number; + virtual: boolean; descr: { - brc_parameters: { DX: number; DY: number }; + brc_parameters: { DX: number; DY: number; ROT?: number }; class_name: InstructionClassName; class_instr: InstructionClass; class_parameters: { @@ -154,12 +151,32 @@ class ResponseWaiter { let waiter: ResponseWaiter | undefined = undefined; -function createWriteBuffer() { - let _write_buffer = writable([] as any[]); +export class WriteBuffer implements Readable { + private _internal = writable([]); + private _port: GridPort; + + constructor(port: GridPort) { + this._port = port; + } + + public subscribe( + run: Subscriber, + invalidate?: (value?: BufferElement[]) => void + ): Unsubscriber { + return this._internal.subscribe(run, invalidate); + } + + private set(value: BufferElement[]) { + this._internal.set(value); + } + + private update(updater: Updater) { + this._internal.update(updater); + } - function module_destroy_handler(dx: Number, dy: Number) { + public module_destroy_handler(dx: Number, dy: Number) { // remove all of the elements that match the destroyed module's dx dy - _write_buffer.update((s) => + this.update((s) => s.filter( (g) => g.descr.brc_parameters.DX != dx || g.descr.brc_parameters.DY != dy @@ -176,18 +193,18 @@ function createWriteBuffer() { } } - function clear() { - _write_buffer.set([]); + public clear() { + this.set([]); waiter?.destroy(); waiter = undefined; } - function sendDataToGrid(descr: any): Promise { + public sendDataToGrid(descr: any): Promise { return new Promise((resolve, reject) => { let retval: any = grid.encode_packet(descr); connection_manager - .serialWrite(retval.serial) + .serialWrite(retval.serial, this._port) .then(() => { // debugger for message ASCII frames let str = ""; @@ -201,7 +218,7 @@ function createWriteBuffer() { }); } - async function waitResponseFromGrid( + public async waitResponseFromGrid( bufferElement: any, timeout: number ): Promise { @@ -211,16 +228,16 @@ function createWriteBuffer() { return response; } - async function sleep(time: number) { + public async sleep(time: number) { await new Promise((resolve) => setTimeout(resolve, time)); } - async function sendToGrid( + public async sendToGrid( bufferElement: BufferElement, sendImmediate: boolean = false ) { return new Promise((resolve, reject) => { - sendDataToGrid(bufferElement.descr) + this.sendDataToGrid(bufferElement.descr) .then(async (result) => { const { id } = result; if (bufferElement.responseRequired === true && !sendImmediate) { @@ -229,7 +246,10 @@ function createWriteBuffer() { class_parameters.LASTHEADER = id; } const timeout = bufferElement.responseTimeout ?? 1000; - const response = await waitResponseFromGrid(bufferElement, timeout); + const response = await this.waitResponseFromGrid( + bufferElement, + timeout + ); switch (response.status) { case ResponseStatus.OK: { resolve(response.data); @@ -241,7 +261,7 @@ function createWriteBuffer() { } case ResponseStatus.TIMEOUT: { console.log(response.error); - resolve(sendToGrid(bufferElement)); // RETRY recursively until processed + resolve(this.sendToGrid(bufferElement)); // RETRY recursively until processed break; } } @@ -255,19 +275,19 @@ function createWriteBuffer() { }); } - function processElement(current: BufferElement): Promise { + public processElement(current: BufferElement): Promise { return new Promise(async (resolve, reject) => { const sendImmediate = (current.sendImmediate ?? false) && get(appSettings).persistent.sendHeartbeatImmediate; while ( - connection_manager.isSerialWriteLocked() || - get(writeBuffer)[0] !== current || + connection_manager.isSerialWriteLocked(this._port) || + get(this._internal)[0] !== current || (typeof waiter !== "undefined" && !sendImmediate) ) { - if (get(writeBuffer).includes(current)) { - await sleep(1); + if (get(this._internal).includes(current)) { + await this.sleep(1); } else { reject( `Instruction ${current.descr.class_name} was removed from write buffer.` @@ -276,12 +296,12 @@ function createWriteBuffer() { } } - await sleep(10); - sendToGrid(current, sendImmediate).then(resolve).catch(reject); + await this.sleep(10); + this.sendToGrid(current, sendImmediate).then(resolve).catch(reject); }); } - function validate_incoming(descr: any) { + public validate_incoming(descr: any) { if (typeof waiter === "undefined") return; if (!waiter.bufferelement.hasOwnProperty("filter")) return; @@ -321,44 +341,32 @@ function createWriteBuffer() { } } - function validateBufferElement(obj: BufferElement) { + public validateBufferElement(obj: BufferElement) { if (obj.responseRequired && obj.sendImmediate) { throw "Response required and send immediate can not be used together!"; } } - function add_first(obj: BufferElement) { - validateBufferElement(obj); - _write_buffer.update((s) => [obj, ...s]); - return execute(obj); + public add_first(obj: BufferElement) { + this.validateBufferElement(obj); + this.update((s) => [obj, ...s]); + return this.execute(obj); } - async function add_last(obj: BufferElement) { - validateBufferElement(obj); - _write_buffer.update((s) => [...s, obj]); - return execute(obj); + public async add_last(obj: BufferElement) { + this.validateBufferElement(obj); + this.update((s) => [...s, obj]); + return this.execute(obj); } - async function execute(obj: BufferElement) { + public async execute(obj: BufferElement) { return new Promise((resolve, reject) => { let process: (obj: BufferElement) => Promise; - const [dx, dy]: number[] = [ - obj.descr.brc_parameters.DX, - obj.descr.brc_parameters.DY, - ]; - const sender: any = runtime.modules.find( - (e: any) => e.dx === dx && e.dy === dy - )!; - - //TODO: rework instructions into well defined ones, - //where the checking of virtual_runtime is unnecessary - if ( - sender?.architecture === "virtual" || - get(virtual_runtime).length > 0 - ) { + console.log(obj, obj.virtual); + if (obj.virtual) { process = simulateProcess; } else { - process = processElement; + process = this.processElement; } process(obj) @@ -371,37 +379,11 @@ function createWriteBuffer() { reject(e); }) .finally(() => { - _write_buffer.update((s) => { + this.update((s) => { s.shift(); return s; }); }); }); } - - return { - subscribe: _write_buffer.subscribe, - add_fist: add_first, - add_last: add_last, - clear: clear, - validate_incoming: validate_incoming, - module_destroy_handler: module_destroy_handler, - }; -} - -export const writeBuffer = createWriteBuffer(); - -export function sendHeartbeat(type: number) { - // Only add heatbeat into the write buffer if it is not in it already - const buffer = get(writeBuffer); - const isHeartbeatPresent = buffer.some( - (e: any) => e.descr.class_name === "HEARTBEAT" - ); - - if (isHeartbeatPresent) { - return; - } - instructions.sendEditorHeartbeat_immediate(type).catch((e) => { - console.log("EDITOR: Heartbeat skipped..."); - }); } diff --git a/src/renderer/runtime/runtime.store.ts b/src/renderer/runtime/runtime.store.ts index 38915edec..9935378f7 100644 --- a/src/renderer/runtime/runtime.store.ts +++ b/src/renderer/runtime/runtime.store.ts @@ -1,35 +1,184 @@ -import { writable, get, type Writable } from "svelte/store"; -import { writeBuffer, sendHeartbeat } from "./engine.store"; +import { + writable, + get, + type Writable, + derived, + Subscriber, + Unsubscriber, + Updater, + Readable, +} from "svelte/store"; import { appSettings } from "./app-helper.store"; import { modal, Snap } from "../main/modals/modal.store"; import { ProtectedStore } from "./smart-store.store"; import { GridAction, GridRuntime, aliveModules } from "./runtime"; -import { connection_manager, GridPort } from "../serialport/serialport"; +import { GridInstruction } from "../serialport/instructions"; +import { GridConnection } from "../serialport/serialport"; +import { WriteBuffer } from "./engine.store"; +import { MessageStream } from "../serialport/message-stream.store"; + +type GridRuntimeManagerData = { + data: GridRuntime[]; + active: GridRuntime; +}; -function create_runtime_store(): GridRuntime { - const _internal: Writable<{ data: GridRuntime; port: GridPort }[]> = writable( - [] - ); - let runtime = undefined; - - connection_manager.active.subscribe((port) => { - const active = get(_internal).find((e) => e.port.id === port.id); - if (active) { - runtime = active.data; - } else { - const incoming = { data: new GridRuntime(), port: port }; - _internal.update((store) => { - store.push(incoming); - return store; +class GridRuntimeManager implements Readable { + private _internal: Writable = writable({ + data: [], + active: undefined, + }); + + public subscribe( + run: Subscriber, + invalidate?: (value?: GridRuntimeManagerData) => void + ): Unsubscriber { + return this._internal.subscribe(run, invalidate); + } + + private set(value: GridRuntimeManagerData) { + this._internal.set(value); + } + + private update(updater: Updater) { + this._internal.update(updater); + } + + public allocate() { + const runtime = new GridRuntime(); + this.update((store) => { + store.data.push(runtime); + + if (typeof store.active === "undefined") { + store.active = runtime; + } + return store; + }); + + return runtime; + } + + public free(runtime: GridRuntime) { + this.update((store) => { + const destroyed = store.data.find((e) => e.id === runtime.id); + if (!destroyed) { + throw new Error(""); + } + + store.data = store.data.filter((e) => e.id !== runtime.id); + + if (store.active.id === runtime.id) { + store.active = store.data[0]; + } + return store; + }); + } + + public NVMErase() { + logger.set({ + type: "progress", + mode: 0, + classname: "nvmerase", + message: `Erasing all modules...`, + }); + const promises: Promise[] = []; + for (const target of get(this._internal).data) { + const instruction = new GridInstruction.NVMErase(target.virtual); + promises.push(instruction.executeOn(target.connection)); + } + Promise.all(promises) + .then((res) => { + //TODO + logger.set({ + type: "success", + mode: 0, + classname: "nvmerase", + message: `Erase complete!`, + }); + }) + .catch((e) => { + if (typeof e !== "undefined") { + logger.set(e); + } else { + logger.set({ + type: "alert", + mode: 0, + classname: "nvmerase", + message: `Retry erase all modules...`, + }); + } }); - runtime = incoming.data; + } + + public NVMDefrag() { + logger.set({ + type: "progress", + mode: 0, + classname: "nvmdefrag", + message: `Defragging all modules...`, + }); + + const promises: Promise[] = []; + for (const target of get(this._internal).data) { + const instruction = new GridInstruction.NVMDefrag(target.virtual); + promises.push(instruction.executeOn(target.connection)); } - }); + Promise.all(promises) + .then((res) => { + //TODO + logger.set({ + type: "success", + mode: 0, + classname: "nvmdefrag", + message: `Defrag complete!`, + }); + }) + .catch((e) => { + logger.set({ + type: "fail", + mode: 0, + classname: "engine-disabled", + message: `Engine is disabled, NVM Defragmentation failed!`, + }); + }); + } - return runtime; + public LUAExecImmediate(dx: number, dy: number, script: string) { + const target = get(this._internal).active; + if (!target) { + //ERROR HANDLING + return; + } + + const instruction = new GridInstruction.SendConfigImmediate( + dx, + dy, + script, + target.virtual + ); + instruction.executeOn(target.connection).catch((e) => { + console.warn(e); + }); + } + + public createVirtualRuntime(): GridRuntime { + const buffer = new WriteBuffer(undefined); + const port = undefined; + const messageStream = new MessageStream(buffer); + const virtual_connection: GridConnection = { + id: undefined, + buffer: buffer, + port: port, + messageStream: messageStream, + virtual: true, + }; + + return new GridRuntime(virtual_connection, true); + } } -export let runtime: GridRuntime = create_runtime_store(); +export const runtime_manager = new GridRuntimeManager(); + +export let runtime: GridRuntime = runtime_manager.createVirtualRuntime(); const setIntervalAsync = (fn, ms) => { fn().then(() => { @@ -236,11 +385,6 @@ function create_user_input() { } function process_incoming_event_from_grid(descr) { - // engine is disabled - if (get(writeBuffer).length > 0) { - return; - } - // modal block track physical interaction setting if ( typeof get(modal) !== "undefined" && @@ -367,103 +511,6 @@ function create_selected_actions_store() { return internal; } -//Retrieves device name from coordinates of the device -export function getDeviceName(x, y) { - const currentModule = runtime.modules.find( - (device) => device.dx == x && device.dy == y - ); - return currentModule?.type; -} - -export function getElementEventTypes(x, y, elementNumber) { - const currentModule = runtime.modules.find( - (device) => device.dx == x && device.dy == y - ); - - if (typeof currentModule === "undefined") { - console.warn(`Module does not exist on (${x}, ${y})`); - return undefined; - } - const element = currentModule.pages[0].control_elements.find( - (e) => e.elementIndex == elementNumber - ); - - if (typeof element === "undefined") { - console.warn( - `Control element ${elementNumber} does not exist on (${x}, ${y})` - ); - return undefined; - } - - return element.events.map((e) => e.type); -} - -function createEngine() { - const _engine = writable("ENABLED"); - - return { - ..._engine, - }; -} - -export const engine = createEngine(); - -const heartbeat_editor_ms = 300; -const heartbeat_grid_ms = 250; - -const grid_heartbeat_interval_handler = async function () { - if (!runtime) { - return; - } - - for (const device of runtime.modules) { - if (device.architecture === "virtual") { - return; - } - - const last = - get(aliveModules).find((e) => e.id === device.id)?.last ?? Date.now(); - - // Allow less strict elapsedTimeLimit while writeBuffer is busy! - const elapsedTimeLimit = - get(writeBuffer).length > 0 - ? heartbeat_grid_ms * 6 - : heartbeat_grid_ms * 3; - const elapsedTime = Date.now() - last; - - if (!last || elapsedTime > elapsedTimeLimit) { - // TIMEOUT! let's remove the device - runtime.destroy_module(device.dx, device.dy); - } - } -}; - -setIntervalAsync(grid_heartbeat_interval_handler, heartbeat_grid_ms); - -const editor_heartbeat_interval_handler = async function () { - let type = 255; - - /* - if (runtime.unsavedChangesCount() != 0 || typeof get(modal) !== "undefined") { - type = 254; - } - */ - if (!runtime) { - return; - } - - if ( - runtime.modules.length > 0 && - runtime.modules.filter((e) => e.architecture === "virtual").length === 0 - ) { - sendHeartbeat(type); - } else { - //writeBuffer.clear(); - } -}; - -setIntervalAsync(editor_heartbeat_interval_handler, heartbeat_editor_ms); - export class LocalDefinitions { static getFrom({ configs, index }) { const config = configs[index]; @@ -499,4 +546,37 @@ export async function wss_send_message(message) { window.electron.websocket.transmit({ event: "message", data: message }); } -console.log("reached end of runtime"); +//////////////////////////////////////////////////////// +////////// Helper functions to refactor out //////////// +//////////////////////////////////////////////////////// + +//Retrieves device name from coordinates of the device +export function getDeviceName(x, y) { + const currentModule = runtime.modules.find( + (device) => device.dx == x && device.dy == y + ); + return currentModule?.type; +} + +export function getElementEventTypes(x, y, elementNumber) { + const currentModule = runtime.modules.find( + (device) => device.dx == x && device.dy == y + ); + + if (typeof currentModule === "undefined") { + console.warn(`Module does not exist on (${x}, ${y})`); + return undefined; + } + const element = currentModule.pages[0].control_elements.find( + (e) => e.elementIndex == elementNumber + ); + + if (typeof element === "undefined") { + console.warn( + `Control element ${elementNumber} does not exist on (${x}, ${y})` + ); + return undefined; + } + + return element.events.map((e) => e.type); +} diff --git a/src/renderer/runtime/runtime.ts b/src/renderer/runtime/runtime.ts index 5157a2ea8..bd4d82ea1 100644 --- a/src/renderer/runtime/runtime.ts +++ b/src/renderer/runtime/runtime.ts @@ -15,9 +15,8 @@ import { Unsubscriber, Updater, } from "svelte/store"; -import { instructions } from "../serialport/instructions"; -import { writeBuffer } from "./engine.store"; -import { createVirtualModule, virtual_runtime } from "./virtual-engine"; +import { GridInstruction } from "../serialport/instructions"; +import { connection_simulator } from "./virtual-engine"; import { Analytics } from "./analytics.js"; import { appSettings } from "./app-helper.store"; import { add_datapoint } from "../serialport/message-stream.store.js"; @@ -34,6 +33,8 @@ import { appClipboard, ClipboardKey } from "./clipboard.store"; import { ActionBlockInformation } from "../config-blocks/ActionBlockInformation"; import { Runtime } from "./string-table"; import { Grid } from "../lib/_utils"; +import { GridConnection } from "../serialport/serialport"; +import { setIntervalAsync } from "set-interval-async"; type UUID = string; type LuaScript = string; @@ -696,17 +697,21 @@ export class GridEvent extends RuntimeNode { const element = this.parent as GridElement; const page = element.parent as GridPage; const module = page.parent as GridModule; + const runtime = module.parent as GridRuntime; try { const script = this.toLua(); - await instructions.sendConfigToGrid( + const simulate = module.architecture === Architecture.VIRTUAL; + const instruction = new GridInstruction.SendConfig( module.dx, module.dy, page.pageNumber, element.elementIndex, this.type, - script + script, + simulate ); + await instruction.executeOn(runtime.connection); return Promise.resolve({ value: true, text: "OK", @@ -861,15 +866,19 @@ export class GridEvent extends RuntimeNode { const element = this.parent as GridElement; const page = element.parent as GridPage; const module = page.parent as GridModule; + const runtime = module.parent as GridRuntime; try { - const descr = await instructions.fetchConfigFromGrid( + const simulate = module.architecture === Architecture.VIRTUAL; + const instruction = new GridInstruction.FetchConfig( module.dx, module.dy, page.pageNumber, element.elementIndex, - this.type + this.type, + simulate ); + const descr = await instruction.executeOn(runtime.connection); const script = descr.class_parameters.ACTIONSTRING; const actions = GridAction.parse(script); @@ -1399,8 +1408,31 @@ export interface RuntimeData extends NodeData { } export class GridRuntime extends RuntimeNode { - constructor() { + public connection: GridConnection; + public readonly heartbeat_editor_ms = 300; + public readonly heartbeat_grid_ms = 250; + public readonly virtual: boolean; + + constructor( + connection: GridConnection = undefined, + virtual: boolean = false + ) { super(undefined, { modules: [] }); + this.connection = connection; + this.virtual = virtual; + + setIntervalAsync( + () => GridRuntime.editor_heartbeat_interval_handler(this), + this.heartbeat_editor_ms + ); + setIntervalAsync( + () => GridRuntime.grid_heartbeat_interval_handler(this), + this.heartbeat_grid_ms + ); + } + + public bind(connection: GridConnection) { + this.connection = connection; } get modules() { @@ -1574,7 +1606,7 @@ export class GridRuntime extends RuntimeNode { public async change_page(new_page_number): Promise { return new Promise((resolve, reject) => { - if (get(writeBuffer).length > 0) { + if (get(this.connection.buffer).length > 0) { reject("Wait before all operations are finished."); return; } @@ -1594,8 +1626,12 @@ export class GridRuntime extends RuntimeNode { // clean up the writebuffer if pagenumber changes! // writeBuffer.clear(); - instructions - .changeActivePage(new_page_number) + const instruction = new GridInstruction.ChangePage( + new_page_number, + this.virtual + ); + instruction + .executeOn(this.connection) .then(() => { const ui = get(user_input); user_input.set({ @@ -1631,8 +1667,9 @@ export class GridRuntime extends RuntimeNode { public async storePage(index: number) { return new Promise((resolve, reject) => { - instructions - .sendPageStoreToGrid() + const instruction = new GridInstruction.StorePage(this.virtual); + instruction + .executeOn(this.connection) .then((res) => { for (const module of this.modules) { const page = module.findPage(index); @@ -1654,8 +1691,9 @@ export class GridRuntime extends RuntimeNode { message: `Clearing configurations from page...`, }); return new Promise((resolve, reject) => { - instructions - .sendPageClearToGrid() + const instruction = new GridInstruction.ClearPage(this.virtual); + instruction + .executeOn(this.connection) .then(() => { for (const module of this.modules) { const page = module.findPage(index); @@ -1686,8 +1724,9 @@ export class GridRuntime extends RuntimeNode { message: `Discarding configurations...`, }); return new Promise((resolve, reject) => { - instructions - .sendPageDiscardToGrid() + const instruction = new GridInstruction.DiscardPage(this.virtual); + instruction + .executeOn(this.connection) .then(() => { for (const module of this.modules) { const page = module.findPage(index); @@ -1729,7 +1768,7 @@ export class GridRuntime extends RuntimeNode { true ); - createVirtualModule(dx, dy, moduleInfo.type); + connection_simulator.createModule(dx, dy, moduleInfo.type); this.modules = [...this.modules, controller]; this.setDefaultSelectedElement(); @@ -1803,9 +1842,9 @@ export class GridRuntime extends RuntimeNode { } if (removed.architecture === "virtual") { - virtual_runtime.destroyModule(dx, dy); + connection_simulator.destroyModule(dx, dy); } else { - writeBuffer.module_destroy_handler(dx, dy); + this.connection.buffer.module_destroy_handler(dx, dy); } // reset rendering helper stores @@ -1831,4 +1870,52 @@ export class GridRuntime extends RuntimeNode { mandatory: false, }); } + + private static async editor_heartbeat_interval_handler(runtime: GridRuntime) { + let type = 255; + + // if (runtime.unsavedChangesCount() != 0 || typeof get(modal) !== "undefined") { + // type = 254; + // } + + if ( + runtime.modules.length > 0 && + runtime.modules.filter((e) => e.architecture === "virtual").length === 0 + ) { + const instruction = new GridInstruction.SendHeartbeatImmediate( + type, + runtime.virtual + ); + + console.log(runtime); + instruction.executeOn(runtime.connection).catch((e) => { + console.log("EDITOR: Heartbeat skipped..."); + }); + } else { + //writeBuffer.clear(); + } + } + + private static async grid_heartbeat_interval_handler(runtime: GridRuntime) { + for (const device of runtime.modules) { + if (device.architecture === Architecture.VIRTUAL) { + return; + } + + const last = + get(aliveModules).find((e) => e.id === device.id)?.last ?? Date.now(); + + // Allow less strict elapsedTimeLimit while writeBuffer is busy! + const elapsedTimeLimit = + get(runtime.connection.buffer).length > 0 + ? runtime.heartbeat_grid_ms * 6 + : runtime.heartbeat_grid_ms * 3; + const elapsedTime = Date.now() - last; + + if (!last || elapsedTime > elapsedTimeLimit) { + // TIMEOUT! let's remove the device + runtime.destroy_module(device.dx, device.dy); + } + } + } } diff --git a/src/renderer/runtime/virtual-engine.ts b/src/renderer/runtime/virtual-engine.ts index c69c88b97..0c1d0898b 100644 --- a/src/renderer/runtime/virtual-engine.ts +++ b/src/renderer/runtime/virtual-engine.ts @@ -1,4 +1,12 @@ -import { get, writable } from "svelte/store"; +import { + get, + Readable, + Subscriber, + Unsubscriber, + Updater, + Writable, + writable, +} from "svelte/store"; import { grid, ModuleType, ElementType } from "@intechstudio/grid-protocol"; import { InstructionClass, @@ -6,173 +14,7 @@ import { BufferElement, } from "../runtime/engine.store"; -function answerExecuteTypeRequest(obj: BufferElement) { - return new Promise((resolve, reject) => { - const class_name = obj.descr.class_name; - const [dx, dy, page, element, event]: number[] = [ - obj.descr.brc_parameters.DX, - obj.descr.brc_parameters.DY, - obj.descr.class_parameters.PAGENUMBER ?? -1, - obj.descr.class_parameters.ELEMENTNUMBER ?? -1, - obj.descr.class_parameters.EVENTTYPE ?? -1, - ]; - switch (class_name) { - case InstructionClassName.CONFIG: { - virtual_runtime.update((s) => { - const device = s.find((e) => e.dx == dx && e.dy == dy); - const events = device?.pages[page].elements.find( - (e) => e.elementIndex === element - ).events; - events.find((e: any) => e.value == event).config = - obj.descr.class_parameters.ACTIONSTRING; - return s; - }); - - resolve({ - brc_parameters: { - DX: -127, - DY: -127, - SX: dx, - SY: dy, - }, - class_name: InstructionClassName.CONFIG, - class_instr: InstructionClass.ACKNOWLEDGE, - class_parameters: { - ELEMENTNUMBER: element, - EVENTTYPE: event, - LASTHEADER: 0, - PAGENUMBER: page, - }, - }); - break; - } - case InstructionClassName.PAGESTORE: { - virtual_runtime.update((s) => { - s.forEach((device) => { - device.storeChanges(); - }); - return s; - }); - resolve({ - brc_parameters: { - DX: -127, - DY: -127, - SX: dx, - SY: dy, - }, - class_name: class_name, - class_instr: InstructionClass.ACKNOWLEDGE, - class_parameters: { - ELEMENTNUMBER: element, - EVENTTYPE: event, - LASTHEADER: 0, - PAGENUMBER: page, - }, - }); - break; - } - case InstructionClassName.PAGECLEAR: { - virtual_runtime.update((s) => { - s.forEach((device) => { - device.resetDefaultConfiguration(); - }); - return s; - }); - resolve({ - brc_parameters: { - DX: -127, - DY: -127, - SX: dx, - SY: dy, - }, - class_name: class_name, - class_instr: InstructionClass.ACKNOWLEDGE, - class_parameters: { - ELEMENTNUMBER: element, - EVENTTYPE: event, - LASTHEADER: 0, - PAGENUMBER: page, - }, - }); - break; - } - case InstructionClassName.PAGEDISCARD: { - virtual_runtime.update((s) => { - s.forEach((device) => { - device.discardChanges(); - }); - return s; - }); - resolve({ - brc_parameters: { - DX: -127, - DY: -127, - SX: dx, - SY: dy, - }, - class_name: class_name, - class_instr: InstructionClass.ACKNOWLEDGE, - class_parameters: { - ELEMENTNUMBER: element, - EVENTTYPE: event, - LASTHEADER: 0, - PAGENUMBER: page, - }, - }); - break; - } - default: { - reject("This operation is not available in virtual mode!"); - } - } - }); -} - -function answerFetchTypeRequests(obj: BufferElement) { - return new Promise((resolve, reject) => { - const class_name = obj.descr.class_name; - const [dx, dy, page, element, event]: number[] = [ - obj.descr.brc_parameters.DX, - obj.descr.brc_parameters.DY, - obj.descr.class_parameters.PAGENUMBER ?? -1, - obj.descr.class_parameters.ELEMENTNUMBER ?? -1, - obj.descr.class_parameters.EVENTTYPE ?? -1, - ]; - const device = get(virtual_runtime).find((e) => e.dx === dx && e.dy === dy); - switch (class_name) { - case InstructionClassName.CONFIG: { - const events = device?.pages[page].elements.find( - (e) => e.elementIndex === element - ).events; - const config = events.find((e: any) => e.value == event).config; - resolve({ - brc_parameters: { - DX: -127, - DY: -127, - SX: dx, - SY: dy, - }, - class_name: "CONFIG", - class_instr: "REPORT", - class_parameters: { - ACTIONLENGT: config.length, - ACTIONSTRING: config, - ELEMENTNUMBER: element, - EVENTTYPE: event, - LASTHEADER: 0, - PAGENUMBER: page, - }, - }); - break; - } - default: { - reject("This operation is not implemented ye in virtual mode!"); - } - } - }); -} - -class VirtualModule { +export class VirtualModule { public dx: number; public dy: number; public type: ModuleType; @@ -235,46 +77,224 @@ class VirtualModule { } } -function create_virtual_runtime() { - const store = writable([] as VirtualModule[]); +export class ConnectionSimulator implements Readable { + private _internal: Writable = writable([]); + + public subscribe( + run: Subscriber, + invalidate?: (value?: VirtualModule[]) => void + ): Unsubscriber { + return this._internal.subscribe(run, invalidate); + } + + private set(value: VirtualModule[]) { + this._internal.set(value); + } - function destroyModule(dx: number, dy: number) { - const vrt = get(store); + private update(updater: Updater) { + this._internal.update(updater); + } + + public destroyModule(dx: number, dy: number) { + const vrt = get(this._internal); const index = vrt.findIndex((e) => e.dx === dx && e.dy === dy); if (index === -1) { return; } - store.update((s) => { + this.update((s) => { s.splice(index, 1); return s; }); } - return { - ...store, - destroyModule: destroyModule, - }; -} + public createModule(dx: number, dy: number, type: ModuleType) { + const device = new VirtualModule(dx, dy, type); + this.update((s) => [...s, device]); + } + + public answerExecuteTypeRequest(obj: BufferElement) { + return new Promise((resolve, reject) => { + const class_name = obj.descr.class_name; + const [dx, dy, page, element, event]: number[] = [ + obj.descr.brc_parameters.DX, + obj.descr.brc_parameters.DY, + obj.descr.class_parameters.PAGENUMBER ?? -1, + obj.descr.class_parameters.ELEMENTNUMBER ?? -1, + obj.descr.class_parameters.EVENTTYPE ?? -1, + ]; + switch (class_name) { + case InstructionClassName.CONFIG: { + connection_simulator.update((s) => { + const device = s.find((e) => e.dx == dx && e.dy == dy); + const events = device?.pages[page].elements.find( + (e) => e.elementIndex === element + ).events; + events.find((e: any) => e.value == event).config = + obj.descr.class_parameters.ACTIONSTRING; + return s; + }); -export const virtual_runtime = create_virtual_runtime(); + resolve({ + brc_parameters: { + DX: -127, + DY: -127, + SX: dx, + SY: dy, + }, + class_name: InstructionClassName.CONFIG, + class_instr: InstructionClass.ACKNOWLEDGE, + class_parameters: { + ELEMENTNUMBER: element, + EVENTTYPE: event, + LASTHEADER: 0, + PAGENUMBER: page, + }, + }); + break; + } + case InstructionClassName.PAGESTORE: { + connection_simulator.update((s) => { + s.forEach((device) => { + device.storeChanges(); + }); + return s; + }); + resolve({ + brc_parameters: { + DX: -127, + DY: -127, + SX: dx, + SY: dy, + }, + class_name: class_name, + class_instr: InstructionClass.ACKNOWLEDGE, + class_parameters: { + ELEMENTNUMBER: element, + EVENTTYPE: event, + LASTHEADER: 0, + PAGENUMBER: page, + }, + }); + break; + } + case InstructionClassName.PAGECLEAR: { + connection_simulator.update((s) => { + s.forEach((device) => { + device.resetDefaultConfiguration(); + }); + return s; + }); + resolve({ + brc_parameters: { + DX: -127, + DY: -127, + SX: dx, + SY: dy, + }, + class_name: class_name, + class_instr: InstructionClass.ACKNOWLEDGE, + class_parameters: { + ELEMENTNUMBER: element, + EVENTTYPE: event, + LASTHEADER: 0, + PAGENUMBER: page, + }, + }); + break; + } + case InstructionClassName.PAGEDISCARD: { + connection_simulator.update((s) => { + s.forEach((device) => { + device.discardChanges(); + }); + return s; + }); + resolve({ + brc_parameters: { + DX: -127, + DY: -127, + SX: dx, + SY: dy, + }, + class_name: class_name, + class_instr: InstructionClass.ACKNOWLEDGE, + class_parameters: { + ELEMENTNUMBER: element, + EVENTTYPE: event, + LASTHEADER: 0, + PAGENUMBER: page, + }, + }); + break; + } + default: { + reject("This operation is not available in virtual mode!"); + } + } + }); + } -export function createVirtualModule(dx: number, dy: number, type: ModuleType) { - const device = new VirtualModule(dx, dy, type); - virtual_runtime.update((s) => [...s, device]); + public answerFetchTypeRequests(obj: BufferElement) { + return new Promise((resolve, reject) => { + const class_name = obj.descr.class_name; + const [dx, dy, page, element, event]: number[] = [ + obj.descr.brc_parameters.DX, + obj.descr.brc_parameters.DY, + obj.descr.class_parameters.PAGENUMBER ?? -1, + obj.descr.class_parameters.ELEMENTNUMBER ?? -1, + obj.descr.class_parameters.EVENTTYPE ?? -1, + ]; + const device = get(connection_simulator).find( + (e) => e.dx === dx && e.dy === dy + ); + switch (class_name) { + case InstructionClassName.CONFIG: { + const events = device?.pages[page].elements.find( + (e) => e.elementIndex === element + ).events; + const config = events.find((e: any) => e.value == event).config; + resolve({ + brc_parameters: { + DX: -127, + DY: -127, + SX: dx, + SY: dy, + }, + class_name: "CONFIG", + class_instr: "REPORT", + class_parameters: { + ACTIONLENGT: config.length, + ACTIONSTRING: config, + ELEMENTNUMBER: element, + EVENTTYPE: event, + LASTHEADER: 0, + PAGENUMBER: page, + }, + }); + break; + } + default: { + reject("This operation is not implemented ye in virtual mode!"); + } + } + }); + } } +export const connection_simulator = new ConnectionSimulator(); + export function simulateProcess(obj: BufferElement): Promise { const class_instr = obj.descr.class_instr; switch (class_instr) { case InstructionClass.FETCH: { - return answerFetchTypeRequests(obj); + return connection_simulator.answerFetchTypeRequests(obj); } case InstructionClass.ACKNOWLEDGE: { return Promise.reject(); } case InstructionClass.EXECUTE: { - return answerExecuteTypeRequest(obj); + return connection_simulator.answerExecuteTypeRequest(obj); } case InstructionClass.REPORT: { return Promise.reject(); diff --git a/src/renderer/serialport/instructions.ts b/src/renderer/serialport/instructions.ts index a8b08b0e2..4b5de82f1 100644 --- a/src/renderer/serialport/instructions.ts +++ b/src/renderer/serialport/instructions.ts @@ -4,386 +4,445 @@ import { appSettings } from "../runtime/app-helper.store.js"; import { grid } from "@intechstudio/grid-protocol"; import { - writeBuffer, BufferElement, InstructionClass, InstructionClassName, } from "../runtime/engine.store"; import { logger } from "../runtime/runtime.store"; import { v4 as uuidv4 } from "uuid"; +import { GridConnection } from "./serialport.js"; +import { GridRuntime } from "../runtime/runtime.js"; -export interface Instructions { - sendEditorHeartbeat_immediate(type: number): Promise; - fetchConfigFromGrid( - dx: number, - dy: number, - page: number, - element: number, - event: number - ): Promise; - sendConfigToGrid( - dx: number, - dy: number, - page: number, - element: number, - event: number, - config: string - ): Promise; - changeActivePage(page: number): Promise; - fetchPageCountFromGrid(params: { brc: any }): Promise; - sendPageStoreToGrid(): Promise; - sendNVMEraseToGrid(): Promise; - sendNVMDefragToGrid(): Promise; - sendPageDiscardToGrid(): Promise; - sendPageClearToGrid(): Promise; -} +export namespace GridInstruction { + abstract class AbstractInstruction { + public buffer_element: BufferElement; + public simulate: boolean; -export class GridInstructions implements Instructions { - sendEditorHeartbeat_immediate(type: number): Promise { - let buffer_element: BufferElement = { - id: uuidv4(), - descr: { - brc_parameters: { DX: -127, DY: -127 }, // GLOBAL - class_name: InstructionClassName.HEARTBEAT, - class_instr: InstructionClass.EXECUTE, - class_parameters: { - TYPE: type, - HWCFG: 255, - VMAJOR: get(appSettings).version.major, - VMINOR: get(appSettings).version.minor, - VPATCH: get(appSettings).version.patch, - }, - }, - responseRequired: false, - sendImmediate: true, - }; + constructor(simulate: boolean) { + this.simulate = simulate; + } - return writeBuffer.add_last(buffer_element); + public abstract executeOn(connection: GridConnection): Promise; } - fetchConfigFromGrid( - dx: number, - dy: number, - page: number, - element: number, - event: number - ): Promise { - //TODO: callback - let buffer_element: BufferElement = { - id: uuidv4(), - descr: { - brc_parameters: { - DX: dx, - DY: dy, - }, - class_name: InstructionClassName.CONFIG, - class_instr: InstructionClass.FETCH, - class_parameters: { - VERSIONMAJOR: grid.getProperty("VERSION").MAJOR, - VERSIONMINOR: grid.getProperty("VERSION").MINOR, - VERSIONPATCH: grid.getProperty("VERSION").PATCH, - PAGENUMBER: page, - ELEMENTNUMBER: element, - EVENTTYPE: event, - ACTIONLENGTH: 0, - }, - }, - responseRequired: true, - filter: { - brc_parameters: { - SX: dx, - SY: dy, - }, - class_instr: InstructionClass.REPORT, - class_name: InstructionClassName.CONFIG, - class_parameters: { - PAGENUMBER: page, - ELEMENTNUMBER: element, - EVENTTYPE: event, + export class SendHeartbeatImmediate extends AbstractInstruction { + constructor(type: number, virtual: boolean = false) { + super(virtual); + this.buffer_element = { + id: uuidv4(), + virtual: virtual, + descr: { + brc_parameters: { DX: -127, DY: -127 }, // GLOBAL + class_name: InstructionClassName.HEARTBEAT, + class_instr: InstructionClass.EXECUTE, + class_parameters: { + TYPE: type, + HWCFG: 255, + VMAJOR: get(appSettings).version.major, + VMINOR: get(appSettings).version.minor, + VPATCH: get(appSettings).version.patch, + }, }, - }, - }; + responseRequired: false, + sendImmediate: true, + }; + } - const promise = writeBuffer.add_last(buffer_element); - return promise; - } + public executeOn(connection: GridConnection): Promise { + // Only add heatbeat into the write buffer if it is not in it already + const buffer = get(connection.buffer); + const isHeartbeatPresent = buffer.some( + (e: any) => e.descr.class_name === "HEARTBEAT" + ); + + if (isHeartbeatPresent) { + return Promise.reject(); + } - sendConfigToGrid( - dx: number, - dy: number, - page: number, - element: number, - event: number, - config: string - ): Promise { - //TODO: Promise reject handling should do this - if (config.length >= grid.getProperty("CONFIG_LENGTH")) { - logger.set({ - type: "alert", - mode: 0, - classname: "configlength", - message: `Config is too long! ${config.length} characters`, - }); + return connection.buffer.add_last(this.buffer_element); } + } - let buffer_element: BufferElement = { - id: uuidv4(), - descr: { - brc_parameters: { - DX: dx, - DY: dy, - }, - class_name: InstructionClassName.CONFIG, - class_instr: InstructionClass.EXECUTE, - class_parameters: { - VERSIONMAJOR: grid.getProperty("VERSION").MAJOR, - VERSIONMINOR: grid.getProperty("VERSION").MINOR, - VERSIONPATCH: grid.getProperty("VERSION").PATCH, - PAGENUMBER: page, - ELEMENTNUMBER: element, - EVENTTYPE: event, - ACTIONLENGTH: config.length, - ACTIONSTRING: config, + export class FetchConfig extends AbstractInstruction { + constructor( + dx: number, + dy: number, + page: number, + element: number, + event: number, + virtual: boolean = false + ) { + super(virtual); + this.buffer_element = { + id: uuidv4(), + virtual: virtual, + descr: { + brc_parameters: { + DX: dx, + DY: dy, + }, + class_name: InstructionClassName.CONFIG, + class_instr: InstructionClass.FETCH, + class_parameters: { + VERSIONMAJOR: grid.getProperty("VERSION").MAJOR, + VERSIONMINOR: grid.getProperty("VERSION").MINOR, + VERSIONPATCH: grid.getProperty("VERSION").PATCH, + PAGENUMBER: page, + ELEMENTNUMBER: element, + EVENTTYPE: event, + ACTIONLENGTH: 0, + }, }, - }, - responseRequired: true, - filter: { - brc_parameters: { - SX: dx, - SY: dy, + responseRequired: true, + filter: { + brc_parameters: { + SX: dx, + SY: dy, + }, + class_instr: InstructionClass.REPORT, + class_name: InstructionClassName.CONFIG, + class_parameters: { + PAGENUMBER: page, + ELEMENTNUMBER: element, + EVENTTYPE: event, + }, }, - class_name: InstructionClassName.CONFIG, - class_instr: InstructionClass.ACKNOWLEDGE, - }, - }; - - const promise = writeBuffer.add_last(buffer_element); - return promise; - } - - sendImmediateToGrid(dx: number, dy: number, script: string): Promise { - //TODO: Promise reject handling should do this - if (script.length >= grid.getProperty("CONFIG_LENGTH")) { - logger.set({ - type: "alert", - mode: 0, - classname: "configlength", - message: `Script is too long! ${script.length} characters`, - }); + }; + } + public executeOn(connection: GridConnection): Promise { + return connection.buffer.add_last(this.buffer_element); } + } - let buffer_element: BufferElement = { - id: uuidv4(), - descr: { - brc_parameters: { - DX: dx, - DY: dy, + export class SendConfig extends AbstractInstruction { + constructor( + dx: number, + dy: number, + page: number, + element: number, + event: number, + config: string, + virtual: boolean = false + ) { + super(virtual); + this.buffer_element = { + id: uuidv4(), + virtual: virtual, + descr: { + brc_parameters: { + DX: dx, + DY: dy, + }, + class_name: InstructionClassName.CONFIG, + class_instr: InstructionClass.EXECUTE, + class_parameters: { + VERSIONMAJOR: grid.getProperty("VERSION").MAJOR, + VERSIONMINOR: grid.getProperty("VERSION").MINOR, + VERSIONPATCH: grid.getProperty("VERSION").PATCH, + PAGENUMBER: page, + ELEMENTNUMBER: element, + EVENTTYPE: event, + ACTIONLENGTH: config.length, + ACTIONSTRING: config, + }, }, - class_name: InstructionClassName.IMMEDIATE, - class_instr: InstructionClass.EXECUTE, - class_parameters: { - ACTIONLENGTH: script.length, - ACTIONSTRING: script, + responseRequired: true, + filter: { + brc_parameters: { + SX: dx, + SY: dy, + }, + class_name: InstructionClassName.CONFIG, + class_instr: InstructionClass.ACKNOWLEDGE, }, - }, - }; + }; + } + + public executeOn(connection: GridConnection): Promise { + const configLength = + this.buffer_element.descr.class_parameters.ACTIONLENGTH; + if (configLength >= grid.getProperty("CONFIG_LENGTH")) { + logger.set({ + type: "alert", + mode: 0, + classname: "configlength", + message: `Config is too long! ${configLength} characters`, + }); + return Promise.reject(); + } - const promise = writeBuffer.add_last(buffer_element); - return promise; + return connection.buffer.add_last(this.buffer_element); + } } - changeActivePage(page: number): Promise { - let buffer_element: BufferElement = { - id: uuidv4(), - descr: { - brc_parameters: { - DX: -127, - DY: -127, - }, - class_name: InstructionClassName.PAGEACTIVE, - class_instr: InstructionClass.EXECUTE, - class_parameters: { - PAGENUMBER: page, + export class SendConfigImmediate extends AbstractInstruction { + constructor( + dx: number, + dy: number, + script: string, + virtual: boolean = false + ) { + super(virtual); + this.buffer_element = { + id: uuidv4(), + virtual: virtual, + descr: { + brc_parameters: { + DX: dx, + DY: dy, + }, + class_name: InstructionClassName.IMMEDIATE, + class_instr: InstructionClass.EXECUTE, + class_parameters: { + ACTIONLENGTH: script.length, + ACTIONSTRING: script, + }, }, - }, - }; - const promise = writeBuffer.add_last(buffer_element); - return promise; + }; + } + + public executeOn(connection: GridConnection): Promise { + const configLength = + this.buffer_element.descr.class_parameters.ACTIONLENGTH; + if (configLength >= grid.getProperty("CONFIG_LENGTH")) { + //TODO: Reject handling logging + logger.set({ + type: "alert", + mode: 0, + classname: "configlength", + message: `Script is too long! ${configLength} characters`, + }); + return Promise.reject(); + } + + return connection.buffer.add_last(this.buffer_element); + } } - fetchPageCountFromGrid({ brc }): Promise { - let buffer_element = { - id: uuidv4(), - descr: { - brc_parameters: { - DX: brc.dx, - DY: brc.dy, - ROT: brc.rot, + export class ChangePage extends AbstractInstruction { + constructor(page: number, virtual: boolean = false) { + super(virtual); + this.buffer_element = { + id: uuidv4(), + virtual: virtual, + descr: { + brc_parameters: { + DX: -127, + DY: -127, + }, + class_name: InstructionClassName.PAGEACTIVE, + class_instr: InstructionClass.EXECUTE, + class_parameters: { + PAGENUMBER: page, + }, }, - class_name: InstructionClassName.PAGECOUNT, - class_instr: InstructionClass.FETCH, - class_parameters: {}, - }, - responseRequired: true, - filter: { - class_name: InstructionClassName.PAGECOUNT, - class_instr: InstructionClass.REPORT, - }, - }; - - const promise = writeBuffer.add_last(buffer_element); - return promise; + }; + } + public executeOn(connection: GridConnection): Promise { + return connection.buffer.add_last(this.buffer_element); + } } - sendPageStoreToGrid(): Promise { - let buffer_element: BufferElement = { - id: uuidv4(), - descr: { - brc_parameters: { - DX: -127, - DY: -127, + export class FetchPageCount extends AbstractInstruction { + constructor({ brc }, virtual: boolean = false) { + super(virtual); + this.buffer_element = { + id: uuidv4(), + virtual: virtual, + descr: { + brc_parameters: { + DX: brc.dx, + DY: brc.dy, + ROT: brc.rot, + }, + class_name: InstructionClassName.PAGECOUNT, + class_instr: InstructionClass.FETCH, + class_parameters: {}, }, - class_name: InstructionClassName.PAGESTORE, - class_instr: InstructionClass.EXECUTE, - class_parameters: {}, - }, - //responseTimeout: 8000, - responseRequired: true, - filter: { - class_name: InstructionClassName.PAGESTORE, - class_instr: InstructionClass.ACKNOWLEDGE, - class_parameters: { - LASTHEADER: null, + responseRequired: true, + filter: { + class_name: InstructionClassName.PAGECOUNT, + class_instr: InstructionClass.REPORT, }, - }, - }; - - const promise = writeBuffer.add_last(buffer_element); - return promise; + }; + } + public executeOn(connection: GridConnection): Promise { + return connection.buffer.add_last(this.buffer_element); + } } - sendNVMEraseToGrid(): Promise { - if (get(writeBuffer).length > 0) { - return Promise.reject({ - type: "fail", - mode: 0, - classname: "engine-disabled", - message: `Engine is disabled, erasing NVM memory failed!`, - }); + export class StorePage extends AbstractInstruction { + constructor(virtual: boolean = false) { + super(virtual); + this.buffer_element = { + id: uuidv4(), + virtual: virtual, + descr: { + brc_parameters: { + DX: -127, + DY: -127, + }, + class_name: InstructionClassName.PAGESTORE, + class_instr: InstructionClass.EXECUTE, + class_parameters: {}, + }, + //responseTimeout: 8000, + responseRequired: true, + filter: { + class_name: InstructionClassName.PAGESTORE, + class_instr: InstructionClass.ACKNOWLEDGE, + class_parameters: { + LASTHEADER: null, + }, + }, + }; } + public executeOn(connection: GridConnection): Promise { + return connection.buffer.add_last(this.buffer_element); + } + } - let buffer_element: BufferElement = { - id: uuidv4(), - responseTimeout: 8000, - descr: { - brc_parameters: { - DX: -127, - DY: -127, + export class NVMErase extends AbstractInstruction { + constructor(virtual: boolean = false) { + super(virtual); + this.buffer_element = { + id: uuidv4(), + virtual: virtual, + responseTimeout: 8000, + descr: { + brc_parameters: { + DX: -127, + DY: -127, + }, + class_name: InstructionClassName.NVMERASE, + class_instr: InstructionClass.EXECUTE, + class_parameters: {}, }, - class_name: InstructionClassName.NVMERASE, - class_instr: InstructionClass.EXECUTE, - class_parameters: {}, - }, - responseRequired: true, - filter: { - class_name: InstructionClassName.NVMERASE, - class_instr: InstructionClass.ACKNOWLEDGE, - class_parameters: { - LASTHEADER: null, + responseRequired: true, + filter: { + class_name: InstructionClassName.NVMERASE, + class_instr: InstructionClass.ACKNOWLEDGE, + class_parameters: { + LASTHEADER: null, + }, }, - }, - }; + }; + } - const promise = writeBuffer.add_last(buffer_element); - return promise; - } + public executeOn(connection: GridConnection): Promise { + if (get(connection.buffer).length > 0) { + //TODO: Reject handling logging + return Promise.reject({ + type: "fail", + mode: 0, + classname: "engine-disabled", + message: `Engine is disabled, erasing NVM memory failed!`, + }); + } - sendNVMDefragToGrid(): Promise { - if (get(writeBuffer).length > 0) { - return Promise.reject("NVM defrag failed"); + return connection.buffer.add_last(this.buffer_element); } + } - let buffer_element: BufferElement = { - id: uuidv4(), - descr: { - brc_parameters: { - DX: -127, - DY: -127, + export class NVMDefrag extends AbstractInstruction { + constructor(virtual: boolean = false) { + super(virtual); + this.buffer_element = { + id: uuidv4(), + virtual: virtual, + descr: { + brc_parameters: { + DX: -127, + DY: -127, + }, + class_name: InstructionClassName.NVMDEFRAG, + class_instr: InstructionClass.EXECUTE, + class_parameters: {}, }, - class_name: InstructionClassName.NVMDEFRAG, - class_instr: InstructionClass.EXECUTE, - class_parameters: {}, - }, - responseRequired: true, - filter: { - class_name: InstructionClassName.NVMDEFRAG, - class_instr: InstructionClass.ACKNOWLEDGE, - class_parameters: { - LASTHEADER: null, + responseRequired: true, + filter: { + class_name: InstructionClassName.NVMDEFRAG, + class_instr: InstructionClass.ACKNOWLEDGE, + class_parameters: { + LASTHEADER: null, + }, }, - }, - }; + }; + } - const promise = writeBuffer.add_last(buffer_element); - return promise; + public executeOn(connection: GridConnection): Promise { + if (get(connection.buffer).length > 0) { + return Promise.reject("NVM defrag failed"); + } + + return connection.buffer.add_last(this.buffer_element); + } } - sendPageDiscardToGrid(): Promise { - let buffer_element: BufferElement = { - id: uuidv4(), - responseTimeout: 3000, - descr: { - brc_parameters: { - DX: -127, - DY: -127, - }, - class_name: InstructionClassName.PAGEDISCARD, - class_instr: InstructionClass.EXECUTE, - class_parameters: {}, - }, - responseRequired: true, - filter: { - PAGEDISCARD_ACKNOWLEDGE: { - LASTHEADER: null, + export class DiscardPage extends AbstractInstruction { + constructor(virtual: boolean = false) { + super(virtual); + this.buffer_element = { + id: uuidv4(), + virtual: virtual, + responseTimeout: 3000, + descr: { + brc_parameters: { + DX: -127, + DY: -127, + }, + class_name: InstructionClassName.PAGEDISCARD, + class_instr: InstructionClass.EXECUTE, + class_parameters: {}, }, - class_name: InstructionClassName.PAGEDISCARD, - class_instr: InstructionClass.ACKNOWLEDGE, - class_parameters: { - LASTHEADER: null, + responseRequired: true, + filter: { + PAGEDISCARD_ACKNOWLEDGE: { + LASTHEADER: null, + }, + class_name: InstructionClassName.PAGEDISCARD, + class_instr: InstructionClass.ACKNOWLEDGE, + class_parameters: { + LASTHEADER: null, + }, }, - }, - }; + }; + } - const promise = writeBuffer.add_last(buffer_element); - return promise; + public executeOn(connection: GridConnection): Promise { + return connection.buffer.add_last(this.buffer_element); + } } - sendPageClearToGrid(): Promise { - let buffer_element: BufferElement = { - id: uuidv4(), - responseTimeout: 3000, - descr: { - brc_parameters: { - DX: -127, - DY: -127, + export class ClearPage extends AbstractInstruction { + constructor(virtual: boolean = false) { + super(virtual); + this.buffer_element = { + id: uuidv4(), + virtual: virtual, + responseTimeout: 3000, + descr: { + brc_parameters: { + DX: -127, + DY: -127, + }, + class_name: InstructionClassName.PAGECLEAR, + class_instr: InstructionClass.EXECUTE, + class_parameters: {}, }, - class_name: InstructionClassName.PAGECLEAR, - class_instr: InstructionClass.EXECUTE, - class_parameters: {}, - }, - responseRequired: true, - filter: { - class_name: InstructionClassName.PAGECLEAR, - class_instr: InstructionClass.ACKNOWLEDGE, - class_parameters: { - LASTHEADER: null, + responseRequired: true, + filter: { + class_name: InstructionClassName.PAGECLEAR, + class_instr: InstructionClass.ACKNOWLEDGE, + class_parameters: { + LASTHEADER: null, + }, }, - }, - }; + }; + } - const promise = writeBuffer.add_last(buffer_element); - return promise; + public executeOn(connection: GridConnection): Promise { + return connection.buffer.add_last(this.buffer_element); + } } } - -export const instructions = new GridInstructions(); diff --git a/src/renderer/serialport/message-stream.store.js b/src/renderer/serialport/message-stream.store.ts similarity index 92% rename from src/renderer/serialport/message-stream.store.js rename to src/renderer/serialport/message-stream.store.ts index 60477b0e6..4162316cf 100644 --- a/src/renderer/serialport/message-stream.store.js +++ b/src/renderer/serialport/message-stream.store.ts @@ -1,6 +1,5 @@ // Top level imports import { writable, get } from "svelte/store"; -import { writeBuffer } from "../runtime/engine.store"; import { appSettings } from "../runtime/app-helper.store.js"; import { runtime, @@ -10,7 +9,7 @@ import { update_elementPositionStore, update_elementPositionStore_fromPreview, update_ledColorStore, -} from "../runtime/runtime.store.ts"; +} from "../runtime/runtime.store"; import { debug_monitor_store, lua_error_store, @@ -22,6 +21,7 @@ import { import { logger } from "../runtime/runtime.store"; import { PolyLineGraphData } from "../main/user-interface/PolyLineGraph.js"; +import { WriteBuffer } from "../runtime/engine.store.js"; export const incoming_messages = writable([]); export function add_datapoint(key, value) { @@ -49,8 +49,13 @@ export function get_datapoint_last(key) { return store?.value; } -function createMessageStream() { - const _deliver_inbound = function (class_array) { +export class MessageStream { + private _buffer: WriteBuffer; + constructor(buffer: WriteBuffer) { + this._buffer = buffer; + } + + public deliver_inbound(class_array: any) { if (class_array === undefined) { return; } @@ -163,8 +168,11 @@ function createMessageStream() { // update control element rotation update_elementPositionStore(class_descr); - // update active element selection - user_input.process_incoming_event_from_grid(class_descr); + // engine is enabled + if (get(this._buffer).length === 0) { + // update active element selection + user_input.process_incoming_event_from_grid(class_descr); + } } if (class_descr.class_name === "EVENTPREVIEW") { @@ -211,13 +219,7 @@ function createMessageStream() { } } - writeBuffer.validate_incoming(class_descr); + this._buffer.validate_incoming(class_descr); }); - }; - - return { - deliver_inbound: _deliver_inbound, - }; + } } - -export const messageStream = createMessageStream(); diff --git a/src/renderer/serialport/serialport.ts b/src/renderer/serialport/serialport.ts index 158c2d9ac..0c1ce34dd 100644 --- a/src/renderer/serialport/serialport.ts +++ b/src/renderer/serialport/serialport.ts @@ -1,10 +1,19 @@ import { grid } from "@intechstudio/grid-protocol"; -import { messageStream } from "./message-stream.store.js"; - import { debug_lowlevel_store } from "../main/panels/DebugMonitor/DebugMonitor.store.js"; import { v4 as uuidv4 } from "uuid"; -import { get, type Writable, writable } from "svelte/store"; +import { + get, + Readable, + Unsubscriber, + Updater, + type Writable, + writable, +} from "svelte/store"; +import { Subscriber } from "svelte/motion"; +import { GridRuntime } from "../runtime/runtime.js"; +import { WriteBuffer } from "../runtime/engine.store.js"; +import { MessageStream } from "./message-stream.store.js"; const configuration = window.ctxProcess.configuration(); @@ -40,9 +49,7 @@ interface SerialPortInfo { usbProductId?: number; } -export interface GridPort extends WebSerialPort { - id: string; -} +export type GridPort = WebSerialPort; const filter: SerialPortInfo[] = [ { @@ -59,41 +66,60 @@ const filter: SerialPortInfo[] = [ }, ]; -class GridConnectionManager { - private _ports: Writable; - private _active: Writable; +export type GridConnection = { + id: string; + buffer: WriteBuffer; + port: GridPort; + messageStream: MessageStream; + virtual: boolean; +}; + +class GridConnectionManager implements Readable { + private _internal: Writable = writable([]); + + public subscribe( + run: Subscriber, + invalidate?: (value?: GridConnection[]) => void + ): Unsubscriber { + return this._internal.subscribe(run, invalidate); + } + + private set(value: GridConnection[]) { + this._internal.set(value); + } - constructor() { - this._ports = writable([]); - this._active = writable(); + private update(updater: Updater) { + this._internal.update(updater); } get ports() { - return this._ports; + return this._internal; } - openPort(port: any): Promise { + openPort(port: GridPort): Promise { return new Promise((resolve, reject) => { port .open({ baudRate: 2000000 }) .then(() => { - const current = port as GridPort; - current.id = uuidv4(); - current.addEventListener("disconnect", (e) => { + const buffer = new WriteBuffer(port); + const messageStream = new MessageStream(buffer); + const current = { + id: uuidv4(), + port: port, + buffer: buffer, + messageStream: messageStream, + virtual: false, + }; + + port.addEventListener("disconnect", (e) => { console.log("Port disconnected:", current); - - const ports = get(this._ports); - this._ports.set(ports.filter((e) => e.id !== current.id)); - if (get(this._ports).length > 0) { - if (get(this._active).id === current.id) { - this.fetchStream(get(this._ports)[0]); - } - } else { - this._active = undefined; - } + this.update((store) => { + return store.filter((e) => e.id !== current.id); + }); }); - this._ports.update((store) => { + this.update((store) => { + console.log("Port connected:", current); store.push(current); return store; }); @@ -106,14 +132,7 @@ class GridConnectionManager { }); } - disconnectPort() {} - - get active() { - return this._active; - } - - isSerialWriteLocked() { - const port = get(this.active); + isSerialWriteLocked(port: GridPort) { if (port === undefined || port === null) { return true; } @@ -127,13 +146,11 @@ class GridConnectionManager { } } - serialWrite(param) { + serialWrite(param: any, port: GridPort) { if (param === undefined) { return Promise.reject("Serial Write Error 1."); } - const port = get(this.active); - if (port === undefined || port === null) { return Promise.reject("Serial Write Error 2."); } @@ -171,17 +188,13 @@ class GridConnectionManager { }); } - async fetchStream(port: GridPort) { + async fetchStream(connection: GridConnection) { + const port = connection.port; if (!port || !port.readable) { console.warn("Invalid port: ", port); return; } - if (port.id === get(this.active)?.id) { - return; - } - - this._active.set(port); const reader = port.readable.getReader(); let charsReceived = 0; let rxBuffer = []; @@ -190,7 +203,12 @@ class GridConnectionManager { while (true) { const { done, value } = await reader.read(); - if (done || get(this.active) !== port) { + if ( + done || + !get(this._internal) + .map((e) => e.port) + .includes(port) + ) { console.log("Stream complete"); break; } @@ -223,7 +241,7 @@ class GridConnectionManager { grid.decode_packet_classes(class_array); if (class_array !== false) { - messageStream.deliver_inbound(class_array); + connection.messageStream.deliver_inbound(class_array); } } } @@ -236,53 +254,55 @@ class GridConnectionManager { reader.releaseLock(); } } -} -export const connection_manager = new GridConnectionManager(); + static async tryConnectGrid() { + return; + try { + let ports: any[]; + if (import.meta.env.VITE_WEB_MODE == "true") { + const port = await navigator.serial.requestPort({ filters: filter }); + ports = [port]; // Add the newly requested port to the list + } else { + // Retrieve all available ports. Must requestPort before getPort to allow MACOS to open D51 + const port = await navigator.serial.requestPort({ filters: filter }); + if (navigator.debugSerial) { + console.warn("port:", port); + } + ports = await navigator.serial.getPorts(); + } -navigator.tryConnectGrid = async () => { - try { - let ports: any[]; - if (import.meta.env.VITE_WEB_MODE == "true") { - const port = await navigator.serial.requestPort({ filters: filter }); - ports = [port]; // Add the newly requested port to the list - } else { - // Retrieve all available ports. Must requestPort before getPort to allow MACOS to open D51 - const port = await navigator.serial.requestPort({ filters: filter }); + // Filter ports based on the provided filter criteria + const matchingPorts = ports.filter((port) => { + const { usbVendorId, usbProductId } = port.getInfo(); + return filter.some( + (f) => + f.usbVendorId === usbVendorId && f.usbProductId === usbProductId + ); + }); + + // Attempt to open each matching port + for (const port of matchingPorts) { + connection_manager + .openPort(port as GridPort) + .then((connection) => { + connection_manager.fetchStream(connection); + }) + .catch((openError) => { + // Handle any errors that occur when opening the port + if (navigator.debugSerial) { + console.warn("Failed to open port:", openError); + } + }); + } + } catch (listPortsError) { + // Handle any errors that occur when listing the ports if (navigator.debugSerial) { - console.warn("port:", port); + console.warn("Failed to list ports:", listPortsError); } - ports = await navigator.serial.getPorts(); } + } +} - // Filter ports based on the provided filter criteria - const matchingPorts = ports.filter((port) => { - const { usbVendorId, usbProductId } = port.getInfo(); - return filter.some( - (f) => f.usbVendorId === usbVendorId && f.usbProductId === usbProductId - ); - }); +export const connection_manager = new GridConnectionManager(); - // Attempt to open each matching port - for (const port of matchingPorts) { - connection_manager - .openPort(port) - .then((port) => { - if (typeof connection_manager.active === "undefined") { - connection_manager.fetchStream(port); - } - }) - .catch((openError) => { - // Handle any errors that occur when opening the port - if (navigator.debugSerial) { - console.warn("Failed to open port:", openError); - } - }); - } - } catch (listPortsError) { - // Handle any errors that occur when listing the ports - if (navigator.debugSerial) { - console.warn("Failed to list ports:", listPortsError); - } - } -}; +navigator.tryConnectGrid = GridConnectionManager.tryConnectGrid; From a8ba18b2c359c711afb3268f58d742b8028dbe56 Mon Sep 17 00:00:00 2001 From: unknown Date: Wed, 18 Dec 2024 23:49:33 +0100 Subject: [PATCH 4/8] Daily push2 --- src/renderer/runtime/engine.store.ts | 13 ++-- src/renderer/runtime/runtime.store.ts | 70 +++++++++++++------ src/renderer/runtime/runtime.ts | 25 +++++-- .../serialport/message-stream.store.ts | 14 +++- src/renderer/serialport/serialport.ts | 23 +++--- 5 files changed, 100 insertions(+), 45 deletions(-) diff --git a/src/renderer/runtime/engine.store.ts b/src/renderer/runtime/engine.store.ts index bc696b79d..f66b5d8d7 100644 --- a/src/renderer/runtime/engine.store.ts +++ b/src/renderer/runtime/engine.store.ts @@ -4,6 +4,7 @@ import { connection_manager, GridPort } from "../serialport/serialport"; import { appSettings } from "./app-helper.store"; import { Subscriber } from "svelte/motion"; import { simulateProcess } from "./virtual-engine"; +import { MessageStream } from "../serialport/message-stream.store"; export enum InstructionClassName { HEARTBEAT = "HEARTBEAT", @@ -155,8 +156,11 @@ export class WriteBuffer implements Readable { private _internal = writable([]); private _port: GridPort; + public readonly messageStream: MessageStream; + constructor(port: GridPort) { this._port = port; + this.messageStream = new MessageStream(this); } public subscribe( @@ -361,15 +365,14 @@ export class WriteBuffer implements Readable { public async execute(obj: BufferElement) { return new Promise((resolve, reject) => { - let process: (obj: BufferElement) => Promise; - console.log(obj, obj.virtual); + let promise: Promise; if (obj.virtual) { - process = simulateProcess; + promise = simulateProcess(obj); } else { - process = this.processElement; + promise = this.processElement(obj); } - process(obj) + promise .then((res) => { resolve(res); }) diff --git a/src/renderer/runtime/runtime.store.ts b/src/renderer/runtime/runtime.store.ts index 9935378f7..1906acfc9 100644 --- a/src/renderer/runtime/runtime.store.ts +++ b/src/renderer/runtime/runtime.store.ts @@ -44,12 +44,22 @@ class GridRuntimeManager implements Readable { } public allocate() { - const runtime = new GridRuntime(); + const incoming = new GridRuntime(); this.update((store) => { - store.data.push(runtime); + for (const runtime of store.data) { + if (runtime.virtual) { + console.log("Killed (it's good):", runtime); + runtime.killHeartbeat(); + store.data = store.data.filter((e) => e.id !== runtime.id); + } + } + + console.log("Incoming runtime:", incoming); + store.data.push(incoming); if (typeof store.active === "undefined") { - store.active = runtime; + console.log("Active runtime:", incoming); + store.active = incoming; } return store; }); @@ -57,18 +67,49 @@ class GridRuntimeManager implements Readable { return runtime; } + public allocateVirtual(): GridRuntime { + const buffer = new WriteBuffer(undefined); + const virtual_connection: GridConnection = { + id: undefined, + buffer: buffer, + port: undefined, + virtual: true, + }; + + const incoming = new GridRuntime(virtual_connection, true); + + this.update((store) => { + store.data.push(incoming); + + if (typeof store.active === "undefined") { + store.active = incoming; + } + return store; + }); + + return incoming; + } + public free(runtime: GridRuntime) { this.update((store) => { const destroyed = store.data.find((e) => e.id === runtime.id); if (!destroyed) { - throw new Error(""); + throw new Error("Ca"); } - store.data = store.data.filter((e) => e.id !== runtime.id); + console.log("Killed (it's good):", runtime); + destroyed.killHeartbeat(); - if (store.active.id === runtime.id) { + store.data = store.data.filter((e) => e.id !== destroyed.id); + + if (store.active.id === destroyed.id) { store.active = store.data[0]; } + + if (typeof store.active === "undefined") { + store.active = this.allocateVirtual(); + } + return store; }); } @@ -159,26 +200,11 @@ class GridRuntimeManager implements Readable { console.warn(e); }); } - - public createVirtualRuntime(): GridRuntime { - const buffer = new WriteBuffer(undefined); - const port = undefined; - const messageStream = new MessageStream(buffer); - const virtual_connection: GridConnection = { - id: undefined, - buffer: buffer, - port: port, - messageStream: messageStream, - virtual: true, - }; - - return new GridRuntime(virtual_connection, true); - } } export const runtime_manager = new GridRuntimeManager(); -export let runtime: GridRuntime = runtime_manager.createVirtualRuntime(); +export let runtime: GridRuntime = runtime_manager.allocateVirtual(); const setIntervalAsync = (fn, ms) => { fn().then(() => { diff --git a/src/renderer/runtime/runtime.ts b/src/renderer/runtime/runtime.ts index bd4d82ea1..3e4f9c5f8 100644 --- a/src/renderer/runtime/runtime.ts +++ b/src/renderer/runtime/runtime.ts @@ -19,7 +19,10 @@ import { GridInstruction } from "../serialport/instructions"; import { connection_simulator } from "./virtual-engine"; import { Analytics } from "./analytics.js"; import { appSettings } from "./app-helper.store"; -import { add_datapoint } from "../serialport/message-stream.store.js"; +import { + add_datapoint, + MessageStream, +} from "../serialport/message-stream.store.js"; import { elementPositionStore, ledColorStore, @@ -34,7 +37,12 @@ import { ActionBlockInformation } from "../config-blocks/ActionBlockInformation" import { Runtime } from "./string-table"; import { Grid } from "../lib/_utils"; import { GridConnection } from "../serialport/serialport"; -import { setIntervalAsync } from "set-interval-async"; +import { + clearIntervalAsync, + setIntervalAsync, + SetIntervalAsyncTimer, +} from "set-interval-async"; +import { WriteBuffer } from "./engine.store"; type UUID = string; type LuaScript = string; @@ -1409,10 +1417,14 @@ export interface RuntimeData extends NodeData { export class GridRuntime extends RuntimeNode { public connection: GridConnection; + public readonly heartbeat_editor_ms = 300; public readonly heartbeat_grid_ms = 250; public readonly virtual: boolean; + private grid_heartbeat: SetIntervalAsyncTimer<[]>; + private editor_heartbeat: SetIntervalAsyncTimer<[]>; + constructor( connection: GridConnection = undefined, virtual: boolean = false @@ -1421,18 +1433,19 @@ export class GridRuntime extends RuntimeNode { this.connection = connection; this.virtual = virtual; - setIntervalAsync( + this.editor_heartbeat = setIntervalAsync( () => GridRuntime.editor_heartbeat_interval_handler(this), this.heartbeat_editor_ms ); - setIntervalAsync( + this.grid_heartbeat = setIntervalAsync( () => GridRuntime.grid_heartbeat_interval_handler(this), this.heartbeat_grid_ms ); } - public bind(connection: GridConnection) { - this.connection = connection; + public killHeartbeat() { + clearIntervalAsync(this.grid_heartbeat); + clearIntervalAsync(this.editor_heartbeat); } get modules() { diff --git a/src/renderer/serialport/message-stream.store.ts b/src/renderer/serialport/message-stream.store.ts index 4162316cf..69b3a97ce 100644 --- a/src/renderer/serialport/message-stream.store.ts +++ b/src/renderer/serialport/message-stream.store.ts @@ -2,7 +2,6 @@ import { writable, get } from "svelte/store"; import { appSettings } from "../runtime/app-helper.store.js"; import { - runtime, user_input, wss_send_message, update_element_name, @@ -22,6 +21,7 @@ import { logger } from "../runtime/runtime.store"; import { PolyLineGraphData } from "../main/user-interface/PolyLineGraph.js"; import { WriteBuffer } from "../runtime/engine.store.js"; +import { GridRuntime } from "../runtime/runtime.js"; export const incoming_messages = writable([]); export function add_datapoint(key, value) { @@ -51,15 +51,25 @@ export function get_datapoint_last(key) { export class MessageStream { private _buffer: WriteBuffer; + public runtime: GridRuntime; + constructor(buffer: WriteBuffer) { this._buffer = buffer; } + public bind(runtime: GridRuntime) { + this.runtime = runtime; + } + public deliver_inbound(class_array: any) { if (class_array === undefined) { return; } + if (!this.runtime) { + return; + } + class_array.forEach((class_descr, i) => { if (i == 0 && get(appSettings).persistent.messageIdDebugEnabled) { const brc_parameters = class_array[0].brc_parameters; @@ -85,7 +95,7 @@ export class MessageStream { if (class_descr.class_name === "HEARTBEAT") { // check if it is online and if not then create a new module - runtime.incoming_heartbeat_handler(class_descr); + this.runtime.incoming_heartbeat_handler(class_descr); } if (class_descr.class_name === "PAGECOUNT") { diff --git a/src/renderer/serialport/serialport.ts b/src/renderer/serialport/serialport.ts index 0c1ce34dd..50c35be04 100644 --- a/src/renderer/serialport/serialport.ts +++ b/src/renderer/serialport/serialport.ts @@ -14,6 +14,7 @@ import { Subscriber } from "svelte/motion"; import { GridRuntime } from "../runtime/runtime.js"; import { WriteBuffer } from "../runtime/engine.store.js"; import { MessageStream } from "./message-stream.store.js"; +import { runtime_manager } from "../runtime/runtime.store.js"; const configuration = window.ctxProcess.configuration(); @@ -70,7 +71,6 @@ export type GridConnection = { id: string; buffer: WriteBuffer; port: GridPort; - messageStream: MessageStream; virtual: boolean; }; @@ -102,26 +102,30 @@ class GridConnectionManager implements Readable { .open({ baudRate: 2000000 }) .then(() => { const buffer = new WriteBuffer(port); - const messageStream = new MessageStream(buffer); const current = { id: uuidv4(), port: port, buffer: buffer, - messageStream: messageStream, virtual: false, }; + const runtime = runtime_manager.allocate(); + buffer.messageStream.bind(runtime); + runtime.connection = current; + + this.update((store) => { + console.log("Port connected:", current); + store.push(current); + return store; + }); + port.addEventListener("disconnect", (e) => { console.log("Port disconnected:", current); this.update((store) => { return store.filter((e) => e.id !== current.id); }); - }); - this.update((store) => { - console.log("Port connected:", current); - store.push(current); - return store; + runtime_manager.free(runtime); }); resolve(current); @@ -241,7 +245,7 @@ class GridConnectionManager implements Readable { grid.decode_packet_classes(class_array); if (class_array !== false) { - connection.messageStream.deliver_inbound(class_array); + connection.buffer.messageStream.deliver_inbound(class_array); } } } @@ -256,7 +260,6 @@ class GridConnectionManager implements Readable { } static async tryConnectGrid() { - return; try { let ports: any[]; if (import.meta.env.VITE_WEB_MODE == "true") { From 1e9f93ceb242a167fed67f70bd243f3cf8270ebe Mon Sep 17 00:00:00 2001 From: unknown Date: Thu, 19 Dec 2024 17:44:57 +0100 Subject: [PATCH 5/8] Daily push --- src/electron/ipcmain_serialport.ts | 7 - src/renderer/App.svelte | 3 +- src/renderer/main/MiddlePanelContainer.svelte | 3 +- .../main/grid-layout/GridLayout.svelte | 12 +- .../panels/DebugMonitor/DebugMonitor.svelte | 2 +- .../panels/configuration/Configuration.svelte | 5 +- .../panels/preferences/Preferences.svelte | 2 +- .../main/user-interface/ActiveChanges.svelte | 4 +- .../main/user-interface/PortSelector.svelte | 67 ++-- src/renderer/runtime/runtime.manager.store.ts | 286 ++++++++++++++++++ src/renderer/runtime/runtime.store.ts | 235 ++------------ src/renderer/runtime/runtime.ts | 73 ----- src/renderer/serialport/serialport.ts | 16 +- 13 files changed, 368 insertions(+), 347 deletions(-) create mode 100644 src/renderer/runtime/runtime.manager.store.ts diff --git a/src/electron/ipcmain_serialport.ts b/src/electron/ipcmain_serialport.ts index 800ebe8ff..6519a6404 100644 --- a/src/electron/ipcmain_serialport.ts +++ b/src/electron/ipcmain_serialport.ts @@ -8,13 +8,6 @@ const RECONNECT_INTERVAL = 1000; let port_disovery_interval; -/* const setIntervalAsync = (fn, ms) => { - fn().then(() => { - port_disovery_interval = setTimeout(() => setIntervalAsync(fn, ms), ms); - }); -}; - */ - // Basic serial usage async function attemptSerialConnection() { if (serial.mainWindow !== undefined) { diff --git a/src/renderer/App.svelte b/src/renderer/App.svelte index 915c8073b..074b3df76 100644 --- a/src/renderer/App.svelte +++ b/src/renderer/App.svelte @@ -29,7 +29,7 @@ import { watchResize } from "svelte-watch-resize"; import { debug_lowlevel_store } from "./main/panels/WebsocketMonitor/WebsocketMonitor.store"; - import { runtime, logger, runtime_manager } from "./runtime/runtime.store"; + import { runtime, logger } from "./runtime/runtime.store"; import MiddlePanelContainer from "./main/MiddlePanelContainer.svelte"; import { addPackageAction, removePackageAction } from "./lib/_configs"; @@ -41,6 +41,7 @@ import VersionUpdateBar from "./main/VersionUpdateBar.svelte"; import "redefine-custom-elements"; + import { runtime_manager } from "./runtime/runtime.manager.store"; console.log("Hello from Svelte main.js"); diff --git a/src/renderer/main/MiddlePanelContainer.svelte b/src/renderer/main/MiddlePanelContainer.svelte index df35c12a1..2dbebe8c4 100644 --- a/src/renderer/main/MiddlePanelContainer.svelte +++ b/src/renderer/main/MiddlePanelContainer.svelte @@ -12,6 +12,7 @@ import StickyContainer from "./user-interface/StickyContainer.svelte"; import { onDestroy, onMount } from "svelte"; import ControlSurface from "./panels/configuration/components/ControlSurface.svelte"; + import { runtime_manager } from "../runtime/runtime.manager.store"; let logLength = 0; let trackerVisible = true; @@ -123,7 +124,7 @@
- {#if $runtime.modules.length == 0 && $appSettings.firmwareNotificationState === 0} + {#if $runtime_manager.active.runtime.modules.length == 0 && $appSettings.firmwareNotificationState === 0}
+ -
- {#if disabled} - - {:else} - - {/if} -
+{#key options} +
+ {#if disabled} + + {:else} + + {/if} +
+{/key} diff --git a/src/renderer/runtime/runtime.manager.store.ts b/src/renderer/runtime/runtime.manager.store.ts new file mode 100644 index 000000000..166cf80aa --- /dev/null +++ b/src/renderer/runtime/runtime.manager.store.ts @@ -0,0 +1,286 @@ +import { + writable, + get, + type Writable, + Subscriber, + Unsubscriber, + Updater, + Readable, +} from "svelte/store"; +import { GridRuntime, aliveModules } from "./runtime"; +import { GridInstruction } from "../serialport/instructions"; +import { GridConnection } from "../serialport/serialport"; +import { WriteBuffer } from "./engine.store"; +import { Architecture } from "@intechstudio/grid-protocol"; +import { logger } from "./runtime.store"; +import { + clearIntervalAsync, + setIntervalAsync, + SetIntervalAsyncTimer, +} from "set-interval-async"; + +type ManagedConnection = { + runtime: GridRuntime; + grid_heartbeat: SetIntervalAsyncTimer<[]>; + editor_heartbeat: SetIntervalAsyncTimer<[]>; +}; + +export type GridRuntimeManagerData = { + data: ManagedConnection[]; + active: ManagedConnection; +}; + +export class GridRuntimeManager implements Readable { + private _internal: Writable; + + public static readonly heartbeat_editor_ms = 300; + public static readonly heartbeat_grid_ms = 250; + + constructor() { + this._internal = writable({ data: [], active: undefined }); + const virtual = this.createVirtual(); + this.add(virtual); + } + + get data(): ManagedConnection[] { + return get(this._internal).data; + } + + get active(): ManagedConnection { + return get(this._internal).active; + } + + set active(value: ManagedConnection) { + this.update((store) => { + store.active = value; + return store; + }); + } + + public subscribe( + run: Subscriber, + invalidate?: (value?: GridRuntimeManagerData) => void + ): Unsubscriber { + return this._internal.subscribe(run, invalidate); + } + + private set(value: GridRuntimeManagerData) { + this._internal.set(value); + } + + private update(updater: Updater) { + this._internal.update(updater); + } + + public add(runtime: GridRuntime) { + const incoming: ManagedConnection = { + runtime: runtime, + grid_heartbeat: undefined, + editor_heartbeat: undefined, + }; + + this.update((store) => { + store.data.push(incoming); + if (typeof store.active === "undefined") { + store.active = incoming; + } + return store; + }); + + this.startHeartbeat(incoming); + } + + public destroy(runtime: GridRuntime) { + const destroyed = get(this._internal).data.find( + (e) => e.runtime.id === runtime.id + ); + + console.log(destroyed); + + if (!destroyed) { + throw new Error("Destroyed module is not found..."); + } + + this.killHeartbeat(destroyed); + + this.update((store) => { + store.data = store.data.filter( + (e) => e.runtime.id !== destroyed.runtime.id + ); + + if (store.active.runtime.id === destroyed.runtime.id) { + store.active = store.data[0]; + } + + return store; + }); + } + + public createVirtual(): GridRuntime { + const buffer = new WriteBuffer(undefined); + const virtual_connection: GridConnection = { + id: undefined, + buffer: buffer, + port: undefined, + virtual: true, + }; + + return new GridRuntime(virtual_connection, true); + } + + public NVMErase() { + logger.set({ + type: "progress", + mode: 0, + classname: "nvmerase", + message: `Erasing all modules...`, + }); + const promises: Promise[] = []; + for (const target of get(this._internal).data) { + const instruction = new GridInstruction.NVMErase(target.runtime.virtual); + promises.push(instruction.executeOn(target.runtime.connection)); + } + Promise.all(promises) + .then((res) => { + //TODO + logger.set({ + type: "success", + mode: 0, + classname: "nvmerase", + message: `Erase complete!`, + }); + }) + .catch((e) => { + if (typeof e !== "undefined") { + logger.set(e); + } else { + logger.set({ + type: "alert", + mode: 0, + classname: "nvmerase", + message: `Retry erase all modules...`, + }); + } + }); + } + + public NVMDefrag() { + logger.set({ + type: "progress", + mode: 0, + classname: "nvmdefrag", + message: `Defragging all modules...`, + }); + + const promises: Promise[] = []; + for (const target of get(this._internal).data) { + const instruction = new GridInstruction.NVMDefrag(target.runtime.virtual); + promises.push(instruction.executeOn(target.runtime.connection)); + } + Promise.all(promises) + .then((res) => { + //TODO + logger.set({ + type: "success", + mode: 0, + classname: "nvmdefrag", + message: `Defrag complete!`, + }); + }) + .catch((e) => { + logger.set({ + type: "fail", + mode: 0, + classname: "engine-disabled", + message: `Engine is disabled, NVM Defragmentation failed!`, + }); + }); + } + + public LUAExecImmediate(dx: number, dy: number, script: string) { + const target = get(this._internal).active; + if (!target) { + //ERROR HANDLING + return; + } + + const instruction = new GridInstruction.SendConfigImmediate( + dx, + dy, + script, + target.runtime.virtual + ); + instruction.executeOn(target.runtime.connection).catch((e) => { + console.warn(e); + }); + } + + private startHeartbeat(connection: ManagedConnection) { + connection.editor_heartbeat = setIntervalAsync( + () => + GridRuntimeManager.editor_heartbeat_interval_handler( + connection.runtime + ), + GridRuntimeManager.heartbeat_editor_ms + ); + connection.grid_heartbeat = setIntervalAsync( + () => + GridRuntimeManager.grid_heartbeat_interval_handler(connection.runtime), + GridRuntimeManager.heartbeat_grid_ms + ); + } + + public killHeartbeat(connection: ManagedConnection) { + clearIntervalAsync(connection.grid_heartbeat); + clearIntervalAsync(connection.editor_heartbeat); + } + + private static async editor_heartbeat_interval_handler(runtime: GridRuntime) { + let type = 255; + + // if (runtime.unsavedChangesCount() != 0 || typeof get(modal) !== "undefined") { + // type = 254; + // } + + if ( + runtime.modules.length > 0 && + runtime.modules.filter((e) => e.architecture === "virtual").length === 0 + ) { + const instruction = new GridInstruction.SendHeartbeatImmediate( + type, + runtime.virtual + ); + + instruction.executeOn(runtime.connection).catch((e) => { + console.log("EDITOR: Heartbeat skipped..."); + }); + } else { + //writeBuffer.clear(); + } + } + + private static async grid_heartbeat_interval_handler(runtime: GridRuntime) { + for (const device of runtime.modules) { + if (device.architecture === Architecture.VIRTUAL) { + return; + } + + const last = + get(aliveModules).find((e) => e.id === device.id)?.last ?? Date.now(); + + // Allow less strict elapsedTimeLimit while writeBuffer is busy! + const elapsedTimeLimit = + get(runtime.connection.buffer).length > 0 + ? GridRuntimeManager.heartbeat_grid_ms * 6 + : GridRuntimeManager.heartbeat_grid_ms * 3; + const elapsedTime = Date.now() - last; + + if (!last || elapsedTime > elapsedTimeLimit) { + // TIMEOUT! let's remove the device + runtime.destroy_module(device.dx, device.dy); + } + } + } +} + +export const runtime_manager = new GridRuntimeManager(); diff --git a/src/renderer/runtime/runtime.store.ts b/src/renderer/runtime/runtime.store.ts index 1906acfc9..bb3eb1092 100644 --- a/src/renderer/runtime/runtime.store.ts +++ b/src/renderer/runtime/runtime.store.ts @@ -1,216 +1,11 @@ -import { - writable, - get, - type Writable, - derived, - Subscriber, - Unsubscriber, - Updater, - Readable, -} from "svelte/store"; +import { writable, get, type Writable } from "svelte/store"; import { appSettings } from "./app-helper.store"; import { modal, Snap } from "../main/modals/modal.store"; import { ProtectedStore } from "./smart-store.store"; -import { GridAction, GridRuntime, aliveModules } from "./runtime"; -import { GridInstruction } from "../serialport/instructions"; -import { GridConnection } from "../serialport/serialport"; -import { WriteBuffer } from "./engine.store"; -import { MessageStream } from "../serialport/message-stream.store"; - -type GridRuntimeManagerData = { - data: GridRuntime[]; - active: GridRuntime; -}; - -class GridRuntimeManager implements Readable { - private _internal: Writable = writable({ - data: [], - active: undefined, - }); - - public subscribe( - run: Subscriber, - invalidate?: (value?: GridRuntimeManagerData) => void - ): Unsubscriber { - return this._internal.subscribe(run, invalidate); - } - - private set(value: GridRuntimeManagerData) { - this._internal.set(value); - } - - private update(updater: Updater) { - this._internal.update(updater); - } - - public allocate() { - const incoming = new GridRuntime(); - this.update((store) => { - for (const runtime of store.data) { - if (runtime.virtual) { - console.log("Killed (it's good):", runtime); - runtime.killHeartbeat(); - store.data = store.data.filter((e) => e.id !== runtime.id); - } - } - - console.log("Incoming runtime:", incoming); - store.data.push(incoming); - - if (typeof store.active === "undefined") { - console.log("Active runtime:", incoming); - store.active = incoming; - } - return store; - }); - - return runtime; - } - - public allocateVirtual(): GridRuntime { - const buffer = new WriteBuffer(undefined); - const virtual_connection: GridConnection = { - id: undefined, - buffer: buffer, - port: undefined, - virtual: true, - }; - - const incoming = new GridRuntime(virtual_connection, true); +import { GridAction, GridRuntime } from "./runtime"; +import { runtime_manager } from "./runtime.manager.store"; - this.update((store) => { - store.data.push(incoming); - - if (typeof store.active === "undefined") { - store.active = incoming; - } - return store; - }); - - return incoming; - } - - public free(runtime: GridRuntime) { - this.update((store) => { - const destroyed = store.data.find((e) => e.id === runtime.id); - if (!destroyed) { - throw new Error("Ca"); - } - - console.log("Killed (it's good):", runtime); - destroyed.killHeartbeat(); - - store.data = store.data.filter((e) => e.id !== destroyed.id); - - if (store.active.id === destroyed.id) { - store.active = store.data[0]; - } - - if (typeof store.active === "undefined") { - store.active = this.allocateVirtual(); - } - - return store; - }); - } - - public NVMErase() { - logger.set({ - type: "progress", - mode: 0, - classname: "nvmerase", - message: `Erasing all modules...`, - }); - const promises: Promise[] = []; - for (const target of get(this._internal).data) { - const instruction = new GridInstruction.NVMErase(target.virtual); - promises.push(instruction.executeOn(target.connection)); - } - Promise.all(promises) - .then((res) => { - //TODO - logger.set({ - type: "success", - mode: 0, - classname: "nvmerase", - message: `Erase complete!`, - }); - }) - .catch((e) => { - if (typeof e !== "undefined") { - logger.set(e); - } else { - logger.set({ - type: "alert", - mode: 0, - classname: "nvmerase", - message: `Retry erase all modules...`, - }); - } - }); - } - - public NVMDefrag() { - logger.set({ - type: "progress", - mode: 0, - classname: "nvmdefrag", - message: `Defragging all modules...`, - }); - - const promises: Promise[] = []; - for (const target of get(this._internal).data) { - const instruction = new GridInstruction.NVMDefrag(target.virtual); - promises.push(instruction.executeOn(target.connection)); - } - Promise.all(promises) - .then((res) => { - //TODO - logger.set({ - type: "success", - mode: 0, - classname: "nvmdefrag", - message: `Defrag complete!`, - }); - }) - .catch((e) => { - logger.set({ - type: "fail", - mode: 0, - classname: "engine-disabled", - message: `Engine is disabled, NVM Defragmentation failed!`, - }); - }); - } - - public LUAExecImmediate(dx: number, dy: number, script: string) { - const target = get(this._internal).active; - if (!target) { - //ERROR HANDLING - return; - } - - const instruction = new GridInstruction.SendConfigImmediate( - dx, - dy, - script, - target.virtual - ); - instruction.executeOn(target.connection).catch((e) => { - console.warn(e); - }); - } -} - -export const runtime_manager = new GridRuntimeManager(); - -export let runtime: GridRuntime = runtime_manager.allocateVirtual(); - -const setIntervalAsync = (fn, ms) => { - fn().then(() => { - setTimeout(() => setIntervalAsync(fn, ms), ms); - }); -}; +export let runtime: GridRuntime; // The controller which is added to runtime first, load a default config! @@ -356,15 +151,15 @@ export function update_ledColorStore(descr) { export const logger = writable(); function create_user_input() { - const defaultValues: UserInputValue = { - dx: undefined, - dy: undefined, - pagenumber: undefined, - elementnumber: undefined, - eventtype: undefined, + const ui_default: UserInputValue = { + dx: 0, + dy: 0, + pagenumber: 0, + elementnumber: 0, + eventtype: 2, }; - const store = new ProtectedStore(defaultValues); + const store = new ProtectedStore(ui_default); function setOverride({ dx, dy, pagenumber, elementnumber, eventtype }) { for (const [key, value] of Object.entries({ @@ -376,7 +171,7 @@ function create_user_input() { })) { if (typeof value === "undefined") { store.set({ - ...defaultValues, + ...ui_default, }); return; } @@ -502,7 +297,7 @@ function create_user_input() { }); } else { setOverride({ - ...defaultValues, + ...ui_default, }); } } @@ -606,3 +401,7 @@ export function getElementEventTypes(x, y, elementNumber) { return element.events.map((e) => e.type); } + +runtime_manager.subscribe((store) => { + runtime = store.active.runtime; +}); diff --git a/src/renderer/runtime/runtime.ts b/src/renderer/runtime/runtime.ts index 3e4f9c5f8..19d78cc03 100644 --- a/src/renderer/runtime/runtime.ts +++ b/src/renderer/runtime/runtime.ts @@ -37,12 +37,6 @@ import { ActionBlockInformation } from "../config-blocks/ActionBlockInformation" import { Runtime } from "./string-table"; import { Grid } from "../lib/_utils"; import { GridConnection } from "../serialport/serialport"; -import { - clearIntervalAsync, - setIntervalAsync, - SetIntervalAsyncTimer, -} from "set-interval-async"; -import { WriteBuffer } from "./engine.store"; type UUID = string; type LuaScript = string; @@ -1418,13 +1412,8 @@ export interface RuntimeData extends NodeData { export class GridRuntime extends RuntimeNode { public connection: GridConnection; - public readonly heartbeat_editor_ms = 300; - public readonly heartbeat_grid_ms = 250; public readonly virtual: boolean; - private grid_heartbeat: SetIntervalAsyncTimer<[]>; - private editor_heartbeat: SetIntervalAsyncTimer<[]>; - constructor( connection: GridConnection = undefined, virtual: boolean = false @@ -1432,20 +1421,6 @@ export class GridRuntime extends RuntimeNode { super(undefined, { modules: [] }); this.connection = connection; this.virtual = virtual; - - this.editor_heartbeat = setIntervalAsync( - () => GridRuntime.editor_heartbeat_interval_handler(this), - this.heartbeat_editor_ms - ); - this.grid_heartbeat = setIntervalAsync( - () => GridRuntime.grid_heartbeat_interval_handler(this), - this.heartbeat_grid_ms - ); - } - - public killHeartbeat() { - clearIntervalAsync(this.grid_heartbeat); - clearIntervalAsync(this.editor_heartbeat); } get modules() { @@ -1883,52 +1858,4 @@ export class GridRuntime extends RuntimeNode { mandatory: false, }); } - - private static async editor_heartbeat_interval_handler(runtime: GridRuntime) { - let type = 255; - - // if (runtime.unsavedChangesCount() != 0 || typeof get(modal) !== "undefined") { - // type = 254; - // } - - if ( - runtime.modules.length > 0 && - runtime.modules.filter((e) => e.architecture === "virtual").length === 0 - ) { - const instruction = new GridInstruction.SendHeartbeatImmediate( - type, - runtime.virtual - ); - - console.log(runtime); - instruction.executeOn(runtime.connection).catch((e) => { - console.log("EDITOR: Heartbeat skipped..."); - }); - } else { - //writeBuffer.clear(); - } - } - - private static async grid_heartbeat_interval_handler(runtime: GridRuntime) { - for (const device of runtime.modules) { - if (device.architecture === Architecture.VIRTUAL) { - return; - } - - const last = - get(aliveModules).find((e) => e.id === device.id)?.last ?? Date.now(); - - // Allow less strict elapsedTimeLimit while writeBuffer is busy! - const elapsedTimeLimit = - get(runtime.connection.buffer).length > 0 - ? runtime.heartbeat_grid_ms * 6 - : runtime.heartbeat_grid_ms * 3; - const elapsedTime = Date.now() - last; - - if (!last || elapsedTime > elapsedTimeLimit) { - // TIMEOUT! let's remove the device - runtime.destroy_module(device.dx, device.dy); - } - } - } } diff --git a/src/renderer/serialport/serialport.ts b/src/renderer/serialport/serialport.ts index 50c35be04..a998f752e 100644 --- a/src/renderer/serialport/serialport.ts +++ b/src/renderer/serialport/serialport.ts @@ -13,8 +13,7 @@ import { import { Subscriber } from "svelte/motion"; import { GridRuntime } from "../runtime/runtime.js"; import { WriteBuffer } from "../runtime/engine.store.js"; -import { MessageStream } from "./message-stream.store.js"; -import { runtime_manager } from "../runtime/runtime.store.js"; +import { runtime_manager } from "../runtime/runtime.manager.store.js"; const configuration = window.ctxProcess.configuration(); @@ -74,7 +73,7 @@ export type GridConnection = { virtual: boolean; }; -class GridConnectionManager implements Readable { +export class GridConnectionManager implements Readable { private _internal: Writable = writable([]); public subscribe( @@ -109,9 +108,11 @@ class GridConnectionManager implements Readable { virtual: false, }; - const runtime = runtime_manager.allocate(); - buffer.messageStream.bind(runtime); - runtime.connection = current; + const incoming = new GridRuntime(); + buffer.messageStream.bind(incoming); + incoming.connection = current; + + runtime_manager.add(incoming); this.update((store) => { console.log("Port connected:", current); @@ -124,8 +125,7 @@ class GridConnectionManager implements Readable { this.update((store) => { return store.filter((e) => e.id !== current.id); }); - - runtime_manager.free(runtime); + runtime_manager.destroy(incoming); }); resolve(current); From db96c4ad9f9ce99b2c0f5a628e40230bd47bbdc2 Mon Sep 17 00:00:00 2001 From: unknown Date: Thu, 19 Dec 2024 22:14:59 +0100 Subject: [PATCH 6/8] Initial solution --- src/renderer/App.svelte | 7 +- src/renderer/config-blocks/LedColor.svelte | 21 +- src/renderer/config-blocks/LedPhase.svelte | 21 +- src/renderer/config-blocks/Macro.svelte | 26 +- src/renderer/lib/_utils.ts | 13 + src/renderer/main/FirmwareCheck.svelte | 18 +- src/renderer/main/MiddlePanelContainer.svelte | 14 +- .../main/grid-layout/GridLayout.svelte | 134 +++---- .../grid-layout/grid-modules/Device.svelte | 36 +- .../grid-modules/devices/EF44.svelte | 18 +- .../grid-modules/devices/PBF4.svelte | 18 +- .../grid-modules/devices/TEK1.svelte | 19 +- .../grid-modules/devices/VSNX.svelte | 19 +- .../grid-modules/devices/XX16.svelte | 17 +- .../overlays/ControlNameOverlay.svelte | 18 +- .../overlays/PresetLoadOverlay.svelte | 73 +--- .../overlays/ProfileLoadOverlay.svelte | 14 +- .../underlays/ActiveChanges.svelte | 23 +- .../underlays/ElementSelection.svelte | 41 +- .../underlays/ModuleBorder.svelte | 8 +- .../grid-modules/underlays/ModuleInfo.svelte | 4 +- .../main/modals/AddVirtualModule.svelte | 15 +- src/renderer/main/modals/Export.svelte | 10 +- .../panels/DebugMonitor/DebugMonitor.store.js | 7 +- .../panels/DebugMonitor/DebugMonitor.svelte | 10 +- .../panels/MidiMonitor/MidiMonitor.store.ts | 10 +- .../panels/MidiMonitor/MidiMonitor.svelte | 7 +- .../panels/configuration/ActionList.svelte | 2 +- .../panels/configuration/Configuration.svelte | 42 +- .../ElementSelectionPanel.svelte | 24 +- .../panels/configuration/EventPanel.svelte | 32 +- .../configuration/MultiEventView.svelte | 6 +- .../components/ActionPicker.svelte | 7 - .../configuration/components/Pages.svelte | 12 +- .../configuration/components/Toolbar.svelte | 66 +--- .../configuration/components/Toolbar.ts | 10 +- .../panels/preferences/Preferences.svelte | 2 +- .../panels/profileCloud/ProfileCloud.svelte | 31 +- .../main/user-interface/ActiveChanges.svelte | 20 +- .../main/user-interface/PortSelector.svelte | 4 +- .../user-interface/StickyContainer.svelte | 19 +- src/renderer/runtime/operations.ts | 5 +- ...ager.store.ts => runtime-manager.store.ts} | 18 +- src/renderer/runtime/runtime.store.ts | 370 +----------------- src/renderer/runtime/runtime.ts | 81 ++-- src/renderer/runtime/user-input.store.ts | 164 ++++++++ .../serialport/message-stream.store.ts | 178 ++++++++- src/renderer/serialport/serialport.ts | 2 +- 48 files changed, 765 insertions(+), 951 deletions(-) rename src/renderer/runtime/{runtime.manager.store.ts => runtime-manager.store.ts} (92%) create mode 100644 src/renderer/runtime/user-input.store.ts diff --git a/src/renderer/App.svelte b/src/renderer/App.svelte index 074b3df76..931e77151 100644 --- a/src/renderer/App.svelte +++ b/src/renderer/App.svelte @@ -29,7 +29,7 @@ import { watchResize } from "svelte-watch-resize"; import { debug_lowlevel_store } from "./main/panels/WebsocketMonitor/WebsocketMonitor.store"; - import { runtime, logger } from "./runtime/runtime.store"; + import { logger } from "./runtime/runtime.store"; import MiddlePanelContainer from "./main/MiddlePanelContainer.svelte"; import { addPackageAction, removePackageAction } from "./lib/_configs"; @@ -41,7 +41,8 @@ import VersionUpdateBar from "./main/VersionUpdateBar.svelte"; import "redefine-custom-elements"; - import { runtime_manager } from "./runtime/runtime.manager.store"; + import { runtime_manager } from "./runtime/runtime-manager.store"; + import { get } from "svelte/store"; console.log("Hello from Svelte main.js"); @@ -127,7 +128,7 @@ break; } case "change-page": { - runtime.change_page(data.num); + get(runtime_manager).active.runtime.change_page(data.num); break; } case "persist-github-package": { diff --git a/src/renderer/config-blocks/LedColor.svelte b/src/renderer/config-blocks/LedColor.svelte index 460747815..67fba35a3 100644 --- a/src/renderer/config-blocks/LedColor.svelte +++ b/src/renderer/config-blocks/LedColor.svelte @@ -1,5 +1,4 @@ diff --git a/src/renderer/config-blocks/LedPhase.svelte b/src/renderer/config-blocks/LedPhase.svelte index f347675ef..ce4973b77 100644 --- a/src/renderer/config-blocks/LedPhase.svelte +++ b/src/renderer/config-blocks/LedPhase.svelte @@ -48,20 +48,15 @@ - -{#if visible && elementNumber !== 255} +{#if visible && $element.elementIndex !== 255}

{typeof $element.name === "undefined" ? "" : $element.name}

diff --git a/src/renderer/main/grid-layout/grid-modules/overlays/PresetLoadOverlay.svelte b/src/renderer/main/grid-layout/grid-modules/overlays/PresetLoadOverlay.svelte index efafb11fd..991454034 100644 --- a/src/renderer/main/grid-layout/grid-modules/overlays/PresetLoadOverlay.svelte +++ b/src/renderer/main/grid-layout/grid-modules/overlays/PresetLoadOverlay.svelte @@ -1,21 +1,24 @@ @@ -27,12 +18,12 @@ {$element.hasChanges() ? 'changed-element' : ''} {isRightCut ? 'corner-cut-r' : ''} {isLeftCut ? 'corner-cut-l' : ''}" - style=" {elementNumber == 255 + style=" {$element.elementIndex == 255 ? 'border-top-left-radius: 20px; border-top-right-radius: 20px;' : 'border-radius: var(--grid-rounding);'} " on:click={() => { dispatch("click", { - elementNumber: elementNumber, + elementNumber: $element.elementIndex, }); }} /> diff --git a/src/renderer/main/grid-layout/grid-modules/underlays/ElementSelection.svelte b/src/renderer/main/grid-layout/grid-modules/underlays/ElementSelection.svelte index 7a5eb0f48..efd0da107 100644 --- a/src/renderer/main/grid-layout/grid-modules/underlays/ElementSelection.svelte +++ b/src/renderer/main/grid-layout/grid-modules/underlays/ElementSelection.svelte @@ -1,29 +1,34 @@ - @@ -38,12 +43,12 @@ {isRightCut ? 'corner-cut-r' : ''} {isLeftCut ? 'corner-cut-l' : ''} " - style=" {elementNumber == 255 + style=" {$element.elementIndex == 255 ? 'border-top-left-radius: 9999px; border-top-right-radius: 9999px;' : 'border-radius: var(--grid-rounding);'} " on:click={() => { dispatch("click", { - elementNumber: elementNumber, + elementNumber: $element.elementIndex, }); }} /> diff --git a/src/renderer/main/grid-layout/grid-modules/underlays/ModuleBorder.svelte b/src/renderer/main/grid-layout/grid-modules/underlays/ModuleBorder.svelte index f13d4bf68..00a070082 100644 --- a/src/renderer/main/grid-layout/grid-modules/underlays/ModuleBorder.svelte +++ b/src/renderer/main/grid-layout/grid-modules/underlays/ModuleBorder.svelte @@ -1,10 +1,8 @@ -
@@ -101,5 +91,11 @@
- + {#key $page} + + {/key}
diff --git a/src/renderer/main/panels/configuration/EventPanel.svelte b/src/renderer/main/panels/configuration/EventPanel.svelte index 6a5a15b6c..1245df86f 100644 --- a/src/renderer/main/panels/configuration/EventPanel.svelte +++ b/src/renderer/main/panels/configuration/EventPanel.svelte @@ -1,15 +1,14 @@
diff --git a/src/renderer/main/panels/configuration/components/Toolbar.ts b/src/renderer/main/panels/configuration/components/Toolbar.ts index 4963a03ce..69df68077 100644 --- a/src/renderer/main/panels/configuration/components/Toolbar.ts +++ b/src/renderer/main/panels/configuration/components/Toolbar.ts @@ -1,11 +1,12 @@ import { derived } from "svelte/store"; -import { runtime, selected_actions } from "../../../../runtime/runtime.store"; +import { selected_actions } from "../../../../runtime/user-input.store"; import { appClipboard, ClipboardData, ClipboardKey, } from "../../../../runtime/clipboard.store"; import { ElementData } from "../../../../runtime/runtime"; +import { runtime_manager } from "../../../../runtime/runtime-manager.store"; export const isCutActionsEnabled = derived( selected_actions, @@ -15,9 +16,10 @@ export const isCutActionsEnabled = derived( ); export const isCopyElementEnabled = derived( - [selected_actions], - ([$selected_actions]) => { - return $selected_actions.length === 0 && runtime.modules.length > 0; + [selected_actions, runtime_manager], + ([$selected_actions, $runtime_manager]) => { + const active = $runtime_manager.active.runtime; + return $selected_actions.length === 0 && active.modules.length > 0; } ); diff --git a/src/renderer/main/panels/preferences/Preferences.svelte b/src/renderer/main/panels/preferences/Preferences.svelte index 2b6d9b239..061a000e8 100644 --- a/src/renderer/main/panels/preferences/Preferences.svelte +++ b/src/renderer/main/panels/preferences/Preferences.svelte @@ -17,7 +17,7 @@ MoltenInput, } from "@intechstudio/grid-uikit"; import { reduced_motion_store } from "../../../runtime/animations.js"; - import { runtime_manager } from "../../../runtime/runtime.manager.store"; + import { runtime_manager } from "../../../runtime/runtime-manager.store"; const configuration = window.ctxProcess.configuration(); async function selectDirectory() { diff --git a/src/renderer/main/panels/profileCloud/ProfileCloud.svelte b/src/renderer/main/panels/profileCloud/ProfileCloud.svelte index 974754bb4..c8b205bd4 100644 --- a/src/renderer/main/panels/profileCloud/ProfileCloud.svelte +++ b/src/renderer/main/panels/profileCloud/ProfileCloud.svelte @@ -1,9 +1,7 @@ diff --git a/src/renderer/main/user-interface/StickyContainer.svelte b/src/renderer/main/user-interface/StickyContainer.svelte index 290ddfc47..ce28f94b3 100644 --- a/src/renderer/main/user-interface/StickyContainer.svelte +++ b/src/renderer/main/user-interface/StickyContainer.svelte @@ -1,20 +1,26 @@ diff --git a/src/renderer/runtime/operations.ts b/src/renderer/runtime/operations.ts index 6c2510c1a..eda945749 100644 --- a/src/renderer/runtime/operations.ts +++ b/src/renderer/runtime/operations.ts @@ -1,6 +1,8 @@ import { Analytics } from "./analytics"; import { appClipboard, ClipboardKey } from "./clipboard.store"; -import { logger, user_input, selected_actions } from "./runtime.store"; +import { logger } from "./runtime.store"; +import { selected_actions } from "./user-input.store"; + import { GridOperationResult, ElementData, @@ -17,6 +19,7 @@ import { GridPresetData, } from "./runtime"; import { get } from "svelte/store"; +import { user_input } from "./user-input.store"; function handleError(e: GridOperationResult) { //TODO: Better error handling diff --git a/src/renderer/runtime/runtime.manager.store.ts b/src/renderer/runtime/runtime-manager.store.ts similarity index 92% rename from src/renderer/runtime/runtime.manager.store.ts rename to src/renderer/runtime/runtime-manager.store.ts index 166cf80aa..67526de4d 100644 --- a/src/renderer/runtime/runtime.manager.store.ts +++ b/src/renderer/runtime/runtime-manager.store.ts @@ -7,7 +7,7 @@ import { Updater, Readable, } from "svelte/store"; -import { GridRuntime, aliveModules } from "./runtime"; +import { GridRuntime } from "./runtime"; import { GridInstruction } from "../serialport/instructions"; import { GridConnection } from "../serialport/serialport"; import { WriteBuffer } from "./engine.store"; @@ -244,7 +244,8 @@ export class GridRuntimeManager implements Readable { if ( runtime.modules.length > 0 && - runtime.modules.filter((e) => e.architecture === "virtual").length === 0 + runtime.modules.filter((e) => e.architecture === Architecture.VIRTUAL) + .length === 0 ) { const instruction = new GridInstruction.SendHeartbeatImmediate( type, @@ -265,18 +266,9 @@ export class GridRuntimeManager implements Readable { return; } - const last = - get(aliveModules).find((e) => e.id === device.id)?.last ?? Date.now(); - - // Allow less strict elapsedTimeLimit while writeBuffer is busy! - const elapsedTimeLimit = - get(runtime.connection.buffer).length > 0 - ? GridRuntimeManager.heartbeat_grid_ms * 6 - : GridRuntimeManager.heartbeat_grid_ms * 3; - const elapsedTime = Date.now() - last; - - if (!last || elapsedTime > elapsedTimeLimit) { + if (!runtime.isAlive(device)) { // TIMEOUT! let's remove the device + console.log("Heartbeat lost..."); runtime.destroy_module(device.dx, device.dy); } } diff --git a/src/renderer/runtime/runtime.store.ts b/src/renderer/runtime/runtime.store.ts index bb3eb1092..a71d4517d 100644 --- a/src/renderer/runtime/runtime.store.ts +++ b/src/renderer/runtime/runtime.store.ts @@ -1,337 +1,8 @@ -import { writable, get, type Writable } from "svelte/store"; -import { appSettings } from "./app-helper.store"; -import { modal, Snap } from "../main/modals/modal.store"; -import { ProtectedStore } from "./smart-store.store"; -import { GridAction, GridRuntime } from "./runtime"; -import { runtime_manager } from "./runtime.manager.store"; - -export let runtime: GridRuntime; - -// The controller which is added to runtime first, load a default config! - -let selection_changed_timestamp = 0; - -export const elementPositionStore = writable({}); -export const ledColorStore = writable({}); - -export function update_elementPositionStore(descr) { - if (descr.class_parameters.EVENTTYPE == 3) { - // button change must not be registered - - return; - } - - let eps = get(elementPositionStore); - - if (eps[descr.brc_parameters.SX] === undefined) { - eps[descr.brc_parameters.SX] = {}; - } - if (eps[descr.brc_parameters.SX][descr.brc_parameters.SY] === undefined) { - eps[descr.brc_parameters.SX][descr.brc_parameters.SY] = {}; - } - if ( - eps[descr.brc_parameters.SX][descr.brc_parameters.SY][ - descr.class_parameters.ELEMENTNUMBER - ] === undefined - ) { - eps[descr.brc_parameters.SX][descr.brc_parameters.SY][ - descr.class_parameters.ELEMENTNUMBER - ] = -1; - } - - eps[descr.brc_parameters.SX][descr.brc_parameters.SY][ - descr.class_parameters.ELEMENTNUMBER - ] = [descr.class_parameters.EVENTPARAM1, descr.class_parameters.EVENTPARAM2]; - - //console.log("Pos", descr.class_parameters.EVENTPARAM) - - elementPositionStore.set(eps); -} - -export function update_element_name(descr) { - const [dx, dy, element, name] = [ - Number(descr.brc_parameters.SX), - Number(descr.brc_parameters.SY), - Number(descr.class_parameters.NUM), - String(descr.class_parameters.NAME), - ]; - - runtime.modules - .find((e) => e.dx === dx && e.dy === dy) - .pages.forEach( - (e) => - (e.control_elements.find((e) => e.elementIndex === element).name = - name.length > 0 ? name : undefined) - ); -} - -export function update_elementPositionStore_fromPreview(descr) { - let eps = get(elementPositionStore); - - if (eps[descr.brc_parameters.SX] === undefined) { - eps[descr.brc_parameters.SX] = {}; - } - if (eps[descr.brc_parameters.SX][descr.brc_parameters.SY] === undefined) { - eps[descr.brc_parameters.SX][descr.brc_parameters.SY] = {}; - } - - for (let i = 1; i < descr.class_parameters.LENGTH / 4; i++) { - const num = parseInt( - "0x" + - String.fromCharCode(descr.raw[4 + i * 4 + 0]) + - String.fromCharCode(descr.raw[4 + i * 4 + 1]) - ); - const val = parseInt( - "0x" + - String.fromCharCode(descr.raw[4 + i * 4 + 2]) + - String.fromCharCode(descr.raw[4 + i * 4 + 3]) - ); - //console.log(num, val) - - if ( - eps[descr.brc_parameters.SX][descr.brc_parameters.SY][num] === undefined - ) { - eps[descr.brc_parameters.SX][descr.brc_parameters.SY][num] = -1; - } - - eps[descr.brc_parameters.SX][descr.brc_parameters.SY][num] = val; - - elementPositionStore.set(eps); - } -} - -export function update_ledColorStore(descr) { - for (let i = 0; i < descr.class_parameters.LENGTH / 8; i++) { - const num = parseInt( - "0x" + - String.fromCharCode(descr.raw[8 + i * 8 + 0]) + - String.fromCharCode(descr.raw[8 + i * 8 + 1]) - ); - const red = parseInt( - "0x" + - String.fromCharCode(descr.raw[8 + i * 8 + 2]) + - String.fromCharCode(descr.raw[8 + i * 8 + 3]) - ); - const gre = parseInt( - "0x" + - String.fromCharCode(descr.raw[8 + i * 8 + 4]) + - String.fromCharCode(descr.raw[8 + i * 8 + 5]) - ); - const blu = parseInt( - "0x" + - String.fromCharCode(descr.raw[8 + i * 8 + 6]) + - String.fromCharCode(descr.raw[8 + i * 8 + 7]) - ); - - //console.log(num, red, gre, blu) - - let lcs = get(ledColorStore); - - if (lcs[descr.brc_parameters.SX] === undefined) { - lcs[descr.brc_parameters.SX] = {}; - } - if (lcs[descr.brc_parameters.SX][descr.brc_parameters.SY] === undefined) { - lcs[descr.brc_parameters.SX][descr.brc_parameters.SY] = {}; - } - if ( - lcs[descr.brc_parameters.SX][descr.brc_parameters.SY][num] === undefined - ) { - lcs[descr.brc_parameters.SX][descr.brc_parameters.SY][num] = [0, 0, 0]; - } - - lcs[descr.brc_parameters.SX][descr.brc_parameters.SY][num][0] = red * 4; - lcs[descr.brc_parameters.SX][descr.brc_parameters.SY][num][1] = gre * 4; - lcs[descr.brc_parameters.SX][descr.brc_parameters.SY][num][2] = blu * 4; - - ledColorStore.set(lcs); - } -} +import { writable } from "svelte/store"; //Template logger object: { type: "", message: "", classname: "" } export const logger = writable(); -function create_user_input() { - const ui_default: UserInputValue = { - dx: 0, - dy: 0, - pagenumber: 0, - elementnumber: 0, - eventtype: 2, - }; - - const store = new ProtectedStore(ui_default); - - function setOverride({ dx, dy, pagenumber, elementnumber, eventtype }) { - for (const [key, value] of Object.entries({ - dx, - dy, - pagenumber, - elementnumber, - eventtype, - })) { - if (typeof value === "undefined") { - store.set({ - ...ui_default, - }); - return; - } - if (typeof value !== "number") { - throw `($user_input store): ${key} (${value}) is not the type of Number.`; - } - } - - const events = getElementEventTypes(dx, dy, elementnumber); - const closestEvent = get_closest_event(events, eventtype); - - store.set({ - dx: dx, - dy: dy, - pagenumber: pagenumber, - elementnumber: elementnumber, - eventtype: closestEvent, - }); - } - - function get_closest_event(events, event) { - if (events.map((e) => Number(e)).includes(Number(event))) { - return event; - } - - //Select closest event type if incoming device does not have the corrently selected event type - const closestEvent = Math.min( - ...events.map((e) => Number(e)).filter((e) => e > 0) - ); - - return closestEvent !== Infinity ? closestEvent : 0; - } - - function process_incoming_event_from_grid(descr) { - // modal block track physical interaction setting - if ( - typeof get(modal) !== "undefined" && - get(modal).options.snap === Snap.FULL - ) { - return; - } - - // event is init, mapmode, midirx, timer - if ( - descr.class_parameters.EVENTTYPE == 0 || - descr.class_parameters.EVENTTYPE == 4 || - descr.class_parameters.EVENTTYPE == 5 || - descr.class_parameters.EVENTTYPE == 6 - ) { - return; - } - - // system element - if (descr.class_parameters.ELEMENTNUMBER == 255) { - return; - } - - const ui = get(store); - - // filter same control element had multiple interactions - let elementDifferent = - ui.elementnumber != descr.class_parameters.ELEMENTNUMBER; - let eventDifferent = ui.eventtype != descr.class_parameters.EVENTTYPE; - let sxDifferent = ui.dx != descr.brc_parameters.SX; - let syDifferent = ui.dy != descr.brc_parameters.SY; - - if (eventDifferent || elementDifferent || sxDifferent || syDifferent) { - //Filtering out clashing events of control elements - //Example: multiple fader movenet at the same time - if (Date.now() < selection_changed_timestamp + 100) { - return; - } - - let eventtype; - switch (get(appSettings).persistent.changeOnEvent) { - case "element": { - const incomingEventTypes = getElementEventTypes( - descr.brc_parameters.SX, - descr.brc_parameters.SY, - descr.class_parameters.ELEMENTNUMBER - ); - const current = ui.eventtype; - eventtype = get_closest_event(incomingEventTypes, current); - if (!elementDifferent) { - return; - } - break; - } - case "none": { - return; - } - case "event": - default: { - eventtype = descr.class_parameters.EVENTTYPE; - } - } - store.set({ - dx: descr.brc_parameters.SX, - dy: descr.brc_parameters.SY, - pagenumber: ui.pagenumber, - elementnumber: descr.class_parameters.ELEMENTNUMBER, - eventtype: eventtype, - }); - } - selection_changed_timestamp = Date.now(); - } - - function module_destroy_handler(dx, dy) { - // This is used to re-init local settings panel if a module is removed which values have been displayed - const ui = get(store); - - if (dx == ui.dx && dy == ui.dy) { - if (runtime.modules.length > 0) { - //Set user input to an EXISTING module - //(0,0) module is not guaranteed to exist when unplugging all modules once - //NOTE: Hardcoding (0,0) may break user_input - setOverride({ - dx: runtime.modules[0].dx, - dy: runtime.modules[0].dy, - pagenumber: ui.pagenumber, - elementnumber: 0, - eventtype: ui.eventtype, - }); - } else { - setOverride({ - ...ui_default, - }); - } - } - } - - return { - ...store, - set: setOverride, - process_incoming_event_from_grid: process_incoming_event_from_grid, - module_destroy_handler: module_destroy_handler, - }; -} - -export type UserInputValue = { - dx: number; - dy: number; - pagenumber: number; - elementnumber: number; - eventtype: number; -}; - -export const user_input = create_user_input(); - -export const selected_actions: Writable = - create_selected_actions_store(); - -function create_selected_actions_store() { - const internal: Writable = writable([]); - user_input.subscribe(() => { - internal.set([]); - }); - return internal; -} - export class LocalDefinitions { static getFrom({ configs, index }) { const config = configs[index]; @@ -366,42 +37,3 @@ export class LocalDefinitions { export async function wss_send_message(message) { window.electron.websocket.transmit({ event: "message", data: message }); } - -//////////////////////////////////////////////////////// -////////// Helper functions to refactor out //////////// -//////////////////////////////////////////////////////// - -//Retrieves device name from coordinates of the device -export function getDeviceName(x, y) { - const currentModule = runtime.modules.find( - (device) => device.dx == x && device.dy == y - ); - return currentModule?.type; -} - -export function getElementEventTypes(x, y, elementNumber) { - const currentModule = runtime.modules.find( - (device) => device.dx == x && device.dy == y - ); - - if (typeof currentModule === "undefined") { - console.warn(`Module does not exist on (${x}, ${y})`); - return undefined; - } - const element = currentModule.pages[0].control_elements.find( - (e) => e.elementIndex == elementNumber - ); - - if (typeof element === "undefined") { - console.warn( - `Control element ${elementNumber} does not exist on (${x}, ${y})` - ); - return undefined; - } - - return element.events.map((e) => e.type); -} - -runtime_manager.subscribe((store) => { - runtime = store.active.runtime; -}); diff --git a/src/renderer/runtime/runtime.ts b/src/renderer/runtime/runtime.ts index 19d78cc03..215dc90b7 100644 --- a/src/renderer/runtime/runtime.ts +++ b/src/renderer/runtime/runtime.ts @@ -4,7 +4,6 @@ import { ElementType, ModuleType, GridScript, - EventType, NumberToEventType, } from "@intechstudio/grid-protocol"; import { @@ -19,16 +18,8 @@ import { GridInstruction } from "../serialport/instructions"; import { connection_simulator } from "./virtual-engine"; import { Analytics } from "./analytics.js"; import { appSettings } from "./app-helper.store"; -import { - add_datapoint, - MessageStream, -} from "../serialport/message-stream.store.js"; -import { - elementPositionStore, - ledColorStore, - logger, - user_input, -} from "./runtime.store"; +import { add_datapoint } from "../serialport/message-stream.store.js"; +import { logger } from "./runtime.store"; import { v4 as uuidv4 } from "uuid"; import { getComponentInformation } from "../lib/_configs"; import * as CodeBlock from "../config-blocks/CodeBlock.svelte"; @@ -37,6 +28,8 @@ import { ActionBlockInformation } from "../config-blocks/ActionBlockInformation" import { Runtime } from "./string-table"; import { Grid } from "../lib/_utils"; import { GridConnection } from "../serialport/serialport"; +import { GridRuntimeManager } from "./runtime-manager.store"; +import { user_input } from "./user-input.store"; type UUID = string; type LuaScript = string; @@ -46,9 +39,6 @@ class NodeData { parent?: RuntimeNode; } -export const aliveModules: Writable> = - writable([]); - export class GridProfileData { public presets: GridPresetData[] = []; @@ -1411,8 +1401,10 @@ export interface RuntimeData extends NodeData { export class GridRuntime extends RuntimeNode { public connection: GridConnection; - public readonly virtual: boolean; + private aliveModules: Writable>; + public elementPositionStore: Writable = writable({}); + public ledColorStore: Writable = writable({}); constructor( connection: GridConnection = undefined, @@ -1421,6 +1413,7 @@ export class GridRuntime extends RuntimeNode { super(undefined, { modules: [] }); this.connection = connection; this.virtual = virtual; + this.aliveModules = writable([]); } get modules() { @@ -1495,12 +1488,6 @@ export class GridRuntime extends RuntimeNode { incoming_heartbeat_handler(descr) { try { - for (const module of this.modules) { - if (module.architecture === "virtual") { - this.destroy_module(module.dx, module.dy); - } - } - const [sx, sy] = [descr.brc_parameters.SX, descr.brc_parameters.SY]; let firstConnection = false; @@ -1514,17 +1501,17 @@ export class GridRuntime extends RuntimeNode { module.portstate = descr.class_parameters.PORTSTATE; } - aliveModules.update((s) => { - const index = s.findIndex((e) => e.id === module.id); - const lastDate = s[index].last; + this.aliveModules.update((store) => { + const obj = store.find((e) => e.id === module.id); + const lastDate = obj.last; const newDate = Date.now(); - s[index].last = newDate; + obj.last = newDate; if (get(appSettings).persistent.heartbeatDebugEnabled) { const key1 = `Hearbeat (${module.dx}, ${module.dy})`; add_datapoint(key1, newDate - lastDate); } - return s; + return store; }); } // device not found, add it to runtime and get page count from grid @@ -1556,13 +1543,11 @@ export class GridRuntime extends RuntimeNode { ); this.modules = [...this.modules, controller]; - aliveModules.update((s) => { + this.aliveModules.update((s) => { s.push({ id: controller.id, last: Date.now() }); return s; }); - firstConnection = this.modules.length === 1; - Analytics.track({ event: "Connect Module", payload: { @@ -1573,25 +1558,11 @@ export class GridRuntime extends RuntimeNode { mandatory: false, }); } - - if (firstConnection) { - this.setDefaultSelectedElement(); - } } catch (error) { console.warn(error); } } - setDefaultSelectedElement() { - user_input.set({ - dx: this.modules[0].dx, - dy: this.modules[0].dy, - pagenumber: 0, - elementnumber: 0, - eventtype: 2, - }); - } - public async change_page(new_page_number): Promise { return new Promise((resolve, reject) => { if (get(this.connection.buffer).length > 0) { @@ -1759,7 +1730,6 @@ export class GridRuntime extends RuntimeNode { connection_simulator.createModule(dx, dy, moduleInfo.type); this.modules = [...this.modules, controller]; - this.setDefaultSelectedElement(); } create_module(header_param, heartbeat_class_param, virtual = false) { @@ -1808,6 +1778,21 @@ export class GridRuntime extends RuntimeNode { }); } + public isAlive(device: GridModule) { + const last = + get(this.aliveModules).find((e) => e.id === device.id)?.last ?? + Date.now(); + + // Allow less strict elapsedTimeLimit while writeBuffer is busy! + const elapsedTimeLimit = + get(this.connection.buffer).length > 0 + ? GridRuntimeManager.heartbeat_grid_ms * 6 + : GridRuntimeManager.heartbeat_grid_ms * 3; + const elapsedTime = Date.now() - last; + + return last && elapsedTime <= elapsedTimeLimit; + } + destroy_module(dx, dy) { console.log("DESTORY", dx, dy); user_input.module_destroy_handler(dx, dy); @@ -1816,7 +1801,7 @@ export class GridRuntime extends RuntimeNode { this.modules = this.modules.filter((e) => e.dx !== dx && e.dy !== dy); removed.destroy(); - aliveModules.update((s) => { + this.aliveModules.update((s) => { const index = s.findIndex((e) => e.id === removed.id); s.splice(index, 1); return s; @@ -1829,7 +1814,7 @@ export class GridRuntime extends RuntimeNode { }); } - if (removed.architecture === "virtual") { + if (removed.architecture === Architecture.VIRTUAL) { connection_simulator.destroyModule(dx, dy); } else { this.connection.buffer.module_destroy_handler(dx, dy); @@ -1838,12 +1823,12 @@ export class GridRuntime extends RuntimeNode { // reset rendering helper stores try { - elementPositionStore.update((eps) => { + this.elementPositionStore.update((eps) => { eps[dx][dy] = undefined; return eps; }); - ledColorStore.update((lcs) => { + this.ledColorStore.update((lcs) => { lcs[dx][dy] = undefined; return lcs; }); diff --git a/src/renderer/runtime/user-input.store.ts b/src/renderer/runtime/user-input.store.ts new file mode 100644 index 000000000..892659757 --- /dev/null +++ b/src/renderer/runtime/user-input.store.ts @@ -0,0 +1,164 @@ +import { get, Unsubscriber, Updater, writable, Writable } from "svelte/store"; +import { modal, Snap } from "../main/modals/modal.store"; +import { appSettings } from "./app-helper.store"; +import { runtime_manager } from "./runtime-manager.store"; +import { Subscriber } from "svelte/motion"; +import { Grid } from "../lib/_utils"; +import { GridAction } from "./runtime"; + +export type UserInputValue = { + dx: number; + dy: number; + pagenumber: number; + elementnumber: number; + eventtype: number; +}; + +export class UserInput implements Writable { + static readonly defaultValue: UserInputValue = { + dx: 0, + dy: 0, + pagenumber: 0, + elementnumber: 0, + eventtype: 2, + }; + + private _internal: Writable; + private _changed_timestamp = 0; + + constructor() { + this._internal = writable(UserInput.defaultValue); + } + + // Subscribe to the entire object + public subscribe( + run: Subscriber, + invalidate?: (value?: UserInputValue) => void + ): Unsubscriber { + return this._internal.subscribe(run, invalidate); + } + + // Set the entire object + public set(value: UserInputValue) { + const runtime = get(runtime_manager).active.runtime; + const events = runtime + .findElement(value.dx, value.dy, value.pagenumber, value.elementnumber) + ?.events.map((e) => e.type); + const closestEvent = Grid.getClosestEvent(events ?? [2], value.eventtype); + + this._internal.set({ + dx: value.dx, + dy: value.dy, + pagenumber: value.pagenumber, + elementnumber: value.elementnumber, + eventtype: closestEvent, + }); + } + + // Update the object with a partial update function + public update(updater: Updater) { + this._internal.update(updater); + } + + // Process incoming events + public process_incoming_event_from_grid(descr: any) { + if ( + typeof get(modal) !== "undefined" && + get(modal).options.snap === Snap.FULL + ) { + return; + } + + if ( + descr.class_parameters.EVENTTYPE === 0 || + descr.class_parameters.EVENTTYPE === 4 || + descr.class_parameters.EVENTTYPE === 5 || + descr.class_parameters.EVENTTYPE === 6 + ) { + return; + } + + if (descr.class_parameters.ELEMENTNUMBER === 255) { + return; + } + + const ui = get(this._internal); + + let elementDifferent = + ui.elementnumber !== descr.class_parameters.ELEMENTNUMBER; + let eventDifferent = ui.eventtype !== descr.class_parameters.EVENTTYPE; + let sxDifferent = ui.dx !== descr.brc_parameters.SX; + let syDifferent = ui.dy !== descr.brc_parameters.SY; + + if (eventDifferent || elementDifferent || sxDifferent || syDifferent) { + if (Date.now() < this._changed_timestamp + 100) { + return; + } + + let eventtype; + switch (get(appSettings).persistent.changeOnEvent) { + case "element": { + const runtime = get(runtime_manager).active.runtime; + const incomingEventTypes = runtime + .findElement( + descr.brc_parameters.SX, + descr.brc_parameters.SY, + ui.pagenumber, + descr.class_parameters.ELEMENTNUMBER + ) + .events.map((e) => e.type); + + const current = ui.eventtype; + eventtype = Grid.getClosestEvent(incomingEventTypes, current); + if (!elementDifferent) { + return; + } + break; + } + case "none": { + return; + } + case "event": + default: { + eventtype = descr.class_parameters.EVENTTYPE; + } + } + this.set({ + dx: descr.brc_parameters.SX, + dy: descr.brc_parameters.SY, + pagenumber: ui.pagenumber, + elementnumber: descr.class_parameters.ELEMENTNUMBER, + eventtype: eventtype, + }); + } + + this._changed_timestamp = Date.now(); + } + + // Module destroy handler + public module_destroy_handler(dx: number, dy: number) { + const ui = get(this._internal); + + if (dx === ui.dx && dy === ui.dy) { + const active = get(runtime_manager).active.runtime; + if (active.modules.length > 0) { + this.set({ + dx: active.modules[0].dx, + dy: active.modules[0].dy, + pagenumber: ui.pagenumber, + elementnumber: 0, + eventtype: ui.eventtype, + }); + } else { + this.set(UserInput.defaultValue); + } + } + } +} + +export const user_input = new UserInput(); + +export const selected_actions: Writable = writable([]); +user_input.subscribe(() => { + selected_actions.set([]); +}); diff --git a/src/renderer/serialport/message-stream.store.ts b/src/renderer/serialport/message-stream.store.ts index 69b3a97ce..3dc605eda 100644 --- a/src/renderer/serialport/message-stream.store.ts +++ b/src/renderer/serialport/message-stream.store.ts @@ -1,14 +1,7 @@ // Top level imports import { writable, get } from "svelte/store"; import { appSettings } from "../runtime/app-helper.store.js"; -import { - user_input, - wss_send_message, - update_element_name, - update_elementPositionStore, - update_elementPositionStore_fromPreview, - update_ledColorStore, -} from "../runtime/runtime.store"; +import { wss_send_message } from "../runtime/runtime.store"; import { debug_monitor_store, lua_error_store, @@ -22,6 +15,7 @@ import { logger } from "../runtime/runtime.store"; import { PolyLineGraphData } from "../main/user-interface/PolyLineGraph.js"; import { WriteBuffer } from "../runtime/engine.store.js"; import { GridRuntime } from "../runtime/runtime.js"; +import { user_input } from "../runtime/user-input.store"; export const incoming_messages = writable([]); export function add_datapoint(key, value) { @@ -61,6 +55,142 @@ export class MessageStream { this.runtime = runtime; } + private update_elementPositionStore(descr) { + if (descr.class_parameters.EVENTTYPE == 3) { + // button change must not be registered + + return; + } + + let eps = get(this.runtime.elementPositionStore); + + if (eps[descr.brc_parameters.SX] === undefined) { + eps[descr.brc_parameters.SX] = {}; + } + if (eps[descr.brc_parameters.SX][descr.brc_parameters.SY] === undefined) { + eps[descr.brc_parameters.SX][descr.brc_parameters.SY] = {}; + } + if ( + eps[descr.brc_parameters.SX][descr.brc_parameters.SY][ + descr.class_parameters.ELEMENTNUMBER + ] === undefined + ) { + eps[descr.brc_parameters.SX][descr.brc_parameters.SY][ + descr.class_parameters.ELEMENTNUMBER + ] = -1; + } + + eps[descr.brc_parameters.SX][descr.brc_parameters.SY][ + descr.class_parameters.ELEMENTNUMBER + ] = [ + descr.class_parameters.EVENTPARAM1, + descr.class_parameters.EVENTPARAM2, + ]; + + //console.log("Pos", descr.class_parameters.EVENTPARAM) + + this.runtime.elementPositionStore.set(eps); + } + + private update_element_name(descr) { + const [dx, dy, element, name] = [ + Number(descr.brc_parameters.SX), + Number(descr.brc_parameters.SY), + Number(descr.class_parameters.NUM), + String(descr.class_parameters.NAME), + ]; + + this.runtime.modules + .find((e) => e.dx === dx && e.dy === dy) + .pages.forEach( + (e) => + (e.control_elements.find((e) => e.elementIndex === element).name = + name.length > 0 ? name : undefined) + ); + } + + private update_elementPositionStore_fromPreview(descr) { + let eps = get(this.runtime.elementPositionStore); + + if (eps[descr.brc_parameters.SX] === undefined) { + eps[descr.brc_parameters.SX] = {}; + } + if (eps[descr.brc_parameters.SX][descr.brc_parameters.SY] === undefined) { + eps[descr.brc_parameters.SX][descr.brc_parameters.SY] = {}; + } + + for (let i = 1; i < descr.class_parameters.LENGTH / 4; i++) { + const num = parseInt( + "0x" + + String.fromCharCode(descr.raw[4 + i * 4 + 0]) + + String.fromCharCode(descr.raw[4 + i * 4 + 1]) + ); + const val = parseInt( + "0x" + + String.fromCharCode(descr.raw[4 + i * 4 + 2]) + + String.fromCharCode(descr.raw[4 + i * 4 + 3]) + ); + //console.log(num, val) + + if ( + eps[descr.brc_parameters.SX][descr.brc_parameters.SY][num] === undefined + ) { + eps[descr.brc_parameters.SX][descr.brc_parameters.SY][num] = -1; + } + + eps[descr.brc_parameters.SX][descr.brc_parameters.SY][num] = val; + + this.runtime.elementPositionStore.set(eps); + } + } + + private update_ledColorStore(descr) { + for (let i = 0; i < descr.class_parameters.LENGTH / 8; i++) { + const num = parseInt( + "0x" + + String.fromCharCode(descr.raw[8 + i * 8 + 0]) + + String.fromCharCode(descr.raw[8 + i * 8 + 1]) + ); + const red = parseInt( + "0x" + + String.fromCharCode(descr.raw[8 + i * 8 + 2]) + + String.fromCharCode(descr.raw[8 + i * 8 + 3]) + ); + const gre = parseInt( + "0x" + + String.fromCharCode(descr.raw[8 + i * 8 + 4]) + + String.fromCharCode(descr.raw[8 + i * 8 + 5]) + ); + const blu = parseInt( + "0x" + + String.fromCharCode(descr.raw[8 + i * 8 + 6]) + + String.fromCharCode(descr.raw[8 + i * 8 + 7]) + ); + + //console.log(num, red, gre, blu) + + let lcs = get(this.runtime.ledColorStore); + + if (lcs[descr.brc_parameters.SX] === undefined) { + lcs[descr.brc_parameters.SX] = {}; + } + if (lcs[descr.brc_parameters.SX][descr.brc_parameters.SY] === undefined) { + lcs[descr.brc_parameters.SX][descr.brc_parameters.SY] = {}; + } + if ( + lcs[descr.brc_parameters.SX][descr.brc_parameters.SY][num] === undefined + ) { + lcs[descr.brc_parameters.SX][descr.brc_parameters.SY][num] = [0, 0, 0]; + } + + lcs[descr.brc_parameters.SX][descr.brc_parameters.SY][num][0] = red * 4; + lcs[descr.brc_parameters.SX][descr.brc_parameters.SY][num][1] = gre * 4; + lcs[descr.brc_parameters.SX][descr.brc_parameters.SY][num][2] = blu * 4; + + this.runtime.ledColorStore.set(lcs); + } + } + public deliver_inbound(class_array: any) { if (class_array === undefined) { return; @@ -105,6 +235,11 @@ export class MessageStream { if (class_descr.class_name === "DEBUGTEXT") { debug_monitor_store.update_debugtext(class_descr); const text = class_descr.class_parameters.TEXT; + const [sx, sy] = [ + class_descr.brc_parameters.SX, + class_descr.brc_parameters.SY, + ]; + const device = this.runtime.findModule(sx, sy); //LUA not OK const regex = /EL:\s*(\d+(?:\.\d+)?)\s*EV:\s*(\d+(?:\.\d+)?)/; @@ -129,11 +264,12 @@ export class MessageStream { if (luaNotOKMatch) { class_descr.element = Number(luaNotOKMatch[1]); class_descr.event = Number(luaNotOKMatch[2]); - lua_error_store.update_lua_error("luanotok", class_descr); + + lua_error_store.update_lua_error("luanotok", class_descr, device); } //KB IS DISABLED else if (text == "KB IS DISABLED") { - lua_error_store.update_lua_error("kbisdisabled", class_descr); + lua_error_store.update_lua_error("kbisdisabled", class_descr, device); } else if (text == "page change is disabled") { logger.set({ type: "alert", @@ -157,18 +293,28 @@ export class MessageStream { } if (class_descr.class_name === "LEDPREVIEW") { - update_ledColorStore(class_descr); + this.update_ledColorStore(class_descr); } if (class_descr.class_name === "MIDI") { - midi_monitor_store.update_midi(class_descr); + const [sx, sy] = [ + class_descr.brc_parameters.SX, + class_descr.brc_parameters.SY, + ]; + const device = this.runtime.findModule(sx, sy); + midi_monitor_store.update_midi(class_descr, device); // websocket send data to package // ipcRenderer.send('websocket_tx', class_descr); } if (class_descr.class_name === "MIDISYSEX") { - sysex_monitor_store.update_sysex(class_descr); + const [sx, sy] = [ + class_descr.brc_parameters.SX, + class_descr.brc_parameters.SY, + ]; + const device = this.runtime.findModule(sx, sy); + sysex_monitor_store.update_sysex(class_descr, device); } if (class_descr.class_name === "CONFIG") { @@ -176,7 +322,7 @@ export class MessageStream { if (class_descr.class_name === "EVENT") { // update control element rotation - update_elementPositionStore(class_descr); + this.update_elementPositionStore(class_descr); // engine is enabled if (get(this._buffer).length === 0) { @@ -188,7 +334,7 @@ export class MessageStream { if (class_descr.class_name === "EVENTPREVIEW") { //console.log("EVENTPREVIEW", class_descr.class_parameters["LENGTH"]) - update_elementPositionStore_fromPreview(class_descr); + this.update_elementPositionStore_fromPreview(class_descr); // update control element rotation //update_elementPositionStore(class_descr); @@ -201,7 +347,7 @@ export class MessageStream { class_descr.class_name === "ELEMENTNAME" && class_descr.class_instr === "EXECUTE" ) { - update_element_name(class_descr); + this.update_element_name(class_descr); } if ( diff --git a/src/renderer/serialport/serialport.ts b/src/renderer/serialport/serialport.ts index a998f752e..d01cdd89c 100644 --- a/src/renderer/serialport/serialport.ts +++ b/src/renderer/serialport/serialport.ts @@ -13,7 +13,7 @@ import { import { Subscriber } from "svelte/motion"; import { GridRuntime } from "../runtime/runtime.js"; import { WriteBuffer } from "../runtime/engine.store.js"; -import { runtime_manager } from "../runtime/runtime.manager.store.js"; +import { runtime_manager } from "../runtime/runtime-manager.store.js"; const configuration = window.ctxProcess.configuration(); From 5027984e6ca099c7867d9fd96ae60a9a2474d2a5 Mon Sep 17 00:00:00 2001 From: unknown Date: Fri, 20 Dec 2024 00:03:31 +0100 Subject: [PATCH 7/8] Fixed tests --- src/renderer/config-blocks/Macro.svelte | 7 ++- .../main/grid-layout/GridLayout.svelte | 50 +++++++++++++------ .../panels/configuration/Configuration.svelte | 13 +++-- .../main/user-interface/ActiveChanges.svelte | 5 +- .../main/user-interface/PortSelector.svelte | 2 +- src/renderer/runtime/runtime-manager.store.ts | 13 ++++- src/renderer/runtime/runtime.ts | 8 ++- src/renderer/runtime/user-input.store.ts | 14 ++++++ 8 files changed, 86 insertions(+), 26 deletions(-) diff --git a/src/renderer/config-blocks/Macro.svelte b/src/renderer/config-blocks/Macro.svelte index 0f151754f..0b64fb2fd 100644 --- a/src/renderer/config-blocks/Macro.svelte +++ b/src/renderer/config-blocks/Macro.svelte @@ -81,12 +81,15 @@ let event = config.parent as GridEvent; let element = event.parent as GridElement; let page = element.parent as GridPage; - let runtime = page.parent as GridRuntime; + let module = page.parent as GridModule; + let runtime = module.parent as GridRuntime; let macroInputField; let isChanges = false; - $: isChanges = $runtime.unsavedChangesCount() > 0; + $: if ($runtime) { + isChanges = runtime.unsavedChangesCount() > 0; + } onMount(() => { selectedLayout = $appSettings.persistent.keyboardLayout; diff --git a/src/renderer/main/grid-layout/GridLayout.svelte b/src/renderer/main/grid-layout/GridLayout.svelte index a942a6993..2914b4d22 100644 --- a/src/renderer/main/grid-layout/GridLayout.svelte +++ b/src/renderer/main/grid-layout/GridLayout.svelte @@ -20,7 +20,6 @@ const dispatch = createEventDispatcher(); - const devices = writable([]); let columns = 0; let rows = 0; const deviceGap = 5; @@ -84,6 +83,29 @@ const min_y = Math.min(...active.modules.map((e) => e.dy)); const max_x = Math.max(...active.modules.map((e) => e.dx)); const max_y = Math.max(...active.modules.map((e) => e.dy)); + + layoutMargin = { + left: + active.modules.find((e) => e.dx == min_x)?.architecture == + Architecture.VIRTUAL + ? 30 + : 0, + right: + active.modules.find((e) => e.dx == max_x)?.architecture == + Architecture.VIRTUAL + ? 30 + : 0, + top: + active.modules.find((e) => e.dy == max_y)?.architecture == + Architecture.VIRTUAL + ? 30 + : 0, + bottom: + active.modules.find((e) => e.dy == min_y)?.architecture == + Architecture.VIRTUAL + ? 30 + : 0, + }; return { min_x: min_x, min_y: min_y, @@ -170,8 +192,8 @@ grid-template-rows: repeat({rows}, auto); width: {width}px; height: {height}px;" > - {#each $runtime.modules as module (module.id)} - {@const props = calculateModuleProperties(module)} + {#each $runtime.modules as device (device.id)} + {@const props = calculateModuleProperties(device)}
- {#if module.architecture === Architecture.VIRTUAL} + {#if device.architecture === Architecture.VIRTUAL} - {#if typeof $devices.find((e) => e.dx === module.dx - 1 && e.dy === module.dy) === "undefined"} + {#if typeof $runtime.modules.find((e) => e.dx === device.dx - 1 && e.dy === device.dy) === "undefined"}
- handleAddModuleButtonClicked(module.dx - 1, module.dy)} + handleAddModuleButtonClicked(device.dx - 1, device.dy)} />
{/if} - {#if typeof $devices.find((e) => e.dx === module.dx + 1 && e.dy === module.dy) === "undefined"} + {#if typeof $runtime.modules.find((e) => e.dx === device.dx + 1 && e.dy === device.dy) === "undefined"}
- handleAddModuleButtonClicked(module.dx + 1, module.dy)} + handleAddModuleButtonClicked(device.dx + 1, device.dy)} />
{/if} - {#if typeof $devices.find((e) => e.dy === module.dy - 1 && e.dx === module.dx) === "undefined"} + {#if typeof $runtime.modules.find((e) => e.dy === device.dy - 1 && e.dx === device.dx) === "undefined"}
- handleAddModuleButtonClicked(module.dx, module.dy - 1)} + handleAddModuleButtonClicked(device.dx, device.dy - 1)} />
{/if} - {#if typeof $devices.find((e) => e.dy === module.dy + 1 && e.dx === module.dx) === "undefined"} + {#if typeof $runtime.modules.find((e) => e.dy === device.dy + 1 && e.dx === device.dx) === "undefined" || true}
- handleAddModuleButtonClicked(module.dx, module.dy + 1)} + handleAddModuleButtonClicked(device.dx, device.dy + 1)} />
{/if} {/if} diff --git a/src/renderer/main/panels/configuration/Configuration.svelte b/src/renderer/main/panels/configuration/Configuration.svelte index fe6c45281..0683a6012 100644 --- a/src/renderer/main/panels/configuration/Configuration.svelte +++ b/src/renderer/main/panels/configuration/Configuration.svelte @@ -25,7 +25,9 @@ $: runtime = $runtime_manager.active.runtime; - $: handleUserInputChange($user_input); + $: if ($runtime) { + handleUserInputChange($user_input); + } function handleUserInputChange(ui: UserInputValue) { page = runtime.findPage(ui.dx, ui.dy, ui.pagenumber); @@ -47,9 +49,12 @@ if (typeof element !== "undefined" && !element.isLoaded()) { console.log("LOADDD"); - element.load().catch((err) => { - console.error("Failed to load event:", err); - }); + element + .load() + .then((e) => {}) + .catch((err) => { + console.error("Failed to load event:", err); + }); } } diff --git a/src/renderer/main/user-interface/ActiveChanges.svelte b/src/renderer/main/user-interface/ActiveChanges.svelte index 1521e9cef..c17dfefc4 100644 --- a/src/renderer/main/user-interface/ActiveChanges.svelte +++ b/src/renderer/main/user-interface/ActiveChanges.svelte @@ -154,7 +154,10 @@ class={$$props.class} >
- + 1} + disabled={isChanges} + />
{changes} active changes diff --git a/src/renderer/main/user-interface/PortSelector.svelte b/src/renderer/main/user-interface/PortSelector.svelte index d7f251c40..9dfd1b01e 100644 --- a/src/renderer/main/user-interface/PortSelector.svelte +++ b/src/renderer/main/user-interface/PortSelector.svelte @@ -45,9 +45,9 @@ $: handleSelectedChange(selected); function handleSelectedChange(id: string) { + user_input.set(UserInput.defaultValue); const selected = runtime_manager.data.find((e) => e.runtime.id === id); runtime_manager.active = selected; - user_input.set(UserInput.defaultValue); } diff --git a/src/renderer/runtime/runtime-manager.store.ts b/src/renderer/runtime/runtime-manager.store.ts index 67526de4d..8757fe024 100644 --- a/src/renderer/runtime/runtime-manager.store.ts +++ b/src/renderer/runtime/runtime-manager.store.ts @@ -80,6 +80,12 @@ export class GridRuntimeManager implements Readable { }; this.update((store) => { + if (store.active?.runtime.virtual) { + this.killHeartbeat(store.active); + store.data = []; + store.active = undefined; + } + store.data.push(incoming); if (typeof store.active === "undefined") { store.active = incoming; @@ -95,8 +101,6 @@ export class GridRuntimeManager implements Readable { (e) => e.runtime.id === runtime.id ); - console.log(destroyed); - if (!destroyed) { throw new Error("Destroyed module is not found..."); } @@ -112,6 +116,11 @@ export class GridRuntimeManager implements Readable { store.active = store.data[0]; } + if (typeof store.active === "undefined") { + const virtual = this.createVirtual(); + this.add(virtual); + } + return store; }); } diff --git a/src/renderer/runtime/runtime.ts b/src/renderer/runtime/runtime.ts index 215dc90b7..cb67a08e9 100644 --- a/src/renderer/runtime/runtime.ts +++ b/src/renderer/runtime/runtime.ts @@ -29,7 +29,7 @@ import { Runtime } from "./string-table"; import { Grid } from "../lib/_utils"; import { GridConnection } from "../serialport/serialport"; import { GridRuntimeManager } from "./runtime-manager.store"; -import { user_input } from "./user-input.store"; +import { user_input, UserInput } from "./user-input.store"; type UUID = string; type LuaScript = string; @@ -870,17 +870,19 @@ export class GridEvent extends RuntimeNode { this.type, simulate ); + + this.loaded = true; const descr = await instruction.executeOn(runtime.connection); const script = descr.class_parameters.ACTIONSTRING; const actions = GridAction.parse(script); this.push(...actions); this.store(); - this.loaded = true; console.log("EVENT LOADED"); return Promise.resolve(); } catch (e) { + this.loaded = false; return Promise.reject(e); } } @@ -1543,6 +1545,7 @@ export class GridRuntime extends RuntimeNode { ); this.modules = [...this.modules, controller]; + user_input.set(UserInput.defaultValue); this.aliveModules.update((s) => { s.push({ id: controller.id, last: Date.now() }); return s; @@ -1730,6 +1733,7 @@ export class GridRuntime extends RuntimeNode { connection_simulator.createModule(dx, dy, moduleInfo.type); this.modules = [...this.modules, controller]; + user_input.set(UserInput.defaultValue); } create_module(header_param, heartbeat_class_param, virtual = false) { diff --git a/src/renderer/runtime/user-input.store.ts b/src/renderer/runtime/user-input.store.ts index 892659757..e4b2144c0 100644 --- a/src/renderer/runtime/user-input.store.ts +++ b/src/renderer/runtime/user-input.store.ts @@ -41,10 +41,24 @@ export class UserInput implements Writable { // Set the entire object public set(value: UserInputValue) { const runtime = get(runtime_manager).active.runtime; + console.log( + "YAY", + runtime.virtual, + typeof runtime, + value.dx, + value.dy, + runtime.findModule( + value.dx, + value.dy + //value.pagenumber, + //value.elementnumber + ) + ); const events = runtime .findElement(value.dx, value.dy, value.pagenumber, value.elementnumber) ?.events.map((e) => e.type); const closestEvent = Grid.getClosestEvent(events ?? [2], value.eventtype); + console.log(value, runtime, events, closestEvent); this._internal.set({ dx: value.dx, From 439666cb66ba6c9f93235d8e1e36e9a8af6a7ac7 Mon Sep 17 00:00:00 2001 From: unknown Date: Fri, 20 Dec 2024 18:56:31 +0100 Subject: [PATCH 8/8] Fine tuning and bug fixes --- .../overlays/ProfileLoadOverlay.svelte | 5 +- .../panels/configuration/Configuration.svelte | 1 - .../panels/configuration/EventPanel.svelte | 7 ++- .../main/user-interface/ActiveChanges.svelte | 5 +- .../main/user-interface/PortSelector.svelte | 1 - src/renderer/runtime/operations.ts | 1 - src/renderer/runtime/runtime-manager.store.ts | 7 ++- src/renderer/runtime/runtime.ts | 21 ++++--- src/renderer/runtime/user-input.store.ts | 58 ++++++++++--------- .../serialport/message-stream.store.ts | 15 +++-- src/renderer/serialport/serialport.ts | 7 ++- 11 files changed, 78 insertions(+), 50 deletions(-) diff --git a/src/renderer/main/grid-layout/grid-modules/overlays/ProfileLoadOverlay.svelte b/src/renderer/main/grid-layout/grid-modules/overlays/ProfileLoadOverlay.svelte index 6e24aaf6d..738532404 100644 --- a/src/renderer/main/grid-layout/grid-modules/overlays/ProfileLoadOverlay.svelte +++ b/src/renderer/main/grid-layout/grid-modules/overlays/ProfileLoadOverlay.svelte @@ -8,6 +8,8 @@ GridProfileData, } from "../../../../runtime/runtime.js"; import { loadProfile } from "../../../../runtime/operations"; + import { user_input } from "../../../../runtime/user-input.store"; + import { get } from "svelte/store"; export let device: GridModule; export let visible = false; @@ -27,7 +29,7 @@ } function handleProfileLoad(e) { - const page = device.parent as GridPage; + const page = device.findPage(get(user_input).pagenumber) as GridPage; const profile = GridProfileData.createFromCloudData($selectedConfigStore); state = LoadState.BUSY; @@ -36,6 +38,7 @@ state = LoadState.LOADED; }) .catch((e) => { + console.log(e); state = LoadState.READY; }); } diff --git a/src/renderer/main/panels/configuration/Configuration.svelte b/src/renderer/main/panels/configuration/Configuration.svelte index 0683a6012..2615e23f6 100644 --- a/src/renderer/main/panels/configuration/Configuration.svelte +++ b/src/renderer/main/panels/configuration/Configuration.svelte @@ -48,7 +48,6 @@ ); if (typeof element !== "undefined" && !element.isLoaded()) { - console.log("LOADDD"); element .load() .then((e) => {}) diff --git a/src/renderer/main/panels/configuration/EventPanel.svelte b/src/renderer/main/panels/configuration/EventPanel.svelte index 1245df86f..9bb2ae4fc 100644 --- a/src/renderer/main/panels/configuration/EventPanel.svelte +++ b/src/renderer/main/panels/configuration/EventPanel.svelte @@ -7,6 +7,7 @@ GridElement, ElementData, } from "../../../runtime/runtime"; + import { Grid } from "../../../lib/_utils"; export let element: GridElement; @@ -41,7 +42,11 @@ }) ); - selected = options.find((e) => e.value === ui.eventtype).value; + const closestEvent = Grid.getClosestEvent( + options.map((e) => e.value), + ui.eventtype + ); + selected = closestEvent; } $: handleSelectEvent(selected); diff --git a/src/renderer/main/user-interface/ActiveChanges.svelte b/src/renderer/main/user-interface/ActiveChanges.svelte index c17dfefc4..13a31e8e9 100644 --- a/src/renderer/main/user-interface/ActiveChanges.svelte +++ b/src/renderer/main/user-interface/ActiveChanges.svelte @@ -154,10 +154,7 @@ class={$$props.class} >
- 1} - disabled={isChanges} - /> + 1} />
{changes} active changes diff --git a/src/renderer/main/user-interface/PortSelector.svelte b/src/renderer/main/user-interface/PortSelector.svelte index 9dfd1b01e..0d60a3cf2 100644 --- a/src/renderer/main/user-interface/PortSelector.svelte +++ b/src/renderer/main/user-interface/PortSelector.svelte @@ -45,7 +45,6 @@ $: handleSelectedChange(selected); function handleSelectedChange(id: string) { - user_input.set(UserInput.defaultValue); const selected = runtime_manager.data.find((e) => e.runtime.id === id); runtime_manager.active = selected; } diff --git a/src/renderer/runtime/operations.ts b/src/renderer/runtime/operations.ts index eda945749..12cdbe326 100644 --- a/src/renderer/runtime/operations.ts +++ b/src/renderer/runtime/operations.ts @@ -355,7 +355,6 @@ export async function loadProfile( payload: {}, mandatory: false, }); - target .loadProfile(profile) .then(() => { diff --git a/src/renderer/runtime/runtime-manager.store.ts b/src/renderer/runtime/runtime-manager.store.ts index 8757fe024..535cd574b 100644 --- a/src/renderer/runtime/runtime-manager.store.ts +++ b/src/renderer/runtime/runtime-manager.store.ts @@ -277,7 +277,12 @@ export class GridRuntimeManager implements Readable { if (!runtime.isAlive(device)) { // TIMEOUT! let's remove the device - console.log("Heartbeat lost..."); + console.log( + "Heartbeat lost. DESTROY:", + device.dx, + device.dy, + device.type + ); runtime.destroy_module(device.dx, device.dy); } } diff --git a/src/renderer/runtime/runtime.ts b/src/renderer/runtime/runtime.ts index cb67a08e9..9f20f8d97 100644 --- a/src/renderer/runtime/runtime.ts +++ b/src/renderer/runtime/runtime.ts @@ -871,13 +871,19 @@ export class GridEvent extends RuntimeNode { simulate ); - this.loaded = true; const descr = await instruction.executeOn(runtime.connection); const script = descr.class_parameters.ACTIONSTRING; const actions = GridAction.parse(script); + + //Handling multiple load call at the same time + if (this.isLoaded()) { + return Promise.resolve(); + } + this.push(...actions); this.store(); + this.loaded = true; console.log("EVENT LOADED"); return Promise.resolve(); @@ -1524,8 +1530,10 @@ export class GridRuntime extends RuntimeNode { false ); // check if the firmware version of the newly connected device is acceptable - console.log("Incoming Device"); - console.log("Architecture", controller.architecture); + console.log( + "Incoming Device:", + `${controller.dx} ${controller.dy} ${controller.type} (${controller.architecture})` + ); const as = get(appSettings); const firmware_required = @@ -1545,7 +1553,6 @@ export class GridRuntime extends RuntimeNode { ); this.modules = [...this.modules, controller]; - user_input.set(UserInput.defaultValue); this.aliveModules.update((s) => { s.push({ id: controller.id, last: Date.now() }); return s; @@ -1733,7 +1740,6 @@ export class GridRuntime extends RuntimeNode { connection_simulator.createModule(dx, dy, moduleInfo.type); this.modules = [...this.modules, controller]; - user_input.set(UserInput.defaultValue); } create_module(header_param, heartbeat_class_param, virtual = false) { @@ -1798,11 +1804,10 @@ export class GridRuntime extends RuntimeNode { } destroy_module(dx, dy) { - console.log("DESTORY", dx, dy); user_input.module_destroy_handler(dx, dy); // remove the destroyed device from runtime - const removed = this.modules.find((e) => e.dx == dx && e.dy == dy); - this.modules = this.modules.filter((e) => e.dx !== dx && e.dy !== dy); + const removed = this.findModule(dx, dy); + this.modules = this.modules.filter((e) => e.id !== removed.id); removed.destroy(); this.aliveModules.update((s) => { diff --git a/src/renderer/runtime/user-input.store.ts b/src/renderer/runtime/user-input.store.ts index e4b2144c0..103c19f98 100644 --- a/src/renderer/runtime/user-input.store.ts +++ b/src/renderer/runtime/user-input.store.ts @@ -35,38 +35,42 @@ export class UserInput implements Writable { run: Subscriber, invalidate?: (value?: UserInputValue) => void ): Unsubscriber { + const runtime = get(runtime_manager).active.runtime; + const value = get(this._internal); + + const element = runtime.findElement( + value.dx, + value.dy, + value.pagenumber, + value.elementnumber + ); + + if (typeof element !== "undefined") { + const event = element.findEvent(value.eventtype); + + if (typeof event === "undefined") { + const closestEvent = Grid.getClosestEvent( + element.events.map((e) => e.type), + value.eventtype + ); + this.set({ + dx: value.dx, + dy: value.dy, + pagenumber: value.pagenumber, + elementnumber: value.elementnumber, + eventtype: closestEvent, + }); + } + } else { + this.set(UserInput.defaultValue); + } + return this._internal.subscribe(run, invalidate); } // Set the entire object public set(value: UserInputValue) { - const runtime = get(runtime_manager).active.runtime; - console.log( - "YAY", - runtime.virtual, - typeof runtime, - value.dx, - value.dy, - runtime.findModule( - value.dx, - value.dy - //value.pagenumber, - //value.elementnumber - ) - ); - const events = runtime - .findElement(value.dx, value.dy, value.pagenumber, value.elementnumber) - ?.events.map((e) => e.type); - const closestEvent = Grid.getClosestEvent(events ?? [2], value.eventtype); - console.log(value, runtime, events, closestEvent); - - this._internal.set({ - dx: value.dx, - dy: value.dy, - pagenumber: value.pagenumber, - elementnumber: value.elementnumber, - eventtype: closestEvent, - }); + this._internal.set(value); } // Update the object with a partial update function diff --git a/src/renderer/serialport/message-stream.store.ts b/src/renderer/serialport/message-stream.store.ts index 3dc605eda..b5165377a 100644 --- a/src/renderer/serialport/message-stream.store.ts +++ b/src/renderer/serialport/message-stream.store.ts @@ -16,6 +16,7 @@ import { PolyLineGraphData } from "../main/user-interface/PolyLineGraph.js"; import { WriteBuffer } from "../runtime/engine.store.js"; import { GridRuntime } from "../runtime/runtime.js"; import { user_input } from "../runtime/user-input.store"; +import { runtime_manager } from "../runtime/runtime-manager.store.js"; export const incoming_messages = writable([]); export function add_datapoint(key, value) { @@ -324,8 +325,12 @@ export class MessageStream { // update control element rotation this.update_elementPositionStore(class_descr); + const active = get(runtime_manager).active.runtime; // engine is enabled - if (get(this._buffer).length === 0) { + if ( + get(this._buffer).length === 0 && + this.runtime.id === get(active).id + ) { // update active element selection user_input.process_incoming_event_from_grid(class_descr); } @@ -362,9 +367,11 @@ export class MessageStream { class_descr.class_instr === "REPORT" ) { const ui = get(user_input); - if (typeof ui === "undefined") return; - - if (ui.pagenumber !== class_descr.class_parameters.PAGENUMBER) { + const active = get(runtime_manager).active.runtime; + if ( + ui.pagenumber !== class_descr.class_parameters.PAGENUMBER && + this.runtime.id === get(active).id + ) { user_input.set({ dx: ui.dx, dy: ui.dy, diff --git a/src/renderer/serialport/serialport.ts b/src/renderer/serialport/serialport.ts index d01cdd89c..67be71e22 100644 --- a/src/renderer/serialport/serialport.ts +++ b/src/renderer/serialport/serialport.ts @@ -245,7 +245,12 @@ export class GridConnectionManager implements Readable { grid.decode_packet_classes(class_array); if (class_array !== false) { - connection.buffer.messageStream.deliver_inbound(class_array); + try { + connection.buffer.messageStream.deliver_inbound(class_array); + } catch (e) { + //TODO: Serialize properly messageStream + console.error("MessageStrem works to fast (TODO):", e); + } } } }