From fbfcec4b47577da8d3f6466ea93314ee370b9d48 Mon Sep 17 00:00:00 2001 From: znetsixe <73483679+znetsixe@users.noreply.github.com> Date: Mon, 10 Nov 2025 16:20:23 +0100 Subject: [PATCH] Added simpel case for level control --- src/specificClass.js | 113 +++++++++++++++++++++++++++++++++++++------ 1 file changed, 97 insertions(+), 16 deletions(-) diff --git a/src/specificClass.js b/src/specificClass.js index ceb8628..8d731ca 100644 --- a/src/specificClass.js +++ b/src/specificClass.js @@ -20,6 +20,10 @@ class PumpingStation { this.stations = {}; this.machineGroups = {}; + //fetch control mode from config by default + this.mode = this.config.control.mode; + this._levelState = { crossed: new Set(), dwellUntil: null }; + //variants in determining what gets priority this.flowVariants = ['measured', 'predicted']; this.levelVariants = ['measured', 'predicted']; @@ -80,16 +84,11 @@ class PumpingStation { this.logger.warn(`Unsupported child software type: ${softwareType}`); } - _safeGuardSystem(snapshot,remainingTime,direction){ - let vol = null; + _safetyController(snapshot,remainingTime,direction){ - for (const variant of this.volVariants){ - const volsnap = snapshot.vols[variant]; - //go through with variants until we find one that exists - if (!volsnap.samples.exists){ continue}; + this.safetyControllerActive = false; - vol = volsnap.samples.current?.value ?? null; - } + const vol = this._resolveVolume(snapshot); if(vol == null){ //if we cant get a volume we cant control blind turn all pumps off. @@ -97,13 +96,14 @@ class PumpingStation { machine.handleInput('parent', 'execSequence', 'shutdown'); }); this.logger.warn('No volume data available to safe guard system; shutting down all machines.'); + this.safetyControllerActive = true; return; } //get threshholds from config - const timeThreshhold = this.config.control.timeThreshholdSeconds; //seconds - const triggerHighVol = this.basin.maxVolOverflow * ( this.config.control.thresholdHighVolume/100 ); - const triggerLowVol = this.basin.minVolOut * ( this.config.control.thresholdLowVolume/100); + 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 ); // trigger conditions for draining if(direction == "draining"){ @@ -127,13 +127,90 @@ class PumpingStation { group.handleInput('parent', 'execSequence', 'shutdown'); this.logger.warn(`Safe guard triggered: vol=${vol.toFixed(2)} m3, remainingTime=${remainingTime ? remainingTime.toFixed(1) : 'N/A'} s; shutting down machine group "${groupId}"`); }); + this.safetyControllerActive = true; } } - // trigger conditions for filling - if(direction == "filling"){ - + } + + changeMode(newMode){ + + if ( this.config.control.allowedModes.has(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}`); + } + + } + + 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'); + } + } + + _resolveVolume(snapshot) { + for (const variant of this.volVariants) { + const volsnap = snapshot.vols[variant]; + if (volsnap?.samples?.exists) return volsnap.samples.current?.value ?? null; + } + return null; + } + + _nextIdleMachine() { + return Object.values(this.machines).find(m => !m._isOperationalState()); + } + + //control logic + _controlLogic(snapshot, remainingTime){ + const mode = this.mode; + + switch(mode){ + case "levelbased": + this.logger.debug(`Executing level-based control logic`); + this._controlLevelBased(snapshot, remainingTime); + break; + case "flowbased": + this._controlFlowBased(); + break; + default: + this.logger.warn(`Unsupported control mode: ${mode}`); + } + } //calibrate the predicted volume to a known value @@ -178,11 +255,15 @@ class PumpingStation { this._updatePredictedVolume(snapshot); - const netFlow = this._selectBestNetFlow(snapshot); const remaining = this._computeRemainingTime(snapshot, netFlow); - this._safeGuardSystem(snapshot,remaining.seconds,netFlow.direction); + //check safety conditions + this._safetyController(snapshot,remaining.seconds,netFlow.direction); + if(this.safetyControllerActive) return; + + //if safety not active proceed with normal control + this._controlLogic(snapshot,remaining.seconds); this.state = { direction: netFlow.direction,