UNPKG

@ifp-software/node-red-contrib-oee-ai-connector

Version:

Easily connect your production machines to oee.ai – The Industry 4.0 solution for OEE optimization.

204 lines (170 loc) 6.1 kB
module.exports = function (RED) { "use strict"; const mqtt = require("mqtt"); const HttpsProxyAgent = require("https-proxy-agent"); // These error codes are considered fatal and will cause the node to stop // trying to connect if they occur during the first connection attempt. // See the list of error codes at https://github.com/mqttjs/MQTT.js/blob/master/lib/client.js. const fatalErrorCodes = [4, 5, 134, 135, 138, 142, 156, 159, 160]; const hostnames = { preview: "mqtt.preview.oee.ai", production: "mqtt.oee.ai", }; function clientIdFromCredentials(credentials) { const sensorId = credentials.id; const randomId = Math.random().toString(32).substring(2); if (sensorId) { const shortId = sensorId.split("-")[0]; return `oee-node-red_${shortId}_${randomId}`; } } function topicFromCredentials(credentials) { const sensorId = credentials.id; if (sensorId) { return `oee/sensor/${sensorId}/measurement`; } } function brokerUrlFromConfig(config) { const hostname = hostnames[config.server]; const [schema, port] = config.protocol.split("-"); const url = `${schema}://${hostname}:${port}`; if (config.protocol.startsWith("ws")) { return `${url}/mqtt`; } return url; } function buildMessage(from, to, count) { return JSON.stringify({ data: [ { attributes: { from: from.toISOString(), to: to.toISOString(), count, }, }, ], }); } function setupNode(node, config) { node.status({ fill: "red", shape: "ring", text: "disconnected" }); node.connected = false; node.connectedOnce = false; if (!node.credentials.id || !node.credentials.token) { node.error("Incomplete credentials. Please provide sensor ID and token."); Promise.resolve().then(() => { node.status({ fill: "grey", shape: "ring", text: "incomplete credentials" }); }); return; } node.options = { clientId: clientIdFromCredentials(node.credentials), username: node.credentials.id, password: node.credentials.token, proxy: node.credentials.proxy, proxyUsername: node.credentials.proxyUsername, proxyPassword: node.credentials.proxyPassword, keepalive: 60, reschedulePings: false, clean: true, reconnectPeriod: 60_000, rejectUnauthorized: false, }; if (node.credentials.proxy) { const proxyUrl = new URL(node.credentials.proxy); if (node.credentials.proxyUsername) { proxyUrl.username = node.credentials.proxyUsername; } if (node.credentials.proxyPassword) { proxyUrl.password = node.credentials.proxyPassword; } node.options.wsOptions = { agent: new HttpsProxyAgent(proxyUrl.toString()), }; } node.brokerUrl = brokerUrlFromConfig(config); node.mqttClient = mqtt.connect(node.brokerUrl, node.options); node.status({ fill: "yellow", shape: "ring", text: "connecting" }); node.connecting = true; node.mqttClient.on("offline", () => { node.warn("Lost connection to broker"); }); node.mqttClient.on("reconnect", () => { node.log("Attempting to reconnect to broker"); }); node.mqttClient.on("packetsend", (packet) => { node.trace(`Packet sent: ${JSON.stringify(packet)}`); }); node.mqttClient.on("packetreceive", (packet) => { node.trace(`Packet received: ${JSON.stringify(packet)}`); }); node.mqttClient.on("connect", () => { node.connecting = false; node.status({ fill: "green", shape: "dot", text: "connected" }); node.connected = true; node.connectedOnce = true; node.log(`Connected to ${node.brokerUrl} as ${node.options.clientId}`); }); node.mqttClient.on("error", (error) => { if (!node.connectedOnce && fatalErrorCodes.includes(error.code)) { node.error(`Fatal ${error} (code ${error.code})`); node.mqttClient.end(); } else { node.error(`${error} (code ${error.code})`); } node.status({ fill: "red", shape: "ring", text: "error" }); }); node.mqttClient.on("close", () => { if (node.connected) { node.connected = false; node.status({ fill: "red", shape: "ring", text: "disconnected" }); node.log(`Disconnected from ${node.brokerUrl} as ${node.options.clientId}`); } }); node.on("input", (msg) => { const to = msg.to ? new Date(msg.to) : new Date(); const from = msg.from ? new Date(msg.from) : new Date(to.valueOf() - 30000); const count = parseFloat(msg.count) || 0; const topic = topicFromCredentials(node.credentials); const options = { qos: 2, retain: false }; const message = buildMessage(from, to, count); node.mqttClient.publish(topic, message, options); node.debug(`Message ${message} sent to topic ${topic}`); }); node.on("close", (done) => { node.mqttClient.end(); done(); }); } function OeeAiConnectorNode(config) { RED.nodes.createNode(this, config); const node = this; // Migrations: // // `protocol` was previously: // - `undefined` (until 2.3.0) // - "wss" or "tcp" (until 2.9.0) // // Please make sure to keep this migration code equivalent to the one in the // `.html` file of the node if you change it. if (config.protocol === undefined || config.protocol === "tcp") { if (config.server === "preview") { config.protocol = "mqtt-1883"; } else if (config.server === "production") { config.protocol = "mqtts-8883"; } } else if (config.protocol === "wss") { config.protocol = "wss-8084"; } setupNode(node, config); } RED.nodes.registerType("oee-ai-connector", OeeAiConnectorNode, { credentials: { id: { type: "text" }, token: { type: "password" }, proxy: { type: "text" }, proxyUsername: { type: "text" }, proxyPassword: { type: "password" }, }, }); };