2025-10-07 18:05:54 +02:00
const EventEmitter = require ( 'events' ) ;
const { logger , configUtils , configManager , MeasurementContainer , coolprop } = require ( 'generalFunctions' ) ;
2025-10-14 08:36:45 +02:00
class pumpingStation {
2025-10-07 18:05:54 +02:00
constructor ( config = { } ) {
this . emitter = new EventEmitter ( ) ; // Own EventEmitter
this . configManager = new configManager ( ) ;
2025-10-14 08:36:45 +02:00
this . defaultConfig = this . configManager . getConfig ( 'pumpingStation' ) ;
2025-10-07 18:05:54 +02:00
this . configUtils = new configUtils ( this . defaultConfig ) ;
this . config = this . configUtils . initConfig ( config ) ;
// Init after config is set
this . logger = new logger ( this . config . general . logging . enabled , this . config . general . logging . logLevel , this . config . general . name ) ;
// General properties
this . measurements = new MeasurementContainer ( {
autoConvert : true ,
windowSize : this . config . smoothing . smoothWindow
} ) ;
2025-10-14 08:36:45 +02:00
// pumpingStation-specific properties
2025-10-07 18:05:54 +02:00
this . flowrate = null ; // Function to calculate flow rate based on water level rise or fall
this . timeBeforeOverflow = null ; // Time before the basin overflows at current inflow rate
this . timeBeforeEmpty = null ; // Time before the basin empties at current outflow rate
this . heightInlet = null ; // Height of the inlet pipe from the bottom of the basin
this . heightOutlet = null ; // Height of the outlet pipe from the bottom of the basin
this . heightOverflow = null ; // Height of the overflow point from the bottom of the basin
this . volume = null ; // Total volume of water in the basin, calculated from water level and basin dimensions
this . emptyVolume = null ; // Volume in the basin when empty (at level of outlet pipe)
this . fullVolume = null ; // Volume in the basin when at level of overflow point
this . crossSectionalArea = null ; // Cross-sectional area of the basin, used to calculate volume from water level
// Initialize basin-specific properties from config
this . initBasinProperties ( ) ;
}
/*------------------- Register child events -------------------*/
registerChild ( child , softwareType ) {
this . logger . debug ( 'Setting up child event for softwaretype ' + softwareType ) ;
if ( softwareType === "measurement" ) {
const position = child . config . functionality . positionVsParent ;
const distance = child . config . functionality . distanceVsParent || 0 ;
const measurementType = child . config . asset . type ;
const key = ` ${ measurementType } _ ${ position } ` ;
//rebuild to measurementype.variant no position and then switch based on values not strings or names.
const eventName = ` ${ measurementType } .measured. ${ position } ` ;
this . logger . debug ( ` Setting up listener for ${ eventName } from child ${ child . config . general . name } ` ) ;
// Register event listener for measurement updates
child . measurements . emitter . on ( eventName , ( eventData ) => {
this . logger . debug ( ` 🔄 ${ position } ${ measurementType } from ${ eventData . childName } : ${ eventData . value } ${ eventData . unit } ` ) ;
console . log ( ` Emitting... ${ eventName } with data: ` ) ;
// Store directly in parent's measurement container
this . measurements
. type ( measurementType )
. variant ( "measured" )
. position ( position )
. value ( eventData . value , eventData . timestamp , eventData . unit ) ;
// Call the appropriate handler
this . _callMeasurementHandler ( measurementType , eventData . value , position , eventData ) ;
} ) ;
}
}
_callMeasurementHandler ( measurementType , value , position , context ) {
switch ( measurementType ) {
case 'pressure' :
this . updateMeasuredPressure ( value , position , context ) ;
break ;
case 'flow' :
this . updateMeasuredFlow ( value , position , context ) ;
break ;
case 'temperature' :
this . updateMeasuredTemperature ( value , position , context ) ;
break ;
case 'level' :
this . updateMeasuredLevel ( value , position , context ) ;
break ;
default :
this . logger . warn ( ` No handler for measurement type: ${ measurementType } ` ) ;
// Generic handler - just update position
this . updatePosition ( ) ;
break ;
}
}
// context handler for pressure updates
updateMeasuredPressure ( value , position , context = { } ) {
let kelvinTemp = null ;
//pressure updates come from pressure boxes inside the basin they get converted to a level and stored as level measured at position inlet or outlet
this . logger . debug ( ` Pressure update: ${ value } at ${ position } from ${ context . childName || 'child' } ( ${ context . childId || 'unknown-id' } ) ` ) ;
// Store in parent's measurement container for the first time
this . measurements . type ( "pressure" ) . variant ( "measured" ) . position ( position ) . value ( value , context . timestamp , context . unit ) ;
//convert pressure to level based on density of water and height of pressure sensor
const mTemp = this . measurements . type ( "temperature" ) . variant ( "measured" ) . position ( "atEquipment" ) . getCurrentValue ( 'K' ) ; //default to 20C if no temperature measurement
//prefer measured temp but otherwise assume nominal temp for wastewater
if ( mTemp === null ) {
this . logger . warn ( ` No temperature measurement available, defaulting to 15C for pressure to level conversion. ` ) ;
this . measurements . type ( "temperature" ) . variant ( "assumed" ) . position ( "atEquipment" ) . value ( 15 , Date . now ( ) , "C" ) ;
kelvinTemp = this . measurements . type ( 'temperature' ) . variant ( 'assumed' ) . position ( 'atEquipment' ) . getCurrentValue ( 'K' ) ;
} else {
kelvinTemp = mTemp ;
}
this . logger . debug ( ` Using temperature: ${ kelvinTemp } K for calculations ` ) ;
const density = coolprop . PropsSI ( 'D' , 'T' , kelvinTemp , 'P' , 101325 , 'Water' ) ; //density in kg/m3 at temp and surface pressure
const g =
//calculate how muc flow went in or out based on pressure difference
this . logger . debug ( ` Using pressure: ${ pressure } for calculations ` ) ;
}
initBasinProperties ( ) {
// Initialize basin-specific properties from config
this . heightInlet = this . config . basin . heightInlet || 0 ; // Default to 0 if not specified
this . heightOutlet = this . config . basin . heightOutlet || 0 ; // Default to 0 if not specified
this . heightOverflow = this . config . basin . heightOverflow || 0 ; // Default to 0 if not specified
this . crossSectionalArea = this . config . basin . crossSectionalArea || 1 ; // Default to 1 m² if not specified
}
getOutput ( ) {
return {
volume : this . volume ,
} ;
}
}
2025-10-14 13:51:32 +02:00
module . exports = pumpingStation ;
2025-10-07 18:05:54 +02:00
/ *
// */
( async ( ) => {
const PropsSI = await coolprop . getPropsSI ( ) ;
// 👇 replace these with your real inputs
const tC _input = 25 ; // °C
const pPa _input = 101325 ; // Pa
// Sanitize & convert
const T = Number ( tC _input ) + 273.15 ; // K
const P = Number ( pPa _input ) ; // Pa
const fluid = 'Water' ;
// Preconditions
if ( ! Number . isFinite ( T ) || ! Number . isFinite ( P ) ) {
throw new Error ( ` Bad inputs: T= ${ T } K, P= ${ P } Pa ` ) ;
}
if ( T <= 0 ) throw new Error ( ` Temperature must be in Kelvin (>0). Got ${ T } . ` ) ;
if ( P <= 0 ) throw new Error ( ` Pressure must be >0 Pa. Got ${ P } . ` ) ;
// Try T,P order
let rho = PropsSI ( 'D' , 'T' , T , 'P' , P , fluid ) ;
// Fallback: P,T order (should be equivalent)
if ( ! Number . isFinite ( rho ) ) rho = PropsSI ( 'D' , 'P' , P , 'T' , T , fluid ) ;
console . log ( { T , P , rho } ) ;
if ( ! Number . isFinite ( rho ) ) {
console . error ( 'Still Infinity. Extra checks:' ) ;
console . error ( 'typeof T:' , typeof T , 'typeof P:' , typeof P ) ;
console . error ( 'Example known-good call:' , PropsSI ( 'D' , 'T' , 298.15 , 'P' , 101325 , 'Water' ) ) ;
}
} ) ( ) ;