Compare commits

...

200 Commits
3.1.1 ... 3.1.8

Author SHA1 Message Date
fd2cef153e Merge pull request #239 from airgradienthq/hotfix/led-bar-show-pm-status
Fix: Correct LED bar show PM status
2024-09-17 10:25:42 +07:00
507b958fdf Correct LED bar show PM value use compensate 2024-09-17 10:15:47 +07:00
335c29ebb1 Merge remote-tracking branch 'origin/develop' into hotfix/led-bar-show-pm-status 2024-09-17 10:01:58 +07:00
2907d6f14e Merge pull request #238 from airgradienthq/hotfix/PM-compensation-receiving-temperature-instead-of-RH
Fix pm compensation: receiving temperature instead of humidity
2024-09-17 09:44:50 +07:00
c8d5b546ed correct PM compensate the input argument value humidity instead of temperature, fix #234 2024-09-16 14:52:04 +07:00
b7cfdc4c4d Update AirGradient.h to v 3.1.8 2024-09-16 12:47:50 +07:00
994d281e02 Update Version to 3.1.8 2024-09-16 12:47:08 +07:00
39470384e4 Merge pull request #233 from airgradienthq/cubic-PM2009X
Changed PM initialization to also support the Cubic PM2009X
2024-09-16 12:08:18 +07:00
c25ba764bf Merge pull request #236 from airgradienthq/add-log-pms-version-code
Add log: PMS5003x sensor print log firmware version
2024-09-16 11:02:32 +07:00
826ff00f42 add log message PM sensor firmware version 2024-09-16 10:36:45 +07:00
520550037d Explicitly set active mode for PM sensor upon initialization 2024-09-15 08:26:38 +07:00
90f336dee7 Revert "Explicitly set active mode for PM sensor upon initialization"
This reverts commit 0d39643e76.
2024-09-15 08:23:32 +07:00
0d39643e76 Explicitly set active mode for PM sensor upon initialization 2024-09-15 08:22:50 +07:00
b7339de31f Merge pull request #232 from samuelbles07/feat/ag-client-timeout
Feat/ag-client-timeout
2024-09-12 15:06:23 +07:00
013fb94307 Only for tcp timeout
Ignoring connect to server timeout
2024-09-11 16:37:50 +07:00
e16373a64d Add new public member to set http client timeout by caller 2024-09-11 16:02:13 +07:00
f929623443 Fix uri formatting postToServer to use apiRoot 2024-09-11 16:01:16 +07:00
59587ce2b7 Add http request timeout number for ApiClient 2024-09-11 15:48:44 +07:00
9ec74450a5 Merge branch 'master' into develop 2024-09-02 19:56:46 +07:00
28096e9faf Update version to 3.1.7 2024-09-02 19:55:16 +07:00
682378a47c Merge pull request #231 from airgradienthq/develop
Add WiFi feature
2024-09-02 19:53:33 +07:00
a1861be7b7 Merge pull request #230 from airgradienthq/feature/wifi-connect-to-default
Add default WiFi connect
2024-09-02 19:50:47 +07:00
99ddd24432 Merge branch 'develop' into feature/wifi-connect-to-default 2024-09-02 19:44:53 +07:00
29491e4cbe Merge pull request #229 from airgradienthq/develop
Merge to Master to Release 3.1.6
2024-09-02 12:19:46 +07:00
87cc3fc45f Update library.properties to v 3.1.6 2024-09-02 12:18:04 +07:00
7471d8079a Update AirGradient.h to v 3.1.6 2024-09-02 12:17:08 +07:00
8b0fe967f1 Merge pull request #223 from airgradienthq/hotfix/print-log-wrong-format
Fix print log message number format
2024-09-02 12:11:43 +07:00
6f1cef4e67 Merge pull request #224 from airgradienthq/hotfix/pms25-compensated-show-on-display
[Fix] PM2.5 compensated show on display
2024-09-02 12:09:51 +07:00
02b63ff816 Merge pull request #226 from airgradienthq/fix/pm2.5-compensated-formula
Fix pm2.5 compensation formula
2024-09-02 12:06:39 +07:00
228bf83e92 Merge pull request #228 from airgradienthq/feature/support-led-test-on-openair
`OpenAir` handle `ledBarTestRequested`
2024-09-02 12:05:20 +07:00
d3534cda52 handle ledBarTestRequested on OpenAir 2024-09-01 20:19:18 +07:00
aafaa42a68 Update formula link 2024-09-01 19:56:11 +07:00
2e9ff0d7dd add link to formula document 2024-08-30 19:21:54 +07:00
244b7814a6 add link to formula documents 2024-08-30 19:20:08 +07:00
28d27ee8fd Rename temperatureCompensated to compensateTemp and humidityCompensated to compensateHum 2024-08-30 19:17:58 +07:00
753f22923c rename isValidPMS to isValidPm 2024-08-30 19:07:31 +07:00
c45901706f Merge branch 'develop' into hotfix/pms25-compensated-show-on-display 2024-08-30 19:02:50 +07:00
663836e277 Merge pull request #205 from airgradienthq/feature/send-pms-sensor-fw-version-to-ag-cloud
Send PMS5003T firmware version to Ag Cloud
2024-08-30 10:54:36 +07:00
d39e10908d Merge branch 'develop' into hotfix/print-log-wrong-format 2024-08-28 09:57:45 +07:00
c52962d628 Update float constant 2024-08-26 20:47:48 +07:00
6b65efd3d6 fix pm2.5 compensated formula, #225 2024-08-26 20:43:48 +07:00
8bb87a75ef Merge pull request #222 from airgradienthq/hotfix/pms-fail-count-restart
Hotfix/pms fail count restart
2024-08-26 20:17:20 +07:00
1afcca25a1 Fix compile failed. 2024-08-26 15:54:41 +07:00
17238cff86 fix compile failed. 2024-08-26 15:52:31 +07:00
03e2afbf54 WiFi Connect to default airgradient if WiFi connected is empty 2024-08-26 15:47:49 +07:00
104d58a8c0 resolve review #222 2024-08-26 14:14:42 +07:00
7a988ea6c1 rename compensated to compensate 2024-08-26 11:56:01 +07:00
54ed83cb89 Revert las misstake commit changed. 2024-08-25 20:56:30 +07:00
e461b92c9f Fix build failed 2024-08-25 20:51:07 +07:00
db21648e91 Merge branch 'develop' into feature/send-pms-sensor-fw-version-to-ag-cloud 2024-08-25 20:46:28 +07:00
a9654506f5 Update log format, #218 2024-08-25 20:40:45 +07:00
63f653d5cd fix PM2.5 compensated on display, #221 2024-08-25 20:37:38 +07:00
b049a23657 Restart device after PMS sensor read failed 10 times 2024-08-25 20:21:26 +07:00
d6766ef68b Correct print log number format, fix #218 2024-08-25 08:37:25 +07:00
6c3259b94b Merge branch 'master' into develop 2024-08-23 09:16:10 +07:00
b1aaa04421 update GIT_BUILD version 2024-08-23 09:12:43 +07:00
2df78e9066 Merge branch 'master' into develop 2024-08-23 08:59:49 +07:00
186f0d27ab Update version 2024-08-23 08:59:28 +07:00
e25aa87ecc Merge pull request #215 from airgradienthq/develop
Develop
2024-08-23 08:50:04 +07:00
1cc8941a5c csv alignment format 2024-08-22 11:27:25 +07:00
9bf1495be7 Merge branch 'hotfix/firmware-version' into develop 2024-08-22 11:24:26 +07:00
73089b51f5 add firmware version for arduino build 2024-08-22 11:23:53 +07:00
625e60a5bf Merge branch 'hotfix/local-configuration-return-result' into develop 2024-08-21 12:54:47 +07:00
88e3d0bd3f Ignore and return failed if configurationControl is cloud 2024-08-21 12:54:06 +07:00
171821cfcf Merge pull request #214 from airgradienthq/hotfix/api-client-log
API client log post data
2024-08-21 12:28:50 +07:00
900a2da2ac Log POST data 2024-08-21 12:27:56 +07:00
fb57a112c9 Merge pull request #201 from McJoppy/feature/root-api
Reintroduce 'ROOTAPI' so domain and protocol can be configured
2024-08-20 09:25:48 +07:00
ab69b686ec Merge branch 'develop' into feature/root-api 2024-08-20 09:22:36 +07:00
6746d25dc2 Add comment to all example scripts showing how to change APIROOT 2024-08-18 16:46:56 +12:00
be150e105a Merge pull request #204 from airgradienthq/feature/add-http-request-to-ag-log
Add log message HTTP request and response to AG server
2024-08-18 11:37:42 +07:00
ecadeeb156 Merge pull request #209 from airgradienthq/hotfix/json-round-2-decimal-place-support-negative-value
`round2` support negative value
2024-08-18 11:33:39 +07:00
219ff73132 Merge pull request #212 from airgradienthq/feature/correct-pm2.5-formula
correct pm2.5 formula to show value on display
2024-08-18 11:32:50 +07:00
0a9142204d Merge pull request #213 from airgradienthq/hotfix/sensor-value-out-of-range
sensor value out of range
2024-08-18 11:30:27 +07:00
81b13134d2 update PM2.5 firmware prefix to PMS5003x 2024-08-16 06:42:43 +07:00
f3a9c722b2 Change pm25Compensated to compensated 2024-08-16 06:39:52 +07:00
3be3218115 Update invalid value and optimize code operator. 2024-08-15 09:11:38 +07:00
5edb21cfe9 Fix: PMS5003T only return positive temperature value 2024-08-15 09:10:48 +07:00
6cd587b008 Merge remote-tracking branch 'origin/develop' into feature/correct-pm2.5-formula 2024-08-15 08:19:10 +07:00
6d01366887 change pmsFirmare to firmware 2024-08-15 08:04:30 +07:00
1a347e9cfe Merge pull request #206 from airgradienthq/hotfix/display-show-nox-incorrect-position
`NOx` show incorrect position on display
2024-08-12 09:20:21 +07:00
6432e4451e Merge pull request #210 from airgradienthq/hotfix/set-connect-to-default-wifi-on-factory-reset
Set WiFi connect to default WiFi on factory reset
2024-08-10 07:45:06 +07:00
97f0696002 set default wifi on factory reset. 2024-08-09 13:19:28 +07:00
e46e11c030 round2 support negative value 2024-08-08 05:53:24 +07:00
dc261f668d Update local-server.md II 2024-08-07 11:08:48 +07:00
b5cced40d2 Update local-server.md 2024-08-07 11:05:39 +07:00
040bd28038 Add report PMS5003 and PMS5003T firmware version 2024-08-07 08:50:43 +07:00
b0ae851427 Fix nox position 2024-07-30 20:26:19 +07:00
01943f594d Send PMS5003T firmware version to cloud 2024-07-29 13:20:07 +07:00
01a69668cc Merge branch 'develop' into feature/send-pms-sensor-fw-version-to-ag-cloud 2024-07-29 12:56:45 +07:00
ed7b8df6fe log URLs of all HTTP requests to AG backend / log status codes of responses 2024-07-29 06:00:54 +07:00
6c1c914716 Merge pull request #196 from airgradienthq/hotfix/led-bar-show-sensor-data-incorrect
Correct LED bar show sensor value level color
2024-07-26 06:16:33 +07:00
6a0d88ff10 Merge pull request #197 from airgradienthq/hotfix/ignore-parameter-out-of-range
Ignore parameter values out of range
2024-07-26 06:12:04 +07:00
9097eed137 [fix] typo comment 2024-07-24 20:31:43 +07:00
c9b5e5f0d7 Merge branch 'develop' into hotfix/ignore-parameter-out-of-range 2024-07-24 20:24:27 +07:00
c12bac4ce3 Update invalid temperature value 2024-07-24 20:19:06 +07:00
9ae9b2ac9c display float value on display with 1 digit 2024-07-24 20:18:48 +07:00
7fb3e68b6d Merge pull request #195 from airgradienthq/hotfix/change-tvoc-to-VOC
Change `tvoc` to `VOC` on display
2024-07-24 09:19:03 +02:00
cf65a1f901 Merge pull request #200 from airgradienthq/feature/doc-quote-properties-name
Update local-server.md
2024-07-24 09:16:09 +02:00
5fb27b6d1e Check value sensor value 2024-07-24 09:05:57 +07:00
7b9dac756b Reintroduce 'ROOTAPI' so domain and protocol can be configured
eg. setter for api root added as comment in examples/BASIC/BASIC.ino
2024-07-21 19:38:50 +12:00
4b2a5f5540 Add PM2.5 correction formula, #182 2024-07-21 07:13:34 +07:00
4af77d532e Update local-server.md
Quote properties name
2024-07-20 09:39:29 +07:00
812c2ab803 add PMS5003T get module firmware version code 2024-07-20 08:53:19 +07:00
0ece16f434 Update screen layout, #139 2024-07-18 11:28:12 +07:00
df6cca3714 Ignore parameter values out of range #190 2024-07-11 06:25:31 +07:00
c8aa07ae20 Correct LED bar show sensor value level color, #161 2024-07-09 06:44:04 +07:00
a1d216ac77 Change tvoc to VOC, #139 2024-07-09 06:10:07 +07:00
a9d9c60dfa Merge branch 'develop' 2024-06-30 07:23:06 +07:00
e58ce1cbea Upgraded version number for Arduino library manager 2024-06-29 15:08:36 +07:00
64827223ec Merge pull request #191 from airgradienthq/feature/add-esp8266-examples
Add esp8266 examples
2024-06-29 15:03:56 +07:00
bddd4fef25 Update BASIC CO2 calibration display message. 2024-06-25 17:32:19 +07:00
0eba54fd28 Remove don't use code 2024-06-25 17:20:04 +07:00
a4176f966a Update board name comment 2024-06-25 17:13:18 +07:00
2de54ca6a6 update platformio.ini 2024-06-25 16:54:42 +07:00
bfd20a73da Rename board type 3.7 to 3.3 2024-06-25 16:43:50 +07:00
3ebce4ac44 Update firmware model name 2024-06-25 16:29:21 +07:00
f08c8edd19 Rename example 2024-06-25 16:27:35 +07:00
6b3e8e3096 Update default configuration json for special board 2024-06-25 16:24:12 +07:00
beb17de7dc [Fix] WiFi reset failed and crash in offline mode 2024-06-25 16:08:57 +07:00
bccadd17d7 [Fix] Wrong firmware model name 2024-06-25 15:35:18 +07:00
863b1f908e Merge commit '5a2c1bd1d0290e5cae0b8dec02eb2a931211d1af' into feature/add-esp8266-examples 2024-06-25 15:19:28 +07:00
5a2c1bd1d0 Merge pull request #175 from airgradienthq/fix/local-configuration-response-result
[Fix] Local `configurationControl` return failed if set `cloud`
2024-06-25 15:18:49 +07:00
dbc63194e6 Update BASIC.ino example 2024-06-24 18:34:24 +07:00
57c33e4900 check to handle sgp sensor 2024-06-24 18:33:46 +07:00
48f1a8042a Fixed FW_MODE_I_37PS 2024-06-22 13:47:13 +07:00
fbd5779fe6 platformio.ini update 2024-06-20 11:36:07 +07:00
45a4d98267 Fix wrong firmware mode 2024-06-20 11:34:43 +07:00
0119a4d62a Add support board DIY Pro 3.7 and add example 2024-06-20 11:34:23 +07:00
7560251deb Update mqtt print log message 2024-06-20 10:12:37 +07:00
31655b0a4f [Fix] PCB 4.3: Boot loop if not connected to Dashboard, #186 2024-06-20 10:12:19 +07:00
7d15c37ad9 Update firmware mode, fix #185 2024-06-20 09:34:25 +07:00
411beda220 Update tvocRaw and noxRaw, fix #184 2024-06-20 09:03:21 +07:00
0e869d1c0c Update README.md 2024-06-19 15:18:51 +07:00
f25f816428 Add MQTT 2024-06-19 15:17:48 +07:00
c8745e123b Update board support handle and fix connect timeout handle. 2024-06-19 10:33:02 +07:00
5ea01b8e9f [Fix] WiFi reset and set connect to factory wifi 2024-06-18 20:35:50 +07:00
a11eaea532 [fix] build fail on example BASIC 2024-06-18 19:59:29 +07:00
3b6859f483 Updated documentation for local server 2024-06-17 08:46:56 +07:00
bd237ed95d Add DiyProIndoorV4_2.ino example 2024-06-15 15:40:50 +07:00
d24b20a734 Merge pull request #177 from airgradienthq/feature/factory-reset-open-air-connect-to-airgradient-wifi
OpenAir set WiFi connect to `airgradient` on factory reset
2024-06-14 09:38:21 +07:00
227a4f76f7 Merge pull request #176 from airgradienthq/fix/display-show-configureation-failed-status
Fix display message is not correct.
2024-06-12 06:07:01 +07:00
adabb9baa4 OpenAir set WiFi connect to airgradient on factory reset 2024-06-12 06:04:13 +07:00
935e7f365f Fix display message is not correct. 2024-06-10 22:04:23 +07:00
d4ea03f39e Handle configurationControl set cloud return success 2024-06-08 12:28:17 +07:00
89aefdda43 fix type 2024-06-08 12:01:42 +07:00
8a83e408d3 Merge pull request #173 from airgradienthq/develop
Next version 3.1.3
2024-06-06 13:17:08 +07:00
7a1a0337d1 Merge pull request #163 from joostlek/typo
Fix typos in docs
2024-06-06 13:13:00 +07:00
6bdd4cb02f Merge pull request #172 from airgradienthq/hotfix/display-show-rebooting-but-not-rebooting
[Fix] Reboot device after WiFi Configure porttal timeout
2024-06-06 13:11:33 +07:00
38bd758b69 Merge pull request #171 from airgradienthq/feature/update-display-message
Update the message show on display
2024-06-06 13:11:19 +07:00
6aa19ea3e6 Set next version 2024-06-06 08:44:12 +07:00
da323b1a46 fix reboot device after WiFi portal configure timeout. 2024-06-05 19:01:25 +07:00
2ae90444bb Fix reboot device after WiFi Connector perform but not connected 2024-06-05 14:31:34 +07:00
21e802da33 Merge pull request #170 from airgradienthq/feature/regularly-call-ota-handler
OTA update each 60 min
2024-06-05 13:38:05 +07:00
e0869fbaf0 Merge pull request #167 from airgradienthq/hotfix/too-many-get-config-request
[Fix] Too many server GET config request
2024-06-05 13:24:59 +07:00
70662091ec Merge pull request #166 from airgradienthq/hotfix/wifi-not-reset-in-offline-mode
[Fix] WiFi not reset on offline mode
2024-06-05 13:24:44 +07:00
960d2bad64 Merge pull request #169 from airgradienthq/feature/add-boott-support-ha
Add bootCount to support on HA
2024-06-05 13:22:19 +07:00
7abf7f5e6a Merge pull request #162 from joostlek/update-put-table
Update configuration parameters
2024-06-05 10:34:03 +07:00
6bad4fd04b Merge pull request #164 from joostlek/json
Reformat JSON in docs
2024-06-05 10:30:56 +07:00
a17f18b3db Merge pull request #165 from joostlek/table
Reformat table
2024-06-05 10:29:59 +07:00
3da4900462 Update show mesage on display 2024-06-05 09:58:08 +07:00
d2d81f6b4b OTA update each 60 min 2024-06-05 09:29:30 +07:00
0b12f56513 add bootCount to measure report 2024-06-04 22:17:20 +07:00
f7e811e34b Update server GET configure period to 60 sec 2024-06-04 18:30:33 +07:00
037bb37184 Change macro name SERVER_CONFIG_UPDATE_INTERVAL' to SERVER_CONFIG_SYNC_INTERVAL` 2024-06-04 18:29:35 +07:00
fde510ba96 Merge pull request #137 from airgradienthq/hotfix/correct-ota-update-message-on-display
Correct OTA process message show on display
2024-06-04 18:15:01 +07:00
14b152a2a7 Merge branch 'develop' into hotfix/correct-ota-update-message-on-display 2024-06-04 18:09:04 +07:00
ef6b041529 Fix: ota not callback on first powerup perform 2024-06-04 18:01:55 +07:00
11ecea1493 Change showFirmwareUpdateSuccess input fromString to int 2024-06-04 18:01:28 +07:00
3e2e8b15eb Fix: WiFi configuration not reset in offline mode. 2024-06-04 15:51:01 +07:00
4249a86fd5 Reformat table 2024-06-04 09:21:29 +02:00
4a58b0b1c7 Reformat JSON in docs 2024-06-04 09:20:18 +02:00
5f93329e96 Fix typos in docs 2024-06-04 09:17:33 +02:00
59d189e1d3 Update configuration parameters 2024-06-04 09:14:30 +02:00
2e62abe2d7 Merge pull request #156 from airgradienthq/develop
Develop
2024-05-29 18:02:27 +07:00
7569b114bf Merge pull request #155 from airgradienthq/feature/update-led-bar-button-test
Handle LED bar test button before init sensor initialize
2024-05-29 16:36:08 +07:00
3c1d0a862f update comment 2024-05-29 16:34:00 +07:00
4d883af77e Merge pull request #138 from airgradienthq/hotfix/ledbar-flickers
Fix LED bar flickers
2024-05-29 08:01:05 +07:00
e9224a5de0 Remove test logging message 2024-05-29 07:58:49 +07:00
af0fbadd80 Handle LED bar button test before init sensor. 2024-05-29 07:55:27 +07:00
79f6c040c7 Merge pull request #143 from airgradienthq/feature/reboot-after-wifi-connect-timeout
Reboot After 180s WiFi Manager not Connect.
2024-05-28 13:43:08 +07:00
f262866148 Merge pull request #154 from airgradienthq/hotfix/display-co2-calibration-message
Update CO2 calibration message show on display
2024-05-28 13:42:14 +07:00
78a2a78020 Update CO2 calibration message show on display 2024-05-25 20:25:50 +07:00
65e759fb90 Merge branch 'develop' into hotfix/correct-ota-update-message-on-display 2024-05-24 13:30:09 +07:00
3fc7e4b55e Merge pull request #149 from airgradienthq/hotfix/reporting-fw-version-on-dashboard
Official OTA 3.1.1 version not reporting FW version on dashboard
2024-05-24 09:30:13 +07:00
5857388c2d Merge pull request #152 from airgradienthq/hotfix/remove-configuration-missleading-message
Remove `Update ignored due to local unofficial changes`
2024-05-24 09:01:04 +07:00
5770b41fd4 remove Update ignored due to local unofficial changes 2024-05-24 08:45:50 +07:00
d85d890878 Change OTA update period from 2h to 1h 2024-05-24 08:26:24 +07:00
9fbd31d0c8 Merge pull request #150 from airgradienthq/hotfix/show-message-monitor-not-setup-on-dashboard
disp.setText("Monitor not", "setup on", "dashboard") called when monitor is actually already on dashboard
2024-05-24 08:23:52 +07:00
c5b7c43293 disp.setText("Monitor not", "setup on", "dashboard") called when monitor is actually already on dashboard 2024-05-22 12:13:57 +07:00
7550ef7b0c change OTA update period each 2h. 2024-05-22 11:45:56 +07:00
805546b78e check firmware update after powerup 2024-05-22 11:34:42 +07:00
59880f4be5 fix typo 2024-05-22 11:17:11 +07:00
ee7837a471 Merge pull request #148 from MallocArray/patch-1
Correct API value for boot
2024-05-20 08:50:57 +07:00
ebbf0adf2f Correct API value for boot 2024-05-18 21:19:40 -05:00
d9551dc560 Reboot After 180s WiFi Manager not Connect. 2024-05-18 08:47:49 +07:00
6ea0ab9272 Set process 100% after received firmware file. 2024-05-17 20:18:59 +07:00
6e54409512 rename function 2024-05-17 20:17:49 +07:00
f35bc4feaa Fix LED bar flickers 2024-05-17 11:52:22 +07:00
c04ab90fd2 Update OTA processing message. Fix always retry of bootCount = 0 2024-05-17 10:32:20 +07:00
ed02f66ca2 Correct OTA update process show message on display 2024-05-16 21:12:02 +07:00
76 changed files with 6687 additions and 1048 deletions

View File

@ -6,6 +6,8 @@ jobs:
matrix: matrix:
example: example:
- "BASIC" - "BASIC"
- "DiyProIndoorV4_2"
- "DiyProIndoorV3_3"
- "TestCO2" - "TestCO2"
- "TestPM" - "TestPM"
- "TestSht" - "TestSht"
@ -23,6 +25,10 @@ jobs:
exclude: exclude:
- example: "BASIC" - example: "BASIC"
fqbn: "esp32:esp32:esp32c3" fqbn: "esp32:esp32:esp32c3"
- example: "DiyProIndoorV4_2"
fqbn: "esp32:esp32:esp32c3"
- example: "DiyProIndoorV3_3"
fqbn: "esp32:esp32:esp32c3"
- example: "OneOpenAir" - example: "OneOpenAir"
fqbn: "esp8266:esp8266:d1_mini" fqbn: "esp8266:esp8266:d1_mini"
runs-on: ubuntu-latest runs-on: ubuntu-latest

View File

@ -41,6 +41,7 @@ Local server API documentation is available in [/docs/local-server.md](/docs/loc
- [Sensirion I2C SHT](https://github.com/Sensirion/arduino-sht) - [Sensirion I2C SHT](https://github.com/Sensirion/arduino-sht)
- [WiFiManager](https://github.com/tzapu/WiFiManager) - [WiFiManager](https://github.com/tzapu/WiFiManager)
- [Arduino_JSON](https://github.com/arduino-libraries/Arduino_JSON) - [Arduino_JSON](https://github.com/arduino-libraries/Arduino_JSON)
- [PubSubClient](https://github.com/knolleary/pubsubclient)
## License ## License
CC BY-SA 4.0 Attribution-ShareAlike 4.0 International License CC BY-SA 4.0 Attribution-ShareAlike 4.0 International License

View File

@ -17,62 +17,77 @@ With the path "/measures/current" you can get the current air quality data.
http://airgradient_ecda3b1eaaaf.local/measures/current http://airgradient_ecda3b1eaaaf.local/measures/current
“ecda3b1eaaaf” being the serial number of your monitor “ecda3b1eaaaf” being the serial number of your monitor.
You get the following response: You get the following response:
~~~ ```json
{"wifi":-46, {
"serialno":"ecda3b1eaaaf", "wifi": -46,
"rco2":447, "serialno": "ecda3b1eaaaf",
"pm01":3, "rco2": 447,
"pm02":7, "pm01": 3,
"pm10":8, "pm02": 7,
"pm003Count":442, "pm10": 8,
"atmp":25.87, "pm003Count": 442,
"rhum":43, "atmp": 25.87,
"tvocIndex":100, "atmpCompensated": 24.47,
"tvoc_raw":33051, "rhum": 43,
"noxIndex":1, "rhumCompensated": 49,
"nox_raw":16307, "tvocIndex": 100,
"boot":6, "tvocRaw": 33051,
"ledMode":"pm", "noxIndex": 1,
"firmwareVersion":"3.0.10beta", "noxRaw": 16307,
"fwMode":"I-9PSL"} "boot": 6,
~~~ "bootCount": 6,
"ledMode": "pm",
"firmware": "3.1.3",
"model": "I-9PSL"
}
```
|Properties|Type|Explanation| | Properties | Type | Explanation |
|-|-|-| |------------------|--------|--------------------------------------------------------------------|
|serialno|String| Serial Number of the monitor| | `serialno` | String | Serial Number of the monitor |
|wifi|Number| WiFi signal strength| | `wifi` | Number | WiFi signal strength |
|pm01, pm02, pm10|Number| PM1, PM2.5 and PM10 in ug/m3| | `pm01` | Number | PM1 in ug/m3 |
|rco2|Number| CO2 in ppm| | `pm02` | Number | PM2.5 in ug/m3 |
|pm003Count|Number| Particle count per dL| | `pm10` | Number | PM10 in ug/m3 |
|atmp|Number| Temperature in Degrees Celcius| | `pm02Compensated` | Number | PM2.5 in ug/m3 with correction applied (from fw version 3.1.4 onwards) |
|rhum|Number| Relative Humidity| | `rco2` | Number | CO2 in ppm |
|tvocIndex|Number| Senisiron VOC Index| | `pm003Count` | Number | Particle count per dL |
|tvoc_raw|Number| VOC raw value| | `atmp` | Number | Temperature in Degrees Celsius |
|noxIndex|Number| Senisirion NOx Index| | `atmpCompensated` | Number | Temperature in Degrees Celsius with correction applied |
|nox_raw|Number| NOx raw value| | `rhum` | Number | Relative Humidity |
|boot|Number| Counts every measurement cycle. Low boot counts indicate restarts.| | `rhumCompensated` | Number | Relative Humidity with correction applied |
|ledMode|String| Current configuration of the LED mode| | `tvocIndex` | Number | Senisiron VOC Index |
|firmwareVersion|String| Current firmware version| | `tvocRaw` | Number | VOC raw value |
|fwMode|String| Current model name| | `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. Will be depreciated. |
| `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) #### Get Configuration Parameters (GET)
With the path "/config" you can get the current configuration. With the path "/config" you can get the current configuration.
~~~ ```json
{"country":"US", {
"pmStandard":"ugm3", "country": "US",
"ledBarMode":"pm", "pmStandard": "ugm3",
"displayMode":"on", "ledBarMode": "pm",
"abcDays":30, "displayMode": "on",
"tvocLearningOffset":12, "abcDays": 30,
"noxLearningOffset":12, "tvocLearningOffset": 12,
"mqttBrokerUrl":"", "noxLearningOffset": 12,
"temperatureUnit":"f", "mqttBrokerUrl": "",
"configurationControl":"both", "temperatureUnit": "f",
"postDataToAirGradient":true} "configurationControl": "both",
~~~ "postDataToAirGradient": true
}
```
#### Set Configuration Parameters (PUT) #### Set Configuration Parameters (PUT)
@ -82,24 +97,34 @@ Example to force CO2 calibration
```curl -X PUT -H "Content-Type: application/json" -d '{"co2CalibrationRequested":true}' http://airgradient_84fce612eff4.local/config ``` ```curl -X PUT -H "Content-Type: application/json" -d '{"co2CalibrationRequested":true}' http://airgradient_84fce612eff4.local/config ```
Example to set monitor to Celcius Example to set monitor to Celsius
```curl -X PUT -H "Content-Type: application/json" -d '{"temperatureUnit":"c"}' http://airgradient_84fce612eff4.local/config ``` ```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 #### Avoiding Conflicts with Configuration on AirGradient Server
If the monitor is setup on the AirGradient dashboard, it will also receive configurations from there. In case you do not want this, please set "configurationControl" to local. In case you set it to cloud and want to change it to local, you need to make a factory reset. If the monitor is set up on the AirGradient dashboard, it will also receive configurations from there. In case you do not want this, please set `configurationControl` to `local`. In case you set it to `cloud` and want to change it to `local`, you need to make a factory reset.
#### Configuration Parameters (GET/PUT) #### Configuration Parameters (GET/PUT)
|Properties|Type|Accepted Values|Example| | Properties | Description | Type | Accepted Values | Example |
|-|-|-|-| |-------------------------|:-------------------------------------------------------|---------|-----------------------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------|
|country|String| Country code as [ALPHA-2 notation](https://www.iban.com/country-codes) | {"country": "TH"}| | `country` | Country where the device is. | String | Country code as [ALPHA-2 notation](https://www.iban.com/country-codes) | {"country": "TH"} |
|pmStandard|String|ugm3 : ug/m3 <br> usaqi: USAQI | {"pmStandard": "ugm3"}| | `model` | Hardware identifier (only GET). | String | I-9PSL-DE | {"model": "I-9PSL-DE"} |
|ledBarMode|String|co2: LED bar displays CO2 <br> pm: LED bar displays PM <br> off: Turn off LED bar | {"ledBarMode": "off"}| | `pmStandard` | Particle matter standard used on the display. | String | `ugm3`: ug/m3 <br> `us-aqi`: USAQI | {"pmStandard": "ugm3"} |
|abcDays|Number|Number of days for CO2 automatic baseline balibration. Maximum 200 days. Default 8 days. | {"abcDays": 8}| | `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"} |
|mqttBrokerUrl|String|MQTT broker URL. | {"mqttBrokerUrl":"mqtt://192.168.0.18:1883"} | | `displayBrightness` | Brightness of the Display. | Number | 0-100 | {"displayBrightness": 50} |
|temperatureUnit|String|c or C: Degree Celsius °C <br>f or F: Degree Fahrenheit °F | {"temperatureUnit": "c"}| | `ledBarBrightness` | Brightness of the LEDBar. | Number | 0-100 | {"ledBarBrightness": 40} |
|configurationControl|String|both : Accept local and cloud configuration <br>local : Accept only local configuration <br>cloud : Accept only cloud configuration | {"configurationControl": "both"}| | `abcDays` | Number of days for CO2 automatic baseline calibration. | Number | Maximum 200 days. Default 8 days. | {"abcDays": 8} |
|postDataToAirGradient|Boolean|Send data to AirGradient cloud: <br>true : Enabled <br>false: Disabled | {"postDataToAirGradient": true}| | `mqttBrokerUrl` | MQTT broker URL. | String | | {"mqttBrokerUrl": "mqtt://192.168.0.18:1883"} |
|co2CalibrationRequested|Boolean|Trigger CO2 calibration (400ppm) on monitor:<br>true : Calibration will be triggered | {"co2CalibrationRequested": true}| | `temperatureUnit` | Temperature unit shown on the display. | String | `c` or `C`: Degree Celsius °C <br>`f` or `F`: Degree Fahrenheit °F | {"temperatureUnit": "c"} |
|ledBarTestRequested|Boolean|Test LED bar:<br> true : LEDs will run test sequence | {"ledBarTestRequested": true}| | `configurationControl` | The configuration source of the device. | String | `both`: Accept local and cloud configuration <br>`local`: Accept only local configuration <br>`cloud`: Accept only cloud configuration | {"configurationControl": "both"} |
| `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} |

View File

@ -31,187 +31,377 @@ CC BY-SA 4.0 Attribution-ShareAlike 4.0 International License
#include "AgConfigure.h" #include "AgConfigure.h"
#include "AgSchedule.h" #include "AgSchedule.h"
#include "AgWiFiConnector.h" #include "AgWiFiConnector.h"
#include "LocalServer.h"
#include "OpenMetrics.h"
#include "MqttClient.h"
#include <AirGradient.h> #include <AirGradient.h>
#include <ESP8266HTTPClient.h> #include <ESP8266HTTPClient.h>
#include <ESP8266WiFi.h> #include <ESP8266WiFi.h>
#include <ESP8266mDNS.h>
#include <WiFiClient.h> #include <WiFiClient.h>
#define WIFI_CONNECT_COUNTDOWN_MAX 180 /** sec */ #define LED_BAR_ANIMATION_PERIOD 100 /** ms */
#define WIFI_CONNECT_RETRY_MS 10000 /** ms */ #define DISP_UPDATE_INTERVAL 2500 /** ms */
#define LED_BAR_COUNT_INIT_VALUE (-1) /** */ #define SERVER_CONFIG_SYNC_INTERVAL 60000 /** ms */
#define LED_BAR_ANIMATION_PERIOD 100 /** ms */ #define SERVER_SYNC_INTERVAL 60000 /** ms */
#define DISP_UPDATE_INTERVAL 5000 /** ms */ #define MQTT_SYNC_INTERVAL 60000 /** ms */
#define SERVER_CONFIG_UPDATE_INTERVAL 30000 /** ms */ #define SENSOR_CO2_CALIB_COUNTDOWN_MAX 5 /** sec */
#define SERVER_SYNC_INTERVAL 60000 /** ms */ #define SENSOR_TVOC_UPDATE_INTERVAL 1000 /** ms */
#define SENSOR_CO2_CALIB_COUNTDOWN_MAX 5 /** sec */ #define SENSOR_CO2_UPDATE_INTERVAL 4000 /** ms */
#define SENSOR_TVOC_UPDATE_INTERVAL 1000 /** ms */ #define SENSOR_PM_UPDATE_INTERVAL 2000 /** ms */
#define SENSOR_CO2_UPDATE_INTERVAL 5000 /** ms */ #define SENSOR_TEMP_HUM_UPDATE_INTERVAL 2000 /** ms */
#define SENSOR_PM_UPDATE_INTERVAL 5000 /** ms */ #define DISPLAY_DELAY_SHOW_CONTENT_MS 2000 /** ms */
#define SENSOR_TEMP_HUM_UPDATE_INTERVAL 2000 /** ms */ #define FIRMWARE_CHECK_FOR_UPDATE_MS (60 * 60 * 1000) /** ms */
#define DISPLAY_DELAY_SHOW_CONTENT_MS 2000 /** ms */
#define WIFI_HOTSPOT_PASSWORD_DEFAULT \
"cleanair" /** default WiFi AP password \
*/
/** Create airgradient instance for 'DIY_BASIC' board */ static AirGradient ag(DIY_BASIC);
static AirGradient ag = AirGradient(DIY_BASIC);
static Configuration configuration(Serial); static Configuration configuration(Serial);
static AgApiClient apiClient(Serial, configuration); static AgApiClient apiClient(Serial, configuration);
static WifiConnector wifiConnector(Serial); 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 int co2Ppm = -1; static int getCO2FailCount = 0;
static int pm25 = -1; static AgFirmwareMode fwMode = FW_MODE_I_BASIC_40PS;
static float temp = -1001;
static int hum = -1; static String fwNewVersion;
static long val;
static void boardInit(void); static void boardInit(void);
static void failedHandler(String msg); static void failedHandler(String msg);
static void executeCo2Calibration(void); static void configurationUpdateSchedule(void);
static void updateServerConfiguration(void); static void appDispHandler(void);
static void co2Update(void); static void oledDisplaySchedule(void);
static void pmUpdate(void); static void updateTvoc(void);
static void tempHumUpdate(void); static void updatePm(void);
static void sendDataToServer(void); static void sendDataToServer(void);
static void dispHandler(void); static void tempHumUpdate(void);
static String getDevId(void); static void co2Update(void);
static void showNr(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);
bool hasSensorS8 = true; AgSchedule dispLedSchedule(DISP_UPDATE_INTERVAL, oledDisplaySchedule);
bool hasSensorPMS = true; AgSchedule configSchedule(SERVER_CONFIG_SYNC_INTERVAL,
bool hasSensorSHT = true; configurationUpdateSchedule);
int pmFailCount = 0; AgSchedule agApiPostSchedule(SERVER_SYNC_INTERVAL, sendDataToServer);
int getCO2FailCount = 0;
AgSchedule configSchedule(SERVER_CONFIG_UPDATE_INTERVAL,
updateServerConfiguration);
AgSchedule serverSchedule(SERVER_SYNC_INTERVAL, sendDataToServer);
AgSchedule dispSchedule(DISP_UPDATE_INTERVAL, dispHandler);
AgSchedule co2Schedule(SENSOR_CO2_UPDATE_INTERVAL, co2Update); AgSchedule co2Schedule(SENSOR_CO2_UPDATE_INTERVAL, co2Update);
AgSchedule pmsSchedule(SENSOR_PM_UPDATE_INTERVAL, pmUpdate); AgSchedule pmsSchedule(SENSOR_PM_UPDATE_INTERVAL, updatePm);
AgSchedule tempHumSchedule(SENSOR_TEMP_HUM_UPDATE_INTERVAL, tempHumUpdate); AgSchedule tempHumSchedule(SENSOR_TEMP_HUM_UPDATE_INTERVAL, tempHumUpdate);
AgSchedule tvocSchedule(SENSOR_TVOC_UPDATE_INTERVAL, updateTvoc);
AgSchedule watchdogFeedSchedule(60000, wdgFeedUpdate);
AgSchedule mqttSchedule(MQTT_SYNC_INTERVAL, mqttHandle);
void setup() { void setup() {
/** Serial for print debug message */
Serial.begin(115200); Serial.begin(115200);
showNr(); delay(100); /** For bester show log */
/** Print device ID into log */
Serial.println("Serial nr: " + ag.deviceId());
/** Initialize local configure */
configuration.begin();
/** Init I2C */ /** Init I2C */
Wire.begin(ag.getI2cSdaPin(), ag.getI2cSclPin()); Wire.begin(ag.getI2cSdaPin(), ag.getI2cSclPin());
delay(1000); delay(1000);
/** Board init */ 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(); boardInit();
/** Init AirGradient server */ /** Connecting wifi */
apiClient.begin(); bool connectToWifi = false;
apiClient.setAirGradient(&ag);
configuration.setAirGradient(&ag);
wifiConnector.setAirGradient(&ag);
/** Show boot display */ connectToWifi = !configuration.isOfflineMode();
displayShowText("DIY basic", "Lib:" + ag.getVersion(), ""); if (connectToWifi) {
delay(2000); apiClient.begin();
/** WiFi connect */ if (wifiConnector.connect()) {
// connectToWifi(); if (wifiConnector.isConnected()) {
if (wifiConnector.connect()) { mdnsInit();
if (WiFi.status() == WL_CONNECTED) { localServer.begin();
sendDataToAg(); initMqtt();
sendDataToAg();
apiClient.fetchServerConfiguration(); apiClient.fetchServerConfiguration();
if (configuration.isCo2CalibrationRequested()) { configSchedule.update();
executeCo2Calibration(); 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();
}
} }
} }
} }
/** Show serial number display */ /** Set offline mode without saving, cause wifi is not configured */
ag.display.clear(); if (wifiConnector.hasConfigurated() == false) {
ag.display.setCursor(1, 1); Serial.println("Set offline mode cause wifi is not configurated");
ag.display.setText("Warm Up"); configuration.setOfflineModeWithoutSave(true);
ag.display.setCursor(1, 15); }
ag.display.setText("Serial#");
ag.display.setCursor(1, 29);
String id = getNormalizedMac();
Serial.println("Device id: " + id);
String id1 = id.substring(0, 9);
String id2 = id.substring(9, 12);
ag.display.setText("\'" + id1);
ag.display.setCursor(1, 40);
ag.display.setText(id2 + "\'");
ag.display.show();
delay(5000); /** 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() { void loop() {
/** Handle schedule */
dispLedSchedule.run();
configSchedule.run(); configSchedule.run();
serverSchedule.run(); agApiPostSchedule.run();
dispSchedule.run();
if (hasSensorS8) { if (configuration.hasSensorS8) {
co2Schedule.run(); co2Schedule.run();
} }
if (hasSensorPMS) { if (configuration.hasSensorPMS1) {
pmsSchedule.run(); pmsSchedule.run();
ag.pms5003.handle();
} }
if (hasSensorSHT) { if (configuration.hasSensorSHT) {
tempHumSchedule.run(); tempHumSchedule.run();
} }
if (configuration.hasSensorSGP) {
tvocSchedule.run();
}
/** Auto reset watchdog timer if offline mode or postDataToAirGradient */
if (configuration.isOfflineMode() ||
(configuration.isPostDataToAirGradient() == false)) {
watchdogFeedSchedule.run();
}
/** Check for handle WiFi reconnect */
wifiConnector.handle(); wifiConnector.handle();
/** Read PMS on loop */ /** factory reset handle */
ag.pms5003.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) {
int value = ag.s8.getCo2();
if (utils::isValidCO2(value)) {
measurements.CO2 = value;
getCO2FailCount = 0;
Serial.printf("CO2 (ppm): %d\r\n", measurements.CO2);
} else {
getCO2FailCount++;
Serial.printf("Get CO2 failed: %d\r\n", getCO2FailCount);
if (getCO2FailCount >= 3) {
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) {
if (mqttClient.begin(configuration.getMqttBrokerUri())) {
Serial.println("Setup connect to MQTT broker successful");
} else {
Serial.println("setup Connect to MQTT broker failed");
}
}
static void wdgFeedUpdate(void) {
ag.watchdog.reset();
Serial.println();
Serial.println("Offline mode or isPostToAirGradient = false: watchdog reset");
Serial.println();
}
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() { static void sendDataToAg() {
// delay(1500); /** Change oledDisplay and led state */
if (apiClient.sendPing(wifiConnector.RSSI(), 0)) { stateMachine.displayHandle(AgStateMachineWiFiOkServerConnecting);
// Ping Server succses
delay(1500);
if (apiClient.sendPing(wifiConnector.RSSI(), measurements.bootCount)) {
stateMachine.displayHandle(AgStateMachineWiFiOkServerConnected);
} else { } else {
// Ping server failed stateMachine.displayHandle(AgStateMachineWiFiOkServerConnectFailed);
} }
// delay(DISPLAY_DELAY_SHOW_CONTENT_MS); delay(DISPLAY_DELAY_SHOW_CONTENT_MS);
} }
void displayShowText(String ln1, String ln2, String ln3) { void dispSensorNotFound(String ss) {
char buf[9]; oledDisplay.setText("Sensor", ss.c_str(), "not found");
ag.display.clear(); delay(2000);
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();
delay(100);
} }
static void boardInit(void) { static void boardInit(void) {
/** Init SHT sensor */ /** 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) { if (ag.sht.begin(Wire) == false) {
hasSensorSHT = false; Serial.println("SHTx sensor not found");
Serial.println("SHT sensor not found"); configuration.hasSensorSHT = false;
dispSensorNotFound("SHT");
} }
/** CO2 init */ /** Init S8 CO2 sensor */
if (ag.s8.begin(&Serial) == false) { if (ag.s8.begin(&Serial) == false) {
Serial.println("CO2 S8 snsor not found"); Serial.println("CO2 S8 sensor not found");
hasSensorS8 = false; configuration.hasSensorS8 = false;
dispSensorNotFound("S8");
} }
/** PMS init */ /** Init PMS5003 */
configuration.hasSensorPMS1 = true;
configuration.hasSensorPMS2 = false;
if (ag.pms5003.begin(&Serial) == false) { if (ag.pms5003.begin(&Serial) == false) {
Serial.println("PMS sensor not found"); Serial.println("PMS sensor not found");
hasSensorPMS = false; configuration.hasSensorPMS1 = false;
dispSensorNotFound("PMS");
} }
/** Display init */ /** Set S8 CO2 abc days period */
ag.display.begin(Wire); if (configuration.hasSensorS8) {
ag.display.setTextColor(1); if (ag.s8.setAbcPeriod(configuration.getCO2CalibrationAbcDays() * 24)) {
ag.display.clear(); Serial.println("Set S8 AbcDays successful");
ag.display.show(); } else {
delay(100); Serial.println("Set S8 AbcDays failure");
}
}
localServer.setFwMode(fwMode);
} }
static void failedHandler(String msg) { static void failedHandler(String msg) {
@ -221,181 +411,168 @@ static void failedHandler(String msg) {
} }
} }
static void executeCo2Calibration(void) { static void configurationUpdateSchedule(void) {
/** Count down for co2CalibCountdown secs */
for (int i = 0; i < SENSOR_CO2_CALIB_COUNTDOWN_MAX; i++) {
displayShowText("CO2 calib", "after",
String(SENSOR_CO2_CALIB_COUNTDOWN_MAX - 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(DISPLAY_DELAY_SHOW_CONTENT_MS);
} else {
displayShowText("Calib", "failure!!!", "");
delay(DISPLAY_DELAY_SHOW_CONTENT_MS);
}
}
static void updateServerConfiguration(void) {
if (apiClient.fetchServerConfiguration()) { if (apiClient.fetchServerConfiguration()) {
if (configuration.isCo2CalibrationRequested()) { configUpdateHandle();
if (hasSensorS8) { }
executeCo2Calibration(); }
} else {
Serial.println("CO2 S8 not available, calib ignored"); 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()) {
if (configuration.getCO2CalibrationAbcDays() > 0) { Serial.printf("Setting tvocLearningOffset from %d to %d hours %s\r\n",
if (hasSensorS8) { oldTvocOffset, configuration.getTvocLearningOffset(),
int newHour = configuration.getCO2CalibrationAbcDays() * 24; resultStr);
Serial.printf("abcDays config: %d days(%d hours)\r\n", }
configuration.getCO2CalibrationAbcDays(), newHour); if (oldNoxOffset != configuration.getNoxLearningOffset()) {
int curHour = ag.s8.getAbcPeriod(); Serial.printf("Setting noxLearningOffset from %d to %d hours %s\r\n",
Serial.printf("Current config: %d (hours)\r\n", curHour); oldNoxOffset, configuration.getNoxLearningOffset(),
if (curHour == newHour) { resultStr);
Serial.println("set 'abcDays' ignored");
} else {
if (ag.s8.setAbcPeriod(configuration.getCO2CalibrationAbcDays() *
24) == false) {
Serial.println("Set S8 abcDays period calib failed");
} else {
Serial.println("Set S8 abcDays period calib success");
}
}
} else {
Serial.println("CO2 S8 not available, set 'abcDays' ignored");
} }
} }
} }
if (configuration.isDisplayBrightnessChanged()) {
oledDisplay.setBrightness(configuration.getDisplayBrightness());
}
appDispHandler();
} }
static void co2Update() { static void appDispHandler(void) {
int value = ag.s8.getCo2(); AgStateMachineState state = AgStateMachineNormal;
if (value >= 0) {
co2Ppm = value; /** Only show display status on online mode. */
getCO2FailCount = 0; if (configuration.isOfflineMode() == false) {
Serial.printf("CO2 index: %d\r\n", co2Ppm); if (wifiConnector.isConnected() == false) {
} else { state = AgStateMachineWiFiLost;
getCO2FailCount++; } else if (apiClient.isFetchConfigureFailed()) {
Serial.printf("Get CO2 failed: %d\r\n", getCO2FailCount); state = AgStateMachineSensorConfigFailed;
if (getCO2FailCount >= 3) { if (apiClient.isNotAvailableOnDashboard()) {
co2Ppm = -1; stateMachine.displaySetAddToDashBoard();
} else {
stateMachine.displayClearAddToDashBoard();
}
} else if (apiClient.isPostToServerFailed()) {
state = AgStateMachineServerLost;
} }
} }
stateMachine.displayHandle(state);
} }
void pmUpdate() { static void oledDisplaySchedule(void) {
if (ag.pms5003.isFailed() == false) {
pm25 = ag.pms5003.getPm25Ae(); appDispHandler();
Serial.printf("PMS2.5: %d\r\n", pm25);
pmFailCount = 0;
} else {
Serial.printf("PM read failed, %d", pmFailCount);
pmFailCount++;
if (pmFailCount >= 3) {
pm25 = -1;
}
}
} }
static void tempHumUpdate() { static void updateTvoc(void) {
if (ag.sht.measure()) { measurements.TVOC = ag.sgp41.getTvocIndex();
temp = ag.sht.getTemperature(); measurements.TVOCRaw = ag.sgp41.getTvocRaw();
hum = ag.sht.getRelativeHumidity(); measurements.NOx = ag.sgp41.getNoxIndex();
Serial.printf("Temperature: %0.2f\r\n", temp); measurements.NOxRaw = ag.sgp41.getNoxRaw();
Serial.printf(" Humidity: %d\r\n", hum);
} else {
Serial.println("Meaure SHT failed");
}
}
static void sendDataToServer() {
String wifi = "\"wifi\":" + String(WiFi.RSSI());
String rco2 = "";
if(co2Ppm >= 0){
rco2 = ",\"rco2\":" + String(co2Ppm);
}
String pm02 = "";
if(pm25) {
pm02 = ",\"pm02\":" + String(pm25);
}
String rhum = "";
if(hum >= 0){
rhum = ",\"rhum\":" + String(rhum);
}
String payload = "{" + wifi + rco2 + pm02 + rhum + "}";
if (apiClient.postToServer(payload) == false) {
Serial.println("Post to server failed");
}
}
static void dispHandler() {
String ln1 = "";
String ln2 = "";
String ln3 = "";
if (configuration.isPmStandardInUSAQI()) {
if (pm25 < 0) {
ln1 = "AQI: -";
} else {
ln1 = "AQI:" + String(ag.pms5003.convertPm25ToUsAqi(pm25));
}
} else {
if (pm25 < 0) {
ln1 = "PM :- ug";
} else {
ln1 = "PM :" + String(pm25) + " ug";
}
}
if (co2Ppm > -1001) {
ln2 = "CO2:" + String(co2Ppm);
} else {
ln2 = "CO2: -";
}
String _hum = "-";
if (hum > 0) {
_hum = String(hum);
}
String _temp = "-";
if (configuration.isTemperatureUnitInF()) {
if (temp > -1001) {
_temp = String((temp * 9 / 5) + 32).substring(0, 4);
}
ln3 = _temp + " " + _hum + "%";
} else {
if (temp > -1001) {
_temp = String(temp).substring(0, 4);
}
ln3 = _temp + " " + _hum + "%";
}
displayShowText(ln1, ln2, ln3);
}
static String getDevId(void) { return getNormalizedMac(); }
static void showNr(void) {
Serial.println(); Serial.println();
Serial.println("Serial nr: " + getDevId()); Serial.printf("TVOC index: %d\r\n", measurements.TVOC);
Serial.printf("TVOC raw: %d\r\n", measurements.TVOCRaw);
Serial.printf("NOx index: %d\r\n", measurements.NOx);
Serial.printf("NOx raw: %d\r\n", measurements.NOxRaw);
} }
String getNormalizedMac() { static void updatePm(void) {
String mac = WiFi.macAddress(); if (ag.pms5003.isFailed() == false) {
mac.replace(":", ""); measurements.pm01_1 = ag.pms5003.getPm01Ae();
mac.toLowerCase(); measurements.pm25_1 = ag.pms5003.getPm25Ae();
return mac; measurements.pm10_1 = ag.pms5003.getPm10Ae();
measurements.pm03PCount_1 = ag.pms5003.getPm03ParticleCount();
Serial.println();
Serial.printf("PM1 ug/m3: %d\r\n", measurements.pm01_1);
Serial.printf("PM2.5 ug/m3: %d\r\n", measurements.pm25_1);
Serial.printf("PM10 ug/m3: %d\r\n", measurements.pm10_1);
Serial.printf("PM0.3 Count: %d\r\n", measurements.pm03PCount_1);
Serial.printf("PM firmware version: %d\r\n", ag.pms5003.getFirmwareVersion());
ag.pms5003.resetFailCount();
} else {
ag.pms5003.updateFailCount();
Serial.printf("PMS read failed %d times\r\n", ag.pms5003.getFailCount());
if (ag.pms5003.getFailCount() >= PMS_FAIL_COUNT_SET_INVALID) {
measurements.pm01_1 = utils::getInvalidPmValue();
measurements.pm25_1 = utils::getInvalidPmValue();
measurements.pm10_1 = utils::getInvalidPmValue();
measurements.pm03PCount_1 = utils::getInvalidPmValue();
}
if(ag.pms5003.getFailCount() >= ag.pms5003.getFailCountMax()) {
Serial.printf("PMS failure count reach to max set %d, restarting...", ag.pms5003.getFailCountMax());
ESP.restart();
}
}
}
static void sendDataToServer(void) {
/** 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)) {
ag.watchdog.reset();
Serial.println();
Serial.println(
"Online mode and isPostToAirGradient = true: watchdog reset");
Serial.println();
}
measurements.bootCount++;
}
static void tempHumUpdate(void) {
delay(100);
if (ag.sht.measure()) {
measurements.Temperature = ag.sht.getTemperature();
measurements.Humidity = ag.sht.getRelativeHumidity();
Serial.printf("Temperature in C: %0.2f\r\n", measurements.Temperature);
Serial.printf("Relative Humidity: %d\r\n", measurements.Humidity);
Serial.printf("Temperature compensated in C: %0.2f\r\n",
measurements.Temperature);
Serial.printf("Relative Humidity compensated: %d\r\n",
measurements.Humidity);
// Update compensation temperature and humidity for SGP41
if (configuration.hasSensorSGP) {
ag.sgp41.setCompensationTemperatureHumidity(measurements.Temperature,
measurements.Humidity);
}
} else {
Serial.println("SHT read failed");
measurements.Temperature = utils::getInvalidTemperature();
measurements.Humidity = utils::getInvalidHumidity();
}
} }

View File

@ -0,0 +1,61 @@
#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) {
server.send(
200, "application/json",
measure.toString(true, fwMode, wifiConnector.RSSI(), ag, &config));
}
void LocalServer::setFwMode(AgFirmwareMode fwMode) { this->fwMode = fwMode; }

View 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_ */

View File

@ -0,0 +1,186 @@
#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()));
if (config.hasSensorS8 && measure.CO2 >= 0) {
add_metric("co2",
"Carbon dioxide concentration as measured by the AirGradient S8 "
"sensor, in parts per million",
"gauge", "ppm");
add_metric_point("", String(measure.CO2));
}
float _temp = utils::getInvalidTemperature();
float _hum = utils::getInvalidHumidity();
int pm01 = utils::getInvalidPmValue();
int pm25 = utils::getInvalidPmValue();
int pm10 = utils::getInvalidPmValue();
int pm03PCount = utils::getInvalidPmValue();
int atmpCompensated = utils::getInvalidTemperature();
int ahumCompensated = utils::getInvalidHumidity();
if (config.hasSensorSHT) {
_temp = measure.Temperature;
_hum = measure.Humidity;
atmpCompensated = _temp;
ahumCompensated = _hum;
}
if (config.hasSensorPMS1) {
pm01 = measure.pm01_1;
pm25 = measure.pm25_1;
pm10 = measure.pm10_1;
pm03PCount = measure.pm03PCount_1;
}
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(measure.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(measure.TVOC));
}
if (utils::isValidVOC(measure.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(measure.TVOCRaw));
}
if (utils::isValidNOx(measure.NOx)) {
add_metric("nox_index",
"The processed Nitrous Oxide (NOx) index as measured by the "
"AirGradient SGP sensor",
"gauge");
add_metric_point("", String(measure.NOx));
}
if (utils::isValidNOx(measure.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(measure.NOxRaw));
}
}
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;
}

View 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_ */

View File

@ -0,0 +1,630 @@
/*
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 2000 /** ms */
#define DISPLAY_DELAY_SHOW_CONTENT_MS 2000 /** ms */
#define FIRMWARE_CHECK_FOR_UPDATE_MS (60 * 60 * 1000) /** ms */
static AirGradient ag(DIY_PRO_INDOOR_V3_3);
static 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 int getCO2FailCount = 0;
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);
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();
/** 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();
}
/** Auto reset watchdog timer if offline mode or postDataToAirGradient */
if (configuration.isOfflineMode() ||
(configuration.isPostDataToAirGradient() == false)) {
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) {
int value = ag.s8.getCo2();
if (utils::isValidCO2(value)) {
measurements.CO2 = value;
getCO2FailCount = 0;
Serial.printf("CO2 (ppm): %d\r\n", measurements.CO2);
} else {
getCO2FailCount++;
Serial.printf("Get CO2 failed: %d\r\n", getCO2FailCount);
if (getCO2FailCount >= 3) {
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) {
if (mqttClient.begin(configuration.getMqttBrokerUri())) {
Serial.println("Setup connect to MQTT broker successful");
} else {
Serial.println("setup Connect 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();
Serial.println("Offline mode or isPostToAirGradient = false: watchdog reset");
Serial.println();
}
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) {
measurements.TVOC = ag.sgp41.getTvocIndex();
measurements.TVOCRaw = ag.sgp41.getTvocRaw();
measurements.NOx = ag.sgp41.getNoxIndex();
measurements.NOxRaw = ag.sgp41.getNoxRaw();
Serial.println();
Serial.printf("TVOC index: %d\r\n", measurements.TVOC);
Serial.printf("TVOC raw: %d\r\n", measurements.TVOCRaw);
Serial.printf("NOx index: %d\r\n", measurements.NOx);
Serial.printf("NOx raw: %d\r\n", measurements.NOxRaw);
}
static void updatePm(void) {
if (ag.pms5003.isFailed() == false) {
measurements.pm01_1 = ag.pms5003.getPm01Ae();
measurements.pm25_1 = ag.pms5003.getPm25Ae();
measurements.pm10_1 = ag.pms5003.getPm10Ae();
measurements.pm03PCount_1 = ag.pms5003.getPm03ParticleCount();
Serial.println();
Serial.printf("PM1 ug/m3: %d\r\n", measurements.pm01_1);
Serial.printf("PM2.5 ug/m3: %d\r\n", measurements.pm25_1);
Serial.printf("PM10 ug/m3: %d\r\n", measurements.pm10_1);
Serial.printf("PM0.3 Count: %d\r\n", measurements.pm03PCount_1);
Serial.printf("PM firmware version: %d\r\n", ag.pms5003.getFirmwareVersion());
ag.pms5003.resetFailCount();
} else {
ag.pms5003.updateFailCount();
Serial.printf("PMS read failed %d times\r\n", ag.pms5003.getFailCount());
if (ag.pms5003.getFailCount() >= PMS_FAIL_COUNT_SET_INVALID) {
measurements.pm01_1 = utils::getInvalidPmValue();
measurements.pm25_1 = utils::getInvalidPmValue();
measurements.pm10_1 = utils::getInvalidPmValue();
measurements.pm03PCount_1 = utils::getInvalidPmValue();
}
if(ag.pms5003.getFailCount() >= ag.pms5003.getFailCountMax()) {
Serial.printf("PMS failure count reach to max set %d, restarting...", ag.pms5003.getFailCountMax());
ESP.restart();
}
}
}
static void sendDataToServer(void) {
/** 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)) {
ag.watchdog.reset();
Serial.println();
Serial.println(
"Online mode and isPostToAirGradient = true: watchdog reset");
Serial.println();
}
measurements.bootCount++;
}
static void tempHumUpdate(void) {
delay(100);
if (ag.sht.measure()) {
measurements.Temperature = ag.sht.getTemperature();
measurements.Humidity = ag.sht.getRelativeHumidity();
Serial.printf("Temperature in C: %0.2f\r\n", measurements.Temperature);
Serial.printf("Relative Humidity: %d\r\n", measurements.Humidity);
Serial.printf("Temperature compensated in C: %0.2f\r\n",
measurements.Temperature);
Serial.printf("Relative Humidity compensated: %d\r\n",
measurements.Humidity);
// Update compensation temperature and humidity for SGP41
if (configuration.hasSensorSGP) {
ag.sgp41.setCompensationTemperatureHumidity(measurements.Temperature,
measurements.Humidity);
}
} else {
Serial.println("SHT read failed");
measurements.Temperature = utils::getInvalidTemperature();
measurements.Humidity = utils::getInvalidHumidity();
}
}

View File

@ -0,0 +1,61 @@
#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) {
server.send(
200, "application/json",
measure.toString(true, fwMode, wifiConnector.RSSI(), ag, &config));
}
void LocalServer::setFwMode(AgFirmwareMode fwMode) { this->fwMode = fwMode; }

View 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_ */

View File

@ -0,0 +1,186 @@
#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()));
if (config.hasSensorS8 && measure.CO2 >= 0) {
add_metric("co2",
"Carbon dioxide concentration as measured by the AirGradient S8 "
"sensor, in parts per million",
"gauge", "ppm");
add_metric_point("", String(measure.CO2));
}
float _temp = utils::getInvalidTemperature();
float _hum = utils::getInvalidHumidity();
int pm01 = utils::getInvalidPmValue();
int pm25 = utils::getInvalidPmValue();
int pm10 = utils::getInvalidPmValue();
int pm03PCount = utils::getInvalidPmValue();
int atmpCompensated = utils::getInvalidTemperature();
int ahumCompensated = utils::getInvalidHumidity();
if (config.hasSensorSHT) {
_temp = measure.Temperature;
_hum = measure.Humidity;
atmpCompensated = _temp;
ahumCompensated = _hum;
}
if (config.hasSensorPMS1) {
pm01 = measure.pm01_1;
pm25 = measure.pm25_1;
pm10 = measure.pm10_1;
pm03PCount = measure.pm03PCount_1;
}
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(measure.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(measure.TVOC));
}
if (utils::isValidVOC(measure.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(measure.TVOCRaw));
}
if (utils::isValidNOx(measure.NOx)) {
add_metric("nox_index",
"The processed Nitrous Oxide (NOx) index as measured by the "
"AirGradient SGP sensor",
"gauge");
add_metric_point("", String(measure.NOx));
}
if (utils::isValidNOx(measure.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(measure.NOxRaw));
}
}
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;
}

View 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_ */

View File

@ -0,0 +1,671 @@
/*
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 2000 /** ms */
#define DISPLAY_DELAY_SHOW_CONTENT_MS 2000 /** ms */
#define FIRMWARE_CHECK_FOR_UPDATE_MS (60 * 60 * 1000) /** ms */
static AirGradient ag(DIY_PRO_INDOOR_V4_2);
static 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 int getCO2FailCount = 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);
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();
/** 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();
}
/** Auto reset watchdog timer if offline mode or postDataToAirGradient */
if (configuration.isOfflineMode() ||
(configuration.isPostDataToAirGradient() == false)) {
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) {
int value = ag.s8.getCo2();
if (utils::isValidCO2(value)) {
measurements.CO2 = value;
getCO2FailCount = 0;
Serial.printf("CO2 (ppm): %d\r\n", measurements.CO2);
} else {
getCO2FailCount++;
Serial.printf("Get CO2 failed: %d\r\n", getCO2FailCount);
if (getCO2FailCount >= 3) {
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) {
if (mqttClient.begin(configuration.getMqttBrokerUri())) {
Serial.println("Setup connect to MQTT broker successful");
} else {
Serial.println("setup Connect 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 */
WiFi.disconnect(true, true);
/** 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();
Serial.println("Offline mode or isPostToAirGradient = false: watchdog reset");
Serial.println();
}
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) {
measurements.TVOC = ag.sgp41.getTvocIndex();
measurements.TVOCRaw = ag.sgp41.getTvocRaw();
measurements.NOx = ag.sgp41.getNoxIndex();
measurements.NOxRaw = ag.sgp41.getNoxRaw();
Serial.println();
Serial.printf("TVOC index: %d\r\n", measurements.TVOC);
Serial.printf("TVOC raw: %d\r\n", measurements.TVOCRaw);
Serial.printf("NOx index: %d\r\n", measurements.NOx);
Serial.printf("NOx raw: %d\r\n", measurements.NOxRaw);
}
static void updatePm(void) {
if (ag.pms5003.isFailed() == false) {
measurements.pm01_1 = ag.pms5003.getPm01Ae();
measurements.pm25_1 = ag.pms5003.getPm25Ae();
measurements.pm10_1 = ag.pms5003.getPm10Ae();
measurements.pm03PCount_1 = ag.pms5003.getPm03ParticleCount();
Serial.println();
Serial.printf("PM1 ug/m3: %d\r\n", measurements.pm01_1);
Serial.printf("PM2.5 ug/m3: %d\r\n", measurements.pm25_1);
Serial.printf("PM10 ug/m3: %d\r\n", measurements.pm10_1);
Serial.printf("PM0.3 Count: %d\r\n", measurements.pm03PCount_1);
Serial.printf("PM firmware version: %d\r\n", ag.pms5003.getFirmwareVersion());
ag.pms5003.resetFailCount();
} else {
ag.pms5003.updateFailCount();
Serial.printf("PMS read failed %d times\r\n", ag.pms5003.getFailCount());
if (ag.pms5003.getFailCount() >= PMS_FAIL_COUNT_SET_INVALID) {
measurements.pm01_1 = utils::getInvalidPmValue();
measurements.pm25_1 = utils::getInvalidPmValue();
measurements.pm10_1 = utils::getInvalidPmValue();
measurements.pm03PCount_1 = utils::getInvalidPmValue();
}
if(ag.pms5003.getFailCount() >= ag.pms5003.getFailCountMax()) {
Serial.printf("PMS failure count reach to max set %d, restarting...", ag.pms5003.getFailCountMax());
ESP.restart();
}
}
}
static void sendDataToServer(void) {
/** 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)) {
ag.watchdog.reset();
Serial.println();
Serial.println(
"Online mode and isPostToAirGradient = true: watchdog reset");
Serial.println();
}
measurements.bootCount++;
}
static void tempHumUpdate(void) {
delay(100);
if (ag.sht.measure()) {
measurements.Temperature = ag.sht.getTemperature();
measurements.Humidity = ag.sht.getRelativeHumidity();
Serial.printf("Temperature in C: %0.2f\r\n", measurements.Temperature);
Serial.printf("Relative Humidity: %d\r\n", measurements.Humidity);
Serial.printf("Temperature compensated in C: %0.2f\r\n",
measurements.Temperature);
Serial.printf("Relative Humidity compensated: %d\r\n",
measurements.Humidity);
// Update compensation temperature and humidity for SGP41
if (configuration.hasSensorSGP) {
ag.sgp41.setCompensationTemperatureHumidity(measurements.Temperature,
measurements.Humidity);
}
} else {
Serial.println("SHT read failed");
measurements.Temperature = utils::getInvalidTemperature();
measurements.Humidity = utils::getInvalidHumidity();
}
}

View File

@ -0,0 +1,61 @@
#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) {
server.send(
200, "application/json",
measure.toString(true, fwMode, wifiConnector.RSSI(), ag, &config));
}
void LocalServer::setFwMode(AgFirmwareMode fwMode) { this->fwMode = fwMode; }

View 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_ */

View File

@ -0,0 +1,186 @@
#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()));
if (config.hasSensorS8 && measure.CO2 >= 0) {
add_metric("co2",
"Carbon dioxide concentration as measured by the AirGradient S8 "
"sensor, in parts per million",
"gauge", "ppm");
add_metric_point("", String(measure.CO2));
}
float _temp = utils::getInvalidTemperature();
float _hum = utils::getInvalidHumidity();
int pm01 = utils::getInvalidPmValue();
int pm25 = utils::getInvalidPmValue();
int pm10 = utils::getInvalidPmValue();
int pm03PCount = utils::getInvalidPmValue();
int atmpCompensated = utils::getInvalidTemperature();
int ahumCompensated = utils::getInvalidHumidity();
if (config.hasSensorSHT) {
_temp = measure.Temperature;
_hum = measure.Humidity;
atmpCompensated = _temp;
ahumCompensated = _hum;
}
if (config.hasSensorPMS1) {
pm01 = measure.pm01_1;
pm25 = measure.pm25_1;
pm10 = measure.pm10_1;
pm03PCount = measure.pm03PCount_1;
}
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(measure.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(measure.TVOC));
}
if (utils::isValidVOC(measure.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(measure.TVOCRaw));
}
if (utils::isValidNOx(measure.NOx)) {
add_metric("nox_index",
"The processed Nitrous Oxide (NOx) index as measured by the "
"AirGradient SGP sensor",
"gauge");
add_metric_point("", String(measure.NOx));
}
if (utils::isValidNOx(measure.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(measure.NOxRaw));
}
}
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;
}

View 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_ */

View File

@ -55,7 +55,7 @@ CC BY-SA 4.0 Attribution-ShareAlike 4.0 International License
#define LED_BAR_ANIMATION_PERIOD 100 /** ms */ #define LED_BAR_ANIMATION_PERIOD 100 /** ms */
#define DISP_UPDATE_INTERVAL 2500 /** ms */ #define DISP_UPDATE_INTERVAL 2500 /** ms */
#define SERVER_CONFIG_UPDATE_INTERVAL 15000 /** ms */ #define SERVER_CONFIG_SYNC_INTERVAL 60000 /** ms */
#define SERVER_SYNC_INTERVAL 60000 /** ms */ #define SERVER_SYNC_INTERVAL 60000 /** ms */
#define MQTT_SYNC_INTERVAL 60000 /** ms */ #define MQTT_SYNC_INTERVAL 60000 /** ms */
#define SENSOR_CO2_CALIB_COUNTDOWN_MAX 5 /** sec */ #define SENSOR_CO2_CALIB_COUNTDOWN_MAX 5 /** sec */
@ -64,6 +64,7 @@ CC BY-SA 4.0 Attribution-ShareAlike 4.0 International License
#define SENSOR_PM_UPDATE_INTERVAL 2000 /** ms */ #define SENSOR_PM_UPDATE_INTERVAL 2000 /** ms */
#define SENSOR_TEMP_HUM_UPDATE_INTERVAL 2000 /** ms */ #define SENSOR_TEMP_HUM_UPDATE_INTERVAL 2000 /** ms */
#define DISPLAY_DELAY_SHOW_CONTENT_MS 2000 /** ms */ #define DISPLAY_DELAY_SHOW_CONTENT_MS 2000 /** ms */
#define FIRMWARE_CHECK_FOR_UPDATE_MS (60*60*1000) /** ms */
/** I2C define */ /** I2C define */
#define I2C_SDA_PIN 7 #define I2C_SDA_PIN 7
@ -87,7 +88,6 @@ static OtaHandler otaHandler;
static LocalServer localServer(Serial, openMetrics, measurements, configuration, static LocalServer localServer(Serial, openMetrics, measurements, configuration,
wifiConnector); wifiConnector);
static int pmFailCount = 0;
static uint32_t factoryBtnPressTime = 0; static uint32_t factoryBtnPressTime = 0;
static int getCO2FailCount = 0; static int getCO2FailCount = 0;
static AgFirmwareMode fwMode = FW_MODE_I_9PSL; static AgFirmwareMode fwMode = FW_MODE_I_9PSL;
@ -113,12 +113,13 @@ static void factoryConfigReset(void);
static void wdgFeedUpdate(void); static void wdgFeedUpdate(void);
static void ledBarEnabledUpdate(void); static void ledBarEnabledUpdate(void);
static bool sgp41Init(void); static bool sgp41Init(void);
static void firmwareCheckForUpdate(void);
static void otaHandlerCallback(OtaState state, String mesasge); static void otaHandlerCallback(OtaState state, String mesasge);
static void displayExecuteOta(OtaState state, String msg, static void displayExecuteOta(OtaState state, String msg,
int processing); int processing);
AgSchedule dispLedSchedule(DISP_UPDATE_INTERVAL, oledDisplayLedBarSchedule); AgSchedule dispLedSchedule(DISP_UPDATE_INTERVAL, oledDisplayLedBarSchedule);
AgSchedule configSchedule(SERVER_CONFIG_UPDATE_INTERVAL, AgSchedule configSchedule(SERVER_CONFIG_SYNC_INTERVAL,
configurationUpdateSchedule); configurationUpdateSchedule);
AgSchedule agApiPostSchedule(SERVER_SYNC_INTERVAL, sendDataToServer); AgSchedule agApiPostSchedule(SERVER_SYNC_INTERVAL, sendDataToServer);
AgSchedule co2Schedule(SENSOR_CO2_UPDATE_INTERVAL, co2Update); AgSchedule co2Schedule(SENSOR_CO2_UPDATE_INTERVAL, co2Update);
@ -126,6 +127,7 @@ AgSchedule pmsSchedule(SENSOR_PM_UPDATE_INTERVAL, updatePm);
AgSchedule tempHumSchedule(SENSOR_TEMP_HUM_UPDATE_INTERVAL, tempHumUpdate); AgSchedule tempHumSchedule(SENSOR_TEMP_HUM_UPDATE_INTERVAL, tempHumUpdate);
AgSchedule tvocSchedule(SENSOR_TVOC_UPDATE_INTERVAL, updateTvoc); AgSchedule tvocSchedule(SENSOR_TVOC_UPDATE_INTERVAL, updateTvoc);
AgSchedule watchdogFeedSchedule(60000, wdgFeedUpdate); AgSchedule watchdogFeedSchedule(60000, wdgFeedUpdate);
AgSchedule checkForUpdateSchedule(FIRMWARE_CHECK_FOR_UPDATE_MS, firmwareCheckForUpdate);
void setup() { void setup() {
/** Serial for print debug message */ /** Serial for print debug message */
@ -152,8 +154,6 @@ void setup() {
} }
Serial.println("Detected " + ag->getBoardName()); Serial.println("Detected " + ag->getBoardName());
/** Init sensor */
boardInit();
configuration.setAirGradient(ag); configuration.setAirGradient(ag);
oledDisplay.setAirGradient(ag); oledDisplay.setAirGradient(ag);
stateMachine.setAirGradient(ag); stateMachine.setAirGradient(ag);
@ -162,20 +162,15 @@ void setup() {
openMetrics.setAirGradient(ag); openMetrics.setAirGradient(ag);
localServer.setAirGraident(ag); localServer.setAirGraident(ag);
/** Example set custom API root URL */
// apiClient.setApiRoot("https://example.custom.api");
/** Init sensor */
boardInit();
/** Connecting wifi */ /** Connecting wifi */
bool connectToWifi = false; bool connectToWifi = false;
if (ag->isOne()) { if (ag->isOne()) {
if (ledBarButtonTest) {
stateMachine.executeLedBarPowerUpTest();
if (ag->button.getState() == PushButton::BUTTON_PRESSED) {
WiFi.begin("airgradient", "cleanair");
Serial.println("WiFi Credential reset to factory defaults");
ESP.restart();
}
} else {
ledBarEnabledUpdate();
}
/** Show message confirm offline mode, should me perform if LED bar button /** Show message confirm offline mode, should me perform if LED bar button
* test pressed */ * test pressed */
if (ledBarButtonTest == false) { if (ledBarButtonTest == false) {
@ -219,15 +214,21 @@ void setup() {
#ifdef ESP8266 #ifdef ESP8266
// ota not supported // ota not supported
#else #else
// otaHandler.updateFirmwareIfOutdated(ag->deviceId()); firmwareCheckForUpdate();
checkForUpdateSchedule.update();
#endif #endif
apiClient.fetchServerConfiguration(); apiClient.fetchServerConfiguration();
configSchedule.update(); configSchedule.update();
if (apiClient.isFetchConfigureFailed()) { if (apiClient.isFetchConfigureFailed()) {
if (ag->isOne()) { if (ag->isOne()) {
stateMachine.displayHandle( if (apiClient.isNotAvailableOnDashboard()) {
AgStateMachineWiFiOkServerOkSensorConfigFailed); stateMachine.displaySetAddToDashBoard();
stateMachine.displayHandle(
AgStateMachineWiFiOkServerOkSensorConfigFailed);
} else {
stateMachine.displayClearAddToDashBoard();
}
} }
stateMachine.handleLeds( stateMachine.handleLeds(
AgStateMachineWiFiOkServerOkSensorConfigFailed); AgStateMachineWiFiOkServerOkSensorConfigFailed);
@ -235,6 +236,13 @@ void setup() {
} else { } else {
ledBarEnabledUpdate(); ledBarEnabledUpdate();
} }
} else {
if (wifiConnector.isConfigurePorttalTimeout()) {
oledDisplay.showRebooting();
delay(2500);
oledDisplay.setText("", "", "");
ESP.restart();
}
} }
} }
} }
@ -304,11 +312,14 @@ void loop() {
/** check that local configura changed then do some action */ /** check that local configura changed then do some action */
configUpdateHandle(); configUpdateHandle();
/** Firmware check for update handle */
checkForUpdateSchedule.run();
} }
static void co2Update(void) { static void co2Update(void) {
int value = ag->s8.getCo2(); int value = ag->s8.getCo2();
if (value >= 0) { if (utils::isValidCO2(value)) {
measurements.CO2 = value; measurements.CO2 = value;
getCO2FailCount = 0; getCO2FailCount = 0;
Serial.printf("CO2 (ppm): %d\r\n", measurements.CO2); Serial.printf("CO2 (ppm): %d\r\n", measurements.CO2);
@ -316,7 +327,7 @@ static void co2Update(void) {
getCO2FailCount++; getCO2FailCount++;
Serial.printf("Get CO2 failed: %d\r\n", getCO2FailCount); Serial.printf("Get CO2 failed: %d\r\n", getCO2FailCount);
if (getCO2FailCount >= 3) { if (getCO2FailCount >= 3) {
measurements.CO2 = -1; measurements.CO2 = utils::getInvalidCO2();
} }
} }
} }
@ -423,6 +434,7 @@ static void factoryConfigReset(void) {
Serial.println("Factory reset successful"); Serial.println("Factory reset successful");
} }
delay(3000); delay(3000);
oledDisplay.setText("","","");
ESP.restart(); ESP.restart();
} }
} }
@ -459,9 +471,10 @@ static void ledBarEnabledUpdate(void) {
if ((brightness == 0) || (configuration.getLedBarMode() == LedBarModeOff)) { if ((brightness == 0) || (configuration.getLedBarMode() == LedBarModeOff)) {
ag->ledBar.setEnable(false); ag->ledBar.setEnable(false);
} else { } else {
ag->ledBar.setBrighness(brightness); ag->ledBar.setBrightness(brightness);
ag->ledBar.setEnable(configuration.getLedBarMode() != LedBarModeOff); ag->ledBar.setEnable(configuration.getLedBarMode() != LedBarModeOff);
} }
ag->ledBar.show();
} }
} }
@ -479,7 +492,22 @@ static bool sgp41Init(void) {
return false; return false;
} }
static void firmwareCheckForUpdate(void) {
Serial.println();
Serial.println("firmwareCheckForUpdate:");
if (wifiConnector.isConnected()) {
Serial.println("firmwareCheckForUpdate: Perform");
otaHandler.setHandlerCallback(otaHandlerCallback);
otaHandler.updateFirmwareIfOutdated(ag->deviceId());
} else {
Serial.println("firmwareCheckForUpdate: Ignored");
}
Serial.println();
}
static void otaHandlerCallback(OtaState state, String mesasge) { static void otaHandlerCallback(OtaState state, String mesasge) {
Serial.println("OTA message: " + mesasge);
switch (state) { switch (state) {
case OtaState::OTA_STATE_BEGIN: case OtaState::OTA_STATE_BEGIN:
displayExecuteOta(state, fwNewVersion, 0); displayExecuteOta(state, fwNewVersion, 0);
@ -502,7 +530,7 @@ static void displayExecuteOta(OtaState state, String msg, int processing) {
switch (state) { switch (state) {
case OtaState::OTA_STATE_BEGIN: { case OtaState::OTA_STATE_BEGIN: {
if (ag->isOne()) { if (ag->isOne()) {
oledDisplay.showNewFirmwareVersion(msg); oledDisplay.showFirmwareUpdateVersion(msg);
} else { } else {
Serial.println("New firmware: " + msg); Serial.println("New firmware: " + msg);
} }
@ -511,7 +539,7 @@ static void displayExecuteOta(OtaState state, String msg, int processing) {
} }
case OtaState::OTA_STATE_FAIL: { case OtaState::OTA_STATE_FAIL: {
if (ag->isOne()) { if (ag->isOne()) {
oledDisplay.showNewFirmwareFailed(); oledDisplay.showFirmwareUpdateFailed();
} else { } else {
Serial.println("Error: Firmware update: failed"); Serial.println("Error: Firmware update: failed");
} }
@ -519,9 +547,29 @@ static void displayExecuteOta(OtaState state, String msg, int processing) {
delay(2500); delay(2500);
break; break;
} }
case OtaState::OTA_STATE_SKIP: {
if (ag->isOne()) {
oledDisplay.showFirmwareUpdateSkipped();
} else {
Serial.println("Firmware update: Skipped");
}
delay(2500);
break;
}
case OtaState::OTA_STATE_UP_TO_DATE: {
if (ag->isOne()) {
oledDisplay.showFirmwareUpdateUpToDate();
} else {
Serial.println("Firmware update: up to date");
}
delay(2500);
break;
}
case OtaState::OTA_STATE_PROCESSING: { case OtaState::OTA_STATE_PROCESSING: {
if (ag->isOne()) { if (ag->isOne()) {
oledDisplay.showNewFirmwareUpdating(String(processing)); oledDisplay.showFirmwareUpdateProgress(processing);
} else { } else {
Serial.println("Firmware update: " + String(processing) + String("%")); Serial.println("Firmware update: " + String(processing) + String("%"));
} }
@ -537,13 +585,14 @@ static void displayExecuteOta(OtaState state, String msg, int processing) {
while (i != 0) { while (i != 0) {
i = i - 1; i = i - 1;
if (ag->isOne()) { if (ag->isOne()) {
oledDisplay.showNewFirmwareSuccess(String(i)); oledDisplay.showFirmwareUpdateSuccess(i);
} else { } else {
Serial.println("Rebooting... " + String(i)); Serial.println("Rebooting... " + String(i));
} }
delay(1000); delay(1000);
} }
oledDisplay.setBrightness(0);
esp_restart(); esp_restart();
} }
break; break;
@ -615,6 +664,41 @@ static void oneIndoorInit(void) {
ag->button.begin(); ag->button.begin();
ag->watchdog.begin(); ag->watchdog.begin();
/** Run LED test on start up if button pressed */
oledDisplay.setText("Press now for", "LED test", "");
ledBarButtonTest = false;
uint32_t stime = millis();
while (true) {
if (ag->button.getState() == ag->button.BUTTON_PRESSED) {
ledBarButtonTest = true;
stateMachine.executeLedBarPowerUpTest();
break;
}
delay(1);
uint32_t ms = (uint32_t)(millis() - stime);
if (ms >= 3000) {
break;
}
}
/** Check for button to reset WiFi connecto to "airgraident" after test LED
* bar */
if (ledBarButtonTest) {
if (ag->button.getState() == ag->button.BUTTON_PRESSED) {
WiFi.begin("airgradient", "cleanair");
oledDisplay.setText("Configure WiFi", "connect to", "\'airgradient\'");
delay(2500);
oledDisplay.setText("Rebooting...", "","");
delay(2500);
oledDisplay.setText("","","");
ESP.restart();
}
}
ledBarEnabledUpdate();
/** Show message init sensor */
oledDisplay.setText("Monitor", "initializing...", "");
/** Init sensor SGP41 */ /** Init sensor SGP41 */
if (sgp41Init() == false) { if (sgp41Init() == false) {
dispSensorNotFound("SGP41"); dispSensorNotFound("SGP41");
@ -641,22 +725,6 @@ static void oneIndoorInit(void) {
dispSensorNotFound("PMS"); dispSensorNotFound("PMS");
} }
/** Run LED test on start up */
oledDisplay.setText("Press now for", "LED test", "");
ledBarButtonTest = false;
uint32_t stime = millis();
while (true) {
if (ag->button.getState() == ag->button.BUTTON_PRESSED) {
ledBarButtonTest = true;
break;
}
delay(1);
uint32_t ms = (uint32_t)(millis() - stime);
if (ms >= 3000) {
break;
}
}
} }
static void openAirInit(void) { static void openAirInit(void) {
configuration.hasSensorSHT = false; configuration.hasSensorSHT = false;
@ -710,27 +778,27 @@ static void openAirInit(void) {
} }
} }
/** Try to find the PMS on other difference port with S8 */ /** Attempt to detect PM sensors */
if (fwMode == FW_MODE_O_1PST) { if (fwMode == FW_MODE_O_1PST) {
bool pmInitSuccess = false; bool pmInitSuccess = false;
if (serial0Available) { if (serial0Available) {
if (ag->pms5003t_1.begin(Serial0) == false) { if (ag->pms5003t_1.begin(Serial0) == false) {
configuration.hasSensorPMS1 = false; configuration.hasSensorPMS1 = false;
Serial.println("PMS1 sensor not found"); Serial.println("No PM sensor detected on Serial0");
} else { } else {
serial0Available = false; serial0Available = false;
pmInitSuccess = true; pmInitSuccess = true;
Serial.println("Found PMS 1 on Serial0"); Serial.println("Detected PM 1 on Serial0");
} }
} }
if (pmInitSuccess == false) { if (pmInitSuccess == false) {
if (serial1Available) { if (serial1Available) {
if (ag->pms5003t_1.begin(Serial1) == false) { if (ag->pms5003t_1.begin(Serial1) == false) {
configuration.hasSensorPMS1 = false; configuration.hasSensorPMS1 = false;
Serial.println("PMS1 sensor not found"); Serial.println("No PM sensor detected on Serial1");
} else { } else {
serial1Available = false; serial1Available = false;
Serial.println("Found PMS 1 on Serial1"); Serial.println("Detected PM 1 on Serial1");
} }
} }
} }
@ -738,15 +806,15 @@ static void openAirInit(void) {
} else { } else {
if (ag->pms5003t_1.begin(Serial0) == false) { if (ag->pms5003t_1.begin(Serial0) == false) {
configuration.hasSensorPMS1 = false; configuration.hasSensorPMS1 = false;
Serial.println("PMS1 sensor not found"); Serial.println("No PM sensor detected on Serial0");
} else { } else {
Serial.println("Found PMS 1 on Serial0"); Serial.println("Detected PM 1 on Serial0");
} }
if (ag->pms5003t_2.begin(Serial1) == false) { if (ag->pms5003t_2.begin(Serial1) == false) {
configuration.hasSensorPMS2 = false; configuration.hasSensorPMS2 = false;
Serial.println("PMS2 sensor not found"); Serial.println("No PM sensor detected on Serial1");
} else { } else {
Serial.println("Found PMS 2 on Serial1"); Serial.println("Detected PM 2 on Serial1");
} }
if (fwMode == FW_MODE_O_1PP) { if (fwMode == FW_MODE_O_1PP) {
@ -804,10 +872,6 @@ static void configUpdateHandle() {
return; return;
} }
if (ag->isOne()) {
ledBarEnabledUpdate();
stateMachine.executeLedBarTest();
}
stateMachine.executeCo2Calibration(); stateMachine.executeCo2Calibration();
String mqttUri = configuration.getMqttBrokerUri(); String mqttUri = configuration.getMqttBrokerUri();
@ -843,39 +907,39 @@ static void configUpdateHandle() {
if (ag->isOne()) { if (ag->isOne()) {
if (configuration.isLedBarBrightnessChanged()) { if (configuration.isLedBarBrightnessChanged()) {
ag->ledBar.setBrighness(configuration.getLedBarBrightness()); if (configuration.getLedBarBrightness() == 0) {
Serial.println("Set 'LedBarBrightness' brightness: " + ag->ledBar.setEnable(false);
String(configuration.getLedBarBrightness())); } else {
if (configuration.getLedBarMode() != LedBarMode::LedBarModeOff) {
ag->ledBar.setEnable(true);
}
ag->ledBar.setBrightness(configuration.getLedBarBrightness());
}
ag->ledBar.show();
} }
if (configuration.isLedBarModeChanged()) {
if (configuration.getLedBarBrightness() == 0) {
ag->ledBar.setEnable(false);
} else {
if(configuration.getLedBarMode() == LedBarMode::LedBarModeOff) {
ag->ledBar.setEnable(false);
} else {
ag->ledBar.setEnable(true);
ag->ledBar.setBrightness(configuration.getLedBarBrightness());
}
}
ag->ledBar.show();
}
if (configuration.isDisplayBrightnessChanged()) { if (configuration.isDisplayBrightnessChanged()) {
oledDisplay.setBrightness(configuration.getDisplayBrightness()); oledDisplay.setBrightness(configuration.getDisplayBrightness());
Serial.println("Set 'DisplayBrightness' brightness: " +
String(configuration.getDisplayBrightness()));
} }
stateMachine.executeLedBarTest();
} }
else if(ag->isOpenAir()) {
fwNewVersion = configuration.newFirmwareVersion(); stateMachine.executeLedBarTest();
if (fwNewVersion.length()) {
bool doOta = false;
if (measurements.otaBootCount == 0) {
doOta = true;
Serial.println("First OTA");
} else {
if ((measurements.bootCount - measurements.otaBootCount) >= 30) {
doOta = true;
} else {
Serial.println(
"OTA ignore, try again next " +
String(30 - (measurements.bootCount - measurements.otaBootCount)) +
String(" boots"));
}
}
if (doOta) {
measurements.otaBootCount = measurements.bootCount;
otaHandler.setHandlerCallback(otaHandlerCallback);
otaHandler.updateFirmwareIfOutdated(ag->deviceId());
}
} }
appDispHandler(); appDispHandler();
@ -888,7 +952,6 @@ static void appLedHandler(void) {
if (wifiConnector.isConnected() == false) { if (wifiConnector.isConnected() == false) {
state = AgStateMachineWiFiLost; state = AgStateMachineWiFiLost;
} else if (apiClient.isFetchConfigureFailed()) { } else if (apiClient.isFetchConfigureFailed()) {
stateMachine.displaySetAddToDashBoard();
state = AgStateMachineSensorConfigFailed; state = AgStateMachineSensorConfigFailed;
} else if (apiClient.isPostToServerFailed()) { } else if (apiClient.isPostToServerFailed()) {
state = AgStateMachineServerLost; state = AgStateMachineServerLost;
@ -908,6 +971,11 @@ static void appDispHandler(void) {
state = AgStateMachineWiFiLost; state = AgStateMachineWiFiLost;
} else if (apiClient.isFetchConfigureFailed()) { } else if (apiClient.isFetchConfigureFailed()) {
state = AgStateMachineSensorConfigFailed; state = AgStateMachineSensorConfigFailed;
if (apiClient.isNotAvailableOnDashboard()) {
stateMachine.displaySetAddToDashBoard();
} else {
stateMachine.displayClearAddToDashBoard();
}
} else if (apiClient.isPostToServerFailed()) { } else if (apiClient.isPostToServerFailed()) {
state = AgStateMachineServerLost; state = AgStateMachineServerLost;
} }
@ -939,6 +1007,7 @@ static void updateTvoc(void) {
} }
static void updatePm(void) { static void updatePm(void) {
bool restart = false;
if (ag->isOne()) { if (ag->isOne()) {
if (ag->pms5003.isFailed() == false) { if (ag->pms5003.isFailed() == false) {
measurements.pm01_1 = ag->pms5003.getPm01Ae(); measurements.pm01_1 = ag->pms5003.getPm01Ae();
@ -951,15 +1020,20 @@ static void updatePm(void) {
Serial.printf("PM2.5 ug/m3: %d\r\n", measurements.pm25_1); Serial.printf("PM2.5 ug/m3: %d\r\n", measurements.pm25_1);
Serial.printf("PM10 ug/m3: %d\r\n", measurements.pm10_1); Serial.printf("PM10 ug/m3: %d\r\n", measurements.pm10_1);
Serial.printf("PM0.3 Count: %d\r\n", measurements.pm03PCount_1); Serial.printf("PM0.3 Count: %d\r\n", measurements.pm03PCount_1);
pmFailCount = 0; Serial.printf("PM firmware version: %d\r\n", ag->pms5003.getFirmwareVersion());
ag->pms5003.resetFailCount();
} else { } else {
pmFailCount++; ag->pms5003.updateFailCount();
Serial.printf("PMS read failed: %d\r\n", pmFailCount); Serial.printf("PMS read failed %d times\r\n", ag->pms5003.getFailCount());
if (pmFailCount >= 3) { if (ag->pms5003.getFailCount() >= PMS_FAIL_COUNT_SET_INVALID) {
measurements.pm01_1 = -1; measurements.pm01_1 = utils::getInvalidPmValue();
measurements.pm25_1 = -1; measurements.pm25_1 = utils::getInvalidPmValue();
measurements.pm10_1 = -1; measurements.pm10_1 = utils::getInvalidPmValue();
measurements.pm03PCount_1 = -1; measurements.pm03PCount_1 = utils::getInvalidPmValue();
}
if (ag->pms5003.getFailCount() >= ag->pms5003.getFailCountMax()) {
restart = true;
} }
} }
} else { } else {
@ -983,16 +1057,30 @@ static void updatePm(void) {
Serial.printf("[1] Temperature in C: %0.2f\r\n", measurements.temp_1); Serial.printf("[1] Temperature in C: %0.2f\r\n", measurements.temp_1);
Serial.printf("[1] Relative Humidity: %d\r\n", measurements.hum_1); Serial.printf("[1] Relative Humidity: %d\r\n", measurements.hum_1);
Serial.printf("[1] Temperature compensated in C: %0.2f\r\n", Serial.printf("[1] Temperature compensated in C: %0.2f\r\n",
ag->pms5003t_1.temperatureCompensated(measurements.temp_1)); ag->pms5003t_1.compensateTemp(measurements.temp_1));
Serial.printf("[1] Relative Humidity compensated: %f\r\n", Serial.printf("[1] Relative Humidity compensated: %0.2f\r\n",
ag->pms5003t_1.humidityCompensated(measurements.hum_1)); ag->pms5003t_1.compensateHum(measurements.hum_1));
Serial.printf("[1] PM firmware version: %d\r\n", ag->pms5003t_1.getFirmwareVersion());
ag->pms5003t_1.resetFailCount();
} else { } else {
measurements.pm01_1 = -1; if (configuration.hasSensorPMS1) {
measurements.pm25_1 = -1; ag->pms5003t_1.updateFailCount();
measurements.pm10_1 = -1; Serial.printf("[1] PMS read failed %d times\r\n", ag->pms5003t_1.getFailCount());
measurements.pm03PCount_1 = -1;
measurements.temp_1 = -1001; if (ag->pms5003t_1.getFailCount() >= PMS_FAIL_COUNT_SET_INVALID) {
measurements.hum_1 = -1; measurements.pm01_1 = utils::getInvalidPmValue();
measurements.pm25_1 = utils::getInvalidPmValue();
measurements.pm10_1 = utils::getInvalidPmValue();
measurements.pm03PCount_1 = utils::getInvalidPmValue();
measurements.temp_1 = utils::getInvalidTemperature();
measurements.hum_1 = utils::getInvalidHumidity();
}
if (ag->pms5003t_1.getFailCount() >= ag->pms5003t_1.getFailCountMax()) {
restart = true;
}
}
} }
if (configuration.hasSensorPMS2 && (ag->pms5003t_2.isFailed() == false)) { if (configuration.hasSensorPMS2 && (ag->pms5003t_2.isFailed() == false)) {
@ -1013,16 +1101,30 @@ static void updatePm(void) {
Serial.printf("[2] Temperature in C: %0.2f\r\n", measurements.temp_2); Serial.printf("[2] Temperature in C: %0.2f\r\n", measurements.temp_2);
Serial.printf("[2] Relative Humidity: %d\r\n", measurements.hum_2); Serial.printf("[2] Relative Humidity: %d\r\n", measurements.hum_2);
Serial.printf("[2] Temperature compensated in C: %0.2f\r\n", Serial.printf("[2] Temperature compensated in C: %0.2f\r\n",
ag->pms5003t_1.temperatureCompensated(measurements.temp_2)); ag->pms5003t_1.compensateTemp(measurements.temp_2));
Serial.printf("[2] Relative Humidity compensated: %d\r\n", Serial.printf("[2] Relative Humidity compensated: %0.2f\r\n",
ag->pms5003t_1.humidityCompensated(measurements.hum_2)); ag->pms5003t_1.compensateHum(measurements.hum_2));
Serial.printf("[2] PM firmware version: %d\r\n", ag->pms5003t_2.getFirmwareVersion());
ag->pms5003t_2.resetFailCount();
} else { } else {
measurements.pm01_2 = -1; if (configuration.hasSensorPMS2) {
measurements.pm25_2 = -1; ag->pms5003t_2.updateFailCount();
measurements.pm10_2 = -1; Serial.printf("[2] PMS read failed %d times\r\n", ag->pms5003t_2.getFailCount());
measurements.pm03PCount_2 = -1;
measurements.temp_2 = -1001; if (ag->pms5003t_2.getFailCount() >= PMS_FAIL_COUNT_SET_INVALID) {
measurements.hum_2 = -1; measurements.pm01_2 = utils::getInvalidPmValue();
measurements.pm25_2 = utils::getInvalidPmValue();
measurements.pm10_2 = utils::getInvalidPmValue();
measurements.pm03PCount_2 = utils::getInvalidPmValue();
measurements.temp_2 = utils::getInvalidTemperature();
measurements.hum_2 = utils::getInvalidHumidity();
}
if (ag->pms5003t_2.getFailCount() >= ag->pms5003t_2.getFailCountMax()) {
restart = true;
}
}
} }
if (configuration.hasSensorPMS1 && configuration.hasSensorPMS2 && if (configuration.hasSensorPMS1 && configuration.hasSensorPMS2 &&
@ -1122,6 +1224,11 @@ static void updatePm(void) {
ag->sgp41.setCompensationTemperatureHumidity(temp, hum); ag->sgp41.setCompensationTemperatureHumidity(temp, hum);
} }
} }
if (restart) {
Serial.printf("PMS failure count reach to max set %d, restarting...", ag->pms5003.getFailCountMax());
ESP.restart();
}
} }
static void sendDataToServer(void) { static void sendDataToServer(void) {
@ -1162,6 +1269,8 @@ static void tempHumUpdate(void) {
measurements.Humidity); measurements.Humidity);
} }
} else { } else {
measurements.Temperature = utils::getInvalidTemperature();
measurements.Humidity = utils::getInvalidHumidity();
Serial.println("SHT read failed"); Serial.println("SHT read failed");
} }
} }

View File

@ -65,14 +65,14 @@ String OpenMetrics::getPayload(void) {
add_metric_point("", String(measure.CO2)); add_metric_point("", String(measure.CO2));
} }
float _temp = -1001; float _temp = utils::getInvalidTemperature();
float _hum = -1; float _hum = utils::getInvalidHumidity();
int pm01 = -1; int pm01 = utils::getInvalidPmValue();
int pm25 = -1; int pm25 = utils::getInvalidPmValue();
int pm10 = -1; int pm10 = utils::getInvalidPmValue();
int pm03PCount = -1; int pm03PCount = utils::getInvalidPmValue();
int atmpCompensated = -1; int atmpCompensated = utils::getInvalidTemperature();
int ahumCompensated = -1; int ahumCompensated = utils::getInvalidHumidity();
if (config.hasSensorPMS1 && config.hasSensorPMS2) { if (config.hasSensorPMS1 && config.hasSensorPMS2) {
_temp = (measure.temp_1 + measure.temp_2) / 2.0f; _temp = (measure.temp_1 + measure.temp_2) / 2.0f;
_hum = (measure.hum_1 + measure.hum_2) / 2.0f; _hum = (measure.hum_1 + measure.hum_2) / 2.0f;
@ -118,33 +118,33 @@ String OpenMetrics::getPayload(void) {
atmpCompensated = _temp; atmpCompensated = _temp;
ahumCompensated = _hum; ahumCompensated = _hum;
} else { } else {
atmpCompensated = ag->pms5003t_1.temperatureCompensated(_temp); atmpCompensated = ag->pms5003t_1.compensateTemp(_temp);
ahumCompensated = ag->pms5003t_1.humidityCompensated(_hum); ahumCompensated = ag->pms5003t_1.compensateHum(_hum);
} }
if (config.hasSensorPMS1 || config.hasSensorPMS2) { if (config.hasSensorPMS1 || config.hasSensorPMS2) {
if (pm01 >= 0) { if (utils::isValidPm(pm01)) {
add_metric("pm1", add_metric("pm1",
"PM1.0 concentration as measured by the AirGradient PMS " "PM1.0 concentration as measured by the AirGradient PMS "
"sensor, in micrograms per cubic meter", "sensor, in micrograms per cubic meter",
"gauge", "ugm3"); "gauge", "ugm3");
add_metric_point("", String(pm01)); add_metric_point("", String(pm01));
} }
if (pm25 >= 0) { if (utils::isValidPm(pm25)) {
add_metric("pm2d5", add_metric("pm2d5",
"PM2.5 concentration as measured by the AirGradient PMS " "PM2.5 concentration as measured by the AirGradient PMS "
"sensor, in micrograms per cubic meter", "sensor, in micrograms per cubic meter",
"gauge", "ugm3"); "gauge", "ugm3");
add_metric_point("", String(pm25)); add_metric_point("", String(pm25));
} }
if (pm10 >= 0) { if (utils::isValidPm(pm10)) {
add_metric("pm10", add_metric("pm10",
"PM10 concentration as measured by the AirGradient PMS " "PM10 concentration as measured by the AirGradient PMS "
"sensor, in micrograms per cubic meter", "sensor, in micrograms per cubic meter",
"gauge", "ugm3"); "gauge", "ugm3");
add_metric_point("", String(pm10)); add_metric_point("", String(pm10));
} }
if (pm03PCount >= 0) { if (utils::isValidPm03Count(pm03PCount)) {
add_metric("pm0d3", add_metric("pm0d3",
"PM0.3 concentration as measured by the AirGradient PMS " "PM0.3 concentration as measured by the AirGradient PMS "
"sensor, in number of particules per 100 milliliters", "sensor, in number of particules per 100 milliliters",
@ -154,28 +154,28 @@ String OpenMetrics::getPayload(void) {
} }
if (config.hasSensorSGP) { if (config.hasSensorSGP) {
if (measure.TVOC >= 0) { if (utils::isValidVOC(measure.TVOC)) {
add_metric("tvoc_index", add_metric("tvoc_index",
"The processed Total Volatile Organic Compounds (TVOC) index " "The processed Total Volatile Organic Compounds (TVOC) index "
"as measured by the AirGradient SGP sensor", "as measured by the AirGradient SGP sensor",
"gauge"); "gauge");
add_metric_point("", String(measure.TVOC)); add_metric_point("", String(measure.TVOC));
} }
if (measure.TVOCRaw >= 0) { if (utils::isValidVOC(measure.TVOCRaw)) {
add_metric("tvoc_raw", add_metric("tvoc_raw",
"The raw input value to the Total Volatile Organic Compounds " "The raw input value to the Total Volatile Organic Compounds "
"(TVOC) index as measured by the AirGradient SGP sensor", "(TVOC) index as measured by the AirGradient SGP sensor",
"gauge"); "gauge");
add_metric_point("", String(measure.TVOCRaw)); add_metric_point("", String(measure.TVOCRaw));
} }
if (measure.NOx >= 0) { if (utils::isValidNOx(measure.NOx)) {
add_metric("nox_index", add_metric("nox_index",
"The processed Nitrous Oxide (NOx) index as measured by the " "The processed Nitrous Oxide (NOx) index as measured by the "
"AirGradient SGP sensor", "AirGradient SGP sensor",
"gauge"); "gauge");
add_metric_point("", String(measure.NOx)); add_metric_point("", String(measure.NOx));
} }
if (measure.NOxRaw >= 0) { if (utils::isValidNOx(measure.NOxRaw)) {
add_metric("nox_raw", add_metric("nox_raw",
"The raw input value to the Nitrous Oxide (NOx) index as " "The raw input value to the Nitrous Oxide (NOx) index as "
"measured by the AirGradient SGP sensor", "measured by the AirGradient SGP sensor",
@ -184,14 +184,14 @@ String OpenMetrics::getPayload(void) {
} }
} }
if (_temp > -1001) { if (utils::isValidTemperature(_temp)) {
add_metric("temperature", add_metric("temperature",
"The ambient temperature as measured by the AirGradient SHT / PMS " "The ambient temperature as measured by the AirGradient SHT / PMS "
"sensor, in degrees Celsius", "sensor, in degrees Celsius",
"gauge", "celsius"); "gauge", "celsius");
add_metric_point("", String(_temp)); add_metric_point("", String(_temp));
} }
if (atmpCompensated > -1001) { if (utils::isValidTemperature(atmpCompensated)) {
add_metric( add_metric(
"temperature_compensated", "temperature_compensated",
"The compensated ambient temperature as measured by the AirGradient SHT / PMS " "The compensated ambient temperature as measured by the AirGradient SHT / PMS "
@ -199,14 +199,14 @@ String OpenMetrics::getPayload(void) {
"gauge", "celsius"); "gauge", "celsius");
add_metric_point("", String(atmpCompensated)); add_metric_point("", String(atmpCompensated));
} }
if (_hum >= 0) { if (utils::isValidHumidity(_hum)) {
add_metric( add_metric(
"humidity", "humidity",
"The relative humidity as measured by the AirGradient SHT sensor", "The relative humidity as measured by the AirGradient SHT sensor",
"gauge", "percent"); "gauge", "percent");
add_metric_point("", String(_hum)); add_metric_point("", String(_hum));
} }
if (ahumCompensated >= 0) { if (utils::isValidHumidity(ahumCompensated)) {
add_metric( add_metric(
"humidity_compensated", "humidity_compensated",
"The compensated relative humidity as measured by the AirGradient SHT / PMS sensor", "The compensated relative humidity as measured by the AirGradient SHT / PMS sensor",

View File

@ -18,6 +18,8 @@ enum OtaUpdateOutcome {
enum OtaState { enum OtaState {
OTA_STATE_BEGIN, OTA_STATE_BEGIN,
OTA_STATE_FAIL, OTA_STATE_FAIL,
OTA_STATE_SKIP,
OTA_STATE_UP_TO_DATE,
OTA_STATE_PROCESSING, OTA_STATE_PROCESSING,
OTA_STATE_SUCCESS OTA_STATE_SUCCESS
}; };
@ -40,13 +42,22 @@ public:
config.url = urlAsChar; config.url = urlAsChar;
OtaUpdateOutcome ret = attemptToPerformOta(&config); OtaUpdateOutcome ret = attemptToPerformOta(&config);
Serial.println(ret); Serial.println(ret);
if (ret == OtaUpdateOutcome::UPDATE_PERFORMED) { if (this->callback) {
if (this->callback) { switch (ret) {
case OtaUpdateOutcome::UPDATE_PERFORMED:
this->callback(OtaState::OTA_STATE_SUCCESS, ""); this->callback(OtaState::OTA_STATE_SUCCESS, "");
} break;
} else { case OtaUpdateOutcome::UDPATE_SKIPPED:
if(this->callback) { 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, ""); this->callback(OtaState::OTA_STATE_FAIL, "");
break;
default:
break;
} }
} }
} }
@ -127,6 +138,9 @@ private:
int data_read = int data_read =
esp_http_client_read(client, upgrade_data_buf, OTA_BUF_SIZE); esp_http_client_read(client, upgrade_data_buf, OTA_BUF_SIZE);
if (data_read == 0) { if (data_read == 0) {
if (this->callback) {
this->callback(OtaState::OTA_STATE_PROCESSING, String(100));
}
Serial.println("Connection closed, all data received"); Serial.println("Connection closed, all data received");
break; break;
} }

View File

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

View File

@ -1,7 +1,7 @@
# Name, Type, SubType, Offset, Size, Flags # Name ,Type ,SubType ,Offset ,Size ,Flags
nvs, data, nvs, 0x9000, 0x5000, nvs ,data ,nvs ,0x9000 ,0x5000 ,
otadata, data, ota, 0xe000, 0x2000, otadata ,data ,ota ,0xe000 ,0x2000 ,
app0, app, ota_0, 0x10000, 0x1E0000, app0 ,app ,ota_0 ,0x10000 ,0x1E0000 ,
app1, app, ota_1, 0x1F0000,0x1E0000, app1 ,app ,ota_1 ,0x1F0000 ,0x1E0000 ,
spiffs, data, spiffs, 0x3D0000,0x20000, spiffs ,data ,spiffs ,0x3D0000 ,0x20000 ,
coredump, data, coredump,0x3F0000,0x10000, coredump ,data ,coredump ,0x3F0000 ,0x10000 ,

1 # Name # Name Type Type SubType SubType Offset Offset Size Size Flags
2 nvs nvs data data nvs nvs 0x9000 0x9000 0x5000 0x5000
3 otadata otadata data data ota ota 0xe000 0xe000 0x2000 0x2000
4 app0 app0 app app ota_0 ota_0 0x10000 0x10000 0x1E0000 0x1E0000
5 app1 app1 app app ota_1 ota_1 0x1F0000 0x1F0000 0x1E0000 0x1E0000
6 spiffs spiffs data data spiffs spiffs 0x3D0000 0x3D0000 0x20000 0x20000
7 coredump coredump data data coredump coredump 0x3F0000 0x3F0000 0x10000 0x10000

View File

@ -26,7 +26,6 @@ lib_deps =
WiFiClientSecure WiFiClientSecure
Update Update
DNSServer DNSServer
monitor_filters = time
[env:esp8266] [env:esp8266]
platform = espressif8266 platform = espressif8266
@ -45,6 +44,8 @@ monitor_filters = time
[platformio] [platformio]
src_dir = examples/OneOpenAir src_dir = examples/OneOpenAir
; src_dir = examples/BASIC ; src_dir = examples/BASIC
; src_dir = examples/DiyProIndoorV4_2
; src_dir = examples/DiyProIndoorV3_3
; src_dir = examples/TestCO2 ; src_dir = examples/TestCO2
; src_dir = examples/TestPM ; src_dir = examples/TestPM
; src_dir = examples/TestSht ; src_dir = examples/TestSht

View File

@ -22,6 +22,7 @@ AgApiClient::~AgApiClient() {}
void AgApiClient::begin(void) { void AgApiClient::begin(void) {
getConfigFailed = false; getConfigFailed = false;
postToServerFailed = false; postToServerFailed = false;
logInfo("Init apiRoot: " + apiRoot);
logInfo("begin"); logInfo("begin");
} }
@ -44,9 +45,8 @@ bool AgApiClient::fetchServerConfiguration(void) {
return false; return false;
} }
String uri = String uri = apiRoot + "/sensors/airgradient:" +
"http://hw.airgradient.com/sensors/airgradient:" + ag->deviceId() + ag->deviceId() + "/one/config";
"/one/config";
/** Init http client */ /** Init http client */
#ifdef ESP8266 #ifdef ESP8266
@ -58,6 +58,7 @@ bool AgApiClient::fetchServerConfiguration(void) {
} }
#else #else
HTTPClient client; HTTPClient client;
client.setTimeout(timeoutMs);
if (client.begin(uri) == false) { if (client.begin(uri) == false) {
getConfigFailed = true; getConfigFailed = true;
return false; return false;
@ -66,14 +67,24 @@ bool AgApiClient::fetchServerConfiguration(void) {
/** Get data */ /** Get data */
int retCode = client.GET(); int retCode = client.GET();
logInfo(String("GET: ") + uri);
logInfo(String("Return code: ") + String(retCode));
if (retCode != 200) { if (retCode != 200) {
client.end(); client.end();
getConfigFailed = true; getConfigFailed = true;
/** Return code 400 mean device not setup on cloud. */
if (retCode == 400) {
notAvailableOnDashboard = true;
}
return false; return false;
} }
/** clear failed */ /** clear failed */
getConfigFailed = false; getConfigFailed = false;
notAvailableOnDashboard = false;
/** Get response string */ /** Get response string */
String respContent = client.getString(); String respContent = client.getString();
@ -103,21 +114,25 @@ bool AgApiClient::postToServer(String data) {
return false; return false;
} }
String uri = String uri = apiRoot + "/sensors/airgradient:" + ag->deviceId() + "/measures";
"http://hw.airgradient.com/sensors/airgradient:" + ag->deviceId() + // logInfo("Post uri: " + uri);
"/measures"; // logInfo("Post data: " + data);
logInfo("Post uri: " + uri);
logInfo("Post data: " + data);
WiFiClient wifiClient; WiFiClient wifiClient;
HTTPClient client; HTTPClient client;
client.setTimeout(timeoutMs);
if (client.begin(wifiClient, uri.c_str()) == false) { if (client.begin(wifiClient, uri.c_str()) == false) {
logError("Init client failed");
return false; return false;
} }
client.addHeader("content-type", "application/json"); client.addHeader("content-type", "application/json");
int retCode = client.POST(data); int retCode = client.POST(data);
client.end(); client.end();
logInfo(String("POST: ") + uri);
logInfo(String("DATA: ") + data);
logInfo(String("Return code: ") + String(retCode));
if ((retCode == 200) || (retCode == 429)) { if ((retCode == 200) || (retCode == 429)) {
postToServerFailed = false; postToServerFailed = false;
return true; return true;
@ -144,6 +159,17 @@ bool AgApiClient::isFetchConfigureFailed(void) { return getConfigFailed; }
*/ */
bool AgApiClient::isPostToServerFailed(void) { return postToServerFailed; } 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; } void AgApiClient::setAirGradient(AirGradient *ag) { this->ag = ag; }
/** /**
@ -160,3 +186,16 @@ bool AgApiClient::sendPing(int rssi, int bootCount) {
root["boot"] = bootCount; root["boot"] = bootCount;
return postToServer(JSON.stringify(root)); 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;
}

View File

@ -20,9 +20,12 @@ class AgApiClient : public PrintLog {
private: private:
Configuration &config; Configuration &config;
AirGradient *ag; AirGradient *ag;
String apiRoot = "http://hw.airgradient.com";
bool getConfigFailed; bool getConfigFailed;
bool postToServerFailed; bool postToServerFailed;
bool notAvailableOnDashboard = false; // Device not setup on Airgradient cloud dashboard.
uint16_t timeoutMs = 10000; // Default set to 10s
public: public:
AgApiClient(Stream &stream, Configuration &config); AgApiClient(Stream &stream, Configuration &config);
@ -33,8 +36,12 @@ public:
bool postToServer(String data); bool postToServer(String data);
bool isFetchConfigureFailed(void); bool isFetchConfigureFailed(void);
bool isPostToServerFailed(void); bool isPostToServerFailed(void);
bool isNotAvailableOnDashboard(void);
void setAirGradient(AirGradient *ag); void setAirGradient(AirGradient *ag);
bool sendPing(int rssi, int bootCount); bool sendPing(int rssi, int bootCount);
String getApiRoot() const;
void setApiRoot(const String &apiRoot);
void setTimeout(uint16_t timeoutMs);
}; };
#endif /** _AG_API_CLIENT_H_ */ #endif /** _AG_API_CLIENT_H_ */

View File

@ -43,7 +43,7 @@ JSON_PROP_DEF(ledBarTestRequested);
JSON_PROP_DEF(offlineMode); JSON_PROP_DEF(offlineMode);
#define jprop_model_default "" #define jprop_model_default ""
#define jprop_country_default "" #define jprop_country_default "TH"
#define jprop_pmStandard_default getPMStandardString(false) #define jprop_pmStandard_default getPMStandardString(false)
#define jprop_ledBarMode_default getLedBarModeName(LedBarMode::LedBarModeCO2) #define jprop_ledBarMode_default getLedBarModeName(LedBarMode::LedBarModeCO2)
#define jprop_abcDays_default 8 #define jprop_abcDays_default 8
@ -153,9 +153,15 @@ void Configuration::defaultConfig(void) {
jconfig[jprop_pmStandard] = jprop_pmStandard_default; jconfig[jprop_pmStandard] = jprop_pmStandard_default;
jconfig[jprop_temperatureUnit] = jprop_temperatureUnit_default; jconfig[jprop_temperatureUnit] = jprop_temperatureUnit_default;
jconfig[jprop_postDataToAirGradient] = jprop_postDataToAirGradient_default; jconfig[jprop_postDataToAirGradient] = jprop_postDataToAirGradient_default;
jconfig[jprop_ledBarBrightness] = jprop_ledBarBrightness_default; if (ag->isOne()) {
jconfig[jprop_displayBrightness] = jprop_displayBrightness_default; jconfig[jprop_ledBarBrightness] = jprop_ledBarBrightness_default;
jconfig[jprop_ledBarMode] = jprop_ledBarBrightness_default; }
if (ag->isOne() || ag->isPro3_3() || ag->isPro4_2() || ag->isBasic()) {
jconfig[jprop_displayBrightness] = jprop_displayBrightness_default;
}
if (ag->isOne()) {
jconfig[jprop_ledBarMode] = jprop_ledBarBrightness_default;
}
jconfig[jprop_tvocLearningOffset] = jprop_tvocLearningOffset_default; jconfig[jprop_tvocLearningOffset] = jprop_tvocLearningOffset_default;
jconfig[jprop_noxLearningOffset] = jprop_noxLearningOffset_default; jconfig[jprop_noxLearningOffset] = jprop_noxLearningOffset_default;
jconfig[jprop_abcDays] = jprop_abcDays_default; jconfig[jprop_abcDays] = jprop_abcDays_default;
@ -235,7 +241,10 @@ bool Configuration::parse(String data, bool isLocal) {
bool changed = false; bool changed = false;
/** Get ConfigurationControl */ /** Get ConfigurationControl */
String lasCtrl = jconfig[jprop_configurationControl]; String lastCtrl = jconfig[jprop_configurationControl];
const char *msg = "Monitor set to accept only configuration from the "
"cloud. Use property configurationControl to change.";
if (isLocal) { if (isLocal) {
if (JSON.typeof_(root[jprop_configurationControl]) == "string") { if (JSON.typeof_(root[jprop_configurationControl]) == "string") {
String ctrl = root[jprop_configurationControl]; String ctrl = root[jprop_configurationControl];
@ -248,9 +257,21 @@ bool Configuration::parse(String data, bool isLocal) {
ctrl == ctrl ==
String(CONFIGURATION_CONTROL_NAME String(CONFIGURATION_CONTROL_NAME
[ConfigurationControl::ConfigurationControlCloud])) { [ConfigurationControl::ConfigurationControlCloud])) {
if (ctrl != lasCtrl) { if (ctrl != lastCtrl) {
jconfig[jprop_configurationControl] = ctrl; jconfig[jprop_configurationControl] = ctrl;
changed = true; saveConfig();
configLogInfo(String(jprop_configurationControl), lastCtrl,
jconfig[jprop_configurationControl]);
}
/** Return failed if new "configurationControl" new and old is "cloud" */
if (ctrl == String(CONFIGURATION_CONTROL_NAME [ConfigurationControl::ConfigurationControlCloud])) {
if(ctrl != lastCtrl) {
return true;
} else {
failedMessage = String(msg);
return false;
}
} }
} else { } else {
failedMessage = failedMessage =
@ -267,18 +288,11 @@ bool Configuration::parse(String data, bool isLocal) {
} }
} }
if (changed) { /** Ignore all configuration value if 'configurationControl' is 'cloud' */
changed = false;
saveConfig();
configLogInfo(String(jprop_configurationControl), lasCtrl,
jconfig[jprop_configurationControl]);
}
if (jconfig[jprop_configurationControl] == if (jconfig[jprop_configurationControl] ==
String(CONFIGURATION_CONTROL_NAME String(CONFIGURATION_CONTROL_NAME
[ConfigurationControl::ConfigurationControlCloud])) { [ConfigurationControl::ConfigurationControlCloud])) {
failedMessage = "Monitor set to accept only configuration from the " failedMessage = String(msg);
"cloud. Use property configurationControl to change.";
jsonInvalid(); jsonInvalid();
return false; return false;
} }
@ -372,6 +386,7 @@ bool Configuration::parse(String data, bool isLocal) {
} }
} }
_ledBarModeChanged = false;
if (JSON.typeof_(root[jprop_ledBarMode]) == "string") { if (JSON.typeof_(root[jprop_ledBarMode]) == "string") {
String mode = root[jprop_ledBarMode]; String mode = root[jprop_ledBarMode];
if (mode == getLedBarModeName(LedBarMode::LedBarModeCO2) || if (mode == getLedBarModeName(LedBarMode::LedBarModeCO2) ||
@ -380,6 +395,7 @@ bool Configuration::parse(String data, bool isLocal) {
String oldMode = jconfig[jprop_ledBarMode]; String oldMode = jconfig[jprop_ledBarMode];
if (mode != oldMode) { if (mode != oldMode) {
jconfig[jprop_ledBarMode] = mode; jconfig[jprop_ledBarMode] = mode;
_ledBarModeChanged = true;
changed = true; changed = true;
} }
} else { } else {
@ -559,6 +575,7 @@ bool Configuration::parse(String data, bool isLocal) {
} }
} }
ledBarBrightnessChanged = false;
if (JSON.typeof_(root[jprop_ledBarBrightness]) == "number") { if (JSON.typeof_(root[jprop_ledBarBrightness]) == "number") {
int value = root[jprop_ledBarBrightness]; int value = root[jprop_ledBarBrightness];
int oldValue = jconfig[jprop_ledBarBrightness]; int oldValue = jconfig[jprop_ledBarBrightness];
@ -611,15 +628,18 @@ bool Configuration::parse(String data, bool isLocal) {
} }
} }
if (JSON.typeof_(root["targetFirmware"]) == "string") { if (ag->getBoardType() == ONE_INDOOR ||
String newVer = root["targetFirmware"]; ag->getBoardType() == OPEN_AIR_OUTDOOR) {
String curVer = String(GIT_VERSION); if (JSON.typeof_(root["targetFirmware"]) == "string") {
if (curVer != newVer) { String newVer = root["targetFirmware"];
logInfo("Detected new firmware version: " + newVer); String curVer = String(GIT_VERSION);
otaNewFirmwareVersion = newVer; if (curVer != newVer) {
udpated = true; logInfo("Detected new firmware version: " + newVer);
} else { otaNewFirmwareVersion = newVer;
otaNewFirmwareVersion = String(""); udpated = true;
} else {
otaNewFirmwareVersion = String("");
}
} }
} }
@ -628,7 +648,6 @@ bool Configuration::parse(String data, bool isLocal) {
saveConfig(); saveConfig();
printConfig(); printConfig();
} else { } else {
logInfo("Update ignored due to local unofficial changes");
if (ledBarTestRequested || co2CalibrationRequested) { if (ledBarTestRequested || co2CalibrationRequested) {
udpated = true; udpated = true;
} }
@ -1148,6 +1167,12 @@ void Configuration::setOfflineModeWithoutSave(bool offline) {
_offlineMode = offline; _offlineMode = offline;
} }
bool Configuration::isLedBarModeChanged(void) {
bool changed = _ledBarModeChanged;
_ledBarModeChanged = false;
return changed;
}
bool Configuration::isDisplayBrightnessChanged(void) { bool Configuration::isDisplayBrightnessChanged(void) {
bool changed = displayBrightnessChanged; bool changed = displayBrightnessChanged;
displayBrightnessChanged = false; displayBrightnessChanged = false;

View File

@ -18,6 +18,7 @@ private:
bool displayBrightnessChanged = false; bool displayBrightnessChanged = false;
String otaNewFirmwareVersion; String otaNewFirmwareVersion;
bool _offlineMode = false; bool _offlineMode = false;
bool _ledBarModeChanged = false;
AirGradient* ag; AirGradient* ag;
@ -80,6 +81,7 @@ public:
bool isOfflineMode(void); bool isOfflineMode(void);
void setOfflineMode(bool offline); void setOfflineMode(bool offline);
void setOfflineModeWithoutSave(bool offline); void setOfflineModeWithoutSave(bool offline);
bool isLedBarModeChanged(void);
}; };
#endif /** _AG_CONFIG_H_ */ #endif /** _AG_CONFIG_H_ */

View File

@ -1,17 +1,18 @@
#include "AgOledDisplay.h" #include "AgOledDisplay.h"
#include "Libraries/U8g2/src/U8g2lib.h" #include "Libraries/U8g2/src/U8g2lib.h"
#include "Main/utils.h"
/** Cast U8G2 */ /** Cast U8G2 */
#define DISP() ((U8G2_SH1106_128X64_NONAME_F_HW_I2C *)(this->u8g2)) #define DISP() ((U8G2_SH1106_128X64_NONAME_F_HW_I2C *)(this->u8g2))
/** /**
* @brief Show dashboard temperature and humdity * @brief Show dashboard temperature and humdity
* *
* @param hasStatus * @param hasStatus
*/ */
void OledDisplay::showTempHum(bool hasStatus) { void OledDisplay::showTempHum(bool hasStatus) {
char buf[10]; char buf[16];
if (value.Temperature > -1001) { if (utils::isValidTemperature(value.Temperature)) {
if (config.isTemperatureUnitInF()) { if (config.isTemperatureUnitInF()) {
float tempF = (value.Temperature * 9) / 5 + 32; float tempF = (value.Temperature * 9) / 5 + 32;
if (hasStatus) { if (hasStatus) {
@ -36,10 +37,10 @@ void OledDisplay::showTempHum(bool hasStatus) {
DISP()->drawUTF8(1, 10, buf); DISP()->drawUTF8(1, 10, buf);
/** Show humidty */ /** Show humidty */
if (value.Humidity >= 0) { if (utils::isValidHumidity(value.Humidity)) {
snprintf(buf, sizeof(buf), "%d%%", value.Humidity); snprintf(buf, sizeof(buf), "%d%%", value.Humidity);
} else { } else {
snprintf(buf, sizeof(buf), "%-%%"); snprintf(buf, sizeof(buf), "-%%");
} }
if (value.Humidity > 99) { if (value.Humidity > 99) {
@ -58,20 +59,20 @@ void OledDisplay::setCentralText(int y, const char *text) {
DISP()->drawStr(x, y, text); DISP()->drawStr(x, y, text);
} }
/** /**
* @brief Construct a new Ag Oled Display:: Ag Oled Display object * @brief Construct a new Ag Oled Display:: Ag Oled Display object
* *
* @param config AgConfiguration * @param config AgConfiguration
* @param value Measurements * @param value Measurements
* @param log Serial Stream * @param log Serial Stream
*/ */
OledDisplay::OledDisplay(Configuration &config, Measurements &value, Stream &log) OledDisplay::OledDisplay(Configuration &config, Measurements &value,
Stream &log)
: PrintLog(log, "OledDisplay"), config(config), value(value) {} : PrintLog(log, "OledDisplay"), config(config), value(value) {}
/** /**
* @brief Set AirGradient instance * @brief Set AirGradient instance
* *
* @param ag Point to AirGradient instance * @param ag Point to AirGradient instance
*/ */
void OledDisplay::setAirGradient(AirGradient *ag) { this->ag = ag; } void OledDisplay::setAirGradient(AirGradient *ag) { this->ag = ag; }
@ -90,23 +91,31 @@ bool OledDisplay::begin(void) {
return true; return true;
} }
/** Create u8g2 instance */ if (ag->isOne() || ag->isPro3_3() || ag->isPro4_2()) {
u8g2 = new U8G2_SH1106_128X64_NONAME_F_HW_I2C(U8G2_R0, U8X8_PIN_NONE); /** Create u8g2 instance */
if (u8g2 == NULL) { u8g2 = new U8G2_SH1106_128X64_NONAME_F_HW_I2C(U8G2_R0, U8X8_PIN_NONE);
logError("Create 'U8G2' failed"); if (u8g2 == NULL) {
return false; logError("Create 'U8G2' failed");
} return false;
}
/** Init u8g2 */ /** Init u8g2 */
if (DISP()->begin() == false) { if (DISP()->begin() == false) {
logError("U8G2 'begin' failed"); logError("U8G2 'begin' failed");
return false; 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 /** Show low brightness on startup. then it's completely turn off on main
* application */ * application */
int brightness = config.getDisplayBrightness(); int brightness = config.getDisplayBrightness();
if(brightness == 0) { if (brightness == 0) {
setBrightness(1); setBrightness(1);
} }
@ -125,9 +134,13 @@ void OledDisplay::end(void) {
return; return;
} }
/** Free u8g2 */ if (ag->isOne() || ag->isPro3_3() || ag->isPro4_2()) {
delete DISP(); /** Free u8g2 */
u8g2 = NULL; delete DISP();
u8g2 = NULL;
} else if (ag->isBasic()) {
ag->display.end();
}
isBegin = false; isBegin = false;
logInfo("end"); logInfo("end");
@ -135,10 +148,10 @@ void OledDisplay::end(void) {
/** /**
* @brief Show text on 3 line of display * @brief Show text on 3 line of display
* *
* @param line1 * @param line1
* @param line2 * @param line2
* @param line3 * @param line3
*/ */
void OledDisplay::setText(String &line1, String &line2, String &line3) { void OledDisplay::setText(String &line1, String &line2, String &line3) {
setText(line1.c_str(), line2.c_str(), line3.c_str()); setText(line1.c_str(), line2.c_str(), line3.c_str());
@ -146,191 +159,277 @@ void OledDisplay::setText(String &line1, String &line2, String &line3) {
/** /**
* @brief Show text on 3 line of display * @brief Show text on 3 line of display
* *
* @param line1 * @param line1
* @param line2 * @param line2
* @param line3 * @param line3
*/ */
void OledDisplay::setText(const char *line1, const char *line2, void OledDisplay::setText(const char *line1, const char *line2,
const char *line3) { const char *line3) {
if (isDisplayOff) { if (isDisplayOff) {
return; return;
} }
DISP()->firstPage(); if (ag->isOne() || ag->isPro3_3() || ag->isPro4_2()) {
do { DISP()->firstPage();
DISP()->setFont(u8g2_font_t0_16_tf); do {
DISP()->drawStr(1, 10, line1); DISP()->setFont(u8g2_font_t0_16_tf);
DISP()->drawStr(1, 30, line2); DISP()->drawStr(1, 10, line1);
DISP()->drawStr(1, 50, line3); DISP()->drawStr(1, 30, line2);
} while (DISP()->nextPage()); 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 * @brief Set Text on 4 line
* *
* @param line1 * @param line1
* @param line2 * @param line2
* @param line3 * @param line3
* @param line4 * @param line4
*/ */
void OledDisplay::setText(String &line1, String &line2, String &line3, void OledDisplay::setText(String &line1, String &line2, String &line3,
String &line4) { String &line4) {
setText(line1.c_str(), line2.c_str(), line3.c_str(), line4.c_str()); setText(line1.c_str(), line2.c_str(), line3.c_str(), line4.c_str());
} }
/** /**
* @brief Set Text on 4 line * @brief Set Text on 4 line
* *
* @param line1 * @param line1
* @param line2 * @param line2
* @param line3 * @param line3
* @param line4 * @param line4
*/ */
void OledDisplay::setText(const char *line1, const char *line2, void OledDisplay::setText(const char *line1, const char *line2,
const char *line3, const char *line4) { const char *line3, const char *line4) {
if (isDisplayOff) { if (isDisplayOff) {
return; return;
} }
DISP()->firstPage(); if (ag->isOne() || ag->isPro3_3() || ag->isPro4_2()) {
do { DISP()->firstPage();
DISP()->setFont(u8g2_font_t0_16_tf); do {
DISP()->drawStr(1, 10, line1); DISP()->setFont(u8g2_font_t0_16_tf);
DISP()->drawStr(1, 25, line2); DISP()->drawStr(1, 10, line1);
DISP()->drawStr(1, 40, line3); DISP()->drawStr(1, 25, line2);
DISP()->drawStr(1, 55, line4); DISP()->drawStr(1, 40, line3);
} while (DISP()->nextPage()); 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 * @brief Update dashboard content
* *
*/ */
void OledDisplay::showDashboard(void) { showDashboard(NULL); } void OledDisplay::showDashboard(void) { showDashboard(NULL); }
/** /**
* @brief Update dashboard content and error status * @brief Update dashboard content and error status
* *
*/ */
void OledDisplay::showDashboard(const char *status) { void OledDisplay::showDashboard(const char *status) {
if (isDisplayOff) { if (isDisplayOff) {
return; return;
} }
char strBuf[10]; 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);
} 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);
}
}
/** 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);
if (value.CO2 > 0) {
int val = 9999;
if (value.CO2 < 10000) {
val = value.CO2;
}
sprintf(strBuf, "%d", val);
} 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(45, 14, 45, 64);
DISP()->drawLine(82, 14, 82, 64);
/** Draw PM2.5 label */
DISP()->setFont(u8g2_font_t0_12_tf);
DISP()->drawStr(48, 27, "PM2.5");
/** Draw PM2.5 value */
DISP()->setFont(u8g2_font_t0_22b_tf);
if (config.isPmStandardInUSAQI()) {
if (value.pm25_1 >= 0) {
sprintf(strBuf, "%d", ag->pms5003.convertPm25ToUsAqi(value.pm25_1));
} else {
sprintf(strBuf, "%s", "-");
}
DISP()->drawStr(48, 48, strBuf);
DISP()->setFont(u8g2_font_t0_12_tf);
DISP()->drawUTF8(48, 61, "AQI");
} else {
if (value.pm25_1 >= 0) {
sprintf(strBuf, "%d", value.pm25_1);
} else {
sprintf(strBuf, "%s", "-");
}
DISP()->drawStr(48, 48, strBuf);
DISP()->setFont(u8g2_font_t0_12_tf);
DISP()->drawUTF8(48, 61, "ug/m³");
}
/** Draw tvocIndexlabel */
DISP()->setFont(u8g2_font_t0_12_tf);
DISP()->drawStr(85, 27, "tvoc:");
/** Draw tvocIndexvalue */
if (value.TVOC >= 0) {
sprintf(strBuf, "%d", value.TVOC);
} else {
sprintf(strBuf, "%s", "-");
}
DISP()->drawStr(85, 39, strBuf);
/** Draw NOx label */
DISP()->drawStr(85, 53, "NOx:");
if (value.NOx >= 0) {
sprintf(strBuf, "%d", value.NOx);
} else {
sprintf(strBuf, "%s", "-");
}
DISP()->drawStr(85, 63, strBuf);
} while (DISP()->nextPage());
}
void OledDisplay::setBrightness(int percent) {
if (percent == 0) {
isDisplayOff = true;
// Clear display.
DISP()->firstPage(); DISP()->firstPage();
do { do {
} while (DISP()->nextPage()); DISP()->setFont(u8g2_font_t0_16_tf);
if ((status == NULL) || (strlen(status) == 0)) {
showTempHum(false);
} else {
String strStatus = "Show status: " + String(status);
logInfo(strStatus);
} else { int strWidth = DISP()->getStrWidth(status);
isDisplayOff = false; DISP()->drawStr((DISP()->getWidth() - strWidth) / 2, 10, status);
DISP()->setContrast((127 * percent) / 100);
/** Show WiFi NA*/
if (strcmp(status, "WiFi N/A") == 0) {
DISP()->setFont(u8g2_font_t0_12_tf);
showTempHum(true);
}
}
/** 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);
if (utils::isValidCO2(value.CO2)) {
sprintf(strBuf, "%d", value.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 = value.pm25_1;
if (config.hasSensorSHT) {
pm25 = ag->pms5003.compensate(pm25, value.Humidity);
logInfo("PM2.5:" + String(value.pm25_1) + String("Compensated:") + String(pm25));
}
DISP()->setFont(u8g2_font_t0_22b_tf);
if (config.isPmStandardInUSAQI()) {
if (utils::isValidPm(pm25)) {
sprintf(strBuf, "%d", ag->pms5003.convertPm25ToUsAqi(pm25));
} else {
sprintf(strBuf, "%s", "-");
}
DISP()->drawStr(55, 48, strBuf);
DISP()->setFont(u8g2_font_t0_12_tf);
DISP()->drawUTF8(55, 61, "AQI");
} else {
if (utils::isValidPm(pm25)) {
sprintf(strBuf, "%d", pm25);
} else {
sprintf(strBuf, "%s", "-");
}
DISP()->drawStr(55, 48, strBuf);
DISP()->setFont(u8g2_font_t0_12_tf);
DISP()->drawUTF8(55, 61, "ug/m³");
}
/** Draw tvocIndexlabel */
DISP()->setFont(u8g2_font_t0_12_tf);
DISP()->drawStr(100, 27, "VOC:");
/** Draw tvocIndexvalue */
if (utils::isValidVOC(value.TVOC)) {
sprintf(strBuf, "%d", value.TVOC);
} else {
sprintf(strBuf, "%s", "-");
}
DISP()->drawStr(100, 39, strBuf);
/** Draw NOx label */
DISP()->drawStr(100, 53, "NOx:");
if (utils::isValidNOx(value.NOx)) {
sprintf(strBuf, "%d", value.NOx);
} else {
sprintf(strBuf, "%s", "-");
}
DISP()->drawStr(100, 63, strBuf);
} while (DISP()->nextPage());
} else if (ag->isBasic()) {
ag->display.clear();
/** Set CO2 */
if(utils::isValidCO2(value.CO2)) {
snprintf(strBuf, sizeof(strBuf), "CO2:%d", value.CO2);
} else {
snprintf(strBuf, sizeof(strBuf), "CO2:-");
}
ag->display.setCursor(0, 0);
ag->display.setText(strBuf);
/** Set PM */
int pm25 = value.pm25_1;
if(config.hasSensorSHT) {
pm25 = (int)ag->pms5003.compensate(pm25, value.Humidity);
}
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 */
if (utils::isValidTemperature(value.Temperature)) {
if (config.isTemperatureUnitInF()) {
float tempF = (value.Temperature * 9) / 5 + 32;
snprintf(strBuf, sizeof(strBuf), "T:%0.1f F", tempF);
} else {
snprintf(strBuf, sizeof(strBuf), "T:%0.f1 C", value.Temperature);
}
} 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);
if (utils::isValidHumidity(value.Humidity)) {
snprintf(strBuf, sizeof(strBuf), "H:%d %%", (int)value.Humidity);
} else {
snprintf(strBuf, sizeof(strBuf), "H:- %%");
}
ag->display.setCursor(0, 36);
ag->display.setText(strBuf);
ag->display.show();
} }
} }
void OledDisplay::showNewFirmwareVersion(String version) { 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()) {
ag->display.setContrast((255 * percent) / 100);
}
}
#ifdef ESP32
void OledDisplay::showFirmwareUpdateVersion(String version) {
if (isDisplayOff) { if (isDisplayOff) {
return; return;
} }
@ -344,7 +443,7 @@ void OledDisplay::showNewFirmwareVersion(String version) {
} while (DISP()->nextPage()); } while (DISP()->nextPage());
} }
void OledDisplay::showNewFirmwareUpdating(String percent) { void OledDisplay::showFirmwareUpdateProgress(int percent) {
if (isDisplayOff) { if (isDisplayOff) {
return; return;
} }
@ -353,11 +452,11 @@ void OledDisplay::showNewFirmwareUpdating(String percent) {
do { do {
DISP()->setFont(u8g2_font_t0_16_tf); DISP()->setFont(u8g2_font_t0_16_tf);
setCentralText(20, "Firmware Update"); setCentralText(20, "Firmware Update");
setCentralText(50, String("Updating... ") + percent + String("%")); setCentralText(50, String("Updating... ") + String(percent) + String("%"));
} while (DISP()->nextPage()); } while (DISP()->nextPage());
} }
void OledDisplay::showNewFirmwareSuccess(String count) { void OledDisplay::showFirmwareUpdateSuccess(int count) {
if (isDisplayOff) { if (isDisplayOff) {
return; return;
} }
@ -367,11 +466,11 @@ void OledDisplay::showNewFirmwareSuccess(String count) {
DISP()->setFont(u8g2_font_t0_16_tf); DISP()->setFont(u8g2_font_t0_16_tf);
setCentralText(20, "Firmware Update"); setCentralText(20, "Firmware Update");
setCentralText(40, "Success"); setCentralText(40, "Success");
setCentralText(60, String("Rebooting... ") + count); setCentralText(60, String("Rebooting... ") + String(count));
} while (DISP()->nextPage()); } while (DISP()->nextPage());
} }
void OledDisplay::showNewFirmwareFailed(void) { void OledDisplay::showFirmwareUpdateFailed(void) {
if (isDisplayOff) { if (isDisplayOff) {
return; return;
} }
@ -380,7 +479,55 @@ void OledDisplay::showNewFirmwareFailed(void) {
do { do {
DISP()->setFont(u8g2_font_t0_16_tf); DISP()->setFont(u8g2_font_t0_16_tf);
setCentralText(20, "Firmware Update"); setCentralText(20, "Firmware Update");
setCentralText(40, "Failed"); setCentralText(40, "fail, will retry");
setCentralText(60, String("Retry after 24h")); // setCentralText(60, "will retry");
} while (DISP()->nextPage()); } 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();
}
}

View File

@ -36,10 +36,17 @@ public:
void showDashboard(void); void showDashboard(void);
void showDashboard(const char *status); void showDashboard(const char *status);
void setBrightness(int percent); void setBrightness(int percent);
void showNewFirmwareVersion(String version); #ifdef ESP32
void showNewFirmwareUpdating(String percent); void showFirmwareUpdateVersion(String version);
void showNewFirmwareSuccess(String count); void showFirmwareUpdateProgress(int percent);
void showNewFirmwareFailed(void); void showFirmwareUpdateSuccess(int count);
void showFirmwareUpdateFailed(void);
void showFirmwareUpdateSkipped(void);
void showFirmwareUpdateUpToDate(void);
#else
#endif
void showRebooting(void);
}; };
#endif /** _AG_OLED_DISPLAY_H_ */ #endif /** _AG_OLED_DISPLAY_H_ */

View File

@ -1,5 +1,6 @@
#include "AgStateMachine.h" #include "AgStateMachine.h"
#define LED_TEST_BLINK_DELAY 50 /** ms */
#define LED_FAST_BLINK_DELAY 250 /** ms */ #define LED_FAST_BLINK_DELAY 250 /** ms */
#define LED_SLOW_BLINK_DELAY 1000 /** ms */ #define LED_SLOW_BLINK_DELAY 1000 /** ms */
#define LED_SHORT_BLINK_DELAY 500 /** ms */ #define LED_SHORT_BLINK_DELAY 500 /** ms */
@ -7,11 +8,11 @@
#define SENSOR_CO2_CALIB_COUNTDOWN_MAX 5 /** sec */ #define SENSOR_CO2_CALIB_COUNTDOWN_MAX 5 /** sec */
#define RGB_COLOR_R 255, 0, 0 /** Red */ #define RGB_COLOR_R 255, 0, 0 /** Red */
#define RGB_COLOR_G 0, 255, 0 /** Green */ #define RGB_COLOR_G 0, 255, 0 /** Green */
#define RGB_COLOR_Y 255, 255, 0 /** Yellow */ #define RGB_COLOR_Y 255, 150, 0 /** Yellow */
#define RGB_COLOR_O 255, 165, 0 /** Organge */ #define RGB_COLOR_O 255, 40, 0 /** Orange */
#define RGB_COLOR_P 160, 32, 240 /** Purple */ #define RGB_COLOR_P 180, 0, 255 /** Purple */
/** /**
* @brief Animation LED bar with color * @brief Animation LED bar with color
@ -140,7 +141,7 @@ void StateMachine::co2handleLeds(void) {
* *
*/ */
void StateMachine::pm25handleLeds(void) { void StateMachine::pm25handleLeds(void) {
int pm25Value = value.pm25_1; int pm25Value = ag->pms5003.compensate(value.pm25_1, value.Humidity);
if (pm25Value < 5) { if (pm25Value < 5) {
/** G; 1 */ /** G; 1 */
ag->ledBar.setColor(RGB_COLOR_G, ag->ledBar.getNumberOfLeds() - 1); ag->ledBar.setColor(RGB_COLOR_G, ag->ledBar.getNumberOfLeds() - 1);
@ -224,10 +225,13 @@ void StateMachine::co2Calibration(void) {
/** Count down to 0 then start */ /** Count down to 0 then start */
for (int i = 0; i < SENSOR_CO2_CALIB_COUNTDOWN_MAX; i++) { for (int i = 0; i < SENSOR_CO2_CALIB_COUNTDOWN_MAX; i++) {
if (ag->isOne()) { if (ag->isOne() || (ag->isPro4_2()) || ag->isPro3_3()) {
String str = String str =
"after " + String(SENSOR_CO2_CALIB_COUNTDOWN_MAX - i) + " sec"; "after " + String(SENSOR_CO2_CALIB_COUNTDOWN_MAX - i) + " sec";
disp.setText("Start CO2 calib", str.c_str(), ""); 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 { } else {
logInfo("Start CO2 calib after " + logInfo("Start CO2 calib after " +
String(SENSOR_CO2_CALIB_COUNTDOWN_MAX - i) + " sec"); String(SENSOR_CO2_CALIB_COUNTDOWN_MAX - i) + " sec");
@ -236,14 +240,16 @@ void StateMachine::co2Calibration(void) {
} }
if (ag->s8.setBaselineCalibration()) { if (ag->s8.setBaselineCalibration()) {
if (ag->isOne()) { if (ag->isOne() || (ag->isPro4_2()) || ag->isPro3_3()) {
disp.setText("Calibration", "success", ""); disp.setText("Calibration", "success", "");
} else if (ag->isBasic()) {
disp.setText("CO2 Calib", "success", "");
} else { } else {
logInfo("CO2 Calibration: success"); logInfo("CO2 Calibration: success");
} }
delay(1000); delay(1000);
if (ag->isOne()) { if (ag->isOne() || (ag->isPro4_2()) || ag->isPro3_3() || ag->isBasic()) {
disp.setText("Wait for", "calib finish", "..."); disp.setText("Wait for", "calib done", "...");
} else { } else {
logInfo("CO2 Calibration: Wait for calibration finish..."); logInfo("CO2 Calibration: Wait for calibration finish...");
} }
@ -254,16 +260,18 @@ void StateMachine::co2Calibration(void) {
delay(1000); delay(1000);
count++; count++;
} }
if (ag->isOne()) { if (ag->isOne() || (ag->isPro4_2()) || ag->isPro3_3() || ag->isBasic()) {
String str = "after " + String(count); String str = "after " + String(count);
disp.setText("Calib finish", str.c_str(), "sec"); disp.setText("Calib done", str.c_str(), "sec");
} else { } else {
logInfo("CO2 Calibration: finish after " + String(count) + " sec"); logInfo("CO2 Calibration: finish after " + String(count) + " sec");
} }
delay(2000); delay(2000);
} else { } else {
if (ag->isOne()) { if (ag->isOne() || (ag->isPro4_2()) || ag->isPro3_3()) {
disp.setText("Calibration", "failure!!!", ""); disp.setText("Calibration", "failure!!!", "");
} else if (ag->isBasic()) {
disp.setText("CO2 calib", "failure!!!", "");
} else { } else {
logInfo("CO2 Calibration: failure!!!"); logInfo("CO2 Calibration: failure!!!");
} }
@ -279,15 +287,16 @@ void StateMachine::co2Calibration(void) {
if (ag->s8.setAbcPeriod(config.getCO2CalibrationAbcDays() * 24)) { if (ag->s8.setAbcPeriod(config.getCO2CalibrationAbcDays() * 24)) {
resultStr = "successful"; resultStr = "successful";
} }
String fromStr = String(curHour/24) + " days"; String fromStr = String(curHour / 24) + " days";
if(curHour == 0){ if (curHour == 0) {
fromStr = "off"; fromStr = "off";
} }
String toStr = String(config.getCO2CalibrationAbcDays()) + " days"; String toStr = String(config.getCO2CalibrationAbcDays()) + " days";
if(config.getCO2CalibrationAbcDays() == 0) { if (config.getCO2CalibrationAbcDays() == 0) {
toStr = "off"; toStr = "off";
} }
String msg = "Setting S8 from " + fromStr + " to " + toStr + " " + resultStr; String msg =
"Setting S8 from " + fromStr + " to " + toStr + " " + resultStr;
logInfo(msg); logInfo(msg);
} }
} else { } else {
@ -297,44 +306,56 @@ void StateMachine::co2Calibration(void) {
void StateMachine::ledBarTest(void) { void StateMachine::ledBarTest(void) {
if (config.isLedBarTestRequested()) { if (config.isLedBarTestRequested()) {
if (config.getCountry() == "TH") { if (ag->isOne()) {
uint32_t tstart = millis(); if (config.getCountry() == "TH") {
logInfo("Start run LED test for 2 min"); uint32_t tstart = millis();
while (1) { logInfo("Start run LED test for 2 min");
ledBarRunTest(); while (1) {
uint32_t ms = (uint32_t)(millis() - tstart); ledBarRunTest();
if (ms >= (60 * 1000 * 2)) { uint32_t ms = (uint32_t)(millis() - tstart);
logInfo("LED test after 2 min finish"); if (ms >= (60 * 1000 * 2)) {
break; logInfo("LED test after 2 min finish");
break;
}
} }
} else {
ledBarRunTest();
} }
} else { }
else if(ag->isOpenAir()) {
ledBarRunTest(); ledBarRunTest();
} }
} }
} }
void StateMachine::ledBarPowerUpTest(void) { void StateMachine::ledBarPowerUpTest(void) { ledBarRunTest(); }
ledBarRunTest();
}
void StateMachine::ledBarRunTest(void) { void StateMachine::ledBarRunTest(void) {
disp.setText("LED Test", "running", "....."); if (ag->isOne()) {
runLedTest('r'); disp.setText("LED Test", "running", ".....");
ag->ledBar.show(); runLedTest('r');
delay(1000); ag->ledBar.show();
runLedTest('g'); delay(1000);
ag->ledBar.show(); runLedTest('g');
delay(1000); ag->ledBar.show();
runLedTest('b'); delay(1000);
ag->ledBar.show(); runLedTest('b');
delay(1000); ag->ledBar.show();
runLedTest('w'); delay(1000);
ag->ledBar.show(); runLedTest('w');
delay(1000); ag->ledBar.show();
runLedTest('n'); delay(1000);
ag->ledBar.show(); runLedTest('n');
delay(1000); 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) { void StateMachine::runLedTest(char color) {
@ -398,8 +419,8 @@ StateMachine::~StateMachine() {}
* @param state * @param state
*/ */
void StateMachine::displayHandle(AgStateMachineState state) { void StateMachine::displayHandle(AgStateMachineState state) {
// Ignore handle if not ONE_INDOOR board // Ignore handle if not support display
if (!ag->isOne()) { if (!(ag->isOne() || (ag->isPro4_2()) || ag->isPro3_3() || ag->isBasic())) {
if (state == AgStateMachineCo2Calibration) { if (state == AgStateMachineCo2Calibration) {
co2Calibration(); co2Calibration();
} }
@ -417,11 +438,17 @@ void StateMachine::displayHandle(AgStateMachineState state) {
case AgStateMachineWiFiManagerMode: case AgStateMachineWiFiManagerMode:
case AgStateMachineWiFiManagerPortalActive: { case AgStateMachineWiFiManagerPortalActive: {
if (wifiConnectCountDown >= 0) { if (wifiConnectCountDown >= 0) {
String line1 = String(wifiConnectCountDown) + "s to connect"; if (ag->isBasic()) {
String line2 = "to WiFi hotspot:"; String ssid = "\"airgradient-" + ag->deviceId() + "\" " +
String line3 = "\"airgradient-"; String(wifiConnectCountDown) + String("s");
String line4 = ag->deviceId() + "\""; disp.setText("Connect tohotspot:", ssid.c_str(), "");
disp.setText(line1, line2, line3, line4); } 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--; wifiConnectCountDown--;
} }
break; break;
@ -435,7 +462,12 @@ void StateMachine::displayHandle(AgStateMachineState state) {
break; break;
} }
case AgStateMachineWiFiOkServerConnecting: { case AgStateMachineWiFiOkServerConnecting: {
disp.setText("Connecting to", "Server", "..."); if (ag->isBasic()) {
disp.setText("Connecting", "to", "Server...");
} else {
disp.setText("Connecting to", "Server", "...");
}
break; break;
} }
case AgStateMachineWiFiOkServerConnected: { case AgStateMachineWiFiOkServerConnected: {
@ -451,7 +483,11 @@ void StateMachine::displayHandle(AgStateMachineState state) {
break; break;
} }
case AgStateMachineWiFiOkServerOkSensorConfigFailed: { case AgStateMachineWiFiOkServerOkSensorConfigFailed: {
disp.setText("Monitor not", "setup on", "dashboard"); if (ag->isBasic()) {
disp.setText("Monitor", "not on", "dashboard");
} else {
disp.setText("Monitor not", "setup on", "dashboard");
}
break; break;
} }
case AgStateMachineWiFiLost: { case AgStateMachineWiFiLost: {
@ -502,7 +538,7 @@ void StateMachine::displayHandle(void) { displayHandle(dispState); }
* *
*/ */
void StateMachine::displaySetAddToDashBoard(void) { void StateMachine::displaySetAddToDashBoard(void) {
if(addToDashBoard == false) { if (addToDashBoard == false) {
addToDashboardTime = 0; addToDashboardTime = 0;
addToDashBoardToggle = true; addToDashBoardToggle = true;
} }
@ -527,11 +563,18 @@ void StateMachine::displayWiFiConnectCountDown(int count) {
void StateMachine::ledAnimationInit(void) { ledBarAnimationCount = -1; } void StateMachine::ledAnimationInit(void) { ledBarAnimationCount = -1; }
/** /**
* @brief Handle LED from state * @brief Handle LED from state, only handle LED if board type is: One Indoor or
* Open Air
* *
* @param state * @param state
*/ */
void StateMachine::handleLeds(AgStateMachineState 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) { if (state > AgStateMachineNormal) {
logError("ledHandle: state invalid"); logError("ledHandle: state invalid");
return; return;
@ -545,7 +588,7 @@ void StateMachine::handleLeds(AgStateMachineState state) {
case AgStateMachineWiFiManagerMode: { case AgStateMachineWiFiManagerMode: {
/** In WiFi Manager Mode */ /** In WiFi Manager Mode */
/** Turn LED OFF */ /** Turn LED OFF */
/** Turn midle LED Color */ /** Turn middle LED Color */
if (ag->isOne()) { if (ag->isOne()) {
ag->ledBar.setColor(0, 0, 255, ag->ledBar.getNumberOfLeds() / 2); ag->ledBar.setColor(0, 0, 255, ag->ledBar.getNumberOfLeds() / 2);
} else { } else {
@ -685,7 +728,7 @@ void StateMachine::handleLeds(AgStateMachineState state) {
break; break;
} }
case AgStateMachineSensorConfigFailed: { case AgStateMachineSensorConfigFailed: {
/** Server is reachable but there is some conguration issue to be fixed on /** Server is reachable but there is some configuration issue to be fixed on
* the server side */ * the server side */
if (ag->isOne()) { if (ag->isOne()) {
ag->ledBar.setColor(139, 24, 248, 0); ag->ledBar.setColor(139, 24, 248, 0);

View File

@ -1,8 +1,42 @@
#include "AgValue.h" #include "AgValue.h"
#include "AgConfigure.h" #include "AgConfigure.h"
#include "AirGradient.h" #include "AirGradient.h"
#include "Main/utils.h"
#include "Libraries/Arduino_JSON/src/Arduino_JSON.h" #include "Libraries/Arduino_JSON/src/Arduino_JSON.h"
#define json_prop_pmFirmware "firmware"
/**
* @brief Get PMS5003 firmware version string
*
* @param fwCode
* @return String
*/
String Measurements::pms5003FirmwareVersion(int fwCode) {
return pms5003FirmwareVersionBase("PMS5003x", fwCode);
}
/**
* @brief Get PMS5003T firmware version string
*
* @param fwCode
* @return String
*/
String Measurements::pms5003TFirmwareVersion(int fwCode) {
return pms5003FirmwareVersionBase("PMS5003x", fwCode);
}
/**
* @brief Get firmware version string
*
* @param prefix Prefix firmware string
* @param fwCode Version code
* @return string
*/
String Measurements::pms5003FirmwareVersionBase(String prefix, int fwCode) {
return prefix + String("-") + String(fwCode);
}
String Measurements::toString(bool localServer, AgFirmwareMode fwMode, int rssi, String Measurements::toString(bool localServer, AgFirmwareMode fwMode, int rssi,
void *_ag, void *_config) { void *_ag, void *_config) {
AirGradient *ag = (AirGradient *)_ag; AirGradient *ag = (AirGradient *)_ag;
@ -13,36 +47,40 @@ String Measurements::toString(bool localServer, AgFirmwareMode fwMode, int rssi,
if (localServer) { if (localServer) {
root["serialno"] = ag->deviceId(); root["serialno"] = ag->deviceId();
} }
if (config->hasSensorS8) {
if (this->CO2 >= 0) { if (config->hasSensorS8 && utils::isValidCO2(this->CO2)) {
root["rco2"] = this->CO2; root["rco2"] = this->CO2;
}
} }
if (ag->isOne()) { if (ag->isOne() || (ag->isPro4_2()) || ag->isPro3_3() || ag->isBasic()) {
if (config->hasSensorPMS1) { if (config->hasSensorPMS1) {
if (this->pm01_1 >= 0) { if (utils::isValidPm(this->pm01_1)) {
root["pm01"] = this->pm01_1; root["pm01"] = this->pm01_1;
} }
if (this->pm25_1 >= 0) { if (utils::isValidPm(this->pm25_1)) {
root["pm02"] = this->pm25_1; root["pm02"] = this->pm25_1;
} }
if (this->pm10_1 >= 0) { if (utils::isValidPm(this->pm10_1)) {
root["pm10"] = this->pm10_1; root["pm10"] = this->pm10_1;
} }
if (this->pm03PCount_1 >= 0) { if (utils::isValidPm03Count(this->pm03PCount_1)) {
root["pm003Count"] = this->pm03PCount_1; root["pm003Count"] = this->pm03PCount_1;
} }
if (!localServer) {
root[json_prop_pmFirmware] =
this->pms5003FirmwareVersion(ag->pms5003.getFirmwareVersion());
}
} }
if (config->hasSensorSHT) { if (config->hasSensorSHT) {
if (this->Temperature > -1001) { if (utils::isValidTemperature(this->Temperature)) {
root["atmp"] = ag->round2(this->Temperature); root["atmp"] = ag->round2(this->Temperature);
if (localServer) { if (localServer) {
root["atmpCompensated"] = ag->round2(this->Temperature); root["atmpCompensated"] = ag->round2(this->Temperature);
} }
} }
if (this->Humidity >= 0) { if (utils::isValidHumidity(this->Humidity)) {
root["rhum"] = this->Humidity; root["rhum"] = this->Humidity;
if (localServer) { if (localServer) {
root["rhumCompensated"] = this->Humidity; root["rhumCompensated"] = this->Humidity;
@ -50,109 +88,297 @@ String Measurements::toString(bool localServer, AgFirmwareMode fwMode, int rssi,
} }
} }
} else { if (config->hasSensorSHT && config->hasSensorPMS1) {
if (config->hasSensorPMS1 && config->hasSensorPMS2) { int pm25 = ag->pms5003.compensate(this->pm25_1, this->Humidity);
root["pm01"] = ag->round2((this->pm01_1 + this->pm01_2) / 2.0); if (pm25 >= 0) {
root["pm02"] = ag->round2((this->pm25_1 + this->pm25_2) / 2.0); root["pm02Compensated"] = pm25;
root["pm10"] = ag->round2((this->pm10_1 + this->pm10_2) / 2.0);
root["pm003Count"] =
ag->round2((this->pm03PCount_1 + this->pm03PCount_2) / 2.0);
root["atmp"] = ag->round2((this->temp_1 + this->temp_2) / 2.0f);
root["rhum"] = ag->round2((this->hum_1 + this->hum_2) / 2.0f);
if (localServer) {
root["atmpCompensated"] =
ag->round2(ag->pms5003t_2.temperatureCompensated(
(this->temp_1 + this->temp_2) / 2.0f));
root["rhumCompensated"] = (int)ag->pms5003t_2.humidityCompensated(
(this->hum_1 + this->hum_2) / 2.0f);
} }
} }
if (fwMode == FW_MODE_O_1PS || fwMode == FW_MODE_O_1PST) { } else {
if (config->hasSensorPMS1) { if (config->hasSensorPMS1 && config->hasSensorPMS2) {
root["pm01"] = this->pm01_1; if (utils::isValidPm(this->pm01_1) && utils::isValidPm(this->pm01_2)) {
root["pm02"] = this->pm25_1; root["pm01"] = ag->round2((this->pm01_1 + this->pm01_2) / 2.0f);
root["pm10"] = this->pm10_1; }
root["pm003Count"] = this->pm03PCount_1; if (utils::isValidPm(this->pm25_1) && utils::isValidPm(this->pm25_2)) {
root["atmp"] = ag->round2(this->temp_1); root["pm02"] = ag->round2((this->pm25_1 + this->pm25_2) / 2.0f);
root["rhum"] = this->hum_1; }
if (utils::isValidPm(this->pm10_1) && utils::isValidPm(this->pm10_2)) {
root["pm10"] = ag->round2((this->pm10_1 + this->pm10_2) / 2.0f);
}
if (utils::isValidPm(this->pm03PCount_1) && utils::isValidPm(this->pm03PCount_2)) {
root["pm003Count"] = ag->round2((this->pm03PCount_1 + this->pm03PCount_2) / 2.0f);
}
float val;
if (utils::isValidTemperature(this->temp_1) && utils::isValidTemperature(this->temp_1)) {
root["atmp"] = ag->round2((this->temp_1 + this->temp_2) / 2.0f);
if (localServer) { if (localServer) {
root["atmpCompensated"] = val = ag->pms5003t_2.compensateTemp((this->temp_1 + this->temp_2) / 2.0f);
ag->round2(ag->pms5003t_1.temperatureCompensated(this->temp_1)); if (utils::isValidTemperature(val)) {
root["rhumCompensated"] = root["atmpCompensated"] = ag->round2(val);
(int)ag->pms5003t_1.humidityCompensated(this->hum_1); }
}
}
if (utils::isValidHumidity(this->hum_1) && utils::isValidHumidity(this->hum_1)) {
root["rhum"] = ag->round2((this->hum_1 + this->hum_2) / 2.0f);
if (localServer) {
val = ag->pms5003t_2.compensateHum((this->hum_1 + this->hum_2) / 2.0f);
if (utils::isValidHumidity(val)) {
root["rhumCompensated"] = (int)val;
}
}
}
int pm25 = (ag->pms5003t_1.compensate(this->pm25_1, this->hum_1) +
ag->pms5003t_2.compensate(this->pm25_2, this->hum_2)) /
2;
root["pm02Compensated"] = pm25;
}
if (fwMode == FW_MODE_O_1PS || fwMode == FW_MODE_O_1PST) {
float val;
if (config->hasSensorPMS1) {
if (utils::isValidPm(this->pm01_1)) {
root["pm01"] = this->pm01_1;
}
if (utils::isValidPm(this->pm25_1)) {
root["pm02"] = this->pm25_1;
}
if (utils::isValidPm(this->pm10_1)) {
root["pm10"] = this->pm10_1;
}
if (utils::isValidPm03Count(this->pm03PCount_1)) {
root["pm003Count"] = this->pm03PCount_1;
}
if (utils::isValidTemperature(this->temp_1)) {
root["atmp"] = ag->round2(this->temp_1);
if (localServer) {
val = ag->pms5003t_1.compensateTemp(this->temp_1);
if (utils::isValidTemperature(val)) {
root["atmpCompensated"] = ag->round2(val);
}
}
}
if (utils::isValidHumidity(this->hum_1)) {
root["rhum"] = this->hum_1;
if (localServer) {
val = ag->pms5003t_1.compensateHum(this->hum_1);
if (utils::isValidHumidity(val)) {
root["rhumCompensated"] = (int)val;
}
}
}
root["pm02Compensated"] = ag->pms5003t_1.compensate(this->pm25_1, this->hum_1);
if (!localServer) {
root[json_prop_pmFirmware] =
pms5003TFirmwareVersion(ag->pms5003t_1.getFirmwareVersion());
} }
} }
if (config->hasSensorPMS2) { if (config->hasSensorPMS2) {
root["pm01"] = this->pm01_2; if(utils::isValidPm(this->pm01_2)) {
root["pm02"] = this->pm25_2; root["pm01"] = this->pm01_2;
root["pm10"] = this->pm10_2; }
root["pm003Count"] = this->pm03PCount_2; if(utils::isValidPm(this->pm25_2)) {
root["atmp"] = ag->round2(this->temp_2); root["pm02"] = this->pm25_2;
root["rhum"] = this->hum_2; }
if (localServer) { if(utils::isValidPm(this->pm10_2)) {
root["atmpCompensated"] = root["pm10"] = this->pm10_2;
ag->round2(ag->pms5003t_2.temperatureCompensated(this->temp_2)); }
root["rhumCompensated"] = if(utils::isValidPm03Count(this->pm03PCount_2)) {
(int)ag->pms5003t_2.humidityCompensated(this->hum_2); root["pm003Count"] = this->pm03PCount_2;
}
float val;
if (utils::isValidTemperature(this->temp_2)) {
root["atmp"] = ag->round2(this->temp_2);
if (localServer) {
val = ag->pms5003t_2.compensateTemp(this->temp_2);
if (utils::isValidTemperature(val)) {
root["atmpCompensated"] = ag->round2(val);
}
}
}
if(utils::isValidHumidity(this->hum_2)) {
root["rhum"] = this->hum_2;
if (localServer) {
val = ag->pms5003t_2.compensateHum(this->hum_2);
if (utils::isValidHumidity(val)) {
root["rhumCompensated"] = (int)val;
}
}
}
root["pm02Compensated"] = ag->pms5003t_2.compensate(this->pm25_2, this->hum_2);
if(!localServer) {
root[json_prop_pmFirmware] =
pms5003TFirmwareVersion(ag->pms5003t_1.getFirmwareVersion());
} }
} }
} else { } else {
if (fwMode == FW_MODE_O_1P) { if (fwMode == FW_MODE_O_1P) {
float val;
if (config->hasSensorPMS1) { if (config->hasSensorPMS1) {
root["pm01"] = this->pm01_1; if (utils::isValidPm(this->pm01_1)) {
root["pm02"] = this->pm25_1; root["pm01"] = this->pm01_1;
root["pm10"] = this->pm10_1; }
root["pm003Count"] = this->pm03PCount_1; if (utils::isValidPm(this->pm25_1)) {
root["atmp"] = ag->round2(this->temp_1); root["pm02"] = this->pm25_1;
root["rhum"] = this->hum_1; }
if (localServer) { if (utils::isValidPm(this->pm10_1)) {
root["atmpCompensated"] = root["pm10"] = this->pm10_1;
ag->round2(ag->pms5003t_1.temperatureCompensated(this->temp_1)); }
root["rhumCompensated"] = if (utils::isValidPm03Count(this->pm03PCount_1)) {
(int)ag->pms5003t_1.humidityCompensated(this->hum_1); root["pm003Count"] = this->pm03PCount_1;
}
if (utils::isValidTemperature(this->temp_1)) {
root["atmp"] = ag->round2(this->temp_1);
if (localServer) {
val = ag->pms5003t_1.compensateTemp(this->temp_1);
if (utils::isValidTemperature(val)) {
root["atmpCompensated"] = ag->round2(val);
}
}
}
if (utils::isValidHumidity(this->hum_1)) {
root["rhum"] = this->hum_1;
if(localServer) {
val = ag->pms5003t_1.compensateHum(this->hum_1);
if(utils::isValidHumidity(val)) {
root["rhumCompensated"] = (int)val;
}
}
}
root["pm02Compensated"] = ag->pms5003t_1.compensate(this->pm25_1, this->hum_1);
if(!localServer) {
root[json_prop_pmFirmware] =
pms5003TFirmwareVersion(ag->pms5003t_1.getFirmwareVersion());
} }
} else if (config->hasSensorPMS2) { } else if (config->hasSensorPMS2) {
root["pm01"] = this->pm01_2; if(utils::isValidPm(this->pm01_2)) {
root["pm02"] = this->pm25_2; root["pm01"] = this->pm01_2;
root["pm10"] = this->pm10_2; }
root["pm003Count"] = this->pm03PCount_2; if(utils::isValidPm(this->pm25_2)) {
root["atmp"] = ag->round2(this->temp_2); root["pm02"] = this->pm25_2;
root["rhum"] = this->hum_2; }
if (localServer) { if(utils::isValidPm(this->pm10_2)) {
root["atmpCompensated"] = root["pm10"] = this->pm10_2;
ag->round2(ag->pms5003t_1.temperatureCompensated(this->temp_2)); }
root["rhumCompensated"] = if(utils::isValidPm03Count(this->pm03PCount_2)) {
(int)ag->pms5003t_1.humidityCompensated(this->hum_2); root["pm003Count"] = this->pm03PCount_2;
}
if (utils::isValidTemperature(this->temp_2)) {
root["atmp"] = ag->round2(this->temp_2);
if (localServer) {
val = ag->pms5003t_1.compensateTemp(this->temp_2);
if (utils::isValidTemperature(val)) {
root["atmpCompensated"] = ag->round2(val);
}
}
}
if (utils::isValidHumidity(this->hum_2)) {
root["rhum"] = this->hum_2;
if(localServer) {
val = ag->pms5003t_1.compensateHum(this->hum_2);
if(utils::isValidHumidity(val)) {
root["rhumCompensated"] = (int)val;
}
}
}
root["pm02Compensated"] = ag->pms5003t_1.compensate(this->pm25_1, this->hum_1);
if(!localServer) {
root[json_prop_pmFirmware] =
pms5003TFirmwareVersion(ag->pms5003t_2.getFirmwareVersion());
} }
} }
} else { } else {
float val;
if (config->hasSensorPMS1) { if (config->hasSensorPMS1) {
root["channels"]["1"]["pm01"] = this->pm01_1; if(utils::isValidPm(this->pm01_1)) {
root["channels"]["1"]["pm02"] = this->pm25_1; root["channels"]["1"]["pm01"] = this->pm01_1;
root["channels"]["1"]["pm10"] = this->pm10_1; }
root["channels"]["1"]["pm003Count"] = this->pm03PCount_1; if(utils::isValidPm(this->pm25_1)) {
root["channels"]["1"]["atmp"] = ag->round2(this->temp_1); root["channels"]["1"]["pm02"] = this->pm25_1;
root["channels"]["1"]["rhum"] = this->hum_1; }
if (localServer) { if(utils::isValidPm(this->pm10_1)) {
root["channels"]["1"]["atmpCompensated"] = root["channels"]["1"]["pm10"] = this->pm10_1;
ag->round2(ag->pms5003t_1.temperatureCompensated(this->temp_1)); }
root["channels"]["1"]["rhumCompensated"] = if (utils::isValidPm03Count(this->pm03PCount_1)) {
(int)ag->pms5003t_1.humidityCompensated(this->hum_1); root["channels"]["1"]["pm003Count"] = this->pm03PCount_1;
}
if(utils::isValidTemperature(this->temp_1)) {
root["channels"]["1"]["atmp"] = ag->round2(this->temp_1);
if (localServer) {
val = ag->pms5003t_1.compensateTemp(this->temp_1);
if (utils::isValidTemperature(val)) {
root["channels"]["1"]["atmpCompensated"] = ag->round2(val);
}
}
}
if (utils::isValidHumidity(this->hum_1)) {
root["channels"]["1"]["rhum"] = this->hum_1;
if (localServer) {
val = ag->pms5003t_1.compensateHum(this->hum_1);
if (utils::isValidHumidity(val)) {
root["channels"]["1"]["rhumCompensated"] = (int)val;
}
}
}
root["channels"]["1"]["pm02Compensated"] = ag->pms5003t_1.compensate(this->pm25_1, this->hum_1);
// PMS5003T version
if(!localServer) {
root["channels"]["1"][json_prop_pmFirmware] =
pms5003TFirmwareVersion(ag->pms5003t_1.getFirmwareVersion());
} }
} }
if (config->hasSensorPMS2) { if (config->hasSensorPMS2) {
root["channels"]["2"]["pm01"] = this->pm01_2; float val;
root["channels"]["2"]["pm02"] = this->pm25_2; if (utils::isValidPm(this->pm01_2)) {
root["channels"]["2"]["pm10"] = this->pm10_2; root["channels"]["2"]["pm01"] = this->pm01_2;
root["channels"]["2"]["pm003Count"] = this->pm03PCount_2; }
root["channels"]["2"]["atmp"] = ag->round2(this->temp_2); if (utils::isValidPm(this->pm25_2)) {
root["channels"]["2"]["rhum"] = this->hum_2; root["channels"]["2"]["pm02"] = this->pm25_2;
if (localServer) { }
root["channels"]["2"]["atmpCompensated"] = if (utils::isValidPm(this->pm10_2)) {
ag->round2(ag->pms5003t_1.temperatureCompensated(this->temp_2)); root["channels"]["2"]["pm10"] = this->pm10_2;
root["channels"]["2"]["rhumCompensated"] = }
(int)ag->pms5003t_1.humidityCompensated(this->hum_2); if (utils::isValidPm03Count(this->pm03PCount_2)) {
root["channels"]["2"]["pm003Count"] = this->pm03PCount_2;
}
if (utils::isValidTemperature(this->temp_2)) {
root["channels"]["2"]["atmp"] = ag->round2(this->temp_2);
if (localServer) {
val = ag->pms5003t_1.compensateTemp(this->temp_2);
if (utils::isValidTemperature(val)) {
root["channels"]["2"]["atmpCompensated"] = ag->round2(val);
}
}
}
if (utils::isValidHumidity(this->hum_2)) {
root["channels"]["2"]["rhum"] = this->hum_2;
if (localServer) {
val = ag->pms5003t_1.compensateHum(this->hum_2);
if (utils::isValidHumidity(val)) {
root["channels"]["2"]["rhumCompensated"] = (int)val;
}
}
}
root["channels"]["2"]["pm02Compensated"] = ag->pms5003t_2.compensate(this->pm25_2, this->hum_2);
// PMS5003T version
if(!localServer) {
root["channels"]["2"][json_prop_pmFirmware] =
pms5003TFirmwareVersion(ag->pms5003t_2.getFirmwareVersion());
} }
} }
} }
@ -160,23 +386,26 @@ String Measurements::toString(bool localServer, AgFirmwareMode fwMode, int rssi,
} }
if (config->hasSensorSGP) { if (config->hasSensorSGP) {
if (this->TVOC >= 0) { if (utils::isValidVOC(this->TVOC)) {
root["tvocIndex"] = this->TVOC; root["tvocIndex"] = this->TVOC;
} }
if (this->TVOCRaw >= 0) { if (utils::isValidVOC(this->TVOCRaw)) {
root["tvocRaw"] = this->TVOCRaw; root["tvocRaw"] = this->TVOCRaw;
} }
if (this->NOx >= 0) { if (utils::isValidNOx(this->NOx)) {
root["noxIndex"] = this->NOx; root["noxIndex"] = this->NOx;
} }
if (this->NOxRaw >= 0) { if (utils::isValidNOx(this->NOxRaw)) {
root["noxRaw"] = this->NOxRaw; root["noxRaw"] = this->NOxRaw;
} }
} }
root["boot"] = bootCount;
root["bootCount"] = bootCount; root["bootCount"] = bootCount;
if (localServer) { if (localServer) {
root["ledMode"] = config->getLedBarModeName(); if (ag->isOne()) {
root["ledMode"] = config->getLedBarModeName();
}
root["firmware"] = ag->getVersion(); root["firmware"] = ag->getVersion();
root["model"] = AgFirmwareModeName(fwMode); root["model"] = AgFirmwareModeName(fwMode);
} }

View File

@ -6,6 +6,9 @@
class Measurements { class Measurements {
private: private:
String pms5003FirmwareVersion(int fwCode);
String pms5003TFirmwareVersion(int fwCode);
String pms5003FirmwareVersionBase(String prefix, int fwCode);
public: public:
Measurements() { Measurements() {
pm25_1 = -1; pm25_1 = -1;
@ -69,7 +72,6 @@ public:
int countPosition; int countPosition;
const int targetCount = 20; const int targetCount = 20;
int bootCount; int bootCount;
int otaBootCount;
String toString(bool isLocal, AgFirmwareMode fwMode, int rssi, void* _ag, void* _config); String toString(bool isLocal, AgFirmwareMode fwMode, int rssi, void* _ag, void* _config);
}; };

View File

@ -13,7 +13,6 @@
*/ */
void WifiConnector::setAirGradient(AirGradient *ag) { this->ag = ag; } void WifiConnector::setAirGradient(AirGradient *ag) { this->ag = ag; }
#ifdef ESP32
/** /**
* @brief Construct a new Ag Wi Fi Connector:: Ag Wi Fi Connector object * @brief Construct a new Ag Wi Fi Connector:: Ag Wi Fi Connector object
* *
@ -24,9 +23,6 @@ void WifiConnector::setAirGradient(AirGradient *ag) { this->ag = ag; }
WifiConnector::WifiConnector(OledDisplay &disp, Stream &log, StateMachine &sm, WifiConnector::WifiConnector(OledDisplay &disp, Stream &log, StateMachine &sm,
Configuration &config) Configuration &config)
: PrintLog(log, "WifiConnector"), disp(disp), sm(sm), config(config) {} : PrintLog(log, "WifiConnector"), disp(disp), sm(sm), config(config) {}
#else
WifiConnector::WifiConnector(Stream &log) : PrintLog(log, "WiFiConnector") {}
#endif
WifiConnector::~WifiConnector() {} WifiConnector::~WifiConnector() {}
@ -45,23 +41,44 @@ bool WifiConnector::connect(void) {
} }
} }
WiFi.begin();
String wifiSSID = WIFI()->getWiFiSSID(true);
if (wifiSSID.isEmpty()) {
logInfo("Connected WiFi is empty, connect to default wifi \"" +
String(this->defaultSsid) + String("\""));
/** Set wifi connect */
WiFi.begin(this->defaultSsid, this->defaultPassword);
/** Wait for wifi connect to AP */
int count = 0;
while (WiFi.status() != WL_CONNECTED) {
delay(1000);
count++;
if (count >= 15) {
logError("Try connect to default wifi \"" + String(this->defaultSsid) +
String("\" failed"));
break;
}
}
}
WIFI()->setConfigPortalBlocking(false); WIFI()->setConfigPortalBlocking(false);
WIFI()->setConnectTimeout(15); WIFI()->setConnectTimeout(15);
WIFI()->setTimeout(WIFI_CONNECT_COUNTDOWN_MAX); WIFI()->setTimeout(WIFI_CONNECT_COUNTDOWN_MAX);
#ifdef ESP32
WIFI()->setAPCallback([this](WiFiManager *obj) { _wifiApCallback(); }); WIFI()->setAPCallback([this](WiFiManager *obj) { _wifiApCallback(); });
WIFI()->setSaveConfigCallback([this]() { _wifiSaveConfig(); }); WIFI()->setSaveConfigCallback([this]() { _wifiSaveConfig(); });
WIFI()->setSaveParamsCallback([this]() { _wifiSaveParamCallback(); }); WIFI()->setSaveParamsCallback([this]() { _wifiSaveParamCallback(); });
if (ag->isOne()) { WIFI()->setConfigPortalTimeoutCallback([this]() {_wifiTimeoutCallback();});
if (ag->isOne() || (ag->isPro4_2()) || ag->isPro3_3() || ag->isBasic()) {
disp.setText("Connecting to", "WiFi", "..."); disp.setText("Connecting to", "WiFi", "...");
} else { } else {
logInfo("Connecting to WiFi..."); logInfo("Connecting to WiFi...");
} }
ssid = "airgradient-" + ag->deviceId(); ssid = "airgradient-" + ag->deviceId();
#else
ssid = "AG-" + String(ESP.getChipId(), HEX); // ssid = "AG-" + String(ESP.getChipId(), HEX);
#endif
WIFI()->setConfigPortalTimeout(WIFI_CONNECT_COUNTDOWN_MAX); WIFI()->setConfigPortalTimeout(WIFI_CONNECT_COUNTDOWN_MAX);
WiFiManagerParameter postToAg("chbPostToAg", WiFiManagerParameter postToAg("chbPostToAg",
@ -77,6 +94,8 @@ bool WifiConnector::connect(void) {
WIFI()->autoConnect(ssid.c_str(), WIFI_HOTSPOT_PASSWORD_DEFAULT); WIFI()->autoConnect(ssid.c_str(), WIFI_HOTSPOT_PASSWORD_DEFAULT);
logInfo("Wait for configure portal");
#ifdef ESP32 #ifdef ESP32
// Task handle WiFi connection. // Task handle WiFi connection.
xTaskCreate( xTaskCreate(
@ -84,6 +103,7 @@ bool WifiConnector::connect(void) {
WifiConnector *connector = (WifiConnector *)obj; WifiConnector *connector = (WifiConnector *)obj;
while (connector->_wifiConfigPortalActive()) { while (connector->_wifiConfigPortalActive()) {
connector->_wifiProcess(); connector->_wifiProcess();
vTaskDelay(1);
} }
vTaskDelete(NULL); vTaskDelete(NULL);
}, },
@ -138,10 +158,14 @@ bool WifiConnector::connect(void) {
delay(1); // avoid watchdog timer reset. delay(1); // avoid watchdog timer reset.
} }
#else
_wifiProcess();
#endif
/** Show display wifi connect result failed */ /** Show display wifi connect result failed */
if (WiFi.isConnected() == false) { if (WiFi.isConnected() == false) {
sm.handleLeds(AgStateMachineWiFiManagerConnectFailed); sm.handleLeds(AgStateMachineWiFiManagerConnectFailed);
if (ag->isOne()) { if (ag->isOne() || ag->isPro4_2() || ag->isPro3_3() || ag->isBasic()) {
sm.displayHandle(AgStateMachineWiFiManagerConnectFailed); sm.displayHandle(AgStateMachineWiFiManagerConnectFailed);
} }
delay(6000); delay(6000);
@ -159,9 +183,7 @@ bool WifiConnector::connect(void) {
} }
hasPortalConfig = false; hasPortalConfig = false;
} }
#else
_wifiProcess();
#endif
return true; return true;
} }
@ -176,24 +198,6 @@ void WifiConnector::disconnect(void) {
} }
} }
#ifdef ESP32
#else
void WifiConnector::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();
delay(100);
}
#endif
/** /**
* @brief Has wifi STA connected to WIFI softAP (this device) * @brief Has wifi STA connected to WIFI softAP (this device)
* *
@ -204,7 +208,6 @@ bool WifiConnector::wifiClientConnected(void) {
return WiFi.softAPgetStationNum() ? true : false; return WiFi.softAPgetStationNum() ? true : false;
} }
#ifdef ESP32
/** /**
* @brief Handle WiFiManage softAP setup completed callback * @brief Handle WiFiManage softAP setup completed callback
* *
@ -245,7 +248,8 @@ void WifiConnector::_wifiSaveParamCallback(void) {
bool WifiConnector::_wifiConfigPortalActive(void) { bool WifiConnector::_wifiConfigPortalActive(void) {
return WIFI()->getConfigPortalActive(); return WIFI()->getConfigPortalActive();
} }
#endif void WifiConnector::_wifiTimeoutCallback(void) { connectorTimeout = true; }
/** /**
* @brief Process WiFiManager connection * @brief Process WiFiManager connection
* *
@ -254,33 +258,67 @@ void WifiConnector::_wifiProcess() {
#ifdef ESP32 #ifdef ESP32
WIFI()->process(); WIFI()->process();
#else #else
int count = WIFI_CONNECT_COUNTDOWN_MAX; /** Wait for WiFi connect and show LED, display status */
displayShowText(String(WIFI_CONNECT_COUNTDOWN_MAX) + " sec", "SSID:", ssid); uint32_t dispPeriod = millis();
uint32_t ledPeriod = millis();
bool clientConnectChanged = false;
AgStateMachineState stateOld = sm.getDisplayState();
while (WIFI()->getConfigPortalActive()) { while (WIFI()->getConfigPortalActive()) {
WIFI()->process(); WIFI()->process();
uint32_t lastTime = millis(); if (WiFi.isConnected() == false) {
uint32_t ms = (uint32_t)(millis() - lastTime); /** Display countdown */
if (ms >= 1000) { uint32_t ms;
lastTime = millis(); 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();
}
}
}
displayShowText(String(count) + " sec", "SSID:", ssid); /** LED animations */
ms = (uint32_t)(millis() - ledPeriod);
if (ms >= 100) {
ledPeriod = millis();
sm.handleLeds();
}
count--; /** Check for client connect to change led color */
bool clientConnected = wifiClientConnected();
// Timeout if (clientConnected != clientConnectChanged) {
if (count == 0) { clientConnectChanged = clientConnected;
break; if (clientConnectChanged) {
sm.handleLeds(AgStateMachineWiFiManagerPortalActive);
} else {
sm.ledAnimationInit();
sm.handleLeds(AgStateMachineWiFiManagerMode);
if (ag->isOne()) {
sm.displayHandle(AgStateMachineWiFiManagerMode);
}
}
} }
} }
delay(1);
} }
if (!WiFi.isConnected()) { // TODO This is for basic
displayShowText("Booting", "offline", "mode"); if (ag->getBoardType() == DIY_BASIC) {
Serial.println("failed to connect and hit timeout"); if (!WiFi.isConnected()) {
delay(2500); // disp.setText("Booting", "offline", "mode");
} else { Serial.println("failed to connect and hit timeout");
hasConfig = true; delay(2500);
} else {
hasConfig = true;
}
} }
#endif #endif
} }
@ -305,8 +343,6 @@ void WifiConnector::handle(void) {
if (ms >= 10000) { if (ms >= 10000) {
lastRetry = millis(); lastRetry = millis();
WiFi.reconnect(); WiFi.reconnect();
// Serial.printf("Re-Connect WiFi\r\n");
logInfo("Re-Connect WiFi"); logInfo("Re-Connect WiFi");
} }
} }
@ -324,7 +360,16 @@ bool WifiConnector::isConnected(void) { return WiFi.isConnected(); }
* this method * this method
* *
*/ */
void WifiConnector::reset(void) { WIFI()->resetSettings(); } 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 * @brief Get wifi RSSI
@ -342,7 +387,7 @@ String WifiConnector::localIpStr(void) { return WiFi.localIP().toString(); }
/** /**
* @brief Get status that wifi has configurated * @brief Get status that wifi has configurated
* *
* @return true Configurated * @return true Configurated
* @return false Not Configurated * @return false Not Configurated
*/ */
@ -352,3 +397,19 @@ bool WifiConnector::hasConfigurated(void) {
} }
return true; 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");
}

View File

@ -12,45 +12,44 @@
class WifiConnector : public PrintLog { class WifiConnector : public PrintLog {
private: private:
AirGradient *ag; AirGradient *ag;
#ifdef ESP32
OledDisplay &disp; OledDisplay &disp;
StateMachine &sm; StateMachine &sm;
Configuration &config; Configuration &config;
#else
void displayShowText(String ln1, String ln2, String ln3);
#endif
String ssid; String ssid;
void *wifi = NULL; void *wifi = NULL;
bool hasConfig; bool hasConfig;
uint32_t lastRetry; uint32_t lastRetry;
bool hasPortalConfig = false; bool hasPortalConfig = false;
bool connectorTimeout = false;
bool wifiClientConnected(void); bool wifiClientConnected(void);
public: public:
void setAirGradient(AirGradient *ag); void setAirGradient(AirGradient *ag);
#ifdef ESP32
WifiConnector(OledDisplay &disp, Stream &log, StateMachine &sm, Configuration& config); WifiConnector(OledDisplay &disp, Stream &log, StateMachine &sm, Configuration& config);
#else
WifiConnector(Stream &log);
#endif
~WifiConnector(); ~WifiConnector();
bool connect(void); bool connect(void);
void disconnect(void); void disconnect(void);
void handle(void); void handle(void);
#ifdef ESP32
void _wifiApCallback(void); void _wifiApCallback(void);
void _wifiSaveConfig(void); void _wifiSaveConfig(void);
void _wifiSaveParamCallback(void); void _wifiSaveParamCallback(void);
bool _wifiConfigPortalActive(void); bool _wifiConfigPortalActive(void);
#endif void _wifiTimeoutCallback(void);
void _wifiProcess(); void _wifiProcess();
bool isConnected(void); bool isConnected(void);
void reset(void); void reset(void);
int RSSI(void); int RSSI(void);
String localIpStr(void); String localIpStr(void);
bool hasConfigurated(void); bool hasConfigurated(void);
bool isConfigurePorttalTimeout(void);
const char* defaultSsid = "airgradient";
const char* defaultPassword = "cleanair";
void setDefault(void);
}; };
#endif /** _AG_WIFI_CONNECTOR_H_ */ #endif /** _AG_WIFI_CONNECTOR_H_ */

View File

@ -41,7 +41,14 @@ String AirGradient::getVersion(void) { return GIT_VERSION; }
BoardType AirGradient::getBoardType(void) { return boardType; } BoardType AirGradient::getBoardType(void) { return boardType; }
double AirGradient::round2(double value) { double AirGradient::round2(double value) {
return (int)(value * 100 + 0.5) / 100.0; 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) { String AirGradient::getBoardName(void) {
@ -58,6 +65,20 @@ bool AirGradient::isOne(void) {
return boardType == BoardType::ONE_INDOOR; return boardType == BoardType::ONE_INDOOR;
} }
bool AirGradient::isOpenAir(void) {
return boardType == BoardType::OPEN_AIR_OUTDOOR;
}
bool AirGradient::isPro4_2(void) {
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 AirGradient::deviceId(void) {
String mac = WiFi.macAddress(); String mac = WiFi.macAddress();
mac.replace(":", ""); mac.replace(":", "");

View File

@ -12,9 +12,10 @@
#include "S8/S8.h" #include "S8/S8.h"
#include "Sgp41/Sgp41.h" #include "Sgp41/Sgp41.h"
#include "Sht/Sht.h" #include "Sht/Sht.h"
#include "Main/utils.h"
#ifndef GIT_VERSION #ifndef GIT_VERSION
#define GIT_VERSION "snapshot" #define GIT_VERSION "3.1.8-snap"
#endif #endif
/** /**
@ -134,6 +135,37 @@ public:
*/ */
bool isOne(void); bool isOne(void);
/**
* @brief Check that Airgradient object is OPEN_AIR
*
* @return true
* @return false
*/
bool isOpenAir(void);
/**
* @brief Check that Airgradient object is DIY_PRO 4.2 indoor
*
* @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 * @brief Get device Id
* *

View File

@ -14,6 +14,12 @@ const char *AgFirmwareModeName(AgFirmwareMode mode) {
return "0-1PS"; return "0-1PS";
case FW_MODE_O_1P: case FW_MODE_O_1P:
return "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: default:
break; break;
} }

View File

@ -101,6 +101,9 @@ enum AgFirmwareMode {
FW_MODE_O_1PP, /** PMS5003T_1, PMS5003T_2 */ FW_MODE_O_1PP, /** PMS5003T_1, PMS5003T_2 */
FW_MODE_O_1PS, /** PMS5003T, S8 */ FW_MODE_O_1PS, /** PMS5003T, S8 */
FW_MODE_O_1P, /** PMS5003T */ FW_MODE_O_1P, /** PMS5003T */
FW_MODE_I_42PS, /** DIY_PRO 4.2 */
FW_MODE_I_33PS, /** DIY_PRO 3.3 */
FW_MODE_I_BASIC_40PS, /** DIY_BASIC 4.0 */
}; };
const char *AgFirmwareModeName(AgFirmwareMode mode); const char *AgFirmwareModeName(AgFirmwareMode mode);

View File

@ -0,0 +1,5 @@
tests/bin
.pioenvs
.piolibdeps
.clang_complete
.gcc-flags.json

View File

@ -0,0 +1,7 @@
sudo: false
language: cpp
compiler:
- g++
script: cd tests && make && make test
os:
- linux

View File

@ -0,0 +1,85 @@
2.8
* Add setBufferSize() to override MQTT_MAX_PACKET_SIZE
* Add setKeepAlive() to override MQTT_KEEPALIVE
* Add setSocketTimeout() to overide MQTT_SOCKET_TIMEOUT
* Added check to prevent subscribe/unsubscribe to empty topics
* Declare wifi mode prior to connect in ESP example
* Use `strnlen` to avoid overruns
* Support pre-connected Client objects
2.7
* Fix remaining-length handling to prevent buffer overrun
* Add large-payload API - beginPublish/write/publish/endPublish
* Add yield call to improve reliability on ESP
* Add Clean Session flag to connect options
* Add ESP32 support for functional callback signature
* Various other fixes
2.4
* Add MQTT_SOCKET_TIMEOUT to prevent it blocking indefinitely
whilst waiting for inbound data
* Fixed return code when publishing >256 bytes
2.3
* Add publish(topic,payload,retained) function
2.2
* Change code layout to match Arduino Library reqs
2.1
* Add MAX_TRANSFER_SIZE def to chunk messages if needed
* Reject topic/payloads that exceed MQTT_MAX_PACKET_SIZE
2.0
* Add (and default to) MQTT 3.1.1 support
* Fix PROGMEM handling for Intel Galileo/ESP8266
* Add overloaded constructors for convenience
* Add chainable setters for server/callback/client/stream
* Add state function to return connack return code
1.9
* Do not split MQTT packets over multiple calls to _client->write()
* API change: All constructors now require an instance of Client
to be passed in.
* Fixed example to match 1.8 api changes - dpslwk
* Added username/password support - WilHall
* Added publish_P - publishes messages from PROGMEM - jobytaffey
1.8
* KeepAlive interval is configurable in PubSubClient.h
* Maximum packet size is configurable in PubSubClient.h
* API change: Return boolean rather than int from various functions
* API change: Length parameter in message callback changed
from int to unsigned int
* Various internal tidy-ups around types
1.7
* Improved keepalive handling
* Updated to the Arduino-1.0 API
1.6
* Added the ability to publish a retained message
1.5
* Added default constructor
* Fixed compile error when used with arduino-0021 or later
1.4
* Fixed connection lost handling
1.3
* Fixed packet reading bug in PubSubClient.readPacket
1.2
* Fixed compile error when used with arduino-0016 or later
1.1
* Reduced size of library
* Added support for Will messages
* Clarified licensing - see LICENSE.txt
1.0
* Only Quality of Service (QOS) 0 messaging is supported
* The maximum message size, including header, is 128 bytes
* The keepalive interval is set to 30 seconds
* No support for Will messages

View File

@ -0,0 +1,20 @@
Copyright (c) 2008-2020 Nicholas O'Leary
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@ -0,0 +1,50 @@
# Arduino Client for MQTT
This library provides a client for doing simple publish/subscribe messaging with
a server that supports MQTT.
## Examples
The library comes with a number of example sketches. See File > Examples > PubSubClient
within the Arduino application.
Full API documentation is available here: https://pubsubclient.knolleary.net
## Limitations
- It can only publish QoS 0 messages. It can subscribe at QoS 0 or QoS 1.
- The maximum message size, including header, is **256 bytes** by default. This
is configurable via `MQTT_MAX_PACKET_SIZE` in `PubSubClient.h` or can be changed
by calling `PubSubClient::setBufferSize(size)`.
- The keepalive interval is set to 15 seconds by default. This is configurable
via `MQTT_KEEPALIVE` in `PubSubClient.h` or can be changed by calling
`PubSubClient::setKeepAlive(keepAlive)`.
- The client uses MQTT 3.1.1 by default. It can be changed to use MQTT 3.1 by
changing value of `MQTT_VERSION` in `PubSubClient.h`.
## Compatible Hardware
The library uses the Arduino Ethernet Client api for interacting with the
underlying network hardware. This means it Just Works with a growing number of
boards and shields, including:
- Arduino Ethernet
- Arduino Ethernet Shield
- Arduino YUN use the included `YunClient` in place of `EthernetClient`, and
be sure to do a `Bridge.begin()` first
- Arduino WiFi Shield - if you want to send packets > 90 bytes with this shield,
enable the `MQTT_MAX_TRANSFER_SIZE` define in `PubSubClient.h`.
- Sparkfun WiFly Shield [library](https://github.com/dpslwk/WiFly)
- TI CC3000 WiFi - [library](https://github.com/sparkfun/SFE_CC3000_Library)
- Intel Galileo/Edison
- ESP8266
- ESP32
The library cannot currently be used with hardware based on the ENC28J60 chip
such as the Nanode or the Nuelectronics Ethernet Shield. For those, there is an
[alternative library](https://github.com/njh/NanodeMQTT) available.
## License
This code is released under the MIT License.

View File

@ -0,0 +1,43 @@
/*
Basic MQTT example with Authentication
- connects to an MQTT server, providing username
and password
- publishes "hello world" to the topic "outTopic"
- subscribes to the topic "inTopic"
*/
#include <SPI.h>
#include <Ethernet.h>
#include <PubSubClient.h>
// Update these with values suitable for your network.
byte mac[] = { 0xDE, 0xED, 0xBA, 0xFE, 0xFE, 0xED };
IPAddress ip(172, 16, 0, 100);
IPAddress server(172, 16, 0, 2);
void callback(char* topic, byte* payload, unsigned int length) {
// handle message arrived
}
EthernetClient ethClient;
PubSubClient client(server, 1883, callback, ethClient);
void setup()
{
Ethernet.begin(mac, ip);
// Note - the default maximum packet size is 128 bytes. If the
// combined length of clientId, username and password exceed this use the
// following to increase the buffer size:
// client.setBufferSize(255);
if (client.connect("arduinoClient", "testuser", "testpass")) {
client.publish("outTopic","hello world");
client.subscribe("inTopic");
}
}
void loop()
{
client.loop();
}

View File

@ -0,0 +1,77 @@
/*
Basic MQTT example
This sketch demonstrates the basic capabilities of the library.
It connects to an MQTT server then:
- publishes "hello world" to the topic "outTopic"
- subscribes to the topic "inTopic", printing out any messages
it receives. NB - it assumes the received payloads are strings not binary
It will reconnect to the server if the connection is lost using a blocking
reconnect function. See the 'mqtt_reconnect_nonblocking' example for how to
achieve the same result without blocking the main loop.
*/
#include <SPI.h>
#include <Ethernet.h>
#include <PubSubClient.h>
// Update these with values suitable for your network.
byte mac[] = { 0xDE, 0xED, 0xBA, 0xFE, 0xFE, 0xED };
IPAddress ip(172, 16, 0, 100);
IPAddress server(172, 16, 0, 2);
void callback(char* topic, byte* payload, unsigned int length) {
Serial.print("Message arrived [");
Serial.print(topic);
Serial.print("] ");
for (int i=0;i<length;i++) {
Serial.print((char)payload[i]);
}
Serial.println();
}
EthernetClient ethClient;
PubSubClient client(ethClient);
void reconnect() {
// Loop until we're reconnected
while (!client.connected()) {
Serial.print("Attempting MQTT connection...");
// Attempt to connect
if (client.connect("arduinoClient")) {
Serial.println("connected");
// Once connected, publish an announcement...
client.publish("outTopic","hello world");
// ... and resubscribe
client.subscribe("inTopic");
} else {
Serial.print("failed, rc=");
Serial.print(client.state());
Serial.println(" try again in 5 seconds");
// Wait 5 seconds before retrying
delay(5000);
}
}
}
void setup()
{
Serial.begin(57600);
client.setServer(server, 1883);
client.setCallback(callback);
Ethernet.begin(mac, ip);
// Allow the hardware to sort itself out
delay(1500);
}
void loop()
{
if (!client.connected()) {
reconnect();
}
client.loop();
}

View File

@ -0,0 +1,129 @@
/*
Basic ESP8266 MQTT example
This sketch demonstrates the capabilities of the pubsub library in combination
with the ESP8266 board/library.
It connects to an MQTT server then:
- publishes "hello world" to the topic "outTopic" every two seconds
- subscribes to the topic "inTopic", printing out any messages
it receives. NB - it assumes the received payloads are strings not binary
- If the first character of the topic "inTopic" is an 1, switch ON the ESP Led,
else switch it off
It will reconnect to the server if the connection is lost using a blocking
reconnect function. See the 'mqtt_reconnect_nonblocking' example for how to
achieve the same result without blocking the main loop.
To install the ESP8266 board, (using Arduino 1.6.4+):
- Add the following 3rd party board manager under "File -> Preferences -> Additional Boards Manager URLs":
http://arduino.esp8266.com/stable/package_esp8266com_index.json
- Open the "Tools -> Board -> Board Manager" and click install for the ESP8266"
- Select your ESP8266 in "Tools -> Board"
*/
#include <ESP8266WiFi.h>
#include <PubSubClient.h>
// Update these with values suitable for your network.
const char* ssid = "........";
const char* password = "........";
const char* mqtt_server = "broker.mqtt-dashboard.com";
WiFiClient espClient;
PubSubClient client(espClient);
unsigned long lastMsg = 0;
#define MSG_BUFFER_SIZE (50)
char msg[MSG_BUFFER_SIZE];
int value = 0;
void setup_wifi() {
delay(10);
// We start by connecting to a WiFi network
Serial.println();
Serial.print("Connecting to ");
Serial.println(ssid);
WiFi.mode(WIFI_STA);
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
randomSeed(micros());
Serial.println("");
Serial.println("WiFi connected");
Serial.println("IP address: ");
Serial.println(WiFi.localIP());
}
void callback(char* topic, byte* payload, unsigned int length) {
Serial.print("Message arrived [");
Serial.print(topic);
Serial.print("] ");
for (int i = 0; i < length; i++) {
Serial.print((char)payload[i]);
}
Serial.println();
// Switch on the LED if an 1 was received as first character
if ((char)payload[0] == '1') {
digitalWrite(BUILTIN_LED, LOW); // Turn the LED on (Note that LOW is the voltage level
// but actually the LED is on; this is because
// it is active low on the ESP-01)
} else {
digitalWrite(BUILTIN_LED, HIGH); // Turn the LED off by making the voltage HIGH
}
}
void reconnect() {
// Loop until we're reconnected
while (!client.connected()) {
Serial.print("Attempting MQTT connection...");
// Create a random client ID
String clientId = "ESP8266Client-";
clientId += String(random(0xffff), HEX);
// Attempt to connect
if (client.connect(clientId.c_str())) {
Serial.println("connected");
// Once connected, publish an announcement...
client.publish("outTopic", "hello world");
// ... and resubscribe
client.subscribe("inTopic");
} else {
Serial.print("failed, rc=");
Serial.print(client.state());
Serial.println(" try again in 5 seconds");
// Wait 5 seconds before retrying
delay(5000);
}
}
}
void setup() {
pinMode(BUILTIN_LED, OUTPUT); // Initialize the BUILTIN_LED pin as an output
Serial.begin(115200);
setup_wifi();
client.setServer(mqtt_server, 1883);
client.setCallback(callback);
}
void loop() {
if (!client.connected()) {
reconnect();
}
client.loop();
unsigned long now = millis();
if (now - lastMsg > 2000) {
lastMsg = now;
++value;
snprintf (msg, MSG_BUFFER_SIZE, "hello world #%ld", value);
Serial.print("Publish message: ");
Serial.println(msg);
client.publish("outTopic", msg);
}
}

View File

@ -0,0 +1,179 @@
/*
Long message ESP8266 MQTT example
This sketch demonstrates sending arbitrarily large messages in combination
with the ESP8266 board/library.
It connects to an MQTT server then:
- publishes "hello world" to the topic "outTopic"
- subscribes to the topic "greenBottles/#", printing out any messages
it receives. NB - it assumes the received payloads are strings not binary
- If the sub-topic is a number, it publishes a "greenBottles/lyrics" message
with a payload consisting of the lyrics to "10 green bottles", replacing
10 with the number given in the sub-topic.
It will reconnect to the server if the connection is lost using a blocking
reconnect function. See the 'mqtt_reconnect_nonblocking' example for how to
achieve the same result without blocking the main loop.
To install the ESP8266 board, (using Arduino 1.6.4+):
- Add the following 3rd party board manager under "File -> Preferences -> Additional Boards Manager URLs":
http://arduino.esp8266.com/stable/package_esp8266com_index.json
- Open the "Tools -> Board -> Board Manager" and click install for the ESP8266"
- Select your ESP8266 in "Tools -> Board"
*/
#include <ESP8266WiFi.h>
#include <PubSubClient.h>
// Update these with values suitable for your network.
const char* ssid = "........";
const char* password = "........";
const char* mqtt_server = "broker.mqtt-dashboard.com";
WiFiClient espClient;
PubSubClient client(espClient);
long lastMsg = 0;
char msg[50];
int value = 0;
void setup_wifi() {
delay(10);
// We start by connecting to a WiFi network
Serial.println();
Serial.print("Connecting to ");
Serial.println(ssid);
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
randomSeed(micros());
Serial.println("");
Serial.println("WiFi connected");
Serial.println("IP address: ");
Serial.println(WiFi.localIP());
}
void callback(char* topic, byte* payload, unsigned int length) {
Serial.print("Message arrived [");
Serial.print(topic);
Serial.print("] ");
for (int i = 0; i < length; i++) {
Serial.print((char)payload[i]);
}
Serial.println();
// Find out how many bottles we should generate lyrics for
String topicStr(topic);
int bottleCount = 0; // assume no bottles unless we correctly parse a value from the topic
if (topicStr.indexOf('/') >= 0) {
// The topic includes a '/', we'll try to read the number of bottles from just after that
topicStr.remove(0, topicStr.indexOf('/')+1);
// Now see if there's a number of bottles after the '/'
bottleCount = topicStr.toInt();
}
if (bottleCount > 0) {
// Work out how big our resulting message will be
int msgLen = 0;
for (int i = bottleCount; i > 0; i--) {
String numBottles(i);
msgLen += 2*numBottles.length();
if (i == 1) {
msgLen += 2*String(" green bottle, standing on the wall\n").length();
} else {
msgLen += 2*String(" green bottles, standing on the wall\n").length();
}
msgLen += String("And if one green bottle should accidentally fall\nThere'll be ").length();
switch (i) {
case 1:
msgLen += String("no green bottles, standing on the wall\n\n").length();
break;
case 2:
msgLen += String("1 green bottle, standing on the wall\n\n").length();
break;
default:
numBottles = i-1;
msgLen += numBottles.length();
msgLen += String(" green bottles, standing on the wall\n\n").length();
break;
};
}
// Now we can start to publish the message
client.beginPublish("greenBottles/lyrics", msgLen, false);
for (int i = bottleCount; i > 0; i--) {
for (int j = 0; j < 2; j++) {
client.print(i);
if (i == 1) {
client.print(" green bottle, standing on the wall\n");
} else {
client.print(" green bottles, standing on the wall\n");
}
}
client.print("And if one green bottle should accidentally fall\nThere'll be ");
switch (i) {
case 1:
client.print("no green bottles, standing on the wall\n\n");
break;
case 2:
client.print("1 green bottle, standing on the wall\n\n");
break;
default:
client.print(i-1);
client.print(" green bottles, standing on the wall\n\n");
break;
};
}
// Now we're done!
client.endPublish();
}
}
void reconnect() {
// Loop until we're reconnected
while (!client.connected()) {
Serial.print("Attempting MQTT connection...");
// Create a random client ID
String clientId = "ESP8266Client-";
clientId += String(random(0xffff), HEX);
// Attempt to connect
if (client.connect(clientId.c_str())) {
Serial.println("connected");
// Once connected, publish an announcement...
client.publish("outTopic", "hello world");
// ... and resubscribe
client.subscribe("greenBottles/#");
} else {
Serial.print("failed, rc=");
Serial.print(client.state());
Serial.println(" try again in 5 seconds");
// Wait 5 seconds before retrying
delay(5000);
}
}
}
void setup() {
pinMode(BUILTIN_LED, OUTPUT); // Initialize the BUILTIN_LED pin as an output
Serial.begin(115200);
setup_wifi();
client.setServer(mqtt_server, 1883);
client.setCallback(callback);
}
void loop() {
if (!client.connected()) {
reconnect();
}
client.loop();
}

View File

@ -0,0 +1,60 @@
/*
Publishing in the callback
- connects to an MQTT server
- subscribes to the topic "inTopic"
- when a message is received, republishes it to "outTopic"
This example shows how to publish messages within the
callback function. The callback function header needs to
be declared before the PubSubClient constructor and the
actual callback defined afterwards.
This ensures the client reference in the callback function
is valid.
*/
#include <SPI.h>
#include <Ethernet.h>
#include <PubSubClient.h>
// Update these with values suitable for your network.
byte mac[] = { 0xDE, 0xED, 0xBA, 0xFE, 0xFE, 0xED };
IPAddress ip(172, 16, 0, 100);
IPAddress server(172, 16, 0, 2);
// Callback function header
void callback(char* topic, byte* payload, unsigned int length);
EthernetClient ethClient;
PubSubClient client(server, 1883, callback, ethClient);
// Callback function
void callback(char* topic, byte* payload, unsigned int length) {
// In order to republish this payload, a copy must be made
// as the orignal payload buffer will be overwritten whilst
// constructing the PUBLISH packet.
// Allocate the correct amount of memory for the payload copy
byte* p = (byte*)malloc(length);
// Copy the payload to the new buffer
memcpy(p,payload,length);
client.publish("outTopic", p, length);
// Free the memory
free(p);
}
void setup()
{
Ethernet.begin(mac, ip);
if (client.connect("arduinoClient")) {
client.publish("outTopic","hello world");
client.subscribe("inTopic");
}
}
void loop()
{
client.loop();
}

View File

@ -0,0 +1,67 @@
/*
Reconnecting MQTT example - non-blocking
This sketch demonstrates how to keep the client connected
using a non-blocking reconnect function. If the client loses
its connection, it attempts to reconnect every 5 seconds
without blocking the main loop.
*/
#include <SPI.h>
#include <Ethernet.h>
#include <PubSubClient.h>
// Update these with values suitable for your hardware/network.
byte mac[] = { 0xDE, 0xED, 0xBA, 0xFE, 0xFE, 0xED };
IPAddress ip(172, 16, 0, 100);
IPAddress server(172, 16, 0, 2);
void callback(char* topic, byte* payload, unsigned int length) {
// handle message arrived
}
EthernetClient ethClient;
PubSubClient client(ethClient);
long lastReconnectAttempt = 0;
boolean reconnect() {
if (client.connect("arduinoClient")) {
// Once connected, publish an announcement...
client.publish("outTopic","hello world");
// ... and resubscribe
client.subscribe("inTopic");
}
return client.connected();
}
void setup()
{
client.setServer(server, 1883);
client.setCallback(callback);
Ethernet.begin(mac, ip);
delay(1500);
lastReconnectAttempt = 0;
}
void loop()
{
if (!client.connected()) {
long now = millis();
if (now - lastReconnectAttempt > 5000) {
lastReconnectAttempt = now;
// Attempt to reconnect
if (reconnect()) {
lastReconnectAttempt = 0;
}
}
} else {
// Client connected
client.loop();
}
}

View File

@ -0,0 +1,57 @@
/*
Example of using a Stream object to store the message payload
Uses SRAM library: https://github.com/ennui2342/arduino-sram
but could use any Stream based class such as SD
- connects to an MQTT server
- publishes "hello world" to the topic "outTopic"
- subscribes to the topic "inTopic"
*/
#include <SPI.h>
#include <Ethernet.h>
#include <PubSubClient.h>
#include <SRAM.h>
// Update these with values suitable for your network.
byte mac[] = { 0xDE, 0xED, 0xBA, 0xFE, 0xFE, 0xED };
IPAddress ip(172, 16, 0, 100);
IPAddress server(172, 16, 0, 2);
SRAM sram(4, SRAM_1024);
void callback(char* topic, byte* payload, unsigned int length) {
sram.seek(1);
// do something with the message
for(uint8_t i=0; i<length; i++) {
Serial.write(sram.read());
}
Serial.println();
// Reset position for the next message to be stored
sram.seek(1);
}
EthernetClient ethClient;
PubSubClient client(server, 1883, callback, ethClient, sram);
void setup()
{
Ethernet.begin(mac, ip);
if (client.connect("arduinoClient")) {
client.publish("outTopic","hello world");
client.subscribe("inTopic");
}
sram.begin();
sram.seek(1);
Serial.begin(9600);
}
void loop()
{
client.loop();
}

View File

@ -0,0 +1,36 @@
#######################################
# Syntax Coloring Map For PubSubClient
#######################################
#######################################
# Datatypes (KEYWORD1)
#######################################
PubSubClient KEYWORD1
#######################################
# Methods and Functions (KEYWORD2)
#######################################
connect KEYWORD2
disconnect KEYWORD2
publish KEYWORD2
publish_P KEYWORD2
beginPublish KEYWORD2
endPublish KEYWORD2
write KEYWORD2
subscribe KEYWORD2
unsubscribe KEYWORD2
loop KEYWORD2
connected KEYWORD2
setServer KEYWORD2
setCallback KEYWORD2
setClient KEYWORD2
setStream KEYWORD2
setKeepAlive KEYWORD2
setBufferSize KEYWORD2
setSocketTimeout KEYWORD2
#######################################
# Constants (LITERAL1)
#######################################

View File

@ -0,0 +1,18 @@
{
"name": "PubSubClient",
"keywords": "ethernet, mqtt, m2m, iot",
"description": "A client library for MQTT messaging. MQTT is a lightweight messaging protocol ideal for small devices. This library allows you to send and receive MQTT messages. It supports the latest MQTT 3.1.1 protocol and can be configured to use the older MQTT 3.1 if needed. It supports all Arduino Ethernet Client compatible hardware, including the Intel Galileo/Edison, ESP8266 and TI CC3000.",
"repository": {
"type": "git",
"url": "https://github.com/knolleary/pubsubclient.git"
},
"version": "2.8",
"exclude": "tests",
"examples": "examples/*/*.ino",
"frameworks": "arduino",
"platforms": [
"atmelavr",
"espressif8266",
"espressif32"
]
}

View File

@ -0,0 +1,9 @@
name=PubSubClient
version=2.8
author=Nick O'Leary <nick.oleary@gmail.com>
maintainer=Nick O'Leary <nick.oleary@gmail.com>
sentence=A client library for MQTT messaging.
paragraph=MQTT is a lightweight messaging protocol ideal for small devices. This library allows you to send and receive MQTT messages. It supports the latest MQTT 3.1.1 protocol and can be configured to use the older MQTT 3.1 if needed. It supports all Arduino Ethernet Client compatible hardware, including the Intel Galileo/Edison, ESP8266 and TI CC3000.
category=Communication
url=http://pubsubclient.knolleary.net
architectures=*

View File

@ -0,0 +1,769 @@
/*
PubSubClient.cpp - A simple client for MQTT.
Nick O'Leary
http://knolleary.net
*/
#include "PubSubClient.h"
#include "Arduino.h"
PubSubClient::PubSubClient() {
this->_state = MQTT_DISCONNECTED;
this->_client = NULL;
this->stream = NULL;
setCallback(NULL);
this->bufferSize = 0;
setBufferSize(MQTT_MAX_PACKET_SIZE);
setKeepAlive(MQTT_KEEPALIVE);
setSocketTimeout(MQTT_SOCKET_TIMEOUT);
}
PubSubClient::PubSubClient(Client& client) {
this->_state = MQTT_DISCONNECTED;
setClient(client);
this->stream = NULL;
this->bufferSize = 0;
setBufferSize(MQTT_MAX_PACKET_SIZE);
setKeepAlive(MQTT_KEEPALIVE);
setSocketTimeout(MQTT_SOCKET_TIMEOUT);
}
PubSubClient::PubSubClient(IPAddress addr, uint16_t port, Client& client) {
this->_state = MQTT_DISCONNECTED;
setServer(addr, port);
setClient(client);
this->stream = NULL;
this->bufferSize = 0;
setBufferSize(MQTT_MAX_PACKET_SIZE);
setKeepAlive(MQTT_KEEPALIVE);
setSocketTimeout(MQTT_SOCKET_TIMEOUT);
}
PubSubClient::PubSubClient(IPAddress addr, uint16_t port, Client& client, Stream& stream) {
this->_state = MQTT_DISCONNECTED;
setServer(addr,port);
setClient(client);
setStream(stream);
this->bufferSize = 0;
setBufferSize(MQTT_MAX_PACKET_SIZE);
setKeepAlive(MQTT_KEEPALIVE);
setSocketTimeout(MQTT_SOCKET_TIMEOUT);
}
PubSubClient::PubSubClient(IPAddress addr, uint16_t port, MQTT_CALLBACK_SIGNATURE, Client& client) {
this->_state = MQTT_DISCONNECTED;
setServer(addr, port);
setCallback(callback);
setClient(client);
this->stream = NULL;
this->bufferSize = 0;
setBufferSize(MQTT_MAX_PACKET_SIZE);
setKeepAlive(MQTT_KEEPALIVE);
setSocketTimeout(MQTT_SOCKET_TIMEOUT);
}
PubSubClient::PubSubClient(IPAddress addr, uint16_t port, MQTT_CALLBACK_SIGNATURE, Client& client, Stream& stream) {
this->_state = MQTT_DISCONNECTED;
setServer(addr,port);
setCallback(callback);
setClient(client);
setStream(stream);
this->bufferSize = 0;
setBufferSize(MQTT_MAX_PACKET_SIZE);
setKeepAlive(MQTT_KEEPALIVE);
setSocketTimeout(MQTT_SOCKET_TIMEOUT);
}
PubSubClient::PubSubClient(uint8_t *ip, uint16_t port, Client& client) {
this->_state = MQTT_DISCONNECTED;
setServer(ip, port);
setClient(client);
this->stream = NULL;
this->bufferSize = 0;
setBufferSize(MQTT_MAX_PACKET_SIZE);
setKeepAlive(MQTT_KEEPALIVE);
setSocketTimeout(MQTT_SOCKET_TIMEOUT);
}
PubSubClient::PubSubClient(uint8_t *ip, uint16_t port, Client& client, Stream& stream) {
this->_state = MQTT_DISCONNECTED;
setServer(ip,port);
setClient(client);
setStream(stream);
this->bufferSize = 0;
setBufferSize(MQTT_MAX_PACKET_SIZE);
setKeepAlive(MQTT_KEEPALIVE);
setSocketTimeout(MQTT_SOCKET_TIMEOUT);
}
PubSubClient::PubSubClient(uint8_t *ip, uint16_t port, MQTT_CALLBACK_SIGNATURE, Client& client) {
this->_state = MQTT_DISCONNECTED;
setServer(ip, port);
setCallback(callback);
setClient(client);
this->stream = NULL;
this->bufferSize = 0;
setBufferSize(MQTT_MAX_PACKET_SIZE);
setKeepAlive(MQTT_KEEPALIVE);
setSocketTimeout(MQTT_SOCKET_TIMEOUT);
}
PubSubClient::PubSubClient(uint8_t *ip, uint16_t port, MQTT_CALLBACK_SIGNATURE, Client& client, Stream& stream) {
this->_state = MQTT_DISCONNECTED;
setServer(ip,port);
setCallback(callback);
setClient(client);
setStream(stream);
this->bufferSize = 0;
setBufferSize(MQTT_MAX_PACKET_SIZE);
setKeepAlive(MQTT_KEEPALIVE);
setSocketTimeout(MQTT_SOCKET_TIMEOUT);
}
PubSubClient::PubSubClient(const char* domain, uint16_t port, Client& client) {
this->_state = MQTT_DISCONNECTED;
setServer(domain,port);
setClient(client);
this->stream = NULL;
this->bufferSize = 0;
setBufferSize(MQTT_MAX_PACKET_SIZE);
setKeepAlive(MQTT_KEEPALIVE);
setSocketTimeout(MQTT_SOCKET_TIMEOUT);
}
PubSubClient::PubSubClient(const char* domain, uint16_t port, Client& client, Stream& stream) {
this->_state = MQTT_DISCONNECTED;
setServer(domain,port);
setClient(client);
setStream(stream);
this->bufferSize = 0;
setBufferSize(MQTT_MAX_PACKET_SIZE);
setKeepAlive(MQTT_KEEPALIVE);
setSocketTimeout(MQTT_SOCKET_TIMEOUT);
}
PubSubClient::PubSubClient(const char* domain, uint16_t port, MQTT_CALLBACK_SIGNATURE, Client& client) {
this->_state = MQTT_DISCONNECTED;
setServer(domain,port);
setCallback(callback);
setClient(client);
this->stream = NULL;
this->bufferSize = 0;
setBufferSize(MQTT_MAX_PACKET_SIZE);
setKeepAlive(MQTT_KEEPALIVE);
setSocketTimeout(MQTT_SOCKET_TIMEOUT);
}
PubSubClient::PubSubClient(const char* domain, uint16_t port, MQTT_CALLBACK_SIGNATURE, Client& client, Stream& stream) {
this->_state = MQTT_DISCONNECTED;
setServer(domain,port);
setCallback(callback);
setClient(client);
setStream(stream);
this->bufferSize = 0;
setBufferSize(MQTT_MAX_PACKET_SIZE);
setKeepAlive(MQTT_KEEPALIVE);
setSocketTimeout(MQTT_SOCKET_TIMEOUT);
}
PubSubClient::~PubSubClient() {
free(this->buffer);
}
boolean PubSubClient::connect(const char *id) {
return connect(id,NULL,NULL,0,0,0,0,1);
}
boolean PubSubClient::connect(const char *id, const char *user, const char *pass) {
return connect(id,user,pass,0,0,0,0,1);
}
boolean PubSubClient::connect(const char *id, const char* willTopic, uint8_t willQos, boolean willRetain, const char* willMessage) {
return connect(id,NULL,NULL,willTopic,willQos,willRetain,willMessage,1);
}
boolean PubSubClient::connect(const char *id, const char *user, const char *pass, const char* willTopic, uint8_t willQos, boolean willRetain, const char* willMessage) {
return connect(id,user,pass,willTopic,willQos,willRetain,willMessage,1);
}
boolean PubSubClient::connect(const char *id, const char *user, const char *pass, const char* willTopic, uint8_t willQos, boolean willRetain, const char* willMessage, boolean cleanSession) {
if (!connected()) {
int result = 0;
if(_client->connected()) {
result = 1;
} else {
if (domain != NULL) {
result = _client->connect(this->domain, this->port);
} else {
result = _client->connect(this->ip, this->port);
}
}
if (result == 1) {
nextMsgId = 1;
// Leave room in the buffer for header and variable length field
uint16_t length = MQTT_MAX_HEADER_SIZE;
unsigned int j;
#if MQTT_VERSION == MQTT_VERSION_3_1
uint8_t d[9] = {0x00,0x06,'M','Q','I','s','d','p', MQTT_VERSION};
#define MQTT_HEADER_VERSION_LENGTH 9
#elif MQTT_VERSION == MQTT_VERSION_3_1_1
uint8_t d[7] = {0x00,0x04,'M','Q','T','T',MQTT_VERSION};
#define MQTT_HEADER_VERSION_LENGTH 7
#endif
for (j = 0;j<MQTT_HEADER_VERSION_LENGTH;j++) {
this->buffer[length++] = d[j];
}
uint8_t v;
if (willTopic) {
v = 0x04|(willQos<<3)|(willRetain<<5);
} else {
v = 0x00;
}
if (cleanSession) {
v = v|0x02;
}
if(user != NULL) {
v = v|0x80;
if(pass != NULL) {
v = v|(0x80>>1);
}
}
this->buffer[length++] = v;
this->buffer[length++] = ((this->keepAlive) >> 8);
this->buffer[length++] = ((this->keepAlive) & 0xFF);
CHECK_STRING_LENGTH(length,id)
length = writeString(id,this->buffer,length);
if (willTopic) {
CHECK_STRING_LENGTH(length,willTopic)
length = writeString(willTopic,this->buffer,length);
CHECK_STRING_LENGTH(length,willMessage)
length = writeString(willMessage,this->buffer,length);
}
if(user != NULL) {
CHECK_STRING_LENGTH(length,user)
length = writeString(user,this->buffer,length);
if(pass != NULL) {
CHECK_STRING_LENGTH(length,pass)
length = writeString(pass,this->buffer,length);
}
}
write(MQTTCONNECT,this->buffer,length-MQTT_MAX_HEADER_SIZE);
lastInActivity = lastOutActivity = millis();
while (!_client->available()) {
unsigned long t = millis();
if (t-lastInActivity >= ((int32_t) this->socketTimeout*1000UL)) {
_state = MQTT_CONNECTION_TIMEOUT;
_client->stop();
return false;
}
}
uint8_t llen;
uint32_t len = readPacket(&llen);
if (len == 4) {
if (buffer[3] == 0) {
lastInActivity = millis();
pingOutstanding = false;
_state = MQTT_CONNECTED;
return true;
} else {
_state = buffer[3];
}
}
_client->stop();
} else {
_state = MQTT_CONNECT_FAILED;
}
return false;
}
return true;
}
// reads a byte into result
boolean PubSubClient::readByte(uint8_t * result) {
uint32_t previousMillis = millis();
while(!_client->available()) {
yield();
uint32_t currentMillis = millis();
if(currentMillis - previousMillis >= ((int32_t) this->socketTimeout * 1000)){
return false;
}
}
*result = _client->read();
return true;
}
// reads a byte into result[*index] and increments index
boolean PubSubClient::readByte(uint8_t * result, uint16_t * index){
uint16_t current_index = *index;
uint8_t * write_address = &(result[current_index]);
if(readByte(write_address)){
*index = current_index + 1;
return true;
}
return false;
}
uint32_t PubSubClient::readPacket(uint8_t* lengthLength) {
uint16_t len = 0;
if(!readByte(this->buffer, &len)) return 0;
bool isPublish = (this->buffer[0]&0xF0) == MQTTPUBLISH;
uint32_t multiplier = 1;
uint32_t length = 0;
uint8_t digit = 0;
uint16_t skip = 0;
uint32_t start = 0;
do {
if (len == 5) {
// Invalid remaining length encoding - kill the connection
_state = MQTT_DISCONNECTED;
_client->stop();
return 0;
}
if(!readByte(&digit)) return 0;
this->buffer[len++] = digit;
length += (digit & 127) * multiplier;
multiplier <<=7; //multiplier *= 128
} while ((digit & 128) != 0);
*lengthLength = len-1;
if (isPublish) {
// Read in topic length to calculate bytes to skip over for Stream writing
if(!readByte(this->buffer, &len)) return 0;
if(!readByte(this->buffer, &len)) return 0;
skip = (this->buffer[*lengthLength+1]<<8)+this->buffer[*lengthLength+2];
start = 2;
if (this->buffer[0]&MQTTQOS1) {
// skip message id
skip += 2;
}
}
uint32_t idx = len;
for (uint32_t i = start;i<length;i++) {
if(!readByte(&digit)) return 0;
if (this->stream) {
if (isPublish && idx-*lengthLength-2>skip) {
this->stream->write(digit);
}
}
if (len < this->bufferSize) {
this->buffer[len] = digit;
len++;
}
idx++;
}
if (!this->stream && idx > this->bufferSize) {
len = 0; // This will cause the packet to be ignored.
}
return len;
}
boolean PubSubClient::loop() {
if (connected()) {
unsigned long t = millis();
if ((t - lastInActivity > this->keepAlive*1000UL) || (t - lastOutActivity > this->keepAlive*1000UL)) {
if (pingOutstanding) {
this->_state = MQTT_CONNECTION_TIMEOUT;
_client->stop();
return false;
} else {
this->buffer[0] = MQTTPINGREQ;
this->buffer[1] = 0;
_client->write(this->buffer,2);
lastOutActivity = t;
lastInActivity = t;
pingOutstanding = true;
}
}
if (_client->available()) {
uint8_t llen;
uint16_t len = readPacket(&llen);
uint16_t msgId = 0;
uint8_t *payload;
if (len > 0) {
lastInActivity = t;
uint8_t type = this->buffer[0]&0xF0;
if (type == MQTTPUBLISH) {
if (callback) {
uint16_t tl = (this->buffer[llen+1]<<8)+this->buffer[llen+2]; /* topic length in bytes */
memmove(this->buffer+llen+2,this->buffer+llen+3,tl); /* move topic inside buffer 1 byte to front */
this->buffer[llen+2+tl] = 0; /* end the topic as a 'C' string with \x00 */
char *topic = (char*) this->buffer+llen+2;
// msgId only present for QOS>0
if ((this->buffer[0]&0x06) == MQTTQOS1) {
msgId = (this->buffer[llen+3+tl]<<8)+this->buffer[llen+3+tl+1];
payload = this->buffer+llen+3+tl+2;
callback(topic,payload,len-llen-3-tl-2);
this->buffer[0] = MQTTPUBACK;
this->buffer[1] = 2;
this->buffer[2] = (msgId >> 8);
this->buffer[3] = (msgId & 0xFF);
_client->write(this->buffer,4);
lastOutActivity = t;
} else {
payload = this->buffer+llen+3+tl;
callback(topic,payload,len-llen-3-tl);
}
}
} else if (type == MQTTPINGREQ) {
this->buffer[0] = MQTTPINGRESP;
this->buffer[1] = 0;
_client->write(this->buffer,2);
} else if (type == MQTTPINGRESP) {
pingOutstanding = false;
}
} else if (!connected()) {
// readPacket has closed the connection
return false;
}
}
return true;
}
return false;
}
boolean PubSubClient::publish(const char* topic, const char* payload) {
return publish(topic,(const uint8_t*)payload, payload ? strnlen(payload, this->bufferSize) : 0,false);
}
boolean PubSubClient::publish(const char* topic, const char* payload, boolean retained) {
return publish(topic,(const uint8_t*)payload, payload ? strnlen(payload, this->bufferSize) : 0,retained);
}
boolean PubSubClient::publish(const char* topic, const uint8_t* payload, unsigned int plength) {
return publish(topic, payload, plength, false);
}
boolean PubSubClient::publish(const char* topic, const uint8_t* payload, unsigned int plength, boolean retained) {
if (connected()) {
if (this->bufferSize < MQTT_MAX_HEADER_SIZE + 2+strnlen(topic, this->bufferSize) + plength) {
// Too long
return false;
}
// Leave room in the buffer for header and variable length field
uint16_t length = MQTT_MAX_HEADER_SIZE;
length = writeString(topic,this->buffer,length);
// Add payload
uint16_t i;
for (i=0;i<plength;i++) {
this->buffer[length++] = payload[i];
}
// Write the header
uint8_t header = MQTTPUBLISH;
if (retained) {
header |= 1;
}
return write(header,this->buffer,length-MQTT_MAX_HEADER_SIZE);
}
return false;
}
boolean PubSubClient::publish_P(const char* topic, const char* payload, boolean retained) {
return publish_P(topic, (const uint8_t*)payload, payload ? strnlen(payload, this->bufferSize) : 0, retained);
}
boolean PubSubClient::publish_P(const char* topic, const uint8_t* payload, unsigned int plength, boolean retained) {
uint8_t llen = 0;
uint8_t digit;
unsigned int rc = 0;
uint16_t tlen;
unsigned int pos = 0;
unsigned int i;
uint8_t header;
unsigned int len;
int expectedLength;
if (!connected()) {
return false;
}
tlen = strnlen(topic, this->bufferSize);
header = MQTTPUBLISH;
if (retained) {
header |= 1;
}
this->buffer[pos++] = header;
len = plength + 2 + tlen;
do {
digit = len & 127; //digit = len %128
len >>= 7; //len = len / 128
if (len > 0) {
digit |= 0x80;
}
this->buffer[pos++] = digit;
llen++;
} while(len>0);
pos = writeString(topic,this->buffer,pos);
rc += _client->write(this->buffer,pos);
for (i=0;i<plength;i++) {
rc += _client->write((char)pgm_read_byte_near(payload + i));
}
lastOutActivity = millis();
expectedLength = 1 + llen + 2 + tlen + plength;
return (rc == expectedLength);
}
boolean PubSubClient::beginPublish(const char* topic, unsigned int plength, boolean retained) {
if (connected()) {
// Send the header and variable length field
uint16_t length = MQTT_MAX_HEADER_SIZE;
length = writeString(topic,this->buffer,length);
uint8_t header = MQTTPUBLISH;
if (retained) {
header |= 1;
}
size_t hlen = buildHeader(header, this->buffer, plength+length-MQTT_MAX_HEADER_SIZE);
uint16_t rc = _client->write(this->buffer+(MQTT_MAX_HEADER_SIZE-hlen),length-(MQTT_MAX_HEADER_SIZE-hlen));
lastOutActivity = millis();
return (rc == (length-(MQTT_MAX_HEADER_SIZE-hlen)));
}
return false;
}
int PubSubClient::endPublish() {
return 1;
}
size_t PubSubClient::write(uint8_t data) {
lastOutActivity = millis();
return _client->write(data);
}
size_t PubSubClient::write(const uint8_t *buffer, size_t size) {
lastOutActivity = millis();
return _client->write(buffer,size);
}
size_t PubSubClient::buildHeader(uint8_t header, uint8_t* buf, uint16_t length) {
uint8_t lenBuf[4];
uint8_t llen = 0;
uint8_t digit;
uint8_t pos = 0;
uint16_t len = length;
do {
digit = len & 127; //digit = len %128
len >>= 7; //len = len / 128
if (len > 0) {
digit |= 0x80;
}
lenBuf[pos++] = digit;
llen++;
} while(len>0);
buf[4-llen] = header;
for (int i=0;i<llen;i++) {
buf[MQTT_MAX_HEADER_SIZE-llen+i] = lenBuf[i];
}
return llen+1; // Full header size is variable length bit plus the 1-byte fixed header
}
boolean PubSubClient::write(uint8_t header, uint8_t* buf, uint16_t length) {
uint16_t rc;
uint8_t hlen = buildHeader(header, buf, length);
#ifdef MQTT_MAX_TRANSFER_SIZE
uint8_t* writeBuf = buf+(MQTT_MAX_HEADER_SIZE-hlen);
uint16_t bytesRemaining = length+hlen; //Match the length type
uint8_t bytesToWrite;
boolean result = true;
while((bytesRemaining > 0) && result) {
bytesToWrite = (bytesRemaining > MQTT_MAX_TRANSFER_SIZE)?MQTT_MAX_TRANSFER_SIZE:bytesRemaining;
rc = _client->write(writeBuf,bytesToWrite);
result = (rc == bytesToWrite);
bytesRemaining -= rc;
writeBuf += rc;
}
return result;
#else
rc = _client->write(buf+(MQTT_MAX_HEADER_SIZE-hlen),length+hlen);
lastOutActivity = millis();
return (rc == hlen+length);
#endif
}
boolean PubSubClient::subscribe(const char* topic) {
return subscribe(topic, 0);
}
boolean PubSubClient::subscribe(const char* topic, uint8_t qos) {
size_t topicLength = strnlen(topic, this->bufferSize);
if (topic == 0) {
return false;
}
if (qos > 1) {
return false;
}
if (this->bufferSize < 9 + topicLength) {
// Too long
return false;
}
if (connected()) {
// Leave room in the buffer for header and variable length field
uint16_t length = MQTT_MAX_HEADER_SIZE;
nextMsgId++;
if (nextMsgId == 0) {
nextMsgId = 1;
}
this->buffer[length++] = (nextMsgId >> 8);
this->buffer[length++] = (nextMsgId & 0xFF);
length = writeString((char*)topic, this->buffer,length);
this->buffer[length++] = qos;
return write(MQTTSUBSCRIBE|MQTTQOS1,this->buffer,length-MQTT_MAX_HEADER_SIZE);
}
return false;
}
boolean PubSubClient::unsubscribe(const char* topic) {
size_t topicLength = strnlen(topic, this->bufferSize);
if (topic == 0) {
return false;
}
if (this->bufferSize < 9 + topicLength) {
// Too long
return false;
}
if (connected()) {
uint16_t length = MQTT_MAX_HEADER_SIZE;
nextMsgId++;
if (nextMsgId == 0) {
nextMsgId = 1;
}
this->buffer[length++] = (nextMsgId >> 8);
this->buffer[length++] = (nextMsgId & 0xFF);
length = writeString(topic, this->buffer,length);
return write(MQTTUNSUBSCRIBE|MQTTQOS1,this->buffer,length-MQTT_MAX_HEADER_SIZE);
}
return false;
}
void PubSubClient::disconnect() {
this->buffer[0] = MQTTDISCONNECT;
this->buffer[1] = 0;
_client->write(this->buffer,2);
_state = MQTT_DISCONNECTED;
_client->flush();
_client->stop();
lastInActivity = lastOutActivity = millis();
}
uint16_t PubSubClient::writeString(const char* string, uint8_t* buf, uint16_t pos) {
const char* idp = string;
uint16_t i = 0;
pos += 2;
while (*idp) {
buf[pos++] = *idp++;
i++;
}
buf[pos-i-2] = (i >> 8);
buf[pos-i-1] = (i & 0xFF);
return pos;
}
boolean PubSubClient::connected() {
boolean rc;
if (_client == NULL ) {
rc = false;
} else {
rc = (int)_client->connected();
if (!rc) {
if (this->_state == MQTT_CONNECTED) {
this->_state = MQTT_CONNECTION_LOST;
_client->flush();
_client->stop();
}
} else {
return this->_state == MQTT_CONNECTED;
}
}
return rc;
}
PubSubClient& PubSubClient::setServer(uint8_t * ip, uint16_t port) {
IPAddress addr(ip[0],ip[1],ip[2],ip[3]);
return setServer(addr,port);
}
PubSubClient& PubSubClient::setServer(IPAddress ip, uint16_t port) {
this->ip = ip;
this->port = port;
this->domain = NULL;
return *this;
}
PubSubClient& PubSubClient::setServer(const char * domain, uint16_t port) {
this->domain = domain;
this->port = port;
return *this;
}
PubSubClient& PubSubClient::setCallback(MQTT_CALLBACK_SIGNATURE) {
this->callback = callback;
return *this;
}
PubSubClient& PubSubClient::setClient(Client& client){
this->_client = &client;
return *this;
}
PubSubClient& PubSubClient::setStream(Stream& stream){
this->stream = &stream;
return *this;
}
int PubSubClient::state() {
return this->_state;
}
boolean PubSubClient::setBufferSize(uint16_t size) {
if (size == 0) {
// Cannot set it back to 0
return false;
}
if (this->bufferSize == 0) {
this->buffer = (uint8_t*)malloc(size);
} else {
uint8_t* newBuffer = (uint8_t*)realloc(this->buffer, size);
if (newBuffer != NULL) {
this->buffer = newBuffer;
} else {
return false;
}
}
this->bufferSize = size;
return (this->buffer != NULL);
}
uint16_t PubSubClient::getBufferSize() {
return this->bufferSize;
}
PubSubClient& PubSubClient::setKeepAlive(uint16_t keepAlive) {
this->keepAlive = keepAlive;
return *this;
}
PubSubClient& PubSubClient::setSocketTimeout(uint16_t timeout) {
this->socketTimeout = timeout;
return *this;
}

View File

@ -0,0 +1,184 @@
/*
PubSubClient.h - A simple client for MQTT.
Nick O'Leary
http://knolleary.net
*/
#ifndef PubSubClient_h
#define PubSubClient_h
#include <Arduino.h>
#include "IPAddress.h"
#include "Client.h"
#include "Stream.h"
#define MQTT_VERSION_3_1 3
#define MQTT_VERSION_3_1_1 4
// MQTT_VERSION : Pick the version
//#define MQTT_VERSION MQTT_VERSION_3_1
#ifndef MQTT_VERSION
#define MQTT_VERSION MQTT_VERSION_3_1_1
#endif
// MQTT_MAX_PACKET_SIZE : Maximum packet size. Override with setBufferSize().
#ifndef MQTT_MAX_PACKET_SIZE
#define MQTT_MAX_PACKET_SIZE 256
#endif
// MQTT_KEEPALIVE : keepAlive interval in Seconds. Override with setKeepAlive()
#ifndef MQTT_KEEPALIVE
#define MQTT_KEEPALIVE 15
#endif
// MQTT_SOCKET_TIMEOUT: socket timeout interval in Seconds. Override with setSocketTimeout()
#ifndef MQTT_SOCKET_TIMEOUT
#define MQTT_SOCKET_TIMEOUT 15
#endif
// MQTT_MAX_TRANSFER_SIZE : limit how much data is passed to the network client
// in each write call. Needed for the Arduino Wifi Shield. Leave undefined to
// pass the entire MQTT packet in each write call.
//#define MQTT_MAX_TRANSFER_SIZE 80
// Possible values for client.state()
#define MQTT_CONNECTION_TIMEOUT -4
#define MQTT_CONNECTION_LOST -3
#define MQTT_CONNECT_FAILED -2
#define MQTT_DISCONNECTED -1
#define MQTT_CONNECTED 0
#define MQTT_CONNECT_BAD_PROTOCOL 1
#define MQTT_CONNECT_BAD_CLIENT_ID 2
#define MQTT_CONNECT_UNAVAILABLE 3
#define MQTT_CONNECT_BAD_CREDENTIALS 4
#define MQTT_CONNECT_UNAUTHORIZED 5
#define MQTTCONNECT 1 << 4 // Client request to connect to Server
#define MQTTCONNACK 2 << 4 // Connect Acknowledgment
#define MQTTPUBLISH 3 << 4 // Publish message
#define MQTTPUBACK 4 << 4 // Publish Acknowledgment
#define MQTTPUBREC 5 << 4 // Publish Received (assured delivery part 1)
#define MQTTPUBREL 6 << 4 // Publish Release (assured delivery part 2)
#define MQTTPUBCOMP 7 << 4 // Publish Complete (assured delivery part 3)
#define MQTTSUBSCRIBE 8 << 4 // Client Subscribe request
#define MQTTSUBACK 9 << 4 // Subscribe Acknowledgment
#define MQTTUNSUBSCRIBE 10 << 4 // Client Unsubscribe request
#define MQTTUNSUBACK 11 << 4 // Unsubscribe Acknowledgment
#define MQTTPINGREQ 12 << 4 // PING Request
#define MQTTPINGRESP 13 << 4 // PING Response
#define MQTTDISCONNECT 14 << 4 // Client is Disconnecting
#define MQTTReserved 15 << 4 // Reserved
#define MQTTQOS0 (0 << 1)
#define MQTTQOS1 (1 << 1)
#define MQTTQOS2 (2 << 1)
// Maximum size of fixed header and variable length size header
#define MQTT_MAX_HEADER_SIZE 5
#if defined(ESP8266) || defined(ESP32)
#include <functional>
#define MQTT_CALLBACK_SIGNATURE std::function<void(char*, uint8_t*, unsigned int)> callback
#else
#define MQTT_CALLBACK_SIGNATURE void (*callback)(char*, uint8_t*, unsigned int)
#endif
#define CHECK_STRING_LENGTH(l,s) if (l+2+strnlen(s, this->bufferSize) > this->bufferSize) {_client->stop();return false;}
class PubSubClient : public Print {
private:
Client* _client;
uint8_t* buffer;
uint16_t bufferSize;
uint16_t keepAlive;
uint16_t socketTimeout;
uint16_t nextMsgId;
unsigned long lastOutActivity;
unsigned long lastInActivity;
bool pingOutstanding;
MQTT_CALLBACK_SIGNATURE;
uint32_t readPacket(uint8_t*);
boolean readByte(uint8_t * result);
boolean readByte(uint8_t * result, uint16_t * index);
boolean write(uint8_t header, uint8_t* buf, uint16_t length);
uint16_t writeString(const char* string, uint8_t* buf, uint16_t pos);
// Build up the header ready to send
// Returns the size of the header
// Note: the header is built at the end of the first MQTT_MAX_HEADER_SIZE bytes, so will start
// (MQTT_MAX_HEADER_SIZE - <returned size>) bytes into the buffer
size_t buildHeader(uint8_t header, uint8_t* buf, uint16_t length);
IPAddress ip;
const char* domain;
uint16_t port;
Stream* stream;
int _state;
public:
PubSubClient();
PubSubClient(Client& client);
PubSubClient(IPAddress, uint16_t, Client& client);
PubSubClient(IPAddress, uint16_t, Client& client, Stream&);
PubSubClient(IPAddress, uint16_t, MQTT_CALLBACK_SIGNATURE,Client& client);
PubSubClient(IPAddress, uint16_t, MQTT_CALLBACK_SIGNATURE,Client& client, Stream&);
PubSubClient(uint8_t *, uint16_t, Client& client);
PubSubClient(uint8_t *, uint16_t, Client& client, Stream&);
PubSubClient(uint8_t *, uint16_t, MQTT_CALLBACK_SIGNATURE,Client& client);
PubSubClient(uint8_t *, uint16_t, MQTT_CALLBACK_SIGNATURE,Client& client, Stream&);
PubSubClient(const char*, uint16_t, Client& client);
PubSubClient(const char*, uint16_t, Client& client, Stream&);
PubSubClient(const char*, uint16_t, MQTT_CALLBACK_SIGNATURE,Client& client);
PubSubClient(const char*, uint16_t, MQTT_CALLBACK_SIGNATURE,Client& client, Stream&);
~PubSubClient();
PubSubClient& setServer(IPAddress ip, uint16_t port);
PubSubClient& setServer(uint8_t * ip, uint16_t port);
PubSubClient& setServer(const char * domain, uint16_t port);
PubSubClient& setCallback(MQTT_CALLBACK_SIGNATURE);
PubSubClient& setClient(Client& client);
PubSubClient& setStream(Stream& stream);
PubSubClient& setKeepAlive(uint16_t keepAlive);
PubSubClient& setSocketTimeout(uint16_t timeout);
boolean setBufferSize(uint16_t size);
uint16_t getBufferSize();
boolean connect(const char* id);
boolean connect(const char* id, const char* user, const char* pass);
boolean connect(const char* id, const char* willTopic, uint8_t willQos, boolean willRetain, const char* willMessage);
boolean connect(const char* id, const char* user, const char* pass, const char* willTopic, uint8_t willQos, boolean willRetain, const char* willMessage);
boolean connect(const char* id, const char* user, const char* pass, const char* willTopic, uint8_t willQos, boolean willRetain, const char* willMessage, boolean cleanSession);
void disconnect();
boolean publish(const char* topic, const char* payload);
boolean publish(const char* topic, const char* payload, boolean retained);
boolean publish(const char* topic, const uint8_t * payload, unsigned int plength);
boolean publish(const char* topic, const uint8_t * payload, unsigned int plength, boolean retained);
boolean publish_P(const char* topic, const char* payload, boolean retained);
boolean publish_P(const char* topic, const uint8_t * payload, unsigned int plength, boolean retained);
// Start to publish a message.
// This API:
// beginPublish(...)
// one or more calls to write(...)
// endPublish()
// Allows for arbitrarily large payloads to be sent without them having to be copied into
// a new buffer and held in memory at one time
// Returns 1 if the message was started successfully, 0 if there was an error
boolean beginPublish(const char* topic, unsigned int plength, boolean retained);
// Finish off this publish message (started with beginPublish)
// Returns 1 if the packet was sent successfully, 0 if there was an error
int endPublish();
// Write a single byte of payload (only to be used with beginPublish/endPublish)
virtual size_t write(uint8_t);
// Write size bytes from buffer into the payload (only to be used with beginPublish/endPublish)
// Returns the number of bytes written
virtual size_t write(const uint8_t *buffer, size_t size);
boolean subscribe(const char* topic);
boolean subscribe(const char* topic, uint8_t qos);
boolean unsubscribe(const char* topic);
boolean loop();
boolean connected();
int state();
};
#endif

View File

@ -235,92 +235,168 @@ const BoardDef bsps[_BOARD_MAX] = {
.name = "ONE_INDOOR", .name = "ONE_INDOOR",
}, },
/** OPEN_AIR_OUTDOOR */ /** OPEN_AIR_OUTDOOR */
[OPEN_AIR_OUTDOOR] = { [OPEN_AIR_OUTDOOR] =
.SenseAirS8 = {
{ .SenseAirS8 =
.uart_tx_pin = 1, {
.uart_rx_pin = 0, .uart_tx_pin = 1,
.uart_rx_pin = 0,
#if defined(ESP8266) #if defined(ESP8266)
.supported = false, .supported = false,
#else #else
.supported = true, .supported = true,
#endif #endif
}, },
/** Use UART0 don't use define pin number */ /** Use UART0 don't use define pin number */
.Pms5003 = .Pms5003 =
{ {
.uart_tx_pin = -1, .uart_tx_pin = -1,
.uart_rx_pin = -1, .uart_rx_pin = -1,
#if defined(ESP8266) #if defined(ESP8266)
.supported = false, .supported = false,
#else #else
.supported = true, .supported = true,
#endif #endif
}, },
.I2C = .I2C =
{ {
.sda_pin = 7, .sda_pin = 7,
.scl_pin = 6, .scl_pin = 6,
#if defined(ESP8266) #if defined(ESP8266)
.supported = false, .supported = false,
#else #else
.supported = true, .supported = true,
#endif #endif
}, },
.SW = .SW =
{ {
#if defined(ESP8266) #if defined(ESP8266)
.pin = -1, .pin = -1,
.activeLevel = 1, .activeLevel = 1,
.supported = false, .supported = false,
#else #else
.pin = 9, .pin = 9,
.activeLevel = 0, .activeLevel = 0,
.supported = true, .supported = true,
#endif #endif
}, },
.LED = .LED =
{ {
#if defined(ESP8266) #if defined(ESP8266)
.pin = -1, .pin = -1,
.rgbNum = 0, .rgbNum = 0,
.onState = 0, .onState = 0,
.supported = false, .supported = false,
.rgbSupported = false, .rgbSupported = false,
#else #else
.pin = 10, .pin = 10,
.rgbNum = 0, .rgbNum = 0,
.onState = 1, .onState = 1,
.supported = true, .supported = true,
.rgbSupported = false, .rgbSupported = false,
#endif #endif
}, },
.OLED = .OLED =
{ {
#if defined(ESP8266) #if defined(ESP8266)
.width = 0, .width = 0,
.height = 0, .height = 0,
.addr = 0, .addr = 0,
.supported = false, .supported = false,
#else #else
.width = 128, .width = 128,
.height = 64, .height = 64,
.addr = 0x3C, .addr = 0x3C,
.supported = true, .supported = true,
#endif #endif
}, },
.WDG = .WDG =
{ {
#if defined(ESP8266) #if defined(ESP8266)
.resetPin = -1, .resetPin = -1,
.supported = false, .supported = false,
#else #else
.resetPin = 2, .resetPin = 2,
.supported = true, .supported = true,
#endif #endif
}, },
.name = "OPEN_AIR_OUTDOOR", .name = "OPEN_AIR_OUTDOOR",
}}; },
/** DIY_PRO_INDOOR_V3_3 */
[DIY_PRO_INDOOR_V3_3] =
{
.SenseAirS8 =
{
.uart_tx_pin = 2,
.uart_rx_pin = 0,
#if defined(ESP8266)
.supported = true,
#else
.supported = false,
#endif
},
.Pms5003 =
{
.uart_tx_pin = 14,
.uart_rx_pin = 12,
#if defined(ESP8266)
.supported = true,
#else
.supported = false,
#endif
},
.I2C =
{
.sda_pin = 4,
.scl_pin = 5,
#if defined(ESP8266)
.supported = true,
#else
.supported = false,
#endif
},
.SW =
{
#if defined(ESP8266)
.pin = -1, /** D7 */
.activeLevel = 0,
.supported = false,
#else
.pin = -1,
.activeLevel = 1,
.supported = false,
#endif
},
.LED =
{
.pin = -1,
.rgbNum = 0,
.onState = 0,
.supported = false,
.rgbSupported = false,
},
.OLED =
{
#if defined(ESP8266)
.width = 128,
.height = 64,
.addr = 0x3C,
.supported = true,
#else
.width = 0,
.height = 0,
.addr = 0,
.supported = false,
#endif
},
.WDG =
{
.resetPin = -1,
.supported = false,
},
.name = "DIY_PRO_INDOOR_V3_3",
},
};
/** /**
* @brief Get Board Support Package * @brief Get Board Support Package
@ -337,9 +413,9 @@ const BoardDef *getBoardDef(BoardType def) {
/** /**
* @brief Get the Board Name * @brief Get the Board Name
* *
* @param type BoarType * @param type BoarType
* @return const char* * @return const char*
*/ */
const char *getBoardDefName(BoardType type) { const char *getBoardDefName(BoardType type) {
if (type >= _BOARD_MAX) { if (type >= _BOARD_MAX) {

View File

@ -21,6 +21,7 @@ enum BoardType {
DIY_PRO_INDOOR_V4_2 = 0x01, DIY_PRO_INDOOR_V4_2 = 0x01,
ONE_INDOOR = 0x02, ONE_INDOOR = 0x02,
OPEN_AIR_OUTDOOR = 0x03, OPEN_AIR_OUTDOOR = 0x03,
DIY_PRO_INDOOR_V3_3 = 0x04,
_BOARD_MAX _BOARD_MAX
}; };

View File

@ -64,7 +64,7 @@ void LedBar::setColor(uint8_t red, uint8_t green, uint8_t blue, int ledNum) {
* *
* @param brightness Brightness (0 - 100)% * @param brightness Brightness (0 - 100)%
*/ */
void LedBar::setBrighness(uint8_t brightness) { void LedBar::setBrightness(uint8_t brightness) {
if (this->isBegin() == false) { if (this->isBegin() == false) {
return; return;
} }
@ -116,6 +116,9 @@ void LedBar::setColor(uint8_t red, uint8_t green, uint8_t blue) {
*/ */
void LedBar::show(void) { void LedBar::show(void) {
// Ignore update the LED if LED bar disabled // Ignore update the LED if LED bar disabled
if(this->isBegin() == false) {
return;
}
if (enabled == false) { if (enabled == false) {
return; return;
} }

View File

@ -19,7 +19,7 @@ public:
void begin(void); void begin(void);
void setColor(uint8_t red, uint8_t green, uint8_t blue, int ledNum); void setColor(uint8_t red, uint8_t green, uint8_t blue, int ledNum);
void setColor(uint8_t red, uint8_t green, uint8_t blue); void setColor(uint8_t red, uint8_t green, uint8_t blue);
void setBrighness(uint8_t brightness); void setBrightness(uint8_t brightness);
int getNumberOfLeds(void); int getNumberOfLeds(void);
void show(void); void show(void);
void clear(void); void clear(void);

89
src/Main/utils.cpp Normal file
View File

@ -0,0 +1,89 @@
#include "utils.h"
#define VALID_TEMPERATURE_MAX (125)
#define VALID_TEMPERATURE_MIN (-40)
#define INVALID_TEMPERATURE (-1000)
#define VALID_HUMIDITY_MAX (100)
#define VALID_HUMIDITY_MIN (0)
#define INVALID_HUMIDITY (-1)
#define VALID_PMS_MAX (1000)
#define VALID_PMS_MIN (0)
#define INVALID_PMS (-1)
#define VALID_PMS03COUNT_MIN (0)
#define VALID_CO2_MAX (10000)
#define VALID_CO2_MIN (0)
#define INVALID_CO2 (-1)
#define VALID_NOX_MIN (0)
#define VALID_VOC_MIN (0)
#define INVALID_NOX (-1)
#define INVALID_VOC (-1)
utils::utils(/* args */) {}
utils::~utils() {}
bool utils::isValidTemperature(float value) {
if ((value >= VALID_TEMPERATURE_MIN) && (value <= VALID_TEMPERATURE_MAX)) {
return true;
}
return false;
}
bool utils::isValidHumidity(float value) {
if ((value >= VALID_HUMIDITY_MIN) && (value <= VALID_HUMIDITY_MAX)) {
return true;
}
return false;
}
bool utils::isValidCO2(int16_t value) {
if ((value >= VALID_CO2_MIN) && (value <= VALID_CO2_MAX)) {
return true;
}
return false;
}
bool utils::isValidPm(int value) {
if ((value >= VALID_PMS_MIN) && (value <= VALID_PMS_MAX)) {
return true;
}
return false;
}
bool utils::isValidPm03Count(int value) {
if (value >= VALID_PMS03COUNT_MIN) {
return true;
}
return false;
}
bool utils::isValidNOx(int value) {
if (value >= VALID_NOX_MIN) {
return true;
}
return false;
}
bool utils::isValidVOC(int value) {
if (value >= VALID_VOC_MIN) {
return true;
}
return false;
}
float utils::getInvalidTemperature(void) { return INVALID_TEMPERATURE; }
float utils::getInvalidHumidity(void) { return INVALID_HUMIDITY; }
int utils::getInvalidCO2(void) { return INVALID_CO2; }
int utils::getInvalidPmValue(void) { return INVALID_PMS; }
int utils::getInvalidNOx(void) { return INVALID_NOX; }
int utils::getInvalidVOC(void) { return INVALID_VOC; }

30
src/Main/utils.h Normal file
View File

@ -0,0 +1,30 @@
#ifndef _UTILS_H_
#define _UTILS_H_
#include <Arduino.h>
class utils
{
private:
/* data */
public:
utils(/* args */);
~utils();
static bool isValidTemperature(float value);
static bool isValidHumidity(float value);
static bool isValidCO2(int16_t value);
static bool isValidPm(int value);
static bool isValidPm03Count(int value);
static bool isValidNOx(int value);
static bool isValidVOC(int value);
static float getInvalidTemperature(void);
static float getInvalidHumidity(void);
static int getInvalidCO2(void);
static int getInvalidPmValue(void);
static int getInvalidNOx(void);
static int getInvalidVOC(void);
};
#endif /** _UTILS_H_ */

View File

@ -1,11 +1,19 @@
#ifdef ESP32
#include "MqttClient.h" #include "MqttClient.h"
#include "Libraries/pubsubclient-2.8/src/PubSubClient.h"
#ifdef ESP32
static void __mqtt_event_handler(void *handler_args, esp_event_base_t base, static void __mqtt_event_handler(void *handler_args, esp_event_base_t base,
int32_t event_id, void *event_data); int32_t event_id, void *event_data);
#else
#define CLIENT() ((PubSubClient *)client)
#endif
MqttClient::MqttClient(Stream &debugLog) : PrintLog(debugLog, "MqttClient") {} MqttClient::MqttClient(Stream &debugLog) : PrintLog(debugLog, "MqttClient") {
#ifdef ESP32
#else
client = NULL;
#endif
}
MqttClient::~MqttClient() {} MqttClient::~MqttClient() {}
@ -22,6 +30,7 @@ bool MqttClient::begin(String uri) {
this->uri = uri; this->uri = uri;
logInfo("Init uri: " + uri); logInfo("Init uri: " + uri);
#ifdef ESP32
/** config esp_mqtt client */ /** config esp_mqtt client */
esp_mqtt_client_config_t config = { esp_mqtt_client_config_t config = {
.uri = this->uri.c_str(), .uri = this->uri.c_str(),
@ -45,6 +54,108 @@ bool MqttClient::begin(String uri) {
logError("Client start failed"); logError("Client start failed");
return false; return false;
} }
#else
// mqtt://<Username>:<Password>@<Host>:<Port>
bool hasUser = false;
for (unsigned int i = 0; i < this->uri.length(); i++) {
if (this->uri[i] == '@') {
hasUser = true;
break;
}
}
user = "";
password = "";
server = "";
port = 0;
char *serverPort = NULL;
char *buf = (char *)this->uri.c_str();
if (hasUser) {
// mqtt://<Username>:<Password>@<Host>:<Port>
char *userPass = strtok(buf, "@");
serverPort = strtok(NULL, "@");
if (userPass == NULL) {
logError("User and Password invalid");
return false;
} else {
if ((userPass[5] == '/') && (userPass[6] == '/')) { /** Check mqtt:// */
userPass = &userPass[7];
} else if ((userPass[6] == '/') &&
(userPass[7] == '/')) { /** Check mqtts:// */
userPass = &userPass[8];
} else {
logError("Server invalid");
return false;
}
buf = strtok(userPass, ":");
if (buf == NULL) {
logError("User invalid");
return false;
}
user = String(buf);
buf = strtok(NULL, "@");
if (buf == NULL) {
logError("Password invalid");
return false;
}
password = String(buf);
logInfo("Username: " + user);
logInfo("Password: " + password);
}
if (serverPort == NULL) {
logError("Server and port invalid");
return false;
}
} else {
// mqtt://<Host>:<Port>
if ((buf[5] == '/') && (buf[6] == '/')) { /** Check mqtt:// */
serverPort = &buf[7];
} else if ((buf[6] == '/') && (buf[7] == '/')) { /** Check mqtts:// */
serverPort = &buf[8];
} else {
logError("Server invalid");
return false;
}
}
if (serverPort == NULL) {
logError("Server and port invalid");
return false;
}
buf = strtok(serverPort, ":");
if (buf == NULL) {
logError("Server invalid");
return false;
}
server = String(buf);
logInfo("Server: " + server);
buf = strtok(NULL, ":");
if (buf == NULL) {
logError("Port invalid");
return false;
}
port = (uint16_t)String(buf).toInt();
logInfo("Port: " + String(port));
if (client == NULL) {
client = new PubSubClient(__wifiClient);
if (client == NULL) {
return false;
}
}
CLIENT()->setServer(server.c_str(), port);
CLIENT()->setBufferSize(1024);
connected = false;
#endif
isBegin = true; isBegin = true;
connectionFailedCount = 0; connectionFailedCount = 0;
@ -56,12 +167,16 @@ void MqttClient::end(void) {
logWarning("Already end, call 'begin' and try again"); logWarning("Already end, call 'begin' and try again");
return; return;
} }
#ifdef ESP32
esp_mqtt_client_disconnect(client); esp_mqtt_client_disconnect(client);
esp_mqtt_client_stop(client); esp_mqtt_client_stop(client);
esp_mqtt_client_destroy(client); esp_mqtt_client_destroy(client);
client = NULL; client = NULL;
#else
CLIENT()->disconnect();
#endif
isBegin = false; isBegin = false;
this->uri = "";
logInfo("end"); logInfo("end");
} }
@ -86,10 +201,17 @@ bool MqttClient::publish(const char *topic, const char *payload, int len) {
return false; return false;
} }
#ifdef ESP32
if (esp_mqtt_client_publish(client, topic, payload, len, 0, 0) == ESP_OK) { if (esp_mqtt_client_publish(client, topic, payload, len, 0, 0) == ESP_OK) {
logInfo("Publish success"); logInfo("Publish success");
return true; return true;
} }
#else
if (CLIENT()->publish(topic, payload)) {
logInfo("Publish success");
return true;
}
#endif
logError("Publish failed"); logError("Publish failed");
return false; return false;
} }
@ -114,7 +236,9 @@ bool MqttClient::isCurrentUri(String &uri) {
* @return true Connected * @return true Connected
* @return false Disconnected * @return false Disconnected
*/ */
bool MqttClient::isConnected(void) { return connected; } bool MqttClient::isConnected(void) {
return connected;
}
/** /**
* @brief Get number of connection failed * @brief Get number of connection failed
@ -123,6 +247,35 @@ bool MqttClient::isConnected(void) { return connected; }
*/ */
int MqttClient::getConnectionFailedCount(void) { return connectionFailedCount; } int MqttClient::getConnectionFailedCount(void) { return connectionFailedCount; }
#ifdef ESP8266
bool MqttClient::connect(String id) {
if (isBegin == false) {
return false;
}
if (this->uri.isEmpty()) {
return false;
}
connected = false;
if (user.isEmpty()) {
logInfo("Connect without auth");
if(CLIENT()->connect(id.c_str())) {
connected = true;
}
return connected;
}
return CLIENT()->connect(id.c_str(), user.c_str(), password.c_str());
}
void MqttClient::handle(void) {
if (isBegin == false) {
return;
}
CLIENT()->loop();
}
#endif
#ifdef ESP32
static void __mqtt_event_handler(void *handler_args, esp_event_base_t base, static void __mqtt_event_handler(void *handler_args, esp_event_base_t base,
int32_t event_id, void *event_data) { int32_t event_id, void *event_data) {
MqttClient *mqtt = (MqttClient *)handler_args; MqttClient *mqtt = (MqttClient *)handler_args;
@ -164,5 +317,4 @@ static void __mqtt_event_handler(void *handler_args, esp_event_base_t base,
break; break;
} }
} }
#endif
#endif /** ESP32 */

View File

@ -2,8 +2,10 @@
#define _AG_MQTT_CLIENT_H_ #define _AG_MQTT_CLIENT_H_
#ifdef ESP32 #ifdef ESP32
#include "mqtt_client.h" #include "mqtt_client.h"
#else
#include <WiFiClient.h>
#endif /** ESP32 */
#include "Main/PrintLog.h" #include "Main/PrintLog.h"
#include <Arduino.h> #include <Arduino.h>
@ -11,7 +13,16 @@ class MqttClient: public PrintLog {
private: private:
bool isBegin = false; bool isBegin = false;
String uri; String uri;
#ifdef ESP32
esp_mqtt_client_handle_t client; esp_mqtt_client_handle_t client;
#else
WiFiClient __wifiClient;
void* client;
String password;
String user;
String server;
uint16_t port;
#endif
bool connected = false; bool connected = false;
int connectionFailedCount = 0; int connectionFailedCount = 0;
@ -26,8 +37,10 @@ public:
bool isCurrentUri(String &uri); bool isCurrentUri(String &uri);
bool isConnected(void); bool isConnected(void);
int getConnectionFailedCount(void); int getConnectionFailedCount(void);
#ifdef ESP8266
bool connect(String id);
void handle(void);
#endif
}; };
#endif /** ESP32 */
#endif /** _AG_MQTT_CLIENT_H_ */ #endif /** _AG_MQTT_CLIENT_H_ */

View File

@ -2,33 +2,48 @@
#include "../Main/BoardDef.h" #include "../Main/BoardDef.h"
/** /**
* @brief Init and check that sensor has connected * @brief Initializes the sensor and attempts to read data.
* *
* @param stream UART stream * @param stream UART stream
* @return true Sucecss * @return true Sucecss
* @return false Failure * @return false Failure
*/ */
bool PMSBase::begin(Stream *stream) { bool PMSBase::begin(Stream *stream) {
Serial.printf("initializing PM sensor\n");
this->stream = stream; this->stream = stream;
failed = true; failed = true;
failCount = 0;
lastRead = 0; // To read buffer on handle without wait after 1.5sec lastRead = 0; // To read buffer on handle without wait after 1.5sec
this->stream->flush(); // empty first
int bytesCleared = 0;
while (this->stream->read() != -1) {
bytesCleared++;
}
Serial.printf("cleared %d byte(s)\n", bytesCleared);
// explicitly put the sensor into active mode, this seems to be be needed for the Cubic PM2009X
Serial.printf("setting active mode\n");
uint8_t activeModeCommand[] = { 0x42, 0x4D, 0xE1, 0x00, 0x01, 0x01, 0x71 };
size_t bytesWritten = this->stream->write(activeModeCommand, sizeof(activeModeCommand));
Serial.printf("%d byte(s) written\n", bytesWritten);
// Run and check sensor data for 4sec // Run and check sensor data for 4sec
while (1) { while (1) {
handle(); handle();
if (failed == false) { if (failed == false) {
return true; Serial.printf("PM sensor initialized\n");
return true;
} }
delay(1); delay(10);
uint32_t ms = (uint32_t)(millis() - lastRead); uint32_t ms = (uint32_t)(millis() - lastRead);
if (ms >= 4000) { if (ms >= 4000) {
break; break;
} }
} }
Serial.printf("PM sensor initialization failed\n");
return false; return false;
} }
@ -86,7 +101,7 @@ void PMSBase::handle() {
case 2: { case 2: {
buf[bufIndex++] = value; buf[bufIndex++] = value;
if (bufIndex >= 4) { if (bufIndex >= 4) {
len = toValue(&buf[2]); len = toI16(&buf[2]);
if (len != 28) { if (len != 28) {
// Serial.printf("Got good bad len %d\r\n", len); // Serial.printf("Got good bad len %d\r\n", len);
len += 4; len += 4;
@ -147,103 +162,138 @@ void PMSBase::handle() {
*/ */
bool PMSBase::isFailed(void) { return failed; } bool PMSBase::isFailed(void) { return failed; }
/**
* @brief Increate number of fail
*
*/
void PMSBase::updateFailCount(void) {
if (failCount < failCountMax) {
failCount++;
}
}
void PMSBase::resetFailCount(void) { failCount = 0; }
/**
* @brief Get number of fail
*
* @return int
*/
int PMSBase::getFailCount(void) { return failCount; }
int PMSBase::getFailCountMax(void) { return failCountMax; }
/** /**
* @brief Read PMS 0.1 ug/m3 with CF = 1 PM estimates * @brief Read PMS 0.1 ug/m3 with CF = 1 PM estimates
* *
* @return uint16_t * @return uint16_t
*/ */
uint16_t PMSBase::getRaw0_1(void) { return toValue(&package[4]); } uint16_t PMSBase::getRaw0_1(void) { return toU16(&package[4]); }
/** /**
* @brief Read PMS 2.5 ug/m3 with CF = 1 PM estimates * @brief Read PMS 2.5 ug/m3 with CF = 1 PM estimates
* *
* @return uint16_t * @return uint16_t
*/ */
uint16_t PMSBase::getRaw2_5(void) { return toValue(&package[6]); } uint16_t PMSBase::getRaw2_5(void) { return toU16(&package[6]); }
/** /**
* @brief Read PMS 10 ug/m3 with CF = 1 PM estimates * @brief Read PMS 10 ug/m3 with CF = 1 PM estimates
* *
* @return uint16_t * @return uint16_t
*/ */
uint16_t PMSBase::getRaw10(void) { return toValue(&package[8]); } uint16_t PMSBase::getRaw10(void) { return toU16(&package[8]); }
/** /**
* @brief Read PMS 0.1 ug/m3 * @brief Read PMS 0.1 ug/m3
* *
* @return uint16_t * @return uint16_t
*/ */
uint16_t PMSBase::getPM0_1(void) { return toValue(&package[10]); } uint16_t PMSBase::getPM0_1(void) { return toU16(&package[10]); }
/** /**
* @brief Read PMS 2.5 ug/m3 * @brief Read PMS 2.5 ug/m3
* *
* @return uint16_t * @return uint16_t
*/ */
uint16_t PMSBase::getPM2_5(void) { return toValue(&package[12]); } uint16_t PMSBase::getPM2_5(void) { return toU16(&package[12]); }
/** /**
* @brief Read PMS 10 ug/m3 * @brief Read PMS 10 ug/m3
* *
* @return uint16_t * @return uint16_t
*/ */
uint16_t PMSBase::getPM10(void) { return toValue(&package[14]); } uint16_t PMSBase::getPM10(void) { return toU16(&package[14]); }
/** /**
* @brief Get numnber concentrations over 0.3 um/0.1L * @brief Get numnber concentrations over 0.3 um/0.1L
* *
* @return uint16_t * @return uint16_t
*/ */
uint16_t PMSBase::getCount0_3(void) { return toValue(&package[16]); } uint16_t PMSBase::getCount0_3(void) { return toU16(&package[16]); }
/** /**
* @brief Get numnber concentrations over 0.5 um/0.1L * @brief Get numnber concentrations over 0.5 um/0.1L
* *
* @return uint16_t * @return uint16_t
*/ */
uint16_t PMSBase::getCount0_5(void) { return toValue(&package[18]); } uint16_t PMSBase::getCount0_5(void) { return toU16(&package[18]); }
/** /**
* @brief Get numnber concentrations over 1.0 um/0.1L * @brief Get numnber concentrations over 1.0 um/0.1L
* *
* @return uint16_t * @return uint16_t
*/ */
uint16_t PMSBase::getCount1_0(void) { return toValue(&package[20]); } uint16_t PMSBase::getCount1_0(void) { return toU16(&package[20]); }
/** /**
* @brief Get numnber concentrations over 2.5 um/0.1L * @brief Get numnber concentrations over 2.5 um/0.1L
* *
* @return uint16_t * @return uint16_t
*/ */
uint16_t PMSBase::getCount2_5(void) { return toValue(&package[22]); } uint16_t PMSBase::getCount2_5(void) { return toU16(&package[22]); }
/** /**
* @brief Get numnber concentrations over 5.0 um/0.1L (only PMS5003) * @brief Get numnber concentrations over 5.0 um/0.1L (only PMS5003)
* *
* @return uint16_t * @return uint16_t
*/ */
uint16_t PMSBase::getCount5_0(void) { return toValue(&package[24]); } uint16_t PMSBase::getCount5_0(void) { return toU16(&package[24]); }
/** /**
* @brief Get numnber concentrations over 10.0 um/0.1L (only PMS5003) * @brief Get numnber concentrations over 10.0 um/0.1L (only PMS5003)
* *
* @return uint16_t * @return uint16_t
*/ */
uint16_t PMSBase::getCount10(void) { return toValue(&package[26]); } uint16_t PMSBase::getCount10(void) { return toU16(&package[26]); }
/** /**
* @brief Get temperature (only PMS5003T) * @brief Get temperature (only PMS5003T)
* *
* @return uint16_t * @return uint16_t
*/ */
uint16_t PMSBase::getTemp(void) { return toValue(&package[24]); } int16_t PMSBase::getTemp(void) { return toI16(&package[24]); }
/** /**
* @brief Get humidity (only PMS5003T) * @brief Get humidity (only PMS5003T)
* *
* @return uint16_t * @return uint16_t
*/ */
uint16_t PMSBase::getHum(void) { return toValue(&package[26]); } uint16_t PMSBase::getHum(void) { return toU16(&package[26]); }
/**
* @brief Get firmware version code
*
* @return uint8_t
*/
uint8_t PMSBase::getFirmwareVersion(void) { return package[28]; }
/**
* @brief Ge PMS5003 error code
*
* @return uint8_t
*/
uint8_t PMSBase::getErrorCode(void) { return package[29]; }
/** /**
* @brief Convert PMS2.5 to US AQI unit * @brief Convert PMS2.5 to US AQI unit
@ -270,13 +320,61 @@ int PMSBase::pm25ToAQI(int pm02) {
return 500; return 500;
} }
/**
* @brief Correction PM2.5
*
* Formula: https://www.airgradient.com/documentation/correction-algorithms/
*
* @param pm25 Raw PM2.5 value
* @param humidity Humidity value (%)
* @return int
*/
int PMSBase::compensate(int pm25, float humidity) {
float value;
float fpm25 = pm25;
if (humidity < 0) {
humidity = 0;
}
if (humidity > 100) {
humidity = 100.0f;
}
if(pm25 < 30) { /** pm2.5 < 30 */
value = (fpm25 * 0.524f) - (humidity * 0.0862f) + 5.75f;
} else if(pm25 < 50) { /** 30 <= pm2.5 < 50 */
value = (0.786f * (fpm25 * 0.05f - 1.5f) + 0.524f * (1.0f - (fpm25 * 0.05f - 1.5f))) * fpm25 - (0.0862f * humidity) + 5.75f;
} else if(pm25 < 210) { /** 50 <= pm2.5 < 210 */
value = (0.786f * fpm25) - (0.0862f * humidity) + 5.75f;
} else if(pm25 < 260) { /** 210 <= pm2.5 < 260 */
value = (0.69f * (fpm25 * 0.02f - 4.2f) + 0.786f * (1.0f - (fpm25 * 0.02f - 4.2f))) * fpm25 - (0.0862f * humidity * (1.0f - (fpm25 * 0.02f - 4.2f))) + (2.966f * (fpm25 * 0.02f - 4.2f)) + (5.75f * (1.0f - (fpm25 * 0.02f - 4.2f))) + (8.84f * (1.e-4) * fpm25 * fpm25 * (fpm25 * 0.02f - 4.2f));
} else { /** 260 <= pm2.5 */
value = 2.966f + (0.69f * fpm25) + (8.84f * (1.e-4) * fpm25 * fpm25);
}
if(value < 0) {
value = 0;
}
return (int)value;
}
/** /**
* @brief Convert two byte value to uint16_t value * @brief Convert two byte value to uint16_t value
* *
* @param buf bytes array (must be >= 2) * @param buf bytes array (must be >= 2)
* @return uint16_t * @return int16_t
*/ */
uint16_t PMSBase::toValue(char *buf) { return (buf[0] << 8) | buf[1]; } int16_t PMSBase::toI16(char *buf) {
int16_t value = buf[0];
value = (value << 8) | buf[1];
return value;
}
uint16_t PMSBase::toU16(char *buf) {
uint16_t value = buf[0];
value = (value << 8) | buf[1];
return value;
}
/** /**
* @brief Validate package data * @brief Validate package data
@ -290,7 +388,7 @@ bool PMSBase::validate(char *buf) {
for (int i = 0; i < 30; i++) { for (int i = 0; i < 30; i++) {
sum += buf[i]; sum += buf[i];
} }
if (sum == toValue(&buf[30])) { if (sum == toU16(&buf[30])) {
for (int i = 0; i < 32; i++) { for (int i = 0; i < 32; i++) {
package[i] = buf[i]; package[i] = buf[i];
} }

View File

@ -3,11 +3,20 @@
#include <Arduino.h> #include <Arduino.h>
#define PMS_FAIL_COUNT_SET_INVALID 3
/**
* Known to work with these sensors: Plantower PMS5003, Plantower PMS5003, Cubic PM2009X
*/
class PMSBase { class PMSBase {
public: public:
bool begin(Stream *stream); bool begin(Stream *stream);
void handle(); void handle();
bool isFailed(void); bool isFailed(void);
void updateFailCount(void);
void resetFailCount(void);
int getFailCount(void);
int getFailCountMax(void);
uint16_t getRaw0_1(void); uint16_t getRaw0_1(void);
uint16_t getRaw2_5(void); uint16_t getRaw2_5(void);
uint16_t getRaw10(void); uint16_t getRaw10(void);
@ -24,10 +33,13 @@ public:
uint16_t getCount10(void); uint16_t getCount10(void);
/** For PMS5003T*/ /** For PMS5003T*/
uint16_t getTemp(void); int16_t getTemp(void);
uint16_t getHum(void); uint16_t getHum(void);
uint8_t getFirmwareVersion(void);
uint8_t getErrorCode(void);
int pm25ToAQI(int pm02); int pm25ToAQI(int pm02);
int compensate(int pm25, float humidity);
private: private:
Stream *stream; Stream *stream;
@ -35,8 +47,11 @@ private:
int packageIndex; int packageIndex;
bool failed = false; bool failed = false;
uint32_t lastRead; uint32_t lastRead;
const int failCountMax = 10;
int failCount = 0;
uint16_t toValue(char *buf); int16_t toI16(char *buf);
uint16_t toU16(char* buf);
bool validate(char *buf); bool validate(char *buf);
}; };

View File

@ -1,5 +1,6 @@
#include "PMS5003.h" #include "PMS5003.h"
#include "Arduino.h" #include "Arduino.h"
#include "../Main/utils.h"
#if defined(ESP8266) #if defined(ESP8266)
#include <SoftwareSerial.h> #include <SoftwareSerial.h>
@ -37,14 +38,11 @@ bool PMS5003::begin(HardwareSerial &serial) {
PMS5003::PMS5003(BoardType def) : _boardDef(def) {} PMS5003::PMS5003(BoardType def) : _boardDef(def) {}
/** /**
* @brief Init sensor * Initializes the sensor.
*
* @return true Success
* @return false Failure
*/ */
bool PMS5003::begin(void) { bool PMS5003::begin(void) {
if (this->_isBegin) { if (this->_isBegin) {
AgLog("Initialized, call end() then try again"); AgLog("Already initialized, call end() then try again");
return true; return true;
} }
@ -77,7 +75,7 @@ bool PMS5003::begin(void) {
return false; return false;
} }
#endif #endif
_ver = pms.getFirmwareVersion();
this->_isBegin = true; this->_isBegin = true;
return true; return true;
} }
@ -108,7 +106,9 @@ int PMS5003::getPm10Ae(void) { return pms.getPM10(); }
* *
* @return int PM0.3 index * @return int PM0.3 index
*/ */
int PMS5003::getPm03ParticleCount(void) { return pms.getCount0_3(); } int PMS5003::getPm03ParticleCount(void) {
return pms.getCount0_3();
}
/** /**
* @brief Convert PM2.5 to US AQI * @brief Convert PM2.5 to US AQI
@ -118,6 +118,33 @@ int PMS5003::getPm03ParticleCount(void) { return pms.getCount0_3(); }
*/ */
int PMS5003::convertPm25ToUsAqi(int pm25) { return pms.pm25ToAQI(pm25); } int PMS5003::convertPm25ToUsAqi(int pm25) { return pms.pm25ToAQI(pm25); }
/**
* @brief Correct PM2.5
*
* Reference formula: https://www.airgradient.com/documentation/correction-algorithms/
*
* @param pm25 PM2.5 raw value
* @param humidity Humidity value
* @return int
*/
int PMS5003::compensate(int pm25, float humidity) {
return pms.compensate(pm25, humidity);
}
/**
* @brief Get sensor firmware version
*
* @return int
*/
int PMS5003::getFirmwareVersion(void) { return _ver; }
/**
* @brief Get sensor error code
*
* @return uint8_t
*/
uint8_t PMS5003::getErrorCode(void) { return pms.getErrorCode(); }
/** /**
* @brief Check device initialized or not * @brief Check device initialized or not
* *
@ -161,3 +188,25 @@ void PMS5003::handle(void) { pms.handle(); }
* @return false Communication timeout or sensor has removed * @return false Communication timeout or sensor has removed
*/ */
bool PMS5003::isFailed(void) { return pms.isFailed(); } bool PMS5003::isFailed(void) { return pms.isFailed(); }
void PMS5003::updateFailCount(void) {
pms.updateFailCount();
}
void PMS5003::resetFailCount(void) {
pms.resetFailCount();
}
/**
* @brief Get number of fail count
*
* @return int
*/
int PMS5003::getFailCount(void) { return pms.getFailCount(); }
/**
* @brief Get number of fail count max
*
* @return int
*/
int PMS5003::getFailCountMax(void) { return pms.getFailCountMax(); }

View File

@ -19,14 +19,22 @@ public:
void end(void); void end(void);
void handle(void); void handle(void);
bool isFailed(void); bool isFailed(void);
void updateFailCount(void);
void resetFailCount(void);
int getFailCount(void);
int getFailCountMax(void);
int getPm01Ae(void); int getPm01Ae(void);
int getPm25Ae(void); int getPm25Ae(void);
int getPm10Ae(void); int getPm10Ae(void);
int getPm03ParticleCount(void); int getPm03ParticleCount(void);
int convertPm25ToUsAqi(int pm25); int convertPm25ToUsAqi(int pm25);
int compensate(int pm25, float humidity);
int getFirmwareVersion(void);
uint8_t getErrorCode(void);
private: private:
bool _isBegin = false; bool _isBegin = false;
int _ver;
BoardType _boardDef; BoardType _boardDef;
PMSBase pms; PMSBase pms;
const BoardDef *bsp; const BoardDef *bsp;

View File

@ -1,5 +1,6 @@
#include "PMS5003T.h" #include "PMS5003T.h"
#include "Arduino.h" #include "Arduino.h"
#include "../Main/utils.h"
#if defined(ESP8266) #if defined(ESP8266)
#include <SoftwareSerial.h> #include <SoftwareSerial.h>
@ -102,7 +103,7 @@ bool PMS5003T::begin(void) {
return false; return false;
} }
#endif #endif
_ver = pms.getFirmwareVersion();
this->_isBegin = true; this->_isBegin = true;
return true; return true;
} }
@ -133,7 +134,9 @@ int PMS5003T::getPm10Ae(void) { return pms.getPM10(); }
* *
* @return int PM 0.3 Count index * @return int PM 0.3 Count index
*/ */
int PMS5003T::getPm03ParticleCount(void) { return pms.getCount0_3(); } int PMS5003T::getPm03ParticleCount(void) {
return pms.getCount0_3();
}
/** /**
* @brief Convert PM2.5 to US AQI * @brief Convert PM2.5 to US AQI
@ -149,7 +152,7 @@ int PMS5003T::convertPm25ToUsAqi(int pm25) { return pms.pm25ToAQI(pm25); }
* @return float Degree Celcius * @return float Degree Celcius
*/ */
float PMS5003T::getTemperature(void) { float PMS5003T::getTemperature(void) {
return pms.getTemp()/10.0f; return pms.getTemp() / 10.0f;
} }
/** /**
@ -158,9 +161,36 @@ float PMS5003T::getTemperature(void) {
* @return float Percent (%) * @return float Percent (%)
*/ */
float PMS5003T::getRelativeHumidity(void) { float PMS5003T::getRelativeHumidity(void) {
return pms.getHum()/10.0f; return pms.getHum() / 10.0f;
} }
/**
* @brief Correct PM2.5
*
* Reference formula: https://www.airgradient.com/documentation/correction-algorithms/
*
* @param pm25 PM2.5 raw value
* @param humidity Humidity value
* @return int
*/
int PMS5003T::compensate(int pm25, float humidity) {
return pms.compensate(pm25, humidity);
}
/**
* @brief Get module(s) firmware version
*
* @return int Version code
*/
int PMS5003T::getFirmwareVersion(void) { return _ver; }
/**
* @brief Get sensor error code
*
* @return uint8_t
*/
uint8_t PMS5003T::getErrorCode(void) { return pms.getErrorCode(); }
/** /**
* @brief Check device initialized or not * @brief Check device initialized or not
* *
@ -202,3 +232,24 @@ void PMS5003T::handle(void) { pms.handle(); }
*/ */
bool PMS5003T::isFailed(void) { return pms.isFailed(); } bool PMS5003T::isFailed(void) { return pms.isFailed(); }
void PMS5003T::updateFailCount(void) {
pms.updateFailCount();
}
void PMS5003T::resetFailCount(void) {
pms.resetFailCount();
}
/**
* @brief Get fail count
*
* @return int
*/
int PMS5003T::getFailCount(void) { return pms.getFailCount(); }
/**
* @brief Get fail count max
*
* @return int
*/
int PMS5003T::getFailCountMax(void) { return pms.getFailCountMax(); }

View File

@ -22,6 +22,10 @@ public:
void handle(void); void handle(void);
bool isFailed(void); bool isFailed(void);
void updateFailCount(void);
void resetFailCount(void);
int getFailCount(void);
int getFailCountMax(void);
int getPm01Ae(void); int getPm01Ae(void);
int getPm25Ae(void); int getPm25Ae(void);
int getPm10Ae(void); int getPm10Ae(void);
@ -29,10 +33,14 @@ public:
int convertPm25ToUsAqi(int pm25); int convertPm25ToUsAqi(int pm25);
float getTemperature(void); float getTemperature(void);
float getRelativeHumidity(void); float getRelativeHumidity(void);
int compensate(int pm25, float humidity);
int getFirmwareVersion(void);
uint8_t getErrorCode(void);
private: private:
bool _isBegin = false; bool _isBegin = false;
bool _isSleep = false; bool _isSleep = false;
int _ver; /** Firmware version code */
BoardType _boardDef; BoardType _boardDef;
const BoardDef *bsp; const BoardDef *bsp;

View File

@ -4,14 +4,30 @@ PMS5003TBase::PMS5003TBase() {}
PMS5003TBase::~PMS5003TBase() {} PMS5003TBase::~PMS5003TBase() {}
float PMS5003TBase::temperatureCompensated(float temp) { /**
* @brief Compensate the temperature
*
* Reference formula: https://www.airgradient.com/documentation/correction-algorithms/
*
* @param temp
* @return * float
*/
float PMS5003TBase::compensateTemp(float temp) {
if (temp < 10.0f) { if (temp < 10.0f) {
return temp * 1.327f - 6.738f; return temp * 1.327f - 6.738f;
} }
return temp * 1.181f - 5.113f; return temp * 1.181f - 5.113f;
} }
float PMS5003TBase::humidityCompensated(float hum) { /**
* @brief Compensate the humidity
*
* Reference formula: https://www.airgradient.com/documentation/correction-algorithms/
*
* @param temp
* @return * float
*/
float PMS5003TBase::compensateHum(float hum) {
hum = hum * 1.259f + 7.34f; hum = hum * 1.259f + 7.34f;
if (hum > 100.0f) { if (hum > 100.0f) {

View File

@ -8,8 +8,8 @@ private:
public: public:
PMS5003TBase(); PMS5003TBase();
~PMS5003TBase(); ~PMS5003TBase();
float temperatureCompensated(float temp); float compensateTemp(float temp);
float humidityCompensated(float hum); float compensateHum(float hum);
}; };
#endif #endif

View File

@ -1,5 +1,6 @@
#include "S8.h" #include "S8.h"
#include "mb_crc.h" #include "mb_crc.h"
#include "../Main/utils.h"
#if defined(ESP8266) #if defined(ESP8266)
#include <SoftwareSerial.h> #include <SoftwareSerial.h>
#else #else
@ -103,7 +104,7 @@ void S8::getFirmwareVersion(char firmver[]) {
*/ */
int32_t S8::getSensorTypeId(void) { int32_t S8::getSensorTypeId(void) {
if (this->isBegin() == false) { if (this->isBegin() == false) {
return -1; return utils::getInvalidCO2();
} }
int32_t sensorType = 0; int32_t sensorType = 0;

View File

@ -2,6 +2,7 @@
#include "../Libraries/SensirionSGP41/src/SensirionI2CSgp41.h" #include "../Libraries/SensirionSGP41/src/SensirionI2CSgp41.h"
#include "../Libraries/Sensirion_Gas_Index_Algorithm/src/NOxGasIndexAlgorithm.h" #include "../Libraries/Sensirion_Gas_Index_Algorithm/src/NOxGasIndexAlgorithm.h"
#include "../Libraries/Sensirion_Gas_Index_Algorithm/src/VOCGasIndexAlgorithm.h" #include "../Libraries/Sensirion_Gas_Index_Algorithm/src/VOCGasIndexAlgorithm.h"
#include "../Main/utils.h"
#define sgpSensor() ((SensirionI2CSgp41 *)(this->_sensor)) #define sgpSensor() ((SensirionI2CSgp41 *)(this->_sensor))
#define vocAlgorithm() ((VOCGasIndexAlgorithm *)(this->_vocAlgorithm)) #define vocAlgorithm() ((VOCGasIndexAlgorithm *)(this->_vocAlgorithm))
@ -66,6 +67,7 @@ bool Sgp41::begin(TwoWire &wire) {
} }
onConditioning = true; onConditioning = true;
_handleFailCount = 0;
#ifdef ESP32 #ifdef ESP32
/** Create task */ /** Create task */
xTaskCreate( xTaskCreate(
@ -104,9 +106,25 @@ void Sgp41::handle(void) {
} else { } else {
uint16_t srawVoc, srawNox; uint16_t srawVoc, srawNox;
if (getRawSignal(srawVoc, srawNox)) { if (getRawSignal(srawVoc, srawNox)) {
tvocRaw = srawVoc;
noxRaw = srawNox;
nox = noxAlgorithm()->process(srawNox); nox = noxAlgorithm()->process(srawNox);
tvoc = vocAlgorithm()->process(srawVoc); tvoc = vocAlgorithm()->process(srawVoc);
_handleFailCount = 0;
// AgLog("Polling SGP41 success: tvoc: %d, nox: %d", tvoc, nox); // AgLog("Polling SGP41 success: tvoc: %d, nox: %d", tvoc, nox);
} else {
if(_handleFailCount < 5) {
_handleFailCount++;
AgLog("Polling SGP41 failed: %d", _handleFailCount);
}
if (_handleFailCount >= 5) {
tvocRaw = utils::getInvalidVOC();
tvoc = utils::getInvalidVOC();
noxRaw = utils::getInvalidNOx();
nox = utils::getInvalidNOx();
}
} }
} }
} }
@ -139,7 +157,21 @@ void Sgp41::_handle(void) {
noxRaw = srawNox; noxRaw = srawNox;
nox = noxAlgorithm()->process(srawNox); nox = noxAlgorithm()->process(srawNox);
tvoc = vocAlgorithm()->process(srawVoc); tvoc = vocAlgorithm()->process(srawVoc);
_handleFailCount = 0;
// AgLog("Polling SGP41 success: tvoc: %d, nox: %d", tvoc, nox); // AgLog("Polling SGP41 success: tvoc: %d, nox: %d", tvoc, nox);
} else {
if(_handleFailCount < 5) {
_handleFailCount++;
AgLog("Polling SGP41 failed: %d", _handleFailCount);
}
if (_handleFailCount >= 5) {
tvocRaw = utils::getInvalidVOC();
tvoc = utils::getInvalidVOC();
noxRaw = utils::getInvalidNOx();
nox = utils::getInvalidNOx();
}
} }
} }
} }
@ -174,7 +206,7 @@ void Sgp41::end(void) {
*/ */
int Sgp41::getTvocIndex(void) { int Sgp41::getTvocIndex(void) {
if (onConditioning) { if (onConditioning) {
return -1; return utils::getInvalidVOC();
} }
return tvoc; return tvoc;
} }
@ -186,7 +218,7 @@ int Sgp41::getTvocIndex(void) {
*/ */
int Sgp41::getNoxIndex(void) { int Sgp41::getNoxIndex(void) {
if (onConditioning) { if (onConditioning) {
return -1; return utils::getInvalidNOx();
} }
return nox; return nox;
} }

View File

@ -35,6 +35,7 @@ private:
bool onConditioning = true; bool onConditioning = true;
bool ready = false; bool ready = false;
bool _isBegin = false; bool _isBegin = false;
uint8_t _handleFailCount = 0;
void *_sensor; void *_sensor;
void *_vocAlgorithm; void *_vocAlgorithm;
void *_noxAlgorithm; void *_noxAlgorithm;

View File

@ -1,6 +1,7 @@
#include "Sht.h" #include "Sht.h"
#include "../Libraries/arduino-sht/SHTSensor.h" #include "../Libraries/arduino-sht/SHTSensor.h"
#include "../Main/utils.h"
/** Cast _sensor to SHTSensor */ /** Cast _sensor to SHTSensor */
#define shtSensor() ((SHTSensor *)(this->_sensor)) #define shtSensor() ((SHTSensor *)(this->_sensor))
@ -131,14 +132,18 @@ void Sht::end(void) {
* *
* @return float * @return float
*/ */
float Sht::getTemperature(void) { return shtSensor()->getTemperature(); } float Sht::getTemperature(void) {
return shtSensor()->getTemperature();
}
/** /**
* @brief Get humidity * @brief Get humidity
* *
* @return float * @return float
*/ */
float Sht::getRelativeHumidity(void) { return shtSensor()->getHumidity(); } float Sht::getRelativeHumidity(void) {
return shtSensor()->getHumidity();
}
/** /**
* @brief Measure temperature and humidity * @brief Measure temperature and humidity