diff --git a/src/nodeClass.js b/src/nodeClass.js index cbc7194..2b83066 100644 --- a/src/nodeClass.js +++ b/src/nodeClass.js @@ -114,8 +114,8 @@ class nodeClass { 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('atEquipment').getCurrentValue()); + const flow = Math.round(m.measurements.type("flow").variant("predicted").position('downstream').getCurrentValue('m3/h')); + const power = Math.round(m.measurements.type("power").variant("predicted").position('atequipment').getCurrentValue('kW')); let symbolState; switch(state){ case "off": @@ -145,6 +145,9 @@ class nodeClass { case "decelerating": symbolState = "⏪"; break; + case "maintenance": + symbolState = "🔧"; + break; } const position = m.state.getCurrentPosition(); const roundedPosition = Math.round(position * 100) / 100; diff --git a/src/specificClass.js b/src/specificClass.js index 0c1ed60..b0379b6 100644 --- a/src/specificClass.js +++ b/src/specificClass.js @@ -48,7 +48,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,6 +78,12 @@ class Machine { this.updatePosition(); }); + //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(); + }); + this.child = {}; // object to hold child information so we know on what to subscribe this.childRegistrationUtils = new childRegistrationUtils(this); // Child registration utility @@ -102,6 +118,53 @@ class Machine { }); + // --- KPI tracking --- + this.kpi = { + failures: 0, + totalRuntimeHours: 0, + totalDowntimeHours: 0, + lastFailureTime: null, + lastRepairTime: null, + MTBF: 0, + MTTR: 0, + availability: 0 + }; + + this.assetHealth = { + index: 0 // 0 = optimal, 5 = failure + }; + + this.state.emitter.on('stateChange', (payload) => { + + const stateStr = typeof payload === 'string' + ? payload + : (payload?.state ?? payload?.newState ?? payload); + + if (typeof stateStr !== 'string') { + this.logger.warn(`stateChange event without parsable state: ${JSON.stringify(payload)}`); + return; + } + + this._handleStateChangeForKPI(stateStr); + }); + + + } + + _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); + } + } + + _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); + } } /*------------------- Register child events -------------------*/ @@ -192,43 +255,68 @@ _callMeasurementHandler(measurementType, value, position, context) { // -------- 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}; - } + this.logger.debug("hello"); + //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.debug("hello2"); 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; @@ -528,13 +616,14 @@ _callMeasurementHandler(measurementType, value, position, context) { // Update predicted flow if you have prediction capability if (this.predictFlow) { - this.measurements.type("flow").variant("predicted").position("atEquipment").value(this.predictFlow.outputY || 0); + this.measurements.type("flow").variant("predicted").position("downstream").value(this.predictFlow.outputY || 0); } } // 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); } @@ -718,7 +807,6 @@ _handleStateChangeForKPI(newState) { }; } - // Calculate the center of gravity for current pressure calcCog() { @@ -834,23 +922,12 @@ _handleStateChangeForKPI(newState) { // 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; - } + Object.entries(this.measurements.measurements).forEach(([type, variants]) => { + Object.entries(variants).forEach(([variant, positions]) => { + Object.entries(positions).forEach(([position, measurement]) => { + output[`${type}.${variant}.${position}`] = measurement.getCurrentValue(); + }); }); }); @@ -870,6 +947,7 @@ _handleStateChangeForKPI(newState) { output["asset_tag_number"] = 'L001'; // output["asset_tag_number"] = this.assetTagNumber; + output["maintenanceTime"] = this.state.getMaintenanceTimeHours(); if(this.flowDrift != null){ const flowDrift = this.flowDrift;