dev-Rene #1
@@ -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,
|
||||||
|
|||||||
Reference in New Issue
Block a user