module.exports = function (RED) { function rotatingMachine(config) { RED.nodes.createNode(this, config); var node = this; try { // Load Machine class and curve data const Machine = require("./dependencies/machine/machine"); const OutputUtils = require("../generalFunctions/helper/outputUtils"); const machineConfig = { general: { name: config.name || "Default Machine", 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", machineCurve: config.machineCurve } }; 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 m = new Machine(machineConfig, stateConfig); // put m on node memory as source node.source = m; //load output utils const output = new OutputUtils(); function updateNodeStatus() { try { const mode = m.currentMode; const state = m.state.getCurrentState(); const flow = Math.round(m.measurements.type("flow").variant("predicted").position('downstream').getCurrentValue()); const power = Math.round(m.measurements.type("power").variant("predicted").position('upstream').getCurrentValue()); let symbolState; switch(state){ case "off": symbolState = "⬛"; break; case "idle": symbolState = "⏸️"; break; case "operational": symbolState = "⏵️"; break; case "starting": symbolState = "⏯️"; break; case "warmingup": symbolState = "🔄"; break; case "accelerating": symbolState = "⏩"; break; case "stopping": symbolState = "⏹️"; break; case "coolingdown": symbolState = "❄️"; break; case "decelerating": symbolState = "⏪"; break; } const position = m.state.getCurrentPosition(); const roundedPosition = Math.round(position * 100) / 100; let status; switch (state) { case "off": status = { fill: "red", shape: "dot", text: `${mode}: OFF` }; break; case "idle": status = { fill: "blue", shape: "dot", text: `${mode}: ${symbolState}` }; break; case "operational": status = { fill: "green", shape: "dot", text: `${mode}: ${symbolState} | ${roundedPosition}% | 💨${flow}m³/h | ⚡${power}kW` }; break; case "starting": status = { fill: "yellow", shape: "dot", text: `${mode}: ${symbolState}` }; break; case "warmingup": status = { fill: "green", shape: "dot", text: `${mode}: ${symbolState} | ${roundedPosition}% | 💨${flow}m³/h | ⚡${power}kW` }; break; case "accelerating": status = { fill: "yellow", shape: "dot", text: `${mode}: ${symbolState} | ${roundedPosition}%| 💨${flow}m³/h | ⚡${power}kW` }; break; case "stopping": status = { fill: "yellow", shape: "dot", text: `${mode}: ${symbolState}` }; break; case "coolingdown": status = { fill: "yellow", shape: "dot", text: `${mode}: ${symbolState}` }; break; case "decelerating": status = { fill: "yellow", shape: "dot", text: `${mode}: ${symbolState} - ${roundedPosition}% | 💨${flow}m³/h | ⚡${power}kW` }; 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 = m.getOutput(); const dbOutput = output.formatMsg(classOutput, m.config, "influxdb"); const pOutput = output.formatMsg(classOutput, m.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); m.childRegistrationUtils.registerChild(childObj.source ,msg.positionVsParent); break; case 'setMode': m.setMode(msg.payload); break; case 'execSequence': const { source, action, parameter } = msg.payload; m.handleInput(source, action, parameter); break; case 'execMovement': const { source: mvSource, action: mvAction, setpoint } = msg.payload; m.handleInput(mvSource, mvAction, Number(setpoint)); break; case 'flowMovement': const { source: fmSource, action: fmAction, setpoint: fmSetpoint } = msg.payload; m.handleInput(fmSource, fmAction, Number(fmSetpoint)); break; case 'emergencystop': const { source: esSource, action: esAction } = msg.payload; m.handleInput(esSource, esAction); break; case 'showCompleteCurve': m.showCompleteCurve(); send({ topic : "Showing curve" , payload: m.showCompleteCurve() }); break; case 'CoG': m.showCoG(); send({ topic : "Showing CoG" , payload: m.showCoG() }); 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("rotatingMachine", rotatingMachine); };