Switch to moving average for sensor data

average value to floating points
This commit is contained in:
samuelbles07
2024-10-20 19:01:41 +07:00
parent 399b4ca1dc
commit f36f860c2e
2 changed files with 97 additions and 100 deletions

View File

@ -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)) {

View File

@ -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;
}; };