Compare commits

..

5 Commits

11 changed files with 228 additions and 336 deletions

View File

@@ -66,6 +66,15 @@
"units": ["g/m³", "mol/m³"] "units": ["g/m³", "mol/m³"]
} }
] ]
},
{
"name": "Quantity (TSS)",
"models": [
{
"name": "VegaSolidsProbe",
"units": ["g/m³"]
}
]
} }
] ]
} }

View File

@@ -412,6 +412,14 @@
], ],
"description": "The frequency at which calculations are performed." "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

@@ -5,14 +5,18 @@ class ChildRegistrationUtils {
this.registeredChildren = new Map(); this.registeredChildren = new Map();
} }
async registerChild(child, positionVsParent, distance) { async registerChild(child, positionVsParent) {
const { softwareType } = child.config.functionality; const { softwareType } = child.config.functionality;
const { name, id } = child.config.general; const { name, id } = child.config.general;
this.logger.debug(`Registering child: ${name} (${id}) as ${softwareType} at ${positionVsParent}`); this.logger.debug(`Registering child: ${name} (${id}) as ${softwareType} at ${positionVsParent}`);
// Enhanced child setup // Enhanced child setup - multiple parents
child.parent = this.mainClass; if (Array.isArray(child.parent)) {
child.parent.push(this.mainClass);
} else {
child.parent = [this.mainClass];
}
child.positionVsParent = positionVsParent; child.positionVsParent = positionVsParent;
// Enhanced measurement container with rich context // Enhanced measurement container with rich context

View File

@@ -2,12 +2,11 @@
const convertModule = require('../convert/index'); const convertModule = require('../convert/index');
class Measurement { class Measurement {
constructor(type, variant, position, windowSize, distance = null) { constructor(type, variant, position, windowSize) {
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
@@ -37,12 +36,13 @@ 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,8 +168,7 @@ 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,7 +5,6 @@ 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
} }
@@ -33,11 +32,6 @@ 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) {
@@ -49,14 +43,12 @@ 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 = {},logger) { constructor(options = {}) {
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,7 +12,6 @@ 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
@@ -94,22 +93,16 @@ class MeasurementContainer {
throw new Error('Variant must be specified before position'); throw new Error('Variant must be specified before position');
} }
// Turn string positions into numeric values
if (typeof positionValue == "string") {
positionValue = this._convertPositionStr2Num(positionValue);
}
this._currentPosition = positionValue; this._currentPosition = positionValue;
return this; 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;
}
// ENHANCED: Update your existing value method // ENHANCED: Update your existing value method
value(val, timestamp = Date.now(), sourceUnit = null) { value(val, timestamp = Date.now(), sourceUnit = null) {
if (!this._ensureChainIsValid()) return this; if (!this._ensureChainIsValid()) return this;
@@ -152,18 +145,17 @@ 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}`); //console.log(`Emitted event: ${this._currentType}.${this._currentVariant}.${this._currentPosition}`, eventData);
return this; return this;
} }
@@ -251,12 +243,22 @@ 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 downstream = this.measurements?.[this._currentType]?.[this._currentVariant]?.['downstream'] || null; const savedPosition = this._currentPosition;
// Get upstream and downstream measurements
const positions = this.getPositions();
this._currentPosition = Math.min(...positions);
const upstream = this.get();
this._currentPosition = Math.max(...positions);
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;
@@ -307,7 +309,6 @@ 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();
} }
@@ -327,7 +328,7 @@ class MeasurementContainer {
Object.keys(this.measurements[this._currentType]) : []; Object.keys(this.measurements[this._currentType]) : [];
} }
getPositions() { getPositions(asNumber = false) {
if (!this._currentType || !this._currentVariant) { if (!this._currentType || !this._currentVariant) {
throw new Error('Type and variant must be specified before listing positions'); throw new Error('Type and variant must be specified before listing positions');
} }
@@ -337,9 +338,13 @@ class MeasurementContainer {
return []; return [];
} }
if (asNumber) {
return Object.keys(this.measurements[this._currentType][this._currentVariant]); return Object.keys(this.measurements[this._currentType][this._currentVariant]);
} }
return Object.keys(this.measurements[this._currentType][this._currentVariant]).map(this._convertPositionNum2Str);
}
clear() { clear() {
this.measurements = {}; this.measurements = {};
this._currentType = null; this._currentType = null;
@@ -430,15 +435,18 @@ class MeasurementContainer {
} }
_convertPositionNum2Str(positionValue) { _convertPositionNum2Str(positionValue) {
switch (positionValue) { if (positionValue === 0) {
case 0:
return "atEquipment"; return "atEquipment";
case (positionValue < 0): }
if (positionValue < 0) {
return "upstream"; return "upstream";
case (positionValue > 0): }
if (positionValue > 0) {
return "downstream"; return "downstream";
default: }
console.log(`Invalid position provided: ${positionValue}`);
if (this.logger) {
this.logger.error(`Invalid position provided: ${positionValue}`);
} }
} }

View File

@@ -7,325 +7,249 @@ console.log('retrieving, and converting measurement data with automatic unit han
// ==================================== // ====================================
// BASIC SETUP EXAMPLES // BASIC SETUP EXAMPLES
// ==================================== // ====================================
console.log('--- Example 1: Basic Setup & Distance ---'); console.log('--- Example 1: Basic Setup & Event Subscription ---');
// Create a basic container // Create a basic container
const basicContainer = new MeasurementContainer({ windowSize: 20 }); const basicContainer = new MeasurementContainer({ windowSize: 20 });
// Subscribe to events to monitor changes // Subscribe to flow events to monitor changes
basicContainer.emitter.on('flow.predicted.upstream', (data) => { basicContainer.emitter.on('flow.predicted.upstream', (data) => {
console.log(`📡 Event: Flow predicted upstream = ${data.value} ${data.unit || ''} (distance=${data.distance ?? 'n/a'}m)`); console.log(`📡 Event: Flow predicted upstream update: ${data.value} at ${new Date(data.timestamp).toLocaleTimeString()}`);
}); });
// Subscribe to all measured flow events using wildcard //show all flow values from variant measured
basicContainer.emitter.on('flow.measured.*', (data) => { basicContainer.emitter.on('flow.measured.*', (data) => {
console.log(`📡 Event: Flow measured ${data.position} = ${data.value} ${data.unit || ''} (distance=${data.distance ?? 'n/a'}m)`); console.log(`📡 Event---------- I DID IT: Flow measured ${data.position} update: ${data.value}`)
}); });
// Basic value setting with distance // Basic value setting with chaining
console.log('\nSetting pressure values with distances:'); console.log('Setting basic pressure values...');
basicContainer basicContainer.type('pressure').variant('measured').position('upstream').value(100).unit('psi');
.type('pressure') basicContainer.type('pressure').variant('measured').position('downstream').value(95).unit('psi');
.variant('measured') basicContainer.type('pressure').variant('measured').position('downstream').value(80); // Additional value
.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 // AUTO-CONVERSION SETUP EXAMPLES
// ==================================== // ====================================
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', pressure: 'bar', // Default pressure unit
flow: 'l/min', flow: 'l/min', // Default flow unit
power: 'kW', power: 'kW', // Default power unit
temperature: 'C' temperature: 'C' // Default temperature unit
}, },
preferredUnits: { preferredUnits: {
pressure: 'psi' pressure: 'psi' // Override: store pressure in PSI instead of bar
} }
}); });
// Values automatically convert to preferred units // Values are automatically converted to preferred units
console.log('Adding pressure with auto-conversion:'); console.log('Adding pressure data with auto-conversion:');
autoContainer autoContainer.type('pressure').variant('measured').position('upstream')
.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 .value(1.5, Date.now(), 'bar'); // Input: 1.5 bar → Auto-stored as ~21.76 psi
const converted = autoContainer autoContainer.type('pressure').variant('measured').position('downstream')
.type('pressure') .value(20, Date.now(), 'psi'); // Input: 20 psi → Stored as 20 psi (already in preferred unit)
.variant('measured')
.position('upstream')
.get();
console.log(`Stored as: ${converted.getCurrentValue()} ${converted.unit} (distance=${converted.distance}m)`); // Check what was actually stored
console.log('✅ Auto-conversion complete\n'); const storedPressure = autoContainer.type('pressure').variant('measured').position('upstream').get();
console.log(` Stored upstream pressure: ${storedPressure.getCurrentValue()} ${storedPressure.unit}`);
console.log(' Auto-conversion setup complete\n');
// ==================================== // ====================================
// UNIT CONVERSION ON RETRIEVAL // UNIT CONVERSION EXAMPLES
// ==================================== // ====================================
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');
autoContainer // Add flow data in different units
.type('flow') autoContainer.type('flow').variant('predicted').position('upstream')
.variant('predicted') .value(100, Date.now(), 'l/min'); // Stored in l/min (default)
.position('upstream')
.distance(2.4)
.value(100, Date.now(), 'l/min');
const flowMeasurement = autoContainer autoContainer.type('flow').variant('predicted').position('downstream')
.type('flow') .value(6, Date.now(), 'm3/h'); // Auto-converted from m3/h to l/min
.variant('predicted')
.position('upstream')
.get();
console.log(`Flow in l/min: ${flowMeasurement.getCurrentValue('l/min')}`); // Retrieve the same data in different units
console.log(`Flow in m³/h: ${flowMeasurement.getCurrentValue('m3/h').toFixed(2)}`); const flowLPM = autoContainer.type('flow').variant('predicted').position('upstream').getCurrentValue('l/min');
console.log(`Flow in gal/min: ${flowMeasurement.getCurrentValue('gal/min').toFixed(2)}`); const flowM3H = autoContainer.type('flow').variant('predicted').position('upstream').getCurrentValue('m3/h');
console.log(`Distance: ${flowMeasurement.distance}m\n`); const flowGPM = autoContainer.type('flow').variant('predicted').position('upstream').getCurrentValue('gal/min');
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');
autoContainer // Add a very small pressure value
.type('pressure') autoContainer.type('pressure').variant('test').position('sensor')
.variant('test')
.position('sensor')
.distance(0.2)
.value(0.001, Date.now(), 'bar'); .value(0.001, Date.now(), 'bar');
const bestUnit = autoContainer // Get the best unit for this small value
.type('pressure') const bestUnit = autoContainer.type('pressure').variant('test').position('sensor').getBestUnit();
.variant('test')
.position('sensor')
.getBestUnit();
if (bestUnit) { if (bestUnit) {
console.log(`Best unit: ${bestUnit.val.toFixed(2)} ${bestUnit.unit}`); console.log(`Best unit representation: ${bestUnit.val} ${bestUnit.unit}`);
} }
// Get all available units for pressure
const availableUnits = autoContainer.getAvailableUnits('pressure'); const availableUnits = autoContainer.getAvailableUnits('pressure');
console.log(`Available units: ${availableUnits.slice(0, 5).join(', ')}...\n`); console.log(`Available pressure units: ${availableUnits.slice(0, 8).join(', ')}... (${availableUnits.length} total)`);
console.log('Smart unit selection complete\n');
// ==================================== // ====================================
// BASIC RETRIEVAL // BASIC RETRIEVAL AND CALCULATIONS
// ==================================== // ====================================
console.log('--- Example 5: Basic Value Retrieval ---'); console.log('--- Example 5: Basic Value Retrieval ---');
console.log('Getting individual values and their units...\n');
const upstreamVal = basicContainer // Using basic container for clear examples
.type('pressure') const upstreamValue = basicContainer.type('pressure').variant('measured').position('upstream').getCurrentValue();
.variant('measured') const upstreamUnit = basicContainer.type('pressure').variant('measured').position('upstream').get().unit;
.position('upstream') console.log(`Upstream pressure: ${upstreamValue} ${upstreamUnit}`);
.getCurrentValue();
const upstreamData = basicContainer const downstreamValue = basicContainer.type('pressure').variant('measured').position('downstream').getCurrentValue();
.type('pressure') const downstreamUnit = basicContainer.type('pressure').variant('measured').position('downstream').get().unit;
.variant('measured') console.log(`Downstream pressure: ${downstreamValue} ${downstreamUnit}`);
.position('upstream') console.log('Basic retrieval complete\n');
.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 & STATISTICS // CALCULATIONS AND STATISTICS
// ==================================== // ====================================
console.log('--- Example 6: Calculations & Statistics ---'); console.log('--- Example 6: Calculations & Statistics ---');
console.log('Using built-in calculation methods...\n');
basicContainer // Add flow data for calculations
.type('flow') basicContainer.type('flow').variant('predicted').position('upstream').value(200).unit('gpm');
.variant('predicted') basicContainer.type('flow').variant('predicted').position('downstream').value(195).unit('gpm');
.position('upstream')
.distance(3.0)
.value(200)
.unit('gpm');
basicContainer const flowAvg = basicContainer.type('flow').variant('predicted').position('upstream').getAverage();
.type('flow') console.log(`Average upstream flow: ${flowAvg} gpm`);
.variant('predicted')
.position('downstream')
.distance(8.5)
.value(195)
.unit('gpm');
const flowAvg = basicContainer // Calculate pressure difference between upstream and downstream
.type('flow') const pressureDiff = basicContainer.type('pressure').variant('measured').difference();
.variant('predicted') console.log(`Pressure difference: ${pressureDiff.value} ${pressureDiff.unit}`);
.position('upstream') console.log('Calculations complete\n');
.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 & HISTORY // ADVANCED STATISTICS
// ==================================== // ====================================
console.log('--- Example 7: Advanced Statistics & History ---'); console.log('--- Example 7: Advanced Statistics & History ---');
console.log('Adding multiple values and getting comprehensive statistics...\n');
basicContainer // Add several flow measurements to build history
.type('flow') basicContainer.type('flow').variant('measured').position('upstream')
.variant('measured') .value(210).value(215).value(205).value(220).value(200).unit('m3/h');
.position('upstream') basicContainer.type('flow').variant('measured').position('downstream')
.distance(3.0) .value(190).value(195).value(185).value(200).value(180).unit('m3/h');
.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: ${stats.getCurrentValue()} ${statsData.unit}`); console.log(`- Current value: ${measurement.getCurrentValue()} ${measurement.get().unit}`);
console.log(` Average: ${stats.getAverage().toFixed(1)} ${statsData.unit}`); console.log(`- Average: ${measurement.getAverage().toFixed(1)} ${measurement.get().unit}`);
console.log(` Min: ${stats.getMin()} ${statsData.unit}`); console.log(`- Minimum: ${measurement.getMin()} ${measurement.get().unit}`);
console.log(` Max: ${stats.getMax()} ${statsData.unit}`); console.log(`- Maximum: ${measurement.getMax()} ${measurement.get().unit}`);
console.log(` Distance: ${statsData.distance}m`);
const allValues = stats.getAllValues(); // Show all values with timestamps
console.log(` Samples: ${allValues.values.length}`); const allValues = measurement.getAllValues();
console.log(` History: [${allValues.values.join(', ')}]\n`); console.log(`- Total samples: ${allValues.values.length}`);
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');
autoContainer // Add new flow data - will auto-convert to new preferred unit
.type('flow') autoContainer.type('flow').variant('realtime').position('inlet')
.variant('realtime') .value(150, Date.now(), 'l/min'); // Input in l/min, stored as m³/h
.position('inlet')
.distance(1.2)
.value(150, Date.now(), 'l/min');
const realtimeFlow = autoContainer const realtimeFlow = autoContainer.type('flow').variant('realtime').position('inlet');
.type('flow') console.log(`Stored as: ${realtimeFlow.getCurrentValue()} ${realtimeFlow.get().unit}`);
.variant('realtime') console.log(`Original unit: ${realtimeFlow.getCurrentValue('l/min')} l/min`);
.position('inlet') console.log('Dynamic unit management complete\n');
.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 types:', basicContainer.getTypes()); console.log('Available measurement 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());
console.log('\nData Structure:'); // Show data structure overview
console.log('\nData Structure Overview:');
basicContainer.getTypes().forEach(type => { basicContainer.getTypes().forEach(type => {
const variants = basicContainer.type(type).getVariants();
if (variants.length > 0) {
console.log(`${type.toUpperCase()}:`); console.log(`${type.toUpperCase()}:`);
const variants = basicContainer.type(type).getVariants();
variants.forEach(variant => { variants.forEach(variant => {
const positions = basicContainer.type(type).variant(variant).getPositions(); const positions = basicContainer.type(type).variant(variant).getPositions();
positions.forEach(position => { positions.forEach(position => {
const m = basicContainer.type(type).variant(variant).position(position).get(); const measurement = basicContainer.type(type).variant(variant).position(position).get();
if (m && m.values.length > 0) { if (measurement && measurement.values.length > 0) {
console.log(` ${variant}.${position}: ${m.values.length} values, ${m.unit || 'no unit'}, dist=${m.distance ?? 'n/a'}m`); console.log(` └─${variant}.${position}: ${measurement.values.length} values (${measurement.unit || 'no unit'})`);
} }
}); });
}); });
}
}); });
console.log('Data exploration complete\n');
console.log('\n✅ All examples complete!\n');
// ==================================== // ====================================
// BEST PRACTICES // BEST PRACTICES SUMMARY
// ==================================== // ====================================
console.log('--- Best Practices Summary ---\n'); console.log('--- Best Practices Summary ---');
console.log('BEST PRACTICES FOR NEW USERS:\n');
console.log('SETUP:'); console.log('1. SETUP:');
console.log(' • Enable autoConvert for consistent units'); console.log(' • Enable auto-conversion for consistent units');
console.log(' • Define defaultUnits for your measurement types'); console.log(' • Define default units for your measurement types');
console.log(' • Set windowSize based on your data retention needs\n'); console.log(' • Set appropriate window size for your data needs\n');
console.log('STORING DATA:'); console.log('2. STORING DATA:');
console.log(' • Chain methods: type().variant().position().distance().value()'); console.log(' Always use the full chain: type().variant().position().value()');
console.log(' • Set distance once - it persists for that position'); console.log(' • Specify source unit when adding values: .value(100, timestamp, "psi")');
console.log(' • Specify source unit: .value(100, timestamp, "psi")'); console.log(' • Set units immediately after first value: .value(100).unit("psi")\n');
console.log(' • Set unit immediately: .value(100).unit("psi")\n');
console.log('RETRIEVING DATA:'); console.log('3. RETRIEVING DATA:');
console.log(' • Use .getCurrentValue("unit") for specific units'); console.log(' • Use .getCurrentValue("unit") to get values in specific units');
console.log(' • Use .getBestUnit() for automatic selection'); console.log(' • Use .getBestUnit() for automatic unit selection');
console.log(' • Use .difference() for upstream/downstream deltas'); console.log(' • Use .difference() for automatic upstream/downstream calculations\n');
console.log(' • Access .get().distance for physical positioning\n');
console.log('MONITORING:'); console.log('4. MONITORING:');
console.log(' • Subscribe: .emitter.on("type.variant.position", callback)'); console.log(' • Subscribe to events for real-time updates');
console.log(' • Event data includes: value, unit, timestamp, distance'); console.log(' Use .emitter.on("type.variant.position", callback)');
console.log(' • Explore data: .getTypes(), .getVariants(), .getPositions()\n'); console.log(' • Explore available data with .getTypes(), .getVariants(), .getPositions()\n');
module.exports = { basicContainer, autoContainer }; console.log('All examples complete! Ready to use MeasurementContainer');
// 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,47 +180,19 @@ 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');
console.log("→ sel element found:", !!sel);
console.log("→ sel:", sel);
console.log("→ sel.value:", sel ? sel.value : "NO ELEMENT");
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 // Save existing position data
const positionValue = sel.value; node.positionVsParent = sel ? sel.value : 'atEquipment';
const selectedOption = sel.options[sel.selectedIndex]; node.positionLabel = sel ? sel.options[sel.selectedIndex].textContent : 'At Equipment';
node.positionIcon = sel ? sel.options[sel.selectedIndex].getAttribute('data-icon') : 'fa fa-cog';
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
@@ -228,18 +200,13 @@ 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 {
console.log("→ clearing distance data"); // Clear distance data if not specified
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,7 +52,6 @@ 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
@@ -87,33 +86,14 @@ 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}`);
@@ -128,6 +108,7 @@ 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."