Merge pull request #261 from airgradienthq/feat/allavg

Values on display and led bar using measurement average values
This commit is contained in:
Samuel Siburian
2024-11-11 12:40:48 +07:00
committed by GitHub
7 changed files with 120 additions and 35 deletions

View File

@ -162,7 +162,7 @@ If the monitor is set up on the AirGradient dashboard, it will also receive the
#### Corrections
The `corrections` object allows configuring PM2.5 correction algorithms and parameters. This affects both the display and local server response values.
The `corrections` object allows configuring PM2.5 correction algorithms and parameters locally. This affects both the display and local server response values.
Example correction configuration:
@ -189,13 +189,28 @@ Example correction configuration:
| PMS5003_20231218 | `"slr_PMS5003_20231218"` | Correction for PMS5003 sensor batch 20231218| Yes |
| PMS5003_20231030 | `"slr_PMS5003_20231030"` | Correction for PMS5003 sensor batch 20231030| Yes |
**Notes**:
**NOTES**:
- Set `useEpa2021` to true if want to apply EPA 2021 correction factors on top of SLR correction value.
- Set `useEpa2021` to `true` if want to apply EPA 2021 correction factors on top of SLR correction value, otherwise `false`
- `intercept` and `scalingFactor` values can be obtained from [this article](https://www.airgradient.com/blog/low-readings-from-pms5003/)
- If `configurationControl` is set to `local` (eg. when using Home Assistant), correction need to be set manually, see examples below
**Example**:
**Examples**:
- PMS5003_20231030
```bash
curl --location -X PUT 'http://airgradient_84fce612eff4.local/config' --header 'Content-Type: application/json' --data '{"corrections":{"pm02":{"correctionAlgorithm":"slr_PMS5003_20231030","slr":{"intercept":0,"scalingFactor":0.02838,"useEpa2021":false}}}}'
curl --location -X PUT 'http://airgradient_84fce612eff4.local/config' --header 'Content-Type: application/json' --data '{"corrections":{"pm02":{"correctionAlgorithm":"slr_PMS5003_20231030","slr":{"intercept":0,"scalingFactor":0.02838,"useEpa2021":true}}}}'
```
- PMS5003_20231218
```bash
curl --location -X PUT 'http://airgradient_84fce612eff4.local/config' --header 'Content-Type: application/json' --data '{"corrections":{"pm02":{"correctionAlgorithm":"slr_PMS5003_20231218","slr":{"intercept":0,"scalingFactor":0,03525,"useEpa2021":true}}}}'
```
- PMS5003_20240104
```bash
curl --location -X PUT 'http://airgradient_84fce612eff4.local/config' --header 'Content-Type: application/json' --data '{"corrections":{"pm02":{"correctionAlgorithm":"slr_PMS5003_20240104","slr":{"intercept":0,"scalingFactor":0.02896,"useEpa2021":true}}}}'
```

View File

@ -1216,6 +1216,6 @@ void setMeasurementMaxPeriod() {
}
int calculateMaxPeriod(int updateInterval) {
// 0.5 is 50% reduced interval for max period
return (SERVER_SYNC_INTERVAL - (SERVER_SYNC_INTERVAL * 0.5)) / updateInterval;
// 0.8 is 80% reduced interval for max period
return (SERVER_SYNC_INTERVAL - (SERVER_SYNC_INTERVAL * 0.8)) / updateInterval;
}

View File

@ -292,8 +292,8 @@ void Configuration::defaultConfig(void) {
// PM2.5 correction
pmCorrection.algorithm = None;
pmCorrection.changed = false;
pmCorrection.intercept = -1;
pmCorrection.scalingFactor = -1;
pmCorrection.intercept = 0;
pmCorrection.scalingFactor = 1;
pmCorrection.useEPA = false;
saveConfig();
@ -1369,7 +1369,12 @@ bool Configuration::isPMCorrectionChanged(void) {
*/
bool Configuration::isPMCorrectionEnabled(void) {
PMCorrection pmCorrection = getPMCorrection();
return pmCorrection.algorithm != PMCorrectionAlgorithm::None;
if (pmCorrection.algorithm == PMCorrectionAlgorithm::None ||
pmCorrection.algorithm == PMCorrectionAlgorithm::Unknown) {
return false;
}
return true;
}
Configuration::PMCorrection Configuration::getPMCorrection(void) {

View File

@ -12,7 +12,7 @@
*/
void OledDisplay::showTempHum(bool hasStatus, char *buf, int buf_size) {
/** Temperature */
float temp = value.getFloat(Measurements::Temperature);
float temp = value.getAverage(Measurements::Temperature);
if (utils::isValidTemperature(temp)) {
float t = 0.0f;
if (config.isTemperatureUnitInF()) {
@ -44,7 +44,7 @@ void OledDisplay::showTempHum(bool hasStatus, char *buf, int buf_size) {
DISP()->drawUTF8(1, 10, buf);
/** Show humidity */
int rhum = (int)value.getFloat(Measurements::Humidity);
int rhum = round(value.getAverage(Measurements::Humidity));
if (utils::isValidHumidity(rhum)) {
snprintf(buf, buf_size, "%d%%", rhum);
} else {
@ -292,7 +292,7 @@ void OledDisplay::showDashboard(const char *status) {
DISP()->drawUTF8(1, 27, "CO2");
DISP()->setFont(u8g2_font_t0_22b_tf);
int co2 = value.get(Measurements::CO2);
int co2 = round(value.getAverage(Measurements::CO2));
if (utils::isValidCO2(co2)) {
sprintf(strBuf, "%d", co2);
} else {
@ -313,11 +313,10 @@ void OledDisplay::showDashboard(const char *status) {
DISP()->drawStr(55, 27, "PM2.5");
/** Draw PM2.5 value */
int pm25 = value.get(Measurements::PM25);
int pm25 = round(value.getAverage(Measurements::PM25));
if (utils::isValidPm(pm25)) {
if (config.hasSensorSHT && config.isPMCorrectionEnabled()) {
pm25 = (int)value.getCorrectedPM25(*ag, config);
pm25 = round(value.getCorrectedPM25(*ag, config, true));
}
if (config.isPmStandardInUSAQI()) {
sprintf(strBuf, "%d", ag->pms5003.convertPm25ToUsAqi(pm25));
@ -343,7 +342,7 @@ void OledDisplay::showDashboard(const char *status) {
DISP()->drawStr(100, 27, "VOC:");
/** Draw tvocIndexvalue */
int tvoc = value.get(Measurements::TVOC);
int tvoc = round(value.getAverage(Measurements::TVOC));
if (utils::isValidVOC(tvoc)) {
sprintf(strBuf, "%d", tvoc);
} else {
@ -352,7 +351,7 @@ void OledDisplay::showDashboard(const char *status) {
DISP()->drawStr(100, 39, strBuf);
/** Draw NOx label */
int nox = value.get(Measurements::NOx);
int nox = round(value.getAverage(Measurements::NOx));
DISP()->drawStr(100, 53, "NOx:");
if (utils::isValidNOx(nox)) {
sprintf(strBuf, "%d", nox);
@ -365,7 +364,7 @@ void OledDisplay::showDashboard(const char *status) {
ag->display.clear();
/** Set CO2 */
int co2 = value.get(Measurements::CO2);
int co2 = round(value.getAverage(Measurements::CO2));
if (utils::isValidCO2(co2)) {
snprintf(strBuf, sizeof(strBuf), "CO2:%d", co2);
} else {
@ -376,9 +375,9 @@ void OledDisplay::showDashboard(const char *status) {
ag->display.setText(strBuf);
/** Set PM */
int pm25 = value.get(Measurements::PM25);
int pm25 = round(value.getAverage(Measurements::PM25));
if (config.hasSensorSHT && config.isPMCorrectionEnabled()) {
pm25 = (int)value.getCorrectedPM25(*ag, config);
pm25 = round(value.getCorrectedPM25(*ag, config, true));
}
ag->display.setCursor(0, 12);
@ -390,7 +389,7 @@ void OledDisplay::showDashboard(const char *status) {
ag->display.setText(strBuf);
/** Set temperature and humidity */
float temp = value.getFloat(Measurements::Temperature);
float temp = value.getAverage(Measurements::Temperature);
if (utils::isValidTemperature(temp)) {
if (config.isTemperatureUnitInF()) {
snprintf(strBuf, sizeof(strBuf), "T:%0.1f F", utils::degreeC_To_F(temp));
@ -408,7 +407,7 @@ void OledDisplay::showDashboard(const char *status) {
ag->display.setCursor(0, 24);
ag->display.setText(strBuf);
int rhum = (int)value.getFloat(Measurements::Humidity);
int rhum = round(value.getAverage(Measurements::Humidity));
if (utils::isValidHumidity(rhum)) {
snprintf(strBuf, sizeof(strBuf), "H:%d %%", rhum);
} else {

View File

@ -86,7 +86,7 @@ bool StateMachine::sensorhandleLeds(void) {
*/
int StateMachine::co2handleLeds(void) {
int totalUsed = ag->ledBar.getNumberOfLeds();
int co2Value = value.get(Measurements::CO2);
int co2Value = round(value.getAverage(Measurements::CO2));
if (co2Value <= 700) {
/** G; 1 */
ag->ledBar.setColor(RGB_COLOR_G, ag->ledBar.getNumberOfLeds() - 1);
@ -171,9 +171,10 @@ int StateMachine::co2handleLeds(void) {
*/
int StateMachine::pm25handleLeds(void) {
int totalUsed = ag->ledBar.getNumberOfLeds();
int pm25Value = value.get(Measurements::PM25);
int pm25Value = round(value.getAverage(Measurements::PM25));
if (config.hasSensorSHT && config.isPMCorrectionEnabled()) {
pm25Value = (int)value.getCorrectedPM25(*ag, config);
pm25Value = round(value.getCorrectedPM25(*ag, config, true));
}
if (pm25Value <= 5) {

View File

@ -396,6 +396,52 @@ float Measurements::getFloat(MeasurementType type, int ch) {
return temporary->listValues.back();
}
float Measurements::getAverage(MeasurementType type, int ch) {
// Sanity check to validate channel, assert if invalid
validateChannel(ch);
// Follow array indexing just for get address of the value type
ch = ch - 1;
// Define data point source. Data type doesn't matter because only to get the average value
FloatValue *temporary = nullptr;
Update update;
float measurementAverage;
switch (type) {
case CO2:
measurementAverage = _co2.update.avg;
break;
case TVOC:
measurementAverage = _tvoc.update.avg;
break;
case NOx:
measurementAverage = _nox.update.avg;
break;
case PM25:
measurementAverage = _pm_25[ch].update.avg;
break;
case Temperature:
measurementAverage = _temperature[ch].update.avg;
break;
case Humidity:
measurementAverage = _humidity[ch].update.avg;
break;
default:
// Invalidate, measurements type not handled
measurementAverage = -1000;
break;
};
// Sanity check if measurement type is not defined
if (measurementAverage == -1000) {
Serial.printf("ERROR! %s is not defined on get average value function\n", measurementTypeStr(type));
delay(1000);
assert(0);
}
return measurementAverage;
}
String Measurements::pms5003FirmwareVersion(int fwCode) {
return pms5003FirmwareVersionBase("PMS5003x", fwCode);
}
@ -485,11 +531,12 @@ void Measurements::validateChannel(int ch) {
float Measurements::getCorrectedPM25(AirGradient &ag, Configuration &config, bool useAvg, int ch) {
float pm25;
float corrected;
float humidity;
float pm003Count;
int channel = ch - 1; // Array index
if (useAvg) {
// Directly call from the index
int channel = ch - 1; // Array index
pm25 = _pm_25[channel].update.avg;
humidity = _humidity[channel].update.avg;
pm003Count = _pm_03_pc[channel].update.avg;
@ -500,19 +547,27 @@ float Measurements::getCorrectedPM25(AirGradient &ag, Configuration &config, boo
}
Configuration::PMCorrection pmCorrection = config.getPMCorrection();
if (pmCorrection.algorithm == PMCorrectionAlgorithm::EPA_2021) {
// EPA correction directly applied
pm25 = ag.pms5003.compensate(pm25, humidity);
} else {
// SLR correction, this is assumes before calling this function, correction algorithm is not None
pm25 = ag.pms5003.slrCorrection(pm25, pm003Count, pmCorrection.scalingFactor, pmCorrection.intercept);
switch (pmCorrection.algorithm) {
case PMCorrectionAlgorithm::Unknown:
case PMCorrectionAlgorithm::None:
// If correction is Unknown, then default is None
corrected = pm25;
break;
case PMCorrectionAlgorithm::EPA_2021:
corrected = ag.pms5003.compensate(pm25, humidity);
break;
default: {
// All SLR correction using the same flow, hence default condition
corrected = ag.pms5003.slrCorrection(pm25, pm003Count, pmCorrection.scalingFactor,
pmCorrection.intercept);
if (pmCorrection.useEPA) {
// Add EPA compensation on top of SLR
pm25 = ag.pms5003.compensate(pm25, humidity);
corrected = ag.pms5003.compensate(pm25, humidity);
}
}
}
return pm25;
return corrected;
}
String Measurements::toString(bool localServer, AgFirmwareMode fwMode, int rssi, AirGradient &ag,

View File

@ -114,9 +114,19 @@ public:
*/
float getFloat(MeasurementType type, int ch = 1);
/**
* @brief Get the target measurement average value
*
* @param type measurement type that will be retrieve
* @param ch target type value channel
* @return moving average value of target measurements type
*/
float getAverage(MeasurementType type, int ch = 1);
/**
* @brief Get the Corrected PM25 object based on the correction algorithm from configuration
*
* If correction is not enabled, then will return the raw value (either average or last value)
*
* @param ag AirGradient instance
* @param config Configuration instance