first commit
This commit is contained in:
595
dependencies/measurement/measurement.js
vendored
Normal file
595
dependencies/measurement/measurement.js
vendored
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 = require('../../../generalFunctions/helper/logger');
|
||||
const defaultConfig = require('./measurementConfig.json');
|
||||
const ConfigUtils = require('../../../generalFunctions/helper/configUtils');
|
||||
|
||||
class Measurement {
|
||||
constructor(config={}) {
|
||||
|
||||
this.emitter = new EventEmitter(); // Own EventEmitter
|
||||
this.configUtils = new ConfigUtils(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;
|
||||
}
|
||||
|
||||
// */
|
||||
357
dependencies/measurement/measurementConfig.json
vendored
Normal file
357
dependencies/measurement/measurementConfig.json
vendored
Normal file
@@ -0,0 +1,357 @@
|
||||
{
|
||||
"general": {
|
||||
"name": {
|
||||
"default": "Measurement Configuration",
|
||||
"rules": {
|
||||
"type": "string",
|
||||
"description": "A human-readable name or label for this measurement configuration."
|
||||
}
|
||||
},
|
||||
"id": {
|
||||
"default": null,
|
||||
"rules": {
|
||||
"type": "string",
|
||||
"nullable": true,
|
||||
"description": "A unique identifier for this configuration. If not provided, defaults to null."
|
||||
}
|
||||
},
|
||||
"unit": {
|
||||
"default": "unitless",
|
||||
"rules": {
|
||||
"type": "string",
|
||||
"description": "The unit of measurement for this configuration (e.g., 'meters', 'seconds', 'unitless')."
|
||||
}
|
||||
},
|
||||
"logging": {
|
||||
"logLevel": {
|
||||
"default": "info",
|
||||
"rules": {
|
||||
"type": "enum",
|
||||
"values": [
|
||||
{
|
||||
"value": "debug",
|
||||
"description": "Log messages are printed for debugging purposes."
|
||||
},
|
||||
{
|
||||
"value": "info",
|
||||
"description": "Informational messages are printed."
|
||||
},
|
||||
{
|
||||
"value": "warn",
|
||||
"description": "Warning messages are printed."
|
||||
},
|
||||
{
|
||||
"value": "error",
|
||||
"description": "Error messages are printed."
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"enabled": {
|
||||
"default": true,
|
||||
"rules": {
|
||||
"type": "boolean",
|
||||
"description": "Indicates whether logging is active. If true, log messages will be generated."
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"functionality": {
|
||||
"softwareType": {
|
||||
"default": "measurement",
|
||||
"rules": {
|
||||
"type": "string",
|
||||
"description": "Specified software type for this configuration."
|
||||
}
|
||||
},
|
||||
"role": {
|
||||
"default": "Sensor",
|
||||
"rules": {
|
||||
"type": "string",
|
||||
"description": "Indicates the role this configuration plays (e.g., sensor, controller, etc.)."
|
||||
}
|
||||
}
|
||||
},
|
||||
"asset": {
|
||||
"uuid": {
|
||||
"default": null,
|
||||
"rules": {
|
||||
"type": "string",
|
||||
"nullable": true,
|
||||
"description": "Asset tag number which is a universally unique identifier for this asset. May be null if not assigned."
|
||||
}
|
||||
},
|
||||
"tagCode":{
|
||||
"default": null,
|
||||
"rules": {
|
||||
"type": "string",
|
||||
"nullable": true,
|
||||
"description": "Asset tag code which is a unique identifier for this asset. May be null if not assigned."
|
||||
}
|
||||
},
|
||||
"geoLocation": {
|
||||
"default": {
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"z": 0
|
||||
},
|
||||
"rules": {
|
||||
"type": "object",
|
||||
"description": "An object representing the asset's physical coordinates or location.",
|
||||
"schema": {
|
||||
"x": {
|
||||
"default": 0,
|
||||
"rules": {
|
||||
"type": "number",
|
||||
"description": "X coordinate of the asset's location."
|
||||
}
|
||||
},
|
||||
"y": {
|
||||
"default": 0,
|
||||
"rules": {
|
||||
"type": "number",
|
||||
"description": "Y coordinate of the asset's location."
|
||||
}
|
||||
},
|
||||
"z": {
|
||||
"default": 0,
|
||||
"rules": {
|
||||
"type": "number",
|
||||
"description": "Z coordinate of the asset's location."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"supplier": {
|
||||
"default": "Unknown",
|
||||
"rules": {
|
||||
"type": "string",
|
||||
"description": "The supplier or manufacturer of the asset."
|
||||
}
|
||||
},
|
||||
"type": {
|
||||
"default": "sensor",
|
||||
"rules": {
|
||||
"type": "enum",
|
||||
"values": [
|
||||
{
|
||||
"value": "sensor",
|
||||
"description": "A device that detects or measures a physical property and responds to it (e.g. temperature sensor)."
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"subType": {
|
||||
"default": "pressure",
|
||||
"rules": {
|
||||
"type": "string",
|
||||
"description": "A more specific classification within 'type'. For example, 'pressure' for a pressure sensor."
|
||||
}
|
||||
},
|
||||
"model": {
|
||||
"default": "Unknown",
|
||||
"rules": {
|
||||
"type": "string",
|
||||
"description": "A user-defined or manufacturer-defined model identifier for the asset."
|
||||
}
|
||||
},
|
||||
"accuracy": {
|
||||
"default": null,
|
||||
"rules": {
|
||||
"type": "number",
|
||||
"nullable": true,
|
||||
"description": "The accuracy of the sensor, typically represented as a percentage or absolute value."
|
||||
}
|
||||
},
|
||||
"repeatability": {
|
||||
"default": null,
|
||||
"rules": {
|
||||
"type": "number",
|
||||
"nullable": true,
|
||||
"description": "The repeatability of the sensor, typically represented as a percentage or absolute value."
|
||||
}
|
||||
}
|
||||
},
|
||||
"scaling": {
|
||||
"enabled": {
|
||||
"default": false,
|
||||
"rules": {
|
||||
"type": "boolean",
|
||||
"description": "Indicates whether input scaling is active. If true, input values will be scaled according to the parameters below."
|
||||
}
|
||||
},
|
||||
"inputMin": {
|
||||
"default": 0,
|
||||
"rules": {
|
||||
"type": "number",
|
||||
"description": "The minimum expected input value before scaling."
|
||||
}
|
||||
},
|
||||
"inputMax": {
|
||||
"default": 1,
|
||||
"rules": {
|
||||
"type": "number",
|
||||
"description": "The maximum expected input value before scaling."
|
||||
}
|
||||
},
|
||||
"absMin": {
|
||||
"default": 50,
|
||||
"rules": {
|
||||
"type": "number",
|
||||
"description": "The absolute minimum value that can be read or displayed after scaling."
|
||||
}
|
||||
},
|
||||
"absMax": {
|
||||
"default": 100,
|
||||
"rules": {
|
||||
"type": "number",
|
||||
"description": "The absolute maximum value that can be read or displayed after scaling."
|
||||
}
|
||||
},
|
||||
"offset": {
|
||||
"default": 0,
|
||||
"rules": {
|
||||
"type": "number",
|
||||
"description": "A constant offset to apply to the scaled output (e.g., to calibrate zero-points)."
|
||||
}
|
||||
}
|
||||
},
|
||||
"smoothing": {
|
||||
"smoothWindow": {
|
||||
"default": 10,
|
||||
"rules": {
|
||||
"type": "number",
|
||||
"min": 1,
|
||||
"description": "Determines the size of the data window (number of samples) used for smoothing operations."
|
||||
}
|
||||
},
|
||||
"smoothMethod": {
|
||||
"default": "mean",
|
||||
"rules": {
|
||||
"type": "enum",
|
||||
"values": [
|
||||
{
|
||||
"value": "none",
|
||||
"description": "No smoothing is applied; raw data is passed through."
|
||||
},
|
||||
{
|
||||
"value": "mean",
|
||||
"description": "Calculates the simple arithmetic mean (average) of the data points in a window."
|
||||
},
|
||||
{
|
||||
"value": "min",
|
||||
"description": "Selects the smallest (minimum) value among the data points in a window."
|
||||
},
|
||||
{
|
||||
"value": "max",
|
||||
"description": "Selects the largest (maximum) value among the data points in a window."
|
||||
},
|
||||
{
|
||||
"value": "sd",
|
||||
"description": "Computes the standard deviation to measure the variation or spread of the data."
|
||||
},
|
||||
{
|
||||
"value": "lowPass",
|
||||
"description": "Filters out high-frequency components, allowing only lower frequencies to pass."
|
||||
},
|
||||
{
|
||||
"value": "highPass",
|
||||
"description": "Filters out low-frequency components, allowing only higher frequencies to pass."
|
||||
},
|
||||
{
|
||||
"value": "weightedMovingAverage",
|
||||
"description": "Applies varying weights to each data point in a window before averaging."
|
||||
},
|
||||
{
|
||||
"value": "bandPass",
|
||||
"description": "Filters the signal to allow only frequencies within a specific range to pass."
|
||||
},
|
||||
{
|
||||
"value": "median",
|
||||
"description": "Selects the median (middle) value in a window, minimizing the effect of outliers."
|
||||
},
|
||||
{
|
||||
"value": "kalman",
|
||||
"description": "Applies a Kalman filter to combine noisy measurements over time for more accurate estimates."
|
||||
},
|
||||
{
|
||||
"value": "savitzkyGolay",
|
||||
"description": "Uses a polynomial smoothing filter on a moving window, which can also provide derivative estimates."
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"simulation": {
|
||||
"enabled": {
|
||||
"default": false,
|
||||
"rules": {
|
||||
"type": "boolean",
|
||||
"description": "If true, the system operates in simulation mode, generating simulated values instead of using real inputs."
|
||||
}
|
||||
},
|
||||
"safeCalibrationTime": {
|
||||
"default": 100,
|
||||
"rules": {
|
||||
"type": "number",
|
||||
"min": 100,
|
||||
"description": "Time to wait before finalizing calibration in simulation mode (in milliseconds or appropriate unit)."
|
||||
}
|
||||
}
|
||||
},
|
||||
"interpolation": {
|
||||
"percentMin": {
|
||||
"default": 0,
|
||||
"rules": {
|
||||
"type": "number",
|
||||
"min": 0,
|
||||
"description": "Minimum percentage for interpolation or data scaling operations."
|
||||
}
|
||||
},
|
||||
"percentMax": {
|
||||
"default": 100,
|
||||
"rules": {
|
||||
"type": "number",
|
||||
"max": 100,
|
||||
"description": "Maximum percentage for interpolation or data scaling operations."
|
||||
}
|
||||
}
|
||||
},
|
||||
"outlierDetection": {
|
||||
"enabled": {
|
||||
"default": false,
|
||||
"rules": {
|
||||
"type": "boolean",
|
||||
"description": "Indicates whether outlier detection is enabled. If true, outliers will be identified and handled according to the method specified."
|
||||
}
|
||||
},
|
||||
"method": {
|
||||
"default": "zScore",
|
||||
"rules": {
|
||||
"type": "enum",
|
||||
"values": [
|
||||
{
|
||||
"value": "zScore",
|
||||
"description": "Uses the Z-score method to identify outliers based on standard deviations from the mean."
|
||||
},
|
||||
{
|
||||
"value": "iqr",
|
||||
"description": "Uses the Interquartile Range (IQR) method to identify outliers based on the spread of the middle 50% of the data."
|
||||
},
|
||||
{
|
||||
"value": "modifiedZScore",
|
||||
"description": "Uses a modified Z-score method that is more robust to small sample sizes."
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"threshold": {
|
||||
"default": 3,
|
||||
"rules": {
|
||||
"type": "number",
|
||||
"description": "The threshold value used by the selected outlier detection method. For example, a Z-score threshold of 3.0."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user