mirror of
https://github.com/airgradienthq/arduino.git
synced 2025-06-28 01:00:59 +02:00
Compare commits
146 Commits
3.1.9
...
feat/local
Author | SHA1 | Date | |
---|---|---|---|
227bd518c9 | |||
d0caee99aa | |||
3162030800 | |||
6b6116ab6d | |||
15dec1713d | |||
70e626cbc9 | |||
c003912d7a | |||
902797ceb0 | |||
430e908d88 | |||
6cb06986c3 | |||
e3156d438c | |||
4ae0206e6b | |||
83a4eddc37 | |||
67b71f583b | |||
e2798f1193 | |||
f4357cca7e | |||
20dcea20ad | |||
cfe6fa9fd5 | |||
391186dd59 | |||
a9f7f72871 | |||
9a3f71b33c | |||
d8f433bd3e | |||
da414bf3fc | |||
d225af623a | |||
b7d22c2136 | |||
6cd5e9f4b8 | |||
0cec71ceb6 | |||
424d1d89fa | |||
6186e3eca0 | |||
b79c4e74e2 | |||
baa8601b5c | |||
a9fa7b6e63 | |||
1034f1892a | |||
859c1a7e92 | |||
bce46445d6 | |||
be7ca28a0e | |||
12e6f72b85 | |||
e95627ece6 | |||
80b9ae11d8 | |||
1937e3d59e | |||
107fb21331 | |||
ccc1ab463a | |||
39ef69cbdf | |||
3473e30e2e | |||
566f8a63b4 | |||
9e4d52454b | |||
5f5e985309 | |||
d638573ca7 | |||
79fbd901bd | |||
3644dc43fe | |||
03fa62d8f0 | |||
902a768f28 | |||
1de9344f43 | |||
46f6309b77 | |||
a6b48acb41 | |||
1b4d89e1a1 | |||
0d2b0fb657 | |||
9f08af44b0 | |||
6b661cdeb7 | |||
dc299c4b54 | |||
2f595b4e41 | |||
a30535f75f | |||
a513943cba | |||
96bb6952fb | |||
10653bfe26 | |||
c7f89fa7b7 | |||
b11c461b60 | |||
404c14aad2 | |||
bfbae680fd | |||
3ae5982380 | |||
db2c2ef052 | |||
593547cdbe | |||
673c46950d | |||
ae0b4038d4 | |||
cac0bd5355 | |||
3d6203dabf | |||
1db8fbefe9 | |||
d850d27dc1 | |||
f49e4a4b37 | |||
75f88b0009 | |||
c6961b3ca8 | |||
ade72ff3b8 | |||
9fbbea22ff | |||
7b0381dea3 | |||
5867d0f1d5 | |||
a98d77e0c3 | |||
641003f9d1 | |||
0275aee370 | |||
ea46b812c1 | |||
16c932962a | |||
f90b2e1a07 | |||
3a9bb16c09 | |||
bb754edc51 | |||
1d991b1004 | |||
3ebcc584a4 | |||
4d40ae421c | |||
3004a82e7e | |||
4af5ca2665 | |||
e6696f3d41 | |||
2b33823162 | |||
bf0768c7da | |||
33e2977eb4 | |||
85e779cfc2 | |||
4783684443 | |||
3b0c77ca4d | |||
eeba41f497 | |||
fd1f35f6d8 | |||
eb76eff403 | |||
4673999dda | |||
83aa6a4502 | |||
8a87b865e6 | |||
c3068be6e9 | |||
63bb5f8ddb | |||
8548d3e9f4 | |||
f7e1363da9 | |||
2ffe0a62aa | |||
2cda36ed0d | |||
7de2d0cc30 | |||
f478dd16c8 | |||
43ca0a2c2e | |||
84884d0c15 | |||
f36f860c2e | |||
e47a9057ea | |||
399b4ca1dc | |||
2e4f4643fa | |||
0ccf46c219 | |||
76a2f332d7 | |||
ed344d3e1a | |||
2082a2fa93 | |||
e145d32714 | |||
a2c19438c0 | |||
ac838efdb5 | |||
751d4e8380 | |||
6925b1ac9a | |||
77a23b4202 | |||
ea91cf9b6c | |||
467b3e8637 | |||
2a5cf78b68 | |||
9c09b82efd | |||
60d01c0d94 | |||
e7a91c53bc | |||
4e41fd5d71 | |||
fe4389bff4 | |||
9325830fad | |||
b86f0d45e3 | |||
210f0a5ff9 |
BIN
docs/epoch.png
Normal file
BIN
docs/epoch.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 221 KiB |
@ -50,12 +50,20 @@ You get the following response:
|
|||||||
|-----------------------------------|---------|----------------------------------------------------------------------------------------|
|
|-----------------------------------|---------|----------------------------------------------------------------------------------------|
|
||||||
| `serialno` | String | Serial Number of the monitor |
|
| `serialno` | String | Serial Number of the monitor |
|
||||||
| `wifi` | Number | WiFi signal strength |
|
| `wifi` | Number | WiFi signal strength |
|
||||||
| `pm01` | Number | PM1 in ug/m3 |
|
| `pm01` | Number | PM1.0 in ug/m3 (atmospheric environment) |
|
||||||
| `pm02` | Number | PM2.5 in ug/m3 |
|
| `pm02` | Number | PM2.5 in ug/m3 (atmospheric environment) |
|
||||||
| `pm10` | Number | PM10 in ug/m3 |
|
| `pm10` | Number | PM10 in ug/m3 (atmospheric environment) |
|
||||||
| `pm02Compensated` | Number | PM2.5 in ug/m3 with correction applied (from fw version 3.1.4 onwards) |
|
| `pm02Compensated` | Number | PM2.5 in ug/m3 with correction applied (from fw version 3.1.4 onwards) |
|
||||||
|
| `pm01Standard` | Number | PM1.0 in ug/m3 (standard particle) |
|
||||||
|
| `pm02Standard` | Number | PM2.5 in ug/m3 (standard particle) |
|
||||||
|
| `pm10Standard` | Number | PM10 in ug/m3 (standard particle) |
|
||||||
| `rco2` | Number | CO2 in ppm |
|
| `rco2` | Number | CO2 in ppm |
|
||||||
| `pm003Count` | Number | Particle count per dL |
|
| `pm003Count` | Number | Particle count 0.3um per dL |
|
||||||
|
| `pm005Count` | Number | Particle count 0.5um per dL |
|
||||||
|
| `pm01Count` | Number | Particle count 1.0um per dL |
|
||||||
|
| `pm02Count` | Number | Particle count 2.5um per dL |
|
||||||
|
| `pm50Count` | Number | Particle count 5.0um per dL (only for indoor monitor) |
|
||||||
|
| `pm10Count` | Number | Particle count 10um per dL (only for indoor monitor) |
|
||||||
| `atmp` | Number | Temperature in Degrees Celsius |
|
| `atmp` | Number | Temperature in Degrees Celsius |
|
||||||
| `atmpCompensated` | Number | Temperature in Degrees Celsius with correction applied |
|
| `atmpCompensated` | Number | Temperature in Degrees Celsius with correction applied |
|
||||||
| `rhum` | Number | Relative Humidity |
|
| `rhum` | Number | Relative Humidity |
|
||||||
@ -65,16 +73,17 @@ You get the following response:
|
|||||||
| `noxIndex` | Number | Senisirion NOx Index |
|
| `noxIndex` | Number | Senisirion NOx Index |
|
||||||
| `noxRaw` | Number | NOx raw value |
|
| `noxRaw` | Number | NOx raw value |
|
||||||
| `boot` | Number | Counts every measurement cycle. Low boot counts indicate restarts. |
|
| `boot` | Number | Counts every measurement cycle. Low boot counts indicate restarts. |
|
||||||
| `bootCount` | Number | Same as boot property. Required for Home Assistant compatability. Will be depreciated. |
|
| `bootCount` | Number | Same as boot property. Required for Home Assistant compatability. (deprecated soon!) |
|
||||||
| `ledMode` | String | Current configuration of the LED mode |
|
| `ledMode` | String | Current configuration of the LED mode |
|
||||||
| `firmware` | String | Current firmware version |
|
| `firmware` | String | Current firmware version |
|
||||||
| `model` | String | Current model name |
|
| `model` | String | Current model name |
|
||||||
| `monitorDisplayCompensatedValues` | Boolean | Switching Display of AirGradient ONE to Compensated / Non Compensated Values |
|
|
||||||
|
|
||||||
Compensated values apply correction algorithms to make the sensor values more accurate. Temperature and relative humidity correction is only applied on the outdoor monitor Open Air but the properties _compensated will still be send also for the indoor monitor AirGradient ONE.
|
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)
|
||||||
With the path "/config" you can get the current configuration.
|
|
||||||
|
"/config" path returns the current configuration of the monitor.
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"country": "TH",
|
"country": "TH",
|
||||||
@ -91,28 +100,40 @@ With the path "/config" you can get the current configuration.
|
|||||||
"displayBrightness": 100,
|
"displayBrightness": 100,
|
||||||
"offlineMode": false,
|
"offlineMode": false,
|
||||||
"model": "I-9PSL",
|
"model": "I-9PSL",
|
||||||
"monitorDisplayCompensatedValues": true
|
"monitorDisplayCompensatedValues": true,
|
||||||
|
"corrections": {
|
||||||
|
"pm02": {
|
||||||
|
"correctionAlgorithm": "epa_2021",
|
||||||
|
"slr": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Set Configuration Parameters (PUT)
|
#### Set Configuration Parameters (PUT)
|
||||||
|
|
||||||
Configuration parameters can be changed with a put request to the monitor, e.g.
|
Configuration parameters can be changed with a PUT request to the monitor, e.g.
|
||||||
|
|
||||||
Example to force CO2 calibration
|
Example to force CO2 calibration
|
||||||
|
|
||||||
```curl -X PUT -H "Content-Type: application/json" -d '{"co2CalibrationRequested":true}' http://airgradient_84fce612eff4.local/config ```
|
```bash
|
||||||
|
curl -X PUT -H "Content-Type: application/json" -d '{"co2CalibrationRequested":true}' http://airgradient_84fce612eff4.local/config
|
||||||
|
```
|
||||||
|
|
||||||
Example to set monitor to Celsius
|
Example to set monitor to Celsius
|
||||||
|
|
||||||
```curl -X PUT -H "Content-Type: application/json" -d '{"temperatureUnit":"c"}' http://airgradient_84fce612eff4.local/config ```
|
```bash
|
||||||
|
curl -X PUT -H "Content-Type: application/json" -d '{"temperatureUnit":"c"}' http://airgradient_84fce612eff4.local/config
|
||||||
|
```
|
||||||
|
|
||||||
If you use command prompt on Windows, you need to escape the quotes:
|
If you use command prompt on Windows, you need to escape the quotes:
|
||||||
|
|
||||||
``` -d "{\"param\":\"value\"}" ```
|
``` -d "{\"param\":\"value\"}" ```
|
||||||
|
|
||||||
#### Avoiding Conflicts with Configuration on AirGradient Server
|
#### Avoiding Conflicts with Configuration on AirGradient Server
|
||||||
If the monitor is set up on the AirGradient dashboard, it will also receive configurations from there. In case you do not want this, please set `configurationControl` to `local`. In case you set it to `cloud` and want to change it to `local`, you need to make a factory reset.
|
|
||||||
|
If the monitor is set up on the AirGradient dashboard, it will also receive the configuration parameters from there. In case you do not want this, please set `configurationControl` to `local`. In case you set it to `cloud` and want to change it to `local`, you need to make a factory reset.
|
||||||
|
|
||||||
#### Configuration Parameters (GET/PUT)
|
#### Configuration Parameters (GET/PUT)
|
||||||
|
|
||||||
@ -134,4 +155,62 @@ If the monitor is set up on the AirGradient dashboard, it will also receive conf
|
|||||||
| `noxLearningOffset` | Set NOx learning gain offset. | Number | 0-720 (default 12) | `{"noxLearningOffset": 12}` |
|
| `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}` |
|
| `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}` |
|
| `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 (From [3.1.9]()) | Boolean | `false`: Without compensate (default) <br> `true`: with compensate | `{"monitorDisplayCompensatedValues": false }` |
|
| `monitorDisplayCompensatedValues` | Set the display show the PM value with/without compensate value (only on [3.1.9]()) | Boolean | `false`: Without compensate (default) <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_ |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#### Corrections
|
||||||
|
|
||||||
|
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:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"corrections": {
|
||||||
|
"pm02": {
|
||||||
|
"correctionAlgorithm": "<Option In String>",
|
||||||
|
"slr": {
|
||||||
|
"intercept": 0,
|
||||||
|
"scalingFactor": 0,
|
||||||
|
"useEpa2021": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
| Algorithm | Value | Description | SLR required |
|
||||||
|
|------------|-------------|------|---------|
|
||||||
|
| Raw | `"none"` | No correction (default) | No |
|
||||||
|
| EPA 2021 | `"epa_2021"` | Use EPA 2021 correction factors on top of raw value | No |
|
||||||
|
| PMS5003_20240104 | `"slr_PMS5003_20240104"` | Correction for PMS5003 sensor batch 20240104| Yes |
|
||||||
|
| PMS5003_20231218 | `"slr_PMS5003_20231218"` | Correction for PMS5003 sensor batch 20231218| Yes |
|
||||||
|
| PMS5003_20231030 | `"slr_PMS5003_20231030"` | Correction for PMS5003 sensor batch 20231030| Yes |
|
||||||
|
|
||||||
|
**NOTES**:
|
||||||
|
|
||||||
|
- Set `useEpa2021` to `true` if want to apply EPA 2021 correction factors on top of SLR correction value, otherwise `false`
|
||||||
|
- `intercept` and `scalingFactor` values can be obtained from [this article](https://www.airgradient.com/blog/low-readings-from-pms5003/)
|
||||||
|
- If `configurationControl` is set to `local` (eg. when using Home Assistant), correction need to be set manually, see examples below
|
||||||
|
|
||||||
|
**Examples**:
|
||||||
|
|
||||||
|
- PMS5003_20231030
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl --location -X PUT 'http://airgradient_84fce612eff4.local/config' --header 'Content-Type: application/json' --data '{"corrections":{"pm02":{"correctionAlgorithm":"slr_PMS5003_20231030","slr":{"intercept":0,"scalingFactor":0.02838,"useEpa2021":true}}}}'
|
||||||
|
```
|
||||||
|
|
||||||
|
- PMS5003_20231218
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl --location -X PUT 'http://airgradient_84fce612eff4.local/config' --header 'Content-Type: application/json' --data '{"corrections":{"pm02":{"correctionAlgorithm":"slr_PMS5003_20231218","slr":{"intercept":0,"scalingFactor":0.03525,"useEpa2021":true}}}}'
|
||||||
|
```
|
||||||
|
|
||||||
|
- PMS5003_20240104
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl --location -X PUT 'http://airgradient_84fce612eff4.local/config' --header 'Content-Type: application/json' --data '{"corrections":{"pm02":{"correctionAlgorithm":"slr_PMS5003_20240104","slr":{"intercept":0,"scalingFactor":0.02896,"useEpa2021":true}}}}'
|
||||||
|
```
|
||||||
|
56
docs/local-storage-experimental.md
Normal file
56
docs/local-storage-experimental.md
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
*This document to explain local storage mode - experimental*
|
||||||
|
|
||||||
|
## How it works?
|
||||||
|
|
||||||
|
1. Monitor directly goes to local storage mode
|
||||||
|
2. On boot, monitor will attempt to connect to default wifi. And if connected, mdns and local server will be enabled, otherwise it will ignore and continues the measurements
|
||||||
|
3. On display, when boot it will show the mode ("local storage mode") and wifi related scenario. After that, monitor will show the measurements dashboard
|
||||||
|
4. Measurement records to the local storage every two minutes that saved on CSV file in SPIFFs partition
|
||||||
|
5. Every successful writes, monitor will blink the most left led bar to *blue* twice, but if failed it will blink *red* twice. There are two possibilities for failed write, SPIFFs partition already full or out of heap memory when load the file.
|
||||||
|
6. There are 2 endpoinds added for this mode, download measurements from local storage and reset measurement (delete old measurements file and create new one) with new timestamp. Timestamp here to set the monitor system time.
|
||||||
|
|
||||||
|
**Notes**
|
||||||
|
|
||||||
|
1. Default wifi
|
||||||
|
- ssid ➝ `airgradient`
|
||||||
|
- password ➝ `cleanair`
|
||||||
|
2. Maximum measurements file is around 113kb. If assume each measurements is 60 bytes, with write schedule 2 minutes, SPIFFS will be full in around 5 days
|
||||||
|
3. WiFi connection attempt on boot wait for 10s before considering timeout
|
||||||
|
4. Tips. If monitor not connected to wifi on boot, no need to restart the monitor for reconnection, it will automatically connect to AP once it is available
|
||||||
|
|
||||||
|
### Local Storage Endpoinds
|
||||||
|
|
||||||
|
*Make sure monitor is connected to AP, and client also connect to it. And change the serial number on the url*
|
||||||
|
|
||||||
|
**Download measurements file**
|
||||||
|
|
||||||
|
To download measurements file from local storage, just directly access following url on the browser `http://airgradient_aaaaaaaa.local/storage`, and browser should automatically download the file.
|
||||||
|
|
||||||
|
**Reset measurements**
|
||||||
|
|
||||||
|
Execute below command in terminal
|
||||||
|
|
||||||
|
```sh
|
||||||
|
curl -X PUT -H "Content-Type: text/plain" -d '1733431986' http://airgradient_aaaaaaa.local/storage/reset
|
||||||
|
```
|
||||||
|
|
||||||
|
`1733431986` this data is the time that we want to set monitor system time to. Its in epoch time format and expecting UTC+0 timezone.
|
||||||
|
|
||||||
|
To get epoch time, access this url [https://www.unixtimestamp.com/](https://www.unixtimestamp.com/), and click copy button.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
### Example measurements file content
|
||||||
|
|
||||||
|
```csv
|
||||||
|
datetime,pm0.3 count,pm1,pm2.5,pm10,temp,rhum,co2,tvoc,nox
|
||||||
|
05/12 21:10:59,869.67,11.17,20.33,21.83,26.69,72.93,417,40,1
|
||||||
|
05/12 21:11:30,834.83,11.50,19.33,20.33,26.68,73.08,413,79,1
|
||||||
|
05/12 21:12:01,829.67,10.33,19.33,22.00,26.64,73.09,412,90,1
|
||||||
|
05/12 21:12:32,831.50,10.33,18.33,20.83,26.62,73.21,411,97,1
|
||||||
|
05/12 21:13:02,887.50,12.00,20.33,21.67,26.59,73.33,412,95,1
|
||||||
|
05/12 21:13:33,785.17,8.67,18.50,19.50,26.56,73.43,414,92,1
|
||||||
|
05/12 21:14:04,827.50,10.50,18.50,19.50,26.54,73.43,415,98,1
|
||||||
|
05/12 21:14:35,815.83,10.50,19.50,19.83,26.49,73.47,413,99,1
|
||||||
|
```
|
||||||
|
|
@ -49,9 +49,8 @@ CC BY-SA 4.0 Attribution-ShareAlike 4.0 International License
|
|||||||
#define SENSOR_TVOC_UPDATE_INTERVAL 1000 /** ms */
|
#define SENSOR_TVOC_UPDATE_INTERVAL 1000 /** ms */
|
||||||
#define SENSOR_CO2_UPDATE_INTERVAL 4000 /** ms */
|
#define SENSOR_CO2_UPDATE_INTERVAL 4000 /** ms */
|
||||||
#define SENSOR_PM_UPDATE_INTERVAL 2000 /** ms */
|
#define SENSOR_PM_UPDATE_INTERVAL 2000 /** ms */
|
||||||
#define SENSOR_TEMP_HUM_UPDATE_INTERVAL 2000 /** ms */
|
#define SENSOR_TEMP_HUM_UPDATE_INTERVAL 6000 /** ms */
|
||||||
#define DISPLAY_DELAY_SHOW_CONTENT_MS 2000 /** ms */
|
#define DISPLAY_DELAY_SHOW_CONTENT_MS 2000 /** ms */
|
||||||
#define FIRMWARE_CHECK_FOR_UPDATE_MS (60 * 60 * 1000) /** ms */
|
|
||||||
|
|
||||||
static AirGradient ag(DIY_BASIC);
|
static AirGradient ag(DIY_BASIC);
|
||||||
static Configuration configuration(Serial);
|
static Configuration configuration(Serial);
|
||||||
@ -68,7 +67,6 @@ static LocalServer localServer(Serial, openMetrics, measurements, configuration,
|
|||||||
wifiConnector);
|
wifiConnector);
|
||||||
static MqttClient mqttClient(Serial);
|
static MqttClient mqttClient(Serial);
|
||||||
|
|
||||||
static int getCO2FailCount = 0;
|
|
||||||
static AgFirmwareMode fwMode = FW_MODE_I_BASIC_40PS;
|
static AgFirmwareMode fwMode = FW_MODE_I_BASIC_40PS;
|
||||||
|
|
||||||
static String fwNewVersion;
|
static String fwNewVersion;
|
||||||
@ -90,6 +88,8 @@ static void wdgFeedUpdate(void);
|
|||||||
static bool sgp41Init(void);
|
static bool sgp41Init(void);
|
||||||
static void wifiFactoryConfigure(void);
|
static void wifiFactoryConfigure(void);
|
||||||
static void mqttHandle(void);
|
static void mqttHandle(void);
|
||||||
|
static int calculateMaxPeriod(int updateInterval);
|
||||||
|
static void setMeasurementMaxPeriod();
|
||||||
|
|
||||||
AgSchedule dispLedSchedule(DISP_UPDATE_INTERVAL, oledDisplaySchedule);
|
AgSchedule dispLedSchedule(DISP_UPDATE_INTERVAL, oledDisplaySchedule);
|
||||||
AgSchedule configSchedule(SERVER_CONFIG_SYNC_INTERVAL,
|
AgSchedule configSchedule(SERVER_CONFIG_SYNC_INTERVAL,
|
||||||
@ -130,6 +130,10 @@ void setup() {
|
|||||||
|
|
||||||
/** Init sensor */
|
/** Init sensor */
|
||||||
boardInit();
|
boardInit();
|
||||||
|
setMeasurementMaxPeriod();
|
||||||
|
|
||||||
|
// Uncomment below line to print every measurements reading update
|
||||||
|
// measurements.setDebug(true);
|
||||||
|
|
||||||
/** Connecting wifi */
|
/** Connecting wifi */
|
||||||
bool connectToWifi = false;
|
bool connectToWifi = false;
|
||||||
@ -230,17 +234,16 @@ void loop() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void co2Update(void) {
|
static void co2Update(void) {
|
||||||
|
if (!configuration.hasSensorS8) {
|
||||||
|
// Device don't have S8 sensor
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
int value = ag.s8.getCo2();
|
int value = ag.s8.getCo2();
|
||||||
if (utils::isValidCO2(value)) {
|
if (utils::isValidCO2(value)) {
|
||||||
measurements.CO2 = value;
|
measurements.update(Measurements::CO2, value);
|
||||||
getCO2FailCount = 0;
|
|
||||||
Serial.printf("CO2 (ppm): %d\r\n", measurements.CO2);
|
|
||||||
} else {
|
} else {
|
||||||
getCO2FailCount++;
|
measurements.update(Measurements::CO2, utils::getInvalidCO2());
|
||||||
Serial.printf("Get CO2 failed: %d\r\n", getCO2FailCount);
|
|
||||||
if (getCO2FailCount >= 3) {
|
|
||||||
measurements.CO2 = utils::getInvalidCO2();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -313,8 +316,7 @@ static void mqttHandle(void) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (mqttClient.isConnected()) {
|
if (mqttClient.isConnected()) {
|
||||||
String payload = measurements.toString(true, fwMode, wifiConnector.RSSI(),
|
String payload = measurements.toString(true, fwMode, wifiConnector.RSSI(), ag, configuration);
|
||||||
&ag, &configuration);
|
|
||||||
String topic = "airgradient/readings/" + ag.deviceId();
|
String topic = "airgradient/readings/" + ag.deviceId();
|
||||||
if (mqttClient.publish(topic.c_str(), payload.c_str(), payload.length())) {
|
if (mqttClient.publish(topic.c_str(), payload.c_str(), payload.length())) {
|
||||||
Serial.println("MQTT sync success");
|
Serial.println("MQTT sync success");
|
||||||
@ -490,88 +492,98 @@ static void oledDisplaySchedule(void) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void updateTvoc(void) {
|
static void updateTvoc(void) {
|
||||||
measurements.TVOC = ag.sgp41.getTvocIndex();
|
if (!configuration.hasSensorSGP) {
|
||||||
measurements.TVOCRaw = ag.sgp41.getTvocRaw();
|
return;
|
||||||
measurements.NOx = ag.sgp41.getNoxIndex();
|
}
|
||||||
measurements.NOxRaw = ag.sgp41.getNoxRaw();
|
|
||||||
|
|
||||||
Serial.println();
|
measurements.update(Measurements::TVOC, ag.sgp41.getTvocIndex());
|
||||||
Serial.printf("TVOC index: %d\r\n", measurements.TVOC);
|
measurements.update(Measurements::TVOCRaw, ag.sgp41.getTvocRaw());
|
||||||
Serial.printf("TVOC raw: %d\r\n", measurements.TVOCRaw);
|
measurements.update(Measurements::NOx, ag.sgp41.getNoxIndex());
|
||||||
Serial.printf("NOx index: %d\r\n", measurements.NOx);
|
measurements.update(Measurements::NOxRaw, ag.sgp41.getNoxRaw());
|
||||||
Serial.printf("NOx raw: %d\r\n", measurements.NOxRaw);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void updatePm(void) {
|
static void updatePm(void) {
|
||||||
if (ag.pms5003.connected()) {
|
if (ag.pms5003.connected()) {
|
||||||
measurements.pm01_1 = ag.pms5003.getPm01Ae();
|
measurements.update(Measurements::PM01, ag.pms5003.getPm01Ae());
|
||||||
measurements.pm25_1 = ag.pms5003.getPm25Ae();
|
measurements.update(Measurements::PM25, ag.pms5003.getPm25Ae());
|
||||||
measurements.pm10_1 = ag.pms5003.getPm10Ae();
|
measurements.update(Measurements::PM10, ag.pms5003.getPm10Ae());
|
||||||
measurements.pm03PCount_1 = ag.pms5003.getPm03ParticleCount();
|
measurements.update(Measurements::PM03_PC, ag.pms5003.getPm03ParticleCount());
|
||||||
|
|
||||||
Serial.println();
|
|
||||||
Serial.printf("PM1 ug/m3: %d\r\n", measurements.pm01_1);
|
|
||||||
Serial.printf("PM2.5 ug/m3: %d\r\n", measurements.pm25_1);
|
|
||||||
Serial.printf("PM10 ug/m3: %d\r\n", measurements.pm10_1);
|
|
||||||
Serial.printf("PM0.3 Count: %d\r\n", measurements.pm03PCount_1);
|
|
||||||
Serial.printf("PM firmware version: %d\r\n", ag.pms5003.getFirmwareVersion());
|
|
||||||
ag.pms5003.resetFailCount();
|
|
||||||
} else {
|
} else {
|
||||||
ag.pms5003.updateFailCount();
|
measurements.update(Measurements::PM01, utils::getInvalidPmValue());
|
||||||
Serial.printf("PMS read failed %d times\r\n", ag.pms5003.getFailCount());
|
measurements.update(Measurements::PM25, utils::getInvalidPmValue());
|
||||||
if (ag.pms5003.getFailCount() >= PMS_FAIL_COUNT_SET_INVALID) {
|
measurements.update(Measurements::PM10, utils::getInvalidPmValue());
|
||||||
measurements.pm01_1 = utils::getInvalidPmValue();
|
measurements.update(Measurements::PM03_PC, utils::getInvalidPmValue());
|
||||||
measurements.pm25_1 = utils::getInvalidPmValue();
|
|
||||||
measurements.pm10_1 = utils::getInvalidPmValue();
|
|
||||||
measurements.pm03PCount_1 = utils::getInvalidPmValue();
|
|
||||||
}
|
|
||||||
|
|
||||||
if(ag.pms5003.getFailCount() >= ag.pms5003.getFailCountMax()) {
|
|
||||||
Serial.printf("PMS failure count reach to max set %d, restarting...", ag.pms5003.getFailCountMax());
|
|
||||||
ESP.restart();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void sendDataToServer(void) {
|
static void sendDataToServer(void) {
|
||||||
|
/** Increment bootcount when send measurements data is scheduled */
|
||||||
|
measurements.bootCount++;
|
||||||
|
|
||||||
/** Ignore send data to server if postToAirGradient disabled */
|
/** Ignore send data to server if postToAirGradient disabled */
|
||||||
if (configuration.isPostDataToAirGradient() == false ||
|
if (configuration.isPostDataToAirGradient() == false ||
|
||||||
configuration.isOfflineMode()) {
|
configuration.isOfflineMode()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
String syncData = measurements.toString(false, fwMode, wifiConnector.RSSI(),
|
String syncData = measurements.toString(false, fwMode, wifiConnector.RSSI(), ag, configuration);
|
||||||
&ag, &configuration);
|
|
||||||
if (apiClient.postToServer(syncData)) {
|
if (apiClient.postToServer(syncData)) {
|
||||||
Serial.println();
|
Serial.println();
|
||||||
Serial.println(
|
Serial.println(
|
||||||
"Online mode and isPostToAirGradient = true: watchdog reset");
|
"Online mode and isPostToAirGradient = true: watchdog reset");
|
||||||
Serial.println();
|
Serial.println();
|
||||||
}
|
}
|
||||||
measurements.bootCount++;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void tempHumUpdate(void) {
|
static void tempHumUpdate(void) {
|
||||||
delay(100);
|
|
||||||
if (ag.sht.measure()) {
|
if (ag.sht.measure()) {
|
||||||
measurements.Temperature = ag.sht.getTemperature();
|
float temp = ag.sht.getTemperature();
|
||||||
measurements.Humidity = ag.sht.getRelativeHumidity();
|
float rhum = ag.sht.getRelativeHumidity();
|
||||||
|
|
||||||
Serial.printf("Temperature in C: %0.2f\r\n", measurements.Temperature);
|
measurements.update(Measurements::Temperature, temp);
|
||||||
Serial.printf("Relative Humidity: %d\r\n", measurements.Humidity);
|
measurements.update(Measurements::Humidity, rhum);
|
||||||
Serial.printf("Temperature compensated in C: %0.2f\r\n",
|
|
||||||
measurements.Temperature);
|
|
||||||
Serial.printf("Relative Humidity compensated: %d\r\n",
|
|
||||||
measurements.Humidity);
|
|
||||||
|
|
||||||
// Update compensation temperature and humidity for SGP41
|
// Update compensation temperature and humidity for SGP41
|
||||||
if (configuration.hasSensorSGP) {
|
if (configuration.hasSensorSGP) {
|
||||||
ag.sgp41.setCompensationTemperatureHumidity(measurements.Temperature,
|
ag.sgp41.setCompensationTemperatureHumidity(temp, rhum);
|
||||||
measurements.Humidity);
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
measurements.update(Measurements::Temperature, utils::getInvalidTemperature());
|
||||||
|
measurements.update(Measurements::Humidity, utils::getInvalidHumidity());
|
||||||
Serial.println("SHT read failed");
|
Serial.println("SHT read failed");
|
||||||
measurements.Temperature = utils::getInvalidTemperature();
|
|
||||||
measurements.Humidity = utils::getInvalidHumidity();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Set max period for each measurement type based on sensor update interval*/
|
||||||
|
void setMeasurementMaxPeriod() {
|
||||||
|
/// Max period for S8 sensors measurements
|
||||||
|
measurements.maxPeriod(Measurements::CO2, calculateMaxPeriod(SENSOR_CO2_UPDATE_INTERVAL));
|
||||||
|
/// Max period for SGP sensors measurements
|
||||||
|
measurements.maxPeriod(Measurements::TVOC, calculateMaxPeriod(SENSOR_TVOC_UPDATE_INTERVAL));
|
||||||
|
measurements.maxPeriod(Measurements::TVOCRaw, calculateMaxPeriod(SENSOR_TVOC_UPDATE_INTERVAL));
|
||||||
|
measurements.maxPeriod(Measurements::NOx, calculateMaxPeriod(SENSOR_TVOC_UPDATE_INTERVAL));
|
||||||
|
measurements.maxPeriod(Measurements::NOxRaw, calculateMaxPeriod(SENSOR_TVOC_UPDATE_INTERVAL));
|
||||||
|
/// Max period for PMS sensors measurements
|
||||||
|
measurements.maxPeriod(Measurements::PM25, calculateMaxPeriod(SENSOR_PM_UPDATE_INTERVAL));
|
||||||
|
measurements.maxPeriod(Measurements::PM01, calculateMaxPeriod(SENSOR_PM_UPDATE_INTERVAL));
|
||||||
|
measurements.maxPeriod(Measurements::PM10, calculateMaxPeriod(SENSOR_PM_UPDATE_INTERVAL));
|
||||||
|
measurements.maxPeriod(Measurements::PM03_PC, calculateMaxPeriod(SENSOR_PM_UPDATE_INTERVAL));
|
||||||
|
// Temperature and Humidity
|
||||||
|
if (configuration.hasSensorSHT) {
|
||||||
|
/// Max period for SHT sensors measurements
|
||||||
|
measurements.maxPeriod(Measurements::Temperature,
|
||||||
|
calculateMaxPeriod(SENSOR_TEMP_HUM_UPDATE_INTERVAL));
|
||||||
|
measurements.maxPeriod(Measurements::Humidity,
|
||||||
|
calculateMaxPeriod(SENSOR_TEMP_HUM_UPDATE_INTERVAL));
|
||||||
|
} else {
|
||||||
|
/// Temp and hum data retrieved from PMS5003T sensor
|
||||||
|
measurements.maxPeriod(Measurements::Temperature,
|
||||||
|
calculateMaxPeriod(SENSOR_PM_UPDATE_INTERVAL));
|
||||||
|
measurements.maxPeriod(Measurements::Humidity, calculateMaxPeriod(SENSOR_PM_UPDATE_INTERVAL));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int calculateMaxPeriod(int updateInterval) {
|
||||||
|
// 0.5 is 50% reduced interval for max period
|
||||||
|
return (SERVER_SYNC_INTERVAL - (SERVER_SYNC_INTERVAL * 0.5)) / updateInterval;
|
||||||
|
}
|
@ -53,9 +53,8 @@ void LocalServer::_GET_metrics(void) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void LocalServer::_GET_measure(void) {
|
void LocalServer::_GET_measure(void) {
|
||||||
server.send(
|
String toSend = measure.toString(true, fwMode, wifiConnector.RSSI(), *ag, config);
|
||||||
200, "application/json",
|
server.send(200, "application/json", toSend);
|
||||||
measure.toString(true, fwMode, wifiConnector.RSSI(), ag, &config));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void LocalServer::setFwMode(AgFirmwareMode fwMode) { this->fwMode = fwMode; }
|
void LocalServer::setFwMode(AgFirmwareMode fwMode) { this->fwMode = fwMode; }
|
||||||
|
@ -57,35 +57,45 @@ String OpenMetrics::getPayload(void) {
|
|||||||
"gauge", "dbm");
|
"gauge", "dbm");
|
||||||
add_metric_point("", String(wifiConnector.RSSI()));
|
add_metric_point("", String(wifiConnector.RSSI()));
|
||||||
|
|
||||||
if (config.hasSensorS8 && measure.CO2 >= 0) {
|
// Initialize default invalid value for each measurements
|
||||||
add_metric("co2",
|
|
||||||
"Carbon dioxide concentration as measured by the AirGradient S8 "
|
|
||||||
"sensor, in parts per million",
|
|
||||||
"gauge", "ppm");
|
|
||||||
add_metric_point("", String(measure.CO2));
|
|
||||||
}
|
|
||||||
|
|
||||||
float _temp = utils::getInvalidTemperature();
|
float _temp = utils::getInvalidTemperature();
|
||||||
float _hum = utils::getInvalidHumidity();
|
float _hum = utils::getInvalidHumidity();
|
||||||
int pm01 = utils::getInvalidPmValue();
|
int pm01 = utils::getInvalidPmValue();
|
||||||
int pm25 = utils::getInvalidPmValue();
|
int pm25 = utils::getInvalidPmValue();
|
||||||
int pm10 = utils::getInvalidPmValue();
|
int pm10 = utils::getInvalidPmValue();
|
||||||
int pm03PCount = utils::getInvalidPmValue();
|
int pm03PCount = utils::getInvalidPmValue();
|
||||||
|
int co2 = utils::getInvalidCO2();
|
||||||
int atmpCompensated = utils::getInvalidTemperature();
|
int atmpCompensated = utils::getInvalidTemperature();
|
||||||
int ahumCompensated = utils::getInvalidHumidity();
|
int ahumCompensated = utils::getInvalidHumidity();
|
||||||
|
int tvoc = utils::getInvalidVOC();
|
||||||
|
int tvocRaw = utils::getInvalidVOC();
|
||||||
|
int nox = utils::getInvalidNOx();
|
||||||
|
int noxRaw = utils::getInvalidNOx();
|
||||||
|
|
||||||
if (config.hasSensorSHT) {
|
if (config.hasSensorSHT) {
|
||||||
_temp = measure.Temperature;
|
_temp = measure.getFloat(Measurements::Temperature);
|
||||||
_hum = measure.Humidity;
|
_hum = measure.getFloat(Measurements::Humidity);
|
||||||
atmpCompensated = _temp;
|
atmpCompensated = _temp;
|
||||||
ahumCompensated = _hum;
|
ahumCompensated = _hum;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (config.hasSensorPMS1) {
|
if (config.hasSensorPMS1) {
|
||||||
pm01 = measure.pm01_1;
|
pm01 = measure.get(Measurements::PM01);
|
||||||
pm25 = measure.pm25_1;
|
float correctedPm = measure.getCorrectedPM25(*ag, config, false, 1);
|
||||||
pm10 = measure.pm10_1;
|
pm25 = round(correctedPm);
|
||||||
pm03PCount = measure.pm03PCount_1;
|
pm10 = measure.get(Measurements::PM10);
|
||||||
|
pm03PCount = measure.get(Measurements::PM03_PC);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config.hasSensorSGP) {
|
||||||
|
tvoc = measure.get(Measurements::TVOC);
|
||||||
|
tvocRaw = measure.get(Measurements::TVOCRaw);
|
||||||
|
nox = measure.get(Measurements::NOx);
|
||||||
|
noxRaw = measure.get(Measurements::NOxRaw);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config.hasSensorS8) {
|
||||||
|
co2 = measure.get(Measurements::CO2);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (config.hasSensorPMS1) {
|
if (config.hasSensorPMS1) {
|
||||||
@ -120,36 +130,44 @@ String OpenMetrics::getPayload(void) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (config.hasSensorSGP) {
|
if (config.hasSensorSGP) {
|
||||||
if (utils::isValidVOC(measure.TVOC)) {
|
if (utils::isValidVOC(tvoc)) {
|
||||||
add_metric("tvoc_index",
|
add_metric("tvoc_index",
|
||||||
"The processed Total Volatile Organic Compounds (TVOC) index "
|
"The processed Total Volatile Organic Compounds (TVOC) index "
|
||||||
"as measured by the AirGradient SGP sensor",
|
"as measured by the AirGradient SGP sensor",
|
||||||
"gauge");
|
"gauge");
|
||||||
add_metric_point("", String(measure.TVOC));
|
add_metric_point("", String(tvoc));
|
||||||
}
|
}
|
||||||
if (utils::isValidVOC(measure.TVOCRaw)) {
|
if (utils::isValidVOC(tvocRaw)) {
|
||||||
add_metric("tvoc_raw",
|
add_metric("tvoc_raw",
|
||||||
"The raw input value to the Total Volatile Organic Compounds "
|
"The raw input value to the Total Volatile Organic Compounds "
|
||||||
"(TVOC) index as measured by the AirGradient SGP sensor",
|
"(TVOC) index as measured by the AirGradient SGP sensor",
|
||||||
"gauge");
|
"gauge");
|
||||||
add_metric_point("", String(measure.TVOCRaw));
|
add_metric_point("", String(tvocRaw));
|
||||||
}
|
}
|
||||||
if (utils::isValidNOx(measure.NOx)) {
|
if (utils::isValidNOx(nox)) {
|
||||||
add_metric("nox_index",
|
add_metric("nox_index",
|
||||||
"The processed Nitrous Oxide (NOx) index as measured by the "
|
"The processed Nitrous Oxide (NOx) index as measured by the "
|
||||||
"AirGradient SGP sensor",
|
"AirGradient SGP sensor",
|
||||||
"gauge");
|
"gauge");
|
||||||
add_metric_point("", String(measure.NOx));
|
add_metric_point("", String(nox));
|
||||||
}
|
}
|
||||||
if (utils::isValidNOx(measure.NOxRaw)) {
|
if (utils::isValidNOx(noxRaw)) {
|
||||||
add_metric("nox_raw",
|
add_metric("nox_raw",
|
||||||
"The raw input value to the Nitrous Oxide (NOx) index as "
|
"The raw input value to the Nitrous Oxide (NOx) index as "
|
||||||
"measured by the AirGradient SGP sensor",
|
"measured by the AirGradient SGP sensor",
|
||||||
"gauge");
|
"gauge");
|
||||||
add_metric_point("", String(measure.NOxRaw));
|
add_metric_point("", String(noxRaw));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (utils::isValidCO2(co2)) {
|
||||||
|
add_metric("co2",
|
||||||
|
"Carbon dioxide concentration as measured by the AirGradient S8 "
|
||||||
|
"sensor, in parts per million",
|
||||||
|
"gauge", "ppm");
|
||||||
|
add_metric_point("", String(co2));
|
||||||
|
}
|
||||||
|
|
||||||
if (utils::isValidTemperature(_temp)) {
|
if (utils::isValidTemperature(_temp)) {
|
||||||
add_metric(
|
add_metric(
|
||||||
"temperature",
|
"temperature",
|
||||||
|
@ -49,9 +49,8 @@ CC BY-SA 4.0 Attribution-ShareAlike 4.0 International License
|
|||||||
#define SENSOR_TVOC_UPDATE_INTERVAL 1000 /** ms */
|
#define SENSOR_TVOC_UPDATE_INTERVAL 1000 /** ms */
|
||||||
#define SENSOR_CO2_UPDATE_INTERVAL 4000 /** ms */
|
#define SENSOR_CO2_UPDATE_INTERVAL 4000 /** ms */
|
||||||
#define SENSOR_PM_UPDATE_INTERVAL 2000 /** ms */
|
#define SENSOR_PM_UPDATE_INTERVAL 2000 /** ms */
|
||||||
#define SENSOR_TEMP_HUM_UPDATE_INTERVAL 2000 /** ms */
|
#define SENSOR_TEMP_HUM_UPDATE_INTERVAL 6000 /** ms */
|
||||||
#define DISPLAY_DELAY_SHOW_CONTENT_MS 2000 /** ms */
|
#define DISPLAY_DELAY_SHOW_CONTENT_MS 2000 /** ms */
|
||||||
#define FIRMWARE_CHECK_FOR_UPDATE_MS (60 * 60 * 1000) /** ms */
|
|
||||||
|
|
||||||
static AirGradient ag(DIY_PRO_INDOOR_V3_3);
|
static AirGradient ag(DIY_PRO_INDOOR_V3_3);
|
||||||
static Configuration configuration(Serial);
|
static Configuration configuration(Serial);
|
||||||
@ -68,7 +67,6 @@ static LocalServer localServer(Serial, openMetrics, measurements, configuration,
|
|||||||
wifiConnector);
|
wifiConnector);
|
||||||
static MqttClient mqttClient(Serial);
|
static MqttClient mqttClient(Serial);
|
||||||
|
|
||||||
static int getCO2FailCount = 0;
|
|
||||||
static AgFirmwareMode fwMode = FW_MODE_I_33PS;
|
static AgFirmwareMode fwMode = FW_MODE_I_33PS;
|
||||||
|
|
||||||
static String fwNewVersion;
|
static String fwNewVersion;
|
||||||
@ -90,6 +88,8 @@ static void wdgFeedUpdate(void);
|
|||||||
static bool sgp41Init(void);
|
static bool sgp41Init(void);
|
||||||
static void wifiFactoryConfigure(void);
|
static void wifiFactoryConfigure(void);
|
||||||
static void mqttHandle(void);
|
static void mqttHandle(void);
|
||||||
|
static int calculateMaxPeriod(int updateInterval);
|
||||||
|
static void setMeasurementMaxPeriod();
|
||||||
|
|
||||||
AgSchedule dispLedSchedule(DISP_UPDATE_INTERVAL, oledDisplaySchedule);
|
AgSchedule dispLedSchedule(DISP_UPDATE_INTERVAL, oledDisplaySchedule);
|
||||||
AgSchedule configSchedule(SERVER_CONFIG_SYNC_INTERVAL,
|
AgSchedule configSchedule(SERVER_CONFIG_SYNC_INTERVAL,
|
||||||
@ -130,6 +130,10 @@ void setup() {
|
|||||||
|
|
||||||
/** Init sensor */
|
/** Init sensor */
|
||||||
boardInit();
|
boardInit();
|
||||||
|
setMeasurementMaxPeriod();
|
||||||
|
|
||||||
|
// Uncomment below line to print every measurements reading update
|
||||||
|
// measurements.setDebug(true);
|
||||||
|
|
||||||
/** Connecting wifi */
|
/** Connecting wifi */
|
||||||
bool connectToWifi = false;
|
bool connectToWifi = false;
|
||||||
@ -228,17 +232,16 @@ void loop() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void co2Update(void) {
|
static void co2Update(void) {
|
||||||
|
if (!configuration.hasSensorS8) {
|
||||||
|
// Device don't have S8 sensor
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
int value = ag.s8.getCo2();
|
int value = ag.s8.getCo2();
|
||||||
if (utils::isValidCO2(value)) {
|
if (utils::isValidCO2(value)) {
|
||||||
measurements.CO2 = value;
|
measurements.update(Measurements::CO2, value);
|
||||||
getCO2FailCount = 0;
|
|
||||||
Serial.printf("CO2 (ppm): %d\r\n", measurements.CO2);
|
|
||||||
} else {
|
} else {
|
||||||
getCO2FailCount++;
|
measurements.update(Measurements::CO2, utils::getInvalidCO2());
|
||||||
Serial.printf("Get CO2 failed: %d\r\n", getCO2FailCount);
|
|
||||||
if (getCO2FailCount >= 3) {
|
|
||||||
measurements.CO2 = utils::getInvalidCO2();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -370,8 +373,7 @@ static void mqttHandle(void) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (mqttClient.isConnected()) {
|
if (mqttClient.isConnected()) {
|
||||||
String payload = measurements.toString(true, fwMode, wifiConnector.RSSI(),
|
String payload = measurements.toString(true, fwMode, wifiConnector.RSSI(), ag, configuration);
|
||||||
&ag, &configuration);
|
|
||||||
String topic = "airgradient/readings/" + ag.deviceId();
|
String topic = "airgradient/readings/" + ag.deviceId();
|
||||||
if (mqttClient.publish(topic.c_str(), payload.c_str(), payload.length())) {
|
if (mqttClient.publish(topic.c_str(), payload.c_str(), payload.length())) {
|
||||||
Serial.println("MQTT sync success");
|
Serial.println("MQTT sync success");
|
||||||
@ -542,88 +544,98 @@ static void oledDisplaySchedule(void) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void updateTvoc(void) {
|
static void updateTvoc(void) {
|
||||||
measurements.TVOC = ag.sgp41.getTvocIndex();
|
if (!configuration.hasSensorSGP) {
|
||||||
measurements.TVOCRaw = ag.sgp41.getTvocRaw();
|
return;
|
||||||
measurements.NOx = ag.sgp41.getNoxIndex();
|
}
|
||||||
measurements.NOxRaw = ag.sgp41.getNoxRaw();
|
|
||||||
|
|
||||||
Serial.println();
|
measurements.update(Measurements::TVOC, ag.sgp41.getTvocIndex());
|
||||||
Serial.printf("TVOC index: %d\r\n", measurements.TVOC);
|
measurements.update(Measurements::TVOCRaw, ag.sgp41.getTvocRaw());
|
||||||
Serial.printf("TVOC raw: %d\r\n", measurements.TVOCRaw);
|
measurements.update(Measurements::NOx, ag.sgp41.getNoxIndex());
|
||||||
Serial.printf("NOx index: %d\r\n", measurements.NOx);
|
measurements.update(Measurements::NOxRaw, ag.sgp41.getNoxRaw());
|
||||||
Serial.printf("NOx raw: %d\r\n", measurements.NOxRaw);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void updatePm(void) {
|
static void updatePm(void) {
|
||||||
if (ag.pms5003.connected()) {
|
if (ag.pms5003.connected()) {
|
||||||
measurements.pm01_1 = ag.pms5003.getPm01Ae();
|
measurements.update(Measurements::PM01, ag.pms5003.getPm01Ae());
|
||||||
measurements.pm25_1 = ag.pms5003.getPm25Ae();
|
measurements.update(Measurements::PM25, ag.pms5003.getPm25Ae());
|
||||||
measurements.pm10_1 = ag.pms5003.getPm10Ae();
|
measurements.update(Measurements::PM10, ag.pms5003.getPm10Ae());
|
||||||
measurements.pm03PCount_1 = ag.pms5003.getPm03ParticleCount();
|
measurements.update(Measurements::PM03_PC, ag.pms5003.getPm03ParticleCount());
|
||||||
|
|
||||||
Serial.println();
|
|
||||||
Serial.printf("PM1 ug/m3: %d\r\n", measurements.pm01_1);
|
|
||||||
Serial.printf("PM2.5 ug/m3: %d\r\n", measurements.pm25_1);
|
|
||||||
Serial.printf("PM10 ug/m3: %d\r\n", measurements.pm10_1);
|
|
||||||
Serial.printf("PM0.3 Count: %d\r\n", measurements.pm03PCount_1);
|
|
||||||
Serial.printf("PM firmware version: %d\r\n", ag.pms5003.getFirmwareVersion());
|
|
||||||
ag.pms5003.resetFailCount();
|
|
||||||
} else {
|
} else {
|
||||||
ag.pms5003.updateFailCount();
|
measurements.update(Measurements::PM01, utils::getInvalidPmValue());
|
||||||
Serial.printf("PMS read failed %d times\r\n", ag.pms5003.getFailCount());
|
measurements.update(Measurements::PM25, utils::getInvalidPmValue());
|
||||||
if (ag.pms5003.getFailCount() >= PMS_FAIL_COUNT_SET_INVALID) {
|
measurements.update(Measurements::PM10, utils::getInvalidPmValue());
|
||||||
measurements.pm01_1 = utils::getInvalidPmValue();
|
measurements.update(Measurements::PM03_PC, utils::getInvalidPmValue());
|
||||||
measurements.pm25_1 = utils::getInvalidPmValue();
|
|
||||||
measurements.pm10_1 = utils::getInvalidPmValue();
|
|
||||||
measurements.pm03PCount_1 = utils::getInvalidPmValue();
|
|
||||||
}
|
|
||||||
|
|
||||||
if(ag.pms5003.getFailCount() >= ag.pms5003.getFailCountMax()) {
|
|
||||||
Serial.printf("PMS failure count reach to max set %d, restarting...", ag.pms5003.getFailCountMax());
|
|
||||||
ESP.restart();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void sendDataToServer(void) {
|
static void sendDataToServer(void) {
|
||||||
|
/** Increment bootcount when send measurements data is scheduled */
|
||||||
|
measurements.bootCount++;
|
||||||
|
|
||||||
/** Ignore send data to server if postToAirGradient disabled */
|
/** Ignore send data to server if postToAirGradient disabled */
|
||||||
if (configuration.isPostDataToAirGradient() == false ||
|
if (configuration.isPostDataToAirGradient() == false ||
|
||||||
configuration.isOfflineMode()) {
|
configuration.isOfflineMode()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
String syncData = measurements.toString(false, fwMode, wifiConnector.RSSI(),
|
String syncData = measurements.toString(false, fwMode, wifiConnector.RSSI(), ag, configuration);
|
||||||
&ag, &configuration);
|
|
||||||
if (apiClient.postToServer(syncData)) {
|
if (apiClient.postToServer(syncData)) {
|
||||||
Serial.println();
|
Serial.println();
|
||||||
Serial.println(
|
Serial.println(
|
||||||
"Online mode and isPostToAirGradient = true: watchdog reset");
|
"Online mode and isPostToAirGradient = true: watchdog reset");
|
||||||
Serial.println();
|
Serial.println();
|
||||||
}
|
}
|
||||||
measurements.bootCount++;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void tempHumUpdate(void) {
|
static void tempHumUpdate(void) {
|
||||||
delay(100);
|
|
||||||
if (ag.sht.measure()) {
|
if (ag.sht.measure()) {
|
||||||
measurements.Temperature = ag.sht.getTemperature();
|
float temp = ag.sht.getTemperature();
|
||||||
measurements.Humidity = ag.sht.getRelativeHumidity();
|
float rhum = ag.sht.getRelativeHumidity();
|
||||||
|
|
||||||
Serial.printf("Temperature in C: %0.2f\r\n", measurements.Temperature);
|
measurements.update(Measurements::Temperature, temp);
|
||||||
Serial.printf("Relative Humidity: %d\r\n", measurements.Humidity);
|
measurements.update(Measurements::Humidity, rhum);
|
||||||
Serial.printf("Temperature compensated in C: %0.2f\r\n",
|
|
||||||
measurements.Temperature);
|
|
||||||
Serial.printf("Relative Humidity compensated: %d\r\n",
|
|
||||||
measurements.Humidity);
|
|
||||||
|
|
||||||
// Update compensation temperature and humidity for SGP41
|
// Update compensation temperature and humidity for SGP41
|
||||||
if (configuration.hasSensorSGP) {
|
if (configuration.hasSensorSGP) {
|
||||||
ag.sgp41.setCompensationTemperatureHumidity(measurements.Temperature,
|
ag.sgp41.setCompensationTemperatureHumidity(temp, rhum);
|
||||||
measurements.Humidity);
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
measurements.update(Measurements::Temperature, utils::getInvalidTemperature());
|
||||||
|
measurements.update(Measurements::Humidity, utils::getInvalidHumidity());
|
||||||
Serial.println("SHT read failed");
|
Serial.println("SHT read failed");
|
||||||
measurements.Temperature = utils::getInvalidTemperature();
|
|
||||||
measurements.Humidity = utils::getInvalidHumidity();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Set max period for each measurement type based on sensor update interval*/
|
||||||
|
void setMeasurementMaxPeriod() {
|
||||||
|
/// Max period for S8 sensors measurements
|
||||||
|
measurements.maxPeriod(Measurements::CO2, calculateMaxPeriod(SENSOR_CO2_UPDATE_INTERVAL));
|
||||||
|
/// Max period for SGP sensors measurements
|
||||||
|
measurements.maxPeriod(Measurements::TVOC, calculateMaxPeriod(SENSOR_TVOC_UPDATE_INTERVAL));
|
||||||
|
measurements.maxPeriod(Measurements::TVOCRaw, calculateMaxPeriod(SENSOR_TVOC_UPDATE_INTERVAL));
|
||||||
|
measurements.maxPeriod(Measurements::NOx, calculateMaxPeriod(SENSOR_TVOC_UPDATE_INTERVAL));
|
||||||
|
measurements.maxPeriod(Measurements::NOxRaw, calculateMaxPeriod(SENSOR_TVOC_UPDATE_INTERVAL));
|
||||||
|
/// Max period for PMS sensors measurements
|
||||||
|
measurements.maxPeriod(Measurements::PM25, calculateMaxPeriod(SENSOR_PM_UPDATE_INTERVAL));
|
||||||
|
measurements.maxPeriod(Measurements::PM01, calculateMaxPeriod(SENSOR_PM_UPDATE_INTERVAL));
|
||||||
|
measurements.maxPeriod(Measurements::PM10, calculateMaxPeriod(SENSOR_PM_UPDATE_INTERVAL));
|
||||||
|
measurements.maxPeriod(Measurements::PM03_PC, calculateMaxPeriod(SENSOR_PM_UPDATE_INTERVAL));
|
||||||
|
// Temperature and Humidity
|
||||||
|
if (configuration.hasSensorSHT) {
|
||||||
|
/// Max period for SHT sensors measurements
|
||||||
|
measurements.maxPeriod(Measurements::Temperature,
|
||||||
|
calculateMaxPeriod(SENSOR_TEMP_HUM_UPDATE_INTERVAL));
|
||||||
|
measurements.maxPeriod(Measurements::Humidity,
|
||||||
|
calculateMaxPeriod(SENSOR_TEMP_HUM_UPDATE_INTERVAL));
|
||||||
|
} else {
|
||||||
|
/// Temp and hum data retrieved from PMS5003T sensor
|
||||||
|
measurements.maxPeriod(Measurements::Temperature,
|
||||||
|
calculateMaxPeriod(SENSOR_PM_UPDATE_INTERVAL));
|
||||||
|
measurements.maxPeriod(Measurements::Humidity, calculateMaxPeriod(SENSOR_PM_UPDATE_INTERVAL));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int calculateMaxPeriod(int updateInterval) {
|
||||||
|
// 0.5 is 50% reduced interval for max period
|
||||||
|
return (SERVER_SYNC_INTERVAL - (SERVER_SYNC_INTERVAL * 0.5)) / updateInterval;
|
||||||
|
}
|
@ -53,9 +53,8 @@ void LocalServer::_GET_metrics(void) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void LocalServer::_GET_measure(void) {
|
void LocalServer::_GET_measure(void) {
|
||||||
server.send(
|
String toSend = measure.toString(true, fwMode, wifiConnector.RSSI(), *ag, config);
|
||||||
200, "application/json",
|
server.send(200, "application/json", toSend);
|
||||||
measure.toString(true, fwMode, wifiConnector.RSSI(), ag, &config));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void LocalServer::setFwMode(AgFirmwareMode fwMode) { this->fwMode = fwMode; }
|
void LocalServer::setFwMode(AgFirmwareMode fwMode) { this->fwMode = fwMode; }
|
||||||
|
@ -57,35 +57,45 @@ String OpenMetrics::getPayload(void) {
|
|||||||
"gauge", "dbm");
|
"gauge", "dbm");
|
||||||
add_metric_point("", String(wifiConnector.RSSI()));
|
add_metric_point("", String(wifiConnector.RSSI()));
|
||||||
|
|
||||||
if (config.hasSensorS8 && measure.CO2 >= 0) {
|
// Initialize default invalid value for each measurements
|
||||||
add_metric("co2",
|
|
||||||
"Carbon dioxide concentration as measured by the AirGradient S8 "
|
|
||||||
"sensor, in parts per million",
|
|
||||||
"gauge", "ppm");
|
|
||||||
add_metric_point("", String(measure.CO2));
|
|
||||||
}
|
|
||||||
|
|
||||||
float _temp = utils::getInvalidTemperature();
|
float _temp = utils::getInvalidTemperature();
|
||||||
float _hum = utils::getInvalidHumidity();
|
float _hum = utils::getInvalidHumidity();
|
||||||
int pm01 = utils::getInvalidPmValue();
|
int pm01 = utils::getInvalidPmValue();
|
||||||
int pm25 = utils::getInvalidPmValue();
|
int pm25 = utils::getInvalidPmValue();
|
||||||
int pm10 = utils::getInvalidPmValue();
|
int pm10 = utils::getInvalidPmValue();
|
||||||
int pm03PCount = utils::getInvalidPmValue();
|
int pm03PCount = utils::getInvalidPmValue();
|
||||||
|
int co2 = utils::getInvalidCO2();
|
||||||
int atmpCompensated = utils::getInvalidTemperature();
|
int atmpCompensated = utils::getInvalidTemperature();
|
||||||
int ahumCompensated = utils::getInvalidHumidity();
|
int ahumCompensated = utils::getInvalidHumidity();
|
||||||
|
int tvoc = utils::getInvalidVOC();
|
||||||
|
int tvocRaw = utils::getInvalidVOC();
|
||||||
|
int nox = utils::getInvalidNOx();
|
||||||
|
int noxRaw = utils::getInvalidNOx();
|
||||||
|
|
||||||
if (config.hasSensorSHT) {
|
if (config.hasSensorSHT) {
|
||||||
_temp = measure.Temperature;
|
_temp = measure.getFloat(Measurements::Temperature);
|
||||||
_hum = measure.Humidity;
|
_hum = measure.getFloat(Measurements::Humidity);
|
||||||
atmpCompensated = _temp;
|
atmpCompensated = _temp;
|
||||||
ahumCompensated = _hum;
|
ahumCompensated = _hum;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (config.hasSensorPMS1) {
|
if (config.hasSensorPMS1) {
|
||||||
pm01 = measure.pm01_1;
|
pm01 = measure.get(Measurements::PM01);
|
||||||
pm25 = measure.pm25_1;
|
float correctedPm = measure.getCorrectedPM25(*ag, config, false, 1);
|
||||||
pm10 = measure.pm10_1;
|
pm25 = round(correctedPm);
|
||||||
pm03PCount = measure.pm03PCount_1;
|
pm10 = measure.get(Measurements::PM10);
|
||||||
|
pm03PCount = measure.get(Measurements::PM03_PC);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config.hasSensorSGP) {
|
||||||
|
tvoc = measure.get(Measurements::TVOC);
|
||||||
|
tvocRaw = measure.get(Measurements::TVOCRaw);
|
||||||
|
nox = measure.get(Measurements::NOx);
|
||||||
|
noxRaw = measure.get(Measurements::NOxRaw);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config.hasSensorS8) {
|
||||||
|
co2 = measure.get(Measurements::CO2);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (config.hasSensorPMS1) {
|
if (config.hasSensorPMS1) {
|
||||||
@ -120,36 +130,45 @@ String OpenMetrics::getPayload(void) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (config.hasSensorSGP) {
|
if (config.hasSensorSGP) {
|
||||||
if (utils::isValidVOC(measure.TVOC)) {
|
if (utils::isValidVOC(tvoc)) {
|
||||||
add_metric("tvoc_index",
|
add_metric("tvoc_index",
|
||||||
"The processed Total Volatile Organic Compounds (TVOC) index "
|
"The processed Total Volatile Organic Compounds (TVOC) index "
|
||||||
"as measured by the AirGradient SGP sensor",
|
"as measured by the AirGradient SGP sensor",
|
||||||
"gauge");
|
"gauge");
|
||||||
add_metric_point("", String(measure.TVOC));
|
add_metric_point("", String(tvoc));
|
||||||
}
|
}
|
||||||
if (utils::isValidVOC(measure.TVOCRaw)) {
|
|
||||||
|
if (utils::isValidVOC(tvocRaw)) {
|
||||||
add_metric("tvoc_raw",
|
add_metric("tvoc_raw",
|
||||||
"The raw input value to the Total Volatile Organic Compounds "
|
"The raw input value to the Total Volatile Organic Compounds "
|
||||||
"(TVOC) index as measured by the AirGradient SGP sensor",
|
"(TVOC) index as measured by the AirGradient SGP sensor",
|
||||||
"gauge");
|
"gauge");
|
||||||
add_metric_point("", String(measure.TVOCRaw));
|
add_metric_point("", String(tvocRaw));
|
||||||
}
|
}
|
||||||
if (utils::isValidNOx(measure.NOx)) {
|
if (utils::isValidNOx(nox)) {
|
||||||
add_metric("nox_index",
|
add_metric("nox_index",
|
||||||
"The processed Nitrous Oxide (NOx) index as measured by the "
|
"The processed Nitrous Oxide (NOx) index as measured by the "
|
||||||
"AirGradient SGP sensor",
|
"AirGradient SGP sensor",
|
||||||
"gauge");
|
"gauge");
|
||||||
add_metric_point("", String(measure.NOx));
|
add_metric_point("", String(nox));
|
||||||
}
|
}
|
||||||
if (utils::isValidNOx(measure.NOxRaw)) {
|
if (utils::isValidNOx(noxRaw)) {
|
||||||
add_metric("nox_raw",
|
add_metric("nox_raw",
|
||||||
"The raw input value to the Nitrous Oxide (NOx) index as "
|
"The raw input value to the Nitrous Oxide (NOx) index as "
|
||||||
"measured by the AirGradient SGP sensor",
|
"measured by the AirGradient SGP sensor",
|
||||||
"gauge");
|
"gauge");
|
||||||
add_metric_point("", String(measure.NOxRaw));
|
add_metric_point("", String(noxRaw));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (utils::isValidCO2(co2)) {
|
||||||
|
add_metric("co2",
|
||||||
|
"Carbon dioxide concentration as measured by the AirGradient S8 "
|
||||||
|
"sensor, in parts per million",
|
||||||
|
"gauge", "ppm");
|
||||||
|
add_metric_point("", String(co2));
|
||||||
|
}
|
||||||
|
|
||||||
if (utils::isValidTemperature(_temp)) {
|
if (utils::isValidTemperature(_temp)) {
|
||||||
add_metric(
|
add_metric(
|
||||||
"temperature",
|
"temperature",
|
||||||
|
@ -49,9 +49,8 @@ CC BY-SA 4.0 Attribution-ShareAlike 4.0 International License
|
|||||||
#define SENSOR_TVOC_UPDATE_INTERVAL 1000 /** ms */
|
#define SENSOR_TVOC_UPDATE_INTERVAL 1000 /** ms */
|
||||||
#define SENSOR_CO2_UPDATE_INTERVAL 4000 /** ms */
|
#define SENSOR_CO2_UPDATE_INTERVAL 4000 /** ms */
|
||||||
#define SENSOR_PM_UPDATE_INTERVAL 2000 /** ms */
|
#define SENSOR_PM_UPDATE_INTERVAL 2000 /** ms */
|
||||||
#define SENSOR_TEMP_HUM_UPDATE_INTERVAL 2000 /** ms */
|
#define SENSOR_TEMP_HUM_UPDATE_INTERVAL 6000 /** ms */
|
||||||
#define DISPLAY_DELAY_SHOW_CONTENT_MS 2000 /** ms */
|
#define DISPLAY_DELAY_SHOW_CONTENT_MS 2000 /** ms */
|
||||||
#define FIRMWARE_CHECK_FOR_UPDATE_MS (60 * 60 * 1000) /** ms */
|
|
||||||
|
|
||||||
static AirGradient ag(DIY_PRO_INDOOR_V4_2);
|
static AirGradient ag(DIY_PRO_INDOOR_V4_2);
|
||||||
static Configuration configuration(Serial);
|
static Configuration configuration(Serial);
|
||||||
@ -69,7 +68,6 @@ static LocalServer localServer(Serial, openMetrics, measurements, configuration,
|
|||||||
static MqttClient mqttClient(Serial);
|
static MqttClient mqttClient(Serial);
|
||||||
|
|
||||||
static uint32_t factoryBtnPressTime = 0;
|
static uint32_t factoryBtnPressTime = 0;
|
||||||
static int getCO2FailCount = 0;
|
|
||||||
static AgFirmwareMode fwMode = FW_MODE_I_42PS;
|
static AgFirmwareMode fwMode = FW_MODE_I_42PS;
|
||||||
|
|
||||||
static String fwNewVersion;
|
static String fwNewVersion;
|
||||||
@ -91,6 +89,8 @@ static void wdgFeedUpdate(void);
|
|||||||
static bool sgp41Init(void);
|
static bool sgp41Init(void);
|
||||||
static void wifiFactoryConfigure(void);
|
static void wifiFactoryConfigure(void);
|
||||||
static void mqttHandle(void);
|
static void mqttHandle(void);
|
||||||
|
static int calculateMaxPeriod(int updateInterval);
|
||||||
|
static void setMeasurementMaxPeriod();
|
||||||
|
|
||||||
AgSchedule dispLedSchedule(DISP_UPDATE_INTERVAL, oledDisplaySchedule);
|
AgSchedule dispLedSchedule(DISP_UPDATE_INTERVAL, oledDisplaySchedule);
|
||||||
AgSchedule configSchedule(SERVER_CONFIG_SYNC_INTERVAL,
|
AgSchedule configSchedule(SERVER_CONFIG_SYNC_INTERVAL,
|
||||||
@ -131,6 +131,10 @@ void setup() {
|
|||||||
|
|
||||||
/** Init sensor */
|
/** Init sensor */
|
||||||
boardInit();
|
boardInit();
|
||||||
|
setMeasurementMaxPeriod();
|
||||||
|
|
||||||
|
// Uncomment below line to print every measurements reading update
|
||||||
|
// measurements.setDebug(true);
|
||||||
|
|
||||||
/** Connecting wifi */
|
/** Connecting wifi */
|
||||||
bool connectToWifi = false;
|
bool connectToWifi = false;
|
||||||
@ -255,17 +259,16 @@ void loop() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void co2Update(void) {
|
static void co2Update(void) {
|
||||||
|
if (!configuration.hasSensorS8) {
|
||||||
|
// Device don't have S8 sensor
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
int value = ag.s8.getCo2();
|
int value = ag.s8.getCo2();
|
||||||
if (utils::isValidCO2(value)) {
|
if (utils::isValidCO2(value)) {
|
||||||
measurements.CO2 = value;
|
measurements.update(Measurements::CO2, value);
|
||||||
getCO2FailCount = 0;
|
|
||||||
Serial.printf("CO2 (ppm): %d\r\n", measurements.CO2);
|
|
||||||
} else {
|
} else {
|
||||||
getCO2FailCount++;
|
measurements.update(Measurements::CO2, utils::getInvalidCO2());
|
||||||
Serial.printf("Get CO2 failed: %d\r\n", getCO2FailCount);
|
|
||||||
if (getCO2FailCount >= 3) {
|
|
||||||
measurements.CO2 = utils::getInvalidCO2();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -393,8 +396,7 @@ static void mqttHandle(void) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (mqttClient.isConnected()) {
|
if (mqttClient.isConnected()) {
|
||||||
String payload = measurements.toString(true, fwMode, wifiConnector.RSSI(),
|
String payload = measurements.toString(true, fwMode, wifiConnector.RSSI(), ag, configuration);
|
||||||
&ag, &configuration);
|
|
||||||
String topic = "airgradient/readings/" + ag.deviceId();
|
String topic = "airgradient/readings/" + ag.deviceId();
|
||||||
if (mqttClient.publish(topic.c_str(), payload.c_str(), payload.length())) {
|
if (mqttClient.publish(topic.c_str(), payload.c_str(), payload.length())) {
|
||||||
Serial.println("MQTT sync success");
|
Serial.println("MQTT sync success");
|
||||||
@ -583,88 +585,98 @@ static void oledDisplaySchedule(void) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void updateTvoc(void) {
|
static void updateTvoc(void) {
|
||||||
measurements.TVOC = ag.sgp41.getTvocIndex();
|
if (!configuration.hasSensorSGP) {
|
||||||
measurements.TVOCRaw = ag.sgp41.getTvocRaw();
|
return;
|
||||||
measurements.NOx = ag.sgp41.getNoxIndex();
|
}
|
||||||
measurements.NOxRaw = ag.sgp41.getNoxRaw();
|
|
||||||
|
|
||||||
Serial.println();
|
measurements.update(Measurements::TVOC, ag.sgp41.getTvocIndex());
|
||||||
Serial.printf("TVOC index: %d\r\n", measurements.TVOC);
|
measurements.update(Measurements::TVOCRaw, ag.sgp41.getTvocRaw());
|
||||||
Serial.printf("TVOC raw: %d\r\n", measurements.TVOCRaw);
|
measurements.update(Measurements::NOx, ag.sgp41.getNoxIndex());
|
||||||
Serial.printf("NOx index: %d\r\n", measurements.NOx);
|
measurements.update(Measurements::NOxRaw, ag.sgp41.getNoxRaw());
|
||||||
Serial.printf("NOx raw: %d\r\n", measurements.NOxRaw);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void updatePm(void) {
|
static void updatePm(void) {
|
||||||
if (ag.pms5003.connected()) {
|
if (ag.pms5003.connected()) {
|
||||||
measurements.pm01_1 = ag.pms5003.getPm01Ae();
|
measurements.update(Measurements::PM01, ag.pms5003.getPm01Ae());
|
||||||
measurements.pm25_1 = ag.pms5003.getPm25Ae();
|
measurements.update(Measurements::PM25, ag.pms5003.getPm25Ae());
|
||||||
measurements.pm10_1 = ag.pms5003.getPm10Ae();
|
measurements.update(Measurements::PM10, ag.pms5003.getPm10Ae());
|
||||||
measurements.pm03PCount_1 = ag.pms5003.getPm03ParticleCount();
|
measurements.update(Measurements::PM03_PC, ag.pms5003.getPm03ParticleCount());
|
||||||
|
|
||||||
Serial.println();
|
|
||||||
Serial.printf("PM1 ug/m3: %d\r\n", measurements.pm01_1);
|
|
||||||
Serial.printf("PM2.5 ug/m3: %d\r\n", measurements.pm25_1);
|
|
||||||
Serial.printf("PM10 ug/m3: %d\r\n", measurements.pm10_1);
|
|
||||||
Serial.printf("PM0.3 Count: %d\r\n", measurements.pm03PCount_1);
|
|
||||||
Serial.printf("PM firmware version: %d\r\n", ag.pms5003.getFirmwareVersion());
|
|
||||||
ag.pms5003.resetFailCount();
|
|
||||||
} else {
|
} else {
|
||||||
ag.pms5003.updateFailCount();
|
measurements.update(Measurements::PM01, utils::getInvalidPmValue());
|
||||||
Serial.printf("PMS read failed %d times\r\n", ag.pms5003.getFailCount());
|
measurements.update(Measurements::PM25, utils::getInvalidPmValue());
|
||||||
if (ag.pms5003.getFailCount() >= PMS_FAIL_COUNT_SET_INVALID) {
|
measurements.update(Measurements::PM10, utils::getInvalidPmValue());
|
||||||
measurements.pm01_1 = utils::getInvalidPmValue();
|
measurements.update(Measurements::PM03_PC, utils::getInvalidPmValue());
|
||||||
measurements.pm25_1 = utils::getInvalidPmValue();
|
|
||||||
measurements.pm10_1 = utils::getInvalidPmValue();
|
|
||||||
measurements.pm03PCount_1 = utils::getInvalidPmValue();
|
|
||||||
}
|
|
||||||
|
|
||||||
if(ag.pms5003.getFailCount() >= ag.pms5003.getFailCountMax()) {
|
|
||||||
Serial.printf("PMS failure count reach to max set %d, restarting...", ag.pms5003.getFailCountMax());
|
|
||||||
ESP.restart();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void sendDataToServer(void) {
|
static void sendDataToServer(void) {
|
||||||
|
/** Increment bootcount when send measurements data is scheduled */
|
||||||
|
measurements.bootCount++;
|
||||||
|
|
||||||
/** Ignore send data to server if postToAirGradient disabled */
|
/** Ignore send data to server if postToAirGradient disabled */
|
||||||
if (configuration.isPostDataToAirGradient() == false ||
|
if (configuration.isPostDataToAirGradient() == false ||
|
||||||
configuration.isOfflineMode()) {
|
configuration.isOfflineMode()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
String syncData = measurements.toString(false, fwMode, wifiConnector.RSSI(),
|
String syncData = measurements.toString(false, fwMode, wifiConnector.RSSI(), ag, configuration);
|
||||||
&ag, &configuration);
|
|
||||||
if (apiClient.postToServer(syncData)) {
|
if (apiClient.postToServer(syncData)) {
|
||||||
Serial.println();
|
Serial.println();
|
||||||
Serial.println(
|
Serial.println(
|
||||||
"Online mode and isPostToAirGradient = true: watchdog reset");
|
"Online mode and isPostToAirGradient = true: watchdog reset");
|
||||||
Serial.println();
|
Serial.println();
|
||||||
}
|
}
|
||||||
measurements.bootCount++;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void tempHumUpdate(void) {
|
static void tempHumUpdate(void) {
|
||||||
delay(100);
|
|
||||||
if (ag.sht.measure()) {
|
if (ag.sht.measure()) {
|
||||||
measurements.Temperature = ag.sht.getTemperature();
|
float temp = ag.sht.getTemperature();
|
||||||
measurements.Humidity = ag.sht.getRelativeHumidity();
|
float rhum = ag.sht.getRelativeHumidity();
|
||||||
|
|
||||||
Serial.printf("Temperature in C: %0.2f\r\n", measurements.Temperature);
|
measurements.update(Measurements::Temperature, temp);
|
||||||
Serial.printf("Relative Humidity: %d\r\n", measurements.Humidity);
|
measurements.update(Measurements::Humidity, rhum);
|
||||||
Serial.printf("Temperature compensated in C: %0.2f\r\n",
|
|
||||||
measurements.Temperature);
|
|
||||||
Serial.printf("Relative Humidity compensated: %d\r\n",
|
|
||||||
measurements.Humidity);
|
|
||||||
|
|
||||||
// Update compensation temperature and humidity for SGP41
|
// Update compensation temperature and humidity for SGP41
|
||||||
if (configuration.hasSensorSGP) {
|
if (configuration.hasSensorSGP) {
|
||||||
ag.sgp41.setCompensationTemperatureHumidity(measurements.Temperature,
|
ag.sgp41.setCompensationTemperatureHumidity(temp, rhum);
|
||||||
measurements.Humidity);
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
measurements.update(Measurements::Temperature, utils::getInvalidTemperature());
|
||||||
|
measurements.update(Measurements::Humidity, utils::getInvalidHumidity());
|
||||||
Serial.println("SHT read failed");
|
Serial.println("SHT read failed");
|
||||||
measurements.Temperature = utils::getInvalidTemperature();
|
|
||||||
measurements.Humidity = utils::getInvalidHumidity();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Set max period for each measurement type based on sensor update interval*/
|
||||||
|
void setMeasurementMaxPeriod() {
|
||||||
|
/// Max period for S8 sensors measurements
|
||||||
|
measurements.maxPeriod(Measurements::CO2, calculateMaxPeriod(SENSOR_CO2_UPDATE_INTERVAL));
|
||||||
|
/// Max period for SGP sensors measurements
|
||||||
|
measurements.maxPeriod(Measurements::TVOC, calculateMaxPeriod(SENSOR_TVOC_UPDATE_INTERVAL));
|
||||||
|
measurements.maxPeriod(Measurements::TVOCRaw, calculateMaxPeriod(SENSOR_TVOC_UPDATE_INTERVAL));
|
||||||
|
measurements.maxPeriod(Measurements::NOx, calculateMaxPeriod(SENSOR_TVOC_UPDATE_INTERVAL));
|
||||||
|
measurements.maxPeriod(Measurements::NOxRaw, calculateMaxPeriod(SENSOR_TVOC_UPDATE_INTERVAL));
|
||||||
|
/// Max period for PMS sensors measurements
|
||||||
|
measurements.maxPeriod(Measurements::PM25, calculateMaxPeriod(SENSOR_PM_UPDATE_INTERVAL));
|
||||||
|
measurements.maxPeriod(Measurements::PM01, calculateMaxPeriod(SENSOR_PM_UPDATE_INTERVAL));
|
||||||
|
measurements.maxPeriod(Measurements::PM10, calculateMaxPeriod(SENSOR_PM_UPDATE_INTERVAL));
|
||||||
|
measurements.maxPeriod(Measurements::PM03_PC, calculateMaxPeriod(SENSOR_PM_UPDATE_INTERVAL));
|
||||||
|
// Temperature and Humidity
|
||||||
|
if (configuration.hasSensorSHT) {
|
||||||
|
/// Max period for SHT sensors measurements
|
||||||
|
measurements.maxPeriod(Measurements::Temperature,
|
||||||
|
calculateMaxPeriod(SENSOR_TEMP_HUM_UPDATE_INTERVAL));
|
||||||
|
measurements.maxPeriod(Measurements::Humidity,
|
||||||
|
calculateMaxPeriod(SENSOR_TEMP_HUM_UPDATE_INTERVAL));
|
||||||
|
} else {
|
||||||
|
/// Temp and hum data retrieved from PMS5003T sensor
|
||||||
|
measurements.maxPeriod(Measurements::Temperature,
|
||||||
|
calculateMaxPeriod(SENSOR_PM_UPDATE_INTERVAL));
|
||||||
|
measurements.maxPeriod(Measurements::Humidity, calculateMaxPeriod(SENSOR_PM_UPDATE_INTERVAL));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int calculateMaxPeriod(int updateInterval) {
|
||||||
|
// 0.5 is 50% reduced interval for max period
|
||||||
|
return (SERVER_SYNC_INTERVAL - (SERVER_SYNC_INTERVAL * 0.5)) / updateInterval;
|
||||||
|
}
|
@ -53,9 +53,8 @@ void LocalServer::_GET_metrics(void) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void LocalServer::_GET_measure(void) {
|
void LocalServer::_GET_measure(void) {
|
||||||
server.send(
|
String toSend = measure.toString(true, fwMode, wifiConnector.RSSI(), *ag, config);
|
||||||
200, "application/json",
|
server.send(200, "application/json", toSend);
|
||||||
measure.toString(true, fwMode, wifiConnector.RSSI(), ag, &config));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void LocalServer::setFwMode(AgFirmwareMode fwMode) { this->fwMode = fwMode; }
|
void LocalServer::setFwMode(AgFirmwareMode fwMode) { this->fwMode = fwMode; }
|
||||||
|
@ -57,35 +57,45 @@ String OpenMetrics::getPayload(void) {
|
|||||||
"gauge", "dbm");
|
"gauge", "dbm");
|
||||||
add_metric_point("", String(wifiConnector.RSSI()));
|
add_metric_point("", String(wifiConnector.RSSI()));
|
||||||
|
|
||||||
if (config.hasSensorS8 && measure.CO2 >= 0) {
|
// Initialize default invalid value for each measurements
|
||||||
add_metric("co2",
|
|
||||||
"Carbon dioxide concentration as measured by the AirGradient S8 "
|
|
||||||
"sensor, in parts per million",
|
|
||||||
"gauge", "ppm");
|
|
||||||
add_metric_point("", String(measure.CO2));
|
|
||||||
}
|
|
||||||
|
|
||||||
float _temp = utils::getInvalidTemperature();
|
float _temp = utils::getInvalidTemperature();
|
||||||
float _hum = utils::getInvalidHumidity();
|
float _hum = utils::getInvalidHumidity();
|
||||||
int pm01 = utils::getInvalidPmValue();
|
int pm01 = utils::getInvalidPmValue();
|
||||||
int pm25 = utils::getInvalidPmValue();
|
int pm25 = utils::getInvalidPmValue();
|
||||||
int pm10 = utils::getInvalidPmValue();
|
int pm10 = utils::getInvalidPmValue();
|
||||||
int pm03PCount = utils::getInvalidPmValue();
|
int pm03PCount = utils::getInvalidPmValue();
|
||||||
|
int co2 = utils::getInvalidCO2();
|
||||||
int atmpCompensated = utils::getInvalidTemperature();
|
int atmpCompensated = utils::getInvalidTemperature();
|
||||||
int ahumCompensated = utils::getInvalidHumidity();
|
int ahumCompensated = utils::getInvalidHumidity();
|
||||||
|
int tvoc = utils::getInvalidVOC();
|
||||||
|
int tvocRaw = utils::getInvalidVOC();
|
||||||
|
int nox = utils::getInvalidNOx();
|
||||||
|
int noxRaw = utils::getInvalidNOx();
|
||||||
|
|
||||||
if (config.hasSensorSHT) {
|
if (config.hasSensorSHT) {
|
||||||
_temp = measure.Temperature;
|
_temp = measure.getFloat(Measurements::Temperature);
|
||||||
_hum = measure.Humidity;
|
_hum = measure.getFloat(Measurements::Humidity);
|
||||||
atmpCompensated = _temp;
|
atmpCompensated = _temp;
|
||||||
ahumCompensated = _hum;
|
ahumCompensated = _hum;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (config.hasSensorPMS1) {
|
if (config.hasSensorPMS1) {
|
||||||
pm01 = measure.pm01_1;
|
pm01 = measure.get(Measurements::PM01);
|
||||||
pm25 = measure.pm25_1;
|
float correctedPm = measure.getCorrectedPM25(*ag, config, false, 1);
|
||||||
pm10 = measure.pm10_1;
|
pm25 = round(correctedPm);
|
||||||
pm03PCount = measure.pm03PCount_1;
|
pm10 = measure.get(Measurements::PM10);
|
||||||
|
pm03PCount = measure.get(Measurements::PM03_PC);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config.hasSensorSGP) {
|
||||||
|
tvoc = measure.get(Measurements::TVOC);
|
||||||
|
tvocRaw = measure.get(Measurements::TVOCRaw);
|
||||||
|
nox = measure.get(Measurements::NOx);
|
||||||
|
noxRaw = measure.get(Measurements::NOxRaw);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config.hasSensorS8) {
|
||||||
|
co2 = measure.get(Measurements::CO2);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (config.hasSensorPMS1) {
|
if (config.hasSensorPMS1) {
|
||||||
@ -120,36 +130,44 @@ String OpenMetrics::getPayload(void) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (config.hasSensorSGP) {
|
if (config.hasSensorSGP) {
|
||||||
if (utils::isValidVOC(measure.TVOC)) {
|
if (utils::isValidVOC(tvoc)) {
|
||||||
add_metric("tvoc_index",
|
add_metric("tvoc_index",
|
||||||
"The processed Total Volatile Organic Compounds (TVOC) index "
|
"The processed Total Volatile Organic Compounds (TVOC) index "
|
||||||
"as measured by the AirGradient SGP sensor",
|
"as measured by the AirGradient SGP sensor",
|
||||||
"gauge");
|
"gauge");
|
||||||
add_metric_point("", String(measure.TVOC));
|
add_metric_point("", String(tvoc));
|
||||||
}
|
}
|
||||||
if (utils::isValidVOC(measure.TVOCRaw)) {
|
if (utils::isValidVOC(tvocRaw)) {
|
||||||
add_metric("tvoc_raw",
|
add_metric("tvoc_raw",
|
||||||
"The raw input value to the Total Volatile Organic Compounds "
|
"The raw input value to the Total Volatile Organic Compounds "
|
||||||
"(TVOC) index as measured by the AirGradient SGP sensor",
|
"(TVOC) index as measured by the AirGradient SGP sensor",
|
||||||
"gauge");
|
"gauge");
|
||||||
add_metric_point("", String(measure.TVOCRaw));
|
add_metric_point("", String(tvocRaw));
|
||||||
}
|
}
|
||||||
if (utils::isValidNOx(measure.NOx)) {
|
if (utils::isValidNOx(nox)) {
|
||||||
add_metric("nox_index",
|
add_metric("nox_index",
|
||||||
"The processed Nitrous Oxide (NOx) index as measured by the "
|
"The processed Nitrous Oxide (NOx) index as measured by the "
|
||||||
"AirGradient SGP sensor",
|
"AirGradient SGP sensor",
|
||||||
"gauge");
|
"gauge");
|
||||||
add_metric_point("", String(measure.NOx));
|
add_metric_point("", String(nox));
|
||||||
}
|
}
|
||||||
if (utils::isValidNOx(measure.NOxRaw)) {
|
if (utils::isValidNOx(noxRaw)) {
|
||||||
add_metric("nox_raw",
|
add_metric("nox_raw",
|
||||||
"The raw input value to the Nitrous Oxide (NOx) index as "
|
"The raw input value to the Nitrous Oxide (NOx) index as "
|
||||||
"measured by the AirGradient SGP sensor",
|
"measured by the AirGradient SGP sensor",
|
||||||
"gauge");
|
"gauge");
|
||||||
add_metric_point("", String(measure.NOxRaw));
|
add_metric_point("", String(noxRaw));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (utils::isValidCO2(co2)) {
|
||||||
|
add_metric("co2",
|
||||||
|
"Carbon dioxide concentration as measured by the AirGradient S8 "
|
||||||
|
"sensor, in parts per million",
|
||||||
|
"gauge", "ppm");
|
||||||
|
add_metric_point("", String(co2));
|
||||||
|
}
|
||||||
|
|
||||||
if (utils::isValidTemperature(_temp)) {
|
if (utils::isValidTemperature(_temp)) {
|
||||||
add_metric(
|
add_metric(
|
||||||
"temperature",
|
"temperature",
|
||||||
|
@ -9,10 +9,16 @@ LocalServer::LocalServer(Stream &log, OpenMetrics &openMetrics,
|
|||||||
LocalServer::~LocalServer() {}
|
LocalServer::~LocalServer() {}
|
||||||
|
|
||||||
bool LocalServer::begin(void) {
|
bool LocalServer::begin(void) {
|
||||||
|
server.on("/", HTTP_GET, [this]() { _GET_root(); });
|
||||||
server.on("/measures/current", HTTP_GET, [this]() { _GET_measure(); });
|
server.on("/measures/current", HTTP_GET, [this]() { _GET_measure(); });
|
||||||
server.on(openMetrics.getApi(), HTTP_GET, [this]() { _GET_metrics(); });
|
server.on(openMetrics.getApi(), HTTP_GET, [this]() { _GET_metrics(); });
|
||||||
server.on("/config", HTTP_GET, [this]() { _GET_config(); });
|
server.on("/config", HTTP_GET, [this]() { _GET_config(); });
|
||||||
server.on("/config", HTTP_PUT, [this]() { _PUT_config(); });
|
server.on("/config", HTTP_PUT, [this]() { _PUT_config(); });
|
||||||
|
server.on("/dashboard", HTTP_GET, [this]() { _GET_dashboard(); });
|
||||||
|
server.on("/storage/download", HTTP_GET, [this]() { _GET_storage(); });
|
||||||
|
server.on("/storage/reset", HTTP_POST, [this]() { _POST_storage(); });
|
||||||
|
server.on("/timestamp", HTTP_POST, [this]() { _POST_time(); });
|
||||||
|
|
||||||
server.begin();
|
server.begin();
|
||||||
|
|
||||||
if (xTaskCreate(
|
if (xTaskCreate(
|
||||||
@ -38,6 +44,13 @@ String LocalServer::getHostname(void) {
|
|||||||
|
|
||||||
void LocalServer::_handle(void) { server.handleClient(); }
|
void LocalServer::_handle(void) { server.handleClient(); }
|
||||||
|
|
||||||
|
void LocalServer::_GET_root(void) {
|
||||||
|
String body = "If you are not redirected automatically, go to <a "
|
||||||
|
"href='http://192.168.4.1/dashboard'>dashboard</a>.";
|
||||||
|
|
||||||
|
server.send(302, "text/html", htmlResponse(body, true));
|
||||||
|
}
|
||||||
|
|
||||||
void LocalServer::_GET_config(void) {
|
void LocalServer::_GET_config(void) {
|
||||||
if(ag->isOne()) {
|
if(ag->isOne()) {
|
||||||
server.send(200, "application/json", config.toString());
|
server.send(200, "application/json", config.toString());
|
||||||
@ -64,9 +77,178 @@ void LocalServer::_GET_metrics(void) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void LocalServer::_GET_measure(void) {
|
void LocalServer::_GET_measure(void) {
|
||||||
server.send(
|
String toSend = measure.toString(true, fwMode, wifiConnector.RSSI(), *ag, config);
|
||||||
200, "application/json",
|
server.send(200, "application/json", toSend);
|
||||||
measure.toString(true, fwMode, wifiConnector.RSSI(), ag, &config));
|
}
|
||||||
|
|
||||||
|
void LocalServer::_GET_dashboard(void) {
|
||||||
|
String timestamp = ag->getCurrentTime();
|
||||||
|
server.send(200, "text/html", htmlDashboard(timestamp));
|
||||||
|
}
|
||||||
|
|
||||||
|
void LocalServer::_GET_storage(void) {
|
||||||
|
char *data = measure.getLocalStorage();
|
||||||
|
if (data != nullptr) {
|
||||||
|
String filename =
|
||||||
|
"measurements-" + ag->deviceId().substring(8) + ".csv"; // measurements-fdsa.csv
|
||||||
|
server.sendHeader("Content-Disposition", "attachment; filename=\"" + filename + "\"");
|
||||||
|
server.send_P(200, "text/plain", data);
|
||||||
|
free(data);
|
||||||
|
} else {
|
||||||
|
server.send(204, "text/plain", "No data");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void LocalServer::_POST_storage(void) {
|
||||||
|
String body;
|
||||||
|
int statusCode = 200;
|
||||||
|
|
||||||
|
if (measure.resetLocalStorage()) {
|
||||||
|
body = "Success reset storage";
|
||||||
|
} else {
|
||||||
|
body = "Failed reset local storage, unknown error";
|
||||||
|
statusCode = 500;
|
||||||
|
}
|
||||||
|
body += ". Go to <a href='http://192.168.4.1/dashboard'>dashboard</a>.";
|
||||||
|
|
||||||
|
server.send(statusCode, "text/html", htmlResponse(body, false));
|
||||||
|
}
|
||||||
|
|
||||||
|
void LocalServer::_POST_time(void) {
|
||||||
|
String epochTime = server.arg(0);
|
||||||
|
Serial.printf("Received epoch: %s \n", epochTime.c_str());
|
||||||
|
if (epochTime.isEmpty()) {
|
||||||
|
server.send(400, "text/plain", "Time query not provided");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
long _epochTime = epochTime.toInt();
|
||||||
|
if (_epochTime == 0) {
|
||||||
|
server.send(400, "text/plain", "Time format is not in epoch time");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ag->setCurrentTime(_epochTime);
|
||||||
|
|
||||||
|
String body = "Success set new time. Go to <a href='http://192.168.4.1/dashboard'>dashboard</a>.";
|
||||||
|
server.send(200, "text/html", htmlResponse(body, false));
|
||||||
}
|
}
|
||||||
|
|
||||||
void LocalServer::setFwMode(AgFirmwareMode fwMode) { this->fwMode = fwMode; }
|
void LocalServer::setFwMode(AgFirmwareMode fwMode) { this->fwMode = fwMode; }
|
||||||
|
|
||||||
|
String LocalServer::htmlDashboard(String timestamp) {
|
||||||
|
String page = "";
|
||||||
|
page += "<!DOCTYPE html>";
|
||||||
|
page += "<html lang=\"en\">";
|
||||||
|
page += "<head>";
|
||||||
|
page += " <meta charset=\"UTF-8\">";
|
||||||
|
page += " <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">";
|
||||||
|
page += " <title>AirGradient Local Storage Mode</title>";
|
||||||
|
page += " <style>";
|
||||||
|
page += " body {";
|
||||||
|
page += " font-family: Arial, sans-serif;";
|
||||||
|
page += " display: flex;";
|
||||||
|
page += " flex-direction: column;";
|
||||||
|
page += " align-items: center;";
|
||||||
|
page += " margin-top: 50px;";
|
||||||
|
page += " }";
|
||||||
|
page += "";
|
||||||
|
page += " button {";
|
||||||
|
page += " display: block;";
|
||||||
|
page += " margin: 10px 0;";
|
||||||
|
page += " padding: 10px 20px;";
|
||||||
|
page += " font-size: 16px;";
|
||||||
|
page += " cursor: pointer;";
|
||||||
|
page += " }";
|
||||||
|
page += " .datetime-container {";
|
||||||
|
page += " display: flex;";
|
||||||
|
page += " align-items: center;";
|
||||||
|
page += " margin: 10px 0;";
|
||||||
|
page += " }";
|
||||||
|
page += " .datetime-container input[type=\"datetime-local\"] {";
|
||||||
|
page += " margin-left: 10px;";
|
||||||
|
page += " padding: 5px;";
|
||||||
|
page += " font-size: 16px;";
|
||||||
|
page += " }";
|
||||||
|
page += " button.reset-button {";
|
||||||
|
page += " background-color: red;";
|
||||||
|
page += " color: white;";
|
||||||
|
page += " border: none;";
|
||||||
|
page += " padding: 10px 20px;";
|
||||||
|
page += " font-size: 16px;";
|
||||||
|
page += " cursor: pointer;";
|
||||||
|
page += " }";
|
||||||
|
page += " .spacer {";
|
||||||
|
page += " height: 50px;";
|
||||||
|
page += " }";
|
||||||
|
page += " </style>";
|
||||||
|
page += "</head>";
|
||||||
|
page += "<body>";
|
||||||
|
page += " <h2>";
|
||||||
|
page += " Device Time: ";
|
||||||
|
page += timestamp;
|
||||||
|
page += " </h2>";
|
||||||
|
page += " <h2>";
|
||||||
|
page += " Serial Number: ";
|
||||||
|
page += ag->deviceId();
|
||||||
|
page += " </h2>";
|
||||||
|
page += " <form action=\"/storage/download\" method=\"GET\">";
|
||||||
|
page += " <button type=\"submit\">Download Measurements</button>";
|
||||||
|
page += " </form>";
|
||||||
|
page += " <form id=\"timestampForm\" method=\"POST\" action=\"/timestamp\">";
|
||||||
|
page += " <input type=\"datetime-local\" id=\"timestampInput\" required>";
|
||||||
|
page += " <button type=\"submit\">Set Timestamp</button>";
|
||||||
|
page += " <input type=\"hidden\" name=\"timestamp\" id=\"epochInput\">";
|
||||||
|
page += " </form>";
|
||||||
|
page += " <div class=\"spacer\"></div>";
|
||||||
|
page += " <form action=\"/storage/reset\" method=\"POST\"";
|
||||||
|
page += " onsubmit=\"return confirm('Are you sure you want to reset the measurements? "
|
||||||
|
"This action will permanently delete the existing measurement files!');\">";
|
||||||
|
page += " <button class=\"reset-button\" type=\"submit\">Reset Measurements</button>";
|
||||||
|
page += " </form>";
|
||||||
|
page += "</body>";
|
||||||
|
page += "<script>";
|
||||||
|
page += " document.querySelector('#timestampForm').onsubmit = function (event) {";
|
||||||
|
page += " const datetimeInput = document.querySelector('#timestampInput').value;";
|
||||||
|
page += " const localDate = new Date(datetimeInput);";
|
||||||
|
page += " const epochTimeUTC = Math.floor(Date.UTC(";
|
||||||
|
page += " localDate.getFullYear(),";
|
||||||
|
page += " localDate.getMonth(),";
|
||||||
|
page += " localDate.getDate(),";
|
||||||
|
page += " localDate.getHours(),";
|
||||||
|
page += " localDate.getMinutes()";
|
||||||
|
page += " ) / 1000);";
|
||||||
|
page += " document.querySelector('#epochInput').value = epochTimeUTC;";
|
||||||
|
page += " return true;";
|
||||||
|
page += " };";
|
||||||
|
page += "</script>";
|
||||||
|
page += "</html>";
|
||||||
|
|
||||||
|
return page;
|
||||||
|
}
|
||||||
|
|
||||||
|
String LocalServer::htmlResponse(String body, bool redirect) {
|
||||||
|
String page = "";
|
||||||
|
page += "<!DOCTYPE HTML>";
|
||||||
|
page += "<html lang=\"en-US\">";
|
||||||
|
page += " <head>";
|
||||||
|
page += "<style>";
|
||||||
|
page += "p { font-size: 22px; }";
|
||||||
|
page += "</style>";
|
||||||
|
page += " <meta charset=\"UTF-8\">";
|
||||||
|
|
||||||
|
if (redirect) {
|
||||||
|
page += " <meta http-equiv=\"refresh\" content=\"0;url=/dashboard\">";
|
||||||
|
}
|
||||||
|
|
||||||
|
page += " <title>Page Redirection</title>";
|
||||||
|
page += " </head>";
|
||||||
|
page += " <body>";
|
||||||
|
page += " <p>";
|
||||||
|
page += body;
|
||||||
|
page += " </p>";
|
||||||
|
page += " </body>";
|
||||||
|
page += "</html>";
|
||||||
|
|
||||||
|
return page;
|
||||||
|
}
|
@ -19,6 +19,9 @@ private:
|
|||||||
WebServer server;
|
WebServer server;
|
||||||
AgFirmwareMode fwMode;
|
AgFirmwareMode fwMode;
|
||||||
|
|
||||||
|
String htmlDashboard(String timestamp);
|
||||||
|
String htmlResponse(String body, bool redirect);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
LocalServer(Stream &log, OpenMetrics &openMetrics, Measurements &measure,
|
LocalServer(Stream &log, OpenMetrics &openMetrics, Measurements &measure,
|
||||||
Configuration &config, WifiConnector& wifiConnector);
|
Configuration &config, WifiConnector& wifiConnector);
|
||||||
@ -29,10 +32,15 @@ public:
|
|||||||
String getHostname(void);
|
String getHostname(void);
|
||||||
void setFwMode(AgFirmwareMode fwMode);
|
void setFwMode(AgFirmwareMode fwMode);
|
||||||
void _handle(void);
|
void _handle(void);
|
||||||
|
void _GET_root(void);
|
||||||
void _GET_config(void);
|
void _GET_config(void);
|
||||||
void _PUT_config(void);
|
void _PUT_config(void);
|
||||||
void _GET_metrics(void);
|
void _GET_metrics(void);
|
||||||
void _GET_measure(void);
|
void _GET_measure(void);
|
||||||
|
void _GET_dashboard(void);
|
||||||
|
void _GET_storage(void);
|
||||||
|
void _POST_storage(void);
|
||||||
|
void _POST_time(void);
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif /** _LOCAL_SERVER_H_ */
|
#endif /** _LOCAL_SERVER_H_ */
|
||||||
|
@ -62,7 +62,7 @@ CC BY-SA 4.0 Attribution-ShareAlike 4.0 International License
|
|||||||
#define SENSOR_TVOC_UPDATE_INTERVAL 1000 /** ms */
|
#define SENSOR_TVOC_UPDATE_INTERVAL 1000 /** ms */
|
||||||
#define SENSOR_CO2_UPDATE_INTERVAL 4000 /** ms */
|
#define SENSOR_CO2_UPDATE_INTERVAL 4000 /** ms */
|
||||||
#define SENSOR_PM_UPDATE_INTERVAL 2000 /** ms */
|
#define SENSOR_PM_UPDATE_INTERVAL 2000 /** ms */
|
||||||
#define SENSOR_TEMP_HUM_UPDATE_INTERVAL 2000 /** ms */
|
#define SENSOR_TEMP_HUM_UPDATE_INTERVAL 6000 /** ms */
|
||||||
#define DISPLAY_DELAY_SHOW_CONTENT_MS 2000 /** ms */
|
#define DISPLAY_DELAY_SHOW_CONTENT_MS 2000 /** ms */
|
||||||
#define FIRMWARE_CHECK_FOR_UPDATE_MS (60*60*1000) /** ms */
|
#define FIRMWARE_CHECK_FOR_UPDATE_MS (60*60*1000) /** ms */
|
||||||
|
|
||||||
@ -89,11 +89,11 @@ static LocalServer localServer(Serial, openMetrics, measurements, configuration,
|
|||||||
wifiConnector);
|
wifiConnector);
|
||||||
|
|
||||||
static uint32_t factoryBtnPressTime = 0;
|
static uint32_t factoryBtnPressTime = 0;
|
||||||
static int getCO2FailCount = 0;
|
|
||||||
static AgFirmwareMode fwMode = FW_MODE_I_9PSL;
|
static AgFirmwareMode fwMode = FW_MODE_I_9PSL;
|
||||||
|
|
||||||
static bool ledBarButtonTest = false;
|
static bool ledBarButtonTest = false;
|
||||||
static String fwNewVersion;
|
static String fwNewVersion;
|
||||||
|
static bool isLocalServerInitialized = false;
|
||||||
|
|
||||||
static void boardInit(void);
|
static void boardInit(void);
|
||||||
static void failedHandler(String msg);
|
static void failedHandler(String msg);
|
||||||
@ -115,23 +115,31 @@ static void firmwareCheckForUpdate(void);
|
|||||||
static void otaHandlerCallback(OtaState state, String mesasge);
|
static void otaHandlerCallback(OtaState state, String mesasge);
|
||||||
static void displayExecuteOta(OtaState state, String msg,
|
static void displayExecuteOta(OtaState state, String msg,
|
||||||
int processing);
|
int processing);
|
||||||
|
static int calculateMaxPeriod(int updateInterval);
|
||||||
|
static void setMeasurementMaxPeriod();
|
||||||
|
static void offlineStorageUpdate();
|
||||||
|
|
||||||
AgSchedule dispLedSchedule(DISP_UPDATE_INTERVAL, updateDisplayAndLedBar);
|
AgSchedule dispLedSchedule(DISP_UPDATE_INTERVAL, updateDisplayAndLedBar);
|
||||||
AgSchedule configSchedule(SERVER_CONFIG_SYNC_INTERVAL,
|
// AgSchedule configSchedule(SERVER_CONFIG_SYNC_INTERVAL,
|
||||||
configurationUpdateSchedule);
|
// configurationUpdateSchedule);
|
||||||
AgSchedule agApiPostSchedule(SERVER_SYNC_INTERVAL, sendDataToServer);
|
// AgSchedule agApiPostSchedule(SERVER_SYNC_INTERVAL, sendDataToServer);
|
||||||
AgSchedule co2Schedule(SENSOR_CO2_UPDATE_INTERVAL, co2Update);
|
AgSchedule co2Schedule(SENSOR_CO2_UPDATE_INTERVAL, co2Update);
|
||||||
AgSchedule pmsSchedule(SENSOR_PM_UPDATE_INTERVAL, updatePm);
|
AgSchedule pmsSchedule(SENSOR_PM_UPDATE_INTERVAL, updatePm);
|
||||||
AgSchedule tempHumSchedule(SENSOR_TEMP_HUM_UPDATE_INTERVAL, tempHumUpdate);
|
AgSchedule tempHumSchedule(SENSOR_TEMP_HUM_UPDATE_INTERVAL, tempHumUpdate);
|
||||||
AgSchedule tvocSchedule(SENSOR_TVOC_UPDATE_INTERVAL, updateTvoc);
|
AgSchedule tvocSchedule(SENSOR_TVOC_UPDATE_INTERVAL, updateTvoc);
|
||||||
AgSchedule watchdogFeedSchedule(60000, wdgFeedUpdate);
|
AgSchedule watchdogFeedSchedule(60000, wdgFeedUpdate);
|
||||||
AgSchedule checkForUpdateSchedule(FIRMWARE_CHECK_FOR_UPDATE_MS, firmwareCheckForUpdate);
|
AgSchedule offlineStorage((2 * 60000), offlineStorageUpdate);
|
||||||
|
// AgSchedule checkForUpdateSchedule(FIRMWARE_CHECK_FOR_UPDATE_MS, firmwareCheckForUpdate);
|
||||||
|
|
||||||
void setup() {
|
void setup() {
|
||||||
/** Serial for print debug message */
|
/** Serial for print debug message */
|
||||||
Serial.begin(115200);
|
Serial.begin(115200);
|
||||||
delay(100); /** For bester show log */
|
delay(100); /** For bester show log */
|
||||||
|
|
||||||
|
// Set timezone to UTC
|
||||||
|
setenv("TZ", "UTC", 1);
|
||||||
|
tzset();
|
||||||
|
|
||||||
/** Print device ID into log */
|
/** Print device ID into log */
|
||||||
Serial.println("Serial nr: " + ag->deviceId());
|
Serial.println("Serial nr: " + ag->deviceId());
|
||||||
|
|
||||||
@ -165,100 +173,37 @@ void setup() {
|
|||||||
|
|
||||||
/** Init sensor */
|
/** Init sensor */
|
||||||
boardInit();
|
boardInit();
|
||||||
|
setMeasurementMaxPeriod();
|
||||||
|
|
||||||
/** Connecting wifi */
|
// Comment below line to disable debug measurement readings
|
||||||
bool connectToWifi = false;
|
measurements.setDebug(false);
|
||||||
if (ag->isOne()) {
|
|
||||||
/** Show message confirm offline mode, should me perform if LED bar button
|
|
||||||
* test pressed */
|
|
||||||
if (ledBarButtonTest == false) {
|
|
||||||
oledDisplay.setText(
|
|
||||||
"Press now for",
|
|
||||||
configuration.isOfflineMode() ? "online mode" : "offline mode", "");
|
|
||||||
uint32_t startTime = millis();
|
|
||||||
while (true) {
|
|
||||||
if (ag->button.getState() == ag->button.BUTTON_PRESSED) {
|
|
||||||
configuration.setOfflineMode(!configuration.isOfflineMode());
|
|
||||||
|
|
||||||
oledDisplay.setText(
|
// Force to offline mode
|
||||||
"Offline Mode",
|
configuration.setOfflineMode(true);
|
||||||
configuration.isOfflineMode() ? " = True" : " = False", "");
|
|
||||||
delay(1000);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
uint32_t periodMs = (uint32_t)(millis() - startTime);
|
|
||||||
if (periodMs >= 3000) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
connectToWifi = !configuration.isOfflineMode();
|
|
||||||
} else {
|
|
||||||
configuration.setOfflineModeWithoutSave(true);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
connectToWifi = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (connectToWifi) {
|
|
||||||
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");
|
|
||||||
configuration.setOfflineModeWithoutSave(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Show display Warning up */
|
/** Show display Warning up */
|
||||||
if (ag->isOne()) {
|
if (ag->isOne()) {
|
||||||
oledDisplay.setText("Warming Up", "Serial Number:", ag->deviceId().c_str());
|
oledDisplay.setText("Warming Up", "Serial Number:", ag->deviceId().c_str());
|
||||||
delay(DISPLAY_DELAY_SHOW_CONTENT_MS);
|
delay(DISPLAY_DELAY_SHOW_CONTENT_MS);
|
||||||
|
|
||||||
Serial.println("Display brightness: " + String(configuration.getDisplayBrightness()));
|
|
||||||
oledDisplay.setBrightness(configuration.getDisplayBrightness());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String deviceId = ag->deviceId();
|
||||||
|
|
||||||
|
// Connect to Wi-Fi network with SSID and password
|
||||||
|
Serial.print("Setting AP (Access Point)…");
|
||||||
|
// Remove the password parameter, if you want the AP (Access Point) to be open
|
||||||
|
WiFi.softAP("ag_" + deviceId, "cleanair");
|
||||||
|
IPAddress IP = WiFi.softAPIP();
|
||||||
|
Serial.print("AP IP address: ");
|
||||||
|
Serial.println(IP);
|
||||||
|
Serial.printf("SSID: ag_%s\n", deviceId.c_str());
|
||||||
|
|
||||||
|
oledDisplay.setText("", "Offline Storage Mode", "");
|
||||||
|
|
||||||
|
delay(3000);
|
||||||
|
// mdnsInit();
|
||||||
|
localServer.begin();
|
||||||
|
|
||||||
// Update display and led bar after finishing setup to show dashboard
|
// Update display and led bar after finishing setup to show dashboard
|
||||||
updateDisplayAndLedBar();
|
updateDisplayAndLedBar();
|
||||||
}
|
}
|
||||||
@ -266,8 +211,9 @@ void setup() {
|
|||||||
void loop() {
|
void loop() {
|
||||||
/** Handle schedule */
|
/** Handle schedule */
|
||||||
dispLedSchedule.run();
|
dispLedSchedule.run();
|
||||||
configSchedule.run();
|
// configSchedule.run();
|
||||||
agApiPostSchedule.run();
|
// agApiPostSchedule.run();
|
||||||
|
offlineStorage.run();
|
||||||
|
|
||||||
if (configuration.hasSensorS8) {
|
if (configuration.hasSensorS8) {
|
||||||
co2Schedule.run();
|
co2Schedule.run();
|
||||||
@ -303,8 +249,8 @@ void loop() {
|
|||||||
|
|
||||||
watchdogFeedSchedule.run();
|
watchdogFeedSchedule.run();
|
||||||
|
|
||||||
/** Check for handle WiFi reconnect */
|
// /** Check for handle WiFi reconnect */
|
||||||
wifiConnector.handle();
|
// wifiConnector.handle();
|
||||||
|
|
||||||
/** factory reset handle */
|
/** factory reset handle */
|
||||||
factoryConfigReset();
|
factoryConfigReset();
|
||||||
@ -313,21 +259,20 @@ void loop() {
|
|||||||
configUpdateHandle();
|
configUpdateHandle();
|
||||||
|
|
||||||
/** Firmware check for update handle */
|
/** Firmware check for update handle */
|
||||||
checkForUpdateSchedule.run();
|
// checkForUpdateSchedule.run();
|
||||||
}
|
}
|
||||||
|
|
||||||
static void co2Update(void) {
|
static void co2Update(void) {
|
||||||
|
if (!configuration.hasSensorS8) {
|
||||||
|
// Device don't have S8 sensor
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
int value = ag->s8.getCo2();
|
int value = ag->s8.getCo2();
|
||||||
if (utils::isValidCO2(value)) {
|
if (utils::isValidCO2(value)) {
|
||||||
measurements.CO2 = value;
|
measurements.update(Measurements::CO2, value);
|
||||||
getCO2FailCount = 0;
|
|
||||||
Serial.printf("CO2 (ppm): %d\r\n", measurements.CO2);
|
|
||||||
} else {
|
} else {
|
||||||
getCO2FailCount++;
|
measurements.update(Measurements::CO2, utils::getInvalidCO2());
|
||||||
Serial.printf("Get CO2 failed: %d\r\n", getCO2FailCount);
|
|
||||||
if (getCO2FailCount >= 3) {
|
|
||||||
measurements.CO2 = utils::getInvalidCO2();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -360,8 +305,8 @@ static void createMqttTask(void) {
|
|||||||
|
|
||||||
/** Send data */
|
/** Send data */
|
||||||
if (mqttClient.isConnected()) {
|
if (mqttClient.isConnected()) {
|
||||||
String payload = measurements.toString(
|
String payload =
|
||||||
true, fwMode, wifiConnector.RSSI(), ag, &configuration);
|
measurements.toString(true, fwMode, wifiConnector.RSSI(), *ag, configuration);
|
||||||
String topic = "airgradient/readings/" + ag->deviceId();
|
String topic = "airgradient/readings/" + ag->deviceId();
|
||||||
|
|
||||||
if (mqttClient.publish(topic.c_str(), payload.c_str(),
|
if (mqttClient.publish(topic.c_str(), payload.c_str(),
|
||||||
@ -432,7 +377,7 @@ static void factoryConfigReset(void) {
|
|||||||
WiFi.disconnect(true, true);
|
WiFi.disconnect(true, true);
|
||||||
|
|
||||||
/** Reset local config */
|
/** Reset local config */
|
||||||
configuration.reset();
|
// configuration.reset();
|
||||||
|
|
||||||
if (ag->isOne()) {
|
if (ag->isOne()) {
|
||||||
oledDisplay.setText("Factory reset", "successful", "");
|
oledDisplay.setText("Factory reset", "successful", "");
|
||||||
@ -466,6 +411,8 @@ static void factoryConfigReset(void) {
|
|||||||
static void wdgFeedUpdate(void) {
|
static void wdgFeedUpdate(void) {
|
||||||
ag->watchdog.reset();
|
ag->watchdog.reset();
|
||||||
Serial.println("External watchdog feed!");
|
Serial.println("External watchdog feed!");
|
||||||
|
/** Log current free heap size */
|
||||||
|
Serial.printf("Free heap: %u\n", ESP.getFreeHeap());
|
||||||
}
|
}
|
||||||
|
|
||||||
static void ledBarEnabledUpdate(void) {
|
static void ledBarEnabledUpdate(void) {
|
||||||
@ -656,6 +603,7 @@ static void oneIndoorInit(void) {
|
|||||||
|
|
||||||
/** Display init */
|
/** Display init */
|
||||||
oledDisplay.begin();
|
oledDisplay.begin();
|
||||||
|
oledDisplay.setBrightness(40);
|
||||||
|
|
||||||
/** Show boot display */
|
/** Show boot display */
|
||||||
Serial.println("Firmware Version: " + ag->getVersion());
|
Serial.println("Firmware Version: " + ag->getVersion());
|
||||||
@ -959,7 +907,7 @@ static void updateDisplayAndLedBar(void) {
|
|||||||
if (configuration.isOfflineMode()) {
|
if (configuration.isOfflineMode()) {
|
||||||
// Ignore network related status when in offline mode
|
// Ignore network related status when in offline mode
|
||||||
stateMachine.displayHandle(AgStateMachineNormal);
|
stateMachine.displayHandle(AgStateMachineNormal);
|
||||||
stateMachine.handleLeds(AgStateMachineNormal);
|
// stateMachine.handleLeds(AgStateMachineNormal);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -982,281 +930,245 @@ static void updateDisplayAndLedBar(void) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void updateTvoc(void) {
|
static void updateTvoc(void) {
|
||||||
measurements.TVOC = ag->sgp41.getTvocIndex();
|
if (!configuration.hasSensorSGP) {
|
||||||
measurements.TVOCRaw = ag->sgp41.getTvocRaw();
|
return;
|
||||||
measurements.NOx = ag->sgp41.getNoxIndex();
|
}
|
||||||
measurements.NOxRaw = ag->sgp41.getNoxRaw();
|
|
||||||
|
|
||||||
Serial.println();
|
measurements.update(Measurements::TVOC, ag->sgp41.getTvocIndex());
|
||||||
Serial.printf("TVOC index: %d\r\n", measurements.TVOC);
|
measurements.update(Measurements::TVOCRaw, ag->sgp41.getTvocRaw());
|
||||||
Serial.printf("TVOC raw: %d\r\n", measurements.TVOCRaw);
|
measurements.update(Measurements::NOx, ag->sgp41.getNoxIndex());
|
||||||
Serial.printf("NOx index: %d\r\n", measurements.NOx);
|
measurements.update(Measurements::NOxRaw, ag->sgp41.getNoxRaw());
|
||||||
Serial.printf("NOx raw: %d\r\n", measurements.NOxRaw);
|
}
|
||||||
|
|
||||||
|
static void updatePMS5003() {
|
||||||
|
if (ag->pms5003.connected()) {
|
||||||
|
measurements.update(Measurements::PM01, ag->pms5003.getPm01Ae());
|
||||||
|
measurements.update(Measurements::PM25, ag->pms5003.getPm25Ae());
|
||||||
|
measurements.update(Measurements::PM10, ag->pms5003.getPm10Ae());
|
||||||
|
measurements.update(Measurements::PM01_SP, ag->pms5003.getPm01Sp());
|
||||||
|
measurements.update(Measurements::PM25_SP, ag->pms5003.getPm25Sp());
|
||||||
|
measurements.update(Measurements::PM10_SP, ag->pms5003.getPm10Sp());
|
||||||
|
measurements.update(Measurements::PM03_PC, ag->pms5003.getPm03ParticleCount());
|
||||||
|
measurements.update(Measurements::PM05_PC, ag->pms5003.getPm05ParticleCount());
|
||||||
|
measurements.update(Measurements::PM01_PC, ag->pms5003.getPm01ParticleCount());
|
||||||
|
measurements.update(Measurements::PM25_PC, ag->pms5003.getPm25ParticleCount());
|
||||||
|
measurements.update(Measurements::PM5_PC, ag->pms5003.getPm5ParticleCount());
|
||||||
|
measurements.update(Measurements::PM10_PC, ag->pms5003.getPm10ParticleCount());
|
||||||
|
} else {
|
||||||
|
measurements.update(Measurements::PM01, utils::getInvalidPmValue());
|
||||||
|
measurements.update(Measurements::PM25, utils::getInvalidPmValue());
|
||||||
|
measurements.update(Measurements::PM10, utils::getInvalidPmValue());
|
||||||
|
measurements.update(Measurements::PM01_SP, utils::getInvalidPmValue());
|
||||||
|
measurements.update(Measurements::PM25_SP, utils::getInvalidPmValue());
|
||||||
|
measurements.update(Measurements::PM10_SP, utils::getInvalidPmValue());
|
||||||
|
measurements.update(Measurements::PM03_PC, utils::getInvalidPmValue());
|
||||||
|
measurements.update(Measurements::PM05_PC, utils::getInvalidPmValue());
|
||||||
|
measurements.update(Measurements::PM01_PC, utils::getInvalidPmValue());
|
||||||
|
measurements.update(Measurements::PM25_PC, utils::getInvalidPmValue());
|
||||||
|
measurements.update(Measurements::PM5_PC, utils::getInvalidPmValue());
|
||||||
|
measurements.update(Measurements::PM10_PC, utils::getInvalidPmValue());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void updatePm(void) {
|
static void updatePm(void) {
|
||||||
bool restart = false;
|
|
||||||
if (ag->isOne()) {
|
if (ag->isOne()) {
|
||||||
if (ag->pms5003.connected()) {
|
updatePMS5003();
|
||||||
measurements.pm01_1 = ag->pms5003.getPm01Ae();
|
return;
|
||||||
measurements.pm25_1 = ag->pms5003.getPm25Ae();
|
|
||||||
measurements.pm10_1 = ag->pms5003.getPm10Ae();
|
|
||||||
measurements.pm03PCount_1 = ag->pms5003.getPm03ParticleCount();
|
|
||||||
|
|
||||||
Serial.println();
|
|
||||||
Serial.printf("PM1 ug/m3: %d\r\n", measurements.pm01_1);
|
|
||||||
Serial.printf("PM2.5 ug/m3: %d\r\n", measurements.pm25_1);
|
|
||||||
Serial.printf("PM10 ug/m3: %d\r\n", measurements.pm10_1);
|
|
||||||
Serial.printf("PM0.3 Count: %d\r\n", measurements.pm03PCount_1);
|
|
||||||
Serial.printf("PM firmware version: %d\r\n", ag->pms5003.getFirmwareVersion());
|
|
||||||
ag->pms5003.resetFailCount();
|
|
||||||
} else {
|
|
||||||
ag->pms5003.updateFailCount();
|
|
||||||
Serial.printf("PMS read failed %d times\r\n", ag->pms5003.getFailCount());
|
|
||||||
if (ag->pms5003.getFailCount() >= PMS_FAIL_COUNT_SET_INVALID) {
|
|
||||||
measurements.pm01_1 = utils::getInvalidPmValue();
|
|
||||||
measurements.pm25_1 = utils::getInvalidPmValue();
|
|
||||||
measurements.pm10_1 = utils::getInvalidPmValue();
|
|
||||||
measurements.pm03PCount_1 = utils::getInvalidPmValue();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ag->pms5003.getFailCount() >= ag->pms5003.getFailCountMax()) {
|
// Open Air Monitor series, can have two PMS5003T sensor
|
||||||
restart = true;
|
bool newPMS1Value = false;
|
||||||
}
|
bool newPMS2Value = false;
|
||||||
}
|
|
||||||
} else {
|
|
||||||
bool pmsResult_1 = false;
|
|
||||||
bool pmsResult_2 = false;
|
|
||||||
if (configuration.hasSensorPMS1 && ag->pms5003t_1.connected()) {
|
|
||||||
measurements.pm01_1 = ag->pms5003t_1.getPm01Ae();
|
|
||||||
measurements.pm25_1 = ag->pms5003t_1.getPm25Ae();
|
|
||||||
measurements.pm10_1 = ag->pms5003t_1.getPm10Ae();
|
|
||||||
measurements.pm03PCount_1 = ag->pms5003t_1.getPm03ParticleCount();
|
|
||||||
measurements.temp_1 = ag->pms5003t_1.getTemperature();
|
|
||||||
measurements.hum_1 = ag->pms5003t_1.getRelativeHumidity();
|
|
||||||
|
|
||||||
pmsResult_1 = true;
|
// Read PMS channel 1 if available
|
||||||
|
int channel = 1;
|
||||||
Serial.println();
|
|
||||||
Serial.printf("[1] PM1 ug/m3: %d\r\n", measurements.pm01_1);
|
|
||||||
Serial.printf("[1] PM2.5 ug/m3: %d\r\n", measurements.pm25_1);
|
|
||||||
Serial.printf("[1] PM10 ug/m3: %d\r\n", measurements.pm10_1);
|
|
||||||
Serial.printf("[1] PM3.0 Count: %d\r\n", measurements.pm03PCount_1);
|
|
||||||
Serial.printf("[1] Temperature in C: %0.2f\r\n", measurements.temp_1);
|
|
||||||
Serial.printf("[1] Relative Humidity: %d\r\n", measurements.hum_1);
|
|
||||||
Serial.printf("[1] Temperature compensated in C: %0.2f\r\n",
|
|
||||||
ag->pms5003t_1.compensateTemp(measurements.temp_1));
|
|
||||||
Serial.printf("[1] Relative Humidity compensated: %0.2f\r\n",
|
|
||||||
ag->pms5003t_1.compensateHum(measurements.hum_1));
|
|
||||||
Serial.printf("[1] PM firmware version: %d\r\n", ag->pms5003t_1.getFirmwareVersion());
|
|
||||||
|
|
||||||
ag->pms5003t_1.resetFailCount();
|
|
||||||
} else {
|
|
||||||
if (configuration.hasSensorPMS1) {
|
if (configuration.hasSensorPMS1) {
|
||||||
ag->pms5003t_1.updateFailCount();
|
if (ag->pms5003t_1.connected()) {
|
||||||
Serial.printf("[1] PMS read failed %d times\r\n", ag->pms5003t_1.getFailCount());
|
measurements.update(Measurements::PM01, ag->pms5003t_1.getPm01Ae(), channel);
|
||||||
|
measurements.update(Measurements::PM25, ag->pms5003t_1.getPm25Ae(), channel);
|
||||||
|
measurements.update(Measurements::PM10, ag->pms5003t_1.getPm10Ae(), channel);
|
||||||
|
measurements.update(Measurements::PM01_SP, ag->pms5003t_1.getPm01Sp(), channel);
|
||||||
|
measurements.update(Measurements::PM25_SP, ag->pms5003t_1.getPm25Sp(), channel);
|
||||||
|
measurements.update(Measurements::PM10_SP, ag->pms5003t_1.getPm10Sp(), channel);
|
||||||
|
measurements.update(Measurements::PM03_PC, ag->pms5003t_1.getPm03ParticleCount(), channel);
|
||||||
|
measurements.update(Measurements::PM05_PC, ag->pms5003t_1.getPm05ParticleCount(), channel);
|
||||||
|
measurements.update(Measurements::PM01_PC, ag->pms5003t_1.getPm01ParticleCount(), channel);
|
||||||
|
measurements.update(Measurements::PM25_PC, ag->pms5003t_1.getPm25ParticleCount(), channel);
|
||||||
|
measurements.update(Measurements::Temperature, ag->pms5003t_1.getTemperature(), channel);
|
||||||
|
measurements.update(Measurements::Humidity, ag->pms5003t_1.getRelativeHumidity(), channel);
|
||||||
|
|
||||||
if (ag->pms5003t_1.getFailCount() >= PMS_FAIL_COUNT_SET_INVALID) {
|
// flag that new valid PMS value exists
|
||||||
measurements.pm01_1 = utils::getInvalidPmValue();
|
newPMS1Value = true;
|
||||||
measurements.pm25_1 = utils::getInvalidPmValue();
|
|
||||||
measurements.pm10_1 = utils::getInvalidPmValue();
|
|
||||||
measurements.pm03PCount_1 = utils::getInvalidPmValue();
|
|
||||||
measurements.temp_1 = utils::getInvalidTemperature();
|
|
||||||
measurements.hum_1 = utils::getInvalidHumidity();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ag->pms5003t_1.getFailCount() >= ag->pms5003t_1.getFailCountMax()) {
|
|
||||||
restart = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (configuration.hasSensorPMS2 && ag->pms5003t_2.connected()) {
|
|
||||||
measurements.pm01_2 = ag->pms5003t_2.getPm01Ae();
|
|
||||||
measurements.pm25_2 = ag->pms5003t_2.getPm25Ae();
|
|
||||||
measurements.pm10_2 = ag->pms5003t_2.getPm10Ae();
|
|
||||||
measurements.pm03PCount_2 = ag->pms5003t_2.getPm03ParticleCount();
|
|
||||||
measurements.temp_2 = ag->pms5003t_2.getTemperature();
|
|
||||||
measurements.hum_2 = ag->pms5003t_2.getRelativeHumidity();
|
|
||||||
|
|
||||||
pmsResult_2 = true;
|
|
||||||
|
|
||||||
Serial.println();
|
|
||||||
Serial.printf("[2] PM1 ug/m3: %d\r\n", measurements.pm01_2);
|
|
||||||
Serial.printf("[2] PM2.5 ug/m3: %d\r\n", measurements.pm25_2);
|
|
||||||
Serial.printf("[2] PM10 ug/m3: %d\r\n", measurements.pm10_2);
|
|
||||||
Serial.printf("[2] PM3.0 Count: %d\r\n", measurements.pm03PCount_2);
|
|
||||||
Serial.printf("[2] Temperature in C: %0.2f\r\n", measurements.temp_2);
|
|
||||||
Serial.printf("[2] Relative Humidity: %d\r\n", measurements.hum_2);
|
|
||||||
Serial.printf("[2] Temperature compensated in C: %0.2f\r\n",
|
|
||||||
ag->pms5003t_1.compensateTemp(measurements.temp_2));
|
|
||||||
Serial.printf("[2] Relative Humidity compensated: %0.2f\r\n",
|
|
||||||
ag->pms5003t_1.compensateHum(measurements.hum_2));
|
|
||||||
Serial.printf("[2] PM firmware version: %d\r\n", ag->pms5003t_2.getFirmwareVersion());
|
|
||||||
|
|
||||||
ag->pms5003t_2.resetFailCount();
|
|
||||||
} else {
|
} else {
|
||||||
|
// PMS channel 1 now is not connected, update using invalid value
|
||||||
|
measurements.update(Measurements::PM01, utils::getInvalidPmValue(), channel);
|
||||||
|
measurements.update(Measurements::PM25, utils::getInvalidPmValue(), channel);
|
||||||
|
measurements.update(Measurements::PM10, utils::getInvalidPmValue(), channel);
|
||||||
|
measurements.update(Measurements::PM01_SP, utils::getInvalidPmValue(), channel);
|
||||||
|
measurements.update(Measurements::PM25_SP, utils::getInvalidPmValue(), channel);
|
||||||
|
measurements.update(Measurements::PM10_SP, utils::getInvalidPmValue(), channel);
|
||||||
|
measurements.update(Measurements::PM03_PC, utils::getInvalidPmValue(), channel);
|
||||||
|
measurements.update(Measurements::PM05_PC, utils::getInvalidPmValue(), channel);
|
||||||
|
measurements.update(Measurements::PM01_PC, utils::getInvalidPmValue(), channel);
|
||||||
|
measurements.update(Measurements::PM25_PC, utils::getInvalidPmValue(), channel);
|
||||||
|
measurements.update(Measurements::Temperature, utils::getInvalidTemperature(), channel);
|
||||||
|
measurements.update(Measurements::Humidity, utils::getInvalidHumidity(), channel);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read PMS channel 2 if available
|
||||||
|
channel = 2;
|
||||||
if (configuration.hasSensorPMS2) {
|
if (configuration.hasSensorPMS2) {
|
||||||
ag->pms5003t_2.updateFailCount();
|
if (ag->pms5003t_2.connected()) {
|
||||||
Serial.printf("[2] PMS read failed %d times\r\n", ag->pms5003t_2.getFailCount());
|
measurements.update(Measurements::PM01, ag->pms5003t_2.getPm01Ae(), channel);
|
||||||
|
measurements.update(Measurements::PM25, ag->pms5003t_2.getPm25Ae(), channel);
|
||||||
|
measurements.update(Measurements::PM10, ag->pms5003t_2.getPm10Ae(), channel);
|
||||||
|
measurements.update(Measurements::PM01_SP, ag->pms5003t_2.getPm01Sp(), channel);
|
||||||
|
measurements.update(Measurements::PM25_SP, ag->pms5003t_2.getPm25Sp(), channel);
|
||||||
|
measurements.update(Measurements::PM10_SP, ag->pms5003t_2.getPm10Sp(), channel);
|
||||||
|
measurements.update(Measurements::PM03_PC, ag->pms5003t_2.getPm03ParticleCount(), channel);
|
||||||
|
measurements.update(Measurements::PM05_PC, ag->pms5003t_2.getPm05ParticleCount(), channel);
|
||||||
|
measurements.update(Measurements::PM01_PC, ag->pms5003t_2.getPm01ParticleCount(), channel);
|
||||||
|
measurements.update(Measurements::PM25_PC, ag->pms5003t_2.getPm25ParticleCount(), channel);
|
||||||
|
measurements.update(Measurements::Temperature, ag->pms5003t_2.getTemperature(), channel);
|
||||||
|
measurements.update(Measurements::Humidity, ag->pms5003t_2.getRelativeHumidity(), channel);
|
||||||
|
|
||||||
if (ag->pms5003t_2.getFailCount() >= PMS_FAIL_COUNT_SET_INVALID) {
|
// flag that new valid PMS value exists
|
||||||
measurements.pm01_2 = utils::getInvalidPmValue();
|
newPMS2Value = true;
|
||||||
measurements.pm25_2 = utils::getInvalidPmValue();
|
|
||||||
measurements.pm10_2 = utils::getInvalidPmValue();
|
|
||||||
measurements.pm03PCount_2 = utils::getInvalidPmValue();
|
|
||||||
measurements.temp_2 = utils::getInvalidTemperature();
|
|
||||||
measurements.hum_2 = utils::getInvalidHumidity();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ag->pms5003t_2.getFailCount() >= ag->pms5003t_2.getFailCountMax()) {
|
|
||||||
restart = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (configuration.hasSensorPMS1 && configuration.hasSensorPMS2 &&
|
|
||||||
pmsResult_1 && pmsResult_2) {
|
|
||||||
/** Get total of PMS1*/
|
|
||||||
measurements.pm1Value01 = measurements.pm1Value01 + measurements.pm01_1;
|
|
||||||
measurements.pm1Value25 = measurements.pm1Value25 + measurements.pm25_1;
|
|
||||||
measurements.pm1Value10 = measurements.pm1Value10 + measurements.pm10_1;
|
|
||||||
measurements.pm1PCount =
|
|
||||||
measurements.pm1PCount + measurements.pm03PCount_1;
|
|
||||||
measurements.pm1temp = measurements.pm1temp + measurements.temp_1;
|
|
||||||
measurements.pm1hum = measurements.pm1hum + measurements.hum_1;
|
|
||||||
|
|
||||||
/** Get total of PMS2 */
|
|
||||||
measurements.pm2Value01 = measurements.pm2Value01 + measurements.pm01_2;
|
|
||||||
measurements.pm2Value25 = measurements.pm2Value25 + measurements.pm25_2;
|
|
||||||
measurements.pm2Value10 = measurements.pm2Value10 + measurements.pm10_2;
|
|
||||||
measurements.pm2PCount =
|
|
||||||
measurements.pm2PCount + measurements.pm03PCount_2;
|
|
||||||
measurements.pm2temp = measurements.pm2temp + measurements.temp_2;
|
|
||||||
measurements.pm2hum = measurements.pm2hum + measurements.hum_2;
|
|
||||||
|
|
||||||
measurements.countPosition++;
|
|
||||||
|
|
||||||
/** Get average */
|
|
||||||
if (measurements.countPosition == measurements.targetCount) {
|
|
||||||
measurements.pm01_1 =
|
|
||||||
measurements.pm1Value01 / measurements.targetCount;
|
|
||||||
measurements.pm25_1 =
|
|
||||||
measurements.pm1Value25 / measurements.targetCount;
|
|
||||||
measurements.pm10_1 =
|
|
||||||
measurements.pm1Value10 / measurements.targetCount;
|
|
||||||
measurements.pm03PCount_1 =
|
|
||||||
measurements.pm1PCount / measurements.targetCount;
|
|
||||||
measurements.temp_1 = measurements.pm1temp / measurements.targetCount;
|
|
||||||
measurements.hum_1 = measurements.pm1hum / measurements.targetCount;
|
|
||||||
|
|
||||||
measurements.pm01_2 =
|
|
||||||
measurements.pm2Value01 / measurements.targetCount;
|
|
||||||
measurements.pm25_2 =
|
|
||||||
measurements.pm2Value25 / measurements.targetCount;
|
|
||||||
measurements.pm10_2 =
|
|
||||||
measurements.pm2Value10 / measurements.targetCount;
|
|
||||||
measurements.pm03PCount_2 =
|
|
||||||
measurements.pm2PCount / measurements.targetCount;
|
|
||||||
measurements.temp_2 = measurements.pm2temp / measurements.targetCount;
|
|
||||||
measurements.hum_2 = measurements.pm2hum / measurements.targetCount;
|
|
||||||
|
|
||||||
measurements.countPosition = 0;
|
|
||||||
|
|
||||||
measurements.pm1Value01 = 0;
|
|
||||||
measurements.pm1Value25 = 0;
|
|
||||||
measurements.pm1Value10 = 0;
|
|
||||||
measurements.pm1PCount = 0;
|
|
||||||
measurements.pm1temp = 0;
|
|
||||||
measurements.pm1hum = 0;
|
|
||||||
measurements.pm2Value01 = 0;
|
|
||||||
measurements.pm2Value25 = 0;
|
|
||||||
measurements.pm2Value10 = 0;
|
|
||||||
measurements.pm2PCount = 0;
|
|
||||||
measurements.pm2temp = 0;
|
|
||||||
measurements.pm2hum = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (pmsResult_1 && pmsResult_2) {
|
|
||||||
measurements.Temperature =
|
|
||||||
(measurements.temp_1 + measurements.temp_2) / 2;
|
|
||||||
measurements.Humidity = (measurements.hum_1 + measurements.hum_2) / 2;
|
|
||||||
} else {
|
} else {
|
||||||
if (pmsResult_1) {
|
// PMS channel 2 now is not connected, update using invalid value
|
||||||
measurements.Temperature = measurements.temp_1;
|
measurements.update(Measurements::PM01, utils::getInvalidPmValue(), channel);
|
||||||
measurements.Humidity = measurements.hum_1;
|
measurements.update(Measurements::PM25, utils::getInvalidPmValue(), channel);
|
||||||
}
|
measurements.update(Measurements::PM10, utils::getInvalidPmValue(), channel);
|
||||||
if (pmsResult_2) {
|
measurements.update(Measurements::PM01_SP, utils::getInvalidPmValue(), channel);
|
||||||
measurements.Temperature = measurements.temp_2;
|
measurements.update(Measurements::PM25_SP, utils::getInvalidPmValue(), channel);
|
||||||
measurements.Humidity = measurements.hum_2;
|
measurements.update(Measurements::PM10_SP, utils::getInvalidPmValue(), channel);
|
||||||
|
measurements.update(Measurements::PM03_PC, utils::getInvalidPmValue(), channel);
|
||||||
|
measurements.update(Measurements::PM05_PC, utils::getInvalidPmValue(), channel);
|
||||||
|
measurements.update(Measurements::PM01_PC, utils::getInvalidPmValue(), channel);
|
||||||
|
measurements.update(Measurements::PM25_PC, utils::getInvalidPmValue(), channel);
|
||||||
|
measurements.update(Measurements::Temperature, utils::getInvalidTemperature(), channel);
|
||||||
|
measurements.update(Measurements::Humidity, utils::getInvalidHumidity(), channel);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (configuration.hasSensorSGP) {
|
if (configuration.hasSensorSGP) {
|
||||||
float temp;
|
float temp, hum;
|
||||||
float hum;
|
if (newPMS1Value && newPMS2Value) {
|
||||||
if (pmsResult_1 && pmsResult_2) {
|
// Both PMS has new valid value
|
||||||
temp = (measurements.temp_1 + measurements.temp_2) / 2.0f;
|
temp = (measurements.getFloat(Measurements::Temperature, 1) +
|
||||||
hum = (measurements.hum_1 + measurements.hum_2) / 2.0f;
|
measurements.getFloat(Measurements::Temperature, 2)) /
|
||||||
|
2.0f;
|
||||||
|
hum = (measurements.getFloat(Measurements::Humidity, 1) +
|
||||||
|
measurements.getFloat(Measurements::Humidity, 2)) /
|
||||||
|
2.0f;
|
||||||
|
} else if (newPMS1Value) {
|
||||||
|
// Only PMS1 has new valid value
|
||||||
|
temp = measurements.getFloat(Measurements::Temperature, 1);
|
||||||
|
hum = measurements.getFloat(Measurements::Humidity, 1);
|
||||||
} else {
|
} else {
|
||||||
if (pmsResult_1) {
|
// Only PMS2 has new valid value
|
||||||
temp = measurements.temp_1;
|
temp = measurements.getFloat(Measurements::Temperature, 2);
|
||||||
hum = measurements.hum_1;
|
hum = measurements.getFloat(Measurements::Humidity, 2);
|
||||||
}
|
|
||||||
if (pmsResult_2) {
|
|
||||||
temp = measurements.temp_2;
|
|
||||||
hum = measurements.hum_2;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Update compensation temperature and humidity for SGP41
|
||||||
ag->sgp41.setCompensationTemperatureHumidity(temp, hum);
|
ag->sgp41.setCompensationTemperatureHumidity(temp, hum);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (restart) {
|
|
||||||
Serial.printf("PMS failure count reach to max set %d, restarting...", ag->pms5003.getFailCountMax());
|
|
||||||
ESP.restart();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void sendDataToServer(void) {
|
static void sendDataToServer(void) {
|
||||||
|
/** Increment bootcount when send measurements data is scheduled */
|
||||||
|
measurements.bootCount++;
|
||||||
|
|
||||||
/** Ignore send data to server if postToAirGradient disabled */
|
/** Ignore send data to server if postToAirGradient disabled */
|
||||||
if (configuration.isPostDataToAirGradient() == false || configuration.isOfflineMode()) {
|
if (configuration.isPostDataToAirGradient() == false || configuration.isOfflineMode()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
String syncData = measurements.toString(false, fwMode, wifiConnector.RSSI(),
|
String syncData = measurements.toString(false, fwMode, wifiConnector.RSSI(), *ag, configuration);
|
||||||
ag, &configuration);
|
|
||||||
if (apiClient.postToServer(syncData)) {
|
if (apiClient.postToServer(syncData)) {
|
||||||
Serial.println();
|
Serial.println();
|
||||||
Serial.println(
|
Serial.println(
|
||||||
"Online mode and isPostToAirGradient = true: watchdog reset");
|
"Online mode and isPostToAirGradient = true: watchdog reset");
|
||||||
Serial.println();
|
Serial.println();
|
||||||
}
|
}
|
||||||
measurements.bootCount++;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void tempHumUpdate(void) {
|
static void tempHumUpdate(void) {
|
||||||
delay(100);
|
delay(100);
|
||||||
if (ag->sht.measure()) {
|
if (ag->sht.measure()) {
|
||||||
measurements.Temperature = ag->sht.getTemperature();
|
float temp = ag->sht.getTemperature();
|
||||||
measurements.Humidity = ag->sht.getRelativeHumidity();
|
float rhum = ag->sht.getRelativeHumidity();
|
||||||
|
|
||||||
Serial.printf("Temperature in C: %0.2f\r\n", measurements.Temperature);
|
measurements.update(Measurements::Temperature, temp);
|
||||||
Serial.printf("Relative Humidity: %d\r\n", measurements.Humidity);
|
measurements.update(Measurements::Humidity, rhum);
|
||||||
Serial.printf("Temperature compensated in C: %0.2f\r\n",
|
|
||||||
measurements.Temperature);
|
|
||||||
Serial.printf("Relative Humidity compensated: %d\r\n",
|
|
||||||
measurements.Humidity);
|
|
||||||
|
|
||||||
// Update compensation temperature and humidity for SGP41
|
// Update compensation temperature and humidity for SGP41
|
||||||
if (configuration.hasSensorSGP) {
|
if (configuration.hasSensorSGP) {
|
||||||
ag->sgp41.setCompensationTemperatureHumidity(measurements.Temperature,
|
ag->sgp41.setCompensationTemperatureHumidity(temp, rhum);
|
||||||
measurements.Humidity);
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
measurements.Temperature = utils::getInvalidTemperature();
|
measurements.update(Measurements::Temperature, utils::getInvalidTemperature());
|
||||||
measurements.Humidity = utils::getInvalidHumidity();
|
measurements.update(Measurements::Humidity, utils::getInvalidHumidity());
|
||||||
Serial.println("SHT read failed");
|
Serial.println("SHT read failed");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Set max period for each measurement type based on sensor update interval*/
|
||||||
|
void setMeasurementMaxPeriod() {
|
||||||
|
int max;
|
||||||
|
|
||||||
|
/// Max period for S8 sensors measurements
|
||||||
|
measurements.maxPeriod(Measurements::CO2, calculateMaxPeriod(SENSOR_CO2_UPDATE_INTERVAL));
|
||||||
|
|
||||||
|
/// Max period for SGP sensors measurements
|
||||||
|
max = calculateMaxPeriod(SENSOR_TVOC_UPDATE_INTERVAL);
|
||||||
|
measurements.maxPeriod(Measurements::TVOC, max);
|
||||||
|
measurements.maxPeriod(Measurements::TVOCRaw, max);
|
||||||
|
measurements.maxPeriod(Measurements::NOx, max);
|
||||||
|
measurements.maxPeriod(Measurements::NOxRaw, max);
|
||||||
|
|
||||||
|
/// Max period for PMS sensors measurements
|
||||||
|
max = calculateMaxPeriod(SENSOR_PM_UPDATE_INTERVAL);
|
||||||
|
measurements.maxPeriod(Measurements::PM25, max);
|
||||||
|
measurements.maxPeriod(Measurements::PM01, max);
|
||||||
|
measurements.maxPeriod(Measurements::PM10, max);
|
||||||
|
measurements.maxPeriod(Measurements::PM25_SP, max);
|
||||||
|
measurements.maxPeriod(Measurements::PM01_SP, max);
|
||||||
|
measurements.maxPeriod(Measurements::PM10_SP, max);
|
||||||
|
measurements.maxPeriod(Measurements::PM03_PC, max);
|
||||||
|
measurements.maxPeriod(Measurements::PM05_PC, max);
|
||||||
|
measurements.maxPeriod(Measurements::PM01_PC, max);
|
||||||
|
measurements.maxPeriod(Measurements::PM25_PC, max);
|
||||||
|
measurements.maxPeriod(Measurements::PM5_PC, max);
|
||||||
|
measurements.maxPeriod(Measurements::PM10_PC, max);
|
||||||
|
|
||||||
|
// Temperature and Humidity
|
||||||
|
if (configuration.hasSensorSHT) {
|
||||||
|
/// Max period for SHT sensors measurements
|
||||||
|
measurements.maxPeriod(Measurements::Temperature,
|
||||||
|
calculateMaxPeriod(SENSOR_TEMP_HUM_UPDATE_INTERVAL));
|
||||||
|
measurements.maxPeriod(Measurements::Humidity,
|
||||||
|
calculateMaxPeriod(SENSOR_TEMP_HUM_UPDATE_INTERVAL));
|
||||||
|
} else {
|
||||||
|
/// Temp and hum data retrieved from PMS5003T sensor
|
||||||
|
measurements.maxPeriod(Measurements::Temperature,
|
||||||
|
calculateMaxPeriod(SENSOR_PM_UPDATE_INTERVAL));
|
||||||
|
measurements.maxPeriod(Measurements::Humidity, calculateMaxPeriod(SENSOR_PM_UPDATE_INTERVAL));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int calculateMaxPeriod(int updateInterval) {
|
||||||
|
// 0.8 is 80% reduced interval for max period
|
||||||
|
return (SERVER_SYNC_INTERVAL - (SERVER_SYNC_INTERVAL * 0.8)) / updateInterval;
|
||||||
|
}
|
||||||
|
|
||||||
|
void offlineStorageUpdate() {
|
||||||
|
if (measurements.saveLocalStorage(*ag, configuration)) {
|
||||||
|
oledDisplay.setText("", "New Measurements", "");
|
||||||
|
} else {
|
||||||
|
oledDisplay.setText("Failed write", "Measurements", "");
|
||||||
|
}
|
||||||
|
delay(1200);
|
||||||
|
}
|
@ -57,62 +57,84 @@ String OpenMetrics::getPayload(void) {
|
|||||||
"gauge", "dbm");
|
"gauge", "dbm");
|
||||||
add_metric_point("", String(wifiConnector.RSSI()));
|
add_metric_point("", String(wifiConnector.RSSI()));
|
||||||
|
|
||||||
if (config.hasSensorS8 && measure.CO2 >= 0) {
|
// Initialize default invalid value for each measurements
|
||||||
add_metric("co2",
|
|
||||||
"Carbon dioxide concentration as measured by the AirGradient S8 "
|
|
||||||
"sensor, in parts per million",
|
|
||||||
"gauge", "ppm");
|
|
||||||
add_metric_point("", String(measure.CO2));
|
|
||||||
}
|
|
||||||
|
|
||||||
float _temp = utils::getInvalidTemperature();
|
float _temp = utils::getInvalidTemperature();
|
||||||
float _hum = utils::getInvalidHumidity();
|
float _hum = utils::getInvalidHumidity();
|
||||||
int pm01 = utils::getInvalidPmValue();
|
int pm01 = utils::getInvalidPmValue();
|
||||||
int pm25 = utils::getInvalidPmValue();
|
int pm25 = utils::getInvalidPmValue();
|
||||||
int pm10 = utils::getInvalidPmValue();
|
int pm10 = utils::getInvalidPmValue();
|
||||||
int pm03PCount = utils::getInvalidPmValue();
|
int pm03PCount = utils::getInvalidPmValue();
|
||||||
|
int co2 = utils::getInvalidCO2();
|
||||||
int atmpCompensated = utils::getInvalidTemperature();
|
int atmpCompensated = utils::getInvalidTemperature();
|
||||||
int ahumCompensated = utils::getInvalidHumidity();
|
int ahumCompensated = utils::getInvalidHumidity();
|
||||||
|
int tvoc = utils::getInvalidVOC();
|
||||||
|
int tvocRaw = utils::getInvalidVOC();
|
||||||
|
int nox = utils::getInvalidNOx();
|
||||||
|
int noxRaw = utils::getInvalidNOx();
|
||||||
|
|
||||||
|
// Get values
|
||||||
if (config.hasSensorPMS1 && config.hasSensorPMS2) {
|
if (config.hasSensorPMS1 && config.hasSensorPMS2) {
|
||||||
_temp = (measure.temp_1 + measure.temp_2) / 2.0f;
|
_temp = (measure.getFloat(Measurements::Temperature, 1) +
|
||||||
_hum = (measure.hum_1 + measure.hum_2) / 2.0f;
|
measure.getFloat(Measurements::Temperature, 2)) /
|
||||||
pm01 = (measure.pm01_1 + measure.pm01_2) / 2;
|
2.0f;
|
||||||
pm25 = (measure.pm25_1 + measure.pm25_2) / 2;
|
_hum = (measure.getFloat(Measurements::Humidity, 1) +
|
||||||
pm10 = (measure.pm10_1 + measure.pm10_2) / 2;
|
measure.getFloat(Measurements::Humidity, 2)) /
|
||||||
pm03PCount = (measure.pm03PCount_1 + measure.pm03PCount_2) / 2;
|
2.0f;
|
||||||
|
pm01 = (measure.get(Measurements::PM01, 1) + measure.get(Measurements::PM01, 2)) / 2.0f;
|
||||||
|
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;
|
||||||
|
pm03PCount =
|
||||||
|
(measure.get(Measurements::PM03_PC, 1) + measure.get(Measurements::PM03_PC, 2)) / 2.0f;
|
||||||
} else {
|
} else {
|
||||||
if (ag->isOne()) {
|
if (ag->isOne()) {
|
||||||
if (config.hasSensorSHT) {
|
if (config.hasSensorSHT) {
|
||||||
_temp = measure.Temperature;
|
_temp = measure.getFloat(Measurements::Temperature);
|
||||||
_hum = measure.Humidity;
|
_hum = measure.getFloat(Measurements::Humidity);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (config.hasSensorPMS1) {
|
if (config.hasSensorPMS1) {
|
||||||
pm01 = measure.pm01_1;
|
pm01 = measure.get(Measurements::PM01);
|
||||||
pm25 = measure.pm25_1;
|
float correctedPm = measure.getCorrectedPM25(*ag, config, false, 1);
|
||||||
pm10 = measure.pm10_1;
|
pm25 = round(correctedPm);
|
||||||
pm03PCount = measure.pm03PCount_1;
|
pm10 = measure.get(Measurements::PM10);
|
||||||
|
pm03PCount = measure.get(Measurements::PM03_PC);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (config.hasSensorPMS1) {
|
if (config.hasSensorPMS1) {
|
||||||
_temp = measure.temp_1;
|
_temp = measure.getFloat(Measurements::Temperature, 1);
|
||||||
_hum = measure.hum_1;
|
_hum = measure.getFloat(Measurements::Humidity, 1);
|
||||||
pm01 = measure.pm01_1;
|
pm01 = measure.get(Measurements::PM01, 1);
|
||||||
pm25 = measure.pm25_1;
|
float correctedPm = measure.getCorrectedPM25(*ag, config, false, 1);
|
||||||
pm10 = measure.pm10_1;
|
pm25 = round(correctedPm);
|
||||||
pm03PCount = measure.pm03PCount_1;
|
pm10 = measure.get(Measurements::PM10, 1);
|
||||||
|
pm03PCount = measure.get(Measurements::PM03_PC, 1);
|
||||||
}
|
}
|
||||||
if (config.hasSensorPMS2) {
|
if (config.hasSensorPMS2) {
|
||||||
_temp = measure.temp_2;
|
_temp = measure.getFloat(Measurements::Temperature, 2);
|
||||||
_hum = measure.hum_2;
|
_hum = measure.getFloat(Measurements::Humidity, 2);
|
||||||
pm01 = measure.pm01_2;
|
pm01 = measure.get(Measurements::PM01, 2);
|
||||||
pm25 = measure.pm25_2;
|
float correctedPm = measure.getCorrectedPM25(*ag, config, false, 2);
|
||||||
pm10 = measure.pm10_2;
|
pm25 = round(correctedPm);
|
||||||
pm03PCount = measure.pm03PCount_2;
|
pm10 = measure.get(Measurements::PM10, 2);
|
||||||
|
pm03PCount = measure.get(Measurements::PM03_PC, 2);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (config.hasSensorSGP) {
|
||||||
|
tvoc = measure.get(Measurements::TVOC);
|
||||||
|
tvocRaw = measure.get(Measurements::TVOCRaw);
|
||||||
|
nox = measure.get(Measurements::NOx);
|
||||||
|
noxRaw = measure.get(Measurements::NOxRaw);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config.hasSensorS8) {
|
||||||
|
co2 = measure.get(Measurements::CO2);
|
||||||
|
}
|
||||||
|
|
||||||
/** Get temperature and humidity compensated */
|
/** Get temperature and humidity compensated */
|
||||||
if (ag->isOne()) {
|
if (ag->isOne()) {
|
||||||
atmpCompensated = _temp;
|
atmpCompensated = _temp;
|
||||||
@ -122,6 +144,7 @@ String OpenMetrics::getPayload(void) {
|
|||||||
ahumCompensated = ag->pms5003t_1.compensateHum(_hum);
|
ahumCompensated = ag->pms5003t_1.compensateHum(_hum);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add measurements that valid to the metrics
|
||||||
if (config.hasSensorPMS1 || config.hasSensorPMS2) {
|
if (config.hasSensorPMS1 || config.hasSensorPMS2) {
|
||||||
if (utils::isValidPm(pm01)) {
|
if (utils::isValidPm(pm01)) {
|
||||||
add_metric("pm1",
|
add_metric("pm1",
|
||||||
@ -154,36 +177,44 @@ String OpenMetrics::getPayload(void) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (config.hasSensorSGP) {
|
if (config.hasSensorSGP) {
|
||||||
if (utils::isValidVOC(measure.TVOC)) {
|
if (utils::isValidVOC(tvoc)) {
|
||||||
add_metric("tvoc_index",
|
add_metric("tvoc_index",
|
||||||
"The processed Total Volatile Organic Compounds (TVOC) index "
|
"The processed Total Volatile Organic Compounds (TVOC) index "
|
||||||
"as measured by the AirGradient SGP sensor",
|
"as measured by the AirGradient SGP sensor",
|
||||||
"gauge");
|
"gauge");
|
||||||
add_metric_point("", String(measure.TVOC));
|
add_metric_point("", String(tvoc));
|
||||||
}
|
}
|
||||||
if (utils::isValidVOC(measure.TVOCRaw)) {
|
if (utils::isValidVOC(tvocRaw)) {
|
||||||
add_metric("tvoc_raw",
|
add_metric("tvoc_raw",
|
||||||
"The raw input value to the Total Volatile Organic Compounds "
|
"The raw input value to the Total Volatile Organic Compounds "
|
||||||
"(TVOC) index as measured by the AirGradient SGP sensor",
|
"(TVOC) index as measured by the AirGradient SGP sensor",
|
||||||
"gauge");
|
"gauge");
|
||||||
add_metric_point("", String(measure.TVOCRaw));
|
add_metric_point("", String(tvocRaw));
|
||||||
}
|
}
|
||||||
if (utils::isValidNOx(measure.NOx)) {
|
if (utils::isValidNOx(nox)) {
|
||||||
add_metric("nox_index",
|
add_metric("nox_index",
|
||||||
"The processed Nitrous Oxide (NOx) index as measured by the "
|
"The processed Nitrous Oxide (NOx) index as measured by the "
|
||||||
"AirGradient SGP sensor",
|
"AirGradient SGP sensor",
|
||||||
"gauge");
|
"gauge");
|
||||||
add_metric_point("", String(measure.NOx));
|
add_metric_point("", String(nox));
|
||||||
}
|
}
|
||||||
if (utils::isValidNOx(measure.NOxRaw)) {
|
if (utils::isValidNOx(noxRaw)) {
|
||||||
add_metric("nox_raw",
|
add_metric("nox_raw",
|
||||||
"The raw input value to the Nitrous Oxide (NOx) index as "
|
"The raw input value to the Nitrous Oxide (NOx) index as "
|
||||||
"measured by the AirGradient SGP sensor",
|
"measured by the AirGradient SGP sensor",
|
||||||
"gauge");
|
"gauge");
|
||||||
add_metric_point("", String(measure.NOxRaw));
|
add_metric_point("", String(noxRaw));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (utils::isValidCO2(co2)) {
|
||||||
|
add_metric("co2",
|
||||||
|
"Carbon dioxide concentration as measured by the AirGradient S8 "
|
||||||
|
"sensor, in parts per million",
|
||||||
|
"gauge", "ppm");
|
||||||
|
add_metric_point("", String(co2));
|
||||||
|
}
|
||||||
|
|
||||||
if (utils::isValidTemperature(_temp)) {
|
if (utils::isValidTemperature(_temp)) {
|
||||||
add_metric("temperature",
|
add_metric("temperature",
|
||||||
"The ambient temperature as measured by the AirGradient SHT / PMS "
|
"The ambient temperature as measured by the AirGradient SHT / PMS "
|
||||||
@ -192,23 +223,19 @@ String OpenMetrics::getPayload(void) {
|
|||||||
add_metric_point("", String(_temp));
|
add_metric_point("", String(_temp));
|
||||||
}
|
}
|
||||||
if (utils::isValidTemperature(atmpCompensated)) {
|
if (utils::isValidTemperature(atmpCompensated)) {
|
||||||
add_metric(
|
add_metric("temperature_compensated",
|
||||||
"temperature_compensated",
|
|
||||||
"The compensated ambient temperature as measured by the AirGradient SHT / PMS "
|
"The compensated ambient temperature as measured by the AirGradient SHT / PMS "
|
||||||
"sensor, in degrees Celsius",
|
"sensor, in degrees Celsius",
|
||||||
"gauge", "celsius");
|
"gauge", "celsius");
|
||||||
add_metric_point("", String(atmpCompensated));
|
add_metric_point("", String(atmpCompensated));
|
||||||
}
|
}
|
||||||
if (utils::isValidHumidity(_hum)) {
|
if (utils::isValidHumidity(_hum)) {
|
||||||
add_metric(
|
add_metric("humidity", "The relative humidity as measured by the AirGradient SHT sensor",
|
||||||
"humidity",
|
|
||||||
"The relative humidity as measured by the AirGradient SHT sensor",
|
|
||||||
"gauge", "percent");
|
"gauge", "percent");
|
||||||
add_metric_point("", String(_hum));
|
add_metric_point("", String(_hum));
|
||||||
}
|
}
|
||||||
if (utils::isValidHumidity(ahumCompensated)) {
|
if (utils::isValidHumidity(ahumCompensated)) {
|
||||||
add_metric(
|
add_metric("humidity_compensated",
|
||||||
"humidity_compensated",
|
|
||||||
"The compensated relative humidity as measured by the AirGradient SHT / PMS sensor",
|
"The compensated relative humidity as measured by the AirGradient SHT / PMS sensor",
|
||||||
"gauge", "percent");
|
"gauge", "percent");
|
||||||
add_metric_point("", String(ahumCompensated));
|
add_metric_point("", String(ahumCompensated));
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
name=AirGradient Air Quality Sensor
|
name=AirGradient Air Quality Sensor
|
||||||
version=3.1.9
|
version=3.1.13
|
||||||
author=AirGradient <support@airgradient.com>
|
author=AirGradient <support@airgradient.com>
|
||||||
maintainer=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.
|
sentence=ESP32-C3 / ESP8266 library for air quality monitor measuring PM, CO2, Temperature, TVOC and Humidity with OLED display.
|
||||||
|
@ -130,7 +130,7 @@ bool AgApiClient::postToServer(String data) {
|
|||||||
client.end();
|
client.end();
|
||||||
|
|
||||||
logInfo(String("POST: ") + uri);
|
logInfo(String("POST: ") + uri);
|
||||||
logInfo(String("DATA: ") + data);
|
// logInfo(String("DATA: ") + data);
|
||||||
logInfo(String("Return code: ") + String(retCode));
|
logInfo(String("Return code: ") + String(retCode));
|
||||||
|
|
||||||
if ((retCode == 200) || (retCode == 429)) {
|
if ((retCode == 200) || (retCode == 429)) {
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
#include "AgConfigure.h"
|
#include "AgConfigure.h"
|
||||||
#include "Libraries/Arduino_JSON/src/Arduino_JSON.h"
|
|
||||||
#if ESP32
|
#if ESP32
|
||||||
#include "FS.h"
|
#include "FS.h"
|
||||||
#include "SPIFFS.h"
|
#include "SPIFFS.h"
|
||||||
@ -22,6 +21,18 @@ const char *LED_BAR_MODE_NAMES[] = {
|
|||||||
[LedBarModeCO2] = "co2",
|
[LedBarModeCO2] = "co2",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const char *PM_CORRECTION_ALGORITHM_NAMES[] = {
|
||||||
|
[Unknown] = "-", // This is only to pass "non-trivial designated initializers" error
|
||||||
|
[None] = "none",
|
||||||
|
[EPA_2021] = "epa_2021",
|
||||||
|
[SLR_PMS5003_20220802] = "slr_PMS5003_20220802",
|
||||||
|
[SLR_PMS5003_20220803] = "slr_PMS5003_20220803",
|
||||||
|
[SLR_PMS5003_20220824] = "slr_PMS5003_20220824",
|
||||||
|
[SLR_PMS5003_20231030] = "slr_PMS5003_20231030",
|
||||||
|
[SLR_PMS5003_20231218] = "slr_PMS5003_20231218",
|
||||||
|
[SLR_PMS5003_20240104] = "slr_PMS5003_20240104",
|
||||||
|
};
|
||||||
|
|
||||||
#define JSON_PROP_NAME(name) jprop_##name
|
#define JSON_PROP_NAME(name) jprop_##name
|
||||||
#define JSON_PROP_DEF(name) const char *JSON_PROP_NAME(name) = #name
|
#define JSON_PROP_DEF(name) const char *JSON_PROP_NAME(name) = #name
|
||||||
|
|
||||||
@ -42,6 +53,7 @@ JSON_PROP_DEF(co2CalibrationRequested);
|
|||||||
JSON_PROP_DEF(ledBarTestRequested);
|
JSON_PROP_DEF(ledBarTestRequested);
|
||||||
JSON_PROP_DEF(offlineMode);
|
JSON_PROP_DEF(offlineMode);
|
||||||
JSON_PROP_DEF(monitorDisplayCompensatedValues);
|
JSON_PROP_DEF(monitorDisplayCompensatedValues);
|
||||||
|
JSON_PROP_DEF(corrections);
|
||||||
|
|
||||||
#define jprop_model_default ""
|
#define jprop_model_default ""
|
||||||
#define jprop_country_default "TH"
|
#define jprop_country_default "TH"
|
||||||
@ -87,6 +99,112 @@ String Configuration::getLedBarModeName(LedBarMode mode) {
|
|||||||
return String("unknown");
|
return String("unknown");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
PMCorrectionAlgorithm Configuration::matchPmAlgorithm(String algorithm) {
|
||||||
|
// Loop through all algorithm names in the PM_CORRECTION_ALGORITHM_NAMES array
|
||||||
|
// If the input string matches an algorithm name, return the corresponding enum value
|
||||||
|
// Else return Unknown
|
||||||
|
|
||||||
|
const size_t enumSize = SLR_PMS5003_20240104 + 1; // Get the actual size of the enum
|
||||||
|
PMCorrectionAlgorithm result = PMCorrectionAlgorithm::Unknown;
|
||||||
|
|
||||||
|
// Loop through enum values
|
||||||
|
for (size_t enumVal = 0; enumVal < enumSize; enumVal++) {
|
||||||
|
if (algorithm == PM_CORRECTION_ALGORITHM_NAMES[enumVal]) {
|
||||||
|
result = static_cast<PMCorrectionAlgorithm>(enumVal);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Configuration::updatePmCorrection(JSONVar &json) {
|
||||||
|
if (!json.hasOwnProperty("corrections")) {
|
||||||
|
// TODO: need to response message?
|
||||||
|
Serial.println("corrections not found");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
JSONVar corrections = json["corrections"];
|
||||||
|
if (!corrections.hasOwnProperty("pm02")) {
|
||||||
|
Serial.println("pm02 not found");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
JSONVar pm02 = corrections["pm02"];
|
||||||
|
if (!pm02.hasOwnProperty("correctionAlgorithm")) {
|
||||||
|
Serial.println("correctionAlgorithm not found");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Need to have data type check, with error message response if invalid
|
||||||
|
|
||||||
|
// Check algorithm
|
||||||
|
String algorithm = pm02["correctionAlgorithm"];
|
||||||
|
PMCorrectionAlgorithm algo = matchPmAlgorithm(algorithm);
|
||||||
|
if (algo == Unknown) {
|
||||||
|
logInfo("Unknown algorithm");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
logInfo("Correction algorithm: " + algorithm);
|
||||||
|
|
||||||
|
// If algo is None or EPA_2021, no need to check slr
|
||||||
|
// But first check if pmCorrection different from algo
|
||||||
|
if (algo == None || algo == EPA_2021) {
|
||||||
|
if (pmCorrection.algorithm != algo) {
|
||||||
|
// Deep copy corrections from root to jconfig, so it will be saved later
|
||||||
|
jconfig[jprop_corrections]["pm02"]["correctionAlgorithm"] = algorithm;
|
||||||
|
jconfig[jprop_corrections]["pm02"]["slr"] = JSON.parse("{}"); // Clear slr
|
||||||
|
// Update pmCorrection with new values
|
||||||
|
pmCorrection.algorithm = algo;
|
||||||
|
pmCorrection.changed = true;
|
||||||
|
logInfo("PM2.5 correction updated");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if pm02 has slr object
|
||||||
|
if (!pm02.hasOwnProperty("slr")) {
|
||||||
|
Serial.println("slr not found");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
JSONVar slr = pm02["slr"];
|
||||||
|
|
||||||
|
// Validate required slr properties exist
|
||||||
|
if (!slr.hasOwnProperty("intercept") || !slr.hasOwnProperty("scalingFactor") ||
|
||||||
|
!slr.hasOwnProperty("useEpa2021")) {
|
||||||
|
Serial.println("Missing required slr properties");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// arduino_json doesn't support float type, need to cast to double first
|
||||||
|
float intercept = (float)((double)slr["intercept"]);
|
||||||
|
float scalingFactor = (float)((double)slr["scalingFactor"]);
|
||||||
|
|
||||||
|
// Compare with current pmCorrection
|
||||||
|
if (pmCorrection.algorithm == algo && pmCorrection.intercept == intercept &&
|
||||||
|
pmCorrection.scalingFactor == scalingFactor &&
|
||||||
|
pmCorrection.useEPA == (bool)slr["useEpa2021"]) {
|
||||||
|
return false; // No changes needed
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deep copy corrections from root to jconfig, so it will be saved later
|
||||||
|
jconfig[jprop_corrections] = corrections;
|
||||||
|
|
||||||
|
// Update pmCorrection with new values
|
||||||
|
pmCorrection.algorithm = algo;
|
||||||
|
pmCorrection.intercept = intercept;
|
||||||
|
pmCorrection.scalingFactor = scalingFactor;
|
||||||
|
pmCorrection.useEPA = (bool)slr["useEpa2021"];
|
||||||
|
pmCorrection.changed = true;
|
||||||
|
|
||||||
|
// Correction values were updated
|
||||||
|
logInfo("PM2.5 correction updated");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Save configure to device storage (EEPROM)
|
* @brief Save configure to device storage (EEPROM)
|
||||||
*
|
*
|
||||||
@ -135,7 +253,7 @@ void Configuration::loadConfig(void) {
|
|||||||
}
|
}
|
||||||
file.close();
|
file.close();
|
||||||
} else {
|
} else {
|
||||||
SPIFFS.format();
|
// SPIFFS.format();
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
toConfig(buf);
|
toConfig(buf);
|
||||||
@ -162,7 +280,7 @@ void Configuration::defaultConfig(void) {
|
|||||||
jconfig[jprop_displayBrightness] = jprop_displayBrightness_default;
|
jconfig[jprop_displayBrightness] = jprop_displayBrightness_default;
|
||||||
}
|
}
|
||||||
if (ag->isOne()) {
|
if (ag->isOne()) {
|
||||||
jconfig[jprop_ledBarMode] = jprop_ledBarBrightness_default;
|
jconfig[jprop_ledBarMode] = jprop_ledBarMode_default;
|
||||||
}
|
}
|
||||||
jconfig[jprop_tvocLearningOffset] = jprop_tvocLearningOffset_default;
|
jconfig[jprop_tvocLearningOffset] = jprop_tvocLearningOffset_default;
|
||||||
jconfig[jprop_noxLearningOffset] = jprop_noxLearningOffset_default;
|
jconfig[jprop_noxLearningOffset] = jprop_noxLearningOffset_default;
|
||||||
@ -171,6 +289,13 @@ void Configuration::defaultConfig(void) {
|
|||||||
jconfig[jprop_offlineMode] = jprop_offlineMode_default;
|
jconfig[jprop_offlineMode] = jprop_offlineMode_default;
|
||||||
jconfig[jprop_monitorDisplayCompensatedValues] = jprop_monitorDisplayCompensatedValues_default;
|
jconfig[jprop_monitorDisplayCompensatedValues] = jprop_monitorDisplayCompensatedValues_default;
|
||||||
|
|
||||||
|
// PM2.5 correction
|
||||||
|
pmCorrection.algorithm = None;
|
||||||
|
pmCorrection.changed = false;
|
||||||
|
pmCorrection.intercept = 0;
|
||||||
|
pmCorrection.scalingFactor = 1;
|
||||||
|
pmCorrection.useEPA = false;
|
||||||
|
|
||||||
saveConfig();
|
saveConfig();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -229,16 +354,16 @@ bool Configuration::begin(void) {
|
|||||||
* @return false Failure
|
* @return false Failure
|
||||||
*/
|
*/
|
||||||
bool Configuration::parse(String data, bool isLocal) {
|
bool Configuration::parse(String data, bool isLocal) {
|
||||||
logInfo("Parse configure: " + data);
|
logInfo("Parsing configuration: " + data);
|
||||||
|
|
||||||
JSONVar root = JSON.parse(data);
|
JSONVar root = JSON.parse(data);
|
||||||
failedMessage = "";
|
failedMessage = "";
|
||||||
if (root == undefined) {
|
if (root == undefined || JSONVar::typeof_(root) != "object") {
|
||||||
|
logError("Parse configuration failed, JSON invalid (" + JSONVar::typeof_(root) + ")");
|
||||||
failedMessage = "JSON invalid";
|
failedMessage = "JSON invalid";
|
||||||
logError(failedMessage);
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
logInfo("Parse configure success");
|
logInfo("Parse configuration success");
|
||||||
|
|
||||||
/** Is configuration changed */
|
/** Is configuration changed */
|
||||||
bool changed = false;
|
bool changed = false;
|
||||||
@ -660,20 +785,25 @@ bool Configuration::parse(String data, bool isLocal) {
|
|||||||
if (curVer != newVer) {
|
if (curVer != newVer) {
|
||||||
logInfo("Detected new firmware version: " + newVer);
|
logInfo("Detected new firmware version: " + newVer);
|
||||||
otaNewFirmwareVersion = newVer;
|
otaNewFirmwareVersion = newVer;
|
||||||
udpated = true;
|
updated = true;
|
||||||
} else {
|
} else {
|
||||||
otaNewFirmwareVersion = String("");
|
otaNewFirmwareVersion = String("");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Corrections
|
||||||
|
if (updatePmCorrection(root)) {
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
|
||||||
if (changed) {
|
if (changed) {
|
||||||
udpated = true;
|
updated = true;
|
||||||
saveConfig();
|
saveConfig();
|
||||||
printConfig();
|
printConfig();
|
||||||
} else {
|
} else {
|
||||||
if (ledBarTestRequested || co2CalibrationRequested) {
|
if (ledBarTestRequested || co2CalibrationRequested) {
|
||||||
udpated = true;
|
updated = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
@ -860,8 +990,8 @@ String Configuration::getModel(void) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool Configuration::isUpdated(void) {
|
bool Configuration::isUpdated(void) {
|
||||||
bool updated = this->udpated;
|
bool updated = this->updated;
|
||||||
this->udpated = false;
|
this->updated = false;
|
||||||
return updated;
|
return updated;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1118,6 +1248,15 @@ void Configuration::toConfig(const char *buf) {
|
|||||||
jprop_monitorDisplayCompensatedValues_default;
|
jprop_monitorDisplayCompensatedValues_default;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Set default first before parsing local config
|
||||||
|
pmCorrection.algorithm = PMCorrectionAlgorithm::None;
|
||||||
|
pmCorrection.intercept = 0;
|
||||||
|
pmCorrection.scalingFactor = 0;
|
||||||
|
pmCorrection.useEPA = false;
|
||||||
|
// Load correction from saved config
|
||||||
|
updatePmCorrection(jconfig);
|
||||||
|
|
||||||
if (changed) {
|
if (changed) {
|
||||||
saveConfig();
|
saveConfig();
|
||||||
}
|
}
|
||||||
@ -1216,3 +1355,28 @@ String Configuration::newFirmwareVersion(void) {
|
|||||||
otaNewFirmwareVersion = String("");
|
otaNewFirmwareVersion = String("");
|
||||||
return newFw;
|
return newFw;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool Configuration::isPMCorrectionChanged(void) {
|
||||||
|
bool changed = pmCorrection.changed;
|
||||||
|
pmCorrection.changed = false;
|
||||||
|
return changed;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Check if PM correction is enabled
|
||||||
|
*
|
||||||
|
* @return true if PM correction algorithm is not None, otherwise false
|
||||||
|
*/
|
||||||
|
bool Configuration::isPMCorrectionEnabled(void) {
|
||||||
|
PMCorrection pmCorrection = getPMCorrection();
|
||||||
|
if (pmCorrection.algorithm == PMCorrectionAlgorithm::None ||
|
||||||
|
pmCorrection.algorithm == PMCorrectionAlgorithm::Unknown) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
Configuration::PMCorrection Configuration::getPMCorrection(void) {
|
||||||
|
return pmCorrection;
|
||||||
|
}
|
||||||
|
@ -5,12 +5,22 @@
|
|||||||
#include "Main/PrintLog.h"
|
#include "Main/PrintLog.h"
|
||||||
#include "AirGradient.h"
|
#include "AirGradient.h"
|
||||||
#include <Arduino.h>
|
#include <Arduino.h>
|
||||||
|
#include "Libraries/Arduino_JSON/src/Arduino_JSON.h"
|
||||||
|
|
||||||
class Configuration : public PrintLog {
|
class Configuration : public PrintLog {
|
||||||
|
public:
|
||||||
|
struct PMCorrection {
|
||||||
|
PMCorrectionAlgorithm algorithm;
|
||||||
|
float intercept;
|
||||||
|
float scalingFactor;
|
||||||
|
bool useEPA; // EPA 2021
|
||||||
|
bool changed;
|
||||||
|
};
|
||||||
|
|
||||||
private:
|
private:
|
||||||
bool co2CalibrationRequested;
|
bool co2CalibrationRequested;
|
||||||
bool ledBarTestRequested;
|
bool ledBarTestRequested;
|
||||||
bool udpated;
|
bool updated;
|
||||||
String failedMessage;
|
String failedMessage;
|
||||||
bool _noxLearnOffsetChanged;
|
bool _noxLearnOffsetChanged;
|
||||||
bool _tvocLearningOffsetChanged;
|
bool _tvocLearningOffsetChanged;
|
||||||
@ -19,10 +29,13 @@ private:
|
|||||||
String otaNewFirmwareVersion;
|
String otaNewFirmwareVersion;
|
||||||
bool _offlineMode = false;
|
bool _offlineMode = false;
|
||||||
bool _ledBarModeChanged = false;
|
bool _ledBarModeChanged = false;
|
||||||
|
PMCorrection pmCorrection;
|
||||||
|
|
||||||
AirGradient* ag;
|
AirGradient* ag;
|
||||||
|
|
||||||
String getLedBarModeName(LedBarMode mode);
|
String getLedBarModeName(LedBarMode mode);
|
||||||
|
PMCorrectionAlgorithm matchPmAlgorithm(String algorithm);
|
||||||
|
bool updatePmCorrection(JSONVar &json);
|
||||||
void saveConfig(void);
|
void saveConfig(void);
|
||||||
void loadConfig(void);
|
void loadConfig(void);
|
||||||
void defaultConfig(void);
|
void defaultConfig(void);
|
||||||
@ -83,6 +96,9 @@ public:
|
|||||||
void setOfflineModeWithoutSave(bool offline);
|
void setOfflineModeWithoutSave(bool offline);
|
||||||
bool isLedBarModeChanged(void);
|
bool isLedBarModeChanged(void);
|
||||||
bool isMonitorDisplayCompensatedValues(void);
|
bool isMonitorDisplayCompensatedValues(void);
|
||||||
|
bool isPMCorrectionChanged(void);
|
||||||
|
bool isPMCorrectionEnabled(void);
|
||||||
|
PMCorrection getPMCorrection(void);
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif /** _AG_CONFIG_H_ */
|
#endif /** _AG_CONFIG_H_ */
|
||||||
|
@ -12,12 +12,13 @@
|
|||||||
*/
|
*/
|
||||||
void OledDisplay::showTempHum(bool hasStatus, char *buf, int buf_size) {
|
void OledDisplay::showTempHum(bool hasStatus, char *buf, int buf_size) {
|
||||||
/** Temperature */
|
/** Temperature */
|
||||||
if (utils::isValidTemperature(value.Temperature)) {
|
float temp = value.getAverage(Measurements::Temperature);
|
||||||
|
if (utils::isValidTemperature(temp)) {
|
||||||
float t = 0.0f;
|
float t = 0.0f;
|
||||||
if (config.isTemperatureUnitInF()) {
|
if (config.isTemperatureUnitInF()) {
|
||||||
t = utils::degreeC_To_F(value.Temperature);
|
t = utils::degreeC_To_F(temp);
|
||||||
} else {
|
} else {
|
||||||
t = value.Temperature;
|
t = temp;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (config.isTemperatureUnitInF()) {
|
if (config.isTemperatureUnitInF()) {
|
||||||
@ -43,13 +44,14 @@ void OledDisplay::showTempHum(bool hasStatus, char *buf, int buf_size) {
|
|||||||
DISP()->drawUTF8(1, 10, buf);
|
DISP()->drawUTF8(1, 10, buf);
|
||||||
|
|
||||||
/** Show humidity */
|
/** Show humidity */
|
||||||
if (utils::isValidHumidity(value.Humidity)) {
|
int rhum = round(value.getAverage(Measurements::Humidity));
|
||||||
snprintf(buf, buf_size, "%d%%", value.Humidity);
|
if (utils::isValidHumidity(rhum)) {
|
||||||
|
snprintf(buf, buf_size, "%d%%", rhum);
|
||||||
} else {
|
} else {
|
||||||
snprintf(buf, buf_size, "-%%");
|
snprintf(buf, buf_size, "-%%");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (value.Humidity > 99) {
|
if (rhum > 99.0) {
|
||||||
DISP()->drawStr(97, 10, buf);
|
DISP()->drawStr(97, 10, buf);
|
||||||
} else {
|
} else {
|
||||||
DISP()->drawStr(105, 10, buf);
|
DISP()->drawStr(105, 10, buf);
|
||||||
@ -290,8 +292,9 @@ void OledDisplay::showDashboard(const char *status) {
|
|||||||
DISP()->drawUTF8(1, 27, "CO2");
|
DISP()->drawUTF8(1, 27, "CO2");
|
||||||
|
|
||||||
DISP()->setFont(u8g2_font_t0_22b_tf);
|
DISP()->setFont(u8g2_font_t0_22b_tf);
|
||||||
if (utils::isValidCO2(value.CO2)) {
|
int co2 = round(value.getAverage(Measurements::CO2));
|
||||||
sprintf(strBuf, "%d", value.CO2);
|
if (utils::isValidCO2(co2)) {
|
||||||
|
sprintf(strBuf, "%d", co2);
|
||||||
} else {
|
} else {
|
||||||
sprintf(strBuf, "%s", "-");
|
sprintf(strBuf, "%s", "-");
|
||||||
}
|
}
|
||||||
@ -310,15 +313,11 @@ void OledDisplay::showDashboard(const char *status) {
|
|||||||
DISP()->drawStr(55, 27, "PM2.5");
|
DISP()->drawStr(55, 27, "PM2.5");
|
||||||
|
|
||||||
/** Draw PM2.5 value */
|
/** Draw PM2.5 value */
|
||||||
if (utils::isValidPm(value.pm25_1)) {
|
int pm25 = round(value.getAverage(Measurements::PM25));
|
||||||
int pm25 = value.pm25_1;
|
if (utils::isValidPm(pm25)) {
|
||||||
|
if (config.hasSensorSHT && config.isPMCorrectionEnabled()) {
|
||||||
/** Compensate PM2.5 value. */
|
pm25 = round(value.getCorrectedPM25(*ag, config, true));
|
||||||
if (config.hasSensorSHT && config.isMonitorDisplayCompensatedValues()) {
|
|
||||||
pm25 = ag->pms5003.compensate(pm25, value.Humidity);
|
|
||||||
logInfo("PM2.5 compensate: " + String(pm25));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (config.isPmStandardInUSAQI()) {
|
if (config.isPmStandardInUSAQI()) {
|
||||||
sprintf(strBuf, "%d", ag->pms5003.convertPm25ToUsAqi(pm25));
|
sprintf(strBuf, "%d", ag->pms5003.convertPm25ToUsAqi(pm25));
|
||||||
} else {
|
} else {
|
||||||
@ -343,17 +342,19 @@ void OledDisplay::showDashboard(const char *status) {
|
|||||||
DISP()->drawStr(100, 27, "VOC:");
|
DISP()->drawStr(100, 27, "VOC:");
|
||||||
|
|
||||||
/** Draw tvocIndexvalue */
|
/** Draw tvocIndexvalue */
|
||||||
if (utils::isValidVOC(value.TVOC)) {
|
int tvoc = round(value.getAverage(Measurements::TVOC));
|
||||||
sprintf(strBuf, "%d", value.TVOC);
|
if (utils::isValidVOC(tvoc)) {
|
||||||
|
sprintf(strBuf, "%d", tvoc);
|
||||||
} else {
|
} else {
|
||||||
sprintf(strBuf, "%s", "-");
|
sprintf(strBuf, "%s", "-");
|
||||||
}
|
}
|
||||||
DISP()->drawStr(100, 39, strBuf);
|
DISP()->drawStr(100, 39, strBuf);
|
||||||
|
|
||||||
/** Draw NOx label */
|
/** Draw NOx label */
|
||||||
|
int nox = round(value.getAverage(Measurements::NOx));
|
||||||
DISP()->drawStr(100, 53, "NOx:");
|
DISP()->drawStr(100, 53, "NOx:");
|
||||||
if (utils::isValidNOx(value.NOx)) {
|
if (utils::isValidNOx(nox)) {
|
||||||
sprintf(strBuf, "%d", value.NOx);
|
sprintf(strBuf, "%d", nox);
|
||||||
} else {
|
} else {
|
||||||
sprintf(strBuf, "%s", "-");
|
sprintf(strBuf, "%s", "-");
|
||||||
}
|
}
|
||||||
@ -363,8 +364,9 @@ void OledDisplay::showDashboard(const char *status) {
|
|||||||
ag->display.clear();
|
ag->display.clear();
|
||||||
|
|
||||||
/** Set CO2 */
|
/** Set CO2 */
|
||||||
if (utils::isValidCO2(value.CO2)) {
|
int co2 = round(value.getAverage(Measurements::CO2));
|
||||||
snprintf(strBuf, sizeof(strBuf), "CO2:%d", value.CO2);
|
if (utils::isValidCO2(co2)) {
|
||||||
|
snprintf(strBuf, sizeof(strBuf), "CO2:%d", co2);
|
||||||
} else {
|
} else {
|
||||||
snprintf(strBuf, sizeof(strBuf), "CO2:-");
|
snprintf(strBuf, sizeof(strBuf), "CO2:-");
|
||||||
}
|
}
|
||||||
@ -373,9 +375,9 @@ void OledDisplay::showDashboard(const char *status) {
|
|||||||
ag->display.setText(strBuf);
|
ag->display.setText(strBuf);
|
||||||
|
|
||||||
/** Set PM */
|
/** Set PM */
|
||||||
int pm25 = value.pm25_1;
|
int pm25 = round(value.getAverage(Measurements::PM25));
|
||||||
if (config.hasSensorSHT && config.isMonitorDisplayCompensatedValues()) {
|
if (config.hasSensorSHT && config.isPMCorrectionEnabled()) {
|
||||||
pm25 = (int)ag->pms5003.compensate(pm25, value.Humidity);
|
pm25 = round(value.getCorrectedPM25(*ag, config, true));
|
||||||
}
|
}
|
||||||
|
|
||||||
ag->display.setCursor(0, 12);
|
ag->display.setCursor(0, 12);
|
||||||
@ -387,12 +389,12 @@ void OledDisplay::showDashboard(const char *status) {
|
|||||||
ag->display.setText(strBuf);
|
ag->display.setText(strBuf);
|
||||||
|
|
||||||
/** Set temperature and humidity */
|
/** Set temperature and humidity */
|
||||||
if (utils::isValidTemperature(value.Temperature)) {
|
float temp = value.getAverage(Measurements::Temperature);
|
||||||
|
if (utils::isValidTemperature(temp)) {
|
||||||
if (config.isTemperatureUnitInF()) {
|
if (config.isTemperatureUnitInF()) {
|
||||||
snprintf(strBuf, sizeof(strBuf), "T:%0.1f F",
|
snprintf(strBuf, sizeof(strBuf), "T:%0.1f F", utils::degreeC_To_F(temp));
|
||||||
utils::degreeC_To_F(value.Temperature));
|
|
||||||
} else {
|
} else {
|
||||||
snprintf(strBuf, sizeof(strBuf), "T:%0.f1 C", value.Temperature);
|
snprintf(strBuf, sizeof(strBuf), "T:%0.1f C", temp);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (config.isTemperatureUnitInF()) {
|
if (config.isTemperatureUnitInF()) {
|
||||||
@ -405,8 +407,9 @@ void OledDisplay::showDashboard(const char *status) {
|
|||||||
ag->display.setCursor(0, 24);
|
ag->display.setCursor(0, 24);
|
||||||
ag->display.setText(strBuf);
|
ag->display.setText(strBuf);
|
||||||
|
|
||||||
if (utils::isValidHumidity(value.Humidity)) {
|
int rhum = round(value.getAverage(Measurements::Humidity));
|
||||||
snprintf(strBuf, sizeof(strBuf), "H:%d %%", (int)value.Humidity);
|
if (utils::isValidHumidity(rhum)) {
|
||||||
|
snprintf(strBuf, sizeof(strBuf), "H:%d %%", rhum);
|
||||||
} else {
|
} else {
|
||||||
snprintf(strBuf, sizeof(strBuf), "H:- %%");
|
snprintf(strBuf, sizeof(strBuf), "H:- %%");
|
||||||
}
|
}
|
||||||
|
@ -13,6 +13,7 @@
|
|||||||
#define RGB_COLOR_Y 255, 150, 0 /** Yellow */
|
#define RGB_COLOR_Y 255, 150, 0 /** Yellow */
|
||||||
#define RGB_COLOR_O 255, 40, 0 /** Orange */
|
#define RGB_COLOR_O 255, 40, 0 /** Orange */
|
||||||
#define RGB_COLOR_P 180, 0, 255 /** Purple */
|
#define RGB_COLOR_P 180, 0, 255 /** Purple */
|
||||||
|
#define RGB_COLOR_CLEAR 0, 0, 0 /** No color */
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Animation LED bar with color
|
* @brief Animation LED bar with color
|
||||||
@ -47,47 +48,67 @@ void StateMachine::ledStatusBlinkDelay(uint32_t ms) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Led bar show led color status
|
* @brief Led bar show PM or CO2 led color status
|
||||||
*
|
*
|
||||||
|
* @return true if all led bar are used, false othwerwise
|
||||||
*/
|
*/
|
||||||
void StateMachine::sensorhandleLeds(void) {
|
bool StateMachine::sensorhandleLeds(void) {
|
||||||
|
int totalLedUsed = 0;
|
||||||
switch (config.getLedBarMode()) {
|
switch (config.getLedBarMode()) {
|
||||||
case LedBarMode::LedBarModeCO2:
|
case LedBarMode::LedBarModeCO2:
|
||||||
co2handleLeds();
|
totalLedUsed = co2handleLeds();
|
||||||
break;
|
break;
|
||||||
case LedBarMode::LedBarModePm:
|
case LedBarMode::LedBarModePm:
|
||||||
pm25handleLeds();
|
totalLedUsed = pm25handleLeds();
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
ag->ledBar.clear();
|
ag->ledBar.clear();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (totalLedUsed == ag->ledBar.getNumberOfLeds()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear the rest of unused led
|
||||||
|
int startIndex = totalLedUsed + 1;
|
||||||
|
for (int i = startIndex; i <= ag->ledBar.getNumberOfLeds(); i++) {
|
||||||
|
ag->ledBar.setColor(RGB_COLOR_CLEAR, ag->ledBar.getNumberOfLeds() - i);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Show CO2 LED status
|
* @brief Show CO2 LED status
|
||||||
*
|
*
|
||||||
|
* @return return total number of led that are used on the monitor
|
||||||
*/
|
*/
|
||||||
void StateMachine::co2handleLeds(void) {
|
int StateMachine::co2handleLeds(void) {
|
||||||
int co2Value = value.CO2;
|
int totalUsed = ag->ledBar.getNumberOfLeds();
|
||||||
|
int co2Value = round(value.getAverage(Measurements::CO2));
|
||||||
if (co2Value <= 600) {
|
if (co2Value <= 600) {
|
||||||
/** G; 1 */
|
/** G; 1 */
|
||||||
ag->ledBar.setColor(RGB_COLOR_G, ag->ledBar.getNumberOfLeds() - 1);
|
ag->ledBar.setColor(RGB_COLOR_G, ag->ledBar.getNumberOfLeds() - 1);
|
||||||
|
totalUsed = 1;
|
||||||
} else if (co2Value <= 800) {
|
} else if (co2Value <= 800) {
|
||||||
/** GG; 2 */
|
/** GG; 2 */
|
||||||
ag->ledBar.setColor(RGB_COLOR_G, ag->ledBar.getNumberOfLeds() - 1);
|
ag->ledBar.setColor(RGB_COLOR_G, ag->ledBar.getNumberOfLeds() - 1);
|
||||||
ag->ledBar.setColor(RGB_COLOR_G, ag->ledBar.getNumberOfLeds() - 2);
|
ag->ledBar.setColor(RGB_COLOR_G, ag->ledBar.getNumberOfLeds() - 2);
|
||||||
|
totalUsed = 2;
|
||||||
} else if (co2Value <= 1000) {
|
} else if (co2Value <= 1000) {
|
||||||
/** YYY; 3 */
|
/** YYY; 3 */
|
||||||
ag->ledBar.setColor(RGB_COLOR_Y, ag->ledBar.getNumberOfLeds() - 1);
|
ag->ledBar.setColor(RGB_COLOR_Y, ag->ledBar.getNumberOfLeds() - 1);
|
||||||
ag->ledBar.setColor(RGB_COLOR_Y, ag->ledBar.getNumberOfLeds() - 2);
|
ag->ledBar.setColor(RGB_COLOR_Y, ag->ledBar.getNumberOfLeds() - 2);
|
||||||
ag->ledBar.setColor(RGB_COLOR_Y, ag->ledBar.getNumberOfLeds() - 3);
|
ag->ledBar.setColor(RGB_COLOR_Y, ag->ledBar.getNumberOfLeds() - 3);
|
||||||
|
totalUsed = 3;
|
||||||
} else if (co2Value <= 1250) {
|
} else if (co2Value <= 1250) {
|
||||||
/** OOOO; 4 */
|
/** OOOO; 4 */
|
||||||
ag->ledBar.setColor(RGB_COLOR_O, ag->ledBar.getNumberOfLeds() - 1);
|
ag->ledBar.setColor(RGB_COLOR_O, ag->ledBar.getNumberOfLeds() - 1);
|
||||||
ag->ledBar.setColor(RGB_COLOR_O, ag->ledBar.getNumberOfLeds() - 2);
|
ag->ledBar.setColor(RGB_COLOR_O, ag->ledBar.getNumberOfLeds() - 2);
|
||||||
ag->ledBar.setColor(RGB_COLOR_O, ag->ledBar.getNumberOfLeds() - 3);
|
ag->ledBar.setColor(RGB_COLOR_O, ag->ledBar.getNumberOfLeds() - 3);
|
||||||
ag->ledBar.setColor(RGB_COLOR_O, ag->ledBar.getNumberOfLeds() - 4);
|
ag->ledBar.setColor(RGB_COLOR_O, ag->ledBar.getNumberOfLeds() - 4);
|
||||||
|
totalUsed = 4;
|
||||||
} else if (co2Value <= 1500) {
|
} else if (co2Value <= 1500) {
|
||||||
/** OOOOO; 5 */
|
/** OOOOO; 5 */
|
||||||
ag->ledBar.setColor(RGB_COLOR_O, ag->ledBar.getNumberOfLeds() - 1);
|
ag->ledBar.setColor(RGB_COLOR_O, ag->ledBar.getNumberOfLeds() - 1);
|
||||||
@ -95,6 +116,7 @@ void StateMachine::co2handleLeds(void) {
|
|||||||
ag->ledBar.setColor(RGB_COLOR_O, ag->ledBar.getNumberOfLeds() - 3);
|
ag->ledBar.setColor(RGB_COLOR_O, ag->ledBar.getNumberOfLeds() - 3);
|
||||||
ag->ledBar.setColor(RGB_COLOR_O, ag->ledBar.getNumberOfLeds() - 4);
|
ag->ledBar.setColor(RGB_COLOR_O, ag->ledBar.getNumberOfLeds() - 4);
|
||||||
ag->ledBar.setColor(RGB_COLOR_O, ag->ledBar.getNumberOfLeds() - 5);
|
ag->ledBar.setColor(RGB_COLOR_O, ag->ledBar.getNumberOfLeds() - 5);
|
||||||
|
totalUsed = 5;
|
||||||
} else if (co2Value <= 1750) {
|
} else if (co2Value <= 1750) {
|
||||||
/** RRRRRR; 6 */
|
/** RRRRRR; 6 */
|
||||||
ag->ledBar.setColor(RGB_COLOR_R, ag->ledBar.getNumberOfLeds() - 1);
|
ag->ledBar.setColor(RGB_COLOR_R, ag->ledBar.getNumberOfLeds() - 1);
|
||||||
@ -103,6 +125,7 @@ void StateMachine::co2handleLeds(void) {
|
|||||||
ag->ledBar.setColor(RGB_COLOR_R, ag->ledBar.getNumberOfLeds() - 4);
|
ag->ledBar.setColor(RGB_COLOR_R, ag->ledBar.getNumberOfLeds() - 4);
|
||||||
ag->ledBar.setColor(RGB_COLOR_R, ag->ledBar.getNumberOfLeds() - 5);
|
ag->ledBar.setColor(RGB_COLOR_R, ag->ledBar.getNumberOfLeds() - 5);
|
||||||
ag->ledBar.setColor(RGB_COLOR_R, ag->ledBar.getNumberOfLeds() - 6);
|
ag->ledBar.setColor(RGB_COLOR_R, ag->ledBar.getNumberOfLeds() - 6);
|
||||||
|
totalUsed = 6;
|
||||||
} else if (co2Value <= 2000) {
|
} else if (co2Value <= 2000) {
|
||||||
/** RRRRRRR; 7 */
|
/** RRRRRRR; 7 */
|
||||||
ag->ledBar.setColor(RGB_COLOR_R, ag->ledBar.getNumberOfLeds() - 1);
|
ag->ledBar.setColor(RGB_COLOR_R, ag->ledBar.getNumberOfLeds() - 1);
|
||||||
@ -112,6 +135,7 @@ void StateMachine::co2handleLeds(void) {
|
|||||||
ag->ledBar.setColor(RGB_COLOR_R, ag->ledBar.getNumberOfLeds() - 5);
|
ag->ledBar.setColor(RGB_COLOR_R, ag->ledBar.getNumberOfLeds() - 5);
|
||||||
ag->ledBar.setColor(RGB_COLOR_R, ag->ledBar.getNumberOfLeds() - 6);
|
ag->ledBar.setColor(RGB_COLOR_R, ag->ledBar.getNumberOfLeds() - 6);
|
||||||
ag->ledBar.setColor(RGB_COLOR_R, ag->ledBar.getNumberOfLeds() - 7);
|
ag->ledBar.setColor(RGB_COLOR_R, ag->ledBar.getNumberOfLeds() - 7);
|
||||||
|
totalUsed = 7;
|
||||||
} else if (co2Value <= 3000) {
|
} else if (co2Value <= 3000) {
|
||||||
/** PPPPPPPP; 8 */
|
/** PPPPPPPP; 8 */
|
||||||
ag->ledBar.setColor(RGB_COLOR_P, ag->ledBar.getNumberOfLeds() - 1);
|
ag->ledBar.setColor(RGB_COLOR_P, ag->ledBar.getNumberOfLeds() - 1);
|
||||||
@ -122,6 +146,7 @@ void StateMachine::co2handleLeds(void) {
|
|||||||
ag->ledBar.setColor(RGB_COLOR_P, ag->ledBar.getNumberOfLeds() - 6);
|
ag->ledBar.setColor(RGB_COLOR_P, ag->ledBar.getNumberOfLeds() - 6);
|
||||||
ag->ledBar.setColor(RGB_COLOR_P, ag->ledBar.getNumberOfLeds() - 7);
|
ag->ledBar.setColor(RGB_COLOR_P, ag->ledBar.getNumberOfLeds() - 7);
|
||||||
ag->ledBar.setColor(RGB_COLOR_P, ag->ledBar.getNumberOfLeds() - 8);
|
ag->ledBar.setColor(RGB_COLOR_P, ag->ledBar.getNumberOfLeds() - 8);
|
||||||
|
totalUsed = 8;
|
||||||
} else { /** > 3000 */
|
} else { /** > 3000 */
|
||||||
/* PRPRPRPRP; 9 */
|
/* PRPRPRPRP; 9 */
|
||||||
ag->ledBar.setColor(RGB_COLOR_P, ag->ledBar.getNumberOfLeds() - 1);
|
ag->ledBar.setColor(RGB_COLOR_P, ag->ledBar.getNumberOfLeds() - 1);
|
||||||
@ -133,45 +158,56 @@ void StateMachine::co2handleLeds(void) {
|
|||||||
ag->ledBar.setColor(RGB_COLOR_P, ag->ledBar.getNumberOfLeds() - 7);
|
ag->ledBar.setColor(RGB_COLOR_P, ag->ledBar.getNumberOfLeds() - 7);
|
||||||
ag->ledBar.setColor(RGB_COLOR_R, ag->ledBar.getNumberOfLeds() - 8);
|
ag->ledBar.setColor(RGB_COLOR_R, ag->ledBar.getNumberOfLeds() - 8);
|
||||||
ag->ledBar.setColor(RGB_COLOR_P, ag->ledBar.getNumberOfLeds() - 9);
|
ag->ledBar.setColor(RGB_COLOR_P, ag->ledBar.getNumberOfLeds() - 9);
|
||||||
|
totalUsed = 9;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return totalUsed;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Show PM2.5 LED status
|
* @brief Show PM2.5 LED status
|
||||||
*
|
*
|
||||||
|
* @return return total number of led that are used on the monitor
|
||||||
*/
|
*/
|
||||||
void StateMachine::pm25handleLeds(void) {
|
int StateMachine::pm25handleLeds(void) {
|
||||||
int pm25Value = value.pm25_1;
|
int totalUsed = ag->ledBar.getNumberOfLeds();
|
||||||
if (config.isMonitorDisplayCompensatedValues() && config.hasSensorSHT) {
|
|
||||||
pm25Value = ag->pms5003.compensate(value.pm25_1, value.Humidity);
|
int pm25Value = round(value.getAverage(Measurements::PM25));
|
||||||
|
if (config.hasSensorSHT && config.isPMCorrectionEnabled()) {
|
||||||
|
pm25Value = round(value.getCorrectedPM25(*ag, config, true));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (pm25Value < 5) {
|
if (pm25Value <= 5) {
|
||||||
/** G; 1 */
|
/** G; 1 */
|
||||||
ag->ledBar.setColor(RGB_COLOR_G, ag->ledBar.getNumberOfLeds() - 1);
|
ag->ledBar.setColor(RGB_COLOR_G, ag->ledBar.getNumberOfLeds() - 1);
|
||||||
} else if (pm25Value < 10) {
|
totalUsed = 1;
|
||||||
|
} else if (pm25Value <= 9) {
|
||||||
/** GG; 2 */
|
/** GG; 2 */
|
||||||
ag->ledBar.setColor(RGB_COLOR_G, ag->ledBar.getNumberOfLeds() - 1);
|
ag->ledBar.setColor(RGB_COLOR_G, ag->ledBar.getNumberOfLeds() - 1);
|
||||||
ag->ledBar.setColor(RGB_COLOR_G, ag->ledBar.getNumberOfLeds() - 2);
|
ag->ledBar.setColor(RGB_COLOR_G, ag->ledBar.getNumberOfLeds() - 2);
|
||||||
} else if (pm25Value < 20) {
|
totalUsed = 2;
|
||||||
|
} else if (pm25Value <= 20) {
|
||||||
/** YYY; 3 */
|
/** YYY; 3 */
|
||||||
ag->ledBar.setColor(RGB_COLOR_Y, ag->ledBar.getNumberOfLeds() - 1);
|
ag->ledBar.setColor(RGB_COLOR_Y, ag->ledBar.getNumberOfLeds() - 1);
|
||||||
ag->ledBar.setColor(RGB_COLOR_Y, ag->ledBar.getNumberOfLeds() - 2);
|
ag->ledBar.setColor(RGB_COLOR_Y, ag->ledBar.getNumberOfLeds() - 2);
|
||||||
ag->ledBar.setColor(RGB_COLOR_Y, ag->ledBar.getNumberOfLeds() - 3);
|
ag->ledBar.setColor(RGB_COLOR_Y, ag->ledBar.getNumberOfLeds() - 3);
|
||||||
} else if (pm25Value < 35) {
|
totalUsed = 3;
|
||||||
|
} else if (pm25Value <= 35) {
|
||||||
/** YYYY; 4 */
|
/** YYYY; 4 */
|
||||||
ag->ledBar.setColor(RGB_COLOR_Y, ag->ledBar.getNumberOfLeds() - 1);
|
ag->ledBar.setColor(RGB_COLOR_Y, ag->ledBar.getNumberOfLeds() - 1);
|
||||||
ag->ledBar.setColor(RGB_COLOR_Y, ag->ledBar.getNumberOfLeds() - 2);
|
ag->ledBar.setColor(RGB_COLOR_Y, ag->ledBar.getNumberOfLeds() - 2);
|
||||||
ag->ledBar.setColor(RGB_COLOR_Y, ag->ledBar.getNumberOfLeds() - 3);
|
ag->ledBar.setColor(RGB_COLOR_Y, ag->ledBar.getNumberOfLeds() - 3);
|
||||||
ag->ledBar.setColor(RGB_COLOR_Y, ag->ledBar.getNumberOfLeds() - 4);
|
ag->ledBar.setColor(RGB_COLOR_Y, ag->ledBar.getNumberOfLeds() - 4);
|
||||||
} else if (pm25Value < 45) {
|
totalUsed = 4;
|
||||||
|
} else if (pm25Value <= 45) {
|
||||||
/** OOOOO; 5 */
|
/** OOOOO; 5 */
|
||||||
ag->ledBar.setColor(RGB_COLOR_O, ag->ledBar.getNumberOfLeds() - 1);
|
ag->ledBar.setColor(RGB_COLOR_O, ag->ledBar.getNumberOfLeds() - 1);
|
||||||
ag->ledBar.setColor(RGB_COLOR_O, ag->ledBar.getNumberOfLeds() - 2);
|
ag->ledBar.setColor(RGB_COLOR_O, ag->ledBar.getNumberOfLeds() - 2);
|
||||||
ag->ledBar.setColor(RGB_COLOR_O, ag->ledBar.getNumberOfLeds() - 3);
|
ag->ledBar.setColor(RGB_COLOR_O, ag->ledBar.getNumberOfLeds() - 3);
|
||||||
ag->ledBar.setColor(RGB_COLOR_O, ag->ledBar.getNumberOfLeds() - 4);
|
ag->ledBar.setColor(RGB_COLOR_O, ag->ledBar.getNumberOfLeds() - 4);
|
||||||
ag->ledBar.setColor(RGB_COLOR_O, ag->ledBar.getNumberOfLeds() - 5);
|
ag->ledBar.setColor(RGB_COLOR_O, ag->ledBar.getNumberOfLeds() - 5);
|
||||||
} else if (pm25Value < 55) {
|
totalUsed = 5;
|
||||||
|
} else if (pm25Value <= 55) {
|
||||||
/** OOOOOO; 6 */
|
/** OOOOOO; 6 */
|
||||||
ag->ledBar.setColor(RGB_COLOR_O, ag->ledBar.getNumberOfLeds() - 1);
|
ag->ledBar.setColor(RGB_COLOR_O, ag->ledBar.getNumberOfLeds() - 1);
|
||||||
ag->ledBar.setColor(RGB_COLOR_O, ag->ledBar.getNumberOfLeds() - 2);
|
ag->ledBar.setColor(RGB_COLOR_O, ag->ledBar.getNumberOfLeds() - 2);
|
||||||
@ -179,7 +215,8 @@ void StateMachine::pm25handleLeds(void) {
|
|||||||
ag->ledBar.setColor(RGB_COLOR_O, ag->ledBar.getNumberOfLeds() - 4);
|
ag->ledBar.setColor(RGB_COLOR_O, ag->ledBar.getNumberOfLeds() - 4);
|
||||||
ag->ledBar.setColor(RGB_COLOR_O, ag->ledBar.getNumberOfLeds() - 5);
|
ag->ledBar.setColor(RGB_COLOR_O, ag->ledBar.getNumberOfLeds() - 5);
|
||||||
ag->ledBar.setColor(RGB_COLOR_O, ag->ledBar.getNumberOfLeds() - 6);
|
ag->ledBar.setColor(RGB_COLOR_O, ag->ledBar.getNumberOfLeds() - 6);
|
||||||
} else if (pm25Value < 100) {
|
totalUsed = 6;
|
||||||
|
} else if (pm25Value <= 100) {
|
||||||
/** RRRRRRR; 7 */
|
/** RRRRRRR; 7 */
|
||||||
ag->ledBar.setColor(RGB_COLOR_R, ag->ledBar.getNumberOfLeds() - 1);
|
ag->ledBar.setColor(RGB_COLOR_R, ag->ledBar.getNumberOfLeds() - 1);
|
||||||
ag->ledBar.setColor(RGB_COLOR_R, ag->ledBar.getNumberOfLeds() - 2);
|
ag->ledBar.setColor(RGB_COLOR_R, ag->ledBar.getNumberOfLeds() - 2);
|
||||||
@ -188,7 +225,8 @@ void StateMachine::pm25handleLeds(void) {
|
|||||||
ag->ledBar.setColor(RGB_COLOR_R, ag->ledBar.getNumberOfLeds() - 5);
|
ag->ledBar.setColor(RGB_COLOR_R, ag->ledBar.getNumberOfLeds() - 5);
|
||||||
ag->ledBar.setColor(RGB_COLOR_R, ag->ledBar.getNumberOfLeds() - 6);
|
ag->ledBar.setColor(RGB_COLOR_R, ag->ledBar.getNumberOfLeds() - 6);
|
||||||
ag->ledBar.setColor(RGB_COLOR_R, ag->ledBar.getNumberOfLeds() - 7);
|
ag->ledBar.setColor(RGB_COLOR_R, ag->ledBar.getNumberOfLeds() - 7);
|
||||||
} else if (pm25Value < 200) {
|
totalUsed = 7;
|
||||||
|
} else if (pm25Value <= 125) {
|
||||||
/** RRRRRRRR; 8 */
|
/** RRRRRRRR; 8 */
|
||||||
ag->ledBar.setColor(RGB_COLOR_R, ag->ledBar.getNumberOfLeds() - 1);
|
ag->ledBar.setColor(RGB_COLOR_R, ag->ledBar.getNumberOfLeds() - 1);
|
||||||
ag->ledBar.setColor(RGB_COLOR_R, ag->ledBar.getNumberOfLeds() - 2);
|
ag->ledBar.setColor(RGB_COLOR_R, ag->ledBar.getNumberOfLeds() - 2);
|
||||||
@ -198,7 +236,8 @@ void StateMachine::pm25handleLeds(void) {
|
|||||||
ag->ledBar.setColor(RGB_COLOR_R, ag->ledBar.getNumberOfLeds() - 6);
|
ag->ledBar.setColor(RGB_COLOR_R, ag->ledBar.getNumberOfLeds() - 6);
|
||||||
ag->ledBar.setColor(RGB_COLOR_R, ag->ledBar.getNumberOfLeds() - 7);
|
ag->ledBar.setColor(RGB_COLOR_R, ag->ledBar.getNumberOfLeds() - 7);
|
||||||
ag->ledBar.setColor(RGB_COLOR_R, ag->ledBar.getNumberOfLeds() - 8);
|
ag->ledBar.setColor(RGB_COLOR_R, ag->ledBar.getNumberOfLeds() - 8);
|
||||||
} else if (pm25Value < 250) {
|
totalUsed = 8;
|
||||||
|
} else if (pm25Value <= 225) {
|
||||||
/** PPPPPPPPP; 9 */
|
/** PPPPPPPPP; 9 */
|
||||||
ag->ledBar.setColor(RGB_COLOR_P, ag->ledBar.getNumberOfLeds() - 1);
|
ag->ledBar.setColor(RGB_COLOR_P, ag->ledBar.getNumberOfLeds() - 1);
|
||||||
ag->ledBar.setColor(RGB_COLOR_P, ag->ledBar.getNumberOfLeds() - 2);
|
ag->ledBar.setColor(RGB_COLOR_P, ag->ledBar.getNumberOfLeds() - 2);
|
||||||
@ -209,7 +248,8 @@ void StateMachine::pm25handleLeds(void) {
|
|||||||
ag->ledBar.setColor(RGB_COLOR_P, ag->ledBar.getNumberOfLeds() - 7);
|
ag->ledBar.setColor(RGB_COLOR_P, ag->ledBar.getNumberOfLeds() - 7);
|
||||||
ag->ledBar.setColor(RGB_COLOR_P, ag->ledBar.getNumberOfLeds() - 8);
|
ag->ledBar.setColor(RGB_COLOR_P, ag->ledBar.getNumberOfLeds() - 8);
|
||||||
ag->ledBar.setColor(RGB_COLOR_P, ag->ledBar.getNumberOfLeds() - 9);
|
ag->ledBar.setColor(RGB_COLOR_P, ag->ledBar.getNumberOfLeds() - 9);
|
||||||
} else { /** > 250 */
|
totalUsed = 9;
|
||||||
|
} else { /** > 225 */
|
||||||
/* PRPRPRPRP; 9 */
|
/* PRPRPRPRP; 9 */
|
||||||
ag->ledBar.setColor(RGB_COLOR_P, ag->ledBar.getNumberOfLeds() - 1);
|
ag->ledBar.setColor(RGB_COLOR_P, ag->ledBar.getNumberOfLeds() - 1);
|
||||||
ag->ledBar.setColor(RGB_COLOR_R, ag->ledBar.getNumberOfLeds() - 2);
|
ag->ledBar.setColor(RGB_COLOR_R, ag->ledBar.getNumberOfLeds() - 2);
|
||||||
@ -220,7 +260,10 @@ void StateMachine::pm25handleLeds(void) {
|
|||||||
ag->ledBar.setColor(RGB_COLOR_P, ag->ledBar.getNumberOfLeds() - 7);
|
ag->ledBar.setColor(RGB_COLOR_P, ag->ledBar.getNumberOfLeds() - 7);
|
||||||
ag->ledBar.setColor(RGB_COLOR_R, ag->ledBar.getNumberOfLeds() - 8);
|
ag->ledBar.setColor(RGB_COLOR_R, ag->ledBar.getNumberOfLeds() - 8);
|
||||||
ag->ledBar.setColor(RGB_COLOR_P, ag->ledBar.getNumberOfLeds() - 9);
|
ag->ledBar.setColor(RGB_COLOR_P, ag->ledBar.getNumberOfLeds() - 9);
|
||||||
|
totalUsed = 9;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return totalUsed;
|
||||||
}
|
}
|
||||||
|
|
||||||
void StateMachine::co2Calibration(void) {
|
void StateMachine::co2Calibration(void) {
|
||||||
@ -311,6 +354,7 @@ void StateMachine::co2Calibration(void) {
|
|||||||
void StateMachine::ledBarTest(void) {
|
void StateMachine::ledBarTest(void) {
|
||||||
if (config.isLedBarTestRequested()) {
|
if (config.isLedBarTestRequested()) {
|
||||||
if (ag->isOne()) {
|
if (ag->isOne()) {
|
||||||
|
ag->ledBar.clear();
|
||||||
if (config.getCountry() == "TH") {
|
if (config.getCountry() == "TH") {
|
||||||
uint32_t tstart = millis();
|
uint32_t tstart = millis();
|
||||||
logInfo("Start run LED test for 2 min");
|
logInfo("Start run LED test for 2 min");
|
||||||
@ -332,7 +376,12 @@ void StateMachine::ledBarTest(void) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void StateMachine::ledBarPowerUpTest(void) { ledBarRunTest(); }
|
void StateMachine::ledBarPowerUpTest(void) {
|
||||||
|
if (ag->isOne()) {
|
||||||
|
ag->ledBar.clear();
|
||||||
|
}
|
||||||
|
ledBarRunTest();
|
||||||
|
}
|
||||||
|
|
||||||
void StateMachine::ledBarRunTest(void) {
|
void StateMachine::ledBarRunTest(void) {
|
||||||
if (ag->isOne()) {
|
if (ag->isOne()) {
|
||||||
@ -585,15 +634,13 @@ void StateMachine::handleLeds(AgStateMachineState state) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ledState = state;
|
ledState = state;
|
||||||
if (ag->isOne()) {
|
|
||||||
ag->ledBar.clear(); // Set all LED OFF
|
|
||||||
}
|
|
||||||
switch (state) {
|
switch (state) {
|
||||||
case AgStateMachineWiFiManagerMode: {
|
case AgStateMachineWiFiManagerMode: {
|
||||||
/** In WiFi Manager Mode */
|
/** In WiFi Manager Mode */
|
||||||
/** Turn LED OFF */
|
/** Turn LED OFF */
|
||||||
/** Turn middle LED Color */
|
/** Turn middle LED Color */
|
||||||
if (ag->isOne()) {
|
if (ag->isOne()) {
|
||||||
|
ag->ledBar.clear();
|
||||||
ag->ledBar.setColor(0, 0, 255, ag->ledBar.getNumberOfLeds() / 2);
|
ag->ledBar.setColor(0, 0, 255, ag->ledBar.getNumberOfLeds() / 2);
|
||||||
} else {
|
} else {
|
||||||
ag->statusLed.setToggle();
|
ag->statusLed.setToggle();
|
||||||
@ -603,6 +650,7 @@ void StateMachine::handleLeds(AgStateMachineState state) {
|
|||||||
case AgStateMachineWiFiManagerPortalActive: {
|
case AgStateMachineWiFiManagerPortalActive: {
|
||||||
/** WiFi Manager has connected to mobile phone */
|
/** WiFi Manager has connected to mobile phone */
|
||||||
if (ag->isOne()) {
|
if (ag->isOne()) {
|
||||||
|
ag->ledBar.clear();
|
||||||
ag->ledBar.setColor(0, 0, 255);
|
ag->ledBar.setColor(0, 0, 255);
|
||||||
} else {
|
} else {
|
||||||
ag->statusLed.setOn();
|
ag->statusLed.setOn();
|
||||||
@ -613,6 +661,7 @@ void StateMachine::handleLeds(AgStateMachineState state) {
|
|||||||
/** after SSID and PW entered and OK clicked, connection to WiFI network is
|
/** after SSID and PW entered and OK clicked, connection to WiFI network is
|
||||||
* attempted */
|
* attempted */
|
||||||
if (ag->isOne()) {
|
if (ag->isOne()) {
|
||||||
|
ag->ledBar.clear();
|
||||||
ledBarSingleLedAnimation(255, 255, 255);
|
ledBarSingleLedAnimation(255, 255, 255);
|
||||||
} else {
|
} else {
|
||||||
ag->statusLed.setOff();
|
ag->statusLed.setOff();
|
||||||
@ -622,6 +671,7 @@ void StateMachine::handleLeds(AgStateMachineState state) {
|
|||||||
case AgStateMachineWiFiManagerStaConnected: {
|
case AgStateMachineWiFiManagerStaConnected: {
|
||||||
/** Connecting to WiFi worked */
|
/** Connecting to WiFi worked */
|
||||||
if (ag->isOne()) {
|
if (ag->isOne()) {
|
||||||
|
ag->ledBar.clear();
|
||||||
ag->ledBar.setColor(255, 255, 255);
|
ag->ledBar.setColor(255, 255, 255);
|
||||||
} else {
|
} else {
|
||||||
ag->statusLed.setOff();
|
ag->statusLed.setOff();
|
||||||
@ -631,6 +681,7 @@ void StateMachine::handleLeds(AgStateMachineState state) {
|
|||||||
case AgStateMachineWiFiOkServerConnecting: {
|
case AgStateMachineWiFiOkServerConnecting: {
|
||||||
/** once connected to WiFi an attempt to reach the server is performed */
|
/** once connected to WiFi an attempt to reach the server is performed */
|
||||||
if (ag->isOne()) {
|
if (ag->isOne()) {
|
||||||
|
ag->ledBar.clear();
|
||||||
ledBarSingleLedAnimation(0, 255, 0);
|
ledBarSingleLedAnimation(0, 255, 0);
|
||||||
} else {
|
} else {
|
||||||
ag->statusLed.setOff();
|
ag->statusLed.setOff();
|
||||||
@ -640,6 +691,7 @@ void StateMachine::handleLeds(AgStateMachineState state) {
|
|||||||
case AgStateMachineWiFiOkServerConnected: {
|
case AgStateMachineWiFiOkServerConnected: {
|
||||||
/** Server is reachable, all fine */
|
/** Server is reachable, all fine */
|
||||||
if (ag->isOne()) {
|
if (ag->isOne()) {
|
||||||
|
ag->ledBar.clear();
|
||||||
ag->ledBar.setColor(0, 255, 0);
|
ag->ledBar.setColor(0, 255, 0);
|
||||||
} else {
|
} else {
|
||||||
ag->statusLed.setOff();
|
ag->statusLed.setOff();
|
||||||
@ -656,6 +708,7 @@ void StateMachine::handleLeds(AgStateMachineState state) {
|
|||||||
case AgStateMachineWiFiManagerConnectFailed: {
|
case AgStateMachineWiFiManagerConnectFailed: {
|
||||||
/** Cannot connect to WiFi (e.g. wrong password, WPA Enterprise etc.) */
|
/** Cannot connect to WiFi (e.g. wrong password, WPA Enterprise etc.) */
|
||||||
if (ag->isOne()) {
|
if (ag->isOne()) {
|
||||||
|
ag->ledBar.clear();
|
||||||
ag->ledBar.setColor(255, 0, 0);
|
ag->ledBar.setColor(255, 0, 0);
|
||||||
} else {
|
} else {
|
||||||
ag->statusLed.setOff();
|
ag->statusLed.setOff();
|
||||||
@ -674,6 +727,7 @@ void StateMachine::handleLeds(AgStateMachineState state) {
|
|||||||
/** Connected to WiFi but server not reachable, e.g. firewall block/
|
/** Connected to WiFi but server not reachable, e.g. firewall block/
|
||||||
* whitelisting needed etc. */
|
* whitelisting needed etc. */
|
||||||
if (ag->isOne()) {
|
if (ag->isOne()) {
|
||||||
|
ag->ledBar.clear();
|
||||||
ag->ledBar.setColor(233, 183, 54); /** orange */
|
ag->ledBar.setColor(233, 183, 54); /** orange */
|
||||||
} else {
|
} else {
|
||||||
ag->statusLed.setOff();
|
ag->statusLed.setOff();
|
||||||
@ -690,6 +744,7 @@ void StateMachine::handleLeds(AgStateMachineState state) {
|
|||||||
case AgStateMachineWiFiOkServerOkSensorConfigFailed: {
|
case AgStateMachineWiFiOkServerOkSensorConfigFailed: {
|
||||||
/** Server reachable but sensor not configured correctly */
|
/** Server reachable but sensor not configured correctly */
|
||||||
if (ag->isOne()) {
|
if (ag->isOne()) {
|
||||||
|
ag->ledBar.clear();
|
||||||
ag->ledBar.setColor(139, 24, 248); /** violet */
|
ag->ledBar.setColor(139, 24, 248); /** violet */
|
||||||
} else {
|
} else {
|
||||||
ag->statusLed.setOff();
|
ag->statusLed.setOff();
|
||||||
@ -707,11 +762,10 @@ void StateMachine::handleLeds(AgStateMachineState state) {
|
|||||||
/** Connection to WiFi network failed credentials incorrect encryption not
|
/** Connection to WiFi network failed credentials incorrect encryption not
|
||||||
* supported etc. */
|
* supported etc. */
|
||||||
if (ag->isOne()) {
|
if (ag->isOne()) {
|
||||||
/** WIFI failed status LED color */
|
bool allUsed = sensorhandleLeds();
|
||||||
|
if (allUsed == false) {
|
||||||
ag->ledBar.setColor(255, 0, 0, 0);
|
ag->ledBar.setColor(255, 0, 0, 0);
|
||||||
/** Show CO2 or PM color status */
|
}
|
||||||
// sensorLedColorHandler();
|
|
||||||
sensorhandleLeds();
|
|
||||||
} else {
|
} else {
|
||||||
ag->statusLed.setOff();
|
ag->statusLed.setOff();
|
||||||
}
|
}
|
||||||
@ -721,11 +775,10 @@ void StateMachine::handleLeds(AgStateMachineState state) {
|
|||||||
/** Connected to WiFi network but the server cannot be reached through the
|
/** Connected to WiFi network but the server cannot be reached through the
|
||||||
* internet, e.g. blocked by firewall */
|
* internet, e.g. blocked by firewall */
|
||||||
if (ag->isOne()) {
|
if (ag->isOne()) {
|
||||||
|
bool allUsed = sensorhandleLeds();
|
||||||
|
if (allUsed == false) {
|
||||||
ag->ledBar.setColor(233, 183, 54, 0);
|
ag->ledBar.setColor(233, 183, 54, 0);
|
||||||
|
}
|
||||||
/** Show CO2 or PM color status */
|
|
||||||
sensorhandleLeds();
|
|
||||||
// sensorLedColorHandler();
|
|
||||||
} else {
|
} else {
|
||||||
ag->statusLed.setOff();
|
ag->statusLed.setOff();
|
||||||
}
|
}
|
||||||
@ -735,10 +788,10 @@ void StateMachine::handleLeds(AgStateMachineState state) {
|
|||||||
/** Server is reachable but there is some configuration issue to be fixed on
|
/** Server is reachable but there is some configuration issue to be fixed on
|
||||||
* the server side */
|
* the server side */
|
||||||
if (ag->isOne()) {
|
if (ag->isOne()) {
|
||||||
|
bool allUsed = sensorhandleLeds();
|
||||||
|
if (allUsed == false) {
|
||||||
ag->ledBar.setColor(139, 24, 248, 0);
|
ag->ledBar.setColor(139, 24, 248, 0);
|
||||||
|
}
|
||||||
/** Show CO2 or PM color status */
|
|
||||||
sensorhandleLeds();
|
|
||||||
} else {
|
} else {
|
||||||
ag->statusLed.setOff();
|
ag->statusLed.setOff();
|
||||||
}
|
}
|
||||||
|
@ -24,9 +24,9 @@ private:
|
|||||||
|
|
||||||
void ledBarSingleLedAnimation(uint8_t r, uint8_t g, uint8_t b);
|
void ledBarSingleLedAnimation(uint8_t r, uint8_t g, uint8_t b);
|
||||||
void ledStatusBlinkDelay(uint32_t delay);
|
void ledStatusBlinkDelay(uint32_t delay);
|
||||||
void sensorhandleLeds(void);
|
bool sensorhandleLeds(void);
|
||||||
void co2handleLeds(void);
|
int co2handleLeds(void);
|
||||||
void pm25handleLeds(void);
|
int pm25handleLeds(void);
|
||||||
void co2Calibration(void);
|
void co2Calibration(void);
|
||||||
void ledBarTest(void);
|
void ledBarTest(void);
|
||||||
void ledBarPowerUpTest(void);
|
void ledBarPowerUpTest(void);
|
||||||
|
1496
src/AgValue.cpp
1496
src/AgValue.cpp
File diff suppressed because it is too large
Load Diff
265
src/AgValue.h
265
src/AgValue.h
@ -1,79 +1,222 @@
|
|||||||
#ifndef _AG_VALUE_H_
|
#ifndef _AG_VALUE_H_
|
||||||
#define _AG_VALUE_H_
|
#define _AG_VALUE_H_
|
||||||
|
|
||||||
#include <Arduino.h>
|
#include "AgConfigure.h"
|
||||||
|
#include "AirGradient.h"
|
||||||
#include "App/AppDef.h"
|
#include "App/AppDef.h"
|
||||||
|
#include "Libraries/Arduino_JSON/src/Arduino_JSON.h"
|
||||||
|
#include "Main/utils.h"
|
||||||
|
#include <Arduino.h>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
class Measurements {
|
class Measurements {
|
||||||
private:
|
private:
|
||||||
String pms5003FirmwareVersion(int fwCode);
|
// Generic struct for update indication for respective value
|
||||||
String pms5003TFirmwareVersion(int fwCode);
|
struct Update {
|
||||||
String pms5003FirmwareVersionBase(String prefix, int fwCode);
|
int invalidCounter; // Counting on how many invalid value that are passed to update function
|
||||||
|
int max; // Maximum length of the period of the moving average
|
||||||
|
float avg; // Moving average value, updated every update function called
|
||||||
|
};
|
||||||
|
|
||||||
|
// Reading type for sensor value that outputs float
|
||||||
|
struct FloatValue {
|
||||||
|
float sumValues; // Total value from each update
|
||||||
|
std::vector<float> listValues; // List of update value that are kept
|
||||||
|
Update update;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Reading type for sensor value that outputs integer
|
||||||
|
struct IntegerValue {
|
||||||
|
unsigned long sumValues; // Total value from each update; unsigned long to accomodate TVOx and
|
||||||
|
// NOx raw data
|
||||||
|
std::vector<int> listValues; // List of update value that are kept
|
||||||
|
Update update;
|
||||||
|
};
|
||||||
|
|
||||||
public:
|
public:
|
||||||
Measurements() {
|
Measurements() {}
|
||||||
pm25_1 = -1;
|
|
||||||
pm01_1 = -1;
|
|
||||||
pm10_1 = -1;
|
|
||||||
pm03PCount_1 = -1;
|
|
||||||
temp_1 = -1001;
|
|
||||||
hum_1 = -1;
|
|
||||||
|
|
||||||
pm25_2 = -1;
|
|
||||||
pm01_2 = -1;
|
|
||||||
pm10_2 = -1;
|
|
||||||
pm03PCount_2 = -1;
|
|
||||||
temp_2 = -1001;
|
|
||||||
hum_2 = -1;
|
|
||||||
|
|
||||||
Temperature = -1001;
|
|
||||||
Humidity = -1;
|
|
||||||
CO2 = -1;
|
|
||||||
TVOC = -1;
|
|
||||||
TVOCRaw = -1;
|
|
||||||
NOx = -1;
|
|
||||||
NOxRaw = -1;
|
|
||||||
}
|
|
||||||
~Measurements() {}
|
~Measurements() {}
|
||||||
|
|
||||||
float Temperature;
|
// Enumeration for every AG measurements
|
||||||
int Humidity;
|
enum MeasurementType {
|
||||||
int CO2;
|
Temperature,
|
||||||
int TVOC;
|
Humidity,
|
||||||
int TVOCRaw;
|
CO2,
|
||||||
int NOx;
|
TVOC, // index value
|
||||||
int NOxRaw;
|
TVOCRaw,
|
||||||
|
NOx, // index value
|
||||||
|
NOxRaw,
|
||||||
|
PM01, // PM1.0 under atmospheric environment
|
||||||
|
PM25, // PM2.5 under athompheric environment
|
||||||
|
PM10, // PM10 under atmospheric environment
|
||||||
|
PM01_SP, // PM1.0 standard particle
|
||||||
|
PM25_SP, // PM2.5 standard particle
|
||||||
|
PM10_SP, // PM10 standard particle
|
||||||
|
PM03_PC, // Particle 0.3 count
|
||||||
|
PM05_PC, // Particle 0.5 count
|
||||||
|
PM01_PC, // Particle 1.0 count
|
||||||
|
PM25_PC, // Particle 2.5 count
|
||||||
|
PM5_PC, // Particle 5.0 count
|
||||||
|
PM10_PC, // Particle 10 count
|
||||||
|
};
|
||||||
|
|
||||||
int pm25_1;
|
/**
|
||||||
int pm01_1;
|
* @brief Set each MeasurementType maximum period length for moving average
|
||||||
int pm10_1;
|
*
|
||||||
int pm03PCount_1;
|
* @param type the target measurement type to set
|
||||||
float temp_1;
|
* @param max the maximum period length
|
||||||
int hum_1;
|
*/
|
||||||
|
void maxPeriod(MeasurementType, int max);
|
||||||
|
|
||||||
int pm25_2;
|
/**
|
||||||
int pm01_2;
|
* @brief update target measurement type with new value.
|
||||||
int pm10_2;
|
* Each MeasurementType has last raw value and moving average value based on max period
|
||||||
int pm03PCount_2;
|
* This function is for MeasurementType that use INT as the data type
|
||||||
float temp_2;
|
*
|
||||||
int hum_2;
|
* @param type measurement type that will be updated
|
||||||
|
* @param val (int) the new value
|
||||||
|
* @param ch (int) the MeasurementType channel, not every MeasurementType has more than 1 channel.
|
||||||
|
* Currently maximum channel is 2. Default: 1 (channel 1)
|
||||||
|
* @return false if new value invalid consecutively reach threshold (max period)
|
||||||
|
* @return true otherwise
|
||||||
|
*/
|
||||||
|
bool update(MeasurementType type, int val, int ch = 1);
|
||||||
|
|
||||||
int pm1Value01;
|
/**
|
||||||
int pm1Value25;
|
* @brief update target measurement type with new value.
|
||||||
int pm1Value10;
|
* Each MeasurementType has last raw value and moving average value based on max period
|
||||||
int pm1PCount;
|
* This function is for MeasurementType that use FLOAT as the data type
|
||||||
int pm1temp;
|
*
|
||||||
int pm1hum;
|
* @param type measurement type that will be updated
|
||||||
int pm2Value01;
|
* @param val (float) the new value
|
||||||
int pm2Value25;
|
* @param ch (int) the MeasurementType channel, not every MeasurementType has more than 1 channel.
|
||||||
int pm2Value10;
|
* Currently maximum channel is 2. Default: 1 (channel 1)
|
||||||
int pm2PCount;
|
* @return false if new value invalid consecutively reach threshold (max period)
|
||||||
int pm2temp;
|
* @return true otherwise
|
||||||
int pm2hum;
|
*/
|
||||||
int countPosition;
|
bool update(MeasurementType type, float val, int ch = 1);
|
||||||
const int targetCount = 20;
|
|
||||||
|
/**
|
||||||
|
* @brief Get the target measurement latest value
|
||||||
|
*
|
||||||
|
* @param type measurement type that will be retrieve
|
||||||
|
* @param ch target type value channel
|
||||||
|
* @return int measurement type value
|
||||||
|
*/
|
||||||
|
int get(MeasurementType type, int ch = 1);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get the target measurement latest value
|
||||||
|
*
|
||||||
|
* @param type measurement type that will be retrieve
|
||||||
|
* @param ch target type value channel
|
||||||
|
* @return float measurement type value
|
||||||
|
*/
|
||||||
|
float getFloat(MeasurementType type, int ch = 1);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get the target measurement average value
|
||||||
|
*
|
||||||
|
* @param type measurement type that will be retrieve
|
||||||
|
* @param ch target type value channel
|
||||||
|
* @return moving average value of target measurements type
|
||||||
|
*/
|
||||||
|
float getAverage(MeasurementType type, int ch = 1);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get the Corrected PM25 object based on the correction algorithm from configuration
|
||||||
|
*
|
||||||
|
* If correction is not enabled, then will return the raw value (either average or last value)
|
||||||
|
*
|
||||||
|
* @param ag AirGradient instance
|
||||||
|
* @param config Configuration instance
|
||||||
|
* @param useAvg Use moving average value if true, otherwise use latest value
|
||||||
|
* @param ch MeasurementType channel
|
||||||
|
* @return float Corrected PM2.5 value
|
||||||
|
*/
|
||||||
|
float getCorrectedPM25(AirGradient &ag, Configuration &config, bool useAvg = false, int ch = 1);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* build json payload for every measurements
|
||||||
|
*/
|
||||||
|
String toString(bool localServer, AgFirmwareMode fwMode, int rssi, AirGradient &ag,
|
||||||
|
Configuration &config);
|
||||||
|
|
||||||
|
bool resetLocalStorage();
|
||||||
|
bool saveLocalStorage(AirGradient &ag, Configuration &config);
|
||||||
|
char *getLocalStorage();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set to true if want to debug every update value
|
||||||
|
*/
|
||||||
|
void setDebug(bool debug);
|
||||||
|
|
||||||
|
// TODO: update this to use setter
|
||||||
int bootCount;
|
int bootCount;
|
||||||
|
|
||||||
String toString(bool isLocal, AgFirmwareMode fwMode, int rssi, void* _ag, void* _config);
|
private:
|
||||||
|
// Some declared as an array (channel), because FW_MODE_O_1PPx has two PMS5003T
|
||||||
|
FloatValue _temperature[2];
|
||||||
|
FloatValue _humidity[2];
|
||||||
|
IntegerValue _co2;
|
||||||
|
IntegerValue _tvoc; // Index value
|
||||||
|
IntegerValue _tvoc_raw;
|
||||||
|
IntegerValue _nox; // Index value
|
||||||
|
IntegerValue _nox_raw;
|
||||||
|
IntegerValue _pm_01[2]; // pm 1.0 atmospheric environment
|
||||||
|
IntegerValue _pm_25[2]; // pm 2.5 atmospheric environment
|
||||||
|
IntegerValue _pm_10[2]; // pm 10 atmospheric environment
|
||||||
|
IntegerValue _pm_01_sp[2]; // pm 1.0 standard particle
|
||||||
|
IntegerValue _pm_25_sp[2]; // pm 2.5 standard particle
|
||||||
|
IntegerValue _pm_10_sp[2]; // pm 10 standard particle
|
||||||
|
IntegerValue _pm_03_pc[2]; // particle count 0.3
|
||||||
|
IntegerValue _pm_05_pc[2]; // particle count 0.5
|
||||||
|
IntegerValue _pm_01_pc[2]; // particle count 1.0
|
||||||
|
IntegerValue _pm_25_pc[2]; // particle count 2.5
|
||||||
|
IntegerValue _pm_5_pc[2]; // particle count 5.0
|
||||||
|
IntegerValue _pm_10_pc[2]; // particle count 10
|
||||||
|
|
||||||
|
bool _debug = false;
|
||||||
|
const char *FILE_PATH = "/measurements.csv"; // Local storage file path
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get PMS5003 firmware version string
|
||||||
|
*
|
||||||
|
* @param fwCode
|
||||||
|
* @return String
|
||||||
|
*/
|
||||||
|
String pms5003FirmwareVersion(int fwCode);
|
||||||
|
/**
|
||||||
|
* @brief Get PMS5003T firmware version string
|
||||||
|
*
|
||||||
|
* @param fwCode
|
||||||
|
* @return String
|
||||||
|
*/
|
||||||
|
String pms5003TFirmwareVersion(int fwCode);
|
||||||
|
/**
|
||||||
|
* @brief Get firmware version string
|
||||||
|
*
|
||||||
|
* @param prefix Prefix firmware string
|
||||||
|
* @param fwCode Version code
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
String pms5003FirmwareVersionBase(String prefix, int fwCode);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert AgValue Type to string representation of the value
|
||||||
|
*/
|
||||||
|
String measurementTypeStr(MeasurementType type);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief check if provided channel is a valid channel or not
|
||||||
|
* abort program if invalid
|
||||||
|
*/
|
||||||
|
void validateChannel(int ch);
|
||||||
|
|
||||||
|
JSONVar buildOutdoor(bool localServer, AgFirmwareMode fwMode, AirGradient &ag,
|
||||||
|
Configuration &config);
|
||||||
|
JSONVar buildIndoor(bool localServer, AirGradient &ag, Configuration &config);
|
||||||
|
JSONVar buildPMS(AirGradient &ag, int ch, bool allCh, bool withTempHum, bool compensate);
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif /** _AG_VALUE_H_ */
|
#endif /** _AG_VALUE_H_ */
|
||||||
|
@ -85,3 +85,25 @@ String AirGradient::deviceId(void) {
|
|||||||
mac.toLowerCase();
|
mac.toLowerCase();
|
||||||
return mac;
|
return mac;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void AirGradient::setCurrentTime(long epochTime) {
|
||||||
|
// set current day/time
|
||||||
|
struct timeval tv;
|
||||||
|
tv.tv_sec = epochTime; // - 1020; // 17 minutes // don't know why it always off by 17 minutes
|
||||||
|
settimeofday(&tv, NULL);
|
||||||
|
Serial.println(epochTime);
|
||||||
|
Serial.printf("Set current time to %s\n", getCurrentTime().c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
String AirGradient::getCurrentTime() {
|
||||||
|
// Get time
|
||||||
|
time_t now;
|
||||||
|
char strftime_buf[64];
|
||||||
|
struct tm timeinfo;
|
||||||
|
time(&now);
|
||||||
|
// Format
|
||||||
|
localtime_r(&now, &timeinfo);
|
||||||
|
strftime(strftime_buf, sizeof(strftime_buf), "%d/%m %H:%M:%S", &timeinfo);
|
||||||
|
|
||||||
|
return String(strftime_buf);
|
||||||
|
}
|
@ -15,7 +15,7 @@
|
|||||||
#include "Main/utils.h"
|
#include "Main/utils.h"
|
||||||
|
|
||||||
#ifndef GIT_VERSION
|
#ifndef GIT_VERSION
|
||||||
#define GIT_VERSION "3.1.9-snap"
|
#define GIT_VERSION "3.1.13-snap"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -173,6 +173,9 @@ public:
|
|||||||
*/
|
*/
|
||||||
String deviceId(void);
|
String deviceId(void);
|
||||||
|
|
||||||
|
void setCurrentTime(long epochTime);
|
||||||
|
String getCurrentTime();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
BoardType boardType;
|
BoardType boardType;
|
||||||
};
|
};
|
||||||
|
@ -94,6 +94,18 @@ enum ConfigurationControl {
|
|||||||
ConfigurationControlBoth
|
ConfigurationControlBoth
|
||||||
};
|
};
|
||||||
|
|
||||||
|
enum PMCorrectionAlgorithm {
|
||||||
|
Unknown, // Unknown algorithm
|
||||||
|
None, // No PM correction
|
||||||
|
EPA_2021,
|
||||||
|
SLR_PMS5003_20220802,
|
||||||
|
SLR_PMS5003_20220803,
|
||||||
|
SLR_PMS5003_20220824,
|
||||||
|
SLR_PMS5003_20231030,
|
||||||
|
SLR_PMS5003_20231218,
|
||||||
|
SLR_PMS5003_20240104,
|
||||||
|
};
|
||||||
|
|
||||||
enum AgFirmwareMode {
|
enum AgFirmwareMode {
|
||||||
FW_MODE_I_9PSL, /** ONE_INDOOR */
|
FW_MODE_I_9PSL, /** ONE_INDOOR */
|
||||||
FW_MODE_O_1PST, /** PMS5003T, S8 and SGP41 */
|
FW_MODE_O_1PST, /** PMS5003T, S8 and SGP41 */
|
||||||
|
@ -151,6 +151,7 @@ void PMSBase::readPackage(Stream *serial) {
|
|||||||
if (ms >= READ_PACKGE_TIMEOUT) {
|
if (ms >= READ_PACKGE_TIMEOUT) {
|
||||||
lastPackage = 0;
|
lastPackage = 0;
|
||||||
_connected = false;
|
_connected = false;
|
||||||
|
Serial.println("PMS disconnected");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -297,24 +298,52 @@ uint8_t PMSBase::getErrorCode(void) { return pms_errorCode; }
|
|||||||
* @return int
|
* @return int
|
||||||
*/
|
*/
|
||||||
int PMSBase::pm25ToAQI(int pm02) {
|
int PMSBase::pm25ToAQI(int pm02) {
|
||||||
if (pm02 <= 12.0)
|
if (pm02 <= 9.0)
|
||||||
return ((50 - 0) / (12.0 - .0) * (pm02 - .0) + 0);
|
return ((50 - 0) / (9.0 - .0) * (pm02 - .0) + 0);
|
||||||
else if (pm02 <= 35.4)
|
else if (pm02 <= 35.4)
|
||||||
return ((100 - 50) / (35.4 - 12.0) * (pm02 - 12.0) + 50);
|
return ((100 - 51) / (35.4 - 9.1) * (pm02 - 9.0) + 51);
|
||||||
else if (pm02 <= 55.4)
|
else if (pm02 <= 55.4)
|
||||||
return ((150 - 100) / (55.4 - 35.4) * (pm02 - 35.4) + 100);
|
return ((150 - 101) / (55.4 - 35.5) * (pm02 - 35.5) + 101);
|
||||||
else if (pm02 <= 150.4)
|
else if (pm02 <= 125.4)
|
||||||
return ((200 - 150) / (150.4 - 55.4) * (pm02 - 55.4) + 150);
|
return ((200 - 151) / (125.4 - 55.5) * (pm02 - 55.5) + 151);
|
||||||
else if (pm02 <= 250.4)
|
else if (pm02 <= 225.4)
|
||||||
return ((300 - 200) / (250.4 - 150.4) * (pm02 - 150.4) + 200);
|
return ((300 - 201) / (225.4 - 125.5) * (pm02 - 125.5) + 201);
|
||||||
else if (pm02 <= 350.4)
|
else if (pm02 <= 325.4)
|
||||||
return ((400 - 300) / (350.4 - 250.4) * (pm02 - 250.4) + 300);
|
return ((500 - 301) / (325.4 - 225.5) * (pm02 - 225.5) + 301);
|
||||||
else if (pm02 <= 500.4)
|
|
||||||
return ((500 - 400) / (500.4 - 350.4) * (pm02 - 350.4) + 400);
|
|
||||||
else
|
else
|
||||||
return 500;
|
return 500;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief SLR correction for PM2.5
|
||||||
|
*
|
||||||
|
* Reference: https://www.airgradient.com/blog/low-readings-from-pms5003/
|
||||||
|
*
|
||||||
|
* @param pm25 PM2.5 raw value
|
||||||
|
* @param pm003Count PM0.3 count
|
||||||
|
* @param scalingFactor Scaling factor
|
||||||
|
* @param intercept Intercept
|
||||||
|
* @return float Calibrated PM2.5 value
|
||||||
|
*/
|
||||||
|
float PMSBase::slrCorrection(float pm25, float pm003Count, float scalingFactor, float intercept) {
|
||||||
|
float calibrated;
|
||||||
|
|
||||||
|
float lowCalibrated = (scalingFactor * pm003Count) + intercept;
|
||||||
|
if (lowCalibrated < 31) {
|
||||||
|
calibrated = lowCalibrated;
|
||||||
|
} else {
|
||||||
|
calibrated = pm25;
|
||||||
|
}
|
||||||
|
|
||||||
|
// No negative value for pm2.5
|
||||||
|
if (calibrated < 0) {
|
||||||
|
return 0.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return calibrated;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Correction PM2.5
|
* @brief Correction PM2.5
|
||||||
*
|
*
|
||||||
@ -322,11 +351,12 @@ int PMSBase::pm25ToAQI(int pm02) {
|
|||||||
*
|
*
|
||||||
* @param pm25 Raw PM2.5 value
|
* @param pm25 Raw PM2.5 value
|
||||||
* @param humidity Humidity value (%)
|
* @param humidity Humidity value (%)
|
||||||
* @return int
|
* @return compensated pm25 value
|
||||||
*/
|
*/
|
||||||
int PMSBase::compensate(int pm25, float humidity) {
|
float PMSBase::compensate(float pm25, float humidity) {
|
||||||
float value;
|
float value;
|
||||||
float fpm25 = pm25;
|
|
||||||
|
// Correct invalid humidity value
|
||||||
if (humidity < 0) {
|
if (humidity < 0) {
|
||||||
humidity = 0;
|
humidity = 0;
|
||||||
}
|
}
|
||||||
@ -334,23 +364,33 @@ int PMSBase::compensate(int pm25, float humidity) {
|
|||||||
humidity = 100.0f;
|
humidity = 100.0f;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If its already 0, do not proceed
|
||||||
|
if (pm25 == 0) {
|
||||||
|
return 0.0;
|
||||||
|
}
|
||||||
|
|
||||||
if (pm25 < 30) { /** pm2.5 < 30 */
|
if (pm25 < 30) { /** pm2.5 < 30 */
|
||||||
value = (fpm25 * 0.524f) - (humidity * 0.0862f) + 5.75f;
|
value = (pm25 * 0.524f) - (humidity * 0.0862f) + 5.75f;
|
||||||
} else if (pm25 < 50) { /** 30 <= pm2.5 < 50 */
|
} else if (pm25 < 50) { /** 30 <= pm2.5 < 50 */
|
||||||
value = (0.786f * (fpm25 * 0.05f - 1.5f) + 0.524f * (1.0f - (fpm25 * 0.05f - 1.5f))) * fpm25 - (0.0862f * humidity) + 5.75f;
|
value = (0.786f * (pm25 * 0.05f - 1.5f) + 0.524f * (1.0f - (pm25 * 0.05f - 1.5f))) * pm25 -
|
||||||
|
(0.0862f * humidity) + 5.75f;
|
||||||
} else if (pm25 < 210) { /** 50 <= pm2.5 < 210 */
|
} else if (pm25 < 210) { /** 50 <= pm2.5 < 210 */
|
||||||
value = (0.786f * fpm25) - (0.0862f * humidity) + 5.75f;
|
value = (0.786f * pm25) - (0.0862f * humidity) + 5.75f;
|
||||||
} else if (pm25 < 260) { /** 210 <= pm2.5 < 260 */
|
} else if (pm25 < 260) { /** 210 <= pm2.5 < 260 */
|
||||||
value = (0.69f * (fpm25 * 0.02f - 4.2f) + 0.786f * (1.0f - (fpm25 * 0.02f - 4.2f))) * fpm25 - (0.0862f * humidity * (1.0f - (fpm25 * 0.02f - 4.2f))) + (2.966f * (fpm25 * 0.02f - 4.2f)) + (5.75f * (1.0f - (fpm25 * 0.02f - 4.2f))) + (8.84f * (1.e-4) * fpm25 * fpm25 * (fpm25 * 0.02f - 4.2f));
|
value = (0.69f * (pm25 * 0.02f - 4.2f) + 0.786f * (1.0f - (pm25 * 0.02f - 4.2f))) * pm25 -
|
||||||
|
(0.0862f * humidity * (1.0f - (pm25 * 0.02f - 4.2f))) +
|
||||||
|
(2.966f * (pm25 * 0.02f - 4.2f)) + (5.75f * (1.0f - (pm25 * 0.02f - 4.2f))) +
|
||||||
|
(8.84f * (1.e-4) * pm25 * pm25 * (pm25 * 0.02f - 4.2f));
|
||||||
} else { /** 260 <= pm2.5 */
|
} else { /** 260 <= pm2.5 */
|
||||||
value = 2.966f + (0.69f * fpm25) + (8.84f * (1.e-4) * fpm25 * fpm25);
|
value = 2.966f + (0.69f * pm25) + (8.84f * (1.e-4) * pm25 * pm25);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// No negative value for pm2.5
|
||||||
if (value < 0) {
|
if (value < 0) {
|
||||||
value = 0;
|
return 0.0;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (int)value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -390,20 +430,26 @@ bool PMSBase::validate(const uint8_t *buf) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void PMSBase::parse(const uint8_t *buf) {
|
void PMSBase::parse(const uint8_t *buf) {
|
||||||
|
// Standard particle
|
||||||
pms_raw0_1 = toU16(&buf[4]);
|
pms_raw0_1 = toU16(&buf[4]);
|
||||||
pms_raw2_5 = toU16(&buf[6]);
|
pms_raw2_5 = toU16(&buf[6]);
|
||||||
pms_raw10 = toU16(&buf[8]);
|
pms_raw10 = toU16(&buf[8]);
|
||||||
|
// atmospheric
|
||||||
pms_pm0_1 = toU16(&buf[10]);
|
pms_pm0_1 = toU16(&buf[10]);
|
||||||
pms_pm2_5 = toU16(&buf[12]);
|
pms_pm2_5 = toU16(&buf[12]);
|
||||||
pms_pm10 = toU16(&buf[14]);
|
pms_pm10 = toU16(&buf[14]);
|
||||||
|
|
||||||
|
// particle count
|
||||||
pms_count0_3 = toU16(&buf[16]);
|
pms_count0_3 = toU16(&buf[16]);
|
||||||
pms_count0_5 = toU16(&buf[18]);
|
pms_count0_5 = toU16(&buf[18]);
|
||||||
pms_count1_0 = toU16(&buf[20]);
|
pms_count1_0 = toU16(&buf[20]);
|
||||||
pms_count2_5 = toU16(&buf[22]);
|
pms_count2_5 = toU16(&buf[22]);
|
||||||
pms_count5_0 = toU16(&buf[24]);
|
pms_count5_0 = toU16(&buf[24]); // PMS5003 only
|
||||||
pms_count10 = toU16(&buf[26]);
|
pms_count10 = toU16(&buf[26]); // PMS5003 only
|
||||||
pms_temp = toU16(&buf[24]);
|
|
||||||
pms_hum = toU16(&buf[26]);
|
// Others
|
||||||
|
pms_temp = toU16(&buf[24]); // PMS5003T only
|
||||||
|
pms_hum = toU16(&buf[26]); // PMS5003T only
|
||||||
pms_firmwareVersion = buf[28];
|
pms_firmwareVersion = buf[28];
|
||||||
pms_errorCode = buf[29];
|
pms_errorCode = buf[29];
|
||||||
}
|
}
|
||||||
|
@ -39,7 +39,8 @@ public:
|
|||||||
uint8_t getErrorCode(void);
|
uint8_t getErrorCode(void);
|
||||||
|
|
||||||
int pm25ToAQI(int pm02);
|
int pm25ToAQI(int pm02);
|
||||||
int compensate(int pm25, float humidity);
|
float slrCorrection(float pm25, float pm003Count, float scalingFactor, float intercept);
|
||||||
|
float compensate(float pm25, float humidity);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static const uint8_t package_size = 32;
|
static const uint8_t package_size = 32;
|
||||||
|
@ -79,28 +79,49 @@ bool PMS5003::begin(void) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Read PM1.0 must call this function after @ref readData success
|
* @brief Read PM1.0
|
||||||
*
|
*
|
||||||
* @return int PM1.0 index
|
* @return int PM1.0 index (atmospheric environment)
|
||||||
*/
|
*/
|
||||||
int PMS5003::getPm01Ae(void) { return pms.getPM0_1(); }
|
int PMS5003::getPm01Ae(void) { return pms.getPM0_1(); }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Read PM2.5 must call this function after @ref readData success
|
* @brief Read PM2.5
|
||||||
*
|
*
|
||||||
* @return int PM2.5 index
|
* @return int PM2.5 index (atmospheric environment)
|
||||||
*/
|
*/
|
||||||
int PMS5003::getPm25Ae(void) { return pms.getPM2_5(); }
|
int PMS5003::getPm25Ae(void) { return pms.getPM2_5(); }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Read PM10.0 must call this function after @ref readData success
|
* @brief Read PM10.0
|
||||||
*
|
*
|
||||||
* @return int PM10.0 index
|
* @return int PM10.0 index (atmospheric environment)
|
||||||
*/
|
*/
|
||||||
int PMS5003::getPm10Ae(void) { return pms.getPM10(); }
|
int PMS5003::getPm10Ae(void) { return pms.getPM10(); }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Read PM0.3 must call this function after @ref readData success
|
* @brief Read PM1.0
|
||||||
|
*
|
||||||
|
* @return int PM1.0 index (standard particle)
|
||||||
|
*/
|
||||||
|
int PMS5003::getPm01Sp(void) { return pms.getRaw0_1(); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Read PM2.5
|
||||||
|
*
|
||||||
|
* @return int PM2.5 index (standard particle)
|
||||||
|
*/
|
||||||
|
int PMS5003::getPm25Sp(void) { return pms.getRaw2_5(); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Read PM10
|
||||||
|
*
|
||||||
|
* @return int PM10 index (standard particle)
|
||||||
|
*/
|
||||||
|
int PMS5003::getPm10Sp(void) { return pms.getRaw10(); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Read particle 0.3 count
|
||||||
*
|
*
|
||||||
* @return int PM0.3 index
|
* @return int PM0.3 index
|
||||||
*/
|
*/
|
||||||
@ -108,6 +129,41 @@ int PMS5003::getPm03ParticleCount(void) {
|
|||||||
return pms.getCount0_3();
|
return pms.getCount0_3();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Read particle 1.0 count
|
||||||
|
*
|
||||||
|
* @return int particle 1.0 count index
|
||||||
|
*/
|
||||||
|
int PMS5003::getPm01ParticleCount(void) { return pms.getCount1_0(); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Read particle 0.5 count
|
||||||
|
*
|
||||||
|
* @return int particle 0.5 count index
|
||||||
|
*/
|
||||||
|
int PMS5003::getPm05ParticleCount(void) { return pms.getCount0_5(); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Read particle 2.5 count
|
||||||
|
*
|
||||||
|
* @return int particle 2.5 count index
|
||||||
|
*/
|
||||||
|
int PMS5003::getPm25ParticleCount(void) { return pms.getCount2_5(); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Read particle 5.0 count
|
||||||
|
*
|
||||||
|
* @return int particle 5.0 count index
|
||||||
|
*/
|
||||||
|
int PMS5003::getPm5ParticleCount(void) { return pms.getCount5_0(); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Read particle 10 count
|
||||||
|
*
|
||||||
|
* @return int particle 10 count index
|
||||||
|
*/
|
||||||
|
int PMS5003::getPm10ParticleCount(void) { return pms.getCount10(); }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Convert PM2.5 to US AQI
|
* @brief Convert PM2.5 to US AQI
|
||||||
*
|
*
|
||||||
@ -116,6 +172,10 @@ int PMS5003::getPm03ParticleCount(void) {
|
|||||||
*/
|
*/
|
||||||
int PMS5003::convertPm25ToUsAqi(int pm25) { return pms.pm25ToAQI(pm25); }
|
int PMS5003::convertPm25ToUsAqi(int pm25) { return pms.pm25ToAQI(pm25); }
|
||||||
|
|
||||||
|
float PMS5003::slrCorrection(float pm25, float pm003Count, float scalingFactor, float intercept) {
|
||||||
|
return pms.slrCorrection(pm25, pm003Count, scalingFactor, intercept);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Correct PM2.5
|
* @brief Correct PM2.5
|
||||||
*
|
*
|
||||||
@ -123,11 +183,9 @@ int PMS5003::convertPm25ToUsAqi(int pm25) { return pms.pm25ToAQI(pm25); }
|
|||||||
*
|
*
|
||||||
* @param pm25 PM2.5 raw value
|
* @param pm25 PM2.5 raw value
|
||||||
* @param humidity Humidity value
|
* @param humidity Humidity value
|
||||||
* @return int
|
* @return compensated value in float
|
||||||
*/
|
*/
|
||||||
int PMS5003::compensate(int pm25, float humidity) {
|
float PMS5003::compensate(float pm25, float humidity) { return pms.compensate(pm25, humidity); }
|
||||||
return pms.compensate(pm25, humidity);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Get sensor firmware version
|
* @brief Get sensor firmware version
|
||||||
|
@ -25,12 +25,25 @@ public:
|
|||||||
void resetFailCount(void);
|
void resetFailCount(void);
|
||||||
int getFailCount(void);
|
int getFailCount(void);
|
||||||
int getFailCountMax(void);
|
int getFailCountMax(void);
|
||||||
|
// Atmospheric environment
|
||||||
int getPm01Ae(void);
|
int getPm01Ae(void);
|
||||||
int getPm25Ae(void);
|
int getPm25Ae(void);
|
||||||
int getPm10Ae(void);
|
int getPm10Ae(void);
|
||||||
|
// Standard particle
|
||||||
|
int getPm01Sp(void);
|
||||||
|
int getPm25Sp(void);
|
||||||
|
int getPm10Sp(void);
|
||||||
|
// Particle count
|
||||||
int getPm03ParticleCount(void);
|
int getPm03ParticleCount(void);
|
||||||
|
int getPm05ParticleCount(void);
|
||||||
|
int getPm01ParticleCount(void);
|
||||||
|
int getPm25ParticleCount(void);
|
||||||
|
int getPm5ParticleCount(void);
|
||||||
|
int getPm10ParticleCount(void);
|
||||||
|
|
||||||
int convertPm25ToUsAqi(int pm25);
|
int convertPm25ToUsAqi(int pm25);
|
||||||
int compensate(int pm25, float humidity);
|
float slrCorrection(float pm25, float pm003Count, float scalingFactor, float intercept);
|
||||||
|
float compensate(float pm25, float humidity);
|
||||||
int getFirmwareVersion(void);
|
int getFirmwareVersion(void);
|
||||||
uint8_t getErrorCode(void);
|
uint8_t getErrorCode(void);
|
||||||
bool connected(void);
|
bool connected(void);
|
||||||
|
@ -108,35 +108,77 @@ bool PMS5003T::begin(void) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Read PM1.0 must call this function after @ref readData success
|
* @brief Read PM1.0
|
||||||
*
|
*
|
||||||
* @return int PM1.0 index
|
* @return int PM1.0 index (atmospheric environment)
|
||||||
*/
|
*/
|
||||||
int PMS5003T::getPm01Ae(void) { return pms.getPM0_1(); }
|
int PMS5003T::getPm01Ae(void) { return pms.getPM0_1(); }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Read PM2.5 must call this function after @ref readData success
|
* @brief Read PM2.5
|
||||||
*
|
*
|
||||||
* @return int PM2.5 index
|
* @return int PM2.5 index (atmospheric environment)
|
||||||
*/
|
*/
|
||||||
int PMS5003T::getPm25Ae(void) { return pms.getPM2_5(); }
|
int PMS5003T::getPm25Ae(void) { return pms.getPM2_5(); }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Read PM10.0 must call this function after @ref readData success
|
* @brief Read PM10.0
|
||||||
*
|
*
|
||||||
* @return int PM10.0 index
|
* @return int PM10.0 index (atmospheric environment)
|
||||||
*/
|
*/
|
||||||
int PMS5003T::getPm10Ae(void) { return pms.getPM10(); }
|
int PMS5003T::getPm10Ae(void) { return pms.getPM10(); }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Read PM 0.3 Count must call this function after @ref readData success
|
* @brief Read PM1.0
|
||||||
*
|
*
|
||||||
* @return int PM 0.3 Count index
|
* @return int PM1.0 index (standard particle)
|
||||||
|
*/
|
||||||
|
int PMS5003T::getPm01Sp(void) { return pms.getRaw0_1(); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Read PM2.5
|
||||||
|
*
|
||||||
|
* @return int PM2.5 index (standard particle)
|
||||||
|
*/
|
||||||
|
int PMS5003T::getPm25Sp(void) { return pms.getRaw2_5(); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Read PM10
|
||||||
|
*
|
||||||
|
* @return int PM10 index (standard particle)
|
||||||
|
*/
|
||||||
|
int PMS5003T::getPm10Sp(void) { return pms.getRaw10(); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Read particle 0.3 count
|
||||||
|
*
|
||||||
|
* @return int particle 0.3 count index
|
||||||
*/
|
*/
|
||||||
int PMS5003T::getPm03ParticleCount(void) {
|
int PMS5003T::getPm03ParticleCount(void) {
|
||||||
return pms.getCount0_3();
|
return pms.getCount0_3();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Read particle 0.5 count
|
||||||
|
*
|
||||||
|
* @return int particle 0.5 count index
|
||||||
|
*/
|
||||||
|
int PMS5003T::getPm05ParticleCount(void) { return pms.getCount0_5(); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Read particle 1.0 count
|
||||||
|
*
|
||||||
|
* @return int particle 1.0 count index
|
||||||
|
*/
|
||||||
|
int PMS5003T::getPm01ParticleCount(void) { return pms.getCount1_0(); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Read particle 2.5 count
|
||||||
|
*
|
||||||
|
* @return int particle 2.5 count index
|
||||||
|
*/
|
||||||
|
int PMS5003T::getPm25ParticleCount(void) { return pms.getCount2_5(); }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Convert PM2.5 to US AQI
|
* @brief Convert PM2.5 to US AQI
|
||||||
*
|
*
|
||||||
@ -170,11 +212,9 @@ float PMS5003T::getRelativeHumidity(void) {
|
|||||||
*
|
*
|
||||||
* @param pm25 PM2.5 raw value
|
* @param pm25 PM2.5 raw value
|
||||||
* @param humidity Humidity value
|
* @param humidity Humidity value
|
||||||
* @return int
|
* @return compensated value
|
||||||
*/
|
*/
|
||||||
int PMS5003T::compensate(int pm25, float humidity) {
|
float PMS5003T::compensate(float pm25, float humidity) { return pms.compensate(pm25, humidity); }
|
||||||
return pms.compensate(pm25, humidity);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Get module(s) firmware version
|
* @brief Get module(s) firmware version
|
||||||
|
@ -28,14 +28,24 @@ public:
|
|||||||
void resetFailCount(void);
|
void resetFailCount(void);
|
||||||
int getFailCount(void);
|
int getFailCount(void);
|
||||||
int getFailCountMax(void);
|
int getFailCountMax(void);
|
||||||
|
// Atmospheric environment
|
||||||
int getPm01Ae(void);
|
int getPm01Ae(void);
|
||||||
int getPm25Ae(void);
|
int getPm25Ae(void);
|
||||||
int getPm10Ae(void);
|
int getPm10Ae(void);
|
||||||
|
// Standard particle
|
||||||
|
int getPm01Sp(void);
|
||||||
|
int getPm25Sp(void);
|
||||||
|
int getPm10Sp(void);
|
||||||
|
// Particle count
|
||||||
int getPm03ParticleCount(void);
|
int getPm03ParticleCount(void);
|
||||||
|
int getPm05ParticleCount(void);
|
||||||
|
int getPm01ParticleCount(void);
|
||||||
|
int getPm25ParticleCount(void);
|
||||||
|
|
||||||
int convertPm25ToUsAqi(int pm25);
|
int convertPm25ToUsAqi(int pm25);
|
||||||
float getTemperature(void);
|
float getTemperature(void);
|
||||||
float getRelativeHumidity(void);
|
float getRelativeHumidity(void);
|
||||||
int compensate(int pm25, float humidity);
|
float compensate(float pm25, float humidity);
|
||||||
int getFirmwareVersion(void);
|
int getFirmwareVersion(void);
|
||||||
uint8_t getErrorCode(void);
|
uint8_t getErrorCode(void);
|
||||||
bool connected(void);
|
bool connected(void);
|
||||||
|
Reference in New Issue
Block a user