Compare commits

...

34 Commits

Author SHA1 Message Date
2fb73e6713 Remove printing of EventData to prevent console spam 2025-10-10 11:12:38 +02:00
znetsixe
de0b947c56 Updated distance in measurement helper so its a settable compoment. See example.js file in measurement helper folder 2025-10-05 09:34:00 +02:00
Rene De ren
d99561fa80 need to further update measurement emit function 2025-10-03 15:37:08 +02:00
znetsixe
44033da15d Added logging data on menu and distance
Added helper functionality to abort movements in state class and safeguards to NOT be able to abort in protected states.
some caps removal
2025-10-02 17:29:31 +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
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
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
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
12 changed files with 377 additions and 197 deletions

View File

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

View File

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

View File

@@ -91,6 +91,13 @@
], ],
"description": "Defines the position of the measurement relative to its parent equipment or system." "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": { "asset": {

View File

@@ -5,7 +5,7 @@ class ChildRegistrationUtils {
this.registeredChildren = new Map(); this.registeredChildren = new Map();
} }
async registerChild(child, positionVsParent) { async registerChild(child, positionVsParent, distance) {
const { softwareType } = child.config.functionality; const { softwareType } = child.config.functionality;
const { name, id } = child.config.general; const { name, id } = child.config.general;

View File

@@ -2,11 +2,12 @@
const convertModule = require('../convert/index'); const convertModule = require('../convert/index');
class Measurement { class Measurement {
constructor(type, variant, position, windowSize) { constructor(type, variant, position, windowSize, distance = null) {
this.type = type; // e.g. 'pressure', 'flow', etc. this.type = type; // e.g. 'pressure', 'flow', etc.
this.variant = variant; // e.g. 'predicted' or 'measured', etc.. this.variant = variant; // e.g. 'predicted' or 'measured', etc..
this.position = position; // Downstream or upstream of parent object this.position = position; // Downstream or upstream of parent object
this.windowSize = windowSize; // Rolling window size this.windowSize = windowSize; // Rolling window size
this.distance = distance; // Distance from parent, if applicable
// Place all data inside an array // Place all data inside an array
this.values = []; // Array to store all values this.values = []; // Array to store all values
@@ -36,13 +37,12 @@ class Measurement {
return this; return this;
} }
setDistance(distance) {
this.distance = distance;
return this;
}
setValue(value, timestamp = Date.now()) { setValue(value, timestamp = Date.now()) {
/*
if (value === undefined || value === null) {
value = null ;
//throw new Error('Value cannot be null or undefined');
}
*/
//shift the oldest value //shift the oldest value
if(this.values.length >= this.windowSize){ if(this.values.length >= this.windowSize){
@@ -168,7 +168,8 @@ class Measurement {
this.type, this.type,
this.variant, this.variant,
this.position, this.position,
this.windowSize this.windowSize,
this.distance
); );
// Copy values and timestamps // Copy values and timestamps

View File

@@ -5,6 +5,7 @@ class MeasurementBuilder {
this.type = null; this.type = null;
this.variant = null; this.variant = null;
this.position = null; this.position = null;
this.distance = null;
this.windowSize = 10; // Default window size this.windowSize = 10; // Default window size
} }
@@ -32,6 +33,11 @@ class MeasurementBuilder {
return this; return this;
} }
setDistance(distance) {
this.distance = distance;
return this;
}
build() { build() {
// Validate required fields // Validate required fields
if (!this.type) { if (!this.type) {
@@ -43,12 +49,14 @@ class MeasurementBuilder {
if (!this.position) { if (!this.position) {
throw new Error('Measurement position is required'); throw new Error('Measurement position is required');
} }
// distance is not a requirement as it can be derived from position
return new Measurement( return new Measurement(
this.type, this.type,
this.variant, this.variant,
this.position, this.position,
this.windowSize this.windowSize,
this.distance
); );

View File

@@ -3,7 +3,7 @@ const EventEmitter = require('events');
const convertModule = require('../convert/index'); const convertModule = require('../convert/index');
class MeasurementContainer { class MeasurementContainer {
constructor(options = {}) { constructor(options = {},logger) {
this.emitter = new EventEmitter(); this.emitter = new EventEmitter();
this.measurements = {}; this.measurements = {};
this.windowSize = options.windowSize || 10; // Default window size this.windowSize = options.windowSize || 10; // Default window size
@@ -12,6 +12,7 @@ class MeasurementContainer {
this._currentType = null; this._currentType = null;
this._currentVariant = null; this._currentVariant = null;
this._currentPosition = null; this._currentPosition = null;
this._currentDistance = null;
this._unit = null; this._unit = null;
// Default units for each measurement type // Default units for each measurement type
@@ -88,11 +89,24 @@ class MeasurementContainer {
return this; return this;
} }
position(positionName) { position(positionValue) {
if (!this._currentVariant) { if (!this._currentVariant) {
throw new Error('Variant must be specified before position'); throw new Error('Variant must be specified before position');
} }
this._currentPosition = positionName;
this._currentPosition = positionValue;
return this;
}
distance(distance) {
// If distance is not provided, derive from positionVsParent
if(distance === null) {
distance = this._convertPositionStr2Num(this._currentPosition);
}
this._currentDistance = distance;
return this; return this;
} }
@@ -138,17 +152,18 @@ class MeasurementContainer {
sourceUnit: sourceUnit, sourceUnit: sourceUnit,
timestamp, timestamp,
position: this._currentPosition, position: this._currentPosition,
distance: this._currentDistance,
variant: this._currentVariant, variant: this._currentVariant,
type: this._currentType, type: this._currentType,
// NEW: Enhanced context // NEW: Enhanced context
childId: this.childId, childId: this.childId,
childName: this.childName, childName: this.childName,
parentRef: this.parentRef parentRef: this.parentRef,
}; };
// Emit the exact event your parent expects // Emit the exact event your parent expects
this.emitter.emit(`${this._currentType}.${this._currentVariant}.${this._currentPosition}`, eventData); this.emitter.emit(`${this._currentType}.${this._currentVariant}.${this._currentPosition}`, eventData);
//console.log(`Emitted event: ${this._currentType}.${this._currentVariant}.${this._currentPosition}`, eventData); console.log(`Emitted event: ${this._currentType}.${this._currentVariant}.${this._currentPosition}`);
return this; return this;
} }
@@ -236,20 +251,12 @@ class MeasurementContainer {
// Difference calculations between positions // Difference calculations between positions
difference(requestedUnit = null) { difference(requestedUnit = null) {
if (!this._currentType || !this._currentVariant) { if (!this._currentType || !this._currentVariant) {
throw new Error('Type and variant must be specified for difference calculation'); throw new Error('Type and variant must be specified for difference calculation');
} }
const upstream = this.measurements?.[this._currentType]?.[this._currentVariant]?.['upstream'] || null;
const savedPosition = this._currentPosition; const downstream = this.measurements?.[this._currentType]?.[this._currentVariant]?.['downstream'] || null;
// Get upstream and downstream measurements
this._currentPosition = 'upstream';
const upstream = this.get();
this._currentPosition = 'downstream';
const downstream = this.get();
this._currentPosition = savedPosition;
if (!upstream || !downstream || upstream.values.length === 0 || downstream.values.length === 0) { if (!upstream || !downstream || upstream.values.length === 0 || downstream.values.length === 0) {
return null; return null;
@@ -300,6 +307,7 @@ class MeasurementContainer {
.setVariant(this._currentVariant) .setVariant(this._currentVariant)
.setPosition(this._currentPosition) .setPosition(this._currentPosition)
.setWindowSize(this.windowSize) .setWindowSize(this.windowSize)
.setDistance(this._currentDistance)
.build(); .build();
} }
@@ -404,6 +412,36 @@ 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) {
switch (positionValue) {
case 0:
return "atEquipment";
case (positionValue < 0):
return "upstream";
case (positionValue > 0):
return "downstream";
default:
console.log(`Invalid position provided: ${positionValue}`);
}
}
} }
module.exports = MeasurementContainer; module.exports = MeasurementContainer;

View File

@@ -7,249 +7,325 @@ console.log('retrieving, and converting measurement data with automatic unit han
// ==================================== // ====================================
// BASIC SETUP EXAMPLES // BASIC SETUP EXAMPLES
// ==================================== // ====================================
console.log('--- Example 1: Basic Setup & Event Subscription ---'); console.log('--- Example 1: Basic Setup & Distance ---');
// Create a basic container // Create a basic container
const basicContainer = new MeasurementContainer({ windowSize: 20 }); const basicContainer = new MeasurementContainer({ windowSize: 20 });
// Subscribe to flow events to monitor changes // Subscribe to events to monitor changes
basicContainer.emitter.on('flow.predicted.upstream', (data) => { basicContainer.emitter.on('flow.predicted.upstream', (data) => {
console.log(`📡 Event: Flow predicted upstream update: ${data.value} at ${new Date(data.timestamp).toLocaleTimeString()}`); console.log(`📡 Event: Flow predicted upstream = ${data.value} ${data.unit || ''} (distance=${data.distance ?? 'n/a'}m)`);
}); });
//show all flow values from variant measured // Subscribe to all measured flow events using wildcard
basicContainer.emitter.on('flow.measured.*', (data) => { basicContainer.emitter.on('flow.measured.*', (data) => {
console.log(`📡 Event---------- I DID IT: Flow measured ${data.position} update: ${data.value}`) console.log(`📡 Event: Flow measured ${data.position} = ${data.value} ${data.unit || ''} (distance=${data.distance ?? 'n/a'}m)`);
}); });
// Basic value setting with chaining // Basic value setting with distance
console.log('Setting basic pressure values...'); console.log('\nSetting pressure values with distances:');
basicContainer.type('pressure').variant('measured').position('upstream').value(100).unit('psi'); basicContainer
basicContainer.type('pressure').variant('measured').position('downstream').value(95).unit('psi'); .type('pressure')
basicContainer.type('pressure').variant('measured').position('downstream').value(80); // Additional value .variant('measured')
.position('upstream')
.distance(1.5)
.value(100)
.unit('psi');
basicContainer
.type('pressure')
.variant('measured')
.position('downstream')
.distance(5.2)
.value(95)
.unit('psi');
// Distance persists - no need to set it again for same position
basicContainer
.type('pressure')
.variant('measured')
.position('downstream')
.value(90); // distance 5.2 is automatically reused
console.log('✅ Basic setup complete\n'); console.log('✅ Basic setup complete\n');
// Retrieve and display the distance
const upstreamPressure = basicContainer
.type('pressure')
.variant('measured')
.position('upstream')
.get();
console.log(`Retrieved upstream pressure: ${upstreamPressure.getCurrentValue()} ${upstreamPressure.unit}`);
console.log(`Distance from parent: ${upstreamPressure.distance ?? 'not set'} m\n`);
// ==================================== // ====================================
// AUTO-CONVERSION SETUP EXAMPLES // AUTO-CONVERSION SETUP
// ==================================== // ====================================
console.log('--- Example 2: Auto-Conversion Setup ---'); console.log('--- Example 2: Auto-Conversion Setup ---');
console.log('Setting up a container with automatic unit conversion...\n');
// Create container with auto-conversion enabled
const autoContainer = new MeasurementContainer({ const autoContainer = new MeasurementContainer({
autoConvert: true, autoConvert: true,
windowSize: 50, windowSize: 50,
defaultUnits: { defaultUnits: {
pressure: 'bar', // Default pressure unit pressure: 'bar',
flow: 'l/min', // Default flow unit flow: 'l/min',
power: 'kW', // Default power unit power: 'kW',
temperature: 'C' // Default temperature unit temperature: 'C'
}, },
preferredUnits: { preferredUnits: {
pressure: 'psi' // Override: store pressure in PSI instead of bar pressure: 'psi'
} }
}); });
// Values are automatically converted to preferred units // Values automatically convert to preferred units
console.log('Adding pressure data with auto-conversion:'); console.log('Adding pressure with auto-conversion:');
autoContainer.type('pressure').variant('measured').position('upstream') autoContainer
.value(1.5, Date.now(), 'bar'); // Input: 1.5 bar → Auto-stored as ~21.76 psi .type('pressure')
.variant('measured')
.position('upstream')
.distance(0.5)
.value(1.5, Date.now(), 'bar'); // Input: 1.5 bar → Auto-stored as ~21.76 psi
autoContainer.type('pressure').variant('measured').position('downstream') const converted = autoContainer
.value(20, Date.now(), 'psi'); // Input: 20 psi → Stored as 20 psi (already in preferred unit) .type('pressure')
.variant('measured')
.position('upstream')
.get();
// Check what was actually stored console.log(`Stored as: ${converted.getCurrentValue()} ${converted.unit} (distance=${converted.distance}m)`);
const storedPressure = autoContainer.type('pressure').variant('measured').position('upstream').get(); console.log('✅ Auto-conversion complete\n');
console.log(` Stored upstream pressure: ${storedPressure.getCurrentValue()} ${storedPressure.unit}`);
console.log(' Auto-conversion setup complete\n');
// ==================================== // ====================================
// UNIT CONVERSION EXAMPLES // UNIT CONVERSION ON RETRIEVAL
// ==================================== // ====================================
console.log('--- Example 3: Unit Conversion on Retrieval ---'); console.log('--- Example 3: Unit Conversion on Retrieval ---');
console.log('Getting values in different units without changing stored data...\n');
// Add flow data in different units autoContainer
autoContainer.type('flow').variant('predicted').position('upstream') .type('flow')
.value(100, Date.now(), 'l/min'); // Stored in l/min (default) .variant('predicted')
.position('upstream')
.distance(2.4)
.value(100, Date.now(), 'l/min');
autoContainer.type('flow').variant('predicted').position('downstream') const flowMeasurement = autoContainer
.value(6, Date.now(), 'm3/h'); // Auto-converted from m3/h to l/min .type('flow')
.variant('predicted')
.position('upstream')
.get();
// Retrieve the same data in different units console.log(`Flow in l/min: ${flowMeasurement.getCurrentValue('l/min')}`);
const flowLPM = autoContainer.type('flow').variant('predicted').position('upstream').getCurrentValue('l/min'); console.log(`Flow in m³/h: ${flowMeasurement.getCurrentValue('m3/h').toFixed(2)}`);
const flowM3H = autoContainer.type('flow').variant('predicted').position('upstream').getCurrentValue('m3/h'); console.log(`Flow in gal/min: ${flowMeasurement.getCurrentValue('gal/min').toFixed(2)}`);
const flowGPM = autoContainer.type('flow').variant('predicted').position('upstream').getCurrentValue('gal/min'); console.log(`Distance: ${flowMeasurement.distance}m\n`);
console.log(`Flow in l/min: ${flowLPM}`);
console.log(`Flow in m³/h: ${flowM3H.toFixed(2)}`);
console.log(`Flow in gal/min: ${flowGPM.toFixed(2)}`);
console.log('Unit conversion examples complete\n');
// ==================================== // ====================================
// SMART UNIT SELECTION // SMART UNIT SELECTION
// ==================================== // ====================================
console.log('--- Example 4: Smart Unit Selection ---'); console.log('--- Example 4: Smart Unit Selection ---');
console.log('Automatically finding the best unit for readability...\n');
// Add a very small pressure value autoContainer
autoContainer.type('pressure').variant('test').position('sensor') .type('pressure')
.variant('test')
.position('sensor')
.distance(0.2)
.value(0.001, Date.now(), 'bar'); .value(0.001, Date.now(), 'bar');
// Get the best unit for this small value const bestUnit = autoContainer
const bestUnit = autoContainer.type('pressure').variant('test').position('sensor').getBestUnit(); .type('pressure')
.variant('test')
.position('sensor')
.getBestUnit();
if (bestUnit) { if (bestUnit) {
console.log(`Best unit representation: ${bestUnit.val} ${bestUnit.unit}`); console.log(`Best unit: ${bestUnit.val.toFixed(2)} ${bestUnit.unit}`);
} }
// Get all available units for pressure
const availableUnits = autoContainer.getAvailableUnits('pressure'); const availableUnits = autoContainer.getAvailableUnits('pressure');
console.log(`Available pressure units: ${availableUnits.slice(0, 8).join(', ')}... (${availableUnits.length} total)`); console.log(`Available units: ${availableUnits.slice(0, 5).join(', ')}...\n`);
console.log('Smart unit selection complete\n');
// ==================================== // ====================================
// BASIC RETRIEVAL AND CALCULATIONS // BASIC RETRIEVAL
// ==================================== // ====================================
console.log('--- Example 5: Basic Value Retrieval ---'); console.log('--- Example 5: Basic Value Retrieval ---');
console.log('Getting individual values and their units...\n');
// Using basic container for clear examples const upstreamVal = basicContainer
const upstreamValue = basicContainer.type('pressure').variant('measured').position('upstream').getCurrentValue(); .type('pressure')
const upstreamUnit = basicContainer.type('pressure').variant('measured').position('upstream').get().unit; .variant('measured')
console.log(`Upstream pressure: ${upstreamValue} ${upstreamUnit}`); .position('upstream')
.getCurrentValue();
const downstreamValue = basicContainer.type('pressure').variant('measured').position('downstream').getCurrentValue(); const upstreamData = basicContainer
const downstreamUnit = basicContainer.type('pressure').variant('measured').position('downstream').get().unit; .type('pressure')
console.log(`Downstream pressure: ${downstreamValue} ${downstreamUnit}`); .variant('measured')
console.log('Basic retrieval complete\n'); .position('upstream')
.get();
console.log(`Upstream: ${upstreamVal} ${upstreamData.unit} at ${upstreamData.distance}m`);
const downstreamVal = basicContainer
.type('pressure')
.variant('measured')
.position('downstream')
.getCurrentValue();
const downstreamData = basicContainer
.type('pressure')
.variant('measured')
.position('downstream')
.get();
console.log(`Downstream: ${downstreamVal} ${downstreamData.unit} at ${downstreamData.distance}m\n`);
// ==================================== // ====================================
// CALCULATIONS AND STATISTICS // CALCULATIONS & STATISTICS
// ==================================== // ====================================
console.log('--- Example 6: Calculations & Statistics ---'); console.log('--- Example 6: Calculations & Statistics ---');
console.log('Using built-in calculation methods...\n');
// Add flow data for calculations basicContainer
basicContainer.type('flow').variant('predicted').position('upstream').value(200).unit('gpm'); .type('flow')
basicContainer.type('flow').variant('predicted').position('downstream').value(195).unit('gpm'); .variant('predicted')
.position('upstream')
.distance(3.0)
.value(200)
.unit('gpm');
const flowAvg = basicContainer.type('flow').variant('predicted').position('upstream').getAverage(); basicContainer
console.log(`Average upstream flow: ${flowAvg} gpm`); .type('flow')
.variant('predicted')
.position('downstream')
.distance(8.5)
.value(195)
.unit('gpm');
// Calculate pressure difference between upstream and downstream const flowAvg = basicContainer
const pressureDiff = basicContainer.type('pressure').variant('measured').difference(); .type('flow')
console.log(`Pressure difference: ${pressureDiff.value} ${pressureDiff.unit}`); .variant('predicted')
console.log('Calculations complete\n'); .position('upstream')
.getAverage();
console.log(`Average upstream flow: ${flowAvg.toFixed(1)} gpm`);
const pressureDiff = basicContainer
.type('pressure')
.variant('measured')
.difference();
console.log(`Pressure difference: ${pressureDiff.value} ${pressureDiff.unit}\n`);
// ==================================== // ====================================
// ADVANCED STATISTICS // ADVANCED STATISTICS & HISTORY
// ==================================== // ====================================
console.log('--- Example 7: Advanced Statistics & History ---'); console.log('--- Example 7: Advanced Statistics & History ---');
console.log('Adding multiple values and getting comprehensive statistics...\n');
// Add several flow measurements to build history basicContainer
basicContainer.type('flow').variant('measured').position('upstream') .type('flow')
.value(210).value(215).value(205).value(220).value(200).unit('m3/h'); .variant('measured')
basicContainer.type('flow').variant('measured').position('downstream') .position('upstream')
.value(190).value(195).value(185).value(200).value(180).unit('m3/h'); .distance(3.0)
.value(210)
.value(215)
.value(205)
.value(220)
.value(200)
.unit('m3/h');
const stats = basicContainer
.type('flow')
.variant('measured')
.position('upstream');
const statsData = stats.get();
// Get comprehensive statistics
const measurement = basicContainer.type('flow').variant('measured').position('upstream');
console.log('Flow Statistics:'); console.log('Flow Statistics:');
console.log(`- Current value: ${measurement.getCurrentValue()} ${measurement.get().unit}`); console.log(` Current: ${stats.getCurrentValue()} ${statsData.unit}`);
console.log(`- Average: ${measurement.getAverage().toFixed(1)} ${measurement.get().unit}`); console.log(` Average: ${stats.getAverage().toFixed(1)} ${statsData.unit}`);
console.log(`- Minimum: ${measurement.getMin()} ${measurement.get().unit}`); console.log(` Min: ${stats.getMin()} ${statsData.unit}`);
console.log(`- Maximum: ${measurement.getMax()} ${measurement.get().unit}`); console.log(` Max: ${stats.getMax()} ${statsData.unit}`);
console.log(` Distance: ${statsData.distance}m`);
// Show all values with timestamps const allValues = stats.getAllValues();
const allValues = measurement.getAllValues(); console.log(` Samples: ${allValues.values.length}`);
console.log(`- Total samples: ${allValues.values.length}`); console.log(` History: [${allValues.values.join(', ')}]\n`);
console.log(`- Value history: [${allValues.values.join(', ')}]`);
console.log('Advanced statistics complete\n');
// ==================================== // ====================================
// DYNAMIC UNIT MANAGEMENT // DYNAMIC UNIT MANAGEMENT
// ==================================== // ====================================
console.log('--- Example 8: Dynamic Unit Management ---'); console.log('--- Example 8: Dynamic Unit Management ---');
console.log('Changing preferred units at runtime...\n');
// Change preferred unit for flow measurements
autoContainer.setPreferredUnit('flow', 'm3/h'); autoContainer.setPreferredUnit('flow', 'm3/h');
console.log('Changed preferred flow unit to m³/h'); console.log('Changed preferred flow unit to m³/h');
// Add new flow data - will auto-convert to new preferred unit autoContainer
autoContainer.type('flow').variant('realtime').position('inlet') .type('flow')
.value(150, Date.now(), 'l/min'); // Input in l/min, stored as m³/h .variant('realtime')
.position('inlet')
.distance(1.2)
.value(150, Date.now(), 'l/min');
const realtimeFlow = autoContainer.type('flow').variant('realtime').position('inlet'); const realtimeFlow = autoContainer
console.log(`Stored as: ${realtimeFlow.getCurrentValue()} ${realtimeFlow.get().unit}`); .type('flow')
console.log(`Original unit: ${realtimeFlow.getCurrentValue('l/min')} l/min`); .variant('realtime')
console.log('Dynamic unit management complete\n'); .position('inlet')
.get();
console.log(`Stored as: ${realtimeFlow.getCurrentValue()} ${realtimeFlow.unit}`);
console.log(`Original: ${realtimeFlow.getCurrentValue('l/min').toFixed(1)} l/min`);
console.log(`Distance: ${realtimeFlow.distance}m\n`);
// ==================================== // ====================================
// DATA EXPLORATION // DATA EXPLORATION
// ==================================== // ====================================
console.log('--- Example 9: Data Exploration ---'); console.log('--- Example 9: Data Exploration ---');
console.log('Discovering what data is available in the container...\n');
console.log('Available measurement types:', basicContainer.getTypes()); console.log('Available types:', basicContainer.getTypes());
console.log('Pressure variants:', basicContainer.type('pressure').getVariants()); console.log('Pressure variants:', basicContainer.type('pressure').getVariants());
console.log('Measured pressure positions:', basicContainer.type('pressure').variant('measured').getPositions()); console.log('Measured pressure positions:', basicContainer.type('pressure').variant('measured').getPositions());
// Show data structure overview console.log('\nData Structure:');
console.log('\nData Structure Overview:');
basicContainer.getTypes().forEach(type => { basicContainer.getTypes().forEach(type => {
console.log(`${type.toUpperCase()}:`);
const variants = basicContainer.type(type).getVariants(); const variants = basicContainer.type(type).getVariants();
variants.forEach(variant => { if (variants.length > 0) {
const positions = basicContainer.type(type).variant(variant).getPositions(); console.log(`${type.toUpperCase()}:`);
positions.forEach(position => { variants.forEach(variant => {
const measurement = basicContainer.type(type).variant(variant).position(position).get(); const positions = basicContainer.type(type).variant(variant).getPositions();
if (measurement && measurement.values.length > 0) { positions.forEach(position => {
console.log(` └── ${variant}.${position}: ${measurement.values.length} values (${measurement.unit || 'no unit'})`); const m = basicContainer.type(type).variant(variant).position(position).get();
} if (m && m.values.length > 0) {
console.log(` └─ ${variant}.${position}: ${m.values.length} values, ${m.unit || 'no unit'}, dist=${m.distance ?? 'n/a'}m`);
}
});
}); });
}); }
}); });
console.log('Data exploration complete\n');
console.log('\n✅ All examples complete!\n');
// ==================================== // ====================================
// BEST PRACTICES SUMMARY // BEST PRACTICES
// ==================================== // ====================================
console.log('--- Best Practices Summary ---'); console.log('--- Best Practices Summary ---\n');
console.log('BEST PRACTICES FOR NEW USERS:\n');
console.log('1. SETUP:'); console.log('SETUP:');
console.log(' • Enable auto-conversion for consistent units'); console.log(' • Enable autoConvert for consistent units');
console.log(' • Define default units for your measurement types'); console.log(' • Define defaultUnits for your measurement types');
console.log(' • Set appropriate window size for your data needs\n'); console.log(' • Set windowSize based on your data retention needs\n');
console.log('2. STORING DATA:'); console.log('STORING DATA:');
console.log(' Always use the full chain: type().variant().position().value()'); console.log(' • Chain methods: type().variant().position().distance().value()');
console.log(' • Specify source unit when adding values: .value(100, timestamp, "psi")'); console.log(' • Set distance once - it persists for that position');
console.log(' • Set units immediately after first value: .value(100).unit("psi")\n'); console.log(' • Specify source unit: .value(100, timestamp, "psi")');
console.log(' • Set unit immediately: .value(100).unit("psi")\n');
console.log('3. RETRIEVING DATA:'); console.log('RETRIEVING DATA:');
console.log(' • Use .getCurrentValue("unit") to get values in specific units'); console.log(' • Use .getCurrentValue("unit") for specific units');
console.log(' • Use .getBestUnit() for automatic unit selection'); console.log(' • Use .getBestUnit() for automatic selection');
console.log(' • Use .difference() for automatic upstream/downstream calculations\n'); console.log(' • Use .difference() for upstream/downstream deltas');
console.log(' • Access .get().distance for physical positioning\n');
console.log('4. MONITORING:'); console.log('MONITORING:');
console.log(' • Subscribe to events for real-time updates'); console.log(' • Subscribe: .emitter.on("type.variant.position", callback)');
console.log(' Use .emitter.on("type.variant.position", callback)'); console.log(' • Event data includes: value, unit, timestamp, distance');
console.log(' • Explore available data with .getTypes(), .getVariants(), .getPositions()\n'); console.log(' • Explore data: .getTypes(), .getVariants(), .getPositions()\n');
console.log('All examples complete! Ready to use MeasurementContainer'); module.exports = { basicContainer, autoContainer };
// Export for programmatic use
module.exports = {
runExamples: () => {
console.log('Measurement Container Examples - Complete Guide for New Users');
console.log('This file demonstrates all features with practical examples.');
},
// Export containers for testing
basicContainer,
autoContainer
};

View File

@@ -180,19 +180,47 @@ getSaveInjectionCode(nodeName) {
return ` return `
// PhysicalPosition Save injection for ${nodeName} // PhysicalPosition Save injection for ${nodeName}
window.EVOLV.nodes.${nodeName}.positionMenu.saveEditor = function(node) { window.EVOLV.nodes.${nodeName}.positionMenu.saveEditor = function(node) {
console.log("=== PhysicalPosition Save Debug ===");
const sel = document.getElementById('node-input-positionVsParent'); const sel = document.getElementById('node-input-positionVsParent');
const hasDistanceCheck = document.getElementById('node-input-hasDistance'); const hasDistanceCheck = document.getElementById('node-input-hasDistance');
const distanceInput = document.getElementById('node-input-distance'); const distanceInput = document.getElementById('node-input-distance');
// Save existing position data console.log("→ sel element found:", !!sel);
node.positionVsParent = sel ? sel.value : 'atEquipment'; console.log("→ sel:", sel);
node.positionLabel = sel ? sel.options[sel.selectedIndex].textContent : 'At Equipment'; console.log("→ sel.value:", sel ? sel.value : "NO ELEMENT");
node.positionIcon = sel ? sel.options[sel.selectedIndex].getAttribute('data-icon') : 'fa fa-cog'; console.log("→ sel.selectedIndex:", sel ? sel.selectedIndex : "NO ELEMENT");
console.log("→ sel.options:", sel ? Array.from(sel.options).map(o => ({value: o.value, text: o.textContent})) : "NO OPTIONS");
if (!sel) {
console.error("→ positionMenu.saveEditor FAILED: select element not found!");
return false;
}
// Save existing position data
const positionValue = sel.value;
const selectedOption = sel.options[sel.selectedIndex];
console.log("→ positionValue:", positionValue);
console.log("→ selectedOption:", selectedOption);
console.log("→ selectedOption.textContent:", selectedOption ? selectedOption.textContent : "NO OPTION");
console.log("→ selectedOption data-icon:", selectedOption ? selectedOption.getAttribute('data-icon') : "NO ICON");
node.positionVsParent = positionValue || 'atEquipment';
node.positionLabel = selectedOption ? selectedOption.textContent : 'At Equipment';
node.positionIcon = selectedOption ? selectedOption.getAttribute('data-icon') : 'fa fa-cog';
console.log("→ node.positionVsParent set to:", node.positionVsParent);
console.log("→ node.positionLabel set to:", node.positionLabel);
// Save distance data
console.log("→ hasDistanceCheck found:", !!hasDistanceCheck);
console.log("→ hasDistanceCheck.checked:", hasDistanceCheck ? hasDistanceCheck.checked : "NO ELEMENT");
// Save distance data (NEW)
node.hasDistance = hasDistanceCheck ? hasDistanceCheck.checked : false; node.hasDistance = hasDistanceCheck ? hasDistanceCheck.checked : false;
if (node.hasDistance && distanceInput && distanceInput.value) { if (node.hasDistance && distanceInput && distanceInput.value) {
console.log("→ distanceInput.value:", distanceInput.value);
node.distance = parseFloat(distanceInput.value) || 0; node.distance = parseFloat(distanceInput.value) || 0;
node.distanceUnit = 'm'; // Fixed to meters for now node.distanceUnit = 'm'; // Fixed to meters for now
@@ -200,13 +228,18 @@ getSaveInjectionCode(nodeName) {
const contexts = window.EVOLV.nodes.${nodeName}.menuData.position.distanceContexts; const contexts = window.EVOLV.nodes.${nodeName}.menuData.position.distanceContexts;
const context = contexts && contexts[node.positionVsParent]; const context = contexts && contexts[node.positionVsParent];
node.distanceDescription = context ? context.description : 'Distance from parent'; node.distanceDescription = context ? context.description : 'Distance from parent';
console.log("→ distance set to:", node.distance);
} else { } else {
// Clear distance data if not specified console.log("→ clearing distance data");
delete node.distance; delete node.distance;
delete node.distanceUnit; delete node.distanceUnit;
delete node.distanceDescription; delete node.distanceDescription;
} }
console.log("→ positionMenu.saveEditor result: SUCCESS");
console.log("→ final node.positionVsParent:", node.positionVsParent);
return true; return true;
}; };
`; `;

View File

@@ -1,7 +1,7 @@
{ {
"general": { "general": {
"name": { "name": {
"default": "Interpolation Configuration", "default": "interpolation configuration",
"rules": { "rules": {
"type": "string", "type": "string",
"description": "A human-readable name or label for this interpolation configuration." "description": "A human-readable name or label for this interpolation configuration."
@@ -70,7 +70,7 @@
} }
}, },
"role": { "role": {
"default": "Interpolator", "default": "interpolator",
"rules": { "rules": {
"type": "string", "type": "string",
"description": "Indicates the role of this configuration (e.g., 'Interpolator', 'DataCurve', etc.)." "description": "Indicates the role of this configuration (e.g., 'Interpolator', 'DataCurve', etc.)."

View File

@@ -52,6 +52,7 @@ class state{
return this.stateManager.getRunTimeHours(); return this.stateManager.getRunTimeHours();
} }
async moveTo(targetPosition) { async moveTo(targetPosition) {
// Check for invalid conditions and throw errors // Check for invalid conditions and throw errors
@@ -86,14 +87,33 @@ class state{
// -------- State Transition Methods -------- // // -------- State Transition Methods -------- //
abortCurrentMovement(reason = "group override") {
if (this.abortController && !this.abortController.signal.aborted) {
this.logger.warn(`Aborting movement: ${reason}`);
this.abortController.abort();
}
}
async transitionToState(targetState, signal) { async transitionToState(targetState, signal) {
const fromState = this.getCurrentState(); const fromState = this.getCurrentState();
const position = this.getCurrentPosition(); const position = this.getCurrentPosition();
// Define states that cannot be aborted for safety reasons
const protectedStates = ['warmingup', 'coolingdown'];
const isProtectedTransition = protectedStates.includes(fromState);
try { try {
this.logger.debug(`Starting transition from ${fromState} to ${targetState}.`); this.logger.debug(`Starting transition from ${fromState} to ${targetState}.`);
if( isProtectedTransition){
//overrule signal to prevent abortion
signal = null; // Disable abortion for protected states
//spit warning
this.logger.warn(`Transition from ${fromState} to ${targetState} is protected and cannot be aborted.`);
}
// Await the state transition and pass signal for abortion
const feedback = await this.stateManager.transitionTo(targetState,signal); const feedback = await this.stateManager.transitionTo(targetState,signal);
this.logger.info(`Statemanager: ${feedback}`); this.logger.info(`Statemanager: ${feedback}`);
@@ -108,7 +128,6 @@ class state{
//trigger move //trigger move
await this.moveTo(this.delayedMove,signal); await this.moveTo(this.delayedMove,signal);
this.delayedMove = null; this.delayedMove = null;
this.logger.info(`moveTo : ${feedback} `); this.logger.info(`moveTo : ${feedback} `);
} }

View File

@@ -1,7 +1,7 @@
{ {
"general": { "general": {
"name": { "name": {
"default": "State Configuration", "default": "state configuration",
"rules": { "rules": {
"type": "string", "type": "string",
"description": "A human-readable name for the state configuration." "description": "A human-readable name for the state configuration."
@@ -65,7 +65,7 @@
} }
}, },
"role": { "role": {
"default": "StateController", "default": "statecontroller",
"rules": { "rules": {
"type": "string", "type": "string",
"description": "Functional role within the system." "description": "Functional role within the system."