mirror of
https://github.com/airgradienthq/arduino.git
synced 2025-06-26 08:11:33 +02:00
Compare commits
273 Commits
3.1.0-beta
...
3.1.7
Author | SHA1 | Date | |
---|---|---|---|
28096e9faf | |||
682378a47c | |||
a1861be7b7 | |||
99ddd24432 | |||
29491e4cbe | |||
87cc3fc45f | |||
7471d8079a | |||
8b0fe967f1 | |||
6f1cef4e67 | |||
02b63ff816 | |||
228bf83e92 | |||
d3534cda52 | |||
aafaa42a68 | |||
2e9ff0d7dd | |||
244b7814a6 | |||
28d27ee8fd | |||
753f22923c | |||
c45901706f | |||
663836e277 | |||
d39e10908d | |||
c52962d628 | |||
6b65efd3d6 | |||
8bb87a75ef | |||
1afcca25a1 | |||
17238cff86 | |||
03e2afbf54 | |||
104d58a8c0 | |||
7a988ea6c1 | |||
54ed83cb89 | |||
e461b92c9f | |||
db21648e91 | |||
a9654506f5 | |||
63f653d5cd | |||
b049a23657 | |||
d6766ef68b | |||
6c3259b94b | |||
b1aaa04421 | |||
2df78e9066 | |||
186f0d27ab | |||
e25aa87ecc | |||
1cc8941a5c | |||
9bf1495be7 | |||
73089b51f5 | |||
625e60a5bf | |||
88e3d0bd3f | |||
171821cfcf | |||
900a2da2ac | |||
fb57a112c9 | |||
ab69b686ec | |||
6746d25dc2 | |||
be150e105a | |||
ecadeeb156 | |||
219ff73132 | |||
0a9142204d | |||
81b13134d2 | |||
f3a9c722b2 | |||
3be3218115 | |||
5edb21cfe9 | |||
6cd587b008 | |||
6d01366887 | |||
1a347e9cfe | |||
6432e4451e | |||
97f0696002 | |||
e46e11c030 | |||
dc261f668d | |||
b5cced40d2 | |||
040bd28038 | |||
b0ae851427 | |||
01943f594d | |||
01a69668cc | |||
ed7b8df6fe | |||
6c1c914716 | |||
6a0d88ff10 | |||
9097eed137 | |||
c9b5e5f0d7 | |||
c12bac4ce3 | |||
9ae9b2ac9c | |||
7fb3e68b6d | |||
cf65a1f901 | |||
5fb27b6d1e | |||
7b9dac756b | |||
4b2a5f5540 | |||
4af77d532e | |||
812c2ab803 | |||
0ece16f434 | |||
df6cca3714 | |||
c8aa07ae20 | |||
a1d216ac77 | |||
a9d9c60dfa | |||
e58ce1cbea | |||
64827223ec | |||
bddd4fef25 | |||
0eba54fd28 | |||
a4176f966a | |||
2de54ca6a6 | |||
bfd20a73da | |||
3ebce4ac44 | |||
f08c8edd19 | |||
6b3e8e3096 | |||
beb17de7dc | |||
bccadd17d7 | |||
863b1f908e | |||
5a2c1bd1d0 | |||
dbc63194e6 | |||
57c33e4900 | |||
48f1a8042a | |||
fbd5779fe6 | |||
45a4d98267 | |||
0119a4d62a | |||
7560251deb | |||
31655b0a4f | |||
7d15c37ad9 | |||
411beda220 | |||
0e869d1c0c | |||
f25f816428 | |||
c8745e123b | |||
5ea01b8e9f | |||
a11eaea532 | |||
3b6859f483 | |||
bd237ed95d | |||
d24b20a734 | |||
227a4f76f7 | |||
adabb9baa4 | |||
935e7f365f | |||
d4ea03f39e | |||
89aefdda43 | |||
8a83e408d3 | |||
7a1a0337d1 | |||
6bdd4cb02f | |||
38bd758b69 | |||
6aa19ea3e6 | |||
da323b1a46 | |||
2ae90444bb | |||
21e802da33 | |||
e0869fbaf0 | |||
70662091ec | |||
960d2bad64 | |||
7abf7f5e6a | |||
6bad4fd04b | |||
a17f18b3db | |||
3da4900462 | |||
d2d81f6b4b | |||
0b12f56513 | |||
f7e811e34b | |||
037bb37184 | |||
fde510ba96 | |||
14b152a2a7 | |||
ef6b041529 | |||
11ecea1493 | |||
3e2e8b15eb | |||
4249a86fd5 | |||
4a58b0b1c7 | |||
5f93329e96 | |||
59d189e1d3 | |||
2e62abe2d7 | |||
7569b114bf | |||
3c1d0a862f | |||
4d883af77e | |||
e9224a5de0 | |||
af0fbadd80 | |||
79f6c040c7 | |||
f262866148 | |||
78a2a78020 | |||
65e759fb90 | |||
3fc7e4b55e | |||
5857388c2d | |||
5770b41fd4 | |||
d85d890878 | |||
9fbd31d0c8 | |||
c5b7c43293 | |||
7550ef7b0c | |||
805546b78e | |||
59880f4be5 | |||
ee7837a471 | |||
ebbf0adf2f | |||
d9551dc560 | |||
6ea0ab9272 | |||
6e54409512 | |||
f35bc4feaa | |||
c04ab90fd2 | |||
ed02f66ca2 | |||
4b94926651 | |||
f505b39247 | |||
9e44cd89d9 | |||
e7b95c0bde | |||
b72394b004 | |||
1544989fe6 | |||
d7b5e999c1 | |||
9b0210f7b5 | |||
3715266f8c | |||
a4eb607174 | |||
f55fa6a617 | |||
4f9f800cce | |||
cad5d1f0e7 | |||
55ede2b04d | |||
563bdfe4b2 | |||
e2154af85f | |||
9a2bbd7a57 | |||
a8c8246632 | |||
a71c038864 | |||
799217e724 | |||
b3f02f0a58 | |||
c640cf773e | |||
c145666fcb | |||
466bb0eb21 | |||
4612f4b793 | |||
45c7279866 | |||
5cb838af29 | |||
3201fd8d9c | |||
f23c7e9e31 | |||
5b18a8353d | |||
1e81c9b125 | |||
3dae4cb06d | |||
b4745ef55d | |||
348cb2663a | |||
1b69e8a599 | |||
d17ad3cbab | |||
a6d8936ea6 | |||
8b428855b0 | |||
22dc2136e4 | |||
f23f81f575 | |||
7a4255b2bb | |||
d844ab09fa | |||
31c60dcbec | |||
f0749783fe | |||
d34605a018 | |||
1bcb9bf5ee | |||
296bf49e5e | |||
e3dee42b4b | |||
279ccb8bfb | |||
a3c9727b02 | |||
1b886d9843 | |||
066e81b186 | |||
c98d078d4c | |||
cb98183e20 | |||
0e1734b35d | |||
da6326db0f | |||
ad1da129c0 | |||
8a90fe511b | |||
cca1ab69bb | |||
955172d3d3 | |||
4500f41ed3 | |||
7c2f8e5b9b | |||
fb84b53077 | |||
3359b1817b | |||
8eb8d4a1ec | |||
d2723de0f8 | |||
4493156739 | |||
0acb7d470d | |||
8428442dea | |||
f32d6b1bbe | |||
46600f59a3 | |||
01c42387ed | |||
f08438db46 | |||
221730160b | |||
d40b1d37a8 | |||
ecd3aa988f | |||
095787d1f2 | |||
d94d074abe | |||
cdef9822b3 | |||
29c1989e78 | |||
bc3872e631 | |||
7a182ebb12 | |||
5e6f801534 | |||
88808a2ad2 | |||
a4fc954712 | |||
1580cd51aa | |||
3efba24e24 | |||
940dd0167c | |||
6b4f86e7e4 | |||
e5a0c6bc7b | |||
b8fdb38db8 | |||
b2c55c38dc |
8
.github/workflows/check.yml
vendored
8
.github/workflows/check.yml
vendored
@ -6,6 +6,8 @@ jobs:
|
||||
matrix:
|
||||
example:
|
||||
- "BASIC"
|
||||
- "DiyProIndoorV4_2"
|
||||
- "DiyProIndoorV3_3"
|
||||
- "TestCO2"
|
||||
- "TestPM"
|
||||
- "TestSht"
|
||||
@ -18,11 +20,15 @@ jobs:
|
||||
core: "esp8266:esp8266@3.1.2"
|
||||
core_url: "https://arduino.esp8266.com/stable/package_esp8266com_index.json"
|
||||
- fqbn: "esp32:esp32:esp32c3"
|
||||
board_options: "JTAGAdapter=default,CDCOnBoot=cdc,PartitionScheme=default,CPUFreq=160,FlashMode=qio,FlashFreq=80,FlashSize=4M,UploadSpeed=921600,DebugLevel=verbose,EraseFlash=none"
|
||||
board_options: "JTAGAdapter=default,CDCOnBoot=cdc,PartitionScheme=min_spiffs,CPUFreq=160,FlashMode=qio,FlashFreq=80,FlashSize=4M,UploadSpeed=921600,DebugLevel=verbose,EraseFlash=none"
|
||||
core: "esp32:esp32@2.0.11"
|
||||
exclude:
|
||||
- example: "BASIC"
|
||||
fqbn: "esp32:esp32:esp32c3"
|
||||
- example: "DiyProIndoorV4_2"
|
||||
fqbn: "esp32:esp32:esp32c3"
|
||||
- example: "DiyProIndoorV3_3"
|
||||
fqbn: "esp32:esp32:esp32c3"
|
||||
- example: "OneOpenAir"
|
||||
fqbn: "esp8266:esp8266:d1_mini"
|
||||
runs-on: ubuntu-latest
|
||||
|
@ -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)
|
||||
- [WiFiManager](https://github.com/tzapu/WiFiManager)
|
||||
- [Arduino_JSON](https://github.com/arduino-libraries/Arduino_JSON)
|
||||
- [PubSubClient](https://github.com/knolleary/pubsubclient)
|
||||
|
||||
## License
|
||||
CC BY-SA 4.0 Attribution-ShareAlike 4.0 International License
|
||||
|
@ -17,62 +17,77 @@ With the path "/measures/current" you can get the current air quality data.
|
||||
|
||||
http://airgradient_ecda3b1eaaaf.local/measures/current
|
||||
|
||||
“ecda3b1eaaaf” being the serial number of your monitor
|
||||
“ecda3b1eaaaf” being the serial number of your monitor.
|
||||
|
||||
You get the following response:
|
||||
~~~
|
||||
{"wifi":-46,
|
||||
"serialno":"ecda3b1eaaaf",
|
||||
"rco2":447,
|
||||
"pm01":3,
|
||||
"pm02":7,
|
||||
"pm10":8,
|
||||
"pm003Count":442,
|
||||
"atmp":25.87,
|
||||
"rhum":43,
|
||||
"tvocIndex":100,
|
||||
"tvoc_raw":33051,
|
||||
"noxIndex":1,
|
||||
"nox_raw":16307,
|
||||
"boot":6,
|
||||
"ledMode":"pm",
|
||||
"firmwareVersion":"3.0.10beta",
|
||||
"fwMode":"I-9PSL"}
|
||||
~~~
|
||||
```json
|
||||
{
|
||||
"wifi": -46,
|
||||
"serialno": "ecda3b1eaaaf",
|
||||
"rco2": 447,
|
||||
"pm01": 3,
|
||||
"pm02": 7,
|
||||
"pm10": 8,
|
||||
"pm003Count": 442,
|
||||
"atmp": 25.87,
|
||||
"atmpCompensated": 24.47,
|
||||
"rhum": 43,
|
||||
"rhumCompensated": 49,
|
||||
"tvocIndex": 100,
|
||||
"tvocRaw": 33051,
|
||||
"noxIndex": 1,
|
||||
"noxRaw": 16307,
|
||||
"boot": 6,
|
||||
"bootCount": 6,
|
||||
"ledMode": "pm",
|
||||
"firmware": "3.1.3",
|
||||
"model": "I-9PSL"
|
||||
}
|
||||
```
|
||||
|
||||
|Properties|Type|Explanation|
|
||||
|-|-|-|
|
||||
|serialno|String| Serial Number of the monitor|
|
||||
|wifi|Number| WiFi signal strength|
|
||||
|pm01, pm02, pm10|Number| PM1, PM2.5 and PM10 in ug/m3|
|
||||
|rco2|Number| CO2 in ppm|
|
||||
|pm003Count|Number| Particle count per dL|
|
||||
|atmp|Number| Temperature in Degrees Celcius|
|
||||
|rhum|Number| Relative Humidity|
|
||||
|tvocIndex|Number| Senisiron VOC Index|
|
||||
|tvoc_raw|Number| VOC raw value|
|
||||
|noxIndex|Number| Senisirion NOx Index|
|
||||
|nox_raw|Number| NOx raw value|
|
||||
|boot|Number| Counts every measurement cycle. Low boot counts indicate restarts.|
|
||||
|ledMode|String| Current configuration of the LED mode|
|
||||
|firmwareVersion|String| Current firmware version|
|
||||
|fwMode|String| Current model name|
|
||||
| Properties | Type | Explanation |
|
||||
|------------------|--------|--------------------------------------------------------------------|
|
||||
| `serialno` | String | Serial Number of the monitor |
|
||||
| `wifi` | Number | WiFi signal strength |
|
||||
| `pm01` | Number | PM1 in ug/m3 |
|
||||
| `pm02` | Number | PM2.5 in ug/m3 |
|
||||
| `pm10` | Number | PM10 in ug/m3 |
|
||||
| `pm02Compensated` | Number | PM2.5 in ug/m3 with correction applied (from fw version 3.1.4 onwards) |
|
||||
| `rco2` | Number | CO2 in ppm |
|
||||
| `pm003Count` | Number | Particle count per dL |
|
||||
| `atmp` | Number | Temperature in Degrees Celsius |
|
||||
| `atmpCompensated` | Number | Temperature in Degrees Celsius with correction applied |
|
||||
| `rhum` | Number | Relative Humidity |
|
||||
| `rhumCompensated` | Number | Relative Humidity with correction applied |
|
||||
| `tvocIndex` | Number | Senisiron VOC Index |
|
||||
| `tvocRaw` | Number | VOC raw value |
|
||||
| `noxIndex` | Number | Senisirion NOx Index |
|
||||
| `noxRaw` | Number | NOx raw value |
|
||||
| `boot` | Number | Counts every measurement cycle. Low boot counts indicate restarts. |
|
||||
| `bootCount` | Number | Same as boot property. Required for Home Assistant compatability. 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)
|
||||
With the path "/config" you can get the current configuration.
|
||||
~~~
|
||||
{"country":"US",
|
||||
"pmStandard":"ugm3",
|
||||
"ledBarMode":"pm",
|
||||
"displayMode":"on",
|
||||
"abcDays":30,
|
||||
"tvocLearningOffset":12,
|
||||
"noxLearningOffset":12,
|
||||
"mqttBrokerUrl":"",
|
||||
"temperatureUnit":"f",
|
||||
"configurationControl":"both",
|
||||
"postDataToAirGradient":true}
|
||||
~~~
|
||||
```json
|
||||
{
|
||||
"country": "US",
|
||||
"pmStandard": "ugm3",
|
||||
"ledBarMode": "pm",
|
||||
"displayMode": "on",
|
||||
"abcDays": 30,
|
||||
"tvocLearningOffset": 12,
|
||||
"noxLearningOffset": 12,
|
||||
"mqttBrokerUrl": "",
|
||||
"temperatureUnit": "f",
|
||||
"configurationControl": "both",
|
||||
"postDataToAirGradient": true
|
||||
}
|
||||
```
|
||||
|
||||
#### 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 ```
|
||||
|
||||
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 ```
|
||||
|
||||
If you use command prompt on Windows, you need to escape the quotes:
|
||||
|
||||
``` -d "{\"param\":\"value\"}" ```
|
||||
|
||||
#### Avoiding Conflicts with Configuration on AirGradient Server
|
||||
If the monitor is 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)
|
||||
|
||||
|Properties|Type|Accepted Values|Example|
|
||||
|-|-|-|-|
|
||||
|country|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"}|
|
||||
|ledBarMode|String|co2: LED bar displays CO2 <br> pm: LED bar displays PM <br> off: Turn off LED bar | {"ledBarMode": "off"}|
|
||||
|abcDays|Number|Number of days for CO2 automatic baseline balibration. Maximum 200 days. Default 8 days. | {"abcDays": 8}|
|
||||
|mqttBrokerUrl|String|MQTT broker URL. | {"mqttBrokerUrl":"mqtt://192.168.0.18:1883"} |
|
||||
|temperatureUnit|String|c or C: Degree Celsius °C <br>f or F: Degree Fahrenheit °F | {"temperatureUnit": "c"}|
|
||||
|configurationControl|String|both : Accept local and cloud configuration <br>local : Accept only local configuration <br>cloud : Accept only cloud configuration | {"configurationControl": "both"}|
|
||||
|postDataToAirGradient|Boolean|Send data to AirGradient cloud: <br>true : Enabled <br>false: Disabled | {"postDataToAirGradient": true}|
|
||||
|co2CalibrationRequested|Boolean|Trigger CO2 calibration (400ppm) on monitor:<br>true : Calibration will be triggered | {"co2CalibrationRequested": true}|
|
||||
|ledBarTestRequested|Boolean|Test LED bar:<br> true : LEDs will run test sequence | {"ledBarTestRequested": true}|
|
||||
| Properties | Description | Type | Accepted Values | Example |
|
||||
|-------------------------|:-------------------------------------------------------|---------|-----------------------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------|
|
||||
| `country` | Country where the device is. | String | Country code as [ALPHA-2 notation](https://www.iban.com/country-codes) | {"country": "TH"} |
|
||||
| `model` | Hardware identifier (only GET). | String | I-9PSL-DE | {"model": "I-9PSL-DE"} |
|
||||
| `pmStandard` | Particle matter standard used on the display. | String | `ugm3`: ug/m3 <br> `us-aqi`: USAQI | {"pmStandard": "ugm3"} |
|
||||
| `ledBarMode` | Mode in which the led bar can be set. | String | `co2`: LED bar displays CO2 <br>`pm`: LED bar displays PM <br>`off`: Turn off LED bar | {"ledBarMode": "off"} |
|
||||
| `displayBrightness` | Brightness of the Display. | Number | 0-100 | {"displayBrightness": 50} |
|
||||
| `ledBarBrightness` | Brightness of the LEDBar. | Number | 0-100 | {"ledBarBrightness": 40} |
|
||||
| `abcDays` | Number of days for CO2 automatic baseline calibration. | Number | Maximum 200 days. Default 8 days. | {"abcDays": 8} |
|
||||
| `mqttBrokerUrl` | MQTT broker URL. | String | | {"mqttBrokerUrl": "mqtt://192.168.0.18:1883"} |
|
||||
| `temperatureUnit` | Temperature unit shown on the display. | String | `c` or `C`: Degree Celsius °C <br>`f` or `F`: Degree Fahrenheit °F | {"temperatureUnit": "c"} |
|
||||
| `configurationControl` | The configuration source of the device. | String | `both`: Accept local and cloud configuration <br>`local`: Accept only local configuration <br>`cloud`: Accept only cloud configuration | {"configurationControl": "both"} |
|
||||
| `postDataToAirGradient` | Send data to AirGradient cloud. | Boolean | `true`: Enabled <br>`false`: Disabled | {"postDataToAirGradient": true} |
|
||||
| `co2CalibrationRequested` | Can be set to trigger a calibration. | Boolean | `true`: CO2 calibration (400ppm) will be triggered | {"co2CalibrationRequested": true} |
|
||||
| `ledBarTestRequested` | Can be set to trigger a test. | Boolean | `true` : LEDs will run test sequence | {"ledBarTestRequested": true} |
|
||||
| `noxLearningOffset` | Set NOx learning gain offset. | Number | 0-720 (default 12) | {"noxLearningOffset": 12} |
|
||||
| `tvocLearningOffset` | Set VOC learning gain offset. | Number | 0-720 (default 12) | {"tvocLearningOffset": 12} |
|
||||
| `offlineMode` | Set monitor to run without WiFi. | Boolean | `false`: Disabled (default) <br> `true`: Enabled | {"offlineMode": true} |
|
||||
|
22
docs/ota-updates.md
Normal file
22
docs/ota-updates.md
Normal file
@ -0,0 +1,22 @@
|
||||
## OTA Updates
|
||||
|
||||
From [firmware version 3.1.1](https://github.com/airgradienthq/arduino/tree/3.1.1) onwards, the AirGradient ONE and Open Air monitors support over the air (OTA) updates.
|
||||
|
||||
#### Mechanism
|
||||
|
||||
Upon compilation of an official release the git tag (GIT_VERSION) is compiled into the binary.
|
||||
|
||||
The device attempts to update to the latest version on startup and in regular intervals using URL
|
||||
|
||||
http://hw.airgradient.com/sensors/{deviceId}/generic/os/firmware.bin?current_firmware={GIT_VERSION}
|
||||
|
||||
If does pass the version it is currently running on along to the server through URL parameter 'current_firmware'.
|
||||
This allows the server to identify if the device is already running on the latest version or should update.
|
||||
|
||||
The following scenarios are possible
|
||||
|
||||
1. The device is already on the latest firmware. Then the server returns a 304 with a short explanation text in the body saying this.
|
||||
2. The device reports a firmware unknown to the server. A 400 with an empty payload is returned in this case and the update is not performed. This case is relevant for local changes. The GIT_VERSION then defaults to "snapshot" which is unknown to the server.
|
||||
3. There is an update available. A 200 along with the binary data of the new version is returned and the update is performed.
|
||||
|
||||
More information about the implementation details are available here: https://docs.espressif.com/projects/esp-idf/en/stable/esp32/api-reference/system/ota.html
|
@ -31,187 +31,377 @@ CC BY-SA 4.0 Attribution-ShareAlike 4.0 International License
|
||||
#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 WIFI_CONNECT_COUNTDOWN_MAX 180 /** sec */
|
||||
#define WIFI_CONNECT_RETRY_MS 10000 /** ms */
|
||||
#define LED_BAR_COUNT_INIT_VALUE (-1) /** */
|
||||
#define LED_BAR_ANIMATION_PERIOD 100 /** ms */
|
||||
#define DISP_UPDATE_INTERVAL 5000 /** ms */
|
||||
#define SERVER_CONFIG_UPDATE_INTERVAL 30000 /** 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 5000 /** ms */
|
||||
#define SENSOR_PM_UPDATE_INTERVAL 5000 /** 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 WIFI_HOTSPOT_PASSWORD_DEFAULT \
|
||||
"cleanair" /** default WiFi AP password \
|
||||
*/
|
||||
#define FIRMWARE_CHECK_FOR_UPDATE_MS (60 * 60 * 1000) /** ms */
|
||||
|
||||
/** Create airgradient instance for 'DIY_BASIC' board */
|
||||
static AirGradient ag = AirGradient(DIY_BASIC);
|
||||
static AirGradient ag(DIY_BASIC);
|
||||
static Configuration configuration(Serial);
|
||||
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 pm25 = -1;
|
||||
static float temp = -1001;
|
||||
static int hum = -1;
|
||||
static long val;
|
||||
static int getCO2FailCount = 0;
|
||||
static AgFirmwareMode fwMode = FW_MODE_I_BASIC_40PS;
|
||||
|
||||
static String fwNewVersion;
|
||||
|
||||
static void boardInit(void);
|
||||
static void failedHandler(String msg);
|
||||
static void executeCo2Calibration(void);
|
||||
static void updateServerConfiguration(void);
|
||||
static void co2Update(void);
|
||||
static void pmUpdate(void);
|
||||
static void tempHumUpdate(void);
|
||||
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 dispHandler(void);
|
||||
static String getDevId(void);
|
||||
static void showNr(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);
|
||||
|
||||
bool hasSensorS8 = true;
|
||||
bool hasSensorPMS = true;
|
||||
bool hasSensorSHT = true;
|
||||
int pmFailCount = 0;
|
||||
int getCO2FailCount = 0;
|
||||
AgSchedule configSchedule(SERVER_CONFIG_UPDATE_INTERVAL,
|
||||
updateServerConfiguration);
|
||||
AgSchedule serverSchedule(SERVER_SYNC_INTERVAL, sendDataToServer);
|
||||
AgSchedule dispSchedule(DISP_UPDATE_INTERVAL, dispHandler);
|
||||
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, pmUpdate);
|
||||
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);
|
||||
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 */
|
||||
Wire.begin(ag.getI2cSdaPin(), ag.getI2cSclPin());
|
||||
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();
|
||||
|
||||
/** Init AirGradient server */
|
||||
/** Connecting wifi */
|
||||
bool connectToWifi = false;
|
||||
|
||||
connectToWifi = !configuration.isOfflineMode();
|
||||
if (connectToWifi) {
|
||||
apiClient.begin();
|
||||
apiClient.setAirGradient(&ag);
|
||||
configuration.setAirGradient(&ag);
|
||||
wifiConnector.setAirGradient(&ag);
|
||||
|
||||
/** Show boot display */
|
||||
displayShowText("DIY basic", "Lib:" + ag.getVersion(), "");
|
||||
delay(2000);
|
||||
|
||||
/** WiFi connect */
|
||||
// connectToWifi();
|
||||
if (wifiConnector.connect()) {
|
||||
if (WiFi.status() == WL_CONNECTED) {
|
||||
if (wifiConnector.isConnected()) {
|
||||
mdnsInit();
|
||||
localServer.begin();
|
||||
initMqtt();
|
||||
sendDataToAg();
|
||||
|
||||
apiClient.fetchServerConfiguration();
|
||||
if (configuration.isCo2CalibrationRequested()) {
|
||||
executeCo2Calibration();
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
/** Show serial number display */
|
||||
ag.display.clear();
|
||||
ag.display.setCursor(1, 1);
|
||||
ag.display.setText("Warm Up");
|
||||
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();
|
||||
}
|
||||
/** 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);
|
||||
}
|
||||
|
||||
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() {
|
||||
/** Handle schedule */
|
||||
dispLedSchedule.run();
|
||||
configSchedule.run();
|
||||
serverSchedule.run();
|
||||
dispSchedule.run();
|
||||
if (hasSensorS8) {
|
||||
agApiPostSchedule.run();
|
||||
|
||||
if (configuration.hasSensorS8) {
|
||||
co2Schedule.run();
|
||||
}
|
||||
if (hasSensorPMS) {
|
||||
if (configuration.hasSensorPMS1) {
|
||||
pmsSchedule.run();
|
||||
ag.pms5003.handle();
|
||||
}
|
||||
if (hasSensorSHT) {
|
||||
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();
|
||||
|
||||
/** Read PMS on loop */
|
||||
ag.pms5003.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 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() {
|
||||
// delay(1500);
|
||||
if (apiClient.sendPing(wifiConnector.RSSI(), 0)) {
|
||||
// Ping Server succses
|
||||
/** Change oledDisplay and led state */
|
||||
stateMachine.displayHandle(AgStateMachineWiFiOkServerConnecting);
|
||||
|
||||
delay(1500);
|
||||
if (apiClient.sendPing(wifiConnector.RSSI(), measurements.bootCount)) {
|
||||
stateMachine.displayHandle(AgStateMachineWiFiOkServerConnected);
|
||||
} 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) {
|
||||
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);
|
||||
void dispSensorNotFound(String ss) {
|
||||
oledDisplay.setText("Sensor", ss.c_str(), "not found");
|
||||
delay(2000);
|
||||
}
|
||||
|
||||
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) {
|
||||
hasSensorSHT = false;
|
||||
Serial.println("SHT sensor not found");
|
||||
Serial.println("SHTx sensor not found");
|
||||
configuration.hasSensorSHT = false;
|
||||
dispSensorNotFound("SHT");
|
||||
}
|
||||
|
||||
/** CO2 init */
|
||||
/** Init S8 CO2 sensor */
|
||||
if (ag.s8.begin(&Serial) == false) {
|
||||
Serial.println("CO2 S8 snsor not found");
|
||||
hasSensorS8 = false;
|
||||
Serial.println("CO2 S8 sensor not found");
|
||||
configuration.hasSensorS8 = false;
|
||||
dispSensorNotFound("S8");
|
||||
}
|
||||
|
||||
/** PMS init */
|
||||
/** Init PMS5003 */
|
||||
configuration.hasSensorPMS1 = true;
|
||||
configuration.hasSensorPMS2 = false;
|
||||
if (ag.pms5003.begin(&Serial) == false) {
|
||||
Serial.println("PMS sensor not found");
|
||||
hasSensorPMS = false;
|
||||
configuration.hasSensorPMS1 = false;
|
||||
|
||||
dispSensorNotFound("PMS");
|
||||
}
|
||||
|
||||
/** Display init */
|
||||
ag.display.begin(Wire);
|
||||
ag.display.setTextColor(1);
|
||||
ag.display.clear();
|
||||
ag.display.show();
|
||||
delay(100);
|
||||
/** Set S8 CO2 abc days period */
|
||||
if (configuration.hasSensorS8) {
|
||||
if (ag.s8.setAbcPeriod(configuration.getCO2CalibrationAbcDays() * 24)) {
|
||||
Serial.println("Set S8 AbcDays successful");
|
||||
} else {
|
||||
Serial.println("Set S8 AbcDays failure");
|
||||
}
|
||||
}
|
||||
|
||||
localServer.setFwMode(fwMode);
|
||||
}
|
||||
|
||||
static void failedHandler(String msg) {
|
||||
@ -221,181 +411,167 @@ static void failedHandler(String msg) {
|
||||
}
|
||||
}
|
||||
|
||||
static void executeCo2Calibration(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) {
|
||||
static void configurationUpdateSchedule(void) {
|
||||
if (apiClient.fetchServerConfiguration()) {
|
||||
if (configuration.isCo2CalibrationRequested()) {
|
||||
if (hasSensorS8) {
|
||||
executeCo2Calibration();
|
||||
} else {
|
||||
Serial.println("CO2 S8 not available, calib ignored");
|
||||
}
|
||||
}
|
||||
if (configuration.getCO2CalibrationAbcDays() > 0) {
|
||||
if (hasSensorS8) {
|
||||
int newHour = configuration.getCO2CalibrationAbcDays() * 24;
|
||||
Serial.printf("abcDays config: %d days(%d hours)\r\n",
|
||||
configuration.getCO2CalibrationAbcDays(), newHour);
|
||||
int curHour = ag.s8.getAbcPeriod();
|
||||
Serial.printf("Current config: %d (hours)\r\n", curHour);
|
||||
if (curHour == newHour) {
|
||||
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");
|
||||
}
|
||||
}
|
||||
configUpdateHandle();
|
||||
}
|
||||
}
|
||||
|
||||
static void co2Update() {
|
||||
int value = ag.s8.getCo2();
|
||||
if (value >= 0) {
|
||||
co2Ppm = value;
|
||||
getCO2FailCount = 0;
|
||||
Serial.printf("CO2 index: %d\r\n", co2Ppm);
|
||||
} else {
|
||||
getCO2FailCount++;
|
||||
Serial.printf("Get CO2 failed: %d\r\n", getCO2FailCount);
|
||||
if (getCO2FailCount >= 3) {
|
||||
co2Ppm = -1;
|
||||
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();
|
||||
}
|
||||
|
||||
void pmUpdate() {
|
||||
if (ag.pms5003.isFailed() == false) {
|
||||
pm25 = ag.pms5003.getPm25Ae();
|
||||
Serial.printf("PMS2.5: %d\r\n", pm25);
|
||||
pmFailCount = 0;
|
||||
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 {
|
||||
Serial.printf("PM read failed, %d", pmFailCount);
|
||||
pmFailCount++;
|
||||
if (pmFailCount >= 3) {
|
||||
pm25 = -1;
|
||||
stateMachine.displayClearAddToDashBoard();
|
||||
}
|
||||
} else if (apiClient.isPostToServerFailed()) {
|
||||
state = AgStateMachineServerLost;
|
||||
}
|
||||
}
|
||||
stateMachine.displayHandle(state);
|
||||
}
|
||||
|
||||
static void tempHumUpdate() {
|
||||
if (ag.sht.measure()) {
|
||||
temp = ag.sht.getTemperature();
|
||||
hum = ag.sht.getRelativeHumidity();
|
||||
Serial.printf("Temperature: %0.2f\r\n", temp);
|
||||
Serial.printf(" Humidity: %d\r\n", hum);
|
||||
} else {
|
||||
Serial.println("Meaure SHT failed");
|
||||
}
|
||||
static void oledDisplaySchedule(void) {
|
||||
|
||||
appDispHandler();
|
||||
}
|
||||
|
||||
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 + "}";
|
||||
static void updateTvoc(void) {
|
||||
measurements.TVOC = ag.sgp41.getTvocIndex();
|
||||
measurements.TVOCRaw = ag.sgp41.getTvocRaw();
|
||||
measurements.NOx = ag.sgp41.getNoxIndex();
|
||||
measurements.NOxRaw = ag.sgp41.getNoxRaw();
|
||||
|
||||
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 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() {
|
||||
String mac = WiFi.macAddress();
|
||||
mac.replace(":", "");
|
||||
mac.toLowerCase();
|
||||
return mac;
|
||||
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);
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
61
examples/BASIC/LocalServer.cpp
Normal file
61
examples/BASIC/LocalServer.cpp
Normal 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; }
|
38
examples/BASIC/LocalServer.h
Normal file
38
examples/BASIC/LocalServer.h
Normal file
@ -0,0 +1,38 @@
|
||||
#ifndef _LOCAL_SERVER_H_
|
||||
#define _LOCAL_SERVER_H_
|
||||
|
||||
#include "AgConfigure.h"
|
||||
#include "AgValue.h"
|
||||
#include "AirGradient.h"
|
||||
#include "OpenMetrics.h"
|
||||
#include "AgWiFiConnector.h"
|
||||
#include <Arduino.h>
|
||||
#include <ESP8266WebServer.h>
|
||||
|
||||
class LocalServer : public PrintLog {
|
||||
private:
|
||||
AirGradient *ag;
|
||||
OpenMetrics &openMetrics;
|
||||
Measurements &measure;
|
||||
Configuration &config;
|
||||
WifiConnector &wifiConnector;
|
||||
ESP8266WebServer server;
|
||||
AgFirmwareMode fwMode;
|
||||
|
||||
public:
|
||||
LocalServer(Stream &log, OpenMetrics &openMetrics, Measurements &measure,
|
||||
Configuration &config, WifiConnector& wifiConnector);
|
||||
~LocalServer();
|
||||
|
||||
bool begin(void);
|
||||
void setAirGraident(AirGradient *ag);
|
||||
String getHostname(void);
|
||||
void setFwMode(AgFirmwareMode fwMode);
|
||||
void _handle(void);
|
||||
void _GET_config(void);
|
||||
void _PUT_config(void);
|
||||
void _GET_metrics(void);
|
||||
void _GET_measure(void);
|
||||
};
|
||||
|
||||
#endif /** _LOCAL_SERVER_H_ */
|
186
examples/BASIC/OpenMetrics.cpp
Normal file
186
examples/BASIC/OpenMetrics.cpp
Normal 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;
|
||||
}
|
28
examples/BASIC/OpenMetrics.h
Normal file
28
examples/BASIC/OpenMetrics.h
Normal file
@ -0,0 +1,28 @@
|
||||
#ifndef _OPEN_METRICS_H_
|
||||
#define _OPEN_METRICS_H_
|
||||
|
||||
#include "AgConfigure.h"
|
||||
#include "AgValue.h"
|
||||
#include "AgWiFiConnector.h"
|
||||
#include "AirGradient.h"
|
||||
#include "AgApiClient.h"
|
||||
|
||||
class OpenMetrics {
|
||||
private:
|
||||
AirGradient *ag;
|
||||
Measurements &measure;
|
||||
Configuration &config;
|
||||
WifiConnector &wifiConnector;
|
||||
AgApiClient &apiClient;
|
||||
|
||||
public:
|
||||
OpenMetrics(Measurements &measure, Configuration &conig,
|
||||
WifiConnector &wifiConnector, AgApiClient& apiClient);
|
||||
~OpenMetrics();
|
||||
void setAirGradient(AirGradient *ag);
|
||||
const char *getApiContentType(void);
|
||||
const char* getApi(void);
|
||||
String getPayload(void);
|
||||
};
|
||||
|
||||
#endif /** _OPEN_METRICS_H_ */
|
629
examples/DiyProIndoorV3_3/DiyProIndoorV3_3.ino
Normal file
629
examples/DiyProIndoorV3_3/DiyProIndoorV3_3.ino
Normal file
@ -0,0 +1,629 @@
|
||||
/*
|
||||
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);
|
||||
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();
|
||||
}
|
||||
}
|
61
examples/DiyProIndoorV3_3/LocalServer.cpp
Normal file
61
examples/DiyProIndoorV3_3/LocalServer.cpp
Normal 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; }
|
38
examples/DiyProIndoorV3_3/LocalServer.h
Normal file
38
examples/DiyProIndoorV3_3/LocalServer.h
Normal file
@ -0,0 +1,38 @@
|
||||
#ifndef _LOCAL_SERVER_H_
|
||||
#define _LOCAL_SERVER_H_
|
||||
|
||||
#include "AgConfigure.h"
|
||||
#include "AgValue.h"
|
||||
#include "AirGradient.h"
|
||||
#include "OpenMetrics.h"
|
||||
#include "AgWiFiConnector.h"
|
||||
#include <Arduino.h>
|
||||
#include <ESP8266WebServer.h>
|
||||
|
||||
class LocalServer : public PrintLog {
|
||||
private:
|
||||
AirGradient *ag;
|
||||
OpenMetrics &openMetrics;
|
||||
Measurements &measure;
|
||||
Configuration &config;
|
||||
WifiConnector &wifiConnector;
|
||||
ESP8266WebServer server;
|
||||
AgFirmwareMode fwMode;
|
||||
|
||||
public:
|
||||
LocalServer(Stream &log, OpenMetrics &openMetrics, Measurements &measure,
|
||||
Configuration &config, WifiConnector& wifiConnector);
|
||||
~LocalServer();
|
||||
|
||||
bool begin(void);
|
||||
void setAirGraident(AirGradient *ag);
|
||||
String getHostname(void);
|
||||
void setFwMode(AgFirmwareMode fwMode);
|
||||
void _handle(void);
|
||||
void _GET_config(void);
|
||||
void _PUT_config(void);
|
||||
void _GET_metrics(void);
|
||||
void _GET_measure(void);
|
||||
};
|
||||
|
||||
#endif /** _LOCAL_SERVER_H_ */
|
186
examples/DiyProIndoorV3_3/OpenMetrics.cpp
Normal file
186
examples/DiyProIndoorV3_3/OpenMetrics.cpp
Normal 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;
|
||||
}
|
28
examples/DiyProIndoorV3_3/OpenMetrics.h
Normal file
28
examples/DiyProIndoorV3_3/OpenMetrics.h
Normal file
@ -0,0 +1,28 @@
|
||||
#ifndef _OPEN_METRICS_H_
|
||||
#define _OPEN_METRICS_H_
|
||||
|
||||
#include "AgConfigure.h"
|
||||
#include "AgValue.h"
|
||||
#include "AgWiFiConnector.h"
|
||||
#include "AirGradient.h"
|
||||
#include "AgApiClient.h"
|
||||
|
||||
class OpenMetrics {
|
||||
private:
|
||||
AirGradient *ag;
|
||||
Measurements &measure;
|
||||
Configuration &config;
|
||||
WifiConnector &wifiConnector;
|
||||
AgApiClient &apiClient;
|
||||
|
||||
public:
|
||||
OpenMetrics(Measurements &measure, Configuration &conig,
|
||||
WifiConnector &wifiConnector, AgApiClient& apiClient);
|
||||
~OpenMetrics();
|
||||
void setAirGradient(AirGradient *ag);
|
||||
const char *getApiContentType(void);
|
||||
const char* getApi(void);
|
||||
String getPayload(void);
|
||||
};
|
||||
|
||||
#endif /** _OPEN_METRICS_H_ */
|
670
examples/DiyProIndoorV4_2/DiyProIndoorV4_2.ino
Normal file
670
examples/DiyProIndoorV4_2/DiyProIndoorV4_2.ino
Normal file
@ -0,0 +1,670 @@
|
||||
/*
|
||||
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);
|
||||
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();
|
||||
}
|
||||
}
|
61
examples/DiyProIndoorV4_2/LocalServer.cpp
Normal file
61
examples/DiyProIndoorV4_2/LocalServer.cpp
Normal 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; }
|
38
examples/DiyProIndoorV4_2/LocalServer.h
Normal file
38
examples/DiyProIndoorV4_2/LocalServer.h
Normal file
@ -0,0 +1,38 @@
|
||||
#ifndef _LOCAL_SERVER_H_
|
||||
#define _LOCAL_SERVER_H_
|
||||
|
||||
#include "AgConfigure.h"
|
||||
#include "AgValue.h"
|
||||
#include "AirGradient.h"
|
||||
#include "OpenMetrics.h"
|
||||
#include "AgWiFiConnector.h"
|
||||
#include <Arduino.h>
|
||||
#include <ESP8266WebServer.h>
|
||||
|
||||
class LocalServer : public PrintLog {
|
||||
private:
|
||||
AirGradient *ag;
|
||||
OpenMetrics &openMetrics;
|
||||
Measurements &measure;
|
||||
Configuration &config;
|
||||
WifiConnector &wifiConnector;
|
||||
ESP8266WebServer server;
|
||||
AgFirmwareMode fwMode;
|
||||
|
||||
public:
|
||||
LocalServer(Stream &log, OpenMetrics &openMetrics, Measurements &measure,
|
||||
Configuration &config, WifiConnector& wifiConnector);
|
||||
~LocalServer();
|
||||
|
||||
bool begin(void);
|
||||
void setAirGraident(AirGradient *ag);
|
||||
String getHostname(void);
|
||||
void setFwMode(AgFirmwareMode fwMode);
|
||||
void _handle(void);
|
||||
void _GET_config(void);
|
||||
void _PUT_config(void);
|
||||
void _GET_metrics(void);
|
||||
void _GET_measure(void);
|
||||
};
|
||||
|
||||
#endif /** _LOCAL_SERVER_H_ */
|
186
examples/DiyProIndoorV4_2/OpenMetrics.cpp
Normal file
186
examples/DiyProIndoorV4_2/OpenMetrics.cpp
Normal 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;
|
||||
}
|
28
examples/DiyProIndoorV4_2/OpenMetrics.h
Normal file
28
examples/DiyProIndoorV4_2/OpenMetrics.h
Normal file
@ -0,0 +1,28 @@
|
||||
#ifndef _OPEN_METRICS_H_
|
||||
#define _OPEN_METRICS_H_
|
||||
|
||||
#include "AgConfigure.h"
|
||||
#include "AgValue.h"
|
||||
#include "AgWiFiConnector.h"
|
||||
#include "AirGradient.h"
|
||||
#include "AgApiClient.h"
|
||||
|
||||
class OpenMetrics {
|
||||
private:
|
||||
AirGradient *ag;
|
||||
Measurements &measure;
|
||||
Configuration &config;
|
||||
WifiConnector &wifiConnector;
|
||||
AgApiClient &apiClient;
|
||||
|
||||
public:
|
||||
OpenMetrics(Measurements &measure, Configuration &conig,
|
||||
WifiConnector &wifiConnector, AgApiClient& apiClient);
|
||||
~OpenMetrics();
|
||||
void setAirGradient(AirGradient *ag);
|
||||
const char *getApiContentType(void);
|
||||
const char* getApi(void);
|
||||
String getPayload(void);
|
||||
};
|
||||
|
||||
#endif /** _OPEN_METRICS_H_ */
|
@ -39,7 +39,11 @@ String LocalServer::getHostname(void) {
|
||||
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) {
|
||||
|
@ -51,10 +51,11 @@ CC BY-SA 4.0 Attribution-ShareAlike 4.0 International License
|
||||
#include "OpenMetrics.h"
|
||||
#include "WebServer.h"
|
||||
#include <WebServer.h>
|
||||
#include <WiFi.h>
|
||||
|
||||
#define LED_BAR_ANIMATION_PERIOD 100 /** 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 MQTT_SYNC_INTERVAL 60000 /** ms */
|
||||
#define SENSOR_CO2_CALIB_COUNTDOWN_MAX 5 /** sec */
|
||||
@ -63,6 +64,7 @@ CC BY-SA 4.0 Attribution-ShareAlike 4.0 International License
|
||||
#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 */
|
||||
|
||||
/** I2C define */
|
||||
#define I2C_SDA_PIN 7
|
||||
@ -86,13 +88,12 @@ static OtaHandler otaHandler;
|
||||
static LocalServer localServer(Serial, openMetrics, measurements, configuration,
|
||||
wifiConnector);
|
||||
|
||||
static int pmFailCount = 0;
|
||||
static uint32_t factoryBtnPressTime = 0;
|
||||
static int getCO2FailCount = 0;
|
||||
static bool offlineMode = false;
|
||||
static AgFirmwareMode fwMode = FW_MODE_I_9PSL;
|
||||
|
||||
static bool ledBarButtonTest = false;
|
||||
static String fwNewVersion;
|
||||
|
||||
static void boardInit(void);
|
||||
static void failedHandler(String msg);
|
||||
@ -112,9 +113,13 @@ static void factoryConfigReset(void);
|
||||
static void wdgFeedUpdate(void);
|
||||
static void ledBarEnabledUpdate(void);
|
||||
static bool sgp41Init(void);
|
||||
static void firmwareCheckForUpdate(void);
|
||||
static void otaHandlerCallback(OtaState state, String mesasge);
|
||||
static void displayExecuteOta(OtaState state, String msg,
|
||||
int processing);
|
||||
|
||||
AgSchedule dispLedSchedule(DISP_UPDATE_INTERVAL, oledDisplayLedBarSchedule);
|
||||
AgSchedule configSchedule(SERVER_CONFIG_UPDATE_INTERVAL,
|
||||
AgSchedule configSchedule(SERVER_CONFIG_SYNC_INTERVAL,
|
||||
configurationUpdateSchedule);
|
||||
AgSchedule agApiPostSchedule(SERVER_SYNC_INTERVAL, sendDataToServer);
|
||||
AgSchedule co2Schedule(SENSOR_CO2_UPDATE_INTERVAL, co2Update);
|
||||
@ -122,6 +127,7 @@ AgSchedule pmsSchedule(SENSOR_PM_UPDATE_INTERVAL, updatePm);
|
||||
AgSchedule tempHumSchedule(SENSOR_TEMP_HUM_UPDATE_INTERVAL, tempHumUpdate);
|
||||
AgSchedule tvocSchedule(SENSOR_TVOC_UPDATE_INTERVAL, updateTvoc);
|
||||
AgSchedule watchdogFeedSchedule(60000, wdgFeedUpdate);
|
||||
AgSchedule checkForUpdateSchedule(FIRMWARE_CHECK_FOR_UPDATE_MS, firmwareCheckForUpdate);
|
||||
|
||||
void setup() {
|
||||
/** Serial for print debug message */
|
||||
@ -148,8 +154,6 @@ void setup() {
|
||||
}
|
||||
Serial.println("Detected " + ag->getBoardName());
|
||||
|
||||
/** Init sensor */
|
||||
boardInit();
|
||||
configuration.setAirGradient(ag);
|
||||
oledDisplay.setAirGradient(ag);
|
||||
stateMachine.setAirGradient(ag);
|
||||
@ -158,14 +162,40 @@ void setup() {
|
||||
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;
|
||||
if (ag->isOne()) {
|
||||
if (ledBarButtonTest) {
|
||||
stateMachine.executeLedBarTest();
|
||||
/** Show message confirm offline mode, should me perform if LED bar button
|
||||
* test pressed */
|
||||
if (ledBarButtonTest == false) {
|
||||
oledDisplay.setText(
|
||||
"Press now for",
|
||||
configuration.isOfflineMode() ? "online mode" : "offline mode", "");
|
||||
uint32_t startTime = millis();
|
||||
while (true) {
|
||||
if (ag->button.getState() == ag->button.BUTTON_PRESSED) {
|
||||
configuration.setOfflineMode(!configuration.isOfflineMode());
|
||||
|
||||
oledDisplay.setText(
|
||||
"Offline Mode",
|
||||
configuration.isOfflineMode() ? " = True" : " = False", "");
|
||||
delay(1000);
|
||||
break;
|
||||
}
|
||||
uint32_t periodMs = (uint32_t)(millis() - startTime);
|
||||
if (periodMs >= 3000) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
connectToWifi = !configuration.isOfflineMode();
|
||||
} else {
|
||||
ledBarEnabledUpdate();
|
||||
connectToWifi = true;
|
||||
configuration.setOfflineModeWithoutSave(true);
|
||||
}
|
||||
} else {
|
||||
connectToWifi = true;
|
||||
@ -184,15 +214,21 @@ void setup() {
|
||||
#ifdef ESP8266
|
||||
// ota not supported
|
||||
#else
|
||||
otaHandler.updateFirmwareIfOutdated(ag->deviceId());
|
||||
firmwareCheckForUpdate();
|
||||
checkForUpdateSchedule.update();
|
||||
#endif
|
||||
|
||||
apiClient.fetchServerConfiguration();
|
||||
configSchedule.update();
|
||||
if (apiClient.isFetchConfigureFailed()) {
|
||||
if (ag->isOne()) {
|
||||
if (apiClient.isNotAvailableOnDashboard()) {
|
||||
stateMachine.displaySetAddToDashBoard();
|
||||
stateMachine.displayHandle(
|
||||
AgStateMachineWiFiOkServerOkSensorConfigFailed);
|
||||
} else {
|
||||
stateMachine.displayClearAddToDashBoard();
|
||||
}
|
||||
}
|
||||
stateMachine.handleLeds(
|
||||
AgStateMachineWiFiOkServerOkSensorConfigFailed);
|
||||
@ -201,15 +237,28 @@ void setup() {
|
||||
ledBarEnabledUpdate();
|
||||
}
|
||||
} else {
|
||||
offlineMode = true;
|
||||
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 */
|
||||
if (ag->isOne()) {
|
||||
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());
|
||||
}
|
||||
|
||||
appLedHandler();
|
||||
@ -249,9 +298,9 @@ void loop() {
|
||||
}
|
||||
}
|
||||
|
||||
/** Auto reset external watchdog timer on offline mode and
|
||||
* postDataToAirGradient disabled. */
|
||||
if (offlineMode || (configuration.isPostDataToAirGradient() == false)) {
|
||||
/** Auto reset watchdog timer if offline mode or postDataToAirGradient */
|
||||
if (configuration.isOfflineMode() ||
|
||||
(configuration.isPostDataToAirGradient() == false)) {
|
||||
watchdogFeedSchedule.run();
|
||||
}
|
||||
|
||||
@ -263,11 +312,14 @@ void loop() {
|
||||
|
||||
/** check that local configura changed then do some action */
|
||||
configUpdateHandle();
|
||||
|
||||
/** Firmware check for update handle */
|
||||
checkForUpdateSchedule.run();
|
||||
}
|
||||
|
||||
static void co2Update(void) {
|
||||
int value = ag->s8.getCo2();
|
||||
if (value >= 0) {
|
||||
if (utils::isValidCO2(value)) {
|
||||
measurements.CO2 = value;
|
||||
getCO2FailCount = 0;
|
||||
Serial.printf("CO2 (ppm): %d\r\n", measurements.CO2);
|
||||
@ -275,7 +327,7 @@ static void co2Update(void) {
|
||||
getCO2FailCount++;
|
||||
Serial.printf("Get CO2 failed: %d\r\n", getCO2FailCount);
|
||||
if (getCO2FailCount >= 3) {
|
||||
measurements.CO2 = -1;
|
||||
measurements.CO2 = utils::getInvalidCO2();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -370,9 +422,8 @@ static void factoryConfigReset(void) {
|
||||
mqttTask = NULL;
|
||||
}
|
||||
|
||||
/** Disconnect WIFI */
|
||||
wifiConnector.disconnect();
|
||||
wifiConnector.reset();
|
||||
/** Reset WIFI */
|
||||
WiFi.disconnect(true, true);
|
||||
|
||||
/** Reset local config */
|
||||
configuration.reset();
|
||||
@ -383,6 +434,7 @@ static void factoryConfigReset(void) {
|
||||
Serial.println("Factory reset successful");
|
||||
}
|
||||
delay(3000);
|
||||
oledDisplay.setText("","","");
|
||||
ESP.restart();
|
||||
}
|
||||
}
|
||||
@ -408,14 +460,22 @@ static void factoryConfigReset(void) {
|
||||
static void wdgFeedUpdate(void) {
|
||||
ag->watchdog.reset();
|
||||
Serial.println();
|
||||
Serial.println("External watchdog feed");
|
||||
Serial.println("Offline mode or isPostToAirGradient = false: watchdog reset");
|
||||
Serial.println();
|
||||
}
|
||||
|
||||
static void ledBarEnabledUpdate(void) {
|
||||
if (ag->isOne()) {
|
||||
int brightness = configuration.getLedBarBrightness();
|
||||
Serial.println("LED bar brightness: " + String(brightness));
|
||||
if ((brightness == 0) || (configuration.getLedBarMode() == LedBarModeOff)) {
|
||||
ag->ledBar.setEnable(false);
|
||||
} else {
|
||||
ag->ledBar.setBrightness(brightness);
|
||||
ag->ledBar.setEnable(configuration.getLedBarMode() != LedBarModeOff);
|
||||
}
|
||||
ag->ledBar.show();
|
||||
}
|
||||
}
|
||||
|
||||
static bool sgp41Init(void) {
|
||||
@ -432,6 +492,116 @@ static bool sgp41Init(void) {
|
||||
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) {
|
||||
Serial.println("OTA message: " + mesasge);
|
||||
switch (state) {
|
||||
case OtaState::OTA_STATE_BEGIN:
|
||||
displayExecuteOta(state, fwNewVersion, 0);
|
||||
break;
|
||||
case OtaState::OTA_STATE_FAIL:
|
||||
displayExecuteOta(state, "", 0);
|
||||
break;
|
||||
case OtaState::OTA_STATE_PROCESSING:
|
||||
displayExecuteOta(state, "", mesasge.toInt());
|
||||
break;
|
||||
case OtaState::OTA_STATE_SUCCESS:
|
||||
displayExecuteOta(state, "", mesasge.toInt());
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void displayExecuteOta(OtaState state, String msg, int processing) {
|
||||
switch (state) {
|
||||
case OtaState::OTA_STATE_BEGIN: {
|
||||
if (ag->isOne()) {
|
||||
oledDisplay.showFirmwareUpdateVersion(msg);
|
||||
} else {
|
||||
Serial.println("New firmware: " + msg);
|
||||
}
|
||||
delay(2500);
|
||||
break;
|
||||
}
|
||||
case OtaState::OTA_STATE_FAIL: {
|
||||
if (ag->isOne()) {
|
||||
oledDisplay.showFirmwareUpdateFailed();
|
||||
} else {
|
||||
Serial.println("Error: Firmware update: failed");
|
||||
}
|
||||
|
||||
delay(2500);
|
||||
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: {
|
||||
if (ag->isOne()) {
|
||||
oledDisplay.showFirmwareUpdateProgress(processing);
|
||||
} else {
|
||||
Serial.println("Firmware update: " + String(processing) + String("%"));
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case OtaState::OTA_STATE_SUCCESS: {
|
||||
int i = 6;
|
||||
while(i != 0) {
|
||||
i = i - 1;
|
||||
Serial.println("OTA update performed, restarting ...");
|
||||
int i = 6;
|
||||
while (i != 0) {
|
||||
i = i - 1;
|
||||
if (ag->isOne()) {
|
||||
oledDisplay.showFirmwareUpdateSuccess(i);
|
||||
} else {
|
||||
Serial.println("Rebooting... " + String(i));
|
||||
}
|
||||
|
||||
delay(1000);
|
||||
}
|
||||
oledDisplay.setBrightness(0);
|
||||
esp_restart();
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void sendDataToAg() {
|
||||
/** Change oledDisplay and led state */
|
||||
if (ag->isOne()) {
|
||||
@ -471,11 +641,6 @@ static void sendDataToAg() {
|
||||
stateMachine.handleLeds(AgStateMachineNormal);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Must reset each 5min to avoid ESP32 reset
|
||||
*/
|
||||
static void resetWatchdog() { ag->watchdog.reset(); }
|
||||
|
||||
void dispSensorNotFound(String ss) {
|
||||
ss = ss + " not found";
|
||||
oledDisplay.setText("Sensor init", "Error:", ss.c_str());
|
||||
@ -499,6 +664,41 @@ static void oneIndoorInit(void) {
|
||||
ag->button.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("Sensor", "initializing...", "");
|
||||
|
||||
/** Init sensor SGP41 */
|
||||
if (sgp41Init() == false) {
|
||||
dispSensorNotFound("SGP41");
|
||||
@ -525,22 +725,6 @@ static void oneIndoorInit(void) {
|
||||
|
||||
dispSensorNotFound("PMS");
|
||||
}
|
||||
|
||||
/** Run LED test on start up */
|
||||
oledDisplay.setText("Press now for", "LED test &", "offline mode");
|
||||
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) {
|
||||
configuration.hasSensorSHT = false;
|
||||
@ -688,9 +872,7 @@ static void configUpdateHandle() {
|
||||
return;
|
||||
}
|
||||
|
||||
ledBarEnabledUpdate();
|
||||
stateMachine.executeCo2Calibration();
|
||||
stateMachine.executeLedBarTest();
|
||||
|
||||
String mqttUri = configuration.getMqttBrokerUri();
|
||||
if (mqttClient.isCurrentUri(mqttUri) == false) {
|
||||
@ -698,6 +880,7 @@ static void configUpdateHandle() {
|
||||
initMqtt();
|
||||
}
|
||||
|
||||
if (configuration.hasSensorSGP) {
|
||||
if (configuration.noxLearnOffsetChanged() ||
|
||||
configuration.tvocLearnOffsetChanged()) {
|
||||
ag->sgp41.end();
|
||||
@ -720,6 +903,44 @@ static void configUpdateHandle() {
|
||||
resultStr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (ag->isOne()) {
|
||||
if (configuration.isLedBarBrightnessChanged()) {
|
||||
if (configuration.getLedBarBrightness() == 0) {
|
||||
ag->ledBar.setEnable(false);
|
||||
} 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()) {
|
||||
oledDisplay.setBrightness(configuration.getDisplayBrightness());
|
||||
}
|
||||
|
||||
stateMachine.executeLedBarTest();
|
||||
}
|
||||
else if(ag->isOpenAir()) {
|
||||
stateMachine.executeLedBarTest();
|
||||
}
|
||||
|
||||
appDispHandler();
|
||||
appLedHandler();
|
||||
@ -727,14 +948,15 @@ static void configUpdateHandle() {
|
||||
|
||||
static void appLedHandler(void) {
|
||||
AgStateMachineState state = AgStateMachineNormal;
|
||||
if (configuration.isOfflineMode() == false) {
|
||||
if (wifiConnector.isConnected() == false) {
|
||||
state = AgStateMachineWiFiLost;
|
||||
} else if (apiClient.isFetchConfigureFailed()) {
|
||||
stateMachine.displaySetAddToDashBoard();
|
||||
state = AgStateMachineSensorConfigFailed;
|
||||
} else if (apiClient.isPostToServerFailed()) {
|
||||
state = AgStateMachineServerLost;
|
||||
}
|
||||
}
|
||||
|
||||
stateMachine.handleLeds(state);
|
||||
}
|
||||
@ -742,14 +964,22 @@ static void appLedHandler(void) {
|
||||
static void appDispHandler(void) {
|
||||
if (ag->isOne()) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
@ -777,6 +1007,7 @@ static void updateTvoc(void) {
|
||||
}
|
||||
|
||||
static void updatePm(void) {
|
||||
bool restart = false;
|
||||
if (ag->isOne()) {
|
||||
if (ag->pms5003.isFailed() == false) {
|
||||
measurements.pm01_1 = ag->pms5003.getPm01Ae();
|
||||
@ -789,15 +1020,19 @@ static void updatePm(void) {
|
||||
Serial.printf("PM2.5 ug/m3: %d\r\n", measurements.pm25_1);
|
||||
Serial.printf("PM10 ug/m3: %d\r\n", measurements.pm10_1);
|
||||
Serial.printf("PM0.3 Count: %d\r\n", measurements.pm03PCount_1);
|
||||
pmFailCount = 0;
|
||||
ag->pms5003.resetFailCount();
|
||||
} else {
|
||||
pmFailCount++;
|
||||
Serial.printf("PMS read failed: %d\r\n", pmFailCount);
|
||||
if (pmFailCount >= 3) {
|
||||
measurements.pm01_1 = -1;
|
||||
measurements.pm25_1 = -1;
|
||||
measurements.pm10_1 = -1;
|
||||
measurements.pm03PCount_1 = -1;
|
||||
ag->pms5003.updateFailCount();
|
||||
Serial.printf("PMS read faile %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()) {
|
||||
restart = true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@ -821,16 +1056,29 @@ static void updatePm(void) {
|
||||
Serial.printf("[1] Temperature in C: %0.2f\r\n", measurements.temp_1);
|
||||
Serial.printf("[1] Relative Humidity: %d\r\n", measurements.hum_1);
|
||||
Serial.printf("[1] Temperature compensated in C: %0.2f\r\n",
|
||||
ag->pms5003t_1.temperatureCompensated(measurements.temp_1));
|
||||
Serial.printf("[1] Relative Humidity compensated: %d\r\n",
|
||||
ag->pms5003t_1.humidityCompensated(measurements.hum_1));
|
||||
ag->pms5003t_1.compensateTemp(measurements.temp_1));
|
||||
Serial.printf("[1] Relative Humidity compensated: %0.2f\r\n",
|
||||
ag->pms5003t_1.compensateHum(measurements.hum_1));
|
||||
|
||||
ag->pms5003t_1.resetFailCount();
|
||||
} else {
|
||||
measurements.pm01_1 = -1;
|
||||
measurements.pm25_1 = -1;
|
||||
measurements.pm10_1 = -1;
|
||||
measurements.pm03PCount_1 = -1;
|
||||
measurements.temp_1 = -1001;
|
||||
measurements.hum_1 = -1;
|
||||
if (configuration.hasSensorPMS1) {
|
||||
ag->pms5003t_1.updateFailCount();
|
||||
Serial.printf("[1] PMS read failed %d times\r\n", ag->pms5003t_1.getFailCount());
|
||||
|
||||
if (ag->pms5003t_1.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();
|
||||
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)) {
|
||||
@ -851,16 +1099,29 @@ static void updatePm(void) {
|
||||
Serial.printf("[2] Temperature in C: %0.2f\r\n", measurements.temp_2);
|
||||
Serial.printf("[2] Relative Humidity: %d\r\n", measurements.hum_2);
|
||||
Serial.printf("[2] Temperature compensated in C: %0.2f\r\n",
|
||||
ag->pms5003t_1.temperatureCompensated(measurements.temp_2));
|
||||
Serial.printf("[2] Relative Humidity compensated: %d\r\n",
|
||||
ag->pms5003t_1.humidityCompensated(measurements.hum_2));
|
||||
ag->pms5003t_1.compensateTemp(measurements.temp_2));
|
||||
Serial.printf("[2] Relative Humidity compensated: %0.2f\r\n",
|
||||
ag->pms5003t_1.compensateHum(measurements.hum_2));
|
||||
|
||||
ag->pms5003t_2.resetFailCount();
|
||||
} else {
|
||||
measurements.pm01_2 = -1;
|
||||
measurements.pm25_2 = -1;
|
||||
measurements.pm10_2 = -1;
|
||||
measurements.pm03PCount_2 = -1;
|
||||
measurements.temp_2 = -1001;
|
||||
measurements.hum_2 = -1;
|
||||
if (configuration.hasSensorPMS2) {
|
||||
ag->pms5003t_2.updateFailCount();
|
||||
Serial.printf("[2] PMS read failed %d times\r\n", ag->pms5003t_2.getFailCount());
|
||||
|
||||
if (ag->pms5003t_2.getFailCount() >= PMS_FAIL_COUNT_SET_INVALID) {
|
||||
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 &&
|
||||
@ -960,13 +1221,27 @@ static void updatePm(void) {
|
||||
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) {
|
||||
/** 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)) {
|
||||
resetWatchdog();
|
||||
ag->watchdog.reset();
|
||||
Serial.println();
|
||||
Serial.println(
|
||||
"Online mode and isPostToAirGradient = true: watchdog reset");
|
||||
Serial.println();
|
||||
}
|
||||
|
||||
measurements.bootCount++;
|
||||
@ -991,6 +1266,8 @@ static void tempHumUpdate(void) {
|
||||
measurements.Humidity);
|
||||
}
|
||||
} else {
|
||||
measurements.Temperature = utils::getInvalidTemperature();
|
||||
measurements.Humidity = utils::getInvalidHumidity();
|
||||
Serial.println("SHT read failed");
|
||||
}
|
||||
}
|
||||
|
@ -65,14 +65,14 @@ String OpenMetrics::getPayload(void) {
|
||||
add_metric_point("", String(measure.CO2));
|
||||
}
|
||||
|
||||
float _temp = -1001;
|
||||
float _hum = -1;
|
||||
int pm01 = -1;
|
||||
int pm25 = -1;
|
||||
int pm10 = -1;
|
||||
int pm03PCount = -1;
|
||||
int atmpCompensated = -1;
|
||||
int ahumCompensated = -1;
|
||||
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.hasSensorPMS1 && config.hasSensorPMS2) {
|
||||
_temp = (measure.temp_1 + measure.temp_2) / 2.0f;
|
||||
_hum = (measure.hum_1 + measure.hum_2) / 2.0f;
|
||||
@ -86,6 +86,13 @@ String OpenMetrics::getPayload(void) {
|
||||
_temp = measure.Temperature;
|
||||
_hum = measure.Humidity;
|
||||
}
|
||||
|
||||
if (config.hasSensorPMS1) {
|
||||
pm01 = measure.pm01_1;
|
||||
pm25 = measure.pm25_1;
|
||||
pm10 = measure.pm10_1;
|
||||
pm03PCount = measure.pm03PCount_1;
|
||||
}
|
||||
} else {
|
||||
if (config.hasSensorPMS1) {
|
||||
_temp = measure.temp_1;
|
||||
@ -111,33 +118,33 @@ String OpenMetrics::getPayload(void) {
|
||||
atmpCompensated = _temp;
|
||||
ahumCompensated = _hum;
|
||||
} else {
|
||||
atmpCompensated = ag->pms5003t_1.temperatureCompensated(_temp);
|
||||
ahumCompensated = ag->pms5003t_1.humidityCompensated(_hum);
|
||||
atmpCompensated = ag->pms5003t_1.compensateTemp(_temp);
|
||||
ahumCompensated = ag->pms5003t_1.compensateHum(_hum);
|
||||
}
|
||||
|
||||
if (config.hasSensorPMS1 || config.hasSensorPMS2) {
|
||||
if (pm01 >= 0) {
|
||||
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 (pm25 >= 0) {
|
||||
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 (pm10 >= 0) {
|
||||
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 (pm03PCount >= 0) {
|
||||
if (utils::isValidPm03Count(pm03PCount)) {
|
||||
add_metric("pm0d3",
|
||||
"PM0.3 concentration as measured by the AirGradient PMS "
|
||||
"sensor, in number of particules per 100 milliliters",
|
||||
@ -147,28 +154,28 @@ String OpenMetrics::getPayload(void) {
|
||||
}
|
||||
|
||||
if (config.hasSensorSGP) {
|
||||
if (measure.TVOC >= 0) {
|
||||
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 (measure.TVOCRaw >= 0) {
|
||||
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 (measure.NOx >= 0) {
|
||||
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 (measure.NOxRaw >= 0) {
|
||||
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",
|
||||
@ -177,14 +184,14 @@ String OpenMetrics::getPayload(void) {
|
||||
}
|
||||
}
|
||||
|
||||
if (_temp > -1001) {
|
||||
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 (atmpCompensated > -1001) {
|
||||
if (utils::isValidTemperature(atmpCompensated)) {
|
||||
add_metric(
|
||||
"temperature_compensated",
|
||||
"The compensated ambient temperature as measured by the AirGradient SHT / PMS "
|
||||
@ -192,14 +199,14 @@ String OpenMetrics::getPayload(void) {
|
||||
"gauge", "celsius");
|
||||
add_metric_point("", String(atmpCompensated));
|
||||
}
|
||||
if (_hum >= 0) {
|
||||
if (utils::isValidHumidity(_hum)) {
|
||||
add_metric(
|
||||
"humidity",
|
||||
"The relative humidity as measured by the AirGradient SHT sensor"
|
||||
"The relative humidity as measured by the AirGradient SHT sensor",
|
||||
"gauge", "percent");
|
||||
add_metric_point("", String(_hum));
|
||||
}
|
||||
if (ahumCompensated >= 0) {
|
||||
if (utils::isValidHumidity(ahumCompensated)) {
|
||||
add_metric(
|
||||
"humidity_compensated",
|
||||
"The compensated relative humidity as measured by the AirGradient SHT / PMS sensor",
|
||||
|
@ -1,64 +1,110 @@
|
||||
#ifndef _OTA_HANDLER_H_
|
||||
#define _OTA_HANDLER_H_
|
||||
|
||||
#include <esp_ota_ops.h>
|
||||
#include <esp_http_client.h>
|
||||
#include <esp_err.h>
|
||||
#include <Arduino.h>
|
||||
#include <esp_err.h>
|
||||
#include <esp_http_client.h>
|
||||
#include <esp_ota_ops.h>
|
||||
|
||||
#define OTA_BUF_SIZE 512
|
||||
#define OTA_BUF_SIZE 1024
|
||||
#define URL_BUF_SIZE 256
|
||||
|
||||
enum OtaUpdateOutcome {
|
||||
UPDATE_PERFORMED,
|
||||
ALREADY_UP_TO_DATE,
|
||||
UPDATE_FAILED,
|
||||
UDPATE_SKIPPED
|
||||
};
|
||||
|
||||
enum OtaState {
|
||||
OTA_STATE_BEGIN,
|
||||
OTA_STATE_FAIL,
|
||||
OTA_STATE_SKIP,
|
||||
OTA_STATE_UP_TO_DATE,
|
||||
OTA_STATE_PROCESSING,
|
||||
OTA_STATE_SUCCESS
|
||||
};
|
||||
|
||||
typedef void(*OtaHandlerCallback_t)(OtaState state,
|
||||
String message);
|
||||
|
||||
class OtaHandler {
|
||||
public:
|
||||
void updateFirmwareIfOutdated(String deviceId) {
|
||||
|
||||
String url = "http://hw.airgradient.com/sensors/airgradient:"
|
||||
+ deviceId + "/generic/os/firmware.bin";
|
||||
String url = "http://hw.airgradient.com/sensors/airgradient:" + deviceId +
|
||||
"/generic/os/firmware.bin";
|
||||
url += "?current_firmware=";
|
||||
url += GIT_VERSION;
|
||||
char urlAsChar[URL_BUF_SIZE];
|
||||
url.toCharArray(urlAsChar, URL_BUF_SIZE);
|
||||
Serial.printf("checking for new ota @ %s\n", urlAsChar);
|
||||
Serial.printf("checking for new OTA update @ %s\n", urlAsChar);
|
||||
|
||||
esp_http_client_config_t config = {};
|
||||
config.url = urlAsChar;
|
||||
esp_err_t ret = attemptToPerformOta(&config);
|
||||
OtaUpdateOutcome ret = attemptToPerformOta(&config);
|
||||
Serial.println(ret);
|
||||
if (ret == 0) {
|
||||
Serial.println("OTA completed");
|
||||
esp_restart();
|
||||
} else {
|
||||
Serial.println("OTA failed, maybe already up to date");
|
||||
if (this->callback) {
|
||||
switch (ret) {
|
||||
case OtaUpdateOutcome::UPDATE_PERFORMED:
|
||||
this->callback(OtaState::OTA_STATE_SUCCESS, "");
|
||||
break;
|
||||
case OtaUpdateOutcome::UDPATE_SKIPPED:
|
||||
this->callback(OtaState::OTA_STATE_SKIP, "");
|
||||
break;
|
||||
case OtaUpdateOutcome::ALREADY_UP_TO_DATE:
|
||||
this->callback(OtaState::OTA_STATE_UP_TO_DATE, "");
|
||||
break;
|
||||
case OtaUpdateOutcome::UPDATE_FAILED:
|
||||
this->callback(OtaState::OTA_STATE_FAIL, "");
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void setHandlerCallback(OtaHandlerCallback_t callback) {
|
||||
this->callback = callback;
|
||||
}
|
||||
|
||||
private:
|
||||
OtaHandlerCallback_t callback;
|
||||
|
||||
int attemptToPerformOta(const esp_http_client_config_t *config) {
|
||||
OtaUpdateOutcome attemptToPerformOta(const esp_http_client_config_t *config) {
|
||||
esp_http_client_handle_t client = esp_http_client_init(config);
|
||||
if (client == NULL) {
|
||||
Serial.println("Failed to initialize HTTP connection");
|
||||
return -1;
|
||||
return OtaUpdateOutcome::UPDATE_FAILED;
|
||||
}
|
||||
|
||||
esp_err_t err = esp_http_client_open(client, 0);
|
||||
if (err != ESP_OK) {
|
||||
esp_http_client_cleanup(client);
|
||||
Serial.printf("Failed to open HTTP connection: %s\n", esp_err_to_name(err));
|
||||
return -1;
|
||||
Serial.printf("Failed to open HTTP connection: %s\n",
|
||||
esp_err_to_name(err));
|
||||
return OtaUpdateOutcome::UPDATE_FAILED;
|
||||
}
|
||||
esp_http_client_fetch_headers(client);
|
||||
|
||||
int httpStatusCode = esp_http_client_get_status_code(client);
|
||||
if (httpStatusCode == 304) {
|
||||
Serial.println("Firmware is already up to date");
|
||||
cleanupHttp(client);
|
||||
return OtaUpdateOutcome::ALREADY_UP_TO_DATE;
|
||||
} else if (httpStatusCode != 200) {
|
||||
Serial.printf("Firmware update skipped, the server returned %d\n",
|
||||
httpStatusCode);
|
||||
cleanupHttp(client);
|
||||
return OtaUpdateOutcome::UDPATE_SKIPPED;
|
||||
}
|
||||
|
||||
esp_ota_handle_t update_handle = 0;
|
||||
const esp_partition_t *update_partition = NULL;
|
||||
Serial.println("Starting OTA ...");
|
||||
Serial.println("Starting OTA update ...");
|
||||
update_partition = esp_ota_get_next_update_partition(NULL);
|
||||
if (update_partition == NULL) {
|
||||
Serial.println("Passive OTA partition not found");
|
||||
cleanupHttp(client);
|
||||
return ESP_FAIL;
|
||||
return OtaUpdateOutcome::UPDATE_FAILED;
|
||||
}
|
||||
Serial.printf("Writing to partition subtype %d at offset 0x%x\n",
|
||||
update_partition->subtype, update_partition->address);
|
||||
@ -67,34 +113,66 @@ private:
|
||||
if (err != ESP_OK) {
|
||||
Serial.printf("esp_ota_begin failed, error=%d\n", err);
|
||||
cleanupHttp(client);
|
||||
return err;
|
||||
return OtaUpdateOutcome::UPDATE_FAILED;
|
||||
}
|
||||
|
||||
esp_err_t ota_write_err = ESP_OK;
|
||||
char *upgrade_data_buf = (char *)malloc(OTA_BUF_SIZE);
|
||||
if (!upgrade_data_buf) {
|
||||
Serial.println("Couldn't allocate memory for data buffer");
|
||||
return ESP_ERR_NO_MEM;
|
||||
return OtaUpdateOutcome::UPDATE_FAILED;
|
||||
}
|
||||
|
||||
int binary_file_len = 0;
|
||||
int totalSize = esp_http_client_get_content_length(client);
|
||||
Serial.println("File size: " + String(totalSize) + String(" bytes"));
|
||||
|
||||
// Show display start update new firmware.
|
||||
if (this->callback) {
|
||||
this->callback(OtaState::OTA_STATE_BEGIN, "");
|
||||
}
|
||||
|
||||
// Download file and write new firmware to OTA partition
|
||||
uint32_t lastUpdate = millis();
|
||||
while (1) {
|
||||
int data_read = esp_http_client_read(client, upgrade_data_buf, OTA_BUF_SIZE);
|
||||
int data_read =
|
||||
esp_http_client_read(client, upgrade_data_buf, OTA_BUF_SIZE);
|
||||
if (data_read == 0) {
|
||||
if (this->callback) {
|
||||
this->callback(OtaState::OTA_STATE_PROCESSING, String(100));
|
||||
}
|
||||
Serial.println("Connection closed, all data received");
|
||||
break;
|
||||
}
|
||||
if (data_read < 0) {
|
||||
Serial.println("Data read error");
|
||||
if (this->callback) {
|
||||
this->callback(OtaState::OTA_STATE_FAIL, "");
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (data_read > 0) {
|
||||
ota_write_err = esp_ota_write( update_handle, (const void *)upgrade_data_buf, data_read);
|
||||
ota_write_err = esp_ota_write(
|
||||
update_handle, (const void *)upgrade_data_buf, data_read);
|
||||
if (ota_write_err != ESP_OK) {
|
||||
if (this->callback) {
|
||||
this->callback(OtaState::OTA_STATE_FAIL, "");
|
||||
}
|
||||
break;
|
||||
}
|
||||
binary_file_len += data_read;
|
||||
// Serial.printf("Written image length %d\n", binary_file_len);
|
||||
|
||||
int percent = (binary_file_len * 100) / totalSize;
|
||||
uint32_t ms = (uint32_t)(millis() - lastUpdate);
|
||||
if (ms >= 250) {
|
||||
// sm.executeOTA(StateMachine::OtaState::OTA_STATE_PROCESSING, "",
|
||||
// percent);
|
||||
if (this->callback) {
|
||||
this->callback(OtaState::OTA_STATE_PROCESSING,
|
||||
String(percent));
|
||||
}
|
||||
lastUpdate = millis();
|
||||
}
|
||||
}
|
||||
}
|
||||
free(upgrade_data_buf);
|
||||
@ -104,25 +182,25 @@ private:
|
||||
esp_err_t ota_end_err = esp_ota_end(update_handle);
|
||||
if (ota_write_err != ESP_OK) {
|
||||
Serial.printf("Error: esp_ota_write failed! err=0x%d\n", err);
|
||||
return ota_write_err;
|
||||
return OtaUpdateOutcome::UPDATE_FAILED;
|
||||
} else if (ota_end_err != ESP_OK) {
|
||||
Serial.printf("Error: esp_ota_end failed! err=0x%d. Image is invalid", ota_end_err);
|
||||
return ota_end_err;
|
||||
Serial.printf("Error: esp_ota_end failed! err=0x%d. Image is invalid",
|
||||
ota_end_err);
|
||||
return OtaUpdateOutcome::UPDATE_FAILED;
|
||||
}
|
||||
|
||||
err = esp_ota_set_boot_partition(update_partition);
|
||||
if (err != ESP_OK) {
|
||||
Serial.printf("esp_ota_set_boot_partition failed! err=0x%d\n", err);
|
||||
return err;
|
||||
return OtaUpdateOutcome::UPDATE_FAILED;
|
||||
}
|
||||
return 0;
|
||||
return OtaUpdateOutcome::UPDATE_PERFORMED;
|
||||
}
|
||||
|
||||
void cleanupHttp(esp_http_client_handle_t client) {
|
||||
esp_http_client_close(client);
|
||||
esp_http_client_cleanup(client);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
#endif
|
||||
|
@ -1,5 +1,5 @@
|
||||
name=AirGradient Air Quality Sensor
|
||||
version=3.1.0-beta.1
|
||||
version=3.1.7
|
||||
author=AirGradient <support@airgradient.com>
|
||||
maintainer=AirGradient <support@airgradient.com>
|
||||
sentence=ESP32-C3 / ESP8266 library for air quality monitor measuring PM, CO2, Temperature, TVOC and Humidity with OLED display.
|
||||
|
@ -1,7 +1,7 @@
|
||||
# Name, Type, SubType, Offset, Size, Flags
|
||||
nvs, data, nvs, 0x9000, 0x5000,
|
||||
otadata, data, ota, 0xe000, 0x2000,
|
||||
app0, app, ota_0, 0x10000, 0x1E0000,
|
||||
app1, app, ota_1, 0x1F0000,0x1E0000,
|
||||
spiffs, data, spiffs, 0x3D0000,0x20000,
|
||||
coredump, data, coredump,0x3F0000,0x10000,
|
||||
# Name ,Type ,SubType ,Offset ,Size ,Flags
|
||||
nvs ,data ,nvs ,0x9000 ,0x5000 ,
|
||||
otadata ,data ,ota ,0xe000 ,0x2000 ,
|
||||
app0 ,app ,ota_0 ,0x10000 ,0x1E0000 ,
|
||||
app1 ,app ,ota_1 ,0x1F0000 ,0x1E0000 ,
|
||||
spiffs ,data ,spiffs ,0x3D0000 ,0x20000 ,
|
||||
coredump ,data ,coredump ,0x3F0000 ,0x10000 ,
|
||||
|
|
@ -8,7 +8,7 @@
|
||||
; Please visit documentation for the other options and examples
|
||||
; https://docs.platformio.org/page/projectconf.html
|
||||
|
||||
[env:esp32-c3-devkitm-1]
|
||||
[env:esp32-c3]
|
||||
platform = espressif32
|
||||
board = esp32-c3-devkitm-1
|
||||
framework = arduino
|
||||
@ -17,8 +17,35 @@ board_build.partitions = partitions.csv
|
||||
monitor_speed = 115200
|
||||
lib_deps =
|
||||
aglib=symlink://../arduino
|
||||
EEPROM
|
||||
WebServer
|
||||
ESPmDNS
|
||||
FS
|
||||
SPIFFS
|
||||
HTTPClient
|
||||
WiFiClientSecure
|
||||
Update
|
||||
DNSServer
|
||||
|
||||
[env:esp8266]
|
||||
platform = espressif8266
|
||||
board = d1_mini
|
||||
framework = arduino
|
||||
monitor_speed = 115200
|
||||
lib_deps =
|
||||
aglib=symlink://../arduino
|
||||
EEPROM
|
||||
ESP8266HTTPClient
|
||||
ESP8266WebServer
|
||||
DNSServer
|
||||
|
||||
monitor_filters = time
|
||||
|
||||
[platformio]
|
||||
src_dir = examples/OneOpenAir
|
||||
; src_dir = examples/BASIC
|
||||
; src_dir = examples/DiyProIndoorV4_2
|
||||
; src_dir = examples/DiyProIndoorV3_3
|
||||
; src_dir = examples/TestCO2
|
||||
; src_dir = examples/TestPM
|
||||
; src_dir = examples/TestSht
|
||||
|
@ -22,6 +22,7 @@ AgApiClient::~AgApiClient() {}
|
||||
void AgApiClient::begin(void) {
|
||||
getConfigFailed = false;
|
||||
postToServerFailed = false;
|
||||
logInfo("Init apiRoot: " + apiRoot);
|
||||
logInfo("begin");
|
||||
}
|
||||
|
||||
@ -34,7 +35,8 @@ void AgApiClient::begin(void) {
|
||||
*/
|
||||
bool AgApiClient::fetchServerConfiguration(void) {
|
||||
if (config.getConfigurationControl() ==
|
||||
ConfigurationControl::ConfigurationControlLocal) {
|
||||
ConfigurationControl::ConfigurationControlLocal ||
|
||||
config.isOfflineMode()) {
|
||||
logWarning("Ignore fetch server configuration");
|
||||
|
||||
// Clear server configuration failed flag, cause it's ignore but not
|
||||
@ -43,9 +45,8 @@ bool AgApiClient::fetchServerConfiguration(void) {
|
||||
return false;
|
||||
}
|
||||
|
||||
String uri =
|
||||
"http://hw.airgradient.com/sensors/airgradient:" + ag->deviceId() +
|
||||
"/one/config";
|
||||
String uri = apiRoot + "/sensors/airgradient:" +
|
||||
ag->deviceId() + "/one/config";
|
||||
|
||||
/** Init http client */
|
||||
#ifdef ESP8266
|
||||
@ -65,14 +66,24 @@ bool AgApiClient::fetchServerConfiguration(void) {
|
||||
|
||||
/** Get data */
|
||||
int retCode = client.GET();
|
||||
|
||||
logInfo(String("GET: ") + uri);
|
||||
logInfo(String("Return code: ") + String(retCode));
|
||||
|
||||
if (retCode != 200) {
|
||||
client.end();
|
||||
getConfigFailed = true;
|
||||
|
||||
/** Return code 400 mean device not setup on cloud. */
|
||||
if (retCode == 400) {
|
||||
notAvailableOnDashboard = true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/** clear failed */
|
||||
getConfigFailed = false;
|
||||
notAvailableOnDashboard = false;
|
||||
|
||||
/** Get response string */
|
||||
String respContent = client.getString();
|
||||
@ -105,18 +116,23 @@ bool AgApiClient::postToServer(String data) {
|
||||
String uri =
|
||||
"http://hw.airgradient.com/sensors/airgradient:" + ag->deviceId() +
|
||||
"/measures";
|
||||
logInfo("Post uri: " + uri);
|
||||
logInfo("Post data: " + data);
|
||||
// logInfo("Post uri: " + uri);
|
||||
// logInfo("Post data: " + data);
|
||||
|
||||
WiFiClient wifiClient;
|
||||
HTTPClient client;
|
||||
if (client.begin(wifiClient, uri.c_str()) == false) {
|
||||
logError("Init client failed");
|
||||
return false;
|
||||
}
|
||||
client.addHeader("content-type", "application/json");
|
||||
int retCode = client.POST(data);
|
||||
client.end();
|
||||
|
||||
logInfo(String("POST: ") + uri);
|
||||
logInfo(String("DATA: ") + data);
|
||||
logInfo(String("Return code: ") + String(retCode));
|
||||
|
||||
if ((retCode == 200) || (retCode == 429)) {
|
||||
postToServerFailed = false;
|
||||
return true;
|
||||
@ -143,6 +159,17 @@ bool AgApiClient::isFetchConfigureFailed(void) { return getConfigFailed; }
|
||||
*/
|
||||
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; }
|
||||
|
||||
/**
|
||||
@ -159,3 +186,7 @@ bool AgApiClient::sendPing(int rssi, int bootCount) {
|
||||
root["boot"] = bootCount;
|
||||
return postToServer(JSON.stringify(root));
|
||||
}
|
||||
|
||||
String AgApiClient::getApiRoot() const { return apiRoot; }
|
||||
|
||||
void AgApiClient::setApiRoot(const String &apiRoot) { this->apiRoot = apiRoot; }
|
||||
|
@ -20,9 +20,11 @@ class AgApiClient : public PrintLog {
|
||||
private:
|
||||
Configuration &config;
|
||||
AirGradient *ag;
|
||||
String apiRoot = "http://hw.airgradient.com";
|
||||
|
||||
bool getConfigFailed;
|
||||
bool postToServerFailed;
|
||||
bool notAvailableOnDashboard = false; // Device not setup on Airgradient cloud dashboard.
|
||||
|
||||
public:
|
||||
AgApiClient(Stream &stream, Configuration &config);
|
||||
@ -33,8 +35,11 @@ public:
|
||||
bool postToServer(String data);
|
||||
bool isFetchConfigureFailed(void);
|
||||
bool isPostToServerFailed(void);
|
||||
bool isNotAvailableOnDashboard(void);
|
||||
void setAirGradient(AirGradient *ag);
|
||||
bool sendPing(int rssi, int bootCount);
|
||||
String getApiRoot() const;
|
||||
void setApiRoot(const String &apiRoot);
|
||||
};
|
||||
|
||||
#endif /** _AG_API_CLIENT_H_ */
|
||||
|
1113
src/AgConfigure.cpp
1113
src/AgConfigure.cpp
File diff suppressed because it is too large
Load Diff
@ -8,34 +8,17 @@
|
||||
|
||||
class Configuration : public PrintLog {
|
||||
private:
|
||||
struct Config {
|
||||
char model[20];
|
||||
char country[3]; /** Country name has only 2 character, ex: TH = Thailand */
|
||||
char mqttBroker[256]; /** MQTT broker URI */
|
||||
bool inUSAQI; /** If PM standard "ugm3" inUSAQI = false, otherwise is true
|
||||
*/
|
||||
bool inF; /** Temperature unit F */
|
||||
bool postDataToAirGradient; /** If true, monitor will not POST data to
|
||||
airgradient server. Make sure no error
|
||||
message shown on monitor */
|
||||
uint8_t configurationControl; /** If true, configuration from airgradient
|
||||
server will be ignored */
|
||||
bool displayMode; /** true if enable display */
|
||||
uint8_t useRGBLedBar;
|
||||
uint8_t abcDays;
|
||||
int tvocLearningOffset;
|
||||
int noxLearningOffset;
|
||||
char temperatureUnit; // 'f' or 'c'
|
||||
|
||||
uint32_t _check;
|
||||
};
|
||||
struct Config config;
|
||||
bool co2CalibrationRequested;
|
||||
bool ledBarTestRequested;
|
||||
bool udpated;
|
||||
String failedMessage;
|
||||
bool _noxLearnOffsetChanged;
|
||||
bool _tvocLearningOffsetChanged;
|
||||
bool ledBarBrightnessChanged = false;
|
||||
bool displayBrightnessChanged = false;
|
||||
String otaNewFirmwareVersion;
|
||||
bool _offlineMode = false;
|
||||
bool _ledBarModeChanged = false;
|
||||
|
||||
AirGradient* ag;
|
||||
|
||||
@ -49,8 +32,8 @@ private:
|
||||
void jsonInvalid(void);
|
||||
void configLogInfo(String name, String fromValue, String toValue);
|
||||
String getPMStandardString(bool usaqi);
|
||||
String getDisplayModeString(bool dispMode);
|
||||
String getAbcDayString(int value);
|
||||
void toConfig(const char* buf);
|
||||
|
||||
public:
|
||||
Configuration(Stream &debugLog);
|
||||
@ -65,6 +48,7 @@ public:
|
||||
bool begin(void);
|
||||
bool parse(String data, bool isLocal);
|
||||
String toString(void);
|
||||
String toString(AgFirmwareMode fwMode);
|
||||
bool isTemperatureUnitInF(void);
|
||||
String getCountry(void);
|
||||
bool isPmStandardInUSAQI(void);
|
||||
@ -89,6 +73,15 @@ public:
|
||||
String wifiSSID(void);
|
||||
String wifiPass(void);
|
||||
void setAirGradient(AirGradient *ag);
|
||||
bool isLedBarBrightnessChanged(void);
|
||||
int getLedBarBrightness(void);
|
||||
bool isDisplayBrightnessChanged(void);
|
||||
int getDisplayBrightness(void);
|
||||
String newFirmwareVersion(void);
|
||||
bool isOfflineMode(void);
|
||||
void setOfflineMode(bool offline);
|
||||
void setOfflineModeWithoutSave(bool offline);
|
||||
bool isLedBarModeChanged(void);
|
||||
};
|
||||
|
||||
#endif /** _AG_CONFIG_H_ */
|
||||
|
@ -1,6 +1,6 @@
|
||||
#include "AgOledDisplay.h"
|
||||
#include "Libraries/U8g2/src/U8g2lib.h"
|
||||
#include "Libraries/QRCode/src/qrcode.h"
|
||||
#include "Main/utils.h"
|
||||
|
||||
/** Cast U8G2 */
|
||||
#define DISP() ((U8G2_SH1106_128X64_NONAME_F_HW_I2C *)(this->u8g2))
|
||||
@ -11,8 +11,8 @@
|
||||
* @param hasStatus
|
||||
*/
|
||||
void OledDisplay::showTempHum(bool hasStatus) {
|
||||
char buf[10];
|
||||
if (value.Temperature > -1001) {
|
||||
char buf[16];
|
||||
if (utils::isValidTemperature(value.Temperature)) {
|
||||
if (config.isTemperatureUnitInF()) {
|
||||
float tempF = (value.Temperature * 9) / 5 + 32;
|
||||
if (hasStatus) {
|
||||
@ -37,10 +37,10 @@ void OledDisplay::showTempHum(bool hasStatus) {
|
||||
DISP()->drawUTF8(1, 10, buf);
|
||||
|
||||
/** Show humidty */
|
||||
if (value.Humidity >= 0) {
|
||||
if (utils::isValidHumidity(value.Humidity)) {
|
||||
snprintf(buf, sizeof(buf), "%d%%", value.Humidity);
|
||||
} else {
|
||||
snprintf(buf, sizeof(buf), "%-%%");
|
||||
snprintf(buf, sizeof(buf), "-%%");
|
||||
}
|
||||
|
||||
if (value.Humidity > 99) {
|
||||
@ -50,6 +50,15 @@ void OledDisplay::showTempHum(bool hasStatus) {
|
||||
}
|
||||
}
|
||||
|
||||
void OledDisplay::setCentralText(int y, String text) {
|
||||
setCentralText(y, text.c_str());
|
||||
}
|
||||
|
||||
void OledDisplay::setCentralText(int y, const char *text) {
|
||||
int x = (DISP()->getWidth() - DISP()->getStrWidth(text)) / 2;
|
||||
DISP()->drawStr(x, y, text);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Construct a new Ag Oled Display:: Ag Oled Display object
|
||||
*
|
||||
@ -57,7 +66,8 @@ void OledDisplay::showTempHum(bool hasStatus) {
|
||||
* @param value Measurements
|
||||
* @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) {}
|
||||
|
||||
/**
|
||||
@ -81,6 +91,7 @@ bool OledDisplay::begin(void) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (ag->isOne() || ag->isPro3_3() || ag->isPro4_2()) {
|
||||
/** Create u8g2 instance */
|
||||
u8g2 = new U8G2_SH1106_128X64_NONAME_F_HW_I2C(U8G2_R0, U8X8_PIN_NONE);
|
||||
if (u8g2 == NULL) {
|
||||
@ -93,6 +104,20 @@ bool OledDisplay::begin(void) {
|
||||
logError("U8G2 'begin' failed");
|
||||
return false;
|
||||
}
|
||||
} else if (ag->isBasic()) {
|
||||
logInfo("DIY_BASIC init");
|
||||
ag->display.begin(Wire);
|
||||
ag->display.setTextColor(1);
|
||||
ag->display.clear();
|
||||
ag->display.show();
|
||||
}
|
||||
|
||||
/** Show low brightness on startup. then it's completely turn off on main
|
||||
* application */
|
||||
int brightness = config.getDisplayBrightness();
|
||||
if (brightness == 0) {
|
||||
setBrightness(1);
|
||||
}
|
||||
|
||||
isBegin = true;
|
||||
logInfo("begin");
|
||||
@ -109,9 +134,13 @@ void OledDisplay::end(void) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (ag->isOne() || ag->isPro3_3() || ag->isPro4_2()) {
|
||||
/** Free u8g2 */
|
||||
delete DISP();
|
||||
u8g2 = NULL;
|
||||
} else if (ag->isBasic()) {
|
||||
ag->display.end();
|
||||
}
|
||||
|
||||
isBegin = false;
|
||||
logInfo("end");
|
||||
@ -137,6 +166,11 @@ void OledDisplay::setText(String &line1, String &line2, String &line3) {
|
||||
*/
|
||||
void OledDisplay::setText(const char *line1, const char *line2,
|
||||
const char *line3) {
|
||||
if (isDisplayOff) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (ag->isOne() || ag->isPro3_3() || ag->isPro4_2()) {
|
||||
DISP()->firstPage();
|
||||
do {
|
||||
DISP()->setFont(u8g2_font_t0_16_tf);
|
||||
@ -144,6 +178,18 @@ void OledDisplay::setText(const char *line1, const char *line2,
|
||||
DISP()->drawStr(1, 30, line2);
|
||||
DISP()->drawStr(1, 50, line3);
|
||||
} while (DISP()->nextPage());
|
||||
} else if (ag->isBasic()) {
|
||||
ag->display.clear();
|
||||
|
||||
ag->display.setCursor(1, 1);
|
||||
ag->display.setText(line1);
|
||||
ag->display.setCursor(1, 17);
|
||||
ag->display.setText(line2);
|
||||
ag->display.setCursor(1, 33);
|
||||
ag->display.setText(line3);
|
||||
|
||||
ag->display.show();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -169,6 +215,11 @@ void OledDisplay::setText(String &line1, String &line2, String &line3,
|
||||
*/
|
||||
void OledDisplay::setText(const char *line1, const char *line2,
|
||||
const char *line3, const char *line4) {
|
||||
if (isDisplayOff) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (ag->isOne() || ag->isPro3_3() || ag->isPro4_2()) {
|
||||
DISP()->firstPage();
|
||||
do {
|
||||
DISP()->setFont(u8g2_font_t0_16_tf);
|
||||
@ -177,6 +228,16 @@ void OledDisplay::setText(const char *line1, const char *line2,
|
||||
DISP()->drawStr(1, 40, line3);
|
||||
DISP()->drawStr(1, 55, line4);
|
||||
} while (DISP()->nextPage());
|
||||
} else if (ag->isBasic()) {
|
||||
ag->display.clear();
|
||||
ag->display.setCursor(0, 0);
|
||||
ag->display.setText(line1);
|
||||
ag->display.setCursor(0, 10);
|
||||
ag->display.setText(line2);
|
||||
ag->display.setCursor(0, 20);
|
||||
ag->display.setText(line3);
|
||||
ag->display.show();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -190,8 +251,12 @@ void OledDisplay::showDashboard(void) { showDashboard(NULL); }
|
||||
*
|
||||
*/
|
||||
void OledDisplay::showDashboard(const char *status) {
|
||||
char strBuf[10];
|
||||
if (isDisplayOff) {
|
||||
return;
|
||||
}
|
||||
|
||||
char strBuf[16];
|
||||
if (ag->isOne() || ag->isPro3_3() || ag->isPro4_2()) {
|
||||
DISP()->firstPage();
|
||||
do {
|
||||
DISP()->setFont(u8g2_font_t0_16_tf);
|
||||
@ -219,12 +284,8 @@ void OledDisplay::showDashboard(const char *status) {
|
||||
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);
|
||||
if (utils::isValidCO2(value.CO2)) {
|
||||
sprintf(strBuf, "%d", value.CO2);
|
||||
} else {
|
||||
sprintf(strBuf, "%s", "-");
|
||||
}
|
||||
@ -235,76 +296,238 @@ void OledDisplay::showDashboard(const char *status) {
|
||||
DISP()->drawStr(1, 61, "ppm");
|
||||
|
||||
/** Draw vertical line */
|
||||
DISP()->drawLine(45, 14, 45, 64);
|
||||
DISP()->drawLine(82, 14, 82, 64);
|
||||
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(48, 27, "PM2.5");
|
||||
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 (value.pm25_1 >= 0) {
|
||||
sprintf(strBuf, "%d", ag->pms5003.convertPm25ToUsAqi(value.pm25_1));
|
||||
if (utils::isValidPm(pm25)) {
|
||||
sprintf(strBuf, "%d", ag->pms5003.convertPm25ToUsAqi(pm25));
|
||||
} else {
|
||||
sprintf(strBuf, "%s", "-");
|
||||
}
|
||||
DISP()->drawStr(48, 48, strBuf);
|
||||
DISP()->drawStr(55, 48, strBuf);
|
||||
DISP()->setFont(u8g2_font_t0_12_tf);
|
||||
DISP()->drawUTF8(48, 61, "AQI");
|
||||
DISP()->drawUTF8(55, 61, "AQI");
|
||||
} else {
|
||||
if (value.pm25_1 >= 0) {
|
||||
sprintf(strBuf, "%d", value.pm25_1);
|
||||
if (utils::isValidPm(pm25)) {
|
||||
sprintf(strBuf, "%d", pm25);
|
||||
} else {
|
||||
sprintf(strBuf, "%s", "-");
|
||||
}
|
||||
DISP()->drawStr(48, 48, strBuf);
|
||||
DISP()->drawStr(55, 48, strBuf);
|
||||
DISP()->setFont(u8g2_font_t0_12_tf);
|
||||
DISP()->drawUTF8(48, 61, "ug/m³");
|
||||
DISP()->drawUTF8(55, 61, "ug/m³");
|
||||
}
|
||||
|
||||
/** Draw tvocIndexlabel */
|
||||
DISP()->setFont(u8g2_font_t0_12_tf);
|
||||
DISP()->drawStr(85, 27, "tvoc:");
|
||||
DISP()->drawStr(100, 27, "VOC:");
|
||||
|
||||
/** Draw tvocIndexvalue */
|
||||
if (value.TVOC >= 0) {
|
||||
if (utils::isValidVOC(value.TVOC)) {
|
||||
sprintf(strBuf, "%d", value.TVOC);
|
||||
} else {
|
||||
sprintf(strBuf, "%s", "-");
|
||||
}
|
||||
DISP()->drawStr(85, 39, strBuf);
|
||||
DISP()->drawStr(100, 39, strBuf);
|
||||
|
||||
/** Draw NOx label */
|
||||
DISP()->drawStr(85, 53, "NOx:");
|
||||
if (value.NOx >= 0) {
|
||||
DISP()->drawStr(100, 53, "NOx:");
|
||||
if (utils::isValidNOx(value.NOx)) {
|
||||
sprintf(strBuf, "%d", value.NOx);
|
||||
} else {
|
||||
sprintf(strBuf, "%s", "-");
|
||||
}
|
||||
DISP()->drawStr(85, 63, strBuf);
|
||||
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::showWiFiQrCode(String content, String label) {
|
||||
QRCode qrcode;
|
||||
int version = 6;
|
||||
int x_start = (DISP()->getWidth() - (version * 4 + 17))/ 2;
|
||||
uint8_t qrcodeData[qrcode_getBufferSize(version)];
|
||||
qrcode_initText(&qrcode, qrcodeData, version, 0, content.c_str());
|
||||
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) {
|
||||
return;
|
||||
}
|
||||
|
||||
DISP()->firstPage();
|
||||
do {
|
||||
for (uint8_t y = 0; y < qrcode.size; y++) {
|
||||
for (uint8_t x = 0; x < qrcode.size; x++) {
|
||||
if (qrcode_getModule(&qrcode, x, y)) {
|
||||
DISP()->drawPixel(x + x_start, y);
|
||||
}
|
||||
}
|
||||
}
|
||||
DISP()->setFont(u8g2_font_t0_16_tf);
|
||||
x_start = (DISP()->getWidth() - DISP()->getStrWidth(label.c_str()))/2;
|
||||
DISP()->drawStr(x_start, 60, label.c_str());
|
||||
setCentralText(20, "Firmware Update");
|
||||
setCentralText(40, "New version");
|
||||
setCentralText(60, version.c_str());
|
||||
} while (DISP()->nextPage());
|
||||
}
|
||||
|
||||
void OledDisplay::showFirmwareUpdateProgress(int percent) {
|
||||
if (isDisplayOff) {
|
||||
return;
|
||||
}
|
||||
|
||||
DISP()->firstPage();
|
||||
do {
|
||||
DISP()->setFont(u8g2_font_t0_16_tf);
|
||||
setCentralText(20, "Firmware Update");
|
||||
setCentralText(50, String("Updating... ") + String(percent) + String("%"));
|
||||
} while (DISP()->nextPage());
|
||||
}
|
||||
|
||||
void OledDisplay::showFirmwareUpdateSuccess(int count) {
|
||||
if (isDisplayOff) {
|
||||
return;
|
||||
}
|
||||
|
||||
DISP()->firstPage();
|
||||
do {
|
||||
DISP()->setFont(u8g2_font_t0_16_tf);
|
||||
setCentralText(20, "Firmware Update");
|
||||
setCentralText(40, "Success");
|
||||
setCentralText(60, String("Rebooting... ") + String(count));
|
||||
} while (DISP()->nextPage());
|
||||
}
|
||||
|
||||
void OledDisplay::showFirmwareUpdateFailed(void) {
|
||||
if (isDisplayOff) {
|
||||
return;
|
||||
}
|
||||
|
||||
DISP()->firstPage();
|
||||
do {
|
||||
DISP()->setFont(u8g2_font_t0_16_tf);
|
||||
setCentralText(20, "Firmware Update");
|
||||
setCentralText(40, "fail, will retry");
|
||||
// setCentralText(60, "will retry");
|
||||
} while (DISP()->nextPage());
|
||||
}
|
||||
|
||||
void OledDisplay::showFirmwareUpdateSkipped(void) {
|
||||
if (isDisplayOff) {
|
||||
return;
|
||||
}
|
||||
|
||||
DISP()->firstPage();
|
||||
do {
|
||||
DISP()->setFont(u8g2_font_t0_16_tf);
|
||||
setCentralText(20, "Firmware Update");
|
||||
setCentralText(40, "skipped");
|
||||
} while (DISP()->nextPage());
|
||||
}
|
||||
|
||||
void OledDisplay::showFirmwareUpdateUpToDate(void) {
|
||||
if (isDisplayOff) {
|
||||
return;
|
||||
}
|
||||
|
||||
DISP()->firstPage();
|
||||
do {
|
||||
DISP()->setFont(u8g2_font_t0_16_tf);
|
||||
setCentralText(20, "Firmware Update");
|
||||
setCentralText(40, "up to date");
|
||||
} while (DISP()->nextPage());
|
||||
}
|
||||
#else
|
||||
|
||||
#endif
|
||||
|
||||
void OledDisplay::showRebooting(void) {
|
||||
if (ag->isOne() || ag->isPro3_3() || ag->isPro4_2()) {
|
||||
DISP()->firstPage();
|
||||
do {
|
||||
DISP()->setFont(u8g2_font_t0_16_tf);
|
||||
// setCentralText(20, "Firmware Update");
|
||||
setCentralText(40, "Reboot...");
|
||||
// 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();
|
||||
}
|
||||
}
|
||||
|
@ -14,8 +14,12 @@ private:
|
||||
bool isBegin = false;
|
||||
void *u8g2 = NULL;
|
||||
Measurements &value;
|
||||
bool isDisplayOff = false;
|
||||
|
||||
void showTempHum(bool hasStatus);
|
||||
void setCentralText(int y, String text);
|
||||
void setCentralText(int y, const char *text);
|
||||
|
||||
public:
|
||||
OledDisplay(Configuration &config, Measurements &value,
|
||||
Stream &log);
|
||||
@ -31,7 +35,18 @@ public:
|
||||
const char *line4);
|
||||
void showDashboard(void);
|
||||
void showDashboard(const char *status);
|
||||
void showWiFiQrCode(String content, String label);
|
||||
void setBrightness(int percent);
|
||||
#ifdef ESP32
|
||||
void showFirmwareUpdateVersion(String version);
|
||||
void showFirmwareUpdateProgress(int percent);
|
||||
void showFirmwareUpdateSuccess(int count);
|
||||
void showFirmwareUpdateFailed(void);
|
||||
void showFirmwareUpdateSkipped(void);
|
||||
void showFirmwareUpdateUpToDate(void);
|
||||
#else
|
||||
|
||||
#endif
|
||||
void showRebooting(void);
|
||||
};
|
||||
|
||||
#endif /** _AG_OLED_DISPLAY_H_ */
|
||||
|
@ -1,5 +1,6 @@
|
||||
#include "AgStateMachine.h"
|
||||
|
||||
#define LED_TEST_BLINK_DELAY 50 /** ms */
|
||||
#define LED_FAST_BLINK_DELAY 250 /** ms */
|
||||
#define LED_SLOW_BLINK_DELAY 1000 /** ms */
|
||||
#define LED_SHORT_BLINK_DELAY 500 /** ms */
|
||||
@ -7,6 +8,12 @@
|
||||
|
||||
#define SENSOR_CO2_CALIB_COUNTDOWN_MAX 5 /** sec */
|
||||
|
||||
#define RGB_COLOR_R 255, 0, 0 /** Red */
|
||||
#define RGB_COLOR_G 0, 255, 0 /** Green */
|
||||
#define RGB_COLOR_Y 255, 150, 0 /** Yellow */
|
||||
#define RGB_COLOR_O 255, 40, 0 /** Orange */
|
||||
#define RGB_COLOR_P 180, 0, 255 /** Purple */
|
||||
|
||||
/**
|
||||
* @brief Animation LED bar with color
|
||||
*
|
||||
@ -63,80 +70,69 @@ void StateMachine::sensorhandleLeds(void) {
|
||||
*/
|
||||
void StateMachine::co2handleLeds(void) {
|
||||
int co2Value = value.CO2;
|
||||
if (co2Value <= 400) {
|
||||
if (co2Value <= 600) {
|
||||
/** G; 1 */
|
||||
ag->ledBar.setColor(0, 255, 0, ag->ledBar.getNumberOfLeds() - 1);
|
||||
} else if (co2Value <= 700) {
|
||||
ag->ledBar.setColor(RGB_COLOR_G, ag->ledBar.getNumberOfLeds() - 1);
|
||||
} else if (co2Value <= 800) {
|
||||
/** GG; 2 */
|
||||
ag->ledBar.setColor(0, 255, 0, ag->ledBar.getNumberOfLeds() - 1);
|
||||
ag->ledBar.setColor(0, 255, 0, ag->ledBar.getNumberOfLeds() - 2);
|
||||
ag->ledBar.setColor(RGB_COLOR_G, ag->ledBar.getNumberOfLeds() - 1);
|
||||
ag->ledBar.setColor(RGB_COLOR_G, ag->ledBar.getNumberOfLeds() - 2);
|
||||
} else if (co2Value <= 1000) {
|
||||
/** YYY; 3 */
|
||||
ag->ledBar.setColor(255, 255, 0, ag->ledBar.getNumberOfLeds() - 1);
|
||||
ag->ledBar.setColor(255, 255, 0, ag->ledBar.getNumberOfLeds() - 2);
|
||||
ag->ledBar.setColor(255, 255, 0, ag->ledBar.getNumberOfLeds() - 3);
|
||||
} else if (co2Value <= 1333) {
|
||||
/** YYYY; 4 */
|
||||
ag->ledBar.setColor(255, 255, 0, ag->ledBar.getNumberOfLeds() - 1);
|
||||
ag->ledBar.setColor(255, 255, 0, ag->ledBar.getNumberOfLeds() - 2);
|
||||
ag->ledBar.setColor(255, 255, 0, ag->ledBar.getNumberOfLeds() - 3);
|
||||
ag->ledBar.setColor(255, 255, 0, ag->ledBar.getNumberOfLeds() - 4);
|
||||
} else if (co2Value <= 1666) {
|
||||
/** YYYYY; 5 */
|
||||
ag->ledBar.setColor(255, 255, 0, ag->ledBar.getNumberOfLeds() - 1);
|
||||
ag->ledBar.setColor(255, 255, 0, ag->ledBar.getNumberOfLeds() - 2);
|
||||
ag->ledBar.setColor(255, 255, 0, ag->ledBar.getNumberOfLeds() - 3);
|
||||
ag->ledBar.setColor(255, 255, 0, ag->ledBar.getNumberOfLeds() - 4);
|
||||
ag->ledBar.setColor(255, 255, 0, ag->ledBar.getNumberOfLeds() - 5);
|
||||
} else if (co2Value <= 2000) {
|
||||
ag->ledBar.setColor(RGB_COLOR_Y, ag->ledBar.getNumberOfLeds() - 1);
|
||||
ag->ledBar.setColor(RGB_COLOR_Y, ag->ledBar.getNumberOfLeds() - 2);
|
||||
ag->ledBar.setColor(RGB_COLOR_Y, ag->ledBar.getNumberOfLeds() - 3);
|
||||
} else if (co2Value <= 1250) {
|
||||
/** OOOO; 4 */
|
||||
ag->ledBar.setColor(RGB_COLOR_O, ag->ledBar.getNumberOfLeds() - 1);
|
||||
ag->ledBar.setColor(RGB_COLOR_O, ag->ledBar.getNumberOfLeds() - 2);
|
||||
ag->ledBar.setColor(RGB_COLOR_O, ag->ledBar.getNumberOfLeds() - 3);
|
||||
ag->ledBar.setColor(RGB_COLOR_O, ag->ledBar.getNumberOfLeds() - 4);
|
||||
} else if (co2Value <= 1500) {
|
||||
/** OOOOO; 5 */
|
||||
ag->ledBar.setColor(RGB_COLOR_O, ag->ledBar.getNumberOfLeds() - 1);
|
||||
ag->ledBar.setColor(RGB_COLOR_O, ag->ledBar.getNumberOfLeds() - 2);
|
||||
ag->ledBar.setColor(RGB_COLOR_O, ag->ledBar.getNumberOfLeds() - 3);
|
||||
ag->ledBar.setColor(RGB_COLOR_O, ag->ledBar.getNumberOfLeds() - 4);
|
||||
ag->ledBar.setColor(RGB_COLOR_O, ag->ledBar.getNumberOfLeds() - 5);
|
||||
} else if (co2Value <= 1750) {
|
||||
/** RRRRRR; 6 */
|
||||
ag->ledBar.setColor(255, 0, 0, ag->ledBar.getNumberOfLeds() - 1);
|
||||
ag->ledBar.setColor(255, 0, 0, ag->ledBar.getNumberOfLeds() - 2);
|
||||
ag->ledBar.setColor(255, 0, 0, ag->ledBar.getNumberOfLeds() - 3);
|
||||
ag->ledBar.setColor(255, 0, 0, ag->ledBar.getNumberOfLeds() - 4);
|
||||
ag->ledBar.setColor(255, 0, 0, ag->ledBar.getNumberOfLeds() - 5);
|
||||
ag->ledBar.setColor(255, 0, 0, ag->ledBar.getNumberOfLeds() - 6);
|
||||
} else if (co2Value <= 2666) {
|
||||
ag->ledBar.setColor(RGB_COLOR_R, ag->ledBar.getNumberOfLeds() - 1);
|
||||
ag->ledBar.setColor(RGB_COLOR_R, ag->ledBar.getNumberOfLeds() - 2);
|
||||
ag->ledBar.setColor(RGB_COLOR_R, ag->ledBar.getNumberOfLeds() - 3);
|
||||
ag->ledBar.setColor(RGB_COLOR_R, ag->ledBar.getNumberOfLeds() - 4);
|
||||
ag->ledBar.setColor(RGB_COLOR_R, ag->ledBar.getNumberOfLeds() - 5);
|
||||
ag->ledBar.setColor(RGB_COLOR_R, ag->ledBar.getNumberOfLeds() - 6);
|
||||
} else if (co2Value <= 2000) {
|
||||
/** RRRRRRR; 7 */
|
||||
ag->ledBar.setColor(255, 0, 0, ag->ledBar.getNumberOfLeds() - 1);
|
||||
ag->ledBar.setColor(255, 0, 0, ag->ledBar.getNumberOfLeds() - 2);
|
||||
ag->ledBar.setColor(255, 0, 0, ag->ledBar.getNumberOfLeds() - 3);
|
||||
ag->ledBar.setColor(255, 0, 0, ag->ledBar.getNumberOfLeds() - 4);
|
||||
ag->ledBar.setColor(255, 0, 0, ag->ledBar.getNumberOfLeds() - 5);
|
||||
ag->ledBar.setColor(255, 0, 0, ag->ledBar.getNumberOfLeds() - 6);
|
||||
ag->ledBar.setColor(255, 0, 0, ag->ledBar.getNumberOfLeds() - 7);
|
||||
} else if (co2Value <= 3333) {
|
||||
/** RRRRRRRR; 8 */
|
||||
ag->ledBar.setColor(255, 0, 0, ag->ledBar.getNumberOfLeds() - 1);
|
||||
ag->ledBar.setColor(255, 0, 0, ag->ledBar.getNumberOfLeds() - 2);
|
||||
ag->ledBar.setColor(255, 0, 0, ag->ledBar.getNumberOfLeds() - 3);
|
||||
ag->ledBar.setColor(255, 0, 0, ag->ledBar.getNumberOfLeds() - 4);
|
||||
ag->ledBar.setColor(255, 0, 0, ag->ledBar.getNumberOfLeds() - 5);
|
||||
ag->ledBar.setColor(255, 0, 0, ag->ledBar.getNumberOfLeds() - 6);
|
||||
ag->ledBar.setColor(255, 0, 0, ag->ledBar.getNumberOfLeds() - 7);
|
||||
ag->ledBar.setColor(255, 0, 0, ag->ledBar.getNumberOfLeds() - 8);
|
||||
} else if (co2Value <= 4000) {
|
||||
/** RRRRRRRRR; 9 */
|
||||
ag->ledBar.setColor(255, 0, 0, ag->ledBar.getNumberOfLeds() - 1);
|
||||
ag->ledBar.setColor(255, 0, 0, ag->ledBar.getNumberOfLeds() - 2);
|
||||
ag->ledBar.setColor(255, 0, 0, ag->ledBar.getNumberOfLeds() - 3);
|
||||
ag->ledBar.setColor(255, 0, 0, ag->ledBar.getNumberOfLeds() - 4);
|
||||
ag->ledBar.setColor(255, 0, 0, ag->ledBar.getNumberOfLeds() - 5);
|
||||
ag->ledBar.setColor(255, 0, 0, ag->ledBar.getNumberOfLeds() - 6);
|
||||
ag->ledBar.setColor(255, 0, 0, ag->ledBar.getNumberOfLeds() - 7);
|
||||
ag->ledBar.setColor(255, 0, 0, ag->ledBar.getNumberOfLeds() - 8);
|
||||
ag->ledBar.setColor(255, 0, 0, ag->ledBar.getNumberOfLeds() - 9);
|
||||
} else { /** > 4000 */
|
||||
ag->ledBar.setColor(RGB_COLOR_R, ag->ledBar.getNumberOfLeds() - 1);
|
||||
ag->ledBar.setColor(RGB_COLOR_R, ag->ledBar.getNumberOfLeds() - 2);
|
||||
ag->ledBar.setColor(RGB_COLOR_R, ag->ledBar.getNumberOfLeds() - 3);
|
||||
ag->ledBar.setColor(RGB_COLOR_R, ag->ledBar.getNumberOfLeds() - 4);
|
||||
ag->ledBar.setColor(RGB_COLOR_R, ag->ledBar.getNumberOfLeds() - 5);
|
||||
ag->ledBar.setColor(RGB_COLOR_R, ag->ledBar.getNumberOfLeds() - 6);
|
||||
ag->ledBar.setColor(RGB_COLOR_R, ag->ledBar.getNumberOfLeds() - 7);
|
||||
} else if (co2Value <= 3000) {
|
||||
/** PPPPPPPP; 8 */
|
||||
ag->ledBar.setColor(RGB_COLOR_P, ag->ledBar.getNumberOfLeds() - 1);
|
||||
ag->ledBar.setColor(RGB_COLOR_P, ag->ledBar.getNumberOfLeds() - 2);
|
||||
ag->ledBar.setColor(RGB_COLOR_P, ag->ledBar.getNumberOfLeds() - 3);
|
||||
ag->ledBar.setColor(RGB_COLOR_P, ag->ledBar.getNumberOfLeds() - 4);
|
||||
ag->ledBar.setColor(RGB_COLOR_P, ag->ledBar.getNumberOfLeds() - 5);
|
||||
ag->ledBar.setColor(RGB_COLOR_P, ag->ledBar.getNumberOfLeds() - 6);
|
||||
ag->ledBar.setColor(RGB_COLOR_P, ag->ledBar.getNumberOfLeds() - 7);
|
||||
ag->ledBar.setColor(RGB_COLOR_P, ag->ledBar.getNumberOfLeds() - 8);
|
||||
} else { /** > 3000 */
|
||||
/* PRPRPRPRP; 9 */
|
||||
ag->ledBar.setColor(153, 153, 0, ag->ledBar.getNumberOfLeds() - 1);
|
||||
ag->ledBar.setColor(255, 0, 0, ag->ledBar.getNumberOfLeds() - 2);
|
||||
ag->ledBar.setColor(153, 153, 0, ag->ledBar.getNumberOfLeds() - 3);
|
||||
ag->ledBar.setColor(255, 0, 0, ag->ledBar.getNumberOfLeds() - 4);
|
||||
ag->ledBar.setColor(153, 153, 0, ag->ledBar.getNumberOfLeds() - 5);
|
||||
ag->ledBar.setColor(255, 0, 0, ag->ledBar.getNumberOfLeds() - 6);
|
||||
ag->ledBar.setColor(153, 153, 0, ag->ledBar.getNumberOfLeds() - 7);
|
||||
ag->ledBar.setColor(255, 0, 0, ag->ledBar.getNumberOfLeds() - 8);
|
||||
ag->ledBar.setColor(153, 153, 0, ag->ledBar.getNumberOfLeds() - 9);
|
||||
ag->ledBar.setColor(RGB_COLOR_P, ag->ledBar.getNumberOfLeds() - 1);
|
||||
ag->ledBar.setColor(RGB_COLOR_R, ag->ledBar.getNumberOfLeds() - 2);
|
||||
ag->ledBar.setColor(RGB_COLOR_P, ag->ledBar.getNumberOfLeds() - 3);
|
||||
ag->ledBar.setColor(RGB_COLOR_R, ag->ledBar.getNumberOfLeds() - 4);
|
||||
ag->ledBar.setColor(RGB_COLOR_P, ag->ledBar.getNumberOfLeds() - 5);
|
||||
ag->ledBar.setColor(RGB_COLOR_R, ag->ledBar.getNumberOfLeds() - 6);
|
||||
ag->ledBar.setColor(RGB_COLOR_P, ag->ledBar.getNumberOfLeds() - 7);
|
||||
ag->ledBar.setColor(RGB_COLOR_R, ag->ledBar.getNumberOfLeds() - 8);
|
||||
ag->ledBar.setColor(RGB_COLOR_P, ag->ledBar.getNumberOfLeds() - 9);
|
||||
}
|
||||
}
|
||||
|
||||
@ -146,80 +142,80 @@ void StateMachine::co2handleLeds(void) {
|
||||
*/
|
||||
void StateMachine::pm25handleLeds(void) {
|
||||
int pm25Value = value.pm25_1;
|
||||
if (pm25Value <= 5) {
|
||||
if (pm25Value < 5) {
|
||||
/** G; 1 */
|
||||
ag->ledBar.setColor(0, 255, 0, ag->ledBar.getNumberOfLeds() - 1);
|
||||
} else if (pm25Value <= 10) {
|
||||
ag->ledBar.setColor(RGB_COLOR_G, ag->ledBar.getNumberOfLeds() - 1);
|
||||
} else if (pm25Value < 10) {
|
||||
/** GG; 2 */
|
||||
ag->ledBar.setColor(0, 255, 0, ag->ledBar.getNumberOfLeds() - 1);
|
||||
ag->ledBar.setColor(0, 255, 0, ag->ledBar.getNumberOfLeds() - 2);
|
||||
} else if (pm25Value <= 20) {
|
||||
ag->ledBar.setColor(RGB_COLOR_G, ag->ledBar.getNumberOfLeds() - 1);
|
||||
ag->ledBar.setColor(RGB_COLOR_G, ag->ledBar.getNumberOfLeds() - 2);
|
||||
} else if (pm25Value < 20) {
|
||||
/** YYY; 3 */
|
||||
ag->ledBar.setColor(255, 255, 0, ag->ledBar.getNumberOfLeds() - 1);
|
||||
ag->ledBar.setColor(255, 255, 0, ag->ledBar.getNumberOfLeds() - 2);
|
||||
ag->ledBar.setColor(255, 255, 0, ag->ledBar.getNumberOfLeds() - 3);
|
||||
} else if (pm25Value <= 35) {
|
||||
ag->ledBar.setColor(RGB_COLOR_Y, ag->ledBar.getNumberOfLeds() - 1);
|
||||
ag->ledBar.setColor(RGB_COLOR_Y, ag->ledBar.getNumberOfLeds() - 2);
|
||||
ag->ledBar.setColor(RGB_COLOR_Y, ag->ledBar.getNumberOfLeds() - 3);
|
||||
} else if (pm25Value < 35) {
|
||||
/** YYYY; 4 */
|
||||
ag->ledBar.setColor(255, 255, 0, ag->ledBar.getNumberOfLeds() - 1);
|
||||
ag->ledBar.setColor(255, 255, 0, ag->ledBar.getNumberOfLeds() - 2);
|
||||
ag->ledBar.setColor(255, 255, 0, ag->ledBar.getNumberOfLeds() - 3);
|
||||
ag->ledBar.setColor(255, 255, 0, ag->ledBar.getNumberOfLeds() - 4);
|
||||
} else if (pm25Value <= 45) {
|
||||
/** YYYYY; 5 */
|
||||
ag->ledBar.setColor(255, 255, 0, ag->ledBar.getNumberOfLeds() - 1);
|
||||
ag->ledBar.setColor(255, 255, 0, ag->ledBar.getNumberOfLeds() - 2);
|
||||
ag->ledBar.setColor(255, 255, 0, ag->ledBar.getNumberOfLeds() - 3);
|
||||
ag->ledBar.setColor(255, 255, 0, ag->ledBar.getNumberOfLeds() - 4);
|
||||
ag->ledBar.setColor(255, 255, 0, ag->ledBar.getNumberOfLeds() - 5);
|
||||
} else if (pm25Value <= 55) {
|
||||
/** RRRRRR; 6 */
|
||||
ag->ledBar.setColor(255, 0, 0, ag->ledBar.getNumberOfLeds() - 1);
|
||||
ag->ledBar.setColor(255, 0, 0, ag->ledBar.getNumberOfLeds() - 2);
|
||||
ag->ledBar.setColor(255, 0, 0, ag->ledBar.getNumberOfLeds() - 3);
|
||||
ag->ledBar.setColor(255, 0, 0, ag->ledBar.getNumberOfLeds() - 4);
|
||||
ag->ledBar.setColor(255, 0, 0, ag->ledBar.getNumberOfLeds() - 5);
|
||||
ag->ledBar.setColor(255, 0, 0, ag->ledBar.getNumberOfLeds() - 6);
|
||||
} else if (pm25Value <= 65) {
|
||||
ag->ledBar.setColor(RGB_COLOR_Y, ag->ledBar.getNumberOfLeds() - 1);
|
||||
ag->ledBar.setColor(RGB_COLOR_Y, ag->ledBar.getNumberOfLeds() - 2);
|
||||
ag->ledBar.setColor(RGB_COLOR_Y, ag->ledBar.getNumberOfLeds() - 3);
|
||||
ag->ledBar.setColor(RGB_COLOR_Y, ag->ledBar.getNumberOfLeds() - 4);
|
||||
} else if (pm25Value < 45) {
|
||||
/** OOOOO; 5 */
|
||||
ag->ledBar.setColor(RGB_COLOR_O, ag->ledBar.getNumberOfLeds() - 1);
|
||||
ag->ledBar.setColor(RGB_COLOR_O, ag->ledBar.getNumberOfLeds() - 2);
|
||||
ag->ledBar.setColor(RGB_COLOR_O, ag->ledBar.getNumberOfLeds() - 3);
|
||||
ag->ledBar.setColor(RGB_COLOR_O, ag->ledBar.getNumberOfLeds() - 4);
|
||||
ag->ledBar.setColor(RGB_COLOR_O, ag->ledBar.getNumberOfLeds() - 5);
|
||||
} else if (pm25Value < 55) {
|
||||
/** OOOOOO; 6 */
|
||||
ag->ledBar.setColor(RGB_COLOR_O, ag->ledBar.getNumberOfLeds() - 1);
|
||||
ag->ledBar.setColor(RGB_COLOR_O, ag->ledBar.getNumberOfLeds() - 2);
|
||||
ag->ledBar.setColor(RGB_COLOR_O, ag->ledBar.getNumberOfLeds() - 3);
|
||||
ag->ledBar.setColor(RGB_COLOR_O, ag->ledBar.getNumberOfLeds() - 4);
|
||||
ag->ledBar.setColor(RGB_COLOR_O, ag->ledBar.getNumberOfLeds() - 5);
|
||||
ag->ledBar.setColor(RGB_COLOR_O, ag->ledBar.getNumberOfLeds() - 6);
|
||||
} else if (pm25Value < 100) {
|
||||
/** RRRRRRR; 7 */
|
||||
ag->ledBar.setColor(255, 0, 0, ag->ledBar.getNumberOfLeds() - 1);
|
||||
ag->ledBar.setColor(255, 0, 0, ag->ledBar.getNumberOfLeds() - 2);
|
||||
ag->ledBar.setColor(255, 0, 0, ag->ledBar.getNumberOfLeds() - 3);
|
||||
ag->ledBar.setColor(255, 0, 0, ag->ledBar.getNumberOfLeds() - 4);
|
||||
ag->ledBar.setColor(255, 0, 0, ag->ledBar.getNumberOfLeds() - 5);
|
||||
ag->ledBar.setColor(255, 0, 0, ag->ledBar.getNumberOfLeds() - 6);
|
||||
ag->ledBar.setColor(255, 0, 0, ag->ledBar.getNumberOfLeds() - 7);
|
||||
} else if (pm25Value <= 150) {
|
||||
ag->ledBar.setColor(RGB_COLOR_R, ag->ledBar.getNumberOfLeds() - 1);
|
||||
ag->ledBar.setColor(RGB_COLOR_R, ag->ledBar.getNumberOfLeds() - 2);
|
||||
ag->ledBar.setColor(RGB_COLOR_R, ag->ledBar.getNumberOfLeds() - 3);
|
||||
ag->ledBar.setColor(RGB_COLOR_R, ag->ledBar.getNumberOfLeds() - 4);
|
||||
ag->ledBar.setColor(RGB_COLOR_R, ag->ledBar.getNumberOfLeds() - 5);
|
||||
ag->ledBar.setColor(RGB_COLOR_R, ag->ledBar.getNumberOfLeds() - 6);
|
||||
ag->ledBar.setColor(RGB_COLOR_R, ag->ledBar.getNumberOfLeds() - 7);
|
||||
} else if (pm25Value < 200) {
|
||||
/** RRRRRRRR; 8 */
|
||||
ag->ledBar.setColor(255, 0, 0, ag->ledBar.getNumberOfLeds() - 1);
|
||||
ag->ledBar.setColor(255, 0, 0, ag->ledBar.getNumberOfLeds() - 2);
|
||||
ag->ledBar.setColor(255, 0, 0, ag->ledBar.getNumberOfLeds() - 3);
|
||||
ag->ledBar.setColor(255, 0, 0, ag->ledBar.getNumberOfLeds() - 4);
|
||||
ag->ledBar.setColor(255, 0, 0, ag->ledBar.getNumberOfLeds() - 5);
|
||||
ag->ledBar.setColor(255, 0, 0, ag->ledBar.getNumberOfLeds() - 6);
|
||||
ag->ledBar.setColor(255, 0, 0, ag->ledBar.getNumberOfLeds() - 7);
|
||||
ag->ledBar.setColor(255, 0, 0, ag->ledBar.getNumberOfLeds() - 8);
|
||||
} else if (pm25Value <= 250) {
|
||||
/** RRRRRRRRR; 9 */
|
||||
ag->ledBar.setColor(255, 0, 0, ag->ledBar.getNumberOfLeds() - 1);
|
||||
ag->ledBar.setColor(255, 0, 0, ag->ledBar.getNumberOfLeds() - 2);
|
||||
ag->ledBar.setColor(255, 0, 0, ag->ledBar.getNumberOfLeds() - 3);
|
||||
ag->ledBar.setColor(255, 0, 0, ag->ledBar.getNumberOfLeds() - 4);
|
||||
ag->ledBar.setColor(255, 0, 0, ag->ledBar.getNumberOfLeds() - 5);
|
||||
ag->ledBar.setColor(255, 0, 0, ag->ledBar.getNumberOfLeds() - 6);
|
||||
ag->ledBar.setColor(255, 0, 0, ag->ledBar.getNumberOfLeds() - 7);
|
||||
ag->ledBar.setColor(255, 0, 0, ag->ledBar.getNumberOfLeds() - 8);
|
||||
ag->ledBar.setColor(255, 0, 0, ag->ledBar.getNumberOfLeds() - 9);
|
||||
ag->ledBar.setColor(RGB_COLOR_R, ag->ledBar.getNumberOfLeds() - 1);
|
||||
ag->ledBar.setColor(RGB_COLOR_R, ag->ledBar.getNumberOfLeds() - 2);
|
||||
ag->ledBar.setColor(RGB_COLOR_R, ag->ledBar.getNumberOfLeds() - 3);
|
||||
ag->ledBar.setColor(RGB_COLOR_R, ag->ledBar.getNumberOfLeds() - 4);
|
||||
ag->ledBar.setColor(RGB_COLOR_R, ag->ledBar.getNumberOfLeds() - 5);
|
||||
ag->ledBar.setColor(RGB_COLOR_R, ag->ledBar.getNumberOfLeds() - 6);
|
||||
ag->ledBar.setColor(RGB_COLOR_R, ag->ledBar.getNumberOfLeds() - 7);
|
||||
ag->ledBar.setColor(RGB_COLOR_R, ag->ledBar.getNumberOfLeds() - 8);
|
||||
} else if (pm25Value < 250) {
|
||||
/** PPPPPPPPP; 9 */
|
||||
ag->ledBar.setColor(RGB_COLOR_P, ag->ledBar.getNumberOfLeds() - 1);
|
||||
ag->ledBar.setColor(RGB_COLOR_P, ag->ledBar.getNumberOfLeds() - 2);
|
||||
ag->ledBar.setColor(RGB_COLOR_P, ag->ledBar.getNumberOfLeds() - 3);
|
||||
ag->ledBar.setColor(RGB_COLOR_P, ag->ledBar.getNumberOfLeds() - 4);
|
||||
ag->ledBar.setColor(RGB_COLOR_P, ag->ledBar.getNumberOfLeds() - 5);
|
||||
ag->ledBar.setColor(RGB_COLOR_P, ag->ledBar.getNumberOfLeds() - 6);
|
||||
ag->ledBar.setColor(RGB_COLOR_P, ag->ledBar.getNumberOfLeds() - 7);
|
||||
ag->ledBar.setColor(RGB_COLOR_P, ag->ledBar.getNumberOfLeds() - 8);
|
||||
ag->ledBar.setColor(RGB_COLOR_P, ag->ledBar.getNumberOfLeds() - 9);
|
||||
} else { /** > 250 */
|
||||
/* PRPRPRPRP; 9 */
|
||||
ag->ledBar.setColor(153, 153, 0, ag->ledBar.getNumberOfLeds() - 1);
|
||||
ag->ledBar.setColor(255, 0, 0, ag->ledBar.getNumberOfLeds() - 2);
|
||||
ag->ledBar.setColor(153, 153, 0, ag->ledBar.getNumberOfLeds() - 3);
|
||||
ag->ledBar.setColor(255, 0, 0, ag->ledBar.getNumberOfLeds() - 4);
|
||||
ag->ledBar.setColor(153, 153, 0, ag->ledBar.getNumberOfLeds() - 5);
|
||||
ag->ledBar.setColor(255, 0, 0, ag->ledBar.getNumberOfLeds() - 6);
|
||||
ag->ledBar.setColor(153, 153, 0, ag->ledBar.getNumberOfLeds() - 7);
|
||||
ag->ledBar.setColor(255, 0, 0, ag->ledBar.getNumberOfLeds() - 8);
|
||||
ag->ledBar.setColor(153, 153, 0, ag->ledBar.getNumberOfLeds() - 9);
|
||||
ag->ledBar.setColor(RGB_COLOR_P, ag->ledBar.getNumberOfLeds() - 1);
|
||||
ag->ledBar.setColor(RGB_COLOR_R, ag->ledBar.getNumberOfLeds() - 2);
|
||||
ag->ledBar.setColor(RGB_COLOR_P, ag->ledBar.getNumberOfLeds() - 3);
|
||||
ag->ledBar.setColor(RGB_COLOR_R, ag->ledBar.getNumberOfLeds() - 4);
|
||||
ag->ledBar.setColor(RGB_COLOR_P, ag->ledBar.getNumberOfLeds() - 5);
|
||||
ag->ledBar.setColor(RGB_COLOR_R, ag->ledBar.getNumberOfLeds() - 6);
|
||||
ag->ledBar.setColor(RGB_COLOR_P, ag->ledBar.getNumberOfLeds() - 7);
|
||||
ag->ledBar.setColor(RGB_COLOR_R, ag->ledBar.getNumberOfLeds() - 8);
|
||||
ag->ledBar.setColor(RGB_COLOR_P, ag->ledBar.getNumberOfLeds() - 9);
|
||||
}
|
||||
}
|
||||
|
||||
@ -229,10 +225,13 @@ void StateMachine::co2Calibration(void) {
|
||||
|
||||
/** Count down to 0 then start */
|
||||
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 =
|
||||
"after " + String(SENSOR_CO2_CALIB_COUNTDOWN_MAX - i) + " sec";
|
||||
disp.setText("Start CO2 calib", str.c_str(), "");
|
||||
} else if (ag->isBasic()) {
|
||||
String str = String(SENSOR_CO2_CALIB_COUNTDOWN_MAX - i) + " sec";
|
||||
disp.setText("CO2 Calib", "after", str.c_str());
|
||||
} else {
|
||||
logInfo("Start CO2 calib after " +
|
||||
String(SENSOR_CO2_CALIB_COUNTDOWN_MAX - i) + " sec");
|
||||
@ -241,14 +240,16 @@ void StateMachine::co2Calibration(void) {
|
||||
}
|
||||
|
||||
if (ag->s8.setBaselineCalibration()) {
|
||||
if (ag->isOne()) {
|
||||
if (ag->isOne() || (ag->isPro4_2()) || ag->isPro3_3()) {
|
||||
disp.setText("Calibration", "success", "");
|
||||
} else if (ag->isBasic()) {
|
||||
disp.setText("CO2 Calib", "success", "");
|
||||
} else {
|
||||
logInfo("CO2 Calibration: success");
|
||||
}
|
||||
delay(1000);
|
||||
if (ag->isOne()) {
|
||||
disp.setText("Wait for", "calib finish", "...");
|
||||
if (ag->isOne() || (ag->isPro4_2()) || ag->isPro3_3() || ag->isBasic()) {
|
||||
disp.setText("Wait for", "calib done", "...");
|
||||
} else {
|
||||
logInfo("CO2 Calibration: Wait for calibration finish...");
|
||||
}
|
||||
@ -259,16 +260,18 @@ void StateMachine::co2Calibration(void) {
|
||||
delay(1000);
|
||||
count++;
|
||||
}
|
||||
if (ag->isOne()) {
|
||||
if (ag->isOne() || (ag->isPro4_2()) || ag->isPro3_3() || ag->isBasic()) {
|
||||
String str = "after " + String(count);
|
||||
disp.setText("Calib finish", str.c_str(), "sec");
|
||||
disp.setText("Calib done", str.c_str(), "sec");
|
||||
} else {
|
||||
logInfo("CO2 Calibration: finish after " + String(count) + " sec");
|
||||
}
|
||||
delay(2000);
|
||||
} else {
|
||||
if (ag->isOne()) {
|
||||
if (ag->isOne() || (ag->isPro4_2()) || ag->isPro3_3()) {
|
||||
disp.setText("Calibration", "failure!!!", "");
|
||||
} else if (ag->isBasic()) {
|
||||
disp.setText("CO2 calib", "failure!!!", "");
|
||||
} else {
|
||||
logInfo("CO2 Calibration: failure!!!");
|
||||
}
|
||||
@ -284,15 +287,16 @@ void StateMachine::co2Calibration(void) {
|
||||
if (ag->s8.setAbcPeriod(config.getCO2CalibrationAbcDays() * 24)) {
|
||||
resultStr = "successful";
|
||||
}
|
||||
String fromStr = String(curHour/24) + " days";
|
||||
if(curHour == 0){
|
||||
String fromStr = String(curHour / 24) + " days";
|
||||
if (curHour == 0) {
|
||||
fromStr = "off";
|
||||
}
|
||||
String toStr = String(config.getCO2CalibrationAbcDays()) + " days";
|
||||
if(config.getCO2CalibrationAbcDays() == 0) {
|
||||
if (config.getCO2CalibrationAbcDays() == 0) {
|
||||
toStr = "off";
|
||||
}
|
||||
String msg = "Setting S8 from " + fromStr + " to " + toStr + " " + resultStr;
|
||||
String msg =
|
||||
"Setting S8 from " + fromStr + " to " + toStr + " " + resultStr;
|
||||
logInfo(msg);
|
||||
}
|
||||
} else {
|
||||
@ -302,6 +306,7 @@ void StateMachine::co2Calibration(void) {
|
||||
|
||||
void StateMachine::ledBarTest(void) {
|
||||
if (config.isLedBarTestRequested()) {
|
||||
if (ag->isOne()) {
|
||||
if (config.getCountry() == "TH") {
|
||||
uint32_t tstart = millis();
|
||||
logInfo("Start run LED test for 2 min");
|
||||
@ -317,9 +322,16 @@ void StateMachine::ledBarTest(void) {
|
||||
ledBarRunTest();
|
||||
}
|
||||
}
|
||||
else if(ag->isOpenAir()) {
|
||||
ledBarRunTest();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void StateMachine::ledBarPowerUpTest(void) { ledBarRunTest(); }
|
||||
|
||||
void StateMachine::ledBarRunTest(void) {
|
||||
if (ag->isOne()) {
|
||||
disp.setText("LED Test", "running", ".....");
|
||||
runLedTest('r');
|
||||
ag->ledBar.show();
|
||||
@ -336,6 +348,14 @@ void StateMachine::ledBarRunTest(void) {
|
||||
runLedTest('n');
|
||||
ag->ledBar.show();
|
||||
delay(1000);
|
||||
} else if (ag->isOpenAir()) {
|
||||
for (int i = 0; i < 100; i++) {
|
||||
ag->statusLed.setOn();
|
||||
delay(LED_TEST_BLINK_DELAY);
|
||||
ag->statusLed.setOff();
|
||||
delay(LED_TEST_BLINK_DELAY);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void StateMachine::runLedTest(char color) {
|
||||
@ -399,8 +419,11 @@ StateMachine::~StateMachine() {}
|
||||
* @param state
|
||||
*/
|
||||
void StateMachine::displayHandle(AgStateMachineState state) {
|
||||
// Ignore handle if not ONE_INDOOR board
|
||||
if (!ag->isOne()) {
|
||||
// Ignore handle if not support display
|
||||
if (!(ag->isOne() || (ag->isPro4_2()) || ag->isPro3_3() || ag->isBasic())) {
|
||||
if (state == AgStateMachineCo2Calibration) {
|
||||
co2Calibration();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@ -414,19 +437,18 @@ void StateMachine::displayHandle(AgStateMachineState state) {
|
||||
switch (state) {
|
||||
case AgStateMachineWiFiManagerMode:
|
||||
case AgStateMachineWiFiManagerPortalActive: {
|
||||
// if (wifiConnectCountDown >= 0) {
|
||||
// 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--;
|
||||
// }
|
||||
if (wifiConnectCountDown >= 0) {
|
||||
String qrContent = "WIFI:S:" + config.wifiSSID() +
|
||||
";T:WPA;P:" + config.wifiPass() + ";;";
|
||||
String label = "Scan me (" + String(wifiConnectCountDown) + String(")");
|
||||
disp.showWiFiQrCode(qrContent, label);
|
||||
if (ag->isBasic()) {
|
||||
String ssid = "\"airgradient-" + ag->deviceId() + "\" " +
|
||||
String(wifiConnectCountDown) + String("s");
|
||||
disp.setText("Connect tohotspot:", ssid.c_str(), "");
|
||||
} else {
|
||||
String line1 = String(wifiConnectCountDown) + "s to connect";
|
||||
String line2 = "to WiFi hotspot:";
|
||||
String line3 = "\"airgradient-";
|
||||
String line4 = ag->deviceId() + "\"";
|
||||
disp.setText(line1, line2, line3, line4);
|
||||
}
|
||||
wifiConnectCountDown--;
|
||||
}
|
||||
break;
|
||||
@ -440,7 +462,12 @@ void StateMachine::displayHandle(AgStateMachineState state) {
|
||||
break;
|
||||
}
|
||||
case AgStateMachineWiFiOkServerConnecting: {
|
||||
if (ag->isBasic()) {
|
||||
disp.setText("Connecting", "to", "Server...");
|
||||
} else {
|
||||
disp.setText("Connecting to", "Server", "...");
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case AgStateMachineWiFiOkServerConnected: {
|
||||
@ -456,7 +483,11 @@ void StateMachine::displayHandle(AgStateMachineState state) {
|
||||
break;
|
||||
}
|
||||
case AgStateMachineWiFiOkServerOkSensorConfigFailed: {
|
||||
if (ag->isBasic()) {
|
||||
disp.setText("Monitor", "not on", "dashboard");
|
||||
} else {
|
||||
disp.setText("Monitor not", "setup on", "dashboard");
|
||||
}
|
||||
break;
|
||||
}
|
||||
case AgStateMachineWiFiLost: {
|
||||
@ -468,15 +499,19 @@ void StateMachine::displayHandle(AgStateMachineState state) {
|
||||
break;
|
||||
}
|
||||
case AgStateMachineSensorConfigFailed: {
|
||||
if (addToDashBoard) {
|
||||
uint32_t ms = (uint32_t)(millis() - addToDashboardTime);
|
||||
if (ms >= 5000) {
|
||||
addToDashboardTime = millis();
|
||||
if (addToDashBoard) {
|
||||
if (addToDashBoardToggle) {
|
||||
disp.showDashboard("Add to Dashboard");
|
||||
} else {
|
||||
disp.showDashboard(ag->deviceId().c_str());
|
||||
}
|
||||
addToDashBoard = !addToDashBoard;
|
||||
addToDashBoardToggle = !addToDashBoardToggle;
|
||||
}
|
||||
} else {
|
||||
disp.showDashboard("");
|
||||
}
|
||||
break;
|
||||
}
|
||||
@ -503,8 +538,11 @@ void StateMachine::displayHandle(void) { displayHandle(dispState); }
|
||||
*
|
||||
*/
|
||||
void StateMachine::displaySetAddToDashBoard(void) {
|
||||
if (addToDashBoard == false) {
|
||||
addToDashboardTime = 0;
|
||||
addToDashBoardToggle = true;
|
||||
}
|
||||
addToDashBoard = true;
|
||||
addToDashboardTime = millis();
|
||||
}
|
||||
|
||||
void StateMachine::displayClearAddToDashBoard(void) { addToDashBoard = false; }
|
||||
@ -525,11 +563,18 @@ void StateMachine::displayWiFiConnectCountDown(int count) {
|
||||
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
|
||||
*/
|
||||
void StateMachine::handleLeds(AgStateMachineState state) {
|
||||
/** Ignore if board type if not ONE_INDOOR or OPEN_AIR_OUTDOOR */
|
||||
if ((ag->getBoardType() != BoardType::ONE_INDOOR) &&
|
||||
(ag->getBoardType() != BoardType::OPEN_AIR_OUTDOOR)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (state > AgStateMachineNormal) {
|
||||
logError("ledHandle: state invalid");
|
||||
return;
|
||||
@ -543,7 +588,7 @@ void StateMachine::handleLeds(AgStateMachineState state) {
|
||||
case AgStateMachineWiFiManagerMode: {
|
||||
/** In WiFi Manager Mode */
|
||||
/** Turn LED OFF */
|
||||
/** Turn midle LED Color */
|
||||
/** Turn middle LED Color */
|
||||
if (ag->isOne()) {
|
||||
ag->ledBar.setColor(0, 0, 255, ag->ledBar.getNumberOfLeds() / 2);
|
||||
} else {
|
||||
@ -683,7 +728,7 @@ void StateMachine::handleLeds(AgStateMachineState state) {
|
||||
break;
|
||||
}
|
||||
case AgStateMachineSensorConfigFailed: {
|
||||
/** Server is reachable but there is some configuration issue to be fixed on
|
||||
/** Server is reachable but there is some configuration issue to be fixed on
|
||||
* the server side */
|
||||
if (ag->isOne()) {
|
||||
ag->ledBar.setColor(139, 24, 248, 0);
|
||||
@ -706,6 +751,8 @@ void StateMachine::handleLeds(AgStateMachineState state) {
|
||||
case AgStateMachineLedBarTest:
|
||||
ledBarTest();
|
||||
break;
|
||||
case AgStateMachineLedBarPowerUpTest:
|
||||
ledBarPowerUpTest();
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@ -759,3 +806,7 @@ void StateMachine::executeCo2Calibration(void) {
|
||||
void StateMachine::executeLedBarTest(void) {
|
||||
handleLeds(AgStateMachineLedBarTest);
|
||||
}
|
||||
|
||||
void StateMachine::executeLedBarPowerUpTest(void) {
|
||||
handleLeds(AgStateMachineLedBarPowerUpTest);
|
||||
}
|
||||
|
@ -17,6 +17,7 @@ private:
|
||||
Measurements &value;
|
||||
Configuration &config;
|
||||
bool addToDashBoard = false;
|
||||
bool addToDashBoardToggle = false;
|
||||
uint32_t addToDashboardTime;
|
||||
int wifiConnectCountDown;
|
||||
int ledBarAnimationCount;
|
||||
@ -28,6 +29,7 @@ private:
|
||||
void pm25handleLeds(void);
|
||||
void co2Calibration(void);
|
||||
void ledBarTest(void);
|
||||
void ledBarPowerUpTest(void);
|
||||
void ledBarRunTest(void);
|
||||
void runLedTest(char color);
|
||||
|
||||
@ -49,6 +51,7 @@ public:
|
||||
AgStateMachineState getLedState(void);
|
||||
void executeCo2Calibration(void);
|
||||
void executeLedBarTest(void);
|
||||
void executeLedBarPowerUpTest(void);
|
||||
};
|
||||
|
||||
#endif /** _AG_STATE_MACHINE_H_ */
|
||||
|
384
src/AgValue.cpp
384
src/AgValue.cpp
@ -1,8 +1,42 @@
|
||||
#include "AgValue.h"
|
||||
#include "AgConfigure.h"
|
||||
#include "AirGradient.h"
|
||||
#include "Main/utils.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,
|
||||
void *_ag, void *_config) {
|
||||
AirGradient *ag = (AirGradient *)_ag;
|
||||
@ -13,36 +47,40 @@ String Measurements::toString(bool localServer, AgFirmwareMode fwMode, int rssi,
|
||||
if (localServer) {
|
||||
root["serialno"] = ag->deviceId();
|
||||
}
|
||||
if (config->hasSensorS8) {
|
||||
if (this->CO2 >= 0) {
|
||||
|
||||
if (config->hasSensorS8 && utils::isValidCO2(this->CO2)) {
|
||||
root["rco2"] = this->CO2;
|
||||
}
|
||||
}
|
||||
|
||||
if (ag->isOne()) {
|
||||
if (ag->isOne() || (ag->isPro4_2()) || ag->isPro3_3() || ag->isBasic()) {
|
||||
if (config->hasSensorPMS1) {
|
||||
if (this->pm01_1 >= 0) {
|
||||
if (utils::isValidPm(this->pm01_1)) {
|
||||
root["pm01"] = this->pm01_1;
|
||||
}
|
||||
if (this->pm25_1 >= 0) {
|
||||
if (utils::isValidPm(this->pm25_1)) {
|
||||
root["pm02"] = this->pm25_1;
|
||||
}
|
||||
if (this->pm10_1 >= 0) {
|
||||
if (utils::isValidPm(this->pm10_1)) {
|
||||
root["pm10"] = this->pm10_1;
|
||||
}
|
||||
if (this->pm03PCount_1 >= 0) {
|
||||
if (utils::isValidPm03Count(this->pm03PCount_1)) {
|
||||
root["pm003Count"] = this->pm03PCount_1;
|
||||
}
|
||||
if (!localServer) {
|
||||
|
||||
root[json_prop_pmFirmware] =
|
||||
this->pms5003FirmwareVersion(ag->pms5003.getFirmwareVersion());
|
||||
}
|
||||
}
|
||||
|
||||
if (config->hasSensorSHT) {
|
||||
if (this->Temperature > -1001) {
|
||||
if (utils::isValidTemperature(this->Temperature)) {
|
||||
root["atmp"] = ag->round2(this->Temperature);
|
||||
if (localServer) {
|
||||
root["atmpCompensated"] = ag->round2(this->Temperature);
|
||||
}
|
||||
}
|
||||
if (this->Humidity >= 0) {
|
||||
if (utils::isValidHumidity(this->Humidity)) {
|
||||
root["rhum"] = this->Humidity;
|
||||
if (localServer) {
|
||||
root["rhumCompensated"] = this->Humidity;
|
||||
@ -50,108 +88,297 @@ String Measurements::toString(bool localServer, AgFirmwareMode fwMode, int rssi,
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
if (config->hasSensorPMS1 && config->hasSensorPMS2) {
|
||||
root["pm01"] = ag->round2((this->pm01_1 + this->pm01_2) / 2.0);
|
||||
root["pm02"] = ag->round2((this->pm25_1 + this->pm25_2) / 2.0);
|
||||
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 (config->hasSensorSHT && config->hasSensorPMS1) {
|
||||
int pm25 = ag->pms5003.compensate(this->pm25_1, this->Humidity);
|
||||
if (pm25 >= 0) {
|
||||
root["pm02Compensated"] = pm25;
|
||||
}
|
||||
}
|
||||
|
||||
if (fwMode == FW_MODE_O_1PS || fwMode == FW_MODE_O_1PST) {
|
||||
if (config->hasSensorPMS1) {
|
||||
root["pm01"] = this->pm01_1;
|
||||
root["pm02"] = this->pm25_1;
|
||||
root["pm10"] = this->pm10_1;
|
||||
root["pm003Count"] = this->pm03PCount_1;
|
||||
root["atmp"] = ag->round2(this->temp_1);
|
||||
root["rhum"] = this->hum_1;
|
||||
} else {
|
||||
if (config->hasSensorPMS1 && config->hasSensorPMS2) {
|
||||
if (utils::isValidPm(this->pm01_1) && utils::isValidPm(this->pm01_2)) {
|
||||
root["pm01"] = ag->round2((this->pm01_1 + this->pm01_2) / 2.0f);
|
||||
}
|
||||
if (utils::isValidPm(this->pm25_1) && utils::isValidPm(this->pm25_2)) {
|
||||
root["pm02"] = ag->round2((this->pm25_1 + this->pm25_2) / 2.0f);
|
||||
}
|
||||
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) {
|
||||
root["atmpCompensated"] =
|
||||
ag->round2(ag->pms5003t_1.temperatureCompensated(this->temp_1));
|
||||
root["rhumCompensated"] =
|
||||
(int)ag->pms5003t_1.humidityCompensated(this->hum_1);
|
||||
val = ag->pms5003t_2.compensateTemp((this->temp_1 + this->temp_2) / 2.0f);
|
||||
if (utils::isValidTemperature(val)) {
|
||||
root["atmpCompensated"] = ag->round2(val);
|
||||
}
|
||||
}
|
||||
}
|
||||
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->temp_1) +
|
||||
ag->pms5003t_2.compensate(this->pm25_2, this->temp_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->temp_1);
|
||||
if (!localServer) {
|
||||
root[json_prop_pmFirmware] =
|
||||
pms5003TFirmwareVersion(ag->pms5003t_1.getFirmwareVersion());
|
||||
}
|
||||
}
|
||||
if (config->hasSensorPMS2) {
|
||||
if(utils::isValidPm(this->pm01_2)) {
|
||||
root["pm01"] = this->pm01_2;
|
||||
}
|
||||
if(utils::isValidPm(this->pm25_2)) {
|
||||
root["pm02"] = this->pm25_2;
|
||||
}
|
||||
if(utils::isValidPm(this->pm10_2)) {
|
||||
root["pm10"] = this->pm10_2;
|
||||
}
|
||||
if(utils::isValidPm03Count(this->pm03PCount_2)) {
|
||||
root["pm003Count"] = this->pm03PCount_2;
|
||||
}
|
||||
|
||||
float val;
|
||||
if (utils::isValidTemperature(this->temp_2)) {
|
||||
root["atmp"] = ag->round2(this->temp_2);
|
||||
root["rhum"] = this->hum_2;
|
||||
|
||||
if (localServer) {
|
||||
root["atmpCompensated"] =
|
||||
ag->round2(ag->pms5003t_2.temperatureCompensated(this->temp_2));
|
||||
root["rhumCompensated"] =
|
||||
(int)ag->pms5003t_2.humidityCompensated(this->hum_2);
|
||||
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->temp_2);
|
||||
if(!localServer) {
|
||||
root[json_prop_pmFirmware] =
|
||||
pms5003TFirmwareVersion(ag->pms5003t_1.getFirmwareVersion());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (fwMode == FW_MODE_O_1P) {
|
||||
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);
|
||||
root["rhum"] = this->hum_1;
|
||||
|
||||
if (localServer) {
|
||||
root["atmpCompensated"] =
|
||||
ag->round2(ag->pms5003t_1.temperatureCompensated(this->temp_1));
|
||||
root["rhumCompensated"] =
|
||||
(int)ag->pms5003t_1.humidityCompensated(this->hum_1);
|
||||
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->temp_1);
|
||||
if(!localServer) {
|
||||
root[json_prop_pmFirmware] =
|
||||
pms5003TFirmwareVersion(ag->pms5003t_1.getFirmwareVersion());
|
||||
}
|
||||
} else if (config->hasSensorPMS2) {
|
||||
if(utils::isValidPm(this->pm01_2)) {
|
||||
root["pm01"] = this->pm01_2;
|
||||
}
|
||||
if(utils::isValidPm(this->pm25_2)) {
|
||||
root["pm02"] = this->pm25_2;
|
||||
}
|
||||
if(utils::isValidPm(this->pm10_2)) {
|
||||
root["pm10"] = this->pm10_2;
|
||||
}
|
||||
if(utils::isValidPm03Count(this->pm03PCount_2)) {
|
||||
root["pm003Count"] = this->pm03PCount_2;
|
||||
}
|
||||
if (utils::isValidTemperature(this->temp_2)) {
|
||||
root["atmp"] = ag->round2(this->temp_2);
|
||||
root["rhum"] = this->hum_2;
|
||||
if (localServer) {
|
||||
root["atmpCompensated"] =
|
||||
ag->round2(ag->pms5003t_1.temperatureCompensated(this->temp_2));
|
||||
root["rhumCompensated"] =
|
||||
(int)ag->pms5003t_1.humidityCompensated(this->hum_2);
|
||||
|
||||
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->temp_1);
|
||||
if(!localServer) {
|
||||
root[json_prop_pmFirmware] =
|
||||
pms5003TFirmwareVersion(ag->pms5003t_2.getFirmwareVersion());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
float val;
|
||||
if (config->hasSensorPMS1) {
|
||||
if(utils::isValidPm(this->pm01_1)) {
|
||||
root["channels"]["1"]["pm01"] = this->pm01_1;
|
||||
root["channels"]["1"]["pm02"] = this->pm25_1;
|
||||
root["channels"]["1"]["pm10"] = this->pm10_1;
|
||||
root["channels"]["1"]["pm003Count"] = this->pm03PCount_1;
|
||||
root["channels"]["1"]["atmp"] = ag->round2(this->temp_1);
|
||||
root["channels"]["1"]["rhum"] = this->hum_1;
|
||||
if (localServer) {
|
||||
root["channels"]["1"]["atmpCompensated"] =
|
||||
ag->round2(ag->pms5003t_1.temperatureCompensated(this->temp_1));
|
||||
root["channels"]["1"]["rhumCompensated"] =
|
||||
(int)ag->pms5003t_1.humidityCompensated(this->hum_1);
|
||||
}
|
||||
} else if (config->hasSensorPMS2) {
|
||||
root["channels"]["2"]["pm01"] = this->pm01_2;
|
||||
root["channels"]["2"]["pm02"] = this->pm25_2;
|
||||
root["channels"]["2"]["pm10"] = this->pm10_2;
|
||||
root["channels"]["2"]["pm003Count"] = this->pm03PCount_2;
|
||||
root["channels"]["2"]["atmp"] = ag->round2(this->temp_2);
|
||||
root["channels"]["2"]["rhum"] = this->hum_2;
|
||||
if(utils::isValidPm(this->pm25_1)) {
|
||||
root["channels"]["1"]["pm02"] = this->pm25_1;
|
||||
}
|
||||
if(utils::isValidPm(this->pm10_1)) {
|
||||
root["channels"]["1"]["pm10"] = this->pm10_1;
|
||||
}
|
||||
if (utils::isValidPm03Count(this->pm03PCount_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) {
|
||||
root["channels"]["2"]["atmpCompensated"] =
|
||||
ag->round2(ag->pms5003t_1.temperatureCompensated(this->temp_2));
|
||||
root["channels"]["2"]["rhumCompensated"] =
|
||||
(int)ag->pms5003t_1.humidityCompensated(this->hum_2);
|
||||
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->temp_1);
|
||||
|
||||
// PMS5003T version
|
||||
if(!localServer) {
|
||||
root["channels"]["1"][json_prop_pmFirmware] =
|
||||
pms5003TFirmwareVersion(ag->pms5003t_1.getFirmwareVersion());
|
||||
}
|
||||
}
|
||||
if (config->hasSensorPMS2) {
|
||||
float val;
|
||||
if (utils::isValidPm(this->pm01_2)) {
|
||||
root["channels"]["2"]["pm01"] = this->pm01_2;
|
||||
}
|
||||
if (utils::isValidPm(this->pm25_2)) {
|
||||
root["channels"]["2"]["pm02"] = this->pm25_2;
|
||||
}
|
||||
if (utils::isValidPm(this->pm10_2)) {
|
||||
root["channels"]["2"]["pm10"] = this->pm10_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->temp_2);
|
||||
// PMS5003T version
|
||||
if(!localServer) {
|
||||
root["channels"]["2"][json_prop_pmFirmware] =
|
||||
pms5003TFirmwareVersion(ag->pms5003t_2.getFirmwareVersion());
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -159,23 +386,26 @@ String Measurements::toString(bool localServer, AgFirmwareMode fwMode, int rssi,
|
||||
}
|
||||
|
||||
if (config->hasSensorSGP) {
|
||||
if (this->TVOC >= 0) {
|
||||
if (utils::isValidVOC(this->TVOC)) {
|
||||
root["tvocIndex"] = this->TVOC;
|
||||
}
|
||||
if (this->TVOCRaw >= 0) {
|
||||
if (utils::isValidVOC(this->TVOCRaw)) {
|
||||
root["tvocRaw"] = this->TVOCRaw;
|
||||
}
|
||||
if (this->NOx >= 0) {
|
||||
if (utils::isValidNOx(this->NOx)) {
|
||||
root["noxIndex"] = this->NOx;
|
||||
}
|
||||
if (this->NOxRaw >= 0) {
|
||||
if (utils::isValidNOx(this->NOxRaw)) {
|
||||
root["noxRaw"] = this->NOxRaw;
|
||||
}
|
||||
}
|
||||
root["boot"] = bootCount;
|
||||
root["bootCount"] = bootCount;
|
||||
|
||||
if (localServer) {
|
||||
if (ag->isOne()) {
|
||||
root["ledMode"] = config->getLedBarModeName();
|
||||
}
|
||||
root["firmware"] = ag->getVersion();
|
||||
root["model"] = AgFirmwareModeName(fwMode);
|
||||
}
|
||||
|
@ -6,6 +6,9 @@
|
||||
|
||||
class Measurements {
|
||||
private:
|
||||
String pms5003FirmwareVersion(int fwCode);
|
||||
String pms5003TFirmwareVersion(int fwCode);
|
||||
String pms5003FirmwareVersionBase(String prefix, int fwCode);
|
||||
public:
|
||||
Measurements() {
|
||||
pm25_1 = -1;
|
||||
|
@ -13,7 +13,6 @@
|
||||
*/
|
||||
void WifiConnector::setAirGradient(AirGradient *ag) { this->ag = ag; }
|
||||
|
||||
#ifdef ESP32
|
||||
/**
|
||||
* @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,
|
||||
Configuration &config)
|
||||
: PrintLog(log, "WifiConnector"), disp(disp), sm(sm), config(config) {}
|
||||
#else
|
||||
WifiConnector::WifiConnector(Stream &log) : PrintLog(log, "WiFiConnector") {}
|
||||
#endif
|
||||
|
||||
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()->setConnectTimeout(15);
|
||||
WIFI()->setTimeout(WIFI_CONNECT_COUNTDOWN_MAX);
|
||||
|
||||
#ifdef ESP32
|
||||
WIFI()->setAPCallback([this](WiFiManager *obj) { _wifiApCallback(); });
|
||||
WIFI()->setSaveConfigCallback([this]() { _wifiSaveConfig(); });
|
||||
WIFI()->setSaveParamsCallback([this]() { _wifiSaveParamCallback(); });
|
||||
if (ag->isOne()) {
|
||||
disp.setText("Connecting to", "WiFi", "...");
|
||||
WIFI()->setConfigPortalTimeoutCallback([this]() {_wifiTimeoutCallback();});
|
||||
if (ag->isOne() || (ag->isPro4_2()) || ag->isPro3_3() || ag->isBasic()) {
|
||||
disp.setText("Connect to", "WiFi", "...");
|
||||
} else {
|
||||
logInfo("Connecting to WiFi...");
|
||||
}
|
||||
ssid = "airgradient-" + ag->deviceId();
|
||||
#else
|
||||
ssid = "AG-" + String(ESP.getChipId(), HEX);
|
||||
#endif
|
||||
|
||||
// ssid = "AG-" + String(ESP.getChipId(), HEX);
|
||||
WIFI()->setConfigPortalTimeout(WIFI_CONNECT_COUNTDOWN_MAX);
|
||||
|
||||
WiFiManagerParameter postToAg("chbPostToAg",
|
||||
@ -77,6 +94,8 @@ bool WifiConnector::connect(void) {
|
||||
|
||||
WIFI()->autoConnect(ssid.c_str(), WIFI_HOTSPOT_PASSWORD_DEFAULT);
|
||||
|
||||
logInfo("Wait for configure portal");
|
||||
|
||||
#ifdef ESP32
|
||||
// Task handle WiFi connection.
|
||||
xTaskCreate(
|
||||
@ -84,6 +103,7 @@ bool WifiConnector::connect(void) {
|
||||
WifiConnector *connector = (WifiConnector *)obj;
|
||||
while (connector->_wifiConfigPortalActive()) {
|
||||
connector->_wifiProcess();
|
||||
vTaskDelay(1);
|
||||
}
|
||||
vTaskDelete(NULL);
|
||||
},
|
||||
@ -138,10 +158,14 @@ bool WifiConnector::connect(void) {
|
||||
|
||||
delay(1); // avoid watchdog timer reset.
|
||||
}
|
||||
#else
|
||||
_wifiProcess();
|
||||
#endif
|
||||
|
||||
/** Show display wifi connect result failed */
|
||||
if (WiFi.isConnected() == false) {
|
||||
sm.handleLeds(AgStateMachineWiFiManagerConnectFailed);
|
||||
if (ag->isOne()) {
|
||||
if (ag->isOne() || ag->isPro4_2() || ag->isPro3_3() || ag->isBasic()) {
|
||||
sm.displayHandle(AgStateMachineWiFiManagerConnectFailed);
|
||||
}
|
||||
delay(6000);
|
||||
@ -159,9 +183,7 @@ bool WifiConnector::connect(void) {
|
||||
}
|
||||
hasPortalConfig = false;
|
||||
}
|
||||
#else
|
||||
_wifiProcess();
|
||||
#endif
|
||||
|
||||
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)
|
||||
*
|
||||
@ -204,7 +208,6 @@ bool WifiConnector::wifiClientConnected(void) {
|
||||
return WiFi.softAPgetStationNum() ? true : false;
|
||||
}
|
||||
|
||||
#ifdef ESP32
|
||||
/**
|
||||
* @brief Handle WiFiManage softAP setup completed callback
|
||||
*
|
||||
@ -245,7 +248,8 @@ void WifiConnector::_wifiSaveParamCallback(void) {
|
||||
bool WifiConnector::_wifiConfigPortalActive(void) {
|
||||
return WIFI()->getConfigPortalActive();
|
||||
}
|
||||
#endif
|
||||
void WifiConnector::_wifiTimeoutCallback(void) { connectorTimeout = true; }
|
||||
|
||||
/**
|
||||
* @brief Process WiFiManager connection
|
||||
*
|
||||
@ -254,34 +258,68 @@ void WifiConnector::_wifiProcess() {
|
||||
#ifdef ESP32
|
||||
WIFI()->process();
|
||||
#else
|
||||
int count = WIFI_CONNECT_COUNTDOWN_MAX;
|
||||
displayShowText(String(WIFI_CONNECT_COUNTDOWN_MAX) + " sec", "SSID:", ssid);
|
||||
/** Wait for WiFi connect and show LED, display status */
|
||||
uint32_t dispPeriod = millis();
|
||||
uint32_t ledPeriod = millis();
|
||||
bool clientConnectChanged = false;
|
||||
AgStateMachineState stateOld = sm.getDisplayState();
|
||||
|
||||
while (WIFI()->getConfigPortalActive()) {
|
||||
WIFI()->process();
|
||||
|
||||
uint32_t lastTime = millis();
|
||||
uint32_t ms = (uint32_t)(millis() - lastTime);
|
||||
if (WiFi.isConnected() == false) {
|
||||
/** Display countdown */
|
||||
uint32_t ms;
|
||||
if (ag->isOne() || (ag->isPro4_2()) || ag->isPro3_3() || ag->isBasic()) {
|
||||
ms = (uint32_t)(millis() - dispPeriod);
|
||||
if (ms >= 1000) {
|
||||
lastTime = millis();
|
||||
|
||||
displayShowText(String(count) + " sec", "SSID:", ssid);
|
||||
|
||||
count--;
|
||||
|
||||
// Timeout
|
||||
if (count == 0) {
|
||||
break;
|
||||
dispPeriod = millis();
|
||||
sm.displayHandle();
|
||||
logInfo("displayHandle state: " + String(sm.getDisplayState()));
|
||||
} else {
|
||||
if (stateOld != sm.getDisplayState()) {
|
||||
stateOld = sm.getDisplayState();
|
||||
sm.displayHandle();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** LED animations */
|
||||
ms = (uint32_t)(millis() - ledPeriod);
|
||||
if (ms >= 100) {
|
||||
ledPeriod = millis();
|
||||
sm.handleLeds();
|
||||
}
|
||||
|
||||
/** Check for client connect to change led color */
|
||||
bool clientConnected = wifiClientConnected();
|
||||
if (clientConnected != clientConnectChanged) {
|
||||
clientConnectChanged = clientConnected;
|
||||
if (clientConnectChanged) {
|
||||
sm.handleLeds(AgStateMachineWiFiManagerPortalActive);
|
||||
} else {
|
||||
sm.ledAnimationInit();
|
||||
sm.handleLeds(AgStateMachineWiFiManagerMode);
|
||||
if (ag->isOne()) {
|
||||
sm.displayHandle(AgStateMachineWiFiManagerMode);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
delay(1);
|
||||
}
|
||||
|
||||
// TODO This is for basic
|
||||
if (ag->getBoardType() == DIY_BASIC) {
|
||||
if (!WiFi.isConnected()) {
|
||||
displayShowText("Booting", "offline", "mode");
|
||||
// disp.setText("Booting", "offline", "mode");
|
||||
Serial.println("failed to connect and hit timeout");
|
||||
delay(2500);
|
||||
} else {
|
||||
hasConfig = true;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
@ -305,8 +343,6 @@ void WifiConnector::handle(void) {
|
||||
if (ms >= 10000) {
|
||||
lastRetry = millis();
|
||||
WiFi.reconnect();
|
||||
|
||||
// Serial.printf("Re-Connect WiFi\r\n");
|
||||
logInfo("Re-Connect WiFi");
|
||||
}
|
||||
}
|
||||
@ -324,7 +360,16 @@ bool WifiConnector::isConnected(void) { return WiFi.isConnected(); }
|
||||
* 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
|
||||
@ -339,3 +384,32 @@ int WifiConnector::RSSI(void) { return WiFi.RSSI(); }
|
||||
* @return String
|
||||
*/
|
||||
String WifiConnector::localIpStr(void) { return WiFi.localIP().toString(); }
|
||||
|
||||
/**
|
||||
* @brief Get status that wifi has configurated
|
||||
*
|
||||
* @return true Configurated
|
||||
* @return false Not Configurated
|
||||
*/
|
||||
bool WifiConnector::hasConfigurated(void) {
|
||||
if (WiFi.SSID().isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get WiFi connection porttal timeout.
|
||||
*
|
||||
* @return true
|
||||
* @return false
|
||||
*/
|
||||
bool WifiConnector::isConfigurePorttalTimeout(void) { return connectorTimeout; }
|
||||
|
||||
/**
|
||||
* @brief Set wifi connect to default WiFi
|
||||
*
|
||||
*/
|
||||
void WifiConnector::setDefault(void) {
|
||||
WiFi.begin("airgradient", "cleanair");
|
||||
}
|
||||
|
@ -12,44 +12,44 @@
|
||||
class WifiConnector : public PrintLog {
|
||||
private:
|
||||
AirGradient *ag;
|
||||
#ifdef ESP32
|
||||
OledDisplay &disp;
|
||||
StateMachine &sm;
|
||||
Configuration &config;
|
||||
#else
|
||||
void displayShowText(String ln1, String ln2, String ln3);
|
||||
#endif
|
||||
|
||||
String ssid;
|
||||
void *wifi = NULL;
|
||||
bool hasConfig;
|
||||
uint32_t lastRetry;
|
||||
bool hasPortalConfig = false;
|
||||
bool connectorTimeout = false;
|
||||
|
||||
bool wifiClientConnected(void);
|
||||
|
||||
public:
|
||||
void setAirGradient(AirGradient *ag);
|
||||
#ifdef ESP32
|
||||
|
||||
WifiConnector(OledDisplay &disp, Stream &log, StateMachine &sm, Configuration& config);
|
||||
#else
|
||||
WifiConnector(Stream &log);
|
||||
#endif
|
||||
~WifiConnector();
|
||||
|
||||
bool connect(void);
|
||||
void disconnect(void);
|
||||
void handle(void);
|
||||
#ifdef ESP32
|
||||
void _wifiApCallback(void);
|
||||
void _wifiSaveConfig(void);
|
||||
void _wifiSaveParamCallback(void);
|
||||
bool _wifiConfigPortalActive(void);
|
||||
#endif
|
||||
void _wifiTimeoutCallback(void);
|
||||
void _wifiProcess();
|
||||
bool isConnected(void);
|
||||
void reset(void);
|
||||
int RSSI(void);
|
||||
String localIpStr(void);
|
||||
bool hasConfigurated(void);
|
||||
bool isConfigurePorttalTimeout(void);
|
||||
|
||||
const char* defaultSsid = "airgradient";
|
||||
const char* defaultPassword = "cleanair";
|
||||
void setDefault(void);
|
||||
};
|
||||
|
||||
#endif /** _AG_WIFI_CONNECTOR_H_ */
|
||||
|
@ -41,7 +41,14 @@ String AirGradient::getVersion(void) { return GIT_VERSION; }
|
||||
BoardType AirGradient::getBoardType(void) { return boardType; }
|
||||
|
||||
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) {
|
||||
@ -58,6 +65,20 @@ bool AirGradient::isOne(void) {
|
||||
return boardType == BoardType::ONE_INDOOR;
|
||||
}
|
||||
|
||||
bool AirGradient::isOpenAir(void) {
|
||||
return boardType == BoardType::OPEN_AIR_OUTDOOR;
|
||||
}
|
||||
|
||||
bool AirGradient::isPro4_2(void) {
|
||||
return boardType == BoardType::DIY_PRO_INDOOR_V4_2;
|
||||
}
|
||||
|
||||
bool AirGradient::isPro3_3(void) {
|
||||
return boardType == BoardType::DIY_PRO_INDOOR_V3_3;
|
||||
}
|
||||
|
||||
bool AirGradient::isBasic(void) { return boardType == BoardType::DIY_BASIC; }
|
||||
|
||||
String AirGradient::deviceId(void) {
|
||||
String mac = WiFi.macAddress();
|
||||
mac.replace(":", "");
|
||||
|
@ -12,9 +12,10 @@
|
||||
#include "S8/S8.h"
|
||||
#include "Sgp41/Sgp41.h"
|
||||
#include "Sht/Sht.h"
|
||||
#include "Main/utils.h"
|
||||
|
||||
#ifndef GIT_VERSION
|
||||
#define GIT_VERSION "snapshot"
|
||||
#define GIT_VERSION "3.1.7-snap"
|
||||
#endif
|
||||
|
||||
/**
|
||||
@ -134,6 +135,37 @@ public:
|
||||
*/
|
||||
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
|
||||
*
|
||||
|
@ -14,6 +14,12 @@ const char *AgFirmwareModeName(AgFirmwareMode mode) {
|
||||
return "0-1PS";
|
||||
case FW_MODE_O_1P:
|
||||
return "O-1P";
|
||||
case FW_MODE_I_42PS:
|
||||
return "DIY-PRO-I-4.2PS";
|
||||
case FW_MODE_I_33PS:
|
||||
return "DIY-PRO-I-3.3PS";
|
||||
case FW_MODE_I_BASIC_40PS:
|
||||
return "DIY-BASIC-I-4.0PS";
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
@ -59,6 +59,10 @@ enum AgStateMachineState {
|
||||
|
||||
/* LED bar testing */
|
||||
AgStateMachineLedBarTest,
|
||||
AgStateMachineLedBarPowerUpTest,
|
||||
|
||||
/** OTA perform, show display status */
|
||||
AgStateMachineOtaPerform,
|
||||
|
||||
/** LED: Show working state.
|
||||
* Display: Show dashboard */
|
||||
@ -97,6 +101,9 @@ enum AgFirmwareMode {
|
||||
FW_MODE_O_1PP, /** PMS5003T_1, PMS5003T_2 */
|
||||
FW_MODE_O_1PS, /** PMS5003T, S8 */
|
||||
FW_MODE_O_1P, /** PMS5003T */
|
||||
FW_MODE_I_42PS, /** DIY_PRO 4.2 */
|
||||
FW_MODE_I_33PS, /** DIY_PRO 3.3 */
|
||||
FW_MODE_I_BASIC_40PS, /** DIY_BASIC 4.0 */
|
||||
};
|
||||
const char *AgFirmwareModeName(AgFirmwareMode mode);
|
||||
|
||||
|
1
src/Libraries/QRCode/.gitignore
vendored
1
src/Libraries/QRCode/.gitignore
vendored
@ -1 +0,0 @@
|
||||
.DS_Store
|
@ -1,26 +0,0 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
This library is written and maintained by Richard Moore.
|
||||
Major parts were derived from Project Nayuki's library.
|
||||
|
||||
Copyright (c) 2017 Richard Moore (https://github.com/ricmoo/QRCode)
|
||||
Copyright (c) 2017 Project Nayuki (https://www.nayuki.io/page/qr-code-generator-library)
|
||||
|
||||
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.
|
||||
|
@ -1,677 +0,0 @@
|
||||
QRCode
|
||||
======
|
||||
|
||||
A simple library for generating [QR codes](https://en.wikipedia.org/wiki/QR_code) in C,
|
||||
optimized for processing and memory constrained systems.
|
||||
|
||||
**Features:**
|
||||
|
||||
- Stack-based (no heap necessary; but you can use heap if you want)
|
||||
- Low-memory foot print (relatively)
|
||||
- Compile-time stripping of unecessary logic and constants
|
||||
- MIT License; do with this as you please
|
||||
|
||||
|
||||
Installing
|
||||
----------
|
||||
|
||||
To install this library, download and save it to your Arduino libraries directory.
|
||||
|
||||
Rename the directory to QRCode (if downloaded from GitHub, the filename may be
|
||||
qrcode-master; library names may not contain the hyphen, so it must be renamed)
|
||||
|
||||
|
||||
API
|
||||
---
|
||||
|
||||
**Generate a QR Code**
|
||||
|
||||
```c
|
||||
// The structure to manage the QR code
|
||||
QRCode qrcode;
|
||||
|
||||
// Allocate a chunk of memory to store the QR code
|
||||
uint8_t qrcodeBytes[qrcode_getBufferSize()];
|
||||
|
||||
qrcode_initText(&qrcode, qrcodeBytes, 3, ECC_LOW, "HELLO WORLD");
|
||||
```
|
||||
|
||||
**Draw a QR Code**
|
||||
|
||||
How a QR code is used will vary greatly from project to project. For example:
|
||||
|
||||
- Display on an OLED screen (128x64 nicely supports 2 side-by-side version 3 QR codes)
|
||||
- Print as a bitmap on a thermal printer
|
||||
- Store as a BMP (or with a some extra work, possibly a PNG) on an SD card
|
||||
|
||||
The following example prints a QR code to the Serial Monitor (it likely will
|
||||
not be scannable, but is just for demonstration purposes).
|
||||
|
||||
```c
|
||||
for (uint8 y = 0; y < qrcode.size; y++) {
|
||||
for (uint8 x = 0; x < qrcode.size; x++) {
|
||||
if (qrcode_getModule(&qrcode, x, y) {
|
||||
Serial.print("**");
|
||||
} else {
|
||||
Serial.print(" ");
|
||||
}
|
||||
}
|
||||
Serial.print("\n");
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
What is Version, Error Correction and Mode?
|
||||
-------------------------------------------
|
||||
|
||||
A QR code is composed of many little squares, called **modules**, which represent
|
||||
encoded data, with additional error correction (allowing partially damaged QR
|
||||
codes to still be read).
|
||||
|
||||
The **version** of a QR code is a number between 1 and 40 (inclusive), which indicates
|
||||
the size of the QR code. The width and height of a QR code are always equal (it is
|
||||
square) and are equal to `4 * version + 17`.
|
||||
|
||||
The level of **error correction** is a number between 0 and 3 (inclusive), or can be
|
||||
one of the symbolic names ECC_LOW, ECC_MEDIUM, ECC_QUARTILE and ECC_HIGH. Higher
|
||||
levels of error correction sacrifice data capacity, but allow a larger portion of
|
||||
the QR code to be damaged or unreadable.
|
||||
|
||||
The **mode** of a QR code is determined by the data being encoded. Each mode is encoded
|
||||
internally using a compact representation, so lower modes can contain more data.
|
||||
|
||||
- **NUMERIC:** numbers (`0-9`)
|
||||
- **ALPHANUMERIC:** uppercase letters (`A-Z`), numbers (`0-9`), the space (` `), dollar sign (`$`), percent sign (`%`), asterisk (`*`), plus (`+`), minus (`-`), decimal point (`.`), slash (`/`) and colon (`:`).
|
||||
- **BYTE:** any character
|
||||
|
||||
|
||||
Data Capacities
|
||||
---------------
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<th rowspan="2">Version</th>
|
||||
<th rowspan="2">Size</th>
|
||||
<th rowspan="2">Error Correction</th>
|
||||
<th colspan="3">Mode</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Numeric</th>
|
||||
<th>Alphanumeric</th>
|
||||
<th>Byte</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td rowspan="4">1</td>
|
||||
<td rowspan="4">21 x 21</td>
|
||||
<td>LOW</td><td>41</td><td>25</td><td>17</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>MEDIUM</td><td>34</td><td>20</td><td>14</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>QUARTILE</td><td>27</td><td>16</td><td>11</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>HIGH</td><td>17</td><td>10</td><td>7</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td rowspan="4">2</td>
|
||||
<td rowspan="4">25 x 25</td>
|
||||
<td>LOW</td><td>77</td><td>47</td><td>32</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>MEDIUM</td><td>63</td><td>38</td><td>26</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>QUARTILE</td><td>48</td><td>29</td><td>20</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>HIGH</td><td>34</td><td>20</td><td>14</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td rowspan="4">3</td>
|
||||
<td rowspan="4">29 x 29</td>
|
||||
<td>LOW</td><td>127</td><td>77</td><td>53</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>MEDIUM</td><td>101</td><td>61</td><td>42</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>QUARTILE</td><td>77</td><td>47</td><td>32</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>HIGH</td><td>58</td><td>35</td><td>24</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td rowspan="4">4</td>
|
||||
<td rowspan="4">33 x 33</td>
|
||||
<td>LOW</td><td>187</td><td>114</td><td>78</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>MEDIUM</td><td>149</td><td>90</td><td>62</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>QUARTILE</td><td>111</td><td>67</td><td>46</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>HIGH</td><td>82</td><td>50</td><td>34</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td rowspan="4">5</td>
|
||||
<td rowspan="4">37 x 37</td>
|
||||
<td>LOW</td><td>255</td><td>154</td><td>106</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>MEDIUM</td><td>202</td><td>122</td><td>84</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>QUARTILE</td><td>144</td><td>87</td><td>60</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>HIGH</td><td>106</td><td>64</td><td>44</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td rowspan="4">6</td>
|
||||
<td rowspan="4">41 x 41</td>
|
||||
<td>LOW</td><td>322</td><td>195</td><td>134</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>MEDIUM</td><td>255</td><td>154</td><td>106</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>QUARTILE</td><td>178</td><td>108</td><td>74</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>HIGH</td><td>139</td><td>84</td><td>58</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td rowspan="4">7</td>
|
||||
<td rowspan="4">45 x 45</td>
|
||||
<td>LOW</td><td>370</td><td>224</td><td>154</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>MEDIUM</td><td>293</td><td>178</td><td>122</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>QUARTILE</td><td>207</td><td>125</td><td>86</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>HIGH</td><td>154</td><td>93</td><td>64</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td rowspan="4">8</td>
|
||||
<td rowspan="4">49 x 49</td>
|
||||
<td>LOW</td><td>461</td><td>279</td><td>192</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>MEDIUM</td><td>365</td><td>221</td><td>152</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>QUARTILE</td><td>259</td><td>157</td><td>108</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>HIGH</td><td>202</td><td>122</td><td>84</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td rowspan="4">9</td>
|
||||
<td rowspan="4">53 x 53</td>
|
||||
<td>LOW</td><td>552</td><td>335</td><td>230</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>MEDIUM</td><td>432</td><td>262</td><td>180</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>QUARTILE</td><td>312</td><td>189</td><td>130</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>HIGH</td><td>235</td><td>143</td><td>98</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td rowspan="4">10</td>
|
||||
<td rowspan="4">57 x 57</td>
|
||||
<td>LOW</td><td>652</td><td>395</td><td>271</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>MEDIUM</td><td>513</td><td>311</td><td>213</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>QUARTILE</td><td>364</td><td>221</td><td>151</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>HIGH</td><td>288</td><td>174</td><td>119</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td rowspan="4">11</td>
|
||||
<td rowspan="4">61 x 61</td>
|
||||
<td>LOW</td><td>772</td><td>468</td><td>321</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>MEDIUM</td><td>604</td><td>366</td><td>251</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>QUARTILE</td><td>427</td><td>259</td><td>177</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>HIGH</td><td>331</td><td>200</td><td>137</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td rowspan="4">12</td>
|
||||
<td rowspan="4">65 x 65</td>
|
||||
<td>LOW</td><td>883</td><td>535</td><td>367</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>MEDIUM</td><td>691</td><td>419</td><td>287</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>QUARTILE</td><td>489</td><td>296</td><td>203</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>HIGH</td><td>374</td><td>227</td><td>155</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td rowspan="4">13</td>
|
||||
<td rowspan="4">69 x 69</td>
|
||||
<td>LOW</td><td>1022</td><td>619</td><td>425</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>MEDIUM</td><td>796</td><td>483</td><td>331</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>QUARTILE</td><td>580</td><td>352</td><td>241</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>HIGH</td><td>427</td><td>259</td><td>177</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td rowspan="4">14</td>
|
||||
<td rowspan="4">73 x 73</td>
|
||||
<td>LOW</td><td>1101</td><td>667</td><td>458</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>MEDIUM</td><td>871</td><td>528</td><td>362</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>QUARTILE</td><td>621</td><td>376</td><td>258</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>HIGH</td><td>468</td><td>283</td><td>194</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td rowspan="4">15</td>
|
||||
<td rowspan="4">77 x 77</td>
|
||||
<td>LOW</td><td>1250</td><td>758</td><td>520</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>MEDIUM</td><td>991</td><td>600</td><td>412</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>QUARTILE</td><td>703</td><td>426</td><td>292</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>HIGH</td><td>530</td><td>321</td><td>220</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td rowspan="4">16</td>
|
||||
<td rowspan="4">81 x 81</td>
|
||||
<td>LOW</td><td>1408</td><td>854</td><td>586</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>MEDIUM</td><td>1082</td><td>656</td><td>450</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>QUARTILE</td><td>775</td><td>470</td><td>322</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>HIGH</td><td>602</td><td>365</td><td>250</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td rowspan="4">17</td>
|
||||
<td rowspan="4">85 x 85</td>
|
||||
<td>LOW</td><td>1548</td><td>938</td><td>644</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>MEDIUM</td><td>1212</td><td>734</td><td>504</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>QUARTILE</td><td>876</td><td>531</td><td>364</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>HIGH</td><td>674</td><td>408</td><td>280</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td rowspan="4">18</td>
|
||||
<td rowspan="4">89 x 89</td>
|
||||
<td>LOW</td><td>1725</td><td>1046</td><td>718</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>MEDIUM</td><td>1346</td><td>816</td><td>560</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>QUARTILE</td><td>948</td><td>574</td><td>394</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>HIGH</td><td>746</td><td>452</td><td>310</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td rowspan="4">19</td>
|
||||
<td rowspan="4">93 x 93</td>
|
||||
<td>LOW</td><td>1903</td><td>1153</td><td>792</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>MEDIUM</td><td>1500</td><td>909</td><td>624</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>QUARTILE</td><td>1063</td><td>644</td><td>442</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>HIGH</td><td>813</td><td>493</td><td>338</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td rowspan="4">20</td>
|
||||
<td rowspan="4">97 x 97</td>
|
||||
<td>LOW</td><td>2061</td><td>1249</td><td>858</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>MEDIUM</td><td>1600</td><td>970</td><td>666</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>QUARTILE</td><td>1159</td><td>702</td><td>482</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>HIGH</td><td>919</td><td>557</td><td>382</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td rowspan="4">21</td>
|
||||
<td rowspan="4">101 x 101</td>
|
||||
<td>LOW</td><td>2232</td><td>1352</td><td>929</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>MEDIUM</td><td>1708</td><td>1035</td><td>711</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>QUARTILE</td><td>1224</td><td>742</td><td>509</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>HIGH</td><td>969</td><td>587</td><td>403</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td rowspan="4">22</td>
|
||||
<td rowspan="4">105 x 105</td>
|
||||
<td>LOW</td><td>2409</td><td>1460</td><td>1003</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>MEDIUM</td><td>1872</td><td>1134</td><td>779</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>QUARTILE</td><td>1358</td><td>823</td><td>565</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>HIGH</td><td>1056</td><td>640</td><td>439</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td rowspan="4">23</td>
|
||||
<td rowspan="4">109 x 109</td>
|
||||
<td>LOW</td><td>2620</td><td>1588</td><td>1091</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>MEDIUM</td><td>2059</td><td>1248</td><td>857</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>QUARTILE</td><td>1468</td><td>890</td><td>611</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>HIGH</td><td>1108</td><td>672</td><td>461</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td rowspan="4">24</td>
|
||||
<td rowspan="4">113 x 113</td>
|
||||
<td>LOW</td><td>2812</td><td>1704</td><td>1171</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>MEDIUM</td><td>2188</td><td>1326</td><td>911</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>QUARTILE</td><td>1588</td><td>963</td><td>661</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>HIGH</td><td>1228</td><td>744</td><td>511</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td rowspan="4">25</td>
|
||||
<td rowspan="4">117 x 117</td>
|
||||
<td>LOW</td><td>3057</td><td>1853</td><td>1273</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>MEDIUM</td><td>2395</td><td>1451</td><td>997</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>QUARTILE</td><td>1718</td><td>1041</td><td>715</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>HIGH</td><td>1286</td><td>779</td><td>535</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td rowspan="4">26</td>
|
||||
<td rowspan="4">121 x 121</td>
|
||||
<td>LOW</td><td>3283</td><td>1990</td><td>1367</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>MEDIUM</td><td>2544</td><td>1542</td><td>1059</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>QUARTILE</td><td>1804</td><td>1094</td><td>751</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>HIGH</td><td>1425</td><td>864</td><td>593</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td rowspan="4">27</td>
|
||||
<td rowspan="4">125 x 125</td>
|
||||
<td>LOW</td><td>3517</td><td>2132</td><td>1465</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>MEDIUM</td><td>2701</td><td>1637</td><td>1125</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>QUARTILE</td><td>1933</td><td>1172</td><td>805</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>HIGH</td><td>1501</td><td>910</td><td>625</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td rowspan="4">28</td>
|
||||
<td rowspan="4">129 x 129</td>
|
||||
<td>LOW</td><td>3669</td><td>2223</td><td>1528</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>MEDIUM</td><td>2857</td><td>1732</td><td>1190</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>QUARTILE</td><td>2085</td><td>1263</td><td>868</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>HIGH</td><td>1581</td><td>958</td><td>658</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td rowspan="4">29</td>
|
||||
<td rowspan="4">133 x 133</td>
|
||||
<td>LOW</td><td>3909</td><td>2369</td><td>1628</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>MEDIUM</td><td>3035</td><td>1839</td><td>1264</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>QUARTILE</td><td>2181</td><td>1322</td><td>908</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>HIGH</td><td>1677</td><td>1016</td><td>698</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td rowspan="4">30</td>
|
||||
<td rowspan="4">137 x 137</td>
|
||||
<td>LOW</td><td>4158</td><td>2520</td><td>1732</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>MEDIUM</td><td>3289</td><td>1994</td><td>1370</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>QUARTILE</td><td>2358</td><td>1429</td><td>982</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>HIGH</td><td>1782</td><td>1080</td><td>742</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td rowspan="4">31</td>
|
||||
<td rowspan="4">141 x 141</td>
|
||||
<td>LOW</td><td>4417</td><td>2677</td><td>1840</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>MEDIUM</td><td>3486</td><td>2113</td><td>1452</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>QUARTILE</td><td>2473</td><td>1499</td><td>1030</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>HIGH</td><td>1897</td><td>1150</td><td>790</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td rowspan="4">32</td>
|
||||
<td rowspan="4">145 x 145</td>
|
||||
<td>LOW</td><td>4686</td><td>2840</td><td>1952</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>MEDIUM</td><td>3693</td><td>2238</td><td>1538</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>QUARTILE</td><td>2670</td><td>1618</td><td>1112</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>HIGH</td><td>2022</td><td>1226</td><td>842</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td rowspan="4">33</td>
|
||||
<td rowspan="4">149 x 149</td>
|
||||
<td>LOW</td><td>4965</td><td>3009</td><td>2068</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>MEDIUM</td><td>3909</td><td>2369</td><td>1628</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>QUARTILE</td><td>2805</td><td>1700</td><td>1168</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>HIGH</td><td>2157</td><td>1307</td><td>898</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td rowspan="4">34</td>
|
||||
<td rowspan="4">153 x 153</td>
|
||||
<td>LOW</td><td>5253</td><td>3183</td><td>2188</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>MEDIUM</td><td>4134</td><td>2506</td><td>1722</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>QUARTILE</td><td>2949</td><td>1787</td><td>1228</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>HIGH</td><td>2301</td><td>1394</td><td>958</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td rowspan="4">35</td>
|
||||
<td rowspan="4">157 x 157</td>
|
||||
<td>LOW</td><td>5529</td><td>3351</td><td>2303</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>MEDIUM</td><td>4343</td><td>2632</td><td>1809</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>QUARTILE</td><td>3081</td><td>1867</td><td>1283</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>HIGH</td><td>2361</td><td>1431</td><td>983</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td rowspan="4">36</td>
|
||||
<td rowspan="4">161 x 161</td>
|
||||
<td>LOW</td><td>5836</td><td>3537</td><td>2431</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>MEDIUM</td><td>4588</td><td>2780</td><td>1911</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>QUARTILE</td><td>3244</td><td>1966</td><td>1351</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>HIGH</td><td>2524</td><td>1530</td><td>1051</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td rowspan="4">37</td>
|
||||
<td rowspan="4">165 x 165</td>
|
||||
<td>LOW</td><td>6153</td><td>3729</td><td>2563</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>MEDIUM</td><td>4775</td><td>2894</td><td>1989</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>QUARTILE</td><td>3417</td><td>2071</td><td>1423</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>HIGH</td><td>2625</td><td>1591</td><td>1093</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td rowspan="4">38</td>
|
||||
<td rowspan="4">169 x 169</td>
|
||||
<td>LOW</td><td>6479</td><td>3927</td><td>2699</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>MEDIUM</td><td>5039</td><td>3054</td><td>2099</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>QUARTILE</td><td>3599</td><td>2181</td><td>1499</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>HIGH</td><td>2735</td><td>1658</td><td>1139</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td rowspan="4">39</td>
|
||||
<td rowspan="4">173 x 173</td>
|
||||
<td>LOW</td><td>6743</td><td>4087</td><td>2809</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>MEDIUM</td><td>5313</td><td>3220</td><td>2213</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>QUARTILE</td><td>3791</td><td>2298</td><td>1579</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>HIGH</td><td>2927</td><td>1774</td><td>1219</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td rowspan="4">40</td>
|
||||
<td rowspan="4">177 x 177</td>
|
||||
<td>LOW</td><td>7089</td><td>4296</td><td>2953</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>MEDIUM</td><td>5596</td><td>3391</td><td>2331</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>QUARTILE</td><td>3993</td><td>2420</td><td>1663</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>HIGH</td><td>3057</td><td>1852</td><td>1273</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
|
||||
Special Thanks
|
||||
--------------
|
||||
|
||||
A HUGE thank you to [Project Nayuki](https://www.nayuki.io/) for the
|
||||
[QR code C++ library](https://github.com/nayuki/QR-Code-generator/tree/master/cpp)
|
||||
which was critical in development of this library.
|
||||
|
||||
|
||||
License
|
||||
-------
|
||||
|
||||
MIT License.
|
@ -1,56 +0,0 @@
|
||||
/**
|
||||
* QRCode
|
||||
*
|
||||
* A quick example of generating a QR code.
|
||||
*
|
||||
* This prints the QR code to the serial monitor as solid blocks. Each module
|
||||
* is two characters wide, since the monospace font used in the serial monitor
|
||||
* is approximately twice as tall as wide.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "qrcode.h"
|
||||
|
||||
void setup() {
|
||||
Serial.begin(115200);
|
||||
|
||||
// Start time
|
||||
uint32_t dt = millis();
|
||||
|
||||
// Create the QR code
|
||||
QRCode qrcode;
|
||||
uint8_t qrcodeData[qrcode_getBufferSize(3)];
|
||||
qrcode_initText(&qrcode, qrcodeData, 3, 0, "HELLO WORLD");
|
||||
|
||||
// Delta time
|
||||
dt = millis() - dt;
|
||||
Serial.print("QR Code Generation Time: ");
|
||||
Serial.print(dt);
|
||||
Serial.print("\n");
|
||||
|
||||
// Top quiet zone
|
||||
Serial.print("\n\n\n\n");
|
||||
|
||||
for (uint8_t y = 0; y < qrcode.size; y++) {
|
||||
|
||||
// Left quiet zone
|
||||
Serial.print(" ");
|
||||
|
||||
// Each horizontal module
|
||||
for (uint8_t x = 0; x < qrcode.size; x++) {
|
||||
|
||||
// Print each module (UTF-8 \u2588 is a solid block)
|
||||
Serial.print(qrcode_getModule(&qrcode, x, y) ? "\u2588\u2588": " ");
|
||||
|
||||
}
|
||||
|
||||
Serial.print("\n");
|
||||
}
|
||||
|
||||
// Bottom quiet zone
|
||||
Serial.print("\n\n\n\n");
|
||||
}
|
||||
|
||||
void loop() {
|
||||
|
||||
}
|
@ -1,62 +0,0 @@
|
||||
Data = [
|
||||
["1", "41", "25", "17", "34", "20", "14","27", "16", "11","17", "10", "7"],
|
||||
["2", "77", "47", "32", "63", "38", "26", "48", "29", "20", "34", "20", "14"],
|
||||
["3", "127", "77", "53", "101", "61", "42", "77", "47", "32", "58", "35", "24"],
|
||||
["4", "187", "114", "78", "149", "90", "62", "111", "67", "46", "82", "50", "34"],
|
||||
["5", "255", "154", "106", "202", "122", "84", "144", "87", "60", "106", "64", "44"],
|
||||
["6", "322", "195", "134", "255", "154", "106", "178", "108", "74", "139", "84", "58"],
|
||||
["7", "370", "224", "154", "293", "178", "122", "207", "125", "86", "154", "93", "64"],
|
||||
["8", "461", "279", "192", "365", "221", "152", "259", "157", "108", "202", "122", "84"],
|
||||
["9", "552", "335", "230", "432", "262", "180", "312", "189", "130", "235", "143", "98"],
|
||||
["10", "652", "395", "271", "513", "311", "213", "364", "221", "151", "288", "174", "119"],
|
||||
["11", "772", "468", "321", "604", "366", "251", "427", "259", "177", "331", "200", "137"],
|
||||
["12", "883", "535", "367", "691", "419", "287", "489", "296", "203", "374", "227", "155"],
|
||||
["13", "1022", "619", "425", "796", "483", "331", "580", "352", "241", "427", "259", "177"],
|
||||
["14", "1101", "667", "458", "871", "528", "362", "621", "376", "258", "468", "283", "194"],
|
||||
["15", "1250", "758", "520", "991", "600", "412", "703", "426", "292", "530", "321", "220"],
|
||||
["16", "1408", "854", "586", "1082", "656", "450", "775", "470", "322", "602", "365", "250"],
|
||||
["17", "1548", "938", "644", "1212", "734", "504", "876", "531", "364", "674", "408", "280"],
|
||||
["18", "1725", "1046", "718", "1346", "816", "560", "948", "574", "394", "746", "452", "310"],
|
||||
["19", "1903", "1153", "792", "1500", "909", "624", "1063", "644", "442", "813", "493", "338"],
|
||||
["20", "2061", "1249", "858", "1600", "970", "666", "1159", "702", "482", "919", "557", "382"],
|
||||
["21", "2232", "1352", "929", "1708", "1035", "711", "1224", "742", "509", "969", "587", "403"],
|
||||
["22", "2409", "1460", "1003", "1872", "1134", "779", "1358", "823", "565", "1056", "640", "439"],
|
||||
["23", "2620", "1588", "1091", "2059", "1248", "857", "1468", "890", "611", "1108", "672", "461"],
|
||||
["24", "2812", "1704", "1171", "2188", "1326", "911", "1588", "963", "661", "1228", "744", "511"],
|
||||
["25", "3057", "1853", "1273", "2395", "1451", "997", "1718", "1041", "715", "1286", "779", "535"],
|
||||
["26", "3283", "1990", "1367", "2544", "1542", "1059", "1804", "1094", "751", "1425", "864", "593"],
|
||||
["27", "3517", "2132", "1465", "2701", "1637", "1125", "1933", "1172", "805", "1501", "910", "625"],
|
||||
["28", "3669", "2223", "1528", "2857", "1732", "1190", "2085", "1263", "868", "1581", "958", "658"],
|
||||
["29", "3909", "2369", "1628", "3035", "1839", "1264", "2181", "1322", "908", "1677", "1016", "698"],
|
||||
["30", "4158", "2520", "1732", "3289", "1994", "1370", "2358", "1429", "982", "1782", "1080", "742"],
|
||||
["31", "4417", "2677", "1840", "3486", "2113", "1452", "2473", "1499", "1030", "1897", "1150", "790"],
|
||||
["32", "4686", "2840", "1952", "3693", "2238", "1538", "2670", "1618", "1112", "2022", "1226", "842"],
|
||||
["33", "4965", "3009", "2068", "3909", "2369", "1628", "2805", "1700", "1168", "2157", "1307", "898"],
|
||||
["34", "5253", "3183", "2188", "4134", "2506", "1722", "2949", "1787", "1228", "2301", "1394", "958"],
|
||||
["35", "5529", "3351", "2303", "4343", "2632", "1809", "3081", "1867", "1283", "2361", "1431", "983"],
|
||||
["36", "5836", "3537", "2431", "4588", "2780", "1911", "3244", "1966", "1351", "2524", "1530", "1051"],
|
||||
["37", "6153", "3729", "2563", "4775", "2894", "1989", "3417", "2071", "1423", "2625", "1591", "1093"],
|
||||
["38", "6479", "3927", "2699", "5039", "3054", "2099", "3599", "2181", "1499", "2735", "1658", "1139"],
|
||||
["39", "6743", "4087", "2809", "5313", "3220", "2213", "3791", "2298", "1579", "2927", "1774", "1219"],
|
||||
["40", "7089", "4296", "2953", "5596", "3391", "2331", "3993", "2420", "1663", "3057", "1852", "1273"],
|
||||
]
|
||||
Template = ''' <tr>
|
||||
<td rowspan="4">%s</td>
|
||||
<td rowspan="4">%s</td>
|
||||
<td>LOW</td><td>%s</td><td>%s</td><td>%s</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>MEDIUM</td><td>%s</td><td>%s</td><td>%s</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>QUARTILE</td><td>%s</td><td>%s</td><td>%s</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>HIGH</td><td>%s</td><td>%s</td><td>%s</td>
|
||||
</tr>'''
|
||||
|
||||
for data in Data:
|
||||
data = data[:]
|
||||
size = 4 * int(data[0]) + 17
|
||||
data.insert(1, "%d x %d" % (size, size))
|
||||
print Template % tuple(data)
|
@ -1,31 +0,0 @@
|
||||
|
||||
# Datatypes (KEYWORD1)
|
||||
|
||||
bool KEYWORD1
|
||||
uint8_t KEYWORD1
|
||||
QRCode KEYWORD1
|
||||
|
||||
|
||||
# Methods and Functions (KEYWORD2)
|
||||
|
||||
qrcode_getBufferSize KEYWORD2
|
||||
qrcode_initText KEYWORD2
|
||||
qrcode_initBytes KEYWORD2
|
||||
qrcode_getModule KEYWORD2
|
||||
|
||||
|
||||
# Instances (KEYWORD2)
|
||||
|
||||
|
||||
# Constants (LITERAL1)
|
||||
|
||||
false LITERAL1
|
||||
true LITERAL1
|
||||
|
||||
ECC_LOW LITERAL1
|
||||
ECC_MEDIUM LITERAL1
|
||||
ECC_QUARTILE LITERAL1
|
||||
ECC_HIGH LITERAL1
|
||||
MODE_NUMERIC LITERAL1
|
||||
MODE_ALPHANUMERIC LITERAL1
|
||||
MODE_BYTE LITERAL1
|
@ -1,10 +0,0 @@
|
||||
name=QRCode
|
||||
version=0.0.1
|
||||
author=Richard Moore <me@ricmoo.com>
|
||||
maintainer=Richard Moore <me@ricmoo.com>
|
||||
sentence=A simple QR code generation library.
|
||||
paragraph=A simple QR code generation library.
|
||||
category=Other
|
||||
url=https://github.com/ricmoo/qrcode/
|
||||
architectures=*
|
||||
includes=qrcode.h
|
@ -1,876 +0,0 @@
|
||||
/**
|
||||
* The MIT License (MIT)
|
||||
*
|
||||
* This library is written and maintained by Richard Moore.
|
||||
* Major parts were derived from Project Nayuki's library.
|
||||
*
|
||||
* Copyright (c) 2017 Richard Moore (https://github.com/ricmoo/QRCode)
|
||||
* Copyright (c) 2017 Project Nayuki (https://www.nayuki.io/page/qr-code-generator-library)
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Special thanks to Nayuki (https://www.nayuki.io/) from which this library was
|
||||
* heavily inspired and compared against.
|
||||
*
|
||||
* See: https://github.com/nayuki/QR-Code-generator/tree/master/cpp
|
||||
*/
|
||||
|
||||
#include "qrcode.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#pragma mark - Error Correction Lookup tables
|
||||
|
||||
#if LOCK_VERSION == 0
|
||||
|
||||
static const uint16_t NUM_ERROR_CORRECTION_CODEWORDS[4][40] = {
|
||||
// 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40 Error correction level
|
||||
{ 10, 16, 26, 36, 48, 64, 72, 88, 110, 130, 150, 176, 198, 216, 240, 280, 308, 338, 364, 416, 442, 476, 504, 560, 588, 644, 700, 728, 784, 812, 868, 924, 980, 1036, 1064, 1120, 1204, 1260, 1316, 1372}, // Medium
|
||||
{ 7, 10, 15, 20, 26, 36, 40, 48, 60, 72, 80, 96, 104, 120, 132, 144, 168, 180, 196, 224, 224, 252, 270, 300, 312, 336, 360, 390, 420, 450, 480, 510, 540, 570, 570, 600, 630, 660, 720, 750}, // Low
|
||||
{ 17, 28, 44, 64, 88, 112, 130, 156, 192, 224, 264, 308, 352, 384, 432, 480, 532, 588, 650, 700, 750, 816, 900, 960, 1050, 1110, 1200, 1260, 1350, 1440, 1530, 1620, 1710, 1800, 1890, 1980, 2100, 2220, 2310, 2430}, // High
|
||||
{ 13, 22, 36, 52, 72, 96, 108, 132, 160, 192, 224, 260, 288, 320, 360, 408, 448, 504, 546, 600, 644, 690, 750, 810, 870, 952, 1020, 1050, 1140, 1200, 1290, 1350, 1440, 1530, 1590, 1680, 1770, 1860, 1950, 2040}, // Quartile
|
||||
};
|
||||
|
||||
static const uint8_t NUM_ERROR_CORRECTION_BLOCKS[4][40] = {
|
||||
// Version: (note that index 0 is for padding, and is set to an illegal value)
|
||||
// 1, 2, 3, 4, 5, 6, 7, 8, 9,10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40 Error correction level
|
||||
{ 1, 1, 1, 2, 2, 4, 4, 4, 5, 5, 5, 8, 9, 9, 10, 10, 11, 13, 14, 16, 17, 17, 18, 20, 21, 23, 25, 26, 28, 29, 31, 33, 35, 37, 38, 40, 43, 45, 47, 49}, // Medium
|
||||
{ 1, 1, 1, 1, 1, 2, 2, 2, 2, 4, 4, 4, 4, 4, 6, 6, 6, 6, 7, 8, 8, 9, 9, 10, 12, 12, 12, 13, 14, 15, 16, 17, 18, 19, 19, 20, 21, 22, 24, 25}, // Low
|
||||
{ 1, 1, 2, 4, 4, 4, 5, 6, 8, 8, 11, 11, 16, 16, 18, 16, 19, 21, 25, 25, 25, 34, 30, 32, 35, 37, 40, 42, 45, 48, 51, 54, 57, 60, 63, 66, 70, 74, 77, 81}, // High
|
||||
{ 1, 1, 2, 2, 4, 4, 6, 6, 8, 8, 8, 10, 12, 16, 12, 17, 16, 18, 21, 20, 23, 23, 25, 27, 29, 34, 34, 35, 38, 40, 43, 45, 48, 51, 53, 56, 59, 62, 65, 68}, // Quartile
|
||||
};
|
||||
|
||||
static const uint16_t NUM_RAW_DATA_MODULES[40] = {
|
||||
// 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17,
|
||||
208, 359, 567, 807, 1079, 1383, 1568, 1936, 2336, 2768, 3232, 3728, 4256, 4651, 5243, 5867, 6523,
|
||||
// 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31,
|
||||
7211, 7931, 8683, 9252, 10068, 10916, 11796, 12708, 13652, 14628, 15371, 16411, 17483, 18587,
|
||||
// 32, 33, 34, 35, 36, 37, 38, 39, 40
|
||||
19723, 20891, 22091, 23008, 24272, 25568, 26896, 28256, 29648
|
||||
};
|
||||
|
||||
// @TODO: Put other LOCK_VERSIONS here
|
||||
#elif LOCK_VERSION == 3
|
||||
|
||||
static const int16_t NUM_ERROR_CORRECTION_CODEWORDS[4] = {
|
||||
26, 15, 44, 36
|
||||
};
|
||||
|
||||
static const int8_t NUM_ERROR_CORRECTION_BLOCKS[4] = {
|
||||
1, 1, 2, 2
|
||||
};
|
||||
|
||||
static const uint16_t NUM_RAW_DATA_MODULES = 567;
|
||||
|
||||
#else
|
||||
|
||||
#error Unsupported LOCK_VERSION (add it...)
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
static int max(int a, int b) {
|
||||
if (a > b) { return a; }
|
||||
return b;
|
||||
}
|
||||
|
||||
/*
|
||||
static int abs(int value) {
|
||||
if (value < 0) { return -value; }
|
||||
return value;
|
||||
}
|
||||
*/
|
||||
|
||||
|
||||
#pragma mark - Mode testing and conversion
|
||||
|
||||
static int8_t getAlphanumeric(char c) {
|
||||
|
||||
if (c >= '0' && c <= '9') { return (c - '0'); }
|
||||
if (c >= 'A' && c <= 'Z') { return (c - 'A' + 10); }
|
||||
|
||||
switch (c) {
|
||||
case ' ': return 36;
|
||||
case '$': return 37;
|
||||
case '%': return 38;
|
||||
case '*': return 39;
|
||||
case '+': return 40;
|
||||
case '-': return 41;
|
||||
case '.': return 42;
|
||||
case '/': return 43;
|
||||
case ':': return 44;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
static bool isAlphanumeric(const char *text, uint16_t length) {
|
||||
while (length != 0) {
|
||||
if (getAlphanumeric(text[--length]) == -1) { return false; }
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
static bool isNumeric(const char *text, uint16_t length) {
|
||||
while (length != 0) {
|
||||
char c = text[--length];
|
||||
if (c < '0' || c > '9') { return false; }
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - Counting
|
||||
|
||||
// We store the following tightly packed (less 8) in modeInfo
|
||||
// <=9 <=26 <= 40
|
||||
// NUMERIC ( 10, 12, 14);
|
||||
// ALPHANUMERIC ( 9, 11, 13);
|
||||
// BYTE ( 8, 16, 16);
|
||||
static char getModeBits(uint8_t version, uint8_t mode) {
|
||||
// Note: We use 15 instead of 16; since 15 doesn't exist and we cannot store 16 (8 + 8) in 3 bits
|
||||
// hex(int("".join(reversed([('00' + bin(x - 8)[2:])[-3:] for x in [10, 9, 8, 12, 11, 15, 14, 13, 15]])), 2))
|
||||
unsigned int modeInfo = 0x7bbb80a;
|
||||
|
||||
#if LOCK_VERSION == 0 || LOCK_VERSION > 9
|
||||
if (version > 9) { modeInfo >>= 9; }
|
||||
#endif
|
||||
|
||||
#if LOCK_VERSION == 0 || LOCK_VERSION > 26
|
||||
if (version > 26) { modeInfo >>= 9; }
|
||||
#endif
|
||||
|
||||
char result = 8 + ((modeInfo >> (3 * mode)) & 0x07);
|
||||
if (result == 15) { result = 16; }
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - BitBucket
|
||||
|
||||
typedef struct BitBucket {
|
||||
uint32_t bitOffsetOrWidth;
|
||||
uint16_t capacityBytes;
|
||||
uint8_t *data;
|
||||
} BitBucket;
|
||||
|
||||
/*
|
||||
void bb_dump(BitBucket *bitBuffer) {
|
||||
printf("Buffer: ");
|
||||
for (uint32_t i = 0; i < bitBuffer->capacityBytes; i++) {
|
||||
printf("%02x", bitBuffer->data[i]);
|
||||
if ((i % 4) == 3) { printf(" "); }
|
||||
}
|
||||
printf("\n");
|
||||
}
|
||||
*/
|
||||
|
||||
static uint16_t bb_getGridSizeBytes(uint8_t size) {
|
||||
return (((size * size) + 7) / 8);
|
||||
}
|
||||
|
||||
static uint16_t bb_getBufferSizeBytes(uint32_t bits) {
|
||||
return ((bits + 7) / 8);
|
||||
}
|
||||
|
||||
static void bb_initBuffer(BitBucket *bitBuffer, uint8_t *data, int32_t capacityBytes) {
|
||||
bitBuffer->bitOffsetOrWidth = 0;
|
||||
bitBuffer->capacityBytes = capacityBytes;
|
||||
bitBuffer->data = data;
|
||||
|
||||
memset(data, 0, bitBuffer->capacityBytes);
|
||||
}
|
||||
|
||||
static void bb_initGrid(BitBucket *bitGrid, uint8_t *data, uint8_t size) {
|
||||
bitGrid->bitOffsetOrWidth = size;
|
||||
bitGrid->capacityBytes = bb_getGridSizeBytes(size);
|
||||
bitGrid->data = data;
|
||||
|
||||
memset(data, 0, bitGrid->capacityBytes);
|
||||
}
|
||||
|
||||
static void bb_appendBits(BitBucket *bitBuffer, uint32_t val, uint8_t length) {
|
||||
uint32_t offset = bitBuffer->bitOffsetOrWidth;
|
||||
for (int8_t i = length - 1; i >= 0; i--, offset++) {
|
||||
bitBuffer->data[offset >> 3] |= ((val >> i) & 1) << (7 - (offset & 7));
|
||||
}
|
||||
bitBuffer->bitOffsetOrWidth = offset;
|
||||
}
|
||||
/*
|
||||
void bb_setBits(BitBucket *bitBuffer, uint32_t val, int offset, uint8_t length) {
|
||||
for (int8_t i = length - 1; i >= 0; i--, offset++) {
|
||||
bitBuffer->data[offset >> 3] |= ((val >> i) & 1) << (7 - (offset & 7));
|
||||
}
|
||||
}
|
||||
*/
|
||||
static void bb_setBit(BitBucket *bitGrid, uint8_t x, uint8_t y, bool on) {
|
||||
uint32_t offset = y * bitGrid->bitOffsetOrWidth + x;
|
||||
uint8_t mask = 1 << (7 - (offset & 0x07));
|
||||
if (on) {
|
||||
bitGrid->data[offset >> 3] |= mask;
|
||||
} else {
|
||||
bitGrid->data[offset >> 3] &= ~mask;
|
||||
}
|
||||
}
|
||||
|
||||
static void bb_invertBit(BitBucket *bitGrid, uint8_t x, uint8_t y, bool invert) {
|
||||
uint32_t offset = y * bitGrid->bitOffsetOrWidth + x;
|
||||
uint8_t mask = 1 << (7 - (offset & 0x07));
|
||||
bool on = ((bitGrid->data[offset >> 3] & (1 << (7 - (offset & 0x07)))) != 0);
|
||||
if (on ^ invert) {
|
||||
bitGrid->data[offset >> 3] |= mask;
|
||||
} else {
|
||||
bitGrid->data[offset >> 3] &= ~mask;
|
||||
}
|
||||
}
|
||||
|
||||
static bool bb_getBit(BitBucket *bitGrid, uint8_t x, uint8_t y) {
|
||||
uint32_t offset = y * bitGrid->bitOffsetOrWidth + x;
|
||||
return (bitGrid->data[offset >> 3] & (1 << (7 - (offset & 0x07)))) != 0;
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - Drawing Patterns
|
||||
|
||||
// XORs the data modules in this QR Code with the given mask pattern. Due to XOR's mathematical
|
||||
// properties, calling applyMask(m) twice with the same value is equivalent to no change at all.
|
||||
// This means it is possible to apply a mask, undo it, and try another mask. Note that a final
|
||||
// well-formed QR Code symbol needs exactly one mask applied (not zero, not two, etc.).
|
||||
static void applyMask(BitBucket *modules, BitBucket *isFunction, uint8_t mask) {
|
||||
uint8_t size = modules->bitOffsetOrWidth;
|
||||
|
||||
for (uint8_t y = 0; y < size; y++) {
|
||||
for (uint8_t x = 0; x < size; x++) {
|
||||
if (bb_getBit(isFunction, x, y)) { continue; }
|
||||
|
||||
bool invert = 0;
|
||||
switch (mask) {
|
||||
case 0: invert = (x + y) % 2 == 0; break;
|
||||
case 1: invert = y % 2 == 0; break;
|
||||
case 2: invert = x % 3 == 0; break;
|
||||
case 3: invert = (x + y) % 3 == 0; break;
|
||||
case 4: invert = (x / 3 + y / 2) % 2 == 0; break;
|
||||
case 5: invert = x * y % 2 + x * y % 3 == 0; break;
|
||||
case 6: invert = (x * y % 2 + x * y % 3) % 2 == 0; break;
|
||||
case 7: invert = ((x + y) % 2 + x * y % 3) % 2 == 0; break;
|
||||
}
|
||||
bb_invertBit(modules, x, y, invert);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void setFunctionModule(BitBucket *modules, BitBucket *isFunction, uint8_t x, uint8_t y, bool on) {
|
||||
bb_setBit(modules, x, y, on);
|
||||
bb_setBit(isFunction, x, y, true);
|
||||
}
|
||||
|
||||
// Draws a 9*9 finder pattern including the border separator, with the center module at (x, y).
|
||||
static void drawFinderPattern(BitBucket *modules, BitBucket *isFunction, uint8_t x, uint8_t y) {
|
||||
uint8_t size = modules->bitOffsetOrWidth;
|
||||
|
||||
for (int8_t i = -4; i <= 4; i++) {
|
||||
for (int8_t j = -4; j <= 4; j++) {
|
||||
uint8_t dist = max(abs(i), abs(j)); // Chebyshev/infinity norm
|
||||
int16_t xx = x + j, yy = y + i;
|
||||
if (0 <= xx && xx < size && 0 <= yy && yy < size) {
|
||||
setFunctionModule(modules, isFunction, xx, yy, dist != 2 && dist != 4);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Draws a 5*5 alignment pattern, with the center module at (x, y).
|
||||
static void drawAlignmentPattern(BitBucket *modules, BitBucket *isFunction, uint8_t x, uint8_t y) {
|
||||
for (int8_t i = -2; i <= 2; i++) {
|
||||
for (int8_t j = -2; j <= 2; j++) {
|
||||
setFunctionModule(modules, isFunction, x + j, y + i, max(abs(i), abs(j)) != 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Draws two copies of the format bits (with its own error correction code)
|
||||
// based on the given mask and this object's error correction level field.
|
||||
static void drawFormatBits(BitBucket *modules, BitBucket *isFunction, uint8_t ecc, uint8_t mask) {
|
||||
|
||||
uint8_t size = modules->bitOffsetOrWidth;
|
||||
|
||||
// Calculate error correction code and pack bits
|
||||
uint32_t data = ecc << 3 | mask; // errCorrLvl is uint2, mask is uint3
|
||||
uint32_t rem = data;
|
||||
for (int i = 0; i < 10; i++) {
|
||||
rem = (rem << 1) ^ ((rem >> 9) * 0x537);
|
||||
}
|
||||
|
||||
data = data << 10 | rem;
|
||||
data ^= 0x5412; // uint15
|
||||
|
||||
// Draw first copy
|
||||
for (uint8_t i = 0; i <= 5; i++) {
|
||||
setFunctionModule(modules, isFunction, 8, i, ((data >> i) & 1) != 0);
|
||||
}
|
||||
|
||||
setFunctionModule(modules, isFunction, 8, 7, ((data >> 6) & 1) != 0);
|
||||
setFunctionModule(modules, isFunction, 8, 8, ((data >> 7) & 1) != 0);
|
||||
setFunctionModule(modules, isFunction, 7, 8, ((data >> 8) & 1) != 0);
|
||||
|
||||
for (int8_t i = 9; i < 15; i++) {
|
||||
setFunctionModule(modules, isFunction, 14 - i, 8, ((data >> i) & 1) != 0);
|
||||
}
|
||||
|
||||
// Draw second copy
|
||||
for (int8_t i = 0; i <= 7; i++) {
|
||||
setFunctionModule(modules, isFunction, size - 1 - i, 8, ((data >> i) & 1) != 0);
|
||||
}
|
||||
|
||||
for (int8_t i = 8; i < 15; i++) {
|
||||
setFunctionModule(modules, isFunction, 8, size - 15 + i, ((data >> i) & 1) != 0);
|
||||
}
|
||||
|
||||
setFunctionModule(modules, isFunction, 8, size - 8, true);
|
||||
}
|
||||
|
||||
|
||||
// Draws two copies of the version bits (with its own error correction code),
|
||||
// based on this object's version field (which only has an effect for 7 <= version <= 40).
|
||||
static void drawVersion(BitBucket *modules, BitBucket *isFunction, uint8_t version) {
|
||||
|
||||
int8_t size = modules->bitOffsetOrWidth;
|
||||
|
||||
#if LOCK_VERSION != 0 && LOCK_VERSION < 7
|
||||
return;
|
||||
|
||||
#else
|
||||
if (version < 7) { return; }
|
||||
|
||||
// Calculate error correction code and pack bits
|
||||
uint32_t rem = version; // version is uint6, in the range [7, 40]
|
||||
for (uint8_t i = 0; i < 12; i++) {
|
||||
rem = (rem << 1) ^ ((rem >> 11) * 0x1F25);
|
||||
}
|
||||
|
||||
uint32_t data = version << 12 | rem; // uint18
|
||||
|
||||
// Draw two copies
|
||||
for (uint8_t i = 0; i < 18; i++) {
|
||||
bool bit = ((data >> i) & 1) != 0;
|
||||
uint8_t a = size - 11 + i % 3, b = i / 3;
|
||||
setFunctionModule(modules, isFunction, a, b, bit);
|
||||
setFunctionModule(modules, isFunction, b, a, bit);
|
||||
}
|
||||
|
||||
#endif
|
||||
}
|
||||
|
||||
static void drawFunctionPatterns(BitBucket *modules, BitBucket *isFunction, uint8_t version, uint8_t ecc) {
|
||||
|
||||
uint8_t size = modules->bitOffsetOrWidth;
|
||||
|
||||
// Draw the horizontal and vertical timing patterns
|
||||
for (uint8_t i = 0; i < size; i++) {
|
||||
setFunctionModule(modules, isFunction, 6, i, i % 2 == 0);
|
||||
setFunctionModule(modules, isFunction, i, 6, i % 2 == 0);
|
||||
}
|
||||
|
||||
// Draw 3 finder patterns (all corners except bottom right; overwrites some timing modules)
|
||||
drawFinderPattern(modules, isFunction, 3, 3);
|
||||
drawFinderPattern(modules, isFunction, size - 4, 3);
|
||||
drawFinderPattern(modules, isFunction, 3, size - 4);
|
||||
|
||||
#if LOCK_VERSION == 0 || LOCK_VERSION > 1
|
||||
|
||||
if (version > 1) {
|
||||
|
||||
// Draw the numerous alignment patterns
|
||||
|
||||
uint8_t alignCount = version / 7 + 2;
|
||||
uint8_t step;
|
||||
if (version != 32) {
|
||||
step = (version * 4 + alignCount * 2 + 1) / (2 * alignCount - 2) * 2; // ceil((size - 13) / (2*numAlign - 2)) * 2
|
||||
} else { // C-C-C-Combo breaker!
|
||||
step = 26;
|
||||
}
|
||||
|
||||
uint8_t alignPositionIndex = alignCount - 1;
|
||||
uint8_t alignPosition[alignCount];
|
||||
|
||||
alignPosition[0] = 6;
|
||||
|
||||
uint8_t size = version * 4 + 17;
|
||||
for (uint8_t i = 0, pos = size - 7; i < alignCount - 1; i++, pos -= step) {
|
||||
alignPosition[alignPositionIndex--] = pos;
|
||||
}
|
||||
|
||||
for (uint8_t i = 0; i < alignCount; i++) {
|
||||
for (uint8_t j = 0; j < alignCount; j++) {
|
||||
if ((i == 0 && j == 0) || (i == 0 && j == alignCount - 1) || (i == alignCount - 1 && j == 0)) {
|
||||
continue; // Skip the three finder corners
|
||||
} else {
|
||||
drawAlignmentPattern(modules, isFunction, alignPosition[i], alignPosition[j]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
// Draw configuration data
|
||||
drawFormatBits(modules, isFunction, ecc, 0); // Dummy mask value; overwritten later in the constructor
|
||||
drawVersion(modules, isFunction, version);
|
||||
}
|
||||
|
||||
|
||||
// Draws the given sequence of 8-bit codewords (data and error correction) onto the entire
|
||||
// data area of this QR Code symbol. Function modules need to be marked off before this is called.
|
||||
static void drawCodewords(BitBucket *modules, BitBucket *isFunction, BitBucket *codewords) {
|
||||
|
||||
uint32_t bitLength = codewords->bitOffsetOrWidth;
|
||||
uint8_t *data = codewords->data;
|
||||
|
||||
uint8_t size = modules->bitOffsetOrWidth;
|
||||
|
||||
// Bit index into the data
|
||||
uint32_t i = 0;
|
||||
|
||||
// Do the funny zigzag scan
|
||||
for (int16_t right = size - 1; right >= 1; right -= 2) { // Index of right column in each column pair
|
||||
if (right == 6) { right = 5; }
|
||||
|
||||
for (uint8_t vert = 0; vert < size; vert++) { // Vertical counter
|
||||
for (int j = 0; j < 2; j++) {
|
||||
uint8_t x = right - j; // Actual x coordinate
|
||||
bool upwards = ((right & 2) == 0) ^ (x < 6);
|
||||
uint8_t y = upwards ? size - 1 - vert : vert; // Actual y coordinate
|
||||
if (!bb_getBit(isFunction, x, y) && i < bitLength) {
|
||||
bb_setBit(modules, x, y, ((data[i >> 3] >> (7 - (i & 7))) & 1) != 0);
|
||||
i++;
|
||||
}
|
||||
// If there are any remainder bits (0 to 7), they are already
|
||||
// set to 0/false/white when the grid of modules was initialized
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
#pragma mark - Penalty Calculation
|
||||
|
||||
#define PENALTY_N1 3
|
||||
#define PENALTY_N2 3
|
||||
#define PENALTY_N3 40
|
||||
#define PENALTY_N4 10
|
||||
|
||||
// Calculates and returns the penalty score based on state of this QR Code's current modules.
|
||||
// This is used by the automatic mask choice algorithm to find the mask pattern that yields the lowest score.
|
||||
// @TODO: This can be optimized by working with the bytes instead of bits.
|
||||
static uint32_t getPenaltyScore(BitBucket *modules) {
|
||||
uint32_t result = 0;
|
||||
|
||||
uint8_t size = modules->bitOffsetOrWidth;
|
||||
|
||||
// Adjacent modules in row having same color
|
||||
for (uint8_t y = 0; y < size; y++) {
|
||||
|
||||
bool colorX = bb_getBit(modules, 0, y);
|
||||
for (uint8_t x = 1, runX = 1; x < size; x++) {
|
||||
bool cx = bb_getBit(modules, x, y);
|
||||
if (cx != colorX) {
|
||||
colorX = cx;
|
||||
runX = 1;
|
||||
|
||||
} else {
|
||||
runX++;
|
||||
if (runX == 5) {
|
||||
result += PENALTY_N1;
|
||||
} else if (runX > 5) {
|
||||
result++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Adjacent modules in column having same color
|
||||
for (uint8_t x = 0; x < size; x++) {
|
||||
bool colorY = bb_getBit(modules, x, 0);
|
||||
for (uint8_t y = 1, runY = 1; y < size; y++) {
|
||||
bool cy = bb_getBit(modules, x, y);
|
||||
if (cy != colorY) {
|
||||
colorY = cy;
|
||||
runY = 1;
|
||||
} else {
|
||||
runY++;
|
||||
if (runY == 5) {
|
||||
result += PENALTY_N1;
|
||||
} else if (runY > 5) {
|
||||
result++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
uint16_t black = 0;
|
||||
for (uint8_t y = 0; y < size; y++) {
|
||||
uint16_t bitsRow = 0, bitsCol = 0;
|
||||
for (uint8_t x = 0; x < size; x++) {
|
||||
bool color = bb_getBit(modules, x, y);
|
||||
|
||||
// 2*2 blocks of modules having same color
|
||||
if (x > 0 && y > 0) {
|
||||
bool colorUL = bb_getBit(modules, x - 1, y - 1);
|
||||
bool colorUR = bb_getBit(modules, x, y - 1);
|
||||
bool colorL = bb_getBit(modules, x - 1, y);
|
||||
if (color == colorUL && color == colorUR && color == colorL) {
|
||||
result += PENALTY_N2;
|
||||
}
|
||||
}
|
||||
|
||||
// Finder-like pattern in rows and columns
|
||||
bitsRow = ((bitsRow << 1) & 0x7FF) | color;
|
||||
bitsCol = ((bitsCol << 1) & 0x7FF) | bb_getBit(modules, y, x);
|
||||
|
||||
// Needs 11 bits accumulated
|
||||
if (x >= 10) {
|
||||
if (bitsRow == 0x05D || bitsRow == 0x5D0) {
|
||||
result += PENALTY_N3;
|
||||
}
|
||||
if (bitsCol == 0x05D || bitsCol == 0x5D0) {
|
||||
result += PENALTY_N3;
|
||||
}
|
||||
}
|
||||
|
||||
// Balance of black and white modules
|
||||
if (color) { black++; }
|
||||
}
|
||||
}
|
||||
|
||||
// Find smallest k such that (45-5k)% <= dark/total <= (55+5k)%
|
||||
uint16_t total = size * size;
|
||||
for (uint16_t k = 0; black * 20 < (9 - k) * total || black * 20 > (11 + k) * total; k++) {
|
||||
result += PENALTY_N4;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - Reed-Solomon Generator
|
||||
|
||||
static uint8_t rs_multiply(uint8_t x, uint8_t y) {
|
||||
// Russian peasant multiplication
|
||||
// See: https://en.wikipedia.org/wiki/Ancient_Egyptian_multiplication
|
||||
uint16_t z = 0;
|
||||
for (int8_t i = 7; i >= 0; i--) {
|
||||
z = (z << 1) ^ ((z >> 7) * 0x11D);
|
||||
z ^= ((y >> i) & 1) * x;
|
||||
}
|
||||
return z;
|
||||
}
|
||||
|
||||
static void rs_init(uint8_t degree, uint8_t *coeff) {
|
||||
memset(coeff, 0, degree);
|
||||
coeff[degree - 1] = 1;
|
||||
|
||||
// Compute the product polynomial (x - r^0) * (x - r^1) * (x - r^2) * ... * (x - r^{degree-1}),
|
||||
// drop the highest term, and store the rest of the coefficients in order of descending powers.
|
||||
// Note that r = 0x02, which is a generator element of this field GF(2^8/0x11D).
|
||||
uint16_t root = 1;
|
||||
for (uint8_t i = 0; i < degree; i++) {
|
||||
// Multiply the current product by (x - r^i)
|
||||
for (uint8_t j = 0; j < degree; j++) {
|
||||
coeff[j] = rs_multiply(coeff[j], root);
|
||||
if (j + 1 < degree) {
|
||||
coeff[j] ^= coeff[j + 1];
|
||||
}
|
||||
}
|
||||
root = (root << 1) ^ ((root >> 7) * 0x11D); // Multiply by 0x02 mod GF(2^8/0x11D)
|
||||
}
|
||||
}
|
||||
|
||||
static void rs_getRemainder(uint8_t degree, uint8_t *coeff, uint8_t *data, uint8_t length, uint8_t *result, uint8_t stride) {
|
||||
// Compute the remainder by performing polynomial division
|
||||
|
||||
//for (uint8_t i = 0; i < degree; i++) { result[] = 0; }
|
||||
//memset(result, 0, degree);
|
||||
|
||||
for (uint8_t i = 0; i < length; i++) {
|
||||
uint8_t factor = data[i] ^ result[0];
|
||||
for (uint8_t j = 1; j < degree; j++) {
|
||||
result[(j - 1) * stride] = result[j * stride];
|
||||
}
|
||||
result[(degree - 1) * stride] = 0;
|
||||
|
||||
for (uint8_t j = 0; j < degree; j++) {
|
||||
result[j * stride] ^= rs_multiply(coeff[j], factor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
#pragma mark - QrCode
|
||||
|
||||
static int8_t encodeDataCodewords(BitBucket *dataCodewords, const uint8_t *text, uint16_t length, uint8_t version) {
|
||||
int8_t mode = MODE_BYTE;
|
||||
|
||||
if (isNumeric((char*)text, length)) {
|
||||
mode = MODE_NUMERIC;
|
||||
bb_appendBits(dataCodewords, 1 << MODE_NUMERIC, 4);
|
||||
bb_appendBits(dataCodewords, length, getModeBits(version, MODE_NUMERIC));
|
||||
|
||||
uint16_t accumData = 0;
|
||||
uint8_t accumCount = 0;
|
||||
for (uint16_t i = 0; i < length; i++) {
|
||||
accumData = accumData * 10 + ((char)(text[i]) - '0');
|
||||
accumCount++;
|
||||
if (accumCount == 3) {
|
||||
bb_appendBits(dataCodewords, accumData, 10);
|
||||
accumData = 0;
|
||||
accumCount = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// 1 or 2 digits remaining
|
||||
if (accumCount > 0) {
|
||||
bb_appendBits(dataCodewords, accumData, accumCount * 3 + 1);
|
||||
}
|
||||
|
||||
} else if (isAlphanumeric((char*)text, length)) {
|
||||
mode = MODE_ALPHANUMERIC;
|
||||
bb_appendBits(dataCodewords, 1 << MODE_ALPHANUMERIC, 4);
|
||||
bb_appendBits(dataCodewords, length, getModeBits(version, MODE_ALPHANUMERIC));
|
||||
|
||||
uint16_t accumData = 0;
|
||||
uint8_t accumCount = 0;
|
||||
for (uint16_t i = 0; i < length; i++) {
|
||||
accumData = accumData * 45 + getAlphanumeric((char)(text[i]));
|
||||
accumCount++;
|
||||
if (accumCount == 2) {
|
||||
bb_appendBits(dataCodewords, accumData, 11);
|
||||
accumData = 0;
|
||||
accumCount = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// 1 character remaining
|
||||
if (accumCount > 0) {
|
||||
bb_appendBits(dataCodewords, accumData, 6);
|
||||
}
|
||||
|
||||
} else {
|
||||
bb_appendBits(dataCodewords, 1 << MODE_BYTE, 4);
|
||||
bb_appendBits(dataCodewords, length, getModeBits(version, MODE_BYTE));
|
||||
for (uint16_t i = 0; i < length; i++) {
|
||||
bb_appendBits(dataCodewords, (char)(text[i]), 8);
|
||||
}
|
||||
}
|
||||
|
||||
//bb_setBits(dataCodewords, length, 4, getModeBits(version, mode));
|
||||
|
||||
return mode;
|
||||
}
|
||||
|
||||
static void performErrorCorrection(uint8_t version, uint8_t ecc, BitBucket *data) {
|
||||
|
||||
// See: http://www.thonky.com/qr-code-tutorial/structure-final-message
|
||||
|
||||
#if LOCK_VERSION == 0
|
||||
uint8_t numBlocks = NUM_ERROR_CORRECTION_BLOCKS[ecc][version - 1];
|
||||
uint16_t totalEcc = NUM_ERROR_CORRECTION_CODEWORDS[ecc][version - 1];
|
||||
uint16_t moduleCount = NUM_RAW_DATA_MODULES[version - 1];
|
||||
#else
|
||||
uint8_t numBlocks = NUM_ERROR_CORRECTION_BLOCKS[ecc];
|
||||
uint16_t totalEcc = NUM_ERROR_CORRECTION_CODEWORDS[ecc];
|
||||
uint16_t moduleCount = NUM_RAW_DATA_MODULES;
|
||||
#endif
|
||||
|
||||
uint8_t blockEccLen = totalEcc / numBlocks;
|
||||
uint8_t numShortBlocks = numBlocks - moduleCount / 8 % numBlocks;
|
||||
uint8_t shortBlockLen = moduleCount / 8 / numBlocks;
|
||||
|
||||
uint8_t shortDataBlockLen = shortBlockLen - blockEccLen;
|
||||
|
||||
uint8_t result[data->capacityBytes];
|
||||
memset(result, 0, sizeof(result));
|
||||
|
||||
uint8_t coeff[blockEccLen];
|
||||
rs_init(blockEccLen, coeff);
|
||||
|
||||
uint16_t offset = 0;
|
||||
uint8_t *dataBytes = data->data;
|
||||
|
||||
|
||||
// Interleave all short blocks
|
||||
for (uint8_t i = 0; i < shortDataBlockLen; i++) {
|
||||
uint16_t index = i;
|
||||
uint8_t stride = shortDataBlockLen;
|
||||
for (uint8_t blockNum = 0; blockNum < numBlocks; blockNum++) {
|
||||
result[offset++] = dataBytes[index];
|
||||
|
||||
#if LOCK_VERSION == 0 || LOCK_VERSION >= 5
|
||||
if (blockNum == numShortBlocks) { stride++; }
|
||||
#endif
|
||||
index += stride;
|
||||
}
|
||||
}
|
||||
|
||||
// Version less than 5 only have short blocks
|
||||
#if LOCK_VERSION == 0 || LOCK_VERSION >= 5
|
||||
{
|
||||
// Interleave long blocks
|
||||
uint16_t index = shortDataBlockLen * (numShortBlocks + 1);
|
||||
uint8_t stride = shortDataBlockLen;
|
||||
for (uint8_t blockNum = 0; blockNum < numBlocks - numShortBlocks; blockNum++) {
|
||||
result[offset++] = dataBytes[index];
|
||||
|
||||
if (blockNum == 0) { stride++; }
|
||||
index += stride;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
// Add all ecc blocks, interleaved
|
||||
uint8_t blockSize = shortDataBlockLen;
|
||||
for (uint8_t blockNum = 0; blockNum < numBlocks; blockNum++) {
|
||||
|
||||
#if LOCK_VERSION == 0 || LOCK_VERSION >= 5
|
||||
if (blockNum == numShortBlocks) { blockSize++; }
|
||||
#endif
|
||||
rs_getRemainder(blockEccLen, coeff, dataBytes, blockSize, &result[offset + blockNum], numBlocks);
|
||||
dataBytes += blockSize;
|
||||
}
|
||||
|
||||
memcpy(data->data, result, data->capacityBytes);
|
||||
data->bitOffsetOrWidth = moduleCount;
|
||||
}
|
||||
|
||||
// We store the Format bits tightly packed into a single byte (each of the 4 modes is 2 bits)
|
||||
// The format bits can be determined by ECC_FORMAT_BITS >> (2 * ecc)
|
||||
static const uint8_t ECC_FORMAT_BITS = (0x02 << 6) | (0x03 << 4) | (0x00 << 2) | (0x01 << 0);
|
||||
|
||||
|
||||
#pragma mark - Public QRCode functions
|
||||
|
||||
uint16_t qrcode_getBufferSize(uint8_t version) {
|
||||
return bb_getGridSizeBytes(4 * version + 17);
|
||||
}
|
||||
|
||||
// @TODO: Return error if data is too big.
|
||||
int8_t qrcode_initBytes(QRCode *qrcode, uint8_t *modules, uint8_t version, uint8_t ecc, uint8_t *data, uint16_t length) {
|
||||
uint8_t size = version * 4 + 17;
|
||||
qrcode->version = version;
|
||||
qrcode->size = size;
|
||||
qrcode->ecc = ecc;
|
||||
qrcode->modules = modules;
|
||||
|
||||
uint8_t eccFormatBits = (ECC_FORMAT_BITS >> (2 * ecc)) & 0x03;
|
||||
|
||||
#if LOCK_VERSION == 0
|
||||
uint16_t moduleCount = NUM_RAW_DATA_MODULES[version - 1];
|
||||
uint16_t dataCapacity = moduleCount / 8 - NUM_ERROR_CORRECTION_CODEWORDS[eccFormatBits][version - 1];
|
||||
#else
|
||||
version = LOCK_VERSION;
|
||||
uint16_t moduleCount = NUM_RAW_DATA_MODULES;
|
||||
uint16_t dataCapacity = moduleCount / 8 - NUM_ERROR_CORRECTION_CODEWORDS[eccFormatBits];
|
||||
#endif
|
||||
|
||||
struct BitBucket codewords;
|
||||
uint8_t codewordBytes[bb_getBufferSizeBytes(moduleCount)];
|
||||
bb_initBuffer(&codewords, codewordBytes, (int32_t)sizeof(codewordBytes));
|
||||
|
||||
// Place the data code words into the buffer
|
||||
int8_t mode = encodeDataCodewords(&codewords, data, length, version);
|
||||
|
||||
if (mode < 0) { return -1; }
|
||||
qrcode->mode = mode;
|
||||
|
||||
// Add terminator and pad up to a byte if applicable
|
||||
uint32_t padding = (dataCapacity * 8) - codewords.bitOffsetOrWidth;
|
||||
if (padding > 4) { padding = 4; }
|
||||
bb_appendBits(&codewords, 0, padding);
|
||||
bb_appendBits(&codewords, 0, (8 - codewords.bitOffsetOrWidth % 8) % 8);
|
||||
|
||||
// Pad with alternate bytes until data capacity is reached
|
||||
for (uint8_t padByte = 0xEC; codewords.bitOffsetOrWidth < (dataCapacity * 8); padByte ^= 0xEC ^ 0x11) {
|
||||
bb_appendBits(&codewords, padByte, 8);
|
||||
}
|
||||
|
||||
BitBucket modulesGrid;
|
||||
bb_initGrid(&modulesGrid, modules, size);
|
||||
|
||||
BitBucket isFunctionGrid;
|
||||
uint8_t isFunctionGridBytes[bb_getGridSizeBytes(size)];
|
||||
bb_initGrid(&isFunctionGrid, isFunctionGridBytes, size);
|
||||
|
||||
// Draw function patterns, draw all codewords, do masking
|
||||
drawFunctionPatterns(&modulesGrid, &isFunctionGrid, version, eccFormatBits);
|
||||
performErrorCorrection(version, eccFormatBits, &codewords);
|
||||
drawCodewords(&modulesGrid, &isFunctionGrid, &codewords);
|
||||
|
||||
// Find the best (lowest penalty) mask
|
||||
uint8_t mask = 0;
|
||||
int32_t minPenalty = INT32_MAX;
|
||||
for (uint8_t i = 0; i < 8; i++) {
|
||||
drawFormatBits(&modulesGrid, &isFunctionGrid, eccFormatBits, i);
|
||||
applyMask(&modulesGrid, &isFunctionGrid, i);
|
||||
int penalty = getPenaltyScore(&modulesGrid);
|
||||
if (penalty < minPenalty) {
|
||||
mask = i;
|
||||
minPenalty = penalty;
|
||||
}
|
||||
applyMask(&modulesGrid, &isFunctionGrid, i); // Undoes the mask due to XOR
|
||||
}
|
||||
|
||||
qrcode->mask = mask;
|
||||
|
||||
// Overwrite old format bits
|
||||
drawFormatBits(&modulesGrid, &isFunctionGrid, eccFormatBits, mask);
|
||||
|
||||
// Apply the final choice of mask
|
||||
applyMask(&modulesGrid, &isFunctionGrid, mask);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int8_t qrcode_initText(QRCode *qrcode, uint8_t *modules, uint8_t version, uint8_t ecc, const char *data) {
|
||||
return qrcode_initBytes(qrcode, modules, version, ecc, (uint8_t*)data, strlen(data));
|
||||
}
|
||||
|
||||
bool qrcode_getModule(QRCode *qrcode, uint8_t x, uint8_t y) {
|
||||
if (x < 0 || x >= qrcode->size || y < 0 || y >= qrcode->size) {
|
||||
return false;
|
||||
}
|
||||
|
||||
uint32_t offset = y * qrcode->size + x;
|
||||
return (qrcode->modules[offset >> 3] & (1 << (7 - (offset & 0x07)))) != 0;
|
||||
}
|
||||
|
||||
/*
|
||||
uint8_t qrcode_getHexLength(QRCode *qrcode) {
|
||||
return ((qrcode->size * qrcode->size) + 7) / 4;
|
||||
}
|
||||
|
||||
void qrcode_getHex(QRCode *qrcode, char *result) {
|
||||
|
||||
}
|
||||
*/
|
@ -1,99 +0,0 @@
|
||||
/**
|
||||
* The MIT License (MIT)
|
||||
*
|
||||
* This library is written and maintained by Richard Moore.
|
||||
* Major parts were derived from Project Nayuki's library.
|
||||
*
|
||||
* Copyright (c) 2017 Richard Moore (https://github.com/ricmoo/QRCode)
|
||||
* Copyright (c) 2017 Project Nayuki (https://www.nayuki.io/page/qr-code-generator-library)
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Special thanks to Nayuki (https://www.nayuki.io/) from which this library was
|
||||
* heavily inspired and compared against.
|
||||
*
|
||||
* See: https://github.com/nayuki/QR-Code-generator/tree/master/cpp
|
||||
*/
|
||||
|
||||
|
||||
#ifndef __QRCODE_H_
|
||||
#define __QRCODE_H_
|
||||
|
||||
#ifndef __cplusplus
|
||||
typedef unsigned char bool;
|
||||
static const bool false = 0;
|
||||
static const bool true = 1;
|
||||
#endif
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
|
||||
// QR Code Format Encoding
|
||||
#define MODE_NUMERIC 0
|
||||
#define MODE_ALPHANUMERIC 1
|
||||
#define MODE_BYTE 2
|
||||
|
||||
|
||||
// Error Correction Code Levels
|
||||
#define ECC_LOW 0
|
||||
#define ECC_MEDIUM 1
|
||||
#define ECC_QUARTILE 2
|
||||
#define ECC_HIGH 3
|
||||
|
||||
|
||||
// If set to non-zero, this library can ONLY produce QR codes at that version
|
||||
// This saves a lot of dynamic memory, as the codeword tables are skipped
|
||||
#ifndef LOCK_VERSION
|
||||
#define LOCK_VERSION 0
|
||||
#endif
|
||||
|
||||
|
||||
typedef struct QRCode {
|
||||
uint8_t version;
|
||||
uint8_t size;
|
||||
uint8_t ecc;
|
||||
uint8_t mode;
|
||||
uint8_t mask;
|
||||
uint8_t *modules;
|
||||
} QRCode;
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C"{
|
||||
#endif /* __cplusplus */
|
||||
|
||||
|
||||
|
||||
uint16_t qrcode_getBufferSize(uint8_t version);
|
||||
|
||||
int8_t qrcode_initText(QRCode *qrcode, uint8_t *modules, uint8_t version, uint8_t ecc, const char *data);
|
||||
int8_t qrcode_initBytes(QRCode *qrcode, uint8_t *modules, uint8_t version, uint8_t ecc, uint8_t *data, uint16_t length);
|
||||
|
||||
bool qrcode_getModule(QRCode *qrcode, uint8_t x, uint8_t y);
|
||||
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif /* __cplusplus */
|
||||
|
||||
|
||||
#endif /* __QRCODE_H_ */
|
5
src/Libraries/pubsubclient-2.8/.gitignore
vendored
Normal file
5
src/Libraries/pubsubclient-2.8/.gitignore
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
tests/bin
|
||||
.pioenvs
|
||||
.piolibdeps
|
||||
.clang_complete
|
||||
.gcc-flags.json
|
7
src/Libraries/pubsubclient-2.8/.travis.yml
Normal file
7
src/Libraries/pubsubclient-2.8/.travis.yml
Normal file
@ -0,0 +1,7 @@
|
||||
sudo: false
|
||||
language: cpp
|
||||
compiler:
|
||||
- g++
|
||||
script: cd tests && make && make test
|
||||
os:
|
||||
- linux
|
85
src/Libraries/pubsubclient-2.8/CHANGES.txt
Normal file
85
src/Libraries/pubsubclient-2.8/CHANGES.txt
Normal 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
|
20
src/Libraries/pubsubclient-2.8/LICENSE.txt
Normal file
20
src/Libraries/pubsubclient-2.8/LICENSE.txt
Normal 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.
|
50
src/Libraries/pubsubclient-2.8/README.md
Normal file
50
src/Libraries/pubsubclient-2.8/README.md
Normal 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.
|
@ -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();
|
||||
}
|
@ -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();
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
@ -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();
|
||||
}
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
@ -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();
|
||||
}
|
36
src/Libraries/pubsubclient-2.8/keywords.txt
Normal file
36
src/Libraries/pubsubclient-2.8/keywords.txt
Normal 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)
|
||||
#######################################
|
18
src/Libraries/pubsubclient-2.8/library.json
Normal file
18
src/Libraries/pubsubclient-2.8/library.json
Normal 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"
|
||||
]
|
||||
}
|
9
src/Libraries/pubsubclient-2.8/library.properties
Normal file
9
src/Libraries/pubsubclient-2.8/library.properties
Normal 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=*
|
769
src/Libraries/pubsubclient-2.8/src/PubSubClient.cpp
Normal file
769
src/Libraries/pubsubclient-2.8/src/PubSubClient.cpp
Normal 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;
|
||||
}
|
184
src/Libraries/pubsubclient-2.8/src/PubSubClient.h
Normal file
184
src/Libraries/pubsubclient-2.8/src/PubSubClient.h
Normal 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
|
@ -235,7 +235,8 @@ const BoardDef bsps[_BOARD_MAX] = {
|
||||
.name = "ONE_INDOOR",
|
||||
},
|
||||
/** OPEN_AIR_OUTDOOR */
|
||||
[OPEN_AIR_OUTDOOR] = {
|
||||
[OPEN_AIR_OUTDOOR] =
|
||||
{
|
||||
.SenseAirS8 =
|
||||
{
|
||||
.uart_tx_pin = 1,
|
||||
@ -320,7 +321,82 @@ const BoardDef bsps[_BOARD_MAX] = {
|
||||
#endif
|
||||
},
|
||||
.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
|
||||
|
@ -21,6 +21,7 @@ enum BoardType {
|
||||
DIY_PRO_INDOOR_V4_2 = 0x01,
|
||||
ONE_INDOOR = 0x02,
|
||||
OPEN_AIR_OUTDOOR = 0x03,
|
||||
DIY_PRO_INDOOR_V3_3 = 0x04,
|
||||
_BOARD_MAX
|
||||
};
|
||||
|
||||
|
@ -62,13 +62,13 @@ void LedBar::setColor(uint8_t red, uint8_t green, uint8_t blue, int ledNum) {
|
||||
/**
|
||||
* @brief Set LED brightness apply for all LED bar
|
||||
*
|
||||
* @param brightness Brightness (0 - 255)
|
||||
* @param brightness Brightness (0 - 100)%
|
||||
*/
|
||||
void LedBar::setBrighness(uint8_t brightness) {
|
||||
void LedBar::setBrightness(uint8_t brightness) {
|
||||
if (this->isBegin() == false) {
|
||||
return;
|
||||
}
|
||||
pixel()->setBrightness(brightness);
|
||||
pixel()->setBrightness((brightness * 0xff) / 100);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -116,6 +116,9 @@ void LedBar::setColor(uint8_t red, uint8_t green, uint8_t blue) {
|
||||
*/
|
||||
void LedBar::show(void) {
|
||||
// Ignore update the LED if LED bar disabled
|
||||
if(this->isBegin() == false) {
|
||||
return;
|
||||
}
|
||||
if (enabled == false) {
|
||||
return;
|
||||
}
|
||||
|
@ -19,7 +19,7 @@ public:
|
||||
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);
|
||||
void setBrighness(uint8_t brightness);
|
||||
void setBrightness(uint8_t brightness);
|
||||
int getNumberOfLeds(void);
|
||||
void show(void);
|
||||
void clear(void);
|
||||
|
89
src/Main/utils.cpp
Normal file
89
src/Main/utils.cpp
Normal 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
30
src/Main/utils.h
Normal 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_ */
|
@ -1,11 +1,19 @@
|
||||
#ifdef ESP32
|
||||
|
||||
#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,
|
||||
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() {}
|
||||
|
||||
@ -22,6 +30,7 @@ bool MqttClient::begin(String uri) {
|
||||
this->uri = uri;
|
||||
logInfo("Init uri: " + uri);
|
||||
|
||||
#ifdef ESP32
|
||||
/** config esp_mqtt client */
|
||||
esp_mqtt_client_config_t config = {
|
||||
.uri = this->uri.c_str(),
|
||||
@ -45,6 +54,108 @@ bool MqttClient::begin(String uri) {
|
||||
logError("Client start failed");
|
||||
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;
|
||||
connectionFailedCount = 0;
|
||||
@ -56,12 +167,16 @@ void MqttClient::end(void) {
|
||||
logWarning("Already end, call 'begin' and try again");
|
||||
return;
|
||||
}
|
||||
|
||||
#ifdef ESP32
|
||||
esp_mqtt_client_disconnect(client);
|
||||
esp_mqtt_client_stop(client);
|
||||
esp_mqtt_client_destroy(client);
|
||||
client = NULL;
|
||||
#else
|
||||
CLIENT()->disconnect();
|
||||
#endif
|
||||
isBegin = false;
|
||||
this->uri = "";
|
||||
|
||||
logInfo("end");
|
||||
}
|
||||
@ -86,10 +201,17 @@ bool MqttClient::publish(const char *topic, const char *payload, int len) {
|
||||
return false;
|
||||
}
|
||||
|
||||
#ifdef ESP32
|
||||
if (esp_mqtt_client_publish(client, topic, payload, len, 0, 0) == ESP_OK) {
|
||||
logInfo("Publish success");
|
||||
return true;
|
||||
}
|
||||
#else
|
||||
if (CLIENT()->publish(topic, payload)) {
|
||||
logInfo("Publish success");
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
logError("Publish failed");
|
||||
return false;
|
||||
}
|
||||
@ -114,7 +236,9 @@ bool MqttClient::isCurrentUri(String &uri) {
|
||||
* @return true Connected
|
||||
* @return false Disconnected
|
||||
*/
|
||||
bool MqttClient::isConnected(void) { return connected; }
|
||||
bool MqttClient::isConnected(void) {
|
||||
return connected;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get number of connection failed
|
||||
@ -123,6 +247,35 @@ bool MqttClient::isConnected(void) { return connected; }
|
||||
*/
|
||||
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,
|
||||
int32_t event_id, void *event_data) {
|
||||
MqttClient *mqtt = (MqttClient *)handler_args;
|
||||
@ -164,5 +317,4 @@ static void __mqtt_event_handler(void *handler_args, esp_event_base_t base,
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
#endif /** ESP32 */
|
||||
#endif
|
||||
|
@ -2,8 +2,10 @@
|
||||
#define _AG_MQTT_CLIENT_H_
|
||||
|
||||
#ifdef ESP32
|
||||
|
||||
#include "mqtt_client.h"
|
||||
#else
|
||||
#include <WiFiClient.h>
|
||||
#endif /** ESP32 */
|
||||
#include "Main/PrintLog.h"
|
||||
#include <Arduino.h>
|
||||
|
||||
@ -11,7 +13,16 @@ class MqttClient: public PrintLog {
|
||||
private:
|
||||
bool isBegin = false;
|
||||
String uri;
|
||||
#ifdef ESP32
|
||||
esp_mqtt_client_handle_t client;
|
||||
#else
|
||||
WiFiClient __wifiClient;
|
||||
void* client;
|
||||
String password;
|
||||
String user;
|
||||
String server;
|
||||
uint16_t port;
|
||||
#endif
|
||||
bool connected = false;
|
||||
int connectionFailedCount = 0;
|
||||
|
||||
@ -26,8 +37,10 @@ public:
|
||||
bool isCurrentUri(String &uri);
|
||||
bool isConnected(void);
|
||||
int getConnectionFailedCount(void);
|
||||
#ifdef ESP8266
|
||||
bool connect(String id);
|
||||
void handle(void);
|
||||
#endif
|
||||
};
|
||||
|
||||
#endif /** ESP32 */
|
||||
|
||||
#endif /** _AG_MQTT_CLIENT_H_ */
|
||||
|
120
src/PMS/PMS.cpp
120
src/PMS/PMS.cpp
@ -12,6 +12,7 @@ bool PMSBase::begin(Stream *stream) {
|
||||
this->stream = stream;
|
||||
|
||||
failed = true;
|
||||
failCount = 0;
|
||||
lastRead = 0; // To read buffer on handle without wait after 1.5sec
|
||||
|
||||
this->stream->flush();
|
||||
@ -86,7 +87,7 @@ void PMSBase::handle() {
|
||||
case 2: {
|
||||
buf[bufIndex++] = value;
|
||||
if (bufIndex >= 4) {
|
||||
len = toValue(&buf[2]);
|
||||
len = toI16(&buf[2]);
|
||||
if (len != 28) {
|
||||
// Serial.printf("Got good bad len %d\r\n", len);
|
||||
len += 4;
|
||||
@ -147,103 +148,138 @@ void PMSBase::handle() {
|
||||
*/
|
||||
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
|
||||
*
|
||||
* @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
|
||||
*
|
||||
* @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
|
||||
*
|
||||
* @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
|
||||
*
|
||||
* @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
|
||||
*
|
||||
* @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
|
||||
*
|
||||
* @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
|
||||
*
|
||||
* @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
|
||||
*
|
||||
* @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
|
||||
*
|
||||
* @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
|
||||
*
|
||||
* @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)
|
||||
*
|
||||
* @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)
|
||||
*
|
||||
* @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)
|
||||
*
|
||||
* @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)
|
||||
*
|
||||
* @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
|
||||
@ -270,13 +306,61 @@ int PMSBase::pm25ToAQI(int pm02) {
|
||||
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
|
||||
*
|
||||
* @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
|
||||
@ -290,7 +374,7 @@ bool PMSBase::validate(char *buf) {
|
||||
for (int i = 0; i < 30; i++) {
|
||||
sum += buf[i];
|
||||
}
|
||||
if (sum == toValue(&buf[30])) {
|
||||
if (sum == toU16(&buf[30])) {
|
||||
for (int i = 0; i < 32; i++) {
|
||||
package[i] = buf[i];
|
||||
}
|
||||
|
@ -3,11 +3,17 @@
|
||||
|
||||
#include <Arduino.h>
|
||||
|
||||
#define PMS_FAIL_COUNT_SET_INVALID 3
|
||||
|
||||
class PMSBase {
|
||||
public:
|
||||
bool begin(Stream *stream);
|
||||
void handle();
|
||||
bool isFailed(void);
|
||||
void updateFailCount(void);
|
||||
void resetFailCount(void);
|
||||
int getFailCount(void);
|
||||
int getFailCountMax(void);
|
||||
uint16_t getRaw0_1(void);
|
||||
uint16_t getRaw2_5(void);
|
||||
uint16_t getRaw10(void);
|
||||
@ -24,10 +30,13 @@ public:
|
||||
uint16_t getCount10(void);
|
||||
|
||||
/** For PMS5003T*/
|
||||
uint16_t getTemp(void);
|
||||
int16_t getTemp(void);
|
||||
uint16_t getHum(void);
|
||||
uint8_t getFirmwareVersion(void);
|
||||
uint8_t getErrorCode(void);
|
||||
|
||||
int pm25ToAQI(int pm02);
|
||||
int compensate(int pm25, float humidity);
|
||||
|
||||
private:
|
||||
Stream *stream;
|
||||
@ -35,8 +44,11 @@ private:
|
||||
int packageIndex;
|
||||
bool failed = false;
|
||||
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);
|
||||
};
|
||||
|
||||
|
@ -1,5 +1,6 @@
|
||||
#include "PMS5003.h"
|
||||
#include "Arduino.h"
|
||||
#include "../Main/utils.h"
|
||||
|
||||
#if defined(ESP8266)
|
||||
#include <SoftwareSerial.h>
|
||||
@ -77,7 +78,7 @@ bool PMS5003::begin(void) {
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
_ver = pms.getFirmwareVersion();
|
||||
this->_isBegin = true;
|
||||
return true;
|
||||
}
|
||||
@ -108,7 +109,9 @@ int PMS5003::getPm10Ae(void) { return pms.getPM10(); }
|
||||
*
|
||||
* @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
|
||||
@ -118,6 +121,33 @@ int PMS5003::getPm03ParticleCount(void) { return pms.getCount0_3(); }
|
||||
*/
|
||||
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
|
||||
*
|
||||
@ -161,3 +191,25 @@ void PMS5003::handle(void) { pms.handle(); }
|
||||
* @return false Communication timeout or sensor has removed
|
||||
*/
|
||||
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(); }
|
||||
|
@ -19,14 +19,22 @@ public:
|
||||
void end(void);
|
||||
void handle(void);
|
||||
bool isFailed(void);
|
||||
void updateFailCount(void);
|
||||
void resetFailCount(void);
|
||||
int getFailCount(void);
|
||||
int getFailCountMax(void);
|
||||
int getPm01Ae(void);
|
||||
int getPm25Ae(void);
|
||||
int getPm10Ae(void);
|
||||
int getPm03ParticleCount(void);
|
||||
int convertPm25ToUsAqi(int pm25);
|
||||
int compensate(int pm25, float humidity);
|
||||
int getFirmwareVersion(void);
|
||||
uint8_t getErrorCode(void);
|
||||
|
||||
private:
|
||||
bool _isBegin = false;
|
||||
int _ver;
|
||||
BoardType _boardDef;
|
||||
PMSBase pms;
|
||||
const BoardDef *bsp;
|
||||
|
@ -1,5 +1,6 @@
|
||||
#include "PMS5003T.h"
|
||||
#include "Arduino.h"
|
||||
#include "../Main/utils.h"
|
||||
|
||||
#if defined(ESP8266)
|
||||
#include <SoftwareSerial.h>
|
||||
@ -102,7 +103,7 @@ bool PMS5003T::begin(void) {
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
_ver = pms.getFirmwareVersion();
|
||||
this->_isBegin = true;
|
||||
return true;
|
||||
}
|
||||
@ -133,7 +134,9 @@ int PMS5003T::getPm10Ae(void) { return pms.getPM10(); }
|
||||
*
|
||||
* @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
|
||||
@ -149,7 +152,7 @@ int PMS5003T::convertPm25ToUsAqi(int pm25) { return pms.pm25ToAQI(pm25); }
|
||||
* @return float Degree Celcius
|
||||
*/
|
||||
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 (%)
|
||||
*/
|
||||
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
|
||||
*
|
||||
@ -202,3 +232,24 @@ void PMS5003T::handle(void) { pms.handle(); }
|
||||
*/
|
||||
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(); }
|
||||
|
@ -22,6 +22,10 @@ public:
|
||||
|
||||
void handle(void);
|
||||
bool isFailed(void);
|
||||
void updateFailCount(void);
|
||||
void resetFailCount(void);
|
||||
int getFailCount(void);
|
||||
int getFailCountMax(void);
|
||||
int getPm01Ae(void);
|
||||
int getPm25Ae(void);
|
||||
int getPm10Ae(void);
|
||||
@ -29,10 +33,14 @@ public:
|
||||
int convertPm25ToUsAqi(int pm25);
|
||||
float getTemperature(void);
|
||||
float getRelativeHumidity(void);
|
||||
int compensate(int pm25, float humidity);
|
||||
int getFirmwareVersion(void);
|
||||
uint8_t getErrorCode(void);
|
||||
|
||||
private:
|
||||
bool _isBegin = false;
|
||||
bool _isSleep = false;
|
||||
int _ver; /** Firmware version code */
|
||||
|
||||
BoardType _boardDef;
|
||||
const BoardDef *bsp;
|
||||
|
@ -4,14 +4,30 @@ 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) {
|
||||
return temp * 1.327f - 6.738f;
|
||||
}
|
||||
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;
|
||||
|
||||
if (hum > 100.0f) {
|
||||
|
@ -8,8 +8,8 @@ private:
|
||||
public:
|
||||
PMS5003TBase();
|
||||
~PMS5003TBase();
|
||||
float temperatureCompensated(float temp);
|
||||
float humidityCompensated(float hum);
|
||||
float compensateTemp(float temp);
|
||||
float compensateHum(float hum);
|
||||
};
|
||||
|
||||
#endif
|
||||
|
@ -1,5 +1,6 @@
|
||||
#include "S8.h"
|
||||
#include "mb_crc.h"
|
||||
#include "../Main/utils.h"
|
||||
#if defined(ESP8266)
|
||||
#include <SoftwareSerial.h>
|
||||
#else
|
||||
@ -103,7 +104,7 @@ void S8::getFirmwareVersion(char firmver[]) {
|
||||
*/
|
||||
int32_t S8::getSensorTypeId(void) {
|
||||
if (this->isBegin() == false) {
|
||||
return -1;
|
||||
return utils::getInvalidCO2();
|
||||
}
|
||||
|
||||
int32_t sensorType = 0;
|
||||
|
@ -2,6 +2,7 @@
|
||||
#include "../Libraries/SensirionSGP41/src/SensirionI2CSgp41.h"
|
||||
#include "../Libraries/Sensirion_Gas_Index_Algorithm/src/NOxGasIndexAlgorithm.h"
|
||||
#include "../Libraries/Sensirion_Gas_Index_Algorithm/src/VOCGasIndexAlgorithm.h"
|
||||
#include "../Main/utils.h"
|
||||
|
||||
#define sgpSensor() ((SensirionI2CSgp41 *)(this->_sensor))
|
||||
#define vocAlgorithm() ((VOCGasIndexAlgorithm *)(this->_vocAlgorithm))
|
||||
@ -66,6 +67,7 @@ bool Sgp41::begin(TwoWire &wire) {
|
||||
}
|
||||
|
||||
onConditioning = true;
|
||||
_handleFailCount = 0;
|
||||
#ifdef ESP32
|
||||
/** Create task */
|
||||
xTaskCreate(
|
||||
@ -104,9 +106,25 @@ void Sgp41::handle(void) {
|
||||
} else {
|
||||
uint16_t srawVoc, srawNox;
|
||||
if (getRawSignal(srawVoc, srawNox)) {
|
||||
tvocRaw = srawVoc;
|
||||
noxRaw = srawNox;
|
||||
nox = noxAlgorithm()->process(srawNox);
|
||||
tvoc = vocAlgorithm()->process(srawVoc);
|
||||
|
||||
_handleFailCount = 0;
|
||||
// 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;
|
||||
nox = noxAlgorithm()->process(srawNox);
|
||||
tvoc = vocAlgorithm()->process(srawVoc);
|
||||
|
||||
_handleFailCount = 0;
|
||||
// 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) {
|
||||
if (onConditioning) {
|
||||
return -1;
|
||||
return utils::getInvalidVOC();
|
||||
}
|
||||
return tvoc;
|
||||
}
|
||||
@ -186,7 +218,7 @@ int Sgp41::getTvocIndex(void) {
|
||||
*/
|
||||
int Sgp41::getNoxIndex(void) {
|
||||
if (onConditioning) {
|
||||
return -1;
|
||||
return utils::getInvalidNOx();
|
||||
}
|
||||
return nox;
|
||||
}
|
||||
|
@ -35,6 +35,7 @@ private:
|
||||
bool onConditioning = true;
|
||||
bool ready = false;
|
||||
bool _isBegin = false;
|
||||
uint8_t _handleFailCount = 0;
|
||||
void *_sensor;
|
||||
void *_vocAlgorithm;
|
||||
void *_noxAlgorithm;
|
||||
|
@ -1,6 +1,7 @@
|
||||
#include "Sht.h"
|
||||
|
||||
#include "../Libraries/arduino-sht/SHTSensor.h"
|
||||
#include "../Main/utils.h"
|
||||
|
||||
/** Cast _sensor to SHTSensor */
|
||||
#define shtSensor() ((SHTSensor *)(this->_sensor))
|
||||
@ -131,14 +132,18 @@ void Sht::end(void) {
|
||||
*
|
||||
* @return float
|
||||
*/
|
||||
float Sht::getTemperature(void) { return shtSensor()->getTemperature(); }
|
||||
float Sht::getTemperature(void) {
|
||||
return shtSensor()->getTemperature();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get humidity
|
||||
*
|
||||
* @return float
|
||||
*/
|
||||
float Sht::getRelativeHumidity(void) { return shtSensor()->getHumidity(); }
|
||||
float Sht::getRelativeHumidity(void) {
|
||||
return shtSensor()->getHumidity();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Measure temperature and humidity
|
||||
|
Reference in New Issue
Block a user