diff --git a/reactor.html b/reactor.html
index 8fb9526..a591479 100644
--- a/reactor.html
+++ b/reactor.html
@@ -130,8 +130,8 @@
}
// save position field
- if (window.EVOLV?.nodes?.measurement?.positionMenu?.saveEditor) {
- window.EVOLV.nodes.rotatingMachine.positionMenu.saveEditor(this);
+ if (window.EVOLV?.nodes?.reactor?.positionMenu?.saveEditor) {
+ window.EVOLV.nodes.reactor.positionMenu.saveEditor(this);
}
let volume = parseFloat($("#node-input-volume").typedInput("value"));
diff --git a/src/nodeClass.js b/src/nodeClass.js
index a9e53d3..62b8b66 100644
--- a/src/nodeClass.js
+++ b/src/nodeClass.js
@@ -30,35 +30,42 @@ class nodeClass {
*/
_attachInputHandler() {
this.node.on('input', (msg, send, done) => {
-
- switch (msg.topic) {
- case "clock":
- this.source.updateState(msg.timestamp);
- send([msg, null, null]);
- break;
- case "Fluent":
- this.source.setInfluent = msg;
- break;
- case "OTR":
- this.source.setOTR = msg;
- break;
- case "Temperature":
- this.source.setTemperature = msg;
- break;
- case "Dispersion":
- this.source.setDispersion = msg;
- break;
- case 'registerChild':
- // Register this node as a parent of the child node
- const childId = msg.payload;
- const childObj = this.RED.nodes.getNode(childId);
- this.source.childRegistrationUtils.registerChild(childObj.source, msg.positionVsParent);
- break;
- default:
- console.log("Unknown topic: " + msg.topic);
+ try {
+ switch (msg.topic) {
+ case "clock":
+ this.source.updateState(msg.timestamp);
+ send([msg, null, null]);
+ break;
+ case "Fluent":
+ this.source.setInfluent = msg;
+ break;
+ case "OTR":
+ this.source.setOTR = msg;
+ break;
+ case "Temperature":
+ this.source.setTemperature = msg;
+ break;
+ case "Dispersion":
+ this.source.setDispersion = msg;
+ break;
+ case 'registerChild': {
+ const childId = msg.payload;
+ const childObj = this.RED.nodes.getNode(childId);
+ if (!childObj || !childObj.source) {
+ this.source?.logger?.warn(`registerChild skipped: missing child/source for id=${childId}`);
+ break;
+ }
+ this.source.childRegistrationUtils.registerChild(childObj.source, msg.positionVsParent);
+ break;
+ }
+ default:
+ this.source?.logger?.warn(`Unknown topic: ${msg.topic}`);
+ }
+ } catch (error) {
+ this.source?.logger?.error(`Input handler failure: ${error.message}`);
}
- if (done) {
+ if (typeof done === 'function') {
done();
}
});
@@ -137,7 +144,8 @@ class nodeClass {
new_reactor = new Reactor_PFR(this.config);
break;
default:
- console.warn("Unknown reactor type: " + uiConfig.reactor_type);
+ this.node.warn("Unknown reactor type: " + this.config.reactor_type + ". Falling back to CSTR.");
+ new_reactor = new Reactor_CSTR(this.config);
}
this.source = new_reactor; // protect from reassignment
@@ -157,9 +165,9 @@ class nodeClass {
_attachCloseHandler() {
this.node.on('close', (done) => {
clearInterval(this._tickInterval);
- done();
+ if (typeof done === 'function') done();
});
}
}
-module.exports = nodeClass;
\ No newline at end of file
+module.exports = nodeClass;
diff --git a/src/specificClass.js b/src/specificClass.js
index d34e6cc..e8d71ae 100644
--- a/src/specificClass.js
+++ b/src/specificClass.js
@@ -62,6 +62,24 @@ class Reactor {
this.OTR = input.payload;
}
+ /**
+ * Setter for reactor temperature [C].
+ * Accepts either a direct numeric payload or { value } object payload.
+ * @param {object} input - Input object (msg)
+ */
+ set setTemperature(input) {
+ const payload = input?.payload;
+ const rawValue = (payload && typeof payload === 'object' && payload.value !== undefined)
+ ? payload.value
+ : payload;
+ const parsedValue = Number(rawValue);
+ if (!Number.isFinite(parsedValue)) {
+ this.logger.warn(`Invalid temperature input: ${rawValue}`);
+ return;
+ }
+ this.temperature = parsedValue;
+ }
+
/**
* Getter for effluent data.
* @returns {object} Effluent data object (msg), defaults to inlet 0.
@@ -323,8 +341,16 @@ class Reactor_PFR extends Reactor {
_updateMeasurement(measurementType, value, position, context) {
switch(measurementType) {
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
+ if (!Number.isFinite(position) || !Number.isFinite(value) || this.config.length <= 0) {
+ this.logger.warn(`Ignoring oxygen measurement update with invalid data (position=${position}, value=${value}).`);
+ break;
+ }
+ {
+ // Clamp sensor-derived position to valid PFR grid bounds.
+ const rawIndex = Math.round(position / this.config.length * this.n_x);
+ const grid_pos = Math.max(0, Math.min(this.n_x - 1, rawIndex));
+ this.state[grid_pos][S_O_INDEX] = value; // reconcile measured oxygen concentration into nearest grid cell
+ }
break;
default:
super._updateMeasurement(measurementType, value, position, context);
@@ -416,4 +442,4 @@ module.exports = { Reactor_CSTR, Reactor_PFR };
// while (N < 5000) {
// console.log(Reactor.tick(0.001));
// N += 1;
-// }
\ No newline at end of file
+// }
diff --git a/test/edge/missing-child.edge.test.js b/test/edge/missing-child.edge.test.js
index 6d8184a..3e2cb0f 100644
--- a/test/edge/missing-child.edge.test.js
+++ b/test/edge/missing-child.edge.test.js
@@ -4,7 +4,7 @@ const assert = require('node:assert/strict');
const NodeClass = require('../../src/nodeClass');
const { makeNodeStub, makeREDStub } = require('../helpers/factories');
-test('registerChild with unknown node id currently throws (known robustness gap)', () => {
+test('registerChild with unknown node id is ignored without throwing', () => {
const inst = Object.create(NodeClass.prototype);
const node = makeNodeStub();
@@ -18,7 +18,7 @@ test('registerChild with unknown node id currently throws (known robustness gap)
inst._attachInputHandler();
- assert.throws(() => {
+ assert.doesNotThrow(() => {
node._handlers.input(
{ topic: 'registerChild', payload: 'missing-child', positionVsParent: 'upstream' },
() => {},
diff --git a/test/edge/pfr-measurement-grid.edge.test.js b/test/edge/pfr-measurement-grid.edge.test.js
index 16889ec..3932a6f 100644
--- a/test/edge/pfr-measurement-grid.edge.test.js
+++ b/test/edge/pfr-measurement-grid.edge.test.js
@@ -4,12 +4,13 @@ const assert = require('node:assert/strict');
const { Reactor_PFR } = require('../../src/specificClass');
const { makeReactorConfig } = require('../helpers/factories');
-test('oxygen measurement at exact reactor length overflows PFR grid index (known bounds gap)', () => {
+test('oxygen measurement at exact reactor length is clamped to the last PFR grid index', () => {
const reactor = new Reactor_PFR(
makeReactorConfig({ reactor_type: 'PFR', length: 10, resolution_L: 5, n_inlets: 1 }),
);
- assert.throws(() => {
+ assert.doesNotThrow(() => {
reactor._updateMeasurement('quantity (oxygen)', 2.5, 10, {});
});
+ assert.equal(reactor.state[reactor.n_x - 1][0], 2.5);
});