mirror of
https://github.com/airgradienthq/arduino.git
synced 2025-07-29 16:37:17 +02:00
Merge pull request #261 from airgradienthq/feat/allavg
Values on display and led bar using measurement average values
This commit is contained in:
@ -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}}}}'
|
||||
```
|
@ -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;
|
||||
}
|
||||
|
@ -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) {
|
||||
|
@ -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 {
|
||||
|
@ -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) {
|
||||
|
@ -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,
|
||||
|
@ -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
|
||||
|
Reference in New Issue
Block a user