From 371f3c65e79445f9ee2bca870c8dba4af7f1b40e Mon Sep 17 00:00:00 2001 From: znetsixe <73483679+znetsixe@users.noreply.github.com> Date: Thu, 23 Oct 2025 09:51:54 +0200 Subject: [PATCH] updated retrieval mechanism --- src/nodeClass.js | 5 +- src/specificClass.js | 271 ++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 256 insertions(+), 20 deletions(-) diff --git a/src/nodeClass.js b/src/nodeClass.js index b25598a..23d156d 100644 --- a/src/nodeClass.js +++ b/src/nodeClass.js @@ -178,8 +178,9 @@ class nodeClass { * Execute a single tick: update measurement, format and send outputs. */ _tick() { - //this.source.tick(); - + + //pumping station needs time based ticks to recalc level when predicted + this.source.tick(); const raw = this.source.getOutput(); const processMsg = this._output.formatMsg(raw, this.config, 'process'); const influxMsg = this._output.formatMsg(raw, this.config, 'influxdb'); diff --git a/src/specificClass.js b/src/specificClass.js index 15ade1b..5c6d3d4 100644 --- a/src/specificClass.js +++ b/src/specificClass.js @@ -25,7 +25,7 @@ class pumpingStation { // Initialize basin-specific properties and calculate used parameters this.initBasinProperties(); - + this.parent = {}; // object to hold parent information for when we follow flow directions. this.child = {}; // object to hold child information so we know on what to subscribe this.machines = {}; // object to hold child machine information this.childRegistrationUtils = new childRegistrationUtils(this); // Child registration utility @@ -66,40 +66,88 @@ class pumpingStation { this.machines[child.config.general.id] === undefined ? this.machines[child.config.general.id] = child : this.logger.warn(`Machine ${child.config.general.id} is already registered.`); //listen for machine pressure changes - this.logger.debug(`Listening for pressure changes from machine ${child.config.general.id}`); + this.logger.debug(`Listening for flow changes from machine ${child.config.general.id}`); //for now lets focus on handling downstream predicted flow child.measurements.emitter.on("flow.predicted.downstream", (eventData) => { this.logger.debug(`Flow prediction update from ${child.config.general.id}: ${eventData.value} ${eventData.unit}`); - this.updateMachineFlowPrediction(); + this.measurements.type('flow').variant('predicted').position('atEquipment').value(eventData.value,eventData.timestamp,eventData.unit); }); - } + } + + // add one for group later + if( softwareType == "machineGroup" ){ + + } } - //how to handle when there are machines connected and there is an updated predicted flow variable - updateMachineFlowPrediction(){ - - //check if container exists - const hasMeasuredFlow = measurements.type("flow").variant("measured").exists(); + //update prediction in outgoing downstream flow + _updateDownstreamFlowPrediction(){ - //if there is no down / upstream flow being measured we can take the machines flow to calculate the flow and update predicted level - if( ! hasMeasuredFlow ) { - - + //get downflow + const downFlowExists = this.measurements.type("flow").variant("predicted").position("atEquipment").exists(); + if(!downFlowExists){return}; + + const downFlow = this.measurements.type("flow").variant("predicted").position("atEquipment"); + const currDownFlow = downFlow.getLaggedValue(0, "m3/s"); // { value, timestamp, unit } + const prevDownFlow = downFlow.getLaggedValue(1, "m3/s"); // { value, timestamp, unit } + + if (!currDownFlow || !prevDownFlow) return; + + this.logger.debug(`currDownflow = ${currDownFlow.value} , prevDownFlow = ${prevDownFlow.value}`); + + // calc difference in time + const deltaT = currDownFlow.timestamp - prevDownFlow.timestamp; + const deltaSeconds = deltaT / 1000; + + if (deltaSeconds <= 0) { + this.logger.warn(`Flow integration aborted; invalid Δt=${deltaSeconds}s.`); + return; } + const avgFlow = (currDownFlow.value + prevDownFlow.value) / 2; + const volumeSubstracted = avgFlow * deltaSeconds; + + //substract seeing as this is downstream and is being pulled away from the pumpingstaion and keep track of status + const currVolume = this.measurements.type('volume').variant('predicted').position('atEquipment').getCurrentValue('m3'); + const newVol = currVolume - volumeSubstracted; + + this.measurements.type('volume').variant('predicted').position('atEquipment').value(newVol).unit('m3'); + //convert to a predicted level + const newLevel = this._calcLevelFromVolume(newVol); + + this.measurements.type('level').variant('predicted').position('atEquipment').value(newLevel).unit('m'); + + this.logger.debug(`new predicted volume : ${newVol} new predicted level: ${newLevel} `); + + } + + //update prediction in incomming upstream flow + _updateUpstreamFlowPrediction(){ + + } + //trigger shutdown when level is too low and trigger no start flag for childs ? + safetyVolCheck(){ + } + //update measured temperature to adjust density of liquid updateMeasuredTemperature(){ } + //update measured flow and recalc updateMeasuredFlow(){ } + //keep updating the volume / level when the flow is still active from a machine or machinegroup or incoming from another source + tick(){ + //go through all the functions that require time based checks or updates + this._updateDownstreamFlowPrediction(); + } _callMeasurementHandler(measurementType, value, position, context) { @@ -204,7 +252,7 @@ class pumpingStation { if (flowBased && levelBased) { this.logger.debug( - `Flow vs Level comparison | flow=${flowBased.netFlowRate.toFixed(3)} ` + + `Flow vs Level comparison | flow=${flowBased.netFlowRate.value.toFixed(3)} ` + `m3/s, level=${levelBased.netFlowRate.toFixed(3)} m3/s` ); } @@ -249,7 +297,7 @@ class pumpingStation { 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` + `Flow-based net flow | diff=${flowDiff.value.toFixed(3)} m3/s, level=${level.toFixed(3)} m` ); return { source: "flow", netFlowRate: flowDiff, state }; @@ -344,6 +392,11 @@ _calcVolumeFromLevel(level) { return Math.max(level, 0) * surfaceArea; } +_calcLevelFromVolume(vol){ + const surfaceArea = this.basin.surfaceArea; + return Math.max(vol, 0) / surfaceArea; +} + getOutput() { return { @@ -355,10 +408,192 @@ _calcVolumeFromLevel(level) { module.exports = pumpingStation; +/* ------------------------------------------------------------------------- */ +/* Example: pumping station + rotating machine + measurements (stand-alone) */ +/* ------------------------------------------------------------------------- */ + +const PumpingStation = require("./specificClass"); +const RotatingMachine = require("../../rotatingMachine/src/specificClass"); +const Measurement = require("../../measurement/src/specificClass"); + +/** Helpers ******************************************************************/ +function createPumpingStationConfig(name) { + return { + general: { + logging: { enabled: true, logLevel: "debug" }, + name, + id: `${name}-${Date.now()}`, + unit: "m3/h" + }, + functionality: { + softwareType: "pumpingStation", + role: "stationcontroller" + }, + basin: { + volume: 43.75, + height: 3.5, + heightInlet: 0.3, + heightOutlet: 0.2, + heightOverflow: 3.0 + }, + hydraulics: { + refHeight: "NAP", + basinBottomRef: 0 + } + }; +} + +function createLevelMeasurementConfig(name) { + return { + general: { + logging: { enabled: true, logLevel: "debug" }, + name, + id: `${name}-${Date.now()}`, + unit: "m" + }, + functionality: { + softwareType: "measurement", + role: "sensor", + positionVsParent: "atEquipment" + }, + asset: { + category: "sensor", + type: "level", + model: "demo-level", + supplier: "demoCo", + unit: "m" + }, + scaling: { enabled: false }, + smoothing: { smoothWindow: 5, smoothMethod: "none" } + }; +} + +function createFlowMeasurementConfig(name, position) { + return { + general: { + logging: { enabled: true, logLevel: "debug" }, + name, + id: `${name}-${Date.now()}`, + unit: "m3/s" + }, + functionality: { + softwareType: "measurement", + role: "sensor", + positionVsParent: position + }, + asset: { + category: "sensor", + type: "flow", + model: "demo-flow", + supplier: "demoCo", + unit: "m3/s" + }, + scaling: { enabled: false }, + smoothing: { smoothWindow: 5, smoothMethod: "none" } + }; +} + + +function createMachineConfig(name) { + + curve = require('C:/Users/zn375/.node-red/public/fallbackData.json'); + return { + + general: { + name: name, + logging: { + enabled: true, + logLevel: "warn", + } + }, + asset: { + supplier: "Hydrostal", + type: "pump", + category: "centrifugal", + model: "hidrostal-H05K-S03R", // Ensure this field is present. + } + } +} + +function createMachineStateConfig() { + return { + general: { + logging: { + enabled: true, + logLevel: "debug", + }, + }, + // Your custom config here (or leave empty for defaults) + movement: { + speed: 1, + }, + time: { + starting: 2, + warmingup: 3, + stopping: 2, + coolingdown: 3, + }, + } +} + +// convenience for seeding measurements +function pushSample(measurement, type, value, unit) { + const pos = measurement.config.functionality.positionVsParent; + measurement.measurements + .type(type) + .variant("measured") + .position(pos) + .value(value, Date.now(), unit); +} + +/** Demo *********************************************************************/ +(async function demoStationWithPump() { + const station = new PumpingStation(createPumpingStationConfig("PumpingStationDemo")); + const pump = new RotatingMachine(createMachineConfig("Pump1"), createMachineStateConfig()); + + const levelSensor = new Measurement(createLevelMeasurementConfig("WetWellLevel")); + const upstreamFlow = new Measurement(createFlowMeasurementConfig("InfluentFlow", "upstream")); + const downstreamFlow = new Measurement(createFlowMeasurementConfig("PumpDischargeFlow", "downstream")); + + + // station uses the sensors + /* + station.childRegistrationUtils.registerChild(levelSensor, levelSensor.config.functionality.softwareType); + station.childRegistrationUtils.registerChild(upstreamFlow, upstreamFlow.config.functionality.softwareType); + station.childRegistrationUtils.registerChild(downstreamFlow, downstreamFlow.config.functionality.softwareType); + */ + + // pump owns the downstream flow sensor + pump.childRegistrationUtils.registerChild(downstreamFlow, downstreamFlow.config.functionality.positionVsParent); + station.childRegistrationUtils.registerChild(pump,"downstream"); + + setInterval(() => station.tick(), 1000); + + // seed a starting level & flow + /* + pushSample(levelSensor, "level", 1.8, "m"); + pushSample(upstreamFlow, "flow", 0.35, "m3/s"); + pushSample(downstreamFlow, "flow", 0.20, "m3/s"); + */ + + await new Promise(resolve => setTimeout(resolve, 20)); + + // pump increases discharge flow + /* + pushSample(downstreamFlow, "flow", 0.28, "m3/s"); + pushSample(upstreamFlow, "flow", 0.40, "m3/s"); + pushSample(levelSensor, "level", 1.85, "m"); + */ + +await pump.handleInput("parent", "execSequence", "startup"); +await pump.handleInput("parent", "execMovement", 50); + console.log("Station state:", station.state); + console.log("Station output:", station.getOutput()); + console.log("Pump state:", pump.state.getCurrentState()); +})(); + + /* - -// - //coolprop example (async () => { const PropsSI = await coolprop.getPropsSI();