Fix preload bridge crash and bump version to 0.2.0

This commit is contained in:
jhartworks
2026-06-16 21:23:48 +02:00
parent f1886a31c9
commit ad7ef9546b
7 changed files with 149 additions and 18 deletions
Binary file not shown.
+1 -1
View File
File diff suppressed because one or more lines are too long
BIN
View File
Binary file not shown.
Binary file not shown.
+73 -7
View File
@@ -1,14 +1,14 @@
{ {
"name": "time4midi", "name": "time4midi",
"version": "0.1.0", "version": "0.2.0",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "time4midi", "name": "time4midi",
"version": "0.1.0", "version": "0.2.0",
"dependencies": { "dependencies": {
"knx": "^0.0.1", "knx": "^2.5.4",
"modbus-serial": "^8.0.25", "modbus-serial": "^8.0.25",
"mqtt": "^5.10.4", "mqtt": "^5.10.4",
"serialport": "^13.0.0" "serialport": "^13.0.0"
@@ -1171,6 +1171,27 @@
], ],
"license": "MIT" "license": "MIT"
}, },
"node_modules/binary-parser": {
"version": "1.1.5",
"resolved": "https://registry.npmjs.org/binary-parser/-/binary-parser-1.1.5.tgz",
"integrity": "sha512-KsW0sfQ3e5Lpk8EVP44okdpOUhUlVaQ5HQ/qBKLpuMgEV29pTIfPvFobs9j4kp/4BqOSZ+kZpC7f4mleVonTRw==",
"license": "MIT"
},
"node_modules/binary-protocol": {
"version": "0.0.0",
"resolved": "https://registry.npmjs.org/binary-protocol/-/binary-protocol-0.0.0.tgz",
"integrity": "sha512-kcFqbqtZnZEGSY8Jh6dEO6VbxJDZu1tqJc3LODqADPHrDwHQwoTNdGTHW4sLhmNXeWUOZKjfRfopAu5xpRQbPw==",
"license": "MIT",
"dependencies": {
"bluebird": "~2.2.2"
}
},
"node_modules/binary-protocol/node_modules/bluebird": {
"version": "2.2.2",
"resolved": "https://registry.npmjs.org/bluebird/-/bluebird-2.2.2.tgz",
"integrity": "sha512-NqzuS2y44FmP++Ihv6/PgXpNRB+c282TKq0URBTQdA+EqPpR1Qu44QY8H8667LA39iOIaVHpmRPI4gpthYT8Ug==",
"license": "MIT"
},
"node_modules/bluebird": { "node_modules/bluebird": {
"version": "3.7.2", "version": "3.7.2",
"resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz",
@@ -2595,6 +2616,15 @@
"node": ">= 12" "node": ">= 12"
} }
}, },
"node_modules/ipaddr.js": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.2.0.tgz",
"integrity": "sha512-ku//LD7ie/m9UVGCm9KweBIIHP4mB0maNGvav6Hz778fQCNLQF7iZ+H/UuHuqmjJCHCpA5hw8hOeRKxZl8IlXw==",
"license": "MIT",
"engines": {
"node": ">= 0.10"
}
},
"node_modules/is-fullwidth-code-point": { "node_modules/is-fullwidth-code-point": {
"version": "3.0.0", "version": "3.0.0",
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
@@ -2755,9 +2785,26 @@
} }
}, },
"node_modules/knx": { "node_modules/knx": {
"version": "0.0.1", "version": "2.5.4",
"resolved": "https://registry.npmjs.org/knx/-/knx-0.0.1.tgz", "resolved": "https://registry.npmjs.org/knx/-/knx-2.5.4.tgz",
"integrity": "sha512-6AikhvtVK/U4CKL0/+vrLXsg8e/CNTUxmdKHgeLgWDObb/ggdr8BXeB7xmPjmnA+1ieDS6liFIkuHAPAlD0k9Q==", "integrity": "sha512-h+WMbx2NPgCL1Vr4X8xAsTV9E+72XjPviKRv67iJLomapENO25ZsRGq8egvqUJwtnsordJGOxxUA4o1jMOzQYw==",
"license": "MIT",
"dependencies": {
"@types/node": "^6.14.4",
"binary-parser": "1.1.5",
"binary-protocol": "0.0.0",
"ipaddr.js": "1.2.0",
"log-driver": "1.2.7",
"machina": "^4.0.2"
},
"engines": {
"node": ">=6"
}
},
"node_modules/knx/node_modules/@types/node": {
"version": "6.14.13",
"resolved": "https://registry.npmjs.org/@types/node/-/node-6.14.13.tgz",
"integrity": "sha512-J1F0XJ/9zxlZel5ZlbeSuHW2OpabrUAqpFuC2sm2I3by8sERQ8+KCjNKUcq8QHuzpGMWiJpo9ZxeHrqrP2KzQw==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/lazy-val": { "node_modules/lazy-val": {
@@ -2771,9 +2818,17 @@
"version": "4.18.1", "version": "4.18.1",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz",
"integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==", "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==",
"dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/log-driver": {
"version": "1.2.7",
"resolved": "https://registry.npmjs.org/log-driver/-/log-driver-1.2.7.tgz",
"integrity": "sha512-U7KCmLdqsGHBLeWqYlFA0V0Sl6P08EE1ZrmA9cxjUE0WVqT9qnyVDPz1kzpFEP0jdJuFnasWIfSd7fsaNXkpbg==",
"license": "ISC",
"engines": {
"node": ">=0.8.6"
}
},
"node_modules/lowercase-keys": { "node_modules/lowercase-keys": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz",
@@ -2797,6 +2852,17 @@
"node": ">=10" "node": ">=10"
} }
}, },
"node_modules/machina": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/machina/-/machina-4.0.2.tgz",
"integrity": "sha512-OOlFrW1rd783S6tF36v5Ie/TM64gfvSl9kYLWL2cPA31J71HHWW3XrgSe1BZSFAPkh8532CMJMLv/s9L2aopiA==",
"dependencies": {
"lodash": "^4.17.5"
},
"engines": {
"node": ">=0.4.0"
}
},
"node_modules/matcher": { "node_modules/matcher": {
"version": "3.0.0", "version": "3.0.0",
"resolved": "https://registry.npmjs.org/matcher/-/matcher-3.0.0.tgz", "resolved": "https://registry.npmjs.org/matcher/-/matcher-3.0.0.tgz",
+2 -2
View File
@@ -1,6 +1,6 @@
{ {
"name": "time4midi", "name": "time4midi",
"version": "0.1.0", "version": "0.2.0",
"description": "Cross-platform timeline editor for MIDI notes and Art-Net values, slaved to incoming timecode.", "description": "Cross-platform timeline editor for MIDI notes and Art-Net values, slaved to incoming timecode.",
"main": "main.js", "main": "main.js",
"author": "J.H. Artworks", "author": "J.H. Artworks",
@@ -13,7 +13,7 @@
"electron-builder": "^26.15.3" "electron-builder": "^26.15.3"
}, },
"dependencies": { "dependencies": {
"knx": "^0.0.1", "knx": "^2.5.4",
"modbus-serial": "^8.0.25", "modbus-serial": "^8.0.25",
"mqtt": "^5.10.4", "mqtt": "^5.10.4",
"serialport": "^13.0.0" "serialport": "^13.0.0"
+73 -8
View File
@@ -4,10 +4,30 @@ const dgram = require("node:dgram");
const fs = require("node:fs/promises"); const fs = require("node:fs/promises");
const os = require("node:os"); const os = require("node:os");
const { pathToFileURL } = require("node:url"); const { pathToFileURL } = require("node:url");
const mqtt = require("mqtt"); function loadOptionalDependency(packageName) {
const { SerialPort } = require("serialport"); try {
const ModbusRTU = require("modbus-serial"); return {
const knx = require("knx"); module: require(packageName),
error: null
};
} catch (error) {
console.error(`[time4midi] Optional dependency "${packageName}" failed to load:`, error);
return {
module: null,
error
};
}
}
const mqttDependency = loadOptionalDependency("mqtt");
const serialportDependency = loadOptionalDependency("serialport");
const modbusDependency = loadOptionalDependency("modbus-serial");
const knxDependency = loadOptionalDependency("knx");
const mqtt = mqttDependency.module;
const SerialPort = serialportDependency.module?.SerialPort || null;
const ModbusRTU = modbusDependency.module || null;
const knx = knxDependency.module || null;
const ARTNET_PORT = 6454; const ARTNET_PORT = 6454;
const SACN_PORT = 5568; const SACN_PORT = 5568;
@@ -34,6 +54,11 @@ function emitStatus(target, message) {
} }
} }
function dependencyError(label, dependency) {
const detail = dependency.error?.message || "Modul nicht verfuegbar";
return new Error(`${label} Modul nicht verfuegbar: ${detail}`);
}
function addStatusApi(target, handler) { function addStatusApi(target, handler) {
target.statusListeners.add(handler); target.statusListeners.add(handler);
handler({ handler({
@@ -120,7 +145,7 @@ const serialState = {
const modbusTcpState = { const modbusTcpState = {
...createStatusEmitter(), ...createStatusEmitter(),
client: new ModbusRTU(), client: null,
connected: false, connected: false,
signature: "", signature: "",
config: { config: {
@@ -134,7 +159,7 @@ const modbusTcpState = {
const modbusRtuState = { const modbusRtuState = {
...createStatusEmitter(), ...createStatusEmitter(),
client: new ModbusRTU(), client: null,
connected: false, connected: false,
signature: "", signature: "",
config: { config: {
@@ -635,6 +660,11 @@ function configureMqtt(nextConfig) {
return mqttState.config; return mqttState.config;
} }
if (!mqtt) {
emitStatus(mqttState, dependencyError("MQTT", mqttDependency).message);
return mqttState.config;
}
const options = { reconnectPeriod: 1000 }; const options = { reconnectPeriod: 1000 };
if (mqttState.config.clientId) options.clientId = mqttState.config.clientId; if (mqttState.config.clientId) options.clientId = mqttState.config.clientId;
if (mqttState.config.username) options.username = mqttState.config.username; if (mqttState.config.username) options.username = mqttState.config.username;
@@ -705,11 +735,18 @@ function closeSerialPort() {
async function configureSerial(nextConfig) { async function configureSerial(nextConfig) {
serialState.config = normalizeSerialConfig(nextConfig); serialState.config = normalizeSerialConfig(nextConfig);
await closeSerialPort(); await closeSerialPort();
if (serialState.config.enabled && !SerialPort) {
emitStatus(serialState, dependencyError("Serial", serialportDependency).message);
return serialState.config;
}
emitStatus(serialState, ""); emitStatus(serialState, "");
return serialState.config; return serialState.config;
} }
async function ensureSerialPortOpen() { async function ensureSerialPortOpen() {
if (!SerialPort) {
throw dependencyError("Serial", serialportDependency);
}
if (!serialState.config.enabled) { if (!serialState.config.enabled) {
throw new Error("Serial deaktiviert"); throw new Error("Serial deaktiviert");
} }
@@ -768,6 +805,9 @@ async function sendSerial(payload) {
} }
async function listSerialPorts() { async function listSerialPorts() {
if (!SerialPort) {
throw dependencyError("Serial", serialportDependency);
}
const ports = await SerialPort.list(); const ports = await SerialPort.list();
return ports.map((port) => ({ return ports.map((port) => ({
path: port.path, path: port.path,
@@ -799,9 +839,11 @@ async function listNetworkInterfaces() {
} }
async function disconnectModbusClient(state) { async function disconnectModbusClient(state) {
if (state.client?.isOpen) { const client = state.client;
state.client = null;
if (client?.isOpen) {
try { try {
await state.client.close(); await client.close();
} catch (_error) { } catch (_error) {
// Best effort close. // Best effort close.
} }
@@ -813,6 +855,10 @@ async function disconnectModbusClient(state) {
async function configureModbusTcp(nextConfig) { async function configureModbusTcp(nextConfig) {
modbusTcpState.config = normalizeModbusTcpConfig(nextConfig); modbusTcpState.config = normalizeModbusTcpConfig(nextConfig);
await disconnectModbusClient(modbusTcpState); await disconnectModbusClient(modbusTcpState);
if (modbusTcpState.config.enabled && !ModbusRTU) {
emitStatus(modbusTcpState, dependencyError("Modbus TCP", modbusDependency).message);
return modbusTcpState.config;
}
emitStatus(modbusTcpState, ""); emitStatus(modbusTcpState, "");
return modbusTcpState.config; return modbusTcpState.config;
} }
@@ -820,11 +866,18 @@ async function configureModbusTcp(nextConfig) {
async function configureModbusRtu(nextConfig) { async function configureModbusRtu(nextConfig) {
modbusRtuState.config = normalizeModbusRtuConfig(nextConfig); modbusRtuState.config = normalizeModbusRtuConfig(nextConfig);
await disconnectModbusClient(modbusRtuState); await disconnectModbusClient(modbusRtuState);
if (modbusRtuState.config.enabled && !ModbusRTU) {
emitStatus(modbusRtuState, dependencyError("Modbus RTU", modbusDependency).message);
return modbusRtuState.config;
}
emitStatus(modbusRtuState, ""); emitStatus(modbusRtuState, "");
return modbusRtuState.config; return modbusRtuState.config;
} }
async function ensureModbusTcpConnected() { async function ensureModbusTcpConnected() {
if (!ModbusRTU) {
throw dependencyError("Modbus TCP", modbusDependency);
}
if (!modbusTcpState.config.enabled) { if (!modbusTcpState.config.enabled) {
throw new Error("Modbus TCP deaktiviert"); throw new Error("Modbus TCP deaktiviert");
} }
@@ -833,6 +886,7 @@ async function ensureModbusTcpConnected() {
return modbusTcpState.client; return modbusTcpState.client;
} }
await disconnectModbusClient(modbusTcpState); await disconnectModbusClient(modbusTcpState);
modbusTcpState.client = new ModbusRTU();
await modbusTcpState.client.connectTCP(modbusTcpState.config.host, { await modbusTcpState.client.connectTCP(modbusTcpState.config.host, {
port: modbusTcpState.config.port, port: modbusTcpState.config.port,
timeout: modbusTcpState.config.timeout, timeout: modbusTcpState.config.timeout,
@@ -846,6 +900,9 @@ async function ensureModbusTcpConnected() {
} }
async function ensureModbusRtuConnected() { async function ensureModbusRtuConnected() {
if (!ModbusRTU) {
throw dependencyError("Modbus RTU", modbusDependency);
}
if (!modbusRtuState.config.enabled) { if (!modbusRtuState.config.enabled) {
throw new Error("Modbus RTU deaktiviert"); throw new Error("Modbus RTU deaktiviert");
} }
@@ -857,6 +914,7 @@ async function ensureModbusRtuConnected() {
return modbusRtuState.client; return modbusRtuState.client;
} }
await disconnectModbusClient(modbusRtuState); await disconnectModbusClient(modbusRtuState);
modbusRtuState.client = new ModbusRTU();
await modbusRtuState.client.connectRTUBuffered(modbusRtuState.config.path, { await modbusRtuState.client.connectRTUBuffered(modbusRtuState.config.path, {
baudRate: modbusRtuState.config.baudRate, baudRate: modbusRtuState.config.baudRate,
parity: modbusRtuState.config.parity, parity: modbusRtuState.config.parity,
@@ -929,11 +987,18 @@ function disconnectKnx() {
async function configureKnx(nextConfig) { async function configureKnx(nextConfig) {
knxState.config = normalizeKnxConfig(nextConfig); knxState.config = normalizeKnxConfig(nextConfig);
await disconnectKnx(); await disconnectKnx();
if (knxState.config.enabled && !knx) {
emitStatus(knxState, dependencyError("KNX", knxDependency).message);
return knxState.config;
}
emitStatus(knxState, ""); emitStatus(knxState, "");
return knxState.config; return knxState.config;
} }
async function ensureKnxConnection() { async function ensureKnxConnection() {
if (!knx) {
throw dependencyError("KNX", knxDependency);
}
if (!knxState.config.enabled) { if (!knxState.config.enabled) {
throw new Error("KNX deaktiviert"); throw new Error("KNX deaktiviert");
} }