diff --git a/src/specificClass.js b/src/specificClass.js index 5c6d3d4..d7b2ca2 100644 --- a/src/specificClass.js +++ b/src/specificClass.js @@ -28,6 +28,7 @@ class pumpingStation { 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.stations = {}; // object to hold station information this.childRegistrationUtils = new childRegistrationUtils(this); // Child registration utility this.logger.debug('pumpstation Initialized with all helpers'); @@ -68,11 +69,30 @@ class pumpingStation { //listen for machine pressure changes 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) => { + switch(child.config.functionality.positionVsParent){ + case("downstream"): + case("atequipment"): //in case of atequipment we also assume downstream seeing as it is registered at this pumpingstation as part of it. + //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.measurements.type('flow').variant('predicted').position('out').value(eventData.value,eventData.timestamp,eventData.unit); + }); + break; + + + case("upstream"): + //check for predicted outgoing flow at the connected child pumpingsation + child.measurements.emitter.on("flow.predicted.downstream", (eventData) => { this.logger.debug(`Flow prediction update from ${child.config.general.id}: ${eventData.value} ${eventData.unit}`); - this.measurements.type('flow').variant('predicted').position('atEquipment').value(eventData.value,eventData.timestamp,eventData.unit); - }); + //register this then as upstream flow that arrives at the station + this.measurements.type('flow').variant('predicted').position('in').value(eventData.value,eventData.timestamp,eventData.unit); + }); + break; + + default: + this.logger.warn(`nu such position ${child.config.functionality.positionVsParent}`); + } + } // add one for group later @@ -80,38 +100,84 @@ class pumpingStation { } + // add one for pumping station + if ( softwareType == "pumpingStation"){ + // Check if the machine is already registered + this.stations[child.config.general.id] === undefined ? this.machistationsnes[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 flow changes from machine ${child.config.general.id}`); + + switch(child.config.functionality.positionVsParent){ + case("downstream"): + //check for predicted outgoing flow at the connected child pumpingsation + child.measurements.emitter.on("flow.predicted.downstream", (eventData) => { + this.logger.debug(`Flow prediction update from ${child.config.general.id}: ${eventData.value} ${eventData.unit}`); + //register this then as upstream flow that arrives at the station + this.measurements.type('flow').variant('predicted').position('out').value(eventData.value,eventData.timestamp,eventData.unit); + }); + break; + + case("upstream"): + //check for predicted outgoing flow at the connected child pumpingsation + child.measurements.emitter.on("flow.predicted.downstream", (eventData) => { + this.logger.debug(`Flow prediction update from ${child.config.general.id}: ${eventData.value} ${eventData.unit}`); + //register this then as upstream flow that arrives at the station + this.measurements.type('flow').variant('predicted').position('in').value(eventData.value,eventData.timestamp,eventData.unit); + }); + break; + + default: + // there is no such thing as atequipment from 1 pumpingstation to another.... + this.logger.warn(`nu such position ${child.config.functionality.positionVsParent} for pumping station`); + } + } } - //update prediction in outgoing downstream flow - _updateDownstreamFlowPrediction(){ + //in or outgoing flow = direction + _updateVolumePrediction(flowDir){ //get downflow - const downFlowExists = this.measurements.type("flow").variant("predicted").position("atEquipment").exists(); - if(!downFlowExists){return}; + const seriesExists = this.measurements.type("flow").variant("predicted").position(flowDir).exists(); + if(!seriesExists){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 } + const series = this.measurements.type("flow").variant("predicted").position(flowDir); + const currFLow = series.getLaggedValue(0, "m3/s"); // { value, timestamp, unit } + const prevFlow = series.getLaggedValue(1, "m3/s"); // { value, timestamp, unit } - if (!currDownFlow || !prevDownFlow) return; + if (!currFLow || !prevFlow) return; - this.logger.debug(`currDownflow = ${currDownFlow.value} , prevDownFlow = ${prevDownFlow.value}`); + this.logger.debug(`currDownflow = ${currFLow.value} , prevDownFlow = ${prevFlow.value}`); // calc difference in time - const deltaT = currDownFlow.timestamp - prevDownFlow.timestamp; + const deltaT = currFLow.timestamp - prevFlow.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; + + const avgFlow = (currFLow.value + prevFlow.value) / 2; + const calcVol = 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; + let newVol = currVolume; + + switch(flowDir){ + case("out"): + newVol = currVolume - calcVol; + break; + + case("in"): + newVol = currVolume + calcVol; + break; + + default: + this.logger.error('Flow must come in or out of the station!'); + } + this.measurements.type('volume').variant('predicted').position('atEquipment').value(newVol).unit('m3'); //convert to a predicted level @@ -123,10 +189,7 @@ class pumpingStation { } - //update prediction in incomming upstream flow - _updateUpstreamFlowPrediction(){ - } //trigger shutdown when level is too low and trigger no start flag for childs ? safetyVolCheck(){ @@ -146,7 +209,12 @@ class pumpingStation { //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(); + this._updateVolumePrediction("out"); //check for changes in outgoing flow + this._updateVolumePrediction("in"); // check for changes in incomming flow + //calc the most important values back to determine state and net up or downstream flow + this._calcNetFlow(); + this._calcTimeRemaining(); + } @@ -229,81 +297,81 @@ class pumpingStation { 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(); - } _calcNetFlow() { + let netFlow = null; + + const netFlow_FlowSensor = Math.abs(this.measurements.type("flow").variant("measured").difference({ from: "downstream", to: "upstream", unit: "m3/s" })); + const netFlow_LevelSensor = this._calcNetFlowFromLevelDiff(); + const netFlow_PredictedFlow = Math.abs(this.measurements.type('flow').variant('predicted').difference({ from: "in", to: "out", unit: "m3/s" })); + + switch (true){ + //prefer flowsensor netflow + case (netFlow_FlowSensor!=null): + return netFlow_FlowSensor; + //try using level difference if possible to infer netflow + case (netFlow_LevelSensor!= null): + return netFlow_LevelSensor; + case (netFlow_PredictedFlow != null): + return netFlow_PredictedFlow; + default: + this.logger.warn(`Can't calculate netflow without the proper measurements or predictions`); + return null; + } + + } + + _calcRemainingTime(level,variant){ + const { heightOverflow, heightOutlet, surfaceArea } = this.basin; + const flowDiff = this.measurements.type("flow").variant(variant).difference({ from: "downstream", to: "upstream", unit: "m3/s" }); - const flowBased = this._calcNetFlowFromMeasurements({ - heightOverflow, - heightOutlet, - surfaceArea - }); + switch(true){ + case(flowDiff>0): + remainingHeight = Math.max(heightOverflow - level, 0); + this.state.seconds = remainingHeight * surfaceArea / flowDiff; + break; - const levelBased = this._calcNetFlowFromLevel({ - heightOverflow, - heightOutlet, - surfaceArea - }); + case(flowDiff<0): + remainingHeight = Math.max(level - heightOutlet, 0); + this.state.seconds = remainingHeight * surfaceArea / Math.abs(flowDiff); + break; - if (flowBased && levelBased) { - this.logger.debug( - `Flow vs Level comparison | flow=${flowBased.netFlowRate.value.toFixed(3)} ` + - `m3/s, level=${levelBased.netFlowRate.toFixed(3)} m3/s` - ); - } + default: + this.logger.debug(`doing nothing with level calc`) - const effective = flowBased || levelBased; - if (effective) { - this.state = effective.state; - this.state.netFlowSource = flowBased ? (levelBased ? "flow+level" : "flow") : "level"; - this.logger.debug(`Net-flow state: ${JSON.stringify(this.state)}`); - } else { - this.logger.debug("Net-flow state: insufficient data"); - } + } - return effective; } - _calcNetFlowFromMeasurements({ heightOverflow, heightOutlet, surfaceArea }) { + _calcDirection(flowDiff){ - 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"); + let direction = null; + + switch (true){ + case flowDiff > flowThreshold: + direction = "filling"; + break; + + case flowDiff < -flowThreshold: + direction = "draining"; + break; + + case flowDiff < flowThreshold && flowDiff > -flowThreshold: + direction = "stable"; + break; + + default: + this.logger.warn("Uknown state direction detected??"); + return null; - 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 }; - - if (flowDiff > flowThreshold) { - state.direction = "filling"; - const remainingHeight = Math.max(heightOverflow - level, 0); - state.seconds = remainingHeight * surfaceArea / flowDiff; - } else if (flowDiff < -flowThreshold) { - state.direction = "draining"; - const remainingHeight = Math.max(level - heightOutlet, 0); - state.seconds = remainingHeight * surfaceArea / Math.abs(flowDiff); - } - - this.measurements.type("netFlowRate").variant("predicted").position("atEquipment").value(flowDiff).unit("m3/s"); - - this.logger.debug( - `Flow-based net flow | diff=${flowDiff.value.toFixed(3)} m3/s, level=${level.toFixed(3)} m` - ); - - return { source: "flow", netFlowRate: flowDiff, state }; + return direction; } - _calcNetFlowFromLevel({ heightOverflow, heightOutlet, surfaceArea }) { + _calcNetFlowFromLevelDiff() { + const { surfaceArea } = this.basin; const levelObj = this.measurements.type("level").variant("measured").position("atEquipment"); const level = levelObj.getCurrentValue("m"); const prevLevel = levelObj.getLaggedValue(2, "m"); // { value, timestamp, unit } @@ -323,29 +391,9 @@ class pumpingStation { const lvlDiff = level - prevLevel.value; const lvlRate = lvlDiff / deltaSeconds; // m/s - const levelRateThreshold = 0.1 / surfaceArea; // same 0.1 m³/s threshold translated to height - - const state = { direction: "stable", seconds: 0, netUpstream: 0, netDownstream: 0 }; - - if (lvlRate > levelRateThreshold) { - state.direction = "filling"; - const remainingHeight = Math.max(heightOverflow - level, 0); - state.seconds = remainingHeight / lvlRate; - } else if (lvlRate < -levelRateThreshold) { - state.direction = "draining"; - const remainingHeight = Math.max(level - heightOutlet, 0); - state.seconds = remainingHeight / Math.abs(lvlRate); - } - const netFlowRate = lvlRate * surfaceArea; // m³/s inferred from level trend - this.measurements.type("netFlowRate").variant("predicted").position("atEquipment").value(netFlowRate).unit("m3/s"); - - this.logger.warn( - `Level-based net flow | rate=${lvlRate.toExponential(3)} m/s, inferred=${netFlowRate.toFixed(3)} m3/s` - ); - - return { source: "level", netFlowRate, state }; + return netFlowRate; } initBasinProperties() { @@ -383,8 +431,6 @@ class pumpingStation { max=${maxVol.toFixed(2)} m³, overflow=${maxVolOverflow.toFixed(2)} m³` ); - - } _calcVolumeFromLevel(level) { @@ -398,12 +444,34 @@ _calcLevelFromVolume(vol){ } - getOutput() { - return { - volume_m3: this.measurements.type("volume").variant("measured").position("atEquipment").getCurrentValue('m3') , +getOutput() { + // Improved output object generation + const output = {}; + //build the output object + this.measurements.getTypes().forEach(type => { + this.measurements.getVariants(type).forEach(variant => { + this.measurements.getPositions(variant).forEach(position => { + const sample = this.measurements.type(type).variant(variant).position(position); + output[`${type}.${variant}.${position}`] = sample.getCurrentValue(); + }); + }); + }); - }; - } + //fill in the rest of the output object + output["state"] = this.state; + output["basin"] = this.basin; + + if(this.flowDrift != null){ + const flowDrift = this.flowDrift; + output["flowNrmse"] = flowDrift.nrmse; + output["flowLongterNRMSD"] = flowDrift.longTermNRMSD; + output["flowImmediateLevel"] = flowDrift.immediateLevel; + output["flowLongTermLevel"] = flowDrift.longTermLevel; + } + + + return output; +} } module.exports = pumpingStation; @@ -584,7 +652,7 @@ function pushSample(measurement, type, value, unit) { pushSample(upstreamFlow, "flow", 0.40, "m3/s"); pushSample(levelSensor, "level", 1.85, "m"); */ - +console.log("Station output:", station.getOutput()); await pump.handleInput("parent", "execSequence", "startup"); await pump.handleInput("parent", "execMovement", 50); console.log("Station state:", station.state);