From c081acae4e18c0d714c015be36c84278a0eac684 Mon Sep 17 00:00:00 2001
From: "p.vanderwilt"
Date: Fri, 10 Oct 2025 13:27:31 +0200
Subject: [PATCH 01/13] Remove non-implemented temperature handling function
---
src/specificClass.js | 4 ----
1 file changed, 4 deletions(-)
diff --git a/src/specificClass.js b/src/specificClass.js
index 823f60c..679f3b9 100644
--- a/src/specificClass.js
+++ b/src/specificClass.js
@@ -161,10 +161,6 @@ _callMeasurementHandler(measurementType, value, position, context) {
this.updateMeasuredFlow(value, position, context);
break;
- case 'temperature':
- this.updateMeasuredTemperature(value, position, context);
- break;
-
default:
this.logger.warn(`No handler for measurement type: ${measurementType}`);
// Generic handler - just update position
From 37e6523c5527e3bfd9d9dbb4f021e018f45be3cf Mon Sep 17 00:00:00 2001
From: HorriblePerson555 <47578455+HorriblePerson555@users.noreply.github.com>
Date: Thu, 16 Oct 2025 16:32:20 +0200
Subject: [PATCH 02/13] Refactor child registration to use dedicated connection
methods for measurement and reactor types
---
src/specificClass.js | 90 ++++++++++++++++++++++++--------------------
1 file changed, 50 insertions(+), 40 deletions(-)
diff --git a/src/specificClass.js b/src/specificClass.js
index 245946b..a0f3853 100644
--- a/src/specificClass.js
+++ b/src/specificClass.js
@@ -77,52 +77,62 @@ class Machine {
registerChild(child, softwareType) {
this.logger.debug('Setting up child event for softwaretype ' + softwareType);
- 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}`;
-
- this.logger.debug(`Setting up listener for ${eventName} from child ${child.config.general.name}`);
- // Register event listener for measurement updates
- child.measurements.emitter.on(eventName, (eventData) => {
- this.logger.debug(`🔄 ${position} ${measurementType} from ${eventData.childName}: ${eventData.value} ${eventData.unit}`);
+ switch (softwareType) {
+ case "measurement":
+ this._connectMeasurement(child);
+ break;
+ case "reactor":
+ this._connectReactor(child);
+ break;
-
- console.log(` Emitting... ${eventName} with data:`);
- // Store directly in parent's measurement container
- this.measurements
- .type(measurementType)
- .variant("measured")
- .position(position)
- .value(eventData.value, eventData.timestamp, eventData.unit);
-
- // Call the appropriate handler
- this._callMeasurementHandler(measurementType, eventData.value, position, eventData);
- });
+ default:
+ this.logger.error(`Unrecognized softwareType: ${softwareType}`);
}
}
-// Centralized handler dispatcher
-_callMeasurementHandler(measurementType, value, position, context) {
- switch (measurementType) {
- case 'pressure':
- this.updateMeasuredPressure(value, position, context);
- break;
+ _connectMeasurement(measurementChild) {
+ const position = measurementChild.config.functionality.positionVsParent;
+ const distance = measurementChild.config.functionality.distanceVsParent || 0;
+ const measurementType = measurementChild.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}`;
+
+ this.logger.debug(`Setting up listener for ${eventName} from child ${measurementChild.config.general.name}`);
+ // Register event listener for measurement updates
+ measurementChild.measurements.emitter.on(eventName, (eventData) => {
+ this.logger.debug(`🔄 ${position} ${measurementType} from ${eventData.childName}: ${eventData.value} ${eventData.unit}`);
+
+
+ console.log(` Emitting... ${eventName} with data:`);
+ // Store directly in parent's measurement container
+ this.measurements
+ .type(measurementType)
+ .variant("measured")
+ .position(position)
+ .value(eventData.value, eventData.timestamp, eventData.unit);
- case 'flow':
- this.updateMeasuredFlow(value, position, context);
- break;
-
- default:
- this.logger.warn(`No handler for measurement type: ${measurementType}`);
- // Generic handler - just update position
- this.updatePosition();
- break;
+ // Call the appropriate handler
+ switch (measurementType) {
+ case 'pressure':
+ this.updateMeasuredPressure(eventData.value, position, eventData);
+ break;
+
+ case 'flow':
+ this.updateMeasuredFlow(eventData.value, position, eventData);
+ break;
+
+ default:
+ this.logger.warn(`No handler for measurement type: ${measurementType}`);
+ // Generic handler - just update position
+ this.updatePosition();
+ }
+ });
+ }
+
+ _connectReactor(reactorChild) {
+ this.logger.error("Reactor child not implemented yet.");
}
-}
//---------------- END child stuff -------------//
From d7cc6a4a8b1f35a12dd2a69fddd25b81767fac23 Mon Sep 17 00:00:00 2001
From: HorriblePerson555 <47578455+HorriblePerson555@users.noreply.github.com>
Date: Fri, 17 Oct 2025 13:38:05 +0200
Subject: [PATCH 03/13] Enhance child registration logging and add validation
for measurement child
---
src/specificClass.js | 10 +++++++---
1 file changed, 7 insertions(+), 3 deletions(-)
diff --git a/src/specificClass.js b/src/specificClass.js
index a0f3853..3ad3fa5 100644
--- a/src/specificClass.js
+++ b/src/specificClass.js
@@ -75,13 +75,13 @@ class Machine {
/*------------------- Register child events -------------------*/
registerChild(child, softwareType) {
- this.logger.debug('Setting up child event for softwaretype ' + softwareType);
-
switch (softwareType) {
case "measurement":
+ this.logger.debug(`Registering measurement child...`);
this._connectMeasurement(child);
break;
case "reactor":
+ this.logger.debug(`Registering reactor child...`);
this._connectReactor(child);
break;
@@ -91,10 +91,14 @@ class Machine {
}
_connectMeasurement(measurementChild) {
+ if (!measurementChild) {
+ this.logger.warn("Invalid measurement provided.");
+ return;
+ }
+
const position = measurementChild.config.functionality.positionVsParent;
const distance = measurementChild.config.functionality.distanceVsParent || 0;
const measurementType = measurementChild.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}`;
From a8fb56bfb8ceb5d11ab7748844d5b5b986eb2289 Mon Sep 17 00:00:00 2001
From: "p.vanderwilt"
Date: Wed, 22 Oct 2025 14:41:35 +0200
Subject: [PATCH 04/13] Add upstream and downstream reactor handling; improve
error logging
---
src/specificClass.js | 56 ++++++++++++++++++++++++++++----------------
1 file changed, 36 insertions(+), 20 deletions(-)
diff --git a/src/specificClass.js b/src/specificClass.js
index 3ad3fa5..aba5570 100644
--- a/src/specificClass.js
+++ b/src/specificClass.js
@@ -68,6 +68,10 @@ class Machine {
this.updatePosition();
});
+ // used for holding the source and sink unit operations or other object with setInfluent / getEffluent method for e.g. recirculation.
+ this.upstreamReactor = null;
+ this.downstreamReactor = null;
+
this.child = {}; // object to hold child information so we know on what to subscribe
this.childRegistrationUtils = new childRegistrationUtils(this); // Child registration utility
@@ -92,7 +96,7 @@ class Machine {
_connectMeasurement(measurementChild) {
if (!measurementChild) {
- this.logger.warn("Invalid measurement provided.");
+ this.logger.error("Invalid measurement provided.");
return;
}
@@ -107,14 +111,12 @@ class Machine {
measurementChild.measurements.emitter.on(eventName, (eventData) => {
this.logger.debug(`🔄 ${position} ${measurementType} from ${eventData.childName}: ${eventData.value} ${eventData.unit}`);
-
- console.log(` Emitting... ${eventName} with data:`);
// Store directly in parent's measurement container
this.measurements
.type(measurementType)
.variant("measured")
.position(position)
- .value(eventData.value, eventData.timestamp, eventData.unit);
+ .value(eventData.value, eventData.timestamp, eventData.unit);
// Call the appropriate handler
switch (measurementType) {
@@ -135,26 +137,31 @@ class Machine {
}
_connectReactor(reactorChild) {
- this.logger.error("Reactor child not implemented yet.");
+ if (!reactorChild) {
+ this.logger.error("Invalid measurement provided.");
+ return;
+ }
+
+ this.downstreamReactor = reactorChild; // downstream from the pumps perpective
}
-//---------------- END child stuff -------------//
+ //---------------- END child stuff -------------//
- // Method to assess drift using errorMetrics
- assessDrift(measurement, processMin, processMax) {
- this.logger.debug(`Assessing drift for measurement: ${measurement} processMin: ${processMin} processMax: ${processMax}`);
- const predictedMeasurement = this.measurements.type(measurement).variant("predicted").position("downstream").getAllValues().values;
- const measuredMeasurement = this.measurements.type(measurement).variant("measured").position("downstream").getAllValues().values;
+ // Method to assess drift using errorMetrics
+ assessDrift(measurement, processMin, processMax) {
+ this.logger.debug(`Assessing drift for measurement: ${measurement} processMin: ${processMin} processMax: ${processMax}`);
+ const predictedMeasurement = this.measurements.type(measurement).variant("predicted").position("downstream").getAllValues().values;
+ const measuredMeasurement = this.measurements.type(measurement).variant("measured").position("downstream").getAllValues().values;
- if (!predictedMeasurement || !measuredMeasurement) return null;
-
- return this.errorMetrics.assessDrift(
- predictedMeasurement,
- measuredMeasurement,
- processMin,
- processMax
- );
- }
+ if (!predictedMeasurement || !measuredMeasurement) return null;
+
+ return this.errorMetrics.assessDrift(
+ predictedMeasurement,
+ measuredMeasurement,
+ processMin,
+ processMax
+ );
+ }
reverseCurve(curve) {
const reversedCurve = {};
@@ -499,12 +506,17 @@ class Machine {
// NEW: Flow handler
updateMeasuredFlow(value, position, context = {}) {
+
if (!this._isOperationalState()) {
this.logger.warn(`Machine not operational, skipping flow update from ${context.childName || 'unknown'}`);
return;
}
this.logger.debug(`Flow update: ${value} at ${position} from ${context.childName || 'child'}`);
+
+ if (this.upstreamReactor && this.downstreamReactor){
+ this._updateConnectedReactor();
+ }
// Store in parent's measurement container
this.measurements.type("flow").variant("measured").position(position).value(value, context.timestamp, context.unit);
@@ -515,6 +527,10 @@ class Machine {
}
}
+ _updateConnectedReactor() {
+ this.downstreamReactor.setInfluent = this.upstreamReactor.getEffluent[1];
+ }
+
// Helper method for operational state check
_isOperationalState() {
const state = this.state.getCurrentState();
From ac40a93ef1728a777eb1d7855695c851e7e1e8bb Mon Sep 17 00:00:00 2001
From: "p.vanderwilt"
Date: Fri, 31 Oct 2025 13:07:52 +0100
Subject: [PATCH 05/13] Simplify child registration error handling
---
src/specificClass.js | 15 +++++----------
1 file changed, 5 insertions(+), 10 deletions(-)
diff --git a/src/specificClass.js b/src/specificClass.js
index aba5570..f1d8123 100644
--- a/src/specificClass.js
+++ b/src/specificClass.js
@@ -79,6 +79,11 @@ class Machine {
/*------------------- Register child events -------------------*/
registerChild(child, softwareType) {
+ if(!child) {
+ this.logger.error(`Invalid ${softwareType} child provided.`);
+ return;
+ }
+
switch (softwareType) {
case "measurement":
this.logger.debug(`Registering measurement child...`);
@@ -95,11 +100,6 @@ class Machine {
}
_connectMeasurement(measurementChild) {
- if (!measurementChild) {
- this.logger.error("Invalid measurement provided.");
- return;
- }
-
const position = measurementChild.config.functionality.positionVsParent;
const distance = measurementChild.config.functionality.distanceVsParent || 0;
const measurementType = measurementChild.config.asset.type;
@@ -137,11 +137,6 @@ class Machine {
}
_connectReactor(reactorChild) {
- if (!reactorChild) {
- this.logger.error("Invalid measurement provided.");
- return;
- }
-
this.downstreamReactor = reactorChild; // downstream from the pumps perpective
}
From 303dfc477d7f25461c59f1cde6b5db1d8f8a1d1c Mon Sep 17 00:00:00 2001
From: "p.vanderwilt"
Date: Fri, 31 Oct 2025 14:16:00 +0100
Subject: [PATCH 06/13] Add flow number configuration and UI input for rotating
machine
---
rotatingMachine.html | 5 +++++
src/specificClass.js | 3 ++-
2 files changed, 7 insertions(+), 1 deletion(-)
diff --git a/rotatingMachine.html b/rotatingMachine.html
index 6d54b66..e2a0a69 100644
--- a/rotatingMachine.html
+++ b/rotatingMachine.html
@@ -25,6 +25,7 @@
shutdown: { value: 0 },
cooldown: { value: 0 },
machineCurve : { value: {}},
+ flowNumber : { value: 1, required: true },
//define asset properties
uuid: { value: "" },
@@ -127,6 +128,10 @@
+
+
+
+
diff --git a/src/specificClass.js b/src/specificClass.js
index f1d8123..1579611 100644
--- a/src/specificClass.js
+++ b/src/specificClass.js
@@ -523,7 +523,8 @@ class Machine {
}
_updateConnectedReactor() {
- this.downstreamReactor.setInfluent = this.upstreamReactor.getEffluent[1];
+ // Handles flow according to the configured "flow number"
+ this.downstreamReactor.setInfluent = this.upstreamReactor.getEffluent[this.config.flowNumber];
}
// Helper method for operational state check
From b6d268659a9d2d5b9e42dd922c24eaf63f80321a Mon Sep 17 00:00:00 2001
From: "p.vanderwilt"
Date: Thu, 6 Nov 2025 14:50:40 +0100
Subject: [PATCH 07/13] Refactor flow handling: rename reactor references to
source and sink and fix config minor bug
---
rotatingMachine.html | 4 ++--
src/nodeClass.js | 3 ++-
src/specificClass.js | 16 ++++++++--------
3 files changed, 12 insertions(+), 11 deletions(-)
diff --git a/rotatingMachine.html b/rotatingMachine.html
index e2a0a69..93473b4 100644
--- a/rotatingMachine.html
+++ b/rotatingMachine.html
@@ -24,8 +24,8 @@
warmup: { value: 0 },
shutdown: { value: 0 },
cooldown: { value: 0 },
- machineCurve : { value: {}},
- flowNumber : { value: 1, required: true },
+ machineCurve: { value: {}},
+ flowNumber: { value: 1, required: true },
//define asset properties
uuid: { value: "" },
diff --git a/src/nodeClass.js b/src/nodeClass.js
index 6d20326..7bb504e 100644
--- a/src/nodeClass.js
+++ b/src/nodeClass.js
@@ -63,7 +63,8 @@ class nodeClass {
},
functionality: {
positionVsParent: uiConfig.positionVsParent
- }
+ },
+ flowNumber: uiConfig.flowNumber
};
// Utility for formatting outputs
diff --git a/src/specificClass.js b/src/specificClass.js
index 1579611..55a4b32 100644
--- a/src/specificClass.js
+++ b/src/specificClass.js
@@ -69,8 +69,8 @@ class Machine {
});
// used for holding the source and sink unit operations or other object with setInfluent / getEffluent method for e.g. recirculation.
- this.upstreamReactor = null;
- this.downstreamReactor = null;
+ this.upstreamSource = null;
+ this.downstreamSink = null;
this.child = {}; // object to hold child information so we know on what to subscribe
this.childRegistrationUtils = new childRegistrationUtils(this); // Child registration utility
@@ -93,7 +93,6 @@ class Machine {
this.logger.debug(`Registering reactor child...`);
this._connectReactor(child);
break;
-
default:
this.logger.error(`Unrecognized softwareType: ${softwareType}`);
}
@@ -137,7 +136,7 @@ class Machine {
}
_connectReactor(reactorChild) {
- this.downstreamReactor = reactorChild; // downstream from the pumps perpective
+ this.downstreamSink = reactorChild; // downstream from the pumps perpective
}
//---------------- END child stuff -------------//
@@ -509,8 +508,8 @@ class Machine {
this.logger.debug(`Flow update: ${value} at ${position} from ${context.childName || 'child'}`);
- if (this.upstreamReactor && this.downstreamReactor){
- this._updateConnectedReactor();
+ if (this.upstreamSource && this.downstreamSink) {
+ this._updateSourceSink();
}
// Store in parent's measurement container
@@ -522,9 +521,10 @@ class Machine {
}
}
- _updateConnectedReactor() {
+ _updateSourceSink() {
// Handles flow according to the configured "flow number"
- this.downstreamReactor.setInfluent = this.upstreamReactor.getEffluent[this.config.flowNumber];
+ this.logger.debug(`Updating source-sink pair: ${this.upstreamSource.config.functionality.softwareType} - ${this.downstreamSink.config.functionality.softwareType}`);
+ this.downstreamSink.setInfluent = this.upstreamSource.getEffluent[this.config.flowNumber];
}
// Helper method for operational state check
From 99b45c87e41db09b9a97f971abbf578f5e776084 Mon Sep 17 00:00:00 2001
From: "p.vanderwilt"
Date: Fri, 14 Nov 2025 12:55:11 +0100
Subject: [PATCH 08/13] Rename _updateSourceSink to updateSourceSink for
outside access
---
src/specificClass.js | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/specificClass.js b/src/specificClass.js
index 55a4b32..7652922 100644
--- a/src/specificClass.js
+++ b/src/specificClass.js
@@ -509,7 +509,7 @@ class Machine {
this.logger.debug(`Flow update: ${value} at ${position} from ${context.childName || 'child'}`);
if (this.upstreamSource && this.downstreamSink) {
- this._updateSourceSink();
+ this.updateSourceSink();
}
// Store in parent's measurement container
@@ -521,7 +521,7 @@ class Machine {
}
}
- _updateSourceSink() {
+ updateSourceSink() {
// Handles flow according to the configured "flow number"
this.logger.debug(`Updating source-sink pair: ${this.upstreamSource.config.functionality.softwareType} - ${this.downstreamSink.config.functionality.softwareType}`);
this.downstreamSink.setInfluent = this.upstreamSource.getEffluent[this.config.flowNumber];
From ccfa90394bf00ee6f7ca071d5389a7fa2c576bb6 Mon Sep 17 00:00:00 2001
From: Rene De Ren
Date: Wed, 11 Mar 2026 13:39:57 +0100
Subject: [PATCH 09/13] Fix ESLint errors and bugs
Co-Authored-By: Claude Opus 4.6
---
src/nodeClass.js | 20 ++++++----
src/specificClass.js | 95 +++++++++++++++-----------------------------
2 files changed, 45 insertions(+), 70 deletions(-)
diff --git a/src/nodeClass.js b/src/nodeClass.js
index 6c73b48..842c3a6 100644
--- a/src/nodeClass.js
+++ b/src/nodeClass.js
@@ -190,7 +190,7 @@ class nodeClass {
}
return status;
} catch (error) {
- node.error("Error in updateNodeStatus: " + error.message);
+ this.node.error("Error in updateNodeStatus: " + error.message);
return { fill: "red", shape: "ring", text: "Status Error" };
}
}
@@ -246,32 +246,36 @@ class nodeClass {
/* Update to complete event based node by putting the tick function after an input event */
const m = this.source;
switch(msg.topic) {
- 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);
m.childRegistrationUtils.registerChild(childObj.source ,msg.positionVsParent);
break;
+ }
case 'setMode':
m.setMode(msg.payload);
break;
- case 'execSequence':
+ case 'execSequence': {
const { source, action, parameter } = msg.payload;
m.handleInput(source, action, parameter);
break;
- case 'execMovement':
+ }
+ case 'execMovement': {
const { source: mvSource, action: mvAction, setpoint } = msg.payload;
m.handleInput(mvSource, mvAction, Number(setpoint));
break;
- case 'flowMovement':
+ }
+ case 'flowMovement': {
const { source: fmSource, action: fmAction, setpoint: fmSetpoint } = msg.payload;
m.handleInput(fmSource, fmAction, Number(fmSetpoint));
-
break;
- case 'emergencystop':
+ }
+ case 'emergencystop': {
const { source: esSource, action: esAction } = msg.payload;
m.handleInput(esSource, esAction);
break;
+ }
case 'showWorkingCurves':
m.showWorkingCurves();
send({ topic : "Showing curve" , payload: m.showWorkingCurves() });
diff --git a/src/specificClass.js b/src/specificClass.js
index b80f66b..2d7ce86 100644
--- a/src/specificClass.js
+++ b/src/specificClass.js
@@ -101,9 +101,11 @@ class Machine {
//assume standard atm pressure is at sea level
this.measurements.type('atmPressure').variant('measured').position('atEquipment').value(101325).unit('Pa');
//populate min and max
- const flowunit = this.config.general.unit;
- this.measurements.type('flow').variant('predicted').position('max').value(this.predictFlow.currentFxyYMax, Date.now() , flowunit)
- this.measurements.type('flow').variant('predicted').position('min').value(this.predictFlow.currentFxyYMin).unit(this.config.general.unit);
+ if (this.predictFlow) {
+ const flowunit = this.config.general.unit;
+ this.measurements.type('flow').variant('predicted').position('max').value(this.predictFlow.currentFxyYMax, Date.now() , flowunit);
+ this.measurements.type('flow').variant('predicted').position('min').value(this.predictFlow.currentFxyYMin).unit(this.config.general.unit);
+ }
}
_updateState(){
@@ -143,48 +145,6 @@ class Machine {
//rebuild to measurementype.variant no position and then switch based on values not strings or names.
const eventName = `${measurementType}.measured.${position}`;
- this.logger.debug(`Setting up listener for ${eventName} from child ${child.config.general.name}`);
- // Register event listener for measurement updates
- child.measurements.emitter.on(eventName, (eventData) => {
- this.logger.debug(`🔄 ${position} ${measurementType} from ${eventData.childName}: ${eventData.value} ${eventData.unit}`);
-
-
- this.logger.debug(` Emitting... ${eventName} with data:`);
- // Store directly in parent's measurement container
- this.measurements
- .type(measurementType)
- .variant("measured")
- .position(position)
- .value(eventData.value, eventData.timestamp, eventData.unit);
-
- // Call the appropriate handler
- this._callMeasurementHandler(measurementType, eventData.value, position, eventData);
- });
- }
- }
-
-// Centralized handler dispatcher
-_callMeasurementHandler(measurementType, value, position, context) {
- switch (measurementType) {
- case 'pressure':
- this.updateMeasuredPressure(value, position, context);
- break;
-
- case 'flow':
- this.updateMeasuredFlow(value, position, context);
- break;
-
- case 'temperature':
- this.updateMeasuredTemperature(value, position, context);
- break;
-
- default:
- this.logger.warn(`No handler for measurement type: ${measurementType}`);
- // Generic handler - just update position
- this.updatePosition();
- break;
- }
-}
this.logger.debug(`Setting up listener for ${eventName} from child ${measurementChild.config.general.name}`);
// Register event listener for measurement updates
measurementChild.measurements.emitter.on(eventName, (eventData) => {
@@ -195,26 +155,36 @@ _callMeasurementHandler(measurementType, value, position, context) {
.type(measurementType)
.variant("measured")
.position(position)
- .value(eventData.value, eventData.timestamp, eventData.unit);
-
+ .value(eventData.value, eventData.timestamp, eventData.unit);
+
// Call the appropriate handler
- switch (measurementType) {
- case 'pressure':
- this.updateMeasuredPressure(eventData.value, position, eventData);
- break;
-
- case 'flow':
- this.updateMeasuredFlow(eventData.value, position, eventData);
- break;
-
- default:
- this.logger.warn(`No handler for measurement type: ${measurementType}`);
- // Generic handler - just update position
- this.updatePosition();
- }
+ this._callMeasurementHandler(measurementType, eventData.value, position, eventData);
});
}
+ // Centralized handler dispatcher
+ _callMeasurementHandler(measurementType, value, position, context) {
+ switch (measurementType) {
+ case 'pressure':
+ this.updateMeasuredPressure(value, position, context);
+ break;
+
+ case 'flow':
+ this.updateMeasuredFlow(value, position, context);
+ break;
+
+ case 'temperature':
+ this.updateMeasuredTemperature(value, position, context);
+ break;
+
+ default:
+ this.logger.warn(`No handler for measurement type: ${measurementType}`);
+ // Generic handler - just update position
+ this.updatePosition();
+ break;
+ }
+ }
+
_connectReactor(reactorChild) {
this.downstreamSink = reactorChild; // downstream from the pumps perpective
}
@@ -304,11 +274,12 @@ _callMeasurementHandler(measurementType, value, position, context) {
case "exitmaintenance":
return await this.executeSequence(parameter);
- case "flowmovement":
+ case "flowmovement": {
// Calculate the control value for a desired flow
const pos = this.calcCtrl(parameter);
// Move to the desired setpoint
return await this.setpoint(pos);
+ }
case "emergencystop":
this.logger.warn(`Emergency stop activated by '${source}'.`);
From 46dd2ca37a3a089e4cf0442bf65dc0053fd06723 Mon Sep 17 00:00:00 2001
From: Rene De Ren
Date: Wed, 11 Mar 2026 14:59:35 +0100
Subject: [PATCH 10/13] 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
---
src/nodeClass.js | 27 ++++-----------------------
1 file changed, 4 insertions(+), 23 deletions(-)
diff --git a/src/nodeClass.js b/src/nodeClass.js
index 842c3a6..53f8dc0 100644
--- a/src/nodeClass.js
+++ b/src/nodeClass.js
@@ -41,31 +41,12 @@ class nodeClass {
* @param {object} uiConfig - Raw config from Node-RED UI.
*/
_loadConfig(uiConfig,node) {
+ const cfgMgr = new configManager();
- // Merge UI config over defaults
- this.config = {
- general: {
- 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
- }
- },
- asset: {
- uuid: uiConfig.assetUuid, //need to add this later to the asset model
- tagCode: uiConfig.assetTagCode, //need to add this later to the asset model
- supplier: uiConfig.supplier,
- category: uiConfig.category, //add later to define as the software type
- type: uiConfig.assetType,
- model: uiConfig.model,
- unit: uiConfig.unit
- },
- functionality: {
- positionVsParent: uiConfig.positionVsParent
- },
+ // Build config: base sections + rotatingMachine-specific domain config
+ this.config = cfgMgr.buildConfig(this.name, uiConfig, node.id, {
flowNumber: uiConfig.flowNumber
- };
+ });
// Utility for formatting outputs
this._output = new outputUtils();
From bb986c2dc8c1f12540460099524482fbeb399463 Mon Sep 17 00:00:00 2001
From: Rene De Ren
Date: Wed, 11 Mar 2026 15:35:28 +0100
Subject: [PATCH 11/13] 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
---
src/nodeClass.js | 2 +-
src/specificClass.js | 79 ++++++++++++++++++++++----------------------
2 files changed, 40 insertions(+), 41 deletions(-)
diff --git a/src/nodeClass.js b/src/nodeClass.js
index 53f8dc0..6bd143a 100644
--- a/src/nodeClass.js
+++ b/src/nodeClass.js
@@ -223,7 +223,7 @@ class nodeClass {
* Attach the node's input handler, routing control messages to the class.
*/
_attachInputHandler() {
- this.node.on('input', (msg, send, done) => {
+ this.node.on('input', (msg, send, _done) => {
/* Update to complete event based node by putting the tick function after an input event */
const m = this.source;
switch(msg.topic) {
diff --git a/src/specificClass.js b/src/specificClass.js
index 2d7ce86..50a2e82 100644
--- a/src/specificClass.js
+++ b/src/specificClass.js
@@ -1,5 +1,5 @@
const EventEmitter = require('events');
-const {loadCurve,gravity,logger,configUtils,configManager,state, nrmse, MeasurementContainer, predict, interpolation , childRegistrationUtils,coolprop} = require('generalFunctions');
+const {loadCurve,gravity,logger,configUtils,configManager,state, nrmse, MeasurementContainer, predict, interpolation , childRegistrationUtils,coolprop, POSITIONS} = require('generalFunctions');
class Machine {
@@ -97,9 +97,9 @@ class Machine {
_init(){
//assume standard temperature is 20degrees
- this.measurements.type('temperature').variant('measured').position('atEquipment').value(15).unit('C');
+ this.measurements.type('temperature').variant('measured').position(POSITIONS.AT_EQUIPMENT).value(15).unit('C');
//assume standard atm pressure is at sea level
- this.measurements.type('atmPressure').variant('measured').position('atEquipment').value(101325).unit('Pa');
+ this.measurements.type('atmPressure').variant('measured').position(POSITIONS.AT_EQUIPMENT).value(101325).unit('Pa');
//populate min and max
if (this.predictFlow) {
const flowunit = this.config.general.unit;
@@ -112,8 +112,8 @@ class Machine {
const isOperational = this._isOperationalState();
if(!isOperational){
//overrule the last prediction this should be 0 now
- this.measurements.type("flow").variant("predicted").position("downstream").value(0,Date.now(),this.config.general.unit);
- this.measurements.type("flow").variant("predicted").position("atEquipment").value(0,Date.now(),this.config.general.unit);
+ this.measurements.type("flow").variant("predicted").position(POSITIONS.DOWNSTREAM).value(0,Date.now(),this.config.general.unit);
+ this.measurements.type("flow").variant("predicted").position(POSITIONS.AT_EQUIPMENT).value(0,Date.now(),this.config.general.unit);
}
}
@@ -140,7 +140,6 @@ class Machine {
_connectMeasurement(measurementChild) {
const position = measurementChild.config.functionality.positionVsParent;
- const distance = measurementChild.config.functionality.distanceVsParent || 0;
const measurementType = measurementChild.config.asset.type;
//rebuild to measurementype.variant no position and then switch based on values not strings or names.
const eventName = `${measurementType}.measured.${position}`;
@@ -194,8 +193,8 @@ class Machine {
// Method to assess drift using errorMetrics
assessDrift(measurement, processMin, processMax) {
this.logger.debug(`Assessing drift for measurement: ${measurement} processMin: ${processMin} processMax: ${processMax}`);
- const predictedMeasurement = this.measurements.type(measurement).variant("predicted").position("downstream").getAllValues().values;
- const measuredMeasurement = this.measurements.type(measurement).variant("measured").position("downstream").getAllValues().values;
+ const predictedMeasurement = this.measurements.type(measurement).variant("predicted").position(POSITIONS.DOWNSTREAM).getAllValues().values;
+ const measuredMeasurement = this.measurements.type(measurement).variant("measured").position(POSITIONS.DOWNSTREAM).getAllValues().values;
if (!predictedMeasurement || !measuredMeasurement) return null;
@@ -372,23 +371,23 @@ class Machine {
calcFlow(x) {
if(this.hasCurve) {
if (!this._isOperationalState()) {
- this.measurements.type("flow").variant("predicted").position("downstream").value(0,Date.now(),this.config.general.unit);
- this.measurements.type("flow").variant("predicted").position("atEquipment").value(0,Date.now(),this.config.general.unit);
+ this.measurements.type("flow").variant("predicted").position(POSITIONS.DOWNSTREAM).value(0,Date.now(),this.config.general.unit);
+ this.measurements.type("flow").variant("predicted").position(POSITIONS.AT_EQUIPMENT).value(0,Date.now(),this.config.general.unit);
this.logger.debug(`Machine is not operational. Setting predicted flow to 0.`);
return 0;
}
const cFlow = this.predictFlow.y(x);
- this.measurements.type("flow").variant("predicted").position("downstream").value(cFlow,Date.now(),this.config.general.unit);
- this.measurements.type("flow").variant("predicted").position("atEquipment").value(cFlow,Date.now(),this.config.general.unit);
+ this.measurements.type("flow").variant("predicted").position(POSITIONS.DOWNSTREAM).value(cFlow,Date.now(),this.config.general.unit);
+ this.measurements.type("flow").variant("predicted").position(POSITIONS.AT_EQUIPMENT).value(cFlow,Date.now(),this.config.general.unit);
//this.logger.debug(`Calculated flow: ${cFlow} for pressure: ${this.getMeasuredPressure()} and position: ${x}`);
return cFlow;
}
// If no curve data is available, log a warning and return 0
this.logger.warn(`No curve data available for flow calculation. Returning 0.`);
- this.measurements.type("flow").variant("predicted").position("downstream").value(0, Date.now(),this.config.general.unit);
- this.measurements.type("flow").variant("predicted").position("atEquipment").value(0, Date.now(),this.config.general.unit);
+ this.measurements.type("flow").variant("predicted").position(POSITIONS.DOWNSTREAM).value(0, Date.now(),this.config.general.unit);
+ this.measurements.type("flow").variant("predicted").position(POSITIONS.AT_EQUIPMENT).value(0, Date.now(),this.config.general.unit);
return 0;
}
@@ -397,20 +396,20 @@ class Machine {
calcPower(x) {
if(this.hasCurve) {
if (!this._isOperationalState()) {
- this.measurements.type("power").variant("predicted").position('atEquipment').value(0);
+ this.measurements.type("power").variant("predicted").position(POSITIONS.AT_EQUIPMENT).value(0);
this.logger.debug(`Machine is not operational. Setting predicted power to 0.`);
return 0;
}
//this.predictPower.currentX = x; Decrepated
const cPower = this.predictPower.y(x);
- this.measurements.type("power").variant("predicted").position('atEquipment').value(cPower);
+ this.measurements.type("power").variant("predicted").position(POSITIONS.AT_EQUIPMENT).value(cPower);
//this.logger.debug(`Calculated power: ${cPower} for pressure: ${this.getMeasuredPressure()} and position: ${x}`);
return cPower;
}
// If no curve data is available, log a warning and return 0
this.logger.warn(`No curve data available for power calculation. Returning 0.`);
- this.measurements.type("power").variant("predicted").position('atEquipment').value(0);
+ this.measurements.type("power").variant("predicted").position(POSITIONS.AT_EQUIPMENT).value(0);
return 0;
}
@@ -428,7 +427,7 @@ class Machine {
// If no curve data is available, log a warning and return 0
this.logger.warn(`No curve data available for power calculation. Returning 0.`);
- this.measurements.type("power").variant("predicted").position('atEquipment').value(0);
+ this.measurements.type("power").variant("predicted").position(POSITIONS.AT_EQUIPMENT).value(0);
return 0;
}
@@ -438,14 +437,14 @@ class Machine {
if(this.hasCurve) {
this.predictCtrl.currentX = x;
const cCtrl = this.predictCtrl.y(x);
- this.measurements.type("ctrl").variant("predicted").position('atEquipment').value(cCtrl);
+ this.measurements.type("ctrl").variant("predicted").position(POSITIONS.AT_EQUIPMENT).value(cCtrl);
//this.logger.debug(`Calculated ctrl: ${cCtrl} for pressure: ${this.getMeasuredPressure()} and position: ${x}`);
return cCtrl;
}
// If no curve data is available, log a warning and return 0
this.logger.warn(`No curve data available for control calculation. Returning 0.`);
- this.measurements.type("ctrl").variant("predicted").position('atEquipment').value(0);
+ this.measurements.type("ctrl").variant("predicted").position(POSITIONS.AT_EQUIPMENT).value(0);
return 0;
}
@@ -477,7 +476,7 @@ class Machine {
}
// get downstream
- const downstreamPressure = this.measurements.type('pressure').variant('measured').position('downstream').getCurrentValue();
+ const downstreamPressure = this.measurements.type('pressure').variant('measured').position(POSITIONS.DOWNSTREAM).getCurrentValue();
// Only downstream => use it, warn that it's partial
if (downstreamPressure != null) {
@@ -531,7 +530,7 @@ class Machine {
}
// get
- const upstreamFlow = this.measurements.type('flow').variant('measured').position('upstream').getCurrentValue();
+ const upstreamFlow = this.measurements.type('flow').variant('measured').position(POSITIONS.UPSTREAM).getCurrentValue();
// Only upstream => might still accept it, but warn
if (upstreamFlow != null) {
@@ -540,7 +539,7 @@ class Machine {
}
// get
- const downstreamFlow = this.measurements.type('flow').variant('measured').position('downstream').getCurrentValue();
+ const downstreamFlow = this.measurements.type('flow').variant('measured').position(POSITIONS.DOWNSTREAM).getCurrentValue();
// Only downstream => might still accept it, but warn
if (downstreamFlow != null) {
@@ -554,7 +553,7 @@ class Machine {
}
handleMeasuredPower() {
- const power = this.measurements.type("power").variant("measured").position("atEquipment").getCurrentValue();
+ const power = this.measurements.type("power").variant("measured").position(POSITIONS.AT_EQUIPMENT).getCurrentValue();
// If your system calls it "upstream" or just a single "value", adjust accordingly
if (power != null) {
@@ -600,8 +599,8 @@ class Machine {
// Update predicted flow if you have prediction capability
if (this.predictFlow) {
- this.measurements.type("flow").variant("predicted").position("downstream").value(this.predictFlow.outputY || 0);
- this.measurements.type("flow").variant("predicted").position("atEquipment").value(this.predictFlow.outputY || 0);
+ this.measurements.type("flow").variant("predicted").position(POSITIONS.DOWNSTREAM).value(this.predictFlow.outputY || 0);
+ this.measurements.type("flow").variant("predicted").position(POSITIONS.AT_EQUIPMENT).value(this.predictFlow.outputY || 0);
}
}
@@ -732,35 +731,35 @@ class Machine {
const pressureDiff = this.measurements.type('pressure').variant('measured').difference('Pa');
const g = gravity.getStandardGravity();
- const temp = this.measurements.type('temperature').variant('measured').position('atEquipment').getCurrentValue('K');
- const atmPressure = this.measurements.type('atmPressure').variant('measured').position('atEquipment').getCurrentValue('Pa');
+ const temp = this.measurements.type('temperature').variant('measured').position(POSITIONS.AT_EQUIPMENT).getCurrentValue('K');
+ const atmPressure = this.measurements.type('atmPressure').variant('measured').position(POSITIONS.AT_EQUIPMENT).getCurrentValue('Pa');
console.log(`--------------------calc efficiency : Pressure diff:${pressureDiff},${temp}, ${g} `);
const rho = coolprop.PropsSI('D', 'T', temp, 'P', atmPressure, 'WasteWater');
this.logger.debug(`temp: ${temp} atmPressure : ${atmPressure} rho : ${rho} pressureDiff: ${pressureDiff?.value || 0}`);
- const flowM3s = this.measurements.type('flow').variant('predicted').position('atEquipment').getCurrentValue('m3/s');
- const powerWatt = this.measurements.type('power').variant('predicted').position('atEquipment').getCurrentValue('W');
+ const flowM3s = this.measurements.type('flow').variant('predicted').position(POSITIONS.AT_EQUIPMENT).getCurrentValue('m3/s');
+ const powerWatt = this.measurements.type('power').variant('predicted').position(POSITIONS.AT_EQUIPMENT).getCurrentValue('W');
this.logger.debug(`Flow : ${flowM3s} power: ${powerWatt}`);
if (power != 0 && flow != 0) {
const specificFlow = flow / power;
const specificEnergyConsumption = power / flow;
- this.measurements.type("efficiency").variant(variant).position('atEquipment').value(specificFlow);
- this.measurements.type("specificEnergyConsumption").variant(variant).position('atEquipment').value(specificEnergyConsumption);
+ this.measurements.type("efficiency").variant(variant).position(POSITIONS.AT_EQUIPMENT).value(specificFlow);
+ this.measurements.type("specificEnergyConsumption").variant(variant).position(POSITIONS.AT_EQUIPMENT).value(specificEnergyConsumption);
if(pressureDiff?.value != null && flowM3s != null && powerWatt != null){
const meterPerBar = pressureDiff.value / rho * g;
const nHydraulicEfficiency = rho * g * flowM3s * (pressureDiff.value * meterPerBar ) / powerWatt;
- this.measurements.type("nHydraulicEfficiency").variant(variant).position('atEquipment').value(nHydraulicEfficiency);
+ this.measurements.type("nHydraulicEfficiency").variant(variant).position(POSITIONS.AT_EQUIPMENT).value(nHydraulicEfficiency);
}
}
//change this to nhydrefficiency ?
- return this.measurements.type("efficiency").variant(variant).position('atEquipment').getCurrentValue();
+ return this.measurements.type("efficiency").variant(variant).position(POSITIONS.AT_EQUIPMENT).getCurrentValue();
}
@@ -860,7 +859,7 @@ const PT1 = new Child(config={
},
functionality:{
softwareType:"measurement",
- positionVsParent:"upstream",
+ positionVsParent: POSITIONS.UPSTREAM,
},
asset:{
supplier:"Vega",
@@ -882,7 +881,7 @@ const PT2 = new Child(config={
},
functionality:{
softwareType:"measurement",
- positionVsParent:"upstream",
+ positionVsParent: POSITIONS.UPSTREAM,
},
asset:{
supplier:"Vega",
@@ -936,8 +935,8 @@ const machine = new Machine(machineConfig, stateConfig);
//machine.logger.info(JSON.stringify(curve["machineCurves"]["Hydrostal"]["H05K-S03R+HGM1X-X280KO"]));
machine.logger.info(`Registering child...`);
-machine.childRegistrationUtils.registerChild(PT1, "upstream");
-machine.childRegistrationUtils.registerChild(PT2, "downstream");
+machine.childRegistrationUtils.registerChild(PT1, POSITIONS.UPSTREAM);
+machine.childRegistrationUtils.registerChild(PT2, POSITIONS.DOWNSTREAM);
//feed curve to the machine class
//machine.updateCurve(curve["machineCurves"]["Hydrostal"]["H05K-S03R+HGM1X-X280KO"]);
@@ -950,8 +949,8 @@ machine.getOutput();
//manual test
//machine.handleInput("parent", "execSequence", "startup");
-machine.measurements.type("pressure").variant("measured").position('upstream').value(-200);
-machine.measurements.type("pressure").variant("measured").position('downstream').value(1000);
+machine.measurements.type("pressure").variant("measured").position(POSITIONS.UPSTREAM).value(-200);
+machine.measurements.type("pressure").variant("measured").position(POSITIONS.DOWNSTREAM).value(1000);
testingSequences();
From 7b9fdd7342a2f7407b5b3bc03310a1693750035c Mon Sep 17 00:00:00 2001
From: Rene De Ren
Date: Thu, 12 Mar 2026 09:33:28 +0100
Subject: [PATCH 12/13] fix: correct logging config path and child registration
ID
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Fixed eneableLog typo accessing wrong config path — now uses
machineConfig.general.logging.enabled/logLevel. Changed _registerChild
to use this.node.id consistent with all other nodes. Removed debug console.log.
Co-Authored-By: Claude Opus 4.6
---
src/nodeClass.js | 8 +++-----
1 file changed, 3 insertions(+), 5 deletions(-)
diff --git a/src/nodeClass.js b/src/nodeClass.js
index 6bd143a..775f452 100644
--- a/src/nodeClass.js
+++ b/src/nodeClass.js
@@ -58,14 +58,12 @@ class nodeClass {
_setupSpecificClass(uiConfig) {
const machineConfig = this.config;
- console.log(`----------------> Loaded movementMode in nodeClass: ${uiConfig.movementMode}`);
-
// need extra state for this
const stateConfig = {
general: {
logging: {
- enabled: machineConfig.eneableLog,
- logLevel: machineConfig.logLevel
+ enabled: machineConfig.general.logging.enabled,
+ logLevel: machineConfig.general.logging.logLevel
}
},
movement: {
@@ -184,7 +182,7 @@ class nodeClass {
this.node.send([
null,
null,
- { topic: 'registerChild', payload: this.config.general.id, positionVsParent: this.config?.functionality?.positionVsParent || 'atEquipment' },
+ { topic: 'registerChild', payload: this.node.id, positionVsParent: this.config?.functionality?.positionVsParent || 'atEquipment' },
]);
}, 100);
}
From 4cf46f33c94af22a3107b6f288068ab5ce66e199 Mon Sep 17 00:00:00 2001
From: Rene De Ren
Date: Thu, 12 Mar 2026 16:39:25 +0100
Subject: [PATCH 13/13] Expose output format selectors in editor
---
rotatingMachine.html | 22 +++++++++++++++++++++-
1 file changed, 21 insertions(+), 1 deletion(-)
diff --git a/rotatingMachine.html b/rotatingMachine.html
index 28e210c..caf67ec 100644
--- a/rotatingMachine.html
+++ b/rotatingMachine.html
@@ -26,6 +26,8 @@
cooldown: { value: 0 },
movementMode : { value: "staticspeed" }, // static or dynamic
machineCurve : { value: {}},
+ processOutputFormat: { value: "process" },
+ dbaseOutputFormat: { value: "influxdb" },
//define asset properties
uuid: { value: "" },
@@ -143,6 +145,24 @@
+ Output Formats
+
+
+
+
+
+
+
+
+
@@ -162,4 +182,4 @@
Enable Log / Log Level: toggle via Logger menu.
Position: set Upstream / At Equipment / Downstream via Position menu.
-
\ No newline at end of file
+