module.exports = function (RED) { function ggc(config) { RED.nodes.createNode(this, config); var node = this; try { // Load Machine class and curve data const Ggc = require("./dependencies/ggc/ggc"); const OutputUtils = require("../generalFunctions/helper/outputUtils"); const ggcConfig = { general: { name: config.name || "Unknown", id: node.id, logging: { enabled: config.eneableLog, logLevel: config.logLevel } }, asset: { supplier: config.supplier || "Unknown", type: config.machineType || "generic", subType: config.subType || "generic", model: config.model || "generic", } }; const stateConfig = { general: { logging: { enabled: config.eneableLog, logLevel: config.logLevel } }, movement: { speed: Number(config.speed) }, time: { starting: Number(config.startup), warmingup: Number(config.warmup), stopping: Number(config.shutdown), coolingdown: Number(config.cooldown) } }; // Create machine instance const ggc = new Ggc(ggcConfig, stateConfig); // put m on node memory as source node.source = ggc; //load output utils const output = new OutputUtils(); function updateNodeStatus() { try { const mode = "auto";//ggc.currentMode; const state = ggc.state; const totPower = Math.round(ggc.measurements.type("power").variant("measured").position('wire').getCurrentValue()) || 0; const SYMBOL_MAP = { gateGroupClosed: "G1🔴 & G2🔴", gateGroupOpened: "G1🟢 & G2🟢", gateGroupStopped: "G1🟥 & G2🟥", gateGroupAccelerating: "G1🟡 & G2🟡", gateGroupDecelerating: "G1🟠 & G2🟠", gateOneAccelerating: "G1🟡 & G2🟢", gateTwoAccelerating: "G1🟢 & G2🟡", gateOneDecelerating: "G1🟠 & G2🟢", gateTwoDecelerating: "G1🟢 & G2🟠", oneGateOpenOneGateClosed: "G1🟢 & G2🔴", gatePushingStop: "G1⚡ & G2⚡", unknown: "❓ & ❓", }; symbolState = SYMBOL_MAP[state] || "Unknown"; const position = "" ; //ggc.getGatePositions(); const roundedPosition = Math.round(position * 100) / 100; let status; switch (state) { // —— gateGroup states first —— case "gateGroupClosed": status = { fill: "red", shape: "dot", text: `${mode}: ${symbolState}` }; break; case "gateGroupOpened": status = { fill: "green", shape: "dot", text: `${mode}: ${symbolState}` }; break; case "gateGroupStopped": status = { fill: "red", shape: "dot", text: `${mode}: ${symbolState}` }; break; case "oneGateOpenOneGateClosed": status = { fill: "green", shape: "dot", text: `${mode}: ${symbolState}` }; break; default: status = { fill: "grey", shape: "dot", text: `${mode}: ${symbolState}` }; } return status; } catch (error) { node.error("Error in updateNodeStatus: " + error.message); return { fill: "red", shape: "ring", text: "Status Error" }; } } function tick() { try { const status = updateNodeStatus(); node.status(status); //get output const classOutput = ggc.getOutput(); const dbOutput = output.formatMsg(classOutput, ggc.config, "influxdb"); const pOutput = output.formatMsg(classOutput, ggc.config, "process"); //console.log(pOutput); //only send output on values that changed let msgs = []; msgs[0] = pOutput; msgs[1] = dbOutput; node.send(msgs); } catch (error) { node.error("Error in tick function: " + error); node.status({ fill: "red", shape: "ring", text: "Tick Error" }); } } // register child on first output this timeout is needed because of node - red stuff setTimeout( () => { /*---execute code on first start----*/ let msgs = []; msgs[2] = { topic : "registerChild" , payload: node.id, positionVsParent: "upstream" }; msgs[3] = { topic : "registerChild" , payload: node.id, positionVsParent: "downstream" }; //send msg this.send(msgs); }, 100 ); //declare refresh interval internal node setTimeout( () => { //---execute code on first start---- this.interval_id = setInterval(function(){ tick() },1000) }, 1000 ); node.on("input", function(msg, send, done) { try { /* Update to complete event based node by putting the tick function after an input event */ switch(msg.topic) { case 'registerChild': const childId = msg.payload; const childObj = RED.nodes.getNode(childId); ggc.childRegistrationUtils.registerChild(childObj.source ,msg.positionVsParent); break; case 'setMode': ggc.setMode(msg.payload); break; case 'execSequence': const { source, action, parameter } = msg.payload; ggc.handleInput(source, action, parameter); break; case 'emergencystop': const { source: esSource, action: esAction } = msg.payload; ggc.handleInput(esSource, esAction); break; case 'safetySensor': if(typeof msg.payload === "boolean") { const safetySensor = msg.payload; ggc.safetySensor = safetySensor; } break; } if (done) done(); } catch (error) { node.error("Error processing input: " + error.message); if (done) done(error); } }); node.on('close', function(done) { if (node.interval_id) clearTimeout(node.interval_id); if (node.tick_interval) clearInterval(node.tick_interval); if (done) done(); }); } catch (error) { node.error("Fatal error in node initialization: " + error.stack); node.status({fill: "red", shape: "ring", text: "Fatal Error"}); } } RED.nodes.registerType("ggc", ggc); };