const { Reactor_CSTR, Reactor_PFR } = require('./specificClass.js'); class nodeClass { /** * Node-RED node class for advanced-reactor. * @param {object} uiConfig - Node-RED node configuration * @param {object} RED - Node-RED runtime API * @param {object} nodeInstance - Node-RED node instance * @param {string} nameOfNode - Name of the node */ constructor(uiConfig, RED, nodeInstance, nameOfNode) { // Preserve RED reference for HTTP endpoints if needed this.node = nodeInstance; this.RED = RED; this.name = nameOfNode; this._loadConfig(uiConfig) this._registerChild(); this._setupClass(); this._attachInputHandler(); } /** * Handle node-red input messages */ _attachInputHandler() { this.node.on('input', (msg, send, done) => { let toggleUpdate = false; switch (msg.topic) { case "clock": toggleUpdate = true; this.reactor.updateState(msg.timestamp); send([this.reactor.getEffluent, null, null]); send([msg, null, null]) break; case "Fluent": this.reactor.setInfluent = msg; break; case "OTR": this.reactor.setOTR = msg; break; case "Temperature": this.reactor.setTemperature = msg; break; case "Dispersion": this.reactor.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); break; default: console.log("Unknown topic: " + msg.topic); } if (done) { done(); } }); } /** * Parse node configuration * @param {object} uiConfig Config set in UI in node-red */ _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), resolution_L: parseInt(uiConfig.resolution_L), alpha: parseFloat(uiConfig.alpha), n_inlets: parseInt(uiConfig.n_inlets), kla: parseFloat(uiConfig.kla), initialState: [ parseFloat(uiConfig.S_O_init), parseFloat(uiConfig.S_I_init), parseFloat(uiConfig.S_S_init), parseFloat(uiConfig.S_NH_init), parseFloat(uiConfig.S_N2_init), parseFloat(uiConfig.S_NO_init), parseFloat(uiConfig.S_HCO_init), parseFloat(uiConfig.X_I_init), parseFloat(uiConfig.X_S_init), parseFloat(uiConfig.X_H_init), parseFloat(uiConfig.X_STO_init), parseFloat(uiConfig.X_A_init), parseFloat(uiConfig.X_TS_init) ] } } /** * 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 */ _setupClass() { let new_reactor; switch (this.config.reactor_type) { case "CSTR": new_reactor = new Reactor_CSTR(this.config); break; case "PFR": new_reactor = new Reactor_PFR(this.config); break; default: console.warn("Unknown reactor type: " + uiConfig.reactor_type); } this.reactor = new_reactor; // protect from reassignment } } module.exports = nodeClass;