Refactor code structure for improved readability and maintainability
This commit is contained in:
+180
-37
@@ -1,6 +1,9 @@
|
||||
const { contextBridge, ipcRenderer } = require("electron");
|
||||
const crypto = require("node:crypto");
|
||||
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");
|
||||
@@ -44,11 +47,13 @@ function addStatusApi(target, handler) {
|
||||
|
||||
const artnet = {
|
||||
...createStatusEmitter(),
|
||||
sendSocket: dgram.createSocket("udp4"),
|
||||
sendSocket: null,
|
||||
sendSocketSignature: "",
|
||||
listenSocket: null,
|
||||
listeners: new Set(),
|
||||
config: {
|
||||
enabled: true,
|
||||
localAddress: "",
|
||||
host: "255.255.255.255",
|
||||
port: ARTNET_PORT,
|
||||
broadcast: true,
|
||||
@@ -59,9 +64,11 @@ const artnet = {
|
||||
|
||||
const sacn = {
|
||||
...createStatusEmitter(),
|
||||
socket: dgram.createSocket("udp4"),
|
||||
socket: null,
|
||||
socketSignature: "",
|
||||
config: {
|
||||
enabled: false,
|
||||
localAddress: "",
|
||||
host: "",
|
||||
port: SACN_PORT,
|
||||
multicast: true,
|
||||
@@ -73,9 +80,11 @@ const sacn = {
|
||||
|
||||
const osc = {
|
||||
...createStatusEmitter(),
|
||||
socket: dgram.createSocket("udp4"),
|
||||
socket: null,
|
||||
socketSignature: "",
|
||||
config: {
|
||||
enabled: false,
|
||||
localAddress: "",
|
||||
host: "127.0.0.1",
|
||||
port: 8000
|
||||
}
|
||||
@@ -87,6 +96,7 @@ const mqttState = {
|
||||
connected: false,
|
||||
config: {
|
||||
enabled: false,
|
||||
localAddress: "",
|
||||
brokerUrl: "mqtt://127.0.0.1:1883",
|
||||
username: "",
|
||||
password: "",
|
||||
@@ -115,6 +125,7 @@ const modbusTcpState = {
|
||||
signature: "",
|
||||
config: {
|
||||
enabled: false,
|
||||
localAddress: "",
|
||||
host: "127.0.0.1",
|
||||
port: 502,
|
||||
timeout: 1000
|
||||
@@ -144,6 +155,7 @@ const knxState = {
|
||||
connected: false,
|
||||
config: {
|
||||
enabled: false,
|
||||
interface: "",
|
||||
ipAddr: "224.0.23.12",
|
||||
ipPort: KNX_PORT,
|
||||
physAddr: "15.15.15",
|
||||
@@ -154,6 +166,7 @@ const knxState = {
|
||||
function normalizeArtNetConfig(nextConfig = {}) {
|
||||
return {
|
||||
enabled: nextConfig.enabled !== false,
|
||||
localAddress: String(nextConfig.localAddress || "").trim(),
|
||||
host: nextConfig.host || "255.255.255.255",
|
||||
port: Number.isFinite(Number(nextConfig.port)) ? Number(nextConfig.port) : ARTNET_PORT,
|
||||
broadcast: Boolean(nextConfig.broadcast),
|
||||
@@ -166,6 +179,7 @@ function normalizeArtNetConfig(nextConfig = {}) {
|
||||
function normalizeSacnConfig(nextConfig = {}) {
|
||||
return {
|
||||
enabled: Boolean(nextConfig.enabled),
|
||||
localAddress: String(nextConfig.localAddress || "").trim(),
|
||||
host: String(nextConfig.host || ""),
|
||||
port: Number.isFinite(Number(nextConfig.port)) ? Number(nextConfig.port) : SACN_PORT,
|
||||
multicast: nextConfig.multicast !== false,
|
||||
@@ -176,6 +190,7 @@ function normalizeSacnConfig(nextConfig = {}) {
|
||||
function normalizeOscConfig(nextConfig = {}) {
|
||||
return {
|
||||
enabled: Boolean(nextConfig.enabled),
|
||||
localAddress: String(nextConfig.localAddress || "").trim(),
|
||||
host: nextConfig.host || "127.0.0.1",
|
||||
port: Number.isFinite(Number(nextConfig.port)) ? Number(nextConfig.port) : 8000
|
||||
};
|
||||
@@ -184,6 +199,7 @@ function normalizeOscConfig(nextConfig = {}) {
|
||||
function normalizeMqttConfig(nextConfig = {}) {
|
||||
return {
|
||||
enabled: Boolean(nextConfig.enabled),
|
||||
localAddress: String(nextConfig.localAddress || "").trim(),
|
||||
brokerUrl: String(nextConfig.brokerUrl || "").trim() || "mqtt://127.0.0.1:1883",
|
||||
username: String(nextConfig.username || ""),
|
||||
password: String(nextConfig.password || ""),
|
||||
@@ -206,6 +222,7 @@ function normalizeSerialConfig(nextConfig = {}) {
|
||||
function normalizeModbusTcpConfig(nextConfig = {}) {
|
||||
return {
|
||||
enabled: Boolean(nextConfig.enabled),
|
||||
localAddress: String(nextConfig.localAddress || "").trim(),
|
||||
host: String(nextConfig.host || "127.0.0.1"),
|
||||
port: Number.isFinite(Number(nextConfig.port)) ? Number(nextConfig.port) : 502,
|
||||
timeout: Number.isFinite(Number(nextConfig.timeout)) ? Number(nextConfig.timeout) : 1000
|
||||
@@ -227,6 +244,7 @@ function normalizeModbusRtuConfig(nextConfig = {}) {
|
||||
function normalizeKnxConfig(nextConfig = {}) {
|
||||
return {
|
||||
enabled: Boolean(nextConfig.enabled),
|
||||
interface: String(nextConfig.interface || "").trim(),
|
||||
ipAddr: String(nextConfig.ipAddr || "224.0.23.12"),
|
||||
ipPort: Number.isFinite(Number(nextConfig.ipPort)) ? Number(nextConfig.ipPort) : KNX_PORT,
|
||||
physAddr: String(nextConfig.physAddr || "15.15.15"),
|
||||
@@ -234,6 +252,79 @@ function normalizeKnxConfig(nextConfig = {}) {
|
||||
};
|
||||
}
|
||||
|
||||
function createUdpSocket(target, label) {
|
||||
const socket = dgram.createSocket("udp4");
|
||||
socket.on("error", (error) => {
|
||||
emitStatus(target, `${label} Socket Fehler: ${error.message}`);
|
||||
});
|
||||
return socket;
|
||||
}
|
||||
|
||||
function closeUdpSocket(target, key, signatureKey) {
|
||||
if (target[key]) {
|
||||
try {
|
||||
target[key].close();
|
||||
} catch (_error) {
|
||||
// Ignore close races while reconfiguring sockets.
|
||||
}
|
||||
}
|
||||
target[key] = null;
|
||||
target[signatureKey] = "";
|
||||
}
|
||||
|
||||
async function ensureUdpSocket(target, key, signatureKey, label, config, options = {}) {
|
||||
const signature = `${config.localAddress || ""}|${options.broadcast ? "1" : "0"}`;
|
||||
if (target[key] && target[signatureKey] === signature) {
|
||||
return target[key];
|
||||
}
|
||||
|
||||
closeUdpSocket(target, key, signatureKey);
|
||||
const socket = createUdpSocket(target, label);
|
||||
target[key] = socket;
|
||||
target[signatureKey] = signature;
|
||||
|
||||
if (options.broadcast) {
|
||||
socket.setBroadcast(true);
|
||||
}
|
||||
|
||||
if (!config.localAddress) {
|
||||
return socket;
|
||||
}
|
||||
|
||||
await new Promise((resolve, reject) => {
|
||||
let settled = false;
|
||||
const handleError = (error) => {
|
||||
if (settled) {
|
||||
return;
|
||||
}
|
||||
settled = true;
|
||||
socket.off("listening", handleListening);
|
||||
reject(error);
|
||||
};
|
||||
const handleListening = () => {
|
||||
if (settled) {
|
||||
return;
|
||||
}
|
||||
settled = true;
|
||||
socket.off("error", handleError);
|
||||
resolve(true);
|
||||
};
|
||||
|
||||
socket.once("error", handleError);
|
||||
socket.once("listening", handleListening);
|
||||
try {
|
||||
socket.bind(0, config.localAddress);
|
||||
} catch (error) {
|
||||
socket.off("error", handleError);
|
||||
socket.off("listening", handleListening);
|
||||
reject(error);
|
||||
}
|
||||
});
|
||||
|
||||
emitStatus(target, "");
|
||||
return socket;
|
||||
}
|
||||
|
||||
function closeArtNetListener() {
|
||||
if (artnet.listenSocket) {
|
||||
try {
|
||||
@@ -273,10 +364,7 @@ function ensureArtNetListener() {
|
||||
return;
|
||||
}
|
||||
|
||||
artnet.listenSocket = dgram.createSocket("udp4");
|
||||
artnet.listenSocket.on("error", (error) => {
|
||||
emitStatus(artnet, `Art-Net Listen Fehler: ${error.message}`);
|
||||
});
|
||||
artnet.listenSocket = createUdpSocket(artnet, "Art-Net");
|
||||
artnet.listenSocket.on("listening", () => {
|
||||
emitStatus(artnet, "");
|
||||
});
|
||||
@@ -289,7 +377,11 @@ function ensureArtNetListener() {
|
||||
});
|
||||
|
||||
try {
|
||||
artnet.listenSocket.bind(artnet.config.timecodeListenPort);
|
||||
if (artnet.config.localAddress) {
|
||||
artnet.listenSocket.bind(artnet.config.timecodeListenPort, artnet.config.localAddress);
|
||||
} else {
|
||||
artnet.listenSocket.bind(artnet.config.timecodeListenPort);
|
||||
}
|
||||
} catch (error) {
|
||||
emitStatus(artnet, `Art-Net Listen Fehler: ${error.message}`);
|
||||
}
|
||||
@@ -297,32 +389,40 @@ function ensureArtNetListener() {
|
||||
|
||||
function configureArtNet(nextConfig) {
|
||||
const previousPort = artnet.config.timecodeListenPort;
|
||||
const previousLocalAddress = artnet.config.localAddress;
|
||||
artnet.config = normalizeArtNetConfig(nextConfig);
|
||||
|
||||
try {
|
||||
artnet.sendSocket.setBroadcast(artnet.config.broadcast);
|
||||
} catch (error) {
|
||||
emitStatus(artnet, `Art-Net Konfig Fehler: ${error.message}`);
|
||||
}
|
||||
|
||||
if (!artnet.config.enabled) {
|
||||
closeArtNetListener();
|
||||
closeUdpSocket(artnet, "sendSocket", "sendSocketSignature");
|
||||
emitStatus(artnet, "");
|
||||
return artnet.config;
|
||||
}
|
||||
|
||||
if (previousPort !== artnet.config.timecodeListenPort) {
|
||||
if (
|
||||
previousPort !== artnet.config.timecodeListenPort
|
||||
|| previousLocalAddress !== artnet.config.localAddress
|
||||
) {
|
||||
closeArtNetListener();
|
||||
}
|
||||
if (
|
||||
artnet.sendSocket
|
||||
&& (
|
||||
previousLocalAddress !== artnet.config.localAddress
|
||||
|| Boolean(artnet.config.broadcast) !== artnet.sendSocketSignature.endsWith("|1")
|
||||
)
|
||||
) {
|
||||
closeUdpSocket(artnet, "sendSocket", "sendSocketSignature");
|
||||
}
|
||||
if (artnet.listeners.size > 0) {
|
||||
ensureArtNetListener();
|
||||
}
|
||||
return artnet.config;
|
||||
}
|
||||
|
||||
function sendArtNetDmx(payload) {
|
||||
async function sendArtNetDmx(payload) {
|
||||
if (!artnet.config.enabled) {
|
||||
return Promise.resolve(false);
|
||||
return false;
|
||||
}
|
||||
|
||||
const universe = Math.max(0, Math.min(32767, Number(payload.universe) || 0));
|
||||
@@ -344,8 +444,17 @@ function sendArtNetDmx(payload) {
|
||||
}
|
||||
artnet.sequence = artnet.sequence >= 255 ? 1 : artnet.sequence + 1;
|
||||
|
||||
const socket = await ensureUdpSocket(
|
||||
artnet,
|
||||
"sendSocket",
|
||||
"sendSocketSignature",
|
||||
"Art-Net",
|
||||
artnet.config,
|
||||
{ broadcast: artnet.config.broadcast }
|
||||
);
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
artnet.sendSocket.send(packet, 0, packet.length, artnet.config.port, artnet.config.host, (error) => {
|
||||
socket.send(packet, 0, packet.length, artnet.config.port, artnet.config.host, (error) => {
|
||||
if (error) {
|
||||
emitStatus(artnet, `Art-Net Send Fehler: ${error.message}`);
|
||||
reject(error);
|
||||
@@ -358,7 +467,11 @@ function sendArtNetDmx(payload) {
|
||||
}
|
||||
|
||||
function configureOsc(nextConfig) {
|
||||
const previousLocalAddress = osc.config.localAddress;
|
||||
osc.config = normalizeOscConfig(nextConfig);
|
||||
if (previousLocalAddress !== osc.config.localAddress) {
|
||||
closeUdpSocket(osc, "socket", "socketSignature");
|
||||
}
|
||||
emitStatus(osc, "");
|
||||
return osc.config;
|
||||
}
|
||||
@@ -383,9 +496,9 @@ function encodeOscArgument(value, argType) {
|
||||
return { tag: "f", data };
|
||||
}
|
||||
|
||||
function sendOsc(payload) {
|
||||
async function sendOsc(payload) {
|
||||
if (!osc.config.enabled) {
|
||||
return Promise.resolve(false);
|
||||
return false;
|
||||
}
|
||||
const address = String(payload.address || "/time4midi");
|
||||
const normalizedAddress = address.startsWith("/") ? address : `/${address}`;
|
||||
@@ -395,8 +508,9 @@ function sendOsc(payload) {
|
||||
encodeOscString(`,${argument.tag}`),
|
||||
argument.data
|
||||
]);
|
||||
const socket = await ensureUdpSocket(osc, "socket", "socketSignature", "OSC", osc.config);
|
||||
return new Promise((resolve, reject) => {
|
||||
osc.socket.send(packet, 0, packet.length, osc.config.port, osc.config.host, (error) => {
|
||||
socket.send(packet, 0, packet.length, osc.config.port, osc.config.host, (error) => {
|
||||
if (error) {
|
||||
emitStatus(osc, `OSC Send Fehler: ${error.message}`);
|
||||
reject(error);
|
||||
@@ -409,7 +523,11 @@ function sendOsc(payload) {
|
||||
}
|
||||
|
||||
function configureSacn(nextConfig) {
|
||||
const previousLocalAddress = sacn.config.localAddress;
|
||||
sacn.config = normalizeSacnConfig(nextConfig);
|
||||
if (previousLocalAddress !== sacn.config.localAddress) {
|
||||
closeUdpSocket(sacn, "socket", "socketSignature");
|
||||
}
|
||||
emitStatus(sacn, "");
|
||||
return sacn.config;
|
||||
}
|
||||
@@ -437,9 +555,9 @@ function writeFixedString(buffer, offset, size, value) {
|
||||
Buffer.from(String(value || ""), "utf8").copy(buffer, offset, 0, size);
|
||||
}
|
||||
|
||||
function sendSacnDmx(payload) {
|
||||
async function sendSacnDmx(payload) {
|
||||
if (!sacn.config.enabled) {
|
||||
return Promise.resolve(false);
|
||||
return false;
|
||||
}
|
||||
|
||||
const universe = clampSacnUniverse(payload.universe);
|
||||
@@ -480,9 +598,10 @@ function sendSacnDmx(payload) {
|
||||
|
||||
sacn.sequence = sacn.sequence >= 255 ? 1 : sacn.sequence + 1;
|
||||
const host = sacnDestination(universe);
|
||||
const socket = await ensureUdpSocket(sacn, "socket", "socketSignature", "sACN", sacn.config);
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
sacn.socket.send(packet, 0, packet.length, sacn.config.port, host, (error) => {
|
||||
socket.send(packet, 0, packet.length, sacn.config.port, host, (error) => {
|
||||
if (error) {
|
||||
emitStatus(sacn, `sACN Send Fehler: ${error.message}`);
|
||||
reject(error);
|
||||
@@ -520,6 +639,7 @@ function configureMqtt(nextConfig) {
|
||||
if (mqttState.config.clientId) options.clientId = mqttState.config.clientId;
|
||||
if (mqttState.config.username) options.username = mqttState.config.username;
|
||||
if (mqttState.config.password) options.password = mqttState.config.password;
|
||||
if (mqttState.config.localAddress) options.localAddress = mqttState.config.localAddress;
|
||||
|
||||
try {
|
||||
mqttState.client = mqtt.connect(mqttState.config.brokerUrl, options);
|
||||
@@ -656,6 +776,28 @@ async function listSerialPorts() {
|
||||
}));
|
||||
}
|
||||
|
||||
async function listNetworkInterfaces() {
|
||||
const interfaces = os.networkInterfaces();
|
||||
const items = [];
|
||||
|
||||
Object.entries(interfaces).forEach(([name, entries]) => {
|
||||
(entries || []).forEach((entry) => {
|
||||
const family = typeof entry.family === "string" ? entry.family : String(entry.family);
|
||||
if (family !== "IPv4" || entry.internal || !entry.address) {
|
||||
return;
|
||||
}
|
||||
|
||||
items.push({
|
||||
id: entry.address,
|
||||
name: `${name} - ${entry.address}`
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
items.sort((left, right) => left.name.localeCompare(right.name));
|
||||
return items;
|
||||
}
|
||||
|
||||
async function disconnectModbusClient(state) {
|
||||
if (state.client?.isOpen) {
|
||||
try {
|
||||
@@ -693,7 +835,8 @@ async function ensureModbusTcpConnected() {
|
||||
await disconnectModbusClient(modbusTcpState);
|
||||
await modbusTcpState.client.connectTCP(modbusTcpState.config.host, {
|
||||
port: modbusTcpState.config.port,
|
||||
timeout: modbusTcpState.config.timeout
|
||||
timeout: modbusTcpState.config.timeout,
|
||||
localAddress: modbusTcpState.config.localAddress || undefined
|
||||
});
|
||||
modbusTcpState.client.setTimeout(modbusTcpState.config.timeout);
|
||||
modbusTcpState.connected = true;
|
||||
@@ -806,6 +949,7 @@ async function ensureKnxConnection() {
|
||||
const connection = knx.Connection({
|
||||
ipAddr: knxState.config.ipAddr,
|
||||
ipPort: knxState.config.ipPort,
|
||||
interface: knxState.config.interface || undefined,
|
||||
physAddr: knxState.config.physAddr,
|
||||
forceTunneling: knxState.config.forceTunneling,
|
||||
handlers: {
|
||||
@@ -871,18 +1015,6 @@ async function sendKnx(payload) {
|
||||
});
|
||||
}
|
||||
|
||||
artnet.sendSocket.on("error", (error) => {
|
||||
emitStatus(artnet, `Art-Net Socket Fehler: ${error.message}`);
|
||||
});
|
||||
|
||||
osc.socket.on("error", (error) => {
|
||||
emitStatus(osc, `OSC Socket Fehler: ${error.message}`);
|
||||
});
|
||||
|
||||
sacn.socket.on("error", (error) => {
|
||||
emitStatus(sacn, `sACN Socket Fehler: ${error.message}`);
|
||||
});
|
||||
|
||||
configureMqtt(mqttState.config);
|
||||
|
||||
contextBridge.exposeInMainWorld("time4midi", {
|
||||
@@ -890,6 +1022,17 @@ contextBridge.exposeInMainWorld("time4midi", {
|
||||
save: (payload) => ipcRenderer.invoke("project:save", payload),
|
||||
load: () => ipcRenderer.invoke("project:load")
|
||||
},
|
||||
audio: {
|
||||
pickFile: () => ipcRenderer.invoke("audio:pickFile"),
|
||||
readFile: async (filePath) => {
|
||||
const buffer = await fs.readFile(filePath);
|
||||
return buffer.buffer.slice(buffer.byteOffset, buffer.byteOffset + buffer.byteLength);
|
||||
},
|
||||
fileUrl: (filePath) => pathToFileURL(filePath).href
|
||||
},
|
||||
network: {
|
||||
listInterfaces: () => listNetworkInterfaces()
|
||||
},
|
||||
artnet: {
|
||||
configure: (config) => configureArtNet(config),
|
||||
sendDmx: (payload) => sendArtNetDmx(payload),
|
||||
|
||||
Reference in New Issue
Block a user