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
+73 -8
View File
@@ -4,10 +4,30 @@ const dgram = require("node:dgram");
const fs = require("node:fs/promises");
const os = require("node:os");
const { pathToFileURL } = require("node:url");
const mqtt = require("mqtt");
const { SerialPort } = require("serialport");
const ModbusRTU = require("modbus-serial");
const knx = require("knx");
function loadOptionalDependency(packageName) {
try {
return {
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 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) {
target.statusListeners.add(handler);
handler({
@@ -120,7 +145,7 @@ const serialState = {
const modbusTcpState = {
...createStatusEmitter(),
client: new ModbusRTU(),
client: null,
connected: false,
signature: "",
config: {
@@ -134,7 +159,7 @@ const modbusTcpState = {
const modbusRtuState = {
...createStatusEmitter(),
client: new ModbusRTU(),
client: null,
connected: false,
signature: "",
config: {
@@ -635,6 +660,11 @@ function configureMqtt(nextConfig) {
return mqttState.config;
}
if (!mqtt) {
emitStatus(mqttState, dependencyError("MQTT", mqttDependency).message);
return mqttState.config;
}
const options = { reconnectPeriod: 1000 };
if (mqttState.config.clientId) options.clientId = mqttState.config.clientId;
if (mqttState.config.username) options.username = mqttState.config.username;
@@ -705,11 +735,18 @@ function closeSerialPort() {
async function configureSerial(nextConfig) {
serialState.config = normalizeSerialConfig(nextConfig);
await closeSerialPort();
if (serialState.config.enabled && !SerialPort) {
emitStatus(serialState, dependencyError("Serial", serialportDependency).message);
return serialState.config;
}
emitStatus(serialState, "");
return serialState.config;
}
async function ensureSerialPortOpen() {
if (!SerialPort) {
throw dependencyError("Serial", serialportDependency);
}
if (!serialState.config.enabled) {
throw new Error("Serial deaktiviert");
}
@@ -768,6 +805,9 @@ async function sendSerial(payload) {
}
async function listSerialPorts() {
if (!SerialPort) {
throw dependencyError("Serial", serialportDependency);
}
const ports = await SerialPort.list();
return ports.map((port) => ({
path: port.path,
@@ -799,9 +839,11 @@ async function listNetworkInterfaces() {
}
async function disconnectModbusClient(state) {
if (state.client?.isOpen) {
const client = state.client;
state.client = null;
if (client?.isOpen) {
try {
await state.client.close();
await client.close();
} catch (_error) {
// Best effort close.
}
@@ -813,6 +855,10 @@ async function disconnectModbusClient(state) {
async function configureModbusTcp(nextConfig) {
modbusTcpState.config = normalizeModbusTcpConfig(nextConfig);
await disconnectModbusClient(modbusTcpState);
if (modbusTcpState.config.enabled && !ModbusRTU) {
emitStatus(modbusTcpState, dependencyError("Modbus TCP", modbusDependency).message);
return modbusTcpState.config;
}
emitStatus(modbusTcpState, "");
return modbusTcpState.config;
}
@@ -820,11 +866,18 @@ async function configureModbusTcp(nextConfig) {
async function configureModbusRtu(nextConfig) {
modbusRtuState.config = normalizeModbusRtuConfig(nextConfig);
await disconnectModbusClient(modbusRtuState);
if (modbusRtuState.config.enabled && !ModbusRTU) {
emitStatus(modbusRtuState, dependencyError("Modbus RTU", modbusDependency).message);
return modbusRtuState.config;
}
emitStatus(modbusRtuState, "");
return modbusRtuState.config;
}
async function ensureModbusTcpConnected() {
if (!ModbusRTU) {
throw dependencyError("Modbus TCP", modbusDependency);
}
if (!modbusTcpState.config.enabled) {
throw new Error("Modbus TCP deaktiviert");
}
@@ -833,6 +886,7 @@ async function ensureModbusTcpConnected() {
return modbusTcpState.client;
}
await disconnectModbusClient(modbusTcpState);
modbusTcpState.client = new ModbusRTU();
await modbusTcpState.client.connectTCP(modbusTcpState.config.host, {
port: modbusTcpState.config.port,
timeout: modbusTcpState.config.timeout,
@@ -846,6 +900,9 @@ async function ensureModbusTcpConnected() {
}
async function ensureModbusRtuConnected() {
if (!ModbusRTU) {
throw dependencyError("Modbus RTU", modbusDependency);
}
if (!modbusRtuState.config.enabled) {
throw new Error("Modbus RTU deaktiviert");
}
@@ -857,6 +914,7 @@ async function ensureModbusRtuConnected() {
return modbusRtuState.client;
}
await disconnectModbusClient(modbusRtuState);
modbusRtuState.client = new ModbusRTU();
await modbusRtuState.client.connectRTUBuffered(modbusRtuState.config.path, {
baudRate: modbusRtuState.config.baudRate,
parity: modbusRtuState.config.parity,
@@ -929,11 +987,18 @@ function disconnectKnx() {
async function configureKnx(nextConfig) {
knxState.config = normalizeKnxConfig(nextConfig);
await disconnectKnx();
if (knxState.config.enabled && !knx) {
emitStatus(knxState, dependencyError("KNX", knxDependency).message);
return knxState.config;
}
emitStatus(knxState, "");
return knxState.config;
}
async function ensureKnxConnection() {
if (!knx) {
throw dependencyError("KNX", knxDependency);
}
if (!knxState.config.enabled) {
throw new Error("KNX deaktiviert");
}