added safeguarding when vol gets too low for machines,

This commit is contained in:
znetsixe
2025-11-07 15:07:56 +01:00
parent 9e4b149b64
commit 43eb97407f
2 changed files with 117 additions and 17 deletions

View File

@@ -204,6 +204,14 @@ class nodeClass {
const childObj = this.RED.nodes.getNode(childId); const childObj = this.RED.nodes.getNode(childId);
this.source.childRegistrationUtils.registerChild(childObj.source ,msg.positionVsParent); this.source.childRegistrationUtils.registerChild(childObj.source ,msg.positionVsParent);
break; break;
case 'calibratePredictedVolume':
const calibratedVolume = this.source.measurements
.type('volume')
.variant('measured')
.position('atequipment')
.getCurrentValue('m3');
this.source.calibratePredictedVolume(calibratedVolume);
break;
} }
done(); done();
}); });

View File

@@ -16,9 +16,9 @@ class PumpingStation {
this.measurements.setPreferredUnit('level', 'm'); this.measurements.setPreferredUnit('level', 'm');
this.measurements.setPreferredUnit('volume', 'm3'); this.measurements.setPreferredUnit('volume', 'm3');
this.childRegistrationUtils = new childRegistrationUtils(this); this.childRegistrationUtils = new childRegistrationUtils(this);
this.machines = {}; this.machines = {};
this.stations = {}; this.stations = {};
this.machineGroups = {};
//variants in determining what gets priority //variants in determining what gets priority
this.flowVariants = ['measured', 'predicted']; this.flowVariants = ['measured', 'predicted'];
@@ -51,15 +51,36 @@ class PumpingStation {
return; return;
} }
//for machines register them for control
if(softwareType === 'machine'){
const childId = child.config.general.id;
this.machines[childId] = child;
this.logger.debug(`Registered machine child "${child.config.general.name}" with id "${childId}"`);
}
// for pumping stations register them for control
if(softwareType === 'pumpingStation'){
const childId = child.config.general.id;
this.stations[childId] = child;
this.logger.debug(`Registered pumping station child "${child.config.general.name}" with id "${childId}"`);
}
// for machine group controllers register them for control
if(softwareType === 'machineGroupController'){
const childId = child.config.general.id;
this.machineGroups[childId] = child;
this.logger.debug(`Registered machine group controller child "${child.config.general.name}" with id "${childId}"`);
}
//for all childs that can provide predicted flow data
if (softwareType === 'machine' || softwareType === 'pumpingStation' || softwareType === 'machineGroupController') { if (softwareType === 'machine' || softwareType === 'pumpingStation' || softwareType === 'machineGroupController') {
this._registerPredictedFlowChild(child); this._registerPredictedFlowChild(child);
return;
} }
this.logger.warn(`Unsupported child software type: ${softwareType}`); this.logger.warn(`Unsupported child software type: ${softwareType}`);
} }
_safeGuardSystem(snapshot,remainingTime){ _safeGuardSystem(snapshot,remainingTime,direction){
let vol = null; let vol = null;
for (const variant of this.volVariants){ for (const variant of this.volVariants){
@@ -67,17 +88,89 @@ class PumpingStation {
//go through with variants until we find one that exists //go through with variants until we find one that exists
if (!volsnap.samples.exists){ continue}; if (!volsnap.samples.exists){ continue};
const vol = volsnap.samples.current?.value ?? null; vol = volsnap.samples.current?.value ?? null;
} }
if(vol == null){ if(vol == null){
//if we cant get a volume, we must force whole system off. //if we cant get a volume we cant control blind turn all pumps off.
Object.entries(this.machines).forEach(([machineId, machine]) => {
machine.handleInput('parent', 'execSequence', 'shutdown');
});
this.logger.warn('No volume data available to safe guard system; shutting down all machines.');
return;
}
}; //get threshholds from config
/* const timeThreshhold = this.config.control.timeThreshholdSeconds; //seconds
if(remainingTime < timeThreshhold || vol > maxVolume || vol < minVolume){} const triggerHighVol = this.basin.maxVolOverflow * ( this.config.control.thresholdHighVolume/100 );
*/ const triggerLowVol = this.basin.minVolOut * ( this.config.control.thresholdLowVolume/100);
// trigger conditions for draining
if(direction == "draining"){
this.logger.debug(
`Safe-guard (draining): vol=${vol != null ? vol.toFixed(2) + ' m3' : 'N/A'}; ` +
`remainingTime=${Number.isFinite(remainingTime) ? remainingTime.toFixed(1) + ' s' : 'N/A'}; ` +
`direction=${String(direction)}; triggerLowVol=${Number.isFinite(triggerLowVol) ? triggerLowVol.toFixed(2) + ' m3' : 'N/A'}`
);
if( (remainingTime < timeThreshhold && remainingTime !== null) || vol < triggerLowVol || vol == null){
//shut down all downstream or atequipment machines,pumping stations and machine groups
Object.entries(this.machines).forEach(([machineId, machine]) => {
machine.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 "${machineId}"`);
});
Object.entries(this.stations).forEach(([stationId, station]) => {
station.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 station "${stationId}"`);
});
Object.entries(this.machineGroups).forEach(([groupId, group]) => {
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}"`);
});
}
}
// trigger conditions for filling
if(direction == "filling"){
}
}
//calibrate the predicted volume to a known value
calibratePredictedVolume(calibratedVol, timestamp = Date.now()){
const volumeChain = this.measurements
.type('volume')
.variant('predicted')
.position('atequipment');
//if we have existing values clear them out
const volumeMeasurement = volumeChain.exists() ? volumeChain.get() : null;
if (volumeMeasurement) {
volumeMeasurement.values = [];
volumeMeasurement.timestamps = [];
}
volumeChain.value(calibratedVol, timestamp, 'm3').unit('m3');
const levelChain = this.measurements
.type('level')
.variant('predicted')
.position('atequipment');
const levelMeasurement = levelChain.exists() ? levelChain.get() : null;
if (levelMeasurement) {
levelMeasurement.values = [];
levelMeasurement.timestamps = [];
}
levelChain.value(this._calcLevelFromVolume(calibratedVol), timestamp, 'm');
this._predictedFlowState = {
inflow: 0,
outflow: 0,
lastTimestamp: timestamp
};
} }
tick() { tick() {
@@ -89,7 +182,7 @@ class PumpingStation {
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); this._safeGuardSystem(snapshot,remaining.seconds,netFlow.direction);
this.state = { this.state = {
direction: netFlow.direction, direction: netFlow.direction,
@@ -151,9 +244,7 @@ class PumpingStation {
const timestamp = eventData.timestamp ?? Date.now(); const timestamp = eventData.timestamp ?? Date.now();
const unit = eventData.unit ?? 'm3/s'; const unit = eventData.unit ?? 'm3/s';
this.logger.debug( this.logger.debug(`Predicted flow update from ${childName} (${childId}, ${posKey}) -> ${value} ${unit}`);
`Predicted flow update from ${childName} (${childId}, ${posKey}) -> ${value} ${unit}`
);
this.predictedFlowChildren.get(childId)[posKey] = value; this.predictedFlowChildren.get(childId)[posKey] = value;
this._refreshAggregatedPredictedFlow(posKey, timestamp, unit); this._refreshAggregatedPredictedFlow(posKey, timestamp, unit);
@@ -201,6 +292,7 @@ class PumpingStation {
_onLevelMeasurement(position, value, context = {}) { _onLevelMeasurement(position, value, context = {}) {
const levelSeries = this.measurements.type('level').variant('measured').position(position); const levelSeries = this.measurements.type('level').variant('measured').position(position);
const levelMeters = levelSeries.getCurrentValue('m'); const levelMeters = levelSeries.getCurrentValue('m');
if (levelMeters == null) return; if (levelMeters == null) return;
const volume = this._calcVolumeFromLevel(levelMeters); const volume = this._calcVolumeFromLevel(levelMeters);
@@ -598,7 +690,7 @@ module.exports = PumpingStation;
/* ------------------------------------------------------------------------- */ /* ------------------------------------------------------------------------- */
/* Example usage */ /* Example usage */
/* ------------------------------------------------------------------------- */ /* ------------------------------------------------------------------------- */
/*
if (require.main === module) { if (require.main === module) {
const Measurement = require('../../measurement/src/specificClass'); const Measurement = require('../../measurement/src/specificClass');
const RotatingMachine = require('../../rotatingMachine/src/specificClass'); const RotatingMachine = require('../../rotatingMachine/src/specificClass');