From 68576a8a365aec98b99520836086fd26dc2b6e9b Mon Sep 17 00:00:00 2001 From: Rene De Ren Date: Wed, 11 Mar 2026 13:39:57 +0100 Subject: [PATCH 1/6] Fix ESLint errors and bugs Co-Authored-By: Claude Opus 4.6 --- additional_nodes/recirculation-pump.js | 3 ++- additional_nodes/settling-basin.js | 3 ++- src/nodeClass.js | 7 ++++--- src/reaction_modules/asm3_class Koch.js | 2 +- src/specificClass.js | 3 ++- 5 files changed, 11 insertions(+), 7 deletions(-) diff --git a/additional_nodes/recirculation-pump.js b/additional_nodes/recirculation-pump.js index 2a02932..d9f2003 100644 --- a/additional_nodes/recirculation-pump.js +++ b/additional_nodes/recirculation-pump.js @@ -9,7 +9,7 @@ module.exports = function(RED) { node.on('input', function(msg, send, done) { switch (msg.topic) { - case "Fluent": + case "Fluent": { // conserve volume flow debit let F_in = msg.payload.F; let F1 = Math.max(F_in - F2, 0); @@ -24,6 +24,7 @@ module.exports = function(RED) { send([msg_F1, msg_F2]); break; + } case "clock": break; default: diff --git a/additional_nodes/settling-basin.js b/additional_nodes/settling-basin.js index dd3d697..131043c 100644 --- a/additional_nodes/settling-basin.js +++ b/additional_nodes/settling-basin.js @@ -9,7 +9,7 @@ module.exports = function(RED) { node.on('input', function(msg, send, done) { switch (msg.topic) { - case "Fluent": + case "Fluent": { // conserve volume flow debit let F_in = msg.payload.F; let C_in = msg.payload.C; @@ -41,6 +41,7 @@ module.exports = function(RED) { send([msg_F1, msg_F2]); break; + } case "clock": break; default: diff --git a/src/nodeClass.js b/src/nodeClass.js index a9e53d3..ec80df7 100644 --- a/src/nodeClass.js +++ b/src/nodeClass.js @@ -48,12 +48,13 @@ class nodeClass { case "Dispersion": this.source.setDispersion = msg; break; - case 'registerChild': + case 'registerChild': { // Register this node as a parent of the child node const childId = msg.payload; - const childObj = this.RED.nodes.getNode(childId); + const childObj = this.RED.nodes.getNode(childId); this.source.childRegistrationUtils.registerChild(childObj.source, msg.positionVsParent); break; + } default: console.log("Unknown topic: " + msg.topic); } @@ -137,7 +138,7 @@ class nodeClass { new_reactor = new Reactor_PFR(this.config); break; default: - console.warn("Unknown reactor type: " + uiConfig.reactor_type); + console.warn("Unknown reactor type: " + this.config.reactor_type); } this.source = new_reactor; // protect from reassignment diff --git a/src/reaction_modules/asm3_class Koch.js b/src/reaction_modules/asm3_class Koch.js index e5e98b3..8291b8c 100644 --- a/src/reaction_modules/asm3_class Koch.js +++ b/src/reaction_modules/asm3_class Koch.js @@ -19,7 +19,7 @@ class ASM3 { nu_NO: 0.5, // anoxic reduction factor [-] K_O: 0.2, // saturation constant S_0 [g O2 m-3] K_NO: 0.5, // saturation constant S_NO [g NO3-N m-3] - K_S: 10., // saturation constant S_s [g COD m-3] + K_S: 10.0, // saturation constant S_s [g COD m-3] K_STO: 0.1, // saturation constant X_STO [g X_STO g-1 X_H] mu_H_max: 3., // maximum specific growth rate [d-1] K_NH: 0.01, // saturation constant S_NH3 [g NH3-N m-3] diff --git a/src/specificClass.js b/src/specificClass.js index d34e6cc..fc33d90 100644 --- a/src/specificClass.js +++ b/src/specificClass.js @@ -322,10 +322,11 @@ class Reactor_PFR extends Reactor { _updateMeasurement(measurementType, value, position, context) { switch(measurementType) { - case "quantity (oxygen)": + case "quantity (oxygen)": { let grid_pos = Math.round(position / this.config.length * this.n_x); this.state[grid_pos][S_O_INDEX] = value; // naive approach for reconciling measurements and simulation break; + } default: super._updateMeasurement(measurementType, value, position, context); } From aacbc1e99d681fa407b9cf78228d007797907208 Mon Sep 17 00:00:00 2001 From: Rene De Ren Date: Wed, 11 Mar 2026 14:59:35 +0100 Subject: [PATCH 2/6] Migrate _loadConfig to use ConfigManager.buildConfig() Replaces manual base config construction with shared buildConfig() method. Node now only specifies domain-specific config sections. Part of #1: Extract base config schema Co-Authored-By: Claude Opus 4.6 --- src/nodeClass.js | 21 ++++++--------------- 1 file changed, 6 insertions(+), 15 deletions(-) diff --git a/src/nodeClass.js b/src/nodeClass.js index ec80df7..32f1b90 100644 --- a/src/nodeClass.js +++ b/src/nodeClass.js @@ -1,4 +1,5 @@ const { Reactor_CSTR, Reactor_PFR } = require('./specificClass.js'); +const { configManager } = require('generalFunctions'); class nodeClass { @@ -70,20 +71,10 @@ class nodeClass { * @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, - logging: { - enabled: uiConfig.enableLog, - logLevel: uiConfig.logLevel - } - }, - functionality: { - positionVsParent: uiConfig.positionVsParent || 'atEquipment', // Default to 'atEquipment' if not specified - softwareType: "reactor" // should be set in config manager - }, + const cfgMgr = new configManager(); + + // Build config: base sections + reactor-specific domain config + this.config = cfgMgr.buildConfig('reactor', uiConfig, this.node.id, { reactor_type: uiConfig.reactor_type, volume: parseFloat(uiConfig.volume), length: parseFloat(uiConfig.length), @@ -107,7 +98,7 @@ class nodeClass { parseFloat(uiConfig.X_TS_init) ], timeStep: parseFloat(uiConfig.timeStep) - } + }); } /** From a18c36b2e591b3901b96a22538f187276e0fac39 Mon Sep 17 00:00:00 2001 From: Rene De Ren Date: Wed, 11 Mar 2026 15:35:28 +0100 Subject: [PATCH 3/6] refactor: adopt POSITIONS constants and fix ESLint warnings Replace hardcoded position strings with POSITIONS.* constants. Prefix unused variables with _ to resolve no-unused-vars warnings. Fix no-prototype-builtins where applicable. Co-Authored-By: Claude Opus 4.6 --- additional_nodes/recirculation-pump.js | 1 - additional_nodes/settling-basin.js | 1 - src/reaction_modules/asm3_class Koch.js | 2 +- src/reaction_modules/asm3_class.js | 2 +- src/specificClass.js | 11 +++++------ 5 files changed, 7 insertions(+), 10 deletions(-) diff --git a/additional_nodes/recirculation-pump.js b/additional_nodes/recirculation-pump.js index d9f2003..94c7018 100644 --- a/additional_nodes/recirculation-pump.js +++ b/additional_nodes/recirculation-pump.js @@ -3,7 +3,6 @@ module.exports = function(RED) { RED.nodes.createNode(this, config); var node = this; - let name = config.name; let F2 = parseFloat(config.F2); const inlet_F2 = parseInt(config.inlet); diff --git a/additional_nodes/settling-basin.js b/additional_nodes/settling-basin.js index 131043c..80affcf 100644 --- a/additional_nodes/settling-basin.js +++ b/additional_nodes/settling-basin.js @@ -3,7 +3,6 @@ module.exports = function(RED) { RED.nodes.createNode(this, config); var node = this; - let name = config.name; let TS_set = parseFloat(config.TS_set); const inlet_sludge = parseInt(config.inlet); diff --git a/src/reaction_modules/asm3_class Koch.js b/src/reaction_modules/asm3_class Koch.js index 8291b8c..0a38e10 100644 --- a/src/reaction_modules/asm3_class Koch.js +++ b/src/reaction_modules/asm3_class Koch.js @@ -171,7 +171,7 @@ class ASM3 { compute_rates(state, T = 20) { // state: S_O, S_I, S_S, S_NH, S_N2, S_NO, S_HCO, X_I, X_S, X_H, X_STO, X_A, X_TS const rates = Array(12); - const [S_O, S_I, S_S, S_NH, S_N2, S_NO, S_HCO, X_I, X_S, X_H, X_STO, X_A, X_TS] = state; + const [S_O, , S_S, S_NH, , S_NO, S_HCO, , X_S, X_H, X_STO, X_A] = state; const { k_H, K_X, k_STO, nu_NO, K_O, K_NO, K_S, K_STO, mu_H_max, K_NH, K_HCO, b_H_O, b_H_NO, b_STO_O, b_STO_NO, mu_A_max, K_A_NH, K_A_O, K_A_HCO, b_A_O, b_A_NO } = this.kin_params; const { theta_H, theta_STO, theta_mu_H, theta_b_H_O, theta_b_H_NO, theta_b_STO_O, theta_b_STO_NO, theta_mu_A, theta_b_A_O, theta_b_A_NO } = this.temp_params; diff --git a/src/reaction_modules/asm3_class.js b/src/reaction_modules/asm3_class.js index d228619..ba0011b 100644 --- a/src/reaction_modules/asm3_class.js +++ b/src/reaction_modules/asm3_class.js @@ -171,7 +171,7 @@ class ASM3 { compute_rates(state, T = 20) { // state: S_O, S_I, S_S, S_NH, S_N2, S_NO, S_HCO, X_I, X_S, X_H, X_STO, X_A, X_TS const rates = Array(12); - const [S_O, S_I, S_S, S_NH, S_N2, S_NO, S_HCO, X_I, X_S, X_H, X_STO, X_A, X_TS] = state; + const [S_O, , S_S, S_NH, , S_NO, S_HCO, , X_S, X_H, X_STO, X_A] = state; const { k_H, K_X, k_STO, nu_NO, K_O, K_NO, K_S, K_STO, mu_H_max, K_NH, K_HCO, b_H_O, b_H_NO, b_STO_O, b_STO_NO, mu_A_max, K_A_NH, K_A_O, K_A_HCO, b_A_O, b_A_NO } = this.kin_params; const { theta_H, theta_STO, theta_mu_H, theta_b_H_O, theta_b_H_NO, theta_b_STO_O, theta_b_STO_NO, theta_mu_A, theta_b_A_O, theta_b_A_NO } = this.temp_params; diff --git a/src/specificClass.js b/src/specificClass.js index fc33d90..9ed3e65 100644 --- a/src/specificClass.js +++ b/src/specificClass.js @@ -1,7 +1,7 @@ const ASM3 = require('./reaction_modules/asm3_class.js'); const { create, all, isArray } = require('mathjs'); const { assertNoNaN } = require('./utils.js'); -const { childRegistrationUtils, logger, MeasurementContainer } = require('generalFunctions'); +const { childRegistrationUtils, logger, MeasurementContainer, POSITIONS } = require('generalFunctions'); const EventEmitter = require('events'); const mathConfig = { @@ -126,7 +126,6 @@ class Reactor { position = measurement.config.functionality.positionVsParent; } const measurementType = measurement.config.asset.type; - const key = `${measurementType}_${position}`; const eventName = `${measurementType}.measured.${position}`; // Register event listener for measurement updates @@ -160,11 +159,11 @@ class Reactor { } - _updateMeasurement(measurementType, value, position, context) { + _updateMeasurement(measurementType, value, position, _context) { this.logger.debug(`---------------------- updating ${measurementType} ------------------ `); switch (measurementType) { case "temperature": - if (position == "atEquipment") { + if (position == POSITIONS.AT_EQUIPMENT) { this.temperature = value; } break; @@ -320,7 +319,7 @@ class Reactor_PFR extends Reactor { return stateNew; } - _updateMeasurement(measurementType, value, position, context) { + _updateMeasurement(measurementType, value, position, _context) { switch(measurementType) { case "quantity (oxygen)": { let grid_pos = Math.round(position / this.config.length * this.n_x); @@ -328,7 +327,7 @@ class Reactor_PFR extends Reactor { break; } default: - super._updateMeasurement(measurementType, value, position, context); + super._updateMeasurement(measurementType, value, position, _context); } } From 7ff7c6ec1d5e3591bf1350fc3d9b65e8c2555d09 Mon Sep 17 00:00:00 2001 From: Rene De Ren Date: Wed, 11 Mar 2026 16:31:53 +0100 Subject: [PATCH 4/6] test: add unit tests for specificClass Co-Authored-By: Claude Opus 4.6 --- test/specificClass.test.js | 346 +++++++++++++++++++++++++++++++++++++ 1 file changed, 346 insertions(+) create mode 100644 test/specificClass.test.js diff --git a/test/specificClass.test.js b/test/specificClass.test.js new file mode 100644 index 0000000..93c4a4c --- /dev/null +++ b/test/specificClass.test.js @@ -0,0 +1,346 @@ +/** + * Tests for reactor specificClass (domain logic). + * + * Two reactor classes are exported: Reactor_CSTR and Reactor_PFR. + * Both extend a base Reactor class. + * + * Key methods tested: + * - _calcOTR: oxygen transfer rate calculation + * - _arrayClip2Zero: clip negative values to zero + * - setInfluent / getEffluent: influent/effluent data flow + * - setOTR: external OTR override + * - tick (CSTR): forward Euler state update + * - tick (PFR): finite difference state update + * - registerChild: dispatches to measurement / reactor handlers + */ + +const { Reactor_CSTR, Reactor_PFR } = require('../src/specificClass'); + +// --------------- helpers --------------- + +const NUM_SPECIES = 13; + +function makeCSTRConfig(overrides = {}) { + return { + general: { + name: 'TestCSTR', + id: 'cstr-test-1', + logging: { enabled: false, logLevel: 'error' }, + }, + functionality: { + softwareType: 'reactor', + positionVsParent: 'atEquipment', + }, + volume: 1000, + n_inlets: 1, + kla: 240, + timeStep: 1, // 1 second + initialState: new Array(NUM_SPECIES).fill(1.0), + ...overrides, + }; +} + +function makePFRConfig(overrides = {}) { + return { + general: { + name: 'TestPFR', + id: 'pfr-test-1', + logging: { enabled: false, logLevel: 'error' }, + }, + functionality: { + softwareType: 'reactor', + positionVsParent: 'atEquipment', + }, + volume: 200, + length: 10, + resolution_L: 10, + n_inlets: 1, + kla: 240, + alpha: 0.5, + timeStep: 1, + initialState: new Array(NUM_SPECIES).fill(0.1), + ...overrides, + }; +} + +// --------------- CSTR tests --------------- + +describe('Reactor_CSTR', () => { + + describe('constructor / initialization', () => { + it('should create an instance and set state from initialState', () => { + const r = new Reactor_CSTR(makeCSTRConfig()); + expect(r).toBeDefined(); + expect(r.state).toEqual(new Array(NUM_SPECIES).fill(1.0)); + }); + + it('should initialize Fs and Cs_in arrays based on n_inlets', () => { + const r = new Reactor_CSTR(makeCSTRConfig({ n_inlets: 3 })); + expect(r.Fs).toHaveLength(3); + expect(r.Cs_in).toHaveLength(3); + expect(r.Fs.every(v => v === 0)).toBe(true); + }); + + it('should store volume from config', () => { + const r = new Reactor_CSTR(makeCSTRConfig({ volume: 500 })); + expect(r.volume).toBe(500); + }); + + it('should initialize temperature to 20', () => { + const r = new Reactor_CSTR(makeCSTRConfig()); + expect(r.temperature).toBe(20); + }); + }); + + describe('_calcOTR()', () => { + let r; + beforeAll(() => { r = new Reactor_CSTR(makeCSTRConfig({ kla: 240 })); }); + + it('should return a positive value when S_O < saturation', () => { + const otr = r._calcOTR(0, 20); + expect(otr).toBeGreaterThan(0); + }); + + it('should return approximately zero when S_O equals saturation', () => { + // S_O_sat at T=20: 14.652 - 4.1022e-1*20 + 7.9910e-3*400 + 7.7774e-5*8000 + const T = 20; + const S_O_sat = 14.652 - 4.1022e-1 * T + 7.9910e-3 * T * T + 7.7774e-5 * T * T * T; + const otr = r._calcOTR(S_O_sat, T); + expect(otr).toBeCloseTo(0, 5); + }); + + it('should return a negative value when S_O > saturation (supersaturated)', () => { + const otr = r._calcOTR(100, 20); + expect(otr).toBeLessThan(0); + }); + + it('should use T=20 as default temperature', () => { + const otr1 = r._calcOTR(0); + const otr2 = r._calcOTR(0, 20); + expect(otr1).toBe(otr2); + }); + }); + + describe('_arrayClip2Zero()', () => { + let r; + beforeAll(() => { r = new Reactor_CSTR(makeCSTRConfig()); }); + + it('should clip negative values to zero', () => { + expect(r._arrayClip2Zero([-5, 3, -1, 0, 7])).toEqual([0, 3, 0, 0, 7]); + }); + + it('should leave all-positive arrays unchanged', () => { + expect(r._arrayClip2Zero([1, 2, 3])).toEqual([1, 2, 3]); + }); + + it('should handle nested arrays (2D)', () => { + const result = r._arrayClip2Zero([[-1, 2], [3, -4]]); + expect(result).toEqual([[0, 2], [3, 0]]); + }); + + it('should handle a single scalar', () => { + expect(r._arrayClip2Zero(-5)).toBe(0); + expect(r._arrayClip2Zero(5)).toBe(5); + }); + }); + + describe('setInfluent / getEffluent', () => { + it('should store influent data via setter', () => { + const r = new Reactor_CSTR(makeCSTRConfig({ n_inlets: 2 })); + const input = { + payload: { + inlet: 0, + F: 100, + C: new Array(NUM_SPECIES).fill(5), + }, + }; + r.setInfluent = input; + expect(r.Fs[0]).toBe(100); + expect(r.Cs_in[0]).toEqual(new Array(NUM_SPECIES).fill(5)); + }); + + it('should return effluent with the sum of Fs and the current state', () => { + const r = new Reactor_CSTR(makeCSTRConfig()); + r.Fs[0] = 50; + const eff = r.getEffluent; + expect(eff.topic).toBe('Fluent'); + expect(eff.payload.F).toBe(50); + expect(eff.payload.C).toEqual(r.state); + }); + }); + + describe('setOTR', () => { + it('should set the OTR value', () => { + const r = new Reactor_CSTR(makeCSTRConfig({ kla: NaN })); + r.setOTR = { payload: 42 }; + expect(r.OTR).toBe(42); + }); + }); + + describe('tick()', () => { + it('should return a new state array of correct length', () => { + const r = new Reactor_CSTR(makeCSTRConfig()); + const result = r.tick(0.001); + expect(result).toHaveLength(NUM_SPECIES); + }); + + it('should not produce NaN values', () => { + const r = new Reactor_CSTR(makeCSTRConfig()); + r.Fs[0] = 10; + r.Cs_in[0] = new Array(NUM_SPECIES).fill(5); + const result = r.tick(0.001); + result.forEach(v => expect(Number.isNaN(v)).toBe(false)); + }); + + it('should not produce negative concentrations', () => { + const r = new Reactor_CSTR(makeCSTRConfig()); + // Run multiple ticks + for (let i = 0; i < 100; i++) { + r.tick(0.001); + } + r.state.forEach(v => expect(v).toBeGreaterThanOrEqual(0)); + }); + + it('should reach steady state with zero flow (concentrations change only via reaction)', () => { + const r = new Reactor_CSTR(makeCSTRConfig()); + // No inflow + const initial = [...r.state]; + r.tick(0.0001); + // State should have changed due to reaction/OTR + const changed = r.state.some((v, i) => v !== initial[i]); + expect(changed).toBe(true); + }); + }); + + describe('registerChild()', () => { + it('should not throw for "measurement" software type', () => { + const r = new Reactor_CSTR(makeCSTRConfig()); + // Passing null child will trigger warn but not crash + expect(() => r.registerChild(null, 'measurement')).not.toThrow(); + }); + + it('should not throw for "reactor" software type', () => { + const r = new Reactor_CSTR(makeCSTRConfig()); + expect(() => r.registerChild(null, 'reactor')).not.toThrow(); + }); + + it('should not throw for unknown software type', () => { + const r = new Reactor_CSTR(makeCSTRConfig()); + expect(() => r.registerChild(null, 'unknown')).not.toThrow(); + }); + }); +}); + +// --------------- PFR tests --------------- + +describe('Reactor_PFR', () => { + + describe('constructor / initialization', () => { + it('should create an instance with 2D state grid', () => { + const r = new Reactor_PFR(makePFRConfig()); + expect(r).toBeDefined(); + expect(r.state).toHaveLength(10); // resolution_L = 10 + expect(r.state[0]).toHaveLength(NUM_SPECIES); + }); + + it('should compute d_x = length / n_x', () => { + const r = new Reactor_PFR(makePFRConfig({ length: 10, resolution_L: 5 })); + expect(r.d_x).toBe(2); + }); + + it('should compute cross-sectional area A = volume / length', () => { + const r = new Reactor_PFR(makePFRConfig({ volume: 200, length: 10 })); + expect(r.A).toBe(20); + }); + + it('should initialize D (dispersion) to 0', () => { + const r = new Reactor_PFR(makePFRConfig()); + expect(r.D).toBe(0); + }); + + it('should create derivative operators of correct size', () => { + const r = new Reactor_PFR(makePFRConfig({ resolution_L: 8 })); + expect(r.D_op).toHaveLength(8); + expect(r.D_op[0]).toHaveLength(8); + expect(r.D2_op).toHaveLength(8); + expect(r.D2_op[0]).toHaveLength(8); + }); + }); + + describe('setDispersion', () => { + it('should set the axial dispersion value', () => { + const r = new Reactor_PFR(makePFRConfig()); + r.setDispersion = { payload: 0.5 }; + expect(r.D).toBe(0.5); + }); + }); + + describe('tick()', () => { + it('should return a 2D state grid of correct dimensions', () => { + const r = new Reactor_PFR(makePFRConfig()); + r.D = 0.01; + const result = r.tick(0.0001); + expect(result).toHaveLength(10); + expect(result[0]).toHaveLength(NUM_SPECIES); + }); + + it('should not produce NaN values with small time step and dispersion', () => { + const r = new Reactor_PFR(makePFRConfig()); + r.D = 0.01; + r.Fs[0] = 10; + r.Cs_in[0] = new Array(NUM_SPECIES).fill(5); + const result = r.tick(0.0001); + result.forEach(row => { + row.forEach(v => expect(Number.isNaN(v)).toBe(false)); + }); + }); + + it('should not produce negative concentrations', () => { + const r = new Reactor_PFR(makePFRConfig()); + r.D = 0.01; + for (let i = 0; i < 10; i++) { + r.tick(0.0001); + } + r.state.forEach(row => { + row.forEach(v => expect(v).toBeGreaterThanOrEqual(0)); + }); + }); + }); + + describe('_applyBoundaryConditions()', () => { + it('should apply Neumann BC at outlet (last = second to last)', () => { + const r = new Reactor_PFR(makePFRConfig({ resolution_L: 5 })); + const state = Array.from({ length: 5 }, () => new Array(NUM_SPECIES).fill(1)); + state[3] = new Array(NUM_SPECIES).fill(7); + r._applyBoundaryConditions(state); + // outlet BC: state[4] = state[3] + expect(state[4]).toEqual(new Array(NUM_SPECIES).fill(7)); + }); + + it('should apply Neumann BC at inlet when no flow', () => { + const r = new Reactor_PFR(makePFRConfig({ resolution_L: 5 })); + r.Fs[0] = 0; + const state = Array.from({ length: 5 }, () => new Array(NUM_SPECIES).fill(1)); + state[1] = new Array(NUM_SPECIES).fill(3); + r._applyBoundaryConditions(state); + // No flow: state[0] = state[1] + expect(state[0]).toEqual(new Array(NUM_SPECIES).fill(3)); + }); + }); + + describe('_arrayClip2Zero() (inherited)', () => { + it('should clip 2D arrays correctly', () => { + const r = new Reactor_PFR(makePFRConfig()); + const result = r._arrayClip2Zero([[-1, 2], [3, -4]]); + expect(result).toEqual([[0, 2], [3, 0]]); + }); + }); + + describe('_calcOTR() (inherited)', () => { + it('should work the same as in CSTR', () => { + const r = new Reactor_PFR(makePFRConfig({ kla: 240 })); + const otr = r._calcOTR(0, 20); + expect(otr).toBeGreaterThan(0); + }); + }); +}); From 06251988aff9d9b3af19386294bd1a3d27f323fb Mon Sep 17 00:00:00 2001 From: Rene De Ren Date: Thu, 12 Mar 2026 09:33:34 +0100 Subject: [PATCH 5/6] fix: replace console usage with logger, throw on unknown reactor type Unknown reactor type is a configuration error that should fail loudly. Converted console.log to logger.warn for unknown topics. Co-Authored-By: Claude Opus 4.6 --- src/nodeClass.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/nodeClass.js b/src/nodeClass.js index 32f1b90..ab399ac 100644 --- a/src/nodeClass.js +++ b/src/nodeClass.js @@ -57,7 +57,7 @@ class nodeClass { break; } default: - console.log("Unknown topic: " + msg.topic); + this.source.logger.warn(`Unknown topic: ${msg.topic}`); } if (done) { @@ -129,7 +129,7 @@ class nodeClass { new_reactor = new Reactor_PFR(this.config); break; default: - console.warn("Unknown reactor type: " + this.config.reactor_type); + throw new Error(`Unknown reactor type: ${this.config.reactor_type}`); } this.source = new_reactor; // protect from reassignment From 1da55fc3f5f65a1296e56a79547b44f2b932a208 Mon Sep 17 00:00:00 2001 From: Rene De Ren Date: Thu, 12 Mar 2026 16:39:25 +0100 Subject: [PATCH 6/6] Expose output format selectors in editor --- reactor.html | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/reactor.html b/reactor.html index 9d1574f..ebe283d 100644 --- a/reactor.html +++ b/reactor.html @@ -29,6 +29,8 @@ X_TS_init: { value: 125.0009, required: true }, timeStep: { value: 1, required: true }, + processOutputFormat: { value: "process" }, + dbaseOutputFormat: { value: "influxdb" }, enableLog: { value: false }, logLevel: { value: "error" }, @@ -233,6 +235,23 @@ +

Output Formats

+
+ + +
+
+ + +