|
|
|
|
@@ -106,7 +106,7 @@ class PumpingStation {
|
|
|
|
|
//get threshholds from config
|
|
|
|
|
const timeThreshhold = this.config.safety.timeleftToFullOrEmptyThresholdSeconds; //seconds
|
|
|
|
|
const triggerHighVol = this.basin.maxVolOverflow * ( this.config.safety.overfillThresholdPercent/100 );
|
|
|
|
|
const triggerLowVol = this.basin.minVolOut * ( this.config.safety.dryRunThresholdPercent/100 );
|
|
|
|
|
const triggerLowVol = this.basin.minVol * ( this.config.safety.dryRunThresholdPercent/100 );
|
|
|
|
|
|
|
|
|
|
// trigger conditions for draining
|
|
|
|
|
if(direction == "draining"){
|
|
|
|
|
@@ -165,10 +165,10 @@ class PumpingStation {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
changeMode(newMode){
|
|
|
|
|
|
|
|
|
|
if ( this.config.control.allowedModes.has(newMode) ){
|
|
|
|
|
const currentMode = this.mode;
|
|
|
|
|
this.logger.info(`Control mode changing from ${currentMode} to ${newMode}`);
|
|
|
|
|
this.mode = newMode;
|
|
|
|
|
this.logger.info(`Control mode changed from ${currentMode} to ${newMode}`);
|
|
|
|
|
}
|
|
|
|
|
else{
|
|
|
|
|
this.logger.warn(`Attempted to change to unsupported control mode: ${newMode}`);
|
|
|
|
|
@@ -176,48 +176,6 @@ class PumpingStation {
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* old levelcontrol
|
|
|
|
|
async _controlLevelBased(snapshot, remainingTime) {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// current volume as a percentage of usable capacity
|
|
|
|
|
const vol = this._resolveVolume(snapshot);
|
|
|
|
|
if (vol == null) {
|
|
|
|
|
this.logger.warn('No valid volume found for level-based control');
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const { thresholds, timeThresholdSeconds } = this.config.control.levelbased;
|
|
|
|
|
const percentFull = (vol / this.basin.maxVolOverflow) * 100;
|
|
|
|
|
|
|
|
|
|
// pick thresholds that are now crossed but were not crossed before
|
|
|
|
|
const newlyCrossed = thresholds.filter(t => percentFull >= t && !this._levelState.crossed.has(t));
|
|
|
|
|
this.logger.debug(`Level-based control: vol=${vol.toFixed(2)} m³ (${percentFull.toFixed(1)}%), newly crossed thresholds: [${newlyCrossed.join(', ')}]`);
|
|
|
|
|
if (!newlyCrossed.length) return;
|
|
|
|
|
|
|
|
|
|
const now = Date.now();
|
|
|
|
|
if (!this._levelState.dwellUntil) {
|
|
|
|
|
this._levelState.dwellUntil = now + timeThresholdSeconds * 1000;
|
|
|
|
|
this.logger.debug(`Level-based control: waiting ${timeThresholdSeconds}s before acting`);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
this.logger.debug(`Level-based control: dwelling for another ${Math.round((this._levelState.dwellUntil - now) / 1000)} seconds`);
|
|
|
|
|
if (now < this._levelState.dwellUntil) return; // still waiting
|
|
|
|
|
|
|
|
|
|
this._levelState.dwellUntil = null; // dwell satisfied, let pumps start
|
|
|
|
|
|
|
|
|
|
for (const threshold of newlyCrossed) {
|
|
|
|
|
const nextMachine = this._nextIdleMachine();
|
|
|
|
|
if (!nextMachine) break;
|
|
|
|
|
this._levelState.crossed.add(threshold);
|
|
|
|
|
this.logger.info(
|
|
|
|
|
`level-based control: threshold ${threshold}% reached, starting "${nextMachine.config.general.name}" (vol=${vol.toFixed(2)} m³)`
|
|
|
|
|
);
|
|
|
|
|
await nextMachine.handleInput('parent', 'execSequence', 'startup');
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
*/
|
|
|
|
|
async _controlLevelBased(snapshot, remainingTime) {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@@ -337,12 +295,19 @@ class PumpingStation {
|
|
|
|
|
case "flowbased":
|
|
|
|
|
this._controlFlowBased();
|
|
|
|
|
break;
|
|
|
|
|
case "manual":
|
|
|
|
|
this._manualControl();
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
this.logger.warn(`Unsupported control mode: ${mode}`);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_manualControl() {
|
|
|
|
|
// Nothing to do - manual mode
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//calibrate the predicted volume to a known value
|
|
|
|
|
calibratePredictedVolume(calibratedVol, timestamp = Date.now()){
|
|
|
|
|
|
|
|
|
|
@@ -835,17 +800,22 @@ class PumpingStation {
|
|
|
|
|
|
|
|
|
|
initBasinProperties() {
|
|
|
|
|
|
|
|
|
|
const volEmptyBasin = this.config.basin.volume;
|
|
|
|
|
const heightBasin = this.config.basin.height;
|
|
|
|
|
const heightInlet = this.config.basin.heightInlet;
|
|
|
|
|
const heightOutlet = this.config.basin.heightOutlet;
|
|
|
|
|
const heightOverflow = this.config.basin.heightOverflow;
|
|
|
|
|
//is min height based on inlet or outlet elevation?
|
|
|
|
|
const minHeightBasedOn = this.config.hydraulics.minHeightBasedOn;
|
|
|
|
|
|
|
|
|
|
const surfaceArea = volEmptyBasin / heightBasin;
|
|
|
|
|
const maxVol = heightBasin * surfaceArea;
|
|
|
|
|
const maxVolOverflow = heightOverflow * surfaceArea;
|
|
|
|
|
const minVol = heightOutlet * surfaceArea;
|
|
|
|
|
const minVolOut = heightInlet * surfaceArea;
|
|
|
|
|
const volEmptyBasin = this.config.basin.volume; //volume when basin is empty
|
|
|
|
|
const heightBasin = this.config.basin.height; //total height of basin
|
|
|
|
|
const heightInlet = this.config.basin.heightInlet; //height at which inlet is located
|
|
|
|
|
const heightOutlet = this.config.basin.heightOutlet; //height at which outlet is located
|
|
|
|
|
const heightOverflow = this.config.basin.heightOverflow; //height at which overflow occurs
|
|
|
|
|
|
|
|
|
|
const surfaceArea = volEmptyBasin / heightBasin; //assume uniform cross section for now
|
|
|
|
|
const maxVol = heightBasin * surfaceArea; //maximum volume when basin is full
|
|
|
|
|
const maxVolOverflow = heightOverflow * surfaceArea; //maximum volume before overflow occurs
|
|
|
|
|
const minVolOut = heightOutlet * surfaceArea; //minimum volume to have outlet just above basin bottom
|
|
|
|
|
const minVolIn = heightInlet * surfaceArea; //minimum volume to have inlet just above waterline
|
|
|
|
|
const minVol = (minHeightBasedOn === "inlet") ? minVolIn : minVolOut;
|
|
|
|
|
this.logger.debug(`Basin min volume based on ${minHeightBasedOn} : ${minVol.toFixed(2)} m3`);
|
|
|
|
|
|
|
|
|
|
this.basin = {
|
|
|
|
|
volEmptyBasin,
|
|
|
|
|
@@ -856,11 +826,13 @@ class PumpingStation {
|
|
|
|
|
surfaceArea,
|
|
|
|
|
maxVol,
|
|
|
|
|
maxVolOverflow,
|
|
|
|
|
minVolIn,
|
|
|
|
|
minVolOut,
|
|
|
|
|
minVol,
|
|
|
|
|
minVolOut
|
|
|
|
|
minHeightBasedOn
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
this.measurements.type('volume').variant('predicted').position('atEquipment').value(heightOutlet).unit('m3');
|
|
|
|
|
this.measurements.type('volume').variant('predicted').position('atEquipment').value(minVol).unit('m3');
|
|
|
|
|
|
|
|
|
|
this.logger.debug(
|
|
|
|
|
`Basin initialized | area=${surfaceArea.toFixed(2)} m2, max=${maxVol.toFixed(2)} m3, overflow=${maxVolOverflow.toFixed(2)} m3`
|
|
|
|
|
@@ -899,6 +871,8 @@ class PumpingStation {
|
|
|
|
|
output.minVol = this.basin.minVol;
|
|
|
|
|
output.maxVolOverflow = this.basin.maxVolOverflow;
|
|
|
|
|
output.minVolOut = this.basin.minVolOut;
|
|
|
|
|
output.minVolIn = this.basin.minVolIn;
|
|
|
|
|
output.minHeightBasedOn = this.basin.minHeightBasedOn;
|
|
|
|
|
return output;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|