diff --git a/additional_nodes/recirculation-pump.html b/additional_nodes/recirculation-pump.html new file mode 100644 index 0000000..52f94a6 --- /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..489df01 --- /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); + const inlet_F2 = 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 = {...msg}; + msg_F2.payload.F = F2_corr; + msg_F2.payload.inlet = inlet_F2; + + 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/additional_nodes/settling-basin.html b/additional_nodes/settling-basin.html new file mode 100644 index 0000000..da614bc --- /dev/null +++ b/additional_nodes/settling-basin.html @@ -0,0 +1,57 @@ + + + + + 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/advanced-reactor.html b/advanced-reactor.html index a18e6a3..ff12949 100644 --- a/advanced-reactor.html +++ b/advanced-reactor.html @@ -1,10 +1,12 @@ @@ -55,6 +69,15 @@ +
+ + +
+

Internal mass transfer calculation (optional)

+
+ + +

Dissolved components

diff --git a/advanced-reactor.js b/advanced-reactor.js index 0a0bbcd..edfad14 100644 --- a/advanced-reactor.js +++ b/advanced-reactor.js @@ -8,7 +8,9 @@ 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.kla), [ parseFloat(config.S_O_init), parseFloat(config.S_I_init), @@ -27,15 +29,17 @@ module.exports = function(RED) { ); node.on('input', function(msg, send, done) { + let toggleUpdate = false; + switch (msg.topic) { case "clock": - reactor.updateState(msg); + toggleUpdate = true; break; - case "Influent": - reactor.setInflux = msg; - break; - case "Effluent": - reactor.setInflux = msg; + case "Fluent": + reactor.setInfluent = msg; + if (msg.payload.inlet == 0) { + toggleUpdate = true; + } break; case "OTR": reactor.setOTR = msg; @@ -44,7 +48,10 @@ module.exports = function(RED) { console.log("Unknown topic: " + msg.topic); } - send(reactor.getEffluent); + if (toggleUpdate) { + reactor.updateState(msg.timestamp); + send(reactor.getEffluent); + } if (done) { done(); diff --git a/dependencies/reactor_class.js b/dependencies/reactor_class.js index 29a3111..dcc5cfd 100644 --- a/dependencies/reactor_class.js +++ b/dependencies/reactor_class.js @@ -3,36 +3,44 @@ const math = require('mathjs') class Reactor_CSTR { - constructor(volume, initial_state) { + constructor(volume, n_inlets, kla, 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 influents this.OTR = 0.0; // oxygen transfer rate [g O2 d-1] + + this.kla = kla; // if NaN, use external OTR [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}; + } + + calcOTR(S_O, T=20.0) { // caculate the OTR using basic correlation, default to temperature: 20 C + let S_O_sat = 14.652 - 4.1022e-1*T + 7.9910e-3*T*T + 7.7774e-5*T*T*T; + return this.kla * (S_O_sat - S_O); } // expect update with timestamp - updateState(input) { - let newTime = input.payload; + updateState(timestamp) { + let newTime = timestamp; const day2ms = 1000 * 60 * 60 * 24; @@ -50,12 +58,12 @@ 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 T_O = Array(13).fill(0.0); - T_O[0] = this.OTR; + const dC_in = math.multiply(math.divide([this.Fs], this.Vl), this.Cs_in)[0]; + const dC_out = math.multiply(math.sum(this.Fs)/this.Vl, this.state); + const t_O = Array(13).fill(0.0); + t_O[0] = isNaN(this.kla) ? this.OTR : this.calcOTR(this.state[0]); // calculate OTR if kla is not NaN, otherwise use externaly calculated OTR - const dC_total = math.multiply(math.add(dC_in, dC_out, r, T_O), time_step); + const dC_total = math.multiply(math.add(dC_in, dC_out, r, t_O), time_step); this.state = math.add(this.state, dC_total); return this.state; diff --git a/package.json b/package.json index b4a086b..e60fa97 100644 --- a/package.json +++ b/package.json @@ -21,7 +21,9 @@ }, "node-red": { "nodes": { - "advanced-reactor": "advanced-reactor.js" + "advanced-reactor": "advanced-reactor.js", + "recirculation-pump": "additional_nodes/recirculation-pump.js", + "settling-basin": "additional_nodes/settling-basin.js" } }, "dependencies": {