This commit is contained in:
znetsixe
2026-02-23 12:51:10 +01:00
parent 2b9ad5fd19
commit 460b872053
5 changed files with 74 additions and 39 deletions

View File

@@ -130,8 +130,8 @@
} }
// save position field // save position field
if (window.EVOLV?.nodes?.measurement?.positionMenu?.saveEditor) { if (window.EVOLV?.nodes?.reactor?.positionMenu?.saveEditor) {
window.EVOLV.nodes.rotatingMachine.positionMenu.saveEditor(this); window.EVOLV.nodes.reactor.positionMenu.saveEditor(this);
} }
let volume = parseFloat($("#node-input-volume").typedInput("value")); let volume = parseFloat($("#node-input-volume").typedInput("value"));

View File

@@ -30,35 +30,42 @@ class nodeClass {
*/ */
_attachInputHandler() { _attachInputHandler() {
this.node.on('input', (msg, send, done) => { this.node.on('input', (msg, send, done) => {
try {
switch (msg.topic) { switch (msg.topic) {
case "clock": case "clock":
this.source.updateState(msg.timestamp); this.source.updateState(msg.timestamp);
send([msg, null, null]); send([msg, null, null]);
break; break;
case "Fluent": case "Fluent":
this.source.setInfluent = msg; this.source.setInfluent = msg;
break; break;
case "OTR": case "OTR":
this.source.setOTR = msg; this.source.setOTR = msg;
break; break;
case "Temperature": case "Temperature":
this.source.setTemperature = msg; this.source.setTemperature = msg;
break; break;
case "Dispersion": case "Dispersion":
this.source.setDispersion = msg; this.source.setDispersion = msg;
break; break;
case 'registerChild': case 'registerChild': {
// Register this node as a parent of the child node const childId = msg.payload;
const childId = msg.payload; const childObj = this.RED.nodes.getNode(childId);
const childObj = this.RED.nodes.getNode(childId); if (!childObj || !childObj.source) {
this.source.childRegistrationUtils.registerChild(childObj.source, msg.positionVsParent); this.source?.logger?.warn(`registerChild skipped: missing child/source for id=${childId}`);
break; break;
default: }
console.log("Unknown topic: " + msg.topic); 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(); done();
} }
}); });
@@ -137,7 +144,8 @@ class nodeClass {
new_reactor = new Reactor_PFR(this.config); new_reactor = new Reactor_PFR(this.config);
break; break;
default: 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 this.source = new_reactor; // protect from reassignment
@@ -157,7 +165,7 @@ class nodeClass {
_attachCloseHandler() { _attachCloseHandler() {
this.node.on('close', (done) => { this.node.on('close', (done) => {
clearInterval(this._tickInterval); clearInterval(this._tickInterval);
done(); if (typeof done === 'function') done();
}); });
} }
} }

View File

@@ -62,6 +62,24 @@ class Reactor {
this.OTR = input.payload; 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. * Getter for effluent data.
* @returns {object} Effluent data object (msg), defaults to inlet 0. * @returns {object} Effluent data object (msg), defaults to inlet 0.
@@ -323,8 +341,16 @@ class Reactor_PFR extends Reactor {
_updateMeasurement(measurementType, value, position, context) { _updateMeasurement(measurementType, value, position, context) {
switch(measurementType) { switch(measurementType) {
case "quantity (oxygen)": case "quantity (oxygen)":
let grid_pos = Math.round(position / this.config.length * this.n_x); if (!Number.isFinite(position) || !Number.isFinite(value) || this.config.length <= 0) {
this.state[grid_pos][S_O_INDEX] = value; // naive approach for reconciling measurements and simulation 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; break;
default: default:
super._updateMeasurement(measurementType, value, position, context); super._updateMeasurement(measurementType, value, position, context);

View File

@@ -4,7 +4,7 @@ const assert = require('node:assert/strict');
const NodeClass = require('../../src/nodeClass'); const NodeClass = require('../../src/nodeClass');
const { makeNodeStub, makeREDStub } = require('../helpers/factories'); 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 inst = Object.create(NodeClass.prototype);
const node = makeNodeStub(); const node = makeNodeStub();
@@ -18,7 +18,7 @@ test('registerChild with unknown node id currently throws (known robustness gap)
inst._attachInputHandler(); inst._attachInputHandler();
assert.throws(() => { assert.doesNotThrow(() => {
node._handlers.input( node._handlers.input(
{ topic: 'registerChild', payload: 'missing-child', positionVsParent: 'upstream' }, { topic: 'registerChild', payload: 'missing-child', positionVsParent: 'upstream' },
() => {}, () => {},

View File

@@ -4,12 +4,13 @@ const assert = require('node:assert/strict');
const { Reactor_PFR } = require('../../src/specificClass'); const { Reactor_PFR } = require('../../src/specificClass');
const { makeReactorConfig } = require('../helpers/factories'); 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( const reactor = new Reactor_PFR(
makeReactorConfig({ reactor_type: 'PFR', length: 10, resolution_L: 5, n_inlets: 1 }), makeReactorConfig({ reactor_type: 'PFR', length: 10, resolution_L: 5, n_inlets: 1 }),
); );
assert.throws(() => { assert.doesNotThrow(() => {
reactor._updateMeasurement('quantity (oxygen)', 2.5, 10, {}); reactor._updateMeasurement('quantity (oxygen)', 2.5, 10, {});
}); });
assert.equal(reactor.state[reactor.n_x - 1][0], 2.5);
}); });