Compare commits

...

38 Commits

Author SHA1 Message Date
d5d078413c Add flowNumber configuration to define effluent flow handling 2025-10-31 14:03:54 +01:00
17662ef7cb Add total suspended solids sensor to assetData 2025-10-31 13:53:35 +01:00
f653a1e98c Refactor child setup to support multiple parents consistently 2025-10-24 13:37:26 +02:00
3886277616 Fix bug in parent registration code block 2025-09-29 17:13:34 +02:00
83018fabe0 Allow for multiple parents 2025-09-29 16:06:06 +02:00
e72579e5d0 Merge branch 'p.vanderwilt-main' 2025-09-26 16:18:33 +02:00
0fb42865ff Add distance configuration to measurement settings 2025-09-26 15:51:40 +02:00
b2b811e802 Add test oxygen sensor to assets 2025-09-26 14:29:14 +02:00
bde2dcf7d8 Add oygen sensor to assets 2025-09-26 14:26:41 +02:00
76570280bc Add null check for logger before logging errors in position validation 2025-09-26 13:58:09 +02:00
d7017b5d33 Add logger checks before error logging for position validation 2025-09-26 13:51:59 +02:00
f93603c182 Merge pull request 'Add distance float position handling with backward compatibility' (#1) from p.vanderwilt/generalFunctions:main into main
Reviewed-on: #1
2025-09-26 11:41:53 +00:00
c261335df5 Fix comparison operator in _convertPositionNum2Str method 2025-09-25 13:54:12 +02:00
a41f053d5d Merge branch 'position-float' 2025-09-24 13:38:50 +02:00
8d7d98f126 Fix inversion bug 2025-09-23 14:31:09 +02:00
3f90685834 Enhance position handling by adding utility methods for conversion 2025-09-23 14:17:42 +02:00
efc97d6cd1 Fix errorMetrics.js again 2025-09-23 11:55:44 +02:00
znetsixe
d72bfd5560 updated files 2025-09-22 16:02:04 +02:00
6d30e25daa Add string handling for position 2025-09-17 14:52:25 +02:00
16e202e841 Refactor position handling in MeasurementContainer to use position values instead of names 2025-09-17 13:21:35 +02:00
znetsixe
241ed1d3cb errormetrics fix 2025-09-16 12:10:41 +02:00
3876f86530 Merge branch 'main' into fix-missing-references 2025-09-15 15:11:39 +02:00
56be0f1840 Merge remote-tracking branch 'upstream/main' 2025-09-15 15:10:34 +02:00
znetsixe
a30f2c90f4 physicalPosition 1D update 2025-09-05 16:18:42 +02:00
302e122387 Fixing the same bug in reference, again 2025-09-05 13:39:15 +02:00
6dcd3c3d26 Merge pull request 'implement-reactor-child' (#2) from implement-reactor-child into main
Reviewed-on: p.vanderwilt/generalFunctions#2
2025-09-03 10:18:21 +00:00
958ec2269c Print reactors state after configuration 2025-09-03 11:13:00 +02:00
0bccad05f8 Added error message to node registration 2025-08-01 12:30:12 +02:00
7191e57aea Improved reactor registration 2025-07-31 14:57:38 +02:00
aec2d3692d Fixed missing reference to position 2025-07-31 11:36:42 +02:00
71643375fc Added additional reactor handling 2025-07-24 15:09:04 +02:00
f13ee68938 merge upstream 2025-07-22 11:00:42 +00:00
475caa90db Fixed bugs in connectReactor 2025-07-21 17:32:00 +02:00
9aa38f9000 Merge pull request 'Implement Reactor parent-child' (#1) from implement-reactor-child into main
Reviewed-on: p.vanderwilt/generalFunctions#1
2025-07-21 12:29:42 +00:00
4a6273b037 Merge branch 'main' into implement-reactor-child 2025-07-21 14:15:51 +02:00
8c9301b128 Remove undefined reference to 'desc' 2025-07-21 14:14:30 +02:00
7cdfc87c83 Add state update on recieving child signal 2025-07-16 16:04:32 +02:00
839ae2f3da feat: add reactor registration and handling in ChildRegistrationUtils 2025-07-16 15:34:58 +02:00
8 changed files with 234 additions and 47 deletions

View File

@@ -59,15 +59,20 @@
]
},
{
"name": "Level",
"name": "Quantity (oxygen)",
"models": [
{
"name": "VegaLevel 10",
"units": ["m", "ft", "mm"]
"name": "VegaOxySense 10",
"units": ["g/m³", "mol/m³"]
}
]
},
{
"name": "VegaLevel 20",
"units": ["m", "ft", "mm"]
"name": "Quantity (TSS)",
"models": [
{
"name": "VegaSolidsProbe",
"units": ["g/m³"]
}
]
}

View File

@@ -12,11 +12,12 @@ const outputUtils = require('./src/helper/outputUtils.js');
const logger = require('./src/helper/logger.js');
const validation = require('./src/helper/validationUtils.js');
const configUtils = require('./src/helper/configUtils.js');
const assertions = require('./src/helper/assertionUtils.js')
// Domain-specific modules
const { MeasurementContainer } = require('./src/measurements/index.js');
const configManager = require('./src/configs/index.js');
const nrmse = require('./src/nrmse/ErrorMetrics.js');
const nrmse = require('./src/nrmse/errorMetrics.js');
const state = require('./src/state/state.js');
const convert = require('./src/convert/index.js');
const MenuManager = require('./src/menu/index.js');
@@ -34,6 +35,7 @@ module.exports = {
configUtils,
logger,
validation,
assertions,
MeasurementContainer,
nrmse,
state,

View File

@@ -91,6 +91,13 @@
],
"description": "Defines the position of the measurement relative to its parent equipment or system."
}
},
"distance":{
"default": null,
"rules": {
"type": "number",
"description": "Defines the position of the measurement relative to its parent equipment or system."
}
}
},
"asset": {

View File

@@ -412,6 +412,14 @@
],
"description": "The frequency at which calculations are performed."
}
},
"flowNumber": {
"default": 1,
"rules": {
"type": "number",
"nullable": false,
"description": "Defines which effluent flow of the parent node to handle."
}
}
}

View File

@@ -11,8 +11,12 @@ class ChildRegistrationUtils {
this.logger.debug(`Registering child: ${name} (${id}) as ${softwareType} at ${positionVsParent}`);
// Enhanced child setup
child.parent = this.mainClass;
// Enhanced child setup - multiple parents
if (Array.isArray(child.parent)) {
child.parent.push(this.mainClass);
} else {
child.parent = [this.mainClass];
}
child.positionVsParent = positionVsParent;
// Enhanced measurement container with rich context

View File

@@ -131,7 +131,6 @@ class Measurement {
const downIndex = downValues.timestamps.indexOf(upTimestamp);
if (downIndex !== -1) {
const diff = upValues.values[i] - downValues.values[downIndex];
diffMeasurement.setValue(diff, upTimestamp);
}

View File

@@ -88,11 +88,18 @@ class MeasurementContainer {
return this;
}
position(positionName) {
position(positionValue) {
if (!this._currentVariant) {
throw new Error('Variant must be specified before position');
}
this._currentPosition = positionName;
// Turn string positions into numeric values
if (typeof positionValue == "string") {
positionValue = this._convertPositionStr2Num(positionValue);
}
this._currentPosition = positionValue;
return this;
}
@@ -243,10 +250,12 @@ class MeasurementContainer {
const savedPosition = this._currentPosition;
// Get upstream and downstream measurements
this._currentPosition = 'upstream';
const positions = this.getPositions();
this._currentPosition = Math.min(...positions);
const upstream = this.get();
this._currentPosition = 'downstream';
this._currentPosition = Math.max(...positions);
const downstream = this.get();
this._currentPosition = savedPosition;
@@ -319,7 +328,7 @@ class MeasurementContainer {
Object.keys(this.measurements[this._currentType]) : [];
}
getPositions() {
getPositions(asNumber = false) {
if (!this._currentType || !this._currentVariant) {
throw new Error('Type and variant must be specified before listing positions');
}
@@ -329,9 +338,13 @@ class MeasurementContainer {
return [];
}
if (asNumber) {
return Object.keys(this.measurements[this._currentType][this._currentVariant]);
}
return Object.keys(this.measurements[this._currentType][this._currentVariant]).map(this._convertPositionNum2Str);
}
clear() {
this.measurements = {};
this._currentType = null;
@@ -404,6 +417,39 @@ class MeasurementContainer {
}
}
_convertPositionStr2Num(positionString) {
switch(positionString) {
case "atEquipment":
return 0;
case "upstream":
return Number.POSITIVE_INFINITY;
case "downstream":
return Number.NEGATIVE_INFINITY;
default:
if (this.logger) {
this.logger.error(`Invalid positionVsParent provided: ${positionString}`);
}
return;
}
}
_convertPositionNum2Str(positionValue) {
if (positionValue === 0) {
return "atEquipment";
}
if (positionValue < 0) {
return "upstream";
}
if (positionValue > 0) {
return "downstream";
}
if (this.logger) {
this.logger.error(`Invalid position provided: ${positionValue}`);
}
}
}
module.exports = MeasurementContainer;

View File

@@ -11,7 +11,25 @@ class PhysicalPositionMenu {
{ value: 'downstream', label: '→ Downstream' , icon: '→' }
]
}
]
],
// Distance contexts for each position
distanceContexts: {
upstream: {
description: 'Distance from parent inlet',
placeholder: 'e.g., 2.5 (meters before parent)',
helpText: 'How far upstream from the parent equipment'
},
downstream: {
description: 'Distance from parent outlet',
placeholder: 'e.g., 3.0 (meters after parent)',
helpText: 'How far downstream from the parent equipment'
},
atEquipment: {
description: 'Distance from parent start',
placeholder: 'e.g., 1.2 (meters from start)',
helpText: 'Position within the parent equipment boundaries'
}
}
};
}
@@ -26,6 +44,24 @@ class PhysicalPositionMenu {
<!-- optgroups will be injected -->
</select>
</div>
<!-- Distance section -->
<div class="form-row">
<label>&nbsp;</label>
<input type="checkbox" id="node-input-hasDistance" style="display:inline-block; width:auto; margin-right:5px;">
<label for="node-input-hasDistance" style="width:auto;">Specify 1D Distance</label>
</div>
<div id="distance-section" class="form-row" style="display:none;">
<label for="node-input-distance"><i class="fa fa-ruler"></i>Distance</label>
<div style="display:flex; align-items:center; width:70%;">
<input type="number" id="node-input-distance" step="0.1" min="0" style="width:60%;" placeholder="0.0">
<span style="margin-left:5px; margin-right:5px;">meters</span>
</div>
<div id="distance-help" class="form-tips" style="margin-left:105px; font-size:11px; color:#666;">
Select a position to see distance context
</div>
</div>
<hr />
`;
}
@@ -52,7 +88,12 @@ class PhysicalPositionMenu {
window.EVOLV.nodes.${nodeName}.positionMenu.loadData = function(node) {
const data = window.EVOLV.nodes.${nodeName}.menuData.position;
const sel = document.getElementById('node-input-positionVsParent');
if (!sel) return;
const hasDistanceCheck = document.getElementById('node-input-hasDistance');
const distanceInput = document.getElementById('node-input-distance');
const distanceSection = document.getElementById('distance-section');
//Load position options
if (sel) {
sel.innerHTML = '';
(data.positionGroups||[]).forEach(grp => {
const optg = document.createElement('optgroup');
@@ -66,8 +107,21 @@ class PhysicalPositionMenu {
});
sel.appendChild(optg);
});
// default to “atEquipment” if not set
sel.value = node.positionVsParent || 'atEquipment';
}
//Load distance values
if (hasDistanceCheck) {
hasDistanceCheck.checked = node.hasDistance || false;
distanceSection.style.display = hasDistanceCheck.checked ? 'block' : 'none';
}
if (distanceInput) {
distanceInput.value = node.distance || '';
}
// Update distance context for current position
this.updateDistanceContext(node.positionVsParent || 'atEquipment', data.distanceContexts);
};
`;
}
@@ -77,7 +131,46 @@ class PhysicalPositionMenu {
return `
// PhysicalPosition events for ${nodeName}
window.EVOLV.nodes.${nodeName}.positionMenu.wireEvents = function(node) {
// no dynamic behavior
const positionSel = document.getElementById('node-input-positionVsParent');
const hasDistanceCheck = document.getElementById('node-input-hasDistance');
const distanceSection = document.getElementById('distance-section');
const data = window.EVOLV.nodes.${nodeName}.menuData.position;
// Toggle distance section visibility
if (hasDistanceCheck && distanceSection) {
hasDistanceCheck.addEventListener('change', function() {
distanceSection.style.display = this.checked ? 'block' : 'none';
// Clear distance if unchecked
if (!this.checked) {
const distanceInput = document.getElementById('node-input-distance');
if (distanceInput) {
distanceInput.value = '';
}
}
});
}
// Update distance context when position changes
if (positionSel) {
positionSel.addEventListener('change', function() {
const position = this.value;
window.EVOLV.nodes.${nodeName}.positionMenu.updateDistanceContext(position, data.distanceContexts);
});
}
};
// Helper function to update distance context
window.EVOLV.nodes.${nodeName}.positionMenu.updateDistanceContext = function(position, contexts) {
const distanceInput = document.getElementById('node-input-distance');
const distanceHelp = document.getElementById('distance-help');
const context = contexts && contexts[position];
if (context && distanceInput && distanceHelp) {
distanceInput.placeholder = context.placeholder || '0.0';
distanceHelp.textContent = context.helpText || 'Enter distance in meters';
}
};
`;
}
@@ -88,9 +181,32 @@ class PhysicalPositionMenu {
// PhysicalPosition Save injection for ${nodeName}
window.EVOLV.nodes.${nodeName}.positionMenu.saveEditor = function(node) {
const sel = document.getElementById('node-input-positionVsParent');
const hasDistanceCheck = document.getElementById('node-input-hasDistance');
const distanceInput = document.getElementById('node-input-distance');
// Save existing position data
node.positionVsParent = sel ? sel.value : 'atEquipment';
node.positionLabel = sel ? sel.options[sel.selectedIndex].textContent : 'At Equipment';
node.positionIcon = sel ? sel.options[sel.selectedIndex].getAttribute('data-icon') : 'fa fa-cog';
// Save distance data (NEW)
node.hasDistance = hasDistanceCheck ? hasDistanceCheck.checked : false;
if (node.hasDistance && distanceInput && distanceInput.value) {
node.distance = parseFloat(distanceInput.value) || 0;
node.distanceUnit = 'm'; // Fixed to meters for now
// Generate distance description based on position
const contexts = window.EVOLV.nodes.${nodeName}.menuData.position.distanceContexts;
const context = contexts && contexts[node.positionVsParent];
node.distanceDescription = context ? context.description : 'Distance from parent';
} else {
// Clear distance data if not specified
delete node.distance;
delete node.distanceUnit;
delete node.distanceDescription;
}
return true;
};
`;