forked from RnD/measurement
fix for childregistration
This commit is contained in:
@@ -5,7 +5,7 @@
|
||||
* This allows us to keep the Node-RED node clean and focused on wiring up the UI and event handlers.
|
||||
*/
|
||||
const { outputUtils, configManager } = require('generalFunctions');
|
||||
const Measurement = require("../dependencies/measurement/measurement");
|
||||
const Measurement = require("./specificClass");
|
||||
|
||||
/**
|
||||
* Class representing a Measurement Node-RED node.
|
||||
@@ -23,7 +23,7 @@ class MeasurementNode {
|
||||
this.RED = RED;
|
||||
|
||||
// Load default & UI config
|
||||
this._loadConfig(uiConfig);
|
||||
this._loadConfig(uiConfig,this.node);
|
||||
|
||||
// Instantiate core Measurement class
|
||||
this._setupMeasurementClass();
|
||||
@@ -40,15 +40,16 @@ class MeasurementNode {
|
||||
* Load and merge default config with user-defined settings.
|
||||
* @param {object} uiConfig - Raw config from Node-RED UI.
|
||||
*/
|
||||
_loadConfig(uiConfig) {
|
||||
_loadConfig(uiConfig,node) {
|
||||
const cfgMgr = new configManager();
|
||||
this.defaultConfig = cfgMgr.getConfig("measurement");
|
||||
|
||||
console.log( uiConfig.positionVsParent);
|
||||
// Merge UI config over defaults
|
||||
this.config = {
|
||||
general: {
|
||||
name: uiConfig.name,
|
||||
id: uiConfig.id, //need to add this later use node.uuid from a single project file thats unique per location + node-red environment + node
|
||||
id: node.id, //need to add this later use node.uuid from a single project file thats unique per location + node-red environment + node
|
||||
unit: uiConfig.unit, // add converter options later to convert to default units (need like a model that defines this which units we are going to use and then conver to those standards)
|
||||
logging: {
|
||||
enabled: uiConfig.enableLog,
|
||||
@@ -78,7 +79,9 @@ class MeasurementNode {
|
||||
simulation: {
|
||||
enabled: uiConfig.simulator
|
||||
},
|
||||
positionVsParent: uiConfig.position || 'atEquipment', // default to 'atEquipment' if not set
|
||||
functionality: {
|
||||
positionVsParent: uiConfig.positionVsParent || 'atEquipment', // Default to 'atEquipment' if not specified
|
||||
}
|
||||
};
|
||||
|
||||
// Utility for formatting outputs
|
||||
@@ -110,7 +113,7 @@ class MeasurementNode {
|
||||
this.node.send([
|
||||
null,
|
||||
null,
|
||||
{ topic: 'registerChild', payload: this.id, positionVsParent: this.config.functionality.positionVsParent }
|
||||
{ topic: 'registerChild', payload: this.config.general.id, positionVsParent: this.config?.functionality?.positionVsParent || 'atEquipment' },
|
||||
]);
|
||||
}, 100);
|
||||
}
|
||||
|
||||
595
src/specificClass.js
Normal file
595
src/specificClass.js
Normal file
@@ -0,0 +1,595 @@
|
||||
/**
|
||||
* @file Measurement.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.
|
||||
*
|
||||
* Author:
|
||||
* - Rene De Ren
|
||||
* Email:
|
||||
* - rene@thegoldenbasket.nl
|
||||
*
|
||||
* Future Improvements:
|
||||
* - Time-based stability checks
|
||||
* - Warmup handling
|
||||
* - Dynamic outlier detection thresholds
|
||||
* - Dynamic smoothing window and methods
|
||||
* - Alarm and threshold handling
|
||||
* - Maintenance mode
|
||||
* - Historical data and trend analysis
|
||||
*/
|
||||
|
||||
const EventEmitter = require('events');
|
||||
const {logger,configUtils,configManager} = require('generalFunctions');
|
||||
|
||||
class Measurement {
|
||||
constructor(config={}) {
|
||||
|
||||
this.emitter = new EventEmitter(); // Own EventEmitter
|
||||
this.configManager = new configManager();
|
||||
this.defaultConfig = this.configManager.getConfig('measurement');
|
||||
this.configUtils = new configUtils(this.defaultConfig);
|
||||
this.config = this.configUtils.initConfig(config);
|
||||
|
||||
// Init after config is set
|
||||
this.logger = new logger(this.config.general.logging.enabled,this.config.general.logging.logLevel, this.config.general.name);
|
||||
|
||||
// Smoothing
|
||||
this.storedValues = [];
|
||||
|
||||
// Simulation
|
||||
this.simValue = 0;
|
||||
|
||||
// Internal tracking
|
||||
this.inputValue = 0;
|
||||
this.outputAbs = 0;
|
||||
this.outputPercent = 0;
|
||||
|
||||
// Stability
|
||||
this.stableThreshold = null;
|
||||
|
||||
//internal variables
|
||||
this.totalMinValue = Infinity;
|
||||
this.totalMaxValue = -Infinity;
|
||||
this.totalMinSmooth = 0;
|
||||
this.totalMaxSmooth = 0;
|
||||
|
||||
// Scaling
|
||||
this.inputRange = Math.abs(this.config.scaling.inputMax - this.config.scaling.inputMin);
|
||||
this.processRange = Math.abs(this.config.scaling.absMax - this.config.scaling.absMin);
|
||||
|
||||
this.logger.debug(`Measurement id: ${this.config.general.id}, initialized successfully.`);
|
||||
|
||||
}
|
||||
|
||||
// -------- Config Initializers -------- //
|
||||
updateconfig(newConfig) {
|
||||
this.config = this.configUtils.updateConfig(this.config, newConfig);
|
||||
}
|
||||
|
||||
async tick() {
|
||||
if (this.config.simulation.enabled) {
|
||||
this.simulateInput();
|
||||
}
|
||||
|
||||
this.calculateInput(this.inputValue);
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
calibrate() {
|
||||
|
||||
let offset = 0;
|
||||
|
||||
const { isStable } = this.isStable();
|
||||
|
||||
//first check if the input is stable
|
||||
if( !isStable ){
|
||||
this.logger.warn(`Large fluctuations detected between stored values. Calibration aborted.`);
|
||||
}else{
|
||||
|
||||
this.logger.info(`Stable input value detected. Proceeding with calibration.`);
|
||||
|
||||
// offset should be the difference between the input and the output
|
||||
if(this.config.scaling.enabled){
|
||||
offset = this.config.scaling.inputMin - this.outputAbs;
|
||||
} else {
|
||||
offset = this.config.scaling.absMin - this.outputAbs;
|
||||
}
|
||||
|
||||
this.config.scaling.offset = offset;
|
||||
this.logger.info(`Calibration completed. Offset set to ${offset}`);
|
||||
}
|
||||
}
|
||||
|
||||
isStable() {
|
||||
const marginFactor = 2; // or 3, depending on strictness
|
||||
let stableThreshold = 0;
|
||||
|
||||
if (this.storedValues.length < 2) return false;
|
||||
const stdDev = this.standardDeviation(this.storedValues);
|
||||
stableThreshold = stdDev * marginFactor;
|
||||
|
||||
return { isStable: ( stdDev < stableThreshold || stdDev == 0) , stdDev} ;
|
||||
}
|
||||
|
||||
evaluateRepeatability() {
|
||||
|
||||
const { isStable, stdDev } = this.isStable();
|
||||
|
||||
if(this.config.smoothing.smoothMethod == 'none'){
|
||||
this.logger.warn('Repeatability evaluation is not possible without smoothing.');
|
||||
return null;
|
||||
}
|
||||
|
||||
if (this.storedValues.length < 2) {
|
||||
this.logger.warn('Not enough data to evaluate repeatability.');
|
||||
return null;
|
||||
}
|
||||
|
||||
if( isStable == false){
|
||||
this.logger.warn('Data not stable enough to evaluate repeatability.');
|
||||
return null;
|
||||
}
|
||||
|
||||
const standardDeviation = stdDev
|
||||
|
||||
this.logger.info(`Repeatability evaluated. Standard Deviation: ${stdDev}`);
|
||||
|
||||
return standardDeviation;
|
||||
}
|
||||
|
||||
simulateInput() {
|
||||
|
||||
// Simulate input value
|
||||
const absMax = this.config.scaling.absMax;
|
||||
const absMin = this.config.scaling.absMin;
|
||||
const inputMin = this.config.scaling.inputMin;
|
||||
const inputMax = this.config.scaling.inputMax;
|
||||
const sign = Math.random() < 0.5 ? -1 : 1;
|
||||
let maxStep = 0;
|
||||
|
||||
switch ( this.config.scaling.enabled ) {
|
||||
case true:
|
||||
|
||||
maxStep = this.inputRange > 0 ? this.inputRange * 0.05 : 1;
|
||||
|
||||
if (this.simValue < inputMin || this.simValue > inputMax) {
|
||||
this.logger.warn(`Simulated value ${this.simValue} is outside of input range constraining between min=${inputMin} and max=${inputMax}`);
|
||||
this.simValue = this.constrain(this.simValue, inputMin, inputMax);
|
||||
}
|
||||
break;
|
||||
case false:
|
||||
|
||||
maxStep = this.processRange > 0 ? this.processRange * 0.05 : 1;
|
||||
|
||||
if (this.simValue < absMin || this.simValue > absMax) {
|
||||
this.logger.warn(`Simulated value ${this.simValue} is outside of abs range constraining between min=${absMin} and max=${absMax}`);
|
||||
this.simValue = this.constrain(this.simValue, absMin, absMax);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
this.simValue += sign * Math.random() * maxStep;
|
||||
|
||||
this.inputValue = this.simValue;
|
||||
|
||||
}
|
||||
|
||||
outlierDetection(val) {
|
||||
if (this.storedValues.length < 2) return false;
|
||||
|
||||
this.logger.debug(`Outlier detection method: ${this.config.outlierDetection.method}`);
|
||||
|
||||
switch (this.config.outlierDetection.method) {
|
||||
case 'zScore':
|
||||
return this.zScoreOutlierDetection(val);
|
||||
case 'iqr':
|
||||
return this.iqrOutlierDetection(val);
|
||||
case 'modifiedZScore':
|
||||
return this.modifiedZScoreOutlierDetection(val);
|
||||
default:
|
||||
this.logger.warn(`Outlier detection method "${this.config.outlierDetection.method}" is not recognized.`);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
zScoreOutlierDetection(val) {
|
||||
const threshold = this.config.outlierDetection.threshold || 3;
|
||||
const mean = this.mean(this.storedValues);
|
||||
const stdDev = this.standardDeviation(this.storedValues);
|
||||
const zScore = (val - mean) / stdDev;
|
||||
|
||||
if (Math.abs(zScore) > threshold) {
|
||||
this.logger.warn(`Outlier detected using Z-Score method. Z-score=${zScore}`);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
iqrOutlierDetection(val) {
|
||||
const sortedValues = [...this.storedValues].sort((a, b) => a - b);
|
||||
const q1 = sortedValues[Math.floor(sortedValues.length / 4)];
|
||||
const q3 = sortedValues[Math.floor(sortedValues.length * 3 / 4)];
|
||||
const iqr = q3 - q1;
|
||||
const lowerBound = q1 - 1.5 * iqr;
|
||||
const upperBound = q3 + 1.5 * iqr;
|
||||
|
||||
if (val < lowerBound || val > upperBound) {
|
||||
this.logger.warn(`Outlier detected using IQR method. Value=${val}`);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
modifiedZScoreOutlierDetection(val) {
|
||||
const median = this.medianFilter(this.storedValues);
|
||||
const mad = this.medianFilter(this.storedValues.map(v => Math.abs(v - median)));
|
||||
const modifiedZScore = 0.6745 * (val - median) / mad;
|
||||
const threshold = this.config.outlierDetection.threshold || 3.5;
|
||||
|
||||
if (Math.abs(modifiedZScore) > threshold) {
|
||||
this.logger.warn(`Outlier detected using Modified Z-Score method. Modified Z-Score=${modifiedZScore}`);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
calculateInput(value) {
|
||||
|
||||
// Check if the value is an outlier and check if outlier detection is enabled
|
||||
if (this.config.outlierDetection.enabled) {
|
||||
if ( this.outlierDetection(value) ){
|
||||
this.logger.warn(`Outlier detected. Ignoring value=${value}`);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Apply offset
|
||||
let val = this.applyOffset(value);
|
||||
|
||||
// Track raw min/max
|
||||
this.updateMinMaxValues(val);
|
||||
|
||||
// Handle scaling if enabled
|
||||
if (this.config.scaling.enabled) {
|
||||
val = this.handleScaling(val);
|
||||
}
|
||||
|
||||
// Apply smoothing
|
||||
const smoothed = this.applySmoothing(val);
|
||||
|
||||
// Update smoothed min/max and output
|
||||
this.updateSmoothMinMaxValues(smoothed);
|
||||
this.updateOutputAbs(smoothed);
|
||||
}
|
||||
|
||||
applyOffset(value) {
|
||||
return value + this.config.scaling.offset;
|
||||
}
|
||||
|
||||
handleScaling(value) {
|
||||
// Check if input range is valid
|
||||
if (this.inputRange <= 0) {
|
||||
this.logger.warn(`Input range is invalid. Falling back to default range [0, 1].`);
|
||||
this.config.scaling.inputMin = 0;
|
||||
this.config.scaling.inputMax = 1;
|
||||
this.inputRange = this.config.scaling.inputMax - this.config.scaling.inputMin;
|
||||
}
|
||||
|
||||
// Constrain value within input range
|
||||
if (value < this.config.scaling.inputMin || value > this.config.scaling.inputMax) {
|
||||
this.logger.warn(`Value=${value} is outside of INPUT range. Constraining.`);
|
||||
value = this.constrain(value, this.config.scaling.inputMin, this.config.scaling.inputMax);
|
||||
}
|
||||
|
||||
// Interpolate value
|
||||
this.logger.debug(`Interpolating value=${value} between min=${this.config.scaling.inputMin} and max=${this.config.scaling.inputMax} to absMin=${this.config.scaling.absMin} and absMax=${this.config.scaling.absMax}`);
|
||||
return this.interpolateLinear(value, this.config.scaling.inputMin, this.config.scaling.inputMax, this.config.scaling.absMin, this.config.scaling.absMax);
|
||||
}
|
||||
|
||||
constrain(input, inputMin , inputMax) {
|
||||
this.logger.warn(`New value=${input} is constrained to fit between min=${inputMin} and max=${inputMax}`);
|
||||
return Math.min(Math.max(input, inputMin), inputMax);
|
||||
}
|
||||
|
||||
interpolateLinear(iNumber, iMin, iMax, oMin, oMax) {
|
||||
if (iMin >= iMax || oMin >= oMax) {
|
||||
this.logger.warn(`Invalid input for linear interpolation iMin=${JSON.stringify(iMin)} iMax=${iMax} oMin=${JSON.stringify(oMin)} oMax=${oMax}`);
|
||||
return iNumber;
|
||||
}
|
||||
|
||||
const range = iMax - iMin;
|
||||
return oMin + ((iNumber - iMin) * (oMax - oMin)) / range;
|
||||
}
|
||||
|
||||
applySmoothing(value) {
|
||||
|
||||
this.storedValues.push(value);
|
||||
|
||||
// Maintain only the latest 'smoothWindow' number of values
|
||||
if (this.storedValues.length > this.config.smoothing.smoothWindow) {
|
||||
this.storedValues.shift();
|
||||
}
|
||||
|
||||
// Smoothing strategies
|
||||
const smoothingMethods = {
|
||||
none: (arr) => arr[arr.length - 1],
|
||||
mean: (arr) => this.mean(arr),
|
||||
min: (arr) => this.min(arr),
|
||||
max: (arr) => this.max(arr),
|
||||
sd: (arr) => this.standardDeviation(arr),
|
||||
lowPass: (arr) => this.lowPassFilter(arr),
|
||||
highPass: (arr) => this.highPassFilter(arr),
|
||||
weightedMovingAverage: (arr) => this.weightedMovingAverage(arr),
|
||||
bandPass: (arr) => this.bandPassFilter(arr),
|
||||
median: (arr) => this.medianFilter(arr),
|
||||
kalman: (arr) => this.kalmanFilter(arr),
|
||||
savitzkyGolay: (arr) => this.savitzkyGolayFilter(arr),
|
||||
};
|
||||
|
||||
// Ensure the smoothing method is valid
|
||||
const method = this.config.smoothing.smoothMethod;
|
||||
this.logger.debug(`Applying smoothing method "${method}"`);
|
||||
|
||||
if (!smoothingMethods[method]) {
|
||||
this.logger.error(`Smoothing method "${method}" is not implemented.`);
|
||||
return value;
|
||||
}
|
||||
|
||||
// Apply the smoothing method
|
||||
return smoothingMethods[method](this.storedValues);
|
||||
}
|
||||
|
||||
standardDeviation(values) {
|
||||
if (values.length <= 1) return 0;
|
||||
const mean = values.reduce((a, b) => a + b, 0) / values.length;
|
||||
const sqDiffs = values.map(v => (v - mean) ** 2);
|
||||
const variance = sqDiffs.reduce((a, b) => a + b, 0) / (values.length - 1);
|
||||
return Math.sqrt(variance);
|
||||
}
|
||||
|
||||
savitzkyGolayFilter(arr) {
|
||||
const coefficients = [-3, 12, 17, 12, -3]; // Example coefficients for 5-point smoothing
|
||||
const normFactor = coefficients.reduce((a, b) => a + b, 0);
|
||||
|
||||
if (arr.length < coefficients.length) {
|
||||
return arr[arr.length - 1]; // Return last value if array is too small
|
||||
}
|
||||
|
||||
let smoothed = 0;
|
||||
for (let i = 0; i < coefficients.length; i++) {
|
||||
smoothed += arr[arr.length - coefficients.length + i] * coefficients[i];
|
||||
}
|
||||
|
||||
return smoothed / normFactor;
|
||||
}
|
||||
|
||||
kalmanFilter(arr) {
|
||||
let estimate = arr[0];
|
||||
const measurementNoise = 1; // Adjust based on your sensor's characteristics
|
||||
const processNoise = 0.1; // Adjust based on signal variability
|
||||
const kalmanGain = processNoise / (processNoise + measurementNoise);
|
||||
|
||||
for (let i = 1; i < arr.length; i++) {
|
||||
estimate = estimate + kalmanGain * (arr[i] - estimate);
|
||||
}
|
||||
|
||||
return estimate;
|
||||
}
|
||||
|
||||
medianFilter(arr) {
|
||||
const sorted = [...arr].sort((a, b) => a - b);
|
||||
const middle = Math.floor(sorted.length / 2);
|
||||
|
||||
return sorted.length % 2 !== 0
|
||||
? sorted[middle]
|
||||
: (sorted[middle - 1] + sorted[middle]) / 2;
|
||||
}
|
||||
|
||||
bandPassFilter(arr) {
|
||||
const lowPass = this.lowPassFilter(arr); // Apply low-pass filter
|
||||
const highPass = this.highPassFilter(arr); // Apply high-pass filter
|
||||
|
||||
return arr.map((val, idx) => lowPass + highPass - val).pop(); // Combine the filters
|
||||
}
|
||||
|
||||
weightedMovingAverage(arr) {
|
||||
const weights = arr.map((_, i) => i + 1); // Weights increase linearly
|
||||
const weightedSum = arr.reduce((sum, val, idx) => sum + val * weights[idx], 0);
|
||||
const weightTotal = weights.reduce((sum, weight) => sum + weight, 0);
|
||||
|
||||
return weightedSum / weightTotal;
|
||||
}
|
||||
|
||||
highPassFilter(arr) {
|
||||
const alpha = 0.8; // Smoothing factor (0 < alpha <= 1)
|
||||
let filteredValues = [];
|
||||
filteredValues[0] = arr[0];
|
||||
|
||||
for (let i = 1; i < arr.length; i++) {
|
||||
filteredValues[i] = alpha * (filteredValues[i - 1] + arr[i] - arr[i - 1]);
|
||||
}
|
||||
|
||||
return filteredValues[filteredValues.length - 1];
|
||||
}
|
||||
|
||||
lowPassFilter(arr) {
|
||||
const alpha = 0.2; // Smoothing factor (0 < alpha <= 1)
|
||||
let smoothedValue = arr[0];
|
||||
|
||||
for (let i = 1; i < arr.length; i++) {
|
||||
smoothedValue = alpha * arr[i] + (1 - alpha) * smoothedValue;
|
||||
}
|
||||
|
||||
return smoothedValue;
|
||||
}
|
||||
|
||||
// Or also EMA called exponential moving average
|
||||
recursiveLowpassFilter() {
|
||||
|
||||
}
|
||||
|
||||
mean(arr) {
|
||||
return arr.reduce((a, b) => a + b, 0) / arr.length;
|
||||
}
|
||||
|
||||
min(arr) {
|
||||
return Math.min(...arr);
|
||||
}
|
||||
|
||||
max(arr) {
|
||||
return Math.max(...arr);
|
||||
}
|
||||
|
||||
updateMinMaxValues(value) {
|
||||
if (value < this.totalMinValue) {
|
||||
this.totalMinValue = value;
|
||||
}
|
||||
if (value > this.totalMaxValue) {
|
||||
this.totalMaxValue = value;
|
||||
}
|
||||
}
|
||||
|
||||
updateSmoothMinMaxValues(value) {
|
||||
// If this is the first run, initialize them
|
||||
if (this.totalMinSmooth === 0 && this.totalMaxSmooth === 0) {
|
||||
this.totalMinSmooth = value;
|
||||
this.totalMaxSmooth = value;
|
||||
}
|
||||
if (value < this.totalMinSmooth) {
|
||||
this.totalMinSmooth = value;
|
||||
}
|
||||
if (value > this.totalMaxSmooth) {
|
||||
this.totalMaxSmooth = value;
|
||||
}
|
||||
}
|
||||
|
||||
updateOutputAbs(val) {
|
||||
|
||||
//only update on change
|
||||
if(val != this.outputAbs){
|
||||
|
||||
// Constrain value within process range
|
||||
if (val < this.config.scaling.absMin || val > this.config.scaling.absMax) {
|
||||
this.logger.warn(`Output value=${val} is outside of ABS range. Constraining.`);
|
||||
val = this.constrain(val, this.config.scaling.absMin, this.config.scaling.absMax);
|
||||
}
|
||||
|
||||
this.outputAbs = Math.round(val * 100) / 100;
|
||||
this.outputPercent = this.updateOutputPercent(val);
|
||||
|
||||
this.logger.debug(`[DEBUG] Emitting mAbs=${this.outputAbs}, Current listeners:`, this.emitter.eventNames());
|
||||
this.emitter.emit('mAbs', this.outputAbs);
|
||||
}
|
||||
}
|
||||
|
||||
updateOutputPercent(value) {
|
||||
|
||||
let outputPercent;
|
||||
|
||||
if (this.processRange <= 0) {
|
||||
this.logger.debug(`Process range is smaller or equal to 0 interpolating between input range`);
|
||||
outputPercent = this.interpolateLinear( value, this.totalMinValue, this.totalMaxValue, this.config.interpolation.percentMin, this.config.interpolation.percentMax );
|
||||
}
|
||||
else {
|
||||
outputPercent = this.interpolateLinear( value, this.config.scaling.absMin, this.config.scaling.absMax, this.config.interpolation.percentMin, this.config.interpolation.percentMax );
|
||||
}
|
||||
|
||||
return Math.round(outputPercent * 100) / 100;
|
||||
}
|
||||
|
||||
toggleSimulation(){
|
||||
this.config.simulation.enabled = !this.config.simulation.enabled;
|
||||
}
|
||||
|
||||
toggleOutlierDetection() {
|
||||
this.config.outlierDetection = !this.config.outlierDetection;
|
||||
}
|
||||
|
||||
getOutput() {
|
||||
return {
|
||||
mAbs: this.outputAbs,
|
||||
mPercent: this.outputPercent,
|
||||
totalMinValue: this.totalMinValue,
|
||||
totalMaxValue: this.totalMaxValue,
|
||||
totalMinSmooth: this.totalMinSmooth,
|
||||
totalMaxSmooth: this.totalMaxSmooth,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Measurement;
|
||||
|
||||
/*
|
||||
// Testing the class
|
||||
const configuration = {
|
||||
general: {
|
||||
name: "PT1",
|
||||
logging: {
|
||||
enabled: true,
|
||||
logLevel: "debug",
|
||||
},
|
||||
},
|
||||
scaling:{
|
||||
enabled: true,
|
||||
inputMin: 0,
|
||||
inputMax: 3000,
|
||||
absMin: 500,
|
||||
absMax: 4000,
|
||||
offset: 1000
|
||||
},
|
||||
smoothing: {
|
||||
smoothWindow: 10,
|
||||
smoothMethod: 'mean',
|
||||
},
|
||||
simulation: {
|
||||
enabled: true,
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
const m = new Measurement(configuration);
|
||||
|
||||
m.logger.info(`Measurement created with config : ${JSON.stringify(m.config)}`);
|
||||
|
||||
m.logger.setLogLevel("debug");
|
||||
|
||||
m.emitter.on('mAbs', (val) => {
|
||||
m.logger.info(`Received : ${val}`);
|
||||
const repeatability = m.evaluateRepeatability();
|
||||
if (repeatability !== null) {
|
||||
m.logger.info(`Current repeatability (standard deviation): ${repeatability}`);
|
||||
}
|
||||
});
|
||||
|
||||
const tickLoop = setInterval(changeInput,1000);
|
||||
|
||||
function changeInput(){
|
||||
m.logger.info(`tick...`);
|
||||
m.tick();
|
||||
//m.inputValue = 5;
|
||||
}
|
||||
|
||||
// */
|
||||
Reference in New Issue
Block a user