Compare commits
368 Commits
3.1.5
...
fix/api-ro
Author | SHA1 | Date | |
---|---|---|---|
4daa817a0b | |||
81a4502952 | |||
764e2eae38 | |||
79bf9811be | |||
9475724d0c | |||
e7603a7659 | |||
9bba89722e | |||
81945a358e | |||
3d26a54d69 | |||
b70ee75d50 | |||
c6846c818a | |||
0b1c901a76 | |||
83504c8628 | |||
4487992748 | |||
3c8a65a329 | |||
673d564ddb | |||
423eb4808f | |||
18a710ffc2 | |||
040cb79a4d | |||
52d3dc03f1 | |||
1c6bc3ec55 | |||
34d7c93e14 | |||
fee1dc25d6 | |||
9fb01d42f4 | |||
7bb013939c | |||
0da21155e7 | |||
7a153cc0ea | |||
b079c35e6b | |||
6051e183b8 | |||
c95379b957 | |||
0cae8bc185 | |||
5902a4c8e4 | |||
66818cd075 | |||
c1a6ddc68f | |||
20a32dd22c | |||
263dc9934e | |||
61b863b7f1 | |||
e01c1029fe | |||
ba5d817739 | |||
a91747e379 | |||
029457c3fa | |||
55710dd4d9 | |||
4886163cda | |||
7c57477238 | |||
9ed58d1853 | |||
6c52b038e9 | |||
2f69932ef7 | |||
1d96a274a6 | |||
df9f6dfc95 | |||
3fc02b3f54 | |||
958ed0bd80 | |||
e9be9dcc83 | |||
7fbab82088 | |||
decdecdf22 | |||
145c612867 | |||
37de127887 | |||
baf80ce250 | |||
80100e2475 | |||
d9c3fc6ec4 | |||
67d377a514 | |||
fff982f35f | |||
86cd90b94a | |||
656509c74d | |||
01f83cb02e | |||
5c9c25c6b5 | |||
9291598209 | |||
429adb5e5e | |||
4e651afc8c | |||
859abbe177 | |||
f079bb30d2 | |||
070a103234 | |||
ef87cde9d6 | |||
ea5e23b307 | |||
c2a26e78a0 | |||
0297059e91 | |||
30622fca99 | |||
7c2aa35e4f | |||
e93009f31c | |||
26db6372cd | |||
d94ebbc570 | |||
299234ac40 | |||
76b2b3f940 | |||
bf09b746c7 | |||
b5c67cb0b1 | |||
5f40a327b3 | |||
66b0c63de5 | |||
cc3228f49a | |||
8728589ca1 | |||
4b356920c2 | |||
c94b886360 | |||
e056e44917 | |||
3b00fa69b8 | |||
e0720ac580 | |||
0861c2dcaa | |||
59fc0c409b | |||
6d63fdf643 | |||
033358e2c2 | |||
47034f62b4 | |||
71a21ce7e6 | |||
3f5e5eebbb | |||
f9be400a5d | |||
54808ac076 | |||
063bb2a227 | |||
93f79173b2 | |||
615c2389e7 | |||
f972637cca | |||
4c7e72b8e7 | |||
d4b4f51c3c | |||
1c42ff083d | |||
17d2e62b15 | |||
38aebeb50a | |||
b0f5263829 | |||
3226c14b6d | |||
0e26aa1b5d | |||
830f652bf9 | |||
bd9dbec663 | |||
29d701780a | |||
15869be234 | |||
4b09b98524 | |||
afd498074b | |||
e851d0781c | |||
2c27c6904c | |||
03f1b969c2 | |||
85ba13de12 | |||
6ec545b00e | |||
05dbe60db2 | |||
d2ee3a5d24 | |||
1839664137 | |||
154f3ecf8a | |||
b75e40b800 | |||
84a358291b | |||
0e41b2d630 | |||
3e48a562e7 | |||
f0c4df42b7 | |||
a50e1e2472 | |||
c8f0e6a0d2 | |||
1537d5d480 | |||
32c78f6018 | |||
c5c0dae4bb | |||
4bb97fc8be | |||
a28931493a | |||
af16c1c060 | |||
1666923ab3 | |||
89475ddf95 | |||
20db9d699b | |||
88c2437907 | |||
e9b27185b4 | |||
4534f7319a | |||
c842346724 | |||
92e74feabd | |||
cc0fd88068 | |||
56809a412c | |||
6a83743e2a | |||
faaf051e39 | |||
5bc1821ef9 | |||
280ea5e997 | |||
e95627ece6 | |||
80b9ae11d8 | |||
1937e3d59e | |||
107fb21331 | |||
ccc1ab463a | |||
38e792b88d | |||
aeee0cad01 | |||
401326d00d | |||
fb0dcad54d | |||
3556e4a96a | |||
283646a699 | |||
6312612ada | |||
c1f22674e2 | |||
40d38a75d8 | |||
39ef69cbdf | |||
3473e30e2e | |||
566f8a63b4 | |||
9e4d52454b | |||
5f5e985309 | |||
d638573ca7 | |||
4c165b31f5 | |||
2be91b3968 | |||
3ca2d1d208 | |||
aad12fc868 | |||
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 | |||
c841476ca4 | |||
359394af53 | |||
b8e10f473e | |||
cb511903ef | |||
ebb3f01dcd | |||
2e0ba26c97 | |||
c1a4758c6c | |||
0370a8aa15 | |||
863a37132a | |||
612317d976 | |||
8873bacf55 | |||
bf2388b121 | |||
b3918bd1fb | |||
2a6fce674e | |||
2f0663ced0 | |||
3adf58537a | |||
e10c9ff854 | |||
12c6ec9910 | |||
d108b63a57 | |||
6e212714fc | |||
866684eb30 | |||
9d01479406 | |||
20245f2110 | |||
3890919f54 | |||
76e40fea8c | |||
c4024f49fb | |||
ca5fc8d65b | |||
fd2cef153e | |||
507b958fdf | |||
335c29ebb1 | |||
2907d6f14e | |||
c8d5b546ed | |||
b7cfdc4c4d | |||
994d281e02 | |||
39470384e4 | |||
c25ba764bf | |||
826ff00f42 | |||
520550037d | |||
90f336dee7 | |||
0d39643e76 | |||
21232ec49d | |||
b7339de31f | |||
013fb94307 | |||
e16373a64d | |||
f929623443 | |||
59587ce2b7 | |||
9ec74450a5 | |||
28096e9faf | |||
682378a47c | |||
a1861be7b7 | |||
99ddd24432 | |||
29491e4cbe | |||
87cc3fc45f | |||
7471d8079a | |||
8b0fe967f1 | |||
6f1cef4e67 | |||
02b63ff816 | |||
228bf83e92 | |||
d3534cda52 | |||
aafaa42a68 | |||
2e9ff0d7dd | |||
244b7814a6 | |||
28d27ee8fd | |||
753f22923c | |||
c45901706f | |||
663836e277 | |||
d39e10908d | |||
c52962d628 | |||
6b65efd3d6 | |||
8bb87a75ef | |||
1afcca25a1 | |||
17238cff86 | |||
03e2afbf54 | |||
104d58a8c0 | |||
7a988ea6c1 | |||
54ed83cb89 | |||
e461b92c9f | |||
db21648e91 | |||
a9654506f5 | |||
63f653d5cd | |||
b049a23657 | |||
d6766ef68b | |||
6c3259b94b | |||
2df78e9066 | |||
81b13134d2 | |||
6d01366887 | |||
040bd28038 | |||
01943f594d | |||
01a69668cc | |||
812c2ab803 |
53
.github/workflows/check.yml
vendored
@ -17,11 +17,14 @@ jobs:
|
|||||||
- "esp32:esp32:esp32c3"
|
- "esp32:esp32:esp32c3"
|
||||||
include:
|
include:
|
||||||
- fqbn: "esp8266:esp8266:d1_mini"
|
- fqbn: "esp8266:esp8266:d1_mini"
|
||||||
core: "esp8266:esp8266@3.1.2"
|
core: "esp8266:esp8266"
|
||||||
|
core_version: "3.1.2"
|
||||||
core_url: "https://arduino.esp8266.com/stable/package_esp8266com_index.json"
|
core_url: "https://arduino.esp8266.com/stable/package_esp8266com_index.json"
|
||||||
- fqbn: "esp32:esp32:esp32c3"
|
- fqbn: "esp32:esp32:esp32c3"
|
||||||
|
core: "esp32:esp32"
|
||||||
|
core_version: "2.0.17"
|
||||||
|
core_url: "https://espressif.github.io/arduino-esp32/package_esp32_index.json"
|
||||||
board_options: "JTAGAdapter=default,CDCOnBoot=cdc,PartitionScheme=min_spiffs,CPUFreq=160,FlashMode=qio,FlashFreq=80,FlashSize=4M,UploadSpeed=921600,DebugLevel=verbose,EraseFlash=none"
|
board_options: "JTAGAdapter=default,CDCOnBoot=cdc,PartitionScheme=min_spiffs,CPUFreq=160,FlashMode=qio,FlashFreq=80,FlashSize=4M,UploadSpeed=921600,DebugLevel=verbose,EraseFlash=none"
|
||||||
core: "esp32:esp32@2.0.11"
|
|
||||||
exclude:
|
exclude:
|
||||||
- example: "BASIC"
|
- example: "BASIC"
|
||||||
fqbn: "esp32:esp32:esp32c3"
|
fqbn: "esp32:esp32:esp32c3"
|
||||||
@ -31,30 +34,30 @@ jobs:
|
|||||||
fqbn: "esp32:esp32:esp32c3"
|
fqbn: "esp32:esp32:esp32c3"
|
||||||
- example: "OneOpenAir"
|
- example: "OneOpenAir"
|
||||||
fqbn: "esp8266:esp8266:d1_mini"
|
fqbn: "esp8266:esp8266:d1_mini"
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-24.04
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4.2.2
|
||||||
- run:
|
with:
|
||||||
curl -fsSL https://raw.githubusercontent.com/arduino/arduino-cli/master/install.sh |
|
fetch-depth: 0
|
||||||
sh -s 0.35.3
|
submodules: 'true'
|
||||||
- run: bin/arduino-cli --verbose core install '${{ matrix.core }}'
|
- uses: arduino/compile-sketches@v1.1.2
|
||||||
--additional-urls '${{ matrix.core_url }}'
|
with:
|
||||||
- run: bin/arduino-cli --verbose lib install
|
fqbn: ${{ matrix.fqbn }}
|
||||||
WiFiManager@2.0.16-rc.2
|
sketch-paths: |
|
||||||
Arduino_JSON@0.2.0
|
examples/${{ matrix.example }}
|
||||||
U8g2@2.34.22
|
libraries: |
|
||||||
# In some cases, actions/checkout@v4 will check out a detached HEAD; for
|
- source-path: ./
|
||||||
# example, this happens on pull request events, where an hypothetical
|
cli-compile-flags: |
|
||||||
# PR merge commit is checked out. This tends to confuse
|
- --warnings
|
||||||
# `arduino-cli lib install --git-url`, making it fail with errors such as:
|
- none
|
||||||
# Error installing Git Library: Library install failed: object not found
|
- --board-options
|
||||||
# Create and check out a dummy branch to work around this issue.
|
- "${{ matrix.board_options }}"
|
||||||
- run: git checkout -b check
|
platforms: |
|
||||||
- run: bin/arduino-cli --verbose lib install --git-url .
|
- name: ${{ matrix.core }}
|
||||||
env:
|
version: ${{ matrix.core_version}}
|
||||||
ARDUINO_LIBRARY_ENABLE_UNSAFE_INSTALL: "true"
|
source-url: ${{ matrix.core_url }}
|
||||||
- run: bin/arduino-cli --verbose compile 'examples/${{ matrix.example }}'
|
enable-deltas-report: true
|
||||||
--fqbn '${{ matrix.fqbn }}' --board-options '${{ matrix.board_options }}'
|
|
||||||
# TODO: at this point it would be a good idea to run some smoke tests on
|
# TODO: at this point it would be a good idea to run some smoke tests on
|
||||||
# the resulting image (e.g. that it boots successfully and sends metrics)
|
# the resulting image (e.g. that it boots successfully and sends metrics)
|
||||||
# but that would either require a high fidelity device emulator, or a
|
# but that would either require a high fidelity device emulator, or a
|
||||||
|
5
.gitignore
vendored
@ -3,3 +3,8 @@ build
|
|||||||
.vscode
|
.vscode
|
||||||
/.idea/
|
/.idea/
|
||||||
.pio
|
.pio
|
||||||
|
.cache
|
||||||
|
.clangd
|
||||||
|
logs
|
||||||
|
gen_compile_commands.py
|
||||||
|
compile_commands.json
|
||||||
|
6
.gitmodules
vendored
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
[submodule "src/Libraries/airgradient-client"]
|
||||||
|
path = src/Libraries/airgradient-client
|
||||||
|
url = git@github.com:airgradienthq/airgradient-client.git
|
||||||
|
[submodule "src/Libraries/airgradient-ota"]
|
||||||
|
path = src/Libraries/airgradient-ota
|
||||||
|
url = git@github.com:airgradienthq/airgradient-ota.git
|
105
docs/howto-compile.md
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
# How to compile AirGradient firmware on Arduino IDE
|
||||||
|
|
||||||
|
## Prequisite
|
||||||
|
|
||||||
|
Arduino IDE version 2.x ([download](https://www.arduino.cc/en/software))
|
||||||
|
|
||||||
|
> For AirGradient model ONE and Open Air, the codebase **WILL NOT** work on the latest major version of arduino-esp32 which is *3.x* . This related to when installing "esp32 by Espressif Systems" in board manager. Instead use version **2.0.17**, please follow the first step carefully.
|
||||||
|
|
||||||
|
## Steps for ESP32C3 based board (ONE and Open Air Model)
|
||||||
|
|
||||||
|
1. Install "esp32 by Espressif Systems" in board manager with version **2.0.17** (Tools ➝ Board ➝ Boards Manager ➝ search for `"espressif"`)
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
2. Install AirGradient library
|
||||||
|
|
||||||
|
#### Version < 3.2.0
|
||||||
|
|
||||||
|
Using library manager install the latest version (Tools ➝ Manage Libraries... ➝ search for `"airgradient"`)
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
#### Version >= 3.3.0
|
||||||
|
|
||||||
|
- From your terminal, go to Arduino libraries folder (windows and mac: `Documents/Arduino/libraries` or linux: `~/Arduino/Libraries`).
|
||||||
|
- With **git** cli, execute this command `git clone --recursive https://github.com/airgradienthq/arduino.git AirGradient_Air_Quality_Sensor`
|
||||||
|
- Restart Arduino IDE
|
||||||
|
|
||||||
|
3. On tools tab, follow settings below
|
||||||
|
|
||||||
|
```
|
||||||
|
Board ➝ ESP32C3 Dev Module
|
||||||
|
USB CDC On Boot ➝ Enabled
|
||||||
|
CPU Frequency ➝ 160MHz (WiFi)
|
||||||
|
Core Debug Level ➝ Info
|
||||||
|
Erase All Flash Before Sketch Upload ➝ Enabled (or choose as needed)
|
||||||
|
Flash Frequency ➝ 80MHz
|
||||||
|
Flash Mode ➝ QIO
|
||||||
|
Flash Size ➝ 4MB (32Mb)
|
||||||
|
JTAG Adapter ➝ Disabled
|
||||||
|
Partition Scheme ➝ Minimal SPIFFS (1.9MB APP with OTA/190KB SPIFFS)
|
||||||
|
Upload Speed ➝ 921600
|
||||||
|
```
|
||||||
|
|
||||||
|
4. Open sketch to compile (File ➝ Examples ➝ AirGradient Air Quality Sensor ➝ OneOpenAir). This sketch for AirGradient ONE and Open Air monitor model
|
||||||
|
5. Compile
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## Steps for ESP8266 based board (DIY model)
|
||||||
|
|
||||||
|
1. Add esp8266 board by adding http://arduino.esp8266.com/stable/package_esp8266com_index.json into Additional Board Manager URLs field (File ➝ Preferences ➝ Additional boards manager URLs)
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
2. Install esp8266 board on board manager with version **3.1.2** (Tools ➝ Board ➝ Boards Manager ➝ search for `"esp8266"`)
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
3. Install AirGradient library on library manager using the latest version (Tools ➝ Manage Libraries... ➝ search for `"airgradient"`)
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
4. On tools tab, set board to `LOLIN(WEMOS) D1 R2 & mini`, and let other settings to default
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
5. Open sketch to compile (File ➝ Examples ➝ AirGradient Air Quality Sensor ➝ `<Model Option>`). Depends on the DIY model, either `BASIC`, `DiyProIndoorV3_3` and `DiyProIndoorV4_2`
|
||||||
|
6. Compile
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## Possible Issues
|
||||||
|
|
||||||
|
### Linux (Debian)
|
||||||
|
|
||||||
|
ModuleNotFoundError: No module named ‘serial’
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Make sure python pyserial module installed globally in the environment by executing:
|
||||||
|
|
||||||
|
`$ sudo apt install -y python3-pyserial`
|
||||||
|
|
||||||
|
or
|
||||||
|
|
||||||
|
`$ pip install pyserial`
|
||||||
|
|
||||||
|
Choose based on how python installed on your machine. But most user, using `apt` is better.
|
||||||
|
|
||||||
|
## How to contribute
|
||||||
|
|
||||||
|
The instructions above are the instructions for how to build an official release of the AirGradient firmware using the Arduino IDE. If you intend to make changes that will you intent to contribute back to the main project, instead of installing the AirGradient library, check out the repo at `Documents/Arduino/libraries` (for Windows and Mac), or `~/Arduino/Libraries` (Linux). If you installed the library, you can remove it from the library manager in the Arduino IDE, or just delete the directory.
|
||||||
|
|
||||||
|
**NOTE:** When cloning the repository, for version >= 3.3.0 it has submodule, please use `--recursive` flag like this: `git clone --recursive https://github.com/airgradienthq/arduino.git AirGradient_Air_Quality_Sensor`
|
||||||
|
|
||||||
|
Please follow github [contributing to a project](https://docs.github.com/en/get-started/exploring-projects-on-github/contributing-to-a-project) tutorial to contribute to this project.
|
||||||
|
|
||||||
|
There are 2 environment options to compile this project, PlatformIO and ArduinoIDE.
|
||||||
|
|
||||||
|
- For PlatformIO, it should work out of the box
|
||||||
|
- For arduino, files in `src` folder and also from `Examples` can be modified at `Documents/Arduino/libraries` for Windows and Mac, and `~/Arduino/Libraries` for Linux
|
||||||
|
|
||||||
|
|
||||||
|
|
BIN
docs/images/additional-board.png
Normal file
After Width: | Height: | Size: 50 KiB |
BIN
docs/images/ag-lib.png
Normal file
After Width: | Height: | Size: 108 KiB |
BIN
docs/images/compiled-esp8266.png
Normal file
After Width: | Height: | Size: 148 KiB |
BIN
docs/images/compiled.png
Normal file
After Width: | Height: | Size: 131 KiB |
BIN
docs/images/esp32-board.png
Normal file
After Width: | Height: | Size: 62 KiB |
BIN
docs/images/esp8266-board.png
Normal file
After Width: | Height: | Size: 61 KiB |
BIN
docs/images/linux-failed.png
Normal file
After Width: | Height: | Size: 73 KiB |
BIN
docs/images/settings-esp8266.png
Normal file
After Width: | Height: | Size: 104 KiB |
BIN
docs/images/settings.png
Normal file
After Width: | Height: | Size: 106 KiB |
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
From [firmware version 3.0.10](firmwares) onwards, the AirGradient ONE and Open Air monitors have below API available.
|
From [firmware version 3.0.10](firmwares) onwards, the AirGradient ONE and Open Air monitors have below API available.
|
||||||
|
|
||||||
#### Discovery
|
### Discovery
|
||||||
|
|
||||||
The monitors run a mDNS discovery. So within the same network, the monitor can be accessed through:
|
The monitors run a mDNS discovery. So within the same network, the monitor can be accessed through:
|
||||||
|
|
||||||
@ -11,7 +11,7 @@ http://airgradient_{{serialnumber}}.local
|
|||||||
|
|
||||||
The following requests are possible:
|
The following requests are possible:
|
||||||
|
|
||||||
#### Get Current Air Quality (GET)
|
### Get Current Air Quality (GET)
|
||||||
|
|
||||||
With the path "/measures/current" you can get the current air quality data.
|
With the path "/measures/current" you can get the current air quality data.
|
||||||
|
|
||||||
@ -41,20 +41,29 @@ You get the following response:
|
|||||||
"bootCount": 6,
|
"bootCount": 6,
|
||||||
"ledMode": "pm",
|
"ledMode": "pm",
|
||||||
"firmware": "3.1.3",
|
"firmware": "3.1.3",
|
||||||
"model": "I-9PSL"
|
"model": "I-9PSL",
|
||||||
|
"monitorDisplayCompensatedValues": true
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
| Properties | Type | Explanation |
|
| Properties | Type | Explanation |
|
||||||
|------------------|--------|--------------------------------------------------------------------|
|
|-----------------------------------|---------|----------------------------------------------------------------------------------------|
|
||||||
| `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 |
|
||||||
@ -64,67 +73,187 @@ 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 |
|
||||||
|
|
||||||
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": "US",
|
"country": "TH",
|
||||||
"pmStandard": "ugm3",
|
"pmStandard": "ugm3",
|
||||||
"ledBarMode": "pm",
|
"ledBarMode": "pm",
|
||||||
"displayMode": "on",
|
"abcDays": 7,
|
||||||
"abcDays": 30,
|
|
||||||
"tvocLearningOffset": 12,
|
"tvocLearningOffset": 12,
|
||||||
"noxLearningOffset": 12,
|
"noxLearningOffset": 12,
|
||||||
"mqttBrokerUrl": "",
|
"mqttBrokerUrl": "",
|
||||||
"temperatureUnit": "f",
|
"httpDomain": "",
|
||||||
"configurationControl": "both",
|
"temperatureUnit": "c",
|
||||||
"postDataToAirGradient": true
|
"configurationControl": "local",
|
||||||
|
"postDataToAirGradient": true,
|
||||||
|
"ledBarBrightness": 100,
|
||||||
|
"displayBrightness": 100,
|
||||||
|
"offlineMode": false,
|
||||||
|
"model": "I-9PSL",
|
||||||
|
"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.
|
|
||||||
|
|
||||||
#### Configuration Parameters (GET/PUT)
|
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)
|
||||||
|
|
||||||
| Properties | Description | Type | Accepted Values | Example |
|
| Properties | Description | Type | Accepted Values | Example |
|
||||||
|-------------------------|:-------------------------------------------------------|---------|-----------------------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------|
|
|-----------------------------------|:-----------------------------------------------------------------|---------|-----------------------------------------------------------------------------------------------------------------------------------------|-------------------------------------------------|
|
||||||
| `country` | Country where the device is. | String | Country code as [ALPHA-2 notation](https://www.iban.com/country-codes) | {"country": "TH"} |
|
| `country` | Country where the device is. | String | Country code as [ALPHA-2 notation](https://www.iban.com/country-codes) | `{"country": "TH"}` |
|
||||||
| `model` | Hardware identifier (only GET). | String | I-9PSL-DE | {"model": "I-9PSL-DE"} |
|
| `model` | Hardware identifier (only GET). | String | I-9PSL-DE | `{"model": "I-9PSL-DE"}` |
|
||||||
| `pmStandard` | Particle matter standard used on the display. | String | `ugm3`: ug/m3 <br> `us-aqi`: USAQI | {"pmStandard": "ugm3"} |
|
| `pmStandard` | Particle matter standard used on the display. | String | `ugm3`: ug/m3 <br> `us-aqi`: USAQI | `{"pmStandard": "ugm3"}` |
|
||||||
| `ledBarMode` | Mode in which the led bar can be set. | String | `co2`: LED bar displays CO2 <br>`pm`: LED bar displays PM <br>`off`: Turn off LED bar | {"ledBarMode": "off"} |
|
| `ledBarMode` | Mode in which the led bar can be set. | String | `co2`: LED bar displays CO2 <br>`pm`: LED bar displays PM <br>`off`: Turn off LED bar | `{"ledBarMode": "off"}` |
|
||||||
| `displayBrightness` | Brightness of the Display. | Number | 0-100 | {"displayBrightness": 50} |
|
| `displayBrightness` | Brightness of the Display. | Number | 0-100 | `{"displayBrightness": 50}` |
|
||||||
| `ledBarBrightness` | Brightness of the LEDBar. | Number | 0-100 | {"ledBarBrightness": 40} |
|
| `ledBarBrightness` | Brightness of the LEDBar. | Number | 0-100 | `{"ledBarBrightness": 40}` |
|
||||||
| `abcDays` | Number of days for CO2 automatic baseline calibration. | Number | Maximum 200 days. Default 8 days. | {"abcDays": 8} |
|
| `abcDays` | Number of days for CO2 automatic baseline calibration. | Number | Maximum 200 days. Default 8 days. | `{"abcDays": 8}` |
|
||||||
| `mqttBrokerUrl` | MQTT broker URL. | String | | {"mqttBrokerUrl": "mqtt://192.168.0.18:1883"} |
|
| `mqttBrokerUrl` | MQTT broker URL. | String | Maximum 255 characters. Set value to empty string to disable mqtt connection. | `{"mqttBrokerUrl": "mqtt://192.168.0.18:1883"}` |
|
||||||
| `temperatureUnit` | Temperature unit shown on the display. | String | `c` or `C`: Degree Celsius °C <br>`f` or `F`: Degree Fahrenheit °F | {"temperatureUnit": "c"} |
|
| `httpDomain` | Domain name for http request. (version > 3.3.2) | String | Maximum 255 characters. Set value to empty string to set http domain to default airgradient | `{"httpDomain": "sub.domain.com"}` |
|
||||||
| `configurationControl` | The configuration source of the device. | String | `both`: Accept local and cloud configuration <br>`local`: Accept only local configuration <br>`cloud`: Accept only cloud configuration | {"configurationControl": "both"} |
|
| `temperatureUnit` | Temperature unit shown on the display. | String | `c` or `C`: Degree Celsius °C <br>`f` or `F`: Degree Fahrenheit °F | `{"temperatureUnit": "c"}` |
|
||||||
| `postDataToAirGradient` | Send data to AirGradient cloud. | Boolean | `true`: Enabled <br>`false`: Disabled | {"postDataToAirGradient": true} |
|
| `configurationControl` | The configuration source of the device. | String | `both`: Accept local and cloud configuration <br>`local`: Accept only local configuration <br>`cloud`: Accept only cloud configuration | `{"configurationControl": "both"}` |
|
||||||
| `co2CalibrationRequested` | Can be set to trigger a calibration. | Boolean | `true`: CO2 calibration (400ppm) will be triggered | {"co2CalibrationRequested": true} |
|
| `postDataToAirGradient` | Send data to AirGradient cloud. | Boolean | `true`: Enabled <br>`false`: Disabled | `{"postDataToAirGradient": true}` |
|
||||||
| `ledBarTestRequested` | Can be set to trigger a test. | Boolean | `true` : LEDs will run test sequence | {"ledBarTestRequested": true} |
|
| `co2CalibrationRequested` | Can be set to trigger a calibration. | Boolean | `true`: CO2 calibration (400ppm) will be triggered | `{"co2CalibrationRequested": true}` |
|
||||||
| `noxLearningOffset` | Set NOx learning gain offset. | Number | 0-720 (default 12) | {"noxLearningOffset": 12} |
|
| `ledBarTestRequested` | Can be set to trigger a test. | Boolean | `true` : LEDs will run test sequence | `{"ledBarTestRequested": true}` |
|
||||||
| `tvocLearningOffset` | Set VOC learning gain offset. | Number | 0-720 (default 12) | {"tvocLearningOffset": 12} |
|
| `noxLearningOffset` | Set NOx learning gain offset. | Number | 0-720 (default 12) | `{"noxLearningOffset": 12}` |
|
||||||
| `offlineMode` | Set monitor to run without WiFi. | Boolean | `false`: Disabled (default) <br> `true`: Enabled | {"offlineMode": true} |
|
| `tvocLearningOffset` | Set VOC learning gain offset. | Number | 0-720 (default 12) | `{"tvocLearningOffset": 12}` |
|
||||||
|
| `monitorDisplayCompensatedValues` | Set the display show the PM value with/without compensate value (only on 3.1.9) | Boolean | `false`: Without compensate (default) <br> `true`: with compensate | `{"monitorDisplayCompensatedValues": false }` |
|
||||||
|
| `corrections` | Sets correction options to display and measurement values on local server response. (version >= 3.1.11) | Object | _see corrections section_ | _see corrections section_ |
|
||||||
|
|
||||||
|
|
||||||
|
**Notes**
|
||||||
|
|
||||||
|
- `offlineMode` : the device will disable all network operation, and only show measurements on the display and ledbar; Read-Only; Change can be apply using reset button on boot.
|
||||||
|
- `disableCloudConnection` : disable every request to AirGradient server, means features like post data to AirGradient server, configuration from AirGradient server and automatic firmware updates are disabled. This configuration overrides `configurationControl` and `postDataToAirGradient`; Read-Only; Change can be apply from wifi setup webpage.
|
||||||
|
|
||||||
|
### Corrections
|
||||||
|
|
||||||
|
The `corrections` object allows configuring PM2.5, Temperature and Humidity correction algorithms and parameters locally. This affects both the display, local server response and open metrics values.
|
||||||
|
|
||||||
|
Example correction configuration:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"corrections": {
|
||||||
|
"pm02": {
|
||||||
|
"correctionAlgorithm": "<Option In String>",
|
||||||
|
"slr": {
|
||||||
|
"intercept": 0,
|
||||||
|
"scalingFactor": 0,
|
||||||
|
"useEpa2021": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"atmp": {
|
||||||
|
"correctionAlgorithm": "<Option In String>",
|
||||||
|
"slr": {
|
||||||
|
"intercept": 0,
|
||||||
|
"scalingFactor": 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"rhum": {
|
||||||
|
"correctionAlgorithm": "<Option In String>",
|
||||||
|
"slr": {
|
||||||
|
"intercept": 0,
|
||||||
|
"scalingFactor": 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### PM 2.5
|
||||||
|
|
||||||
|
Field Name: `pm02`
|
||||||
|
|
||||||
|
| Algorithm | Value | Description | SLR required |
|
||||||
|
|------------|-------------|------|---------|
|
||||||
|
| Raw | `"none"` | No correction (default) | No |
|
||||||
|
| 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}}}}'
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Temperature & Humidity
|
||||||
|
|
||||||
|
Field Name:
|
||||||
|
- Temperature: `atmp`
|
||||||
|
- Humidity: `rhum`
|
||||||
|
|
||||||
|
| Algorithm | Value | Description | SLR required |
|
||||||
|
|------------|-------------|------|---------|
|
||||||
|
| Raw | `"none"` | No correction (default) | No |
|
||||||
|
| AirGradient Standard Correction | `"ag_pms5003t_2024"` | Using standard airgradient correction (for outdoor monitor)| No |
|
||||||
|
| Custom | `"custom"` | custom corrections constant, set `intercept` and `scalingFactor` manually | Yes |
|
||||||
|
|
||||||
|
*Table above apply for both Temperature and Humidity*
|
||||||
|
|
||||||
|
**Example**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl --location -X PUT 'http://airgradient_84fce612eff4.local/config' --header 'Content-Type: application/json' --data '{"corrections":{"atmp":{"correctionAlgorithm":"custom","slr":{"intercept":0.2,"scalingFactor":1.1}}}}'
|
||||||
|
```
|
@ -12,10 +12,8 @@ Outdoor Monitor: https://www.airgradient.com/outdoor/
|
|||||||
Build Instructions:
|
Build Instructions:
|
||||||
https://www.airgradient.com/documentation/diy-v4/
|
https://www.airgradient.com/documentation/diy-v4/
|
||||||
|
|
||||||
Please make sure you have esp8266 board manager installed. Tested with
|
Compile Instructions:
|
||||||
version 3.1.2.
|
https://github.com/airgradienthq/arduino/blob/master/docs/howto-compile.md
|
||||||
|
|
||||||
Set board to "LOLIN(WEMOS) D1 R2 & mini"
|
|
||||||
|
|
||||||
Configuration parameters, e.g. Celsius / Fahrenheit or PM unit (US AQI vs ug/m3)
|
Configuration parameters, e.g. Celsius / Fahrenheit or PM unit (US AQI vs ug/m3)
|
||||||
can be set through the AirGradient dashboard.
|
can be set through the AirGradient dashboard.
|
||||||
@ -49,14 +47,13 @@ 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);
|
||||||
static AgApiClient apiClient(Serial, configuration);
|
static AgApiClient apiClient(Serial, configuration);
|
||||||
static Measurements measurements;
|
static Measurements measurements(configuration);
|
||||||
static OledDisplay oledDisplay(configuration, measurements, Serial);
|
static OledDisplay oledDisplay(configuration, measurements, Serial);
|
||||||
static StateMachine stateMachine(oledDisplay, Serial, measurements,
|
static StateMachine stateMachine(oledDisplay, Serial, measurements,
|
||||||
configuration);
|
configuration);
|
||||||
@ -68,8 +65,6 @@ static LocalServer localServer(Serial, openMetrics, measurements, configuration,
|
|||||||
wifiConnector);
|
wifiConnector);
|
||||||
static MqttClient mqttClient(Serial);
|
static MqttClient mqttClient(Serial);
|
||||||
|
|
||||||
static int pmFailCount = 0;
|
|
||||||
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;
|
||||||
@ -91,6 +86,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,
|
||||||
@ -125,12 +122,17 @@ void setup() {
|
|||||||
apiClient.setAirGradient(&ag);
|
apiClient.setAirGradient(&ag);
|
||||||
openMetrics.setAirGradient(&ag);
|
openMetrics.setAirGradient(&ag);
|
||||||
localServer.setAirGraident(&ag);
|
localServer.setAirGraident(&ag);
|
||||||
|
measurements.setAirGradient(&ag);
|
||||||
|
|
||||||
/** Example set custom API root URL */
|
/** Example set custom API root URL */
|
||||||
// apiClient.setApiRoot("https://example.custom.api");
|
// apiClient.setApiRoot("https://example.custom.api");
|
||||||
|
|
||||||
/** 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;
|
||||||
@ -146,9 +148,12 @@ void setup() {
|
|||||||
initMqtt();
|
initMqtt();
|
||||||
sendDataToAg();
|
sendDataToAg();
|
||||||
|
|
||||||
|
if (configuration.getConfigurationControl() !=
|
||||||
|
ConfigurationControl::ConfigurationControlLocal) {
|
||||||
apiClient.fetchServerConfiguration();
|
apiClient.fetchServerConfiguration();
|
||||||
|
}
|
||||||
configSchedule.update();
|
configSchedule.update();
|
||||||
if (apiClient.isFetchConfigureFailed()) {
|
if (apiClient.isFetchConfigurationFailed()) {
|
||||||
if (apiClient.isNotAvailableOnDashboard()) {
|
if (apiClient.isNotAvailableOnDashboard()) {
|
||||||
stateMachine.displaySetAddToDashBoard();
|
stateMachine.displaySetAddToDashBoard();
|
||||||
stateMachine.displayHandle(
|
stateMachine.displayHandle(
|
||||||
@ -207,11 +212,7 @@ void loop() {
|
|||||||
tvocSchedule.run();
|
tvocSchedule.run();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Auto reset watchdog timer if offline mode or postDataToAirGradient */
|
|
||||||
if (configuration.isOfflineMode() ||
|
|
||||||
(configuration.isPostDataToAirGradient() == false)) {
|
|
||||||
watchdogFeedSchedule.run();
|
watchdogFeedSchedule.run();
|
||||||
}
|
|
||||||
|
|
||||||
/** Check for handle WiFi reconnect */
|
/** Check for handle WiFi reconnect */
|
||||||
wifiConnector.handle();
|
wifiConnector.handle();
|
||||||
@ -235,17 +236,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();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -267,18 +267,23 @@ static void mdnsInit(void) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void initMqtt(void) {
|
static void initMqtt(void) {
|
||||||
if (mqttClient.begin(configuration.getMqttBrokerUri())) {
|
String mqttUri = configuration.getMqttBrokerUri();
|
||||||
Serial.println("Setup connect to MQTT broker successful");
|
if (mqttUri.isEmpty()) {
|
||||||
|
Serial.println(
|
||||||
|
"MQTT is not configured, skipping initialization of MQTT client");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mqttClient.begin(mqttUri)) {
|
||||||
|
Serial.println("Successfully connected to MQTT broker");
|
||||||
} else {
|
} else {
|
||||||
Serial.println("setup Connect to MQTT broker failed");
|
Serial.println("Connection to MQTT broker failed");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void wdgFeedUpdate(void) {
|
static void wdgFeedUpdate(void) {
|
||||||
ag.watchdog.reset();
|
ag.watchdog.reset();
|
||||||
Serial.println();
|
Serial.println("External watchdog feed!");
|
||||||
Serial.println("Offline mode or isPostToAirGradient = false: watchdog reset");
|
|
||||||
Serial.println();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool sgp41Init(void) {
|
static bool sgp41Init(void) {
|
||||||
@ -313,8 +318,7 @@ static void mqttHandle(void) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (mqttClient.isConnected()) {
|
if (mqttClient.isConnected()) {
|
||||||
String payload = measurements.toString(true, fwMode, wifiConnector.RSSI(),
|
String payload = measurements.toString(true, fwMode, wifiConnector.RSSI());
|
||||||
&ag, &configuration);
|
|
||||||
String topic = "airgradient/readings/" + ag.deviceId();
|
String topic = "airgradient/readings/" + ag.deviceId();
|
||||||
if (mqttClient.publish(topic.c_str(), payload.c_str(), payload.length())) {
|
if (mqttClient.publish(topic.c_str(), payload.c_str(), payload.length())) {
|
||||||
Serial.println("MQTT sync success");
|
Serial.println("MQTT sync success");
|
||||||
@ -329,7 +333,7 @@ static void sendDataToAg() {
|
|||||||
stateMachine.displayHandle(AgStateMachineWiFiOkServerConnecting);
|
stateMachine.displayHandle(AgStateMachineWiFiOkServerConnecting);
|
||||||
|
|
||||||
delay(1500);
|
delay(1500);
|
||||||
if (apiClient.sendPing(wifiConnector.RSSI(), measurements.bootCount)) {
|
if (apiClient.sendPing(wifiConnector.RSSI(), measurements.bootCount())) {
|
||||||
stateMachine.displayHandle(AgStateMachineWiFiOkServerConnected);
|
stateMachine.displayHandle(AgStateMachineWiFiOkServerConnected);
|
||||||
} else {
|
} else {
|
||||||
stateMachine.displayHandle(AgStateMachineWiFiOkServerConnectFailed);
|
stateMachine.displayHandle(AgStateMachineWiFiOkServerConnectFailed);
|
||||||
@ -413,6 +417,14 @@ static void failedHandler(String msg) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void configurationUpdateSchedule(void) {
|
static void configurationUpdateSchedule(void) {
|
||||||
|
if (configuration.isOfflineMode() ||
|
||||||
|
configuration.getConfigurationControl() == ConfigurationControl::ConfigurationControlLocal) {
|
||||||
|
Serial.println("Ignore fetch server configuration. Either mode is offline "
|
||||||
|
"or configurationControl set to local");
|
||||||
|
apiClient.resetFetchConfigurationStatus();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (apiClient.fetchServerConfiguration()) {
|
if (apiClient.fetchServerConfiguration()) {
|
||||||
configUpdateHandle();
|
configUpdateHandle();
|
||||||
}
|
}
|
||||||
@ -470,7 +482,7 @@ static void appDispHandler(void) {
|
|||||||
if (configuration.isOfflineMode() == false) {
|
if (configuration.isOfflineMode() == false) {
|
||||||
if (wifiConnector.isConnected() == false) {
|
if (wifiConnector.isConnected() == false) {
|
||||||
state = AgStateMachineWiFiLost;
|
state = AgStateMachineWiFiLost;
|
||||||
} else if (apiClient.isFetchConfigureFailed()) {
|
} else if (apiClient.isFetchConfigurationFailed()) {
|
||||||
state = AgStateMachineSensorConfigFailed;
|
state = AgStateMachineSensorConfigFailed;
|
||||||
if (apiClient.isNotAvailableOnDashboard()) {
|
if (apiClient.isNotAvailableOnDashboard()) {
|
||||||
stateMachine.displaySetAddToDashBoard();
|
stateMachine.displaySetAddToDashBoard();
|
||||||
@ -490,84 +502,103 @@ 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.isFailed() == false) {
|
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);
|
|
||||||
pmFailCount = 0;
|
|
||||||
} else {
|
} else {
|
||||||
pmFailCount++;
|
measurements.update(Measurements::PM01, utils::getInvalidPmValue());
|
||||||
Serial.printf("PMS read failed: %d\r\n", pmFailCount);
|
measurements.update(Measurements::PM25, utils::getInvalidPmValue());
|
||||||
if (pmFailCount >= 3) {
|
measurements.update(Measurements::PM10, utils::getInvalidPmValue());
|
||||||
measurements.pm01_1 = utils::getInvalidPMS();
|
measurements.update(Measurements::PM03_PC, utils::getInvalidPmValue());
|
||||||
measurements.pm25_1 = utils::getInvalidPMS();
|
|
||||||
measurements.pm10_1 = utils::getInvalidPMS();
|
|
||||||
measurements.pm03PCount_1 = utils::getInvalidPMS();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void sendDataToServer(void) {
|
static void sendDataToServer(void) {
|
||||||
/** Ignore send data to server if postToAirGradient disabled */
|
/** Increment bootcount when send measurements data is scheduled */
|
||||||
if (configuration.isPostDataToAirGradient() == false ||
|
int bootCount = measurements.bootCount() + 1;
|
||||||
configuration.isOfflineMode()) {
|
measurements.setBootCount(bootCount);
|
||||||
|
|
||||||
|
if (configuration.isOfflineMode() || !configuration.isPostDataToAirGradient()) {
|
||||||
|
Serial.println("Skipping transmission of data to AG server. Either mode is offline "
|
||||||
|
"or post data to server disabled");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
String syncData = measurements.toString(false, fwMode, wifiConnector.RSSI(),
|
if (wifiConnector.isConnected() == false) {
|
||||||
&ag, &configuration);
|
Serial.println("WiFi not connected, skipping data transmission to AG server");
|
||||||
if (apiClient.postToServer(syncData)) {
|
return;
|
||||||
ag.watchdog.reset();
|
|
||||||
Serial.println();
|
|
||||||
Serial.println(
|
|
||||||
"Online mode and isPostToAirGradient = true: watchdog reset");
|
|
||||||
Serial.println();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
measurements.bootCount++;
|
String syncData = measurements.toString(false, fwMode, wifiConnector.RSSI());
|
||||||
|
if (apiClient.postToServer(syncData)) {
|
||||||
|
Serial.println();
|
||||||
|
Serial.println("Online mode and isPostToAirGradient = true");
|
||||||
|
Serial.println();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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());
|
||||||
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; }
|
||||||
|
@ -43,7 +43,7 @@ String OpenMetrics::getPayload(void) {
|
|||||||
"1 if the AirGradient device was able to successfully fetch its "
|
"1 if the AirGradient device was able to successfully fetch its "
|
||||||
"configuration from the server",
|
"configuration from the server",
|
||||||
"gauge");
|
"gauge");
|
||||||
add_metric_point("", apiClient.isFetchConfigureFailed() ? "0" : "1");
|
add_metric_point("", apiClient.isFetchConfigurationFailed() ? "0" : "1");
|
||||||
|
|
||||||
add_metric(
|
add_metric(
|
||||||
"post_ok",
|
"post_ok",
|
||||||
@ -57,60 +57,70 @@ 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::getInvalidPMS();
|
int pm01 = utils::getInvalidPmValue();
|
||||||
int pm25 = utils::getInvalidPMS();
|
int pm25 = utils::getInvalidPmValue();
|
||||||
int pm10 = utils::getInvalidPMS();
|
int pm10 = utils::getInvalidPmValue();
|
||||||
int pm03PCount = utils::getInvalidPMS();
|
int pm03PCount = utils::getInvalidPmValue();
|
||||||
|
int co2 = utils::getInvalidCO2();
|
||||||
int atmpCompensated = utils::getInvalidTemperature();
|
int atmpCompensated = utils::getInvalidTemperature();
|
||||||
int ahumCompensated = utils::getInvalidHumidity();
|
int rhumCompensated = 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;
|
rhumCompensated = _hum;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (config.hasSensorPMS1) {
|
if (config.hasSensorPMS1) {
|
||||||
pm01 = measure.pm01_1;
|
pm01 = measure.get(Measurements::PM01);
|
||||||
pm25 = measure.pm25_1;
|
float correctedPm = measure.getCorrectedPM25(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) {
|
||||||
if (utils::isValidPMS(pm01)) {
|
if (utils::isValidPm(pm01)) {
|
||||||
add_metric("pm1",
|
add_metric("pm1",
|
||||||
"PM1.0 concentration as measured by the AirGradient PMS "
|
"PM1.0 concentration as measured by the AirGradient PMS "
|
||||||
"sensor, in micrograms per cubic meter",
|
"sensor, in micrograms per cubic meter",
|
||||||
"gauge", "ugm3");
|
"gauge", "ugm3");
|
||||||
add_metric_point("", String(pm01));
|
add_metric_point("", String(pm01));
|
||||||
}
|
}
|
||||||
if (utils::isValidPMS(pm25)) {
|
if (utils::isValidPm(pm25)) {
|
||||||
add_metric("pm2d5",
|
add_metric("pm2d5",
|
||||||
"PM2.5 concentration as measured by the AirGradient PMS "
|
"PM2.5 concentration as measured by the AirGradient PMS "
|
||||||
"sensor, in micrograms per cubic meter",
|
"sensor, in micrograms per cubic meter",
|
||||||
"gauge", "ugm3");
|
"gauge", "ugm3");
|
||||||
add_metric_point("", String(pm25));
|
add_metric_point("", String(pm25));
|
||||||
}
|
}
|
||||||
if (utils::isValidPMS(pm10)) {
|
if (utils::isValidPm(pm10)) {
|
||||||
add_metric("pm10",
|
add_metric("pm10",
|
||||||
"PM10 concentration as measured by the AirGradient PMS "
|
"PM10 concentration as measured by the AirGradient PMS "
|
||||||
"sensor, in micrograms per cubic meter",
|
"sensor, in micrograms per cubic meter",
|
||||||
"gauge", "ugm3");
|
"gauge", "ugm3");
|
||||||
add_metric_point("", String(pm10));
|
add_metric_point("", String(pm10));
|
||||||
}
|
}
|
||||||
if (utils::isValidPMS03Count(pm03PCount)) {
|
if (utils::isValidPm03Count(pm03PCount)) {
|
||||||
add_metric("pm0d3",
|
add_metric("pm0d3",
|
||||||
"PM0.3 concentration as measured by the AirGradient PMS "
|
"PM0.3 concentration as measured by the AirGradient PMS "
|
||||||
"sensor, in number of particules per 100 milliliters",
|
"sensor, in number of particules per 100 milliliters",
|
||||||
@ -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",
|
||||||
@ -173,12 +191,12 @@ String OpenMetrics::getPayload(void) {
|
|||||||
"gauge", "percent");
|
"gauge", "percent");
|
||||||
add_metric_point("", String(_hum));
|
add_metric_point("", String(_hum));
|
||||||
}
|
}
|
||||||
if (utils::isValidHumidity(ahumCompensated)) {
|
if (utils::isValidHumidity(rhumCompensated)) {
|
||||||
add_metric("humidity_compensated",
|
add_metric("humidity_compensated",
|
||||||
"The compensated relative humidity as measured by the "
|
"The compensated relative humidity as measured by the "
|
||||||
"AirGradient SHT / PMS sensor",
|
"AirGradient SHT / PMS sensor",
|
||||||
"gauge", "percent");
|
"gauge", "percent");
|
||||||
add_metric_point("", String(ahumCompensated));
|
add_metric_point("", String(rhumCompensated));
|
||||||
}
|
}
|
||||||
|
|
||||||
response += "# EOF\n";
|
response += "# EOF\n";
|
||||||
|
@ -12,10 +12,8 @@ Outdoor Monitor: https://www.airgradient.com/outdoor/
|
|||||||
Build Instructions:
|
Build Instructions:
|
||||||
https://www.airgradient.com/documentation/diy-v4/
|
https://www.airgradient.com/documentation/diy-v4/
|
||||||
|
|
||||||
Please make sure you have esp8266 board manager installed. Tested with
|
Compile Instructions:
|
||||||
version 3.1.2.
|
https://github.com/airgradienthq/arduino/blob/master/docs/howto-compile.md
|
||||||
|
|
||||||
Set board to "LOLIN(WEMOS) D1 R2 & mini"
|
|
||||||
|
|
||||||
Configuration parameters, e.g. Celsius / Fahrenheit or PM unit (US AQI vs ug/m3)
|
Configuration parameters, e.g. Celsius / Fahrenheit or PM unit (US AQI vs ug/m3)
|
||||||
can be set through the AirGradient dashboard.
|
can be set through the AirGradient dashboard.
|
||||||
@ -49,14 +47,13 @@ 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);
|
||||||
static AgApiClient apiClient(Serial, configuration);
|
static AgApiClient apiClient(Serial, configuration);
|
||||||
static Measurements measurements;
|
static Measurements measurements(configuration);
|
||||||
static OledDisplay oledDisplay(configuration, measurements, Serial);
|
static OledDisplay oledDisplay(configuration, measurements, Serial);
|
||||||
static StateMachine stateMachine(oledDisplay, Serial, measurements,
|
static StateMachine stateMachine(oledDisplay, Serial, measurements,
|
||||||
configuration);
|
configuration);
|
||||||
@ -68,8 +65,6 @@ static LocalServer localServer(Serial, openMetrics, measurements, configuration,
|
|||||||
wifiConnector);
|
wifiConnector);
|
||||||
static MqttClient mqttClient(Serial);
|
static MqttClient mqttClient(Serial);
|
||||||
|
|
||||||
static int pmFailCount = 0;
|
|
||||||
static int getCO2FailCount = 0;
|
|
||||||
static AgFirmwareMode fwMode = FW_MODE_I_33PS;
|
static AgFirmwareMode fwMode = FW_MODE_I_33PS;
|
||||||
|
|
||||||
static String fwNewVersion;
|
static String fwNewVersion;
|
||||||
@ -91,6 +86,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,
|
||||||
@ -125,12 +122,17 @@ void setup() {
|
|||||||
apiClient.setAirGradient(&ag);
|
apiClient.setAirGradient(&ag);
|
||||||
openMetrics.setAirGradient(&ag);
|
openMetrics.setAirGradient(&ag);
|
||||||
localServer.setAirGraident(&ag);
|
localServer.setAirGraident(&ag);
|
||||||
|
measurements.setAirGradient(&ag);
|
||||||
|
|
||||||
/** Example set custom API root URL */
|
/** Example set custom API root URL */
|
||||||
// apiClient.setApiRoot("https://example.custom.api");
|
// apiClient.setApiRoot("https://example.custom.api");
|
||||||
|
|
||||||
/** 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;
|
||||||
@ -146,9 +148,12 @@ void setup() {
|
|||||||
initMqtt();
|
initMqtt();
|
||||||
sendDataToAg();
|
sendDataToAg();
|
||||||
|
|
||||||
|
if (configuration.getConfigurationControl() !=
|
||||||
|
ConfigurationControl::ConfigurationControlLocal) {
|
||||||
apiClient.fetchServerConfiguration();
|
apiClient.fetchServerConfiguration();
|
||||||
|
}
|
||||||
configSchedule.update();
|
configSchedule.update();
|
||||||
if (apiClient.isFetchConfigureFailed()) {
|
if (apiClient.isFetchConfigurationFailed()) {
|
||||||
if (apiClient.isNotAvailableOnDashboard()) {
|
if (apiClient.isNotAvailableOnDashboard()) {
|
||||||
stateMachine.displaySetAddToDashBoard();
|
stateMachine.displaySetAddToDashBoard();
|
||||||
stateMachine.displayHandle(
|
stateMachine.displayHandle(
|
||||||
@ -205,11 +210,7 @@ void loop() {
|
|||||||
tvocSchedule.run();
|
tvocSchedule.run();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Auto reset watchdog timer if offline mode or postDataToAirGradient */
|
|
||||||
if (configuration.isOfflineMode() ||
|
|
||||||
(configuration.isPostDataToAirGradient() == false)) {
|
|
||||||
watchdogFeedSchedule.run();
|
watchdogFeedSchedule.run();
|
||||||
}
|
|
||||||
|
|
||||||
/** Check for handle WiFi reconnect */
|
/** Check for handle WiFi reconnect */
|
||||||
wifiConnector.handle();
|
wifiConnector.handle();
|
||||||
@ -233,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();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -265,10 +265,17 @@ static void mdnsInit(void) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void initMqtt(void) {
|
static void initMqtt(void) {
|
||||||
if (mqttClient.begin(configuration.getMqttBrokerUri())) {
|
String mqttUri = configuration.getMqttBrokerUri();
|
||||||
Serial.println("Setup connect to MQTT broker successful");
|
if (mqttUri.isEmpty()) {
|
||||||
|
Serial.println(
|
||||||
|
"MQTT is not configured, skipping initialization of MQTT client");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mqttClient.begin(mqttUri)) {
|
||||||
|
Serial.println("Successfully connected to MQTT broker");
|
||||||
} else {
|
} else {
|
||||||
Serial.println("setup Connect to MQTT broker failed");
|
Serial.println("Connection to MQTT broker failed");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -333,9 +340,7 @@ static void factoryConfigReset(void) {
|
|||||||
|
|
||||||
static void wdgFeedUpdate(void) {
|
static void wdgFeedUpdate(void) {
|
||||||
ag.watchdog.reset();
|
ag.watchdog.reset();
|
||||||
Serial.println();
|
Serial.println("External watchdog feed!");
|
||||||
Serial.println("Offline mode or isPostToAirGradient = false: watchdog reset");
|
|
||||||
Serial.println();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool sgp41Init(void) {
|
static bool sgp41Init(void) {
|
||||||
@ -370,8 +375,7 @@ static void mqttHandle(void) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (mqttClient.isConnected()) {
|
if (mqttClient.isConnected()) {
|
||||||
String payload = measurements.toString(true, fwMode, wifiConnector.RSSI(),
|
String payload = measurements.toString(true, fwMode, wifiConnector.RSSI());
|
||||||
&ag, &configuration);
|
|
||||||
String topic = "airgradient/readings/" + ag.deviceId();
|
String topic = "airgradient/readings/" + ag.deviceId();
|
||||||
if (mqttClient.publish(topic.c_str(), payload.c_str(), payload.length())) {
|
if (mqttClient.publish(topic.c_str(), payload.c_str(), payload.length())) {
|
||||||
Serial.println("MQTT sync success");
|
Serial.println("MQTT sync success");
|
||||||
@ -386,7 +390,7 @@ static void sendDataToAg() {
|
|||||||
stateMachine.displayHandle(AgStateMachineWiFiOkServerConnecting);
|
stateMachine.displayHandle(AgStateMachineWiFiOkServerConnecting);
|
||||||
|
|
||||||
delay(1500);
|
delay(1500);
|
||||||
if (apiClient.sendPing(wifiConnector.RSSI(), measurements.bootCount)) {
|
if (apiClient.sendPing(wifiConnector.RSSI(), measurements.bootCount())) {
|
||||||
stateMachine.displayHandle(AgStateMachineWiFiOkServerConnected);
|
stateMachine.displayHandle(AgStateMachineWiFiOkServerConnected);
|
||||||
} else {
|
} else {
|
||||||
stateMachine.displayHandle(AgStateMachineWiFiOkServerConnectFailed);
|
stateMachine.displayHandle(AgStateMachineWiFiOkServerConnectFailed);
|
||||||
@ -465,6 +469,14 @@ static void failedHandler(String msg) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void configurationUpdateSchedule(void) {
|
static void configurationUpdateSchedule(void) {
|
||||||
|
if (configuration.isOfflineMode() ||
|
||||||
|
configuration.getConfigurationControl() == ConfigurationControl::ConfigurationControlLocal) {
|
||||||
|
Serial.println("Ignore fetch server configuration. Either mode is offline "
|
||||||
|
"or configurationControl set to local");
|
||||||
|
apiClient.resetFetchConfigurationStatus();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (apiClient.fetchServerConfiguration()) {
|
if (apiClient.fetchServerConfiguration()) {
|
||||||
configUpdateHandle();
|
configUpdateHandle();
|
||||||
}
|
}
|
||||||
@ -522,7 +534,7 @@ static void appDispHandler(void) {
|
|||||||
if (configuration.isOfflineMode() == false) {
|
if (configuration.isOfflineMode() == false) {
|
||||||
if (wifiConnector.isConnected() == false) {
|
if (wifiConnector.isConnected() == false) {
|
||||||
state = AgStateMachineWiFiLost;
|
state = AgStateMachineWiFiLost;
|
||||||
} else if (apiClient.isFetchConfigureFailed()) {
|
} else if (apiClient.isFetchConfigurationFailed()) {
|
||||||
state = AgStateMachineSensorConfigFailed;
|
state = AgStateMachineSensorConfigFailed;
|
||||||
if (apiClient.isNotAvailableOnDashboard()) {
|
if (apiClient.isNotAvailableOnDashboard()) {
|
||||||
stateMachine.displaySetAddToDashBoard();
|
stateMachine.displaySetAddToDashBoard();
|
||||||
@ -542,84 +554,103 @@ 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.isFailed() == false) {
|
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);
|
|
||||||
pmFailCount = 0;
|
|
||||||
} else {
|
} else {
|
||||||
pmFailCount++;
|
measurements.update(Measurements::PM01, utils::getInvalidPmValue());
|
||||||
Serial.printf("PMS read failed: %d\r\n", pmFailCount);
|
measurements.update(Measurements::PM25, utils::getInvalidPmValue());
|
||||||
if (pmFailCount >= 3) {
|
measurements.update(Measurements::PM10, utils::getInvalidPmValue());
|
||||||
measurements.pm01_1 = utils::getInvalidPMS();
|
measurements.update(Measurements::PM03_PC, utils::getInvalidPmValue());
|
||||||
measurements.pm25_1 = utils::getInvalidPMS();
|
|
||||||
measurements.pm10_1 = utils::getInvalidPMS();
|
|
||||||
measurements.pm03PCount_1 = utils::getInvalidPMS();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void sendDataToServer(void) {
|
static void sendDataToServer(void) {
|
||||||
/** Ignore send data to server if postToAirGradient disabled */
|
/** Increment bootcount when send measurements data is scheduled */
|
||||||
if (configuration.isPostDataToAirGradient() == false ||
|
int bootCount = measurements.bootCount() + 1;
|
||||||
configuration.isOfflineMode()) {
|
measurements.setBootCount(bootCount);
|
||||||
|
|
||||||
|
if (configuration.isOfflineMode() || !configuration.isPostDataToAirGradient()) {
|
||||||
|
Serial.println("Skipping transmission of data to AG server. Either mode is offline "
|
||||||
|
"or post data to server disabled");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
String syncData = measurements.toString(false, fwMode, wifiConnector.RSSI(),
|
if (wifiConnector.isConnected() == false) {
|
||||||
&ag, &configuration);
|
Serial.println("WiFi not connected, skipping data transmission to AG server");
|
||||||
if (apiClient.postToServer(syncData)) {
|
return;
|
||||||
ag.watchdog.reset();
|
|
||||||
Serial.println();
|
|
||||||
Serial.println(
|
|
||||||
"Online mode and isPostToAirGradient = true: watchdog reset");
|
|
||||||
Serial.println();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
measurements.bootCount++;
|
String syncData = measurements.toString(false, fwMode, wifiConnector.RSSI());
|
||||||
|
if (apiClient.postToServer(syncData)) {
|
||||||
|
Serial.println();
|
||||||
|
Serial.println("Online mode and isPostToAirGradient = true");
|
||||||
|
Serial.println();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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());
|
||||||
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; }
|
||||||
|
@ -43,7 +43,7 @@ String OpenMetrics::getPayload(void) {
|
|||||||
"1 if the AirGradient device was able to successfully fetch its "
|
"1 if the AirGradient device was able to successfully fetch its "
|
||||||
"configuration from the server",
|
"configuration from the server",
|
||||||
"gauge");
|
"gauge");
|
||||||
add_metric_point("", apiClient.isFetchConfigureFailed() ? "0" : "1");
|
add_metric_point("", apiClient.isFetchConfigurationFailed() ? "0" : "1");
|
||||||
|
|
||||||
add_metric(
|
add_metric(
|
||||||
"post_ok",
|
"post_ok",
|
||||||
@ -57,60 +57,70 @@ 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::getInvalidPMS();
|
int pm01 = utils::getInvalidPmValue();
|
||||||
int pm25 = utils::getInvalidPMS();
|
int pm25 = utils::getInvalidPmValue();
|
||||||
int pm10 = utils::getInvalidPMS();
|
int pm10 = utils::getInvalidPmValue();
|
||||||
int pm03PCount = utils::getInvalidPMS();
|
int pm03PCount = utils::getInvalidPmValue();
|
||||||
|
int co2 = utils::getInvalidCO2();
|
||||||
int atmpCompensated = utils::getInvalidTemperature();
|
int atmpCompensated = utils::getInvalidTemperature();
|
||||||
int ahumCompensated = utils::getInvalidHumidity();
|
int rhumCompensated = 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;
|
rhumCompensated = _hum;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (config.hasSensorPMS1) {
|
if (config.hasSensorPMS1) {
|
||||||
pm01 = measure.pm01_1;
|
pm01 = measure.get(Measurements::PM01);
|
||||||
pm25 = measure.pm25_1;
|
float correctedPm = measure.getCorrectedPM25(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) {
|
||||||
if (utils::isValidPMS(pm01)) {
|
if (utils::isValidPm(pm01)) {
|
||||||
add_metric("pm1",
|
add_metric("pm1",
|
||||||
"PM1.0 concentration as measured by the AirGradient PMS "
|
"PM1.0 concentration as measured by the AirGradient PMS "
|
||||||
"sensor, in micrograms per cubic meter",
|
"sensor, in micrograms per cubic meter",
|
||||||
"gauge", "ugm3");
|
"gauge", "ugm3");
|
||||||
add_metric_point("", String(pm01));
|
add_metric_point("", String(pm01));
|
||||||
}
|
}
|
||||||
if (utils::isValidPMS(pm25)) {
|
if (utils::isValidPm(pm25)) {
|
||||||
add_metric("pm2d5",
|
add_metric("pm2d5",
|
||||||
"PM2.5 concentration as measured by the AirGradient PMS "
|
"PM2.5 concentration as measured by the AirGradient PMS "
|
||||||
"sensor, in micrograms per cubic meter",
|
"sensor, in micrograms per cubic meter",
|
||||||
"gauge", "ugm3");
|
"gauge", "ugm3");
|
||||||
add_metric_point("", String(pm25));
|
add_metric_point("", String(pm25));
|
||||||
}
|
}
|
||||||
if (utils::isValidPMS(pm10)) {
|
if (utils::isValidPm(pm10)) {
|
||||||
add_metric("pm10",
|
add_metric("pm10",
|
||||||
"PM10 concentration as measured by the AirGradient PMS "
|
"PM10 concentration as measured by the AirGradient PMS "
|
||||||
"sensor, in micrograms per cubic meter",
|
"sensor, in micrograms per cubic meter",
|
||||||
"gauge", "ugm3");
|
"gauge", "ugm3");
|
||||||
add_metric_point("", String(pm10));
|
add_metric_point("", String(pm10));
|
||||||
}
|
}
|
||||||
if (utils::isValidPMS03Count(pm03PCount)) {
|
if (utils::isValidPm03Count(pm03PCount)) {
|
||||||
add_metric("pm0d3",
|
add_metric("pm0d3",
|
||||||
"PM0.3 concentration as measured by the AirGradient PMS "
|
"PM0.3 concentration as measured by the AirGradient PMS "
|
||||||
"sensor, in number of particules per 100 milliliters",
|
"sensor, in number of particules per 100 milliliters",
|
||||||
@ -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",
|
||||||
@ -173,12 +192,12 @@ String OpenMetrics::getPayload(void) {
|
|||||||
"gauge", "percent");
|
"gauge", "percent");
|
||||||
add_metric_point("", String(_hum));
|
add_metric_point("", String(_hum));
|
||||||
}
|
}
|
||||||
if (utils::isValidHumidity(ahumCompensated)) {
|
if (utils::isValidHumidity(rhumCompensated)) {
|
||||||
add_metric("humidity_compensated",
|
add_metric("humidity_compensated",
|
||||||
"The compensated relative humidity as measured by the "
|
"The compensated relative humidity as measured by the "
|
||||||
"AirGradient SHT / PMS sensor",
|
"AirGradient SHT / PMS sensor",
|
||||||
"gauge", "percent");
|
"gauge", "percent");
|
||||||
add_metric_point("", String(ahumCompensated));
|
add_metric_point("", String(rhumCompensated));
|
||||||
}
|
}
|
||||||
|
|
||||||
response += "# EOF\n";
|
response += "# EOF\n";
|
||||||
|
@ -12,10 +12,8 @@ Outdoor Monitor: https://www.airgradient.com/outdoor/
|
|||||||
Build Instructions:
|
Build Instructions:
|
||||||
https://www.airgradient.com/documentation/diy-v4/
|
https://www.airgradient.com/documentation/diy-v4/
|
||||||
|
|
||||||
Please make sure you have esp8266 board manager installed. Tested with
|
Compile Instructions:
|
||||||
version 3.1.2.
|
https://github.com/airgradienthq/arduino/blob/master/docs/howto-compile.md
|
||||||
|
|
||||||
Set board to "LOLIN(WEMOS) D1 R2 & mini"
|
|
||||||
|
|
||||||
Configuration parameters, e.g. Celsius / Fahrenheit or PM unit (US AQI vs ug/m3)
|
Configuration parameters, e.g. Celsius / Fahrenheit or PM unit (US AQI vs ug/m3)
|
||||||
can be set through the AirGradient dashboard.
|
can be set through the AirGradient dashboard.
|
||||||
@ -49,14 +47,13 @@ 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);
|
||||||
static AgApiClient apiClient(Serial, configuration);
|
static AgApiClient apiClient(Serial, configuration);
|
||||||
static Measurements measurements;
|
static Measurements measurements(configuration);
|
||||||
static OledDisplay oledDisplay(configuration, measurements, Serial);
|
static OledDisplay oledDisplay(configuration, measurements, Serial);
|
||||||
static StateMachine stateMachine(oledDisplay, Serial, measurements,
|
static StateMachine stateMachine(oledDisplay, Serial, measurements,
|
||||||
configuration);
|
configuration);
|
||||||
@ -68,9 +65,7 @@ static LocalServer localServer(Serial, openMetrics, measurements, configuration,
|
|||||||
wifiConnector);
|
wifiConnector);
|
||||||
static MqttClient mqttClient(Serial);
|
static MqttClient mqttClient(Serial);
|
||||||
|
|
||||||
static int pmFailCount = 0;
|
|
||||||
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;
|
||||||
@ -92,6 +87,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,
|
||||||
@ -126,12 +123,17 @@ void setup() {
|
|||||||
apiClient.setAirGradient(&ag);
|
apiClient.setAirGradient(&ag);
|
||||||
openMetrics.setAirGradient(&ag);
|
openMetrics.setAirGradient(&ag);
|
||||||
localServer.setAirGraident(&ag);
|
localServer.setAirGraident(&ag);
|
||||||
|
measurements.setAirGradient(&ag);
|
||||||
|
|
||||||
/** Example set custom API root URL */
|
/** Example set custom API root URL */
|
||||||
// apiClient.setApiRoot("https://example.custom.api");
|
// apiClient.setApiRoot("https://example.custom.api");
|
||||||
|
|
||||||
/** 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;
|
||||||
@ -173,9 +175,12 @@ void setup() {
|
|||||||
initMqtt();
|
initMqtt();
|
||||||
sendDataToAg();
|
sendDataToAg();
|
||||||
|
|
||||||
|
if (configuration.getConfigurationControl() !=
|
||||||
|
ConfigurationControl::ConfigurationControlLocal) {
|
||||||
apiClient.fetchServerConfiguration();
|
apiClient.fetchServerConfiguration();
|
||||||
|
}
|
||||||
configSchedule.update();
|
configSchedule.update();
|
||||||
if (apiClient.isFetchConfigureFailed()) {
|
if (apiClient.isFetchConfigurationFailed()) {
|
||||||
if (apiClient.isNotAvailableOnDashboard()) {
|
if (apiClient.isNotAvailableOnDashboard()) {
|
||||||
stateMachine.displaySetAddToDashBoard();
|
stateMachine.displaySetAddToDashBoard();
|
||||||
stateMachine.displayHandle(
|
stateMachine.displayHandle(
|
||||||
@ -232,11 +237,7 @@ void loop() {
|
|||||||
tvocSchedule.run();
|
tvocSchedule.run();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Auto reset watchdog timer if offline mode or postDataToAirGradient */
|
|
||||||
if (configuration.isOfflineMode() ||
|
|
||||||
(configuration.isPostDataToAirGradient() == false)) {
|
|
||||||
watchdogFeedSchedule.run();
|
watchdogFeedSchedule.run();
|
||||||
}
|
|
||||||
|
|
||||||
/** Check for handle WiFi reconnect */
|
/** Check for handle WiFi reconnect */
|
||||||
wifiConnector.handle();
|
wifiConnector.handle();
|
||||||
@ -260,17 +261,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();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -292,10 +292,17 @@ static void mdnsInit(void) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void initMqtt(void) {
|
static void initMqtt(void) {
|
||||||
if (mqttClient.begin(configuration.getMqttBrokerUri())) {
|
String mqttUri = configuration.getMqttBrokerUri();
|
||||||
Serial.println("Setup connect to MQTT broker successful");
|
if (mqttUri.isEmpty()) {
|
||||||
|
Serial.println(
|
||||||
|
"MQTT is not configured, skipping initialization of MQTT client");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mqttClient.begin(mqttUri)) {
|
||||||
|
Serial.println("Successfully connected to MQTT broker");
|
||||||
} else {
|
} else {
|
||||||
Serial.println("setup Connect to MQTT broker failed");
|
Serial.println("Connection to MQTT broker failed");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -328,8 +335,6 @@ static void factoryConfigReset(void) {
|
|||||||
// }
|
// }
|
||||||
|
|
||||||
/** Reset WIFI */
|
/** Reset WIFI */
|
||||||
// WiFi.enableSTA(true); // Incase offline mode
|
|
||||||
// WiFi.disconnect(true, true);
|
|
||||||
wifiConnector.reset();
|
wifiConnector.reset();
|
||||||
|
|
||||||
/** Reset local config */
|
/** Reset local config */
|
||||||
@ -358,9 +363,7 @@ static void factoryConfigReset(void) {
|
|||||||
|
|
||||||
static void wdgFeedUpdate(void) {
|
static void wdgFeedUpdate(void) {
|
||||||
ag.watchdog.reset();
|
ag.watchdog.reset();
|
||||||
Serial.println();
|
Serial.println("External watchdog feed!");
|
||||||
Serial.println("Offline mode or isPostToAirGradient = false: watchdog reset");
|
|
||||||
Serial.println();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool sgp41Init(void) {
|
static bool sgp41Init(void) {
|
||||||
@ -395,8 +398,7 @@ static void mqttHandle(void) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (mqttClient.isConnected()) {
|
if (mqttClient.isConnected()) {
|
||||||
String payload = measurements.toString(true, fwMode, wifiConnector.RSSI(),
|
String payload = measurements.toString(true, fwMode, wifiConnector.RSSI());
|
||||||
&ag, &configuration);
|
|
||||||
String topic = "airgradient/readings/" + ag.deviceId();
|
String topic = "airgradient/readings/" + ag.deviceId();
|
||||||
if (mqttClient.publish(topic.c_str(), payload.c_str(), payload.length())) {
|
if (mqttClient.publish(topic.c_str(), payload.c_str(), payload.length())) {
|
||||||
Serial.println("MQTT sync success");
|
Serial.println("MQTT sync success");
|
||||||
@ -411,7 +413,7 @@ static void sendDataToAg() {
|
|||||||
stateMachine.displayHandle(AgStateMachineWiFiOkServerConnecting);
|
stateMachine.displayHandle(AgStateMachineWiFiOkServerConnecting);
|
||||||
|
|
||||||
delay(1500);
|
delay(1500);
|
||||||
if (apiClient.sendPing(wifiConnector.RSSI(), measurements.bootCount)) {
|
if (apiClient.sendPing(wifiConnector.RSSI(), measurements.bootCount())) {
|
||||||
stateMachine.displayHandle(AgStateMachineWiFiOkServerConnected);
|
stateMachine.displayHandle(AgStateMachineWiFiOkServerConnected);
|
||||||
} else {
|
} else {
|
||||||
stateMachine.displayHandle(AgStateMachineWiFiOkServerConnectFailed);
|
stateMachine.displayHandle(AgStateMachineWiFiOkServerConnectFailed);
|
||||||
@ -507,6 +509,14 @@ static void failedHandler(String msg) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void configurationUpdateSchedule(void) {
|
static void configurationUpdateSchedule(void) {
|
||||||
|
if (configuration.isOfflineMode() ||
|
||||||
|
configuration.getConfigurationControl() == ConfigurationControl::ConfigurationControlLocal) {
|
||||||
|
Serial.println("Ignore fetch server configuration. Either mode is offline "
|
||||||
|
"or configurationControl set to local");
|
||||||
|
apiClient.resetFetchConfigurationStatus();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (apiClient.fetchServerConfiguration()) {
|
if (apiClient.fetchServerConfiguration()) {
|
||||||
configUpdateHandle();
|
configUpdateHandle();
|
||||||
}
|
}
|
||||||
@ -564,7 +574,7 @@ static void appDispHandler(void) {
|
|||||||
if (configuration.isOfflineMode() == false) {
|
if (configuration.isOfflineMode() == false) {
|
||||||
if (wifiConnector.isConnected() == false) {
|
if (wifiConnector.isConnected() == false) {
|
||||||
state = AgStateMachineWiFiLost;
|
state = AgStateMachineWiFiLost;
|
||||||
} else if (apiClient.isFetchConfigureFailed()) {
|
} else if (apiClient.isFetchConfigurationFailed()) {
|
||||||
state = AgStateMachineSensorConfigFailed;
|
state = AgStateMachineSensorConfigFailed;
|
||||||
if (apiClient.isNotAvailableOnDashboard()) {
|
if (apiClient.isNotAvailableOnDashboard()) {
|
||||||
stateMachine.displaySetAddToDashBoard();
|
stateMachine.displaySetAddToDashBoard();
|
||||||
@ -585,84 +595,103 @@ 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.isFailed() == false) {
|
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);
|
|
||||||
pmFailCount = 0;
|
|
||||||
} else {
|
} else {
|
||||||
pmFailCount++;
|
measurements.update(Measurements::PM01, utils::getInvalidPmValue());
|
||||||
Serial.printf("PMS read failed: %d\r\n", pmFailCount);
|
measurements.update(Measurements::PM25, utils::getInvalidPmValue());
|
||||||
if (pmFailCount >= 3) {
|
measurements.update(Measurements::PM10, utils::getInvalidPmValue());
|
||||||
measurements.pm01_1 = utils::getInvalidPMS();
|
measurements.update(Measurements::PM03_PC, utils::getInvalidPmValue());
|
||||||
measurements.pm25_1 = utils::getInvalidPMS();
|
|
||||||
measurements.pm10_1 = utils::getInvalidPMS();
|
|
||||||
measurements.pm03PCount_1 = utils::getInvalidPMS();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void sendDataToServer(void) {
|
static void sendDataToServer(void) {
|
||||||
/** Ignore send data to server if postToAirGradient disabled */
|
/** Increment bootcount when send measurements data is scheduled */
|
||||||
if (configuration.isPostDataToAirGradient() == false ||
|
int bootCount = measurements.bootCount() + 1;
|
||||||
configuration.isOfflineMode()) {
|
measurements.setBootCount(bootCount);
|
||||||
|
|
||||||
|
if (configuration.isOfflineMode() || !configuration.isPostDataToAirGradient()) {
|
||||||
|
Serial.println("Skipping transmission of data to AG server. Either mode is offline "
|
||||||
|
"or post data to server disabled");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
String syncData = measurements.toString(false, fwMode, wifiConnector.RSSI(),
|
if (wifiConnector.isConnected() == false) {
|
||||||
&ag, &configuration);
|
Serial.println("WiFi not connected, skipping data transmission to AG server");
|
||||||
if (apiClient.postToServer(syncData)) {
|
return;
|
||||||
ag.watchdog.reset();
|
|
||||||
Serial.println();
|
|
||||||
Serial.println(
|
|
||||||
"Online mode and isPostToAirGradient = true: watchdog reset");
|
|
||||||
Serial.println();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
measurements.bootCount++;
|
String syncData = measurements.toString(false, fwMode, wifiConnector.RSSI());
|
||||||
|
if (apiClient.postToServer(syncData)) {
|
||||||
|
Serial.println();
|
||||||
|
Serial.println("Online mode and isPostToAirGradient = true");
|
||||||
|
Serial.println();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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());
|
||||||
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; }
|
||||||
|
@ -43,7 +43,7 @@ String OpenMetrics::getPayload(void) {
|
|||||||
"1 if the AirGradient device was able to successfully fetch its "
|
"1 if the AirGradient device was able to successfully fetch its "
|
||||||
"configuration from the server",
|
"configuration from the server",
|
||||||
"gauge");
|
"gauge");
|
||||||
add_metric_point("", apiClient.isFetchConfigureFailed() ? "0" : "1");
|
add_metric_point("", apiClient.isFetchConfigurationFailed() ? "0" : "1");
|
||||||
|
|
||||||
add_metric(
|
add_metric(
|
||||||
"post_ok",
|
"post_ok",
|
||||||
@ -57,60 +57,70 @@ 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::getInvalidPMS();
|
int pm01 = utils::getInvalidPmValue();
|
||||||
int pm25 = utils::getInvalidPMS();
|
int pm25 = utils::getInvalidPmValue();
|
||||||
int pm10 = utils::getInvalidPMS();
|
int pm10 = utils::getInvalidPmValue();
|
||||||
int pm03PCount = utils::getInvalidPMS();
|
int pm03PCount = utils::getInvalidPmValue();
|
||||||
|
int co2 = utils::getInvalidCO2();
|
||||||
int atmpCompensated = utils::getInvalidTemperature();
|
int atmpCompensated = utils::getInvalidTemperature();
|
||||||
int ahumCompensated = utils::getInvalidHumidity();
|
int rhumCompensated = 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;
|
rhumCompensated = _hum;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (config.hasSensorPMS1) {
|
if (config.hasSensorPMS1) {
|
||||||
pm01 = measure.pm01_1;
|
pm01 = measure.get(Measurements::PM01);
|
||||||
pm25 = measure.pm25_1;
|
float correctedPm = measure.getCorrectedPM25(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) {
|
||||||
if (utils::isValidPMS(pm01)) {
|
if (utils::isValidPm(pm01)) {
|
||||||
add_metric("pm1",
|
add_metric("pm1",
|
||||||
"PM1.0 concentration as measured by the AirGradient PMS "
|
"PM1.0 concentration as measured by the AirGradient PMS "
|
||||||
"sensor, in micrograms per cubic meter",
|
"sensor, in micrograms per cubic meter",
|
||||||
"gauge", "ugm3");
|
"gauge", "ugm3");
|
||||||
add_metric_point("", String(pm01));
|
add_metric_point("", String(pm01));
|
||||||
}
|
}
|
||||||
if (utils::isValidPMS(pm25)) {
|
if (utils::isValidPm(pm25)) {
|
||||||
add_metric("pm2d5",
|
add_metric("pm2d5",
|
||||||
"PM2.5 concentration as measured by the AirGradient PMS "
|
"PM2.5 concentration as measured by the AirGradient PMS "
|
||||||
"sensor, in micrograms per cubic meter",
|
"sensor, in micrograms per cubic meter",
|
||||||
"gauge", "ugm3");
|
"gauge", "ugm3");
|
||||||
add_metric_point("", String(pm25));
|
add_metric_point("", String(pm25));
|
||||||
}
|
}
|
||||||
if (utils::isValidPMS(pm10)) {
|
if (utils::isValidPm(pm10)) {
|
||||||
add_metric("pm10",
|
add_metric("pm10",
|
||||||
"PM10 concentration as measured by the AirGradient PMS "
|
"PM10 concentration as measured by the AirGradient PMS "
|
||||||
"sensor, in micrograms per cubic meter",
|
"sensor, in micrograms per cubic meter",
|
||||||
"gauge", "ugm3");
|
"gauge", "ugm3");
|
||||||
add_metric_point("", String(pm10));
|
add_metric_point("", String(pm10));
|
||||||
}
|
}
|
||||||
if (utils::isValidPMS03Count(pm03PCount)) {
|
if (utils::isValidPm03Count(pm03PCount)) {
|
||||||
add_metric("pm0d3",
|
add_metric("pm0d3",
|
||||||
"PM0.3 concentration as measured by the AirGradient PMS "
|
"PM0.3 concentration as measured by the AirGradient PMS "
|
||||||
"sensor, in number of particules per 100 milliliters",
|
"sensor, in number of particules per 100 milliliters",
|
||||||
@ -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",
|
||||||
@ -173,12 +191,12 @@ String OpenMetrics::getPayload(void) {
|
|||||||
"gauge", "percent");
|
"gauge", "percent");
|
||||||
add_metric_point("", String(_hum));
|
add_metric_point("", String(_hum));
|
||||||
}
|
}
|
||||||
if (utils::isValidHumidity(ahumCompensated)) {
|
if (utils::isValidHumidity(rhumCompensated)) {
|
||||||
add_metric("humidity_compensated",
|
add_metric("humidity_compensated",
|
||||||
"The compensated relative humidity as measured by the "
|
"The compensated relative humidity as measured by the "
|
||||||
"AirGradient SHT / PMS sensor",
|
"AirGradient SHT / PMS sensor",
|
||||||
"gauge", "percent");
|
"gauge", "percent");
|
||||||
add_metric_point("", String(ahumCompensated));
|
add_metric_point("", String(rhumCompensated));
|
||||||
}
|
}
|
||||||
|
|
||||||
response += "# EOF\n";
|
response += "# EOF\n";
|
||||||
|
@ -64,9 +64,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());
|
||||||
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; }
|
||||||
|
@ -1,13 +1,15 @@
|
|||||||
#include "OpenMetrics.h"
|
#include "OpenMetrics.h"
|
||||||
|
|
||||||
OpenMetrics::OpenMetrics(Measurements &measure, Configuration &config,
|
OpenMetrics::OpenMetrics(Measurements &measure, Configuration &config,
|
||||||
WifiConnector &wifiConnector, AgApiClient &apiClient)
|
WifiConnector &wifiConnector)
|
||||||
: measure(measure), config(config), wifiConnector(wifiConnector),
|
: measure(measure), config(config), wifiConnector(wifiConnector) {}
|
||||||
apiClient(apiClient) {}
|
|
||||||
|
|
||||||
OpenMetrics::~OpenMetrics() {}
|
OpenMetrics::~OpenMetrics() {}
|
||||||
|
|
||||||
void OpenMetrics::setAirGradient(AirGradient *ag) { this->ag = ag; }
|
void OpenMetrics::setAirGradient(AirGradient *ag, AirgradientClient *client) {
|
||||||
|
this->ag = ag;
|
||||||
|
this->agClient = client;
|
||||||
|
}
|
||||||
|
|
||||||
const char *OpenMetrics::getApiContentType(void) {
|
const char *OpenMetrics::getApiContentType(void) {
|
||||||
return "application/openmetrics-text; version=1.0.0; charset=utf-8";
|
return "application/openmetrics-text; version=1.0.0; charset=utf-8";
|
||||||
@ -43,13 +45,13 @@ String OpenMetrics::getPayload(void) {
|
|||||||
"1 if the AirGradient device was able to successfully fetch its "
|
"1 if the AirGradient device was able to successfully fetch its "
|
||||||
"configuration from the server",
|
"configuration from the server",
|
||||||
"gauge");
|
"gauge");
|
||||||
add_metric_point("", apiClient.isFetchConfigureFailed() ? "0" : "1");
|
add_metric_point("", agClient->isLastFetchConfigSucceed() ? "1" : "0");
|
||||||
|
|
||||||
add_metric(
|
add_metric(
|
||||||
"post_ok",
|
"post_ok",
|
||||||
"1 if the AirGradient device was able to successfully send to the server",
|
"1 if the AirGradient device was able to successfully send to the server",
|
||||||
"gauge");
|
"gauge");
|
||||||
add_metric_point("", apiClient.isPostToServerFailed() ? "0" : "1");
|
add_metric_point("", agClient->isLastPostMeasureSucceed() ? "1" : "0");
|
||||||
|
|
||||||
add_metric(
|
add_metric(
|
||||||
"wifi_rssi",
|
"wifi_rssi",
|
||||||
@ -57,94 +59,121 @@ 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::getInvalidPMS();
|
int pm01 = utils::getInvalidPmValue();
|
||||||
int pm25 = utils::getInvalidPMS();
|
int pm25 = utils::getInvalidPmValue();
|
||||||
int pm10 = utils::getInvalidPMS();
|
int pm10 = utils::getInvalidPmValue();
|
||||||
int pm03PCount = utils::getInvalidPMS();
|
int pm03PCount = utils::getInvalidPmValue();
|
||||||
|
int co2 = utils::getInvalidCO2();
|
||||||
int atmpCompensated = utils::getInvalidTemperature();
|
int atmpCompensated = utils::getInvalidTemperature();
|
||||||
int ahumCompensated = utils::getInvalidHumidity();
|
int rhumCompensated = 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(false, 1);
|
||||||
|
float correctedPm25_2 = measure.getCorrectedPM25(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(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(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(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 = round(measure.getCorrectedTempHum(Measurements::Temperature));
|
||||||
ahumCompensated = _hum;
|
rhumCompensated = round(measure.getCorrectedTempHum(Measurements::Humidity));
|
||||||
} else {
|
} else {
|
||||||
atmpCompensated = ag->pms5003t_1.temperatureCompensated(_temp);
|
atmpCompensated = round((measure.getCorrectedTempHum(Measurements::Temperature, 1) +
|
||||||
ahumCompensated = ag->pms5003t_1.humidityCompensated(_hum);
|
measure.getCorrectedTempHum(Measurements::Temperature, 2)) /
|
||||||
|
2.0f);
|
||||||
|
rhumCompensated = round((measure.getCorrectedTempHum(Measurements::Humidity, 1) +
|
||||||
|
measure.getCorrectedTempHum(Measurements::Humidity, 2)) /
|
||||||
|
2.0f);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add measurements that valid to the metrics
|
||||||
if (config.hasSensorPMS1 || config.hasSensorPMS2) {
|
if (config.hasSensorPMS1 || config.hasSensorPMS2) {
|
||||||
if (utils::isValidPMS(pm01)) {
|
if (utils::isValidPm(pm01)) {
|
||||||
add_metric("pm1",
|
add_metric("pm1",
|
||||||
"PM1.0 concentration as measured by the AirGradient PMS "
|
"PM1.0 concentration as measured by the AirGradient PMS "
|
||||||
"sensor, in micrograms per cubic meter",
|
"sensor, in micrograms per cubic meter",
|
||||||
"gauge", "ugm3");
|
"gauge", "ugm3");
|
||||||
add_metric_point("", String(pm01));
|
add_metric_point("", String(pm01));
|
||||||
}
|
}
|
||||||
if (utils::isValidPMS(pm25)) {
|
if (utils::isValidPm(pm25)) {
|
||||||
add_metric("pm2d5",
|
add_metric("pm2d5",
|
||||||
"PM2.5 concentration as measured by the AirGradient PMS "
|
"PM2.5 concentration as measured by the AirGradient PMS "
|
||||||
"sensor, in micrograms per cubic meter",
|
"sensor, in micrograms per cubic meter",
|
||||||
"gauge", "ugm3");
|
"gauge", "ugm3");
|
||||||
add_metric_point("", String(pm25));
|
add_metric_point("", String(pm25));
|
||||||
}
|
}
|
||||||
if (utils::isValidPMS(pm10)) {
|
if (utils::isValidPm(pm10)) {
|
||||||
add_metric("pm10",
|
add_metric("pm10",
|
||||||
"PM10 concentration as measured by the AirGradient PMS "
|
"PM10 concentration as measured by the AirGradient PMS "
|
||||||
"sensor, in micrograms per cubic meter",
|
"sensor, in micrograms per cubic meter",
|
||||||
"gauge", "ugm3");
|
"gauge", "ugm3");
|
||||||
add_metric_point("", String(pm10));
|
add_metric_point("", String(pm10));
|
||||||
}
|
}
|
||||||
if (utils::isValidPMS03Count(pm03PCount)) {
|
if (utils::isValidPm03Count(pm03PCount)) {
|
||||||
add_metric("pm0d3",
|
add_metric("pm0d3",
|
||||||
"PM0.3 concentration as measured by the AirGradient PMS "
|
"PM0.3 concentration as measured by the AirGradient PMS "
|
||||||
"sensor, in number of particules per 100 milliliters",
|
"sensor, in number of particules per 100 milliliters",
|
||||||
@ -154,36 +183,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,26 +229,22 @@ 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(rhumCompensated)) {
|
||||||
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(rhumCompensated));
|
||||||
}
|
}
|
||||||
|
|
||||||
response += "# EOF\n";
|
response += "# EOF\n";
|
||||||
|
@ -5,21 +5,21 @@
|
|||||||
#include "AgValue.h"
|
#include "AgValue.h"
|
||||||
#include "AgWiFiConnector.h"
|
#include "AgWiFiConnector.h"
|
||||||
#include "AirGradient.h"
|
#include "AirGradient.h"
|
||||||
#include "AgApiClient.h"
|
#include "Libraries/airgradient-client/src/airgradientClient.h"
|
||||||
|
|
||||||
class OpenMetrics {
|
class OpenMetrics {
|
||||||
private:
|
private:
|
||||||
AirGradient *ag;
|
AirGradient *ag;
|
||||||
|
AirgradientClient *agClient;
|
||||||
Measurements &measure;
|
Measurements &measure;
|
||||||
Configuration &config;
|
Configuration &config;
|
||||||
WifiConnector &wifiConnector;
|
WifiConnector &wifiConnector;
|
||||||
AgApiClient &apiClient;
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
OpenMetrics(Measurements &measure, Configuration &conig,
|
OpenMetrics(Measurements &measure, Configuration &config,
|
||||||
WifiConnector &wifiConnector, AgApiClient& apiClient);
|
WifiConnector &wifiConnector);
|
||||||
~OpenMetrics();
|
~OpenMetrics();
|
||||||
void setAirGradient(AirGradient *ag);
|
void setAirGradient(AirGradient *ag, AirgradientClient *client);
|
||||||
const char *getApiContentType(void);
|
const char *getApiContentType(void);
|
||||||
const char* getApi(void);
|
const char* getApi(void);
|
||||||
String getPayload(void);
|
String getPayload(void);
|
||||||
|
@ -1,206 +0,0 @@
|
|||||||
#ifndef _OTA_HANDLER_H_
|
|
||||||
#define _OTA_HANDLER_H_
|
|
||||||
#include <Arduino.h>
|
|
||||||
#include <esp_err.h>
|
|
||||||
#include <esp_http_client.h>
|
|
||||||
#include <esp_ota_ops.h>
|
|
||||||
|
|
||||||
#define OTA_BUF_SIZE 1024
|
|
||||||
#define URL_BUF_SIZE 256
|
|
||||||
|
|
||||||
enum OtaUpdateOutcome {
|
|
||||||
UPDATE_PERFORMED,
|
|
||||||
ALREADY_UP_TO_DATE,
|
|
||||||
UPDATE_FAILED,
|
|
||||||
UDPATE_SKIPPED
|
|
||||||
};
|
|
||||||
|
|
||||||
enum OtaState {
|
|
||||||
OTA_STATE_BEGIN,
|
|
||||||
OTA_STATE_FAIL,
|
|
||||||
OTA_STATE_SKIP,
|
|
||||||
OTA_STATE_UP_TO_DATE,
|
|
||||||
OTA_STATE_PROCESSING,
|
|
||||||
OTA_STATE_SUCCESS
|
|
||||||
};
|
|
||||||
|
|
||||||
typedef void(*OtaHandlerCallback_t)(OtaState state,
|
|
||||||
String message);
|
|
||||||
|
|
||||||
class OtaHandler {
|
|
||||||
public:
|
|
||||||
void updateFirmwareIfOutdated(String deviceId) {
|
|
||||||
String url = "http://hw.airgradient.com/sensors/airgradient:" + deviceId +
|
|
||||||
"/generic/os/firmware.bin";
|
|
||||||
url += "?current_firmware=";
|
|
||||||
url += GIT_VERSION;
|
|
||||||
char urlAsChar[URL_BUF_SIZE];
|
|
||||||
url.toCharArray(urlAsChar, URL_BUF_SIZE);
|
|
||||||
Serial.printf("checking for new OTA update @ %s\n", urlAsChar);
|
|
||||||
|
|
||||||
esp_http_client_config_t config = {};
|
|
||||||
config.url = urlAsChar;
|
|
||||||
OtaUpdateOutcome ret = attemptToPerformOta(&config);
|
|
||||||
Serial.println(ret);
|
|
||||||
if (this->callback) {
|
|
||||||
switch (ret) {
|
|
||||||
case OtaUpdateOutcome::UPDATE_PERFORMED:
|
|
||||||
this->callback(OtaState::OTA_STATE_SUCCESS, "");
|
|
||||||
break;
|
|
||||||
case OtaUpdateOutcome::UDPATE_SKIPPED:
|
|
||||||
this->callback(OtaState::OTA_STATE_SKIP, "");
|
|
||||||
break;
|
|
||||||
case OtaUpdateOutcome::ALREADY_UP_TO_DATE:
|
|
||||||
this->callback(OtaState::OTA_STATE_UP_TO_DATE, "");
|
|
||||||
break;
|
|
||||||
case OtaUpdateOutcome::UPDATE_FAILED:
|
|
||||||
this->callback(OtaState::OTA_STATE_FAIL, "");
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void setHandlerCallback(OtaHandlerCallback_t callback) {
|
|
||||||
this->callback = callback;
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
OtaHandlerCallback_t callback;
|
|
||||||
|
|
||||||
OtaUpdateOutcome attemptToPerformOta(const esp_http_client_config_t *config) {
|
|
||||||
esp_http_client_handle_t client = esp_http_client_init(config);
|
|
||||||
if (client == NULL) {
|
|
||||||
Serial.println("Failed to initialize HTTP connection");
|
|
||||||
return OtaUpdateOutcome::UPDATE_FAILED;
|
|
||||||
}
|
|
||||||
|
|
||||||
esp_err_t err = esp_http_client_open(client, 0);
|
|
||||||
if (err != ESP_OK) {
|
|
||||||
esp_http_client_cleanup(client);
|
|
||||||
Serial.printf("Failed to open HTTP connection: %s\n",
|
|
||||||
esp_err_to_name(err));
|
|
||||||
return OtaUpdateOutcome::UPDATE_FAILED;
|
|
||||||
}
|
|
||||||
esp_http_client_fetch_headers(client);
|
|
||||||
|
|
||||||
int httpStatusCode = esp_http_client_get_status_code(client);
|
|
||||||
if (httpStatusCode == 304) {
|
|
||||||
Serial.println("Firmware is already up to date");
|
|
||||||
cleanupHttp(client);
|
|
||||||
return OtaUpdateOutcome::ALREADY_UP_TO_DATE;
|
|
||||||
} else if (httpStatusCode != 200) {
|
|
||||||
Serial.printf("Firmware update skipped, the server returned %d\n",
|
|
||||||
httpStatusCode);
|
|
||||||
cleanupHttp(client);
|
|
||||||
return OtaUpdateOutcome::UDPATE_SKIPPED;
|
|
||||||
}
|
|
||||||
|
|
||||||
esp_ota_handle_t update_handle = 0;
|
|
||||||
const esp_partition_t *update_partition = NULL;
|
|
||||||
Serial.println("Starting OTA update ...");
|
|
||||||
update_partition = esp_ota_get_next_update_partition(NULL);
|
|
||||||
if (update_partition == NULL) {
|
|
||||||
Serial.println("Passive OTA partition not found");
|
|
||||||
cleanupHttp(client);
|
|
||||||
return OtaUpdateOutcome::UPDATE_FAILED;
|
|
||||||
}
|
|
||||||
Serial.printf("Writing to partition subtype %d at offset 0x%x\n",
|
|
||||||
update_partition->subtype, update_partition->address);
|
|
||||||
|
|
||||||
err = esp_ota_begin(update_partition, OTA_SIZE_UNKNOWN, &update_handle);
|
|
||||||
if (err != ESP_OK) {
|
|
||||||
Serial.printf("esp_ota_begin failed, error=%d\n", err);
|
|
||||||
cleanupHttp(client);
|
|
||||||
return OtaUpdateOutcome::UPDATE_FAILED;
|
|
||||||
}
|
|
||||||
|
|
||||||
esp_err_t ota_write_err = ESP_OK;
|
|
||||||
char *upgrade_data_buf = (char *)malloc(OTA_BUF_SIZE);
|
|
||||||
if (!upgrade_data_buf) {
|
|
||||||
Serial.println("Couldn't allocate memory for data buffer");
|
|
||||||
return OtaUpdateOutcome::UPDATE_FAILED;
|
|
||||||
}
|
|
||||||
|
|
||||||
int binary_file_len = 0;
|
|
||||||
int totalSize = esp_http_client_get_content_length(client);
|
|
||||||
Serial.println("File size: " + String(totalSize) + String(" bytes"));
|
|
||||||
|
|
||||||
// Show display start update new firmware.
|
|
||||||
if (this->callback) {
|
|
||||||
this->callback(OtaState::OTA_STATE_BEGIN, "");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Download file and write new firmware to OTA partition
|
|
||||||
uint32_t lastUpdate = millis();
|
|
||||||
while (1) {
|
|
||||||
int data_read =
|
|
||||||
esp_http_client_read(client, upgrade_data_buf, OTA_BUF_SIZE);
|
|
||||||
if (data_read == 0) {
|
|
||||||
if (this->callback) {
|
|
||||||
this->callback(OtaState::OTA_STATE_PROCESSING, String(100));
|
|
||||||
}
|
|
||||||
Serial.println("Connection closed, all data received");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (data_read < 0) {
|
|
||||||
Serial.println("Data read error");
|
|
||||||
if (this->callback) {
|
|
||||||
this->callback(OtaState::OTA_STATE_FAIL, "");
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (data_read > 0) {
|
|
||||||
ota_write_err = esp_ota_write(
|
|
||||||
update_handle, (const void *)upgrade_data_buf, data_read);
|
|
||||||
if (ota_write_err != ESP_OK) {
|
|
||||||
if (this->callback) {
|
|
||||||
this->callback(OtaState::OTA_STATE_FAIL, "");
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
binary_file_len += data_read;
|
|
||||||
|
|
||||||
int percent = (binary_file_len * 100) / totalSize;
|
|
||||||
uint32_t ms = (uint32_t)(millis() - lastUpdate);
|
|
||||||
if (ms >= 250) {
|
|
||||||
// sm.executeOTA(StateMachine::OtaState::OTA_STATE_PROCESSING, "",
|
|
||||||
// percent);
|
|
||||||
if (this->callback) {
|
|
||||||
this->callback(OtaState::OTA_STATE_PROCESSING,
|
|
||||||
String(percent));
|
|
||||||
}
|
|
||||||
lastUpdate = millis();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
free(upgrade_data_buf);
|
|
||||||
cleanupHttp(client);
|
|
||||||
Serial.printf("# of bytes written: %d\n", binary_file_len);
|
|
||||||
|
|
||||||
esp_err_t ota_end_err = esp_ota_end(update_handle);
|
|
||||||
if (ota_write_err != ESP_OK) {
|
|
||||||
Serial.printf("Error: esp_ota_write failed! err=0x%d\n", err);
|
|
||||||
return OtaUpdateOutcome::UPDATE_FAILED;
|
|
||||||
} else if (ota_end_err != ESP_OK) {
|
|
||||||
Serial.printf("Error: esp_ota_end failed! err=0x%d. Image is invalid",
|
|
||||||
ota_end_err);
|
|
||||||
return OtaUpdateOutcome::UPDATE_FAILED;
|
|
||||||
}
|
|
||||||
|
|
||||||
err = esp_ota_set_boot_partition(update_partition);
|
|
||||||
if (err != ESP_OK) {
|
|
||||||
Serial.printf("esp_ota_set_boot_partition failed! err=0x%d\n", err);
|
|
||||||
return OtaUpdateOutcome::UPDATE_FAILED;
|
|
||||||
}
|
|
||||||
return OtaUpdateOutcome::UPDATE_PERFORMED;
|
|
||||||
}
|
|
||||||
|
|
||||||
void cleanupHttp(esp_http_client_handle_t client) {
|
|
||||||
esp_http_client_close(client);
|
|
||||||
esp_http_client_cleanup(client);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif
|
|
@ -44,7 +44,7 @@ void loop() {
|
|||||||
if (ms >= 5000) {
|
if (ms >= 5000) {
|
||||||
lastRead = millis();
|
lastRead = millis();
|
||||||
#ifdef ESP8266
|
#ifdef ESP8266
|
||||||
if (ag.pms5003.isFailed() == false) {
|
if (ag.pms5003.connected()) {
|
||||||
PM2 = ag.pms5003.getPm25Ae();
|
PM2 = ag.pms5003.getPm25Ae();
|
||||||
Serial.printf("PM2.5 in ug/m3: %d\r\n", PM2);
|
Serial.printf("PM2.5 in ug/m3: %d\r\n", PM2);
|
||||||
Serial.printf("PM2.5 in US AQI: %d\r\n",
|
Serial.printf("PM2.5 in US AQI: %d\r\n",
|
||||||
@ -54,12 +54,12 @@ void loop() {
|
|||||||
}
|
}
|
||||||
#else
|
#else
|
||||||
if (ag.getBoardType() == OPEN_AIR_OUTDOOR) {
|
if (ag.getBoardType() == OPEN_AIR_OUTDOOR) {
|
||||||
if (ag.pms5003t_1.isFailed() == false) {
|
if (ag.pms5003t_1.connected()) {
|
||||||
PM2 = ag.pms5003t_1.getPm25Ae();
|
PM2 = ag.pms5003t_1.getPm25Ae();
|
||||||
readResul = true;
|
readResul = true;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (ag.pms5003.isFailed() == false) {
|
if (ag.pms5003.connected()) {
|
||||||
PM2 = ag.pms5003.getPm25Ae();
|
PM2 = ag.pms5003.getPm25Ae();
|
||||||
readResul = true;
|
readResul = true;
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
name=AirGradient Air Quality Sensor
|
name=AirGradient Air Quality Sensor
|
||||||
version=3.1.5
|
version=3.3.6
|
||||||
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.
|
||||||
|
@ -12,7 +12,7 @@
|
|||||||
platform = espressif32
|
platform = espressif32
|
||||||
board = esp32-c3-devkitm-1
|
board = esp32-c3-devkitm-1
|
||||||
framework = arduino
|
framework = arduino
|
||||||
build_flags = !echo '-D ARDUINO_USB_CDC_ON_BOOT=1 -D ARDUINO_USB_MODE=1 -D GIT_VERSION=\\"'$(git describe --tags --always --dirty)'\\"'
|
build_flags = !echo '-D ARDUINO_USB_CDC_ON_BOOT=1 -D ARDUINO_USB_MODE=1 -D AG_LOG_LEVEL=AG_LOG_LEVEL_INFO -D GIT_VERSION=\\"'$(git describe --tags --always --dirty)'\\"'
|
||||||
board_build.partitions = partitions.csv
|
board_build.partitions = partitions.csv
|
||||||
monitor_speed = 115200
|
monitor_speed = 115200
|
||||||
lib_deps =
|
lib_deps =
|
||||||
|
@ -34,17 +34,6 @@ void AgApiClient::begin(void) {
|
|||||||
* @return false Failure
|
* @return false Failure
|
||||||
*/
|
*/
|
||||||
bool AgApiClient::fetchServerConfiguration(void) {
|
bool AgApiClient::fetchServerConfiguration(void) {
|
||||||
if (config.getConfigurationControl() ==
|
|
||||||
ConfigurationControl::ConfigurationControlLocal ||
|
|
||||||
config.isOfflineMode()) {
|
|
||||||
logWarning("Ignore fetch server configuration");
|
|
||||||
|
|
||||||
// Clear server configuration failed flag, cause it's ignore but not
|
|
||||||
// really failed
|
|
||||||
getConfigFailed = false;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
String uri = apiRoot + "/sensors/airgradient:" +
|
String uri = apiRoot + "/sensors/airgradient:" +
|
||||||
ag->deviceId() + "/one/config";
|
ag->deviceId() + "/one/config";
|
||||||
|
|
||||||
@ -58,10 +47,23 @@ bool AgApiClient::fetchServerConfiguration(void) {
|
|||||||
}
|
}
|
||||||
#else
|
#else
|
||||||
HTTPClient client;
|
HTTPClient client;
|
||||||
|
client.setConnectTimeout(timeoutMs); // Set timeout when establishing connection to server
|
||||||
|
client.setTimeout(timeoutMs); // Timeout when waiting for response from AG server
|
||||||
|
if (apiRootChanged) {
|
||||||
|
// If apiRoot is changed, assume not using https
|
||||||
if (client.begin(uri) == false) {
|
if (client.begin(uri) == false) {
|
||||||
|
logError("Begin HTTPClient failed (GET)");
|
||||||
getConfigFailed = true;
|
getConfigFailed = true;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
// By default, airgradient using https
|
||||||
|
if (client.begin(uri, AG_SERVER_ROOT_CA) == false) {
|
||||||
|
logError("Begin HTTPClient using tls failed (GET)");
|
||||||
|
getConfigFailed = true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
/** Get data */
|
/** Get data */
|
||||||
@ -89,8 +91,6 @@ bool AgApiClient::fetchServerConfiguration(void) {
|
|||||||
String respContent = client.getString();
|
String respContent = client.getString();
|
||||||
client.end();
|
client.end();
|
||||||
|
|
||||||
// logInfo("Get configuration: " + respContent);
|
|
||||||
|
|
||||||
/** Parse configuration and return result */
|
/** Parse configuration and return result */
|
||||||
return config.parse(respContent, false);
|
return config.parse(respContent, false);
|
||||||
}
|
}
|
||||||
@ -104,33 +104,39 @@ bool AgApiClient::fetchServerConfiguration(void) {
|
|||||||
* @return false Failure
|
* @return false Failure
|
||||||
*/
|
*/
|
||||||
bool AgApiClient::postToServer(String data) {
|
bool AgApiClient::postToServer(String data) {
|
||||||
if (config.isPostDataToAirGradient() == false) {
|
String uri = apiRoot + "/sensors/airgradient:" + ag->deviceId() + "/measures";
|
||||||
logWarning("Ignore post data to server");
|
#ifdef ESP8266
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (WiFi.isConnected() == false) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
String uri =
|
|
||||||
"http://hw.airgradient.com/sensors/airgradient:" + ag->deviceId() +
|
|
||||||
"/measures";
|
|
||||||
// logInfo("Post uri: " + uri);
|
|
||||||
// logInfo("Post data: " + data);
|
|
||||||
|
|
||||||
WiFiClient wifiClient;
|
|
||||||
HTTPClient client;
|
HTTPClient client;
|
||||||
if (client.begin(wifiClient, uri.c_str()) == false) {
|
WiFiClient wifiClient;
|
||||||
logError("Init client failed");
|
if (client.begin(wifiClient, uri) == false) {
|
||||||
|
getConfigFailed = true;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
#else
|
||||||
|
HTTPClient client;
|
||||||
|
client.setConnectTimeout(timeoutMs); // Set timeout when establishing connection to server
|
||||||
|
client.setTimeout(timeoutMs); // Timeout when waiting for response from AG server
|
||||||
|
if (apiRootChanged) {
|
||||||
|
// If apiRoot is changed, assume not using https
|
||||||
|
if (client.begin(uri) == false) {
|
||||||
|
logError("Begin HTTPClient failed (POST)");
|
||||||
|
getConfigFailed = true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// By default, airgradient using https
|
||||||
|
if (client.begin(uri, AG_SERVER_ROOT_CA) == false) {
|
||||||
|
logError("Begin HTTPClient using tls failed (POST)");
|
||||||
|
getConfigFailed = true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
client.addHeader("content-type", "application/json");
|
client.addHeader("content-type", "application/json");
|
||||||
int retCode = client.POST(data);
|
int retCode = client.POST(data);
|
||||||
client.end();
|
client.end();
|
||||||
|
|
||||||
logInfo(String("POST: ") + uri);
|
logInfo(String("POST: ") + uri);
|
||||||
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)) {
|
||||||
@ -149,7 +155,12 @@ bool AgApiClient::postToServer(String data) {
|
|||||||
* @return true Success
|
* @return true Success
|
||||||
* @return false Failure
|
* @return false Failure
|
||||||
*/
|
*/
|
||||||
bool AgApiClient::isFetchConfigureFailed(void) { return getConfigFailed; }
|
bool AgApiClient::isFetchConfigurationFailed(void) { return getConfigFailed; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Reset status of get configuration from AirGradient cloud
|
||||||
|
*/
|
||||||
|
void AgApiClient::resetFetchConfigurationStatus(void) { getConfigFailed = false; }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Get failed status when post data to AirGradient cloud
|
* @brief Get failed status when post data to AirGradient cloud
|
||||||
@ -189,4 +200,16 @@ bool AgApiClient::sendPing(int rssi, int bootCount) {
|
|||||||
|
|
||||||
String AgApiClient::getApiRoot() const { return apiRoot; }
|
String AgApiClient::getApiRoot() const { return apiRoot; }
|
||||||
|
|
||||||
void AgApiClient::setApiRoot(const String &apiRoot) { this->apiRoot = apiRoot; }
|
void AgApiClient::setApiRoot(const String &apiRoot) {
|
||||||
|
this->apiRootChanged = true;
|
||||||
|
this->apiRoot = apiRoot;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Set http request timeout. (Default: 10s)
|
||||||
|
*
|
||||||
|
* @param timeoutMs
|
||||||
|
*/
|
||||||
|
void AgApiClient::setTimeout(uint16_t timeoutMs) {
|
||||||
|
this->timeoutMs = timeoutMs;
|
||||||
|
}
|
@ -20,11 +20,18 @@ class AgApiClient : public PrintLog {
|
|||||||
private:
|
private:
|
||||||
Configuration &config;
|
Configuration &config;
|
||||||
AirGradient *ag;
|
AirGradient *ag;
|
||||||
|
#ifdef ESP8266
|
||||||
|
// ESP8266 not support HTTPS
|
||||||
String apiRoot = "http://hw.airgradient.com";
|
String apiRoot = "http://hw.airgradient.com";
|
||||||
|
#else
|
||||||
|
String apiRoot = "https://hw.airgradient.com";
|
||||||
|
#endif
|
||||||
|
|
||||||
|
bool apiRootChanged = false; // Indicate if setApiRoot() is called
|
||||||
bool getConfigFailed;
|
bool getConfigFailed;
|
||||||
bool postToServerFailed;
|
bool postToServerFailed;
|
||||||
bool notAvailableOnDashboard = false; // Device not setup on Airgradient cloud dashboard.
|
bool notAvailableOnDashboard = false; // Device not setup on Airgradient cloud dashboard.
|
||||||
|
uint16_t timeoutMs = 15000; // Default set to 15s
|
||||||
|
|
||||||
public:
|
public:
|
||||||
AgApiClient(Stream &stream, Configuration &config);
|
AgApiClient(Stream &stream, Configuration &config);
|
||||||
@ -33,13 +40,15 @@ public:
|
|||||||
void begin(void);
|
void begin(void);
|
||||||
bool fetchServerConfiguration(void);
|
bool fetchServerConfiguration(void);
|
||||||
bool postToServer(String data);
|
bool postToServer(String data);
|
||||||
bool isFetchConfigureFailed(void);
|
bool isFetchConfigurationFailed(void);
|
||||||
|
void resetFetchConfigurationStatus(void);
|
||||||
bool isPostToServerFailed(void);
|
bool isPostToServerFailed(void);
|
||||||
bool isNotAvailableOnDashboard(void);
|
bool isNotAvailableOnDashboard(void);
|
||||||
void setAirGradient(AirGradient *ag);
|
void setAirGradient(AirGradient *ag);
|
||||||
bool sendPing(int rssi, int bootCount);
|
bool sendPing(int rssi, int bootCount);
|
||||||
String getApiRoot() const;
|
String getApiRoot() const;
|
||||||
void setApiRoot(const String &apiRoot);
|
void setApiRoot(const String &apiRoot);
|
||||||
|
void setTimeout(uint16_t timeoutMs);
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif /** _AG_API_CLIENT_H_ */
|
#endif /** _AG_API_CLIENT_H_ */
|
||||||
|
@ -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,20 @@ const char *LED_BAR_MODE_NAMES[] = {
|
|||||||
[LedBarModeCO2] = "co2",
|
[LedBarModeCO2] = "co2",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const char *PM_CORRECTION_ALGORITHM_NAMES[] = {
|
||||||
|
[COR_ALGO_PM_UNKNOWN] = "-", // This is only to pass "non-trivial designated initializers" error
|
||||||
|
[COR_ALGO_PM_NONE] = "none",
|
||||||
|
[COR_ALGO_PM_EPA_2021] = "epa_2021",
|
||||||
|
[COR_ALGO_PM_SLR_CUSTOM] = "custom",
|
||||||
|
};
|
||||||
|
|
||||||
|
const char *TEMP_HUM_CORRECTION_ALGORITHM_NAMES[] = {
|
||||||
|
[COR_ALGO_TEMP_HUM_UNKNOWN] = "-", // This is only to pass "non-trivial designated initializers" error
|
||||||
|
[COR_ALGO_TEMP_HUM_NONE] = "none",
|
||||||
|
[COR_ALGO_TEMP_HUM_AG_PMS5003T_2024] = "ag_pms5003t_2024",
|
||||||
|
[COR_ALGO_TEMP_HUM_SLR_CUSTOM] = "custom",
|
||||||
|
};
|
||||||
|
|
||||||
#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
|
||||||
|
|
||||||
@ -33,14 +46,20 @@ JSON_PROP_DEF(abcDays);
|
|||||||
JSON_PROP_DEF(tvocLearningOffset);
|
JSON_PROP_DEF(tvocLearningOffset);
|
||||||
JSON_PROP_DEF(noxLearningOffset);
|
JSON_PROP_DEF(noxLearningOffset);
|
||||||
JSON_PROP_DEF(mqttBrokerUrl);
|
JSON_PROP_DEF(mqttBrokerUrl);
|
||||||
|
JSON_PROP_DEF(httpDomain);
|
||||||
JSON_PROP_DEF(temperatureUnit);
|
JSON_PROP_DEF(temperatureUnit);
|
||||||
JSON_PROP_DEF(configurationControl);
|
JSON_PROP_DEF(configurationControl);
|
||||||
JSON_PROP_DEF(postDataToAirGradient);
|
JSON_PROP_DEF(postDataToAirGradient);
|
||||||
|
JSON_PROP_DEF(disableCloudConnection);
|
||||||
JSON_PROP_DEF(ledBarBrightness);
|
JSON_PROP_DEF(ledBarBrightness);
|
||||||
JSON_PROP_DEF(displayBrightness);
|
JSON_PROP_DEF(displayBrightness);
|
||||||
JSON_PROP_DEF(co2CalibrationRequested);
|
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(corrections);
|
||||||
|
JSON_PROP_DEF(atmp);
|
||||||
|
JSON_PROP_DEF(rhum);
|
||||||
|
|
||||||
#define jprop_model_default ""
|
#define jprop_model_default ""
|
||||||
#define jprop_country_default "TH"
|
#define jprop_country_default "TH"
|
||||||
@ -50,12 +69,15 @@ JSON_PROP_DEF(offlineMode);
|
|||||||
#define jprop_tvocLearningOffset_default 12
|
#define jprop_tvocLearningOffset_default 12
|
||||||
#define jprop_noxLearningOffset_default 12
|
#define jprop_noxLearningOffset_default 12
|
||||||
#define jprop_mqttBrokerUrl_default ""
|
#define jprop_mqttBrokerUrl_default ""
|
||||||
|
#define jprop_httpDomain_default ""
|
||||||
#define jprop_temperatureUnit_default "c"
|
#define jprop_temperatureUnit_default "c"
|
||||||
#define jprop_configurationControl_default String(CONFIGURATION_CONTROL_NAME[ConfigurationControl::ConfigurationControlBoth])
|
#define jprop_configurationControl_default String(CONFIGURATION_CONTROL_NAME[ConfigurationControl::ConfigurationControlBoth])
|
||||||
#define jprop_postDataToAirGradient_default true
|
#define jprop_postDataToAirGradient_default true
|
||||||
|
#define jprop_disableCloudConnection_default false
|
||||||
#define jprop_ledBarBrightness_default 100
|
#define jprop_ledBarBrightness_default 100
|
||||||
#define jprop_displayBrightness_default 100
|
#define jprop_displayBrightness_default 100
|
||||||
#define jprop_offlineMode_default false
|
#define jprop_offlineMode_default false
|
||||||
|
#define jprop_monitorDisplayCompensatedValues_default false
|
||||||
|
|
||||||
JSONVar jconfig;
|
JSONVar jconfig;
|
||||||
|
|
||||||
@ -85,6 +107,214 @@ 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 = COR_ALGO_PM_SLR_CUSTOM + 1; // Get the actual size of the enum
|
||||||
|
PMCorrectionAlgorithm result = COR_ALGO_PM_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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If string not match from enum, check if correctionAlgorithm is one of the PM batch corrections
|
||||||
|
if (result == COR_ALGO_PM_UNKNOWN) {
|
||||||
|
// Check the substring "slr_PMS5003_xxxxxxxx"
|
||||||
|
if (algorithm.substring(0, 11) == "slr_PMS5003") {
|
||||||
|
// If it is, then its a custom correction
|
||||||
|
result = COR_ALGO_PM_SLR_CUSTOM;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
TempHumCorrectionAlgorithm Configuration::matchTempHumAlgorithm(String algorithm) {
|
||||||
|
// Get the actual size of the enum
|
||||||
|
const int enumSize = static_cast<int>(COR_ALGO_TEMP_HUM_SLR_CUSTOM);
|
||||||
|
TempHumCorrectionAlgorithm result = COR_ALGO_TEMP_HUM_UNKNOWN;
|
||||||
|
|
||||||
|
// Loop through enum values
|
||||||
|
for (size_t enumVal = 0; enumVal <= enumSize; enumVal++) {
|
||||||
|
if (algorithm == TEMP_HUM_CORRECTION_ALGORITHM_NAMES[enumVal]) {
|
||||||
|
result = static_cast<TempHumCorrectionAlgorithm>(enumVal);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Configuration::updatePmCorrection(JSONVar &json) {
|
||||||
|
if (!json.hasOwnProperty("corrections")) {
|
||||||
|
logInfo("corrections not found");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
JSONVar corrections = json["corrections"];
|
||||||
|
if (!corrections.hasOwnProperty("pm02")) {
|
||||||
|
logWarning("pm02 not found");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
JSONVar pm02 = corrections["pm02"];
|
||||||
|
if (!pm02.hasOwnProperty("correctionAlgorithm")) {
|
||||||
|
logWarning("pm02 correctionAlgorithm not found");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check algorithm
|
||||||
|
String algorithm = pm02["correctionAlgorithm"];
|
||||||
|
PMCorrectionAlgorithm algo = matchPmAlgorithm(algorithm);
|
||||||
|
if (algo == COR_ALGO_PM_UNKNOWN) {
|
||||||
|
logWarning("Unknown algorithm");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
logInfo("Correction algorithm: " + algorithm);
|
||||||
|
|
||||||
|
// If algo is None or EPA_2021, no need to check slr
|
||||||
|
// But first check if pmCorrection different from algo
|
||||||
|
if (algo == COR_ALGO_PM_NONE || algo == COR_ALGO_PM_EPA_2021) {
|
||||||
|
if (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")) {
|
||||||
|
logWarning("slr not found");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
JSONVar slr = pm02["slr"];
|
||||||
|
|
||||||
|
// Validate required slr properties exist
|
||||||
|
if (!slr.hasOwnProperty("intercept") || !slr.hasOwnProperty("scalingFactor") ||
|
||||||
|
!slr.hasOwnProperty("useEpa2021")) {
|
||||||
|
logWarning("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;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Configuration::updateTempHumCorrection(JSONVar &json, TempHumCorrection &target,
|
||||||
|
const char *correctionName) {
|
||||||
|
if (!json.hasOwnProperty(jprop_corrections)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
JSONVar corrections = json[jprop_corrections];
|
||||||
|
if (!corrections.hasOwnProperty(correctionName)) {
|
||||||
|
logInfo(String(correctionName) + " correction field not found on configuration");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
JSONVar correctionTarget = corrections[correctionName];
|
||||||
|
if (!correctionTarget.hasOwnProperty("correctionAlgorithm")) {
|
||||||
|
Serial.println("correctionAlgorithm not found");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
String algorithm = correctionTarget["correctionAlgorithm"];
|
||||||
|
TempHumCorrectionAlgorithm algo = matchTempHumAlgorithm(algorithm);
|
||||||
|
if (algo == COR_ALGO_TEMP_HUM_UNKNOWN) {
|
||||||
|
logInfo("Uknown temp/hum algorithm");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
logInfo(String(correctionName) + " correction algorithm: " + algorithm);
|
||||||
|
|
||||||
|
// If algo is None or Standard, then no need to check slr
|
||||||
|
// But first check if target correction different from algo
|
||||||
|
if (algo == COR_ALGO_TEMP_HUM_NONE || algo == COR_ALGO_TEMP_HUM_AG_PMS5003T_2024) {
|
||||||
|
if (target.algorithm != algo) {
|
||||||
|
// Deep copy corrections from root to jconfig, so it will be saved later
|
||||||
|
jconfig[jprop_corrections][correctionName]["correctionAlgorithm"] = algorithm;
|
||||||
|
jconfig[jprop_corrections][correctionName]["slr"] = JSON.parse("{}"); // Clear slr
|
||||||
|
// Update pmCorrection with new values
|
||||||
|
target.algorithm = algo;
|
||||||
|
target.changed = true;
|
||||||
|
logInfo(String(correctionName) + " correction updated");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if correction.target (atmp or rhum) has slr object
|
||||||
|
if (!correctionTarget.hasOwnProperty("slr")) {
|
||||||
|
logWarning(String(correctionName) + " slr not found");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
JSONVar slr = correctionTarget["slr"];
|
||||||
|
|
||||||
|
// Validate required slr properties exist
|
||||||
|
if (!slr.hasOwnProperty("intercept") || !slr.hasOwnProperty("scalingFactor")) {
|
||||||
|
Serial.println("Missing required slr properties");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// arduino_json doesn't support float type, need to cast to double first
|
||||||
|
float intercept = (float)((double)slr["intercept"]);
|
||||||
|
float scalingFactor = (float)((double)slr["scalingFactor"]);
|
||||||
|
|
||||||
|
// Compare with current target correciont
|
||||||
|
if (target.algorithm == algo && target.intercept == intercept &&
|
||||||
|
target.scalingFactor == scalingFactor) {
|
||||||
|
return false; // No changes needed
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deep copy corrections from root to jconfig, so it will be saved later
|
||||||
|
jconfig[jprop_corrections] = corrections;
|
||||||
|
|
||||||
|
// Update target with new values
|
||||||
|
target.algorithm = algo;
|
||||||
|
target.intercept = intercept;
|
||||||
|
target.scalingFactor = scalingFactor;
|
||||||
|
target.changed = true;
|
||||||
|
|
||||||
|
// Correction values were updated
|
||||||
|
logInfo(String(correctionName) + " correction updated");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Save configure to device storage (EEPROM)
|
* @brief Save configure to device storage (EEPROM)
|
||||||
*
|
*
|
||||||
@ -149,9 +379,11 @@ void Configuration::defaultConfig(void) {
|
|||||||
|
|
||||||
jconfig[jprop_country] = jprop_country_default;
|
jconfig[jprop_country] = jprop_country_default;
|
||||||
jconfig[jprop_mqttBrokerUrl] = jprop_mqttBrokerUrl_default;
|
jconfig[jprop_mqttBrokerUrl] = jprop_mqttBrokerUrl_default;
|
||||||
|
jconfig[jprop_httpDomain] = jprop_httpDomain_default;
|
||||||
jconfig[jprop_configurationControl] = jprop_configurationControl_default;
|
jconfig[jprop_configurationControl] = jprop_configurationControl_default;
|
||||||
jconfig[jprop_pmStandard] = jprop_pmStandard_default;
|
jconfig[jprop_pmStandard] = jprop_pmStandard_default;
|
||||||
jconfig[jprop_temperatureUnit] = jprop_temperatureUnit_default;
|
jconfig[jprop_temperatureUnit] = jprop_temperatureUnit_default;
|
||||||
|
jconfig[jprop_disableCloudConnection] = jprop_disableCloudConnection_default;
|
||||||
jconfig[jprop_postDataToAirGradient] = jprop_postDataToAirGradient_default;
|
jconfig[jprop_postDataToAirGradient] = jprop_postDataToAirGradient_default;
|
||||||
if (ag->isOne()) {
|
if (ag->isOne()) {
|
||||||
jconfig[jprop_ledBarBrightness] = jprop_ledBarBrightness_default;
|
jconfig[jprop_ledBarBrightness] = jprop_ledBarBrightness_default;
|
||||||
@ -160,13 +392,21 @@ 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;
|
||||||
jconfig[jprop_abcDays] = jprop_abcDays_default;
|
jconfig[jprop_abcDays] = jprop_abcDays_default;
|
||||||
jconfig[jprop_model] = jprop_model_default;
|
jconfig[jprop_model] = jprop_model_default;
|
||||||
jconfig[jprop_offlineMode] = jprop_offlineMode_default;
|
jconfig[jprop_offlineMode] = jprop_offlineMode_default;
|
||||||
|
jconfig[jprop_monitorDisplayCompensatedValues] = jprop_monitorDisplayCompensatedValues_default;
|
||||||
|
|
||||||
|
// PM2.5 default correction
|
||||||
|
pmCorrection.algorithm = COR_ALGO_PM_NONE;
|
||||||
|
pmCorrection.changed = false;
|
||||||
|
pmCorrection.intercept = 0;
|
||||||
|
pmCorrection.scalingFactor = 1;
|
||||||
|
pmCorrection.useEPA = false;
|
||||||
|
|
||||||
saveConfig();
|
saveConfig();
|
||||||
}
|
}
|
||||||
@ -226,16 +466,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;
|
||||||
@ -498,11 +738,17 @@ bool Configuration::parse(String data, bool isLocal) {
|
|||||||
jconfig[jprop_mqttBrokerUrl] = broker;
|
jconfig[jprop_mqttBrokerUrl] = broker;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
failedMessage = "\"mqttBrokerUrl\" length should <= 255";
|
failedMessage = "\"mqttBrokerUrl\" length should less than 255 character";
|
||||||
jsonInvalid();
|
jsonInvalid();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
} else {
|
}
|
||||||
|
else if (JSON.typeof_(root[jprop_mqttBrokerUrl]) == "null" and !isLocal) {
|
||||||
|
// So if its not available on the json and json comes from aigradient server
|
||||||
|
// then set its value to default (empty)
|
||||||
|
jconfig[jprop_mqttBrokerUrl] = jprop_mqttBrokerUrl_default;
|
||||||
|
}
|
||||||
|
else {
|
||||||
if (jsonTypeInvalid(root[jprop_mqttBrokerUrl], "string")) {
|
if (jsonTypeInvalid(root[jprop_mqttBrokerUrl], "string")) {
|
||||||
failedMessage =
|
failedMessage =
|
||||||
jsonTypeInvalidMessage(String(jprop_mqttBrokerUrl), "string");
|
jsonTypeInvalidMessage(String(jprop_mqttBrokerUrl), "string");
|
||||||
@ -511,6 +757,32 @@ bool Configuration::parse(String data, bool isLocal) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isLocal) {
|
||||||
|
if (JSON.typeof_(root[jprop_httpDomain]) == "string") {
|
||||||
|
String httpDomain = root[jprop_httpDomain];
|
||||||
|
String oldHttpDomain = jconfig[jprop_httpDomain];
|
||||||
|
if (httpDomain.length() <= 255) {
|
||||||
|
if (httpDomain != oldHttpDomain) {
|
||||||
|
changed = true;
|
||||||
|
configLogInfo(String(jprop_httpDomain), oldHttpDomain, httpDomain);
|
||||||
|
jconfig[jprop_httpDomain] = httpDomain;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
failedMessage = "\"httpDomain\" length should less than 255 character";
|
||||||
|
jsonInvalid();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (jsonTypeInvalid(root[jprop_httpDomain], "string")) {
|
||||||
|
failedMessage =
|
||||||
|
jsonTypeInvalidMessage(String(jprop_httpDomain), "string");
|
||||||
|
jsonInvalid();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (JSON.typeof_(root[jprop_temperatureUnit]) == "string") {
|
if (JSON.typeof_(root[jprop_temperatureUnit]) == "string") {
|
||||||
String unit = root[jprop_temperatureUnit];
|
String unit = root[jprop_temperatureUnit];
|
||||||
String oldUnit = jconfig[jprop_temperatureUnit];
|
String oldUnit = jconfig[jprop_temperatureUnit];
|
||||||
@ -628,6 +900,27 @@ bool Configuration::parse(String data, bool isLocal) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (JSON.typeof_(root[jprop_monitorDisplayCompensatedValues]) == "boolean") {
|
||||||
|
bool value = root[jprop_monitorDisplayCompensatedValues];
|
||||||
|
bool oldValue = jconfig[jprop_monitorDisplayCompensatedValues];
|
||||||
|
if (value != oldValue) {
|
||||||
|
changed = true;
|
||||||
|
jconfig[jprop_monitorDisplayCompensatedValues] = value;
|
||||||
|
|
||||||
|
configLogInfo(String(jprop_monitorDisplayCompensatedValues),
|
||||||
|
String(oldValue ? "true" : "false"),
|
||||||
|
String(value ? "true" : "false"));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (jsonTypeInvalid(root[jprop_monitorDisplayCompensatedValues],
|
||||||
|
"boolean")) {
|
||||||
|
failedMessage = jsonTypeInvalidMessage(
|
||||||
|
String(jprop_monitorDisplayCompensatedValues), "boolean");
|
||||||
|
jsonInvalid();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (ag->getBoardType() == ONE_INDOOR ||
|
if (ag->getBoardType() == ONE_INDOOR ||
|
||||||
ag->getBoardType() == OPEN_AIR_OUTDOOR) {
|
ag->getBoardType() == OPEN_AIR_OUTDOOR) {
|
||||||
if (JSON.typeof_(root["targetFirmware"]) == "string") {
|
if (JSON.typeof_(root["targetFirmware"]) == "string") {
|
||||||
@ -636,20 +929,35 @@ 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("");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// PM2.5 Corrections
|
||||||
|
if (updatePmCorrection(root)) {
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Temperature correction
|
||||||
|
if (updateTempHumCorrection(root, tempCorrection, jprop_atmp)) {
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Relative humidity correction
|
||||||
|
if (updateTempHumCorrection(root, rhumCorrection, jprop_rhum)) {
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
|
||||||
if (changed) {
|
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;
|
||||||
@ -757,6 +1065,16 @@ String Configuration::getMqttBrokerUri(void) {
|
|||||||
return broker;
|
return broker;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get HTTP domain for post measures and get configuration
|
||||||
|
*
|
||||||
|
* @return String http domain, might be empty string
|
||||||
|
*/
|
||||||
|
String Configuration::getHttpDomain(void) {
|
||||||
|
String httpDomain = jconfig[jprop_httpDomain];
|
||||||
|
return httpDomain;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Get configuratoin post data to AirGradient cloud
|
* @brief Get configuratoin post data to AirGradient cloud
|
||||||
*
|
*
|
||||||
@ -836,13 +1154,13 @@ 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;
|
||||||
}
|
}
|
||||||
|
|
||||||
String Configuration::jsonTypeInvalidMessage(String name, String type) {
|
String Configuration::jsonTypeInvalidMessage(String name, String type) {
|
||||||
return "'" + name + "' type invalid, it's should '" + type + "'";
|
return "'" + name + "' type is invalid, expecting '" + type + "'";
|
||||||
}
|
}
|
||||||
|
|
||||||
String Configuration::jsonValueInvalidMessage(String name, String value) {
|
String Configuration::jsonValueInvalidMessage(String name, String value) {
|
||||||
@ -882,20 +1200,20 @@ void Configuration::toConfig(const char *buf) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool changed = false;
|
bool changed = false;
|
||||||
bool isInvalid = false;
|
bool isConfigFieldInvalid = false;
|
||||||
|
|
||||||
/** Validate country */
|
/** Validate country */
|
||||||
if (JSON.typeof_(jconfig[jprop_country]) != "string") {
|
if (JSON.typeof_(jconfig[jprop_country]) != "string") {
|
||||||
isInvalid = true;
|
isConfigFieldInvalid = true;
|
||||||
} else {
|
} else {
|
||||||
String country = jconfig[jprop_country];
|
String country = jconfig[jprop_country];
|
||||||
if (country.length() != 2) {
|
if (country.length() != 2) {
|
||||||
isInvalid = true;
|
isConfigFieldInvalid = true;
|
||||||
} else {
|
} else {
|
||||||
isInvalid = false;
|
isConfigFieldInvalid = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (isInvalid) {
|
if (isConfigFieldInvalid) {
|
||||||
jconfig[jprop_country] = jprop_country_default;
|
jconfig[jprop_country] = jprop_country_default;
|
||||||
changed = true;
|
changed = true;
|
||||||
logInfo("toConfig: country changed");
|
logInfo("toConfig: country changed");
|
||||||
@ -903,17 +1221,17 @@ void Configuration::toConfig(const char *buf) {
|
|||||||
|
|
||||||
/** validate: PM standard */
|
/** validate: PM standard */
|
||||||
if (JSON.typeof_(jconfig[jprop_pmStandard]) != "string") {
|
if (JSON.typeof_(jconfig[jprop_pmStandard]) != "string") {
|
||||||
isInvalid = true;
|
isConfigFieldInvalid = true;
|
||||||
} else {
|
} else {
|
||||||
String standard = jconfig[jprop_pmStandard];
|
String standard = jconfig[jprop_pmStandard];
|
||||||
if (standard != getPMStandardString(true) &&
|
if (standard != getPMStandardString(true) &&
|
||||||
standard != getPMStandardString(false)) {
|
standard != getPMStandardString(false)) {
|
||||||
isInvalid = true;
|
isConfigFieldInvalid = true;
|
||||||
} else {
|
} else {
|
||||||
isInvalid = false;
|
isConfigFieldInvalid = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (isInvalid) {
|
if (isConfigFieldInvalid) {
|
||||||
jconfig[jprop_pmStandard] = jprop_pmStandard_default;
|
jconfig[jprop_pmStandard] = jprop_pmStandard_default;
|
||||||
changed = true;
|
changed = true;
|
||||||
logInfo("toConfig: pmStandard changed");
|
logInfo("toConfig: pmStandard changed");
|
||||||
@ -921,18 +1239,18 @@ void Configuration::toConfig(const char *buf) {
|
|||||||
|
|
||||||
/** validate led bar mode */
|
/** validate led bar mode */
|
||||||
if (JSON.typeof_(jconfig[jprop_ledBarMode]) != "string") {
|
if (JSON.typeof_(jconfig[jprop_ledBarMode]) != "string") {
|
||||||
isInvalid = true;
|
isConfigFieldInvalid = true;
|
||||||
} else {
|
} else {
|
||||||
String mode = jconfig[jprop_ledBarMode];
|
String mode = jconfig[jprop_ledBarMode];
|
||||||
if (mode != getLedBarModeName(LedBarMode::LedBarModeCO2) &&
|
if (mode != getLedBarModeName(LedBarMode::LedBarModeCO2) &&
|
||||||
mode != getLedBarModeName(LedBarMode::LedBarModeOff) &&
|
mode != getLedBarModeName(LedBarMode::LedBarModeOff) &&
|
||||||
mode != getLedBarModeName(LedBarMode::LedBarModePm)) {
|
mode != getLedBarModeName(LedBarMode::LedBarModePm)) {
|
||||||
isInvalid = true;
|
isConfigFieldInvalid = true;
|
||||||
} else {
|
} else {
|
||||||
isInvalid = false;
|
isConfigFieldInvalid = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (isInvalid) {
|
if (isConfigFieldInvalid) {
|
||||||
jconfig[jprop_ledBarMode] = jprop_ledBarMode_default;
|
jconfig[jprop_ledBarMode] = jprop_ledBarMode_default;
|
||||||
changed = true;
|
changed = true;
|
||||||
logInfo("toConfig: ledBarMode changed");
|
logInfo("toConfig: ledBarMode changed");
|
||||||
@ -940,11 +1258,11 @@ void Configuration::toConfig(const char *buf) {
|
|||||||
|
|
||||||
/** validate abcday */
|
/** validate abcday */
|
||||||
if (JSON.typeof_(jconfig[jprop_abcDays]) != "number") {
|
if (JSON.typeof_(jconfig[jprop_abcDays]) != "number") {
|
||||||
isInvalid = true;
|
isConfigFieldInvalid = true;
|
||||||
} else {
|
} else {
|
||||||
isInvalid = false;
|
isConfigFieldInvalid = false;
|
||||||
}
|
}
|
||||||
if (isInvalid) {
|
if (isConfigFieldInvalid) {
|
||||||
jconfig[jprop_abcDays] = jprop_abcDays_default;
|
jconfig[jprop_abcDays] = jprop_abcDays_default;
|
||||||
changed = true;
|
changed = true;
|
||||||
logInfo("toConfig: abcDays changed");
|
logInfo("toConfig: abcDays changed");
|
||||||
@ -952,16 +1270,16 @@ void Configuration::toConfig(const char *buf) {
|
|||||||
|
|
||||||
/** validate tvoc learning offset */
|
/** validate tvoc learning offset */
|
||||||
if (JSON.typeof_(jconfig[jprop_tvocLearningOffset]) != "number") {
|
if (JSON.typeof_(jconfig[jprop_tvocLearningOffset]) != "number") {
|
||||||
isInvalid = true;
|
isConfigFieldInvalid = true;
|
||||||
} else {
|
} else {
|
||||||
int value = jconfig[jprop_tvocLearningOffset];
|
int value = jconfig[jprop_tvocLearningOffset];
|
||||||
if (value < 0) {
|
if (value < 0) {
|
||||||
isInvalid = true;
|
isConfigFieldInvalid = true;
|
||||||
} else {
|
} else {
|
||||||
isInvalid = false;
|
isConfigFieldInvalid = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (isInvalid) {
|
if (isConfigFieldInvalid) {
|
||||||
jconfig[jprop_tvocLearningOffset] = jprop_tvocLearningOffset_default;
|
jconfig[jprop_tvocLearningOffset] = jprop_tvocLearningOffset_default;
|
||||||
changed = true;
|
changed = true;
|
||||||
logInfo("toConfig: tvocLearningOffset changed");
|
logInfo("toConfig: tvocLearningOffset changed");
|
||||||
@ -969,16 +1287,16 @@ void Configuration::toConfig(const char *buf) {
|
|||||||
|
|
||||||
/** validate nox learning offset */
|
/** validate nox learning offset */
|
||||||
if (JSON.typeof_(jconfig[jprop_noxLearningOffset]) != "number") {
|
if (JSON.typeof_(jconfig[jprop_noxLearningOffset]) != "number") {
|
||||||
isInvalid = true;
|
isConfigFieldInvalid = true;
|
||||||
} else {
|
} else {
|
||||||
int value = jconfig[jprop_noxLearningOffset];
|
int value = jconfig[jprop_noxLearningOffset];
|
||||||
if (value < 0) {
|
if (value < 0) {
|
||||||
isInvalid = true;
|
isConfigFieldInvalid = true;
|
||||||
} else {
|
} else {
|
||||||
isInvalid = false;
|
isConfigFieldInvalid = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (isInvalid) {
|
if (isConfigFieldInvalid) {
|
||||||
jconfig[jprop_noxLearningOffset] = jprop_noxLearningOffset_default;
|
jconfig[jprop_noxLearningOffset] = jprop_noxLearningOffset_default;
|
||||||
changed = true;
|
changed = true;
|
||||||
logInfo("toConfig: noxLearningOffset changed");
|
logInfo("toConfig: noxLearningOffset changed");
|
||||||
@ -986,36 +1304,60 @@ void Configuration::toConfig(const char *buf) {
|
|||||||
|
|
||||||
/** validate mqtt broker */
|
/** validate mqtt broker */
|
||||||
if (JSON.typeof_(jconfig[jprop_mqttBrokerUrl]) != "string") {
|
if (JSON.typeof_(jconfig[jprop_mqttBrokerUrl]) != "string") {
|
||||||
isInvalid = true;
|
isConfigFieldInvalid = true;
|
||||||
} else {
|
} else {
|
||||||
isInvalid = false;
|
isConfigFieldInvalid = false;
|
||||||
}
|
}
|
||||||
if (isInvalid) {
|
if (isConfigFieldInvalid) {
|
||||||
changed = true;
|
changed = true;
|
||||||
jconfig[jprop_mqttBrokerUrl] = jprop_mqttBrokerUrl_default;
|
jconfig[jprop_mqttBrokerUrl] = jprop_mqttBrokerUrl_default;
|
||||||
logInfo("toConfig: mqttBroker changed");
|
logInfo("toConfig: mqttBroker changed");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** validate http domain */
|
||||||
|
if (JSON.typeof_(jconfig[jprop_httpDomain]) != "string") {
|
||||||
|
isConfigFieldInvalid = true;
|
||||||
|
} else {
|
||||||
|
isConfigFieldInvalid = false;
|
||||||
|
}
|
||||||
|
if (isConfigFieldInvalid) {
|
||||||
|
changed = true;
|
||||||
|
jconfig[jprop_httpDomain] = jprop_httpDomain_default;
|
||||||
|
logInfo("toConfig: httpDomain changed");
|
||||||
|
}
|
||||||
|
|
||||||
/** Validate temperature unit */
|
/** Validate temperature unit */
|
||||||
if (JSON.typeof_(jconfig[jprop_temperatureUnit]) != "string") {
|
if (JSON.typeof_(jconfig[jprop_temperatureUnit]) != "string") {
|
||||||
isInvalid = true;
|
isConfigFieldInvalid = true;
|
||||||
} else {
|
} else {
|
||||||
String unit = jconfig[jprop_temperatureUnit];
|
String unit = jconfig[jprop_temperatureUnit];
|
||||||
if (unit != "c" && unit != "f") {
|
if (unit != "c" && unit != "f") {
|
||||||
isInvalid = true;
|
isConfigFieldInvalid = true;
|
||||||
} else {
|
} else {
|
||||||
isInvalid = false;
|
isConfigFieldInvalid = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (isInvalid) {
|
if (isConfigFieldInvalid) {
|
||||||
jconfig[jprop_temperatureUnit] = jprop_temperatureUnit_default;
|
jconfig[jprop_temperatureUnit] = jprop_temperatureUnit_default;
|
||||||
changed = true;
|
changed = true;
|
||||||
logInfo("toConfig: temperatureUnit changed");
|
logInfo("toConfig: temperatureUnit changed");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** validate disableCloudConnection configuration */
|
||||||
|
if (JSON.typeof_(jconfig[jprop_disableCloudConnection]) != "boolean") {
|
||||||
|
isConfigFieldInvalid = true;
|
||||||
|
} else {
|
||||||
|
isConfigFieldInvalid = false;
|
||||||
|
}
|
||||||
|
if (isConfigFieldInvalid) {
|
||||||
|
jconfig[jprop_disableCloudConnection] = jprop_disableCloudConnection_default;
|
||||||
|
changed = true;
|
||||||
|
logInfo("toConfig: disableCloudConnection changed");
|
||||||
|
}
|
||||||
|
|
||||||
/** validate configuration control */
|
/** validate configuration control */
|
||||||
if (JSON.typeof_(jprop_configurationControl) != "string") {
|
if (JSON.typeof_(jprop_configurationControl) != "string") {
|
||||||
isInvalid = true;
|
isConfigFieldInvalid = true;
|
||||||
} else {
|
} else {
|
||||||
String ctrl = jconfig[jprop_configurationControl];
|
String ctrl = jconfig[jprop_configurationControl];
|
||||||
if (ctrl != String(CONFIGURATION_CONTROL_NAME
|
if (ctrl != String(CONFIGURATION_CONTROL_NAME
|
||||||
@ -1024,12 +1366,12 @@ void Configuration::toConfig(const char *buf) {
|
|||||||
[ConfigurationControl::ConfigurationControlLocal]) &&
|
[ConfigurationControl::ConfigurationControlLocal]) &&
|
||||||
ctrl != String(CONFIGURATION_CONTROL_NAME
|
ctrl != String(CONFIGURATION_CONTROL_NAME
|
||||||
[ConfigurationControl::ConfigurationControlCloud])) {
|
[ConfigurationControl::ConfigurationControlCloud])) {
|
||||||
isInvalid = true;
|
isConfigFieldInvalid = true;
|
||||||
} else {
|
} else {
|
||||||
isInvalid = false;
|
isConfigFieldInvalid = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (isInvalid) {
|
if (isConfigFieldInvalid) {
|
||||||
jconfig[jprop_configurationControl] =jprop_configurationControl_default;
|
jconfig[jprop_configurationControl] =jprop_configurationControl_default;
|
||||||
changed = true;
|
changed = true;
|
||||||
logInfo("toConfig: configurationControl changed");
|
logInfo("toConfig: configurationControl changed");
|
||||||
@ -1037,11 +1379,11 @@ void Configuration::toConfig(const char *buf) {
|
|||||||
|
|
||||||
/** Validate post to airgradient cloud */
|
/** Validate post to airgradient cloud */
|
||||||
if (JSON.typeof_(jconfig[jprop_postDataToAirGradient]) != "boolean") {
|
if (JSON.typeof_(jconfig[jprop_postDataToAirGradient]) != "boolean") {
|
||||||
isInvalid = true;
|
isConfigFieldInvalid = true;
|
||||||
} else {
|
} else {
|
||||||
isInvalid = false;
|
isConfigFieldInvalid = false;
|
||||||
}
|
}
|
||||||
if (isInvalid) {
|
if (isConfigFieldInvalid) {
|
||||||
jconfig[jprop_postDataToAirGradient] = jprop_postDataToAirGradient_default;
|
jconfig[jprop_postDataToAirGradient] = jprop_postDataToAirGradient_default;
|
||||||
changed = true;
|
changed = true;
|
||||||
logInfo("toConfig: postToAirGradient changed");
|
logInfo("toConfig: postToAirGradient changed");
|
||||||
@ -1049,16 +1391,16 @@ void Configuration::toConfig(const char *buf) {
|
|||||||
|
|
||||||
/** validate led bar brightness */
|
/** validate led bar brightness */
|
||||||
if (JSON.typeof_(jconfig[jprop_ledBarBrightness]) != "number") {
|
if (JSON.typeof_(jconfig[jprop_ledBarBrightness]) != "number") {
|
||||||
isInvalid = true;
|
isConfigFieldInvalid = true;
|
||||||
} else {
|
} else {
|
||||||
int value = jconfig[jprop_ledBarBrightness];
|
int value = jconfig[jprop_ledBarBrightness];
|
||||||
if (value < 0 || value > 100) {
|
if (value < 0 || value > 100) {
|
||||||
isInvalid = true;
|
isConfigFieldInvalid = true;
|
||||||
} else {
|
} else {
|
||||||
isInvalid = false;
|
isConfigFieldInvalid = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (isInvalid) {
|
if (isConfigFieldInvalid) {
|
||||||
jconfig[jprop_ledBarBrightness] = jprop_ledBarBrightness_default;
|
jconfig[jprop_ledBarBrightness] = jprop_ledBarBrightness_default;
|
||||||
changed = true;
|
changed = true;
|
||||||
logInfo("toConfig: ledBarBrightness changed");
|
logInfo("toConfig: ledBarBrightness changed");
|
||||||
@ -1066,30 +1408,59 @@ void Configuration::toConfig(const char *buf) {
|
|||||||
|
|
||||||
/** Validate display brightness */
|
/** Validate display brightness */
|
||||||
if (JSON.typeof_(jconfig[jprop_displayBrightness]) != "number") {
|
if (JSON.typeof_(jconfig[jprop_displayBrightness]) != "number") {
|
||||||
isInvalid = true;
|
isConfigFieldInvalid = true;
|
||||||
} else {
|
} else {
|
||||||
int value = jconfig[jprop_displayBrightness];
|
int value = jconfig[jprop_displayBrightness];
|
||||||
if (value < 0 || value > 100) {
|
if (value < 0 || value > 100) {
|
||||||
isInvalid = true;
|
isConfigFieldInvalid = true;
|
||||||
} else {
|
} else {
|
||||||
isInvalid = false;
|
isConfigFieldInvalid = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (isInvalid) {
|
if (isConfigFieldInvalid) {
|
||||||
jconfig[jprop_displayBrightness] = jprop_displayBrightness_default;
|
jconfig[jprop_displayBrightness] = jprop_displayBrightness_default;
|
||||||
changed = true;
|
changed = true;
|
||||||
logInfo("toConfig: displayBrightness changed");
|
logInfo("toConfig: displayBrightness changed");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (JSON.typeof_(jconfig[jprop_offlineMode]) != "boolean") {
|
if (JSON.typeof_(jconfig[jprop_offlineMode]) != "boolean") {
|
||||||
isInvalid = true;
|
changed = true;
|
||||||
} else {
|
jconfig[jprop_offlineMode] = jprop_offlineMode_default;
|
||||||
isInvalid = false;
|
|
||||||
}
|
}
|
||||||
if (isInvalid) {
|
|
||||||
jconfig[jprop_offlineMode] = false;
|
/** Validate monitorDisplayCompensatedValues */
|
||||||
|
if (JSON.typeof_(jconfig[jprop_monitorDisplayCompensatedValues]) !=
|
||||||
|
"boolean") {
|
||||||
|
changed = true;
|
||||||
|
jconfig[jprop_monitorDisplayCompensatedValues] =
|
||||||
|
jprop_monitorDisplayCompensatedValues_default;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// PM2.5 correction
|
||||||
|
/// Set default first before parsing local config
|
||||||
|
pmCorrection.algorithm = COR_ALGO_PM_NONE;
|
||||||
|
pmCorrection.intercept = 0;
|
||||||
|
pmCorrection.scalingFactor = 0;
|
||||||
|
pmCorrection.useEPA = false;
|
||||||
|
/// Load correction from saved config
|
||||||
|
updatePmCorrection(jconfig);
|
||||||
|
|
||||||
|
// Temperature correction
|
||||||
|
/// Set default first before parsing local config
|
||||||
|
tempCorrection.algorithm = COR_ALGO_TEMP_HUM_NONE;
|
||||||
|
tempCorrection.intercept = 0;
|
||||||
|
tempCorrection.scalingFactor = 0;
|
||||||
|
/// Load correction from saved config
|
||||||
|
updateTempHumCorrection(jconfig, tempCorrection, jprop_atmp);
|
||||||
|
|
||||||
|
// Relative humidity correction
|
||||||
|
/// Set default first before parsing local config
|
||||||
|
rhumCorrection.algorithm = COR_ALGO_TEMP_HUM_NONE;
|
||||||
|
rhumCorrection.intercept = 0;
|
||||||
|
rhumCorrection.scalingFactor = 0;
|
||||||
|
/// Load correction from saved config
|
||||||
|
updateTempHumCorrection(jconfig, rhumCorrection, jprop_rhum);
|
||||||
|
|
||||||
if (changed) {
|
if (changed) {
|
||||||
saveConfig();
|
saveConfig();
|
||||||
}
|
}
|
||||||
@ -1167,12 +1538,27 @@ void Configuration::setOfflineModeWithoutSave(bool offline) {
|
|||||||
_offlineMode = offline;
|
_offlineMode = offline;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool Configuration::isCloudConnectionDisabled(void) {
|
||||||
|
bool disabled = jconfig[jprop_disableCloudConnection];
|
||||||
|
return disabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Configuration::setDisableCloudConnection(bool disable) {
|
||||||
|
logInfo("Set DisableCloudConnection to " + String(disable ? "True" : "False"));
|
||||||
|
jconfig[jprop_disableCloudConnection] = disable;
|
||||||
|
saveConfig();
|
||||||
|
}
|
||||||
|
|
||||||
bool Configuration::isLedBarModeChanged(void) {
|
bool Configuration::isLedBarModeChanged(void) {
|
||||||
bool changed = _ledBarModeChanged;
|
bool changed = _ledBarModeChanged;
|
||||||
_ledBarModeChanged = false;
|
_ledBarModeChanged = false;
|
||||||
return changed;
|
return changed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool Configuration::isMonitorDisplayCompensatedValues(void) {
|
||||||
|
return jconfig[jprop_monitorDisplayCompensatedValues];
|
||||||
|
}
|
||||||
|
|
||||||
bool Configuration::isDisplayBrightnessChanged(void) {
|
bool Configuration::isDisplayBrightnessChanged(void) {
|
||||||
bool changed = displayBrightnessChanged;
|
bool changed = displayBrightnessChanged;
|
||||||
displayBrightnessChanged = false;
|
displayBrightnessChanged = false;
|
||||||
@ -1184,3 +1570,30 @@ 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 == COR_ALGO_PM_NONE ||
|
||||||
|
pmCorrection.algorithm == COR_ALGO_PM_UNKNOWN) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
Configuration::PMCorrection Configuration::getPMCorrection(void) { return pmCorrection; }
|
||||||
|
|
||||||
|
Configuration::TempHumCorrection Configuration::getTempCorrection(void) { return tempCorrection; }
|
||||||
|
|
||||||
|
Configuration::TempHumCorrection Configuration::getHumCorrection(void) { return rhumCorrection; }
|
||||||
|
@ -5,12 +5,29 @@
|
|||||||
#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;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct TempHumCorrection {
|
||||||
|
TempHumCorrectionAlgorithm algorithm;
|
||||||
|
float intercept;
|
||||||
|
float scalingFactor;
|
||||||
|
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 +36,18 @@ private:
|
|||||||
String otaNewFirmwareVersion;
|
String otaNewFirmwareVersion;
|
||||||
bool _offlineMode = false;
|
bool _offlineMode = false;
|
||||||
bool _ledBarModeChanged = false;
|
bool _ledBarModeChanged = false;
|
||||||
|
PMCorrection pmCorrection;
|
||||||
|
TempHumCorrection tempCorrection;
|
||||||
|
TempHumCorrection rhumCorrection;
|
||||||
|
|
||||||
AirGradient* ag;
|
AirGradient* ag;
|
||||||
|
|
||||||
String getLedBarModeName(LedBarMode mode);
|
String getLedBarModeName(LedBarMode mode);
|
||||||
|
PMCorrectionAlgorithm matchPmAlgorithm(String algorithm);
|
||||||
|
TempHumCorrectionAlgorithm matchTempHumAlgorithm(String algorithm);
|
||||||
|
bool updatePmCorrection(JSONVar &json);
|
||||||
|
bool updateTempHumCorrection(JSONVar &json, TempHumCorrection &target,
|
||||||
|
const char *correctionName);
|
||||||
void saveConfig(void);
|
void saveConfig(void);
|
||||||
void loadConfig(void);
|
void loadConfig(void);
|
||||||
void defaultConfig(void);
|
void defaultConfig(void);
|
||||||
@ -33,7 +58,7 @@ private:
|
|||||||
void configLogInfo(String name, String fromValue, String toValue);
|
void configLogInfo(String name, String fromValue, String toValue);
|
||||||
String getPMStandardString(bool usaqi);
|
String getPMStandardString(bool usaqi);
|
||||||
String getAbcDayString(int value);
|
String getAbcDayString(int value);
|
||||||
void toConfig(const char* buf);
|
void toConfig(const char *buf);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
Configuration(Stream &debugLog);
|
Configuration(Stream &debugLog);
|
||||||
@ -57,6 +82,7 @@ public:
|
|||||||
String getLedBarModeName(void);
|
String getLedBarModeName(void);
|
||||||
bool getDisplayMode(void);
|
bool getDisplayMode(void);
|
||||||
String getMqttBrokerUri(void);
|
String getMqttBrokerUri(void);
|
||||||
|
String getHttpDomain(void);
|
||||||
bool isPostDataToAirGradient(void);
|
bool isPostDataToAirGradient(void);
|
||||||
ConfigurationControl getConfigurationControl(void);
|
ConfigurationControl getConfigurationControl(void);
|
||||||
bool isCo2CalibrationRequested(void);
|
bool isCo2CalibrationRequested(void);
|
||||||
@ -81,7 +107,15 @@ public:
|
|||||||
bool isOfflineMode(void);
|
bool isOfflineMode(void);
|
||||||
void setOfflineMode(bool offline);
|
void setOfflineMode(bool offline);
|
||||||
void setOfflineModeWithoutSave(bool offline);
|
void setOfflineModeWithoutSave(bool offline);
|
||||||
|
bool isCloudConnectionDisabled(void);
|
||||||
|
void setDisableCloudConnection(bool disable);
|
||||||
bool isLedBarModeChanged(void);
|
bool isLedBarModeChanged(void);
|
||||||
|
bool isMonitorDisplayCompensatedValues(void);
|
||||||
|
bool isPMCorrectionChanged(void);
|
||||||
|
bool isPMCorrectionEnabled(void);
|
||||||
|
PMCorrection getPMCorrection(void);
|
||||||
|
TempHumCorrection getTempCorrection(void);
|
||||||
|
TempHumCorrection getHumCorrection(void);
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif /** _AG_CONFIG_H_ */
|
#endif /** _AG_CONFIG_H_ */
|
||||||
|
@ -5,29 +5,55 @@
|
|||||||
/** Cast U8G2 */
|
/** Cast U8G2 */
|
||||||
#define DISP() ((U8G2_SH1106_128X64_NONAME_F_HW_I2C *)(this->u8g2))
|
#define DISP() ((U8G2_SH1106_128X64_NONAME_F_HW_I2C *)(this->u8g2))
|
||||||
|
|
||||||
|
static const unsigned char WIFI_ISSUE_BITS[] = {
|
||||||
|
0xd8, 0xc6, 0xde, 0xde, 0xc7, 0xf8, 0xd1, 0xe2, 0xdc, 0xce, 0xcc,
|
||||||
|
0xcc, 0xc0, 0xc0, 0xd0, 0xc2, 0x00, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0};
|
||||||
|
|
||||||
|
static const unsigned char CLOUD_ISSUE_BITS[] = {
|
||||||
|
0x70, 0xc0, 0x88, 0xc0, 0x04, 0xc1, 0x04, 0xcf, 0x02, 0xd0, 0x01,
|
||||||
|
0xe0, 0x01, 0xe0, 0x01, 0xe0, 0xa2, 0xd0, 0x4c, 0xce, 0xa0, 0xc0};
|
||||||
|
|
||||||
|
// Offline mode icon
|
||||||
|
static unsigned char OFFLINE_BITS[] = {
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x30, 0x00, 0x62, 0x00,
|
||||||
|
0xE6, 0x00, 0xFE, 0x1F, 0xFE, 0x1F, 0xE6, 0x00, 0x62, 0x00,
|
||||||
|
0x30, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
};
|
||||||
|
// {
|
||||||
|
// 0x00, 0x00, 0x00, 0x00, 0x30, 0x00, 0x60, 0x00, 0x62, 0x00, 0xE2, 0x00,
|
||||||
|
// 0xFE, 0x1F, 0xFE, 0x1F, 0xE2, 0x00, 0x62, 0x00, 0x60, 0x00, 0x30, 0x00,
|
||||||
|
// 0x00, 0x00, 0x00, 0x00, };
|
||||||
/**
|
/**
|
||||||
* @brief Show dashboard temperature and humdity
|
* @brief Show dashboard temperature and humdity
|
||||||
*
|
*
|
||||||
* @param hasStatus
|
* @param hasStatus
|
||||||
*/
|
*/
|
||||||
void OledDisplay::showTempHum(bool hasStatus) {
|
void OledDisplay::showTempHum(bool hasStatus) {
|
||||||
char buf[16];
|
char buf[10];
|
||||||
if (utils::isValidTemperature(value.Temperature)) {
|
/** Temperature */
|
||||||
|
float temp = value.getCorrectedTempHum(Measurements::Temperature, 1);
|
||||||
|
if (utils::isValidTemperature(temp)) {
|
||||||
|
float t = 0.0f;
|
||||||
if (config.isTemperatureUnitInF()) {
|
if (config.isTemperatureUnitInF()) {
|
||||||
float tempF = (value.Temperature * 9) / 5 + 32;
|
t = utils::degreeC_To_F(temp);
|
||||||
if (hasStatus) {
|
|
||||||
snprintf(buf, sizeof(buf), "%0.1f", tempF);
|
|
||||||
} else {
|
} else {
|
||||||
snprintf(buf, sizeof(buf), "%0.1f°F", tempF);
|
t = temp;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config.isTemperatureUnitInF()) {
|
||||||
|
if (hasStatus) {
|
||||||
|
snprintf(buf, sizeof(buf), "%0.1f", t);
|
||||||
|
} else {
|
||||||
|
snprintf(buf, sizeof(buf), "%0.1f°F", t);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (hasStatus) {
|
if (hasStatus) {
|
||||||
snprintf(buf, sizeof(buf), "%.1f", value.Temperature);
|
snprintf(buf, sizeof(buf), "%.1f", t);
|
||||||
} else {
|
} else {
|
||||||
snprintf(buf, sizeof(buf), "%.1f°C", value.Temperature);
|
snprintf(buf, sizeof(buf), "%.1f°C", t);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else { /** Show invalid value */
|
||||||
if (config.isTemperatureUnitInF()) {
|
if (config.isTemperatureUnitInF()) {
|
||||||
snprintf(buf, sizeof(buf), "-°F");
|
snprintf(buf, sizeof(buf), "-°F");
|
||||||
} else {
|
} else {
|
||||||
@ -36,14 +62,15 @@ void OledDisplay::showTempHum(bool hasStatus) {
|
|||||||
}
|
}
|
||||||
DISP()->drawUTF8(1, 10, buf);
|
DISP()->drawUTF8(1, 10, buf);
|
||||||
|
|
||||||
/** Show humidty */
|
/** Show humidity */
|
||||||
if (utils::isValidHumidity(value.Humidity)) {
|
int rhum = round(value.getCorrectedTempHum(Measurements::Humidity, 1));
|
||||||
snprintf(buf, sizeof(buf), "%d%%", value.Humidity);
|
if (utils::isValidHumidity(rhum)) {
|
||||||
|
snprintf(buf, sizeof(buf), "%d%%", rhum);
|
||||||
} else {
|
} else {
|
||||||
snprintf(buf, sizeof(buf), "-%%");
|
snprintf(buf, sizeof(buf), "-%%");
|
||||||
}
|
}
|
||||||
|
|
||||||
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);
|
||||||
@ -59,6 +86,9 @@ void OledDisplay::setCentralText(int y, const char *text) {
|
|||||||
DISP()->drawStr(x, y, text);
|
DISP()->drawStr(x, y, text);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void OledDisplay::showIcon(int x, int y, xbm_icon_t *icon) {
|
||||||
|
DISP()->drawXBM(x, y, icon->width, icon->height, icon->icon);
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* @brief Construct a new Ag Oled Display:: Ag Oled Display object
|
* @brief Construct a new Ag Oled Display:: Ag Oled Display object
|
||||||
*
|
*
|
||||||
@ -244,36 +274,60 @@ void OledDisplay::setText(const char *line1, const char *line2,
|
|||||||
* @brief Update dashboard content
|
* @brief Update dashboard content
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
void OledDisplay::showDashboard(void) { showDashboard(NULL); }
|
void OledDisplay::showDashboard(void) { showDashboard(DashBoardStatusNone); }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Update dashboard content and error status
|
* @brief Update dashboard content and error status
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
void OledDisplay::showDashboard(const char *status) {
|
void OledDisplay::showDashboard(DashboardStatus status) {
|
||||||
if (isDisplayOff) {
|
if (isDisplayOff) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
char strBuf[16];
|
char strBuf[16];
|
||||||
|
const int icon_pos_x = 64;
|
||||||
|
xbm_icon_t xbm_icon = {
|
||||||
|
.width = 0,
|
||||||
|
.height = 0,
|
||||||
|
.icon = nullptr,
|
||||||
|
};
|
||||||
|
|
||||||
if (ag->isOne() || ag->isPro3_3() || ag->isPro4_2()) {
|
if (ag->isOne() || ag->isPro3_3() || ag->isPro4_2()) {
|
||||||
DISP()->firstPage();
|
DISP()->firstPage();
|
||||||
do {
|
do {
|
||||||
DISP()->setFont(u8g2_font_t0_16_tf);
|
DISP()->setFont(u8g2_font_t0_16_tf);
|
||||||
if ((status == NULL) || (strlen(status) == 0)) {
|
switch (status) {
|
||||||
|
case DashBoardStatusNone: {
|
||||||
|
// Maybe show signal strength?
|
||||||
showTempHum(false);
|
showTempHum(false);
|
||||||
} else {
|
break;
|
||||||
String strStatus = "Show status: " + String(status);
|
|
||||||
logInfo(strStatus);
|
|
||||||
|
|
||||||
int strWidth = DISP()->getStrWidth(status);
|
|
||||||
DISP()->drawStr((DISP()->getWidth() - strWidth) / 2, 10, status);
|
|
||||||
|
|
||||||
/** Show WiFi NA*/
|
|
||||||
if (strcmp(status, "WiFi N/A") == 0) {
|
|
||||||
DISP()->setFont(u8g2_font_t0_12_tf);
|
|
||||||
showTempHum(true);
|
|
||||||
}
|
}
|
||||||
|
case DashBoardStatusWiFiIssue: {
|
||||||
|
DISP()->drawXBM(icon_pos_x, 0, 14, 11, WIFI_ISSUE_BITS);
|
||||||
|
showTempHum(false);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case DashBoardStatusServerIssue: {
|
||||||
|
DISP()->drawXBM(icon_pos_x, 0, 14, 11, CLOUD_ISSUE_BITS);
|
||||||
|
showTempHum(false);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case DashBoardStatusAddToDashboard: {
|
||||||
|
setCentralText(10, "Add To Dashboard");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case DashBoardStatusDeviceId: {
|
||||||
|
setCentralText(10, ag->deviceId().c_str());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case DashBoardStatusOfflineMode: {
|
||||||
|
DISP()->drawXBM(icon_pos_x, 0, 14, 14, OFFLINE_BITS);
|
||||||
|
showTempHum(false); // First true
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Draw horizonal line */
|
/** Draw horizonal line */
|
||||||
@ -284,8 +338,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", "-");
|
||||||
}
|
}
|
||||||
@ -304,28 +359,27 @@ 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 */
|
||||||
int pm25 = value.pm25_1;
|
int pm25 = round(value.getAverage(Measurements::PM25));
|
||||||
if (config.hasSensorSHT) {
|
if (utils::isValidPm(pm25)) {
|
||||||
pm25 = ag->pms5003.compensated(pm25, value.Humidity);
|
if (config.hasSensorSHT && config.isPMCorrectionEnabled()) {
|
||||||
|
pm25 = round(value.getCorrectedPM25(true));
|
||||||
|
}
|
||||||
|
if (config.isPmStandardInUSAQI()) {
|
||||||
|
sprintf(strBuf, "%d", ag->pms5003.convertPm25ToUsAqi(pm25));
|
||||||
|
} else {
|
||||||
|
sprintf(strBuf, "%d", pm25);
|
||||||
|
}
|
||||||
|
} else { /** Show invalid value. */
|
||||||
|
sprintf(strBuf, "%s", "-");
|
||||||
}
|
}
|
||||||
DISP()->setFont(u8g2_font_t0_22b_tf);
|
DISP()->setFont(u8g2_font_t0_22b_tf);
|
||||||
if (config.isPmStandardInUSAQI()) {
|
|
||||||
if (utils::isValidPMS(value.pm25_1)) {
|
|
||||||
sprintf(strBuf, "%d", ag->pms5003.convertPm25ToUsAqi(value.pm25_1));
|
|
||||||
} else {
|
|
||||||
sprintf(strBuf, "%s", "-");
|
|
||||||
}
|
|
||||||
DISP()->drawStr(55, 48, strBuf);
|
DISP()->drawStr(55, 48, strBuf);
|
||||||
|
|
||||||
|
/** Draw PM2.5 unit */
|
||||||
DISP()->setFont(u8g2_font_t0_12_tf);
|
DISP()->setFont(u8g2_font_t0_12_tf);
|
||||||
|
if (config.isPmStandardInUSAQI()) {
|
||||||
DISP()->drawUTF8(55, 61, "AQI");
|
DISP()->drawUTF8(55, 61, "AQI");
|
||||||
} else {
|
} else {
|
||||||
if (utils::isValidPMS(value.pm25_1)) {
|
|
||||||
sprintf(strBuf, "%d", value.pm25_1);
|
|
||||||
} else {
|
|
||||||
sprintf(strBuf, "%s", "-");
|
|
||||||
}
|
|
||||||
DISP()->drawStr(55, 48, strBuf);
|
|
||||||
DISP()->setFont(u8g2_font_t0_12_tf);
|
|
||||||
DISP()->drawUTF8(55, 61, "ug/m³");
|
DISP()->drawUTF8(55, 61, "ug/m³");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -334,17 +388,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", "-");
|
||||||
}
|
}
|
||||||
@ -354,8 +410,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:-");
|
||||||
}
|
}
|
||||||
@ -364,25 +421,27 @@ 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) {
|
if (config.hasSensorSHT && config.isPMCorrectionEnabled()) {
|
||||||
pm25 = (int)ag->pms5003.compensated(pm25, value.Humidity);
|
pm25 = round(value.getCorrectedPM25(true));
|
||||||
}
|
}
|
||||||
|
|
||||||
ag->display.setCursor(0, 12);
|
ag->display.setCursor(0, 12);
|
||||||
if (utils::isValidPMS(value.pm25_1)) {
|
if (utils::isValidPm(pm25)) {
|
||||||
snprintf(strBuf, sizeof(strBuf), "PM2.5:%d", value.pm25_1);
|
snprintf(strBuf, sizeof(strBuf), "PM2.5:%d", pm25);
|
||||||
} else {
|
} else {
|
||||||
snprintf(strBuf, sizeof(strBuf), "PM2.5:-");
|
snprintf(strBuf, sizeof(strBuf), "PM2.5:-");
|
||||||
}
|
}
|
||||||
ag->display.setText(strBuf);
|
ag->display.setText(strBuf);
|
||||||
|
|
||||||
/** Set temperature and humidity */
|
/** Set temperature and humidity */
|
||||||
if (utils::isValidTemperature(value.Temperature)) {
|
float temp = value.getCorrectedTempHum(Measurements::Temperature, 1);
|
||||||
|
if (utils::isValidTemperature(temp)) {
|
||||||
if (config.isTemperatureUnitInF()) {
|
if (config.isTemperatureUnitInF()) {
|
||||||
float tempF = (value.Temperature * 9) / 5 + 32;
|
snprintf(strBuf, sizeof(strBuf), "T:%0.1f F",
|
||||||
snprintf(strBuf, sizeof(strBuf), "T:%0.1f F", tempF);
|
utils::degreeC_To_F(temp));
|
||||||
} 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()) {
|
||||||
@ -395,8 +454,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.getCorrectedTempHum(Measurements::Humidity, 1));
|
||||||
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:- %%");
|
||||||
}
|
}
|
||||||
@ -423,8 +483,17 @@ void OledDisplay::setBrightness(int percent) {
|
|||||||
DISP()->setContrast((127 * percent) / 100);
|
DISP()->setContrast((127 * percent) / 100);
|
||||||
}
|
}
|
||||||
} else if (ag->isBasic()) {
|
} else if (ag->isBasic()) {
|
||||||
|
if (percent == 0) {
|
||||||
|
isDisplayOff = true;
|
||||||
|
|
||||||
|
// Clear display.
|
||||||
|
ag->display.clear();
|
||||||
|
ag->display.show();
|
||||||
|
} else {
|
||||||
|
isDisplayOff = false;
|
||||||
ag->display.setContrast((255 * percent) / 100);
|
ag->display.setContrast((255 * percent) / 100);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef ESP32
|
#ifdef ESP32
|
||||||
@ -518,7 +587,7 @@ void OledDisplay::showRebooting(void) {
|
|||||||
do {
|
do {
|
||||||
DISP()->setFont(u8g2_font_t0_16_tf);
|
DISP()->setFont(u8g2_font_t0_16_tf);
|
||||||
// setCentralText(20, "Firmware Update");
|
// setCentralText(20, "Firmware Update");
|
||||||
setCentralText(40, "Reboot...");
|
setCentralText(40, "Rebooting...");
|
||||||
// setCentralText(60, String("Retry after 24h"));
|
// setCentralText(60, String("Retry after 24h"));
|
||||||
} while (DISP()->nextPage());
|
} while (DISP()->nextPage());
|
||||||
} else if (ag->isBasic()) {
|
} else if (ag->isBasic()) {
|
||||||
|
@ -16,15 +16,30 @@ private:
|
|||||||
Measurements &value;
|
Measurements &value;
|
||||||
bool isDisplayOff = false;
|
bool isDisplayOff = false;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
int width;
|
||||||
|
int height;
|
||||||
|
unsigned char *icon;
|
||||||
|
} xbm_icon_t;
|
||||||
|
|
||||||
void showTempHum(bool hasStatus);
|
void showTempHum(bool hasStatus);
|
||||||
void setCentralText(int y, String text);
|
void setCentralText(int y, String text);
|
||||||
void setCentralText(int y, const char *text);
|
void setCentralText(int y, const char *text);
|
||||||
|
void showIcon(int x, int y, xbm_icon_t *icon);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
OledDisplay(Configuration &config, Measurements &value,
|
OledDisplay(Configuration &config, Measurements &value, Stream &log);
|
||||||
Stream &log);
|
|
||||||
~OledDisplay();
|
~OledDisplay();
|
||||||
|
|
||||||
|
enum DashboardStatus {
|
||||||
|
DashBoardStatusNone,
|
||||||
|
DashBoardStatusWiFiIssue,
|
||||||
|
DashBoardStatusServerIssue,
|
||||||
|
DashBoardStatusAddToDashboard,
|
||||||
|
DashBoardStatusDeviceId,
|
||||||
|
DashBoardStatusOfflineMode,
|
||||||
|
};
|
||||||
|
|
||||||
void setAirGradient(AirGradient *ag);
|
void setAirGradient(AirGradient *ag);
|
||||||
bool begin(void);
|
bool begin(void);
|
||||||
void end(void);
|
void end(void);
|
||||||
@ -34,7 +49,7 @@ public:
|
|||||||
void setText(const char *line1, const char *line2, const char *line3,
|
void setText(const char *line1, const char *line2, const char *line3,
|
||||||
const char *line4);
|
const char *line4);
|
||||||
void showDashboard(void);
|
void showDashboard(void);
|
||||||
void showDashboard(const char *status);
|
void showDashboard(DashboardStatus status);
|
||||||
void setBrightness(int percent);
|
void setBrightness(int percent);
|
||||||
#ifdef ESP32
|
#ifdef ESP32
|
||||||
void showFirmwareUpdateVersion(String version);
|
void showFirmwareUpdateVersion(String version);
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
#include "AgStateMachine.h"
|
#include "AgStateMachine.h"
|
||||||
|
#include "AgOledDisplay.h"
|
||||||
|
|
||||||
|
#define LED_TEST_BLINK_DELAY 50 /** ms */
|
||||||
#define LED_FAST_BLINK_DELAY 250 /** ms */
|
#define LED_FAST_BLINK_DELAY 250 /** ms */
|
||||||
#define LED_SLOW_BLINK_DELAY 1000 /** ms */
|
#define LED_SLOW_BLINK_DELAY 1000 /** ms */
|
||||||
#define LED_SHORT_BLINK_DELAY 500 /** ms */
|
#define LED_SHORT_BLINK_DELAY 500 /** ms */
|
||||||
@ -12,6 +14,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
|
||||||
@ -46,47 +49,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);
|
||||||
@ -94,6 +117,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);
|
||||||
@ -102,6 +126,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);
|
||||||
@ -111,6 +136,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);
|
||||||
@ -121,6 +147,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);
|
||||||
@ -132,41 +159,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 (pm25Value < 5) {
|
|
||||||
|
int pm25Value = round(value.getAverage(Measurements::PM25));
|
||||||
|
if (config.hasSensorSHT && config.isPMCorrectionEnabled()) {
|
||||||
|
pm25Value = round(value.getCorrectedPM25(true));
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
@ -174,7 +216,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);
|
||||||
@ -183,7 +226,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);
|
||||||
@ -193,7 +237,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);
|
||||||
@ -204,7 +249,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);
|
||||||
@ -215,7 +261,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) {
|
||||||
@ -305,6 +354,8 @@ void StateMachine::co2Calibration(void) {
|
|||||||
|
|
||||||
void StateMachine::ledBarTest(void) {
|
void StateMachine::ledBarTest(void) {
|
||||||
if (config.isLedBarTestRequested()) {
|
if (config.isLedBarTestRequested()) {
|
||||||
|
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");
|
||||||
@ -319,12 +370,21 @@ void StateMachine::ledBarTest(void) {
|
|||||||
} else {
|
} else {
|
||||||
ledBarRunTest();
|
ledBarRunTest();
|
||||||
}
|
}
|
||||||
|
} else if (ag->isOpenAir()) {
|
||||||
|
ledBarRunTest();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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()) {
|
||||||
disp.setText("LED Test", "running", ".....");
|
disp.setText("LED Test", "running", ".....");
|
||||||
runLedTest('r');
|
runLedTest('r');
|
||||||
ag->ledBar.show();
|
ag->ledBar.show();
|
||||||
@ -341,6 +401,14 @@ void StateMachine::ledBarRunTest(void) {
|
|||||||
runLedTest('n');
|
runLedTest('n');
|
||||||
ag->ledBar.show();
|
ag->ledBar.show();
|
||||||
delay(1000);
|
delay(1000);
|
||||||
|
} else if (ag->isOpenAir()) {
|
||||||
|
for (int i = 0; i < 100; i++) {
|
||||||
|
ag->statusLed.setOn();
|
||||||
|
delay(LED_TEST_BLINK_DELAY);
|
||||||
|
ag->statusLed.setOff();
|
||||||
|
delay(LED_TEST_BLINK_DELAY);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void StateMachine::runLedTest(char color) {
|
void StateMachine::runLedTest(char color) {
|
||||||
@ -476,11 +544,11 @@ void StateMachine::displayHandle(AgStateMachineState state) {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case AgStateMachineWiFiLost: {
|
case AgStateMachineWiFiLost: {
|
||||||
disp.showDashboard("WiFi N/A");
|
disp.showDashboard(OledDisplay::DashBoardStatusWiFiIssue);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case AgStateMachineServerLost: {
|
case AgStateMachineServerLost: {
|
||||||
disp.showDashboard("Server N/A");
|
disp.showDashboard(OledDisplay::DashBoardStatusServerIssue);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case AgStateMachineSensorConfigFailed: {
|
case AgStateMachineSensorConfigFailed: {
|
||||||
@ -489,19 +557,24 @@ void StateMachine::displayHandle(AgStateMachineState state) {
|
|||||||
if (ms >= 5000) {
|
if (ms >= 5000) {
|
||||||
addToDashboardTime = millis();
|
addToDashboardTime = millis();
|
||||||
if (addToDashBoardToggle) {
|
if (addToDashBoardToggle) {
|
||||||
disp.showDashboard("Add to Dashboard");
|
disp.showDashboard(OledDisplay::DashBoardStatusAddToDashboard);
|
||||||
} else {
|
} else {
|
||||||
disp.showDashboard(ag->deviceId().c_str());
|
disp.showDashboard(OledDisplay::DashBoardStatusDeviceId);
|
||||||
}
|
}
|
||||||
addToDashBoardToggle = !addToDashBoardToggle;
|
addToDashBoardToggle = !addToDashBoardToggle;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
disp.showDashboard("");
|
disp.showDashboard();
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case AgStateMachineNormal: {
|
case AgStateMachineNormal: {
|
||||||
|
if (config.isOfflineMode()) {
|
||||||
|
disp.showDashboard(
|
||||||
|
OledDisplay::DashBoardStatusOfflineMode);
|
||||||
|
} else {
|
||||||
disp.showDashboard();
|
disp.showDashboard();
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case AgStateMachineCo2Calibration:
|
case AgStateMachineCo2Calibration:
|
||||||
@ -566,15 +639,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();
|
||||||
@ -584,6 +655,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();
|
||||||
@ -594,6 +666,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();
|
||||||
@ -603,6 +676,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();
|
||||||
@ -612,6 +686,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();
|
||||||
@ -621,6 +696,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();
|
||||||
@ -637,6 +713,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();
|
||||||
@ -655,6 +732,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();
|
||||||
@ -671,6 +749,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();
|
||||||
@ -688,11 +767,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();
|
||||||
}
|
}
|
||||||
@ -702,11 +780,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();
|
||||||
}
|
}
|
||||||
@ -716,10 +793,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);
|
||||||
|
1668
src/AgValue.cpp
312
src/AgValue.h
@ -1,76 +1,266 @@
|
|||||||
#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 <cstdint>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
class Measurements {
|
class Measurements {
|
||||||
private:
|
private:
|
||||||
|
// Generic struct for update indication for respective value
|
||||||
|
struct Update {
|
||||||
|
int invalidCounter; // Counting on how many invalid value that are passed to update function
|
||||||
|
int max; // Maximum length of the period of the moving average
|
||||||
|
float avg; // Moving average value, updated every update function called
|
||||||
|
};
|
||||||
|
|
||||||
|
// Reading type for sensor value that outputs float
|
||||||
|
struct FloatValue {
|
||||||
|
float sumValues; // Total value from each update
|
||||||
|
std::vector<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(Configuration &config);
|
||||||
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;
|
struct Measures {
|
||||||
int Humidity;
|
float temperature[2];
|
||||||
int CO2;
|
float humidity[2];
|
||||||
int TVOC;
|
float co2;
|
||||||
int TVOCRaw;
|
float tvoc; // Index value
|
||||||
int NOx;
|
float tvoc_raw;
|
||||||
int NOxRaw;
|
float nox; // Index value
|
||||||
|
float nox_raw;
|
||||||
int pm25_1;
|
float pm_01[2]; // pm 1.0 atmospheric environment
|
||||||
int pm01_1;
|
float pm_25[2]; // pm 2.5 atmospheric environment
|
||||||
int pm10_1;
|
float pm_10[2]; // pm 10 atmospheric environment
|
||||||
int pm03PCount_1;
|
float pm_01_sp[2]; // pm 1.0 standard particle
|
||||||
float temp_1;
|
float pm_25_sp[2]; // pm 2.5 standard particle
|
||||||
int hum_1;
|
float pm_10_sp[2]; // pm 10 standard particle
|
||||||
|
float pm_03_pc[2]; // particle count 0.3
|
||||||
int pm25_2;
|
float pm_05_pc[2]; // particle count 0.5
|
||||||
int pm01_2;
|
float pm_01_pc[2]; // particle count 1.0
|
||||||
int pm10_2;
|
float pm_25_pc[2]; // particle count 2.5
|
||||||
int pm03PCount_2;
|
float pm_5_pc[2]; // particle count 5.0
|
||||||
float temp_2;
|
float pm_10_pc[2]; // particle count 10
|
||||||
int hum_2;
|
|
||||||
|
|
||||||
int pm1Value01;
|
|
||||||
int pm1Value25;
|
|
||||||
int pm1Value10;
|
|
||||||
int pm1PCount;
|
|
||||||
int pm1temp;
|
|
||||||
int pm1hum;
|
|
||||||
int pm2Value01;
|
|
||||||
int pm2Value25;
|
|
||||||
int pm2Value10;
|
|
||||||
int pm2PCount;
|
|
||||||
int pm2temp;
|
|
||||||
int pm2hum;
|
|
||||||
int countPosition;
|
|
||||||
const int targetCount = 20;
|
|
||||||
int bootCount;
|
int bootCount;
|
||||||
|
int signal;
|
||||||
|
uint32_t freeHeap;
|
||||||
|
};
|
||||||
|
|
||||||
String toString(bool isLocal, AgFirmwareMode fwMode, int rssi, void* _ag, void* _config);
|
void setAirGradient(AirGradient *ag);
|
||||||
|
|
||||||
|
// Enumeration for every AG measurements
|
||||||
|
enum MeasurementType {
|
||||||
|
Temperature,
|
||||||
|
Humidity,
|
||||||
|
CO2,
|
||||||
|
TVOC, // index value
|
||||||
|
TVOCRaw,
|
||||||
|
NOx, // index value
|
||||||
|
NOxRaw,
|
||||||
|
PM01, // PM1.0 under atmospheric environment
|
||||||
|
PM25, // PM2.5 under athompheric environment
|
||||||
|
PM10, // PM10 under atmospheric environment
|
||||||
|
PM01_SP, // PM1.0 standard particle
|
||||||
|
PM25_SP, // PM2.5 standard particle
|
||||||
|
PM10_SP, // PM10 standard particle
|
||||||
|
PM03_PC, // Particle 0.3 count
|
||||||
|
PM05_PC, // Particle 0.5 count
|
||||||
|
PM01_PC, // Particle 1.0 count
|
||||||
|
PM25_PC, // Particle 2.5 count
|
||||||
|
PM5_PC, // Particle 5.0 count
|
||||||
|
PM10_PC, // Particle 10 count
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Set each MeasurementType maximum period length for moving average
|
||||||
|
*
|
||||||
|
* @param type the target measurement type to set
|
||||||
|
* @param max the maximum period length
|
||||||
|
*/
|
||||||
|
void maxPeriod(MeasurementType, int max);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief update target measurement type with new value.
|
||||||
|
* Each MeasurementType has last raw value and moving average value based on max period
|
||||||
|
* This function is for MeasurementType that use INT as the data type
|
||||||
|
*
|
||||||
|
* @param type measurement type that will be updated
|
||||||
|
* @param val (int) the new value
|
||||||
|
* @param ch (int) the MeasurementType channel, not every MeasurementType has more than 1 channel.
|
||||||
|
* Currently maximum channel is 2. Default: 1 (channel 1)
|
||||||
|
* @return false if new value invalid consecutively reach threshold (max period)
|
||||||
|
* @return true otherwise
|
||||||
|
*/
|
||||||
|
bool update(MeasurementType type, int val, int ch = 1);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief update target measurement type with new value.
|
||||||
|
* Each MeasurementType has last raw value and moving average value based on max period
|
||||||
|
* This function is for MeasurementType that use FLOAT as the data type
|
||||||
|
*
|
||||||
|
* @param type measurement type that will be updated
|
||||||
|
* @param val (float) the new value
|
||||||
|
* @param ch (int) the MeasurementType channel, not every MeasurementType has more than 1 channel.
|
||||||
|
* Currently maximum channel is 2. Default: 1 (channel 1)
|
||||||
|
* @return false if new value invalid consecutively reach threshold (max period)
|
||||||
|
* @return true otherwise
|
||||||
|
*/
|
||||||
|
bool update(MeasurementType type, float val, int ch = 1);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get the target measurement latest value
|
||||||
|
*
|
||||||
|
* @param type measurement type that will be retrieve
|
||||||
|
* @param ch target type value channel
|
||||||
|
* @return int measurement type value
|
||||||
|
*/
|
||||||
|
int get(MeasurementType type, int ch = 1);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get the target measurement latest value
|
||||||
|
*
|
||||||
|
* @param type measurement type that will be retrieve
|
||||||
|
* @param ch target type value channel
|
||||||
|
* @return float measurement type value
|
||||||
|
*/
|
||||||
|
float getFloat(MeasurementType type, int ch = 1);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get the 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 Temperature or Humidity correction value
|
||||||
|
* Only if correction is applied from configuration or forceCorrection is True
|
||||||
|
*
|
||||||
|
* @param type measurement type either Temperature or Humidity
|
||||||
|
* @param ch target type value channel
|
||||||
|
* @param forceCorrection force using correction even though config correction is not applied, but
|
||||||
|
* not for CUSTOM
|
||||||
|
* @return correction value
|
||||||
|
*/
|
||||||
|
float getCorrectedTempHum(MeasurementType type, int ch = 1, bool forceCorrection = false);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get the Corrected PM25 object based on the correction algorithm from configuration
|
||||||
|
*
|
||||||
|
* If correction is not enabled, then will return the raw value (either average or last value)
|
||||||
|
*
|
||||||
|
* @param useAvg Use moving average value if true, otherwise use latest value
|
||||||
|
* @param ch MeasurementType channel
|
||||||
|
* @param forceCorrection force using correction even though config correction is not applied, default to EPA
|
||||||
|
* @return float Corrected PM2.5 value
|
||||||
|
*/
|
||||||
|
float getCorrectedPM25(bool useAvg = false, int ch = 1, bool forceCorrection = false);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* build json payload for every measurements
|
||||||
|
*/
|
||||||
|
String toString(bool localServer, AgFirmwareMode fwMode, int rssi);
|
||||||
|
|
||||||
|
Measures getMeasures();
|
||||||
|
|
||||||
|
std::string buildMeasuresPayload(Measures &measures);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set to true if want to debug every update value
|
||||||
|
*/
|
||||||
|
void setDebug(bool debug);
|
||||||
|
|
||||||
|
int bootCount();
|
||||||
|
void setBootCount(int bootCount);
|
||||||
|
|
||||||
|
#ifndef ESP8266
|
||||||
|
void setResetReason(esp_reset_reason_t reason);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
private:
|
||||||
|
Configuration &config;
|
||||||
|
AirGradient *ag;
|
||||||
|
|
||||||
|
// Some declared as an array (channel), because FW_MODE_O_1PPx has two PMS5003T
|
||||||
|
FloatValue _temperature[2];
|
||||||
|
FloatValue _humidity[2];
|
||||||
|
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
|
||||||
|
int _bootCount;
|
||||||
|
int _resetReason;
|
||||||
|
bool _debug = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get PMS5003 firmware version string
|
||||||
|
*
|
||||||
|
* @param fwCode
|
||||||
|
* @return String
|
||||||
|
*/
|
||||||
|
String pms5003FirmwareVersion(int fwCode);
|
||||||
|
/**
|
||||||
|
* @brief Get PMS5003T firmware version string
|
||||||
|
*
|
||||||
|
* @param fwCode
|
||||||
|
* @return String
|
||||||
|
*/
|
||||||
|
String pms5003TFirmwareVersion(int fwCode);
|
||||||
|
/**
|
||||||
|
* @brief Get firmware version string
|
||||||
|
*
|
||||||
|
* @param prefix Prefix firmware string
|
||||||
|
* @param fwCode Version code
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
String pms5003FirmwareVersionBase(String prefix, int fwCode);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert AgValue Type to string representation of the value
|
||||||
|
*/
|
||||||
|
String measurementTypeStr(MeasurementType type);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief check if provided channel is a valid channel or not
|
||||||
|
* abort program if invalid
|
||||||
|
*/
|
||||||
|
void validateChannel(int ch);
|
||||||
|
|
||||||
|
JSONVar buildOutdoor(bool localServer, AgFirmwareMode fwMode);
|
||||||
|
JSONVar buildIndoor(bool localServer);
|
||||||
|
JSONVar buildPMS(int ch, bool allCh, bool withTempHum, bool compensate);
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif /** _AG_VALUE_H_ */
|
#endif /** _AG_VALUE_H_ */
|
||||||
|
@ -41,6 +41,28 @@ bool WifiConnector::connect(void) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
WiFi.begin();
|
||||||
|
String wifiSSID = WIFI()->getWiFiSSID(true);
|
||||||
|
if (wifiSSID.isEmpty()) {
|
||||||
|
logInfo("Connected WiFi is empty, connect to default wifi \"" +
|
||||||
|
String(this->defaultSsid) + String("\""));
|
||||||
|
|
||||||
|
/** Set wifi connect */
|
||||||
|
WiFi.begin(this->defaultSsid, this->defaultPassword);
|
||||||
|
|
||||||
|
/** Wait for wifi connect to AP */
|
||||||
|
int count = 0;
|
||||||
|
while (WiFi.status() != WL_CONNECTED) {
|
||||||
|
delay(1000);
|
||||||
|
count++;
|
||||||
|
if (count >= 15) {
|
||||||
|
logError("Try connect to default wifi \"" + String(this->defaultSsid) +
|
||||||
|
String("\" failed"));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
WIFI()->setConfigPortalBlocking(false);
|
WIFI()->setConfigPortalBlocking(false);
|
||||||
WIFI()->setConnectTimeout(15);
|
WIFI()->setConnectTimeout(15);
|
||||||
WIFI()->setTimeout(WIFI_CONNECT_COUNTDOWN_MAX);
|
WIFI()->setTimeout(WIFI_CONNECT_COUNTDOWN_MAX);
|
||||||
@ -50,7 +72,7 @@ bool WifiConnector::connect(void) {
|
|||||||
WIFI()->setSaveParamsCallback([this]() { _wifiSaveParamCallback(); });
|
WIFI()->setSaveParamsCallback([this]() { _wifiSaveParamCallback(); });
|
||||||
WIFI()->setConfigPortalTimeoutCallback([this]() {_wifiTimeoutCallback();});
|
WIFI()->setConfigPortalTimeoutCallback([this]() {_wifiTimeoutCallback();});
|
||||||
if (ag->isOne() || (ag->isPro4_2()) || ag->isPro3_3() || ag->isBasic()) {
|
if (ag->isOne() || (ag->isPro4_2()) || ag->isPro3_3() || ag->isBasic()) {
|
||||||
disp.setText("Connect to", "WiFi", "...");
|
disp.setText("Connecting to", "WiFi", "...");
|
||||||
} else {
|
} else {
|
||||||
logInfo("Connecting to WiFi...");
|
logInfo("Connecting to WiFi...");
|
||||||
}
|
}
|
||||||
@ -59,16 +81,15 @@ bool WifiConnector::connect(void) {
|
|||||||
// ssid = "AG-" + String(ESP.getChipId(), HEX);
|
// ssid = "AG-" + String(ESP.getChipId(), HEX);
|
||||||
WIFI()->setConfigPortalTimeout(WIFI_CONNECT_COUNTDOWN_MAX);
|
WIFI()->setConfigPortalTimeout(WIFI_CONNECT_COUNTDOWN_MAX);
|
||||||
|
|
||||||
WiFiManagerParameter postToAg("chbPostToAg",
|
WiFiManagerParameter disableCloud("chbPostToAg", "Prevent Connection to AirGradient Server", "T",
|
||||||
"Prevent Connection to AirGradient Server", "T",
|
|
||||||
2, "type=\"checkbox\" ", WFM_LABEL_AFTER);
|
2, "type=\"checkbox\" ", WFM_LABEL_AFTER);
|
||||||
WIFI()->addParameter(&postToAg);
|
WIFI()->addParameter(&disableCloud);
|
||||||
WiFiManagerParameter postToAgInfo(
|
WiFiManagerParameter disableCloudInfo(
|
||||||
"<p>Prevent connection to the AirGradient Server. Important: Only enable "
|
"<p>Prevent connection to the AirGradient Server. Important: Only enable "
|
||||||
"it if you are sure you don't want to use any AirGradient cloud "
|
"it if you are sure you don't want to use any AirGradient cloud "
|
||||||
"features. As a result you will not receive automatic firmware updates "
|
"features. As a result you will not receive automatic firmware updates, "
|
||||||
"and your data will not reach the AirGradient dashboard.</p>");
|
"configuration settings from cloud and the measure data will not reach the AirGradient dashboard.</p>");
|
||||||
WIFI()->addParameter(&postToAgInfo);
|
WIFI()->addParameter(&disableCloudInfo);
|
||||||
|
|
||||||
WIFI()->autoConnect(ssid.c_str(), WIFI_HOTSPOT_PASSWORD_DEFAULT);
|
WIFI()->autoConnect(ssid.c_str(), WIFI_HOTSPOT_PASSWORD_DEFAULT);
|
||||||
|
|
||||||
@ -152,12 +173,11 @@ bool WifiConnector::connect(void) {
|
|||||||
logInfo("WiFi Connected: " + WiFi.SSID() + " IP: " + localIpStr());
|
logInfo("WiFi Connected: " + WiFi.SSID() + " IP: " + localIpStr());
|
||||||
|
|
||||||
if (hasPortalConfig) {
|
if (hasPortalConfig) {
|
||||||
String result = String(postToAg.getValue());
|
String result = String(disableCloud.getValue());
|
||||||
logInfo("Setting postToAirGradient set from " +
|
logInfo("Setting disableCloudConnection set from " +
|
||||||
String(config.isPostDataToAirGradient() ? "True" : "False") +
|
String(config.isCloudConnectionDisabled() ? "True" : "False") + String(" to ") +
|
||||||
String(" to ") + String(result != "T" ? "True" : "False") +
|
String(result == "T" ? "True" : "False") + String(" successful"));
|
||||||
String(" successful"));
|
config.setDisableCloudConnection(result == "T");
|
||||||
config.setPostToAirGradient(result != "T");
|
|
||||||
}
|
}
|
||||||
hasPortalConfig = false;
|
hasPortalConfig = false;
|
||||||
}
|
}
|
||||||
@ -383,3 +403,11 @@ bool WifiConnector::hasConfigurated(void) {
|
|||||||
* @return false
|
* @return false
|
||||||
*/
|
*/
|
||||||
bool WifiConnector::isConfigurePorttalTimeout(void) { return connectorTimeout; }
|
bool WifiConnector::isConfigurePorttalTimeout(void) { return connectorTimeout; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Set wifi connect to default WiFi
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
void WifiConnector::setDefault(void) {
|
||||||
|
WiFi.begin("airgradient", "cleanair");
|
||||||
|
}
|
||||||
|
@ -46,6 +46,10 @@ public:
|
|||||||
String localIpStr(void);
|
String localIpStr(void);
|
||||||
bool hasConfigurated(void);
|
bool hasConfigurated(void);
|
||||||
bool isConfigurePorttalTimeout(void);
|
bool isConfigurePorttalTimeout(void);
|
||||||
|
|
||||||
|
const char* defaultSsid = "airgradient";
|
||||||
|
const char* defaultPassword = "cleanair";
|
||||||
|
void setDefault(void);
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif /** _AG_WIFI_CONNECTOR_H_ */
|
#endif /** _AG_WIFI_CONNECTOR_H_ */
|
||||||
|
@ -65,6 +65,10 @@ bool AirGradient::isOne(void) {
|
|||||||
return boardType == BoardType::ONE_INDOOR;
|
return boardType == BoardType::ONE_INDOOR;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool AirGradient::isOpenAir(void) {
|
||||||
|
return boardType == BoardType::OPEN_AIR_OUTDOOR;
|
||||||
|
}
|
||||||
|
|
||||||
bool AirGradient::isPro4_2(void) {
|
bool AirGradient::isPro4_2(void) {
|
||||||
return boardType == BoardType::DIY_PRO_INDOOR_V4_2;
|
return boardType == BoardType::DIY_PRO_INDOOR_V4_2;
|
||||||
}
|
}
|
||||||
|
@ -15,7 +15,47 @@
|
|||||||
#include "Main/utils.h"
|
#include "Main/utils.h"
|
||||||
|
|
||||||
#ifndef GIT_VERSION
|
#ifndef GIT_VERSION
|
||||||
#define GIT_VERSION "3.1.5-snap"
|
#define GIT_VERSION "3.3.6-snap"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
#ifndef ESP8266
|
||||||
|
// Airgradient server root ca certificate
|
||||||
|
const char *const AG_SERVER_ROOT_CA =
|
||||||
|
"-----BEGIN CERTIFICATE-----\n"
|
||||||
|
"MIIF4jCCA8oCCQD7MgvcaVWxkTANBgkqhkiG9w0BAQsFADCBsjELMAkGA1UEBhMC\n"
|
||||||
|
"VEgxEzARBgNVBAgMCkNoaWFuZyBNYWkxEDAOBgNVBAcMB01hZSBSaW0xGTAXBgNV\n"
|
||||||
|
"BAoMEEFpckdyYWRpZW50IEx0ZC4xFDASBgNVBAsMC1NlbnNvciBMYWJzMSgwJgYD\n"
|
||||||
|
"VQQDDB9BaXJHcmFkaWVudCBTZW5zb3IgTGFicyBSb290IENBMSEwHwYJKoZIhvcN\n"
|
||||||
|
"AQkBFhJjYUBhaXJncmFkaWVudC5jb20wHhcNMjEwOTE3MTE0NDE3WhcNNDEwOTEy\n"
|
||||||
|
"MTE0NDE3WjCBsjELMAkGA1UEBhMCVEgxEzARBgNVBAgMCkNoaWFuZyBNYWkxEDAO\n"
|
||||||
|
"BgNVBAcMB01hZSBSaW0xGTAXBgNVBAoMEEFpckdyYWRpZW50IEx0ZC4xFDASBgNV\n"
|
||||||
|
"BAsMC1NlbnNvciBMYWJzMSgwJgYDVQQDDB9BaXJHcmFkaWVudCBTZW5zb3IgTGFi\n"
|
||||||
|
"cyBSb290IENBMSEwHwYJKoZIhvcNAQkBFhJjYUBhaXJncmFkaWVudC5jb20wggIi\n"
|
||||||
|
"MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC6XkVQ4O9d5GcUjPYRgF/uaY6O\n"
|
||||||
|
"5ry1xCGvotxkEeKkBk99lB1oNUUfNsP5bwuDci4XKfY9Ro6/jmkfHSVcPAwUnjAt\n"
|
||||||
|
"BcHqZtA/cMXykaynf9yXPxPQN7XLu/Rk32RIfb90sIGS318xgNziCYvzWZmlxpxc\n"
|
||||||
|
"3gUcAgGtamlgZ6wD3yOHVo8B9aFNvmP16QwkUm8fKDHunJG+iX2Bxa4ka5FJovhG\n"
|
||||||
|
"TnUwtso6Vrn0JaWF9qWcPZE0JZMjFW8PYRriyJmHwr/nAXfPPKphD1oRO+oA7/jq\n"
|
||||||
|
"dYkrJw6+OHfFXnPB1xkeh4OPBzcCZHT5XWNfwBYazYpjcJa9ngGFSmg8lX1ac23C\n"
|
||||||
|
"zea1XJmSrPwbZbWxoQznnf7Y78mRjruYKgSP8rf74KYvBe/HGPL5NQyXQ3l6kwmu\n"
|
||||||
|
"CCUqfcC0wCWEtWESxwSdFE2qQii8CZ12kQExzvR2PrOIyKQYSdkGx9/RBZtAVPXP\n"
|
||||||
|
"hmLuRBQYHrF5Cxf1oIbBK8OMoNVgBm6ftt15t9Sq9dH5Aup2YR6WEJkVaYkYzZzK\n"
|
||||||
|
"X7M+SQcdbXp+hAO8PFpABJxkaDAO2kiB5Ov7pDYPAcmNFqnJT48AY0TZJeVeCa5W\n"
|
||||||
|
"sIv3lPvB/XcFjP0+aZxxNSEEwpGPUYgvKUYUUmb0NammlYQwZHKaShPEmZ3UZ0bp\n"
|
||||||
|
"VNt4p6374nzO376sSwIDAQABMA0GCSqGSIb3DQEBCwUAA4ICAQB/LfBPgTx7xKQB\n"
|
||||||
|
"JNMUhah17AFAn050NiviGJOHdPQely6u3DmJGg+ijEVlPWO1FEW3it+LOuNP5zOu\n"
|
||||||
|
"bhq8paTYIxPxtALIxw5ksykX9woDuX3H6FF9mPdQIbL7ft+3ZtZ4FWPui9dUtaPe\n"
|
||||||
|
"ZBmDFDi4U29nhWZK68JSp5QkWjfaYLV/vtag7120eVyGEPFZ0UAuTUNqpw+stOt9\n"
|
||||||
|
"gJ2ZxNx13xJ8ZnLK7qz1crPe8/8IVAdxbVLoY7JaWPLc//+VF+ceKicy8+4gV7zN\n"
|
||||||
|
"Gnq2IyM+CHFz8VYMLbW+3eVp4iJjTa72vae116kozboEIUVN9rgLqIKyVqQXiuoN\n"
|
||||||
|
"g3xY+yfncPB2+H/+lfyy6mepPIfgksd3+KeNxFADSc5EVY2JKEdorRodnAh7a8K6\n"
|
||||||
|
"WjTYgq+GjWXU2uQW2SyPt6Tu33OT8nBnu3NB80eT8WXgdVCkgsuyCuLvNRf1Xmze\n"
|
||||||
|
"igvurpU6JmQ1GlLgLJo8omJHTh1zIbkR9injPYne2v9ciHCoP6+LDEqe+rOsvPCB\n"
|
||||||
|
"C/o/iZ4svmYX4fWGuU7GgqZE8hhrC3+GdOTf2ADC752cYCZxBidXGtkrGNoHQKmQ\n"
|
||||||
|
"KCOMFBxZIvWteB3tUo3BKYz1D2CvKWz1wV4moc5JHkOgS+jqxhvOkQ/vfQBQ1pUY\n"
|
||||||
|
"TMui9BSwU7B1G2XjdLbfF3Dc67zaSg==\n"
|
||||||
|
"-----END CERTIFICATE-----\n";
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -135,6 +175,14 @@ public:
|
|||||||
*/
|
*/
|
||||||
bool isOne(void);
|
bool isOne(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Check that Airgradient object is OPEN_AIR
|
||||||
|
*
|
||||||
|
* @return true
|
||||||
|
* @return false
|
||||||
|
*/
|
||||||
|
bool isOpenAir(void);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Check that Airgradient object is DIY_PRO 4.2 indoor
|
* @brief Check that Airgradient object is DIY_PRO 4.2 indoor
|
||||||
*
|
*
|
||||||
|
@ -94,6 +94,21 @@ enum ConfigurationControl {
|
|||||||
ConfigurationControlBoth
|
ConfigurationControlBoth
|
||||||
};
|
};
|
||||||
|
|
||||||
|
enum PMCorrectionAlgorithm {
|
||||||
|
COR_ALGO_PM_UNKNOWN, // Unknown algorithm
|
||||||
|
COR_ALGO_PM_NONE, // No PM correction
|
||||||
|
COR_ALGO_PM_EPA_2021,
|
||||||
|
COR_ALGO_PM_SLR_CUSTOM,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Don't change the order of the enum
|
||||||
|
enum TempHumCorrectionAlgorithm {
|
||||||
|
COR_ALGO_TEMP_HUM_UNKNOWN, // Unknown algorithm
|
||||||
|
COR_ALGO_TEMP_HUM_NONE, // No PM correction
|
||||||
|
COR_ALGO_TEMP_HUM_AG_PMS5003T_2024,
|
||||||
|
COR_ALGO_TEMP_HUM_SLR_CUSTOM
|
||||||
|
};
|
||||||
|
|
||||||
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 */
|
||||||
|
1
src/Libraries/airgradient-client
Submodule
1
src/Libraries/airgradient-ota
Submodule
@ -48,14 +48,14 @@ bool utils::isValidCO2(int16_t value) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool utils::isValidPMS(int value) {
|
bool utils::isValidPm(int value) {
|
||||||
if ((value >= VALID_PMS_MIN) && (value <= VALID_PMS_MAX)) {
|
if ((value >= VALID_PMS_MIN) && (value <= VALID_PMS_MAX)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool utils::isValidPMS03Count(int value) {
|
bool utils::isValidPm03Count(int value) {
|
||||||
if (value >= VALID_PMS03COUNT_MIN) {
|
if (value >= VALID_PMS03COUNT_MIN) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -82,8 +82,13 @@ float utils::getInvalidHumidity(void) { return INVALID_HUMIDITY; }
|
|||||||
|
|
||||||
int utils::getInvalidCO2(void) { return INVALID_CO2; }
|
int utils::getInvalidCO2(void) { return INVALID_CO2; }
|
||||||
|
|
||||||
int utils::getInvalidPMS(void) { return INVALID_PMS; }
|
int utils::getInvalidPmValue(void) { return INVALID_PMS; }
|
||||||
|
|
||||||
int utils::getInvalidNOx(void) { return INVALID_NOX; }
|
int utils::getInvalidNOx(void) { return INVALID_NOX; }
|
||||||
|
|
||||||
int utils::getInvalidVOC(void) { return INVALID_VOC; }
|
int utils::getInvalidVOC(void) { return INVALID_VOC; }
|
||||||
|
|
||||||
|
float utils::degreeC_To_F(float t) {
|
||||||
|
/** (t * 9)/5 + 32 */
|
||||||
|
return t * 1.8f + 32.0f;
|
||||||
|
}
|
||||||
|
@ -14,16 +14,17 @@ public:
|
|||||||
static bool isValidTemperature(float value);
|
static bool isValidTemperature(float value);
|
||||||
static bool isValidHumidity(float value);
|
static bool isValidHumidity(float value);
|
||||||
static bool isValidCO2(int16_t value);
|
static bool isValidCO2(int16_t value);
|
||||||
static bool isValidPMS(int value);
|
static bool isValidPm(int value);
|
||||||
static bool isValidPMS03Count(int value);
|
static bool isValidPm03Count(int value);
|
||||||
static bool isValidNOx(int value);
|
static bool isValidNOx(int value);
|
||||||
static bool isValidVOC(int value);
|
static bool isValidVOC(int value);
|
||||||
static float getInvalidTemperature(void);
|
static float getInvalidTemperature(void);
|
||||||
static float getInvalidHumidity(void);
|
static float getInvalidHumidity(void);
|
||||||
static int getInvalidCO2(void);
|
static int getInvalidCO2(void);
|
||||||
static int getInvalidPMS(void);
|
static int getInvalidPmValue(void);
|
||||||
static int getInvalidNOx(void);
|
static int getInvalidNOx(void);
|
||||||
static int getInvalidVOC(void);
|
static int getInvalidVOC(void);
|
||||||
|
static float degreeC_To_F(float t);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
386
src/PMS/PMS.cpp
@ -2,248 +2,294 @@
|
|||||||
#include "../Main/BoardDef.h"
|
#include "../Main/BoardDef.h"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Init and check that sensor has connected
|
* @brief Initializes the sensor and attempts to read data.
|
||||||
*
|
*
|
||||||
* @param stream UART stream
|
* @param stream UART stream
|
||||||
* @return true Sucecss
|
* @return true Sucecss
|
||||||
* @return false Failure
|
* @return false Failure
|
||||||
*/
|
*/
|
||||||
bool PMSBase::begin(Stream *stream) {
|
bool PMSBase::begin(Stream *stream) {
|
||||||
this->stream = stream;
|
Serial.printf("initializing PM sensor\n");
|
||||||
|
|
||||||
failed = true;
|
failCount = 0;
|
||||||
lastRead = 0; // To read buffer on handle without wait after 1.5sec
|
_connected = false;
|
||||||
|
|
||||||
this->stream->flush();
|
// empty first
|
||||||
|
int bytesCleared = 0;
|
||||||
|
while (stream->read() != -1) {
|
||||||
|
bytesCleared++;
|
||||||
|
}
|
||||||
|
Serial.printf("cleared %d byte(s)\n", bytesCleared);
|
||||||
|
|
||||||
|
// explicitly put the sensor into active mode, this seems to be be needed for the Cubic PM2009X
|
||||||
|
Serial.printf("setting active mode\n");
|
||||||
|
uint8_t activeModeCommand[] = { 0x42, 0x4D, 0xE1, 0x00, 0x01, 0x01, 0x71 };
|
||||||
|
size_t bytesWritten = stream->write(activeModeCommand, sizeof(activeModeCommand));
|
||||||
|
Serial.printf("%d byte(s) written\n", bytesWritten);
|
||||||
|
|
||||||
// Run and check sensor data for 4sec
|
// Run and check sensor data for 4sec
|
||||||
while (1) {
|
unsigned long lastInit = millis();
|
||||||
handle();
|
while (true) {
|
||||||
if (failed == false) {
|
readPackage(stream);
|
||||||
return true;
|
if (_connected) {
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
delay(1);
|
delay(1);
|
||||||
uint32_t ms = (uint32_t)(millis() - lastRead);
|
unsigned long ms = (unsigned long)(millis() - lastInit);
|
||||||
if (ms >= 4000) {
|
if (ms >= 4000) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false;
|
return _connected;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Check and read sensor data then update variable.
|
* @brief Read PMS package send to device each 1sec
|
||||||
* Check result from method @isFailed before get value
|
*
|
||||||
|
* @param serial
|
||||||
*/
|
*/
|
||||||
void PMSBase::handle() {
|
void PMSBase::readPackage(Stream *serial) {
|
||||||
uint32_t ms;
|
/** If readPackage has process as period larger than READ_PACKAGE_TIMEOUT,
|
||||||
if (lastRead == 0) {
|
* should be clear the lastPackage and readBufferIndex */
|
||||||
lastRead = millis();
|
if (lastReadPackage) {
|
||||||
if (lastRead == 0) {
|
unsigned long ms = (unsigned long)(millis() - lastReadPackage);
|
||||||
lastRead = 1;
|
if (ms >= READ_PACKGE_TIMEOUT) {
|
||||||
|
/** Clear buffer */
|
||||||
|
readBufferIndex = 0;
|
||||||
|
|
||||||
|
/** Disable check read package timeout */
|
||||||
|
lastPackage = 0;
|
||||||
|
|
||||||
|
Serial.println("Last process timeout, clear buffer and last handle package");
|
||||||
|
}
|
||||||
|
|
||||||
|
lastReadPackage = millis();
|
||||||
|
if (!lastReadPackage) {
|
||||||
|
lastReadPackage = 1;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
ms = (uint32_t)(millis() - lastRead);
|
lastReadPackage = millis();
|
||||||
/**
|
if (!lastReadPackage) {
|
||||||
* The PMS in Active mode sends an update data every 1 second. If we read
|
lastReadPackage = 1;
|
||||||
* exactly every 1 sec then we may or may not get an update (depending on
|
|
||||||
* timing tolerances). Hence we read every 2.5 seconds and expect 2 ..3
|
|
||||||
* updates,
|
|
||||||
*/
|
|
||||||
if (ms < 2500) {
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
bool result = false;
|
|
||||||
char buf[32];
|
|
||||||
int bufIndex;
|
|
||||||
int step = 0;
|
|
||||||
int len = 0;
|
|
||||||
int bcount = 0;
|
|
||||||
|
|
||||||
while (stream->available()) {
|
/** Count to call delay() to release the while loop MCU resource for avoid the
|
||||||
char value = stream->read();
|
* watchdog time reset */
|
||||||
switch (step) {
|
uint8_t delayCount = 0;
|
||||||
case 0: {
|
while (serial->available()) {
|
||||||
|
/** Get value */
|
||||||
|
uint8_t value = (uint8_t)serial->read();
|
||||||
|
|
||||||
|
/** Process receiving package... */
|
||||||
|
switch (readBufferIndex) {
|
||||||
|
case 0: /** Start byte 1 */
|
||||||
if (value == 0x42) {
|
if (value == 0x42) {
|
||||||
step = 1;
|
readBuffer[readBufferIndex++] = value;
|
||||||
bufIndex = 0;
|
|
||||||
buf[bufIndex++] = value;
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
case 1: /** Start byte 2 */
|
||||||
case 1: {
|
|
||||||
if (value == 0x4d) {
|
if (value == 0x4d) {
|
||||||
step = 2;
|
readBuffer[readBufferIndex++] = value;
|
||||||
buf[bufIndex++] = value;
|
|
||||||
// Serial.println("Got 0x4d");
|
|
||||||
} else {
|
} else {
|
||||||
step = 0;
|
readBufferIndex = 0;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
case 2: /** Frame length */
|
||||||
case 2: {
|
if (value == 0x00) {
|
||||||
buf[bufIndex++] = value;
|
readBuffer[readBufferIndex++] = value;
|
||||||
if (bufIndex >= 4) {
|
|
||||||
len = toI16(&buf[2]);
|
|
||||||
if (len != 28) {
|
|
||||||
// Serial.printf("Got good bad len %d\r\n", len);
|
|
||||||
len += 4;
|
|
||||||
step = 3;
|
|
||||||
} else {
|
} else {
|
||||||
// Serial.println("Got good len");
|
readBufferIndex = 0;
|
||||||
step = 4;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
case 3: /** Frame length */
|
||||||
case 3: {
|
if (value == 0x1C) {
|
||||||
bufIndex++;
|
readBuffer[readBufferIndex++] = value;
|
||||||
if (bufIndex >= len) {
|
} else {
|
||||||
step = 0;
|
readBufferIndex = 0;
|
||||||
// Serial.println("Bad lengh read all buffer");
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
default: /** Data */
|
||||||
|
{
|
||||||
|
readBuffer[readBufferIndex++] = value;
|
||||||
|
|
||||||
|
/** Check that received full bufer */
|
||||||
|
if (readBufferIndex >= sizeof(readBuffer)) {
|
||||||
|
/** validata package */
|
||||||
|
if (validate(readBuffer)) {
|
||||||
|
_connected = true; /** Set connected status */
|
||||||
|
|
||||||
|
/** Parse data */
|
||||||
|
parse(readBuffer);
|
||||||
|
|
||||||
|
/** Set last received package */
|
||||||
|
lastPackage = millis();
|
||||||
|
if (lastPackage == 0) {
|
||||||
|
lastPackage = 1;
|
||||||
}
|
}
|
||||||
case 4: {
|
|
||||||
buf[bufIndex++] = value;
|
|
||||||
if (bufIndex >= 32) {
|
|
||||||
result |= validate(buf);
|
|
||||||
step = 0;
|
|
||||||
// Serial.println("Got data");
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reduce core panic: delay 1 ms each 32bytes data
|
/** Clear buffer index */
|
||||||
bcount++;
|
readBufferIndex = 0;
|
||||||
if ((bcount % 32) == 0) {
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Avoid task watchdog timer reset... */
|
||||||
|
delayCount++;
|
||||||
|
if (delayCount >= 32) {
|
||||||
|
delayCount = 0;
|
||||||
delay(1);
|
delay(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (result) {
|
/** Check that sensor removed */
|
||||||
lastRead = millis();
|
if (lastPackage) {
|
||||||
if (lastRead == 0) {
|
unsigned long ms = (unsigned long)(millis() - lastPackage);
|
||||||
lastRead = 1;
|
if (ms >= READ_PACKGE_TIMEOUT) {
|
||||||
}
|
lastPackage = 0;
|
||||||
failed = false;
|
_connected = false;
|
||||||
} else {
|
Serial.println("PMS disconnected");
|
||||||
if (ms > 5000) {
|
|
||||||
failed = true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Check that PMS send is failed or disconnected
|
* @brief Increate number of fail
|
||||||
*
|
*
|
||||||
* @return true Failed
|
|
||||||
* @return false No problem
|
|
||||||
*/
|
*/
|
||||||
bool PMSBase::isFailed(void) { return failed; }
|
void PMSBase::updateFailCount(void) {
|
||||||
|
if (failCount < failCountMax) {
|
||||||
|
failCount++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void PMSBase::resetFailCount(void) { failCount = 0; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get number of fail
|
||||||
|
*
|
||||||
|
* @return int
|
||||||
|
*/
|
||||||
|
int PMSBase::getFailCount(void) { return failCount; }
|
||||||
|
|
||||||
|
int PMSBase::getFailCountMax(void) { return failCountMax; }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Read PMS 0.1 ug/m3 with CF = 1 PM estimates
|
* @brief Read PMS 0.1 ug/m3 with CF = 1 PM estimates
|
||||||
*
|
*
|
||||||
* @return uint16_t
|
* @return uint16_t
|
||||||
*/
|
*/
|
||||||
uint16_t PMSBase::getRaw0_1(void) { return toU16(&package[4]); }
|
uint16_t PMSBase::getRaw0_1(void) { return pms_raw0_1; }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Read PMS 2.5 ug/m3 with CF = 1 PM estimates
|
* @brief Read PMS 2.5 ug/m3 with CF = 1 PM estimates
|
||||||
*
|
*
|
||||||
* @return uint16_t
|
* @return uint16_t
|
||||||
*/
|
*/
|
||||||
uint16_t PMSBase::getRaw2_5(void) { return toU16(&package[6]); }
|
uint16_t PMSBase::getRaw2_5(void) { return pms_raw2_5; }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Read PMS 10 ug/m3 with CF = 1 PM estimates
|
* @brief Read PMS 10 ug/m3 with CF = 1 PM estimates
|
||||||
*
|
*
|
||||||
* @return uint16_t
|
* @return uint16_t
|
||||||
*/
|
*/
|
||||||
uint16_t PMSBase::getRaw10(void) { return toU16(&package[8]); }
|
uint16_t PMSBase::getRaw10(void) { return pms_raw10; }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Read PMS 0.1 ug/m3
|
* @brief Read PMS 0.1 ug/m3
|
||||||
*
|
*
|
||||||
* @return uint16_t
|
* @return uint16_t
|
||||||
*/
|
*/
|
||||||
uint16_t PMSBase::getPM0_1(void) { return toU16(&package[10]); }
|
uint16_t PMSBase::getPM0_1(void) { return pms_pm0_1; }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Read PMS 2.5 ug/m3
|
* @brief Read PMS 2.5 ug/m3
|
||||||
*
|
*
|
||||||
* @return uint16_t
|
* @return uint16_t
|
||||||
*/
|
*/
|
||||||
uint16_t PMSBase::getPM2_5(void) { return toU16(&package[12]); }
|
uint16_t PMSBase::getPM2_5(void) { return pms_pm2_5; }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Read PMS 10 ug/m3
|
* @brief Read PMS 10 ug/m3
|
||||||
*
|
*
|
||||||
* @return uint16_t
|
* @return uint16_t
|
||||||
*/
|
*/
|
||||||
uint16_t PMSBase::getPM10(void) { return toU16(&package[14]); }
|
uint16_t PMSBase::getPM10(void) { return pms_pm10; }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Get numnber concentrations over 0.3 um/0.1L
|
* @brief Get numnber concentrations over 0.3 um/0.1L
|
||||||
*
|
*
|
||||||
* @return uint16_t
|
* @return uint16_t
|
||||||
*/
|
*/
|
||||||
uint16_t PMSBase::getCount0_3(void) { return toU16(&package[16]); }
|
uint16_t PMSBase::getCount0_3(void) { return pms_count0_3; }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Get numnber concentrations over 0.5 um/0.1L
|
* @brief Get numnber concentrations over 0.5 um/0.1L
|
||||||
*
|
*
|
||||||
* @return uint16_t
|
* @return uint16_t
|
||||||
*/
|
*/
|
||||||
uint16_t PMSBase::getCount0_5(void) { return toU16(&package[18]); }
|
uint16_t PMSBase::getCount0_5(void) { return pms_count0_5; }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Get numnber concentrations over 1.0 um/0.1L
|
* @brief Get numnber concentrations over 1.0 um/0.1L
|
||||||
*
|
*
|
||||||
* @return uint16_t
|
* @return uint16_t
|
||||||
*/
|
*/
|
||||||
uint16_t PMSBase::getCount1_0(void) { return toU16(&package[20]); }
|
uint16_t PMSBase::getCount1_0(void) { return pms_count1_0; }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Get numnber concentrations over 2.5 um/0.1L
|
* @brief Get numnber concentrations over 2.5 um/0.1L
|
||||||
*
|
*
|
||||||
* @return uint16_t
|
* @return uint16_t
|
||||||
*/
|
*/
|
||||||
uint16_t PMSBase::getCount2_5(void) { return toU16(&package[22]); }
|
uint16_t PMSBase::getCount2_5(void) { return pms_count2_5; }
|
||||||
|
|
||||||
|
bool PMSBase::connected(void) { return _connected; }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Get numnber concentrations over 5.0 um/0.1L (only PMS5003)
|
* @brief Get numnber concentrations over 5.0 um/0.1L (only PMS5003)
|
||||||
*
|
*
|
||||||
* @return uint16_t
|
* @return uint16_t
|
||||||
*/
|
*/
|
||||||
uint16_t PMSBase::getCount5_0(void) { return toU16(&package[24]); }
|
uint16_t PMSBase::getCount5_0(void) { return pms_count5_0; }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Get numnber concentrations over 10.0 um/0.1L (only PMS5003)
|
* @brief Get numnber concentrations over 10.0 um/0.1L (only PMS5003)
|
||||||
*
|
*
|
||||||
* @return uint16_t
|
* @return uint16_t
|
||||||
*/
|
*/
|
||||||
uint16_t PMSBase::getCount10(void) { return toU16(&package[26]); }
|
uint16_t PMSBase::getCount10(void) { return pms_count10; }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Get temperature (only PMS5003T)
|
* @brief Get temperature (only PMS5003T)
|
||||||
*
|
*
|
||||||
* @return uint16_t
|
* @return uint16_t
|
||||||
*/
|
*/
|
||||||
int16_t PMSBase::getTemp(void) { return toI16(&package[24]); }
|
int16_t PMSBase::getTemp(void) { return pms_temp; }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Get humidity (only PMS5003T)
|
* @brief Get humidity (only PMS5003T)
|
||||||
*
|
*
|
||||||
* @return uint16_t
|
* @return uint16_t
|
||||||
*/
|
*/
|
||||||
uint16_t PMSBase::getHum(void) { return toU16(&package[26]); }
|
uint16_t PMSBase::getHum(void) { return pms_hum; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get firmware version code
|
||||||
|
*
|
||||||
|
* @return uint8_t
|
||||||
|
*/
|
||||||
|
uint8_t PMSBase::getFirmwareVersion(void) { return pms_firmwareVersion; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Ge PMS5003 error code
|
||||||
|
*
|
||||||
|
* @return uint8_t
|
||||||
|
*/
|
||||||
|
uint8_t PMSBase::getErrorCode(void) { return pms_errorCode; }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Convert PMS2.5 to US AQI unit
|
* @brief Convert PMS2.5 to US AQI unit
|
||||||
@ -252,57 +298,99 @@ uint16_t PMSBase::getHum(void) { return toU16(&package[26]); }
|
|||||||
* @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
|
||||||
*
|
*
|
||||||
|
* Formula: https://www.airgradient.com/documentation/correction-algorithms/
|
||||||
|
*
|
||||||
* @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::compensated(int pm25, float humidity) {
|
float PMSBase::compensate(float pm25, float humidity) {
|
||||||
float value;
|
float value;
|
||||||
|
|
||||||
|
// Correct invalid humidity value
|
||||||
if (humidity < 0) {
|
if (humidity < 0) {
|
||||||
humidity = 0;
|
humidity = 0;
|
||||||
}
|
}
|
||||||
if (humidity > 100) {
|
if (humidity > 100) {
|
||||||
humidity = 100;
|
humidity = 100.0f;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(pm25 < 30) {
|
// If its already 0, do not proceed
|
||||||
|
if (pm25 == 0) {
|
||||||
|
return 0.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pm25 < 30) { /** pm2.5 < 30 */
|
||||||
value = (pm25 * 0.524f) - (humidity * 0.0862f) + 5.75f;
|
value = (pm25 * 0.524f) - (humidity * 0.0862f) + 5.75f;
|
||||||
} else if(pm25 < 50) {
|
} else if (pm25 < 50) { /** 30 <= pm2.5 < 50 */
|
||||||
value = (0.786f * (pm25 / 20 - 3 / 2) + 0.524f * (1 - (pm25 / 20 - 3 / 2))) * pm25 - (0.0862f * humidity) + 5.75f;
|
value = (0.786f * (pm25 * 0.05f - 1.5f) + 0.524f * (1.0f - (pm25 * 0.05f - 1.5f))) * pm25 -
|
||||||
} else if(pm25 < 210) {
|
(0.0862f * humidity) + 5.75f;
|
||||||
|
} else if (pm25 < 210) { /** 50 <= pm2.5 < 210 */
|
||||||
value = (0.786f * pm25) - (0.0862f * humidity) + 5.75f;
|
value = (0.786f * pm25) - (0.0862f * humidity) + 5.75f;
|
||||||
} else if(pm25 < 260) {
|
} else if (pm25 < 260) { /** 210 <= pm2.5 < 260 */
|
||||||
value = (0.69f * (pm25/50 - 21/5) + 0.786f * (1 - (pm25/50 - 21/5))) * pm25 - (0.0862f * humidity * (1 - (pm25/50 - 21/5))) + (2.966f * (pm25/50 -21/5)) + (5.75f * (1 - (pm25/50 - 21/5))) + (8.84f * (1.e-4) * pm25* (pm25/50 - 21/5));
|
value = (0.69f * (pm25 * 0.02f - 4.2f) + 0.786f * (1.0f - (pm25 * 0.02f - 4.2f))) * pm25 -
|
||||||
} else {
|
(0.0862f * humidity * (1.0f - (pm25 * 0.02f - 4.2f))) +
|
||||||
value = 2.966f + (0.69f * pm25) + (8.84f * (1.e-4) * pm25);
|
(2.966f * (pm25 * 0.02f - 4.2f)) + (5.75f * (1.0f - (pm25 * 0.02f - 4.2f))) +
|
||||||
|
(8.84f * (1.e-4) * pm25 * pm25 * (pm25 * 0.02f - 4.2f));
|
||||||
|
} else { /** 260 <= pm2.5 */
|
||||||
|
value = 2.966f + (0.69f * pm25) + (8.84f * (1.e-4) * pm25 * pm25);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(value < 0) {
|
// No negative value for pm2.5
|
||||||
value = 0;
|
if (value < 0) {
|
||||||
|
return 0.0;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (int)value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -311,13 +399,13 @@ int PMSBase::compensated(int pm25, float humidity) {
|
|||||||
* @param buf bytes array (must be >= 2)
|
* @param buf bytes array (must be >= 2)
|
||||||
* @return int16_t
|
* @return int16_t
|
||||||
*/
|
*/
|
||||||
int16_t PMSBase::toI16(char *buf) {
|
int16_t PMSBase::toI16(const uint8_t *buf) {
|
||||||
int16_t value = buf[0];
|
int16_t value = buf[0];
|
||||||
value = (value << 8) | buf[1];
|
value = (value << 8) | buf[1];
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint16_t PMSBase::toU16(char *buf) {
|
uint16_t PMSBase::toU16(const uint8_t *buf) {
|
||||||
uint16_t value = buf[0];
|
uint16_t value = buf[0];
|
||||||
value = (value << 8) | buf[1];
|
value = (value << 8) | buf[1];
|
||||||
return value;
|
return value;
|
||||||
@ -330,16 +418,38 @@ uint16_t PMSBase::toU16(char *buf) {
|
|||||||
* @return true Success
|
* @return true Success
|
||||||
* @return false Failed
|
* @return false Failed
|
||||||
*/
|
*/
|
||||||
bool PMSBase::validate(char *buf) {
|
bool PMSBase::validate(const uint8_t *buf) {
|
||||||
uint16_t sum = 0;
|
uint16_t sum = 0;
|
||||||
for (int i = 0; i < 30; i++) {
|
for (int i = 0; i < 30; i++) {
|
||||||
sum += buf[i];
|
sum += buf[i];
|
||||||
}
|
}
|
||||||
if (sum == toU16(&buf[30])) {
|
if (sum == toU16(&buf[30])) {
|
||||||
for (int i = 0; i < 32; i++) {
|
|
||||||
package[i] = buf[i];
|
|
||||||
}
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void PMSBase::parse(const uint8_t *buf) {
|
||||||
|
// Standard particle
|
||||||
|
pms_raw0_1 = toU16(&buf[4]);
|
||||||
|
pms_raw2_5 = toU16(&buf[6]);
|
||||||
|
pms_raw10 = toU16(&buf[8]);
|
||||||
|
// atmospheric
|
||||||
|
pms_pm0_1 = toU16(&buf[10]);
|
||||||
|
pms_pm2_5 = toU16(&buf[12]);
|
||||||
|
pms_pm10 = toU16(&buf[14]);
|
||||||
|
|
||||||
|
// particle count
|
||||||
|
pms_count0_3 = toU16(&buf[16]);
|
||||||
|
pms_count0_5 = toU16(&buf[18]);
|
||||||
|
pms_count1_0 = toU16(&buf[20]);
|
||||||
|
pms_count2_5 = toU16(&buf[22]);
|
||||||
|
pms_count5_0 = toU16(&buf[24]); // PMS5003 only
|
||||||
|
pms_count10 = toU16(&buf[26]); // PMS5003 only
|
||||||
|
|
||||||
|
// Others
|
||||||
|
pms_temp = toU16(&buf[24]); // PMS5003T only
|
||||||
|
pms_hum = toU16(&buf[26]); // PMS5003T only
|
||||||
|
pms_firmwareVersion = buf[28];
|
||||||
|
pms_errorCode = buf[29];
|
||||||
|
}
|
||||||
|
@ -3,11 +3,19 @@
|
|||||||
|
|
||||||
#include <Arduino.h>
|
#include <Arduino.h>
|
||||||
|
|
||||||
|
#define PMS_FAIL_COUNT_SET_INVALID 3
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Known to work with these sensors: Plantower PMS5003, Plantower PMS5003, Cubic PM2009X
|
||||||
|
*/
|
||||||
class PMSBase {
|
class PMSBase {
|
||||||
public:
|
public:
|
||||||
bool begin(Stream *stream);
|
bool begin(Stream *stream);
|
||||||
void handle();
|
void readPackage(Stream *stream);
|
||||||
bool isFailed(void);
|
void updateFailCount(void);
|
||||||
|
void resetFailCount(void);
|
||||||
|
int getFailCount(void);
|
||||||
|
int getFailCountMax(void);
|
||||||
uint16_t getRaw0_1(void);
|
uint16_t getRaw0_1(void);
|
||||||
uint16_t getRaw2_5(void);
|
uint16_t getRaw2_5(void);
|
||||||
uint16_t getRaw10(void);
|
uint16_t getRaw10(void);
|
||||||
@ -18,6 +26,7 @@ public:
|
|||||||
uint16_t getCount0_5(void);
|
uint16_t getCount0_5(void);
|
||||||
uint16_t getCount1_0(void);
|
uint16_t getCount1_0(void);
|
||||||
uint16_t getCount2_5(void);
|
uint16_t getCount2_5(void);
|
||||||
|
bool connected(void);
|
||||||
|
|
||||||
/** For PMS5003 */
|
/** For PMS5003 */
|
||||||
uint16_t getCount5_0(void);
|
uint16_t getCount5_0(void);
|
||||||
@ -26,20 +35,56 @@ public:
|
|||||||
/** For PMS5003T*/
|
/** For PMS5003T*/
|
||||||
int16_t getTemp(void);
|
int16_t getTemp(void);
|
||||||
uint16_t getHum(void);
|
uint16_t getHum(void);
|
||||||
|
uint8_t getFirmwareVersion(void);
|
||||||
|
uint8_t getErrorCode(void);
|
||||||
|
|
||||||
int pm25ToAQI(int pm02);
|
int pm25ToAQI(int pm02);
|
||||||
int compensated(int pm25, float humidity);
|
float slrCorrection(float pm25, float pm003Count, float scalingFactor, float intercept);
|
||||||
|
float compensate(float pm25, float humidity);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Stream *stream;
|
static const uint8_t package_size = 32;
|
||||||
char package[32];
|
|
||||||
int packageIndex;
|
|
||||||
bool failed = false;
|
|
||||||
uint32_t lastRead;
|
|
||||||
|
|
||||||
int16_t toI16(char *buf);
|
/** In normal package interval is 200-800ms, In case small changed on sensor
|
||||||
uint16_t toU16(char* buf);
|
* it's will interval reach to 2.3sec
|
||||||
bool validate(char *buf);
|
*/
|
||||||
|
const uint16_t READ_PACKGE_TIMEOUT = 3000; /** ms */
|
||||||
|
const int failCountMax = 10;
|
||||||
|
int failCount = 0;
|
||||||
|
|
||||||
|
uint8_t readBuffer[package_size];
|
||||||
|
uint8_t readBufferIndex = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Save last time received package success. 0 to disable check package
|
||||||
|
* timeout.
|
||||||
|
*/
|
||||||
|
unsigned long lastPackage = 0;
|
||||||
|
bool _connected;
|
||||||
|
|
||||||
|
unsigned long lastReadPackage = 0;
|
||||||
|
|
||||||
|
uint16_t pms_raw0_1;
|
||||||
|
uint16_t pms_raw2_5;
|
||||||
|
uint16_t pms_raw10;
|
||||||
|
uint16_t pms_pm0_1;
|
||||||
|
uint16_t pms_pm2_5;
|
||||||
|
uint16_t pms_pm10;
|
||||||
|
uint16_t pms_count0_3;
|
||||||
|
uint16_t pms_count0_5;
|
||||||
|
uint16_t pms_count1_0;
|
||||||
|
uint16_t pms_count2_5;
|
||||||
|
uint16_t pms_count5_0;
|
||||||
|
uint16_t pms_count10;
|
||||||
|
int16_t pms_temp;
|
||||||
|
uint16_t pms_hum;
|
||||||
|
uint8_t pms_errorCode;
|
||||||
|
uint8_t pms_firmwareVersion;
|
||||||
|
|
||||||
|
int16_t toI16(const uint8_t *buf);
|
||||||
|
uint16_t toU16(const uint8_t *buf);
|
||||||
|
bool validate(const uint8_t *buf);
|
||||||
|
void parse(const uint8_t* buf);
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif /** _PMS5003_BASE_H_ */
|
#endif /** _PMS5003_BASE_H_ */
|
||||||
|
@ -3,7 +3,6 @@
|
|||||||
#include "../Main/utils.h"
|
#include "../Main/utils.h"
|
||||||
|
|
||||||
#if defined(ESP8266)
|
#if defined(ESP8266)
|
||||||
#include <SoftwareSerial.h>
|
|
||||||
/**
|
/**
|
||||||
* @brief Init sensor
|
* @brief Init sensor
|
||||||
*
|
*
|
||||||
@ -38,14 +37,11 @@ bool PMS5003::begin(HardwareSerial &serial) {
|
|||||||
PMS5003::PMS5003(BoardType def) : _boardDef(def) {}
|
PMS5003::PMS5003(BoardType def) : _boardDef(def) {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Init sensor
|
* Initializes the sensor.
|
||||||
*
|
|
||||||
* @return true Success
|
|
||||||
* @return false Failure
|
|
||||||
*/
|
*/
|
||||||
bool PMS5003::begin(void) {
|
bool PMS5003::begin(void) {
|
||||||
if (this->_isBegin) {
|
if (this->_isBegin) {
|
||||||
AgLog("Initialized, call end() then try again");
|
AgLog("Already initialized, call end() then try again");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -63,11 +59,10 @@ bool PMS5003::begin(void) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#if defined(ESP8266)
|
#if defined(ESP8266)
|
||||||
bsp->Pms5003.uart_tx_pin;
|
this->_serial =
|
||||||
SoftwareSerial *uart =
|
|
||||||
new SoftwareSerial(bsp->Pms5003.uart_tx_pin, bsp->Pms5003.uart_rx_pin);
|
new SoftwareSerial(bsp->Pms5003.uart_tx_pin, bsp->Pms5003.uart_rx_pin);
|
||||||
uart->begin(9600);
|
this->_serial->begin(9600);
|
||||||
if (pms.begin(uart) == false) {
|
if (pms.begin(this->_serial) == false) {
|
||||||
AgLog("PMS failed");
|
AgLog("PMS failed");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -78,34 +73,55 @@ bool PMS5003::begin(void) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
_ver = pms.getFirmwareVersion();
|
||||||
this->_isBegin = true;
|
this->_isBegin = true;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @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
|
||||||
*/
|
*/
|
||||||
@ -113,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
|
||||||
*
|
*
|
||||||
@ -121,16 +172,42 @@ 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
|
||||||
*
|
*
|
||||||
|
* Reference formula: https://www.airgradient.com/documentation/correction-algorithms/
|
||||||
|
*
|
||||||
* @param pm25 PM2.5 raw value
|
* @param pm25 PM2.5 raw value
|
||||||
* @param humidity Humidity value
|
* @param humidity Humidity value
|
||||||
* @return float
|
* @return compensated value in float
|
||||||
*/
|
*/
|
||||||
int PMS5003::compensated(int pm25, float humidity) {
|
float PMS5003::compensate(float pm25, float humidity) { return pms.compensate(pm25, humidity); }
|
||||||
return pms.compensated(pm25, humidity);
|
|
||||||
}
|
/**
|
||||||
|
* @brief Get sensor firmware version
|
||||||
|
*
|
||||||
|
* @return int
|
||||||
|
*/
|
||||||
|
int PMS5003::getFirmwareVersion(void) { return _ver; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get sensor error code
|
||||||
|
*
|
||||||
|
* @return uint8_t
|
||||||
|
*/
|
||||||
|
uint8_t PMS5003::getErrorCode(void) { return pms.getErrorCode(); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Is sensor connect with device
|
||||||
|
*
|
||||||
|
* @return true Connected
|
||||||
|
* @return false Removed
|
||||||
|
*/
|
||||||
|
bool PMS5003::connected(void) { return pms.connected(); }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Check device initialized or not
|
* @brief Check device initialized or not
|
||||||
@ -166,12 +243,26 @@ void PMS5003::end(void) {
|
|||||||
* @brief Check and read PMS sensor data. This method should be callack from
|
* @brief Check and read PMS sensor data. This method should be callack from
|
||||||
* loop process to continoue check sensor data if it's available
|
* loop process to continoue check sensor data if it's available
|
||||||
*/
|
*/
|
||||||
void PMS5003::handle(void) { pms.handle(); }
|
void PMS5003::handle(void) { pms.readPackage(this->_serial); }
|
||||||
|
|
||||||
|
void PMS5003::updateFailCount(void) {
|
||||||
|
pms.updateFailCount();
|
||||||
|
}
|
||||||
|
|
||||||
|
void PMS5003::resetFailCount(void) {
|
||||||
|
pms.resetFailCount();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Get sensor status
|
* @brief Get number of fail count
|
||||||
*
|
*
|
||||||
* @return true No problem
|
* @return int
|
||||||
* @return false Communication timeout or sensor has removed
|
|
||||||
*/
|
*/
|
||||||
bool PMS5003::isFailed(void) { return pms.isFailed(); }
|
int PMS5003::getFailCount(void) { return pms.getFailCount(); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get number of fail count max
|
||||||
|
*
|
||||||
|
* @return int
|
||||||
|
*/
|
||||||
|
int PMS5003::getFailCountMax(void) { return pms.getFailCountMax(); }
|
||||||
|
@ -4,6 +4,9 @@
|
|||||||
#include "../Main/BoardDef.h"
|
#include "../Main/BoardDef.h"
|
||||||
#include "PMS.h"
|
#include "PMS.h"
|
||||||
#include "Stream.h"
|
#include "Stream.h"
|
||||||
|
#ifdef ESP8266
|
||||||
|
#include <SoftwareSerial.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief The class define how to handle PMS5003 sensor bas on @ref PMS class
|
* @brief The class define how to handle PMS5003 sensor bas on @ref PMS class
|
||||||
@ -18,22 +21,43 @@ public:
|
|||||||
#endif
|
#endif
|
||||||
void end(void);
|
void end(void);
|
||||||
void handle(void);
|
void handle(void);
|
||||||
bool isFailed(void);
|
void updateFailCount(void);
|
||||||
|
void resetFailCount(void);
|
||||||
|
int getFailCount(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 compensated(int pm25, float humidity);
|
float slrCorrection(float pm25, float pm003Count, float scalingFactor, float intercept);
|
||||||
|
float compensate(float pm25, float humidity);
|
||||||
|
int getFirmwareVersion(void);
|
||||||
|
uint8_t getErrorCode(void);
|
||||||
|
bool connected(void);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
bool _isBegin = false;
|
bool _isBegin = false;
|
||||||
|
int _ver;
|
||||||
BoardType _boardDef;
|
BoardType _boardDef;
|
||||||
PMSBase pms;
|
PMSBase pms;
|
||||||
const BoardDef *bsp;
|
const BoardDef *bsp;
|
||||||
#if defined(ESP8266)
|
#if defined(ESP8266)
|
||||||
Stream *_debugStream;
|
Stream *_debugStream;
|
||||||
const char *TAG = "PMS5003";
|
const char *TAG = "PMS5003";
|
||||||
|
SoftwareSerial *_serial;
|
||||||
#else
|
#else
|
||||||
HardwareSerial *_serial;
|
HardwareSerial *_serial;
|
||||||
#endif
|
#endif
|
||||||
|
@ -67,11 +67,10 @@ bool PMS5003T::begin(void) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#if defined(ESP8266)
|
#if defined(ESP8266)
|
||||||
bsp->Pms5003.uart_tx_pin;
|
this->_serial =
|
||||||
SoftwareSerial *uart =
|
|
||||||
new SoftwareSerial(bsp->Pms5003.uart_tx_pin, bsp->Pms5003.uart_rx_pin);
|
new SoftwareSerial(bsp->Pms5003.uart_tx_pin, bsp->Pms5003.uart_rx_pin);
|
||||||
uart->begin(9600);
|
this->_serial->begin(9600);
|
||||||
if (pms.begin(uart) == false) {
|
if (pms.begin(this->_serial) == false) {
|
||||||
AgLog("PMS failed");
|
AgLog("PMS failed");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -103,41 +102,83 @@ bool PMS5003T::begin(void) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
_ver = pms.getFirmwareVersion();
|
||||||
this->_isBegin = true;
|
this->_isBegin = true;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @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
|
||||||
*
|
*
|
||||||
@ -167,13 +208,35 @@ float PMS5003T::getRelativeHumidity(void) {
|
|||||||
/**
|
/**
|
||||||
* @brief Correct PM2.5
|
* @brief Correct PM2.5
|
||||||
*
|
*
|
||||||
|
* Reference formula: https://www.airgradient.com/documentation/correction-algorithms/
|
||||||
|
*
|
||||||
* @param pm25 PM2.5 raw value
|
* @param pm25 PM2.5 raw value
|
||||||
* @param humidity Humidity value
|
* @param humidity Humidity value
|
||||||
* @return float
|
* @return compensated value
|
||||||
*/
|
*/
|
||||||
float PMS5003T::compensated(int pm25, float humidity) {
|
float PMS5003T::compensate(float pm25, float humidity) { return pms.compensate(pm25, humidity); }
|
||||||
return pms.compensated(pm25, humidity);
|
|
||||||
}
|
/**
|
||||||
|
* @brief Get module(s) firmware version
|
||||||
|
*
|
||||||
|
* @return int Version code
|
||||||
|
*/
|
||||||
|
int PMS5003T::getFirmwareVersion(void) { return _ver; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get sensor error code
|
||||||
|
*
|
||||||
|
* @return uint8_t
|
||||||
|
*/
|
||||||
|
uint8_t PMS5003T::getErrorCode(void) { return pms.getErrorCode(); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Is sensor connect to device
|
||||||
|
*
|
||||||
|
* @return true Connected
|
||||||
|
* @return false Removed
|
||||||
|
*/
|
||||||
|
bool PMS5003T::connected(void) { return pms.connected(); }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Check device initialized or not
|
* @brief Check device initialized or not
|
||||||
@ -206,13 +269,26 @@ void PMS5003T::end(void) {
|
|||||||
* @brief Check and read PMS sensor data. This method should be callack from
|
* @brief Check and read PMS sensor data. This method should be callack from
|
||||||
* loop process to continoue check sensor data if it's available
|
* loop process to continoue check sensor data if it's available
|
||||||
*/
|
*/
|
||||||
void PMS5003T::handle(void) { pms.handle(); }
|
void PMS5003T::handle(void) { pms.readPackage(this->_serial); }
|
||||||
|
|
||||||
|
void PMS5003T::updateFailCount(void) {
|
||||||
|
pms.updateFailCount();
|
||||||
|
}
|
||||||
|
|
||||||
|
void PMS5003T::resetFailCount(void) {
|
||||||
|
pms.resetFailCount();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Get sensor status
|
* @brief Get fail count
|
||||||
*
|
*
|
||||||
* @return true No problem
|
* @return int
|
||||||
* @return false Communication timeout or sensor has removed
|
|
||||||
*/
|
*/
|
||||||
bool PMS5003T::isFailed(void) { return pms.isFailed(); }
|
int PMS5003T::getFailCount(void) { return pms.getFailCount(); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get fail count max
|
||||||
|
*
|
||||||
|
* @return int
|
||||||
|
*/
|
||||||
|
int PMS5003T::getFailCountMax(void) { return pms.getFailCountMax(); }
|
||||||
|
@ -6,6 +6,9 @@
|
|||||||
#include "PMS5003TBase.h"
|
#include "PMS5003TBase.h"
|
||||||
#include "Stream.h"
|
#include "Stream.h"
|
||||||
#include <HardwareSerial.h>
|
#include <HardwareSerial.h>
|
||||||
|
#ifdef ESP8266
|
||||||
|
#include <SoftwareSerial.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief The class define how to handle PMS5003T sensor bas on @ref PMS class
|
* @brief The class define how to handle PMS5003T sensor bas on @ref PMS class
|
||||||
@ -21,25 +24,43 @@ public:
|
|||||||
void end(void);
|
void end(void);
|
||||||
|
|
||||||
void handle(void);
|
void handle(void);
|
||||||
bool isFailed(void);
|
void updateFailCount(void);
|
||||||
|
void resetFailCount(void);
|
||||||
|
int getFailCount(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);
|
||||||
float compensated(int pm25, float humidity);
|
float compensate(float pm25, float humidity);
|
||||||
|
int getFirmwareVersion(void);
|
||||||
|
uint8_t getErrorCode(void);
|
||||||
|
bool connected(void);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
bool _isBegin = false;
|
bool _isBegin = false;
|
||||||
bool _isSleep = false;
|
bool _isSleep = false;
|
||||||
|
int _ver; /** Firmware version code */
|
||||||
|
|
||||||
BoardType _boardDef;
|
BoardType _boardDef;
|
||||||
const BoardDef *bsp;
|
const BoardDef *bsp;
|
||||||
#if defined(ESP8266)
|
#if defined(ESP8266)
|
||||||
Stream *_debugStream;
|
Stream *_debugStream;
|
||||||
const char *TAG = "PMS5003T";
|
const char *TAG = "PMS5003T";
|
||||||
|
SoftwareSerial *_serial;
|
||||||
#else
|
#else
|
||||||
HardwareSerial *_serial;
|
HardwareSerial *_serial;
|
||||||
#endif
|
#endif
|
||||||
|
@ -4,14 +4,30 @@ PMS5003TBase::PMS5003TBase() {}
|
|||||||
|
|
||||||
PMS5003TBase::~PMS5003TBase() {}
|
PMS5003TBase::~PMS5003TBase() {}
|
||||||
|
|
||||||
float PMS5003TBase::temperatureCompensated(float temp) {
|
/**
|
||||||
|
* @brief Compensate the temperature
|
||||||
|
*
|
||||||
|
* Reference formula: https://www.airgradient.com/documentation/correction-algorithms/
|
||||||
|
*
|
||||||
|
* @param temp
|
||||||
|
* @return * float
|
||||||
|
*/
|
||||||
|
float PMS5003TBase::compensateTemp(float temp) {
|
||||||
if (temp < 10.0f) {
|
if (temp < 10.0f) {
|
||||||
return temp * 1.327f - 6.738f;
|
return temp * 1.327f - 6.738f;
|
||||||
}
|
}
|
||||||
return temp * 1.181f - 5.113f;
|
return temp * 1.181f - 5.113f;
|
||||||
}
|
}
|
||||||
|
|
||||||
float PMS5003TBase::humidityCompensated(float hum) {
|
/**
|
||||||
|
* @brief Compensate the humidity
|
||||||
|
*
|
||||||
|
* Reference formula: https://www.airgradient.com/documentation/correction-algorithms/
|
||||||
|
*
|
||||||
|
* @param temp
|
||||||
|
* @return * float
|
||||||
|
*/
|
||||||
|
float PMS5003TBase::compensateHum(float hum) {
|
||||||
hum = hum * 1.259f + 7.34f;
|
hum = hum * 1.259f + 7.34f;
|
||||||
|
|
||||||
if (hum > 100.0f) {
|
if (hum > 100.0f) {
|
||||||
|
@ -8,8 +8,8 @@ private:
|
|||||||
public:
|
public:
|
||||||
PMS5003TBase();
|
PMS5003TBase();
|
||||||
~PMS5003TBase();
|
~PMS5003TBase();
|
||||||
float temperatureCompensated(float temp);
|
float compensateTemp(float temp);
|
||||||
float humidityCompensated(float hum);
|
float compensateHum(float hum);
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
@ -131,6 +131,22 @@ void Sgp41::handle(void) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#else
|
#else
|
||||||
|
|
||||||
|
void Sgp41::pause() {
|
||||||
|
onPause = true;
|
||||||
|
Serial.println("Pausing SGP41 handler task");
|
||||||
|
// Set latest value to invalid
|
||||||
|
tvocRaw = utils::getInvalidVOC();
|
||||||
|
tvoc = utils::getInvalidVOC();
|
||||||
|
noxRaw = utils::getInvalidNOx();
|
||||||
|
nox = utils::getInvalidNOx();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Sgp41::resume() {
|
||||||
|
onPause = false;
|
||||||
|
Serial.println("Resuming SGP41 handler task");
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Handle the sensor conditioning and run time udpate value, This method
|
* @brief Handle the sensor conditioning and run time udpate value, This method
|
||||||
* must not call, it's called on private task
|
* must not call, it's called on private task
|
||||||
@ -152,6 +168,11 @@ void Sgp41::_handle(void) {
|
|||||||
uint16_t srawVoc, srawNox;
|
uint16_t srawVoc, srawNox;
|
||||||
for (;;) {
|
for (;;) {
|
||||||
vTaskDelay(pdMS_TO_TICKS(1000));
|
vTaskDelay(pdMS_TO_TICKS(1000));
|
||||||
|
|
||||||
|
if (onPause) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
if (getRawSignal(srawVoc, srawNox)) {
|
if (getRawSignal(srawVoc, srawNox)) {
|
||||||
tvocRaw = srawVoc;
|
tvocRaw = srawVoc;
|
||||||
noxRaw = srawNox;
|
noxRaw = srawNox;
|
||||||
|
@ -18,6 +18,10 @@ public:
|
|||||||
bool begin(TwoWire &wire, Stream &stream);
|
bool begin(TwoWire &wire, Stream &stream);
|
||||||
void handle(void);
|
void handle(void);
|
||||||
#else
|
#else
|
||||||
|
/* pause _handle task to read sensor */
|
||||||
|
void pause();
|
||||||
|
/* resume _handle task to read sensor */
|
||||||
|
void resume();
|
||||||
void _handle(void);
|
void _handle(void);
|
||||||
#endif
|
#endif
|
||||||
void end(void);
|
void end(void);
|
||||||
@ -32,6 +36,7 @@ public:
|
|||||||
int getTvocLearningOffset(void);
|
int getTvocLearningOffset(void);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
bool onPause = false;
|
||||||
bool onConditioning = true;
|
bool onConditioning = true;
|
||||||
bool ready = false;
|
bool ready = false;
|
||||||
bool _isBegin = false;
|
bool _isBegin = false;
|
||||||
|