From d0f8ada144b1bd09f2df450689519ef1b8fb933f Mon Sep 17 00:00:00 2001 From: "p.vanderwilt" Date: Mon, 16 Jun 2025 14:01:19 +0200 Subject: [PATCH 1/6] Add number of inlets input handling to advanced-reactor node --- advanced-reactor.html | 13 +++++++++++++ advanced-reactor.js | 13 ++++++------- dependencies/reactor_class.js | 25 +++++++++++++------------ 3 files changed, 32 insertions(+), 19 deletions(-) diff --git a/advanced-reactor.html b/advanced-reactor.html index a18e6a3..a1a7ea7 100644 --- a/advanced-reactor.html +++ b/advanced-reactor.html @@ -5,6 +5,7 @@ defaults: { name: { value: "" }, volume: { value: 0., required: true}, + n_inlets: { value: 1, required: true}, S_O_init: { value: 0., required: true }, S_I_init: { value: 30., required: true }, S_S_init: { value: 100., required: true }, @@ -31,6 +32,10 @@ type:"num", types:["num"] }); + $("#node-input-n_inlets").typedInput({ + type:"num", + types:["num"] + }); $(".concentrations").typedInput({ type:"num", types:["num"] @@ -41,6 +46,10 @@ if (isNaN(volume) || volume <= 0) { RED.notify("Fluid volume not set correctly", {type: "error"}); } + let n_inlets = parseInt($("#node-input-n_inlets").typedInput("value")); + if (isNaN(n_inlets) || n_inlets < 1) { + RED.notify("Number of inlets not set correctly", {type: "error"}); + } } }); @@ -55,6 +64,10 @@ +
+ + +

Dissolved components

diff --git a/advanced-reactor.js b/advanced-reactor.js index 0a0bbcd..15369ec 100644 --- a/advanced-reactor.js +++ b/advanced-reactor.js @@ -8,7 +8,8 @@ module.exports = function(RED) { const Reactor = require('./dependencies/reactor_class'); const reactor = new Reactor( - config.volume, + parseFloat(config.volume), + parseInt(config.n_inlets), [ parseFloat(config.S_O_init), parseFloat(config.S_I_init), @@ -29,13 +30,11 @@ module.exports = function(RED) { node.on('input', function(msg, send, done) { switch (msg.topic) { case "clock": - reactor.updateState(msg); + reactor.updateState(msg.timestamp); break; - case "Influent": - reactor.setInflux = msg; - break; - case "Effluent": - reactor.setInflux = msg; + case "Fluent": + reactor.setInfluent = msg; + reactor.updateState(msg.timestamp); break; case "OTR": reactor.setOTR = msg; diff --git a/dependencies/reactor_class.js b/dependencies/reactor_class.js index 29a3111..fd459d2 100644 --- a/dependencies/reactor_class.js +++ b/dependencies/reactor_class.js @@ -3,36 +3,37 @@ const math = require('mathjs') class Reactor_CSTR { - constructor(volume, initial_state) { + constructor(volume, n_inlets, initial_state) { this.state = initial_state; console.log(this.state); this.asm = new ASM3(); this.Vl = volume; // fluid volume reactor [m3] - this.F = 0.0; // fluid debit [m3 d-1] - this.C_in = Array(13).fill(0.0); // composition influent + this.Fs = Array(n_inlets).fill(0.0); // fluid debits per inlet [m3 d-1] + this.Cs_in = Array.from(Array(n_inlets), () => new Array(13)).fill(0.0); // composition influent this.OTR = 0.0; // oxygen transfer rate [g O2 d-1] this.currentTime = Date.now(); // milliseconds since epoch [ms] this.timeStep = 1/(24*60*15) // time step [d] } - set setInflux(input) { // setter for C_in (WIP) - this.F = input.payload.F; - this.C_in = input.payload.C_in; + set setInfluent(input) { // setter for C_in (WIP) + let index_in = input.payload.inlet; + this.Fs[index_in] = input.payload.F; + this.Cs_in[index_in] = input.payload.C; } set setOTR(input) { // setter for OTR (WIP) [g O2 d-1] this.OTR = input.payload; } - get getEffluent() { - return {topic: "Effluent", payload: {F: this.F, C_in:this.state}}; + 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}; } // expect update with timestamp - updateState(input) { - let newTime = input.payload; + updateState(timestamp) { + let newTime = timestamp; const day2ms = 1000 * 60 * 60 * 24; @@ -50,8 +51,8 @@ class Reactor_CSTR { tick_fe(time_step) { // tick reactor state using forward Euler method const r = this.asm.compute_dC(this.state); - const dC_in = math.multiply(this.C_in, this.F/this.Vl); - const dC_out = math.multiply(this.state, this.F/this.Vl); + const dC_in = math.multiply(this.Cs_in, this.Fs/this.Vl)[0]; + const dC_out = math.multiply(this.state, math.sum(this.Fs)/this.Vl); const T_O = Array(13).fill(0.0); T_O[0] = this.OTR; From 5281696a21da0ec279d25746dc8e7290b0ab3aaa Mon Sep 17 00:00:00 2001 From: "p.vanderwilt" Date: Mon, 16 Jun 2025 16:53:07 +0200 Subject: [PATCH 2/6] Add recirculation pump node with input handling and flow management --- additional_nodes/recirculation-pump.html | 57 ++++++++++++++++++++++++ additional_nodes/recirculation-pump.js | 38 ++++++++++++++++ advanced-reactor.html | 4 +- advanced-reactor.js | 4 +- dependencies/reactor_class.js | 6 +-- package.json | 3 +- 6 files changed, 105 insertions(+), 7 deletions(-) create mode 100644 additional_nodes/recirculation-pump.html create mode 100644 additional_nodes/recirculation-pump.js diff --git a/additional_nodes/recirculation-pump.html b/additional_nodes/recirculation-pump.html new file mode 100644 index 0000000..2027a17 --- /dev/null +++ b/additional_nodes/recirculation-pump.html @@ -0,0 +1,57 @@ + + + + + diff --git a/additional_nodes/recirculation-pump.js b/additional_nodes/recirculation-pump.js new file mode 100644 index 0000000..ddf0602 --- /dev/null +++ b/additional_nodes/recirculation-pump.js @@ -0,0 +1,38 @@ +module.exports = function(RED) { + function recirculation(config) { + RED.nodes.createNode(this, config); + var node = this; + + let name = config.name; + let F2 = parseFloat(config.F2); + let inlet = parseInt(config.inlet); + + node.on('input', function(msg, send, done) { + switch (msg.topic) { + case "Fluent": + // conserve volume flow debit + let F1 = msg.payload.F; + let F_diff = Math.max(F1 - F2, 0); + let F2_corr = F1 < F2 ? F1 : F2; + + let msg_F1 = structuredClone(msg); + msg_F1.payload.F = F_diff; + + let msg_F2 = structuredClone(msg); + msg_F2.payload.F = F2_corr; + msg_F2.payload.inlet = inlet; + + send([msg_F1, msg_F2]); + break; + default: + console.log("Unknown topic: " + msg.topic); + } + + if (done) { + done(); + } + }); + + } + RED.nodes.registerType("recirculation-pump", recirculation); +}; diff --git a/advanced-reactor.html b/advanced-reactor.html index a1a7ea7..6db1abe 100644 --- a/advanced-reactor.html +++ b/advanced-reactor.html @@ -1,7 +1,7 @@ + + + + diff --git a/additional_nodes/settling-basin.js b/additional_nodes/settling-basin.js new file mode 100644 index 0000000..f012b81 --- /dev/null +++ b/additional_nodes/settling-basin.js @@ -0,0 +1,53 @@ +module.exports = function(RED) { + function settler(config) { + RED.nodes.createNode(this, config); + var node = this; + + let name = config.name; + let SVI = parseFloat(config.SVI); + const inlet_sludge = parseInt(config.inlet); + + node.on('input', function(msg, send, done) { + switch (msg.topic) { + case "Fluent": + // conserve volume flow debit + let F_in = msg.payload.F; + let C_in = msg.payload.C; + let X_in = (C_in[7] + C_in[8] + C_in[9] + C_in[10] + C_in[11] + C_in[12]); + let F2 = (F_in * X_in) / (SVI*1000*1000); + + let msg_F1 = structuredClone(msg); + msg_F1.payload.F = F_in - F2; + msg_F1.payload.C[7] = 0; + msg_F1.payload.C[8] = 0; + msg_F1.payload.C[9] = 0; + msg_F1.payload.C[10] = 0; + msg_F1.payload.C[11] = 0; + msg_F1.payload.C[12] = 0; + + let msg_F2 = {...msg}; + msg_F2.payload.F = F2; + if (F2 != 0) { + msg_F2.payload.C[7] = F_in * C_in[7] / F2; + msg_F2.payload.C[8] = F_in * C_in[8] / F2; + msg_F2.payload.C[9] = F_in * C_in[9] / F2; + msg_F2.payload.C[10] = F_in * C_in[10] / F2; + msg_F2.payload.C[11] = F_in * C_in[11] / F2; + msg_F2.payload.C[12] = F_in * C_in[12] / F2; + } + msg_F2.payload.inlet = inlet_sludge; + + send([msg_F1, msg_F2]); + break; + default: + console.log("Unknown topic: " + msg.topic); + } + + if (done) { + done(); + } + }); + + } + RED.nodes.registerType("settling-basin", settler); +}; diff --git a/package.json b/package.json index 59c69a7..e60fa97 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,8 @@ "node-red": { "nodes": { "advanced-reactor": "advanced-reactor.js", - "recirculation-pump": "additional_nodes/recirculation-pump.js" + "recirculation-pump": "additional_nodes/recirculation-pump.js", + "settling-basin": "additional_nodes/settling-basin.js" } }, "dependencies": {