updated valve to latest version of EVOLV eco

This commit is contained in:
znetsixe
2025-07-24 13:14:19 +02:00
parent 9b1af8ffa2
commit 167628a436
6 changed files with 527 additions and 918 deletions

278
valve.js
View File

@@ -1,250 +1,40 @@
module.exports = function (RED) {
function valve(config) {
//create node
const nameOfNode = 'valve'; // this is the name of the node, it should match the file name and the node type in Node-RED
const nodeClass = require('./src/nodeClass.js'); // this is the specific node class
const { MenuManager, configManager } = require('generalFunctions');
// This is the main entry point for the Node-RED node, it will register the node and setup the endpoints
module.exports = function(RED) {
// Register the node type
RED.nodes.registerType(nameOfNode, function(config) {
// Initialize the Node-RED node first
RED.nodes.createNode(this, config);
//call this => node so whenver you want to call a node function type node and the function behind it
var node = this;
// Then create your custom class and attach it
this.nodeClass = new nodeClass(config, RED, this, nameOfNode);
});
// Setup admin UIs
const menuMgr = new MenuManager(); //this will handle the menu endpoints so we can load them dynamically
const cfgMgr = new configManager(); // this will handle the config endpoints so we can load them dynamically
// Register the different menu's for the measurement node (in the future we could automate this further by refering to the config)
RED.httpAdmin.get(`/${nameOfNode}/menu.js`, (req, res) => {
try {
const Valve = require("./dependencies/valveClass"); // Importeer de valve class
const OutputUtils = require("../generalFunctions/helper/outputUtils"); // Importeer de OutputUtils class
const valveConfig = { // Configuratie van de valve
general: {
name: config.name || "Default Valve",
id: node.id,
logging: {
enabled: config.eneableLog,
logLevel: config.logLevel
}
},
asset: {
supplier: config.supplier || "Unknown",
/* NOT USED
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 v = new Valve(valveConfig, stateConfig);
// put m on node memory as source
node.source = v;
//load output utils
const output = new OutputUtils();
//Hier worden node-red statussen en metingen geupdate
function updateNodeStatus() {
try {
const mode = v.currentMode; // modus is bijv. auto, manual, etc.
const state = v.state.getCurrentState(); //is bijv. operational, idle, off, etc.
const flow = Math.round(v.measurements.type("flow").variant("predicted").position("downstream").getCurrentValue());
let deltaP = v.measurements.type("pressure").variant("predicted").position("delta").getCurrentValue();
if (deltaP !== null) {
deltaP = parseFloat(deltaP.toFixed(0));
} //afronden op 4 decimalen indien geen "null"
if(isNaN(deltaP)) {
deltaP = "∞";
}
const roundedPosition = Math.round(v.state.getCurrentPosition() * 100) / 100;
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;
}
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 | ΔP${deltaP} mbar`}; //deltaP toegevoegd
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 | ΔP${deltaP} mbar`}; //deltaP toegevoegd
break;
case "accelerating":
status = { fill: "yellow", shape: "dot", text: `${mode}: ${symbolState} | ${roundedPosition}% | 💨${flow}m³/h | ΔP${deltaP} mbar` }; //deltaP toegevoegd
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 | ΔP${deltaP} mbar`}; //deltaP toegevoegd
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 klop. Is tijd based en niet event based
try {
const status = updateNodeStatus();
node.status(status);
//v.tick();
//get output
const classOutput = v.getOutput();
const dbOutput = output.formatMsg(classOutput, v.config, "influxdb");
const pOutput = output.formatMsg(classOutput, v.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
console.log("CKECK! Input received: ", msg.topic, msg.payload); // CHECKPOINT
try {
let result;
switch(msg.topic) {
case 'registerChild':
const childId = msg.payload;
const childObj = RED.nodes.getNode(childId);
v.childRegistrationUtils.registerChild(childObj.source ,msg.positionVsParent);
break;
case 'setMode':
v.setMode(msg.payload);
break;
case 'execSequence':
const { source: seqSource, action: seqAction, parameter } = msg.payload;
v.handleInput(seqSource, seqAction, parameter);
break;
case 'execMovement':
const { source: mvSource, action: mvAction, setpoint } = msg.payload;
v.handleInput(mvSource, mvAction, Number(setpoint));
break;
case 'emergencystop':
const { source: esSource, action: esAction } = msg.payload;
v.handleInput(esSource, esAction);
break;
case 'showcurve':
v.showCurve();
send({ topic : "Showing curve" , payload: v.showCurve() });
break;
case 'newFlow': //Als nieuwe flow van header node dan moet deltaP weer opnieuw worden berekend en doorgegeven aan header node
const { source: nfSource, action: nfAction, parameter: nfParameter } = msg.payload; //parameter is new flow, action should be "calcNewDeltaP"
v.handleInput(nfSource, nfAction, nfParameter);
}
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"});
const script = menuMgr.createEndpoint(nameOfNode, ['asset','logger','position']);
res.type('application/javascript').send(script);
} catch (err) {
res.status(500).send(`// Error generating menu: ${err.message}`);
}
});
// Endpoint to get the configuration data for the specific node
RED.httpAdmin.get(`/${nameOfNode}/configData.js`, (req, res) => {
try {
const script = cfgMgr.createEndpoint(nameOfNode);
// Send the configuration data as JSON response
res.type('application/javascript').send(script);
} catch (err) {
res.status(500).send(`// Error generating configData: ${err.message}`);
}
});
}
RED.nodes.registerType("valve", valve);
};