|
|
|
@@ -18,7 +18,7 @@ class PumpingStation {
|
|
|
|
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
|
|
|
|
|
|
|
|
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
|
|
|
|
};
|
|
|
|
};
|
|
|
|
/*
|
|
|
|
|
|
|
|
if(remainingTime < timeThreshhold || vol > maxVolume || vol < minVolume){}
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
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');
|
|
|
|
|