Merge pull request 'Implemented parent-child relations for reactor node' (#15) from multi-output into main
Reviewed-on: p.vanderwilt/asm3#15
This commit is contained in:
@@ -24,6 +24,8 @@ module.exports = function(RED) {
|
||||
|
||||
send([msg_F1, msg_F2]);
|
||||
break;
|
||||
case "clock":
|
||||
break;
|
||||
default:
|
||||
console.log("Unknown topic: " + msg.topic);
|
||||
}
|
||||
|
||||
@@ -41,6 +41,8 @@ module.exports = function(RED) {
|
||||
|
||||
send([msg_F1, msg_F2]);
|
||||
break;
|
||||
case "clock":
|
||||
break;
|
||||
default:
|
||||
console.log("Unknown topic: " + msg.topic);
|
||||
}
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -14,12 +14,15 @@ class nodeClass {
|
||||
this.node = nodeInstance;
|
||||
this.RED = RED;
|
||||
this.name = nameOfNode;
|
||||
this.source = null;
|
||||
|
||||
this._loadConfig(uiConfig)
|
||||
|
||||
this._setupClass();
|
||||
|
||||
this._attachInputHandler();
|
||||
this._registerChild();
|
||||
this._startTickLoop();
|
||||
this._attachCloseHandler();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -27,42 +30,34 @@ 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([msg, null, null]);
|
||||
break;
|
||||
case "Fluent":
|
||||
this.reactor.setInfluent = msg;
|
||||
if (msg.payload.inlet == 0) {
|
||||
toggleUpdate = true;
|
||||
}
|
||||
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);
|
||||
this.source.childRegistrationUtils.registerChild(childObj.source, msg.positionVsParent);
|
||||
break;
|
||||
default:
|
||||
console.log("Unknown topic: " + msg.topic);
|
||||
}
|
||||
|
||||
if (toggleUpdate) {
|
||||
this.reactor.updateState(msg.timestamp);
|
||||
send(this.reactor.getEffluent);
|
||||
}
|
||||
|
||||
if (done) {
|
||||
done();
|
||||
}
|
||||
@@ -75,6 +70,14 @@ class nodeClass {
|
||||
*/
|
||||
_loadConfig(uiConfig) {
|
||||
this.config = {
|
||||
general: {
|
||||
name: uiConfig.name || this.name,
|
||||
id: this.node.id,
|
||||
unit: null
|
||||
},
|
||||
functionality: {
|
||||
softwareType: "reactor" // should be set in config manager
|
||||
},
|
||||
reactor_type: uiConfig.reactor_type,
|
||||
volume: parseFloat(uiConfig.volume),
|
||||
length: parseFloat(uiConfig.length),
|
||||
@@ -100,6 +103,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
|
||||
*/
|
||||
@@ -117,7 +134,25 @@ 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;
|
||||
}
|
||||
|
||||
_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();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
@@ -19,9 +20,12 @@ class Reactor {
|
||||
* @param {object} config - Configuration object containing reactor parameters.
|
||||
*/
|
||||
constructor(config) {
|
||||
this.config = 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.upstreamReactor = null;
|
||||
this.childRegistrationUtils = new childRegistrationUtils(this); // Child registration utility
|
||||
|
||||
this.asm = new ASM3();
|
||||
@@ -70,6 +74,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].
|
||||
@@ -101,14 +116,19 @@ 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;
|
||||
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("stateChange", newTime);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -123,14 +143,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 +203,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)
|
||||
|
||||
Reference in New Issue
Block a user