This commit is contained in:
znetsixe
2026-03-11 11:13:26 +01:00
parent 33f3c2ef61
commit 6b2a8239f2
16 changed files with 2850 additions and 146 deletions

View File

@@ -4,7 +4,7 @@
* Encapsulates all node logic in a reusable class. In future updates we can split this into multiple generic classes and use the config to specifiy which ones to use.
* This allows us to keep the Node-RED node clean and focused on wiring up the UI and event handlers.
*/
const { outputUtils, configManager } = require('generalFunctions');
const { outputUtils, configManager, convert } = require('generalFunctions');
const Specific = require("./specificClass");
class nodeClass {
@@ -42,25 +42,37 @@ class nodeClass {
* @param {object} uiConfig - Raw config from Node-RED UI.
*/
_loadConfig(uiConfig,node) {
const resolvedAssetUuid = uiConfig.assetUuid || uiConfig.uuid || null;
const resolvedAssetTagCode = uiConfig.assetTagCode || uiConfig.assetTagNumber || null;
const flowUnit = this._resolveUnitOrFallback(uiConfig.unit, 'volumeFlowRate', 'm3/h', 'flow');
const curveUnits = {
pressure: this._resolveUnitOrFallback(uiConfig.curvePressureUnit, 'pressure', 'mbar', 'curve pressure'),
flow: this._resolveUnitOrFallback(uiConfig.curveFlowUnit || flowUnit, 'volumeFlowRate', flowUnit, 'curve flow'),
power: this._resolveUnitOrFallback(uiConfig.curvePowerUnit, 'power', 'kW', 'curve power'),
control: this._resolveControlUnitOrFallback(uiConfig.curveControlUnit, '%'),
};
// Merge UI config over defaults
this.config = {
general: {
name: this.name,
id: node.id, // node.id is for the child registration process
unit: uiConfig.unit, // add converter options later to convert to default units (need like a model that defines this which units we are going to use and then conver to those standards)
unit: flowUnit,
logging: {
enabled: uiConfig.enableLog,
logLevel: uiConfig.logLevel
}
},
asset: {
uuid: uiConfig.assetUuid, //need to add this later to the asset model
tagCode: uiConfig.assetTagCode, //need to add this later to the asset model
uuid: resolvedAssetUuid, // support both legacy and current editor field names
tagCode: resolvedAssetTagCode, // support both legacy and current editor field names
tagNumber: uiConfig.assetTagNumber || null,
supplier: uiConfig.supplier,
category: uiConfig.category, //add later to define as the software type
type: uiConfig.assetType,
model: uiConfig.model,
unit: uiConfig.unit
unit: flowUnit,
curveUnits
},
functionality: {
positionVsParent: uiConfig.positionVsParent
@@ -71,6 +83,29 @@ class nodeClass {
this._output = new outputUtils();
}
_resolveUnitOrFallback(candidate, expectedMeasure, fallbackUnit, label) {
const raw = typeof candidate === 'string' ? candidate.trim() : '';
const fallback = String(fallbackUnit || '').trim();
if (!raw) {
return fallback;
}
try {
const desc = convert().describe(raw);
if (expectedMeasure && desc.measure !== expectedMeasure) {
throw new Error(`expected '${expectedMeasure}' but got '${desc.measure}'`);
}
return raw;
} catch (error) {
this.node?.warn?.(`Invalid ${label} unit '${raw}' (${error.message}). Falling back to '${fallback}'.`);
return fallback;
}
}
_resolveControlUnitOrFallback(candidate, fallback = '%') {
const raw = typeof candidate === 'string' ? candidate.trim() : '';
return raw || fallback;
}
/**
* Instantiate the core Measurement logic and store as source.
*/
@@ -81,8 +116,8 @@ class nodeClass {
const stateConfig = {
general: {
logging: {
enabled: machineConfig.eneableLog,
logLevel: machineConfig.logLevel
enabled: machineConfig.general.logging.enabled,
logLevel: machineConfig.general.logging.logLevel
}
},
movement: {
@@ -132,7 +167,8 @@ class nodeClass {
if (pressureStatus.initialized) {
this._pressureInitWarned = false;
}
const flow = Math.round(m.measurements.type("flow").variant("predicted").position('downstream').getCurrentValue('m3/h'));
const flowUnit = m?.config?.general?.unit || 'm3/h';
const flow = Math.round(m.measurements.type("flow").variant("predicted").position('downstream').getCurrentValue(flowUnit));
const power = Math.round(m.measurements.type("power").variant("predicted").position('atEquipment').getCurrentValue('kW'));
let symbolState;
switch(state){
@@ -179,16 +215,16 @@ class nodeClass {
status = { fill: "blue", shape: "dot", text: `${mode}: ${symbolState}` };
break;
case "operational":
status = { fill: "green", shape: "dot", text: `${mode}: ${symbolState} | ${roundedPosition}% | 💨${flow}m³/h | ⚡${power}kW` };
status = { fill: "green", shape: "dot", text: `${mode}: ${symbolState} | ${roundedPosition}% | 💨${flow}${flowUnit} | ⚡${power}kW` };
break;
case "starting":
status = { fill: "yellow", shape: "dot", text: `${mode}: ${symbolState}` };
break;
case "warmingup":
status = { fill: "green", shape: "dot", text: `${mode}: ${symbolState} | ${roundedPosition}% | 💨${flow}m³/h | ⚡${power}kW` };
status = { fill: "green", shape: "dot", text: `${mode}: ${symbolState} | ${roundedPosition}% | 💨${flow}${flowUnit} | ⚡${power}kW` };
break;
case "accelerating":
status = { fill: "yellow", shape: "dot", text: `${mode}: ${symbolState} | ${roundedPosition}%| 💨${flow}m³/h | ⚡${power}kW` };
status = { fill: "yellow", shape: "dot", text: `${mode}: ${symbolState} | ${roundedPosition}%| 💨${flow}${flowUnit} | ⚡${power}kW` };
break;
case "stopping":
status = { fill: "yellow", shape: "dot", text: `${mode}: ${symbolState}` };
@@ -197,7 +233,7 @@ class nodeClass {
status = { fill: "yellow", shape: "dot", text: `${mode}: ${symbolState}` };
break;
case "decelerating":
status = { fill: "yellow", shape: "dot", text: `${mode}: ${symbolState} - ${roundedPosition}% | 💨${flow}m³/h | ⚡${power}kW` };
status = { fill: "yellow", shape: "dot", text: `${mode}: ${symbolState} - ${roundedPosition}% | 💨${flow}${flowUnit} | ⚡${power}kW` };
break;
default:
status = { fill: "grey", shape: "dot", text: `${mode}: ${symbolState}` };
@@ -299,7 +335,8 @@ class nodeClass {
const type = String(payload.type || '').toLowerCase();
const position = payload.position || 'atEquipment';
const value = Number(payload.value);
const unit = payload.unit;
const unit = typeof payload.unit === 'string' ? payload.unit.trim() : '';
const supportedTypes = new Set(['pressure', 'flow', 'temperature', 'power']);
const context = {
timestamp: payload.timestamp || Date.now(),
unit,
@@ -312,6 +349,21 @@ class nodeClass {
break;
}
if (!supportedTypes.has(type)) {
this.node.warn(`Unsupported simulateMeasurement type: ${type}`);
break;
}
if (!unit) {
this.node.warn('simulateMeasurement payload.unit is required');
break;
}
if (typeof m.isUnitValidForType === 'function' && !m.isUnitValidForType(type, unit)) {
this.node.warn(`simulateMeasurement payload.unit '${unit}' is invalid for type '${type}'`);
break;
}
switch (type) {
case 'pressure':
if (typeof m.updateSimulatedMeasurement === "function") {
@@ -326,8 +378,9 @@ class nodeClass {
case 'temperature':
m.updateMeasuredTemperature(value, position, context);
break;
default:
this.node.warn(`Unsupported simulateMeasurement type: ${type}`);
case 'power':
m.updateMeasuredPower(value, position, context);
break;
}
}
break;