diff --git a/docs/local-server.md b/docs/local-server.md
index 307ad8c..f4b3f0c 100644
--- a/docs/local-server.md
+++ b/docs/local-server.md
@@ -50,12 +50,20 @@ You get the following response:
|-----------------------------------|---------|----------------------------------------------------------------------------------------|
| `serialno` | String | Serial Number of the monitor |
| `wifi` | Number | WiFi signal strength |
-| `pm01` | Number | PM1 in ug/m3 |
-| `pm02` | Number | PM2.5 in ug/m3 |
-| `pm10` | Number | PM10 in ug/m3 |
+| `pm01` | Number | PM1.0 in ug/m3 (atmospheric environment) |
+| `pm02` | Number | PM2.5 in ug/m3 (atmospheric environment) |
+| `pm10` | Number | PM10 in ug/m3 (atmospheric environment) |
| `pm02Compensated` | Number | PM2.5 in ug/m3 with correction applied (from fw version 3.1.4 onwards) |
+| `pm01Standard` | Number | PM1.0 in ug/m3 (standard particle) |
+| `pm02Standard` | Number | PM2.5 in ug/m3 (standard particle) |
+| `pm10Standard` | Number | PM10 in ug/m3 (standard particle) |
| `rco2` | Number | CO2 in ppm |
-| `pm003Count` | Number | Particle count per dL |
+| `pm003Count` | Number | Particle count 0.3um per dL |
+| `pm005Count` | Number | Particle count 0.5um per dL |
+| `pm01Count` | Number | Particle count 1.0um per dL |
+| `pm02Count` | Number | Particle count 2.5um per dL |
+| `pm50Count` | Number | Particle count 5.0um per dL (only for indoor monitor) |
+| `pm10Count` | Number | Particle count 10um per dL (only for indoor monitor) |
| `atmp` | Number | Temperature in Degrees Celsius |
| `atmpCompensated` | Number | Temperature in Degrees Celsius with correction applied |
| `rhum` | Number | Relative Humidity |
@@ -65,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",
@@ -91,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)
@@ -134,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/examples/BASIC/BASIC.ino b/examples/BASIC/BASIC.ino
index b3be046..78da232 100644
--- a/examples/BASIC/BASIC.ino
+++ b/examples/BASIC/BASIC.ino
@@ -49,9 +49,8 @@ CC BY-SA 4.0 Attribution-ShareAlike 4.0 International License
#define SENSOR_TVOC_UPDATE_INTERVAL 1000 /** ms */
#define SENSOR_CO2_UPDATE_INTERVAL 4000 /** ms */
#define SENSOR_PM_UPDATE_INTERVAL 2000 /** ms */
-#define SENSOR_TEMP_HUM_UPDATE_INTERVAL 2000 /** ms */
+#define SENSOR_TEMP_HUM_UPDATE_INTERVAL 6000 /** ms */
#define DISPLAY_DELAY_SHOW_CONTENT_MS 2000 /** ms */
-#define FIRMWARE_CHECK_FOR_UPDATE_MS (60 * 60 * 1000) /** ms */
static AirGradient ag(DIY_BASIC);
static Configuration configuration(Serial);
@@ -68,7 +67,6 @@ static LocalServer localServer(Serial, openMetrics, measurements, configuration,
wifiConnector);
static MqttClient mqttClient(Serial);
-static int getCO2FailCount = 0;
static AgFirmwareMode fwMode = FW_MODE_I_BASIC_40PS;
static String fwNewVersion;
@@ -90,6 +88,8 @@ static void wdgFeedUpdate(void);
static bool sgp41Init(void);
static void wifiFactoryConfigure(void);
static void mqttHandle(void);
+static int calculateMaxPeriod(int updateInterval);
+static void setMeasurementMaxPeriod();
AgSchedule dispLedSchedule(DISP_UPDATE_INTERVAL, oledDisplaySchedule);
AgSchedule configSchedule(SERVER_CONFIG_SYNC_INTERVAL,
@@ -130,6 +130,10 @@ void setup() {
/** Init sensor */
boardInit();
+ setMeasurementMaxPeriod();
+
+ // Uncomment below line to print every measurements reading update
+ // measurements.setDebug(true);
/** Connecting wifi */
bool connectToWifi = false;
@@ -230,17 +234,16 @@ void loop() {
}
static void co2Update(void) {
+ if (!configuration.hasSensorS8) {
+ // Device don't have S8 sensor
+ return;
+ }
+
int value = ag.s8.getCo2();
if (utils::isValidCO2(value)) {
- measurements.CO2 = value;
- getCO2FailCount = 0;
- Serial.printf("CO2 (ppm): %d\r\n", measurements.CO2);
+ measurements.update(Measurements::CO2, value);
} else {
- getCO2FailCount++;
- Serial.printf("Get CO2 failed: %d\r\n", getCO2FailCount);
- if (getCO2FailCount >= 3) {
- measurements.CO2 = utils::getInvalidCO2();
- }
+ measurements.update(Measurements::CO2, utils::getInvalidCO2());
}
}
@@ -313,8 +316,7 @@ static void mqttHandle(void) {
}
if (mqttClient.isConnected()) {
- String payload = measurements.toString(true, fwMode, wifiConnector.RSSI(),
- &ag, &configuration);
+ String payload = measurements.toString(true, fwMode, wifiConnector.RSSI(), ag, configuration);
String topic = "airgradient/readings/" + ag.deviceId();
if (mqttClient.publish(topic.c_str(), payload.c_str(), payload.length())) {
Serial.println("MQTT sync success");
@@ -490,88 +492,98 @@ static void oledDisplaySchedule(void) {
}
static void updateTvoc(void) {
- measurements.TVOC = ag.sgp41.getTvocIndex();
- measurements.TVOCRaw = ag.sgp41.getTvocRaw();
- measurements.NOx = ag.sgp41.getNoxIndex();
- measurements.NOxRaw = ag.sgp41.getNoxRaw();
+ if (!configuration.hasSensorSGP) {
+ return;
+ }
- Serial.println();
- Serial.printf("TVOC index: %d\r\n", measurements.TVOC);
- Serial.printf("TVOC raw: %d\r\n", measurements.TVOCRaw);
- Serial.printf("NOx index: %d\r\n", measurements.NOx);
- Serial.printf("NOx raw: %d\r\n", measurements.NOxRaw);
+ measurements.update(Measurements::TVOC, ag.sgp41.getTvocIndex());
+ measurements.update(Measurements::TVOCRaw, ag.sgp41.getTvocRaw());
+ measurements.update(Measurements::NOx, ag.sgp41.getNoxIndex());
+ measurements.update(Measurements::NOxRaw, ag.sgp41.getNoxRaw());
}
static void updatePm(void) {
if (ag.pms5003.connected()) {
- measurements.pm01_1 = ag.pms5003.getPm01Ae();
- measurements.pm25_1 = ag.pms5003.getPm25Ae();
- measurements.pm10_1 = ag.pms5003.getPm10Ae();
- measurements.pm03PCount_1 = ag.pms5003.getPm03ParticleCount();
-
- Serial.println();
- Serial.printf("PM1 ug/m3: %d\r\n", measurements.pm01_1);
- Serial.printf("PM2.5 ug/m3: %d\r\n", measurements.pm25_1);
- Serial.printf("PM10 ug/m3: %d\r\n", measurements.pm10_1);
- Serial.printf("PM0.3 Count: %d\r\n", measurements.pm03PCount_1);
- Serial.printf("PM firmware version: %d\r\n", ag.pms5003.getFirmwareVersion());
- ag.pms5003.resetFailCount();
+ measurements.update(Measurements::PM01, ag.pms5003.getPm01Ae());
+ measurements.update(Measurements::PM25, ag.pms5003.getPm25Ae());
+ measurements.update(Measurements::PM10, ag.pms5003.getPm10Ae());
+ measurements.update(Measurements::PM03_PC, ag.pms5003.getPm03ParticleCount());
} else {
- ag.pms5003.updateFailCount();
- Serial.printf("PMS read failed %d times\r\n", ag.pms5003.getFailCount());
- if (ag.pms5003.getFailCount() >= PMS_FAIL_COUNT_SET_INVALID) {
- measurements.pm01_1 = utils::getInvalidPmValue();
- measurements.pm25_1 = utils::getInvalidPmValue();
- measurements.pm10_1 = utils::getInvalidPmValue();
- measurements.pm03PCount_1 = utils::getInvalidPmValue();
- }
-
- if(ag.pms5003.getFailCount() >= ag.pms5003.getFailCountMax()) {
- Serial.printf("PMS failure count reach to max set %d, restarting...", ag.pms5003.getFailCountMax());
- ESP.restart();
- }
+ measurements.update(Measurements::PM01, utils::getInvalidPmValue());
+ measurements.update(Measurements::PM25, utils::getInvalidPmValue());
+ measurements.update(Measurements::PM10, utils::getInvalidPmValue());
+ measurements.update(Measurements::PM03_PC, utils::getInvalidPmValue());
}
}
static void sendDataToServer(void) {
+ /** Increment bootcount when send measurements data is scheduled */
+ measurements.bootCount++;
+
/** Ignore send data to server if postToAirGradient disabled */
if (configuration.isPostDataToAirGradient() == false ||
configuration.isOfflineMode()) {
return;
}
- String syncData = measurements.toString(false, fwMode, wifiConnector.RSSI(),
- &ag, &configuration);
+ String syncData = measurements.toString(false, fwMode, wifiConnector.RSSI(), ag, configuration);
if (apiClient.postToServer(syncData)) {
Serial.println();
Serial.println(
"Online mode and isPostToAirGradient = true: watchdog reset");
Serial.println();
}
- measurements.bootCount++;
}
static void tempHumUpdate(void) {
- delay(100);
if (ag.sht.measure()) {
- measurements.Temperature = ag.sht.getTemperature();
- measurements.Humidity = ag.sht.getRelativeHumidity();
+ float temp = ag.sht.getTemperature();
+ float rhum = ag.sht.getRelativeHumidity();
- Serial.printf("Temperature in C: %0.2f\r\n", measurements.Temperature);
- Serial.printf("Relative Humidity: %d\r\n", measurements.Humidity);
- Serial.printf("Temperature compensated in C: %0.2f\r\n",
- measurements.Temperature);
- Serial.printf("Relative Humidity compensated: %d\r\n",
- measurements.Humidity);
+ measurements.update(Measurements::Temperature, temp);
+ measurements.update(Measurements::Humidity, rhum);
// Update compensation temperature and humidity for SGP41
if (configuration.hasSensorSGP) {
- ag.sgp41.setCompensationTemperatureHumidity(measurements.Temperature,
- measurements.Humidity);
+ ag.sgp41.setCompensationTemperatureHumidity(temp, rhum);
}
} else {
+ measurements.update(Measurements::Temperature, utils::getInvalidTemperature());
+ measurements.update(Measurements::Humidity, utils::getInvalidHumidity());
Serial.println("SHT read failed");
- measurements.Temperature = utils::getInvalidTemperature();
- measurements.Humidity = utils::getInvalidHumidity();
}
}
+
+/* Set max period for each measurement type based on sensor update interval*/
+void setMeasurementMaxPeriod() {
+ /// Max period for S8 sensors measurements
+ measurements.maxPeriod(Measurements::CO2, calculateMaxPeriod(SENSOR_CO2_UPDATE_INTERVAL));
+ /// Max period for SGP sensors measurements
+ measurements.maxPeriod(Measurements::TVOC, calculateMaxPeriod(SENSOR_TVOC_UPDATE_INTERVAL));
+ measurements.maxPeriod(Measurements::TVOCRaw, calculateMaxPeriod(SENSOR_TVOC_UPDATE_INTERVAL));
+ measurements.maxPeriod(Measurements::NOx, calculateMaxPeriod(SENSOR_TVOC_UPDATE_INTERVAL));
+ measurements.maxPeriod(Measurements::NOxRaw, calculateMaxPeriod(SENSOR_TVOC_UPDATE_INTERVAL));
+ /// Max period for PMS sensors measurements
+ measurements.maxPeriod(Measurements::PM25, calculateMaxPeriod(SENSOR_PM_UPDATE_INTERVAL));
+ measurements.maxPeriod(Measurements::PM01, calculateMaxPeriod(SENSOR_PM_UPDATE_INTERVAL));
+ measurements.maxPeriod(Measurements::PM10, calculateMaxPeriod(SENSOR_PM_UPDATE_INTERVAL));
+ measurements.maxPeriod(Measurements::PM03_PC, calculateMaxPeriod(SENSOR_PM_UPDATE_INTERVAL));
+ // Temperature and Humidity
+ if (configuration.hasSensorSHT) {
+ /// Max period for SHT sensors measurements
+ measurements.maxPeriod(Measurements::Temperature,
+ calculateMaxPeriod(SENSOR_TEMP_HUM_UPDATE_INTERVAL));
+ measurements.maxPeriod(Measurements::Humidity,
+ calculateMaxPeriod(SENSOR_TEMP_HUM_UPDATE_INTERVAL));
+ } else {
+ /// Temp and hum data retrieved from PMS5003T sensor
+ measurements.maxPeriod(Measurements::Temperature,
+ calculateMaxPeriod(SENSOR_PM_UPDATE_INTERVAL));
+ measurements.maxPeriod(Measurements::Humidity, calculateMaxPeriod(SENSOR_PM_UPDATE_INTERVAL));
+ }
+}
+
+int calculateMaxPeriod(int updateInterval) {
+ // 0.5 is 50% reduced interval for max period
+ return (SERVER_SYNC_INTERVAL - (SERVER_SYNC_INTERVAL * 0.5)) / updateInterval;
+}
\ No newline at end of file
diff --git a/examples/BASIC/LocalServer.cpp b/examples/BASIC/LocalServer.cpp
index 8970ece..61a6dfb 100644
--- a/examples/BASIC/LocalServer.cpp
+++ b/examples/BASIC/LocalServer.cpp
@@ -53,9 +53,8 @@ void LocalServer::_GET_metrics(void) {
}
void LocalServer::_GET_measure(void) {
- server.send(
- 200, "application/json",
- measure.toString(true, fwMode, wifiConnector.RSSI(), ag, &config));
+ String toSend = measure.toString(true, fwMode, wifiConnector.RSSI(), *ag, config);
+ server.send(200, "application/json", toSend);
}
void LocalServer::setFwMode(AgFirmwareMode fwMode) { this->fwMode = fwMode; }
diff --git a/examples/BASIC/OpenMetrics.cpp b/examples/BASIC/OpenMetrics.cpp
index cffd2c6..ddf2d4c 100644
--- a/examples/BASIC/OpenMetrics.cpp
+++ b/examples/BASIC/OpenMetrics.cpp
@@ -73,19 +73,30 @@ String OpenMetrics::getPayload(void) {
int pm03PCount = utils::getInvalidPmValue();
int atmpCompensated = utils::getInvalidTemperature();
int ahumCompensated = utils::getInvalidHumidity();
+ int tvoc = utils::getInvalidVOC();
+ int tvoc_raw = utils::getInvalidVOC();
+ int nox = utils::getInvalidNOx();
+ int nox_raw = utils::getInvalidNOx();
if (config.hasSensorSHT) {
- _temp = measure.Temperature;
- _hum = measure.Humidity;
+ _temp = measure.getFloat(Measurements::Temperature);
+ _hum = measure.getFloat(Measurements::Humidity);
atmpCompensated = _temp;
ahumCompensated = _hum;
}
if (config.hasSensorPMS1) {
- pm01 = measure.pm01_1;
- pm25 = measure.pm25_1;
- pm10 = measure.pm10_1;
- pm03PCount = measure.pm03PCount_1;
+ pm01 = measure.get(Measurements::PM01);
+ pm25 = measure.get(Measurements::PM25);
+ pm10 = measure.get(Measurements::PM10);
+ pm03PCount = measure.get(Measurements::PM03_PC);
+ }
+
+ if (config.hasSensorSGP) {
+ tvoc = measure.get(Measurements::TVOC);
+ tvoc_raw = measure.get(Measurements::TVOCRaw);
+ nox = measure.get(Measurements::NOx);
+ nox_raw = measure.get(Measurements::NOxRaw);
}
if (config.hasSensorPMS1) {
@@ -120,33 +131,33 @@ String OpenMetrics::getPayload(void) {
}
if (config.hasSensorSGP) {
- if (utils::isValidVOC(measure.TVOC)) {
+ if (utils::isValidVOC(tvoc)) {
add_metric("tvoc_index",
"The processed Total Volatile Organic Compounds (TVOC) index "
"as measured by the AirGradient SGP sensor",
"gauge");
- add_metric_point("", String(measure.TVOC));
+ add_metric_point("", String(tvoc));
}
- if (utils::isValidVOC(measure.TVOCRaw)) {
+ if (utils::isValidVOC(tvoc_raw)) {
add_metric("tvoc_raw",
"The raw input value to the Total Volatile Organic Compounds "
"(TVOC) index as measured by the AirGradient SGP sensor",
"gauge");
- add_metric_point("", String(measure.TVOCRaw));
+ add_metric_point("", String(tvoc_raw));
}
- if (utils::isValidNOx(measure.NOx)) {
+ if (utils::isValidNOx(nox)) {
add_metric("nox_index",
"The processed Nitrous Oxide (NOx) index as measured by the "
"AirGradient SGP sensor",
"gauge");
- add_metric_point("", String(measure.NOx));
+ add_metric_point("", String(nox));
}
- if (utils::isValidNOx(measure.NOxRaw)) {
+ if (utils::isValidNOx(nox_raw)) {
add_metric("nox_raw",
"The raw input value to the Nitrous Oxide (NOx) index as "
"measured by the AirGradient SGP sensor",
"gauge");
- add_metric_point("", String(measure.NOxRaw));
+ add_metric_point("", String(nox_raw));
}
}
diff --git a/examples/DiyProIndoorV3_3/DiyProIndoorV3_3.ino b/examples/DiyProIndoorV3_3/DiyProIndoorV3_3.ino
index b67e262..04430e3 100644
--- a/examples/DiyProIndoorV3_3/DiyProIndoorV3_3.ino
+++ b/examples/DiyProIndoorV3_3/DiyProIndoorV3_3.ino
@@ -49,9 +49,8 @@ CC BY-SA 4.0 Attribution-ShareAlike 4.0 International License
#define SENSOR_TVOC_UPDATE_INTERVAL 1000 /** ms */
#define SENSOR_CO2_UPDATE_INTERVAL 4000 /** ms */
#define SENSOR_PM_UPDATE_INTERVAL 2000 /** ms */
-#define SENSOR_TEMP_HUM_UPDATE_INTERVAL 2000 /** ms */
+#define SENSOR_TEMP_HUM_UPDATE_INTERVAL 6000 /** ms */
#define DISPLAY_DELAY_SHOW_CONTENT_MS 2000 /** ms */
-#define FIRMWARE_CHECK_FOR_UPDATE_MS (60 * 60 * 1000) /** ms */
static AirGradient ag(DIY_PRO_INDOOR_V3_3);
static Configuration configuration(Serial);
@@ -68,7 +67,6 @@ static LocalServer localServer(Serial, openMetrics, measurements, configuration,
wifiConnector);
static MqttClient mqttClient(Serial);
-static int getCO2FailCount = 0;
static AgFirmwareMode fwMode = FW_MODE_I_33PS;
static String fwNewVersion;
@@ -90,6 +88,8 @@ static void wdgFeedUpdate(void);
static bool sgp41Init(void);
static void wifiFactoryConfigure(void);
static void mqttHandle(void);
+static int calculateMaxPeriod(int updateInterval);
+static void setMeasurementMaxPeriod();
AgSchedule dispLedSchedule(DISP_UPDATE_INTERVAL, oledDisplaySchedule);
AgSchedule configSchedule(SERVER_CONFIG_SYNC_INTERVAL,
@@ -130,6 +130,10 @@ void setup() {
/** Init sensor */
boardInit();
+ setMeasurementMaxPeriod();
+
+ // Uncomment below line to print every measurements reading update
+ // measurements.setDebug(true);
/** Connecting wifi */
bool connectToWifi = false;
@@ -228,17 +232,16 @@ void loop() {
}
static void co2Update(void) {
+ if (!configuration.hasSensorS8) {
+ // Device don't have S8 sensor
+ return;
+ }
+
int value = ag.s8.getCo2();
if (utils::isValidCO2(value)) {
- measurements.CO2 = value;
- getCO2FailCount = 0;
- Serial.printf("CO2 (ppm): %d\r\n", measurements.CO2);
+ measurements.update(Measurements::CO2, value);
} else {
- getCO2FailCount++;
- Serial.printf("Get CO2 failed: %d\r\n", getCO2FailCount);
- if (getCO2FailCount >= 3) {
- measurements.CO2 = utils::getInvalidCO2();
- }
+ measurements.update(Measurements::CO2, utils::getInvalidCO2());
}
}
@@ -370,8 +373,7 @@ static void mqttHandle(void) {
}
if (mqttClient.isConnected()) {
- String payload = measurements.toString(true, fwMode, wifiConnector.RSSI(),
- &ag, &configuration);
+ String payload = measurements.toString(true, fwMode, wifiConnector.RSSI(), ag, configuration);
String topic = "airgradient/readings/" + ag.deviceId();
if (mqttClient.publish(topic.c_str(), payload.c_str(), payload.length())) {
Serial.println("MQTT sync success");
@@ -542,88 +544,98 @@ static void oledDisplaySchedule(void) {
}
static void updateTvoc(void) {
- measurements.TVOC = ag.sgp41.getTvocIndex();
- measurements.TVOCRaw = ag.sgp41.getTvocRaw();
- measurements.NOx = ag.sgp41.getNoxIndex();
- measurements.NOxRaw = ag.sgp41.getNoxRaw();
+ if (!configuration.hasSensorSGP) {
+ return;
+ }
- Serial.println();
- Serial.printf("TVOC index: %d\r\n", measurements.TVOC);
- Serial.printf("TVOC raw: %d\r\n", measurements.TVOCRaw);
- Serial.printf("NOx index: %d\r\n", measurements.NOx);
- Serial.printf("NOx raw: %d\r\n", measurements.NOxRaw);
+ measurements.update(Measurements::TVOC, ag.sgp41.getTvocIndex());
+ measurements.update(Measurements::TVOCRaw, ag.sgp41.getTvocRaw());
+ measurements.update(Measurements::NOx, ag.sgp41.getNoxIndex());
+ measurements.update(Measurements::NOxRaw, ag.sgp41.getNoxRaw());
}
static void updatePm(void) {
if (ag.pms5003.connected()) {
- measurements.pm01_1 = ag.pms5003.getPm01Ae();
- measurements.pm25_1 = ag.pms5003.getPm25Ae();
- measurements.pm10_1 = ag.pms5003.getPm10Ae();
- measurements.pm03PCount_1 = ag.pms5003.getPm03ParticleCount();
-
- Serial.println();
- Serial.printf("PM1 ug/m3: %d\r\n", measurements.pm01_1);
- Serial.printf("PM2.5 ug/m3: %d\r\n", measurements.pm25_1);
- Serial.printf("PM10 ug/m3: %d\r\n", measurements.pm10_1);
- Serial.printf("PM0.3 Count: %d\r\n", measurements.pm03PCount_1);
- Serial.printf("PM firmware version: %d\r\n", ag.pms5003.getFirmwareVersion());
- ag.pms5003.resetFailCount();
+ measurements.update(Measurements::PM01, ag.pms5003.getPm01Ae());
+ measurements.update(Measurements::PM25, ag.pms5003.getPm25Ae());
+ measurements.update(Measurements::PM10, ag.pms5003.getPm10Ae());
+ measurements.update(Measurements::PM03_PC, ag.pms5003.getPm03ParticleCount());
} else {
- ag.pms5003.updateFailCount();
- Serial.printf("PMS read failed %d times\r\n", ag.pms5003.getFailCount());
- if (ag.pms5003.getFailCount() >= PMS_FAIL_COUNT_SET_INVALID) {
- measurements.pm01_1 = utils::getInvalidPmValue();
- measurements.pm25_1 = utils::getInvalidPmValue();
- measurements.pm10_1 = utils::getInvalidPmValue();
- measurements.pm03PCount_1 = utils::getInvalidPmValue();
- }
-
- if(ag.pms5003.getFailCount() >= ag.pms5003.getFailCountMax()) {
- Serial.printf("PMS failure count reach to max set %d, restarting...", ag.pms5003.getFailCountMax());
- ESP.restart();
- }
+ measurements.update(Measurements::PM01, utils::getInvalidPmValue());
+ measurements.update(Measurements::PM25, utils::getInvalidPmValue());
+ measurements.update(Measurements::PM10, utils::getInvalidPmValue());
+ measurements.update(Measurements::PM03_PC, utils::getInvalidPmValue());
}
}
static void sendDataToServer(void) {
+ /** Increment bootcount when send measurements data is scheduled */
+ measurements.bootCount++;
+
/** Ignore send data to server if postToAirGradient disabled */
if (configuration.isPostDataToAirGradient() == false ||
configuration.isOfflineMode()) {
return;
}
- String syncData = measurements.toString(false, fwMode, wifiConnector.RSSI(),
- &ag, &configuration);
+ String syncData = measurements.toString(false, fwMode, wifiConnector.RSSI(), ag, configuration);
if (apiClient.postToServer(syncData)) {
Serial.println();
Serial.println(
"Online mode and isPostToAirGradient = true: watchdog reset");
Serial.println();
}
- measurements.bootCount++;
}
static void tempHumUpdate(void) {
- delay(100);
if (ag.sht.measure()) {
- measurements.Temperature = ag.sht.getTemperature();
- measurements.Humidity = ag.sht.getRelativeHumidity();
+ float temp = ag.sht.getTemperature();
+ float rhum = ag.sht.getRelativeHumidity();
- Serial.printf("Temperature in C: %0.2f\r\n", measurements.Temperature);
- Serial.printf("Relative Humidity: %d\r\n", measurements.Humidity);
- Serial.printf("Temperature compensated in C: %0.2f\r\n",
- measurements.Temperature);
- Serial.printf("Relative Humidity compensated: %d\r\n",
- measurements.Humidity);
+ measurements.update(Measurements::Temperature, temp);
+ measurements.update(Measurements::Humidity, rhum);
// Update compensation temperature and humidity for SGP41
if (configuration.hasSensorSGP) {
- ag.sgp41.setCompensationTemperatureHumidity(measurements.Temperature,
- measurements.Humidity);
+ ag.sgp41.setCompensationTemperatureHumidity(temp, rhum);
}
} else {
+ measurements.update(Measurements::Temperature, utils::getInvalidTemperature());
+ measurements.update(Measurements::Humidity, utils::getInvalidHumidity());
Serial.println("SHT read failed");
- measurements.Temperature = utils::getInvalidTemperature();
- measurements.Humidity = utils::getInvalidHumidity();
}
}
+
+/* Set max period for each measurement type based on sensor update interval*/
+void setMeasurementMaxPeriod() {
+ /// Max period for S8 sensors measurements
+ measurements.maxPeriod(Measurements::CO2, calculateMaxPeriod(SENSOR_CO2_UPDATE_INTERVAL));
+ /// Max period for SGP sensors measurements
+ measurements.maxPeriod(Measurements::TVOC, calculateMaxPeriod(SENSOR_TVOC_UPDATE_INTERVAL));
+ measurements.maxPeriod(Measurements::TVOCRaw, calculateMaxPeriod(SENSOR_TVOC_UPDATE_INTERVAL));
+ measurements.maxPeriod(Measurements::NOx, calculateMaxPeriod(SENSOR_TVOC_UPDATE_INTERVAL));
+ measurements.maxPeriod(Measurements::NOxRaw, calculateMaxPeriod(SENSOR_TVOC_UPDATE_INTERVAL));
+ /// Max period for PMS sensors measurements
+ measurements.maxPeriod(Measurements::PM25, calculateMaxPeriod(SENSOR_PM_UPDATE_INTERVAL));
+ measurements.maxPeriod(Measurements::PM01, calculateMaxPeriod(SENSOR_PM_UPDATE_INTERVAL));
+ measurements.maxPeriod(Measurements::PM10, calculateMaxPeriod(SENSOR_PM_UPDATE_INTERVAL));
+ measurements.maxPeriod(Measurements::PM03_PC, calculateMaxPeriod(SENSOR_PM_UPDATE_INTERVAL));
+ // Temperature and Humidity
+ if (configuration.hasSensorSHT) {
+ /// Max period for SHT sensors measurements
+ measurements.maxPeriod(Measurements::Temperature,
+ calculateMaxPeriod(SENSOR_TEMP_HUM_UPDATE_INTERVAL));
+ measurements.maxPeriod(Measurements::Humidity,
+ calculateMaxPeriod(SENSOR_TEMP_HUM_UPDATE_INTERVAL));
+ } else {
+ /// Temp and hum data retrieved from PMS5003T sensor
+ measurements.maxPeriod(Measurements::Temperature,
+ calculateMaxPeriod(SENSOR_PM_UPDATE_INTERVAL));
+ measurements.maxPeriod(Measurements::Humidity, calculateMaxPeriod(SENSOR_PM_UPDATE_INTERVAL));
+ }
+}
+
+int calculateMaxPeriod(int updateInterval) {
+ // 0.5 is 50% reduced interval for max period
+ return (SERVER_SYNC_INTERVAL - (SERVER_SYNC_INTERVAL * 0.5)) / updateInterval;
+}
\ No newline at end of file
diff --git a/examples/DiyProIndoorV3_3/LocalServer.cpp b/examples/DiyProIndoorV3_3/LocalServer.cpp
index 8970ece..61a6dfb 100644
--- a/examples/DiyProIndoorV3_3/LocalServer.cpp
+++ b/examples/DiyProIndoorV3_3/LocalServer.cpp
@@ -53,9 +53,8 @@ void LocalServer::_GET_metrics(void) {
}
void LocalServer::_GET_measure(void) {
- server.send(
- 200, "application/json",
- measure.toString(true, fwMode, wifiConnector.RSSI(), ag, &config));
+ String toSend = measure.toString(true, fwMode, wifiConnector.RSSI(), *ag, config);
+ server.send(200, "application/json", toSend);
}
void LocalServer::setFwMode(AgFirmwareMode fwMode) { this->fwMode = fwMode; }
diff --git a/examples/DiyProIndoorV3_3/OpenMetrics.cpp b/examples/DiyProIndoorV3_3/OpenMetrics.cpp
index cffd2c6..ddf2d4c 100644
--- a/examples/DiyProIndoorV3_3/OpenMetrics.cpp
+++ b/examples/DiyProIndoorV3_3/OpenMetrics.cpp
@@ -73,19 +73,30 @@ String OpenMetrics::getPayload(void) {
int pm03PCount = utils::getInvalidPmValue();
int atmpCompensated = utils::getInvalidTemperature();
int ahumCompensated = utils::getInvalidHumidity();
+ int tvoc = utils::getInvalidVOC();
+ int tvoc_raw = utils::getInvalidVOC();
+ int nox = utils::getInvalidNOx();
+ int nox_raw = utils::getInvalidNOx();
if (config.hasSensorSHT) {
- _temp = measure.Temperature;
- _hum = measure.Humidity;
+ _temp = measure.getFloat(Measurements::Temperature);
+ _hum = measure.getFloat(Measurements::Humidity);
atmpCompensated = _temp;
ahumCompensated = _hum;
}
if (config.hasSensorPMS1) {
- pm01 = measure.pm01_1;
- pm25 = measure.pm25_1;
- pm10 = measure.pm10_1;
- pm03PCount = measure.pm03PCount_1;
+ pm01 = measure.get(Measurements::PM01);
+ pm25 = measure.get(Measurements::PM25);
+ pm10 = measure.get(Measurements::PM10);
+ pm03PCount = measure.get(Measurements::PM03_PC);
+ }
+
+ if (config.hasSensorSGP) {
+ tvoc = measure.get(Measurements::TVOC);
+ tvoc_raw = measure.get(Measurements::TVOCRaw);
+ nox = measure.get(Measurements::NOx);
+ nox_raw = measure.get(Measurements::NOxRaw);
}
if (config.hasSensorPMS1) {
@@ -120,33 +131,33 @@ String OpenMetrics::getPayload(void) {
}
if (config.hasSensorSGP) {
- if (utils::isValidVOC(measure.TVOC)) {
+ if (utils::isValidVOC(tvoc)) {
add_metric("tvoc_index",
"The processed Total Volatile Organic Compounds (TVOC) index "
"as measured by the AirGradient SGP sensor",
"gauge");
- add_metric_point("", String(measure.TVOC));
+ add_metric_point("", String(tvoc));
}
- if (utils::isValidVOC(measure.TVOCRaw)) {
+ if (utils::isValidVOC(tvoc_raw)) {
add_metric("tvoc_raw",
"The raw input value to the Total Volatile Organic Compounds "
"(TVOC) index as measured by the AirGradient SGP sensor",
"gauge");
- add_metric_point("", String(measure.TVOCRaw));
+ add_metric_point("", String(tvoc_raw));
}
- if (utils::isValidNOx(measure.NOx)) {
+ if (utils::isValidNOx(nox)) {
add_metric("nox_index",
"The processed Nitrous Oxide (NOx) index as measured by the "
"AirGradient SGP sensor",
"gauge");
- add_metric_point("", String(measure.NOx));
+ add_metric_point("", String(nox));
}
- if (utils::isValidNOx(measure.NOxRaw)) {
+ if (utils::isValidNOx(nox_raw)) {
add_metric("nox_raw",
"The raw input value to the Nitrous Oxide (NOx) index as "
"measured by the AirGradient SGP sensor",
"gauge");
- add_metric_point("", String(measure.NOxRaw));
+ add_metric_point("", String(nox_raw));
}
}
diff --git a/examples/DiyProIndoorV4_2/DiyProIndoorV4_2.ino b/examples/DiyProIndoorV4_2/DiyProIndoorV4_2.ino
index bac20a5..8bfb466 100644
--- a/examples/DiyProIndoorV4_2/DiyProIndoorV4_2.ino
+++ b/examples/DiyProIndoorV4_2/DiyProIndoorV4_2.ino
@@ -49,9 +49,8 @@ CC BY-SA 4.0 Attribution-ShareAlike 4.0 International License
#define SENSOR_TVOC_UPDATE_INTERVAL 1000 /** ms */
#define SENSOR_CO2_UPDATE_INTERVAL 4000 /** ms */
#define SENSOR_PM_UPDATE_INTERVAL 2000 /** ms */
-#define SENSOR_TEMP_HUM_UPDATE_INTERVAL 2000 /** ms */
+#define SENSOR_TEMP_HUM_UPDATE_INTERVAL 6000 /** ms */
#define DISPLAY_DELAY_SHOW_CONTENT_MS 2000 /** ms */
-#define FIRMWARE_CHECK_FOR_UPDATE_MS (60 * 60 * 1000) /** ms */
static AirGradient ag(DIY_PRO_INDOOR_V4_2);
static Configuration configuration(Serial);
@@ -69,7 +68,6 @@ static LocalServer localServer(Serial, openMetrics, measurements, configuration,
static MqttClient mqttClient(Serial);
static uint32_t factoryBtnPressTime = 0;
-static int getCO2FailCount = 0;
static AgFirmwareMode fwMode = FW_MODE_I_42PS;
static String fwNewVersion;
@@ -91,6 +89,8 @@ static void wdgFeedUpdate(void);
static bool sgp41Init(void);
static void wifiFactoryConfigure(void);
static void mqttHandle(void);
+static int calculateMaxPeriod(int updateInterval);
+static void setMeasurementMaxPeriod();
AgSchedule dispLedSchedule(DISP_UPDATE_INTERVAL, oledDisplaySchedule);
AgSchedule configSchedule(SERVER_CONFIG_SYNC_INTERVAL,
@@ -131,6 +131,10 @@ void setup() {
/** Init sensor */
boardInit();
+ setMeasurementMaxPeriod();
+
+ // Uncomment below line to print every measurements reading update
+ // measurements.setDebug(true);
/** Connecting wifi */
bool connectToWifi = false;
@@ -255,17 +259,16 @@ void loop() {
}
static void co2Update(void) {
+ if (!configuration.hasSensorS8) {
+ // Device don't have S8 sensor
+ return;
+ }
+
int value = ag.s8.getCo2();
if (utils::isValidCO2(value)) {
- measurements.CO2 = value;
- getCO2FailCount = 0;
- Serial.printf("CO2 (ppm): %d\r\n", measurements.CO2);
+ measurements.update(Measurements::CO2, value);
} else {
- getCO2FailCount++;
- Serial.printf("Get CO2 failed: %d\r\n", getCO2FailCount);
- if (getCO2FailCount >= 3) {
- measurements.CO2 = utils::getInvalidCO2();
- }
+ measurements.update(Measurements::CO2, utils::getInvalidCO2());
}
}
@@ -393,8 +396,7 @@ static void mqttHandle(void) {
}
if (mqttClient.isConnected()) {
- String payload = measurements.toString(true, fwMode, wifiConnector.RSSI(),
- &ag, &configuration);
+ String payload = measurements.toString(true, fwMode, wifiConnector.RSSI(), ag, configuration);
String topic = "airgradient/readings/" + ag.deviceId();
if (mqttClient.publish(topic.c_str(), payload.c_str(), payload.length())) {
Serial.println("MQTT sync success");
@@ -583,88 +585,98 @@ static void oledDisplaySchedule(void) {
}
static void updateTvoc(void) {
- measurements.TVOC = ag.sgp41.getTvocIndex();
- measurements.TVOCRaw = ag.sgp41.getTvocRaw();
- measurements.NOx = ag.sgp41.getNoxIndex();
- measurements.NOxRaw = ag.sgp41.getNoxRaw();
+ if (!configuration.hasSensorSGP) {
+ return;
+ }
- Serial.println();
- Serial.printf("TVOC index: %d\r\n", measurements.TVOC);
- Serial.printf("TVOC raw: %d\r\n", measurements.TVOCRaw);
- Serial.printf("NOx index: %d\r\n", measurements.NOx);
- Serial.printf("NOx raw: %d\r\n", measurements.NOxRaw);
+ measurements.update(Measurements::TVOC, ag.sgp41.getTvocIndex());
+ measurements.update(Measurements::TVOCRaw, ag.sgp41.getTvocRaw());
+ measurements.update(Measurements::NOx, ag.sgp41.getNoxIndex());
+ measurements.update(Measurements::NOxRaw, ag.sgp41.getNoxRaw());
}
static void updatePm(void) {
if (ag.pms5003.connected()) {
- measurements.pm01_1 = ag.pms5003.getPm01Ae();
- measurements.pm25_1 = ag.pms5003.getPm25Ae();
- measurements.pm10_1 = ag.pms5003.getPm10Ae();
- measurements.pm03PCount_1 = ag.pms5003.getPm03ParticleCount();
-
- Serial.println();
- Serial.printf("PM1 ug/m3: %d\r\n", measurements.pm01_1);
- Serial.printf("PM2.5 ug/m3: %d\r\n", measurements.pm25_1);
- Serial.printf("PM10 ug/m3: %d\r\n", measurements.pm10_1);
- Serial.printf("PM0.3 Count: %d\r\n", measurements.pm03PCount_1);
- Serial.printf("PM firmware version: %d\r\n", ag.pms5003.getFirmwareVersion());
- ag.pms5003.resetFailCount();
+ measurements.update(Measurements::PM01, ag.pms5003.getPm01Ae());
+ measurements.update(Measurements::PM25, ag.pms5003.getPm25Ae());
+ measurements.update(Measurements::PM10, ag.pms5003.getPm10Ae());
+ measurements.update(Measurements::PM03_PC, ag.pms5003.getPm03ParticleCount());
} else {
- ag.pms5003.updateFailCount();
- Serial.printf("PMS read failed %d times\r\n", ag.pms5003.getFailCount());
- if (ag.pms5003.getFailCount() >= PMS_FAIL_COUNT_SET_INVALID) {
- measurements.pm01_1 = utils::getInvalidPmValue();
- measurements.pm25_1 = utils::getInvalidPmValue();
- measurements.pm10_1 = utils::getInvalidPmValue();
- measurements.pm03PCount_1 = utils::getInvalidPmValue();
- }
-
- if(ag.pms5003.getFailCount() >= ag.pms5003.getFailCountMax()) {
- Serial.printf("PMS failure count reach to max set %d, restarting...", ag.pms5003.getFailCountMax());
- ESP.restart();
- }
+ measurements.update(Measurements::PM01, utils::getInvalidPmValue());
+ measurements.update(Measurements::PM25, utils::getInvalidPmValue());
+ measurements.update(Measurements::PM10, utils::getInvalidPmValue());
+ measurements.update(Measurements::PM03_PC, utils::getInvalidPmValue());
}
}
static void sendDataToServer(void) {
+ /** Increment bootcount when send measurements data is scheduled */
+ measurements.bootCount++;
+
/** Ignore send data to server if postToAirGradient disabled */
if (configuration.isPostDataToAirGradient() == false ||
configuration.isOfflineMode()) {
return;
}
- String syncData = measurements.toString(false, fwMode, wifiConnector.RSSI(),
- &ag, &configuration);
+ String syncData = measurements.toString(false, fwMode, wifiConnector.RSSI(), ag, configuration);
if (apiClient.postToServer(syncData)) {
Serial.println();
Serial.println(
"Online mode and isPostToAirGradient = true: watchdog reset");
Serial.println();
}
- measurements.bootCount++;
}
static void tempHumUpdate(void) {
- delay(100);
if (ag.sht.measure()) {
- measurements.Temperature = ag.sht.getTemperature();
- measurements.Humidity = ag.sht.getRelativeHumidity();
+ float temp = ag.sht.getTemperature();
+ float rhum = ag.sht.getRelativeHumidity();
- Serial.printf("Temperature in C: %0.2f\r\n", measurements.Temperature);
- Serial.printf("Relative Humidity: %d\r\n", measurements.Humidity);
- Serial.printf("Temperature compensated in C: %0.2f\r\n",
- measurements.Temperature);
- Serial.printf("Relative Humidity compensated: %d\r\n",
- measurements.Humidity);
+ measurements.update(Measurements::Temperature, temp);
+ measurements.update(Measurements::Humidity, rhum);
// Update compensation temperature and humidity for SGP41
if (configuration.hasSensorSGP) {
- ag.sgp41.setCompensationTemperatureHumidity(measurements.Temperature,
- measurements.Humidity);
+ ag.sgp41.setCompensationTemperatureHumidity(temp, rhum);
}
} else {
+ measurements.update(Measurements::Temperature, utils::getInvalidTemperature());
+ measurements.update(Measurements::Humidity, utils::getInvalidHumidity());
Serial.println("SHT read failed");
- measurements.Temperature = utils::getInvalidTemperature();
- measurements.Humidity = utils::getInvalidHumidity();
}
}
+
+/* Set max period for each measurement type based on sensor update interval*/
+void setMeasurementMaxPeriod() {
+ /// Max period for S8 sensors measurements
+ measurements.maxPeriod(Measurements::CO2, calculateMaxPeriod(SENSOR_CO2_UPDATE_INTERVAL));
+ /// Max period for SGP sensors measurements
+ measurements.maxPeriod(Measurements::TVOC, calculateMaxPeriod(SENSOR_TVOC_UPDATE_INTERVAL));
+ measurements.maxPeriod(Measurements::TVOCRaw, calculateMaxPeriod(SENSOR_TVOC_UPDATE_INTERVAL));
+ measurements.maxPeriod(Measurements::NOx, calculateMaxPeriod(SENSOR_TVOC_UPDATE_INTERVAL));
+ measurements.maxPeriod(Measurements::NOxRaw, calculateMaxPeriod(SENSOR_TVOC_UPDATE_INTERVAL));
+ /// Max period for PMS sensors measurements
+ measurements.maxPeriod(Measurements::PM25, calculateMaxPeriod(SENSOR_PM_UPDATE_INTERVAL));
+ measurements.maxPeriod(Measurements::PM01, calculateMaxPeriod(SENSOR_PM_UPDATE_INTERVAL));
+ measurements.maxPeriod(Measurements::PM10, calculateMaxPeriod(SENSOR_PM_UPDATE_INTERVAL));
+ measurements.maxPeriod(Measurements::PM03_PC, calculateMaxPeriod(SENSOR_PM_UPDATE_INTERVAL));
+ // Temperature and Humidity
+ if (configuration.hasSensorSHT) {
+ /// Max period for SHT sensors measurements
+ measurements.maxPeriod(Measurements::Temperature,
+ calculateMaxPeriod(SENSOR_TEMP_HUM_UPDATE_INTERVAL));
+ measurements.maxPeriod(Measurements::Humidity,
+ calculateMaxPeriod(SENSOR_TEMP_HUM_UPDATE_INTERVAL));
+ } else {
+ /// Temp and hum data retrieved from PMS5003T sensor
+ measurements.maxPeriod(Measurements::Temperature,
+ calculateMaxPeriod(SENSOR_PM_UPDATE_INTERVAL));
+ measurements.maxPeriod(Measurements::Humidity, calculateMaxPeriod(SENSOR_PM_UPDATE_INTERVAL));
+ }
+}
+
+int calculateMaxPeriod(int updateInterval) {
+ // 0.5 is 50% reduced interval for max period
+ return (SERVER_SYNC_INTERVAL - (SERVER_SYNC_INTERVAL * 0.5)) / updateInterval;
+}
\ No newline at end of file
diff --git a/examples/DiyProIndoorV4_2/LocalServer.cpp b/examples/DiyProIndoorV4_2/LocalServer.cpp
index 8970ece..61a6dfb 100644
--- a/examples/DiyProIndoorV4_2/LocalServer.cpp
+++ b/examples/DiyProIndoorV4_2/LocalServer.cpp
@@ -53,9 +53,8 @@ void LocalServer::_GET_metrics(void) {
}
void LocalServer::_GET_measure(void) {
- server.send(
- 200, "application/json",
- measure.toString(true, fwMode, wifiConnector.RSSI(), ag, &config));
+ String toSend = measure.toString(true, fwMode, wifiConnector.RSSI(), *ag, config);
+ server.send(200, "application/json", toSend);
}
void LocalServer::setFwMode(AgFirmwareMode fwMode) { this->fwMode = fwMode; }
diff --git a/examples/DiyProIndoorV4_2/OpenMetrics.cpp b/examples/DiyProIndoorV4_2/OpenMetrics.cpp
index cffd2c6..ddf2d4c 100644
--- a/examples/DiyProIndoorV4_2/OpenMetrics.cpp
+++ b/examples/DiyProIndoorV4_2/OpenMetrics.cpp
@@ -73,19 +73,30 @@ String OpenMetrics::getPayload(void) {
int pm03PCount = utils::getInvalidPmValue();
int atmpCompensated = utils::getInvalidTemperature();
int ahumCompensated = utils::getInvalidHumidity();
+ int tvoc = utils::getInvalidVOC();
+ int tvoc_raw = utils::getInvalidVOC();
+ int nox = utils::getInvalidNOx();
+ int nox_raw = utils::getInvalidNOx();
if (config.hasSensorSHT) {
- _temp = measure.Temperature;
- _hum = measure.Humidity;
+ _temp = measure.getFloat(Measurements::Temperature);
+ _hum = measure.getFloat(Measurements::Humidity);
atmpCompensated = _temp;
ahumCompensated = _hum;
}
if (config.hasSensorPMS1) {
- pm01 = measure.pm01_1;
- pm25 = measure.pm25_1;
- pm10 = measure.pm10_1;
- pm03PCount = measure.pm03PCount_1;
+ pm01 = measure.get(Measurements::PM01);
+ pm25 = measure.get(Measurements::PM25);
+ pm10 = measure.get(Measurements::PM10);
+ pm03PCount = measure.get(Measurements::PM03_PC);
+ }
+
+ if (config.hasSensorSGP) {
+ tvoc = measure.get(Measurements::TVOC);
+ tvoc_raw = measure.get(Measurements::TVOCRaw);
+ nox = measure.get(Measurements::NOx);
+ nox_raw = measure.get(Measurements::NOxRaw);
}
if (config.hasSensorPMS1) {
@@ -120,33 +131,33 @@ String OpenMetrics::getPayload(void) {
}
if (config.hasSensorSGP) {
- if (utils::isValidVOC(measure.TVOC)) {
+ if (utils::isValidVOC(tvoc)) {
add_metric("tvoc_index",
"The processed Total Volatile Organic Compounds (TVOC) index "
"as measured by the AirGradient SGP sensor",
"gauge");
- add_metric_point("", String(measure.TVOC));
+ add_metric_point("", String(tvoc));
}
- if (utils::isValidVOC(measure.TVOCRaw)) {
+ if (utils::isValidVOC(tvoc_raw)) {
add_metric("tvoc_raw",
"The raw input value to the Total Volatile Organic Compounds "
"(TVOC) index as measured by the AirGradient SGP sensor",
"gauge");
- add_metric_point("", String(measure.TVOCRaw));
+ add_metric_point("", String(tvoc_raw));
}
- if (utils::isValidNOx(measure.NOx)) {
+ if (utils::isValidNOx(nox)) {
add_metric("nox_index",
"The processed Nitrous Oxide (NOx) index as measured by the "
"AirGradient SGP sensor",
"gauge");
- add_metric_point("", String(measure.NOx));
+ add_metric_point("", String(nox));
}
- if (utils::isValidNOx(measure.NOxRaw)) {
+ if (utils::isValidNOx(nox_raw)) {
add_metric("nox_raw",
"The raw input value to the Nitrous Oxide (NOx) index as "
"measured by the AirGradient SGP sensor",
"gauge");
- add_metric_point("", String(measure.NOxRaw));
+ add_metric_point("", String(nox_raw));
}
}
diff --git a/examples/OneOpenAir/LocalServer.cpp b/examples/OneOpenAir/LocalServer.cpp
index 1b88e8e..e0b5c98 100644
--- a/examples/OneOpenAir/LocalServer.cpp
+++ b/examples/OneOpenAir/LocalServer.cpp
@@ -64,9 +64,8 @@ void LocalServer::_GET_metrics(void) {
}
void LocalServer::_GET_measure(void) {
- server.send(
- 200, "application/json",
- measure.toString(true, fwMode, wifiConnector.RSSI(), ag, &config));
+ String toSend = measure.toString(true, fwMode, wifiConnector.RSSI(), *ag, config);
+ server.send(200, "application/json", toSend);
}
void LocalServer::setFwMode(AgFirmwareMode fwMode) { this->fwMode = fwMode; }
diff --git a/examples/OneOpenAir/OneOpenAir.ino b/examples/OneOpenAir/OneOpenAir.ino
index f4bf37b..87d5d21 100644
--- a/examples/OneOpenAir/OneOpenAir.ino
+++ b/examples/OneOpenAir/OneOpenAir.ino
@@ -62,7 +62,7 @@ CC BY-SA 4.0 Attribution-ShareAlike 4.0 International License
#define SENSOR_TVOC_UPDATE_INTERVAL 1000 /** ms */
#define SENSOR_CO2_UPDATE_INTERVAL 4000 /** ms */
#define SENSOR_PM_UPDATE_INTERVAL 2000 /** ms */
-#define SENSOR_TEMP_HUM_UPDATE_INTERVAL 2000 /** ms */
+#define SENSOR_TEMP_HUM_UPDATE_INTERVAL 6000 /** ms */
#define DISPLAY_DELAY_SHOW_CONTENT_MS 2000 /** ms */
#define FIRMWARE_CHECK_FOR_UPDATE_MS (60*60*1000) /** ms */
@@ -89,7 +89,6 @@ static LocalServer localServer(Serial, openMetrics, measurements, configuration,
wifiConnector);
static uint32_t factoryBtnPressTime = 0;
-static int getCO2FailCount = 0;
static AgFirmwareMode fwMode = FW_MODE_I_9PSL;
static bool ledBarButtonTest = false;
@@ -115,6 +114,8 @@ static void firmwareCheckForUpdate(void);
static void otaHandlerCallback(OtaState state, String mesasge);
static void displayExecuteOta(OtaState state, String msg,
int processing);
+static int calculateMaxPeriod(int updateInterval);
+static void setMeasurementMaxPeriod();
AgSchedule dispLedSchedule(DISP_UPDATE_INTERVAL, updateDisplayAndLedBar);
AgSchedule configSchedule(SERVER_CONFIG_SYNC_INTERVAL,
@@ -165,6 +166,10 @@ void setup() {
/** Init sensor */
boardInit();
+ setMeasurementMaxPeriod();
+
+ // Uncomment below line to print every measurements reading update
+ measurements.setDebug(true);
/** Connecting wifi */
bool connectToWifi = false;
@@ -317,17 +322,16 @@ void loop() {
}
static void co2Update(void) {
+ if (!configuration.hasSensorS8) {
+ // Device don't have S8 sensor
+ return;
+ }
+
int value = ag->s8.getCo2();
if (utils::isValidCO2(value)) {
- measurements.CO2 = value;
- getCO2FailCount = 0;
- Serial.printf("CO2 (ppm): %d\r\n", measurements.CO2);
+ measurements.update(Measurements::CO2, value);
} else {
- getCO2FailCount++;
- Serial.printf("Get CO2 failed: %d\r\n", getCO2FailCount);
- if (getCO2FailCount >= 3) {
- measurements.CO2 = utils::getInvalidCO2();
- }
+ measurements.update(Measurements::CO2, utils::getInvalidCO2());
}
}
@@ -360,8 +364,8 @@ static void createMqttTask(void) {
/** Send data */
if (mqttClient.isConnected()) {
- String payload = measurements.toString(
- true, fwMode, wifiConnector.RSSI(), ag, &configuration);
+ String payload =
+ measurements.toString(true, fwMode, wifiConnector.RSSI(), *ag, configuration);
String topic = "airgradient/readings/" + ag->deviceId();
if (mqttClient.publish(topic.c_str(), payload.c_str(),
@@ -982,281 +986,236 @@ static void updateDisplayAndLedBar(void) {
}
static void updateTvoc(void) {
- measurements.TVOC = ag->sgp41.getTvocIndex();
- measurements.TVOCRaw = ag->sgp41.getTvocRaw();
- measurements.NOx = ag->sgp41.getNoxIndex();
- measurements.NOxRaw = ag->sgp41.getNoxRaw();
+ if (!configuration.hasSensorSGP) {
+ return;
+ }
- Serial.println();
- Serial.printf("TVOC index: %d\r\n", measurements.TVOC);
- Serial.printf("TVOC raw: %d\r\n", measurements.TVOCRaw);
- Serial.printf("NOx index: %d\r\n", measurements.NOx);
- Serial.printf("NOx raw: %d\r\n", measurements.NOxRaw);
+ measurements.update(Measurements::TVOC, ag->sgp41.getTvocIndex());
+ measurements.update(Measurements::TVOCRaw, ag->sgp41.getTvocRaw());
+ measurements.update(Measurements::NOx, ag->sgp41.getNoxIndex());
+ measurements.update(Measurements::NOxRaw, ag->sgp41.getNoxRaw());
+}
+
+static void updatePMS5003() {
+ if (ag->pms5003.connected()) {
+ measurements.update(Measurements::PM01, ag->pms5003.getPm01Ae());
+ measurements.update(Measurements::PM25, ag->pms5003.getPm25Ae());
+ measurements.update(Measurements::PM10, ag->pms5003.getPm10Ae());
+ measurements.update(Measurements::PM01_SP, ag->pms5003.getPm01Sp());
+ measurements.update(Measurements::PM25_SP, ag->pms5003.getPm25Sp());
+ measurements.update(Measurements::PM10_SP, ag->pms5003.getPm10Sp());
+ measurements.update(Measurements::PM03_PC, ag->pms5003.getPm03ParticleCount());
+ measurements.update(Measurements::PM05_PC, ag->pms5003.getPm05ParticleCount());
+ measurements.update(Measurements::PM01_PC, ag->pms5003.getPm01ParticleCount());
+ measurements.update(Measurements::PM25_PC, ag->pms5003.getPm25ParticleCount());
+ measurements.update(Measurements::PM5_PC, ag->pms5003.getPm5ParticleCount());
+ measurements.update(Measurements::PM10_PC, ag->pms5003.getPm10ParticleCount());
+ } else {
+ measurements.update(Measurements::PM01, utils::getInvalidPmValue());
+ measurements.update(Measurements::PM25, utils::getInvalidPmValue());
+ measurements.update(Measurements::PM10, utils::getInvalidPmValue());
+ measurements.update(Measurements::PM01_SP, utils::getInvalidPmValue());
+ measurements.update(Measurements::PM25_SP, utils::getInvalidPmValue());
+ measurements.update(Measurements::PM10_SP, utils::getInvalidPmValue());
+ measurements.update(Measurements::PM03_PC, utils::getInvalidPmValue());
+ measurements.update(Measurements::PM05_PC, utils::getInvalidPmValue());
+ measurements.update(Measurements::PM01_PC, utils::getInvalidPmValue());
+ measurements.update(Measurements::PM25_PC, utils::getInvalidPmValue());
+ measurements.update(Measurements::PM5_PC, utils::getInvalidPmValue());
+ measurements.update(Measurements::PM10_PC, utils::getInvalidPmValue());
+ }
}
static void updatePm(void) {
- bool restart = false;
if (ag->isOne()) {
- if (ag->pms5003.connected()) {
- measurements.pm01_1 = ag->pms5003.getPm01Ae();
- measurements.pm25_1 = ag->pms5003.getPm25Ae();
- measurements.pm10_1 = ag->pms5003.getPm10Ae();
- measurements.pm03PCount_1 = ag->pms5003.getPm03ParticleCount();
+ updatePMS5003();
+ return;
+ }
- Serial.println();
- Serial.printf("PM1 ug/m3: %d\r\n", measurements.pm01_1);
- Serial.printf("PM2.5 ug/m3: %d\r\n", measurements.pm25_1);
- Serial.printf("PM10 ug/m3: %d\r\n", measurements.pm10_1);
- Serial.printf("PM0.3 Count: %d\r\n", measurements.pm03PCount_1);
- Serial.printf("PM firmware version: %d\r\n", ag->pms5003.getFirmwareVersion());
- ag->pms5003.resetFailCount();
+ // Open Air Monitor series, can have two PMS5003T sensor
+ bool newPMS1Value = false;
+ bool newPMS2Value = false;
+
+ // Read PMS channel 1 if available
+ int channel = 1;
+ if (configuration.hasSensorPMS1) {
+ if (ag->pms5003t_1.connected()) {
+ measurements.update(Measurements::PM01, ag->pms5003t_1.getPm01Ae(), channel);
+ measurements.update(Measurements::PM25, ag->pms5003t_1.getPm25Ae(), channel);
+ measurements.update(Measurements::PM10, ag->pms5003t_1.getPm10Ae(), channel);
+ measurements.update(Measurements::PM01_SP, ag->pms5003t_1.getPm01Sp(), channel);
+ measurements.update(Measurements::PM25_SP, ag->pms5003t_1.getPm25Sp(), channel);
+ measurements.update(Measurements::PM10_SP, ag->pms5003t_1.getPm10Sp(), channel);
+ measurements.update(Measurements::PM03_PC, ag->pms5003t_1.getPm03ParticleCount(), channel);
+ measurements.update(Measurements::PM05_PC, ag->pms5003t_1.getPm05ParticleCount(), channel);
+ measurements.update(Measurements::PM01_PC, ag->pms5003t_1.getPm01ParticleCount(), channel);
+ measurements.update(Measurements::PM25_PC, ag->pms5003t_1.getPm25ParticleCount(), channel);
+ measurements.update(Measurements::Temperature, ag->pms5003t_1.getTemperature(), channel);
+ measurements.update(Measurements::Humidity, ag->pms5003t_1.getRelativeHumidity(), channel);
+
+ // flag that new valid PMS value exists
+ newPMS1Value = true;
} else {
- ag->pms5003.updateFailCount();
- Serial.printf("PMS read failed %d times\r\n", ag->pms5003.getFailCount());
- if (ag->pms5003.getFailCount() >= PMS_FAIL_COUNT_SET_INVALID) {
- measurements.pm01_1 = utils::getInvalidPmValue();
- measurements.pm25_1 = utils::getInvalidPmValue();
- measurements.pm10_1 = utils::getInvalidPmValue();
- measurements.pm03PCount_1 = utils::getInvalidPmValue();
- }
-
- if (ag->pms5003.getFailCount() >= ag->pms5003.getFailCountMax()) {
- restart = true;
- }
- }
- } else {
- bool pmsResult_1 = false;
- bool pmsResult_2 = false;
- if (configuration.hasSensorPMS1 && ag->pms5003t_1.connected()) {
- measurements.pm01_1 = ag->pms5003t_1.getPm01Ae();
- measurements.pm25_1 = ag->pms5003t_1.getPm25Ae();
- measurements.pm10_1 = ag->pms5003t_1.getPm10Ae();
- measurements.pm03PCount_1 = ag->pms5003t_1.getPm03ParticleCount();
- measurements.temp_1 = ag->pms5003t_1.getTemperature();
- measurements.hum_1 = ag->pms5003t_1.getRelativeHumidity();
-
- pmsResult_1 = true;
-
- Serial.println();
- Serial.printf("[1] PM1 ug/m3: %d\r\n", measurements.pm01_1);
- Serial.printf("[1] PM2.5 ug/m3: %d\r\n", measurements.pm25_1);
- Serial.printf("[1] PM10 ug/m3: %d\r\n", measurements.pm10_1);
- Serial.printf("[1] PM3.0 Count: %d\r\n", measurements.pm03PCount_1);
- Serial.printf("[1] Temperature in C: %0.2f\r\n", measurements.temp_1);
- Serial.printf("[1] Relative Humidity: %d\r\n", measurements.hum_1);
- Serial.printf("[1] Temperature compensated in C: %0.2f\r\n",
- ag->pms5003t_1.compensateTemp(measurements.temp_1));
- Serial.printf("[1] Relative Humidity compensated: %0.2f\r\n",
- ag->pms5003t_1.compensateHum(measurements.hum_1));
- Serial.printf("[1] PM firmware version: %d\r\n", ag->pms5003t_1.getFirmwareVersion());
-
- ag->pms5003t_1.resetFailCount();
- } else {
- if (configuration.hasSensorPMS1) {
- ag->pms5003t_1.updateFailCount();
- Serial.printf("[1] PMS read failed %d times\r\n", ag->pms5003t_1.getFailCount());
-
- if (ag->pms5003t_1.getFailCount() >= PMS_FAIL_COUNT_SET_INVALID) {
- measurements.pm01_1 = utils::getInvalidPmValue();
- measurements.pm25_1 = utils::getInvalidPmValue();
- measurements.pm10_1 = utils::getInvalidPmValue();
- measurements.pm03PCount_1 = utils::getInvalidPmValue();
- measurements.temp_1 = utils::getInvalidTemperature();
- measurements.hum_1 = utils::getInvalidHumidity();
- }
-
- if (ag->pms5003t_1.getFailCount() >= ag->pms5003t_1.getFailCountMax()) {
- restart = true;
- }
- }
- }
-
- if (configuration.hasSensorPMS2 && ag->pms5003t_2.connected()) {
- measurements.pm01_2 = ag->pms5003t_2.getPm01Ae();
- measurements.pm25_2 = ag->pms5003t_2.getPm25Ae();
- measurements.pm10_2 = ag->pms5003t_2.getPm10Ae();
- measurements.pm03PCount_2 = ag->pms5003t_2.getPm03ParticleCount();
- measurements.temp_2 = ag->pms5003t_2.getTemperature();
- measurements.hum_2 = ag->pms5003t_2.getRelativeHumidity();
-
- pmsResult_2 = true;
-
- Serial.println();
- Serial.printf("[2] PM1 ug/m3: %d\r\n", measurements.pm01_2);
- Serial.printf("[2] PM2.5 ug/m3: %d\r\n", measurements.pm25_2);
- Serial.printf("[2] PM10 ug/m3: %d\r\n", measurements.pm10_2);
- Serial.printf("[2] PM3.0 Count: %d\r\n", measurements.pm03PCount_2);
- Serial.printf("[2] Temperature in C: %0.2f\r\n", measurements.temp_2);
- Serial.printf("[2] Relative Humidity: %d\r\n", measurements.hum_2);
- Serial.printf("[2] Temperature compensated in C: %0.2f\r\n",
- ag->pms5003t_1.compensateTemp(measurements.temp_2));
- Serial.printf("[2] Relative Humidity compensated: %0.2f\r\n",
- ag->pms5003t_1.compensateHum(measurements.hum_2));
- Serial.printf("[2] PM firmware version: %d\r\n", ag->pms5003t_2.getFirmwareVersion());
-
- ag->pms5003t_2.resetFailCount();
- } else {
- if (configuration.hasSensorPMS2) {
- ag->pms5003t_2.updateFailCount();
- Serial.printf("[2] PMS read failed %d times\r\n", ag->pms5003t_2.getFailCount());
-
- if (ag->pms5003t_2.getFailCount() >= PMS_FAIL_COUNT_SET_INVALID) {
- measurements.pm01_2 = utils::getInvalidPmValue();
- measurements.pm25_2 = utils::getInvalidPmValue();
- measurements.pm10_2 = utils::getInvalidPmValue();
- measurements.pm03PCount_2 = utils::getInvalidPmValue();
- measurements.temp_2 = utils::getInvalidTemperature();
- measurements.hum_2 = utils::getInvalidHumidity();
- }
-
- if (ag->pms5003t_2.getFailCount() >= ag->pms5003t_2.getFailCountMax()) {
- restart = true;
- }
- }
- }
-
- if (configuration.hasSensorPMS1 && configuration.hasSensorPMS2 &&
- pmsResult_1 && pmsResult_2) {
- /** Get total of PMS1*/
- measurements.pm1Value01 = measurements.pm1Value01 + measurements.pm01_1;
- measurements.pm1Value25 = measurements.pm1Value25 + measurements.pm25_1;
- measurements.pm1Value10 = measurements.pm1Value10 + measurements.pm10_1;
- measurements.pm1PCount =
- measurements.pm1PCount + measurements.pm03PCount_1;
- measurements.pm1temp = measurements.pm1temp + measurements.temp_1;
- measurements.pm1hum = measurements.pm1hum + measurements.hum_1;
-
- /** Get total of PMS2 */
- measurements.pm2Value01 = measurements.pm2Value01 + measurements.pm01_2;
- measurements.pm2Value25 = measurements.pm2Value25 + measurements.pm25_2;
- measurements.pm2Value10 = measurements.pm2Value10 + measurements.pm10_2;
- measurements.pm2PCount =
- measurements.pm2PCount + measurements.pm03PCount_2;
- measurements.pm2temp = measurements.pm2temp + measurements.temp_2;
- measurements.pm2hum = measurements.pm2hum + measurements.hum_2;
-
- measurements.countPosition++;
-
- /** Get average */
- if (measurements.countPosition == measurements.targetCount) {
- measurements.pm01_1 =
- measurements.pm1Value01 / measurements.targetCount;
- measurements.pm25_1 =
- measurements.pm1Value25 / measurements.targetCount;
- measurements.pm10_1 =
- measurements.pm1Value10 / measurements.targetCount;
- measurements.pm03PCount_1 =
- measurements.pm1PCount / measurements.targetCount;
- measurements.temp_1 = measurements.pm1temp / measurements.targetCount;
- measurements.hum_1 = measurements.pm1hum / measurements.targetCount;
-
- measurements.pm01_2 =
- measurements.pm2Value01 / measurements.targetCount;
- measurements.pm25_2 =
- measurements.pm2Value25 / measurements.targetCount;
- measurements.pm10_2 =
- measurements.pm2Value10 / measurements.targetCount;
- measurements.pm03PCount_2 =
- measurements.pm2PCount / measurements.targetCount;
- measurements.temp_2 = measurements.pm2temp / measurements.targetCount;
- measurements.hum_2 = measurements.pm2hum / measurements.targetCount;
-
- measurements.countPosition = 0;
-
- measurements.pm1Value01 = 0;
- measurements.pm1Value25 = 0;
- measurements.pm1Value10 = 0;
- measurements.pm1PCount = 0;
- measurements.pm1temp = 0;
- measurements.pm1hum = 0;
- measurements.pm2Value01 = 0;
- measurements.pm2Value25 = 0;
- measurements.pm2Value10 = 0;
- measurements.pm2PCount = 0;
- measurements.pm2temp = 0;
- measurements.pm2hum = 0;
- }
- }
-
- if (pmsResult_1 && pmsResult_2) {
- measurements.Temperature =
- (measurements.temp_1 + measurements.temp_2) / 2;
- measurements.Humidity = (measurements.hum_1 + measurements.hum_2) / 2;
- } else {
- if (pmsResult_1) {
- measurements.Temperature = measurements.temp_1;
- measurements.Humidity = measurements.hum_1;
- }
- if (pmsResult_2) {
- measurements.Temperature = measurements.temp_2;
- measurements.Humidity = measurements.hum_2;
- }
- }
-
- if (configuration.hasSensorSGP) {
- float temp;
- float hum;
- if (pmsResult_1 && pmsResult_2) {
- temp = (measurements.temp_1 + measurements.temp_2) / 2.0f;
- hum = (measurements.hum_1 + measurements.hum_2) / 2.0f;
- } else {
- if (pmsResult_1) {
- temp = measurements.temp_1;
- hum = measurements.hum_1;
- }
- if (pmsResult_2) {
- temp = measurements.temp_2;
- hum = measurements.hum_2;
- }
- }
- ag->sgp41.setCompensationTemperatureHumidity(temp, hum);
+ // PMS channel 1 now is not connected, update using invalid value
+ measurements.update(Measurements::PM01, utils::getInvalidPmValue(), channel);
+ measurements.update(Measurements::PM25, utils::getInvalidPmValue(), channel);
+ measurements.update(Measurements::PM10, utils::getInvalidPmValue(), channel);
+ measurements.update(Measurements::PM01_SP, utils::getInvalidPmValue(), channel);
+ measurements.update(Measurements::PM25_SP, utils::getInvalidPmValue(), channel);
+ measurements.update(Measurements::PM10_SP, utils::getInvalidPmValue(), channel);
+ measurements.update(Measurements::PM03_PC, utils::getInvalidPmValue(), channel);
+ measurements.update(Measurements::PM05_PC, utils::getInvalidPmValue(), channel);
+ measurements.update(Measurements::PM01_PC, utils::getInvalidPmValue(), channel);
+ measurements.update(Measurements::PM25_PC, utils::getInvalidPmValue(), channel);
+ measurements.update(Measurements::Temperature, utils::getInvalidTemperature(), channel);
+ measurements.update(Measurements::Humidity, utils::getInvalidHumidity(), channel);
}
}
- if (restart) {
- Serial.printf("PMS failure count reach to max set %d, restarting...", ag->pms5003.getFailCountMax());
- ESP.restart();
+ // Read PMS channel 2 if available
+ channel = 2;
+ if (configuration.hasSensorPMS2) {
+ if (ag->pms5003t_2.connected()) {
+ measurements.update(Measurements::PM01, ag->pms5003t_2.getPm01Ae(), channel);
+ measurements.update(Measurements::PM25, ag->pms5003t_2.getPm25Ae(), channel);
+ measurements.update(Measurements::PM10, ag->pms5003t_2.getPm10Ae(), channel);
+ measurements.update(Measurements::PM01_SP, ag->pms5003t_2.getPm01Sp(), channel);
+ measurements.update(Measurements::PM25_SP, ag->pms5003t_2.getPm25Sp(), channel);
+ measurements.update(Measurements::PM10_SP, ag->pms5003t_2.getPm10Sp(), channel);
+ measurements.update(Measurements::PM03_PC, ag->pms5003t_2.getPm03ParticleCount(), channel);
+ measurements.update(Measurements::PM05_PC, ag->pms5003t_2.getPm05ParticleCount(), channel);
+ measurements.update(Measurements::PM01_PC, ag->pms5003t_2.getPm01ParticleCount(), channel);
+ measurements.update(Measurements::PM25_PC, ag->pms5003t_2.getPm25ParticleCount(), channel);
+ measurements.update(Measurements::Temperature, ag->pms5003t_2.getTemperature(), channel);
+ measurements.update(Measurements::Humidity, ag->pms5003t_2.getRelativeHumidity(), channel);
+
+ // flag that new valid PMS value exists
+ newPMS2Value = true;
+ } else {
+ // PMS channel 2 now is not connected, update using invalid value
+ measurements.update(Measurements::PM01, utils::getInvalidPmValue(), channel);
+ measurements.update(Measurements::PM25, utils::getInvalidPmValue(), channel);
+ measurements.update(Measurements::PM10, utils::getInvalidPmValue(), channel);
+ measurements.update(Measurements::PM01_SP, utils::getInvalidPmValue(), channel);
+ measurements.update(Measurements::PM25_SP, utils::getInvalidPmValue(), channel);
+ measurements.update(Measurements::PM10_SP, utils::getInvalidPmValue(), channel);
+ measurements.update(Measurements::PM03_PC, utils::getInvalidPmValue(), channel);
+ measurements.update(Measurements::PM05_PC, utils::getInvalidPmValue(), channel);
+ measurements.update(Measurements::PM01_PC, utils::getInvalidPmValue(), channel);
+ measurements.update(Measurements::PM25_PC, utils::getInvalidPmValue(), channel);
+ measurements.update(Measurements::Temperature, utils::getInvalidTemperature(), channel);
+ measurements.update(Measurements::Humidity, utils::getInvalidHumidity(), channel);
+ }
+ }
+
+ if (configuration.hasSensorSGP) {
+ float temp, hum;
+ if (newPMS1Value && newPMS2Value) {
+ // Both PMS has new valid value
+ temp = (measurements.getFloat(Measurements::Temperature, 1) +
+ measurements.getFloat(Measurements::Temperature, 2)) /
+ 2.0f;
+ hum = (measurements.getFloat(Measurements::Humidity, 1) +
+ measurements.getFloat(Measurements::Humidity, 2)) /
+ 2.0f;
+ } else if (newPMS1Value) {
+ // Only PMS1 has new valid value
+ temp = measurements.getFloat(Measurements::Temperature, 1);
+ hum = measurements.getFloat(Measurements::Humidity, 1);
+ } else {
+ // Only PMS2 has new valid value
+ temp = measurements.getFloat(Measurements::Temperature, 2);
+ hum = measurements.getFloat(Measurements::Humidity, 2);
+ }
+
+ // Update compensation temperature and humidity for SGP41
+ ag->sgp41.setCompensationTemperatureHumidity(temp, hum);
}
}
static void sendDataToServer(void) {
+ /** Increment bootcount when send measurements data is scheduled */
+ measurements.bootCount++;
+
/** Ignore send data to server if postToAirGradient disabled */
if (configuration.isPostDataToAirGradient() == false || configuration.isOfflineMode()) {
return;
}
- String syncData = measurements.toString(false, fwMode, wifiConnector.RSSI(),
- ag, &configuration);
+ String syncData = measurements.toString(false, fwMode, wifiConnector.RSSI(), *ag, configuration);
if (apiClient.postToServer(syncData)) {
Serial.println();
Serial.println(
"Online mode and isPostToAirGradient = true: watchdog reset");
Serial.println();
}
- measurements.bootCount++;
}
static void tempHumUpdate(void) {
delay(100);
if (ag->sht.measure()) {
- measurements.Temperature = ag->sht.getTemperature();
- measurements.Humidity = ag->sht.getRelativeHumidity();
+ float temp = ag->sht.getTemperature();
+ float rhum = ag->sht.getRelativeHumidity();
- Serial.printf("Temperature in C: %0.2f\r\n", measurements.Temperature);
- Serial.printf("Relative Humidity: %d\r\n", measurements.Humidity);
- Serial.printf("Temperature compensated in C: %0.2f\r\n",
- measurements.Temperature);
- Serial.printf("Relative Humidity compensated: %d\r\n",
- measurements.Humidity);
+ measurements.update(Measurements::Temperature, temp);
+ measurements.update(Measurements::Humidity, rhum);
// Update compensation temperature and humidity for SGP41
if (configuration.hasSensorSGP) {
- ag->sgp41.setCompensationTemperatureHumidity(measurements.Temperature,
- measurements.Humidity);
+ ag->sgp41.setCompensationTemperatureHumidity(temp, rhum);
}
} else {
- measurements.Temperature = utils::getInvalidTemperature();
- measurements.Humidity = utils::getInvalidHumidity();
+ measurements.update(Measurements::Temperature, utils::getInvalidTemperature());
+ measurements.update(Measurements::Humidity, utils::getInvalidHumidity());
Serial.println("SHT read failed");
}
-}
\ No newline at end of file
+}
+
+/* Set max period for each measurement type based on sensor update interval*/
+void setMeasurementMaxPeriod() {
+ int max;
+
+ /// Max period for S8 sensors measurements
+ measurements.maxPeriod(Measurements::CO2, calculateMaxPeriod(SENSOR_CO2_UPDATE_INTERVAL));
+
+ /// Max period for SGP sensors measurements
+ max = calculateMaxPeriod(SENSOR_TVOC_UPDATE_INTERVAL);
+ measurements.maxPeriod(Measurements::TVOC, max);
+ measurements.maxPeriod(Measurements::TVOCRaw, max);
+ measurements.maxPeriod(Measurements::NOx, max);
+ measurements.maxPeriod(Measurements::NOxRaw, max);
+
+ /// Max period for PMS sensors measurements
+ max = calculateMaxPeriod(SENSOR_PM_UPDATE_INTERVAL);
+ measurements.maxPeriod(Measurements::PM25, max);
+ measurements.maxPeriod(Measurements::PM01, max);
+ measurements.maxPeriod(Measurements::PM10, max);
+ measurements.maxPeriod(Measurements::PM25_SP, max);
+ measurements.maxPeriod(Measurements::PM01_SP, max);
+ measurements.maxPeriod(Measurements::PM10_SP, max);
+ measurements.maxPeriod(Measurements::PM03_PC, max);
+ measurements.maxPeriod(Measurements::PM05_PC, max);
+ measurements.maxPeriod(Measurements::PM01_PC, max);
+ measurements.maxPeriod(Measurements::PM25_PC, max);
+ measurements.maxPeriod(Measurements::PM5_PC, max);
+ measurements.maxPeriod(Measurements::PM10_PC, max);
+
+ // Temperature and Humidity
+ if (configuration.hasSensorSHT) {
+ /// Max period for SHT sensors measurements
+ measurements.maxPeriod(Measurements::Temperature,
+ calculateMaxPeriod(SENSOR_TEMP_HUM_UPDATE_INTERVAL));
+ measurements.maxPeriod(Measurements::Humidity,
+ calculateMaxPeriod(SENSOR_TEMP_HUM_UPDATE_INTERVAL));
+ } else {
+ /// Temp and hum data retrieved from PMS5003T sensor
+ measurements.maxPeriod(Measurements::Temperature,
+ calculateMaxPeriod(SENSOR_PM_UPDATE_INTERVAL));
+ measurements.maxPeriod(Measurements::Humidity, calculateMaxPeriod(SENSOR_PM_UPDATE_INTERVAL));
+ }
+}
+
+int calculateMaxPeriod(int updateInterval) {
+ // 0.5 is 50% reduced interval for max period
+ return (SERVER_SYNC_INTERVAL - (SERVER_SYNC_INTERVAL * 0.5)) / updateInterval;
+}
diff --git a/examples/OneOpenAir/OpenMetrics.cpp b/examples/OneOpenAir/OpenMetrics.cpp
index a7dfe70..23b2dd0 100644
--- a/examples/OneOpenAir/OpenMetrics.cpp
+++ b/examples/OneOpenAir/OpenMetrics.cpp
@@ -74,41 +74,46 @@ String OpenMetrics::getPayload(void) {
int atmpCompensated = utils::getInvalidTemperature();
int ahumCompensated = utils::getInvalidHumidity();
if (config.hasSensorPMS1 && config.hasSensorPMS2) {
- _temp = (measure.temp_1 + measure.temp_2) / 2.0f;
- _hum = (measure.hum_1 + measure.hum_2) / 2.0f;
- pm01 = (measure.pm01_1 + measure.pm01_2) / 2;
- pm25 = (measure.pm25_1 + measure.pm25_2) / 2;
- pm10 = (measure.pm10_1 + measure.pm10_2) / 2;
- pm03PCount = (measure.pm03PCount_1 + measure.pm03PCount_2) / 2;
+ _temp = (measure.getFloat(Measurements::Temperature, 1) +
+ measure.getFloat(Measurements::Temperature, 2)) /
+ 2.0f;
+ _hum = (measure.getFloat(Measurements::Humidity, 1) +
+ measure.getFloat(Measurements::Humidity, 2)) /
+ 2.0f;
+ pm01 = (measure.get(Measurements::PM01, 1) + measure.get(Measurements::PM01, 2)) / 2.0f;
+ pm25 = (measure.get(Measurements::PM25, 1) + measure.get(Measurements::PM25, 2)) / 2.0f;
+ pm10 = (measure.get(Measurements::PM10, 1) + measure.get(Measurements::PM10, 2)) / 2.0f;
+ pm03PCount =
+ (measure.get(Measurements::PM03_PC, 1) + measure.get(Measurements::PM03_PC, 2)) / 2.0f;
} else {
if (ag->isOne()) {
if (config.hasSensorSHT) {
- _temp = measure.Temperature;
- _hum = measure.Humidity;
+ _temp = measure.getFloat(Measurements::Temperature);
+ _hum = measure.getFloat(Measurements::Humidity);
}
if (config.hasSensorPMS1) {
- pm01 = measure.pm01_1;
- pm25 = measure.pm25_1;
- pm10 = measure.pm10_1;
- pm03PCount = measure.pm03PCount_1;
+ pm01 = measure.get(Measurements::PM01);
+ pm25 = measure.get(Measurements::PM25);
+ pm10 = measure.get(Measurements::PM10);
+ pm03PCount = measure.get(Measurements::PM03_PC);
}
} else {
if (config.hasSensorPMS1) {
- _temp = measure.temp_1;
- _hum = measure.hum_1;
- pm01 = measure.pm01_1;
- pm25 = measure.pm25_1;
- pm10 = measure.pm10_1;
- pm03PCount = measure.pm03PCount_1;
+ _temp = measure.getFloat(Measurements::Temperature, 1);
+ _hum = measure.getFloat(Measurements::Humidity, 1);
+ pm01 = measure.get(Measurements::PM01, 1);
+ pm25 = measure.get(Measurements::PM25, 1);
+ pm10 = measure.get(Measurements::PM10, 1);
+ pm03PCount = measure.get(Measurements::PM03_PC, 1);
}
if (config.hasSensorPMS2) {
- _temp = measure.temp_2;
- _hum = measure.hum_2;
- pm01 = measure.pm01_2;
- pm25 = measure.pm25_2;
- pm10 = measure.pm10_2;
- pm03PCount = measure.pm03PCount_2;
+ _temp = measure.getFloat(Measurements::Temperature, 2);
+ _hum = measure.getFloat(Measurements::Humidity, 2);
+ pm01 = measure.get(Measurements::PM01, 2);
+ pm25 = measure.get(Measurements::PM25, 2);
+ pm10 = measure.get(Measurements::PM10, 2);
+ pm03PCount = measure.get(Measurements::PM03_PC, 2);
}
}
}
diff --git a/src/AgApiClient.cpp b/src/AgApiClient.cpp
index 5933c30..8a72a67 100644
--- a/src/AgApiClient.cpp
+++ b/src/AgApiClient.cpp
@@ -130,7 +130,7 @@ bool AgApiClient::postToServer(String data) {
client.end();
logInfo(String("POST: ") + uri);
- logInfo(String("DATA: ") + data);
+ // logInfo(String("DATA: ") + data);
logInfo(String("Return code: ") + String(retCode));
if ((retCode == 200) || (retCode == 429)) {
diff --git a/src/AgConfigure.cpp b/src/AgConfigure.cpp
index 3f3005f..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)
*
@@ -162,7 +280,7 @@ void Configuration::defaultConfig(void) {
jconfig[jprop_displayBrightness] = jprop_displayBrightness_default;
}
if (ag->isOne()) {
- jconfig[jprop_ledBarMode] = jprop_ledBarBrightness_default;
+ jconfig[jprop_ledBarMode] = jprop_ledBarMode_default;
}
jconfig[jprop_tvocLearningOffset] = jprop_tvocLearningOffset_default;
jconfig[jprop_noxLearningOffset] = jprop_noxLearningOffset_default;
@@ -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 050782a..1dfbbba 100644
--- a/src/AgOledDisplay.cpp
+++ b/src/AgOledDisplay.cpp
@@ -12,12 +12,13 @@
*/
void OledDisplay::showTempHum(bool hasStatus, char *buf, int buf_size) {
/** Temperature */
- if (utils::isValidTemperature(value.Temperature)) {
+ float temp = value.getFloat(Measurements::Temperature);
+ if (utils::isValidTemperature(temp)) {
float t = 0.0f;
if (config.isTemperatureUnitInF()) {
- t = utils::degreeC_To_F(value.Temperature);
+ t = utils::degreeC_To_F(temp);
} else {
- t = value.Temperature;
+ t = temp;
}
if (config.isTemperatureUnitInF()) {
@@ -43,13 +44,14 @@ void OledDisplay::showTempHum(bool hasStatus, char *buf, int buf_size) {
DISP()->drawUTF8(1, 10, buf);
/** Show humidity */
- if (utils::isValidHumidity(value.Humidity)) {
- snprintf(buf, buf_size, "%d%%", value.Humidity);
+ int rhum = (int)value.getFloat(Measurements::Humidity);
+ if (utils::isValidHumidity(rhum)) {
+ snprintf(buf, buf_size, "%d%%", rhum);
} else {
snprintf(buf, buf_size, "-%%");
}
- if (value.Humidity > 99) {
+ if (rhum > 99.0) {
DISP()->drawStr(97, 10, buf);
} else {
DISP()->drawStr(105, 10, buf);
@@ -290,8 +292,9 @@ void OledDisplay::showDashboard(const char *status) {
DISP()->drawUTF8(1, 27, "CO2");
DISP()->setFont(u8g2_font_t0_22b_tf);
- if (utils::isValidCO2(value.CO2)) {
- sprintf(strBuf, "%d", value.CO2);
+ int co2 = value.get(Measurements::CO2);
+ if (utils::isValidCO2(co2)) {
+ sprintf(strBuf, "%d", co2);
} else {
sprintf(strBuf, "%s", "-");
}
@@ -310,15 +313,12 @@ void OledDisplay::showDashboard(const char *status) {
DISP()->drawStr(55, 27, "PM2.5");
/** Draw PM2.5 value */
- if (utils::isValidPm(value.pm25_1)) {
- int pm25 = value.pm25_1;
- /** Compensate PM2.5 value. */
- if (config.hasSensorSHT && config.isMonitorDisplayCompensatedValues()) {
- pm25 = ag->pms5003.compensate(pm25, value.Humidity);
- logInfo("PM2.5 compensate: " + String(pm25));
+ int pm25 = value.get(Measurements::PM25);
+ if (utils::isValidPm(pm25)) {
+ if (config.hasSensorSHT && config.isPMCorrectionEnabled()) {
+ pm25 = (int)value.getCorrectedPM25(*ag, config);
}
-
if (config.isPmStandardInUSAQI()) {
sprintf(strBuf, "%d", ag->pms5003.convertPm25ToUsAqi(pm25));
} else {
@@ -343,17 +343,19 @@ void OledDisplay::showDashboard(const char *status) {
DISP()->drawStr(100, 27, "VOC:");
/** Draw tvocIndexvalue */
- if (utils::isValidVOC(value.TVOC)) {
- sprintf(strBuf, "%d", value.TVOC);
+ int tvoc = value.get(Measurements::TVOC);
+ if (utils::isValidVOC(tvoc)) {
+ sprintf(strBuf, "%d", tvoc);
} else {
sprintf(strBuf, "%s", "-");
}
DISP()->drawStr(100, 39, strBuf);
/** Draw NOx label */
+ int nox = value.get(Measurements::NOx);
DISP()->drawStr(100, 53, "NOx:");
- if (utils::isValidNOx(value.NOx)) {
- sprintf(strBuf, "%d", value.NOx);
+ if (utils::isValidNOx(nox)) {
+ sprintf(strBuf, "%d", nox);
} else {
sprintf(strBuf, "%s", "-");
}
@@ -363,8 +365,9 @@ void OledDisplay::showDashboard(const char *status) {
ag->display.clear();
/** Set CO2 */
- if (utils::isValidCO2(value.CO2)) {
- snprintf(strBuf, sizeof(strBuf), "CO2:%d", value.CO2);
+ int co2 = value.get(Measurements::CO2);
+ if (utils::isValidCO2(co2)) {
+ snprintf(strBuf, sizeof(strBuf), "CO2:%d", co2);
} else {
snprintf(strBuf, sizeof(strBuf), "CO2:-");
}
@@ -373,9 +376,9 @@ void OledDisplay::showDashboard(const char *status) {
ag->display.setText(strBuf);
/** Set PM */
- int pm25 = value.pm25_1;
- if (config.hasSensorSHT && config.isMonitorDisplayCompensatedValues()) {
- pm25 = (int)ag->pms5003.compensate(pm25, value.Humidity);
+ int pm25 = value.get(Measurements::PM25);
+ if (config.hasSensorSHT && config.isPMCorrectionEnabled()) {
+ pm25 = (int)value.getCorrectedPM25(*ag, config);
}
ag->display.setCursor(0, 12);
@@ -387,12 +390,12 @@ void OledDisplay::showDashboard(const char *status) {
ag->display.setText(strBuf);
/** Set temperature and humidity */
- if (utils::isValidTemperature(value.Temperature)) {
+ float temp = value.getFloat(Measurements::Temperature);
+ if (utils::isValidTemperature(temp)) {
if (config.isTemperatureUnitInF()) {
- snprintf(strBuf, sizeof(strBuf), "T:%0.1f F",
- utils::degreeC_To_F(value.Temperature));
+ snprintf(strBuf, sizeof(strBuf), "T:%0.1f F", utils::degreeC_To_F(temp));
} else {
- snprintf(strBuf, sizeof(strBuf), "T:%0.f1 C", value.Temperature);
+ snprintf(strBuf, sizeof(strBuf), "T:%0.f1 C", temp);
}
} else {
if (config.isTemperatureUnitInF()) {
@@ -405,8 +408,9 @@ void OledDisplay::showDashboard(const char *status) {
ag->display.setCursor(0, 24);
ag->display.setText(strBuf);
- if (utils::isValidHumidity(value.Humidity)) {
- snprintf(strBuf, sizeof(strBuf), "H:%d %%", (int)value.Humidity);
+ int rhum = (int)value.getFloat(Measurements::Humidity);
+ if (utils::isValidHumidity(rhum)) {
+ snprintf(strBuf, sizeof(strBuf), "H:%d %%", rhum);
} else {
snprintf(strBuf, sizeof(strBuf), "H:- %%");
}
diff --git a/src/AgStateMachine.cpp b/src/AgStateMachine.cpp
index 85f87d5..e36446f 100644
--- a/src/AgStateMachine.cpp
+++ b/src/AgStateMachine.cpp
@@ -69,33 +69,33 @@ void StateMachine::sensorhandleLeds(void) {
*
*/
void StateMachine::co2handleLeds(void) {
- int co2Value = value.CO2;
- if (co2Value <= 600) {
+ int co2Value = value.get(Measurements::CO2);
+ if (co2Value <= 700) {
/** G; 1 */
ag->ledBar.setColor(RGB_COLOR_G, ag->ledBar.getNumberOfLeds() - 1);
- } else if (co2Value <= 800) {
+ } else if (co2Value <= 1000) {
/** GG; 2 */
ag->ledBar.setColor(RGB_COLOR_G, ag->ledBar.getNumberOfLeds() - 1);
ag->ledBar.setColor(RGB_COLOR_G, ag->ledBar.getNumberOfLeds() - 2);
- } else if (co2Value <= 1000) {
+ } else if (co2Value <= 1333) {
/** YYY; 3 */
ag->ledBar.setColor(RGB_COLOR_Y, ag->ledBar.getNumberOfLeds() - 1);
ag->ledBar.setColor(RGB_COLOR_Y, ag->ledBar.getNumberOfLeds() - 2);
ag->ledBar.setColor(RGB_COLOR_Y, ag->ledBar.getNumberOfLeds() - 3);
- } else if (co2Value <= 1250) {
+ } else if (co2Value <= 1666) {
/** OOOO; 4 */
- ag->ledBar.setColor(RGB_COLOR_O, ag->ledBar.getNumberOfLeds() - 1);
- ag->ledBar.setColor(RGB_COLOR_O, ag->ledBar.getNumberOfLeds() - 2);
- ag->ledBar.setColor(RGB_COLOR_O, ag->ledBar.getNumberOfLeds() - 3);
- ag->ledBar.setColor(RGB_COLOR_O, ag->ledBar.getNumberOfLeds() - 4);
- } else if (co2Value <= 1500) {
+ ag->ledBar.setColor(RGB_COLOR_Y, ag->ledBar.getNumberOfLeds() - 1);
+ ag->ledBar.setColor(RGB_COLOR_Y, ag->ledBar.getNumberOfLeds() - 2);
+ ag->ledBar.setColor(RGB_COLOR_Y, ag->ledBar.getNumberOfLeds() - 3);
+ ag->ledBar.setColor(RGB_COLOR_Y, ag->ledBar.getNumberOfLeds() - 4);
+ } else if (co2Value <= 2000) {
/** OOOOO; 5 */
- ag->ledBar.setColor(RGB_COLOR_O, ag->ledBar.getNumberOfLeds() - 1);
- ag->ledBar.setColor(RGB_COLOR_O, ag->ledBar.getNumberOfLeds() - 2);
- ag->ledBar.setColor(RGB_COLOR_O, ag->ledBar.getNumberOfLeds() - 3);
- ag->ledBar.setColor(RGB_COLOR_O, ag->ledBar.getNumberOfLeds() - 4);
- ag->ledBar.setColor(RGB_COLOR_O, ag->ledBar.getNumberOfLeds() - 5);
- } else if (co2Value <= 1750) {
+ ag->ledBar.setColor(RGB_COLOR_Y, ag->ledBar.getNumberOfLeds() - 1);
+ ag->ledBar.setColor(RGB_COLOR_Y, ag->ledBar.getNumberOfLeds() - 2);
+ ag->ledBar.setColor(RGB_COLOR_Y, ag->ledBar.getNumberOfLeds() - 3);
+ ag->ledBar.setColor(RGB_COLOR_Y, ag->ledBar.getNumberOfLeds() - 4);
+ ag->ledBar.setColor(RGB_COLOR_Y, ag->ledBar.getNumberOfLeds() - 5);
+ } else if (co2Value <= 2666) {
/** RRRRRR; 6 */
ag->ledBar.setColor(RGB_COLOR_R, ag->ledBar.getNumberOfLeds() - 1);
ag->ledBar.setColor(RGB_COLOR_R, ag->ledBar.getNumberOfLeds() - 2);
@@ -103,7 +103,7 @@ void StateMachine::co2handleLeds(void) {
ag->ledBar.setColor(RGB_COLOR_R, ag->ledBar.getNumberOfLeds() - 4);
ag->ledBar.setColor(RGB_COLOR_R, ag->ledBar.getNumberOfLeds() - 5);
ag->ledBar.setColor(RGB_COLOR_R, ag->ledBar.getNumberOfLeds() - 6);
- } else if (co2Value <= 2000) {
+ } else if (co2Value <= 3333) {
/** RRRRRRR; 7 */
ag->ledBar.setColor(RGB_COLOR_R, ag->ledBar.getNumberOfLeds() - 1);
ag->ledBar.setColor(RGB_COLOR_R, ag->ledBar.getNumberOfLeds() - 2);
@@ -112,17 +112,17 @@ void StateMachine::co2handleLeds(void) {
ag->ledBar.setColor(RGB_COLOR_R, ag->ledBar.getNumberOfLeds() - 5);
ag->ledBar.setColor(RGB_COLOR_R, ag->ledBar.getNumberOfLeds() - 6);
ag->ledBar.setColor(RGB_COLOR_R, ag->ledBar.getNumberOfLeds() - 7);
- } else if (co2Value <= 3000) {
- /** PPPPPPPP; 8 */
- ag->ledBar.setColor(RGB_COLOR_P, ag->ledBar.getNumberOfLeds() - 1);
- ag->ledBar.setColor(RGB_COLOR_P, ag->ledBar.getNumberOfLeds() - 2);
- ag->ledBar.setColor(RGB_COLOR_P, ag->ledBar.getNumberOfLeds() - 3);
- ag->ledBar.setColor(RGB_COLOR_P, ag->ledBar.getNumberOfLeds() - 4);
- ag->ledBar.setColor(RGB_COLOR_P, ag->ledBar.getNumberOfLeds() - 5);
- ag->ledBar.setColor(RGB_COLOR_P, ag->ledBar.getNumberOfLeds() - 6);
- ag->ledBar.setColor(RGB_COLOR_P, ag->ledBar.getNumberOfLeds() - 7);
- ag->ledBar.setColor(RGB_COLOR_P, ag->ledBar.getNumberOfLeds() - 8);
- } else { /** > 3000 */
+ } else if (co2Value <= 4000) {
+ /** RRRRRRRR; 8 */
+ ag->ledBar.setColor(RGB_COLOR_R, ag->ledBar.getNumberOfLeds() - 1);
+ ag->ledBar.setColor(RGB_COLOR_R, ag->ledBar.getNumberOfLeds() - 2);
+ ag->ledBar.setColor(RGB_COLOR_R, ag->ledBar.getNumberOfLeds() - 3);
+ ag->ledBar.setColor(RGB_COLOR_R, ag->ledBar.getNumberOfLeds() - 4);
+ ag->ledBar.setColor(RGB_COLOR_R, ag->ledBar.getNumberOfLeds() - 5);
+ ag->ledBar.setColor(RGB_COLOR_R, ag->ledBar.getNumberOfLeds() - 6);
+ ag->ledBar.setColor(RGB_COLOR_R, ag->ledBar.getNumberOfLeds() - 7);
+ ag->ledBar.setColor(RGB_COLOR_R, ag->ledBar.getNumberOfLeds() - 8);
+ } else { /** > 4000 */
/* PRPRPRPRP; 9 */
ag->ledBar.setColor(RGB_COLOR_P, ag->ledBar.getNumberOfLeds() - 1);
ag->ledBar.setColor(RGB_COLOR_R, ag->ledBar.getNumberOfLeds() - 2);
@@ -141,37 +141,37 @@ void StateMachine::co2handleLeds(void) {
*
*/
void StateMachine::pm25handleLeds(void) {
- int pm25Value = value.pm25_1;
- if (config.isMonitorDisplayCompensatedValues() && config.hasSensorSHT) {
- pm25Value = ag->pms5003.compensate(value.pm25_1, value.Humidity);
+ int pm25Value = value.get(Measurements::PM25);
+ if (config.hasSensorSHT && config.isPMCorrectionEnabled()) {
+ pm25Value = (int)value.getCorrectedPM25(*ag, config);
}
- if (pm25Value < 5) {
+ if (pm25Value <= 5) {
/** G; 1 */
ag->ledBar.setColor(RGB_COLOR_G, ag->ledBar.getNumberOfLeds() - 1);
- } else if (pm25Value < 10) {
+ } else if (pm25Value <= 9) {
/** GG; 2 */
ag->ledBar.setColor(RGB_COLOR_G, ag->ledBar.getNumberOfLeds() - 1);
ag->ledBar.setColor(RGB_COLOR_G, ag->ledBar.getNumberOfLeds() - 2);
- } else if (pm25Value < 20) {
+ } else if (pm25Value <= 20) {
/** YYY; 3 */
ag->ledBar.setColor(RGB_COLOR_Y, ag->ledBar.getNumberOfLeds() - 1);
ag->ledBar.setColor(RGB_COLOR_Y, ag->ledBar.getNumberOfLeds() - 2);
ag->ledBar.setColor(RGB_COLOR_Y, ag->ledBar.getNumberOfLeds() - 3);
- } else if (pm25Value < 35) {
+ } else if (pm25Value <= 35) {
/** YYYY; 4 */
ag->ledBar.setColor(RGB_COLOR_Y, ag->ledBar.getNumberOfLeds() - 1);
ag->ledBar.setColor(RGB_COLOR_Y, ag->ledBar.getNumberOfLeds() - 2);
ag->ledBar.setColor(RGB_COLOR_Y, ag->ledBar.getNumberOfLeds() - 3);
ag->ledBar.setColor(RGB_COLOR_Y, ag->ledBar.getNumberOfLeds() - 4);
- } else if (pm25Value < 45) {
+ } else if (pm25Value <= 45) {
/** OOOOO; 5 */
ag->ledBar.setColor(RGB_COLOR_O, ag->ledBar.getNumberOfLeds() - 1);
ag->ledBar.setColor(RGB_COLOR_O, ag->ledBar.getNumberOfLeds() - 2);
ag->ledBar.setColor(RGB_COLOR_O, ag->ledBar.getNumberOfLeds() - 3);
ag->ledBar.setColor(RGB_COLOR_O, ag->ledBar.getNumberOfLeds() - 4);
ag->ledBar.setColor(RGB_COLOR_O, ag->ledBar.getNumberOfLeds() - 5);
- } else if (pm25Value < 55) {
+ } else if (pm25Value <= 55) {
/** OOOOOO; 6 */
ag->ledBar.setColor(RGB_COLOR_O, ag->ledBar.getNumberOfLeds() - 1);
ag->ledBar.setColor(RGB_COLOR_O, ag->ledBar.getNumberOfLeds() - 2);
@@ -179,7 +179,7 @@ void StateMachine::pm25handleLeds(void) {
ag->ledBar.setColor(RGB_COLOR_O, ag->ledBar.getNumberOfLeds() - 4);
ag->ledBar.setColor(RGB_COLOR_O, ag->ledBar.getNumberOfLeds() - 5);
ag->ledBar.setColor(RGB_COLOR_O, ag->ledBar.getNumberOfLeds() - 6);
- } else if (pm25Value < 100) {
+ } else if (pm25Value <= 100) {
/** RRRRRRR; 7 */
ag->ledBar.setColor(RGB_COLOR_R, ag->ledBar.getNumberOfLeds() - 1);
ag->ledBar.setColor(RGB_COLOR_R, ag->ledBar.getNumberOfLeds() - 2);
@@ -188,7 +188,7 @@ void StateMachine::pm25handleLeds(void) {
ag->ledBar.setColor(RGB_COLOR_R, ag->ledBar.getNumberOfLeds() - 5);
ag->ledBar.setColor(RGB_COLOR_R, ag->ledBar.getNumberOfLeds() - 6);
ag->ledBar.setColor(RGB_COLOR_R, ag->ledBar.getNumberOfLeds() - 7);
- } else if (pm25Value < 200) {
+ } else if (pm25Value <= 125) {
/** RRRRRRRR; 8 */
ag->ledBar.setColor(RGB_COLOR_R, ag->ledBar.getNumberOfLeds() - 1);
ag->ledBar.setColor(RGB_COLOR_R, ag->ledBar.getNumberOfLeds() - 2);
@@ -198,7 +198,7 @@ void StateMachine::pm25handleLeds(void) {
ag->ledBar.setColor(RGB_COLOR_R, ag->ledBar.getNumberOfLeds() - 6);
ag->ledBar.setColor(RGB_COLOR_R, ag->ledBar.getNumberOfLeds() - 7);
ag->ledBar.setColor(RGB_COLOR_R, ag->ledBar.getNumberOfLeds() - 8);
- } else if (pm25Value < 250) {
+ } else if (pm25Value <= 225) {
/** PPPPPPPPP; 9 */
ag->ledBar.setColor(RGB_COLOR_P, ag->ledBar.getNumberOfLeds() - 1);
ag->ledBar.setColor(RGB_COLOR_P, ag->ledBar.getNumberOfLeds() - 2);
@@ -209,7 +209,7 @@ void StateMachine::pm25handleLeds(void) {
ag->ledBar.setColor(RGB_COLOR_P, ag->ledBar.getNumberOfLeds() - 7);
ag->ledBar.setColor(RGB_COLOR_P, ag->ledBar.getNumberOfLeds() - 8);
ag->ledBar.setColor(RGB_COLOR_P, ag->ledBar.getNumberOfLeds() - 9);
- } else { /** > 250 */
+ } else { /** > 225 */
/* PRPRPRPRP; 9 */
ag->ledBar.setColor(RGB_COLOR_P, ag->ledBar.getNumberOfLeds() - 1);
ag->ledBar.setColor(RGB_COLOR_R, ag->ledBar.getNumberOfLeds() - 2);
diff --git a/src/AgValue.cpp b/src/AgValue.cpp
index 867ee42..ca148ca 100644
--- a/src/AgValue.cpp
+++ b/src/AgValue.cpp
@@ -1,414 +1,1013 @@
#include "AgValue.h"
#include "AgConfigure.h"
#include "AirGradient.h"
-#include "Main/utils.h"
-#include "Libraries/Arduino_JSON/src/Arduino_JSON.h"
+#include "App/AppDef.h"
#define json_prop_pmFirmware "firmware"
+#define json_prop_pm01Ae "pm01"
+#define json_prop_pm25Ae "pm02"
+#define json_prop_pm10Ae "pm10"
+#define json_prop_pm01Sp "pm01Standard"
+#define json_prop_pm25Sp "pm02Standard"
+#define json_prop_pm10Sp "pm10Standard"
+#define json_prop_pm25Compensated "pm02Compensated"
+#define json_prop_pm03Count "pm003Count"
+#define json_prop_pm05Count "pm005Count"
+#define json_prop_pm1Count "pm01Count"
+#define json_prop_pm25Count "pm02Count"
+#define json_prop_pm5Count "pm50Count"
+#define json_prop_pm10Count "pm10Count"
+#define json_prop_temp "atmp"
+#define json_prop_tempCompensated "atmpCompensated"
+#define json_prop_rhum "rhum"
+#define json_prop_rhumCompensated "rhumCompensated"
+#define json_prop_tvoc "tvocIndex"
+#define json_prop_tvocRaw "tvocRaw"
+#define json_prop_nox "noxIndex"
+#define json_prop_noxRaw "noxRaw"
+#define json_prop_co2 "rco2"
+
+void Measurements::maxPeriod(MeasurementType type, int max) {
+ switch (type) {
+ case Temperature:
+ _temperature[0].update.max = max;
+ _temperature[1].update.max = max;
+ break;
+ case Humidity:
+ _humidity[0].update.max = max;
+ _humidity[1].update.max = max;
+ break;
+ case CO2:
+ _co2.update.max = max;
+ break;
+ case TVOC:
+ _tvoc.update.max = max;
+ break;
+ case TVOCRaw:
+ _tvoc_raw.update.max = max;
+ break;
+ case NOx:
+ _nox.update.max = max;
+ break;
+ case NOxRaw:
+ _nox_raw.update.max = max;
+ break;
+ case PM25:
+ _pm_25[0].update.max = max;
+ _pm_25[1].update.max = max;
+ break;
+ case PM01:
+ _pm_01[0].update.max = max;
+ _pm_01[1].update.max = max;
+ break;
+ case PM10:
+ _pm_10[0].update.max = max;
+ _pm_10[1].update.max = max;
+ break;
+ case PM01_SP:
+ _pm_01_sp[0].update.max = max;
+ _pm_01_sp[1].update.max = max;
+ break;
+ case PM25_SP:
+ _pm_25_sp[0].update.max = max;
+ _pm_25_sp[1].update.max = max;
+ break;
+ case PM10_SP:
+ _pm_10_sp[0].update.max = max;
+ _pm_10_sp[1].update.max = max;
+ break;
+ case PM03_PC:
+ _pm_03_pc[0].update.max = max;
+ _pm_03_pc[1].update.max = max;
+ break;
+ case PM05_PC:
+ _pm_05_pc[0].update.max = max;
+ _pm_05_pc[1].update.max = max;
+ break;
+ case PM01_PC:
+ _pm_01_pc[0].update.max = max;
+ _pm_01_pc[1].update.max = max;
+ break;
+ case PM25_PC:
+ _pm_25_pc[0].update.max = max;
+ _pm_25_pc[1].update.max = max;
+ break;
+ case PM5_PC:
+ _pm_5_pc[0].update.max = max;
+ _pm_5_pc[1].update.max = max;
+ break;
+ case PM10_PC:
+ _pm_10_pc[0].update.max = max;
+ _pm_10_pc[1].update.max = max;
+ break;
+ };
+}
+
+bool Measurements::update(MeasurementType type, int val, 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
+ IntegerValue *temporary = nullptr;
+ // Act as reference invalid value respective to target measurements
+ int invalidValue = 0;
+ switch (type) {
+ case CO2:
+ temporary = &_co2;
+ invalidValue = utils::getInvalidCO2();
+ break;
+ case TVOC:
+ temporary = &_tvoc;
+ invalidValue = utils::getInvalidVOC();
+ break;
+ case TVOCRaw:
+ temporary = &_tvoc_raw;
+ invalidValue = utils::getInvalidVOC();
+ break;
+ case NOx:
+ temporary = &_nox;
+ invalidValue = utils::getInvalidNOx();
+ break;
+ case NOxRaw:
+ temporary = &_nox_raw;
+ invalidValue = utils::getInvalidNOx();
+ break;
+ case PM25:
+ temporary = &_pm_25[ch];
+ invalidValue = utils::getInvalidPmValue();
+ break;
+ case PM01:
+ temporary = &_pm_01[ch];
+ invalidValue = utils::getInvalidPmValue();
+ break;
+ case PM10:
+ temporary = &_pm_10[ch];
+ invalidValue = utils::getInvalidPmValue();
+ break;
+ case PM01_SP:
+ temporary = &_pm_01_sp[ch];
+ invalidValue = utils::getInvalidPmValue();
+ break;
+ case PM25_SP:
+ temporary = &_pm_25_sp[ch];
+ invalidValue = utils::getInvalidPmValue();
+ break;
+ case PM10_SP:
+ temporary = &_pm_10_sp[ch];
+ invalidValue = utils::getInvalidPmValue();
+ break;
+ case PM03_PC:
+ temporary = &_pm_03_pc[ch];
+ invalidValue = utils::getInvalidPmValue();
+ break;
+ case PM05_PC:
+ temporary = &_pm_05_pc[ch];
+ invalidValue = utils::getInvalidPmValue();
+ break;
+ case PM01_PC:
+ temporary = &_pm_01_pc[ch];
+ invalidValue = utils::getInvalidPmValue();
+ break;
+ case PM25_PC:
+ temporary = &_pm_25_pc[ch];
+ invalidValue = utils::getInvalidPmValue();
+ break;
+ case PM5_PC:
+ temporary = &_pm_5_pc[ch];
+ invalidValue = utils::getInvalidPmValue();
+ break;
+ case PM10_PC:
+ temporary = &_pm_10_pc[ch];
+ invalidValue = utils::getInvalidPmValue();
+ break;
+ default:
+ break;
+ };
+
+ // Sanity check if measurement type is defined for integer data type or not
+ if (temporary == nullptr) {
+ Serial.printf("%s is not defined for integer data type\n", measurementTypeStr(type));
+ // TODO: Just assert?
+ return false;
+ }
+
+ // Restore channel value for debugging purpose
+ ch = ch + 1;
+
+ if (val == invalidValue) {
+ temporary->update.invalidCounter++;
+ if (temporary->update.invalidCounter >= temporary->update.max) {
+ Serial.printf("%s{%d} invalid value update counter reached (%dx)! Setting its average value "
+ "to invalid!\n",
+ measurementTypeStr(type).c_str(), ch, temporary->update.max);
+ temporary->update.avg = invalidValue;
+ return false;
+ }
+
+ // Still consider updating value to valid
+ return true;
+ }
+
+ // Reset invalid counter when update new valid value
+ temporary->update.invalidCounter = 0;
+
+ // Add new value to the end of the list
+ temporary->listValues.push_back(val);
+ // Sum the new value
+ temporary->sumValues = temporary->sumValues + val;
+ // Remove the oldest value on the list when the list exceed max elements
+ if (temporary->listValues.size() > temporary->update.max) {
+ auto it = temporary->listValues.begin();
+ temporary->sumValues = temporary->sumValues - *it; // subtract the oldest value from sum
+ temporary->listValues.erase(it); // And remove it from the list
+ }
+
+ // Calculate average based on how many elements on the list
+ temporary->update.avg = temporary->sumValues / (float)temporary->listValues.size();
+ if (_debug) {
+ Serial.printf("%s{%d}: %.2f\n", measurementTypeStr(type), ch, temporary->update.avg);
+ }
+
+ return true;
+}
+
+bool Measurements::update(MeasurementType type, float val, 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
+ FloatValue *temporary = nullptr;
+ // Act as reference invalid value respective to target measurements
+ float invalidValue = 0;
+ switch (type) {
+ case Temperature:
+ temporary = &_temperature[ch];
+ invalidValue = utils::getInvalidTemperature();
+ break;
+ case Humidity:
+ temporary = &_humidity[ch];
+ invalidValue = utils::getInvalidHumidity();
+ break;
+ default:
+ break;
+ }
+
+ // Sanity check if measurement type is defined for float data type or not
+ if (temporary == nullptr) {
+ Serial.printf("%s is not defined for float data type\n", measurementTypeStr(type));
+ // TODO: Just assert?
+ return false;
+ }
+
+ // Restore channel value for debugging purpose
+ ch = ch + 1;
+
+ if (val == invalidValue) {
+ temporary->update.invalidCounter++;
+ if (temporary->update.invalidCounter >= temporary->update.max) {
+ Serial.printf("%s{%d} invalid value update counter reached (%dx)! Setting its average value "
+ "to invalid!\n",
+ measurementTypeStr(type).c_str(), ch, temporary->update.max);
+ temporary->update.avg = invalidValue;
+ return false;
+ }
+
+ // Still consider updating value to valid
+ return true;
+ }
+
+ // Reset invalid counter when update new valid value
+ temporary->update.invalidCounter = 0;
+
+ // Add new value to the end of the list
+ temporary->listValues.push_back(val);
+ // Sum the new value
+ temporary->sumValues = temporary->sumValues + val;
+ // Remove the oldest value on the list when the list exceed max elements
+ if (temporary->listValues.size() > temporary->update.max) {
+ auto it = temporary->listValues.begin();
+ temporary->sumValues = temporary->sumValues - *it; // subtract the oldest value from sum
+ temporary->listValues.erase(it); // And remove it from the list
+ }
+
+ // Calculate average based on how many elements on the list
+ temporary->update.avg = temporary->sumValues / (float)temporary->listValues.size();
+ if (_debug) {
+ Serial.printf("%s{%d}: %.2f\n", measurementTypeStr(type), ch, temporary->update.avg);
+ }
+
+ return true;
+}
+
+int Measurements::get(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
+ IntegerValue *temporary = nullptr;
+ switch (type) {
+ case CO2:
+ temporary = &_co2;
+ break;
+ case TVOC:
+ temporary = &_tvoc;
+ break;
+ case TVOCRaw:
+ temporary = &_tvoc_raw;
+ break;
+ case NOx:
+ temporary = &_nox;
+ break;
+ case NOxRaw:
+ temporary = &_nox_raw;
+ break;
+ case PM25:
+ temporary = &_pm_25[ch];
+ break;
+ case PM01:
+ temporary = &_pm_01[ch];
+ break;
+ case PM10:
+ temporary = &_pm_10[ch];
+ break;
+ case PM03_PC:
+ temporary = &_pm_03_pc[ch];
+ break;
+ default:
+ break;
+ };
+
+ // Sanity check if measurement type is defined for integer data type or not
+ if (temporary == nullptr) {
+ Serial.printf("%s is not defined for integer data type\n", measurementTypeStr(type));
+ // TODO: Just assert?
+ return false;
+ }
+
+ if (temporary->listValues.empty()) {
+ // Values still empty, return 0
+ return 0;
+ }
+
+ return temporary->listValues.back();
+}
+
+float Measurements::getFloat(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
+ FloatValue *temporary = nullptr;
+ switch (type) {
+ case Temperature:
+ temporary = &_temperature[ch];
+ break;
+ case Humidity:
+ temporary = &_humidity[ch];
+ break;
+ default:
+ break;
+ }
+
+ // Sanity check if measurement type is defined for float data type or not
+ if (temporary == nullptr) {
+ Serial.printf("%s is not defined for float data type\n", measurementTypeStr(type));
+ // TODO: Just assert?
+ return false;
+ }
+
+ if (temporary->listValues.empty()) {
+ // Values still empty, return 0
+ return 0;
+ }
+
+ return temporary->listValues.back();
+}
-/**
- * @brief Get PMS5003 firmware version string
- *
- * @param fwCode
- * @return String
- */
String Measurements::pms5003FirmwareVersion(int fwCode) {
return pms5003FirmwareVersionBase("PMS5003x", fwCode);
}
-/**
- * @brief Get PMS5003T firmware version string
- *
- * @param fwCode
- * @return String
- */
String Measurements::pms5003TFirmwareVersion(int fwCode) {
return pms5003FirmwareVersionBase("PMS5003x", fwCode);
}
-/**
- * @brief Get firmware version string
- *
- * @param prefix Prefix firmware string
- * @param fwCode Version code
- * @return string
- */
String Measurements::pms5003FirmwareVersionBase(String prefix, int fwCode) {
return prefix + String("-") + String(fwCode);
}
-String Measurements::toString(bool localServer, AgFirmwareMode fwMode, int rssi,
- void *_ag, void *_config) {
- AirGradient *ag = (AirGradient *)_ag;
- Configuration *config = (Configuration *)_config;
+String Measurements::measurementTypeStr(MeasurementType type) {
+ String str;
+ switch (type) {
+ case Temperature:
+ str = "Temperature";
+ break;
+ case Humidity:
+ str = "Humidity";
+ break;
+ case CO2:
+ str = "CO2";
+ break;
+ case TVOC:
+ str = "TVOC";
+ break;
+ case TVOCRaw:
+ str = "TVOCRaw";
+ break;
+ case NOx:
+ str = "NOx";
+ break;
+ case NOxRaw:
+ str = "NOxRaw";
+ break;
+ case PM25:
+ str = "PM25_AE";
+ break;
+ case PM01:
+ str = "PM1_AE";
+ break;
+ case PM10:
+ str = "PM10_AE";
+ break;
+ case PM25_SP:
+ str = "PM25_SP";
+ break;
+ case PM01_SP:
+ str = "PM1_SP";
+ break;
+ case PM10_SP:
+ str = "PM10_SP";
+ break;
+ case PM03_PC:
+ str = "PM003_PC";
+ break;
+ case PM05_PC:
+ str = "PM005_PC";
+ break;
+ case PM01_PC:
+ str = "PM01_PC";
+ break;
+ case PM25_PC:
+ str = "PM25_PC";
+ break;
+ case PM5_PC:
+ str = "PM05_PC";
+ break;
+ case PM10_PC:
+ str = "PM10_PC";
+ break;
+ default:
+ break;
+ };
- JSONVar root;
- root["wifi"] = rssi;
- if (localServer) {
- root["serialno"] = ag->deviceId();
+ return str;
+}
+
+void Measurements::validateChannel(int ch) {
+ if (ch != 1 && ch != 2) {
+ Serial.printf("ERROR! Channel %d is undefined. Only channel 1 or 2 is the optional value!", ch);
+ delay(1000);
+ assert(0);
}
+}
- if (config->hasSensorS8 && utils::isValidCO2(this->CO2)) {
- root["rco2"] = this->CO2;
- }
-
- if (ag->isOne() || (ag->isPro4_2()) || ag->isPro3_3() || ag->isBasic()) {
- if (config->hasSensorPMS1) {
- if (utils::isValidPm(this->pm01_1)) {
- root["pm01"] = this->pm01_1;
- }
- if (utils::isValidPm(this->pm25_1)) {
- root["pm02"] = this->pm25_1;
- }
- if (utils::isValidPm(this->pm10_1)) {
- root["pm10"] = this->pm10_1;
- }
- if (utils::isValidPm03Count(this->pm03PCount_1)) {
- root["pm003Count"] = this->pm03PCount_1;
- }
- if (!localServer) {
-
- root[json_prop_pmFirmware] =
- this->pms5003FirmwareVersion(ag->pms5003.getFirmwareVersion());
- }
- }
-
- if (config->hasSensorSHT) {
- if (utils::isValidTemperature(this->Temperature)) {
- root["atmp"] = ag->round2(this->Temperature);
- if (localServer) {
- root["atmpCompensated"] = ag->round2(this->Temperature);
- }
- }
- if (utils::isValidHumidity(this->Humidity)) {
- root["rhum"] = this->Humidity;
- if (localServer) {
- root["rhumCompensated"] = this->Humidity;
- }
- }
- }
-
- if (config->hasSensorSHT && config->hasSensorPMS1) {
- int pm25 = ag->pms5003.compensate(this->pm25_1, this->Humidity);
- if (pm25 >= 0) {
- root["pm02Compensated"] = pm25;
- }
- }
-
+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 {
- if (config->hasSensorPMS1 && config->hasSensorPMS2) {
- if (utils::isValidPm(this->pm01_1) && utils::isValidPm(this->pm01_2)) {
- root["pm01"] = ag->round2((this->pm01_1 + this->pm01_2) / 2.0f);
- }
- if (utils::isValidPm(this->pm25_1) && utils::isValidPm(this->pm25_2)) {
- root["pm02"] = ag->round2((this->pm25_1 + this->pm25_2) / 2.0f);
- }
- if (utils::isValidPm(this->pm10_1) && utils::isValidPm(this->pm10_2)) {
- root["pm10"] = ag->round2((this->pm10_1 + this->pm10_2) / 2.0f);
- }
- if (utils::isValidPm(this->pm03PCount_1) && utils::isValidPm(this->pm03PCount_2)) {
- root["pm003Count"] = ag->round2((this->pm03PCount_1 + this->pm03PCount_2) / 2.0f);
- }
+ pm25 = get(PM25, ch);
+ humidity = getFloat(Humidity, ch);
+ pm003Count = get(PM03_PC, ch);
+ }
- float val;
- if (utils::isValidTemperature(this->temp_1) && utils::isValidTemperature(this->temp_1)) {
- root["atmp"] = ag->round2((this->temp_1 + this->temp_2) / 2.0f);
- if (localServer) {
- val = ag->pms5003t_2.compensateTemp((this->temp_1 + this->temp_2) / 2.0f);
- if (utils::isValidTemperature(val)) {
- root["atmpCompensated"] = ag->round2(val);
- }
- }
- }
- if (utils::isValidHumidity(this->hum_1) && utils::isValidHumidity(this->hum_1)) {
- root["rhum"] = ag->round2((this->hum_1 + this->hum_2) / 2.0f);
- if (localServer) {
- val = ag->pms5003t_2.compensateHum((this->hum_1 + this->hum_2) / 2.0f);
- if (utils::isValidHumidity(val)) {
- root["rhumCompensated"] = (int)val;
- }
- }
- }
-
- int pm25 = (ag->pms5003t_1.compensate(this->pm25_1, this->hum_1) +
- ag->pms5003t_2.compensate(this->pm25_2, this->hum_2)) /
- 2;
- root["pm02Compensated"] = pm25;
- }
-
- if (fwMode == FW_MODE_O_1PS || fwMode == FW_MODE_O_1PST) {
- float val;
- if (config->hasSensorPMS1) {
- if (utils::isValidPm(this->pm01_1)) {
- root["pm01"] = this->pm01_1;
- }
- if (utils::isValidPm(this->pm25_1)) {
- root["pm02"] = this->pm25_1;
- }
- if (utils::isValidPm(this->pm10_1)) {
- root["pm10"] = this->pm10_1;
- }
- if (utils::isValidPm03Count(this->pm03PCount_1)) {
- root["pm003Count"] = this->pm03PCount_1;
- }
- if (utils::isValidTemperature(this->temp_1)) {
- root["atmp"] = ag->round2(this->temp_1);
-
- if (localServer) {
- val = ag->pms5003t_1.compensateTemp(this->temp_1);
- if (utils::isValidTemperature(val)) {
- root["atmpCompensated"] = ag->round2(val);
- }
- }
- }
- if (utils::isValidHumidity(this->hum_1)) {
- root["rhum"] = this->hum_1;
-
- if (localServer) {
- val = ag->pms5003t_1.compensateHum(this->hum_1);
- if (utils::isValidHumidity(val)) {
- root["rhumCompensated"] = (int)val;
- }
- }
- }
- root["pm02Compensated"] = ag->pms5003t_1.compensate(this->pm25_1, this->hum_1);
- if (!localServer) {
- root[json_prop_pmFirmware] =
- pms5003TFirmwareVersion(ag->pms5003t_1.getFirmwareVersion());
- }
- }
- if (config->hasSensorPMS2) {
- if(utils::isValidPm(this->pm01_2)) {
- root["pm01"] = this->pm01_2;
- }
- if(utils::isValidPm(this->pm25_2)) {
- root["pm02"] = this->pm25_2;
- }
- if(utils::isValidPm(this->pm10_2)) {
- root["pm10"] = this->pm10_2;
- }
- if(utils::isValidPm03Count(this->pm03PCount_2)) {
- root["pm003Count"] = this->pm03PCount_2;
- }
-
- float val;
- if (utils::isValidTemperature(this->temp_2)) {
- root["atmp"] = ag->round2(this->temp_2);
-
- if (localServer) {
- val = ag->pms5003t_2.compensateTemp(this->temp_2);
- if (utils::isValidTemperature(val)) {
- root["atmpCompensated"] = ag->round2(val);
- }
- }
- }
- if(utils::isValidHumidity(this->hum_2)) {
- root["rhum"] = this->hum_2;
-
- if (localServer) {
- val = ag->pms5003t_2.compensateHum(this->hum_2);
- if (utils::isValidHumidity(val)) {
- root["rhumCompensated"] = (int)val;
- }
- }
- }
- root["pm02Compensated"] = ag->pms5003t_2.compensate(this->pm25_2, this->hum_2);
- if(!localServer) {
- root[json_prop_pmFirmware] =
- pms5003TFirmwareVersion(ag->pms5003t_1.getFirmwareVersion());
- }
- }
- } else {
- if (fwMode == FW_MODE_O_1P) {
- float val;
- if (config->hasSensorPMS1) {
- if (utils::isValidPm(this->pm01_1)) {
- root["pm01"] = this->pm01_1;
- }
- if (utils::isValidPm(this->pm25_1)) {
- root["pm02"] = this->pm25_1;
- }
- if (utils::isValidPm(this->pm10_1)) {
- root["pm10"] = this->pm10_1;
- }
- if (utils::isValidPm03Count(this->pm03PCount_1)) {
- root["pm003Count"] = this->pm03PCount_1;
- }
- if (utils::isValidTemperature(this->temp_1)) {
- root["atmp"] = ag->round2(this->temp_1);
-
- if (localServer) {
- val = ag->pms5003t_1.compensateTemp(this->temp_1);
- if (utils::isValidTemperature(val)) {
- root["atmpCompensated"] = ag->round2(val);
- }
- }
- }
- if (utils::isValidHumidity(this->hum_1)) {
- root["rhum"] = this->hum_1;
- if(localServer) {
- val = ag->pms5003t_1.compensateHum(this->hum_1);
- if(utils::isValidHumidity(val)) {
- root["rhumCompensated"] = (int)val;
- }
- }
- }
- root["pm02Compensated"] = ag->pms5003t_1.compensate(this->pm25_1, this->hum_1);
- if(!localServer) {
- root[json_prop_pmFirmware] =
- pms5003TFirmwareVersion(ag->pms5003t_1.getFirmwareVersion());
- }
- } else if (config->hasSensorPMS2) {
- if(utils::isValidPm(this->pm01_2)) {
- root["pm01"] = this->pm01_2;
- }
- if(utils::isValidPm(this->pm25_2)) {
- root["pm02"] = this->pm25_2;
- }
- if(utils::isValidPm(this->pm10_2)) {
- root["pm10"] = this->pm10_2;
- }
- if(utils::isValidPm03Count(this->pm03PCount_2)) {
- root["pm003Count"] = this->pm03PCount_2;
- }
- if (utils::isValidTemperature(this->temp_2)) {
- root["atmp"] = ag->round2(this->temp_2);
- if (localServer) {
-
- val = ag->pms5003t_1.compensateTemp(this->temp_2);
- if (utils::isValidTemperature(val)) {
- root["atmpCompensated"] = ag->round2(val);
- }
- }
- }
- if (utils::isValidHumidity(this->hum_2)) {
- root["rhum"] = this->hum_2;
-
- if(localServer) {
- val = ag->pms5003t_1.compensateHum(this->hum_2);
- if(utils::isValidHumidity(val)) {
- root["rhumCompensated"] = (int)val;
- }
- }
- }
- root["pm02Compensated"] = ag->pms5003t_1.compensate(this->pm25_1, this->hum_1);
- if(!localServer) {
- root[json_prop_pmFirmware] =
- pms5003TFirmwareVersion(ag->pms5003t_2.getFirmwareVersion());
- }
- }
- } else {
- float val;
- if (config->hasSensorPMS1) {
- if(utils::isValidPm(this->pm01_1)) {
- root["channels"]["1"]["pm01"] = this->pm01_1;
- }
- if(utils::isValidPm(this->pm25_1)) {
- root["channels"]["1"]["pm02"] = this->pm25_1;
- }
- if(utils::isValidPm(this->pm10_1)) {
- root["channels"]["1"]["pm10"] = this->pm10_1;
- }
- if (utils::isValidPm03Count(this->pm03PCount_1)) {
- root["channels"]["1"]["pm003Count"] = this->pm03PCount_1;
- }
- if(utils::isValidTemperature(this->temp_1)) {
- root["channels"]["1"]["atmp"] = ag->round2(this->temp_1);
-
- if (localServer) {
- val = ag->pms5003t_1.compensateTemp(this->temp_1);
- if (utils::isValidTemperature(val)) {
- root["channels"]["1"]["atmpCompensated"] = ag->round2(val);
- }
- }
- }
- if (utils::isValidHumidity(this->hum_1)) {
- root["channels"]["1"]["rhum"] = this->hum_1;
-
- if (localServer) {
- val = ag->pms5003t_1.compensateHum(this->hum_1);
- if (utils::isValidHumidity(val)) {
- root["channels"]["1"]["rhumCompensated"] = (int)val;
- }
- }
- }
- root["channels"]["1"]["pm02Compensated"] = ag->pms5003t_1.compensate(this->pm25_1, this->hum_1);
-
- // PMS5003T version
- if(!localServer) {
- root["channels"]["1"][json_prop_pmFirmware] =
- pms5003TFirmwareVersion(ag->pms5003t_1.getFirmwareVersion());
- }
- }
- if (config->hasSensorPMS2) {
- float val;
- if (utils::isValidPm(this->pm01_2)) {
- root["channels"]["2"]["pm01"] = this->pm01_2;
- }
- if (utils::isValidPm(this->pm25_2)) {
- root["channels"]["2"]["pm02"] = this->pm25_2;
- }
- if (utils::isValidPm(this->pm10_2)) {
- root["channels"]["2"]["pm10"] = this->pm10_2;
- }
- if (utils::isValidPm03Count(this->pm03PCount_2)) {
- root["channels"]["2"]["pm003Count"] = this->pm03PCount_2;
- }
- if (utils::isValidTemperature(this->temp_2)) {
- root["channels"]["2"]["atmp"] = ag->round2(this->temp_2);
-
- if (localServer) {
- val = ag->pms5003t_1.compensateTemp(this->temp_2);
- if (utils::isValidTemperature(val)) {
- root["channels"]["2"]["atmpCompensated"] = ag->round2(val);
- }
- }
- }
- if (utils::isValidHumidity(this->hum_2)) {
- root["channels"]["2"]["rhum"] = this->hum_2;
-
- if (localServer) {
- val = ag->pms5003t_1.compensateHum(this->hum_2);
- if (utils::isValidHumidity(val)) {
- root["channels"]["2"]["rhumCompensated"] = (int)val;
- }
- }
- }
- root["channels"]["2"]["pm02Compensated"] = ag->pms5003t_2.compensate(this->pm25_2, this->hum_2);
- // PMS5003T version
- if(!localServer) {
- root["channels"]["2"][json_prop_pmFirmware] =
- pms5003TFirmwareVersion(ag->pms5003t_2.getFirmwareVersion());
- }
- }
- }
+ 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);
}
}
- if (config->hasSensorSGP) {
- if (utils::isValidVOC(this->TVOC)) {
- root["tvocIndex"] = this->TVOC;
+ return pm25;
+}
+
+String Measurements::toString(bool localServer, AgFirmwareMode fwMode, int rssi, AirGradient &ag,
+ Configuration &config) {
+ JSONVar root;
+
+ if (ag.isOne() || (ag.isPro4_2()) || ag.isPro3_3() || ag.isBasic()) {
+ root = buildIndoor(localServer, ag, config);
+ } else {
+ root = buildOutdoor(localServer, fwMode, ag, config);
+ }
+
+ // CO2
+ if (config.hasSensorS8 && utils::isValidCO2(_co2.update.avg)) {
+ root[json_prop_co2] = ag.round2(_co2.update.avg);
+ }
+
+ /// TVOx and NOx
+ if (config.hasSensorSGP) {
+ if (utils::isValidVOC(_tvoc.update.avg)) {
+ root[json_prop_tvoc] = ag.round2(_tvoc.update.avg);
}
- if (utils::isValidVOC(this->TVOCRaw)) {
- root["tvocRaw"] = this->TVOCRaw;
+ if (utils::isValidVOC(_tvoc_raw.update.avg)) {
+ root[json_prop_tvocRaw] = ag.round2(_tvoc_raw.update.avg);
}
- if (utils::isValidNOx(this->NOx)) {
- root["noxIndex"] = this->NOx;
+ if (utils::isValidNOx(_nox.update.avg)) {
+ root[json_prop_nox] = ag.round2(_nox.update.avg);
}
- if (utils::isValidNOx(this->NOxRaw)) {
- root["noxRaw"] = this->NOxRaw;
+ if (utils::isValidNOx(_nox_raw.update.avg)) {
+ root[json_prop_noxRaw] = ag.round2(_nox_raw.update.avg);
}
}
+
root["boot"] = bootCount;
root["bootCount"] = bootCount;
+ root["wifi"] = rssi;
if (localServer) {
- if (ag->isOne()) {
- root["ledMode"] = config->getLedBarModeName();
+ if (ag.isOne()) {
+ root["ledMode"] = config.getLedBarModeName();
}
- root["firmware"] = ag->getVersion();
+ root["serialno"] = ag.deviceId();
+ root["firmware"] = ag.getVersion();
root["model"] = AgFirmwareModeName(fwMode);
}
- return JSON.stringify(root);
+ String result = JSON.stringify(root);
+ Serial.printf("\n---- PAYLOAD\n %s \n-----\n", result.c_str());
+ return result;
}
+
+JSONVar Measurements::buildOutdoor(bool localServer, AgFirmwareMode fwMode, AirGradient &ag,
+ Configuration &config) {
+ JSONVar outdoor;
+ if (fwMode == FW_MODE_O_1P || fwMode == FW_MODE_O_1PS || fwMode == FW_MODE_O_1PST) {
+ // buildPMS params:
+ /// Because only have 1 PMS, allCh is set to false
+ /// But enable temp hum from PMS
+ /// compensated values if requested by local server
+ /// Set ch based on hasSensorPMSx
+ if (config.hasSensorPMS1) {
+ outdoor = buildPMS(ag, 1, false, true, localServer);
+ if (!localServer) {
+ outdoor[json_prop_pmFirmware] = pms5003TFirmwareVersion(ag.pms5003t_1.getFirmwareVersion());
+ }
+ } else {
+ outdoor = buildPMS(ag, 2, false, true, localServer);
+ if (!localServer) {
+ outdoor[json_prop_pmFirmware] = pms5003TFirmwareVersion(ag.pms5003t_2.getFirmwareVersion());
+ }
+ }
+ } else {
+ // FW_MODE_O_1PPT && FW_MODE_O_1PP: Outdoor monitor that have 2 PMS sensor
+ // buildPMS params:
+ /// Have 2 PMS sensor, allCh is set to true (ch params ignored)
+ /// Enable temp hum from PMS
+ /// compensated values if requested by local server
+ outdoor = buildPMS(ag, 1, true, true, localServer);
+ // PMS5003T version
+ if (!localServer) {
+ outdoor["channels"]["1"][json_prop_pmFirmware] =
+ pms5003TFirmwareVersion(ag.pms5003t_1.getFirmwareVersion());
+ outdoor["channels"]["2"][json_prop_pmFirmware] =
+ pms5003TFirmwareVersion(ag.pms5003t_2.getFirmwareVersion());
+ }
+ }
+
+ return outdoor;
+}
+
+JSONVar Measurements::buildIndoor(bool localServer, AirGradient &ag, Configuration &config) {
+ JSONVar indoor;
+
+ if (config.hasSensorPMS1) {
+ // buildPMS params:
+ /// PMS channel 1 (indoor only have 1 PMS; hence allCh false)
+ /// Not include temperature and humidity from PMS sensor
+ /// Not include compensated calculation
+ indoor = buildPMS(ag, 1, false, false, false);
+ if (!localServer) {
+ // Indoor is using PMS5003
+ indoor[json_prop_pmFirmware] = this->pms5003FirmwareVersion(ag.pms5003.getFirmwareVersion());
+ }
+ }
+
+ if (config.hasSensorSHT) {
+ // Add temperature
+ if (utils::isValidTemperature(_temperature[0].update.avg)) {
+ indoor[json_prop_temp] = ag.round2(_temperature[0].update.avg);
+ if (localServer) {
+ indoor[json_prop_tempCompensated] = ag.round2(_temperature[0].update.avg);
+ }
+ }
+ // Add humidity
+ if (utils::isValidHumidity(_humidity[0].update.avg)) {
+ indoor[json_prop_rhum] = ag.round2(_humidity[0].update.avg);
+ if (localServer) {
+ indoor[json_prop_rhumCompensated] = ag.round2(_humidity[0].update.avg);
+ }
+ }
+ }
+
+ // 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)) {
+ // Correction using moving average value
+ float tmp = getCorrectedPM25(ag, config, true);
+ indoor[json_prop_pm25Compensated] = ag.round2(tmp);
+ }
+ }
+
+ return indoor;
+}
+
+JSONVar Measurements::buildPMS(AirGradient &ag, int ch, bool allCh, bool withTempHum,
+ bool compensate) {
+ JSONVar pms;
+
+ // When only one of the channel
+ if (allCh == false) {
+ // Sanity check to validate channel, assert if invalid
+ validateChannel(ch);
+
+ // Follow array indexing just for get address of the value type
+ ch = ch - 1;
+
+ if (utils::isValidPm(_pm_01[ch].update.avg)) {
+ pms[json_prop_pm01Ae] = ag.round2(_pm_01[ch].update.avg);
+ }
+ if (utils::isValidPm(_pm_25[ch].update.avg)) {
+ pms[json_prop_pm25Ae] = ag.round2(_pm_25[ch].update.avg);
+ }
+ if (utils::isValidPm(_pm_10[ch].update.avg)) {
+ pms[json_prop_pm10Ae] = ag.round2(_pm_10[ch].update.avg);
+ }
+ if (utils::isValidPm(_pm_01_sp[ch].update.avg)) {
+ pms[json_prop_pm01Sp] = ag.round2(_pm_01_sp[ch].update.avg);
+ }
+ if (utils::isValidPm(_pm_25_sp[ch].update.avg)) {
+ pms[json_prop_pm25Sp] = ag.round2(_pm_25_sp[ch].update.avg);
+ }
+ if (utils::isValidPm(_pm_10_sp[ch].update.avg)) {
+ pms[json_prop_pm10Sp] = ag.round2(_pm_10_sp[ch].update.avg);
+ }
+ if (utils::isValidPm03Count(_pm_03_pc[ch].update.avg)) {
+ pms[json_prop_pm03Count] = ag.round2(_pm_03_pc[ch].update.avg);
+ }
+ if (utils::isValidPm03Count(_pm_05_pc[ch].update.avg)) {
+ pms[json_prop_pm05Count] = ag.round2(_pm_05_pc[ch].update.avg);
+ }
+ if (utils::isValidPm03Count(_pm_01_pc[ch].update.avg)) {
+ pms[json_prop_pm1Count] = ag.round2(_pm_01_pc[ch].update.avg);
+ }
+ if (utils::isValidPm03Count(_pm_25_pc[ch].update.avg)) {
+ pms[json_prop_pm25Count] = ag.round2(_pm_25_pc[ch].update.avg);
+ }
+ if (_pm_5_pc[ch].listValues.empty() == false) {
+ // Only include pm5.0 count when values available on its list
+ // If not, means no pm5_pc available from the sensor
+ if (utils::isValidPm03Count(_pm_5_pc[ch].update.avg)) {
+ pms[json_prop_pm5Count] = ag.round2(_pm_5_pc[ch].update.avg);
+ }
+ }
+ if (_pm_10_pc[ch].listValues.empty() == false) {
+ // Only include pm10 count when values available on its list
+ // If not, means no pm10_pc available from the sensor
+ if (utils::isValidPm03Count(_pm_10_pc[ch].update.avg)) {
+ pms[json_prop_pm10Count] = ag.round2(_pm_10_pc[ch].update.avg);
+ }
+ }
+
+ if (withTempHum) {
+ float _vc;
+ // Set temperature if valid
+ if (utils::isValidTemperature(_temperature[ch].update.avg)) {
+ pms[json_prop_temp] = ag.round2(_temperature[ch].update.avg);
+ // Compensate temperature when flag is set
+ if (compensate) {
+ _vc = ag.pms5003t_1.compensateTemp(_temperature[ch].update.avg);
+ if (utils::isValidTemperature(_vc)) {
+ pms[json_prop_tempCompensated] = ag.round2(_vc);
+ }
+ }
+ }
+ // Set humidity if valid
+ if (utils::isValidHumidity(_humidity[ch].update.avg)) {
+ pms[json_prop_rhum] = ag.round2(_humidity[ch].update.avg);
+ // Compensate relative humidity when flag is set
+ if (compensate) {
+ _vc = ag.pms5003t_1.compensateHum(_humidity[ch].update.avg);
+ if (utils::isValidTemperature(_vc)) {
+ pms[json_prop_rhumCompensated] = ag.round2(_vc);
+ }
+ }
+ }
+
+ // Add pm25 compensated value only if PM2.5 and humidity value is valid
+ if (compensate) {
+ if (utils::isValidPm(_pm_25[ch].update.avg) &&
+ utils::isValidHumidity(_humidity[ch].update.avg)) {
+ // Note: the pms5003t object is not matter either for channel 1 or 2, compensate points to
+ // the same base function
+ float pm25 = ag.pms5003t_1.compensate(_pm_25[ch].update.avg, _humidity[ch].update.avg);
+ if (utils::isValidPm(pm25)) {
+ pms[json_prop_pm25Compensated] = ag.round2(pm25);
+ }
+ }
+ }
+ }
+
+ // Directly return the json object
+ return pms;
+ };
+
+ /** Handle both channels by averaging their values; if one channel's value is not valid, skip
+ * averaging and use the valid value from the other channel */
+
+ /// PM1.0 atmospheric environment
+ if (utils::isValidPm(_pm_01[0].update.avg) && utils::isValidPm(_pm_01[1].update.avg)) {
+ float avg = (_pm_01[0].update.avg + _pm_01[1].update.avg) / 2.0f;
+ pms[json_prop_pm01Ae] = ag.round2(avg);
+ pms["channels"]["1"][json_prop_pm01Ae] = ag.round2(_pm_01[0].update.avg);
+ pms["channels"]["2"][json_prop_pm01Ae] = ag.round2(_pm_01[1].update.avg);
+ } else if (utils::isValidPm(_pm_01[0].update.avg)) {
+ pms[json_prop_pm01Ae] = ag.round2(_pm_01[0].update.avg);
+ pms["channels"]["1"][json_prop_pm01Ae] = ag.round2(_pm_01[0].update.avg);
+ } else if (utils::isValidPm(_pm_01[1].update.avg)) {
+ pms[json_prop_pm01Ae] = ag.round2(_pm_01[1].update.avg);
+ pms["channels"]["2"][json_prop_pm01Ae] = ag.round2(_pm_01[1].update.avg);
+ }
+
+ /// PM2.5 atmospheric environment
+ if (utils::isValidPm(_pm_25[0].update.avg) && utils::isValidPm(_pm_25[1].update.avg)) {
+ float avg = (_pm_25[0].update.avg + _pm_25[1].update.avg) / 2.0f;
+ pms[json_prop_pm25Ae] = ag.round2(avg);
+ pms["channels"]["1"][json_prop_pm25Ae] = ag.round2(_pm_25[0].update.avg);
+ pms["channels"]["2"][json_prop_pm25Ae] = ag.round2(_pm_25[1].update.avg);
+ } else if (utils::isValidPm(_pm_25[0].update.avg)) {
+ pms[json_prop_pm25Ae] = ag.round2(_pm_25[0].update.avg);
+ pms["channels"]["1"][json_prop_pm25Ae] = ag.round2(_pm_25[0].update.avg);
+ } else if (utils::isValidPm(_pm_25[1].update.avg)) {
+ pms[json_prop_pm25Ae] = ag.round2(_pm_25[1].update.avg);
+ pms["channels"]["2"][json_prop_pm25Ae] = ag.round2(_pm_25[1].update.avg);
+ }
+
+ /// PM10 atmospheric environment
+ if (utils::isValidPm(_pm_10[0].update.avg) && utils::isValidPm(_pm_10[1].update.avg)) {
+ float avg = (_pm_10[0].update.avg + _pm_10[1].update.avg) / 2.0f;
+ pms[json_prop_pm10Ae] = ag.round2(avg);
+ pms["channels"]["1"][json_prop_pm10Ae] = ag.round2(_pm_10[0].update.avg);
+ pms["channels"]["2"][json_prop_pm10Ae] = ag.round2(_pm_10[1].update.avg);
+ } else if (utils::isValidPm(_pm_10[0].update.avg)) {
+ pms[json_prop_pm10Ae] = ag.round2(_pm_10[0].update.avg);
+ pms["channels"]["1"][json_prop_pm10Ae] = ag.round2(_pm_10[0].update.avg);
+ } else if (utils::isValidPm(_pm_10[1].update.avg)) {
+ pms[json_prop_pm10Ae] = ag.round2(_pm_10[1].update.avg);
+ pms["channels"]["2"][json_prop_pm10Ae] = ag.round2(_pm_10[1].update.avg);
+ }
+
+ /// PM1.0 standard particle
+ if (utils::isValidPm(_pm_01_sp[0].update.avg) && utils::isValidPm(_pm_01_sp[1].update.avg)) {
+ float avg = (_pm_01_sp[0].update.avg + _pm_01_sp[1].update.avg) / 2.0f;
+ pms[json_prop_pm01Sp] = ag.round2(avg);
+ pms["channels"]["1"][json_prop_pm01Sp] = ag.round2(_pm_01_sp[0].update.avg);
+ pms["channels"]["2"][json_prop_pm01Sp] = ag.round2(_pm_01_sp[1].update.avg);
+ } else if (utils::isValidPm(_pm_01_sp[0].update.avg)) {
+ pms[json_prop_pm01Sp] = ag.round2(_pm_01_sp[0].update.avg);
+ pms["channels"]["1"][json_prop_pm01Sp] = ag.round2(_pm_01_sp[0].update.avg);
+ } else if (utils::isValidPm(_pm_01_sp[1].update.avg)) {
+ pms[json_prop_pm01Sp] = ag.round2(_pm_01_sp[1].update.avg);
+ pms["channels"]["2"][json_prop_pm01Sp] = ag.round2(_pm_01_sp[1].update.avg);
+ }
+
+ /// PM2.5 standard particle
+ if (utils::isValidPm(_pm_25_sp[0].update.avg) && utils::isValidPm(_pm_25_sp[1].update.avg)) {
+ float avg = (_pm_25_sp[0].update.avg + _pm_25_sp[1].update.avg) / 2.0f;
+ pms[json_prop_pm25Sp] = ag.round2(avg);
+ pms["channels"]["1"][json_prop_pm25Sp] = ag.round2(_pm_25_sp[0].update.avg);
+ pms["channels"]["2"][json_prop_pm25Sp] = ag.round2(_pm_25_sp[1].update.avg);
+ } else if (utils::isValidPm(_pm_25_sp[0].update.avg)) {
+ pms[json_prop_pm25Sp] = ag.round2(_pm_25_sp[0].update.avg);
+ pms["channels"]["1"][json_prop_pm25Sp] = ag.round2(_pm_25_sp[0].update.avg);
+ } else if (utils::isValidPm(_pm_25_sp[1].update.avg)) {
+ pms[json_prop_pm25Sp] = ag.round2(_pm_25_sp[1].update.avg);
+ pms["channels"]["2"][json_prop_pm25Sp] = ag.round2(_pm_25_sp[1].update.avg);
+ }
+
+ /// PM10 standard particle
+ if (utils::isValidPm(_pm_10_sp[0].update.avg) && utils::isValidPm(_pm_10_sp[1].update.avg)) {
+ float avg = (_pm_10_sp[0].update.avg + _pm_10_sp[1].update.avg) / 2.0f;
+ pms[json_prop_pm10Sp] = ag.round2(avg);
+ pms["channels"]["1"][json_prop_pm10Sp] = ag.round2(_pm_10_sp[0].update.avg);
+ pms["channels"]["2"][json_prop_pm10Sp] = ag.round2(_pm_10_sp[1].update.avg);
+ } else if (utils::isValidPm(_pm_10_sp[0].update.avg)) {
+ pms[json_prop_pm10Sp] = ag.round2(_pm_10_sp[0].update.avg);
+ pms["channels"]["1"][json_prop_pm10Sp] = ag.round2(_pm_10_sp[0].update.avg);
+ } else if (utils::isValidPm(_pm_10_sp[1].update.avg)) {
+ pms[json_prop_pm10Sp] = ag.round2(_pm_10_sp[1].update.avg);
+ pms["channels"]["2"][json_prop_pm10Sp] = ag.round2(_pm_10_sp[1].update.avg);
+ }
+
+ /// PM003 particle count
+ if (utils::isValidPm03Count(_pm_03_pc[0].update.avg) &&
+ utils::isValidPm03Count(_pm_03_pc[1].update.avg)) {
+ float avg = (_pm_03_pc[0].update.avg + _pm_03_pc[1].update.avg) / 2.0f;
+ pms[json_prop_pm03Count] = ag.round2(avg);
+ pms["channels"]["1"][json_prop_pm03Count] = ag.round2(_pm_03_pc[0].update.avg);
+ pms["channels"]["2"][json_prop_pm03Count] = ag.round2(_pm_03_pc[1].update.avg);
+ } else if (utils::isValidPm(_pm_03_pc[0].update.avg)) {
+ pms[json_prop_pm03Count] = ag.round2(_pm_03_pc[0].update.avg);
+ pms["channels"]["1"][json_prop_pm03Count] = ag.round2(_pm_03_pc[0].update.avg);
+ } else if (utils::isValidPm(_pm_03_pc[1].update.avg)) {
+ pms[json_prop_pm03Count] = ag.round2(_pm_03_pc[1].update.avg);
+ pms["channels"]["2"][json_prop_pm03Count] = ag.round2(_pm_03_pc[1].update.avg);
+ }
+
+ /// PM0.5 particle count
+ if (utils::isValidPm03Count(_pm_05_pc[0].update.avg) &&
+ utils::isValidPm03Count(_pm_05_pc[1].update.avg)) {
+ float avg = (_pm_05_pc[0].update.avg + _pm_05_pc[1].update.avg) / 2.0f;
+ pms[json_prop_pm05Count] = ag.round2(avg);
+ pms["channels"]["1"][json_prop_pm05Count] = ag.round2(_pm_05_pc[0].update.avg);
+ pms["channels"]["2"][json_prop_pm05Count] = ag.round2(_pm_05_pc[1].update.avg);
+ } else if (utils::isValidPm(_pm_05_pc[0].update.avg)) {
+ pms[json_prop_pm05Count] = ag.round2(_pm_05_pc[0].update.avg);
+ pms["channels"]["1"][json_prop_pm05Count] = ag.round2(_pm_05_pc[0].update.avg);
+ } else if (utils::isValidPm(_pm_05_pc[1].update.avg)) {
+ pms[json_prop_pm05Count] = ag.round2(_pm_05_pc[1].update.avg);
+ pms["channels"]["2"][json_prop_pm05Count] = ag.round2(_pm_05_pc[1].update.avg);
+ }
+ /// PM1.0 particle count
+ if (utils::isValidPm03Count(_pm_01_pc[0].update.avg) &&
+ utils::isValidPm03Count(_pm_01_pc[1].update.avg)) {
+ float avg = (_pm_01_pc[0].update.avg + _pm_01_pc[1].update.avg) / 2.0f;
+ pms[json_prop_pm1Count] = ag.round2(avg);
+ pms["channels"]["1"][json_prop_pm1Count] = ag.round2(_pm_01_pc[0].update.avg);
+ pms["channels"]["2"][json_prop_pm1Count] = ag.round2(_pm_01_pc[1].update.avg);
+ } else if (utils::isValidPm(_pm_01_pc[0].update.avg)) {
+ pms[json_prop_pm1Count] = ag.round2(_pm_01_pc[0].update.avg);
+ pms["channels"]["1"][json_prop_pm1Count] = ag.round2(_pm_01_pc[0].update.avg);
+ } else if (utils::isValidPm(_pm_01_pc[1].update.avg)) {
+ pms[json_prop_pm1Count] = ag.round2(_pm_01_pc[1].update.avg);
+ pms["channels"]["2"][json_prop_pm1Count] = ag.round2(_pm_01_pc[1].update.avg);
+ }
+
+ /// PM2.5 particle count
+ if (utils::isValidPm03Count(_pm_25_pc[0].update.avg) &&
+ utils::isValidPm03Count(_pm_25_pc[1].update.avg)) {
+ float avg = (_pm_25_pc[0].update.avg + _pm_25_pc[1].update.avg) / 2.0f;
+ pms[json_prop_pm25Count] = ag.round2(avg);
+ pms["channels"]["1"][json_prop_pm25Count] = ag.round2(_pm_25_pc[0].update.avg);
+ pms["channels"]["2"][json_prop_pm25Count] = ag.round2(_pm_25_pc[1].update.avg);
+ } else if (utils::isValidPm(_pm_25_pc[0].update.avg)) {
+ pms[json_prop_pm25Count] = ag.round2(_pm_25_pc[0].update.avg);
+ pms["channels"]["1"][json_prop_pm25Count] = ag.round2(_pm_25_pc[0].update.avg);
+ } else if (utils::isValidPm(_pm_25_pc[1].update.avg)) {
+ pms[json_prop_pm25Count] = ag.round2(_pm_25_pc[1].update.avg);
+ pms["channels"]["2"][json_prop_pm25Count] = ag.round2(_pm_25_pc[1].update.avg);
+ }
+
+ // NOTE: No need for particle count 5.0 and 10. When allCh is true, basically monitor using
+ // PM5003T, which don't have PC 5.0 and 10
+
+ if (withTempHum) {
+ /// Temperature
+ if (utils::isValidTemperature(_temperature[0].update.avg) &&
+ utils::isValidTemperature(_temperature[1].update.avg)) {
+
+ float temperature = (_temperature[0].update.avg + _temperature[1].update.avg) / 2.0f;
+ pms[json_prop_temp] = ag.round2(temperature);
+ pms["channels"]["1"][json_prop_temp] = ag.round2(_temperature[0].update.avg);
+ pms["channels"]["2"][json_prop_temp] = ag.round2(_temperature[1].update.avg);
+
+ if (compensate) {
+ // Compensate both temperature channel
+ float temp = ag.pms5003t_1.compensateTemp(temperature);
+ float temp1 = ag.pms5003t_1.compensateTemp(_temperature[0].update.avg);
+ float temp2 = ag.pms5003t_2.compensateTemp(_temperature[1].update.avg);
+ pms[json_prop_tempCompensated] = ag.round2(temp);
+ pms["channels"]["1"][json_prop_tempCompensated] = ag.round2(temp1);
+ pms["channels"]["2"][json_prop_tempCompensated] = ag.round2(temp2);
+ }
+
+ } else if (utils::isValidTemperature(_temperature[0].update.avg)) {
+ pms[json_prop_temp] = ag.round2(_temperature[0].update.avg);
+ pms["channels"]["1"][json_prop_temp] = ag.round2(_temperature[0].update.avg);
+
+ if (compensate) {
+ // Compensate channel 1
+ float temp1 = ag.pms5003t_1.compensateTemp(_temperature[0].update.avg);
+ pms[json_prop_tempCompensated] = ag.round2(temp1);
+ pms["channels"]["1"][json_prop_tempCompensated] = ag.round2(temp1);
+ }
+
+ } else if (utils::isValidTemperature(_temperature[1].update.avg)) {
+ pms[json_prop_temp] = ag.round2(_temperature[1].update.avg);
+ pms["channels"]["2"][json_prop_temp] = ag.round2(_temperature[1].update.avg);
+
+ if (compensate) {
+ // Compensate channel 2
+ float temp2 = ag.pms5003t_2.compensateTemp(_temperature[1].update.avg);
+ pms[json_prop_tempCompensated] = ag.round2(temp2);
+ pms["channels"]["2"][json_prop_tempCompensated] = ag.round2(temp2);
+ }
+ }
+
+ /// Relative humidity
+ if (utils::isValidHumidity(_humidity[0].update.avg) &&
+ utils::isValidHumidity(_humidity[1].update.avg)) {
+ float humidity = (_humidity[0].update.avg + _humidity[1].update.avg) / 2.0f;
+ pms[json_prop_rhum] = ag.round2(humidity);
+ pms["channels"]["1"][json_prop_rhum] = ag.round2(_humidity[0].update.avg);
+ pms["channels"]["2"][json_prop_rhum] = ag.round2(_humidity[1].update.avg);
+
+ if (compensate) {
+ // Compensate both humidity channel
+ float hum = ag.pms5003t_1.compensateHum(humidity);
+ float hum1 = ag.pms5003t_1.compensateHum(_humidity[0].update.avg);
+ float hum2 = ag.pms5003t_2.compensateHum(_humidity[1].update.avg);
+ pms[json_prop_rhumCompensated] = ag.round2(hum);
+ pms["channels"]["1"][json_prop_rhumCompensated] = ag.round2(hum1);
+ pms["channels"]["2"][json_prop_rhumCompensated] = ag.round2(hum2);
+ }
+
+ } else if (utils::isValidHumidity(_humidity[0].update.avg)) {
+ pms[json_prop_rhum] = ag.round2(_humidity[0].update.avg);
+ pms["channels"]["1"][json_prop_rhum] = ag.round2(_humidity[0].update.avg);
+
+ if (compensate) {
+ // Compensate humidity channel 1
+ float hum1 = ag.pms5003t_1.compensateHum(_humidity[0].update.avg);
+ pms[json_prop_rhumCompensated] = ag.round2(hum1);
+ pms["channels"]["1"][json_prop_rhumCompensated] = ag.round2(hum1);
+ }
+
+ } else if (utils::isValidHumidity(_humidity[1].update.avg)) {
+ pms[json_prop_rhum] = ag.round2(_humidity[1].update.avg);
+ pms["channels"]["2"][json_prop_rhum] = ag.round2(_humidity[1].update.avg);
+
+ if (compensate) {
+ // Compensate humidity channel 2
+ float hum2 = ag.pms5003t_2.compensateHum(_humidity[1].update.avg);
+ pms[json_prop_rhumCompensated] = ag.round2(hum2);
+ pms["channels"]["2"][json_prop_rhumCompensated] = ag.round2(hum2);
+ }
+ }
+
+ if (compensate) {
+ // Add pm25 compensated value
+ /// First get both channel compensated value
+ float pm25_comp1 = utils::getInvalidPmValue();
+ float pm25_comp2 = utils::getInvalidPmValue();
+ if (utils::isValidPm(_pm_25[0].update.avg) &&
+ utils::isValidHumidity(_humidity[0].update.avg)) {
+ pm25_comp1 = ag.pms5003t_1.compensate(_pm_25[0].update.avg, _humidity[0].update.avg);
+ pms["channels"]["1"][json_prop_pm25Compensated] = ag.round2(pm25_comp1);
+ }
+ if (utils::isValidPm(_pm_25[1].update.avg) &&
+ utils::isValidHumidity(_humidity[1].update.avg)) {
+ pm25_comp2 = ag.pms5003t_2.compensate(_pm_25[1].update.avg, _humidity[1].update.avg);
+ pms["channels"]["2"][json_prop_pm25Compensated] = ag.round2(pm25_comp2);
+ }
+
+ /// Get average or one of the channel compensated value if only one channel is valid
+ if (utils::isValidPm(pm25_comp1) && utils::isValidPm(pm25_comp2)) {
+ pms[json_prop_pm25Compensated] = ag.round2((pm25_comp1 + pm25_comp2) / 2.0f);
+ } else if (utils::isValidPm(pm25_comp1)) {
+ pms[json_prop_pm25Compensated] = ag.round2(pm25_comp1);
+ } else if (utils::isValidPm(pm25_comp2)) {
+ pms[json_prop_pm25Compensated] = ag.round2(pm25_comp2);
+ }
+ }
+ }
+
+ return pms;
+}
+
+void Measurements::setDebug(bool debug) { _debug = debug; }
\ No newline at end of file
diff --git a/src/AgValue.h b/src/AgValue.h
index 1957828..3b129b3 100644
--- a/src/AgValue.h
+++ b/src/AgValue.h
@@ -1,79 +1,207 @@
#ifndef _AG_VALUE_H_
#define _AG_VALUE_H_
-#include
+#include "AgConfigure.h"
+#include "AirGradient.h"
#include "App/AppDef.h"
+#include "Libraries/Arduino_JSON/src/Arduino_JSON.h"
+#include "Main/utils.h"
+#include
+#include
class Measurements {
private:
- String pms5003FirmwareVersion(int fwCode);
- String pms5003TFirmwareVersion(int fwCode);
- String pms5003FirmwareVersionBase(String prefix, int fwCode);
+ // Generic struct for update indication for respective value
+ struct Update {
+ int invalidCounter; // Counting on how many invalid value that are passed to update function
+ int max; // Maximum length of the period of the moving average
+ float avg; // Moving average value, updated every update function called
+ };
+
+ // Reading type for sensor value that outputs float
+ struct FloatValue {
+ float sumValues; // Total value from each update
+ std::vector listValues; // List of update value that are kept
+ Update update;
+ };
+
+ // Reading type for sensor value that outputs integer
+ struct IntegerValue {
+ unsigned long sumValues; // Total value from each update; unsigned long to accomodate TVOx and
+ // NOx raw data
+ std::vector listValues; // List of update value that are kept
+ Update update;
+ };
+
public:
- Measurements() {
- pm25_1 = -1;
- pm01_1 = -1;
- pm10_1 = -1;
- pm03PCount_1 = -1;
- temp_1 = -1001;
- hum_1 = -1;
-
- pm25_2 = -1;
- pm01_2 = -1;
- pm10_2 = -1;
- pm03PCount_2 = -1;
- temp_2 = -1001;
- hum_2 = -1;
-
- Temperature = -1001;
- Humidity = -1;
- CO2 = -1;
- TVOC = -1;
- TVOCRaw = -1;
- NOx = -1;
- NOxRaw = -1;
- }
+ Measurements() {}
~Measurements() {}
- float Temperature;
- int Humidity;
- int CO2;
- int TVOC;
- int TVOCRaw;
- int NOx;
- int NOxRaw;
+ // Enumeration for every AG measurements
+ enum MeasurementType {
+ Temperature,
+ Humidity,
+ CO2,
+ TVOC, // index value
+ TVOCRaw,
+ NOx, // index value
+ NOxRaw,
+ PM01, // PM1.0 under atmospheric environment
+ PM25, // PM2.5 under athompheric environment
+ PM10, // PM10 under atmospheric environment
+ PM01_SP, // PM1.0 standard particle
+ PM25_SP, // PM2.5 standard particle
+ PM10_SP, // PM10 standard particle
+ PM03_PC, // Particle 0.3 count
+ PM05_PC, // Particle 0.5 count
+ PM01_PC, // Particle 1.0 count
+ PM25_PC, // Particle 2.5 count
+ PM5_PC, // Particle 5.0 count
+ PM10_PC, // Particle 10 count
+ };
- int pm25_1;
- int pm01_1;
- int pm10_1;
- int pm03PCount_1;
- float temp_1;
- int hum_1;
+ /**
+ * @brief Set each MeasurementType maximum period length for moving average
+ *
+ * @param type the target measurement type to set
+ * @param max the maximum period length
+ */
+ void maxPeriod(MeasurementType, int max);
- int pm25_2;
- int pm01_2;
- int pm10_2;
- int pm03PCount_2;
- float temp_2;
- int hum_2;
+ /**
+ * @brief update target measurement type with new value.
+ * Each MeasurementType has last raw value and moving average value based on max period
+ * This function is for MeasurementType that use INT as the data type
+ *
+ * @param type measurement type that will be updated
+ * @param val (int) the new value
+ * @param ch (int) the MeasurementType channel, not every MeasurementType has more than 1 channel.
+ * Currently maximum channel is 2. Default: 1 (channel 1)
+ * @return false if new value invalid consecutively reach threshold (max period)
+ * @return true otherwise
+ */
+ bool update(MeasurementType type, int val, int ch = 1);
- int pm1Value01;
- int pm1Value25;
- int pm1Value10;
- int pm1PCount;
- int pm1temp;
- int pm1hum;
- int pm2Value01;
- int pm2Value25;
- int pm2Value10;
- int pm2PCount;
- int pm2temp;
- int pm2hum;
- int countPosition;
- const int targetCount = 20;
+ /**
+ * @brief update target measurement type with new value.
+ * Each MeasurementType has last raw value and moving average value based on max period
+ * This function is for MeasurementType that use FLOAT as the data type
+ *
+ * @param type measurement type that will be updated
+ * @param val (float) the new value
+ * @param ch (int) the MeasurementType channel, not every MeasurementType has more than 1 channel.
+ * Currently maximum channel is 2. Default: 1 (channel 1)
+ * @return false if new value invalid consecutively reach threshold (max period)
+ * @return true otherwise
+ */
+ bool update(MeasurementType type, float val, int ch = 1);
+
+ /**
+ * @brief Get the target measurement latest value
+ *
+ * @param type measurement type that will be retrieve
+ * @param ch target type value channel
+ * @return int measurement type value
+ */
+ int get(MeasurementType type, int ch = 1);
+
+ /**
+ * @brief Get the target measurement latest value
+ *
+ * @param type measurement type that will be retrieve
+ * @param ch target type value channel
+ * @return float measurement type value
+ */
+ 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
+ */
+ String toString(bool localServer, AgFirmwareMode fwMode, int rssi, AirGradient &ag,
+ Configuration &config);
+
+ /**
+ * Set to true if want to debug every update value
+ */
+ void setDebug(bool debug);
+
+ // TODO: update this to use setter
int bootCount;
- String toString(bool isLocal, AgFirmwareMode fwMode, int rssi, void* _ag, void* _config);
+private:
+ // Some declared as an array (channel), because FW_MODE_O_1PPx has two PMS5003T
+ FloatValue _temperature[2];
+ FloatValue _humidity[2];
+ IntegerValue _co2;
+ IntegerValue _tvoc; // Index value
+ IntegerValue _tvoc_raw;
+ IntegerValue _nox; // Index value
+ IntegerValue _nox_raw;
+ IntegerValue _pm_01[2]; // pm 1.0 atmospheric environment
+ IntegerValue _pm_25[2]; // pm 2.5 atmospheric environment
+ IntegerValue _pm_10[2]; // pm 10 atmospheric environment
+ IntegerValue _pm_01_sp[2]; // pm 1.0 standard particle
+ IntegerValue _pm_25_sp[2]; // pm 2.5 standard particle
+ IntegerValue _pm_10_sp[2]; // pm 10 standard particle
+ IntegerValue _pm_03_pc[2]; // particle count 0.3
+ IntegerValue _pm_05_pc[2]; // particle count 0.5
+ IntegerValue _pm_01_pc[2]; // particle count 1.0
+ IntegerValue _pm_25_pc[2]; // particle count 2.5
+ IntegerValue _pm_5_pc[2]; // particle count 5.0
+ IntegerValue _pm_10_pc[2]; // particle count 10
+
+ bool _debug = false;
+
+ /**
+ * @brief Get PMS5003 firmware version string
+ *
+ * @param fwCode
+ * @return String
+ */
+ String pms5003FirmwareVersion(int fwCode);
+ /**
+ * @brief Get PMS5003T firmware version string
+ *
+ * @param fwCode
+ * @return String
+ */
+ String pms5003TFirmwareVersion(int fwCode);
+ /**
+ * @brief Get firmware version string
+ *
+ * @param prefix Prefix firmware string
+ * @param fwCode Version code
+ * @return string
+ */
+ String pms5003FirmwareVersionBase(String prefix, int fwCode);
+
+ /**
+ * Convert AgValue Type to string representation of the value
+ */
+ String measurementTypeStr(MeasurementType type);
+
+ /**
+ * @brief check if provided channel is a valid channel or not
+ * abort program if invalid
+ */
+ void validateChannel(int ch);
+
+ JSONVar buildOutdoor(bool localServer, AgFirmwareMode fwMode, AirGradient &ag,
+ Configuration &config);
+ JSONVar buildIndoor(bool localServer, AirGradient &ag, Configuration &config);
+ JSONVar buildPMS(AirGradient &ag, int ch, bool allCh, bool withTempHum, bool compensate);
};
#endif /** _AG_VALUE_H_ */
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 4bf4da6..feeb4b4 100644
--- a/src/PMS/PMS.cpp
+++ b/src/PMS/PMS.cpp
@@ -151,6 +151,7 @@ void PMSBase::readPackage(Stream *serial) {
if (ms >= READ_PACKGE_TIMEOUT) {
lastPackage = 0;
_connected = false;
+ Serial.println("PMS disconnected");
}
}
}
@@ -297,24 +298,52 @@ uint8_t PMSBase::getErrorCode(void) { return pms_errorCode; }
* @return int
*/
int PMSBase::pm25ToAQI(int pm02) {
- if (pm02 <= 12.0)
- return ((50 - 0) / (12.0 - .0) * (pm02 - .0) + 0);
+ if (pm02 <= 9.0)
+ return ((50 - 0) / (9.0 - .0) * (pm02 - .0) + 0);
else if (pm02 <= 35.4)
- return ((100 - 50) / (35.4 - 12.0) * (pm02 - 12.0) + 50);
+ return ((100 - 51) / (35.4 - 9.1) * (pm02 - 9.0) + 51);
else if (pm02 <= 55.4)
- return ((150 - 100) / (55.4 - 35.4) * (pm02 - 35.4) + 100);
- else if (pm02 <= 150.4)
- return ((200 - 150) / (150.4 - 55.4) * (pm02 - 55.4) + 150);
- else if (pm02 <= 250.4)
- return ((300 - 200) / (250.4 - 150.4) * (pm02 - 150.4) + 200);
- else if (pm02 <= 350.4)
- return ((400 - 300) / (350.4 - 250.4) * (pm02 - 250.4) + 300);
- else if (pm02 <= 500.4)
- return ((500 - 400) / (500.4 - 350.4) * (pm02 - 350.4) + 400);
+ return ((150 - 101) / (55.4 - 35.5) * (pm02 - 35.5) + 101);
+ else if (pm02 <= 125.4)
+ return ((200 - 151) / (125.4 - 55.5) * (pm02 - 55.5) + 151);
+ else if (pm02 <= 225.4)
+ return ((300 - 201) / (225.4 - 125.5) * (pm02 - 125.5) + 201);
+ else if (pm02 <= 325.4)
+ return ((500 - 301) / (325.4 - 225.5) * (pm02 - 225.5) + 301);
else
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
*
@@ -322,11 +351,12 @@ int PMSBase::pm25ToAQI(int pm02) {
*
* @param pm25 Raw PM2.5 value
* @param humidity Humidity value (%)
- * @return int
+ * @return compensated pm25 value
*/
-int PMSBase::compensate(int pm25, float humidity) {
+float PMSBase::compensate(float pm25, float humidity) {
float value;
- float fpm25 = pm25;
+
+ // Correct invalid humidity value
if (humidity < 0) {
humidity = 0;
}
@@ -334,23 +364,33 @@ int PMSBase::compensate(int pm25, float humidity) {
humidity = 100.0f;
}
- if(pm25 < 30) { /** pm2.5 < 30 */
- value = (fpm25 * 0.524f) - (humidity * 0.0862f) + 5.75f;
- } else if(pm25 < 50) { /** 30 <= pm2.5 < 50 */
- value = (0.786f * (fpm25 * 0.05f - 1.5f) + 0.524f * (1.0f - (fpm25 * 0.05f - 1.5f))) * fpm25 - (0.0862f * humidity) + 5.75f;
- } else if(pm25 < 210) { /** 50 <= pm2.5 < 210 */
- value = (0.786f * fpm25) - (0.0862f * humidity) + 5.75f;
- } else if(pm25 < 260) { /** 210 <= pm2.5 < 260 */
- value = (0.69f * (fpm25 * 0.02f - 4.2f) + 0.786f * (1.0f - (fpm25 * 0.02f - 4.2f))) * fpm25 - (0.0862f * humidity * (1.0f - (fpm25 * 0.02f - 4.2f))) + (2.966f * (fpm25 * 0.02f - 4.2f)) + (5.75f * (1.0f - (fpm25 * 0.02f - 4.2f))) + (8.84f * (1.e-4) * fpm25 * fpm25 * (fpm25 * 0.02f - 4.2f));
+ // If its already 0, do not proceed
+ if (pm25 == 0) {
+ return 0.0;
+ }
+
+ if (pm25 < 30) { /** pm2.5 < 30 */
+ value = (pm25 * 0.524f) - (humidity * 0.0862f) + 5.75f;
+ } else if (pm25 < 50) { /** 30 <= pm2.5 < 50 */
+ value = (0.786f * (pm25 * 0.05f - 1.5f) + 0.524f * (1.0f - (pm25 * 0.05f - 1.5f))) * pm25 -
+ (0.0862f * humidity) + 5.75f;
+ } else if (pm25 < 210) { /** 50 <= pm2.5 < 210 */
+ value = (0.786f * pm25) - (0.0862f * humidity) + 5.75f;
+ } else if (pm25 < 260) { /** 210 <= pm2.5 < 260 */
+ value = (0.69f * (pm25 * 0.02f - 4.2f) + 0.786f * (1.0f - (pm25 * 0.02f - 4.2f))) * pm25 -
+ (0.0862f * humidity * (1.0f - (pm25 * 0.02f - 4.2f))) +
+ (2.966f * (pm25 * 0.02f - 4.2f)) + (5.75f * (1.0f - (pm25 * 0.02f - 4.2f))) +
+ (8.84f * (1.e-4) * pm25 * pm25 * (pm25 * 0.02f - 4.2f));
} else { /** 260 <= pm2.5 */
- value = 2.966f + (0.69f * fpm25) + (8.84f * (1.e-4) * fpm25 * fpm25);
+ value = 2.966f + (0.69f * pm25) + (8.84f * (1.e-4) * pm25 * pm25);
}
+ // No negative value for pm2.5
if (value < 0) {
- value = 0;
+ return 0.0;
}
- return (int)value;
+ return value;
}
/**
@@ -390,20 +430,26 @@ bool PMSBase::validate(const uint8_t *buf) {
}
void PMSBase::parse(const uint8_t *buf) {
+ // Standard particle
pms_raw0_1 = toU16(&buf[4]);
pms_raw2_5 = toU16(&buf[6]);
pms_raw10 = toU16(&buf[8]);
+ // atmospheric
pms_pm0_1 = toU16(&buf[10]);
pms_pm2_5 = toU16(&buf[12]);
pms_pm10 = toU16(&buf[14]);
+
+ // particle count
pms_count0_3 = toU16(&buf[16]);
pms_count0_5 = toU16(&buf[18]);
pms_count1_0 = toU16(&buf[20]);
pms_count2_5 = toU16(&buf[22]);
- pms_count5_0 = toU16(&buf[24]);
- pms_count10 = toU16(&buf[26]);
- pms_temp = toU16(&buf[24]);
- pms_hum = toU16(&buf[26]);
+ pms_count5_0 = toU16(&buf[24]); // PMS5003 only
+ pms_count10 = toU16(&buf[26]); // PMS5003 only
+
+ // Others
+ pms_temp = toU16(&buf[24]); // PMS5003T only
+ pms_hum = toU16(&buf[26]); // PMS5003T only
pms_firmwareVersion = buf[28];
pms_errorCode = buf[29];
}
diff --git a/src/PMS/PMS.h b/src/PMS/PMS.h
index d381d69..c363c69 100644
--- a/src/PMS/PMS.h
+++ b/src/PMS/PMS.h
@@ -39,7 +39,8 @@ public:
uint8_t getErrorCode(void);
int pm25ToAQI(int pm02);
- int compensate(int pm25, float humidity);
+ float slrCorrection(float pm25, float pm003Count, float scalingFactor, float intercept);
+ float compensate(float pm25, float humidity);
private:
static const uint8_t package_size = 32;
diff --git a/src/PMS/PMS5003.cpp b/src/PMS/PMS5003.cpp
index 7488bd6..d20c4e9 100644
--- a/src/PMS/PMS5003.cpp
+++ b/src/PMS/PMS5003.cpp
@@ -79,28 +79,49 @@ bool PMS5003::begin(void) {
}
/**
- * @brief Read PM1.0 must call this function after @ref readData success
+ * @brief Read PM1.0
*
- * @return int PM1.0 index
+ * @return int PM1.0 index (atmospheric environment)
*/
int PMS5003::getPm01Ae(void) { return pms.getPM0_1(); }
/**
- * @brief Read PM2.5 must call this function after @ref readData success
+ * @brief Read PM2.5
*
- * @return int PM2.5 index
+ * @return int PM2.5 index (atmospheric environment)
*/
int PMS5003::getPm25Ae(void) { return pms.getPM2_5(); }
/**
- * @brief Read PM10.0 must call this function after @ref readData success
+ * @brief Read PM10.0
*
- * @return int PM10.0 index
+ * @return int PM10.0 index (atmospheric environment)
*/
int PMS5003::getPm10Ae(void) { return pms.getPM10(); }
/**
- * @brief Read PM0.3 must call this function after @ref readData success
+ * @brief Read PM1.0
+ *
+ * @return int PM1.0 index (standard particle)
+ */
+int PMS5003::getPm01Sp(void) { return pms.getRaw0_1(); }
+
+/**
+ * @brief Read PM2.5
+ *
+ * @return int PM2.5 index (standard particle)
+ */
+int PMS5003::getPm25Sp(void) { return pms.getRaw2_5(); }
+
+/**
+ * @brief Read PM10
+ *
+ * @return int PM10 index (standard particle)
+ */
+int PMS5003::getPm10Sp(void) { return pms.getRaw10(); }
+
+/**
+ * @brief Read particle 0.3 count
*
* @return int PM0.3 index
*/
@@ -108,6 +129,41 @@ int PMS5003::getPm03ParticleCount(void) {
return pms.getCount0_3();
}
+/**
+ * @brief Read particle 1.0 count
+ *
+ * @return int particle 1.0 count index
+ */
+int PMS5003::getPm01ParticleCount(void) { return pms.getCount1_0(); }
+
+/**
+ * @brief Read particle 0.5 count
+ *
+ * @return int particle 0.5 count index
+ */
+int PMS5003::getPm05ParticleCount(void) { return pms.getCount0_5(); }
+
+/**
+ * @brief Read particle 2.5 count
+ *
+ * @return int particle 2.5 count index
+ */
+int PMS5003::getPm25ParticleCount(void) { return pms.getCount2_5(); }
+
+/**
+ * @brief Read particle 5.0 count
+ *
+ * @return int particle 5.0 count index
+ */
+int PMS5003::getPm5ParticleCount(void) { return pms.getCount5_0(); }
+
+/**
+ * @brief Read particle 10 count
+ *
+ * @return int particle 10 count index
+ */
+int PMS5003::getPm10ParticleCount(void) { return pms.getCount10(); }
+
/**
* @brief Convert PM2.5 to US AQI
*
@@ -116,18 +172,20 @@ int PMS5003::getPm03ParticleCount(void) {
*/
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
- *
+ *
* Reference formula: https://www.airgradient.com/documentation/correction-algorithms/
- *
+ *
* @param pm25 PM2.5 raw value
* @param humidity Humidity value
- * @return int
+ * @return compensated value in float
*/
-int PMS5003::compensate(int pm25, float humidity) {
- return pms.compensate(pm25, humidity);
-}
+float PMS5003::compensate(float pm25, float humidity) { return pms.compensate(pm25, humidity); }
/**
* @brief Get sensor firmware version
diff --git a/src/PMS/PMS5003.h b/src/PMS/PMS5003.h
index 5bfadde..bd2cf7b 100644
--- a/src/PMS/PMS5003.h
+++ b/src/PMS/PMS5003.h
@@ -25,12 +25,25 @@ public:
void resetFailCount(void);
int getFailCount(void);
int getFailCountMax(void);
+ // Atmospheric environment
int getPm01Ae(void);
int getPm25Ae(void);
int getPm10Ae(void);
+ // Standard particle
+ int getPm01Sp(void);
+ int getPm25Sp(void);
+ int getPm10Sp(void);
+ // Particle count
int getPm03ParticleCount(void);
+ int getPm05ParticleCount(void);
+ int getPm01ParticleCount(void);
+ int getPm25ParticleCount(void);
+ int getPm5ParticleCount(void);
+ int getPm10ParticleCount(void);
+
int convertPm25ToUsAqi(int pm25);
- int compensate(int pm25, float humidity);
+ float slrCorrection(float pm25, float pm003Count, float scalingFactor, float intercept);
+ float compensate(float pm25, float humidity);
int getFirmwareVersion(void);
uint8_t getErrorCode(void);
bool connected(void);
diff --git a/src/PMS/PMS5003T.cpp b/src/PMS/PMS5003T.cpp
index 4294ec5..27d6313 100644
--- a/src/PMS/PMS5003T.cpp
+++ b/src/PMS/PMS5003T.cpp
@@ -108,35 +108,77 @@ bool PMS5003T::begin(void) {
}
/**
- * @brief Read PM1.0 must call this function after @ref readData success
+ * @brief Read PM1.0
*
- * @return int PM1.0 index
+ * @return int PM1.0 index (atmospheric environment)
*/
int PMS5003T::getPm01Ae(void) { return pms.getPM0_1(); }
/**
- * @brief Read PM2.5 must call this function after @ref readData success
+ * @brief Read PM2.5
*
- * @return int PM2.5 index
+ * @return int PM2.5 index (atmospheric environment)
*/
int PMS5003T::getPm25Ae(void) { return pms.getPM2_5(); }
/**
- * @brief Read PM10.0 must call this function after @ref readData success
+ * @brief Read PM10.0
*
- * @return int PM10.0 index
+ * @return int PM10.0 index (atmospheric environment)
*/
int PMS5003T::getPm10Ae(void) { return pms.getPM10(); }
/**
- * @brief Read PM 0.3 Count must call this function after @ref readData success
+ * @brief Read PM1.0
*
- * @return int PM 0.3 Count index
+ * @return int PM1.0 index (standard particle)
+ */
+int PMS5003T::getPm01Sp(void) { return pms.getRaw0_1(); }
+
+/**
+ * @brief Read PM2.5
+ *
+ * @return int PM2.5 index (standard particle)
+ */
+int PMS5003T::getPm25Sp(void) { return pms.getRaw2_5(); }
+
+/**
+ * @brief Read PM10
+ *
+ * @return int PM10 index (standard particle)
+ */
+int PMS5003T::getPm10Sp(void) { return pms.getRaw10(); }
+
+/**
+ * @brief Read particle 0.3 count
+ *
+ * @return int particle 0.3 count index
*/
int PMS5003T::getPm03ParticleCount(void) {
return pms.getCount0_3();
}
+/**
+ * @brief Read particle 0.5 count
+ *
+ * @return int particle 0.5 count index
+ */
+int PMS5003T::getPm05ParticleCount(void) { return pms.getCount0_5(); }
+
+/**
+ * @brief Read particle 1.0 count
+ *
+ * @return int particle 1.0 count index
+ */
+int PMS5003T::getPm01ParticleCount(void) { return pms.getCount1_0(); }
+
+/**
+ * @brief Read particle 2.5 count
+ *
+ * @return int particle 2.5 count index
+ */
+int PMS5003T::getPm25ParticleCount(void) { return pms.getCount2_5(); }
+
/**
* @brief Convert PM2.5 to US AQI
*
@@ -165,16 +207,14 @@ float PMS5003T::getRelativeHumidity(void) {
/**
* @brief Correct PM2.5
- *
+ *
* Reference formula: https://www.airgradient.com/documentation/correction-algorithms/
- *
+ *
* @param pm25 PM2.5 raw value
* @param humidity Humidity value
- * @return int
+ * @return compensated value
*/
-int PMS5003T::compensate(int pm25, float humidity) {
- return pms.compensate(pm25, humidity);
-}
+float PMS5003T::compensate(float pm25, float humidity) { return pms.compensate(pm25, humidity); }
/**
* @brief Get module(s) firmware version
diff --git a/src/PMS/PMS5003T.h b/src/PMS/PMS5003T.h
index 7c3e0df..73017e7 100644
--- a/src/PMS/PMS5003T.h
+++ b/src/PMS/PMS5003T.h
@@ -28,14 +28,24 @@ public:
void resetFailCount(void);
int getFailCount(void);
int getFailCountMax(void);
+ // Atmospheric environment
int getPm01Ae(void);
int getPm25Ae(void);
int getPm10Ae(void);
+ // Standard particle
+ int getPm01Sp(void);
+ int getPm25Sp(void);
+ int getPm10Sp(void);
+ // Particle count
int getPm03ParticleCount(void);
+ int getPm05ParticleCount(void);
+ int getPm01ParticleCount(void);
+ int getPm25ParticleCount(void);
+
int convertPm25ToUsAqi(int pm25);
float getTemperature(void);
float getRelativeHumidity(void);
- int compensate(int pm25, float humidity);
+ float compensate(float pm25, float humidity);
int getFirmwareVersion(void);
uint8_t getErrorCode(void);
bool connected(void);