2025-06-23 13:23:51 +02:00
/ * *
* measurement . class . js
*
2025-06-24 10:48:40 +02:00
* Encapsulates all node logic in a reusable class . In future updates we can split this into multiple generic classes and use the config to specifiy which ones to use .
* This allows us to keep the Node - RED node clean and focused on wiring up the UI and event handlers .
2025-06-23 13:23:51 +02:00
* /
const { outputUtils , configManager } = require ( 'generalFunctions' ) ;
2025-06-25 11:45:32 +02:00
const Measurement = require ( "./specificClass" ) ;
2025-06-23 13:23:51 +02:00
/ * *
* Class representing a Measurement Node - RED node .
* /
class MeasurementNode {
/ * *
* Create a MeasurementNode .
2025-06-24 10:48:40 +02:00
* @ param { object } uiConfig - Node - RED node configuration .
2025-06-23 13:23:51 +02:00
* @ param { object } RED - Node - RED runtime API .
* /
2025-06-24 10:48:40 +02:00
constructor ( uiConfig , RED , nodeInstance ) {
2025-06-23 13:23:51 +02:00
// Preserve RED reference for HTTP endpoints if needed
this . node = nodeInstance ;
this . RED = RED ;
// Load default & UI config
2025-06-25 11:45:32 +02:00
this . _loadConfig ( uiConfig , this . node ) ;
2025-06-23 13:23:51 +02:00
// Instantiate core Measurement class
this . _setupMeasurementClass ( ) ;
// Wire up event and lifecycle handlers
this . _bindEvents ( ) ;
this . _registerChild ( ) ;
this . _startTickLoop ( ) ;
this . _attachInputHandler ( ) ;
this . _attachCloseHandler ( ) ;
}
/ * *
* Load and merge default config with user - defined settings .
* @ param { object } uiConfig - Raw config from Node - RED UI .
* /
2025-06-25 11:45:32 +02:00
_loadConfig ( uiConfig , node ) {
2025-06-23 13:23:51 +02:00
const cfgMgr = new configManager ( ) ;
this . defaultConfig = cfgMgr . getConfig ( "measurement" ) ;
2025-06-25 11:45:32 +02:00
console . log ( uiConfig . positionVsParent ) ;
2025-06-23 13:23:51 +02:00
// Merge UI config over defaults
this . config = {
general : {
name : uiConfig . name ,
2025-06-25 14:52:20 +02:00
id : node . id , // node.id is for the child registration process
2025-06-25 10:43:15 +02:00
unit : uiConfig . unit , // add converter options later to convert to default units (need like a model that defines this which units we are going to use and then conver to those standards)
2025-06-23 13:23:51 +02:00
logging : {
enabled : uiConfig . enableLog ,
logLevel : uiConfig . logLevel
}
} ,
asset : {
2025-06-25 14:52:20 +02:00
uuid : uiConfig . assetUuid , //need to add this later to the asset model
2025-06-25 10:43:15 +02:00
tagCode : uiConfig . assetTagCode , //need to add this later to the asset model
2025-06-23 13:23:51 +02:00
supplier : uiConfig . supplier ,
2025-06-25 10:43:15 +02:00
category : uiConfig . category , //add later to define as the software type
2025-06-24 10:48:40 +02:00
type : uiConfig . assetType ,
model : uiConfig . model ,
unit : uiConfig . unit
2025-06-23 13:23:51 +02:00
} ,
scaling : {
enabled : uiConfig . scaling ,
inputMin : uiConfig . i _min ,
inputMax : uiConfig . i _max ,
absMin : uiConfig . o _min ,
absMax : uiConfig . o _max ,
offset : uiConfig . i _offset
} ,
smoothing : {
smoothWindow : uiConfig . count ,
smoothMethod : uiConfig . smooth _method
} ,
simulation : {
enabled : uiConfig . simulator
2025-06-25 10:43:15 +02:00
} ,
2025-06-25 11:45:32 +02:00
functionality : {
positionVsParent : uiConfig . positionVsParent || 'atEquipment' , // Default to 'atEquipment' if not specified
}
2025-06-23 13:23:51 +02:00
} ;
// Utility for formatting outputs
this . _output = new outputUtils ( ) ;
}
/ * *
* Instantiate the core Measurement logic and store as source .
* /
_setupMeasurementClass ( ) {
this . source = new Measurement ( this . config ) ;
}
/ * *
2025-06-25 10:43:15 +02:00
* Bind Measurement events to Node - RED status updates . Using internal emitter . -- > REMOVE LATER WE NEED ONLY COMPLETE CHILDS AND THEN CHECK FOR UPDATES
2025-06-23 13:23:51 +02:00
* /
_bindEvents ( ) {
this . source . emitter . on ( 'mAbs' , ( val ) => {
this . node . status ( { fill : 'green' , shape : 'dot' , text : ` ${ val } ${ this . config . general . unit } ` } ) ;
} ) ;
}
/ * *
* Register this node as a child upstream and downstream .
* Delayed to avoid Node - RED startup race conditions .
* /
_registerChild ( ) {
setTimeout ( ( ) => {
this . node . send ( [
null ,
null ,
2025-06-25 11:45:32 +02:00
{ topic : 'registerChild' , payload : this . config . general . id , positionVsParent : this . config ? . functionality ? . positionVsParent || 'atEquipment' } ,
2025-06-23 13:23:51 +02:00
] ) ;
} , 100 ) ;
}
/ * *
* Start the periodic tick loop to drive the Measurement class .
* /
_startTickLoop ( ) {
setTimeout ( ( ) => {
this . _tickInterval = setInterval ( ( ) => this . _tick ( ) , 1000 ) ;
} , 1000 ) ;
}
/ * *
* Execute a single tick : update measurement , format and send outputs .
* /
_tick ( ) {
this . source . tick ( ) ;
const raw = this . source . getOutput ( ) ;
const processMsg = this . _output . formatMsg ( raw , this . config , 'process' ) ;
const influxMsg = this . _output . formatMsg ( raw , this . config , 'influxdb' ) ;
// Send only updated outputs on ports 0 & 1
this . node . send ( [ processMsg , influxMsg ] ) ;
}
/ * *
* Attach the node ' s input handler , routing control messages to the Measurement class .
* /
_attachInputHandler ( ) {
this . node . on ( 'input' , ( msg , send , done ) => {
switch ( msg . topic ) {
case 'simulator' : this . source . toggleSimulation ( ) ; break ;
case 'outlierDetection' : this . source . toggleOutlierDetection ( ) ; break ;
case 'calibrate' : this . source . calibrate ( ) ; break ;
case 'measurement' :
if ( typeof msg . payload === 'number' ) {
this . source . inputValue = parseFloat ( msg . payload ) ;
}
break ;
}
done ( ) ;
} ) ;
}
/ * *
* Clean up timers and intervals when Node - RED stops the node .
* /
_attachCloseHandler ( ) {
this . node . on ( 'close' , ( done ) => {
clearInterval ( this . _tickInterval ) ;
done ( ) ;
} ) ;
}
}
module . exports = MeasurementNode ;