diff --git a/dist/Time4Midi 0.1.0.exe b/dist/Time4Midi 0.2.0.exe similarity index 96% rename from dist/Time4Midi 0.1.0.exe rename to dist/Time4Midi 0.2.0.exe index a4b3a71..d8c3d97 100644 Binary files a/dist/Time4Midi 0.1.0.exe and b/dist/Time4Midi 0.2.0.exe differ diff --git a/dist/builder-debug.yml b/dist/builder-debug.yml index 721c31b..aac5826 100644 --- a/dist/builder-debug.yml +++ b/dist/builder-debug.yml @@ -23,4 +23,4 @@ x64: - JHArtworks2023_wh.png - package.json nsis: - script: "!include \"C:\\Projects\\Time4Midi\\node_modules\\app-builder-lib\\templates\\nsis\\include\\StdUtils.nsh\"\n!addincludedir \"C:\\Projects\\Time4Midi\\node_modules\\app-builder-lib\\templates\\nsis\\include\"\n!macro _isUpdated _a _b _t _f\n ${StdUtils.TestParameter} $R9 \"updated\"\n StrCmp \"$R9\" \"true\" `${_t}` `${_f}`\n!macroend\n!define isUpdated `\"\" isUpdated \"\"`\n\n!macro _isForceRun _a _b _t _f\n ${StdUtils.TestParameter} $R9 \"force-run\"\n StrCmp \"$R9\" \"true\" `${_t}` `${_f}`\n!macroend\n!define isForceRun `\"\" isForceRun \"\"`\n\n!macro _isKeepShortcuts _a _b _t _f\n ${StdUtils.TestParameter} $R9 \"keep-shortcuts\"\n StrCmp \"$R9\" \"true\" `${_t}` `${_f}`\n!macroend\n!define isKeepShortcuts `\"\" isKeepShortcuts \"\"`\n\n!macro _isNoDesktopShortcut _a _b _t _f\n ${StdUtils.TestParameter} $R9 \"no-desktop-shortcut\"\n StrCmp \"$R9\" \"true\" `${_t}` `${_f}`\n!macroend\n!define isNoDesktopShortcut `\"\" isNoDesktopShortcut \"\"`\n\n!macro _isDeleteAppData _a _b _t _f\n ${StdUtils.TestParameter} $R9 \"delete-app-data\"\n StrCmp \"$R9\" \"true\" `${_t}` `${_f}`\n!macroend\n!define isDeleteAppData `\"\" isDeleteAppData \"\"`\n\n!macro _isForAllUsers _a _b _t _f\n ${StdUtils.TestParameter} $R9 \"allusers\"\n StrCmp \"$R9\" \"true\" `${_t}` `${_f}`\n!macroend\n!define isForAllUsers `\"\" isForAllUsers \"\"`\n\n!macro _isForCurrentUser _a _b _t _f\n ${StdUtils.TestParameter} $R9 \"currentuser\"\n StrCmp \"$R9\" \"true\" `${_t}` `${_f}`\n!macroend\n!define isForCurrentUser `\"\" isForCurrentUser \"\"`\n\n!macro addLangs\n !insertmacro MUI_LANGUAGE \"English\"\n !insertmacro MUI_LANGUAGE \"German\"\n !insertmacro MUI_LANGUAGE \"French\"\n !insertmacro MUI_LANGUAGE \"SpanishInternational\"\n !insertmacro MUI_LANGUAGE \"SimpChinese\"\n !insertmacro MUI_LANGUAGE \"TradChinese\"\n !insertmacro MUI_LANGUAGE \"Japanese\"\n !insertmacro MUI_LANGUAGE \"Korean\"\n !insertmacro MUI_LANGUAGE \"Italian\"\n !insertmacro MUI_LANGUAGE \"Dutch\"\n !insertmacro MUI_LANGUAGE \"Danish\"\n !insertmacro MUI_LANGUAGE \"Swedish\"\n !insertmacro MUI_LANGUAGE \"Norwegian\"\n !insertmacro MUI_LANGUAGE \"Finnish\"\n !insertmacro MUI_LANGUAGE \"Russian\"\n !insertmacro MUI_LANGUAGE \"Portuguese\"\n !insertmacro MUI_LANGUAGE \"PortugueseBR\"\n !insertmacro MUI_LANGUAGE \"Polish\"\n !insertmacro MUI_LANGUAGE \"Ukrainian\"\n !insertmacro MUI_LANGUAGE \"Czech\"\n !insertmacro MUI_LANGUAGE \"Slovak\"\n !insertmacro MUI_LANGUAGE \"Hungarian\"\n !insertmacro MUI_LANGUAGE \"Arabic\"\n !insertmacro MUI_LANGUAGE \"Turkish\"\n !insertmacro MUI_LANGUAGE \"Thai\"\n !insertmacro MUI_LANGUAGE \"Vietnamese\"\n!macroend\n\n!include \"C:\\Users\\jh\\AppData\\Local\\Temp\\t-R6ALfD\\1-messages.nsh\"\n!addplugindir /x86-unicode \"C:\\Users\\jh\\AppData\\Local\\electron-builder\\Cache\\nsis-resources-3.4.1\\nsis-resources-3.4.1-2jx2y\\plugins\\x86-unicode\"\n\n!include \"common.nsh\"\n!include \"extractAppPackage.nsh\"\n\n# https://github.com/electron-userland/electron-builder/issues/3972#issuecomment-505171582\nCRCCheck off\nWindowIcon Off\nAutoCloseWindow True\nRequestExecutionLevel ${REQUEST_EXECUTION_LEVEL}\n\nFunction .onInit\n !ifndef SPLASH_IMAGE\n SetSilent silent\n !endif\n\n !insertmacro check64BitAndSetRegView\nFunctionEnd\n\nFunction .onGUIInit\n InitPluginsDir\n\n !ifdef SPLASH_IMAGE\n File /oname=$PLUGINSDIR\\splash.bmp \"${SPLASH_IMAGE}\"\n BgImage::SetBg $PLUGINSDIR\\splash.bmp\n BgImage::Redraw\n !endif\nFunctionEnd\n\nSection\n !ifdef SPLASH_IMAGE\n HideWindow\n !endif\n\n StrCpy $INSTDIR \"$PLUGINSDIR\\app\"\n !ifdef UNPACK_DIR_NAME\n StrCpy $INSTDIR \"$TEMP\\${UNPACK_DIR_NAME}\"\n !endif\n\n RMDir /r $INSTDIR\n SetOutPath $INSTDIR\n\n !ifdef APP_DIR_64\n !ifdef APP_DIR_ARM64\n !ifdef APP_DIR_32\n ${if} ${IsNativeARM64}\n File /r \"${APP_DIR_ARM64}\\*.*\"\n ${elseif} ${RunningX64}\n File /r \"${APP_DIR_64}\\*.*\"\n ${else}\n File /r \"${APP_DIR_32}\\*.*\"\n ${endIf}\n !else\n ${if} ${IsNativeARM64}\n File /r \"${APP_DIR_ARM64}\\*.*\"\n ${else}\n File /r \"${APP_DIR_64}\\*.*\"\n {endIf}\n !endif\n !else\n !ifdef APP_DIR_32\n ${if} ${RunningX64}\n File /r \"${APP_DIR_64}\\*.*\"\n ${else}\n File /r \"${APP_DIR_32}\\*.*\"\n ${endIf}\n !else\n File /r \"${APP_DIR_64}\\*.*\"\n !endif\n !endif\n !else\n !ifdef APP_DIR_32\n File /r \"${APP_DIR_32}\\*.*\"\n !else\n !insertmacro extractEmbeddedAppPackage\n !endif\n !endif\n\n System::Call 'Kernel32::SetEnvironmentVariable(t, t)i (\"PORTABLE_EXECUTABLE_DIR\", \"$EXEDIR\").r0'\n System::Call 'Kernel32::SetEnvironmentVariable(t, t)i (\"PORTABLE_EXECUTABLE_FILE\", \"$EXEPATH\").r0'\n System::Call 'Kernel32::SetEnvironmentVariable(t, t)i (\"PORTABLE_EXECUTABLE_APP_FILENAME\", \"${APP_FILENAME}\").r0'\n ${StdUtils.GetAllParameters} $R0 0\n\n !ifdef SPLASH_IMAGE\n BgImage::Destroy\n !endif\n\n\tExecWait \"$INSTDIR\\${APP_EXECUTABLE_FILENAME} $R0\" $0\n SetErrorLevel $0\n\n SetOutPath $EXEDIR\n\tRMDir /r $INSTDIR\nSectionEnd\n" + script: "!include \"C:\\Projects\\Time4Midi\\node_modules\\app-builder-lib\\templates\\nsis\\include\\StdUtils.nsh\"\n!addincludedir \"C:\\Projects\\Time4Midi\\node_modules\\app-builder-lib\\templates\\nsis\\include\"\n!macro _isUpdated _a _b _t _f\n ${StdUtils.TestParameter} $R9 \"updated\"\n StrCmp \"$R9\" \"true\" `${_t}` `${_f}`\n!macroend\n!define isUpdated `\"\" isUpdated \"\"`\n\n!macro _isForceRun _a _b _t _f\n ${StdUtils.TestParameter} $R9 \"force-run\"\n StrCmp \"$R9\" \"true\" `${_t}` `${_f}`\n!macroend\n!define isForceRun `\"\" isForceRun \"\"`\n\n!macro _isKeepShortcuts _a _b _t _f\n ${StdUtils.TestParameter} $R9 \"keep-shortcuts\"\n StrCmp \"$R9\" \"true\" `${_t}` `${_f}`\n!macroend\n!define isKeepShortcuts `\"\" isKeepShortcuts \"\"`\n\n!macro _isNoDesktopShortcut _a _b _t _f\n ${StdUtils.TestParameter} $R9 \"no-desktop-shortcut\"\n StrCmp \"$R9\" \"true\" `${_t}` `${_f}`\n!macroend\n!define isNoDesktopShortcut `\"\" isNoDesktopShortcut \"\"`\n\n!macro _isDeleteAppData _a _b _t _f\n ${StdUtils.TestParameter} $R9 \"delete-app-data\"\n StrCmp \"$R9\" \"true\" `${_t}` `${_f}`\n!macroend\n!define isDeleteAppData `\"\" isDeleteAppData \"\"`\n\n!macro _isForAllUsers _a _b _t _f\n ${StdUtils.TestParameter} $R9 \"allusers\"\n StrCmp \"$R9\" \"true\" `${_t}` `${_f}`\n!macroend\n!define isForAllUsers `\"\" isForAllUsers \"\"`\n\n!macro _isForCurrentUser _a _b _t _f\n ${StdUtils.TestParameter} $R9 \"currentuser\"\n StrCmp \"$R9\" \"true\" `${_t}` `${_f}`\n!macroend\n!define isForCurrentUser `\"\" isForCurrentUser \"\"`\n\n!macro addLangs\n !insertmacro MUI_LANGUAGE \"English\"\n !insertmacro MUI_LANGUAGE \"German\"\n !insertmacro MUI_LANGUAGE \"French\"\n !insertmacro MUI_LANGUAGE \"SpanishInternational\"\n !insertmacro MUI_LANGUAGE \"SimpChinese\"\n !insertmacro MUI_LANGUAGE \"TradChinese\"\n !insertmacro MUI_LANGUAGE \"Japanese\"\n !insertmacro MUI_LANGUAGE \"Korean\"\n !insertmacro MUI_LANGUAGE \"Italian\"\n !insertmacro MUI_LANGUAGE \"Dutch\"\n !insertmacro MUI_LANGUAGE \"Danish\"\n !insertmacro MUI_LANGUAGE \"Swedish\"\n !insertmacro MUI_LANGUAGE \"Norwegian\"\n !insertmacro MUI_LANGUAGE \"Finnish\"\n !insertmacro MUI_LANGUAGE \"Russian\"\n !insertmacro MUI_LANGUAGE \"Portuguese\"\n !insertmacro MUI_LANGUAGE \"PortugueseBR\"\n !insertmacro MUI_LANGUAGE \"Polish\"\n !insertmacro MUI_LANGUAGE \"Ukrainian\"\n !insertmacro MUI_LANGUAGE \"Czech\"\n !insertmacro MUI_LANGUAGE \"Slovak\"\n !insertmacro MUI_LANGUAGE \"Hungarian\"\n !insertmacro MUI_LANGUAGE \"Arabic\"\n !insertmacro MUI_LANGUAGE \"Turkish\"\n !insertmacro MUI_LANGUAGE \"Thai\"\n !insertmacro MUI_LANGUAGE \"Vietnamese\"\n!macroend\n\n!addplugindir /x86-unicode \"C:\\Users\\jh\\AppData\\Local\\electron-builder\\Cache\\nsis-resources-3.4.1\\nsis-resources-3.4.1-2jx2y\\plugins\\x86-unicode\"\n!include \"C:\\Users\\jh\\AppData\\Local\\Temp\\t-jBU1Xj\\1-messages.nsh\"\n\n!include \"common.nsh\"\n!include \"extractAppPackage.nsh\"\n\n# https://github.com/electron-userland/electron-builder/issues/3972#issuecomment-505171582\nCRCCheck off\nWindowIcon Off\nAutoCloseWindow True\nRequestExecutionLevel ${REQUEST_EXECUTION_LEVEL}\n\nFunction .onInit\n !ifndef SPLASH_IMAGE\n SetSilent silent\n !endif\n\n !insertmacro check64BitAndSetRegView\nFunctionEnd\n\nFunction .onGUIInit\n InitPluginsDir\n\n !ifdef SPLASH_IMAGE\n File /oname=$PLUGINSDIR\\splash.bmp \"${SPLASH_IMAGE}\"\n BgImage::SetBg $PLUGINSDIR\\splash.bmp\n BgImage::Redraw\n !endif\nFunctionEnd\n\nSection\n !ifdef SPLASH_IMAGE\n HideWindow\n !endif\n\n StrCpy $INSTDIR \"$PLUGINSDIR\\app\"\n !ifdef UNPACK_DIR_NAME\n StrCpy $INSTDIR \"$TEMP\\${UNPACK_DIR_NAME}\"\n !endif\n\n RMDir /r $INSTDIR\n SetOutPath $INSTDIR\n\n !ifdef APP_DIR_64\n !ifdef APP_DIR_ARM64\n !ifdef APP_DIR_32\n ${if} ${IsNativeARM64}\n File /r \"${APP_DIR_ARM64}\\*.*\"\n ${elseif} ${RunningX64}\n File /r \"${APP_DIR_64}\\*.*\"\n ${else}\n File /r \"${APP_DIR_32}\\*.*\"\n ${endIf}\n !else\n ${if} ${IsNativeARM64}\n File /r \"${APP_DIR_ARM64}\\*.*\"\n ${else}\n File /r \"${APP_DIR_64}\\*.*\"\n {endIf}\n !endif\n !else\n !ifdef APP_DIR_32\n ${if} ${RunningX64}\n File /r \"${APP_DIR_64}\\*.*\"\n ${else}\n File /r \"${APP_DIR_32}\\*.*\"\n ${endIf}\n !else\n File /r \"${APP_DIR_64}\\*.*\"\n !endif\n !endif\n !else\n !ifdef APP_DIR_32\n File /r \"${APP_DIR_32}\\*.*\"\n !else\n !insertmacro extractEmbeddedAppPackage\n !endif\n !endif\n\n System::Call 'Kernel32::SetEnvironmentVariable(t, t)i (\"PORTABLE_EXECUTABLE_DIR\", \"$EXEDIR\").r0'\n System::Call 'Kernel32::SetEnvironmentVariable(t, t)i (\"PORTABLE_EXECUTABLE_FILE\", \"$EXEPATH\").r0'\n System::Call 'Kernel32::SetEnvironmentVariable(t, t)i (\"PORTABLE_EXECUTABLE_APP_FILENAME\", \"${APP_FILENAME}\").r0'\n ${StdUtils.GetAllParameters} $R0 0\n\n !ifdef SPLASH_IMAGE\n BgImage::Destroy\n !endif\n\n\tExecWait \"$INSTDIR\\${APP_EXECUTABLE_FILENAME} $R0\" $0\n SetErrorLevel $0\n\n SetOutPath $EXEDIR\n\tRMDir /r $INSTDIR\nSectionEnd\n" diff --git a/dist/win-unpacked/Time4Midi.exe b/dist/win-unpacked/Time4Midi.exe index dfe4568..4b96055 100644 Binary files a/dist/win-unpacked/Time4Midi.exe and b/dist/win-unpacked/Time4Midi.exe differ diff --git a/dist/win-unpacked/resources/app.asar b/dist/win-unpacked/resources/app.asar index edee2fc..76c0e3e 100644 Binary files a/dist/win-unpacked/resources/app.asar and b/dist/win-unpacked/resources/app.asar differ diff --git a/package-lock.json b/package-lock.json index 9ed54de..0e88d29 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,14 +1,14 @@ { "name": "time4midi", - "version": "0.1.0", + "version": "0.2.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "time4midi", - "version": "0.1.0", + "version": "0.2.0", "dependencies": { - "knx": "^0.0.1", + "knx": "^2.5.4", "modbus-serial": "^8.0.25", "mqtt": "^5.10.4", "serialport": "^13.0.0" @@ -1171,6 +1171,27 @@ ], "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": { "version": "3.7.2", "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", @@ -2595,6 +2616,15 @@ "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": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", @@ -2755,9 +2785,26 @@ } }, "node_modules/knx": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/knx/-/knx-0.0.1.tgz", - "integrity": "sha512-6AikhvtVK/U4CKL0/+vrLXsg8e/CNTUxmdKHgeLgWDObb/ggdr8BXeB7xmPjmnA+1ieDS6liFIkuHAPAlD0k9Q==", + "version": "2.5.4", + "resolved": "https://registry.npmjs.org/knx/-/knx-2.5.4.tgz", + "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" }, "node_modules/lazy-val": { @@ -2771,9 +2818,17 @@ "version": "4.18.1", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz", "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==", - "dev": true, "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": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", @@ -2797,6 +2852,17 @@ "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": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/matcher/-/matcher-3.0.0.tgz", diff --git a/package.json b/package.json index e04971a..e9e6d87 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "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.", "main": "main.js", "author": "J.H. Artworks", @@ -13,7 +13,7 @@ "electron-builder": "^26.15.3" }, "dependencies": { - "knx": "^0.0.1", + "knx": "^2.5.4", "modbus-serial": "^8.0.25", "mqtt": "^5.10.4", "serialport": "^13.0.0" diff --git a/preload.js b/preload.js index 142c3f9..42bcf78 100644 --- a/preload.js +++ b/preload.js @@ -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"); }