UNPKG

@janart19/node-red-fusebox

Version:

A collection of Fusebox-specific custom nodes for Node-RED

187 lines (145 loc) 7.75 kB
const dgram = require("dgram"); const http = require("http"); // This custom Node-RED node will establish either a UDP or HTTP connection depending on the protocol selected by the user. // The received data will be parsed, formatted, and saved into the global context. // Once the data is received, the node will emit a message with the data. // The node will also emit a status message to indicate the status of the connection. module.exports = function (RED) { function QueryDataStreamsNode(config) { RED.nodes.createNode(this, config); const node = this; // Retrieve configuration settings node.name = config.name; node.protocol = config.protocol; node.queryInterval = config.queryInterval; // Retrieve the config node's settings node.controller = RED.nodes.getNode(config.controller); // Validate the controller configuration if (!node.controller || !node.controller.host || (!node.controller.httpPort && !node.controller.udpPort)) { node.error("Controller configuration invalid"); node.status({ fill: "red", shape: "dot", text: "Controller configuration invalid" }); return; } // Initialize global context to get and set values const globalStatesKey = `${node.controller.uniqueId}_states`; const globalContext = node.context().global; // UDP Connection if (node.protocol === "UDP") { const server = dgram.createSocket("udp4"); server.bind(node.controller.udpPort, node.controller.host); node.status({ fill: "yellow", shape: "dot", text: "Listening on UDP" }); server.on("message", (message) => { try { // node.debug(`Received UDP message: ${message}`); const data = JSON.parse(message); const key = Object.keys(data)[0]; const obj = { values: data[key].values ?? data[key].v, status: data[key].status ?? data[key].s, timestamp: data[key].timestamp ?? data[key].t ?? Math.floor(Date.now() / 1000), }; const payload = { [key]: obj }; const _dataStreams = globalContext.get(globalStatesKey) || {}; _dataStreams[key] = obj; globalContext.set(globalStatesKey, _dataStreams); node.status({ fill: "green", shape: "dot", text: `UDP data received (${formatDate()})` }); node.send({ controller: { id: node.controller.id, uniqueId: node.controller.uniqueId, protocol: node.protocol, host: node.controller.host }, payload: payload, }); } catch (error) { node.error(`Failed to parse UDP message: ${error}`, { error }); node.status({ fill: "red", shape: "dot", text: "UDP error" }); } }); server.on("error", (error) => { node.error(`UDP error: ${error}`, { error }); node.status({ fill: "red", shape: "dot", text: "UDP error" }); server.close(); }); node.on("close", function (done) { server.close(done); }); } // HTTP Connection if (node.protocol === "HTTP") { let firstQuery = true; function queryHTTP() { const path = firstQuery ? "/allstates" : "/states"; firstQuery = false; const options = { hostname: node.controller.host, port: node.controller.httpPort, path: path, method: "GET", }; // node.debug(`Querying HTTP: ${JSON.stringify(options)}`); const req = http.request(options, (res) => { let data = ""; res.on("data", (chunk) => { data += chunk; }); res.on("end", () => { try { // node.debug(`Received HTTP message: ${data}`); const _dataStreams = globalContext.get(globalStatesKey) || {}; const parsedData = JSON.parse(data); const localhost = Object.keys(parsedData)[0]; const payload = {}; for (const key in parsedData[localhost]) { if (parsedData[localhost].hasOwnProperty(key)) { const obj = { values: parsedData[localhost][key].v ?? parsedData[localhost][key].values, status: parsedData[localhost][key].s ?? parsedData[localhost][key].status, timestamp: parsedData[localhost][key].t ?? parsedData[localhost][key].timestamp, }; _dataStreams[key] = obj; payload[key] = obj; } } globalContext.set(globalStatesKey, _dataStreams); node.status({ fill: "green", shape: "dot", text: `HTTP data received (${formatDate()})` }); node.send({ controller: { id: node.controller.id, uniqueId: node.controller.uniqueId, protocol: node.protocol, host: node.controller.host }, payload: payload, }); } catch (error) { node.error("Failed to parse HTTP response", { error }); node.error(error); node.status({ fill: "red", shape: "dot", text: "HTTP parse error" }); } }); }); req.on("error", (error) => { node.error(`HTTP request error: ${error}`, { error }); node.status({ fill: "red", shape: "dot", text: "HTTP request error" }); firstQuery = true; }); req.end(); } // Query immediately and then periodically queryHTTP(); const interval = setInterval(queryHTTP, node.queryInterval * 1000); node.status({ fill: "yellow", shape: "dot", text: `Querying HTTP every ${node.queryInterval} seconds` }); node.on("close", function (done) { clearInterval(interval); done(); }); } // Format the current date and time as DD/MM/YYYY HH:MM:SS function formatDate() { const now = new Date(); const options = { day: "2-digit", month: "2-digit", year: "2-digit", hour: "2-digit", minute: "2-digit", second: "2-digit", hour12: false, // Use 24-hour format }; return now.toLocaleString("en-GB", options); // 'en-GB' locale for DD/MM/YYYY format } } RED.nodes.registerType("fusebox-query-data-streams", QueryDataStreamsNode); };