Compare commits

...

3 Commits

Author SHA1 Message Date
Rene De Ren
4e098eefaa refactor: adopt POSITIONS constants and fix ESLint warnings
Replace hardcoded position strings with POSITIONS.* constants.
Prefix unused variables with _ to resolve no-unused-vars warnings.
Fix no-prototype-builtins where applicable.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-11 15:35:28 +01:00
Rene De Ren
90f87bb538 Migrate _loadConfig to use ConfigManager.buildConfig()
Replaces manual base config construction with shared buildConfig() method.
Node now only specifies domain-specific config sections.

Part of #1: Extract base config schema

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-11 14:59:35 +01:00
Rene De Ren
8fe9c7ec05 Fix ESLint errors and bugs
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-11 13:39:57 +01:00
2 changed files with 35 additions and 51 deletions

View File

@@ -39,35 +39,20 @@ class nodeClass {
const cfgMgr = new configManager();
this.defaultConfig = cfgMgr.getConfig(this.name);
// Merge UI config over defaults
this.config = {
general: {
name: this.name,
id: node.id, // node.id is for the child registration process
unit: uiConfig.unit, // add converter options later to convert to default units (need like a model that defines this which units we are going to use and then conver to those standards)
logging: {
enabled: uiConfig.enableLog,
logLevel: uiConfig.logLevel
}
},
functionality: {
positionVsParent: uiConfig.positionVsParent,// Default to 'atEquipment' if not specified
distance: uiConfig.hasDistance ? uiConfig.distance : undefined
},
basin:{
// Build config: base sections + pumpingStation-specific domain config
this.config = cfgMgr.buildConfig(this.name, uiConfig, node.id, {
basin: {
volume: uiConfig.basinVolume,
height: uiConfig.basinHeight,
heightInlet: uiConfig.heightInlet,
heightOutlet: uiConfig.heightOutlet,
heightOverflow: uiConfig.heightOverflow,
},
hydraulics:{
hydraulics: {
refHeight: uiConfig.refHeight,
basinBottomRef: uiConfig.basinBottomRef,
}
};
console.log(`position vs child for ${this.name} is ${this.config.functionality.positionVsParent} the distance is ${this.config.functionality.distance}`);
});
// Utility for formatting outputs
this._output = new outputUtils();
@@ -203,12 +188,13 @@ class nodeClass {
this.source.handleInput(msg);
break;
*/
case 'registerChild':
case 'registerChild': {
// Register this node as a child of the parent node
const childId = msg.payload;
const childObj = this.RED.nodes.getNode(childId);
const childObj = this.RED.nodes.getNode(childId);
this.source.childRegistrationUtils.registerChild(childObj.source ,msg.positionVsParent);
break;
}
}
done();
});

View File

@@ -1,5 +1,5 @@
const EventEmitter = require('events');
const {logger,configUtils,configManager,childRegistrationUtils,MeasurementContainer,coolprop,interpolation} = require('generalFunctions');
const {logger,configUtils,configManager,childRegistrationUtils,MeasurementContainer,coolprop,interpolation, POSITIONS} = require('generalFunctions');
class pumpingStation {
constructor(config={}) {
@@ -41,9 +41,7 @@ class pumpingStation {
//define what to do with measurements
if(softwareType === "measurement"){
const position = child.config.functionality.positionVsParent;
const distance = child.config.functionality.distanceVsParent || 0;
const measurementType = child.config.asset.type;
const key = `${measurementType}_${position}`;
//rebuild to measurementype.variant no position and then switch based on values not strings or names.
const eventName = `${measurementType}.measured.${position}`;
@@ -70,7 +68,7 @@ class pumpingStation {
this.logger.debug(`Listening for flow changes from machine ${child.config.general.id}`);
switch(child.config.functionality.positionVsParent){
case("downstream"):
case(POSITIONS.DOWNSTREAM):
case("atequipment"): //in case of atequipment we also assume downstream seeing as it is registered at this pumpingstation as part of it.
//for now lets focus on handling downstream predicted flow
child.measurements.emitter.on("flow.predicted.downstream", (eventData) => {
@@ -80,7 +78,7 @@ class pumpingStation {
break;
case("upstream"):
case(POSITIONS.UPSTREAM):
//check for predicted outgoing flow at the connected child pumpingsation
child.measurements.emitter.on("flow.predicted.downstream", (eventData) => {
this.logger.debug(`Flow prediction update from ${child.config.general.id}: ${eventData.value} ${eventData.unit}`);
@@ -97,7 +95,7 @@ class pumpingStation {
// add one for group later
if( softwareType == "machineGroup" ){
/* intentionally empty */
}
// add one for pumping station
@@ -109,7 +107,7 @@ class pumpingStation {
this.logger.debug(`Listening for flow changes from machine ${child.config.general.id}`);
switch(child.config.functionality.positionVsParent){
case("downstream"):
case(POSITIONS.DOWNSTREAM):
//check for predicted outgoing flow at the connected child pumpingsation
child.measurements.emitter.on("flow.predicted.downstream", (eventData) => {
this.logger.debug(`Flow prediction update from ${child.config.general.id}: ${eventData.value} ${eventData.unit}`);
@@ -118,7 +116,7 @@ class pumpingStation {
});
break;
case("upstream"):
case(POSITIONS.UPSTREAM):
//check for predicted outgoing flow at the connected child pumpingsation
child.measurements.emitter.on("flow.predicted.downstream", (eventData) => {
this.logger.debug(`Flow prediction update from ${child.config.general.id}: ${eventData.value} ${eventData.unit}`);
@@ -139,7 +137,7 @@ class pumpingStation {
//get downflow
const seriesExists = this.measurements.type("flow").variant("predicted").position(flowDir).exists();
if(!seriesExists){return};
if(!seriesExists){return}
const series = this.measurements.type("flow").variant("predicted").position(flowDir);
const currFLow = series.getLaggedValue(0, "m3/s"); // { value, timestamp, unit }
@@ -162,7 +160,7 @@ class pumpingStation {
const calcVol = avgFlow * deltaSeconds;
//substract seeing as this is downstream and is being pulled away from the pumpingstaion and keep track of status
const currVolume = this.measurements.type('volume').variant('predicted').position('atEquipment').getCurrentValue('m3');
const currVolume = this.measurements.type('volume').variant('predicted').position(POSITIONS.AT_EQUIPMENT).getCurrentValue('m3');
let newVol = currVolume;
switch(flowDir){
@@ -179,11 +177,11 @@ class pumpingStation {
}
this.measurements.type('volume').variant('predicted').position('atEquipment').value(newVol).unit('m3');
this.measurements.type('volume').variant('predicted').position(POSITIONS.AT_EQUIPMENT).value(newVol).unit('m3');
//convert to a predicted level
const newLevel = this._calcLevelFromVolume(newVol);
this.measurements.type('level').variant('predicted').position('atEquipment').value(newLevel).unit('m');
this.measurements.type('level').variant('predicted').position(POSITIONS.AT_EQUIPMENT).value(newLevel).unit('m');
this.logger.debug(`new predicted volume : ${newVol} new predicted level: ${newLevel} `);
@@ -257,13 +255,13 @@ class pumpingStation {
this.measurements.type("pressure").variant("measured").position(position).value(value, context.timestamp, context.unit);
//convert pressure to level based on density of water and height of pressure sensor
const mTemp = this.measurements.type("temperature").variant("measured").position("atEquipment").getCurrentValue('K'); //default to 20C if no temperature measurement
const mTemp = this.measurements.type("temperature").variant("measured").position(POSITIONS.AT_EQUIPMENT).getCurrentValue('K'); //default to 20C if no temperature measurement
//prefer measured temp but otherwise assume nominal temp for wastewater
if(mTemp === null){
this.logger.warn(`No temperature measurement available, defaulting to 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');
this.measurements.type("temperature").variant("assumed").position(POSITIONS.AT_EQUIPMENT).value(15, Date.now(), "C");
kelvinTemp = this.measurements.type('temperature').variant('assumed').position(POSITIONS.AT_EQUIPMENT).getCurrentValue('K');
this.logger.debug(`Temperature is : ${kelvinTemp}`);
} else {
kelvinTemp = mTemp;
@@ -294,15 +292,14 @@ class pumpingStation {
const proc = this.interpolate.interpolate_lin_single_point(volume,this.basin.minVol,this.basin.maxVolOverflow,0,100);
this.logger.debug(`PROC volume : ${proc}`);
this.measurements.type("volume").variant("measured").position("atEquipment").value(volume).unit('m3');
this.measurements.type("volume").variant("procent").position("atEquipment").value(proc);
this.measurements.type("volume").variant("measured").position(POSITIONS.AT_EQUIPMENT).value(volume).unit('m3');
this.measurements.type("volume").variant("procent").position(POSITIONS.AT_EQUIPMENT).value(proc);
}
_calcNetFlow() {
let netFlow = null;
const netFlow_FlowSensor = Math.abs(this.measurements.type("flow").variant("measured").difference({ from: "downstream", to: "upstream", unit: "m3/s" }));
const netFlow_FlowSensor = Math.abs(this.measurements.type("flow").variant("measured").difference({ from: POSITIONS.DOWNSTREAM, to: POSITIONS.UPSTREAM, unit: "m3/s" }));
const netFlow_LevelSensor = this._calcNetFlowFromLevelDiff();
const netFlow_PredictedFlow = Math.abs(this.measurements.type('flow').variant('predicted').difference({ from: "in", to: "out", unit: "m3/s" }));
@@ -325,8 +322,9 @@ class pumpingStation {
_calcRemainingTime(level,variant){
const { heightOverflow, heightOutlet, surfaceArea } = this.basin;
const flowDiff = this.measurements.type("flow").variant(variant).difference({ from: "downstream", to: "upstream", unit: "m3/s" });
const flowDiff = this.measurements.type("flow").variant(variant).difference({ from: POSITIONS.DOWNSTREAM, to: POSITIONS.UPSTREAM, unit: "m3/s" });
let remainingHeight;
switch(true){
case(flowDiff>0):
remainingHeight = Math.max(heightOverflow - level, 0);
@@ -348,6 +346,7 @@ class pumpingStation {
_calcDirection(flowDiff){
let direction = null;
const flowThreshold = 0.001;
switch (true){
case flowDiff > flowThreshold:
@@ -372,7 +371,7 @@ class pumpingStation {
_calcNetFlowFromLevelDiff() {
const { surfaceArea } = this.basin;
const levelObj = this.measurements.type("level").variant("measured").position("atEquipment");
const levelObj = this.measurements.type("level").variant("measured").position(POSITIONS.AT_EQUIPMENT);
const level = levelObj.getCurrentValue("m");
const prevLevel = levelObj.getLaggedValue(2, "m"); // { value, timestamp, unit }
const measurement = levelObj.get();
@@ -424,7 +423,7 @@ class pumpingStation {
this.basin.minVolOut = minVolOut ;
//init predicted min volume to min vol in order to have a starting point
this.measurements.type("volume").variant("predicted").position("atEquipment").value(minVol).unit('m3');
this.measurements.type("volume").variant("predicted").position(POSITIONS.AT_EQUIPMENT).value(minVol).unit('m3');
this.logger.debug(`
Basin initialized | area=${surfaceArea.toFixed(2)} m²,
@@ -522,7 +521,7 @@ function createLevelMeasurementConfig(name) {
functionality: {
softwareType: "measurement",
role: "sensor",
positionVsParent: "atEquipment"
positionVsParent: POSITIONS.AT_EQUIPMENT
},
asset: {
category: "sensor",
@@ -564,7 +563,6 @@ function createFlowMeasurementConfig(name, position) {
function createMachineConfig(name) {
curve = require('C:/Users/zn375/.node-red/public/fallbackData.json');
return {
general: {
@@ -605,7 +603,7 @@ function createMachineStateConfig() {
}
// convenience for seeding measurements
function pushSample(measurement, type, value, unit) {
function pushSample(measurement, type, value, unit) { // eslint-disable-line no-unused-vars
const pos = measurement.config.functionality.positionVsParent;
measurement.measurements
.type(type)
@@ -619,9 +617,9 @@ function pushSample(measurement, type, value, unit) {
const station = new PumpingStation(createPumpingStationConfig("PumpingStationDemo"));
const pump = new RotatingMachine(createMachineConfig("Pump1"), createMachineStateConfig());
const levelSensor = new Measurement(createLevelMeasurementConfig("WetWellLevel"));
const upstreamFlow = new Measurement(createFlowMeasurementConfig("InfluentFlow", "upstream"));
const downstreamFlow = new Measurement(createFlowMeasurementConfig("PumpDischargeFlow", "downstream"));
const levelSensor = new Measurement(createLevelMeasurementConfig("WetWellLevel")); // eslint-disable-line no-unused-vars
const upstreamFlow = new Measurement(createFlowMeasurementConfig("InfluentFlow", POSITIONS.UPSTREAM)); // eslint-disable-line no-unused-vars
const downstreamFlow = new Measurement(createFlowMeasurementConfig("PumpDischargeFlow", POSITIONS.DOWNSTREAM));
// station uses the sensors
@@ -633,7 +631,7 @@ function pushSample(measurement, type, value, unit) {
// pump owns the downstream flow sensor
pump.childRegistrationUtils.registerChild(downstreamFlow, downstreamFlow.config.functionality.positionVsParent);
station.childRegistrationUtils.registerChild(pump,"downstream");
station.childRegistrationUtils.registerChild(pump, POSITIONS.DOWNSTREAM);
setInterval(() => station.tick(), 1000);