Compare commits

..

4 Commits

Author SHA1 Message Date
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
25 changed files with 525 additions and 952 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.
### Discovery
#### Discovery
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:
### Get Current Air Quality (GET)
#### Get Current Air Quality (GET)
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.
### Get Configuration Parameters (GET)
#### Get Configuration Parameters (GET)
"/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.
@ -131,11 +131,11 @@ Example to set monitor to Celsius
``` -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.
### Configuration Parameters (GET/PUT)
#### Configuration Parameters (GET/PUT)
| Properties | Description | Type | Accepted Values | Example |
|-----------------------------------|:-----------------------------------------------------------------|---------|-----------------------------------------------------------------------------------------------------------------------------------------|-------------------------------------------------|
@ -154,18 +154,15 @@ If the monitor is set up on the AirGradient dashboard, it will also receive the
| `ledBarTestRequested` | Can be set to trigger a test. | Boolean | `true` : LEDs will run test sequence | `{"ledBarTestRequested": true}` |
| `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) <br> `true`: Enabled | `{"offlineMode": true}` |
| `monitorDisplayCompensatedValues` | Set the display show the PM value with/without compensate value (only on [3.1.9]()) | Boolean | `false`: Without compensate (default) <br> `true`: with compensate | `{"monitorDisplayCompensatedValues": false }` |
| `corrections` | Sets correction options to display and measurement values on local server response. (version >= [3.1.11]()) | Object | _see corrections section_ | _see corrections section_ |
**Notes**
- `offlineMode` : the device will disable all network operation, and only show measurements on the display and ledbar; Read-Only; Change can be apply using reset button on boot.
- `disableCloudConnection` : disable every request to AirGradient server, means features like post data to AirGradient server, configuration from AirGradient server and automatic firmware updates are disabled. This configuration overrides `configurationControl` and `postDataToAirGradient`; Read-Only; Change can be apply from wifi setup webpage.
#### 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:
@ -179,29 +176,11 @@ Example correction configuration:
"scalingFactor": 0,
"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 |
|------------|-------------|------|---------|
| Raw | `"none"` | No correction (default) | No |
@ -235,23 +214,3 @@ curl --location -X PUT 'http://airgradient_84fce612eff4.local/config' --header '
```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}}}}'
```
#### 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 Configuration configuration(Serial);
static AgApiClient apiClient(Serial, configuration);
static Measurements measurements(configuration);
static Measurements measurements;
static OledDisplay oledDisplay(configuration, measurements, Serial);
static StateMachine stateMachine(oledDisplay, Serial, measurements,
configuration);
@ -124,7 +124,6 @@ void setup() {
apiClient.setAirGradient(&ag);
openMetrics.setAirGradient(&ag);
localServer.setAirGraident(&ag);
measurements.setAirGradient(&ag);
/** Example set custom API root URL */
// apiClient.setApiRoot("https://example.custom.api");
@ -150,12 +149,9 @@ void setup() {
initMqtt();
sendDataToAg();
if (configuration.getConfigurationControl() !=
ConfigurationControl::ConfigurationControlLocal) {
apiClient.fetchServerConfiguration();
}
apiClient.fetchServerConfiguration();
configSchedule.update();
if (apiClient.isFetchConfigurationFailed()) {
if (apiClient.isFetchConfigureFailed()) {
if (apiClient.isNotAvailableOnDashboard()) {
stateMachine.displaySetAddToDashBoard();
stateMachine.displayHandle(
@ -320,7 +316,7 @@ static void mqttHandle(void) {
}
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();
if (mqttClient.publish(topic.c_str(), payload.c_str(), payload.length())) {
Serial.println("MQTT sync success");
@ -419,14 +415,6 @@ static void failedHandler(String msg) {
}
static void configurationUpdateSchedule(void) {
if (configuration.isOfflineMode() ||
configuration.getConfigurationControl() == ConfigurationControl::ConfigurationControlLocal) {
Serial.println("Ignore fetch server configuration. Either mode is offline "
"or configurationControl set to local");
apiClient.resetFetchConfigurationStatus();
return;
}
if (apiClient.fetchServerConfiguration()) {
configUpdateHandle();
}
@ -484,7 +472,7 @@ static void appDispHandler(void) {
if (configuration.isOfflineMode() == false) {
if (wifiConnector.isConnected() == false) {
state = AgStateMachineWiFiLost;
} else if (apiClient.isFetchConfigurationFailed()) {
} else if (apiClient.isFetchConfigureFailed()) {
state = AgStateMachineSensorConfigFailed;
if (apiClient.isNotAvailableOnDashboard()) {
stateMachine.displaySetAddToDashBoard();
@ -533,21 +521,17 @@ static void sendDataToServer(void) {
int bootCount = measurements.bootCount() + 1;
measurements.setBootCount(bootCount);
if (configuration.isOfflineMode() || !configuration.isPostDataToAirGradient()) {
Serial.println("Skipping transmission of data to AG server. Either mode is offline "
"or post data to server disabled");
/** Ignore send data to server if postToAirGradient disabled */
if (configuration.isPostDataToAirGradient() == false ||
configuration.isOfflineMode()) {
return;
}
if (wifiConnector.isConnected() == false) {
Serial.println("WiFi not connected, skipping data transmission to AG server");
return;
}
String syncData = measurements.toString(false, fwMode, wifiConnector.RSSI());
String syncData = measurements.toString(false, fwMode, wifiConnector.RSSI(), ag, configuration);
if (apiClient.postToServer(syncData)) {
Serial.println();
Serial.println("Online mode and isPostToAirGradient = true");
Serial.println(
"Online mode and isPostToAirGradient = true: watchdog reset");
Serial.println();
}
}

View File

@ -53,7 +53,7 @@ void LocalServer::_GET_metrics(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);
}

View File

@ -43,7 +43,7 @@ String OpenMetrics::getPayload(void) {
"1 if the AirGradient device was able to successfully fetch its "
"configuration from the server",
"gauge");
add_metric_point("", apiClient.isFetchConfigurationFailed() ? "0" : "1");
add_metric_point("", apiClient.isFetchConfigureFailed() ? "0" : "1");
add_metric(
"post_ok",
@ -66,7 +66,7 @@ String OpenMetrics::getPayload(void) {
int pm03PCount = utils::getInvalidPmValue();
int co2 = utils::getInvalidCO2();
int atmpCompensated = utils::getInvalidTemperature();
int rhumCompensated = utils::getInvalidHumidity();
int ahumCompensated = utils::getInvalidHumidity();
int tvoc = utils::getInvalidVOC();
int tvocRaw = utils::getInvalidVOC();
int nox = utils::getInvalidNOx();
@ -76,12 +76,12 @@ String OpenMetrics::getPayload(void) {
_temp = measure.getFloat(Measurements::Temperature);
_hum = measure.getFloat(Measurements::Humidity);
atmpCompensated = _temp;
rhumCompensated = _hum;
ahumCompensated = _hum;
}
if (config.hasSensorPMS1) {
pm01 = measure.get(Measurements::PM01);
float correctedPm = measure.getCorrectedPM25(false, 1);
float correctedPm = measure.getCorrectedPM25(*ag, config, false, 1);
pm25 = round(correctedPm);
pm10 = measure.get(Measurements::PM10);
pm03PCount = measure.get(Measurements::PM03_PC);
@ -191,12 +191,12 @@ String OpenMetrics::getPayload(void) {
"gauge", "percent");
add_metric_point("", String(_hum));
}
if (utils::isValidHumidity(rhumCompensated)) {
if (utils::isValidHumidity(ahumCompensated)) {
add_metric("humidity_compensated",
"The compensated relative humidity as measured by the "
"AirGradient SHT / PMS sensor",
"gauge", "percent");
add_metric_point("", String(rhumCompensated));
add_metric_point("", String(ahumCompensated));
}
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 Configuration configuration(Serial);
static AgApiClient apiClient(Serial, configuration);
static Measurements measurements(configuration);
static Measurements measurements;
static OledDisplay oledDisplay(configuration, measurements, Serial);
static StateMachine stateMachine(oledDisplay, Serial, measurements,
configuration);
@ -124,7 +124,6 @@ void setup() {
apiClient.setAirGradient(&ag);
openMetrics.setAirGradient(&ag);
localServer.setAirGraident(&ag);
measurements.setAirGradient(&ag);
/** Example set custom API root URL */
// apiClient.setApiRoot("https://example.custom.api");
@ -150,12 +149,9 @@ void setup() {
initMqtt();
sendDataToAg();
if (configuration.getConfigurationControl() !=
ConfigurationControl::ConfigurationControlLocal) {
apiClient.fetchServerConfiguration();
}
apiClient.fetchServerConfiguration();
configSchedule.update();
if (apiClient.isFetchConfigurationFailed()) {
if (apiClient.isFetchConfigureFailed()) {
if (apiClient.isNotAvailableOnDashboard()) {
stateMachine.displaySetAddToDashBoard();
stateMachine.displayHandle(
@ -377,7 +373,7 @@ static void mqttHandle(void) {
}
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();
if (mqttClient.publish(topic.c_str(), payload.c_str(), payload.length())) {
Serial.println("MQTT sync success");
@ -471,14 +467,6 @@ static void failedHandler(String msg) {
}
static void configurationUpdateSchedule(void) {
if (configuration.isOfflineMode() ||
configuration.getConfigurationControl() == ConfigurationControl::ConfigurationControlLocal) {
Serial.println("Ignore fetch server configuration. Either mode is offline "
"or configurationControl set to local");
apiClient.resetFetchConfigurationStatus();
return;
}
if (apiClient.fetchServerConfiguration()) {
configUpdateHandle();
}
@ -536,7 +524,7 @@ static void appDispHandler(void) {
if (configuration.isOfflineMode() == false) {
if (wifiConnector.isConnected() == false) {
state = AgStateMachineWiFiLost;
} else if (apiClient.isFetchConfigurationFailed()) {
} else if (apiClient.isFetchConfigureFailed()) {
state = AgStateMachineSensorConfigFailed;
if (apiClient.isNotAvailableOnDashboard()) {
stateMachine.displaySetAddToDashBoard();
@ -585,21 +573,17 @@ static void sendDataToServer(void) {
int bootCount = measurements.bootCount() + 1;
measurements.setBootCount(bootCount);
if (configuration.isOfflineMode() || !configuration.isPostDataToAirGradient()) {
Serial.println("Skipping transmission of data to AG server. Either mode is offline "
"or post data to server disabled");
/** Ignore send data to server if postToAirGradient disabled */
if (configuration.isPostDataToAirGradient() == false ||
configuration.isOfflineMode()) {
return;
}
if (wifiConnector.isConnected() == false) {
Serial.println("WiFi not connected, skipping data transmission to AG server");
return;
}
String syncData = measurements.toString(false, fwMode, wifiConnector.RSSI());
String syncData = measurements.toString(false, fwMode, wifiConnector.RSSI(), ag, configuration);
if (apiClient.postToServer(syncData)) {
Serial.println();
Serial.println("Online mode and isPostToAirGradient = true");
Serial.println(
"Online mode and isPostToAirGradient = true: watchdog reset");
Serial.println();
}
}

View File

@ -53,7 +53,7 @@ void LocalServer::_GET_metrics(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);
}

View File

@ -43,7 +43,7 @@ String OpenMetrics::getPayload(void) {
"1 if the AirGradient device was able to successfully fetch its "
"configuration from the server",
"gauge");
add_metric_point("", apiClient.isFetchConfigurationFailed() ? "0" : "1");
add_metric_point("", apiClient.isFetchConfigureFailed() ? "0" : "1");
add_metric(
"post_ok",
@ -66,7 +66,7 @@ String OpenMetrics::getPayload(void) {
int pm03PCount = utils::getInvalidPmValue();
int co2 = utils::getInvalidCO2();
int atmpCompensated = utils::getInvalidTemperature();
int rhumCompensated = utils::getInvalidHumidity();
int ahumCompensated = utils::getInvalidHumidity();
int tvoc = utils::getInvalidVOC();
int tvocRaw = utils::getInvalidVOC();
int nox = utils::getInvalidNOx();
@ -76,12 +76,12 @@ String OpenMetrics::getPayload(void) {
_temp = measure.getFloat(Measurements::Temperature);
_hum = measure.getFloat(Measurements::Humidity);
atmpCompensated = _temp;
rhumCompensated = _hum;
ahumCompensated = _hum;
}
if (config.hasSensorPMS1) {
pm01 = measure.get(Measurements::PM01);
float correctedPm = measure.getCorrectedPM25(false, 1);
float correctedPm = measure.getCorrectedPM25(*ag, config, false, 1);
pm25 = round(correctedPm);
pm10 = measure.get(Measurements::PM10);
pm03PCount = measure.get(Measurements::PM03_PC);
@ -192,12 +192,12 @@ String OpenMetrics::getPayload(void) {
"gauge", "percent");
add_metric_point("", String(_hum));
}
if (utils::isValidHumidity(rhumCompensated)) {
if (utils::isValidHumidity(ahumCompensated)) {
add_metric("humidity_compensated",
"The compensated relative humidity as measured by the "
"AirGradient SHT / PMS sensor",
"gauge", "percent");
add_metric_point("", String(rhumCompensated));
add_metric_point("", String(ahumCompensated));
}
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 Configuration configuration(Serial);
static AgApiClient apiClient(Serial, configuration);
static Measurements measurements(configuration);
static Measurements measurements;
static OledDisplay oledDisplay(configuration, measurements, Serial);
static StateMachine stateMachine(oledDisplay, Serial, measurements,
configuration);
@ -125,7 +125,6 @@ void setup() {
apiClient.setAirGradient(&ag);
openMetrics.setAirGradient(&ag);
localServer.setAirGraident(&ag);
measurements.setAirGradient(&ag);
/** Example set custom API root URL */
// apiClient.setApiRoot("https://example.custom.api");
@ -177,12 +176,9 @@ void setup() {
initMqtt();
sendDataToAg();
if (configuration.getConfigurationControl() !=
ConfigurationControl::ConfigurationControlLocal) {
apiClient.fetchServerConfiguration();
}
apiClient.fetchServerConfiguration();
configSchedule.update();
if (apiClient.isFetchConfigurationFailed()) {
if (apiClient.isFetchConfigureFailed()) {
if (apiClient.isNotAvailableOnDashboard()) {
stateMachine.displaySetAddToDashBoard();
stateMachine.displayHandle(
@ -400,7 +396,7 @@ static void mqttHandle(void) {
}
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();
if (mqttClient.publish(topic.c_str(), payload.c_str(), payload.length())) {
Serial.println("MQTT sync success");
@ -511,14 +507,6 @@ static void failedHandler(String msg) {
}
static void configurationUpdateSchedule(void) {
if (configuration.isOfflineMode() ||
configuration.getConfigurationControl() == ConfigurationControl::ConfigurationControlLocal) {
Serial.println("Ignore fetch server configuration. Either mode is offline "
"or configurationControl set to local");
apiClient.resetFetchConfigurationStatus();
return;
}
if (apiClient.fetchServerConfiguration()) {
configUpdateHandle();
}
@ -576,7 +564,7 @@ static void appDispHandler(void) {
if (configuration.isOfflineMode() == false) {
if (wifiConnector.isConnected() == false) {
state = AgStateMachineWiFiLost;
} else if (apiClient.isFetchConfigurationFailed()) {
} else if (apiClient.isFetchConfigureFailed()) {
state = AgStateMachineSensorConfigFailed;
if (apiClient.isNotAvailableOnDashboard()) {
stateMachine.displaySetAddToDashBoard();
@ -626,21 +614,17 @@ static void sendDataToServer(void) {
int bootCount = measurements.bootCount() + 1;
measurements.setBootCount(bootCount);
if (configuration.isOfflineMode() || !configuration.isPostDataToAirGradient()) {
Serial.println("Skipping transmission of data to AG server. Either mode is offline "
"or post data to server disabled");
/** Ignore send data to server if postToAirGradient disabled */
if (configuration.isPostDataToAirGradient() == false ||
configuration.isOfflineMode()) {
return;
}
if (wifiConnector.isConnected() == false) {
Serial.println("WiFi not connected, skipping data transmission to AG server");
return;
}
String syncData = measurements.toString(false, fwMode, wifiConnector.RSSI());
String syncData = measurements.toString(false, fwMode, wifiConnector.RSSI(), ag, configuration);
if (apiClient.postToServer(syncData)) {
Serial.println();
Serial.println("Online mode and isPostToAirGradient = true");
Serial.println(
"Online mode and isPostToAirGradient = true: watchdog reset");
Serial.println();
}
}

View File

@ -53,7 +53,7 @@ void LocalServer::_GET_metrics(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);
}

View File

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

View File

@ -64,7 +64,7 @@ void LocalServer::_GET_metrics(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);
}

View File

@ -76,7 +76,7 @@ static MqttClient mqttClient(Serial);
static TaskHandle_t mqttTask = NULL;
static Configuration configuration(Serial);
static AgApiClient apiClient(Serial, configuration);
static Measurements measurements(configuration);
static Measurements measurements;
static AirGradient *ag;
static OledDisplay oledDisplay(configuration, measurements, Serial);
static StateMachine stateMachine(oledDisplay, Serial, measurements,
@ -96,7 +96,6 @@ static bool ledBarButtonTest = false;
static String fwNewVersion;
static void boardInit(void);
static void initializeNetwork(void);
static void failedHandler(String msg);
static void configurationUpdateSchedule(void);
static void updateDisplayAndLedBar(void);
@ -112,7 +111,7 @@ static void factoryConfigReset(void);
static void wdgFeedUpdate(void);
static void ledBarEnabledUpdate(void);
static bool sgp41Init(void);
static void checkForFirmwareUpdate(void);
static void firmwareCheckForUpdate(void);
static void otaHandlerCallback(OtaHandler::OtaState state, String mesasge);
static void displayExecuteOta(OtaHandler::OtaState state, String msg, int processing);
static int calculateMaxPeriod(int updateInterval);
@ -127,7 +126,7 @@ AgSchedule pmsSchedule(SENSOR_PM_UPDATE_INTERVAL, updatePm);
AgSchedule tempHumSchedule(SENSOR_TEMP_HUM_UPDATE_INTERVAL, tempHumUpdate);
AgSchedule tvocSchedule(SENSOR_TVOC_UPDATE_INTERVAL, updateTvoc);
AgSchedule watchdogFeedSchedule(60000, wdgFeedUpdate);
AgSchedule checkForUpdateSchedule(FIRMWARE_CHECK_FOR_UPDATE_MS, checkForFirmwareUpdate);
AgSchedule checkForUpdateSchedule(FIRMWARE_CHECK_FOR_UPDATE_MS, firmwareCheckForUpdate);
void setup() {
/** Serial for print debug message */
@ -165,7 +164,6 @@ void setup() {
apiClient.setAirGradient(ag);
openMetrics.setAirGradient(ag);
localServer.setAirGraident(ag);
measurements.setAirGradient(ag);
/** Example set custom API root URL */
// apiClient.setApiRoot("https://example.custom.api");
@ -210,11 +208,51 @@ void setup() {
connectToWifi = true;
}
// Initialize networking configuration
if (connectToWifi) {
initializeNetwork();
}
apiClient.begin();
if (wifiConnector.connect()) {
if (wifiConnector.isConnected()) {
mdnsInit();
localServer.begin();
initMqtt();
sendDataToAg();
#ifdef ESP8266
// ota not supported
#else
firmwareCheckForUpdate();
checkForUpdateSchedule.update();
#endif
apiClient.fetchServerConfiguration();
configSchedule.update();
if (apiClient.isFetchConfigureFailed()) {
if (ag->isOne()) {
if (apiClient.isNotAvailableOnDashboard()) {
stateMachine.displaySetAddToDashBoard();
stateMachine.displayHandle(
AgStateMachineWiFiOkServerOkSensorConfigFailed);
} else {
stateMachine.displayClearAddToDashBoard();
}
}
stateMachine.handleLeds(
AgStateMachineWiFiOkServerOkSensorConfigFailed);
delay(DISPLAY_DELAY_SHOW_CONTENT_MS);
} else {
ledBarEnabledUpdate();
}
} else {
if (wifiConnector.isConfigurePorttalTimeout()) {
oledDisplay.showRebooting();
delay(2500);
oledDisplay.setText("", "", "");
ESP.restart();
}
}
}
}
/** Set offline mode without saving, cause wifi is not configured */
if (wifiConnector.hasConfigurated() == false) {
Serial.println("Set offline mode cause wifi is not configurated");
@ -230,16 +268,15 @@ void setup() {
oledDisplay.setBrightness(configuration.getDisplayBrightness());
}
// Reset post schedulers to make sure measurements value already available
agApiPostSchedule.update();
// Update display and led bar after finishing setup to show dashboard
updateDisplayAndLedBar();
}
void loop() {
/** Run schedulers */
/** Handle schedule */
dispLedSchedule.run();
configSchedule.run();
agApiPostSchedule.run();
watchdogFeedSchedule.run();
if (configuration.hasSensorS8) {
co2Schedule.run();
@ -273,13 +310,15 @@ void loop() {
}
}
watchdogFeedSchedule.run();
/** Check for handle WiFi reconnect */
wifiConnector.handle();
/** factory reset handle */
factoryConfigReset();
/** check that local configuration changed then do some action */
/** check that local configura changed then do some action */
configUpdateHandle();
/** Firmware check for update handle */
@ -329,7 +368,8 @@ static void createMqttTask(void) {
/** Send data */
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();
if (mqttClient.publish(topic.c_str(), payload.c_str(),
@ -464,23 +504,17 @@ static bool sgp41Init(void) {
return false;
}
static void checkForFirmwareUpdate(void) {
static void firmwareCheckForUpdate(void) {
Serial.println();
Serial.print("checkForFirmwareUpdate: ");
Serial.println("firmwareCheckForUpdate:");
if (configuration.isOfflineMode() || configuration.isCloudConnectionDisabled()) {
Serial.println("mode is offline or cloud connection disabled, ignored");
return;
if (wifiConnector.isConnected()) {
Serial.println("firmwareCheckForUpdate: Perform");
otaHandler.setHandlerCallback(otaHandlerCallback);
otaHandler.updateFirmwareIfOutdated(ag->deviceId());
} else {
Serial.println("firmwareCheckForUpdate: Ignored");
}
if (!wifiConnector.isConnected()) {
Serial.println("wifi not connected, ignored");
return;
}
Serial.println("perform");
otaHandler.setHandlerCallback(otaHandlerCallback);
otaHandler.updateFirmwareIfOutdated(ag->deviceId());
Serial.println();
}
@ -613,7 +647,7 @@ static void sendDataToAg() {
}
stateMachine.handleLeds(AgStateMachineWiFiOkServerConnectFailed);
}
delay(DISPLAY_DELAY_SHOW_CONTENT_MS);
stateMachine.handleLeds(AgStateMachineNormal);
}
@ -837,82 +871,7 @@ static void failedHandler(String msg) {
}
}
void initializeNetwork() {
if (!wifiConnector.connect()) {
Serial.println("Cannot initiate wifi connection");
return;
}
if (!wifiConnector.isConnected()) {
Serial.println("Failed connect to WiFi");
if (wifiConnector.isConfigurePorttalTimeout()) {
oledDisplay.showRebooting();
delay(2500);
oledDisplay.setText("", "", "");
ESP.restart();
}
// Directly return because the rest of the function applied if wifi is connect only
return;
}
// Initiate local network configuration
mdnsInit();
localServer.begin();
// Apply mqtt connection if configured
initMqtt();
// Ignore the rest if cloud connection to AirGradient is disabled
if (configuration.isCloudConnectionDisabled()) {
return;
}
// Initialize api client
apiClient.begin();
// Send data for the first time to AG server at boot
sendDataToAg();
// OTA check
#ifdef ESP8266
// ota not supported
#else
checkForFirmwareUpdate();
checkForUpdateSchedule.update();
#endif
apiClient.fetchServerConfiguration();
configSchedule.update();
if (apiClient.isFetchConfigurationFailed()) {
if (ag->isOne()) {
if (apiClient.isNotAvailableOnDashboard()) {
stateMachine.displaySetAddToDashBoard();
stateMachine.displayHandle(AgStateMachineWiFiOkServerOkSensorConfigFailed);
} else {
stateMachine.displayClearAddToDashBoard();
}
}
stateMachine.handleLeds(AgStateMachineWiFiOkServerOkSensorConfigFailed);
delay(DISPLAY_DELAY_SHOW_CONTENT_MS);
} else {
ledBarEnabledUpdate();
}
}
static void configurationUpdateSchedule(void) {
if (configuration.isOfflineMode() || configuration.isCloudConnectionDisabled() ||
configuration.getConfigurationControl() == ConfigurationControl::ConfigurationControlLocal) {
Serial.println("Ignore fetch server configuration. Either mode is offline or cloud connection "
"disabled or configurationControl set to local");
apiClient.resetFetchConfigurationStatus();
return;
}
if (wifiConnector.isConnected() == false) {
Serial.println(" WiFi not connected, skipping fetch configuration from AG server");
return;
}
if (apiClient.fetchServerConfiguration()) {
configUpdateHandle();
}
@ -1010,21 +969,10 @@ static void updateDisplayAndLedBar(void) {
return;
}
if (wifiConnector.isConnected() == false) {
stateMachine.displayHandle(AgStateMachineWiFiLost);
stateMachine.handleLeds(AgStateMachineWiFiLost);
return;
}
if (configuration.isCloudConnectionDisabled()) {
// Ignore API related check since cloud is disabled
stateMachine.displayHandle(AgStateMachineNormal);
stateMachine.handleLeds(AgStateMachineNormal);
return;
}
AgStateMachineState state = AgStateMachineNormal;
if (apiClient.isFetchConfigurationFailed()) {
if (wifiConnector.isConnected() == false) {
state = AgStateMachineWiFiLost;
} else if (apiClient.isFetchConfigureFailed()) {
state = AgStateMachineSensorConfigFailed;
if (apiClient.isNotAvailableOnDashboard()) {
stateMachine.displaySetAddToDashBoard();
@ -1192,22 +1140,16 @@ static void sendDataToServer(void) {
int bootCount = measurements.bootCount() + 1;
measurements.setBootCount(bootCount);
if (configuration.isOfflineMode() || configuration.isCloudConnectionDisabled() ||
!configuration.isPostDataToAirGradient()) {
Serial.println("Skipping transmission of data to AG server. Either mode is offline or cloud connection is "
"disabled or post data to server disabled");
/** Ignore send data to server if postToAirGradient disabled */
if (configuration.isPostDataToAirGradient() == false || configuration.isOfflineMode()) {
return;
}
if (wifiConnector.isConnected() == false) {
Serial.println("WiFi not connected, skipping data transmission to AG server");
return;
}
String syncData = measurements.toString(false, fwMode, wifiConnector.RSSI());
String syncData = measurements.toString(false, fwMode, wifiConnector.RSSI(), *ag, configuration);
if (apiClient.postToServer(syncData)) {
Serial.println();
Serial.println("Online mode and isPostToAirGradient = true");
Serial.println(
"Online mode and isPostToAirGradient = true: watchdog reset");
Serial.println();
}

View File

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

View File

@ -1,5 +1,5 @@
name=AirGradient Air Quality Sensor
version=3.1.21
version=3.1.16
author=AirGradient <support@airgradient.com>
maintainer=AirGradient <support@airgradient.com>
sentence=ESP32-C3 / ESP8266 library for air quality monitor measuring PM, CO2, Temperature, TVOC and Humidity with OLED display.

View File

@ -34,6 +34,17 @@ void AgApiClient::begin(void) {
* @return false Failure
*/
bool AgApiClient::fetchServerConfiguration(void) {
if (config.getConfigurationControl() ==
ConfigurationControl::ConfigurationControlLocal ||
config.isOfflineMode()) {
logWarning("Ignore fetch server configuration");
// Clear server configuration failed flag, cause it's ignore but not
// really failed
getConfigFailed = false;
return false;
}
String uri = apiRoot + "/sensors/airgradient:" +
ag->deviceId() + "/one/config";
@ -47,8 +58,7 @@ bool AgApiClient::fetchServerConfiguration(void) {
}
#else
HTTPClient client;
client.setConnectTimeout(timeoutMs); // Set timeout when establishing connection to server
client.setTimeout(timeoutMs); // Timeout when waiting for response from AG server
client.setTimeout(timeoutMs);
if (apiRootChanged) {
// If apiRoot is changed, assume not using https
if (client.begin(uri) == false) {
@ -104,6 +114,15 @@ bool AgApiClient::fetchServerConfiguration(void) {
* @return false Failure
*/
bool AgApiClient::postToServer(String data) {
if (config.isPostDataToAirGradient() == false) {
logWarning("Ignore post data to server");
return true;
}
if (WiFi.isConnected() == false) {
return false;
}
String uri = apiRoot + "/sensors/airgradient:" + ag->deviceId() + "/measures";
#ifdef ESP8266
HTTPClient client;
@ -114,8 +133,7 @@ bool AgApiClient::postToServer(String data) {
}
#else
HTTPClient client;
client.setConnectTimeout(timeoutMs); // Set timeout when establishing connection to server
client.setTimeout(timeoutMs); // Timeout when waiting for response from AG server
client.setTimeout(timeoutMs);
if (apiRootChanged) {
// If apiRoot is changed, assume not using https
if (client.begin(uri) == false) {
@ -155,12 +173,7 @@ bool AgApiClient::postToServer(String data) {
* @return true Success
* @return false Failure
*/
bool AgApiClient::isFetchConfigurationFailed(void) { return getConfigFailed; }
/**
* @brief Reset status of get configuration from AirGradient cloud
*/
void AgApiClient::resetFetchConfigurationStatus(void) { getConfigFailed = false; }
bool AgApiClient::isFetchConfigureFailed(void) { return getConfigFailed; }
/**
* @brief Get failed status when post data to AirGradient cloud

View File

@ -31,7 +31,7 @@ private:
bool getConfigFailed;
bool postToServerFailed;
bool notAvailableOnDashboard = false; // Device not setup on Airgradient cloud dashboard.
uint16_t timeoutMs = 15000; // Default set to 15s
uint16_t timeoutMs = 10000; // Default set to 10s
public:
AgApiClient(Stream &stream, Configuration &config);
@ -40,8 +40,7 @@ public:
void begin(void);
bool fetchServerConfiguration(void);
bool postToServer(String data);
bool isFetchConfigurationFailed(void);
void resetFetchConfigurationStatus(void);
bool isFetchConfigureFailed(void);
bool isPostToServerFailed(void);
bool isNotAvailableOnDashboard(void);
void setAirGradient(AirGradient *ag);

View File

@ -22,17 +22,15 @@ const char *LED_BAR_MODE_NAMES[] = {
};
const char *PM_CORRECTION_ALGORITHM_NAMES[] = {
[COR_ALGO_PM_UNKNOWN] = "-", // This is only to pass "non-trivial designated initializers" error
[COR_ALGO_PM_NONE] = "none",
[COR_ALGO_PM_EPA_2021] = "epa_2021",
[COR_ALGO_PM_SLR_CUSTOM] = "custom",
};
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",
[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
@ -49,7 +47,6 @@ JSON_PROP_DEF(mqttBrokerUrl);
JSON_PROP_DEF(temperatureUnit);
JSON_PROP_DEF(configurationControl);
JSON_PROP_DEF(postDataToAirGradient);
JSON_PROP_DEF(disableCloudConnection);
JSON_PROP_DEF(ledBarBrightness);
JSON_PROP_DEF(displayBrightness);
JSON_PROP_DEF(co2CalibrationRequested);
@ -57,8 +54,6 @@ JSON_PROP_DEF(ledBarTestRequested);
JSON_PROP_DEF(offlineMode);
JSON_PROP_DEF(monitorDisplayCompensatedValues);
JSON_PROP_DEF(corrections);
JSON_PROP_DEF(atmp);
JSON_PROP_DEF(rhum);
#define jprop_model_default ""
#define jprop_country_default "TH"
@ -71,7 +66,6 @@ JSON_PROP_DEF(rhum);
#define jprop_temperatureUnit_default "c"
#define jprop_configurationControl_default String(CONFIGURATION_CONTROL_NAME[ConfigurationControl::ConfigurationControlBoth])
#define jprop_postDataToAirGradient_default true
#define jprop_disableCloudConnection_default false
#define jprop_ledBarBrightness_default 100
#define jprop_displayBrightness_default 100
#define jprop_offlineMode_default false
@ -110,8 +104,8 @@ PMCorrectionAlgorithm Configuration::matchPmAlgorithm(String algorithm) {
// If the input string matches an algorithm name, return the corresponding enum value
// Else return Unknown
const size_t enumSize = COR_ALGO_PM_SLR_CUSTOM + 1; // Get the actual size of the enum
PMCorrectionAlgorithm result = COR_ALGO_PM_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++) {
@ -120,63 +114,42 @@ PMCorrectionAlgorithm Configuration::matchPmAlgorithm(String algorithm) {
}
}
// If string not match from enum, check if correctionAlgorithm is one of the PM batch corrections
if (result == COR_ALGO_PM_UNKNOWN) {
// Check the substring "slr_PMS5003_xxxxxxxx"
if (algorithm.substring(0, 11) == "slr_PMS5003") {
// If it is, then its a custom correction
result = COR_ALGO_PM_SLR_CUSTOM;
}
}
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) {
if (!json.hasOwnProperty("corrections")) {
logInfo("corrections not found");
// TODO: need to response message?
Serial.println("corrections not found");
return false;
}
JSONVar corrections = json["corrections"];
if (!corrections.hasOwnProperty("pm02")) {
logWarning("pm02 not found");
Serial.println("pm02 not found");
return false;
}
JSONVar pm02 = corrections["pm02"];
if (!pm02.hasOwnProperty("correctionAlgorithm")) {
logWarning("pm02 correctionAlgorithm not found");
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 == COR_ALGO_PM_UNKNOWN) {
logWarning("Unknown 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 == COR_ALGO_PM_NONE || algo == COR_ALGO_PM_EPA_2021) {
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;
@ -193,7 +166,7 @@ bool Configuration::updatePmCorrection(JSONVar &json) {
// Check if pm02 has slr object
if (!pm02.hasOwnProperty("slr")) {
logWarning("slr not found");
Serial.println("slr not found");
return false;
}
@ -202,7 +175,7 @@ bool Configuration::updatePmCorrection(JSONVar &json) {
// Validate required slr properties exist
if (!slr.hasOwnProperty("intercept") || !slr.hasOwnProperty("scalingFactor") ||
!slr.hasOwnProperty("useEpa2021")) {
logWarning("Missing required slr properties");
Serial.println("Missing required slr properties");
return false;
}
@ -232,87 +205,6 @@ bool Configuration::updatePmCorrection(JSONVar &json) {
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)) {
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)
*
@ -380,7 +272,6 @@ void Configuration::defaultConfig(void) {
jconfig[jprop_configurationControl] = jprop_configurationControl_default;
jconfig[jprop_pmStandard] = jprop_pmStandard_default;
jconfig[jprop_temperatureUnit] = jprop_temperatureUnit_default;
jconfig[jprop_disableCloudConnection] = jprop_disableCloudConnection_default;
jconfig[jprop_postDataToAirGradient] = jprop_postDataToAirGradient_default;
if (ag->isOne()) {
jconfig[jprop_ledBarBrightness] = jprop_ledBarBrightness_default;
@ -398,8 +289,8 @@ void Configuration::defaultConfig(void) {
jconfig[jprop_offlineMode] = jprop_offlineMode_default;
jconfig[jprop_monitorDisplayCompensatedValues] = jprop_monitorDisplayCompensatedValues_default;
// PM2.5 default correction
pmCorrection.algorithm = COR_ALGO_PM_NONE;
// PM2.5 correction
pmCorrection.algorithm = None;
pmCorrection.changed = false;
pmCorrection.intercept = 0;
pmCorrection.scalingFactor = 1;
@ -901,21 +792,11 @@ bool Configuration::parse(String data, bool isLocal) {
}
}
// PM2.5 Corrections
// Corrections
if (updatePmCorrection(root)) {
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) {
updated = true;
saveConfig();
@ -1155,20 +1036,20 @@ void Configuration::toConfig(const char *buf) {
}
bool changed = false;
bool isConfigFieldInvalid = false;
bool isInvalid = false;
/** Validate country */
if (JSON.typeof_(jconfig[jprop_country]) != "string") {
isConfigFieldInvalid = true;
isInvalid = true;
} else {
String country = jconfig[jprop_country];
if (country.length() != 2) {
isConfigFieldInvalid = true;
isInvalid = true;
} else {
isConfigFieldInvalid = false;
isInvalid = false;
}
}
if (isConfigFieldInvalid) {
if (isInvalid) {
jconfig[jprop_country] = jprop_country_default;
changed = true;
logInfo("toConfig: country changed");
@ -1176,17 +1057,17 @@ void Configuration::toConfig(const char *buf) {
/** validate: PM standard */
if (JSON.typeof_(jconfig[jprop_pmStandard]) != "string") {
isConfigFieldInvalid = true;
isInvalid = true;
} else {
String standard = jconfig[jprop_pmStandard];
if (standard != getPMStandardString(true) &&
standard != getPMStandardString(false)) {
isConfigFieldInvalid = true;
isInvalid = true;
} else {
isConfigFieldInvalid = false;
isInvalid = false;
}
}
if (isConfigFieldInvalid) {
if (isInvalid) {
jconfig[jprop_pmStandard] = jprop_pmStandard_default;
changed = true;
logInfo("toConfig: pmStandard changed");
@ -1194,18 +1075,18 @@ void Configuration::toConfig(const char *buf) {
/** validate led bar mode */
if (JSON.typeof_(jconfig[jprop_ledBarMode]) != "string") {
isConfigFieldInvalid = true;
isInvalid = true;
} else {
String mode = jconfig[jprop_ledBarMode];
if (mode != getLedBarModeName(LedBarMode::LedBarModeCO2) &&
mode != getLedBarModeName(LedBarMode::LedBarModeOff) &&
mode != getLedBarModeName(LedBarMode::LedBarModePm)) {
isConfigFieldInvalid = true;
isInvalid = true;
} else {
isConfigFieldInvalid = false;
isInvalid = false;
}
}
if (isConfigFieldInvalid) {
if (isInvalid) {
jconfig[jprop_ledBarMode] = jprop_ledBarMode_default;
changed = true;
logInfo("toConfig: ledBarMode changed");
@ -1213,11 +1094,11 @@ void Configuration::toConfig(const char *buf) {
/** validate abcday */
if (JSON.typeof_(jconfig[jprop_abcDays]) != "number") {
isConfigFieldInvalid = true;
isInvalid = true;
} else {
isConfigFieldInvalid = false;
isInvalid = false;
}
if (isConfigFieldInvalid) {
if (isInvalid) {
jconfig[jprop_abcDays] = jprop_abcDays_default;
changed = true;
logInfo("toConfig: abcDays changed");
@ -1225,16 +1106,16 @@ void Configuration::toConfig(const char *buf) {
/** validate tvoc learning offset */
if (JSON.typeof_(jconfig[jprop_tvocLearningOffset]) != "number") {
isConfigFieldInvalid = true;
isInvalid = true;
} else {
int value = jconfig[jprop_tvocLearningOffset];
if (value < 0) {
isConfigFieldInvalid = true;
isInvalid = true;
} else {
isConfigFieldInvalid = false;
isInvalid = false;
}
}
if (isConfigFieldInvalid) {
if (isInvalid) {
jconfig[jprop_tvocLearningOffset] = jprop_tvocLearningOffset_default;
changed = true;
logInfo("toConfig: tvocLearningOffset changed");
@ -1242,16 +1123,16 @@ void Configuration::toConfig(const char *buf) {
/** validate nox learning offset */
if (JSON.typeof_(jconfig[jprop_noxLearningOffset]) != "number") {
isConfigFieldInvalid = true;
isInvalid = true;
} else {
int value = jconfig[jprop_noxLearningOffset];
if (value < 0) {
isConfigFieldInvalid = true;
isInvalid = true;
} else {
isConfigFieldInvalid = false;
isInvalid = false;
}
}
if (isConfigFieldInvalid) {
if (isInvalid) {
jconfig[jprop_noxLearningOffset] = jprop_noxLearningOffset_default;
changed = true;
logInfo("toConfig: noxLearningOffset changed");
@ -1259,11 +1140,11 @@ void Configuration::toConfig(const char *buf) {
/** validate mqtt broker */
if (JSON.typeof_(jconfig[jprop_mqttBrokerUrl]) != "string") {
isConfigFieldInvalid = true;
isInvalid = true;
} else {
isConfigFieldInvalid = false;
isInvalid = false;
}
if (isConfigFieldInvalid) {
if (isInvalid) {
changed = true;
jconfig[jprop_mqttBrokerUrl] = jprop_mqttBrokerUrl_default;
logInfo("toConfig: mqttBroker changed");
@ -1271,36 +1152,24 @@ void Configuration::toConfig(const char *buf) {
/** Validate temperature unit */
if (JSON.typeof_(jconfig[jprop_temperatureUnit]) != "string") {
isConfigFieldInvalid = true;
isInvalid = true;
} else {
String unit = jconfig[jprop_temperatureUnit];
if (unit != "c" && unit != "f") {
isConfigFieldInvalid = true;
isInvalid = true;
} else {
isConfigFieldInvalid = false;
isInvalid = false;
}
}
if (isConfigFieldInvalid) {
if (isInvalid) {
jconfig[jprop_temperatureUnit] = jprop_temperatureUnit_default;
changed = true;
logInfo("toConfig: temperatureUnit changed");
}
/** validate disableCloudConnection configuration */
if (JSON.typeof_(jconfig[jprop_disableCloudConnection]) != "boolean") {
isConfigFieldInvalid = true;
} else {
isConfigFieldInvalid = false;
}
if (isConfigFieldInvalid) {
jconfig[jprop_disableCloudConnection] = jprop_disableCloudConnection_default;
changed = true;
logInfo("toConfig: disableCloudConnection changed");
}
/** validate configuration control */
if (JSON.typeof_(jprop_configurationControl) != "string") {
isConfigFieldInvalid = true;
isInvalid = true;
} else {
String ctrl = jconfig[jprop_configurationControl];
if (ctrl != String(CONFIGURATION_CONTROL_NAME
@ -1309,12 +1178,12 @@ void Configuration::toConfig(const char *buf) {
[ConfigurationControl::ConfigurationControlLocal]) &&
ctrl != String(CONFIGURATION_CONTROL_NAME
[ConfigurationControl::ConfigurationControlCloud])) {
isConfigFieldInvalid = true;
isInvalid = true;
} else {
isConfigFieldInvalid = false;
isInvalid = false;
}
}
if (isConfigFieldInvalid) {
if (isInvalid) {
jconfig[jprop_configurationControl] =jprop_configurationControl_default;
changed = true;
logInfo("toConfig: configurationControl changed");
@ -1322,11 +1191,11 @@ void Configuration::toConfig(const char *buf) {
/** Validate post to airgradient cloud */
if (JSON.typeof_(jconfig[jprop_postDataToAirGradient]) != "boolean") {
isConfigFieldInvalid = true;
isInvalid = true;
} else {
isConfigFieldInvalid = false;
isInvalid = false;
}
if (isConfigFieldInvalid) {
if (isInvalid) {
jconfig[jprop_postDataToAirGradient] = jprop_postDataToAirGradient_default;
changed = true;
logInfo("toConfig: postToAirGradient changed");
@ -1334,16 +1203,16 @@ void Configuration::toConfig(const char *buf) {
/** validate led bar brightness */
if (JSON.typeof_(jconfig[jprop_ledBarBrightness]) != "number") {
isConfigFieldInvalid = true;
isInvalid = true;
} else {
int value = jconfig[jprop_ledBarBrightness];
if (value < 0 || value > 100) {
isConfigFieldInvalid = true;
isInvalid = true;
} else {
isConfigFieldInvalid = false;
isInvalid = false;
}
}
if (isConfigFieldInvalid) {
if (isInvalid) {
jconfig[jprop_ledBarBrightness] = jprop_ledBarBrightness_default;
changed = true;
logInfo("toConfig: ledBarBrightness changed");
@ -1351,16 +1220,16 @@ void Configuration::toConfig(const char *buf) {
/** Validate display brightness */
if (JSON.typeof_(jconfig[jprop_displayBrightness]) != "number") {
isConfigFieldInvalid = true;
isInvalid = true;
} else {
int value = jconfig[jprop_displayBrightness];
if (value < 0 || value > 100) {
isConfigFieldInvalid = true;
isInvalid = true;
} else {
isConfigFieldInvalid = false;
isInvalid = false;
}
}
if (isConfigFieldInvalid) {
if (isInvalid) {
jconfig[jprop_displayBrightness] = jprop_displayBrightness_default;
changed = true;
logInfo("toConfig: displayBrightness changed");
@ -1379,31 +1248,15 @@ void Configuration::toConfig(const char *buf) {
jprop_monitorDisplayCompensatedValues_default;
}
// PM2.5 correction
/// Set default first before parsing local config
pmCorrection.algorithm = COR_ALGO_PM_NONE;
// 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
// Load correction from saved config
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) {
saveConfig();
}
@ -1481,17 +1334,6 @@ void Configuration::setOfflineModeWithoutSave(bool offline) {
_offlineMode = offline;
}
bool Configuration::isCloudConnectionDisabled(void) {
bool disabled = jconfig[jprop_disableCloudConnection];
return disabled;
}
void Configuration::setDisableCloudConnection(bool disable) {
logInfo("Set DisableCloudConnection to " + String(disable ? "True" : "False"));
jconfig[jprop_disableCloudConnection] = disable;
saveConfig();
}
bool Configuration::isLedBarModeChanged(void) {
bool changed = _ledBarModeChanged;
_ledBarModeChanged = false;
@ -1527,16 +1369,14 @@ bool Configuration::isPMCorrectionChanged(void) {
*/
bool Configuration::isPMCorrectionEnabled(void) {
PMCorrection pmCorrection = getPMCorrection();
if (pmCorrection.algorithm == COR_ALGO_PM_NONE ||
pmCorrection.algorithm == COR_ALGO_PM_UNKNOWN) {
if (pmCorrection.algorithm == PMCorrectionAlgorithm::None ||
pmCorrection.algorithm == PMCorrectionAlgorithm::Unknown) {
return false;
}
return true;
}
Configuration::PMCorrection Configuration::getPMCorrection(void) { return pmCorrection; }
Configuration::TempHumCorrection Configuration::getTempCorrection(void) { return tempCorrection; }
Configuration::TempHumCorrection Configuration::getHumCorrection(void) { return rhumCorrection; }
Configuration::PMCorrection Configuration::getPMCorrection(void) {
return pmCorrection;
}

View File

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

View File

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

View File

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

View File

@ -27,53 +27,12 @@
#define json_prop_noxRaw "noxRaw"
#define json_prop_co2 "rco2"
Measurements::Measurements(Configuration &config) : config(config) {
Measurements::Measurements() {
#ifndef ESP8266
_resetReason = (int)ESP_RST_UNKNOWN;
#endif
/* Set invalid value for each measurements as default value when initialized*/
_temperature[0].update.avg = utils::getInvalidTemperature();
_temperature[1].update.avg = utils::getInvalidTemperature();
_humidity[0].update.avg = utils::getInvalidHumidity();
_humidity[1].update.avg = utils::getInvalidHumidity();
_co2.update.avg = utils::getInvalidCO2();
_tvoc.update.avg = utils::getInvalidVOC();
_tvoc_raw.update.avg = utils::getInvalidVOC();
_nox.update.avg = utils::getInvalidNOx();
_nox_raw.update.avg = utils::getInvalidNOx();
_pm_03_pc[0].update.avg = utils::getInvalidPmValue();
_pm_03_pc[1].update.avg = utils::getInvalidPmValue();
_pm_05_pc[0].update.avg = utils::getInvalidPmValue();
_pm_05_pc[1].update.avg = utils::getInvalidPmValue();
_pm_5_pc[0].update.avg = utils::getInvalidPmValue();
_pm_5_pc[1].update.avg = utils::getInvalidPmValue();
_pm_01[0].update.avg = utils::getInvalidPmValue();
_pm_01_sp[0].update.avg = utils::getInvalidPmValue();
_pm_01_pc[0].update.avg = utils::getInvalidPmValue();
_pm_01[1].update.avg = utils::getInvalidPmValue();
_pm_01_sp[1].update.avg = utils::getInvalidPmValue();
_pm_01_pc[1].update.avg = utils::getInvalidPmValue();
_pm_25[0].update.avg = utils::getInvalidPmValue();
_pm_25_sp[0].update.avg = utils::getInvalidPmValue();
_pm_25_pc[0].update.avg = utils::getInvalidPmValue();
_pm_25[1].update.avg = utils::getInvalidPmValue();
_pm_25_sp[1].update.avg = utils::getInvalidPmValue();
_pm_25_pc[1].update.avg = utils::getInvalidPmValue();
_pm_10[0].update.avg = utils::getInvalidPmValue();
_pm_10_sp[0].update.avg = utils::getInvalidPmValue();
_pm_10_pc[0].update.avg = utils::getInvalidPmValue();
_pm_10[1].update.avg = utils::getInvalidPmValue();
_pm_10_sp[1].update.avg = utils::getInvalidPmValue();
_pm_10_pc[1].update.avg = utils::getInvalidPmValue();
}
void Measurements::setAirGradient(AirGradient *ag) { this->ag = ag; }
void Measurements::maxPeriod(MeasurementType type, int max) {
switch (type) {
case Temperature:
@ -447,12 +406,12 @@ float Measurements::getAverage(MeasurementType type, int ch) {
// Sanity check to validate channel, assert if invalid
validateChannel(ch);
bool undefined = false;
// Follow array indexing just for get address of the value type
ch = ch - 1;
// Define data point source. Data type doesn't matter because only to get the average value
FloatValue *temporary = nullptr;
Update update;
float measurementAverage;
switch (type) {
case CO2:
@ -475,12 +434,12 @@ float Measurements::getAverage(MeasurementType type, int ch) {
break;
default:
// Invalidate, measurements type not handled
undefined = true;
measurementAverage = -1000;
break;
};
// Sanity check if measurement type is not defined
if (undefined) {
// Sanity check if measurement type is not defined
if (measurementAverage == -1000) {
Serial.printf("ERROR! %s is not defined on get average value function\n", measurementTypeStr(type).c_str());
delay(1000);
assert(0);
@ -576,70 +535,7 @@ void Measurements::validateChannel(int ch) {
}
}
float Measurements::getCorrectedTempHum(MeasurementType type, int ch, bool forceCorrection) {
// 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, bool forceCorrection) {
float Measurements::getCorrectedPM25(AirGradient &ag, Configuration &config, bool useAvg, int ch) {
float pm25;
float corrected;
float humidity;
@ -658,27 +554,21 @@ float Measurements::getCorrectedPM25(bool useAvg, int ch, bool forceCorrection)
Configuration::PMCorrection pmCorrection = config.getPMCorrection();
switch (pmCorrection.algorithm) {
case PMCorrectionAlgorithm::COR_ALGO_PM_UNKNOWN:
case PMCorrectionAlgorithm::COR_ALGO_PM_NONE: {
// If correction is Unknown or None, then default is None
// Unless forceCorrection enabled
if (forceCorrection) {
corrected = ag->pms5003.compensate(pm25, humidity);
} else {
corrected = pm25;
}
case PMCorrectionAlgorithm::Unknown:
case PMCorrectionAlgorithm::None:
// If correction is Unknown, then default is None
corrected = pm25;
break;
}
case PMCorrectionAlgorithm::COR_ALGO_PM_EPA_2021:
corrected = ag->pms5003.compensate(pm25, humidity);
case PMCorrectionAlgorithm::EPA_2021:
corrected = ag.pms5003.compensate(pm25, humidity);
break;
default: {
// All SLR correction using the same flow, hence default condition
corrected = ag->pms5003.slrCorrection(pm25, pm003Count, pmCorrection.scalingFactor,
pmCorrection.intercept);
corrected = ag.pms5003.slrCorrection(pm25, pm003Count, pmCorrection.scalingFactor,
pmCorrection.intercept);
if (pmCorrection.useEPA) {
// Add EPA compensation on top of SLR
corrected = ag->pms5003.compensate(corrected, humidity);
corrected = ag.pms5003.compensate(corrected, humidity);
}
}
}
@ -686,33 +576,34 @@ float Measurements::getCorrectedPM25(bool useAvg, int ch, bool forceCorrection)
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;
if (ag->isOne() || (ag->isPro4_2()) || ag->isPro3_3() || ag->isBasic()) {
root = buildIndoor(localServer);
if (ag.isOne() || (ag.isPro4_2()) || ag.isPro3_3() || ag.isBasic()) {
root = buildIndoor(localServer, ag, config);
} else {
root = buildOutdoor(localServer, fwMode);
root = buildOutdoor(localServer, fwMode, ag, config);
}
// CO2
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
if (config.hasSensorSGP) {
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)) {
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)) {
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)) {
root[json_prop_noxRaw] = ag->round2(_nox_raw.update.avg);
root[json_prop_noxRaw] = ag.round2(_nox_raw.update.avg);
}
}
@ -721,11 +612,11 @@ String Measurements::toString(bool localServer, AgFirmwareMode fwMode, int rssi)
root["wifi"] = rssi;
if (localServer) {
if (ag->isOne()) {
if (ag.isOne()) {
root["ledMode"] = config.getLedBarModeName();
}
root["serialno"] = ag->deviceId();
root["firmware"] = ag->getVersion();
root["serialno"] = ag.deviceId();
root["firmware"] = ag.getVersion();
root["model"] = AgFirmwareModeName(fwMode);
} else {
#ifndef ESP8266
@ -739,7 +630,8 @@ String Measurements::toString(bool localServer, AgFirmwareMode fwMode, int rssi)
return result;
}
JSONVar Measurements::buildOutdoor(bool localServer, AgFirmwareMode fwMode) {
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:
@ -748,16 +640,14 @@ JSONVar Measurements::buildOutdoor(bool localServer, AgFirmwareMode fwMode) {
/// compensated values if requested by local server
/// Set ch based on hasSensorPMSx
if (config.hasSensorPMS1) {
outdoor = buildPMS(1, false, true, localServer);
outdoor = buildPMS(ag, 1, false, true, localServer);
if (!localServer) {
outdoor[json_prop_pmFirmware] =
pms5003TFirmwareVersion(ag->pms5003t_1.getFirmwareVersion());
outdoor[json_prop_pmFirmware] = pms5003TFirmwareVersion(ag.pms5003t_1.getFirmwareVersion());
}
} else {
outdoor = buildPMS(2, false, true, localServer);
outdoor = buildPMS(ag, 2, false, true, localServer);
if (!localServer) {
outdoor[json_prop_pmFirmware] =
pms5003TFirmwareVersion(ag->pms5003t_2.getFirmwareVersion());
outdoor[json_prop_pmFirmware] = pms5003TFirmwareVersion(ag.pms5003t_2.getFirmwareVersion());
}
}
} else {
@ -766,55 +656,65 @@ JSONVar Measurements::buildOutdoor(bool localServer, AgFirmwareMode fwMode) {
/// 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(1, true, true, localServer);
outdoor = buildPMS(ag, 1, true, true, localServer);
// PMS5003T version
if (!localServer) {
outdoor["channels"]["1"][json_prop_pmFirmware] =
pms5003TFirmwareVersion(ag->pms5003t_1.getFirmwareVersion());
pms5003TFirmwareVersion(ag.pms5003t_1.getFirmwareVersion());
outdoor["channels"]["2"][json_prop_pmFirmware] =
pms5003TFirmwareVersion(ag->pms5003t_2.getFirmwareVersion());
pms5003TFirmwareVersion(ag.pms5003t_2.getFirmwareVersion());
}
}
return outdoor;
}
JSONVar Measurements::buildIndoor(bool localServer) {
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
/// Include compensated calculation
indoor = buildPMS(1, false, false, true);
/// 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());
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);
indoor[json_prop_temp] = ag.round2(_temperature[0].update.avg);
if (localServer) {
indoor[json_prop_tempCompensated] = ag->round2(getCorrectedTempHum(Temperature));
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);
indoor[json_prop_rhum] = ag.round2(_humidity[0].update.avg);
if (localServer) {
indoor[json_prop_rhumCompensated] = ag->round2(getCorrectedTempHum(Humidity));
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(int ch, bool allCh, bool withTempHum, bool compensate) {
JSONVar Measurements::buildPMS(AirGradient &ag, int ch, bool allCh, bool withTempHum,
bool compensate) {
JSONVar pms;
// When only one of the channel
@ -823,86 +723,89 @@ JSONVar Measurements::buildPMS(int ch, bool allCh, bool withTempHum, bool compen
validateChannel(ch);
// Follow array indexing just for get address of the value type
int chIndex = ch - 1;
ch = ch - 1;
if (utils::isValidPm(_pm_01[chIndex].update.avg)) {
pms[json_prop_pm01Ae] = ag->round2(_pm_01[chIndex].update.avg);
if (utils::isValidPm(_pm_01[ch].update.avg)) {
pms[json_prop_pm01Ae] = ag.round2(_pm_01[ch].update.avg);
}
if (utils::isValidPm(_pm_25[chIndex].update.avg)) {
pms[json_prop_pm25Ae] = ag->round2(_pm_25[chIndex].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[chIndex].update.avg)) {
pms[json_prop_pm10Ae] = ag->round2(_pm_10[chIndex].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[chIndex].update.avg)) {
pms[json_prop_pm01Sp] = ag->round2(_pm_01_sp[chIndex].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[chIndex].update.avg)) {
pms[json_prop_pm25Sp] = ag->round2(_pm_25_sp[chIndex].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[chIndex].update.avg)) {
pms[json_prop_pm10Sp] = ag->round2(_pm_10_sp[chIndex].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[chIndex].update.avg)) {
pms[json_prop_pm03Count] = ag->round2(_pm_03_pc[chIndex].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[chIndex].update.avg)) {
pms[json_prop_pm05Count] = ag->round2(_pm_05_pc[chIndex].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[chIndex].update.avg)) {
pms[json_prop_pm1Count] = ag->round2(_pm_01_pc[chIndex].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[chIndex].update.avg)) {
pms[json_prop_pm25Count] = ag->round2(_pm_25_pc[chIndex].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[chIndex].listValues.empty() == false) {
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[chIndex].update.avg)) {
pms[json_prop_pm5Count] = ag->round2(_pm_5_pc[chIndex].update.avg);
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[chIndex].listValues.empty() == false) {
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[chIndex].update.avg)) {
pms[json_prop_pm10Count] = ag->round2(_pm_10_pc[chIndex].update.avg);
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[chIndex].update.avg)) {
pms[json_prop_temp] = ag->round2(_temperature[chIndex].update.avg);
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 = getCorrectedTempHum(Temperature, ch, true);
_vc = ag.pms5003t_1.compensateTemp(_temperature[ch].update.avg);
if (utils::isValidTemperature(_vc)) {
pms[json_prop_tempCompensated] = ag->round2(_vc);
pms[json_prop_tempCompensated] = ag.round2(_vc);
}
}
}
// Set humidity if valid
if (utils::isValidHumidity(_humidity[chIndex].update.avg)) {
pms[json_prop_rhum] = ag->round2(_humidity[chIndex].update.avg);
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 = getCorrectedTempHum(Humidity, ch, true);
if (utils::isValidHumidity(_vc)) {
pms[json_prop_rhumCompensated] = ag->round2(_vc);
_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[chIndex].update.avg) &&
utils::isValidHumidity(_humidity[chIndex].update.avg)) {
float pm25 = getCorrectedPM25(true, ch, true);
pms[json_prop_pm25Compensated] = ag->round2(pm25);
// 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);
}
}
}
}
@ -916,144 +819,144 @@ JSONVar Measurements::buildPMS(int ch, bool allCh, bool withTempHum, bool compen
/// 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);
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);
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);
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);
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);
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);
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);
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);
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);
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);
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);
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);
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);
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);
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);
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);
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);
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);
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);
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::isValidPm03Count(_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[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)) {
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[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);
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::isValidPm03Count(_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[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)) {
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[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);
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::isValidPm03Count(_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[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)) {
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[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);
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::isValidPm03Count(_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[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)) {
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[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
@ -1065,40 +968,40 @@ JSONVar Measurements::buildPMS(int ch, bool allCh, bool withTempHum, bool compen
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);
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 temp1 = getCorrectedTempHum(Temperature, 1, true);
float temp2 = getCorrectedTempHum(Temperature, 2, true);
float tempAverage = (temp1 + temp2) / 2.0f;
pms[json_prop_tempCompensated] = ag->round2(tempAverage);
pms["channels"]["1"][json_prop_tempCompensated] = ag->round2(temp1);
pms["channels"]["2"][json_prop_tempCompensated] = ag->round2(temp2);
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);
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 = getCorrectedTempHum(Temperature, 1, true);
pms[json_prop_tempCompensated] = ag->round2(temp1);
pms["channels"]["1"][json_prop_tempCompensated] = ag->round2(temp1);
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);
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 = getCorrectedTempHum(Temperature, 2, true);
pms[json_prop_tempCompensated] = ag->round2(temp2);
pms["channels"]["2"][json_prop_tempCompensated] = ag->round2(temp2);
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);
}
}
@ -1106,40 +1009,40 @@ JSONVar Measurements::buildPMS(int ch, bool allCh, bool withTempHum, bool compen
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);
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 hum1 = getCorrectedTempHum(Humidity, 1, true);
float hum2 = getCorrectedTempHum(Humidity, 2, true);
float humAverage = (hum1 + hum2) / 2.0f;
pms[json_prop_rhumCompensated] = ag->round2(humAverage);
pms["channels"]["1"][json_prop_rhumCompensated] = ag->round2(hum1);
pms["channels"]["2"][json_prop_rhumCompensated] = ag->round2(hum2);
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);
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 = getCorrectedTempHum(Humidity, 1, true);
pms[json_prop_rhumCompensated] = ag->round2(hum1);
pms["channels"]["1"][json_prop_rhumCompensated] = ag->round2(hum1);
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);
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 = getCorrectedTempHum(Humidity, 2, true);
pms[json_prop_rhumCompensated] = ag->round2(hum2);
pms["channels"]["2"][json_prop_rhumCompensated] = ag->round2(hum2);
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);
}
}
@ -1150,22 +1053,22 @@ JSONVar Measurements::buildPMS(int ch, bool allCh, bool withTempHum, bool compen
float pm25_comp2 = utils::getInvalidPmValue();
if (utils::isValidPm(_pm_25[0].update.avg) &&
utils::isValidHumidity(_humidity[0].update.avg)) {
pm25_comp1 = getCorrectedPM25(true, 1, true);
pms["channels"]["1"][json_prop_pm25Compensated] = ag->round2(pm25_comp1);
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 = getCorrectedPM25(true, 2, true);
pms["channels"]["2"][json_prop_pm25Compensated] = ag->round2(pm25_comp2);
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);
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);
pms[json_prop_pm25Compensated] = ag.round2(pm25_comp1);
} 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:
Measurements(Configuration &config);
Measurements();
~Measurements() {}
void setAirGradient(AirGradient *ag);
// Enumeration for every AG measurements
enum MeasurementType {
Temperature,
@ -125,34 +123,24 @@ public:
*/
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
*
*
* 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 ch MeasurementType channel
* @param forceCorrection force using correction even though config correction is not applied, default to EPA
* @return float Corrected PM2.5 value
*/
float getCorrectedPM25(bool useAvg = false, int ch = 1, bool forceCorrection = false);
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);
String toString(bool localServer, AgFirmwareMode fwMode, int rssi, AirGradient &ag,
Configuration &config);
/**
* Set to true if want to debug every update value
@ -167,9 +155,6 @@ public:
#endif
private:
Configuration &config;
AirGradient *ag;
// Some declared as an array (channel), because FW_MODE_O_1PPx has two PMS5003T
FloatValue _temperature[2];
FloatValue _humidity[2];
@ -228,9 +213,10 @@ private:
*/
void validateChannel(int ch);
JSONVar buildOutdoor(bool localServer, AgFirmwareMode fwMode);
JSONVar buildIndoor(bool localServer);
JSONVar buildPMS(int ch, bool allCh, bool withTempHum, bool compensate);
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_ */

View File

@ -81,15 +81,16 @@ bool WifiConnector::connect(void) {
// ssid = "AG-" + String(ESP.getChipId(), HEX);
WIFI()->setConfigPortalTimeout(WIFI_CONNECT_COUNTDOWN_MAX);
WiFiManagerParameter disableCloud("chbPostToAg", "Prevent Connection to AirGradient Server", "T",
2, "type=\"checkbox\" ", WFM_LABEL_AFTER);
WIFI()->addParameter(&disableCloud);
WiFiManagerParameter disableCloudInfo(
WiFiManagerParameter postToAg("chbPostToAg",
"Prevent Connection to AirGradient Server", "T",
2, "type=\"checkbox\" ", WFM_LABEL_AFTER);
WIFI()->addParameter(&postToAg);
WiFiManagerParameter postToAgInfo(
"<p>Prevent connection to the AirGradient Server. Important: Only enable "
"it if you are sure you don't want to use any AirGradient cloud "
"features. As a result you will not receive automatic firmware updates, "
"configuration settings from cloud and the measure data will not reach the AirGradient dashboard.</p>");
WIFI()->addParameter(&disableCloudInfo);
"features. As a result you will not receive automatic firmware updates "
"and your data will not reach the AirGradient dashboard.</p>");
WIFI()->addParameter(&postToAgInfo);
WIFI()->autoConnect(ssid.c_str(), WIFI_HOTSPOT_PASSWORD_DEFAULT);
@ -173,11 +174,12 @@ bool WifiConnector::connect(void) {
logInfo("WiFi Connected: " + WiFi.SSID() + " IP: " + localIpStr());
if (hasPortalConfig) {
String result = String(disableCloud.getValue());
logInfo("Setting disableCloudConnection set from " +
String(config.isCloudConnectionDisabled() ? "True" : "False") + String(" to ") +
String(result == "T" ? "True" : "False") + String(" successful"));
config.setDisableCloudConnection(result == "T");
String result = String(postToAg.getValue());
logInfo("Setting postToAirGradient set from " +
String(config.isPostDataToAirGradient() ? "True" : "False") +
String(" to ") + String(result != "T" ? "True" : "False") +
String(" successful"));
config.setPostToAirGradient(result != "T");
}
hasPortalConfig = false;
}

View File

@ -15,7 +15,7 @@
#include "Main/utils.h"
#ifndef GIT_VERSION
#define GIT_VERSION "3.1.21-snap"
#define GIT_VERSION "3.1.16-snap"
#endif
#ifndef ESP8266

View File

@ -95,29 +95,26 @@ enum ConfigurationControl {
};
enum PMCorrectionAlgorithm {
COR_ALGO_PM_UNKNOWN, // Unknown algorithm
COR_ALGO_PM_NONE, // No PM correction
COR_ALGO_PM_EPA_2021,
COR_ALGO_PM_SLR_CUSTOM,
};
// 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
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 */
FW_MODE_O_1PPT, /** PMS5003T_1, PMS5003T_2, SGP41 */
FW_MODE_O_1PP, /** PMS5003T_1, PMS5003T_2 */
FW_MODE_O_1PS, /** PMS5003T, S8 */
FW_MODE_O_1P, /** PMS5003T */
FW_MODE_I_42PS, /** DIY_PRO 4.2 */
FW_MODE_I_33PS, /** DIY_PRO 3.3 */
FW_MODE_I_9PSL, /** ONE_INDOOR */
FW_MODE_O_1PST, /** PMS5003T, S8 and SGP41 */
FW_MODE_O_1PPT, /** PMS5003T_1, PMS5003T_2, SGP41 */
FW_MODE_O_1PP, /** PMS5003T_1, PMS5003T_2 */
FW_MODE_O_1PS, /** PMS5003T, S8 */
FW_MODE_O_1P, /** PMS5003T */
FW_MODE_I_42PS, /** DIY_PRO 4.2 */
FW_MODE_I_33PS, /** DIY_PRO 3.3 */
FW_MODE_I_BASIC_40PS, /** DIY_BASIC 4.0 */
};
const char *AgFirmwareModeName(AgFirmwareMode mode);