diff --git a/dependencies/machine/machine.js b/dependencies/machine/machine.js deleted file mode 100644 index bbd2fab..0000000 --- a/dependencies/machine/machine.js +++ /dev/null @@ -1,814 +0,0 @@ -/** - * @file machine.js - * - * Permission is hereby granted to any person obtaining a copy of this software - * and associated documentation files (the "Software"), to use it for personal - * or non-commercial purposes, with the following restrictions: - * - * 1. **No Copying or Redistribution**: The Software or any of its parts may not - * be copied, merged, distributed, sublicensed, or sold without explicit - * prior written permission from the author. - * - * 2. **Commercial Use**: Any use of the Software for commercial purposes requires - * a valid license, obtainable only with the explicit consent of the author. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE, AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES, OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT, OR OTHERWISE, ARISING FROM, - * OUT OF, OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * - * Ownership of this code remains solely with the original author. Unauthorized - * use of this Software is strictly prohibited. - * - * @summary A class to interact and manipulate machines with a non-euclidian curve - * @description A class to interact and manipulate machines with a non-euclidian curve - * @module machine - * @exports machine - * @version 0.1.0 - * @since 0.1.0 - * - * Author: - * - Rene De Ren - * Email: - * - rene@thegoldenbasket.nl - * - * Add functionality later - // -------- Operational Metrics -------- // -maintenanceAlert: this.state.checkMaintenanceStatus() - - -*/ - -//load local dependencies -const EventEmitter = require('events'); -const Logger = require('../../../generalFunctions/helper/logger'); -const State = require('../../../generalFunctions/helper/state/state'); -const Predict = require('../../../predict/dependencies/predict/predict_class'); -const { MeasurementContainer } = require('../../../generalFunctions/helper/measurements/index'); -const Interpolation = require('../../../predict/dependencies/predict/interpolation'); - -//load all config modules -const defaultConfig = require('../rotatingMachine/rotatingMachineConfig.json'); -const ConfigUtils = require('../../../generalFunctions/helper/configUtils'); - -//load registration utility -const ChildRegistrationUtils = require('../../../generalFunctions/helper/childRegistrationUtils'); -const ErrorMetrics = require('../../../generalFunctions/helper/nrmse/errorMetrics'); - -class Machine { - - /*------------------- Construct and set vars -------------------*/ - constructor(machineConfig = {}, stateConfig = {}, errorMetricsConfig = {}) { - - //basic setup - this.emitter = new EventEmitter(); // Own EventEmitter - this.configUtils = new ConfigUtils(defaultConfig); - this.config = this.configUtils.initConfig(machineConfig); - - // Initialize measurements - this.measurements = new MeasurementContainer(); - this.interpolation = new Interpolation(); - this.child = {}; // object to hold child information so we know on what to subscribe - - this.flowDrift = null; - - // Init after config is set - this.logger = new Logger(this.config.general.logging.enabled, this.config.general.logging.logLevel, this.config.general.name); - this.state = new State(stateConfig, this.logger); // Init State manager and pass logger - this.errorMetrics = new ErrorMetrics(errorMetricsConfig, this.logger); - - 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) - - this.currentMode = this.config.mode.current; - this.currentEfficiencyCurve = {}; - this.cog = 0; - this.NCog = 0; - this.cogIndex = 0; - this.minEfficiency = 0; - this.absDistFromPeak = 0; - this.relDistFromPeak = 0; - - this.state.emitter.on("positionChange", (data) => { - this.logger.debug(`Position change detected: ${data}`); - this.updatePosition(); - }); - - - - - //this.calcCog(); - - - this.childRegistrationUtils = new ChildRegistrationUtils(this); // Child registration utility - - } - - // 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 - ); - } - - reverseCurve(curve) { - const reversedCurve = {}; - for (const [pressure, values] of Object.entries(curve)) { - reversedCurve[pressure] = { - x: [...values.y], // Previous y becomes new x - y: [...values.x] // Previous x becomes new y - }; - } - return reversedCurve; - } - - // -------- Config -------- // - updateConfig(newConfig) { - this.config = this.configUtils.updateConfig(this.config, newConfig); - } - - // -------- Mode and Input Management -------- // - isValidSourceForMode(source, mode) { - const allowedSourcesSet = this.config.mode.allowedSources[mode] || []; - return allowedSourcesSet.has(source); - } - - isValidActionForMode(action, mode) { - const allowedActionsSet = this.config.mode.allowedActions[mode] || []; - return allowedActionsSet.has(action); - } - - 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}; - } - - - this.logger.info(`Handling input from source '${source}' with action '${action}' in mode '${this.currentMode}'.`); - try { - switch (action) { - case "execSequence": - await this.executeSequence(parameter); - //recalc flow and power - this.updatePosition(); - break; - case "execMovement": - await this.setpoint(parameter); - break; - case "flowMovement": - // Calculate the control value for a desired flow - const pos = this.calcCtrl(parameter); - // Move to the desired setpoint - await this.setpoint(pos); - break; - case "emergencyStop": - this.logger.warn(`Emergency stop activated by '${source}'.`); - await this.executeSequence("emergencyStop"); - break; - case "statusCheck": - this.logger.info(`Status Check: Mode = '${this.currentMode}', Source = '${source}'.`); - break; - default: - this.logger.warn(`Action '${action}' is not implemented.`); - break; - } - this.logger.debug(`Action '${action}' successfully executed`); - return {status : true , feedback: `Action '${action}' successfully executed.`}; - } catch (error) { - this.logger.error(`Error handling input: ${error}`); - } - } - - setMode(newMode) { - const availableModes = defaultConfig.mode.current.rules.values.map(v => v.value); - if (!availableModes.includes(newMode)) { - this.logger.warn(`Invalid mode '${newMode}'. Allowed modes are: ${availableModes.join(', ')}`); - return; - } - - this.currentMode = newMode; - this.logger.info(`Mode successfully changed to '${newMode}'.`); - } - - // -------- Sequence Handlers -------- // - async executeSequence(sequenceName) { - - const sequence = this.config.sequences[sequenceName]; - - if (!sequence || sequence.size === 0) { - this.logger.warn(`Sequence '${sequenceName}' not defined.`); - return; - } - - if (this.state.getCurrentState() == "operational" && sequenceName == "shutdown") { - this.logger.info(`Machine will ramp down to position 0 before performing ${sequenceName} sequence`); - await this.setpoint(0); - } - - this.logger.info(` --------- Executing sequence: ${sequenceName} -------------`); - - for (const state of sequence) { - try { - await this.state.transitionToState(state); - // Update measurements after state change - - } catch (error) { - this.logger.error(`Error during sequence '${sequenceName}': ${error}`); - break; // Exit sequence execution on error - } - } - } - - async setpoint(setpoint) { - - try { - // Validate setpoint - if (typeof setpoint !== 'number' || setpoint < 0) { - throw new Error("Invalid setpoint: Setpoint must be a non-negative number."); - } - - // Move to the desired setpoint - await this.state.moveTo(setpoint); - - } catch (error) { - console.error(`Error setting setpoint: ${error}`); - } - } - - // Calculate flow based on current pressure and position - calcFlow(x) { - const state = this.state.getCurrentState(); - - if (!["operational", "accelerating", "decelerating"].includes(state)) { - this.measurements.type("flow").variant("predicted").position("downstream").value(0); - 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.logger.debug(`Calculated flow: ${cFlow} for pressure: ${this.getMeasuredPressure()} and position: ${x}`); - return cFlow; - - } - - // Calculate power based on current pressure and position - calcPower(x) { - const state = this.state.getCurrentState(); - if (!["operational", "accelerating", "decelerating"].includes(state)) { - this.measurements.type("power").variant("predicted").position('upstream').value(0); - this.logger.debug(`Machine is not operational. Setting predicted power to 0.`); - return 0; - } - - //this.predictPower.currentX = x; Decrepated - const cPower = this.predictPower.y(x); - this.measurements.type("power").variant("predicted").position('upstream').value(cPower); - //this.logger.debug(`Calculated power: ${cPower} for pressure: ${this.getMeasuredPressure()} and position: ${x}`); - return cPower; - } - - // calculate the power consumption using only flow and pressure - inputFlowCalcPower(flow) { - this.predictCtrl.currentX = flow; - const cCtrl = this.predictCtrl.y(flow); - this.predictPower.currentX = cCtrl; - const cPower = this.predictPower.y(cCtrl); - return cPower; - } - - // Function to predict control value for a desired flow - calcCtrl(x) { - - this.predictCtrl.currentX = x; - const cCtrl = this.predictCtrl.y(x); - this.measurements.type("ctrl").variant("predicted").position('upstream').value(cCtrl); - //this.logger.debug(`Calculated ctrl: ${cCtrl} for pressure: ${this.getMeasuredPressure()} and position: ${x}`); - return cCtrl; - - } - - // this function returns the pressure for calculations - getMeasuredPressure() { - const pressureDiff = this.measurements.type('pressure').variant('measured').difference(); - - // Both upstream & downstream => differential - if (pressureDiff != null) { - this.logger.debug(`Pressure differential: ${pressureDiff.value}`); - this.predictFlow.fDimension = pressureDiff.value; - this.predictPower.fDimension = pressureDiff.value; - this.predictCtrl.fDimension = pressureDiff.value; - //update the cog - const { cog, minEfficiency } = this.calcCog(); - // calc efficiency - const efficiency = this.calcEfficiency(this.predictPower.outputY, this.predictFlow.outputY, "predicted"); - //update the distance from peak - this.calcDistanceBEP(efficiency,cog,minEfficiency); - - return pressureDiff.value; - } - - // get downstream - const downstreamPressure = this.measurements.type('pressure').variant('measured').position('downstream').getCurrentValue(); - - // Only downstream => use it, warn that it's partial - if (downstreamPressure != null) { - this.logger.warn(`Using downstream pressure only for prediction: ${downstreamPressure} `); - this.predictFlow.fDimension = downstreamPressure; - this.predictPower.fDimension = downstreamPressure; - this.predictCtrl.fDimension = downstreamPressure; - //update the cog - const { cog, minEfficiency } = this.calcCog(); - // calc efficiency - const efficiency = this.calcEfficiency(this.predictPower.outputY, this.predictFlow.outputY, "predicted"); - //update the distance from peak - this.calcDistanceBEP(efficiency,cog,minEfficiency); - return downstreamPressure; - } - - this.logger.error(`No valid pressure measurements available to calculate prediction using last known pressure`); - - //set default at 0 => lowest pressure possible - this.predictFlow.fDimension = 0; - this.predictPower.fDimension = 0; - this.predictCtrl.fDimension = 0; - //update the cog - const { cog, minEfficiency } = this.calcCog(); - // calc efficiency - const efficiency = this.calcEfficiency(this.predictPower.outputY, this.predictFlow.outputY, "predicted"); - //update the distance from peak - this.calcDistanceBEP(efficiency,cog,minEfficiency); - return 0; - } - - handleMeasuredFlow() { - const flowDiff = this.measurements.type('flow').variant('measured').difference(); - - // If both are present - if (flowDiff != null) { - // In theory, mass flow in = mass flow out, so they should match or be close. - if (flowDiff.value < 0.001) { - // flows match within tolerance - this.logger.debug(`Flow match: ${flowDiff.value}`); - return flowDiff.value; - } else { - // Mismatch => decide how to handle. Maybe take the average? - // Or bail out with an error. Example: we bail out here. - this.logger.error(`Something wrong with down or upstream flow measurement. Bailing out!`); - return null; - } - } - - // get - const upstreamFlow = this.measurements.type('pressure').variant('measured').position('upstream').getCurrentValue(); - - // Only upstream => might still accept it, but warn - if (upstreamFlow != null) { - this.logger.warn(`Only upstream flow is present. Using it but results may be incomplete!`); - return upstreamFlow; - } - - // get - const downstreamFlow = this.measurements.type('pressure').variant('measured').position('downstream').getCurrentValue(); - - // Only downstream => might still accept it, but warn - if (downstreamFlow != null) { - this.logger.warn(`Only downstream flow is present. Using it but results may be incomplete!`); - return downstreamFlow; - } - - // Neither => error - this.logger.error(`No upstream or downstream flow measurement. Bailing out!`); - return null; - } - - handleMeasuredPower() { - const power = this.measurements.type("power").variant("measured").position("upstream").getCurrentValue(); - // If your system calls it "upstream" or just a single "value", adjust accordingly - - if (power != null) { - this.logger.debug(`Measured power: ${power}`); - return power; - } else { - this.logger.error(`No measured power found. Bailing out!`); - return null; - } - } - - updatePressure(variant,value,position) { - - switch (variant) { - case ("measured"): - //only update when machine is in a state where it can be used - if (this.state.getCurrentState() == "operational" || this.state.getCurrentState() == "accelerating" || this.state.getCurrentState() == "decelerating") { - // put value in measurements - this.measurements.type("pressure").variant("measured").position(position).value(value); - //when measured pressure gets updated we need some logic to fetch the relevant value which could be downstream or differential pressure - const pressure = this.getMeasuredPressure(); - //update the flow power and cog - this.updatePosition(); - this.logger.debug(`Measured pressure: ${pressure}`); - } - break; - - default: - this.logger.warn(`Unrecognized variant '${variant}' for pressure update.`); - break; - } - } - - updateFlow(variant,value,position) { - - switch (variant) { - case ("measured"): - // put value in measurements - this.measurements.type("flow").variant("measured").position(position).value(value); - //when measured flow gets updated we need to push the last known value in the prediction measurements to keep them synced - this.measurements.type("flow").variant("predicted").position("downstream").value(this.predictFlow.outputY); - break; - - case ("predicted"): - this.logger.debug('not doing anythin yet'); - break; - - default: - this.logger.warn(`Unrecognized variant '${variant}' for flow update.`); - break; - } - } - - updateMeasurement(variant, subType, value, position) { - this.logger.debug(`---------------------- updating ${subType} ------------------ `); - switch (subType) { - case "pressure": - // Update pressure measurement - this.updatePressure(variant,value,position); - break; - case "flow": - this.updateFlow(variant,value,position); - // Update flow measurement - this.flowDrift = this.assessDrift("flow", this.predictFlow.currentFxyYMin , this.predictFlow.currentFxyYMax); - this.logger.debug(`---------------------------------------- `); - break; - case "power": - // Update power measurement - break; - default: - this.logger.error(`Type '${type}' not recognized for measured update.`); - return; - } - } - - //what is the internal functions that need updating when something changes that has influence on this. - updatePosition() { - if (this.state.getCurrentState() == "operational" || this.state.getCurrentState() == "accelerating" || this.state.getCurrentState() == "decelerating") { - - const currentPosition = this.state.getCurrentPosition(); - - // Update the predicted values based on the new position - const { cPower, cFlow } = this.calcFlowPower(currentPosition); - - // Calc predicted efficiency - const efficiency = this.calcEfficiency(cPower, cFlow, "predicted"); - - //update the cog - const { cog, minEfficiency } = this.calcCog(); - - //update the distance from peak - this.calcDistanceBEP(efficiency,cog,minEfficiency); - - } - } - - calcDistanceFromPeak(currentEfficiency,peakEfficiency){ - return Math.abs(currentEfficiency - peakEfficiency); - } - - calcRelativeDistanceFromPeak(currentEfficiency,maxEfficiency,minEfficiency){ - let distance = 1; - if(currentEfficiency != null){ - distance = this.interpolation.interpolate_lin_single_point(currentEfficiency,maxEfficiency, minEfficiency, 0, 1); - } - return distance; - } - - // Calculate the center of gravity for current pressure - calcCog() { - - //fetch current curve data for power and flow - const { powerCurve, flowCurve } = this.getCurrentCurves(); - - 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); - - //store in object for later retrieval - this.currentEfficiencyCurve = efficiencyCurve; - this.cog = peak; - this.cogIndex = peakIndex; - this.NCog = NCog; - this.minEfficiency = minEfficiency; - - return { cog: peak, cogIndex: peakIndex, NCog: NCog, minEfficiency: minEfficiency }; - - } - - calcEfficiencyCurve(powerCurve, flowCurve) { - - const efficiencyCurve = []; - let peak = 0; - let peakIndex = 0; - let minEfficiency = 0; - - // Calculate efficiency curve based on power and flow curves - powerCurve.y.forEach((power, index) => { - - // Get flow for the current power - const flow = flowCurve.y[index]; - - // higher efficiency is better - efficiencyCurve.push( Math.round( ( flow / power ) * 100 ) / 100); - - // Keep track of peak efficiency - peak = Math.max(peak, efficiencyCurve[index]); - peakIndex = peak == efficiencyCurve[index] ? index : peakIndex; - minEfficiency = Math.min(...efficiencyCurve); - - }); - - return { efficiencyCurve, peak, peakIndex, minEfficiency }; - - } - - //calc flow power based on pressure and current position - calcFlowPower(x) { - - // Calculate flow and power - const cFlow = this.calcFlow(x); - const cPower = this.calcPower(x); - - return { cPower, cFlow }; - } - - calcEfficiency(power, flow, variant) { - - if (power != 0 && flow != 0) { - // Calculate efficiency after measurements update - this.measurements.type("efficiency").variant(variant).position('downstream').value((flow / power)); - } else { - this.measurements.type("efficiency").variant(variant).position('downstream').value(null); - } - - return this.measurements.type("efficiency").variant(variant).position('downstream').getCurrentValue(); - - } - - updateCurve(newCurve) { - this.logger.info(`Updating machine curve`); - const newConfig = { asset: { machineCurve: newCurve } }; - - //validate input of new curve fed to the machine - this.config = this.configUtils.updateConfig(this.config, newConfig); - - //After we passed validation load the curves into their predictors - this.predictFlow.updateCurve(this.config.asset.machineCurve.nq); - this.predictPower.updateCurve(this.config.asset.machineCurve.np); - this.predictCtrl.updateCurve(this.reverseCurve(this.config.asset.machineCurve.nq)); - } - - getCompleteCurve() { - const powerCurve = this.predictPower.inputCurveData; - const flowCurve = this.predictFlow.inputCurveData; - return { powerCurve, flowCurve }; - } - - getCurrentCurves() { - const powerCurve = this.predictPower.currentFxyCurve[this.predictPower.currentF]; - const flowCurve = this.predictFlow.currentFxyCurve[this.predictFlow.currentF]; - - return { powerCurve, flowCurve }; - - } - - calcDistanceBEP(efficiency,maxEfficiency,minEfficiency) { - - const absDistFromPeak = this.calcDistanceFromPeak(efficiency,maxEfficiency); - const relDistFromPeak = this.calcRelativeDistanceFromPeak(efficiency,maxEfficiency,minEfficiency); - - //store internally - this.absDistFromPeak = absDistFromPeak ; - this.relDistFromPeak = relDistFromPeak; - - return { absDistFromPeak: absDistFromPeak, relDistFromPeak: relDistFromPeak }; - } - - 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; - } - }); - }); - - //fill in the rest of the output object - output["state"] = this.state.getCurrentState(); - output["runtime"] = this.state.getRunTimeHours(); - output["ctrl"] = this.state.getCurrentPosition(); - output["moveTimeleft"] = this.state.getMoveTimeLeft(); - output["mode"] = this.currentMode; - output["cog"] = this.cog; // flow / power efficiency - output["NCog"] = this.NCog; // normalized cog - output["NCogPercent"] = Math.round(this.NCog * 100 * 100) / 100 ; - - if(this.flowDrift != null){ - const flowDrift = this.flowDrift; - output["flowNrmse"] = flowDrift.nrmse; - output["flowLongterNRMSD"] = flowDrift.longTermNRMSD; - output["flowImmediateLevel"] = flowDrift.immediateLevel; - output["flowLongTermLevel"] = flowDrift.longTermLevel; - } - - //should this all go in the container of measurements? - output["effDistFromPeak"] = this.absDistFromPeak; - output["effRelDistFromPeak"] = this.relDistFromPeak; - //this.logger.debug(`Output: ${JSON.stringify(output)}`); - - return output; - } - - -} // end of class - -module.exports = Machine; - -/*------------------- Testing -------------------*/ - -/* -curve = require('C:/Users/zn375/.node-red/public/fallbackData.json'); - -//import a child -const Child = require('../../../measurement/dependencies/measurement/measurement'); - -console.log(`Creating child...`); -const PT1 = new Child(config={ - general:{ - name:"PT1", - logging:{ - enabled:true, - logLevel:"debug", - }, - }, - functionality:{ - softwareType:"measurement", - }, - asset:{ - type:"sensor", - subType:"pressure", - }, -}); - -const PT2 = new Child(config={ - general:{ - name:"PT2", - logging:{ - enabled:true, - logLevel:"debug", - }, - }, - functionality:{ - softwareType:"measurement", - }, - asset:{ - type:"sensor", - subType:"pressure", - }, -}); - -//create a machine -console.log(`Creating machine...`); - -const machineConfig = { - general: { - name: "Hydrostal", - logging: { - enabled: true, - logLevel: "debug", - } - }, - asset: { - supplier: "Hydrostal", - type: "pump", - subType: "centrifugal", - model: "H05K-S03R+HGM1X-X280KO", // Ensure this field is present. - machineCurve: curve["machineCurves"]["Hydrostal"]["H05K-S03R+HGM1X-X280KO"], - } -} - -const stateConfig = { - general: { - logging: { - enabled: true, - logLevel: "debug", - }, - }, - // Your custom config here (or leave empty for defaults) - movement: { - speed: 1, - }, - time: { - starting: 2, - warmingup: 3, - stopping: 2, - coolingdown: 3, - }, -}; - -const machine = new Machine(machineConfig, stateConfig); - -//machine.logger.info(JSON.stringify(curve["machineCurves"]["Hydrostal"]["H05K-S03R+HGM1X-X280KO"])); -machine.logger.info(`Registering child...`); -machine.childRegistrationUtils.registerChild(PT1, "upstream"); -machine.childRegistrationUtils.registerChild(PT2, "downstream"); - -//feed curve to the machine class -//machine.updateCurve(curve["machineCurves"]["Hydrostal"]["H05K-S03R+HGM1X-X280KO"]); - -PT1.logger.info(`Enable sim...`); -PT1.toggleSimulation(); -PT2.logger.info(`Enable sim...`); -PT2.toggleSimulation(); -machine.getOutput(); -//manual test -//machine.handleInput("parent", "execSequence", "startup"); - -machine.measurements.type("pressure").variant("measured").position('upstream').value(-200); -machine.measurements.type("pressure").variant("measured").position('downstream').value(1000); - -testingSequences(); - -const tickLoop = setInterval(changeInput,1000); - -function changeInput(){ - PT1.logger.info(`tick...`); - PT1.tick(); - PT2.tick(); -} - -async function testingSequences(){ - try{ - console.log(` ********** Testing sequence startup... **********`); - await machine.handleInput("parent", "execSequence", "startup"); - console.log(` ********** Testing movement to 15... **********`); - await machine.handleInput("parent", "execMovement", 15); - machine.getOutput(); - console.log(` ********** Testing sequence shutdown... **********`); - await machine.handleInput("parent", "execSequence", "shutdown"); - console.log(`********** Testing moving to setpoint 10... while in idle **********`); - await machine.handleInput("parent", "execMovement", 10); - console.log(` ********** Testing sequence emergencyStop... **********`); - await machine.handleInput("parent", "execSequence", "emergencystop"); - console.log(`********** Testing sequence boot... **********`); - await machine.handleInput("parent", "execSequence", "boot"); - }catch(error){ - console.error(`Error: ${error}`); - } -} - - -//*/ - - - diff --git a/dependencies/machine/machine.test.js b/dependencies/machine/machine.test.js deleted file mode 100644 index 6d60b03..0000000 --- a/dependencies/machine/machine.test.js +++ /dev/null @@ -1,297 +0,0 @@ -const Machine = require('./machine'); -const specs = require('../../../generalFunctions/datasets/assetData/pumps/hydrostal/centrifugal pumps/models.json'); - -class MachineTester { - constructor() { - this.totalTests = 0; - this.passedTests = 0; - this.failedTests = 0; - this.machineCurve = specs[0].machineCurve; - } - - assert(condition, message) { - this.totalTests++; - if (condition) { - console.log(`✓ PASS: ${message}`); - this.passedTests++; - } else { - console.log(`✗ FAIL: ${message}`); - this.failedTests++; - } - } - - createBaseMachineConfig(name) { - return { - general: { - logging: { enabled: true, logLevel: "debug" }, - name: name, - unit: "m3/h" - }, - functionality: { - softwareType: "machine", - role: "RotationalDeviceController" - }, - asset: { - type: "pump", - subType: "Centrifugal", - model: "TestModel", - supplier: "Hydrostal", - machineCurve: this.machineCurve - }, - mode: { - current: "auto", - allowedActions: { - auto: ["execSequence", "execMovement", "statusCheck"], - virtualControl: ["execMovement", "statusCheck"], - fysicalControl: ["statusCheck"] - }, - allowedSources: { - auto: ["parent", "GUI"], - virtualControl: ["GUI"], - fysicalControl: ["fysical"] - } - }, - sequences: { - startup: ["starting", "warmingup", "operational"], - shutdown: ["stopping", "coolingdown", "idle"], - emergencystop: ["emergencystop", "off"], - boot: ["idle", "starting", "warmingup", "operational"] - }, - calculationMode: "medium" - }; - } - - async testBasicOperations() { - console.log('\nTesting Basic Machine Operations...'); - - const machine = new Machine(this.createBaseMachineConfig("TestMachine")); - - try { - // Test 1: Initialization - this.assert( - machine.currentMode === "auto", - 'Machine should initialize in auto mode' - ); - - // Test 2: Set pressure measurement - machine.measurements.type("pressure").variant("measured").position("downstream").value(800); - const pressure = machine.handleMeasuredPressure(); - this.assert( - pressure === 800, - 'Should correctly handle pressure measurement' - ); - - // Test 3: State transition - await machine.state.transitionToState("idle"); - this.assert( - machine.state.getCurrentState() === "idle", - 'Should transition to idle state' - ); - - // Test 4: Mode change - machine.setMode("virtualControl"); - this.assert( - machine.currentMode === "virtualControl", - 'Should change mode to virtual control' - ); - } catch (error) { - console.error('Test failed with error:', error); - this.failedTests++; - } - } - - async testPredictions() { - console.log('\nTesting Machine Predictions...'); - - const machine = new Machine(this.createBaseMachineConfig("TestMachine")); - machine.measurements.type("pressure").variant("measured").position("downstream").value(800); - - try { - // Test 1: Flow prediction - const flow = machine.calcFlow(50); - this.assert( - flow > 0 && !isNaN(flow), - 'Should calculate valid flow for control value' - ); - - // Test 2: Power prediction - const power = machine.calcPower(50); - this.assert( - power > 0 && !isNaN(power), - 'Should calculate valid power for control value' - ); - - // Test 3: Control prediction - const ctrl = machine.calcCtrl(100); - this.assert( - ctrl >= 0 && ctrl <= 100, - 'Should calculate valid control value for desired flow' - ); - } catch (error) { - console.error('Test failed with error:', error); - this.failedTests++; - } - } - - async testSequenceExecution() { - console.log('\nTesting Machine Sequences...'); - - const machine = new Machine(this.createBaseMachineConfig("TestMachine")); - - try { - // Test 1: Startup sequence - await machine.handleInput("parent", "execSequence", "startup"); - this.assert( - machine.state.getCurrentState() === "operational", - 'Should complete startup sequence' - ); - - // Test 2: Movement after startup - await machine.handleInput("parent", "execMovement", 50); - this.assert( - machine.state.getCurrentPosition() === 50, - 'Should move to specified position' - ); - - // Test 3: Shutdown sequence - await machine.handleInput("parent", "execSequence", "shutdown"); - this.assert( - machine.state.getCurrentState() === "idle", - 'Should complete shutdown sequence' - ); - - // Test 4: Emergency stop - await machine.handleInput("parent", "execSequence", "emergencystop"); - this.assert( - machine.state.getCurrentState() === "off", - 'Should execute emergency stop' - ); - } catch (error) { - console.error('Test failed with error:', error); - this.failedTests++; - } - } - - async testMeasurementHandling() { - console.log('\nTesting Measurement Handling...'); - - const machine = new Machine(this.createBaseMachineConfig("TestMachine")); - - try { - // Test 1: Pressure measurement - machine.measurements.type("pressure").variant("measured").position("downstream").value(800); - machine.measurements.type("pressure").variant("measured").setUpstream(1000); - const pressure = machine.handleMeasuredPressure(); - this.assert( - pressure === 200, - 'Should calculate correct differential pressure' - ); - - // Test 2: Flow measurement - machine.measurements.type("flow").variant("measured").position("downstream").value(100); - const flow = machine.handleMeasuredFlow(); - this.assert( - flow === 100, - 'Should handle flow measurement correctly' - ); - - // Test 3: Power measurement - machine.measurements.type("power").variant("measured").setUpstream(75); - const power = machine.handleMeasuredPower(); - this.assert( - power === 75, - 'Should handle power measurement correctly' - ); - - // Test 4: Efficiency calculation - machine.calcEfficiency(); - const efficiency = machine.measurements.type("efficiency").variant("measured").getDownstream(); - this.assert( - efficiency > 0 && !isNaN(efficiency), - 'Should calculate valid efficiency' - ); - } catch (error) { - console.error('Test failed with error:', error); - this.failedTests++; - } - } - - async testCurveHandling() { - console.log('\nTesting Machine Curve Handling...'); - - const machine = new Machine(this.createBaseMachineConfig("TestMachine")); - - try { - // Test 1: Curve initialization - const curves = machine.showCurve(); - this.assert( - curves.powerCurve && curves.flowCurve, - 'Should properly initialize power and flow curves' - ); - - // Test 2: Test reverse curve creation - const reversedCurve = machine.reverseCurve(this.machineCurve.nq); - this.assert( - reversedCurve["1"].x[0] === this.machineCurve.nq["1"].y[0] && - reversedCurve["1"].y[0] === this.machineCurve.nq["1"].x[0], - 'Should correctly reverse x and y values in curve' - ); - - // Test 3: Update curve dynamically - const newCurve = { - nq: { - "1": { - x: [0, 25, 50, 75, 100], - y: [0, 125, 250, 375, 500] - } - }, - np: { - "1": { - x: [0, 25, 50, 75, 100], - y: [0, 75, 150, 225, 300] - } - } - }; - machine.updateCurve(newCurve); - const updatedCurves = machine.showCurve(); - this.assert( - updatedCurves.flowCurve["1"].y[2] === 250, - 'Should update curve with new values' - ); - - // Test 4: Verify curve interpolation - machine.measurements.type("pressure").variant("measured").position("downstream").value(800); - const midpointCtrl = machine.calcCtrl(250); // Should interpolate between points - const calculatedFlow = machine.calcFlow(midpointCtrl); - this.assert( - Math.abs(calculatedFlow - 250) < 1, // Allow small numerical error - 'Should accurately interpolate between curve points' - ); - - } catch (error) { - console.error('Test failed with error:', error); - this.failedTests++; - } - } - - async runAllTests() { - console.log('Starting Machine Tests...\n'); - - await this.testBasicOperations(); - await this.testPredictions(); - await this.testSequenceExecution(); - await this.testMeasurementHandling(); - await this.testCurveHandling(); - - console.log('\nTest Summary:'); - console.log(`Total Tests: ${this.totalTests}`); - console.log(`Passed: ${this.passedTests}`); - console.log(`Failed: ${this.failedTests}`); - - process.exit(this.failedTests > 0 ? 1 : 0); - } -} - -// Run the tests -const tester = new MachineTester(); -tester.runAllTests().catch(console.error); \ No newline at end of file diff --git a/readme/Training_data b/misc/Training_data similarity index 100% rename from readme/Training_data rename to misc/Training_data diff --git a/readme/measured_curve.json b/misc/measured_curve.json similarity index 100% rename from readme/measured_curve.json rename to misc/measured_curve.json diff --git a/rotatingMachine copy.js b/rotatingMachine copy.js deleted file mode 100644 index 1205bc4..0000000 --- a/rotatingMachine copy.js +++ /dev/null @@ -1,247 +0,0 @@ -module.exports = function (RED) { - function rotatingMachine(config) { - RED.nodes.createNode(this, config); - var node = this; - - try { - // Load Machine class and curve data - const Machine = require("./dependencies/machine/machine"); - const OutputUtils = require("../generalFunctions/helper/outputUtils"); - - const machineConfig = { - general: { - name: config.name || "Default Machine", - id: node.id, - logging: { - enabled: config.eneableLog, - logLevel: config.logLevel - } - }, - asset: { - supplier: config.supplier || "Unknown", - type: config.machineType || "generic", - subType: config.subType || "generic", - model: config.model || "generic", - machineCurve: config.machineCurve - } - }; - - const stateConfig = { - general: { - logging: { - enabled: config.eneableLog, - logLevel: config.logLevel - } - }, - movement: { - speed: Number(config.speed) - }, - time: { - starting: Number(config.startup), - warmingup: Number(config.warmup), - stopping: Number(config.shutdown), - coolingdown: Number(config.cooldown) - } - }; - - // Create machine instance - const m = new Machine(machineConfig, stateConfig); - - // put m on node memory as source - node.source = m; - - //load output utils - const output = new OutputUtils(); - - function updateNodeStatus() { - try { - const mode = m.currentMode; - const state = m.state.getCurrentState(); - const flow = Math.round(m.measurements.type("flow").variant("predicted").position('downstream').getCurrentValue()); - const power = Math.round(m.measurements.type("power").variant("predicted").position('upstream').getCurrentValue()); - let symbolState; - switch(state){ - case "off": - symbolState = "⬛"; - break; - case "idle": - symbolState = "⏸️"; - break; - case "operational": - symbolState = "⏵️"; - break; - case "starting": - symbolState = "⏯️"; - break; - case "warmingup": - symbolState = "🔄"; - break; - case "accelerating": - symbolState = "⏩"; - break; - case "stopping": - symbolState = "⏹️"; - break; - case "coolingdown": - symbolState = "❄️"; - break; - case "decelerating": - symbolState = "⏪"; - break; - } - const position = m.state.getCurrentPosition(); - const roundedPosition = Math.round(position * 100) / 100; - - let status; - switch (state) { - case "off": - status = { fill: "red", shape: "dot", text: `${mode}: OFF` }; - break; - case "idle": - status = { fill: "blue", shape: "dot", text: `${mode}: ${symbolState}` }; - break; - case "operational": - status = { fill: "green", shape: "dot", text: `${mode}: ${symbolState} | ${roundedPosition}% | 💨${flow}m³/h | ⚡${power}kW` }; - break; - case "starting": - status = { fill: "yellow", shape: "dot", text: `${mode}: ${symbolState}` }; - break; - case "warmingup": - status = { fill: "green", shape: "dot", text: `${mode}: ${symbolState} | ${roundedPosition}% | 💨${flow}m³/h | ⚡${power}kW` }; - break; - case "accelerating": - status = { fill: "yellow", shape: "dot", text: `${mode}: ${symbolState} | ${roundedPosition}%| 💨${flow}m³/h | ⚡${power}kW` }; - break; - case "stopping": - status = { fill: "yellow", shape: "dot", text: `${mode}: ${symbolState}` }; - break; - case "coolingdown": - status = { fill: "yellow", shape: "dot", text: `${mode}: ${symbolState}` }; - break; - case "decelerating": - status = { fill: "yellow", shape: "dot", text: `${mode}: ${symbolState} - ${roundedPosition}% | 💨${flow}m³/h | ⚡${power}kW` }; - break; - default: - status = { fill: "grey", shape: "dot", text: `${mode}: ${symbolState}` }; - } - return status; - } catch (error) { - node.error("Error in updateNodeStatus: " + error.message); - return { fill: "red", shape: "ring", text: "Status Error" }; - } - } - - function tick() { - try { - const status = updateNodeStatus(); - node.status(status); - - //get output - const classOutput = m.getOutput(); - const dbOutput = output.formatMsg(classOutput, m.config, "influxdb"); - const pOutput = output.formatMsg(classOutput, m.config, "process"); - - //console.log(pOutput); - - //only send output on values that changed - let msgs = []; - msgs[0] = pOutput; - msgs[1] = dbOutput; - - node.send(msgs); - - } catch (error) { - node.error("Error in tick function: " + error); - node.status({ fill: "red", shape: "ring", text: "Tick Error" }); - } - } - - // register child on first output this timeout is needed because of node - red stuff - setTimeout( - () => { - - /*---execute code on first start----*/ - let msgs = []; - - msgs[2] = { topic : "registerChild" , payload: node.id, positionVsParent: "upstream" }; - msgs[3] = { topic : "registerChild" , payload: node.id, positionVsParent: "downstream" }; - - //send msg - this.send(msgs); - }, - 100 - ); - - //declare refresh interval internal node - - setTimeout( - () => { - //---execute code on first start---- - this.interval_id = setInterval(function(){ tick() },1000) - }, - 1000 - ); - - node.on("input", function(msg, send, done) { - try { - - /* Update to complete event based node by putting the tick function after an input event */ - - - switch(msg.topic) { - case 'registerChild': - const childId = msg.payload; - const childObj = RED.nodes.getNode(childId); - m.childRegistrationUtils.registerChild(childObj.source ,msg.positionVsParent); - break; - case 'setMode': - m.setMode(msg.payload); - break; - case 'execSequence': - const { source, action, parameter } = msg.payload; - m.handleInput(source, action, parameter); - break; - case 'execMovement': - const { source: mvSource, action: mvAction, setpoint } = msg.payload; - m.handleInput(mvSource, mvAction, Number(setpoint)); - break; - case 'flowMovement': - const { source: fmSource, action: fmAction, setpoint: fmSetpoint } = msg.payload; - m.handleInput(fmSource, fmAction, Number(fmSetpoint)); - - break; - case 'emergencystop': - const { source: esSource, action: esAction } = msg.payload; - m.handleInput(esSource, esAction); - break; - case 'showCompleteCurve': - m.showCompleteCurve(); - send({ topic : "Showing curve" , payload: m.showCompleteCurve() }); - break; - case 'CoG': - m.showCoG(); - send({ topic : "Showing CoG" , payload: m.showCoG() }); - break; - } - - if (done) done(); - } catch (error) { - node.error("Error processing input: " + error.message); - if (done) done(error); - } - }); - - node.on('close', function(done) { - if (node.interval_id) clearTimeout(node.interval_id); - if (node.tick_interval) clearInterval(node.tick_interval); - if (done) done(); - }); - - } catch (error) { - node.error("Fatal error in node initialization: " + error.stack); - node.status({fill: "red", shape: "ring", text: "Fatal Error"}); - } - } - - RED.nodes.registerType("rotatingMachine", rotatingMachine); -}; \ No newline at end of file diff --git a/rotatingMachine.html b/rotatingMachine.html index 3978958..7e31298 100644 --- a/rotatingMachine.html +++ b/rotatingMachine.html @@ -8,7 +8,7 @@ color: "#4f8582", defaults: { // Define default properties - name: { value: "", required: true }, // use asset category as name ? + name: { value: ""}, // use asset category as name ? // Define specific properties speed: { value: 1, required: true }, @@ -31,6 +31,7 @@ //physicalAspect positionVsParent: { value: "" }, + positionIcon: { value: "" }, }, inputs: 1, @@ -39,8 +40,8 @@ outputLabels: ["process", "dbase", "parent"], icon: "font-awesome/fa-tachometer", - label: function() { - return this.name || "rotatingMachine"; + label: function () { + return this.positionIcon + " " + this.category.slice(0, -1) || "Machine"; }, oneditprepare: function() { diff --git a/rotatingMachine.js b/rotatingMachine.js index 86647c0..0f1abf4 100644 --- a/rotatingMachine.js +++ b/rotatingMachine.js @@ -1,12 +1,12 @@ const nameOfNode = 'rotatingMachine'; -const NodeClass = require('./src/nodeClass.js'); +const nodeClass = require('./src/nodeClass.js'); const { MenuManager, configManager } = require('generalFunctions'); module.exports = function(RED) { // 1) Register the node type and delegate to your class RED.nodes.registerType(nameOfNode, function(config) { RED.nodes.createNode(this, config); - this.nodeClass = new NodeClass(config, RED, this, nameOfNode); + this.nodeClass = new nodeClass(config, RED, this, nameOfNode); }); // 2) Setup the dynamic menu & config endpoints diff --git a/src/nodeClass.js b/src/nodeClass.js index 26c3bb9..ffbf4d8 100644 --- a/src/nodeClass.js +++ b/src/nodeClass.js @@ -1,5 +1,5 @@ /** - * measurement.class.js + * node class.js * * 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. @@ -7,9 +7,6 @@ const { outputUtils, configManager } = require('generalFunctions'); const Specific = require("./specificClass"); -/** - * Class representing a Node-RED node. - */ class nodeClass { /** * Create a Node. @@ -79,27 +76,32 @@ class nodeClass { * Instantiate the core Measurement logic and store as source. */ _setupSpecificClass() { + const machineConfig = this.config; // need extra state for this const stateConfig = { general: { logging: { - enabled: config.eneableLog, - logLevel: config.logLevel + enabled: machineConfig.eneableLog, + logLevel: machineConfig.logLevel } }, movement: { - speed: Number(config.speed) + speed: Number(machineConfig.speed) }, time: { - starting: Number(config.startup), - warmingup: Number(config.warmup), - stopping: Number(config.shutdown), - coolingdown: Number(config.cooldown) + starting: Number(machineConfig.startup), + warmingup: Number(machineConfig.warmup), + stopping: Number(machineConfig.shutdown), + coolingdown: Number(machineConfig.cooldown) } }; - this.source = new Specific(this.config, stateConfig); + this.source = new Specific(machineConfig, stateConfig); + + //store in node + this.node.source = this.source; // Store the source in the node instance for easy access + } /** @@ -223,7 +225,7 @@ class nodeClass { * Execute a single tick: update measurement, format and send outputs. */ _tick() { - this.source.tick(); + //this.source.tick(); const raw = this.source.getOutput(); const processMsg = this._output.formatMsg(raw, this.config, 'process'); @@ -242,6 +244,7 @@ class nodeClass { const m = this.source; switch(msg.topic) { case 'registerChild': + // Register this node as a child of the parent node const childId = msg.payload; const childObj = this.RED.nodes.getNode(childId); m.childRegistrationUtils.registerChild(childObj.source ,msg.positionVsParent); diff --git a/src/specificClass.js b/src/specificClass.js index 6d53763..6097fdc 100644 --- a/src/specificClass.js +++ b/src/specificClass.js @@ -44,7 +44,7 @@ maintenanceAlert: this.state.checkMaintenanceStatus() //load local dependencies const EventEmitter = require('events'); -const {logger,configUtils,configManager,state, nrmse, MeasurementContainer, predict, interpolation , childRegistrationUtils} = require('generalFunctions'); +const {loadCurve,logger,configUtils,configManager,state, nrmse, MeasurementContainer, predict, interpolation , childRegistrationUtils} = require('generalFunctions'); class Machine { @@ -53,13 +53,31 @@ class Machine { //basic setup this.emitter = new EventEmitter(); // Own EventEmitter + this.logger = new logger(machineConfig.general.logging.enabled,machineConfig.general.logging.logLevel, machineConfig.general.name); this.configManager = new configManager(); - this.defaultConfig = this.configManager.getConfig('rotatingMachine'); // Load default config for rotating machine + this.defaultConfig = this.configManager.getConfig('rotatingMachine'); // Load default config for rotating machine ( use software type name ? ) this.configUtils = new configUtils(this.defaultConfig); - this.config = this.configUtils.initConfig(machineConfig); - // Init after config is set - this.logger = new logger(this.config.general.logging.enabled,this.config.general.logging.logLevel, this.config.general.name); + // Load a specific curve + this.model = machineConfig.asset.model; // Get the model from the machineConfig + this.curve = this.model ? loadCurve(this.model) : null; + + if (!this.model || !this.curve) { + this.logger.warning(`${!this.model ? 'Model not specified' : 'Curve not found for model ' + this.model} in machineConfig. Cannot make predictions.`); + // Set prediction objects to null to prevent method calls + this.predictFlow = null; + this.predictPower = null; + this.predictCtrl = null; + this.hasCurve = false; + } + else{ + this.hasCurve = true; + machineConfig = { ...machineConfig, asset: { ...machineConfig.asset, machineCurve: this.curve } }; // Merge curve into machineConfig + this.config = this.configUtils.initConfig(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) + } this.state = new state(stateConfig, this.logger); // Init State manager and pass logger this.errorMetrics = new nrmse(errorMetricsConfig, this.logger); @@ -71,10 +89,6 @@ class Machine { this.flowDrift = null; - 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) - this.currentMode = this.config.mode.current; this.currentEfficiencyCurve = {}; this.cog = 0; @@ -239,55 +253,84 @@ class Machine { // Calculate flow based on current pressure and position calcFlow(x) { - const state = this.state.getCurrentState(); + if(!this.hasCurve) { + const state = this.state.getCurrentState(); - if (!["operational", "accelerating", "decelerating"].includes(state)) { - this.measurements.type("flow").variant("predicted").position("downstream").value(0); - this.logger.debug(`Machine is not operational. Setting predicted flow to 0.`); - return 0; - } + if (!["operational", "accelerating", "decelerating"].includes(state)) { + this.measurements.type("flow").variant("predicted").position("downstream").value(0); + 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.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); + return 0; } // Calculate power based on current pressure and position calcPower(x) { - const state = this.state.getCurrentState(); - if (!["operational", "accelerating", "decelerating"].includes(state)) { - this.measurements.type("power").variant("predicted").position('upstream').value(0); - this.logger.debug(`Machine is not operational. Setting predicted power to 0.`); - return 0; - } + if(!this.hasCurve) { + const state = this.state.getCurrentState(); + if (!["operational", "accelerating", "decelerating"].includes(state)) { + this.measurements.type("power").variant("predicted").position('upstream').value(0); + this.logger.debug(`Machine is not operational. Setting predicted power to 0.`); + return 0; + } //this.predictPower.currentX = x; Decrepated const cPower = this.predictPower.y(x); this.measurements.type("power").variant("predicted").position('upstream').value(cPower); //this.logger.debug(`Calculated power: ${cPower} for pressure: ${this.getMeasuredPressure()} and position: ${x}`); return cPower; + } + // If no curve data is available, log a warning and return 0 + this.logger.warn(`No curve data available for power calculation. Returning 0.`); + this.measurements.type("power").variant("predicted").position('upstream').value(0); + return 0; + } // calculate the power consumption using only flow and pressure inputFlowCalcPower(flow) { - this.predictCtrl.currentX = flow; - const cCtrl = this.predictCtrl.y(flow); - this.predictPower.currentX = cCtrl; - const cPower = this.predictPower.y(cCtrl); - return cPower; + if(!this.hasCurve) { + + this.predictCtrl.currentX = flow; + const cCtrl = this.predictCtrl.y(flow); + this.predictPower.currentX = cCtrl; + const cPower = this.predictPower.y(cCtrl); + return cPower; + } + + // If no curve data is available, log a warning and return 0 + this.logger.warn(`No curve data available for power calculation. Returning 0.`); + this.measurements.type("power").variant("predicted").position('upstream').value(0); + return 0; + } // Function to predict control value for a desired flow calcCtrl(x) { - - this.predictCtrl.currentX = x; - const cCtrl = this.predictCtrl.y(x); - this.measurements.type("ctrl").variant("predicted").position('upstream').value(cCtrl); - //this.logger.debug(`Calculated ctrl: ${cCtrl} for pressure: ${this.getMeasuredPressure()} and position: ${x}`); - return cCtrl; + if(!this.hasCurve) { + this.predictCtrl.currentX = x; + const cCtrl = this.predictCtrl.y(x); + this.measurements.type("ctrl").variant("predicted").position('upstream').value(cCtrl); + //this.logger.debug(`Calculated ctrl: ${cCtrl} for pressure: ${this.getMeasuredPressure()} and position: ${x}`); + return cCtrl; + } + + // If no curve data is available, log a warning and return 0 + this.logger.warn(`No curve data available for control calculation. Returning 0.`); + this.measurements.type("ctrl").variant("predicted").position('upstream').value(0); + return 0; }