From d5db1ae0a0fc4c00c4fb71761e29c4bbe8f07734 Mon Sep 17 00:00:00 2001
From: "p.vanderwilt"
Date: Wed, 16 Jul 2025 10:57:35 +0200
Subject: [PATCH 1/6] Added seperate process, DB and parent outputs
---
advanced-reactor.html | 5 +++--
src/nodeClass.js | 18 ++++++++++++++++--
2 files changed, 19 insertions(+), 4 deletions(-)
diff --git a/advanced-reactor.html b/advanced-reactor.html
index 40ea4f6..91df690 100644
--- a/advanced-reactor.html
+++ b/advanced-reactor.html
@@ -26,8 +26,9 @@
X_TS_init: { value: 125.0009, required: true }
},
inputs: 1,
- outputs: 1,
- outputLabels: "Effluent",
+ outputs: 3,
+ inputLabels: ["input"],
+ outputLabels: ["process", "dbase", "parent"],
icon: "font-awesome/fa-recycle",
label: function() {
return this.name || "advanced-reactor";
diff --git a/src/nodeClass.js b/src/nodeClass.js
index f4bbe25..9eba995 100644
--- a/src/nodeClass.js
+++ b/src/nodeClass.js
@@ -16,7 +16,7 @@ class nodeClass {
this.name = nameOfNode;
this._loadConfig(uiConfig)
-
+ this._registerChild();
this._setupClass();
this._attachInputHandler();
@@ -60,7 +60,7 @@ class nodeClass {
if (toggleUpdate) {
this.reactor.updateState(msg.timestamp);
- send(this.reactor.getEffluent);
+ send([this.reactor.getEffluent, null, null]);
}
if (done) {
@@ -100,6 +100,20 @@ class nodeClass {
}
}
+ /**
+ * Register this node as a child upstream and downstream.
+ * Delayed to avoid Node-RED startup race conditions.
+ */
+ _registerChild() {
+ setTimeout(() => {
+ this.node.send([
+ null,
+ null,
+ { topic: 'registerChild', payload: this.node.id , positionVsParent: this.config?.functionality?.positionVsParent || 'atEquipment' },
+ ]);
+ }, 100);
+ }
+
/**
* Setup reactor class based on config
*/
From 633a0884835c69095aa2696779371bbefafc8900 Mon Sep 17 00:00:00 2001
From: "p.vanderwilt"
Date: Wed, 16 Jul 2025 16:08:14 +0200
Subject: [PATCH 2/6] Added handles for influent change emitter
---
src/nodeClass.js | 20 ++++++++++---------
src/specificClass.js | 46 +++++++++++++++++++++-----------------------
2 files changed, 33 insertions(+), 33 deletions(-)
diff --git a/src/nodeClass.js b/src/nodeClass.js
index 9eba995..c36152d 100644
--- a/src/nodeClass.js
+++ b/src/nodeClass.js
@@ -32,12 +32,11 @@ class nodeClass {
switch (msg.topic) {
case "clock":
toggleUpdate = true;
+ this.reactor.updateState(msg.timestamp);
+ send([this.reactor.getEffluent, null, null]);
break;
case "Fluent":
this.reactor.setInfluent = msg;
- if (msg.payload.inlet == 0) {
- toggleUpdate = true;
- }
break;
case "OTR":
this.reactor.setOTR = msg;
@@ -58,11 +57,6 @@ class nodeClass {
console.log("Unknown topic: " + msg.topic);
}
- if (toggleUpdate) {
- this.reactor.updateState(msg.timestamp);
- send([this.reactor.getEffluent, null, null]);
- }
-
if (done) {
done();
}
@@ -75,6 +69,14 @@ class nodeClass {
*/
_loadConfig(uiConfig) {
this.config = {
+ general: {
+ name: uiConfig.name || this.name,
+ id: this.node.id,
+ unit: null
+ },
+ functionality: {
+ softwareType: null // should be set in config manager
+ },
reactor_type: uiConfig.reactor_type,
volume: parseFloat(uiConfig.volume),
length: parseFloat(uiConfig.length),
@@ -109,7 +111,7 @@ class nodeClass {
this.node.send([
null,
null,
- { topic: 'registerChild', payload: this.node.id , positionVsParent: this.config?.functionality?.positionVsParent || 'atEquipment' },
+ { topic: 'registerChild', payload: this.node.id, positionVsParent: this.config?.functionality?.positionVsParent || 'atEquipment' },
]);
}, 100);
}
diff --git a/src/specificClass.js b/src/specificClass.js
index 8fb1448..474ebf7 100644
--- a/src/specificClass.js
+++ b/src/specificClass.js
@@ -1,13 +1,14 @@
const ASM3 = require('./reaction_modules/asm3_class.js');
-const { create, all } = require('mathjs');
+const { create, all, isArray } = require('mathjs');
const { assertNoNaN } = require('./utils.js');
const { childRegistrationUtils, logger, MeasurementContainer } = require('generalFunctions');
+const EventEmitter = require('events');
-const config = {
+const mathConfig = {
matrix: 'Array' // use Array as the matrix type
};
-const math = create(all, config);
+const math = create(all, mathConfig);
const S_O_INDEX = 0;
const NUM_SPECIES = 13;
@@ -20,7 +21,8 @@ class Reactor {
*/
constructor(config) {
// EVOLV stuff
- this.logger = new logger(); //TODO: attach config
+ this.logger = new logger(undefined, undefined, config.general.name);
+ this.emitter = new EventEmitter();
this.measurements = new MeasurementContainer();
this.childRegistrationUtils = new childRegistrationUtils(this); // Child registration utility
@@ -70,6 +72,17 @@ class Reactor {
this.OTR = input.payload;
}
+ /**
+ * Getter for effluent data.
+ * @returns {object} Effluent data object (msg), defaults to inlet 0.
+ */
+ get getEffluent() { // getter for Effluent, defaults to inlet 0
+ if (isArray(this.state.at(-1))) {
+ return { topic: "Fluent", payload: { inlet: 0, F: math.sum(this.Fs), C: this.state.at(-1) }, timestamp: this.currentTime };
+ }
+ return { topic: "Fluent", payload: { inlet: 0, F: math.sum(this.Fs), C: this.state }, timestamp: this.currentTime };
+ }
+
/**
* Calculate the oxygen transfer rate (OTR) based on the dissolved oxygen concentration and temperature.
* @param {number} S_O - Dissolved oxygen concentration [g O2 m-3].
@@ -104,11 +117,12 @@ class Reactor {
let n_iter = Math.floor(this.speedUpFactor * (newTime-this.currentTime) / (this.timeStep*day2ms));
if (n_iter) {
let n = 0;
- while (n < n_iter) {
- this.tick(this.timeStep);
- n += 1;
- }
+ while (n < n_iter) {
+ this.tick(this.timeStep);
+ n += 1;
+ }
this.currentTime += n_iter * this.timeStep * day2ms / this.speedUpFactor;
+ this.emitter.emit("stateChanges", this.getEffluent);
}
}
}
@@ -123,14 +137,6 @@ class Reactor_CSTR extends Reactor {
this.state = config.initialState;
}
- /**
- * Getter for effluent data.
- * @returns {object} Effluent data object (msg), defaults to inlet 0.
- */
- get getEffluent() { // getter for Effluent, defaults to inlet 0
- return { topic: "Fluent", payload: { inlet: 0, F: math.sum(this.Fs), C: this.state }, timestamp: this.currentTime };
- }
-
/**
* Tick the reactor state using the forward Euler method.
* @param {number} time_step - Time step for the simulation [d].
@@ -191,14 +197,6 @@ class Reactor_PFR extends Reactor {
this.D = input.payload;
}
- /**
- * Getter for effluent data.
- * @returns {object} Effluent data object (msg), defaults to inlet 0.
- */
- get getEffluent() {
- return { topic: "Fluent", payload: { inlet: 0, F: math.sum(this.Fs), C: this.state.at(-1) }, timestamp: this.currentTime };
- }
-
updateState(newTime) {
super.updateState(newTime);
let Pe_local = this.d_x*math.sum(this.Fs)/(this.D*this.A)
From 73c2b654e145e7c8ec3f96282f49fb54b6e8a1aa Mon Sep 17 00:00:00 2001
From: "p.vanderwilt"
Date: Wed, 16 Jul 2025 16:54:30 +0200
Subject: [PATCH 3/6] Deal with clock singal
---
additional_nodes/recirculation-pump.js | 2 ++
additional_nodes/settling-basin.js | 2 ++
src/nodeClass.js | 1 +
3 files changed, 5 insertions(+)
diff --git a/additional_nodes/recirculation-pump.js b/additional_nodes/recirculation-pump.js
index cd9fc21..2a02932 100644
--- a/additional_nodes/recirculation-pump.js
+++ b/additional_nodes/recirculation-pump.js
@@ -24,6 +24,8 @@ module.exports = function(RED) {
send([msg_F1, msg_F2]);
break;
+ case "clock":
+ break;
default:
console.log("Unknown topic: " + msg.topic);
}
diff --git a/additional_nodes/settling-basin.js b/additional_nodes/settling-basin.js
index e19457a..dd3d697 100644
--- a/additional_nodes/settling-basin.js
+++ b/additional_nodes/settling-basin.js
@@ -41,6 +41,8 @@ module.exports = function(RED) {
send([msg_F1, msg_F2]);
break;
+ case "clock":
+ break;
default:
console.log("Unknown topic: " + msg.topic);
}
diff --git a/src/nodeClass.js b/src/nodeClass.js
index c36152d..94b3965 100644
--- a/src/nodeClass.js
+++ b/src/nodeClass.js
@@ -34,6 +34,7 @@ class nodeClass {
toggleUpdate = true;
this.reactor.updateState(msg.timestamp);
send([this.reactor.getEffluent, null, null]);
+ send([msg, null, null])
break;
case "Fluent":
this.reactor.setInfluent = msg;
From b1719376cffcd0e32d96dae20e438262b528851d Mon Sep 17 00:00:00 2001
From: "p.vanderwilt"
Date: Mon, 21 Jul 2025 12:44:07 +0200
Subject: [PATCH 4/6] Rewrite reactor to source and register it properly to
node object
---
src/nodeClass.js | 25 ++++++++++++++-----------
src/specificClass.js | 3 ++-
2 files changed, 16 insertions(+), 12 deletions(-)
diff --git a/src/nodeClass.js b/src/nodeClass.js
index 94b3965..989b806 100644
--- a/src/nodeClass.js
+++ b/src/nodeClass.js
@@ -14,12 +14,13 @@ class nodeClass {
this.node = nodeInstance;
this.RED = RED;
this.name = nameOfNode;
+ this.source = null;
this._loadConfig(uiConfig)
- this._registerChild();
this._setupClass();
this._attachInputHandler();
+ this._registerChild();
}
/**
@@ -32,27 +33,28 @@ class nodeClass {
switch (msg.topic) {
case "clock":
toggleUpdate = true;
- this.reactor.updateState(msg.timestamp);
- send([this.reactor.getEffluent, null, null]);
+ this.source.updateState(msg.timestamp);
+ send([this.source.getEffluent, null, null]);
send([msg, null, null])
break;
case "Fluent":
- this.reactor.setInfluent = msg;
+ this.source.setInfluent = msg;
break;
case "OTR":
- this.reactor.setOTR = msg;
+ this.source.setOTR = msg;
break;
case "Temperature":
- this.reactor.setTemperature = msg;
+ this.source.setTemperature = msg;
break;
case "Dispersion":
- this.reactor.setDispersion = msg;
+ this.source.setDispersion = msg;
break;
case 'registerChild':
// Register this node as a child of the parent node
const childId = msg.payload;
const childObj = this.RED.nodes.getNode(childId);
- this.reactor.childRegistrationUtils.registerChild(childObj.source, msg.positionVsParent);
+ console.log(childObj.source);
+ this.source.childRegistrationUtils.registerChild(childObj.source, msg.positionVsParent);
break;
default:
console.log("Unknown topic: " + msg.topic);
@@ -76,7 +78,7 @@ class nodeClass {
unit: null
},
functionality: {
- softwareType: null // should be set in config manager
+ softwareType: "reactor" // should be set in config manager
},
reactor_type: uiConfig.reactor_type,
volume: parseFloat(uiConfig.volume),
@@ -112,7 +114,7 @@ class nodeClass {
this.node.send([
null,
null,
- { topic: 'registerChild', payload: this.node.id, positionVsParent: this.config?.functionality?.positionVsParent || 'atEquipment' },
+ { topic: 'registerChild', payload: this.node.id, positionVsParent: this.config?.functionality?.positionVsParent || 'atEquipment' }
]);
}, 100);
}
@@ -134,7 +136,8 @@ class nodeClass {
console.warn("Unknown reactor type: " + uiConfig.reactor_type);
}
- this.reactor = new_reactor; // protect from reassignment
+ this.source = new_reactor; // protect from reassignment
+ this.node.source = this.source;
}
}
diff --git a/src/specificClass.js b/src/specificClass.js
index 474ebf7..99ed988 100644
--- a/src/specificClass.js
+++ b/src/specificClass.js
@@ -20,6 +20,7 @@ class Reactor {
* @param {object} config - Configuration object containing reactor parameters.
*/
constructor(config) {
+ this.config = config;
// EVOLV stuff
this.logger = new logger(undefined, undefined, config.general.name);
this.emitter = new EventEmitter();
@@ -122,7 +123,7 @@ class Reactor {
n += 1;
}
this.currentTime += n_iter * this.timeStep * day2ms / this.speedUpFactor;
- this.emitter.emit("stateChanges", this.getEffluent);
+ this.emitter.emit("stateChange", this.getEffluent);
}
}
}
From 57aafe3e0b8ae4ebaf3784dc24497b21be791592 Mon Sep 17 00:00:00 2001
From: "p.vanderwilt"
Date: Mon, 21 Jul 2025 17:28:09 +0200
Subject: [PATCH 5/6] Minor optimisations
---
src/nodeClass.js | 6 ++----
src/specificClass.js | 7 ++++++-
2 files changed, 8 insertions(+), 5 deletions(-)
diff --git a/src/nodeClass.js b/src/nodeClass.js
index 989b806..97ec545 100644
--- a/src/nodeClass.js
+++ b/src/nodeClass.js
@@ -28,14 +28,12 @@ class nodeClass {
*/
_attachInputHandler() {
this.node.on('input', (msg, send, done) => {
- let toggleUpdate = false;
switch (msg.topic) {
case "clock":
- toggleUpdate = true;
this.source.updateState(msg.timestamp);
send([this.source.getEffluent, null, null]);
- send([msg, null, null])
+ send([msg, null, null]);
break;
case "Fluent":
this.source.setInfluent = msg;
@@ -53,7 +51,6 @@ class nodeClass {
// Register this node as a child of the parent node
const childId = msg.payload;
const childObj = this.RED.nodes.getNode(childId);
- console.log(childObj.source);
this.source.childRegistrationUtils.registerChild(childObj.source, msg.positionVsParent);
break;
default:
@@ -139,6 +136,7 @@ class nodeClass {
this.source = new_reactor; // protect from reassignment
this.node.source = this.source;
}
+
}
module.exports = nodeClass;
\ No newline at end of file
diff --git a/src/specificClass.js b/src/specificClass.js
index 99ed988..a240275 100644
--- a/src/specificClass.js
+++ b/src/specificClass.js
@@ -25,6 +25,7 @@ class Reactor {
this.logger = new logger(undefined, undefined, config.general.name);
this.emitter = new EventEmitter();
this.measurements = new MeasurementContainer();
+ this.upstreamReactor = null;
this.childRegistrationUtils = new childRegistrationUtils(this); // Child registration utility
this.asm = new ASM3();
@@ -115,6 +116,10 @@ class Reactor {
updateState(newTime) { // expect update with timestamp
const day2ms = 1000 * 60 * 60 * 24;
+ if (this.upstreamReactor) {
+ this.setInfluent = this.upstreamReactor.getEffluent;
+ }
+
let n_iter = Math.floor(this.speedUpFactor * (newTime-this.currentTime) / (this.timeStep*day2ms));
if (n_iter) {
let n = 0;
@@ -123,7 +128,7 @@ class Reactor {
n += 1;
}
this.currentTime += n_iter * this.timeStep * day2ms / this.speedUpFactor;
- this.emitter.emit("stateChange", this.getEffluent);
+ this.emitter.emit("stateChange", newTime);
}
}
}
From f81161b2d571b881fe291ec15401528796e1e9ab Mon Sep 17 00:00:00 2001
From: "p.vanderwilt"
Date: Tue, 22 Jul 2025 12:20:29 +0200
Subject: [PATCH 6/6] Process output using tick function rather than clock
message
---
src/nodeClass.js | 19 ++++++++++++++++++-
1 file changed, 18 insertions(+), 1 deletion(-)
diff --git a/src/nodeClass.js b/src/nodeClass.js
index 97ec545..637ec5b 100644
--- a/src/nodeClass.js
+++ b/src/nodeClass.js
@@ -21,6 +21,8 @@ class nodeClass {
this._attachInputHandler();
this._registerChild();
+ this._startTickLoop();
+ this._attachCloseHandler();
}
/**
@@ -32,7 +34,6 @@ class nodeClass {
switch (msg.topic) {
case "clock":
this.source.updateState(msg.timestamp);
- send([this.source.getEffluent, null, null]);
send([msg, null, null]);
break;
case "Fluent":
@@ -137,6 +138,22 @@ class nodeClass {
this.node.source = this.source;
}
+ _startTickLoop() {
+ setTimeout(() => {
+ this._tickInterval = setInterval(() => this._tick(), 1000);
+ }, 1000);
+ }
+
+ _tick(){
+ this.node.send([this.source.getEffluent, null, null]);
+ }
+
+ _attachCloseHandler() {
+ this.node.on('close', (done) => {
+ clearInterval(this._tickInterval);
+ done();
+ });
+ }
}
module.exports = nodeClass;
\ No newline at end of file