forked from airgradienthq/arduino
Switch to moving average for sensor data
average value to floating points
This commit is contained in:
183
src/AgValue.cpp
183
src/AgValue.cpp
@ -109,39 +109,32 @@ bool Measurements::update(MeasurementType type, int val, int ch) {
|
|||||||
// Restore channel value for debugging purpose
|
// Restore channel value for debugging purpose
|
||||||
ch = ch + 1;
|
ch = ch + 1;
|
||||||
|
|
||||||
// Update new value when value provided is not the invalid one
|
if (val == invalidValue) {
|
||||||
if (val != invalidValue) {
|
temporary->update.invalidCounter++;
|
||||||
temporary->lastValue = val;
|
// TODO: Need to check if its more than threshold, to create some indication. Maybe reference to
|
||||||
temporary->sumValues = temporary->sumValues + val;
|
// max element?
|
||||||
temporary->update.success = temporary->update.success + 1;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Increment update.counter
|
// Reset invalid counter when update new valid value
|
||||||
temporary->update.counter = temporary->update.counter + 1;
|
temporary->update.invalidCounter = 0;
|
||||||
|
|
||||||
// Calculate value average when maximum set is reached
|
// Add new value to the end of the list
|
||||||
if (temporary->update.counter >= temporary->update.max) {
|
temporary->listValues.push_back(val);
|
||||||
// TODO: Need to check if SUCCESS == 0, what should we do?
|
// Sum the new value
|
||||||
// Calculate the average
|
temporary->sumValues = temporary->sumValues + val;
|
||||||
temporary->avg = temporary->sumValues / temporary->update.success;
|
// Remove the oldest value on the list when the list exceed max elements
|
||||||
Serial.printf("%s{%d} count reached! Average value %d\n", measurementTypeStr(type), ch,
|
if (temporary->listValues.size() > temporary->update.max) {
|
||||||
temporary->avg);
|
auto it = temporary->listValues.begin();
|
||||||
|
temporary->sumValues = temporary->sumValues - *it; // subtract the oldest value from sum
|
||||||
// Notify if there's are invalid value when updating
|
temporary->listValues.erase(it); // And remove it from the list
|
||||||
int miss = temporary->update.max - temporary->update.success;
|
|
||||||
if (miss != 0) {
|
|
||||||
Serial.printf("%s{%d} has %d invalid value out of %d update\n", measurementTypeStr(type), ch,
|
|
||||||
miss, temporary->update.max);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Resets average related variable calculation
|
|
||||||
temporary->sumValues = 0;
|
|
||||||
temporary->update.counter = 0;
|
|
||||||
temporary->update.success = 0;
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
// Calculate average based on how many elements on the list
|
||||||
|
temporary->avg = temporary->sumValues / (float)temporary->listValues.size();
|
||||||
|
Serial.printf("%s{%d}: %.2f\n", measurementTypeStr(type), ch, temporary->avg);
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Measurements::update(MeasurementType type, float val, int ch) {
|
bool Measurements::update(MeasurementType type, float val, int ch) {
|
||||||
@ -177,39 +170,32 @@ bool Measurements::update(MeasurementType type, float val, int ch) {
|
|||||||
// Restore channel value for debugging purpose
|
// Restore channel value for debugging purpose
|
||||||
ch = ch + 1;
|
ch = ch + 1;
|
||||||
|
|
||||||
// Update new value when value provided is not the invalid one
|
if (val == invalidValue) {
|
||||||
if (val != invalidValue) {
|
temporary->update.invalidCounter++;
|
||||||
temporary->lastValue = val;
|
// TODO: Need to check if its more than threshold, to create some indication. Maybe reference to
|
||||||
temporary->sumValues = temporary->sumValues + val;
|
// max element?
|
||||||
temporary->update.success = temporary->update.success + 1;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Increment update.counter
|
// Reset invalid counter when update new valid value
|
||||||
temporary->update.counter = temporary->update.counter + 1;
|
temporary->update.invalidCounter = 0;
|
||||||
|
|
||||||
// Calculate value average when maximum set is reached
|
// Add new value to the end of the list
|
||||||
if (temporary->update.counter >= temporary->update.max) {
|
temporary->listValues.push_back(val);
|
||||||
// TODO: Need to check if SUCCESS == 0
|
// Sum the new value
|
||||||
// Calculate the average
|
temporary->sumValues = temporary->sumValues + val;
|
||||||
temporary->avg = temporary->sumValues / temporary->update.success;
|
// Remove the oldest value on the list when the list exceed max elements
|
||||||
Serial.printf("%s{%d} count reached! Average value %0.2f\n", measurementTypeStr(type), ch,
|
if (temporary->listValues.size() > temporary->update.max) {
|
||||||
temporary->avg);
|
auto it = temporary->listValues.begin();
|
||||||
|
temporary->sumValues = temporary->sumValues - *it; // subtract the oldest value from sum
|
||||||
// This is just for notifying
|
temporary->listValues.erase(it); // And remove it from the list
|
||||||
int miss = temporary->update.max - temporary->update.success;
|
|
||||||
if (miss != 0) {
|
|
||||||
Serial.printf("%s{%d} has %d invalid value out of %d update\n", measurementTypeStr(type), ch,
|
|
||||||
miss, temporary->update.max);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Resets average related variable calculation
|
|
||||||
temporary->sumValues = 0;
|
|
||||||
temporary->update.counter = 0;
|
|
||||||
temporary->update.success = 0;
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
// Calculate average based on how many elements on the list
|
||||||
|
temporary->avg = temporary->sumValues / (float)temporary->listValues.size();
|
||||||
|
Serial.printf("%s{%d}: %.2f\n", measurementTypeStr(type), ch, temporary->avg);
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
int Measurements::get(MeasurementType type, bool average, int ch) {
|
int Measurements::get(MeasurementType type, bool average, int ch) {
|
||||||
@ -261,11 +247,17 @@ int Measurements::get(MeasurementType type, bool average, int ch) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (temporary->listValues.empty()) {
|
||||||
|
// Values still empty, return 0
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
if (average) {
|
if (average) {
|
||||||
|
// TODO: This now is average value, need to update this
|
||||||
return temporary->avg;
|
return temporary->avg;
|
||||||
}
|
}
|
||||||
|
|
||||||
return temporary->lastValue;
|
return temporary->listValues.back();
|
||||||
}
|
}
|
||||||
|
|
||||||
float Measurements::getFloat(MeasurementType type, bool average, int ch) {
|
float Measurements::getFloat(MeasurementType type, bool average, int ch) {
|
||||||
@ -295,11 +287,16 @@ float Measurements::getFloat(MeasurementType type, bool average, int ch) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (temporary->listValues.empty()) {
|
||||||
|
// Values still empty, return 0
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
if (average) {
|
if (average) {
|
||||||
return temporary->avg;
|
return temporary->avg;
|
||||||
}
|
}
|
||||||
|
|
||||||
return temporary->lastValue;
|
return temporary->listValues.back();
|
||||||
}
|
}
|
||||||
|
|
||||||
String Measurements::pms5003FirmwareVersion(int fwCode) {
|
String Measurements::pms5003FirmwareVersion(int fwCode) {
|
||||||
@ -377,22 +374,22 @@ String Measurements::toString(bool localServer, AgFirmwareMode fwMode, int rssi,
|
|||||||
|
|
||||||
// CO2
|
// CO2
|
||||||
if (config.hasSensorS8 && utils::isValidCO2(_co2.avg)) {
|
if (config.hasSensorS8 && utils::isValidCO2(_co2.avg)) {
|
||||||
root["rco2"] = _co2.avg;
|
root["rco2"] = ag.round2(_co2.avg);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// TVOx and NOx
|
/// TVOx and NOx
|
||||||
if (config.hasSensorSGP) {
|
if (config.hasSensorSGP) {
|
||||||
if (utils::isValidVOC(_tvoc.avg)) {
|
if (utils::isValidVOC(_tvoc.avg)) {
|
||||||
root["tvocIndex"] = _tvoc.avg;
|
root["tvocIndex"] = ag.round2(_tvoc.avg);
|
||||||
}
|
}
|
||||||
if (utils::isValidVOC(_tvoc_raw.avg)) {
|
if (utils::isValidVOC(_tvoc_raw.avg)) {
|
||||||
root["tvocRaw"] = _tvoc_raw.avg;
|
root["tvocRaw"] = ag.round2(_tvoc_raw.avg);
|
||||||
}
|
}
|
||||||
if (utils::isValidNOx(_nox.avg)) {
|
if (utils::isValidNOx(_nox.avg)) {
|
||||||
root["noxIndex"] = _nox.avg;
|
root["noxIndex"] = ag.round2(_nox.avg);
|
||||||
}
|
}
|
||||||
if (utils::isValidNOx(_nox_raw.avg)) {
|
if (utils::isValidNOx(_nox_raw.avg)) {
|
||||||
root["noxRaw"] = _nox_raw.avg;
|
root["noxRaw"] = ag.round2(_nox_raw.avg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -511,16 +508,16 @@ JSONVar Measurements::buildPMS(AirGradient &ag, int ch, bool allCh, bool withTem
|
|||||||
ch = ch - 1;
|
ch = ch - 1;
|
||||||
|
|
||||||
if (utils::isValidPm(_pm_01[ch].avg)) {
|
if (utils::isValidPm(_pm_01[ch].avg)) {
|
||||||
pms["pm01"] = _pm_01[ch].avg;
|
pms["pm01"] = ag.round2(_pm_01[ch].avg);
|
||||||
}
|
}
|
||||||
if (utils::isValidPm(_pm_25[ch].avg)) {
|
if (utils::isValidPm(_pm_25[ch].avg)) {
|
||||||
pms["pm02"] = _pm_25[ch].avg;
|
pms["pm02"] = ag.round2(_pm_25[ch].avg);
|
||||||
}
|
}
|
||||||
if (utils::isValidPm(_pm_10[ch].avg)) {
|
if (utils::isValidPm(_pm_10[ch].avg)) {
|
||||||
pms["pm10"] = _pm_10[ch].avg;
|
pms["pm10"] = ag.round2(_pm_10[ch].avg);
|
||||||
}
|
}
|
||||||
if (utils::isValidPm03Count(_pm_03_pc[ch].avg)) {
|
if (utils::isValidPm03Count(_pm_03_pc[ch].avg)) {
|
||||||
pms["pm003Count"] = _pm_03_pc[ch].avg;
|
pms["pm003Count"] = ag.round2(_pm_03_pc[ch].avg);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (withTempHum) {
|
if (withTempHum) {
|
||||||
@ -568,58 +565,58 @@ JSONVar Measurements::buildPMS(AirGradient &ag, int ch, bool allCh, bool withTem
|
|||||||
// Handle both channel with average, if one of the channel not valid, use another one
|
// Handle both channel with average, if one of the channel not valid, use another one
|
||||||
/// PM01
|
/// PM01
|
||||||
if (utils::isValidPm(_pm_01[0].avg) && utils::isValidPm(_pm_01[1].avg)) {
|
if (utils::isValidPm(_pm_01[0].avg) && utils::isValidPm(_pm_01[1].avg)) {
|
||||||
float avg = (_pm_01[0].avg + _pm_01[1].avg) / 2;
|
float avg = (_pm_01[0].avg + _pm_01[1].avg) / 2.0f;
|
||||||
pms["pm01"] = ag.round2(avg);
|
pms["pm01"] = ag.round2(avg);
|
||||||
pms["channels"]["1"]["pm01"] = _pm_01[0].avg;
|
pms["channels"]["1"]["pm01"] = ag.round2(_pm_01[0].avg);
|
||||||
pms["channels"]["2"]["pm01"] = _pm_01[1].avg;
|
pms["channels"]["2"]["pm01"] = ag.round2(_pm_01[1].avg);
|
||||||
} else if (utils::isValidPm(_pm_01[0].avg)) {
|
} else if (utils::isValidPm(_pm_01[0].avg)) {
|
||||||
pms["pm01"] = _pm_01[0].avg;
|
pms["pm01"] = ag.round2(_pm_01[0].avg);
|
||||||
pms["channels"]["1"]["pm01"] = _pm_01[0].avg;
|
pms["channels"]["1"]["pm01"] = ag.round2(_pm_01[0].avg);
|
||||||
} else if (utils::isValidPm(_pm_01[1].avg)) {
|
} else if (utils::isValidPm(_pm_01[1].avg)) {
|
||||||
pms["pm01"] = _pm_01[1].avg;
|
pms["pm01"] = ag.round2(_pm_01[1].avg);
|
||||||
pms["channels"]["2"]["pm01"] = _pm_01[1].avg;
|
pms["channels"]["2"]["pm01"] = ag.round2(_pm_01[1].avg);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// PM2.5
|
/// PM2.5
|
||||||
if (utils::isValidPm(_pm_25[0].avg) && utils::isValidPm(_pm_25[1].avg)) {
|
if (utils::isValidPm(_pm_25[0].avg) && utils::isValidPm(_pm_25[1].avg)) {
|
||||||
float avg = (_pm_25[0].avg + _pm_25[1].avg) / 2.0f;
|
float avg = (_pm_25[0].avg + _pm_25[1].avg) / 2.0f;
|
||||||
pms["pm02"] = ag.round2(avg);
|
pms["pm02"] = ag.round2(avg);
|
||||||
pms["channels"]["1"]["pm02"] = _pm_25[0].avg;
|
pms["channels"]["1"]["pm02"] = ag.round2(_pm_25[0].avg);
|
||||||
pms["channels"]["2"]["pm02"] = _pm_25[1].avg;
|
pms["channels"]["2"]["pm02"] = ag.round2(_pm_25[1].avg);
|
||||||
} else if (utils::isValidPm(_pm_25[0].avg)) {
|
} else if (utils::isValidPm(_pm_25[0].avg)) {
|
||||||
pms["pm02"] = _pm_25[0].avg;
|
pms["pm02"] = ag.round2(_pm_25[0].avg);
|
||||||
pms["channels"]["1"]["pm02"] = _pm_25[0].avg;
|
pms["channels"]["1"]["pm02"] = ag.round2(_pm_25[0].avg);
|
||||||
} else if (utils::isValidPm(_pm_25[1].avg)) {
|
} else if (utils::isValidPm(_pm_25[1].avg)) {
|
||||||
pms["pm02"] = _pm_25[1].avg;
|
pms["pm02"] = ag.round2(_pm_25[1].avg);
|
||||||
pms["channels"]["2"]["pm02"] = _pm_25[1].avg;
|
pms["channels"]["2"]["pm02"] = ag.round2(_pm_25[1].avg);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// PM10
|
/// PM10
|
||||||
if (utils::isValidPm(_pm_10[0].avg) && utils::isValidPm(_pm_10[1].avg)) {
|
if (utils::isValidPm(_pm_10[0].avg) && utils::isValidPm(_pm_10[1].avg)) {
|
||||||
float avg = (_pm_10[0].avg + _pm_10[1].avg) / 2.0f;
|
float avg = (_pm_10[0].avg + _pm_10[1].avg) / 2.0f;
|
||||||
pms["pm10"] = ag.round2(avg);
|
pms["pm10"] = ag.round2(avg);
|
||||||
pms["channels"]["1"]["pm10"] = _pm_10[0].avg;
|
pms["channels"]["1"]["pm10"] = ag.round2(_pm_10[0].avg);
|
||||||
pms["channels"]["2"]["pm10"] = _pm_10[1].avg;
|
pms["channels"]["2"]["pm10"] = ag.round2(_pm_10[1].avg);
|
||||||
} else if (utils::isValidPm(_pm_10[0].avg)) {
|
} else if (utils::isValidPm(_pm_10[0].avg)) {
|
||||||
pms["pm10"] = _pm_10[0].avg;
|
pms["pm10"] = ag.round2(_pm_10[0].avg);
|
||||||
pms["channels"]["1"]["pm10"] = _pm_10[0].avg;
|
pms["channels"]["1"]["pm10"] = ag.round2(_pm_10[0].avg);
|
||||||
} else if (utils::isValidPm(_pm_10[1].avg)) {
|
} else if (utils::isValidPm(_pm_10[1].avg)) {
|
||||||
pms["pm10"] = _pm_10[1].avg;
|
pms["pm10"] = ag.round2(_pm_10[1].avg);
|
||||||
pms["channels"]["2"]["pm10"] = _pm_10[1].avg;
|
pms["channels"]["2"]["pm10"] = ag.round2(_pm_10[1].avg);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// PM03 particle count
|
/// PM03 particle count
|
||||||
if (utils::isValidPm03Count(_pm_03_pc[0].avg) && utils::isValidPm03Count(_pm_03_pc[1].avg)) {
|
if (utils::isValidPm03Count(_pm_03_pc[0].avg) && utils::isValidPm03Count(_pm_03_pc[1].avg)) {
|
||||||
float avg = (_pm_03_pc[0].avg + _pm_03_pc[1].avg) / 2.0f;
|
float avg = (_pm_03_pc[0].avg + _pm_03_pc[1].avg) / 2.0f;
|
||||||
pms["pm003Count"] = ag.round2(avg);
|
pms["pm003Count"] = ag.round2(avg);
|
||||||
pms["channels"]["1"]["pm003Count"] = _pm_03_pc[0].avg;
|
pms["channels"]["1"]["pm003Count"] = ag.round2(_pm_03_pc[0].avg);
|
||||||
pms["channels"]["2"]["pm003Count"] = _pm_03_pc[1].avg;
|
pms["channels"]["2"]["pm003Count"] = ag.round2(_pm_03_pc[1].avg);
|
||||||
} else if (utils::isValidPm(_pm_03_pc[0].avg)) {
|
} else if (utils::isValidPm(_pm_03_pc[0].avg)) {
|
||||||
pms["pm003Count"] = _pm_03_pc[0].avg;
|
pms["pm003Count"] = ag.round2(_pm_03_pc[0].avg);
|
||||||
pms["channels"]["1"]["pm003Count"] = _pm_03_pc[0].avg;
|
pms["channels"]["1"]["pm003Count"] = ag.round2(_pm_03_pc[0].avg);
|
||||||
} else if (utils::isValidPm(_pm_03_pc[1].avg)) {
|
} else if (utils::isValidPm(_pm_03_pc[1].avg)) {
|
||||||
pms["pm003Count"] = _pm_03_pc[1].avg;
|
pms["pm003Count"] = ag.round2(_pm_03_pc[1].avg);
|
||||||
pms["channels"]["2"]["pm003Count"] = _pm_03_pc[1].avg;
|
pms["channels"]["2"]["pm003Count"] = ag.round2(_pm_03_pc[1].avg);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (withTempHum) {
|
if (withTempHum) {
|
||||||
@ -721,7 +718,7 @@ JSONVar Measurements::buildPMS(AirGradient &ag, int ch, bool allCh, bool withTem
|
|||||||
|
|
||||||
/// Get average or one of the channel compensated value if only one channel is valid
|
/// Get average or one of the channel compensated value if only one channel is valid
|
||||||
if (utils::isValidPm(pm25_comp1) && utils::isValidPm(pm25_comp2)) {
|
if (utils::isValidPm(pm25_comp1) && utils::isValidPm(pm25_comp2)) {
|
||||||
pms["pm02Compensated"] = (int)((pm25_comp1 / pm25_comp2) / 2);
|
pms["pm02Compensated"] = ag.round2((pm25_comp1 / pm25_comp2) / 2.0f);
|
||||||
} else if (utils::isValidPm(pm25_comp1)) {
|
} else if (utils::isValidPm(pm25_comp1)) {
|
||||||
pms["pm02Compensated"] = pm25_comp1;
|
pms["pm02Compensated"] = pm25_comp1;
|
||||||
} else if (utils::isValidPm(pm25_comp2)) {
|
} else if (utils::isValidPm(pm25_comp2)) {
|
||||||
|
@ -7,30 +7,30 @@
|
|||||||
#include "Libraries/Arduino_JSON/src/Arduino_JSON.h"
|
#include "Libraries/Arduino_JSON/src/Arduino_JSON.h"
|
||||||
#include "Main/utils.h"
|
#include "Main/utils.h"
|
||||||
#include <Arduino.h>
|
#include <Arduino.h>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
class Measurements {
|
class Measurements {
|
||||||
private:
|
private:
|
||||||
// Generic struct for update indication for respective value
|
// Generic struct for update indication for respective value
|
||||||
struct Update {
|
struct Update {
|
||||||
int counter; // How many update attempts done
|
int invalidCounter; // Counting on how many invalid value that are passed to update function
|
||||||
int success; // How many update value that actually give valid value
|
int max; // Maximum elements on the list
|
||||||
int max; // Maximum update counter before calculating average
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Reading type for sensor value that outputs float
|
// Reading type for sensor value that outputs float
|
||||||
struct FloatValue {
|
struct FloatValue {
|
||||||
float lastValue; // Last update value
|
|
||||||
float sumValues; // Total value from each update
|
float sumValues; // Total value from each update
|
||||||
float avg; // The last average calculation after maximum update attempt reached
|
std::vector<float> listValues; // List of update value that are kept
|
||||||
|
float avg; // Moving average value, updated every update function called
|
||||||
Update update;
|
Update update;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Reading type for sensor value that outputs integer
|
// Reading type for sensor value that outputs integer
|
||||||
struct IntegerValue {
|
struct IntegerValue {
|
||||||
int lastValue; // Last update value
|
|
||||||
unsigned long sumValues; // Total value from each update; unsigned long to accomodate TVOx and
|
unsigned long sumValues; // Total value from each update; unsigned long to accomodate TVOx and
|
||||||
// NOx raw data
|
// NOx raw data
|
||||||
int avg; // The last average calculation after maximum update attempt reached
|
std::vector<int> listValues; // List of update value that are kept
|
||||||
|
float avg; // Moving average value, updated every update function called
|
||||||
Update update;
|
Update update;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user