Compare commits

...

147 Commits
3.1.0 ... 3.1.3

Author SHA1 Message Date
8a83e408d3 Merge pull request #173 from airgradienthq/develop
Next version 3.1.3
2024-06-06 13:17:08 +07:00
7a1a0337d1 Merge pull request #163 from joostlek/typo
Fix typos in docs
2024-06-06 13:13:00 +07:00
6bdd4cb02f Merge pull request #172 from airgradienthq/hotfix/display-show-rebooting-but-not-rebooting
[Fix] Reboot device after WiFi Configure porttal timeout
2024-06-06 13:11:33 +07:00
38bd758b69 Merge pull request #171 from airgradienthq/feature/update-display-message
Update the message show on display
2024-06-06 13:11:19 +07:00
6aa19ea3e6 Set next version 2024-06-06 08:44:12 +07:00
da323b1a46 fix reboot device after WiFi portal configure timeout. 2024-06-05 19:01:25 +07:00
2ae90444bb Fix reboot device after WiFi Connector perform but not connected 2024-06-05 14:31:34 +07:00
21e802da33 Merge pull request #170 from airgradienthq/feature/regularly-call-ota-handler
OTA update each 60 min
2024-06-05 13:38:05 +07:00
e0869fbaf0 Merge pull request #167 from airgradienthq/hotfix/too-many-get-config-request
[Fix] Too many server GET config request
2024-06-05 13:24:59 +07:00
70662091ec Merge pull request #166 from airgradienthq/hotfix/wifi-not-reset-in-offline-mode
[Fix] WiFi not reset on offline mode
2024-06-05 13:24:44 +07:00
960d2bad64 Merge pull request #169 from airgradienthq/feature/add-boott-support-ha
Add bootCount to support on HA
2024-06-05 13:22:19 +07:00
7abf7f5e6a Merge pull request #162 from joostlek/update-put-table
Update configuration parameters
2024-06-05 10:34:03 +07:00
6bad4fd04b Merge pull request #164 from joostlek/json
Reformat JSON in docs
2024-06-05 10:30:56 +07:00
a17f18b3db Merge pull request #165 from joostlek/table
Reformat table
2024-06-05 10:29:59 +07:00
3da4900462 Update show mesage on display 2024-06-05 09:58:08 +07:00
d2d81f6b4b OTA update each 60 min 2024-06-05 09:29:30 +07:00
0b12f56513 add bootCount to measure report 2024-06-04 22:17:20 +07:00
f7e811e34b Update server GET configure period to 60 sec 2024-06-04 18:30:33 +07:00
037bb37184 Change macro name SERVER_CONFIG_UPDATE_INTERVAL' to SERVER_CONFIG_SYNC_INTERVAL` 2024-06-04 18:29:35 +07:00
fde510ba96 Merge pull request #137 from airgradienthq/hotfix/correct-ota-update-message-on-display
Correct OTA process message show on display
2024-06-04 18:15:01 +07:00
14b152a2a7 Merge branch 'develop' into hotfix/correct-ota-update-message-on-display 2024-06-04 18:09:04 +07:00
ef6b041529 Fix: ota not callback on first powerup perform 2024-06-04 18:01:55 +07:00
11ecea1493 Change showFirmwareUpdateSuccess input fromString to int 2024-06-04 18:01:28 +07:00
3e2e8b15eb Fix: WiFi configuration not reset in offline mode. 2024-06-04 15:51:01 +07:00
4249a86fd5 Reformat table 2024-06-04 09:21:29 +02:00
4a58b0b1c7 Reformat JSON in docs 2024-06-04 09:20:18 +02:00
5f93329e96 Fix typos in docs 2024-06-04 09:17:33 +02:00
59d189e1d3 Update configuration parameters 2024-06-04 09:14:30 +02:00
2e62abe2d7 Merge pull request #156 from airgradienthq/develop
Develop
2024-05-29 18:02:27 +07:00
7569b114bf Merge pull request #155 from airgradienthq/feature/update-led-bar-button-test
Handle LED bar test button before init sensor initialize
2024-05-29 16:36:08 +07:00
3c1d0a862f update comment 2024-05-29 16:34:00 +07:00
4d883af77e Merge pull request #138 from airgradienthq/hotfix/ledbar-flickers
Fix LED bar flickers
2024-05-29 08:01:05 +07:00
e9224a5de0 Remove test logging message 2024-05-29 07:58:49 +07:00
af0fbadd80 Handle LED bar button test before init sensor. 2024-05-29 07:55:27 +07:00
79f6c040c7 Merge pull request #143 from airgradienthq/feature/reboot-after-wifi-connect-timeout
Reboot After 180s WiFi Manager not Connect.
2024-05-28 13:43:08 +07:00
f262866148 Merge pull request #154 from airgradienthq/hotfix/display-co2-calibration-message
Update CO2 calibration message show on display
2024-05-28 13:42:14 +07:00
78a2a78020 Update CO2 calibration message show on display 2024-05-25 20:25:50 +07:00
65e759fb90 Merge branch 'develop' into hotfix/correct-ota-update-message-on-display 2024-05-24 13:30:09 +07:00
3fc7e4b55e Merge pull request #149 from airgradienthq/hotfix/reporting-fw-version-on-dashboard
Official OTA 3.1.1 version not reporting FW version on dashboard
2024-05-24 09:30:13 +07:00
5857388c2d Merge pull request #152 from airgradienthq/hotfix/remove-configuration-missleading-message
Remove `Update ignored due to local unofficial changes`
2024-05-24 09:01:04 +07:00
5770b41fd4 remove Update ignored due to local unofficial changes 2024-05-24 08:45:50 +07:00
d85d890878 Change OTA update period from 2h to 1h 2024-05-24 08:26:24 +07:00
9fbd31d0c8 Merge pull request #150 from airgradienthq/hotfix/show-message-monitor-not-setup-on-dashboard
disp.setText("Monitor not", "setup on", "dashboard") called when monitor is actually already on dashboard
2024-05-24 08:23:52 +07:00
c5b7c43293 disp.setText("Monitor not", "setup on", "dashboard") called when monitor is actually already on dashboard 2024-05-22 12:13:57 +07:00
7550ef7b0c change OTA update period each 2h. 2024-05-22 11:45:56 +07:00
805546b78e check firmware update after powerup 2024-05-22 11:34:42 +07:00
59880f4be5 fix typo 2024-05-22 11:17:11 +07:00
ee7837a471 Merge pull request #148 from MallocArray/patch-1
Correct API value for boot
2024-05-20 08:50:57 +07:00
ebbf0adf2f Correct API value for boot 2024-05-18 21:19:40 -05:00
d9551dc560 Reboot After 180s WiFi Manager not Connect. 2024-05-18 08:47:49 +07:00
6ea0ab9272 Set process 100% after received firmware file. 2024-05-17 20:18:59 +07:00
6e54409512 rename function 2024-05-17 20:17:49 +07:00
f35bc4feaa Fix LED bar flickers 2024-05-17 11:52:22 +07:00
c04ab90fd2 Update OTA processing message. Fix always retry of bootCount = 0 2024-05-17 10:32:20 +07:00
ed02f66ca2 Correct OTA update process show message on display 2024-05-16 21:12:02 +07:00
4b94926651 Merge pull request #136 from airgradienthq/develop
merge to master to prepare for release
2024-05-16 16:36:17 +07:00
f505b39247 Merge pull request #135 from airgradienthq/hotfix/pm25-appear-colors
Update PM2.5 LED bar color and level
2024-05-16 13:09:58 +07:00
9e44cd89d9 Update PM2.5 LED bar color and level 2024-05-16 12:50:38 +07:00
e7b95c0bde Merge pull request #126 from airgradienthq/OTA-dev
added doc regarding the OTA mechanism
2024-05-16 11:55:57 +07:00
b72394b004 Merge pull request #127 from airgradienthq/hotfix/purple-color
Hotfix/purple color
2024-05-16 11:55:31 +07:00
1544989fe6 Merge pull request #128 from airgradienthq/hotfix/change-ota-update-period
Change the OTA update period use timestamp to bootCount
2024-05-16 11:54:56 +07:00
d7b5e999c1 Merge pull request #129 from airgradienthq/hotfix/turn-led-off-on-offlinemode
Turn LED indicate wifi failed, cloud failed and get cloud configuration failed to off
2024-05-16 11:54:00 +07:00
9b0210f7b5 Merge pull request #130 from airgradienthq/hotfilx/print-log-wrong-number-format
Fix print number value wrong format
2024-05-16 11:53:16 +07:00
3715266f8c Merge pull request #131 from airgradienthq/hotfix/update-local-configure-repsonse-fail-message
Update local configuration response failed message
2024-05-16 11:52:57 +07:00
a4eb607174 Merge pull request #132 from airgradienthq/hotfix/update-configuration-log-message
Update configuration log message
2024-05-16 11:52:21 +07:00
f55fa6a617 Update configuration log mesasge 2024-05-15 17:20:10 +07:00
4f9f800cce Update local configuration response failed message 2024-05-15 17:15:36 +07:00
cad5d1f0e7 fix print number value wrong format 2024-05-15 17:13:39 +07:00
55ede2b04d turn led indicate wifi failed, cloud fail and get cloud configuration fail to off. 2024-05-15 17:11:26 +07:00
563bdfe4b2 Change the OTA update period use timestamp to bootCount 2024-05-15 17:09:06 +07:00
e2154af85f Merge commit '9a2bbd7a57a7dd4a6eaa27b85576c20a85d7ca09' into hotfix/purple-color 2024-05-15 16:10:08 +07:00
9a2bbd7a57 fix: Purple color 2024-05-15 14:02:49 +07:00
a8c8246632 added doc regarding the OTA mechanism 2024-05-15 11:05:37 +07:00
a71c038864 Fix camelCase, #122 2024-05-13 21:28:37 +07:00
799217e724 Remove displayMode from configuration, #123 2024-05-13 21:25:00 +07:00
b3f02f0a58 Merge pull request #125 from airgradienthq/hotfix/openair-sync-miss-data
Fix: Firmware mode `O-1PP` send miss data channel-2
2024-05-13 20:44:26 +07:00
c640cf773e Merge pull request #124 from airgradienthq/hotfix/configuration-handle
Configuration failed response message and failed condition handle.
2024-05-13 20:39:45 +07:00
c145666fcb Merge pull request #121 from airgradienthq/hotfix/led-bar-power-up-test-wifi-connection-still-perform
Fix issue: LED bar test presssed but WiFi connection still perform
2024-05-13 20:39:26 +07:00
466bb0eb21 Merge pull request #120 from airgradienthq/hotfix/not-show-the-message-add-to-dashboard
Fix issue: dashboard not show if get cloud configuration failed.
2024-05-13 20:39:11 +07:00
4612f4b793 Add condition to handle configuration changed. 2024-05-13 19:00:34 +07:00
45c7279866 Fix: Firmware mode O-1PP send miss data channel-2 2024-05-13 18:25:03 +07:00
5cb838af29 fix: OpenAir send incorrect model(firmware mode) 2024-05-13 18:11:46 +07:00
3201fd8d9c Fix: Configuratoin failed response mesasge and failed condition handle. 2024-05-13 17:43:42 +07:00
f23c7e9e31 Set offline mode incase wifi is not configuraion or configuration ignored. 2024-05-13 15:07:10 +07:00
5b18a8353d Fix issue: LED bar button test pressed but WiFi connection still perform. 2024-05-13 14:43:53 +07:00
1e81c9b125 Fix issue: dashboard not show if get cloud configuration failed. 2024-05-13 13:52:14 +07:00
3dae4cb06d Merge pull request #119 from airgradienthq/hotfix/fix-issue-before-release-new-version
Hotfix: Fix some issue before release new version
2024-05-13 12:26:53 +07:00
b4745ef55d Merge pull request #118 from airgradienthq/hotfix/offline-mode-should-not-show-server-status
Offline mode should not show server status on display
2024-05-13 12:09:59 +07:00
348cb2663a Merge pull request #116 from airgradienthq/hotfix/add-debug-mesage-when-hw-watchdog-reset
Update watchdog reset message
2024-05-13 12:09:45 +07:00
1b69e8a599 Offline mode should not shown status on display. #111 2024-05-13 12:02:17 +07:00
d17ad3cbab Merge branch 'hotfix/factory-reset-offline-mode' into hotfix/offline-mode-should-not-show-server-status 2024-05-13 11:52:01 +07:00
a6d8936ea6 Fix factory reset failed, the configuration not set to default. #112 2024-05-13 11:47:37 +07:00
8b428855b0 Merge branch 'hotfix/add-debug-mesage-when-hw-watchdog-reset' into hotfix/factory-reset-offline-mode 2024-05-13 11:21:01 +07:00
22dc2136e4 Update watchdog reset message 2024-05-13 11:18:08 +07:00
f23f81f575 Merge branch 'hotfix/ledbar-display-should-off-when-brightness-0-percent' into hotfix/fix-issue-before-release-new-version 2024-05-13 10:39:48 +07:00
7a4255b2bb Turn off LED bar and Display if brightness is 0%, fix #114 2024-05-13 10:34:06 +07:00
d844ab09fa Merge pull request #107 from airgradienthq/hotfix/configuration-default-after-ota-success-and-new-firmware-has-change-configuration-param
Move structure configure to JSON
2024-05-12 18:19:39 +07:00
31c60dcbec fix build failed 2024-05-12 11:48:03 +07:00
f0749783fe Merge branch 'develop' into hotfix/configuration-default-after-ota-success-and-new-firmware-has-change-configuration-param 2024-05-12 10:49:13 +07:00
d34605a018 Merge pull request #109 from airgradienthq/feature/adjust-co2-color-and-ranges
Adjust CO2 Colors and Ranges
2024-05-11 20:12:11 +07:00
1bcb9bf5ee Adjust CO2 Colors and Ranges 2024-05-11 19:29:02 +07:00
296bf49e5e Merge pull request #108 from airgradienthq/feature/press-button-for-offline-mode
Display show message for offline/online mode
2024-05-10 09:18:32 +07:00
e3dee42b4b fix esp8266 build fail 2024-05-10 09:16:22 +07:00
279ccb8bfb update configuration filename, log mesage and add configuration default value 2024-05-10 09:11:43 +07:00
a3c9727b02 Update variable a descriptive name 2024-05-10 07:19:11 +07:00
1b886d9843 Merge branch 'hotfix/configuration-default-after-ota-success-and-new-firmware-has-change-configuration-param' into feature/press-button-for-offline-mode 2024-05-09 15:09:00 +07:00
066e81b186 fix esp8266 build fail 2024-05-09 15:03:27 +07:00
c98d078d4c Resolve complex build failed 2024-05-09 14:56:40 +07:00
cb98183e20 Merge branch 'develop' into feature/press-button-for-offline-mode 2024-05-09 14:48:30 +07:00
0e1734b35d Merge branch 'develop' into hotfix/configuration-default-after-ota-success-and-new-firmware-has-change-configuration-param 2024-05-09 14:46:00 +07:00
da6326db0f Add display ask for offline/online mode 2024-05-09 14:32:42 +07:00
ad1da129c0 Merge branch 'hotfix/configuration-default-after-ota-success-and-new-firmware-has-change-configuration-param' into feature/press-button-for-offline-mode 2024-05-09 13:35:46 +07:00
8a90fe511b Merge pull request #104 from airgradienthq/feature/show-ota-process-on-display
Feature/show ota process on display
2024-05-09 06:23:03 +07:00
cca1ab69bb Move rebooting process out of OtaHandler 2024-05-08 12:31:54 +07:00
955172d3d3 Move structure configure to JSON 2024-05-08 12:22:34 +07:00
4500f41ed3 Merge pull request #106 from airgradienthq/feature/wifi-auto-connect-to-airgradient
Code for autoconnect to: airgradient:cleanair
2024-05-07 15:47:22 +07:00
7c2f8e5b9b Add WiFi reset to factory default: connect to SSID airgradient after led bar test and button still keep pressed. 2024-05-07 15:00:32 +07:00
fb84b53077 Merge pull request #102 from airgradienthq/hotfix/led-button-test-not-handle-on-power-up
fix led bar button test not work on power up
2024-05-03 21:03:15 +07:00
3359b1817b Merge pull request #98 from airgradienthq/hotfix/remove-wifi-qrcode
Remove WiFi QrCode
2024-05-03 21:02:16 +07:00
8eb8d4a1ec Remove dependency from StateMachine 2024-05-03 17:27:05 +07:00
d2723de0f8 Remove OtaHandler constructor 2024-05-02 18:22:26 +07:00
4493156739 Implement regular OTA update attempt / indicate OTA processing on display 2024-05-02 10:19:49 +07:00
0acb7d470d Merge remote-tracking branch 'origin/develop' into feature/show-ota-process-on-display 2024-05-02 09:50:28 +07:00
8428442dea Merge branch 'develop' into hotfix/remove-wifi-qrcode 2024-05-02 09:19:14 +07:00
f32d6b1bbe Remove country dependency on LED bar button test 2024-05-02 09:10:43 +07:00
46600f59a3 Merge pull request #103 from airgradienthq/feature/display-and-ledbar-brightness
Feature/display and ledbar brightness
2024-05-02 08:58:45 +07:00
01c42387ed fix: typo 2024-05-01 21:27:01 +07:00
f08438db46 Implement display / ledBar brightness 2024-05-01 21:25:35 +07:00
221730160b fix led bar button test not work on power up 2024-05-01 21:02:57 +07:00
d40b1d37a8 Merge pull request #99 from airgradienthq/hotfix/git-workflow-ota-failed
fix github workflows build fail and platformio multiple project build
2024-05-01 09:54:59 +07:00
ecd3aa988f Merge pull request #101 from airgradienthq/hotfix/EEPROM-data-override-by-after-OTA-perform-success
fix issue EEPROM override after OTA perform by use `spiffs`
2024-05-01 09:54:37 +07:00
095787d1f2 Merge pull request #100 from airgradienthq/hotfix/temperature-unit-supported-shortname-value
Temperature configuration unit support shortname value `c` and `f`
2024-04-30 22:26:53 +07:00
d94d074abe fix issue EEPROM override after OTA perform by use spiffs 2024-04-30 20:51:08 +07:00
cdef9822b3 Merge pull request #97 from airgradienthq/dev/fix-source-code-format
re-format source code
2024-04-30 20:46:14 +07:00
29c1989e78 Temperature configuration unit support shortname value c and f 2024-04-30 20:32:53 +07:00
bc3872e631 fix github workflows build fail and platformio multiple project build 2024-04-30 20:28:34 +07:00
7a182ebb12 Remove WiFi QrCode 2024-04-30 19:59:43 +07:00
5e6f801534 re-format source code 2024-04-29 19:46:13 +07:00
88808a2ad2 Revert "fix: source code formating"
This reverts commit 1580cd51aa.
2024-04-29 19:34:15 +07:00
a4fc954712 Merge remote-tracking branch 'origin/master' into develop 2024-04-29 10:01:10 +07:00
1580cd51aa fix: source code formating 2024-04-29 09:54:37 +07:00
3efba24e24 Merge pull request #96 from danielmoore/prom-pms-data
Report PM data to metrics API for AirGradient ONE
2024-04-29 09:43:45 +07:00
940dd0167c Fix OpenMetrics humidity metric 2024-04-28 22:10:39 -04:00
6b4f86e7e4 Report PM data to metrics API for AirGradient ONE 2024-04-28 21:47:38 -04:00
e5a0c6bc7b Merge pull request #95 from airgradienthq/ota
OTA Update, better handle the 304 and 400 case
2024-04-27 14:14:09 +07:00
b8fdb38db8 Merge branch 'develop' into ota 2024-04-25 16:47:28 +07:00
b2c55c38dc better handle the 304 and 400 case 2024-04-25 16:11:49 +07:00
33 changed files with 1709 additions and 2654 deletions

View File

@ -18,7 +18,7 @@ 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"

View File

@ -17,62 +17,66 @@ 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,
"rhum": 43,
"tvocIndex": 100,
"tvoc_raw": 33051,
"noxIndex": 1,
"nox_raw": 16307,
"boot": 6,
"ledMode": "pm",
"firmwareVersion": "3.0.10beta",
"fwMode": "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, 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 |
#### 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 +86,24 @@ 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 ```
#### 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"} |
| 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"} |
| 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} |

22
docs/ota-updates.md Normal file
View 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

View File

@ -41,7 +41,7 @@ CC BY-SA 4.0 Attribution-ShareAlike 4.0 International License
#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 SERVER_CONFIG_SYNC_INTERVAL 60000 /** ms */
#define SERVER_SYNC_INTERVAL 60000 /** ms */
#define SENSOR_CO2_CALIB_COUNTDOWN_MAX 5 /** sec */
#define SENSOR_TVOC_UPDATE_INTERVAL 1000 /** ms */
@ -82,7 +82,7 @@ bool hasSensorPMS = true;
bool hasSensorSHT = true;
int pmFailCount = 0;
int getCO2FailCount = 0;
AgSchedule configSchedule(SERVER_CONFIG_UPDATE_INTERVAL,
AgSchedule configSchedule(SERVER_CONFIG_SYNC_INTERVAL,
updateServerConfiguration);
AgSchedule serverSchedule(SERVER_SYNC_INTERVAL, sendDataToServer);
AgSchedule dispSchedule(DISP_UPDATE_INTERVAL, dispHandler);
@ -224,7 +224,7 @@ 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",
displayShowText("CO2 calib.", "after",
String(SENSOR_CO2_CALIB_COUNTDOWN_MAX - i) + " sec");
delay(1000);
}
@ -232,16 +232,16 @@ static void executeCo2Calibration(void) {
if (ag.s8.setBaselineCalibration()) {
displayShowText("Calib", "success", "");
delay(1000);
displayShowText("Wait for", "finish", "...");
displayShowText("Wait to", "complete", "...");
int count = 0;
while (ag.s8.isBaseLineCalibrationDone() == false) {
delay(1000);
count++;
}
displayShowText("Finish", "after", String(count) + " sec");
displayShowText("Finished", "after", String(count) + " sec");
delay(DISPLAY_DELAY_SHOW_CONTENT_MS);
} else {
displayShowText("Calib", "failure!!!", "");
displayShowText("Calibration", "failure", "");
delay(DISPLAY_DELAY_SHOW_CONTENT_MS);
}
}

View File

@ -39,7 +39,11 @@ String LocalServer::getHostname(void) {
void LocalServer::_handle(void) { server.handleClient(); }
void LocalServer::_GET_config(void) {
server.send(200, "application/json", config.toString());
if(ag->isOne()) {
server.send(200, "application/json", config.toString());
} else {
server.send(200, "application/json", config.toString(fwMode));
}
}
void LocalServer::_PUT_config(void) {

View File

@ -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
@ -89,10 +91,10 @@ static LocalServer localServer(Serial, openMetrics, measurements, configuration,
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 +114,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 +128,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 +155,6 @@ void setup() {
}
Serial.println("Detected " + ag->getBoardName());
/** Init sensor */
boardInit();
configuration.setAirGradient(ag);
oledDisplay.setAirGradient(ag);
stateMachine.setAirGradient(ag);
@ -158,14 +163,37 @@ void setup() {
openMetrics.setAirGradient(ag);
localServer.setAirGraident(ag);
/** 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 +212,18 @@ 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()) {
stateMachine.displayHandle(
AgStateMachineWiFiOkServerOkSensorConfigFailed);
if (apiClient.isNotAvailableOnDashboard()) {
stateMachine.displayHandle(
AgStateMachineWiFiOkServerOkSensorConfigFailed);
}
}
stateMachine.handleLeds(
AgStateMachineWiFiOkServerOkSensorConfigFailed);
@ -201,15 +232,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 +293,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,6 +307,9 @@ void loop() {
/** check that local configura changed then do some action */
configUpdateHandle();
/** Firmware check for update handle */
checkForUpdateSchedule.run();
}
static void co2Update(void) {
@ -370,9 +417,9 @@ static void factoryConfigReset(void) {
mqttTask = NULL;
}
/** Disconnect WIFI */
wifiConnector.disconnect();
wifiConnector.reset();
/** Reset WIFI */
WiFi.enableSTA(true); // Incase offline mode
WiFi.disconnect(true, true);
/** Reset local config */
configuration.reset();
@ -383,6 +430,7 @@ static void factoryConfigReset(void) {
Serial.println("Factory reset successful");
}
delay(3000);
oledDisplay.setText("","","");
ESP.restart();
}
}
@ -408,13 +456,21 @@ 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()) {
ag->ledBar.setEnable(configuration.getLedBarMode() != LedBarModeOff);
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();
}
}
@ -432,6 +488,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 +637,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 +660,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 +721,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 +868,7 @@ static void configUpdateHandle() {
return;
}
ledBarEnabledUpdate();
stateMachine.executeCo2Calibration();
stateMachine.executeLedBarTest();
String mqttUri = configuration.getMqttBrokerUri();
if (mqttClient.isCurrentUri(mqttUri) == false) {
@ -698,27 +876,63 @@ static void configUpdateHandle() {
initMqtt();
}
if (configuration.noxLearnOffsetChanged() ||
configuration.tvocLearnOffsetChanged()) {
ag->sgp41.end();
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";
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 (oldTvocOffset != configuration.getTvocLearningOffset()) {
Serial.printf("Setting tvocLearningOffset from %d to %d hours %s\r\n",
oldTvocOffset, configuration.getTvocLearningOffset(),
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 (oldNoxOffset != configuration.getNoxLearningOffset()) {
Serial.printf("Setting noxLearningOffset from %d to %d hours %s\r\n",
oldNoxOffset, configuration.getNoxLearningOffset(),
resultStr);
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();
}
appDispHandler();
@ -727,13 +941,15 @@ static void configUpdateHandle() {
static void appLedHandler(void) {
AgStateMachineState state = AgStateMachineNormal;
if (wifiConnector.isConnected() == false) {
state = AgStateMachineWiFiLost;
} else if (apiClient.isFetchConfigureFailed()) {
stateMachine.displaySetAddToDashBoard();
state = AgStateMachineSensorConfigFailed;
} else if (apiClient.isPostToServerFailed()) {
state = AgStateMachineServerLost;
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 +958,17 @@ static void appLedHandler(void) {
static void appDispHandler(void) {
if (ag->isOne()) {
AgStateMachineState state = AgStateMachineNormal;
if (wifiConnector.isConnected() == false) {
state = AgStateMachineWiFiLost;
} else if (apiClient.isFetchConfigureFailed()) {
state = AgStateMachineSensorConfigFailed;
} else if (apiClient.isPostToServerFailed()) {
state = AgStateMachineServerLost;
}
/** Only show display status on online mode. */
if (configuration.isOfflineMode() == false) {
if (wifiConnector.isConnected() == false) {
state = AgStateMachineWiFiLost;
} else if (apiClient.isFetchConfigureFailed()) {
state = AgStateMachineSensorConfigFailed;
} else if (apiClient.isPostToServerFailed()) {
state = AgStateMachineServerLost;
}
}
stateMachine.displayHandle(state);
}
}
@ -822,7 +1041,7 @@ static void updatePm(void) {
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",
Serial.printf("[1] Relative Humidity compensated: %f\r\n",
ag->pms5003t_1.humidityCompensated(measurements.hum_1));
} else {
measurements.pm01_1 = -1;
@ -963,10 +1182,19 @@ static void updatePm(void) {
}
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++;

View File

@ -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;
@ -195,7 +202,7 @@ String OpenMetrics::getPayload(void) {
if (_hum >= 0) {
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));
}

View File

@ -1,128 +1,206 @@
#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) {
void updateFirmwareIfOutdated(String deviceId) {
String url = "http://hw.airgradient.com/sensors/airgradient:" + deviceId +
"/generic/os/firmware.bin";
url += "?current_firmware=";
url += GIT_VERSION;
char urlAsChar[URL_BUF_SIZE];
url.toCharArray(urlAsChar, URL_BUF_SIZE);
Serial.printf("checking for new OTA update @ %s\n", urlAsChar);
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);
esp_http_client_config_t config = {};
config.url = urlAsChar;
esp_err_t 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");
}
esp_http_client_config_t config = {};
config.url = urlAsChar;
OtaUpdateOutcome ret = attemptToPerformOta(&config);
Serial.println(ret);
if (this->callback) {
switch (ret) {
case OtaUpdateOutcome::UPDATE_PERFORMED:
this->callback(OtaState::OTA_STATE_SUCCESS, "");
break;
case OtaUpdateOutcome::UDPATE_SKIPPED:
this->callback(OtaState::OTA_STATE_SKIP, "");
break;
case OtaUpdateOutcome::ALREADY_UP_TO_DATE:
this->callback(OtaState::OTA_STATE_UP_TO_DATE, "");
break;
case OtaUpdateOutcome::UPDATE_FAILED:
this->callback(OtaState::OTA_STATE_FAIL, "");
break;
default:
break;
}
}
}
void setHandlerCallback(OtaHandlerCallback_t callback) {
this->callback = callback;
}
private:
OtaHandlerCallback_t callback;
int 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;
}
OtaUpdateOutcome attemptToPerformOta(const esp_http_client_config_t *config) {
esp_http_client_handle_t client = esp_http_client_init(config);
if (client == NULL) {
Serial.println("Failed to initialize HTTP connection");
return OtaUpdateOutcome::UPDATE_FAILED;
}
esp_err_t err = esp_http_client_open(client, 0);
if (err != ESP_OK) {
esp_http_client_cleanup(client);
Serial.printf("Failed to open HTTP connection: %s\n", esp_err_to_name(err));
return -1;
}
esp_http_client_fetch_headers(client);
esp_err_t err = esp_http_client_open(client, 0);
if (err != ESP_OK) {
esp_http_client_cleanup(client);
Serial.printf("Failed to open HTTP connection: %s\n",
esp_err_to_name(err));
return OtaUpdateOutcome::UPDATE_FAILED;
}
esp_http_client_fetch_headers(client);
esp_ota_handle_t update_handle = 0;
const esp_partition_t *update_partition = NULL;
Serial.println("Starting OTA ...");
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;
}
Serial.printf("Writing to partition subtype %d at offset 0x%x\n",
update_partition->subtype, update_partition->address);
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;
}
err = esp_ota_begin(update_partition, OTA_SIZE_UNKNOWN, &update_handle);
if (err != ESP_OK) {
Serial.printf("esp_ota_begin failed, error=%d\n", err);
cleanupHttp(client);
return err;
}
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;
}
esp_ota_handle_t update_handle = 0;
const esp_partition_t *update_partition = NULL;
Serial.println("Starting OTA update ...");
update_partition = esp_ota_get_next_update_partition(NULL);
if (update_partition == NULL) {
Serial.println("Passive OTA partition not found");
cleanupHttp(client);
return OtaUpdateOutcome::UPDATE_FAILED;
}
Serial.printf("Writing to partition subtype %d at offset 0x%x\n",
update_partition->subtype, update_partition->address);
int binary_file_len = 0;
while (1) {
int data_read = esp_http_client_read(client, upgrade_data_buf, OTA_BUF_SIZE);
if (data_read == 0) {
Serial.println("Connection closed, all data received");
break;
}
if (data_read < 0) {
Serial.println("Data read error");
break;
}
if (data_read > 0) {
ota_write_err = esp_ota_write( update_handle, (const void *)upgrade_data_buf, data_read);
if (ota_write_err != ESP_OK) {
break;
}
binary_file_len += data_read;
// Serial.printf("Written image length %d\n", binary_file_len);
}
err = esp_ota_begin(update_partition, OTA_SIZE_UNKNOWN, &update_handle);
if (err != ESP_OK) {
Serial.printf("esp_ota_begin failed, error=%d\n", err);
cleanupHttp(client);
return OtaUpdateOutcome::UPDATE_FAILED;
}
esp_err_t ota_write_err = ESP_OK;
char *upgrade_data_buf = (char *)malloc(OTA_BUF_SIZE);
if (!upgrade_data_buf) {
Serial.println("Couldn't allocate memory for data buffer");
return OtaUpdateOutcome::UPDATE_FAILED;
}
int binary_file_len = 0;
int totalSize = esp_http_client_get_content_length(client);
Serial.println("File size: " + String(totalSize) + String(" bytes"));
// Show display start update new firmware.
if (this->callback) {
this->callback(OtaState::OTA_STATE_BEGIN, "");
}
// Download file and write new firmware to OTA partition
uint32_t lastUpdate = millis();
while (1) {
int data_read =
esp_http_client_read(client, upgrade_data_buf, OTA_BUF_SIZE);
if (data_read == 0) {
if (this->callback) {
this->callback(OtaState::OTA_STATE_PROCESSING, String(100));
}
free(upgrade_data_buf);
cleanupHttp(client);
Serial.printf("# of bytes written: %d\n", binary_file_len);
esp_err_t ota_end_err = esp_ota_end(update_handle);
Serial.println("Connection closed, all data received");
break;
}
if (data_read < 0) {
Serial.println("Data read error");
if (this->callback) {
this->callback(OtaState::OTA_STATE_FAIL, "");
}
break;
}
if (data_read > 0) {
ota_write_err = esp_ota_write(
update_handle, (const void *)upgrade_data_buf, data_read);
if (ota_write_err != ESP_OK) {
Serial.printf("Error: esp_ota_write failed! err=0x%d\n", err);
return ota_write_err;
} 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;
if (this->callback) {
this->callback(OtaState::OTA_STATE_FAIL, "");
}
break;
}
binary_file_len += data_read;
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;
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();
}
return 0;
}
}
free(upgrade_data_buf);
cleanupHttp(client);
Serial.printf("# of bytes written: %d\n", binary_file_len);
esp_err_t ota_end_err = esp_ota_end(update_handle);
if (ota_write_err != ESP_OK) {
Serial.printf("Error: esp_ota_write failed! err=0x%d\n", err);
return OtaUpdateOutcome::UPDATE_FAILED;
} else if (ota_end_err != ESP_OK) {
Serial.printf("Error: esp_ota_end failed! err=0x%d. Image is invalid",
ota_end_err);
return OtaUpdateOutcome::UPDATE_FAILED;
}
void cleanupHttp(esp_http_client_handle_t client) {
esp_http_client_close(client);
esp_http_client_cleanup(client);
err = esp_ota_set_boot_partition(update_partition);
if (err != ESP_OK) {
Serial.printf("esp_ota_set_boot_partition failed! err=0x%d\n", err);
return OtaUpdateOutcome::UPDATE_FAILED;
}
return OtaUpdateOutcome::UPDATE_PERFORMED;
}
void cleanupHttp(esp_http_client_handle_t client) {
esp_http_client_close(client);
esp_http_client_cleanup(client);
}
};
#endif

View File

@ -1,5 +1,5 @@
name=AirGradient Air Quality Sensor
version=3.1.0-beta.1
version=3.1.3
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.

View File

@ -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,34 @@ board_build.partitions = partitions.csv
monitor_speed = 115200
lib_deps =
aglib=symlink://../arduino
EEPROM
WebServer
ESPmDNS
FS
SPIFFS
HTTPClient
WiFiClientSecure
Update
DNSServer
monitor_filters = time
[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/TestCO2
; src_dir = examples/TestPM
; src_dir = examples/TestSht

View File

@ -34,7 +34,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
@ -68,11 +69,17 @@ bool AgApiClient::fetchServerConfiguration(void) {
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();
@ -143,6 +150,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; }
/**

View File

@ -23,6 +23,7 @@ private:
bool getConfigFailed;
bool postToServerFailed;
bool notAvailableOnDashboard = false; // Device not setup on Airgradient cloud dashboard.
public:
AgApiClient(Stream &stream, Configuration &config);
@ -33,6 +34,7 @@ public:
bool postToServer(String data);
bool isFetchConfigureFailed(void);
bool isPostToServerFailed(void);
bool isNotAvailableOnDashboard(void);
void setAirGradient(AirGradient *ag);
bool sendPing(int rssi, int bootCount);
};

File diff suppressed because it is too large Load Diff

View File

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

View File

@ -1,6 +1,5 @@
#include "AgOledDisplay.h"
#include "Libraries/U8g2/src/U8g2lib.h"
#include "Libraries/QRCode/src/qrcode.h"
/** Cast U8G2 */
#define DISP() ((U8G2_SH1106_128X64_NONAME_F_HW_I2C *)(this->u8g2))
@ -50,6 +49,16 @@ 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
*
@ -94,6 +103,13 @@ bool OledDisplay::begin(void) {
return false;
}
/** Show low brightness on startup. then it's completely turn off on main
* application */
int brightness = config.getDisplayBrightness();
if(brightness == 0) {
setBrightness(1);
}
isBegin = true;
logInfo("begin");
return true;
@ -137,6 +153,10 @@ void OledDisplay::setText(String &line1, String &line2, String &line3) {
*/
void OledDisplay::setText(const char *line1, const char *line2,
const char *line3) {
if (isDisplayOff) {
return;
}
DISP()->firstPage();
do {
DISP()->setFont(u8g2_font_t0_16_tf);
@ -169,6 +189,10 @@ 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;
}
DISP()->firstPage();
do {
DISP()->setFont(u8g2_font_t0_16_tf);
@ -190,6 +214,10 @@ void OledDisplay::showDashboard(void) { showDashboard(NULL); }
*
*/
void OledDisplay::showDashboard(const char *status) {
if (isDisplayOff) {
return;
}
char strBuf[10];
DISP()->firstPage();
@ -287,24 +315,108 @@ void OledDisplay::showDashboard(const char *status) {
} while (DISP()->nextPage());
}
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 (percent == 0) {
isDisplayOff = true;
// Clear display.
DISP()->firstPage();
do {
} while (DISP()->nextPage());
} else {
isDisplayOff = false;
DISP()->setContrast((127 * percent) / 100);
}
}
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());
}
void OledDisplay::showRebooting(void) {
DISP()->firstPage();
do {
DISP()->setFont(u8g2_font_t0_16_tf);
// setCentralText(20, "Firmware Update");
setCentralText(40, "Rebooting...");
// setCentralText(60, String("Retry after 24h"));
} while (DISP()->nextPage());
}

View File

@ -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,14 @@ public:
const char *line4);
void showDashboard(void);
void showDashboard(const char *status);
void showWiFiQrCode(String content, String label);
void setBrightness(int percent);
void showFirmwareUpdateVersion(String version);
void showFirmwareUpdateProgress(int percent);
void showFirmwareUpdateSuccess(int count);
void showFirmwareUpdateFailed(void);
void showFirmwareUpdateSkipped(void);
void showFirmwareUpdateUpToDate(void);
void showRebooting(void);
};
#endif /** _AG_OLED_DISPLAY_H_ */

View File

@ -7,6 +7,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, 255, 0 /** Yellow */
#define RGB_COLOR_O 255, 165, 0 /** Organge */
#define RGB_COLOR_P 160, 32, 240 /** Purple */
/**
* @brief Animation LED bar with color
*
@ -63,80 +69,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 +141,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);
}
}
@ -319,6 +314,10 @@ void StateMachine::ledBarTest(void) {
}
}
void StateMachine::ledBarPowerUpTest(void) {
ledBarRunTest();
}
void StateMachine::ledBarRunTest(void) {
disp.setText("LED Test", "running", ".....");
runLedTest('r');
@ -401,6 +400,9 @@ StateMachine::~StateMachine() {}
void StateMachine::displayHandle(AgStateMachineState state) {
// Ignore handle if not ONE_INDOOR board
if (!ag->isOne()) {
if (state == AgStateMachineCo2Calibration) {
co2Calibration();
}
return;
}
@ -414,19 +416,12 @@ 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);
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;
@ -468,15 +463,19 @@ void StateMachine::displayHandle(AgStateMachineState state) {
break;
}
case AgStateMachineSensorConfigFailed: {
uint32_t ms = (uint32_t)(millis() - addToDashboardTime);
if (ms >= 5000) {
addToDashboardTime = millis();
if (addToDashBoard) {
disp.showDashboard("Add to Dashboard");
} else {
disp.showDashboard(ag->deviceId().c_str());
if (addToDashBoard) {
uint32_t ms = (uint32_t)(millis() - addToDashboardTime);
if (ms >= 5000) {
addToDashboardTime = millis();
if (addToDashBoardToggle) {
disp.showDashboard("Add to Dashboard");
} else {
disp.showDashboard(ag->deviceId().c_str());
}
addToDashBoardToggle = !addToDashBoardToggle;
}
addToDashBoard = !addToDashBoard;
} else {
disp.showDashboard("");
}
break;
}
@ -503,8 +502,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; }
@ -706,6 +708,8 @@ void StateMachine::handleLeds(AgStateMachineState state) {
case AgStateMachineLedBarTest:
ledBarTest();
break;
case AgStateMachineLedBarPowerUpTest:
ledBarPowerUpTest();
default:
break;
}
@ -759,3 +763,7 @@ void StateMachine::executeCo2Calibration(void) {
void StateMachine::executeLedBarTest(void) {
handleLeds(AgStateMachineLedBarTest);
}
void StateMachine::executeLedBarPowerUpTest(void) {
handleLeds(AgStateMachineLedBarPowerUpTest);
}

View File

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

View File

@ -140,7 +140,8 @@ String Measurements::toString(bool localServer, AgFirmwareMode fwMode, int rssi,
root["channels"]["1"]["rhumCompensated"] =
(int)ag->pms5003t_1.humidityCompensated(this->hum_1);
}
} else if (config->hasSensorPMS2) {
}
if (config->hasSensorPMS2) {
root["channels"]["2"]["pm01"] = this->pm01_2;
root["channels"]["2"]["pm02"] = this->pm25_2;
root["channels"]["2"]["pm10"] = this->pm10_2;
@ -172,6 +173,7 @@ String Measurements::toString(bool localServer, AgFirmwareMode fwMode, int rssi,
root["noxRaw"] = this->NOxRaw;
}
}
root["boot"] = bootCount;
root["bootCount"] = bootCount;
if (localServer) {

View File

@ -53,6 +53,7 @@ bool WifiConnector::connect(void) {
WIFI()->setAPCallback([this](WiFiManager *obj) { _wifiApCallback(); });
WIFI()->setSaveConfigCallback([this]() { _wifiSaveConfig(); });
WIFI()->setSaveParamsCallback([this]() { _wifiSaveParamCallback(); });
WIFI()->setConfigPortalTimeoutCallback([this](){});
if (ag->isOne()) {
disp.setText("Connecting to", "WiFi", "...");
} else {
@ -245,6 +246,7 @@ void WifiConnector::_wifiSaveParamCallback(void) {
bool WifiConnector::_wifiConfigPortalActive(void) {
return WIFI()->getConfigPortalActive();
}
void WifiConnector::_wifiTimeoutCallback(void) { connectorTimeout = true; }
#endif
/**
* @brief Process WiFiManager connection
@ -339,3 +341,24 @@ 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; }

View File

@ -24,6 +24,7 @@ private:
bool hasConfig;
uint32_t lastRetry;
bool hasPortalConfig = false;
bool connectorTimeout = false;
bool wifiClientConnected(void);
@ -44,12 +45,15 @@ public:
void _wifiSaveConfig(void);
void _wifiSaveParamCallback(void);
bool _wifiConfigPortalActive(void);
void _wifiTimeoutCallback(void);
#endif
void _wifiProcess();
bool isConnected(void);
void reset(void);
int RSSI(void);
String localIpStr(void);
bool hasConfigurated(void);
bool isConfigurePorttalTimeout(void);
};
#endif /** _AG_WIFI_CONNECTOR_H_ */

View File

@ -59,6 +59,10 @@ enum AgStateMachineState {
/* LED bar testing */
AgStateMachineLedBarTest,
AgStateMachineLedBarPowerUpTest,
/** OTA perform, show display status */
AgStateMachineOtaPerform,
/** LED: Show working state.
* Display: Show dashboard */

View File

@ -1 +0,0 @@
.DS_Store

View File

@ -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.

View File

@ -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.

View File

@ -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() {
}

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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) {
}
*/

View File

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

View File

@ -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);
}
/**

View File

@ -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);