diff --git a/pumpingStation.html b/pumpingStation.html index 97b42ae..8c56986 100644 --- a/pumpingStation.html +++ b/pumpingStation.html @@ -58,7 +58,7 @@ icon: "font-awesome/fa-tint", label: function () { - return this.positionIcon + " " + this.assetType || "pumpingStation"; + return this.positionIcon + " PumpingStation"; }, oneditprepare: function() { diff --git a/src/nodeClass.js b/src/nodeClass.js index 404686a..b25598a 100644 --- a/src/nodeClass.js +++ b/src/nodeClass.js @@ -88,10 +88,7 @@ class nodeClass { } - /** - * Register this node as a child upstream and downstream. - * Delayed to avoid Node-RED startup race conditions. - */ + // init registration msg _registerChild() { setTimeout(() => { this.node.send([ @@ -102,12 +99,78 @@ class nodeClass { }, 100); } - /** - * Start the periodic tick loop to drive the Measurement class. - */ + _updateNodeStatus() { + const ps = this.source; + try { + // --- Basin & measurements ------------------------------------------------- + const maxVolBeforeOverflow = ps.basin?.maxVolOverflow ?? ps.basin?.maxVol ?? 0; + const volumeMeasurement = ps.measurements.type("volume").variant("measured").position("atEquipment"); + const currentVolume = volumeMeasurement.getCurrentValue("m3") ?? 0; + const netFlowMeasurement = ps.measurements.type("netFlowRate").variant("predicted").position("atEquipment"); + const netFlowM3s = netFlowMeasurement?.getCurrentValue("m3/s") ?? 0; + const netFlowM3h = netFlowM3s * 3600; + const percentFull = ps.measurements.type("volume").variant("procent").position("atEquipment").getCurrentValue() ?? 0; + + // --- State information ---------------------------------------------------- + const direction = ps.state?.direction || "unknown"; + const secondsRemaining = ps.state?.seconds ?? null; + + const timeRemaining = secondsRemaining ? `${Math.round(secondsRemaining / 60)}` : 0 + " min"; + + // --- Icon / colour selection --------------------------------------------- + let symbol = "❔"; + let fill = "grey"; + + switch (direction) { + case "filling": + symbol = "⬆️"; + fill = "blue"; + break; + case "draining": + symbol = "⬇️"; + fill = "orange"; + break; + case "stable": + symbol = "⏸️"; + fill = "green"; + break; + default: + symbol = "❔"; + fill = "grey"; + break; + } + + // --- Status text ---------------------------------------------------------- + const textParts = [ + `${symbol} ${percentFull.toFixed(1)}%`, + `V=${currentVolume.toFixed(2)} / ${maxVolBeforeOverflow.toFixed(2)} m³`, + `net=${netFlowM3h.toFixed(1)} m³/h`, + `t≈${timeRemaining}` + ]; + + return { + fill, + shape: "dot", + text: textParts.join(" | ") + }; + } catch (error) { + this.node.error("Error in updateNodeStatus: " + error.message); + return { fill: "red", shape: "ring", text: "Status Error" }; + } + } + + + // any time based functions here _startTickLoop() { setTimeout(() => { this._tickInterval = setInterval(() => this._tick(), 1000); + + // Update node status on nodered screen every second ( this is not the best way to do this, but it works for now) + this._statusInterval = setInterval(() => { + const status = this._updateNodeStatus(); + this.node.status(status); + }, 1000); + }, 1000); } @@ -156,7 +219,7 @@ class nodeClass { _attachCloseHandler() { this.node.on('close', (done) => { clearInterval(this._tickInterval); - //clearInterval(this._statusInterval); + clearInterval(this._statusInterval); done(); }); } diff --git a/src/specificClass.js b/src/specificClass.js index 87c1339..84aa41e 100644 --- a/src/specificClass.js +++ b/src/specificClass.js @@ -1,5 +1,5 @@ const EventEmitter = require('events'); -const {logger,configUtils,configManager,childRegistrationUtils,MeasurementContainer,coolprop} = require('generalFunctions'); +const {logger,configUtils,configManager,childRegistrationUtils,MeasurementContainer,coolprop,interpolation} = require('generalFunctions'); class pumpingStation { constructor(config={}) { @@ -9,6 +9,7 @@ class pumpingStation { this.defaultConfig = this.configManager.getConfig('pumpingStation'); this.configUtils = new configUtils(this.defaultConfig); this.config = this.configUtils.initConfig(config); + this.interpolate = new interpolation(); // Init after config is set this.logger = new logger(this.config.general.logging.enabled,this.config.general.logging.logLevel, this.config.general.name); @@ -118,11 +119,10 @@ class pumpingStation { const level = pressure_Pa / density * g; this.measurements.type("level").variant("predicted").position(position).value(level); - //updatePredictedLevel(); ?? + //updatePredictedLevel(); ?? OLIFANT! //calculate how muc flow went in or out based on pressure difference this.logger.debug(`Using pressure: ${value} for calculations`); - } @@ -134,7 +134,13 @@ class pumpingStation { const level = this.measurements.type("level").variant("measured").position(position).getCurrentValue('m'); //calc vol in m3 const volume = this._calcVolumeFromLevel(level); + this.logger.debug(`basin minvol : ${this.basin.minVol}, cur volume : ${volume} / ${this.basin.maxVolOverflow}`); + + const proc = this.interpolate.interpolate_lin_single_point(volume,this.basin.minVol,this.basin.maxVolOverflow,0,100); +this.logger.debug(`PROC volume : ${proc}`); this.measurements.type("volume").variant("measured").position("atEquipment").value(volume).unit('m3'); + this.measurements.type("volume").variant("procent").position("atEquipment").value(proc) + //calc the most important values back to determine state and net up or downstream flow this._calcNetFlow(); @@ -142,6 +148,7 @@ class pumpingStation { } + _calcNetFlow() { const { heightOverflow, heightOutlet, surfaceArea } = this.basin; @@ -177,35 +184,17 @@ class pumpingStation { } _calcNetFlowFromMeasurements({ heightOverflow, heightOutlet, surfaceArea }) { - const flowDiff = this.measurements - .type("flow") - .variant("measured") - .difference({ from: "downstream", to: "upstream", unit: "m3/s" }); - const level = this.measurements - .type("level") - .variant("measured") - .position("atEquipment") - .getCurrentValue("m"); - - const flowUpstream = this.measurements - .type("flow") - .variant("measured") - .position("upstream") - .getCurrentValue("m3/s"); - - const flowDownstream = this.measurements - .type("flow") - .variant("measured") - .position("downstream") - .getCurrentValue("m3/s"); + const flowDiff = this.measurements.type("flow").variant("measured").difference({ from: "downstream", to: "upstream", unit: "m3/s" }); + const level = this.measurements.type("level").variant("measured").position("atEquipment").getCurrentValue("m"); + const flowUpstream = this.measurements.type("flow").variant("measured").position("upstream").getCurrentValue("m3/s"); + const flowDownstream = this.measurements.type("flow").variant("measured").position("downstream").getCurrentValue("m3/s"); if (flowDiff === null || level === null) { this.logger.warn(`no flowdiff ${flowDiff} or level ${level} found escaping`); return null; } - const flowThreshold = 0.1; // m³/s const state = { direction: "stable", seconds: 0, netUpstream: flowUpstream ?? 0, netDownstream: flowDownstream ?? 0 }; @@ -219,12 +208,7 @@ class pumpingStation { state.seconds = remainingHeight * surfaceArea / Math.abs(flowDiff); } - this.measurements - .type("netFlowRate") - .variant("predicted") - .position("atEquipment") - .value(flowDiff) - .unit("m3/s"); + this.measurements.type("netFlowRate").variant("predicted").position("atEquipment").value(flowDiff).unit("m3/s"); this.logger.debug( `Flow-based net flow | diff=${flowDiff.toFixed(3)} m3/s, level=${level.toFixed(3)} m` @@ -288,7 +272,6 @@ class pumpingStation { } initBasinProperties() { - // Load and calc basic params const volEmptyBasin = this.config.basin.volume; @@ -301,8 +284,8 @@ class pumpingStation { const surfaceArea = volEmptyBasin / heightBasin; const maxVol = heightBasin * surfaceArea; // if Basin where to ever fill up completely this is the water volume const maxVolOverflow = heightOverflow * surfaceArea ; // Max water volume before you start loosing water to overflow - const minVol = heightInlet * surfaceArea; - const minVolOut = heightOutlet * surfaceArea ; // this will indicate if its an open end or a closed end. + const minVol = heightOutlet * surfaceArea; + const minVolOut = heightInlet * surfaceArea ; // this will indicate if its an open end or a closed end. this.basin.volEmptyBasin = volEmptyBasin ; this.basin.heightBasin = heightBasin ;