forked from RnD/pumpingStation
Need to stich everything together then V1.0 is done.
This commit is contained in:
@@ -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}`);
|
||||
|
||||
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('atEquipment').value(eventData.value,eventData.timestamp,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}`);
|
||||
//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,25 +100,57 @@ 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) {
|
||||
@@ -106,12 +158,26 @@ class pumpingStation {
|
||||
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() {
|
||||
const { heightOverflow, heightOutlet, surfaceArea } = this.basin;
|
||||
let netFlow = null;
|
||||
|
||||
const flowBased = this._calcNetFlowFromMeasurements({
|
||||
heightOverflow,
|
||||
heightOutlet,
|
||||
surfaceArea
|
||||
});
|
||||
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" }));
|
||||
|
||||
const levelBased = this._calcNetFlowFromLevel({
|
||||
heightOverflow,
|
||||
heightOutlet,
|
||||
surfaceArea
|
||||
});
|
||||
|
||||
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`
|
||||
);
|
||||
}
|
||||
|
||||
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 }) {
|
||||
|
||||
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`);
|
||||
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;
|
||||
}
|
||||
|
||||
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");
|
||||
_calcRemainingTime(level,variant){
|
||||
|
||||
this.logger.debug(
|
||||
`Flow-based net flow | diff=${flowDiff.value.toFixed(3)} m3/s, level=${level.toFixed(3)} m`
|
||||
);
|
||||
const { heightOverflow, heightOutlet, surfaceArea } = this.basin;
|
||||
const flowDiff = this.measurements.type("flow").variant(variant).difference({ from: "downstream", to: "upstream", unit: "m3/s" });
|
||||
|
||||
switch(true){
|
||||
case(flowDiff>0):
|
||||
remainingHeight = Math.max(heightOverflow - level, 0);
|
||||
this.state.seconds = remainingHeight * surfaceArea / flowDiff;
|
||||
break;
|
||||
|
||||
case(flowDiff<0):
|
||||
remainingHeight = Math.max(level - heightOutlet, 0);
|
||||
this.state.seconds = remainingHeight * surfaceArea / Math.abs(flowDiff);
|
||||
break;
|
||||
|
||||
default:
|
||||
this.logger.debug(`doing nothing with level calc`)
|
||||
|
||||
return { source: "flow", netFlowRate: flowDiff, state };
|
||||
}
|
||||
|
||||
_calcNetFlowFromLevel({ heightOverflow, heightOutlet, surfaceArea }) {
|
||||
}
|
||||
|
||||
_calcDirection(flowDiff){
|
||||
|
||||
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;
|
||||
|
||||
}
|
||||
return direction;
|
||||
}
|
||||
|
||||
_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) {
|
||||
@@ -399,10 +445,32 @@ _calcLevelFromVolume(vol){
|
||||
|
||||
|
||||
getOutput() {
|
||||
return {
|
||||
volume_m3: this.measurements.type("volume").variant("measured").position("atEquipment").getCurrentValue('m3') ,
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user