Compare commits

..

3 Commits

Author SHA1 Message Date
Rene De Ren
548778c3f5 Expose output format selectors in editor 2026-03-12 16:39:25 +01:00
Rene De Ren
d594131cfc 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 <noreply@anthropic.com>
2026-03-11 14:59:35 +01:00
Rene De Ren
aaa88a7792 Fix ESLint errors and bugs
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-11 13:39:57 +01:00
3 changed files with 137 additions and 134 deletions

View File

@@ -42,32 +42,13 @@ class nodeClass {
* @param {object} uiConfig - Raw config from Node-RED UI.
*/
_loadConfig(uiConfig,node) {
// Resolve flow unit with validation before building config
const flowUnit = this._resolveUnitOrFallback(uiConfig.unit, 'volumeFlowRate', 'm3/h', 'flow');
const resolvedUiConfig = { ...uiConfig, unit: flowUnit };
// Merge UI config over defaults
this.config = {
general: {
name: uiConfig.name,
id: node.id, // node.id is for the child registration process
unit: flowUnit,
logging: {
enabled: uiConfig.enableLog,
logLevel: uiConfig.logLevel
}
},
asset: {
uuid: uiConfig.uuid || uiConfig.assetUuid || null,
tagCode: uiConfig.tagCode || uiConfig.assetTagCode || null,
supplier: uiConfig.supplier,
category: uiConfig.category, //add later to define as the software type
type: uiConfig.assetType,
model: uiConfig.model,
unit: flowUnit
},
functionality: {
positionVsParent: uiConfig.positionVsParent || 'atEquipment', // Default to 'atEquipment' if not specified
}
};
// Build config: base sections handle general, asset, functionality
const cfgMgr = new configManager();
this.config = cfgMgr.buildConfig(this.name, resolvedUiConfig, node.id);
// Utility for formatting outputs
this._output = new outputUtils();
@@ -141,111 +122,109 @@ class nodeClass {
}
_updateNodeStatus() {
const v = this.source;
_updateNodeStatus() {
const v = this.source;
try {
const mode = v.currentMode; // modus is bijv. auto, manual, etc.
const state = v.state.getCurrentState(); //is bijv. operational, idle, off, etc.
const fluidCompatibility = typeof v.getFluidCompatibility === "function"
? v.getFluidCompatibility()
: null;
const fluidWarningText = (
fluidCompatibility
&& (fluidCompatibility.status === "mismatch" || fluidCompatibility.status === "conflict")
)
? fluidCompatibility.message
: "";
const flowUnit = v?.unitPolicy?.output?.flow || this.config.general.unit || "m3/h";
const pressureUnit = v?.unitPolicy?.output?.pressure || "mbar";
// check if measured flow is available otherwise use predicted flow
const flow = Math.round(v.measurements.type("flow").variant("predicted").position("downstream").getCurrentValue(flowUnit));
try {
const mode = v.currentMode;
const state = v.state.getCurrentState();
const fluidCompatibility = typeof v.getFluidCompatibility === "function"
? v.getFluidCompatibility()
: null;
const fluidWarningText = (
fluidCompatibility
&& (fluidCompatibility.status === "mismatch" || fluidCompatibility.status === "conflict")
)
? fluidCompatibility.message
: "";
const flowUnit = v?.unitPolicy?.output?.flow || this.config.general.unit || "m3/h";
const pressureUnit = v?.unitPolicy?.output?.pressure || "mbar";
const flow = Math.round(v.measurements.type("flow").variant("predicted").position("downstream").getCurrentValue(flowUnit));
let deltaP = v.measurements.type("pressure").variant("predicted").position("delta").getCurrentValue(pressureUnit);
if (deltaP !== null) {
deltaP = parseFloat(deltaP.toFixed(0));
} //afronden op 4 decimalen indien geen "null"
if(isNaN(deltaP)) {
deltaP = "∞";
}
const roundedPosition = Math.round(v.state.getCurrentPosition() * 100) / 100;
let symbolState;
switch(state){
case "off":
symbolState = "⬛";
break;
case "idle":
symbolState = "⏸️";
break;
case "operational":
symbolState = "⏵️";
break;
case "starting":
symbolState = "⏯️";
break;
case "warmingup":
symbolState = "🔄";
break;
case "accelerating":
symbolState = "⏩";
break;
case "stopping":
symbolState = "⏹️";
break;
case "coolingdown":
symbolState = "❄️";
break;
case "decelerating":
symbolState = "⏪";
break;
}
let status;
switch (state) {
case "off":
status = { fill: "red", shape: "dot", text: `${mode}: OFF` };
break;
case "idle":
status = { fill: "blue", shape: "dot", text: `${mode}: ${symbolState}` };
break;
case "operational":
status = { fill: "green", shape: "dot", text: `${mode}: ${symbolState} | ${roundedPosition}% | 💨${flow}${flowUnit} | ΔP${deltaP} ${pressureUnit}`}; //deltaP toegevoegd
break;
case "starting":
status = { fill: "yellow", shape: "dot", text: `${mode}: ${symbolState}` };
break;
case "warmingup":
status = { fill: "green", shape: "dot", text: `${mode}: ${symbolState} | ${roundedPosition}% | 💨${flow}${flowUnit} | ΔP${deltaP} ${pressureUnit}`}; //deltaP toegevoegd
break;
case "accelerating":
status = { fill: "yellow", shape: "dot", text: `${mode}: ${symbolState} | ${roundedPosition}% | 💨${flow}${flowUnit} | ΔP${deltaP} ${pressureUnit}` }; //deltaP toegevoegd
break;
case "stopping":
status = { fill: "yellow", shape: "dot", text: `${mode}: ${symbolState}` };
break;
case "coolingdown":
status = { fill: "yellow", shape: "dot", text: `${mode}: ${symbolState}` };
break;
case "decelerating":
status = { fill: "yellow", shape: "dot", text: `${mode}: ${symbolState} - ${roundedPosition}% | 💨${flow}${flowUnit} | ΔP${deltaP} ${pressureUnit}`}; //deltaP toegevoegd
break;
default:
status = { fill: "grey", shape: "dot", text: `${mode}: ${symbolState}` };
}
if (fluidWarningText) {
status = {
fill: "yellow",
shape: "ring",
text: `${status.text} | ⚠ ${fluidWarningText}`,
};
}
return status;
} catch (error) {
this.node.error("Error in updateNodeStatus: " + error.message);
return { fill: "red", shape: "ring", text: "Status Error" };
}
let deltaP = v.measurements.type("pressure").variant("predicted").position("delta").getCurrentValue(pressureUnit);
if (deltaP !== null) {
deltaP = parseFloat(deltaP.toFixed(0));
}
if(isNaN(deltaP)) {
deltaP = "∞";
}
const roundedPosition = Math.round(v.state.getCurrentPosition() * 100) / 100;
let symbolState;
switch(state){
case "off":
symbolState = "⬛";
break;
case "idle":
symbolState = "⏸️";
break;
case "operational":
symbolState = "⏵️";
break;
case "starting":
symbolState = "⏯️";
break;
case "warmingup":
symbolState = "🔄";
break;
case "accelerating":
symbolState = "⏩";
break;
case "stopping":
symbolState = "⏹️";
break;
case "coolingdown":
symbolState = "❄️";
break;
case "decelerating":
symbolState = "⏪";
break;
}
let status;
switch (state) {
case "off":
status = { fill: "red", shape: "dot", text: `${mode}: OFF` };
break;
case "idle":
status = { fill: "blue", shape: "dot", text: `${mode}: ${symbolState}` };
break;
case "operational":
status = { fill: "green", shape: "dot", text: `${mode}: ${symbolState} | ${roundedPosition}% | 💨${flow}${flowUnit} | ΔP${deltaP} ${pressureUnit}`};
break;
case "starting":
status = { fill: "yellow", shape: "dot", text: `${mode}: ${symbolState}` };
break;
case "warmingup":
status = { fill: "green", shape: "dot", text: `${mode}: ${symbolState} | ${roundedPosition}% | 💨${flow}${flowUnit} | ΔP${deltaP} ${pressureUnit}`};
break;
case "accelerating":
status = { fill: "yellow", shape: "dot", text: `${mode}: ${symbolState} | ${roundedPosition}% | 💨${flow}${flowUnit} | ΔP${deltaP} ${pressureUnit}` };
break;
case "stopping":
status = { fill: "yellow", shape: "dot", text: `${mode}: ${symbolState}` };
break;
case "coolingdown":
status = { fill: "yellow", shape: "dot", text: `${mode}: ${symbolState}` };
break;
case "decelerating":
status = { fill: "yellow", shape: "dot", text: `${mode}: ${symbolState} - ${roundedPosition}% | 💨${flow}${flowUnit} | ΔP${deltaP} ${pressureUnit}`};
break;
default:
status = { fill: "grey", shape: "dot", text: `${mode}: ${symbolState}` };
}
if (fluidWarningText) {
status = {
fill: "yellow",
shape: "ring",
text: `${status.text} | ⚠ ${fluidWarningText}`,
};
}
return status;
} catch (error) {
this.node.error("Error in updateNodeStatus: " + error.message);
return { fill: "red", shape: "ring", text: "Status Error" };
}
}
/**
* Register this node as a child upstream and downstream.

View File

@@ -703,7 +703,7 @@ class Valve {
this.logger.debug(`Updating pressure: variant=${variant}, value=${value}, position=${position}`);
switch (variant) {
case ("measured"):
case ("measured"): {
// put value in measurements container
this._writeMeasurement("pressure", "measured", position, Number(value), unit);
// get latest downstream pressure measurement
@@ -712,18 +712,20 @@ class Valve {
const predictedFlow = this._readMeasurement("flow", "predicted", "downstream", FORMULA_UNITS.flow);
const activeFlow = Number.isFinite(predictedFlow) ? predictedFlow : measuredFlow;
// update predicted flow measurement
this.updateDeltaPKlep(activeFlow,this.kv,measuredDownStreamP,this.rho,this.T); //update deltaP based on new flow
this.updateDeltaPKlep(activeFlow,this.kv,measuredDownStreamP,this.rho,this.T);
break;
}
case ("predicted"):
case ("predicted"): {
// put value in measurements container
this._writeMeasurement("pressure", "predicted", position, Number(value), unit);
const predictedDownStreamP = this._readMeasurement("pressure", "predicted", "downstream", FORMULA_UNITS.pressure);
const measuredFlowFromPred = this._readMeasurement("flow", "measured", "downstream", FORMULA_UNITS.flow);
const predictedFlowFromPred = this._readMeasurement("flow", "predicted", "downstream", FORMULA_UNITS.flow);
const activeFlowFromPred = Number.isFinite(predictedFlowFromPred) ? predictedFlowFromPred : measuredFlowFromPred;
this.updateDeltaPKlep(activeFlowFromPred,this.kv,predictedDownStreamP,this.rho,this.T); //update deltaP based on new flow
this.updateDeltaPKlep(activeFlowFromPred,this.kv,predictedDownStreamP,this.rho,this.T);
break;
}
default:
this.logger.warn(`Unrecognized variant '${variant}' for flow update.`);
@@ -784,23 +786,25 @@ class Valve {
this.logger.debug(`Updating flow: variant=${variant}, value=${value}, position=${position}`);
switch (variant) {
case ("measured"):
case ("measured"): {
// put value in measurements container
this._writeMeasurement("flow", "measured", position, Number(value), unit);
// get latest downstream pressure measurement
const measuredDownStreamP = this._readMeasurement("pressure", "measured", "downstream", FORMULA_UNITS.pressure);
const measuredFlow = this._readMeasurement("flow", "measured", position, FORMULA_UNITS.flow);
// update predicted flow measurement
this.updateDeltaPKlep(measuredFlow,this.kv,measuredDownStreamP,this.rho,this.T); //update deltaP based on new flow
this.updateDeltaPKlep(measuredFlow,this.kv,measuredDownStreamP,this.rho,this.T);
break;
}
case ("predicted"):
case ("predicted"): {
// put value in measurements container
this._writeMeasurement("flow", "predicted", position, Number(value), unit);
const predictedDownStreamP = this._readMeasurement("pressure", "measured", "downstream", FORMULA_UNITS.pressure);
const predictedFlow = this._readMeasurement("flow", "predicted", position, FORMULA_UNITS.flow);
this.updateDeltaPKlep(predictedFlow,this.kv,predictedDownStreamP,this.rho,this.T); //update deltaP based on new flow
this.updateDeltaPKlep(predictedFlow,this.kv,predictedDownStreamP,this.rho,this.T);
break;
}
default:
this.logger.warn(`Unrecognized variant '${variant}' for flow update.`);

View File

@@ -22,6 +22,8 @@
// Define specific properties
speed: { value: 1, required: true },
processOutputFormat: { value: "process" },
dbaseOutputFormat: { value: "influxdb" },
//define asset properties
uuid: { value: "" },
@@ -113,6 +115,24 @@
<input type="number" id="node-input-speed" style="width:60%;" />
</div>
<h3>Output Formats</h3>
<div class="form-row">
<label for="node-input-processOutputFormat"><i class="fa fa-random"></i> Process Output</label>
<select id="node-input-processOutputFormat" style="width:60%;">
<option value="process">process</option>
<option value="json">json</option>
<option value="csv">csv</option>
</select>
</div>
<div class="form-row">
<label for="node-input-dbaseOutputFormat"><i class="fa fa-database"></i> Database Output</label>
<select id="node-input-dbaseOutputFormat" style="width:60%;">
<option value="influxdb">influxdb</option>
<option value="json">json</option>
<option value="csv">csv</option>
</select>
</div>
<!-- Optional Extended Fields: supplier, cat, type, model, unit -->
<!-- Asset fields will be injected here -->
<div id="asset-fields-placeholder"></div>