forked from RnD/rotatingMachine
Compare commits
13 Commits
d2a0274eb3
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fa5083840b | ||
|
|
f450eab56e | ||
|
|
9e7c951aa8 | ||
| 818c989a86 | |||
|
|
4b5ec33c1d | ||
|
|
51f966cfb9 | ||
|
|
4ae6beba37 | ||
|
|
59dec46846 | ||
|
|
133cadf225 | ||
|
|
60d7dcb1ac | ||
| 5a14f44fdd | |||
| c081acae4e | |||
| 08185243bc |
24
README.md
24
README.md
@@ -1 +1,23 @@
|
|||||||
# rotating machine
|
# rotating machine
|
||||||
|
---Explanation KPIs---
|
||||||
|
Mean Time Between Failures: MTBF is a key indicator of asset reliability. It represents the average amount of time between two consecutive failures during normal operation. The higher the value, the better.
|
||||||
|
- Formula: MTBF = runtime / failures
|
||||||
|
|
||||||
|
Mean Time to Repair: MTTR focuses on the speed of failure recovery. It indicates how long it takes on average to repair a failure and restore the system to operational condition. The lower the value, the better.
|
||||||
|
- Formula: MTTR = downtime / failures
|
||||||
|
|
||||||
|
Asset Availability: Asset availability indicates how often machines are available for use. Higher availability means that the equipment experiences fewer breakdowns and remains operational for a greater amount of time.
|
||||||
|
- Formula: availability = MTBF / (MTBF + MTTR) * 100%
|
||||||
|
|
||||||
|
Asset Health Index: A score ranging from 0 (optimal condition) to 5 (worst case) that reflects the overall health status of an asset.
|
||||||
|
|
||||||
|
|
||||||
|
---KPI message---
|
||||||
|
The message consists of the following components:
|
||||||
|
- asset tagnumber:
|
||||||
|
- asset availability
|
||||||
|
- mean time between failures
|
||||||
|
- mean time to repair
|
||||||
|
- asset health index
|
||||||
|
- asset health color: Gives a color based on the asset health index (0 = Darkgreen, 1 = Green, 2 = Yellow, 3 = Orange, 4 = Red, 5 = Darkred.)
|
||||||
|
- total failures: the total number of failures that have occured for a particular asset
|
||||||
@@ -114,8 +114,8 @@ class nodeClass {
|
|||||||
try {
|
try {
|
||||||
const mode = m.currentMode;
|
const mode = m.currentMode;
|
||||||
const state = m.state.getCurrentState();
|
const state = m.state.getCurrentState();
|
||||||
const flow = Math.round(m.measurements.type("flow").variant("predicted").position('downstream').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('upstream').getCurrentValue());
|
const power = Math.round(m.measurements.type("power").variant("predicted").position('atequipment').getCurrentValue('kW'));
|
||||||
let symbolState;
|
let symbolState;
|
||||||
switch(state){
|
switch(state){
|
||||||
case "off":
|
case "off":
|
||||||
@@ -145,6 +145,9 @@ class nodeClass {
|
|||||||
case "decelerating":
|
case "decelerating":
|
||||||
symbolState = "⏪";
|
symbolState = "⏪";
|
||||||
break;
|
break;
|
||||||
|
case "maintenance":
|
||||||
|
symbolState = "🔧";
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
const position = m.state.getCurrentPosition();
|
const position = m.state.getCurrentPosition();
|
||||||
const roundedPosition = Math.round(position * 100) / 100;
|
const roundedPosition = Math.round(position * 100) / 100;
|
||||||
|
|||||||
@@ -48,7 +48,17 @@ class Machine {
|
|||||||
this.errorMetrics = new nrmse(errorMetricsConfig, this.logger);
|
this.errorMetrics = new nrmse(errorMetricsConfig, this.logger);
|
||||||
|
|
||||||
// Initialize measurements
|
// 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.interpolation = new interpolation();
|
||||||
|
|
||||||
this.flowDrift = null;
|
this.flowDrift = null;
|
||||||
@@ -68,9 +78,93 @@ class Machine {
|
|||||||
this.updatePosition();
|
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.child = {}; // object to hold child information so we know on what to subscribe
|
||||||
this.childRegistrationUtils = new childRegistrationUtils(this); // Child registration utility
|
this.childRegistrationUtils = new childRegistrationUtils(this); // Child registration utility
|
||||||
|
|
||||||
|
// --- 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);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
// --- 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 -------------------*/
|
/*------------------- Register child events -------------------*/
|
||||||
@@ -116,10 +210,6 @@ _callMeasurementHandler(measurementType, value, position, context) {
|
|||||||
this.updateMeasuredFlow(value, position, context);
|
this.updateMeasuredFlow(value, position, context);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'temperature':
|
|
||||||
this.updateMeasuredTemperature(value, position, context);
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
default:
|
||||||
this.logger.warn(`No handler for measurement type: ${measurementType}`);
|
this.logger.warn(`No handler for measurement type: ${measurementType}`);
|
||||||
// Generic handler - just update position
|
// Generic handler - just update position
|
||||||
@@ -165,43 +255,68 @@ _callMeasurementHandler(measurementType, value, position, context) {
|
|||||||
// -------- Mode and Input Management -------- //
|
// -------- Mode and Input Management -------- //
|
||||||
isValidSourceForMode(source, mode) {
|
isValidSourceForMode(source, mode) {
|
||||||
const allowedSourcesSet = this.config.mode.allowedSources[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) {
|
isValidActionForMode(action, mode) {
|
||||||
const allowedActionsSet = this.config.mode.allowedActions[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) {
|
async handleInput(source, action, parameter) {
|
||||||
|
|
||||||
if (!this.isValidSourceForMode(source, this.currentMode)) {
|
this.logger.debug("hello");
|
||||||
let warningTxt = `Source '${source}' is not valid for mode '${this.currentMode}'.`;
|
//sanitize input
|
||||||
this.logger.warn(warningTxt);
|
if( typeof action !== 'string'){this.logger.error(`Action must be string`); return;}
|
||||||
return {status : false , feedback: warningTxt};
|
//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}'.`);
|
this.logger.info(`Handling input from source '${source}' with action '${action}' in mode '${this.currentMode}'.`);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
switch (action) {
|
switch (action) {
|
||||||
case "execSequence":
|
|
||||||
|
case "execsequence":
|
||||||
return await this.executeSequence(parameter);
|
return await this.executeSequence(parameter);
|
||||||
|
|
||||||
case "execMovement":
|
case "execmovement":
|
||||||
return await this.setpoint(parameter);
|
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
|
// Calculate the control value for a desired flow
|
||||||
const pos = this.calcCtrl(parameter);
|
const pos = this.calcCtrl(parameter);
|
||||||
// Move to the desired setpoint
|
// Move to the desired setpoint
|
||||||
return await this.setpoint(pos);
|
return await this.setpoint(pos);
|
||||||
|
|
||||||
case "emergencyStop":
|
case "emergencystop":
|
||||||
this.logger.warn(`Emergency stop activated by '${source}'.`);
|
this.logger.warn(`Emergency stop activated by '${source}'.`);
|
||||||
return await this.executeSequence("emergencyStop");
|
return await this.executeSequence("emergencyStop");
|
||||||
|
|
||||||
case "statusCheck":
|
case "statuscheck":
|
||||||
this.logger.info(`Status Check: Mode = '${this.currentMode}', Source = '${source}'.`);
|
this.logger.info(`Status Check: Mode = '${this.currentMode}', Source = '${source}'.`);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@@ -501,13 +616,14 @@ _callMeasurementHandler(measurementType, value, position, context) {
|
|||||||
|
|
||||||
// Update predicted flow if you have prediction capability
|
// Update predicted flow if you have prediction capability
|
||||||
if (this.predictFlow) {
|
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
|
// Helper method for operational state check
|
||||||
_isOperationalState() {
|
_isOperationalState() {
|
||||||
const state = this.state.getCurrentState();
|
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);
|
return ["operational", "accelerating", "decelerating"].includes(state);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -532,6 +648,136 @@ _callMeasurementHandler(measurementType, value, position, context) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/////////////////////////////
|
||||||
|
/**
|
||||||
|
* Compute a single drift score in [0..1] using predicted vs measured series.
|
||||||
|
* Uses min/max of the *predicted* window as normalization range.
|
||||||
|
* If no usable data -> returns 0 (neutral).
|
||||||
|
*/
|
||||||
|
_computeDriftScore() {
|
||||||
|
try {
|
||||||
|
const metrics = [
|
||||||
|
{ key: "pressure", pos: "downstream" },
|
||||||
|
{ key: "flow", pos: "downstream" },
|
||||||
|
{ key: "power", pos: "atEquipment" }
|
||||||
|
];
|
||||||
|
|
||||||
|
const values = [];
|
||||||
|
|
||||||
|
for (const m of metrics) {
|
||||||
|
const pred = this.measurements.type(m.key).variant("predicted").position(m.pos).getAllValues()?.values;
|
||||||
|
const meas = this.measurements.type(m.key).variant("measured").position(m.pos).getAllValues()?.values;
|
||||||
|
|
||||||
|
if (!Array.isArray(pred) || !Array.isArray(meas) || pred.length < 2 || meas.length < 2) continue;
|
||||||
|
|
||||||
|
const expectedMin = Math.min(...pred);
|
||||||
|
const expectedMax = Math.max(...pred);
|
||||||
|
if (!Number.isFinite(expectedMin) || !Number.isFinite(expectedMax) || expectedMax === expectedMin) continue;
|
||||||
|
|
||||||
|
const drift = this.errorMetrics.assessDrift(pred, meas, expectedMin, expectedMax);
|
||||||
|
if (Number.isFinite(drift)) {
|
||||||
|
// assessDrift is already normalized; keep it in [0..1]
|
||||||
|
values.push(Math.max(0, Math.min(1, Math.abs(drift))));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (values.length === 0) return 0; // neutral if no data
|
||||||
|
const avg = values.reduce((s, v) => s + v, 0) / values.length;
|
||||||
|
return Math.max(0, Math.min(1, avg));
|
||||||
|
} catch (e) {
|
||||||
|
this.logger?.warn?.(`Drift score error: ${e.message}`);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_calculateAssetHealthIndex() {
|
||||||
|
try {
|
||||||
|
// 1) Hard fail -> worst health
|
||||||
|
// if (this.state?.getCurrentState && this.state.getCurrentState() === "failed")
|
||||||
|
if (["off"].includes(this.state?.getCurrentState?.())){
|
||||||
|
this.assetHealth.index = 5;
|
||||||
|
return 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2) Inputs (clamped to 0..1)
|
||||||
|
const availability = typeof this.kpi?.availability === 'number' ? this.kpi.availability : 1;
|
||||||
|
const unavailability = 1 - Math.max(0, Math.min(1, availability));
|
||||||
|
|
||||||
|
const effPenalty = Math.max(0, Math.min(1, typeof this.relDistFromPeak === 'number' ? this.relDistFromPeak : 0));
|
||||||
|
|
||||||
|
const driftScore = this._computeDriftScore(); // 0..1
|
||||||
|
|
||||||
|
// 3) Blend (weights sum to 1.0)
|
||||||
|
// Tweak these if you like: e.g. make drift more/less important.
|
||||||
|
const wAvail = 0.4; // unavailability weight
|
||||||
|
const wDrift = 0.4; // drift weight
|
||||||
|
const wEff = 0.2; // efficiency distance weight
|
||||||
|
|
||||||
|
const score01 = (wAvail * unavailability) + (wDrift * driftScore) + (wEff * effPenalty);
|
||||||
|
|
||||||
|
// 4) Scale to 0..5 integer, clamp
|
||||||
|
const index = Math.max(0, Math.min(5, Math.round(score01 * 5)));
|
||||||
|
|
||||||
|
this.assetHealth.index = index;
|
||||||
|
return index;
|
||||||
|
} catch (err) {
|
||||||
|
this.logger?.error?.(`AHI calc error: ${err.message}`);
|
||||||
|
this.assetHealth.index = 0;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_handleStateChangeForKPI(newState) {
|
||||||
|
const now = Date.now();
|
||||||
|
const runtime = this.state.getRunTimeHours();
|
||||||
|
const lastState = this.state.getPreviousState?.() || "unknown";
|
||||||
|
|
||||||
|
// --- Treat OFF as failure and start of downtime ---
|
||||||
|
if (newState === "off") {
|
||||||
|
this.kpi.failures++; // always count a new failure when OFF
|
||||||
|
this.kpi.lastFailureTime = now; // mark the start of downtime
|
||||||
|
this.logger.warn(`Machine OFF (counted as failure). Total failures: ${this.kpi.failures}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- When we leave OFF and become OPERATIONAL, book downtime ---
|
||||||
|
if (newState === "operational") {
|
||||||
|
// Only calculate downtime if we had an OFF period before
|
||||||
|
if (this.kpi.lastFailureTime != null) {
|
||||||
|
const downtimeHours = (now - this.kpi.lastFailureTime) / 3600000;
|
||||||
|
this.kpi.totalDowntimeHours += downtimeHours;
|
||||||
|
this.kpi.lastRepairTime = now; // moment of "repaired"
|
||||||
|
this.kpi.lastFailureTime = null; // close downtime window
|
||||||
|
this.logger.info(`OFF → OPERATIONAL. Added ${downtimeHours.toFixed(2)}h downtime.`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Compute KPI Metrics ---
|
||||||
|
const failures = this.kpi.failures;
|
||||||
|
const downtime = this.kpi.totalDowntimeHours;
|
||||||
|
|
||||||
|
// If no failures yet: MTBF = total runtime; MTTR = 0
|
||||||
|
this.kpi.MTBF = failures > 0 ? runtime / failures : runtime;
|
||||||
|
this.kpi.MTTR = failures > 0 ? downtime / failures : 0;
|
||||||
|
|
||||||
|
// --- Compute Availability ---
|
||||||
|
const mtbf = this.kpi.MTBF ?? 0;
|
||||||
|
const mttr = this.kpi.MTTR ?? 0;
|
||||||
|
|
||||||
|
if (mtbf <= 0 && mttr <= 0) {
|
||||||
|
this.kpi.availability = 1; // Default: 100% if no data
|
||||||
|
} else {
|
||||||
|
const availability = mtbf / (mtbf + mttr);
|
||||||
|
this.kpi.availability = Math.min(1, Math.max(0, availability)); // clamp 0–1
|
||||||
|
}
|
||||||
|
|
||||||
|
this.logger.debug(
|
||||||
|
`KPI updated — MTBF: ${this.kpi.MTBF.toFixed(2)}h, MTTR: ${this.kpi.MTTR.toFixed(2)}h, ` +
|
||||||
|
`Availability: ${(this.kpi.availability * 100).toFixed(2)}%`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
//////////////////////////////////////////////
|
||||||
|
|
||||||
calcDistanceFromPeak(currentEfficiency,peakEfficiency){
|
calcDistanceFromPeak(currentEfficiency,peakEfficiency){
|
||||||
return Math.abs(currentEfficiency - peakEfficiency);
|
return Math.abs(currentEfficiency - peakEfficiency);
|
||||||
@@ -561,7 +807,6 @@ _callMeasurementHandler(measurementType, value, position, context) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Calculate the center of gravity for current pressure
|
// Calculate the center of gravity for current pressure
|
||||||
calcCog() {
|
calcCog() {
|
||||||
|
|
||||||
@@ -677,23 +922,12 @@ _callMeasurementHandler(measurementType, value, position, context) {
|
|||||||
|
|
||||||
// Improved output object generation
|
// Improved output object generation
|
||||||
const output = {};
|
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();
|
Object.entries(this.measurements.measurements).forEach(([type, variants]) => {
|
||||||
const upstreamVal = this.measurements.type(type).variant(variant).position("upstream").getCurrentValue();
|
Object.entries(variants).forEach(([variant, positions]) => {
|
||||||
|
Object.entries(positions).forEach(([position, measurement]) => {
|
||||||
if (downstreamVal != null) {
|
output[`${type}.${variant}.${position}`] = measurement.getCurrentValue();
|
||||||
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;
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -706,6 +940,14 @@ _callMeasurementHandler(measurementType, value, position, context) {
|
|||||||
output["cog"] = this.cog; // flow / power efficiency
|
output["cog"] = this.cog; // flow / power efficiency
|
||||||
output["NCog"] = this.NCog; // normalized cog
|
output["NCog"] = this.NCog; // normalized cog
|
||||||
output["NCogPercent"] = Math.round(this.NCog * 100 * 100) / 100 ;
|
output["NCogPercent"] = Math.round(this.NCog * 100 * 100) / 100 ;
|
||||||
|
output["kpi_MTBF"] = this.kpi.MTBF;
|
||||||
|
output["kpi_MTTR"] = this.kpi.MTTR;
|
||||||
|
output["kpi_assetAvailability"] = Math.round(this.kpi.availability * 100 * 100) / 100;
|
||||||
|
output["kpi_totalFailuresCount"] = this.kpi.failures;
|
||||||
|
output["asset_tag_number"] = 'L001';
|
||||||
|
|
||||||
|
// output["asset_tag_number"] = this.assetTagNumber;
|
||||||
|
output["maintenanceTime"] = this.state.getMaintenanceTimeHours();
|
||||||
|
|
||||||
if(this.flowDrift != null){
|
if(this.flowDrift != null){
|
||||||
const flowDrift = this.flowDrift;
|
const flowDrift = this.flowDrift;
|
||||||
@@ -720,6 +962,21 @@ _callMeasurementHandler(measurementType, value, position, context) {
|
|||||||
output["effRelDistFromPeak"] = this.relDistFromPeak;
|
output["effRelDistFromPeak"] = this.relDistFromPeak;
|
||||||
//this.logger.debug(`Output: ${JSON.stringify(output)}`);
|
//this.logger.debug(`Output: ${JSON.stringify(output)}`);
|
||||||
|
|
||||||
|
|
||||||
|
/////////////////////////////////
|
||||||
|
// this._calculateAssetHealthIndex();
|
||||||
|
// output["assetHealthIndex"] = this.assetHealth.index;
|
||||||
|
|
||||||
|
this._calculateAssetHealthIndex();
|
||||||
|
output["assetHealthIndex"] = this.assetHealth.index;
|
||||||
|
|
||||||
|
// 0 = darkgreen, 1 = green, 2 = yellow, 3 = orange, 4 = red, 5 = darkred
|
||||||
|
// const healthColors = ["darkgreen", "green", "yellow", "orange", "red", "darkred"];
|
||||||
|
const healthColors = ["#006400", "#008000", "#FFFF00", "#FFA500", "#FF0000", "#8B0000"];
|
||||||
|
output["assetHealthColor"] = healthColors[this.assetHealth.index] || "unknown";
|
||||||
|
//////////////////////////
|
||||||
|
|
||||||
|
|
||||||
return output;
|
return output;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -729,9 +986,9 @@ _callMeasurementHandler(measurementType, value, position, context) {
|
|||||||
module.exports = Machine;
|
module.exports = Machine;
|
||||||
|
|
||||||
/*------------------- Testing -------------------*/
|
/*------------------- Testing -------------------*/
|
||||||
/*
|
|
||||||
|
|
||||||
curve = require('C:/Users/zn375/.node-red/public/fallbackData.json');
|
/*
|
||||||
|
//curve = require('C:/Users/zn375/.node-red/public/fallbackData.json');
|
||||||
|
|
||||||
//import a child
|
//import a child
|
||||||
const Child = require('../../measurement/src/specificClass');
|
const Child = require('../../measurement/src/specificClass');
|
||||||
@@ -747,7 +1004,6 @@ const PT1 = new Child(config={
|
|||||||
},
|
},
|
||||||
functionality:{
|
functionality:{
|
||||||
softwareType:"measurement",
|
softwareType:"measurement",
|
||||||
positionVsParent:"upstream",
|
|
||||||
},
|
},
|
||||||
asset:{
|
asset:{
|
||||||
supplier:"Vega",
|
supplier:"Vega",
|
||||||
@@ -769,7 +1025,6 @@ const PT2 = new Child(config={
|
|||||||
},
|
},
|
||||||
functionality:{
|
functionality:{
|
||||||
softwareType:"measurement",
|
softwareType:"measurement",
|
||||||
positionVsParent:"upstream",
|
|
||||||
},
|
},
|
||||||
asset:{
|
asset:{
|
||||||
supplier:"Vega",
|
supplier:"Vega",
|
||||||
@@ -785,18 +1040,17 @@ console.log(`Creating machine...`);
|
|||||||
|
|
||||||
const machineConfig = {
|
const machineConfig = {
|
||||||
general: {
|
general: {
|
||||||
name: "Hydrostal",
|
name: "Hidrostal",
|
||||||
logging: {
|
logging: {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
logLevel: "debug",
|
logLevel: "debug",
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
asset: {
|
asset: {
|
||||||
supplier: "Hydrostal",
|
supplier: "Hidrostal",
|
||||||
type: "pump",
|
type: "pump",
|
||||||
category: "centrifugal",
|
category: "centrifugal",
|
||||||
model: "H05K-S03R+HGM1X-X280KO", // Ensure this field is present.
|
model: "hidrostal-H05K-S03R", // Ensure this field is present.
|
||||||
machineCurve: curve["machineCurves"]["Hydrostal"]["H05K-S03R+HGM1X-X280KO"],
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -825,18 +1079,17 @@ const machine = new Machine(machineConfig, stateConfig);
|
|||||||
machine.logger.info(`Registering child...`);
|
machine.logger.info(`Registering child...`);
|
||||||
machine.childRegistrationUtils.registerChild(PT1, "upstream");
|
machine.childRegistrationUtils.registerChild(PT1, "upstream");
|
||||||
machine.childRegistrationUtils.registerChild(PT2, "downstream");
|
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.logger.info(`Enable sim...`);
|
||||||
PT1.toggleSimulation();
|
PT1.toggleSimulation();
|
||||||
PT2.logger.info(`Enable sim...`);
|
PT2.logger.info(`Enable sim...`);
|
||||||
PT2.toggleSimulation();
|
PT2.toggleSimulation();
|
||||||
machine.getOutput();
|
*/
|
||||||
|
|
||||||
|
|
||||||
//manual test
|
//manual test
|
||||||
//machine.handleInput("parent", "execSequence", "startup");
|
//machine.handleInput("parent", "execSequence", "startup");
|
||||||
|
/*
|
||||||
machine.measurements.type("pressure").variant("measured").position('upstream').value(-200);
|
machine.measurements.type("pressure").variant("measured").position('upstream').value(-200);
|
||||||
machine.measurements.type("pressure").variant("measured").position('downstream').value(1000);
|
machine.measurements.type("pressure").variant("measured").position('downstream').value(1000);
|
||||||
|
|
||||||
@@ -846,8 +1099,8 @@ const tickLoop = setInterval(changeInput,1000);
|
|||||||
|
|
||||||
function changeInput(){
|
function changeInput(){
|
||||||
PT1.logger.info(`tick...`);
|
PT1.logger.info(`tick...`);
|
||||||
PT1.tick();
|
//PT1.tick();
|
||||||
PT2.tick();
|
//PT2.tick();
|
||||||
}
|
}
|
||||||
|
|
||||||
async function testingSequences(){
|
async function testingSequences(){
|
||||||
|
|||||||
Reference in New Issue
Block a user