Files
pumpingStation/src/specificClass.js
2025-11-30 20:13:21 +01:00

801 lines
28 KiB
JavaScript

const EventEmitter = require('events');
const {
logger,
configUtils,
configManager,
childRegistrationUtils,
MeasurementContainer,
coolprop,
interpolation
} = require('generalFunctions');
class PumpingStation {
constructor(config = {}) {
this.emitter = new EventEmitter();
this.configManager = new configManager();
this.defaultConfig = this.configManager.getConfig('pumpingStation');
this.configUtils = new configUtils(this.defaultConfig);
this.config = this.configUtils.initConfig(config);
this.interpolate = new interpolation();
this.logger = new logger(this.config.general.logging.enabled,this.config.general.logging.logLevel,this.config.general.name);
this.measurements = new MeasurementContainer({
autoConvert: true,
preferredUnits: { flow: 'm3/s', netFlowRate: 'm3/s', level: 'm', volume: 'm3' }
});
this.childRegistrationUtils = new childRegistrationUtils(this);
this.machines = {};
this.stations = {};
this.machineGroups = {};
this.predictedFlowChildren = new Map();
this.flowVariants = ['measured', 'predicted'];
this.levelVariants = ['measured', 'predicted'];
this.volVariants = ['measured', 'predicted'];
this.flowPositions = { inflow: ['in', 'upstream'], outflow: ['out', 'downstream'] };
this.mode = this.config.control.mode;
this._levelState = { crossed: new Set(), dwellUntil: null };
this.state = { direction: 'steady', netFlow: 0, flowSource: null, seconds: null, remainingSource: null };
const thresholdFromConfig = Number(this.config.general?.flowThreshold);
this.flowThreshold = Number.isFinite(thresholdFromConfig) ? thresholdFromConfig : 1e-4;
this.initBasinProperties();
this.logger.debug('PumpingStation initialized');
}
/* --------------------------- Registration --------------------------- */
registerChild(child, softwareType) {
this.logger.debug(`Registering child (${softwareType}) "${child.config.general.name}"`);
if (softwareType === 'measurement') {
this._registerMeasurementChild(child);
return;
}
if (softwareType === 'machine') {
this.machines[child.config.general.id] = child;
} else if (softwareType === 'pumpingstation') {
this.stations[child.config.general.id] = child;
} else if (softwareType === 'machinegroup') {
this.machineGroups[child.config.general.id] = child;
this._registerPredictedFlowChild(child);
}
if (softwareType === 'machine' || softwareType === 'pumpingstation' || softwareType === 'machinegroup') {
this._registerPredictedFlowChild(child);
}
}
_registerMeasurementChild(child) {
const position = child.config.functionality.positionVsParent;
const measurementType = child.config.asset.type;
const eventName = `${measurementType}.measured.${position}`;
child.measurements.emitter.on(eventName, (eventData = {}) => {
this.logger.debug(
`Measurement update ${eventName} <- ${eventData.childName || child.config.general.name}: ${eventData.value} ${eventData.unit}`
);
this.measurements
.type(measurementType)
.variant('measured')
.position(position)
.value(eventData.value, eventData.timestamp, eventData.unit);
this._handleMeasurement(measurementType, eventData.value, position, eventData);
});
}
_registerPredictedFlowChild(child) {
const position = (child.config.functionality.positionVsParent || '').toLowerCase();
const childName = child.config.general.name;
const childId = child.config.general.id ?? childName;
let posKey;
let eventNames;
switch (position) {
case 'downstream':
case 'out':
case 'atequipment':
posKey = 'out';
eventNames = ['flow.predicted.downstream', 'flow.predicted.atequipment'];
break;
case 'upstream':
case 'in':
posKey = 'in';
eventNames = ['flow.predicted.upstream', 'flow.predicted.atequipment'];
break;
default:
this.logger.warn(`Unsupported predicted flow position "${position}" from ${childName}`);
return;
}
if (!this.predictedFlowChildren.has(childId)) {
this.predictedFlowChildren.set(childId, { in: 0, out: 0 });
}
const handler = (eventData = {}) => {
const unit = eventData.unit || child.config?.general?.unit;
const ts = eventData.timestamp || Date.now();
this.logger.debug(`Emitting for child ${unit} `);
this.measurements
.type('flow')
.variant('predicted')
.position(posKey)
.child(childId)
.value(eventData.value, ts, unit);
};
eventNames.forEach((ev) => child.measurements.emitter.on(ev, handler));
}
/* --------------------------- Calibration --------------------------- */
calibratePredictedVolume(calibratedVol, timestamp = Date.now()) {
const volume = this.measurements.type('volume').variant('predicted').position('atequipment').get();
const level = this.measurements.type('level').variant('predicted').position('atequipment').get();
if (volume) {
volume.values = [];
volume.timestamps = [];
}
if (level) {
level.values = [];
level.timestamps = [];
}
this.measurements.type('volume').variant('predicted').position('atequipment').value(calibratedVol, timestamp, 'm3').unit('m3');
this.measurements.type('level').variant('predicted').position('atequipment').value(this._calcLevelFromVolume(calibratedVol), timestamp, 'm');
this._predictedFlowState = { inflow: 0, outflow: 0, lastTimestamp: timestamp };
}
calibratePredictedLevel(val, timestamp = Date.now(), unit = 'm') {
const volumeChain = this.measurements.type('volume').variant('predicted').position('atequipment');
const levelChain = this.measurements.type('level').variant('predicted').position('atequipment');
const volumeMeasurement = volumeChain.exists() ? volumeChain.get() : null;
if (volumeMeasurement) {
volumeMeasurement.values = [];
volumeMeasurement.timestamps = [];
}
const levelMeasurement = levelChain.exists() ? levelChain.get() : null;
if (levelMeasurement) {
levelMeasurement.values = [];
levelMeasurement.timestamps = [];
}
levelChain.value(val, timestamp).unit(unit);
volumeChain.value(this._calcVolumeFromLevel(val), timestamp, 'm3');
this._predictedFlowState = { inflow: 0, outflow: 0, lastTimestamp: timestamp };
}
setManualInflow(value, timestamp = Date.now(), unit) {
const num = Number(value);
this.measurements.type('flow').variant('predicted').position('in').child('manual-qin').value(num, timestamp, unit);
}
/* --------------------------- Tick / Control --------------------------- */
tick() {
this._updatePredictedVolume();
const netFlow = this._selectBestNetFlow();
const remaining = this._computeRemainingTime(netFlow);
this._safetyController(remaining.seconds, netFlow.direction);
if (this.safetyControllerActive) return;
this._controlLogic(netFlow.direction);
this.state = {
direction: netFlow.direction,
netFlow: netFlow.value,
flowSource: netFlow.source,
seconds: remaining.seconds,
remainingSource: remaining.source
};
this.logger.debug(`netflow = ${JSON.stringify(netFlow)}`);
this.logger.debug(
`Height : ${this.measurements.type('level').variant('predicted').position('atequipment').getCurrentValue('m')} m`
);
}
changeMode(newMode){
if ( this.config.control.allowedModes.has(newMode) ){
const currentMode = this.mode;
this.logger.info(`Control mode changing from ${currentMode} to ${newMode}`);
this.mode = newMode;
}
else{
this.logger.warn(`Attempted to change to unsupported control mode: ${newMode}`);
}
}
_controlLogic(direction) {
switch (this.mode) {
case 'levelbased':
this._controlLevelBased(direction);
break;
case 'flowbased':
this._controlFlowBased?.();
break;
case 'manual':
break;
default:
this.logger.warn(`Unsupported control mode: ${this.mode}`);
}
}
async _controlLevelBased(direction) {
const { startLevel, stopLevel } = this.config.control.levelbased;
const flowUnit = this.measurements.getUnit('flow');
const levelUnit = this.measurements.getUnit('level');
const level = this._pickVariant('level', this.levelVariants, 'atequipment', levelUnit);
if (level == null) {
this.logger.warn('No valid level found');
return;
}
if (level > startLevel && direction === 'filling') {
const percControl = this._scaleLevelToFlowPercent(level);
this.logger.debug(`Controllevel based => Level ${level} control applying to pump : ${percControl}`);
await this._applyMachineLevelControl(percControl);
}
if (level < stopLevel && direction === 'draining') {
Object.values(this.machines).forEach((machine) => {
const pos = machine?.config?.functionality?.positionVsParent;
if ((pos === 'downstream' || pos === 'atequipment') && machine._isOperationalState()) {
machine.handleInput('parent', 'execSequence', 'shutdown');
}
});
Object.values(this.stations).forEach((station) => station.handleInput('parent', 'execSequence', 'shutdown'));
Object.values(this.machineGroups).forEach((group) => group.turnOffAllMachines());
}
}
_controlFlowBased() {
// placeholder for flow-based logic
}
async _applyMachineGroupLevelControl(percentControl) {
if (!this.machineGroups || Object.keys(this.machineGroups).length === 0) return;
await Promise.all(
Object.values(this.machineGroups).map((group) =>
group.handleInput('parent', percentControl).catch((err) => {
this.logger.error(`Failed to send level control to group "${group.config.general.name}": ${err.message}`);
})
)
);
}
async _applyMachineLevelControl(percentControl) {
const machines = Object.values(this.machines).filter((machine) => {
const pos = machine?.config?.functionality?.positionVsParent;
return (pos === 'downstream' || pos === 'atequipment');
});
if (!machines.length) return;
const perMachine = percentControl / machines.length;
for (const machine of machines) {
try {
await machine.handleInput('parent', 'execSequence', 'startup');
await machine.handleInput('parent', 'execMovement', perMachine);
} catch (err) {
this.logger.error(`Failed to start machine "${machine.config.general.name}": ${err.message}`);
}
}
}
/* --------------------------- Measurements --------------------------- */
_handleMeasurement(measurementType, value, position, context) {
switch (measurementType) {
case 'level':
this._onLevelMeasurement(position, value, context);
break;
case 'pressure':
this._onPressureMeasurement(position, value, context);
break;
default:
break;
}
}
_onLevelMeasurement(position, value, context = {}) {
this.measurements.type('level').variant('measured').position(position).value(value).unit(context.unit);
const levelSeries = this.measurements.type('level').variant('measured').position(position);
const levelMeters = levelSeries.getCurrentValue('m');
if (levelMeters == null) return;
const volume = this._calcVolumeFromLevel(levelMeters);
const percent = this.interpolate.interpolate_lin_single_point(
volume,
this.basin.minVol,
this.basin.maxVolOverflow,
0,
100
);
this.measurements.type('volume').variant('measured').position('atequipment').value(volume, context.timestamp, 'm3');
this.measurements
.type('volumePercent')
.variant('measured')
.position('atequipment')
.value(percent, context.timestamp, '%');
}
_onPressureMeasurement(position, value, context = {}) {
let kelvinTemp =
this.measurements.type('temperature').variant('measured').position('atequipment').getCurrentValue('K') ?? null;
if (kelvinTemp === null) {
this.logger.warn('No temperature measurement; assuming 15C for pressure to level conversion.');
this.measurements.type('temperature').variant('assumed').position('atequipment').value(15, Date.now(), 'C');
kelvinTemp = this.measurements.type('temperature').variant('assumed').position('atequipment').getCurrentValue('K');
}
if (kelvinTemp == null) return;
const density = coolprop.PropsSI('D', 'T', kelvinTemp, 'P', 101325, 'Water');
const pressurePa = this.measurements.type('pressure').variant('measured').position(position).getCurrentValue('Pa');
if (!Number.isFinite(pressurePa) || !Number.isFinite(density)) return;
const g = 9.80665;
const level = pressurePa / (density * g);
this.measurements.type('level').variant('predicted').position(position).value(level, context.timestamp, 'm');
}
/* --------------------------- Core Calculations --------------------------- */
_pickVariant(type, variants, position, unit) {
for (const variant of variants) {
const val = this.measurements.type(type).variant(variant).position(position).getCurrentValue(unit);
if (!Number.isFinite(val)) continue;
return val;
}
return null;
}
//scaled for robin min 2039 - 2960 max 53.04
_scaleLevelToFlowPercent(level) {
const { minFlowLevel, maxFlowLevel } = this.config.control.levelbased;
this.logger.debug(`Scaling minflow level : ${minFlowLevel} and maxflowLevel : ${maxFlowLevel}`);
return this.interpolate.interpolate_lin_single_point(level, minFlowLevel, maxFlowLevel, 0, 100);
}
_levelRate(variant) {
const chain = this.measurements.type('level').variant(variant).position('atequipment');
if (!chain.exists({ requireValues: true })) return null;
const m = chain.get();
const current = m?.getLaggedSample?.(0);
const previous = m?.getLaggedSample?.(1);
if (!current || !previous || previous.timestamp == null) return null;
const dt = (current.timestamp - previous.timestamp) / 1000;
if (!Number.isFinite(dt) || dt <= 0) return null;
return (current.value - previous.value) / dt;
}
_updatePredictedVolume() {
const flowUnit = 'm3/s'; // this has to be in m3/s for the actions below
const now = Date.now();
const inflow = this.measurements.sum('flow', 'predicted', this.flowPositions.inflow, flowUnit) || 0;
const outflow = this.measurements.sum('flow', 'predicted', this.flowPositions.outflow, flowUnit) || 0;
if (!this._predictedFlowState) {
this._predictedFlowState = { inflow, outflow, lastTimestamp: now };
}
const timestampPrev = this._predictedFlowState.lastTimestamp ?? now;
const deltaSeconds = Math.max((now - timestampPrev) / 1000, 0);
const netVolumeChange = deltaSeconds > 0 ? (inflow - outflow) * deltaSeconds : 0;
const volumeSeries = this.measurements.type('volume').variant('predicted').position('atequipment');
const currentVolume = volumeSeries.getCurrentValue('m3');
const nextVolume = currentVolume + netVolumeChange;
const writeTimestamp = timestampPrev + deltaSeconds * 1000;
volumeSeries.value(nextVolume, writeTimestamp, 'm3').unit('m3'); //olifant
const nextLevel = this._calcLevelFromVolume(nextVolume);
this.measurements
.type('level')
.variant('predicted')
.position('atequipment')
.value(nextLevel, writeTimestamp, 'm')
.unit('m');
const percent = this.interpolate.interpolate_lin_single_point(
nextVolume,
this.basin.minVol,
this.basin.maxVolOverflow,
0,
100
);
this.measurements
.type('volumePercent')
.variant('predicted')
.position('atequipment')
.value(percent, writeTimestamp, '%');
this._predictedFlowState = { inflow, outflow, lastTimestamp: writeTimestamp };
}
_selectBestNetFlow() {
const type = 'flow';
const unit = this.measurements.getUnit(type) || 'm3/s';
for (const variant of this.flowVariants) {
const bucket = this.measurements.measurements?.[type]?.[variant];
if (!bucket || Object.keys(bucket).length === 0) continue;
const inflow = this.measurements.sum(type, variant, this.flowPositions.inflow, unit) || 0;
const outflow = this.measurements.sum(type, variant, this.flowPositions.outflow, unit) || 0;
if (Math.abs(inflow) < this.flowThreshold && Math.abs(outflow) < this.flowThreshold) continue;
const net = inflow - outflow;
this.measurements.type('netFlowRate').variant(variant).position('atequipment').value(net, Date.now(), unit);
return { value: net, source: variant, direction: this._deriveDirection(net) };
}
// Fallback: level trend
for (const variant of this.levelVariants) {
const rate = this._levelRate(variant);
if (!Number.isFinite(rate)) continue;
const netFlow = rate * this.basin.surfaceArea;
return { value: netFlow, source: `level:${variant}`, direction: this._deriveDirection(netFlow) };
}
this.logger.warn('No usable measurements to compute net flow; assuming steady.');
return { value: 0, source: null, direction: 'steady' };
}
_computeRemainingTime(netFlow) {
if (!netFlow || Math.abs(netFlow.value) < this.flowThreshold) return { seconds: null, source: null };
const { heightOverflow, heightOutlet, surfaceArea } = this.basin;
if (!Number.isFinite(surfaceArea) || surfaceArea <= 0) return { seconds: null, source: null };
for (const variant of this.levelVariants) {
const lvl = this.measurements.type('level').variant(variant).position('atequipment').getCurrentValue('m');
if (!Number.isFinite(lvl)) continue;
const remainingHeight = netFlow.value > 0 ? Math.max(heightOverflow - lvl, 0) : Math.max(lvl - heightOutlet, 0);
const seconds = (remainingHeight * surfaceArea) / Math.abs(netFlow.value);
if (!Number.isFinite(seconds)) continue;
return { seconds, source: `${netFlow.source}/${variant}` };
}
return { seconds: null, source: netFlow.source };
}
_deriveDirection(netFlow) {
if (netFlow > this.flowThreshold) return 'filling';
if (netFlow < -this.flowThreshold) return 'draining';
return 'steady';
}
/* --------------------------- Safety --------------------------- */
_safetyController(remainingTime, direction) {
this.safetyControllerActive = false;
const volUnit = this.measurements.getUnit('volume');
const vol = this._pickVariant('volume', this.volVariants, 'atequipment', volUnit);
if (vol == null) {
Object.values(this.machines).forEach((machine) => machine.handleInput('parent', 'execSequence', 'shutdown'));
this.logger.warn('No volume data available to safe guard system; shutting down all machines.');
this.safetyControllerActive = true;
return;
}
const {
enableDryRunProtection,
dryRunThresholdPercent,
enableOverfillProtection,
overfillThresholdPercent,
timeleftToFullOrEmptyThresholdSeconds
} = this.config.safety || {};
const dryRunEnabled = Boolean(enableDryRunProtection);
const overfillEnabled = Boolean(enableOverfillProtection);
const timeProtectionEnabled = timeleftToFullOrEmptyThresholdSeconds > 0;
const triggerHighVol = this.basin.maxVolOverflow * ((Number(overfillThresholdPercent) || 0) / 100);
const triggerLowVol = this.basin.minVol * (1 + ((Number(dryRunThresholdPercent) || 0) / 100));
if (direction === 'draining') {
const timeTriggered = timeProtectionEnabled && remainingTime != null && remainingTime < timeleftToFullOrEmptyThresholdSeconds;
const dryRunTriggered = dryRunEnabled && vol < triggerLowVol;
if (timeTriggered || dryRunTriggered) {
Object.values(this.machines).forEach((machine) => {
const pos = machine?.config?.functionality?.positionVsParent;
if ((pos === 'downstream' || pos === 'atequipment') && machine._isOperationalState()) {
machine.handleInput('parent', 'execSequence', 'shutdown');
}
});
Object.values(this.stations).forEach((station) => station.handleInput('parent', 'execSequence', 'shutdown'));
Object.values(this.machineGroups).forEach((group) => group.turnOffAllMachines());
this.logger.warn(
`Safe guard triggered: vol=${vol.toFixed(2)} m3, remainingTime=${remainingTime ? remainingTime.toFixed(1) : 'N/A'} s; shutting down downstream equipment`
);
this.safetyControllerActive = true;
}
}
if (direction === 'filling') {
const timeTriggered = timeProtectionEnabled && remainingTime != null && remainingTime < timeleftToFullOrEmptyThresholdSeconds;
const overfillTriggered = overfillEnabled && vol > triggerHighVol;
if (timeTriggered || overfillTriggered) {
Object.values(this.machines).forEach((machine) => {
const pos = machine?.config?.functionality?.positionVsParent;
if (pos === 'upstream' && machine._isOperationalState()) {
machine.handleInput('parent', 'execSequence', 'shutdown');
}
});
Object.values(this.machineGroups).forEach((group) => group.turnOffAllMachines());
Object.values(this.stations).forEach((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 upstream equipment`
);
this.safetyControllerActive = true;
}
}
}
/* --------------------------- Basin --------------------------- */
initBasinProperties() {
const minHeightBasedOn = this.config.hydraulics.minHeightBasedOn;
const volEmptyBasin = this.config.basin.volume;
const heightBasin = this.config.basin.height;
const heightInlet = this.config.basin.heightInlet;
const heightOutlet = this.config.basin.heightOutlet;
const heightOverflow = this.config.basin.heightOverflow;
const surfaceArea = volEmptyBasin / heightBasin;
const maxVol = heightBasin * surfaceArea;
const maxVolOverflow = heightOverflow * surfaceArea;
const minVolOut = heightOutlet * surfaceArea;
const minVolIn = heightInlet * surfaceArea;
const minVol = minHeightBasedOn === 'inlet' ? minVolIn : minVolOut;
this.basin = {
volEmptyBasin,
heightBasin,
heightInlet,
heightOutlet,
heightOverflow,
surfaceArea,
maxVol,
maxVolOverflow,
minVolIn,
minVolOut,
minVol,
minHeightBasedOn
};
this.measurements.type('volume').variant('predicted').position('atequipment').value(minVol).unit('m3');
}
_calcVolumeFromLevel(level) {
return Math.max(level, 0) * this.basin.surfaceArea;
}
_calcLevelFromVolume(volume) {
return Math.max(volume, 0) / this.basin.surfaceArea;
}
/* --------------------------- Output --------------------------- */
getOutput() {
const output = this.measurements.getFlattenedOutput();
output.direction = this.state.direction;
output.flowSource = this.state.flowSource;
output.timeleft = this.state.seconds;
output.volEmptyBasin = this.basin.volEmptyBasin;
output.heightInlet = this.basin.heightInlet;
output.heightOverflow = this.basin.heightOverflow;
output.maxVol = this.basin.maxVol;
output.minVol = this.basin.minVol;
output.maxVolOverflow = this.basin.maxVolOverflow;
output.minVolOut = this.basin.minVolOut;
output.minVolIn = this.basin.minVolIn;
output.minHeightBasedOn = this.basin.minHeightBasedOn;
return output;
}
}
module.exports = PumpingStation;
/* ------------------------------------------------------------------------- */
/* Example usage */
/* ------------------------------------------------------------------------- */
if (require.main === module) {
const Measurement = require('../../measurement/src/specificClass');
const RotatingMachine = require('../../rotatingMachine/src/specificClass');
function createPumpingStationConfig(name) {
return {
general: {
logging: { enabled: true, logLevel: 'debug' },
name,
id: `${name}-${Date.now()}`,
flowThreshold: 1e-4
},
functionality: {
softwareType: 'pumpingStation',
role: 'stationcontroller'
},
basin: {
volume: 43.75,
height: 10,
heightInlet: 3,
heightOutlet: 0.2,
heightOverflow: 3.2
},
hydraulics: {
refHeight: 'NAP',
basinBottomRef: 0
},
safety: {
enableDryRunProtection:false,
enableOverfillProtection:false
}
};
}
function createLevelMeasurementConfig(name) {
return {
general: {
logging: { enabled: true, logLevel: 'debug' },
name,
id: `${name}-${Date.now()}`,
unit: 'm'
},
functionality: {
softwareType: 'measurement',
role: 'sensor',
positionVsParent: 'atequipment'
},
asset: {
category: 'sensor',
type: 'level',
model: 'demo-level',
supplier: 'demoCo',
unit: 'm'
},
scaling: { enabled: false },
smoothing: { smoothWindow: 5, smoothMethod: 'none' }
};
}
function createFlowMeasurementConfig(name, position) {
return {
general: {
logging: { enabled: true, logLevel: 'debug' },
name,
id: `${name}-${Date.now()}`,
unit: 'm3/s'
},
functionality: {
softwareType: 'measurement',
role: 'sensor',
positionVsParent: position
},
asset: {
category: 'sensor',
type: 'flow',
model: 'demo-flow',
supplier: 'demoCo',
unit: 'm3/s'
},
scaling: { enabled: false },
smoothing: { smoothWindow: 5, smoothMethod: 'none' }
};
}
function createMachineConfig(name,position) {
return {
general: {
name,
logging: { enabled: false, logLevel: 'debug' }
},
functionality: {
softwareType: "machine",
positionVsParent: position
},
asset: {
supplier: 'Hydrostal',
type: 'pump',
category: 'centrifugal',
model: 'hidrostal-H05K-S03R'
}
};
}
function createMachineStateConfig() {
return {
general: {
logging: {
enabled: true,
logLevel: 'debug'
}
},
movement: { speed: 1 },
time: {
starting: 2,
warmingup: 3,
stopping: 2,
coolingdown: 3
}
};
}
function seedSample(measurement, type, value, unit) {
const pos = measurement.config.functionality.positionVsParent;
measurement.measurements.type(type).variant('measured').position(pos).value(value, Date.now(), unit);
}
(async function demo() {
const station = new PumpingStation(createPumpingStationConfig('PumpingStationDemo'));
const pump1 = new RotatingMachine(createMachineConfig('Pump1','downstream'), createMachineStateConfig());
//const pump2 = new RotatingMachine(createMachineConfig('Pump2','upstream'), createMachineStateConfig());
//const levelSensor = new Measurement(createLevelMeasurementConfig('WetWellLevel'));
//const inflowSensor = new Measurement(createFlowMeasurementConfig('InfluentFlow', 'in'));
//const outflowSensor = new Measurement(createFlowMeasurementConfig('PumpDischargeFlow', 'out'));
//station.childRegistrationUtils.registerChild(levelSensor, levelSensor.config.functionality.softwareType);
//station.childRegistrationUtils.registerChild(inflowSensor, inflowSensor.config.functionality.softwareType);
//station.childRegistrationUtils.registerChild(outflowSensor, outflowSensor.config.functionality.softwareType);
station.childRegistrationUtils.registerChild(pump1, 'machine');
//station.childRegistrationUtils.registerChild(pump2, 'machine');
// Seed initial measurements
//seedSample(levelSensor, 'level', 1.8, 'm');
//seedSample(inflowSensor, 'flow', 0.35, 'm3/s');
//seedSample(outflowSensor, 'flow', 0.20, 'm3/s');
setInterval(
() => station.tick(), 1000);
await new Promise((resolve) => setTimeout(resolve, 10));
console.log('Initial state:', station.state);
station.setManualInflow(300,Date.now(),'l/s');
station.calibratePredictedVolume(3.4);
//await pump1.handleInput('parent', 'execSequence', 'startup');
//await pump1.handleInput('parent', 'execMovement', 10);
//
//await pump2.handleInput('parent', 'execSequence', 'startup');
//await pump2.handleInput('parent', 'execMovement', 10);
console.log('Station state:', station.state);
console.log('Station output:', station.getOutput());
})().catch((err) => {
console.error('Demo failed:', err);
});
}
//*/