forked from RnD/pumpingStation
fixes
This commit is contained in:
@@ -88,11 +88,11 @@ class PumpingStation {
|
|||||||
//this.logger.warn(`Unsupported child software type: ${softwareType}`);
|
//this.logger.warn(`Unsupported child software type: ${softwareType}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
_safetyController(snapshot,remainingTime,direction){
|
_safetyController(remainingTime, direction){
|
||||||
|
|
||||||
this.safetyControllerActive = false;
|
this.safetyControllerActive = false;
|
||||||
|
|
||||||
const vol = this._resolveVolume(snapshot);
|
const vol = this._resolveVolume();
|
||||||
|
|
||||||
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.
|
||||||
@@ -200,21 +200,22 @@ class PumpingStation {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
async _controlLevelBased(snapshot, direction) {
|
async _controlLevelBased(direction) {
|
||||||
const { startLevel, stopLevel } = this.config.control.levelbased;
|
const { startLevel, stopLevel } = this.config.control.levelbased;
|
||||||
|
|
||||||
const flowUnit = this.measurements.getUnit('flow'); // use container as source of truth
|
const flowUnit = this.measurements.getUnit('flow'); // use container as source of truth
|
||||||
|
const levelunit = this.measurements.getUnit('level'); // use container as source of truth
|
||||||
|
|
||||||
let percControl = 0;
|
let percControl = 0;
|
||||||
|
|
||||||
const level = (snap) => {
|
for (const variant of this.levelVariants) {
|
||||||
for (const variant of this.levelVariants) {
|
|
||||||
const levelSnap = snap.levels?.[variant];
|
const level = this.measurements.type('level').variant(variant).postition('atEquipment').getCurrentValue(levelunit);
|
||||||
if (levelSnap?.samples?.current?.value !== undefined) {
|
|
||||||
return levelSnap.samples.current.value;
|
if(!level) continue;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return null;
|
|
||||||
};
|
|
||||||
|
|
||||||
const levelVal = level(snapshot);
|
const levelVal = level(snapshot);
|
||||||
if (levelVal == null || !Number.isFinite(levelVal)) {
|
if (levelVal == null || !Number.isFinite(levelVal)) {
|
||||||
@@ -288,11 +289,15 @@ class PumpingStation {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_resolveVolume() {
|
||||||
_resolveVolume(snapshot) {
|
|
||||||
for (const variant of this.volVariants) {
|
for (const variant of this.volVariants) {
|
||||||
const volsnap = snapshot.vols[variant];
|
const type = 'volume';
|
||||||
if (volsnap?.samples?.exists) return volsnap.samples.current?.value ?? null;
|
const unit = this.measurements.getUnit(type);
|
||||||
|
const volume = this.measurements.type(type).variant(variant).position('atEquipment').getCurrentValue(unit) || null;
|
||||||
|
|
||||||
|
if (!volume) continue;
|
||||||
|
|
||||||
|
return volume;
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -408,7 +413,7 @@ 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(netFlow);
|
||||||
|
|
||||||
//check safety conditions
|
//check safety conditions
|
||||||
this._safetyController(snapshot,remaining.seconds,netFlow.direction);
|
this._safetyController(snapshot,remaining.seconds,netFlow.direction);
|
||||||
@@ -501,28 +506,14 @@ class PumpingStation {
|
|||||||
.child(childId)
|
.child(childId)
|
||||||
.value(eventData.value, ts, unit);
|
.value(eventData.value, ts, unit);
|
||||||
|
|
||||||
this._refreshAggregatedPredictedFlow();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
eventNames.forEach((eventName) => child.measurements.emitter.on(eventName, handler));
|
eventNames.forEach((eventName) => child.measurements.emitter.on(eventName, handler));
|
||||||
}
|
}
|
||||||
|
|
||||||
_refreshAggregatedPredictedFlow() {
|
|
||||||
const preferredUnit = this.measurements.getUnit('flow');
|
|
||||||
const childPositions = Object.keys(this.measurements.measurements?.flow?.predictedChild || {});
|
|
||||||
const inflowPositions = childPositions.filter((p) => p === 'in');
|
|
||||||
const outflowPositions = childPositions.filter((p) => p === 'out');
|
|
||||||
|
|
||||||
const sumIn = this.measurements.sum('flow', 'predicted', inflowPositions, preferredUnit);
|
|
||||||
const sumOut = this.measurements.sum('flow', 'predicted', outflowPositions, preferredUnit);
|
|
||||||
|
|
||||||
this.measurements.type('flow').variant('predicted').position('in').value(sumIn, Date.now(), preferredUnit);
|
|
||||||
this.measurements.type('flow').variant('predicted').position('out').value(sumOut, Date.now(), preferredUnit);
|
|
||||||
}
|
|
||||||
|
|
||||||
setManualInflow(value, timestamp = Date.now(), unit) {
|
setManualInflow(value, timestamp = Date.now(), unit) {
|
||||||
const num = Number(value);
|
const num = Number(value);
|
||||||
const unit = this.measurements.getUnit('flow');
|
|
||||||
|
|
||||||
// Write manual inflow into the aggregated bucket
|
// Write manual inflow into the aggregated bucket
|
||||||
this.measurements
|
this.measurements
|
||||||
@@ -532,7 +523,6 @@ class PumpingStation {
|
|||||||
.child('manual-qin')
|
.child('manual-qin')
|
||||||
.value(num, timestamp, unit);
|
.value(num, timestamp, unit);
|
||||||
|
|
||||||
this._refreshAggregatedPredictedFlow();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_handleMeasurement(measurementType, value, position, context) {
|
_handleMeasurement(measurementType, value, position, context) {
|
||||||
@@ -718,23 +708,64 @@ class PumpingStation {
|
|||||||
}
|
}
|
||||||
|
|
||||||
_selectBestNetFlow(snapshot) {
|
_selectBestNetFlow(snapshot) {
|
||||||
|
const type = 'flow';
|
||||||
|
const unit = this.measurements.getUnit(type) || 'm3/s';
|
||||||
|
|
||||||
for (const variant of this.flowVariants) {
|
for (const variant of this.flowVariants) {
|
||||||
const flow = snapshot.flows[variant];
|
// Check if we have *any* flows for this variant at all
|
||||||
|
const bucket = this.measurements.measurements?.[type]?.[variant];
|
||||||
|
if (!bucket || Object.keys(bucket).length === 0) {
|
||||||
|
this.logger.debug(`No ${type}.${variant} data; skipping this variant`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
if (!flow.inflow.exists && !flow.outflow.exists) continue;
|
// Sum all inflow/outflow positions (in/upstream vs out/downstream)
|
||||||
|
const inflow = this.measurements.sum(
|
||||||
|
type,
|
||||||
|
variant,
|
||||||
|
this.flowPositions.inflow,
|
||||||
|
unit
|
||||||
|
) || 0;
|
||||||
|
|
||||||
const unit = this.measurements.getUnit('flow');
|
const outflow = this.measurements.sum(
|
||||||
const inflow = flow.inflow.current?.value ?? flow.inflow.previous?.value ?? 0;
|
type,
|
||||||
const outflow = flow.outflow.current?.value ?? flow.outflow.previous?.value ?? 0;
|
variant,
|
||||||
const net = inflow - outflow; // -> pos is filling
|
this.flowPositions.outflow,
|
||||||
|
unit
|
||||||
|
) || 0;
|
||||||
|
|
||||||
this.measurements.type('netFlowRate').variant(variant).position('atequipment').value(net, Date.now(), unit);
|
// If absolutely nothing is flowing and bucket only just got created,
|
||||||
this.logger.debug(`inflow : ${inflow} - outflow : ${outflow}`);
|
// we can choose to skip this variant and try the next one.
|
||||||
|
const absIn = Math.abs(inflow);
|
||||||
|
const absOut = Math.abs(outflow);
|
||||||
|
|
||||||
return { value: net,source: variant,direction: this._deriveDirection(net) };
|
if (absIn < this.flowThreshold && absOut < this.flowThreshold) {
|
||||||
|
this.logger.debug(
|
||||||
|
`Flows for ${type}.${variant} below threshold; inflow=${inflow}, outflow=${outflow}, trying next variant`
|
||||||
|
);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const net = inflow - outflow; // >0 = filling
|
||||||
|
|
||||||
|
this.measurements
|
||||||
|
.type('netFlowRate')
|
||||||
|
.variant(variant)
|
||||||
|
.position('atequipment')
|
||||||
|
.value(net, Date.now(), unit);
|
||||||
|
|
||||||
|
this.logger.debug(
|
||||||
|
`netFlow (${variant}): inflow=${inflow} ${unit}, outflow=${outflow} ${unit}, net=${net} ${unit}`
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
value: net,
|
||||||
|
source: variant,
|
||||||
|
direction: this._deriveDirection(net)
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// fallback using level trend
|
// --- Fallback: level trend
|
||||||
for (const variant of this.levelVariants) {
|
for (const variant of this.levelVariants) {
|
||||||
const levelRate = snapshot.levelRates[variant];
|
const levelRate = snapshot.levelRates[variant];
|
||||||
if (!Number.isFinite(levelRate)) continue;
|
if (!Number.isFinite(levelRate)) continue;
|
||||||
@@ -751,7 +782,9 @@ class PumpingStation {
|
|||||||
return { value: 0, source: null, direction: 'steady' };
|
return { value: 0, source: null, direction: 'steady' };
|
||||||
}
|
}
|
||||||
|
|
||||||
_computeRemainingTime(snapshot, netFlow) {
|
|
||||||
|
_computeRemainingTime(netFlow) {
|
||||||
|
|
||||||
if (!netFlow || Math.abs(netFlow.value) < this.flowThreshold) {
|
if (!netFlow || Math.abs(netFlow.value) < this.flowThreshold) {
|
||||||
return { seconds: null, source: null };
|
return { seconds: null, source: null };
|
||||||
}
|
}
|
||||||
@@ -761,17 +794,22 @@ class PumpingStation {
|
|||||||
this.logger.warn('Invalid basin surface area.');
|
this.logger.warn('Invalid basin surface area.');
|
||||||
return { seconds: null, source: null };
|
return { seconds: null, source: null };
|
||||||
}
|
}
|
||||||
|
const type = 'level';
|
||||||
|
const unit = this.measurements.getUnit(type);
|
||||||
for (const variant of this.levelVariants) {
|
for (const variant of this.levelVariants) {
|
||||||
const levelSnap = snapshot.levels[variant];
|
|
||||||
const current = levelSnap.samples.current?.value ?? null;
|
const lvl = this.measurements
|
||||||
if (!Number.isFinite(current)) continue;
|
.type(type)
|
||||||
|
.variant(variant)
|
||||||
|
.position('atequipment')
|
||||||
|
.getCurrentValue()
|
||||||
|
|
||||||
|
if (!Number.isFinite(lvl)) continue;
|
||||||
|
|
||||||
const remainingHeight =
|
const remainingHeight =
|
||||||
netFlow.value > 0
|
netFlow.value > 0
|
||||||
? Math.max(heightOverflow - current, 0)
|
? Math.max(heightOverflow - lvl, 0)
|
||||||
: Math.max(current - heightOutlet, 0);
|
: Math.max(lvl - heightOutlet, 0);
|
||||||
|
|
||||||
const seconds = (remainingHeight * surfaceArea) / Math.abs(netFlow.value);
|
const seconds = (remainingHeight * surfaceArea) / Math.abs(netFlow.value);
|
||||||
if (!Number.isFinite(seconds)) continue;
|
if (!Number.isFinite(seconds)) continue;
|
||||||
@@ -956,7 +994,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');
|
||||||
@@ -1101,24 +1139,27 @@ if (require.main === module) {
|
|||||||
//seedSample(inflowSensor, 'flow', 0.35, 'm3/s');
|
//seedSample(inflowSensor, 'flow', 0.35, 'm3/s');
|
||||||
//seedSample(outflowSensor, 'flow', 0.20, 'm3/s');
|
//seedSample(outflowSensor, 'flow', 0.20, 'm3/s');
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
setInterval(
|
setInterval(
|
||||||
() => station.tick(), 1000);
|
() => station.tick(), 1000);
|
||||||
|
|
||||||
await new Promise((resolve) => setTimeout(resolve, 10));
|
await new Promise((resolve) => setTimeout(resolve, 10));
|
||||||
|
|
||||||
console.log('Initial state:', station.state);
|
console.log('Initial state:', station.state);
|
||||||
|
station.setManualInflow(10,Date.now(),'l/s');
|
||||||
|
|
||||||
await pump1.handleInput('parent', 'execSequence', 'startup');
|
await pump1.handleInput('parent', 'execSequence', 'startup');
|
||||||
await pump1.handleInput('parent', 'execMovement', 10);
|
await pump1.handleInput('parent', 'execMovement', 10);
|
||||||
|
|
||||||
await pump2.handleInput('parent', 'execSequence', 'startup');
|
await pump2.handleInput('parent', 'execSequence', 'startup');
|
||||||
await pump2.handleInput('parent', 'execMovement', 10);
|
await pump2.handleInput('parent', 'execMovement', 10);
|
||||||
|
|
||||||
|
|
||||||
console.log('Station state:', station.state);
|
console.log('Station state:', station.state);
|
||||||
console.log('Station output:', station.getOutput());
|
console.log('Station output:', station.getOutput());
|
||||||
})().catch((err) => {
|
})().catch((err) => {
|
||||||
console.error('Demo failed:', err);
|
console.error('Demo failed:', err);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
//*/
|
//*/
|
||||||
|
|||||||
Reference in New Issue
Block a user