forked from RnD/pumpingStation
rebuilding pumping station NOT WORKING
This commit is contained in:
@@ -223,12 +223,12 @@ class nodeClass {
|
|||||||
.variant('measured')
|
.variant('measured')
|
||||||
.position('atequipment')
|
.position('atequipment')
|
||||||
.getCurrentValue('m3');
|
.getCurrentValue('m3');
|
||||||
this.source.calibratePredictedVolume(calibratedVolume);
|
this.source.calibratePredictedVolume(calibratedVolume);
|
||||||
break;
|
break;
|
||||||
case 'q_in': {
|
case 'q_in': {
|
||||||
// payload can be number or { value, unit, timestamp }
|
// payload can be number or { value, unit, timestamp }
|
||||||
const val = Number(msg.payload);
|
const val = Number(msg.payload);
|
||||||
const unit = msg?.unit || 'm3/s';
|
const unit = msg?.unit || 'l/s';
|
||||||
const ts = msg?.timestamp || Date.now();
|
const ts = msg?.timestamp || Date.now();
|
||||||
this.source.setManualInflow(val, ts, unit);
|
this.source.setManualInflow(val, ts, unit);
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -11,12 +11,11 @@ class PumpingStation {
|
|||||||
this.interpolate = new interpolation();
|
this.interpolate = new interpolation();
|
||||||
this.logger = new logger(this.config.general.logging.enabled,this.config.general.logging.logLevel,this.config.general.name);
|
this.logger = new logger(this.config.general.logging.enabled,this.config.general.logging.logLevel,this.config.general.name);
|
||||||
|
|
||||||
this.measurements = new MeasurementContainer({ autoConvert: true });
|
this.measurements = new MeasurementContainer({
|
||||||
this.preferredUnits = {flow: "m3/s"};
|
autoConvert: true,
|
||||||
this.measurements.setPreferredUnit('flow', this.preferredUnits.flow);
|
preferredUnits: { flow: 'm3/s', netFlowRate: 'm3/s', level: 'm', volume: 'm3' }
|
||||||
this.measurements.setPreferredUnit('netFlowRate', 'm3/s');
|
});
|
||||||
this.measurements.setPreferredUnit('level', 'm');
|
|
||||||
this.measurements.setPreferredUnit('volume', 'm3');
|
|
||||||
|
|
||||||
this.childRegistrationUtils = new childRegistrationUtils(this);
|
this.childRegistrationUtils = new childRegistrationUtils(this);
|
||||||
this.machines = {};
|
this.machines = {};
|
||||||
@@ -196,19 +195,20 @@ class PumpingStation {
|
|||||||
|
|
||||||
_scaleLevelToFlowPercent(level,minflow,maxflow) {
|
_scaleLevelToFlowPercent(level,minflow,maxflow) {
|
||||||
const { minFlowLevel, maxFlowLevel } = this.config.control.levelbased;
|
const { minFlowLevel, maxFlowLevel } = this.config.control.levelbased;
|
||||||
const output = this.interpolate_lin_single_point(level,minFlowLevel,maxFlowLevel,minflow,maxflow);
|
const output = this.interpolate.interpolate_lin_single_point(level,minFlowLevel,maxFlowLevel,minflow,maxflow);
|
||||||
return output;
|
return output;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
async _controlLevelBased(snapshot) {
|
async _controlLevelBased(snapshot, 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
|
||||||
|
|
||||||
//pick level prefering measured then predicted
|
let percControl = 0;
|
||||||
const level = (snapshot) => {
|
|
||||||
|
const level = (snap) => {
|
||||||
for (const variant of this.levelVariants) {
|
for (const variant of this.levelVariants) {
|
||||||
const levelSnap = snapshot.levels?.[variant];
|
const levelSnap = snap.levels?.[variant];
|
||||||
|
|
||||||
if (levelSnap?.samples?.current?.value !== undefined) {
|
if (levelSnap?.samples?.current?.value !== undefined) {
|
||||||
return levelSnap.samples.current.value;
|
return levelSnap.samples.current.value;
|
||||||
}
|
}
|
||||||
@@ -216,47 +216,41 @@ class PumpingStation {
|
|||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
|
|
||||||
if (level == null) {
|
const levelVal = level(snapshot);
|
||||||
|
if (levelVal == null || !Number.isFinite(levelVal)) {
|
||||||
this.logger.warn('No valid level found');
|
this.logger.warn('No valid level found');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(level > startLevel){
|
if (levelVal > startLevel && direction === 'filling') {
|
||||||
let maxFlow = 0;
|
let sumFlow = 0;
|
||||||
let minTotalFlow = Infinity;
|
let minTotalFlow = Infinity;
|
||||||
Object.entries(this.machines).forEach(([machineId, machine]) => {
|
Object.values(this.machines).forEach((m) => {
|
||||||
sumFlow =+ machine.measurements.type('flow').variant('predicted').variant('max').getCurrentValue(this.preferredUnits.flow);
|
const max = m.measurements.type('flow').variant('predicted').position('max').getCurrentValue(flowUnit);
|
||||||
const minflow = machine.measurements.type('flow').variant('predicted').variant('min').getCurrentValue(this.preferredUnits.flow);
|
const min = m.measurements.type('flow').variant('predicted').position('min').getCurrentValue(flowUnit);
|
||||||
if(minTotalFlow < minflow){ minflow = minTotalFlow};
|
if (Number.isFinite(max)) sumFlow += max;
|
||||||
|
if (Number.isFinite(min) && min < minTotalFlow) minTotalFlow = min;
|
||||||
});
|
});
|
||||||
|
|
||||||
Object.entries(this.machineGroups).forEach(([groupId, group]) => {
|
this.logger.debug(`showing level : ${levelVal}, minTotalFlow: ${minTotalFlow}, sumFlow ${sumFlow}`);
|
||||||
sumFlow = group.dynamicTotals.flow.max;
|
percControl = this._scaleLevelToFlowPercent(levelVal, minTotalFlow, sumFlow);
|
||||||
|
await this._applyIdleMachineLevelControl(percControl);
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
//start machines and or give group % control
|
|
||||||
Object.entries(this.machines).forEach(([machineId, machine]) => {
|
|
||||||
const position = machine?.config?.functionality?.positionVsParent;
|
|
||||||
if ((position === 'downstream' || position === 'atEquipment') && machine._isOperationalState()) {
|
|
||||||
machine.handleInput('parent', 'execSequence', 'startup');
|
|
||||||
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.machineGroups).forEach(([groupId, group]) => {
|
|
||||||
group.handleInput(Qd);
|
|
||||||
this.logger.warn(`Safe guard triggered: vol=${vol.toFixed(2)} m3, remainingTime=${remainingTime ? remainingTime.toFixed(1) : 'N/A'} s; shutting down machine group "${groupId}"`);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const percControl = this._scaleLevelToFlowPercent(level);
|
if (levelVal < stopLevel && direction === 'draining') {
|
||||||
|
Object.entries(this.machines).forEach(([machineId, machine]) => {
|
||||||
|
const position = machine?.config?.functionality?.positionVsParent;
|
||||||
await this._applyMachineGroupLevelControl(percControl);
|
if ((position === 'downstream' || position === 'atEquipment') && machine._isOperationalState()) {
|
||||||
await this._applyIdleMachineLevelControl(percControl);
|
machine.handleInput('parent', 'execSequence', 'shutdown');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
Object.entries(this.stations).forEach(([stationId, station]) => {
|
||||||
|
station.handleInput('parent', 'execSequence', 'shutdown');
|
||||||
|
});
|
||||||
|
Object.entries(this.machineGroups).forEach(([groupId, group]) => {
|
||||||
|
group.turnOffAllMachines();
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async _applyMachineGroupLevelControl(percentControl) {
|
async _applyMachineGroupLevelControl(percentControl) {
|
||||||
@@ -311,13 +305,13 @@ class PumpingStation {
|
|||||||
}
|
}
|
||||||
|
|
||||||
//control logic
|
//control logic
|
||||||
_controlLogic(snapshot){
|
_controlLogic(snapshot,direction){
|
||||||
const mode = this.mode;
|
const mode = this.mode;
|
||||||
|
|
||||||
switch(mode){
|
switch(mode){
|
||||||
case "levelbased":
|
case "levelbased":
|
||||||
this.logger.debug(`Executing level-based control logic`);
|
this.logger.debug(`Executing level-based control logic`);
|
||||||
this._controlLevelBased(snapshot);
|
this._controlLevelBased(snapshot,direction);
|
||||||
break;
|
break;
|
||||||
case "flowbased":
|
case "flowbased":
|
||||||
this._controlFlowBased();
|
this._controlFlowBased();
|
||||||
@@ -421,7 +415,7 @@ class PumpingStation {
|
|||||||
if(this.safetyControllerActive) return;
|
if(this.safetyControllerActive) return;
|
||||||
|
|
||||||
//if safety not active proceed with normal control
|
//if safety not active proceed with normal control
|
||||||
this._controlLogic(snapshot,remaining.seconds);
|
this._controlLogic(snapshot,netFlow.direction);
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
direction: netFlow.direction,
|
direction: netFlow.direction,
|
||||||
@@ -495,58 +489,50 @@ class PumpingStation {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const handler = (eventData = {}) => {
|
const handler = (eventData = {}) => {
|
||||||
const preferred = this.preferredUnits?.flow || 'm3/s';
|
const flowUnit = this.measurements.getUnit('flow');
|
||||||
const unit = eventData.unit || 'l/s'; // don’t assume m3/s
|
const unit = eventData.unit || child.config?.general?.unit || flowUnit;
|
||||||
const raw = Number.isFinite(eventData.value) ? eventData.value : 0;
|
|
||||||
const ts = eventData.timestamp || Date.now();
|
const ts = eventData.timestamp || Date.now();
|
||||||
const normalized = convert(raw).from(unit).to(preferred);
|
const posKeyBase = posKey; // 'in' or 'out'
|
||||||
this.predictedFlowChildren.get(childId)[posKey] = normalized;
|
|
||||||
this._refreshAggregatedPredictedFlow(posKey, ts, preferred);
|
this.measurements
|
||||||
|
.type('flow')
|
||||||
|
.variant('predicted')
|
||||||
|
.position(posKeyBase)
|
||||||
|
.child(childId)
|
||||||
|
.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(direction) {
|
_refreshAggregatedPredictedFlow() {
|
||||||
const preferredUnit = this.preferredUnits.flow;
|
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 sum = Array.from(this.predictedFlowChildren.values())
|
const sumIn = this.measurements.sum('flow', 'predicted', inflowPositions, preferredUnit);
|
||||||
.map((entry) => {
|
const sumOut = this.measurements.sum('flow', 'predicted', outflowPositions, preferredUnit);
|
||||||
const v = entry[direction];
|
|
||||||
const num = Number(v?.value ?? v);
|
|
||||||
if (!Number.isFinite(num)) return 0;
|
|
||||||
const unit = v?.unit || preferredUnit; // default if none stored
|
|
||||||
return convert(num).from(unit).to(preferredUnit);
|
|
||||||
})
|
|
||||||
.reduce((acc, val) => acc + val, 0);
|
|
||||||
|
|
||||||
this.measurements
|
this.measurements.type('flow').variant('predicted').position('in').value(sumIn, Date.now(), preferredUnit);
|
||||||
.type('flow')
|
this.measurements.type('flow').variant('predicted').position('out').value(sumOut, Date.now(), preferredUnit);
|
||||||
.variant('predicted')
|
|
||||||
.position(direction)
|
|
||||||
.value(sum, Date.now(), preferredUnit);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
setManualInflow(value, timestamp = Date.now(), unit) {
|
setManualInflow(value, timestamp = Date.now(), unit) {
|
||||||
const num = Number(value);
|
const num = Number(value);
|
||||||
const preferredUnit = this.preferredUnits.flow;
|
const unit = this.measurements.getUnit('flow');
|
||||||
|
|
||||||
// Store the manual inflow in the measurement container with its source unit.
|
// Write manual inflow into the aggregated bucket
|
||||||
this.measurements.type('flow').variant('manual').position('in').value(num, timestamp, unit);
|
this.measurements
|
||||||
|
|
||||||
// Read back in preferred units so the aggregated predicted flow uses consistent units.
|
|
||||||
const entry = this.measurements
|
|
||||||
.type('flow')
|
.type('flow')
|
||||||
.variant('manual')
|
.variant('predicted')
|
||||||
.position('in')
|
.position('in')
|
||||||
.getCurrentValue(preferredUnit);
|
.child('manual-qin')
|
||||||
|
.value(num, timestamp, unit);
|
||||||
|
|
||||||
const predFlow = this.predictedFlowChildren.get('manual-qin') || { in: 0, out: 0 };
|
this._refreshAggregatedPredictedFlow();
|
||||||
predFlow.in = Number.isFinite(entry) ? entry : 0;
|
|
||||||
this.predictedFlowChildren.set('manual-qin', predFlow);
|
|
||||||
|
|
||||||
// Pass preferred unit so we don't double-convert when writing the aggregate series.
|
|
||||||
this._refreshAggregatedPredictedFlow('in', timestamp, preferredUnit);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_handleMeasurement(measurementType, value, position, context) {
|
_handleMeasurement(measurementType, value, position, context) {
|
||||||
@@ -737,11 +723,12 @@ class PumpingStation {
|
|||||||
|
|
||||||
if (!flow.inflow.exists && !flow.outflow.exists) continue;
|
if (!flow.inflow.exists && !flow.outflow.exists) continue;
|
||||||
|
|
||||||
const inflow = flow.inflow.current?.value ?? 0;
|
const unit = this.measurements.getUnit('flow');
|
||||||
const outflow = flow.outflow.current?.value ?? 0;
|
const inflow = flow.inflow.current?.value ?? flow.inflow.previous?.value ?? 0;
|
||||||
const net = inflow - outflow; // positive => filling
|
const outflow = flow.outflow.current?.value ?? flow.outflow.previous?.value ?? 0;
|
||||||
|
const net = inflow - outflow; // -> pos is filling
|
||||||
|
|
||||||
this.measurements.type('netFlowRate').variant(variant).position('atequipment').value(net).unit('m3/s');
|
this.measurements.type('netFlowRate').variant(variant).position('atequipment').value(net, Date.now(), unit);
|
||||||
this.logger.debug(`inflow : ${inflow} - outflow : ${outflow}`);
|
this.logger.debug(`inflow : ${inflow} - outflow : ${outflow}`);
|
||||||
|
|
||||||
return { value: net,source: variant,direction: this._deriveDirection(net) };
|
return { value: net,source: variant,direction: this._deriveDirection(net) };
|
||||||
@@ -945,14 +932,8 @@ class PumpingStation {
|
|||||||
/* ------------------------------------------------------------------ */
|
/* ------------------------------------------------------------------ */
|
||||||
|
|
||||||
getOutput() {
|
getOutput() {
|
||||||
const output = {};
|
|
||||||
Object.entries(this.measurements.measurements).forEach(([type, variants]) => {
|
const output = this.measurements.getFlattenedOutput();
|
||||||
Object.entries(variants).forEach(([variant, positions]) => {
|
|
||||||
Object.entries(positions).forEach(([position, measurement]) => {
|
|
||||||
output[`${type}.${variant}.${position}`] = measurement.getCurrentValue();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
output.direction = this.state.direction;
|
output.direction = this.state.direction;
|
||||||
output.flowSource = this.state.flowSource;
|
output.flowSource = this.state.flowSource;
|
||||||
|
|||||||
Reference in New Issue
Block a user