Compare commits

..

5 Commits

Author SHA1 Message Date
92e74feabd Merge branch 'develop'
# Conflicts:
#	library.properties
#	src/AirGradient.h
2024-12-05 15:36:32 +07:00
56809a412c Merge branch 'develop' 2024-12-05 15:17:34 +07:00
6a83743e2a Prepared to release 3.1.16 2024-12-05 15:06:10 +07:00
faaf051e39 Prepared to release 3.1.15 2024-12-05 15:00:25 +07:00
280ea5e997 Prepared to release 3.1.14 2024-12-04 10:38:13 +07:00
20 changed files with 268 additions and 545 deletions

View File

@ -2,7 +2,7 @@
From [firmware version 3.0.10](firmwares) onwards, the AirGradient ONE and Open Air monitors have below API available. From [firmware version 3.0.10](firmwares) onwards, the AirGradient ONE and Open Air monitors have below API available.
### Discovery #### Discovery
The monitors run a mDNS discovery. So within the same network, the monitor can be accessed through: The monitors run a mDNS discovery. So within the same network, the monitor can be accessed through:
@ -11,7 +11,7 @@ http://airgradient_{{serialnumber}}.local
The following requests are possible: The following requests are possible:
### Get Current Air Quality (GET) #### Get Current Air Quality (GET)
With the path "/measures/current" you can get the current air quality data. With the path "/measures/current" you can get the current air quality data.
@ -80,7 +80,7 @@ You get the following response:
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. 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) #### Get Configuration Parameters (GET)
"/config" path returns the current configuration of the monitor. "/config" path returns the current configuration of the monitor.
@ -111,7 +111,7 @@ Compensated values apply correction algorithms to make the sensor values more ac
} }
``` ```
### Set Configuration Parameters (PUT) #### 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.
@ -131,11 +131,11 @@ Example to set monitor to Celsius
``` -d "{\"param\":\"value\"}" ``` ``` -d "{\"param\":\"value\"}" ```
### Avoiding Conflicts with Configuration on AirGradient Server #### Avoiding Conflicts with Configuration on AirGradient Server
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. 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) #### Configuration Parameters (GET/PUT)
| Properties | Description | Type | Accepted Values | Example | | Properties | Description | Type | Accepted Values | Example |
|-----------------------------------|:-----------------------------------------------------------------|---------|-----------------------------------------------------------------------------------------------------------------------------------------|-------------------------------------------------| |-----------------------------------|:-----------------------------------------------------------------|---------|-----------------------------------------------------------------------------------------------------------------------------------------|-------------------------------------------------|
@ -160,9 +160,9 @@ If the monitor is set up on the AirGradient dashboard, it will also receive the
### Corrections #### Corrections
The `corrections` object allows configuring PM2.5, Temperature and Humidity correction algorithms and parameters locally. This affects both the display, local server response and open metrics values. The `corrections` object allows configuring PM2.5 correction algorithms and parameters locally. This affects both the display and local server response values.
Example correction configuration: Example correction configuration:
@ -176,29 +176,11 @@ Example correction configuration:
"scalingFactor": 0, "scalingFactor": 0,
"useEpa2021": false "useEpa2021": false
} }
}, }
"atmp": {
"correctionAlgorithm": "<Option In String>",
"slr": {
"intercept": 0,
"scalingFactor": 0
}
},
"rhum": {
"correctionAlgorithm": "<Option In String>",
"slr": {
"intercept": 0,
"scalingFactor": 0
}
},
} }
} }
``` ```
#### PM 2.5
Field Name: `pm02`
| Algorithm | Value | Description | SLR required | | Algorithm | Value | Description | SLR required |
|------------|-------------|------|---------| |------------|-------------|------|---------|
| Raw | `"none"` | No correction (default) | No | | Raw | `"none"` | No correction (default) | No |
@ -232,23 +214,3 @@ curl --location -X PUT 'http://airgradient_84fce612eff4.local/config' --header '
```bash ```bash
curl --location -X PUT 'http://airgradient_84fce612eff4.local/config' --header 'Content-Type: application/json' --data '{"corrections":{"pm02":{"correctionAlgorithm":"slr_PMS5003_20240104","slr":{"intercept":0,"scalingFactor":0.02896,"useEpa2021":true}}}}' curl --location -X PUT 'http://airgradient_84fce612eff4.local/config' --header 'Content-Type: application/json' --data '{"corrections":{"pm02":{"correctionAlgorithm":"slr_PMS5003_20240104","slr":{"intercept":0,"scalingFactor":0.02896,"useEpa2021":true}}}}'
``` ```
#### Temperature & Humidity
Field Name:
- Temperature: `atmp`
- Humidity: `rhum`
| Algorithm | Value | Description | SLR required |
|------------|-------------|------|---------|
| Raw | `"none"` | No correction (default) | No |
| AirGradient Standard Correction | `"ag_pms5003t_2024"` | Using standard airgradient correction (for outdoor monitor)| No |
| Custom | `"custom"` | custom corrections constant, set `intercept` and `scalingFactor` manually | Yes |
*Table above apply for both Temperature and Humidity*
**Example**
```bash
curl --location -X PUT 'http://airgradient_84fce612eff4.local/config' --header 'Content-Type: application/json' --data '{"corrections":{"atmp":{"correctionAlgorithm":"custom","slr":{"intercept":0.2,"scalingFactor":1.1}}}}'
```

View File

@ -55,7 +55,7 @@ CC BY-SA 4.0 Attribution-ShareAlike 4.0 International License
static AirGradient ag(DIY_BASIC); static AirGradient ag(DIY_BASIC);
static Configuration configuration(Serial); static Configuration configuration(Serial);
static AgApiClient apiClient(Serial, configuration); static AgApiClient apiClient(Serial, configuration);
static Measurements measurements(configuration); static Measurements measurements;
static OledDisplay oledDisplay(configuration, measurements, Serial); static OledDisplay oledDisplay(configuration, measurements, Serial);
static StateMachine stateMachine(oledDisplay, Serial, measurements, static StateMachine stateMachine(oledDisplay, Serial, measurements,
configuration); configuration);
@ -124,7 +124,6 @@ void setup() {
apiClient.setAirGradient(&ag); apiClient.setAirGradient(&ag);
openMetrics.setAirGradient(&ag); openMetrics.setAirGradient(&ag);
localServer.setAirGraident(&ag); localServer.setAirGraident(&ag);
measurements.setAirGradient(&ag);
/** Example set custom API root URL */ /** Example set custom API root URL */
// apiClient.setApiRoot("https://example.custom.api"); // apiClient.setApiRoot("https://example.custom.api");
@ -317,7 +316,7 @@ static void mqttHandle(void) {
} }
if (mqttClient.isConnected()) { if (mqttClient.isConnected()) {
String payload = measurements.toString(true, fwMode, wifiConnector.RSSI()); String payload = measurements.toString(true, fwMode, wifiConnector.RSSI(), ag, configuration);
String topic = "airgradient/readings/" + ag.deviceId(); String topic = "airgradient/readings/" + ag.deviceId();
if (mqttClient.publish(topic.c_str(), payload.c_str(), payload.length())) { if (mqttClient.publish(topic.c_str(), payload.c_str(), payload.length())) {
Serial.println("MQTT sync success"); Serial.println("MQTT sync success");
@ -528,7 +527,7 @@ static void sendDataToServer(void) {
return; return;
} }
String syncData = measurements.toString(false, fwMode, wifiConnector.RSSI()); String syncData = measurements.toString(false, fwMode, wifiConnector.RSSI(), ag, configuration);
if (apiClient.postToServer(syncData)) { if (apiClient.postToServer(syncData)) {
Serial.println(); Serial.println();
Serial.println( Serial.println(

View File

@ -53,7 +53,7 @@ void LocalServer::_GET_metrics(void) {
} }
void LocalServer::_GET_measure(void) { void LocalServer::_GET_measure(void) {
String toSend = measure.toString(true, fwMode, wifiConnector.RSSI()); String toSend = measure.toString(true, fwMode, wifiConnector.RSSI(), *ag, config);
server.send(200, "application/json", toSend); server.send(200, "application/json", toSend);
} }

View File

@ -66,7 +66,7 @@ String OpenMetrics::getPayload(void) {
int pm03PCount = utils::getInvalidPmValue(); int pm03PCount = utils::getInvalidPmValue();
int co2 = utils::getInvalidCO2(); int co2 = utils::getInvalidCO2();
int atmpCompensated = utils::getInvalidTemperature(); int atmpCompensated = utils::getInvalidTemperature();
int rhumCompensated = utils::getInvalidHumidity(); int ahumCompensated = utils::getInvalidHumidity();
int tvoc = utils::getInvalidVOC(); int tvoc = utils::getInvalidVOC();
int tvocRaw = utils::getInvalidVOC(); int tvocRaw = utils::getInvalidVOC();
int nox = utils::getInvalidNOx(); int nox = utils::getInvalidNOx();
@ -76,12 +76,12 @@ String OpenMetrics::getPayload(void) {
_temp = measure.getFloat(Measurements::Temperature); _temp = measure.getFloat(Measurements::Temperature);
_hum = measure.getFloat(Measurements::Humidity); _hum = measure.getFloat(Measurements::Humidity);
atmpCompensated = _temp; atmpCompensated = _temp;
rhumCompensated = _hum; ahumCompensated = _hum;
} }
if (config.hasSensorPMS1) { if (config.hasSensorPMS1) {
pm01 = measure.get(Measurements::PM01); pm01 = measure.get(Measurements::PM01);
float correctedPm = measure.getCorrectedPM25(false, 1); float correctedPm = measure.getCorrectedPM25(*ag, config, false, 1);
pm25 = round(correctedPm); pm25 = round(correctedPm);
pm10 = measure.get(Measurements::PM10); pm10 = measure.get(Measurements::PM10);
pm03PCount = measure.get(Measurements::PM03_PC); pm03PCount = measure.get(Measurements::PM03_PC);
@ -191,12 +191,12 @@ String OpenMetrics::getPayload(void) {
"gauge", "percent"); "gauge", "percent");
add_metric_point("", String(_hum)); add_metric_point("", String(_hum));
} }
if (utils::isValidHumidity(rhumCompensated)) { if (utils::isValidHumidity(ahumCompensated)) {
add_metric("humidity_compensated", add_metric("humidity_compensated",
"The compensated relative humidity as measured by the " "The compensated relative humidity as measured by the "
"AirGradient SHT / PMS sensor", "AirGradient SHT / PMS sensor",
"gauge", "percent"); "gauge", "percent");
add_metric_point("", String(rhumCompensated)); add_metric_point("", String(ahumCompensated));
} }
response += "# EOF\n"; response += "# EOF\n";

View File

@ -55,7 +55,7 @@ CC BY-SA 4.0 Attribution-ShareAlike 4.0 International License
static AirGradient ag(DIY_PRO_INDOOR_V3_3); static AirGradient ag(DIY_PRO_INDOOR_V3_3);
static Configuration configuration(Serial); static Configuration configuration(Serial);
static AgApiClient apiClient(Serial, configuration); static AgApiClient apiClient(Serial, configuration);
static Measurements measurements(configuration); static Measurements measurements;
static OledDisplay oledDisplay(configuration, measurements, Serial); static OledDisplay oledDisplay(configuration, measurements, Serial);
static StateMachine stateMachine(oledDisplay, Serial, measurements, static StateMachine stateMachine(oledDisplay, Serial, measurements,
configuration); configuration);
@ -124,7 +124,6 @@ void setup() {
apiClient.setAirGradient(&ag); apiClient.setAirGradient(&ag);
openMetrics.setAirGradient(&ag); openMetrics.setAirGradient(&ag);
localServer.setAirGraident(&ag); localServer.setAirGraident(&ag);
measurements.setAirGradient(&ag);
/** Example set custom API root URL */ /** Example set custom API root URL */
// apiClient.setApiRoot("https://example.custom.api"); // apiClient.setApiRoot("https://example.custom.api");
@ -374,7 +373,7 @@ static void mqttHandle(void) {
} }
if (mqttClient.isConnected()) { if (mqttClient.isConnected()) {
String payload = measurements.toString(true, fwMode, wifiConnector.RSSI()); String payload = measurements.toString(true, fwMode, wifiConnector.RSSI(), ag, configuration);
String topic = "airgradient/readings/" + ag.deviceId(); String topic = "airgradient/readings/" + ag.deviceId();
if (mqttClient.publish(topic.c_str(), payload.c_str(), payload.length())) { if (mqttClient.publish(topic.c_str(), payload.c_str(), payload.length())) {
Serial.println("MQTT sync success"); Serial.println("MQTT sync success");
@ -580,7 +579,7 @@ static void sendDataToServer(void) {
return; return;
} }
String syncData = measurements.toString(false, fwMode, wifiConnector.RSSI()); String syncData = measurements.toString(false, fwMode, wifiConnector.RSSI(), ag, configuration);
if (apiClient.postToServer(syncData)) { if (apiClient.postToServer(syncData)) {
Serial.println(); Serial.println();
Serial.println( Serial.println(

View File

@ -53,7 +53,7 @@ void LocalServer::_GET_metrics(void) {
} }
void LocalServer::_GET_measure(void) { void LocalServer::_GET_measure(void) {
String toSend = measure.toString(true, fwMode, wifiConnector.RSSI()); String toSend = measure.toString(true, fwMode, wifiConnector.RSSI(), *ag, config);
server.send(200, "application/json", toSend); server.send(200, "application/json", toSend);
} }

View File

@ -66,7 +66,7 @@ String OpenMetrics::getPayload(void) {
int pm03PCount = utils::getInvalidPmValue(); int pm03PCount = utils::getInvalidPmValue();
int co2 = utils::getInvalidCO2(); int co2 = utils::getInvalidCO2();
int atmpCompensated = utils::getInvalidTemperature(); int atmpCompensated = utils::getInvalidTemperature();
int rhumCompensated = utils::getInvalidHumidity(); int ahumCompensated = utils::getInvalidHumidity();
int tvoc = utils::getInvalidVOC(); int tvoc = utils::getInvalidVOC();
int tvocRaw = utils::getInvalidVOC(); int tvocRaw = utils::getInvalidVOC();
int nox = utils::getInvalidNOx(); int nox = utils::getInvalidNOx();
@ -76,12 +76,12 @@ String OpenMetrics::getPayload(void) {
_temp = measure.getFloat(Measurements::Temperature); _temp = measure.getFloat(Measurements::Temperature);
_hum = measure.getFloat(Measurements::Humidity); _hum = measure.getFloat(Measurements::Humidity);
atmpCompensated = _temp; atmpCompensated = _temp;
rhumCompensated = _hum; ahumCompensated = _hum;
} }
if (config.hasSensorPMS1) { if (config.hasSensorPMS1) {
pm01 = measure.get(Measurements::PM01); pm01 = measure.get(Measurements::PM01);
float correctedPm = measure.getCorrectedPM25(false, 1); float correctedPm = measure.getCorrectedPM25(*ag, config, false, 1);
pm25 = round(correctedPm); pm25 = round(correctedPm);
pm10 = measure.get(Measurements::PM10); pm10 = measure.get(Measurements::PM10);
pm03PCount = measure.get(Measurements::PM03_PC); pm03PCount = measure.get(Measurements::PM03_PC);
@ -192,12 +192,12 @@ String OpenMetrics::getPayload(void) {
"gauge", "percent"); "gauge", "percent");
add_metric_point("", String(_hum)); add_metric_point("", String(_hum));
} }
if (utils::isValidHumidity(rhumCompensated)) { if (utils::isValidHumidity(ahumCompensated)) {
add_metric("humidity_compensated", add_metric("humidity_compensated",
"The compensated relative humidity as measured by the " "The compensated relative humidity as measured by the "
"AirGradient SHT / PMS sensor", "AirGradient SHT / PMS sensor",
"gauge", "percent"); "gauge", "percent");
add_metric_point("", String(rhumCompensated)); add_metric_point("", String(ahumCompensated));
} }
response += "# EOF\n"; response += "# EOF\n";

View File

@ -55,7 +55,7 @@ CC BY-SA 4.0 Attribution-ShareAlike 4.0 International License
static AirGradient ag(DIY_PRO_INDOOR_V4_2); static AirGradient ag(DIY_PRO_INDOOR_V4_2);
static Configuration configuration(Serial); static Configuration configuration(Serial);
static AgApiClient apiClient(Serial, configuration); static AgApiClient apiClient(Serial, configuration);
static Measurements measurements(configuration); static Measurements measurements;
static OledDisplay oledDisplay(configuration, measurements, Serial); static OledDisplay oledDisplay(configuration, measurements, Serial);
static StateMachine stateMachine(oledDisplay, Serial, measurements, static StateMachine stateMachine(oledDisplay, Serial, measurements,
configuration); configuration);
@ -125,7 +125,6 @@ void setup() {
apiClient.setAirGradient(&ag); apiClient.setAirGradient(&ag);
openMetrics.setAirGradient(&ag); openMetrics.setAirGradient(&ag);
localServer.setAirGraident(&ag); localServer.setAirGraident(&ag);
measurements.setAirGradient(&ag);
/** Example set custom API root URL */ /** Example set custom API root URL */
// apiClient.setApiRoot("https://example.custom.api"); // apiClient.setApiRoot("https://example.custom.api");
@ -397,7 +396,7 @@ static void mqttHandle(void) {
} }
if (mqttClient.isConnected()) { if (mqttClient.isConnected()) {
String payload = measurements.toString(true, fwMode, wifiConnector.RSSI()); String payload = measurements.toString(true, fwMode, wifiConnector.RSSI(), ag, configuration);
String topic = "airgradient/readings/" + ag.deviceId(); String topic = "airgradient/readings/" + ag.deviceId();
if (mqttClient.publish(topic.c_str(), payload.c_str(), payload.length())) { if (mqttClient.publish(topic.c_str(), payload.c_str(), payload.length())) {
Serial.println("MQTT sync success"); Serial.println("MQTT sync success");
@ -621,7 +620,7 @@ static void sendDataToServer(void) {
return; return;
} }
String syncData = measurements.toString(false, fwMode, wifiConnector.RSSI()); String syncData = measurements.toString(false, fwMode, wifiConnector.RSSI(), ag, configuration);
if (apiClient.postToServer(syncData)) { if (apiClient.postToServer(syncData)) {
Serial.println(); Serial.println();
Serial.println( Serial.println(

View File

@ -53,7 +53,7 @@ void LocalServer::_GET_metrics(void) {
} }
void LocalServer::_GET_measure(void) { void LocalServer::_GET_measure(void) {
String toSend = measure.toString(true, fwMode, wifiConnector.RSSI()); String toSend = measure.toString(true, fwMode, wifiConnector.RSSI(), *ag, config);
server.send(200, "application/json", toSend); server.send(200, "application/json", toSend);
} }

View File

@ -66,7 +66,7 @@ String OpenMetrics::getPayload(void) {
int pm03PCount = utils::getInvalidPmValue(); int pm03PCount = utils::getInvalidPmValue();
int co2 = utils::getInvalidCO2(); int co2 = utils::getInvalidCO2();
int atmpCompensated = utils::getInvalidTemperature(); int atmpCompensated = utils::getInvalidTemperature();
int rhumCompensated = utils::getInvalidHumidity(); int ahumCompensated = utils::getInvalidHumidity();
int tvoc = utils::getInvalidVOC(); int tvoc = utils::getInvalidVOC();
int tvocRaw = utils::getInvalidVOC(); int tvocRaw = utils::getInvalidVOC();
int nox = utils::getInvalidNOx(); int nox = utils::getInvalidNOx();
@ -76,12 +76,12 @@ String OpenMetrics::getPayload(void) {
_temp = measure.getFloat(Measurements::Temperature); _temp = measure.getFloat(Measurements::Temperature);
_hum = measure.getFloat(Measurements::Humidity); _hum = measure.getFloat(Measurements::Humidity);
atmpCompensated = _temp; atmpCompensated = _temp;
rhumCompensated = _hum; ahumCompensated = _hum;
} }
if (config.hasSensorPMS1) { if (config.hasSensorPMS1) {
pm01 = measure.get(Measurements::PM01); pm01 = measure.get(Measurements::PM01);
float correctedPm = measure.getCorrectedPM25(false, 1); float correctedPm = measure.getCorrectedPM25(*ag, config, false, 1);
pm25 = round(correctedPm); pm25 = round(correctedPm);
pm10 = measure.get(Measurements::PM10); pm10 = measure.get(Measurements::PM10);
pm03PCount = measure.get(Measurements::PM03_PC); pm03PCount = measure.get(Measurements::PM03_PC);
@ -191,12 +191,12 @@ String OpenMetrics::getPayload(void) {
"gauge", "percent"); "gauge", "percent");
add_metric_point("", String(_hum)); add_metric_point("", String(_hum));
} }
if (utils::isValidHumidity(rhumCompensated)) { if (utils::isValidHumidity(ahumCompensated)) {
add_metric("humidity_compensated", add_metric("humidity_compensated",
"The compensated relative humidity as measured by the " "The compensated relative humidity as measured by the "
"AirGradient SHT / PMS sensor", "AirGradient SHT / PMS sensor",
"gauge", "percent"); "gauge", "percent");
add_metric_point("", String(rhumCompensated)); add_metric_point("", String(ahumCompensated));
} }
response += "# EOF\n"; response += "# EOF\n";

View File

@ -64,7 +64,7 @@ void LocalServer::_GET_metrics(void) {
} }
void LocalServer::_GET_measure(void) { void LocalServer::_GET_measure(void) {
String toSend = measure.toString(true, fwMode, wifiConnector.RSSI()); String toSend = measure.toString(true, fwMode, wifiConnector.RSSI(), *ag, config);
server.send(200, "application/json", toSend); server.send(200, "application/json", toSend);
} }

View File

@ -76,7 +76,7 @@ static MqttClient mqttClient(Serial);
static TaskHandle_t mqttTask = NULL; static TaskHandle_t mqttTask = NULL;
static Configuration configuration(Serial); static Configuration configuration(Serial);
static AgApiClient apiClient(Serial, configuration); static AgApiClient apiClient(Serial, configuration);
static Measurements measurements(configuration); static Measurements measurements;
static AirGradient *ag; static AirGradient *ag;
static OledDisplay oledDisplay(configuration, measurements, Serial); static OledDisplay oledDisplay(configuration, measurements, Serial);
static StateMachine stateMachine(oledDisplay, Serial, measurements, static StateMachine stateMachine(oledDisplay, Serial, measurements,
@ -164,7 +164,6 @@ void setup() {
apiClient.setAirGradient(ag); apiClient.setAirGradient(ag);
openMetrics.setAirGradient(ag); openMetrics.setAirGradient(ag);
localServer.setAirGraident(ag); localServer.setAirGraident(ag);
measurements.setAirGradient(ag);
/** Example set custom API root URL */ /** Example set custom API root URL */
// apiClient.setApiRoot("https://example.custom.api"); // apiClient.setApiRoot("https://example.custom.api");
@ -369,7 +368,8 @@ static void createMqttTask(void) {
/** Send data */ /** Send data */
if (mqttClient.isConnected()) { if (mqttClient.isConnected()) {
String payload = measurements.toString(true, fwMode, wifiConnector.RSSI()); String payload =
measurements.toString(true, fwMode, wifiConnector.RSSI(), *ag, configuration);
String topic = "airgradient/readings/" + ag->deviceId(); String topic = "airgradient/readings/" + ag->deviceId();
if (mqttClient.publish(topic.c_str(), payload.c_str(), if (mqttClient.publish(topic.c_str(), payload.c_str(),
@ -1145,7 +1145,7 @@ static void sendDataToServer(void) {
return; return;
} }
String syncData = measurements.toString(false, fwMode, wifiConnector.RSSI()); String syncData = measurements.toString(false, fwMode, wifiConnector.RSSI(), *ag, configuration);
if (apiClient.postToServer(syncData)) { if (apiClient.postToServer(syncData)) {
Serial.println(); Serial.println();
Serial.println( Serial.println(

View File

@ -66,7 +66,7 @@ String OpenMetrics::getPayload(void) {
int pm03PCount = utils::getInvalidPmValue(); int pm03PCount = utils::getInvalidPmValue();
int co2 = utils::getInvalidCO2(); int co2 = utils::getInvalidCO2();
int atmpCompensated = utils::getInvalidTemperature(); int atmpCompensated = utils::getInvalidTemperature();
int rhumCompensated = utils::getInvalidHumidity(); int ahumCompensated = utils::getInvalidHumidity();
int tvoc = utils::getInvalidVOC(); int tvoc = utils::getInvalidVOC();
int tvocRaw = utils::getInvalidVOC(); int tvocRaw = utils::getInvalidVOC();
int nox = utils::getInvalidNOx(); int nox = utils::getInvalidNOx();
@ -81,8 +81,8 @@ String OpenMetrics::getPayload(void) {
measure.getFloat(Measurements::Humidity, 2)) / measure.getFloat(Measurements::Humidity, 2)) /
2.0f; 2.0f;
pm01 = (measure.get(Measurements::PM01, 1) + measure.get(Measurements::PM01, 2)) / 2.0f; pm01 = (measure.get(Measurements::PM01, 1) + measure.get(Measurements::PM01, 2)) / 2.0f;
float correctedPm25_1 = measure.getCorrectedPM25(false, 1); float correctedPm25_1 = measure.getCorrectedPM25(*ag, config, false, 1);
float correctedPm25_2 = measure.getCorrectedPM25(false, 2); float correctedPm25_2 = measure.getCorrectedPM25(*ag, config, false, 2);
float correctedPm25 = (correctedPm25_1 + correctedPm25_2) / 2.0f; float correctedPm25 = (correctedPm25_1 + correctedPm25_2) / 2.0f;
pm25 = round(correctedPm25); pm25 = round(correctedPm25);
pm10 = (measure.get(Measurements::PM10, 1) + measure.get(Measurements::PM10, 2)) / 2.0f; pm10 = (measure.get(Measurements::PM10, 1) + measure.get(Measurements::PM10, 2)) / 2.0f;
@ -97,7 +97,7 @@ String OpenMetrics::getPayload(void) {
if (config.hasSensorPMS1) { if (config.hasSensorPMS1) {
pm01 = measure.get(Measurements::PM01); pm01 = measure.get(Measurements::PM01);
float correctedPm = measure.getCorrectedPM25(false, 1); float correctedPm = measure.getCorrectedPM25(*ag, config, false, 1);
pm25 = round(correctedPm); pm25 = round(correctedPm);
pm10 = measure.get(Measurements::PM10); pm10 = measure.get(Measurements::PM10);
pm03PCount = measure.get(Measurements::PM03_PC); pm03PCount = measure.get(Measurements::PM03_PC);
@ -107,7 +107,7 @@ String OpenMetrics::getPayload(void) {
_temp = measure.getFloat(Measurements::Temperature, 1); _temp = measure.getFloat(Measurements::Temperature, 1);
_hum = measure.getFloat(Measurements::Humidity, 1); _hum = measure.getFloat(Measurements::Humidity, 1);
pm01 = measure.get(Measurements::PM01, 1); pm01 = measure.get(Measurements::PM01, 1);
float correctedPm = measure.getCorrectedPM25(false, 1); float correctedPm = measure.getCorrectedPM25(*ag, config, false, 1);
pm25 = round(correctedPm); pm25 = round(correctedPm);
pm10 = measure.get(Measurements::PM10, 1); pm10 = measure.get(Measurements::PM10, 1);
pm03PCount = measure.get(Measurements::PM03_PC, 1); pm03PCount = measure.get(Measurements::PM03_PC, 1);
@ -116,7 +116,7 @@ String OpenMetrics::getPayload(void) {
_temp = measure.getFloat(Measurements::Temperature, 2); _temp = measure.getFloat(Measurements::Temperature, 2);
_hum = measure.getFloat(Measurements::Humidity, 2); _hum = measure.getFloat(Measurements::Humidity, 2);
pm01 = measure.get(Measurements::PM01, 2); pm01 = measure.get(Measurements::PM01, 2);
float correctedPm = measure.getCorrectedPM25(false, 2); float correctedPm = measure.getCorrectedPM25(*ag, config, false, 2);
pm25 = round(correctedPm); pm25 = round(correctedPm);
pm10 = measure.get(Measurements::PM10, 2); pm10 = measure.get(Measurements::PM10, 2);
pm03PCount = measure.get(Measurements::PM03_PC, 2); pm03PCount = measure.get(Measurements::PM03_PC, 2);
@ -137,15 +137,11 @@ String OpenMetrics::getPayload(void) {
/** Get temperature and humidity compensated */ /** Get temperature and humidity compensated */
if (ag->isOne()) { if (ag->isOne()) {
atmpCompensated = round(measure.getCorrectedTempHum(Measurements::Temperature)); atmpCompensated = _temp;
rhumCompensated = round(measure.getCorrectedTempHum(Measurements::Humidity)); ahumCompensated = _hum;
} else { } else {
atmpCompensated = round((measure.getCorrectedTempHum(Measurements::Temperature, 1) + atmpCompensated = ag->pms5003t_1.compensateTemp(_temp);
measure.getCorrectedTempHum(Measurements::Temperature, 2)) / ahumCompensated = ag->pms5003t_1.compensateHum(_hum);
2.0f);
rhumCompensated = round((measure.getCorrectedTempHum(Measurements::Humidity, 1) +
measure.getCorrectedTempHum(Measurements::Humidity, 2)) /
2.0f);
} }
// Add measurements that valid to the metrics // Add measurements that valid to the metrics
@ -238,11 +234,11 @@ String OpenMetrics::getPayload(void) {
"gauge", "percent"); "gauge", "percent");
add_metric_point("", String(_hum)); add_metric_point("", String(_hum));
} }
if (utils::isValidHumidity(rhumCompensated)) { if (utils::isValidHumidity(ahumCompensated)) {
add_metric("humidity_compensated", add_metric("humidity_compensated",
"The compensated relative humidity as measured by the AirGradient SHT / PMS sensor", "The compensated relative humidity as measured by the AirGradient SHT / PMS sensor",
"gauge", "percent"); "gauge", "percent");
add_metric_point("", String(rhumCompensated)); add_metric_point("", String(ahumCompensated));
} }
response += "# EOF\n"; response += "# EOF\n";

View File

@ -33,13 +33,6 @@ const char *PM_CORRECTION_ALGORITHM_NAMES[] = {
[SLR_PMS5003_20240104] = "slr_PMS5003_20240104", [SLR_PMS5003_20240104] = "slr_PMS5003_20240104",
}; };
const char *TEMP_HUM_CORRECTION_ALGORITHM_NAMES[] = {
[COR_ALGO_TEMP_HUM_UNKNOWN] = "-", // This is only to pass "non-trivial designated initializers" error
[COR_ALGO_TEMP_HUM_NONE] = "none",
[COR_ALGO_TEMP_HUM_AG_PMS5003T_2024] = "ag_pms5003t_2024",
[COR_ALGO_TEMP_HUM_SLR_CUSTOM] = "custom",
};
#define JSON_PROP_NAME(name) jprop_##name #define JSON_PROP_NAME(name) jprop_##name
#define JSON_PROP_DEF(name) const char *JSON_PROP_NAME(name) = #name #define JSON_PROP_DEF(name) const char *JSON_PROP_NAME(name) = #name
@ -61,8 +54,6 @@ JSON_PROP_DEF(ledBarTestRequested);
JSON_PROP_DEF(offlineMode); JSON_PROP_DEF(offlineMode);
JSON_PROP_DEF(monitorDisplayCompensatedValues); JSON_PROP_DEF(monitorDisplayCompensatedValues);
JSON_PROP_DEF(corrections); JSON_PROP_DEF(corrections);
JSON_PROP_DEF(atmp);
JSON_PROP_DEF(rhum);
#define jprop_model_default "" #define jprop_model_default ""
#define jprop_country_default "TH" #define jprop_country_default "TH"
@ -126,23 +117,9 @@ PMCorrectionAlgorithm Configuration::matchPmAlgorithm(String algorithm) {
return result; return result;
} }
TempHumCorrectionAlgorithm Configuration::matchTempHumAlgorithm(String algorithm) {
// Get the actual size of the enum
const int enumSize = static_cast<int>(COR_ALGO_TEMP_HUM_SLR_CUSTOM);
TempHumCorrectionAlgorithm result = COR_ALGO_TEMP_HUM_UNKNOWN;
// Loop through enum values
for (size_t enumVal = 0; enumVal <= enumSize; enumVal++) {
if (algorithm == TEMP_HUM_CORRECTION_ALGORITHM_NAMES[enumVal]) {
result = static_cast<TempHumCorrectionAlgorithm>(enumVal);
}
}
return result;
}
bool Configuration::updatePmCorrection(JSONVar &json) { bool Configuration::updatePmCorrection(JSONVar &json) {
if (!json.hasOwnProperty("corrections")) { if (!json.hasOwnProperty("corrections")) {
// TODO: need to response message?
Serial.println("corrections not found"); Serial.println("corrections not found");
return false; return false;
} }
@ -228,88 +205,6 @@ bool Configuration::updatePmCorrection(JSONVar &json) {
return true; return true;
} }
bool Configuration::updateTempHumCorrection(JSONVar &json, TempHumCorrection &target,
const char *correctionName) {
if (!json.hasOwnProperty(jprop_corrections)) {
return false;
}
JSONVar corrections = json[jprop_corrections];
if (!corrections.hasOwnProperty(correctionName)) {
Serial.println("pm02 not found");
logWarning(String(correctionName) + " correction field not found on configuration");
return false;
}
JSONVar correctionTarget = corrections[correctionName];
if (!correctionTarget.hasOwnProperty("correctionAlgorithm")) {
Serial.println("correctionAlgorithm not found");
return false;
}
String algorithm = correctionTarget["correctionAlgorithm"];
TempHumCorrectionAlgorithm algo = matchTempHumAlgorithm(algorithm);
if (algo == COR_ALGO_TEMP_HUM_UNKNOWN) {
logInfo("Uknown temp/hum algorithm");
return false;
}
logInfo(String(correctionName) + " correction algorithm: " + algorithm);
// If algo is None or Standard, then no need to check slr
// But first check if target correction different from algo
if (algo == COR_ALGO_TEMP_HUM_NONE || algo == COR_ALGO_TEMP_HUM_AG_PMS5003T_2024) {
if (target.algorithm != algo) {
// Deep copy corrections from root to jconfig, so it will be saved later
jconfig[jprop_corrections][correctionName]["correctionAlgorithm"] = algorithm;
jconfig[jprop_corrections][correctionName]["slr"] = JSON.parse("{}"); // Clear slr
// Update pmCorrection with new values
target.algorithm = algo;
target.changed = true;
logInfo(String(correctionName) + " correction updated");
return true;
}
return false;
}
// Check if correction.target (atmp or rhum) has slr object
if (!correctionTarget.hasOwnProperty("slr")) {
logWarning(String(correctionName) + " slr not found");
return false;
}
JSONVar slr = correctionTarget["slr"];
// Validate required slr properties exist
if (!slr.hasOwnProperty("intercept") || !slr.hasOwnProperty("scalingFactor")) {
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 target correciont
if (target.algorithm == algo && target.intercept == intercept &&
target.scalingFactor == scalingFactor) {
return false; // No changes needed
}
// Deep copy corrections from root to jconfig, so it will be saved later
jconfig[jprop_corrections] = corrections;
// Update target with new values
target.algorithm = algo;
target.intercept = intercept;
target.scalingFactor = scalingFactor;
target.changed = true;
// Correction values were updated
logInfo(String(correctionName) + " correction updated");
return true;
}
/** /**
* @brief Save configure to device storage (EEPROM) * @brief Save configure to device storage (EEPROM)
* *
@ -897,21 +792,11 @@ bool Configuration::parse(String data, bool isLocal) {
} }
} }
// PM2.5 Corrections // Corrections
if (updatePmCorrection(root)) { if (updatePmCorrection(root)) {
changed = true; changed = true;
} }
// Temperature correction
if (updateTempHumCorrection(root, tempCorrection, jprop_atmp)) {
changed = true;
}
// Relative humidity correction
if (updateTempHumCorrection(root, rhumCorrection, jprop_rhum)) {
changed = true;
}
if (changed) { if (changed) {
updated = true; updated = true;
saveConfig(); saveConfig();
@ -1363,31 +1248,15 @@ void Configuration::toConfig(const char *buf) {
jprop_monitorDisplayCompensatedValues_default; jprop_monitorDisplayCompensatedValues_default;
} }
// PM2.5 correction
/// Set default first before parsing local config // Set default first before parsing local config
pmCorrection.algorithm = PMCorrectionAlgorithm::None; pmCorrection.algorithm = PMCorrectionAlgorithm::None;
pmCorrection.intercept = 0; pmCorrection.intercept = 0;
pmCorrection.scalingFactor = 0; pmCorrection.scalingFactor = 0;
pmCorrection.useEPA = false; pmCorrection.useEPA = false;
/// Load correction from saved config // Load correction from saved config
updatePmCorrection(jconfig); updatePmCorrection(jconfig);
// Temperature correction
/// Set default first before parsing local config
tempCorrection.algorithm = COR_ALGO_TEMP_HUM_NONE;
tempCorrection.intercept = 0;
tempCorrection.scalingFactor = 0;
/// Load correction from saved config
updateTempHumCorrection(jconfig, tempCorrection, jprop_atmp);
// Relative humidity correction
/// Set default first before parsing local config
rhumCorrection.algorithm = COR_ALGO_TEMP_HUM_NONE;
rhumCorrection.intercept = 0;
rhumCorrection.scalingFactor = 0;
/// Load correction from saved config
updateTempHumCorrection(jconfig, rhumCorrection, jprop_rhum);
if (changed) { if (changed) {
saveConfig(); saveConfig();
} }
@ -1508,8 +1377,6 @@ bool Configuration::isPMCorrectionEnabled(void) {
return true; return true;
} }
Configuration::PMCorrection Configuration::getPMCorrection(void) { return pmCorrection; } Configuration::PMCorrection Configuration::getPMCorrection(void) {
return pmCorrection;
Configuration::TempHumCorrection Configuration::getTempCorrection(void) { return tempCorrection; } }
Configuration::TempHumCorrection Configuration::getHumCorrection(void) { return rhumCorrection; }

View File

@ -17,13 +17,6 @@ public:
bool changed; bool changed;
}; };
struct TempHumCorrection {
TempHumCorrectionAlgorithm algorithm;
float intercept;
float scalingFactor;
bool changed;
};
private: private:
bool co2CalibrationRequested; bool co2CalibrationRequested;
bool ledBarTestRequested; bool ledBarTestRequested;
@ -37,17 +30,12 @@ private:
bool _offlineMode = false; bool _offlineMode = false;
bool _ledBarModeChanged = false; bool _ledBarModeChanged = false;
PMCorrection pmCorrection; PMCorrection pmCorrection;
TempHumCorrection tempCorrection;
TempHumCorrection rhumCorrection;
AirGradient* ag; AirGradient* ag;
String getLedBarModeName(LedBarMode mode); String getLedBarModeName(LedBarMode mode);
PMCorrectionAlgorithm matchPmAlgorithm(String algorithm); PMCorrectionAlgorithm matchPmAlgorithm(String algorithm);
TempHumCorrectionAlgorithm matchTempHumAlgorithm(String algorithm);
bool updatePmCorrection(JSONVar &json); bool updatePmCorrection(JSONVar &json);
bool updateTempHumCorrection(JSONVar &json, TempHumCorrection &target,
const char *correctionName);
void saveConfig(void); void saveConfig(void);
void loadConfig(void); void loadConfig(void);
void defaultConfig(void); void defaultConfig(void);
@ -58,7 +46,7 @@ private:
void configLogInfo(String name, String fromValue, String toValue); void configLogInfo(String name, String fromValue, String toValue);
String getPMStandardString(bool usaqi); String getPMStandardString(bool usaqi);
String getAbcDayString(int value); String getAbcDayString(int value);
void toConfig(const char *buf); void toConfig(const char* buf);
public: public:
Configuration(Stream &debugLog); Configuration(Stream &debugLog);
@ -111,8 +99,6 @@ public:
bool isPMCorrectionChanged(void); bool isPMCorrectionChanged(void);
bool isPMCorrectionEnabled(void); bool isPMCorrectionEnabled(void);
PMCorrection getPMCorrection(void); PMCorrection getPMCorrection(void);
TempHumCorrection getTempCorrection(void);
TempHumCorrection getHumCorrection(void);
}; };
#endif /** _AG_CONFIG_H_ */ #endif /** _AG_CONFIG_H_ */

View File

@ -12,7 +12,7 @@
*/ */
void OledDisplay::showTempHum(bool hasStatus, char *buf, int buf_size) { void OledDisplay::showTempHum(bool hasStatus, char *buf, int buf_size) {
/** Temperature */ /** Temperature */
float temp = value.getCorrectedTempHum(Measurements::Temperature, 1); float temp = value.getAverage(Measurements::Temperature);
if (utils::isValidTemperature(temp)) { if (utils::isValidTemperature(temp)) {
float t = 0.0f; float t = 0.0f;
if (config.isTemperatureUnitInF()) { if (config.isTemperatureUnitInF()) {
@ -44,7 +44,7 @@ void OledDisplay::showTempHum(bool hasStatus, char *buf, int buf_size) {
DISP()->drawUTF8(1, 10, buf); DISP()->drawUTF8(1, 10, buf);
/** Show humidity */ /** Show humidity */
int rhum = round(value.getCorrectedTempHum(Measurements::Humidity, 1)); int rhum = round(value.getAverage(Measurements::Humidity));
if (utils::isValidHumidity(rhum)) { if (utils::isValidHumidity(rhum)) {
snprintf(buf, buf_size, "%d%%", rhum); snprintf(buf, buf_size, "%d%%", rhum);
} else { } else {
@ -316,7 +316,7 @@ void OledDisplay::showDashboard(const char *status) {
int pm25 = round(value.getAverage(Measurements::PM25)); int pm25 = round(value.getAverage(Measurements::PM25));
if (utils::isValidPm(pm25)) { if (utils::isValidPm(pm25)) {
if (config.hasSensorSHT && config.isPMCorrectionEnabled()) { if (config.hasSensorSHT && config.isPMCorrectionEnabled()) {
pm25 = round(value.getCorrectedPM25(true)); pm25 = round(value.getCorrectedPM25(*ag, config, true));
} }
if (config.isPmStandardInUSAQI()) { if (config.isPmStandardInUSAQI()) {
sprintf(strBuf, "%d", ag->pms5003.convertPm25ToUsAqi(pm25)); sprintf(strBuf, "%d", ag->pms5003.convertPm25ToUsAqi(pm25));
@ -377,7 +377,7 @@ void OledDisplay::showDashboard(const char *status) {
/** Set PM */ /** Set PM */
int pm25 = round(value.getAverage(Measurements::PM25)); int pm25 = round(value.getAverage(Measurements::PM25));
if (config.hasSensorSHT && config.isPMCorrectionEnabled()) { if (config.hasSensorSHT && config.isPMCorrectionEnabled()) {
pm25 = round(value.getCorrectedPM25(true)); pm25 = round(value.getCorrectedPM25(*ag, config, true));
} }
ag->display.setCursor(0, 12); ag->display.setCursor(0, 12);
@ -389,7 +389,7 @@ void OledDisplay::showDashboard(const char *status) {
ag->display.setText(strBuf); ag->display.setText(strBuf);
/** Set temperature and humidity */ /** Set temperature and humidity */
float temp = value.getCorrectedTempHum(Measurements::Temperature, 1); float temp = value.getAverage(Measurements::Temperature);
if (utils::isValidTemperature(temp)) { if (utils::isValidTemperature(temp)) {
if (config.isTemperatureUnitInF()) { if (config.isTemperatureUnitInF()) {
snprintf(strBuf, sizeof(strBuf), "T:%0.1f F", utils::degreeC_To_F(temp)); snprintf(strBuf, sizeof(strBuf), "T:%0.1f F", utils::degreeC_To_F(temp));
@ -407,7 +407,7 @@ void OledDisplay::showDashboard(const char *status) {
ag->display.setCursor(0, 24); ag->display.setCursor(0, 24);
ag->display.setText(strBuf); ag->display.setText(strBuf);
int rhum = round(value.getCorrectedTempHum(Measurements::Humidity, 1)); int rhum = round(value.getAverage(Measurements::Humidity));
if (utils::isValidHumidity(rhum)) { if (utils::isValidHumidity(rhum)) {
snprintf(strBuf, sizeof(strBuf), "H:%d %%", rhum); snprintf(strBuf, sizeof(strBuf), "H:%d %%", rhum);
} else { } else {

View File

@ -174,7 +174,7 @@ int StateMachine::pm25handleLeds(void) {
int pm25Value = round(value.getAverage(Measurements::PM25)); int pm25Value = round(value.getAverage(Measurements::PM25));
if (config.hasSensorSHT && config.isPMCorrectionEnabled()) { if (config.hasSensorSHT && config.isPMCorrectionEnabled()) {
pm25Value = round(value.getCorrectedPM25(true)); pm25Value = round(value.getCorrectedPM25(*ag, config, true));
} }
if (pm25Value <= 5) { if (pm25Value <= 5) {

View File

@ -27,14 +27,12 @@
#define json_prop_noxRaw "noxRaw" #define json_prop_noxRaw "noxRaw"
#define json_prop_co2 "rco2" #define json_prop_co2 "rco2"
Measurements::Measurements(Configuration &config) : config(config) { Measurements::Measurements() {
#ifndef ESP8266 #ifndef ESP8266
_resetReason = (int)ESP_RST_UNKNOWN; _resetReason = (int)ESP_RST_UNKNOWN;
#endif #endif
} }
void Measurements::setAirGradient(AirGradient *ag) { this->ag = ag; }
void Measurements::maxPeriod(MeasurementType type, int max) { void Measurements::maxPeriod(MeasurementType type, int max) {
switch (type) { switch (type) {
case Temperature: case Temperature:
@ -408,12 +406,12 @@ float Measurements::getAverage(MeasurementType type, int ch) {
// Sanity check to validate channel, assert if invalid // Sanity check to validate channel, assert if invalid
validateChannel(ch); validateChannel(ch);
bool undefined = false;
// Follow array indexing just for get address of the value type // Follow array indexing just for get address of the value type
ch = ch - 1; ch = ch - 1;
// Define data point source. Data type doesn't matter because only to get the average value // Define data point source. Data type doesn't matter because only to get the average value
FloatValue *temporary = nullptr;
Update update;
float measurementAverage; float measurementAverage;
switch (type) { switch (type) {
case CO2: case CO2:
@ -436,12 +434,12 @@ float Measurements::getAverage(MeasurementType type, int ch) {
break; break;
default: default:
// Invalidate, measurements type not handled // Invalidate, measurements type not handled
undefined = true; measurementAverage = -1000;
break; break;
}; };
// Sanity check if measurement type is not defined // Sanity check if measurement type is not defined
if (undefined) { if (measurementAverage == -1000) {
Serial.printf("ERROR! %s is not defined on get average value function\n", measurementTypeStr(type).c_str()); Serial.printf("ERROR! %s is not defined on get average value function\n", measurementTypeStr(type).c_str());
delay(1000); delay(1000);
assert(0); assert(0);
@ -537,70 +535,7 @@ void Measurements::validateChannel(int ch) {
} }
} }
float Measurements::getCorrectedTempHum(MeasurementType type, int ch, bool forceCorrection) { float Measurements::getCorrectedPM25(AirGradient &ag, Configuration &config, bool useAvg, 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;
bool undefined = false;
float rawValue;
Configuration::TempHumCorrection correction;
switch (type) {
case Temperature: {
rawValue = _temperature[ch].update.avg;
Configuration::TempHumCorrection tmp = config.getTempCorrection();
// Apply 'standard' correction if its defined or correction forced
if (tmp.algorithm == TempHumCorrectionAlgorithm::COR_ALGO_TEMP_HUM_AG_PMS5003T_2024) {
return ag->pms5003t_1.compensateTemp(rawValue);
} else if (tmp.algorithm == TempHumCorrectionAlgorithm::COR_ALGO_TEMP_HUM_NONE && forceCorrection) {
return ag->pms5003t_1.compensateTemp(rawValue);
}
correction.algorithm = tmp.algorithm;
correction.intercept = tmp.intercept;
correction.scalingFactor = tmp.scalingFactor;
break;
}
case Humidity: {
rawValue = _humidity[ch].update.avg;
Configuration::TempHumCorrection tmp = config.getHumCorrection();
// Apply 'standard' correction if its defined or correction forced
if (tmp.algorithm == TempHumCorrectionAlgorithm::COR_ALGO_TEMP_HUM_AG_PMS5003T_2024) {
return ag->pms5003t_1.compensateHum(rawValue);
} else if (tmp.algorithm == TempHumCorrectionAlgorithm::COR_ALGO_TEMP_HUM_NONE && forceCorrection) {
return ag->pms5003t_1.compensateHum(rawValue);
}
correction.algorithm = tmp.algorithm;
correction.intercept = tmp.intercept;
correction.scalingFactor = tmp.scalingFactor;
break;
}
default:
// Should not be called for other measurements
delay(1000);
assert(0);
}
// Use raw if correction not defined
if (correction.algorithm == TempHumCorrectionAlgorithm::COR_ALGO_TEMP_HUM_NONE ||
correction.algorithm == TempHumCorrectionAlgorithm::COR_ALGO_TEMP_HUM_UNKNOWN) {
return rawValue;
}
// Custom correction constants
float corrected = (rawValue * correction.scalingFactor) + correction.intercept;
Serial.println("Custom correction applied");
return corrected;
}
float Measurements::getCorrectedPM25(bool useAvg, int ch) {
float pm25; float pm25;
float corrected; float corrected;
float humidity; float humidity;
@ -625,15 +560,15 @@ float Measurements::getCorrectedPM25(bool useAvg, int ch) {
corrected = pm25; corrected = pm25;
break; break;
case PMCorrectionAlgorithm::EPA_2021: case PMCorrectionAlgorithm::EPA_2021:
corrected = ag->pms5003.compensate(pm25, humidity); corrected = ag.pms5003.compensate(pm25, humidity);
break; break;
default: { default: {
// All SLR correction using the same flow, hence default condition // All SLR correction using the same flow, hence default condition
corrected = ag->pms5003.slrCorrection(pm25, pm003Count, pmCorrection.scalingFactor, corrected = ag.pms5003.slrCorrection(pm25, pm003Count, pmCorrection.scalingFactor,
pmCorrection.intercept); pmCorrection.intercept);
if (pmCorrection.useEPA) { if (pmCorrection.useEPA) {
// Add EPA compensation on top of SLR // Add EPA compensation on top of SLR
corrected = ag->pms5003.compensate(corrected, humidity); corrected = ag.pms5003.compensate(corrected, humidity);
} }
} }
} }
@ -641,33 +576,34 @@ float Measurements::getCorrectedPM25(bool useAvg, int ch) {
return corrected; return corrected;
} }
String Measurements::toString(bool localServer, AgFirmwareMode fwMode, int rssi) { String Measurements::toString(bool localServer, AgFirmwareMode fwMode, int rssi, AirGradient &ag,
Configuration &config) {
JSONVar root; JSONVar root;
if (ag->isOne() || (ag->isPro4_2()) || ag->isPro3_3() || ag->isBasic()) { if (ag.isOne() || (ag.isPro4_2()) || ag.isPro3_3() || ag.isBasic()) {
root = buildIndoor(localServer); root = buildIndoor(localServer, ag, config);
} else { } else {
root = buildOutdoor(localServer, fwMode); root = buildOutdoor(localServer, fwMode, ag, config);
} }
// CO2 // CO2
if (config.hasSensorS8 && utils::isValidCO2(_co2.update.avg)) { if (config.hasSensorS8 && utils::isValidCO2(_co2.update.avg)) {
root[json_prop_co2] = ag->round2(_co2.update.avg); root[json_prop_co2] = ag.round2(_co2.update.avg);
} }
/// TVOx and NOx /// TVOx and NOx
if (config.hasSensorSGP) { if (config.hasSensorSGP) {
if (utils::isValidVOC(_tvoc.update.avg)) { if (utils::isValidVOC(_tvoc.update.avg)) {
root[json_prop_tvoc] = ag->round2(_tvoc.update.avg); root[json_prop_tvoc] = ag.round2(_tvoc.update.avg);
} }
if (utils::isValidVOC(_tvoc_raw.update.avg)) { if (utils::isValidVOC(_tvoc_raw.update.avg)) {
root[json_prop_tvocRaw] = ag->round2(_tvoc_raw.update.avg); root[json_prop_tvocRaw] = ag.round2(_tvoc_raw.update.avg);
} }
if (utils::isValidNOx(_nox.update.avg)) { if (utils::isValidNOx(_nox.update.avg)) {
root[json_prop_nox] = ag->round2(_nox.update.avg); root[json_prop_nox] = ag.round2(_nox.update.avg);
} }
if (utils::isValidNOx(_nox_raw.update.avg)) { if (utils::isValidNOx(_nox_raw.update.avg)) {
root[json_prop_noxRaw] = ag->round2(_nox_raw.update.avg); root[json_prop_noxRaw] = ag.round2(_nox_raw.update.avg);
} }
} }
@ -676,11 +612,11 @@ String Measurements::toString(bool localServer, AgFirmwareMode fwMode, int rssi)
root["wifi"] = rssi; root["wifi"] = rssi;
if (localServer) { if (localServer) {
if (ag->isOne()) { if (ag.isOne()) {
root["ledMode"] = config.getLedBarModeName(); root["ledMode"] = config.getLedBarModeName();
} }
root["serialno"] = ag->deviceId(); root["serialno"] = ag.deviceId();
root["firmware"] = ag->getVersion(); root["firmware"] = ag.getVersion();
root["model"] = AgFirmwareModeName(fwMode); root["model"] = AgFirmwareModeName(fwMode);
} else { } else {
#ifndef ESP8266 #ifndef ESP8266
@ -694,7 +630,8 @@ String Measurements::toString(bool localServer, AgFirmwareMode fwMode, int rssi)
return result; return result;
} }
JSONVar Measurements::buildOutdoor(bool localServer, AgFirmwareMode fwMode) { JSONVar Measurements::buildOutdoor(bool localServer, AgFirmwareMode fwMode, AirGradient &ag,
Configuration &config) {
JSONVar outdoor; JSONVar outdoor;
if (fwMode == FW_MODE_O_1P || fwMode == FW_MODE_O_1PS || fwMode == FW_MODE_O_1PST) { if (fwMode == FW_MODE_O_1P || fwMode == FW_MODE_O_1PS || fwMode == FW_MODE_O_1PST) {
// buildPMS params: // buildPMS params:
@ -703,16 +640,14 @@ JSONVar Measurements::buildOutdoor(bool localServer, AgFirmwareMode fwMode) {
/// compensated values if requested by local server /// compensated values if requested by local server
/// Set ch based on hasSensorPMSx /// Set ch based on hasSensorPMSx
if (config.hasSensorPMS1) { if (config.hasSensorPMS1) {
outdoor = buildPMS(1, false, true, localServer); outdoor = buildPMS(ag, 1, false, true, localServer);
if (!localServer) { if (!localServer) {
outdoor[json_prop_pmFirmware] = outdoor[json_prop_pmFirmware] = pms5003TFirmwareVersion(ag.pms5003t_1.getFirmwareVersion());
pms5003TFirmwareVersion(ag->pms5003t_1.getFirmwareVersion());
} }
} else { } else {
outdoor = buildPMS(2, false, true, localServer); outdoor = buildPMS(ag, 2, false, true, localServer);
if (!localServer) { if (!localServer) {
outdoor[json_prop_pmFirmware] = outdoor[json_prop_pmFirmware] = pms5003TFirmwareVersion(ag.pms5003t_2.getFirmwareVersion());
pms5003TFirmwareVersion(ag->pms5003t_2.getFirmwareVersion());
} }
} }
} else { } else {
@ -721,20 +656,20 @@ JSONVar Measurements::buildOutdoor(bool localServer, AgFirmwareMode fwMode) {
/// Have 2 PMS sensor, allCh is set to true (ch params ignored) /// Have 2 PMS sensor, allCh is set to true (ch params ignored)
/// Enable temp hum from PMS /// Enable temp hum from PMS
/// compensated values if requested by local server /// compensated values if requested by local server
outdoor = buildPMS(1, true, true, localServer); outdoor = buildPMS(ag, 1, true, true, localServer);
// PMS5003T version // PMS5003T version
if (!localServer) { if (!localServer) {
outdoor["channels"]["1"][json_prop_pmFirmware] = outdoor["channels"]["1"][json_prop_pmFirmware] =
pms5003TFirmwareVersion(ag->pms5003t_1.getFirmwareVersion()); pms5003TFirmwareVersion(ag.pms5003t_1.getFirmwareVersion());
outdoor["channels"]["2"][json_prop_pmFirmware] = outdoor["channels"]["2"][json_prop_pmFirmware] =
pms5003TFirmwareVersion(ag->pms5003t_2.getFirmwareVersion()); pms5003TFirmwareVersion(ag.pms5003t_2.getFirmwareVersion());
} }
} }
return outdoor; return outdoor;
} }
JSONVar Measurements::buildIndoor(bool localServer) { JSONVar Measurements::buildIndoor(bool localServer, AirGradient &ag, Configuration &config) {
JSONVar indoor; JSONVar indoor;
if (config.hasSensorPMS1) { if (config.hasSensorPMS1) {
@ -742,26 +677,26 @@ JSONVar Measurements::buildIndoor(bool localServer) {
/// PMS channel 1 (indoor only have 1 PMS; hence allCh false) /// PMS channel 1 (indoor only have 1 PMS; hence allCh false)
/// Not include temperature and humidity from PMS sensor /// Not include temperature and humidity from PMS sensor
/// Not include compensated calculation /// Not include compensated calculation
indoor = buildPMS(1, false, false, false); indoor = buildPMS(ag, 1, false, false, false);
if (!localServer) { if (!localServer) {
// Indoor is using PMS5003 // Indoor is using PMS5003
indoor[json_prop_pmFirmware] = this->pms5003FirmwareVersion(ag->pms5003.getFirmwareVersion()); indoor[json_prop_pmFirmware] = this->pms5003FirmwareVersion(ag.pms5003.getFirmwareVersion());
} }
} }
if (config.hasSensorSHT) { if (config.hasSensorSHT) {
// Add temperature // Add temperature
if (utils::isValidTemperature(_temperature[0].update.avg)) { if (utils::isValidTemperature(_temperature[0].update.avg)) {
indoor[json_prop_temp] = ag->round2(_temperature[0].update.avg); indoor[json_prop_temp] = ag.round2(_temperature[0].update.avg);
if (localServer) { if (localServer) {
indoor[json_prop_tempCompensated] = ag->round2(getCorrectedTempHum(Temperature)); indoor[json_prop_tempCompensated] = ag.round2(_temperature[0].update.avg);
} }
} }
// Add humidity // Add humidity
if (utils::isValidHumidity(_humidity[0].update.avg)) { if (utils::isValidHumidity(_humidity[0].update.avg)) {
indoor[json_prop_rhum] = ag->round2(_humidity[0].update.avg); indoor[json_prop_rhum] = ag.round2(_humidity[0].update.avg);
if (localServer) { if (localServer) {
indoor[json_prop_rhumCompensated] = ag->round2(getCorrectedTempHum(Humidity)); indoor[json_prop_rhumCompensated] = ag.round2(_humidity[0].update.avg);
} }
} }
} }
@ -770,15 +705,16 @@ JSONVar Measurements::buildIndoor(bool localServer) {
if (config.hasSensorPMS1 && utils::isValidPm(_pm_25[0].update.avg)) { if (config.hasSensorPMS1 && utils::isValidPm(_pm_25[0].update.avg)) {
if (config.hasSensorSHT && utils::isValidHumidity(_humidity[0].update.avg)) { if (config.hasSensorSHT && utils::isValidHumidity(_humidity[0].update.avg)) {
// Correction using moving average value // Correction using moving average value
float tmp = getCorrectedPM25(true); float tmp = getCorrectedPM25(ag, config, true);
indoor[json_prop_pm25Compensated] = ag->round2(tmp); indoor[json_prop_pm25Compensated] = ag.round2(tmp);
} }
} }
return indoor; return indoor;
} }
JSONVar Measurements::buildPMS(int ch, bool allCh, bool withTempHum, bool compensate) { JSONVar Measurements::buildPMS(AirGradient &ag, int ch, bool allCh, bool withTempHum,
bool compensate) {
JSONVar pms; JSONVar pms;
// When only one of the channel // When only one of the channel
@ -790,47 +726,47 @@ JSONVar Measurements::buildPMS(int ch, bool allCh, bool withTempHum, bool compen
ch = ch - 1; ch = ch - 1;
if (utils::isValidPm(_pm_01[ch].update.avg)) { if (utils::isValidPm(_pm_01[ch].update.avg)) {
pms[json_prop_pm01Ae] = ag->round2(_pm_01[ch].update.avg); pms[json_prop_pm01Ae] = ag.round2(_pm_01[ch].update.avg);
} }
if (utils::isValidPm(_pm_25[ch].update.avg)) { if (utils::isValidPm(_pm_25[ch].update.avg)) {
pms[json_prop_pm25Ae] = ag->round2(_pm_25[ch].update.avg); pms[json_prop_pm25Ae] = ag.round2(_pm_25[ch].update.avg);
} }
if (utils::isValidPm(_pm_10[ch].update.avg)) { if (utils::isValidPm(_pm_10[ch].update.avg)) {
pms[json_prop_pm10Ae] = ag->round2(_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)) { if (utils::isValidPm(_pm_01_sp[ch].update.avg)) {
pms[json_prop_pm01Sp] = ag->round2(_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)) { if (utils::isValidPm(_pm_25_sp[ch].update.avg)) {
pms[json_prop_pm25Sp] = ag->round2(_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)) { if (utils::isValidPm(_pm_10_sp[ch].update.avg)) {
pms[json_prop_pm10Sp] = ag->round2(_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)) { if (utils::isValidPm03Count(_pm_03_pc[ch].update.avg)) {
pms[json_prop_pm03Count] = ag->round2(_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)) { if (utils::isValidPm03Count(_pm_05_pc[ch].update.avg)) {
pms[json_prop_pm05Count] = ag->round2(_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)) { if (utils::isValidPm03Count(_pm_01_pc[ch].update.avg)) {
pms[json_prop_pm1Count] = ag->round2(_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)) { if (utils::isValidPm03Count(_pm_25_pc[ch].update.avg)) {
pms[json_prop_pm25Count] = ag->round2(_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) { if (_pm_5_pc[ch].listValues.empty() == false) {
// Only include pm5.0 count when values available on its list // Only include pm5.0 count when values available on its list
// If not, means no pm5_pc available from the sensor // If not, means no pm5_pc available from the sensor
if (utils::isValidPm03Count(_pm_5_pc[ch].update.avg)) { if (utils::isValidPm03Count(_pm_5_pc[ch].update.avg)) {
pms[json_prop_pm5Count] = ag->round2(_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) { if (_pm_10_pc[ch].listValues.empty() == false) {
// Only include pm10 count when values available on its list // Only include pm10 count when values available on its list
// If not, means no pm10_pc available from the sensor // If not, means no pm10_pc available from the sensor
if (utils::isValidPm03Count(_pm_10_pc[ch].update.avg)) { if (utils::isValidPm03Count(_pm_10_pc[ch].update.avg)) {
pms[json_prop_pm10Count] = ag->round2(_pm_10_pc[ch].update.avg); pms[json_prop_pm10Count] = ag.round2(_pm_10_pc[ch].update.avg);
} }
} }
@ -838,23 +774,23 @@ JSONVar Measurements::buildPMS(int ch, bool allCh, bool withTempHum, bool compen
float _vc; float _vc;
// Set temperature if valid // Set temperature if valid
if (utils::isValidTemperature(_temperature[ch].update.avg)) { if (utils::isValidTemperature(_temperature[ch].update.avg)) {
pms[json_prop_temp] = ag->round2(_temperature[ch].update.avg); pms[json_prop_temp] = ag.round2(_temperature[ch].update.avg);
// Compensate temperature when flag is set // Compensate temperature when flag is set
if (compensate) { if (compensate) {
_vc = getCorrectedTempHum(Temperature, ch, true); _vc = ag.pms5003t_1.compensateTemp(_temperature[ch].update.avg);
if (utils::isValidTemperature(_vc)) { if (utils::isValidTemperature(_vc)) {
pms[json_prop_tempCompensated] = ag->round2(_vc); pms[json_prop_tempCompensated] = ag.round2(_vc);
} }
} }
} }
// Set humidity if valid // Set humidity if valid
if (utils::isValidHumidity(_humidity[ch].update.avg)) { if (utils::isValidHumidity(_humidity[ch].update.avg)) {
pms[json_prop_rhum] = ag->round2(_humidity[ch].update.avg); pms[json_prop_rhum] = ag.round2(_humidity[ch].update.avg);
// Compensate relative humidity when flag is set // Compensate relative humidity when flag is set
if (compensate) { if (compensate) {
_vc = getCorrectedTempHum(Humidity, ch, true); _vc = ag.pms5003t_1.compensateHum(_humidity[ch].update.avg);
if (utils::isValidHumidity(_vc)) { if (utils::isValidTemperature(_vc)) {
pms[json_prop_rhumCompensated] = ag->round2(_vc); pms[json_prop_rhumCompensated] = ag.round2(_vc);
} }
} }
} }
@ -865,9 +801,9 @@ JSONVar Measurements::buildPMS(int ch, bool allCh, bool withTempHum, bool compen
utils::isValidHumidity(_humidity[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 // Note: the pms5003t object is not matter either for channel 1 or 2, compensate points to
// the same base function // the same base function
float pm25 = ag->pms5003t_1.compensate(_pm_25[ch].update.avg, _humidity[ch].update.avg); float pm25 = ag.pms5003t_1.compensate(_pm_25[ch].update.avg, _humidity[ch].update.avg);
if (utils::isValidPm(pm25)) { if (utils::isValidPm(pm25)) {
pms[json_prop_pm25Compensated] = ag->round2(pm25); pms[json_prop_pm25Compensated] = ag.round2(pm25);
} }
} }
} }
@ -883,144 +819,144 @@ JSONVar Measurements::buildPMS(int ch, bool allCh, bool withTempHum, bool compen
/// PM1.0 atmospheric environment /// PM1.0 atmospheric environment
if (utils::isValidPm(_pm_01[0].update.avg) && utils::isValidPm(_pm_01[1].update.avg)) { 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; float avg = (_pm_01[0].update.avg + _pm_01[1].update.avg) / 2.0f;
pms[json_prop_pm01Ae] = ag->round2(avg); pms[json_prop_pm01Ae] = ag.round2(avg);
pms["channels"]["1"][json_prop_pm01Ae] = ag->round2(_pm_01[0].update.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); pms["channels"]["2"][json_prop_pm01Ae] = ag.round2(_pm_01[1].update.avg);
} else if (utils::isValidPm(_pm_01[0].update.avg)) { } else if (utils::isValidPm(_pm_01[0].update.avg)) {
pms[json_prop_pm01Ae] = ag->round2(_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); pms["channels"]["1"][json_prop_pm01Ae] = ag.round2(_pm_01[0].update.avg);
} else if (utils::isValidPm(_pm_01[1].update.avg)) { } else if (utils::isValidPm(_pm_01[1].update.avg)) {
pms[json_prop_pm01Ae] = ag->round2(_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); pms["channels"]["2"][json_prop_pm01Ae] = ag.round2(_pm_01[1].update.avg);
} }
/// PM2.5 atmospheric environment /// PM2.5 atmospheric environment
if (utils::isValidPm(_pm_25[0].update.avg) && utils::isValidPm(_pm_25[1].update.avg)) { 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; float avg = (_pm_25[0].update.avg + _pm_25[1].update.avg) / 2.0f;
pms[json_prop_pm25Ae] = ag->round2(avg); pms[json_prop_pm25Ae] = ag.round2(avg);
pms["channels"]["1"][json_prop_pm25Ae] = ag->round2(_pm_25[0].update.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); pms["channels"]["2"][json_prop_pm25Ae] = ag.round2(_pm_25[1].update.avg);
} else if (utils::isValidPm(_pm_25[0].update.avg)) { } else if (utils::isValidPm(_pm_25[0].update.avg)) {
pms[json_prop_pm25Ae] = ag->round2(_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); pms["channels"]["1"][json_prop_pm25Ae] = ag.round2(_pm_25[0].update.avg);
} else if (utils::isValidPm(_pm_25[1].update.avg)) { } else if (utils::isValidPm(_pm_25[1].update.avg)) {
pms[json_prop_pm25Ae] = ag->round2(_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); pms["channels"]["2"][json_prop_pm25Ae] = ag.round2(_pm_25[1].update.avg);
} }
/// PM10 atmospheric environment /// PM10 atmospheric environment
if (utils::isValidPm(_pm_10[0].update.avg) && utils::isValidPm(_pm_10[1].update.avg)) { 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; float avg = (_pm_10[0].update.avg + _pm_10[1].update.avg) / 2.0f;
pms[json_prop_pm10Ae] = ag->round2(avg); pms[json_prop_pm10Ae] = ag.round2(avg);
pms["channels"]["1"][json_prop_pm10Ae] = ag->round2(_pm_10[0].update.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); pms["channels"]["2"][json_prop_pm10Ae] = ag.round2(_pm_10[1].update.avg);
} else if (utils::isValidPm(_pm_10[0].update.avg)) { } else if (utils::isValidPm(_pm_10[0].update.avg)) {
pms[json_prop_pm10Ae] = ag->round2(_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); pms["channels"]["1"][json_prop_pm10Ae] = ag.round2(_pm_10[0].update.avg);
} else if (utils::isValidPm(_pm_10[1].update.avg)) { } else if (utils::isValidPm(_pm_10[1].update.avg)) {
pms[json_prop_pm10Ae] = ag->round2(_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); pms["channels"]["2"][json_prop_pm10Ae] = ag.round2(_pm_10[1].update.avg);
} }
/// PM1.0 standard particle /// PM1.0 standard particle
if (utils::isValidPm(_pm_01_sp[0].update.avg) && utils::isValidPm(_pm_01_sp[1].update.avg)) { 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; float avg = (_pm_01_sp[0].update.avg + _pm_01_sp[1].update.avg) / 2.0f;
pms[json_prop_pm01Sp] = ag->round2(avg); pms[json_prop_pm01Sp] = ag.round2(avg);
pms["channels"]["1"][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);
pms["channels"]["2"][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);
} else if (utils::isValidPm(_pm_01_sp[0].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[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); pms["channels"]["1"][json_prop_pm01Sp] = ag.round2(_pm_01_sp[0].update.avg);
} else if (utils::isValidPm(_pm_01_sp[1].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[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); pms["channels"]["2"][json_prop_pm01Sp] = ag.round2(_pm_01_sp[1].update.avg);
} }
/// PM2.5 standard particle /// PM2.5 standard particle
if (utils::isValidPm(_pm_25_sp[0].update.avg) && utils::isValidPm(_pm_25_sp[1].update.avg)) { 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; float avg = (_pm_25_sp[0].update.avg + _pm_25_sp[1].update.avg) / 2.0f;
pms[json_prop_pm25Sp] = ag->round2(avg); pms[json_prop_pm25Sp] = ag.round2(avg);
pms["channels"]["1"][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);
pms["channels"]["2"][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);
} else if (utils::isValidPm(_pm_25_sp[0].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[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); pms["channels"]["1"][json_prop_pm25Sp] = ag.round2(_pm_25_sp[0].update.avg);
} else if (utils::isValidPm(_pm_25_sp[1].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[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); pms["channels"]["2"][json_prop_pm25Sp] = ag.round2(_pm_25_sp[1].update.avg);
} }
/// PM10 standard particle /// PM10 standard particle
if (utils::isValidPm(_pm_10_sp[0].update.avg) && utils::isValidPm(_pm_10_sp[1].update.avg)) { 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; float avg = (_pm_10_sp[0].update.avg + _pm_10_sp[1].update.avg) / 2.0f;
pms[json_prop_pm10Sp] = ag->round2(avg); pms[json_prop_pm10Sp] = ag.round2(avg);
pms["channels"]["1"][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);
pms["channels"]["2"][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);
} else if (utils::isValidPm(_pm_10_sp[0].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[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); pms["channels"]["1"][json_prop_pm10Sp] = ag.round2(_pm_10_sp[0].update.avg);
} else if (utils::isValidPm(_pm_10_sp[1].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[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); pms["channels"]["2"][json_prop_pm10Sp] = ag.round2(_pm_10_sp[1].update.avg);
} }
/// PM003 particle count /// PM003 particle count
if (utils::isValidPm03Count(_pm_03_pc[0].update.avg) && if (utils::isValidPm03Count(_pm_03_pc[0].update.avg) &&
utils::isValidPm03Count(_pm_03_pc[1].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; float avg = (_pm_03_pc[0].update.avg + _pm_03_pc[1].update.avg) / 2.0f;
pms[json_prop_pm03Count] = ag->round2(avg); pms[json_prop_pm03Count] = ag.round2(avg);
pms["channels"]["1"][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);
pms["channels"]["2"][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);
} else if (utils::isValidPm03Count(_pm_03_pc[0].update.avg)) { } else if (utils::isValidPm03Count(_pm_03_pc[0].update.avg)) {
pms[json_prop_pm03Count] = ag->round2(_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); pms["channels"]["1"][json_prop_pm03Count] = ag.round2(_pm_03_pc[0].update.avg);
} else if (utils::isValidPm03Count(_pm_03_pc[1].update.avg)) { } else if (utils::isValidPm03Count(_pm_03_pc[1].update.avg)) {
pms[json_prop_pm03Count] = ag->round2(_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); pms["channels"]["2"][json_prop_pm03Count] = ag.round2(_pm_03_pc[1].update.avg);
} }
/// PM0.5 particle count /// PM0.5 particle count
if (utils::isValidPm03Count(_pm_05_pc[0].update.avg) && if (utils::isValidPm03Count(_pm_05_pc[0].update.avg) &&
utils::isValidPm03Count(_pm_05_pc[1].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; float avg = (_pm_05_pc[0].update.avg + _pm_05_pc[1].update.avg) / 2.0f;
pms[json_prop_pm05Count] = ag->round2(avg); pms[json_prop_pm05Count] = ag.round2(avg);
pms["channels"]["1"][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);
pms["channels"]["2"][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);
} else if (utils::isValidPm03Count(_pm_05_pc[0].update.avg)) { } else if (utils::isValidPm03Count(_pm_05_pc[0].update.avg)) {
pms[json_prop_pm05Count] = ag->round2(_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); pms["channels"]["1"][json_prop_pm05Count] = ag.round2(_pm_05_pc[0].update.avg);
} else if (utils::isValidPm03Count(_pm_05_pc[1].update.avg)) { } else if (utils::isValidPm03Count(_pm_05_pc[1].update.avg)) {
pms[json_prop_pm05Count] = ag->round2(_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); pms["channels"]["2"][json_prop_pm05Count] = ag.round2(_pm_05_pc[1].update.avg);
} }
/// PM1.0 particle count /// PM1.0 particle count
if (utils::isValidPm03Count(_pm_01_pc[0].update.avg) && if (utils::isValidPm03Count(_pm_01_pc[0].update.avg) &&
utils::isValidPm03Count(_pm_01_pc[1].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; float avg = (_pm_01_pc[0].update.avg + _pm_01_pc[1].update.avg) / 2.0f;
pms[json_prop_pm1Count] = ag->round2(avg); pms[json_prop_pm1Count] = ag.round2(avg);
pms["channels"]["1"][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);
pms["channels"]["2"][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);
} else if (utils::isValidPm03Count(_pm_01_pc[0].update.avg)) { } else if (utils::isValidPm03Count(_pm_01_pc[0].update.avg)) {
pms[json_prop_pm1Count] = ag->round2(_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); pms["channels"]["1"][json_prop_pm1Count] = ag.round2(_pm_01_pc[0].update.avg);
} else if (utils::isValidPm03Count(_pm_01_pc[1].update.avg)) { } else if (utils::isValidPm03Count(_pm_01_pc[1].update.avg)) {
pms[json_prop_pm1Count] = ag->round2(_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); pms["channels"]["2"][json_prop_pm1Count] = ag.round2(_pm_01_pc[1].update.avg);
} }
/// PM2.5 particle count /// PM2.5 particle count
if (utils::isValidPm03Count(_pm_25_pc[0].update.avg) && if (utils::isValidPm03Count(_pm_25_pc[0].update.avg) &&
utils::isValidPm03Count(_pm_25_pc[1].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; float avg = (_pm_25_pc[0].update.avg + _pm_25_pc[1].update.avg) / 2.0f;
pms[json_prop_pm25Count] = ag->round2(avg); pms[json_prop_pm25Count] = ag.round2(avg);
pms["channels"]["1"][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);
pms["channels"]["2"][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);
} else if (utils::isValidPm03Count(_pm_25_pc[0].update.avg)) { } else if (utils::isValidPm03Count(_pm_25_pc[0].update.avg)) {
pms[json_prop_pm25Count] = ag->round2(_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); pms["channels"]["1"][json_prop_pm25Count] = ag.round2(_pm_25_pc[0].update.avg);
} else if (utils::isValidPm03Count(_pm_25_pc[1].update.avg)) { } else if (utils::isValidPm03Count(_pm_25_pc[1].update.avg)) {
pms[json_prop_pm25Count] = ag->round2(_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); 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 // NOTE: No need for particle count 5.0 and 10. When allCh is true, basically monitor using
@ -1032,40 +968,40 @@ JSONVar Measurements::buildPMS(int ch, bool allCh, bool withTempHum, bool compen
utils::isValidTemperature(_temperature[1].update.avg)) { utils::isValidTemperature(_temperature[1].update.avg)) {
float temperature = (_temperature[0].update.avg + _temperature[1].update.avg) / 2.0f; float temperature = (_temperature[0].update.avg + _temperature[1].update.avg) / 2.0f;
pms[json_prop_temp] = ag->round2(temperature); pms[json_prop_temp] = ag.round2(temperature);
pms["channels"]["1"][json_prop_temp] = ag->round2(_temperature[0].update.avg); pms["channels"]["1"][json_prop_temp] = ag.round2(_temperature[0].update.avg);
pms["channels"]["2"][json_prop_temp] = ag->round2(_temperature[1].update.avg); pms["channels"]["2"][json_prop_temp] = ag.round2(_temperature[1].update.avg);
if (compensate) { if (compensate) {
// Compensate both temperature channel // Compensate both temperature channel
float temp1 = getCorrectedTempHum(Temperature, 1, true); float temp = ag.pms5003t_1.compensateTemp(temperature);
float temp2 = getCorrectedTempHum(Temperature, 2, true); float temp1 = ag.pms5003t_1.compensateTemp(_temperature[0].update.avg);
float tempAverage = (temp1 + temp2) / 2.0f; float temp2 = ag.pms5003t_2.compensateTemp(_temperature[1].update.avg);
pms[json_prop_tempCompensated] = ag->round2(tempAverage); pms[json_prop_tempCompensated] = ag.round2(temp);
pms["channels"]["1"][json_prop_tempCompensated] = ag->round2(temp1); pms["channels"]["1"][json_prop_tempCompensated] = ag.round2(temp1);
pms["channels"]["2"][json_prop_tempCompensated] = ag->round2(temp2); pms["channels"]["2"][json_prop_tempCompensated] = ag.round2(temp2);
} }
} else if (utils::isValidTemperature(_temperature[0].update.avg)) { } else if (utils::isValidTemperature(_temperature[0].update.avg)) {
pms[json_prop_temp] = ag->round2(_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); pms["channels"]["1"][json_prop_temp] = ag.round2(_temperature[0].update.avg);
if (compensate) { if (compensate) {
// Compensate channel 1 // Compensate channel 1
float temp1 = getCorrectedTempHum(Temperature, 1, true); float temp1 = ag.pms5003t_1.compensateTemp(_temperature[0].update.avg);
pms[json_prop_tempCompensated] = ag->round2(temp1); pms[json_prop_tempCompensated] = ag.round2(temp1);
pms["channels"]["1"][json_prop_tempCompensated] = ag->round2(temp1); pms["channels"]["1"][json_prop_tempCompensated] = ag.round2(temp1);
} }
} else if (utils::isValidTemperature(_temperature[1].update.avg)) { } else if (utils::isValidTemperature(_temperature[1].update.avg)) {
pms[json_prop_temp] = ag->round2(_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); pms["channels"]["2"][json_prop_temp] = ag.round2(_temperature[1].update.avg);
if (compensate) { if (compensate) {
// Compensate channel 2 // Compensate channel 2
float temp2 = getCorrectedTempHum(Temperature, 2, true); float temp2 = ag.pms5003t_2.compensateTemp(_temperature[1].update.avg);
pms[json_prop_tempCompensated] = ag->round2(temp2); pms[json_prop_tempCompensated] = ag.round2(temp2);
pms["channels"]["2"][json_prop_tempCompensated] = ag->round2(temp2); pms["channels"]["2"][json_prop_tempCompensated] = ag.round2(temp2);
} }
} }
@ -1073,40 +1009,40 @@ JSONVar Measurements::buildPMS(int ch, bool allCh, bool withTempHum, bool compen
if (utils::isValidHumidity(_humidity[0].update.avg) && if (utils::isValidHumidity(_humidity[0].update.avg) &&
utils::isValidHumidity(_humidity[1].update.avg)) { utils::isValidHumidity(_humidity[1].update.avg)) {
float humidity = (_humidity[0].update.avg + _humidity[1].update.avg) / 2.0f; float humidity = (_humidity[0].update.avg + _humidity[1].update.avg) / 2.0f;
pms[json_prop_rhum] = ag->round2(humidity); pms[json_prop_rhum] = ag.round2(humidity);
pms["channels"]["1"][json_prop_rhum] = ag->round2(_humidity[0].update.avg); pms["channels"]["1"][json_prop_rhum] = ag.round2(_humidity[0].update.avg);
pms["channels"]["2"][json_prop_rhum] = ag->round2(_humidity[1].update.avg); pms["channels"]["2"][json_prop_rhum] = ag.round2(_humidity[1].update.avg);
if (compensate) { if (compensate) {
// Compensate both humidity channel // Compensate both humidity channel
float hum1 = getCorrectedTempHum(Humidity, 1, true); float hum = ag.pms5003t_1.compensateHum(humidity);
float hum2 = getCorrectedTempHum(Humidity, 2, true); float hum1 = ag.pms5003t_1.compensateHum(_humidity[0].update.avg);
float humAverage = (hum1 + hum2) / 2.0f; float hum2 = ag.pms5003t_2.compensateHum(_humidity[1].update.avg);
pms[json_prop_rhumCompensated] = ag->round2(humAverage); pms[json_prop_rhumCompensated] = ag.round2(hum);
pms["channels"]["1"][json_prop_rhumCompensated] = ag->round2(hum1); pms["channels"]["1"][json_prop_rhumCompensated] = ag.round2(hum1);
pms["channels"]["2"][json_prop_rhumCompensated] = ag->round2(hum2); pms["channels"]["2"][json_prop_rhumCompensated] = ag.round2(hum2);
} }
} else if (utils::isValidHumidity(_humidity[0].update.avg)) { } else if (utils::isValidHumidity(_humidity[0].update.avg)) {
pms[json_prop_rhum] = ag->round2(_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); pms["channels"]["1"][json_prop_rhum] = ag.round2(_humidity[0].update.avg);
if (compensate) { if (compensate) {
// Compensate humidity channel 1 // Compensate humidity channel 1
float hum1 = getCorrectedTempHum(Humidity, 1, true); float hum1 = ag.pms5003t_1.compensateHum(_humidity[0].update.avg);
pms[json_prop_rhumCompensated] = ag->round2(hum1); pms[json_prop_rhumCompensated] = ag.round2(hum1);
pms["channels"]["1"][json_prop_rhumCompensated] = ag->round2(hum1); pms["channels"]["1"][json_prop_rhumCompensated] = ag.round2(hum1);
} }
} else if (utils::isValidHumidity(_humidity[1].update.avg)) { } else if (utils::isValidHumidity(_humidity[1].update.avg)) {
pms[json_prop_rhum] = ag->round2(_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); pms["channels"]["2"][json_prop_rhum] = ag.round2(_humidity[1].update.avg);
if (compensate) { if (compensate) {
// Compensate humidity channel 2 // Compensate humidity channel 2
float hum2 = getCorrectedTempHum(Humidity, 2, true); float hum2 = ag.pms5003t_2.compensateHum(_humidity[1].update.avg);
pms[json_prop_rhumCompensated] = ag->round2(hum2); pms[json_prop_rhumCompensated] = ag.round2(hum2);
pms["channels"]["2"][json_prop_rhumCompensated] = ag->round2(hum2); pms["channels"]["2"][json_prop_rhumCompensated] = ag.round2(hum2);
} }
} }
@ -1117,22 +1053,22 @@ JSONVar Measurements::buildPMS(int ch, bool allCh, bool withTempHum, bool compen
float pm25_comp2 = utils::getInvalidPmValue(); float pm25_comp2 = utils::getInvalidPmValue();
if (utils::isValidPm(_pm_25[0].update.avg) && if (utils::isValidPm(_pm_25[0].update.avg) &&
utils::isValidHumidity(_humidity[0].update.avg)) { utils::isValidHumidity(_humidity[0].update.avg)) {
pm25_comp1 = ag->pms5003t_1.compensate(_pm_25[0].update.avg, _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); pms["channels"]["1"][json_prop_pm25Compensated] = ag.round2(pm25_comp1);
} }
if (utils::isValidPm(_pm_25[1].update.avg) && if (utils::isValidPm(_pm_25[1].update.avg) &&
utils::isValidHumidity(_humidity[1].update.avg)) { utils::isValidHumidity(_humidity[1].update.avg)) {
pm25_comp2 = ag->pms5003t_2.compensate(_pm_25[1].update.avg, _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); 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 /// Get average or one of the channel compensated value if only one channel is valid
if (utils::isValidPm(pm25_comp1) && utils::isValidPm(pm25_comp2)) { if (utils::isValidPm(pm25_comp1) && utils::isValidPm(pm25_comp2)) {
pms[json_prop_pm25Compensated] = ag->round2((pm25_comp1 + pm25_comp2) / 2.0f); pms[json_prop_pm25Compensated] = ag.round2((pm25_comp1 + pm25_comp2) / 2.0f);
} else if (utils::isValidPm(pm25_comp1)) { } else if (utils::isValidPm(pm25_comp1)) {
pms[json_prop_pm25Compensated] = ag->round2(pm25_comp1); pms[json_prop_pm25Compensated] = ag.round2(pm25_comp1);
} else if (utils::isValidPm(pm25_comp2)) { } else if (utils::isValidPm(pm25_comp2)) {
pms[json_prop_pm25Compensated] = ag->round2(pm25_comp2); pms[json_prop_pm25Compensated] = ag.round2(pm25_comp2);
} }
} }
} }

View File

@ -34,11 +34,9 @@ private:
}; };
public: public:
Measurements(Configuration &config); Measurements();
~Measurements() {} ~Measurements() {}
void setAirGradient(AirGradient *ag);
// Enumeration for every AG measurements // Enumeration for every AG measurements
enum MeasurementType { enum MeasurementType {
Temperature, Temperature,
@ -125,33 +123,24 @@ public:
*/ */
float getAverage(MeasurementType type, int ch = 1); float getAverage(MeasurementType type, int ch = 1);
/**
* @brief Get Temperature or Humidity correction value
* Only if correction is applied from configuration or forceCorrection is True
*
* @param type measurement type either Temperature or Humidity
* @param ch target type value channel
* @param forceCorrection force using correction even though config correction is not applied, but
* not for CUSTOM
* @return correction value
*/
float getCorrectedTempHum(MeasurementType type, int ch = 1, bool forceCorrection = false);
/** /**
* @brief Get the Corrected PM25 object based on the correction algorithm from configuration * @brief Get the Corrected PM25 object based on the correction algorithm from configuration
* *
* If correction is not enabled, then will return the raw value (either average or last value) * If correction is not enabled, then will return the raw value (either average or last value)
* *
* @param ag AirGradient instance
* @param config Configuration instance
* @param useAvg Use moving average value if true, otherwise use latest value * @param useAvg Use moving average value if true, otherwise use latest value
* @param ch MeasurementType channel * @param ch MeasurementType channel
* @return float Corrected PM2.5 value * @return float Corrected PM2.5 value
*/ */
float getCorrectedPM25(bool useAvg = false, int ch = 1); float getCorrectedPM25(AirGradient &ag, Configuration &config, bool useAvg = false, int ch = 1);
/** /**
* build json payload for every measurements * build json payload for every measurements
*/ */
String toString(bool localServer, AgFirmwareMode fwMode, int rssi); String toString(bool localServer, AgFirmwareMode fwMode, int rssi, AirGradient &ag,
Configuration &config);
/** /**
* Set to true if want to debug every update value * Set to true if want to debug every update value
@ -166,9 +155,6 @@ public:
#endif #endif
private: private:
Configuration &config;
AirGradient *ag;
// Some declared as an array (channel), because FW_MODE_O_1PPx has two PMS5003T // Some declared as an array (channel), because FW_MODE_O_1PPx has two PMS5003T
FloatValue _temperature[2]; FloatValue _temperature[2];
FloatValue _humidity[2]; FloatValue _humidity[2];
@ -227,9 +213,10 @@ private:
*/ */
void validateChannel(int ch); void validateChannel(int ch);
JSONVar buildOutdoor(bool localServer, AgFirmwareMode fwMode); JSONVar buildOutdoor(bool localServer, AgFirmwareMode fwMode, AirGradient &ag,
JSONVar buildIndoor(bool localServer); Configuration &config);
JSONVar buildPMS(int ch, bool allCh, bool withTempHum, bool compensate); JSONVar buildIndoor(bool localServer, AirGradient &ag, Configuration &config);
JSONVar buildPMS(AirGradient &ag, int ch, bool allCh, bool withTempHum, bool compensate);
}; };
#endif /** _AG_VALUE_H_ */ #endif /** _AG_VALUE_H_ */

View File

@ -106,23 +106,15 @@ enum PMCorrectionAlgorithm {
SLR_PMS5003_20240104, SLR_PMS5003_20240104,
}; };
// Don't change the order of the enum
enum TempHumCorrectionAlgorithm {
COR_ALGO_TEMP_HUM_UNKNOWN, // Unknown algorithm
COR_ALGO_TEMP_HUM_NONE, // No PM correction
COR_ALGO_TEMP_HUM_AG_PMS5003T_2024,
COR_ALGO_TEMP_HUM_SLR_CUSTOM
};
enum AgFirmwareMode { enum AgFirmwareMode {
FW_MODE_I_9PSL, /** ONE_INDOOR */ FW_MODE_I_9PSL, /** ONE_INDOOR */
FW_MODE_O_1PST, /** PMS5003T, S8 and SGP41 */ FW_MODE_O_1PST, /** PMS5003T, S8 and SGP41 */
FW_MODE_O_1PPT, /** PMS5003T_1, PMS5003T_2, SGP41 */ FW_MODE_O_1PPT, /** PMS5003T_1, PMS5003T_2, SGP41 */
FW_MODE_O_1PP, /** PMS5003T_1, PMS5003T_2 */ FW_MODE_O_1PP, /** PMS5003T_1, PMS5003T_2 */
FW_MODE_O_1PS, /** PMS5003T, S8 */ FW_MODE_O_1PS, /** PMS5003T, S8 */
FW_MODE_O_1P, /** PMS5003T */ FW_MODE_O_1P, /** PMS5003T */
FW_MODE_I_42PS, /** DIY_PRO 4.2 */ FW_MODE_I_42PS, /** DIY_PRO 4.2 */
FW_MODE_I_33PS, /** DIY_PRO 3.3 */ FW_MODE_I_33PS, /** DIY_PRO 3.3 */
FW_MODE_I_BASIC_40PS, /** DIY_BASIC 4.0 */ FW_MODE_I_BASIC_40PS, /** DIY_BASIC 4.0 */
}; };
const char *AgFirmwareModeName(AgFirmwareMode mode); const char *AgFirmwareModeName(AgFirmwareMode mode);