diff --git a/docs/local-server.md b/docs/local-server.md
index 247ec06..f4b3f0c 100644
--- a/docs/local-server.md
+++ b/docs/local-server.md
@@ -73,16 +73,17 @@ You get the following response:
| `noxIndex` | Number | Senisirion NOx Index |
| `noxRaw` | Number | NOx raw value |
| `boot` | Number | Counts every measurement cycle. Low boot counts indicate restarts. |
-| `bootCount` | Number | Same as boot property. Required for Home Assistant compatability. Will be depreciated. |
+| `bootCount` | Number | Same as boot property. Required for Home Assistant compatability. (deprecated soon!) |
| `ledMode` | String | Current configuration of the LED mode |
| `firmware` | String | Current firmware version |
| `model` | String | Current model name |
-| `monitorDisplayCompensatedValues` | Boolean | Switching Display of AirGradient ONE to Compensated / Non Compensated Values |
Compensated values apply correction algorithms to make the sensor values more accurate. Temperature and relative humidity correction is only applied on the outdoor monitor Open Air but the properties _compensated will still be send also for the indoor monitor AirGradient ONE.
#### Get Configuration Parameters (GET)
-With the path "/config" you can get the current configuration.
+
+"/config" path returns the current configuration of the monitor.
+
```json
{
"country": "TH",
@@ -99,28 +100,40 @@ With the path "/config" you can get the current configuration.
"displayBrightness": 100,
"offlineMode": false,
"model": "I-9PSL",
- "monitorDisplayCompensatedValues": true
+ "monitorDisplayCompensatedValues": true,
+ "corrections": {
+ "pm02": {
+ "correctionAlgorithm": "epa_2021",
+ "slr": {}
+ }
+ }
+ }
}
```
#### Set Configuration Parameters (PUT)
-Configuration parameters can be changed with a put request to the monitor, e.g.
+Configuration parameters can be changed with a PUT request to the monitor, e.g.
Example to force CO2 calibration
- ```curl -X PUT -H "Content-Type: application/json" -d '{"co2CalibrationRequested":true}' http://airgradient_84fce612eff4.local/config ```
+ ```bash
+ curl -X PUT -H "Content-Type: application/json" -d '{"co2CalibrationRequested":true}' http://airgradient_84fce612eff4.local/config
+ ```
Example to set monitor to Celsius
- ```curl -X PUT -H "Content-Type: application/json" -d '{"temperatureUnit":"c"}' http://airgradient_84fce612eff4.local/config ```
+ ```bash
+ curl -X PUT -H "Content-Type: application/json" -d '{"temperatureUnit":"c"}' http://airgradient_84fce612eff4.local/config
+ ```
If you use command prompt on Windows, you need to escape the quotes:
``` -d "{\"param\":\"value\"}" ```
#### Avoiding Conflicts with Configuration on AirGradient Server
-If the monitor is set up on the AirGradient dashboard, it will also receive configurations from there. In case you do not want this, please set `configurationControl` to `local`. In case you set it to `cloud` and want to change it to `local`, you need to make a factory reset.
+
+If the monitor is set up on the AirGradient dashboard, it will also receive the configuration parameters from there. In case you do not want this, please set `configurationControl` to `local`. In case you set it to `cloud` and want to change it to `local`, you need to make a factory reset.
#### Configuration Parameters (GET/PUT)
@@ -142,4 +155,47 @@ If the monitor is set up on the AirGradient dashboard, it will also receive conf
| `noxLearningOffset` | Set NOx learning gain offset. | Number | 0-720 (default 12) | `{"noxLearningOffset": 12}` |
| `tvocLearningOffset` | Set VOC learning gain offset. | Number | 0-720 (default 12) | `{"tvocLearningOffset": 12}` |
| `offlineMode` | Set monitor to run without WiFi. | Boolean | `false`: Disabled (default) `true`: Enabled | `{"offlineMode": true}` |
-| `monitorDisplayCompensatedValues` | Set the display show the PM value with/without compensate value (From [3.1.9]()) | Boolean | `false`: Without compensate (default) `true`: with compensate | `{"monitorDisplayCompensatedValues": false }` |
+| `monitorDisplayCompensatedValues` | Set the display show the PM value with/without compensate value (only on [3.1.9]()) | Boolean | `false`: Without compensate (default) `true`: with compensate | `{"monitorDisplayCompensatedValues": false }` |
+| `corrections` | Sets correction options to display and measurement values on local server response. | Object | _see corretions section_ | _see corretions section_ |
+
+
+
+#### Corrections
+
+The `corrections` object allows configuring PM2.5 correction algorithms and parameters. This affects both the display and local server response values.
+
+Example correction configuration:
+
+```json
+{
+ "corrections": {
+ "pm02": {
+ "correctionAlgorithm": "",
+ "slr": {
+ "intercept": 0,
+ "scalingFactor": 0,
+ "useEpa2021": false
+ }
+ }
+ }
+}
+```
+
+| Algorithm | Value | Description | SLR required |
+|------------|-------------|------|---------|
+| Raw | `"none"` | No correction (default) | No |
+| EPA 2021 | `"epa_2021"` | Use EPA 2021 correction factors on top of raw value | No |
+| PMS5003_20240104 | `"slr_PMS5003_20240104"` | Correction for PMS5003 sensor batch 20240104| Yes |
+| 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**:
+
+- Set `useEpa2021` to true if want to apply EPA 2021 correction factors on top of SLR correction value.
+- `intercept` and `scalingFactor` values can be obtained from [this article](https://www.airgradient.com/blog/low-readings-from-pms5003/)
+
+**Example**:
+
+```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}}}}'
+```
\ No newline at end of file
diff --git a/src/AgConfigure.cpp b/src/AgConfigure.cpp
index 1446d1a..266886e 100644
--- a/src/AgConfigure.cpp
+++ b/src/AgConfigure.cpp
@@ -1,5 +1,4 @@
#include "AgConfigure.h"
-#include "Libraries/Arduino_JSON/src/Arduino_JSON.h"
#if ESP32
#include "FS.h"
#include "SPIFFS.h"
@@ -22,6 +21,18 @@ const char *LED_BAR_MODE_NAMES[] = {
[LedBarModeCO2] = "co2",
};
+const char *PM_CORRECTION_ALGORITHM_NAMES[] = {
+ [Unknown] = "-", // This is only to pass "non-trivial designated initializers" error
+ [None] = "none",
+ [EPA_2021] = "epa_2021",
+ [SLR_PMS5003_20220802] = "slr_PMS5003_20220802",
+ [SLR_PMS5003_20220803] = "slr_PMS5003_20220803",
+ [SLR_PMS5003_20220824] = "slr_PMS5003_20220824",
+ [SLR_PMS5003_20231030] = "slr_PMS5003_20231030",
+ [SLR_PMS5003_20231218] = "slr_PMS5003_20231218",
+ [SLR_PMS5003_20240104] = "slr_PMS5003_20240104",
+};
+
#define JSON_PROP_NAME(name) jprop_##name
#define JSON_PROP_DEF(name) const char *JSON_PROP_NAME(name) = #name
@@ -42,6 +53,7 @@ JSON_PROP_DEF(co2CalibrationRequested);
JSON_PROP_DEF(ledBarTestRequested);
JSON_PROP_DEF(offlineMode);
JSON_PROP_DEF(monitorDisplayCompensatedValues);
+JSON_PROP_DEF(corrections);
#define jprop_model_default ""
#define jprop_country_default "TH"
@@ -87,6 +99,112 @@ String Configuration::getLedBarModeName(LedBarMode mode) {
return String("unknown");
}
+PMCorrectionAlgorithm Configuration::matchPmAlgorithm(String algorithm) {
+ // Loop through all algorithm names in the PM_CORRECTION_ALGORITHM_NAMES array
+ // If the input string matches an algorithm name, return the corresponding enum value
+ // Else return Unknown
+
+ const size_t enumSize = SLR_PMS5003_20240104 + 1; // Get the actual size of the enum
+ PMCorrectionAlgorithm result = PMCorrectionAlgorithm::Unknown;
+
+ // Loop through enum values
+ for (size_t enumVal = 0; enumVal < enumSize; enumVal++) {
+ if (algorithm == PM_CORRECTION_ALGORITHM_NAMES[enumVal]) {
+ result = static_cast(enumVal);
+ }
+ }
+
+ return result;
+}
+
+bool Configuration::updatePmCorrection(JSONVar &json) {
+ if (!json.hasOwnProperty("corrections")) {
+ // TODO: need to response message?
+ Serial.println("corrections not found");
+ return false;
+ }
+
+ JSONVar corrections = json["corrections"];
+ if (!corrections.hasOwnProperty("pm02")) {
+ Serial.println("pm02 not found");
+ return false;
+ }
+
+ JSONVar pm02 = corrections["pm02"];
+ if (!pm02.hasOwnProperty("correctionAlgorithm")) {
+ Serial.println("correctionAlgorithm not found");
+ return false;
+ }
+
+ // TODO: Need to have data type check, with error message response if invalid
+
+ // Check algorithm
+ String algorithm = pm02["correctionAlgorithm"];
+ PMCorrectionAlgorithm algo = matchPmAlgorithm(algorithm);
+ if (algo == Unknown) {
+ logInfo("Unknown algorithm");
+ return false;
+ }
+ logInfo("Correction algorithm: " + algorithm);
+
+ // If algo is None or EPA_2021, no need to check slr
+ // But first check if pmCorrection different from algo
+ if (algo == None || algo == EPA_2021) {
+ if (pmCorrection.algorithm != algo) {
+ // Deep copy corrections from root to jconfig, so it will be saved later
+ jconfig[jprop_corrections]["pm02"]["correctionAlgorithm"] = algorithm;
+ jconfig[jprop_corrections]["pm02"]["slr"] = JSON.parse("{}"); // Clear slr
+ // Update pmCorrection with new values
+ pmCorrection.algorithm = algo;
+ pmCorrection.changed = true;
+ logInfo("PM2.5 correction updated");
+ return true;
+ }
+
+ return false;
+ }
+
+ // Check if pm02 has slr object
+ if (!pm02.hasOwnProperty("slr")) {
+ Serial.println("slr not found");
+ return false;
+ }
+
+ JSONVar slr = pm02["slr"];
+
+ // Validate required slr properties exist
+ if (!slr.hasOwnProperty("intercept") || !slr.hasOwnProperty("scalingFactor") ||
+ !slr.hasOwnProperty("useEpa2021")) {
+ Serial.println("Missing required slr properties");
+ return false;
+ }
+
+ // arduino_json doesn't support float type, need to cast to double first
+ float intercept = (float)((double)slr["intercept"]);
+ float scalingFactor = (float)((double)slr["scalingFactor"]);
+
+ // Compare with current pmCorrection
+ if (pmCorrection.algorithm == algo && pmCorrection.intercept == intercept &&
+ pmCorrection.scalingFactor == scalingFactor &&
+ pmCorrection.useEPA == (bool)slr["useEpa2021"]) {
+ return false; // No changes needed
+ }
+
+ // Deep copy corrections from root to jconfig, so it will be saved later
+ jconfig[jprop_corrections] = corrections;
+
+ // Update pmCorrection with new values
+ pmCorrection.algorithm = algo;
+ pmCorrection.intercept = intercept;
+ pmCorrection.scalingFactor = scalingFactor;
+ pmCorrection.useEPA = (bool)slr["useEpa2021"];
+ pmCorrection.changed = true;
+
+ // Correction values were updated
+ logInfo("PM2.5 correction updated");
+ return true;
+}
+
/**
* @brief Save configure to device storage (EEPROM)
*
@@ -171,6 +289,13 @@ void Configuration::defaultConfig(void) {
jconfig[jprop_offlineMode] = jprop_offlineMode_default;
jconfig[jprop_monitorDisplayCompensatedValues] = jprop_monitorDisplayCompensatedValues_default;
+ // PM2.5 correction
+ pmCorrection.algorithm = None;
+ pmCorrection.changed = false;
+ pmCorrection.intercept = -1;
+ pmCorrection.scalingFactor = -1;
+ pmCorrection.useEPA = false;
+
saveConfig();
}
@@ -660,20 +785,25 @@ bool Configuration::parse(String data, bool isLocal) {
if (curVer != newVer) {
logInfo("Detected new firmware version: " + newVer);
otaNewFirmwareVersion = newVer;
- udpated = true;
+ updated = true;
} else {
otaNewFirmwareVersion = String("");
}
}
}
+ // Corrections
+ if (updatePmCorrection(root)) {
+ changed = true;
+ }
+
if (changed) {
- udpated = true;
+ updated = true;
saveConfig();
printConfig();
} else {
if (ledBarTestRequested || co2CalibrationRequested) {
- udpated = true;
+ updated = true;
}
}
return true;
@@ -860,8 +990,8 @@ String Configuration::getModel(void) {
}
bool Configuration::isUpdated(void) {
- bool updated = this->udpated;
- this->udpated = false;
+ bool updated = this->updated;
+ this->updated = false;
return updated;
}
@@ -1118,6 +1248,15 @@ void Configuration::toConfig(const char *buf) {
jprop_monitorDisplayCompensatedValues_default;
}
+
+ // Set default first before parsing local config
+ pmCorrection.algorithm = PMCorrectionAlgorithm::None;
+ pmCorrection.intercept = 0;
+ pmCorrection.scalingFactor = 0;
+ pmCorrection.useEPA = false;
+ // Load correction from saved config
+ updatePmCorrection(jconfig);
+
if (changed) {
saveConfig();
}
@@ -1216,3 +1355,23 @@ String Configuration::newFirmwareVersion(void) {
otaNewFirmwareVersion = String("");
return newFw;
}
+
+bool Configuration::isPMCorrectionChanged(void) {
+ bool changed = pmCorrection.changed;
+ pmCorrection.changed = false;
+ return changed;
+}
+
+/**
+ * @brief Check if PM correction is enabled
+ *
+ * @return true if PM correction algorithm is not None, otherwise false
+ */
+bool Configuration::isPMCorrectionEnabled(void) {
+ PMCorrection pmCorrection = getPMCorrection();
+ return pmCorrection.algorithm != PMCorrectionAlgorithm::None;
+}
+
+Configuration::PMCorrection Configuration::getPMCorrection(void) {
+ return pmCorrection;
+}
diff --git a/src/AgConfigure.h b/src/AgConfigure.h
index a899b64..1cca028 100644
--- a/src/AgConfigure.h
+++ b/src/AgConfigure.h
@@ -5,12 +5,22 @@
#include "Main/PrintLog.h"
#include "AirGradient.h"
#include
+#include "Libraries/Arduino_JSON/src/Arduino_JSON.h"
class Configuration : public PrintLog {
+public:
+ struct PMCorrection {
+ PMCorrectionAlgorithm algorithm;
+ float intercept;
+ float scalingFactor;
+ bool useEPA; // EPA 2021
+ bool changed;
+ };
+
private:
bool co2CalibrationRequested;
bool ledBarTestRequested;
- bool udpated;
+ bool updated;
String failedMessage;
bool _noxLearnOffsetChanged;
bool _tvocLearningOffsetChanged;
@@ -19,10 +29,13 @@ private:
String otaNewFirmwareVersion;
bool _offlineMode = false;
bool _ledBarModeChanged = false;
+ PMCorrection pmCorrection;
AirGradient* ag;
String getLedBarModeName(LedBarMode mode);
+ PMCorrectionAlgorithm matchPmAlgorithm(String algorithm);
+ bool updatePmCorrection(JSONVar &json);
void saveConfig(void);
void loadConfig(void);
void defaultConfig(void);
@@ -83,6 +96,9 @@ public:
void setOfflineModeWithoutSave(bool offline);
bool isLedBarModeChanged(void);
bool isMonitorDisplayCompensatedValues(void);
+ bool isPMCorrectionChanged(void);
+ bool isPMCorrectionEnabled(void);
+ PMCorrection getPMCorrection(void);
};
#endif /** _AG_CONFIG_H_ */
diff --git a/src/AgOledDisplay.cpp b/src/AgOledDisplay.cpp
index 5661f21..1dfbbba 100644
--- a/src/AgOledDisplay.cpp
+++ b/src/AgOledDisplay.cpp
@@ -313,14 +313,12 @@ void OledDisplay::showDashboard(const char *status) {
DISP()->drawStr(55, 27, "PM2.5");
/** Draw PM2.5 value */
+
int pm25 = value.get(Measurements::PM25);
if (utils::isValidPm(pm25)) {
- /** Compensate PM2.5 value. */
- if (config.hasSensorSHT && config.isMonitorDisplayCompensatedValues()) {
- pm25 = ag->pms5003.compensate(pm25, value.getFloat(Measurements::Humidity));
- logInfo("PM2.5 compensate: " + String(pm25));
+ if (config.hasSensorSHT && config.isPMCorrectionEnabled()) {
+ pm25 = (int)value.getCorrectedPM25(*ag, config);
}
-
if (config.isPmStandardInUSAQI()) {
sprintf(strBuf, "%d", ag->pms5003.convertPm25ToUsAqi(pm25));
} else {
@@ -379,8 +377,8 @@ void OledDisplay::showDashboard(const char *status) {
/** Set PM */
int pm25 = value.get(Measurements::PM25);
- if (config.hasSensorSHT && config.isMonitorDisplayCompensatedValues()) {
- pm25 = (int)ag->pms5003.compensate(pm25, value.getFloat(Measurements::Humidity));
+ if (config.hasSensorSHT && config.isPMCorrectionEnabled()) {
+ pm25 = (int)value.getCorrectedPM25(*ag, config);
}
ag->display.setCursor(0, 12);
diff --git a/src/AgStateMachine.cpp b/src/AgStateMachine.cpp
index 9cbe068..8e0be6a 100644
--- a/src/AgStateMachine.cpp
+++ b/src/AgStateMachine.cpp
@@ -142,8 +142,8 @@ void StateMachine::co2handleLeds(void) {
*/
void StateMachine::pm25handleLeds(void) {
int pm25Value = value.get(Measurements::PM25);
- if (config.isMonitorDisplayCompensatedValues() && config.hasSensorSHT) {
- pm25Value = ag->pms5003.compensate(pm25Value, value.getFloat(Measurements::Humidity));
+ if (config.hasSensorSHT && config.isPMCorrectionEnabled()) {
+ pm25Value = (int)value.getCorrectedPM25(*ag, config);
}
if (pm25Value < 5) {
diff --git a/src/AgValue.cpp b/src/AgValue.cpp
index fb7135a..ca148ca 100644
--- a/src/AgValue.cpp
+++ b/src/AgValue.cpp
@@ -1,6 +1,7 @@
#include "AgValue.h"
#include "AgConfigure.h"
#include "AirGradient.h"
+#include "App/AppDef.h"
#define json_prop_pmFirmware "firmware"
#define json_prop_pm01Ae "pm01"
@@ -482,6 +483,38 @@ void Measurements::validateChannel(int ch) {
}
}
+float Measurements::getCorrectedPM25(AirGradient &ag, Configuration &config, bool useAvg, int ch) {
+ float pm25;
+ float humidity;
+ float pm003Count;
+ int channel = ch - 1; // Array index
+ if (useAvg) {
+ // Directly call from the index
+ pm25 = _pm_25[channel].update.avg;
+ humidity = _humidity[channel].update.avg;
+ pm003Count = _pm_03_pc[channel].update.avg;
+ } else {
+ pm25 = get(PM25, ch);
+ humidity = getFloat(Humidity, ch);
+ pm003Count = get(PM03_PC, ch);
+ }
+
+ 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);
+ if (pmCorrection.useEPA) {
+ // Add EPA compensation on top of SLR
+ pm25 = ag.pms5003.compensate(pm25, humidity);
+ }
+ }
+
+ return pm25;
+}
+
String Measurements::toString(bool localServer, AgFirmwareMode fwMode, int rssi, AirGradient &ag,
Configuration &config) {
JSONVar root;
@@ -605,10 +638,9 @@ JSONVar Measurements::buildIndoor(bool localServer, AirGradient &ag, Configurati
// Add pm25 compensated value only if PM2.5 and humidity value is valid
if (config.hasSensorPMS1 && utils::isValidPm(_pm_25[0].update.avg)) {
if (config.hasSensorSHT && utils::isValidHumidity(_humidity[0].update.avg)) {
- float pm25 = ag.pms5003.compensate(_pm_25[0].update.avg, _humidity[0].update.avg);
- if (utils::isValidPm(pm25)) {
- indoor[json_prop_pm25Compensated] = ag.round2(pm25);
- }
+ // Correction using moving average value
+ float tmp = getCorrectedPM25(ag, config, true);
+ indoor[json_prop_pm25Compensated] = ag.round2(tmp);
}
}
diff --git a/src/AgValue.h b/src/AgValue.h
index 5efea18..3b129b3 100644
--- a/src/AgValue.h
+++ b/src/AgValue.h
@@ -114,6 +114,18 @@ public:
*/
float getFloat(MeasurementType type, int ch = 1);
+
+ /**
+ * @brief Get the Corrected PM25 object based on the correction algorithm from configuration
+ *
+ * @param ag AirGradient instance
+ * @param config Configuration instance
+ * @param useAvg Use moving average value if true, otherwise use latest value
+ * @param ch MeasurementType channel
+ * @return float Corrected PM2.5 value
+ */
+ float getCorrectedPM25(AirGradient &ag, Configuration &config, bool useAvg = false, int ch = 1);
+
/**
* build json payload for every measurements
*/
diff --git a/src/App/AppDef.h b/src/App/AppDef.h
index 3bb9320..1e114e7 100644
--- a/src/App/AppDef.h
+++ b/src/App/AppDef.h
@@ -94,6 +94,18 @@ enum ConfigurationControl {
ConfigurationControlBoth
};
+enum PMCorrectionAlgorithm {
+ Unknown, // Unknown algorithm
+ None, // No PM correction
+ EPA_2021,
+ SLR_PMS5003_20220802,
+ SLR_PMS5003_20220803,
+ SLR_PMS5003_20220824,
+ SLR_PMS5003_20231030,
+ SLR_PMS5003_20231218,
+ SLR_PMS5003_20240104,
+};
+
enum AgFirmwareMode {
FW_MODE_I_9PSL, /** ONE_INDOOR */
FW_MODE_O_1PST, /** PMS5003T, S8 and SGP41 */
diff --git a/src/PMS/PMS.cpp b/src/PMS/PMS.cpp
index 88f17bb..feeb4b4 100644
--- a/src/PMS/PMS.cpp
+++ b/src/PMS/PMS.cpp
@@ -314,6 +314,36 @@ int PMSBase::pm25ToAQI(int pm02) {
return 500;
}
+
+/**
+ * @brief SLR correction for PM2.5
+ *
+ * Reference: https://www.airgradient.com/blog/low-readings-from-pms5003/
+ *
+ * @param pm25 PM2.5 raw value
+ * @param pm003Count PM0.3 count
+ * @param scalingFactor Scaling factor
+ * @param intercept Intercept
+ * @return float Calibrated PM2.5 value
+ */
+float PMSBase::slrCorrection(float pm25, float pm003Count, float scalingFactor, float intercept) {
+ float calibrated;
+
+ float lowCalibrated = (scalingFactor * pm003Count) + intercept;
+ if (lowCalibrated < 31) {
+ calibrated = lowCalibrated;
+ } else {
+ calibrated = pm25;
+ }
+
+ // No negative value for pm2.5
+ if (calibrated < 0) {
+ return 0.0;
+ }
+
+ return calibrated;
+}
+
/**
* @brief Correction PM2.5
*
diff --git a/src/PMS/PMS.h b/src/PMS/PMS.h
index 460a87a..c363c69 100644
--- a/src/PMS/PMS.h
+++ b/src/PMS/PMS.h
@@ -39,6 +39,7 @@ public:
uint8_t getErrorCode(void);
int pm25ToAQI(int pm02);
+ float slrCorrection(float pm25, float pm003Count, float scalingFactor, float intercept);
float compensate(float pm25, float humidity);
private:
diff --git a/src/PMS/PMS5003.cpp b/src/PMS/PMS5003.cpp
index 974bb18..d20c4e9 100644
--- a/src/PMS/PMS5003.cpp
+++ b/src/PMS/PMS5003.cpp
@@ -172,6 +172,10 @@ int PMS5003::getPm10ParticleCount(void) { return pms.getCount10(); }
*/
int PMS5003::convertPm25ToUsAqi(int pm25) { return pms.pm25ToAQI(pm25); }
+float PMS5003::slrCorrection(float pm25, float pm003Count, float scalingFactor, float intercept) {
+ return pms.slrCorrection(pm25, pm003Count, scalingFactor, intercept);
+}
+
/**
* @brief Correct PM2.5
*
diff --git a/src/PMS/PMS5003.h b/src/PMS/PMS5003.h
index 594964e..bd2cf7b 100644
--- a/src/PMS/PMS5003.h
+++ b/src/PMS/PMS5003.h
@@ -42,6 +42,7 @@ public:
int getPm10ParticleCount(void);
int convertPm25ToUsAqi(int pm25);
+ float slrCorrection(float pm25, float pm003Count, float scalingFactor, float intercept);
float compensate(float pm25, float humidity);
int getFirmwareVersion(void);
uint8_t getErrorCode(void);