mirror of
https://github.com/airgradienthq/arduino.git
synced 2025-06-26 00:01:32 +02:00
Compare commits
653 Commits
Author | SHA1 | Date | |
---|---|---|---|
6a83743e2a | |||
faaf051e39 | |||
280ea5e997 | |||
566f8a63b4 | |||
9e4d52454b | |||
5f5e985309 | |||
d638573ca7 | |||
79fbd901bd | |||
3644dc43fe | |||
03fa62d8f0 | |||
902a768f28 | |||
1de9344f43 | |||
46f6309b77 | |||
a6b48acb41 | |||
1b4d89e1a1 | |||
0d2b0fb657 | |||
9f08af44b0 | |||
6b661cdeb7 | |||
dc299c4b54 | |||
2f595b4e41 | |||
a30535f75f | |||
a513943cba | |||
96bb6952fb | |||
10653bfe26 | |||
c7f89fa7b7 | |||
b11c461b60 | |||
404c14aad2 | |||
bfbae680fd | |||
3ae5982380 | |||
db2c2ef052 | |||
593547cdbe | |||
673c46950d | |||
ae0b4038d4 | |||
cac0bd5355 | |||
3d6203dabf | |||
1db8fbefe9 | |||
d850d27dc1 | |||
f49e4a4b37 | |||
75f88b0009 | |||
c6961b3ca8 | |||
ade72ff3b8 | |||
9fbbea22ff | |||
7b0381dea3 | |||
5867d0f1d5 | |||
a98d77e0c3 | |||
641003f9d1 | |||
0275aee370 | |||
ea46b812c1 | |||
16c932962a | |||
f90b2e1a07 | |||
3a9bb16c09 | |||
bb754edc51 | |||
1d991b1004 | |||
3ebcc584a4 | |||
4d40ae421c | |||
3004a82e7e | |||
4af5ca2665 | |||
e6696f3d41 | |||
2b33823162 | |||
bf0768c7da | |||
33e2977eb4 | |||
85e779cfc2 | |||
4783684443 | |||
3b0c77ca4d | |||
eeba41f497 | |||
fd1f35f6d8 | |||
eb76eff403 | |||
4673999dda | |||
83aa6a4502 | |||
8a87b865e6 | |||
c3068be6e9 | |||
63bb5f8ddb | |||
8548d3e9f4 | |||
f7e1363da9 | |||
2ffe0a62aa | |||
2cda36ed0d | |||
7de2d0cc30 | |||
f478dd16c8 | |||
43ca0a2c2e | |||
84884d0c15 | |||
f36f860c2e | |||
e47a9057ea | |||
399b4ca1dc | |||
2e4f4643fa | |||
0ccf46c219 | |||
76a2f332d7 | |||
ed344d3e1a | |||
2082a2fa93 | |||
e145d32714 | |||
a2c19438c0 | |||
ac838efdb5 | |||
751d4e8380 | |||
6925b1ac9a | |||
77a23b4202 | |||
ea91cf9b6c | |||
467b3e8637 | |||
2a5cf78b68 | |||
9c09b82efd | |||
60d01c0d94 | |||
e7a91c53bc | |||
4e41fd5d71 | |||
fe4389bff4 | |||
9325830fad | |||
b86f0d45e3 | |||
210f0a5ff9 | |||
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 | |||
b1aaa04421 | |||
2df78e9066 | |||
186f0d27ab | |||
e25aa87ecc | |||
1cc8941a5c | |||
9bf1495be7 | |||
73089b51f5 | |||
625e60a5bf | |||
88e3d0bd3f | |||
171821cfcf | |||
900a2da2ac | |||
fb57a112c9 | |||
ab69b686ec | |||
6746d25dc2 | |||
be150e105a | |||
ecadeeb156 | |||
219ff73132 | |||
0a9142204d | |||
81b13134d2 | |||
f3a9c722b2 | |||
3be3218115 | |||
5edb21cfe9 | |||
6cd587b008 | |||
6d01366887 | |||
1a347e9cfe | |||
6432e4451e | |||
97f0696002 | |||
e46e11c030 | |||
dc261f668d | |||
b5cced40d2 | |||
040bd28038 | |||
b0ae851427 | |||
01943f594d | |||
01a69668cc | |||
ed7b8df6fe | |||
6c1c914716 | |||
6a0d88ff10 | |||
9097eed137 | |||
c9b5e5f0d7 | |||
c12bac4ce3 | |||
9ae9b2ac9c | |||
7fb3e68b6d | |||
cf65a1f901 | |||
5fb27b6d1e | |||
7b9dac756b | |||
4b2a5f5540 | |||
4af77d532e | |||
812c2ab803 | |||
0ece16f434 | |||
df6cca3714 | |||
c8aa07ae20 | |||
a1d216ac77 | |||
a9d9c60dfa | |||
e58ce1cbea | |||
64827223ec | |||
bddd4fef25 | |||
0eba54fd28 | |||
a4176f966a | |||
2de54ca6a6 | |||
bfd20a73da | |||
3ebce4ac44 | |||
f08c8edd19 | |||
6b3e8e3096 | |||
beb17de7dc | |||
bccadd17d7 | |||
863b1f908e | |||
5a2c1bd1d0 | |||
dbc63194e6 | |||
57c33e4900 | |||
48f1a8042a | |||
fbd5779fe6 | |||
45a4d98267 | |||
0119a4d62a | |||
7560251deb | |||
31655b0a4f | |||
7d15c37ad9 | |||
411beda220 | |||
0e869d1c0c | |||
f25f816428 | |||
c8745e123b | |||
5ea01b8e9f | |||
a11eaea532 | |||
3b6859f483 | |||
bd237ed95d | |||
d24b20a734 | |||
227a4f76f7 | |||
adabb9baa4 | |||
935e7f365f | |||
d4ea03f39e | |||
89aefdda43 | |||
8a83e408d3 | |||
7a1a0337d1 | |||
6bdd4cb02f | |||
38bd758b69 | |||
6aa19ea3e6 | |||
da323b1a46 | |||
2ae90444bb | |||
21e802da33 | |||
e0869fbaf0 | |||
70662091ec | |||
960d2bad64 | |||
7abf7f5e6a | |||
6bad4fd04b | |||
a17f18b3db | |||
3da4900462 | |||
d2d81f6b4b | |||
0b12f56513 | |||
f7e811e34b | |||
037bb37184 | |||
fde510ba96 | |||
14b152a2a7 | |||
ef6b041529 | |||
11ecea1493 | |||
3e2e8b15eb | |||
4249a86fd5 | |||
4a58b0b1c7 | |||
5f93329e96 | |||
59d189e1d3 | |||
2e62abe2d7 | |||
7569b114bf | |||
3c1d0a862f | |||
4d883af77e | |||
e9224a5de0 | |||
af0fbadd80 | |||
79f6c040c7 | |||
f262866148 | |||
78a2a78020 | |||
65e759fb90 | |||
3fc7e4b55e | |||
5857388c2d | |||
5770b41fd4 | |||
d85d890878 | |||
9fbd31d0c8 | |||
c5b7c43293 | |||
7550ef7b0c | |||
805546b78e | |||
59880f4be5 | |||
ee7837a471 | |||
ebbf0adf2f | |||
d9551dc560 | |||
6ea0ab9272 | |||
6e54409512 | |||
f35bc4feaa | |||
c04ab90fd2 | |||
ed02f66ca2 | |||
4b94926651 | |||
f505b39247 | |||
9e44cd89d9 | |||
e7b95c0bde | |||
b72394b004 | |||
1544989fe6 | |||
d7b5e999c1 | |||
9b0210f7b5 | |||
3715266f8c | |||
a4eb607174 | |||
f55fa6a617 | |||
4f9f800cce | |||
cad5d1f0e7 | |||
55ede2b04d | |||
563bdfe4b2 | |||
e2154af85f | |||
9a2bbd7a57 | |||
a8c8246632 | |||
a71c038864 | |||
799217e724 | |||
b3f02f0a58 | |||
c640cf773e | |||
c145666fcb | |||
466bb0eb21 | |||
4612f4b793 | |||
45c7279866 | |||
5cb838af29 | |||
3201fd8d9c | |||
f23c7e9e31 | |||
5b18a8353d | |||
1e81c9b125 | |||
3dae4cb06d | |||
b4745ef55d | |||
348cb2663a | |||
1b69e8a599 | |||
d17ad3cbab | |||
a6d8936ea6 | |||
8b428855b0 | |||
22dc2136e4 | |||
f23f81f575 | |||
7a4255b2bb | |||
d844ab09fa | |||
31c60dcbec | |||
f0749783fe | |||
d34605a018 | |||
1bcb9bf5ee | |||
296bf49e5e | |||
e3dee42b4b | |||
279ccb8bfb | |||
a3c9727b02 | |||
1b886d9843 | |||
066e81b186 | |||
c98d078d4c | |||
cb98183e20 | |||
0e1734b35d | |||
da6326db0f | |||
ad1da129c0 | |||
8a90fe511b | |||
cca1ab69bb | |||
955172d3d3 | |||
4500f41ed3 | |||
7c2f8e5b9b | |||
fb84b53077 | |||
3359b1817b | |||
8eb8d4a1ec | |||
d2723de0f8 | |||
4493156739 | |||
0acb7d470d | |||
8428442dea | |||
f32d6b1bbe | |||
46600f59a3 | |||
01c42387ed | |||
f08438db46 | |||
221730160b | |||
d40b1d37a8 | |||
ecd3aa988f | |||
095787d1f2 | |||
d94d074abe | |||
cdef9822b3 | |||
29c1989e78 | |||
bc3872e631 | |||
7a182ebb12 | |||
5e6f801534 | |||
88808a2ad2 | |||
a4fc954712 | |||
1580cd51aa | |||
3efba24e24 | |||
940dd0167c | |||
6b4f86e7e4 | |||
e5a0c6bc7b | |||
b8fdb38db8 | |||
b2c55c38dc | |||
4fac3fddb8 | |||
c025bae3df | |||
e82b5f8369 | |||
1ec5d84043 | |||
26546a6079 | |||
5078b35341 | |||
a3cbca61ee | |||
6d7750d917 | |||
1c8d7b04e9 | |||
e12a154235 | |||
12f03aff30 | |||
5275f5a810 | |||
442f0fd942 | |||
9feac035eb | |||
21d984c95a | |||
b91a3058fc | |||
2d96fc28c5 | |||
7561017b3d | |||
2b891c0194 | |||
d5fc35df2f | |||
a91f6c1fa0 | |||
89802551a0 | |||
556a6fbd6d | |||
302bec9d37 | |||
e61cd9ba6a | |||
71b45fcdc1 | |||
a2b30a5467 | |||
4063536078 | |||
563fc062cb | |||
5534a2cf7e | |||
cb4ae82b1b | |||
8adca3a9ee | |||
5f15e29c76 | |||
6e1ac26187 | |||
ccee987d05 | |||
09cbbed856 | |||
e1115659e2 | |||
25ef1ced9e | |||
2ccddf0e19 | |||
9a1e0f4cdd | |||
86c6095362 | |||
1d6a0a06c0 | |||
bd1197971f | |||
c28a937384 | |||
b2195219ab | |||
cb7a6a2dfd | |||
51ff8f8df4 | |||
5b271f4ed9 | |||
4577082731 | |||
9a03fb2bd7 | |||
dfba4fa4b1 | |||
f681d4b2e8 | |||
dba385f5bb | |||
027ffeaa92 | |||
c1ab99ba8d | |||
8e032927c6 | |||
f52eab87d2 | |||
954a7751cc | |||
7d68b02f76 | |||
3788aa2746 | |||
260e904326 | |||
9055bbb690 | |||
5e20b9e8ec | |||
adce439ce7 | |||
dc875dd5a8 | |||
378688d2fa | |||
a2200795d7 | |||
3889aa660e | |||
efe68a54a4 | |||
a960d086e1 | |||
3537a3012c | |||
d255e6ad04 | |||
e47096feac | |||
063612e08f | |||
7cfa722684 | |||
53285ab4ff | |||
f46c66a77f | |||
9c8ae315a0 | |||
3ef438412f | |||
ce1373141a | |||
aceecde7b6 | |||
6926abd6f7 | |||
15dec40dfc | |||
4a36cf0c13 | |||
ecc92a6824 | |||
3d243cb8ca | |||
471448a0f1 | |||
ea3e976232 | |||
87f2463233 | |||
49c7877ec3 | |||
be1a9778e6 | |||
ed1d45cea1 | |||
db31b39ce2 | |||
d92d312b0c | |||
6837529096 | |||
b94ae9eff0 | |||
1810c0f355 | |||
eb0f45750d | |||
9ae8fb2355 | |||
512509c2e2 | |||
66815f590c | |||
f60e9bbe3e | |||
f361e3c9a9 | |||
e76dcf07c8 | |||
e6fe489be7 | |||
9ddb606a00 | |||
cd5ee2da18 | |||
4c42a9ddc8 | |||
78b1b0975c | |||
d99881aa46 | |||
df937fe65f | |||
dc742d3c92 | |||
a7b2ad526f | |||
bb804b9f6a | |||
1a00073cf6 | |||
469d07a2d6 | |||
6cf5e31843 | |||
3f1da6387b | |||
99b4858f1d | |||
4374c980ec | |||
ded7637b06 | |||
6a79ab6b5b | |||
7baff75524 | |||
d421c94647 | |||
d78205aa20 | |||
c1228bbd06 | |||
1eb43f684b | |||
4798e44cb7 | |||
a867e9af38 | |||
8fcf257726 | |||
a59d5a1bb8 | |||
b4d6006678 | |||
236c5bab84 | |||
852fdc4360 | |||
f7e85a92e8 | |||
e99fc2ecdc | |||
67785ed99b | |||
45ac4f116b | |||
173e3caf2f | |||
351af57591 | |||
0bda7a1c4b | |||
9a31c107fa | |||
5449fa15ea | |||
3e4e2affa8 | |||
d3a242a0b7 | |||
e5e2887c4d | |||
4eda2e4cb5 | |||
fcee721d58 | |||
7d12e63e34 | |||
8ff8b7929e | |||
66c53daed6 | |||
5de3a34dd0 | |||
b94112e22a | |||
99e925e7bd | |||
d8cba0d346 | |||
39de897621 | |||
9f1a793848 | |||
be9ba88d52 | |||
0084b6fb91 | |||
75b579bafa | |||
cf5ff99d8a | |||
ea204d90b1 | |||
13f6c2c747 | |||
760f827d0d | |||
8c8e0d4dea | |||
b749495bf4 | |||
7e3eabf09f | |||
e636876c9b | |||
68953d7390 | |||
1a52c2d9f8 | |||
6afcf6d4c3 | |||
af139331b1 | |||
e79a798b88 | |||
14fb790e2a | |||
2aab02940d | |||
da07067661 | |||
b2091114b3 | |||
e09128572c | |||
e16966d092 | |||
26a8b065bc | |||
589b98d97e | |||
cb4d9372f8 | |||
6cb7fa8a1b | |||
6cdbb8a0a3 | |||
781fb51c6f | |||
17646f3067 | |||
7a4b665bb5 | |||
571b36d05f | |||
23513cf88c | |||
b475c5c1ec | |||
ee9f26ee04 | |||
8c94cea764 | |||
7c63af5ba9 | |||
7c1eae83e4 | |||
e48ff0e41c | |||
c9e3a2a9b4 | |||
5667279cf1 | |||
2941bb2d5d | |||
a0044ad0ac | |||
fc5c0a1d6e | |||
b28719b7a5 | |||
87a3b6e409 | |||
dd62a10ed5 | |||
225d079d48 | |||
7ea43fdc7d | |||
67ad912a71 | |||
5602a456a7 | |||
28e5aa4e69 | |||
e55f3b6e74 | |||
f17afd932e | |||
7a6cc8caef | |||
94ead3751b | |||
ab600e014a | |||
60d02d88b5 | |||
05594441b8 | |||
9e461a9036 | |||
ce6bee19af | |||
0354c6e634 | |||
ac9efccd94 | |||
4df0fc5d5c | |||
4c180fedbd | |||
8b73ac77f9 | |||
cbb444f1bc | |||
b2762a3b6c | |||
512420f5a2 | |||
e94a625072 | |||
7bbe81ad1d | |||
335fad3f0d |
61
.github/workflows/check.yml
vendored
Normal file
61
.github/workflows/check.yml
vendored
Normal file
@ -0,0 +1,61 @@
|
||||
on: [push, pull_request]
|
||||
jobs:
|
||||
compile:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
example:
|
||||
- "BASIC"
|
||||
- "DiyProIndoorV4_2"
|
||||
- "DiyProIndoorV3_3"
|
||||
- "TestCO2"
|
||||
- "TestPM"
|
||||
- "TestSht"
|
||||
- "OneOpenAir"
|
||||
fqbn:
|
||||
- "esp8266:esp8266:d1_mini"
|
||||
- "esp32:esp32:esp32c3"
|
||||
include:
|
||||
- fqbn: "esp8266:esp8266:d1_mini"
|
||||
core: "esp8266:esp8266@3.1.2"
|
||||
core_url: "https://arduino.esp8266.com/stable/package_esp8266com_index.json"
|
||||
- fqbn: "esp32:esp32:esp32c3"
|
||||
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:
|
||||
- example: "BASIC"
|
||||
fqbn: "esp32:esp32:esp32c3"
|
||||
- example: "DiyProIndoorV4_2"
|
||||
fqbn: "esp32:esp32:esp32c3"
|
||||
- example: "DiyProIndoorV3_3"
|
||||
fqbn: "esp32:esp32:esp32c3"
|
||||
- example: "OneOpenAir"
|
||||
fqbn: "esp8266:esp8266:d1_mini"
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- run:
|
||||
curl -fsSL https://raw.githubusercontent.com/arduino/arduino-cli/master/install.sh |
|
||||
sh -s 0.35.3
|
||||
- run: bin/arduino-cli --verbose core install '${{ matrix.core }}'
|
||||
--additional-urls '${{ matrix.core_url }}'
|
||||
- run: bin/arduino-cli --verbose lib install
|
||||
WiFiManager@2.0.16-rc.2
|
||||
Arduino_JSON@0.2.0
|
||||
U8g2@2.34.22
|
||||
# In some cases, actions/checkout@v4 will check out a detached HEAD; for
|
||||
# example, this happens on pull request events, where an hypothetical
|
||||
# PR merge commit is checked out. This tends to confuse
|
||||
# `arduino-cli lib install --git-url`, making it fail with errors such as:
|
||||
# Error installing Git Library: Library install failed: object not found
|
||||
# Create and check out a dummy branch to work around this issue.
|
||||
- run: git checkout -b check
|
||||
- run: bin/arduino-cli --verbose lib install --git-url .
|
||||
env:
|
||||
ARDUINO_LIBRARY_ENABLE_UNSAFE_INSTALL: "true"
|
||||
- run: bin/arduino-cli --verbose compile 'examples/${{ matrix.example }}'
|
||||
--fqbn '${{ matrix.fqbn }}' --board-options '${{ matrix.board_options }}'
|
||||
# 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)
|
||||
# but that would either require a high fidelity device emulator, or a
|
||||
# "hardware lab" runner that is directly connected to a relevant device.
|
5
.gitignore
vendored
Normal file
5
.gitignore
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
*.DS_Store
|
||||
build
|
||||
.vscode
|
||||
/.idea/
|
||||
.pio
|
10
README.md
10
README.md
@ -24,6 +24,10 @@ If you have an older version of the AirGradient PCB not mentioned in the example
|
||||
|
||||
If you have any questions or problems, check out [our forum](https://forum.airgradient.com/).
|
||||
|
||||
## Documentation
|
||||
|
||||
Local server API documentation is available in [/docs/local-server.md](/docs/local-server.md) and AirGradient server API on [https://api.airgradient.com/public/docs/api/v1/](https://api.airgradient.com/public/docs/api/v1/).
|
||||
|
||||
## The following libraries have been integrated into this library for ease of use
|
||||
|
||||
- [Adafruit BusIO](https://github.com/adafruit/Adafruit_BusIO)
|
||||
@ -34,8 +38,10 @@ If you have any questions or problems, check out [our forum](https://forum.airgr
|
||||
- [Sensirion Gas Index Algorithm](https://github.com/Sensirion/arduino-gas-index-algorithm)
|
||||
- [Sensirion Core](https://github.com/Sensirion/arduino-core/)
|
||||
- [Sensirion I2C SGP41](https://github.com/Sensirion/arduino-i2c-sgp41)
|
||||
- [Sensirion I2C SHT4x](https://github.com/Sensirion/arduino-i2c-sht4x)
|
||||
- [PMS](https://github.com/fu-hsi/pms)
|
||||
- [Sensirion I2C SHT](https://github.com/Sensirion/arduino-sht)
|
||||
- [WiFiManager](https://github.com/tzapu/WiFiManager)
|
||||
- [Arduino_JSON](https://github.com/arduino-libraries/Arduino_JSON)
|
||||
- [PubSubClient](https://github.com/knolleary/pubsubclient)
|
||||
|
||||
## License
|
||||
CC BY-SA 4.0 Attribution-ShareAlike 4.0 International License
|
||||
|
216
docs/local-server.md
Normal file
216
docs/local-server.md
Normal file
@ -0,0 +1,216 @@
|
||||
## Local Server API
|
||||
|
||||
From [firmware version 3.0.10](firmwares) onwards, the AirGradient ONE and Open Air monitors have below API available.
|
||||
|
||||
#### Discovery
|
||||
|
||||
The monitors run a mDNS discovery. So within the same network, the monitor can be accessed through:
|
||||
|
||||
http://airgradient_{{serialnumber}}.local
|
||||
|
||||
|
||||
The following requests are possible:
|
||||
|
||||
#### Get Current Air Quality (GET)
|
||||
|
||||
With the path "/measures/current" you can get the current air quality data.
|
||||
|
||||
http://airgradient_ecda3b1eaaaf.local/measures/current
|
||||
|
||||
“ecda3b1eaaaf” being the serial number of your monitor.
|
||||
|
||||
You get the following response:
|
||||
```json
|
||||
{
|
||||
"wifi": -46,
|
||||
"serialno": "ecda3b1eaaaf",
|
||||
"rco2": 447,
|
||||
"pm01": 3,
|
||||
"pm02": 7,
|
||||
"pm10": 8,
|
||||
"pm003Count": 442,
|
||||
"atmp": 25.87,
|
||||
"atmpCompensated": 24.47,
|
||||
"rhum": 43,
|
||||
"rhumCompensated": 49,
|
||||
"tvocIndex": 100,
|
||||
"tvocRaw": 33051,
|
||||
"noxIndex": 1,
|
||||
"noxRaw": 16307,
|
||||
"boot": 6,
|
||||
"bootCount": 6,
|
||||
"ledMode": "pm",
|
||||
"firmware": "3.1.3",
|
||||
"model": "I-9PSL",
|
||||
"monitorDisplayCompensatedValues": true
|
||||
}
|
||||
```
|
||||
|
||||
| Properties | Type | Explanation |
|
||||
|-----------------------------------|---------|----------------------------------------------------------------------------------------|
|
||||
| `serialno` | String | Serial Number of the monitor |
|
||||
| `wifi` | Number | WiFi signal strength |
|
||||
| `pm01` | Number | PM1.0 in ug/m3 (atmospheric environment) |
|
||||
| `pm02` | Number | PM2.5 in ug/m3 (atmospheric environment) |
|
||||
| `pm10` | Number | PM10 in ug/m3 (atmospheric environment) |
|
||||
| `pm02Compensated` | Number | PM2.5 in ug/m3 with correction applied (from fw version 3.1.4 onwards) |
|
||||
| `pm01Standard` | Number | PM1.0 in ug/m3 (standard particle) |
|
||||
| `pm02Standard` | Number | PM2.5 in ug/m3 (standard particle) |
|
||||
| `pm10Standard` | Number | PM10 in ug/m3 (standard particle) |
|
||||
| `rco2` | Number | CO2 in ppm |
|
||||
| `pm003Count` | Number | Particle count 0.3um per dL |
|
||||
| `pm005Count` | Number | Particle count 0.5um per dL |
|
||||
| `pm01Count` | Number | Particle count 1.0um per dL |
|
||||
| `pm02Count` | Number | Particle count 2.5um per dL |
|
||||
| `pm50Count` | Number | Particle count 5.0um per dL (only for indoor monitor) |
|
||||
| `pm10Count` | Number | Particle count 10um per dL (only for indoor monitor) |
|
||||
| `atmp` | Number | Temperature in Degrees Celsius |
|
||||
| `atmpCompensated` | Number | Temperature in Degrees Celsius with correction applied |
|
||||
| `rhum` | Number | Relative Humidity |
|
||||
| `rhumCompensated` | Number | Relative Humidity with correction applied |
|
||||
| `tvocIndex` | Number | Senisiron VOC Index |
|
||||
| `tvocRaw` | Number | VOC raw value |
|
||||
| `noxIndex` | Number | Senisirion NOx Index |
|
||||
| `noxRaw` | Number | NOx raw value |
|
||||
| `boot` | Number | Counts every measurement cycle. Low boot counts indicate restarts. |
|
||||
| `bootCount` | Number | Same as boot property. Required for Home Assistant compatability. (deprecated soon!) |
|
||||
| `ledMode` | String | Current configuration of the LED mode |
|
||||
| `firmware` | String | Current firmware version |
|
||||
| `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.
|
||||
|
||||
#### Get Configuration Parameters (GET)
|
||||
|
||||
"/config" path returns the current configuration of the monitor.
|
||||
|
||||
```json
|
||||
{
|
||||
"country": "TH",
|
||||
"pmStandard": "ugm3",
|
||||
"ledBarMode": "pm",
|
||||
"abcDays": 7,
|
||||
"tvocLearningOffset": 12,
|
||||
"noxLearningOffset": 12,
|
||||
"mqttBrokerUrl": "",
|
||||
"temperatureUnit": "c",
|
||||
"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)
|
||||
|
||||
Configuration parameters can be changed with a PUT request to the monitor, e.g.
|
||||
|
||||
Example to force CO2 calibration
|
||||
|
||||
```bash
|
||||
curl -X PUT -H "Content-Type: application/json" -d '{"co2CalibrationRequested":true}' http://airgradient_84fce612eff4.local/config
|
||||
```
|
||||
|
||||
Example to set monitor to Celsius
|
||||
|
||||
```bash
|
||||
curl -X PUT -H "Content-Type: application/json" -d '{"temperatureUnit":"c"}' http://airgradient_84fce612eff4.local/config
|
||||
```
|
||||
|
||||
If you use command prompt on Windows, you need to escape the quotes:
|
||||
|
||||
``` -d "{\"param\":\"value\"}" ```
|
||||
|
||||
#### Avoiding Conflicts with Configuration on AirGradient Server
|
||||
|
||||
If the monitor is set up on the AirGradient dashboard, it will also receive 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 |
|
||||
|-----------------------------------|:-----------------------------------------------------------------|---------|-----------------------------------------------------------------------------------------------------------------------------------------|-------------------------------------------------|
|
||||
| `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"}` |
|
||||
| `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"}` |
|
||||
| `displayBrightness` | Brightness of the Display. | Number | 0-100 | `{"displayBrightness": 50}` |
|
||||
| `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}` |
|
||||
| `mqttBrokerUrl` | MQTT broker URL. | String | | `{"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"}` |
|
||||
| `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"}` |
|
||||
| `postDataToAirGradient` | Send data to AirGradient cloud. | Boolean | `true`: Enabled <br>`false`: Disabled | `{"postDataToAirGradient": true}` |
|
||||
| `co2CalibrationRequested` | Can be set to trigger a calibration. | Boolean | `true`: CO2 calibration (400ppm) will be triggered | `{"co2CalibrationRequested": true}` |
|
||||
| `ledBarTestRequested` | Can be set to trigger a test. | Boolean | `true` : LEDs will run test sequence | `{"ledBarTestRequested": true}` |
|
||||
| `noxLearningOffset` | Set NOx learning gain offset. | Number | 0-720 (default 12) | `{"noxLearningOffset": 12}` |
|
||||
| `tvocLearningOffset` | Set VOC learning gain offset. | Number | 0-720 (default 12) | `{"tvocLearningOffset": 12}` |
|
||||
| `offlineMode` | Set monitor to run without WiFi. | Boolean | `false`: Disabled (default) <br> `true`: Enabled | `{"offlineMode": true}` |
|
||||
| `monitorDisplayCompensatedValues` | Set the display show the PM value with/without compensate value (only on [3.1.9]()) | Boolean | `false`: Without compensate (default) <br> `true`: with compensate | `{"monitorDisplayCompensatedValues": false }` |
|
||||
| `corrections` | Sets correction options to display and measurement values on local server response. (version >= [3.1.11]()) | Object | _see corrections section_ | _see corrections section_ |
|
||||
|
||||
|
||||
|
||||
#### Corrections
|
||||
|
||||
The `corrections` object allows configuring PM2.5 correction algorithms and parameters locally. This affects both the display and local server response values.
|
||||
|
||||
Example correction configuration:
|
||||
|
||||
```json
|
||||
{
|
||||
"corrections": {
|
||||
"pm02": {
|
||||
"correctionAlgorithm": "<Option In String>",
|
||||
"slr": {
|
||||
"intercept": 0,
|
||||
"scalingFactor": 0,
|
||||
"useEpa2021": false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
| Algorithm | Value | Description | SLR required |
|
||||
|------------|-------------|------|---------|
|
||||
| Raw | `"none"` | No correction (default) | No |
|
||||
| EPA 2021 | `"epa_2021"` | Use EPA 2021 correction factors on top of raw value | No |
|
||||
| PMS5003_20240104 | `"slr_PMS5003_20240104"` | Correction for PMS5003 sensor batch 20240104| Yes |
|
||||
| PMS5003_20231218 | `"slr_PMS5003_20231218"` | Correction for PMS5003 sensor batch 20231218| Yes |
|
||||
| PMS5003_20231030 | `"slr_PMS5003_20231030"` | Correction for PMS5003 sensor batch 20231030| Yes |
|
||||
|
||||
**NOTES**:
|
||||
|
||||
- Set `useEpa2021` to `true` if want to apply EPA 2021 correction factors on top of SLR correction value, otherwise `false`
|
||||
- `intercept` and `scalingFactor` values can be obtained from [this article](https://www.airgradient.com/blog/low-readings-from-pms5003/)
|
||||
- If `configurationControl` is set to `local` (eg. when using Home Assistant), correction need to be set manually, see examples below
|
||||
|
||||
**Examples**:
|
||||
|
||||
- PMS5003_20231030
|
||||
|
||||
```bash
|
||||
curl --location -X PUT 'http://airgradient_84fce612eff4.local/config' --header 'Content-Type: application/json' --data '{"corrections":{"pm02":{"correctionAlgorithm":"slr_PMS5003_20231030","slr":{"intercept":0,"scalingFactor":0.02838,"useEpa2021":true}}}}'
|
||||
```
|
||||
|
||||
- PMS5003_20231218
|
||||
|
||||
```bash
|
||||
curl --location -X PUT 'http://airgradient_84fce612eff4.local/config' --header 'Content-Type: application/json' --data '{"corrections":{"pm02":{"correctionAlgorithm":"slr_PMS5003_20231218","slr":{"intercept":0,"scalingFactor":0.03525,"useEpa2021":true}}}}'
|
||||
```
|
||||
|
||||
- PMS5003_20240104
|
||||
|
||||
```bash
|
||||
curl --location -X PUT 'http://airgradient_84fce612eff4.local/config' --header 'Content-Type: application/json' --data '{"corrections":{"pm02":{"correctionAlgorithm":"slr_PMS5003_20240104","slr":{"intercept":0,"scalingFactor":0.02896,"useEpa2021":true}}}}'
|
||||
```
|
22
docs/ota-updates.md
Normal file
22
docs/ota-updates.md
Normal file
@ -0,0 +1,22 @@
|
||||
## OTA Updates
|
||||
|
||||
From [firmware version 3.1.1](https://github.com/airgradienthq/arduino/tree/3.1.1) onwards, the AirGradient ONE and Open Air monitors support over the air (OTA) updates.
|
||||
|
||||
#### Mechanism
|
||||
|
||||
Upon compilation of an official release the git tag (GIT_VERSION) is compiled into the binary.
|
||||
|
||||
The device attempts to update to the latest version on startup and in regular intervals using URL
|
||||
|
||||
http://hw.airgradient.com/sensors/{deviceId}/generic/os/firmware.bin?current_firmware={GIT_VERSION}
|
||||
|
||||
If does pass the version it is currently running on along to the server through URL parameter 'current_firmware'.
|
||||
This allows the server to identify if the device is already running on the latest version or should update.
|
||||
|
||||
The following scenarios are possible
|
||||
|
||||
1. The device is already on the latest firmware. Then the server returns a 304 with a short explanation text in the body saying this.
|
||||
2. The device reports a firmware unknown to the server. A 400 with an empty payload is returned in this case and the update is not performed. This case is relevant for local changes. The GIT_VERSION then defaults to "snapshot" which is unknown to the server.
|
||||
3. There is an update available. A 200 along with the binary data of the new version is returned and the update is performed.
|
||||
|
||||
More information about the implementation details are available here: https://docs.espressif.com/projects/esp-idf/en/stable/esp32/api-reference/system/ota.html
|
BIN
examples/.DS_Store
vendored
BIN
examples/.DS_Store
vendored
Binary file not shown.
589
examples/BASIC/BASIC.ino
Normal file
589
examples/BASIC/BASIC.ino
Normal file
@ -0,0 +1,589 @@
|
||||
/*
|
||||
This is the code for the AirGradient DIY BASIC Air Quality Monitor with an D1
|
||||
ESP8266 Microcontroller.
|
||||
|
||||
It is an air quality monitor for PM2.5, CO2, Temperature and Humidity with a
|
||||
small display and can send data over Wifi.
|
||||
|
||||
Open source air quality monitors and kits are available:
|
||||
Indoor Monitor: https://www.airgradient.com/indoor/
|
||||
Outdoor Monitor: https://www.airgradient.com/outdoor/
|
||||
|
||||
Build Instructions:
|
||||
https://www.airgradient.com/documentation/diy-v4/
|
||||
|
||||
Please make sure you have esp8266 board manager installed. Tested with
|
||||
version 3.1.2.
|
||||
|
||||
Set board to "LOLIN(WEMOS) D1 R2 & mini"
|
||||
|
||||
Configuration parameters, e.g. Celsius / Fahrenheit or PM unit (US AQI vs ug/m3)
|
||||
can be set through the AirGradient dashboard.
|
||||
|
||||
If you have any questions please visit our forum at
|
||||
https://forum.airgradient.com/
|
||||
|
||||
CC BY-SA 4.0 Attribution-ShareAlike 4.0 International License
|
||||
|
||||
*/
|
||||
|
||||
#include "AgApiClient.h"
|
||||
#include "AgConfigure.h"
|
||||
#include "AgSchedule.h"
|
||||
#include "AgWiFiConnector.h"
|
||||
#include "LocalServer.h"
|
||||
#include "OpenMetrics.h"
|
||||
#include "MqttClient.h"
|
||||
#include <AirGradient.h>
|
||||
#include <ESP8266HTTPClient.h>
|
||||
#include <ESP8266WiFi.h>
|
||||
#include <ESP8266mDNS.h>
|
||||
#include <WiFiClient.h>
|
||||
|
||||
#define LED_BAR_ANIMATION_PERIOD 100 /** ms */
|
||||
#define DISP_UPDATE_INTERVAL 2500 /** ms */
|
||||
#define SERVER_CONFIG_SYNC_INTERVAL 60000 /** ms */
|
||||
#define SERVER_SYNC_INTERVAL 60000 /** ms */
|
||||
#define MQTT_SYNC_INTERVAL 60000 /** ms */
|
||||
#define SENSOR_CO2_CALIB_COUNTDOWN_MAX 5 /** sec */
|
||||
#define SENSOR_TVOC_UPDATE_INTERVAL 1000 /** ms */
|
||||
#define SENSOR_CO2_UPDATE_INTERVAL 4000 /** ms */
|
||||
#define SENSOR_PM_UPDATE_INTERVAL 2000 /** ms */
|
||||
#define SENSOR_TEMP_HUM_UPDATE_INTERVAL 6000 /** ms */
|
||||
#define DISPLAY_DELAY_SHOW_CONTENT_MS 2000 /** ms */
|
||||
|
||||
static AirGradient ag(DIY_BASIC);
|
||||
static Configuration configuration(Serial);
|
||||
static AgApiClient apiClient(Serial, configuration);
|
||||
static Measurements measurements;
|
||||
static OledDisplay oledDisplay(configuration, measurements, Serial);
|
||||
static StateMachine stateMachine(oledDisplay, Serial, measurements,
|
||||
configuration);
|
||||
static WifiConnector wifiConnector(oledDisplay, Serial, stateMachine,
|
||||
configuration);
|
||||
static OpenMetrics openMetrics(measurements, configuration, wifiConnector,
|
||||
apiClient);
|
||||
static LocalServer localServer(Serial, openMetrics, measurements, configuration,
|
||||
wifiConnector);
|
||||
static MqttClient mqttClient(Serial);
|
||||
|
||||
static AgFirmwareMode fwMode = FW_MODE_I_BASIC_40PS;
|
||||
|
||||
static String fwNewVersion;
|
||||
|
||||
static void boardInit(void);
|
||||
static void failedHandler(String msg);
|
||||
static void configurationUpdateSchedule(void);
|
||||
static void appDispHandler(void);
|
||||
static void oledDisplaySchedule(void);
|
||||
static void updateTvoc(void);
|
||||
static void updatePm(void);
|
||||
static void sendDataToServer(void);
|
||||
static void tempHumUpdate(void);
|
||||
static void co2Update(void);
|
||||
static void mdnsInit(void);
|
||||
static void initMqtt(void);
|
||||
static void factoryConfigReset(void);
|
||||
static void wdgFeedUpdate(void);
|
||||
static bool sgp41Init(void);
|
||||
static void wifiFactoryConfigure(void);
|
||||
static void mqttHandle(void);
|
||||
static int calculateMaxPeriod(int updateInterval);
|
||||
static void setMeasurementMaxPeriod();
|
||||
|
||||
AgSchedule dispLedSchedule(DISP_UPDATE_INTERVAL, oledDisplaySchedule);
|
||||
AgSchedule configSchedule(SERVER_CONFIG_SYNC_INTERVAL,
|
||||
configurationUpdateSchedule);
|
||||
AgSchedule agApiPostSchedule(SERVER_SYNC_INTERVAL, sendDataToServer);
|
||||
AgSchedule co2Schedule(SENSOR_CO2_UPDATE_INTERVAL, co2Update);
|
||||
AgSchedule pmsSchedule(SENSOR_PM_UPDATE_INTERVAL, updatePm);
|
||||
AgSchedule tempHumSchedule(SENSOR_TEMP_HUM_UPDATE_INTERVAL, tempHumUpdate);
|
||||
AgSchedule tvocSchedule(SENSOR_TVOC_UPDATE_INTERVAL, updateTvoc);
|
||||
AgSchedule watchdogFeedSchedule(60000, wdgFeedUpdate);
|
||||
AgSchedule mqttSchedule(MQTT_SYNC_INTERVAL, mqttHandle);
|
||||
|
||||
void setup() {
|
||||
/** Serial for print debug message */
|
||||
Serial.begin(115200);
|
||||
delay(100); /** For bester show log */
|
||||
|
||||
/** Print device ID into log */
|
||||
Serial.println("Serial nr: " + ag.deviceId());
|
||||
|
||||
/** Initialize local configure */
|
||||
configuration.begin();
|
||||
|
||||
/** Init I2C */
|
||||
Wire.begin(ag.getI2cSdaPin(), ag.getI2cSclPin());
|
||||
delay(1000);
|
||||
|
||||
configuration.setAirGradient(&ag);
|
||||
oledDisplay.setAirGradient(&ag);
|
||||
stateMachine.setAirGradient(&ag);
|
||||
wifiConnector.setAirGradient(&ag);
|
||||
apiClient.setAirGradient(&ag);
|
||||
openMetrics.setAirGradient(&ag);
|
||||
localServer.setAirGraident(&ag);
|
||||
|
||||
/** Example set custom API root URL */
|
||||
// apiClient.setApiRoot("https://example.custom.api");
|
||||
|
||||
/** Init sensor */
|
||||
boardInit();
|
||||
setMeasurementMaxPeriod();
|
||||
|
||||
// Uncomment below line to print every measurements reading update
|
||||
// measurements.setDebug(true);
|
||||
|
||||
/** Connecting wifi */
|
||||
bool connectToWifi = false;
|
||||
|
||||
connectToWifi = !configuration.isOfflineMode();
|
||||
if (connectToWifi) {
|
||||
apiClient.begin();
|
||||
|
||||
if (wifiConnector.connect()) {
|
||||
if (wifiConnector.isConnected()) {
|
||||
mdnsInit();
|
||||
localServer.begin();
|
||||
initMqtt();
|
||||
sendDataToAg();
|
||||
|
||||
apiClient.fetchServerConfiguration();
|
||||
configSchedule.update();
|
||||
if (apiClient.isFetchConfigureFailed()) {
|
||||
if (apiClient.isNotAvailableOnDashboard()) {
|
||||
stateMachine.displaySetAddToDashBoard();
|
||||
stateMachine.displayHandle(
|
||||
AgStateMachineWiFiOkServerOkSensorConfigFailed);
|
||||
} else {
|
||||
stateMachine.displayClearAddToDashBoard();
|
||||
}
|
||||
delay(DISPLAY_DELAY_SHOW_CONTENT_MS);
|
||||
}
|
||||
} else {
|
||||
if (wifiConnector.isConfigurePorttalTimeout()) {
|
||||
oledDisplay.showRebooting();
|
||||
delay(2500);
|
||||
oledDisplay.setText("", "", "");
|
||||
ESP.restart();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
/** Set offline mode without saving, cause wifi is not configured */
|
||||
if (wifiConnector.hasConfigurated() == false) {
|
||||
Serial.println("Set offline mode cause wifi is not configurated");
|
||||
configuration.setOfflineModeWithoutSave(true);
|
||||
}
|
||||
|
||||
/** Show display Warning up */
|
||||
String sn = "SN:" + ag.deviceId();
|
||||
oledDisplay.setText("Warming Up", sn.c_str(), "");
|
||||
|
||||
delay(DISPLAY_DELAY_SHOW_CONTENT_MS);
|
||||
|
||||
Serial.println("Display brightness: " +
|
||||
String(configuration.getDisplayBrightness()));
|
||||
oledDisplay.setBrightness(configuration.getDisplayBrightness());
|
||||
|
||||
appDispHandler();
|
||||
}
|
||||
|
||||
void loop() {
|
||||
/** Handle schedule */
|
||||
dispLedSchedule.run();
|
||||
configSchedule.run();
|
||||
agApiPostSchedule.run();
|
||||
|
||||
if (configuration.hasSensorS8) {
|
||||
co2Schedule.run();
|
||||
}
|
||||
if (configuration.hasSensorPMS1) {
|
||||
pmsSchedule.run();
|
||||
ag.pms5003.handle();
|
||||
}
|
||||
if (configuration.hasSensorSHT) {
|
||||
tempHumSchedule.run();
|
||||
}
|
||||
if (configuration.hasSensorSGP) {
|
||||
tvocSchedule.run();
|
||||
}
|
||||
|
||||
watchdogFeedSchedule.run();
|
||||
|
||||
/** Check for handle WiFi reconnect */
|
||||
wifiConnector.handle();
|
||||
|
||||
/** factory reset handle */
|
||||
// factoryConfigReset();
|
||||
|
||||
/** check that local configura changed then do some action */
|
||||
configUpdateHandle();
|
||||
|
||||
localServer._handle();
|
||||
|
||||
if (configuration.hasSensorSGP) {
|
||||
ag.sgp41.handle();
|
||||
}
|
||||
|
||||
MDNS.update();
|
||||
|
||||
mqttSchedule.run();
|
||||
mqttClient.handle();
|
||||
}
|
||||
|
||||
static void co2Update(void) {
|
||||
if (!configuration.hasSensorS8) {
|
||||
// Device don't have S8 sensor
|
||||
return;
|
||||
}
|
||||
|
||||
int value = ag.s8.getCo2();
|
||||
if (utils::isValidCO2(value)) {
|
||||
measurements.update(Measurements::CO2, value);
|
||||
} else {
|
||||
measurements.update(Measurements::CO2, utils::getInvalidCO2());
|
||||
}
|
||||
}
|
||||
|
||||
static void mdnsInit(void) {
|
||||
Serial.println("mDNS init");
|
||||
if (!MDNS.begin(localServer.getHostname().c_str())) {
|
||||
Serial.println("Init mDNS failed");
|
||||
return;
|
||||
}
|
||||
|
||||
MDNS.addService("_airgradient", "_tcp", 80);
|
||||
MDNS.addServiceTxt("_airgradient", "_tcp", "model",
|
||||
AgFirmwareModeName(fwMode));
|
||||
MDNS.addServiceTxt("_airgradient", "_tcp", "serialno", ag.deviceId());
|
||||
MDNS.addServiceTxt("_airgradient", "_tcp", "fw_ver", ag.getVersion());
|
||||
MDNS.addServiceTxt("_airgradient", "_tcp", "vendor", "AirGradient");
|
||||
|
||||
MDNS.announce();
|
||||
}
|
||||
|
||||
static void initMqtt(void) {
|
||||
String mqttUri = configuration.getMqttBrokerUri();
|
||||
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 {
|
||||
Serial.println("Connection to MQTT broker failed");
|
||||
}
|
||||
}
|
||||
|
||||
static void wdgFeedUpdate(void) {
|
||||
ag.watchdog.reset();
|
||||
Serial.println("External watchdog feed!");
|
||||
}
|
||||
|
||||
static bool sgp41Init(void) {
|
||||
ag.sgp41.setNoxLearningOffset(configuration.getNoxLearningOffset());
|
||||
ag.sgp41.setTvocLearningOffset(configuration.getTvocLearningOffset());
|
||||
if (ag.sgp41.begin(Wire)) {
|
||||
Serial.println("Init SGP41 success");
|
||||
configuration.hasSensorSGP = true;
|
||||
return true;
|
||||
} else {
|
||||
Serial.println("Init SGP41 failuire");
|
||||
configuration.hasSensorSGP = false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static void wifiFactoryConfigure(void) {
|
||||
WiFi.persistent(true);
|
||||
WiFi.begin("airgradient", "cleanair");
|
||||
WiFi.persistent(false);
|
||||
oledDisplay.setText("Configure WiFi", "connect to", "\'airgradient\'");
|
||||
delay(2500);
|
||||
oledDisplay.setText("Rebooting...", "", "");
|
||||
delay(2500);
|
||||
oledDisplay.setText("", "", "");
|
||||
ESP.restart();
|
||||
}
|
||||
|
||||
static void mqttHandle(void) {
|
||||
if(mqttClient.isConnected() == false) {
|
||||
mqttClient.connect(String("airgradient-") + ag.deviceId());
|
||||
}
|
||||
|
||||
if (mqttClient.isConnected()) {
|
||||
String payload = measurements.toString(true, fwMode, wifiConnector.RSSI(), ag, configuration);
|
||||
String topic = "airgradient/readings/" + ag.deviceId();
|
||||
if (mqttClient.publish(topic.c_str(), payload.c_str(), payload.length())) {
|
||||
Serial.println("MQTT sync success");
|
||||
} else {
|
||||
Serial.println("MQTT sync failure");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void sendDataToAg() {
|
||||
/** Change oledDisplay and led state */
|
||||
stateMachine.displayHandle(AgStateMachineWiFiOkServerConnecting);
|
||||
|
||||
delay(1500);
|
||||
if (apiClient.sendPing(wifiConnector.RSSI(), measurements.bootCount)) {
|
||||
stateMachine.displayHandle(AgStateMachineWiFiOkServerConnected);
|
||||
} else {
|
||||
stateMachine.displayHandle(AgStateMachineWiFiOkServerConnectFailed);
|
||||
}
|
||||
delay(DISPLAY_DELAY_SHOW_CONTENT_MS);
|
||||
}
|
||||
|
||||
void dispSensorNotFound(String ss) {
|
||||
oledDisplay.setText("Sensor", ss.c_str(), "not found");
|
||||
delay(2000);
|
||||
}
|
||||
|
||||
static void boardInit(void) {
|
||||
/** Display init */
|
||||
oledDisplay.begin();
|
||||
|
||||
/** Show boot display */
|
||||
Serial.println("Firmware Version: " + ag.getVersion());
|
||||
|
||||
if (ag.isBasic()) {
|
||||
oledDisplay.setText("DIY Basic", ag.getVersion().c_str(), "");
|
||||
} else {
|
||||
oledDisplay.setText("AirGradient ONE",
|
||||
"FW Version: ", ag.getVersion().c_str());
|
||||
}
|
||||
|
||||
delay(DISPLAY_DELAY_SHOW_CONTENT_MS);
|
||||
|
||||
ag.watchdog.begin();
|
||||
|
||||
/** Show message init sensor */
|
||||
oledDisplay.setText("Sensor", "init...", "");
|
||||
|
||||
/** Init sensor SGP41 */
|
||||
configuration.hasSensorSGP = false;
|
||||
// if (sgp41Init() == false) {
|
||||
// dispSensorNotFound("SGP41");
|
||||
// }
|
||||
|
||||
/** Init SHT */
|
||||
if (ag.sht.begin(Wire) == false) {
|
||||
Serial.println("SHTx sensor not found");
|
||||
configuration.hasSensorSHT = false;
|
||||
dispSensorNotFound("SHT");
|
||||
}
|
||||
|
||||
/** Init S8 CO2 sensor */
|
||||
if (ag.s8.begin(&Serial) == false) {
|
||||
Serial.println("CO2 S8 sensor not found");
|
||||
configuration.hasSensorS8 = false;
|
||||
dispSensorNotFound("S8");
|
||||
}
|
||||
|
||||
/** Init PMS5003 */
|
||||
configuration.hasSensorPMS1 = true;
|
||||
configuration.hasSensorPMS2 = false;
|
||||
if (ag.pms5003.begin(&Serial) == false) {
|
||||
Serial.println("PMS sensor not found");
|
||||
configuration.hasSensorPMS1 = false;
|
||||
|
||||
dispSensorNotFound("PMS");
|
||||
}
|
||||
|
||||
/** Set S8 CO2 abc days period */
|
||||
if (configuration.hasSensorS8) {
|
||||
if (ag.s8.setAbcPeriod(configuration.getCO2CalibrationAbcDays() * 24)) {
|
||||
Serial.println("Set S8 AbcDays successful");
|
||||
} else {
|
||||
Serial.println("Set S8 AbcDays failure");
|
||||
}
|
||||
}
|
||||
|
||||
localServer.setFwMode(fwMode);
|
||||
}
|
||||
|
||||
static void failedHandler(String msg) {
|
||||
while (true) {
|
||||
Serial.println(msg);
|
||||
delay(1000);
|
||||
}
|
||||
}
|
||||
|
||||
static void configurationUpdateSchedule(void) {
|
||||
if (apiClient.fetchServerConfiguration()) {
|
||||
configUpdateHandle();
|
||||
}
|
||||
}
|
||||
|
||||
static void configUpdateHandle() {
|
||||
if (configuration.isUpdated() == false) {
|
||||
return;
|
||||
}
|
||||
|
||||
stateMachine.executeCo2Calibration();
|
||||
|
||||
String mqttUri = configuration.getMqttBrokerUri();
|
||||
if (mqttClient.isCurrentUri(mqttUri) == false) {
|
||||
mqttClient.end();
|
||||
initMqtt();
|
||||
}
|
||||
|
||||
if (configuration.hasSensorSGP) {
|
||||
if (configuration.noxLearnOffsetChanged() ||
|
||||
configuration.tvocLearnOffsetChanged()) {
|
||||
ag.sgp41.end();
|
||||
|
||||
int oldTvocOffset = ag.sgp41.getTvocLearningOffset();
|
||||
int oldNoxOffset = ag.sgp41.getNoxLearningOffset();
|
||||
bool result = sgp41Init();
|
||||
const char *resultStr = "successful";
|
||||
if (!result) {
|
||||
resultStr = "failure";
|
||||
}
|
||||
if (oldTvocOffset != configuration.getTvocLearningOffset()) {
|
||||
Serial.printf("Setting tvocLearningOffset from %d to %d hours %s\r\n",
|
||||
oldTvocOffset, configuration.getTvocLearningOffset(),
|
||||
resultStr);
|
||||
}
|
||||
if (oldNoxOffset != configuration.getNoxLearningOffset()) {
|
||||
Serial.printf("Setting noxLearningOffset from %d to %d hours %s\r\n",
|
||||
oldNoxOffset, configuration.getNoxLearningOffset(),
|
||||
resultStr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (configuration.isDisplayBrightnessChanged()) {
|
||||
oledDisplay.setBrightness(configuration.getDisplayBrightness());
|
||||
}
|
||||
|
||||
appDispHandler();
|
||||
}
|
||||
|
||||
static void appDispHandler(void) {
|
||||
AgStateMachineState state = AgStateMachineNormal;
|
||||
|
||||
/** Only show display status on online mode. */
|
||||
if (configuration.isOfflineMode() == false) {
|
||||
if (wifiConnector.isConnected() == false) {
|
||||
state = AgStateMachineWiFiLost;
|
||||
} else if (apiClient.isFetchConfigureFailed()) {
|
||||
state = AgStateMachineSensorConfigFailed;
|
||||
if (apiClient.isNotAvailableOnDashboard()) {
|
||||
stateMachine.displaySetAddToDashBoard();
|
||||
} else {
|
||||
stateMachine.displayClearAddToDashBoard();
|
||||
}
|
||||
} else if (apiClient.isPostToServerFailed()) {
|
||||
state = AgStateMachineServerLost;
|
||||
}
|
||||
}
|
||||
stateMachine.displayHandle(state);
|
||||
}
|
||||
|
||||
static void oledDisplaySchedule(void) {
|
||||
|
||||
appDispHandler();
|
||||
}
|
||||
|
||||
static void updateTvoc(void) {
|
||||
if (!configuration.hasSensorSGP) {
|
||||
return;
|
||||
}
|
||||
|
||||
measurements.update(Measurements::TVOC, ag.sgp41.getTvocIndex());
|
||||
measurements.update(Measurements::TVOCRaw, ag.sgp41.getTvocRaw());
|
||||
measurements.update(Measurements::NOx, ag.sgp41.getNoxIndex());
|
||||
measurements.update(Measurements::NOxRaw, ag.sgp41.getNoxRaw());
|
||||
}
|
||||
|
||||
static void updatePm(void) {
|
||||
if (ag.pms5003.connected()) {
|
||||
measurements.update(Measurements::PM01, ag.pms5003.getPm01Ae());
|
||||
measurements.update(Measurements::PM25, ag.pms5003.getPm25Ae());
|
||||
measurements.update(Measurements::PM10, ag.pms5003.getPm10Ae());
|
||||
measurements.update(Measurements::PM03_PC, ag.pms5003.getPm03ParticleCount());
|
||||
} else {
|
||||
measurements.update(Measurements::PM01, utils::getInvalidPmValue());
|
||||
measurements.update(Measurements::PM25, utils::getInvalidPmValue());
|
||||
measurements.update(Measurements::PM10, utils::getInvalidPmValue());
|
||||
measurements.update(Measurements::PM03_PC, utils::getInvalidPmValue());
|
||||
}
|
||||
}
|
||||
|
||||
static void sendDataToServer(void) {
|
||||
/** Increment bootcount when send measurements data is scheduled */
|
||||
measurements.bootCount++;
|
||||
|
||||
/** Ignore send data to server if postToAirGradient disabled */
|
||||
if (configuration.isPostDataToAirGradient() == false ||
|
||||
configuration.isOfflineMode()) {
|
||||
return;
|
||||
}
|
||||
|
||||
String syncData = measurements.toString(false, fwMode, wifiConnector.RSSI(), ag, configuration);
|
||||
if (apiClient.postToServer(syncData)) {
|
||||
Serial.println();
|
||||
Serial.println(
|
||||
"Online mode and isPostToAirGradient = true: watchdog reset");
|
||||
Serial.println();
|
||||
}
|
||||
}
|
||||
|
||||
static void tempHumUpdate(void) {
|
||||
if (ag.sht.measure()) {
|
||||
float temp = ag.sht.getTemperature();
|
||||
float rhum = ag.sht.getRelativeHumidity();
|
||||
|
||||
measurements.update(Measurements::Temperature, temp);
|
||||
measurements.update(Measurements::Humidity, rhum);
|
||||
|
||||
// Update compensation temperature and humidity for SGP41
|
||||
if (configuration.hasSensorSGP) {
|
||||
ag.sgp41.setCompensationTemperatureHumidity(temp, rhum);
|
||||
}
|
||||
} else {
|
||||
measurements.update(Measurements::Temperature, utils::getInvalidTemperature());
|
||||
measurements.update(Measurements::Humidity, utils::getInvalidHumidity());
|
||||
Serial.println("SHT read failed");
|
||||
}
|
||||
}
|
||||
|
||||
/* 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;
|
||||
}
|
60
examples/BASIC/LocalServer.cpp
Normal file
60
examples/BASIC/LocalServer.cpp
Normal file
@ -0,0 +1,60 @@
|
||||
#include "LocalServer.h"
|
||||
|
||||
LocalServer::LocalServer(Stream &log, OpenMetrics &openMetrics,
|
||||
Measurements &measure, Configuration &config,
|
||||
WifiConnector &wifiConnector)
|
||||
: PrintLog(log, "LocalServer"), openMetrics(openMetrics), measure(measure),
|
||||
config(config), wifiConnector(wifiConnector), server(80) {}
|
||||
|
||||
LocalServer::~LocalServer() {}
|
||||
|
||||
bool LocalServer::begin(void) {
|
||||
server.on("/measures/current", HTTP_GET, [this]() { _GET_measure(); });
|
||||
server.on(openMetrics.getApi(), HTTP_GET, [this]() { _GET_metrics(); });
|
||||
server.on("/config", HTTP_GET, [this]() { _GET_config(); });
|
||||
server.on("/config", HTTP_PUT, [this]() { _PUT_config(); });
|
||||
server.begin();
|
||||
logInfo("Init: " + getHostname() + ".local");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void LocalServer::setAirGraident(AirGradient *ag) { this->ag = ag; }
|
||||
|
||||
String LocalServer::getHostname(void) {
|
||||
return "airgradient_" + ag->deviceId();
|
||||
}
|
||||
|
||||
void LocalServer::_handle(void) { server.handleClient(); }
|
||||
|
||||
void LocalServer::_GET_config(void) {
|
||||
if(ag->isOne()) {
|
||||
server.send(200, "application/json", config.toString());
|
||||
} else {
|
||||
server.send(200, "application/json", config.toString(fwMode));
|
||||
}
|
||||
}
|
||||
|
||||
void LocalServer::_PUT_config(void) {
|
||||
String data = server.arg(0);
|
||||
String response = "";
|
||||
int statusCode = 400; // Status code for data invalid
|
||||
if (config.parse(data, true)) {
|
||||
statusCode = 200;
|
||||
response = "Success";
|
||||
} else {
|
||||
response = config.getFailedMesage();
|
||||
}
|
||||
server.send(statusCode, "text/plain", response);
|
||||
}
|
||||
|
||||
void LocalServer::_GET_metrics(void) {
|
||||
server.send(200, openMetrics.getApiContentType(), openMetrics.getPayload());
|
||||
}
|
||||
|
||||
void LocalServer::_GET_measure(void) {
|
||||
String toSend = measure.toString(true, fwMode, wifiConnector.RSSI(), *ag, config);
|
||||
server.send(200, "application/json", toSend);
|
||||
}
|
||||
|
||||
void LocalServer::setFwMode(AgFirmwareMode fwMode) { this->fwMode = fwMode; }
|
38
examples/BASIC/LocalServer.h
Normal file
38
examples/BASIC/LocalServer.h
Normal file
@ -0,0 +1,38 @@
|
||||
#ifndef _LOCAL_SERVER_H_
|
||||
#define _LOCAL_SERVER_H_
|
||||
|
||||
#include "AgConfigure.h"
|
||||
#include "AgValue.h"
|
||||
#include "AirGradient.h"
|
||||
#include "OpenMetrics.h"
|
||||
#include "AgWiFiConnector.h"
|
||||
#include <Arduino.h>
|
||||
#include <ESP8266WebServer.h>
|
||||
|
||||
class LocalServer : public PrintLog {
|
||||
private:
|
||||
AirGradient *ag;
|
||||
OpenMetrics &openMetrics;
|
||||
Measurements &measure;
|
||||
Configuration &config;
|
||||
WifiConnector &wifiConnector;
|
||||
ESP8266WebServer server;
|
||||
AgFirmwareMode fwMode;
|
||||
|
||||
public:
|
||||
LocalServer(Stream &log, OpenMetrics &openMetrics, Measurements &measure,
|
||||
Configuration &config, WifiConnector& wifiConnector);
|
||||
~LocalServer();
|
||||
|
||||
bool begin(void);
|
||||
void setAirGraident(AirGradient *ag);
|
||||
String getHostname(void);
|
||||
void setFwMode(AgFirmwareMode fwMode);
|
||||
void _handle(void);
|
||||
void _GET_config(void);
|
||||
void _PUT_config(void);
|
||||
void _GET_metrics(void);
|
||||
void _GET_measure(void);
|
||||
};
|
||||
|
||||
#endif /** _LOCAL_SERVER_H_ */
|
203
examples/BASIC/OpenMetrics.cpp
Normal file
203
examples/BASIC/OpenMetrics.cpp
Normal file
@ -0,0 +1,203 @@
|
||||
#include "OpenMetrics.h"
|
||||
|
||||
OpenMetrics::OpenMetrics(Measurements &measure, Configuration &config,
|
||||
WifiConnector &wifiConnector, AgApiClient &apiClient)
|
||||
: measure(measure), config(config), wifiConnector(wifiConnector),
|
||||
apiClient(apiClient) {}
|
||||
|
||||
OpenMetrics::~OpenMetrics() {}
|
||||
|
||||
void OpenMetrics::setAirGradient(AirGradient *ag) { this->ag = ag; }
|
||||
|
||||
const char *OpenMetrics::getApiContentType(void) {
|
||||
return "application/openmetrics-text; version=1.0.0; charset=utf-8";
|
||||
}
|
||||
|
||||
const char *OpenMetrics::getApi(void) { return "/metrics"; }
|
||||
|
||||
String OpenMetrics::getPayload(void) {
|
||||
String response;
|
||||
String current_metric_name;
|
||||
const auto add_metric = [&](const String &name, const String &help,
|
||||
const String &type, const String &unit = "") {
|
||||
current_metric_name = "airgradient_" + name;
|
||||
if (!unit.isEmpty())
|
||||
current_metric_name += "_" + unit;
|
||||
response += "# HELP " + current_metric_name + " " + help + "\n";
|
||||
response += "# TYPE " + current_metric_name + " " + type + "\n";
|
||||
if (!unit.isEmpty())
|
||||
response += "# UNIT " + current_metric_name + " " + unit + "\n";
|
||||
};
|
||||
const auto add_metric_point = [&](const String &labels, const String &value) {
|
||||
response += current_metric_name + "{" + labels + "} " + value + "\n";
|
||||
};
|
||||
|
||||
add_metric("info", "AirGradient device information", "info");
|
||||
add_metric_point("airgradient_serial_number=\"" + ag->deviceId() +
|
||||
"\",airgradient_device_type=\"" + ag->getBoardName() +
|
||||
"\",airgradient_library_version=\"" + ag->getVersion() +
|
||||
"\"",
|
||||
"1");
|
||||
|
||||
add_metric("config_ok",
|
||||
"1 if the AirGradient device was able to successfully fetch its "
|
||||
"configuration from the server",
|
||||
"gauge");
|
||||
add_metric_point("", apiClient.isFetchConfigureFailed() ? "0" : "1");
|
||||
|
||||
add_metric(
|
||||
"post_ok",
|
||||
"1 if the AirGradient device was able to successfully send to the server",
|
||||
"gauge");
|
||||
add_metric_point("", apiClient.isPostToServerFailed() ? "0" : "1");
|
||||
|
||||
add_metric(
|
||||
"wifi_rssi",
|
||||
"WiFi signal strength from the AirGradient device perspective, in dBm",
|
||||
"gauge", "dbm");
|
||||
add_metric_point("", String(wifiConnector.RSSI()));
|
||||
|
||||
// Initialize default invalid value for each measurements
|
||||
float _temp = utils::getInvalidTemperature();
|
||||
float _hum = utils::getInvalidHumidity();
|
||||
int pm01 = utils::getInvalidPmValue();
|
||||
int pm25 = utils::getInvalidPmValue();
|
||||
int pm10 = utils::getInvalidPmValue();
|
||||
int pm03PCount = utils::getInvalidPmValue();
|
||||
int co2 = utils::getInvalidCO2();
|
||||
int atmpCompensated = utils::getInvalidTemperature();
|
||||
int ahumCompensated = utils::getInvalidHumidity();
|
||||
int tvoc = utils::getInvalidVOC();
|
||||
int tvocRaw = utils::getInvalidVOC();
|
||||
int nox = utils::getInvalidNOx();
|
||||
int noxRaw = utils::getInvalidNOx();
|
||||
|
||||
if (config.hasSensorSHT) {
|
||||
_temp = measure.getFloat(Measurements::Temperature);
|
||||
_hum = measure.getFloat(Measurements::Humidity);
|
||||
atmpCompensated = _temp;
|
||||
ahumCompensated = _hum;
|
||||
}
|
||||
|
||||
if (config.hasSensorPMS1) {
|
||||
pm01 = measure.get(Measurements::PM01);
|
||||
pm25 = measure.get(Measurements::PM25);
|
||||
pm10 = measure.get(Measurements::PM10);
|
||||
pm03PCount = measure.get(Measurements::PM03_PC);
|
||||
}
|
||||
|
||||
if (config.hasSensorSGP) {
|
||||
tvoc = measure.get(Measurements::TVOC);
|
||||
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 (utils::isValidPm(pm01)) {
|
||||
add_metric("pm1",
|
||||
"PM1.0 concentration as measured by the AirGradient PMS "
|
||||
"sensor, in micrograms per cubic meter",
|
||||
"gauge", "ugm3");
|
||||
add_metric_point("", String(pm01));
|
||||
}
|
||||
if (utils::isValidPm(pm25)) {
|
||||
add_metric("pm2d5",
|
||||
"PM2.5 concentration as measured by the AirGradient PMS "
|
||||
"sensor, in micrograms per cubic meter",
|
||||
"gauge", "ugm3");
|
||||
add_metric_point("", String(pm25));
|
||||
}
|
||||
if (utils::isValidPm(pm10)) {
|
||||
add_metric("pm10",
|
||||
"PM10 concentration as measured by the AirGradient PMS "
|
||||
"sensor, in micrograms per cubic meter",
|
||||
"gauge", "ugm3");
|
||||
add_metric_point("", String(pm10));
|
||||
}
|
||||
if (utils::isValidPm03Count(pm03PCount)) {
|
||||
add_metric("pm0d3",
|
||||
"PM0.3 concentration as measured by the AirGradient PMS "
|
||||
"sensor, in number of particules per 100 milliliters",
|
||||
"gauge", "p100ml");
|
||||
add_metric_point("", String(pm03PCount));
|
||||
}
|
||||
}
|
||||
|
||||
if (config.hasSensorSGP) {
|
||||
if (utils::isValidVOC(tvoc)) {
|
||||
add_metric("tvoc_index",
|
||||
"The processed Total Volatile Organic Compounds (TVOC) index "
|
||||
"as measured by the AirGradient SGP sensor",
|
||||
"gauge");
|
||||
add_metric_point("", String(tvoc));
|
||||
}
|
||||
if (utils::isValidVOC(tvocRaw)) {
|
||||
add_metric("tvoc_raw",
|
||||
"The raw input value to the Total Volatile Organic Compounds "
|
||||
"(TVOC) index as measured by the AirGradient SGP sensor",
|
||||
"gauge");
|
||||
add_metric_point("", String(tvocRaw));
|
||||
}
|
||||
if (utils::isValidNOx(nox)) {
|
||||
add_metric("nox_index",
|
||||
"The processed Nitrous Oxide (NOx) index as measured by the "
|
||||
"AirGradient SGP sensor",
|
||||
"gauge");
|
||||
add_metric_point("", String(nox));
|
||||
}
|
||||
if (utils::isValidNOx(noxRaw)) {
|
||||
add_metric("nox_raw",
|
||||
"The raw input value to the Nitrous Oxide (NOx) index as "
|
||||
"measured by the AirGradient SGP sensor",
|
||||
"gauge");
|
||||
add_metric_point("", String(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)) {
|
||||
add_metric(
|
||||
"temperature",
|
||||
"The ambient temperature as measured by the AirGradient SHT / PMS "
|
||||
"sensor, in degrees Celsius",
|
||||
"gauge", "celsius");
|
||||
add_metric_point("", String(_temp));
|
||||
}
|
||||
if (utils::isValidTemperature(atmpCompensated)) {
|
||||
add_metric("temperature_compensated",
|
||||
"The compensated ambient temperature as measured by the "
|
||||
"AirGradient SHT / PMS "
|
||||
"sensor, in degrees Celsius",
|
||||
"gauge", "celsius");
|
||||
add_metric_point("", String(atmpCompensated));
|
||||
}
|
||||
if (utils::isValidHumidity(_hum)) {
|
||||
add_metric(
|
||||
"humidity",
|
||||
"The relative humidity as measured by the AirGradient SHT sensor",
|
||||
"gauge", "percent");
|
||||
add_metric_point("", String(_hum));
|
||||
}
|
||||
if (utils::isValidHumidity(ahumCompensated)) {
|
||||
add_metric("humidity_compensated",
|
||||
"The compensated relative humidity as measured by the "
|
||||
"AirGradient SHT / PMS sensor",
|
||||
"gauge", "percent");
|
||||
add_metric_point("", String(ahumCompensated));
|
||||
}
|
||||
|
||||
response += "# EOF\n";
|
||||
return response;
|
||||
}
|
28
examples/BASIC/OpenMetrics.h
Normal file
28
examples/BASIC/OpenMetrics.h
Normal file
@ -0,0 +1,28 @@
|
||||
#ifndef _OPEN_METRICS_H_
|
||||
#define _OPEN_METRICS_H_
|
||||
|
||||
#include "AgConfigure.h"
|
||||
#include "AgValue.h"
|
||||
#include "AgWiFiConnector.h"
|
||||
#include "AirGradient.h"
|
||||
#include "AgApiClient.h"
|
||||
|
||||
class OpenMetrics {
|
||||
private:
|
||||
AirGradient *ag;
|
||||
Measurements &measure;
|
||||
Configuration &config;
|
||||
WifiConnector &wifiConnector;
|
||||
AgApiClient &apiClient;
|
||||
|
||||
public:
|
||||
OpenMetrics(Measurements &measure, Configuration &conig,
|
||||
WifiConnector &wifiConnector, AgApiClient& apiClient);
|
||||
~OpenMetrics();
|
||||
void setAirGradient(AirGradient *ag);
|
||||
const char *getApiContentType(void);
|
||||
const char* getApi(void);
|
||||
String getPayload(void);
|
||||
};
|
||||
|
||||
#endif /** _OPEN_METRICS_H_ */
|
@ -1,460 +0,0 @@
|
||||
/*
|
||||
This is the code for the AirGradient DIY BASIC Air Quality Monitor with an D1 ESP8266 Microcontroller.
|
||||
|
||||
It is an air quality monitor for PM2.5, CO2, Temperature and Humidity with a small display and can send data over Wifi.
|
||||
|
||||
Open source air quality monitors and kits are available:
|
||||
Indoor Monitor: https://www.airgradient.com/indoor/
|
||||
Outdoor Monitor: https://www.airgradient.com/outdoor/
|
||||
|
||||
Build Instructions:
|
||||
https://www.airgradient.com/documentation/diy-v4/
|
||||
|
||||
Following libraries need to be installed:
|
||||
“WifiManager by tzapu, tablatronix” tested with version 2.0.16-rc.2
|
||||
"Arduino_JSON" by Arduino version 0.2.0
|
||||
"U8g2" by oliver version 2.34.22
|
||||
|
||||
Please make sure you have esp8266 board manager installed. Tested with version 3.1.2.
|
||||
|
||||
Set board to "LOLIN(WEMOS) D1 R2 & mini"
|
||||
|
||||
Configuration parameters, e.g. Celsius / Fahrenheit or PM unit (US AQI vs ug/m3) can be set through the AirGradient dashboard.
|
||||
|
||||
If you have any questions please visit our forum at
|
||||
https://forum.airgradient.com/
|
||||
|
||||
CC BY-SA 4.0 Attribution-ShareAlike 4.0 International License
|
||||
|
||||
*/
|
||||
|
||||
#include <AirGradient.h>
|
||||
#include <Arduino_JSON.h>
|
||||
#include <ESP8266HTTPClient.h>
|
||||
#include <ESP8266WiFi.h>
|
||||
#include <U8g2lib.h>
|
||||
#include <WiFiClient.h>
|
||||
#include <WiFiManager.h>
|
||||
|
||||
typedef struct {
|
||||
bool inF; /** Temperature unit */
|
||||
bool inUSAQI; /** PMS standard */
|
||||
uint8_t ledBarMode; /** @ref UseLedBar*/
|
||||
char model[16]; /** Model string value, Just define, don't know how much
|
||||
memory usage */
|
||||
char mqttBroker[128]; /** Mqtt broker link */
|
||||
uint32_t _check; /** Checksum configuration data */
|
||||
} ServerConfig_t;
|
||||
static ServerConfig_t serverConfig;
|
||||
|
||||
AirGradient ag = AirGradient(BOARD_DIY_BASIC_KIT);
|
||||
|
||||
// CONFIGURATION START
|
||||
|
||||
// set to the endpoint you would like to use
|
||||
String APIROOT = "http://hw.airgradient.com/";
|
||||
|
||||
String wifiApPass = "cleanair";
|
||||
|
||||
// set to true if you want to connect to wifi. You have 60 seconds to connect.
|
||||
// Then it will go into an offline mode.
|
||||
boolean connectWIFI = true;
|
||||
|
||||
// CONFIGURATION END
|
||||
|
||||
unsigned long currentMillis = 0;
|
||||
|
||||
const int oledInterval = 5000;
|
||||
unsigned long previousOled = 0;
|
||||
bool co2CalibrationRequest = false;
|
||||
uint32_t serverConfigLoadTime = 0;
|
||||
String HOSTPOT = "";
|
||||
|
||||
const int sendToServerInterval = 60000;
|
||||
const int pollServerConfigInterval = 30000;
|
||||
const int co2CalibCountdown = 5; /** Seconds */
|
||||
unsigned long previoussendToServer = 0;
|
||||
|
||||
const int co2Interval = 5000;
|
||||
unsigned long previousCo2 = 0;
|
||||
int Co2 = 0;
|
||||
|
||||
const int pm25Interval = 5000;
|
||||
unsigned long previousPm25 = 0;
|
||||
int pm25 = 0;
|
||||
|
||||
const int tempHumInterval = 2500;
|
||||
unsigned long previousTempHum = 0;
|
||||
float temp = 0;
|
||||
int hum = 0;
|
||||
long val;
|
||||
|
||||
void failedHandler(String msg);
|
||||
void boardInit(void);
|
||||
void getServerConfig(void);
|
||||
void co2Calibration(void);
|
||||
|
||||
void setup() {
|
||||
Serial.begin(115200);
|
||||
|
||||
/** Init I2C */
|
||||
Wire.begin(ag.getI2cSdaPin(), ag.getI2cSclPin());
|
||||
|
||||
/** Board init */
|
||||
boardInit();
|
||||
|
||||
/** Show boot display */
|
||||
displayShowText("Basic v4", "Lib:" + ag.getVersion(), "");
|
||||
delay(2000);
|
||||
|
||||
if (connectWIFI) {
|
||||
connectToWifi();
|
||||
}
|
||||
|
||||
/** Show display */
|
||||
displayShowText("Warm Up", "Serial#", String(ESP.getChipId(), HEX));
|
||||
delay(10000);
|
||||
|
||||
getServerConfig();
|
||||
}
|
||||
|
||||
void loop() {
|
||||
currentMillis = millis();
|
||||
updateOLED();
|
||||
updateCo2();
|
||||
updatePm25();
|
||||
updateTempHum();
|
||||
sendToServer();
|
||||
getServerConfig();
|
||||
}
|
||||
|
||||
void updateCo2() {
|
||||
if (currentMillis - previousCo2 >= co2Interval) {
|
||||
previousCo2 += co2Interval;
|
||||
Co2 = ag.s8.getCo2();
|
||||
Serial.println(String(Co2));
|
||||
}
|
||||
}
|
||||
|
||||
void updatePm25() {
|
||||
if (currentMillis - previousPm25 >= pm25Interval) {
|
||||
previousPm25 += pm25Interval;
|
||||
if (ag.pms5003.readData()) {
|
||||
pm25 = ag.pms5003.getPm25Ae();
|
||||
Serial.printf("PM25: %d\r\n", pm25);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void updateTempHum() {
|
||||
if (currentMillis - previousTempHum >= tempHumInterval) {
|
||||
previousTempHum += tempHumInterval;
|
||||
|
||||
/** Get temperature and humidity */
|
||||
temp = ag.sht.getTemperature();
|
||||
hum = ag.sht.getRelativeHumidity();
|
||||
|
||||
/** Print debug message */
|
||||
Serial.printf("SHT Humidity: %d%, Temperature: %0.2f\r\n", hum, temp);
|
||||
}
|
||||
}
|
||||
|
||||
void updateOLED() {
|
||||
if (currentMillis - previousOled >= oledInterval) {
|
||||
previousOled += oledInterval;
|
||||
|
||||
String ln1;
|
||||
String ln2;
|
||||
String ln3;
|
||||
|
||||
if (serverConfig.inUSAQI) {
|
||||
ln1 = "AQI:" + String(ag.pms5003.convertPm25ToUsAqi(pm25));
|
||||
} else {
|
||||
ln1 = "PM :" + String(pm25) + " ug";
|
||||
}
|
||||
ln2 = "CO2:" + String(Co2);
|
||||
|
||||
if (serverConfig.inF) {
|
||||
ln3 =
|
||||
String((temp * 9 / 5) + 32).substring(0, 4) + " " + String(hum) + "%";
|
||||
} else {
|
||||
ln3 = String(temp).substring(0, 4) + " " + String(hum) + "%";
|
||||
}
|
||||
displayShowText(ln1, ln2, ln3);
|
||||
}
|
||||
}
|
||||
|
||||
void displayShowText(String ln1, String ln2, String ln3) {
|
||||
char buf[9];
|
||||
ag.display.clear();
|
||||
|
||||
ag.display.setCursor(1, 1);
|
||||
ag.display.setText(ln1);
|
||||
ag.display.setCursor(1, 19);
|
||||
ag.display.setText(ln2);
|
||||
ag.display.setCursor(1, 37);
|
||||
ag.display.setText(ln3);
|
||||
|
||||
ag.display.show();
|
||||
}
|
||||
|
||||
void sendToServer() {
|
||||
if (currentMillis - previoussendToServer >= sendToServerInterval) {
|
||||
previoussendToServer += sendToServerInterval;
|
||||
|
||||
String payload = "{\"wifi\":" + String(WiFi.RSSI()) +
|
||||
(Co2 < 0 ? "" : ", \"rco2\":" + String(Co2)) +
|
||||
(pm25 < 0 ? "" : ", \"pm02\":" + String(pm25)) +
|
||||
", \"atmp\":" + String(temp) +
|
||||
(hum < 0 ? "" : ", \"rhum\":" + String(hum)) + "}";
|
||||
|
||||
if (WiFi.status() == WL_CONNECTED) {
|
||||
Serial.println(payload);
|
||||
String POSTURL = APIROOT +
|
||||
"sensors/airgradient:" + String(ESP.getChipId(), HEX) +
|
||||
"/measures";
|
||||
Serial.println(POSTURL);
|
||||
WiFiClient client;
|
||||
HTTPClient http;
|
||||
http.begin(client, POSTURL);
|
||||
http.addHeader("content-type", "application/json");
|
||||
int httpCode = http.POST(payload);
|
||||
String response = http.getString();
|
||||
Serial.println(httpCode);
|
||||
Serial.println(response);
|
||||
http.end();
|
||||
} else {
|
||||
Serial.println("WiFi Disconnected");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Wifi Manager
|
||||
void connectToWifi() {
|
||||
WiFiManager wifiManager;
|
||||
// WiFi.disconnect(); //to delete previous saved hotspot
|
||||
String HOTSPOT = "AG-" + String(ESP.getChipId(), HEX);
|
||||
// displayShowText("Connect", "AG-", String(ESP.getChipId(), HEX));
|
||||
delay(2000);
|
||||
// wifiManager.setTimeout(90);
|
||||
wifiManager.setConfigPortalBlocking(false);
|
||||
wifiManager.setConfigPortalTimeout(180);
|
||||
wifiManager.autoConnect(HOTSPOT.c_str(), wifiApPass.c_str());
|
||||
|
||||
uint32_t lastTime = millis();
|
||||
int count = 179;
|
||||
displayShowText("180 sec", "SSID:",HOTSPOT);
|
||||
while (wifiManager.getConfigPortalActive()) {
|
||||
wifiManager.process();
|
||||
uint32_t ms = (uint32_t)(millis() - lastTime);
|
||||
if (ms >= 1000) {
|
||||
lastTime = millis();
|
||||
displayShowText(String(count) + " sec", "SSID:",HOTSPOT);
|
||||
count--;
|
||||
|
||||
// Timeout
|
||||
if (count == 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!WiFi.isConnected()) {
|
||||
displayShowText("Booting", "offline", "mode");
|
||||
Serial.println("failed to connect and hit timeout");
|
||||
delay(6000);
|
||||
}
|
||||
}
|
||||
|
||||
void failedHandler(String msg) {
|
||||
while (true) {
|
||||
Serial.println(msg);
|
||||
delay(1000);
|
||||
}
|
||||
}
|
||||
|
||||
void boardInit(void) {
|
||||
/** Init SHT sensor */
|
||||
if (ag.sht.begin(Wire) == false) {
|
||||
failedHandler("SHT init failed");
|
||||
}
|
||||
|
||||
/** CO2 init */
|
||||
if (ag.s8.begin(&Serial) == false) {
|
||||
failedHandler("SenseAirS8 init failed");
|
||||
}
|
||||
|
||||
/** PMS init */
|
||||
if (ag.pms5003.begin(&Serial) == false) {
|
||||
failedHandler("PMS5003 init failed");
|
||||
}
|
||||
|
||||
/** Display init */
|
||||
ag.display.begin(Wire);
|
||||
ag.display.setTextColor(1);
|
||||
}
|
||||
|
||||
void showConfig(void) {
|
||||
Serial.println("Server configuration: ");
|
||||
Serial.printf(" inF: %s\r\n", serverConfig.inF ? "true" : "false");
|
||||
Serial.printf(" inUSAQI: %s\r\n",
|
||||
serverConfig.inUSAQI ? "true" : "false");
|
||||
Serial.printf("useRGBLedBar: %d\r\n", (int)serverConfig.ledBarMode);
|
||||
Serial.printf(" Model: %.*s\r\n", sizeof(serverConfig.model),
|
||||
serverConfig.model);
|
||||
Serial.printf(" Mqtt Broker: %.*s\r\n", sizeof(serverConfig.mqttBroker),
|
||||
serverConfig.mqttBroker);
|
||||
}
|
||||
|
||||
void updateServerConfigLoadTime(void) {
|
||||
serverConfigLoadTime = millis();
|
||||
if (serverConfigLoadTime == 0) {
|
||||
serverConfigLoadTime = 1;
|
||||
}
|
||||
}
|
||||
|
||||
void getServerConfig(void) {
|
||||
/** Only trigger load configuration again after pollServerConfigInterval sec
|
||||
*/
|
||||
if (serverConfigLoadTime) {
|
||||
uint32_t ms = (uint32_t)(millis() - serverConfigLoadTime);
|
||||
if (ms < pollServerConfigInterval) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
updateServerConfigLoadTime();
|
||||
|
||||
Serial.println("Trigger load server configuration");
|
||||
if (WiFi.status() != WL_CONNECTED) {
|
||||
Serial.println(
|
||||
"Ignore get server configuration because WIFI not connected");
|
||||
return;
|
||||
}
|
||||
|
||||
// WiFiClient wifiClient;
|
||||
HTTPClient httpClient;
|
||||
|
||||
String getUrl = "http://hw.airgradient.com/sensors/airgradient:" +
|
||||
String(ESP.getChipId(), HEX) + "/one/config";
|
||||
Serial.println("HttpClient get: " + getUrl);
|
||||
WiFiClient client;
|
||||
if (httpClient.begin(client, getUrl) == false) {
|
||||
Serial.println("HttpClient init failed");
|
||||
updateServerConfigLoadTime();
|
||||
return;
|
||||
}
|
||||
|
||||
int respCode = httpClient.GET();
|
||||
|
||||
/** get failure */
|
||||
if (respCode != 200) {
|
||||
Serial.printf("HttpClient get failed: %d\r\n", respCode);
|
||||
updateServerConfigLoadTime();
|
||||
return;
|
||||
}
|
||||
|
||||
String respContent = httpClient.getString();
|
||||
Serial.println("Server config: " + respContent);
|
||||
|
||||
/** Parse JSON */
|
||||
JSONVar root = JSON.parse(respContent);
|
||||
if (JSON.typeof_(root) == "undefined") {
|
||||
Serial.println("Server configura JSON invalid");
|
||||
updateServerConfigLoadTime();
|
||||
return;
|
||||
}
|
||||
|
||||
/** Get "country" */
|
||||
bool inF = serverConfig.inF;
|
||||
if (JSON.typeof_(root["country"]) == "string") {
|
||||
String country = root["country"];
|
||||
if (country == "US") {
|
||||
inF = true;
|
||||
} else {
|
||||
inF = false;
|
||||
}
|
||||
}
|
||||
|
||||
/** Get "pmStandard" */
|
||||
bool inUSAQI = serverConfig.inUSAQI;
|
||||
if (JSON.typeof_(root["pmStandard"]) == "string") {
|
||||
String standard = root["pmStandard"];
|
||||
if (standard == "ugm3") {
|
||||
inUSAQI = false;
|
||||
} else {
|
||||
inUSAQI = true;
|
||||
}
|
||||
}
|
||||
|
||||
/** Get CO2 "co2CalibrationRequested" */
|
||||
co2CalibrationRequest = false;
|
||||
if (JSON.typeof_(root["co2CalibrationRequested"]) == "boolean") {
|
||||
co2CalibrationRequest = root["co2CalibrationRequested"];
|
||||
}
|
||||
|
||||
/** get "model" */
|
||||
String model = "";
|
||||
if (JSON.typeof_(root["model"]) == "string") {
|
||||
String _model = root["model"];
|
||||
model = _model;
|
||||
}
|
||||
|
||||
/** get "mqttBrokerUrl" */
|
||||
String mqtt = "";
|
||||
if (JSON.typeof_(root["mqttBrokerUrl"]) == "string") {
|
||||
String _mqtt = root["mqttBrokerUrl"];
|
||||
mqtt = _mqtt;
|
||||
}
|
||||
|
||||
if (inF != serverConfig.inF) {
|
||||
serverConfig.inF = inF;
|
||||
}
|
||||
if (inUSAQI != serverConfig.inUSAQI) {
|
||||
serverConfig.inUSAQI = inUSAQI;
|
||||
}
|
||||
if (model.length()) {
|
||||
if (model != String(serverConfig.model)) {
|
||||
memset(serverConfig.model, 0, sizeof(serverConfig.model));
|
||||
memcpy(serverConfig.model, model.c_str(), model.length());
|
||||
}
|
||||
}
|
||||
if (mqtt.length()) {
|
||||
if (mqtt != String(serverConfig.mqttBroker)) {
|
||||
memset(serverConfig.mqttBroker, 0, sizeof(serverConfig.mqttBroker));
|
||||
memcpy(serverConfig.mqttBroker, mqtt.c_str(), mqtt.length());
|
||||
}
|
||||
}
|
||||
|
||||
/** Show server configuration */
|
||||
showConfig();
|
||||
|
||||
/** Calibration */
|
||||
if (co2CalibrationRequest) {
|
||||
co2Calibration();
|
||||
}
|
||||
}
|
||||
|
||||
void co2Calibration(void) {
|
||||
/** Count down for co2CalibCountdown secs */
|
||||
for (int i = 0; i < co2CalibCountdown; i++) {
|
||||
displayShowText("CO2 calib", "after",
|
||||
String(co2CalibCountdown - i) + " sec");
|
||||
delay(1000);
|
||||
}
|
||||
|
||||
if (ag.s8.setBaselineCalibration()) {
|
||||
displayShowText("Calib", "success", "");
|
||||
delay(1000);
|
||||
displayShowText("Wait for", "finish", "...");
|
||||
int count = 0;
|
||||
while (ag.s8.isBaseLineCalibrationDone() == false) {
|
||||
delay(1000);
|
||||
count++;
|
||||
}
|
||||
displayShowText("Finish", "after", String(count) + " sec");
|
||||
delay(2000);
|
||||
} else {
|
||||
displayShowText("Calib", "failure!!!", "");
|
||||
delay(2000);
|
||||
}
|
||||
}
|
641
examples/DiyProIndoorV3_3/DiyProIndoorV3_3.ino
Normal file
641
examples/DiyProIndoorV3_3/DiyProIndoorV3_3.ino
Normal file
@ -0,0 +1,641 @@
|
||||
/*
|
||||
This is the code for the AirGradient DIY PRO 3.3 Air Quality Monitor with an D1
|
||||
ESP8266 Microcontroller.
|
||||
|
||||
It is an air quality monitor for PM2.5, CO2, Temperature and Humidity with a
|
||||
small display and can send data over Wifi.
|
||||
|
||||
Open source air quality monitors and kits are available:
|
||||
Indoor Monitor: https://www.airgradient.com/indoor/
|
||||
Outdoor Monitor: https://www.airgradient.com/outdoor/
|
||||
|
||||
Build Instructions:
|
||||
https://www.airgradient.com/documentation/diy-v4/
|
||||
|
||||
Please make sure you have esp8266 board manager installed. Tested with
|
||||
version 3.1.2.
|
||||
|
||||
Set board to "LOLIN(WEMOS) D1 R2 & mini"
|
||||
|
||||
Configuration parameters, e.g. Celsius / Fahrenheit or PM unit (US AQI vs ug/m3)
|
||||
can be set through the AirGradient dashboard.
|
||||
|
||||
If you have any questions please visit our forum at
|
||||
https://forum.airgradient.com/
|
||||
|
||||
CC BY-SA 4.0 Attribution-ShareAlike 4.0 International License
|
||||
|
||||
*/
|
||||
|
||||
#include "AgApiClient.h"
|
||||
#include "AgConfigure.h"
|
||||
#include "AgSchedule.h"
|
||||
#include "AgWiFiConnector.h"
|
||||
#include "LocalServer.h"
|
||||
#include "OpenMetrics.h"
|
||||
#include "MqttClient.h"
|
||||
#include <AirGradient.h>
|
||||
#include <ESP8266HTTPClient.h>
|
||||
#include <ESP8266WiFi.h>
|
||||
#include <ESP8266mDNS.h>
|
||||
#include <WiFiClient.h>
|
||||
|
||||
#define LED_BAR_ANIMATION_PERIOD 100 /** ms */
|
||||
#define DISP_UPDATE_INTERVAL 2500 /** ms */
|
||||
#define SERVER_CONFIG_SYNC_INTERVAL 60000 /** ms */
|
||||
#define SERVER_SYNC_INTERVAL 60000 /** ms */
|
||||
#define MQTT_SYNC_INTERVAL 60000 /** ms */
|
||||
#define SENSOR_CO2_CALIB_COUNTDOWN_MAX 5 /** sec */
|
||||
#define SENSOR_TVOC_UPDATE_INTERVAL 1000 /** ms */
|
||||
#define SENSOR_CO2_UPDATE_INTERVAL 4000 /** ms */
|
||||
#define SENSOR_PM_UPDATE_INTERVAL 2000 /** ms */
|
||||
#define SENSOR_TEMP_HUM_UPDATE_INTERVAL 6000 /** ms */
|
||||
#define DISPLAY_DELAY_SHOW_CONTENT_MS 2000 /** ms */
|
||||
|
||||
static AirGradient ag(DIY_PRO_INDOOR_V3_3);
|
||||
static Configuration configuration(Serial);
|
||||
static AgApiClient apiClient(Serial, configuration);
|
||||
static Measurements measurements;
|
||||
static OledDisplay oledDisplay(configuration, measurements, Serial);
|
||||
static StateMachine stateMachine(oledDisplay, Serial, measurements,
|
||||
configuration);
|
||||
static WifiConnector wifiConnector(oledDisplay, Serial, stateMachine,
|
||||
configuration);
|
||||
static OpenMetrics openMetrics(measurements, configuration, wifiConnector,
|
||||
apiClient);
|
||||
static LocalServer localServer(Serial, openMetrics, measurements, configuration,
|
||||
wifiConnector);
|
||||
static MqttClient mqttClient(Serial);
|
||||
|
||||
static AgFirmwareMode fwMode = FW_MODE_I_33PS;
|
||||
|
||||
static String fwNewVersion;
|
||||
|
||||
static void boardInit(void);
|
||||
static void failedHandler(String msg);
|
||||
static void configurationUpdateSchedule(void);
|
||||
static void appDispHandler(void);
|
||||
static void oledDisplaySchedule(void);
|
||||
static void updateTvoc(void);
|
||||
static void updatePm(void);
|
||||
static void sendDataToServer(void);
|
||||
static void tempHumUpdate(void);
|
||||
static void co2Update(void);
|
||||
static void mdnsInit(void);
|
||||
static void initMqtt(void);
|
||||
static void factoryConfigReset(void);
|
||||
static void wdgFeedUpdate(void);
|
||||
static bool sgp41Init(void);
|
||||
static void wifiFactoryConfigure(void);
|
||||
static void mqttHandle(void);
|
||||
static int calculateMaxPeriod(int updateInterval);
|
||||
static void setMeasurementMaxPeriod();
|
||||
|
||||
AgSchedule dispLedSchedule(DISP_UPDATE_INTERVAL, oledDisplaySchedule);
|
||||
AgSchedule configSchedule(SERVER_CONFIG_SYNC_INTERVAL,
|
||||
configurationUpdateSchedule);
|
||||
AgSchedule agApiPostSchedule(SERVER_SYNC_INTERVAL, sendDataToServer);
|
||||
AgSchedule co2Schedule(SENSOR_CO2_UPDATE_INTERVAL, co2Update);
|
||||
AgSchedule pmsSchedule(SENSOR_PM_UPDATE_INTERVAL, updatePm);
|
||||
AgSchedule tempHumSchedule(SENSOR_TEMP_HUM_UPDATE_INTERVAL, tempHumUpdate);
|
||||
AgSchedule tvocSchedule(SENSOR_TVOC_UPDATE_INTERVAL, updateTvoc);
|
||||
AgSchedule watchdogFeedSchedule(60000, wdgFeedUpdate);
|
||||
AgSchedule mqttSchedule(MQTT_SYNC_INTERVAL, mqttHandle);
|
||||
|
||||
void setup() {
|
||||
/** Serial for print debug message */
|
||||
Serial.begin(115200);
|
||||
delay(100); /** For bester show log */
|
||||
|
||||
/** Print device ID into log */
|
||||
Serial.println("Serial nr: " + ag.deviceId());
|
||||
|
||||
/** Initialize local configure */
|
||||
configuration.begin();
|
||||
|
||||
/** Init I2C */
|
||||
Wire.begin(ag.getI2cSdaPin(), ag.getI2cSclPin());
|
||||
delay(1000);
|
||||
|
||||
configuration.setAirGradient(&ag);
|
||||
oledDisplay.setAirGradient(&ag);
|
||||
stateMachine.setAirGradient(&ag);
|
||||
wifiConnector.setAirGradient(&ag);
|
||||
apiClient.setAirGradient(&ag);
|
||||
openMetrics.setAirGradient(&ag);
|
||||
localServer.setAirGraident(&ag);
|
||||
|
||||
/** Example set custom API root URL */
|
||||
// apiClient.setApiRoot("https://example.custom.api");
|
||||
|
||||
/** Init sensor */
|
||||
boardInit();
|
||||
setMeasurementMaxPeriod();
|
||||
|
||||
// Uncomment below line to print every measurements reading update
|
||||
// measurements.setDebug(true);
|
||||
|
||||
/** Connecting wifi */
|
||||
bool connectToWifi = false;
|
||||
|
||||
connectToWifi = !configuration.isOfflineMode();
|
||||
if (connectToWifi) {
|
||||
apiClient.begin();
|
||||
|
||||
if (wifiConnector.connect()) {
|
||||
if (wifiConnector.isConnected()) {
|
||||
mdnsInit();
|
||||
localServer.begin();
|
||||
initMqtt();
|
||||
sendDataToAg();
|
||||
|
||||
apiClient.fetchServerConfiguration();
|
||||
configSchedule.update();
|
||||
if (apiClient.isFetchConfigureFailed()) {
|
||||
if (apiClient.isNotAvailableOnDashboard()) {
|
||||
stateMachine.displaySetAddToDashBoard();
|
||||
stateMachine.displayHandle(
|
||||
AgStateMachineWiFiOkServerOkSensorConfigFailed);
|
||||
} else {
|
||||
stateMachine.displayClearAddToDashBoard();
|
||||
}
|
||||
delay(DISPLAY_DELAY_SHOW_CONTENT_MS);
|
||||
}
|
||||
} else {
|
||||
if (wifiConnector.isConfigurePorttalTimeout()) {
|
||||
oledDisplay.showRebooting();
|
||||
delay(2500);
|
||||
oledDisplay.setText("", "", "");
|
||||
ESP.restart();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
/** Set offline mode without saving, cause wifi is not configured */
|
||||
if (wifiConnector.hasConfigurated() == false) {
|
||||
Serial.println("Set offline mode cause wifi is not configurated");
|
||||
configuration.setOfflineModeWithoutSave(true);
|
||||
}
|
||||
|
||||
/** Show display Warning up */
|
||||
oledDisplay.setText("Warming Up", "Serial Number:", ag.deviceId().c_str());
|
||||
delay(DISPLAY_DELAY_SHOW_CONTENT_MS);
|
||||
|
||||
Serial.println("Display brightness: " +
|
||||
String(configuration.getDisplayBrightness()));
|
||||
oledDisplay.setBrightness(configuration.getDisplayBrightness());
|
||||
|
||||
appDispHandler();
|
||||
}
|
||||
|
||||
void loop() {
|
||||
/** Handle schedule */
|
||||
dispLedSchedule.run();
|
||||
configSchedule.run();
|
||||
agApiPostSchedule.run();
|
||||
|
||||
if (configuration.hasSensorS8) {
|
||||
co2Schedule.run();
|
||||
}
|
||||
if (configuration.hasSensorPMS1) {
|
||||
pmsSchedule.run();
|
||||
ag.pms5003.handle();
|
||||
}
|
||||
if (configuration.hasSensorSHT) {
|
||||
tempHumSchedule.run();
|
||||
}
|
||||
if (configuration.hasSensorSGP) {
|
||||
tvocSchedule.run();
|
||||
}
|
||||
|
||||
watchdogFeedSchedule.run();
|
||||
|
||||
/** Check for handle WiFi reconnect */
|
||||
wifiConnector.handle();
|
||||
|
||||
/** factory reset handle */
|
||||
// factoryConfigReset();
|
||||
|
||||
/** check that local configura changed then do some action */
|
||||
configUpdateHandle();
|
||||
|
||||
localServer._handle();
|
||||
|
||||
if (configuration.hasSensorSGP) {
|
||||
ag.sgp41.handle();
|
||||
}
|
||||
|
||||
MDNS.update();
|
||||
|
||||
mqttSchedule.run();
|
||||
mqttClient.handle();
|
||||
}
|
||||
|
||||
static void co2Update(void) {
|
||||
if (!configuration.hasSensorS8) {
|
||||
// Device don't have S8 sensor
|
||||
return;
|
||||
}
|
||||
|
||||
int value = ag.s8.getCo2();
|
||||
if (utils::isValidCO2(value)) {
|
||||
measurements.update(Measurements::CO2, value);
|
||||
} else {
|
||||
measurements.update(Measurements::CO2, utils::getInvalidCO2());
|
||||
}
|
||||
}
|
||||
|
||||
static void mdnsInit(void) {
|
||||
Serial.println("mDNS init");
|
||||
if (!MDNS.begin(localServer.getHostname().c_str())) {
|
||||
Serial.println("Init mDNS failed");
|
||||
return;
|
||||
}
|
||||
|
||||
MDNS.addService("_airgradient", "_tcp", 80);
|
||||
MDNS.addServiceTxt("_airgradient", "_tcp", "model",
|
||||
AgFirmwareModeName(fwMode));
|
||||
MDNS.addServiceTxt("_airgradient", "_tcp", "serialno", ag.deviceId());
|
||||
MDNS.addServiceTxt("_airgradient", "_tcp", "fw_ver", ag.getVersion());
|
||||
MDNS.addServiceTxt("_airgradient", "_tcp", "vendor", "AirGradient");
|
||||
|
||||
MDNS.announce();
|
||||
}
|
||||
|
||||
static void initMqtt(void) {
|
||||
String mqttUri = configuration.getMqttBrokerUri();
|
||||
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 {
|
||||
Serial.println("Connection to MQTT broker failed");
|
||||
}
|
||||
}
|
||||
|
||||
static void factoryConfigReset(void) {
|
||||
#if 0
|
||||
if (ag.button.getState() == ag.button.BUTTON_PRESSED) {
|
||||
if (factoryBtnPressTime == 0) {
|
||||
factoryBtnPressTime = millis();
|
||||
} else {
|
||||
uint32_t ms = (uint32_t)(millis() - factoryBtnPressTime);
|
||||
if (ms >= 2000) {
|
||||
// Show display message: For factory keep for x seconds
|
||||
if (ag.isOne() || ag.isPro4_2()) {
|
||||
oledDisplay.setText("Factory reset", "keep pressed", "for 8 sec");
|
||||
} else {
|
||||
Serial.println("Factory reset, keep pressed for 8 sec");
|
||||
}
|
||||
|
||||
int count = 7;
|
||||
while (ag.button.getState() == ag.button.BUTTON_PRESSED) {
|
||||
delay(1000);
|
||||
String str = "for " + String(count) + " sec";
|
||||
oledDisplay.setText("Factory reset", "keep pressed", str.c_str());
|
||||
|
||||
count--;
|
||||
if (count == 0) {
|
||||
/** Stop MQTT task first */
|
||||
// if (mqttTask) {
|
||||
// vTaskDelete(mqttTask);
|
||||
// mqttTask = NULL;
|
||||
// }
|
||||
|
||||
/** Reset WIFI */
|
||||
// WiFi.enableSTA(true); // Incase offline mode
|
||||
// WiFi.disconnect(true, true);
|
||||
wifiConnector.reset();
|
||||
|
||||
/** Reset local config */
|
||||
configuration.reset();
|
||||
|
||||
oledDisplay.setText("Factory reset", "successful", "");
|
||||
|
||||
delay(3000);
|
||||
oledDisplay.setText("", "", "");
|
||||
ESP.restart();
|
||||
}
|
||||
}
|
||||
|
||||
/** Show current content cause reset ignore */
|
||||
factoryBtnPressTime = 0;
|
||||
appDispHandler();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (factoryBtnPressTime != 0) {
|
||||
appDispHandler();
|
||||
}
|
||||
factoryBtnPressTime = 0;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
static void wdgFeedUpdate(void) {
|
||||
ag.watchdog.reset();
|
||||
Serial.println("External watchdog feed!");
|
||||
}
|
||||
|
||||
static bool sgp41Init(void) {
|
||||
ag.sgp41.setNoxLearningOffset(configuration.getNoxLearningOffset());
|
||||
ag.sgp41.setTvocLearningOffset(configuration.getTvocLearningOffset());
|
||||
if (ag.sgp41.begin(Wire)) {
|
||||
Serial.println("Init SGP41 success");
|
||||
configuration.hasSensorSGP = true;
|
||||
return true;
|
||||
} else {
|
||||
Serial.println("Init SGP41 failuire");
|
||||
configuration.hasSensorSGP = false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static void wifiFactoryConfigure(void) {
|
||||
WiFi.persistent(true);
|
||||
WiFi.begin("airgradient", "cleanair");
|
||||
WiFi.persistent(false);
|
||||
oledDisplay.setText("Configure WiFi", "connect to", "\'airgradient\'");
|
||||
delay(2500);
|
||||
oledDisplay.setText("Rebooting...", "", "");
|
||||
delay(2500);
|
||||
oledDisplay.setText("", "", "");
|
||||
ESP.restart();
|
||||
}
|
||||
|
||||
static void mqttHandle(void) {
|
||||
if(mqttClient.isConnected() == false) {
|
||||
mqttClient.connect(String("airgradient-") + ag.deviceId());
|
||||
}
|
||||
|
||||
if (mqttClient.isConnected()) {
|
||||
String payload = measurements.toString(true, fwMode, wifiConnector.RSSI(), ag, configuration);
|
||||
String topic = "airgradient/readings/" + ag.deviceId();
|
||||
if (mqttClient.publish(topic.c_str(), payload.c_str(), payload.length())) {
|
||||
Serial.println("MQTT sync success");
|
||||
} else {
|
||||
Serial.println("MQTT sync failure");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void sendDataToAg() {
|
||||
/** Change oledDisplay and led state */
|
||||
stateMachine.displayHandle(AgStateMachineWiFiOkServerConnecting);
|
||||
|
||||
delay(1500);
|
||||
if (apiClient.sendPing(wifiConnector.RSSI(), measurements.bootCount)) {
|
||||
stateMachine.displayHandle(AgStateMachineWiFiOkServerConnected);
|
||||
} else {
|
||||
stateMachine.displayHandle(AgStateMachineWiFiOkServerConnectFailed);
|
||||
}
|
||||
delay(DISPLAY_DELAY_SHOW_CONTENT_MS);
|
||||
}
|
||||
|
||||
void dispSensorNotFound(String ss) {
|
||||
ss = ss + " not found";
|
||||
oledDisplay.setText("Sensor init", "Error:", ss.c_str());
|
||||
delay(2000);
|
||||
}
|
||||
|
||||
static void boardInit(void) {
|
||||
/** Display init */
|
||||
oledDisplay.begin();
|
||||
|
||||
/** Show boot display */
|
||||
Serial.println("Firmware Version: " + ag.getVersion());
|
||||
|
||||
oledDisplay.setText("AirGradient ONE",
|
||||
"FW Version: ", ag.getVersion().c_str());
|
||||
delay(DISPLAY_DELAY_SHOW_CONTENT_MS);
|
||||
|
||||
ag.watchdog.begin();
|
||||
|
||||
/** Show message init sensor */
|
||||
oledDisplay.setText("Sensor", "initializing...", "");
|
||||
|
||||
/** Init sensor SGP41 */
|
||||
if (sgp41Init() == false) {
|
||||
dispSensorNotFound("SGP41");
|
||||
}
|
||||
|
||||
/** Init SHT */
|
||||
if (ag.sht.begin(Wire) == false) {
|
||||
Serial.println("SHTx sensor not found");
|
||||
configuration.hasSensorSHT = false;
|
||||
dispSensorNotFound("SHT");
|
||||
}
|
||||
|
||||
/** Init S8 CO2 sensor */
|
||||
if (ag.s8.begin(&Serial) == false) {
|
||||
Serial.println("CO2 S8 sensor not found");
|
||||
configuration.hasSensorS8 = false;
|
||||
dispSensorNotFound("S8");
|
||||
}
|
||||
|
||||
/** Init PMS5003 */
|
||||
configuration.hasSensorPMS1 = true;
|
||||
configuration.hasSensorPMS2 = false;
|
||||
if (ag.pms5003.begin(&Serial) == false) {
|
||||
Serial.println("PMS sensor not found");
|
||||
configuration.hasSensorPMS1 = false;
|
||||
|
||||
dispSensorNotFound("PMS");
|
||||
}
|
||||
|
||||
/** Set S8 CO2 abc days period */
|
||||
if (configuration.hasSensorS8) {
|
||||
if (ag.s8.setAbcPeriod(configuration.getCO2CalibrationAbcDays() * 24)) {
|
||||
Serial.println("Set S8 AbcDays successful");
|
||||
} else {
|
||||
Serial.println("Set S8 AbcDays failure");
|
||||
}
|
||||
}
|
||||
|
||||
localServer.setFwMode(FW_MODE_I_33PS);
|
||||
}
|
||||
|
||||
static void failedHandler(String msg) {
|
||||
while (true) {
|
||||
Serial.println(msg);
|
||||
delay(1000);
|
||||
}
|
||||
}
|
||||
|
||||
static void configurationUpdateSchedule(void) {
|
||||
if (apiClient.fetchServerConfiguration()) {
|
||||
configUpdateHandle();
|
||||
}
|
||||
}
|
||||
|
||||
static void configUpdateHandle() {
|
||||
if (configuration.isUpdated() == false) {
|
||||
return;
|
||||
}
|
||||
|
||||
stateMachine.executeCo2Calibration();
|
||||
|
||||
String mqttUri = configuration.getMqttBrokerUri();
|
||||
if (mqttClient.isCurrentUri(mqttUri) == false) {
|
||||
mqttClient.end();
|
||||
initMqtt();
|
||||
}
|
||||
|
||||
if (configuration.hasSensorSGP) {
|
||||
if (configuration.noxLearnOffsetChanged() ||
|
||||
configuration.tvocLearnOffsetChanged()) {
|
||||
ag.sgp41.end();
|
||||
|
||||
int oldTvocOffset = ag.sgp41.getTvocLearningOffset();
|
||||
int oldNoxOffset = ag.sgp41.getNoxLearningOffset();
|
||||
bool result = sgp41Init();
|
||||
const char *resultStr = "successful";
|
||||
if (!result) {
|
||||
resultStr = "failure";
|
||||
}
|
||||
if (oldTvocOffset != configuration.getTvocLearningOffset()) {
|
||||
Serial.printf("Setting tvocLearningOffset from %d to %d hours %s\r\n",
|
||||
oldTvocOffset, configuration.getTvocLearningOffset(),
|
||||
resultStr);
|
||||
}
|
||||
if (oldNoxOffset != configuration.getNoxLearningOffset()) {
|
||||
Serial.printf("Setting noxLearningOffset from %d to %d hours %s\r\n",
|
||||
oldNoxOffset, configuration.getNoxLearningOffset(),
|
||||
resultStr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (configuration.isDisplayBrightnessChanged()) {
|
||||
oledDisplay.setBrightness(configuration.getDisplayBrightness());
|
||||
}
|
||||
|
||||
appDispHandler();
|
||||
}
|
||||
|
||||
static void appDispHandler(void) {
|
||||
AgStateMachineState state = AgStateMachineNormal;
|
||||
|
||||
/** Only show display status on online mode. */
|
||||
if (configuration.isOfflineMode() == false) {
|
||||
if (wifiConnector.isConnected() == false) {
|
||||
state = AgStateMachineWiFiLost;
|
||||
} else if (apiClient.isFetchConfigureFailed()) {
|
||||
state = AgStateMachineSensorConfigFailed;
|
||||
if (apiClient.isNotAvailableOnDashboard()) {
|
||||
stateMachine.displaySetAddToDashBoard();
|
||||
} else {
|
||||
stateMachine.displayClearAddToDashBoard();
|
||||
}
|
||||
} else if (apiClient.isPostToServerFailed()) {
|
||||
state = AgStateMachineServerLost;
|
||||
}
|
||||
}
|
||||
stateMachine.displayHandle(state);
|
||||
}
|
||||
|
||||
static void oledDisplaySchedule(void) {
|
||||
|
||||
appDispHandler();
|
||||
}
|
||||
|
||||
static void updateTvoc(void) {
|
||||
if (!configuration.hasSensorSGP) {
|
||||
return;
|
||||
}
|
||||
|
||||
measurements.update(Measurements::TVOC, ag.sgp41.getTvocIndex());
|
||||
measurements.update(Measurements::TVOCRaw, ag.sgp41.getTvocRaw());
|
||||
measurements.update(Measurements::NOx, ag.sgp41.getNoxIndex());
|
||||
measurements.update(Measurements::NOxRaw, ag.sgp41.getNoxRaw());
|
||||
}
|
||||
|
||||
static void updatePm(void) {
|
||||
if (ag.pms5003.connected()) {
|
||||
measurements.update(Measurements::PM01, ag.pms5003.getPm01Ae());
|
||||
measurements.update(Measurements::PM25, ag.pms5003.getPm25Ae());
|
||||
measurements.update(Measurements::PM10, ag.pms5003.getPm10Ae());
|
||||
measurements.update(Measurements::PM03_PC, ag.pms5003.getPm03ParticleCount());
|
||||
} else {
|
||||
measurements.update(Measurements::PM01, utils::getInvalidPmValue());
|
||||
measurements.update(Measurements::PM25, utils::getInvalidPmValue());
|
||||
measurements.update(Measurements::PM10, utils::getInvalidPmValue());
|
||||
measurements.update(Measurements::PM03_PC, utils::getInvalidPmValue());
|
||||
}
|
||||
}
|
||||
|
||||
static void sendDataToServer(void) {
|
||||
/** Increment bootcount when send measurements data is scheduled */
|
||||
measurements.bootCount++;
|
||||
|
||||
/** Ignore send data to server if postToAirGradient disabled */
|
||||
if (configuration.isPostDataToAirGradient() == false ||
|
||||
configuration.isOfflineMode()) {
|
||||
return;
|
||||
}
|
||||
|
||||
String syncData = measurements.toString(false, fwMode, wifiConnector.RSSI(), ag, configuration);
|
||||
if (apiClient.postToServer(syncData)) {
|
||||
Serial.println();
|
||||
Serial.println(
|
||||
"Online mode and isPostToAirGradient = true: watchdog reset");
|
||||
Serial.println();
|
||||
}
|
||||
}
|
||||
|
||||
static void tempHumUpdate(void) {
|
||||
if (ag.sht.measure()) {
|
||||
float temp = ag.sht.getTemperature();
|
||||
float rhum = ag.sht.getRelativeHumidity();
|
||||
|
||||
measurements.update(Measurements::Temperature, temp);
|
||||
measurements.update(Measurements::Humidity, rhum);
|
||||
|
||||
// Update compensation temperature and humidity for SGP41
|
||||
if (configuration.hasSensorSGP) {
|
||||
ag.sgp41.setCompensationTemperatureHumidity(temp, rhum);
|
||||
}
|
||||
} else {
|
||||
measurements.update(Measurements::Temperature, utils::getInvalidTemperature());
|
||||
measurements.update(Measurements::Humidity, utils::getInvalidHumidity());
|
||||
Serial.println("SHT read failed");
|
||||
}
|
||||
}
|
||||
|
||||
/* 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;
|
||||
}
|
60
examples/DiyProIndoorV3_3/LocalServer.cpp
Normal file
60
examples/DiyProIndoorV3_3/LocalServer.cpp
Normal file
@ -0,0 +1,60 @@
|
||||
#include "LocalServer.h"
|
||||
|
||||
LocalServer::LocalServer(Stream &log, OpenMetrics &openMetrics,
|
||||
Measurements &measure, Configuration &config,
|
||||
WifiConnector &wifiConnector)
|
||||
: PrintLog(log, "LocalServer"), openMetrics(openMetrics), measure(measure),
|
||||
config(config), wifiConnector(wifiConnector), server(80) {}
|
||||
|
||||
LocalServer::~LocalServer() {}
|
||||
|
||||
bool LocalServer::begin(void) {
|
||||
server.on("/measures/current", HTTP_GET, [this]() { _GET_measure(); });
|
||||
server.on(openMetrics.getApi(), HTTP_GET, [this]() { _GET_metrics(); });
|
||||
server.on("/config", HTTP_GET, [this]() { _GET_config(); });
|
||||
server.on("/config", HTTP_PUT, [this]() { _PUT_config(); });
|
||||
server.begin();
|
||||
logInfo("Init: " + getHostname() + ".local");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void LocalServer::setAirGraident(AirGradient *ag) { this->ag = ag; }
|
||||
|
||||
String LocalServer::getHostname(void) {
|
||||
return "airgradient_" + ag->deviceId();
|
||||
}
|
||||
|
||||
void LocalServer::_handle(void) { server.handleClient(); }
|
||||
|
||||
void LocalServer::_GET_config(void) {
|
||||
if(ag->isOne()) {
|
||||
server.send(200, "application/json", config.toString());
|
||||
} else {
|
||||
server.send(200, "application/json", config.toString(fwMode));
|
||||
}
|
||||
}
|
||||
|
||||
void LocalServer::_PUT_config(void) {
|
||||
String data = server.arg(0);
|
||||
String response = "";
|
||||
int statusCode = 400; // Status code for data invalid
|
||||
if (config.parse(data, true)) {
|
||||
statusCode = 200;
|
||||
response = "Success";
|
||||
} else {
|
||||
response = config.getFailedMesage();
|
||||
}
|
||||
server.send(statusCode, "text/plain", response);
|
||||
}
|
||||
|
||||
void LocalServer::_GET_metrics(void) {
|
||||
server.send(200, openMetrics.getApiContentType(), openMetrics.getPayload());
|
||||
}
|
||||
|
||||
void LocalServer::_GET_measure(void) {
|
||||
String toSend = measure.toString(true, fwMode, wifiConnector.RSSI(), *ag, config);
|
||||
server.send(200, "application/json", toSend);
|
||||
}
|
||||
|
||||
void LocalServer::setFwMode(AgFirmwareMode fwMode) { this->fwMode = fwMode; }
|
38
examples/DiyProIndoorV3_3/LocalServer.h
Normal file
38
examples/DiyProIndoorV3_3/LocalServer.h
Normal file
@ -0,0 +1,38 @@
|
||||
#ifndef _LOCAL_SERVER_H_
|
||||
#define _LOCAL_SERVER_H_
|
||||
|
||||
#include "AgConfigure.h"
|
||||
#include "AgValue.h"
|
||||
#include "AirGradient.h"
|
||||
#include "OpenMetrics.h"
|
||||
#include "AgWiFiConnector.h"
|
||||
#include <Arduino.h>
|
||||
#include <ESP8266WebServer.h>
|
||||
|
||||
class LocalServer : public PrintLog {
|
||||
private:
|
||||
AirGradient *ag;
|
||||
OpenMetrics &openMetrics;
|
||||
Measurements &measure;
|
||||
Configuration &config;
|
||||
WifiConnector &wifiConnector;
|
||||
ESP8266WebServer server;
|
||||
AgFirmwareMode fwMode;
|
||||
|
||||
public:
|
||||
LocalServer(Stream &log, OpenMetrics &openMetrics, Measurements &measure,
|
||||
Configuration &config, WifiConnector& wifiConnector);
|
||||
~LocalServer();
|
||||
|
||||
bool begin(void);
|
||||
void setAirGraident(AirGradient *ag);
|
||||
String getHostname(void);
|
||||
void setFwMode(AgFirmwareMode fwMode);
|
||||
void _handle(void);
|
||||
void _GET_config(void);
|
||||
void _PUT_config(void);
|
||||
void _GET_metrics(void);
|
||||
void _GET_measure(void);
|
||||
};
|
||||
|
||||
#endif /** _LOCAL_SERVER_H_ */
|
204
examples/DiyProIndoorV3_3/OpenMetrics.cpp
Normal file
204
examples/DiyProIndoorV3_3/OpenMetrics.cpp
Normal file
@ -0,0 +1,204 @@
|
||||
#include "OpenMetrics.h"
|
||||
|
||||
OpenMetrics::OpenMetrics(Measurements &measure, Configuration &config,
|
||||
WifiConnector &wifiConnector, AgApiClient &apiClient)
|
||||
: measure(measure), config(config), wifiConnector(wifiConnector),
|
||||
apiClient(apiClient) {}
|
||||
|
||||
OpenMetrics::~OpenMetrics() {}
|
||||
|
||||
void OpenMetrics::setAirGradient(AirGradient *ag) { this->ag = ag; }
|
||||
|
||||
const char *OpenMetrics::getApiContentType(void) {
|
||||
return "application/openmetrics-text; version=1.0.0; charset=utf-8";
|
||||
}
|
||||
|
||||
const char *OpenMetrics::getApi(void) { return "/metrics"; }
|
||||
|
||||
String OpenMetrics::getPayload(void) {
|
||||
String response;
|
||||
String current_metric_name;
|
||||
const auto add_metric = [&](const String &name, const String &help,
|
||||
const String &type, const String &unit = "") {
|
||||
current_metric_name = "airgradient_" + name;
|
||||
if (!unit.isEmpty())
|
||||
current_metric_name += "_" + unit;
|
||||
response += "# HELP " + current_metric_name + " " + help + "\n";
|
||||
response += "# TYPE " + current_metric_name + " " + type + "\n";
|
||||
if (!unit.isEmpty())
|
||||
response += "# UNIT " + current_metric_name + " " + unit + "\n";
|
||||
};
|
||||
const auto add_metric_point = [&](const String &labels, const String &value) {
|
||||
response += current_metric_name + "{" + labels + "} " + value + "\n";
|
||||
};
|
||||
|
||||
add_metric("info", "AirGradient device information", "info");
|
||||
add_metric_point("airgradient_serial_number=\"" + ag->deviceId() +
|
||||
"\",airgradient_device_type=\"" + ag->getBoardName() +
|
||||
"\",airgradient_library_version=\"" + ag->getVersion() +
|
||||
"\"",
|
||||
"1");
|
||||
|
||||
add_metric("config_ok",
|
||||
"1 if the AirGradient device was able to successfully fetch its "
|
||||
"configuration from the server",
|
||||
"gauge");
|
||||
add_metric_point("", apiClient.isFetchConfigureFailed() ? "0" : "1");
|
||||
|
||||
add_metric(
|
||||
"post_ok",
|
||||
"1 if the AirGradient device was able to successfully send to the server",
|
||||
"gauge");
|
||||
add_metric_point("", apiClient.isPostToServerFailed() ? "0" : "1");
|
||||
|
||||
add_metric(
|
||||
"wifi_rssi",
|
||||
"WiFi signal strength from the AirGradient device perspective, in dBm",
|
||||
"gauge", "dbm");
|
||||
add_metric_point("", String(wifiConnector.RSSI()));
|
||||
|
||||
// Initialize default invalid value for each measurements
|
||||
float _temp = utils::getInvalidTemperature();
|
||||
float _hum = utils::getInvalidHumidity();
|
||||
int pm01 = utils::getInvalidPmValue();
|
||||
int pm25 = utils::getInvalidPmValue();
|
||||
int pm10 = utils::getInvalidPmValue();
|
||||
int pm03PCount = utils::getInvalidPmValue();
|
||||
int co2 = utils::getInvalidCO2();
|
||||
int atmpCompensated = utils::getInvalidTemperature();
|
||||
int ahumCompensated = utils::getInvalidHumidity();
|
||||
int tvoc = utils::getInvalidVOC();
|
||||
int tvocRaw = utils::getInvalidVOC();
|
||||
int nox = utils::getInvalidNOx();
|
||||
int noxRaw = utils::getInvalidNOx();
|
||||
|
||||
if (config.hasSensorSHT) {
|
||||
_temp = measure.getFloat(Measurements::Temperature);
|
||||
_hum = measure.getFloat(Measurements::Humidity);
|
||||
atmpCompensated = _temp;
|
||||
ahumCompensated = _hum;
|
||||
}
|
||||
|
||||
if (config.hasSensorPMS1) {
|
||||
pm01 = measure.get(Measurements::PM01);
|
||||
pm25 = measure.get(Measurements::PM25);
|
||||
pm10 = measure.get(Measurements::PM10);
|
||||
pm03PCount = measure.get(Measurements::PM03_PC);
|
||||
}
|
||||
|
||||
if (config.hasSensorSGP) {
|
||||
tvoc = measure.get(Measurements::TVOC);
|
||||
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 (utils::isValidPm(pm01)) {
|
||||
add_metric("pm1",
|
||||
"PM1.0 concentration as measured by the AirGradient PMS "
|
||||
"sensor, in micrograms per cubic meter",
|
||||
"gauge", "ugm3");
|
||||
add_metric_point("", String(pm01));
|
||||
}
|
||||
if (utils::isValidPm(pm25)) {
|
||||
add_metric("pm2d5",
|
||||
"PM2.5 concentration as measured by the AirGradient PMS "
|
||||
"sensor, in micrograms per cubic meter",
|
||||
"gauge", "ugm3");
|
||||
add_metric_point("", String(pm25));
|
||||
}
|
||||
if (utils::isValidPm(pm10)) {
|
||||
add_metric("pm10",
|
||||
"PM10 concentration as measured by the AirGradient PMS "
|
||||
"sensor, in micrograms per cubic meter",
|
||||
"gauge", "ugm3");
|
||||
add_metric_point("", String(pm10));
|
||||
}
|
||||
if (utils::isValidPm03Count(pm03PCount)) {
|
||||
add_metric("pm0d3",
|
||||
"PM0.3 concentration as measured by the AirGradient PMS "
|
||||
"sensor, in number of particules per 100 milliliters",
|
||||
"gauge", "p100ml");
|
||||
add_metric_point("", String(pm03PCount));
|
||||
}
|
||||
}
|
||||
|
||||
if (config.hasSensorSGP) {
|
||||
if (utils::isValidVOC(tvoc)) {
|
||||
add_metric("tvoc_index",
|
||||
"The processed Total Volatile Organic Compounds (TVOC) index "
|
||||
"as measured by the AirGradient SGP sensor",
|
||||
"gauge");
|
||||
add_metric_point("", String(tvoc));
|
||||
}
|
||||
|
||||
if (utils::isValidVOC(tvocRaw)) {
|
||||
add_metric("tvoc_raw",
|
||||
"The raw input value to the Total Volatile Organic Compounds "
|
||||
"(TVOC) index as measured by the AirGradient SGP sensor",
|
||||
"gauge");
|
||||
add_metric_point("", String(tvocRaw));
|
||||
}
|
||||
if (utils::isValidNOx(nox)) {
|
||||
add_metric("nox_index",
|
||||
"The processed Nitrous Oxide (NOx) index as measured by the "
|
||||
"AirGradient SGP sensor",
|
||||
"gauge");
|
||||
add_metric_point("", String(nox));
|
||||
}
|
||||
if (utils::isValidNOx(noxRaw)) {
|
||||
add_metric("nox_raw",
|
||||
"The raw input value to the Nitrous Oxide (NOx) index as "
|
||||
"measured by the AirGradient SGP sensor",
|
||||
"gauge");
|
||||
add_metric_point("", String(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)) {
|
||||
add_metric(
|
||||
"temperature",
|
||||
"The ambient temperature as measured by the AirGradient SHT / PMS "
|
||||
"sensor, in degrees Celsius",
|
||||
"gauge", "celsius");
|
||||
add_metric_point("", String(_temp));
|
||||
}
|
||||
if (utils::isValidTemperature(atmpCompensated)) {
|
||||
add_metric("temperature_compensated",
|
||||
"The compensated ambient temperature as measured by the "
|
||||
"AirGradient SHT / PMS "
|
||||
"sensor, in degrees Celsius",
|
||||
"gauge", "celsius");
|
||||
add_metric_point("", String(atmpCompensated));
|
||||
}
|
||||
if (utils::isValidHumidity(_hum)) {
|
||||
add_metric(
|
||||
"humidity",
|
||||
"The relative humidity as measured by the AirGradient SHT sensor",
|
||||
"gauge", "percent");
|
||||
add_metric_point("", String(_hum));
|
||||
}
|
||||
if (utils::isValidHumidity(ahumCompensated)) {
|
||||
add_metric("humidity_compensated",
|
||||
"The compensated relative humidity as measured by the "
|
||||
"AirGradient SHT / PMS sensor",
|
||||
"gauge", "percent");
|
||||
add_metric_point("", String(ahumCompensated));
|
||||
}
|
||||
|
||||
response += "# EOF\n";
|
||||
return response;
|
||||
}
|
28
examples/DiyProIndoorV3_3/OpenMetrics.h
Normal file
28
examples/DiyProIndoorV3_3/OpenMetrics.h
Normal file
@ -0,0 +1,28 @@
|
||||
#ifndef _OPEN_METRICS_H_
|
||||
#define _OPEN_METRICS_H_
|
||||
|
||||
#include "AgConfigure.h"
|
||||
#include "AgValue.h"
|
||||
#include "AgWiFiConnector.h"
|
||||
#include "AirGradient.h"
|
||||
#include "AgApiClient.h"
|
||||
|
||||
class OpenMetrics {
|
||||
private:
|
||||
AirGradient *ag;
|
||||
Measurements &measure;
|
||||
Configuration &config;
|
||||
WifiConnector &wifiConnector;
|
||||
AgApiClient &apiClient;
|
||||
|
||||
public:
|
||||
OpenMetrics(Measurements &measure, Configuration &conig,
|
||||
WifiConnector &wifiConnector, AgApiClient& apiClient);
|
||||
~OpenMetrics();
|
||||
void setAirGradient(AirGradient *ag);
|
||||
const char *getApiContentType(void);
|
||||
const char* getApi(void);
|
||||
String getPayload(void);
|
||||
};
|
||||
|
||||
#endif /** _OPEN_METRICS_H_ */
|
682
examples/DiyProIndoorV4_2/DiyProIndoorV4_2.ino
Normal file
682
examples/DiyProIndoorV4_2/DiyProIndoorV4_2.ino
Normal file
@ -0,0 +1,682 @@
|
||||
/*
|
||||
This is the code for the AirGradient DIY PRO 4.2 Air Quality Monitor with an D1
|
||||
ESP8266 Microcontroller.
|
||||
|
||||
It is an air quality monitor for PM2.5, CO2, Temperature and Humidity with a
|
||||
small display and can send data over Wifi.
|
||||
|
||||
Open source air quality monitors and kits are available:
|
||||
Indoor Monitor: https://www.airgradient.com/indoor/
|
||||
Outdoor Monitor: https://www.airgradient.com/outdoor/
|
||||
|
||||
Build Instructions:
|
||||
https://www.airgradient.com/documentation/diy-v4/
|
||||
|
||||
Please make sure you have esp8266 board manager installed. Tested with
|
||||
version 3.1.2.
|
||||
|
||||
Set board to "LOLIN(WEMOS) D1 R2 & mini"
|
||||
|
||||
Configuration parameters, e.g. Celsius / Fahrenheit or PM unit (US AQI vs ug/m3)
|
||||
can be set through the AirGradient dashboard.
|
||||
|
||||
If you have any questions please visit our forum at
|
||||
https://forum.airgradient.com/
|
||||
|
||||
CC BY-SA 4.0 Attribution-ShareAlike 4.0 International License
|
||||
|
||||
*/
|
||||
|
||||
#include "AgApiClient.h"
|
||||
#include "AgConfigure.h"
|
||||
#include "AgSchedule.h"
|
||||
#include "AgWiFiConnector.h"
|
||||
#include "LocalServer.h"
|
||||
#include "OpenMetrics.h"
|
||||
#include "MqttClient.h"
|
||||
#include <AirGradient.h>
|
||||
#include <ESP8266HTTPClient.h>
|
||||
#include <ESP8266WiFi.h>
|
||||
#include <ESP8266mDNS.h>
|
||||
#include <WiFiClient.h>
|
||||
|
||||
#define LED_BAR_ANIMATION_PERIOD 100 /** ms */
|
||||
#define DISP_UPDATE_INTERVAL 2500 /** ms */
|
||||
#define SERVER_CONFIG_SYNC_INTERVAL 60000 /** ms */
|
||||
#define SERVER_SYNC_INTERVAL 60000 /** ms */
|
||||
#define MQTT_SYNC_INTERVAL 60000 /** ms */
|
||||
#define SENSOR_CO2_CALIB_COUNTDOWN_MAX 5 /** sec */
|
||||
#define SENSOR_TVOC_UPDATE_INTERVAL 1000 /** ms */
|
||||
#define SENSOR_CO2_UPDATE_INTERVAL 4000 /** ms */
|
||||
#define SENSOR_PM_UPDATE_INTERVAL 2000 /** ms */
|
||||
#define SENSOR_TEMP_HUM_UPDATE_INTERVAL 6000 /** ms */
|
||||
#define DISPLAY_DELAY_SHOW_CONTENT_MS 2000 /** ms */
|
||||
|
||||
static AirGradient ag(DIY_PRO_INDOOR_V4_2);
|
||||
static Configuration configuration(Serial);
|
||||
static AgApiClient apiClient(Serial, configuration);
|
||||
static Measurements measurements;
|
||||
static OledDisplay oledDisplay(configuration, measurements, Serial);
|
||||
static StateMachine stateMachine(oledDisplay, Serial, measurements,
|
||||
configuration);
|
||||
static WifiConnector wifiConnector(oledDisplay, Serial, stateMachine,
|
||||
configuration);
|
||||
static OpenMetrics openMetrics(measurements, configuration, wifiConnector,
|
||||
apiClient);
|
||||
static LocalServer localServer(Serial, openMetrics, measurements, configuration,
|
||||
wifiConnector);
|
||||
static MqttClient mqttClient(Serial);
|
||||
|
||||
static uint32_t factoryBtnPressTime = 0;
|
||||
static AgFirmwareMode fwMode = FW_MODE_I_42PS;
|
||||
|
||||
static String fwNewVersion;
|
||||
|
||||
static void boardInit(void);
|
||||
static void failedHandler(String msg);
|
||||
static void configurationUpdateSchedule(void);
|
||||
static void appDispHandler(void);
|
||||
static void oledDisplaySchedule(void);
|
||||
static void updateTvoc(void);
|
||||
static void updatePm(void);
|
||||
static void sendDataToServer(void);
|
||||
static void tempHumUpdate(void);
|
||||
static void co2Update(void);
|
||||
static void mdnsInit(void);
|
||||
static void initMqtt(void);
|
||||
static void factoryConfigReset(void);
|
||||
static void wdgFeedUpdate(void);
|
||||
static bool sgp41Init(void);
|
||||
static void wifiFactoryConfigure(void);
|
||||
static void mqttHandle(void);
|
||||
static int calculateMaxPeriod(int updateInterval);
|
||||
static void setMeasurementMaxPeriod();
|
||||
|
||||
AgSchedule dispLedSchedule(DISP_UPDATE_INTERVAL, oledDisplaySchedule);
|
||||
AgSchedule configSchedule(SERVER_CONFIG_SYNC_INTERVAL,
|
||||
configurationUpdateSchedule);
|
||||
AgSchedule agApiPostSchedule(SERVER_SYNC_INTERVAL, sendDataToServer);
|
||||
AgSchedule co2Schedule(SENSOR_CO2_UPDATE_INTERVAL, co2Update);
|
||||
AgSchedule pmsSchedule(SENSOR_PM_UPDATE_INTERVAL, updatePm);
|
||||
AgSchedule tempHumSchedule(SENSOR_TEMP_HUM_UPDATE_INTERVAL, tempHumUpdate);
|
||||
AgSchedule tvocSchedule(SENSOR_TVOC_UPDATE_INTERVAL, updateTvoc);
|
||||
AgSchedule watchdogFeedSchedule(60000, wdgFeedUpdate);
|
||||
AgSchedule mqttSchedule(MQTT_SYNC_INTERVAL, mqttHandle);
|
||||
|
||||
void setup() {
|
||||
/** Serial for print debug message */
|
||||
Serial.begin(115200);
|
||||
delay(100); /** For bester show log */
|
||||
|
||||
/** Print device ID into log */
|
||||
Serial.println("Serial nr: " + ag.deviceId());
|
||||
|
||||
/** Initialize local configure */
|
||||
configuration.begin();
|
||||
|
||||
/** Init I2C */
|
||||
Wire.begin(ag.getI2cSdaPin(), ag.getI2cSclPin());
|
||||
delay(1000);
|
||||
|
||||
configuration.setAirGradient(&ag);
|
||||
oledDisplay.setAirGradient(&ag);
|
||||
stateMachine.setAirGradient(&ag);
|
||||
wifiConnector.setAirGradient(&ag);
|
||||
apiClient.setAirGradient(&ag);
|
||||
openMetrics.setAirGradient(&ag);
|
||||
localServer.setAirGraident(&ag);
|
||||
|
||||
/** Example set custom API root URL */
|
||||
// apiClient.setApiRoot("https://example.custom.api");
|
||||
|
||||
/** Init sensor */
|
||||
boardInit();
|
||||
setMeasurementMaxPeriod();
|
||||
|
||||
// Uncomment below line to print every measurements reading update
|
||||
// measurements.setDebug(true);
|
||||
|
||||
/** Connecting wifi */
|
||||
bool connectToWifi = false;
|
||||
|
||||
/** Show message confirm offline mode, should me perform if LED bar button
|
||||
* test pressed */
|
||||
|
||||
oledDisplay.setText(
|
||||
"Press now for",
|
||||
configuration.isOfflineMode() ? "online mode" : "offline mode", "");
|
||||
uint32_t startTime = millis();
|
||||
while (true) {
|
||||
if (ag.button.getState() == ag.button.BUTTON_PRESSED) {
|
||||
configuration.setOfflineMode(!configuration.isOfflineMode());
|
||||
|
||||
oledDisplay.setText(
|
||||
"Offline Mode",
|
||||
configuration.isOfflineMode() ? " = True" : " = False", "");
|
||||
delay(1000);
|
||||
break;
|
||||
}
|
||||
uint32_t periodMs = (uint32_t)(millis() - startTime);
|
||||
if (periodMs >= 3000) {
|
||||
Serial.println("Set for offline mode timeout");
|
||||
break;
|
||||
}
|
||||
|
||||
delay(1);
|
||||
}
|
||||
connectToWifi = !configuration.isOfflineMode();
|
||||
|
||||
if (connectToWifi) {
|
||||
apiClient.begin();
|
||||
|
||||
if (wifiConnector.connect()) {
|
||||
if (wifiConnector.isConnected()) {
|
||||
mdnsInit();
|
||||
localServer.begin();
|
||||
initMqtt();
|
||||
sendDataToAg();
|
||||
|
||||
apiClient.fetchServerConfiguration();
|
||||
configSchedule.update();
|
||||
if (apiClient.isFetchConfigureFailed()) {
|
||||
if (apiClient.isNotAvailableOnDashboard()) {
|
||||
stateMachine.displaySetAddToDashBoard();
|
||||
stateMachine.displayHandle(
|
||||
AgStateMachineWiFiOkServerOkSensorConfigFailed);
|
||||
} else {
|
||||
stateMachine.displayClearAddToDashBoard();
|
||||
}
|
||||
delay(DISPLAY_DELAY_SHOW_CONTENT_MS);
|
||||
}
|
||||
} else {
|
||||
if (wifiConnector.isConfigurePorttalTimeout()) {
|
||||
oledDisplay.showRebooting();
|
||||
delay(2500);
|
||||
oledDisplay.setText("", "", "");
|
||||
ESP.restart();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
/** Set offline mode without saving, cause wifi is not configured */
|
||||
if (wifiConnector.hasConfigurated() == false) {
|
||||
Serial.println("Set offline mode cause wifi is not configurated");
|
||||
configuration.setOfflineModeWithoutSave(true);
|
||||
}
|
||||
|
||||
/** Show display Warning up */
|
||||
oledDisplay.setText("Warming Up", "Serial Number:", ag.deviceId().c_str());
|
||||
delay(DISPLAY_DELAY_SHOW_CONTENT_MS);
|
||||
|
||||
Serial.println("Display brightness: " +
|
||||
String(configuration.getDisplayBrightness()));
|
||||
oledDisplay.setBrightness(configuration.getDisplayBrightness());
|
||||
|
||||
appDispHandler();
|
||||
}
|
||||
|
||||
void loop() {
|
||||
/** Handle schedule */
|
||||
dispLedSchedule.run();
|
||||
configSchedule.run();
|
||||
agApiPostSchedule.run();
|
||||
|
||||
if (configuration.hasSensorS8) {
|
||||
co2Schedule.run();
|
||||
}
|
||||
if (configuration.hasSensorPMS1) {
|
||||
pmsSchedule.run();
|
||||
ag.pms5003.handle();
|
||||
}
|
||||
if (configuration.hasSensorSHT) {
|
||||
tempHumSchedule.run();
|
||||
}
|
||||
if (configuration.hasSensorSGP) {
|
||||
tvocSchedule.run();
|
||||
}
|
||||
|
||||
watchdogFeedSchedule.run();
|
||||
|
||||
/** Check for handle WiFi reconnect */
|
||||
wifiConnector.handle();
|
||||
|
||||
/** factory reset handle */
|
||||
factoryConfigReset();
|
||||
|
||||
/** check that local configura changed then do some action */
|
||||
configUpdateHandle();
|
||||
|
||||
localServer._handle();
|
||||
|
||||
if (configuration.hasSensorSGP) {
|
||||
ag.sgp41.handle();
|
||||
}
|
||||
|
||||
MDNS.update();
|
||||
|
||||
mqttSchedule.run();
|
||||
mqttClient.handle();
|
||||
}
|
||||
|
||||
static void co2Update(void) {
|
||||
if (!configuration.hasSensorS8) {
|
||||
// Device don't have S8 sensor
|
||||
return;
|
||||
}
|
||||
|
||||
int value = ag.s8.getCo2();
|
||||
if (utils::isValidCO2(value)) {
|
||||
measurements.update(Measurements::CO2, value);
|
||||
} else {
|
||||
measurements.update(Measurements::CO2, utils::getInvalidCO2());
|
||||
}
|
||||
}
|
||||
|
||||
static void mdnsInit(void) {
|
||||
Serial.println("mDNS init");
|
||||
if (!MDNS.begin(localServer.getHostname().c_str())) {
|
||||
Serial.println("Init mDNS failed");
|
||||
return;
|
||||
}
|
||||
|
||||
MDNS.addService("_airgradient", "_tcp", 80);
|
||||
MDNS.addServiceTxt("_airgradient", "_tcp", "model",
|
||||
AgFirmwareModeName(fwMode));
|
||||
MDNS.addServiceTxt("_airgradient", "_tcp", "serialno", ag.deviceId());
|
||||
MDNS.addServiceTxt("_airgradient", "_tcp", "fw_ver", ag.getVersion());
|
||||
MDNS.addServiceTxt("_airgradient", "_tcp", "vendor", "AirGradient");
|
||||
|
||||
MDNS.announce();
|
||||
}
|
||||
|
||||
static void initMqtt(void) {
|
||||
String mqttUri = configuration.getMqttBrokerUri();
|
||||
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 {
|
||||
Serial.println("Connection to MQTT broker failed");
|
||||
}
|
||||
}
|
||||
|
||||
static void factoryConfigReset(void) {
|
||||
if (ag.button.getState() == ag.button.BUTTON_PRESSED) {
|
||||
if (factoryBtnPressTime == 0) {
|
||||
factoryBtnPressTime = millis();
|
||||
} else {
|
||||
uint32_t ms = (uint32_t)(millis() - factoryBtnPressTime);
|
||||
if (ms >= 2000) {
|
||||
// Show display message: For factory keep for x seconds
|
||||
if (ag.isOne() || ag.isPro4_2()) {
|
||||
oledDisplay.setText("Factory reset", "keep pressed", "for 8 sec");
|
||||
} else {
|
||||
Serial.println("Factory reset, keep pressed for 8 sec");
|
||||
}
|
||||
|
||||
int count = 7;
|
||||
while (ag.button.getState() == ag.button.BUTTON_PRESSED) {
|
||||
delay(1000);
|
||||
String str = "for " + String(count) + " sec";
|
||||
oledDisplay.setText("Factory reset", "keep pressed", str.c_str());
|
||||
|
||||
count--;
|
||||
if (count == 0) {
|
||||
/** Stop MQTT task first */
|
||||
// if (mqttTask) {
|
||||
// vTaskDelete(mqttTask);
|
||||
// mqttTask = NULL;
|
||||
// }
|
||||
|
||||
/** Reset WIFI */
|
||||
wifiConnector.reset();
|
||||
|
||||
/** Reset local config */
|
||||
configuration.reset();
|
||||
|
||||
oledDisplay.setText("Factory reset", "successful", "");
|
||||
|
||||
delay(3000);
|
||||
oledDisplay.setText("", "", "");
|
||||
ESP.restart();
|
||||
}
|
||||
}
|
||||
|
||||
/** Show current content cause reset ignore */
|
||||
factoryBtnPressTime = 0;
|
||||
appDispHandler();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (factoryBtnPressTime != 0) {
|
||||
appDispHandler();
|
||||
}
|
||||
factoryBtnPressTime = 0;
|
||||
}
|
||||
}
|
||||
|
||||
static void wdgFeedUpdate(void) {
|
||||
ag.watchdog.reset();
|
||||
Serial.println("External watchdog feed!");
|
||||
}
|
||||
|
||||
static bool sgp41Init(void) {
|
||||
ag.sgp41.setNoxLearningOffset(configuration.getNoxLearningOffset());
|
||||
ag.sgp41.setTvocLearningOffset(configuration.getTvocLearningOffset());
|
||||
if (ag.sgp41.begin(Wire)) {
|
||||
Serial.println("Init SGP41 success");
|
||||
configuration.hasSensorSGP = true;
|
||||
return true;
|
||||
} else {
|
||||
Serial.println("Init SGP41 failuire");
|
||||
configuration.hasSensorSGP = false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static void wifiFactoryConfigure(void) {
|
||||
WiFi.persistent(true);
|
||||
WiFi.begin("airgradient", "cleanair");
|
||||
WiFi.persistent(false);
|
||||
oledDisplay.setText("Configure WiFi", "connect to", "\'airgradient\'");
|
||||
delay(2500);
|
||||
oledDisplay.setText("Rebooting...", "", "");
|
||||
delay(2500);
|
||||
oledDisplay.setText("", "", "");
|
||||
ESP.restart();
|
||||
}
|
||||
|
||||
static void mqttHandle(void) {
|
||||
if(mqttClient.isConnected() == false) {
|
||||
mqttClient.connect(String("airgradient-") + ag.deviceId());
|
||||
}
|
||||
|
||||
if (mqttClient.isConnected()) {
|
||||
String payload = measurements.toString(true, fwMode, wifiConnector.RSSI(), ag, configuration);
|
||||
String topic = "airgradient/readings/" + ag.deviceId();
|
||||
if (mqttClient.publish(topic.c_str(), payload.c_str(), payload.length())) {
|
||||
Serial.println("MQTT sync success");
|
||||
} else {
|
||||
Serial.println("MQTT sync failure");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void sendDataToAg() {
|
||||
/** Change oledDisplay and led state */
|
||||
stateMachine.displayHandle(AgStateMachineWiFiOkServerConnecting);
|
||||
|
||||
delay(1500);
|
||||
if (apiClient.sendPing(wifiConnector.RSSI(), measurements.bootCount)) {
|
||||
stateMachine.displayHandle(AgStateMachineWiFiOkServerConnected);
|
||||
} else {
|
||||
stateMachine.displayHandle(AgStateMachineWiFiOkServerConnectFailed);
|
||||
}
|
||||
delay(DISPLAY_DELAY_SHOW_CONTENT_MS);
|
||||
}
|
||||
|
||||
void dispSensorNotFound(String ss) {
|
||||
ss = ss + " not found";
|
||||
oledDisplay.setText("Sensor init", "Error:", ss.c_str());
|
||||
delay(2000);
|
||||
}
|
||||
|
||||
static void boardInit(void) {
|
||||
/** Display init */
|
||||
oledDisplay.begin();
|
||||
|
||||
/** Show boot display */
|
||||
Serial.println("Firmware Version: " + ag.getVersion());
|
||||
|
||||
oledDisplay.setText("AirGradient ONE",
|
||||
"FW Version: ", ag.getVersion().c_str());
|
||||
delay(DISPLAY_DELAY_SHOW_CONTENT_MS);
|
||||
|
||||
ag.button.begin();
|
||||
ag.watchdog.begin();
|
||||
|
||||
/** Run LED test on start up if button pressed */
|
||||
oledDisplay.setText("Press now for", "factory WiFi", "configure");
|
||||
|
||||
uint32_t stime = millis();
|
||||
while (true) {
|
||||
if (ag.button.getState() == ag.button.BUTTON_PRESSED) {
|
||||
wifiFactoryConfigure();
|
||||
}
|
||||
delay(1);
|
||||
uint32_t ms = (uint32_t)(millis() - stime);
|
||||
if (ms >= 3000) {
|
||||
break;
|
||||
}
|
||||
delay(1);
|
||||
}
|
||||
|
||||
/** Show message init sensor */
|
||||
oledDisplay.setText("Sensor", "initializing...", "");
|
||||
|
||||
/** Init sensor SGP41 */
|
||||
if (sgp41Init() == false) {
|
||||
dispSensorNotFound("SGP41");
|
||||
}
|
||||
|
||||
/** Init SHT */
|
||||
if (ag.sht.begin(Wire) == false) {
|
||||
Serial.println("SHTx sensor not found");
|
||||
configuration.hasSensorSHT = false;
|
||||
dispSensorNotFound("SHT");
|
||||
}
|
||||
|
||||
/** Init S8 CO2 sensor */
|
||||
if (ag.s8.begin(&Serial) == false) {
|
||||
Serial.println("CO2 S8 sensor not found");
|
||||
configuration.hasSensorS8 = false;
|
||||
dispSensorNotFound("S8");
|
||||
}
|
||||
|
||||
/** Init PMS5003 */
|
||||
configuration.hasSensorPMS1 = true;
|
||||
configuration.hasSensorPMS2 = false;
|
||||
if (ag.pms5003.begin(&Serial) == false) {
|
||||
Serial.println("PMS sensor not found");
|
||||
configuration.hasSensorPMS1 = false;
|
||||
|
||||
dispSensorNotFound("PMS");
|
||||
}
|
||||
|
||||
/** Set S8 CO2 abc days period */
|
||||
if (configuration.hasSensorS8) {
|
||||
if (ag.s8.setAbcPeriod(configuration.getCO2CalibrationAbcDays() * 24)) {
|
||||
Serial.println("Set S8 AbcDays successful");
|
||||
} else {
|
||||
Serial.println("Set S8 AbcDays failure");
|
||||
}
|
||||
}
|
||||
|
||||
localServer.setFwMode(FW_MODE_I_42PS);
|
||||
}
|
||||
|
||||
static void failedHandler(String msg) {
|
||||
while (true) {
|
||||
Serial.println(msg);
|
||||
delay(1000);
|
||||
}
|
||||
}
|
||||
|
||||
static void configurationUpdateSchedule(void) {
|
||||
if (apiClient.fetchServerConfiguration()) {
|
||||
configUpdateHandle();
|
||||
}
|
||||
}
|
||||
|
||||
static void configUpdateHandle() {
|
||||
if (configuration.isUpdated() == false) {
|
||||
return;
|
||||
}
|
||||
|
||||
stateMachine.executeCo2Calibration();
|
||||
|
||||
String mqttUri = configuration.getMqttBrokerUri();
|
||||
if (mqttClient.isCurrentUri(mqttUri) == false) {
|
||||
mqttClient.end();
|
||||
initMqtt();
|
||||
}
|
||||
|
||||
if (configuration.hasSensorSGP) {
|
||||
if (configuration.noxLearnOffsetChanged() ||
|
||||
configuration.tvocLearnOffsetChanged()) {
|
||||
ag.sgp41.end();
|
||||
|
||||
int oldTvocOffset = ag.sgp41.getTvocLearningOffset();
|
||||
int oldNoxOffset = ag.sgp41.getNoxLearningOffset();
|
||||
bool result = sgp41Init();
|
||||
const char *resultStr = "successful";
|
||||
if (!result) {
|
||||
resultStr = "failure";
|
||||
}
|
||||
if (oldTvocOffset != configuration.getTvocLearningOffset()) {
|
||||
Serial.printf("Setting tvocLearningOffset from %d to %d hours %s\r\n",
|
||||
oldTvocOffset, configuration.getTvocLearningOffset(),
|
||||
resultStr);
|
||||
}
|
||||
if (oldNoxOffset != configuration.getNoxLearningOffset()) {
|
||||
Serial.printf("Setting noxLearningOffset from %d to %d hours %s\r\n",
|
||||
oldNoxOffset, configuration.getNoxLearningOffset(),
|
||||
resultStr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (configuration.isDisplayBrightnessChanged()) {
|
||||
oledDisplay.setBrightness(configuration.getDisplayBrightness());
|
||||
}
|
||||
|
||||
appDispHandler();
|
||||
}
|
||||
|
||||
static void appDispHandler(void) {
|
||||
AgStateMachineState state = AgStateMachineNormal;
|
||||
|
||||
/** Only show display status on online mode. */
|
||||
if (configuration.isOfflineMode() == false) {
|
||||
if (wifiConnector.isConnected() == false) {
|
||||
state = AgStateMachineWiFiLost;
|
||||
} else if (apiClient.isFetchConfigureFailed()) {
|
||||
state = AgStateMachineSensorConfigFailed;
|
||||
if (apiClient.isNotAvailableOnDashboard()) {
|
||||
stateMachine.displaySetAddToDashBoard();
|
||||
} else {
|
||||
stateMachine.displayClearAddToDashBoard();
|
||||
}
|
||||
} else if (apiClient.isPostToServerFailed()) {
|
||||
state = AgStateMachineServerLost;
|
||||
}
|
||||
}
|
||||
stateMachine.displayHandle(state);
|
||||
}
|
||||
|
||||
static void oledDisplaySchedule(void) {
|
||||
if (factoryBtnPressTime == 0) {
|
||||
appDispHandler();
|
||||
}
|
||||
}
|
||||
|
||||
static void updateTvoc(void) {
|
||||
if (!configuration.hasSensorSGP) {
|
||||
return;
|
||||
}
|
||||
|
||||
measurements.update(Measurements::TVOC, ag.sgp41.getTvocIndex());
|
||||
measurements.update(Measurements::TVOCRaw, ag.sgp41.getTvocRaw());
|
||||
measurements.update(Measurements::NOx, ag.sgp41.getNoxIndex());
|
||||
measurements.update(Measurements::NOxRaw, ag.sgp41.getNoxRaw());
|
||||
}
|
||||
|
||||
static void updatePm(void) {
|
||||
if (ag.pms5003.connected()) {
|
||||
measurements.update(Measurements::PM01, ag.pms5003.getPm01Ae());
|
||||
measurements.update(Measurements::PM25, ag.pms5003.getPm25Ae());
|
||||
measurements.update(Measurements::PM10, ag.pms5003.getPm10Ae());
|
||||
measurements.update(Measurements::PM03_PC, ag.pms5003.getPm03ParticleCount());
|
||||
} else {
|
||||
measurements.update(Measurements::PM01, utils::getInvalidPmValue());
|
||||
measurements.update(Measurements::PM25, utils::getInvalidPmValue());
|
||||
measurements.update(Measurements::PM10, utils::getInvalidPmValue());
|
||||
measurements.update(Measurements::PM03_PC, utils::getInvalidPmValue());
|
||||
}
|
||||
}
|
||||
|
||||
static void sendDataToServer(void) {
|
||||
/** Increment bootcount when send measurements data is scheduled */
|
||||
measurements.bootCount++;
|
||||
|
||||
/** Ignore send data to server if postToAirGradient disabled */
|
||||
if (configuration.isPostDataToAirGradient() == false ||
|
||||
configuration.isOfflineMode()) {
|
||||
return;
|
||||
}
|
||||
|
||||
String syncData = measurements.toString(false, fwMode, wifiConnector.RSSI(), ag, configuration);
|
||||
if (apiClient.postToServer(syncData)) {
|
||||
Serial.println();
|
||||
Serial.println(
|
||||
"Online mode and isPostToAirGradient = true: watchdog reset");
|
||||
Serial.println();
|
||||
}
|
||||
}
|
||||
|
||||
static void tempHumUpdate(void) {
|
||||
if (ag.sht.measure()) {
|
||||
float temp = ag.sht.getTemperature();
|
||||
float rhum = ag.sht.getRelativeHumidity();
|
||||
|
||||
measurements.update(Measurements::Temperature, temp);
|
||||
measurements.update(Measurements::Humidity, rhum);
|
||||
|
||||
// Update compensation temperature and humidity for SGP41
|
||||
if (configuration.hasSensorSGP) {
|
||||
ag.sgp41.setCompensationTemperatureHumidity(temp, rhum);
|
||||
}
|
||||
} else {
|
||||
measurements.update(Measurements::Temperature, utils::getInvalidTemperature());
|
||||
measurements.update(Measurements::Humidity, utils::getInvalidHumidity());
|
||||
Serial.println("SHT read failed");
|
||||
}
|
||||
}
|
||||
|
||||
/* 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;
|
||||
}
|
60
examples/DiyProIndoorV4_2/LocalServer.cpp
Normal file
60
examples/DiyProIndoorV4_2/LocalServer.cpp
Normal file
@ -0,0 +1,60 @@
|
||||
#include "LocalServer.h"
|
||||
|
||||
LocalServer::LocalServer(Stream &log, OpenMetrics &openMetrics,
|
||||
Measurements &measure, Configuration &config,
|
||||
WifiConnector &wifiConnector)
|
||||
: PrintLog(log, "LocalServer"), openMetrics(openMetrics), measure(measure),
|
||||
config(config), wifiConnector(wifiConnector), server(80) {}
|
||||
|
||||
LocalServer::~LocalServer() {}
|
||||
|
||||
bool LocalServer::begin(void) {
|
||||
server.on("/measures/current", HTTP_GET, [this]() { _GET_measure(); });
|
||||
server.on(openMetrics.getApi(), HTTP_GET, [this]() { _GET_metrics(); });
|
||||
server.on("/config", HTTP_GET, [this]() { _GET_config(); });
|
||||
server.on("/config", HTTP_PUT, [this]() { _PUT_config(); });
|
||||
server.begin();
|
||||
logInfo("Init: " + getHostname() + ".local");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void LocalServer::setAirGraident(AirGradient *ag) { this->ag = ag; }
|
||||
|
||||
String LocalServer::getHostname(void) {
|
||||
return "airgradient_" + ag->deviceId();
|
||||
}
|
||||
|
||||
void LocalServer::_handle(void) { server.handleClient(); }
|
||||
|
||||
void LocalServer::_GET_config(void) {
|
||||
if(ag->isOne()) {
|
||||
server.send(200, "application/json", config.toString());
|
||||
} else {
|
||||
server.send(200, "application/json", config.toString(fwMode));
|
||||
}
|
||||
}
|
||||
|
||||
void LocalServer::_PUT_config(void) {
|
||||
String data = server.arg(0);
|
||||
String response = "";
|
||||
int statusCode = 400; // Status code for data invalid
|
||||
if (config.parse(data, true)) {
|
||||
statusCode = 200;
|
||||
response = "Success";
|
||||
} else {
|
||||
response = config.getFailedMesage();
|
||||
}
|
||||
server.send(statusCode, "text/plain", response);
|
||||
}
|
||||
|
||||
void LocalServer::_GET_metrics(void) {
|
||||
server.send(200, openMetrics.getApiContentType(), openMetrics.getPayload());
|
||||
}
|
||||
|
||||
void LocalServer::_GET_measure(void) {
|
||||
String toSend = measure.toString(true, fwMode, wifiConnector.RSSI(), *ag, config);
|
||||
server.send(200, "application/json", toSend);
|
||||
}
|
||||
|
||||
void LocalServer::setFwMode(AgFirmwareMode fwMode) { this->fwMode = fwMode; }
|
38
examples/DiyProIndoorV4_2/LocalServer.h
Normal file
38
examples/DiyProIndoorV4_2/LocalServer.h
Normal file
@ -0,0 +1,38 @@
|
||||
#ifndef _LOCAL_SERVER_H_
|
||||
#define _LOCAL_SERVER_H_
|
||||
|
||||
#include "AgConfigure.h"
|
||||
#include "AgValue.h"
|
||||
#include "AirGradient.h"
|
||||
#include "OpenMetrics.h"
|
||||
#include "AgWiFiConnector.h"
|
||||
#include <Arduino.h>
|
||||
#include <ESP8266WebServer.h>
|
||||
|
||||
class LocalServer : public PrintLog {
|
||||
private:
|
||||
AirGradient *ag;
|
||||
OpenMetrics &openMetrics;
|
||||
Measurements &measure;
|
||||
Configuration &config;
|
||||
WifiConnector &wifiConnector;
|
||||
ESP8266WebServer server;
|
||||
AgFirmwareMode fwMode;
|
||||
|
||||
public:
|
||||
LocalServer(Stream &log, OpenMetrics &openMetrics, Measurements &measure,
|
||||
Configuration &config, WifiConnector& wifiConnector);
|
||||
~LocalServer();
|
||||
|
||||
bool begin(void);
|
||||
void setAirGraident(AirGradient *ag);
|
||||
String getHostname(void);
|
||||
void setFwMode(AgFirmwareMode fwMode);
|
||||
void _handle(void);
|
||||
void _GET_config(void);
|
||||
void _PUT_config(void);
|
||||
void _GET_metrics(void);
|
||||
void _GET_measure(void);
|
||||
};
|
||||
|
||||
#endif /** _LOCAL_SERVER_H_ */
|
203
examples/DiyProIndoorV4_2/OpenMetrics.cpp
Normal file
203
examples/DiyProIndoorV4_2/OpenMetrics.cpp
Normal file
@ -0,0 +1,203 @@
|
||||
#include "OpenMetrics.h"
|
||||
|
||||
OpenMetrics::OpenMetrics(Measurements &measure, Configuration &config,
|
||||
WifiConnector &wifiConnector, AgApiClient &apiClient)
|
||||
: measure(measure), config(config), wifiConnector(wifiConnector),
|
||||
apiClient(apiClient) {}
|
||||
|
||||
OpenMetrics::~OpenMetrics() {}
|
||||
|
||||
void OpenMetrics::setAirGradient(AirGradient *ag) { this->ag = ag; }
|
||||
|
||||
const char *OpenMetrics::getApiContentType(void) {
|
||||
return "application/openmetrics-text; version=1.0.0; charset=utf-8";
|
||||
}
|
||||
|
||||
const char *OpenMetrics::getApi(void) { return "/metrics"; }
|
||||
|
||||
String OpenMetrics::getPayload(void) {
|
||||
String response;
|
||||
String current_metric_name;
|
||||
const auto add_metric = [&](const String &name, const String &help,
|
||||
const String &type, const String &unit = "") {
|
||||
current_metric_name = "airgradient_" + name;
|
||||
if (!unit.isEmpty())
|
||||
current_metric_name += "_" + unit;
|
||||
response += "# HELP " + current_metric_name + " " + help + "\n";
|
||||
response += "# TYPE " + current_metric_name + " " + type + "\n";
|
||||
if (!unit.isEmpty())
|
||||
response += "# UNIT " + current_metric_name + " " + unit + "\n";
|
||||
};
|
||||
const auto add_metric_point = [&](const String &labels, const String &value) {
|
||||
response += current_metric_name + "{" + labels + "} " + value + "\n";
|
||||
};
|
||||
|
||||
add_metric("info", "AirGradient device information", "info");
|
||||
add_metric_point("airgradient_serial_number=\"" + ag->deviceId() +
|
||||
"\",airgradient_device_type=\"" + ag->getBoardName() +
|
||||
"\",airgradient_library_version=\"" + ag->getVersion() +
|
||||
"\"",
|
||||
"1");
|
||||
|
||||
add_metric("config_ok",
|
||||
"1 if the AirGradient device was able to successfully fetch its "
|
||||
"configuration from the server",
|
||||
"gauge");
|
||||
add_metric_point("", apiClient.isFetchConfigureFailed() ? "0" : "1");
|
||||
|
||||
add_metric(
|
||||
"post_ok",
|
||||
"1 if the AirGradient device was able to successfully send to the server",
|
||||
"gauge");
|
||||
add_metric_point("", apiClient.isPostToServerFailed() ? "0" : "1");
|
||||
|
||||
add_metric(
|
||||
"wifi_rssi",
|
||||
"WiFi signal strength from the AirGradient device perspective, in dBm",
|
||||
"gauge", "dbm");
|
||||
add_metric_point("", String(wifiConnector.RSSI()));
|
||||
|
||||
// Initialize default invalid value for each measurements
|
||||
float _temp = utils::getInvalidTemperature();
|
||||
float _hum = utils::getInvalidHumidity();
|
||||
int pm01 = utils::getInvalidPmValue();
|
||||
int pm25 = utils::getInvalidPmValue();
|
||||
int pm10 = utils::getInvalidPmValue();
|
||||
int pm03PCount = utils::getInvalidPmValue();
|
||||
int co2 = utils::getInvalidCO2();
|
||||
int atmpCompensated = utils::getInvalidTemperature();
|
||||
int ahumCompensated = utils::getInvalidHumidity();
|
||||
int tvoc = utils::getInvalidVOC();
|
||||
int tvocRaw = utils::getInvalidVOC();
|
||||
int nox = utils::getInvalidNOx();
|
||||
int noxRaw = utils::getInvalidNOx();
|
||||
|
||||
if (config.hasSensorSHT) {
|
||||
_temp = measure.getFloat(Measurements::Temperature);
|
||||
_hum = measure.getFloat(Measurements::Humidity);
|
||||
atmpCompensated = _temp;
|
||||
ahumCompensated = _hum;
|
||||
}
|
||||
|
||||
if (config.hasSensorPMS1) {
|
||||
pm01 = measure.get(Measurements::PM01);
|
||||
pm25 = measure.get(Measurements::PM25);
|
||||
pm10 = measure.get(Measurements::PM10);
|
||||
pm03PCount = measure.get(Measurements::PM03_PC);
|
||||
}
|
||||
|
||||
if (config.hasSensorSGP) {
|
||||
tvoc = measure.get(Measurements::TVOC);
|
||||
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 (utils::isValidPm(pm01)) {
|
||||
add_metric("pm1",
|
||||
"PM1.0 concentration as measured by the AirGradient PMS "
|
||||
"sensor, in micrograms per cubic meter",
|
||||
"gauge", "ugm3");
|
||||
add_metric_point("", String(pm01));
|
||||
}
|
||||
if (utils::isValidPm(pm25)) {
|
||||
add_metric("pm2d5",
|
||||
"PM2.5 concentration as measured by the AirGradient PMS "
|
||||
"sensor, in micrograms per cubic meter",
|
||||
"gauge", "ugm3");
|
||||
add_metric_point("", String(pm25));
|
||||
}
|
||||
if (utils::isValidPm(pm10)) {
|
||||
add_metric("pm10",
|
||||
"PM10 concentration as measured by the AirGradient PMS "
|
||||
"sensor, in micrograms per cubic meter",
|
||||
"gauge", "ugm3");
|
||||
add_metric_point("", String(pm10));
|
||||
}
|
||||
if (utils::isValidPm03Count(pm03PCount)) {
|
||||
add_metric("pm0d3",
|
||||
"PM0.3 concentration as measured by the AirGradient PMS "
|
||||
"sensor, in number of particules per 100 milliliters",
|
||||
"gauge", "p100ml");
|
||||
add_metric_point("", String(pm03PCount));
|
||||
}
|
||||
}
|
||||
|
||||
if (config.hasSensorSGP) {
|
||||
if (utils::isValidVOC(tvoc)) {
|
||||
add_metric("tvoc_index",
|
||||
"The processed Total Volatile Organic Compounds (TVOC) index "
|
||||
"as measured by the AirGradient SGP sensor",
|
||||
"gauge");
|
||||
add_metric_point("", String(tvoc));
|
||||
}
|
||||
if (utils::isValidVOC(tvocRaw)) {
|
||||
add_metric("tvoc_raw",
|
||||
"The raw input value to the Total Volatile Organic Compounds "
|
||||
"(TVOC) index as measured by the AirGradient SGP sensor",
|
||||
"gauge");
|
||||
add_metric_point("", String(tvocRaw));
|
||||
}
|
||||
if (utils::isValidNOx(nox)) {
|
||||
add_metric("nox_index",
|
||||
"The processed Nitrous Oxide (NOx) index as measured by the "
|
||||
"AirGradient SGP sensor",
|
||||
"gauge");
|
||||
add_metric_point("", String(nox));
|
||||
}
|
||||
if (utils::isValidNOx(noxRaw)) {
|
||||
add_metric("nox_raw",
|
||||
"The raw input value to the Nitrous Oxide (NOx) index as "
|
||||
"measured by the AirGradient SGP sensor",
|
||||
"gauge");
|
||||
add_metric_point("", String(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)) {
|
||||
add_metric(
|
||||
"temperature",
|
||||
"The ambient temperature as measured by the AirGradient SHT / PMS "
|
||||
"sensor, in degrees Celsius",
|
||||
"gauge", "celsius");
|
||||
add_metric_point("", String(_temp));
|
||||
}
|
||||
if (utils::isValidTemperature(atmpCompensated)) {
|
||||
add_metric("temperature_compensated",
|
||||
"The compensated ambient temperature as measured by the "
|
||||
"AirGradient SHT / PMS "
|
||||
"sensor, in degrees Celsius",
|
||||
"gauge", "celsius");
|
||||
add_metric_point("", String(atmpCompensated));
|
||||
}
|
||||
if (utils::isValidHumidity(_hum)) {
|
||||
add_metric(
|
||||
"humidity",
|
||||
"The relative humidity as measured by the AirGradient SHT sensor",
|
||||
"gauge", "percent");
|
||||
add_metric_point("", String(_hum));
|
||||
}
|
||||
if (utils::isValidHumidity(ahumCompensated)) {
|
||||
add_metric("humidity_compensated",
|
||||
"The compensated relative humidity as measured by the "
|
||||
"AirGradient SHT / PMS sensor",
|
||||
"gauge", "percent");
|
||||
add_metric_point("", String(ahumCompensated));
|
||||
}
|
||||
|
||||
response += "# EOF\n";
|
||||
return response;
|
||||
}
|
28
examples/DiyProIndoorV4_2/OpenMetrics.h
Normal file
28
examples/DiyProIndoorV4_2/OpenMetrics.h
Normal file
@ -0,0 +1,28 @@
|
||||
#ifndef _OPEN_METRICS_H_
|
||||
#define _OPEN_METRICS_H_
|
||||
|
||||
#include "AgConfigure.h"
|
||||
#include "AgValue.h"
|
||||
#include "AgWiFiConnector.h"
|
||||
#include "AirGradient.h"
|
||||
#include "AgApiClient.h"
|
||||
|
||||
class OpenMetrics {
|
||||
private:
|
||||
AirGradient *ag;
|
||||
Measurements &measure;
|
||||
Configuration &config;
|
||||
WifiConnector &wifiConnector;
|
||||
AgApiClient &apiClient;
|
||||
|
||||
public:
|
||||
OpenMetrics(Measurements &measure, Configuration &conig,
|
||||
WifiConnector &wifiConnector, AgApiClient& apiClient);
|
||||
~OpenMetrics();
|
||||
void setAirGradient(AirGradient *ag);
|
||||
const char *getApiContentType(void);
|
||||
const char* getApi(void);
|
||||
String getPayload(void);
|
||||
};
|
||||
|
||||
#endif /** _OPEN_METRICS_H_ */
|
File diff suppressed because it is too large
Load Diff
71
examples/OneOpenAir/LocalServer.cpp
Normal file
71
examples/OneOpenAir/LocalServer.cpp
Normal file
@ -0,0 +1,71 @@
|
||||
#include "LocalServer.h"
|
||||
|
||||
LocalServer::LocalServer(Stream &log, OpenMetrics &openMetrics,
|
||||
Measurements &measure, Configuration &config,
|
||||
WifiConnector &wifiConnector)
|
||||
: PrintLog(log, "LocalServer"), openMetrics(openMetrics), measure(measure),
|
||||
config(config), wifiConnector(wifiConnector) {}
|
||||
|
||||
LocalServer::~LocalServer() {}
|
||||
|
||||
bool LocalServer::begin(void) {
|
||||
server.on("/measures/current", HTTP_GET, [this]() { _GET_measure(); });
|
||||
server.on(openMetrics.getApi(), HTTP_GET, [this]() { _GET_metrics(); });
|
||||
server.on("/config", HTTP_GET, [this]() { _GET_config(); });
|
||||
server.on("/config", HTTP_PUT, [this]() { _PUT_config(); });
|
||||
server.begin();
|
||||
|
||||
if (xTaskCreate(
|
||||
[](void *param) {
|
||||
LocalServer *localServer = (LocalServer *)param;
|
||||
for (;;) {
|
||||
localServer->_handle();
|
||||
}
|
||||
},
|
||||
"webserver", 1024 * 4, this, 5, NULL) != pdTRUE) {
|
||||
Serial.println("Create task handle webserver failed");
|
||||
}
|
||||
logInfo("Init: " + getHostname() + ".local");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void LocalServer::setAirGraident(AirGradient *ag) { this->ag = ag; }
|
||||
|
||||
String LocalServer::getHostname(void) {
|
||||
return "airgradient_" + ag->deviceId();
|
||||
}
|
||||
|
||||
void LocalServer::_handle(void) { server.handleClient(); }
|
||||
|
||||
void LocalServer::_GET_config(void) {
|
||||
if(ag->isOne()) {
|
||||
server.send(200, "application/json", config.toString());
|
||||
} else {
|
||||
server.send(200, "application/json", config.toString(fwMode));
|
||||
}
|
||||
}
|
||||
|
||||
void LocalServer::_PUT_config(void) {
|
||||
String data = server.arg(0);
|
||||
String response = "";
|
||||
int statusCode = 400; // Status code for data invalid
|
||||
if (config.parse(data, true)) {
|
||||
statusCode = 200;
|
||||
response = "Success";
|
||||
} else {
|
||||
response = config.getFailedMesage();
|
||||
}
|
||||
server.send(statusCode, "text/plain", response);
|
||||
}
|
||||
|
||||
void LocalServer::_GET_metrics(void) {
|
||||
server.send(200, openMetrics.getApiContentType(), openMetrics.getPayload());
|
||||
}
|
||||
|
||||
void LocalServer::_GET_measure(void) {
|
||||
String toSend = measure.toString(true, fwMode, wifiConnector.RSSI(), *ag, config);
|
||||
server.send(200, "application/json", toSend);
|
||||
}
|
||||
|
||||
void LocalServer::setFwMode(AgFirmwareMode fwMode) { this->fwMode = fwMode; }
|
38
examples/OneOpenAir/LocalServer.h
Normal file
38
examples/OneOpenAir/LocalServer.h
Normal file
@ -0,0 +1,38 @@
|
||||
#ifndef _LOCAL_SERVER_H_
|
||||
#define _LOCAL_SERVER_H_
|
||||
|
||||
#include "AgConfigure.h"
|
||||
#include "AgValue.h"
|
||||
#include "AirGradient.h"
|
||||
#include "OpenMetrics.h"
|
||||
#include "AgWiFiConnector.h"
|
||||
#include <Arduino.h>
|
||||
#include <WebServer.h>
|
||||
|
||||
class LocalServer : public PrintLog {
|
||||
private:
|
||||
AirGradient *ag;
|
||||
OpenMetrics &openMetrics;
|
||||
Measurements &measure;
|
||||
Configuration &config;
|
||||
WifiConnector &wifiConnector;
|
||||
WebServer server;
|
||||
AgFirmwareMode fwMode;
|
||||
|
||||
public:
|
||||
LocalServer(Stream &log, OpenMetrics &openMetrics, Measurements &measure,
|
||||
Configuration &config, WifiConnector& wifiConnector);
|
||||
~LocalServer();
|
||||
|
||||
bool begin(void);
|
||||
void setAirGraident(AirGradient *ag);
|
||||
String getHostname(void);
|
||||
void setFwMode(AgFirmwareMode fwMode);
|
||||
void _handle(void);
|
||||
void _GET_config(void);
|
||||
void _PUT_config(void);
|
||||
void _GET_metrics(void);
|
||||
void _GET_measure(void);
|
||||
};
|
||||
|
||||
#endif /** _LOCAL_SERVER_H_ */
|
1221
examples/OneOpenAir/OneOpenAir.ino
Normal file
1221
examples/OneOpenAir/OneOpenAir.ino
Normal file
File diff suppressed because it is too large
Load Diff
240
examples/OneOpenAir/OpenMetrics.cpp
Normal file
240
examples/OneOpenAir/OpenMetrics.cpp
Normal file
@ -0,0 +1,240 @@
|
||||
#include "OpenMetrics.h"
|
||||
|
||||
OpenMetrics::OpenMetrics(Measurements &measure, Configuration &config,
|
||||
WifiConnector &wifiConnector, AgApiClient &apiClient)
|
||||
: measure(measure), config(config), wifiConnector(wifiConnector),
|
||||
apiClient(apiClient) {}
|
||||
|
||||
OpenMetrics::~OpenMetrics() {}
|
||||
|
||||
void OpenMetrics::setAirGradient(AirGradient *ag) { this->ag = ag; }
|
||||
|
||||
const char *OpenMetrics::getApiContentType(void) {
|
||||
return "application/openmetrics-text; version=1.0.0; charset=utf-8";
|
||||
}
|
||||
|
||||
const char *OpenMetrics::getApi(void) { return "/metrics"; }
|
||||
|
||||
String OpenMetrics::getPayload(void) {
|
||||
String response;
|
||||
String current_metric_name;
|
||||
const auto add_metric = [&](const String &name, const String &help,
|
||||
const String &type, const String &unit = "") {
|
||||
current_metric_name = "airgradient_" + name;
|
||||
if (!unit.isEmpty())
|
||||
current_metric_name += "_" + unit;
|
||||
response += "# HELP " + current_metric_name + " " + help + "\n";
|
||||
response += "# TYPE " + current_metric_name + " " + type + "\n";
|
||||
if (!unit.isEmpty())
|
||||
response += "# UNIT " + current_metric_name + " " + unit + "\n";
|
||||
};
|
||||
const auto add_metric_point = [&](const String &labels, const String &value) {
|
||||
response += current_metric_name + "{" + labels + "} " + value + "\n";
|
||||
};
|
||||
|
||||
add_metric("info", "AirGradient device information", "info");
|
||||
add_metric_point("airgradient_serial_number=\"" + ag->deviceId() +
|
||||
"\",airgradient_device_type=\"" + ag->getBoardName() +
|
||||
"\",airgradient_library_version=\"" + ag->getVersion() +
|
||||
"\"",
|
||||
"1");
|
||||
|
||||
add_metric("config_ok",
|
||||
"1 if the AirGradient device was able to successfully fetch its "
|
||||
"configuration from the server",
|
||||
"gauge");
|
||||
add_metric_point("", apiClient.isFetchConfigureFailed() ? "0" : "1");
|
||||
|
||||
add_metric(
|
||||
"post_ok",
|
||||
"1 if the AirGradient device was able to successfully send to the server",
|
||||
"gauge");
|
||||
add_metric_point("", apiClient.isPostToServerFailed() ? "0" : "1");
|
||||
|
||||
add_metric(
|
||||
"wifi_rssi",
|
||||
"WiFi signal strength from the AirGradient device perspective, in dBm",
|
||||
"gauge", "dbm");
|
||||
add_metric_point("", String(wifiConnector.RSSI()));
|
||||
|
||||
// Initialize default invalid value for each measurements
|
||||
float _temp = utils::getInvalidTemperature();
|
||||
float _hum = utils::getInvalidHumidity();
|
||||
int pm01 = utils::getInvalidPmValue();
|
||||
int pm25 = utils::getInvalidPmValue();
|
||||
int pm10 = utils::getInvalidPmValue();
|
||||
int pm03PCount = utils::getInvalidPmValue();
|
||||
int co2 = utils::getInvalidCO2();
|
||||
int atmpCompensated = utils::getInvalidTemperature();
|
||||
int ahumCompensated = utils::getInvalidHumidity();
|
||||
int tvoc = utils::getInvalidVOC();
|
||||
int tvocRaw = utils::getInvalidVOC();
|
||||
int nox = utils::getInvalidNOx();
|
||||
int noxRaw = utils::getInvalidNOx();
|
||||
|
||||
// Get values
|
||||
if (config.hasSensorPMS1 && config.hasSensorPMS2) {
|
||||
_temp = (measure.getFloat(Measurements::Temperature, 1) +
|
||||
measure.getFloat(Measurements::Temperature, 2)) /
|
||||
2.0f;
|
||||
_hum = (measure.getFloat(Measurements::Humidity, 1) +
|
||||
measure.getFloat(Measurements::Humidity, 2)) /
|
||||
2.0f;
|
||||
pm01 = (measure.get(Measurements::PM01, 1) + measure.get(Measurements::PM01, 2)) / 2.0f;
|
||||
pm25 = (measure.get(Measurements::PM25, 1) + measure.get(Measurements::PM25, 2)) / 2.0f;
|
||||
pm10 = (measure.get(Measurements::PM10, 1) + measure.get(Measurements::PM10, 2)) / 2.0f;
|
||||
pm03PCount =
|
||||
(measure.get(Measurements::PM03_PC, 1) + measure.get(Measurements::PM03_PC, 2)) / 2.0f;
|
||||
} else {
|
||||
if (ag->isOne()) {
|
||||
if (config.hasSensorSHT) {
|
||||
_temp = measure.getFloat(Measurements::Temperature);
|
||||
_hum = measure.getFloat(Measurements::Humidity);
|
||||
}
|
||||
|
||||
if (config.hasSensorPMS1) {
|
||||
pm01 = measure.get(Measurements::PM01);
|
||||
pm25 = measure.get(Measurements::PM25);
|
||||
pm10 = measure.get(Measurements::PM10);
|
||||
pm03PCount = measure.get(Measurements::PM03_PC);
|
||||
}
|
||||
} else {
|
||||
if (config.hasSensorPMS1) {
|
||||
_temp = measure.getFloat(Measurements::Temperature, 1);
|
||||
_hum = measure.getFloat(Measurements::Humidity, 1);
|
||||
pm01 = measure.get(Measurements::PM01, 1);
|
||||
pm25 = measure.get(Measurements::PM25, 1);
|
||||
pm10 = measure.get(Measurements::PM10, 1);
|
||||
pm03PCount = measure.get(Measurements::PM03_PC, 1);
|
||||
}
|
||||
if (config.hasSensorPMS2) {
|
||||
_temp = measure.getFloat(Measurements::Temperature, 2);
|
||||
_hum = measure.getFloat(Measurements::Humidity, 2);
|
||||
pm01 = measure.get(Measurements::PM01, 2);
|
||||
pm25 = measure.get(Measurements::PM25, 2);
|
||||
pm10 = measure.get(Measurements::PM10, 2);
|
||||
pm03PCount = measure.get(Measurements::PM03_PC, 2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 */
|
||||
if (ag->isOne()) {
|
||||
atmpCompensated = _temp;
|
||||
ahumCompensated = _hum;
|
||||
} else {
|
||||
atmpCompensated = ag->pms5003t_1.compensateTemp(_temp);
|
||||
ahumCompensated = ag->pms5003t_1.compensateHum(_hum);
|
||||
}
|
||||
|
||||
// Add measurements that valid to the metrics
|
||||
if (config.hasSensorPMS1 || config.hasSensorPMS2) {
|
||||
if (utils::isValidPm(pm01)) {
|
||||
add_metric("pm1",
|
||||
"PM1.0 concentration as measured by the AirGradient PMS "
|
||||
"sensor, in micrograms per cubic meter",
|
||||
"gauge", "ugm3");
|
||||
add_metric_point("", String(pm01));
|
||||
}
|
||||
if (utils::isValidPm(pm25)) {
|
||||
add_metric("pm2d5",
|
||||
"PM2.5 concentration as measured by the AirGradient PMS "
|
||||
"sensor, in micrograms per cubic meter",
|
||||
"gauge", "ugm3");
|
||||
add_metric_point("", String(pm25));
|
||||
}
|
||||
if (utils::isValidPm(pm10)) {
|
||||
add_metric("pm10",
|
||||
"PM10 concentration as measured by the AirGradient PMS "
|
||||
"sensor, in micrograms per cubic meter",
|
||||
"gauge", "ugm3");
|
||||
add_metric_point("", String(pm10));
|
||||
}
|
||||
if (utils::isValidPm03Count(pm03PCount)) {
|
||||
add_metric("pm0d3",
|
||||
"PM0.3 concentration as measured by the AirGradient PMS "
|
||||
"sensor, in number of particules per 100 milliliters",
|
||||
"gauge", "p100ml");
|
||||
add_metric_point("", String(pm03PCount));
|
||||
}
|
||||
}
|
||||
|
||||
if (config.hasSensorSGP) {
|
||||
if (utils::isValidVOC(tvoc)) {
|
||||
add_metric("tvoc_index",
|
||||
"The processed Total Volatile Organic Compounds (TVOC) index "
|
||||
"as measured by the AirGradient SGP sensor",
|
||||
"gauge");
|
||||
add_metric_point("", String(tvoc));
|
||||
}
|
||||
if (utils::isValidVOC(tvocRaw)) {
|
||||
add_metric("tvoc_raw",
|
||||
"The raw input value to the Total Volatile Organic Compounds "
|
||||
"(TVOC) index as measured by the AirGradient SGP sensor",
|
||||
"gauge");
|
||||
add_metric_point("", String(tvocRaw));
|
||||
}
|
||||
if (utils::isValidNOx(nox)) {
|
||||
add_metric("nox_index",
|
||||
"The processed Nitrous Oxide (NOx) index as measured by the "
|
||||
"AirGradient SGP sensor",
|
||||
"gauge");
|
||||
add_metric_point("", String(nox));
|
||||
}
|
||||
if (utils::isValidNOx(noxRaw)) {
|
||||
add_metric("nox_raw",
|
||||
"The raw input value to the Nitrous Oxide (NOx) index as "
|
||||
"measured by the AirGradient SGP sensor",
|
||||
"gauge");
|
||||
add_metric_point("", String(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)) {
|
||||
add_metric("temperature",
|
||||
"The ambient temperature as measured by the AirGradient SHT / PMS "
|
||||
"sensor, in degrees Celsius",
|
||||
"gauge", "celsius");
|
||||
add_metric_point("", String(_temp));
|
||||
}
|
||||
if (utils::isValidTemperature(atmpCompensated)) {
|
||||
add_metric("temperature_compensated",
|
||||
"The compensated ambient temperature as measured by the AirGradient SHT / PMS "
|
||||
"sensor, in degrees Celsius",
|
||||
"gauge", "celsius");
|
||||
add_metric_point("", String(atmpCompensated));
|
||||
}
|
||||
if (utils::isValidHumidity(_hum)) {
|
||||
add_metric("humidity", "The relative humidity as measured by the AirGradient SHT sensor",
|
||||
"gauge", "percent");
|
||||
add_metric_point("", String(_hum));
|
||||
}
|
||||
if (utils::isValidHumidity(ahumCompensated)) {
|
||||
add_metric("humidity_compensated",
|
||||
"The compensated relative humidity as measured by the AirGradient SHT / PMS sensor",
|
||||
"gauge", "percent");
|
||||
add_metric_point("", String(ahumCompensated));
|
||||
}
|
||||
|
||||
response += "# EOF\n";
|
||||
return response;
|
||||
}
|
28
examples/OneOpenAir/OpenMetrics.h
Normal file
28
examples/OneOpenAir/OpenMetrics.h
Normal file
@ -0,0 +1,28 @@
|
||||
#ifndef _OPEN_METRICS_H_
|
||||
#define _OPEN_METRICS_H_
|
||||
|
||||
#include "AgConfigure.h"
|
||||
#include "AgValue.h"
|
||||
#include "AgWiFiConnector.h"
|
||||
#include "AirGradient.h"
|
||||
#include "AgApiClient.h"
|
||||
|
||||
class OpenMetrics {
|
||||
private:
|
||||
AirGradient *ag;
|
||||
Measurements &measure;
|
||||
Configuration &config;
|
||||
WifiConnector &wifiConnector;
|
||||
AgApiClient &apiClient;
|
||||
|
||||
public:
|
||||
OpenMetrics(Measurements &measure, Configuration &conig,
|
||||
WifiConnector &wifiConnector, AgApiClient& apiClient);
|
||||
~OpenMetrics();
|
||||
void setAirGradient(AirGradient *ag);
|
||||
const char *getApiContentType(void);
|
||||
const char* getApi(void);
|
||||
String getPayload(void);
|
||||
};
|
||||
|
||||
#endif /** _OPEN_METRICS_H_ */
|
206
examples/OneOpenAir/OtaHandler.h
Normal file
206
examples/OneOpenAir/OtaHandler.h
Normal file
@ -0,0 +1,206 @@
|
||||
#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
|
@ -1,687 +0,0 @@
|
||||
/*
|
||||
This is the code for the AirGradient Open Air open-source hardware outdoor Air Quality Monitor with an ESP32-C3 Microcontroller.
|
||||
|
||||
It is an air quality monitor for PM2.5, CO2, TVOCs, NOx, Temperature and Humidity and can send data over Wifi.
|
||||
|
||||
Open source air quality monitors and kits are available:
|
||||
Indoor Monitor: https://www.airgradient.com/indoor/
|
||||
Outdoor Monitor: https://www.airgradient.com/outdoor/
|
||||
|
||||
Build Instructions: https://www.airgradient.com/documentation/open-air-pst-kit-1-3/
|
||||
|
||||
The codes needs the following libraries installed:
|
||||
“WifiManager by tzapu, tablatronix” tested with version 2.0.16-rc.2
|
||||
"Arduino_JSON" by Arduino Version 0.2.0
|
||||
|
||||
Please make sure you have esp32 board manager installed. Tested with version 2.0.11.
|
||||
|
||||
Important flashing settings:
|
||||
- Set board to "ESP32C3 Dev Module"
|
||||
- Enable "USB CDC On Boot"
|
||||
- Flash frequency "80Mhz"
|
||||
- Flash mode "QIO"
|
||||
- Flash size "4MB"
|
||||
- Partition scheme "Default 4MB with spiffs (1.2MB APP/1,5MB SPIFFS)"
|
||||
- JTAG adapter "Disabled"
|
||||
|
||||
If you have any questions please visit our forum at
|
||||
https://forum.airgradient.com/
|
||||
|
||||
CC BY-SA 4.0 Attribution-ShareAlike 4.0 International License
|
||||
|
||||
*/
|
||||
|
||||
#include <AirGradient.h>
|
||||
#include <Arduino_JSON.h>
|
||||
#include <HTTPClient.h>
|
||||
#include <HardwareSerial.h>
|
||||
#include <WiFiManager.h>
|
||||
#include <Wire.h>
|
||||
|
||||
/**
|
||||
*
|
||||
* @brief Application state machine state
|
||||
*
|
||||
*/
|
||||
enum {
|
||||
APP_SM_WIFI_MANAGER_MODE, /** In WiFi Manger Mode */
|
||||
APP_SM_WIFI_MAMAGER_PORTAL_ACTIVE, /** WiFi Manager has connected to mobile
|
||||
phone */
|
||||
APP_SM_WIFI_MANAGER_STA_CONNECTING, /** After SSID and PW entered and OK
|
||||
clicked, connection to WiFI network is
|
||||
attempted*/
|
||||
APP_SM_WIFI_MANAGER_STA_CONNECTED, /** Connecting to WiFi worked */
|
||||
APP_SM_WIFI_OK_SERVER_CONNECTING, /** Once connected to WiFi an attempt to
|
||||
reach the server is performed */
|
||||
APP_SM_WIFI_OK_SERVER_CONNNECTED, /** Server is reachable, all fine */
|
||||
/** Exceptions during WIFi Setup */
|
||||
APP_SM_WIFI_MANAGER_CONNECT_FAILED, /** Cannot connect to WiFi (e.g. wrong
|
||||
password, WPA Enterprise etc.) */
|
||||
APP_SM_WIFI_OK_SERVER_CONNECT_FAILED, /** Connected to WiFi but server not
|
||||
reachable, e.g. firewall block/
|
||||
whitelisting needed etc. */
|
||||
APP_SM_WIFI_OK_SERVER_OK_SENSOR_CONFIG_FAILED, /** Server reachable but sensor
|
||||
not configured correctly*/
|
||||
|
||||
/** During Normal Operation */
|
||||
APP_SM_WIFI_LOST, /** Connection to WiFi network failed credentials incorrect
|
||||
encryption not supported etc. */
|
||||
APP_SM_SERVER_LOST, /** Connected to WiFi network but the server cannot be
|
||||
reached through the internet, e.g. blocked by firewall
|
||||
*/
|
||||
APP_SM_SENSOR_CONFIG_FAILED, /** Server is reachable but there is some
|
||||
configuration issue to be fixed on the server
|
||||
side */
|
||||
APP_SM_NORMAL,
|
||||
};
|
||||
|
||||
#define DEBUG true
|
||||
|
||||
#define WIFI_CONNECT_COUNTDOWN_MAX 180 /** sec */
|
||||
#define WIFI_HOTSPOT_PASSWORD_DEFAULT "cleanair"
|
||||
|
||||
AirGradient ag(BOARD_OUTDOOR_MONITOR_V1_3);
|
||||
|
||||
// time in seconds needed for NOx conditioning
|
||||
uint16_t conditioning_s = 10;
|
||||
|
||||
String APIROOT = "http://hw.airgradient.com/";
|
||||
|
||||
typedef struct {
|
||||
bool inF; /** Temperature unit */
|
||||
bool inUSAQI; /** PMS standard */
|
||||
uint8_t ledBarMode; /** @ref UseLedBar*/
|
||||
char model[16]; /** Model string value, Just define, don't know how much
|
||||
memory usage */
|
||||
char mqttBroker[128]; /** Mqtt broker link */
|
||||
uint32_t _check; /** Checksum configuration data */
|
||||
} ServerConfig_t;
|
||||
static ServerConfig_t serverConfig;
|
||||
|
||||
// set to true if you want to connect to wifi. You have 60 seconds to connect.
|
||||
// Then it will go into an offline mode.
|
||||
boolean connectWIFI = true;
|
||||
|
||||
static int ledSmState = APP_SM_NORMAL;
|
||||
static bool serverFailed = false;
|
||||
static bool configFailed = false;
|
||||
static bool wifiHasConfig = false;
|
||||
|
||||
int loopCount = 0;
|
||||
|
||||
WiFiManager wifiManager; /** wifi manager instance */
|
||||
|
||||
unsigned long currentMillis = 0;
|
||||
|
||||
const int oledInterval = 5000;
|
||||
unsigned long previousOled = 0;
|
||||
|
||||
const int sendToServerInterval = 60000;
|
||||
const int pollServerConfigInterval = 30000;
|
||||
const int co2CalibCountdown = 5; /** Seconds */
|
||||
unsigned long previoussendToServer = 0;
|
||||
|
||||
const int tvocInterval = 1000;
|
||||
unsigned long previousTVOC = 0;
|
||||
int TVOC = -1;
|
||||
int NOX = -1;
|
||||
|
||||
const int co2Interval = 5000;
|
||||
unsigned long previousCo2 = 0;
|
||||
int Co2 = 0;
|
||||
|
||||
const int pmInterval = 5000;
|
||||
unsigned long previousPm = 0;
|
||||
int pm25 = -1;
|
||||
int pm01 = -1;
|
||||
int pm10 = -1;
|
||||
int pm03PCount = -1;
|
||||
float temp;
|
||||
int hum;
|
||||
|
||||
bool co2CalibrationRequest = false;
|
||||
uint32_t serverConfigLoadTime = 0;
|
||||
String HOTSPOT = "";
|
||||
|
||||
// const int tempHumInterval = 2500;
|
||||
// unsigned long previousTempHum = 0;
|
||||
|
||||
void boardInit(void);
|
||||
void failedHandler(String msg);
|
||||
void getServerConfig(void);
|
||||
void co2Calibration(void);
|
||||
|
||||
void setup() {
|
||||
if (DEBUG) {
|
||||
Serial.begin(115200);
|
||||
}
|
||||
|
||||
/** Board init */
|
||||
boardInit();
|
||||
|
||||
delay(500);
|
||||
|
||||
countdown(3);
|
||||
|
||||
if (connectWIFI) {
|
||||
connectToWifi();
|
||||
}
|
||||
|
||||
if (WiFi.status() == WL_CONNECTED) {
|
||||
sendPing();
|
||||
Serial.println(F("WiFi connected!"));
|
||||
Serial.println("IP address: ");
|
||||
Serial.println(WiFi.localIP());
|
||||
}
|
||||
|
||||
getServerConfig();
|
||||
if (configFailed) {
|
||||
ledSmHandler(APP_SM_SENSOR_CONFIG_FAILED);
|
||||
delay(5000);
|
||||
}
|
||||
|
||||
ledSmHandler(APP_SM_NORMAL);
|
||||
}
|
||||
|
||||
void loop() {
|
||||
currentMillis = millis();
|
||||
updateTVOC();
|
||||
updateCo2();
|
||||
updatePm();
|
||||
sendToServer();
|
||||
getServerConfig();
|
||||
}
|
||||
|
||||
void updateTVOC() {
|
||||
delay(1000);
|
||||
|
||||
if (currentMillis - previousTVOC >= tvocInterval) {
|
||||
previousTVOC += tvocInterval;
|
||||
TVOC = ag.sgp41.getTvocIndex();
|
||||
NOX = ag.sgp41.getNoxIndex();
|
||||
}
|
||||
}
|
||||
|
||||
void updateCo2() {
|
||||
if (currentMillis - previousCo2 >= co2Interval) {
|
||||
previousCo2 += co2Interval;
|
||||
Co2 = ag.s8.getCo2();
|
||||
Serial.printf("CO2: %d\r\n", Co2);
|
||||
}
|
||||
}
|
||||
|
||||
void updatePm() {
|
||||
if (currentMillis - previousPm >= pmInterval) {
|
||||
previousPm += pmInterval;
|
||||
if (ag.pms5003t_1.readData()) {
|
||||
pm01 = ag.pms5003t_1.getPm01Ae();
|
||||
pm25 = ag.pms5003t_1.getPm25Ae();
|
||||
pm10 = ag.pms5003t_1.getPm10Ae();
|
||||
pm03PCount = ag.pms5003t_1.getPm03ParticleCount();
|
||||
temp = ag.pms5003t_1.getTemperature();
|
||||
hum = ag.pms5003t_1.getRelativeHumidity();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void sendPing() {
|
||||
String payload =
|
||||
"{\"wifi\":" + String(WiFi.RSSI()) + ", \"boot\":" + loopCount + "}";
|
||||
if (postToServer(payload)) {
|
||||
ledSmHandler(APP_SM_WIFI_OK_SERVER_CONNNECTED);
|
||||
} else {
|
||||
ledSmHandler(APP_SM_WIFI_OK_SERVER_CONNECT_FAILED);
|
||||
}
|
||||
delay(5000);
|
||||
}
|
||||
|
||||
bool postToServer(String &payload) {
|
||||
String POSTURL = APIROOT +
|
||||
"sensors/airgradient:" + String(getNormalizedMac()) +
|
||||
"/measures";
|
||||
WiFiClient client;
|
||||
HTTPClient http;
|
||||
|
||||
ag.statusLed.setOn();
|
||||
|
||||
http.begin(client, POSTURL);
|
||||
http.addHeader("content-type", "application/json");
|
||||
int httpCode = http.POST(payload);
|
||||
Serial.printf("Post to %s, %d\r\n", POSTURL.c_str(), httpCode);
|
||||
http.end();
|
||||
|
||||
ag.statusLed.setOff();
|
||||
|
||||
return (httpCode == 200);
|
||||
}
|
||||
|
||||
void sendToServer() {
|
||||
if (currentMillis - previoussendToServer >= sendToServerInterval) {
|
||||
previoussendToServer += sendToServerInterval;
|
||||
String payload =
|
||||
"{\"wifi\":" + String(WiFi.RSSI()) +
|
||||
(Co2 < 0 ? "" : ", \"rco2\":" + String(Co2)) +
|
||||
(pm01 < 0 ? "" : ", \"pm01\":" + String(pm01)) +
|
||||
(pm25 < 0 ? "" : ", \"pm02\":" + String(pm25)) +
|
||||
(pm10 < 0 ? "" : ", \"pm10\":" + String(pm10)) +
|
||||
(pm03PCount < 0 ? "" : ", \"pm003_count\":" + String(pm03PCount)) +
|
||||
(TVOC < 0 ? "" : ", \"tvoc_index\":" + String(TVOC)) +
|
||||
(NOX < 0 ? "" : ", \"nox_index\":" + String(NOX)) +
|
||||
", \"atmp\":" + String(temp) +
|
||||
(hum < 0 ? "" : ", \"rhum\":" + String(hum)) +
|
||||
", \"boot\":" + loopCount + "}";
|
||||
|
||||
if (WiFi.status() == WL_CONNECTED) {
|
||||
postToServer(payload);
|
||||
resetWatchdog();
|
||||
loopCount++;
|
||||
} else {
|
||||
Serial.println("WiFi Disconnected");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void countdown(int from) {
|
||||
debug("\n");
|
||||
while (from > 0) {
|
||||
debug(String(from--));
|
||||
debug(" ");
|
||||
delay(1000);
|
||||
}
|
||||
debug("\n");
|
||||
}
|
||||
|
||||
void resetWatchdog() {
|
||||
Serial.println("Watchdog reset");
|
||||
ag.watchdog.reset();
|
||||
}
|
||||
|
||||
bool wifiMangerClientConnected(void) {
|
||||
return WiFi.softAPgetStationNum() ? true : false;
|
||||
}
|
||||
|
||||
// Wifi Manager
|
||||
void connectToWifi() {
|
||||
HOTSPOT = "airgradient-" + String(getNormalizedMac());
|
||||
|
||||
wifiManager.setConfigPortalBlocking(false);
|
||||
wifiManager.setTimeout(WIFI_CONNECT_COUNTDOWN_MAX);
|
||||
|
||||
wifiManager.setAPCallback([](WiFiManager *obj) {
|
||||
/** This callback if wifi connnected failed and try to start configuration
|
||||
* portal */
|
||||
ledSmState = APP_SM_WIFI_MANAGER_MODE;
|
||||
});
|
||||
wifiManager.setSaveConfigCallback([]() {
|
||||
/** Wifi connected save the configuration */
|
||||
ledSmHandler(APP_SM_WIFI_MANAGER_STA_CONNECTED);
|
||||
});
|
||||
wifiManager.setSaveParamsCallback([]() {
|
||||
/** Wifi set connect: ssid, password */
|
||||
ledSmHandler(APP_SM_WIFI_MANAGER_STA_CONNECTING);
|
||||
});
|
||||
wifiManager.autoConnect(HOTSPOT.c_str(), WIFI_HOTSPOT_PASSWORD_DEFAULT);
|
||||
|
||||
xTaskCreate(
|
||||
[](void *obj) {
|
||||
while (wifiManager.getConfigPortalActive()) {
|
||||
wifiManager.process();
|
||||
}
|
||||
vTaskDelete(NULL);
|
||||
},
|
||||
"wifi_cfg", 4096, NULL, 10, NULL);
|
||||
|
||||
uint32_t stimer = millis();
|
||||
bool clientConnectChanged = false;
|
||||
while (wifiManager.getConfigPortalActive()) {
|
||||
if (WiFi.isConnected() == false) {
|
||||
if (ledSmState == APP_SM_WIFI_MANAGER_MODE) {
|
||||
uint32_t ms = (uint32_t)(millis() - stimer);
|
||||
if (ms >= 100) {
|
||||
stimer = millis();
|
||||
ledSmHandler(ledSmState);
|
||||
}
|
||||
}
|
||||
|
||||
/** Check for client connect to change led color */
|
||||
bool clientConnected = wifiMangerClientConnected();
|
||||
if (clientConnected != clientConnectChanged) {
|
||||
clientConnectChanged = clientConnected;
|
||||
if (clientConnectChanged) {
|
||||
ledSmHandler(APP_SM_WIFI_MAMAGER_PORTAL_ACTIVE);
|
||||
} else {
|
||||
ledSmHandler(APP_SM_WIFI_MANAGER_MODE);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Show display wifi connect result failed */
|
||||
if (WiFi.isConnected() == false) {
|
||||
ledSmHandler(APP_SM_WIFI_MANAGER_CONNECT_FAILED);
|
||||
} else {
|
||||
wifiHasConfig = true;
|
||||
}
|
||||
}
|
||||
|
||||
void debug(String msg) {
|
||||
if (DEBUG)
|
||||
Serial.print(msg);
|
||||
}
|
||||
|
||||
void debug(int msg) {
|
||||
if (DEBUG)
|
||||
Serial.print(msg);
|
||||
}
|
||||
|
||||
void debugln(String msg) {
|
||||
if (DEBUG)
|
||||
Serial.println(msg);
|
||||
}
|
||||
|
||||
void debugln(int msg) {
|
||||
if (DEBUG)
|
||||
Serial.println(msg);
|
||||
}
|
||||
|
||||
String getNormalizedMac() {
|
||||
String mac = WiFi.macAddress();
|
||||
mac.replace(":", "");
|
||||
mac.toLowerCase();
|
||||
return mac;
|
||||
}
|
||||
|
||||
void boardInit(void) {
|
||||
if (Wire.begin(ag.getI2cSdaPin(), ag.getI2cSclPin()) == false) {
|
||||
failedHandler("Init I2C failed");
|
||||
}
|
||||
|
||||
ag.watchdog.begin();
|
||||
|
||||
ag.button.begin();
|
||||
|
||||
ag.statusLed.begin();
|
||||
|
||||
if (ag.pms5003t_1.begin(Serial0) == false) {
|
||||
failedHandler("Init PMS5003T failed");
|
||||
}
|
||||
|
||||
if (ag.s8.begin(Serial1) == false) {
|
||||
failedHandler("Init SenseAirS8 failed");
|
||||
}
|
||||
|
||||
if (ag.sgp41.begin(Wire) == false) {
|
||||
failedHandler("Init SGP41 failed");
|
||||
}
|
||||
}
|
||||
|
||||
void failedHandler(String msg) {
|
||||
while (true) {
|
||||
Serial.println(msg);
|
||||
vTaskDelay(1000);
|
||||
}
|
||||
}
|
||||
|
||||
void updateServerConfigLoadTime(void) {
|
||||
serverConfigLoadTime = millis();
|
||||
if (serverConfigLoadTime == 0) {
|
||||
serverConfigLoadTime = 1;
|
||||
}
|
||||
}
|
||||
|
||||
void showConfig(void) {
|
||||
Serial.println("Server configuration: ");
|
||||
Serial.printf(" inF: %s\r\n", serverConfig.inF ? "true" : "false");
|
||||
Serial.printf(" inUSAQI: %s\r\n",
|
||||
serverConfig.inUSAQI ? "true" : "false");
|
||||
Serial.printf("useRGBLedBar: %d\r\n", (int)serverConfig.ledBarMode);
|
||||
Serial.printf(" Model: %.*s\r\n", sizeof(serverConfig.model),
|
||||
serverConfig.model);
|
||||
Serial.printf(" Mqtt Broker: %.*s\r\n", sizeof(serverConfig.mqttBroker),
|
||||
serverConfig.mqttBroker);
|
||||
}
|
||||
|
||||
void getServerConfig(void) {
|
||||
/** Only trigger load configuration again after pollServerConfigInterval sec
|
||||
*/
|
||||
if (serverConfigLoadTime) {
|
||||
uint32_t ms = (uint32_t)(millis() - serverConfigLoadTime);
|
||||
if (ms < pollServerConfigInterval) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
updateServerConfigLoadTime();
|
||||
|
||||
Serial.println("Trigger load server configuration");
|
||||
if (WiFi.status() != WL_CONNECTED) {
|
||||
Serial.println(
|
||||
"Ignore get server configuration because WIFI not connected");
|
||||
return;
|
||||
}
|
||||
|
||||
// WiFiClient wifiClient;
|
||||
HTTPClient httpClient;
|
||||
|
||||
String getUrl = "http://hw.airgradient.com/sensors/airgradient:" +
|
||||
String(getNormalizedMac()) + "/one/config";
|
||||
Serial.println("HttpClient get: " + getUrl);
|
||||
if (httpClient.begin(getUrl) == false) {
|
||||
Serial.println("HttpClient init failed");
|
||||
updateServerConfigLoadTime();
|
||||
return;
|
||||
}
|
||||
|
||||
int respCode = httpClient.GET();
|
||||
|
||||
/** get failure */
|
||||
if (respCode != 200) {
|
||||
Serial.printf("HttpClient get failed: %d\r\n", respCode);
|
||||
updateServerConfigLoadTime();
|
||||
httpClient.end();
|
||||
configFailed = true;
|
||||
return;
|
||||
}
|
||||
|
||||
String respContent = httpClient.getString();
|
||||
Serial.println("Server config: " + respContent);
|
||||
httpClient.end();
|
||||
|
||||
/** Parse JSON */
|
||||
JSONVar root = JSON.parse(respContent);
|
||||
if (JSON.typeof_(root) == "undefined") {
|
||||
Serial.println("Server configura JSON invalid");
|
||||
updateServerConfigLoadTime();
|
||||
configFailed = true;
|
||||
return;
|
||||
}
|
||||
configFailed = false;
|
||||
|
||||
/** Get "country" */
|
||||
bool inF = serverConfig.inF;
|
||||
if (JSON.typeof_(root["country"]) == "string") {
|
||||
String country = root["country"];
|
||||
if (country == "US") {
|
||||
inF = true;
|
||||
} else {
|
||||
inF = false;
|
||||
}
|
||||
}
|
||||
|
||||
/** Get "pmStandard" */
|
||||
bool inUSAQI = serverConfig.inUSAQI;
|
||||
if (JSON.typeof_(root["pmStandard"]) == "string") {
|
||||
String standard = root["pmStandard"];
|
||||
if (standard == "ugm3") {
|
||||
inUSAQI = false;
|
||||
} else {
|
||||
inUSAQI = true;
|
||||
}
|
||||
}
|
||||
|
||||
/** Get CO2 "co2CalibrationRequested" */
|
||||
co2CalibrationRequest = false;
|
||||
if (JSON.typeof_(root["co2CalibrationRequested"]) == "boolean") {
|
||||
co2CalibrationRequest = root["co2CalibrationRequested"];
|
||||
}
|
||||
|
||||
/** get "model" */
|
||||
String model = "";
|
||||
if (JSON.typeof_(root["model"]) == "string") {
|
||||
String _model = root["model"];
|
||||
model = _model;
|
||||
}
|
||||
|
||||
/** get "mqttBrokerUrl" */
|
||||
String mqtt = "";
|
||||
if (JSON.typeof_(root["mqttBrokerUrl"]) == "string") {
|
||||
String _mqtt = root["mqttBrokerUrl"];
|
||||
mqtt = _mqtt;
|
||||
}
|
||||
|
||||
if (inF != serverConfig.inF) {
|
||||
serverConfig.inF = inF;
|
||||
}
|
||||
if (inUSAQI != serverConfig.inUSAQI) {
|
||||
serverConfig.inUSAQI = inUSAQI;
|
||||
}
|
||||
if (model.length()) {
|
||||
if (model != String(serverConfig.model)) {
|
||||
memset(serverConfig.model, 0, sizeof(serverConfig.model));
|
||||
memcpy(serverConfig.model, model.c_str(), model.length());
|
||||
}
|
||||
}
|
||||
if (mqtt.length()) {
|
||||
if (mqtt != String(serverConfig.mqttBroker)) {
|
||||
memset(serverConfig.mqttBroker, 0, sizeof(serverConfig.mqttBroker));
|
||||
memcpy(serverConfig.mqttBroker, mqtt.c_str(), mqtt.length());
|
||||
}
|
||||
}
|
||||
|
||||
/** Show server configuration */
|
||||
showConfig();
|
||||
|
||||
/** Calibration */
|
||||
if (co2CalibrationRequest) {
|
||||
co2Calibration();
|
||||
}
|
||||
}
|
||||
|
||||
void co2Calibration(void) {
|
||||
/** Count down for co2CalibCountdown secs */
|
||||
for (int i = 0; i < co2CalibCountdown; i++) {
|
||||
Serial.printf("Start CO2 calib after %d sec\r\n", co2CalibCountdown - i);
|
||||
delay(1000);
|
||||
}
|
||||
|
||||
if (ag.s8.setBaselineCalibration()) {
|
||||
Serial.println("Calibration success");
|
||||
delay(1000);
|
||||
Serial.println("Wait for calib finish...");
|
||||
int count = 0;
|
||||
while (ag.s8.isBaseLineCalibrationDone() == false) {
|
||||
delay(1000);
|
||||
count++;
|
||||
}
|
||||
Serial.printf("Calib finish after %d sec\r\n", count);
|
||||
delay(2000);
|
||||
} else {
|
||||
Serial.println("Calibration failure!!!");
|
||||
delay(2000);
|
||||
}
|
||||
}
|
||||
|
||||
void ledSmHandler(int sm) {
|
||||
if (sm > APP_SM_NORMAL) {
|
||||
return;
|
||||
}
|
||||
|
||||
ledSmState = sm;
|
||||
switch (sm) {
|
||||
case APP_SM_WIFI_MANAGER_MODE: {
|
||||
ag.statusLed.setToggle();
|
||||
break;
|
||||
}
|
||||
case APP_SM_WIFI_MAMAGER_PORTAL_ACTIVE: {
|
||||
ag.statusLed.setOn();
|
||||
break;
|
||||
}
|
||||
case APP_SM_WIFI_MANAGER_STA_CONNECTING: {
|
||||
ag.statusLed.setOff();
|
||||
break;
|
||||
}
|
||||
case APP_SM_WIFI_MANAGER_STA_CONNECTED: {
|
||||
ag.statusLed.setOff();
|
||||
break;
|
||||
}
|
||||
case APP_SM_WIFI_OK_SERVER_CONNECTING: {
|
||||
ag.statusLed.setOff();
|
||||
break;
|
||||
}
|
||||
case APP_SM_WIFI_OK_SERVER_CONNNECTED: {
|
||||
ag.statusLed.setOff();
|
||||
ag.statusLed.setOn();
|
||||
delay(50);
|
||||
ag.statusLed.setOff();
|
||||
delay(950);
|
||||
ag.statusLed.setOn();
|
||||
delay(50);
|
||||
ag.statusLed.setOff();
|
||||
break;
|
||||
}
|
||||
case APP_SM_WIFI_MANAGER_CONNECT_FAILED: {
|
||||
ag.statusLed.setOff();
|
||||
for (int j = 0; j < 3; j++) {
|
||||
for (int i = 0; i < 3 * 2; i++) {
|
||||
ag.statusLed.setToggle();
|
||||
delay(100);
|
||||
}
|
||||
delay(2000);
|
||||
}
|
||||
ag.statusLed.setOff();
|
||||
break;
|
||||
}
|
||||
case APP_SM_WIFI_OK_SERVER_CONNECT_FAILED: {
|
||||
ag.statusLed.setOff();
|
||||
for (int j = 0; j < 3; j++) {
|
||||
for (int i = 0; i < 4 * 2; i++) {
|
||||
ag.statusLed.setToggle();
|
||||
delay(100);
|
||||
}
|
||||
delay(2000);
|
||||
}
|
||||
ag.statusLed.setOff();
|
||||
break;
|
||||
}
|
||||
case APP_SM_WIFI_OK_SERVER_OK_SENSOR_CONFIG_FAILED: {
|
||||
ag.statusLed.setOff();
|
||||
for (int j = 0; j < 3; j++) {
|
||||
for (int i = 0; i < 5 * 2; i++) {
|
||||
ag.statusLed.setToggle();
|
||||
delay(100);
|
||||
}
|
||||
delay(2000);
|
||||
}
|
||||
ag.statusLed.setOff();
|
||||
break;
|
||||
}
|
||||
case APP_SM_WIFI_LOST: {
|
||||
ag.statusLed.setOff();
|
||||
break;
|
||||
}
|
||||
case APP_SM_SERVER_LOST: {
|
||||
ag.statusLed.setOff();
|
||||
break;
|
||||
}
|
||||
case APP_SM_SENSOR_CONFIG_FAILED: {
|
||||
ag.statusLed.setOff();
|
||||
break;
|
||||
}
|
||||
case APP_SM_NORMAL: {
|
||||
ag.statusLed.setOff();
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
@ -2,41 +2,47 @@
|
||||
This is sample code for the AirGradient library with a minimal implementation to read CO2 values from the SenseAir S8 sensor.
|
||||
|
||||
CC BY-SA 4.0 Attribution-ShareAlike 4.0 International License
|
||||
|
||||
*/
|
||||
|
||||
#include <AirGradient.h>
|
||||
|
||||
#ifdef ESP8266
|
||||
AirGradient ag = AirGradient(BOARD_DIY_PRO_INDOOR_V4_2);
|
||||
// AirGradient ag = AirGradient(BOARD_DIY_BASIC_KIT);
|
||||
AirGradient ag = AirGradient(DIY_BASIC);
|
||||
#else
|
||||
// AirGradient ag = AirGradient(BOARD_ONE_INDOOR_MONITOR_V9_0);
|
||||
AirGradient ag = AirGradient(BOARD_OUTDOOR_MONITOR_V1_3);
|
||||
/** Create airgradient instance for 'OPEN_AIR_OUTDOOR' board */
|
||||
AirGradient ag = AirGradient(OPEN_AIR_OUTDOOR);
|
||||
#endif
|
||||
|
||||
void failedHandler(String msg);
|
||||
|
||||
void setup() {
|
||||
void setup()
|
||||
{
|
||||
Serial.begin(115200);
|
||||
|
||||
/** Init CO2 sensor */
|
||||
#ifdef ESP8266
|
||||
if (ag.s8.begin(&Serial) == false) {
|
||||
if (ag.s8.begin(&Serial) == false)
|
||||
{
|
||||
#else
|
||||
if (ag.s8.begin(Serial1) == false) {
|
||||
if (ag.s8.begin(Serial0) == false)
|
||||
{
|
||||
#endif
|
||||
failedHandler("SenseAir S8 init failed");
|
||||
}
|
||||
}
|
||||
|
||||
void loop() {
|
||||
int CO2 = ag.s8.getCo2();
|
||||
Serial.printf("CO2: %d\r\n", CO2);
|
||||
void loop()
|
||||
{
|
||||
int co2Ppm = ag.s8.getCo2();
|
||||
Serial.printf("CO2: %d\r\n", co2Ppm);
|
||||
delay(5000);
|
||||
}
|
||||
|
||||
void failedHandler(String msg) {
|
||||
while (true) {
|
||||
void failedHandler(String msg)
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
Serial.println(msg);
|
||||
delay(1000);
|
||||
}
|
||||
|
@ -1,369 +0,0 @@
|
||||
#include <AirGradient.h>
|
||||
#include <HardwareSerial.h>
|
||||
#include <Wire.h>
|
||||
|
||||
/**
|
||||
* AirGradient use ESP32C3 has default Serial0 use for PMS5003, to print log
|
||||
* should use esp-hal-log instead.
|
||||
*/
|
||||
#include <esp32-hal-log.h>
|
||||
|
||||
/**
|
||||
* @brief Define test board
|
||||
*/
|
||||
#define TEST_BOARD_OUTDOOR_MONITOR_V1_3 0
|
||||
#define TEST_BOARD_ONE_INDOOR_MONITOR_V9_0 1
|
||||
/**
|
||||
* @brief Define test sensor
|
||||
*/
|
||||
#define TEST_SENSOR_SenseAirS8 0
|
||||
#define TEST_SENSOR_SHT4x 0
|
||||
#define TEST_SENSOR_SGP4x 0
|
||||
#define TEST_SWITCH 0
|
||||
#define TEST_OLED 0
|
||||
#if TEST_BOARD_OUTDOOR_MONITOR_V1_3
|
||||
#define TEST_STATUS_LED 0
|
||||
#define TEST_PMS5003T 1
|
||||
#endif
|
||||
#define TEST_WATCHDOG 1
|
||||
|
||||
#if TEST_BOARD_ONE_INDOOR_MONITOR_V9_0
|
||||
#define TEST_LED_BAR 1
|
||||
#define TEST_SENSOR_PMS5003 0
|
||||
#endif
|
||||
|
||||
#if TEST_BOARD_OUTDOOR_MONITOR_V1_3
|
||||
AirGradient ag(BOARD_OUTDOOR_MONITOR_V1_3);
|
||||
#elif TEST_BOARD_ONE_INDOOR_MONITOR_V9_0
|
||||
AirGradient ag(BOARD_ONE_INDOOR_MONITOR_V9_0);
|
||||
#else
|
||||
#error "Must enable board test
|
||||
#endif
|
||||
|
||||
void setup() {
|
||||
|
||||
/** Print All AirGradient board define */
|
||||
printBoardDef(NULL);
|
||||
|
||||
#if TEST_SENSOR_SenseAirS8
|
||||
/** Cause Serial is use default for PMS, CO2S8 should be use Serial 1
|
||||
* Serial 1 will be init by SenseAirS8 don't need to init any more on user
|
||||
* code
|
||||
*/
|
||||
if (ag.s8.begin(Serial1)) {
|
||||
log_i("CO2S8 sensor init success");
|
||||
} else {
|
||||
log_i("CO2S8 sensor init failure");
|
||||
}
|
||||
|
||||
log_i("Start baseline calib");
|
||||
if (ag.s8.setBaselineCalibration()) {
|
||||
log_i("Calib success");
|
||||
} else {
|
||||
log_e("Calib failure");
|
||||
}
|
||||
delay(5000); // Wait for calib done
|
||||
#endif
|
||||
|
||||
#if TEST_SENSOR_PMS5003
|
||||
if (ag.pms5003.begin(Serial0)) {
|
||||
log_i("PMS5003 sensor init success");
|
||||
} else {
|
||||
log_i("PMS5003 sensor init failure");
|
||||
}
|
||||
#endif
|
||||
|
||||
#if TEST_PMS5003T
|
||||
/**
|
||||
* @brief PMS5003T_1 alway connect to Serial (esp32c3 RXD0, RXD0)
|
||||
*/
|
||||
if (ag.pms5003t_1.begin(Serial)) {
|
||||
log_i("PMS5003T_1 sensor init success");
|
||||
} else {
|
||||
log_i("PMS5003T_1 sensor init failure");
|
||||
}
|
||||
|
||||
// TODO Only test without senseair s8 because it's share the UART bus
|
||||
#if TEST_SENSOR_SenseAirS8 == 0
|
||||
if (ag.pms5003t_2.begin(Serial1)) {
|
||||
log_i("PMS5003T_2 sensor init success");
|
||||
} else {
|
||||
log_i("PMS5003T_2 sensor init failure");
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if TEST_SENSOR_SHT4x || TEST_SENSOR_SGP4x || TEST_OLED
|
||||
Wire.begin(ag.getI2cSdaPin(), ag.getI2cSclPin());
|
||||
#endif
|
||||
|
||||
#if TEST_SENSOR_SHT4x
|
||||
|
||||
if (ag.sht.begin(Wire)) {
|
||||
log_i("SHT init success");
|
||||
} else {
|
||||
log_i("SHT init failed");
|
||||
}
|
||||
#endif
|
||||
|
||||
#if TEST_SENSOR_SGP4x
|
||||
if (ag.sgp41.begin(Wire)) {
|
||||
log_i("SGP init success");
|
||||
} else {
|
||||
log_e("SGP init failure");
|
||||
}
|
||||
#endif
|
||||
|
||||
#if TEST_LED
|
||||
led.begin();
|
||||
#endif
|
||||
|
||||
#if TEST_SWITCH
|
||||
ag.button.begin();
|
||||
#endif
|
||||
|
||||
#if TEST_OLED
|
||||
ag.display.begin(Wire);
|
||||
ag.display.setTextSize(1);
|
||||
ag.display.setCursor(0, 0);
|
||||
ag.display.setTextColor(1);
|
||||
ag.display.setText("180s to connect to wifi hostpost AC-xxxxx");
|
||||
ag.display.show();
|
||||
#endif
|
||||
|
||||
#if TEST_STATUS_LED
|
||||
ag.statusLed.begin();
|
||||
#endif
|
||||
|
||||
#if TEST_LED_BAR
|
||||
ag.ledBar.begin();
|
||||
#endif
|
||||
|
||||
#if TEST_WATCHDOG
|
||||
ag.watchdog.begin();
|
||||
#endif
|
||||
}
|
||||
|
||||
void loop() {
|
||||
uint32_t ms;
|
||||
#if TEST_SENSOR_SenseAirS8
|
||||
static uint32_t lastTime = 0;
|
||||
|
||||
/** Wait for sensor ready */
|
||||
ms = (uint32_t)(millis() - lastTime);
|
||||
if (ms >= 1000) {
|
||||
lastTime = millis();
|
||||
log_i("CO2: %d (PPM)", ag.s8.getCo2());
|
||||
}
|
||||
#endif
|
||||
|
||||
#if TEST_SENSOR_PMS5003
|
||||
static uint32_t pmsTime = 0;
|
||||
ms = (uint32_t)(millis() - pmsTime);
|
||||
|
||||
if (ms >= 1000) {
|
||||
pmsTime = millis();
|
||||
if (ag.pms5003.readData()) {
|
||||
log_i("Passive mode PM 1.0 (ug/m3): %d", ag.pms5003.getPm10Ae());
|
||||
log_i("Passive mode PM 2.5 (ug/m3): %d", ag.pms5003.getPm25Ae());
|
||||
log_i("Passive mode PM 10.0 (ug/m3): %d", ag.pms5003.getPm10Ae());
|
||||
} else {
|
||||
log_i("PMS sensor read failure");
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#if TEST_PMS5003T
|
||||
static uint32_t pmsTime = 0;
|
||||
ms = (uint32_t)(millis() - pmsTime);
|
||||
|
||||
if (ms >= 1000) {
|
||||
pmsTime = millis();
|
||||
if (ag.pms5003t_1.readData()) {
|
||||
log_i("PMS5003_1 PM 1.0 (ug/m3): %d", ag.pms5003t_1.getPm10Ae());
|
||||
log_i("PMS5003_1 PM 2.5 (ug/m3): %d", ag.pms5003t_1.getPm25Ae());
|
||||
log_i("PMS5003_1 PM 10.0 (ug/m3): %d", ag.pms5003t_1.getPm10Ae());
|
||||
log_i("PMS5003_1 PM 3.0 (ug/m3): %d",
|
||||
ag.pms5003t_1.getPm03ParticleCount());
|
||||
log_i("Temperature : %02f °C",
|
||||
ag.pms5003t_1.getTemperature());
|
||||
log_i("Humidity : %02f %%",
|
||||
ag.pms5003t_1.getRelativeHumidity());
|
||||
} else {
|
||||
log_i("PMS5003_1 sensor read failure");
|
||||
}
|
||||
|
||||
if (ag.pms5003t_2.readData()) {
|
||||
log_i("PMS5003_2 PM 1.0 (ug/m3): %d", ag.pms5003t_2.getPm10Ae());
|
||||
log_i("PMS5003_2 PM 2.5 (ug/m3): %d", ag.pms5003t_2.getPm25Ae());
|
||||
log_i("PMS5003_2 PM 10.0 (ug/m3): %d", ag.pms5003t_2.getPm10Ae());
|
||||
log_i("PMS5003_2 PM 3.0 (ug/m3): %d",
|
||||
ag.pms5003t_2.getPm03ParticleCount());
|
||||
// log_i("Temperature : %02f °C",
|
||||
// ag.pms5003t_1.getTemperature());
|
||||
// log_i("Humidity : %02f %%",
|
||||
// ag.pms5003t_1.getRelativeHumidity());
|
||||
} else {
|
||||
log_i("PMS5003_2 sensor read failure");
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#if TEST_SENSOR_SHT4x
|
||||
/**
|
||||
* @brief Get SHT sensor data each 1sec
|
||||
*
|
||||
*/
|
||||
static uint32_t shtTime = 0;
|
||||
ms = (uint32_t)(millis() - shtTime);
|
||||
if (ms >= 1000) {
|
||||
shtTime = millis();
|
||||
log_i("Get sht temperature: %0.2f (degree celsius)",
|
||||
ag.sht.getTemperature());
|
||||
log_i("Get sht temperature: %0.2f (%%)", ag.sht.getRelativeHumidity());
|
||||
}
|
||||
#endif
|
||||
|
||||
#if TEST_SENSOR_SGP4x
|
||||
static uint32_t sgpTime;
|
||||
ms = (uint32_t)(millis() - sgpTime);
|
||||
if (ms >= 1000) {
|
||||
sgpTime = millis();
|
||||
uint16_t rawVOC;
|
||||
log_i("Get TVOC: %d", ag.sgp41.getTvocIndex());
|
||||
log_i("Get NOx: %d", ag.sgp41.getNoxIndex());
|
||||
}
|
||||
#endif
|
||||
|
||||
#if TEST_LED
|
||||
static uint32_t ledTime;
|
||||
#if TEST_BOARD_OUTDOOR_MONITOR_V1_3
|
||||
// ms = (uint32_t)(millis() - ledTime);
|
||||
// if(ms >= 500)
|
||||
// {
|
||||
// ledTime = millis();
|
||||
// led.ledToggle();
|
||||
// }
|
||||
#elif TEST_BOARD_ONE_INDOOR_MONITOR_V9_0
|
||||
|
||||
static int ledIndex;
|
||||
static int ledIndexOld;
|
||||
ms = (uint32_t)(millis() - ledTime);
|
||||
if (ms >= 50) {
|
||||
ledTime = millis();
|
||||
if (ledIndex == ledIndexOld) {
|
||||
led.ledOff();
|
||||
} else {
|
||||
// Turn last LED off
|
||||
led.ledSetColor(0, 0, 0, ledIndexOld);
|
||||
}
|
||||
|
||||
// Turn new led ON
|
||||
led.ledSetColor(255, 0, 0, ledIndex);
|
||||
ledIndexOld = ledIndex;
|
||||
ledIndex++;
|
||||
if (ledIndex >= led.getNumberOfLed()) {
|
||||
ledIndex = 0;
|
||||
}
|
||||
}
|
||||
#else
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
#if TEST_SWITCH
|
||||
static PushButton::State stateOld = PushButton::State::BUTTON_RELEASED;
|
||||
PushButton::State state = ag.button.getState();
|
||||
if (state != stateOld) {
|
||||
stateOld = state;
|
||||
log_i("Button state changed: %s", ag.button.toString(state).c_str());
|
||||
if (state == PushButton::State::BUTTON_PRESSED) {
|
||||
ag.statusLed.setOn();
|
||||
} else {
|
||||
ag.statusLed.setOff();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#if TEST_LED_BAR
|
||||
static uint32_t ledTime;
|
||||
static uint8_t ledNum = 0;
|
||||
static uint8_t ledIndex = 0;
|
||||
static uint8_t ledStep = 0;
|
||||
static bool ledOn = false;
|
||||
|
||||
if (ledNum == 0) {
|
||||
ledNum = ag.ledBar.getNumberOfLed();
|
||||
log_i("Get number of led: %d", ledNum);
|
||||
if (ledNum) {
|
||||
ag.ledBar.setBrighness(0xff);
|
||||
for (int i = 0; i < ledNum; i++) {
|
||||
// ag.ledBar.setColor(0xff, 0xff, 0xff, i);
|
||||
// ag.ledBar.setColor(204, 136, 153, i);
|
||||
// ag.ledBar.setColor(204, 0, 0, i);
|
||||
// ag.ledBar.setColor(204, 100, 153, i);
|
||||
ag.ledBar.setColor(0, 136, 255, i);
|
||||
}
|
||||
ag.ledBar.show();
|
||||
}
|
||||
} else {
|
||||
ms = (uint32_t)(millis() - ledTime);
|
||||
if (ms >= 500) {
|
||||
ledTime = millis();
|
||||
switch (ledStep) {
|
||||
case 0: {
|
||||
ag.ledBar.setColor(255, 0, 0, ledIndex);
|
||||
ledIndex++;
|
||||
if (ledIndex >= ledNum) {
|
||||
ag.ledBar.setColor(0, 0, 0);
|
||||
ledIndex = 0;
|
||||
ledStep = 1;
|
||||
}
|
||||
ag.ledBar.show();
|
||||
break;
|
||||
}
|
||||
case 1: {
|
||||
ledIndex++;
|
||||
if (ledIndex >= ledNum) {
|
||||
ag.ledBar.setColor(255, 0, 0);
|
||||
ag.ledBar.show();
|
||||
ledIndex = ledNum - 1;
|
||||
ledStep = 2;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 2: {
|
||||
if (ledOn) {
|
||||
ag.ledBar.setColor(255, 0, 0);
|
||||
} else {
|
||||
ag.ledBar.setColor(0, 0, 0);
|
||||
}
|
||||
ledOn = !ledOn;
|
||||
ag.ledBar.show();
|
||||
|
||||
ledIndex--;
|
||||
if (ledIndex == 0) {
|
||||
ag.ledBar.setColor(0, 0, 0);
|
||||
ag.ledBar.show();
|
||||
ledStep = 0;
|
||||
ledIndex = 0;
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#if TEST_WATCHDOG
|
||||
static uint32_t wdgTime;
|
||||
ms = (uint32_t)(millis() - wdgTime);
|
||||
if (ms >= (1000 * 60)) {
|
||||
wdgTime = millis();
|
||||
/** Reset watchdog reach 1 minutes */
|
||||
ag.watchdog.reset();
|
||||
}
|
||||
#endif
|
||||
}
|
@ -1,164 +0,0 @@
|
||||
#include <AirGradient.h>
|
||||
#include <Wire.h>
|
||||
|
||||
/**
|
||||
* @brief Define test board
|
||||
*/
|
||||
#define TEST_BOARD_DIY_BASIC_KIT 0
|
||||
#define TEST_BOARD_DIY_PRO_INDOOR_V4_2 1
|
||||
|
||||
/**
|
||||
* @brief Define test sensor
|
||||
*/
|
||||
#define TEST_SENSOR_SenseAirS8 0
|
||||
#define TEST_SENSOR_PMS5003 0
|
||||
#define TEST_SENSOR_SHT4x 0
|
||||
#define TEST_SENSOR_SGP4x 1
|
||||
#define TEST_SWITCH 0
|
||||
#define TEST_OLED 0
|
||||
|
||||
#if TEST_BOARD_DIY_BASIC_KIT
|
||||
AirGradient ag(BOARD_DIY_BASIC_KIT);
|
||||
#elif TEST_BOARD_DIY_PRO_INDOOR_V4_2
|
||||
AirGradient ag(BOARD_DIY_PRO_INDOOR_V4_2);
|
||||
#else
|
||||
#error "Board test not defined"
|
||||
#endif
|
||||
|
||||
void setup() {
|
||||
Serial.begin(115200);
|
||||
|
||||
/** Print All AirGradient board define */
|
||||
printBoardDef(&Serial);
|
||||
|
||||
#if TEST_SENSOR_SenseAirS8
|
||||
if (ag.s8.begin(&Serial) == true) {
|
||||
Serial.println("CO2S8 sensor init success");
|
||||
} else {
|
||||
Serial.println("CO2S8 sensor init failure");
|
||||
}
|
||||
|
||||
if (ag.s8.setBaselineCalibration()) {
|
||||
Serial.println("Manual calib success");
|
||||
} else {
|
||||
Serial.println("Manual calib failure");
|
||||
}
|
||||
delay(5000);
|
||||
#endif
|
||||
|
||||
#if TEST_SENSOR_PMS5003
|
||||
if (ag.pms5003.begin(&Serial) == true) {
|
||||
Serial.println("PMS5003 sensor init success");
|
||||
} else {
|
||||
Serial.println("PMS5003 sensor init failure");
|
||||
}
|
||||
#endif
|
||||
|
||||
#if TEST_SENSOR_SHT4x || TEST_SENSOR_SGP4x || TEST_OLED
|
||||
Wire.begin(ag.getI2cSdaPin(), ag.getI2cSclPin());
|
||||
#endif
|
||||
|
||||
#if TEST_SENSOR_SHT4x
|
||||
if (ag.sht.begin(Wire, Serial)) {
|
||||
Serial.println("SHT init success");
|
||||
} else {
|
||||
Serial.println("SHT init failed");
|
||||
}
|
||||
#endif
|
||||
|
||||
#if TEST_SENSOR_SGP4x
|
||||
if (ag.sgp41.begin(Wire, Serial)) {
|
||||
Serial.println("SGP init succses");
|
||||
} else {
|
||||
Serial.println("SGP init failure");
|
||||
}
|
||||
#endif
|
||||
|
||||
#if TEST_SWITCH
|
||||
ag.button.begin(Serial);
|
||||
#endif
|
||||
|
||||
#if TEST_OLED
|
||||
ag.display.begin(Wire, Serial);
|
||||
ag.display.setTextSize(1);
|
||||
ag.display.setCursor(0, 0);
|
||||
ag.display.setTextColor(1);
|
||||
ag.display.setText("Hello");
|
||||
ag.display.show();
|
||||
#endif
|
||||
}
|
||||
|
||||
void loop() {
|
||||
uint32_t ms;
|
||||
#if TEST_SENSOR_SenseAirS8
|
||||
static uint32_t lastTime = 0;
|
||||
|
||||
/** Wait for sensor ready */
|
||||
// if(co2s8.isReady())
|
||||
// {
|
||||
// Get sensor data each 1sec
|
||||
ms = (uint32_t)(millis() - lastTime);
|
||||
if (ms >= 1000) {
|
||||
lastTime = millis();
|
||||
Serial.printf("CO2: %d (PMM)\r\n", ag.s8.getCo2());
|
||||
}
|
||||
// }
|
||||
#endif
|
||||
|
||||
#if TEST_SENSOR_PMS5003
|
||||
static uint32_t pmsTime = 0;
|
||||
ms = (uint32_t)(millis() - pmsTime);
|
||||
if (ms >= 1000) {
|
||||
pmsTime = millis();
|
||||
if (ag.pms5003.readData()) {
|
||||
Serial.printf("Passive mode PM 1.0 (ug/m3): %d\r\n",
|
||||
ag.pms5003.getPm01Ae());
|
||||
Serial.printf("Passive mode PM 2.5 (ug/m3): %d\r\n",
|
||||
ag.pms5003.getPm25Ae());
|
||||
Serial.printf("Passive mode PM 10.5 (ug/m3): %d\r\n",
|
||||
ag.pms5003.getPm10Ae());
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#if TEST_SENSOR_SHT4x
|
||||
/**
|
||||
* @brief Get SHT sensor data each 1sec
|
||||
*
|
||||
*/
|
||||
static uint32_t shtTime = 0;
|
||||
ms = (uint32_t)(millis() - shtTime);
|
||||
if (ms >= 1000) {
|
||||
shtTime = millis();
|
||||
float temperature, humidity;
|
||||
Serial.printf("SHT Temperature: %f, Humidity: %f\r\n",
|
||||
ag.sht.getTemperature(), ag.sht.getRelativeHumidity());
|
||||
}
|
||||
#endif
|
||||
|
||||
#if TEST_SENSOR_SGP4x
|
||||
static uint32_t sgpTime;
|
||||
ms = (uint32_t)(millis() - sgpTime);
|
||||
|
||||
/***
|
||||
* Must call this task on loop and avoid delay on loop over 1000 ms
|
||||
*/
|
||||
ag.sgp41.handle();
|
||||
|
||||
if (ms >= 1000) {
|
||||
sgpTime = millis();
|
||||
Serial.printf("SGP TVOC: %d, NOx: %d\r\n", ag.sgp41.getTvocIndex(),
|
||||
ag.sgp41.getNoxIndex());
|
||||
}
|
||||
#endif
|
||||
|
||||
#if TEST_SWITCH
|
||||
static PushButton::State stateOld = PushButton::State::BUTTON_RELEASED;
|
||||
PushButton::State state = ag.button.getState();
|
||||
if (state != stateOld) {
|
||||
stateOld = state;
|
||||
Serial.printf("Button state changed: %s\r\n",
|
||||
ag.button.toString(state).c_str());
|
||||
}
|
||||
#endif
|
||||
}
|
@ -1,5 +1,6 @@
|
||||
/*
|
||||
This is sample code for the AirGradient library with a minimal implementation to read PM values from the Plantower sensor.
|
||||
This is sample code for the AirGradient library with a minimal implementation
|
||||
to read PM values from the Plantower sensor.
|
||||
|
||||
CC BY-SA 4.0 Attribution-ShareAlike 4.0 International License
|
||||
*/
|
||||
@ -7,11 +8,10 @@ CC BY-SA 4.0 Attribution-ShareAlike 4.0 International License
|
||||
#include <AirGradient.h>
|
||||
|
||||
#ifdef ESP8266
|
||||
AirGradient ag = AirGradient(BOARD_DIY_PRO_INDOOR_V4_2);
|
||||
// AirGradient ag = AirGradient(BOARD_DIY_BASIC_KIT);
|
||||
AirGradient ag = AirGradient(DIY_BASIC);
|
||||
#else
|
||||
// AirGradient ag = AirGradient(BOARD_ONE_INDOOR_MONITOR_V9_0);
|
||||
AirGradient ag = AirGradient(BOARD_OUTDOOR_MONITOR_V1_3);
|
||||
AirGradient ag = AirGradient(ONE_INDOOR);
|
||||
// AirGradient ag = AirGradient(OPEN_AIR_OUTDOOR);
|
||||
#endif
|
||||
|
||||
void failedHandler(String msg);
|
||||
@ -19,46 +19,72 @@ void failedHandler(String msg);
|
||||
void setup() {
|
||||
Serial.begin(115200);
|
||||
#ifdef ESP8266
|
||||
if(ag.pms5003.begin(&Serial) == false) {
|
||||
if (ag.pms5003.begin(&Serial) == false) {
|
||||
failedHandler("Init PMS5003 failed");
|
||||
}
|
||||
#else
|
||||
if (ag.getBoardType() == BOARD_OUTDOOR_MONITOR_V1_3) {
|
||||
if(ag.pms5003t_1.begin(Serial0) == false) {
|
||||
if (ag.getBoardType() == OPEN_AIR_OUTDOOR) {
|
||||
if (ag.pms5003t_1.begin(Serial0) == false) {
|
||||
failedHandler("Init PMS5003T failed");
|
||||
}
|
||||
} else {
|
||||
if(ag.pms5003.begin(Serial0) == false) {
|
||||
if (ag.pms5003.begin(Serial0) == false) {
|
||||
failedHandler("Init PMS5003T failed");
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
uint32_t lastRead = 0;
|
||||
void loop() {
|
||||
int PM2;
|
||||
#ifdef ESP8266
|
||||
PM2 = ag.pms5003.getPm25Ae();
|
||||
Serial.printf("PM2.5 in ug/m3: %d\r\n", PM2);
|
||||
Serial.printf("PM2.5 in US AQI: %d\r\n", ag.pms5003.convertPm25ToUsAqi(PM2));
|
||||
#else
|
||||
if (ag.getBoardType() == BOARD_OUTDOOR_MONITOR_V1_3) {
|
||||
PM2 = ag.pms5003t_1.getPm25Ae();
|
||||
} else {
|
||||
PM2 = ag.pms5003.getPm25Ae();
|
||||
}
|
||||
|
||||
Serial.printf("PM2.5 in ug/m3: %d\r\n", PM2);
|
||||
if (ag.getBoardType() == BOARD_OUTDOOR_MONITOR_V1_3) {
|
||||
Serial.printf("PM2.5 in US AQI: %d\r\n",
|
||||
ag.pms5003t_1.convertPm25ToUsAqi(PM2));
|
||||
} else {
|
||||
Serial.printf("PM2.5 in US AQI: %d\r\n",
|
||||
ag.pms5003.convertPm25ToUsAqi(PM2));
|
||||
}
|
||||
#endif
|
||||
bool readResul = false;
|
||||
|
||||
delay(5000);
|
||||
uint32_t ms = (uint32_t)(millis() - lastRead);
|
||||
if (ms >= 5000) {
|
||||
lastRead = millis();
|
||||
#ifdef ESP8266
|
||||
if (ag.pms5003.connected()) {
|
||||
PM2 = ag.pms5003.getPm25Ae();
|
||||
Serial.printf("PM2.5 in ug/m3: %d\r\n", PM2);
|
||||
Serial.printf("PM2.5 in US AQI: %d\r\n",
|
||||
ag.pms5003.convertPm25ToUsAqi(PM2));
|
||||
} else {
|
||||
Serial.println("PMS sensor failed");
|
||||
}
|
||||
#else
|
||||
if (ag.getBoardType() == OPEN_AIR_OUTDOOR) {
|
||||
if (ag.pms5003t_1.connected()) {
|
||||
PM2 = ag.pms5003t_1.getPm25Ae();
|
||||
readResul = true;
|
||||
}
|
||||
} else {
|
||||
if (ag.pms5003.connected()) {
|
||||
PM2 = ag.pms5003.getPm25Ae();
|
||||
readResul = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (readResul) {
|
||||
Serial.printf("PM2.5 in ug/m3: %d\r\n", PM2);
|
||||
if (ag.getBoardType() == OPEN_AIR_OUTDOOR) {
|
||||
Serial.printf("PM2.5 in US AQI: %d\r\n",
|
||||
ag.pms5003t_1.convertPm25ToUsAqi(PM2));
|
||||
} else {
|
||||
Serial.printf("PM2.5 in US AQI: %d\r\n",
|
||||
ag.pms5003.convertPm25ToUsAqi(PM2));
|
||||
}
|
||||
} else {
|
||||
Serial.println("PMS sensor failed");
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
if (ag.getBoardType() == OPEN_AIR_OUTDOOR) {
|
||||
ag.pms5003t_1.handle();
|
||||
} else {
|
||||
ag.pms5003.handle();
|
||||
}
|
||||
}
|
||||
|
||||
void failedHandler(String msg) {
|
||||
|
39
examples/TestSht/TestSht.ino
Normal file
39
examples/TestSht/TestSht.ino
Normal file
@ -0,0 +1,39 @@
|
||||
#include <AirGradient.h>
|
||||
|
||||
#if defined(ESP8266)
|
||||
AirGradient ag(DIY_BASIC);
|
||||
#else
|
||||
AirGradient ag(ONE_INDOOR);
|
||||
#endif
|
||||
|
||||
void failedHandler(String msg);
|
||||
|
||||
void setup() {
|
||||
Serial.begin(115200);
|
||||
Serial.println("Hello");
|
||||
Wire.begin(ag.getI2cSdaPin(), ag.getI2cSclPin());
|
||||
delay(1000);
|
||||
|
||||
if (ag.sht.begin(Wire) == false) {
|
||||
failedHandler("SHT init failed");
|
||||
}
|
||||
}
|
||||
|
||||
void loop() {
|
||||
if (ag.sht.measure()) {
|
||||
float hum = ag.sht.getRelativeHumidity();
|
||||
float temp = ag.sht.getTemperature();
|
||||
Serial.printf("Get temperature: %f\r\n", temp);
|
||||
Serial.printf(" Get humidity: %f\r\n", hum);
|
||||
} else {
|
||||
Serial.println("Measure failed");
|
||||
}
|
||||
delay(1000);
|
||||
}
|
||||
|
||||
void failedHandler(String msg) {
|
||||
while (true) {
|
||||
Serial.println(msg);
|
||||
delay(1000);
|
||||
}
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
name=AirGradient Air Quality Sensor
|
||||
version=3.0.0
|
||||
version=3.1.16
|
||||
author=AirGradient <support@airgradient.com>
|
||||
maintainer=AirGradient <support@airgradient.com>
|
||||
sentence=ESP32-C3 / ESP8266 library for air quality monitor measuring PM, CO2, Temperature, TVOC and Humidity with OLED display.
|
||||
|
7
partitions.csv
Normal file
7
partitions.csv
Normal file
@ -0,0 +1,7 @@
|
||||
# Name ,Type ,SubType ,Offset ,Size ,Flags
|
||||
nvs ,data ,nvs ,0x9000 ,0x5000 ,
|
||||
otadata ,data ,ota ,0xe000 ,0x2000 ,
|
||||
app0 ,app ,ota_0 ,0x10000 ,0x1E0000 ,
|
||||
app1 ,app ,ota_1 ,0x1F0000 ,0x1E0000 ,
|
||||
spiffs ,data ,spiffs ,0x3D0000 ,0x20000 ,
|
||||
coredump ,data ,coredump ,0x3F0000 ,0x10000 ,
|
|
51
platformio.ini
Normal file
51
platformio.ini
Normal file
@ -0,0 +1,51 @@
|
||||
; PlatformIO Project Configuration File
|
||||
;
|
||||
; Build options: build flags, source filter
|
||||
; Upload options: custom upload port, speed and extra flags
|
||||
; Library options: dependencies, extra library storages
|
||||
; Advanced options: extra scripting
|
||||
;
|
||||
; Please visit documentation for the other options and examples
|
||||
; https://docs.platformio.org/page/projectconf.html
|
||||
|
||||
[env:esp32-c3]
|
||||
platform = espressif32
|
||||
board = esp32-c3-devkitm-1
|
||||
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)'\\"'
|
||||
board_build.partitions = partitions.csv
|
||||
monitor_speed = 115200
|
||||
lib_deps =
|
||||
aglib=symlink://../arduino
|
||||
EEPROM
|
||||
WebServer
|
||||
ESPmDNS
|
||||
FS
|
||||
SPIFFS
|
||||
HTTPClient
|
||||
WiFiClientSecure
|
||||
Update
|
||||
DNSServer
|
||||
|
||||
[env:esp8266]
|
||||
platform = espressif8266
|
||||
board = d1_mini
|
||||
framework = arduino
|
||||
monitor_speed = 115200
|
||||
lib_deps =
|
||||
aglib=symlink://../arduino
|
||||
EEPROM
|
||||
ESP8266HTTPClient
|
||||
ESP8266WebServer
|
||||
DNSServer
|
||||
|
||||
monitor_filters = time
|
||||
|
||||
[platformio]
|
||||
src_dir = examples/OneOpenAir
|
||||
; src_dir = examples/BASIC
|
||||
; src_dir = examples/DiyProIndoorV4_2
|
||||
; src_dir = examples/DiyProIndoorV3_3
|
||||
; src_dir = examples/TestCO2
|
||||
; src_dir = examples/TestPM
|
||||
; src_dir = examples/TestSht
|
201
src/AgApiClient.cpp
Normal file
201
src/AgApiClient.cpp
Normal file
@ -0,0 +1,201 @@
|
||||
#include "AgApiClient.h"
|
||||
#include "AgConfigure.h"
|
||||
#include "AirGradient.h"
|
||||
#include "Libraries/Arduino_JSON/src/Arduino_JSON.h"
|
||||
#ifdef ESP8266
|
||||
#include <ESP8266HTTPClient.h>
|
||||
#include <ESP8266WiFi.h>
|
||||
#include <WiFiClient.h>
|
||||
#else
|
||||
#include <HTTPClient.h>
|
||||
#endif
|
||||
|
||||
AgApiClient::AgApiClient(Stream &debug, Configuration &config)
|
||||
: PrintLog(debug, "ApiClient"), config(config) {}
|
||||
|
||||
AgApiClient::~AgApiClient() {}
|
||||
|
||||
/**
|
||||
* @brief Initialize the API client
|
||||
*
|
||||
*/
|
||||
void AgApiClient::begin(void) {
|
||||
getConfigFailed = false;
|
||||
postToServerFailed = false;
|
||||
logInfo("Init apiRoot: " + apiRoot);
|
||||
logInfo("begin");
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get configuration from AirGradient cloud
|
||||
*
|
||||
* @param deviceId Device ID
|
||||
* @return true Success
|
||||
* @return false Failure
|
||||
*/
|
||||
bool AgApiClient::fetchServerConfiguration(void) {
|
||||
if (config.getConfigurationControl() ==
|
||||
ConfigurationControl::ConfigurationControlLocal ||
|
||||
config.isOfflineMode()) {
|
||||
logWarning("Ignore fetch server configuration");
|
||||
|
||||
// Clear server configuration failed flag, cause it's ignore but not
|
||||
// really failed
|
||||
getConfigFailed = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
String uri = apiRoot + "/sensors/airgradient:" +
|
||||
ag->deviceId() + "/one/config";
|
||||
|
||||
/** Init http client */
|
||||
#ifdef ESP8266
|
||||
HTTPClient client;
|
||||
WiFiClient wifiClient;
|
||||
if (client.begin(wifiClient, uri) == false) {
|
||||
getConfigFailed = true;
|
||||
return false;
|
||||
}
|
||||
#else
|
||||
HTTPClient client;
|
||||
client.setTimeout(timeoutMs);
|
||||
if (client.begin(uri) == false) {
|
||||
getConfigFailed = true;
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
/** Get data */
|
||||
int retCode = client.GET();
|
||||
|
||||
logInfo(String("GET: ") + uri);
|
||||
logInfo(String("Return code: ") + String(retCode));
|
||||
|
||||
if (retCode != 200) {
|
||||
client.end();
|
||||
getConfigFailed = true;
|
||||
|
||||
/** Return code 400 mean device not setup on cloud. */
|
||||
if (retCode == 400) {
|
||||
notAvailableOnDashboard = true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/** clear failed */
|
||||
getConfigFailed = false;
|
||||
notAvailableOnDashboard = false;
|
||||
|
||||
/** Get response string */
|
||||
String respContent = client.getString();
|
||||
client.end();
|
||||
|
||||
// logInfo("Get configuration: " + respContent);
|
||||
|
||||
/** Parse configuration and return result */
|
||||
return config.parse(respContent, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Post data to AirGradient cloud
|
||||
*
|
||||
* @param deviceId Device Id
|
||||
* @param data String JSON
|
||||
* @return true Success
|
||||
* @return false Failure
|
||||
*/
|
||||
bool AgApiClient::postToServer(String data) {
|
||||
if (config.isPostDataToAirGradient() == false) {
|
||||
logWarning("Ignore post data to server");
|
||||
return true;
|
||||
}
|
||||
|
||||
if (WiFi.isConnected() == false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
String uri = apiRoot + "/sensors/airgradient:" + ag->deviceId() + "/measures";
|
||||
// logInfo("Post uri: " + uri);
|
||||
// logInfo("Post data: " + data);
|
||||
|
||||
WiFiClient wifiClient;
|
||||
HTTPClient client;
|
||||
client.setTimeout(timeoutMs);
|
||||
if (client.begin(wifiClient, uri.c_str()) == false) {
|
||||
logError("Init client failed");
|
||||
return false;
|
||||
}
|
||||
client.addHeader("content-type", "application/json");
|
||||
int retCode = client.POST(data);
|
||||
client.end();
|
||||
|
||||
logInfo(String("POST: ") + uri);
|
||||
// logInfo(String("DATA: ") + data);
|
||||
logInfo(String("Return code: ") + String(retCode));
|
||||
|
||||
if ((retCode == 200) || (retCode == 429)) {
|
||||
postToServerFailed = false;
|
||||
return true;
|
||||
} else {
|
||||
logError("Post response failed code: " + String(retCode));
|
||||
}
|
||||
postToServerFailed = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get failed status when get configuration from AirGradient cloud
|
||||
*
|
||||
* @return true Success
|
||||
* @return false Failure
|
||||
*/
|
||||
bool AgApiClient::isFetchConfigureFailed(void) { return getConfigFailed; }
|
||||
|
||||
/**
|
||||
* @brief Get failed status when post data to AirGradient cloud
|
||||
*
|
||||
* @return true Success
|
||||
* @return false Failure
|
||||
*/
|
||||
bool AgApiClient::isPostToServerFailed(void) { return postToServerFailed; }
|
||||
|
||||
/**
|
||||
* @brief Get status device has available on dashboard or not. should get after
|
||||
* fetch configuration return failed
|
||||
*
|
||||
* @return true
|
||||
* @return false
|
||||
*/
|
||||
bool AgApiClient::isNotAvailableOnDashboard(void) {
|
||||
return notAvailableOnDashboard;
|
||||
}
|
||||
|
||||
void AgApiClient::setAirGradient(AirGradient *ag) { this->ag = ag; }
|
||||
|
||||
/**
|
||||
* @brief Send the package to check the connection with cloud
|
||||
*
|
||||
* @param rssi WiFi RSSI
|
||||
* @param bootCount Boot count
|
||||
* @return true Success
|
||||
* @return false Failure
|
||||
*/
|
||||
bool AgApiClient::sendPing(int rssi, int bootCount) {
|
||||
JSONVar root;
|
||||
root["wifi"] = rssi;
|
||||
root["boot"] = bootCount;
|
||||
return postToServer(JSON.stringify(root));
|
||||
}
|
||||
|
||||
String AgApiClient::getApiRoot() const { return apiRoot; }
|
||||
|
||||
void AgApiClient::setApiRoot(const String &apiRoot) { this->apiRoot = apiRoot; }
|
||||
|
||||
/**
|
||||
* @brief Set http request timeout. (Default: 10s)
|
||||
*
|
||||
* @param timeoutMs
|
||||
*/
|
||||
void AgApiClient::setTimeout(uint16_t timeoutMs) {
|
||||
this->timeoutMs = timeoutMs;
|
||||
}
|
47
src/AgApiClient.h
Normal file
47
src/AgApiClient.h
Normal file
@ -0,0 +1,47 @@
|
||||
/**
|
||||
* @file AgApiClient.h
|
||||
* @brief HTTP client connect post data to Aigradient cloud.
|
||||
* @version 0.1
|
||||
* @date 2024-Apr-02
|
||||
*
|
||||
* @copyright Copyright (c) 2024
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef _AG_API_CLIENT_H_
|
||||
#define _AG_API_CLIENT_H_
|
||||
|
||||
#include "AgConfigure.h"
|
||||
#include "AirGradient.h"
|
||||
#include "Main/PrintLog.h"
|
||||
#include <Arduino.h>
|
||||
|
||||
class AgApiClient : public PrintLog {
|
||||
private:
|
||||
Configuration &config;
|
||||
AirGradient *ag;
|
||||
String apiRoot = "http://hw.airgradient.com";
|
||||
|
||||
bool getConfigFailed;
|
||||
bool postToServerFailed;
|
||||
bool notAvailableOnDashboard = false; // Device not setup on Airgradient cloud dashboard.
|
||||
uint16_t timeoutMs = 10000; // Default set to 10s
|
||||
|
||||
public:
|
||||
AgApiClient(Stream &stream, Configuration &config);
|
||||
~AgApiClient();
|
||||
|
||||
void begin(void);
|
||||
bool fetchServerConfiguration(void);
|
||||
bool postToServer(String data);
|
||||
bool isFetchConfigureFailed(void);
|
||||
bool isPostToServerFailed(void);
|
||||
bool isNotAvailableOnDashboard(void);
|
||||
void setAirGradient(AirGradient *ag);
|
||||
bool sendPing(int rssi, int bootCount);
|
||||
String getApiRoot() const;
|
||||
void setApiRoot(const String &apiRoot);
|
||||
void setTimeout(uint16_t timeoutMs);
|
||||
};
|
||||
|
||||
#endif /** _AG_API_CLIENT_H_ */
|
1382
src/AgConfigure.cpp
Normal file
1382
src/AgConfigure.cpp
Normal file
File diff suppressed because it is too large
Load Diff
104
src/AgConfigure.h
Normal file
104
src/AgConfigure.h
Normal file
@ -0,0 +1,104 @@
|
||||
#ifndef _AG_CONFIG_H_
|
||||
#define _AG_CONFIG_H_
|
||||
|
||||
#include "App/AppDef.h"
|
||||
#include "Main/PrintLog.h"
|
||||
#include "AirGradient.h"
|
||||
#include <Arduino.h>
|
||||
#include "Libraries/Arduino_JSON/src/Arduino_JSON.h"
|
||||
|
||||
class Configuration : public PrintLog {
|
||||
public:
|
||||
struct PMCorrection {
|
||||
PMCorrectionAlgorithm algorithm;
|
||||
float intercept;
|
||||
float scalingFactor;
|
||||
bool useEPA; // EPA 2021
|
||||
bool changed;
|
||||
};
|
||||
|
||||
private:
|
||||
bool co2CalibrationRequested;
|
||||
bool ledBarTestRequested;
|
||||
bool updated;
|
||||
String failedMessage;
|
||||
bool _noxLearnOffsetChanged;
|
||||
bool _tvocLearningOffsetChanged;
|
||||
bool ledBarBrightnessChanged = false;
|
||||
bool displayBrightnessChanged = false;
|
||||
String otaNewFirmwareVersion;
|
||||
bool _offlineMode = false;
|
||||
bool _ledBarModeChanged = false;
|
||||
PMCorrection pmCorrection;
|
||||
|
||||
AirGradient* ag;
|
||||
|
||||
String getLedBarModeName(LedBarMode mode);
|
||||
PMCorrectionAlgorithm matchPmAlgorithm(String algorithm);
|
||||
bool updatePmCorrection(JSONVar &json);
|
||||
void saveConfig(void);
|
||||
void loadConfig(void);
|
||||
void defaultConfig(void);
|
||||
void printConfig(void);
|
||||
String jsonTypeInvalidMessage(String name, String type);
|
||||
String jsonValueInvalidMessage(String name, String value);
|
||||
void jsonInvalid(void);
|
||||
void configLogInfo(String name, String fromValue, String toValue);
|
||||
String getPMStandardString(bool usaqi);
|
||||
String getAbcDayString(int value);
|
||||
void toConfig(const char* buf);
|
||||
|
||||
public:
|
||||
Configuration(Stream &debugLog);
|
||||
~Configuration();
|
||||
|
||||
bool hasSensorS8 = true;
|
||||
bool hasSensorPMS1 = true;
|
||||
bool hasSensorPMS2 = true;
|
||||
bool hasSensorSGP = true;
|
||||
bool hasSensorSHT = true;
|
||||
|
||||
bool begin(void);
|
||||
bool parse(String data, bool isLocal);
|
||||
String toString(void);
|
||||
String toString(AgFirmwareMode fwMode);
|
||||
bool isTemperatureUnitInF(void);
|
||||
String getCountry(void);
|
||||
bool isPmStandardInUSAQI(void);
|
||||
int getCO2CalibrationAbcDays(void);
|
||||
LedBarMode getLedBarMode(void);
|
||||
String getLedBarModeName(void);
|
||||
bool getDisplayMode(void);
|
||||
String getMqttBrokerUri(void);
|
||||
bool isPostDataToAirGradient(void);
|
||||
ConfigurationControl getConfigurationControl(void);
|
||||
bool isCo2CalibrationRequested(void);
|
||||
bool isLedBarTestRequested(void);
|
||||
void reset(void);
|
||||
String getModel(void);
|
||||
bool isUpdated(void);
|
||||
String getFailedMesage(void);
|
||||
void setPostToAirGradient(bool enable);
|
||||
bool noxLearnOffsetChanged(void);
|
||||
bool tvocLearnOffsetChanged(void);
|
||||
int getTvocLearningOffset(void);
|
||||
int getNoxLearningOffset(void);
|
||||
String wifiSSID(void);
|
||||
String wifiPass(void);
|
||||
void setAirGradient(AirGradient *ag);
|
||||
bool isLedBarBrightnessChanged(void);
|
||||
int getLedBarBrightness(void);
|
||||
bool isDisplayBrightnessChanged(void);
|
||||
int getDisplayBrightness(void);
|
||||
String newFirmwareVersion(void);
|
||||
bool isOfflineMode(void);
|
||||
void setOfflineMode(bool offline);
|
||||
void setOfflineModeWithoutSave(bool offline);
|
||||
bool isLedBarModeChanged(void);
|
||||
bool isMonitorDisplayCompensatedValues(void);
|
||||
bool isPMCorrectionChanged(void);
|
||||
bool isPMCorrectionEnabled(void);
|
||||
PMCorrection getPMCorrection(void);
|
||||
};
|
||||
|
||||
#endif /** _AG_CONFIG_H_ */
|
555
src/AgOledDisplay.cpp
Normal file
555
src/AgOledDisplay.cpp
Normal file
@ -0,0 +1,555 @@
|
||||
#include "AgOledDisplay.h"
|
||||
#include "Libraries/U8g2/src/U8g2lib.h"
|
||||
#include "Main/utils.h"
|
||||
|
||||
/** Cast U8G2 */
|
||||
#define DISP() ((U8G2_SH1106_128X64_NONAME_F_HW_I2C *)(this->u8g2))
|
||||
|
||||
/**
|
||||
* @brief Show dashboard temperature and humdity
|
||||
*
|
||||
* @param hasStatus
|
||||
*/
|
||||
void OledDisplay::showTempHum(bool hasStatus, char *buf, int buf_size) {
|
||||
/** Temperature */
|
||||
float temp = value.getAverage(Measurements::Temperature);
|
||||
if (utils::isValidTemperature(temp)) {
|
||||
float t = 0.0f;
|
||||
if (config.isTemperatureUnitInF()) {
|
||||
t = utils::degreeC_To_F(temp);
|
||||
} else {
|
||||
t = temp;
|
||||
}
|
||||
|
||||
if (config.isTemperatureUnitInF()) {
|
||||
if (hasStatus) {
|
||||
snprintf(buf, buf_size, "%0.1f", t);
|
||||
} else {
|
||||
snprintf(buf, buf_size, "%0.1f°F", t);
|
||||
}
|
||||
} else {
|
||||
if (hasStatus) {
|
||||
snprintf(buf, buf_size, "%.1f", t);
|
||||
} else {
|
||||
snprintf(buf, buf_size, "%.1f°C", t);
|
||||
}
|
||||
}
|
||||
} else { /** Show invalid value */
|
||||
if (config.isTemperatureUnitInF()) {
|
||||
snprintf(buf, buf_size, "-°F");
|
||||
} else {
|
||||
snprintf(buf, buf_size, "-°C");
|
||||
}
|
||||
}
|
||||
DISP()->drawUTF8(1, 10, buf);
|
||||
|
||||
/** Show humidity */
|
||||
int rhum = round(value.getAverage(Measurements::Humidity));
|
||||
if (utils::isValidHumidity(rhum)) {
|
||||
snprintf(buf, buf_size, "%d%%", rhum);
|
||||
} else {
|
||||
snprintf(buf, buf_size, "-%%");
|
||||
}
|
||||
|
||||
if (rhum > 99.0) {
|
||||
DISP()->drawStr(97, 10, buf);
|
||||
} else {
|
||||
DISP()->drawStr(105, 10, buf);
|
||||
}
|
||||
}
|
||||
|
||||
void OledDisplay::setCentralText(int y, String text) {
|
||||
setCentralText(y, text.c_str());
|
||||
}
|
||||
|
||||
void OledDisplay::setCentralText(int y, const char *text) {
|
||||
int x = (DISP()->getWidth() - DISP()->getStrWidth(text)) / 2;
|
||||
DISP()->drawStr(x, y, text);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Construct a new Ag Oled Display:: Ag Oled Display object
|
||||
*
|
||||
* @param config AgConfiguration
|
||||
* @param value Measurements
|
||||
* @param log Serial Stream
|
||||
*/
|
||||
OledDisplay::OledDisplay(Configuration &config, Measurements &value,
|
||||
Stream &log)
|
||||
: PrintLog(log, "OledDisplay"), config(config), value(value) {}
|
||||
|
||||
/**
|
||||
* @brief Set AirGradient instance
|
||||
*
|
||||
* @param ag Point to AirGradient instance
|
||||
*/
|
||||
void OledDisplay::setAirGradient(AirGradient *ag) { this->ag = ag; }
|
||||
|
||||
OledDisplay::~OledDisplay() {}
|
||||
|
||||
/**
|
||||
* @brief Initialize display
|
||||
*
|
||||
* @return true Success
|
||||
* @return false Failure
|
||||
*/
|
||||
bool OledDisplay::begin(void) {
|
||||
if (isBegin) {
|
||||
logWarning("Already begin, call 'end' and try again");
|
||||
return true;
|
||||
}
|
||||
|
||||
if (ag->isOne() || ag->isPro3_3() || ag->isPro4_2()) {
|
||||
/** Create u8g2 instance */
|
||||
u8g2 = new U8G2_SH1106_128X64_NONAME_F_HW_I2C(U8G2_R0, U8X8_PIN_NONE);
|
||||
if (u8g2 == NULL) {
|
||||
logError("Create 'U8G2' failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
/** Init u8g2 */
|
||||
if (DISP()->begin() == false) {
|
||||
logError("U8G2 'begin' failed");
|
||||
return false;
|
||||
}
|
||||
} else if (ag->isBasic()) {
|
||||
logInfo("DIY_BASIC init");
|
||||
ag->display.begin(Wire);
|
||||
ag->display.setTextColor(1);
|
||||
ag->display.clear();
|
||||
ag->display.show();
|
||||
}
|
||||
|
||||
/** Show low brightness on startup. then it's completely turn off on main
|
||||
* application */
|
||||
int brightness = config.getDisplayBrightness();
|
||||
if (brightness == 0) {
|
||||
setBrightness(1);
|
||||
}
|
||||
|
||||
isBegin = true;
|
||||
logInfo("begin");
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief De-Initialize display
|
||||
*
|
||||
*/
|
||||
void OledDisplay::end(void) {
|
||||
if (!isBegin) {
|
||||
logWarning("Already end, call 'begin' and try again");
|
||||
return;
|
||||
}
|
||||
|
||||
if (ag->isOne() || ag->isPro3_3() || ag->isPro4_2()) {
|
||||
/** Free u8g2 */
|
||||
delete DISP();
|
||||
u8g2 = NULL;
|
||||
} else if (ag->isBasic()) {
|
||||
ag->display.end();
|
||||
}
|
||||
|
||||
isBegin = false;
|
||||
logInfo("end");
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Show text on 3 line of display
|
||||
*
|
||||
* @param line1
|
||||
* @param line2
|
||||
* @param line3
|
||||
*/
|
||||
void OledDisplay::setText(String &line1, String &line2, String &line3) {
|
||||
setText(line1.c_str(), line2.c_str(), line3.c_str());
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Show text on 3 line of display
|
||||
*
|
||||
* @param line1
|
||||
* @param line2
|
||||
* @param line3
|
||||
*/
|
||||
void OledDisplay::setText(const char *line1, const char *line2,
|
||||
const char *line3) {
|
||||
if (isDisplayOff) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (ag->isOne() || ag->isPro3_3() || ag->isPro4_2()) {
|
||||
DISP()->firstPage();
|
||||
do {
|
||||
DISP()->setFont(u8g2_font_t0_16_tf);
|
||||
DISP()->drawStr(1, 10, line1);
|
||||
DISP()->drawStr(1, 30, line2);
|
||||
DISP()->drawStr(1, 50, line3);
|
||||
} while (DISP()->nextPage());
|
||||
} else if (ag->isBasic()) {
|
||||
ag->display.clear();
|
||||
|
||||
ag->display.setCursor(1, 1);
|
||||
ag->display.setText(line1);
|
||||
ag->display.setCursor(1, 17);
|
||||
ag->display.setText(line2);
|
||||
ag->display.setCursor(1, 33);
|
||||
ag->display.setText(line3);
|
||||
|
||||
ag->display.show();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set Text on 4 line
|
||||
*
|
||||
* @param line1
|
||||
* @param line2
|
||||
* @param line3
|
||||
* @param line4
|
||||
*/
|
||||
void OledDisplay::setText(String &line1, String &line2, String &line3,
|
||||
String &line4) {
|
||||
setText(line1.c_str(), line2.c_str(), line3.c_str(), line4.c_str());
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set Text on 4 line
|
||||
*
|
||||
* @param line1
|
||||
* @param line2
|
||||
* @param line3
|
||||
* @param line4
|
||||
*/
|
||||
void OledDisplay::setText(const char *line1, const char *line2,
|
||||
const char *line3, const char *line4) {
|
||||
if (isDisplayOff) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (ag->isOne() || ag->isPro3_3() || ag->isPro4_2()) {
|
||||
DISP()->firstPage();
|
||||
do {
|
||||
DISP()->setFont(u8g2_font_t0_16_tf);
|
||||
DISP()->drawStr(1, 10, line1);
|
||||
DISP()->drawStr(1, 25, line2);
|
||||
DISP()->drawStr(1, 40, line3);
|
||||
DISP()->drawStr(1, 55, line4);
|
||||
} while (DISP()->nextPage());
|
||||
} else if (ag->isBasic()) {
|
||||
ag->display.clear();
|
||||
ag->display.setCursor(0, 0);
|
||||
ag->display.setText(line1);
|
||||
ag->display.setCursor(0, 10);
|
||||
ag->display.setText(line2);
|
||||
ag->display.setCursor(0, 20);
|
||||
ag->display.setText(line3);
|
||||
ag->display.show();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Update dashboard content
|
||||
*
|
||||
*/
|
||||
void OledDisplay::showDashboard(void) { showDashboard(NULL); }
|
||||
|
||||
/**
|
||||
* @brief Update dashboard content and error status
|
||||
*
|
||||
*/
|
||||
void OledDisplay::showDashboard(const char *status) {
|
||||
if (isDisplayOff) {
|
||||
return;
|
||||
}
|
||||
|
||||
char strBuf[16];
|
||||
if (ag->isOne() || ag->isPro3_3() || ag->isPro4_2()) {
|
||||
DISP()->firstPage();
|
||||
do {
|
||||
DISP()->setFont(u8g2_font_t0_16_tf);
|
||||
if ((status == NULL) || (strlen(status) == 0)) {
|
||||
showTempHum(false, strBuf, sizeof(strBuf));
|
||||
} else {
|
||||
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, strBuf, sizeof(strBuf));
|
||||
}
|
||||
}
|
||||
|
||||
/** Draw horizonal line */
|
||||
DISP()->drawLine(1, 13, 128, 13);
|
||||
|
||||
/** Show CO2 label */
|
||||
DISP()->setFont(u8g2_font_t0_12_tf);
|
||||
DISP()->drawUTF8(1, 27, "CO2");
|
||||
|
||||
DISP()->setFont(u8g2_font_t0_22b_tf);
|
||||
int co2 = round(value.getAverage(Measurements::CO2));
|
||||
if (utils::isValidCO2(co2)) {
|
||||
sprintf(strBuf, "%d", co2);
|
||||
} else {
|
||||
sprintf(strBuf, "%s", "-");
|
||||
}
|
||||
DISP()->drawStr(1, 48, strBuf);
|
||||
|
||||
/** Show CO2 value index */
|
||||
DISP()->setFont(u8g2_font_t0_12_tf);
|
||||
DISP()->drawStr(1, 61, "ppm");
|
||||
|
||||
/** Draw vertical line */
|
||||
DISP()->drawLine(52, 14, 52, 64);
|
||||
DISP()->drawLine(97, 14, 97, 64);
|
||||
|
||||
/** Draw PM2.5 label */
|
||||
DISP()->setFont(u8g2_font_t0_12_tf);
|
||||
DISP()->drawStr(55, 27, "PM2.5");
|
||||
|
||||
/** Draw PM2.5 value */
|
||||
int pm25 = round(value.getAverage(Measurements::PM25));
|
||||
if (utils::isValidPm(pm25)) {
|
||||
if (config.hasSensorSHT && config.isPMCorrectionEnabled()) {
|
||||
pm25 = round(value.getCorrectedPM25(*ag, config, 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()->drawStr(55, 48, strBuf);
|
||||
|
||||
/** Draw PM2.5 unit */
|
||||
DISP()->setFont(u8g2_font_t0_12_tf);
|
||||
if (config.isPmStandardInUSAQI()) {
|
||||
DISP()->drawUTF8(55, 61, "AQI");
|
||||
} else {
|
||||
DISP()->drawUTF8(55, 61, "ug/m³");
|
||||
}
|
||||
|
||||
/** Draw tvocIndexlabel */
|
||||
DISP()->setFont(u8g2_font_t0_12_tf);
|
||||
DISP()->drawStr(100, 27, "VOC:");
|
||||
|
||||
/** Draw tvocIndexvalue */
|
||||
int tvoc = round(value.getAverage(Measurements::TVOC));
|
||||
if (utils::isValidVOC(tvoc)) {
|
||||
sprintf(strBuf, "%d", tvoc);
|
||||
} else {
|
||||
sprintf(strBuf, "%s", "-");
|
||||
}
|
||||
DISP()->drawStr(100, 39, strBuf);
|
||||
|
||||
/** Draw NOx label */
|
||||
int nox = round(value.getAverage(Measurements::NOx));
|
||||
DISP()->drawStr(100, 53, "NOx:");
|
||||
if (utils::isValidNOx(nox)) {
|
||||
sprintf(strBuf, "%d", nox);
|
||||
} else {
|
||||
sprintf(strBuf, "%s", "-");
|
||||
}
|
||||
DISP()->drawStr(100, 63, strBuf);
|
||||
} while (DISP()->nextPage());
|
||||
} else if (ag->isBasic()) {
|
||||
ag->display.clear();
|
||||
|
||||
/** Set CO2 */
|
||||
int co2 = round(value.getAverage(Measurements::CO2));
|
||||
if (utils::isValidCO2(co2)) {
|
||||
snprintf(strBuf, sizeof(strBuf), "CO2:%d", co2);
|
||||
} else {
|
||||
snprintf(strBuf, sizeof(strBuf), "CO2:-");
|
||||
}
|
||||
|
||||
ag->display.setCursor(0, 0);
|
||||
ag->display.setText(strBuf);
|
||||
|
||||
/** Set PM */
|
||||
int pm25 = round(value.getAverage(Measurements::PM25));
|
||||
if (config.hasSensorSHT && config.isPMCorrectionEnabled()) {
|
||||
pm25 = round(value.getCorrectedPM25(*ag, config, true));
|
||||
}
|
||||
|
||||
ag->display.setCursor(0, 12);
|
||||
if (utils::isValidPm(pm25)) {
|
||||
snprintf(strBuf, sizeof(strBuf), "PM2.5:%d", pm25);
|
||||
} else {
|
||||
snprintf(strBuf, sizeof(strBuf), "PM2.5:-");
|
||||
}
|
||||
ag->display.setText(strBuf);
|
||||
|
||||
/** Set temperature and humidity */
|
||||
float temp = value.getAverage(Measurements::Temperature);
|
||||
if (utils::isValidTemperature(temp)) {
|
||||
if (config.isTemperatureUnitInF()) {
|
||||
snprintf(strBuf, sizeof(strBuf), "T:%0.1f F", utils::degreeC_To_F(temp));
|
||||
} else {
|
||||
snprintf(strBuf, sizeof(strBuf), "T:%0.f1 C", temp);
|
||||
}
|
||||
} else {
|
||||
if (config.isTemperatureUnitInF()) {
|
||||
snprintf(strBuf, sizeof(strBuf), "T:-F");
|
||||
} else {
|
||||
snprintf(strBuf, sizeof(strBuf), "T:-C");
|
||||
}
|
||||
}
|
||||
|
||||
ag->display.setCursor(0, 24);
|
||||
ag->display.setText(strBuf);
|
||||
|
||||
int rhum = round(value.getAverage(Measurements::Humidity));
|
||||
if (utils::isValidHumidity(rhum)) {
|
||||
snprintf(strBuf, sizeof(strBuf), "H:%d %%", rhum);
|
||||
} else {
|
||||
snprintf(strBuf, sizeof(strBuf), "H:- %%");
|
||||
}
|
||||
|
||||
ag->display.setCursor(0, 36);
|
||||
ag->display.setText(strBuf);
|
||||
|
||||
ag->display.show();
|
||||
}
|
||||
}
|
||||
|
||||
void OledDisplay::setBrightness(int percent) {
|
||||
if (ag->isOne() || ag->isPro3_3() || ag->isPro4_2()) {
|
||||
if (percent == 0) {
|
||||
isDisplayOff = true;
|
||||
|
||||
// Clear display.
|
||||
DISP()->firstPage();
|
||||
do {
|
||||
} while (DISP()->nextPage());
|
||||
|
||||
} else {
|
||||
isDisplayOff = false;
|
||||
DISP()->setContrast((127 * percent) / 100);
|
||||
}
|
||||
} 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef ESP32
|
||||
void OledDisplay::showFirmwareUpdateVersion(String version) {
|
||||
if (isDisplayOff) {
|
||||
return;
|
||||
}
|
||||
|
||||
DISP()->firstPage();
|
||||
do {
|
||||
DISP()->setFont(u8g2_font_t0_16_tf);
|
||||
setCentralText(20, "Firmware Update");
|
||||
setCentralText(40, "New version");
|
||||
setCentralText(60, version.c_str());
|
||||
} while (DISP()->nextPage());
|
||||
}
|
||||
|
||||
void OledDisplay::showFirmwareUpdateProgress(int percent) {
|
||||
if (isDisplayOff) {
|
||||
return;
|
||||
}
|
||||
|
||||
DISP()->firstPage();
|
||||
do {
|
||||
DISP()->setFont(u8g2_font_t0_16_tf);
|
||||
setCentralText(20, "Firmware Update");
|
||||
setCentralText(50, String("Updating... ") + String(percent) + String("%"));
|
||||
} while (DISP()->nextPage());
|
||||
}
|
||||
|
||||
void OledDisplay::showFirmwareUpdateSuccess(int count) {
|
||||
if (isDisplayOff) {
|
||||
return;
|
||||
}
|
||||
|
||||
DISP()->firstPage();
|
||||
do {
|
||||
DISP()->setFont(u8g2_font_t0_16_tf);
|
||||
setCentralText(20, "Firmware Update");
|
||||
setCentralText(40, "Success");
|
||||
setCentralText(60, String("Rebooting... ") + String(count));
|
||||
} while (DISP()->nextPage());
|
||||
}
|
||||
|
||||
void OledDisplay::showFirmwareUpdateFailed(void) {
|
||||
if (isDisplayOff) {
|
||||
return;
|
||||
}
|
||||
|
||||
DISP()->firstPage();
|
||||
do {
|
||||
DISP()->setFont(u8g2_font_t0_16_tf);
|
||||
setCentralText(20, "Firmware Update");
|
||||
setCentralText(40, "fail, will retry");
|
||||
// setCentralText(60, "will retry");
|
||||
} while (DISP()->nextPage());
|
||||
}
|
||||
|
||||
void OledDisplay::showFirmwareUpdateSkipped(void) {
|
||||
if (isDisplayOff) {
|
||||
return;
|
||||
}
|
||||
|
||||
DISP()->firstPage();
|
||||
do {
|
||||
DISP()->setFont(u8g2_font_t0_16_tf);
|
||||
setCentralText(20, "Firmware Update");
|
||||
setCentralText(40, "skipped");
|
||||
} while (DISP()->nextPage());
|
||||
}
|
||||
|
||||
void OledDisplay::showFirmwareUpdateUpToDate(void) {
|
||||
if (isDisplayOff) {
|
||||
return;
|
||||
}
|
||||
|
||||
DISP()->firstPage();
|
||||
do {
|
||||
DISP()->setFont(u8g2_font_t0_16_tf);
|
||||
setCentralText(20, "Firmware Update");
|
||||
setCentralText(40, "up to date");
|
||||
} while (DISP()->nextPage());
|
||||
}
|
||||
#else
|
||||
|
||||
#endif
|
||||
|
||||
void OledDisplay::showRebooting(void) {
|
||||
if (ag->isOne() || ag->isPro3_3() || ag->isPro4_2()) {
|
||||
DISP()->firstPage();
|
||||
do {
|
||||
DISP()->setFont(u8g2_font_t0_16_tf);
|
||||
// setCentralText(20, "Firmware Update");
|
||||
setCentralText(40, "Rebooting...");
|
||||
// setCentralText(60, String("Retry after 24h"));
|
||||
} while (DISP()->nextPage());
|
||||
} else if (ag->isBasic()) {
|
||||
ag->display.clear();
|
||||
|
||||
ag->display.setCursor(0, 20);
|
||||
ag->display.setText("Rebooting...");
|
||||
|
||||
ag->display.show();
|
||||
}
|
||||
}
|
52
src/AgOledDisplay.h
Normal file
52
src/AgOledDisplay.h
Normal file
@ -0,0 +1,52 @@
|
||||
#ifndef _AG_OLED_DISPLAY_H_
|
||||
#define _AG_OLED_DISPLAY_H_
|
||||
|
||||
#include "AgConfigure.h"
|
||||
#include "AgValue.h"
|
||||
#include "AirGradient.h"
|
||||
#include "Main/PrintLog.h"
|
||||
#include <Arduino.h>
|
||||
|
||||
class OledDisplay : public PrintLog {
|
||||
private:
|
||||
Configuration &config;
|
||||
AirGradient *ag;
|
||||
bool isBegin = false;
|
||||
void *u8g2 = NULL;
|
||||
Measurements &value;
|
||||
bool isDisplayOff = false;
|
||||
|
||||
void showTempHum(bool hasStatus, char* buf, int buf_size);
|
||||
void setCentralText(int y, String text);
|
||||
void setCentralText(int y, const char *text);
|
||||
|
||||
public:
|
||||
OledDisplay(Configuration &config, Measurements &value,
|
||||
Stream &log);
|
||||
~OledDisplay();
|
||||
|
||||
void setAirGradient(AirGradient *ag);
|
||||
bool begin(void);
|
||||
void end(void);
|
||||
void setText(String &line1, String &line2, String &line3);
|
||||
void setText(const char *line1, const char *line2, const char *line3);
|
||||
void setText(String &line1, String &line2, String &line3, String &line4);
|
||||
void setText(const char *line1, const char *line2, const char *line3,
|
||||
const char *line4);
|
||||
void showDashboard(void);
|
||||
void showDashboard(const char *status);
|
||||
void setBrightness(int percent);
|
||||
#ifdef ESP32
|
||||
void showFirmwareUpdateVersion(String version);
|
||||
void showFirmwareUpdateProgress(int percent);
|
||||
void showFirmwareUpdateSuccess(int count);
|
||||
void showFirmwareUpdateFailed(void);
|
||||
void showFirmwareUpdateSkipped(void);
|
||||
void showFirmwareUpdateUpToDate(void);
|
||||
#else
|
||||
|
||||
#endif
|
||||
void showRebooting(void);
|
||||
};
|
||||
|
||||
#endif /** _AG_OLED_DISPLAY_H_ */
|
26
src/AgSchedule.cpp
Normal file
26
src/AgSchedule.cpp
Normal file
@ -0,0 +1,26 @@
|
||||
#include "AgSchedule.h"
|
||||
|
||||
AgSchedule::AgSchedule(int period, void (*handler)(void))
|
||||
: period(period), handler(handler) {}
|
||||
|
||||
AgSchedule::~AgSchedule() {}
|
||||
|
||||
void AgSchedule::run(void) {
|
||||
uint32_t ms = (uint32_t)(millis() - count);
|
||||
if (ms >= period) {
|
||||
handler();
|
||||
count = millis();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set schedule period
|
||||
*
|
||||
* @param period Period in ms
|
||||
*/
|
||||
void AgSchedule::setPeriod(int period) { this->period = period; }
|
||||
|
||||
/**
|
||||
* @brief Update period
|
||||
*/
|
||||
void AgSchedule::update(void) { count = millis(); }
|
20
src/AgSchedule.h
Normal file
20
src/AgSchedule.h
Normal file
@ -0,0 +1,20 @@
|
||||
#ifndef _AG_SCHEDULE_H_
|
||||
#define _AG_SCHEDULE_H_
|
||||
|
||||
#include <Arduino.h>
|
||||
|
||||
class AgSchedule {
|
||||
private:
|
||||
int period;
|
||||
void (*handler)(void);
|
||||
uint32_t count;
|
||||
|
||||
public:
|
||||
AgSchedule(int period, void (*handler)(void));
|
||||
~AgSchedule();
|
||||
void run(void);
|
||||
void update(void);
|
||||
void setPeriod(int period);
|
||||
};
|
||||
|
||||
#endif /** _AG_SCHEDULE_H_ */
|
869
src/AgStateMachine.cpp
Normal file
869
src/AgStateMachine.cpp
Normal file
@ -0,0 +1,869 @@
|
||||
#include "AgStateMachine.h"
|
||||
|
||||
#define LED_TEST_BLINK_DELAY 50 /** ms */
|
||||
#define LED_FAST_BLINK_DELAY 250 /** ms */
|
||||
#define LED_SLOW_BLINK_DELAY 1000 /** ms */
|
||||
#define LED_SHORT_BLINK_DELAY 500 /** ms */
|
||||
#define LED_LONG_BLINK_DELAY 2000 /** ms */
|
||||
|
||||
#define SENSOR_CO2_CALIB_COUNTDOWN_MAX 5 /** sec */
|
||||
|
||||
#define RGB_COLOR_R 255, 0, 0 /** Red */
|
||||
#define RGB_COLOR_G 0, 255, 0 /** Green */
|
||||
#define RGB_COLOR_Y 255, 150, 0 /** Yellow */
|
||||
#define RGB_COLOR_O 255, 40, 0 /** Orange */
|
||||
#define RGB_COLOR_P 180, 0, 255 /** Purple */
|
||||
#define RGB_COLOR_CLEAR 0, 0, 0 /** No color */
|
||||
|
||||
/**
|
||||
* @brief Animation LED bar with color
|
||||
*
|
||||
* @param r
|
||||
* @param g
|
||||
* @param b
|
||||
*/
|
||||
void StateMachine::ledBarSingleLedAnimation(uint8_t r, uint8_t g, uint8_t b) {
|
||||
if (ledBarAnimationCount < 0) {
|
||||
ledBarAnimationCount = 0;
|
||||
ag->ledBar.setColor(r, g, b, ledBarAnimationCount);
|
||||
} else {
|
||||
ledBarAnimationCount++;
|
||||
if (ledBarAnimationCount >= ag->ledBar.getNumberOfLeds()) {
|
||||
ledBarAnimationCount = 0;
|
||||
}
|
||||
ag->ledBar.setColor(r, g, b, ledBarAnimationCount);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief LED status blink with delay
|
||||
*
|
||||
* @param ms Miliseconds
|
||||
*/
|
||||
void StateMachine::ledStatusBlinkDelay(uint32_t ms) {
|
||||
ag->statusLed.setOn();
|
||||
delay(ms);
|
||||
ag->statusLed.setOff();
|
||||
delay(ms);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Led bar show PM or CO2 led color status
|
||||
*
|
||||
* @return true if all led bar are used, false othwerwise
|
||||
*/
|
||||
bool StateMachine::sensorhandleLeds(void) {
|
||||
int totalLedUsed = 0;
|
||||
switch (config.getLedBarMode()) {
|
||||
case LedBarMode::LedBarModeCO2:
|
||||
totalLedUsed = co2handleLeds();
|
||||
break;
|
||||
case LedBarMode::LedBarModePm:
|
||||
totalLedUsed = pm25handleLeds();
|
||||
break;
|
||||
default:
|
||||
ag->ledBar.clear();
|
||||
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
|
||||
*
|
||||
* @return return total number of led that are used on the monitor
|
||||
*/
|
||||
int StateMachine::co2handleLeds(void) {
|
||||
int totalUsed = ag->ledBar.getNumberOfLeds();
|
||||
int co2Value = round(value.getAverage(Measurements::CO2));
|
||||
if (co2Value <= 600) {
|
||||
/** G; 1 */
|
||||
ag->ledBar.setColor(RGB_COLOR_G, ag->ledBar.getNumberOfLeds() - 1);
|
||||
totalUsed = 1;
|
||||
} else if (co2Value <= 800) {
|
||||
/** GG; 2 */
|
||||
ag->ledBar.setColor(RGB_COLOR_G, ag->ledBar.getNumberOfLeds() - 1);
|
||||
ag->ledBar.setColor(RGB_COLOR_G, ag->ledBar.getNumberOfLeds() - 2);
|
||||
totalUsed = 2;
|
||||
} else if (co2Value <= 1000) {
|
||||
/** YYY; 3 */
|
||||
ag->ledBar.setColor(RGB_COLOR_Y, ag->ledBar.getNumberOfLeds() - 1);
|
||||
ag->ledBar.setColor(RGB_COLOR_Y, ag->ledBar.getNumberOfLeds() - 2);
|
||||
ag->ledBar.setColor(RGB_COLOR_Y, ag->ledBar.getNumberOfLeds() - 3);
|
||||
totalUsed = 3;
|
||||
} else if (co2Value <= 1250) {
|
||||
/** OOOO; 4 */
|
||||
ag->ledBar.setColor(RGB_COLOR_O, ag->ledBar.getNumberOfLeds() - 1);
|
||||
ag->ledBar.setColor(RGB_COLOR_O, ag->ledBar.getNumberOfLeds() - 2);
|
||||
ag->ledBar.setColor(RGB_COLOR_O, ag->ledBar.getNumberOfLeds() - 3);
|
||||
ag->ledBar.setColor(RGB_COLOR_O, ag->ledBar.getNumberOfLeds() - 4);
|
||||
totalUsed = 4;
|
||||
} else if (co2Value <= 1500) {
|
||||
/** OOOOO; 5 */
|
||||
ag->ledBar.setColor(RGB_COLOR_O, ag->ledBar.getNumberOfLeds() - 1);
|
||||
ag->ledBar.setColor(RGB_COLOR_O, ag->ledBar.getNumberOfLeds() - 2);
|
||||
ag->ledBar.setColor(RGB_COLOR_O, ag->ledBar.getNumberOfLeds() - 3);
|
||||
ag->ledBar.setColor(RGB_COLOR_O, ag->ledBar.getNumberOfLeds() - 4);
|
||||
ag->ledBar.setColor(RGB_COLOR_O, ag->ledBar.getNumberOfLeds() - 5);
|
||||
totalUsed = 5;
|
||||
} else if (co2Value <= 1750) {
|
||||
/** RRRRRR; 6 */
|
||||
ag->ledBar.setColor(RGB_COLOR_R, ag->ledBar.getNumberOfLeds() - 1);
|
||||
ag->ledBar.setColor(RGB_COLOR_R, ag->ledBar.getNumberOfLeds() - 2);
|
||||
ag->ledBar.setColor(RGB_COLOR_R, ag->ledBar.getNumberOfLeds() - 3);
|
||||
ag->ledBar.setColor(RGB_COLOR_R, ag->ledBar.getNumberOfLeds() - 4);
|
||||
ag->ledBar.setColor(RGB_COLOR_R, ag->ledBar.getNumberOfLeds() - 5);
|
||||
ag->ledBar.setColor(RGB_COLOR_R, ag->ledBar.getNumberOfLeds() - 6);
|
||||
totalUsed = 6;
|
||||
} else if (co2Value <= 2000) {
|
||||
/** RRRRRRR; 7 */
|
||||
ag->ledBar.setColor(RGB_COLOR_R, ag->ledBar.getNumberOfLeds() - 1);
|
||||
ag->ledBar.setColor(RGB_COLOR_R, ag->ledBar.getNumberOfLeds() - 2);
|
||||
ag->ledBar.setColor(RGB_COLOR_R, ag->ledBar.getNumberOfLeds() - 3);
|
||||
ag->ledBar.setColor(RGB_COLOR_R, ag->ledBar.getNumberOfLeds() - 4);
|
||||
ag->ledBar.setColor(RGB_COLOR_R, ag->ledBar.getNumberOfLeds() - 5);
|
||||
ag->ledBar.setColor(RGB_COLOR_R, ag->ledBar.getNumberOfLeds() - 6);
|
||||
ag->ledBar.setColor(RGB_COLOR_R, ag->ledBar.getNumberOfLeds() - 7);
|
||||
totalUsed = 7;
|
||||
} else if (co2Value <= 3000) {
|
||||
/** PPPPPPPP; 8 */
|
||||
ag->ledBar.setColor(RGB_COLOR_P, ag->ledBar.getNumberOfLeds() - 1);
|
||||
ag->ledBar.setColor(RGB_COLOR_P, ag->ledBar.getNumberOfLeds() - 2);
|
||||
ag->ledBar.setColor(RGB_COLOR_P, ag->ledBar.getNumberOfLeds() - 3);
|
||||
ag->ledBar.setColor(RGB_COLOR_P, ag->ledBar.getNumberOfLeds() - 4);
|
||||
ag->ledBar.setColor(RGB_COLOR_P, ag->ledBar.getNumberOfLeds() - 5);
|
||||
ag->ledBar.setColor(RGB_COLOR_P, ag->ledBar.getNumberOfLeds() - 6);
|
||||
ag->ledBar.setColor(RGB_COLOR_P, ag->ledBar.getNumberOfLeds() - 7);
|
||||
ag->ledBar.setColor(RGB_COLOR_P, ag->ledBar.getNumberOfLeds() - 8);
|
||||
totalUsed = 8;
|
||||
} else { /** > 3000 */
|
||||
/* PRPRPRPRP; 9 */
|
||||
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_P, ag->ledBar.getNumberOfLeds() - 3);
|
||||
ag->ledBar.setColor(RGB_COLOR_R, ag->ledBar.getNumberOfLeds() - 4);
|
||||
ag->ledBar.setColor(RGB_COLOR_P, ag->ledBar.getNumberOfLeds() - 5);
|
||||
ag->ledBar.setColor(RGB_COLOR_R, ag->ledBar.getNumberOfLeds() - 6);
|
||||
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_P, ag->ledBar.getNumberOfLeds() - 9);
|
||||
totalUsed = 9;
|
||||
}
|
||||
|
||||
return totalUsed;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Show PM2.5 LED status
|
||||
*
|
||||
* @return return total number of led that are used on the monitor
|
||||
*/
|
||||
int StateMachine::pm25handleLeds(void) {
|
||||
int totalUsed = ag->ledBar.getNumberOfLeds();
|
||||
|
||||
int pm25Value = round(value.getAverage(Measurements::PM25));
|
||||
if (config.hasSensorSHT && config.isPMCorrectionEnabled()) {
|
||||
pm25Value = round(value.getCorrectedPM25(*ag, config, true));
|
||||
}
|
||||
|
||||
if (pm25Value <= 5) {
|
||||
/** G; 1 */
|
||||
ag->ledBar.setColor(RGB_COLOR_G, ag->ledBar.getNumberOfLeds() - 1);
|
||||
totalUsed = 1;
|
||||
} else if (pm25Value <= 9) {
|
||||
/** GG; 2 */
|
||||
ag->ledBar.setColor(RGB_COLOR_G, ag->ledBar.getNumberOfLeds() - 1);
|
||||
ag->ledBar.setColor(RGB_COLOR_G, ag->ledBar.getNumberOfLeds() - 2);
|
||||
totalUsed = 2;
|
||||
} else if (pm25Value <= 20) {
|
||||
/** YYY; 3 */
|
||||
ag->ledBar.setColor(RGB_COLOR_Y, ag->ledBar.getNumberOfLeds() - 1);
|
||||
ag->ledBar.setColor(RGB_COLOR_Y, ag->ledBar.getNumberOfLeds() - 2);
|
||||
ag->ledBar.setColor(RGB_COLOR_Y, ag->ledBar.getNumberOfLeds() - 3);
|
||||
totalUsed = 3;
|
||||
} else if (pm25Value <= 35) {
|
||||
/** YYYY; 4 */
|
||||
ag->ledBar.setColor(RGB_COLOR_Y, ag->ledBar.getNumberOfLeds() - 1);
|
||||
ag->ledBar.setColor(RGB_COLOR_Y, ag->ledBar.getNumberOfLeds() - 2);
|
||||
ag->ledBar.setColor(RGB_COLOR_Y, ag->ledBar.getNumberOfLeds() - 3);
|
||||
ag->ledBar.setColor(RGB_COLOR_Y, ag->ledBar.getNumberOfLeds() - 4);
|
||||
totalUsed = 4;
|
||||
} else if (pm25Value <= 45) {
|
||||
/** OOOOO; 5 */
|
||||
ag->ledBar.setColor(RGB_COLOR_O, ag->ledBar.getNumberOfLeds() - 1);
|
||||
ag->ledBar.setColor(RGB_COLOR_O, ag->ledBar.getNumberOfLeds() - 2);
|
||||
ag->ledBar.setColor(RGB_COLOR_O, ag->ledBar.getNumberOfLeds() - 3);
|
||||
ag->ledBar.setColor(RGB_COLOR_O, ag->ledBar.getNumberOfLeds() - 4);
|
||||
ag->ledBar.setColor(RGB_COLOR_O, ag->ledBar.getNumberOfLeds() - 5);
|
||||
totalUsed = 5;
|
||||
} else if (pm25Value <= 55) {
|
||||
/** OOOOOO; 6 */
|
||||
ag->ledBar.setColor(RGB_COLOR_O, ag->ledBar.getNumberOfLeds() - 1);
|
||||
ag->ledBar.setColor(RGB_COLOR_O, ag->ledBar.getNumberOfLeds() - 2);
|
||||
ag->ledBar.setColor(RGB_COLOR_O, ag->ledBar.getNumberOfLeds() - 3);
|
||||
ag->ledBar.setColor(RGB_COLOR_O, ag->ledBar.getNumberOfLeds() - 4);
|
||||
ag->ledBar.setColor(RGB_COLOR_O, ag->ledBar.getNumberOfLeds() - 5);
|
||||
ag->ledBar.setColor(RGB_COLOR_O, ag->ledBar.getNumberOfLeds() - 6);
|
||||
totalUsed = 6;
|
||||
} else if (pm25Value <= 100) {
|
||||
/** RRRRRRR; 7 */
|
||||
ag->ledBar.setColor(RGB_COLOR_R, ag->ledBar.getNumberOfLeds() - 1);
|
||||
ag->ledBar.setColor(RGB_COLOR_R, ag->ledBar.getNumberOfLeds() - 2);
|
||||
ag->ledBar.setColor(RGB_COLOR_R, ag->ledBar.getNumberOfLeds() - 3);
|
||||
ag->ledBar.setColor(RGB_COLOR_R, ag->ledBar.getNumberOfLeds() - 4);
|
||||
ag->ledBar.setColor(RGB_COLOR_R, ag->ledBar.getNumberOfLeds() - 5);
|
||||
ag->ledBar.setColor(RGB_COLOR_R, ag->ledBar.getNumberOfLeds() - 6);
|
||||
ag->ledBar.setColor(RGB_COLOR_R, ag->ledBar.getNumberOfLeds() - 7);
|
||||
totalUsed = 7;
|
||||
} else if (pm25Value <= 125) {
|
||||
/** RRRRRRRR; 8 */
|
||||
ag->ledBar.setColor(RGB_COLOR_R, ag->ledBar.getNumberOfLeds() - 1);
|
||||
ag->ledBar.setColor(RGB_COLOR_R, ag->ledBar.getNumberOfLeds() - 2);
|
||||
ag->ledBar.setColor(RGB_COLOR_R, ag->ledBar.getNumberOfLeds() - 3);
|
||||
ag->ledBar.setColor(RGB_COLOR_R, ag->ledBar.getNumberOfLeds() - 4);
|
||||
ag->ledBar.setColor(RGB_COLOR_R, ag->ledBar.getNumberOfLeds() - 5);
|
||||
ag->ledBar.setColor(RGB_COLOR_R, ag->ledBar.getNumberOfLeds() - 6);
|
||||
ag->ledBar.setColor(RGB_COLOR_R, ag->ledBar.getNumberOfLeds() - 7);
|
||||
ag->ledBar.setColor(RGB_COLOR_R, ag->ledBar.getNumberOfLeds() - 8);
|
||||
totalUsed = 8;
|
||||
} else if (pm25Value <= 225) {
|
||||
/** PPPPPPPPP; 9 */
|
||||
ag->ledBar.setColor(RGB_COLOR_P, ag->ledBar.getNumberOfLeds() - 1);
|
||||
ag->ledBar.setColor(RGB_COLOR_P, ag->ledBar.getNumberOfLeds() - 2);
|
||||
ag->ledBar.setColor(RGB_COLOR_P, ag->ledBar.getNumberOfLeds() - 3);
|
||||
ag->ledBar.setColor(RGB_COLOR_P, ag->ledBar.getNumberOfLeds() - 4);
|
||||
ag->ledBar.setColor(RGB_COLOR_P, ag->ledBar.getNumberOfLeds() - 5);
|
||||
ag->ledBar.setColor(RGB_COLOR_P, ag->ledBar.getNumberOfLeds() - 6);
|
||||
ag->ledBar.setColor(RGB_COLOR_P, ag->ledBar.getNumberOfLeds() - 7);
|
||||
ag->ledBar.setColor(RGB_COLOR_P, ag->ledBar.getNumberOfLeds() - 8);
|
||||
ag->ledBar.setColor(RGB_COLOR_P, ag->ledBar.getNumberOfLeds() - 9);
|
||||
totalUsed = 9;
|
||||
} else { /** > 225 */
|
||||
/* PRPRPRPRP; 9 */
|
||||
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_P, ag->ledBar.getNumberOfLeds() - 3);
|
||||
ag->ledBar.setColor(RGB_COLOR_R, ag->ledBar.getNumberOfLeds() - 4);
|
||||
ag->ledBar.setColor(RGB_COLOR_P, ag->ledBar.getNumberOfLeds() - 5);
|
||||
ag->ledBar.setColor(RGB_COLOR_R, ag->ledBar.getNumberOfLeds() - 6);
|
||||
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_P, ag->ledBar.getNumberOfLeds() - 9);
|
||||
totalUsed = 9;
|
||||
}
|
||||
|
||||
return totalUsed;
|
||||
}
|
||||
|
||||
void StateMachine::co2Calibration(void) {
|
||||
if (config.isCo2CalibrationRequested() && config.hasSensorS8) {
|
||||
logInfo("CO2 Calibration");
|
||||
|
||||
/** Count down to 0 then start */
|
||||
for (int i = 0; i < SENSOR_CO2_CALIB_COUNTDOWN_MAX; i++) {
|
||||
if (ag->isOne() || (ag->isPro4_2()) || ag->isPro3_3()) {
|
||||
String str =
|
||||
"after " + String(SENSOR_CO2_CALIB_COUNTDOWN_MAX - i) + " sec";
|
||||
disp.setText("Start CO2 calib", str.c_str(), "");
|
||||
} else if (ag->isBasic()) {
|
||||
String str = String(SENSOR_CO2_CALIB_COUNTDOWN_MAX - i) + " sec";
|
||||
disp.setText("CO2 Calib", "after", str.c_str());
|
||||
} else {
|
||||
logInfo("Start CO2 calib after " +
|
||||
String(SENSOR_CO2_CALIB_COUNTDOWN_MAX - i) + " sec");
|
||||
}
|
||||
delay(1000);
|
||||
}
|
||||
|
||||
if (ag->s8.setBaselineCalibration()) {
|
||||
if (ag->isOne() || (ag->isPro4_2()) || ag->isPro3_3()) {
|
||||
disp.setText("Calibration", "success", "");
|
||||
} else if (ag->isBasic()) {
|
||||
disp.setText("CO2 Calib", "success", "");
|
||||
} else {
|
||||
logInfo("CO2 Calibration: success");
|
||||
}
|
||||
delay(1000);
|
||||
if (ag->isOne() || (ag->isPro4_2()) || ag->isPro3_3() || ag->isBasic()) {
|
||||
disp.setText("Wait for", "calib done", "...");
|
||||
} else {
|
||||
logInfo("CO2 Calibration: Wait for calibration finish...");
|
||||
}
|
||||
|
||||
/** Count down wait for finish */
|
||||
int count = 0;
|
||||
while (ag->s8.isBaseLineCalibrationDone() == false) {
|
||||
delay(1000);
|
||||
count++;
|
||||
}
|
||||
if (ag->isOne() || (ag->isPro4_2()) || ag->isPro3_3() || ag->isBasic()) {
|
||||
String str = "after " + String(count);
|
||||
disp.setText("Calib done", str.c_str(), "sec");
|
||||
} else {
|
||||
logInfo("CO2 Calibration: finish after " + String(count) + " sec");
|
||||
}
|
||||
delay(2000);
|
||||
} else {
|
||||
if (ag->isOne() || (ag->isPro4_2()) || ag->isPro3_3()) {
|
||||
disp.setText("Calibration", "failure!!!", "");
|
||||
} else if (ag->isBasic()) {
|
||||
disp.setText("CO2 calib", "failure!!!", "");
|
||||
} else {
|
||||
logInfo("CO2 Calibration: failure!!!");
|
||||
}
|
||||
delay(2000);
|
||||
}
|
||||
}
|
||||
|
||||
if (config.getCO2CalibrationAbcDays() >= 0 && config.hasSensorS8) {
|
||||
int newHour = config.getCO2CalibrationAbcDays() * 24;
|
||||
int curHour = ag->s8.getAbcPeriod();
|
||||
if (curHour != newHour) {
|
||||
String resultStr = "failure";
|
||||
if (ag->s8.setAbcPeriod(config.getCO2CalibrationAbcDays() * 24)) {
|
||||
resultStr = "successful";
|
||||
}
|
||||
String fromStr = String(curHour / 24) + " days";
|
||||
if (curHour == 0) {
|
||||
fromStr = "off";
|
||||
}
|
||||
String toStr = String(config.getCO2CalibrationAbcDays()) + " days";
|
||||
if (config.getCO2CalibrationAbcDays() == 0) {
|
||||
toStr = "off";
|
||||
}
|
||||
String msg =
|
||||
"Setting S8 from " + fromStr + " to " + toStr + " " + resultStr;
|
||||
logInfo(msg);
|
||||
}
|
||||
} else {
|
||||
logWarning("CO2 S8 not available, set 'abcDays' ignored");
|
||||
}
|
||||
}
|
||||
|
||||
void StateMachine::ledBarTest(void) {
|
||||
if (config.isLedBarTestRequested()) {
|
||||
if (ag->isOne()) {
|
||||
ag->ledBar.clear();
|
||||
if (config.getCountry() == "TH") {
|
||||
uint32_t tstart = millis();
|
||||
logInfo("Start run LED test for 2 min");
|
||||
while (1) {
|
||||
ledBarRunTest();
|
||||
uint32_t ms = (uint32_t)(millis() - tstart);
|
||||
if (ms >= (60 * 1000 * 2)) {
|
||||
logInfo("LED test after 2 min finish");
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ledBarRunTest();
|
||||
}
|
||||
}
|
||||
else if(ag->isOpenAir()) {
|
||||
ledBarRunTest();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void StateMachine::ledBarPowerUpTest(void) {
|
||||
if (ag->isOne()) {
|
||||
ag->ledBar.clear();
|
||||
}
|
||||
ledBarRunTest();
|
||||
}
|
||||
|
||||
void StateMachine::ledBarRunTest(void) {
|
||||
if (ag->isOne()) {
|
||||
disp.setText("LED Test", "running", ".....");
|
||||
runLedTest('r');
|
||||
ag->ledBar.show();
|
||||
delay(1000);
|
||||
runLedTest('g');
|
||||
ag->ledBar.show();
|
||||
delay(1000);
|
||||
runLedTest('b');
|
||||
ag->ledBar.show();
|
||||
delay(1000);
|
||||
runLedTest('w');
|
||||
ag->ledBar.show();
|
||||
delay(1000);
|
||||
runLedTest('n');
|
||||
ag->ledBar.show();
|
||||
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) {
|
||||
int r = 0;
|
||||
int g = 0;
|
||||
int b = 0;
|
||||
switch (color) {
|
||||
case 'g':
|
||||
g = 255;
|
||||
break;
|
||||
case 'y':
|
||||
r = 255;
|
||||
g = 255;
|
||||
break;
|
||||
case 'o':
|
||||
r = 255;
|
||||
g = 128;
|
||||
break;
|
||||
case 'r':
|
||||
r = 255;
|
||||
break;
|
||||
case 'b':
|
||||
b = 255;
|
||||
break;
|
||||
case 'w':
|
||||
r = 255;
|
||||
g = 255;
|
||||
b = 255;
|
||||
break;
|
||||
case 'p':
|
||||
r = 153;
|
||||
b = 153;
|
||||
break;
|
||||
case 'z':
|
||||
r = 102;
|
||||
break;
|
||||
case 'n':
|
||||
default:
|
||||
break;
|
||||
}
|
||||
ag->ledBar.setColor(r, g, b);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Construct a new Ag State Machine:: Ag State Machine object
|
||||
*
|
||||
* @param disp OledDisplay
|
||||
* @param log Serial Stream
|
||||
* @param value Measurements
|
||||
* @param config Configuration
|
||||
*/
|
||||
StateMachine::StateMachine(OledDisplay &disp, Stream &log, Measurements &value,
|
||||
Configuration &config)
|
||||
: PrintLog(log, "StateMachine"), disp(disp), value(value), config(config) {}
|
||||
|
||||
StateMachine::~StateMachine() {}
|
||||
|
||||
/**
|
||||
* @brief OLED display show content from state value
|
||||
*
|
||||
* @param state
|
||||
*/
|
||||
void StateMachine::displayHandle(AgStateMachineState state) {
|
||||
// Ignore handle if not support display
|
||||
if (!(ag->isOne() || (ag->isPro4_2()) || ag->isPro3_3() || ag->isBasic())) {
|
||||
if (state == AgStateMachineCo2Calibration) {
|
||||
co2Calibration();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (state > AgStateMachineNormal) {
|
||||
logError("displayHandle: State invalid");
|
||||
return;
|
||||
}
|
||||
|
||||
dispState = state;
|
||||
|
||||
switch (state) {
|
||||
case AgStateMachineWiFiManagerMode:
|
||||
case AgStateMachineWiFiManagerPortalActive: {
|
||||
if (wifiConnectCountDown >= 0) {
|
||||
if (ag->isBasic()) {
|
||||
String ssid = "\"airgradient-" + ag->deviceId() + "\" " +
|
||||
String(wifiConnectCountDown) + String("s");
|
||||
disp.setText("Connect tohotspot:", ssid.c_str(), "");
|
||||
} else {
|
||||
String line1 = String(wifiConnectCountDown) + "s to connect";
|
||||
String line2 = "to WiFi hotspot:";
|
||||
String line3 = "\"airgradient-";
|
||||
String line4 = ag->deviceId() + "\"";
|
||||
disp.setText(line1, line2, line3, line4);
|
||||
}
|
||||
wifiConnectCountDown--;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case AgStateMachineWiFiManagerStaConnecting: {
|
||||
disp.setText("Trying to", "connect to WiFi", "...");
|
||||
break;
|
||||
}
|
||||
case AgStateMachineWiFiManagerStaConnected: {
|
||||
disp.setText("WiFi connection", "successful", "");
|
||||
break;
|
||||
}
|
||||
case AgStateMachineWiFiOkServerConnecting: {
|
||||
if (ag->isBasic()) {
|
||||
disp.setText("Connecting", "to", "Server...");
|
||||
} else {
|
||||
disp.setText("Connecting to", "Server", "...");
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case AgStateMachineWiFiOkServerConnected: {
|
||||
disp.setText("Server", "connection", "successful");
|
||||
break;
|
||||
}
|
||||
case AgStateMachineWiFiManagerConnectFailed: {
|
||||
disp.setText("WiFi not", "connected", "");
|
||||
break;
|
||||
}
|
||||
case AgStateMachineWiFiOkServerConnectFailed: {
|
||||
// displayShowText("Server not", "reachable", "");
|
||||
break;
|
||||
}
|
||||
case AgStateMachineWiFiOkServerOkSensorConfigFailed: {
|
||||
if (ag->isBasic()) {
|
||||
disp.setText("Monitor", "not on", "dashboard");
|
||||
} else {
|
||||
disp.setText("Monitor not", "setup on", "dashboard");
|
||||
}
|
||||
break;
|
||||
}
|
||||
case AgStateMachineWiFiLost: {
|
||||
disp.showDashboard("WiFi N/A");
|
||||
break;
|
||||
}
|
||||
case AgStateMachineServerLost: {
|
||||
disp.showDashboard("AG Server N/A");
|
||||
break;
|
||||
}
|
||||
case AgStateMachineSensorConfigFailed: {
|
||||
if (addToDashBoard) {
|
||||
uint32_t ms = (uint32_t)(millis() - addToDashboardTime);
|
||||
if (ms >= 5000) {
|
||||
addToDashboardTime = millis();
|
||||
if (addToDashBoardToggle) {
|
||||
disp.showDashboard("Add to AG Dashb.");
|
||||
} else {
|
||||
disp.showDashboard(ag->deviceId().c_str());
|
||||
}
|
||||
addToDashBoardToggle = !addToDashBoardToggle;
|
||||
}
|
||||
} else {
|
||||
disp.showDashboard("");
|
||||
}
|
||||
break;
|
||||
}
|
||||
case AgStateMachineNormal: {
|
||||
disp.showDashboard();
|
||||
break;
|
||||
}
|
||||
case AgStateMachineCo2Calibration:
|
||||
co2Calibration();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief OLED display show content as previous state updated
|
||||
*
|
||||
*/
|
||||
void StateMachine::displayHandle(void) { displayHandle(dispState); }
|
||||
|
||||
/**
|
||||
* @brief Update status add to dashboard
|
||||
*
|
||||
*/
|
||||
void StateMachine::displaySetAddToDashBoard(void) {
|
||||
if (addToDashBoard == false) {
|
||||
addToDashboardTime = 0;
|
||||
addToDashBoardToggle = true;
|
||||
}
|
||||
addToDashBoard = true;
|
||||
}
|
||||
|
||||
void StateMachine::displayClearAddToDashBoard(void) { addToDashBoard = false; }
|
||||
|
||||
/**
|
||||
* @brief Set WiFi connection coundown on dashboard
|
||||
*
|
||||
* @param count Seconds
|
||||
*/
|
||||
void StateMachine::displayWiFiConnectCountDown(int count) {
|
||||
wifiConnectCountDown = count;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Init before start LED bar animation
|
||||
*
|
||||
*/
|
||||
void StateMachine::ledAnimationInit(void) { ledBarAnimationCount = -1; }
|
||||
|
||||
/**
|
||||
* @brief Handle LED from state, only handle LED if board type is: One Indoor or
|
||||
* Open Air
|
||||
*
|
||||
* @param state
|
||||
*/
|
||||
void StateMachine::handleLeds(AgStateMachineState state) {
|
||||
/** Ignore if board type if not ONE_INDOOR or OPEN_AIR_OUTDOOR */
|
||||
if ((ag->getBoardType() != BoardType::ONE_INDOOR) &&
|
||||
(ag->getBoardType() != BoardType::OPEN_AIR_OUTDOOR)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (state > AgStateMachineNormal) {
|
||||
logError("ledHandle: state invalid");
|
||||
return;
|
||||
}
|
||||
|
||||
ledState = state;
|
||||
switch (state) {
|
||||
case AgStateMachineWiFiManagerMode: {
|
||||
/** In WiFi Manager Mode */
|
||||
/** Turn LED OFF */
|
||||
/** Turn middle LED Color */
|
||||
if (ag->isOne()) {
|
||||
ag->ledBar.clear();
|
||||
ag->ledBar.setColor(0, 0, 255, ag->ledBar.getNumberOfLeds() / 2);
|
||||
} else {
|
||||
ag->statusLed.setToggle();
|
||||
}
|
||||
break;
|
||||
}
|
||||
case AgStateMachineWiFiManagerPortalActive: {
|
||||
/** WiFi Manager has connected to mobile phone */
|
||||
if (ag->isOne()) {
|
||||
ag->ledBar.clear();
|
||||
ag->ledBar.setColor(0, 0, 255);
|
||||
} else {
|
||||
ag->statusLed.setOn();
|
||||
}
|
||||
break;
|
||||
}
|
||||
case AgStateMachineWiFiManagerStaConnecting: {
|
||||
/** after SSID and PW entered and OK clicked, connection to WiFI network is
|
||||
* attempted */
|
||||
if (ag->isOne()) {
|
||||
ag->ledBar.clear();
|
||||
ledBarSingleLedAnimation(255, 255, 255);
|
||||
} else {
|
||||
ag->statusLed.setOff();
|
||||
}
|
||||
break;
|
||||
}
|
||||
case AgStateMachineWiFiManagerStaConnected: {
|
||||
/** Connecting to WiFi worked */
|
||||
if (ag->isOne()) {
|
||||
ag->ledBar.clear();
|
||||
ag->ledBar.setColor(255, 255, 255);
|
||||
} else {
|
||||
ag->statusLed.setOff();
|
||||
}
|
||||
break;
|
||||
}
|
||||
case AgStateMachineWiFiOkServerConnecting: {
|
||||
/** once connected to WiFi an attempt to reach the server is performed */
|
||||
if (ag->isOne()) {
|
||||
ag->ledBar.clear();
|
||||
ledBarSingleLedAnimation(0, 255, 0);
|
||||
} else {
|
||||
ag->statusLed.setOff();
|
||||
}
|
||||
break;
|
||||
}
|
||||
case AgStateMachineWiFiOkServerConnected: {
|
||||
/** Server is reachable, all fine */
|
||||
if (ag->isOne()) {
|
||||
ag->ledBar.clear();
|
||||
ag->ledBar.setColor(0, 255, 0);
|
||||
} else {
|
||||
ag->statusLed.setOff();
|
||||
|
||||
/** two time slow blink, then off */
|
||||
for (int i = 0; i < 2; i++) {
|
||||
ledStatusBlinkDelay(LED_SLOW_BLINK_DELAY);
|
||||
}
|
||||
|
||||
ag->statusLed.setOff();
|
||||
}
|
||||
break;
|
||||
}
|
||||
case AgStateMachineWiFiManagerConnectFailed: {
|
||||
/** Cannot connect to WiFi (e.g. wrong password, WPA Enterprise etc.) */
|
||||
if (ag->isOne()) {
|
||||
ag->ledBar.clear();
|
||||
ag->ledBar.setColor(255, 0, 0);
|
||||
} else {
|
||||
ag->statusLed.setOff();
|
||||
|
||||
for (int j = 0; j < 3; j++) {
|
||||
for (int i = 0; i < 3; i++) {
|
||||
ledStatusBlinkDelay(LED_FAST_BLINK_DELAY);
|
||||
}
|
||||
delay(2000);
|
||||
}
|
||||
ag->statusLed.setOff();
|
||||
}
|
||||
break;
|
||||
}
|
||||
case AgStateMachineWiFiOkServerConnectFailed: {
|
||||
/** Connected to WiFi but server not reachable, e.g. firewall block/
|
||||
* whitelisting needed etc. */
|
||||
if (ag->isOne()) {
|
||||
ag->ledBar.clear();
|
||||
ag->ledBar.setColor(233, 183, 54); /** orange */
|
||||
} else {
|
||||
ag->statusLed.setOff();
|
||||
for (int j = 0; j < 3; j++) {
|
||||
for (int i = 0; i < 4; i++) {
|
||||
ledStatusBlinkDelay(LED_FAST_BLINK_DELAY);
|
||||
}
|
||||
delay(2000);
|
||||
}
|
||||
ag->statusLed.setOff();
|
||||
}
|
||||
break;
|
||||
}
|
||||
case AgStateMachineWiFiOkServerOkSensorConfigFailed: {
|
||||
/** Server reachable but sensor not configured correctly */
|
||||
if (ag->isOne()) {
|
||||
ag->ledBar.clear();
|
||||
ag->ledBar.setColor(139, 24, 248); /** violet */
|
||||
} else {
|
||||
ag->statusLed.setOff();
|
||||
for (int j = 0; j < 3; j++) {
|
||||
for (int i = 0; i < 5; i++) {
|
||||
ledStatusBlinkDelay(LED_FAST_BLINK_DELAY);
|
||||
}
|
||||
delay(2000);
|
||||
}
|
||||
ag->statusLed.setOff();
|
||||
}
|
||||
break;
|
||||
}
|
||||
case AgStateMachineWiFiLost: {
|
||||
/** Connection to WiFi network failed credentials incorrect encryption not
|
||||
* supported etc. */
|
||||
if (ag->isOne()) {
|
||||
bool allUsed = sensorhandleLeds();
|
||||
if (allUsed == false) {
|
||||
ag->ledBar.setColor(255, 0, 0, 0);
|
||||
}
|
||||
} else {
|
||||
ag->statusLed.setOff();
|
||||
}
|
||||
break;
|
||||
}
|
||||
case AgStateMachineServerLost: {
|
||||
/** Connected to WiFi network but the server cannot be reached through the
|
||||
* internet, e.g. blocked by firewall */
|
||||
if (ag->isOne()) {
|
||||
bool allUsed = sensorhandleLeds();
|
||||
if (allUsed == false) {
|
||||
ag->ledBar.setColor(233, 183, 54, 0);
|
||||
}
|
||||
} else {
|
||||
ag->statusLed.setOff();
|
||||
}
|
||||
break;
|
||||
}
|
||||
case AgStateMachineSensorConfigFailed: {
|
||||
/** Server is reachable but there is some configuration issue to be fixed on
|
||||
* the server side */
|
||||
if (ag->isOne()) {
|
||||
bool allUsed = sensorhandleLeds();
|
||||
if (allUsed == false) {
|
||||
ag->ledBar.setColor(139, 24, 248, 0);
|
||||
}
|
||||
} else {
|
||||
ag->statusLed.setOff();
|
||||
}
|
||||
break;
|
||||
}
|
||||
case AgStateMachineNormal: {
|
||||
if (ag->isOne()) {
|
||||
sensorhandleLeds();
|
||||
} else {
|
||||
ag->statusLed.setOff();
|
||||
}
|
||||
break;
|
||||
}
|
||||
case AgStateMachineLedBarTest:
|
||||
ledBarTest();
|
||||
break;
|
||||
case AgStateMachineLedBarPowerUpTest:
|
||||
ledBarPowerUpTest();
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
// Show LED bar color
|
||||
if (ag->isOne()) {
|
||||
ag->ledBar.show();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Handle LED as previous state updated
|
||||
*
|
||||
*/
|
||||
void StateMachine::handleLeds(void) { handleLeds(ledState); }
|
||||
|
||||
/**
|
||||
* @brief Set display state
|
||||
*
|
||||
* @param state
|
||||
*/
|
||||
void StateMachine::setDisplayState(AgStateMachineState state) {
|
||||
dispState = state;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get current display state
|
||||
*
|
||||
* @return AgStateMachineState
|
||||
*/
|
||||
AgStateMachineState StateMachine::getDisplayState(void) { return dispState; }
|
||||
|
||||
/**
|
||||
* @brief Set AirGradient instance
|
||||
*
|
||||
* @param ag Point to AirGradient instance
|
||||
*/
|
||||
void StateMachine::setAirGradient(AirGradient *ag) { this->ag = ag; }
|
||||
|
||||
/**
|
||||
* @brief Get current LED state
|
||||
*
|
||||
* @return AgStateMachineState
|
||||
*/
|
||||
AgStateMachineState StateMachine::getLedState(void) { return ledState; }
|
||||
|
||||
void StateMachine::executeCo2Calibration(void) {
|
||||
displayHandle(AgStateMachineCo2Calibration);
|
||||
}
|
||||
|
||||
void StateMachine::executeLedBarTest(void) {
|
||||
handleLeds(AgStateMachineLedBarTest);
|
||||
}
|
||||
|
||||
void StateMachine::executeLedBarPowerUpTest(void) {
|
||||
handleLeds(AgStateMachineLedBarPowerUpTest);
|
||||
}
|
57
src/AgStateMachine.h
Normal file
57
src/AgStateMachine.h
Normal file
@ -0,0 +1,57 @@
|
||||
#ifndef _AG_STATE_MACHINE_H_
|
||||
#define _AG_STATE_MACHINE_H_
|
||||
|
||||
#include "AgOledDisplay.h"
|
||||
#include "AgValue.h"
|
||||
#include "AgConfigure.h"
|
||||
#include "Main/PrintLog.h"
|
||||
#include "App/AppDef.h"
|
||||
|
||||
class StateMachine : public PrintLog {
|
||||
private:
|
||||
// AgStateMachineState state;
|
||||
AgStateMachineState ledState;
|
||||
AgStateMachineState dispState;
|
||||
AirGradient *ag;
|
||||
OledDisplay &disp;
|
||||
Measurements &value;
|
||||
Configuration &config;
|
||||
bool addToDashBoard = false;
|
||||
bool addToDashBoardToggle = false;
|
||||
uint32_t addToDashboardTime;
|
||||
int wifiConnectCountDown;
|
||||
int ledBarAnimationCount;
|
||||
|
||||
void ledBarSingleLedAnimation(uint8_t r, uint8_t g, uint8_t b);
|
||||
void ledStatusBlinkDelay(uint32_t delay);
|
||||
bool sensorhandleLeds(void);
|
||||
int co2handleLeds(void);
|
||||
int pm25handleLeds(void);
|
||||
void co2Calibration(void);
|
||||
void ledBarTest(void);
|
||||
void ledBarPowerUpTest(void);
|
||||
void ledBarRunTest(void);
|
||||
void runLedTest(char color);
|
||||
|
||||
public:
|
||||
StateMachine(OledDisplay &disp, Stream &log,
|
||||
Measurements &value, Configuration& config);
|
||||
~StateMachine();
|
||||
void setAirGradient(AirGradient* ag);
|
||||
void displayHandle(AgStateMachineState state);
|
||||
void displayHandle(void);
|
||||
void displaySetAddToDashBoard(void);
|
||||
void displayClearAddToDashBoard(void);
|
||||
void displayWiFiConnectCountDown(int count);
|
||||
void ledAnimationInit(void);
|
||||
void handleLeds(AgStateMachineState state);
|
||||
void handleLeds(void);
|
||||
void setDisplayState(AgStateMachineState state);
|
||||
AgStateMachineState getDisplayState(void);
|
||||
AgStateMachineState getLedState(void);
|
||||
void executeCo2Calibration(void);
|
||||
void executeLedBarTest(void);
|
||||
void executeLedBarPowerUpTest(void);
|
||||
};
|
||||
|
||||
#endif /** _AG_STATE_MACHINE_H_ */
|
1068
src/AgValue.cpp
Normal file
1068
src/AgValue.cpp
Normal file
File diff suppressed because it is too large
Load Diff
217
src/AgValue.h
Normal file
217
src/AgValue.h
Normal file
@ -0,0 +1,217 @@
|
||||
#ifndef _AG_VALUE_H_
|
||||
#define _AG_VALUE_H_
|
||||
|
||||
#include "AgConfigure.h"
|
||||
#include "AirGradient.h"
|
||||
#include "App/AppDef.h"
|
||||
#include "Libraries/Arduino_JSON/src/Arduino_JSON.h"
|
||||
#include "Main/utils.h"
|
||||
#include <Arduino.h>
|
||||
#include <vector>
|
||||
|
||||
class Measurements {
|
||||
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:
|
||||
Measurements() {}
|
||||
~Measurements() {}
|
||||
|
||||
// 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 the Corrected PM25 object based on the correction algorithm from configuration
|
||||
*
|
||||
* If correction is not enabled, then will return the raw value (either average or last value)
|
||||
*
|
||||
* @param ag AirGradient instance
|
||||
* @param config Configuration instance
|
||||
* @param useAvg Use moving average value if true, otherwise use latest value
|
||||
* @param ch MeasurementType channel
|
||||
* @return float Corrected PM2.5 value
|
||||
*/
|
||||
float getCorrectedPM25(AirGradient &ag, Configuration &config, bool useAvg = false, int ch = 1);
|
||||
|
||||
/**
|
||||
* build json payload for every measurements
|
||||
*/
|
||||
String toString(bool localServer, AgFirmwareMode fwMode, int rssi, AirGradient &ag,
|
||||
Configuration &config);
|
||||
|
||||
/**
|
||||
* Set to true if want to debug every update value
|
||||
*/
|
||||
void setDebug(bool debug);
|
||||
|
||||
// TODO: update this to use setter
|
||||
int bootCount;
|
||||
|
||||
private:
|
||||
// Some declared as an array (channel), because FW_MODE_O_1PPx has two PMS5003T
|
||||
FloatValue _temperature[2];
|
||||
FloatValue _humidity[2];
|
||||
IntegerValue _co2;
|
||||
IntegerValue _tvoc; // Index value
|
||||
IntegerValue _tvoc_raw;
|
||||
IntegerValue _nox; // Index value
|
||||
IntegerValue _nox_raw;
|
||||
IntegerValue _pm_01[2]; // pm 1.0 atmospheric environment
|
||||
IntegerValue _pm_25[2]; // pm 2.5 atmospheric environment
|
||||
IntegerValue _pm_10[2]; // pm 10 atmospheric environment
|
||||
IntegerValue _pm_01_sp[2]; // pm 1.0 standard particle
|
||||
IntegerValue _pm_25_sp[2]; // pm 2.5 standard particle
|
||||
IntegerValue _pm_10_sp[2]; // pm 10 standard particle
|
||||
IntegerValue _pm_03_pc[2]; // particle count 0.3
|
||||
IntegerValue _pm_05_pc[2]; // particle count 0.5
|
||||
IntegerValue _pm_01_pc[2]; // particle count 1.0
|
||||
IntegerValue _pm_25_pc[2]; // particle count 2.5
|
||||
IntegerValue _pm_5_pc[2]; // particle count 5.0
|
||||
IntegerValue _pm_10_pc[2]; // particle count 10
|
||||
|
||||
bool _debug = false;
|
||||
|
||||
/**
|
||||
* @brief Get PMS5003 firmware version string
|
||||
*
|
||||
* @param fwCode
|
||||
* @return String
|
||||
*/
|
||||
String pms5003FirmwareVersion(int fwCode);
|
||||
/**
|
||||
* @brief Get PMS5003T firmware version string
|
||||
*
|
||||
* @param fwCode
|
||||
* @return String
|
||||
*/
|
||||
String pms5003TFirmwareVersion(int fwCode);
|
||||
/**
|
||||
* @brief Get firmware version string
|
||||
*
|
||||
* @param prefix Prefix firmware string
|
||||
* @param fwCode Version code
|
||||
* @return string
|
||||
*/
|
||||
String pms5003FirmwareVersionBase(String prefix, int fwCode);
|
||||
|
||||
/**
|
||||
* Convert AgValue Type to string representation of the value
|
||||
*/
|
||||
String measurementTypeStr(MeasurementType type);
|
||||
|
||||
/**
|
||||
* @brief check if provided channel is a valid channel or not
|
||||
* abort program if invalid
|
||||
*/
|
||||
void validateChannel(int ch);
|
||||
|
||||
JSONVar buildOutdoor(bool localServer, AgFirmwareMode fwMode, AirGradient &ag,
|
||||
Configuration &config);
|
||||
JSONVar buildIndoor(bool localServer, AirGradient &ag, Configuration &config);
|
||||
JSONVar buildPMS(AirGradient &ag, int ch, bool allCh, bool withTempHum, bool compensate);
|
||||
};
|
||||
|
||||
#endif /** _AG_VALUE_H_ */
|
415
src/AgWiFiConnector.cpp
Normal file
415
src/AgWiFiConnector.cpp
Normal file
@ -0,0 +1,415 @@
|
||||
#include "AgWiFiConnector.h"
|
||||
#include "Libraries/WiFiManager/WiFiManager.h"
|
||||
|
||||
#define WIFI_CONNECT_COUNTDOWN_MAX 180
|
||||
#define WIFI_HOTSPOT_PASSWORD_DEFAULT "cleanair"
|
||||
|
||||
#define WIFI() ((WiFiManager *)(this->wifi))
|
||||
|
||||
/**
|
||||
* @brief Set reference AirGradient instance
|
||||
*
|
||||
* @param ag Point to AirGradient instance
|
||||
*/
|
||||
void WifiConnector::setAirGradient(AirGradient *ag) { this->ag = ag; }
|
||||
|
||||
/**
|
||||
* @brief Construct a new Ag Wi Fi Connector:: Ag Wi Fi Connector object
|
||||
*
|
||||
* @param disp OledDisplay
|
||||
* @param log Stream
|
||||
* @param sm StateMachine
|
||||
*/
|
||||
WifiConnector::WifiConnector(OledDisplay &disp, Stream &log, StateMachine &sm,
|
||||
Configuration &config)
|
||||
: PrintLog(log, "WifiConnector"), disp(disp), sm(sm), config(config) {}
|
||||
|
||||
WifiConnector::~WifiConnector() {}
|
||||
|
||||
/**
|
||||
* @brief Connection to WIFI AP process. Just call one times
|
||||
*
|
||||
* @return true Success
|
||||
* @return false Failure
|
||||
*/
|
||||
bool WifiConnector::connect(void) {
|
||||
if (wifi == NULL) {
|
||||
wifi = new WiFiManager();
|
||||
if (wifi == NULL) {
|
||||
logError("Create 'WiFiManger' failed");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
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()->setConnectTimeout(15);
|
||||
WIFI()->setTimeout(WIFI_CONNECT_COUNTDOWN_MAX);
|
||||
|
||||
WIFI()->setAPCallback([this](WiFiManager *obj) { _wifiApCallback(); });
|
||||
WIFI()->setSaveConfigCallback([this]() { _wifiSaveConfig(); });
|
||||
WIFI()->setSaveParamsCallback([this]() { _wifiSaveParamCallback(); });
|
||||
WIFI()->setConfigPortalTimeoutCallback([this]() {_wifiTimeoutCallback();});
|
||||
if (ag->isOne() || (ag->isPro4_2()) || ag->isPro3_3() || ag->isBasic()) {
|
||||
disp.setText("Connecting to", "WiFi", "...");
|
||||
} else {
|
||||
logInfo("Connecting to WiFi...");
|
||||
}
|
||||
ssid = "airgradient-" + ag->deviceId();
|
||||
|
||||
// ssid = "AG-" + String(ESP.getChipId(), HEX);
|
||||
WIFI()->setConfigPortalTimeout(WIFI_CONNECT_COUNTDOWN_MAX);
|
||||
|
||||
WiFiManagerParameter postToAg("chbPostToAg",
|
||||
"Prevent Connection to AirGradient Server", "T",
|
||||
2, "type=\"checkbox\" ", WFM_LABEL_AFTER);
|
||||
WIFI()->addParameter(&postToAg);
|
||||
WiFiManagerParameter postToAgInfo(
|
||||
"<p>Prevent connection to the AirGradient Server. Important: Only enable "
|
||||
"it if you are sure you don't want to use any AirGradient cloud "
|
||||
"features. As a result you will not receive automatic firmware updates "
|
||||
"and your data will not reach the AirGradient dashboard.</p>");
|
||||
WIFI()->addParameter(&postToAgInfo);
|
||||
|
||||
WIFI()->autoConnect(ssid.c_str(), WIFI_HOTSPOT_PASSWORD_DEFAULT);
|
||||
|
||||
logInfo("Wait for configure portal");
|
||||
|
||||
#ifdef ESP32
|
||||
// Task handle WiFi connection.
|
||||
xTaskCreate(
|
||||
[](void *obj) {
|
||||
WifiConnector *connector = (WifiConnector *)obj;
|
||||
while (connector->_wifiConfigPortalActive()) {
|
||||
connector->_wifiProcess();
|
||||
vTaskDelay(1);
|
||||
}
|
||||
vTaskDelete(NULL);
|
||||
},
|
||||
"wifi_cfg", 4096, this, 10, NULL);
|
||||
|
||||
/** Wait for WiFi connect and show LED, display status */
|
||||
uint32_t dispPeriod = millis();
|
||||
uint32_t ledPeriod = millis();
|
||||
bool clientConnectChanged = false;
|
||||
|
||||
AgStateMachineState stateOld = sm.getDisplayState();
|
||||
while (WIFI()->getConfigPortalActive()) {
|
||||
/** LED animatoin and display update content */
|
||||
if (WiFi.isConnected() == false) {
|
||||
/** Display countdown */
|
||||
uint32_t ms;
|
||||
if (ag->isOne()) {
|
||||
ms = (uint32_t)(millis() - dispPeriod);
|
||||
if (ms >= 1000) {
|
||||
dispPeriod = millis();
|
||||
sm.displayHandle();
|
||||
} else {
|
||||
if (stateOld != sm.getDisplayState()) {
|
||||
stateOld = sm.getDisplayState();
|
||||
sm.displayHandle();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** LED animations */
|
||||
ms = (uint32_t)(millis() - ledPeriod);
|
||||
if (ms >= 100) {
|
||||
ledPeriod = millis();
|
||||
sm.handleLeds();
|
||||
}
|
||||
|
||||
/** Check for client connect to change led color */
|
||||
bool clientConnected = wifiClientConnected();
|
||||
if (clientConnected != clientConnectChanged) {
|
||||
clientConnectChanged = clientConnected;
|
||||
if (clientConnectChanged) {
|
||||
sm.handleLeds(AgStateMachineWiFiManagerPortalActive);
|
||||
} else {
|
||||
sm.ledAnimationInit();
|
||||
sm.handleLeds(AgStateMachineWiFiManagerMode);
|
||||
if (ag->isOne()) {
|
||||
sm.displayHandle(AgStateMachineWiFiManagerMode);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
delay(1); // avoid watchdog timer reset.
|
||||
}
|
||||
#else
|
||||
_wifiProcess();
|
||||
#endif
|
||||
|
||||
/** Show display wifi connect result failed */
|
||||
if (WiFi.isConnected() == false) {
|
||||
sm.handleLeds(AgStateMachineWiFiManagerConnectFailed);
|
||||
if (ag->isOne() || ag->isPro4_2() || ag->isPro3_3() || ag->isBasic()) {
|
||||
sm.displayHandle(AgStateMachineWiFiManagerConnectFailed);
|
||||
}
|
||||
delay(6000);
|
||||
} else {
|
||||
hasConfig = true;
|
||||
logInfo("WiFi Connected: " + WiFi.SSID() + " IP: " + localIpStr());
|
||||
|
||||
if (hasPortalConfig) {
|
||||
String result = String(postToAg.getValue());
|
||||
logInfo("Setting postToAirGradient set from " +
|
||||
String(config.isPostDataToAirGradient() ? "True" : "False") +
|
||||
String(" to ") + String(result != "T" ? "True" : "False") +
|
||||
String(" successful"));
|
||||
config.setPostToAirGradient(result != "T");
|
||||
}
|
||||
hasPortalConfig = false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Disconnect to current connected WiFi AP
|
||||
*
|
||||
*/
|
||||
void WifiConnector::disconnect(void) {
|
||||
if (WiFi.isConnected()) {
|
||||
logInfo("Disconnect");
|
||||
WiFi.disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Has wifi STA connected to WIFI softAP (this device)
|
||||
*
|
||||
* @return true Connected
|
||||
* @return false Not connected
|
||||
*/
|
||||
bool WifiConnector::wifiClientConnected(void) {
|
||||
return WiFi.softAPgetStationNum() ? true : false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Handle WiFiManage softAP setup completed callback
|
||||
*
|
||||
*/
|
||||
void WifiConnector::_wifiApCallback(void) {
|
||||
sm.displayWiFiConnectCountDown(WIFI_CONNECT_COUNTDOWN_MAX);
|
||||
sm.setDisplayState(AgStateMachineWiFiManagerMode);
|
||||
sm.ledAnimationInit();
|
||||
sm.handleLeds(AgStateMachineWiFiManagerMode);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Handle WiFiManager save configuration callback
|
||||
*
|
||||
*/
|
||||
void WifiConnector::_wifiSaveConfig(void) {
|
||||
sm.setDisplayState(AgStateMachineWiFiManagerStaConnected);
|
||||
sm.handleLeds(AgStateMachineWiFiManagerStaConnected);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Handle WiFiManager save parameter callback
|
||||
*
|
||||
*/
|
||||
void WifiConnector::_wifiSaveParamCallback(void) {
|
||||
sm.ledAnimationInit();
|
||||
sm.handleLeds(AgStateMachineWiFiManagerStaConnecting);
|
||||
sm.setDisplayState(AgStateMachineWiFiManagerStaConnecting);
|
||||
hasPortalConfig = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check that WiFiManager Configure portal active
|
||||
*
|
||||
* @return true Active
|
||||
* @return false Not-Active
|
||||
*/
|
||||
bool WifiConnector::_wifiConfigPortalActive(void) {
|
||||
return WIFI()->getConfigPortalActive();
|
||||
}
|
||||
void WifiConnector::_wifiTimeoutCallback(void) { connectorTimeout = true; }
|
||||
|
||||
/**
|
||||
* @brief Process WiFiManager connection
|
||||
*
|
||||
*/
|
||||
void WifiConnector::_wifiProcess() {
|
||||
#ifdef ESP32
|
||||
WIFI()->process();
|
||||
#else
|
||||
/** Wait for WiFi connect and show LED, display status */
|
||||
uint32_t dispPeriod = millis();
|
||||
uint32_t ledPeriod = millis();
|
||||
bool clientConnectChanged = false;
|
||||
AgStateMachineState stateOld = sm.getDisplayState();
|
||||
|
||||
while (WIFI()->getConfigPortalActive()) {
|
||||
WIFI()->process();
|
||||
|
||||
if (WiFi.isConnected() == false) {
|
||||
/** Display countdown */
|
||||
uint32_t ms;
|
||||
if (ag->isOne() || (ag->isPro4_2()) || ag->isPro3_3() || ag->isBasic()) {
|
||||
ms = (uint32_t)(millis() - dispPeriod);
|
||||
if (ms >= 1000) {
|
||||
dispPeriod = millis();
|
||||
sm.displayHandle();
|
||||
logInfo("displayHandle state: " + String(sm.getDisplayState()));
|
||||
} else {
|
||||
if (stateOld != sm.getDisplayState()) {
|
||||
stateOld = sm.getDisplayState();
|
||||
sm.displayHandle();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** LED animations */
|
||||
ms = (uint32_t)(millis() - ledPeriod);
|
||||
if (ms >= 100) {
|
||||
ledPeriod = millis();
|
||||
sm.handleLeds();
|
||||
}
|
||||
|
||||
/** Check for client connect to change led color */
|
||||
bool clientConnected = wifiClientConnected();
|
||||
if (clientConnected != clientConnectChanged) {
|
||||
clientConnectChanged = clientConnected;
|
||||
if (clientConnectChanged) {
|
||||
sm.handleLeds(AgStateMachineWiFiManagerPortalActive);
|
||||
} else {
|
||||
sm.ledAnimationInit();
|
||||
sm.handleLeds(AgStateMachineWiFiManagerMode);
|
||||
if (ag->isOne()) {
|
||||
sm.displayHandle(AgStateMachineWiFiManagerMode);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
delay(1);
|
||||
}
|
||||
|
||||
// TODO This is for basic
|
||||
if (ag->getBoardType() == DIY_BASIC) {
|
||||
if (!WiFi.isConnected()) {
|
||||
// disp.setText("Booting", "offline", "mode");
|
||||
Serial.println("failed to connect and hit timeout");
|
||||
delay(2500);
|
||||
} else {
|
||||
hasConfig = true;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Handle and reconnect WiFi
|
||||
*
|
||||
*/
|
||||
void WifiConnector::handle(void) {
|
||||
// Ignore if WiFi is not configured
|
||||
if (hasConfig == false) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (WiFi.isConnected()) {
|
||||
lastRetry = millis();
|
||||
return;
|
||||
}
|
||||
|
||||
/** Retry connect WiFi each 10sec */
|
||||
uint32_t ms = (uint32_t)(millis() - lastRetry);
|
||||
if (ms >= 10000) {
|
||||
lastRetry = millis();
|
||||
WiFi.reconnect();
|
||||
logInfo("Re-Connect WiFi");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Is WiFi connected
|
||||
*
|
||||
* @return true Connected
|
||||
* @return false Disconnected
|
||||
*/
|
||||
bool WifiConnector::isConnected(void) { return WiFi.isConnected(); }
|
||||
|
||||
/**
|
||||
* @brief Reset WiFi configuretion and connection, disconnect wifi before call
|
||||
* this method
|
||||
*
|
||||
*/
|
||||
void WifiConnector::reset(void) {
|
||||
if(this->wifi == NULL) {
|
||||
this->wifi = new WiFiManager();
|
||||
if(this->wifi == NULL){
|
||||
logInfo("reset failed");
|
||||
return;
|
||||
}
|
||||
}
|
||||
WIFI()->resetSettings();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get wifi RSSI
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
int WifiConnector::RSSI(void) { return WiFi.RSSI(); }
|
||||
|
||||
/**
|
||||
* @brief Get WIFI IP as string format ex: 192.168.1.1
|
||||
*
|
||||
* @return String
|
||||
*/
|
||||
String WifiConnector::localIpStr(void) { return WiFi.localIP().toString(); }
|
||||
|
||||
/**
|
||||
* @brief Get status that wifi has configurated
|
||||
*
|
||||
* @return true Configurated
|
||||
* @return false Not Configurated
|
||||
*/
|
||||
bool WifiConnector::hasConfigurated(void) {
|
||||
if (WiFi.SSID().isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get WiFi connection porttal timeout.
|
||||
*
|
||||
* @return true
|
||||
* @return false
|
||||
*/
|
||||
bool WifiConnector::isConfigurePorttalTimeout(void) { return connectorTimeout; }
|
||||
|
||||
/**
|
||||
* @brief Set wifi connect to default WiFi
|
||||
*
|
||||
*/
|
||||
void WifiConnector::setDefault(void) {
|
||||
WiFi.begin("airgradient", "cleanair");
|
||||
}
|
55
src/AgWiFiConnector.h
Normal file
55
src/AgWiFiConnector.h
Normal file
@ -0,0 +1,55 @@
|
||||
#ifndef _AG_WIFI_CONNECTOR_H_
|
||||
#define _AG_WIFI_CONNECTOR_H_
|
||||
|
||||
#include "AgOledDisplay.h"
|
||||
#include "AgStateMachine.h"
|
||||
#include "AirGradient.h"
|
||||
#include "AgConfigure.h"
|
||||
#include "Main/PrintLog.h"
|
||||
|
||||
#include <Arduino.h>
|
||||
|
||||
class WifiConnector : public PrintLog {
|
||||
private:
|
||||
AirGradient *ag;
|
||||
OledDisplay &disp;
|
||||
StateMachine &sm;
|
||||
Configuration &config;
|
||||
|
||||
String ssid;
|
||||
void *wifi = NULL;
|
||||
bool hasConfig;
|
||||
uint32_t lastRetry;
|
||||
bool hasPortalConfig = false;
|
||||
bool connectorTimeout = false;
|
||||
|
||||
bool wifiClientConnected(void);
|
||||
|
||||
public:
|
||||
void setAirGradient(AirGradient *ag);
|
||||
|
||||
WifiConnector(OledDisplay &disp, Stream &log, StateMachine &sm, Configuration& config);
|
||||
~WifiConnector();
|
||||
|
||||
bool connect(void);
|
||||
void disconnect(void);
|
||||
void handle(void);
|
||||
void _wifiApCallback(void);
|
||||
void _wifiSaveConfig(void);
|
||||
void _wifiSaveParamCallback(void);
|
||||
bool _wifiConfigPortalActive(void);
|
||||
void _wifiTimeoutCallback(void);
|
||||
void _wifiProcess();
|
||||
bool isConnected(void);
|
||||
void reset(void);
|
||||
int RSSI(void);
|
||||
String localIpStr(void);
|
||||
bool hasConfigurated(void);
|
||||
bool isConfigurePorttalTimeout(void);
|
||||
|
||||
const char* defaultSsid = "airgradient";
|
||||
const char* defaultPassword = "cleanair";
|
||||
void setDefault(void);
|
||||
};
|
||||
|
||||
#endif /** _AG_WIFI_CONNECTOR_H_ */
|
@ -1,11 +1,14 @@
|
||||
#include "AirGradient.h"
|
||||
|
||||
#define AG_LIB_VER "3.0.0"
|
||||
#ifdef ESP8266
|
||||
#include <ESP8266WiFi.h>
|
||||
#else
|
||||
#include "WiFi.h"
|
||||
#endif
|
||||
|
||||
AirGradient::AirGradient(BoardType type)
|
||||
: pms5003(type), pms5003t_1(type), pms5003t_2(type), s8(type), sht(type), sgp41(type),
|
||||
: pms5003(type), pms5003t_1(type), pms5003t_2(type), s8(type), sgp41(type),
|
||||
display(type), boardType(type), button(type), statusLed(type),
|
||||
ledBar(type), watchdog(type) {}
|
||||
ledBar(type), watchdog(type), sht(type) {}
|
||||
|
||||
/**
|
||||
* @brief Get pin number for I2C SDA
|
||||
@ -33,6 +36,52 @@ int AirGradient::getI2cSclPin(void) {
|
||||
return bsp->I2C.scl_pin;
|
||||
}
|
||||
|
||||
String AirGradient::getVersion(void) { return AG_LIB_VER; }
|
||||
String AirGradient::getVersion(void) { return GIT_VERSION; }
|
||||
|
||||
BoardType AirGradient::getBoardType(void) { return boardType; }
|
||||
|
||||
double AirGradient::round2(double value) {
|
||||
double ret;
|
||||
if (value >= 0) {
|
||||
ret = (int)(value * 100 + 0.5f);
|
||||
} else {
|
||||
ret = (int)(value * 100 - 0.5f);
|
||||
}
|
||||
|
||||
return ret / 100;
|
||||
}
|
||||
|
||||
String AirGradient::getBoardName(void) {
|
||||
return String(getBoardDefName(boardType));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Board Type is ONE_INDOOR
|
||||
*
|
||||
* @return true ONE_INDOOR
|
||||
* @return false Other
|
||||
*/
|
||||
bool AirGradient::isOne(void) {
|
||||
return boardType == BoardType::ONE_INDOOR;
|
||||
}
|
||||
|
||||
bool AirGradient::isOpenAir(void) {
|
||||
return boardType == BoardType::OPEN_AIR_OUTDOOR;
|
||||
}
|
||||
|
||||
bool AirGradient::isPro4_2(void) {
|
||||
return boardType == BoardType::DIY_PRO_INDOOR_V4_2;
|
||||
}
|
||||
|
||||
bool AirGradient::isPro3_3(void) {
|
||||
return boardType == BoardType::DIY_PRO_INDOOR_V3_3;
|
||||
}
|
||||
|
||||
bool AirGradient::isBasic(void) { return boardType == BoardType::DIY_BASIC; }
|
||||
|
||||
String AirGradient::deviceId(void) {
|
||||
String mac = WiFi.macAddress();
|
||||
mac.replace(":", "");
|
||||
mac.toLowerCase();
|
||||
return mac;
|
||||
}
|
||||
|
@ -1,18 +1,27 @@
|
||||
#ifndef _AIR_GRADIENT_H_
|
||||
#define _AIR_GRADIENT_H_
|
||||
|
||||
#include "bsp/BoardDef.h"
|
||||
#include "bsp/LedBar.h"
|
||||
#include "bsp/PushButton.h"
|
||||
#include "bsp/StatusLed.h"
|
||||
#include "bsp/HardwareWatchdog.h"
|
||||
#include "co2/s8.h"
|
||||
#include "display/oled.h"
|
||||
#include "pm/pms5003.h"
|
||||
#include "pm/pms5003t.h"
|
||||
#include "sgp/sgp41.h"
|
||||
#include "sht/sht4x.h"
|
||||
#include "Display/Display.h"
|
||||
#include "Main/BoardDef.h"
|
||||
#include "Main/HardwareWatchdog.h"
|
||||
#include "Main/LedBar.h"
|
||||
#include "Main/PushButton.h"
|
||||
#include "Main/StatusLed.h"
|
||||
#include "PMS/PMS5003.h"
|
||||
#include "PMS/PMS5003T.h"
|
||||
#include "S8/S8.h"
|
||||
#include "Sgp41/Sgp41.h"
|
||||
#include "Sht/Sht.h"
|
||||
#include "Main/utils.h"
|
||||
|
||||
#ifndef GIT_VERSION
|
||||
#define GIT_VERSION "3.1.16-snap"
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Class with define all the sensor has supported by Airgradient. Each
|
||||
* sensor usage must be init before use.
|
||||
*/
|
||||
class AirGradient {
|
||||
public:
|
||||
AirGradient(BoardType type);
|
||||
@ -21,7 +30,15 @@ public:
|
||||
* @brief Plantower PMS5003 sensor
|
||||
*/
|
||||
PMS5003 pms5003;
|
||||
/**
|
||||
* @brief Plantower PMS5003T sensor: connect to PM1 connector on
|
||||
* OPEN_AIR_OUTDOOR.
|
||||
*/
|
||||
PMS5003T pms5003t_1;
|
||||
/**
|
||||
* @brief Plantower PMS5003T sensor: connect to PM2 connector on
|
||||
* OPEN_AIR_OUTDOOR.
|
||||
*/
|
||||
PMS5003T pms5003t_2;
|
||||
|
||||
/**
|
||||
@ -30,18 +47,19 @@ public:
|
||||
S8 s8;
|
||||
|
||||
/**
|
||||
* @brief Temperature and humidity sensor
|
||||
* @brief Temperature and humidity sensor supported SHT3x and SHT4x
|
||||
*
|
||||
*/
|
||||
Sht sht;
|
||||
|
||||
/**
|
||||
* @brief TVOC and NOx sensor
|
||||
* @brief SGP41 TVOC and NOx sensor
|
||||
*
|
||||
*/
|
||||
Sgp41 sgp41;
|
||||
|
||||
/**
|
||||
* @brief Display
|
||||
* @brief OLED Display
|
||||
*
|
||||
*/
|
||||
Display display;
|
||||
@ -55,20 +73,106 @@ public:
|
||||
* @brief LED
|
||||
*/
|
||||
StatusLed statusLed;
|
||||
|
||||
/**
|
||||
* @brief RGB LED array
|
||||
*
|
||||
*/
|
||||
LedBar ledBar;
|
||||
|
||||
/**
|
||||
* @brief Hardware watchdog
|
||||
* @brief External hardware watchdog
|
||||
*/
|
||||
HardwareWatchdog watchdog;
|
||||
|
||||
/**
|
||||
* @brief Get I2C SDA pin has of board supported
|
||||
*
|
||||
* @return int Pin number if -1 invalid
|
||||
*/
|
||||
int getI2cSdaPin(void);
|
||||
/**
|
||||
* @brief Get I2C SCL pin has of board supported
|
||||
*
|
||||
* @return int Pin number if -1 invalid
|
||||
*/
|
||||
int getI2cSclPin(void);
|
||||
|
||||
/**
|
||||
* @brief Get the Board Type
|
||||
*
|
||||
* @return BoardType @ref BoardType
|
||||
*/
|
||||
BoardType getBoardType(void);
|
||||
|
||||
/**
|
||||
* @brief Get the library version string
|
||||
*
|
||||
* @return String
|
||||
*/
|
||||
String getVersion(void);
|
||||
|
||||
/**
|
||||
* @brief Get the Board Name object
|
||||
*
|
||||
* @return String
|
||||
*/
|
||||
String getBoardName(void);
|
||||
|
||||
/**
|
||||
* @brief Round double value with for 2 decimal
|
||||
*
|
||||
* @param valuem Round value
|
||||
* @return double
|
||||
*/
|
||||
double round2(double value);
|
||||
|
||||
/**
|
||||
* @brief Check that Airgradient object is ONE_INDOOR
|
||||
*
|
||||
* @return true Yes
|
||||
* @return false No
|
||||
*/
|
||||
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
|
||||
*
|
||||
* @return true Yes
|
||||
* @return false No
|
||||
*/
|
||||
bool isPro4_2(void);
|
||||
/**
|
||||
* @brief Check that Airgradient object is DIY_PRO 3.7 indoor
|
||||
*
|
||||
* @return true Yes
|
||||
* @return false No
|
||||
*/
|
||||
bool isPro3_3(void);
|
||||
|
||||
/**
|
||||
* @brief Check that Airgradient object is DIY_BASIC
|
||||
*
|
||||
* @return true Yes
|
||||
* @return false No
|
||||
*/
|
||||
bool isBasic(void);
|
||||
|
||||
/**
|
||||
* @brief Get device Id
|
||||
*
|
||||
* @return String
|
||||
*/
|
||||
String deviceId(void);
|
||||
|
||||
private:
|
||||
BoardType boardType;
|
||||
};
|
||||
|
27
src/App/AppDef.cpp
Normal file
27
src/App/AppDef.cpp
Normal file
@ -0,0 +1,27 @@
|
||||
#include "AppDef.h"
|
||||
|
||||
const char *AgFirmwareModeName(AgFirmwareMode mode) {
|
||||
switch (mode) {
|
||||
case FW_MODE_I_9PSL:
|
||||
return "I-9PSL";
|
||||
case FW_MODE_O_1PP:
|
||||
return "O-1PP";
|
||||
case FW_MODE_O_1PPT:
|
||||
return "O-1PPT";
|
||||
case FW_MODE_O_1PST:
|
||||
return "O-1PST";
|
||||
case FW_MODE_O_1PS:
|
||||
return "0-1PS";
|
||||
case FW_MODE_O_1P:
|
||||
return "O-1P";
|
||||
case FW_MODE_I_42PS:
|
||||
return "DIY-PRO-I-4.2PS";
|
||||
case FW_MODE_I_33PS:
|
||||
return "DIY-PRO-I-3.3PS";
|
||||
case FW_MODE_I_BASIC_40PS:
|
||||
return "DIY-BASIC-I-4.0PS";
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return "UNKNOWN";
|
||||
}
|
122
src/App/AppDef.h
Normal file
122
src/App/AppDef.h
Normal file
@ -0,0 +1,122 @@
|
||||
#ifndef _APP_DEF_H_
|
||||
#define _APP_DEF_H_
|
||||
|
||||
/**
|
||||
* @brief Application state machine state
|
||||
*
|
||||
*/
|
||||
enum AgStateMachineState {
|
||||
/** In WiFi Manger Mode */
|
||||
AgStateMachineWiFiManagerMode,
|
||||
|
||||
/** WiFi Manager has connected to mobile phone */
|
||||
AgStateMachineWiFiManagerPortalActive,
|
||||
|
||||
/** After SSID and PW entered and OK clicked, connection to WiFI network is
|
||||
attempted*/
|
||||
AgStateMachineWiFiManagerStaConnecting,
|
||||
|
||||
/** Connecting to WiFi worked */
|
||||
AgStateMachineWiFiManagerStaConnected,
|
||||
|
||||
/** Once connected to WiFi an attempt to reach the server is performed */
|
||||
AgStateMachineWiFiOkServerConnecting,
|
||||
|
||||
/** Server is reachable, all fine */
|
||||
AgStateMachineWiFiOkServerConnected,
|
||||
|
||||
/** =================================== *
|
||||
* Exceptions during WIFi Setup *
|
||||
* =================================== **/
|
||||
/** Cannot connect to WiFi (e.g. wrong password, WPA Enterprise etc.) */
|
||||
AgStateMachineWiFiManagerConnectFailed,
|
||||
|
||||
/** Connected to WiFi but server not reachable, e.g. firewall
|
||||
block/whitelisting needed etc. */
|
||||
AgStateMachineWiFiOkServerConnectFailed,
|
||||
|
||||
/** Server reachable but sensor not configured correctly*/
|
||||
AgStateMachineWiFiOkServerOkSensorConfigFailed,
|
||||
|
||||
/** =================================== *
|
||||
* During Normal Operation *
|
||||
* =================================== **/
|
||||
|
||||
/** Connection to WiFi network failed credentials incorrect encryption not
|
||||
supported etc. */
|
||||
AgStateMachineWiFiLost,
|
||||
|
||||
/** Connected to WiFi network but the server cannot be reached through the
|
||||
internet, e.g. blocked by firewall */
|
||||
AgStateMachineServerLost,
|
||||
|
||||
/** Server is reachable but there is some configuration issue to be fixed on
|
||||
the server side */
|
||||
AgStateMachineSensorConfigFailed,
|
||||
|
||||
/** CO2 calibration */
|
||||
AgStateMachineCo2Calibration,
|
||||
|
||||
/* LED bar testing */
|
||||
AgStateMachineLedBarTest,
|
||||
AgStateMachineLedBarPowerUpTest,
|
||||
|
||||
/** OTA perform, show display status */
|
||||
AgStateMachineOtaPerform,
|
||||
|
||||
/** LED: Show working state.
|
||||
* Display: Show dashboard */
|
||||
AgStateMachineNormal,
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief RGB LED bar mode for ONE_INDOOR board
|
||||
*/
|
||||
enum LedBarMode {
|
||||
/** Don't use LED bar */
|
||||
LedBarModeOff,
|
||||
|
||||
/** Use LED bar for show PM2.5 value level */
|
||||
LedBarModePm,
|
||||
|
||||
/** Use LED bar for show CO2 value level */
|
||||
LedBarModeCO2,
|
||||
};
|
||||
|
||||
enum ConfigurationControl {
|
||||
/** Allow set configuration from local over device HTTP server */
|
||||
ConfigurationControlLocal,
|
||||
|
||||
/** Allow set configuration from Airgradient cloud */
|
||||
ConfigurationControlCloud,
|
||||
|
||||
/** Allow set configuration from Local and Cloud */
|
||||
ConfigurationControlBoth
|
||||
};
|
||||
|
||||
enum PMCorrectionAlgorithm {
|
||||
Unknown, // Unknown algorithm
|
||||
None, // No PM correction
|
||||
EPA_2021,
|
||||
SLR_PMS5003_20220802,
|
||||
SLR_PMS5003_20220803,
|
||||
SLR_PMS5003_20220824,
|
||||
SLR_PMS5003_20231030,
|
||||
SLR_PMS5003_20231218,
|
||||
SLR_PMS5003_20240104,
|
||||
};
|
||||
|
||||
enum AgFirmwareMode {
|
||||
FW_MODE_I_9PSL, /** ONE_INDOOR */
|
||||
FW_MODE_O_1PST, /** PMS5003T, S8 and SGP41 */
|
||||
FW_MODE_O_1PPT, /** PMS5003T_1, PMS5003T_2, SGP41 */
|
||||
FW_MODE_O_1PP, /** PMS5003T_1, PMS5003T_2 */
|
||||
FW_MODE_O_1PS, /** PMS5003T, S8 */
|
||||
FW_MODE_O_1P, /** PMS5003T */
|
||||
FW_MODE_I_42PS, /** DIY_PRO 4.2 */
|
||||
FW_MODE_I_33PS, /** DIY_PRO 3.3 */
|
||||
FW_MODE_I_BASIC_40PS, /** DIY_BASIC 4.0 */
|
||||
};
|
||||
const char *AgFirmwareModeName(AgFirmwareMode mode);
|
||||
|
||||
#endif /** _APP_DEF_H_ */
|
282
src/Display/Display.cpp
Normal file
282
src/Display/Display.cpp
Normal file
@ -0,0 +1,282 @@
|
||||
#include "Display.h"
|
||||
#include "../Libraries/Adafruit_SH110x/Adafruit_SH110X.h"
|
||||
#include "../Libraries/Adafruit_SSD1306_Wemos_OLED/Adafruit_SSD1306.h"
|
||||
|
||||
#define disp(func) \
|
||||
if (this->_boardType == DIY_BASIC) { \
|
||||
((Adafruit_SSD1306 *)(this->oled))->func; \
|
||||
} else { \
|
||||
((Adafruit_SH110X *)(this->oled))->func; \
|
||||
}
|
||||
|
||||
#if defined(ESP8266)
|
||||
void Display::begin(TwoWire &wire, Stream &debugStream) {
|
||||
this->_debugStream = &debugStream;
|
||||
this->begin(wire);
|
||||
}
|
||||
#else
|
||||
#endif
|
||||
|
||||
Display::Display(BoardType type) : _boardType(type) {}
|
||||
|
||||
/**
|
||||
* @brief Initialize display, should be call this function before call of ther,
|
||||
* if not it's always return failure.
|
||||
*
|
||||
* @param wire TwoWire instance, Must be initialized
|
||||
*/
|
||||
void Display::begin(TwoWire &wire) {
|
||||
if (_isBegin) {
|
||||
AgLog("Initialized, call end() then try again");
|
||||
return;
|
||||
}
|
||||
|
||||
this->_bsp = getBoardDef(this->_boardType);
|
||||
if ((this->_bsp == nullptr) || (this->_bsp->I2C.supported == false) ||
|
||||
(this->_bsp->OLED.supported == false)) {
|
||||
AgLog("Init failed: board not supported");
|
||||
return;
|
||||
}
|
||||
|
||||
/** Init OLED */
|
||||
if (this->_boardType == DIY_BASIC) {
|
||||
AgLog("Init Adafruit_SSD1306");
|
||||
Adafruit_SSD1306 *_oled = new Adafruit_SSD1306();
|
||||
_oled->begin(wire, SSD1306_SWITCHCAPVCC, this->_bsp->OLED.addr);
|
||||
this->oled = _oled;
|
||||
} else {
|
||||
AgLog("Init Adafruit_SH1106G");
|
||||
Adafruit_SH1106G *_oled = new Adafruit_SH1106G(
|
||||
this->_bsp->OLED.width, this->_bsp->OLED.height, &wire);
|
||||
_oled->begin(this->_bsp->OLED.addr, false);
|
||||
this->oled = _oled;
|
||||
}
|
||||
|
||||
this->_isBegin = true;
|
||||
disp(clearDisplay());
|
||||
AgLog("Initialize");
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Clear display buffer
|
||||
*
|
||||
*/
|
||||
void Display::clear(void) {
|
||||
if (this->isBegin() == false) {
|
||||
return;
|
||||
}
|
||||
disp(clearDisplay());
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Invert display color
|
||||
*
|
||||
* @param i 0: black, other is white
|
||||
*/
|
||||
void Display::invertDisplay(uint8_t i) {
|
||||
if (this->isBegin() == false) {
|
||||
return;
|
||||
}
|
||||
disp(invertDisplay(i));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Send display frame buffer to OLED
|
||||
*
|
||||
*/
|
||||
void Display::show() {
|
||||
if (this->isBegin() == false) {
|
||||
return;
|
||||
}
|
||||
disp(display());
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set display contract
|
||||
*
|
||||
* @param value Contract (0;255);
|
||||
*/
|
||||
void Display::setContrast(uint8_t value) {
|
||||
if (this->isBegin() == false) {
|
||||
return;
|
||||
}
|
||||
disp(setContrast(value));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Draw pixel into display frame buffer, call show to draw to
|
||||
* display(OLED)
|
||||
*
|
||||
* @param x X Position
|
||||
* @param y Y Position
|
||||
* @param color Color (0: black, other white)
|
||||
*/
|
||||
void Display::drawPixel(int16_t x, int16_t y, uint16_t color) {
|
||||
if (this->isBegin() == false) {
|
||||
return;
|
||||
}
|
||||
disp(drawPixel(x, y, color));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set text size, it's scale default font instead of point to multiple
|
||||
* font has define for special size
|
||||
*
|
||||
* @param size Size of text (default = 1)
|
||||
*/
|
||||
void Display::setTextSize(int size) {
|
||||
if (this->isBegin() == false) {
|
||||
return;
|
||||
}
|
||||
disp(setTextSize(size));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Move draw cursor into new position
|
||||
*
|
||||
* @param x X Position
|
||||
* @param y Y Position
|
||||
*/
|
||||
void Display::setCursor(int16_t x, int16_t y) {
|
||||
if (this->isBegin() == false) {
|
||||
return;
|
||||
}
|
||||
disp(setCursor(x, y));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set Text Color
|
||||
*
|
||||
* @param color 0:black, 1: While
|
||||
*/
|
||||
void Display::setTextColor(uint16_t color) {
|
||||
if (this->isBegin() == false) {
|
||||
return;
|
||||
}
|
||||
disp(setTextColor(color));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set text foreground color and background color
|
||||
*
|
||||
* @param foreGroundColor Text Color (foreground color)
|
||||
* @param backGroundColor Text background color
|
||||
*/
|
||||
void Display::setTextColor(uint16_t foreGroundColor, uint16_t backGroundColor) {
|
||||
if (this->isBegin() == false) {
|
||||
return;
|
||||
}
|
||||
disp(setTextColor(foreGroundColor, backGroundColor));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Draw text to display framebuffer, call show() to draw to display
|
||||
* (OLED)
|
||||
*
|
||||
* @param text String
|
||||
*/
|
||||
void Display::setText(String text) {
|
||||
if (this->isBegin() == false) {
|
||||
return;
|
||||
}
|
||||
disp(print(text));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Draw bitmap into display framebuffer, call show() to draw to display
|
||||
* (OLED)
|
||||
*
|
||||
* @param x X Position
|
||||
* @param y Y Position
|
||||
* @param bitmap Bitmap buffer
|
||||
* @param w Bitmap width
|
||||
* @param h Bitmap hight
|
||||
* @param color Bitmap color
|
||||
*/
|
||||
void Display::drawBitmap(int16_t x, int16_t y, const uint8_t bitmap[],
|
||||
int16_t w, int16_t h, uint16_t color) {
|
||||
if (this->isBegin() == false) {
|
||||
return;
|
||||
}
|
||||
disp(drawBitmap(x, y, bitmap, w, h, color));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set text to display framebuffer, call show() to draw into to display
|
||||
* (OLED)
|
||||
*
|
||||
* @param text Character buffer
|
||||
*/
|
||||
void Display::setText(const char text[]) {
|
||||
if (this->isBegin() == false) {
|
||||
return;
|
||||
}
|
||||
disp(print(text));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Draw line to display framebuffer, call show() to draw to
|
||||
* display(OLED)
|
||||
*
|
||||
* @param x0 Start X position
|
||||
* @param y0 Start Y position
|
||||
* @param x1 End X Position
|
||||
* @param y1 End Y Position
|
||||
* @param color Color (0: black, otherwise white)
|
||||
*/
|
||||
void Display::drawLine(int x0, int y0, int x1, int y1, uint16_t color) {
|
||||
if (this->isBegin() == false) {
|
||||
return;
|
||||
}
|
||||
disp(drawLine(x0, y0, x1, y1, color));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Draw circle to display framebuffer,
|
||||
*
|
||||
* @param x
|
||||
* @param y
|
||||
* @param r
|
||||
* @param color
|
||||
*/
|
||||
void Display::drawCircle(int x, int y, int r, uint16_t color) {
|
||||
if (this->isBegin() == false) {
|
||||
return;
|
||||
}
|
||||
disp(drawCircle(x, y, r, color));
|
||||
}
|
||||
|
||||
void Display::drawRect(int x0, int y0, int x1, int y1, uint16_t color) {
|
||||
if (this->isBegin() == false) {
|
||||
return;
|
||||
}
|
||||
disp(drawRect(x0, y0, x1, y1, color));
|
||||
}
|
||||
|
||||
bool Display::isBegin(void) {
|
||||
if (this->_isBegin) {
|
||||
return true;
|
||||
}
|
||||
AgLog("Display not-initialized");
|
||||
return false;
|
||||
}
|
||||
|
||||
void Display::setRotation(uint8_t r) {
|
||||
if (isBegin() == false) {
|
||||
return;
|
||||
}
|
||||
disp(setRotation(r));
|
||||
}
|
||||
|
||||
void Display::end(void) {
|
||||
if (this->_isBegin == false) {
|
||||
return;
|
||||
}
|
||||
_isBegin = false;
|
||||
if (this->_boardType == DIY_BASIC) {
|
||||
delete ((Adafruit_SSD1306 *)(this->oled));
|
||||
} else {
|
||||
delete ((Adafruit_SH110X *)(this->oled));
|
||||
}
|
||||
AgLog("De-initialize");
|
||||
}
|
@ -1,10 +1,14 @@
|
||||
#ifndef _AIR_GRADIENT_OLED_H_
|
||||
#define _AIR_GRADIENT_OLED_H_
|
||||
|
||||
#include "../bsp/BoardDef.h"
|
||||
#include "../Main/BoardDef.h"
|
||||
#include <Arduino.h>
|
||||
#include <Wire.h>
|
||||
|
||||
/**
|
||||
* @brief The class define how to handle the OLED display on Airgradient has
|
||||
* attached or support OLED display like: ONE-V9, Basic-V4
|
||||
*/
|
||||
class Display {
|
||||
public:
|
||||
const uint16_t COLOR_WHILTE = 1;
|
||||
@ -15,10 +19,11 @@ public:
|
||||
#endif
|
||||
Display(BoardType type);
|
||||
void begin(TwoWire &wire);
|
||||
void end(void);
|
||||
|
||||
void clear(void); // .clear
|
||||
void clear(void);
|
||||
void invertDisplay(uint8_t i);
|
||||
void show(); // .show()
|
||||
void show();
|
||||
|
||||
void setContrast(uint8_t value);
|
||||
void drawPixel(int16_t x, int16_t y, uint16_t color);
|
||||
@ -39,14 +44,14 @@ private:
|
||||
BoardType _boardType;
|
||||
const BoardDef *_bsp = nullptr;
|
||||
void *oled;
|
||||
bool _isInit = false;
|
||||
bool _isBegin = false;
|
||||
#if defined(ESP8266)
|
||||
const char *TAG = "oled";
|
||||
Stream *_debugStream = nullptr;
|
||||
#else
|
||||
|
||||
#endif
|
||||
bool checkInit(void);
|
||||
bool isBegin(void);
|
||||
};
|
||||
|
||||
#endif /** _AIR_GRADIENT_OLED_H_ */
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user