|
|
|
|
@@ -1,6 +1,5 @@
|
|
|
|
|
const EventEmitter = require('events');
|
|
|
|
|
const {loadCurve,logger,configUtils,configManager,state, nrmse, MeasurementContainer, predict, interpolation , childRegistrationUtils} = require('generalFunctions');
|
|
|
|
|
const { name } = require('../../generalFunctions/src/convert/lodash/lodash._shimkeys');
|
|
|
|
|
const {loadCurve,gravity,logger,configUtils,configManager,state, nrmse, MeasurementContainer, predict, interpolation , childRegistrationUtils,coolprop} = require('generalFunctions');
|
|
|
|
|
|
|
|
|
|
class Machine {
|
|
|
|
|
|
|
|
|
|
@@ -17,7 +16,7 @@ class Machine {
|
|
|
|
|
|
|
|
|
|
// Load a specific curve
|
|
|
|
|
this.model = machineConfig.asset.model; // Get the model from the machineConfig
|
|
|
|
|
this.curve = this.model ? loadCurve(this.model) : null;
|
|
|
|
|
this.curve = this.model ? loadCurve(this.model) : null; // we need to convert the curve and add units to the curve information
|
|
|
|
|
|
|
|
|
|
//Init config and check if it is valid
|
|
|
|
|
this.config = this.configUtils.initConfig(machineConfig);
|
|
|
|
|
@@ -35,10 +34,8 @@ class Machine {
|
|
|
|
|
}
|
|
|
|
|
else{
|
|
|
|
|
this.hasCurve = true;
|
|
|
|
|
this.config = this.configUtils.updateConfig(this.config, {
|
|
|
|
|
asset: { ...this.config.asset, machineCurve: this.curve }
|
|
|
|
|
});
|
|
|
|
|
machineConfig = { ...machineConfig, asset: { ...machineConfig.asset, machineCurve: this.curve } }; // Merge curve into machineConfig
|
|
|
|
|
this.config = this.configUtils.updateConfig(this.config, { asset: { ...this.config.asset, machineCurve: this.curve } });
|
|
|
|
|
//machineConfig = { ...machineConfig, asset: { ...machineConfig.asset, machineCurve: this.curve } }; // Merge curve into machineConfig
|
|
|
|
|
this.predictFlow = new predict({ curve: this.config.asset.machineCurve.nq }); // load nq (x : ctrl , y : flow relationship)
|
|
|
|
|
this.predictPower = new predict({ curve: this.config.asset.machineCurve.np }); // load np (x : ctrl , y : power relationship)
|
|
|
|
|
this.predictCtrl = new predict({ curve: this.reverseCurve(this.config.asset.machineCurve.nq) }); // load reversed nq (x: flow, y: ctrl relationship)
|
|
|
|
|
@@ -48,7 +45,17 @@ class Machine {
|
|
|
|
|
this.errorMetrics = new nrmse(errorMetricsConfig, this.logger);
|
|
|
|
|
|
|
|
|
|
// Initialize measurements
|
|
|
|
|
this.measurements = new MeasurementContainer();
|
|
|
|
|
this.measurements = new MeasurementContainer({
|
|
|
|
|
autoConvert: true,
|
|
|
|
|
windowSize: 50,
|
|
|
|
|
defaultUnits: {
|
|
|
|
|
pressure: 'mbar',
|
|
|
|
|
flow: this.config.general.unit,
|
|
|
|
|
power: 'kW',
|
|
|
|
|
temperature: 'C'
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
this.interpolation = new interpolation();
|
|
|
|
|
|
|
|
|
|
this.flowDrift = null;
|
|
|
|
|
@@ -68,94 +75,114 @@ class Machine {
|
|
|
|
|
this.updatePosition();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// used for holding the source and sink unit operations or other object with setInfluent / getEffluent method for e.g. recirculation.
|
|
|
|
|
this.upstreamSource = null;
|
|
|
|
|
this.downstreamSink = null;
|
|
|
|
|
//When state changes look if we need to do other updates
|
|
|
|
|
this.state.emitter.on("stateChange", (newState) => {
|
|
|
|
|
this.logger.debug(`State change detected: ${newState}`);
|
|
|
|
|
this._updateState();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//perform init for certain values
|
|
|
|
|
this._init();
|
|
|
|
|
|
|
|
|
|
this.child = {}; // object to hold child information so we know on what to subscribe
|
|
|
|
|
this.childRegistrationUtils = new childRegistrationUtils(this); // Child registration utility
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_init(){
|
|
|
|
|
//assume standard temperature is 20degrees
|
|
|
|
|
this.measurements.type('temperature').variant('measured').position('atEquipment').value(15).unit('C');
|
|
|
|
|
//assume standard atm pressure is at sea level
|
|
|
|
|
this.measurements.type('atmPressure').variant('measured').position('atEquipment').value(101325).unit('Pa');
|
|
|
|
|
//populate min and max
|
|
|
|
|
const flowunit = this.config.general.unit;
|
|
|
|
|
this.measurements.type('flow').variant('predicted').position('max').value(this.predictFlow.currentFxyYMax, Date.now() , flowunit)
|
|
|
|
|
this.measurements.type('flow').variant('predicted').position('min').value(this.predictFlow.currentFxyYMin).unit(this.config.general.unit);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_updateState(){
|
|
|
|
|
const isOperational = this._isOperationalState();
|
|
|
|
|
if(!isOperational){
|
|
|
|
|
//overrule the last prediction this should be 0 now
|
|
|
|
|
this.measurements.type("flow").variant("predicted").position("downstream").value(0,Date.now(),this.config.general.unit);
|
|
|
|
|
this.measurements.type("flow").variant("predicted").position("atEquipment").value(0,Date.now(),this.config.general.unit);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*------------------- Register child events -------------------*/
|
|
|
|
|
registerChild(child, softwareType) {
|
|
|
|
|
if(!child) {
|
|
|
|
|
this.logger.error(`Invalid ${softwareType} child provided.`);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
this.logger.debug('Setting up child event for softwaretype ' + softwareType);
|
|
|
|
|
|
|
|
|
|
switch (softwareType) {
|
|
|
|
|
case "measurement":
|
|
|
|
|
this.logger.debug(`Registering measurement child...`);
|
|
|
|
|
this._connectMeasurement(child);
|
|
|
|
|
break;
|
|
|
|
|
case "reactor":
|
|
|
|
|
this.logger.debug(`Registering reactor child...`);
|
|
|
|
|
this._connectReactor(child);
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
this.logger.error(`Unrecognized 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}`;
|
|
|
|
|
|
|
|
|
|
_connectMeasurement(measurementChild) {
|
|
|
|
|
const position = measurementChild.config.functionality.positionVsParent;
|
|
|
|
|
const distance = measurementChild.config.functionality.distanceVsParent || 0;
|
|
|
|
|
const measurementType = measurementChild.config.asset.type;
|
|
|
|
|
//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 ${measurementChild.config.general.name}`);
|
|
|
|
|
// Register event listener for measurement updates
|
|
|
|
|
measurementChild.measurements.emitter.on(eventName, (eventData) => {
|
|
|
|
|
this.logger.debug(`🔄 ${position} ${measurementType} from ${eventData.childName}: ${eventData.value} ${eventData.unit}`);
|
|
|
|
|
|
|
|
|
|
// Store directly in parent's measurement container
|
|
|
|
|
this.measurements
|
|
|
|
|
.type(measurementType)
|
|
|
|
|
.variant("measured")
|
|
|
|
|
.position(position)
|
|
|
|
|
.value(eventData.value, eventData.timestamp, eventData.unit);
|
|
|
|
|
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}`);
|
|
|
|
|
|
|
|
|
|
// Call the appropriate handler
|
|
|
|
|
switch (measurementType) {
|
|
|
|
|
case 'pressure':
|
|
|
|
|
this.updateMeasuredPressure(eventData.value, position, eventData);
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
this.logger.debug(` 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);
|
|
|
|
|
|
|
|
|
|
case 'flow':
|
|
|
|
|
this.updateMeasuredFlow(eventData.value, position, eventData);
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
default:
|
|
|
|
|
this.logger.warn(`No handler for measurement type: ${measurementType}`);
|
|
|
|
|
// Generic handler - just update position
|
|
|
|
|
this.updatePosition();
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
// Call the appropriate handler
|
|
|
|
|
this._callMeasurementHandler(measurementType, eventData.value, position, eventData);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_connectReactor(reactorChild) {
|
|
|
|
|
this.downstreamSink = reactorChild; // downstream from the pumps perpective
|
|
|
|
|
// Centralized handler dispatcher
|
|
|
|
|
_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;
|
|
|
|
|
|
|
|
|
|
default:
|
|
|
|
|
this.logger.warn(`No handler for measurement type: ${measurementType}`);
|
|
|
|
|
// Generic handler - just update position
|
|
|
|
|
this.updatePosition();
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//---------------- END child stuff -------------//
|
|
|
|
|
//---------------- END child stuff -------------//
|
|
|
|
|
|
|
|
|
|
// Method to assess drift using errorMetrics
|
|
|
|
|
assessDrift(measurement, processMin, processMax) {
|
|
|
|
|
this.logger.debug(`Assessing drift for measurement: ${measurement} processMin: ${processMin} processMax: ${processMax}`);
|
|
|
|
|
const predictedMeasurement = this.measurements.type(measurement).variant("predicted").position("downstream").getAllValues().values;
|
|
|
|
|
const measuredMeasurement = this.measurements.type(measurement).variant("measured").position("downstream").getAllValues().values;
|
|
|
|
|
// Method to assess drift using errorMetrics
|
|
|
|
|
assessDrift(measurement, processMin, processMax) {
|
|
|
|
|
this.logger.debug(`Assessing drift for measurement: ${measurement} processMin: ${processMin} processMax: ${processMax}`);
|
|
|
|
|
const predictedMeasurement = this.measurements.type(measurement).variant("predicted").position("downstream").getAllValues().values;
|
|
|
|
|
const measuredMeasurement = this.measurements.type(measurement).variant("measured").position("downstream").getAllValues().values;
|
|
|
|
|
|
|
|
|
|
if (!predictedMeasurement || !measuredMeasurement) return null;
|
|
|
|
|
|
|
|
|
|
return this.errorMetrics.assessDrift(
|
|
|
|
|
predictedMeasurement,
|
|
|
|
|
measuredMeasurement,
|
|
|
|
|
processMin,
|
|
|
|
|
processMax
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
if (!predictedMeasurement || !measuredMeasurement) return null;
|
|
|
|
|
|
|
|
|
|
return this.errorMetrics.assessDrift(
|
|
|
|
|
predictedMeasurement,
|
|
|
|
|
measuredMeasurement,
|
|
|
|
|
processMin,
|
|
|
|
|
processMax
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
reverseCurve(curve) {
|
|
|
|
|
const reversedCurve = {};
|
|
|
|
|
@@ -176,43 +203,65 @@ class Machine {
|
|
|
|
|
// -------- Mode and Input Management -------- //
|
|
|
|
|
isValidSourceForMode(source, mode) {
|
|
|
|
|
const allowedSourcesSet = this.config.mode.allowedSources[mode] || [];
|
|
|
|
|
return allowedSourcesSet.has(source);
|
|
|
|
|
const allowed = allowedSourcesSet.has(source);
|
|
|
|
|
allowed?
|
|
|
|
|
this.logger.debug(`source is allowed proceeding with ${source} for mode ${mode}`) :
|
|
|
|
|
this.logger.warn(`${source} is not allowed in mode ${mode}`);
|
|
|
|
|
|
|
|
|
|
return allowed;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
isValidActionForMode(action, mode) {
|
|
|
|
|
const allowedActionsSet = this.config.mode.allowedActions[mode] || [];
|
|
|
|
|
return allowedActionsSet.has(action);
|
|
|
|
|
const allowed = allowedActionsSet.has(action);
|
|
|
|
|
allowed ?
|
|
|
|
|
this.logger.debug(`Action is allowed proceeding with ${action} for mode ${mode}`) :
|
|
|
|
|
this.logger.warn(`${action} is not allowed in mode ${mode}`);
|
|
|
|
|
|
|
|
|
|
return allowed;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async handleInput(source, action, parameter) {
|
|
|
|
|
|
|
|
|
|
if (!this.isValidSourceForMode(source, this.currentMode)) {
|
|
|
|
|
let warningTxt = `Source '${source}' is not valid for mode '${this.currentMode}'.`;
|
|
|
|
|
this.logger.warn(warningTxt);
|
|
|
|
|
return {status : false , feedback: warningTxt};
|
|
|
|
|
}
|
|
|
|
|
//sanitize input
|
|
|
|
|
if( typeof action !== 'string'){this.logger.error(`Action must be string`); return;}
|
|
|
|
|
//convert to lower case to avoid to many mistakes in commands
|
|
|
|
|
action = action.toLowerCase();
|
|
|
|
|
|
|
|
|
|
// check for validity of the request
|
|
|
|
|
if(!this.isValidActionForMode(action,this.currentMode)){return ;}
|
|
|
|
|
if (!this.isValidSourceForMode(source, this.currentMode)) {return ;}
|
|
|
|
|
|
|
|
|
|
this.logger.info(`Handling input from source '${source}' with action '${action}' in mode '${this.currentMode}'.`);
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
switch (action) {
|
|
|
|
|
case "execSequence":
|
|
|
|
|
|
|
|
|
|
case "execsequence":
|
|
|
|
|
return await this.executeSequence(parameter);
|
|
|
|
|
|
|
|
|
|
case "execMovement":
|
|
|
|
|
case "execmovement":
|
|
|
|
|
return await this.setpoint(parameter);
|
|
|
|
|
|
|
|
|
|
case "flowMovement":
|
|
|
|
|
case "entermaintenance":
|
|
|
|
|
|
|
|
|
|
return await this.executeSequence(parameter);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
case "exitmaintenance":
|
|
|
|
|
return await this.executeSequence(parameter);
|
|
|
|
|
|
|
|
|
|
case "flowmovement":
|
|
|
|
|
// Calculate the control value for a desired flow
|
|
|
|
|
const pos = this.calcCtrl(parameter);
|
|
|
|
|
// Move to the desired setpoint
|
|
|
|
|
return await this.setpoint(pos);
|
|
|
|
|
|
|
|
|
|
case "emergencyStop":
|
|
|
|
|
case "emergencystop":
|
|
|
|
|
this.logger.warn(`Emergency stop activated by '${source}'.`);
|
|
|
|
|
return await this.executeSequence("emergencyStop");
|
|
|
|
|
|
|
|
|
|
case "statusCheck":
|
|
|
|
|
case "statuscheck":
|
|
|
|
|
this.logger.info(`Status Check: Mode = '${this.currentMode}', Source = '${source}'.`);
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
@@ -299,21 +348,23 @@ class Machine {
|
|
|
|
|
calcFlow(x) {
|
|
|
|
|
if(this.hasCurve) {
|
|
|
|
|
if (!this._isOperationalState()) {
|
|
|
|
|
this.measurements.type("flow").variant("predicted").position("downstream").value(0);
|
|
|
|
|
this.measurements.type("flow").variant("predicted").position("downstream").value(0,Date.now(),this.config.general.unit);
|
|
|
|
|
this.measurements.type("flow").variant("predicted").position("atEquipment").value(0,Date.now(),this.config.general.unit);
|
|
|
|
|
this.logger.debug(`Machine is not operational. Setting predicted flow to 0.`);
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//this.predictFlow.currentX = x; Decrepated
|
|
|
|
|
const cFlow = this.predictFlow.y(x);
|
|
|
|
|
this.measurements.type("flow").variant("predicted").position("downstream").value(cFlow);
|
|
|
|
|
this.measurements.type("flow").variant("predicted").position("downstream").value(cFlow,Date.now(),this.config.general.unit);
|
|
|
|
|
this.measurements.type("flow").variant("predicted").position("atEquipment").value(cFlow,Date.now(),this.config.general.unit);
|
|
|
|
|
//this.logger.debug(`Calculated flow: ${cFlow} for pressure: ${this.getMeasuredPressure()} and position: ${x}`);
|
|
|
|
|
return cFlow;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// If no curve data is available, log a warning and return 0
|
|
|
|
|
this.logger.warn(`No curve data available for flow calculation. Returning 0.`);
|
|
|
|
|
this.measurements.type("flow").variant("predicted").position("downstream").value(0);
|
|
|
|
|
this.measurements.type("flow").variant("predicted").position("downstream").value(0, Date.now(),this.config.general.unit);
|
|
|
|
|
this.measurements.type("flow").variant("predicted").position("atEquipment").value(0, Date.now(),this.config.general.unit);
|
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
@@ -378,6 +429,11 @@ class Machine {
|
|
|
|
|
// returns the best available pressure measurement to use in the prediction calculation
|
|
|
|
|
// this will be either the differential pressure, downstream or upstream pressure
|
|
|
|
|
getMeasuredPressure() {
|
|
|
|
|
if(this.hasCurve === false){
|
|
|
|
|
this.logger.error(`No valid curve available to calculate prediction using last known pressure`);
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const pressureDiff = this.measurements.type('pressure').variant('measured').difference();
|
|
|
|
|
|
|
|
|
|
// Both upstream & downstream => differential
|
|
|
|
|
@@ -426,6 +482,9 @@ class Machine {
|
|
|
|
|
const efficiency = this.calcEfficiency(this.predictPower.outputY, this.predictFlow.outputY, "predicted");
|
|
|
|
|
//update the distance from peak
|
|
|
|
|
this.calcDistanceBEP(efficiency,cog,minEfficiency);
|
|
|
|
|
//place min and max flow capabilities in containerthis.predictFlow.currentFxyYMax - this.predictFlow.currentFxyYMin
|
|
|
|
|
this.measurements.type('flow').variant('predicted').position('max').value(this.predictFlow.currentFxyYMax).unit(this.config.general.unit);
|
|
|
|
|
this.measurements.type('flow').variant('predicted').position('min').value(this.predictFlow.currentFxyYMin).unit(this.config.general.unit);
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@@ -500,36 +559,27 @@ class Machine {
|
|
|
|
|
|
|
|
|
|
// NEW: Flow handler
|
|
|
|
|
updateMeasuredFlow(value, position, context = {}) {
|
|
|
|
|
|
|
|
|
|
if (!this._isOperationalState()) {
|
|
|
|
|
this.logger.warn(`Machine not operational, skipping flow update from ${context.childName || 'unknown'}`);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
this.logger.debug(`Flow update: ${value} at ${position} from ${context.childName || 'child'}`);
|
|
|
|
|
|
|
|
|
|
if (this.upstreamSource && this.downstreamSink) {
|
|
|
|
|
this.updateSourceSink();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Store in parent's measurement container
|
|
|
|
|
this.measurements.type("flow").variant("measured").position(position).value(value, context.timestamp, context.unit);
|
|
|
|
|
|
|
|
|
|
// Update predicted flow if you have prediction capability
|
|
|
|
|
if (this.predictFlow) {
|
|
|
|
|
this.measurements.type("flow").variant("predicted").position("downstream").value(this.predictFlow.outputY || 0);
|
|
|
|
|
this.measurements.type("flow").variant("predicted").position("atEquipment").value(this.predictFlow.outputY || 0);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
updateSourceSink() {
|
|
|
|
|
// Handles flow according to the configured "flow number"
|
|
|
|
|
this.logger.debug(`Updating source-sink pair: ${this.upstreamSource.config.functionality.softwareType} - ${this.downstreamSink.config.functionality.softwareType}`);
|
|
|
|
|
this.downstreamSink.setInfluent = this.upstreamSource.getEffluent[this.config.flowNumber];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Helper method for operational state check
|
|
|
|
|
_isOperationalState() {
|
|
|
|
|
const state = this.state.getCurrentState();
|
|
|
|
|
this.logger.debug(`Checking operational state ${this.state.getCurrentState()} ? ${["operational", "accelerating", "decelerating"].includes(state)}`);
|
|
|
|
|
return ["operational", "accelerating", "decelerating"].includes(state);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@@ -553,6 +603,7 @@ class Machine {
|
|
|
|
|
this.calcDistanceBEP(efficiency,cog,minEfficiency);
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
calcDistanceFromPeak(currentEfficiency,peakEfficiency){
|
|
|
|
|
@@ -583,7 +634,6 @@ class Machine {
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Calculate the center of gravity for current pressure
|
|
|
|
|
calcCog() {
|
|
|
|
|
|
|
|
|
|
@@ -593,7 +643,7 @@ class Machine {
|
|
|
|
|
const {efficiencyCurve, peak, peakIndex, minEfficiency } = this.calcEfficiencyCurve(powerCurve, flowCurve);
|
|
|
|
|
|
|
|
|
|
// Calculate the normalized center of gravity
|
|
|
|
|
const NCog = (flowCurve.y[peakIndex] - this.predictFlow.currentFxyYMin) / (this.predictFlow.currentFxyYMax - this.predictFlow.currentFxyYMin);
|
|
|
|
|
const NCog = (flowCurve.y[peakIndex] - this.predictFlow.currentFxyYMin) / (this.predictFlow.currentFxyYMax - this.predictFlow.currentFxyYMin); //
|
|
|
|
|
|
|
|
|
|
//store in object for later retrieval
|
|
|
|
|
this.currentEfficiencyCurve = efficiencyCurve;
|
|
|
|
|
@@ -643,15 +693,38 @@ class Machine {
|
|
|
|
|
return { cPower, cFlow };
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
calcEfficiency(power, flow, variant) {
|
|
|
|
|
calcEfficiency(power,flow,variant) {
|
|
|
|
|
|
|
|
|
|
const pressureDiff = this.measurements.type('pressure').variant('measured').difference('Pa');
|
|
|
|
|
const g = gravity.getStandardGravity();
|
|
|
|
|
const temp = this.measurements.type('temperature').variant('measured').position('atEquipment').getCurrentValue('K');
|
|
|
|
|
const atmPressure = this.measurements.type('atmPressure').variant('measured').position('atEquipment').getCurrentValue('Pa');
|
|
|
|
|
|
|
|
|
|
console.log(`--------------------calc efficiency : Pressure diff:${pressureDiff},${temp}, ${g} `);
|
|
|
|
|
const rho = coolprop.PropsSI('D', 'T', temp, 'P', atmPressure, 'WasteWater');
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
this.logger.debug(`temp: ${temp} atmPressure : ${atmPressure} rho : ${rho} pressureDiff: ${pressureDiff?.value || 0}`);
|
|
|
|
|
const flowM3s = this.measurements.type('flow').variant('predicted').position('atEquipment').getCurrentValue('m3/s');
|
|
|
|
|
const powerWatt = this.measurements.type('power').variant('predicted').position('atEquipment').getCurrentValue('W');
|
|
|
|
|
this.logger.debug(`Flow : ${flowM3s} power: ${powerWatt}`);
|
|
|
|
|
|
|
|
|
|
if (power != 0 && flow != 0) {
|
|
|
|
|
// Calculate efficiency after measurements update
|
|
|
|
|
this.measurements.type("efficiency").variant(variant).position('atEquipment').value((flow / power));
|
|
|
|
|
} else {
|
|
|
|
|
this.measurements.type("efficiency").variant(variant).position('atEquipment').value(null);
|
|
|
|
|
}
|
|
|
|
|
const specificFlow = flow / power;
|
|
|
|
|
const specificEnergyConsumption = power / flow;
|
|
|
|
|
|
|
|
|
|
this.measurements.type("efficiency").variant(variant).position('atEquipment').value(specificFlow);
|
|
|
|
|
this.measurements.type("specificEnergyConsumption").variant(variant).position('atEquipment').value(specificEnergyConsumption);
|
|
|
|
|
|
|
|
|
|
if(pressureDiff?.value != null && flowM3s != null && powerWatt != null){
|
|
|
|
|
const meterPerBar = pressureDiff.value / rho * g;
|
|
|
|
|
const nHydraulicEfficiency = rho * g * flowM3s * (pressureDiff.value * meterPerBar ) / powerWatt;
|
|
|
|
|
this.measurements.type("nHydraulicEfficiency").variant(variant).position('atEquipment').value(nHydraulicEfficiency);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//change this to nhydrefficiency ?
|
|
|
|
|
return this.measurements.type("efficiency").variant(variant).position('atEquipment').getCurrentValue();
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
@@ -698,26 +771,8 @@ class Machine {
|
|
|
|
|
getOutput() {
|
|
|
|
|
|
|
|
|
|
// Improved output object generation
|
|
|
|
|
const output = {};
|
|
|
|
|
//build the output object
|
|
|
|
|
this.measurements.getTypes().forEach(type => {
|
|
|
|
|
this.measurements.getVariants(type).forEach(variant => {
|
|
|
|
|
|
|
|
|
|
const downstreamVal = this.measurements.type(type).variant(variant).position("downstream").getCurrentValue();
|
|
|
|
|
const upstreamVal = this.measurements.type(type).variant(variant).position("upstream").getCurrentValue();
|
|
|
|
|
|
|
|
|
|
if (downstreamVal != null) {
|
|
|
|
|
output[`downstream_${variant}_${type}`] = downstreamVal;
|
|
|
|
|
}
|
|
|
|
|
if (upstreamVal != null) {
|
|
|
|
|
output[`upstream_${variant}_${type}`] = upstreamVal;
|
|
|
|
|
}
|
|
|
|
|
if (downstreamVal != null && upstreamVal != null) {
|
|
|
|
|
const diffVal = this.measurements.type(type).variant(variant).difference().value;
|
|
|
|
|
output[`differential_${variant}_${type}`] = diffVal;
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
const output = this.measurements.getFlattenedOutput();
|
|
|
|
|
|
|
|
|
|
//fill in the rest of the output object
|
|
|
|
|
output["state"] = this.state.getCurrentState();
|
|
|
|
|
@@ -728,6 +783,7 @@ class Machine {
|
|
|
|
|
output["cog"] = this.cog; // flow / power efficiency
|
|
|
|
|
output["NCog"] = this.NCog; // normalized cog
|
|
|
|
|
output["NCogPercent"] = Math.round(this.NCog * 100 * 100) / 100 ;
|
|
|
|
|
output["maintenanceTime"] = this.state.getMaintenanceTimeHours();
|
|
|
|
|
|
|
|
|
|
if(this.flowDrift != null){
|
|
|
|
|
const flowDrift = this.flowDrift;
|
|
|
|
|
@@ -751,8 +807,8 @@ class Machine {
|
|
|
|
|
module.exports = Machine;
|
|
|
|
|
|
|
|
|
|
/*------------------- Testing -------------------*/
|
|
|
|
|
/*
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
curve = require('C:/Users/zn375/.node-red/public/fallbackData.json');
|
|
|
|
|
|
|
|
|
|
//import a child
|
|
|
|
|
|