Compare commits

..

1 Commits

Author SHA1 Message Date
znetsixe
fbfcec4b47 Added simpel case for level control 2025-11-10 16:20:23 +01:00

View File

@@ -20,6 +20,10 @@ class PumpingStation {
this.stations = {}; this.stations = {};
this.machineGroups = {}; 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 //variants in determining what gets priority
this.flowVariants = ['measured', 'predicted']; this.flowVariants = ['measured', 'predicted'];
this.levelVariants = ['measured', 'predicted']; this.levelVariants = ['measured', 'predicted'];
@@ -80,16 +84,11 @@ class PumpingStation {
this.logger.warn(`Unsupported child software type: ${softwareType}`); this.logger.warn(`Unsupported child software type: ${softwareType}`);
} }
_safeGuardSystem(snapshot,remainingTime,direction){ _safetyController(snapshot,remainingTime,direction){
let vol = null;
for (const variant of this.volVariants){ this.safetyControllerActive = false;
const volsnap = snapshot.vols[variant];
//go through with variants until we find one that exists
if (!volsnap.samples.exists){ continue};
vol = volsnap.samples.current?.value ?? null; const vol = this._resolveVolume(snapshot);
}
if(vol == null){ if(vol == null){
//if we cant get a volume we cant control blind turn all pumps off. //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'); machine.handleInput('parent', 'execSequence', 'shutdown');
}); });
this.logger.warn('No volume data available to safe guard system; shutting down all machines.'); this.logger.warn('No volume data available to safe guard system; shutting down all machines.');
this.safetyControllerActive = true;
return; return;
} }
//get threshholds from config //get threshholds from config
const timeThreshhold = this.config.control.timeThreshholdSeconds; //seconds const timeThreshhold = this.config.safety.timeleftToFullOrEmptyThresholdSeconds; //seconds
const triggerHighVol = this.basin.maxVolOverflow * ( this.config.control.thresholdHighVolume/100 ); const triggerHighVol = this.basin.maxVolOverflow * ( this.config.safety.overfillThresholdPercent/100 );
const triggerLowVol = this.basin.minVolOut * ( this.config.control.thresholdLowVolume/100); const triggerLowVol = this.basin.minVolOut * ( this.config.safety.dryRunThresholdPercent/100 );
// trigger conditions for draining // trigger conditions for draining
if(direction == "draining"){ if(direction == "draining"){
@@ -127,13 +127,90 @@ class PumpingStation {
group.handleInput('parent', 'execSequence', 'shutdown'); 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.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 //calibrate the predicted volume to a known value
@@ -178,11 +255,15 @@ class PumpingStation {
this._updatePredictedVolume(snapshot); this._updatePredictedVolume(snapshot);
const netFlow = this._selectBestNetFlow(snapshot); const netFlow = this._selectBestNetFlow(snapshot);
const remaining = this._computeRemainingTime(snapshot, netFlow); 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 = { this.state = {
direction: netFlow.direction, direction: netFlow.direction,