module.exports = function (RED) { // Export function zodat deze door Node-RED kan worden gebruikt function valveGroupControl(config) { // Functie die wordt aangeroepen wanneer de node wordt aangemaakt - changed to valveGroupControl RED.nodes.createNode(this, config); var node = this; try { // Load valve class (and curve data - not used yet) const valveGroupControl = require("./dependencies/valveGroupControlClass"); // Importeer de valveGroupControl class const OutputUtils = require("../generalFunctions/helper/outputUtils"); // Importeer de OutputUtils class const valveGroupControlConfig = { // Configuratie van de valveGroupControl general: { name: config.name || "Default ValveGroupControl ", id: node.id, logging: { enabled: config.eneableLog, logLevel: config.logLevel } }, /* NOT USED asset: { supplier: config.supplier || "Unknown", type: config.valveType || "generic", subType: config.subType || "generic", model: config.model || "generic", valveCurve: config.valveCurve }*/ }; const stateConfig = { // Configuratie van de state general: { logging: { enabled: config.eneableLog, logLevel: config.logLevel } }, /* NOT USED movement: { speed: Number(config.speed) }, time: { starting: Number(config.startup), warmingup: Number(config.warmup), stopping: Number(config.shutdown), coolingdown: Number(config.cooldown) } */ }; // Create valve instance const vgc = new valveGroupControl(valveGroupControlConfig, stateConfig); // put m on node memory as source node.source = vgc; //load output utils const output = new OutputUtils(); //Hier worden node-red statussen en metingen geupdate function updateNodeStatus() { try { const mode = vgc.currentMode; // modus is bijv. auto, manual, etc. //QUESTION: altijd auto dus mag er denk ander in const state = vgc.state.getCurrentState(); //is bijv. operational, idle, off, etc. //QUESTION: altijd operational dus mag er denk anders in let maxDeltaP = vgc.maxDeltaP; // maximum delta P over child kleppen if (maxDeltaP !== null) { maxDeltaP = parseFloat(maxDeltaP.toFixed(0));} //afronden op 4 decimalen indien geen "null" let totalFlow = vgc.measurements.type("totalFlow").variant("predicted").position("upstream").getCurrentValue(); // totale flow van de kleppen let symbolState; if (maxDeltaP === NaN) { maxDeltaP = "∞"; } 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; } 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} | ΔPmax${maxDeltaP} mbar | 💨${totalFlow} m3/h`}; break; case "starting": status = { fill: "yellow", shape: "dot", text: `${mode}: ${symbolState}` }; break; case "warmingup": status = { fill: "green", shape: "dot", text: `${mode}: ${symbolState} | ΔPmax${maxDeltaP} mbar | 💨${totalFlow} m3/h`}; break; case "accelerating": status = { fill: "yellow", shape: "dot", text: `${mode}: ${symbolState} | ΔPmax${maxDeltaP} mbar | 💨${totalFlow} m3/h`}; 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} | ΔPmax${maxDeltaP} mbar | 💨${totalFlow} m3/h`}; 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() { // versturen van output messages --> tick van tick op de klok. Is tijd based en niet event based try { const status = updateNodeStatus(); node.status(status); //vgc.tick(); //get output const classOutput = vgc.getOutput(); const dbOutput = output.formatMsg(classOutput, vgc.config, "influxdb"); const pOutput = output.formatMsg(classOutput, vgc.config, "process"); //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 node.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) { // Functie die wordt aangeroepen wanneer er een input wordt ontvangen try { let result; switch(msg.topic) { case 'registerChild': vgc.logger.info(`Registering child started}`); //CHECKPOINT const childId = msg.payload; const childObj = RED.nodes.getNode(childId); vgc.childRegistrationUtils.registerChild(childObj.source ,msg.positionVsParent); break; case 'setMode': vgc.setMode(msg.payload); break; case 'execSequence': const { source: seqSource, action: seqAction, parameter } = msg.payload; vgc.handleInput(seqSource, seqAction, parameter); break; case 'totalFlowChange': // als pomp harder gaat pompen dan veranderd de totale flow --> dan moet de nieuwe flow per valve berekend worden const { source: tfcSource, action: tfcAction, q} = msg.payload; vgc.handleInput(tfcSource, tfcAction, Number(q)); break; case 'emergencystop': const { source: esSource, action: esAction } = msg.payload; vgc.handleInput(esSource, esAction); break; } if (done) done(); } catch (error) { node.error("Error processing input: " + error.message); if (done) done(error); } }); node.on('close', function(done) { // Functie die wordt aangeroepen wanneer de node wordt gesloten 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("valveGroupControl", valveGroupControl); };