updates to rotating machine struct

This commit is contained in:
znetsixe
2026-02-12 10:48:44 +01:00
parent c63701db38
commit 405be33626
15 changed files with 1978 additions and 7 deletions

View File

@@ -0,0 +1,31 @@
const test = require('node:test');
const assert = require('node:assert/strict');
const Machine = require('../../src/specificClass');
const { makeMachineConfig, makeStateConfig } = require('../helpers/factories');
test('constructor initializes with valid curve model', () => {
const machine = new Machine(makeMachineConfig(), makeStateConfig());
assert.equal(machine.hasCurve, true);
assert.ok(machine.predictFlow);
assert.ok(machine.predictPower);
assert.ok(machine.predictCtrl);
const out = machine.getOutput();
assert.ok('state' in out);
assert.ok('mode' in out);
assert.ok('ctrl' in out);
});
test('constructor handles missing curve model without throwing', () => {
const cfg = makeMachineConfig({ asset: { supplier: 'x', category: 'machine', type: 'pump', model: 'not-existing-model', unit: 'm3/h' } });
const machine = new Machine(cfg, makeStateConfig());
assert.equal(machine.hasCurve, false);
assert.equal(machine.predictFlow, null);
assert.equal(machine.predictPower, null);
assert.equal(machine.predictCtrl, null);
const out = machine.getOutput();
assert.ok('state' in out);
});

View File

@@ -0,0 +1,35 @@
const test = require('node:test');
const assert = require('node:assert/strict');
const Machine = require('../../src/specificClass');
const { makeMachineConfig, makeStateConfig } = require('../helpers/factories');
test('setMode changes mode only for allowed values', () => {
const machine = new Machine(makeMachineConfig(), makeStateConfig());
const original = machine.currentMode;
machine.setMode('virtualControl');
assert.equal(machine.currentMode, 'virtualControl');
machine.setMode('invalid-mode');
assert.equal(machine.currentMode, 'virtualControl');
assert.notEqual(machine.currentMode, original);
});
test('handleInput rejects non-string action safely', async () => {
const machine = new Machine(makeMachineConfig(), makeStateConfig());
await assert.doesNotReject(async () => {
await machine.handleInput('GUI', 123, null);
});
});
test('handleInput ignores disallowed source/action combination', async () => {
const machine = new Machine(makeMachineConfig(), makeStateConfig());
machine.setMode('fysicalControl');
const before = machine.state.getCurrentState();
await machine.handleInput('GUI', 'execSequence', 'startup');
const after = machine.state.getCurrentState();
assert.equal(before, after);
});

View File

@@ -0,0 +1,31 @@
const test = require('node:test');
const assert = require('node:assert/strict');
const Machine = require('../../src/specificClass');
const NodeClass = require('../../src/nodeClass');
const { makeMachineConfig, makeStateConfig, makeNodeStub } = require('../helpers/factories');
test('setpoint rejects negative inputs without throwing', async () => {
const machine = new Machine(makeMachineConfig(), makeStateConfig({ state: { current: 'operational' } }));
await assert.doesNotReject(async () => {
await machine.setpoint(-1);
});
});
test('nodeClass _updateNodeStatus returns error status on internal failure', () => {
const inst = Object.create(NodeClass.prototype);
const node = makeNodeStub();
inst.node = node;
inst.source = {
currentMode: 'auto',
state: {
getCurrentState() {
throw new Error('boom');
},
},
};
const status = inst._updateNodeStatus();
assert.equal(status.text, 'Status Error');
assert.equal(node._errors.length, 1);
});

View File

@@ -0,0 +1,60 @@
const test = require('node:test');
const assert = require('node:assert/strict');
const NodeClass = require('../../src/nodeClass');
const { makeNodeStub, makeREDStub } = require('../helpers/factories');
test('input handler routes topics to source methods', () => {
const inst = Object.create(NodeClass.prototype);
const node = makeNodeStub();
const calls = [];
inst.node = node;
inst.RED = makeREDStub({
child1: {
source: { id: 'child-source' },
},
});
inst.source = {
childRegistrationUtils: {
registerChild(childSource, pos) {
calls.push(['registerChild', childSource, pos]);
},
},
setMode(mode) {
calls.push(['setMode', mode]);
},
handleInput(source, action, parameter) {
calls.push(['handleInput', source, action, parameter]);
},
showWorkingCurves() {
return { ok: true };
},
showCoG() {
return { cog: 1 };
},
updateMeasuredPressure(value, position) {
calls.push(['updateMeasuredPressure', value, position]);
},
updateMeasuredFlow(value, position) {
calls.push(['updateMeasuredFlow', value, position]);
},
updateMeasuredTemperature(value, position) {
calls.push(['updateMeasuredTemperature', value, position]);
},
};
inst._attachInputHandler();
const onInput = node._handlers.input;
onInput({ topic: 'setMode', payload: 'auto' }, () => {}, () => {});
onInput({ topic: 'execSequence', payload: { source: 'GUI', action: 'execSequence', parameter: 'startup' } }, () => {}, () => {});
onInput({ topic: 'registerChild', payload: 'child1', positionVsParent: 'downstream' }, () => {}, () => {});
onInput({ topic: 'simulateMeasurement', payload: { type: 'pressure', position: 'upstream', value: 250, unit: 'mbar' } }, () => {}, () => {});
assert.deepEqual(calls[0], ['setMode', 'auto']);
assert.deepEqual(calls[1], ['handleInput', 'GUI', 'execSequence', 'startup']);
assert.deepEqual(calls[2], ['registerChild', { id: 'child-source' }, 'downstream']);
assert.deepEqual(calls[3], ['updateMeasuredPressure', 250, 'upstream']);
});

110
test/helpers/factories.js Normal file
View File

@@ -0,0 +1,110 @@
const { MeasurementContainer } = require('generalFunctions');
function makeMachineConfig(overrides = {}) {
return {
general: {
id: 'rm-test-1',
name: 'rotating-machine-test',
unit: 'm3/h',
logging: { enabled: false, logLevel: 'error' },
},
functionality: {
positionVsParent: 'atEquipment',
},
asset: {
supplier: 'hidrostal',
category: 'machine',
type: 'pump',
model: 'hidrostal-H05K-S03R',
unit: 'm3/h',
},
...overrides,
};
}
function makeStateConfig(overrides = {}) {
return {
general: {
logging: { enabled: false, logLevel: 'error' },
},
state: {
current: 'idle',
},
movement: {
mode: 'staticspeed',
speed: 1000,
maxSpeed: 1000,
interval: 10,
},
time: {
starting: 0,
warmingup: 0,
stopping: 0,
coolingdown: 0,
},
...overrides,
};
}
function makeChildMeasurement({ id = 'child-1', name = 'PT-1', positionVsParent = 'downstream', type = 'pressure', unit = 'mbar' } = {}) {
const measurements = new MeasurementContainer({
autoConvert: true,
defaultUnits: {
pressure: 'mbar',
flow: 'm3/h',
temperature: 'C',
power: 'kW',
},
});
return {
config: {
general: { id, name },
functionality: { positionVsParent },
asset: { type, unit },
},
measurements,
};
}
function makeNodeStub() {
const handlers = {};
const sent = [];
const statuses = [];
const errors = [];
const warns = [];
return {
id: 'node-1',
source: null,
send(msg) { sent.push(msg); },
status(s) { statuses.push(s); },
error(e) { errors.push(e); },
warn(w) { warns.push(w); },
on(event, cb) { handlers[event] = cb; },
_handlers: handlers,
_sent: sent,
_statuses: statuses,
_errors: errors,
_warns: warns,
};
}
function makeREDStub(nodeMap = {}) {
return {
nodes: {
getNode(id) {
return nodeMap[id] || null;
},
createNode() {},
registerType() {},
},
};
}
module.exports = {
makeMachineConfig,
makeStateConfig,
makeChildMeasurement,
makeNodeStub,
makeREDStub,
};

View File

@@ -0,0 +1,22 @@
const test = require('node:test');
const assert = require('node:assert/strict');
const Machine = require('../../src/specificClass');
const { makeMachineConfig, makeStateConfig } = require('../helpers/factories');
test('calcEfficiency runs through coolprop path without mocks', () => {
const machine = new Machine(makeMachineConfig(), makeStateConfig({ state: { current: 'operational' } }));
machine.measurements.type('pressure').variant('measured').position('downstream').value(1200, Date.now(), 'mbar');
machine.measurements.type('pressure').variant('measured').position('upstream').value(800, Date.now(), 'mbar');
machine.measurements.type('flow').variant('predicted').position('atEquipment').value(120, Date.now(), 'm3/h');
machine.measurements.type('power').variant('predicted').position('atEquipment').value(12, Date.now(), 'kW');
assert.doesNotThrow(() => {
machine.calcEfficiency(12, 120, 'predicted');
});
const eff = machine.measurements.type('efficiency').variant('predicted').position('atEquipment').getCurrentValue();
assert.equal(typeof eff, 'number');
assert.ok(eff > 0);
});

View File

@@ -0,0 +1,27 @@
const test = require('node:test');
const assert = require('node:assert/strict');
const Machine = require('../../src/specificClass');
const { makeMachineConfig, makeStateConfig, makeChildMeasurement } = require('../helpers/factories');
test('registerChild listens to measurement events and stores measured pressure', async () => {
const machine = new Machine(makeMachineConfig(), makeStateConfig());
const child = makeChildMeasurement({ positionVsParent: 'downstream', type: 'pressure', unit: 'mbar' });
machine.registerChild(child, 'measurement');
child.measurements
.type('pressure')
.variant('measured')
.position('downstream')
.value(123, Date.now(), 'mbar');
const stored = machine.measurements
.type('pressure')
.variant('measured')
.position('downstream')
.getCurrentValue('mbar');
assert.equal(typeof stored, 'number');
assert.equal(Math.round(stored), 123);
});

View File

@@ -0,0 +1,22 @@
const test = require('node:test');
const assert = require('node:assert/strict');
const Machine = require('../../src/specificClass');
const { makeMachineConfig, makeStateConfig } = require('../helpers/factories');
test('execSequence startup reaches operational with zero transition times', async () => {
const machine = new Machine(makeMachineConfig(), makeStateConfig());
await machine.handleInput('parent', 'execSequence', 'startup');
assert.equal(machine.state.getCurrentState(), 'operational');
});
test('execMovement updates controller position in operational state', async () => {
const machine = new Machine(makeMachineConfig(), makeStateConfig({ state: { current: 'operational' } }));
await machine.handleInput('parent', 'execMovement', 10);
const pos = machine.state.getCurrentPosition();
assert.ok(pos >= 9.9 && pos <= 10);
});